@phnx-labs/agents-cli 1.20.35 → 1.20.37
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 +91 -0
- package/LICENSE +185 -21
- package/README.md +8 -4
- package/dist/commands/audit.d.ts +14 -0
- package/dist/commands/audit.js +68 -0
- package/dist/commands/browser.js +82 -8
- package/dist/commands/check.d.ts +15 -0
- package/dist/commands/check.js +84 -0
- package/dist/commands/cloud.js +143 -3
- package/dist/commands/computer.js +81 -0
- package/dist/commands/daemon.js +4 -1
- package/dist/commands/doctor.js +1 -89
- package/dist/commands/events.js +3 -3
- package/dist/commands/exec.d.ts +19 -0
- package/dist/commands/exec.js +277 -11
- package/dist/commands/hosts.js +10 -6
- package/dist/commands/inspect.js +8 -9
- package/dist/commands/lock.d.ts +12 -0
- package/dist/commands/lock.js +70 -0
- package/dist/commands/message.d.ts +15 -0
- package/dist/commands/message.js +56 -0
- package/dist/commands/routines.js +101 -5
- package/dist/commands/secrets-migrate.js +106 -57
- package/dist/commands/secrets.d.ts +31 -18
- package/dist/commands/secrets.js +156 -75
- package/dist/commands/serve.d.ts +10 -0
- package/dist/commands/serve.js +37 -0
- package/dist/commands/sessions-inject.d.ts +14 -0
- package/dist/commands/sessions-inject.js +111 -0
- package/dist/commands/sessions-picker.d.ts +2 -0
- package/dist/commands/sessions-picker.js +24 -3
- package/dist/commands/sessions-resume.js +20 -8
- package/dist/commands/sessions-sync.d.ts +3 -0
- package/dist/commands/sessions-sync.js +44 -4
- package/dist/commands/sessions.d.ts +78 -1
- package/dist/commands/sessions.js +506 -66
- package/dist/commands/setup.js +4 -2
- package/dist/commands/sync.d.ts +3 -1
- package/dist/commands/sync.js +156 -4
- package/dist/commands/teams.js +217 -0
- package/dist/commands/versions.js +2 -4
- package/dist/commands/watchdog.d.ts +18 -0
- package/dist/commands/watchdog.js +238 -0
- package/dist/index.js +25 -2
- package/dist/lib/audit/log.d.ts +92 -0
- package/dist/lib/audit/log.js +177 -0
- package/dist/lib/auto-pull.js +2 -1
- package/dist/lib/browser/chrome.d.ts +10 -0
- package/dist/lib/browser/chrome.js +18 -7
- package/dist/lib/browser/drivers/ssh.js +2 -1
- package/dist/lib/browser/har.d.ts +84 -0
- package/dist/lib/browser/har.js +77 -0
- package/dist/lib/browser/ipc.js +24 -3
- package/dist/lib/browser/profiles.d.ts +1 -1
- package/dist/lib/browser/profiles.js +8 -10
- package/dist/lib/browser/refs.d.ts +65 -0
- package/dist/lib/browser/refs.js +73 -1
- package/dist/lib/browser/runtime-state.js +1 -0
- package/dist/lib/browser/service.d.ts +38 -2
- package/dist/lib/browser/service.js +112 -8
- package/dist/lib/browser/types.d.ts +14 -1
- package/dist/lib/budget/live-cloud.d.ts +42 -0
- package/dist/lib/budget/live-cloud.js +79 -0
- package/dist/lib/budget/live-team.d.ts +31 -0
- package/dist/lib/budget/live-team.js +115 -0
- package/dist/lib/cloud/codex.js +4 -0
- package/dist/lib/cloud/rush.d.ts +12 -1
- package/dist/lib/cloud/rush.js +13 -3
- package/dist/lib/cloud/types.d.ts +9 -0
- package/dist/lib/computer/dispatch.d.ts +8 -0
- package/dist/lib/computer/dispatch.js +125 -0
- package/dist/lib/computer/loop.d.ts +62 -0
- package/dist/lib/computer/loop.js +98 -0
- package/dist/lib/computer/model.d.ts +44 -0
- package/dist/lib/computer/model.js +157 -0
- package/dist/lib/concurrency.d.ts +19 -0
- package/dist/lib/concurrency.js +33 -0
- package/dist/lib/daemon.d.ts +57 -0
- package/dist/lib/daemon.js +196 -18
- package/dist/lib/devices/registry.d.ts +7 -0
- package/dist/lib/devices/registry.js +24 -0
- package/dist/lib/devices/resolve-target.d.ts +24 -0
- package/dist/lib/devices/resolve-target.js +80 -0
- package/dist/lib/devices/tailscale.js +1 -1
- package/dist/lib/drift.d.ts +52 -0
- package/dist/lib/drift.js +112 -0
- package/dist/lib/events.d.ts +1 -1
- package/dist/lib/events.js +31 -13
- package/dist/lib/exec.d.ts +17 -0
- package/dist/lib/exec.js +79 -13
- package/dist/lib/git.d.ts +27 -0
- package/dist/lib/git.js +56 -1
- package/dist/lib/hooks/cache.d.ts +6 -0
- package/dist/lib/hooks/cache.js +54 -12
- package/dist/lib/hooks.d.ts +27 -0
- package/dist/lib/hooks.js +127 -8
- package/dist/lib/hosts/dispatch.d.ts +15 -0
- package/dist/lib/hosts/dispatch.js +39 -6
- package/dist/lib/hosts/logs.js +30 -1
- package/dist/lib/hosts/option.js +1 -1
- package/dist/lib/hosts/passthrough.js +3 -1
- package/dist/lib/hosts/ready.d.ts +29 -6
- package/dist/lib/hosts/ready.js +66 -15
- package/dist/lib/hosts/registry.d.ts +19 -2
- package/dist/lib/hosts/registry.js +58 -2
- package/dist/lib/hosts/remote-cmd.d.ts +62 -1
- package/dist/lib/hosts/remote-cmd.js +70 -1
- package/dist/lib/hosts/remote-os.d.ts +17 -0
- package/dist/lib/hosts/remote-os.js +30 -0
- package/dist/lib/hosts/session-index.d.ts +34 -0
- package/dist/lib/hosts/session-index.js +56 -0
- package/dist/lib/hosts/tasks.d.ts +14 -0
- package/dist/lib/hosts/tasks.js +15 -0
- package/dist/lib/lock.d.ts +93 -0
- package/dist/lib/lock.js +207 -0
- package/dist/lib/loop.js +16 -1
- package/dist/lib/machine-id.d.ts +21 -0
- package/dist/lib/machine-id.js +26 -0
- package/dist/lib/mailbox-target.d.ts +36 -0
- package/dist/lib/mailbox-target.js +45 -0
- package/dist/lib/mailbox.d.ts +47 -0
- package/dist/lib/mailbox.js +194 -0
- package/dist/lib/mcp.d.ts +5 -0
- package/dist/lib/mcp.js +24 -8
- package/dist/lib/migrate.d.ts +19 -0
- package/dist/lib/migrate.js +134 -26
- package/dist/lib/overdue.js +3 -0
- package/dist/lib/picker.d.ts +2 -0
- package/dist/lib/picker.js +4 -1
- package/dist/lib/platform/exec.d.ts +46 -0
- package/dist/lib/platform/exec.js +74 -0
- package/dist/lib/platform/process.d.ts +31 -0
- package/dist/lib/platform/process.js +34 -1
- package/dist/lib/platform/winpath.js +2 -0
- package/dist/lib/plugins.js +16 -6
- package/dist/lib/profiles.d.ts +25 -0
- package/dist/lib/profiles.js +22 -6
- package/dist/lib/pty-client.js +2 -1
- package/dist/lib/rotate.d.ts +61 -0
- package/dist/lib/rotate.js +52 -0
- package/dist/lib/routines.d.ts +40 -2
- package/dist/lib/routines.js +66 -8
- package/dist/lib/runner.d.ts +11 -2
- package/dist/lib/runner.js +49 -7
- package/dist/lib/scheduler.js +6 -1
- package/dist/lib/secrets/bundles.d.ts +60 -4
- package/dist/lib/secrets/bundles.js +131 -12
- package/dist/lib/secrets/filestore.d.ts +3 -0
- package/dist/lib/secrets/filestore.js +42 -16
- package/dist/lib/secrets/index.d.ts +43 -2
- package/dist/lib/secrets/index.js +102 -3
- package/dist/lib/secrets/mcp.d.ts +93 -0
- package/dist/lib/secrets/mcp.js +205 -0
- package/dist/lib/secrets/remote.js +12 -5
- package/dist/lib/secrets/sync.js +83 -4
- package/dist/lib/secrets/windows.js +14 -3
- package/dist/lib/serve/data.d.ts +81 -0
- package/dist/lib/serve/data.js +91 -0
- package/dist/lib/serve/page.d.ts +7 -0
- package/dist/lib/serve/page.js +140 -0
- package/dist/lib/serve/server.d.ts +46 -0
- package/dist/lib/serve/server.js +115 -0
- package/dist/lib/session/active.d.ts +73 -0
- package/dist/lib/session/active.js +199 -22
- package/dist/lib/session/db.d.ts +2 -1
- package/dist/lib/session/db.js +41 -5
- package/dist/lib/session/discover.d.ts +39 -0
- package/dist/lib/session/discover.js +127 -29
- package/dist/lib/session/ghostty-tabs.d.ts +33 -0
- package/dist/lib/session/ghostty-tabs.js +126 -0
- package/dist/lib/session/inject.d.ts +18 -0
- package/dist/lib/session/inject.js +21 -0
- package/dist/lib/session/parse.js +23 -20
- package/dist/lib/session/pid-registry.d.ts +1 -0
- package/dist/lib/session/pid-registry.js +24 -0
- package/dist/lib/session/provenance.d.ts +14 -2
- package/dist/lib/session/provenance.js +39 -8
- package/dist/lib/session/relative-time.js +6 -2
- package/dist/lib/session/remote-active.js +19 -17
- package/dist/lib/session/remote-list.d.ts +51 -0
- package/dist/lib/session/remote-list.js +205 -0
- package/dist/lib/session/remote.d.ts +7 -1
- package/dist/lib/session/remote.js +20 -4
- package/dist/lib/session/sync/config.d.ts +14 -15
- package/dist/lib/session/sync/config.js +60 -20
- package/dist/lib/session/types.d.ts +23 -0
- package/dist/lib/shims.d.ts +36 -6
- package/dist/lib/shims.js +91 -29
- package/dist/lib/ssh-exec.js +2 -0
- package/dist/lib/ssh-tunnel.js +2 -1
- package/dist/lib/startup/command-registry.d.ts +6 -0
- package/dist/lib/startup/command-registry.js +13 -1
- package/dist/lib/state.d.ts +13 -0
- package/dist/lib/state.js +103 -9
- package/dist/lib/sync-umbrella.d.ts +14 -7
- package/dist/lib/sync-umbrella.js +21 -13
- package/dist/lib/teams/forEach.d.ts +110 -0
- package/dist/lib/teams/forEach.js +186 -0
- package/dist/lib/teams/index.d.ts +1 -0
- package/dist/lib/teams/index.js +1 -0
- package/dist/lib/teams/pr-watch.d.ts +226 -0
- package/dist/lib/teams/pr-watch.js +371 -0
- package/dist/lib/teams/supervisor.d.ts +14 -1
- package/dist/lib/teams/supervisor.js +19 -0
- package/dist/lib/teams/worktree.d.ts +9 -0
- package/dist/lib/teams/worktree.js +32 -0
- package/dist/lib/terminal/backends/index.d.ts +2 -1
- package/dist/lib/terminal/backends/index.js +3 -1
- package/dist/lib/terminal/backends/vscodium-agent.d.ts +31 -0
- package/dist/lib/terminal/backends/vscodium-agent.js +72 -0
- package/dist/lib/terminal/index.d.ts +4 -1
- package/dist/lib/terminal/index.js +4 -1
- package/dist/lib/terminal/inject.d.ts +204 -0
- package/dist/lib/terminal/inject.js +247 -0
- package/dist/lib/terminal/resolve.d.ts +64 -0
- package/dist/lib/terminal/resolve.js +90 -0
- package/dist/lib/terminal/types.d.ts +1 -1
- package/dist/lib/tmux/session.d.ts +10 -0
- package/dist/lib/tmux/session.js +31 -0
- package/dist/lib/triggers/webhook.d.ts +85 -0
- package/dist/lib/triggers/webhook.js +141 -0
- package/dist/lib/versions.d.ts +23 -0
- package/dist/lib/versions.js +119 -13
- package/dist/lib/watchdog/index.d.ts +3 -0
- package/dist/lib/watchdog/index.js +5 -0
- package/dist/lib/watchdog/read.d.ts +35 -0
- package/dist/lib/watchdog/read.js +149 -0
- package/dist/lib/watchdog/runner.d.ts +127 -0
- package/dist/lib/watchdog/runner.js +322 -0
- package/dist/lib/watchdog/watchdog.d.ts +40 -0
- package/dist/lib/watchdog/watchdog.js +166 -0
- package/dist/lib/watchdog/watchdogTail.d.ts +5 -0
- package/dist/lib/watchdog/watchdogTail.js +154 -0
- package/dist/lib/workflows.d.ts +166 -0
- package/dist/lib/workflows.js +193 -0
- package/package.json +5 -4
|
@@ -31,7 +31,16 @@ import { extractSessionTopic } from './prompt.js';
|
|
|
31
31
|
import { readSessionTail } from './tail.js';
|
|
32
32
|
import { inferSessionState } from './state.js';
|
|
33
33
|
import { detectProvenance } from './provenance.js';
|
|
34
|
+
import { mapBounded } from '../concurrency.js';
|
|
34
35
|
const execFileAsync = promisify(execFile);
|
|
36
|
+
/**
|
|
37
|
+
* Per-PID `lsof` probes run bounded and staggered rather than as one parallel
|
|
38
|
+
* fan-out: a simultaneous system-wide `lsof` burst reads to behavioral EDR
|
|
39
|
+
* (CrowdStrike Falcon) as lateral-movement recon. Results are identical — the
|
|
40
|
+
* cwds are just gathered at a bounded spawn rate instead of a single burst.
|
|
41
|
+
*/
|
|
42
|
+
export const LSOF_CONCURRENCY = 4;
|
|
43
|
+
const LSOF_STAGGER_MS = 10;
|
|
35
44
|
const HOME = os.homedir();
|
|
36
45
|
const LIVE_TERMINALS_FILE = path.join(getTerminalsDir(), 'live-terminals.json');
|
|
37
46
|
/**
|
|
@@ -49,6 +58,19 @@ const AGENT_CLI_NAMES = {
|
|
|
49
58
|
opencode: 'opencode',
|
|
50
59
|
droid: 'droid',
|
|
51
60
|
};
|
|
61
|
+
/**
|
|
62
|
+
* Resolve an agent kind from a process's reported executable. `comm` may be an
|
|
63
|
+
* absolute path (shim-launched agents), and Windows image names carry an
|
|
64
|
+
* `.exe` suffix (`claude.exe`), so basename + suffix-strip before the lookup.
|
|
65
|
+
*/
|
|
66
|
+
function agentKindFromComm(commRaw) {
|
|
67
|
+
const base = path.basename(commRaw);
|
|
68
|
+
const stripped = base.replace(/\.exe$/i, '');
|
|
69
|
+
// Windows image names compare case-insensitively; POSIX comms stay exact —
|
|
70
|
+
// macOS's Claude desktop app process is named 'Claude' and must NOT match.
|
|
71
|
+
const key = stripped === base ? base : stripped.toLowerCase();
|
|
72
|
+
return AGENT_CLI_NAMES[key];
|
|
73
|
+
}
|
|
52
74
|
function isPidAlive(pid) {
|
|
53
75
|
if (!pid || pid < 1)
|
|
54
76
|
return false;
|
|
@@ -314,6 +336,7 @@ export async function listTerminalsActive() {
|
|
|
314
336
|
context: 'terminal',
|
|
315
337
|
kind: t.kind,
|
|
316
338
|
host: detectHost(t.pid, procByPid),
|
|
339
|
+
tty: procByPid.get(t.pid)?.tty,
|
|
317
340
|
pid: t.pid,
|
|
318
341
|
sessionId: t.sessionId ?? sessionIdFromFile(sessionFile),
|
|
319
342
|
cwd: t.cwd ?? undefined,
|
|
@@ -355,14 +378,14 @@ export function listCloudActive() {
|
|
|
355
378
|
* and a terminal-app is preferred over the multiplexer inside it.
|
|
356
379
|
*/
|
|
357
380
|
const HOST_MATCHERS = [
|
|
358
|
-
// IDE renderers (Electron helper processes)
|
|
359
|
-
{ host: 'code', tokens: ['Code Helper', 'Code - Insiders Helper'] },
|
|
360
|
-
{ host: 'cursor', tokens: ['Cursor Helper'] },
|
|
361
|
-
{ host: 'codium', tokens: ['VSCodium Helper'] },
|
|
362
|
-
{ host: 'windsurf', tokens: ['Windsurf Helper'] },
|
|
381
|
+
// IDE renderers (Electron helper processes on macOS, image names on Windows)
|
|
382
|
+
{ host: 'code', tokens: ['Code Helper', 'Code - Insiders Helper', 'Code.exe'] },
|
|
383
|
+
{ host: 'cursor', tokens: ['Cursor Helper', 'Cursor.exe'] },
|
|
384
|
+
{ host: 'codium', tokens: ['VSCodium Helper', 'VSCodium.exe'] },
|
|
385
|
+
{ host: 'windsurf', tokens: ['Windsurf Helper', 'Windsurf.exe'] },
|
|
363
386
|
// Native terminal apps
|
|
364
387
|
{ host: 'iterm', tokens: ['iTerm2', 'iTermServer', 'iTerm'] },
|
|
365
|
-
{ host: 'terminal', tokens: ['Terminal.app', '/Applications/Utilities/Terminal.app'] },
|
|
388
|
+
{ host: 'terminal', tokens: ['Terminal.app', '/Applications/Utilities/Terminal.app', 'WindowsTerminal.exe'] },
|
|
366
389
|
{ host: 'warp', tokens: ['Warp.app', 'stable_'] },
|
|
367
390
|
{ host: 'alacritty', tokens: ['alacritty', 'Alacritty'] },
|
|
368
391
|
{ host: 'kitty', tokens: ['kitty'] },
|
|
@@ -380,25 +403,64 @@ const HOST_MATCHERS = [
|
|
|
380
403
|
* matching against AGENT_CLI_NAMES.
|
|
381
404
|
*/
|
|
382
405
|
async function readProcessTable() {
|
|
406
|
+
if (process.platform === 'win32')
|
|
407
|
+
return readProcessTableWin32();
|
|
383
408
|
let out;
|
|
384
409
|
try {
|
|
385
|
-
({ stdout: out } = await execFileAsync('ps', ['-A', '-o', 'pid=,ppid=,comm='], { encoding: 'utf8', maxBuffer: 16 * 1024 * 1024 }));
|
|
410
|
+
({ stdout: out } = await execFileAsync('ps', ['-A', '-o', 'pid=,ppid=,tty=,comm='], { encoding: 'utf8', maxBuffer: 16 * 1024 * 1024 }));
|
|
386
411
|
}
|
|
387
412
|
catch {
|
|
388
413
|
return [];
|
|
389
414
|
}
|
|
390
415
|
const rows = [];
|
|
391
416
|
for (const line of out.split('\n')) {
|
|
392
|
-
|
|
417
|
+
// pid ppid tty comm — tty is a single token ('ttys003', 's003', or '??'/'?'
|
|
418
|
+
// for none); comm stays last so it may contain spaces.
|
|
419
|
+
const m = line.trim().match(/^(\d+)\s+(\d+)\s+(\S+)\s+(.+)$/);
|
|
420
|
+
if (!m)
|
|
421
|
+
continue;
|
|
422
|
+
const pid = parseInt(m[1], 10);
|
|
423
|
+
const ppid = parseInt(m[2], 10);
|
|
424
|
+
if (!Number.isFinite(pid) || !Number.isFinite(ppid))
|
|
425
|
+
continue;
|
|
426
|
+
const ttyRaw = m[3];
|
|
427
|
+
const tty = ttyRaw === '??' || ttyRaw === '?' || ttyRaw === '-' ? undefined : ttyRaw;
|
|
428
|
+
const commRaw = m[4].trim();
|
|
429
|
+
rows.push({ pid, ppid, tty, comm: commRaw, kind: agentKindFromComm(commRaw) });
|
|
430
|
+
}
|
|
431
|
+
return rows;
|
|
432
|
+
}
|
|
433
|
+
/**
|
|
434
|
+
* Windows process table in one CIM query (`wmic` is removed on current
|
|
435
|
+
* Windows 11, so PowerShell is the stable interface). Same pid/ppid/comm
|
|
436
|
+
* shape as the POSIX `ps` snapshot; `Name` is the image name (`claude.exe`).
|
|
437
|
+
*/
|
|
438
|
+
async function readProcessTableWin32() {
|
|
439
|
+
let out;
|
|
440
|
+
try {
|
|
441
|
+
({ stdout: out } = await execFileAsync('powershell.exe', [
|
|
442
|
+
'-NoProfile', '-NonInteractive', '-Command',
|
|
443
|
+
'Get-CimInstance Win32_Process | Select-Object ProcessId,ParentProcessId,Name | ConvertTo-Csv -NoTypeInformation',
|
|
444
|
+
], { encoding: 'utf8', maxBuffer: 16 * 1024 * 1024, windowsHide: true }));
|
|
445
|
+
}
|
|
446
|
+
catch {
|
|
447
|
+
return [];
|
|
448
|
+
}
|
|
449
|
+
return parseWin32ProcessCsv(out);
|
|
450
|
+
}
|
|
451
|
+
/** Parse `ConvertTo-Csv` output of Win32_Process rows. Exported for tests. */
|
|
452
|
+
export function parseWin32ProcessCsv(out) {
|
|
453
|
+
const rows = [];
|
|
454
|
+
for (const line of out.split(/\r?\n/)) {
|
|
455
|
+
const m = line.trim().match(/^"(\d+)","(\d+)","(.*)"$/);
|
|
393
456
|
if (!m)
|
|
394
457
|
continue;
|
|
395
458
|
const pid = parseInt(m[1], 10);
|
|
396
459
|
const ppid = parseInt(m[2], 10);
|
|
397
460
|
if (!Number.isFinite(pid) || !Number.isFinite(ppid))
|
|
398
461
|
continue;
|
|
399
|
-
const
|
|
400
|
-
|
|
401
|
-
rows.push({ pid, ppid, comm: commRaw, kind });
|
|
462
|
+
const comm = m[3].replace(/""/g, '"');
|
|
463
|
+
rows.push({ pid, ppid, comm, kind: agentKindFromComm(comm) });
|
|
402
464
|
}
|
|
403
465
|
return rows;
|
|
404
466
|
}
|
|
@@ -418,12 +480,25 @@ function hasAttributedAncestor(pid, ppidMap, attributed) {
|
|
|
418
480
|
}
|
|
419
481
|
return false;
|
|
420
482
|
}
|
|
483
|
+
/**
|
|
484
|
+
* Resolve every candidate PID's cwd, bounded and staggered so the probes no
|
|
485
|
+
* longer fan out as one simultaneous system-wide `lsof` burst (a behavioral-EDR
|
|
486
|
+
* recon trigger). Order matches the input `pids`. The `probe` seam is injectable
|
|
487
|
+
* for testing the bound; production always uses the real `lsof`-backed probe.
|
|
488
|
+
*/
|
|
489
|
+
export function resolveCwds(pids, probe = getCwdForPid) {
|
|
490
|
+
return mapBounded(pids, probe, { concurrency: LSOF_CONCURRENCY, staggerMs: LSOF_STAGGER_MS });
|
|
491
|
+
}
|
|
421
492
|
/**
|
|
422
493
|
* Resolve a process's current working directory via `lsof`. The `-a` flag
|
|
423
494
|
* ANDs the filters; without it macOS treats `-p` and `-d` as a union and
|
|
424
495
|
* returns the cwd of every process on the system.
|
|
425
496
|
*/
|
|
426
497
|
async function getCwdForPid(pid) {
|
|
498
|
+
// No lsof on Windows and no cheap foreign-process cwd API; the pid registry
|
|
499
|
+
// written by `ag run` supplies the cwd for registry-launched agents instead.
|
|
500
|
+
if (process.platform === 'win32')
|
|
501
|
+
return undefined;
|
|
427
502
|
let out;
|
|
428
503
|
try {
|
|
429
504
|
const res = await execFileAsync('lsof', ['-a', '-p', String(pid), '-d', 'cwd', '-Fn'], {
|
|
@@ -470,6 +545,99 @@ const UI_HOSTS = new Set([
|
|
|
470
545
|
'iterm', 'terminal', 'warp', 'alacritty', 'kitty', 'hyper', 'wezterm', 'ghostty',
|
|
471
546
|
'tmux', 'screen',
|
|
472
547
|
]);
|
|
548
|
+
/**
|
|
549
|
+
* Find the launch registry entry recorded by a WRAPPER of this process. The
|
|
550
|
+
* shim delegate records the pid it spawned, but on Windows the `.cmd` shell
|
|
551
|
+
* path makes that a cmd.exe intermediary whose child is the real agent binary
|
|
552
|
+
* — so the agent pid itself has no entry and the wrapper one ancestor up does.
|
|
553
|
+
* The nearest entry wins, and only if its agent matches the candidate's kind:
|
|
554
|
+
* a claude session shelling out to codex must not hand codex its identity.
|
|
555
|
+
*/
|
|
556
|
+
export function readAncestorSessionEntry(pid, ppidMap, kind, readEntry = readPidSessionEntry) {
|
|
557
|
+
let cur = ppidMap.get(pid);
|
|
558
|
+
const seen = new Set();
|
|
559
|
+
while (cur && cur > 1 && !seen.has(cur)) {
|
|
560
|
+
const entry = readEntry(cur);
|
|
561
|
+
if (entry)
|
|
562
|
+
return entry.agent === kind ? entry : undefined;
|
|
563
|
+
seen.add(cur);
|
|
564
|
+
cur = ppidMap.get(cur);
|
|
565
|
+
}
|
|
566
|
+
return undefined;
|
|
567
|
+
}
|
|
568
|
+
/**
|
|
569
|
+
* Collapse agent processes spawned by another live agent process of the same
|
|
570
|
+
* kind onto their nearest kept ancestor. Claude runs subagents, forks, and
|
|
571
|
+
* even its bundled ripgrep as child `claude` processes — on POSIX those
|
|
572
|
+
* children resolve to the parent's cwd and collapse in dedupeBySession, but
|
|
573
|
+
* where no cwd can be recovered (Windows has no lsof) every fork would print
|
|
574
|
+
* as its own headless row. Two exceptions keep their own row: a candidate with
|
|
575
|
+
* its own registry entry — on its pid OR on a wrapper ancestor strictly below
|
|
576
|
+
* the pid it would fold into (the shim's entry lands on the cmd.exe
|
|
577
|
+
* intermediary on Windows) — and a child of a *different* agent kind (claude
|
|
578
|
+
* shelling out to codex is a real second session, not a fork).
|
|
579
|
+
* Returns the kept roots plus, per root pid, how many descendants folded in.
|
|
580
|
+
*/
|
|
581
|
+
export function foldSubordinateAgents(candidates, ppidMap, readEntry) {
|
|
582
|
+
const kindByPid = new Map(candidates.map(c => [c.pid, c.kind]));
|
|
583
|
+
const nearestSameKindAncestor = (pid, kind) => {
|
|
584
|
+
let cur = ppidMap.get(pid);
|
|
585
|
+
const seen = new Set();
|
|
586
|
+
while (cur && cur > 1 && !seen.has(cur)) {
|
|
587
|
+
if (kindByPid.get(cur) === kind)
|
|
588
|
+
return cur;
|
|
589
|
+
seen.add(cur);
|
|
590
|
+
cur = ppidMap.get(cur);
|
|
591
|
+
}
|
|
592
|
+
return undefined;
|
|
593
|
+
};
|
|
594
|
+
// Own launch identity: a matching-kind registry entry on the candidate or on
|
|
595
|
+
// any wrapper between it and the pid it would fold into (exclusive). Entries
|
|
596
|
+
// above the fold target belong to that ancestor's session, not this one.
|
|
597
|
+
const hasOwnSession = (c, stopPid) => {
|
|
598
|
+
if (readEntry(c.pid)?.agent === c.kind)
|
|
599
|
+
return true;
|
|
600
|
+
let cur = ppidMap.get(c.pid);
|
|
601
|
+
const seen = new Set();
|
|
602
|
+
while (cur && cur > 1 && cur !== stopPid && !seen.has(cur)) {
|
|
603
|
+
if (readEntry(cur)?.agent === c.kind)
|
|
604
|
+
return true;
|
|
605
|
+
seen.add(cur);
|
|
606
|
+
cur = ppidMap.get(cur);
|
|
607
|
+
}
|
|
608
|
+
return false;
|
|
609
|
+
};
|
|
610
|
+
const keptPids = new Set();
|
|
611
|
+
for (const c of candidates) {
|
|
612
|
+
const foldTarget = nearestSameKindAncestor(c.pid, c.kind);
|
|
613
|
+
if (foldTarget === undefined || hasOwnSession(c, foldTarget)) {
|
|
614
|
+
keptPids.add(c.pid);
|
|
615
|
+
}
|
|
616
|
+
}
|
|
617
|
+
const kept = [];
|
|
618
|
+
const foldedByRoot = new Map();
|
|
619
|
+
for (const c of candidates) {
|
|
620
|
+
if (keptPids.has(c.pid)) {
|
|
621
|
+
kept.push(c);
|
|
622
|
+
continue;
|
|
623
|
+
}
|
|
624
|
+
// Walk up through folded intermediates to the nearest kept same-kind pid.
|
|
625
|
+
let cur = nearestSameKindAncestor(c.pid, c.kind);
|
|
626
|
+
const seen = new Set();
|
|
627
|
+
while (cur !== undefined && !keptPids.has(cur) && !seen.has(cur)) {
|
|
628
|
+
seen.add(cur);
|
|
629
|
+
cur = nearestSameKindAncestor(cur, c.kind);
|
|
630
|
+
}
|
|
631
|
+
// A fold target must itself be a kept row (a ppid cycle from pid reuse
|
|
632
|
+
// can orphan the whole chain) — otherwise keep the row rather than drop it.
|
|
633
|
+
if (cur === undefined || !keptPids.has(cur)) {
|
|
634
|
+
kept.push(c);
|
|
635
|
+
continue;
|
|
636
|
+
}
|
|
637
|
+
foldedByRoot.set(cur, (foldedByRoot.get(cur) ?? 0) + 1);
|
|
638
|
+
}
|
|
639
|
+
return { kept, foldedByRoot };
|
|
640
|
+
}
|
|
473
641
|
/**
|
|
474
642
|
* Agent processes not attributed to a team or the runtime registry.
|
|
475
643
|
* Classified by walking the ppid chain: any recognised UI ancestor (IDE
|
|
@@ -495,16 +663,22 @@ export async function listUnattributedActive(attributed) {
|
|
|
495
663
|
continue;
|
|
496
664
|
candidates.push({ pid, kind });
|
|
497
665
|
}
|
|
498
|
-
//
|
|
499
|
-
|
|
666
|
+
// Forks/subagents of a live agent process collapse onto their root before
|
|
667
|
+
// the cwd probes — fewer spawns, and one session stays one row even when
|
|
668
|
+
// cwd-based dedupe is unavailable (Windows).
|
|
669
|
+
const { kept, foldedByRoot } = foldSubordinateAgents(candidates, ppidMap, readPidSessionEntry);
|
|
670
|
+
// Bounded + staggered lsof probes: same cwds, but a trickle of spawns instead
|
|
671
|
+
// of one simultaneous system-wide burst that behavioral EDR flags as recon.
|
|
672
|
+
const cwds = await resolveCwds(kept.map(c => c.pid));
|
|
500
673
|
const out = [];
|
|
501
|
-
for (let i = 0; i <
|
|
502
|
-
const { pid, kind } =
|
|
503
|
-
// The per-pid registry (written by `ag run`
|
|
504
|
-
// pid was launched with — so N agents in one cwd
|
|
505
|
-
// sessions instead of all collapsing onto the newest
|
|
506
|
-
//
|
|
507
|
-
|
|
674
|
+
for (let i = 0; i < kept.length; i++) {
|
|
675
|
+
const { pid, kind } = kept[i];
|
|
676
|
+
// The per-pid registry (written by `ag run` and the shim delegate) gives
|
|
677
|
+
// the EXACT session id this pid was launched with — so N agents in one cwd
|
|
678
|
+
// resolve to N distinct sessions instead of all collapsing onto the newest
|
|
679
|
+
// .jsonl. The shim's entry may sit on a wrapper ancestor (Windows .cmd
|
|
680
|
+
// path). Absent entirely (direct launch outside agents-cli) → heuristic.
|
|
681
|
+
const entry = readPidSessionEntry(pid) ?? readAncestorSessionEntry(pid, ppidMap, kind);
|
|
508
682
|
const cwd = cwds[i] ?? entry?.cwd ?? undefined;
|
|
509
683
|
const sessionFile = findSessionFileForKind(kind, cwd, entry?.sessionId);
|
|
510
684
|
const topic = sessionFile ? quickExtractTopic(sessionFile) : undefined;
|
|
@@ -515,11 +689,13 @@ export async function listUnattributedActive(attributed) {
|
|
|
515
689
|
context,
|
|
516
690
|
kind,
|
|
517
691
|
host,
|
|
692
|
+
tty: procByPid.get(pid)?.tty,
|
|
518
693
|
pid,
|
|
519
694
|
cwd,
|
|
520
695
|
sessionId: entry?.sessionId ?? sessionIdFromFile(sessionFile),
|
|
521
696
|
topic,
|
|
522
697
|
sessionFile,
|
|
698
|
+
pidCount: 1 + (foldedByRoot.get(pid) ?? 0),
|
|
523
699
|
}, state, sessionFile));
|
|
524
700
|
}
|
|
525
701
|
// Housekeeping: drop registry files for pids that have since died.
|
|
@@ -581,10 +757,11 @@ function dedupeBySession(sessions) {
|
|
|
581
757
|
}
|
|
582
758
|
const existing = byKey.get(key);
|
|
583
759
|
if (existing) {
|
|
584
|
-
|
|
760
|
+
// Carry pre-folded fork counts (headless rows arrive with pidCount set).
|
|
761
|
+
existing.pidCount = (existing.pidCount ?? 1) + (s.pidCount ?? 1);
|
|
585
762
|
}
|
|
586
763
|
else {
|
|
587
|
-
s.pidCount = 1;
|
|
764
|
+
s.pidCount = s.pidCount ?? 1;
|
|
588
765
|
byKey.set(key, s);
|
|
589
766
|
out.push(s);
|
|
590
767
|
}
|
package/dist/lib/session/db.d.ts
CHANGED
|
@@ -16,6 +16,7 @@ export interface SessionRow {
|
|
|
16
16
|
version: string | null;
|
|
17
17
|
account: string | null;
|
|
18
18
|
timestamp: string;
|
|
19
|
+
last_activity: string | null;
|
|
19
20
|
project: string | null;
|
|
20
21
|
cwd: string | null;
|
|
21
22
|
git_branch: string | null;
|
|
@@ -144,7 +145,7 @@ export declare function syncTopics(topicMap: Map<string, string>): number;
|
|
|
144
145
|
* session hasn't been scanned yet — the caller degrades to no live state.
|
|
145
146
|
*/
|
|
146
147
|
export declare function latestSessionFileForCwd(agent: SessionAgentId, cwd: string): string | undefined;
|
|
147
|
-
/** Query sessions from the database, applying filters and ordering by
|
|
148
|
+
/** Query sessions from the database, applying filters and ordering by last-activity descending (default). */
|
|
148
149
|
export declare function querySessions(options?: QueryOptions): SessionMeta[];
|
|
149
150
|
/** Count sessions matching the given filter options. */
|
|
150
151
|
export declare function countSessions(options?: QueryOptions): number;
|
package/dist/lib/session/db.js
CHANGED
|
@@ -13,7 +13,7 @@ import { getSessionsDir, getSessionsDbPath } from '../state.js';
|
|
|
13
13
|
const SESSIONS_DIR = getSessionsDir();
|
|
14
14
|
const DB_PATH = getSessionsDbPath();
|
|
15
15
|
/** Current schema version; bumped when migrations are added. */
|
|
16
|
-
const SCHEMA_VERSION =
|
|
16
|
+
const SCHEMA_VERSION = 8;
|
|
17
17
|
/**
|
|
18
18
|
* Canonicalize a file path for use as a scan_ledger key. The same physical
|
|
19
19
|
* session file is reachable via multiple aliases — `~/.claude/projects/x.jsonl`
|
|
@@ -46,6 +46,7 @@ CREATE TABLE IF NOT EXISTS sessions (
|
|
|
46
46
|
version TEXT,
|
|
47
47
|
account TEXT,
|
|
48
48
|
timestamp TEXT NOT NULL,
|
|
49
|
+
last_activity TEXT,
|
|
49
50
|
project TEXT,
|
|
50
51
|
cwd TEXT,
|
|
51
52
|
git_branch TEXT,
|
|
@@ -176,6 +177,17 @@ function migrateSchema(db, fromVersion) {
|
|
|
176
177
|
db.exec(`ALTER TABLE sessions ADD COLUMN ticket_id TEXT`);
|
|
177
178
|
db.exec(`DELETE FROM scan_ledger;`);
|
|
178
179
|
}
|
|
180
|
+
if (fromVersion < 8) {
|
|
181
|
+
// v7 → v8: the listing now sorts and labels by last-activity (last message
|
|
182
|
+
// time) instead of creation time. Add the column, seed it to `timestamp` so
|
|
183
|
+
// no row sorts as NULL before the rescan lands, then force a full rescan so
|
|
184
|
+
// every session gets its true last_activity (from lastTsMs) repopulated.
|
|
185
|
+
const cols = db.prepare(`PRAGMA table_info(sessions)`).all();
|
|
186
|
+
if (!cols.some(c => c.name === 'last_activity'))
|
|
187
|
+
db.exec(`ALTER TABLE sessions ADD COLUMN last_activity TEXT`);
|
|
188
|
+
db.exec(`UPDATE sessions SET last_activity = timestamp WHERE last_activity IS NULL`);
|
|
189
|
+
db.exec(`DELETE FROM scan_ledger;`);
|
|
190
|
+
}
|
|
179
191
|
}
|
|
180
192
|
/** Open (or return the cached) sessions database, applying migrations as needed. */
|
|
181
193
|
export function getDB() {
|
|
@@ -202,6 +214,11 @@ export function getDB() {
|
|
|
202
214
|
migrateSchema(db, currentVersion);
|
|
203
215
|
db.prepare(`INSERT OR REPLACE INTO meta(key, value) VALUES ('schema_version', ?)`).run(String(SCHEMA_VERSION));
|
|
204
216
|
}
|
|
217
|
+
// Index last_activity only after the column is guaranteed to exist — fresh DBs
|
|
218
|
+
// get it from CREATE TABLE above, existing pre-v8 DBs from the migration just
|
|
219
|
+
// run. It must NOT live in SCHEMA (executed before migration) or an existing
|
|
220
|
+
// DB would fail the index build on a column it doesn't have yet.
|
|
221
|
+
db.exec(`CREATE INDEX IF NOT EXISTS idx_sessions_last_activity ON sessions(last_activity DESC)`);
|
|
205
222
|
// One-shot cleanup of the pre-SQLite JSONL indexes. Safe — nothing reads
|
|
206
223
|
// them anymore. Guarded by a meta flag so we only try once.
|
|
207
224
|
const cleaned = db.prepare(`SELECT value FROM meta WHERE key = 'legacy_indexes_removed'`).get();
|
|
@@ -383,13 +400,13 @@ export function recordScans(entries) {
|
|
|
383
400
|
}
|
|
384
401
|
const upsertSessionStmt = (db) => db.prepare(`
|
|
385
402
|
INSERT INTO sessions (
|
|
386
|
-
id, short_id, agent, version, account, timestamp,
|
|
403
|
+
id, short_id, agent, version, account, timestamp, last_activity,
|
|
387
404
|
project, cwd, git_branch, topic, label, message_count, token_count,
|
|
388
405
|
cost_usd, duration_ms,
|
|
389
406
|
file_path, file_mtime_ms, file_size, scanned_at, is_team_origin,
|
|
390
407
|
pr_url, pr_number, worktree_slug, ticket_id
|
|
391
408
|
) VALUES (
|
|
392
|
-
@id, @short_id, @agent, @version, @account, @timestamp,
|
|
409
|
+
@id, @short_id, @agent, @version, @account, @timestamp, @last_activity,
|
|
393
410
|
@project, @cwd, @git_branch, @topic, @label, @message_count, @token_count,
|
|
394
411
|
@cost_usd, @duration_ms,
|
|
395
412
|
@file_path, @file_mtime_ms, @file_size, @scanned_at, @is_team_origin,
|
|
@@ -401,6 +418,7 @@ const upsertSessionStmt = (db) => db.prepare(`
|
|
|
401
418
|
version = excluded.version,
|
|
402
419
|
account = excluded.account,
|
|
403
420
|
timestamp = excluded.timestamp,
|
|
421
|
+
last_activity = excluded.last_activity,
|
|
404
422
|
project = excluded.project,
|
|
405
423
|
cwd = excluded.cwd,
|
|
406
424
|
git_branch = excluded.git_branch,
|
|
@@ -447,6 +465,7 @@ export function upsertSession(meta, content, scan) {
|
|
|
447
465
|
version: meta.version ?? null,
|
|
448
466
|
account: meta.account ?? null,
|
|
449
467
|
timestamp: meta.timestamp,
|
|
468
|
+
last_activity: resolveLastActivity(meta, scan),
|
|
450
469
|
project: meta.project ?? null,
|
|
451
470
|
cwd: meta.cwd ?? null,
|
|
452
471
|
git_branch: meta.gitBranch ?? null,
|
|
@@ -526,6 +545,7 @@ export function upsertSessionsBatch(entries) {
|
|
|
526
545
|
version: meta.version ?? null,
|
|
527
546
|
account: meta.account ?? null,
|
|
528
547
|
timestamp: meta.timestamp,
|
|
548
|
+
last_activity: resolveLastActivity(meta, scan),
|
|
529
549
|
project: meta.project ?? null,
|
|
530
550
|
cwd: meta.cwd ?? null,
|
|
531
551
|
git_branch: meta.gitBranch ?? null,
|
|
@@ -641,6 +661,7 @@ function rowToMeta(row) {
|
|
|
641
661
|
shortId: row.short_id,
|
|
642
662
|
agent: row.agent,
|
|
643
663
|
timestamp: row.timestamp,
|
|
664
|
+
lastActivity: row.last_activity ?? undefined,
|
|
644
665
|
project: row.project ?? undefined,
|
|
645
666
|
cwd: row.cwd ?? undefined,
|
|
646
667
|
filePath: row.file_path,
|
|
@@ -660,6 +681,21 @@ function rowToMeta(row) {
|
|
|
660
681
|
ticketId: row.ticket_id ?? undefined,
|
|
661
682
|
};
|
|
662
683
|
}
|
|
684
|
+
/**
|
|
685
|
+
* The recency signal used to sort and label the listing: last-message time when
|
|
686
|
+
* a parser computed it (`meta.lastActivity` from `lastTsMs`), else the file's
|
|
687
|
+
* mtime (its last write), else creation time. Guarded on `filePath` so synthetic
|
|
688
|
+
* / cloud rows (no local file) fall to their creation timestamp rather than a
|
|
689
|
+
* bogus scan-time mtime. Always an ISO string, so it sorts lexicographically
|
|
690
|
+
* against `timestamp` and feeds `formatRelativeTime` unchanged.
|
|
691
|
+
*/
|
|
692
|
+
function resolveLastActivity(meta, scan) {
|
|
693
|
+
if (meta.lastActivity)
|
|
694
|
+
return meta.lastActivity;
|
|
695
|
+
if (scan?.fileMtimeMs && meta.filePath)
|
|
696
|
+
return new Date(scan.fileMtimeMs).toISOString();
|
|
697
|
+
return meta.timestamp;
|
|
698
|
+
}
|
|
663
699
|
/**
|
|
664
700
|
* Newest indexed session file for an agent working in `cwd`. Lets the live
|
|
665
701
|
* `--active` scanner locate a Codex transcript (whose files are date-partitioned,
|
|
@@ -741,7 +777,7 @@ function buildSessionWhere(options) {
|
|
|
741
777
|
const clause = where.length > 0 ? `WHERE ${where.join(' AND ')}` : '';
|
|
742
778
|
return { clause, params };
|
|
743
779
|
}
|
|
744
|
-
/** Query sessions from the database, applying filters and ordering by
|
|
780
|
+
/** Query sessions from the database, applying filters and ordering by last-activity descending (default). */
|
|
745
781
|
export function querySessions(options = {}) {
|
|
746
782
|
const db = getDB();
|
|
747
783
|
const { clause, params } = buildSessionWhere(options);
|
|
@@ -757,7 +793,7 @@ export function querySessions(options = {}) {
|
|
|
757
793
|
? 'ORDER BY cost_usd IS NULL, cost_usd DESC, timestamp DESC'
|
|
758
794
|
: options.sortBy === 'duration'
|
|
759
795
|
? 'ORDER BY duration_ms IS NULL, duration_ms DESC, timestamp DESC'
|
|
760
|
-
: 'ORDER BY timestamp DESC';
|
|
796
|
+
: 'ORDER BY IFNULL(last_activity, timestamp) DESC, timestamp DESC';
|
|
761
797
|
const sql = `SELECT * FROM sessions ${clause} ${orderClause} ${limitClause}`;
|
|
762
798
|
const rows = db.prepare(sql).all(...params);
|
|
763
799
|
// Belt-and-suspenders: drop rows whose JSONL no longer exists on disk. The
|
|
@@ -50,6 +50,8 @@ interface ClaudeSessionScan {
|
|
|
50
50
|
costUsd?: number;
|
|
51
51
|
/** Wall-clock duration in ms between the first and last timestamped event. */
|
|
52
52
|
durationMs?: number;
|
|
53
|
+
/** ISO time of the last timestamped event — the session's last activity. */
|
|
54
|
+
lastActivity?: string;
|
|
53
55
|
/**
|
|
54
56
|
* Value of the JSONL `entrypoint` field on the first event that carries it.
|
|
55
57
|
* 'cli' for real interactive sessions, 'sdk-cli' for team-spawned ones.
|
|
@@ -74,6 +76,22 @@ interface ClaudeSessionScan {
|
|
|
74
76
|
* TTL, no external lock files needed.
|
|
75
77
|
*/
|
|
76
78
|
export declare function discoverSessions(options?: DiscoverOptions): Promise<SessionMeta[]>;
|
|
79
|
+
/**
|
|
80
|
+
* How many agents' dotfile dirs we scan at once, and the minimum spacing between
|
|
81
|
+
* successive scan starts. A small bound + stagger turns a simultaneous bulk
|
|
82
|
+
* multi-dotfile sweep (a behavioral-EDR file-enumeration trigger) into a trickle.
|
|
83
|
+
*/
|
|
84
|
+
export declare const DOTFILE_SCAN_CONCURRENCY = 2;
|
|
85
|
+
/** Run each agent's incremental scan, bounded + staggered. Order is irrelevant (each scan writes its own rows). */
|
|
86
|
+
export declare function scanAgentsBounded<T>(items: readonly T[], run: (item: T) => Promise<void>): Promise<void[]>;
|
|
87
|
+
/**
|
|
88
|
+
* The machine a discovered session originated on. Cross-machine sync mirrors a
|
|
89
|
+
* remote transcript to backups/<agent>/<machine>/<subdir>/… (see mirrorPath in
|
|
90
|
+
* sync/agents.ts); every other transcript is a live-home file on this box. So:
|
|
91
|
+
* when the path sits under the agent's backups root, the first segment below it
|
|
92
|
+
* is the origin machine id; otherwise it's the local machine.
|
|
93
|
+
*/
|
|
94
|
+
export declare function machineForSessionFile(filePath: string, agent: string): string;
|
|
77
95
|
/**
|
|
78
96
|
* Count sessions in scope without running an incremental scan. Assumes the DB
|
|
79
97
|
* is already fresh (typically true because `discoverSessions` ran first this
|
|
@@ -105,8 +123,29 @@ export declare function getAgentSessionDirs(agent: string, subdir: string): stri
|
|
|
105
123
|
* recent startedAt.
|
|
106
124
|
*/
|
|
107
125
|
export declare function buildClaudeLabelMap(): Map<string, string | null>;
|
|
126
|
+
/**
|
|
127
|
+
* Base64url-decode a JWT and return its `email` claim, if present. Split out so
|
|
128
|
+
* the decode is a single, testable step — and so it only runs when someone
|
|
129
|
+
* actually reads the Codex account (see the lazy resolution below).
|
|
130
|
+
*/
|
|
131
|
+
export declare function decodeJwtEmail(idToken: string): string | undefined;
|
|
132
|
+
/** Test seam: how many times getCodexAccount has actually resolved (decoded) since the last reset. */
|
|
133
|
+
export declare function __codexAccountResolveCountForTest(): number;
|
|
134
|
+
/** Test seam: clear the memoized account + resolve counter so laziness can be observed from a clean slate. */
|
|
135
|
+
export declare function __resetCodexAccountCacheForTest(): void;
|
|
108
136
|
/** Parse the lines of a Codex `session_index.jsonl` into a session id -> title map. */
|
|
109
137
|
export declare function parseCodexThreadNameIndex(raw: string): Map<string, string>;
|
|
138
|
+
/**
|
|
139
|
+
* Stream-parse a single Codex JSONL file to extract session metadata.
|
|
140
|
+
*
|
|
141
|
+
* `resolveAccount` is a lazy thunk (not a resolved string): the JWT decode it
|
|
142
|
+
* performs is deferred until we know this file is a real session worth building
|
|
143
|
+
* metadata for, and only then — never during the file walk / stat phase.
|
|
144
|
+
*/
|
|
145
|
+
export declare function readCodexMeta(filePath: string, resolveAccount?: () => string | undefined, currentVersion?: string): Promise<{
|
|
146
|
+
meta: SessionMeta;
|
|
147
|
+
content: string;
|
|
148
|
+
} | null>;
|
|
110
149
|
/** Stream a Claude JSONL file and extract scan-level metadata (timestamp, cwd, topic, tokens). */
|
|
111
150
|
export declare function scanClaudeSession(filePath: string): Promise<ClaudeSessionScan>;
|
|
112
151
|
/** Read up to maxLines non-empty lines from the beginning of a file. */
|