fix(stop-continuation): persist stop state across user messages (#3276)
The /stop-continuation command was ineffective because the stop- continuation-guard cleared its stopped state on the very next chat.message event. Since any user message (including normal chat after stopping) triggers chat.message, the continuation would resume immediately. Root cause: the chat.message handler called clear(sessionID) on every user message, treating it as a 'user resumed work' signal. But the user expects /stop-continuation to persist until they explicitly start work again. Changes: - stop-continuation-guard chat.message: no longer clears stop state - tool-execute-before: /start-work, /ralph-loop, /ulw-loop now explicitly clear the stop state (so continuation resumes when user intentionally restarts work) - Updated and added tests: 12 pass (3 new), 125 related tests pass Closes #3276
This commit is contained in:
@@ -100,10 +100,16 @@ export function createStopContinuationGuardHook(
|
|||||||
}: {
|
}: {
|
||||||
sessionID?: string
|
sessionID?: string
|
||||||
}): Promise<void> => {
|
}): Promise<void> => {
|
||||||
if (sessionID && stoppedSessions.has(sessionID)) {
|
// Intentionally no-op: stop state should persist across user messages.
|
||||||
clear(sessionID)
|
// Previously this cleared the stop on any new user message, but that caused
|
||||||
log(`[${HOOK_NAME}] Cleared stop state on new user message`, { sessionID })
|
// /stop-continuation to be ineffective — the user's very next message
|
||||||
}
|
// (including normal chat) would re-enable continuation.
|
||||||
|
//
|
||||||
|
// Stop state is now only cleared by:
|
||||||
|
// 1. /start-work (or /ulw-loop, /ralph-loop) via explicit clear() call
|
||||||
|
// 2. session.deleted event
|
||||||
|
// 3. Future /resume-continuation command
|
||||||
|
void sessionID
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
|||||||
@@ -162,7 +162,7 @@ describe("stop-continuation-guard", () => {
|
|||||||
expect(guard.isStopped(session2)).toBe(false)
|
expect(guard.isStopped(session2)).toBe(false)
|
||||||
})
|
})
|
||||||
|
|
||||||
test("should clear stopped state on new user message (chat.message)", async () => {
|
test("should NOT clear stopped state on new user message (chat.message)", async () => {
|
||||||
// given - a session that was stopped
|
// given - a session that was stopped
|
||||||
const guard = createStopContinuationGuardHook(createMockPluginInput())
|
const guard = createStopContinuationGuardHook(createMockPluginInput())
|
||||||
const sessionID = "test-session-4"
|
const sessionID = "test-session-4"
|
||||||
@@ -172,7 +172,38 @@ describe("stop-continuation-guard", () => {
|
|||||||
// when - user sends a new message
|
// when - user sends a new message
|
||||||
await guard["chat.message"]({ sessionID })
|
await guard["chat.message"]({ sessionID })
|
||||||
|
|
||||||
// then - stop state should be cleared (one-time only)
|
// then - stop state should persist (not cleared by user messages)
|
||||||
|
// Stop is only cleared by explicit work-starting commands (/start-work, /ralph-loop, /ulw-loop)
|
||||||
|
// or session deletion. This prevents /stop-continuation from being ineffective.
|
||||||
|
expect(guard.isStopped(sessionID)).toBe(true)
|
||||||
|
})
|
||||||
|
|
||||||
|
test("should persist stop state across multiple user messages", async () => {
|
||||||
|
// given - a session that was stopped
|
||||||
|
const guard = createStopContinuationGuardHook(createMockPluginInput())
|
||||||
|
const sessionID = "test-session-persist"
|
||||||
|
guard.stop(sessionID)
|
||||||
|
|
||||||
|
// when - user sends multiple messages
|
||||||
|
await guard["chat.message"]({ sessionID })
|
||||||
|
await guard["chat.message"]({ sessionID })
|
||||||
|
await guard["chat.message"]({ sessionID })
|
||||||
|
|
||||||
|
// then - stop state remains active
|
||||||
|
expect(guard.isStopped(sessionID)).toBe(true)
|
||||||
|
})
|
||||||
|
|
||||||
|
test("should clear stop state only via explicit clear() call", () => {
|
||||||
|
// given - a session that was stopped
|
||||||
|
const guard = createStopContinuationGuardHook(createMockPluginInput())
|
||||||
|
const sessionID = "test-session-explicit-clear"
|
||||||
|
guard.stop(sessionID)
|
||||||
|
expect(guard.isStopped(sessionID)).toBe(true)
|
||||||
|
|
||||||
|
// when - clear is called (simulating /start-work or /ralph-loop)
|
||||||
|
guard.clear(sessionID)
|
||||||
|
|
||||||
|
// then - stop state is cleared
|
||||||
expect(guard.isStopped(sessionID)).toBe(false)
|
expect(guard.isStopped(sessionID)).toBe(false)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|||||||
@@ -184,6 +184,19 @@ export function createToolExecuteBeforeHandler(args: {
|
|||||||
sessionID,
|
sessionID,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Clear stop state when user explicitly resumes work via work-starting commands.
|
||||||
|
// This ensures /stop-continuation persists until the user intentionally restarts.
|
||||||
|
const workStartingCommands = ["start-work", "ralph-loop", "ulw-loop"]
|
||||||
|
if (workStartingCommands.includes(command ?? "") && sessionID) {
|
||||||
|
if (hooks.stopContinuationGuard?.isStopped(sessionID)) {
|
||||||
|
hooks.stopContinuationGuard.clear(sessionID)
|
||||||
|
log("[stop-continuation] Stop state cleared by work-starting command", {
|
||||||
|
sessionID,
|
||||||
|
command,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user