@phnx-labs/agents-cli 1.22.55 → 1.22.57
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 +20 -0
- package/README.md +4 -4
- package/dist/bootstrap.js +3 -1
- package/dist/cli/command-registry.d.ts +0 -1
- package/dist/cli/command-registry.js +0 -3
- package/dist/commands/exec.js +1 -1
- package/dist/commands/hooks.js +4 -4
- package/dist/commands/insights.d.ts +7 -5
- package/dist/commands/insights.js +16 -9
- package/dist/commands/perf.d.ts +16 -7
- package/dist/commands/perf.js +29 -20
- package/dist/commands/rules.js +1 -1
- package/dist/commands/share.js +7 -6
- package/dist/commands/ssh.js +24 -14
- package/dist/commands/trash.d.ts +2 -2
- package/dist/commands/trash.js +2 -6
- package/dist/commands/versions.d.ts +2 -2
- package/dist/commands/versions.js +1 -10
- package/dist/commands/view.d.ts +2 -2
- package/dist/commands/view.js +7 -6
- package/dist/index.d.ts +1 -0
- package/dist/index.js +9 -0
- package/dist/lib/accounting/usage-ingest.d.ts +1 -0
- package/dist/lib/accounting/usage-ingest.js +75 -0
- package/dist/lib/accounting/usage-sync.d.ts +69 -0
- package/dist/lib/accounting/usage-sync.js +129 -0
- package/dist/lib/accounting/usage.d.ts +48 -2
- package/dist/lib/accounting/usage.js +72 -1
- package/dist/lib/agent-spec/agents.js +1 -1
- package/dist/lib/analytics/mix-commands.d.ts +8 -7
- package/dist/lib/analytics/mix-commands.js +50 -73
- package/dist/lib/daemon/daemon.js +5 -0
- package/dist/lib/daemon/runner.js +9 -8
- package/dist/lib/daemon/usage-sync-service.d.ts +21 -0
- package/dist/lib/daemon/usage-sync-service.js +36 -0
- package/dist/lib/daemon-services.d.ts +1 -1
- package/dist/lib/daemon-services.js +5 -0
- package/dist/lib/device-config.d.ts +17 -6
- package/dist/lib/device-config.js +25 -11
- package/dist/lib/devices/pool.d.ts +4 -3
- package/dist/lib/devices/pool.js +13 -5
- package/dist/lib/exec.d.ts +6 -41
- package/dist/lib/exec.js +6 -41
- package/dist/lib/git.d.ts +13 -1
- package/dist/lib/git.js +36 -7
- package/dist/lib/harness/adapter.d.ts +7 -7
- package/dist/lib/harness/adapters/claude.js +3 -2
- package/dist/lib/hosts/remote-cmd.d.ts +9 -0
- package/dist/lib/hosts/remote-cmd.js +22 -0
- package/dist/lib/overdue.js +7 -37
- package/dist/lib/perf/db.d.ts +1 -1
- package/dist/lib/perf/db.js +1 -1
- package/dist/lib/scheduler.d.ts +21 -2
- package/dist/lib/scheduler.js +28 -5
- package/dist/lib/scheduling/routines.d.ts +21 -0
- package/dist/lib/scheduling/routines.js +56 -0
- package/dist/lib/session/active.d.ts +3 -31
- package/dist/lib/session/active.js +8 -68
- package/dist/lib/session/db.d.ts +4 -35
- package/dist/lib/session/db.js +4 -35
- package/dist/lib/session/discover.d.ts +6 -58
- package/dist/lib/session/discover.js +5 -43
- package/dist/lib/session/parse.d.ts +1 -19
- package/dist/lib/session/parse.js +2 -15
- package/dist/lib/share/provision.d.ts +3 -2
- package/dist/lib/share/provision.js +9 -4
- package/dist/lib/share/worker-template.d.ts +24 -1
- package/dist/lib/share/worker-template.js +65 -84
- package/dist/lib/startup/command-registry.d.ts +8 -2
- package/dist/lib/startup/command-registry.js +12 -4
- package/package.json +1 -1
package/dist/lib/session/db.js
CHANGED
|
@@ -3390,15 +3390,9 @@ export function topSessionsByCost(n, options = {}) {
|
|
|
3390
3390
|
durationMs: r.duration_ms ?? 0,
|
|
3391
3391
|
}));
|
|
3392
3392
|
}
|
|
3393
|
-
/** Look up a single session by its unique ID. */
|
|
3394
3393
|
/**
|
|
3395
|
-
*
|
|
3396
|
-
*
|
|
3397
|
-
* `getSessionById` would re-`prepare` a `SELECT *` and materialize a full
|
|
3398
|
-
* `SessionMeta` per id to read it — mirrors {@link findSessionsByShortIds}'s
|
|
3399
|
-
* single-round-trip pattern. Ids absent from the index are simply absent from
|
|
3400
|
-
* the map. Best-effort: an unavailable DB yields an empty map, so the live view
|
|
3401
|
-
* still renders (the caller then leaves rows attributed to this box).
|
|
3394
|
+
* Read machine attribution in batches without materializing full sessions.
|
|
3395
|
+
* Failure is best-effort so the live view can still render local attribution.
|
|
3402
3396
|
*/
|
|
3403
3397
|
export function findSessionMachinesByIds(ids) {
|
|
3404
3398
|
const out = new Map();
|
|
@@ -3429,20 +3423,7 @@ export function getSessionById(id) {
|
|
|
3429
3423
|
const row = db.prepare(`SELECT * FROM sessions WHERE id = ?`).get(id);
|
|
3430
3424
|
return row ? rowToMeta(row) : null;
|
|
3431
3425
|
}
|
|
3432
|
-
/**
|
|
3433
|
-
* Resolve a full-or-partial session id against the index, exact-first then
|
|
3434
|
-
* prefix — the DB-backed equivalent of resolveSessionById() that runs over the
|
|
3435
|
-
* SQLite table instead of a pre-loaded array. Matches both the full id and the
|
|
3436
|
-
* short id. An exact hit short-circuits so a complete id never also drags in its
|
|
3437
|
-
* prefix siblings. `scope` narrows by agent / version / project (cwd) so an
|
|
3438
|
-
* ambiguous prefix disambiguates against the caller's context.
|
|
3439
|
-
*
|
|
3440
|
-
* Routes through the full querySessions existence check (NOT skipExistenceCheck)
|
|
3441
|
-
* on purpose (RUSH-2436): that check now KEEPS a file-gone session whose user
|
|
3442
|
-
* turns still live in session_text (flagged archived) and only suppresses a
|
|
3443
|
-
* contentless phantom — so `agents sessions <id>` resolves an archived session
|
|
3444
|
-
* instead of failing with "No session found", while a phantom id still misses.
|
|
3445
|
-
*/
|
|
3426
|
+
/** Exact ids win over prefixes; the normal existence check preserves archived content but excludes phantoms. */
|
|
3446
3427
|
export function findSessionsById(idQuery, scope = {}) {
|
|
3447
3428
|
const q = idQuery.trim();
|
|
3448
3429
|
if (!q)
|
|
@@ -3452,19 +3433,7 @@ export function findSessionsById(idQuery, scope = {}) {
|
|
|
3452
3433
|
return exact;
|
|
3453
3434
|
return querySessions({ ...scope, idPrefix: q });
|
|
3454
3435
|
}
|
|
3455
|
-
/**
|
|
3456
|
-
* Batch-resolve many 8-char short ids to their sessions in ONE indexed query.
|
|
3457
|
-
* The live-scan path (listTmuxAgentSessions) turns every `ag-<agent>-<shortid>`
|
|
3458
|
-
* tmux pane name back into a full session id this way, so it pays a single
|
|
3459
|
-
* `short_id IN (…)` round-trip per scan instead of N per-pane lookups.
|
|
3460
|
-
*
|
|
3461
|
-
* Returns a map keyed by short_id (lowercased). Short ids are the first 8 chars
|
|
3462
|
-
* of the lowercase session UUID (deriveShortId), so a lowercased `IN` matches and
|
|
3463
|
-
* still uses idx_sessions_short_id. When several sessions share a short id — only
|
|
3464
|
-
* time-ordered ids (ULID/UUIDv7) ever collide; random UUIDv4 short ids are unique
|
|
3465
|
-
* in practice — the most-recently-active one wins (the caller can further
|
|
3466
|
-
* disambiguate by cwd).
|
|
3467
|
-
*/
|
|
3436
|
+
/** Batch-resolve pane short ids; on collision the most recently active session wins. */
|
|
3468
3437
|
export function findSessionsByShortIds(shortIds) {
|
|
3469
3438
|
const out = new Map();
|
|
3470
3439
|
const uniq = [...new Set(shortIds.map((s) => s.trim().toLowerCase()).filter(Boolean))];
|
|
@@ -54,14 +54,7 @@ export interface DiscoverOptions {
|
|
|
54
54
|
idExact?: string;
|
|
55
55
|
/** Session id prefix — a targeted indexed lookup with no scan (RUSH-2477). */
|
|
56
56
|
idPrefix?: string;
|
|
57
|
-
/**
|
|
58
|
-
* Cold-miss repair: when another live process already holds the scan claim,
|
|
59
|
-
* wait (bounded) for that in-flight scan to finish before reading the index,
|
|
60
|
-
* instead of returning the pre-scan snapshot (RUSH-2682). A caller repairing a
|
|
61
|
-
* "not indexed yet" miss wants the fresh result the concurrent scan is about to
|
|
62
|
-
* write, not the stale read that just missed. Ignored when THIS process wins
|
|
63
|
-
* the claim (it scans itself) or no scan is in progress.
|
|
64
|
-
*/
|
|
57
|
+
/** On a cold miss, briefly await the scan already holding the single-flight claim. */
|
|
65
58
|
waitForScan?: boolean;
|
|
66
59
|
}
|
|
67
60
|
/** Progress report emitted during incremental scanning. */
|
|
@@ -181,41 +174,15 @@ export declare function discoverSessions(options?: DiscoverOptions): Promise<Ses
|
|
|
181
174
|
interface IncrementalScanResult {
|
|
182
175
|
/** True when this process won the single-flight claim and ran the scan. */
|
|
183
176
|
claimed: boolean;
|
|
184
|
-
/**
|
|
185
|
-
* Transcripts parsed this scan — i.e. those whose (mtime, size) changed. Zero
|
|
186
|
-
* is the steady state on an idle box and does NOT mean the scan was skipped;
|
|
187
|
-
* read `claimed` for that.
|
|
188
|
-
*
|
|
189
|
-
* Twelve of the 13 SESSION_AGENTS contribute, including OpenCode, whose
|
|
190
|
-
* scanner filters to sessions whose own per-session stamp changed and reports
|
|
191
|
-
* that batch — so a tick whose only changed sessions live there no longer
|
|
192
|
-
* reports 0 (RUSH-2691). OpenClaw is the exception and contributes nothing:
|
|
193
|
-
* its scanner has no change detection to report (a TTL gate, a fresh stamp
|
|
194
|
-
* every run, and an entry list rebuilt as the current inventory), so counting
|
|
195
|
-
* it would overstate rather than measure. See `scanOpenClawIncremental`.
|
|
196
|
-
*/
|
|
177
|
+
/** Changed transcripts parsed; zero with `claimed: true` is a successful no-op scan. */
|
|
197
178
|
scanned: number;
|
|
198
179
|
}
|
|
199
|
-
/**
|
|
200
|
-
* The write half of {@link discoverSessions}: claim the single-flight scan slot,
|
|
201
|
-
* incrementally index this host's transcript dirs, and report what was parsed.
|
|
202
|
-
*
|
|
203
|
-
* Split out so a caller that only wants the index refreshed — the daemon's warm
|
|
204
|
-
* tick — can run it WITHOUT the listing query `discoverSessions` ends with. That
|
|
205
|
-
* query is not free: it applies a cwd filter, runs the `archived_at`-writing
|
|
206
|
-
* existence check, and can issue a Linear fetch, none of which index anything
|
|
207
|
-
* (RUSH-2691). Keeping one implementation here is also what stops the tick and
|
|
208
|
-
* the foreground path from drifting apart.
|
|
209
|
-
*/
|
|
180
|
+
/** Separate write half so daemon warming does not pay for listing or external enrichment. */
|
|
210
181
|
export declare function scanSessionsIncremental(options?: {
|
|
211
182
|
agent?: SessionAgentId;
|
|
212
183
|
onProgress?: (p: ScanProgress) => void;
|
|
213
184
|
}): Promise<IncrementalScanResult>;
|
|
214
|
-
/**
|
|
215
|
-
* Poll until no live process holds the scan claim, or the bound elapses
|
|
216
|
-
* (RUSH-2682). Bounded so a wedged/slow scan can never hang a foreground preview.
|
|
217
|
-
* Exported for the cold-miss repair test.
|
|
218
|
-
*/
|
|
185
|
+
/** Bounded wait so a wedged scan cannot hang a foreground cold-miss repair. */
|
|
219
186
|
export declare function waitForScanToSettle(timeoutMs?: number, pollMs?: number): Promise<boolean>;
|
|
220
187
|
/** Read the current SQLite snapshot without scanning or parsing transcript files. */
|
|
221
188
|
export declare function queryIndexedSessions(options?: DiscoverOptions, indexedOptions?: {
|
|
@@ -223,27 +190,8 @@ export declare function queryIndexedSessions(options?: DiscoverOptions, indexedO
|
|
|
223
190
|
skipExistenceCheck?: boolean;
|
|
224
191
|
}): Promise<SessionMeta[]>;
|
|
225
192
|
/**
|
|
226
|
-
* Resolve
|
|
227
|
-
*
|
|
228
|
-
* A plain WAL read through `queryIndexedSessions` — same origin-machine
|
|
229
|
-
* attribution and managed scoping every indexed read gets — with NO incremental
|
|
230
|
-
* discovery scan (so none of `tryClaimScan`/`releaseScan`'s `BEGIN IMMEDIATE`
|
|
231
|
-
* writer lock) and NO fleet SSH fan-out. This is the crash-restart storm path
|
|
232
|
-
* (RUSH-2477): dozens of `sessions resume <id>` at once for a known local id must
|
|
233
|
-
* each be a cheap read, never a writer-lock contender or a dial into the
|
|
234
|
-
* not-yet-up tailnet. Exact id first, then prefix (matching `findSessionsById`),
|
|
235
|
-
* so a complete id never also drags in its prefix siblings. Returns `[]` on a
|
|
236
|
-
* genuine local miss, leaving the caller to fall back to the fleet resolver.
|
|
237
|
-
*
|
|
238
|
-
* The existence check is left ON (`skipExistenceCheck: false`), exactly as the old
|
|
239
|
-
* `discoverSessions` path and `findSessionsById` do (RUSH-2436): it KEEPS a
|
|
240
|
-
* file-gone session whose user turns still live in `session_text` (flagged
|
|
241
|
-
* archived) and SUPPRESSES a contentless phantom — so a phantom id misses here and
|
|
242
|
-
* falls through to the fleet resolver, instead of resolving to a row with no real
|
|
243
|
-
* transcript to resume. For a present transcript — the storm's actual case, since
|
|
244
|
-
* the crashed tabs' files are on disk — the check does no writes, so the lock-free
|
|
245
|
-
* guarantee holds; it only writes to (un)archive a genuinely missing or resurrected
|
|
246
|
-
* file, which is not the 20-at-once resume path.
|
|
193
|
+
* Resolve locally without scanning or fleet I/O, keeping concurrent crash recovery lock-light.
|
|
194
|
+
* The existence check preserves archived content while rejecting transcriptless phantoms.
|
|
247
195
|
*/
|
|
248
196
|
export declare function resolveIndexedSessionById(idQuery: string): Promise<SessionMeta[]>;
|
|
249
197
|
/**
|
|
@@ -131,12 +131,7 @@ async function applyJsonlAppend(filePath, fromOffset, wasDroppingOversizedLine,
|
|
|
131
131
|
* between `agents sessions` calls but is still being written to.
|
|
132
132
|
*/
|
|
133
133
|
const HOT_FILE_WINDOW_MS = 600_000;
|
|
134
|
-
/**
|
|
135
|
-
* Kill-switch: set `AGENTS_SESSIONS_NO_DIR_LEDGER=1` to force the old full-walk
|
|
136
|
-
* path (readdir + per-file stat every dir, every run — the pre-A-2 behavior),
|
|
137
|
-
* skipping the dir_ledger short-circuit entirely. One env var reverts a field
|
|
138
|
-
* regression to today's behavior.
|
|
139
|
-
*/
|
|
134
|
+
/** Emergency kill-switch for the directory-ledger optimization. */
|
|
140
135
|
function dirLedgerDisabled() {
|
|
141
136
|
const v = process.env.AGENTS_SESSIONS_NO_DIR_LEDGER;
|
|
142
137
|
return v === '1' || v === 'true';
|
|
@@ -175,17 +170,7 @@ export async function discoverSessions(options) {
|
|
|
175
170
|
skipExistenceCheck: options?.skipExistenceCheck ?? false,
|
|
176
171
|
});
|
|
177
172
|
}
|
|
178
|
-
/**
|
|
179
|
-
* The write half of {@link discoverSessions}: claim the single-flight scan slot,
|
|
180
|
-
* incrementally index this host's transcript dirs, and report what was parsed.
|
|
181
|
-
*
|
|
182
|
-
* Split out so a caller that only wants the index refreshed — the daemon's warm
|
|
183
|
-
* tick — can run it WITHOUT the listing query `discoverSessions` ends with. That
|
|
184
|
-
* query is not free: it applies a cwd filter, runs the `archived_at`-writing
|
|
185
|
-
* existence check, and can issue a Linear fetch, none of which index anything
|
|
186
|
-
* (RUSH-2691). Keeping one implementation here is also what stops the tick and
|
|
187
|
-
* the foreground path from drifting apart.
|
|
188
|
-
*/
|
|
173
|
+
/** Separate write half so daemon warming does not pay for listing or external enrichment. */
|
|
189
174
|
export async function scanSessionsIncremental(options) {
|
|
190
175
|
// Touch the DB so the schema is ready and connection is cached for this run.
|
|
191
176
|
getDB();
|
|
@@ -222,11 +207,7 @@ export async function scanSessionsIncremental(options) {
|
|
|
222
207
|
scanned += n;
|
|
223
208
|
return { claimed: true, scanned };
|
|
224
209
|
}
|
|
225
|
-
/**
|
|
226
|
-
* Poll until no live process holds the scan claim, or the bound elapses
|
|
227
|
-
* (RUSH-2682). Bounded so a wedged/slow scan can never hang a foreground preview.
|
|
228
|
-
* Exported for the cold-miss repair test.
|
|
229
|
-
*/
|
|
210
|
+
/** Bounded wait so a wedged scan cannot hang a foreground cold-miss repair. */
|
|
230
211
|
export async function waitForScanToSettle(timeoutMs = WAIT_FOR_SCAN_TIMEOUT_MS, pollMs = WAIT_FOR_SCAN_POLL_MS) {
|
|
231
212
|
const deadline = Date.now() + timeoutMs;
|
|
232
213
|
while (scanInProgressByLivePid()) {
|
|
@@ -259,27 +240,8 @@ export async function queryIndexedSessions(options, indexedOptions = {}) {
|
|
|
259
240
|
return scopeToManaged(sessions, agents, options);
|
|
260
241
|
}
|
|
261
242
|
/**
|
|
262
|
-
* Resolve
|
|
263
|
-
*
|
|
264
|
-
* A plain WAL read through `queryIndexedSessions` — same origin-machine
|
|
265
|
-
* attribution and managed scoping every indexed read gets — with NO incremental
|
|
266
|
-
* discovery scan (so none of `tryClaimScan`/`releaseScan`'s `BEGIN IMMEDIATE`
|
|
267
|
-
* writer lock) and NO fleet SSH fan-out. This is the crash-restart storm path
|
|
268
|
-
* (RUSH-2477): dozens of `sessions resume <id>` at once for a known local id must
|
|
269
|
-
* each be a cheap read, never a writer-lock contender or a dial into the
|
|
270
|
-
* not-yet-up tailnet. Exact id first, then prefix (matching `findSessionsById`),
|
|
271
|
-
* so a complete id never also drags in its prefix siblings. Returns `[]` on a
|
|
272
|
-
* genuine local miss, leaving the caller to fall back to the fleet resolver.
|
|
273
|
-
*
|
|
274
|
-
* The existence check is left ON (`skipExistenceCheck: false`), exactly as the old
|
|
275
|
-
* `discoverSessions` path and `findSessionsById` do (RUSH-2436): it KEEPS a
|
|
276
|
-
* file-gone session whose user turns still live in `session_text` (flagged
|
|
277
|
-
* archived) and SUPPRESSES a contentless phantom — so a phantom id misses here and
|
|
278
|
-
* falls through to the fleet resolver, instead of resolving to a row with no real
|
|
279
|
-
* transcript to resume. For a present transcript — the storm's actual case, since
|
|
280
|
-
* the crashed tabs' files are on disk — the check does no writes, so the lock-free
|
|
281
|
-
* guarantee holds; it only writes to (un)archive a genuinely missing or resurrected
|
|
282
|
-
* file, which is not the 20-at-once resume path.
|
|
243
|
+
* Resolve locally without scanning or fleet I/O, keeping concurrent crash recovery lock-light.
|
|
244
|
+
* The existence check preserves archived content while rejecting transcriptless phantoms.
|
|
283
245
|
*/
|
|
284
246
|
export async function resolveIndexedSessionById(idQuery) {
|
|
285
247
|
const q = idQuery.trim();
|
|
@@ -28,28 +28,10 @@ export declare function sanitizeEvents(events: SessionEvent[]): void;
|
|
|
28
28
|
* ERR_STRING_TOO_LONG ceiling.
|
|
29
29
|
*/
|
|
30
30
|
export declare function safeReadSessionFile(filePath: string, maxBytes?: number): string;
|
|
31
|
-
/**
|
|
32
|
-
* Auto-detect agent type from file path and parse the session.
|
|
33
|
-
*/
|
|
34
31
|
export interface ParseSessionOptions {
|
|
35
32
|
/** Keep normalized tool results compact by default; renderers can request full output. */
|
|
36
33
|
maxToolOutputChars?: number;
|
|
37
|
-
/**
|
|
38
|
-
* Emit an `interrupt` event where the transcript records `[Request interrupted`.
|
|
39
|
-
*
|
|
40
|
-
* OFF by default, deliberately. That marker is not a user message, and the default
|
|
41
|
-
* event array is a versioned consumer contract: `agents sessions <id> --json`
|
|
42
|
-
* serializes it verbatim (see render.ts, issue #743), `computeSummaryStats` folds
|
|
43
|
-
* every event's timestamp into the session duration, and the live-state reader and
|
|
44
|
-
* tail renderer inspect fixed-size windows of the last N events. Emitting it
|
|
45
|
-
* unconditionally changed all four — a measured 12x duration swing on one real
|
|
46
|
-
* transcript, a new object in a published payload, and an eviction from the
|
|
47
|
-
* 12-event rate-limit window whose trigger shape (a trailing interrupt) is exactly
|
|
48
|
-
* a session the user just cancelled.
|
|
49
|
-
*
|
|
50
|
-
* `agents insights` opts in: an interruption is a real friction signal, and dropping
|
|
51
|
-
* it outright is what made it unrecoverable.
|
|
52
|
-
*/
|
|
34
|
+
/** Opt-in because interrupts are not user messages and would change the published event stream. */
|
|
53
35
|
includeInterrupts?: boolean;
|
|
54
36
|
}
|
|
55
37
|
export declare function parseSession(filePath: string, agent?: SessionAgentId, opts?: ParseSessionOptions): SessionEvent[];
|
|
@@ -117,14 +117,7 @@ function truncateNormalizedToolOutput(output, maxChars) {
|
|
|
117
117
|
return output;
|
|
118
118
|
return `${output.slice(0, maxChars)}\n\n[Output truncated: ${output.length - maxChars} characters omitted.]`;
|
|
119
119
|
}
|
|
120
|
-
/**
|
|
121
|
-
* Registry-dispatch table: each `SessionAgentId` to its offline transcript
|
|
122
|
-
* parser. Replaces the per-harness `switch` — the harness axis of Move 3. Kept
|
|
123
|
-
* as its own table (not on the HarnessAdapter registry) because its id domain is
|
|
124
|
-
* `SessionAgentId`: `rush` is a session agent with no `AgentId`, and the offline
|
|
125
|
-
* transcript reader is a deliberately separate concern from live team events.
|
|
126
|
-
* The `Record` is total, so a new session harness must add an entry here.
|
|
127
|
-
*/
|
|
120
|
+
/** Separate from HarnessAdapter because offline transcripts include session-only agents such as Rush. */
|
|
128
121
|
const TRANSCRIPT_PARSERS = {
|
|
129
122
|
claude: (filePath, opts) => parseClaude(filePath, opts),
|
|
130
123
|
codex: (filePath) => parseCodex(filePath),
|
|
@@ -146,13 +139,7 @@ export function parseSession(filePath, agent, opts = {}) {
|
|
|
146
139
|
throw new Error(`Cannot detect agent type from path: ${filePath}`);
|
|
147
140
|
}
|
|
148
141
|
const events = TRANSCRIPT_PARSERS[detected](filePath, opts);
|
|
149
|
-
//
|
|
150
|
-
// file gets stripped of terminal escapes here, so renderers downstream can
|
|
151
|
-
// safely splat values into chalk/console output. Same pass flags
|
|
152
|
-
// harness-injected `role=user` scaffolding (Claude `<bash-input>`/`<bash-stdout>`
|
|
153
|
-
// from `!`-prefix runs, `<system-reminder>`, etc.) as `_synthetic` so turn
|
|
154
|
-
// slicing and `--include user` count only genuine user intent — one place,
|
|
155
|
-
// every harness, instead of per-consumer regex.
|
|
142
|
+
// Sanitize untrusted strings and identify synthetic user scaffolding once for every consumer.
|
|
156
143
|
const maxToolOutputChars = opts.maxToolOutputChars ?? 500;
|
|
157
144
|
for (const e of events) {
|
|
158
145
|
if (e.type === 'tool_result' && e.output) {
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { WorkerBundle } from './worker-template.js';
|
|
1
2
|
export declare const SHARE_LIFECYCLE_RULE_ID = "agents-share-expire-objects";
|
|
2
3
|
export declare const SHARE_LIFECYCLE_RETENTION_DAYS = 366;
|
|
3
4
|
interface R2LifecycleRule {
|
|
@@ -50,7 +51,7 @@ export declare function mergeShareLifecycleRule(existing?: R2LifecycleRule[], ru
|
|
|
50
51
|
/** Ensure the share bucket self-cleans old objects. Exact per-link expiry is enforced by the Worker. */
|
|
51
52
|
export declare function configureBucketLifecycle(apiToken: string, accountId: string, bucketName: string, opts?: ProvisionOptions): Promise<void>;
|
|
52
53
|
/** Upload the module Worker with an R2 binding (`BUCKET`). Secrets are set via the Workers Secrets API. */
|
|
53
|
-
export declare function deployWorker(apiToken: string, accountId: string, workerName: string,
|
|
54
|
+
export declare function deployWorker(apiToken: string, accountId: string, workerName: string, worker: string | WorkerBundle, bucketName: string, opts?: ProvisionOptions): Promise<void>;
|
|
54
55
|
/** sha256 of the rendered Worker script, so a deployed endpoint can be compared
|
|
55
56
|
* against the current `worker-template.ts` without redeploying to find out. */
|
|
56
57
|
export declare function hashWorkerScript(script: string): string;
|
|
@@ -100,7 +101,7 @@ export type UpdateWorkerOpts = ProvisionOptions & {
|
|
|
100
101
|
* re-setting the secret to its own value after every deploy gets the same
|
|
101
102
|
* outcome without depending on an unverified upload-time flag.
|
|
102
103
|
*/
|
|
103
|
-
export declare function updateWorker(apiToken: string, accountId: string, workerName: string, bucketName: string,
|
|
104
|
+
export declare function updateWorker(apiToken: string, accountId: string, workerName: string, bucketName: string, worker: string | WorkerBundle, writeToken: string, previousHash: string | undefined, opts?: UpdateWorkerOpts): Promise<UpdateWorkerResult>;
|
|
104
105
|
/** Add/update a secret_text binding using Cloudflare's Workers Secrets API. */
|
|
105
106
|
export declare function putWorkerSecret(apiToken: string, accountId: string, workerName: string, name: string, text: string, opts?: ProvisionOptions): Promise<void>;
|
|
106
107
|
/** Add/update the WRITE_TOKEN binding using Cloudflare's Workers Secrets API. */
|
|
@@ -77,7 +77,7 @@ export async function configureBucketLifecycle(apiToken, accountId, bucketName,
|
|
|
77
77
|
});
|
|
78
78
|
}
|
|
79
79
|
/** Upload the module Worker with an R2 binding (`BUCKET`). Secrets are set via the Workers Secrets API. */
|
|
80
|
-
export async function deployWorker(apiToken, accountId, workerName,
|
|
80
|
+
export async function deployWorker(apiToken, accountId, workerName, worker, bucketName, opts = {}) {
|
|
81
81
|
const request = opts.request ?? defaultCloudflareRequester;
|
|
82
82
|
const metadata = {
|
|
83
83
|
main_module: 'worker.js',
|
|
@@ -88,7 +88,11 @@ export async function deployWorker(apiToken, accountId, workerName, script, buck
|
|
|
88
88
|
};
|
|
89
89
|
const form = new FormData();
|
|
90
90
|
form.set('metadata', new Blob([JSON.stringify(metadata)], { type: 'application/json' }));
|
|
91
|
-
|
|
91
|
+
const bundle = typeof worker === 'string' ? { script: worker, modules: [] } : worker;
|
|
92
|
+
form.set('worker.js', new Blob([bundle.script], { type: 'application/javascript+module' }), 'worker.js');
|
|
93
|
+
for (const module of bundle.modules) {
|
|
94
|
+
form.set(module.name, new Blob([module.contents], { type: module.contentType }), module.name);
|
|
95
|
+
}
|
|
92
96
|
await request({
|
|
93
97
|
apiToken,
|
|
94
98
|
method: 'PUT',
|
|
@@ -130,7 +134,8 @@ export const WORKER_PHOENIX_ID_BASE_SECRET = 'PHOENIX_ID_BASE';
|
|
|
130
134
|
* re-setting the secret to its own value after every deploy gets the same
|
|
131
135
|
* outcome without depending on an unverified upload-time flag.
|
|
132
136
|
*/
|
|
133
|
-
export async function updateWorker(apiToken, accountId, workerName, bucketName,
|
|
137
|
+
export async function updateWorker(apiToken, accountId, workerName, bucketName, worker, writeToken, previousHash, opts = {}) {
|
|
138
|
+
const script = typeof worker === 'string' ? worker : worker.script;
|
|
134
139
|
const templateHash = hashWorkerScript(script);
|
|
135
140
|
if (!opts.force && previousHash === templateHash) {
|
|
136
141
|
// Script is current so we skip the upload (which would wipe secrets). A
|
|
@@ -142,7 +147,7 @@ export async function updateWorker(apiToken, accountId, workerName, bucketName,
|
|
|
142
147
|
}
|
|
143
148
|
return { templateHash, skipped: true };
|
|
144
149
|
}
|
|
145
|
-
await deployWorker(apiToken, accountId, workerName,
|
|
150
|
+
await deployWorker(apiToken, accountId, workerName, worker, bucketName, opts);
|
|
146
151
|
// Script upload clears bindings/secrets (see JSDoc above). If re-applying
|
|
147
152
|
// WRITE_TOKEN fails here, the live Worker has no write token — every
|
|
148
153
|
// `agents artifacts share` publish/delete 401s until a re-run of `agents artifacts share update`
|
|
@@ -1,4 +1,27 @@
|
|
|
1
|
-
/**
|
|
1
|
+
/**
|
|
2
|
+
* Render the Worker source. Pure — the R2 binding + token are wired at deploy time.
|
|
3
|
+
*
|
|
4
|
+
* The literal below still spells the CLI `agents share` in its provenance comment,
|
|
5
|
+
* its root response, and its gallery title, even though the command is now
|
|
6
|
+
* `agents artifacts share` (RUSH-2580). That is deliberate: `hashWorkerScript` of
|
|
7
|
+
* this exact text is what `shareTemplateStatus` compares a provisioned endpoint's
|
|
8
|
+
* recorded `templateHash` against, so editing ANY byte here marks every already-
|
|
9
|
+
* deployed endpoint `outdated` — which makes `agents artifacts share list` refuse
|
|
10
|
+
* until its owner re-runs `agents artifacts share update`. Cosmetic renames are not
|
|
11
|
+
* worth that; change this text only alongside a real Worker behavior change.
|
|
12
|
+
*/
|
|
13
|
+
export interface WorkerModule {
|
|
14
|
+
name: string;
|
|
15
|
+
contentType: 'application/wasm';
|
|
16
|
+
contents: Uint8Array<ArrayBuffer>;
|
|
17
|
+
}
|
|
18
|
+
export interface WorkerBundle {
|
|
19
|
+
script: string;
|
|
20
|
+
modules: WorkerModule[];
|
|
21
|
+
}
|
|
22
|
+
/** Bundle the Worker renderer into an ES module plus workerd-compiled WASM modules. */
|
|
23
|
+
export declare function renderWorkerBundle(): WorkerBundle;
|
|
24
|
+
/** Single-file representation for direct Node tests, which cannot import compiled WASM modules. */
|
|
2
25
|
export declare function renderWorkerScript(): string;
|
|
3
26
|
/** Unbundled Worker source. Kept separate so esbuild can resolve npm modules. */
|
|
4
27
|
export declare function renderWorkerSource(): string;
|
|
@@ -1,81 +1,53 @@
|
|
|
1
1
|
import { buildSync } from 'esbuild';
|
|
2
2
|
import { dirname } from 'node:path';
|
|
3
3
|
import { fileURLToPath } from 'node:url';
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
//
|
|
26
|
-
//
|
|
27
|
-
//
|
|
28
|
-
//
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
// - GET /<username>/<slug>?revisions=json — machine-readable history of the
|
|
49
|
-
// retained prior versions under that slug, newest first.
|
|
50
|
-
// - GET /<username> — public gallery of that user's shares (HTML).
|
|
51
|
-
// - GET /<username>?format=json — public machine-readable listing of that user's
|
|
52
|
-
// ACTIVE shares (`agents artifacts share list`). Same single-segment path as the HTML
|
|
53
|
-
// gallery and gated on the SAME "does <username>/ hold any object" check, so it
|
|
54
|
-
// only intercepts a genuine namespace — a legacy flat slug with ?format=json
|
|
55
|
-
// still serves its real content, never a fake empty listing.
|
|
56
|
-
// - GET /<slug> — backward-compat flat slug (legacy shares before
|
|
57
|
-
// per-user namespaces).
|
|
58
|
-
//
|
|
59
|
-
// Emitted as a string, so it compiles into `dist/**` and ships with no
|
|
60
|
-
// package.json#files change. `provision.ts` uploads
|
|
61
|
-
// this verbatim as an ES-module Worker with a BUCKET (R2) binding + a WRITE_TOKEN secret.
|
|
62
|
-
/**
|
|
63
|
-
* Render the Worker source. Pure — the R2 binding + token are wired at deploy time.
|
|
64
|
-
*
|
|
65
|
-
* The literal below still spells the CLI `agents share` in its provenance comment,
|
|
66
|
-
* its root response, and its gallery title, even though the command is now
|
|
67
|
-
* `agents artifacts share` (RUSH-2580). That is deliberate: `hashWorkerScript` of
|
|
68
|
-
* this exact text is what `shareTemplateStatus` compares a provisioned endpoint's
|
|
69
|
-
* recorded `templateHash` against, so editing ANY byte here marks every already-
|
|
70
|
-
* deployed endpoint `outdated` — which makes `agents artifacts share list` refuse
|
|
71
|
-
* until its owner re-runs `agents artifacts share update`. Cosmetic renames are not
|
|
72
|
-
* worth that; change this text only alongside a real Worker behavior change.
|
|
73
|
-
*/
|
|
74
|
-
let bundledWorkerScript;
|
|
75
|
-
/** Bundle the Worker and its renderer into one uploadable ES module. */
|
|
4
|
+
let bundledWorker;
|
|
5
|
+
let nodeWorkerScript;
|
|
6
|
+
/** Bundle the Worker renderer into an ES module plus workerd-compiled WASM modules. */
|
|
7
|
+
export function renderWorkerBundle() {
|
|
8
|
+
if (bundledWorker)
|
|
9
|
+
return bundledWorker;
|
|
10
|
+
const result = buildSync({
|
|
11
|
+
stdin: {
|
|
12
|
+
contents: renderWorkerSource(),
|
|
13
|
+
loader: 'js',
|
|
14
|
+
resolveDir: dirname(fileURLToPath(import.meta.url)),
|
|
15
|
+
sourcefile: 'agents-share-worker.js',
|
|
16
|
+
},
|
|
17
|
+
bundle: true,
|
|
18
|
+
format: 'esm',
|
|
19
|
+
platform: 'browser',
|
|
20
|
+
target: 'es2022',
|
|
21
|
+
write: false,
|
|
22
|
+
outdir: 'worker-bundle',
|
|
23
|
+
assetNames: '[name]-[hash]',
|
|
24
|
+
minify: true,
|
|
25
|
+
// Fonts are plain data and can live in JavaScript. WASM must remain a
|
|
26
|
+
// compiled module: workerd deliberately forbids runtime code generation,
|
|
27
|
+
// so esbuild's `binary` loader produces a bundle that works in Node but
|
|
28
|
+
// throws "Wasm code generation disallowed by embedder" in Cloudflare.
|
|
29
|
+
loader: { '.wasm': 'copy', '.woff': 'binary' },
|
|
30
|
+
});
|
|
31
|
+
const script = result.outputFiles.find((output) => output.path.endsWith('.js'));
|
|
32
|
+
if (!script)
|
|
33
|
+
throw new Error('Worker bundling produced no JavaScript output.');
|
|
34
|
+
const modules = result.outputFiles
|
|
35
|
+
.filter((output) => output.path.endsWith('.wasm'))
|
|
36
|
+
.map((output) => ({
|
|
37
|
+
name: output.path.split('/').pop(),
|
|
38
|
+
contentType: 'application/wasm',
|
|
39
|
+
contents: new Uint8Array(output.contents),
|
|
40
|
+
}));
|
|
41
|
+
if (modules.length !== 2) {
|
|
42
|
+
throw new Error(`Worker bundling produced ${modules.length} WASM modules; expected yoga and resvg.`);
|
|
43
|
+
}
|
|
44
|
+
bundledWorker = { script: script.text, modules };
|
|
45
|
+
return bundledWorker;
|
|
46
|
+
}
|
|
47
|
+
/** Single-file representation for direct Node tests, which cannot import compiled WASM modules. */
|
|
76
48
|
export function renderWorkerScript() {
|
|
77
|
-
if (
|
|
78
|
-
return
|
|
49
|
+
if (nodeWorkerScript)
|
|
50
|
+
return nodeWorkerScript;
|
|
79
51
|
const result = buildSync({
|
|
80
52
|
stdin: {
|
|
81
53
|
contents: renderWorkerSource(),
|
|
@@ -93,9 +65,9 @@ export function renderWorkerScript() {
|
|
|
93
65
|
});
|
|
94
66
|
const output = result.outputFiles[0];
|
|
95
67
|
if (!output)
|
|
96
|
-
throw new Error('Worker bundling produced no JavaScript output.');
|
|
97
|
-
|
|
98
|
-
return
|
|
68
|
+
throw new Error('Worker test bundling produced no JavaScript output.');
|
|
69
|
+
nodeWorkerScript = output.text;
|
|
70
|
+
return nodeWorkerScript;
|
|
99
71
|
}
|
|
100
72
|
/** Unbundled Worker source. Kept separate so esbuild can resolve npm modules. */
|
|
101
73
|
export function renderWorkerSource() {
|
|
@@ -491,13 +463,22 @@ export default {
|
|
|
491
463
|
|
|
492
464
|
const pageHtml = await page.text();
|
|
493
465
|
const meta = page.customMetadata || {};
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
466
|
+
let png;
|
|
467
|
+
try {
|
|
468
|
+
png = await hooks.renderOgCard({
|
|
469
|
+
title: meta['og-title'] || extractHtmlMeta(pageHtml, 'title') || meta.label || segments[1],
|
|
470
|
+
description: meta['og-description'] || extractHtmlMeta(pageHtml, 'description') || '',
|
|
471
|
+
handle: segments[0],
|
|
472
|
+
visibility: pageVisibility,
|
|
473
|
+
orgDomain: meta.org_domain || '',
|
|
474
|
+
});
|
|
475
|
+
} catch (error) {
|
|
476
|
+
const detail = error instanceof Error ? error.message : String(error);
|
|
477
|
+
return new Response('OG card render failed: ' + detail, {
|
|
478
|
+
status: 500,
|
|
479
|
+
headers: { 'content-type': 'text/plain; charset=utf-8' },
|
|
480
|
+
});
|
|
481
|
+
}
|
|
501
482
|
const beforeStore = await env.BUCKET.get(pagePath);
|
|
502
483
|
if (!beforeStore || beforeStore.etag !== page.etag) { existingCover = null; continue; }
|
|
503
484
|
await env.BUCKET.put(path, png, {
|
|
@@ -26,7 +26,8 @@ export declare const KNOWN_TOP_LEVEL_COMMANDS: ReadonlySet<string>;
|
|
|
26
26
|
* (linear-cli) (RUSH-2932). `alias` moved under `agents setup alias` (RUSH-2965).
|
|
27
27
|
* `inbox` was a pure alias of `agents feed` (RUSH-2984). `unshare` nested under
|
|
28
28
|
* `agents artifacts unshare` (RUSH-2989). `audit` nested under `agents events audit`.
|
|
29
|
-
* `trends`
|
|
29
|
+
* `trends` was removed with the insights recipe collapse — the one counter
|
|
30
|
+
* surface is `agents insights mix` (PHNX-3391). `serve` (the
|
|
30
31
|
* read-only local web companion + `--control` anchor) was removed with the
|
|
31
32
|
* unshipped iOS Fleet Cockpit it existed for (RUSH-3001). `apply` nested under
|
|
32
33
|
* `agents fleet apply` / `agents devices apply`. `beta` nested under
|
|
@@ -34,7 +35,12 @@ export declare const KNOWN_TOP_LEVEL_COMMANDS: ReadonlySet<string>;
|
|
|
34
35
|
* removed; `agents auth` returned against Phoenix ID with `auth space` as the
|
|
35
36
|
* team surface (RUSH-2581). `usage` was removed as a duplicate surface of
|
|
36
37
|
* `agents view`, which renders per-account usage with account, version, and
|
|
37
|
-
* auth state beside it (RUSH-3079).
|
|
38
|
+
* auth state beside it (RUSH-3079). `perf` nested under `agents insights perf`
|
|
39
|
+
* — performance metrics are an insight, not a top-level noun (PHNX-3391).
|
|
40
|
+
* `list` was removed — it was a long-deprecated full duplicate of `agents view`
|
|
41
|
+
* (it already printed "agents list is now agents view"); `agents view` is the
|
|
42
|
+
* one version-listing surface (PHNX-3391). The `agents trash restore` subcommand
|
|
43
|
+
* was likewise removed as an exact duplicate of top-level `agents restore`.
|
|
38
44
|
*/
|
|
39
45
|
export declare const RETIRED_TOP_LEVEL_COMMANDS: ReadonlySet<string>;
|
|
40
46
|
export declare function isKnownTopLevelCommand(name: string): boolean;
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
const LOADED_COMMAND_NAMES = [
|
|
2
2
|
'accounts', 'auth', 'view', 'inspect', 'feedback', 'commands', 'hooks', 'skills', 'rules', 'memory',
|
|
3
|
-
'permissions', 'mcp', 'clis', 'subagents', 'plugins', 'workflows', 'add', 'use',
|
|
3
|
+
'permissions', 'mcp', 'clis', 'subagents', 'plugins', 'workflows', 'add', 'use',
|
|
4
4
|
'remove', 'rm', 'purge', 'update', 'prune', 'import', 'registry', 'search', 'install',
|
|
5
5
|
'routines', 'monitors', 'projects', 'run', 'open', 'reconnect', 'fork', 'config',
|
|
6
6
|
'models', 'modes', 'trash', 'restore', 'doctor',
|
|
7
7
|
'route', 'harness', 'harnesses', 'secrets', 'menubar', 'sync',
|
|
8
|
-
'refresh-rules', 'factory', 'insights', 'trace',
|
|
8
|
+
'refresh-rules', 'factory', 'insights', 'trace',
|
|
9
9
|
'pty', 'tmux', 'watchdog', 'browser', 'computer', 'logs', 'events',
|
|
10
10
|
'ssh', 'devices', 'fleet', 'repos', 'repo', 'setup', 'uninstall', 'upgrade', 'sessions',
|
|
11
11
|
'teams', 'cloud', 'message', 'send', 'notify', 'feed',
|
|
@@ -46,7 +46,8 @@ export const KNOWN_TOP_LEVEL_COMMANDS = new Set([
|
|
|
46
46
|
* (linear-cli) (RUSH-2932). `alias` moved under `agents setup alias` (RUSH-2965).
|
|
47
47
|
* `inbox` was a pure alias of `agents feed` (RUSH-2984). `unshare` nested under
|
|
48
48
|
* `agents artifacts unshare` (RUSH-2989). `audit` nested under `agents events audit`.
|
|
49
|
-
* `trends`
|
|
49
|
+
* `trends` was removed with the insights recipe collapse — the one counter
|
|
50
|
+
* surface is `agents insights mix` (PHNX-3391). `serve` (the
|
|
50
51
|
* read-only local web companion + `--control` anchor) was removed with the
|
|
51
52
|
* unshipped iOS Fleet Cockpit it existed for (RUSH-3001). `apply` nested under
|
|
52
53
|
* `agents fleet apply` / `agents devices apply`. `beta` nested under
|
|
@@ -54,7 +55,12 @@ export const KNOWN_TOP_LEVEL_COMMANDS = new Set([
|
|
|
54
55
|
* removed; `agents auth` returned against Phoenix ID with `auth space` as the
|
|
55
56
|
* team surface (RUSH-2581). `usage` was removed as a duplicate surface of
|
|
56
57
|
* `agents view`, which renders per-account usage with account, version, and
|
|
57
|
-
* auth state beside it (RUSH-3079).
|
|
58
|
+
* auth state beside it (RUSH-3079). `perf` nested under `agents insights perf`
|
|
59
|
+
* — performance metrics are an insight, not a top-level noun (PHNX-3391).
|
|
60
|
+
* `list` was removed — it was a long-deprecated full duplicate of `agents view`
|
|
61
|
+
* (it already printed "agents list is now agents view"); `agents view` is the
|
|
62
|
+
* one version-listing surface (PHNX-3391). The `agents trash restore` subcommand
|
|
63
|
+
* was likewise removed as an exact duplicate of top-level `agents restore`.
|
|
58
64
|
*/
|
|
59
65
|
export const RETIRED_TOP_LEVEL_COMMANDS = new Set([
|
|
60
66
|
'webhook',
|
|
@@ -85,6 +91,8 @@ export const RETIRED_TOP_LEVEL_COMMANDS = new Set([
|
|
|
85
91
|
'trends',
|
|
86
92
|
'apply',
|
|
87
93
|
'beta',
|
|
94
|
+
'perf',
|
|
95
|
+
'list',
|
|
88
96
|
]);
|
|
89
97
|
export function isKnownTopLevelCommand(name) {
|
|
90
98
|
return KNOWN_TOP_LEVEL_COMMANDS.has(name);
|