fix(runtime): publish a launch draft to mobile only for the tab's own agent

The publish had no agent check while the desktop consumer declines on
mismatch. The seed is keyed by tab id, which survives a pane's agent switch, so
mobile could adopt a draft desktop refuses — seed for claude, never open
desktop chat, switch the pane to Codex, and mobile prefills the Codex chat with
the Claude-era issue link. Align publish with the consumer.
This commit is contained in:
Brennan Benson
2026-07-27 16:56:35 -07:00
parent b7f8b085bd
commit a53dc02cb7
2 changed files with 38 additions and 3 deletions
@@ -726,6 +726,38 @@ describe('buildMobileSessionTabSnapshots', () => {
])
})
it('withholds a launch draft seeded for a different agent than the tab runs', () => {
// The seed is keyed by tab id, which survives an agent switch. Desktop's
// consumer declines on mismatch; publishing anyway would prefill the new
// agent's mobile chat with the previous agent's link.
const leafId = '11111111-1111-4111-8111-111111111111'
const state = makeState({
tabsByWorktree: {
'wt-1': [{ id: 'term-1', title: 'Terminal 1', launchAgent: 'codex' }]
} as unknown as AppState['tabsByWorktree'],
terminalLayoutsByTabId: {
'term-1': {
root: { type: 'leaf', leafId },
activeLeafId: leafId,
expandedLeafId: null,
ptyIdsByLeafId: { [leafId]: 'pty-1' }
}
} as unknown as AppState['terminalLayoutsByTabId'],
nativeChatLaunchDraftByTabId: {
'term-1': {
tabId: 'term-1',
agent: 'claude',
text: 'https://github.com/o/r/issues/12',
createdAt: 1
}
}
})
const snapshot = buildMobileSessionTabSnapshots(state)[0]
expect(snapshot?.tabs[0]).not.toHaveProperty('launchDraft')
})
it('preserves source-control diff metadata for mobile file tabs', () => {
const diffId = 'wt-1::diff::unstaged::src/app.ts'
const state = makeState({
@@ -1367,9 +1367,12 @@ function buildMobileTerminalSurfaceTabs(
: undefined
const savedPtyIdsByLeafId = sanitizedSavedLayout?.ptyIdsByLeafId ?? {}
const terminalTheme = resolveMobileTerminalTheme(state, systemPrefersDark)
const launchDraftText = state.nativeChatLaunchDraftByTabId?.[terminal.id]?.text.trim()
? state.nativeChatLaunchDraftByTabId[terminal.id].text
: null
// Agent-matched like the desktop consumer: a pane whose agent changed keeps its
// tab id, so an unmatched seed would prefill the new agent's chat with stale text.
const seededLaunchDraft = state.nativeChatLaunchDraftByTabId?.[terminal.id]
const launchDraftEntry =
seededLaunchDraft && seededLaunchDraft.agent === terminal.launchAgent ? seededLaunchDraft : null
const launchDraftText = launchDraftEntry?.text.trim() ? launchDraftEntry.text : null
const container = registered?.getContainer()
const firstChild = container?.firstElementChild
const liveLayoutRoot = serializePaneTree(