fix: avoid deadlock by not awaiting summarize
The summarize request parks server-side on the current run's done until the loop executing this tool finishes, so awaiting it never resolves. Fire it without awaiting, poll for the persisted compaction message, then wait for the handoff prompt to appear before returning.
This commit is contained in:
parent
f991c4d0f8
commit
2cac9375d8
1 changed files with 48 additions and 8 deletions
50
index.ts
50
index.ts
|
|
@ -78,6 +78,24 @@ async function resolveCompactModel(
|
||||||
return { providerID: model.providerID, modelID: model.modelID }
|
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 = {}) => {
|
export const server: Plugin = async ({ client }, rawOptions = {}) => {
|
||||||
const options: RestartPluginOptions = {
|
const options: RestartPluginOptions = {
|
||||||
...DEFAULT_OPTIONS,
|
...DEFAULT_OPTIONS,
|
||||||
|
|
@ -111,24 +129,46 @@ export const server: Plugin = async ({ client }, rawOptions = {}) => {
|
||||||
},
|
},
|
||||||
async execute(args, ctx) {
|
async execute(args, ctx) {
|
||||||
const { providerID, modelID } = await resolveCompactModel(client, ctx.sessionID, args.model)
|
const { providerID, modelID } = await resolveCompactModel(client, ctx.sessionID, args.model)
|
||||||
await client.session.summarize({
|
|
||||||
|
void client.session
|
||||||
|
.summarize({
|
||||||
path: { id: ctx.sessionID },
|
path: { id: ctx.sessionID },
|
||||||
body: { providerID, modelID },
|
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 staticText = await resolveStaticText(client, ctx.agent, args.agent, options.static_text)
|
||||||
const parts: TextPartInput[] = [
|
const handoff = buildHandoff(args.task, args.notes, staticText)
|
||||||
{ type: "text", text: buildHandoff(args.task, args.notes, staticText) },
|
|
||||||
]
|
|
||||||
await client.session.promptAsync({
|
await client.session.promptAsync({
|
||||||
path: { id: ctx.sessionID },
|
path: { id: ctx.sessionID },
|
||||||
body: {
|
body: {
|
||||||
parts,
|
parts: [{ type: "text", text: handoff } as TextPartInput],
|
||||||
...(args.model ? { model: normalizeModel(args.model) } : {}),
|
...(args.model ? { model: normalizeModel(args.model) } : {}),
|
||||||
...(args.agent ? { agent: args.agent } : {}),
|
...(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 {
|
return {
|
||||||
title: "Session context cleared",
|
title: "Session context cleared",
|
||||||
output:
|
output:
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue