mirror of
https://github.com/stablyai/orca.git
synced 2026-10-17 00:03:08 +00:00
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:
@@ -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)
|
||||
})
|
||||
})
|
||||
|
||||
|
||||
@@ -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)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user