|
|
@@ -1362,10 +1362,10 @@ describe("session.compaction.process", () => {
|
|
|
"summarizes only the head while keeping recent tail out of summary input",
|
|
|
() => {
|
|
|
const stub = llm()
|
|
|
- let captured = ""
|
|
|
+ let messages: LLM.StreamInput["messages"] = []
|
|
|
stub.push(
|
|
|
reply("summary", (input) => {
|
|
|
- captured = JSON.stringify(input.messages)
|
|
|
+ messages = input.messages
|
|
|
}),
|
|
|
)
|
|
|
return Effect.gen(function* () {
|
|
|
@@ -1386,7 +1386,10 @@ describe("session.compaction.process", () => {
|
|
|
auto: false,
|
|
|
})
|
|
|
|
|
|
- expect(captured).toContain("older context")
|
|
|
+ const captured = JSON.stringify(messages)
|
|
|
+ expect(messages).toHaveLength(1)
|
|
|
+ expect(messages[0]?.role).toBe("user")
|
|
|
+ expect(captured).toContain("[User]: older context")
|
|
|
expect(captured).not.toContain("keep this turn")
|
|
|
expect(captured).not.toContain("and this one too")
|
|
|
expect(captured).not.toContain("What did we do so far?")
|
|
|
@@ -1437,6 +1440,74 @@ describe("session.compaction.process", () => {
|
|
|
{ git: true },
|
|
|
)
|
|
|
|
|
|
+ itCompaction.instance(
|
|
|
+ "serializes repeated compaction history as one user message",
|
|
|
+ () => {
|
|
|
+ const stub = llm()
|
|
|
+ let captured: LLM.StreamInput["messages"] = []
|
|
|
+ stub.push(
|
|
|
+ reply("summary two", (input) => {
|
|
|
+ captured = input.messages
|
|
|
+ }),
|
|
|
+ )
|
|
|
+
|
|
|
+ return Effect.gen(function* () {
|
|
|
+ const ssn = yield* SessionNs.Service
|
|
|
+ const test = yield* TestInstance
|
|
|
+ const session = yield* ssn.create({})
|
|
|
+ const turn = yield* createUserMessage(session.id, "original request")
|
|
|
+ const kept = yield* createAssistantMessage(session.id, turn.id, test.directory)
|
|
|
+ yield* ssn.updatePart({
|
|
|
+ id: PartID.ascending(),
|
|
|
+ messageID: kept.id,
|
|
|
+ sessionID: session.id,
|
|
|
+ type: "tool",
|
|
|
+ callID: "read-call",
|
|
|
+ tool: "read",
|
|
|
+ state: {
|
|
|
+ status: "completed",
|
|
|
+ input: { filePath: "src/index.ts" },
|
|
|
+ output: "file contents",
|
|
|
+ title: "src/index.ts",
|
|
|
+ metadata: {},
|
|
|
+ time: { start: Date.now(), end: Date.now() },
|
|
|
+ },
|
|
|
+ })
|
|
|
+
|
|
|
+ const previous = yield* ssn.updateMessage({
|
|
|
+ id: MessageID.ascending(),
|
|
|
+ role: "user",
|
|
|
+ model: ref,
|
|
|
+ sessionID: session.id,
|
|
|
+ agent: "build",
|
|
|
+ time: { created: Date.now() },
|
|
|
+ })
|
|
|
+ yield* ssn.updatePart({
|
|
|
+ id: PartID.ascending(),
|
|
|
+ messageID: previous.id,
|
|
|
+ sessionID: session.id,
|
|
|
+ type: "compaction",
|
|
|
+ auto: false,
|
|
|
+ tail_start_id: kept.id,
|
|
|
+ })
|
|
|
+ yield* createSummaryAssistantMessage(session.id, previous.id, test.directory, "summary one")
|
|
|
+ yield* createCompactionMarker(session.id)
|
|
|
+
|
|
|
+ const msgs = MessageV2.filterCompacted(yield* MessageV2.stream(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).toHaveLength(1)
|
|
|
+ expect(captured[0]?.role).toBe("user")
|
|
|
+ expect(JSON.stringify(captured)).toContain('[Assistant tool call]: read({\\"filePath\\":\\"src/index.ts\\"})')
|
|
|
+ expect(JSON.stringify(captured)).toContain("[Tool result]: file contents")
|
|
|
+ expect(JSON.stringify(captured)).not.toContain('\\"role\\":\\"assistant\\"')
|
|
|
+ }).pipe(withCompaction({ llm: stub.llmLayer, config: cfg({ tail_turns: 0 }) }))
|
|
|
+ },
|
|
|
+ { git: true },
|
|
|
+ )
|
|
|
+
|
|
|
itCompaction.instance("keeps recent pre-compaction turns across repeated compactions", () => {
|
|
|
const stub = llm()
|
|
|
stub.push(reply("summary one"))
|