@phnx-labs/agents-cli 1.22.26 → 1.22.28
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 +293 -0
- package/README.md +80 -27
- 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 +71 -26
- package/dist/commands/hosts.js +4 -3
- package/dist/commands/insights.js +39 -5
- package/dist/commands/output.js +100 -25
- package/dist/commands/perf.d.ts +10 -0
- package/dist/commands/perf.js +14 -6
- package/dist/commands/projects.js +24 -10
- package/dist/commands/resume.d.ts +11 -0
- package/dist/commands/resume.js +51 -0
- package/dist/commands/routines.js +31 -4
- package/dist/commands/run-account-picker.d.ts +37 -0
- package/dist/commands/run-account-picker.js +101 -17
- package/dist/commands/secrets.js +86 -2
- package/dist/commands/sessions-picker.js +12 -7
- package/dist/commands/sessions.d.ts +73 -7
- package/dist/commands/sessions.js +391 -49
- package/dist/commands/ssh.js +184 -11
- package/dist/commands/teams.js +22 -11
- package/dist/index.js +16 -20
- package/dist/lib/activity.js +45 -99
- 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 +56 -3
- package/dist/lib/cloud/host.js +1 -0
- 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/resolve-target.d.ts +1 -0
- package/dist/lib/devices/resolve-target.js +9 -2
- 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/events.d.ts +1 -1
- package/dist/lib/events.js +2 -2
- 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/git.d.ts +9 -3
- package/dist/lib/git.js +39 -22
- package/dist/lib/hooks/profile.d.ts +12 -1
- package/dist/lib/hooks/profile.js +6 -1
- package/dist/lib/hooks.d.ts +8 -5
- package/dist/lib/hooks.js +14 -7
- package/dist/lib/hosts/dispatch.d.ts +10 -0
- package/dist/lib/hosts/dispatch.js +85 -25
- package/dist/lib/hosts/logs.js +11 -1
- package/dist/lib/hosts/passthrough.d.ts +27 -0
- package/dist/lib/hosts/passthrough.js +19 -10
- package/dist/lib/hosts/progress.d.ts +12 -1
- package/dist/lib/hosts/progress.js +41 -8
- package/dist/lib/hosts/providers/devices.js +1 -0
- package/dist/lib/hosts/ready.d.ts +45 -4
- package/dist/lib/hosts/ready.js +124 -20
- package/dist/lib/hosts/reconcile.d.ts +2 -1
- package/dist/lib/hosts/reconcile.js +24 -7
- package/dist/lib/hosts/reconnect.d.ts +45 -12
- package/dist/lib/hosts/reconnect.js +94 -36
- package/dist/lib/hosts/registry.d.ts +2 -2
- package/dist/lib/hosts/registry.js +4 -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 +3 -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/menubar/snapshot.js +15 -2
- package/dist/lib/models.d.ts +42 -6
- package/dist/lib/models.js +55 -153
- package/dist/lib/perf/db.js +16 -3
- package/dist/lib/perf/types.d.ts +12 -1
- 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/project-probe.d.ts +22 -3
- package/dist/lib/project-probe.js +105 -17
- package/dist/lib/project-status.d.ts +9 -0
- package/dist/lib/project-status.js +15 -0
- package/dist/lib/redact.js +8 -3
- package/dist/lib/refresh.js +22 -11
- 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/rotate.d.ts +22 -0
- package/dist/lib/rotate.js +26 -0
- 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.d.ts +19 -3
- package/dist/lib/secrets/agent.js +61 -22
- package/dist/lib/secrets/audit.d.ts +1 -1
- package/dist/lib/secrets/audit.js +2 -0
- package/dist/lib/secrets/bundles.d.ts +3 -3
- package/dist/lib/secrets/bundles.js +15 -5
- package/dist/lib/secrets/filestore.d.ts +2 -0
- package/dist/lib/secrets/filestore.js +4 -0
- package/dist/lib/secrets/lease.d.ts +25 -0
- package/dist/lib/secrets/lease.js +44 -0
- package/dist/lib/secrets/session-store.d.ts +7 -0
- package/dist/lib/secrets/session-store.js +21 -0
- package/dist/lib/self-update.d.ts +20 -5
- package/dist/lib/self-update.js +93 -16
- package/dist/lib/session/active.d.ts +49 -0
- package/dist/lib/session/active.js +175 -11
- package/dist/lib/session/bash-command.d.ts +16 -0
- package/dist/lib/session/bash-command.js +65 -0
- package/dist/lib/session/db.d.ts +23 -2
- package/dist/lib/session/db.js +86 -20
- 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-bundle.js +1 -1
- 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/staleness/detectors/skills.d.ts +2 -0
- package/dist/lib/staleness/detectors/skills.js +17 -2
- package/dist/lib/staleness/index.d.ts +10 -1
- package/dist/lib/staleness/index.js +28 -3
- package/dist/lib/startup/command-registry.d.ts +1 -0
- package/dist/lib/startup/command-registry.js +2 -0
- package/dist/lib/teams/agents.d.ts +1 -0
- package/dist/lib/teams/agents.js +38 -11
- package/dist/lib/teams/placement-probe.d.ts +1 -1
- package/dist/lib/teams/placement-probe.js +24 -24
- package/dist/lib/teams/remoteWorktree.d.ts +11 -7
- package/dist/lib/teams/remoteWorktree.js +29 -27
- 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/dist/lib/versions.d.ts +1 -0
- package/dist/lib/versions.js +40 -22
- package/package.json +3 -1
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
|
|
@@ -1019,6 +1043,7 @@ export function getDB() {
|
|
|
1019
1043
|
}
|
|
1020
1044
|
/** Close the cached database connection. */
|
|
1021
1045
|
export function closeDB() {
|
|
1046
|
+
clearSessionExistenceCache();
|
|
1022
1047
|
if (dbInstance) {
|
|
1023
1048
|
dbInstance.close();
|
|
1024
1049
|
dbInstance = null;
|
|
@@ -1369,7 +1394,8 @@ const upsertSessionStmt = (db) => db.prepare(`
|
|
|
1369
1394
|
id, short_id, agent, origin, routine_name, routine_run_id,
|
|
1370
1395
|
version, account, account_key, account_org, mode, timestamp, last_activity,
|
|
1371
1396
|
project, cwd, git_branch, topic, label, message_count, token_count,
|
|
1372
|
-
output_tokens,
|
|
1397
|
+
output_tokens, input_tokens, cache_read_tokens, cache_write_tokens,
|
|
1398
|
+
cost_usd, cost_usd_nocache, duration_ms, model, tool_call_count,
|
|
1373
1399
|
file_path, file_mtime_ms, file_size, scanned_at, is_team_origin,
|
|
1374
1400
|
pr_url, pr_number, worktree_slug, ticket_id, spawned_team, plan, todos,
|
|
1375
1401
|
recent_directories_touched, linear_project, linear_project_url, machine,
|
|
@@ -1378,7 +1404,8 @@ const upsertSessionStmt = (db) => db.prepare(`
|
|
|
1378
1404
|
@id, @short_id, @agent, @origin, @routine_name, @routine_run_id,
|
|
1379
1405
|
@version, @account, @account_key, @account_org, @mode, @timestamp, @last_activity,
|
|
1380
1406
|
@project, @cwd, @git_branch, @topic, @label, @message_count, @token_count,
|
|
1381
|
-
@output_tokens, @
|
|
1407
|
+
@output_tokens, @input_tokens, @cache_read_tokens, @cache_write_tokens,
|
|
1408
|
+
@cost_usd, @cost_usd_nocache, @duration_ms, @model, @tool_call_count,
|
|
1382
1409
|
@file_path, @file_mtime_ms, @file_size, @scanned_at, @is_team_origin,
|
|
1383
1410
|
@pr_url, @pr_number, @worktree_slug, @ticket_id, @spawned_team, @plan, @todos,
|
|
1384
1411
|
@recent_directories_touched, @linear_project, @linear_project_url, @machine,
|
|
@@ -1413,7 +1440,11 @@ const upsertSessionStmt = (db) => db.prepare(`
|
|
|
1413
1440
|
message_count = excluded.message_count,
|
|
1414
1441
|
token_count = excluded.token_count,
|
|
1415
1442
|
output_tokens = excluded.output_tokens,
|
|
1443
|
+
input_tokens = excluded.input_tokens,
|
|
1444
|
+
cache_read_tokens = excluded.cache_read_tokens,
|
|
1445
|
+
cache_write_tokens = excluded.cache_write_tokens,
|
|
1416
1446
|
cost_usd = excluded.cost_usd,
|
|
1447
|
+
cost_usd_nocache = excluded.cost_usd_nocache,
|
|
1417
1448
|
duration_ms = excluded.duration_ms,
|
|
1418
1449
|
model = excluded.model,
|
|
1419
1450
|
tool_call_count = excluded.tool_call_count,
|
|
@@ -1652,7 +1683,11 @@ export function upsertSession(meta, content, scan) {
|
|
|
1652
1683
|
message_count: meta.messageCount ?? null,
|
|
1653
1684
|
token_count: meta.tokenCount ?? null,
|
|
1654
1685
|
output_tokens: meta.outputTokens ?? null,
|
|
1686
|
+
input_tokens: meta.inputTokens ?? null,
|
|
1687
|
+
cache_read_tokens: meta.cacheReadTokens ?? null,
|
|
1688
|
+
cache_write_tokens: meta.cacheWriteTokens ?? null,
|
|
1655
1689
|
cost_usd: meta.costUsd ?? null,
|
|
1690
|
+
cost_usd_nocache: meta.costUsdNoCache ?? null,
|
|
1656
1691
|
duration_ms: meta.durationMs ?? null,
|
|
1657
1692
|
model: meta.model ?? null,
|
|
1658
1693
|
tool_call_count: meta.toolCallCount ?? null,
|
|
@@ -1832,7 +1867,11 @@ export function upsertSessionsBatch(entries) {
|
|
|
1832
1867
|
message_count: meta.messageCount ?? null,
|
|
1833
1868
|
token_count: meta.tokenCount ?? null,
|
|
1834
1869
|
output_tokens: meta.outputTokens ?? null,
|
|
1870
|
+
input_tokens: meta.inputTokens ?? null,
|
|
1871
|
+
cache_read_tokens: meta.cacheReadTokens ?? null,
|
|
1872
|
+
cache_write_tokens: meta.cacheWriteTokens ?? null,
|
|
1835
1873
|
cost_usd: meta.costUsd ?? null,
|
|
1874
|
+
cost_usd_nocache: meta.costUsdNoCache ?? null,
|
|
1836
1875
|
duration_ms: meta.durationMs ?? null,
|
|
1837
1876
|
model: meta.model ?? null,
|
|
1838
1877
|
tool_call_count: meta.toolCallCount ?? null,
|
|
@@ -2046,7 +2085,11 @@ function rowToMeta(row) {
|
|
|
2046
2085
|
messageCount: row.message_count ?? undefined,
|
|
2047
2086
|
tokenCount: row.token_count ?? undefined,
|
|
2048
2087
|
outputTokens: row.output_tokens ?? undefined,
|
|
2088
|
+
inputTokens: row.input_tokens ?? undefined,
|
|
2089
|
+
cacheReadTokens: row.cache_read_tokens ?? undefined,
|
|
2090
|
+
cacheWriteTokens: row.cache_write_tokens ?? undefined,
|
|
2049
2091
|
costUsd: row.cost_usd ?? undefined,
|
|
2092
|
+
costUsdNoCache: row.cost_usd_nocache ?? undefined,
|
|
2050
2093
|
durationMs: row.duration_ms ?? undefined,
|
|
2051
2094
|
model: row.model ?? undefined,
|
|
2052
2095
|
toolCallCount: row.tool_call_count ?? undefined,
|
|
@@ -2235,17 +2278,18 @@ function buildSessionWhere(options) {
|
|
|
2235
2278
|
const clause = where.length > 0 ? `WHERE ${where.join(' AND ')}` : '';
|
|
2236
2279
|
return { clause, params };
|
|
2237
2280
|
}
|
|
2238
|
-
|
|
2239
|
-
|
|
2240
|
-
|
|
2241
|
-
|
|
2242
|
-
|
|
2243
|
-
|
|
2244
|
-
|
|
2245
|
-
|
|
2246
|
-
|
|
2247
|
-
|
|
2248
|
-
|
|
2281
|
+
const directoryMembershipCache = new Map();
|
|
2282
|
+
let directoryMembershipSweepCount = 0;
|
|
2283
|
+
const DIRECTORY_MTIME_SETTLE_MS = 2_000;
|
|
2284
|
+
/** Process-local diagnostics for the real-filesystem existence-cache tests. */
|
|
2285
|
+
export function getSessionExistenceCacheStats() {
|
|
2286
|
+
return { sweeps: directoryMembershipSweepCount };
|
|
2287
|
+
}
|
|
2288
|
+
/** Clear process-local directory membership state when the session DB closes. */
|
|
2289
|
+
function clearSessionExistenceCache() {
|
|
2290
|
+
directoryMembershipCache.clear();
|
|
2291
|
+
directoryMembershipSweepCount = 0;
|
|
2292
|
+
}
|
|
2249
2293
|
function findMissingFilePaths(filePaths) {
|
|
2250
2294
|
const byDir = new Map();
|
|
2251
2295
|
for (const p of filePaths) {
|
|
@@ -2261,21 +2305,36 @@ function findMissingFilePaths(filePaths) {
|
|
|
2261
2305
|
for (const [dir, basenames] of byDir) {
|
|
2262
2306
|
let entries;
|
|
2263
2307
|
try {
|
|
2264
|
-
|
|
2308
|
+
const stat = fs.statSync(dir);
|
|
2309
|
+
const cached = directoryMembershipCache.get(dir);
|
|
2310
|
+
const now = Date.now();
|
|
2311
|
+
const settled = now - stat.mtimeMs > DIRECTORY_MTIME_SETTLE_MS;
|
|
2312
|
+
const fresh = cached && now - cached.cachedAtMs <= DIRECTORY_MTIME_SETTLE_MS;
|
|
2313
|
+
if (settled && fresh && cached.mtimeMs === stat.mtimeMs && cached.size === stat.size) {
|
|
2314
|
+
entries = cached.entries;
|
|
2315
|
+
}
|
|
2316
|
+
else {
|
|
2317
|
+
entries = new Set(fs.readdirSync(dir));
|
|
2318
|
+
directoryMembershipCache.set(dir, { mtimeMs: stat.mtimeMs, size: stat.size, cachedAtMs: now, entries });
|
|
2319
|
+
directoryMembershipSweepCount++;
|
|
2320
|
+
}
|
|
2265
2321
|
}
|
|
2266
2322
|
catch {
|
|
2323
|
+
directoryMembershipCache.delete(dir);
|
|
2267
2324
|
// Directory itself is gone (or unreadable) — every file in it is missing.
|
|
2268
2325
|
// Also covers the race where readdir loses to a concurrent delete: fall
|
|
2269
2326
|
// back to a direct stat rather than assuming existence.
|
|
2270
2327
|
for (const base of basenames) {
|
|
2271
|
-
|
|
2272
|
-
|
|
2328
|
+
const filePath = path.join(dir, base);
|
|
2329
|
+
if (!fs.existsSync(filePath))
|
|
2330
|
+
missing.add(filePath);
|
|
2273
2331
|
}
|
|
2274
2332
|
continue;
|
|
2275
2333
|
}
|
|
2276
2334
|
for (const base of basenames) {
|
|
2335
|
+
const filePath = path.join(dir, base);
|
|
2277
2336
|
if (!entries.has(base))
|
|
2278
|
-
missing.add(
|
|
2337
|
+
missing.add(filePath);
|
|
2279
2338
|
}
|
|
2280
2339
|
}
|
|
2281
2340
|
return missing;
|
|
@@ -2494,10 +2553,17 @@ export function queryUsageRollup(options) {
|
|
|
2494
2553
|
THEN account_org || ' <' || account || '>' END) AS label,`
|
|
2495
2554
|
: ''}
|
|
2496
2555
|
IFNULL(SUM(cost_usd), 0) AS costUsd,
|
|
2556
|
+
-- A session with a cost but no persisted no-cache figure records no cache
|
|
2557
|
+
-- split, so its no-cache cost equals its actual cost — fall back to cost_usd
|
|
2558
|
+
-- so it still contributes to the scenario total rather than dropping to 0.
|
|
2559
|
+
IFNULL(SUM(COALESCE(cost_usd_nocache, cost_usd)), 0) AS costUsdNoCache,
|
|
2497
2560
|
IFNULL(SUM(duration_ms), 0) AS durationMs,
|
|
2498
2561
|
COUNT(*) AS sessionCount,
|
|
2499
2562
|
IFNULL(SUM(token_count), 0) AS tokenCount,
|
|
2500
|
-
IFNULL(SUM(output_tokens), 0) AS outputTokens
|
|
2563
|
+
IFNULL(SUM(output_tokens), 0) AS outputTokens,
|
|
2564
|
+
IFNULL(SUM(input_tokens), 0) AS inputTokens,
|
|
2565
|
+
IFNULL(SUM(cache_read_tokens), 0) AS cacheReadTokens,
|
|
2566
|
+
IFNULL(SUM(cache_write_tokens), 0) AS cacheWriteTokens
|
|
2501
2567
|
FROM sessions
|
|
2502
2568
|
${clause}
|
|
2503
2569
|
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;
|
|
@@ -20,6 +20,8 @@ import { parseTimeFilter } from './relative-time.js';
|
|
|
20
20
|
const execFileAsync = promisify(execFile);
|
|
21
21
|
import { AGENTS, agentConfigDirName, getCliVersion } from '../agents.js';
|
|
22
22
|
import { walkForFilesWithStat } from '../fs-walk.js';
|
|
23
|
+
import { hasCommand } from '../cli-resources.js';
|
|
24
|
+
import { execFileShellSpec } from '../platform/exec.js';
|
|
23
25
|
import { getConfigSymlinkVersion } from '../shims.js';
|
|
24
26
|
import { SESSION_AGENTS } from './types.js';
|
|
25
27
|
import { deriveShortId } from './short-id.js';
|
|
@@ -28,7 +30,7 @@ import { extractSessionTopic, extractSlashCommandName, extractSlashCommandFromTo
|
|
|
28
30
|
import { isSkillInvocation, extractSkills, extractSlashCommands } from './highlights.js';
|
|
29
31
|
import { parseAntigravity, parseCursor } from './parse.js';
|
|
30
32
|
import { extractPrUrl, detectWorktree, detectTicket, isPrCreateCommand, detectSpawnedTeam, isTicketCreateTool, extractCreatedTicket, extractRecentDirectoriesTouched, extractTodoProgressFromEvents } from './state.js';
|
|
31
|
-
import { costOfUsage } from '../pricing/index.js';
|
|
33
|
+
import { costOfUsage, costOfUsageNoCache } from '../pricing/index.js';
|
|
32
34
|
import { machineForSessionFile } from './origin-machine.js';
|
|
33
35
|
export { machineForSessionFile } from './origin-machine.js';
|
|
34
36
|
import { mapBounded } from '../concurrency.js';
|
|
@@ -1096,7 +1098,11 @@ async function readClaudeMeta(filePath, sessionId, scanStamp, priorRow, label) {
|
|
|
1096
1098
|
toolCallCount: scan.toolCallCount,
|
|
1097
1099
|
tokenCount: scan.tokenCount,
|
|
1098
1100
|
outputTokens: scan.outputTokens,
|
|
1101
|
+
inputTokens: scan.inputTokens,
|
|
1102
|
+
cacheReadTokens: scan.cacheReadTokens,
|
|
1103
|
+
cacheWriteTokens: scan.cacheWriteTokens,
|
|
1099
1104
|
costUsd: scan.costUsd,
|
|
1105
|
+
costUsdNoCache: scan.costUsdNoCache,
|
|
1100
1106
|
durationMs: scan.durationMs,
|
|
1101
1107
|
isTeamOrigin,
|
|
1102
1108
|
prUrl: scan.prUrl,
|
|
@@ -1130,7 +1136,11 @@ async function readClaudeMeta(filePath, sessionId, scanStamp, priorRow, label) {
|
|
|
1130
1136
|
toolCallCount: scan.toolCallCount,
|
|
1131
1137
|
tokenCount: scan.tokenCount,
|
|
1132
1138
|
outputTokens: scan.outputTokens,
|
|
1139
|
+
inputTokens: scan.inputTokens,
|
|
1140
|
+
cacheReadTokens: scan.cacheReadTokens,
|
|
1141
|
+
cacheWriteTokens: scan.cacheWriteTokens,
|
|
1133
1142
|
costUsd: scan.costUsd,
|
|
1143
|
+
costUsdNoCache: scan.costUsdNoCache,
|
|
1134
1144
|
durationMs: scan.durationMs,
|
|
1135
1145
|
topic: scan.topic,
|
|
1136
1146
|
isTeamOrigin,
|
|
@@ -1438,7 +1448,11 @@ export async function readCodexMeta(filePath, resolveAccount, currentVersion, sc
|
|
|
1438
1448
|
messageCount: scan.messageCount,
|
|
1439
1449
|
tokenCount: scan.tokenCount,
|
|
1440
1450
|
outputTokens: scan.outputTokens,
|
|
1451
|
+
inputTokens: scan.inputTokens,
|
|
1452
|
+
cacheReadTokens: scan.cacheReadTokens,
|
|
1453
|
+
cacheWriteTokens: scan.cacheWriteTokens,
|
|
1441
1454
|
costUsd: scan.costUsd,
|
|
1455
|
+
costUsdNoCache: scan.costUsdNoCache,
|
|
1442
1456
|
durationMs: scan.durationMs,
|
|
1443
1457
|
account: resolveAccount?.(),
|
|
1444
1458
|
prUrl: scan.prUrl,
|
|
@@ -1576,8 +1590,11 @@ function readGeminiMeta(filePath, hashDir, projectMap, currentVersion) {
|
|
|
1576
1590
|
let messageCount = 0;
|
|
1577
1591
|
let tokenCount = 0;
|
|
1578
1592
|
let outputTokens = 0;
|
|
1593
|
+
let inputTokens = 0;
|
|
1594
|
+
let cacheReadTokens = 0;
|
|
1579
1595
|
let sawTokenCount = false;
|
|
1580
1596
|
let costUsd = 0;
|
|
1597
|
+
let costUsdNoCache = 0;
|
|
1581
1598
|
let sawCost = false;
|
|
1582
1599
|
let firstTsMs;
|
|
1583
1600
|
let lastTsMs;
|
|
@@ -1622,21 +1639,28 @@ function readGeminiMeta(filePath, hashDir, projectMap, currentVersion) {
|
|
|
1622
1639
|
(typeof gtk.output === 'number' ? gtk.output : 0) +
|
|
1623
1640
|
(typeof gtk.thoughts === 'number' ? gtk.thoughts : 0) +
|
|
1624
1641
|
(typeof gtk.tool === 'number' ? gtk.tool : 0);
|
|
1642
|
+
// Burn split (RUSH-2287): Gemini has uncached input + cache-read, no cache-write.
|
|
1643
|
+
if (typeof gtk.input === 'number')
|
|
1644
|
+
inputTokens += gtk.input;
|
|
1645
|
+
if (typeof gtk.cached === 'number')
|
|
1646
|
+
cacheReadTokens += gtk.cached;
|
|
1625
1647
|
}
|
|
1626
1648
|
// Per-message cost: directional tokens × this message's model price.
|
|
1627
1649
|
const msgModel = (typeof message.model === 'string' ? message.model : undefined) || sessionModel;
|
|
1628
1650
|
const tk = message.tokens;
|
|
1629
1651
|
if (msgModel && tk && typeof tk === 'object') {
|
|
1630
|
-
const
|
|
1652
|
+
const usage = {
|
|
1631
1653
|
model: msgModel,
|
|
1632
1654
|
inputTokens: typeof tk.input === 'number' ? tk.input : undefined,
|
|
1633
1655
|
outputTokens: (typeof tk.output === 'number' ? tk.output : 0) +
|
|
1634
1656
|
(typeof tk.thoughts === 'number' ? tk.thoughts : 0) +
|
|
1635
1657
|
(typeof tk.tool === 'number' ? tk.tool : 0),
|
|
1636
1658
|
cacheReadTokens: typeof tk.cached === 'number' ? tk.cached : undefined,
|
|
1637
|
-
}
|
|
1659
|
+
};
|
|
1660
|
+
const c = costOfUsage(usage);
|
|
1638
1661
|
if (c > 0) {
|
|
1639
1662
|
costUsd += c;
|
|
1663
|
+
costUsdNoCache += costOfUsageNoCache(usage);
|
|
1640
1664
|
sawCost = true;
|
|
1641
1665
|
}
|
|
1642
1666
|
}
|
|
@@ -1659,7 +1683,10 @@ function readGeminiMeta(filePath, hashDir, projectMap, currentVersion) {
|
|
|
1659
1683
|
messageCount,
|
|
1660
1684
|
tokenCount: sawTokenCount ? tokenCount : undefined,
|
|
1661
1685
|
outputTokens: sawTokenCount ? outputTokens : undefined,
|
|
1686
|
+
inputTokens: sawTokenCount ? inputTokens : undefined,
|
|
1687
|
+
cacheReadTokens: sawTokenCount ? cacheReadTokens : undefined,
|
|
1662
1688
|
costUsd: sawCost ? costUsd : undefined,
|
|
1689
|
+
costUsdNoCache: sawCost ? costUsdNoCache : undefined,
|
|
1663
1690
|
durationMs,
|
|
1664
1691
|
};
|
|
1665
1692
|
return { meta, content: userTexts.join('\n') };
|
|
@@ -2053,13 +2080,12 @@ async function scanOpenCodeIncremental() {
|
|
|
2053
2080
|
// ---------------------------------------------------------------------------
|
|
2054
2081
|
/** Scan active OpenClaw channels and cron jobs via the openclaw CLI. */
|
|
2055
2082
|
async function scanOpenClawIncremental() {
|
|
2056
|
-
// Check if openclaw is installed — silently skip if not.
|
|
2057
|
-
|
|
2058
|
-
|
|
2059
|
-
|
|
2060
|
-
|
|
2083
|
+
// Check if openclaw is installed — silently skip if not. `which` is POSIX-only
|
|
2084
|
+
// (Windows resolves PATH with `where`), so a bare `which` throws ENOENT on every
|
|
2085
|
+
// Windows run and silently disabled the entire OpenClaw scan there — its sessions
|
|
2086
|
+
// never reached the index (RUSH-2286). hasCommand() probes cross-platform.
|
|
2087
|
+
if (!hasCommand('openclaw'))
|
|
2061
2088
|
return;
|
|
2062
|
-
}
|
|
2063
2089
|
// TTL cache: skip subprocess calls if we scanned recently. Stored in the
|
|
2064
2090
|
// meta table so we skip even when no channels/cron exist to produce rows.
|
|
2065
2091
|
const db = getDB();
|
|
@@ -2073,8 +2099,13 @@ async function scanOpenClawIncremental() {
|
|
|
2073
2099
|
const scan = { fileMtimeMs: now, fileSize: 0 };
|
|
2074
2100
|
const entries = [];
|
|
2075
2101
|
try {
|
|
2076
|
-
|
|
2102
|
+
// On Windows `openclaw` resolves to a .cmd/.ps1 shim that execFile can't launch
|
|
2103
|
+
// directly; execFileShellSpec composes a shell-safe invocation there and is a
|
|
2104
|
+
// no-op passthrough on POSIX (RUSH-2286).
|
|
2105
|
+
const channelsSpec = execFileShellSpec('openclaw', ['channels', 'status']);
|
|
2106
|
+
const { stdout: output } = await execFileAsync(channelsSpec.command, channelsSpec.args, {
|
|
2077
2107
|
encoding: 'utf-8',
|
|
2108
|
+
shell: channelsSpec.shell,
|
|
2078
2109
|
});
|
|
2079
2110
|
for (const line of output.split('\n')) {
|
|
2080
2111
|
const match = line.match(/^-\s+\w+\s+(\S+)\s+\((\w+)\):\s*(.+)/);
|
|
@@ -2103,8 +2134,10 @@ async function scanOpenClawIncremental() {
|
|
|
2103
2134
|
/* channels command failed */
|
|
2104
2135
|
}
|
|
2105
2136
|
try {
|
|
2106
|
-
const
|
|
2137
|
+
const cronSpec = execFileShellSpec('openclaw', ['cron', 'list']);
|
|
2138
|
+
const { stdout: output } = await execFileAsync(cronSpec.command, cronSpec.args, {
|
|
2107
2139
|
encoding: 'utf-8',
|
|
2140
|
+
shell: cronSpec.shell,
|
|
2108
2141
|
});
|
|
2109
2142
|
const lines = output.split('\n');
|
|
2110
2143
|
for (let i = 1; i < lines.length; i++) {
|
|
@@ -2610,15 +2643,19 @@ async function readDroidMeta(filePath, currentVersion) {
|
|
|
2610
2643
|
const settings = readDroidSettings(filePath.replace(/\.jsonl$/, '.settings.json'));
|
|
2611
2644
|
const model = settings.model || scan.model;
|
|
2612
2645
|
const tokenCount = settings.tokenCount;
|
|
2613
|
-
|
|
2614
|
-
|
|
2646
|
+
// Droid records a full split (input / cache-read / cache-write / output) in its
|
|
2647
|
+
// settings sidecar, so both the actual and no-cache cost are derivable (RUSH-2287).
|
|
2648
|
+
const usageForCost = model && settings.usage
|
|
2649
|
+
? {
|
|
2615
2650
|
model,
|
|
2616
2651
|
inputTokens: settings.usage.inputTokens,
|
|
2617
2652
|
outputTokens: settings.usage.outputTokens,
|
|
2618
2653
|
cacheReadTokens: settings.usage.cacheReadTokens,
|
|
2619
2654
|
cacheCreationTokens: settings.usage.cacheCreationTokens,
|
|
2620
|
-
}
|
|
2621
|
-
:
|
|
2655
|
+
}
|
|
2656
|
+
: undefined;
|
|
2657
|
+
const costUsd = usageForCost ? costOfUsage(usageForCost) : 0;
|
|
2658
|
+
const costUsdNoCache = usageForCost ? costOfUsageNoCache(usageForCost) : 0;
|
|
2622
2659
|
const stat = safeStatSync(filePath);
|
|
2623
2660
|
const cwd = normalizeCwd(scan.cwd || '');
|
|
2624
2661
|
const meta = {
|
|
@@ -2636,7 +2673,11 @@ async function readDroidMeta(filePath, currentVersion) {
|
|
|
2636
2673
|
messageCount: scan.messageCount,
|
|
2637
2674
|
tokenCount,
|
|
2638
2675
|
outputTokens: settings.usage?.outputTokens,
|
|
2676
|
+
inputTokens: settings.usage?.inputTokens,
|
|
2677
|
+
cacheReadTokens: settings.usage?.cacheReadTokens,
|
|
2678
|
+
cacheWriteTokens: settings.usage?.cacheCreationTokens,
|
|
2639
2679
|
costUsd: costUsd > 0 ? costUsd : undefined,
|
|
2680
|
+
costUsdNoCache: costUsd > 0 ? costUsdNoCache : undefined,
|
|
2640
2681
|
durationMs: scan.durationMs,
|
|
2641
2682
|
};
|
|
2642
2683
|
return { meta, content: scan.contentText || '' };
|
|
@@ -2784,8 +2825,12 @@ export function initClaudeParseState() {
|
|
|
2784
2825
|
toolCallCount: 0,
|
|
2785
2826
|
tokenCount: 0,
|
|
2786
2827
|
outputTokens: 0,
|
|
2828
|
+
inputTokens: 0,
|
|
2829
|
+
cacheReadTokens: 0,
|
|
2830
|
+
cacheWriteTokens: 0,
|
|
2787
2831
|
sawTokenCount: false,
|
|
2788
2832
|
costUsd: 0,
|
|
2833
|
+
costUsdNoCache: 0,
|
|
2789
2834
|
sawCost: false,
|
|
2790
2835
|
firstTsMs: undefined,
|
|
2791
2836
|
lastTsMs: undefined,
|
|
@@ -2982,21 +3027,34 @@ export function applyClaudeLine(state, parsed) {
|
|
|
2982
3027
|
}
|
|
2983
3028
|
if (typeof usageObj?.output_tokens === 'number')
|
|
2984
3029
|
state.outputTokens += usageObj.output_tokens;
|
|
3030
|
+
// Burn split (RUSH-2287): accumulate the raw directional counts so `agents
|
|
3031
|
+
// output` can report uncached-input / cache-read / cache-write separately.
|
|
3032
|
+
if (usageObj && typeof usageObj === 'object') {
|
|
3033
|
+
if (typeof usageObj.input_tokens === 'number')
|
|
3034
|
+
state.inputTokens += usageObj.input_tokens;
|
|
3035
|
+
if (typeof usageObj.cache_read_input_tokens === 'number')
|
|
3036
|
+
state.cacheReadTokens += usageObj.cache_read_input_tokens;
|
|
3037
|
+
if (typeof usageObj.cache_creation_input_tokens === 'number')
|
|
3038
|
+
state.cacheWriteTokens += usageObj.cache_creation_input_tokens;
|
|
3039
|
+
}
|
|
2985
3040
|
// Per-assistant-message cost: each event carries its own model, so we
|
|
2986
3041
|
// multiply that event's raw token directions by that model's price.
|
|
2987
3042
|
const model = parsed.message?.model;
|
|
2988
3043
|
if (typeof model === 'string' && model)
|
|
2989
3044
|
state.model = model;
|
|
2990
3045
|
if (model && usageObj && typeof usageObj === 'object') {
|
|
2991
|
-
const
|
|
3046
|
+
const usageForCost = {
|
|
2992
3047
|
model,
|
|
2993
3048
|
inputTokens: usageObj.input_tokens,
|
|
2994
3049
|
outputTokens: usageObj.output_tokens,
|
|
2995
3050
|
cacheReadTokens: usageObj.cache_read_input_tokens,
|
|
2996
3051
|
cacheCreationTokens: usageObj.cache_creation_input_tokens,
|
|
2997
|
-
}
|
|
3052
|
+
};
|
|
3053
|
+
const eventCost = costOfUsage(usageForCost);
|
|
2998
3054
|
if (eventCost > 0) {
|
|
2999
3055
|
state.costUsd += eventCost;
|
|
3056
|
+
// No-cache scenario: reprice this same event's cache tokens at the input rate.
|
|
3057
|
+
state.costUsdNoCache += costOfUsageNoCache(usageForCost);
|
|
3000
3058
|
state.sawCost = true;
|
|
3001
3059
|
}
|
|
3002
3060
|
}
|
|
@@ -3026,7 +3084,11 @@ export function finalizeClaudeScan(state) {
|
|
|
3026
3084
|
toolCallCount: state.toolCallCount,
|
|
3027
3085
|
tokenCount: state.sawTokenCount ? state.tokenCount : undefined,
|
|
3028
3086
|
outputTokens: state.sawTokenCount ? state.outputTokens : undefined,
|
|
3087
|
+
inputTokens: state.sawTokenCount ? state.inputTokens : undefined,
|
|
3088
|
+
cacheReadTokens: state.sawTokenCount ? state.cacheReadTokens : undefined,
|
|
3089
|
+
cacheWriteTokens: state.sawTokenCount ? state.cacheWriteTokens : undefined,
|
|
3029
3090
|
costUsd: state.sawCost ? state.costUsd : undefined,
|
|
3091
|
+
costUsdNoCache: state.sawCost ? state.costUsdNoCache : undefined,
|
|
3030
3092
|
durationMs,
|
|
3031
3093
|
lastActivity: state.lastTsMs !== undefined ? new Date(state.lastTsMs).toISOString() : undefined,
|
|
3032
3094
|
contentText: state.userTexts.length > 0 ? state.userTexts.join('\n') : undefined,
|
|
@@ -3082,7 +3144,7 @@ export function serializeClaudeParserState(state, offset, jsonlDroppingOversized
|
|
|
3082
3144
|
: allIds;
|
|
3083
3145
|
const ticket = detectTicket(state.userTexts.join('\n') || undefined, state.gitBranch);
|
|
3084
3146
|
return {
|
|
3085
|
-
v:
|
|
3147
|
+
v: 3,
|
|
3086
3148
|
offset,
|
|
3087
3149
|
jsonlDroppingOversizedLine: jsonlDroppingOversizedLine || undefined,
|
|
3088
3150
|
timestamp: state.timestamp,
|
|
@@ -3101,9 +3163,13 @@ export function serializeClaudeParserState(state, offset, jsonlDroppingOversized
|
|
|
3101
3163
|
toolCallCount: state.toolCallCount,
|
|
3102
3164
|
tokenCount: state.tokenCount,
|
|
3103
3165
|
outputTokens: state.outputTokens,
|
|
3166
|
+
inputTokens: state.inputTokens,
|
|
3167
|
+
cacheReadTokens: state.cacheReadTokens,
|
|
3168
|
+
cacheWriteTokens: state.cacheWriteTokens,
|
|
3104
3169
|
sawTokenCount: state.sawTokenCount,
|
|
3105
3170
|
sawCost: state.sawCost,
|
|
3106
3171
|
costUsd: state.costUsd,
|
|
3172
|
+
costUsdNoCache: state.costUsdNoCache,
|
|
3107
3173
|
seenIdsSize: state.seenAssistantIds.size,
|
|
3108
3174
|
seenIdsRecent,
|
|
3109
3175
|
sawPrCreate: state.sawPrCreate,
|
|
@@ -3163,8 +3229,12 @@ export function hydrateClaudeParseState(prior) {
|
|
|
3163
3229
|
toolCallCount: prior.toolCallCount ?? 0,
|
|
3164
3230
|
tokenCount: prior.tokenCount,
|
|
3165
3231
|
outputTokens: prior.outputTokens,
|
|
3232
|
+
inputTokens: prior.inputTokens ?? 0,
|
|
3233
|
+
cacheReadTokens: prior.cacheReadTokens ?? 0,
|
|
3234
|
+
cacheWriteTokens: prior.cacheWriteTokens ?? 0,
|
|
3166
3235
|
sawTokenCount: prior.sawTokenCount,
|
|
3167
3236
|
costUsd: prior.costUsd,
|
|
3237
|
+
costUsdNoCache: prior.costUsdNoCache ?? 0,
|
|
3168
3238
|
sawCost: prior.sawCost,
|
|
3169
3239
|
firstTsMs: prior.firstTsMs,
|
|
3170
3240
|
lastTsMs: prior.lastTsMs,
|
|
@@ -3313,7 +3383,7 @@ function parsePriorClaudeState(row) {
|
|
|
3313
3383
|
return null;
|
|
3314
3384
|
try {
|
|
3315
3385
|
const parsed = JSON.parse(row.parserState);
|
|
3316
|
-
if (parsed?.v !==
|
|
3386
|
+
if (parsed?.v !== 3 || typeof parsed.offset !== 'number' || parsed.toolCalls?.v !== 1)
|
|
3317
3387
|
return null;
|
|
3318
3388
|
return parsed;
|
|
3319
3389
|
}
|
|
@@ -3469,17 +3539,34 @@ export function applyCodexLine(state, parsed) {
|
|
|
3469
3539
|
* exact return-building {@link scanCodexSession} used to run inline.
|
|
3470
3540
|
*/
|
|
3471
3541
|
export function finalizeCodexScan(state) {
|
|
3472
|
-
//
|
|
3542
|
+
// Codex reports one cumulative snapshot: uncached input, cached (cache-read)
|
|
3543
|
+
// input, and output+reasoning. It has no cache-write bucket. Derive the burn
|
|
3544
|
+
// split and both costs (actual + no-cache) from that final snapshot (RUSH-2287).
|
|
3545
|
+
const snap = state.lastTotalTokenUsage;
|
|
3546
|
+
const outputTokens = snap
|
|
3547
|
+
? (snap.output_tokens ?? 0) + (snap.reasoning_output_tokens ?? 0)
|
|
3548
|
+
: undefined;
|
|
3473
3549
|
let costUsd;
|
|
3474
|
-
|
|
3475
|
-
|
|
3550
|
+
let costUsdNoCache;
|
|
3551
|
+
let inputTokens;
|
|
3552
|
+
let cacheReadTokens;
|
|
3553
|
+
if (snap) {
|
|
3554
|
+
inputTokens = typeof snap.input_tokens === 'number' ? snap.input_tokens : undefined;
|
|
3555
|
+
cacheReadTokens = typeof snap.cached_input_tokens === 'number' ? snap.cached_input_tokens : undefined;
|
|
3556
|
+
}
|
|
3557
|
+
// Price the final cumulative token snapshot once, against the session model.
|
|
3558
|
+
if (state.model && snap) {
|
|
3559
|
+
const usage = {
|
|
3476
3560
|
model: state.model,
|
|
3477
|
-
inputTokens:
|
|
3478
|
-
outputTokens
|
|
3479
|
-
cacheReadTokens:
|
|
3480
|
-
}
|
|
3481
|
-
|
|
3561
|
+
inputTokens: snap.input_tokens,
|
|
3562
|
+
outputTokens,
|
|
3563
|
+
cacheReadTokens: snap.cached_input_tokens,
|
|
3564
|
+
};
|
|
3565
|
+
const c = costOfUsage(usage);
|
|
3566
|
+
if (c > 0) {
|
|
3482
3567
|
costUsd = c;
|
|
3568
|
+
costUsdNoCache = costOfUsageNoCache(usage);
|
|
3569
|
+
}
|
|
3483
3570
|
}
|
|
3484
3571
|
const durationMs = state.firstTsMs !== undefined && state.lastTsMs !== undefined && state.lastTsMs > state.firstTsMs
|
|
3485
3572
|
? state.lastTsMs - state.firstTsMs
|
|
@@ -3496,10 +3583,12 @@ export function finalizeCodexScan(state) {
|
|
|
3496
3583
|
topic: state.topic,
|
|
3497
3584
|
messageCount: state.messageCount,
|
|
3498
3585
|
tokenCount: state.tokenCount,
|
|
3499
|
-
outputTokens
|
|
3500
|
-
|
|
3501
|
-
|
|
3586
|
+
outputTokens,
|
|
3587
|
+
inputTokens,
|
|
3588
|
+
cacheReadTokens,
|
|
3589
|
+
cacheWriteTokens: undefined,
|
|
3502
3590
|
costUsd,
|
|
3591
|
+
costUsdNoCache,
|
|
3503
3592
|
durationMs,
|
|
3504
3593
|
lastActivity: state.lastTsMs !== undefined ? new Date(state.lastTsMs).toISOString() : undefined,
|
|
3505
3594
|
contentText: state.userTexts.length > 0 ? state.userTexts.join('\n') : undefined,
|
|
@@ -4457,10 +4546,13 @@ export function readGrokMeta(filePath, currentVersion) {
|
|
|
4457
4546
|
? summary.num_messages
|
|
4458
4547
|
: undefined;
|
|
4459
4548
|
// Grok records its managed home in summary.grok_home
|
|
4460
|
-
// (…/versions/grok/<version>/home/.grok) — recover the version from it.
|
|
4549
|
+
// (…/versions/grok/<version>/home/.grok) — recover the version from it. The
|
|
4550
|
+
// value is written by the Grok CLI in the writing host's native separators, so
|
|
4551
|
+
// a Windows-authored summary is backslash-separated; normalize to `/` before
|
|
4552
|
+
// matching or the version never resolves on Windows (RUSH-2286).
|
|
4461
4553
|
let embeddedVersion;
|
|
4462
4554
|
if (typeof summary?.grok_home === 'string') {
|
|
4463
|
-
embeddedVersion = summary.grok_home.match(/versions\/grok\/([^/]+)\//)?.[1];
|
|
4555
|
+
embeddedVersion = summary.grok_home.replace(/\\/g, '/').match(/versions\/grok\/([^/]+)\//)?.[1];
|
|
4464
4556
|
}
|
|
4465
4557
|
const meta = {
|
|
4466
4558
|
id: sessionId,
|