From daffe51c752f0d6db6bf62560adeeb3b7879244e Mon Sep 17 00:00:00 2001 From: Aiden Cline Date: Wed, 12 Aug 2026 11:12:41 -0500 Subject: [PATCH] fix: plugin hook logic --- packages/opencode/src/session/compaction.ts | 13 +++-- .../opencode/test/session/compaction.test.ts | 57 +++++++++++++++++++ 2 files changed, 66 insertions(+), 4 deletions(-) diff --git a/packages/opencode/src/session/compaction.ts b/packages/opencode/src/session/compaction.ts index 8fc64b9bc42..eee3057dc55 100644 --- a/packages/opencode/src/session/compaction.ts +++ b/packages/opencode/src/session/compaction.ts @@ -386,10 +386,15 @@ const layer = Layer.effect( const conversation = msgs.map(serialize).filter(Boolean).join("\n\n") const nextPrompt = compacting.prompt ?? - buildPrompt({ - previousSummary, - context: [...compacting.context, conversation], - }) + [ + buildPrompt({ + previousSummary, + context: [conversation], + }), + ...compacting.context, + ] + .filter(Boolean) + .join("\n\n") const ctx = yield* InstanceState.context const msg: SessionV1.Assistant = { id: MessageID.ascending(), diff --git a/packages/opencode/test/session/compaction.test.ts b/packages/opencode/test/session/compaction.test.ts index 561a9318777..43985c3f162 100644 --- a/packages/opencode/test/session/compaction.test.ts +++ b/packages/opencode/test/session/compaction.test.ts @@ -365,6 +365,20 @@ function autocontinue(enabled: boolean) { }) } +function compactionContext(context: string) { + return Layer.mock(Plugin.Service)({ + trigger: (name: Name, _input: Input, output: Output) => { + if (name !== "experimental.session.compacting") return Effect.succeed(output) + return Effect.sync(() => { + ;(output as { context: string[] }).context.push(context) + return output + }) + }, + list: () => Effect.succeed([]), + init: () => Effect.void, + }) +} + describe("session.compaction.isOverflow", () => { it.live( "returns true when token count exceeds usable context", @@ -1447,6 +1461,49 @@ describe("session.compaction.process", () => { { git: true }, ) + itCompaction.instance( + "keeps plugin context outside the serialized conversation", + () => { + const stub = llm() + let captured = "" + stub.push( + reply("summary", (input) => { + captured = JSON.stringify(input.messages) + }), + ) + + return Effect.gen(function* () { + const ssn = yield* SessionNs.Service + const session = yield* ssn.create({}) + yield* createUserMessage(session.id, "older context") + yield* createUserMessage(session.id, "keep this turn") + yield* createUserMessage(session.id, "and this one too") + yield* createCompactionMarker(session.id) + + const msgs = yield* ssn.messages({ sessionID: session.id }) + const parent = msgs.at(-1)?.info.id + expect(parent).toBeTruthy() + yield* SessionCompaction.use.process({ + parentID: parent!, + messages: msgs, + sessionID: session.id, + auto: false, + }) + + expect(captured).toContain("Prioritize unresolved migration details") + expect(captured.indexOf("")).toBeLessThan( + captured.indexOf("Prioritize unresolved migration details"), + ) + }).pipe( + withCompaction({ + llm: stub.llmLayer, + plugin: compactionContext("Prioritize unresolved migration details"), + }), + ) + }, + { git: true }, + ) + itCompaction.instance( "serializes repeated compaction history as one user message", () => {