diff --git a/src/hooks/todo-continuation-enforcer/session-state.ts b/src/hooks/todo-continuation-enforcer/session-state.ts index 2116a6af0..d6ca26cb6 100644 --- a/src/hooks/todo-continuation-enforcer/session-state.ts +++ b/src/hooks/todo-continuation-enforcer/session-state.ts @@ -1,4 +1,4 @@ -import type { SessionState } from "./types" +import type { SessionState, Todo } from "./types" // TTL for idle session state entries (10 minutes) const SESSION_STATE_TTL_MS = 10 * 60 * 1000 @@ -8,6 +8,8 @@ const SESSION_STATE_PRUNE_INTERVAL_MS = 2 * 60 * 1000 interface TrackedSessionState { state: SessionState lastAccessedAt: number + lastCompletedCount?: number + lastTodoStatusSignature?: string } export interface ContinuationProgressUpdate { @@ -19,7 +21,7 @@ export interface ContinuationProgressUpdate { export interface SessionStateStore { getState: (sessionID: string) => SessionState getExistingState: (sessionID: string) => SessionState | undefined - trackContinuationProgress: (sessionID: string, incompleteCount: number) => ContinuationProgressUpdate + trackContinuationProgress: (sessionID: string, incompleteCount: number, todos?: Todo[]) => ContinuationProgressUpdate resetContinuationProgress: (sessionID: string) => void cancelCountdown: (sessionID: string) => void cleanup: (sessionID: string) => void @@ -27,6 +29,13 @@ export interface SessionStateStore { shutdown: () => void } +function getTodoStatusSignature(todos: Todo[]): string { + return todos + .map((todo) => `${todo.id ?? `${todo.content}:${todo.priority}`}:${todo.status}`) + .sort() + .join("|") +} + export function createSessionStateStore(): SessionStateStore { const sessions = new Map() @@ -46,19 +55,27 @@ export function createSessionStateStore(): SessionStateStore { pruneInterval.unref() } - function getState(sessionID: string): SessionState { + function getTrackedSession(sessionID: string): TrackedSessionState { const existing = sessions.get(sessionID) if (existing) { existing.lastAccessedAt = Date.now() - return existing.state + return existing } const state: SessionState = { stagnationCount: 0, consecutiveFailures: 0, } - sessions.set(sessionID, { state, lastAccessedAt: Date.now() }) - return state + const trackedSession: TrackedSessionState = { + state, + lastAccessedAt: Date.now(), + } + sessions.set(sessionID, trackedSession) + return trackedSession + } + + function getState(sessionID: string): SessionState { + return getTrackedSession(sessionID).state } function getExistingState(sessionID: string): SessionState | undefined { @@ -72,12 +89,30 @@ export function createSessionStateStore(): SessionStateStore { function trackContinuationProgress( sessionID: string, - incompleteCount: number + incompleteCount: number, + todos?: Todo[] ): ContinuationProgressUpdate { - const state = getState(sessionID) + const trackedSession = getTrackedSession(sessionID) + const state = trackedSession.state const previousIncompleteCount = state.lastIncompleteCount + const currentCompletedCount = todos?.filter((todo) => todo.status === "completed").length + const currentTodoStatusSignature = todos ? getTodoStatusSignature(todos) : undefined + const hasCompletedMoreTodos = + currentCompletedCount !== undefined + && trackedSession.lastCompletedCount !== undefined + && currentCompletedCount > trackedSession.lastCompletedCount + const hasTodoStatusChanged = + currentTodoStatusSignature !== undefined + && trackedSession.lastTodoStatusSignature !== undefined + && currentTodoStatusSignature !== trackedSession.lastTodoStatusSignature state.lastIncompleteCount = incompleteCount + if (currentCompletedCount !== undefined) { + trackedSession.lastCompletedCount = currentCompletedCount + } + if (currentTodoStatusSignature !== undefined) { + trackedSession.lastTodoStatusSignature = currentTodoStatusSignature + } if (previousIncompleteCount === undefined) { state.stagnationCount = 0 @@ -88,7 +123,7 @@ export function createSessionStateStore(): SessionStateStore { } } - if (incompleteCount < previousIncompleteCount) { + if (incompleteCount < previousIncompleteCount || hasCompletedMoreTodos || hasTodoStatusChanged) { state.stagnationCount = 0 return { previousIncompleteCount, @@ -114,11 +149,17 @@ export function createSessionStateStore(): SessionStateStore { } function resetContinuationProgress(sessionID: string): void { - const state = getExistingState(sessionID) - if (!state) return + const trackedSession = sessions.get(sessionID) + if (!trackedSession) return + + trackedSession.lastAccessedAt = Date.now() + + const { state } = trackedSession state.lastIncompleteCount = undefined state.stagnationCount = 0 + trackedSession.lastCompletedCount = undefined + trackedSession.lastTodoStatusSignature = undefined } function cancelCountdown(sessionID: string): void {