Revert "fix(hooks): improve TODO continuation race condition handling with state machine pattern"
This reverts commit e59b0be6cc380a3750e2d56c4c7ba553feef2c40.
This commit is contained in:
@@ -7,8 +7,6 @@ interface Todo {
|
|||||||
id: string
|
id: string
|
||||||
}
|
}
|
||||||
|
|
||||||
type SessionStatus = "idle" | "aborted" | "continuation-sent"
|
|
||||||
|
|
||||||
const CONTINUATION_PROMPT = `[SYSTEM REMINDER - TODO CONTINUATION]
|
const CONTINUATION_PROMPT = `[SYSTEM REMINDER - TODO CONTINUATION]
|
||||||
|
|
||||||
Incomplete tasks remain in your todo list. Continue working on the next pending task.
|
Incomplete tasks remain in your todo list. Continue working on the next pending task.
|
||||||
@@ -17,8 +15,6 @@ Incomplete tasks remain in your todo list. Continue working on the next pending
|
|||||||
- Mark each task complete when finished
|
- Mark each task complete when finished
|
||||||
- Do not stop until all tasks are done`
|
- Do not stop until all tasks are done`
|
||||||
|
|
||||||
const CONTINUATION_DELAY_MS = 500
|
|
||||||
|
|
||||||
function detectInterrupt(error: unknown): boolean {
|
function detectInterrupt(error: unknown): boolean {
|
||||||
if (!error) return false
|
if (!error) return false
|
||||||
if (typeof error === "object") {
|
if (typeof error === "object") {
|
||||||
@@ -37,36 +33,43 @@ function detectInterrupt(error: unknown): boolean {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function createTodoContinuationEnforcer(ctx: PluginInput) {
|
export function createTodoContinuationEnforcer(ctx: PluginInput) {
|
||||||
const sessionStates = new Map<string, SessionStatus>()
|
const remindedSessions = new Set<string>()
|
||||||
const pendingContinuations = new Map<string, Timer>()
|
const interruptedSessions = new Set<string>()
|
||||||
|
const errorSessions = new Set<string>()
|
||||||
|
|
||||||
const cleanupSession = (sessionID: string) => {
|
return async ({ event }: { event: { type: string; properties?: unknown } }) => {
|
||||||
const timer = pendingContinuations.get(sessionID)
|
const props = event.properties as Record<string, unknown> | undefined
|
||||||
if (timer) clearTimeout(timer)
|
|
||||||
pendingContinuations.delete(sessionID)
|
if (event.type === "session.error") {
|
||||||
sessionStates.delete(sessionID)
|
const sessionID = props?.sessionID as string | undefined
|
||||||
|
if (sessionID) {
|
||||||
|
errorSessions.add(sessionID)
|
||||||
|
if (detectInterrupt(props?.error)) {
|
||||||
|
interruptedSessions.add(sessionID)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
const cancelPendingContinuation = (sessionID: string) => {
|
if (event.type === "session.idle") {
|
||||||
const timer = pendingContinuations.get(sessionID)
|
const sessionID = props?.sessionID as string | undefined
|
||||||
if (timer) {
|
if (!sessionID) return
|
||||||
clearTimeout(timer)
|
|
||||||
pendingContinuations.delete(sessionID)
|
// Wait for potential session.error events to be processed first
|
||||||
}
|
await new Promise(resolve => setTimeout(resolve, 150))
|
||||||
sessionStates.set(sessionID, "aborted")
|
|
||||||
|
const shouldBypass = interruptedSessions.has(sessionID) || errorSessions.has(sessionID)
|
||||||
|
|
||||||
|
interruptedSessions.delete(sessionID)
|
||||||
|
errorSessions.delete(sessionID)
|
||||||
|
|
||||||
|
if (shouldBypass) {
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
const scheduleContinuation = (sessionID: string) => {
|
if (remindedSessions.has(sessionID)) {
|
||||||
const prev = pendingContinuations.get(sessionID)
|
return
|
||||||
if (prev) clearTimeout(prev)
|
}
|
||||||
|
|
||||||
sessionStates.set(sessionID, "idle")
|
|
||||||
|
|
||||||
const timer = setTimeout(async () => {
|
|
||||||
pendingContinuations.delete(sessionID)
|
|
||||||
|
|
||||||
const state = sessionStates.get(sessionID)
|
|
||||||
if (state !== "idle") return
|
|
||||||
|
|
||||||
let todos: Todo[] = []
|
let todos: Todo[] = []
|
||||||
try {
|
try {
|
||||||
@@ -90,7 +93,13 @@ export function createTodoContinuationEnforcer(ctx: PluginInput) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
sessionStates.set(sessionID, "continuation-sent")
|
remindedSessions.add(sessionID)
|
||||||
|
|
||||||
|
// Re-check if abort occurred during the delay
|
||||||
|
if (interruptedSessions.has(sessionID) || errorSessions.has(sessionID)) {
|
||||||
|
remindedSessions.delete(sessionID)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await ctx.client.session.prompt({
|
await ctx.client.session.prompt({
|
||||||
@@ -106,46 +115,24 @@ export function createTodoContinuationEnforcer(ctx: PluginInput) {
|
|||||||
query: { directory: ctx.directory },
|
query: { directory: ctx.directory },
|
||||||
})
|
})
|
||||||
} catch {
|
} catch {
|
||||||
sessionStates.delete(sessionID)
|
remindedSessions.delete(sessionID)
|
||||||
}
|
}
|
||||||
}, CONTINUATION_DELAY_MS)
|
|
||||||
|
|
||||||
pendingContinuations.set(sessionID, timer)
|
|
||||||
}
|
|
||||||
|
|
||||||
return async ({ event }: { event: { type: string; properties?: unknown } }) => {
|
|
||||||
const props = event.properties as Record<string, unknown> | undefined
|
|
||||||
|
|
||||||
if (event.type === "session.error") {
|
|
||||||
const sessionID = props?.sessionID as string | undefined
|
|
||||||
if (sessionID && detectInterrupt(props?.error)) {
|
|
||||||
cancelPendingContinuation(sessionID)
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if (event.type === "session.idle") {
|
|
||||||
const sessionID = props?.sessionID as string | undefined
|
|
||||||
if (!sessionID) return
|
|
||||||
|
|
||||||
const state = sessionStates.get(sessionID)
|
|
||||||
if (state === "continuation-sent") return
|
|
||||||
|
|
||||||
scheduleContinuation(sessionID)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (event.type === "message.updated") {
|
if (event.type === "message.updated") {
|
||||||
const info = props?.info as Record<string, unknown> | undefined
|
const info = props?.info as Record<string, unknown> | undefined
|
||||||
const sessionID = info?.sessionID as string | undefined
|
const sessionID = info?.sessionID as string | undefined
|
||||||
if (sessionID && info?.role === "user") {
|
if (sessionID && info?.role === "user") {
|
||||||
sessionStates.delete(sessionID)
|
remindedSessions.delete(sessionID)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (event.type === "session.deleted") {
|
if (event.type === "session.deleted") {
|
||||||
const sessionInfo = props?.info as { id?: string } | undefined
|
const sessionInfo = props?.info as { id?: string } | undefined
|
||||||
if (sessionInfo?.id) {
|
if (sessionInfo?.id) {
|
||||||
cleanupSession(sessionInfo.id)
|
remindedSessions.delete(sessionInfo.id)
|
||||||
|
interruptedSessions.delete(sessionInfo.id)
|
||||||
|
errorSessions.delete(sessionInfo.id)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user