From 55475b4fec45799703c00e7886fbfe07cb2ce572 Mon Sep 17 00:00:00 2001 From: Daniel Smolsky Date: Wed, 17 Dec 2025 02:15:24 -0500 Subject: [PATCH 1/5] Detect compaction from message data instead of event - Scan messages for assistant messages with info.summary === true - Run compaction detection on every checkSession call, not just init - Remove session.compacted event handler (no longer needed) - Remove lastCompacted from persistence (derived from messages now) - Remove unused checkForCompaction function This ensures compaction is detected even when: - Plugin was disabled during compaction - Session was loaded before plugin was installed - Any other case where the event wasn't captured --- lib/hooks.ts | 5 ----- lib/shared-utils.ts | 9 --------- lib/state/persistence.ts | 4 +--- lib/state/state.ts | 26 +++++++++++++++++++++++--- lib/strategies/prune-tool.ts | 4 ++-- 5 files changed, 26 insertions(+), 22 deletions(-) diff --git a/lib/hooks.ts b/lib/hooks.ts index f24d52c3..72fda69e 100644 --- a/lib/hooks.ts +++ b/lib/hooks.ts @@ -48,11 +48,6 @@ export function createEventHandler( return } - if (event.type === "session.compacted") { - logger.info("Session compaction detected - updating state") - state.lastCompaction = Date.now() - } - if (event.type === "session.status" && event.properties.status.type === "idle") { if (!config.strategies.onIdle.enabled) { return diff --git a/lib/shared-utils.ts b/lib/shared-utils.ts index 9cb60a18..fe6fbd60 100644 --- a/lib/shared-utils.ts +++ b/lib/shared-utils.ts @@ -1,4 +1,3 @@ -import { Logger } from "./logger" import { SessionState, WithParts } from "./state" export const isMessageCompacted = ( @@ -20,12 +19,4 @@ export const getLastUserMessage = ( return null } -export const checkForCompaction = ( - state: SessionState, - messages: WithParts[], - logger: Logger -): void => { - for (const msg of messages) { - } -} diff --git a/lib/state/persistence.ts b/lib/state/persistence.ts index 89d67729..eb975511 100644 --- a/lib/state/persistence.ts +++ b/lib/state/persistence.ts @@ -16,7 +16,6 @@ export interface PersistedSessionState { prune: Prune stats: SessionStats; lastUpdated: string; - lastCompacted: number } const STORAGE_DIR = join( @@ -55,8 +54,7 @@ export async function saveSessionState( sessionName: sessionName, prune: sessionState.prune, stats: sessionState.stats, - lastUpdated: new Date().toISOString(), - lastCompacted: sessionState.lastCompaction + lastUpdated: new Date().toISOString() }; const filePath = getSessionFilePath(sessionState.sessionId); diff --git a/lib/state/state.ts b/lib/state/state.ts index 035f81bc..4a2af7a4 100644 --- a/lib/state/state.ts +++ b/lib/state/state.ts @@ -21,11 +21,18 @@ export const checkSession = async ( if (state.sessionId === null || state.sessionId !== lastSessionId) { logger.info(`Session changed: ${state.sessionId} -> ${lastSessionId}`) try { - await ensureSessionInitialized(client, state, lastSessionId, logger) + await ensureSessionInitialized(client, state, lastSessionId, logger, messages) } catch (err: any) { logger.error("Failed to initialize session state", { error: err.message }) } } + + // Always check for compaction on every message check + const lastCompactionTimestamp = findLastCompactionTimestamp(messages) + if (lastCompactionTimestamp > state.lastCompaction) { + state.lastCompaction = lastCompactionTimestamp + logger.info("Detected compaction from messages", { timestamp: lastCompactionTimestamp }) + } } export function createSessionState(): SessionState { @@ -66,7 +73,8 @@ export async function ensureSessionInitialized( client: any, state: SessionState, sessionId: string, - logger: Logger + logger: Logger, + messages: WithParts[] ): Promise { if (state.sessionId === sessionId) { return; @@ -97,5 +105,17 @@ export async function ensureSessionInitialized( pruneTokenCounter: persisted.stats?.pruneTokenCounter || 0, totalPruneTokens: persisted.stats?.totalPruneTokens || 0, } - state.lastCompaction = persisted.lastCompacted || 0 +} + +function findLastCompactionTimestamp(messages: WithParts[]): number { + let lastTimestamp = 0 + for (const msg of messages) { + if (msg.info.role === "assistant" && msg.info.summary === true) { + const created = msg.info.time.created + if (created > lastTimestamp) { + lastTimestamp = created + } + } + } + return lastTimestamp } diff --git a/lib/strategies/prune-tool.ts b/lib/strategies/prune-tool.ts index e361325b..6cf9052c 100644 --- a/lib/strategies/prune-tool.ts +++ b/lib/strategies/prune-tool.ts @@ -66,14 +66,14 @@ export function createPruneTool( return "No numeric IDs provided. Format: [reason, id1, id2, ...] where reason is 'completion', 'noise', or 'consolidation'." } - await ensureSessionInitialized(ctx.client, state, sessionId, logger) - // Fetch messages to calculate tokens and find current agent const messagesResponse = await client.session.messages({ path: { id: sessionId } }) const messages: WithParts[] = messagesResponse.data || messagesResponse + await ensureSessionInitialized(ctx.client, state, sessionId, logger, messages) + const currentParams = getCurrentParams(messages, logger) const toolIdList: string[] = buildToolIdList(state, messages, logger) From a41ff814820936a81123a71c7345b575cec48451 Mon Sep 17 00:00:00 2001 From: Daniel Smolsky Date: Wed, 17 Dec 2025 02:55:07 -0500 Subject: [PATCH 2/5] Clear tool caches on compaction and add stale entry warning - Clear toolParameters and prune.toolIds when compaction is detected - Add warning log when tool is in cache but not in toolIdList - Stale tool data from pre-compaction no longer lingers in state --- lib/messages/prune.ts | 4 ++++ lib/state/state.ts | 5 ++++- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/lib/messages/prune.ts b/lib/messages/prune.ts index 918056e4..33d9a7a4 100644 --- a/lib/messages/prune.ts +++ b/lib/messages/prune.ts @@ -27,6 +27,10 @@ const buildPrunableToolsList = ( return } const numericId = toolIdList.indexOf(toolCallId) + if (numericId === -1) { + logger.warn(`Tool in cache but not in toolIdList - possible stale entry`, { toolCallId, tool: toolParameterEntry.tool }) + return + } const paramKey = extractParameterKey(toolParameterEntry.tool, toolParameterEntry.parameters) const description = paramKey ? `${toolParameterEntry.tool}, ${paramKey}` : toolParameterEntry.tool lines.push(`${numericId}: ${description}`) diff --git a/lib/state/state.ts b/lib/state/state.ts index 4a2af7a4..b9ad05f9 100644 --- a/lib/state/state.ts +++ b/lib/state/state.ts @@ -31,7 +31,10 @@ export const checkSession = async ( const lastCompactionTimestamp = findLastCompactionTimestamp(messages) if (lastCompactionTimestamp > state.lastCompaction) { state.lastCompaction = lastCompactionTimestamp - logger.info("Detected compaction from messages", { timestamp: lastCompactionTimestamp }) + // Clear stale tool data - these tools no longer exist in context + state.toolParameters.clear() + state.prune.toolIds = [] + logger.info("Detected compaction from messages - cleared tool cache", { timestamp: lastCompactionTimestamp }) } } From 4ae8475adedeb11329458196a6cecead8e8a2dff Mon Sep 17 00:00:00 2001 From: Daniel Smolsky Date: Wed, 17 Dec 2025 03:45:55 -0500 Subject: [PATCH 3/5] Optimize findLastCompactionTimestamp to iterate from end --- lib/state/state.ts | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/lib/state/state.ts b/lib/state/state.ts index b9ad05f9..3244b168 100644 --- a/lib/state/state.ts +++ b/lib/state/state.ts @@ -111,14 +111,11 @@ export async function ensureSessionInitialized( } function findLastCompactionTimestamp(messages: WithParts[]): number { - let lastTimestamp = 0 - for (const msg of messages) { + for (let i = messages.length - 1; i >= 0; i--) { + const msg = messages[i] if (msg.info.role === "assistant" && msg.info.summary === true) { - const created = msg.info.time.created - if (created > lastTimestamp) { - lastTimestamp = created - } + return msg.info.time.created } } - return lastTimestamp + return 0 } From 0288214a84a5df4f20770cf23f44e24801792ff4 Mon Sep 17 00:00:00 2001 From: Daniel Smolsky Date: Wed, 17 Dec 2025 03:50:17 -0500 Subject: [PATCH 4/5] cleanup --- lib/state/state.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/lib/state/state.ts b/lib/state/state.ts index 3244b168..6d47f1db 100644 --- a/lib/state/state.ts +++ b/lib/state/state.ts @@ -31,7 +31,6 @@ export const checkSession = async ( const lastCompactionTimestamp = findLastCompactionTimestamp(messages) if (lastCompactionTimestamp > state.lastCompaction) { state.lastCompaction = lastCompactionTimestamp - // Clear stale tool data - these tools no longer exist in context state.toolParameters.clear() state.prune.toolIds = [] logger.info("Detected compaction from messages - cleared tool cache", { timestamp: lastCompactionTimestamp }) From 9468cd804ee9e637f2dca4dbb359819be42e3d27 Mon Sep 17 00:00:00 2001 From: Daniel Smolsky Date: Wed, 17 Dec 2025 03:53:13 -0500 Subject: [PATCH 5/5] cleanup --- lib/state/state.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/lib/state/state.ts b/lib/state/state.ts index 6d47f1db..7ad26174 100644 --- a/lib/state/state.ts +++ b/lib/state/state.ts @@ -27,7 +27,6 @@ export const checkSession = async ( } } - // Always check for compaction on every message check const lastCompactionTimestamp = findLastCompactionTimestamp(messages) if (lastCompactionTimestamp > state.lastCompaction) { state.lastCompaction = lastCompactionTimestamp