fix(todo-continuation-enforcer): track continuation stagnation state

Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-opencode)

Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
This commit is contained in:
YeonGyu-Kim
2026-03-08 02:17:51 +09:00
parent d7349b62da
commit 07e8b32ed1
2 changed files with 111 additions and 0 deletions
@@ -0,0 +1,49 @@
import { afterEach, beforeEach, describe, expect, test } from "bun:test"
import { createSessionStateStore, type SessionStateStore } from "./session-state"
describe("createSessionStateStore", () => {
let sessionStateStore: SessionStateStore
beforeEach(() => {
sessionStateStore = createSessionStateStore()
})
afterEach(() => {
sessionStateStore.shutdown()
})
test("given repeated incomplete counts after a continuation, tracks stagnation", () => {
// given
const sessionID = "ses-stagnation"
const state = sessionStateStore.getState(sessionID)
state.lastInjectedAt = Date.now()
// when
const firstUpdate = sessionStateStore.trackContinuationProgress(sessionID, 2)
const secondUpdate = sessionStateStore.trackContinuationProgress(sessionID, 2)
const thirdUpdate = sessionStateStore.trackContinuationProgress(sessionID, 2)
// then
expect(firstUpdate.stagnationCount).toBe(0)
expect(secondUpdate.stagnationCount).toBe(1)
expect(thirdUpdate.stagnationCount).toBe(2)
})
test("given incomplete count decreases, resets stagnation tracking", () => {
// given
const sessionID = "ses-progress-reset"
const state = sessionStateStore.getState(sessionID)
state.lastInjectedAt = Date.now()
sessionStateStore.trackContinuationProgress(sessionID, 3)
sessionStateStore.trackContinuationProgress(sessionID, 3)
// when
const progressUpdate = sessionStateStore.trackContinuationProgress(sessionID, 2)
// then
expect(progressUpdate.hasProgressed).toBe(true)
expect(progressUpdate.stagnationCount).toBe(0)
expect(sessionStateStore.getState(sessionID).lastIncompleteCount).toBe(2)
})
})
@@ -10,9 +10,17 @@ interface TrackedSessionState {
lastAccessedAt: number
}
export interface ContinuationProgressUpdate {
previousIncompleteCount?: number
stagnationCount: number
hasProgressed: boolean
}
export interface SessionStateStore {
getState: (sessionID: string) => SessionState
getExistingState: (sessionID: string) => SessionState | undefined
trackContinuationProgress: (sessionID: string, incompleteCount: number) => ContinuationProgressUpdate
resetContinuationProgress: (sessionID: string) => void
cancelCountdown: (sessionID: string) => void
cleanup: (sessionID: string) => void
cancelAllCountdowns: () => void
@@ -46,6 +54,7 @@ export function createSessionStateStore(): SessionStateStore {
}
const state: SessionState = {
stagnationCount: 0,
consecutiveFailures: 0,
}
sessions.set(sessionID, { state, lastAccessedAt: Date.now() })
@@ -61,6 +70,57 @@ export function createSessionStateStore(): SessionStateStore {
return undefined
}
function trackContinuationProgress(
sessionID: string,
incompleteCount: number
): ContinuationProgressUpdate {
const state = getState(sessionID)
const previousIncompleteCount = state.lastIncompleteCount
state.lastIncompleteCount = incompleteCount
if (previousIncompleteCount === undefined) {
state.stagnationCount = 0
return {
previousIncompleteCount,
stagnationCount: state.stagnationCount,
hasProgressed: false,
}
}
if (incompleteCount < previousIncompleteCount) {
state.stagnationCount = 0
return {
previousIncompleteCount,
stagnationCount: state.stagnationCount,
hasProgressed: true,
}
}
if (state.lastInjectedAt === undefined) {
return {
previousIncompleteCount,
stagnationCount: state.stagnationCount,
hasProgressed: false,
}
}
state.stagnationCount += 1
return {
previousIncompleteCount,
stagnationCount: state.stagnationCount,
hasProgressed: false,
}
}
function resetContinuationProgress(sessionID: string): void {
const state = getExistingState(sessionID)
if (!state) return
state.lastIncompleteCount = undefined
state.stagnationCount = 0
}
function cancelCountdown(sessionID: string): void {
const tracked = sessions.get(sessionID)
if (!tracked) return
@@ -100,6 +160,8 @@ export function createSessionStateStore(): SessionStateStore {
return {
getState,
getExistingState,
trackContinuationProgress,
resetContinuationProgress,
cancelCountdown,
cleanup,
cancelAllCountdowns,