@phnx-labs/agents-cli 1.22.26 → 1.22.27
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 +244 -0
- package/README.md +18 -1
- package/dist/bin/agents +0 -0
- package/dist/commands/bench.d.ts +2 -0
- package/dist/commands/bench.js +101 -0
- package/dist/commands/doctor.js +24 -10
- package/dist/commands/exec.js +17 -16
- package/dist/commands/insights.js +39 -5
- package/dist/commands/output.js +100 -25
- package/dist/commands/resume.d.ts +11 -0
- package/dist/commands/resume.js +51 -0
- package/dist/commands/sessions-picker.js +12 -7
- package/dist/commands/sessions.d.ts +47 -1
- package/dist/commands/sessions.js +345 -37
- package/dist/commands/ssh.js +184 -11
- package/dist/commands/teams.js +3 -3
- package/dist/index.js +16 -20
- package/dist/lib/auth-health.d.ts +47 -3
- package/dist/lib/auth-health.js +74 -15
- package/dist/lib/bench/index.d.ts +4 -0
- package/dist/lib/bench/index.js +4 -0
- package/dist/lib/bench/runner.d.ts +16 -0
- package/dist/lib/bench/runner.js +111 -0
- package/dist/lib/bench/schema.d.ts +5 -0
- package/dist/lib/bench/schema.js +91 -0
- package/dist/lib/bench/storage.d.ts +5 -0
- package/dist/lib/bench/storage.js +32 -0
- package/dist/lib/bench/types.d.ts +40 -0
- package/dist/lib/bench/types.js +1 -0
- package/dist/lib/claude-account-token.d.ts +2 -0
- package/dist/lib/claude-account-token.js +27 -1
- package/dist/lib/crabbox/cli.d.ts +2 -0
- package/dist/lib/crabbox/cli.js +2 -0
- package/dist/lib/crabbox/lease.js +7 -1
- package/dist/lib/daemon.js +20 -0
- package/dist/lib/devices/connect.d.ts +2 -0
- package/dist/lib/devices/connect.js +7 -0
- package/dist/lib/devices/doctor-findings.d.ts +4 -1
- package/dist/lib/devices/doctor-findings.js +15 -1
- package/dist/lib/devices/harness-inventory.d.ts +97 -0
- package/dist/lib/devices/harness-inventory.js +0 -0
- package/dist/lib/devices/registry.d.ts +2 -0
- package/dist/lib/devices/ssh-config.js +3 -0
- package/dist/lib/devices/windows-ssh-enrollment.d.ts +20 -0
- package/dist/lib/devices/windows-ssh-enrollment.js +98 -0
- package/dist/lib/exec.bench.d.ts +1 -0
- package/dist/lib/exec.bench.js +186 -0
- package/dist/lib/exec.js +18 -8
- package/dist/lib/fleet/apply.js +9 -7
- package/dist/lib/fleet/remote-login.d.ts +4 -3
- package/dist/lib/fleet/remote-login.js +11 -9
- package/dist/lib/gemini-settings.d.ts +0 -1
- package/dist/lib/gemini-settings.js +12 -7
- package/dist/lib/hooks.d.ts +8 -5
- package/dist/lib/hooks.js +10 -6
- package/dist/lib/hosts/dispatch.js +7 -3
- package/dist/lib/hosts/passthrough.d.ts +22 -0
- package/dist/lib/hosts/passthrough.js +14 -8
- package/dist/lib/hosts/progress.d.ts +2 -1
- package/dist/lib/hosts/progress.js +4 -3
- package/dist/lib/hosts/providers/devices.js +1 -0
- package/dist/lib/hosts/ready.d.ts +4 -2
- package/dist/lib/hosts/ready.js +24 -8
- package/dist/lib/hosts/reconcile.d.ts +1 -1
- package/dist/lib/hosts/reconcile.js +16 -7
- package/dist/lib/hosts/reconnect.d.ts +45 -12
- package/dist/lib/hosts/reconnect.js +90 -34
- package/dist/lib/hosts/registry.d.ts +2 -2
- package/dist/lib/hosts/registry.js +3 -5
- package/dist/lib/hosts/remote-cmd.d.ts +17 -0
- package/dist/lib/hosts/remote-cmd.js +29 -0
- package/dist/lib/hosts/tasks.d.ts +2 -0
- package/dist/lib/hosts/types.d.ts +1 -0
- package/dist/lib/hosts/types.js +3 -0
- package/dist/lib/menubar/MenubarHelper.app/Contents/CodeResources +0 -0
- package/dist/lib/menubar/MenubarHelper.app/Contents/MacOS/MenubarHelper +0 -0
- package/dist/lib/menubar/install-menubar.d.ts +9 -6
- package/dist/lib/menubar/install-menubar.js +20 -9
- package/dist/lib/models.d.ts +30 -1
- package/dist/lib/models.js +42 -144
- package/dist/lib/pricing/cost.d.ts +9 -0
- package/dist/lib/pricing/cost.js +24 -0
- package/dist/lib/pricing/index.d.ts +1 -1
- package/dist/lib/pricing/index.js +1 -1
- package/dist/lib/redact.js +8 -3
- package/dist/lib/remote-agents-json.d.ts +32 -0
- package/dist/lib/remote-agents-json.js +47 -16
- package/dist/lib/resource-profiles.js +1 -2
- package/dist/lib/routine-notify-owner.d.ts +102 -0
- package/dist/lib/routine-notify-owner.js +232 -0
- package/dist/lib/routines.d.ts +10 -0
- package/dist/lib/runner.d.ts +9 -1
- package/dist/lib/runner.js +182 -17
- package/dist/lib/sandbox.d.ts +0 -2
- package/dist/lib/sandbox.js +2 -19
- package/dist/lib/secrets/Agents CLI.app/Contents/CodeResources +0 -0
- package/dist/lib/secrets/Agents CLI.app/Contents/MacOS/Agents CLI +0 -0
- package/dist/lib/secrets/agent.js +3 -2
- package/dist/lib/secrets/lease.d.ts +25 -0
- package/dist/lib/secrets/lease.js +44 -0
- package/dist/lib/self-update.d.ts +20 -5
- package/dist/lib/self-update.js +93 -16
- package/dist/lib/session/db.d.ts +19 -2
- package/dist/lib/session/db.js +54 -5
- package/dist/lib/session/discover.d.ts +21 -1
- package/dist/lib/session/discover.js +124 -32
- package/dist/lib/session/insights.d.ts +19 -0
- package/dist/lib/session/insights.js +78 -10
- package/dist/lib/session/recovery.d.ts +17 -1
- package/dist/lib/session/recovery.js +111 -4
- package/dist/lib/session/remote-list.d.ts +10 -9
- package/dist/lib/session/remote-list.js +25 -23
- package/dist/lib/session/resume-owner.d.ts +55 -0
- package/dist/lib/session/resume-owner.js +69 -0
- package/dist/lib/session/team-filter.d.ts +65 -0
- package/dist/lib/session/team-filter.js +98 -3
- package/dist/lib/session/types.d.ts +33 -0
- package/dist/lib/smart-launch.d.ts +31 -5
- package/dist/lib/smart-launch.js +43 -7
- package/dist/lib/ssh-exec.d.ts +27 -0
- package/dist/lib/ssh-exec.js +34 -1
- package/dist/lib/ssh-tunnel.d.ts +3 -2
- package/dist/lib/ssh-tunnel.js +25 -16
- package/dist/lib/startup/command-registry.d.ts +1 -0
- package/dist/lib/startup/command-registry.js +2 -0
- package/dist/lib/teams/placement-probe.d.ts +1 -1
- package/dist/lib/teams/placement-probe.js +24 -24
- package/dist/lib/tmux/session.d.ts +8 -0
- package/dist/lib/tmux/session.js +22 -0
- package/dist/lib/types.d.ts +2 -0
- package/package.json +1 -1
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import { randomBytes } from 'crypto';
|
|
2
|
+
import { GLOBAL_HARNESS } from './scope.js';
|
|
3
|
+
export const MIN_LEASE_MS = 60 * 1000;
|
|
4
|
+
export const MAX_LEASE_MS = 30 * 24 * 60 * 60 * 1000;
|
|
5
|
+
export function clampLeaseTtlMs(ttlMs) {
|
|
6
|
+
if (!Number.isFinite(ttlMs) || ttlMs <= 0) {
|
|
7
|
+
throw new Error('Lease duration must be a positive finite number.');
|
|
8
|
+
}
|
|
9
|
+
return Math.min(Math.max(Math.floor(ttlMs), MIN_LEASE_MS), MAX_LEASE_MS);
|
|
10
|
+
}
|
|
11
|
+
export function normalizeLeaseKeys(keys, availableKeys) {
|
|
12
|
+
const requested = [...new Set(keys.map((key) => key.trim()).filter(Boolean))].sort();
|
|
13
|
+
if (requested.length === 0)
|
|
14
|
+
throw new Error('A secret lease requires at least one key.');
|
|
15
|
+
const available = new Set(availableKeys);
|
|
16
|
+
const missing = requested.filter((key) => !available.has(key));
|
|
17
|
+
if (missing.length > 0)
|
|
18
|
+
throw new Error(`Unknown secret lease key(s): ${missing.join(', ')}`);
|
|
19
|
+
return requested;
|
|
20
|
+
}
|
|
21
|
+
export function createSecretLease(input) {
|
|
22
|
+
const createdAt = input.now ?? Date.now();
|
|
23
|
+
const ttlMs = clampLeaseTtlMs(input.ttlMs);
|
|
24
|
+
return {
|
|
25
|
+
id: input.id ?? randomBytes(12).toString('hex'),
|
|
26
|
+
bundle: input.bundle,
|
|
27
|
+
keys: normalizeLeaseKeys(input.keys, input.availableKeys),
|
|
28
|
+
createdAt,
|
|
29
|
+
expiresAt: createdAt + ttlMs,
|
|
30
|
+
harness: input.harness ?? GLOBAL_HARNESS,
|
|
31
|
+
sleepPersist: input.sleepPersist ?? false,
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
export function leaseIsActive(lease, now = Date.now()) {
|
|
35
|
+
return now < lease.expiresAt;
|
|
36
|
+
}
|
|
37
|
+
export function selectLeasedEnv(lease, env, now = Date.now()) {
|
|
38
|
+
if (!leaseIsActive(lease, now))
|
|
39
|
+
throw new Error(`Secret lease '${lease.id}' has expired.`);
|
|
40
|
+
const missing = lease.keys.filter((key) => typeof env[key] !== 'string');
|
|
41
|
+
if (missing.length > 0)
|
|
42
|
+
throw new Error(`Secret lease '${lease.id}' is missing value(s): ${missing.join(', ')}`);
|
|
43
|
+
return Object.fromEntries(lease.keys.map((key) => [key, env[key]]));
|
|
44
|
+
}
|
|
@@ -136,15 +136,30 @@ export declare function verifyInstalledVersion(packageRoot: string, expectedVers
|
|
|
136
136
|
*/
|
|
137
137
|
export declare function refreshAliasShims(packageRoot: string): void;
|
|
138
138
|
export interface AgentsCliInstall {
|
|
139
|
-
/** The PATH entry (`<dir>/agents`) that resolves to this install. */
|
|
140
|
-
binPath
|
|
139
|
+
/** The PATH entry (`<dir>/agents`) that resolves to this install, when found through PATH. */
|
|
140
|
+
binPath?: string;
|
|
141
141
|
/** Package root containing package.json and dist/. */
|
|
142
142
|
packageRoot: string;
|
|
143
143
|
version: string;
|
|
144
|
+
/** Whether this copy uses the serialized, atomic helper-bundle installer. */
|
|
145
|
+
atomicHelperInstall: boolean;
|
|
144
146
|
}
|
|
147
|
+
export interface FindAgentsCliInstallsOptions {
|
|
148
|
+
homeDir?: string;
|
|
149
|
+
fnmDir?: string;
|
|
150
|
+
npmCacheDir?: string;
|
|
151
|
+
globalNodeModulesDirs?: string[];
|
|
152
|
+
}
|
|
153
|
+
export interface MultiInstallInventoryEntry {
|
|
154
|
+
packageRoot: string;
|
|
155
|
+
version: string;
|
|
156
|
+
note: string;
|
|
157
|
+
}
|
|
158
|
+
export declare function buildMultiInstallInventory(runningRoot: string, runningVersion: string, installs: AgentsCliInstall[]): MultiInstallInventoryEntry[];
|
|
145
159
|
/**
|
|
146
|
-
*
|
|
147
|
-
*
|
|
160
|
+
* Resolve every `agents` entrypoint on PATH, then inspect the bounded global
|
|
161
|
+
* install layouts used by NVM, fnm, Volta, Bun, npm, and npx. More than one
|
|
162
|
+
* distinct package root means upgrades,
|
|
148
163
|
* shims, and the command the user types can act on different copies — the
|
|
149
164
|
* divergence behind silently-failing self-updates.
|
|
150
165
|
*
|
|
@@ -158,4 +173,4 @@ export interface AgentsCliInstall {
|
|
|
158
173
|
* @phnx-labs/agents-cli is some other tool and is skipped.
|
|
159
174
|
* POSIX-only: Windows npm bins are .cmd wrappers, not symlinks.
|
|
160
175
|
*/
|
|
161
|
-
export declare function findAgentsCliInstalls(pathEnv: string): AgentsCliInstall[];
|
|
176
|
+
export declare function findAgentsCliInstalls(pathEnv: string, opts?: FindAgentsCliInstallsOptions): AgentsCliInstall[];
|
package/dist/lib/self-update.js
CHANGED
|
@@ -327,9 +327,89 @@ function packageRootForEntry(real) {
|
|
|
327
327
|
}
|
|
328
328
|
return null;
|
|
329
329
|
}
|
|
330
|
+
export function buildMultiInstallInventory(runningRoot, runningVersion, installs) {
|
|
331
|
+
const byRoot = new Map();
|
|
332
|
+
byRoot.set(runningRoot, { packageRoot: runningRoot, version: runningVersion, note: 'running' });
|
|
333
|
+
for (const install of installs) {
|
|
334
|
+
const notes = [install.packageRoot === runningRoot
|
|
335
|
+
? 'running'
|
|
336
|
+
: install.binPath
|
|
337
|
+
? `agents on PATH: ${install.binPath}`
|
|
338
|
+
: 'discovered install'];
|
|
339
|
+
if (!install.atomicHelperInstall)
|
|
340
|
+
notes.push('unsafe legacy helper installer — remove this copy');
|
|
341
|
+
byRoot.set(install.packageRoot, {
|
|
342
|
+
packageRoot: install.packageRoot,
|
|
343
|
+
version: install.version,
|
|
344
|
+
note: notes.join('; '),
|
|
345
|
+
});
|
|
346
|
+
}
|
|
347
|
+
return [...byRoot.values()];
|
|
348
|
+
}
|
|
349
|
+
function childDirectories(parent) {
|
|
350
|
+
try {
|
|
351
|
+
return fs.readdirSync(parent, { withFileTypes: true })
|
|
352
|
+
.filter((entry) => entry.isDirectory())
|
|
353
|
+
.map((entry) => path.join(parent, entry.name));
|
|
354
|
+
}
|
|
355
|
+
catch {
|
|
356
|
+
return [];
|
|
357
|
+
}
|
|
358
|
+
}
|
|
359
|
+
function knownPackageRoots(opts) {
|
|
360
|
+
const homeDir = opts.homeDir ?? os.homedir();
|
|
361
|
+
const fnmRoots = opts.fnmDir !== undefined
|
|
362
|
+
? [opts.fnmDir]
|
|
363
|
+
: [process.env.FNM_DIR, path.join(homeDir, '.local', 'share', 'fnm')]
|
|
364
|
+
.filter((value) => Boolean(value));
|
|
365
|
+
const roots = [];
|
|
366
|
+
const packageTail = path.join('lib', 'node_modules', ...NPM_PACKAGE_NAME.split('/'));
|
|
367
|
+
for (const nodeDir of childDirectories(path.join(homeDir, '.nvm', 'versions', 'node'))) {
|
|
368
|
+
roots.push(path.join(nodeDir, packageTail));
|
|
369
|
+
}
|
|
370
|
+
for (const fnmRoot of fnmRoots) {
|
|
371
|
+
for (const versionDir of childDirectories(path.join(fnmRoot, 'node-versions'))) {
|
|
372
|
+
roots.push(path.join(versionDir, 'installation', packageTail));
|
|
373
|
+
}
|
|
374
|
+
}
|
|
375
|
+
roots.push(path.join(homeDir, '.volta', 'tools', 'image', 'packages', ...NPM_PACKAGE_NAME.split('/'), packageTail), path.join(homeDir, '.local', packageTail), path.join(homeDir, '.bun', 'install', 'global', 'node_modules', ...NPM_PACKAGE_NAME.split('/')));
|
|
376
|
+
const npmCacheDir = opts.npmCacheDir ?? process.env.npm_config_cache ?? path.join(homeDir, '.npm');
|
|
377
|
+
for (const npxRunDir of childDirectories(path.join(npmCacheDir, '_npx'))) {
|
|
378
|
+
roots.push(path.join(npxRunDir, 'node_modules', ...NPM_PACKAGE_NAME.split('/')));
|
|
379
|
+
}
|
|
380
|
+
const globalNodeModulesDirs = opts.globalNodeModulesDirs ?? [
|
|
381
|
+
'/opt/homebrew/lib/node_modules',
|
|
382
|
+
'/usr/local/lib/node_modules',
|
|
383
|
+
'/usr/lib/node_modules',
|
|
384
|
+
];
|
|
385
|
+
for (const nodeModulesDir of globalNodeModulesDirs) {
|
|
386
|
+
roots.push(path.join(nodeModulesDir, ...NPM_PACKAGE_NAME.split('/')));
|
|
387
|
+
}
|
|
388
|
+
return roots;
|
|
389
|
+
}
|
|
390
|
+
function readAgentsCliInstall(packageRoot, binPath) {
|
|
391
|
+
let canonicalRoot;
|
|
392
|
+
let pkg;
|
|
393
|
+
try {
|
|
394
|
+
canonicalRoot = fs.realpathSync(packageRoot);
|
|
395
|
+
pkg = JSON.parse(fs.readFileSync(path.join(canonicalRoot, 'package.json'), 'utf-8'));
|
|
396
|
+
}
|
|
397
|
+
catch {
|
|
398
|
+
return null;
|
|
399
|
+
}
|
|
400
|
+
if (pkg.name !== NPM_PACKAGE_NAME || typeof pkg.version !== 'string')
|
|
401
|
+
return null;
|
|
402
|
+
return {
|
|
403
|
+
...(binPath ? { binPath } : {}),
|
|
404
|
+
packageRoot: canonicalRoot,
|
|
405
|
+
version: pkg.version,
|
|
406
|
+
atomicHelperInstall: fs.existsSync(path.join(canonicalRoot, 'dist', 'lib', 'app-bundle-install.js')),
|
|
407
|
+
};
|
|
408
|
+
}
|
|
330
409
|
/**
|
|
331
|
-
*
|
|
332
|
-
*
|
|
410
|
+
* Resolve every `agents` entrypoint on PATH, then inspect the bounded global
|
|
411
|
+
* install layouts used by NVM, fnm, Volta, Bun, npm, and npx. More than one
|
|
412
|
+
* distinct package root means upgrades,
|
|
333
413
|
* shims, and the command the user types can act on different copies — the
|
|
334
414
|
* divergence behind silently-failing self-updates.
|
|
335
415
|
*
|
|
@@ -343,11 +423,17 @@ function packageRootForEntry(real) {
|
|
|
343
423
|
* @phnx-labs/agents-cli is some other tool and is skipped.
|
|
344
424
|
* POSIX-only: Windows npm bins are .cmd wrappers, not symlinks.
|
|
345
425
|
*/
|
|
346
|
-
export function findAgentsCliInstalls(pathEnv) {
|
|
426
|
+
export function findAgentsCliInstalls(pathEnv, opts = {}) {
|
|
347
427
|
if (process.platform === 'win32')
|
|
348
428
|
return [];
|
|
349
429
|
const installs = [];
|
|
350
430
|
const seenRoots = new Set();
|
|
431
|
+
const addInstall = (install) => {
|
|
432
|
+
if (!install || seenRoots.has(install.packageRoot))
|
|
433
|
+
return;
|
|
434
|
+
seenRoots.add(install.packageRoot);
|
|
435
|
+
installs.push(install);
|
|
436
|
+
};
|
|
351
437
|
for (const dir of pathEnv.split(path.delimiter).filter(Boolean)) {
|
|
352
438
|
const candidate = path.join(dir, 'agents');
|
|
353
439
|
let real;
|
|
@@ -360,19 +446,10 @@ export function findAgentsCliInstalls(pathEnv) {
|
|
|
360
446
|
const packageRoot = packageRootForEntry(real);
|
|
361
447
|
if (!packageRoot)
|
|
362
448
|
continue;
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
pkg = JSON.parse(fs.readFileSync(path.join(packageRoot, 'package.json'), 'utf-8'));
|
|
368
|
-
}
|
|
369
|
-
catch {
|
|
370
|
-
continue;
|
|
371
|
-
}
|
|
372
|
-
if (pkg.name !== NPM_PACKAGE_NAME || typeof pkg.version !== 'string')
|
|
373
|
-
continue;
|
|
374
|
-
seenRoots.add(packageRoot);
|
|
375
|
-
installs.push({ binPath: candidate, packageRoot, version: pkg.version });
|
|
449
|
+
addInstall(readAgentsCliInstall(packageRoot, candidate));
|
|
450
|
+
}
|
|
451
|
+
for (const packageRoot of knownPackageRoots(opts)) {
|
|
452
|
+
addInstall(readAgentsCliInstall(packageRoot));
|
|
376
453
|
}
|
|
377
454
|
return installs;
|
|
378
455
|
}
|
package/dist/lib/session/db.d.ts
CHANGED
|
@@ -12,7 +12,7 @@ import { type IndexedToolCall } from './tool-calls.js';
|
|
|
12
12
|
/** Current schema version; bumped when migrations are added. Exported so tests
|
|
13
13
|
* assert against the constant instead of hardcoding a number that every bump
|
|
14
14
|
* then has to chase (docs/05-sessions.md calls the constant the source of truth). */
|
|
15
|
-
export declare const SCHEMA_VERSION =
|
|
15
|
+
export declare const SCHEMA_VERSION = 37;
|
|
16
16
|
/**
|
|
17
17
|
* Bump to force `agents sessions backfill resources` to re-derive every
|
|
18
18
|
* session's skill/slash-command tallies on its next run (resource_scan_ledger
|
|
@@ -26,7 +26,8 @@ export declare const RESOURCE_INDEX_VERSION = 1;
|
|
|
26
26
|
* re-derives on the next `agents insights` instead of silently reporting stale
|
|
27
27
|
* numbers alongside fresh ones. Same role as RESOURCE_INDEX_VERSION.
|
|
28
28
|
*/
|
|
29
|
-
|
|
29
|
+
/** Bump when facet extraction changes so cached rows recompute (stalls-by-model v6). */
|
|
30
|
+
export declare const INSIGHTS_EXTRACTOR_VERSION = 6;
|
|
30
31
|
/** Raw row shape returned from the sessions table. */
|
|
31
32
|
export interface SessionRow {
|
|
32
33
|
id: string;
|
|
@@ -50,7 +51,11 @@ export interface SessionRow {
|
|
|
50
51
|
message_count: number | null;
|
|
51
52
|
token_count: number | null;
|
|
52
53
|
output_tokens: number | null;
|
|
54
|
+
input_tokens: number | null;
|
|
55
|
+
cache_read_tokens: number | null;
|
|
56
|
+
cache_write_tokens: number | null;
|
|
53
57
|
cost_usd: number | null;
|
|
58
|
+
cost_usd_nocache: number | null;
|
|
54
59
|
duration_ms: number | null;
|
|
55
60
|
model: string | null;
|
|
56
61
|
tool_call_count: number | null;
|
|
@@ -335,11 +340,23 @@ export interface UsageRollupRow {
|
|
|
335
340
|
*/
|
|
336
341
|
label?: string;
|
|
337
342
|
costUsd: number;
|
|
343
|
+
/**
|
|
344
|
+
* USD cost priced as if caching were off (cache read/write at the input rate),
|
|
345
|
+
* summed from `cost_usd_nocache`. Backs `agents output --pricing no-cache`.
|
|
346
|
+
* Equals `costUsd` for rows whose sessions record no cache split (RUSH-2287).
|
|
347
|
+
*/
|
|
348
|
+
costUsdNoCache: number;
|
|
338
349
|
durationMs: number;
|
|
339
350
|
sessionCount: number;
|
|
340
351
|
tokenCount: number;
|
|
341
352
|
/** Real generated (output) tokens — excludes cache-read/-write context. */
|
|
342
353
|
outputTokens: number;
|
|
354
|
+
/** Uncached input tokens summed across the group (0 where no harness recorded a split). */
|
|
355
|
+
inputTokens: number;
|
|
356
|
+
/** Cache-read tokens summed across the group. */
|
|
357
|
+
cacheReadTokens: number;
|
|
358
|
+
/** Cache-write (cache-creation) tokens summed across the group. */
|
|
359
|
+
cacheWriteTokens: number;
|
|
343
360
|
}
|
|
344
361
|
/** What to group a usage rollup by. */
|
|
345
362
|
/**
|
package/dist/lib/session/db.js
CHANGED
|
@@ -26,7 +26,7 @@ const DB_PATH = getSessionsDbPath();
|
|
|
26
26
|
/** Current schema version; bumped when migrations are added. Exported so tests
|
|
27
27
|
* assert against the constant instead of hardcoding a number that every bump
|
|
28
28
|
* then has to chase (docs/05-sessions.md calls the constant the source of truth). */
|
|
29
|
-
export const SCHEMA_VERSION =
|
|
29
|
+
export const SCHEMA_VERSION = 37;
|
|
30
30
|
/**
|
|
31
31
|
* Bump to force `agents sessions backfill resources` to re-derive every
|
|
32
32
|
* session's skill/slash-command tallies on its next run (resource_scan_ledger
|
|
@@ -81,7 +81,11 @@ CREATE TABLE IF NOT EXISTS sessions (
|
|
|
81
81
|
message_count INTEGER,
|
|
82
82
|
token_count INTEGER,
|
|
83
83
|
output_tokens INTEGER,
|
|
84
|
+
input_tokens INTEGER,
|
|
85
|
+
cache_read_tokens INTEGER,
|
|
86
|
+
cache_write_tokens INTEGER,
|
|
84
87
|
cost_usd REAL,
|
|
88
|
+
cost_usd_nocache REAL,
|
|
85
89
|
duration_ms INTEGER,
|
|
86
90
|
model TEXT,
|
|
87
91
|
tool_call_count INTEGER,
|
|
@@ -308,7 +312,8 @@ CREATE TABLE IF NOT EXISTS session_insights (
|
|
|
308
312
|
* re-derives on the next `agents insights` instead of silently reporting stale
|
|
309
313
|
* numbers alongside fresh ones. Same role as RESOURCE_INDEX_VERSION.
|
|
310
314
|
*/
|
|
311
|
-
|
|
315
|
+
/** Bump when facet extraction changes so cached rows recompute (stalls-by-model v6). */
|
|
316
|
+
export const INSIGHTS_EXTRACTOR_VERSION = 6;
|
|
312
317
|
let dbInstance = null;
|
|
313
318
|
/**
|
|
314
319
|
* Apply schema migrations from `fromVersion` → SCHEMA_VERSION. The new
|
|
@@ -875,6 +880,25 @@ function migrateSchema(db, fromVersion) {
|
|
|
875
880
|
FROM tool_calls;
|
|
876
881
|
`);
|
|
877
882
|
}
|
|
883
|
+
if (fromVersion < 37) {
|
|
884
|
+
// v36 -> v37: persist the burn SPLIT (uncached input / cache-read /
|
|
885
|
+
// cache-write) and a second "no-cache" cost per session, so `agents output`
|
|
886
|
+
// can report the token split and a --pricing no-cache scenario (RUSH-2287).
|
|
887
|
+
// These are new nullable columns — do NOT flush scan_ledger (adding a column
|
|
888
|
+
// must keep warm session ledgers warm, the contract the v33->v34 note above
|
|
889
|
+
// states). Pre-upgrade rows stay NULL for the split until their transcript is
|
|
890
|
+
// re-scanned; `agents output` reports the split only where it is present, so
|
|
891
|
+
// an absent split reads as "not available for this session", never as zero.
|
|
892
|
+
const cols = new Set(db.prepare(`PRAGMA table_info(sessions)`).all().map((c) => c.name));
|
|
893
|
+
if (!cols.has('input_tokens'))
|
|
894
|
+
db.exec(`ALTER TABLE sessions ADD COLUMN input_tokens INTEGER`);
|
|
895
|
+
if (!cols.has('cache_read_tokens'))
|
|
896
|
+
db.exec(`ALTER TABLE sessions ADD COLUMN cache_read_tokens INTEGER`);
|
|
897
|
+
if (!cols.has('cache_write_tokens'))
|
|
898
|
+
db.exec(`ALTER TABLE sessions ADD COLUMN cache_write_tokens INTEGER`);
|
|
899
|
+
if (!cols.has('cost_usd_nocache'))
|
|
900
|
+
db.exec(`ALTER TABLE sessions ADD COLUMN cost_usd_nocache REAL`);
|
|
901
|
+
}
|
|
878
902
|
}
|
|
879
903
|
/**
|
|
880
904
|
* Stamp `account_key` / `account_org` / `account` on every Claude row from its
|
|
@@ -1369,7 +1393,8 @@ const upsertSessionStmt = (db) => db.prepare(`
|
|
|
1369
1393
|
id, short_id, agent, origin, routine_name, routine_run_id,
|
|
1370
1394
|
version, account, account_key, account_org, mode, timestamp, last_activity,
|
|
1371
1395
|
project, cwd, git_branch, topic, label, message_count, token_count,
|
|
1372
|
-
output_tokens,
|
|
1396
|
+
output_tokens, input_tokens, cache_read_tokens, cache_write_tokens,
|
|
1397
|
+
cost_usd, cost_usd_nocache, duration_ms, model, tool_call_count,
|
|
1373
1398
|
file_path, file_mtime_ms, file_size, scanned_at, is_team_origin,
|
|
1374
1399
|
pr_url, pr_number, worktree_slug, ticket_id, spawned_team, plan, todos,
|
|
1375
1400
|
recent_directories_touched, linear_project, linear_project_url, machine,
|
|
@@ -1378,7 +1403,8 @@ const upsertSessionStmt = (db) => db.prepare(`
|
|
|
1378
1403
|
@id, @short_id, @agent, @origin, @routine_name, @routine_run_id,
|
|
1379
1404
|
@version, @account, @account_key, @account_org, @mode, @timestamp, @last_activity,
|
|
1380
1405
|
@project, @cwd, @git_branch, @topic, @label, @message_count, @token_count,
|
|
1381
|
-
@output_tokens, @
|
|
1406
|
+
@output_tokens, @input_tokens, @cache_read_tokens, @cache_write_tokens,
|
|
1407
|
+
@cost_usd, @cost_usd_nocache, @duration_ms, @model, @tool_call_count,
|
|
1382
1408
|
@file_path, @file_mtime_ms, @file_size, @scanned_at, @is_team_origin,
|
|
1383
1409
|
@pr_url, @pr_number, @worktree_slug, @ticket_id, @spawned_team, @plan, @todos,
|
|
1384
1410
|
@recent_directories_touched, @linear_project, @linear_project_url, @machine,
|
|
@@ -1413,7 +1439,11 @@ const upsertSessionStmt = (db) => db.prepare(`
|
|
|
1413
1439
|
message_count = excluded.message_count,
|
|
1414
1440
|
token_count = excluded.token_count,
|
|
1415
1441
|
output_tokens = excluded.output_tokens,
|
|
1442
|
+
input_tokens = excluded.input_tokens,
|
|
1443
|
+
cache_read_tokens = excluded.cache_read_tokens,
|
|
1444
|
+
cache_write_tokens = excluded.cache_write_tokens,
|
|
1416
1445
|
cost_usd = excluded.cost_usd,
|
|
1446
|
+
cost_usd_nocache = excluded.cost_usd_nocache,
|
|
1417
1447
|
duration_ms = excluded.duration_ms,
|
|
1418
1448
|
model = excluded.model,
|
|
1419
1449
|
tool_call_count = excluded.tool_call_count,
|
|
@@ -1652,7 +1682,11 @@ export function upsertSession(meta, content, scan) {
|
|
|
1652
1682
|
message_count: meta.messageCount ?? null,
|
|
1653
1683
|
token_count: meta.tokenCount ?? null,
|
|
1654
1684
|
output_tokens: meta.outputTokens ?? null,
|
|
1685
|
+
input_tokens: meta.inputTokens ?? null,
|
|
1686
|
+
cache_read_tokens: meta.cacheReadTokens ?? null,
|
|
1687
|
+
cache_write_tokens: meta.cacheWriteTokens ?? null,
|
|
1655
1688
|
cost_usd: meta.costUsd ?? null,
|
|
1689
|
+
cost_usd_nocache: meta.costUsdNoCache ?? null,
|
|
1656
1690
|
duration_ms: meta.durationMs ?? null,
|
|
1657
1691
|
model: meta.model ?? null,
|
|
1658
1692
|
tool_call_count: meta.toolCallCount ?? null,
|
|
@@ -1832,7 +1866,11 @@ export function upsertSessionsBatch(entries) {
|
|
|
1832
1866
|
message_count: meta.messageCount ?? null,
|
|
1833
1867
|
token_count: meta.tokenCount ?? null,
|
|
1834
1868
|
output_tokens: meta.outputTokens ?? null,
|
|
1869
|
+
input_tokens: meta.inputTokens ?? null,
|
|
1870
|
+
cache_read_tokens: meta.cacheReadTokens ?? null,
|
|
1871
|
+
cache_write_tokens: meta.cacheWriteTokens ?? null,
|
|
1835
1872
|
cost_usd: meta.costUsd ?? null,
|
|
1873
|
+
cost_usd_nocache: meta.costUsdNoCache ?? null,
|
|
1836
1874
|
duration_ms: meta.durationMs ?? null,
|
|
1837
1875
|
model: meta.model ?? null,
|
|
1838
1876
|
tool_call_count: meta.toolCallCount ?? null,
|
|
@@ -2046,7 +2084,11 @@ function rowToMeta(row) {
|
|
|
2046
2084
|
messageCount: row.message_count ?? undefined,
|
|
2047
2085
|
tokenCount: row.token_count ?? undefined,
|
|
2048
2086
|
outputTokens: row.output_tokens ?? undefined,
|
|
2087
|
+
inputTokens: row.input_tokens ?? undefined,
|
|
2088
|
+
cacheReadTokens: row.cache_read_tokens ?? undefined,
|
|
2089
|
+
cacheWriteTokens: row.cache_write_tokens ?? undefined,
|
|
2049
2090
|
costUsd: row.cost_usd ?? undefined,
|
|
2091
|
+
costUsdNoCache: row.cost_usd_nocache ?? undefined,
|
|
2050
2092
|
durationMs: row.duration_ms ?? undefined,
|
|
2051
2093
|
model: row.model ?? undefined,
|
|
2052
2094
|
toolCallCount: row.tool_call_count ?? undefined,
|
|
@@ -2494,10 +2536,17 @@ export function queryUsageRollup(options) {
|
|
|
2494
2536
|
THEN account_org || ' <' || account || '>' END) AS label,`
|
|
2495
2537
|
: ''}
|
|
2496
2538
|
IFNULL(SUM(cost_usd), 0) AS costUsd,
|
|
2539
|
+
-- A session with a cost but no persisted no-cache figure records no cache
|
|
2540
|
+
-- split, so its no-cache cost equals its actual cost — fall back to cost_usd
|
|
2541
|
+
-- so it still contributes to the scenario total rather than dropping to 0.
|
|
2542
|
+
IFNULL(SUM(COALESCE(cost_usd_nocache, cost_usd)), 0) AS costUsdNoCache,
|
|
2497
2543
|
IFNULL(SUM(duration_ms), 0) AS durationMs,
|
|
2498
2544
|
COUNT(*) AS sessionCount,
|
|
2499
2545
|
IFNULL(SUM(token_count), 0) AS tokenCount,
|
|
2500
|
-
IFNULL(SUM(output_tokens), 0) AS outputTokens
|
|
2546
|
+
IFNULL(SUM(output_tokens), 0) AS outputTokens,
|
|
2547
|
+
IFNULL(SUM(input_tokens), 0) AS inputTokens,
|
|
2548
|
+
IFNULL(SUM(cache_read_tokens), 0) AS cacheReadTokens,
|
|
2549
|
+
IFNULL(SUM(cache_write_tokens), 0) AS cacheWriteTokens
|
|
2501
2550
|
FROM sessions
|
|
2502
2551
|
${clause}
|
|
2503
2552
|
GROUP BY key
|
|
@@ -71,8 +71,14 @@ interface ClaudeSessionScan {
|
|
|
71
71
|
tokenCount?: number;
|
|
72
72
|
/** Real generated (output) tokens, excluding cache-read/-write context. */
|
|
73
73
|
outputTokens?: number;
|
|
74
|
+
/** Burn split — uncached input / cache-read / cache-write tokens (RUSH-2287). */
|
|
75
|
+
inputTokens?: number;
|
|
76
|
+
cacheReadTokens?: number;
|
|
77
|
+
cacheWriteTokens?: number;
|
|
74
78
|
/** Total USD cost accumulated from per-(model, direction) token usage. */
|
|
75
79
|
costUsd?: number;
|
|
80
|
+
/** USD cost with cache read/write repriced at the input rate (RUSH-2287). */
|
|
81
|
+
costUsdNoCache?: number;
|
|
76
82
|
/** Wall-clock duration in ms between the first and last timestamped event. */
|
|
77
83
|
durationMs?: number;
|
|
78
84
|
/** ISO time of the last timestamped event — the session's last activity. */
|
|
@@ -122,7 +128,13 @@ interface CodexSessionScan {
|
|
|
122
128
|
tokenCount?: number;
|
|
123
129
|
/** Real generated (output) tokens, excluding cache-read/-write context. */
|
|
124
130
|
outputTokens?: number;
|
|
131
|
+
/** Burn split — uncached input / cache-read tokens (Codex has no cache-write) (RUSH-2287). */
|
|
132
|
+
inputTokens?: number;
|
|
133
|
+
cacheReadTokens?: number;
|
|
134
|
+
cacheWriteTokens?: number;
|
|
125
135
|
costUsd?: number;
|
|
136
|
+
/** USD cost with cache read repriced at the input rate (RUSH-2287). */
|
|
137
|
+
costUsdNoCache?: number;
|
|
126
138
|
durationMs?: number;
|
|
127
139
|
lastActivity?: string;
|
|
128
140
|
contentText?: string;
|
|
@@ -417,8 +429,12 @@ export interface ClaudeParseState {
|
|
|
417
429
|
toolCallCount: number;
|
|
418
430
|
tokenCount: number;
|
|
419
431
|
outputTokens: number;
|
|
432
|
+
inputTokens: number;
|
|
433
|
+
cacheReadTokens: number;
|
|
434
|
+
cacheWriteTokens: number;
|
|
420
435
|
sawTokenCount: boolean;
|
|
421
436
|
costUsd: number;
|
|
437
|
+
costUsdNoCache: number;
|
|
422
438
|
sawCost: boolean;
|
|
423
439
|
firstTsMs?: number;
|
|
424
440
|
lastTsMs?: number;
|
|
@@ -468,7 +484,7 @@ export declare function scanClaudeSession(filePath: string): Promise<ClaudeSessi
|
|
|
468
484
|
* exact even when the recent window is smaller than the true count.
|
|
469
485
|
*/
|
|
470
486
|
export interface ClaudeParserState {
|
|
471
|
-
v:
|
|
487
|
+
v: 3;
|
|
472
488
|
offset: number;
|
|
473
489
|
jsonlDroppingOversizedLine?: boolean;
|
|
474
490
|
timestamp?: string;
|
|
@@ -487,9 +503,13 @@ export interface ClaudeParserState {
|
|
|
487
503
|
toolCallCount: number;
|
|
488
504
|
tokenCount: number;
|
|
489
505
|
outputTokens: number;
|
|
506
|
+
inputTokens: number;
|
|
507
|
+
cacheReadTokens: number;
|
|
508
|
+
cacheWriteTokens: number;
|
|
490
509
|
sawTokenCount: boolean;
|
|
491
510
|
sawCost: boolean;
|
|
492
511
|
costUsd: number;
|
|
512
|
+
costUsdNoCache: number;
|
|
493
513
|
seenIdsSize: number;
|
|
494
514
|
seenIdsRecent: string[];
|
|
495
515
|
sawPrCreate: boolean;
|