cogpit-memory 0.2.1 → 0.3.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,387 @@
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
+ /** Git worktrees, which Cogpit stores under the Claude projects tree. */
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
+ /** A faster/priority service tier can be requested. */
63
+ readonly fastTier: boolean;
64
+ /**
65
+ * Images may be attached when the selected model's catalog entry says nothing
66
+ * about its input modalities. When false, vision has to be advertised.
67
+ */
68
+ readonly imageInput: boolean;
69
+ /** A message can be delivered into an already-running turn. */
70
+ readonly midTurnSteering: boolean;
71
+ /** The whole session can be shut down from the composer. */
72
+ readonly stopSession: boolean;
73
+ /** Maximum-effort orchestration mode, pinned to the top effort level. */
74
+ readonly ultracode: boolean;
75
+ /** Remaining context can be computed from the transcript. */
76
+ readonly contextWindow: boolean;
77
+ /**
78
+ * A session may be driven by a process Cogpit did not start, in which case it
79
+ * is view-only.
80
+ */
81
+ readonly externalProcesses: boolean;
82
+ /** Several logins are managed by a switcher tool Cogpit can drive. */
83
+ readonly accountSwitching: boolean;
84
+ /** The transcript records model substitutions the composer surfaces. */
85
+ readonly modelFallbackNotices: boolean;
86
+ /** Whether an unattended "auto" permission mode can be offered. */
87
+ readonly autoPermissionMode: "never" | "per-model" | "always";
88
+ /** Whether composer settings take effect immediately or on the next turn. */
89
+ readonly settingsApply: "live" | "next-turn";
90
+ /**
91
+ * Where turn liveness is read for listings and notifications: the transcript
92
+ * tail, or the runtime's own turn state for a CLI whose transcript lags it.
93
+ */
94
+ readonly turnLiveness: "transcript" | "runtime";
95
+ /** Partial assistant output is published on the stream bus while a turn runs. */
96
+ readonly tokenStreaming: boolean;
97
+ /** Branching is a fork RPC on the CLI rather than a copied and cut transcript. */
98
+ readonly nativeFork: boolean;
99
+ /** A title can be given to a session when it is created. */
100
+ readonly namedSessions: boolean;
101
+ }
102
+ export interface AgentDirNameCodec {
103
+ /**
104
+ * True when `decode(encode(cwd))` can differ from `cwd`, so a caller holding
105
+ * the real dirName must pass it along rather than recompute it.
106
+ */
107
+ readonly lossy: boolean;
108
+ /** True when `dirName` belongs to this agent. */
109
+ owns(dirName: string | null | undefined): boolean;
110
+ /** Encode a project path as this agent's dirName. */
111
+ encode(cwd: string): string;
112
+ /** Decode one of this agent's dirNames back to a project path. */
113
+ decode(dirName: string): string | null;
114
+ }
115
+ export interface AgentSessionFileCodec {
116
+ /** Path of a session's transcript, relative to this agent's sessions root. */
117
+ name(sessionId: string, at?: Date): string;
118
+ /** Inverse of `name`: recover the session id, or null if unrecognised. */
119
+ sessionId(fileName: string): string | null;
120
+ /**
121
+ * The identifier a URL carries for this transcript. Agents whose on-disk name
122
+ * is rebuildable from the id shorten to it; the rest keep the relative path,
123
+ * because a date-nested rollout cannot be recovered from its uuid alone.
124
+ */
125
+ urlId(fileName: string): string;
126
+ /** Exact inverse of `urlId`. */
127
+ fileNameFromUrlId(urlId: string): string;
128
+ }
129
+ export interface AgentResumeCodec {
130
+ /** Copy-pasteable shell command that reopens the session in a terminal. */
131
+ command(sessionId: string, cwd?: string): string;
132
+ /** argv handed to `binName` when Cogpit spawns the resume itself. */
133
+ args(sessionId: string): readonly string[];
134
+ }
135
+ export interface AgentHomebrewFormula {
136
+ readonly name: string;
137
+ /** Casks ship a prebuilt app and need `brew upgrade --cask`. */
138
+ readonly cask: boolean;
139
+ }
140
+ export interface AgentSelfUpdate {
141
+ /** argv appended to `binName` to make the CLI update itself. */
142
+ readonly args: readonly string[];
143
+ /**
144
+ * Whether a resolved binary path belongs to a self-managing install.
145
+ *
146
+ * Paths arrive lowercased and forward-slashed, because the caller normalises
147
+ * every candidate before any predicate runs — these are written against that
148
+ * form and comparing a raw Windows path here would never match.
149
+ */
150
+ matches(normalizedPath: string): boolean;
151
+ }
152
+ /** How a CLI shows up in the machine's process listing. */
153
+ export interface AgentProcessMatch {
154
+ /**
155
+ * `"command"`: any command line naming the binary is this CLI, launchers and
156
+ * helpers included. `"executable"`: only a process whose executable is the
157
+ * binary, for a name too common to match loosely.
158
+ */
159
+ readonly by: "command" | "executable";
160
+ /** The session a resumed process was started with, read from its command line. */
161
+ sessionIdFromCommand(command: string): string | null;
162
+ }
163
+ /**
164
+ * Everything Cogpit needs to find, version and upgrade one CLI, plus where that
165
+ * CLI keeps its own state. Cogpit drives whatever the user installed, so all of
166
+ * this is discovery, never assumption — the one exception being a CLI that
167
+ * Cogpit's own dependencies happen to ship a copy of.
168
+ */
169
+ export interface AgentCli {
170
+ /** npm package publishing this CLI. */
171
+ readonly packageName: string;
172
+ readonly homebrew: AgentHomebrewFormula | null;
173
+ readonly wingetId: string | null;
174
+ /** argv that makes the CLI print its version. */
175
+ readonly versionArgs: readonly string[];
176
+ readonly selfUpdate: AgentSelfUpdate | null;
177
+ /**
178
+ * Whether an SDK in Cogpit's dependency tree vendors its own copy of this
179
+ * CLI. That is the only case where there is more than one binary to choose
180
+ * from, so it is what enables the executable picker.
181
+ */
182
+ readonly bundledBySdk: boolean;
183
+ /** Directory holding the CLI's own state, relative to the user's home. */
184
+ readonly homeDirName: string;
185
+ /** Environment variable overriding that directory, if the CLI has one. */
186
+ readonly homeEnvVar: string | null;
187
+ /**
188
+ * Path inside the home whose existence proves the CLI has been used. Empty
189
+ * when the home directory itself is the evidence.
190
+ */
191
+ readonly installMarker: string;
192
+ /**
193
+ * Whether Cogpit can locate this home on its own. Claude's is a user-chosen
194
+ * path stored in Cogpit's own configuration, so it cannot be discovered when
195
+ * that configuration is missing; the rest sit at a fixed, env-overridable
196
+ * location and can be found on a first run.
197
+ */
198
+ readonly homeIsDiscoverable: boolean;
199
+ readonly process: AgentProcessMatch;
200
+ }
201
+ export type AgentConfigScope = "global" | "project";
202
+ export interface AgentConfigFile {
203
+ /**
204
+ * `"scope"` places the file beside the config root — in the user's home
205
+ * directory or at the project root — and `"root"` places it inside it.
206
+ */
207
+ readonly in: "scope" | "root";
208
+ /** Path relative to whichever of those two directories `in` names. */
209
+ readonly path: string;
210
+ /** Scopes at which this file exists. */
211
+ readonly scopes: readonly AgentConfigScope[];
212
+ /** Tree label; defaults to `path`. */
213
+ readonly label?: string;
214
+ }
215
+ /**
216
+ * Where a CLI reads its instructions, settings and extensions from. Drives the
217
+ * config browser, so an agent missing a row here is an agent the user cannot
218
+ * configure through Cogpit at all.
219
+ */
220
+ export interface AgentConfigLayout {
221
+ /** Directory the CLI reads config from, under the home or a project root. */
222
+ readonly rootDirName: string;
223
+ readonly instructions: readonly AgentConfigFile[];
224
+ readonly settings: readonly AgentConfigFile[];
225
+ /** Directories inside the config root; null when the CLI has no such concept. */
226
+ readonly skillsDir: string | null;
227
+ readonly agentsDir: string | null;
228
+ /** Codex calls these "prompts". */
229
+ readonly commandsDir: string | null;
230
+ /** Global-only theme directory, if the CLI has one. */
231
+ readonly themesDir: string | null;
232
+ /** Global-only installed-plugin tree, if the CLI has one. */
233
+ readonly pluginsDir: string | null;
234
+ /**
235
+ * Marker directory holding `plugin.json` inside a plugin the CLI loads from a
236
+ * path, and so the shape Cogpit writes its own local plugin in. Null for a
237
+ * CLI that has no plugins and reads `skillsDir` alone.
238
+ */
239
+ readonly pluginManifestDir: string | null;
240
+ }
241
+ export interface AgentContextWindow {
242
+ /** Window assumed for a model with no entry in `limits`. */
243
+ readonly defaultLimit: number;
244
+ /** Model-id substrings with a different window; first match wins. */
245
+ readonly limits: readonly {
246
+ readonly match: string;
247
+ readonly limit: number;
248
+ }[];
249
+ /**
250
+ * Headroom the CLI reserves before it auto-compacts. Zero for a CLI that
251
+ * decides its own compaction point, so applying another's reserve would
252
+ * report a session as nearly full while it still has room.
253
+ */
254
+ readonly compactBuffer: number;
255
+ /** Model-id marker requesting an extended window, if the CLI has one. */
256
+ readonly extendedContext: {
257
+ readonly marker: string;
258
+ readonly limit: number;
259
+ } | null;
260
+ }
261
+ /**
262
+ * Cogpit's access picker, as it crosses the wire. Every field is optional
263
+ * because it arrives from a client that may be older than the server; each
264
+ * agent maps it onto its own policy vocabulary.
265
+ */
266
+ export interface PermissionsConfig {
267
+ mode?: string;
268
+ allowedTools?: string[];
269
+ disallowedTools?: string[];
270
+ }
271
+ /**
272
+ * argv Cogpit appends to `binName` when it launches a turn. Each function
273
+ * returns an empty array when the setting was not chosen, so call sites spread
274
+ * them unconditionally.
275
+ */
276
+ export interface AgentLaunchArgs {
277
+ /** Translate the access picker into this CLI's own policy flags. */
278
+ permissions(permissions?: PermissionsConfig): string[];
279
+ model(model?: string): string[];
280
+ effort(effort?: string): string[];
281
+ fastTier(enabled?: boolean): string[];
282
+ }
283
+ /**
284
+ * The wire values a "fast mode" request carries.
285
+ *
286
+ * An agent Cogpit drives two ways can want two different spellings of the same
287
+ * intent, and they are recorded separately rather than merged, because only the
288
+ * CLI itself can say which is right.
289
+ */
290
+ export interface AgentServiceTier {
291
+ /** `serviceTier` sent over a persistent RPC/app-server connection. */
292
+ readonly appServerValue: string;
293
+ /** `service_tier` written into the CLI's own config when it is spawned. */
294
+ readonly cliConfigValue: string;
295
+ }
296
+ export interface EffortOption {
297
+ value: string;
298
+ label: string;
299
+ description?: string;
300
+ }
301
+ export interface ServiceTierOption {
302
+ value: string;
303
+ label: string;
304
+ description?: string;
305
+ }
306
+ /** One row of a model picker, and one entry of the `/api/models` catalog. */
307
+ export interface ModelOption {
308
+ value: string;
309
+ label: string;
310
+ description?: string;
311
+ /** Canonical wire model id this option resolves to (`""` is the default). */
312
+ resolvedModel?: string;
313
+ isDefault?: boolean;
314
+ defaultReasoningEffort?: string;
315
+ supportedReasoningEfforts?: EffortOption[];
316
+ inputModalities?: string[];
317
+ supportsPersonality?: boolean;
318
+ serviceTiers?: ServiceTierOption[];
319
+ availabilityMessage?: string;
320
+ supportsEffort?: boolean;
321
+ supportsAdaptiveThinking?: boolean;
322
+ supportsAutoMode?: boolean;
323
+ }
324
+ export interface AgentDescriptor {
325
+ readonly kind: AgentKind;
326
+ /** Human-facing product name. */
327
+ readonly displayName: string;
328
+ /** Executable name, as invoked on PATH. */
329
+ readonly binName: string;
330
+ readonly dirName: AgentDirNameCodec;
331
+ readonly sessionFile: AgentSessionFileCodec;
332
+ /**
333
+ * Whether header metadata is complete after reading only the first bytes of a
334
+ * transcript. False when an exact line count or the last user message matters
335
+ * and nothing short of a full read can produce them, which is what makes a
336
+ * partial head read a false economy for that agent.
337
+ */
338
+ readonly metadataFromHead: boolean;
339
+ readonly resume: AgentResumeCodec;
340
+ readonly launchArgs: AgentLaunchArgs;
341
+ /** Null for an agent with no faster tier, or one that asks for it some other way. */
342
+ readonly serviceTier: AgentServiceTier | null;
343
+ readonly cli: AgentCli;
344
+ readonly config: AgentConfigLayout;
345
+ readonly contextWindow: AgentContextWindow;
346
+ readonly capabilities: AgentCapabilities;
347
+ }
348
+ /** True when a process listing's command line belongs to this CLI. */
349
+ export declare function matchesAgentProcess(descriptor: AgentDescriptor, command: string): boolean;
350
+ /** The `Win32_Process` name filter that lists this CLI, for `Get-CimInstance`. */
351
+ export declare function windowsProcessNameFilter(descriptor: AgentDescriptor): string;
352
+ export declare const CODEX_DIR_PREFIX = "codex__";
353
+ export declare const COPILOT_DIR_PREFIX = "copilot__";
354
+ export declare function descriptorFor(kind: AgentKind): AgentDescriptor;
355
+ export declare function allDescriptors(): readonly AgentDescriptor[];
356
+ /**
357
+ * The one agent with a given property, for code that only makes sense while
358
+ * exactly one has it — the configured home, the slash-command palette. Fails
359
+ * loudly on zero or several rather than silently picking the first, so a
360
+ * capability a second agent gains is noticed instead of misrouted.
361
+ */
362
+ export declare function soleDescriptorWhere(predicate: (descriptor: AgentDescriptor) => boolean, what: string): AgentDescriptor;
363
+ /**
364
+ * Resolve the agent that owns a project dirName.
365
+ *
366
+ * This is Cogpit's **source of truth** for "which agent is this session?". It
367
+ * beats content sniffing because it is known before a single byte is read, it
368
+ * is what the URL carries, and it is what every write path (spawn, undo,
369
+ * branch) already keys on. `ParsedSession.agentKind` is the parser's own
370
+ * finding and is used to cross-check, never to override.
371
+ *
372
+ * Falls back to Claude for a null/unknown dirName, matching the historical
373
+ * behaviour every caller depends on.
374
+ */
375
+ export declare function descriptorForDirName(dirName: string | null | undefined): AgentDescriptor;
376
+ /** Convenience wrapper: the agent kind owning a project dirName. */
377
+ export declare function agentKindForDirName(dirName: string | null | undefined): AgentKind;
378
+ /**
379
+ * Encode a project path for a given agent. An agent whose encoding is lossy
380
+ * cannot recover its canonical dirName from the path, so a caller that already
381
+ * holds it passes it as `knownDirName` rather than let it be re-derived.
382
+ */
383
+ export declare function projectDirNameFor(kind: AgentKind, cwd: string, knownDirName?: string): string;
384
+ /** True when `fileName` looks like a session transcript for `kind`. */
385
+ export declare function sessionIdFromFileName(kind: AgentKind, fileName: string): string | null;
386
+ /** True when `value` is a bare session UUID. */
387
+ export declare function isSessionUuid(value: string): boolean;
@@ -0,0 +1,112 @@
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: it now names
46
+ * `sessionId` and records the session and turn it was cut from. Returns the
47
+ * id the record carried before, which is what the branch reports as its
48
+ * origin.
49
+ */
50
+ brandBranch(firstRecord: Record<string, unknown>, sessionId: string, turnIndex: number | null): {
51
+ record: Record<string, unknown>;
52
+ originalId: string;
53
+ };
54
+ /**
55
+ * Indexes of the records at which a turn starts, in file order.
56
+ *
57
+ * The **one** implementation of turn-boundary detection. Undo cuts a
58
+ * transcript by computing `keepLines` on the client and having the server
59
+ * verify and apply it; if the two sides scanned for boundaries separately
60
+ * and ever disagreed by one line, the result would be a silently corrupted
61
+ * transcript rather than an error. They cannot disagree while they both call
62
+ * this.
63
+ */
64
+ turnBoundaries(records: readonly Record<string, unknown>[]): number[];
65
+ }
66
+ /**
67
+ * The kind that claims any transcript nothing else did. Detection order makes
68
+ * it the last entry of `AGENT_KINDS`; callers that need to know whether
69
+ * detection actually matched compare against this.
70
+ */
71
+ export declare const TERMINAL_AGENT_KIND: AgentKind;
72
+ export declare function formatFor(kind: AgentKind): AgentFormat;
73
+ export declare function formatForText(jsonlText: string): AgentFormat;
74
+ export declare function formatForRecords(records: readonly {
75
+ type?: unknown;
76
+ }[]): AgentFormat;
77
+ /**
78
+ * Boundary indexes for raw JSONL lines rather than parsed records.
79
+ *
80
+ * A malformed line stands in as an empty record so indexes keep matching the
81
+ * caller's line array — every consumer here slices that array by the returned
82
+ * index, so dropping a line would shift the cut.
83
+ */
84
+ export declare function turnBoundaryLines(format: AgentFormat, lines: readonly string[]): number[];
85
+ /**
86
+ * The line to cut at to keep exactly `keepTurnCount` turns — i.e. where the
87
+ * turn after the last kept one begins. `lines.length` when the transcript has
88
+ * no more turns than that, meaning nothing is removed.
89
+ */
90
+ export declare function cutLineForTurnCount(format: AgentFormat, lines: readonly string[], keepTurnCount: number): number;
91
+ /**
92
+ * The line to cut at to keep everything through turn `turnIndex`, or null when
93
+ * that turn is the last one and there is nothing to remove.
94
+ */
95
+ export declare function cutLineAfterTurnIndex(format: AgentFormat, lines: readonly string[], turnIndex: number): number | null;
96
+ /**
97
+ * The line to cut at to keep everything through the turn containing the record
98
+ * whose `uuid` is `turnUuid`. Correct even when the caller has only a tail of
99
+ * the transcript loaded, so its turn indexes do not match file order.
100
+ *
101
+ * `"keep-all"` means the uuid was found in the last turn (nothing to remove);
102
+ * null means it was not found at all, and the caller should fall back to an
103
+ * index-based cut.
104
+ */
105
+ export declare function cutLineAfterUuid(format: AgentFormat, lines: readonly string[], turnUuid: string): number | "keep-all" | null;
106
+ /**
107
+ * The tool calls a recorded call actually ran. Every format records one call
108
+ * per tool use except Codex code mode, which persists a whole script of them as
109
+ * a single `exec` call, so readers that care about what ran — rather than how
110
+ * it is presented — expand a call here instead of learning that grammar.
111
+ */
112
+ 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;
@@ -1,4 +1,4 @@
1
- import type { ParseSessionOptions, ParsedSession } from "./types";
1
+ import type { ParseSessionOptions, ParsedSession, RawRecord, SessionStatusInfo } from "./types";
2
2
  export { extractApplyPatchInputs, findFailedNestedPatchCallIds, parseApplyPatch, parseCodexToolPatches, } from "./codex-patches";
3
3
  export { parseCustomToolOutput } from "./codex-tool-normalization";
4
4
  interface CodexMetadata {
@@ -26,5 +26,33 @@ interface CodexMetadata {
26
26
  agentPath: string;
27
27
  }
28
28
  export declare function isCodexSessionText(jsonlText: string): boolean;
29
+ /** True when already-parsed raw records came from this CLI. */
30
+ export declare function isCodexRawRecords(records: readonly {
31
+ type?: unknown;
32
+ }[]): boolean;
29
33
  export declare function extractCodexMetadataFromLines(lines: string[]): CodexMetadata;
30
34
  export declare function parseCodexSession(jsonlText: string, options?: ParseSessionOptions): ParsedSession;
35
+ /**
36
+ * Incrementally extend a parsed session with newly written lines.
37
+ *
38
+ * Rollout records carry cross-turn state (patch folding, collaboration agent
39
+ * lifecycles, token roll-ups), so the whole transcript is re-parsed rather than
40
+ * only its tail.
41
+ */
42
+ export declare function appendCodexSession(existing: ParsedSession, newJsonlText: string): ParsedSession;
43
+ /** Derive status by walking backward through a rollout's records. */
44
+ export declare function deriveCodexSessionStatus(rawMessages: readonly RawRecord[]): SessionStatusInfo;
45
+ /** A branch keeps the `session_meta` record, renamed and pointed back at its origin. */
46
+ export declare function brandCodexBranch(firstRecord: Record<string, unknown>, sessionId: string, turnIndex: number | null): {
47
+ record: Record<string, unknown>;
48
+ originalId: string;
49
+ };
50
+ /**
51
+ * Indexes at which a turn starts.
52
+ *
53
+ * A rollout writes the `turn_context` record that configures a turn *before*
54
+ * the user message that opens it, so the boundary reported is that context
55
+ * record: cutting at the user message alone would strand a context line
56
+ * belonging to a turn that no longer exists.
57
+ */
58
+ export declare function codexTurnBoundaries(records: readonly Record<string, unknown>[]): number[];