import type { AgentMessage } from "@oh-my-pi/pi-agent-core"; import type { ImageContent, MessageAttribution, ServiceTierByFamily, TextContent } from "@oh-my-pi/pi-ai"; import type { StructuredSubagentSchemaMode } from "../task/types"; export const CURRENT_SESSION_VERSION = 3; export const SESSION_TITLE_SLOT_BYTES = 256; export const SESSION_TITLE_SLOT_ENTRY_TYPE = "title"; export const TITLE_CHANGE_ENTRY_TYPE = "title_change"; export type SessionTitleSource = "auto" | "user"; /** Fixed-width first-line slot carrying the mutable current session title. */ export interface SessionTitleSlotEntry { type: typeof SESSION_TITLE_SLOT_ENTRY_TYPE; v: 1; title: string; source?: SessionTitleSource; updatedAt: string; pad: string; } export const EPHEMERAL_MODEL_CHANGE_ROLE = "fallback"; export interface SessionHeader { type: "session"; version?: number; // v1 sessions don't have this id: string; title?: string; // Auto-generated title from first message titleSource?: SessionTitleSource; timestamp: string; cwd: string; /** * Additional workspace directories beyond `cwd` (multi-root workspace). * Absolute, normalized, deduplicated. Absent on legacy single-cwd sessions. * See {@link SessionWorkspace} in `./session-workspace`. */ additionalDirectories?: string[]; parentSession?: string; /** Prior absolute JSONL locations recorded by successful session moves. */ previousSessionFiles?: string[]; /** Provider prompt-cache identity inherited by exact-route full forks. */ providerPromptCacheKey?: string; } export interface NewSessionOptions { parentSession?: string; /** Provider prompt-cache identity to seed on the new session header. */ providerPromptCacheKey?: string; /** Skip flushing the current session and delete it instead of saving. */ drop?: boolean; /** Additional workspace directories to seed on the new session. */ additionalDirectories?: string[]; } export interface SessionEntryBase { type: string; id: string; parentId: string | null; timestamp: string; } export interface SessionMessageEntry extends SessionEntryBase { type: "message"; message: AgentMessage; } export interface ThinkingLevelChangeEntry extends SessionEntryBase { type: "thinking_level_change"; thinkingLevel?: string | null; /** * The user-configured selector at the time of this change: `"auto"` when auto * mode was active, otherwise the concrete level. Absent on entries written * before auto-mode persistence existed; readers fall back to `thinkingLevel`. */ configured?: string | null; } export interface ModelChangeEntry extends SessionEntryBase { type: "model_change"; /** Model in "provider/modelId" format */ model: string; /** Role: "default", "smol", "slow", etc. Undefined treated as "default" */ role?: string; /** True when this transition selected a retry-fallback model rather than the configured model. */ resolvedModelIsFallback?: boolean; } export interface ServiceTierChangeEntry extends SessionEntryBase { type: "service_tier_change"; serviceTier: ServiceTierByFamily | null; } export interface CompactionEntry extends SessionEntryBase { type: "compaction"; summary: string; shortSummary?: string; firstKeptEntryId: string; tokensBefore: number; /** Extension-specific data (e.g., ArtifactIndex, version markers for structured compaction) */ details?: T; /** Hook-provided data to persist across compaction */ preserveData?: Record; /** True if generated by an extension, undefined/false if pi-generated (backward compatible) */ fromExtension?: boolean; /** * Dead-end warning from the post-pass progress guard: the pass completed * but freed too little for maintenance to continue. Rendered on the * compaction divider. */ warning?: string; } export interface BranchSummaryEntry extends SessionEntryBase { type: "branch_summary"; fromId: string; summary: string; /** Extension-specific data (not sent to LLM) */ details?: T; /** True if generated by an extension, false if pi-generated */ fromExtension?: boolean; } /** * Pure marker entry recorded by `/clear` (resetSessionContext). It carries no * payload — its presence on the branch is a durable boundary the collapsed * live transcript and the model-context rebuild start emission after, so a * rebuild (theme change, focus attach, /shake, resume) does not resurrect the * pre-reset conversation. The on-disk record and the plain `transcript:true` * export path keep the full pre-reset history. */ export interface ResetBoundaryEntry extends SessionEntryBase { type: "reset_boundary"; } /** * Custom entry for extensions to store extension-specific data in the session. * Use customType to identify your extension's entries. * * Purpose: Persist extension state across session reloads. On reload, extensions can * scan entries for their customType and reconstruct internal state. * * Does NOT participate in LLM context (ignored by buildSessionContext). * For injecting content into context, see CustomMessageEntry. */ export interface CustomEntry extends SessionEntryBase { type: "custom"; customType: string; data?: T; } /** Label entry for user-defined bookmarks/markers on entries. */ export interface LabelEntry extends SessionEntryBase { type: "label"; targetId: string; label: string | undefined; } /** Append-only audit entry recording a session title change. */ export interface TitleChangeEntry extends SessionEntryBase { type: typeof TITLE_CHANGE_ENTRY_TYPE; title: string; previousTitle?: string; source: SessionTitleSource; trigger?: string; } declare module "@oh-my-pi/pi-agent-core/compaction/entries" { interface CustomCompactionSessionEntries { titleChange: TitleChangeEntry; credentialPin: CredentialPinEntry; resetBoundary: ResetBoundaryEntry; } } /** TTSR injection entry - tracks which time-traveling rules have been injected this session. */ export interface TtsrInjectionEntry extends SessionEntryBase { type: "ttsr_injection"; /** Names of rules that were injected */ injectedRules: string[]; } /** * Records which OAuth account served this session's requests for a provider. * * Provider prompt caches (Anthropic in particular) are account-scoped, and the * auth store's session-sticky routing is process-local under a remote auth * broker, so resume must re-pin the same account to reuse the warm cache * prefix. Stores a sha-256 of the account + billing-scope tuple instead of * the raw email/uuid/org; note an unsalted digest of a guessable email is * still linkable, so exported sessions are pseudonymous, not anonymous. */ export interface CredentialPinEntry extends SessionEntryBase { type: "credential_pin"; /** Provider id the pin applies to (e.g. "anthropic"). */ provider: string; /** `credentialPinHash()` of the serving account's identity + scope tuple. */ hash: string; } /** Session init entry - captures initial context for subagent sessions (debugging/replay). */ export interface SessionInitEntry extends SessionEntryBase { type: "session_init"; /** Full system prompt sent to the model */ systemPrompt: string; /** Initial task/user message */ task: string; /** Tools available to the agent */ tools: string[]; /** Agent definition name (for example `scout` or `reviewer`). */ agent?: string; /** Semantic model role declared by the agent, retained even after concrete model resolution. */ modelRole?: string; /** Initially resolved provider/model selector for historical display. */ resolvedModel?: string; /** Whether the agent definition is read-only, allowing an exact zero-LoC attribution. */ readOnly?: boolean; /** Output schema if structured output was requested. */ outputSchema?: unknown; /** Enforcement policy recorded with the output schema for faithful revival. */ outputSchemaMode?: StructuredSubagentSchemaMode; /** Whether revival must retain only the explicitly persisted tool names. */ restrictToolNames?: boolean; /** Spawn allowlist the subagent ran with ("" = none, "*" = any, else CSV); absent on pre-spawns files. */ spawns?: string; /** The agent's `readSummarize` setting (`false` = read summarization disabled); absent uses the session default. */ readSummarize?: boolean; } /** Mode change entry - tracks agent mode transitions (e.g. plan mode). */ export interface ModeChangeEntry extends SessionEntryBase { type: "mode_change"; /** Current mode name, or "none" when exiting a mode */ mode: string; /** Optional mode-specific data (e.g. plan file path) */ data?: Record; } /** * Custom message entry for extensions to inject messages into LLM context. * Use customType to identify your extension's entries. * * Unlike CustomEntry, this DOES participate in LLM context. * The content participates in LLM context through convertToLlm(). * Use details for extension-specific metadata (not sent to LLM). * * display controls TUI rendering: * - false: hidden entirely * - true: rendered with distinct styling (different from user messages) */ export interface CustomMessageEntry extends SessionEntryBase { type: "custom_message"; customType: string; content: string | (TextContent | ImageContent)[]; details?: T; display: boolean; /** Who initiated this message for billing/attribution semantics. */ attribution?: MessageAttribution; } /** Session entry - has id/parentId for tree structure (returned by "read" methods in SessionManager) */ export type SessionEntry = | SessionMessageEntry | ThinkingLevelChangeEntry | ModelChangeEntry | ServiceTierChangeEntry | CompactionEntry | BranchSummaryEntry | CustomEntry | CustomMessageEntry | LabelEntry | TitleChangeEntry | TtsrInjectionEntry | SessionInitEntry | ModeChangeEntry | CredentialPinEntry | ResetBoundaryEntry; /** Raw logical file entry after loaders strip any fixed-width title slot. */ export type FileEntry = SessionHeader | SessionEntry; /** Physical JSONL entry before slot-aware loaders fold the title slot. */ export type RawFileEntry = SessionTitleSlotEntry | FileEntry; /** Tree node for getTree() - defensive copy of session structure */ export interface SessionTreeNode { entry: SessionEntry; children: SessionTreeNode[]; /** Resolved label for this entry, if any */ label?: string; } export interface UsageStatistics { input: number; output: number; cacheRead: number; cacheWrite: number; totalTokens: number; orchestrationInput: number; orchestrationOutput: number; orchestrationCacheRead: number; premiumRequests: number; cost: number; }