import * as crypto from "node:crypto"; import * as fs from "node:fs"; import { type AgentMessage } from "@gajae-code/agent-core"; import type { ConfiguredModelChainEntry as SharedConfiguredModelChainEntry } from "@gajae-code/agent-core/compaction"; import type { ImageContent, Message, MessageAttribution, ServiceTier, TextContent } from "@gajae-code/ai/core"; import type * as native from "@gajae-code/natives"; import type { TtsrInjectionRecord } from "../export/ttsr"; import type { ManagedLegacyLocalMigrationSource } from "../internal-urls/local-protocol"; import { ArtifactManager } from "./artifacts"; import { type BlobPutResult, BlobStore } from "./blob-store"; import { type ManagedMigrationPolicy } from "./internal/managed-session-scope"; import { type ManagedBoundedAppendExpectation, type ManagedDirectoryRoot, type ManagedFileIdentity, ManagedSessionDescendantStore } from "./internal/managed-session-storage"; import { type BaseAnchor, BoundedLabelsPinsStore, type CommittedTail, type DescriptorSnapshot, type DictionaryPartitionCommit, FixedCacheAccount, type MetadataDeltaValue, type ParentBucketCommit, type ReducerState, type ReopenClassification, SessionMemoryAccountant } from "./internal/session-memory-sidecar"; import { type MemoryGuardCreateCheckpointInput, type MemoryGuardParticipantIngressLease, type MemoryGuardRestoreInput, type MemoryGuardRestoreResult, type MemoryGuardSessionManagerCheckpointV1 } from "./memory-guard-checkpoint-participant"; import { type BashExecutionMessage, type CustomMessage, type FileMentionMessage, type HookMessage, type PythonExecutionMessage } from "./messages"; import { type SessionManagerReadAccess, sessionManagerReadCapability } from "./session-manager-internal"; import type { ManagedSessionSecurityContext, SessionStorage, SessionStorageStat, VerifiedSessionDeleteResult, VerifiedSessionDeleteTarget } from "./session-storage"; export declare const CURRENT_SESSION_VERSION = 5; export interface SessionHeader { type: "session"; version?: number; id: string; title?: string; titleSource?: "auto" | "user"; timestamp: string; cwd: string; parentSession?: string; } export interface NewSessionOptions { parentSession?: string; /** Skip flushing the current session and delete it instead of saving. */ drop?: boolean; } /** Internal successor prepared without changing the manager's visible identity. */ export interface PreparedNewSession { readonly sessionId: string; readonly sessionFile: string | undefined; readonly artifactsDir: string | null; readonly managedLegacyLocalMigrationSource: ManagedLegacyLocalMigrationSource | null; } export interface SessionEntryBase { type: string; id: string; parentId: string | null; timestamp: string; } export interface ColdSpillRef { kind: "cold_spill"; ref: string; encoding: "utf8" | "json"; originalChars: number; sha256: string; bytes: number; } export interface EvictedContentMarker { evictedAt: number; reason: "compacted_history"; compactionEntryId: string; firstKeptEntryId: string; payloads: Record; } export interface EvictCompactedContentResult { evictedEntries: number; hotCharsRemoved: number; coldBlobBytes: number; payloadRefs: number; alreadyEvictedEntries: number; coldSpillWriteCount: number; coldSpillReadCount: number; residentTextReadCount: number; residentImageReadCount: number; } export interface SessionManagerObservabilityStats { coldSpillWriteCount: number; coldSpillReadCount: number; residentTextReadCount: number; residentImageReadCount: number; residentCacheAdoptFallbackCount: number; residentCacheTrustRejectCount: number; residentCacheWin32FallbackCount: number; residentCacheDegradedReason?: string; residentCacheDegradedCauseCode?: string; residentBlobPlaceholderCount: number; publicMaterializerCallCount: number; getEntryMaterializerCallCount: number; getBranchMaterializerCallCount: number; getEntriesMaterializerCallCount: number; materializedEntriesCachePopulateCount: number; materializedCacheDemotedCount: number; pathOnlyContextBuildCount: number; } export type SessionMemoryMode = "off" | "shadow" | "enabled" | "auto"; export type SessionMemoryGcStrategy = "current" | "none" | "async" | "pressure"; export type SessionMemorySecondaryArtifactMode = "auto" | "enabled" | "disabled"; export interface SessionMemoryPhaseTelemetry { wallMs: number; cpuMs: number | null; } export interface SessionMemoryFirstOpenTelemetry { /** True when a bounded first-open attempt was started for this manager. */ attempted: boolean; /** True only after the bounded sidecar set and context were committed. */ succeeded: boolean; strategy: SessionMemoryGcStrategy; secondaryArtifactMode: SessionMemorySecondaryArtifactMode; wallMs: number; cpuMs: number; gcRequests: number; gcRequestCount: number; gcElapsedMs: number; bytesRead: number; transcriptBytesRead: number; bytesWritten: number; sidecarBytesWritten: number; sidecarFileBytes: number; recordsParsed: number; semanticRecordsParsed: number; suffixRecordsParsed: number; lineAssemblyCopyCount: number; lineCopyCount: number; lineAssemblyCopyBytes: number; indexWriteCalls: number; indexWriteBytes: number; fsyncCount: number; fsyncElapsedMs: number; /** Phase names are stable internal keys; missing phases remain zero-valued. */ phaseTelemetry: Record; /** Alias retained for benchmark/report consumers. */ phaseEvidence: Readonly>; /** Alias retained for benchmark/report consumers. */ phaseTimings: Readonly>; /** Internal pressure-mode baseline; not persisted. */ pressureBaselineBytes?: number; dictionaryArtifactEnabled: boolean; parentArtifactEnabled: boolean; dictionaryBuildElapsedMs: number; parentBuildElapsedMs: number; flatIndexElapsedMs: number; } export interface SessionMemoryStats { sidecarEnabled: boolean; coldRetirementActive: boolean; sidecarIneligible: boolean; hotRegionBytes: number; metaDescriptorBytes: number; totalAccountedBytes: number; /** Fixed cache/reducer reservation charged for enforcement, distinct from live residency. */ reservedBudgetBytes: number; /** Bytes currently allocated in bounded block/entry/tail caches. */ allocatedCacheBytes: number; /** Resident hot suffix object bytes, excluding reserved budgets. */ hotResidentBytes: number; /** Resident reducer/labels/metadata-delta descriptor bytes. */ metadataResidentBytes: number; /** Bytes currently present in disposable sidecar files. */ sidecarFileBytes: number; /** Latest bounded first-open telemetry; zero-valued when no attempt ran. */ firstOpen: SessionMemoryFirstOpenTelemetry; lastReopenTransition: ReopenClassification | undefined; currentCommitTransition: ReopenClassification | undefined; lazyReopenAttempted: boolean; lazyReopenSucceeded: boolean; lazyReopenFallbackReason: string | undefined; retirementFallbackReason: string | undefined; autoDisabledReason: string | undefined; consecutiveBuildFailures: number; /** Persistent bounded parent→children artifact is adopted and usable. */ parentArtifactEnabled: boolean; /** Persistent bounded dictionary artifact is adopted and usable. */ dictionaryArtifactEnabled: boolean; /** Reducer-bucket bytes retained by metadata-delta descriptors (fixed accounting). */ metadataDeltaDescriptorBytes: number; /** Live cold index bytes (descriptor size when proven, else 0). */ coldIndexBytes: number; /** Live cold block-cache allocated bytes. */ coldIndexBlockCacheBytes: number; /** Live cold entry-cache allocated bytes. */ coldEntryCacheBytes: number; /** Live observability counters (P7 contract). */ coldEntriesRetired: number; coldEntriesReloaded: number; rangeReadCount: number; rangeReadGenerationMismatchCount: number; sidecarRebuildCount: number; coldMutationPromotions: number; hotOverflowTransitions: number; labelDiskFallbackCount: number; /** Shadow-mode eager-vs-sidecar parity mismatches observed at build time (AC10 telemetry). */ shadowParityMismatchCount: number; /** Shadow-mode parity comparisons performed at build time (AC10 telemetry). */ shadowParityCheckCount: number; transcriptGeneration: number; } export interface SessionMessageEntry extends SessionEntryBase { type: "message"; message: AgentMessage; /** Cold-spill marker: when present, heavy message content was moved to durable * content-addressed blobs after compaction. The marker is entry-level session * metadata (not a message field) so strict message types stay intact. */ evictedContent?: EvictedContentMarker; } export declare function associateSessionMessageEntryId(message: AgentMessage, entryId: string): void; export declare function getSessionMessageEntryId(message: AgentMessage): string | undefined; export declare function associateSessionMessageObservationId(message: AgentMessage, observationId: string): string; export declare function getSessionMessageObservationId(message: AgentMessage): string | undefined; export declare function associateSessionMessageViewportAnchorId(message: AgentMessage, anchorId: string): void; export declare function getSessionMessageViewportAnchorId(message: AgentMessage): string | undefined; /** Returns registered viewport anchors for durable user messages in session order. */ export declare function getUserMessageViewportAnchorIds(messages: readonly AgentMessage[]): string[]; export declare function transferSessionMessageIdentity(source: AgentMessage[], target: AgentMessage[]): void; export interface ThinkingLevelChangeEntry extends SessionEntryBase { type: "thinking_level_change"; thinkingLevel?: string | null; /** * True only when an operator effort surface (`setThinkingLevelForControl`, * Shift+Tab `cycleThinkingLevel`) recorded this entry. Model-driven appends * (model-switch `defaultLevel`, temporary model switches, context clears, * re-applies after model cycling) leave it unset so `getThinkingScopeForControl` * never mints session scope without operator effort intent (issue #4695). */ operatorIntent?: boolean; } export interface ModelChangeEntry extends SessionEntryBase { type: "model_change"; /** Model in "provider/modelId" format */ model: string; /** Role: "default" or an agent role. Undefined treated as "default" */ role?: string; /** Clears the role's previously recorded model when replaying session context. */ cleared?: boolean; /** Requested model before a runtime substitution/fallback, in "provider/modelId" format. */ previousModel?: string; /** Machine-readable reason for runtime model substitution/fallback. */ reason?: string; /** Effective thinking level when the change was recorded. */ thinkingLevel?: string | null; } /** Persisted configured fallback chain for one model role. */ export type ConfiguredModelChainEntry = SharedConfiguredModelChainEntry; export type ConfiguredModelChain = Pick; export interface ServiceTierChangeEntry extends SessionEntryBase { type: "service_tier_change"; serviceTier: ServiceTier | 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; } 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; } /** * 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; } /** 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[]; /** Rich rule injection records with repeat state. */ injectedRuleRecords?: TtsrInjectionRecord[]; /** TTSR manager message count when this injection was recorded. */ ttsrMessageCount?: number; } /** Persisted MCP discovery selection state for a session branch. */ export interface MCPToolSelectionEntry extends SessionEntryBase { type: "mcp_tool_selection"; /** MCP tool names selected for visibility in discovery mode. */ selectedToolNames: string[]; /** Legacy v4 combined built-in authority, retained for read compatibility. */ selectedDiscoveredBuiltinToolNames?: string[]; /** Correlates the ordered MCP and built-in entries emitted by one combined activation. */ mutationCorrelationId?: string; } /** Persisted discovered-built-in selection state, independent of MCP authority. */ export interface DiscoveredBuiltinToolSelectionEntry extends SessionEntryBase { type: "discovered_builtin_tool_selection"; selectedToolNames: string[]; /** Correlates the ordered MCP and built-in entries emitted by one combined activation. */ mutationCorrelationId?: 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[]; /** Output schema if structured output was requested */ outputSchema?: unknown; /** Fork-context seed metadata for subagent debugging/replay. */ forkContext?: unknown; } /** 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; /** Cold-spill marker for custom-message content evicted after compaction. */ evictedContent?: EvictedContentMarker; } /** 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 | TtsrInjectionEntry | MCPToolSelectionEntry | DiscoveredBuiltinToolSelectionEntry | SessionInitEntry | ModeChangeEntry | ConfiguredModelChainEntry; /** Append-only replacement for mutable fields on the session header. */ export interface HeaderPatchRecord { type: "header_patch"; patch: Partial>; } /** Append-only replacement for replay metadata on one existing session entry. */ export interface EntryPatchRecord { type: "entry_patch"; entryId: string; patch: Partial>; } export type SessionPatchRecord = HeaderPatchRecord | EntryPatchRecord; /** Resolved file entries; patch records are applied by parseSessionEntries(). */ export type FileEntry = SessionHeader | SessionEntry; /** * Cold-region placeholder envelope for a retired entry. The heavy payload was moved * out of RAM into the disposable `.spill.idx`/`.spill.tail` cold region; the exact * transcript byte range is recorded so the entry can be lazily resolved by * `ordinal`/`id`. Never persisted; used only while a cold sidecar region is active. */ export interface SessionColdRefEntry extends SessionEntryBase { type: "session_cold_ref"; ordinal: number; seq: number; byteOffset: number; byteLength: number; } /** One cold entry's transcript location, keyed by entry id. */ export interface ColdEntryIndex { ordinal: number; seq: number; byteOffset: number; byteLength: number; recordDigest: string; parentId?: string | null; entryType?: string; } export interface SessionMemorySidecarRuntime { /** Transcript v5 remains authoritative; sidecars are disposable/rebuildable. */ enabled: boolean; /** Set when duplicate record IDs were detected; session stays eager. */ sidecarIneligible: boolean; base: BaseAnchor; tail: CommittedTail; /** Next non-header ordinal, proven while building or validating the flat index. */ nextOrdinal: number; /** Bounded hot index cache; authoritative lookup falls back to the disk index. */ coldEntries: Map; indexPath: string; tailPath: string; commitPath: string; /** Directory prefix of the persistent `.spill.parent-` bucket files. */ parentPathPrefix: string; /** Directory prefix of the persistent `.spill.dict-part-` partition files. */ dictionaryPathPrefix: string; /** Path of the persistent `.spill.dict-meta` finalization file. */ dictionaryMetaPath: string; /** Path of the persistent `.spill.metadata-delta` section. */ metadataDeltaPath: string; retirementFirstKeptEntryId: string | undefined; /** Hot-suffix byte total (accountant-bounded to ≤ 16 MiB). */ hotSuffixBytes: number; /** Resident hot suffix object bytes charged separately from the raw hot-region byte count. */ hotResidentBytes: number; /** Fixed reservation used for accounting split telemetry. */ reservedBudgetBytes: number; /** Disposable sidecar file byte total captured after first-open publication. */ sidecarFileBytes: number; accountant: SessionMemoryAccountant; reducer: ReducerState; /** Resident inline provider-affecting entries (merged order minus demoted slots). */ providerStateEntries: SessionEntry[]; /** Merged provider-list order as keys (inline + demoted); reopened/appended slots stay exact. */ providerStateOrder: string[]; blockCache: FixedCacheAccount; parentChildrenCache: Map; entryCache: FixedCacheAccount; tailCache: FixedCacheAccount; labelsPins: BoundedLabelsPinsStore; terminalTransition: ReopenClassification | undefined; reopenTransition: ReopenClassification | undefined; /** SHA-256 over the exact `.spill.idx` bytes this runtime has written/adopted ("" until proven). */ indexDigest: string; /** Descriptor of the exact index bytes whose digest was validated for cache use. */ validatedIndexDescriptor?: SessionStorageStat; /** Live running hash of the index bytes; updated on every index append. */ indexHash: crypto.Hash; /** Persistent bounded parent→children artifact state; absent = parent lookups fail closed. */ parentArtifact?: ParentArtifactRuntimeState; /** Persistent bounded dictionary artifact state; absent = dictionary lookups fail closed to the cold scan. */ dictionary?: DictionaryArtifactRuntimeState; /** Persistent metadata-delta section state; absent = no demoted provider values. */ metadataDelta?: MetadataDeltaArtifactRuntimeState; /** Fixed-size false-positive-only cache used solely for generated-ID collision avoidance. */ coldIdHashes?: BoundedColdIdHashSet; coldIdHashesDescriptor?: SessionStorageStat; /** Live observability counters (P7 live-only contract). */ coldEntriesRetired: number; coldEntriesReloaded: number; rangeReadCount: number; rangeReadGenerationMismatchCount: number; sidecarRebuildCount: number; coldMutationPromotions: number; hotOverflowTransitions: number; labelDiskFallbackCount: number; /** Shadow-mode eager-vs-sidecar parity mismatches observed at build time (AC10 telemetry). */ shadowParityMismatchCount: number; /** Shadow-mode parity comparisons performed at build time (AC10 telemetry). */ shadowParityCheckCount: number; transcriptGeneration: number; } /** Retained runtime state of the disposable parent→children artifact (block-cache charged). */ interface ParentArtifactRuntimeState { /** Exact `.spill.idx` digest the artifact covers; artifact lookups require `indexDigest === runtime.indexDigest`. */ indexDigest: string; /** Committed per-bucket exact-byte size + sha256, updated incrementally on append. */ buckets: ParentBucketCommit[]; /** Total artifact bytes (sum of bucket sizes). */ totalBytes: number; /** Block-cache bytes charged for this retained state; released on invalidation/wholesale release. */ chargedBytes: number; /** On-disk byte cap enforced on append; fixed at `PARENT_CHILDREN_BUDGET_BYTES` unless a test shrinks it. */ budgetBytes: number; } /** Retained runtime state of the disposable dictionary artifact (block-cache charged). */ interface DictionaryArtifactRuntimeState { /** Exact `.spill.idx` digest the artifact covers; lookups require `indexDigest === runtime.indexDigest`. */ indexDigest: string; /** Committed per-partition exact-byte size + sha256, updated incrementally on append. */ partitions: DictionaryPartitionCommit[]; /** Exact `.spill.dict-meta` bytes (size + digest). */ metaSize: number; metaDigest: string; recordCount: number; uniqueTerms: number; totalBytes: number; /** Bounded duplicate-id diagnostics; non-empty ⇒ the artifact is never adopted. */ duplicateIds: readonly string[]; sidecarIneligible: boolean; /** Block-cache bytes charged for this retained state; released on invalidation. */ chargedBytes: number; /** On-disk byte cap for a single partition append; fixed unless a test shrinks it. */ budgetBytes: number; /** Running per-partition hashes/sizes/records for append-time rebinding. */ partitionHashes: crypto.Hash[]; partitionSizes: number[]; partitionRecords: number[]; /** Descriptor of the exact meta bytes whose digest was validated for artifact use. */ validatedDescriptor?: SessionStorageStat; } /** Retained runtime state of the disposable metadata-delta section (reducer-bucket accounting). */ interface MetadataDeltaArtifactRuntimeState { /** Exact `.spill.idx` digest the sidecar set was built with. */ indexDigest: string; /** Exact bytes of the `.spill.metadata-delta` file. */ size: number; sha256: string; /** Live running hash of the delta bytes; updated on every value append. */ hash: crypto.Hash; /** Demoted value descriptors keyed by provider key (positions derived at marker time). */ byKey: Map>; /** Reducer-bucket bytes retained by the descriptors (fixed accounting, reported in stats). */ descriptorBytes: number; /** Fixed on-disk byte cap for the section. */ budgetBytes: number; /** Descriptor of the exact delta bytes whose digest was validated. */ validatedDescriptor?: SessionStorageStat; } /** * Bounded first-open transcript scan limits. The eager authoritative path handles * anything outside these bounds; the bounded path only ever fails closed to it. */ export declare const BOUNDED_FIRST_OPEN_MAX_LINE_BYTES: number; /** Fixed-size collision cache for generated IDs. False positives only cause regeneration. */ declare class BoundedColdIdHashSet { #private; constructor(maxEntries?: number); get atCapacity(): boolean; has(value: string): boolean; add(value: string): boolean; } export type DefaultModelSelectionStage = { readonly entryRevision: number; readonly leafRevision: number; readonly headerExportRevision: number; readonly sessionId: string; readonly sessionFile: string | undefined; readonly entries: readonly FileEntry[]; readonly tempPath: string | undefined; readonly persistsToExistingFile: boolean; readonly boundedCold: boolean; readonly appendEntries: readonly SessionEntry[]; readonly sourceDescriptor: DescriptorSnapshot | undefined; readonly sourceStat: SessionStorageStat | undefined; readonly sourceSha256: string | undefined; readonly managedAppendExpectation: ManagedBoundedAppendExpectation | undefined; }; export interface SessionManagerRevisionSnapshot { entry: number; leaf: number; headerExport: number; label: number; replayMetadata: number; } export interface SessionManagerCheckpointRevisionStrings { entry: string; leaf: string; headerExport: string; label: string; replayMetadata: string; } export declare function toSessionManagerCheckpointRevisionStrings(snapshot: SessionManagerRevisionSnapshot): SessionManagerCheckpointRevisionStrings; export type DefaultModelSelectionPromotion = { readonly kind: "promoted"; } | { readonly kind: "not_promoted"; readonly error?: Error; } | { readonly kind: "unknown"; readonly error: Error; }; /** 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 SessionContext { messages: AgentMessage[]; thinkingLevel?: string; serviceTier?: ServiceTier; /** Model roles: { default: "provider/modelId", small: "provider/modelId", ... } */ models: Record; /** Configured fallback chains for model roles on the active branch. */ configuredModelChains: Record; /** Names of TTSR rules that have been injected this session */ injectedTtsrRules: string[]; /** Rich TTSR rule injection records for repeat resume. */ injectedTtsrRuleRecords?: TtsrInjectionRecord[]; /** TTSR manager message count for repeat resume. */ ttsrMessageCount?: number; /** MCP tool names selected through discovery for this session branch. */ selectedMCPToolNames: string[]; /** Built-in discoverable tool names activated through discovery, when explicitly persisted. */ selectedDiscoveredBuiltinToolNames?: string[]; /** Whether this branch contains an explicit persisted MCP selection entry. */ hasPersistedMCPToolSelection: boolean; /** Whether this branch contains an explicit persisted discovered-built-in selection entry. */ hasPersistedDiscoveredBuiltinToolSelection?: boolean; /** Active mode (e.g. "plan") or "none" if no special mode is active */ mode: string; /** Mode-specific data from the last mode_change entry */ modeData?: Record; } /** Immutable fingerprint captured during read-only resume inspection. */ export interface ResumeSessionIdentity { canonicalPath: string; sessionId: string; dev: bigint; ino: bigint; nlink?: bigint; size: number; mtimeMs: number; mtimeNs: bigint; ctimeNs?: bigint; sha256: string; } export interface ResumeTailResumable { kind: "resumable"; identity: ResumeSessionIdentity; } export interface ResumeTailTerminal { kind: "terminal"; identity: ResumeSessionIdentity; } export interface ResumeTailError { kind: "error"; reason: "missing" | "malformed" | "unstable" | "read-failed" | "legacy_migration_disabled" | "oversized" | "context_too_large"; size?: number; } export type SessionDirectoryMigrationPolicy = ManagedMigrationPolicy; export type SessionAppendPersistenceFailurePhase = "current_append" | "prior_failure"; /** Safety bound for eager resume compatibility and managed per-file artifacts. */ export declare const RESUME_TRANSCRIPT_MAX_BYTES: number; /** * Explicit cold-session admission limit. Two-GiB transcripts remain streamable; * the extra MiB covers bounded fork header replacement without rejecting a * source exactly at the advertised limit. */ export declare const BOUNDED_RESUME_TRANSCRIPT_MAX_BYTES: number; export declare const SESSION_OVERSIZED_RECOVERY_MESSAGE = "The selected session transcript is too large to resume safely. Use `gjc export ` to export its content into a new session, or remove/archive it after confirming its content is no longer needed."; export declare class SessionAppendPersistenceError extends Error { readonly phase: SessionAppendPersistenceFailurePhase; readonly entryId: string; readonly persistenceError: Error; constructor(phase: SessionAppendPersistenceFailurePhase, entryId: string, persistenceError: Error); } /** * Typed near-limit append outcome (#4566). * * A live managed append that would cross the per-file transcript cap is now * preflighted: when the append alone cannot fit even after a full rewrite of * the live entries, this deterministic error replaces the generic * `SessionAppendPersistenceError: content_too_large` abort. It states whether * the in-memory entry was kept (so the just-committed source mutation keeps * its receipt on the next successful persist) and how to continue. */ export declare class SessionNearLimitAppendError extends Error { readonly code: "near_limit_append"; /** Serialized size (bytes) of the entry that could not fit. */ readonly entryBytes: number; /** Live-entry rewrite size (bytes) the recovery already attempted. */ readonly liveBytes: number; /** Managed per-file cap in force when the append was rejected. */ readonly capBytes: number; /** True when the entry remains in the resident list awaiting the next persist. */ readonly entryRetained: boolean; constructor(details: { entryBytes: number; liveBytes: number; capBytes: number; entryRetained: boolean; }); } export declare class SessionManagedStorageError extends Error { readonly code = "managed_storage_unsupported"; constructor(); } export declare class SessionMigrationPolicyError extends Error { readonly code = "legacy_migration_disabled"; constructor(); } export declare class SessionArtifactCapacityError extends Error { readonly code = "artifact_capacity_exceeded"; constructor(message: string); } export declare class SessionTranscriptOversizedError extends Error { readonly code = "oversized"; readonly size: number; constructor(size: number); } /** Default synchronous session-context materialization budget (512 MiB). */ export declare const SESSION_CONTEXT_MATERIALIZATION_BUDGET_BYTES_DEFAULT: number; /** Ceiling for a `GJC_SESSION_CONTEXT_BUDGET_BYTES` override (8 GiB) so the memory guard stays meaningful. */ export declare const SESSION_CONTEXT_MATERIALIZATION_BUDGET_BYTES_MAX: number; /** * Resolve the operation-peak session-context materialization budget from the * `GJC_SESSION_CONTEXT_BUDGET_BYTES` override. Parsing is fail-closed: only a * canonical positive-integer decimal value is honored; anything else (empty, * non-numeric, negative, zero, overflowing a safe integer, or above the * documented ceiling) falls back to the 512 MiB default and is surfaced as a * warning so a dropped override is never silent. */ export declare function resolveSessionContextBudgetBytes(override: string | undefined): number; /** Operation-peak budget for one synchronous session-context materialization. */ export declare const SESSION_CONTEXT_MATERIALIZATION_BUDGET_BYTES: number; /** * Thrown by the synchronous session-context builders when the materialized graph * exceeds the operation budget. `instanceof`-stable across module boundaries: * consumers map with `error instanceof SessionContextTooLargeError`, never by name * string. The over-budget graph is never retained — the builder releases scratch * before throwing and public synchronous signatures are unchanged. */ export declare class SessionContextTooLargeError extends Error { readonly code: "context_too_large"; readonly measuredBytes: number; readonly budgetBytes: number; constructor(measuredBytes: number, budgetBytes?: number, options?: ErrorOptions); } export type ResumeTailInspection = ResumeTailResumable | ResumeTailTerminal | ResumeTailError; export interface StrictSessionOpenSuccess { kind: "opened"; manager: SessionManager; } export interface StrictSessionOpenFailure { kind: "error"; reason: ResumeTailError["reason"] | "identity-mismatch" | "migration-required" | "artifact_capacity_exceeded" | "migration_busy"; message?: string; size?: number; } /** * Descriptor-bound strict-capture handle. Bounded recorded-length range reads * revalidate the live source against the captured identity on every pass; no * whole-transcript buffer is ever materialized by capture or fork. */ export interface TranscriptSnapshotHandle { readonly sourcePath: string; readonly identity: ResumeSessionIdentity; readonly storage: SessionStorage; /** * Iterate every transcript line in bounded recorded-length reads. Each line * is delivered without its trailing newline; returning `false` aborts the * pass. A completed pass re-validates the running content hash against the * captured identity and throws `identity-mismatch` on divergence. */ forEachLine(callback: (line: Uint8Array) => boolean | undefined): boolean; /** Descriptor captured after the most recent complete line pass, when supported. */ getLastReadStat(): SessionStorageStat | undefined; /** Revalidate the live source against the captured identity (bounded). */ revalidate(): { kind: "valid"; } | StrictSessionOpenFailure; /** Idempotent close; subsequent reads throw. */ close(): void; /** * Rehydrate the full transcript bytes for bounded-full-return consumers. * This is an explicit compatibility escape hatch, never used by fork/capture * publication itself. */ materialize(): Uint8Array; } /** @deprecated Use {@link TranscriptSnapshotHandle}; retained for call-site compatibility. */ export type CapturedSessionTranscriptSnapshot = TranscriptSnapshotHandle; export type StrictSessionCaptureResult = { kind: "captured"; snapshot: TranscriptSnapshotHandle; } | ResumeTailError; export type StrictSessionForkResult = { kind: "forked"; manager: SessionManager; } | StrictSessionOpenFailure; /** Result of opening an inspected session without create-or-rewrite fallback. */ export type StrictSessionOpenResult = StrictSessionOpenSuccess | StrictSessionOpenFailure; /** * Capability returned only by a strict recovery hydration open. It represents * immutable transcript authority and is consumed by the promotion seam. */ export interface RecoveryHydrationContext { readonly identity: ResumeSessionIdentity; } export interface RecoveryHydrationOpenSuccess { readonly kind: "hydrated"; readonly manager: SessionManager; readonly context: RecoveryHydrationContext; } export type RecoveryHydrationOpenResult = RecoveryHydrationOpenSuccess | StrictSessionOpenFailure; export interface RecoveryHydrationPromotionFence { /** The caller has durably published ownership and acquired its writer lease. */ readonly ownershipReady: true; } export interface SessionInfo { path: string; id: string; /** Working directory where the session was started. Empty string for old sessions. */ cwd: string; title?: string; /** Path to the parent session (if this session was forked). */ parentSessionPath?: string; created: Date; modified: Date; messageCount: number; /** True when messageCount was counted from only the bounded list prefix. */ messageCountIsEstimate?: boolean; /** File size in bytes on disk; used for compact list rendering. */ size: number; firstMessage: string; allMessagesText: string; } /** Kind of failure surfaced by strict scoped inventory. Any failure grants zero authority. */ export type StrictInventoryFailureKind = "root" | "scan" | "lstat" | "read" | "parse" | "stat" | "header" | "cwd" | "containment" | "identity"; /** Sanitized strict-inventory failure. Never carries raw file content. */ export interface StrictInventoryFailure { kind: StrictInventoryFailureKind; message: string; path?: string; } /** One exact-identity candidate suitable for ACP authorization binding. */ export interface StrictInventoryCandidate { /** Canonical absolute transcript path. */ path: string; /** Session id parsed from the header. */ id: string; /** Canonical cwd parsed from the header. */ cwd: string; /** Descriptor-bound transcript identity (dev, ino, ...). */ identity: SessionStorageStat; } /** * Strict inventory result. `complete` carries the full validated candidate set; * `failure` carries every sanitized failure and grants zero page/cursor/authority. * A failure result is never reduced to a partial candidate set. */ export type StrictInventoryResult = { kind: "complete"; candidates: StrictInventoryCandidate[]; } | { kind: "failure"; failures: StrictInventoryFailure[]; }; /** Certainty-aware close outcome for strict ACP disposal. */ export type SessionManagerCloseOutcome = { kind: "closed"; } | { kind: "close_failed_retryable"; error: Error; } | { kind: "close_unknown"; error: Error; }; /** Read-only session state made available to extensions and custom tools. */ /** Frozen read-only session facade made available to extensions and custom tools. */ export interface ReadonlySessionManager { getCwd(): string; getSessionDir(): string; getSessionId(): string; getSessionFile(): string | undefined; getSessionName(): string | undefined; getArtifactsDir(): string | null; getArtifactPath(id: string): Promise; getLeafId(): string | null; getLeafEntry(): SessionEntry | undefined; getEntry(id: string): SessionEntry | undefined; getLabel(id: string): string | undefined; getBranch(fromId?: string): SessionEntry[]; getHeader(): SessionHeader | null; getEntries(): SessionEntry[]; getTree(): SessionTreeNode[]; getUsageStatistics(): UsageStatistics; } /** Creates an immutable facade that never exposes SessionManager mutation authority. */ export declare function createReadonlySessionManager(manager: SessionManager): ReadonlySessionManager; /** Internal artifact-writing capability. Read-only facades expose it only through a private weak-map lookup. */ export type SessionArtifactCapability = Readonly>; /** * Returns the artifact capability for a concrete persistence owner or one of its * immutable read-only facades. Structural lookalikes remain unauthorized. */ export declare function sessionArtifactCapability(value: unknown): SessionArtifactCapability | undefined; /** Exported for testing */ export declare function migrateSessionEntries(entries: FileEntry[]): void; /** Exported for compaction.test.ts */ export declare function parseSessionEntries(content: string): FileEntry[]; export declare function getLatestCompactionEntry(entries: SessionEntry[]): CompactionEntry | null; export declare function buildSessionContext(entries: SessionEntry[], leafId?: string | null, byId?: Map, sessionIdentityNamespace?: string): SessionContext; /** A session directory's authority is distinct from its string path. */ export type SessionDestination = { readonly kind: "managed"; readonly directory: string; readonly securityContext: ManagedSessionSecurityContext; } | { readonly kind: "explicit"; readonly directory: string; }; export type SessionDestinationInput = string | SessionDestination | undefined; type ForkArtifactPublication = { readonly kind: "managed"; readonly snapshot: native.NativeDirectoryTreeSnapshot; readonly store: ManagedSessionDescendantStore; readonly cleanupStore: ManagedSessionDescendantStore; readonly cleanupRelativePath: string; } | { readonly kind: "explicit"; readonly artifactsDir: string; readonly snapshot: native.NativeDirectoryTreeSnapshot; }; /** Exported for testing */ export declare function loadEntriesFromFile(filePath: string, storage?: SessionStorage): Promise; export declare const TRANSCRIPT_CAPTURE_CHUNK_BYTES: number; declare class RecentSessionInfo { #private; readonly path: string; readonly mtime: number; constructor(path: string, mtime: number, header: Record, firstPrompt?: string); /** Display name. Falls back to a timestamp-based label, never the raw UUID. */ get fullName(): string; /** * Display name without an arbitrary length cap. The renderer is responsible for * width-aware truncation so adjacent fields (e.g. the relative time) stay visible. */ get name(): string; /** Human-readable relative time (e.g., "2 hours ago") */ get timeAgo(): string; } /** * Promote orphaned `.jsonl..bak` backups created by * `#replaceSessionFileAfterEperm` back to their primary path when the primary * is missing. This runs once per session-dir scan, before the main `*.jsonl` * glob, so a crash between the two renames in the EPERM-rewrite path does not * leave the user's last good state stranded outside the loader's view. * * Exported for testing. */ export declare function recoverOrphanedBackups(sessionDir: string, storage: SessionStorage): Promise; /** Exported for testing */ export declare function findMostRecentSession(sessionDir: string, storage?: SessionStorage): Promise; interface SessionMoveDirectoryHandle { sync(): Promise; close(): Promise; } export declare function syncSessionMoveDirectory(directory: string, platform?: NodeJS.Platform, openDirectory?: (directory: string) => Promise): Promise; declare const RESIDENT_BLOB_SENTINEL_KEY = "__gjcResidentBlob"; type ResidentBlobKind = "text" | "imageUrl" | "imageData"; interface ResidentBlobSentinel { [RESIDENT_BLOB_SENTINEL_KEY]: true; kind: ResidentBlobKind; ref: string; } export declare function residentBlobSentinelForTests(kind: ResidentBlobKind, ref: string): ResidentBlobSentinel; export declare function assertResidentReferencesResolvableForTests(entries: readonly FileEntry[], textStore: BlobStore, imageStore?: BlobStore, binding?: { sessionId?: string; sessionFile?: string; }): void; export declare function materializeResidentEntriesThrowingForTests(entries: T[], textStore: BlobStore, imageStore?: BlobStore, binding?: { sessionId?: string; sessionFile?: string; }): T[]; export declare function materializeResidentEntriesForPersistenceForTests(entries: T[], textStore: BlobStore, imageStore?: BlobStore): T[]; /** * Discover resumable transcripts intentionally stored inside a project's `.gjc`. * Runtime token/audit JSONL files are excluded by requiring a known transcript * container (`agent-session` or `sessions`). */ export declare function listProjectSessionTranscriptFiles(cwd: string): string[]; /** Get recent sessions for display in welcome screen */ export declare function getRecentSessions(sessionDir: string, limit?: number, storage?: SessionStorage): Promise; export declare function getRecentSessionDisplay(sessions: readonly SessionInfo[], limit?: number): Array<{ name: string; timeAgo: string; }>; /** * Manages conversation sessions as append-only trees stored in JSONL files. * * Each session entry has an id and parentId forming a tree structure. The "leaf" * pointer tracks the current position. Appending creates a child of the current leaf. * Branching moves the leaf to an earlier entry, allowing new branches without * modifying history. * * Use buildSessionContext() to get the resolved message list for the LLM, which * handles compaction summaries and follows the path from root to current leaf. */ export interface UsageStatistics { input: number; output: number; cacheRead: number; cacheWrite: number; premiumRequests: number; cost: number; } export interface ResolvedSessionMatch { session: SessionInfo; scope: "local" | "global"; } export declare function resolveResumableSession(sessionArg: string, cwd: string, sessionDir?: string, storage?: SessionStorage, managedAgentDir?: string): Promise; interface SessionManagerStateSnapshot { sessionId: string; sessionName: string | undefined; titleSource: "auto" | "user" | undefined; sessionFile: string | undefined; managedPersistExpectedIdentity: ManagedFileIdentity | undefined; flushed: boolean; ensuredOnDisk: boolean; needsFullRewriteOnNextPersist: boolean; fileEntries: FileEntry[]; materializedFileEntries: FileEntry[]; adoptedArtifactManager: ArtifactManager | null; coldRestoreFile?: string; } /** Benchmark-derived cap for strong materialized session snapshots. */ export declare const MATERIALIZED_CACHE_MAX_BYTES: number; /** Test-only cache retention and transition seams; these are intentionally not user settings. */ export declare const SessionManagerTestHooks: { materializedCacheMaxBytesOverride?: number; beforeResidentTransitionIndexBuild?: () => void; afterForkSnapshot?: () => void | Promise; afterForkTranscriptPublished?: () => void | Promise; beforeEphemeralArtifactManagerInstall?: (dir: string) => void | Promise; beforePersistPatchFence?: (attempt: number) => void; beforeStrictMissingCheck?: (filePath: string, storage: SessionStorage) => void; beforeManagedResumeAcceptance?: (filePath: string, storage: SessionStorage) => void; beforeManagedResumeReturn?: (filePath: string, storage: SessionStorage) => void; beforeManagedSourceStat?: (filePath: string, storage: SessionStorage) => void | Promise; beforeManagedMissingInit?: (filePath: string, storage: SessionStorage) => void | Promise; beforeManagedMissingPublish?: (filePath: string, storage: SessionStorage) => void | Promise; beforeManagedMissingReturn?: (filePath: string, storage: SessionStorage) => void | Promise; afterManagedMissingAssertion?: (filePath: string, storage: SessionStorage) => void | Promise; beforeManagedSwitchIdentity?: (filePath: string, storage: SessionStorage) => void | Promise; /** Internal first-open GC strategy override; omitted means current. */ firstOpenGcStrategy?: SessionMemoryGcStrategy; /** Internal first-open secondary-artifact mode override; omitted means auto. */ secondaryArtifactMode?: SessionMemorySecondaryArtifactMode; /** Test-only transcript threshold override for automatic routing. */ autoModeMinTranscriptBytesOverride?: number; /** Test-only eager hydration ceiling override. */ eagerHydrationMaxBytesOverride?: number; /** Test-only rolling-tail buffer override for tail-overflow coverage. */ sidecarTailBufferBytesOverride?: number; /** Test-only counter proving complete-index allocation was not used. */ readAllColdEntryIndexesCalls?: number; /** Test-only exact-reopen exception diagnostic. */ lastSidecarInitError?: string; /** Test-only generated-ID cache capacity override. */ coldIdHashMaxEntriesOverride?: number; /** Test-only session-context budget override (in-process; does not leak to subprocesses). */ sessionContextBudgetBytesOverride?: number; }; /** * Freshness snapshot captured with every async whole-session persistence * preparation. Immediately before the synchronous persistence transaction, the * live values are compared: a `sessionFile`/`lifecycleId` change aborts (lifecycle * switch); a revision change discards the prepared bytes and re-prepares (bounded), * so a stale snapshot is never published. */ export interface PersistenceInputToken { /** Canonical session file path the prepared bytes target. */ sessionFile: string; /** Per-manager lifecycle identity (`sessionId@sessionFile`); changes on switch/open/reset. */ lifecycleId: string; /** Revision of #fileEntries affecting persisted bytes. */ entryRevision: number; /** Header/version revision affecting persisted bytes. */ headerRevision: number; /** Resident/blob store revision affecting persisted bytes. */ residentBlobRevision: number; } export declare class SessionManager { #private; private cwd; private sessionDir; private readonly persist; private destination; private constructor(); /** * Snapshot of the five cache-invalidation revision domains (plan: Lane 1 * revision contract). Tests assert the invalidation mapping through this; * future export/label-view caches key off their respective domains. */ revisionSnapshot(): SessionManagerRevisionSnapshot; /** Puts a binary blob into the blob store and returns the blob reference */ putBlob(data: Buffer): Promise; /** Capture rollback authority without materializing an active cold transcript. @internal */ captureRollbackState(): Promise; /** Restore a rollback snapshot, reopening cold authority instead of hydrating it. @internal */ restoreRollbackState(snapshot: SessionManagerStateSnapshot): Promise; captureState(): SessionManagerStateSnapshot; restoreState(snapshot: SessionManagerStateSnapshot): void; /** Switch to a different session file (used for resume and branching). */ setSessionFile(sessionFile: string, options?: { deferEphemeralArtifactRetirement?: boolean; }): Promise; /** Start a new session. Closes any existing writer first. */ newSession(options?: NewSessionOptions): Promise; /** * Allocate a fresh successor without publishing it through the manager's public * getters. The returned authority is deliberately immutable so readiness work * can resolve local:// against the successor while the predecessor stays live. * @internal */ prepareNewSession(options?: NewSessionOptions): Promise; /** Append a model selection to an unpublished successor. @internal */ appendPreparedModelChange(prepared: PreparedNewSession, model: string): string; /** Append a thinking-level selection to an unpublished successor. @internal */ appendPreparedThinkingLevelChange(prepared: PreparedNewSession, thinkingLevel?: string): string; /** Append a service-tier selection to an unpublished successor. @internal */ appendPreparedServiceTierChange(prepared: PreparedNewSession, serviceTier: ServiceTier | null): string; /** Append a displayable custom message to an unpublished successor. @internal */ appendPreparedCustomMessageEntry(prepared: PreparedNewSession, customType: string, content: string | (TextContent | ImageContent)[], display: boolean, details?: T, attribution?: MessageAttribution): string; /** Persist an unpublished successor without adopting it. @internal */ ensurePreparedNewSessionOnDisk(prepared: PreparedNewSession): Promise; /** Build context from an unpublished successor without reading active manager state. @internal */ buildPreparedNewSessionContext(prepared: PreparedNewSession): SessionContext; /** Prepare a forked successor without publishing it through public manager state. @internal */ prepareFork(): Promise; /** Prepare a path-only branch successor without publishing it through public manager state. @internal */ prepareBranchedSession(leafId: string): Promise; /** Publish a prepared successor synchronously after all readiness awaits succeed. @internal */ commitPreparedNewSession(prepared: PreparedNewSession): void; /** Exact-discard only an uncommitted successor prepared by this manager. @internal */ discardPreparedNewSession(prepared: PreparedNewSession): Promise; /** Tombstone and exact-delete managed transcripts, detaching the active transcript first. */ dropSession(sessionPath: string): Promise; /** * Exact-delete a session transcript and its artifacts by path WITHOUT requiring * managed logical authorization. This is only for discarding an UNCOMMITTED * successor that a transaction (e.g. handoff) created via `newSession()` but * never durably authorized — such a session has no managed candidate listing * yet, so `dropSession` would refuse it and leak its artifact root. Bounded to * this manager's configured session root and refuses to touch the active session; * tolerates missing files. */ discardUncommittedSession(sessionPath: string): Promise; /** * Fork the current session, creating a new session file with the same entries. * Returns both the old and new session file paths for artifact copying. * @returns { oldSessionFile, newSessionFile } or undefined if not persisting */ fork(): Promise<{ oldSessionFile: string; newSessionFile: string; } | undefined>; copyArtifactsForFork(oldSessionFile: string, newSessionFile: string): Promise; /** * Serialize every cwd transition (model, TUI, SDK/ACP). Re-entry is allowed * only for the async context that already owns the lock — unrelated callers * queue on the tail instead of skipping it. */ runExclusiveCwdTransition(fn: () => Promise): Promise; /** * Run `fn` under a shared read lease on `cwd`. * * Tools that resolve relative paths against the session cwd must hold this * lease across their WHOLE execution, not merely re-check a generation before * they start: the check-then-yield shape lets a move commit inside the tool's * first `await`, so a command admitted for root A would execute in root B. * Writers wait for outstanding leases to drain, so the cwd observed at lease * acquisition stays authoritative until the lease is released. */ runWithCwdReadLease(fn: () => Promise): Promise; /** Wait for any in-flight exclusive cwd transition to settle. */ joinCwdTransition(): Promise; getCwdGeneration(): number; static openNoFollowDirectory(dir: string): Promise; /** * Claim process-cwd authority for `manager` when it is unowned or the prior * owner has been collected. Returns whether `manager` holds the claim. */ static claimProcessCwdOwnership(manager: SessionManager): boolean; static isProcessCwdOwner(manager: SessionManager): boolean; static releaseProcessCwdOwnership(manager: SessionManager): void; /** * Verify that `process.cwd()` is the directory pinned by `expectedIdentity`. * * `process.chdir` resolves a NAME, so a path replaced after the last * name-based comparison lands the process outside the validated directory — * the exact confinement `move_session` exists to enforce. Node exposes no * `fchdir`, so the handle cannot be the chdir authority directly; comparing * the resulting cwd's identity to the pinned handle closes the same gap. */ static assertProcessCwdIdentity(expectedIdentity: { dev: bigint; ino: bigint; }): Promise; /** * Move the session to a new working directory. * Moves session files and artifacts on disk, updates all internal references, * and rewrites the session header with the new cwd. * * All callers (model `move_session`, TUI `/move`, SDK/ACP `session.cwd.move`) * share this exclusive transition so concurrent moves cannot interleave. */ moveTo(newCwd: string, options?: { expectedIdentity?: { dev: bigint; ino: bigint; }; targetHandle?: { stat: (opts: { bigint: true; }) => Promise; }; }): Promise; isPersisted(): boolean; stageDefaultModelSelection(model: string, thinkingLevel: string | undefined, options?: { readonly appendThinkingLevel: boolean; }): Promise; promoteDefaultModelSelection(stage: DefaultModelSelectionStage): DefaultModelSelectionPromotion; discardDefaultModelSelectionStage(stage: DefaultModelSelectionStage): Promise; /** * Force-persist all current entries to disk, even when no assistant message exists yet. * Used by ACP mode where session/new must create a discoverable session immediately. */ ensureOnDisk(): Promise; /** Flush pending writes to disk. Call before switching sessions or on shutdown. */ flush(): Promise; /** Close the persistent writer after flushing all pending data. */ close(): Promise; /** Flush while open, then strictly close; retryable close skips the invalid second flush. */ flushAndCloseStrict(): Promise; /** * Strictly flush and close the persist writer, returning the certainty-aware close * outcome without manufacturing success. The existing {@link close} path is * preserved for best-effort callers; this seam lets strict ACP disposal prove * writer closure before any destructive operation. */ closeStrict(): Promise; getCwd(): string; /** Get usage statistics across all assistant messages in the session. */ getUsageStatistics(): UsageStatistics; getSessionDir(): string; /** Lists picker candidates within this manager's captured destination authority. */ listForResumePickerReadOnly(): Promise; getSessionId(): string; getSessionFile(): string | undefined; /** * On-disk transcript file size in bytes. Returns 0 when the file is * unavailable, unreadable, or no session file is set. The managed-storage * path reads through the descriptor (no full-file scan); the plain-file * path uses statSync. */ getTranscriptFileBytes(): number; getSessionMemoryStats(): SessionMemoryStats; setSessionMemoryMode(mode: SessionMemoryMode): void; acquireMemoryGuardParticipantIngressLease(): MemoryGuardParticipantIngressLease; createMemoryGuardCheckpoint(input: MemoryGuardCreateCheckpointInput): Promise; /** * Returns the session artifacts directory path (session file path without .jsonl). * Returns null when the session is not persisted to a file. * When this session has adopted an external ArtifactManager (subagent case), * never exposes that managed directory as a pathname. Reads and writes use the * adopted manager capability directly. */ getArtifactsDir(): string | null; isManagedDestination(): boolean; /** Retain the verified destination contract for bounded forks. @internal */ getDestinationForFork(): SessionDestination; /** Supplies opaque retained authority for mandatory managed legacy local migration. */ getManagedLegacyLocalMigrationSource(): ManagedLegacyLocalMigrationSource | null; /** * Adopt an externally-owned ArtifactManager. Used by subagents to share * the parent session's artifact directory and ID counter. */ adoptArtifactManager(manager: ArtifactManager, parent?: ArtifactManager): void; /** Release only the matching externally adopted manager. */ releaseArtifactManager(manager: ArtifactManager): void; /** Temporarily release adopted authority while an outer transition validates its successor. */ stageAdoptedArtifactManagerForTransition(): void; /** Prove manager authority by exact object identity, never by pathname shape. */ isArtifactManagerAuthorized(manager: ArtifactManager): boolean; /** * Returns the ArtifactManager this session writes through. Lazily creates * one bound to the current session file unless an external manager was * adopted via `adoptArtifactManager`. Falls back to the lazily created * ephemeral filesystem store once a non-persistent session has saved an * artifact, so `artifact://` stays resolvable. Returns null only when no * store has been established yet. */ getArtifactManager(): ArtifactManager | null; /** Linearizably establish this session's persistent or ephemeral artifact manager. */ ensureArtifactManager(): Promise; /** * Allocate a new artifact path and ID for the current session. * Returns an empty object when the session is not persisted. */ allocateArtifactPath(toolType: string): Promise<{ id?: string; path?: string; }>; /** * Save artifact content under the current session and return artifact ID. * Persistent sessions write into the session artifact directory; non-persistent * sessions write into a lazily created temporary directory so the content is * read back from the filesystem instead of being retained in memory. */ saveArtifact(content: string, toolType: string): Promise; /** Inspect an evicted artifact using a bounded range read; never rehydrates by default. */ inspectEvictedToolOutput(handle: unknown, range?: { start?: number; endExclusive?: number; }): Promise<{ outcome: "saved" | "unavailable" | "failed"; text?: string; diagnostic?: string; }>; /** Explicit full rehydration operation; callers must opt into materialization. */ rehydrateToolResultMessage(handle: unknown): Promise; /** * Resolve an artifact ID to an on-disk path for the current session. * Returns null when the artifact is missing. */ getArtifactPath(id: string): Promise; /** Retire predecessor ephemeral artifacts after an outer logical transition commits. */ retireEphemeralArtifactsAfterTransition(): void; /** * Persist (or clear) the current editor draft so the next resume of this * session can restore it. Empty text deletes any stale draft. No-op when the * session is not persisted. */ saveDraft(text: string): Promise; /** * Read and remove the saved draft. Returns the previously-saved text, or * null when no draft is pending. Single-shot: a successful read removes the * sidecar so a subsequent resume does not re-restore the same text. */ consumeDraft(): Promise; /** The source that set the session name: "user" (manual /rename or RPC) or "auto" (generated title). */ get titleSource(): "auto" | "user" | undefined; getSessionName(): string | undefined; setSessionName(name: string, source?: "auto" | "user"): Promise; _persist(entry: SessionEntry): void; /** Append a configured fallback chain as child of current leaf, then advance leaf. Returns entry id. */ appendConfiguredModelChain(chain: ConfiguredModelChain): string; /** Append a message as child of current leaf, then advance leaf. Returns entry id. * Does not allow writing CompactionSummaryMessage and BranchSummaryMessage directly. * Reason: we want these to be top-level entries in the session, not message session entries, * so it is easier to find them. * These need to be appended via appendCompaction() and appendBranchSummary() methods. */ appendMessage(message: Message | CustomMessage | HookMessage | BashExecutionMessage | PythonExecutionMessage | FileMentionMessage): string; /** Append a thinking level change as child of current leaf, then advance leaf. Returns entry id. */ appendThinkingLevelChange(thinkingLevel?: string, operatorIntent?: boolean): string; appendServiceTierChange(serviceTier: ServiceTier | null): string; /** Append a mode change as child of current leaf, then advance leaf. Returns entry id. */ appendModeChange(mode: string, data?: Record): string; /** * Append a model change as child of current leaf, then advance leaf. Returns entry id. * @param model Model in "provider/modelId" format * @param role Optional role (default: "default") */ appendModelChange(model: string, role?: string, metadata?: { previousModel?: string; reason?: string; thinkingLevel?: string | null; }): string; /** Append an explicit role-model clear marker, preserving absence during replay. */ clearModelRole(role: string): string; /** Append session init metadata (for subagent debugging/replay). Returns entry id. */ appendSessionInit(init: { systemPrompt: string; task: string; tools: string[]; outputSchema?: unknown; forkContext?: unknown; }): string; /** Append a compaction summary as child of current leaf, then advance leaf. Returns entry id. */ appendCompaction(summary: string, shortSummary: string | undefined, firstKeptEntryId: string, tokensBefore: number, details?: T, fromExtension?: boolean, preserveData?: Record): string; /** Append a custom entry (for extensions) as child of current leaf, then advance leaf. Returns entry id. */ appendCustomEntry(customType: string, data?: unknown): string; /** * Append a root marker that starts a fresh active branch without changing the * session id or deleting earlier durable entries. Subsequent messages descend * from this marker, so provider context is clear while history remains * available for diagnostics/export. */ appendContextClearEntry(data?: Record): string; /** * Write mutated message entries back into the canonical entry store by id. * * `getBranch()` materializes resident-blob entries into copies, so in-place * mutation of returned entries (e.g. pruning tool outputs) does not affect * the canonical store. This applies such mutations for real. */ applyEntryMessageUpdates(entries: readonly SessionMessageEntry[]): void; /** Write mutated custom-message entries back into the canonical entry store by id. */ applyCustomMessageEntryUpdates(entries: readonly CustomMessageEntry[], options?: { preserveEvictedContent?: boolean; }): void; /** * Rehydrate the canonical transcript after a synchronous persistence failure. * * The failed append may have committed before reporting an uncertain outcome, * so callers must not clear the sticky error or retry against the resident * branch. Reloading the exact session file is the only supported recovery * boundary for both managed and explicit persistent destinations. */ recoverPersistenceFailure(): Promise; /** * Rewrite the session file after in-place entry updates. * Use sparingly (e.g., pruning old tool outputs). */ rewriteEntries(): Promise; /** Remap artifact references in an unpublished candidate before its publication fence. */ remapStagedArtifactReferences(idMap: ReadonlyMap): Promise; /** * Append a custom message entry (for extensions) that participates in LLM context. * @param customType Hook identifier for filtering on reload * @param content Message content (string or TextContent/ImageContent array) * @param display Whether to show in TUI (true = styled display, false = hidden) * @param details Optional extension-specific metadata (not sent to LLM) * @param attribution Who initiated this message for billing/attribution semantics * @returns Entry id */ appendCustomMessageEntry(customType: string, content: string | (TextContent | ImageContent)[], display: boolean, details?: T, attribution?: MessageAttribution, observationId?: string): string; /** Append MCP discovery selection authority without altering discovered built-in authority. */ appendMCPToolSelection(selectedToolNames: string[], mutationCorrelationId?: string): string; /** Append discovered built-in selection authority without altering MCP authority. */ appendDiscoveredBuiltinToolSelection(selectedToolNames: string[], mutationCorrelationId?: string): string; /** * Append a TTSR injection entry recording which rules were injected. * @param ruleNames Names of rules that were injected * @returns Entry id */ appendTtsrInjection(ruleNames: string[], records?: TtsrInjectionRecord[], ttsrMessageCount?: number): string; /** * Get all unique TTSR rule names that have been injected in the current branch. * Scans from root to current leaf for ttsr_injection entries. */ getInjectedTtsrRules(): string[]; getLeafId(): string | null; getLeafEntry(): SessionEntry | undefined; getResidentImageBytes(): number; /** * Get the most recent model role from the current session path. * Returns undefined if no model change has been recorded. * * R1: keyed ONLY on the nearest `model_change` on the leaf→root path — never on * `hasExplicitDefaultModel`, which gates only legacy assistant inference into * `models.default` inside `buildSessionContext`. Six parity cases (D1): reviewer- * only → "reviewer"; temporary-only → "temporary"; interleaved → nearest; * no model_change → undefined; legacy-only inference → undefined; explicit * default then legacy inference → "default". */ getLastModelChangeRole(): string | undefined; evictCompactedContent(firstKeptEntryId: string, compactionEntryId: string): EvictCompactedContentResult; getObservabilityStatsForTests(): SessionManagerObservabilityStats; /** * Directory backing the resident *text* blob store, or undefined when the * store is in-memory. The resident-cache root also holds the managed sidecar * cache instance, so callers must not infer the text store from directory * counts. */ residentTextCacheDirForTests(): string | undefined; setSidecarHotSuffixBudgetForTests(bytes: number): void; parentChildrenCacheKeysForTests(): string[]; parentArtifactEnabledForTests(): boolean; setParentArtifactBudgetForTests(bytes: number): void; hotRetainedMessageCharsForTests(): number; getCanonicalEntryForTests(id: string): SessionEntry | undefined; getEntryForFidelity(id: string): SessionEntry | undefined; getBranchForFidelity(fromId?: string): SessionEntry[]; /** * Walk the active branch without materializing resident blobs or rehydrating * cold-spill payloads. Intended for metadata-only scans such as todo-phase * sync; callers must not mutate returned entries. */ getActivePathEntriesCanonical(fromId?: string): SessionEntry[]; visitEntriesForExport(visitor: (entry: SessionEntry) => void): void; getEntriesForExport(): SessionEntry[]; getEntry(id: string): SessionEntry | undefined; /** * Get all direct children of an entry. */ getChildren(parentId: string): SessionEntry[]; /** * Get the label for an entry, if any. */ getLabel(id: string): string | undefined; /** * Set or clear a label on an entry. * Labels are user-defined markers for bookmarking/navigation. * Pass undefined or empty string to clear the label. */ appendLabelChange(targetId: string, label: string | undefined): string; /** * Walk from entry to root, returning all entries in path order. * Includes all entry types (messages, compaction, model changes, etc.). * Use buildSessionContext() to get the resolved messages for the LLM. */ getBranch(fromId?: string): SessionEntry[]; /** * Build the session context (what gets sent to the LLM). * Uses tree traversal from current leaf. */ /** * Return a defensive context snapshot for public consumers. */ buildSessionContext(): SessionContext; /** Strip stale OpenAI Responses assistant replay metadata from loaded in-memory entries without persisting it. */ sanitizeLoadedOpenAIResponsesReplayMetadata(): boolean; /** * Get session header. */ getHeader(): SessionHeader | null; /** Whether this manager contains persisted history without forcing cold hydration. */ hasHistoryEntries(): boolean; getEntries(): SessionEntry[]; /** * Get the session as a tree structure. Returns defensive copies of all entries. * A well-formed session has exactly one root (first entry with parentId === null). * Orphaned entries (broken parent chain) are also returned as roots. */ getTree(): SessionTreeNode[]; [sessionManagerReadCapability](): SessionManagerReadAccess; /** * Start a new branch from an earlier entry. * Moves the leaf pointer to the specified entry. The next appendXXX() call * will create a child of that entry, forming a new branch. Existing entries * are not modified or deleted. */ branch(branchFromId: string): void; /** * Reset the leaf pointer to null (before any entries). * The next appendXXX() call will create a new root entry (parentId = null). * Use this when navigating to re-edit the first user message. */ resetLeaf(): void; /** * Start a new branch with a summary of the abandoned path. * Same as branch(), but also appends a branch_summary entry that captures * context from the abandoned conversation path. */ branchWithSummary(branchFromId: string | null, summary: string, details?: unknown, fromExtension?: boolean): string; /** * Create a new session file containing only the path from root to the specified leaf. * Useful for extracting a single conversation path from a branched session. * Returns the new session file path, or undefined if not persisting. */ createBranchedSession(leafId: string): string | undefined; /** * Resolve the canonical default session directory for a cwd. */ static getDefaultSessionDir(cwd: string, agentDir?: string, storage?: SessionStorage): string; /** Resolve the default session directory without creating or migrating storage. */ static getDefaultSessionDirReadOnly(cwd: string, agentDir?: string): string; /** * Create a new session. * @param cwd Working directory (stored in session header) * @param sessionDir Optional session directory. If omitted, uses default (~/.gjc/agent/sessions//). */ static nestedManagedDestination(authority: ManagedDirectoryRoot | ManagedSessionDescendantStore, directory: string): SessionDestination; static managedDestination(cwd: string, agentDir?: string, storage?: SessionStorage): SessionDestination; static explicitDestination(directory: string): SessionDestination; static create(cwd: string, destinationInput?: SessionDestinationInput, storage?: SessionStorage): SessionManager; /** Prepare a selected candidate using this manager's captured managed destination authority. */ prepareManagedCandidateForWrite(filePath: string, migrationPolicy: SessionDirectoryMigrationPolicy, expectedIdentity?: ResumeSessionIdentity): Promise; /** Prepare a managed candidate and retain its exact post-preparation identity for the next adoption. */ prepareManagedCandidateForStrictAdoption(filePath: string, migrationPolicy: SessionDirectoryMigrationPolicy, expectedIdentity: ResumeSessionIdentity): Promise; /** Resolve a default-managed candidate through binding validation and copy-retain migration before mutation. */ static prepareManagedCandidateForWrite(filePath: string, migrationPolicy: SessionDirectoryMigrationPolicy, destination: SessionDestination, expectedIdentity?: ResumeSessionIdentity): Promise; /** * Fork a session into the current project directory. * Copies history from another session file while creating a new session file in the current sessionDir. */ static forkFrom(sourcePath: string, cwd: string, destinationInput?: SessionDestinationInput, storage?: SessionStorage, migrationPolicy?: SessionDirectoryMigrationPolicy, sessionMemoryMode?: SessionMemoryMode): Promise; /** * Open a specific session file. * @param path Path to session file * @param sessionDir Optional session directory for /new or /branch. If omitted, derives from file's parent. */ /** Open an unpublished candidate transcript below the reserved staging directory. */ static openStaged(finalSessionFile: string, storage?: SessionStorage, attemptId?: string): Promise; /** Managed-authority variant of {@link openStaged}. */ static stagedNestedManaged(finalSessionFile: string, destination: SessionDestination, store: ManagedSessionDescendantStore, storage?: SessionStorage, attemptId?: string): Promise; /** Publish the candidate-owned staged transcript and artifacts at the real accept fence. */ static openStagedNestedManaged(finalSessionFile: string, destination: SessionDestination, store: ManagedSessionDescendantStore, storage?: SessionStorage, attemptId?: string): Promise; commitStaged(options?: { deferArtifactFinalize?: boolean; }): Promise; /** Finalize a staged publication whose post-fence publisher completed successfully. */ finalizeStagedCommit(): void; /** Roll back a staged publication when post-fence visibility setup fails. */ rollbackCommittedStaged(): Promise; /** Refresh the owned final snapshot after post-fence session metadata is appended. */ refreshStagedCommitSnapshot(): Promise; /** Idempotently remove an unpublished staged transcript and its owned artifacts. */ discardStaged(): Promise; commitStagedNestedManaged(): Promise; discardStagedNestedManaged(): Promise; static open(filePath: string, destinationInput?: SessionDestinationInput, storage?: SessionStorage, migrationPolicy?: SessionDirectoryMigrationPolicy, sessionMemoryMode?: SessionMemoryMode): Promise; static openNestedManaged(filePath: string, destination: SessionDestination, store: ManagedSessionDescendantStore, storage?: SessionStorage, cwdOverride?: string, sessionMemoryMode?: SessionMemoryMode): Promise; /** * List default-managed sessions for the resume picker without recovery or other * maintenance writes. This is the only picker inventory that includes legacy * sibling directories. */ static listManagedForResumePickerReadOnly(cwd: string, managedAgentDir?: string, storage?: SessionStorage): Promise; /** * List sessions from an explicitly supplied picker directory without recovery * or other maintenance writes. Unlike managed inventory, this never scans * legacy sibling directories. */ static listForResumePickerReadOnly(cwd: string, sessionDir?: string, storage?: SessionStorage): Promise; /** Delete an authorized managed or project-local picker candidate. */ static deleteManagedCandidate(sessionPath: string): Promise; /** Capture exact source content for a strict fork without granting write ownership. */ static captureTranscriptStrict(filePath: string, storage?: SessionStorage): StrictSessionCaptureResult; /** * Fork strictly from captured source bytes. The source pathname is used only to * revalidate captured authority before destination initialization and transcript * persistence; destination history always comes from the captured bytes. */ static forkFromCaptured(snapshot: CapturedSessionTranscriptSnapshot, cwd: string, destinationInput?: SessionDestinationInput, _migrationPolicy?: SessionDirectoryMigrationPolicy, sessionMemoryMode?: SessionMemoryMode): Promise; static restoreMemoryGuardCheckpoint(input: MemoryGuardRestoreInput): Promise; /** Inspect a selected session without acquiring write-capable ownership. */ static inspectSessionTailReadOnly(filePath: string, storage?: SessionStorage): Promise; /** * Hydrate an existing predecessor transcript for recovery without taking any * write-capable action. The caller must retain the returned context and use * the explicit promotion seam only after its ownership-ready fence and writer * lease are durable. */ static openExistingForRecoveryHydrationStrict(identity: ResumeSessionIdentity, destinationInput?: SessionDestinationInput, storage?: SessionStorage): Promise; /** * Allows the normal post-open metadata sanitation only after the caller has * fsynced its external ownership-ready fence and acquired the writer lease. */ promoteRecoveryHydrationAfterOwnershipReadyFence(context: RecoveryHydrationContext, fence: RecoveryHydrationPromotionFence): Promise; /** * Main startup code MUST retain the consented inspection identity and branch on * the returned discriminant; an error result never creates, rewrites, or adopts * the selected path. Breadcrumb ownership begins only after `kind: "opened"`. */ static openExistingStrict(identity: ResumeSessionIdentity, destinationInput?: SessionDestinationInput, storage?: SessionStorage, migrationPolicy?: SessionDirectoryMigrationPolicy, sessionMemoryMode?: SessionMemoryMode): Promise; /** * Continue the most recent session, or create new if none. * @param cwd Working directory * @param sessionDir Optional session directory. If omitted, uses default (~/.gjc/agent/sessions//). */ static continueRecent(cwd: string, destinationInput?: SessionDestinationInput, storage?: SessionStorage, migrationPolicy?: SessionDirectoryMigrationPolicy, sessionMemoryMode?: SessionMemoryMode): Promise; /** Create an in-memory session (no file persistence) */ static inMemory(cwd?: string, storage?: SessionStorage): SessionManager; /** * List all sessions. * @param cwd Working directory (used to compute default session directory) * @param sessionDir Optional session directory. If omitted, uses default (~/.gjc/agent/sessions//). */ static list(cwd: string, sessionDir?: string, storage?: SessionStorage): Promise; /** * List all sessions across all project directories. */ static listAll(storage?: SessionStorage, managedAgentDir?: string): Promise; /** * Strict inventory bound to this manager's captured session authority. Managed * managers include authorized legacy and current candidates; explicit managers * authorize only their exact directory. */ inventorySessionsStrict(): StrictInventoryResult; /** * Strict raw scoped inventory for ACP authorization. Enumerates the scoped * session directory without suppressing any root/scan/lstat/read/parse/stat/ * header/cwd/containment/identity failure. A failure result carries every * sanitized failure and grants zero authority — it is never reduced to a * partial candidate set. Display/global {@link list} behavior is unaffected. */ static inventorySessionsStrict(cwd: string, options?: { sessionDir?: string; storage?: SessionStorage; destination?: SessionDestination; }): StrictInventoryResult; /** * Propagate the storage-layer verified hard delete bound to exact identity evidence. * Never performs ID lookup or first-match selection; the caller supplies the exact * authorization target captured from a complete strict inventory. */ deleteSessionVerified(target: VerifiedSessionDeleteTarget): Promise; } export {};