@ponythewhite/base-context 1.0.12 → 1.0.13

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.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,9 @@
1
1
  # Changelog
2
2
 
3
+ ## [1.0.13] - 2026-09-21
4
+
5
+ - Changed the default automatic compaction target to 90% of the model context window, retaining required-context headroom, the output reserve, and earlier agent-requested compaction.
6
+
3
7
  ## [1.0.12] - 2026-09-21
4
8
 
5
9
  - Fixed compaction and recovery for primary RLM child requests.
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "base-context-runtime"
3
- version = "1.0.12"
3
+ version = "1.0.13"
4
4
  description = "Kernel-side runtime shim for Base Context recursion"
5
5
  license = "MIT"
6
6
  license-files = ["LICENSE", "NOTICE"]
@@ -367,7 +367,7 @@ wheels = [
367
367
 
368
368
  [[package]]
369
369
  name = "base-context-runtime"
370
- version = "1.0.12"
370
+ version = "1.0.13"
371
371
  source = { editable = "." }
372
372
  dependencies = [
373
373
  { name = "mcp" },
@@ -1,5 +1,5 @@
1
1
  {
2
- "sourceCommit": "8a0001383aefd9636a5f5420ee6b555892145936",
2
+ "sourceCommit": "c29f87f196ab576d986a95a2f3dd3feb3fc02a4a",
3
3
  "sourceDirty": false,
4
4
  "buildHost": {
5
5
  "platform": "linux",
@@ -51,7 +51,7 @@ import {
51
51
  oauth_exports,
52
52
  runPrintMode,
53
53
  runRpcMode
54
- } from "./chunk-IAKHCQA2.js";
54
+ } from "./chunk-TXVF6Q5H.js";
55
55
  import {
56
56
  AgentSession,
57
57
  CURRENT_SESSION_VERSION,
@@ -143,7 +143,7 @@ import {
143
143
  withFileMutationQueue,
144
144
  wrapRegisteredTool,
145
145
  wrapRegisteredTools
146
- } from "./chunk-YGV3Y4AD.js";
146
+ } from "./chunk-TKOBEJ5E.js";
147
147
  import "./chunk-CFNASUYO.js";
148
148
  import "./chunk-Q6UAHLII.js";
149
149
  import {
@@ -34196,7 +34196,7 @@ function shouldCompact(contextTokens, contextWindow, settings, fixedContextToken
34196
34196
  if (contextWindow <= 0)
34197
34197
  return false;
34198
34198
  const modelLimit = contextWindow - settings.reserveTokens;
34199
- const target = settings.targetTokens === "model-limit" ? modelLimit : Math.max(settings.targetTokens ?? Math.min(96e3, contextWindow / 2), fixedContextTokens + 4 * settings.keepRecentTokens);
34199
+ const target = settings.targetTokens === "model-limit" ? modelLimit : Math.max(settings.targetTokens ?? Math.floor(contextWindow * 0.9), fixedContextTokens + 4 * settings.keepRecentTokens);
34200
34200
  return contextTokens > Math.min(target, modelLimit);
34201
34201
  }
34202
34202
  function estimateFixedCompactionTokens(systemPrompt, tools, messages) {
@@ -46083,7 +46083,7 @@ async function loadExtensionModule(extensionPath) {
46083
46083
  // (file-path aliases would load a second, divergent copy of each package).
46084
46084
  // Also disable tryNative so jiti handles ALL imports (not just the entry point)
46085
46085
  // Node/dev must also share the native AI registry and implementation identities.
46086
- ...isBunBinary || isBundledCli ? { virtualModules: (await import("./bundled-modules-WCGPZI2H.js")).VIRTUAL_MODULES, tryNative: false } : {
46086
+ ...isBunBinary || isBundledCli ? { virtualModules: (await import("./bundled-modules-URKVA3UB.js")).VIRTUAL_MODULES, tryNative: false } : {
46087
46087
  alias: getAliases(),
46088
46088
  virtualModules: {
46089
46089
  "@ponythewhite/base-context-ai": src_exports,
@@ -70912,7 +70912,7 @@ import { resolve as resolve15 } from "node:path";
70912
70912
  var BASE_CONTEXT_BUILD_ID_ENV = "BASE_CONTEXT_BUILD_ID";
70913
70913
  var BASE_CONTEXT_LAUNCHER_PATH_ENV = "BASE_CONTEXT_LAUNCHER_PATH";
70914
70914
  function bundledBuildId() {
70915
- return false ? void 0 : "v1.0.11-2-g8a0001383";
70915
+ return false ? void 0 : "v1.0.12-2-gc29f87f19";
70916
70916
  }
70917
70917
  function getDaemonRuntimeIdentity(environment = process.env) {
70918
70918
  const entrypoint = process.argv[1];
@@ -351,7 +351,7 @@ import {
351
351
  watchWithErrorHandler,
352
352
  workerRosterEntryFromSummary,
353
353
  wrapTextWithAnsi
354
- } from "./chunk-YGV3Y4AD.js";
354
+ } from "./chunk-TKOBEJ5E.js";
355
355
  import {
356
356
  array,
357
357
  boolean,
@@ -5,7 +5,7 @@ import {
5
5
  isOwnedSessionWorkerProcess,
6
6
  maybeRunOwnedSessionWorkerFrontend,
7
7
  maybeStartDaemonEarly
8
- } from "./chunk-YGV3Y4AD.js";
8
+ } from "./chunk-TKOBEJ5E.js";
9
9
  import {
10
10
  APP_NAME
11
11
  } from "./chunk-AEMRBMI2.js";
@@ -39,7 +39,7 @@ async function runCli() {
39
39
  }
40
40
  const [{ EnvHttpProxyAgent, setGlobalDispatcher }, { main }] = await Promise.all([
41
41
  import("undici"),
42
- import("./main-GVGK354K.js")
42
+ import("./main-53I74INO.js")
43
43
  ]);
44
44
  setGlobalDispatcher(new EnvHttpProxyAgent({ bodyTimeout: 0, headersTimeout: 0 }));
45
45
  try {
@@ -52,6 +52,6 @@ var supported = assertNodeVersion({
52
52
  exit: (code) => process.exit(code)
53
53
  });
54
54
  if (supported) {
55
- const { runCli } = await import("./cli-main-CTYDQHFE.js");
55
+ const { runCli } = await import("./cli-main-ZTJM5EEH.js");
56
56
  await runCli();
57
57
  }
@@ -17,8 +17,8 @@ import {
17
17
  shouldUseDaemonClientRuntime,
18
18
  shouldUseDaemonInteractive,
19
19
  shouldUseEphemeralSessionManagerForDaemonInteractive
20
- } from "./chunk-IAKHCQA2.js";
21
- import "./chunk-YGV3Y4AD.js";
20
+ } from "./chunk-TXVF6Q5H.js";
21
+ import "./chunk-TKOBEJ5E.js";
22
22
  import "./chunk-CFNASUYO.js";
23
23
  import "./chunk-Q6UAHLII.js";
24
24
  import "./chunk-AEMRBMI2.js";
@@ -1 +1 @@
1
- {"version":3,"file":"compaction.d.ts","sourceRoot":"","sources":["../../../src/core/compaction/compaction.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Context compaction for long sessions.\n *\n * Pure functions for compaction logic. The session manager handles I/O,\n * and after compaction the session is reloaded.\n */\n\nimport type { AgentMessage, ThinkingLevel } from \"@ponythewhite/base-context-agent\";\nimport type { AssistantMessage, Model, Usage } from \"@ponythewhite/base-context-ai\";\nimport {\n\tcompleteInference,\n\tInferenceCoordinator,\n\ttype NativeCompactionOutputBinding,\n} from \"../inference-coordinator.js\";\nimport {\n\tCOMPACTION_SUMMARY_PREFIX,\n\tCOMPACTION_SUMMARY_SUFFIX,\n\tconvertToLlm,\n\tcreateBranchSummaryMessage,\n\tcreateCompactionSummaryMessage,\n\tcreateCustomMessage,\n\tHARNESS_SNAPSHOT_CUSTOM_TYPE,\n} from \"../messages.js\";\nimport type { NativeCompactionRequestOutputAssociation } from \"../request-events.js\";\nimport { MODEL_REQUEST_ID_HEADER } from \"../semantic-edges.js\";\nimport { buildSessionContext, type CompactionEntry, type SessionEntry } from \"../session-manager.js\";\nimport { TASK_FRAME_CUSTOM_TYPE } from \"../task-frame.js\";\nimport { addAssistantUsage, emptyUsage } from \"../usage.js\";\nimport {\n\tcomputeFileLists,\n\tcreateFileOps,\n\textractFileOpsFromMessage,\n\ttype FileOperations,\n\tformatFileOperations,\n\tSUMMARIZATION_SYSTEM_PROMPT,\n\tserializeConversation,\n} from \"./utils.js\";\n/** Details stored in CompactionEntry.details for file tracking */\nexport interface CompactionDetails {\n\treadFiles: string[];\n\tmodifiedFiles: string[];\n}\n\nexport interface SummarySlice {\n\tsummary: string;\n\tusage?: Usage;\n}\n\n// Identity follows the actual completion -> text slice -> built-in composition, never copied fields.\nconst nativeSummarySlices = new WeakMap<SummarySlice, { summary: string; binding: NativeCompactionOutputBinding }>();\nconst nativeCompactionResults = new WeakMap<object, { summary: string; bindings: NativeCompactionOutputBinding[] }>();\n\nfunction captureSummarySlice(\n\tslice: SummarySlice,\n\trequests: InferenceCoordinator | undefined,\n\tcompletion: Promise<AssistantMessage>,\n\tpart: NativeCompactionRequestOutputAssociation[\"part\"],\n): SummarySlice {\n\tconst binding =\n\t\trequests instanceof InferenceCoordinator\n\t\t\t? InferenceCoordinator.prototype.takeCompactionOutput.call(requests, completion)\n\t\t\t: undefined;\n\tif (binding?.output.part === part) nativeSummarySlices.set(slice, { summary: slice.summary, binding });\n\treturn slice;\n}\n\n/** Internal append input: only this exact built-in result and original sink can contribute links. */\nexport function takeCompactionRequestOutputs(\n\tresult: object | undefined,\n\tsink: object,\n\tsummary: string,\n): readonly NativeCompactionRequestOutputAssociation[] | undefined {\n\tconst captured = result ? nativeCompactionResults.get(result) : undefined;\n\tif (result) nativeCompactionResults.delete(result);\n\t// Equality only checks an already privately bound projection for mutation; it never discovers a link.\n\tif (!captured || captured.summary !== summary) return undefined;\n\tconst outputs = captured.bindings.filter((binding) => binding.sink === sink).map((binding) => binding.output);\n\treturn outputs.length ? outputs : undefined;\n}\n\n/**\n * Extract file operations from messages and previous compaction entries.\n */\n/** Preserve file operations recorded by prior compactions and current tool calls. */\nfunction extractFileOperations(\n\tmessages: AgentMessage[],\n\tentries: SessionEntry[],\n\tprevCompactionIndex: number,\n): FileOperations {\n\tconst fileOps = createFileOps();\n\tif (prevCompactionIndex >= 0) {\n\t\tconst prevCompaction = entries[prevCompactionIndex] as CompactionEntry;\n\t\tif (!prevCompaction.fromHook && prevCompaction.details) {\n\t\t\t// fromHook field kept for session file compatibility\n\t\t\tconst details = prevCompaction.details as CompactionDetails;\n\t\t\tif (Array.isArray(details.readFiles)) {\n\t\t\t\tfor (const f of details.readFiles) fileOps.read.add(f);\n\t\t\t}\n\t\t\tif (Array.isArray(details.modifiedFiles)) {\n\t\t\t\tfor (const f of details.modifiedFiles) fileOps.edited.add(f);\n\t\t\t}\n\t\t}\n\t}\n\tfor (const msg of messages) {\n\t\textractFileOpsFromMessage(msg, fileOps);\n\t}\n\n\treturn fileOps;\n}\n/**\n * Extract AgentMessage from an entry if it produces one.\n * Returns undefined for entries that don't contribute to LLM context.\n */\nfunction getMessageFromEntry(entry: SessionEntry): AgentMessage | undefined {\n\tif (entry.type === \"message\") {\n\t\treturn entry.message;\n\t}\n\tif (entry.type === \"custom_message\") {\n\t\treturn createCustomMessage(entry.customType, entry.content, entry.display, entry.details, entry.timestamp);\n\t}\n\tif (entry.type === \"branch_summary\") {\n\t\treturn createBranchSummaryMessage(entry.summary, entry.fromId, entry.timestamp);\n\t}\n\tif (entry.type === \"compaction\") {\n\t\treturn createCompactionSummaryMessage(\n\t\t\tentry.summary,\n\t\t\tentry.tokensBefore,\n\t\t\tentry.timestamp,\n\t\t\tentry.customInstructions,\n\t\t);\n\t}\n\treturn undefined;\n}\n\nfunction getMessageFromEntryForCompaction(entry: SessionEntry): AgentMessage | undefined {\n\tif (entry.type === \"compaction\") {\n\t\treturn undefined;\n\t}\n\treturn getMessageFromEntry(entry);\n}\n\n/** Result from compact() - SessionManager adds uuid/parentUuid when saving */\nexport interface CompactionResult<T = unknown> {\n\tsummary: string;\n\tfirstKeptEntryId: string;\n\t/** Prior-context estimate; null when the original context is not measurable. */\n\ttokensBefore: number | null;\n\t/** Extension-specific data (e.g., ArtifactIndex, version markers for structured compaction) */\n\tdetails?: T;\n\t/** What the summarization call(s) billed; persisted on the compaction entry. */\n\tusage?: Usage;\n}\nexport const COMPACT_SKILL_NAME = \"compact\";\n\nexport interface CompactionSettings {\n\tenabled: boolean;\n\treserveTokens: number;\n\tkeepRecentTokens: number;\n\t/** Soft working-context target; the model limit always takes precedence. */\n\ttargetTokens?: number | \"model-limit\";\n}\n\nexport const DEFAULT_COMPACTION_SETTINGS: CompactionSettings = {\n\tenabled: true,\n\treserveTokens: 16384,\n\tkeepRecentTokens: 20000,\n};\n/**\n * Calculate total context tokens from usage.\n * Uses the native totalTokens field when available, falls back to computing from components.\n *\n * Includes output: the assistant's response becomes part of the prompt on the next\n * request, so it counts toward the context the next turn will send.\n */\nexport function calculateContextTokens(usage: Usage): number {\n\treturn usage.totalTokens || usage.input + usage.output + usage.cacheRead + usage.cacheWrite;\n}\n\n/**\n * Get usage from an assistant message if available.\n * Skips aborted and error messages as they don't have valid usage data.\n */\nfunction getAssistantUsage(msg: AgentMessage): Usage | undefined {\n\tif (msg.role === \"assistant\" && \"usage\" in msg) {\n\t\tconst assistantMsg = msg as AssistantMessage;\n\t\tif (assistantMsg.stopReason !== \"aborted\" && assistantMsg.stopReason !== \"error\" && assistantMsg.usage) {\n\t\t\treturn assistantMsg.usage;\n\t\t}\n\t}\n\treturn undefined;\n}\n\n/**\n * Find the last non-aborted assistant message usage from session entries.\n */\nexport function getLastAssistantUsage(entries: SessionEntry[]): Usage | undefined {\n\tfor (let i = entries.length - 1; i >= 0; i--) {\n\t\tconst entry = entries[i];\n\t\tif (entry.type === \"message\") {\n\t\t\tconst usage = getAssistantUsage(entry.message);\n\t\t\tif (usage) return usage;\n\t\t}\n\t}\n\treturn undefined;\n}\n\nexport interface ContextUsageEstimate {\n\ttokens: number;\n\tusageTokens: number;\n\ttrailingTokens: number;\n\tlastUsageIndex: number | null;\n}\n\nfunction getLastAssistantUsageInfo(messages: readonly AgentMessage[]): { usage: Usage; index: number } | undefined {\n\tfor (let i = messages.length - 1; i >= 0; i--) {\n\t\tconst usage = getAssistantUsage(messages[i]);\n\t\tif (usage) return { usage, index: i };\n\t}\n\treturn undefined;\n}\n\n/**\n * Estimate context tokens from messages, using the last assistant usage when available.\n * If there are messages after the last usage, estimate their tokens with estimateTokens.\n */\nexport function estimateContextTokens(messages: AgentMessage[]): ContextUsageEstimate {\n\tconst usageInfo = getLastAssistantUsageInfo(messages);\n\n\tif (!usageInfo) {\n\t\tlet estimated = 0;\n\t\tfor (const message of messages) {\n\t\t\testimated += estimateTokens(message);\n\t\t}\n\t\treturn {\n\t\t\ttokens: estimated,\n\t\t\tusageTokens: 0,\n\t\t\ttrailingTokens: estimated,\n\t\t\tlastUsageIndex: null,\n\t\t};\n\t}\n\n\tconst usageTokens = calculateContextTokens(usageInfo.usage);\n\tlet trailingTokens = 0;\n\tfor (let i = usageInfo.index + 1; i < messages.length; i++) {\n\t\ttrailingTokens += estimateTokens(messages[i]);\n\t}\n\n\treturn {\n\t\ttokens: usageTokens + trailingTokens,\n\t\tusageTokens,\n\t\ttrailingTokens,\n\t\tlastUsageIndex: usageInfo.index,\n\t};\n}\n\n/**\n * Check if compaction should trigger based on context usage.\n */\nexport function shouldCompact(\n\tcontextTokens: number,\n\tcontextWindow: number,\n\tsettings: CompactionSettings,\n\tfixedContextTokens = 0,\n): boolean {\n\tif (!settings.enabled) return false;\n\tif (contextWindow <= 0) return false;\n\tconst modelLimit = contextWindow - settings.reserveTokens;\n\t// Fixed instructions cannot shrink. Leave meaningful room for retained history\n\t// and new work above that floor, without relaxing the actual model ceiling.\n\tconst target =\n\t\tsettings.targetTokens === \"model-limit\"\n\t\t\t? modelLimit\n\t\t\t: Math.max(\n\t\t\t\t\tsettings.targetTokens ?? Math.min(96_000, contextWindow / 2),\n\t\t\t\t\tfixedContextTokens + 4 * settings.keepRecentTokens,\n\t\t\t\t);\n\treturn contextTokens > Math.min(target, modelLimit);\n}\n\n/** Chars/4 heuristic, not a provider count. Ordinary history never raises this floor. */\nexport function estimateFixedCompactionTokens(\n\tsystemPrompt: string,\n\ttools: readonly { name: string; description: string; parameters: unknown }[],\n\tmessages: readonly AgentMessage[],\n): number {\n\tconst toolSchemas = tools.map(({ name, description, parameters }) => ({ name, description, parameters }));\n\tlet tokens = Math.ceil((systemPrompt.length + (tools.length ? JSON.stringify(toolSchemas).length : 0)) / 4);\n\tlet hasHarnessSnapshot = false;\n\tfor (let index = messages.length - 1; index >= 0; index--) {\n\t\tconst message = messages[index];\n\t\tif (message.role !== \"custom\") continue;\n\t\t// A TaskFrame may contain dependent sparse revisions; all current pieces count.\n\t\tif (message.customType === TASK_FRAME_CUSTOM_TYPE) tokens += estimateTokens(message);\n\t\telse if (message.customType === HARNESS_SNAPSHOT_CUSTOM_TYPE && !hasHarnessSnapshot) {\n\t\t\ttokens += estimateTokens(message);\n\t\t\thasHarnessSnapshot = true;\n\t\t}\n\t}\n\treturn tokens;\n}\n/** Raise suffix estimates when observed usage exceeds chars/4 for the same prefix. */\nfunction compactionTokenScale(messages: readonly AgentMessage[]): number {\n\tconst usageInfo = getLastAssistantUsageInfo(messages);\n\tif (!usageInfo) return 1;\n\tlet estimatedTokens = 0;\n\tfor (let index = 0; index <= usageInfo.index; index++) {\n\t\testimatedTokens += estimateTokens(messages[index]);\n\t}\n\treturn estimatedTokens > 0 ? Math.max(1, calculateContextTokens(usageInfo.usage) / estimatedTokens) : 1;\n}\n\n/** Estimate message tokens with chars/4; dense content can exceed this heuristic. */\nexport function estimateTokens(message: AgentMessage): number {\n\tlet chars = 0;\n\n\tswitch (message.role) {\n\t\tcase \"user\": {\n\t\t\tconst content = (message as { content: string | Array<{ type: string; text?: string }> }).content;\n\t\t\tif (typeof content === \"string\") {\n\t\t\t\tchars = content.length;\n\t\t\t} else if (Array.isArray(content)) {\n\t\t\t\tfor (const block of content) {\n\t\t\t\t\tif (block.type === \"text\" && block.text) {\n\t\t\t\t\t\tchars += block.text.length;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t\treturn Math.ceil(chars / 4);\n\t\t}\n\t\tcase \"assistant\": {\n\t\t\tconst assistant = message as AssistantMessage;\n\t\t\tfor (const block of assistant.content) {\n\t\t\t\tif (block.type === \"text\") {\n\t\t\t\t\tchars += block.text.length;\n\t\t\t\t} else if (block.type === \"thinking\") {\n\t\t\t\t\tchars += block.thinking.length;\n\t\t\t\t} else if (block.type === \"toolCall\") {\n\t\t\t\t\tchars += block.name.length + JSON.stringify(block.arguments).length;\n\t\t\t\t}\n\t\t\t}\n\t\t\treturn Math.ceil(chars / 4);\n\t\t}\n\t\tcase \"custom\":\n\t\tcase \"toolResult\": {\n\t\t\tif (typeof message.content === \"string\") {\n\t\t\t\tchars = message.content.length;\n\t\t\t} else {\n\t\t\t\tfor (const block of message.content) {\n\t\t\t\t\tif (block.type === \"text\" && block.text) {\n\t\t\t\t\t\tchars += block.text.length;\n\t\t\t\t\t}\n\t\t\t\t\tif (block.type === \"image\") {\n\t\t\t\t\t\tchars += 4800; // Estimate images as 4000 chars, or 1200 tokens\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t\treturn Math.ceil(chars / 4);\n\t\t}\n\t\tcase \"bashExecution\": {\n\t\t\tchars = message.command.length + message.output.length;\n\t\t\treturn Math.ceil(chars / 4);\n\t\t}\n\t\tcase \"branchSummary\":\n\t\tcase \"compactionSummary\": {\n\t\t\tchars = message.summary.length;\n\t\t\treturn Math.ceil(chars / 4);\n\t\t}\n\t}\n\n\treturn 0;\n}\n\n/**\n * Find valid cut points: indices of user, assistant, custom, or bashExecution messages.\n * Never cut at tool results (they must follow their tool call).\n * When we cut at an assistant message with tool calls, its tool results follow it\n * and will be kept.\n * BashExecutionMessage is treated like a user message (user-initiated context).\n */\nfunction findValidCutPoints(entries: SessionEntry[], startIndex: number, endIndex: number): number[] {\n\tconst cutPoints: number[] = [];\n\tfor (let i = startIndex; i < endIndex; i++) {\n\t\tconst entry = entries[i];\n\t\tswitch (entry.type) {\n\t\t\tcase \"message\": {\n\t\t\t\tconst role = entry.message.role;\n\t\t\t\tswitch (role) {\n\t\t\t\t\tcase \"bashExecution\":\n\t\t\t\t\tcase \"custom\":\n\t\t\t\t\tcase \"branchSummary\":\n\t\t\t\t\tcase \"compactionSummary\":\n\t\t\t\t\tcase \"user\":\n\t\t\t\t\tcase \"assistant\":\n\t\t\t\t\t\tcutPoints.push(i);\n\t\t\t\t\t\tbreak;\n\t\t\t\t\tcase \"toolResult\":\n\t\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t\tbreak;\n\t\t\t}\n\t\t\tcase \"thinking_level_change\":\n\t\t\tcase \"model_change\":\n\t\t\tcase \"compaction\":\n\t\t\tcase \"branch_summary\":\n\t\t\tcase \"custom\":\n\t\t\tcase \"custom_message\":\n\t\t\tcase \"label\":\n\t\t\tcase \"session_info\":\n\t\t\t\tbreak;\n\t\t}\n\t\t// Branch summaries and custom messages are user-role turn boundaries.\n\t\tif (entry.type === \"branch_summary\" || entry.type === \"custom_message\") {\n\t\t\tcutPoints.push(i);\n\t\t}\n\t}\n\treturn cutPoints;\n}\n\n/**\n * Find the user message (or bashExecution) that starts the turn containing the given entry index.\n * Returns -1 if no turn start found before the index.\n * BashExecutionMessage is treated like a user message for turn boundaries.\n */\nexport function findTurnStartIndex(entries: SessionEntry[], entryIndex: number, startIndex: number): number {\n\tfor (let i = entryIndex; i >= startIndex; i--) {\n\t\tconst entry = entries[i];\n\t\tif (entry.type === \"branch_summary\" || entry.type === \"custom_message\") {\n\t\t\treturn i;\n\t\t}\n\t\tif (entry.type === \"message\") {\n\t\t\tconst role = entry.message.role;\n\t\t\tif (role === \"user\" || role === \"bashExecution\") {\n\t\t\t\treturn i;\n\t\t\t}\n\t\t}\n\t}\n\treturn -1;\n}\n\nexport interface CutPointResult {\n\t/** Index of first entry to keep */\n\tfirstKeptEntryIndex: number;\n\t/** Index of user message that starts the turn being split, or -1 if not splitting */\n\tturnStartIndex: number;\n\t/** Whether this cut splits a turn (cut point is not a user message) */\n\tisSplitTurn: boolean;\n}\n\n/**\n * Find the cut point in session entries that keeps approximately `keepRecentTokens`.\n *\n * Algorithm: Walk backwards from newest, accumulating estimated message sizes.\n * Stop when we've accumulated >= keepRecentTokens. Cut at that point.\n *\n * Can cut at user OR assistant messages (never tool results). When cutting at an\n * assistant message with tool calls, its tool results come after and will be kept.\n *\n * Returns CutPointResult with:\n * - firstKeptEntryIndex: the entry index to start keeping from\n * - turnStartIndex: if cutting mid-turn, the user message that started that turn\n * - isSplitTurn: whether we're cutting in the middle of a turn\n *\n * Only considers entries between `startIndex` and `endIndex` (exclusive).\n */\nexport function findCutPoint(\n\tentries: SessionEntry[],\n\tstartIndex: number,\n\tendIndex: number,\n\tkeepRecentTokens: number,\n\tallowShortSession = false,\n\ttokenScale = 1,\n): CutPointResult {\n\tconst cutPoints = findValidCutPoints(entries, startIndex, endIndex);\n\n\tif (cutPoints.length === 0) {\n\t\treturn { firstKeptEntryIndex: startIndex, turnStartIndex: -1, isSplitTurn: false };\n\t}\n\tlet accumulatedTokens = 0;\n\tlet cutIndex = cutPoints[0]; // Default: keep from first message (not header)\n\n\tfor (let i = endIndex - 1; i >= startIndex; i--) {\n\t\tconst entry = entries[i];\n\t\tif (entry.type !== \"message\") continue;\n\t\tconst messageTokens = estimateTokens(entry.message) * tokenScale;\n\t\taccumulatedTokens += messageTokens;\n\t\tif (accumulatedTokens >= keepRecentTokens) {\n\t\t\tcutIndex = cutPoints.filter((candidate) => candidate <= i).at(-1) ?? cutIndex;\n\t\t\tbreak;\n\t\t}\n\t}\n\tif (allowShortSession && cutIndex === cutPoints[0]) {\n\t\t// An explicit compact may summarize a short prefix, but must keep a real suffix.\n\t\tconst latest = cutPoints\n\t\t\t.filter((index) => {\n\t\t\t\tconst entry = entries[index];\n\t\t\t\treturn entry.type !== \"message\" || entry.message.role !== \"compactionSummary\";\n\t\t\t})\n\t\t\t.at(-1);\n\t\tif (latest !== undefined) cutIndex = latest;\n\t}\n\twhile (cutIndex > startIndex) {\n\t\tconst prevEntry = entries[cutIndex - 1];\n\t\tif (prevEntry.type === \"compaction\") {\n\t\t\tbreak;\n\t\t}\n\t\tif (prevEntry.type === \"message\") {\n\t\t\tbreak;\n\t\t}\n\t\tcutIndex--;\n\t}\n\tconst cutEntry = entries[cutIndex];\n\tconst isUserMessage = cutEntry.type === \"message\" && cutEntry.message.role === \"user\";\n\t// A cut in a non-user turn requires a prefix summary.\n\tconst turnStartIndex = isUserMessage ? -1 : findTurnStartIndex(entries, cutIndex, startIndex);\n\n\treturn {\n\t\tfirstKeptEntryIndex: cutIndex,\n\t\tturnStartIndex,\n\t\tisSplitTurn: !isUserMessage && turnStartIndex !== -1,\n\t};\n}\nconst SUMMARIZATION_PROMPT = `The messages above are a conversation to summarize. Create a structured context checkpoint summary that another LLM will use to continue the work.\n\nUse this EXACT format:\n\n## Goal\n[What is the user trying to accomplish? Can be multiple items if the session covers different tasks.]\n\n## Constraints & Preferences\n- [Any constraints, preferences, or requirements mentioned by user]\n- [Or \"(none)\" if none were mentioned]\n\n## Progress\n### Done\n- [x] [Completed tasks/changes]\n\n### In Progress\n- [ ] [Current work]\n\n### Blocked\n- [Issues preventing progress, if any]\n\n## Key Decisions\n- **[Decision]**: [Brief rationale]\n\n## Next Steps\n1. [Ordered list of what should happen next]\n\n## Critical Context\n- [Any data, examples, or references needed to continue]\n- [Or \"(none)\" if not applicable]\n\nKeep each section concise. Preserve exact file paths, function names, and error messages.`;\n\nconst RUNTIME_STATE_SUMMARY_NOTE =\n\t\"Runtime note: this summary does not establish whether a Python kernel is live or whether its variables, imports, helpers, or jobs remain available. Preserve useful names and their last observed state, including uncertainty. Use current runtime reports before relying on them; do not infer either survival or loss from compaction.\";\n\nconst UPDATE_SUMMARIZATION_PROMPT = `The messages above are NEW conversation messages to incorporate into the existing summary provided in <previous-summary> tags.\n\nUpdate the existing structured summary with new information. RULES:\n- PRESERVE all existing information from the previous summary\n- ADD new progress, decisions, and context from the new messages\n- UPDATE the Progress section: move items from \"In Progress\" to \"Done\" when completed\n- UPDATE \"Next Steps\" based on what was accomplished\n- PRESERVE exact file paths, function names, and error messages\n- If something is no longer relevant, you may remove it\n\nUse this EXACT format:\n\n## Goal\n[Preserve existing goals, add new ones if the task expanded]\n\n## Constraints & Preferences\n- [Preserve existing, add new ones discovered]\n\n## Progress\n### Done\n- [x] [Include previously done items AND newly completed items]\n\n### In Progress\n- [ ] [Current work - update based on progress]\n\n### Blocked\n- [Current blockers - remove if resolved]\n\n## Key Decisions\n- **[Decision]**: [Brief rationale] (preserve all previous, add new)\n\n## Next Steps\n1. [Update based on current state]\n\n## Critical Context\n- [Preserve important context, add new if needed]\n\nKeep each section concise. Preserve exact file paths, function names, and error messages.`;\n\n/**\n * Build the instruction portion of the summarization prompt: the initial or\n * update template, optional user instructions, and the runtime-state qualification.\n */\nexport function buildSummarizationPrompt(customInstructions?: string, previousSummary?: string): string {\n\tlet basePrompt = previousSummary ? UPDATE_SUMMARIZATION_PROMPT : SUMMARIZATION_PROMPT;\n\tif (customInstructions) {\n\t\tbasePrompt += `\\n\\n<user-instructions>\\nThe user provided these instructions for this summary. Follow them with high priority while keeping the section format above: emphasize what they ask to focus on, and preserve verbatim anything they ask to remember.\\n${customInstructions}\\n</user-instructions>`;\n\t}\n\treturn `${basePrompt}\\n\\n${RUNTIME_STATE_SUMMARY_NOTE}`;\n}\n\n/**\n * Generate a summary of the conversation using the LLM.\n * If previousSummary is provided, uses the update prompt to merge.\n */\nexport async function generateSummary(\n\tcurrentMessages: AgentMessage[],\n\tmodel: Model<any>,\n\treserveTokens: number,\n\tapiKey: string,\n\theaders?: Record<string, string>,\n\tsignal?: AbortSignal,\n\tcustomInstructions?: string,\n\tpreviousSummary?: string,\n\tthinkingLevel?: ThinkingLevel,\n\trequests?: InferenceCoordinator,\n\toutputTokenLimit?: number,\n): Promise<SummarySlice> {\n\tconst maxTokens = outputTokenLimit ?? Math.floor(0.8 * reserveTokens);\n\n\tconst basePrompt = buildSummarizationPrompt(customInstructions, previousSummary);\n\t// Serialize before the LLM call so it summarizes rather than continues this conversation.\n\tconst llmMessages = convertToLlm(currentMessages);\n\tconst conversationText = serializeConversation(llmMessages);\n\tlet promptText = `<conversation>\\n${conversationText}\\n</conversation>\\n\\n`;\n\tif (previousSummary) {\n\t\tpromptText += `<previous-summary>\\n${previousSummary}\\n</previous-summary>\\n\\n`;\n\t}\n\tpromptText += basePrompt;\n\n\tconst summarizationMessages = [\n\t\t{\n\t\t\trole: \"user\" as const,\n\t\t\tcontent: [{ type: \"text\" as const, text: promptText }],\n\t\t\ttimestamp: Date.now(),\n\t\t},\n\t];\n\n\tconst completionOptions =\n\t\tmodel.reasoning && thinkingLevel && thinkingLevel !== \"off\"\n\t\t\t? { maxTokens, signal, apiKey, headers, reasoning: thinkingLevel }\n\t\t\t: { maxTokens, signal, apiKey, headers };\n\n\tconst completion = completeInference(\n\t\trequests,\n\t\tmodel,\n\t\t{ systemPrompt: SUMMARIZATION_SYSTEM_PROMPT, messages: summarizationMessages },\n\t\tcompletionOptions,\n\t\t{\n\t\t\tpurpose: \"summary\",\n\t\t\tpurposeDetail: \"compaction\",\n\t\t\toperationId: headers?.[MODEL_REQUEST_ID_HEADER],\n\t\t\tsemanticEdgeId: headers?.[MODEL_REQUEST_ID_HEADER],\n\t\t},\n\t);\n\n\tconst response = await completion;\n\tif (response.stopReason === \"error\") {\n\t\tthrow new Error(`Summarization failed: ${response.errorMessage || \"Unknown error\"}`);\n\t}\n\n\tconst textContent = response.content\n\t\t.filter((c): c is { type: \"text\"; text: string } => c.type === \"text\")\n\t\t.map((c) => c.text)\n\t\t.join(\"\\n\");\n\n\treturn captureSummarySlice({ summary: textContent, usage: response.usage }, requests, completion, \"history\");\n}\nexport interface CompactionPreparation {\n\t/** UUID of first entry to keep */\n\tfirstKeptEntryId: string;\n\t/** Messages that will be summarized and discarded */\n\tmessagesToSummarize: AgentMessage[];\n\t/** Messages that will be turned into turn prefix summary (if splitting) */\n\tturnPrefixMessages: AgentMessage[];\n\t/** Whether this is a split turn (cut point in middle of turn) */\n\tisSplitTurn: boolean;\n\ttokensBefore: number;\n\t/** Summary from previous compaction, for iterative update */\n\tpreviousSummary?: string;\n\t/** File operations extracted from messagesToSummarize */\n\tfileOps: FileOperations;\n\t/** Compaction settions from settings.jsonl\t*/\n\tsettings: CompactionSettings;\n}\n\n/** Prepare the actual selected epoch view. Entry IDs are only real cut anchors, never synthetic rows. */\nexport function prepareViewCompaction(\n\tmessages: readonly AgentMessage[],\n\tentryIds: readonly (string | undefined)[],\n\tpathEntries: SessionEntry[],\n\tsettings: CompactionSettings,\n\tmaxCutEntryId?: string,\n\tallowShortSession = false,\n\tbudgetPressure = false,\n): CompactionPreparation | undefined {\n\tif (messages.length !== entryIds.length) throw new Error(\"Compaction views do not match their source anchors\");\n\t// firstKeptEntryId restores a chronological source suffix, not a selected-view suffix.\n\t// Older pinned views cannot become cut anchors across omitted source messages.\n\tconst selectedIds = new Set(entryIds);\n\tconst suffixAnchors = new Set<string>();\n\tfor (let index = pathEntries.length - 1; index >= 0; index--) {\n\t\tconst entry = pathEntries[index];\n\t\tif (!getMessageFromEntryForCompaction(entry)) continue;\n\t\tif (!selectedIds.has(entry.id)) break;\n\t\tsuffixAnchors.add(entry.id);\n\t}\n\tlet cuts = messages.flatMap((message, index) =>\n\t\tentryIds[index] &&\n\t\tsuffixAnchors.has(entryIds[index]!) &&\n\t\tmessage.role !== \"toolResult\" &&\n\t\tmessage.role !== \"compactionSummary\"\n\t\t\t? [index]\n\t\t\t: [],\n\t);\n\tlet relaxRetention = false;\n\tif (budgetPressure) {\n\t\t// Only caller-confirmed public OVER may relax retention; source/tool groups remain whole.\n\t\tconst spans: [number, number][] = [];\n\t\tconst sourceStarts = new Map<string, number>();\n\t\tconst resultEnds = new Map<string, number>();\n\t\t// Public history renders original roles as custom text; use its captured source anchors.\n\t\tconst sourceMessages = new Map(pathEntries.map((entry) => [entry.id, getMessageFromEntry(entry)]));\n\t\tconst anchoredMessages = messages.map((message, index) => sourceMessages.get(entryIds[index] ?? \"\") ?? message);\n\t\tfor (const [index, message] of anchoredMessages.entries()) {\n\t\t\tconst entryId = entryIds[index];\n\t\t\tif (entryId) {\n\t\t\t\tconst start = sourceStarts.get(entryId);\n\t\t\t\tif (start === undefined) sourceStarts.set(entryId, index);\n\t\t\t\telse spans.push([start, index]);\n\t\t\t}\n\t\t\tif (message.role === \"toolResult\") resultEnds.set(message.toolCallId, index);\n\t\t}\n\t\tlet latestCompleteExchange = messages.length - 1;\n\t\tconst completedAssistants: number[] = [];\n\t\tfor (const [index, message] of anchoredMessages.entries()) {\n\t\t\tif (message.role !== \"assistant\") continue;\n\t\t\tconst calls = message.content.filter((block) => block.type === \"toolCall\");\n\t\t\tconst completeExchange = calls.length > 0 && calls.every((call) => (resultEnds.get(call.id) ?? -1) > index);\n\t\t\tif (completeExchange) latestCompleteExchange = index;\n\t\t\tif (completeExchange || (!calls.length && message.stopReason === \"stop\")) completedAssistants.push(index);\n\t\t\tfor (const call of calls) {\n\t\t\t\tconst end = resultEnds.get(call.id);\n\t\t\t\tif (end !== undefined && end > index) spans.push([index, end]);\n\t\t\t}\n\t\t}\n\t\t// A lone current instruction before its first exchange is not removable history.\n\t\trelaxRetention = completedAssistants.some((index) => index < latestCompleteExchange);\n\t\tif (relaxRetention) {\n\t\t\tcuts = cuts.filter(\n\t\t\t\t(candidate) =>\n\t\t\t\t\tcandidate <= latestCompleteExchange &&\n\t\t\t\t\tspans.every(([start, end]) => candidate <= start || candidate > end),\n\t\t\t);\n\t\t}\n\t}\n\tif (!cuts.length) return;\n\tconst tokenScale = compactionTokenScale(messages);\n\tlet cut = cuts[0];\n\tlet tokens = 0;\n\tfor (let index = messages.length - 1; index >= 0; index--) {\n\t\tif (!entryIds[index] || messages[index].role === \"compactionSummary\") continue;\n\t\ttokens += estimateTokens(messages[index]) * tokenScale;\n\t\tif (tokens >= settings.keepRecentTokens) {\n\t\t\t// Include the message that crossed the threshold and its preceding legal group boundary.\n\t\t\tcut = cuts.filter((candidate) => candidate <= index).at(-1) ?? cut;\n\t\t\tbreak;\n\t\t}\n\t}\n\tif (relaxRetention || (allowShortSession && cut === cuts[0])) cut = cuts.at(-1)!;\n\tif (maxCutEntryId !== undefined) {\n\t\tconst maximum = entryIds.indexOf(maxCutEntryId);\n\t\tconst lastAllowed = cuts.filter((candidate) => candidate <= maximum).at(-1);\n\t\tif (lastAllowed === undefined) throw new Error(\"Compaction recovery boundary is unavailable\");\n\t\tcut = Math.min(cut, lastAllowed);\n\t}\n\tlet turnStart = -1;\n\tif (messages[cut].role !== \"user\") {\n\t\tfor (let index = cut; index >= 0; index--) {\n\t\t\tif (entryIds[index] && [\"user\", \"custom\", \"branchSummary\", \"bashExecution\"].includes(messages[index].role)) {\n\t\t\t\tturnStart = index;\n\t\t\t\tbreak;\n\t\t\t}\n\t\t}\n\t}\n\tconst isSplitTurn = turnStart >= 0;\n\tconst historyEnd = isSplitTurn ? turnStart : cut;\n\tlet previousSummary: string | undefined;\n\tlet previousSummaryIndex = -1;\n\tfor (const [index, message] of messages.entries()) {\n\t\tif (message.role !== \"compactionSummary\") continue;\n\t\tpreviousSummary = message.summary;\n\t\tpreviousSummaryIndex = pathEntries.findIndex((entry) => entry.id === entryIds[index]);\n\t}\n\tconst historical = messages.filter(\n\t\t(message, index) => entryIds[index] && index < historyEnd && message.role !== \"compactionSummary\",\n\t);\n\tconst turnPrefixMessages = isSplitTurn\n\t\t? messages.filter(\n\t\t\t\t(message, index) =>\n\t\t\t\t\tentryIds[index] && index >= turnStart && index < cut && message.role !== \"compactionSummary\",\n\t\t\t)\n\t\t: [];\n\tif (!historical.length && !turnPrefixMessages.length && (budgetPressure || allowShortSession || !previousSummary))\n\t\treturn;\n\t// Virtual TaskFrame displays are continuity input, never a canonical suffix boundary.\n\tconst messagesToSummarize = messages.filter(\n\t\t(message, index) => message.role !== \"compactionSummary\" && (!entryIds[index] || index < historyEnd),\n\t);\n\tconst fileOps = extractFileOperations(messagesToSummarize, pathEntries, previousSummaryIndex);\n\tfor (const message of turnPrefixMessages) extractFileOpsFromMessage(message, fileOps);\n\treturn {\n\t\tfirstKeptEntryId: entryIds[cut]!,\n\t\tmessagesToSummarize: structuredClone(messagesToSummarize),\n\t\tturnPrefixMessages: structuredClone(turnPrefixMessages),\n\t\tisSplitTurn,\n\t\ttokensBefore: estimateContextTokens([...messages]).tokens,\n\t\tpreviousSummary,\n\t\tfileOps,\n\t\tsettings,\n\t};\n}\n\nexport function prepareCompaction(\n\tpathEntries: SessionEntry[],\n\tsettings: CompactionSettings,\n\tallowShortSession = false,\n): CompactionPreparation | undefined {\n\tif (pathEntries.length > 0 && pathEntries[pathEntries.length - 1].type === \"compaction\") {\n\t\treturn undefined;\n\t}\n\n\tlet prevCompactionIndex = -1;\n\tfor (let i = pathEntries.length - 1; i >= 0; i--) {\n\t\tif (pathEntries[i].type === \"compaction\") {\n\t\t\tprevCompactionIndex = i;\n\t\t\tbreak;\n\t\t}\n\t}\n\n\tlet previousSummary: string | undefined;\n\tlet boundaryStart = 0;\n\tif (prevCompactionIndex >= 0) {\n\t\tconst prevCompaction = pathEntries[prevCompactionIndex] as CompactionEntry;\n\t\tpreviousSummary = prevCompaction.summary;\n\t\tconst firstKeptEntryIndex = pathEntries.findIndex((entry) => entry.id === prevCompaction.firstKeptEntryId);\n\t\tboundaryStart = firstKeptEntryIndex >= 0 ? firstKeptEntryIndex : prevCompactionIndex + 1;\n\t}\n\tconst boundaryEnd = pathEntries.length;\n\n\tconst messages = buildSessionContext(pathEntries).messages;\n\tconst tokensBefore = estimateContextTokens(messages).tokens;\n\n\tconst cutPoint = findCutPoint(\n\t\tpathEntries,\n\t\tboundaryStart,\n\t\tboundaryEnd,\n\t\tsettings.keepRecentTokens,\n\t\tallowShortSession,\n\t\tcompactionTokenScale(messages),\n\t);\n\tconst firstKeptEntry = pathEntries[cutPoint.firstKeptEntryIndex];\n\tif (!firstKeptEntry?.id) {\n\t\treturn undefined; // Session needs migration\n\t}\n\tconst firstKeptEntryId = firstKeptEntry.id;\n\n\tconst historyEnd = cutPoint.isSplitTurn ? cutPoint.turnStartIndex : cutPoint.firstKeptEntryIndex;\n\tconst messagesToSummarize: AgentMessage[] = [];\n\tfor (let i = boundaryStart; i < historyEnd; i++) {\n\t\tconst msg = getMessageFromEntryForCompaction(pathEntries[i]);\n\t\tif (msg) messagesToSummarize.push(msg);\n\t}\n\tconst turnPrefixMessages: AgentMessage[] = [];\n\tif (cutPoint.isSplitTurn) {\n\t\tfor (let i = cutPoint.turnStartIndex; i < cutPoint.firstKeptEntryIndex; i++) {\n\t\t\tconst msg = getMessageFromEntryForCompaction(pathEntries[i]);\n\t\t\tif (msg) turnPrefixMessages.push(msg);\n\t\t}\n\t}\n\n\t// Avoid a compaction that would summarize no history.\n\tif (messagesToSummarize.length === 0 && turnPrefixMessages.length === 0 && (allowShortSession || !previousSummary)) {\n\t\treturn undefined;\n\t}\n\tconst fileOps = extractFileOperations(messagesToSummarize, pathEntries, prevCompactionIndex);\n\t// Split turns retain their suffix, but their prefix file operations still belong in the summary.\n\tif (cutPoint.isSplitTurn) {\n\t\tfor (const msg of turnPrefixMessages) {\n\t\t\textractFileOpsFromMessage(msg, fileOps);\n\t\t}\n\t}\n\n\treturn {\n\t\tfirstKeptEntryId,\n\t\tmessagesToSummarize,\n\t\tturnPrefixMessages,\n\t\tisSplitTurn: cutPoint.isSplitTurn,\n\t\ttokensBefore,\n\t\tpreviousSummary,\n\t\tfileOps,\n\t\tsettings,\n\t};\n}\nconst TURN_PREFIX_SUMMARIZATION_PROMPT = `This is the PREFIX of a turn that was too large to keep. The SUFFIX (recent work) is retained.\n\nSummarize the prefix to provide context for the retained suffix:\n\n## Original Request\n[What did the user ask for in this turn?]\n\n## Early Progress\n- [Key decisions and work done in the prefix]\n\n## Context for Suffix\n- [Information needed to understand the retained recent work]\n\nBe concise. Focus on what's needed to understand the kept suffix.`;\n\n/**\n * Generate summaries for compaction using prepared data.\n * Returns CompactionResult - SessionManager adds uuid/parentUuid when saving.\n *\n * @param preparation - Pre-calculated preparation from prepareCompaction()\n * @param customInstructions - Optional custom focus for the summary\n */\n/** Runs one summary wire call; hosts decorate each call with its own request identity. */\nexport type SummaryCallRunner = <T>(\n\tcall: (callHeaders: Record<string, string> | undefined) => Promise<T>,\n) => Promise<T>;\n\nexport async function compact(\n\tpreparation: CompactionPreparation,\n\tmodel: Model<any>,\n\tapiKey: string,\n\theaders?: Record<string, string>,\n\tcustomInstructions?: string,\n\tsignal?: AbortSignal,\n\tthinkingLevel?: ThinkingLevel,\n\tsummaryCall: SummaryCallRunner = (call) => call(headers),\n\trequests?: InferenceCoordinator,\n\tsummaryCapacity?: (wrapper: string) => number | undefined,\n): Promise<CompactionResult> {\n\tconst {\n\t\tfirstKeptEntryId,\n\t\tmessagesToSummarize,\n\t\tturnPrefixMessages,\n\t\tisSplitTurn,\n\t\ttokensBefore,\n\t\tpreviousSummary,\n\t\tfileOps,\n\t\tsettings,\n\t} = preparation;\n\tlet summary: string;\n\tconst slices: SummarySlice[] = [];\n\tconst { readFiles, modifiedFiles } = computeFileLists(fileOps);\n\tconst fileSummary = formatFileOperations(readFiles, modifiedFiles);\n\tconst split = isSplitTurn && turnPrefixMessages.length > 0;\n\tconst splitSeparator = \"\\n\\n---\\n\\n**Turn Context (split turn):**\\n\\n\";\n\tconst wrapper =\n\t\tCOMPACTION_SUMMARY_PREFIX +\n\t\t(split ? (messagesToSummarize.length ? \"\" : \"No prior history.\") + splitSeparator : \"\") +\n\t\tfileSummary +\n\t\tCOMPACTION_SUMMARY_SUFFIX;\n\tconst capacity = summaryCapacity?.(wrapper);\n\tconst historyNeeded = !split || messagesToSummarize.length > 0;\n\tconst calls = Number(historyNeeded) + Number(split);\n\tif (capacity !== undefined && capacity < calls)\n\t\tthrow new Error(\n\t\t\t\"Context capacity exceeded: required evidence, retained tail and summary wrapper leave no room for a summary.\",\n\t\t);\n\tconst historyLimit =\n\t\tcapacity === undefined\n\t\t\t? undefined\n\t\t\t: Math.min(\n\t\t\t\t\tMath.floor(0.8 * settings.reserveTokens),\n\t\t\t\t\tsplit && historyNeeded ? Math.max(1, Math.floor(capacity * 0.6)) : capacity,\n\t\t\t\t);\n\tconst turnLimit =\n\t\tcapacity === undefined\n\t\t\t? undefined\n\t\t\t: Math.min(Math.floor(0.5 * settings.reserveTokens), capacity - (historyNeeded ? historyLimit! : 0));\n\n\tif (split) {\n\t\t// Split turns make two wire calls with different bodies; each needs its own identity.\n\t\tconst [historyResult, turnPrefixResult] = await Promise.all([\n\t\t\tmessagesToSummarize.length > 0\n\t\t\t\t? summaryCall((callHeaders) =>\n\t\t\t\t\t\tgenerateSummary(\n\t\t\t\t\t\t\tmessagesToSummarize,\n\t\t\t\t\t\t\tmodel,\n\t\t\t\t\t\t\tsettings.reserveTokens,\n\t\t\t\t\t\t\tapiKey,\n\t\t\t\t\t\t\tcallHeaders,\n\t\t\t\t\t\t\tsignal,\n\t\t\t\t\t\t\tcustomInstructions,\n\t\t\t\t\t\t\tpreviousSummary,\n\t\t\t\t\t\t\tthinkingLevel,\n\t\t\t\t\t\t\trequests,\n\t\t\t\t\t\t\thistoryLimit,\n\t\t\t\t\t\t),\n\t\t\t\t\t)\n\t\t\t\t: Promise.resolve<SummarySlice>({ summary: \"No prior history.\" }),\n\t\t\tsummaryCall((callHeaders) =>\n\t\t\t\tgenerateTurnPrefixSummary(\n\t\t\t\t\tturnPrefixMessages,\n\t\t\t\t\tmodel,\n\t\t\t\t\tsettings.reserveTokens,\n\t\t\t\t\tapiKey,\n\t\t\t\t\tcallHeaders,\n\t\t\t\t\tsignal,\n\t\t\t\t\tthinkingLevel,\n\t\t\t\t\trequests,\n\t\t\t\t\tturnLimit,\n\t\t\t\t),\n\t\t\t),\n\t\t]);\n\t\tslices.push(historyResult, turnPrefixResult);\n\t\tsummary = `${historyResult.summary}${splitSeparator}${turnPrefixResult.summary}`;\n\t} else {\n\t\tconst result = await summaryCall((callHeaders) =>\n\t\t\tgenerateSummary(\n\t\t\t\tmessagesToSummarize,\n\t\t\t\tmodel,\n\t\t\t\tsettings.reserveTokens,\n\t\t\t\tapiKey,\n\t\t\t\tcallHeaders,\n\t\t\t\tsignal,\n\t\t\t\tcustomInstructions,\n\t\t\t\tpreviousSummary,\n\t\t\t\tthinkingLevel,\n\t\t\t\trequests,\n\t\t\t\thistoryLimit,\n\t\t\t),\n\t\t);\n\t\tslices.push(result);\n\t\tsummary = result.summary;\n\t}\n\tsummary += fileSummary;\n\n\tif (!firstKeptEntryId) {\n\t\tthrow new Error(\"First kept entry has no UUID - session may need migration\");\n\t}\n\n\tlet usage: Usage | undefined;\n\tfor (const slice of slices) {\n\t\tif (!slice.usage) continue;\n\t\tusage ??= emptyUsage();\n\t\taddAssistantUsage(usage, slice.usage);\n\t}\n\tconst result: CompactionResult = {\n\t\tsummary,\n\t\tfirstKeptEntryId,\n\t\ttokensBefore,\n\t\tdetails: { readFiles, modifiedFiles } as CompactionDetails,\n\t\tusage,\n\t};\n\tconst bindings = slices.flatMap((slice) => {\n\t\tconst captured = nativeSummarySlices.get(slice);\n\t\tnativeSummarySlices.delete(slice);\n\t\treturn captured?.summary === slice.summary ? [captured.binding] : [];\n\t});\n\tif (bindings.length) nativeCompactionResults.set(result, { summary, bindings });\n\treturn result;\n}\n\n/**\n * Generate a summary for a turn prefix (when splitting a turn).\n */\nasync function generateTurnPrefixSummary(\n\tmessages: AgentMessage[],\n\tmodel: Model<any>,\n\treserveTokens: number,\n\tapiKey: string,\n\theaders?: Record<string, string>,\n\tsignal?: AbortSignal,\n\tthinkingLevel?: ThinkingLevel,\n\trequests?: InferenceCoordinator,\n\toutputTokenLimit?: number,\n): Promise<SummarySlice> {\n\tconst maxTokens = outputTokenLimit ?? Math.floor(0.5 * reserveTokens); // Smaller budget for turn prefix\n\tconst llmMessages = convertToLlm(messages);\n\tconst conversationText = serializeConversation(llmMessages);\n\tconst promptText = `<conversation>\\n${conversationText}\\n</conversation>\\n\\n${TURN_PREFIX_SUMMARIZATION_PROMPT}`;\n\tconst summarizationMessages = [\n\t\t{\n\t\t\trole: \"user\" as const,\n\t\t\tcontent: [{ type: \"text\" as const, text: promptText }],\n\t\t\ttimestamp: Date.now(),\n\t\t},\n\t];\n\n\tconst completion = completeInference(\n\t\trequests,\n\t\tmodel,\n\t\t{ systemPrompt: SUMMARIZATION_SYSTEM_PROMPT, messages: summarizationMessages },\n\t\tmodel.reasoning && thinkingLevel && thinkingLevel !== \"off\"\n\t\t\t? { maxTokens, signal, apiKey, headers, reasoning: thinkingLevel }\n\t\t\t: { maxTokens, signal, apiKey, headers },\n\t\t{\n\t\t\tpurpose: \"summary\",\n\t\t\tpurposeDetail: \"compaction-turn-prefix\",\n\t\t\toperationId: headers?.[MODEL_REQUEST_ID_HEADER],\n\t\t\tsemanticEdgeId: headers?.[MODEL_REQUEST_ID_HEADER],\n\t\t},\n\t);\n\n\tconst response = await completion;\n\tif (response.stopReason === \"error\") {\n\t\tthrow new Error(`Turn prefix summarization failed: ${response.errorMessage || \"Unknown error\"}`);\n\t}\n\n\tconst slice: SummarySlice = {\n\t\tsummary: response.content\n\t\t\t.filter((c): c is { type: \"text\"; text: string } => c.type === \"text\")\n\t\t\t.map((c) => c.text)\n\t\t\t.join(\"\\n\"),\n\t\tusage: response.usage,\n\t};\n\treturn captureSummarySlice(slice, requests, completion, \"turn-prefix\");\n}\n"]}
1
+ {"version":3,"file":"compaction.d.ts","sourceRoot":"","sources":["../../../src/core/compaction/compaction.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Context compaction for long sessions.\n *\n * Pure functions for compaction logic. The session manager handles I/O,\n * and after compaction the session is reloaded.\n */\n\nimport type { AgentMessage, ThinkingLevel } from \"@ponythewhite/base-context-agent\";\nimport type { AssistantMessage, Model, Usage } from \"@ponythewhite/base-context-ai\";\nimport {\n\tcompleteInference,\n\tInferenceCoordinator,\n\ttype NativeCompactionOutputBinding,\n} from \"../inference-coordinator.js\";\nimport {\n\tCOMPACTION_SUMMARY_PREFIX,\n\tCOMPACTION_SUMMARY_SUFFIX,\n\tconvertToLlm,\n\tcreateBranchSummaryMessage,\n\tcreateCompactionSummaryMessage,\n\tcreateCustomMessage,\n\tHARNESS_SNAPSHOT_CUSTOM_TYPE,\n} from \"../messages.js\";\nimport type { NativeCompactionRequestOutputAssociation } from \"../request-events.js\";\nimport { MODEL_REQUEST_ID_HEADER } from \"../semantic-edges.js\";\nimport { buildSessionContext, type CompactionEntry, type SessionEntry } from \"../session-manager.js\";\nimport { TASK_FRAME_CUSTOM_TYPE } from \"../task-frame.js\";\nimport { addAssistantUsage, emptyUsage } from \"../usage.js\";\nimport {\n\tcomputeFileLists,\n\tcreateFileOps,\n\textractFileOpsFromMessage,\n\ttype FileOperations,\n\tformatFileOperations,\n\tSUMMARIZATION_SYSTEM_PROMPT,\n\tserializeConversation,\n} from \"./utils.js\";\n/** Details stored in CompactionEntry.details for file tracking */\nexport interface CompactionDetails {\n\treadFiles: string[];\n\tmodifiedFiles: string[];\n}\n\nexport interface SummarySlice {\n\tsummary: string;\n\tusage?: Usage;\n}\n\n// Identity follows the actual completion -> text slice -> built-in composition, never copied fields.\nconst nativeSummarySlices = new WeakMap<SummarySlice, { summary: string; binding: NativeCompactionOutputBinding }>();\nconst nativeCompactionResults = new WeakMap<object, { summary: string; bindings: NativeCompactionOutputBinding[] }>();\n\nfunction captureSummarySlice(\n\tslice: SummarySlice,\n\trequests: InferenceCoordinator | undefined,\n\tcompletion: Promise<AssistantMessage>,\n\tpart: NativeCompactionRequestOutputAssociation[\"part\"],\n): SummarySlice {\n\tconst binding =\n\t\trequests instanceof InferenceCoordinator\n\t\t\t? InferenceCoordinator.prototype.takeCompactionOutput.call(requests, completion)\n\t\t\t: undefined;\n\tif (binding?.output.part === part) nativeSummarySlices.set(slice, { summary: slice.summary, binding });\n\treturn slice;\n}\n\n/** Internal append input: only this exact built-in result and original sink can contribute links. */\nexport function takeCompactionRequestOutputs(\n\tresult: object | undefined,\n\tsink: object,\n\tsummary: string,\n): readonly NativeCompactionRequestOutputAssociation[] | undefined {\n\tconst captured = result ? nativeCompactionResults.get(result) : undefined;\n\tif (result) nativeCompactionResults.delete(result);\n\t// Equality only checks an already privately bound projection for mutation; it never discovers a link.\n\tif (!captured || captured.summary !== summary) return undefined;\n\tconst outputs = captured.bindings.filter((binding) => binding.sink === sink).map((binding) => binding.output);\n\treturn outputs.length ? outputs : undefined;\n}\n\n/**\n * Extract file operations from messages and previous compaction entries.\n */\n/** Preserve file operations recorded by prior compactions and current tool calls. */\nfunction extractFileOperations(\n\tmessages: AgentMessage[],\n\tentries: SessionEntry[],\n\tprevCompactionIndex: number,\n): FileOperations {\n\tconst fileOps = createFileOps();\n\tif (prevCompactionIndex >= 0) {\n\t\tconst prevCompaction = entries[prevCompactionIndex] as CompactionEntry;\n\t\tif (!prevCompaction.fromHook && prevCompaction.details) {\n\t\t\t// fromHook field kept for session file compatibility\n\t\t\tconst details = prevCompaction.details as CompactionDetails;\n\t\t\tif (Array.isArray(details.readFiles)) {\n\t\t\t\tfor (const f of details.readFiles) fileOps.read.add(f);\n\t\t\t}\n\t\t\tif (Array.isArray(details.modifiedFiles)) {\n\t\t\t\tfor (const f of details.modifiedFiles) fileOps.edited.add(f);\n\t\t\t}\n\t\t}\n\t}\n\tfor (const msg of messages) {\n\t\textractFileOpsFromMessage(msg, fileOps);\n\t}\n\n\treturn fileOps;\n}\n/**\n * Extract AgentMessage from an entry if it produces one.\n * Returns undefined for entries that don't contribute to LLM context.\n */\nfunction getMessageFromEntry(entry: SessionEntry): AgentMessage | undefined {\n\tif (entry.type === \"message\") {\n\t\treturn entry.message;\n\t}\n\tif (entry.type === \"custom_message\") {\n\t\treturn createCustomMessage(entry.customType, entry.content, entry.display, entry.details, entry.timestamp);\n\t}\n\tif (entry.type === \"branch_summary\") {\n\t\treturn createBranchSummaryMessage(entry.summary, entry.fromId, entry.timestamp);\n\t}\n\tif (entry.type === \"compaction\") {\n\t\treturn createCompactionSummaryMessage(\n\t\t\tentry.summary,\n\t\t\tentry.tokensBefore,\n\t\t\tentry.timestamp,\n\t\t\tentry.customInstructions,\n\t\t);\n\t}\n\treturn undefined;\n}\n\nfunction getMessageFromEntryForCompaction(entry: SessionEntry): AgentMessage | undefined {\n\tif (entry.type === \"compaction\") {\n\t\treturn undefined;\n\t}\n\treturn getMessageFromEntry(entry);\n}\n\n/** Result from compact() - SessionManager adds uuid/parentUuid when saving */\nexport interface CompactionResult<T = unknown> {\n\tsummary: string;\n\tfirstKeptEntryId: string;\n\t/** Prior-context estimate; null when the original context is not measurable. */\n\ttokensBefore: number | null;\n\t/** Extension-specific data (e.g., ArtifactIndex, version markers for structured compaction) */\n\tdetails?: T;\n\t/** What the summarization call(s) billed; persisted on the compaction entry. */\n\tusage?: Usage;\n}\nexport const COMPACT_SKILL_NAME = \"compact\";\n\nexport interface CompactionSettings {\n\tenabled: boolean;\n\treserveTokens: number;\n\tkeepRecentTokens: number;\n\t/** Soft working-context target; the model limit always takes precedence. */\n\ttargetTokens?: number | \"model-limit\";\n}\n\nexport const DEFAULT_COMPACTION_SETTINGS: CompactionSettings = {\n\tenabled: true,\n\treserveTokens: 16384,\n\tkeepRecentTokens: 20000,\n};\n/**\n * Calculate total context tokens from usage.\n * Uses the native totalTokens field when available, falls back to computing from components.\n *\n * Includes output: the assistant's response becomes part of the prompt on the next\n * request, so it counts toward the context the next turn will send.\n */\nexport function calculateContextTokens(usage: Usage): number {\n\treturn usage.totalTokens || usage.input + usage.output + usage.cacheRead + usage.cacheWrite;\n}\n\n/**\n * Get usage from an assistant message if available.\n * Skips aborted and error messages as they don't have valid usage data.\n */\nfunction getAssistantUsage(msg: AgentMessage): Usage | undefined {\n\tif (msg.role === \"assistant\" && \"usage\" in msg) {\n\t\tconst assistantMsg = msg as AssistantMessage;\n\t\tif (assistantMsg.stopReason !== \"aborted\" && assistantMsg.stopReason !== \"error\" && assistantMsg.usage) {\n\t\t\treturn assistantMsg.usage;\n\t\t}\n\t}\n\treturn undefined;\n}\n\n/**\n * Find the last non-aborted assistant message usage from session entries.\n */\nexport function getLastAssistantUsage(entries: SessionEntry[]): Usage | undefined {\n\tfor (let i = entries.length - 1; i >= 0; i--) {\n\t\tconst entry = entries[i];\n\t\tif (entry.type === \"message\") {\n\t\t\tconst usage = getAssistantUsage(entry.message);\n\t\t\tif (usage) return usage;\n\t\t}\n\t}\n\treturn undefined;\n}\n\nexport interface ContextUsageEstimate {\n\ttokens: number;\n\tusageTokens: number;\n\ttrailingTokens: number;\n\tlastUsageIndex: number | null;\n}\n\nfunction getLastAssistantUsageInfo(messages: readonly AgentMessage[]): { usage: Usage; index: number } | undefined {\n\tfor (let i = messages.length - 1; i >= 0; i--) {\n\t\tconst usage = getAssistantUsage(messages[i]);\n\t\tif (usage) return { usage, index: i };\n\t}\n\treturn undefined;\n}\n\n/**\n * Estimate context tokens from messages, using the last assistant usage when available.\n * If there are messages after the last usage, estimate their tokens with estimateTokens.\n */\nexport function estimateContextTokens(messages: AgentMessage[]): ContextUsageEstimate {\n\tconst usageInfo = getLastAssistantUsageInfo(messages);\n\n\tif (!usageInfo) {\n\t\tlet estimated = 0;\n\t\tfor (const message of messages) {\n\t\t\testimated += estimateTokens(message);\n\t\t}\n\t\treturn {\n\t\t\ttokens: estimated,\n\t\t\tusageTokens: 0,\n\t\t\ttrailingTokens: estimated,\n\t\t\tlastUsageIndex: null,\n\t\t};\n\t}\n\n\tconst usageTokens = calculateContextTokens(usageInfo.usage);\n\tlet trailingTokens = 0;\n\tfor (let i = usageInfo.index + 1; i < messages.length; i++) {\n\t\ttrailingTokens += estimateTokens(messages[i]);\n\t}\n\n\treturn {\n\t\ttokens: usageTokens + trailingTokens,\n\t\tusageTokens,\n\t\ttrailingTokens,\n\t\tlastUsageIndex: usageInfo.index,\n\t};\n}\n\n/**\n * Check if compaction should trigger based on context usage.\n */\nexport function shouldCompact(\n\tcontextTokens: number,\n\tcontextWindow: number,\n\tsettings: CompactionSettings,\n\tfixedContextTokens = 0,\n): boolean {\n\tif (!settings.enabled) return false;\n\tif (contextWindow <= 0) return false;\n\tconst modelLimit = contextWindow - settings.reserveTokens;\n\t// Fixed instructions cannot shrink. Leave meaningful room for retained history\n\t// and new work above that floor, without relaxing the actual model ceiling.\n\tconst target =\n\t\tsettings.targetTokens === \"model-limit\"\n\t\t\t? modelLimit\n\t\t\t: Math.max(\n\t\t\t\t\tsettings.targetTokens ?? Math.floor(contextWindow * 0.9),\n\t\t\t\t\tfixedContextTokens + 4 * settings.keepRecentTokens,\n\t\t\t\t);\n\treturn contextTokens > Math.min(target, modelLimit);\n}\n\n/** Chars/4 heuristic, not a provider count. Ordinary history never raises this floor. */\nexport function estimateFixedCompactionTokens(\n\tsystemPrompt: string,\n\ttools: readonly { name: string; description: string; parameters: unknown }[],\n\tmessages: readonly AgentMessage[],\n): number {\n\tconst toolSchemas = tools.map(({ name, description, parameters }) => ({ name, description, parameters }));\n\tlet tokens = Math.ceil((systemPrompt.length + (tools.length ? JSON.stringify(toolSchemas).length : 0)) / 4);\n\tlet hasHarnessSnapshot = false;\n\tfor (let index = messages.length - 1; index >= 0; index--) {\n\t\tconst message = messages[index];\n\t\tif (message.role !== \"custom\") continue;\n\t\t// A TaskFrame may contain dependent sparse revisions; all current pieces count.\n\t\tif (message.customType === TASK_FRAME_CUSTOM_TYPE) tokens += estimateTokens(message);\n\t\telse if (message.customType === HARNESS_SNAPSHOT_CUSTOM_TYPE && !hasHarnessSnapshot) {\n\t\t\ttokens += estimateTokens(message);\n\t\t\thasHarnessSnapshot = true;\n\t\t}\n\t}\n\treturn tokens;\n}\n/** Raise suffix estimates when observed usage exceeds chars/4 for the same prefix. */\nfunction compactionTokenScale(messages: readonly AgentMessage[]): number {\n\tconst usageInfo = getLastAssistantUsageInfo(messages);\n\tif (!usageInfo) return 1;\n\tlet estimatedTokens = 0;\n\tfor (let index = 0; index <= usageInfo.index; index++) {\n\t\testimatedTokens += estimateTokens(messages[index]);\n\t}\n\treturn estimatedTokens > 0 ? Math.max(1, calculateContextTokens(usageInfo.usage) / estimatedTokens) : 1;\n}\n\n/** Estimate message tokens with chars/4; dense content can exceed this heuristic. */\nexport function estimateTokens(message: AgentMessage): number {\n\tlet chars = 0;\n\n\tswitch (message.role) {\n\t\tcase \"user\": {\n\t\t\tconst content = (message as { content: string | Array<{ type: string; text?: string }> }).content;\n\t\t\tif (typeof content === \"string\") {\n\t\t\t\tchars = content.length;\n\t\t\t} else if (Array.isArray(content)) {\n\t\t\t\tfor (const block of content) {\n\t\t\t\t\tif (block.type === \"text\" && block.text) {\n\t\t\t\t\t\tchars += block.text.length;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t\treturn Math.ceil(chars / 4);\n\t\t}\n\t\tcase \"assistant\": {\n\t\t\tconst assistant = message as AssistantMessage;\n\t\t\tfor (const block of assistant.content) {\n\t\t\t\tif (block.type === \"text\") {\n\t\t\t\t\tchars += block.text.length;\n\t\t\t\t} else if (block.type === \"thinking\") {\n\t\t\t\t\tchars += block.thinking.length;\n\t\t\t\t} else if (block.type === \"toolCall\") {\n\t\t\t\t\tchars += block.name.length + JSON.stringify(block.arguments).length;\n\t\t\t\t}\n\t\t\t}\n\t\t\treturn Math.ceil(chars / 4);\n\t\t}\n\t\tcase \"custom\":\n\t\tcase \"toolResult\": {\n\t\t\tif (typeof message.content === \"string\") {\n\t\t\t\tchars = message.content.length;\n\t\t\t} else {\n\t\t\t\tfor (const block of message.content) {\n\t\t\t\t\tif (block.type === \"text\" && block.text) {\n\t\t\t\t\t\tchars += block.text.length;\n\t\t\t\t\t}\n\t\t\t\t\tif (block.type === \"image\") {\n\t\t\t\t\t\tchars += 4800; // Estimate images as 4000 chars, or 1200 tokens\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t\treturn Math.ceil(chars / 4);\n\t\t}\n\t\tcase \"bashExecution\": {\n\t\t\tchars = message.command.length + message.output.length;\n\t\t\treturn Math.ceil(chars / 4);\n\t\t}\n\t\tcase \"branchSummary\":\n\t\tcase \"compactionSummary\": {\n\t\t\tchars = message.summary.length;\n\t\t\treturn Math.ceil(chars / 4);\n\t\t}\n\t}\n\n\treturn 0;\n}\n\n/**\n * Find valid cut points: indices of user, assistant, custom, or bashExecution messages.\n * Never cut at tool results (they must follow their tool call).\n * When we cut at an assistant message with tool calls, its tool results follow it\n * and will be kept.\n * BashExecutionMessage is treated like a user message (user-initiated context).\n */\nfunction findValidCutPoints(entries: SessionEntry[], startIndex: number, endIndex: number): number[] {\n\tconst cutPoints: number[] = [];\n\tfor (let i = startIndex; i < endIndex; i++) {\n\t\tconst entry = entries[i];\n\t\tswitch (entry.type) {\n\t\t\tcase \"message\": {\n\t\t\t\tconst role = entry.message.role;\n\t\t\t\tswitch (role) {\n\t\t\t\t\tcase \"bashExecution\":\n\t\t\t\t\tcase \"custom\":\n\t\t\t\t\tcase \"branchSummary\":\n\t\t\t\t\tcase \"compactionSummary\":\n\t\t\t\t\tcase \"user\":\n\t\t\t\t\tcase \"assistant\":\n\t\t\t\t\t\tcutPoints.push(i);\n\t\t\t\t\t\tbreak;\n\t\t\t\t\tcase \"toolResult\":\n\t\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t\tbreak;\n\t\t\t}\n\t\t\tcase \"thinking_level_change\":\n\t\t\tcase \"model_change\":\n\t\t\tcase \"compaction\":\n\t\t\tcase \"branch_summary\":\n\t\t\tcase \"custom\":\n\t\t\tcase \"custom_message\":\n\t\t\tcase \"label\":\n\t\t\tcase \"session_info\":\n\t\t\t\tbreak;\n\t\t}\n\t\t// Branch summaries and custom messages are user-role turn boundaries.\n\t\tif (entry.type === \"branch_summary\" || entry.type === \"custom_message\") {\n\t\t\tcutPoints.push(i);\n\t\t}\n\t}\n\treturn cutPoints;\n}\n\n/**\n * Find the user message (or bashExecution) that starts the turn containing the given entry index.\n * Returns -1 if no turn start found before the index.\n * BashExecutionMessage is treated like a user message for turn boundaries.\n */\nexport function findTurnStartIndex(entries: SessionEntry[], entryIndex: number, startIndex: number): number {\n\tfor (let i = entryIndex; i >= startIndex; i--) {\n\t\tconst entry = entries[i];\n\t\tif (entry.type === \"branch_summary\" || entry.type === \"custom_message\") {\n\t\t\treturn i;\n\t\t}\n\t\tif (entry.type === \"message\") {\n\t\t\tconst role = entry.message.role;\n\t\t\tif (role === \"user\" || role === \"bashExecution\") {\n\t\t\t\treturn i;\n\t\t\t}\n\t\t}\n\t}\n\treturn -1;\n}\n\nexport interface CutPointResult {\n\t/** Index of first entry to keep */\n\tfirstKeptEntryIndex: number;\n\t/** Index of user message that starts the turn being split, or -1 if not splitting */\n\tturnStartIndex: number;\n\t/** Whether this cut splits a turn (cut point is not a user message) */\n\tisSplitTurn: boolean;\n}\n\n/**\n * Find the cut point in session entries that keeps approximately `keepRecentTokens`.\n *\n * Algorithm: Walk backwards from newest, accumulating estimated message sizes.\n * Stop when we've accumulated >= keepRecentTokens. Cut at that point.\n *\n * Can cut at user OR assistant messages (never tool results). When cutting at an\n * assistant message with tool calls, its tool results come after and will be kept.\n *\n * Returns CutPointResult with:\n * - firstKeptEntryIndex: the entry index to start keeping from\n * - turnStartIndex: if cutting mid-turn, the user message that started that turn\n * - isSplitTurn: whether we're cutting in the middle of a turn\n *\n * Only considers entries between `startIndex` and `endIndex` (exclusive).\n */\nexport function findCutPoint(\n\tentries: SessionEntry[],\n\tstartIndex: number,\n\tendIndex: number,\n\tkeepRecentTokens: number,\n\tallowShortSession = false,\n\ttokenScale = 1,\n): CutPointResult {\n\tconst cutPoints = findValidCutPoints(entries, startIndex, endIndex);\n\n\tif (cutPoints.length === 0) {\n\t\treturn { firstKeptEntryIndex: startIndex, turnStartIndex: -1, isSplitTurn: false };\n\t}\n\tlet accumulatedTokens = 0;\n\tlet cutIndex = cutPoints[0]; // Default: keep from first message (not header)\n\n\tfor (let i = endIndex - 1; i >= startIndex; i--) {\n\t\tconst entry = entries[i];\n\t\tif (entry.type !== \"message\") continue;\n\t\tconst messageTokens = estimateTokens(entry.message) * tokenScale;\n\t\taccumulatedTokens += messageTokens;\n\t\tif (accumulatedTokens >= keepRecentTokens) {\n\t\t\tcutIndex = cutPoints.filter((candidate) => candidate <= i).at(-1) ?? cutIndex;\n\t\t\tbreak;\n\t\t}\n\t}\n\tif (allowShortSession && cutIndex === cutPoints[0]) {\n\t\t// An explicit compact may summarize a short prefix, but must keep a real suffix.\n\t\tconst latest = cutPoints\n\t\t\t.filter((index) => {\n\t\t\t\tconst entry = entries[index];\n\t\t\t\treturn entry.type !== \"message\" || entry.message.role !== \"compactionSummary\";\n\t\t\t})\n\t\t\t.at(-1);\n\t\tif (latest !== undefined) cutIndex = latest;\n\t}\n\twhile (cutIndex > startIndex) {\n\t\tconst prevEntry = entries[cutIndex - 1];\n\t\tif (prevEntry.type === \"compaction\") {\n\t\t\tbreak;\n\t\t}\n\t\tif (prevEntry.type === \"message\") {\n\t\t\tbreak;\n\t\t}\n\t\tcutIndex--;\n\t}\n\tconst cutEntry = entries[cutIndex];\n\tconst isUserMessage = cutEntry.type === \"message\" && cutEntry.message.role === \"user\";\n\t// A cut in a non-user turn requires a prefix summary.\n\tconst turnStartIndex = isUserMessage ? -1 : findTurnStartIndex(entries, cutIndex, startIndex);\n\n\treturn {\n\t\tfirstKeptEntryIndex: cutIndex,\n\t\tturnStartIndex,\n\t\tisSplitTurn: !isUserMessage && turnStartIndex !== -1,\n\t};\n}\nconst SUMMARIZATION_PROMPT = `The messages above are a conversation to summarize. Create a structured context checkpoint summary that another LLM will use to continue the work.\n\nUse this EXACT format:\n\n## Goal\n[What is the user trying to accomplish? Can be multiple items if the session covers different tasks.]\n\n## Constraints & Preferences\n- [Any constraints, preferences, or requirements mentioned by user]\n- [Or \"(none)\" if none were mentioned]\n\n## Progress\n### Done\n- [x] [Completed tasks/changes]\n\n### In Progress\n- [ ] [Current work]\n\n### Blocked\n- [Issues preventing progress, if any]\n\n## Key Decisions\n- **[Decision]**: [Brief rationale]\n\n## Next Steps\n1. [Ordered list of what should happen next]\n\n## Critical Context\n- [Any data, examples, or references needed to continue]\n- [Or \"(none)\" if not applicable]\n\nKeep each section concise. Preserve exact file paths, function names, and error messages.`;\n\nconst RUNTIME_STATE_SUMMARY_NOTE =\n\t\"Runtime note: this summary does not establish whether a Python kernel is live or whether its variables, imports, helpers, or jobs remain available. Preserve useful names and their last observed state, including uncertainty. Use current runtime reports before relying on them; do not infer either survival or loss from compaction.\";\n\nconst UPDATE_SUMMARIZATION_PROMPT = `The messages above are NEW conversation messages to incorporate into the existing summary provided in <previous-summary> tags.\n\nUpdate the existing structured summary with new information. RULES:\n- PRESERVE all existing information from the previous summary\n- ADD new progress, decisions, and context from the new messages\n- UPDATE the Progress section: move items from \"In Progress\" to \"Done\" when completed\n- UPDATE \"Next Steps\" based on what was accomplished\n- PRESERVE exact file paths, function names, and error messages\n- If something is no longer relevant, you may remove it\n\nUse this EXACT format:\n\n## Goal\n[Preserve existing goals, add new ones if the task expanded]\n\n## Constraints & Preferences\n- [Preserve existing, add new ones discovered]\n\n## Progress\n### Done\n- [x] [Include previously done items AND newly completed items]\n\n### In Progress\n- [ ] [Current work - update based on progress]\n\n### Blocked\n- [Current blockers - remove if resolved]\n\n## Key Decisions\n- **[Decision]**: [Brief rationale] (preserve all previous, add new)\n\n## Next Steps\n1. [Update based on current state]\n\n## Critical Context\n- [Preserve important context, add new if needed]\n\nKeep each section concise. Preserve exact file paths, function names, and error messages.`;\n\n/**\n * Build the instruction portion of the summarization prompt: the initial or\n * update template, optional user instructions, and the runtime-state qualification.\n */\nexport function buildSummarizationPrompt(customInstructions?: string, previousSummary?: string): string {\n\tlet basePrompt = previousSummary ? UPDATE_SUMMARIZATION_PROMPT : SUMMARIZATION_PROMPT;\n\tif (customInstructions) {\n\t\tbasePrompt += `\\n\\n<user-instructions>\\nThe user provided these instructions for this summary. Follow them with high priority while keeping the section format above: emphasize what they ask to focus on, and preserve verbatim anything they ask to remember.\\n${customInstructions}\\n</user-instructions>`;\n\t}\n\treturn `${basePrompt}\\n\\n${RUNTIME_STATE_SUMMARY_NOTE}`;\n}\n\n/**\n * Generate a summary of the conversation using the LLM.\n * If previousSummary is provided, uses the update prompt to merge.\n */\nexport async function generateSummary(\n\tcurrentMessages: AgentMessage[],\n\tmodel: Model<any>,\n\treserveTokens: number,\n\tapiKey: string,\n\theaders?: Record<string, string>,\n\tsignal?: AbortSignal,\n\tcustomInstructions?: string,\n\tpreviousSummary?: string,\n\tthinkingLevel?: ThinkingLevel,\n\trequests?: InferenceCoordinator,\n\toutputTokenLimit?: number,\n): Promise<SummarySlice> {\n\tconst maxTokens = outputTokenLimit ?? Math.floor(0.8 * reserveTokens);\n\n\tconst basePrompt = buildSummarizationPrompt(customInstructions, previousSummary);\n\t// Serialize before the LLM call so it summarizes rather than continues this conversation.\n\tconst llmMessages = convertToLlm(currentMessages);\n\tconst conversationText = serializeConversation(llmMessages);\n\tlet promptText = `<conversation>\\n${conversationText}\\n</conversation>\\n\\n`;\n\tif (previousSummary) {\n\t\tpromptText += `<previous-summary>\\n${previousSummary}\\n</previous-summary>\\n\\n`;\n\t}\n\tpromptText += basePrompt;\n\n\tconst summarizationMessages = [\n\t\t{\n\t\t\trole: \"user\" as const,\n\t\t\tcontent: [{ type: \"text\" as const, text: promptText }],\n\t\t\ttimestamp: Date.now(),\n\t\t},\n\t];\n\n\tconst completionOptions =\n\t\tmodel.reasoning && thinkingLevel && thinkingLevel !== \"off\"\n\t\t\t? { maxTokens, signal, apiKey, headers, reasoning: thinkingLevel }\n\t\t\t: { maxTokens, signal, apiKey, headers };\n\n\tconst completion = completeInference(\n\t\trequests,\n\t\tmodel,\n\t\t{ systemPrompt: SUMMARIZATION_SYSTEM_PROMPT, messages: summarizationMessages },\n\t\tcompletionOptions,\n\t\t{\n\t\t\tpurpose: \"summary\",\n\t\t\tpurposeDetail: \"compaction\",\n\t\t\toperationId: headers?.[MODEL_REQUEST_ID_HEADER],\n\t\t\tsemanticEdgeId: headers?.[MODEL_REQUEST_ID_HEADER],\n\t\t},\n\t);\n\n\tconst response = await completion;\n\tif (response.stopReason === \"error\") {\n\t\tthrow new Error(`Summarization failed: ${response.errorMessage || \"Unknown error\"}`);\n\t}\n\n\tconst textContent = response.content\n\t\t.filter((c): c is { type: \"text\"; text: string } => c.type === \"text\")\n\t\t.map((c) => c.text)\n\t\t.join(\"\\n\");\n\n\treturn captureSummarySlice({ summary: textContent, usage: response.usage }, requests, completion, \"history\");\n}\nexport interface CompactionPreparation {\n\t/** UUID of first entry to keep */\n\tfirstKeptEntryId: string;\n\t/** Messages that will be summarized and discarded */\n\tmessagesToSummarize: AgentMessage[];\n\t/** Messages that will be turned into turn prefix summary (if splitting) */\n\tturnPrefixMessages: AgentMessage[];\n\t/** Whether this is a split turn (cut point in middle of turn) */\n\tisSplitTurn: boolean;\n\ttokensBefore: number;\n\t/** Summary from previous compaction, for iterative update */\n\tpreviousSummary?: string;\n\t/** File operations extracted from messagesToSummarize */\n\tfileOps: FileOperations;\n\t/** Compaction settions from settings.jsonl\t*/\n\tsettings: CompactionSettings;\n}\n\n/** Prepare the actual selected epoch view. Entry IDs are only real cut anchors, never synthetic rows. */\nexport function prepareViewCompaction(\n\tmessages: readonly AgentMessage[],\n\tentryIds: readonly (string | undefined)[],\n\tpathEntries: SessionEntry[],\n\tsettings: CompactionSettings,\n\tmaxCutEntryId?: string,\n\tallowShortSession = false,\n\tbudgetPressure = false,\n): CompactionPreparation | undefined {\n\tif (messages.length !== entryIds.length) throw new Error(\"Compaction views do not match their source anchors\");\n\t// firstKeptEntryId restores a chronological source suffix, not a selected-view suffix.\n\t// Older pinned views cannot become cut anchors across omitted source messages.\n\tconst selectedIds = new Set(entryIds);\n\tconst suffixAnchors = new Set<string>();\n\tfor (let index = pathEntries.length - 1; index >= 0; index--) {\n\t\tconst entry = pathEntries[index];\n\t\tif (!getMessageFromEntryForCompaction(entry)) continue;\n\t\tif (!selectedIds.has(entry.id)) break;\n\t\tsuffixAnchors.add(entry.id);\n\t}\n\tlet cuts = messages.flatMap((message, index) =>\n\t\tentryIds[index] &&\n\t\tsuffixAnchors.has(entryIds[index]!) &&\n\t\tmessage.role !== \"toolResult\" &&\n\t\tmessage.role !== \"compactionSummary\"\n\t\t\t? [index]\n\t\t\t: [],\n\t);\n\tlet relaxRetention = false;\n\tif (budgetPressure) {\n\t\t// Only caller-confirmed public OVER may relax retention; source/tool groups remain whole.\n\t\tconst spans: [number, number][] = [];\n\t\tconst sourceStarts = new Map<string, number>();\n\t\tconst resultEnds = new Map<string, number>();\n\t\t// Public history renders original roles as custom text; use its captured source anchors.\n\t\tconst sourceMessages = new Map(pathEntries.map((entry) => [entry.id, getMessageFromEntry(entry)]));\n\t\tconst anchoredMessages = messages.map((message, index) => sourceMessages.get(entryIds[index] ?? \"\") ?? message);\n\t\tfor (const [index, message] of anchoredMessages.entries()) {\n\t\t\tconst entryId = entryIds[index];\n\t\t\tif (entryId) {\n\t\t\t\tconst start = sourceStarts.get(entryId);\n\t\t\t\tif (start === undefined) sourceStarts.set(entryId, index);\n\t\t\t\telse spans.push([start, index]);\n\t\t\t}\n\t\t\tif (message.role === \"toolResult\") resultEnds.set(message.toolCallId, index);\n\t\t}\n\t\tlet latestCompleteExchange = messages.length - 1;\n\t\tconst completedAssistants: number[] = [];\n\t\tfor (const [index, message] of anchoredMessages.entries()) {\n\t\t\tif (message.role !== \"assistant\") continue;\n\t\t\tconst calls = message.content.filter((block) => block.type === \"toolCall\");\n\t\t\tconst completeExchange = calls.length > 0 && calls.every((call) => (resultEnds.get(call.id) ?? -1) > index);\n\t\t\tif (completeExchange) latestCompleteExchange = index;\n\t\t\tif (completeExchange || (!calls.length && message.stopReason === \"stop\")) completedAssistants.push(index);\n\t\t\tfor (const call of calls) {\n\t\t\t\tconst end = resultEnds.get(call.id);\n\t\t\t\tif (end !== undefined && end > index) spans.push([index, end]);\n\t\t\t}\n\t\t}\n\t\t// A lone current instruction before its first exchange is not removable history.\n\t\trelaxRetention = completedAssistants.some((index) => index < latestCompleteExchange);\n\t\tif (relaxRetention) {\n\t\t\tcuts = cuts.filter(\n\t\t\t\t(candidate) =>\n\t\t\t\t\tcandidate <= latestCompleteExchange &&\n\t\t\t\t\tspans.every(([start, end]) => candidate <= start || candidate > end),\n\t\t\t);\n\t\t}\n\t}\n\tif (!cuts.length) return;\n\tconst tokenScale = compactionTokenScale(messages);\n\tlet cut = cuts[0];\n\tlet tokens = 0;\n\tfor (let index = messages.length - 1; index >= 0; index--) {\n\t\tif (!entryIds[index] || messages[index].role === \"compactionSummary\") continue;\n\t\ttokens += estimateTokens(messages[index]) * tokenScale;\n\t\tif (tokens >= settings.keepRecentTokens) {\n\t\t\t// Include the message that crossed the threshold and its preceding legal group boundary.\n\t\t\tcut = cuts.filter((candidate) => candidate <= index).at(-1) ?? cut;\n\t\t\tbreak;\n\t\t}\n\t}\n\tif (relaxRetention || (allowShortSession && cut === cuts[0])) cut = cuts.at(-1)!;\n\tif (maxCutEntryId !== undefined) {\n\t\tconst maximum = entryIds.indexOf(maxCutEntryId);\n\t\tconst lastAllowed = cuts.filter((candidate) => candidate <= maximum).at(-1);\n\t\tif (lastAllowed === undefined) throw new Error(\"Compaction recovery boundary is unavailable\");\n\t\tcut = Math.min(cut, lastAllowed);\n\t}\n\tlet turnStart = -1;\n\tif (messages[cut].role !== \"user\") {\n\t\tfor (let index = cut; index >= 0; index--) {\n\t\t\tif (entryIds[index] && [\"user\", \"custom\", \"branchSummary\", \"bashExecution\"].includes(messages[index].role)) {\n\t\t\t\tturnStart = index;\n\t\t\t\tbreak;\n\t\t\t}\n\t\t}\n\t}\n\tconst isSplitTurn = turnStart >= 0;\n\tconst historyEnd = isSplitTurn ? turnStart : cut;\n\tlet previousSummary: string | undefined;\n\tlet previousSummaryIndex = -1;\n\tfor (const [index, message] of messages.entries()) {\n\t\tif (message.role !== \"compactionSummary\") continue;\n\t\tpreviousSummary = message.summary;\n\t\tpreviousSummaryIndex = pathEntries.findIndex((entry) => entry.id === entryIds[index]);\n\t}\n\tconst historical = messages.filter(\n\t\t(message, index) => entryIds[index] && index < historyEnd && message.role !== \"compactionSummary\",\n\t);\n\tconst turnPrefixMessages = isSplitTurn\n\t\t? messages.filter(\n\t\t\t\t(message, index) =>\n\t\t\t\t\tentryIds[index] && index >= turnStart && index < cut && message.role !== \"compactionSummary\",\n\t\t\t)\n\t\t: [];\n\tif (!historical.length && !turnPrefixMessages.length && (budgetPressure || allowShortSession || !previousSummary))\n\t\treturn;\n\t// Virtual TaskFrame displays are continuity input, never a canonical suffix boundary.\n\tconst messagesToSummarize = messages.filter(\n\t\t(message, index) => message.role !== \"compactionSummary\" && (!entryIds[index] || index < historyEnd),\n\t);\n\tconst fileOps = extractFileOperations(messagesToSummarize, pathEntries, previousSummaryIndex);\n\tfor (const message of turnPrefixMessages) extractFileOpsFromMessage(message, fileOps);\n\treturn {\n\t\tfirstKeptEntryId: entryIds[cut]!,\n\t\tmessagesToSummarize: structuredClone(messagesToSummarize),\n\t\tturnPrefixMessages: structuredClone(turnPrefixMessages),\n\t\tisSplitTurn,\n\t\ttokensBefore: estimateContextTokens([...messages]).tokens,\n\t\tpreviousSummary,\n\t\tfileOps,\n\t\tsettings,\n\t};\n}\n\nexport function prepareCompaction(\n\tpathEntries: SessionEntry[],\n\tsettings: CompactionSettings,\n\tallowShortSession = false,\n): CompactionPreparation | undefined {\n\tif (pathEntries.length > 0 && pathEntries[pathEntries.length - 1].type === \"compaction\") {\n\t\treturn undefined;\n\t}\n\n\tlet prevCompactionIndex = -1;\n\tfor (let i = pathEntries.length - 1; i >= 0; i--) {\n\t\tif (pathEntries[i].type === \"compaction\") {\n\t\t\tprevCompactionIndex = i;\n\t\t\tbreak;\n\t\t}\n\t}\n\n\tlet previousSummary: string | undefined;\n\tlet boundaryStart = 0;\n\tif (prevCompactionIndex >= 0) {\n\t\tconst prevCompaction = pathEntries[prevCompactionIndex] as CompactionEntry;\n\t\tpreviousSummary = prevCompaction.summary;\n\t\tconst firstKeptEntryIndex = pathEntries.findIndex((entry) => entry.id === prevCompaction.firstKeptEntryId);\n\t\tboundaryStart = firstKeptEntryIndex >= 0 ? firstKeptEntryIndex : prevCompactionIndex + 1;\n\t}\n\tconst boundaryEnd = pathEntries.length;\n\n\tconst messages = buildSessionContext(pathEntries).messages;\n\tconst tokensBefore = estimateContextTokens(messages).tokens;\n\n\tconst cutPoint = findCutPoint(\n\t\tpathEntries,\n\t\tboundaryStart,\n\t\tboundaryEnd,\n\t\tsettings.keepRecentTokens,\n\t\tallowShortSession,\n\t\tcompactionTokenScale(messages),\n\t);\n\tconst firstKeptEntry = pathEntries[cutPoint.firstKeptEntryIndex];\n\tif (!firstKeptEntry?.id) {\n\t\treturn undefined; // Session needs migration\n\t}\n\tconst firstKeptEntryId = firstKeptEntry.id;\n\n\tconst historyEnd = cutPoint.isSplitTurn ? cutPoint.turnStartIndex : cutPoint.firstKeptEntryIndex;\n\tconst messagesToSummarize: AgentMessage[] = [];\n\tfor (let i = boundaryStart; i < historyEnd; i++) {\n\t\tconst msg = getMessageFromEntryForCompaction(pathEntries[i]);\n\t\tif (msg) messagesToSummarize.push(msg);\n\t}\n\tconst turnPrefixMessages: AgentMessage[] = [];\n\tif (cutPoint.isSplitTurn) {\n\t\tfor (let i = cutPoint.turnStartIndex; i < cutPoint.firstKeptEntryIndex; i++) {\n\t\t\tconst msg = getMessageFromEntryForCompaction(pathEntries[i]);\n\t\t\tif (msg) turnPrefixMessages.push(msg);\n\t\t}\n\t}\n\n\t// Avoid a compaction that would summarize no history.\n\tif (messagesToSummarize.length === 0 && turnPrefixMessages.length === 0 && (allowShortSession || !previousSummary)) {\n\t\treturn undefined;\n\t}\n\tconst fileOps = extractFileOperations(messagesToSummarize, pathEntries, prevCompactionIndex);\n\t// Split turns retain their suffix, but their prefix file operations still belong in the summary.\n\tif (cutPoint.isSplitTurn) {\n\t\tfor (const msg of turnPrefixMessages) {\n\t\t\textractFileOpsFromMessage(msg, fileOps);\n\t\t}\n\t}\n\n\treturn {\n\t\tfirstKeptEntryId,\n\t\tmessagesToSummarize,\n\t\tturnPrefixMessages,\n\t\tisSplitTurn: cutPoint.isSplitTurn,\n\t\ttokensBefore,\n\t\tpreviousSummary,\n\t\tfileOps,\n\t\tsettings,\n\t};\n}\nconst TURN_PREFIX_SUMMARIZATION_PROMPT = `This is the PREFIX of a turn that was too large to keep. The SUFFIX (recent work) is retained.\n\nSummarize the prefix to provide context for the retained suffix:\n\n## Original Request\n[What did the user ask for in this turn?]\n\n## Early Progress\n- [Key decisions and work done in the prefix]\n\n## Context for Suffix\n- [Information needed to understand the retained recent work]\n\nBe concise. Focus on what's needed to understand the kept suffix.`;\n\n/**\n * Generate summaries for compaction using prepared data.\n * Returns CompactionResult - SessionManager adds uuid/parentUuid when saving.\n *\n * @param preparation - Pre-calculated preparation from prepareCompaction()\n * @param customInstructions - Optional custom focus for the summary\n */\n/** Runs one summary wire call; hosts decorate each call with its own request identity. */\nexport type SummaryCallRunner = <T>(\n\tcall: (callHeaders: Record<string, string> | undefined) => Promise<T>,\n) => Promise<T>;\n\nexport async function compact(\n\tpreparation: CompactionPreparation,\n\tmodel: Model<any>,\n\tapiKey: string,\n\theaders?: Record<string, string>,\n\tcustomInstructions?: string,\n\tsignal?: AbortSignal,\n\tthinkingLevel?: ThinkingLevel,\n\tsummaryCall: SummaryCallRunner = (call) => call(headers),\n\trequests?: InferenceCoordinator,\n\tsummaryCapacity?: (wrapper: string) => number | undefined,\n): Promise<CompactionResult> {\n\tconst {\n\t\tfirstKeptEntryId,\n\t\tmessagesToSummarize,\n\t\tturnPrefixMessages,\n\t\tisSplitTurn,\n\t\ttokensBefore,\n\t\tpreviousSummary,\n\t\tfileOps,\n\t\tsettings,\n\t} = preparation;\n\tlet summary: string;\n\tconst slices: SummarySlice[] = [];\n\tconst { readFiles, modifiedFiles } = computeFileLists(fileOps);\n\tconst fileSummary = formatFileOperations(readFiles, modifiedFiles);\n\tconst split = isSplitTurn && turnPrefixMessages.length > 0;\n\tconst splitSeparator = \"\\n\\n---\\n\\n**Turn Context (split turn):**\\n\\n\";\n\tconst wrapper =\n\t\tCOMPACTION_SUMMARY_PREFIX +\n\t\t(split ? (messagesToSummarize.length ? \"\" : \"No prior history.\") + splitSeparator : \"\") +\n\t\tfileSummary +\n\t\tCOMPACTION_SUMMARY_SUFFIX;\n\tconst capacity = summaryCapacity?.(wrapper);\n\tconst historyNeeded = !split || messagesToSummarize.length > 0;\n\tconst calls = Number(historyNeeded) + Number(split);\n\tif (capacity !== undefined && capacity < calls)\n\t\tthrow new Error(\n\t\t\t\"Context capacity exceeded: required evidence, retained tail and summary wrapper leave no room for a summary.\",\n\t\t);\n\tconst historyLimit =\n\t\tcapacity === undefined\n\t\t\t? undefined\n\t\t\t: Math.min(\n\t\t\t\t\tMath.floor(0.8 * settings.reserveTokens),\n\t\t\t\t\tsplit && historyNeeded ? Math.max(1, Math.floor(capacity * 0.6)) : capacity,\n\t\t\t\t);\n\tconst turnLimit =\n\t\tcapacity === undefined\n\t\t\t? undefined\n\t\t\t: Math.min(Math.floor(0.5 * settings.reserveTokens), capacity - (historyNeeded ? historyLimit! : 0));\n\n\tif (split) {\n\t\t// Split turns make two wire calls with different bodies; each needs its own identity.\n\t\tconst [historyResult, turnPrefixResult] = await Promise.all([\n\t\t\tmessagesToSummarize.length > 0\n\t\t\t\t? summaryCall((callHeaders) =>\n\t\t\t\t\t\tgenerateSummary(\n\t\t\t\t\t\t\tmessagesToSummarize,\n\t\t\t\t\t\t\tmodel,\n\t\t\t\t\t\t\tsettings.reserveTokens,\n\t\t\t\t\t\t\tapiKey,\n\t\t\t\t\t\t\tcallHeaders,\n\t\t\t\t\t\t\tsignal,\n\t\t\t\t\t\t\tcustomInstructions,\n\t\t\t\t\t\t\tpreviousSummary,\n\t\t\t\t\t\t\tthinkingLevel,\n\t\t\t\t\t\t\trequests,\n\t\t\t\t\t\t\thistoryLimit,\n\t\t\t\t\t\t),\n\t\t\t\t\t)\n\t\t\t\t: Promise.resolve<SummarySlice>({ summary: \"No prior history.\" }),\n\t\t\tsummaryCall((callHeaders) =>\n\t\t\t\tgenerateTurnPrefixSummary(\n\t\t\t\t\tturnPrefixMessages,\n\t\t\t\t\tmodel,\n\t\t\t\t\tsettings.reserveTokens,\n\t\t\t\t\tapiKey,\n\t\t\t\t\tcallHeaders,\n\t\t\t\t\tsignal,\n\t\t\t\t\tthinkingLevel,\n\t\t\t\t\trequests,\n\t\t\t\t\tturnLimit,\n\t\t\t\t),\n\t\t\t),\n\t\t]);\n\t\tslices.push(historyResult, turnPrefixResult);\n\t\tsummary = `${historyResult.summary}${splitSeparator}${turnPrefixResult.summary}`;\n\t} else {\n\t\tconst result = await summaryCall((callHeaders) =>\n\t\t\tgenerateSummary(\n\t\t\t\tmessagesToSummarize,\n\t\t\t\tmodel,\n\t\t\t\tsettings.reserveTokens,\n\t\t\t\tapiKey,\n\t\t\t\tcallHeaders,\n\t\t\t\tsignal,\n\t\t\t\tcustomInstructions,\n\t\t\t\tpreviousSummary,\n\t\t\t\tthinkingLevel,\n\t\t\t\trequests,\n\t\t\t\thistoryLimit,\n\t\t\t),\n\t\t);\n\t\tslices.push(result);\n\t\tsummary = result.summary;\n\t}\n\tsummary += fileSummary;\n\n\tif (!firstKeptEntryId) {\n\t\tthrow new Error(\"First kept entry has no UUID - session may need migration\");\n\t}\n\n\tlet usage: Usage | undefined;\n\tfor (const slice of slices) {\n\t\tif (!slice.usage) continue;\n\t\tusage ??= emptyUsage();\n\t\taddAssistantUsage(usage, slice.usage);\n\t}\n\tconst result: CompactionResult = {\n\t\tsummary,\n\t\tfirstKeptEntryId,\n\t\ttokensBefore,\n\t\tdetails: { readFiles, modifiedFiles } as CompactionDetails,\n\t\tusage,\n\t};\n\tconst bindings = slices.flatMap((slice) => {\n\t\tconst captured = nativeSummarySlices.get(slice);\n\t\tnativeSummarySlices.delete(slice);\n\t\treturn captured?.summary === slice.summary ? [captured.binding] : [];\n\t});\n\tif (bindings.length) nativeCompactionResults.set(result, { summary, bindings });\n\treturn result;\n}\n\n/**\n * Generate a summary for a turn prefix (when splitting a turn).\n */\nasync function generateTurnPrefixSummary(\n\tmessages: AgentMessage[],\n\tmodel: Model<any>,\n\treserveTokens: number,\n\tapiKey: string,\n\theaders?: Record<string, string>,\n\tsignal?: AbortSignal,\n\tthinkingLevel?: ThinkingLevel,\n\trequests?: InferenceCoordinator,\n\toutputTokenLimit?: number,\n): Promise<SummarySlice> {\n\tconst maxTokens = outputTokenLimit ?? Math.floor(0.5 * reserveTokens); // Smaller budget for turn prefix\n\tconst llmMessages = convertToLlm(messages);\n\tconst conversationText = serializeConversation(llmMessages);\n\tconst promptText = `<conversation>\\n${conversationText}\\n</conversation>\\n\\n${TURN_PREFIX_SUMMARIZATION_PROMPT}`;\n\tconst summarizationMessages = [\n\t\t{\n\t\t\trole: \"user\" as const,\n\t\t\tcontent: [{ type: \"text\" as const, text: promptText }],\n\t\t\ttimestamp: Date.now(),\n\t\t},\n\t];\n\n\tconst completion = completeInference(\n\t\trequests,\n\t\tmodel,\n\t\t{ systemPrompt: SUMMARIZATION_SYSTEM_PROMPT, messages: summarizationMessages },\n\t\tmodel.reasoning && thinkingLevel && thinkingLevel !== \"off\"\n\t\t\t? { maxTokens, signal, apiKey, headers, reasoning: thinkingLevel }\n\t\t\t: { maxTokens, signal, apiKey, headers },\n\t\t{\n\t\t\tpurpose: \"summary\",\n\t\t\tpurposeDetail: \"compaction-turn-prefix\",\n\t\t\toperationId: headers?.[MODEL_REQUEST_ID_HEADER],\n\t\t\tsemanticEdgeId: headers?.[MODEL_REQUEST_ID_HEADER],\n\t\t},\n\t);\n\n\tconst response = await completion;\n\tif (response.stopReason === \"error\") {\n\t\tthrow new Error(`Turn prefix summarization failed: ${response.errorMessage || \"Unknown error\"}`);\n\t}\n\n\tconst slice: SummarySlice = {\n\t\tsummary: response.content\n\t\t\t.filter((c): c is { type: \"text\"; text: string } => c.type === \"text\")\n\t\t\t.map((c) => c.text)\n\t\t\t.join(\"\\n\"),\n\t\tusage: response.usage,\n\t};\n\treturn captureSummarySlice(slice, requests, completion, \"turn-prefix\");\n}\n"]}
@@ -178,7 +178,7 @@ export function shouldCompact(contextTokens, contextWindow, settings, fixedConte
178
178
  // and new work above that floor, without relaxing the actual model ceiling.
179
179
  const target = settings.targetTokens === "model-limit"
180
180
  ? modelLimit
181
- : Math.max(settings.targetTokens ?? Math.min(96_000, contextWindow / 2), fixedContextTokens + 4 * settings.keepRecentTokens);
181
+ : Math.max(settings.targetTokens ?? Math.floor(contextWindow * 0.9), fixedContextTokens + 4 * settings.keepRecentTokens);
182
182
  return contextTokens > Math.min(target, modelLimit);
183
183
  }
184
184
  /** Chars/4 heuristic, not a provider count. Ordinary history never raises this floor. */
@@ -1 +1 @@
1
- {"version":3,"file":"compaction.js","sourceRoot":"","sources":["../../../src/core/compaction/compaction.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Context compaction for long sessions.\n *\n * Pure functions for compaction logic. The session manager handles I/O,\n * and after compaction the session is reloaded.\n */\n\nimport type { AgentMessage, ThinkingLevel } from \"@ponythewhite/base-context-agent\";\nimport type { AssistantMessage, Model, Usage } from \"@ponythewhite/base-context-ai\";\nimport {\n\tcompleteInference,\n\tInferenceCoordinator,\n\ttype NativeCompactionOutputBinding,\n} from \"../inference-coordinator.js\";\nimport {\n\tCOMPACTION_SUMMARY_PREFIX,\n\tCOMPACTION_SUMMARY_SUFFIX,\n\tconvertToLlm,\n\tcreateBranchSummaryMessage,\n\tcreateCompactionSummaryMessage,\n\tcreateCustomMessage,\n\tHARNESS_SNAPSHOT_CUSTOM_TYPE,\n} from \"../messages.js\";\nimport type { NativeCompactionRequestOutputAssociation } from \"../request-events.js\";\nimport { MODEL_REQUEST_ID_HEADER } from \"../semantic-edges.js\";\nimport { buildSessionContext, type CompactionEntry, type SessionEntry } from \"../session-manager.js\";\nimport { TASK_FRAME_CUSTOM_TYPE } from \"../task-frame.js\";\nimport { addAssistantUsage, emptyUsage } from \"../usage.js\";\nimport {\n\tcomputeFileLists,\n\tcreateFileOps,\n\textractFileOpsFromMessage,\n\ttype FileOperations,\n\tformatFileOperations,\n\tSUMMARIZATION_SYSTEM_PROMPT,\n\tserializeConversation,\n} from \"./utils.js\";\n/** Details stored in CompactionEntry.details for file tracking */\nexport interface CompactionDetails {\n\treadFiles: string[];\n\tmodifiedFiles: string[];\n}\n\nexport interface SummarySlice {\n\tsummary: string;\n\tusage?: Usage;\n}\n\n// Identity follows the actual completion -> text slice -> built-in composition, never copied fields.\nconst nativeSummarySlices = new WeakMap<SummarySlice, { summary: string; binding: NativeCompactionOutputBinding }>();\nconst nativeCompactionResults = new WeakMap<object, { summary: string; bindings: NativeCompactionOutputBinding[] }>();\n\nfunction captureSummarySlice(\n\tslice: SummarySlice,\n\trequests: InferenceCoordinator | undefined,\n\tcompletion: Promise<AssistantMessage>,\n\tpart: NativeCompactionRequestOutputAssociation[\"part\"],\n): SummarySlice {\n\tconst binding =\n\t\trequests instanceof InferenceCoordinator\n\t\t\t? InferenceCoordinator.prototype.takeCompactionOutput.call(requests, completion)\n\t\t\t: undefined;\n\tif (binding?.output.part === part) nativeSummarySlices.set(slice, { summary: slice.summary, binding });\n\treturn slice;\n}\n\n/** Internal append input: only this exact built-in result and original sink can contribute links. */\nexport function takeCompactionRequestOutputs(\n\tresult: object | undefined,\n\tsink: object,\n\tsummary: string,\n): readonly NativeCompactionRequestOutputAssociation[] | undefined {\n\tconst captured = result ? nativeCompactionResults.get(result) : undefined;\n\tif (result) nativeCompactionResults.delete(result);\n\t// Equality only checks an already privately bound projection for mutation; it never discovers a link.\n\tif (!captured || captured.summary !== summary) return undefined;\n\tconst outputs = captured.bindings.filter((binding) => binding.sink === sink).map((binding) => binding.output);\n\treturn outputs.length ? outputs : undefined;\n}\n\n/**\n * Extract file operations from messages and previous compaction entries.\n */\n/** Preserve file operations recorded by prior compactions and current tool calls. */\nfunction extractFileOperations(\n\tmessages: AgentMessage[],\n\tentries: SessionEntry[],\n\tprevCompactionIndex: number,\n): FileOperations {\n\tconst fileOps = createFileOps();\n\tif (prevCompactionIndex >= 0) {\n\t\tconst prevCompaction = entries[prevCompactionIndex] as CompactionEntry;\n\t\tif (!prevCompaction.fromHook && prevCompaction.details) {\n\t\t\t// fromHook field kept for session file compatibility\n\t\t\tconst details = prevCompaction.details as CompactionDetails;\n\t\t\tif (Array.isArray(details.readFiles)) {\n\t\t\t\tfor (const f of details.readFiles) fileOps.read.add(f);\n\t\t\t}\n\t\t\tif (Array.isArray(details.modifiedFiles)) {\n\t\t\t\tfor (const f of details.modifiedFiles) fileOps.edited.add(f);\n\t\t\t}\n\t\t}\n\t}\n\tfor (const msg of messages) {\n\t\textractFileOpsFromMessage(msg, fileOps);\n\t}\n\n\treturn fileOps;\n}\n/**\n * Extract AgentMessage from an entry if it produces one.\n * Returns undefined for entries that don't contribute to LLM context.\n */\nfunction getMessageFromEntry(entry: SessionEntry): AgentMessage | undefined {\n\tif (entry.type === \"message\") {\n\t\treturn entry.message;\n\t}\n\tif (entry.type === \"custom_message\") {\n\t\treturn createCustomMessage(entry.customType, entry.content, entry.display, entry.details, entry.timestamp);\n\t}\n\tif (entry.type === \"branch_summary\") {\n\t\treturn createBranchSummaryMessage(entry.summary, entry.fromId, entry.timestamp);\n\t}\n\tif (entry.type === \"compaction\") {\n\t\treturn createCompactionSummaryMessage(\n\t\t\tentry.summary,\n\t\t\tentry.tokensBefore,\n\t\t\tentry.timestamp,\n\t\t\tentry.customInstructions,\n\t\t);\n\t}\n\treturn undefined;\n}\n\nfunction getMessageFromEntryForCompaction(entry: SessionEntry): AgentMessage | undefined {\n\tif (entry.type === \"compaction\") {\n\t\treturn undefined;\n\t}\n\treturn getMessageFromEntry(entry);\n}\n\n/** Result from compact() - SessionManager adds uuid/parentUuid when saving */\nexport interface CompactionResult<T = unknown> {\n\tsummary: string;\n\tfirstKeptEntryId: string;\n\t/** Prior-context estimate; null when the original context is not measurable. */\n\ttokensBefore: number | null;\n\t/** Extension-specific data (e.g., ArtifactIndex, version markers for structured compaction) */\n\tdetails?: T;\n\t/** What the summarization call(s) billed; persisted on the compaction entry. */\n\tusage?: Usage;\n}\nexport const COMPACT_SKILL_NAME = \"compact\";\n\nexport interface CompactionSettings {\n\tenabled: boolean;\n\treserveTokens: number;\n\tkeepRecentTokens: number;\n\t/** Soft working-context target; the model limit always takes precedence. */\n\ttargetTokens?: number | \"model-limit\";\n}\n\nexport const DEFAULT_COMPACTION_SETTINGS: CompactionSettings = {\n\tenabled: true,\n\treserveTokens: 16384,\n\tkeepRecentTokens: 20000,\n};\n/**\n * Calculate total context tokens from usage.\n * Uses the native totalTokens field when available, falls back to computing from components.\n *\n * Includes output: the assistant's response becomes part of the prompt on the next\n * request, so it counts toward the context the next turn will send.\n */\nexport function calculateContextTokens(usage: Usage): number {\n\treturn usage.totalTokens || usage.input + usage.output + usage.cacheRead + usage.cacheWrite;\n}\n\n/**\n * Get usage from an assistant message if available.\n * Skips aborted and error messages as they don't have valid usage data.\n */\nfunction getAssistantUsage(msg: AgentMessage): Usage | undefined {\n\tif (msg.role === \"assistant\" && \"usage\" in msg) {\n\t\tconst assistantMsg = msg as AssistantMessage;\n\t\tif (assistantMsg.stopReason !== \"aborted\" && assistantMsg.stopReason !== \"error\" && assistantMsg.usage) {\n\t\t\treturn assistantMsg.usage;\n\t\t}\n\t}\n\treturn undefined;\n}\n\n/**\n * Find the last non-aborted assistant message usage from session entries.\n */\nexport function getLastAssistantUsage(entries: SessionEntry[]): Usage | undefined {\n\tfor (let i = entries.length - 1; i >= 0; i--) {\n\t\tconst entry = entries[i];\n\t\tif (entry.type === \"message\") {\n\t\t\tconst usage = getAssistantUsage(entry.message);\n\t\t\tif (usage) return usage;\n\t\t}\n\t}\n\treturn undefined;\n}\n\nexport interface ContextUsageEstimate {\n\ttokens: number;\n\tusageTokens: number;\n\ttrailingTokens: number;\n\tlastUsageIndex: number | null;\n}\n\nfunction getLastAssistantUsageInfo(messages: readonly AgentMessage[]): { usage: Usage; index: number } | undefined {\n\tfor (let i = messages.length - 1; i >= 0; i--) {\n\t\tconst usage = getAssistantUsage(messages[i]);\n\t\tif (usage) return { usage, index: i };\n\t}\n\treturn undefined;\n}\n\n/**\n * Estimate context tokens from messages, using the last assistant usage when available.\n * If there are messages after the last usage, estimate their tokens with estimateTokens.\n */\nexport function estimateContextTokens(messages: AgentMessage[]): ContextUsageEstimate {\n\tconst usageInfo = getLastAssistantUsageInfo(messages);\n\n\tif (!usageInfo) {\n\t\tlet estimated = 0;\n\t\tfor (const message of messages) {\n\t\t\testimated += estimateTokens(message);\n\t\t}\n\t\treturn {\n\t\t\ttokens: estimated,\n\t\t\tusageTokens: 0,\n\t\t\ttrailingTokens: estimated,\n\t\t\tlastUsageIndex: null,\n\t\t};\n\t}\n\n\tconst usageTokens = calculateContextTokens(usageInfo.usage);\n\tlet trailingTokens = 0;\n\tfor (let i = usageInfo.index + 1; i < messages.length; i++) {\n\t\ttrailingTokens += estimateTokens(messages[i]);\n\t}\n\n\treturn {\n\t\ttokens: usageTokens + trailingTokens,\n\t\tusageTokens,\n\t\ttrailingTokens,\n\t\tlastUsageIndex: usageInfo.index,\n\t};\n}\n\n/**\n * Check if compaction should trigger based on context usage.\n */\nexport function shouldCompact(\n\tcontextTokens: number,\n\tcontextWindow: number,\n\tsettings: CompactionSettings,\n\tfixedContextTokens = 0,\n): boolean {\n\tif (!settings.enabled) return false;\n\tif (contextWindow <= 0) return false;\n\tconst modelLimit = contextWindow - settings.reserveTokens;\n\t// Fixed instructions cannot shrink. Leave meaningful room for retained history\n\t// and new work above that floor, without relaxing the actual model ceiling.\n\tconst target =\n\t\tsettings.targetTokens === \"model-limit\"\n\t\t\t? modelLimit\n\t\t\t: Math.max(\n\t\t\t\t\tsettings.targetTokens ?? Math.min(96_000, contextWindow / 2),\n\t\t\t\t\tfixedContextTokens + 4 * settings.keepRecentTokens,\n\t\t\t\t);\n\treturn contextTokens > Math.min(target, modelLimit);\n}\n\n/** Chars/4 heuristic, not a provider count. Ordinary history never raises this floor. */\nexport function estimateFixedCompactionTokens(\n\tsystemPrompt: string,\n\ttools: readonly { name: string; description: string; parameters: unknown }[],\n\tmessages: readonly AgentMessage[],\n): number {\n\tconst toolSchemas = tools.map(({ name, description, parameters }) => ({ name, description, parameters }));\n\tlet tokens = Math.ceil((systemPrompt.length + (tools.length ? JSON.stringify(toolSchemas).length : 0)) / 4);\n\tlet hasHarnessSnapshot = false;\n\tfor (let index = messages.length - 1; index >= 0; index--) {\n\t\tconst message = messages[index];\n\t\tif (message.role !== \"custom\") continue;\n\t\t// A TaskFrame may contain dependent sparse revisions; all current pieces count.\n\t\tif (message.customType === TASK_FRAME_CUSTOM_TYPE) tokens += estimateTokens(message);\n\t\telse if (message.customType === HARNESS_SNAPSHOT_CUSTOM_TYPE && !hasHarnessSnapshot) {\n\t\t\ttokens += estimateTokens(message);\n\t\t\thasHarnessSnapshot = true;\n\t\t}\n\t}\n\treturn tokens;\n}\n/** Raise suffix estimates when observed usage exceeds chars/4 for the same prefix. */\nfunction compactionTokenScale(messages: readonly AgentMessage[]): number {\n\tconst usageInfo = getLastAssistantUsageInfo(messages);\n\tif (!usageInfo) return 1;\n\tlet estimatedTokens = 0;\n\tfor (let index = 0; index <= usageInfo.index; index++) {\n\t\testimatedTokens += estimateTokens(messages[index]);\n\t}\n\treturn estimatedTokens > 0 ? Math.max(1, calculateContextTokens(usageInfo.usage) / estimatedTokens) : 1;\n}\n\n/** Estimate message tokens with chars/4; dense content can exceed this heuristic. */\nexport function estimateTokens(message: AgentMessage): number {\n\tlet chars = 0;\n\n\tswitch (message.role) {\n\t\tcase \"user\": {\n\t\t\tconst content = (message as { content: string | Array<{ type: string; text?: string }> }).content;\n\t\t\tif (typeof content === \"string\") {\n\t\t\t\tchars = content.length;\n\t\t\t} else if (Array.isArray(content)) {\n\t\t\t\tfor (const block of content) {\n\t\t\t\t\tif (block.type === \"text\" && block.text) {\n\t\t\t\t\t\tchars += block.text.length;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t\treturn Math.ceil(chars / 4);\n\t\t}\n\t\tcase \"assistant\": {\n\t\t\tconst assistant = message as AssistantMessage;\n\t\t\tfor (const block of assistant.content) {\n\t\t\t\tif (block.type === \"text\") {\n\t\t\t\t\tchars += block.text.length;\n\t\t\t\t} else if (block.type === \"thinking\") {\n\t\t\t\t\tchars += block.thinking.length;\n\t\t\t\t} else if (block.type === \"toolCall\") {\n\t\t\t\t\tchars += block.name.length + JSON.stringify(block.arguments).length;\n\t\t\t\t}\n\t\t\t}\n\t\t\treturn Math.ceil(chars / 4);\n\t\t}\n\t\tcase \"custom\":\n\t\tcase \"toolResult\": {\n\t\t\tif (typeof message.content === \"string\") {\n\t\t\t\tchars = message.content.length;\n\t\t\t} else {\n\t\t\t\tfor (const block of message.content) {\n\t\t\t\t\tif (block.type === \"text\" && block.text) {\n\t\t\t\t\t\tchars += block.text.length;\n\t\t\t\t\t}\n\t\t\t\t\tif (block.type === \"image\") {\n\t\t\t\t\t\tchars += 4800; // Estimate images as 4000 chars, or 1200 tokens\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t\treturn Math.ceil(chars / 4);\n\t\t}\n\t\tcase \"bashExecution\": {\n\t\t\tchars = message.command.length + message.output.length;\n\t\t\treturn Math.ceil(chars / 4);\n\t\t}\n\t\tcase \"branchSummary\":\n\t\tcase \"compactionSummary\": {\n\t\t\tchars = message.summary.length;\n\t\t\treturn Math.ceil(chars / 4);\n\t\t}\n\t}\n\n\treturn 0;\n}\n\n/**\n * Find valid cut points: indices of user, assistant, custom, or bashExecution messages.\n * Never cut at tool results (they must follow their tool call).\n * When we cut at an assistant message with tool calls, its tool results follow it\n * and will be kept.\n * BashExecutionMessage is treated like a user message (user-initiated context).\n */\nfunction findValidCutPoints(entries: SessionEntry[], startIndex: number, endIndex: number): number[] {\n\tconst cutPoints: number[] = [];\n\tfor (let i = startIndex; i < endIndex; i++) {\n\t\tconst entry = entries[i];\n\t\tswitch (entry.type) {\n\t\t\tcase \"message\": {\n\t\t\t\tconst role = entry.message.role;\n\t\t\t\tswitch (role) {\n\t\t\t\t\tcase \"bashExecution\":\n\t\t\t\t\tcase \"custom\":\n\t\t\t\t\tcase \"branchSummary\":\n\t\t\t\t\tcase \"compactionSummary\":\n\t\t\t\t\tcase \"user\":\n\t\t\t\t\tcase \"assistant\":\n\t\t\t\t\t\tcutPoints.push(i);\n\t\t\t\t\t\tbreak;\n\t\t\t\t\tcase \"toolResult\":\n\t\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t\tbreak;\n\t\t\t}\n\t\t\tcase \"thinking_level_change\":\n\t\t\tcase \"model_change\":\n\t\t\tcase \"compaction\":\n\t\t\tcase \"branch_summary\":\n\t\t\tcase \"custom\":\n\t\t\tcase \"custom_message\":\n\t\t\tcase \"label\":\n\t\t\tcase \"session_info\":\n\t\t\t\tbreak;\n\t\t}\n\t\t// Branch summaries and custom messages are user-role turn boundaries.\n\t\tif (entry.type === \"branch_summary\" || entry.type === \"custom_message\") {\n\t\t\tcutPoints.push(i);\n\t\t}\n\t}\n\treturn cutPoints;\n}\n\n/**\n * Find the user message (or bashExecution) that starts the turn containing the given entry index.\n * Returns -1 if no turn start found before the index.\n * BashExecutionMessage is treated like a user message for turn boundaries.\n */\nexport function findTurnStartIndex(entries: SessionEntry[], entryIndex: number, startIndex: number): number {\n\tfor (let i = entryIndex; i >= startIndex; i--) {\n\t\tconst entry = entries[i];\n\t\tif (entry.type === \"branch_summary\" || entry.type === \"custom_message\") {\n\t\t\treturn i;\n\t\t}\n\t\tif (entry.type === \"message\") {\n\t\t\tconst role = entry.message.role;\n\t\t\tif (role === \"user\" || role === \"bashExecution\") {\n\t\t\t\treturn i;\n\t\t\t}\n\t\t}\n\t}\n\treturn -1;\n}\n\nexport interface CutPointResult {\n\t/** Index of first entry to keep */\n\tfirstKeptEntryIndex: number;\n\t/** Index of user message that starts the turn being split, or -1 if not splitting */\n\tturnStartIndex: number;\n\t/** Whether this cut splits a turn (cut point is not a user message) */\n\tisSplitTurn: boolean;\n}\n\n/**\n * Find the cut point in session entries that keeps approximately `keepRecentTokens`.\n *\n * Algorithm: Walk backwards from newest, accumulating estimated message sizes.\n * Stop when we've accumulated >= keepRecentTokens. Cut at that point.\n *\n * Can cut at user OR assistant messages (never tool results). When cutting at an\n * assistant message with tool calls, its tool results come after and will be kept.\n *\n * Returns CutPointResult with:\n * - firstKeptEntryIndex: the entry index to start keeping from\n * - turnStartIndex: if cutting mid-turn, the user message that started that turn\n * - isSplitTurn: whether we're cutting in the middle of a turn\n *\n * Only considers entries between `startIndex` and `endIndex` (exclusive).\n */\nexport function findCutPoint(\n\tentries: SessionEntry[],\n\tstartIndex: number,\n\tendIndex: number,\n\tkeepRecentTokens: number,\n\tallowShortSession = false,\n\ttokenScale = 1,\n): CutPointResult {\n\tconst cutPoints = findValidCutPoints(entries, startIndex, endIndex);\n\n\tif (cutPoints.length === 0) {\n\t\treturn { firstKeptEntryIndex: startIndex, turnStartIndex: -1, isSplitTurn: false };\n\t}\n\tlet accumulatedTokens = 0;\n\tlet cutIndex = cutPoints[0]; // Default: keep from first message (not header)\n\n\tfor (let i = endIndex - 1; i >= startIndex; i--) {\n\t\tconst entry = entries[i];\n\t\tif (entry.type !== \"message\") continue;\n\t\tconst messageTokens = estimateTokens(entry.message) * tokenScale;\n\t\taccumulatedTokens += messageTokens;\n\t\tif (accumulatedTokens >= keepRecentTokens) {\n\t\t\tcutIndex = cutPoints.filter((candidate) => candidate <= i).at(-1) ?? cutIndex;\n\t\t\tbreak;\n\t\t}\n\t}\n\tif (allowShortSession && cutIndex === cutPoints[0]) {\n\t\t// An explicit compact may summarize a short prefix, but must keep a real suffix.\n\t\tconst latest = cutPoints\n\t\t\t.filter((index) => {\n\t\t\t\tconst entry = entries[index];\n\t\t\t\treturn entry.type !== \"message\" || entry.message.role !== \"compactionSummary\";\n\t\t\t})\n\t\t\t.at(-1);\n\t\tif (latest !== undefined) cutIndex = latest;\n\t}\n\twhile (cutIndex > startIndex) {\n\t\tconst prevEntry = entries[cutIndex - 1];\n\t\tif (prevEntry.type === \"compaction\") {\n\t\t\tbreak;\n\t\t}\n\t\tif (prevEntry.type === \"message\") {\n\t\t\tbreak;\n\t\t}\n\t\tcutIndex--;\n\t}\n\tconst cutEntry = entries[cutIndex];\n\tconst isUserMessage = cutEntry.type === \"message\" && cutEntry.message.role === \"user\";\n\t// A cut in a non-user turn requires a prefix summary.\n\tconst turnStartIndex = isUserMessage ? -1 : findTurnStartIndex(entries, cutIndex, startIndex);\n\n\treturn {\n\t\tfirstKeptEntryIndex: cutIndex,\n\t\tturnStartIndex,\n\t\tisSplitTurn: !isUserMessage && turnStartIndex !== -1,\n\t};\n}\nconst SUMMARIZATION_PROMPT = `The messages above are a conversation to summarize. Create a structured context checkpoint summary that another LLM will use to continue the work.\n\nUse this EXACT format:\n\n## Goal\n[What is the user trying to accomplish? Can be multiple items if the session covers different tasks.]\n\n## Constraints & Preferences\n- [Any constraints, preferences, or requirements mentioned by user]\n- [Or \"(none)\" if none were mentioned]\n\n## Progress\n### Done\n- [x] [Completed tasks/changes]\n\n### In Progress\n- [ ] [Current work]\n\n### Blocked\n- [Issues preventing progress, if any]\n\n## Key Decisions\n- **[Decision]**: [Brief rationale]\n\n## Next Steps\n1. [Ordered list of what should happen next]\n\n## Critical Context\n- [Any data, examples, or references needed to continue]\n- [Or \"(none)\" if not applicable]\n\nKeep each section concise. Preserve exact file paths, function names, and error messages.`;\n\nconst RUNTIME_STATE_SUMMARY_NOTE =\n\t\"Runtime note: this summary does not establish whether a Python kernel is live or whether its variables, imports, helpers, or jobs remain available. Preserve useful names and their last observed state, including uncertainty. Use current runtime reports before relying on them; do not infer either survival or loss from compaction.\";\n\nconst UPDATE_SUMMARIZATION_PROMPT = `The messages above are NEW conversation messages to incorporate into the existing summary provided in <previous-summary> tags.\n\nUpdate the existing structured summary with new information. RULES:\n- PRESERVE all existing information from the previous summary\n- ADD new progress, decisions, and context from the new messages\n- UPDATE the Progress section: move items from \"In Progress\" to \"Done\" when completed\n- UPDATE \"Next Steps\" based on what was accomplished\n- PRESERVE exact file paths, function names, and error messages\n- If something is no longer relevant, you may remove it\n\nUse this EXACT format:\n\n## Goal\n[Preserve existing goals, add new ones if the task expanded]\n\n## Constraints & Preferences\n- [Preserve existing, add new ones discovered]\n\n## Progress\n### Done\n- [x] [Include previously done items AND newly completed items]\n\n### In Progress\n- [ ] [Current work - update based on progress]\n\n### Blocked\n- [Current blockers - remove if resolved]\n\n## Key Decisions\n- **[Decision]**: [Brief rationale] (preserve all previous, add new)\n\n## Next Steps\n1. [Update based on current state]\n\n## Critical Context\n- [Preserve important context, add new if needed]\n\nKeep each section concise. Preserve exact file paths, function names, and error messages.`;\n\n/**\n * Build the instruction portion of the summarization prompt: the initial or\n * update template, optional user instructions, and the runtime-state qualification.\n */\nexport function buildSummarizationPrompt(customInstructions?: string, previousSummary?: string): string {\n\tlet basePrompt = previousSummary ? UPDATE_SUMMARIZATION_PROMPT : SUMMARIZATION_PROMPT;\n\tif (customInstructions) {\n\t\tbasePrompt += `\\n\\n<user-instructions>\\nThe user provided these instructions for this summary. Follow them with high priority while keeping the section format above: emphasize what they ask to focus on, and preserve verbatim anything they ask to remember.\\n${customInstructions}\\n</user-instructions>`;\n\t}\n\treturn `${basePrompt}\\n\\n${RUNTIME_STATE_SUMMARY_NOTE}`;\n}\n\n/**\n * Generate a summary of the conversation using the LLM.\n * If previousSummary is provided, uses the update prompt to merge.\n */\nexport async function generateSummary(\n\tcurrentMessages: AgentMessage[],\n\tmodel: Model<any>,\n\treserveTokens: number,\n\tapiKey: string,\n\theaders?: Record<string, string>,\n\tsignal?: AbortSignal,\n\tcustomInstructions?: string,\n\tpreviousSummary?: string,\n\tthinkingLevel?: ThinkingLevel,\n\trequests?: InferenceCoordinator,\n\toutputTokenLimit?: number,\n): Promise<SummarySlice> {\n\tconst maxTokens = outputTokenLimit ?? Math.floor(0.8 * reserveTokens);\n\n\tconst basePrompt = buildSummarizationPrompt(customInstructions, previousSummary);\n\t// Serialize before the LLM call so it summarizes rather than continues this conversation.\n\tconst llmMessages = convertToLlm(currentMessages);\n\tconst conversationText = serializeConversation(llmMessages);\n\tlet promptText = `<conversation>\\n${conversationText}\\n</conversation>\\n\\n`;\n\tif (previousSummary) {\n\t\tpromptText += `<previous-summary>\\n${previousSummary}\\n</previous-summary>\\n\\n`;\n\t}\n\tpromptText += basePrompt;\n\n\tconst summarizationMessages = [\n\t\t{\n\t\t\trole: \"user\" as const,\n\t\t\tcontent: [{ type: \"text\" as const, text: promptText }],\n\t\t\ttimestamp: Date.now(),\n\t\t},\n\t];\n\n\tconst completionOptions =\n\t\tmodel.reasoning && thinkingLevel && thinkingLevel !== \"off\"\n\t\t\t? { maxTokens, signal, apiKey, headers, reasoning: thinkingLevel }\n\t\t\t: { maxTokens, signal, apiKey, headers };\n\n\tconst completion = completeInference(\n\t\trequests,\n\t\tmodel,\n\t\t{ systemPrompt: SUMMARIZATION_SYSTEM_PROMPT, messages: summarizationMessages },\n\t\tcompletionOptions,\n\t\t{\n\t\t\tpurpose: \"summary\",\n\t\t\tpurposeDetail: \"compaction\",\n\t\t\toperationId: headers?.[MODEL_REQUEST_ID_HEADER],\n\t\t\tsemanticEdgeId: headers?.[MODEL_REQUEST_ID_HEADER],\n\t\t},\n\t);\n\n\tconst response = await completion;\n\tif (response.stopReason === \"error\") {\n\t\tthrow new Error(`Summarization failed: ${response.errorMessage || \"Unknown error\"}`);\n\t}\n\n\tconst textContent = response.content\n\t\t.filter((c): c is { type: \"text\"; text: string } => c.type === \"text\")\n\t\t.map((c) => c.text)\n\t\t.join(\"\\n\");\n\n\treturn captureSummarySlice({ summary: textContent, usage: response.usage }, requests, completion, \"history\");\n}\nexport interface CompactionPreparation {\n\t/** UUID of first entry to keep */\n\tfirstKeptEntryId: string;\n\t/** Messages that will be summarized and discarded */\n\tmessagesToSummarize: AgentMessage[];\n\t/** Messages that will be turned into turn prefix summary (if splitting) */\n\tturnPrefixMessages: AgentMessage[];\n\t/** Whether this is a split turn (cut point in middle of turn) */\n\tisSplitTurn: boolean;\n\ttokensBefore: number;\n\t/** Summary from previous compaction, for iterative update */\n\tpreviousSummary?: string;\n\t/** File operations extracted from messagesToSummarize */\n\tfileOps: FileOperations;\n\t/** Compaction settions from settings.jsonl\t*/\n\tsettings: CompactionSettings;\n}\n\n/** Prepare the actual selected epoch view. Entry IDs are only real cut anchors, never synthetic rows. */\nexport function prepareViewCompaction(\n\tmessages: readonly AgentMessage[],\n\tentryIds: readonly (string | undefined)[],\n\tpathEntries: SessionEntry[],\n\tsettings: CompactionSettings,\n\tmaxCutEntryId?: string,\n\tallowShortSession = false,\n\tbudgetPressure = false,\n): CompactionPreparation | undefined {\n\tif (messages.length !== entryIds.length) throw new Error(\"Compaction views do not match their source anchors\");\n\t// firstKeptEntryId restores a chronological source suffix, not a selected-view suffix.\n\t// Older pinned views cannot become cut anchors across omitted source messages.\n\tconst selectedIds = new Set(entryIds);\n\tconst suffixAnchors = new Set<string>();\n\tfor (let index = pathEntries.length - 1; index >= 0; index--) {\n\t\tconst entry = pathEntries[index];\n\t\tif (!getMessageFromEntryForCompaction(entry)) continue;\n\t\tif (!selectedIds.has(entry.id)) break;\n\t\tsuffixAnchors.add(entry.id);\n\t}\n\tlet cuts = messages.flatMap((message, index) =>\n\t\tentryIds[index] &&\n\t\tsuffixAnchors.has(entryIds[index]!) &&\n\t\tmessage.role !== \"toolResult\" &&\n\t\tmessage.role !== \"compactionSummary\"\n\t\t\t? [index]\n\t\t\t: [],\n\t);\n\tlet relaxRetention = false;\n\tif (budgetPressure) {\n\t\t// Only caller-confirmed public OVER may relax retention; source/tool groups remain whole.\n\t\tconst spans: [number, number][] = [];\n\t\tconst sourceStarts = new Map<string, number>();\n\t\tconst resultEnds = new Map<string, number>();\n\t\t// Public history renders original roles as custom text; use its captured source anchors.\n\t\tconst sourceMessages = new Map(pathEntries.map((entry) => [entry.id, getMessageFromEntry(entry)]));\n\t\tconst anchoredMessages = messages.map((message, index) => sourceMessages.get(entryIds[index] ?? \"\") ?? message);\n\t\tfor (const [index, message] of anchoredMessages.entries()) {\n\t\t\tconst entryId = entryIds[index];\n\t\t\tif (entryId) {\n\t\t\t\tconst start = sourceStarts.get(entryId);\n\t\t\t\tif (start === undefined) sourceStarts.set(entryId, index);\n\t\t\t\telse spans.push([start, index]);\n\t\t\t}\n\t\t\tif (message.role === \"toolResult\") resultEnds.set(message.toolCallId, index);\n\t\t}\n\t\tlet latestCompleteExchange = messages.length - 1;\n\t\tconst completedAssistants: number[] = [];\n\t\tfor (const [index, message] of anchoredMessages.entries()) {\n\t\t\tif (message.role !== \"assistant\") continue;\n\t\t\tconst calls = message.content.filter((block) => block.type === \"toolCall\");\n\t\t\tconst completeExchange = calls.length > 0 && calls.every((call) => (resultEnds.get(call.id) ?? -1) > index);\n\t\t\tif (completeExchange) latestCompleteExchange = index;\n\t\t\tif (completeExchange || (!calls.length && message.stopReason === \"stop\")) completedAssistants.push(index);\n\t\t\tfor (const call of calls) {\n\t\t\t\tconst end = resultEnds.get(call.id);\n\t\t\t\tif (end !== undefined && end > index) spans.push([index, end]);\n\t\t\t}\n\t\t}\n\t\t// A lone current instruction before its first exchange is not removable history.\n\t\trelaxRetention = completedAssistants.some((index) => index < latestCompleteExchange);\n\t\tif (relaxRetention) {\n\t\t\tcuts = cuts.filter(\n\t\t\t\t(candidate) =>\n\t\t\t\t\tcandidate <= latestCompleteExchange &&\n\t\t\t\t\tspans.every(([start, end]) => candidate <= start || candidate > end),\n\t\t\t);\n\t\t}\n\t}\n\tif (!cuts.length) return;\n\tconst tokenScale = compactionTokenScale(messages);\n\tlet cut = cuts[0];\n\tlet tokens = 0;\n\tfor (let index = messages.length - 1; index >= 0; index--) {\n\t\tif (!entryIds[index] || messages[index].role === \"compactionSummary\") continue;\n\t\ttokens += estimateTokens(messages[index]) * tokenScale;\n\t\tif (tokens >= settings.keepRecentTokens) {\n\t\t\t// Include the message that crossed the threshold and its preceding legal group boundary.\n\t\t\tcut = cuts.filter((candidate) => candidate <= index).at(-1) ?? cut;\n\t\t\tbreak;\n\t\t}\n\t}\n\tif (relaxRetention || (allowShortSession && cut === cuts[0])) cut = cuts.at(-1)!;\n\tif (maxCutEntryId !== undefined) {\n\t\tconst maximum = entryIds.indexOf(maxCutEntryId);\n\t\tconst lastAllowed = cuts.filter((candidate) => candidate <= maximum).at(-1);\n\t\tif (lastAllowed === undefined) throw new Error(\"Compaction recovery boundary is unavailable\");\n\t\tcut = Math.min(cut, lastAllowed);\n\t}\n\tlet turnStart = -1;\n\tif (messages[cut].role !== \"user\") {\n\t\tfor (let index = cut; index >= 0; index--) {\n\t\t\tif (entryIds[index] && [\"user\", \"custom\", \"branchSummary\", \"bashExecution\"].includes(messages[index].role)) {\n\t\t\t\tturnStart = index;\n\t\t\t\tbreak;\n\t\t\t}\n\t\t}\n\t}\n\tconst isSplitTurn = turnStart >= 0;\n\tconst historyEnd = isSplitTurn ? turnStart : cut;\n\tlet previousSummary: string | undefined;\n\tlet previousSummaryIndex = -1;\n\tfor (const [index, message] of messages.entries()) {\n\t\tif (message.role !== \"compactionSummary\") continue;\n\t\tpreviousSummary = message.summary;\n\t\tpreviousSummaryIndex = pathEntries.findIndex((entry) => entry.id === entryIds[index]);\n\t}\n\tconst historical = messages.filter(\n\t\t(message, index) => entryIds[index] && index < historyEnd && message.role !== \"compactionSummary\",\n\t);\n\tconst turnPrefixMessages = isSplitTurn\n\t\t? messages.filter(\n\t\t\t\t(message, index) =>\n\t\t\t\t\tentryIds[index] && index >= turnStart && index < cut && message.role !== \"compactionSummary\",\n\t\t\t)\n\t\t: [];\n\tif (!historical.length && !turnPrefixMessages.length && (budgetPressure || allowShortSession || !previousSummary))\n\t\treturn;\n\t// Virtual TaskFrame displays are continuity input, never a canonical suffix boundary.\n\tconst messagesToSummarize = messages.filter(\n\t\t(message, index) => message.role !== \"compactionSummary\" && (!entryIds[index] || index < historyEnd),\n\t);\n\tconst fileOps = extractFileOperations(messagesToSummarize, pathEntries, previousSummaryIndex);\n\tfor (const message of turnPrefixMessages) extractFileOpsFromMessage(message, fileOps);\n\treturn {\n\t\tfirstKeptEntryId: entryIds[cut]!,\n\t\tmessagesToSummarize: structuredClone(messagesToSummarize),\n\t\tturnPrefixMessages: structuredClone(turnPrefixMessages),\n\t\tisSplitTurn,\n\t\ttokensBefore: estimateContextTokens([...messages]).tokens,\n\t\tpreviousSummary,\n\t\tfileOps,\n\t\tsettings,\n\t};\n}\n\nexport function prepareCompaction(\n\tpathEntries: SessionEntry[],\n\tsettings: CompactionSettings,\n\tallowShortSession = false,\n): CompactionPreparation | undefined {\n\tif (pathEntries.length > 0 && pathEntries[pathEntries.length - 1].type === \"compaction\") {\n\t\treturn undefined;\n\t}\n\n\tlet prevCompactionIndex = -1;\n\tfor (let i = pathEntries.length - 1; i >= 0; i--) {\n\t\tif (pathEntries[i].type === \"compaction\") {\n\t\t\tprevCompactionIndex = i;\n\t\t\tbreak;\n\t\t}\n\t}\n\n\tlet previousSummary: string | undefined;\n\tlet boundaryStart = 0;\n\tif (prevCompactionIndex >= 0) {\n\t\tconst prevCompaction = pathEntries[prevCompactionIndex] as CompactionEntry;\n\t\tpreviousSummary = prevCompaction.summary;\n\t\tconst firstKeptEntryIndex = pathEntries.findIndex((entry) => entry.id === prevCompaction.firstKeptEntryId);\n\t\tboundaryStart = firstKeptEntryIndex >= 0 ? firstKeptEntryIndex : prevCompactionIndex + 1;\n\t}\n\tconst boundaryEnd = pathEntries.length;\n\n\tconst messages = buildSessionContext(pathEntries).messages;\n\tconst tokensBefore = estimateContextTokens(messages).tokens;\n\n\tconst cutPoint = findCutPoint(\n\t\tpathEntries,\n\t\tboundaryStart,\n\t\tboundaryEnd,\n\t\tsettings.keepRecentTokens,\n\t\tallowShortSession,\n\t\tcompactionTokenScale(messages),\n\t);\n\tconst firstKeptEntry = pathEntries[cutPoint.firstKeptEntryIndex];\n\tif (!firstKeptEntry?.id) {\n\t\treturn undefined; // Session needs migration\n\t}\n\tconst firstKeptEntryId = firstKeptEntry.id;\n\n\tconst historyEnd = cutPoint.isSplitTurn ? cutPoint.turnStartIndex : cutPoint.firstKeptEntryIndex;\n\tconst messagesToSummarize: AgentMessage[] = [];\n\tfor (let i = boundaryStart; i < historyEnd; i++) {\n\t\tconst msg = getMessageFromEntryForCompaction(pathEntries[i]);\n\t\tif (msg) messagesToSummarize.push(msg);\n\t}\n\tconst turnPrefixMessages: AgentMessage[] = [];\n\tif (cutPoint.isSplitTurn) {\n\t\tfor (let i = cutPoint.turnStartIndex; i < cutPoint.firstKeptEntryIndex; i++) {\n\t\t\tconst msg = getMessageFromEntryForCompaction(pathEntries[i]);\n\t\t\tif (msg) turnPrefixMessages.push(msg);\n\t\t}\n\t}\n\n\t// Avoid a compaction that would summarize no history.\n\tif (messagesToSummarize.length === 0 && turnPrefixMessages.length === 0 && (allowShortSession || !previousSummary)) {\n\t\treturn undefined;\n\t}\n\tconst fileOps = extractFileOperations(messagesToSummarize, pathEntries, prevCompactionIndex);\n\t// Split turns retain their suffix, but their prefix file operations still belong in the summary.\n\tif (cutPoint.isSplitTurn) {\n\t\tfor (const msg of turnPrefixMessages) {\n\t\t\textractFileOpsFromMessage(msg, fileOps);\n\t\t}\n\t}\n\n\treturn {\n\t\tfirstKeptEntryId,\n\t\tmessagesToSummarize,\n\t\tturnPrefixMessages,\n\t\tisSplitTurn: cutPoint.isSplitTurn,\n\t\ttokensBefore,\n\t\tpreviousSummary,\n\t\tfileOps,\n\t\tsettings,\n\t};\n}\nconst TURN_PREFIX_SUMMARIZATION_PROMPT = `This is the PREFIX of a turn that was too large to keep. The SUFFIX (recent work) is retained.\n\nSummarize the prefix to provide context for the retained suffix:\n\n## Original Request\n[What did the user ask for in this turn?]\n\n## Early Progress\n- [Key decisions and work done in the prefix]\n\n## Context for Suffix\n- [Information needed to understand the retained recent work]\n\nBe concise. Focus on what's needed to understand the kept suffix.`;\n\n/**\n * Generate summaries for compaction using prepared data.\n * Returns CompactionResult - SessionManager adds uuid/parentUuid when saving.\n *\n * @param preparation - Pre-calculated preparation from prepareCompaction()\n * @param customInstructions - Optional custom focus for the summary\n */\n/** Runs one summary wire call; hosts decorate each call with its own request identity. */\nexport type SummaryCallRunner = <T>(\n\tcall: (callHeaders: Record<string, string> | undefined) => Promise<T>,\n) => Promise<T>;\n\nexport async function compact(\n\tpreparation: CompactionPreparation,\n\tmodel: Model<any>,\n\tapiKey: string,\n\theaders?: Record<string, string>,\n\tcustomInstructions?: string,\n\tsignal?: AbortSignal,\n\tthinkingLevel?: ThinkingLevel,\n\tsummaryCall: SummaryCallRunner = (call) => call(headers),\n\trequests?: InferenceCoordinator,\n\tsummaryCapacity?: (wrapper: string) => number | undefined,\n): Promise<CompactionResult> {\n\tconst {\n\t\tfirstKeptEntryId,\n\t\tmessagesToSummarize,\n\t\tturnPrefixMessages,\n\t\tisSplitTurn,\n\t\ttokensBefore,\n\t\tpreviousSummary,\n\t\tfileOps,\n\t\tsettings,\n\t} = preparation;\n\tlet summary: string;\n\tconst slices: SummarySlice[] = [];\n\tconst { readFiles, modifiedFiles } = computeFileLists(fileOps);\n\tconst fileSummary = formatFileOperations(readFiles, modifiedFiles);\n\tconst split = isSplitTurn && turnPrefixMessages.length > 0;\n\tconst splitSeparator = \"\\n\\n---\\n\\n**Turn Context (split turn):**\\n\\n\";\n\tconst wrapper =\n\t\tCOMPACTION_SUMMARY_PREFIX +\n\t\t(split ? (messagesToSummarize.length ? \"\" : \"No prior history.\") + splitSeparator : \"\") +\n\t\tfileSummary +\n\t\tCOMPACTION_SUMMARY_SUFFIX;\n\tconst capacity = summaryCapacity?.(wrapper);\n\tconst historyNeeded = !split || messagesToSummarize.length > 0;\n\tconst calls = Number(historyNeeded) + Number(split);\n\tif (capacity !== undefined && capacity < calls)\n\t\tthrow new Error(\n\t\t\t\"Context capacity exceeded: required evidence, retained tail and summary wrapper leave no room for a summary.\",\n\t\t);\n\tconst historyLimit =\n\t\tcapacity === undefined\n\t\t\t? undefined\n\t\t\t: Math.min(\n\t\t\t\t\tMath.floor(0.8 * settings.reserveTokens),\n\t\t\t\t\tsplit && historyNeeded ? Math.max(1, Math.floor(capacity * 0.6)) : capacity,\n\t\t\t\t);\n\tconst turnLimit =\n\t\tcapacity === undefined\n\t\t\t? undefined\n\t\t\t: Math.min(Math.floor(0.5 * settings.reserveTokens), capacity - (historyNeeded ? historyLimit! : 0));\n\n\tif (split) {\n\t\t// Split turns make two wire calls with different bodies; each needs its own identity.\n\t\tconst [historyResult, turnPrefixResult] = await Promise.all([\n\t\t\tmessagesToSummarize.length > 0\n\t\t\t\t? summaryCall((callHeaders) =>\n\t\t\t\t\t\tgenerateSummary(\n\t\t\t\t\t\t\tmessagesToSummarize,\n\t\t\t\t\t\t\tmodel,\n\t\t\t\t\t\t\tsettings.reserveTokens,\n\t\t\t\t\t\t\tapiKey,\n\t\t\t\t\t\t\tcallHeaders,\n\t\t\t\t\t\t\tsignal,\n\t\t\t\t\t\t\tcustomInstructions,\n\t\t\t\t\t\t\tpreviousSummary,\n\t\t\t\t\t\t\tthinkingLevel,\n\t\t\t\t\t\t\trequests,\n\t\t\t\t\t\t\thistoryLimit,\n\t\t\t\t\t\t),\n\t\t\t\t\t)\n\t\t\t\t: Promise.resolve<SummarySlice>({ summary: \"No prior history.\" }),\n\t\t\tsummaryCall((callHeaders) =>\n\t\t\t\tgenerateTurnPrefixSummary(\n\t\t\t\t\tturnPrefixMessages,\n\t\t\t\t\tmodel,\n\t\t\t\t\tsettings.reserveTokens,\n\t\t\t\t\tapiKey,\n\t\t\t\t\tcallHeaders,\n\t\t\t\t\tsignal,\n\t\t\t\t\tthinkingLevel,\n\t\t\t\t\trequests,\n\t\t\t\t\tturnLimit,\n\t\t\t\t),\n\t\t\t),\n\t\t]);\n\t\tslices.push(historyResult, turnPrefixResult);\n\t\tsummary = `${historyResult.summary}${splitSeparator}${turnPrefixResult.summary}`;\n\t} else {\n\t\tconst result = await summaryCall((callHeaders) =>\n\t\t\tgenerateSummary(\n\t\t\t\tmessagesToSummarize,\n\t\t\t\tmodel,\n\t\t\t\tsettings.reserveTokens,\n\t\t\t\tapiKey,\n\t\t\t\tcallHeaders,\n\t\t\t\tsignal,\n\t\t\t\tcustomInstructions,\n\t\t\t\tpreviousSummary,\n\t\t\t\tthinkingLevel,\n\t\t\t\trequests,\n\t\t\t\thistoryLimit,\n\t\t\t),\n\t\t);\n\t\tslices.push(result);\n\t\tsummary = result.summary;\n\t}\n\tsummary += fileSummary;\n\n\tif (!firstKeptEntryId) {\n\t\tthrow new Error(\"First kept entry has no UUID - session may need migration\");\n\t}\n\n\tlet usage: Usage | undefined;\n\tfor (const slice of slices) {\n\t\tif (!slice.usage) continue;\n\t\tusage ??= emptyUsage();\n\t\taddAssistantUsage(usage, slice.usage);\n\t}\n\tconst result: CompactionResult = {\n\t\tsummary,\n\t\tfirstKeptEntryId,\n\t\ttokensBefore,\n\t\tdetails: { readFiles, modifiedFiles } as CompactionDetails,\n\t\tusage,\n\t};\n\tconst bindings = slices.flatMap((slice) => {\n\t\tconst captured = nativeSummarySlices.get(slice);\n\t\tnativeSummarySlices.delete(slice);\n\t\treturn captured?.summary === slice.summary ? [captured.binding] : [];\n\t});\n\tif (bindings.length) nativeCompactionResults.set(result, { summary, bindings });\n\treturn result;\n}\n\n/**\n * Generate a summary for a turn prefix (when splitting a turn).\n */\nasync function generateTurnPrefixSummary(\n\tmessages: AgentMessage[],\n\tmodel: Model<any>,\n\treserveTokens: number,\n\tapiKey: string,\n\theaders?: Record<string, string>,\n\tsignal?: AbortSignal,\n\tthinkingLevel?: ThinkingLevel,\n\trequests?: InferenceCoordinator,\n\toutputTokenLimit?: number,\n): Promise<SummarySlice> {\n\tconst maxTokens = outputTokenLimit ?? Math.floor(0.5 * reserveTokens); // Smaller budget for turn prefix\n\tconst llmMessages = convertToLlm(messages);\n\tconst conversationText = serializeConversation(llmMessages);\n\tconst promptText = `<conversation>\\n${conversationText}\\n</conversation>\\n\\n${TURN_PREFIX_SUMMARIZATION_PROMPT}`;\n\tconst summarizationMessages = [\n\t\t{\n\t\t\trole: \"user\" as const,\n\t\t\tcontent: [{ type: \"text\" as const, text: promptText }],\n\t\t\ttimestamp: Date.now(),\n\t\t},\n\t];\n\n\tconst completion = completeInference(\n\t\trequests,\n\t\tmodel,\n\t\t{ systemPrompt: SUMMARIZATION_SYSTEM_PROMPT, messages: summarizationMessages },\n\t\tmodel.reasoning && thinkingLevel && thinkingLevel !== \"off\"\n\t\t\t? { maxTokens, signal, apiKey, headers, reasoning: thinkingLevel }\n\t\t\t: { maxTokens, signal, apiKey, headers },\n\t\t{\n\t\t\tpurpose: \"summary\",\n\t\t\tpurposeDetail: \"compaction-turn-prefix\",\n\t\t\toperationId: headers?.[MODEL_REQUEST_ID_HEADER],\n\t\t\tsemanticEdgeId: headers?.[MODEL_REQUEST_ID_HEADER],\n\t\t},\n\t);\n\n\tconst response = await completion;\n\tif (response.stopReason === \"error\") {\n\t\tthrow new Error(`Turn prefix summarization failed: ${response.errorMessage || \"Unknown error\"}`);\n\t}\n\n\tconst slice: SummarySlice = {\n\t\tsummary: response.content\n\t\t\t.filter((c): c is { type: \"text\"; text: string } => c.type === \"text\")\n\t\t\t.map((c) => c.text)\n\t\t\t.join(\"\\n\"),\n\t\tusage: response.usage,\n\t};\n\treturn captureSummarySlice(slice, requests, completion, \"turn-prefix\");\n}\n"]}
1
+ {"version":3,"file":"compaction.js","sourceRoot":"","sources":["../../../src/core/compaction/compaction.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Context compaction for long sessions.\n *\n * Pure functions for compaction logic. The session manager handles I/O,\n * and after compaction the session is reloaded.\n */\n\nimport type { AgentMessage, ThinkingLevel } from \"@ponythewhite/base-context-agent\";\nimport type { AssistantMessage, Model, Usage } from \"@ponythewhite/base-context-ai\";\nimport {\n\tcompleteInference,\n\tInferenceCoordinator,\n\ttype NativeCompactionOutputBinding,\n} from \"../inference-coordinator.js\";\nimport {\n\tCOMPACTION_SUMMARY_PREFIX,\n\tCOMPACTION_SUMMARY_SUFFIX,\n\tconvertToLlm,\n\tcreateBranchSummaryMessage,\n\tcreateCompactionSummaryMessage,\n\tcreateCustomMessage,\n\tHARNESS_SNAPSHOT_CUSTOM_TYPE,\n} from \"../messages.js\";\nimport type { NativeCompactionRequestOutputAssociation } from \"../request-events.js\";\nimport { MODEL_REQUEST_ID_HEADER } from \"../semantic-edges.js\";\nimport { buildSessionContext, type CompactionEntry, type SessionEntry } from \"../session-manager.js\";\nimport { TASK_FRAME_CUSTOM_TYPE } from \"../task-frame.js\";\nimport { addAssistantUsage, emptyUsage } from \"../usage.js\";\nimport {\n\tcomputeFileLists,\n\tcreateFileOps,\n\textractFileOpsFromMessage,\n\ttype FileOperations,\n\tformatFileOperations,\n\tSUMMARIZATION_SYSTEM_PROMPT,\n\tserializeConversation,\n} from \"./utils.js\";\n/** Details stored in CompactionEntry.details for file tracking */\nexport interface CompactionDetails {\n\treadFiles: string[];\n\tmodifiedFiles: string[];\n}\n\nexport interface SummarySlice {\n\tsummary: string;\n\tusage?: Usage;\n}\n\n// Identity follows the actual completion -> text slice -> built-in composition, never copied fields.\nconst nativeSummarySlices = new WeakMap<SummarySlice, { summary: string; binding: NativeCompactionOutputBinding }>();\nconst nativeCompactionResults = new WeakMap<object, { summary: string; bindings: NativeCompactionOutputBinding[] }>();\n\nfunction captureSummarySlice(\n\tslice: SummarySlice,\n\trequests: InferenceCoordinator | undefined,\n\tcompletion: Promise<AssistantMessage>,\n\tpart: NativeCompactionRequestOutputAssociation[\"part\"],\n): SummarySlice {\n\tconst binding =\n\t\trequests instanceof InferenceCoordinator\n\t\t\t? InferenceCoordinator.prototype.takeCompactionOutput.call(requests, completion)\n\t\t\t: undefined;\n\tif (binding?.output.part === part) nativeSummarySlices.set(slice, { summary: slice.summary, binding });\n\treturn slice;\n}\n\n/** Internal append input: only this exact built-in result and original sink can contribute links. */\nexport function takeCompactionRequestOutputs(\n\tresult: object | undefined,\n\tsink: object,\n\tsummary: string,\n): readonly NativeCompactionRequestOutputAssociation[] | undefined {\n\tconst captured = result ? nativeCompactionResults.get(result) : undefined;\n\tif (result) nativeCompactionResults.delete(result);\n\t// Equality only checks an already privately bound projection for mutation; it never discovers a link.\n\tif (!captured || captured.summary !== summary) return undefined;\n\tconst outputs = captured.bindings.filter((binding) => binding.sink === sink).map((binding) => binding.output);\n\treturn outputs.length ? outputs : undefined;\n}\n\n/**\n * Extract file operations from messages and previous compaction entries.\n */\n/** Preserve file operations recorded by prior compactions and current tool calls. */\nfunction extractFileOperations(\n\tmessages: AgentMessage[],\n\tentries: SessionEntry[],\n\tprevCompactionIndex: number,\n): FileOperations {\n\tconst fileOps = createFileOps();\n\tif (prevCompactionIndex >= 0) {\n\t\tconst prevCompaction = entries[prevCompactionIndex] as CompactionEntry;\n\t\tif (!prevCompaction.fromHook && prevCompaction.details) {\n\t\t\t// fromHook field kept for session file compatibility\n\t\t\tconst details = prevCompaction.details as CompactionDetails;\n\t\t\tif (Array.isArray(details.readFiles)) {\n\t\t\t\tfor (const f of details.readFiles) fileOps.read.add(f);\n\t\t\t}\n\t\t\tif (Array.isArray(details.modifiedFiles)) {\n\t\t\t\tfor (const f of details.modifiedFiles) fileOps.edited.add(f);\n\t\t\t}\n\t\t}\n\t}\n\tfor (const msg of messages) {\n\t\textractFileOpsFromMessage(msg, fileOps);\n\t}\n\n\treturn fileOps;\n}\n/**\n * Extract AgentMessage from an entry if it produces one.\n * Returns undefined for entries that don't contribute to LLM context.\n */\nfunction getMessageFromEntry(entry: SessionEntry): AgentMessage | undefined {\n\tif (entry.type === \"message\") {\n\t\treturn entry.message;\n\t}\n\tif (entry.type === \"custom_message\") {\n\t\treturn createCustomMessage(entry.customType, entry.content, entry.display, entry.details, entry.timestamp);\n\t}\n\tif (entry.type === \"branch_summary\") {\n\t\treturn createBranchSummaryMessage(entry.summary, entry.fromId, entry.timestamp);\n\t}\n\tif (entry.type === \"compaction\") {\n\t\treturn createCompactionSummaryMessage(\n\t\t\tentry.summary,\n\t\t\tentry.tokensBefore,\n\t\t\tentry.timestamp,\n\t\t\tentry.customInstructions,\n\t\t);\n\t}\n\treturn undefined;\n}\n\nfunction getMessageFromEntryForCompaction(entry: SessionEntry): AgentMessage | undefined {\n\tif (entry.type === \"compaction\") {\n\t\treturn undefined;\n\t}\n\treturn getMessageFromEntry(entry);\n}\n\n/** Result from compact() - SessionManager adds uuid/parentUuid when saving */\nexport interface CompactionResult<T = unknown> {\n\tsummary: string;\n\tfirstKeptEntryId: string;\n\t/** Prior-context estimate; null when the original context is not measurable. */\n\ttokensBefore: number | null;\n\t/** Extension-specific data (e.g., ArtifactIndex, version markers for structured compaction) */\n\tdetails?: T;\n\t/** What the summarization call(s) billed; persisted on the compaction entry. */\n\tusage?: Usage;\n}\nexport const COMPACT_SKILL_NAME = \"compact\";\n\nexport interface CompactionSettings {\n\tenabled: boolean;\n\treserveTokens: number;\n\tkeepRecentTokens: number;\n\t/** Soft working-context target; the model limit always takes precedence. */\n\ttargetTokens?: number | \"model-limit\";\n}\n\nexport const DEFAULT_COMPACTION_SETTINGS: CompactionSettings = {\n\tenabled: true,\n\treserveTokens: 16384,\n\tkeepRecentTokens: 20000,\n};\n/**\n * Calculate total context tokens from usage.\n * Uses the native totalTokens field when available, falls back to computing from components.\n *\n * Includes output: the assistant's response becomes part of the prompt on the next\n * request, so it counts toward the context the next turn will send.\n */\nexport function calculateContextTokens(usage: Usage): number {\n\treturn usage.totalTokens || usage.input + usage.output + usage.cacheRead + usage.cacheWrite;\n}\n\n/**\n * Get usage from an assistant message if available.\n * Skips aborted and error messages as they don't have valid usage data.\n */\nfunction getAssistantUsage(msg: AgentMessage): Usage | undefined {\n\tif (msg.role === \"assistant\" && \"usage\" in msg) {\n\t\tconst assistantMsg = msg as AssistantMessage;\n\t\tif (assistantMsg.stopReason !== \"aborted\" && assistantMsg.stopReason !== \"error\" && assistantMsg.usage) {\n\t\t\treturn assistantMsg.usage;\n\t\t}\n\t}\n\treturn undefined;\n}\n\n/**\n * Find the last non-aborted assistant message usage from session entries.\n */\nexport function getLastAssistantUsage(entries: SessionEntry[]): Usage | undefined {\n\tfor (let i = entries.length - 1; i >= 0; i--) {\n\t\tconst entry = entries[i];\n\t\tif (entry.type === \"message\") {\n\t\t\tconst usage = getAssistantUsage(entry.message);\n\t\t\tif (usage) return usage;\n\t\t}\n\t}\n\treturn undefined;\n}\n\nexport interface ContextUsageEstimate {\n\ttokens: number;\n\tusageTokens: number;\n\ttrailingTokens: number;\n\tlastUsageIndex: number | null;\n}\n\nfunction getLastAssistantUsageInfo(messages: readonly AgentMessage[]): { usage: Usage; index: number } | undefined {\n\tfor (let i = messages.length - 1; i >= 0; i--) {\n\t\tconst usage = getAssistantUsage(messages[i]);\n\t\tif (usage) return { usage, index: i };\n\t}\n\treturn undefined;\n}\n\n/**\n * Estimate context tokens from messages, using the last assistant usage when available.\n * If there are messages after the last usage, estimate their tokens with estimateTokens.\n */\nexport function estimateContextTokens(messages: AgentMessage[]): ContextUsageEstimate {\n\tconst usageInfo = getLastAssistantUsageInfo(messages);\n\n\tif (!usageInfo) {\n\t\tlet estimated = 0;\n\t\tfor (const message of messages) {\n\t\t\testimated += estimateTokens(message);\n\t\t}\n\t\treturn {\n\t\t\ttokens: estimated,\n\t\t\tusageTokens: 0,\n\t\t\ttrailingTokens: estimated,\n\t\t\tlastUsageIndex: null,\n\t\t};\n\t}\n\n\tconst usageTokens = calculateContextTokens(usageInfo.usage);\n\tlet trailingTokens = 0;\n\tfor (let i = usageInfo.index + 1; i < messages.length; i++) {\n\t\ttrailingTokens += estimateTokens(messages[i]);\n\t}\n\n\treturn {\n\t\ttokens: usageTokens + trailingTokens,\n\t\tusageTokens,\n\t\ttrailingTokens,\n\t\tlastUsageIndex: usageInfo.index,\n\t};\n}\n\n/**\n * Check if compaction should trigger based on context usage.\n */\nexport function shouldCompact(\n\tcontextTokens: number,\n\tcontextWindow: number,\n\tsettings: CompactionSettings,\n\tfixedContextTokens = 0,\n): boolean {\n\tif (!settings.enabled) return false;\n\tif (contextWindow <= 0) return false;\n\tconst modelLimit = contextWindow - settings.reserveTokens;\n\t// Fixed instructions cannot shrink. Leave meaningful room for retained history\n\t// and new work above that floor, without relaxing the actual model ceiling.\n\tconst target =\n\t\tsettings.targetTokens === \"model-limit\"\n\t\t\t? modelLimit\n\t\t\t: Math.max(\n\t\t\t\t\tsettings.targetTokens ?? Math.floor(contextWindow * 0.9),\n\t\t\t\t\tfixedContextTokens + 4 * settings.keepRecentTokens,\n\t\t\t\t);\n\treturn contextTokens > Math.min(target, modelLimit);\n}\n\n/** Chars/4 heuristic, not a provider count. Ordinary history never raises this floor. */\nexport function estimateFixedCompactionTokens(\n\tsystemPrompt: string,\n\ttools: readonly { name: string; description: string; parameters: unknown }[],\n\tmessages: readonly AgentMessage[],\n): number {\n\tconst toolSchemas = tools.map(({ name, description, parameters }) => ({ name, description, parameters }));\n\tlet tokens = Math.ceil((systemPrompt.length + (tools.length ? JSON.stringify(toolSchemas).length : 0)) / 4);\n\tlet hasHarnessSnapshot = false;\n\tfor (let index = messages.length - 1; index >= 0; index--) {\n\t\tconst message = messages[index];\n\t\tif (message.role !== \"custom\") continue;\n\t\t// A TaskFrame may contain dependent sparse revisions; all current pieces count.\n\t\tif (message.customType === TASK_FRAME_CUSTOM_TYPE) tokens += estimateTokens(message);\n\t\telse if (message.customType === HARNESS_SNAPSHOT_CUSTOM_TYPE && !hasHarnessSnapshot) {\n\t\t\ttokens += estimateTokens(message);\n\t\t\thasHarnessSnapshot = true;\n\t\t}\n\t}\n\treturn tokens;\n}\n/** Raise suffix estimates when observed usage exceeds chars/4 for the same prefix. */\nfunction compactionTokenScale(messages: readonly AgentMessage[]): number {\n\tconst usageInfo = getLastAssistantUsageInfo(messages);\n\tif (!usageInfo) return 1;\n\tlet estimatedTokens = 0;\n\tfor (let index = 0; index <= usageInfo.index; index++) {\n\t\testimatedTokens += estimateTokens(messages[index]);\n\t}\n\treturn estimatedTokens > 0 ? Math.max(1, calculateContextTokens(usageInfo.usage) / estimatedTokens) : 1;\n}\n\n/** Estimate message tokens with chars/4; dense content can exceed this heuristic. */\nexport function estimateTokens(message: AgentMessage): number {\n\tlet chars = 0;\n\n\tswitch (message.role) {\n\t\tcase \"user\": {\n\t\t\tconst content = (message as { content: string | Array<{ type: string; text?: string }> }).content;\n\t\t\tif (typeof content === \"string\") {\n\t\t\t\tchars = content.length;\n\t\t\t} else if (Array.isArray(content)) {\n\t\t\t\tfor (const block of content) {\n\t\t\t\t\tif (block.type === \"text\" && block.text) {\n\t\t\t\t\t\tchars += block.text.length;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t\treturn Math.ceil(chars / 4);\n\t\t}\n\t\tcase \"assistant\": {\n\t\t\tconst assistant = message as AssistantMessage;\n\t\t\tfor (const block of assistant.content) {\n\t\t\t\tif (block.type === \"text\") {\n\t\t\t\t\tchars += block.text.length;\n\t\t\t\t} else if (block.type === \"thinking\") {\n\t\t\t\t\tchars += block.thinking.length;\n\t\t\t\t} else if (block.type === \"toolCall\") {\n\t\t\t\t\tchars += block.name.length + JSON.stringify(block.arguments).length;\n\t\t\t\t}\n\t\t\t}\n\t\t\treturn Math.ceil(chars / 4);\n\t\t}\n\t\tcase \"custom\":\n\t\tcase \"toolResult\": {\n\t\t\tif (typeof message.content === \"string\") {\n\t\t\t\tchars = message.content.length;\n\t\t\t} else {\n\t\t\t\tfor (const block of message.content) {\n\t\t\t\t\tif (block.type === \"text\" && block.text) {\n\t\t\t\t\t\tchars += block.text.length;\n\t\t\t\t\t}\n\t\t\t\t\tif (block.type === \"image\") {\n\t\t\t\t\t\tchars += 4800; // Estimate images as 4000 chars, or 1200 tokens\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t\treturn Math.ceil(chars / 4);\n\t\t}\n\t\tcase \"bashExecution\": {\n\t\t\tchars = message.command.length + message.output.length;\n\t\t\treturn Math.ceil(chars / 4);\n\t\t}\n\t\tcase \"branchSummary\":\n\t\tcase \"compactionSummary\": {\n\t\t\tchars = message.summary.length;\n\t\t\treturn Math.ceil(chars / 4);\n\t\t}\n\t}\n\n\treturn 0;\n}\n\n/**\n * Find valid cut points: indices of user, assistant, custom, or bashExecution messages.\n * Never cut at tool results (they must follow their tool call).\n * When we cut at an assistant message with tool calls, its tool results follow it\n * and will be kept.\n * BashExecutionMessage is treated like a user message (user-initiated context).\n */\nfunction findValidCutPoints(entries: SessionEntry[], startIndex: number, endIndex: number): number[] {\n\tconst cutPoints: number[] = [];\n\tfor (let i = startIndex; i < endIndex; i++) {\n\t\tconst entry = entries[i];\n\t\tswitch (entry.type) {\n\t\t\tcase \"message\": {\n\t\t\t\tconst role = entry.message.role;\n\t\t\t\tswitch (role) {\n\t\t\t\t\tcase \"bashExecution\":\n\t\t\t\t\tcase \"custom\":\n\t\t\t\t\tcase \"branchSummary\":\n\t\t\t\t\tcase \"compactionSummary\":\n\t\t\t\t\tcase \"user\":\n\t\t\t\t\tcase \"assistant\":\n\t\t\t\t\t\tcutPoints.push(i);\n\t\t\t\t\t\tbreak;\n\t\t\t\t\tcase \"toolResult\":\n\t\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t\tbreak;\n\t\t\t}\n\t\t\tcase \"thinking_level_change\":\n\t\t\tcase \"model_change\":\n\t\t\tcase \"compaction\":\n\t\t\tcase \"branch_summary\":\n\t\t\tcase \"custom\":\n\t\t\tcase \"custom_message\":\n\t\t\tcase \"label\":\n\t\t\tcase \"session_info\":\n\t\t\t\tbreak;\n\t\t}\n\t\t// Branch summaries and custom messages are user-role turn boundaries.\n\t\tif (entry.type === \"branch_summary\" || entry.type === \"custom_message\") {\n\t\t\tcutPoints.push(i);\n\t\t}\n\t}\n\treturn cutPoints;\n}\n\n/**\n * Find the user message (or bashExecution) that starts the turn containing the given entry index.\n * Returns -1 if no turn start found before the index.\n * BashExecutionMessage is treated like a user message for turn boundaries.\n */\nexport function findTurnStartIndex(entries: SessionEntry[], entryIndex: number, startIndex: number): number {\n\tfor (let i = entryIndex; i >= startIndex; i--) {\n\t\tconst entry = entries[i];\n\t\tif (entry.type === \"branch_summary\" || entry.type === \"custom_message\") {\n\t\t\treturn i;\n\t\t}\n\t\tif (entry.type === \"message\") {\n\t\t\tconst role = entry.message.role;\n\t\t\tif (role === \"user\" || role === \"bashExecution\") {\n\t\t\t\treturn i;\n\t\t\t}\n\t\t}\n\t}\n\treturn -1;\n}\n\nexport interface CutPointResult {\n\t/** Index of first entry to keep */\n\tfirstKeptEntryIndex: number;\n\t/** Index of user message that starts the turn being split, or -1 if not splitting */\n\tturnStartIndex: number;\n\t/** Whether this cut splits a turn (cut point is not a user message) */\n\tisSplitTurn: boolean;\n}\n\n/**\n * Find the cut point in session entries that keeps approximately `keepRecentTokens`.\n *\n * Algorithm: Walk backwards from newest, accumulating estimated message sizes.\n * Stop when we've accumulated >= keepRecentTokens. Cut at that point.\n *\n * Can cut at user OR assistant messages (never tool results). When cutting at an\n * assistant message with tool calls, its tool results come after and will be kept.\n *\n * Returns CutPointResult with:\n * - firstKeptEntryIndex: the entry index to start keeping from\n * - turnStartIndex: if cutting mid-turn, the user message that started that turn\n * - isSplitTurn: whether we're cutting in the middle of a turn\n *\n * Only considers entries between `startIndex` and `endIndex` (exclusive).\n */\nexport function findCutPoint(\n\tentries: SessionEntry[],\n\tstartIndex: number,\n\tendIndex: number,\n\tkeepRecentTokens: number,\n\tallowShortSession = false,\n\ttokenScale = 1,\n): CutPointResult {\n\tconst cutPoints = findValidCutPoints(entries, startIndex, endIndex);\n\n\tif (cutPoints.length === 0) {\n\t\treturn { firstKeptEntryIndex: startIndex, turnStartIndex: -1, isSplitTurn: false };\n\t}\n\tlet accumulatedTokens = 0;\n\tlet cutIndex = cutPoints[0]; // Default: keep from first message (not header)\n\n\tfor (let i = endIndex - 1; i >= startIndex; i--) {\n\t\tconst entry = entries[i];\n\t\tif (entry.type !== \"message\") continue;\n\t\tconst messageTokens = estimateTokens(entry.message) * tokenScale;\n\t\taccumulatedTokens += messageTokens;\n\t\tif (accumulatedTokens >= keepRecentTokens) {\n\t\t\tcutIndex = cutPoints.filter((candidate) => candidate <= i).at(-1) ?? cutIndex;\n\t\t\tbreak;\n\t\t}\n\t}\n\tif (allowShortSession && cutIndex === cutPoints[0]) {\n\t\t// An explicit compact may summarize a short prefix, but must keep a real suffix.\n\t\tconst latest = cutPoints\n\t\t\t.filter((index) => {\n\t\t\t\tconst entry = entries[index];\n\t\t\t\treturn entry.type !== \"message\" || entry.message.role !== \"compactionSummary\";\n\t\t\t})\n\t\t\t.at(-1);\n\t\tif (latest !== undefined) cutIndex = latest;\n\t}\n\twhile (cutIndex > startIndex) {\n\t\tconst prevEntry = entries[cutIndex - 1];\n\t\tif (prevEntry.type === \"compaction\") {\n\t\t\tbreak;\n\t\t}\n\t\tif (prevEntry.type === \"message\") {\n\t\t\tbreak;\n\t\t}\n\t\tcutIndex--;\n\t}\n\tconst cutEntry = entries[cutIndex];\n\tconst isUserMessage = cutEntry.type === \"message\" && cutEntry.message.role === \"user\";\n\t// A cut in a non-user turn requires a prefix summary.\n\tconst turnStartIndex = isUserMessage ? -1 : findTurnStartIndex(entries, cutIndex, startIndex);\n\n\treturn {\n\t\tfirstKeptEntryIndex: cutIndex,\n\t\tturnStartIndex,\n\t\tisSplitTurn: !isUserMessage && turnStartIndex !== -1,\n\t};\n}\nconst SUMMARIZATION_PROMPT = `The messages above are a conversation to summarize. Create a structured context checkpoint summary that another LLM will use to continue the work.\n\nUse this EXACT format:\n\n## Goal\n[What is the user trying to accomplish? Can be multiple items if the session covers different tasks.]\n\n## Constraints & Preferences\n- [Any constraints, preferences, or requirements mentioned by user]\n- [Or \"(none)\" if none were mentioned]\n\n## Progress\n### Done\n- [x] [Completed tasks/changes]\n\n### In Progress\n- [ ] [Current work]\n\n### Blocked\n- [Issues preventing progress, if any]\n\n## Key Decisions\n- **[Decision]**: [Brief rationale]\n\n## Next Steps\n1. [Ordered list of what should happen next]\n\n## Critical Context\n- [Any data, examples, or references needed to continue]\n- [Or \"(none)\" if not applicable]\n\nKeep each section concise. Preserve exact file paths, function names, and error messages.`;\n\nconst RUNTIME_STATE_SUMMARY_NOTE =\n\t\"Runtime note: this summary does not establish whether a Python kernel is live or whether its variables, imports, helpers, or jobs remain available. Preserve useful names and their last observed state, including uncertainty. Use current runtime reports before relying on them; do not infer either survival or loss from compaction.\";\n\nconst UPDATE_SUMMARIZATION_PROMPT = `The messages above are NEW conversation messages to incorporate into the existing summary provided in <previous-summary> tags.\n\nUpdate the existing structured summary with new information. RULES:\n- PRESERVE all existing information from the previous summary\n- ADD new progress, decisions, and context from the new messages\n- UPDATE the Progress section: move items from \"In Progress\" to \"Done\" when completed\n- UPDATE \"Next Steps\" based on what was accomplished\n- PRESERVE exact file paths, function names, and error messages\n- If something is no longer relevant, you may remove it\n\nUse this EXACT format:\n\n## Goal\n[Preserve existing goals, add new ones if the task expanded]\n\n## Constraints & Preferences\n- [Preserve existing, add new ones discovered]\n\n## Progress\n### Done\n- [x] [Include previously done items AND newly completed items]\n\n### In Progress\n- [ ] [Current work - update based on progress]\n\n### Blocked\n- [Current blockers - remove if resolved]\n\n## Key Decisions\n- **[Decision]**: [Brief rationale] (preserve all previous, add new)\n\n## Next Steps\n1. [Update based on current state]\n\n## Critical Context\n- [Preserve important context, add new if needed]\n\nKeep each section concise. Preserve exact file paths, function names, and error messages.`;\n\n/**\n * Build the instruction portion of the summarization prompt: the initial or\n * update template, optional user instructions, and the runtime-state qualification.\n */\nexport function buildSummarizationPrompt(customInstructions?: string, previousSummary?: string): string {\n\tlet basePrompt = previousSummary ? UPDATE_SUMMARIZATION_PROMPT : SUMMARIZATION_PROMPT;\n\tif (customInstructions) {\n\t\tbasePrompt += `\\n\\n<user-instructions>\\nThe user provided these instructions for this summary. Follow them with high priority while keeping the section format above: emphasize what they ask to focus on, and preserve verbatim anything they ask to remember.\\n${customInstructions}\\n</user-instructions>`;\n\t}\n\treturn `${basePrompt}\\n\\n${RUNTIME_STATE_SUMMARY_NOTE}`;\n}\n\n/**\n * Generate a summary of the conversation using the LLM.\n * If previousSummary is provided, uses the update prompt to merge.\n */\nexport async function generateSummary(\n\tcurrentMessages: AgentMessage[],\n\tmodel: Model<any>,\n\treserveTokens: number,\n\tapiKey: string,\n\theaders?: Record<string, string>,\n\tsignal?: AbortSignal,\n\tcustomInstructions?: string,\n\tpreviousSummary?: string,\n\tthinkingLevel?: ThinkingLevel,\n\trequests?: InferenceCoordinator,\n\toutputTokenLimit?: number,\n): Promise<SummarySlice> {\n\tconst maxTokens = outputTokenLimit ?? Math.floor(0.8 * reserveTokens);\n\n\tconst basePrompt = buildSummarizationPrompt(customInstructions, previousSummary);\n\t// Serialize before the LLM call so it summarizes rather than continues this conversation.\n\tconst llmMessages = convertToLlm(currentMessages);\n\tconst conversationText = serializeConversation(llmMessages);\n\tlet promptText = `<conversation>\\n${conversationText}\\n</conversation>\\n\\n`;\n\tif (previousSummary) {\n\t\tpromptText += `<previous-summary>\\n${previousSummary}\\n</previous-summary>\\n\\n`;\n\t}\n\tpromptText += basePrompt;\n\n\tconst summarizationMessages = [\n\t\t{\n\t\t\trole: \"user\" as const,\n\t\t\tcontent: [{ type: \"text\" as const, text: promptText }],\n\t\t\ttimestamp: Date.now(),\n\t\t},\n\t];\n\n\tconst completionOptions =\n\t\tmodel.reasoning && thinkingLevel && thinkingLevel !== \"off\"\n\t\t\t? { maxTokens, signal, apiKey, headers, reasoning: thinkingLevel }\n\t\t\t: { maxTokens, signal, apiKey, headers };\n\n\tconst completion = completeInference(\n\t\trequests,\n\t\tmodel,\n\t\t{ systemPrompt: SUMMARIZATION_SYSTEM_PROMPT, messages: summarizationMessages },\n\t\tcompletionOptions,\n\t\t{\n\t\t\tpurpose: \"summary\",\n\t\t\tpurposeDetail: \"compaction\",\n\t\t\toperationId: headers?.[MODEL_REQUEST_ID_HEADER],\n\t\t\tsemanticEdgeId: headers?.[MODEL_REQUEST_ID_HEADER],\n\t\t},\n\t);\n\n\tconst response = await completion;\n\tif (response.stopReason === \"error\") {\n\t\tthrow new Error(`Summarization failed: ${response.errorMessage || \"Unknown error\"}`);\n\t}\n\n\tconst textContent = response.content\n\t\t.filter((c): c is { type: \"text\"; text: string } => c.type === \"text\")\n\t\t.map((c) => c.text)\n\t\t.join(\"\\n\");\n\n\treturn captureSummarySlice({ summary: textContent, usage: response.usage }, requests, completion, \"history\");\n}\nexport interface CompactionPreparation {\n\t/** UUID of first entry to keep */\n\tfirstKeptEntryId: string;\n\t/** Messages that will be summarized and discarded */\n\tmessagesToSummarize: AgentMessage[];\n\t/** Messages that will be turned into turn prefix summary (if splitting) */\n\tturnPrefixMessages: AgentMessage[];\n\t/** Whether this is a split turn (cut point in middle of turn) */\n\tisSplitTurn: boolean;\n\ttokensBefore: number;\n\t/** Summary from previous compaction, for iterative update */\n\tpreviousSummary?: string;\n\t/** File operations extracted from messagesToSummarize */\n\tfileOps: FileOperations;\n\t/** Compaction settions from settings.jsonl\t*/\n\tsettings: CompactionSettings;\n}\n\n/** Prepare the actual selected epoch view. Entry IDs are only real cut anchors, never synthetic rows. */\nexport function prepareViewCompaction(\n\tmessages: readonly AgentMessage[],\n\tentryIds: readonly (string | undefined)[],\n\tpathEntries: SessionEntry[],\n\tsettings: CompactionSettings,\n\tmaxCutEntryId?: string,\n\tallowShortSession = false,\n\tbudgetPressure = false,\n): CompactionPreparation | undefined {\n\tif (messages.length !== entryIds.length) throw new Error(\"Compaction views do not match their source anchors\");\n\t// firstKeptEntryId restores a chronological source suffix, not a selected-view suffix.\n\t// Older pinned views cannot become cut anchors across omitted source messages.\n\tconst selectedIds = new Set(entryIds);\n\tconst suffixAnchors = new Set<string>();\n\tfor (let index = pathEntries.length - 1; index >= 0; index--) {\n\t\tconst entry = pathEntries[index];\n\t\tif (!getMessageFromEntryForCompaction(entry)) continue;\n\t\tif (!selectedIds.has(entry.id)) break;\n\t\tsuffixAnchors.add(entry.id);\n\t}\n\tlet cuts = messages.flatMap((message, index) =>\n\t\tentryIds[index] &&\n\t\tsuffixAnchors.has(entryIds[index]!) &&\n\t\tmessage.role !== \"toolResult\" &&\n\t\tmessage.role !== \"compactionSummary\"\n\t\t\t? [index]\n\t\t\t: [],\n\t);\n\tlet relaxRetention = false;\n\tif (budgetPressure) {\n\t\t// Only caller-confirmed public OVER may relax retention; source/tool groups remain whole.\n\t\tconst spans: [number, number][] = [];\n\t\tconst sourceStarts = new Map<string, number>();\n\t\tconst resultEnds = new Map<string, number>();\n\t\t// Public history renders original roles as custom text; use its captured source anchors.\n\t\tconst sourceMessages = new Map(pathEntries.map((entry) => [entry.id, getMessageFromEntry(entry)]));\n\t\tconst anchoredMessages = messages.map((message, index) => sourceMessages.get(entryIds[index] ?? \"\") ?? message);\n\t\tfor (const [index, message] of anchoredMessages.entries()) {\n\t\t\tconst entryId = entryIds[index];\n\t\t\tif (entryId) {\n\t\t\t\tconst start = sourceStarts.get(entryId);\n\t\t\t\tif (start === undefined) sourceStarts.set(entryId, index);\n\t\t\t\telse spans.push([start, index]);\n\t\t\t}\n\t\t\tif (message.role === \"toolResult\") resultEnds.set(message.toolCallId, index);\n\t\t}\n\t\tlet latestCompleteExchange = messages.length - 1;\n\t\tconst completedAssistants: number[] = [];\n\t\tfor (const [index, message] of anchoredMessages.entries()) {\n\t\t\tif (message.role !== \"assistant\") continue;\n\t\t\tconst calls = message.content.filter((block) => block.type === \"toolCall\");\n\t\t\tconst completeExchange = calls.length > 0 && calls.every((call) => (resultEnds.get(call.id) ?? -1) > index);\n\t\t\tif (completeExchange) latestCompleteExchange = index;\n\t\t\tif (completeExchange || (!calls.length && message.stopReason === \"stop\")) completedAssistants.push(index);\n\t\t\tfor (const call of calls) {\n\t\t\t\tconst end = resultEnds.get(call.id);\n\t\t\t\tif (end !== undefined && end > index) spans.push([index, end]);\n\t\t\t}\n\t\t}\n\t\t// A lone current instruction before its first exchange is not removable history.\n\t\trelaxRetention = completedAssistants.some((index) => index < latestCompleteExchange);\n\t\tif (relaxRetention) {\n\t\t\tcuts = cuts.filter(\n\t\t\t\t(candidate) =>\n\t\t\t\t\tcandidate <= latestCompleteExchange &&\n\t\t\t\t\tspans.every(([start, end]) => candidate <= start || candidate > end),\n\t\t\t);\n\t\t}\n\t}\n\tif (!cuts.length) return;\n\tconst tokenScale = compactionTokenScale(messages);\n\tlet cut = cuts[0];\n\tlet tokens = 0;\n\tfor (let index = messages.length - 1; index >= 0; index--) {\n\t\tif (!entryIds[index] || messages[index].role === \"compactionSummary\") continue;\n\t\ttokens += estimateTokens(messages[index]) * tokenScale;\n\t\tif (tokens >= settings.keepRecentTokens) {\n\t\t\t// Include the message that crossed the threshold and its preceding legal group boundary.\n\t\t\tcut = cuts.filter((candidate) => candidate <= index).at(-1) ?? cut;\n\t\t\tbreak;\n\t\t}\n\t}\n\tif (relaxRetention || (allowShortSession && cut === cuts[0])) cut = cuts.at(-1)!;\n\tif (maxCutEntryId !== undefined) {\n\t\tconst maximum = entryIds.indexOf(maxCutEntryId);\n\t\tconst lastAllowed = cuts.filter((candidate) => candidate <= maximum).at(-1);\n\t\tif (lastAllowed === undefined) throw new Error(\"Compaction recovery boundary is unavailable\");\n\t\tcut = Math.min(cut, lastAllowed);\n\t}\n\tlet turnStart = -1;\n\tif (messages[cut].role !== \"user\") {\n\t\tfor (let index = cut; index >= 0; index--) {\n\t\t\tif (entryIds[index] && [\"user\", \"custom\", \"branchSummary\", \"bashExecution\"].includes(messages[index].role)) {\n\t\t\t\tturnStart = index;\n\t\t\t\tbreak;\n\t\t\t}\n\t\t}\n\t}\n\tconst isSplitTurn = turnStart >= 0;\n\tconst historyEnd = isSplitTurn ? turnStart : cut;\n\tlet previousSummary: string | undefined;\n\tlet previousSummaryIndex = -1;\n\tfor (const [index, message] of messages.entries()) {\n\t\tif (message.role !== \"compactionSummary\") continue;\n\t\tpreviousSummary = message.summary;\n\t\tpreviousSummaryIndex = pathEntries.findIndex((entry) => entry.id === entryIds[index]);\n\t}\n\tconst historical = messages.filter(\n\t\t(message, index) => entryIds[index] && index < historyEnd && message.role !== \"compactionSummary\",\n\t);\n\tconst turnPrefixMessages = isSplitTurn\n\t\t? messages.filter(\n\t\t\t\t(message, index) =>\n\t\t\t\t\tentryIds[index] && index >= turnStart && index < cut && message.role !== \"compactionSummary\",\n\t\t\t)\n\t\t: [];\n\tif (!historical.length && !turnPrefixMessages.length && (budgetPressure || allowShortSession || !previousSummary))\n\t\treturn;\n\t// Virtual TaskFrame displays are continuity input, never a canonical suffix boundary.\n\tconst messagesToSummarize = messages.filter(\n\t\t(message, index) => message.role !== \"compactionSummary\" && (!entryIds[index] || index < historyEnd),\n\t);\n\tconst fileOps = extractFileOperations(messagesToSummarize, pathEntries, previousSummaryIndex);\n\tfor (const message of turnPrefixMessages) extractFileOpsFromMessage(message, fileOps);\n\treturn {\n\t\tfirstKeptEntryId: entryIds[cut]!,\n\t\tmessagesToSummarize: structuredClone(messagesToSummarize),\n\t\tturnPrefixMessages: structuredClone(turnPrefixMessages),\n\t\tisSplitTurn,\n\t\ttokensBefore: estimateContextTokens([...messages]).tokens,\n\t\tpreviousSummary,\n\t\tfileOps,\n\t\tsettings,\n\t};\n}\n\nexport function prepareCompaction(\n\tpathEntries: SessionEntry[],\n\tsettings: CompactionSettings,\n\tallowShortSession = false,\n): CompactionPreparation | undefined {\n\tif (pathEntries.length > 0 && pathEntries[pathEntries.length - 1].type === \"compaction\") {\n\t\treturn undefined;\n\t}\n\n\tlet prevCompactionIndex = -1;\n\tfor (let i = pathEntries.length - 1; i >= 0; i--) {\n\t\tif (pathEntries[i].type === \"compaction\") {\n\t\t\tprevCompactionIndex = i;\n\t\t\tbreak;\n\t\t}\n\t}\n\n\tlet previousSummary: string | undefined;\n\tlet boundaryStart = 0;\n\tif (prevCompactionIndex >= 0) {\n\t\tconst prevCompaction = pathEntries[prevCompactionIndex] as CompactionEntry;\n\t\tpreviousSummary = prevCompaction.summary;\n\t\tconst firstKeptEntryIndex = pathEntries.findIndex((entry) => entry.id === prevCompaction.firstKeptEntryId);\n\t\tboundaryStart = firstKeptEntryIndex >= 0 ? firstKeptEntryIndex : prevCompactionIndex + 1;\n\t}\n\tconst boundaryEnd = pathEntries.length;\n\n\tconst messages = buildSessionContext(pathEntries).messages;\n\tconst tokensBefore = estimateContextTokens(messages).tokens;\n\n\tconst cutPoint = findCutPoint(\n\t\tpathEntries,\n\t\tboundaryStart,\n\t\tboundaryEnd,\n\t\tsettings.keepRecentTokens,\n\t\tallowShortSession,\n\t\tcompactionTokenScale(messages),\n\t);\n\tconst firstKeptEntry = pathEntries[cutPoint.firstKeptEntryIndex];\n\tif (!firstKeptEntry?.id) {\n\t\treturn undefined; // Session needs migration\n\t}\n\tconst firstKeptEntryId = firstKeptEntry.id;\n\n\tconst historyEnd = cutPoint.isSplitTurn ? cutPoint.turnStartIndex : cutPoint.firstKeptEntryIndex;\n\tconst messagesToSummarize: AgentMessage[] = [];\n\tfor (let i = boundaryStart; i < historyEnd; i++) {\n\t\tconst msg = getMessageFromEntryForCompaction(pathEntries[i]);\n\t\tif (msg) messagesToSummarize.push(msg);\n\t}\n\tconst turnPrefixMessages: AgentMessage[] = [];\n\tif (cutPoint.isSplitTurn) {\n\t\tfor (let i = cutPoint.turnStartIndex; i < cutPoint.firstKeptEntryIndex; i++) {\n\t\t\tconst msg = getMessageFromEntryForCompaction(pathEntries[i]);\n\t\t\tif (msg) turnPrefixMessages.push(msg);\n\t\t}\n\t}\n\n\t// Avoid a compaction that would summarize no history.\n\tif (messagesToSummarize.length === 0 && turnPrefixMessages.length === 0 && (allowShortSession || !previousSummary)) {\n\t\treturn undefined;\n\t}\n\tconst fileOps = extractFileOperations(messagesToSummarize, pathEntries, prevCompactionIndex);\n\t// Split turns retain their suffix, but their prefix file operations still belong in the summary.\n\tif (cutPoint.isSplitTurn) {\n\t\tfor (const msg of turnPrefixMessages) {\n\t\t\textractFileOpsFromMessage(msg, fileOps);\n\t\t}\n\t}\n\n\treturn {\n\t\tfirstKeptEntryId,\n\t\tmessagesToSummarize,\n\t\tturnPrefixMessages,\n\t\tisSplitTurn: cutPoint.isSplitTurn,\n\t\ttokensBefore,\n\t\tpreviousSummary,\n\t\tfileOps,\n\t\tsettings,\n\t};\n}\nconst TURN_PREFIX_SUMMARIZATION_PROMPT = `This is the PREFIX of a turn that was too large to keep. The SUFFIX (recent work) is retained.\n\nSummarize the prefix to provide context for the retained suffix:\n\n## Original Request\n[What did the user ask for in this turn?]\n\n## Early Progress\n- [Key decisions and work done in the prefix]\n\n## Context for Suffix\n- [Information needed to understand the retained recent work]\n\nBe concise. Focus on what's needed to understand the kept suffix.`;\n\n/**\n * Generate summaries for compaction using prepared data.\n * Returns CompactionResult - SessionManager adds uuid/parentUuid when saving.\n *\n * @param preparation - Pre-calculated preparation from prepareCompaction()\n * @param customInstructions - Optional custom focus for the summary\n */\n/** Runs one summary wire call; hosts decorate each call with its own request identity. */\nexport type SummaryCallRunner = <T>(\n\tcall: (callHeaders: Record<string, string> | undefined) => Promise<T>,\n) => Promise<T>;\n\nexport async function compact(\n\tpreparation: CompactionPreparation,\n\tmodel: Model<any>,\n\tapiKey: string,\n\theaders?: Record<string, string>,\n\tcustomInstructions?: string,\n\tsignal?: AbortSignal,\n\tthinkingLevel?: ThinkingLevel,\n\tsummaryCall: SummaryCallRunner = (call) => call(headers),\n\trequests?: InferenceCoordinator,\n\tsummaryCapacity?: (wrapper: string) => number | undefined,\n): Promise<CompactionResult> {\n\tconst {\n\t\tfirstKeptEntryId,\n\t\tmessagesToSummarize,\n\t\tturnPrefixMessages,\n\t\tisSplitTurn,\n\t\ttokensBefore,\n\t\tpreviousSummary,\n\t\tfileOps,\n\t\tsettings,\n\t} = preparation;\n\tlet summary: string;\n\tconst slices: SummarySlice[] = [];\n\tconst { readFiles, modifiedFiles } = computeFileLists(fileOps);\n\tconst fileSummary = formatFileOperations(readFiles, modifiedFiles);\n\tconst split = isSplitTurn && turnPrefixMessages.length > 0;\n\tconst splitSeparator = \"\\n\\n---\\n\\n**Turn Context (split turn):**\\n\\n\";\n\tconst wrapper =\n\t\tCOMPACTION_SUMMARY_PREFIX +\n\t\t(split ? (messagesToSummarize.length ? \"\" : \"No prior history.\") + splitSeparator : \"\") +\n\t\tfileSummary +\n\t\tCOMPACTION_SUMMARY_SUFFIX;\n\tconst capacity = summaryCapacity?.(wrapper);\n\tconst historyNeeded = !split || messagesToSummarize.length > 0;\n\tconst calls = Number(historyNeeded) + Number(split);\n\tif (capacity !== undefined && capacity < calls)\n\t\tthrow new Error(\n\t\t\t\"Context capacity exceeded: required evidence, retained tail and summary wrapper leave no room for a summary.\",\n\t\t);\n\tconst historyLimit =\n\t\tcapacity === undefined\n\t\t\t? undefined\n\t\t\t: Math.min(\n\t\t\t\t\tMath.floor(0.8 * settings.reserveTokens),\n\t\t\t\t\tsplit && historyNeeded ? Math.max(1, Math.floor(capacity * 0.6)) : capacity,\n\t\t\t\t);\n\tconst turnLimit =\n\t\tcapacity === undefined\n\t\t\t? undefined\n\t\t\t: Math.min(Math.floor(0.5 * settings.reserveTokens), capacity - (historyNeeded ? historyLimit! : 0));\n\n\tif (split) {\n\t\t// Split turns make two wire calls with different bodies; each needs its own identity.\n\t\tconst [historyResult, turnPrefixResult] = await Promise.all([\n\t\t\tmessagesToSummarize.length > 0\n\t\t\t\t? summaryCall((callHeaders) =>\n\t\t\t\t\t\tgenerateSummary(\n\t\t\t\t\t\t\tmessagesToSummarize,\n\t\t\t\t\t\t\tmodel,\n\t\t\t\t\t\t\tsettings.reserveTokens,\n\t\t\t\t\t\t\tapiKey,\n\t\t\t\t\t\t\tcallHeaders,\n\t\t\t\t\t\t\tsignal,\n\t\t\t\t\t\t\tcustomInstructions,\n\t\t\t\t\t\t\tpreviousSummary,\n\t\t\t\t\t\t\tthinkingLevel,\n\t\t\t\t\t\t\trequests,\n\t\t\t\t\t\t\thistoryLimit,\n\t\t\t\t\t\t),\n\t\t\t\t\t)\n\t\t\t\t: Promise.resolve<SummarySlice>({ summary: \"No prior history.\" }),\n\t\t\tsummaryCall((callHeaders) =>\n\t\t\t\tgenerateTurnPrefixSummary(\n\t\t\t\t\tturnPrefixMessages,\n\t\t\t\t\tmodel,\n\t\t\t\t\tsettings.reserveTokens,\n\t\t\t\t\tapiKey,\n\t\t\t\t\tcallHeaders,\n\t\t\t\t\tsignal,\n\t\t\t\t\tthinkingLevel,\n\t\t\t\t\trequests,\n\t\t\t\t\tturnLimit,\n\t\t\t\t),\n\t\t\t),\n\t\t]);\n\t\tslices.push(historyResult, turnPrefixResult);\n\t\tsummary = `${historyResult.summary}${splitSeparator}${turnPrefixResult.summary}`;\n\t} else {\n\t\tconst result = await summaryCall((callHeaders) =>\n\t\t\tgenerateSummary(\n\t\t\t\tmessagesToSummarize,\n\t\t\t\tmodel,\n\t\t\t\tsettings.reserveTokens,\n\t\t\t\tapiKey,\n\t\t\t\tcallHeaders,\n\t\t\t\tsignal,\n\t\t\t\tcustomInstructions,\n\t\t\t\tpreviousSummary,\n\t\t\t\tthinkingLevel,\n\t\t\t\trequests,\n\t\t\t\thistoryLimit,\n\t\t\t),\n\t\t);\n\t\tslices.push(result);\n\t\tsummary = result.summary;\n\t}\n\tsummary += fileSummary;\n\n\tif (!firstKeptEntryId) {\n\t\tthrow new Error(\"First kept entry has no UUID - session may need migration\");\n\t}\n\n\tlet usage: Usage | undefined;\n\tfor (const slice of slices) {\n\t\tif (!slice.usage) continue;\n\t\tusage ??= emptyUsage();\n\t\taddAssistantUsage(usage, slice.usage);\n\t}\n\tconst result: CompactionResult = {\n\t\tsummary,\n\t\tfirstKeptEntryId,\n\t\ttokensBefore,\n\t\tdetails: { readFiles, modifiedFiles } as CompactionDetails,\n\t\tusage,\n\t};\n\tconst bindings = slices.flatMap((slice) => {\n\t\tconst captured = nativeSummarySlices.get(slice);\n\t\tnativeSummarySlices.delete(slice);\n\t\treturn captured?.summary === slice.summary ? [captured.binding] : [];\n\t});\n\tif (bindings.length) nativeCompactionResults.set(result, { summary, bindings });\n\treturn result;\n}\n\n/**\n * Generate a summary for a turn prefix (when splitting a turn).\n */\nasync function generateTurnPrefixSummary(\n\tmessages: AgentMessage[],\n\tmodel: Model<any>,\n\treserveTokens: number,\n\tapiKey: string,\n\theaders?: Record<string, string>,\n\tsignal?: AbortSignal,\n\tthinkingLevel?: ThinkingLevel,\n\trequests?: InferenceCoordinator,\n\toutputTokenLimit?: number,\n): Promise<SummarySlice> {\n\tconst maxTokens = outputTokenLimit ?? Math.floor(0.5 * reserveTokens); // Smaller budget for turn prefix\n\tconst llmMessages = convertToLlm(messages);\n\tconst conversationText = serializeConversation(llmMessages);\n\tconst promptText = `<conversation>\\n${conversationText}\\n</conversation>\\n\\n${TURN_PREFIX_SUMMARIZATION_PROMPT}`;\n\tconst summarizationMessages = [\n\t\t{\n\t\t\trole: \"user\" as const,\n\t\t\tcontent: [{ type: \"text\" as const, text: promptText }],\n\t\t\ttimestamp: Date.now(),\n\t\t},\n\t];\n\n\tconst completion = completeInference(\n\t\trequests,\n\t\tmodel,\n\t\t{ systemPrompt: SUMMARIZATION_SYSTEM_PROMPT, messages: summarizationMessages },\n\t\tmodel.reasoning && thinkingLevel && thinkingLevel !== \"off\"\n\t\t\t? { maxTokens, signal, apiKey, headers, reasoning: thinkingLevel }\n\t\t\t: { maxTokens, signal, apiKey, headers },\n\t\t{\n\t\t\tpurpose: \"summary\",\n\t\t\tpurposeDetail: \"compaction-turn-prefix\",\n\t\t\toperationId: headers?.[MODEL_REQUEST_ID_HEADER],\n\t\t\tsemanticEdgeId: headers?.[MODEL_REQUEST_ID_HEADER],\n\t\t},\n\t);\n\n\tconst response = await completion;\n\tif (response.stopReason === \"error\") {\n\t\tthrow new Error(`Turn prefix summarization failed: ${response.errorMessage || \"Unknown error\"}`);\n\t}\n\n\tconst slice: SummarySlice = {\n\t\tsummary: response.content\n\t\t\t.filter((c): c is { type: \"text\"; text: string } => c.type === \"text\")\n\t\t\t.map((c) => c.text)\n\t\t\t.join(\"\\n\"),\n\t\tusage: response.usage,\n\t};\n\treturn captureSummarySlice(slice, requests, completion, \"turn-prefix\");\n}\n"]}
@@ -35,18 +35,19 @@ or loss from a summary alone.
35
35
 
36
36
  ### When It Triggers
37
37
 
38
- Auto-compaction uses an earlier, model-aware soft target by default:
38
+ Auto-compaction uses 90% of the model context window as its default soft target:
39
39
 
40
40
  ```
41
- softTarget = max(4 * keepRecentTokens, min(96000, contextWindow / 2))
41
+ softTarget = floor(0.9 * contextWindow)
42
42
  threshold = min(contextWindow - reserveTokens, max(softTarget, fixedContextTokens + 4 * keepRecentTokens))
43
43
  contextTokens > threshold
44
44
  ```
45
45
 
46
46
  With default `keepRecentTokens` of 20000 and `reserveTokens` of 16384, before
47
- fixed-context headroom raises the target, the thresholds are 96000 for a
48
- 272000-token model, 80000 for a 128000-token model, and 47616 for a 64000-token
49
- model. The model-window ceiling always applies.
47
+ fixed-context headroom raises the target, the thresholds are 244800 for a
48
+ 272000-token model, 111616 for a 128000-token model, and 47616 for a 64000-token
49
+ model. The model-window reserve can therefore trigger compaction before 90%.
50
+ The model-window ceiling always applies.
50
51
 
51
52
  `fixedContextTokens` estimates current system instructions, tool schemas, the
52
53
  current TaskFrame and latest harness snapshot. It does not count all historical
@@ -68,6 +69,33 @@ budgets remain separate; see [context management](context-management.md#model-aw
68
69
 
69
70
  You can also trigger manually with `/compact [instructions]`, where optional instructions focus the summary — for example `/compact focus on the auth refactor, remember the exact migration command`. The instructions are passed to the summarization prompt with high priority, persisted on the `CompactionEntry`, and shown on the `[compaction]` message in the TUI.
70
71
 
72
+ ### Agent-Requested Compaction
73
+
74
+ The agent can request compaction before the automatic threshold, including when
75
+ using Astra. The bundled `compact` skill is enabled by default and runs from the
76
+ Python REPL:
77
+
78
+ ```python
79
+ await compact.status()
80
+ await compact.run("keep the remaining plan and exact test names")
81
+ ```
82
+
83
+ `status()` reports `tokens`, `context_window`, `percent`, and `scheduled`. Usage
84
+ can be unknown immediately after compaction. `run()` schedules compaction at the
85
+ next turn boundary, not in the middle of the Python cell. It returns
86
+ `{"scheduled": True}` when accepted, or `{"scheduled": False, "reason": ...}`
87
+ when no turn is active or there is nothing to compact yet. Optional instructions
88
+ focus the summary. Repeating the request before the boundary updates them.
89
+ Interrupted tool work can continue after the checkpoint; this does not start new
90
+ work after an otherwise completed turn.
91
+
92
+ This request does not depend on the automatic threshold or `compaction.enabled`.
93
+ It requires context optimization to be on. `compaction.agentCallable: false`
94
+ disables the skill, and disabling Python or bundled skills can remove its normal
95
+ entry point. Each recursive agent uses its own session's compaction path; children
96
+ inherit the parent's compact-skill availability. There is no Astra-specific gate.
97
+ A request compacts the requesting session, not the whole agent tree.
98
+
71
99
  ### How It Works
72
100
 
73
101
  1. **Find cut point**: Walk backwards from newest message, accumulating token estimates until `keepRecentTokens` (default 20k, configurable in `~/.base-context/settings.json` or `<project-dir>/.base-context/settings.json`) is reached
@@ -455,10 +483,11 @@ Configure compaction in `~/.base-context/settings.json` or `<project-dir>/.base-
455
483
  | `enabled` | `true` | Enable auto-compaction |
456
484
  | `reserveTokens` | `16384` | Headroom used by the compaction threshold |
457
485
  | `keepRecentTokens` | `20000` | Estimated recent-token target for the retained tail |
458
- | `targetTokens` | Model-aware soft target | Positive safe integer to override the soft target, or `"model-limit"` for the model-window threshold |
486
+ | `targetTokens` | 90% of the model context window | Positive safe integer to override the soft target, or `"model-limit"` for the model-window threshold; fixed-context headroom and the model reserve still apply as described above |
487
+ | `agentCallable` | `true` | Expose the `compact` skill so the agent can request earlier compaction |
459
488
 
460
489
  Disable automatic compaction with `"compaction": { "enabled": false }`. Manual
461
- `/compact` remains available while `context.mode` is `"on"`. Setting `context.mode`
490
+ `/compact` and agent-requested compaction remain available while `context.mode` is `"on"`. Setting `context.mode`
462
491
  to `"off"` disables context optimization, including manual compaction; logging,
463
492
  recovery, limits and other non-optimization ownership remain active.
464
493
 
@@ -84,22 +84,28 @@ In supported native Responses/Codex selection paths, historical assistant litera
84
84
 
85
85
  See [SDK request-token profiles](sdk.md#explicit-request-token-budget-profiles) for the exact scope and configuration.
86
86
 
87
- Ordinary [compaction](compaction.md) uses a separate model-aware soft target:
88
- `max(4 * keepRecentTokens, min(96000, contextWindow / 2))`. The actual trigger also
89
- leaves `4 * keepRecentTokens` above estimated fixed context, then caps the result
90
- at `contextWindow - reserveTokens`. Fixed context includes current system
87
+ Ordinary [compaction](compaction.md) uses a separate soft target of 90% of the
88
+ model context window: `floor(0.9 * contextWindow)`. The actual trigger also leaves
89
+ `4 * keepRecentTokens` above estimated fixed context, then caps the result at
90
+ `contextWindow - reserveTokens`. Fixed context includes current system
91
91
  instructions, tool schemas, the current TaskFrame and latest harness snapshot,
92
92
  not every historical message. This avoids repeated ineffective summaries of
93
93
  noncompactable instructions.
94
94
 
95
- With small fixed context, default settings trigger earlier at 96000 estimated
96
- tokens for a 272000-token model. Set `compaction.targetTokens` to a positive safe
97
- integer to replace the soft target (still subject to fixed-context headroom), or
98
- `"model-limit"` to use exactly the full-window threshold.
99
- This policy reduces repeated large-history requests without clipping required
100
- replay groups. It can add summary calls and rereads; it is not a strict request
95
+ With small fixed context, default thresholds are 244800 estimated tokens for a
96
+ 272000-token model, 111616 for a 128000-token model, and 47616 for a 64000-token
97
+ model. The model-window reserve can trigger compaction before 90%. Set
98
+ `compaction.targetTokens` to a positive safe integer to replace the soft target
99
+ (still subject to fixed-context headroom), or `"model-limit"` to use exactly
100
+ `contextWindow - reserveTokens`. Required replay groups are not clipped to meet
101
+ this target. Summaries can add calls and rereads; this is not a strict request
101
102
  cap, an optimality guarantee, or evidence of a deployment's actual limit.
102
103
 
104
+ The agent can request an earlier checkpoint from the Python REPL with
105
+ `await compact.run()`, independently of the automatic threshold. It can inspect
106
+ usage with `await compact.status()`. This is enabled by default for all models,
107
+ including Astra; see [agent-requested compaction](compaction.md#agent-requested-compaction).
108
+
103
109
  ## Stable context epochs
104
110
 
105
111
  An epoch is a committed context choice: selected source recipes, task-frame text, and the accepted request contract. Within an epoch, unchanged context stays stable. A new selection or relevant resource change requires an accepted boundary rather than an unrecorded rewrite of the prefix.
package/docs/settings.md CHANGED
@@ -94,7 +94,8 @@ Private download manifests require `version` and `package` (or `packageName`) se
94
94
  | `compaction.enabled` | boolean | `true` | Enable auto-compaction |
95
95
  | `compaction.reserveTokens` | number | `16384` | Tokens reserved for LLM response |
96
96
  | `compaction.keepRecentTokens` | number | `20000` | Recent tokens to keep (not summarized) |
97
- | `compaction.targetTokens` | positive integer or `"model-limit"` | Model-aware soft target | Override the soft compaction target, still capped by the model window minus reserve |
97
+ | `compaction.targetTokens` | positive integer or `"model-limit"` | 90% of the model context window | Override the soft compaction target, still capped by the model window minus reserve |
98
+ | `compaction.agentCallable` | boolean | `true` | Expose the `compact` skill so the agent can request compaction before the automatic threshold |
98
99
  | `compaction.model` | object | Current main model and effort | Explicit summary model: `provider`, `modelId`, and `thinkingLevel` are all required |
99
100
 
100
101
  ```json
@@ -108,16 +109,24 @@ Private download manifests require `version` and `package` (or `packageName`) se
108
109
  ```
109
110
 
110
111
 
111
- Without `targetTokens`, the soft target is
112
- `max(4 * keepRecentTokens, min(96000, contextWindow / 2))`. The trigger also leaves
113
- `4 * keepRecentTokens` above an estimate of current fixed instructions, tool
114
- schemas, TaskFrame and latest harness snapshot. It is capped at
115
- `contextWindow - reserveTokens`. With small fixed context, default settings use
116
- 96000 for a 272000-token model. A positive safe integer replaces the soft target,
117
- but required-context headroom can raise it; `"model-limit"` uses exactly the
118
- full-window threshold. This is a summary heuristic,
119
- not strict provider-request admission. See [compaction](compaction.md#when-it-triggers)
120
- and [model-aware budgets](context-management.md#model-aware-budgets).
112
+ Without `targetTokens`, the soft target is 90% of the model context window:
113
+ `floor(0.9 * contextWindow)`. The trigger also leaves `4 * keepRecentTokens` above
114
+ an estimate of current fixed instructions, tool schemas, TaskFrame and latest
115
+ harness snapshot. It is capped at `contextWindow - reserveTokens`. With small
116
+ fixed context, default thresholds are 244800 for a 272000-token model, 111616 for a
117
+ 128000-token model, and 47616 for a 64000-token model. The model-window reserve can
118
+ therefore trigger compaction before 90%. A positive safe integer replaces the
119
+ soft target, but required-context headroom can raise it; `"model-limit"` uses
120
+ exactly `contextWindow - reserveTokens`. This is a summary heuristic, not strict
121
+ provider-request admission. See [compaction](compaction.md#when-it-triggers) and
122
+ [model-aware budgets](context-management.md#model-aware-budgets).
123
+
124
+ With the default `compaction.agentCallable: true`, the agent can use
125
+ `await compact.status()` and `await compact.run(instructions=None)` in the Python
126
+ REPL. This model-independent path includes Astra and can request compaction before
127
+ the automatic threshold. It also works with `compaction.enabled: false`, but new
128
+ requests require `context.mode: "on"`. Requests run at a turn boundary, not
129
+ mid-cell. See [agent-requested compaction](compaction.md#agent-requested-compaction).
121
130
 
122
131
  Set `compaction.model` to choose the model and effort for manual, automatic and
123
132
  model-requested compaction summaries. For example, when this exact model/route is
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ponythewhite/base-context",
3
- "version": "1.0.12",
3
+ "version": "1.0.13",
4
4
  "description": "Synerise base-context: a coding and research agent with durable context and a persistent Python REPL",
5
5
  "type": "module",
6
6
  "bin": {
@@ -42,9 +42,9 @@
42
42
  },
43
43
  "dependencies": {
44
44
  "@agentclientprotocol/sdk": "^1.3.0",
45
- "@ponythewhite/base-context-agent": "1.0.12",
46
- "@ponythewhite/base-context-ai": "1.0.12",
47
- "@ponythewhite/base-context-tui": "1.0.12",
45
+ "@ponythewhite/base-context-agent": "1.0.13",
46
+ "@ponythewhite/base-context-ai": "1.0.13",
47
+ "@ponythewhite/base-context-tui": "1.0.13",
48
48
  "@silvia-odwyer/photon-node": "^0.3.4",
49
49
  "chalk": "^5.5.0",
50
50
  "cli-highlight": "^2.1.11",