fix(remote): surface a toast when agent startup prompt/draft delivery is dropped

Co-authored-by: Orca <help@stably.ai>
This commit is contained in:
Jinwoo-H
2026-07-10 05:51:53 -04:00
co-authored by Orca
parent fc40ca2bdf
commit 167deb9697
2 changed files with 83 additions and 6 deletions
+73 -4
View File
@@ -4,6 +4,7 @@ const {
mockInspectRuntimeTerminalProcess,
mockSendRuntimePtyInputVerified,
mockPasteDraftToAgentPtyWhenReady,
mockShowAutomationPromptNotSentToast,
mockTrack,
store,
storeListeners,
@@ -12,6 +13,7 @@ const {
mockInspectRuntimeTerminalProcess: vi.fn(),
mockSendRuntimePtyInputVerified: vi.fn(),
mockPasteDraftToAgentPtyWhenReady: vi.fn(),
mockShowAutomationPromptNotSentToast: vi.fn(),
mockTrack: vi.fn(),
storeListeners: new Set<(state: unknown, previousState: unknown) => void>(),
startupLeafId: '11111111-1111-4111-8111-111111111111',
@@ -88,6 +90,10 @@ vi.mock('@/lib/telemetry', () => ({
track: mockTrack
}))
vi.mock('@/lib/agent-background-session-timeout-toast', () => ({
showAutomationPromptNotSentToast: mockShowAutomationPromptNotSentToast
}))
import {
ensureAgentStartupInTerminal,
getSetupConfig,
@@ -298,6 +304,65 @@ describe('ensureAgentStartupInTerminal prompt delivery', () => {
expect(mockTrack).not.toHaveBeenCalledWith('agent_prompt_sent', expect.anything())
})
it('surfaces the not-sent toast when a follow-up prompt is dropped', async () => {
// Foreground never becomes a recognized agent and there is no live child,
// so the readiness wait times out and the prompt is not delivered.
mockInspectRuntimeTerminalProcess.mockResolvedValue({
foregroundProcess: 'zsh',
hasChildProcesses: false
})
await ensureAgentStartupInTerminal({
worktreeId: 'wt-1',
startup: {
agent: 'aider',
launchCommand: 'aider',
expectedProcess: 'aider',
followupPrompt: 'fix the spinner',
launchConfig: { agentArgs: '', agentEnv: {} }
}
})
expect(mockSendRuntimePtyInputVerified).not.toHaveBeenCalled()
expect(mockShowAutomationPromptNotSentToast).toHaveBeenCalledWith('aider')
})
it('does not toast when a follow-up prompt is delivered', async () => {
await ensureAgentStartupInTerminal({
worktreeId: 'wt-1',
startup: {
agent: 'aider',
launchCommand: 'aider',
expectedProcess: 'aider',
followupPrompt: 'fix the spinner',
launchConfig: { agentArgs: '', agentEnv: {} }
}
})
expect(mockShowAutomationPromptNotSentToast).not.toHaveBeenCalled()
})
it('passes an onTimeout that surfaces the not-sent toast to the draft paste path', async () => {
await ensureAgentStartupInTerminal({
worktreeId: 'wt-1',
startup: {
agent: 'claude',
launchCommand: 'claude',
expectedProcess: 'claude',
followupPrompt: null,
launchConfig: { agentArgs: '', agentEnv: {} },
draftPrompt: 'review this before sending'
}
})
const call = mockPasteDraftToAgentPtyWhenReady.mock.calls.at(-1)?.[0] as
| { onTimeout?: () => void }
| undefined
expect(call?.onTimeout).toBeTypeOf('function')
call?.onTimeout?.()
expect(mockShowAutomationPromptNotSentToast).toHaveBeenCalledWith('claude')
})
it('does not track when follow-up prompt delivery rejects', async () => {
mockSendRuntimePtyInputVerified.mockRejectedValue(new Error('runtime timeout'))
@@ -335,7 +400,8 @@ describe('ensureAgentStartupInTerminal prompt delivery', () => {
ptyId: 'pty-1',
content: 'review this before sending',
agent: 'claude',
forcePaste: true
forcePaste: true,
onTimeout: expect.any(Function)
})
expect(mockTrack).not.toHaveBeenCalledWith('agent_prompt_sent', expect.anything())
})
@@ -378,7 +444,8 @@ describe('ensureAgentStartupInTerminal prompt delivery', () => {
ptyId: 'agent-pty',
content: 'Linear context draft',
agent: 'codex',
forcePaste: true
forcePaste: true,
onTimeout: expect.any(Function)
})
})
@@ -434,7 +501,8 @@ describe('ensureAgentStartupInTerminal prompt delivery', () => {
ptyId: 'pty-delayed',
content: 'https://github.com/stablyai/orca/pull/2051',
agent: 'codex',
forcePaste: true
forcePaste: true,
onTimeout: expect.any(Function)
})
})
@@ -515,7 +583,8 @@ describe('ensureAgentStartupInTerminal prompt delivery', () => {
ptyId: 'startup-pty',
content: 'linked draft',
agent: 'codex',
forcePaste: true
forcePaste: true,
onTimeout: expect.any(Function)
})
})
+10 -2
View File
@@ -4,6 +4,7 @@ import {
pasteDraftToAgentPtyWhenReady
} from '@/lib/agent-paste-draft'
import { sendFollowupPromptWhenAgentReady } from '@/lib/agent-followup-delivery'
import { showAutomationPromptNotSentToast } from '@/lib/agent-background-session-timeout-toast'
import type { AgentStartupPlan } from '@/lib/tui-agent-startup'
import type { LinkedWorkItemContext } from '@/lib/linked-work-item-context'
import {
@@ -302,12 +303,17 @@ async function deliverAgentStartupToTerminal(
// (aider, goose, etc.) that need their initial prompt typed into the live
// session and submitted. Wait until the agent owns the PTY before writing.
if (startup.followupPrompt) {
await sendFollowupPromptWhenAgentReady({
const delivered = await sendFollowupPromptWhenAgentReady({
ptyId,
expectedProcess: startup.expectedProcess,
prompt: startup.followupPrompt,
settings: runtimeSettings
})
// Why: a dropped follow-up is otherwise silent — surface the same toast the
// draft path uses so the user knows to open the workspace and paste it.
if (!delivered) {
showAutomationPromptNotSentToast(startup.agent)
}
}
// Why: draftPrompt uses bracketed-paste so the URL lands atomically in the
@@ -321,7 +327,9 @@ async function deliverAgentStartupToTerminal(
agent: startup.agent,
// Why: startup.draftPrompt is only attached after native draft launch
// planning is unavailable, so this paste is the first delivery attempt.
forcePaste: true
forcePaste: true,
// Why: surface a dropped draft instead of silently losing it.
onTimeout: () => showAutomationPromptNotSentToast(startup.agent)
})
}
}