diff --git a/packages/coding-agent/CHANGELOG.md b/packages/coding-agent/CHANGELOG.md index 506082af..66052fe4 100644 --- a/packages/coding-agent/CHANGELOG.md +++ b/packages/coding-agent/CHANGELOG.md @@ -8,6 +8,7 @@ ### Fixed +- Fixed `/handoff` to use compacted session context instead of pre-compaction raw messages ([#3945](https://github.com/badlogic/pi-mono/issues/3945)). - Fixed idle follow-up submission to clear the editor like normal message submission ([#3926](https://github.com/badlogic/pi-mono/issues/3926)). - Updated the sandbox extension example lockfile to resolve the vulnerable `lodash-es` transitive dependency ([#3901](https://github.com/badlogic/pi-mono/issues/3901)). diff --git a/packages/coding-agent/examples/extensions/handoff.ts b/packages/coding-agent/examples/extensions/handoff.ts index 0f897aef..05f3c995 100644 --- a/packages/coding-agent/examples/extensions/handoff.ts +++ b/packages/coding-agent/examples/extensions/handoff.ts @@ -12,6 +12,7 @@ * The generated prompt appears as a draft in the editor for review/editing. */ +import type { AgentMessage } from "@mariozechner/pi-agent-core"; import { complete, type Message } from "@mariozechner/pi-ai"; import type { ExtensionAPI, SessionEntry } from "@mariozechner/pi-coding-agent"; import { BorderedLoader, convertToLlm, serializeConversation } from "@mariozechner/pi-coding-agent"; @@ -38,6 +39,44 @@ Files involved: ## Task [Clear description of what to do next based on user's goal]`; +function entryToMessage(entry: SessionEntry): AgentMessage | undefined { + if (entry.type === "message") { + return entry.message; + } + if (entry.type === "compaction") { + return { + role: "compactionSummary", + summary: entry.summary, + tokensBefore: entry.tokensBefore, + timestamp: new Date(entry.timestamp).getTime(), + }; + } + return undefined; +} + +function getHandoffMessages(branch: SessionEntry[]): AgentMessage[] { + let compactionIndex = -1; + for (let i = branch.length - 1; i >= 0; i--) { + if (branch[i].type === "compaction") { + compactionIndex = i; + break; + } + } + if (compactionIndex < 0) { + return branch.map(entryToMessage).filter((message) => message !== undefined); + } + + const compaction = branch[compactionIndex]; + const firstKeptIndex = + compaction.type === "compaction" ? branch.findIndex((entry) => entry.id === compaction.firstKeptEntryId) : -1; + const compactedBranch = [ + compaction, + ...(firstKeptIndex >= 0 ? branch.slice(firstKeptIndex, compactionIndex) : []), + ...branch.slice(compactionIndex + 1), + ]; + return compactedBranch.map(entryToMessage).filter((message) => message !== undefined); +} + export default function (pi: ExtensionAPI) { pi.registerCommand("handoff", { description: "Transfer context to a new focused session", @@ -58,11 +97,9 @@ export default function (pi: ExtensionAPI) { return; } - // Gather conversation context from current branch - const branch = ctx.sessionManager.getBranch(); - const messages = branch - .filter((entry): entry is SessionEntry & { type: "message" } => entry.type === "message") - .map((entry) => entry.message); + // Gather conversation context from current branch. If the branch was compacted, + // include the compaction summary plus entries from firstKeptEntryId onward. + const messages = getHandoffMessages(ctx.sessionManager.getBranch()); if (messages.length === 0) { ctx.ui.notify("No conversation to hand off", "error");