cogpit-memory 0.2.1 → 0.4.0

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.
@@ -0,0 +1,425 @@
1
+ /**
2
+ * Static facts about each agent CLI: how it names project directories and
3
+ * session files, and which product capabilities it supports.
4
+ *
5
+ * This module is deliberately parser-free. It imports only `./types`, so a
6
+ * caller that just needs a dirName codec — `server/sessionPaths.ts`, the
7
+ * cogpit-memory CLI, a renderer route guard — does not drag the 40 KB Codex and
8
+ * Copilot transcript parsers along with it. The parsing side of an agent lives
9
+ * behind `AgentFormat` in `./agents`.
10
+ *
11
+ * Everything here is data or pure string math. Anything needing `node:fs` is an
12
+ * `AgentStore` and lives in `server/agents/`, because `shared/` is bundled into
13
+ * the renderer and must stay free of node builtins.
14
+ */
15
+ import { type AgentKind } from "./types";
16
+ export type { AgentKind } from "./types";
17
+ export { AGENT_KINDS } from "./types";
18
+ /**
19
+ * What a given agent can actually do. Every flag names a *capability*, never an
20
+ * agent — `capabilities.worktrees`, never `kind === "claude"`. That is the whole
21
+ * point: a fourth CLI adds one row here instead of a new arm in 40 conditionals.
22
+ */
23
+ export interface AgentCapabilities {
24
+ /** Launch a session in a worktree managed by its CLI. */
25
+ readonly worktrees: boolean;
26
+ /** MCP server configuration can be passed at spawn time. */
27
+ readonly mcp: boolean;
28
+ /** `/slash` command discovery and expansion. */
29
+ readonly slashCommands: boolean;
30
+ /** The Workflow tool writes run state Cogpit can read back. */
31
+ readonly workflows: boolean;
32
+ /** Agent teams (multi-session teammates). */
33
+ readonly agentTeams: boolean;
34
+ /** Sub-agent transcripts are written as separate readable files. */
35
+ readonly subagentTranscripts: boolean;
36
+ /**
37
+ * Whether a sub-agent can itself own sub-agents that are addressed under its
38
+ * own session id. When false, every descendant is filed under the root
39
+ * session, so listing the root's children already yields the whole tree.
40
+ */
41
+ readonly nestedSubagents: boolean;
42
+ /** Sessions can be published as read-only shares. */
43
+ readonly sharing: boolean;
44
+ /**
45
+ * How a long-running goal is tracked: read back from the transcript and set
46
+ * with a slash command, through the CLI's own goal API, or not at all.
47
+ */
48
+ readonly goals: "transcript" | "thread-api" | false;
49
+ /** Transcript-rewinding undo is available. */
50
+ readonly undo: boolean;
51
+ /** Undo history is persisted, so redo and branches work. */
52
+ readonly redo: boolean;
53
+ /** The CLI exposes its own rewind RPC (used instead of replaying edits). */
54
+ readonly nativeRewind: boolean;
55
+ /** The CLI checkpoints files itself, so undo can restore them natively. */
56
+ readonly fileCheckpoints: boolean;
57
+ /**
58
+ * A reasoning-effort ladder applies by default. When false, effort levels
59
+ * exist only for models whose live catalog entry advertises them.
60
+ */
61
+ readonly reasoningEffort: boolean;
62
+ readonly configurableContextWindow: boolean;
63
+ /** A faster/priority service tier can be requested. */
64
+ readonly fastTier: boolean;
65
+ /**
66
+ * The CLI can carry a session past a spent session allowance by continuing at
67
+ * lower priority, spending the weekly allowance to do it. The mode is reached
68
+ * only from the CLI's own terminal UI — no flag, no setting, and nothing on
69
+ * the programmatic interface Cogpit drives — so the hand-off is all Cogpit
70
+ * can offer. Never true for an agent billed per request, which has no
71
+ * allowance to run out of in the first place.
72
+ */
73
+ readonly lowPriorityInTerminal: boolean;
74
+ /**
75
+ * Images may be attached when the selected model's catalog entry says nothing
76
+ * about its input modalities. When false, vision has to be advertised.
77
+ */
78
+ readonly imageInput: boolean;
79
+ /** A message can be delivered into an already-running turn. */
80
+ readonly midTurnSteering: boolean;
81
+ /** The whole session can be shut down from the composer. */
82
+ readonly stopSession: boolean;
83
+ /** Maximum-effort orchestration mode, pinned to the top effort level. */
84
+ readonly ultracode: boolean;
85
+ /** Remaining context can be computed from the transcript. */
86
+ readonly contextWindow: boolean;
87
+ /**
88
+ * A session may be driven by a process Cogpit did not start, in which case it
89
+ * is view-only.
90
+ */
91
+ readonly externalProcesses: boolean;
92
+ /** Several logins are managed by a switcher tool Cogpit can drive. */
93
+ readonly accountSwitching: boolean;
94
+ /** The transcript records model substitutions the composer surfaces. */
95
+ readonly modelFallbackNotices: boolean;
96
+ /** Whether an unattended "auto" permission mode can be offered. */
97
+ readonly autoPermissionMode: "never" | "per-model" | "always";
98
+ readonly toolPermissionRules: boolean;
99
+ /** Whether composer settings take effect immediately or on the next turn. */
100
+ readonly settingsApply: "live" | "next-turn";
101
+ /**
102
+ * Where turn liveness is read for listings and notifications: the transcript
103
+ * tail, or the runtime's own turn state for a CLI whose transcript lags it.
104
+ */
105
+ readonly turnLiveness: "transcript" | "runtime";
106
+ /** Partial assistant output is published on the stream bus while a turn runs. */
107
+ readonly tokenStreaming: boolean;
108
+ /** Branching is a fork RPC on the CLI rather than a copied and cut transcript. */
109
+ readonly nativeFork: boolean;
110
+ /** A title can be given to a session when it is created. */
111
+ readonly namedSessions: boolean;
112
+ }
113
+ export interface AgentDirNameCodec {
114
+ /**
115
+ * True when `decode(encode(cwd))` can differ from `cwd`, so a caller holding
116
+ * the real dirName must pass it along rather than recompute it.
117
+ */
118
+ readonly lossy: boolean;
119
+ /** True when `dirName` belongs to this agent. */
120
+ owns(dirName: string | null | undefined): boolean;
121
+ /** True when `dirName` is a name `encode` produces, rather than one this agent merely owns. */
122
+ recognizes(dirName: string): boolean;
123
+ /** Encode a project path as this agent's dirName. */
124
+ encode(cwd: string): string;
125
+ /** Decode one of this agent's dirNames back to a project path. */
126
+ decode(dirName: string): string | null;
127
+ }
128
+ export interface AgentSessionFileCodec {
129
+ /** Path of a session's transcript, relative to this agent's sessions root. */
130
+ name(sessionId: string, at?: Date): string;
131
+ /** Inverse of `name`: recover the session id, or null if unrecognised. */
132
+ sessionId(fileName: string): string | null;
133
+ /**
134
+ * The identifier a URL carries for this transcript. Agents whose on-disk name
135
+ * is rebuildable from the id shorten to it; the rest keep the relative path,
136
+ * because a date-nested rollout cannot be recovered from its uuid alone.
137
+ */
138
+ urlId(fileName: string): string;
139
+ /** Exact inverse of `urlId`. */
140
+ fileNameFromUrlId(urlId: string): string;
141
+ /**
142
+ * The top-level session a transcript belongs to, so a sub-agent or workflow
143
+ * transcript answers with the session that spawned it. Takes the file's
144
+ * canonical path — symlinks resolved, `/`-separated — relative to the
145
+ * project directory where the agent has one on disk, to the sessions root
146
+ * otherwise; `AgentStore.transcriptRoot` produces exactly that. Null for a
147
+ * path that could step outside it or names no session.
148
+ */
149
+ transcriptRoot(relativePath: string): TranscriptRoot | null;
150
+ }
151
+ /** The top-level session a transcript belongs to. */
152
+ export interface TranscriptRoot {
153
+ /** Lowercased. */
154
+ rootSessionId: string;
155
+ /** False for a sub-agent's or workflow's transcript filed under that session. */
156
+ isRootTranscript: boolean;
157
+ }
158
+ export interface AgentResumeCodec {
159
+ /** Copy-pasteable shell command that reopens the session in a terminal. */
160
+ command(sessionId: string, cwd?: string): string;
161
+ /** argv handed to `binName` when Cogpit spawns the resume itself. */
162
+ args(sessionId: string): readonly string[];
163
+ }
164
+ export interface AgentHomebrewFormula {
165
+ readonly name: string;
166
+ /** Casks ship a prebuilt app and need `brew upgrade --cask`. */
167
+ readonly cask: boolean;
168
+ }
169
+ export interface AgentSelfUpdate {
170
+ /** argv appended to `binName` to make the CLI update itself. */
171
+ readonly args: readonly string[];
172
+ /**
173
+ * Whether a resolved binary path belongs to a self-managing install.
174
+ *
175
+ * Paths arrive lowercased and forward-slashed, because the caller normalises
176
+ * every candidate before any predicate runs — these are written against that
177
+ * form and comparing a raw Windows path here would never match.
178
+ */
179
+ matches(normalizedPath: string): boolean;
180
+ }
181
+ /** How a CLI shows up in the machine's process listing. */
182
+ export interface AgentProcessMatch {
183
+ /**
184
+ * `"command"`: any command line naming the binary is this CLI, launchers and
185
+ * helpers included. `"executable"`: only a process whose executable is the
186
+ * binary, for a name too common to match loosely.
187
+ */
188
+ readonly by: "command" | "executable";
189
+ /** The session a resumed process was started with, read from its command line. */
190
+ sessionIdFromCommand(command: string): string | null;
191
+ }
192
+ /**
193
+ * Everything Cogpit needs to find, version and upgrade one CLI, plus where that
194
+ * CLI keeps its own state. Cogpit drives whatever the user installed, so all of
195
+ * this is discovery, never assumption — the one exception being a CLI that
196
+ * Cogpit's own dependencies happen to ship a copy of.
197
+ */
198
+ export interface AgentCli {
199
+ /** npm package publishing this CLI. */
200
+ readonly packageName: string;
201
+ readonly homebrew: AgentHomebrewFormula | null;
202
+ readonly wingetId: string | null;
203
+ /** argv that makes the CLI print its version. */
204
+ readonly versionArgs: readonly string[];
205
+ readonly selfUpdate: AgentSelfUpdate | null;
206
+ /**
207
+ * Whether an SDK in Cogpit's dependency tree vendors its own copy of this
208
+ * CLI. That is the only case where there is more than one binary to choose
209
+ * from, so it is what enables the executable picker.
210
+ */
211
+ readonly bundledBySdk: boolean;
212
+ /** Directory holding the CLI's own state, relative to the user's home. */
213
+ readonly homeDirName: string;
214
+ /** Environment variable overriding that directory, if the CLI has one. */
215
+ readonly homeEnvVar: string | null;
216
+ readonly sdkHomeEnvVar?: string;
217
+ /**
218
+ * Path inside the home whose existence proves the CLI has been used. Empty
219
+ * when the home directory itself is the evidence.
220
+ */
221
+ readonly installMarker: string;
222
+ /**
223
+ * Whether Cogpit can locate this home on its own. Claude's is a user-chosen
224
+ * path stored in Cogpit's own configuration, so it cannot be discovered when
225
+ * that configuration is missing; the rest sit at a fixed, env-overridable
226
+ * location and can be found on a first run.
227
+ */
228
+ readonly requiresConfiguration?: boolean;
229
+ readonly homeIsDiscoverable: boolean;
230
+ readonly process: AgentProcessMatch;
231
+ }
232
+ export type AgentConfigScope = "global" | "project";
233
+ export interface AgentConfigFile {
234
+ /**
235
+ * `"scope"` places the file beside the config root — in the user's home
236
+ * directory or at the project root — and `"root"` places it inside it.
237
+ */
238
+ readonly in: "scope" | "root";
239
+ /** Path relative to whichever of those two directories `in` names. */
240
+ readonly path: string;
241
+ /** Scopes at which this file exists. */
242
+ readonly scopes: readonly AgentConfigScope[];
243
+ /** Tree label; defaults to `path`. */
244
+ readonly label?: string;
245
+ /** Read only when none of the agent's other instruction files exist at that scope. */
246
+ readonly fallback?: true;
247
+ }
248
+ /**
249
+ * Where a CLI reads its instructions, settings and extensions from. Drives the
250
+ * config browser, so an agent missing a row here is an agent the user cannot
251
+ * configure through Cogpit at all.
252
+ */
253
+ export interface AgentConfigLayout {
254
+ /** Directory the CLI reads config from, under the home or a project root. */
255
+ readonly rootDirName: string;
256
+ readonly instructions: readonly AgentConfigFile[];
257
+ readonly settings: readonly AgentConfigFile[];
258
+ /** Directories inside the config root; null when the CLI has no such concept. */
259
+ readonly skillsDir: string | null;
260
+ readonly agentsDir: string | null;
261
+ /** Codex calls these "prompts". */
262
+ readonly commandsDir: string | null;
263
+ /** Global-only theme directory, if the CLI has one. */
264
+ readonly themesDir: string | null;
265
+ /** Global-only installed-plugin tree, if the CLI has one. */
266
+ readonly pluginsDir: string | null;
267
+ /**
268
+ * Marker directory holding `plugin.json` inside a plugin the CLI loads from a
269
+ * path, and so the shape Cogpit writes its own local plugin in. Null for a
270
+ * CLI that has no plugins and reads `skillsDir` alone.
271
+ */
272
+ readonly pluginManifestDir: string | null;
273
+ }
274
+ export interface AgentContextWindow {
275
+ /** Window assumed for a model with no entry in `limits`. */
276
+ readonly defaultLimit: number;
277
+ /** Model-id substrings with a different window; first match wins. */
278
+ readonly limits: readonly {
279
+ readonly match: string;
280
+ readonly limit: number;
281
+ }[];
282
+ /**
283
+ * Headroom the CLI reserves before it auto-compacts. Zero for a CLI that
284
+ * decides its own compaction point, so applying another's reserve would
285
+ * report a session as nearly full while it still has room.
286
+ */
287
+ readonly compactBuffer: number;
288
+ /** Model-id marker requesting an extended window, if the CLI has one. */
289
+ readonly extendedContext: {
290
+ readonly marker: string;
291
+ readonly limit: number;
292
+ } | null;
293
+ }
294
+ /**
295
+ * Cogpit's access picker, as it crosses the wire. Every field is optional
296
+ * because it arrives from a client that may be older than the server; each
297
+ * agent maps it onto its own policy vocabulary.
298
+ */
299
+ export interface PermissionsConfig {
300
+ mode?: string;
301
+ allowedTools?: string[];
302
+ disallowedTools?: string[];
303
+ }
304
+ /**
305
+ * argv Cogpit appends to `binName` when it launches a turn. Each function
306
+ * returns an empty array when the setting was not chosen, so call sites spread
307
+ * them unconditionally.
308
+ */
309
+ export interface AgentLaunchArgs {
310
+ /** Translate the access picker into this CLI's own policy flags. */
311
+ permissions(permissions?: PermissionsConfig): string[];
312
+ model(model?: string): string[];
313
+ effort(effort?: string): string[];
314
+ fastTier(enabled?: boolean): string[];
315
+ }
316
+ /**
317
+ * The wire values a "fast mode" request carries.
318
+ *
319
+ * An agent Cogpit drives two ways can want two different spellings of the same
320
+ * intent, and they are recorded separately rather than merged, because only the
321
+ * CLI itself can say which is right.
322
+ */
323
+ export interface AgentServiceTier {
324
+ /** `serviceTier` sent over a persistent RPC/app-server connection. */
325
+ readonly appServerValue: string;
326
+ /** `service_tier` written into the CLI's own config when it is spawned. */
327
+ readonly cliConfigValue: string;
328
+ }
329
+ export interface EffortOption {
330
+ value: string;
331
+ label: string;
332
+ description?: string;
333
+ }
334
+ export interface ServiceTierOption {
335
+ value: string;
336
+ label: string;
337
+ description?: string;
338
+ }
339
+ /** One row of a model picker, and one entry of the `/api/models` catalog. */
340
+ export interface ModelOption {
341
+ value: string;
342
+ label: string;
343
+ description?: string;
344
+ /** Canonical wire model id this option resolves to (`""` is the default). */
345
+ resolvedModel?: string;
346
+ isDefault?: boolean;
347
+ defaultReasoningEffort?: string;
348
+ supportedReasoningEfforts?: EffortOption[];
349
+ inputModalities?: string[];
350
+ supportsPersonality?: boolean;
351
+ serviceTiers?: ServiceTierOption[];
352
+ availabilityMessage?: string;
353
+ supportsEffort?: boolean;
354
+ supportsAdaptiveThinking?: boolean;
355
+ supportsAutoMode?: boolean;
356
+ }
357
+ export interface AgentDescriptor {
358
+ readonly kind: AgentKind;
359
+ /** Human-facing product name. */
360
+ readonly displayName: string;
361
+ /** Executable name, as invoked on PATH. */
362
+ readonly binName: string;
363
+ readonly dirName: AgentDirNameCodec;
364
+ readonly sessionFile: AgentSessionFileCodec;
365
+ /** Folder inside a project where the CLI cuts its worktrees; null when they live elsewhere. */
366
+ readonly projectWorktreeDir: string | null;
367
+ /**
368
+ * Whether header metadata is complete after reading only the first bytes of a
369
+ * transcript. False when an exact line count or the last user message matters
370
+ * and nothing short of a full read can produce them, which is what makes a
371
+ * partial head read a false economy for that agent.
372
+ */
373
+ readonly metadataFromHead: boolean;
374
+ readonly resume: AgentResumeCodec;
375
+ readonly launchArgs: AgentLaunchArgs;
376
+ /** Null for an agent with no faster tier, or one that asks for it some other way. */
377
+ readonly serviceTier: AgentServiceTier | null;
378
+ readonly cli: AgentCli;
379
+ readonly config: AgentConfigLayout;
380
+ readonly contextWindow: AgentContextWindow;
381
+ readonly capabilities: AgentCapabilities;
382
+ }
383
+ /** True when a process listing's command line belongs to this CLI. */
384
+ export declare function matchesAgentProcess(descriptor: AgentDescriptor, command: string): boolean;
385
+ /** The `Win32_Process` name filter that lists this CLI, for `Get-CimInstance`. */
386
+ export declare function windowsProcessNameFilter(descriptor: AgentDescriptor): string;
387
+ export declare const CODEX_DIR_PREFIX = "codex__";
388
+ export declare const COPILOT_DIR_PREFIX = "copilot__";
389
+ export declare function descriptorFor(kind: AgentKind): AgentDescriptor;
390
+ export declare function allDescriptors(): readonly AgentDescriptor[];
391
+ /**
392
+ * The one agent with a given property, for code that only makes sense while
393
+ * exactly one has it — the configured home, the slash-command palette. Fails
394
+ * loudly on zero or several rather than silently picking the first, so a
395
+ * capability a second agent gains is noticed instead of misrouted.
396
+ */
397
+ export declare function soleDescriptorWhere(predicate: (descriptor: AgentDescriptor) => boolean, what: string): AgentDescriptor;
398
+ /**
399
+ * Resolve the agent that owns a project dirName.
400
+ *
401
+ * This is Cogpit's **source of truth** for "which agent is this session?". It
402
+ * beats content sniffing because it is known before a single byte is read, it
403
+ * is what the URL carries, and it is what every write path (spawn, undo,
404
+ * branch) already keys on. `ParsedSession.agentKind` is the parser's own
405
+ * finding and is used to cross-check, never to override.
406
+ *
407
+ * Falls back to Claude for a null/unknown dirName, matching the historical
408
+ * behaviour every caller depends on.
409
+ */
410
+ export declare function descriptorForDirName(dirName: string | null | undefined): AgentDescriptor;
411
+ /** Convenience wrapper: the agent kind owning a project dirName. */
412
+ export declare function agentKindForDirName(dirName: string | null | undefined): AgentKind;
413
+ /**
414
+ * Encode a project path for a given agent. An agent whose encoding is lossy
415
+ * cannot recover its canonical dirName from the path, so a caller that already
416
+ * holds it passes it as `knownDirName` rather than let it be re-derived.
417
+ */
418
+ export declare function projectDirNameFor(kind: AgentKind, cwd: string, knownDirName?: string): string;
419
+ /** True when `fileName` looks like a session transcript for `kind`. */
420
+ export declare function sessionIdFromFileName(kind: AgentKind, fileName: string): string | null;
421
+ /** True when `value` is a bare session UUID. */
422
+ export declare function isSessionUuid(value: string): boolean;
423
+ /** Canonical access identity, preserving the instance token's case. */
424
+ export declare function canonicalSessionId(value: string): string | null;
425
+ export declare const PERSISTED_AGENT_HOME_FIELD = "claudeDir";
@@ -0,0 +1,123 @@
1
+ import { type CodexExecCall } from "./codex-exec";
2
+ import { type AgentKind } from "./types";
3
+ import type { ParseSessionOptions, ParsedSession, RawRecord, SessionStatusInfo } from "./types";
4
+ import type { AgentDescriptor } from "./agent-descriptors";
5
+ export type { AgentCapabilities, AgentDescriptor, AgentDirNameCodec, AgentKind, AgentSessionFileCodec, } from "./agent-descriptors";
6
+ export { AGENT_KINDS, CODEX_DIR_PREFIX, COPILOT_DIR_PREFIX, agentKindForDirName, allDescriptors, descriptorFor, descriptorForDirName, isSessionUuid, projectDirNameFor, sessionIdFromFileName, } from "./agent-descriptors";
7
+ /**
8
+ * Header metadata every format can report from the first lines of a transcript.
9
+ *
10
+ * Individual formats return supersets of this (turn counts, first/last user
11
+ * message, sub-agent parentage); the registry narrows them to the fields all
12
+ * three agree on.
13
+ */
14
+ export interface AgentSessionMetadata {
15
+ sessionId: string;
16
+ version: string;
17
+ gitBranch: string;
18
+ cwd: string;
19
+ slug: string;
20
+ name: string;
21
+ model: string;
22
+ branchedFrom?: {
23
+ sessionId: string;
24
+ turnIndex?: number | null;
25
+ };
26
+ }
27
+ export interface AgentFormat {
28
+ readonly kind: AgentKind;
29
+ readonly descriptor: AgentDescriptor;
30
+ /** True when this JSONL text was produced by this CLI. */
31
+ detectsText(jsonlText: string): boolean;
32
+ /** True when these already-parsed raw records came from this CLI. */
33
+ detectsRecords(records: readonly {
34
+ type?: unknown;
35
+ }[]): boolean;
36
+ /** Full parse into the shared session model. */
37
+ parse(jsonlText: string, options?: ParseSessionOptions): ParsedSession;
38
+ /** Incremental append of newly written lines. */
39
+ append(existing: ParsedSession, newJsonlText: string): ParsedSession;
40
+ /** Cheap header metadata read from already-split transcript lines. */
41
+ metadataFromLines(lines: readonly string[]): AgentSessionMetadata;
42
+ /** Status derived from raw records. */
43
+ status(records: readonly RawRecord[]): SessionStatusInfo;
44
+ /**
45
+ * The first record of a transcript, rewritten for a branch cut at
46
+ * `branchedAt`: it now names `sessionId` and records the session and turn
47
+ * it was cut from, and when, where the copied lines cannot otherwise be
48
+ * told from the branch's own. Returns the id the record carried before,
49
+ * which is what the branch reports as its origin.
50
+ */
51
+ brandBranch(firstRecord: Record<string, unknown>, sessionId: string, turnIndex: number | null, branchedAt: number): {
52
+ record: Record<string, unknown>;
53
+ originalId: string;
54
+ };
55
+ /**
56
+ * Indexes of the records at which a turn starts, in file order.
57
+ *
58
+ * The **one** implementation of turn-boundary detection. Undo cuts a
59
+ * transcript by computing `keepLines` on the client and having the server
60
+ * verify and apply it; if the two sides scanned for boundaries separately
61
+ * and ever disagreed by one line, the result would be a silently corrupted
62
+ * transcript rather than an error. They cannot disagree while they both call
63
+ * this.
64
+ */
65
+ turnBoundaries(records: readonly Record<string, unknown>[]): number[];
66
+ }
67
+ /**
68
+ * The kind that claims any transcript nothing else did. Detection order makes
69
+ * it the last entry of `AGENT_KINDS`; callers that need to know whether
70
+ * detection actually matched compare against this.
71
+ */
72
+ export declare const TERMINAL_AGENT_KIND: AgentKind;
73
+ export declare function formatFor(kind: AgentKind): AgentFormat;
74
+ export declare function formatForText(jsonlText: string): AgentFormat;
75
+ export declare function formatForRecords(records: readonly {
76
+ type?: unknown;
77
+ }[]): AgentFormat;
78
+ /**
79
+ * Boundary indexes for raw JSONL lines rather than parsed records.
80
+ *
81
+ * A malformed line stands in as an empty record so indexes keep matching the
82
+ * caller's line array — every consumer here slices that array by the returned
83
+ * index, so dropping a line would shift the cut.
84
+ */
85
+ export declare function turnBoundaryLines(format: AgentFormat, lines: readonly string[]): number[];
86
+ /**
87
+ * The line to cut at to keep exactly `keepTurnCount` turns — i.e. where the
88
+ * turn after the last kept one begins. `lines.length` when the transcript has
89
+ * no more turns than that, meaning nothing is removed.
90
+ */
91
+ export declare function cutLineForTurnCount(format: AgentFormat, lines: readonly string[], keepTurnCount: number): number;
92
+ /**
93
+ * The line to cut at to keep everything through turn `turnIndex`, or null when
94
+ * that turn is the last one and there is nothing to remove.
95
+ */
96
+ export declare function cutLineAfterTurnIndex(format: AgentFormat, lines: readonly string[], turnIndex: number): number | null;
97
+ /**
98
+ * Each turn's first line paired with the id the parser gives that turn, in file
99
+ * order. Null when the two do not pair up, which would mean the ids cannot be
100
+ * trusted to name these lines.
101
+ *
102
+ * This is what lets a caller holding only the loaded tail of a transcript name
103
+ * a turn: its window index says nothing about file order, but its id does.
104
+ */
105
+ export declare function turnStartsWithIds(format: AgentFormat, lines: readonly string[]): {
106
+ line: number;
107
+ id: string;
108
+ }[] | null;
109
+ /**
110
+ * The line to cut at to keep everything through the turn whose id is `turnId`.
111
+ *
112
+ * `"keep-all"` means it is the last turn (nothing to remove); null means no
113
+ * turn has that id — an agent may leave a turn unlabelled — and the caller
114
+ * should fall back to an index-based cut.
115
+ */
116
+ export declare function cutLineAfterTurnId(format: AgentFormat, lines: readonly string[], turnId: string): number | "keep-all" | null;
117
+ /**
118
+ * The tool calls a recorded call actually ran. Every format records one call
119
+ * per tool use except Codex code mode, which persists a whole script of them as
120
+ * a single `exec` call, so readers that care about what ran — rather than how
121
+ * it is presented — expand a call here instead of learning that grammar.
122
+ */
123
+ export declare function expandToolScript<T extends CodexExecCall>(call: T): CodexExecCall[];
@@ -0,0 +1,49 @@
1
+ import type { ParseSessionOptions, ParsedSession, RawRecord, SessionStatusInfo } from "./types";
2
+ /**
3
+ * Unconditionally true: this is the terminal arm of transcript detection.
4
+ *
5
+ * Detection ORDER is the contract. `AGENT_KINDS` ends with this format, so a
6
+ * transcript only reaches it after every positively-discriminated format has
7
+ * declined it. Spelling the fallback out as "not the others" instead would make
8
+ * each new CLI edit this function and re-introduce the mutual references the
9
+ * registry exists to remove.
10
+ */
11
+ export declare function isClaudeSessionText(): boolean;
12
+ export interface ClaudeMetadata {
13
+ sessionId: string;
14
+ version: string;
15
+ gitBranch: string;
16
+ cwd: string;
17
+ slug: string;
18
+ name: string;
19
+ model: string;
20
+ branchedFrom?: {
21
+ sessionId: string;
22
+ turnIndex?: number | null;
23
+ };
24
+ }
25
+ /** Cheap header metadata read from already-split transcript lines. */
26
+ export declare function extractClaudeMetadataFromLines(lines: readonly string[]): ClaudeMetadata;
27
+ export declare function parseClaudeSession(jsonlText: string, options?: ParseSessionOptions): ParsedSession;
28
+ /**
29
+ * Incrementally append new JSONL lines to an existing parsed session.
30
+ * Avoids re-parsing all turns from scratch — only re-processes the last
31
+ * (potentially incomplete) turn and any new messages.
32
+ */
33
+ export declare function appendClaudeSession(existing: ParsedSession, newJsonlText: string): ParsedSession;
34
+ /** Derive status by walking backward through Claude Code's JSONL records. */
35
+ export declare function deriveClaudeSessionStatus(rawMessages: readonly RawRecord[]): SessionStatusInfo;
36
+ /** A branch keeps the header record, renamed and pointed back at its origin. */
37
+ export declare function brandClaudeBranch(firstRecord: Record<string, unknown>, sessionId: string, turnIndex: number | null): {
38
+ record: Record<string, unknown>;
39
+ originalId: string;
40
+ };
41
+ /**
42
+ * Indexes of the records that start a turn.
43
+ *
44
+ * A user record that carries *any* `tool_result` block is a continuation of the
45
+ * turn in flight, not a new one — the CLI writes tool results back as user
46
+ * records. This predicate is the one the client and the server both cut on, so
47
+ * an off-by-one here writes a corrupted transcript rather than failing loudly.
48
+ */
49
+ export declare function claudeTurnBoundaries(records: readonly Record<string, unknown>[]): number[];
@@ -7,6 +7,10 @@ export interface CodexExecInvocation {
7
7
  argumentSource: string;
8
8
  startIndex: number;
9
9
  }
10
+ export interface CodexExecCall {
11
+ name: string;
12
+ input: Record<string, unknown>;
13
+ }
10
14
  export declare function readJsStringLiteral(source: string, startIndex: number): JsStringLiteral | null;
11
15
  /**
12
16
  * Extract actual tool invocations from a Codex code-mode `exec` script.
@@ -25,3 +29,11 @@ export declare function extractJsStringPropertyValues(source: string, property:
25
29
  export declare function extractJsNumberPropertyValues(source: string, property: string): number[];
26
30
  /** Count top-level entries in an array literal, or one for a non-empty value. */
27
31
  export declare function countJsCollectionEntries(source: string): number;
32
+ /** Codex code mode wraps a whole orchestration script in one `exec` tool call. */
33
+ export declare function isCodexExecCall<T extends CodexExecCall>(call: T): call is T & {
34
+ input: {
35
+ raw: string;
36
+ };
37
+ };
38
+ /** Decode literal call inputs for presentation; expressions remain available as source. */
39
+ export declare function getCodexExecCalls(input: Record<string, unknown>): CodexExecCall[];
@@ -1,14 +1,35 @@
1
+ import type { ImageBlock } from "./types";
1
2
  /**
2
3
  * Normalize a Codex function name to its canonical collaboration-tool name.
3
4
  * Current collaboration tools carry a namespace separately, but older and
4
5
  * transitional rollouts also encoded it in the function name itself.
5
6
  */
6
7
  export declare function normalizeFunctionName(rawName: string): string;
7
- export declare function inferToolError(output: string | null): boolean;
8
+ /**
9
+ * Read the exit code Codex's shell wrapper prints into its own output. Nothing
10
+ * else in the text decides failure: a command that prints source files, HTML or
11
+ * logs carries words like "error" that say nothing about how it ended.
12
+ */
13
+ export declare function hasFailedExit(output: string | null): boolean;
14
+ /**
15
+ * Names Codex gives the tool that asks the user a question, both the blocking
16
+ * form and the `_async` one that returns a receipt and keeps working.
17
+ */
18
+ export declare function isCodexQuestionTool(name: string): boolean;
19
+ /**
20
+ * Normalize Codex request_user_input input to AskUserQuestion format.
21
+ *
22
+ * Codex writes a question as `{ title, options: string[] | null }`, where the
23
+ * options are bare labels rather than the `{ label, description }` objects the
24
+ * question card renders.
25
+ */
26
+ export declare function normalizeQuestions(input: Record<string, unknown>): Record<string, unknown>;
8
27
  /** Normalize Codex update_plan input to TodoWrite format. */
9
28
  export declare function normalizePlanToTodos(input: Record<string, unknown>): Record<string, unknown>;
10
- /** Parse Codex custom_tool_call output JSON or structured content blocks. */
11
- export declare function parseCustomToolOutput(output: unknown): {
29
+ export interface CodexToolOutput {
12
30
  text: string;
13
31
  isError: boolean;
14
- };
32
+ images?: ImageBlock[];
33
+ }
34
+ /** Read function, custom-tool, and MCP output without losing structured images. */
35
+ export declare function parseCustomToolOutput(output: unknown): CodexToolOutput;