diff --git a/packages/opencode/src/session/compaction.ts b/packages/opencode/src/session/compaction.ts
index 4aa1ccdec65d..e2be3d4a88d1 100644
--- a/packages/opencode/src/session/compaction.ts
+++ b/packages/opencode/src/session/compaction.ts
@@ -49,6 +49,22 @@ type CompletedCompaction = {
summary: string | undefined
}
+const REQUEST_MAX_CHARS = 8_000
+
+const isRequestText = (part: SessionV1.Part): part is SessionV1.TextPart =>
+ part.type === "text" && !part.synthetic && !part.ignored
+
+// The verbatim request the turn is carrying out; the summary only paraphrases it.
+function currentRequest(messages: SessionV1.WithParts[]) {
+ const request = messages.findLast((m) => m.info.role === "user" && m.parts.some(isRequestText))
+ const text =
+ request?.parts
+ .filter(isRequestText)
+ .map((part) => part.text)
+ .join("\n") ?? ""
+ return text.length <= REQUEST_MAX_CHARS ? text : `${text.slice(0, REQUEST_MAX_CHARS)}\n[truncated]`
+}
+
function summaryText(message: SessionV1.WithParts) {
const text = message.parts
.filter((part): part is SessionV1.TextPart => part.type === "text")
@@ -480,11 +496,18 @@ const layer = Layer.effect(
agent: userMessage.agent,
model: userMessage.model,
})
- const text =
- (input.overflow
- ? "The previous request exceeded the provider's size limit due to large media attachments. The conversation was compacted and media files were removed from context. If the user was asking about attached images or files, explain that the attachments were too large to process and suggest they try again with smaller or fewer files.\n\n"
- : "") +
- "Continue if you have next steps, or stop and ask for clarification if you are unsure how to proceed."
+ const request = currentRequest(input.messages)
+ const text = [
+ input.overflow
+ ? "The previous request exceeded the provider's size limit due to large media attachments. The conversation was compacted and media files were removed from context. If the user was asking about attached images or files, explain that the attachments were too large to process and suggest they try again with smaller or fewer files."
+ : "",
+ request
+ ? `The request you were carrying out when the conversation was compacted:\n\n${request}\n`
+ : "",
+ "Continue the work from Next Move; if nothing remains, say so briefly. Do not restate the summary. Only stop to ask the user if you cannot proceed without their input.",
+ ]
+ .filter(Boolean)
+ .join("\n\n")
yield* session.updatePart({
id: PartID.ascending(),
messageID: continueMsg.id,
diff --git a/packages/opencode/test/session/compaction.test.ts b/packages/opencode/test/session/compaction.test.ts
index b1ce8e55d9ca..0b8652c7ed97 100644
--- a/packages/opencode/test/session/compaction.test.ts
+++ b/packages/opencode/test/session/compaction.test.ts
@@ -921,11 +921,29 @@ describe("session.compaction.process", () => {
metadata: { compaction_continue: true },
})
if (last?.parts[0]?.type === "text") {
- expect(last.parts[0].text).toContain("Continue if you have next steps")
+ expect(last.parts[0].text).toContain("\nhello\n")
+ expect(last.parts[0].text).toContain("Continue the work from Next Move")
}
}),
)
+ it.instance(
+ "quotes the original request, not a prior continue prompt, on a second compaction",
+ Effect.gen(function* () {
+ const ssn = yield* SessionNs.Service
+ const session = yield* ssn.create({})
+ const msg = yield* createUserMessage(session.id, "hello")
+ const first = yield* ssn.messages({ sessionID: session.id })
+ yield* SessionCompaction.use.process({ parentID: msg.id, messages: first, sessionID: session.id, auto: true })
+
+ const second = yield* ssn.messages({ sessionID: session.id })
+ yield* SessionCompaction.use.process({ parentID: msg.id, messages: second, sessionID: session.id, auto: true })
+
+ const last = (yield* ssn.messages({ sessionID: session.id })).at(-1)
+ expect(last?.parts[0]?.type === "text" && last.parts[0].text).toContain("\nhello\n")
+ }),
+ )
+
itCompaction.instance(
"persists tail_start_id for retained recent turns",
Effect.gen(function* () {
@@ -1118,7 +1136,7 @@ describe("session.compaction.process", () => {
(msg) =>
msg.info.role === "user" &&
msg.parts.some(
- (part) => part.type === "text" && part.synthetic && part.text.includes("Continue if you have next steps"),
+ (part) => part.type === "text" && part.synthetic && part.metadata?.compaction_continue === true,
),
),
).toBe(false)