fix(continuation): auto-continue GPT permission-seeking replies

Resume GPT sessions when the last assistant reply ends in a permission-seeking tail, while honoring stop-continuation and avoiding duplicate continuation across todo and atlas flows.

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

Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
This commit is contained in:
YeonGyu-Kim
2026-03-11 21:19:29 +09:00
parent 3f364cc8df
commit a1b060841f
22 changed files with 617 additions and 4 deletions
+6
View File
@@ -110,6 +110,7 @@ function scheduleRetry(input: {
const currentProgress = getPlanProgress(currentBoulder.active_plan)
if (currentProgress.isComplete) return
if (options?.isContinuationStopped?.(sessionID)) return
if (options?.shouldSkipContinuation?.(sessionID)) return
if (hasRunningBackgroundTasks(sessionID, options)) return
await injectContinuation({
@@ -192,6 +193,11 @@ export async function handleAtlasSessionIdle(input: {
return
}
if (options?.shouldSkipContinuation?.(sessionID)) {
log(`[${HOOK_NAME}] Skipped: another continuation hook already injected`, { sessionID })
return
}
if (sessionState.lastContinuationInjectedAt && now - sessionState.lastContinuationInjectedAt < CONTINUATION_COOLDOWN_MS) {
scheduleRetry({ ctx, sessionID, sessionState, options })
log(`[${HOOK_NAME}] Skipped: continuation cooldown active`, {
+31
View File
@@ -1042,6 +1042,37 @@ describe("atlas hook", () => {
expect(mockInput._promptMock).not.toHaveBeenCalled()
})
test("should skip when another continuation hook already injected", async () => {
// given - boulder state with incomplete plan
const planPath = join(TEST_DIR, "test-plan.md")
writeFileSync(planPath, "# Plan\n- [ ] Task 1\n- [ ] Task 2")
const state: BoulderState = {
active_plan: planPath,
started_at: "2026-01-02T10:00:00Z",
session_ids: [MAIN_SESSION_ID],
plan_name: "test-plan",
}
writeBoulderState(TEST_DIR, state)
const mockInput = createMockPluginInput()
const hook = createAtlasHook(mockInput, {
directory: TEST_DIR,
shouldSkipContinuation: (sessionID: string) => sessionID === MAIN_SESSION_ID,
})
// when
await hook.handler({
event: {
type: "session.idle",
properties: { sessionID: MAIN_SESSION_ID },
},
})
// then - should not call prompt because another continuation already handled it
expect(mockInput._promptMock).not.toHaveBeenCalled()
})
test("should clear abort state on message.updated", async () => {
// given - boulder with incomplete plan
const planPath = join(TEST_DIR, "test-plan.md")
+1
View File
@@ -7,6 +7,7 @@ export interface AtlasHookOptions {
directory: string
backgroundManager?: BackgroundManager
isContinuationStopped?: (sessionID: string) => boolean
shouldSkipContinuation?: (sessionID: string) => boolean
agentOverrides?: AgentOverrides
/** Enable auto-commit after each atomic task completion (default: true) */
autoCommit?: boolean