Merge pull request #4282 from SpencerJung/fix/issue-4149-terminal-continuation-guard
fix(atlas): honor stopped continuation after boulder completion
This commit is contained in:
@@ -210,4 +210,55 @@ describe("handleAtlasSessionIdle completion nudge", () => {
|
|||||||
expect(promptAsyncMock).toHaveBeenCalledTimes(1)
|
expect(promptAsyncMock).toHaveBeenCalledTimes(1)
|
||||||
expect(getState(SESSION_ID).boulderCompletionNudgedAt?.[workId]).toBeNumber()
|
expect(getState(SESSION_ID).boulderCompletionNudgedAt?.[workId]).toBeNumber()
|
||||||
})
|
})
|
||||||
|
|
||||||
|
it("does not send a completion nudge after continuation was explicitly stopped", async () => {
|
||||||
|
// given
|
||||||
|
const planPath = join(testDirectory, "plan.md")
|
||||||
|
writeFileSync(planPath, "## TODOs\n- [x] 1. Parse input\n")
|
||||||
|
|
||||||
|
const boulder = createBoulderState(planPath, SESSION_ID, "atlas")
|
||||||
|
const workId = boulder.active_work_id
|
||||||
|
if (!workId) {
|
||||||
|
throw new Error("Expected active_work_id")
|
||||||
|
}
|
||||||
|
writeBoulderState(testDirectory, boulder)
|
||||||
|
|
||||||
|
const promptAsyncMock = mock(async () => ({ data: {} }))
|
||||||
|
const ctx = unsafeTestValue<PluginInput>({
|
||||||
|
directory: testDirectory,
|
||||||
|
client: {
|
||||||
|
session: {
|
||||||
|
promptAsync: promptAsyncMock,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
const retryTimer = setTimeout(() => {}, 60_000)
|
||||||
|
const sessionStateById = new Map<string, SessionState>([
|
||||||
|
[SESSION_ID, { promptFailureCount: 0, pendingRetryTimer: retryTimer }],
|
||||||
|
])
|
||||||
|
const getState = (sessionId: string): SessionState => {
|
||||||
|
let state = sessionStateById.get(sessionId)
|
||||||
|
if (!state) {
|
||||||
|
state = { promptFailureCount: 0 }
|
||||||
|
sessionStateById.set(sessionId, state)
|
||||||
|
}
|
||||||
|
return state
|
||||||
|
}
|
||||||
|
|
||||||
|
// when
|
||||||
|
await handleAtlasSessionIdle({
|
||||||
|
ctx,
|
||||||
|
sessionID: SESSION_ID,
|
||||||
|
getState,
|
||||||
|
options: {
|
||||||
|
isContinuationStopped: (sessionId) => sessionId === SESSION_ID,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
// then
|
||||||
|
expect(promptAsyncMock).not.toHaveBeenCalled()
|
||||||
|
expect(getState(SESSION_ID).pendingRetryTimer).toBeUndefined()
|
||||||
|
expect(getState(SESSION_ID).boulderCompletionNudgedAt?.[workId]).toBeUndefined()
|
||||||
|
expect(readBoulderState(testDirectory)?.works?.[workId]?.status).toBe("completed")
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -246,6 +246,11 @@ export async function handleAtlasSessionIdle(input: {
|
|||||||
|
|
||||||
const { boulderState, progress, appendedSession } = activeBoulderSession
|
const { boulderState, progress, appendedSession } = activeBoulderSession
|
||||||
if (progress.isComplete) {
|
if (progress.isComplete) {
|
||||||
|
if (sessionState.pendingRetryTimer) {
|
||||||
|
clearTimeout(sessionState.pendingRetryTimer)
|
||||||
|
sessionState.pendingRetryTimer = undefined
|
||||||
|
}
|
||||||
|
|
||||||
const work = getWorkForSession(ctx.directory, sessionID)
|
const work = getWorkForSession(ctx.directory, sessionID)
|
||||||
if (work) {
|
if (work) {
|
||||||
completeBoulder(ctx.directory, work.work_id)
|
completeBoulder(ctx.directory, work.work_id)
|
||||||
@@ -258,6 +263,11 @@ export async function handleAtlasSessionIdle(input: {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (options?.isContinuationStopped?.(sessionID)) {
|
||||||
|
log(`[${HOOK_NAME}] Boulder completion nudge skipped because continuation stopped`, { sessionID, plan: boulderState.plan_name })
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
if (sessionState.boulderCompletionNudgedAt?.[work.work_id]) {
|
if (sessionState.boulderCompletionNudgedAt?.[work.work_id]) {
|
||||||
log(`[${HOOK_NAME}] Boulder complete`, { sessionID, plan: boulderState.plan_name })
|
log(`[${HOOK_NAME}] Boulder complete`, { sessionID, plan: boulderState.plan_name })
|
||||||
return
|
return
|
||||||
|
|||||||
Reference in New Issue
Block a user