cogpit-memory 0.1.9 → 0.2.1

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,24 @@
1
+ export interface ParsedAgentEnvelope {
2
+ /** Sender named by the first envelope that names one, or null when none does. */
3
+ sender: string | null;
4
+ /** True when at least one envelope was unwrapped, even if it named no sender. */
5
+ matched: boolean;
6
+ /** Text with every envelope unwrapped. Unchanged when no envelope matched. */
7
+ body: string;
8
+ }
9
+ /**
10
+ * Removes envelope framing that wraps a body — an envelope no reader unwrapped,
11
+ * or the trailing tag the nested case above leaves behind. Only the wrapper
12
+ * goes: a tag quoted inside the body is content, and stays.
13
+ */
14
+ export declare function stripEnvelopeFraming(body: string): string;
15
+ export declare function parseAgentEnvelope(text: string): ParsedAgentEnvelope;
16
+ /**
17
+ * Whether a message reads as asking the reader for something: announced in the
18
+ * opening, or left on the closing line as a `?` or a request.
19
+ *
20
+ * This is the one guessed signal in agent mail, so callers must gate it on the
21
+ * message also being unanswered — a false positive then disappears as soon as
22
+ * the reader replies, and can never go stale on screen.
23
+ */
24
+ export declare function looksLikeQuestion(body: string): boolean;
@@ -0,0 +1,27 @@
1
+ export interface JsStringLiteral {
2
+ endIndex: number;
3
+ value: string;
4
+ }
5
+ export interface CodexExecInvocation {
6
+ name: string;
7
+ argumentSource: string;
8
+ startIndex: number;
9
+ }
10
+ export declare function readJsStringLiteral(source: string, startIndex: number): JsStringLiteral | null;
11
+ /**
12
+ * Extract actual tool invocations from a Codex code-mode `exec` script.
13
+ *
14
+ * This is a lexer, not a JavaScript evaluator: it deliberately ignores strings
15
+ * and comments, balances nested literals, and only recognizes calls rooted at
16
+ * the injected `tools` object. That keeps transcript rendering deterministic
17
+ * and safe even when the persisted script is malformed or user-controlled.
18
+ */
19
+ export declare function extractCodexExecInvocations(source: string): CodexExecInvocation[];
20
+ /** Return the source expression for a top-level property of an object argument. */
21
+ export declare function extractJsPropertySource(source: string, property: string): string | null;
22
+ /** Find simple string-valued object properties at any nesting depth. */
23
+ export declare function extractJsStringPropertyValues(source: string, property: string): string[];
24
+ /** Find simple numeric object properties at any nesting depth. */
25
+ export declare function extractJsNumberPropertyValues(source: string, property: string): number[];
26
+ /** Count top-level entries in an array literal, or one for a non-empty value. */
27
+ export declare function countJsCollectionEntries(source: string): number;
@@ -0,0 +1,9 @@
1
+ import type { ToolCall } from "./types";
2
+ /** Extract patch payloads passed to nested `tools.apply_patch(...)` calls in Codex exec scripts. */
3
+ export declare function extractApplyPatchInputs(execSource: string): string[];
4
+ /** Parse a Codex apply_patch string into per-file Edit/Write tool calls. */
5
+ export declare function parseApplyPatch(patchText: string, callId: string, timestamp: string, cwd?: string): ToolCall[];
6
+ /** Normalize direct and exec-wrapped Codex apply_patch calls into file tool calls. */
7
+ export declare function parseCodexToolPatches(toolName: string, rawInput: string, callId: string, timestamp: string, cwd?: string): ToolCall[];
8
+ /** Attribute an exec-wrapper patch failure without tainting earlier successful patch calls. */
9
+ export declare function findFailedNestedPatchCallIds(output: string, wrapperIsError: boolean, calls: readonly ToolCall[]): Set<string>;
@@ -0,0 +1,14 @@
1
+ /**
2
+ * Normalize a Codex function name to its canonical collaboration-tool name.
3
+ * Current collaboration tools carry a namespace separately, but older and
4
+ * transitional rollouts also encoded it in the function name itself.
5
+ */
6
+ export declare function normalizeFunctionName(rawName: string): string;
7
+ export declare function inferToolError(output: string | null): boolean;
8
+ /** Normalize Codex update_plan input to TodoWrite format. */
9
+ 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): {
12
+ text: string;
13
+ isError: boolean;
14
+ };
@@ -0,0 +1,30 @@
1
+ import type { ParseSessionOptions, ParsedSession } from "./types";
2
+ export { extractApplyPatchInputs, findFailedNestedPatchCallIds, parseApplyPatch, parseCodexToolPatches, } from "./codex-patches";
3
+ export { parseCustomToolOutput } from "./codex-tool-normalization";
4
+ interface CodexMetadata {
5
+ sessionId: string;
6
+ version: string;
7
+ gitBranch: string;
8
+ cwd: string;
9
+ model: string;
10
+ slug: string;
11
+ name: string;
12
+ branchedFrom?: {
13
+ sessionId: string;
14
+ turnIndex?: number | null;
15
+ };
16
+ firstUserMessage: string;
17
+ lastUserMessage: string;
18
+ timestamp: string;
19
+ lastTimestamp: string;
20
+ turnCount: number;
21
+ /** True when this session is a Codex sub-agent (spawned by spawn_agent) */
22
+ isSubagent: boolean;
23
+ /** Parent session ID for sub-agent sessions */
24
+ parentSessionId: string | null;
25
+ /** Canonical collaboration path for the agent that owns this rollout. */
26
+ agentPath: string;
27
+ }
28
+ export declare function isCodexSessionText(jsonlText: string): boolean;
29
+ export declare function extractCodexMetadataFromLines(lines: string[]): CodexMetadata;
30
+ export declare function parseCodexSession(jsonlText: string, options?: ParseSessionOptions): ParsedSession;
@@ -0,0 +1,8 @@
1
+ export declare const dirs: {
2
+ PROJECTS_DIR: string;
3
+ TEAMS_DIR: string;
4
+ TASKS_DIR: string;
5
+ CODEX_SESSIONS_DIR: string;
6
+ };
7
+ /** Default database path for the FTS5 search index. */
8
+ export declare const DEFAULT_DB_PATH: string;
@@ -0,0 +1,25 @@
1
+ /** Find the JSONL file path for a session by searching all project directories. */
2
+ export declare function findJsonlPath(sessionId: string): Promise<string | null>;
3
+ export declare function matchSubagentToMember(leadSessionId: string, subagentFileName: string, members: Array<{
4
+ name: string;
5
+ agentType: string;
6
+ prompt?: string;
7
+ }>): Promise<string | null>;
8
+ /**
9
+ * Encode a cwd using Claude Code's project-directory naming convention: every
10
+ * character outside `[A-Za-z0-9]` becomes `-`, so both `/Users/x/proj` and
11
+ * `C:\Users\x\proj` collapse into a flat directory name under
12
+ * `~/.claude/projects`. Mirrors `shared/providers/claude.ts`; this package is
13
+ * published standalone and cannot import outside its own `src` root.
14
+ */
15
+ export declare function encodeClaudeDirName(cwd: string): string;
16
+ /**
17
+ * Best-effort inverse of {@link encodeClaudeDirName}. The encoding is lossy —
18
+ * a literal `-` is indistinguishable from a separator — so callers that can
19
+ * read a session's recorded `cwd` should always prefer that.
20
+ */
21
+ export declare function decodeClaudeDirName(dirName: string): string;
22
+ export declare function projectDirToReadableName(dirName: string): {
23
+ path: string;
24
+ shortName: string;
25
+ };
@@ -0,0 +1,40 @@
1
+ /**
2
+ * Interactive prompt detection — plan approval and user question states.
3
+ */
4
+ import type { ParsedSession, Turn } from "./types";
5
+ export interface PlanApprovalState {
6
+ type: "plan";
7
+ allowedPrompts?: Array<{
8
+ tool: string;
9
+ prompt: string;
10
+ }>;
11
+ }
12
+ export interface UserQuestionState {
13
+ type: "question";
14
+ toolUseId: string;
15
+ questions: Array<{
16
+ question: string;
17
+ header?: string;
18
+ options: Array<{
19
+ label: string;
20
+ description?: string;
21
+ }>;
22
+ multiSelect?: boolean;
23
+ }>;
24
+ }
25
+ export type PendingInteraction = PlanApprovalState | UserQuestionState | null;
26
+ /**
27
+ * Check if the previous turn's last tool call is the same interactive tool that
28
+ * *errored* -- indicates a stuck re-call loop we should suppress.
29
+ *
30
+ * An unanswered (result === null) prompt in the previous turn is NOT a loop:
31
+ * it just means the user replied with a message instead of answering, and the
32
+ * agent is now asking again. Treating that as stuck suppressed the current
33
+ * prompt entirely, leaving the turn blocked with no way to answer it.
34
+ */
35
+ export declare function isStuckInteractiveLoop(turns: Turn[], toolName: string): boolean;
36
+ /**
37
+ * Detect if the session is waiting for user interaction (plan approval or
38
+ * AskUserQuestion). Returns the interaction state or null.
39
+ */
40
+ export declare function detectPendingInteraction(session: ParsedSession): PendingInteraction;
@@ -0,0 +1,26 @@
1
+ /**
2
+ * Shared message type guards for RawMessage discrimination.
3
+ *
4
+ * Previously duplicated between turnBuilder.ts and parser.ts to avoid circular
5
+ * dependencies. Extracted here as a dependency-free leaf module (imports only
6
+ * from ./types) so both files can share a single source of truth.
7
+ */
8
+ import type { RawMessage, UserMessage, AssistantMessage, ProgressMessage, SystemMessage, SummaryMessage, QueueOperationMessage, AttachmentMessage } from "./types";
9
+ export declare function isUserMessage(msg: RawMessage): msg is UserMessage;
10
+ export declare function isAssistantMessage(msg: RawMessage): msg is AssistantMessage;
11
+ export declare function isProgressMessage(msg: RawMessage): msg is ProgressMessage;
12
+ export declare function isSystemMessage(msg: RawMessage): msg is SystemMessage;
13
+ export declare function isSummaryMessage(msg: RawMessage): msg is SummaryMessage;
14
+ export declare function isQueueOperationMessage(msg: RawMessage): msg is QueueOperationMessage;
15
+ export declare function isAttachmentMessage(msg: RawMessage): msg is AttachmentMessage;
16
+ /**
17
+ * The synthetic user message Claude Code writes after compacting, carrying the
18
+ * summary text. Flagged `isVisibleInTranscriptOnly` too — it is transcript
19
+ * furniture, never something the user typed.
20
+ */
21
+ export declare function isCompactSummaryMessage(msg: RawMessage): msg is UserMessage & {
22
+ isCompactSummary: true;
23
+ };
24
+ export declare function isCompactBoundary(msg: RawMessage): msg is SystemMessage & {
25
+ subtype: "compact_boundary";
26
+ };
@@ -0,0 +1,31 @@
1
+ import { type SessionStatusInfo } from "./sessionStatus";
2
+ export declare function getSessionMeta(filePath: string): Promise<{
3
+ sessionId: string;
4
+ version: string;
5
+ gitBranch: string;
6
+ model: string;
7
+ slug: string;
8
+ cwd: string;
9
+ firstUserMessage: string;
10
+ lastUserMessage: string;
11
+ timestamp: string;
12
+ turnCount: number;
13
+ lineCount: number;
14
+ branchedFrom: {
15
+ sessionId: string;
16
+ turnIndex?: number | null;
17
+ } | undefined;
18
+ }>;
19
+ /**
20
+ * Read backward through a session JSONL to derive agent status.
21
+ * Scans in 4KB chunks from the tail, parsing one line at a time until it
22
+ * finds a meaningful message (assistant or non-meta user). This reads only
23
+ * as far as needed — typically one chunk — and uses the same
24
+ * deriveSessionStatus() function as the client side.
25
+ */
26
+ export declare function getSessionStatus(filePath: string): Promise<SessionStatusInfo>;
27
+ /**
28
+ * Search all user messages in a session file for a query string.
29
+ * Returns the first matching message snippet, or null if no match.
30
+ */
31
+ export declare function searchSessionMessages(filePath: string, query: string): Promise<string | null>;
@@ -0,0 +1,14 @@
1
+ import type { ParsedSession, ImageBlock, ParseSessionOptions, UserContent } from "./types";
2
+ export type { PendingInteraction } from "./interactiveState";
3
+ export type { ParseSessionOptions } from "./types";
4
+ export { detectPendingInteraction } from "./interactiveState";
5
+ export declare function parseSession(jsonlText: string, options?: ParseSessionOptions): ParsedSession;
6
+ /**
7
+ * Incrementally append new JSONL lines to an existing parsed session.
8
+ * Avoids re-parsing all turns from scratch — only re-processes the last
9
+ * (potentially incomplete) turn and any new messages.
10
+ */
11
+ export declare function parseSessionAppend(existing: ParsedSession, newJsonlText: string): ParsedSession;
12
+ export declare function getUserMessageText(content: UserContent | null): string;
13
+ export declare function getUserMessageImages(content: UserContent | null): ImageBlock[];
14
+ export declare function getToolColor(toolName: string): string;
@@ -0,0 +1,2 @@
1
+ /** Parse a duration string like "5d", "12h", "30m" to milliseconds. */
2
+ export declare function parseMaxAge(raw: string): number;
@@ -0,0 +1,154 @@
1
+ export interface IndexStats {
2
+ dbPath: string;
3
+ dbSizeBytes: number;
4
+ dbSizeMB: number;
5
+ indexedFiles: number;
6
+ indexedSessions: number;
7
+ indexedSubagents: number;
8
+ totalRows: number;
9
+ watcherRunning: boolean;
10
+ lastFullBuild: string | null;
11
+ lastUpdate: string | null;
12
+ }
13
+ export interface SearchHit {
14
+ sessionId: string;
15
+ filePath: string;
16
+ location: string;
17
+ snippet: string;
18
+ matchCount: number;
19
+ }
20
+ export declare class SearchIndex {
21
+ private db;
22
+ private dbPath;
23
+ projectsDir: string | null;
24
+ private _watcherRunning;
25
+ private _lastFullBuild;
26
+ private _lastUpdate;
27
+ private watcher;
28
+ private debounceTimers;
29
+ constructor(dbPath: string);
30
+ private initSchema;
31
+ getStats(): IndexStats;
32
+ /** Index tool call inputs and results under the given location prefix. */
33
+ private insertToolCalls;
34
+ /**
35
+ * Insert all searchable content from a parsed session into the FTS5 index.
36
+ * Shared by both `indexFile` (single-file) and `buildFull` (batch).
37
+ */
38
+ private insertSessionContent;
39
+ /**
40
+ * Parse a JSONL file and insert all searchable content into the FTS5 index.
41
+ * Idempotent: deletes old data for the file before re-indexing.
42
+ * All inserts run in a single transaction for performance.
43
+ */
44
+ indexFile(filePath: string, sessionId?: string, mtimeMs?: number, opts?: {
45
+ isSubagent?: boolean;
46
+ parentSessionId?: string | null;
47
+ }): void;
48
+ /**
49
+ * Query the FTS5 index and return structured search results.
50
+ *
51
+ * - FTS5 unicode61 tokenizer is case-insensitive by default.
52
+ * - When `caseSensitive` is true, a post-filter checks the original query
53
+ * against the snippet text (exact case match).
54
+ * - When `maxAgeMs` is provided, only files whose mtime in `indexed_files`
55
+ * falls within the window are included (join on source_file).
56
+ * - `sessionId` restricts results to a single session.
57
+ * - `limit` defaults to 200 and is clamped to a max of 200.
58
+ */
59
+ search(query: string, opts?: {
60
+ limit?: number;
61
+ sessionId?: string;
62
+ maxAgeMs?: number;
63
+ caseSensitive?: boolean;
64
+ }): SearchHit[];
65
+ /**
66
+ * Count total matching rows and distinct sessions for a query (without LIMIT).
67
+ * Used by the route to report accurate totalHits and sessionsSearched.
68
+ */
69
+ countMatches(query: string, opts?: {
70
+ sessionId?: string;
71
+ maxAgeMs?: number;
72
+ }): {
73
+ totalHits: number;
74
+ sessionsSearched: number;
75
+ };
76
+ /**
77
+ * Clear all indexed data and re-index every JSONL file under `projectsDir`.
78
+ * Structure: projectsDir/{projectName}/{sessionId}.jsonl
79
+ * Subagents: projectsDir/{projectName}/{sessionId}/subagents/agent-{id}.jsonl
80
+ *
81
+ * Optimized: discovers all files first, then processes them in a single
82
+ * SQLite transaction with pre-prepared statements. This avoids the overhead
83
+ * of 3000+ individual transactions (each forcing a disk sync).
84
+ *
85
+ * Stores `projectsDir` as a class field so `rebuild()` can reuse it.
86
+ */
87
+ buildFull(projectsDir: string): void;
88
+ /**
89
+ * Incrementally re-index only files whose mtime has changed since last index.
90
+ * New files (not in indexed_files) are always indexed.
91
+ *
92
+ * WARNING: This walks ALL files under projectsDir and stats each one.
93
+ * On large session stores (3000+ files) this can take minutes.
94
+ * Prefer `updateRecent()` for CLI search paths.
95
+ */
96
+ updateStale(projectsDir: string): void;
97
+ /**
98
+ * Lightweight incremental update for CLI search paths.
99
+ *
100
+ * Still walks and stats all files via `discoverFiles`, but skips DB lookups
101
+ * for files with mtime <= the high-water mark that are already indexed.
102
+ * Caps re-indexing to `maxFiles` to prevent blocking on large backlogs
103
+ * (run `index rebuild` for a full catch-up).
104
+ */
105
+ updateRecent(projectsDir: string, maxFiles?: number): void;
106
+ /**
107
+ * Re-run buildFull using the previously stored projectsDir.
108
+ * No-op if projectsDir was never set.
109
+ */
110
+ rebuild(): void;
111
+ /**
112
+ * Walk the projects directory tree and invoke `callback` for every JSONL file.
113
+ *
114
+ * Directory structure:
115
+ * projectsDir/
116
+ * {projectName}/
117
+ * {sessionId}.jsonl <- session file
118
+ * {sessionId}/subagents/
119
+ * agent-{agentId}.jsonl <- subagent file (recursive)
120
+ *
121
+ * Skips the "memory" directory.
122
+ */
123
+ private discoverFiles;
124
+ /**
125
+ * Recursively discover subagent JSONL files under the subagents directory
126
+ * that corresponds to `parentPath`.
127
+ *
128
+ * For a parent at `/path/to/session-1.jsonl`, looks for subagents at
129
+ * `/path/to/session-1/subagents/agent-*.jsonl`.
130
+ *
131
+ * Recurses up to `maxDepth` levels (default 4).
132
+ */
133
+ private discoverSubagents;
134
+ /**
135
+ * Start watching `projectsDir` for JSONL file changes.
136
+ * Runs `updateStale()` immediately for an initial sync, then sets up
137
+ * `fs.watch` with `{ recursive: true }` (macOS-compatible) to detect
138
+ * subsequent file changes and trigger debounced re-indexing.
139
+ */
140
+ startWatching(projectsDir: string): void;
141
+ /**
142
+ * Stop the file watcher and clear any pending debounce timers.
143
+ * Safe to call even when not watching (no-op).
144
+ */
145
+ stopWatching(): void;
146
+ /**
147
+ * Private helper: debounce re-indexing of a single file.
148
+ * Waits 2 seconds after the last change event for a given file path
149
+ * before actually calling `indexFile()`. This coalesces rapid writes
150
+ * (e.g. streaming JSONL appends) into a single index operation.
151
+ */
152
+ private debouncedReindex;
153
+ close(): void;
154
+ }
@@ -0,0 +1,10 @@
1
+ /**
2
+ * Session statistics computation — token usage aggregation and tool call tallying.
3
+ *
4
+ * Token totals are the raw figures the transcript reports. Costs are not
5
+ * computed here: pricing needs the live LiteLLM rate table, which callers
6
+ * apply via shared/usageCost/pricing.ts.
7
+ */
8
+ import type { Turn, SessionStats } from "./types";
9
+ export declare function createEmptySessionStats(turnCount: number): SessionStats;
10
+ export declare function computeStats(turns: Turn[]): SessionStats;
@@ -0,0 +1,42 @@
1
+ /**
2
+ * Derives the current session status from raw JSONL messages.
3
+ * Walks backward from the last message to determine if the agent
4
+ * is idle, thinking, calling tools, etc.
5
+ *
6
+ * This is a pure function — status is always derived from data,
7
+ * never stored as app state.
8
+ */
9
+ export type SessionStatus = "idle" | "thinking" | "tool_use" | "processing" | "completed" | "compacting" | "deferred" | "awaiting_agents";
10
+ export interface SessionStatusInfo {
11
+ status: SessionStatus;
12
+ /** Name of the tool currently being used (if status is tool_use) */
13
+ toolName?: string;
14
+ /** Number of pending queue items (user messages waiting to be processed) */
15
+ pendingQueue?: number;
16
+ /** Why the session terminated (from Claude Code's terminal_reason). Only set for non-normal endings. */
17
+ terminalReason?: string;
18
+ /** Number of background agents/workflows still running (status awaiting_agents) */
19
+ pendingAgents?: number;
20
+ /** Short descriptions of the pending background agents, oldest first */
21
+ pendingAgentDescriptions?: string[];
22
+ }
23
+ type RawMsg = {
24
+ type: string;
25
+ [key: string]: unknown;
26
+ };
27
+ /**
28
+ * Derive session status from raw JSONL message objects.
29
+ *
30
+ * **Provider dispatch:** The function auto-detects the session format by
31
+ * inspecting `rawMessages[0].type`. Codex sessions start with one of
32
+ * `session_meta | turn_context | event_msg | response_item` and are routed
33
+ * to `deriveCodexSessionStatus`. All other sessions are treated as Claude Code
34
+ * format. If a third provider is added, add a detection branch here and in
35
+ * `src/lib/providers/registry.ts`.
36
+ */
37
+ export declare function deriveSessionStatus(rawMessages: RawMsg[]): SessionStatusInfo;
38
+ /** Human-readable label for terminal_reason values from Claude Code. */
39
+ export declare function getTerminalReasonLabel(reason: string): string;
40
+ /** Human-readable label for a session status. Returns null for "idle". */
41
+ export declare function getStatusLabel(status: SessionStatus | undefined, toolName?: string, terminalReason?: string, pendingAgents?: number): string | null;
42
+ export {};
@@ -0,0 +1,27 @@
1
+ /**
2
+ * Token display estimation and cost formatting.
3
+ *
4
+ * Pricing lives in shared/usageCost/pricing.ts, driven by LiteLLM's live rate
5
+ * table — nothing here carries hardcoded rates. What remains is display-only
6
+ * accounting of the thinking/visible output split.
7
+ *
8
+ * Claude Code 2.1.19x+ reports thinking tokens directly on the assistant
9
+ * record, so that half of the split is now exact. Older transcripts recorded
10
+ * only the message_start placeholder usage, so they still fall back to a
11
+ * content estimate at ≈4 chars/token — as does the visible-output half, which
12
+ * the CLI does not break out.
13
+ */
14
+ import type { Turn } from "./types";
15
+ /** Approximate characters per token for content-based estimation. */
16
+ export declare const CHARS_PER_TOKEN = 4;
17
+ /**
18
+ * Thinking tokens for a turn: the count the model reported when it is
19
+ * available, falling back to a character estimate for older transcripts.
20
+ *
21
+ * A reported 0 is a fact — a turn that did no thinking — so presence is tested
22
+ * rather than truthiness.
23
+ */
24
+ export declare function estimateThinkingTokens(turn: Turn): number;
25
+ /** Estimate non-thinking output tokens (text + tool use JSON). */
26
+ export declare function estimateVisibleOutputTokens(turn: Turn): number;
27
+ export declare function formatCost(usd: number): string;
@@ -0,0 +1,38 @@
1
+ /**
2
+ * Turn builder — state machine that converts raw JSONL messages into Turn objects.
3
+ */
4
+ import type { RawMessage, Turn } from "./types";
5
+ /**
6
+ * Attach each peer message to the `SendMessage` that answered it.
7
+ *
8
+ * Walks blocks in chronological order holding the still-unanswered messages per
9
+ * sender, so a reply claims the oldest outstanding message from that sender and
10
+ * pairing only ever runs forward in time. A `SendMessage` sent *before* any
11
+ * inbound message from that sender is an instruction, not a reply.
12
+ *
13
+ * The join is on the sender name, which is the only key available: the block
14
+ * carries no task id, because `origin.senderTaskId` names the sending agent's
15
+ * task rather than the message. `SendMessage.input.to` uses the same names that
16
+ * arrive in `origin.from`, so the join holds.
17
+ *
18
+ * A pure recompute: every existing pairing is cleared before the walk, so the
19
+ * answer depends only on the list handed in. Callers that assemble a transcript
20
+ * from several `buildTurns` calls — the page stitcher and the incremental
21
+ * append — re-run it over the joined list and get what a single parse of that
22
+ * list would have produced. Turns holding no peer message come back by
23
+ * reference, so a re-run costs nothing for the rest of the transcript.
24
+ */
25
+ export declare function pairAgentMessageReplies(turns: readonly Turn[]): Turn[];
26
+ /**
27
+ * Raw-message index where each turn `buildTurns` would produce begins, so
28
+ * `starts[i]` is the index that produces `turns[i]`.
29
+ *
30
+ * Incremental appends need both a rebuild cut point and the number of turns
31
+ * that precede it. Deriving them separately is how an append drifts out of
32
+ * sync with a full re-parse, so both come from this one pass.
33
+ *
34
+ * Mirrors the turn-creation rule of `buildTurns` below — a parser test asserts
35
+ * the two agree, so keep them in lockstep.
36
+ */
37
+ export declare function findTurnStartIndices(messages: RawMessage[]): number[];
38
+ export declare function buildTurns(messages: RawMessage[]): Turn[];