@vib-rato/agent-core 0.16.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (69) hide show
  1. package/CHANGELOG.md +852 -0
  2. package/README.md +493 -0
  3. package/dist/types/agent-loop.d.ts +229 -0
  4. package/dist/types/agent.d.ts +533 -0
  5. package/dist/types/append-only-context.d.ts +141 -0
  6. package/dist/types/attempt-scope.d.ts +84 -0
  7. package/dist/types/compaction/adaptive.d.ts +31 -0
  8. package/dist/types/compaction/branch-summarization.d.ts +103 -0
  9. package/dist/types/compaction/compaction.d.ts +330 -0
  10. package/dist/types/compaction/entries.d.ts +124 -0
  11. package/dist/types/compaction/errors.d.ts +26 -0
  12. package/dist/types/compaction/index.d.ts +12 -0
  13. package/dist/types/compaction/messages.d.ts +61 -0
  14. package/dist/types/compaction/openai.d.ts +65 -0
  15. package/dist/types/compaction/pruning.d.ts +130 -0
  16. package/dist/types/compaction/utils.d.ts +32 -0
  17. package/dist/types/compaction.d.ts +1 -0
  18. package/dist/types/harmony-leak.d.ts +100 -0
  19. package/dist/types/heap-eviction-retainers.test.d.ts +1 -0
  20. package/dist/types/image-placeholder-guard.d.ts +4 -0
  21. package/dist/types/index.d.ts +13 -0
  22. package/dist/types/proxy.d.ts +95 -0
  23. package/dist/types/run-collector.d.ts +223 -0
  24. package/dist/types/run-resource-ledger.d.ts +2 -0
  25. package/dist/types/telemetry.d.ts +605 -0
  26. package/dist/types/thinking.d.ts +18 -0
  27. package/dist/types/tool-dispatch-identity.d.ts +27 -0
  28. package/dist/types/types.d.ts +790 -0
  29. package/package.json +72 -0
  30. package/src/agent-loop.ts +5632 -0
  31. package/src/agent.ts +2437 -0
  32. package/src/append-only-context.ts +496 -0
  33. package/src/attempt-scope.ts +195 -0
  34. package/src/compaction/adaptive.ts +92 -0
  35. package/src/compaction/branch-summarization.ts +358 -0
  36. package/src/compaction/compaction.ts +1569 -0
  37. package/src/compaction/entries.ts +158 -0
  38. package/src/compaction/errors.ts +31 -0
  39. package/src/compaction/index.ts +13 -0
  40. package/src/compaction/messages.ts +212 -0
  41. package/src/compaction/openai.ts +580 -0
  42. package/src/compaction/prompts/auto-handoff-threshold-focus.md +1 -0
  43. package/src/compaction/prompts/branch-summary-context.md +5 -0
  44. package/src/compaction/prompts/branch-summary-preamble.md +2 -0
  45. package/src/compaction/prompts/branch-summary.md +30 -0
  46. package/src/compaction/prompts/compaction-short-summary.md +9 -0
  47. package/src/compaction/prompts/compaction-summary-context.md +5 -0
  48. package/src/compaction/prompts/compaction-summary.md +38 -0
  49. package/src/compaction/prompts/compaction-turn-prefix.md +17 -0
  50. package/src/compaction/prompts/compaction-update-summary.md +45 -0
  51. package/src/compaction/prompts/file-operations.md +10 -0
  52. package/src/compaction/prompts/handoff-document.md +56 -0
  53. package/src/compaction/prompts/summarization-system.md +3 -0
  54. package/src/compaction/pruning.ts +1026 -0
  55. package/src/compaction/utils.ts +189 -0
  56. package/src/compaction.ts +1 -0
  57. package/src/harmony-leak.ts +457 -0
  58. package/src/heap-eviction-retainers.test.ts +293 -0
  59. package/src/image-placeholder-guard.ts +20 -0
  60. package/src/index.ts +23 -0
  61. package/src/prompts/escaped-nonascii-recovery.md +3 -0
  62. package/src/prompts/repeated-tool-failure-recovery.md +1 -0
  63. package/src/proxy.ts +408 -0
  64. package/src/run-collector.ts +728 -0
  65. package/src/run-resource-ledger.ts +345 -0
  66. package/src/telemetry.ts +2161 -0
  67. package/src/thinking.ts +20 -0
  68. package/src/tool-dispatch-identity.ts +87 -0
  69. package/src/types.ts +882 -0
@@ -0,0 +1,84 @@
1
+ /**
2
+ * Per-attempt scope identity for request-scoped execution attribution.
3
+ *
4
+ * An AttemptScope is an immutable, frozen value allocated before every
5
+ * observable lifecycle emission for a single provider/agent attempt.
6
+ * It carries a stable `attemptId`, a monotonic `generation` (per-lineage),
7
+ * and a `lineage` discriminator that distinguishes the main attempt from
8
+ * concurrent side attempts (IRC background, ephemeral/btw turns).
9
+ *
10
+ * The `attemptId` + `generation` + `lineage` form the comparable identity.
11
+ * AttemptScope is structurally assignable to AttemptScopeRef in
12
+ * `packages/ai` so it can be carried through `SimpleStreamOptions` and
13
+ * provider hook signatures without a reverse dependency.
14
+ */
15
+ export type AttemptLineage = "main" | `side:${string}`;
16
+ export interface AttemptScope {
17
+ readonly attemptId: string;
18
+ readonly generation: number;
19
+ readonly lineage: AttemptLineage;
20
+ }
21
+ export declare function attemptScopesEqual(a: AttemptScope, b: AttemptScope): boolean;
22
+ /**
23
+ * Per-lineage currentness authority. Main and side attempts have separate
24
+ * instances so a side attempt never invalidates the main scope, and
25
+ * `forceAbort` advances only the main lineage.
26
+ */
27
+ export interface LineageCurrentness {
28
+ readonly lineage: AttemptLineage;
29
+ /** True iff no successor scope with a greater generation was allocated in this lineage. */
30
+ isCurrent(scope: AttemptScope): boolean;
31
+ /** Allocate the next generation for the given attempt identity in this lineage. */
32
+ advance(attemptId: string): number;
33
+ /** Allocate the next generation in this lineage. */
34
+ /** Current generation value for this lineage. */
35
+ readonly current: number;
36
+ }
37
+ export declare function createLineageCurrentness(lineage: AttemptLineage): LineageCurrentness;
38
+ /**
39
+ * Agent-owned authority over all attempt lineages. Owns the main lineage;
40
+ * side lineages are registered/removed with bounded lifecycle.
41
+ *
42
+ * This is the SINGLE source of currentness truth injected into
43
+ * AttemptRecordStore (packages/coding-agent). Every store operation
44
+ * calls `authority.isCurrent(scope)` and fails closed when the authority
45
+ * is missing or the scope is superseded.
46
+ */
47
+ export interface AttemptScopeAuthority {
48
+ /** Register a side-lineage authority. Returns an unregister function. */
49
+ registerSide(lineage: AttemptLineage, auth: LineageCurrentness): () => void;
50
+ /** True iff the scope's lineage is known and its generation is current. */
51
+ isCurrent(scope: AttemptScope): boolean;
52
+ /** Advance the main lineage (called by forceAbort). Returns the new generation. */
53
+ advanceMain(): number;
54
+ /** Mint the next main-lineage scope. */
55
+ mintMain(): AttemptScope;
56
+ /**
57
+ * Atomically register a fresh side lineage, mint a side scope, and return
58
+ * both the scope and a dispose function. The authority knows the lineage
59
+ * BEFORE the scope is returned, so `isCurrent` succeeds immediately.
60
+ */
61
+ mintSide(): {
62
+ scope: AttemptScope;
63
+ dispose: () => void;
64
+ };
65
+ }
66
+ export interface AttemptMinter {
67
+ mint(lineage: AttemptLineage): AttemptScope;
68
+ }
69
+ export declare function createAttemptMinter(): AttemptMinter;
70
+ /**
71
+ * Create the Agent-owned authority. Owns the main lineage and a bounded
72
+ * (LRU-capped) map of side lineages. Only RETIRED side authorities are
73
+ * eligible for LRU eviction; a live side attempt is never silently
74
+ * invalidated by a newer side registration.
75
+ */
76
+ export declare function createAttemptScopeAuthority(): AttemptScopeAuthority;
77
+ /**
78
+ * Immutable per-run attempt handle, carried through terminal/finalizer paths.
79
+ * Keyed by logicalRunId in the Agent's `#runHandles` map.
80
+ */
81
+ export interface AttemptRunHandle {
82
+ readonly logicalRunId: number | import("./types.js").ManagedLogicalRunId;
83
+ readonly scope: AttemptScope;
84
+ }
@@ -0,0 +1,31 @@
1
+ export interface AdaptiveCompactionState {
2
+ turnsSinceCompact: number;
3
+ callsInWindow: number;
4
+ windowStart: number;
5
+ lastContextTokens: number;
6
+ lastCompactContextTokens: number | null;
7
+ lastCompactTs: number | null;
8
+ }
9
+ export interface AdaptiveCompactionDecisionState {
10
+ turnsSinceCompact: number;
11
+ callsInWindow: number;
12
+ lastContextTokens?: number;
13
+ }
14
+ export interface AdaptiveCompactionOptions {
15
+ enabled: boolean;
16
+ turnWindow: number;
17
+ baseThresholdPercent: number;
18
+ aggression: number;
19
+ minThresholdPercent?: number;
20
+ }
21
+ export declare class AdaptiveCompactionTracker {
22
+ #private;
23
+ windowMs: number;
24
+ constructor(windowMs?: number, now?: number);
25
+ setWindowMs(windowMs: number, now?: number): void;
26
+ reset(now?: number): void;
27
+ recordCall(contextTokens: number, now?: number): void;
28
+ recordCompact(contextTokens: number, now?: number): void;
29
+ snapshot(): AdaptiveCompactionState;
30
+ decisionState(): AdaptiveCompactionDecisionState;
31
+ }
@@ -0,0 +1,103 @@
1
+ /**
2
+ * Branch summarization for tree navigation.
3
+ *
4
+ * When navigating to a different point in the session tree, this generates
5
+ * a summary of the branch being left so context isn't lost.
6
+ */
7
+ import type { Model, ProviderSessionState } from "@vib-rato/ai";
8
+ import { type AgentTelemetry } from "../telemetry";
9
+ import type { AgentMessage } from "../types";
10
+ import type { ReadonlySessionManager, SessionEntry } from "./entries";
11
+ import { type ConvertToLlm } from "./messages";
12
+ import { type FileOperations } from "./utils";
13
+ export interface BranchSummaryResult {
14
+ summary?: string;
15
+ readFiles?: string[];
16
+ modifiedFiles?: string[];
17
+ aborted?: boolean;
18
+ error?: string;
19
+ }
20
+ /** Details stored in BranchSummaryEntry.details for file tracking */
21
+ export interface BranchSummaryDetails {
22
+ readFiles: string[];
23
+ modifiedFiles: string[];
24
+ }
25
+ export type { FileOperations } from "./utils";
26
+ export interface BranchPreparation {
27
+ /** Messages extracted for summarization, in chronological order */
28
+ messages: AgentMessage[];
29
+ /** File operations extracted from tool calls */
30
+ fileOps: FileOperations;
31
+ /** Total estimated tokens in messages */
32
+ totalTokens: number;
33
+ }
34
+ export interface CollectEntriesResult {
35
+ /** Entries to summarize, in chronological order */
36
+ entries: SessionEntry[];
37
+ /** Common ancestor between old and new position, if any */
38
+ commonAncestorId: string | null;
39
+ }
40
+ export interface GenerateBranchSummaryOptions {
41
+ /** Model to use for summarization */
42
+ model: Model;
43
+ /** API key for the model */
44
+ apiKey: string;
45
+ /** Abort signal for cancellation */
46
+ signal: AbortSignal;
47
+ /** Optional custom instructions for summarization */
48
+ customInstructions?: string;
49
+ /** Tokens reserved for prompt + LLM response (default 16384) */
50
+ reserveTokens?: number;
51
+ /** Optional metadata forwarded to the underlying API request (e.g. user_id for session attribution). */
52
+ metadata?: Record<string, unknown>;
53
+ /** Convert app-specific messages before serializing the branch summary prompt. */
54
+ convertToLlm?: ConvertToLlm;
55
+ /**
56
+ * Optional telemetry handle. When provided, the branch summary LLM call is
57
+ * wrapped in an OTEL chat span tagged with `pi.gen_ai.oneshot.kind = "branch_summary"`.
58
+ */
59
+ telemetry?: AgentTelemetry;
60
+ /**
61
+ * Provider session affinity id forwarded to the branch summary LLM call so it
62
+ * reuses the live turn's provider/WebSocket session.
63
+ */
64
+ sessionId?: string;
65
+ /** Shared provider state map so the branch summary call reuses session-scoped transport/session caches. */
66
+ providerSessionState?: Map<string, ProviderSessionState>;
67
+ /** Hint that websocket transport should be preferred when supported by the provider implementation. */
68
+ preferWebsockets?: boolean;
69
+ }
70
+ /**
71
+ * Collect entries that should be summarized when navigating from one position to another.
72
+ *
73
+ * Walks from oldLeafId back to the common ancestor with targetId, collecting entries
74
+ * along the way. Does NOT stop at compaction boundaries - those are included and their
75
+ * summaries become context.
76
+ *
77
+ * @param session - Session manager (read-only access)
78
+ * @param oldLeafId - Current position (where we're navigating from)
79
+ * @param targetId - Target position (where we're navigating to)
80
+ * @returns Entries to summarize and the common ancestor
81
+ */
82
+ export declare function collectEntriesForBranchSummary(session: ReadonlySessionManager, oldLeafId: string | null, targetId: string): CollectEntriesResult;
83
+ /**
84
+ * Prepare entries for summarization with token budget.
85
+ *
86
+ * Walks entries from NEWEST to OLDEST, adding messages until we hit the token budget.
87
+ * This ensures we keep the most recent context when the branch is too long.
88
+ *
89
+ * Also collects file operations from:
90
+ * - Tool calls in assistant messages
91
+ * - Existing branch_summary entries' details (for cumulative tracking)
92
+ *
93
+ * @param entries - Entries in chronological order
94
+ * @param tokenBudget - Maximum tokens to include (0 = no limit)
95
+ */
96
+ export declare function prepareBranchEntries(entries: SessionEntry[], tokenBudget?: number): BranchPreparation;
97
+ /**
98
+ * Generate a summary of abandoned branch entries.
99
+ *
100
+ * @param entries - Session entries to summarize (chronological order)
101
+ * @param options - Generation options
102
+ */
103
+ export declare function generateBranchSummary(entries: SessionEntry[], options: GenerateBranchSummaryOptions): Promise<BranchSummaryResult>;
@@ -0,0 +1,330 @@
1
+ /**
2
+ * Context compaction for long sessions.
3
+ *
4
+ * Pure functions for compaction logic. The session manager handles I/O,
5
+ * and after compaction the session is reloaded.
6
+ */
7
+ import { type MessageAttribution, type Model, type ProviderSessionState, type Usage } from "@vib-rato/ai";
8
+ import { type AgentTelemetry } from "../telemetry";
9
+ import type { AgentMessage, AgentTool } from "../types";
10
+ import type { AdaptiveCompactionDecisionState, AdaptiveCompactionOptions } from "./adaptive";
11
+ import type { SessionEntry } from "./entries";
12
+ import { type ConvertToLlm } from "./messages";
13
+ import { type FileOperations } from "./utils";
14
+ /** Details stored in CompactionEntry.details for file tracking */
15
+ export interface CompactionDetails {
16
+ readFiles: string[];
17
+ modifiedFiles: string[];
18
+ }
19
+ /** Result from compact() - SessionManager adds uuid/parentUuid when saving */
20
+ export interface CompactionResult<T = unknown> {
21
+ summary: string;
22
+ /** Short PR-style summary for display purposes. */
23
+ shortSummary?: string;
24
+ firstKeptEntryId: string;
25
+ tokensBefore: number;
26
+ /** Hook-specific data (e.g., ArtifactIndex, version markers for structured compaction) */
27
+ details?: T;
28
+ /** Hook-provided data to persist alongside compaction entry. */
29
+ preserveData?: Record<string, unknown>;
30
+ }
31
+ export interface CompactionSettings {
32
+ enabled: boolean;
33
+ strategy?: "context-full" | "handoff" | "off";
34
+ thresholdPercent?: number;
35
+ thresholdTokens?: number;
36
+ adaptive?: AdaptiveCompactionOptions;
37
+ adaptiveState?: AdaptiveCompactionDecisionState;
38
+ reserveTokens: number;
39
+ keepRecentTokens: number;
40
+ autoContinue?: boolean;
41
+ remoteEnabled?: boolean;
42
+ remoteEndpoint?: string;
43
+ }
44
+ export type RemoteCompactionFallbackHealthEvent = {
45
+ kind: "success";
46
+ model: string;
47
+ provider: string;
48
+ } | {
49
+ kind: "fallback";
50
+ model: string;
51
+ provider: string;
52
+ error: string;
53
+ };
54
+ export interface RemoteCompactionFallbackHealthHooks {
55
+ recordRemoteCompactionFallback(event: RemoteCompactionFallbackHealthEvent): void;
56
+ }
57
+ export declare const DEFAULT_COMPACTION_SETTINGS: CompactionSettings;
58
+ export declare function computeAdaptiveThresholdPercent(basePercent: number, contextTokens: number, contextWindow: number, state: AdaptiveCompactionDecisionState | undefined, options: AdaptiveCompactionOptions | undefined): number;
59
+ /**
60
+ * Calculate total context tokens from usage.
61
+ * Uses the native totalTokens field when available, falls back to computing from components.
62
+ */
63
+ export declare function calculateContextTokens(usage: Usage): number;
64
+ export declare function calculatePromptTokens(usage: Usage): number;
65
+ /**
66
+ * Find the last non-aborted assistant message usage from session entries.
67
+ */
68
+ export declare function getLastAssistantUsage(entries: SessionEntry[]): Usage | undefined;
69
+ /**
70
+ * Effective reserve: the largest of 15% of the context window, the configured floor,
71
+ * and the model's reserved completion budget (`maxOutputTokens`).
72
+ *
73
+ * Reserving `maxOutputTokens` keeps the safe input/prompt-packing budget below the
74
+ * *total* context window for models whose completion reservation exceeds the 15%
75
+ * floor (e.g. a 400K-context model with 128K max output reserves 128K, not 60K, so
76
+ * input is capped near 272K instead of 340K).
77
+ */
78
+ export declare function effectiveReserveTokens(contextWindow: number, settings: CompactionSettings, maxOutputTokens?: number): number;
79
+ /**
80
+ * Check if compaction should trigger based on context usage.
81
+ *
82
+ * `maxOutputTokens` is the model's reserved completion budget; it is excluded from
83
+ * the safe input budget so prompt + reserved output cannot exceed the total window.
84
+ */
85
+ export declare function shouldCompact(contextTokens: number, contextWindow: number, settings: CompactionSettings, maxOutputTokens?: number): boolean;
86
+ /** Reason a compaction was triggered. `token` is the normal user-configurable path; the rest are emergency floors. */
87
+ export type CompactionTriggerReason = "token" | "heap" | "retainedMemory" | "transcriptFile" | "providerBytes" | "messageCount" | "imageBytes";
88
+ /** A point-in-time resource sample. Supplied by an injectable sampler so tests never read real RSS. */
89
+ export interface EmergencyCompactionSample {
90
+ /** Resident heap bytes (e.g. process.memoryUsage().heapUsed). */
91
+ heapUsedBytes: number;
92
+ /** Approximate serialized provider-context bytes. */
93
+ providerBytes: number;
94
+ /** Provider-visible message count. */
95
+ messageCount: number;
96
+ /** Approximate inline image bytes in the provider context. */
97
+ imageBytes: number;
98
+ /** Bytes retained by session resident image sentinels; separate from provider-visible bytes. */
99
+ sessionResidentImageBytes?: number;
100
+ /** Bytes retained by non-provider materialized/session-local caches. */
101
+ materializedResidentBytes?: number;
102
+ /** Number of live TUI chat-container children. */
103
+ tuiChatChildren?: number;
104
+ /** Bytes retained by TUI render caches. */
105
+ tuiCachedRenderBytes?: number;
106
+ /** On-disk JSONL transcript file size in bytes; 0/undefined when unknown. */
107
+ transcriptFileBytes?: number;
108
+ }
109
+ export interface EmergencyCompactionLimits {
110
+ heapUsedBytes: number;
111
+ providerBytes: number;
112
+ messageCount: number;
113
+ imageBytes: number;
114
+ retainedMemoryBytes?: number;
115
+ retainedMemoryDiagnosticBytes?: number;
116
+ tuiChatChildren?: number;
117
+ tuiChatChildrenDiagnostic?: number;
118
+ transcriptFileBytes?: number;
119
+ }
120
+ export declare function resetEmergencyRetainedMemoryDiagnosticsForTests(): void;
121
+ export declare function resolveEmergencyCompactionLimits(totalMemoryBytes?: number): EmergencyCompactionLimits;
122
+ /**
123
+ * Non-disableable emergency floors. These sit well above normal usage and exist so a
124
+ * long session on weak hardware compacts before OOM even when token-based compaction is
125
+ * disabled or its threshold is set too high. They are NOT user-tunable down to zero.
126
+ */
127
+ export declare const DEFAULT_EMERGENCY_COMPACTION_LIMITS: EmergencyCompactionLimits;
128
+ /**
129
+ * Returns the first emergency limit exceeded (heap > retainedMemory > transcriptFile > providerBytes > imageBytes > messageCount),
130
+ * or null when none is. Pure apart from retained-memory diagnostics; the caller routes the result through the
131
+ * normal pair-safe `compact()` cut logic so a tool_use/tool_result pair is never split.
132
+ */
133
+ export declare function emergencyCompactionReason(sample: EmergencyCompactionSample, limits?: EmergencyCompactionLimits): CompactionTriggerReason | null;
134
+ export declare function resolveThresholdTokens(contextWindow: number, settings: CompactionSettings, maxOutputTokens?: number, contextTokens?: number): number;
135
+ /**
136
+ * Image content has no tokenizer representation; charge a fixed estimate
137
+ * matching what providers typically bill for inline images.
138
+ */
139
+ export declare const IMAGE_TOKEN_ESTIMATE = 1200;
140
+ /**
141
+ * Native-free token estimate for a message. This is the only message
142
+ * token estimator: provider usage (see {@link calculatePromptTokens}) anchors
143
+ * the already-sent context, and this covers unsent/trailing deltas, per-entry
144
+ * budgeting, and display surfaces. Callers add a conservative inflation factor
145
+ * where compaction-threshold safety requires it.
146
+ */
147
+ export declare function estimateMessageTokensHeuristic(message: AgentMessage): number;
148
+ /**
149
+ * Script-aware native-free token estimate for plain string fragments.
150
+ * Fragment-level counterpart of {@link estimateMessageTokensHeuristic}.
151
+ */
152
+ export declare function estimateTextTokensHeuristic(fragments: string | readonly string[]): number;
153
+ export declare function estimateEntryTokens(entry: SessionEntry): number;
154
+ export declare function estimateEntriesTokens(entries: SessionEntry[], startIndex: number, endIndex: number): number;
155
+ /**
156
+ * Find the user message (or bashExecution) that starts the turn containing the given entry index.
157
+ * Returns -1 if no turn start found before the index.
158
+ * BashExecutionMessage is treated like a user message for turn boundaries.
159
+ */
160
+ export declare function findTurnStartIndex(entries: SessionEntry[], entryIndex: number, startIndex: number): number;
161
+ export interface CutPointResult {
162
+ /** Index of first entry to keep */
163
+ firstKeptEntryIndex: number;
164
+ /** Index of user message that starts the turn being split, or -1 if not splitting */
165
+ turnStartIndex: number;
166
+ /** Whether this cut splits a turn (cut point is not a user message) */
167
+ isSplitTurn: boolean;
168
+ }
169
+ /**
170
+ * Find the cut point in session entries that keeps approximately `keepRecentTokens`.
171
+ *
172
+ * Algorithm: Walk backwards from newest, accumulating estimated message sizes.
173
+ * Stop when we've accumulated >= keepRecentTokens. Cut at that point.
174
+ *
175
+ * Can cut at user OR assistant messages (never tool results). When cutting at an
176
+ * assistant message with tool calls, its tool results come after and will be kept.
177
+ *
178
+ * Returns CutPointResult with:
179
+ * - firstKeptEntryIndex: the entry index to start keeping from
180
+ * - turnStartIndex: if cutting mid-turn, the user message that started that turn
181
+ * - isSplitTurn: whether we're cutting in the middle of a turn
182
+ *
183
+ * Only considers entries between `startIndex` and `endIndex` (exclusive).
184
+ */
185
+ export declare function findCutPoint(entries: SessionEntry[], startIndex: number, endIndex: number, keepRecentTokens: number): CutPointResult;
186
+ export declare const AUTO_HANDOFF_THRESHOLD_FOCUS: string;
187
+ /**
188
+ * Generate a summary of the conversation using the LLM.
189
+ * If previousSummary is provided, uses the update prompt to merge.
190
+ */
191
+ export interface SummaryOptions {
192
+ promptOverride?: string;
193
+ extraContext?: string[];
194
+ remoteEndpoint?: string;
195
+ remoteInstructions?: string;
196
+ initiatorOverride?: MessageAttribution;
197
+ metadata?: Record<string, unknown>;
198
+ convertToLlm?: ConvertToLlm;
199
+ /**
200
+ * Optional telemetry handle. When provided, every LLM call emitted during
201
+ * compaction is wrapped in an OTEL chat span tagged with
202
+ * `pi.gen_ai.oneshot.kind` (`compaction_summary` or `compaction_turn_prefix`).
203
+ */
204
+ telemetry?: AgentTelemetry;
205
+ authCredentialType?: "api_key" | "oauth";
206
+ /**
207
+ * Provider session affinity id forwarded to the maintenance LLM call so it
208
+ * reuses the live turn's provider/WebSocket session (matches the
209
+ * `providerSessionId ?? sessionId` the agent loop sends for normal turns).
210
+ */
211
+ sessionId?: string;
212
+ /** Shared provider state map so maintenance calls reuse session-scoped transport/session caches. */
213
+ providerSessionState?: Map<string, ProviderSessionState>;
214
+ /** Hint that websocket transport should be preferred when supported by the provider implementation. */
215
+ preferWebsockets?: boolean;
216
+ /** Session-owned health sink for remote-compaction fallback transition logging. */
217
+ remoteCompactionFallbackHealth?: RemoteCompactionFallbackHealthHooks;
218
+ }
219
+ /**
220
+ * Cap the serialized conversation fed to a summarization request so the request
221
+ * itself fits inside the model's context window.
222
+ *
223
+ * Without this, summarizing a near-full context serializes (nearly) the entire
224
+ * history back into a single summary request; on strict backends (e.g.
225
+ * OpenAI-code/Codex `context_length_exceeded`) that request itself overflows and
226
+ * throws, so context-overflow recovery cannot produce a summary and the agent
227
+ * fails to compact-and-continue — a non-interactive `vib -p` run then terminates
228
+ * on the very overflow the recovery was meant to absorb.
229
+ *
230
+ * The budget reserves the summary's own output tokens plus prompt/system/template
231
+ * overhead, and applies a conservative safety factor for estimator error on
232
+ * dense text (the reason the original overflow was missed).
233
+ * Truncation keeps the head (origin/goals) and the tail (most recent state) and
234
+ * elides the middle; it is a last resort that only triggers when the input would
235
+ * otherwise not fit.
236
+ */
237
+ export declare function boundConversationTextForSummary(conversationText: string, model: Model, outputMaxTokens: number): string;
238
+ export declare function generateSummary(currentMessages: AgentMessage[], model: Model, reserveTokens: number, apiKey: string, signal?: AbortSignal, customInstructions?: string, previousSummary?: string, options?: SummaryOptions): Promise<string>;
239
+ export interface HandoffOptions {
240
+ /** Live agent system prompt — passed verbatim so providers hit the cached prefix. */
241
+ systemPrompt: string[];
242
+ /** Live agent tool list — same purpose. Forced to `toolChoice: "none"`. */
243
+ tools?: AgentTool<any>[];
244
+ customInstructions?: string;
245
+ /**
246
+ * Optional user-configured extension appended to the base handoff prompt.
247
+ * It SUPPLEMENTS the immutable base (safety/continuity structure); it never
248
+ * replaces `HANDOFF_DOCUMENT_PROMPT`.
249
+ */
250
+ promptExtension?: string;
251
+ convertToLlm?: ConvertToLlm;
252
+ initiatorOverride?: MessageAttribution;
253
+ metadata?: Record<string, unknown>;
254
+ /**
255
+ * Optional telemetry handle. When provided, the handoff LLM call is
256
+ * wrapped in an OTEL chat span tagged with `pi.gen_ai.oneshot.kind = "handoff"`.
257
+ */
258
+ telemetry?: AgentTelemetry;
259
+ authCredentialType?: "api_key" | "oauth";
260
+ /**
261
+ * Provider session affinity id forwarded to the handoff LLM call so it
262
+ * reuses the live turn's provider/WebSocket session.
263
+ */
264
+ sessionId?: string;
265
+ /** Shared provider state map so the handoff call reuses session-scoped transport/session caches. */
266
+ providerSessionState?: Map<string, ProviderSessionState>;
267
+ /** Hint that websocket transport should be preferred when supported by the provider implementation. */
268
+ preferWebsockets?: boolean;
269
+ }
270
+ export declare function renderHandoffPrompt(customInstructions?: string, promptExtension?: string): string;
271
+ export declare function generateHandoff(messages: AgentMessage[], model: Model, apiKey: string, options: HandoffOptions, signal?: AbortSignal): Promise<string>;
272
+ export interface CompactionPreparation {
273
+ /** UUID of first entry to keep */
274
+ firstKeptEntryId: string;
275
+ /** Messages that will be summarized and discarded */
276
+ messagesToSummarize: AgentMessage[];
277
+ /** Messages that will be turned into turn prefix summary (if splitting) */
278
+ turnPrefixMessages: AgentMessage[];
279
+ /** Messages kept in full after compaction (recent history) */
280
+ recentMessages: AgentMessage[];
281
+ /** Whether this is a split turn (cut point in middle of turn) */
282
+ isSplitTurn: boolean;
283
+ tokensBefore: number;
284
+ /** Summary from previous compaction, for iterative update */
285
+ previousSummary?: string;
286
+ /** Preserved opaque compaction payload from the previous compaction, if any. */
287
+ previousPreserveData?: Record<string, unknown>;
288
+ /** File operations extracted from messagesToSummarize */
289
+ fileOps: FileOperations;
290
+ /** Compaction settions from settings.jsonl */
291
+ settings: CompactionSettings;
292
+ /**
293
+ * Diagnostics for the keep-window token correction (Finding 7). `ratio` is the
294
+ * clamped heuristic→actual correction that was applied (1 when none supplied);
295
+ * `keepRecentTokensCorrected` is the heuristic budget findCutPoint actually used.
296
+ */
297
+ tokenCorrection: {
298
+ ratio: number;
299
+ keepRecentTokensCorrected: number;
300
+ };
301
+ }
302
+ /** Bounds for the keep-window token correction (Finding 7): never trust a ratio
303
+ * beyond 2x in either direction so a bad estimate cannot balloon or collapse the
304
+ * kept window. */
305
+ export declare const TOKEN_CORRECTION_MIN_RATIO = 0.5;
306
+ export declare const TOKEN_CORRECTION_MAX_RATIO = 2;
307
+ export interface PrepareCompactionOptions {
308
+ /**
309
+ * Observed heuristic→actual token correction for the post-boundary keep window
310
+ * (actualTokens / chars-4-heuristicTokens), supplied by the caller from per-turn
311
+ * Usage deltas or a stable-prefix-subtracted comparison. Clamped to
312
+ * [0.5, 2] and applied bidirectionally. When omitted, no correction is applied
313
+ * (the confounded raw promptTokens/estimatedTokens quotient is never used).
314
+ */
315
+ tokenCorrectionRatio?: number;
316
+ /**
317
+ * Model context-window size. Windows below 66k retain the legacy fixed
318
+ * keepRecentTokens behavior; larger windows scale the keep window to 30%.
319
+ */
320
+ contextWindow?: number;
321
+ }
322
+ export declare function prepareCompaction(pathEntries: SessionEntry[], settings: CompactionSettings, options?: PrepareCompactionOptions): CompactionPreparation | undefined;
323
+ /**
324
+ * Generate summaries for compaction using prepared data.
325
+ * Returns CompactionResult - SessionManager adds id/parentId when saving.
326
+ *
327
+ * @param preparation - Pre-calculated preparation from prepareCompaction()
328
+ * @param customInstructions - Optional custom focus for the summary
329
+ */
330
+ export declare function compact(preparation: CompactionPreparation, model: Model, apiKey: string, customInstructions?: string, signal?: AbortSignal, options?: SummaryOptions): Promise<CompactionResult>;
@@ -0,0 +1,124 @@
1
+ import type { ImageContent, MessageAttribution, ServiceTier, TextContent } from "@vib-rato/ai";
2
+ import type { AgentMessage } from "../types";
3
+ export interface SessionEntryBase {
4
+ type: string;
5
+ id: string;
6
+ parentId: string | null;
7
+ timestamp: string;
8
+ }
9
+ export interface SessionMessageEntry extends SessionEntryBase {
10
+ type: "message";
11
+ message: AgentMessage;
12
+ }
13
+ export interface ThinkingLevelChangeEntry extends SessionEntryBase {
14
+ type: "thinking_level_change";
15
+ thinkingLevel?: string | null;
16
+ }
17
+ export interface ModelChangeEntry extends SessionEntryBase {
18
+ type: "model_change";
19
+ /** Model in "provider/modelId" format */
20
+ model: string;
21
+ /** Role: "default", "smol", "slow", etc. Undefined treated as "default" */
22
+ role?: string;
23
+ /** Requested model before a runtime substitution/fallback, in "provider/modelId" format. */
24
+ previousModel?: string;
25
+ /** Machine-readable reason for runtime model substitution/fallback. */
26
+ reason?: string;
27
+ /** Effective thinking level when the change was recorded. */
28
+ thinkingLevel?: string | null;
29
+ }
30
+ export interface ServiceTierChangeEntry extends SessionEntryBase {
31
+ type: "service_tier_change";
32
+ serviceTier: ServiceTier | null;
33
+ }
34
+ export interface CompactionEntry<T = unknown> extends SessionEntryBase {
35
+ type: "compaction";
36
+ summary: string;
37
+ shortSummary?: string;
38
+ firstKeptEntryId: string;
39
+ tokensBefore: number;
40
+ /** Extension-specific data (e.g., ArtifactIndex, version markers for structured compaction) */
41
+ details?: T;
42
+ /** Hook-provided data to persist across compaction */
43
+ preserveData?: Record<string, unknown>;
44
+ /** True if generated by an extension, undefined/false if pi-generated (backward compatible) */
45
+ fromExtension?: boolean;
46
+ }
47
+ export interface BranchSummaryEntry<T = unknown> extends SessionEntryBase {
48
+ type: "branch_summary";
49
+ fromId: string;
50
+ summary: string;
51
+ /** Extension-specific data (not sent to LLM) */
52
+ details?: T;
53
+ /** True if generated by an extension, false if pi-generated */
54
+ fromExtension?: boolean;
55
+ }
56
+ export interface CustomMessageEntry<T = unknown> extends SessionEntryBase {
57
+ type: "custom_message";
58
+ customType: string;
59
+ content: string | (TextContent | ImageContent)[];
60
+ details?: T;
61
+ display: boolean;
62
+ /** Who initiated this message for billing/attribution semantics. */
63
+ attribution?: MessageAttribution;
64
+ }
65
+ export interface CustomEntry<T = unknown> extends SessionEntryBase {
66
+ type: "custom";
67
+ customType: string;
68
+ data?: T;
69
+ }
70
+ export interface LabelEntry extends SessionEntryBase {
71
+ type: "label";
72
+ targetId: string;
73
+ label: string | undefined;
74
+ }
75
+ export interface TtsrInjectionEntry extends SessionEntryBase {
76
+ type: "ttsr_injection";
77
+ /** Names of rules that were injected */
78
+ injectedRules: string[];
79
+ }
80
+ export interface MCPToolSelectionEntry extends SessionEntryBase {
81
+ type: "mcp_tool_selection";
82
+ /** MCP tool names selected for visibility in discovery mode. */
83
+ selectedToolNames: string[];
84
+ }
85
+ export interface DiscoveredBuiltinToolSelectionEntry extends SessionEntryBase {
86
+ type: "discovered_builtin_tool_selection";
87
+ /** Discoverable built-in tool names selected for visibility in discovery mode. */
88
+ selectedToolNames: string[];
89
+ }
90
+ export interface SessionInitEntry extends SessionEntryBase {
91
+ type: "session_init";
92
+ /** Full system prompt sent to the model */
93
+ systemPrompt: string;
94
+ /** Initial task/user message */
95
+ task: string;
96
+ /** Tools available to the agent */
97
+ tools: string[];
98
+ /** Output schema if structured output was requested */
99
+ outputSchema?: unknown;
100
+ }
101
+ export interface ModeChangeEntry extends SessionEntryBase {
102
+ type: "mode_change";
103
+ /** Current mode name, or "none" when exiting a mode */
104
+ mode: string;
105
+ /** Optional mode-specific data (e.g. plan file path) */
106
+ data?: Record<string, unknown>;
107
+ }
108
+ export interface ConfiguredModelChainEntry extends SessionEntryBase {
109
+ type: "configured_model_chain";
110
+ role: string;
111
+ entries: readonly string[];
112
+ origin: string;
113
+ identity?: string;
114
+ explicitHead: boolean;
115
+ /** Whether this entry removes the configured chain for its role. */
116
+ cleared?: boolean;
117
+ }
118
+ export interface CustomCompactionSessionEntries {
119
+ }
120
+ export type SessionEntry = SessionMessageEntry | ThinkingLevelChangeEntry | ModelChangeEntry | ServiceTierChangeEntry | CompactionEntry | BranchSummaryEntry | CustomEntry | CustomMessageEntry | LabelEntry | TtsrInjectionEntry | MCPToolSelectionEntry | DiscoveredBuiltinToolSelectionEntry | SessionInitEntry | ModeChangeEntry | ConfiguredModelChainEntry | CustomCompactionSessionEntries[keyof CustomCompactionSessionEntries];
121
+ export interface ReadonlySessionManager {
122
+ getBranch(leafId?: string | null): SessionEntry[];
123
+ getEntry(id: string): SessionEntry | undefined;
124
+ }