fix(native-chat): a cancelled launch's unsent messages give their notes back

Closing a launch that never published handed its other unsent messages to a draft no chat shows,
and ended them as "returned", which cleared the notes they carried. They now end as discarded, so
their notes go back on the shelf as before; the text is still kept in that draft.
This commit is contained in:
Brennan Benson
2026-10-04 05:44:52 -07:00
parent f61e6b59c9
commit 7acdff2cc6
2 changed files with 26 additions and 5 deletions
@@ -1,8 +1,8 @@
// What closing a chat does to its outbox: it never throws away a message the person sent. A
// cancelled launch's own prompt, never sent, goes with the launch (its notes back on the shelf).
// Any other message that never went out comes back to the conversation's draft, with its notes
// following the text. One that went out may be the host's, so it stays and settles when the chat
// is reopened.
// cancelled launch's own prompt, never sent, goes with the launch. Any other message that never
// went out comes back to the conversation's draft; its notes follow the text, except for a
// cancelled launch, whose draft no chat shows, so they go back on the shelf. One that went out may
// be the host's, so it stays and settles when the chat is reopened.
import type { StructuredAgentSessionOutboxEntry } from '../../../../shared/structured-agent-session-outbox'
import { structuredAgentSessionEntryAwaitsSettlement } from '../../../../shared/structured-agent-session-outbox-admission'
@@ -31,7 +31,7 @@ export function settleStructuredAgentSessionOutboxForClosedChat(
} else if (!(options.cancelledLaunch && entry.source === 'launch')) {
// The draft holds the text before the notes it carried are cleared.
returnStructuredAgentSessionMessage(entry)
endStructuredAgentSessionEntry(entry, 'returned')
endStructuredAgentSessionEntry(entry, options.cancelledLaunch ? 'discarded' : 'returned')
}
}
if (kept.length !== current.length) {
@@ -55,6 +55,7 @@ import { resetStructuredAgentSessionCarriedNotesForTests } from '@/components/na
import { useStructuredAgentSessionOutbox } from '@/components/native-chat/use-structured-agent-session-outbox'
import { markStructuredAgentSessionLaunchCancelled } from '@/lib/structured-agent-session-launch-registry'
import { isNoteInFlight } from '@/lib/notes-send-in-flight'
import { subscribeToStructuredAgentSessionEntryEndings } from '@/components/native-chat/structured-agent-session-entry-endings'
const WT = 'repo1::/path/wt1'
const SID = 'session-close-1'
@@ -193,4 +194,24 @@ describe('closing a chat tab while the host is out of reach', () => {
expect(draft()).toBe('')
expect(isNoteInFlight('note-a')).toBe(false)
})
// No chat shows a cancelled launch's draft, so a message typed into it gives its notes back.
it("ends a cancelled launch's other unsent messages as discarded, so their notes come back", () => {
const { store, chat } = seed()
markStructuredAgentSessionLaunchCancelled(WT, SID, 'local')
const typed = entry('TYPED-TEXT', { carriedNoteKeys: ['note-b'] })
writeOutbox(SID, [typed])
const endings: string[] = []
const unsubscribe = subscribeToStructuredAgentSessionEntryEndings((ended, ending) => {
if (ended.clientMessageId === typed.clientMessageId) {
endings.push(ending)
}
})
store.getState().closeUnifiedTab(chat.id)
unsubscribe()
expect(endings).toEqual(['discarded'])
expect(readOutbox(SID)).toEqual([])
expect(isNoteInFlight('note-b')).toBe(false)
})
})