import type { SessionState, ToolParameterEntry, WithParts } from "./types" import type { Logger } from "../logger" import { applyPendingCompressionDurations } from "../compress/timing" import { loadManualModeSetting, loadSessionState, saveSessionState } from "./persistence" import { isSubAgentSession, findLastCompactionTimestamp, countTurns, resetOnCompaction, createPruneMessagesState, loadPruneMessagesState, loadPruneMap, collectTurnNudgeAnchors, } from "./utils" import { getLastUserMessage } from "../messages/query" export const checkSession = async ( client: any, state: SessionState, logger: Logger, messages: WithParts[], manualModeDefault: boolean, ): Promise => { const lastUserMessage = getLastUserMessage(messages) if (!lastUserMessage) { return } const lastSessionId = lastUserMessage.info.sessionID if (state.sessionId === null || state.sessionId !== lastSessionId) { logger.info(`Session changed: ${state.sessionId} -> ${lastSessionId}`) try { await ensureSessionInitialized( client, state, lastSessionId, logger, messages, manualModeDefault, ) } catch (err: any) { logger.error("Failed to initialize session state", { error: err.message }) } } const lastCompactionTimestamp = findLastCompactionTimestamp(messages) if (lastCompactionTimestamp > state.lastCompaction) { state.lastCompaction = lastCompactionTimestamp resetOnCompaction(state) logger.info("Detected compaction - reset stale state", { timestamp: lastCompactionTimestamp, }) saveSessionState(state, logger).catch((error) => { logger.warn("Failed to persist state reset after compaction", { error: error instanceof Error ? error.message : String(error), }) }) } state.currentTurn = countTurns(state, messages) await refreshManualMode(state, lastSessionId, logger, manualModeDefault) } export function createSessionState(): SessionState { return { sessionId: null, isSubAgent: false, manualMode: false, compressPermission: undefined, pendingManualTrigger: null, prune: { tools: new Map(), messages: createPruneMessagesState(), }, nudges: { contextLimitAnchors: new Set(), turnNudgeAnchors: new Set(), iterationNudgeAnchors: new Set(), }, stats: { pruneTokenCounter: 0, totalPruneTokens: 0, }, compressionTiming: { startsByCallId: new Map(), pendingByCallId: new Map(), }, toolParameters: new Map(), subAgentResultCache: new Map(), toolIdList: [], messageIds: { byRawId: new Map(), byRef: new Map(), nextRef: 1, }, lastCompaction: 0, currentTurn: 0, modelContextLimit: undefined, systemPromptTokens: undefined, } } export function resetSessionState(state: SessionState): void { state.sessionId = null state.isSubAgent = false state.manualMode = false state.compressPermission = undefined state.pendingManualTrigger = null state.prune = { tools: new Map(), messages: createPruneMessagesState(), } state.nudges = { contextLimitAnchors: new Set(), turnNudgeAnchors: new Set(), iterationNudgeAnchors: new Set(), } state.stats = { pruneTokenCounter: 0, totalPruneTokens: 0, } state.toolParameters.clear() state.subAgentResultCache.clear() state.toolIdList = [] state.messageIds = { byRawId: new Map(), byRef: new Map(), nextRef: 1, } state.lastCompaction = 0 state.currentTurn = 0 state.modelContextLimit = undefined state.systemPromptTokens = undefined } export async function ensureSessionInitialized( client: any, state: SessionState, sessionId: string, logger: Logger, messages: WithParts[], manualModeEnabled: boolean, ): Promise { if (state.sessionId === sessionId) { return } // logger.info("session ID = " + sessionId) // logger.info("Initializing session state", { sessionId: sessionId }) resetSessionState(state) state.manualMode = manualModeEnabled ? "active" : false state.sessionId = sessionId const isSubAgent = await isSubAgentSession(client, sessionId) state.isSubAgent = isSubAgent // logger.info("isSubAgent = " + isSubAgent) state.lastCompaction = findLastCompactionTimestamp(messages) state.currentTurn = countTurns(state, messages) state.nudges.turnNudgeAnchors = collectTurnNudgeAnchors(messages) const persisted = await loadSessionState(sessionId, logger) if (persisted === null) { return } if (typeof persisted.manualMode === "boolean") { state.manualMode = persisted.manualMode ? "active" : false } state.prune.tools = loadPruneMap(persisted.prune.tools) state.prune.messages = loadPruneMessagesState(persisted.prune.messages) state.nudges.contextLimitAnchors = new Set(persisted.nudges.contextLimitAnchors || []) state.nudges.turnNudgeAnchors = new Set([ ...state.nudges.turnNudgeAnchors, ...(persisted.nudges.turnNudgeAnchors || []), ]) state.nudges.iterationNudgeAnchors = new Set( persisted.nudges.iterationNudgeAnchors || [], ) state.stats = { pruneTokenCounter: persisted.stats?.pruneTokenCounter || 0, totalPruneTokens: persisted.stats?.totalPruneTokens || 0, } const applied = applyPendingCompressionDurations(state) if (applied > 0) { await saveSessionState(state, logger) } } export async function refreshManualMode( state: SessionState, sessionId: string, logger: Logger, manualModeDefault: boolean, ): Promise { if (state.manualMode === "compress-pending") { return } const persisted = await loadManualModeSetting(sessionId, logger) const enabled = persisted ?? manualModeDefault state.manualMode = enabled ? "active" : false }