diff --git a/packages/opencode/src/server/routes/session.ts b/packages/opencode/src/server/routes/session.ts index 3850376bdb40..82e6f3121bf7 100644 --- a/packages/opencode/src/server/routes/session.ts +++ b/packages/opencode/src/server/routes/session.ts @@ -539,7 +539,7 @@ export const SessionRoutes = lazy(() => }, auto: body.auto, }) - await SessionPrompt.loop(sessionID) + await SessionPrompt.loop({ sessionID }) return c.json(true) }, ) diff --git a/packages/opencode/src/session/prompt.ts b/packages/opencode/src/session/prompt.ts index 98dce97ba90d..28fbe8fad359 100644 --- a/packages/opencode/src/session/prompt.ts +++ b/packages/opencode/src/session/prompt.ts @@ -177,7 +177,7 @@ export namespace SessionPrompt { return message } - return loop(input.sessionID) + return loop({sessionID: input.sessionID}) }) export async function resolvePromptParts(template: string): Promise { @@ -247,6 +247,13 @@ export namespace SessionPrompt { return controller.signal } + function resume(sessionID: string) { + const s = state() + if (!s[sessionID]) return + + return s[sessionID].abort.signal + } + export function cancel(sessionID: string) { log.info("cancel", { sessionID }) const s = state() @@ -264,8 +271,14 @@ export namespace SessionPrompt { return } - export const loop = fn(Identifier.schema("session"), async (sessionID) => { - const abort = start(sessionID) + export const LoopInput = z.object({ + sessionID: Identifier.schema("session"), + resume_existing: z.boolean().optional(), + }) + export const loop = fn(LoopInput, async (input) => { + const { sessionID, resume_existing } = input + + const abort = resume_existing ? resume(sessionID) : start(sessionID) if (!abort) { return new Promise((resolve, reject) => { const callbacks = state()[sessionID].callbacks @@ -1385,7 +1398,19 @@ NOTE: At any point in time through this workflow you should feel free to ask the if (!abort) { throw new Session.BusyError(input.sessionID) } - using _ = defer(() => cancel(input.sessionID)) + + using _ = defer(() => { + // If no queued callbacks, cancel (the default) + const callbacks = state()[input.sessionID]?.callbacks ?? [] + if (callbacks.length === 0) { + cancel(input.sessionID) + } else { + // Otherwise, trigger the session loop to process queued items + loop({sessionID: input.sessionID, resume_existing: true}).catch((error) => { + log.error("session loop failed to resume after shell command", { sessionID: input.sessionID, error }) + }) + } + }) const session = await Session.get(input.sessionID) if (session.revert) {