fix(background-agent): fire promptAsync before tmux callback
Awaiting the tmux callback blocked the prompt for up to 10s (waitForSessionReady downstream). During that window the spawned pane ran 'opencode attach' against an empty session and rendered a blank TUI. Users saw 'pane created but attach not working'. Start promptWithModelSuggestionRetry immediately after session.create, then invoke the tmux callback as fire-and-forget. The session becomes active before the attach client connects. Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent) Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
This commit is contained in:
@@ -577,3 +577,84 @@ describe("background-agent spawner fallback model promotion", () => {
|
||||
expect(promptCalls[0]?.body?.agent).toBe("sisyphus-junior")
|
||||
})
|
||||
})
|
||||
|
||||
describe("background-agent spawner tmux callback ordering", () => {
|
||||
test("fires promptAsync before tmux callback resolves (no blocking)", async () => {
|
||||
//#given
|
||||
const events: string[] = []
|
||||
let resolveTmuxCallback: () => void = () => {}
|
||||
const tmuxCallbackPromise = new Promise<void>((resolve) => {
|
||||
resolveTmuxCallback = resolve
|
||||
})
|
||||
|
||||
const client = {
|
||||
session: {
|
||||
get: async () => ({ data: { directory: "/tmp/test" } }),
|
||||
create: async () => {
|
||||
events.push("session.create")
|
||||
return { data: { id: "ses_blocking_tmux" } }
|
||||
},
|
||||
promptAsync: async () => {
|
||||
events.push("promptAsync")
|
||||
return { data: {} }
|
||||
},
|
||||
},
|
||||
} as any
|
||||
|
||||
const onSubagentSessionCreated = mock(async () => {
|
||||
events.push("tmux.callback.start")
|
||||
await tmuxCallbackPromise
|
||||
events.push("tmux.callback.end")
|
||||
})
|
||||
|
||||
const task = createTask({
|
||||
description: "Blocking tmux test",
|
||||
prompt: "Do work",
|
||||
agent: "general",
|
||||
parentSessionID: "ses_parent",
|
||||
parentMessageID: "msg_parent",
|
||||
})
|
||||
|
||||
const item = {
|
||||
task,
|
||||
input: {
|
||||
description: task.description,
|
||||
prompt: task.prompt,
|
||||
agent: task.agent,
|
||||
parentSessionID: task.parentSessionID,
|
||||
parentMessageID: task.parentMessageID,
|
||||
},
|
||||
}
|
||||
|
||||
const ctx = {
|
||||
client,
|
||||
directory: "/tmp/test",
|
||||
concurrencyManager: { release: () => {} },
|
||||
tmuxEnabled: true,
|
||||
onSubagentSessionCreated,
|
||||
onTaskError: () => {},
|
||||
}
|
||||
|
||||
const originalTmux = process.env.TMUX
|
||||
process.env.TMUX = "/tmp/fake-tmux-socket"
|
||||
|
||||
try {
|
||||
//#when
|
||||
await startTask(item as any, ctx as any)
|
||||
await new Promise((resolve) => setTimeout(resolve, 20))
|
||||
|
||||
//#then
|
||||
expect(events).toContain("session.create")
|
||||
expect(events).toContain("promptAsync")
|
||||
expect(events).toContain("tmux.callback.start")
|
||||
const promptIdx = events.indexOf("promptAsync")
|
||||
const tmuxStartIdx = events.indexOf("tmux.callback.start")
|
||||
expect(promptIdx < tmuxStartIdx).toBe(true)
|
||||
expect(events).not.toContain("tmux.callback.end")
|
||||
} finally {
|
||||
resolveTmuxCallback()
|
||||
if (originalTmux === undefined) delete process.env.TMUX
|
||||
else process.env.TMUX = originalTmux
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user