/** * Session transcript recording (issue #922). * * Persists per-turn records — including full tool-call loops — as JSONL under * `{transcripts_dir}/{session_id}.jsonl`, so downstream analysis (rootcoz) * can mine repeated tool-call patterns and distill them into code. * Tool calls never leave the sidecar: every string in tool arguments and * results — including object property names — passes through the session's * API-key redactor before it is written. * * OFF by default (`RECORD_TRANSCRIPTS`): disabled means zero I/O — no * directory creation, no file handles, no retention scans. The env value is * the boot-time default; a runtime override (#935, POST /transcripts/recording) * can flip recording without a restart and affects subsequent turns only. * When enabled, * every write is fail-soft: a transcript failure is logged and swallowed, * never propagated into the prompt response path. * * Write ordering (#923 review): writes for a session are chained per session * in turn order (`queueTurn`), so a header always precedes its turns and * turn N+1 is never interleaved ahead of turn N. Heavy work (redaction walk * + serialization) is deferred off the response-critical `agent_end` * callback via `setImmediate`, and `drainTranscriptWrites()` lets shutdown * await outstanding records instead of losing them. */ /** * Runtime recording override (#935) lives on the SessionStore instance, * NOT here: two sidecars in one process must not share the flag. This module * only answers for the boot-time env default. */ /** Recording is enabled only when RECORD_TRANSCRIPTS is explicitly truthy. */ export declare function transcriptsEnabled(): boolean; /** * Directory holding one `{session_id}.jsonl` per recorded session. Default * `~/.pi/sidecar/transcripts` — durable, per-user (NOT the agent dir, whose * default lives under /tmp). Override with PI_SIDECAR_TRANSCRIPTS_DIR. * Container deployments (e.g. RootCoz) MUST point this at a persisted volume * (RootCoz: a writable subdirectory of /data) — see the README section * "Session Transcripts". */ export declare function transcriptsDir(): string; /** * Recursively redact every string in a structured value through the session * redactor (tool arguments, tool result content, nested objects). Property * NAMES are redacted too — a tool result whose property is named after the * credential itself must not persist that name. Binary-ish * values (Uint8Array/Buffer), functions, and symbols are replaced with * placeholders; circular references are cut at depth. NOT redactDiagnostic — * that helper omits oversized diagnostics, while transcripts intentionally * keep full (redacted) payloads, bounded by MAX_RECORD_BYTES instead. */ export declare function redactStructured(value: unknown, redact: (value: string) => string, seen?: WeakSet): unknown; /** * Await all outstanding transcript writes, bounded at 10 seconds. Called * from disposeAll() so shutdown does not lose already-completed turns. * Re-snapshots in a loop: writes queued while draining (e.g. a fallback * record from a prompt rejected by session disposal) are picked up too. * On timeout the loss is WARN-logged, never reported as a successful drain. */ export declare function drainTranscriptWrites(): Promise; /** * Map the sidecar's internal usage aggregate to the documented transcript * field names (issue #922 schema): input, output, cache_read, cache_write, * cost_usd, cost_partial — plus duration_ms. Consumers read these by name. */ export declare function transcriptUsage(usage: { input_tokens: number; output_tokens: number; cache_read_tokens: number; cache_write_tokens: number; cost_usd: number | null; cost_partial: boolean; duration_ms: number; }): Record; /** * Write the one-time session header record. Called on session creation when * recording is enabled; any failure is logged and swallowed. Every * string-valued field — provider, model, system prompt, cwd, tool names — * passes through the session redactor: caller-supplied metadata must never * persist a credential in cleartext. */ /** Header payload for `writeSessionHeader` and inline turn-slot retries. */ export interface SessionHeaderInput { sessionId: string; provider: string; model: string; systemPrompt?: string; cwd?: string; customTools?: string[]; /** Session open time. When omitted (direct header writes), now is used. * A lazily written header passes the session's real creation time so * `created_at` does not report the first recorded turn's time (#935, * Qodo 8597d8f7). */ createdAt?: string; redact: (value: string) => string; } /** * Header guard for queued turns (#935, Qodo 7dad2203): a turn whose session * header is not written at slot-execution time retries the header INLINE, * in the same chain slot, immediately before the turn record. If that retry * fails the turn is SKIPPED — the file never reads turn → session_header → * turn, so a consumer treating the first line as the header always sees a * well-formed transcript (header-first, or no turns at all). */ export interface TurnHeaderGuard { header: SessionHeaderInput; /** Whether the header is known written. Evaluated inside the turn's chain * slot, after any earlier header write in the same chain has settled. */ isWritten(): boolean; /** Record the outcome of an inline header attempt. */ markWritten(appended: boolean): void; } /** * Queue the lazy header write with IN-SLOT bookkeeping (#935): the guard's * state is updated inside the chain slot itself, so any later slot — a * queued turn reading isWritten() — sees the settled outcome with no * microtask lag. Retention runs only after a successful append, with the * caller's pre-write active-session snapshot (Qodo 4d913b74). */ export declare function queueLazySessionHeader(guard: TurnHeaderGuard, activeIds: Set): void; export declare function writeSessionHeader(header: SessionHeaderInput): Promise; /** * Append one turn record: the full redacted `agent_end` message array (user * prompt, assistant text, tool calls with arguments, tool results), the * final response text, the aggregated usage, and the final stop reason. * Fail-soft by contract — the prompt response path must never fail over a * transcript write. Heavy work is deferred past the response callback. */ export declare function appendTurn(turn: { sessionId: string; turnNumber: number; messages: unknown[]; usage: Record; stopReason?: string; /** Final assembled response text (already extracted by the prompt path). */ finalText?: string; /** Error summaries when the prompt rejected without an agent_end event. */ errors?: string[]; redact: (value: string) => string; }): Promise; /** * Queue a turn write on the session's ordered chain. Fire-and-forget from * the prompt path; ordering with the session header and earlier turns is * preserved by `chainWrite`. */ export declare function queueTurn(turn: { sessionId: string; turnNumber: number; messages: unknown[]; usage: Record; stopReason?: string; finalText?: string; errors?: string[]; redact: (value: string) => string; /** Optional header guard (#935, Qodo 7dad2203) — see TurnHeaderGuard. */ ensureHeader?: TurnHeaderGuard; }): void; /** * Opportunistic retention cleanup: deletes the oldest transcript files when * the directory exceeds the age or size budget. PI_SIDECAR_TRANSCRIPTS_MAX_AGE_DAYS * (0 disables age pruning) and PI_SIDECAR_TRANSCRIPTS_MAX_SIZE_MB (0 disables * size pruning). Files belonging to ACTIVE sessions (`activeSessions`) are * never deleted — an in-flight session must not lose its header and turn * history to its own size. Bounded work, fail-soft. */ export declare function enforceRetention(activeSessions?: Set): Promise; /** * Throttled retention trigger for the turn-write path: ongoing sessions * produce turns without new headers, so budgets must be re-checked while * writing, not only at creation. At most one scan per RETENTION_INTERVAL_MS; * active sessions are excluded by the caller-supplied set. */ export declare function maybeEnforceRetention(activeSessions?: Set): void;