fix(repl): queue prompt guidance for next turn (#333)

Keep normal prompt submissions during generation queued instead of interrupting the current turn. Add a visible next-turn banner in the prompt area so users can tell their follow-up guidance was accepted, and cover the new behavior with focused tests.

Fixes #328

Co-authored-by: Claude <noreply@anthropic.com>
This commit is contained in:
KRATOS
2026-04-04 17:57:59 +05:30
committed by GitHub
parent fbf3385395
commit cdc92d16e4
4 changed files with 147 additions and 8 deletions

View File

@@ -0,0 +1,89 @@
import { afterEach, beforeEach, describe, expect, it, mock } from 'bun:test'
import { getCommandQueue, resetCommandQueue } from './messageQueueManager.js'
describe('handlePromptSubmit', () => {
beforeEach(() => {
resetCommandQueue()
mock.module('src/services/analytics/index.js', () => ({
logEvent: () => {},
}))
})
afterEach(() => {
resetCommandQueue()
mock.restore()
})
it('queues prompt submissions during generation without interrupting the current turn', async () => {
const { handlePromptSubmit } = await import('./handlePromptSubmit.js')
const abortCalls: unknown[] = []
const inputChanges: string[] = []
let cursorOffset = 123
let bufferCleared = false
let pastedContentsCleared = false
let historyReset = false
await handlePromptSubmit({
input: ' use another library ',
mode: 'prompt',
pastedContents: {},
helpers: {
setCursorOffset: offset => {
cursorOffset = offset
},
clearBuffer: () => {
bufferCleared = true
},
resetHistory: () => {
historyReset = true
},
},
onInputChange: value => {
inputChanges.push(value)
},
setPastedContents: updater => {
const nextValue =
typeof updater === 'function'
? updater({ 1: { id: 1, type: 'text', content: 'x' } })
: updater
pastedContentsCleared = Object.keys(nextValue).length === 0
},
abortController: {
abort: (reason: unknown) => {
abortCalls.push(reason)
},
} as never,
hasInterruptibleToolInProgress: true,
queryGuard: {
isActive: true,
} as never,
isExternalLoading: false,
commands: [],
messages: [],
mainLoopModel: 'sonnet',
ideSelection: undefined,
querySource: 'repl' as never,
setToolJSX: () => {},
getToolUseContext: () => ({}) as never,
setUserInputOnProcessing: () => {},
setAbortController: () => {},
onQuery: async () => {},
setAppState: () => ({}) as never,
})
expect(abortCalls).toEqual([])
expect(inputChanges).toEqual([''])
expect(cursorOffset).toBe(0)
expect(bufferCleared).toBe(true)
expect(pastedContentsCleared).toBe(true)
expect(historyReset).toBe(true)
expect(getCommandQueue()).toMatchObject([
{
value: 'use another library',
preExpansionValue: 'use another library',
mode: 'prompt',
},
])
})
})

View File

@@ -316,9 +316,10 @@ export async function handlePromptSubmit(
return
}
// Interrupt the current turn when all executing tools have
// interruptBehavior 'cancel' (e.g. SleepTool).
if (params.hasInterruptibleToolInProgress) {
// Prompt submissions during generation should guide the next turn without
// interrupting the current one. Keep the explicit interrupt path only for
// non-prompt inputs that opt into that behavior.
if (mode !== 'prompt' && params.hasInterruptibleToolInProgress) {
logForDebugging(
`[interrupt] Aborting current turn: streamMode=${params.streamMode}`,
)