diff --git a/packages/opencode/src/session/prompt.ts b/packages/opencode/src/session/prompt.ts index 0d3d25feb8de..f46d3db8211d 100644 --- a/packages/opencode/src/session/prompt.ts +++ b/packages/opencode/src/session/prompt.ts @@ -622,6 +622,25 @@ export namespace SessionPrompt { continue } SessionCompaction.prune({ sessionID }) + + // Allow plugins to optionally resume this session before it completes + const sessionInfo = await Session.get(sessionID) + const beforeIdleOutput = { resumePrompt: undefined as string | undefined } + await Plugin.trigger("session.before.idle", { sessionID, parentSessionID: sessionInfo?.parentID }, beforeIdleOutput) + + if (beforeIdleOutput.resumePrompt) { + log.info("resuming session", { sessionID }) + delete state()[sessionID] + const resumeAgent = await lastAgent(sessionID) + const resumeModel = await lastModel(sessionID) + await prompt({ + sessionID, + agent: resumeAgent, + model: resumeModel, + parts: [{ type: "text", text: beforeIdleOutput.resumePrompt }], + }) + } + for await (const item of MessageV2.stream(sessionID)) { if (item.info.role === "user") continue const queued = state()[sessionID]?.callbacks ?? [] @@ -640,6 +659,13 @@ export namespace SessionPrompt { return Provider.defaultModel() } + async function lastAgent(sessionID: string) { + for await (const item of MessageV2.stream(sessionID)) { + if (item.info.role === "user" && item.info.agent) return item.info.agent + } + return Agent.defaultAgent() + } + async function resolveTools(input: { agent: Agent.Info model: Provider.Model diff --git a/packages/plugin/src/index.ts b/packages/plugin/src/index.ts index e57eff579e63..ca3f960ff55e 100644 --- a/packages/plugin/src/index.ts +++ b/packages/plugin/src/index.ts @@ -215,4 +215,9 @@ export interface Hooks { input: { sessionID: string; messageID: string; partID: string }, output: { text: string }, ) => Promise + + "session.before.idle"?: ( + input: { sessionID: string; parentSessionID?: string }, + output: { resumePrompt?: string }, + ) => Promise }