fix(ai-sessions): hand a refused programmatic prompt back to the composer

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
hugocasa
2026-08-27 15:07:03 +02:00
co-authored by Claude Opus 5
parent 2a90cb23ff
commit d1534ee40b
2 changed files with 18 additions and 7 deletions
@@ -1797,7 +1797,18 @@ export class AIChatManager {
files: AttachedTextFile[] = []
) {
if (this.mirroringRemoteRun) {
sendUserToast('This session is running in another tab. Try again when it finishes.', true)
// Handed back, not dropped. These senders have no draft of their own to
// fall back on — the raw-app inline prompt would lose what the user
// typed outright. Not `restoreToInput`, whose no-composer fallback is
// the very queue being refused.
const restored = this.aiChatInput?.prependText(text, images, files) === true
if (restored) this.#restoreDomContext(context, true)
sendUserToast(
restored
? 'This session is running in another tab. Your prompt is waiting in the composer.'
: 'This session is running in another tab. Try again when it finishes.',
true
)
return
}
const trimmed = text.trim()
@@ -416,22 +416,22 @@ describe('AIChatManager.sendOrQueue', () => {
expect(manager.queuedMessage).toBe('')
})
// `loading` while another tab drives is that tab's, and the turn that drains
// this queue is its turn, not ours. Anything parked here would sit until some
// unrelated later turn of our own picked it up and ran an instruction written
// against a workspace that had moved on. The composer is locked for the same
// reason; these senders never touch a composer.
it('refuses to queue a programmatic prompt while another tab drives', () => {
// These senders have no draft of their own, so a refusal that keeps nothing
// loses what the user typed.
it('hands a programmatic prompt back instead of queueing it for another tab', () => {
const manager = new AIChatManager()
manager.isSessionChat = true
manager.sessionId = 'session-programmatic-queue'
noteDriverAlive('session-programmatic-queue', false)
manager.loading = true
const prependText = vi.fn(() => true)
manager.setAiChatInput({ prependText } as any)
manager.sendOrQueue('deploy the fix')
expect(mocks.runChatLoop).not.toHaveBeenCalled()
expect(manager.queuedMessage).toBe('')
expect(prependText).toHaveBeenCalledWith('deploy the fix', [], [])
})
// `loading` only rises after a send's attachment upkeep, so gating on it alone