@phnx-labs/agents-cli 1.20.73 → 1.20.76
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 +989 -0
- package/README.md +36 -10
- package/dist/bin/agents +0 -0
- package/dist/commands/apply.js +21 -3
- package/dist/commands/attach.d.ts +10 -0
- package/dist/commands/attach.js +41 -0
- package/dist/commands/computer.js +3 -3
- package/dist/commands/detach-core.d.ts +46 -0
- package/dist/commands/detach-core.js +61 -0
- package/dist/commands/detach.d.ts +5 -0
- package/dist/commands/detach.js +171 -0
- package/dist/commands/doctor.js +31 -1
- package/dist/commands/exec.js +31 -3
- package/dist/commands/export.d.ts +11 -0
- package/dist/commands/export.js +215 -0
- package/dist/commands/feed.d.ts +7 -1
- package/dist/commands/feed.js +47 -4
- package/dist/commands/go.d.ts +9 -2
- package/dist/commands/go.js +16 -6
- package/dist/commands/harness.d.ts +14 -0
- package/dist/commands/harness.js +145 -0
- package/dist/commands/import.js +87 -9
- package/dist/commands/inspect.js +5 -2
- package/dist/commands/profiles.d.ts +20 -0
- package/dist/commands/profiles.js +68 -21
- package/dist/commands/repo.js +14 -0
- package/dist/commands/routines.js +15 -2
- package/dist/commands/secrets.js +44 -21
- package/dist/commands/sessions-browser.js +1 -1
- package/dist/commands/sessions-export.js +19 -5
- package/dist/commands/sessions-migrate.d.ts +29 -0
- package/dist/commands/sessions-migrate.js +596 -0
- package/dist/commands/sessions-picker.js +16 -1
- package/dist/commands/sessions.d.ts +36 -13
- package/dist/commands/sessions.js +210 -40
- package/dist/commands/setup-computer.js +2 -2
- package/dist/commands/setup.js +6 -1
- package/dist/commands/ssh.js +71 -35
- package/dist/commands/status.js +10 -2
- package/dist/commands/versions.js +51 -11
- package/dist/commands/view.d.ts +5 -0
- package/dist/commands/view.js +99 -31
- package/dist/commands/watchdog.d.ts +7 -2
- package/dist/commands/watchdog.js +73 -57
- package/dist/index.js +59 -7
- package/dist/lib/activity.d.ts +14 -4
- package/dist/lib/activity.js +17 -5
- package/dist/lib/actor.d.ts +50 -0
- package/dist/lib/actor.js +166 -0
- package/dist/lib/agent-spec/provider.js +2 -1
- package/dist/lib/agent-spec/resolve.js +19 -5
- package/dist/lib/agent-spec/types.d.ts +9 -1
- package/dist/lib/agents.d.ts +56 -0
- package/dist/lib/agents.js +133 -7
- package/dist/lib/cloud/codex.d.ts +2 -0
- package/dist/lib/cloud/codex.js +14 -3
- package/dist/lib/cloud/session-index.d.ts +32 -0
- package/dist/lib/cloud/session-index.js +58 -0
- package/dist/lib/cloud/store.d.ts +7 -0
- package/dist/lib/cloud/store.js +25 -0
- package/dist/lib/config-transfer.d.ts +16 -0
- package/dist/lib/config-transfer.js +60 -0
- package/dist/lib/daemon.js +18 -2
- package/dist/lib/devices/connect.d.ts +15 -1
- package/dist/lib/devices/connect.js +15 -1
- package/dist/lib/devices/fleet.d.ts +21 -0
- package/dist/lib/devices/fleet.js +26 -0
- package/dist/lib/devices/health-report.d.ts +38 -0
- package/dist/lib/devices/health-report.js +214 -0
- package/dist/lib/devices/health.js +4 -1
- package/dist/lib/devices/reachability.d.ts +31 -0
- package/dist/lib/devices/reachability.js +40 -0
- package/dist/lib/devices/registry.d.ts +33 -0
- package/dist/lib/devices/registry.js +37 -0
- package/dist/lib/devices/resolve-target.d.ts +15 -27
- package/dist/lib/devices/resolve-target.js +63 -102
- package/dist/lib/devices/sync.d.ts +18 -0
- package/dist/lib/devices/sync.js +23 -1
- package/dist/lib/devices/tailscale.d.ts +3 -0
- package/dist/lib/devices/tailscale.js +1 -0
- package/dist/lib/events.d.ts +1 -1
- package/dist/lib/exec.d.ts +66 -0
- package/dist/lib/exec.js +195 -6
- package/dist/lib/export.d.ts +72 -0
- package/dist/lib/export.js +269 -0
- package/dist/lib/feed-post.d.ts +63 -0
- package/dist/lib/feed-post.js +204 -0
- package/dist/lib/fleet/apply.d.ts +32 -2
- package/dist/lib/fleet/apply.js +97 -10
- package/dist/lib/fleet/types.d.ts +11 -0
- package/dist/lib/fs-walk.d.ts +13 -0
- package/dist/lib/fs-walk.js +16 -7
- package/dist/lib/hooks.d.ts +1 -0
- package/dist/lib/hooks.js +156 -0
- package/dist/lib/hosts/dispatch.d.ts +7 -0
- package/dist/lib/hosts/dispatch.js +2 -0
- package/dist/lib/hosts/passthrough.js +2 -0
- package/dist/lib/hosts/reconnect.d.ts +78 -0
- package/dist/lib/hosts/reconnect.js +127 -0
- package/dist/lib/hosts/registry.d.ts +75 -14
- package/dist/lib/hosts/registry.js +205 -30
- package/dist/lib/hosts/remote-cmd.js +1 -0
- package/dist/lib/hosts/run-target.d.ts +13 -5
- package/dist/lib/hosts/run-target.js +26 -10
- package/dist/lib/hosts/session-index.d.ts +18 -1
- package/dist/lib/hosts/session-index.js +37 -2
- package/dist/lib/hosts/session-marker.d.ts +33 -0
- package/dist/lib/hosts/session-marker.js +51 -0
- package/dist/lib/hosts/tasks.d.ts +8 -4
- package/dist/lib/import.d.ts +25 -0
- package/dist/lib/import.js +107 -2
- package/dist/lib/isolation-boundary-report.d.ts +10 -0
- package/dist/lib/isolation-boundary-report.js +35 -0
- package/dist/lib/menubar/install-menubar.d.ts +20 -0
- package/dist/lib/menubar/install-menubar.js +45 -1
- package/dist/lib/migrate.d.ts +16 -0
- package/dist/lib/migrate.js +36 -0
- package/dist/lib/models.d.ts +27 -0
- package/dist/lib/models.js +54 -1
- package/dist/lib/overdue.d.ts +7 -2
- package/dist/lib/overdue.js +18 -11
- package/dist/lib/picker.js +4 -1
- package/dist/lib/platform/process.d.ts +17 -0
- package/dist/lib/platform/process.js +70 -0
- package/dist/lib/profiles-presets.js +9 -7
- package/dist/lib/profiles.d.ts +31 -0
- package/dist/lib/profiles.js +70 -0
- package/dist/lib/project-resources.js +20 -5
- package/dist/lib/pty-server.d.ts +2 -10
- package/dist/lib/pty-server.js +4 -38
- package/dist/lib/registry.d.ts +1 -1
- package/dist/lib/registry.js +48 -8
- package/dist/lib/rotate.d.ts +18 -0
- package/dist/lib/rotate.js +28 -0
- package/dist/lib/routines.d.ts +26 -0
- package/dist/lib/routines.js +19 -2
- package/dist/lib/runner.js +104 -7
- package/dist/lib/secrets/agent.d.ts +66 -1
- package/dist/lib/secrets/agent.js +154 -90
- package/dist/lib/secrets/bundles.d.ts +34 -0
- package/dist/lib/secrets/bundles.js +179 -19
- package/dist/lib/secrets/index.d.ts +15 -1
- package/dist/lib/secrets/index.js +21 -3
- package/dist/lib/secrets/rc-hygiene.d.ts +55 -0
- package/dist/lib/secrets/rc-hygiene.js +154 -0
- package/dist/lib/secrets/remote.d.ts +7 -4
- package/dist/lib/secrets/remote.js +7 -4
- package/dist/lib/secrets/sync-commands.d.ts +21 -0
- package/dist/lib/secrets/sync-commands.js +21 -0
- package/dist/lib/self-update.d.ts +23 -2
- package/dist/lib/self-update.js +86 -5
- package/dist/lib/session/active.d.ts +114 -7
- package/dist/lib/session/active.js +323 -52
- package/dist/lib/session/cloud.js +2 -1
- package/dist/lib/session/db.d.ts +48 -0
- package/dist/lib/session/db.js +238 -14
- package/dist/lib/session/detached.d.ts +30 -0
- package/dist/lib/session/detached.js +92 -0
- package/dist/lib/session/discover.d.ts +465 -2
- package/dist/lib/session/discover.js +1648 -468
- package/dist/lib/session/fork.js +2 -1
- package/dist/lib/session/hook-sessions.d.ts +43 -0
- package/dist/lib/session/hook-sessions.js +135 -0
- package/dist/lib/session/linear.d.ts +6 -0
- package/dist/lib/session/linear.js +55 -0
- package/dist/lib/session/migrate-targets.d.ts +65 -0
- package/dist/lib/session/migrate-targets.js +94 -0
- package/dist/lib/session/migrations.d.ts +37 -0
- package/dist/lib/session/migrations.js +60 -0
- package/dist/lib/session/parse.d.ts +18 -0
- package/dist/lib/session/parse.js +130 -30
- package/dist/lib/session/pid-registry.d.ts +34 -2
- package/dist/lib/session/pid-registry.js +49 -2
- package/dist/lib/session/render.d.ts +7 -2
- package/dist/lib/session/render.js +22 -10
- package/dist/lib/session/short-id.d.ts +17 -0
- package/dist/lib/session/short-id.js +20 -0
- package/dist/lib/session/state.js +6 -2
- package/dist/lib/shims.d.ts +27 -1
- package/dist/lib/shims.js +88 -6
- package/dist/lib/sqlite.d.ts +3 -2
- package/dist/lib/sqlite.js +27 -4
- package/dist/lib/staleness/detectors/commands.js +1 -1
- package/dist/lib/staleness/detectors/workflows.js +13 -2
- package/dist/lib/staleness/writers/commands.js +7 -7
- package/dist/lib/staleness/writers/workflows.d.ts +4 -2
- package/dist/lib/startup/command-registry.d.ts +2 -0
- package/dist/lib/startup/command-registry.js +5 -0
- package/dist/lib/state.d.ts +1 -1
- package/dist/lib/state.js +15 -36
- package/dist/lib/teams/agents.d.ts +19 -10
- package/dist/lib/teams/agents.js +40 -39
- package/dist/lib/types.d.ts +48 -2
- package/dist/lib/uninstall.js +1 -45
- package/dist/lib/usage.d.ts +33 -1
- package/dist/lib/usage.js +172 -12
- package/dist/lib/versions.d.ts +22 -2
- package/dist/lib/versions.js +98 -10
- package/dist/lib/watchdog/log.d.ts +43 -0
- package/dist/lib/watchdog/log.js +69 -0
- package/dist/lib/watchdog/routine.d.ts +44 -0
- package/dist/lib/watchdog/routine.js +69 -0
- package/dist/lib/watchdog/runner.d.ts +51 -7
- package/dist/lib/watchdog/runner.js +239 -64
- package/dist/lib/watchdog/watchdog.d.ts +1 -1
- package/dist/lib/watchdog/watchdog.js +31 -16
- package/dist/lib/workflows.d.ts +16 -0
- package/dist/lib/workflows.js +110 -1
- package/package.json +1 -1
|
@@ -11,6 +11,14 @@ import { type ScanStamp } from './db.js';
|
|
|
11
11
|
/** Options controlling which sessions to discover and how to report progress. */
|
|
12
12
|
export interface DiscoverOptions {
|
|
13
13
|
agent?: SessionAgentId;
|
|
14
|
+
/**
|
|
15
|
+
* Include sessions from the user's own (unmanaged) `~/.<agent>` alongside managed
|
|
16
|
+
* version homes. Defaults to true only when the agent has no managed versions, so
|
|
17
|
+
* a user who has never run `agents add` sees exactly what they see today.
|
|
18
|
+
*/
|
|
19
|
+
includeUnmanaged?: boolean;
|
|
20
|
+
/** Called with how many rows the managed-only default hid, so callers can say so. */
|
|
21
|
+
onHiddenUnmanaged?: (count: number) => void;
|
|
14
22
|
version?: string;
|
|
15
23
|
project?: string;
|
|
16
24
|
all?: boolean;
|
|
@@ -75,6 +83,29 @@ interface ClaudeSessionScan {
|
|
|
75
83
|
/** Plan markdown from the last ExitPlanMode tool call (Claude sessions only). */
|
|
76
84
|
plan?: string;
|
|
77
85
|
}
|
|
86
|
+
/** Lightweight metadata extracted from a Codex JSONL file during incremental scan. */
|
|
87
|
+
interface CodexSessionScan {
|
|
88
|
+
sessionId?: string;
|
|
89
|
+
timestamp?: string;
|
|
90
|
+
cwd?: string;
|
|
91
|
+
gitBranch?: string;
|
|
92
|
+
version?: string;
|
|
93
|
+
topic?: string;
|
|
94
|
+
messageCount: number;
|
|
95
|
+
tokenCount?: number;
|
|
96
|
+
/** Real generated (output) tokens, excluding cache-read/-write context. */
|
|
97
|
+
outputTokens?: number;
|
|
98
|
+
costUsd?: number;
|
|
99
|
+
durationMs?: number;
|
|
100
|
+
lastActivity?: string;
|
|
101
|
+
contentText?: string;
|
|
102
|
+
prUrl?: string;
|
|
103
|
+
prNumber?: number;
|
|
104
|
+
worktreeSlug?: string;
|
|
105
|
+
ticketId?: string;
|
|
106
|
+
createdTickets?: string[];
|
|
107
|
+
spawnedTeam?: string;
|
|
108
|
+
}
|
|
78
109
|
/**
|
|
79
110
|
* Discover sessions. Scans only files whose (mtime, size) have changed since
|
|
80
111
|
* the last run; everything else is served from the SQLite cache.
|
|
@@ -101,6 +132,18 @@ export declare function scanAgentsBounded<T>(items: readonly T[], run: (item: T)
|
|
|
101
132
|
* when the path sits under the agent's backups root, the first segment below it
|
|
102
133
|
* is the origin machine id; otherwise it's the local machine.
|
|
103
134
|
*/
|
|
135
|
+
/**
|
|
136
|
+
* True when this transcript belongs to a version agents-cli manages — i.e. it lives
|
|
137
|
+
* under a version home (or a backup mirror of one) rather than in the user's own
|
|
138
|
+
* `~/.<agent>`.
|
|
139
|
+
*
|
|
140
|
+
* `agents sessions` scans both, which is right for indexing: the DB stays a complete
|
|
141
|
+
* picture and `--unmanaged` can surface everything without a re-scan. But listing
|
|
142
|
+
* *by default* is a different question. Once you have managed versions, an unmanaged
|
|
143
|
+
* install's history is not really agents-cli's to show — most visibly after
|
|
144
|
+
* `agents add --isolated`, where the whole point was to keep the two apart.
|
|
145
|
+
*/
|
|
146
|
+
export declare function isManagedSessionFile(filePath: string): boolean;
|
|
104
147
|
export declare function machineForSessionFile(filePath: string, agent: string): string;
|
|
105
148
|
/**
|
|
106
149
|
* Count sessions in scope without running an incremental scan. Assumes the DB
|
|
@@ -108,6 +151,45 @@ export declare function machineForSessionFile(filePath: string, agent: string):
|
|
|
108
151
|
* turn). Uses the exact same filter shape as the discover query.
|
|
109
152
|
*/
|
|
110
153
|
export declare function countSessionsInScope(options: DiscoverOptions): number;
|
|
154
|
+
/**
|
|
155
|
+
* Canonicalize a working directory path (follows symlinks when it is local).
|
|
156
|
+
*
|
|
157
|
+
* Most callers pass a cwd RECORDED in a transcript, which may name a directory
|
|
158
|
+
* on another machine — a POSIX path read on a Windows host, say. `path.resolve()`
|
|
159
|
+
* rebases such a path onto the current drive (`/Users/me` -> `D:\Users\me`),
|
|
160
|
+
* inventing a location that never existed. So an absolute path is normalized but
|
|
161
|
+
* never rebased; only a genuinely relative one resolves against the process cwd.
|
|
162
|
+
*
|
|
163
|
+
* `path.normalize()` still runs on every branch: it collapses `.`, `..`, and
|
|
164
|
+
* duplicate separators, and folds separators on Windows. Both sides of the cwd
|
|
165
|
+
* filter in `db.ts` (`cwd = ?` and `cwd LIKE ? || path.sep || '%'`) come through
|
|
166
|
+
* here, so dropping that would leave a trailing slash or a `..` segment in one
|
|
167
|
+
* side and match nothing.
|
|
168
|
+
*
|
|
169
|
+
* Realpath is attempted only for a path that is absolute in THIS platform's
|
|
170
|
+
* terms. A POSIX-rooted path on Windows is drive-relative to `fs.realpathSync`,
|
|
171
|
+
* which would resolve `/Users/me` against the current drive and reintroduce the
|
|
172
|
+
* graft for any path that happens to exist locally.
|
|
173
|
+
*/
|
|
174
|
+
export declare function _normalizeCwdForTest(cwd?: string): string;
|
|
175
|
+
/**
|
|
176
|
+
* Whether a query is a session id in full, rather than an id prefix or a search
|
|
177
|
+
* phrase.
|
|
178
|
+
*
|
|
179
|
+
* Callers use this to decide that an id lookup is the ONLY admissible
|
|
180
|
+
* interpretation: a complete id is unique, so when it misses there is nothing
|
|
181
|
+
* left to widen to. Without the check, `sessions <uuid>` fell through to the
|
|
182
|
+
* FTS content search, which tokenizes the UUID and matches every transcript that
|
|
183
|
+
* merely mentions it — surfacing unrelated sessions as if they were id matches.
|
|
184
|
+
*
|
|
185
|
+
* The accepted shapes are the ones the index actually holds, measured over a
|
|
186
|
+
* 12,507-row index: a bare UUID (11,116 rows), `session_` + UUID (1,360 — kimi
|
|
187
|
+
* and rush), and `ses_` + ULID (15 — opencode). Deliberately NOT covered, so a
|
|
188
|
+
* miss keeps today's search behavior rather than gaining a wrong error: routine
|
|
189
|
+
* run ids (ISO timestamps, matched via `routineRunId` below) and cloud execution
|
|
190
|
+
* ids, whose charset is too permissive to distinguish from a search phrase.
|
|
191
|
+
*/
|
|
192
|
+
export declare function isCompleteSessionId(query: string): boolean;
|
|
111
193
|
/**
|
|
112
194
|
* Resolve a session by full or short ID. Accepts a pre-loaded session list
|
|
113
195
|
* (fast path from discoverSessions) and falls back to a DB lookup for the
|
|
@@ -119,7 +201,87 @@ export declare function resolveSessionById(sessions: SessionMeta[], idQuery: str
|
|
|
119
201
|
* preserving the existing SessionMeta[] contract so sessions.ts is unchanged.
|
|
120
202
|
*/
|
|
121
203
|
export declare function searchContentIndex(sessions: SessionMeta[], query: string): Map<string, SessionMeta>;
|
|
204
|
+
/**
|
|
205
|
+
* For a list of files, stat each, compare to the DB ledger, and return only
|
|
206
|
+
* the ones that need rescanning. One bulk DB query for the whole list.
|
|
207
|
+
*
|
|
208
|
+
* Actively running agents append to their JSONL every few seconds. Without a
|
|
209
|
+
* small debounce, repeated `agents sessions` invocations stream-parse the same
|
|
210
|
+
* growing transcript over and over. The cached row is good enough for a few
|
|
211
|
+
* seconds; once writes settle or the debounce expires, the file is parsed once.
|
|
212
|
+
*/
|
|
213
|
+
export declare function filterChangedFiles(filePaths: string[]): Array<{
|
|
214
|
+
filePath: string;
|
|
215
|
+
scan: ScanStamp;
|
|
216
|
+
}>;
|
|
217
|
+
/** A path already stat'd by the walk — mtime is the raw (unfloored) fs value. */
|
|
218
|
+
export interface PreStatEntry {
|
|
219
|
+
filePath: string;
|
|
220
|
+
fileMtimeMs: number;
|
|
221
|
+
fileSize: number;
|
|
222
|
+
}
|
|
223
|
+
/**
|
|
224
|
+
* Ledger-compare pre-stat'd entries (from the walk's own stat) without a second
|
|
225
|
+
* stat. Same debounce and change-detection as filterChangedFiles; the raw
|
|
226
|
+
* mtime is floored here so warm files match the ledger exactly as the stat path
|
|
227
|
+
* does (Math.floor(stat.mtimeMs)).
|
|
228
|
+
*/
|
|
229
|
+
export declare function filterChangedEntries(entries: PreStatEntry[]): Array<{
|
|
230
|
+
filePath: string;
|
|
231
|
+
scan: ScanStamp;
|
|
232
|
+
}>;
|
|
122
233
|
export declare function shouldDeferRecentAppend(prev: ScanStamp, current: ScanStamp, nowMs: number, debounceMs?: number): boolean;
|
|
234
|
+
/** One leaf directory of transcripts to change-detect, plus its live-root flag. */
|
|
235
|
+
export interface LeafDir {
|
|
236
|
+
/** Absolute path to the directory that directly holds transcript files. */
|
|
237
|
+
dirPath: string;
|
|
238
|
+
/**
|
|
239
|
+
* True if this dir is under the agent's LIVE `~/.<agent>` root — the only tree
|
|
240
|
+
* an agent process appends to live. Every file in such a dir is treated as
|
|
241
|
+
* hot (always re-stat'd), so an in-place append is never missed there.
|
|
242
|
+
*/
|
|
243
|
+
isLiveRoot: boolean;
|
|
244
|
+
}
|
|
245
|
+
/** The changed files a leaf-dir walk surfaced, ready to parse + upsert. */
|
|
246
|
+
export interface LeafDirScan {
|
|
247
|
+
/** Files whose (mtime, size) changed vs the ledger — the parse set. */
|
|
248
|
+
changed: Array<{
|
|
249
|
+
filePath: string;
|
|
250
|
+
scan: ScanStamp;
|
|
251
|
+
}>;
|
|
252
|
+
/**
|
|
253
|
+
* Every transcript file seen across all leaf dirs (changed or not), in
|
|
254
|
+
* live-root-first order, each tagged with whether its dir is a live root.
|
|
255
|
+
* Lets a caller restore cross-root, session-id precedence (prefer the live
|
|
256
|
+
* copy of a session over a frozen backup copy) independent of which copy
|
|
257
|
+
* happened to be flagged "changed" this run.
|
|
258
|
+
*/
|
|
259
|
+
allFiles: Array<{
|
|
260
|
+
filePath: string;
|
|
261
|
+
isLiveRoot: boolean;
|
|
262
|
+
}>;
|
|
263
|
+
}
|
|
264
|
+
/**
|
|
265
|
+
* Walk a set of leaf transcript directories and return the files that changed,
|
|
266
|
+
* skipping the per-file `stat` of directories whose (mtime, entry_count) matches
|
|
267
|
+
* the dir_ledger.
|
|
268
|
+
*
|
|
269
|
+
* Per leaf dir:
|
|
270
|
+
* - `stat` the dir once and `readdir` it (one cheap syscall) to get the entry
|
|
271
|
+
* count and the file list.
|
|
272
|
+
* - If the dir matches the dir_ledger (floored mtime AND entry_count), no file
|
|
273
|
+
* was created / deleted / renamed since we last walked it. We then stat ONLY
|
|
274
|
+
* the hot files (live-root files, or files scanned within HOT_FILE_WINDOW_MS)
|
|
275
|
+
* and run just those through the ledger compare — so an in-place append to a
|
|
276
|
+
* still-live session is still caught, while immutable backup/version dirs
|
|
277
|
+
* collapse to a single dir stat and zero per-file stats.
|
|
278
|
+
* - Else (changed dir, or no ledger row) we stat every file (today's full
|
|
279
|
+
* walk) and record the fresh dir stamp so the next run can short-circuit.
|
|
280
|
+
*
|
|
281
|
+
* The kill-switch (`AGENTS_SESSIONS_NO_DIR_LEDGER=1`) forces the full-walk branch
|
|
282
|
+
* for every dir and never consults or records the dir_ledger.
|
|
283
|
+
*/
|
|
284
|
+
export declare function collectChangedFilesInLeafDirs(leafDirs: LeafDir[], ext: string): LeafDirScan;
|
|
123
285
|
/**
|
|
124
286
|
* Collect all directories to scan for an agent's sessions. Deduplicates by
|
|
125
287
|
* realpath to avoid double-counting symlinked version homes.
|
|
@@ -165,14 +327,315 @@ export declare function parseCodexThreadNameIndex(raw: string): Map<string, stri
|
|
|
165
327
|
* performs is deferred until we know this file is a real session worth building
|
|
166
328
|
* metadata for, and only then — never during the file walk / stat phase.
|
|
167
329
|
*/
|
|
168
|
-
export declare function readCodexMeta(filePath: string, resolveAccount?: () => string | undefined, currentVersion?: string
|
|
330
|
+
export declare function readCodexMeta(filePath: string, resolveAccount?: () => string | undefined, currentVersion?: string, scanStamp?: ScanStamp, priorRow?: {
|
|
331
|
+
parserState: string | null;
|
|
332
|
+
fileMtimeMs: number;
|
|
333
|
+
}): Promise<{
|
|
169
334
|
meta: SessionMeta;
|
|
170
335
|
content: string;
|
|
336
|
+
parserState?: string;
|
|
337
|
+
contentText?: string;
|
|
171
338
|
} | null>;
|
|
339
|
+
/**
|
|
340
|
+
* Mutable accumulator for the Claude transcript reducer. One field per local
|
|
341
|
+
* that {@link scanClaudeSession} previously declared inline — the reducer
|
|
342
|
+
* mutates `state.*` instead of closure locals so the exact same logic can drive
|
|
343
|
+
* both a full parse and a resumable incremental parse (see
|
|
344
|
+
* {@link scanClaudeSessionIncremental}).
|
|
345
|
+
*/
|
|
346
|
+
export interface ClaudeParseState {
|
|
347
|
+
timestamp?: string;
|
|
348
|
+
cwd?: string;
|
|
349
|
+
gitBranch?: string;
|
|
350
|
+
version?: string;
|
|
351
|
+
topic?: string;
|
|
352
|
+
customTitle?: string;
|
|
353
|
+
aiTitle?: string;
|
|
354
|
+
entrypoint?: string;
|
|
355
|
+
messageCount: number;
|
|
356
|
+
tokenCount: number;
|
|
357
|
+
outputTokens: number;
|
|
358
|
+
sawTokenCount: boolean;
|
|
359
|
+
costUsd: number;
|
|
360
|
+
sawCost: boolean;
|
|
361
|
+
firstTsMs?: number;
|
|
362
|
+
lastTsMs?: number;
|
|
363
|
+
seenAssistantIds: Set<string>;
|
|
364
|
+
userTexts: string[];
|
|
365
|
+
sawPrCreate: boolean;
|
|
366
|
+
prUrl?: string;
|
|
367
|
+
prNumber?: number;
|
|
368
|
+
createdTickets: Set<string>;
|
|
369
|
+
pendingTicketTools: Set<string>;
|
|
370
|
+
spawnedTeam?: string;
|
|
371
|
+
plan?: string;
|
|
372
|
+
}
|
|
373
|
+
/** Zero-value accumulator for a fresh (from-byte-0) Claude parse. */
|
|
374
|
+
export declare function initClaudeParseState(): ClaudeParseState;
|
|
375
|
+
/**
|
|
376
|
+
* Fold one parsed transcript line into the accumulator. This is the exact loop
|
|
377
|
+
* body {@link scanClaudeSession} used to run inline — extracted verbatim,
|
|
378
|
+
* mutating `state.*` in place. `parsed` is the already-`JSON.parse`d line (the
|
|
379
|
+
* malformed-line skip happens in the caller, as before).
|
|
380
|
+
*/
|
|
381
|
+
export declare function applyClaudeLine(state: ClaudeParseState, parsed: any): void;
|
|
382
|
+
/**
|
|
383
|
+
* Build the {@link ClaudeSessionScan} return object from an accumulator. This is
|
|
384
|
+
* the exact return-building {@link scanClaudeSession} used to run inline.
|
|
385
|
+
*/
|
|
386
|
+
export declare function finalizeClaudeScan(state: ClaudeParseState): ClaudeSessionScan;
|
|
172
387
|
/** Stream a Claude JSONL file and extract scan-level metadata (timestamp, cwd, topic, tokens). */
|
|
173
388
|
export declare function scanClaudeSession(filePath: string): Promise<ClaudeSessionScan>;
|
|
389
|
+
/**
|
|
390
|
+
* SERIALIZED continuation blob persisted in `scan_ledger.parser_state`. Carries
|
|
391
|
+
* everything {@link hydrateClaudeParseState} needs to resume a parse from
|
|
392
|
+
* `offset` such that resuming + applying the appended lines is byte-for-byte
|
|
393
|
+
* identical to a full parse of the whole file.
|
|
394
|
+
*
|
|
395
|
+
* `seenAssistantIds` is persisted as a size counter plus a bounded FIFO window
|
|
396
|
+
* of the most-recent ids: the fallback logical id `${ts}:${seenAssistantIds.size}`
|
|
397
|
+
* (see {@link applyClaudeLine}) depends on the set's *size*, so the size must be
|
|
398
|
+
* exact even when the recent window is smaller than the true count.
|
|
399
|
+
*/
|
|
400
|
+
export interface ClaudeParserState {
|
|
401
|
+
v: 1;
|
|
402
|
+
offset: number;
|
|
403
|
+
timestamp?: string;
|
|
404
|
+
cwd?: string;
|
|
405
|
+
gitBranch?: string;
|
|
406
|
+
version?: string;
|
|
407
|
+
entrypoint?: string;
|
|
408
|
+
firstTsMs?: number;
|
|
409
|
+
topic?: string;
|
|
410
|
+
customTitle?: string;
|
|
411
|
+
aiTitle?: string;
|
|
412
|
+
plan?: string;
|
|
413
|
+
lastTsMs?: number;
|
|
414
|
+
messageCount: number;
|
|
415
|
+
tokenCount: number;
|
|
416
|
+
outputTokens: number;
|
|
417
|
+
sawTokenCount: boolean;
|
|
418
|
+
sawCost: boolean;
|
|
419
|
+
costUsd: number;
|
|
420
|
+
seenIdsSize: number;
|
|
421
|
+
seenIdsRecent: string[];
|
|
422
|
+
sawPrCreate: boolean;
|
|
423
|
+
prUrl?: string;
|
|
424
|
+
prNumber?: number;
|
|
425
|
+
pendingTicketTools: string[];
|
|
426
|
+
createdTickets: string[];
|
|
427
|
+
spawnedTeam?: string;
|
|
428
|
+
ticketId?: string;
|
|
429
|
+
contentText?: string;
|
|
430
|
+
}
|
|
431
|
+
/**
|
|
432
|
+
* Snapshot a live {@link ClaudeParseState} into its serializable form at
|
|
433
|
+
* `offset` bytes consumed. Round-trips through {@link hydrateClaudeParseState}
|
|
434
|
+
* so incremental replay equals a full parse.
|
|
435
|
+
*/
|
|
436
|
+
export declare function serializeClaudeParserState(state: ClaudeParseState, offset: number): ClaudeParserState;
|
|
437
|
+
/**
|
|
438
|
+
* Rebuild a live {@link ClaudeParseState} from a persisted continuation so that
|
|
439
|
+
* applying the appended lines yields the same accumulator a full parse would.
|
|
440
|
+
*
|
|
441
|
+
* `seenAssistantIds` is rehydrated from the recent-id FIFO window, then padded
|
|
442
|
+
* with unique sentinel entries so its `.size` matches the true prior count
|
|
443
|
+
* (`seenIdsSize`) — the fallback id `${ts}:${size}` must line up with the full
|
|
444
|
+
* parse even when the window dropped older ids. Padding sentinels can never
|
|
445
|
+
* collide with a real logical id (real ids are message ids/uuids or
|
|
446
|
+
* `${ts}:${n}`; the sentinel prefix is not JSON-line-derived).
|
|
447
|
+
*
|
|
448
|
+
* `userTexts` is rehydrated as a single joined blob from `contentText`: only
|
|
449
|
+
* `userTexts.join('\n')` (detectTicket + contentText) and `userTexts.length > 0`
|
|
450
|
+
* are ever read downstream, and both are preserved by a one-element array
|
|
451
|
+
* holding the joined content.
|
|
452
|
+
*/
|
|
453
|
+
export declare function hydrateClaudeParseState(prior: ClaudeParserState): ClaudeParseState;
|
|
454
|
+
/**
|
|
455
|
+
* Resume a Claude parse from `fromOffset` bytes into the file, folding only the
|
|
456
|
+
* newly-appended lines into `prior`. Returns the finalized scan, the next
|
|
457
|
+
* serialized continuation, and the byte offset to resume from next time —
|
|
458
|
+
* `newOffset` stops at the last `'\n'` seen so a half-written trailing record is
|
|
459
|
+
* re-read (not lost) on the next append.
|
|
460
|
+
*
|
|
461
|
+
* NOT wired into the live scan path yet (that is B-2); {@link scanClaudeSession}
|
|
462
|
+
* remains the only caller-facing entry point. This exists so the parity harness
|
|
463
|
+
* can prove full === hydrate(state@k) + apply(k+1..n).
|
|
464
|
+
*/
|
|
465
|
+
export declare function scanClaudeSessionIncremental(filePath: string, fromOffset: number, prior: ClaudeParserState): Promise<{
|
|
466
|
+
scan: ClaudeSessionScan;
|
|
467
|
+
newState: ClaudeParserState;
|
|
468
|
+
newOffset: number;
|
|
469
|
+
}>;
|
|
470
|
+
/** Test seam: read the (incremental, full) Claude parse counters. */
|
|
471
|
+
export declare function __claudeScanBranchCountsForTest(): {
|
|
472
|
+
incremental: number;
|
|
473
|
+
full: number;
|
|
474
|
+
};
|
|
475
|
+
/** Test seam: reset the Claude parse-branch counters to observe a scan from a clean slate. */
|
|
476
|
+
export declare function __resetClaudeScanBranchCountsForTest(): void;
|
|
477
|
+
/**
|
|
478
|
+
* Live (in-memory) accumulator for a Codex parse — the mutable state
|
|
479
|
+
* {@link scanCodexSession} used to hold in local `let`s, extracted so the same
|
|
480
|
+
* fold ({@link applyCodexLine}) runs for both a full parse and an incremental
|
|
481
|
+
* resume. Mirrors {@link ClaudeParseState}.
|
|
482
|
+
*/
|
|
483
|
+
export interface CodexParseState {
|
|
484
|
+
sessionId?: string;
|
|
485
|
+
timestamp?: string;
|
|
486
|
+
cwd?: string;
|
|
487
|
+
gitBranch?: string;
|
|
488
|
+
version?: string;
|
|
489
|
+
model?: string;
|
|
490
|
+
topic?: string;
|
|
491
|
+
messageCount: number;
|
|
492
|
+
tokenCount?: number;
|
|
493
|
+
lastTotalTokenUsage?: any;
|
|
494
|
+
firstTsMs?: number;
|
|
495
|
+
lastTsMs?: number;
|
|
496
|
+
userTexts: string[];
|
|
497
|
+
sawPrCreate: boolean;
|
|
498
|
+
prUrl?: string;
|
|
499
|
+
prNumber?: number;
|
|
500
|
+
createdTickets: Set<string>;
|
|
501
|
+
pendingTicketTools: Set<string>;
|
|
502
|
+
spawnedTeam?: string;
|
|
503
|
+
}
|
|
504
|
+
/** Zero-value accumulator for a fresh (from-byte-0) Codex parse. */
|
|
505
|
+
export declare function initCodexParseState(): CodexParseState;
|
|
506
|
+
/**
|
|
507
|
+
* Fold one parsed Codex line into the accumulator — the exact loop body
|
|
508
|
+
* {@link scanCodexSession} used to run inline, extracted verbatim and mutating
|
|
509
|
+
* `state.*` in place. `parsed` is the already-`JSON.parse`d line (the
|
|
510
|
+
* malformed-line skip happens in the caller, as before).
|
|
511
|
+
*/
|
|
512
|
+
export declare function applyCodexLine(state: CodexParseState, parsed: any): void;
|
|
513
|
+
/**
|
|
514
|
+
* Build the {@link CodexSessionScan} return object from an accumulator — the
|
|
515
|
+
* exact return-building {@link scanCodexSession} used to run inline.
|
|
516
|
+
*/
|
|
517
|
+
export declare function finalizeCodexScan(state: CodexParseState): CodexSessionScan;
|
|
518
|
+
/**
|
|
519
|
+
* SERIALIZED continuation blob persisted in `scan_ledger.parser_state` for a
|
|
520
|
+
* Codex rollout. Carries everything {@link hydrateCodexParseState} needs to
|
|
521
|
+
* resume a parse from `offset` such that resuming + applying the appended lines
|
|
522
|
+
* is byte-for-byte identical to a full parse of the whole file.
|
|
523
|
+
*
|
|
524
|
+
* Unlike Claude, Codex has NO per-message dedup set, so `messageCount` is a
|
|
525
|
+
* plain additive base with no recent-id window to persist. The `lastTotalTokenUsage`
|
|
526
|
+
* object is round-tripped whole so the last-wins cost/output-token pricing at
|
|
527
|
+
* finalize is identical after a resume.
|
|
528
|
+
*/
|
|
529
|
+
export interface CodexParserState {
|
|
530
|
+
v: 1;
|
|
531
|
+
offset: number;
|
|
532
|
+
sessionId?: string;
|
|
533
|
+
timestamp?: string;
|
|
534
|
+
cwd?: string;
|
|
535
|
+
gitBranch?: string;
|
|
536
|
+
version?: string;
|
|
537
|
+
model?: string;
|
|
538
|
+
topic?: string;
|
|
539
|
+
messageCount: number;
|
|
540
|
+
tokenCount?: number;
|
|
541
|
+
lastTotalTokenUsage?: any;
|
|
542
|
+
firstTsMs?: number;
|
|
543
|
+
lastTsMs?: number;
|
|
544
|
+
sawPrCreate: boolean;
|
|
545
|
+
prUrl?: string;
|
|
546
|
+
prNumber?: number;
|
|
547
|
+
pendingTicketTools: string[];
|
|
548
|
+
createdTickets: string[];
|
|
549
|
+
spawnedTeam?: string;
|
|
550
|
+
ticketId?: string;
|
|
551
|
+
contentText?: string;
|
|
552
|
+
}
|
|
553
|
+
/**
|
|
554
|
+
* Snapshot a live {@link CodexParseState} into its serializable form at `offset`
|
|
555
|
+
* bytes consumed. Round-trips through {@link hydrateCodexParseState} so
|
|
556
|
+
* incremental replay equals a full parse.
|
|
557
|
+
*/
|
|
558
|
+
export declare function serializeCodexParserState(state: CodexParseState, offset: number): CodexParserState;
|
|
559
|
+
/**
|
|
560
|
+
* Rebuild a live {@link CodexParseState} from a persisted continuation so that
|
|
561
|
+
* applying the appended lines yields the same accumulator a full parse would.
|
|
562
|
+
*
|
|
563
|
+
* `userTexts` is rehydrated as a single joined blob from `contentText`: only
|
|
564
|
+
* `userTexts.join('\n')` (detectTicket + contentText) and `userTexts.length > 0`
|
|
565
|
+
* are ever read downstream, and both are preserved by a one-element array
|
|
566
|
+
* holding the joined content. Topic is first-wins and already persisted, so a
|
|
567
|
+
* collapsed userTexts never changes it.
|
|
568
|
+
*/
|
|
569
|
+
export declare function hydrateCodexParseState(prior: CodexParserState): CodexParseState;
|
|
570
|
+
/**
|
|
571
|
+
* Resume a Codex parse from `fromOffset` bytes into the file, folding only the
|
|
572
|
+
* newly-appended lines into `prior`. Returns the finalized scan, the next
|
|
573
|
+
* serialized continuation, and the byte offset to resume from next time.
|
|
574
|
+
*
|
|
575
|
+
* Same trailing-line discipline as {@link scanClaudeSessionIncremental}: apply
|
|
576
|
+
* ONLY the run of newline-terminated lines (slice at the last `'\n'`), and set
|
|
577
|
+
* `newOffset = fromOffset + consumedBytes`. Any tail after the last `'\n'` —
|
|
578
|
+
* syntactically broken OR a complete-but-not-yet-terminated record — is DEFERRED
|
|
579
|
+
* to the next pass once its `'\n'` lands. Codex `messageCount` is additive with
|
|
580
|
+
* NO dedup, so re-reading a still-unterminated complete line would double-count
|
|
581
|
+
* it; deferring prevents that (the bug class prix-cloud caught for Claude).
|
|
582
|
+
*/
|
|
583
|
+
export declare function scanCodexSessionIncremental(filePath: string, fromOffset: number, prior: CodexParserState): Promise<{
|
|
584
|
+
scan: CodexSessionScan;
|
|
585
|
+
newState: CodexParserState;
|
|
586
|
+
newOffset: number;
|
|
587
|
+
}>;
|
|
588
|
+
/** Test seam: read the (incremental, full) Codex parse counters. */
|
|
589
|
+
export declare function __codexScanBranchCountsForTest(): {
|
|
590
|
+
incremental: number;
|
|
591
|
+
full: number;
|
|
592
|
+
};
|
|
593
|
+
/** Test seam: reset the Codex parse-branch counters to observe a scan from a clean slate. */
|
|
594
|
+
export declare function __resetCodexScanBranchCountsForTest(): void;
|
|
174
595
|
/** Parse a single Kimi session state.json file to extract session metadata. */
|
|
175
|
-
export declare function readKimiMeta(filePath: string
|
|
596
|
+
export declare function readKimiMeta(filePath: string, priorRow?: {
|
|
597
|
+
parserState: string | null;
|
|
598
|
+
}): {
|
|
599
|
+
meta: SessionMeta;
|
|
600
|
+
content: string;
|
|
601
|
+
parserState?: string;
|
|
602
|
+
} | null;
|
|
603
|
+
/**
|
|
604
|
+
* Kimi wire metrics are pure additive counters (messageCount, tokenCount,
|
|
605
|
+
* outputTokens) with NO straddle/dedup state, so the continuation is just those
|
|
606
|
+
* three bases plus the byte `offset` already consumed from wire.jsonl. Resuming
|
|
607
|
+
* from `offset` + adding the appended tail's deltas equals a full parse.
|
|
608
|
+
*/
|
|
609
|
+
export interface KimiParserState {
|
|
610
|
+
v: 1;
|
|
611
|
+
offset: number;
|
|
612
|
+
messageCount: number;
|
|
613
|
+
tokenCount: number;
|
|
614
|
+
outputTokens: number;
|
|
615
|
+
}
|
|
616
|
+
/**
|
|
617
|
+
* Incrementally parse Kimi's wire.jsonl for message-count and token counters,
|
|
618
|
+
* resuming from a persisted continuation instead of re-reading from byte 0 every
|
|
619
|
+
* scan. Returns the finalized counters and the next {@link KimiParserState} to
|
|
620
|
+
* persist (offset + the three counter bases).
|
|
621
|
+
*
|
|
622
|
+
* Same trailing-line discipline as {@link scanClaudeSessionIncremental}: read
|
|
623
|
+
* only the appended byte range from `prior.offset`, apply ONLY the run of
|
|
624
|
+
* newline-terminated lines (slice at the last `'\n'`), and advance the offset to
|
|
625
|
+
* `prior.offset + consumedBytes`. A complete-but-not-yet-terminated last record
|
|
626
|
+
* is DEFERRED to the next pass; because these counters are additive with no
|
|
627
|
+
* dedup, re-reading such a line would double-count it. FULL parse from byte 0
|
|
628
|
+
* (fresh counters) when there is no prior OR the file shrank below the stored
|
|
629
|
+
* offset (truncation/rewrite).
|
|
630
|
+
*/
|
|
631
|
+
export declare function parseKimiWireMetricsIncremental(sessionDir: string, prior: KimiParserState | null): {
|
|
632
|
+
messageCount: number;
|
|
633
|
+
tokenCount: number;
|
|
634
|
+
outputTokens: number;
|
|
635
|
+
newState: KimiParserState;
|
|
636
|
+
};
|
|
637
|
+
/** Parse a single Grok session summary.json into session metadata. */
|
|
638
|
+
export declare function readGrokMeta(filePath: string, currentVersion?: string): {
|
|
176
639
|
meta: SessionMeta;
|
|
177
640
|
content: string;
|
|
178
641
|
} | null;
|