Skip to content

Commit fe55dbe

Browse files
committed
fix(opencode): process prompts queued during loop shutdown
1 parent ae8de7a commit fe55dbe

2 files changed

Lines changed: 60 additions & 0 deletions

File tree

‎packages/opencode/src/session/prompt.ts‎

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1231,6 +1231,10 @@ export const layer = Layer.effect(
12311231
}
12321232

12331233
if (input.noReply === true) return message
1234+
const result = yield* loop({ sessionID: input.sessionID })
1235+
if (result.info.id > message.info.id) return result
1236+
// The prompt may have joined a loop that was already exiting after its
1237+
// last message snapshot. Run once more so the saved message is consumed.
12341238
return yield* loop({ sessionID: input.sessionID })
12351239
})
12361240

‎packages/opencode/test/session/prompt.test.ts‎

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1226,6 +1226,62 @@ it.instance(
12261226
3_000,
12271227
)
12281228

1229+
it.instance(
1230+
"prompt submitted while an active run exits starts another loop pass",
1231+
() =>
1232+
Effect.gen(function* () {
1233+
const { llm } = yield* useServerConfig(providerCfg)
1234+
const prompt = yield* SessionPrompt.Service
1235+
const run = yield* SessionRunState.Service
1236+
const sessions = yield* Session.Service
1237+
const chat = yield* sessions.create({ title: "Pinned" })
1238+
yield* seed(chat.id, { finish: "stop" })
1239+
const stale = (yield* sessions.messages({ sessionID: chat.id })).findLast((msg) => msg.info.role === "assistant")
1240+
if (!stale) throw new Error("expected seeded assistant")
1241+
const started = yield* Deferred.make<void>()
1242+
const release = yield* Deferred.make<void>()
1243+
1244+
const active = yield* run
1245+
.ensureRunning(
1246+
chat.id,
1247+
Effect.succeed(stale),
1248+
Deferred.succeed(started, void 0).pipe(Effect.andThen(Deferred.await(release)), Effect.as(stale)),
1249+
)
1250+
.pipe(Effect.forkChild)
1251+
yield* Deferred.await(started)
1252+
yield* llm.text("second")
1253+
1254+
const id = MessageID.ascending()
1255+
const queued = yield* prompt
1256+
.prompt({
1257+
sessionID: chat.id,
1258+
messageID: id,
1259+
agent: "build",
1260+
model: ref,
1261+
parts: [{ type: "text", text: "second" }],
1262+
})
1263+
.pipe(Effect.forkChild)
1264+
yield* pollWithTimeout(
1265+
sessions
1266+
.messages({ sessionID: chat.id })
1267+
.pipe(Effect.map((msgs) => (msgs.some((msg) => msg.info.id === id) ? true : undefined))),
1268+
"timed out waiting for queued prompt to save",
1269+
)
1270+
1271+
yield* Deferred.succeed(release, void 0)
1272+
yield* Fiber.await(active)
1273+
const exit = yield* Fiber.await(queued)
1274+
expect(Exit.isSuccess(exit)).toBe(true)
1275+
if (!Exit.isSuccess(exit)) return
1276+
expect(exit.value.info.role).toBe("assistant")
1277+
if (exit.value.info.role !== "assistant") return
1278+
expect(exit.value.info.parentID).toBe(id)
1279+
expect(exit.value.parts.some((part) => part.type === "text" && part.text === "second")).toBe(true)
1280+
expect(yield* llm.calls).toBe(1)
1281+
}),
1282+
3_000,
1283+
)
1284+
12291285
it.instance(
12301286
"assertNotBusy fails with BusyError when loop running",
12311287
() =>

0 commit comments

Comments
 (0)