diff --git a/index.ts b/index.ts index bd79ae4..db319d0 100644 --- a/index.ts +++ b/index.ts @@ -78,6 +78,24 @@ async function resolveCompactModel( return { providerID: model.providerID, modelID: model.modelID } } +const HANDOFF_MARKER = "Work was restarted in this session with a clean context window." + +async function waitFor( + client: PluginClient, + sessionID: string, + test: (messages: { info: import("@opencode-ai/sdk").Message; parts: import("@opencode-ai/sdk").Part[] }[]) => boolean, + timeoutMs = 30000, + intervalMs = 250, +) { + const deadline = Date.now() + timeoutMs + while (true) { + const page = await client.session.messages({ path: { id: sessionID } }) + if (test(page.data ?? [])) return + if (Date.now() >= deadline) throw new Error(`timed out after ${timeoutMs}ms waiting for the session to change`) + await new Promise((resolve) => setTimeout(resolve, intervalMs)) + } +} + export const server: Plugin = async ({ client }, rawOptions = {}) => { const options: RestartPluginOptions = { ...DEFAULT_OPTIONS, @@ -111,24 +129,46 @@ export const server: Plugin = async ({ client }, rawOptions = {}) => { }, async execute(args, ctx) { const { providerID, modelID } = await resolveCompactModel(client, ctx.sessionID, args.model) - await client.session.summarize({ - path: { id: ctx.sessionID }, - body: { providerID, modelID }, - }) + + void client.session + .summarize({ + path: { id: ctx.sessionID }, + body: { providerID, modelID }, + }) + .catch(() => { + // The summarize request parks server-side until the running loop + // finishes; the compaction message it writes is what matters, and it + // is created synchronously before that wait. Errors here are + // surface-level (the loop below still processes the compaction). + }) + + await waitFor(client, ctx.sessionID, (messages) => + messages.some( + (entry) => + entry.info.role === "user" && + entry.parts.some((part) => part.type === "compaction"), + ), + ) const staticText = await resolveStaticText(client, ctx.agent, args.agent, options.static_text) - const parts: TextPartInput[] = [ - { type: "text", text: buildHandoff(args.task, args.notes, staticText) }, - ] + const handoff = buildHandoff(args.task, args.notes, staticText) await client.session.promptAsync({ path: { id: ctx.sessionID }, body: { - parts, + parts: [{ type: "text", text: handoff } as TextPartInput], ...(args.model ? { model: normalizeModel(args.model) } : {}), ...(args.agent ? { agent: args.agent } : {}), }, }) + await waitFor(client, ctx.sessionID, (messages) => + messages.some( + (entry) => + entry.info.role === "user" && + entry.parts.some((part) => part.type === "text" && part.text?.includes(HANDOFF_MARKER)), + ), + ) + return { title: "Session context cleared", output: