@phnx-labs/agents-cli 1.20.74 → 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.
Files changed (199) hide show
  1. package/CHANGELOG.md +893 -0
  2. package/README.md +36 -10
  3. package/dist/bin/agents +0 -0
  4. package/dist/commands/apply.js +21 -3
  5. package/dist/commands/attach.d.ts +10 -0
  6. package/dist/commands/attach.js +41 -0
  7. package/dist/commands/computer.js +3 -3
  8. package/dist/commands/detach-core.d.ts +46 -0
  9. package/dist/commands/detach-core.js +61 -0
  10. package/dist/commands/detach.d.ts +5 -0
  11. package/dist/commands/detach.js +171 -0
  12. package/dist/commands/doctor.js +18 -0
  13. package/dist/commands/exec.js +31 -3
  14. package/dist/commands/feed.d.ts +7 -1
  15. package/dist/commands/feed.js +47 -4
  16. package/dist/commands/go.d.ts +9 -2
  17. package/dist/commands/go.js +16 -6
  18. package/dist/commands/harness.d.ts +14 -0
  19. package/dist/commands/harness.js +145 -0
  20. package/dist/commands/import.js +38 -8
  21. package/dist/commands/inspect.js +5 -2
  22. package/dist/commands/profiles.d.ts +20 -0
  23. package/dist/commands/profiles.js +68 -21
  24. package/dist/commands/repo.js +14 -0
  25. package/dist/commands/routines.js +15 -2
  26. package/dist/commands/secrets.js +44 -21
  27. package/dist/commands/sessions-browser.js +1 -1
  28. package/dist/commands/sessions-export.js +19 -5
  29. package/dist/commands/sessions-migrate.d.ts +29 -0
  30. package/dist/commands/sessions-migrate.js +596 -0
  31. package/dist/commands/sessions-picker.js +16 -1
  32. package/dist/commands/sessions.d.ts +36 -13
  33. package/dist/commands/sessions.js +210 -40
  34. package/dist/commands/setup-computer.js +2 -2
  35. package/dist/commands/ssh.js +71 -35
  36. package/dist/commands/status.js +10 -2
  37. package/dist/commands/versions.js +15 -6
  38. package/dist/commands/view.d.ts +5 -0
  39. package/dist/commands/view.js +24 -4
  40. package/dist/commands/watchdog.d.ts +7 -2
  41. package/dist/commands/watchdog.js +73 -57
  42. package/dist/index.js +58 -7
  43. package/dist/lib/activity.d.ts +14 -4
  44. package/dist/lib/activity.js +17 -5
  45. package/dist/lib/actor.d.ts +50 -0
  46. package/dist/lib/actor.js +166 -0
  47. package/dist/lib/agent-spec/provider.js +2 -1
  48. package/dist/lib/agent-spec/resolve.js +19 -5
  49. package/dist/lib/agent-spec/types.d.ts +9 -1
  50. package/dist/lib/agents.d.ts +21 -0
  51. package/dist/lib/agents.js +70 -6
  52. package/dist/lib/cloud/codex.d.ts +2 -0
  53. package/dist/lib/cloud/codex.js +14 -3
  54. package/dist/lib/cloud/session-index.d.ts +32 -0
  55. package/dist/lib/cloud/session-index.js +58 -0
  56. package/dist/lib/cloud/store.d.ts +7 -0
  57. package/dist/lib/cloud/store.js +25 -0
  58. package/dist/lib/config-transfer.js +4 -0
  59. package/dist/lib/daemon.js +18 -2
  60. package/dist/lib/devices/connect.d.ts +15 -1
  61. package/dist/lib/devices/connect.js +15 -1
  62. package/dist/lib/devices/fleet.d.ts +21 -0
  63. package/dist/lib/devices/fleet.js +26 -0
  64. package/dist/lib/devices/health-report.d.ts +38 -0
  65. package/dist/lib/devices/health-report.js +214 -0
  66. package/dist/lib/devices/health.js +4 -1
  67. package/dist/lib/devices/reachability.d.ts +31 -0
  68. package/dist/lib/devices/reachability.js +40 -0
  69. package/dist/lib/devices/registry.d.ts +33 -0
  70. package/dist/lib/devices/registry.js +37 -0
  71. package/dist/lib/devices/resolve-target.d.ts +15 -27
  72. package/dist/lib/devices/resolve-target.js +63 -102
  73. package/dist/lib/devices/sync.d.ts +18 -0
  74. package/dist/lib/devices/sync.js +23 -1
  75. package/dist/lib/devices/tailscale.d.ts +3 -0
  76. package/dist/lib/devices/tailscale.js +1 -0
  77. package/dist/lib/events.d.ts +1 -1
  78. package/dist/lib/exec.d.ts +66 -0
  79. package/dist/lib/exec.js +195 -6
  80. package/dist/lib/feed-post.d.ts +63 -0
  81. package/dist/lib/feed-post.js +204 -0
  82. package/dist/lib/fleet/apply.d.ts +32 -2
  83. package/dist/lib/fleet/apply.js +97 -10
  84. package/dist/lib/fleet/types.d.ts +11 -0
  85. package/dist/lib/fs-walk.d.ts +13 -0
  86. package/dist/lib/fs-walk.js +16 -7
  87. package/dist/lib/hooks.d.ts +1 -0
  88. package/dist/lib/hooks.js +156 -0
  89. package/dist/lib/hosts/dispatch.d.ts +7 -0
  90. package/dist/lib/hosts/dispatch.js +2 -0
  91. package/dist/lib/hosts/passthrough.js +2 -0
  92. package/dist/lib/hosts/reconnect.d.ts +78 -0
  93. package/dist/lib/hosts/reconnect.js +127 -0
  94. package/dist/lib/hosts/registry.d.ts +75 -14
  95. package/dist/lib/hosts/registry.js +205 -30
  96. package/dist/lib/hosts/remote-cmd.js +1 -0
  97. package/dist/lib/hosts/run-target.d.ts +13 -5
  98. package/dist/lib/hosts/run-target.js +26 -10
  99. package/dist/lib/hosts/session-index.d.ts +18 -1
  100. package/dist/lib/hosts/session-index.js +37 -2
  101. package/dist/lib/hosts/session-marker.d.ts +33 -0
  102. package/dist/lib/hosts/session-marker.js +51 -0
  103. package/dist/lib/hosts/tasks.d.ts +8 -4
  104. package/dist/lib/import.d.ts +2 -0
  105. package/dist/lib/import.js +35 -2
  106. package/dist/lib/menubar/install-menubar.d.ts +20 -0
  107. package/dist/lib/menubar/install-menubar.js +45 -1
  108. package/dist/lib/migrate.d.ts +16 -0
  109. package/dist/lib/migrate.js +36 -0
  110. package/dist/lib/models.d.ts +27 -0
  111. package/dist/lib/models.js +54 -1
  112. package/dist/lib/overdue.d.ts +7 -2
  113. package/dist/lib/overdue.js +18 -11
  114. package/dist/lib/picker.js +4 -1
  115. package/dist/lib/platform/process.d.ts +17 -0
  116. package/dist/lib/platform/process.js +70 -0
  117. package/dist/lib/profiles-presets.js +9 -7
  118. package/dist/lib/profiles.d.ts +31 -0
  119. package/dist/lib/profiles.js +70 -0
  120. package/dist/lib/pty-server.d.ts +2 -10
  121. package/dist/lib/pty-server.js +4 -38
  122. package/dist/lib/registry.d.ts +1 -1
  123. package/dist/lib/registry.js +48 -8
  124. package/dist/lib/rotate.d.ts +18 -0
  125. package/dist/lib/rotate.js +28 -0
  126. package/dist/lib/routines.d.ts +26 -0
  127. package/dist/lib/routines.js +19 -2
  128. package/dist/lib/runner.js +104 -7
  129. package/dist/lib/secrets/agent.d.ts +66 -1
  130. package/dist/lib/secrets/agent.js +154 -90
  131. package/dist/lib/secrets/bundles.d.ts +34 -0
  132. package/dist/lib/secrets/bundles.js +179 -19
  133. package/dist/lib/secrets/index.d.ts +15 -1
  134. package/dist/lib/secrets/index.js +21 -3
  135. package/dist/lib/secrets/rc-hygiene.d.ts +55 -0
  136. package/dist/lib/secrets/rc-hygiene.js +154 -0
  137. package/dist/lib/secrets/remote.d.ts +7 -4
  138. package/dist/lib/secrets/remote.js +7 -4
  139. package/dist/lib/secrets/sync-commands.d.ts +21 -0
  140. package/dist/lib/secrets/sync-commands.js +21 -0
  141. package/dist/lib/self-update.d.ts +23 -2
  142. package/dist/lib/self-update.js +86 -5
  143. package/dist/lib/session/active.d.ts +114 -7
  144. package/dist/lib/session/active.js +323 -52
  145. package/dist/lib/session/cloud.js +2 -1
  146. package/dist/lib/session/db.d.ts +48 -0
  147. package/dist/lib/session/db.js +238 -14
  148. package/dist/lib/session/detached.d.ts +30 -0
  149. package/dist/lib/session/detached.js +92 -0
  150. package/dist/lib/session/discover.d.ts +465 -2
  151. package/dist/lib/session/discover.js +1648 -468
  152. package/dist/lib/session/fork.js +2 -1
  153. package/dist/lib/session/hook-sessions.d.ts +43 -0
  154. package/dist/lib/session/hook-sessions.js +135 -0
  155. package/dist/lib/session/linear.d.ts +6 -0
  156. package/dist/lib/session/linear.js +55 -0
  157. package/dist/lib/session/migrate-targets.d.ts +65 -0
  158. package/dist/lib/session/migrate-targets.js +94 -0
  159. package/dist/lib/session/migrations.d.ts +37 -0
  160. package/dist/lib/session/migrations.js +60 -0
  161. package/dist/lib/session/parse.d.ts +18 -0
  162. package/dist/lib/session/parse.js +130 -30
  163. package/dist/lib/session/pid-registry.d.ts +34 -2
  164. package/dist/lib/session/pid-registry.js +49 -2
  165. package/dist/lib/session/render.d.ts +7 -2
  166. package/dist/lib/session/render.js +22 -10
  167. package/dist/lib/session/short-id.d.ts +17 -0
  168. package/dist/lib/session/short-id.js +20 -0
  169. package/dist/lib/session/state.js +6 -2
  170. package/dist/lib/shims.d.ts +1 -1
  171. package/dist/lib/shims.js +20 -6
  172. package/dist/lib/sqlite.d.ts +3 -2
  173. package/dist/lib/sqlite.js +27 -4
  174. package/dist/lib/staleness/detectors/commands.js +1 -1
  175. package/dist/lib/staleness/detectors/workflows.js +13 -2
  176. package/dist/lib/staleness/writers/commands.js +7 -7
  177. package/dist/lib/staleness/writers/workflows.d.ts +4 -2
  178. package/dist/lib/startup/command-registry.d.ts +1 -0
  179. package/dist/lib/startup/command-registry.js +3 -0
  180. package/dist/lib/state.d.ts +1 -1
  181. package/dist/lib/state.js +1 -32
  182. package/dist/lib/teams/agents.d.ts +19 -10
  183. package/dist/lib/teams/agents.js +40 -39
  184. package/dist/lib/types.d.ts +37 -2
  185. package/dist/lib/usage.d.ts +33 -1
  186. package/dist/lib/usage.js +172 -12
  187. package/dist/lib/versions.d.ts +9 -2
  188. package/dist/lib/versions.js +37 -8
  189. package/dist/lib/watchdog/log.d.ts +43 -0
  190. package/dist/lib/watchdog/log.js +69 -0
  191. package/dist/lib/watchdog/routine.d.ts +44 -0
  192. package/dist/lib/watchdog/routine.js +69 -0
  193. package/dist/lib/watchdog/runner.d.ts +51 -7
  194. package/dist/lib/watchdog/runner.js +239 -64
  195. package/dist/lib/watchdog/watchdog.d.ts +1 -1
  196. package/dist/lib/watchdog/watchdog.js +31 -16
  197. package/dist/lib/workflows.d.ts +16 -0
  198. package/dist/lib/workflows.js +110 -1
  199. 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): Promise<{
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;