mirror of
https://github.com/agentclientprotocol/claude-agent-acp.git
synced 2026-11-09 19:02:05 +00:00
fix: replay a compaction summary by its flag, not its framing (#1536)
Fixes #1535 A compaction summary on replay was only sent as a `compaction_update` if its text also parsed, so a change in Claude Code's framing turned it into a `user_message_chunk`, shown as text the user typed. That is what happens to summaries from Claude Code 2.1.312 and 2.1.316. `isCompactSummary` now decides on its own. A summary whose framing isn't recognized is sent as a completed `compaction_update` without summary text. This reverses the choice to keep such text visible: the model-facing framing isn't kept as a summary either way, and showing it as the user's words is the worse failure. `PERSISTED_PREAMBLE` also allows content before the `Summary:` line, so current summaries keep their text. All 31 summaries in my own transcripts now parse, and a real `session/load` on one from 2.1.316 sends a `compaction_update` with its text and no summary `user_message_chunk`.
This commit is contained in:
@@ -10051,9 +10051,10 @@ export class ClaudeAcpAgent {
|
||||
// The entity is keyed by the summary message's uuid, which differs from
|
||||
// the live id (the `compacting` status uuid) — replay is terminal-first
|
||||
// by the RFD, and no client merges a replay into a live entity store.
|
||||
// A subagent's own compaction summary is not the root session's, and a
|
||||
// summary whose framing is unrecognized stays visible as transcript
|
||||
// text rather than vanishing behind a summary-less entity.
|
||||
// A subagent's own compaction summary is not the root session's. A summary
|
||||
// whose framing is unrecognized is still this compaction, without summary
|
||||
// text: isCompactSummary says what it is, and the framing is Claude Code's
|
||||
// to change.
|
||||
if (
|
||||
replayCompactionUpdates &&
|
||||
isCompactSummaryMessage(message) &&
|
||||
@@ -10069,10 +10070,9 @@ export class ClaudeAcpAgent {
|
||||
airClient: this.toolCallCapabilities.air.client,
|
||||
},
|
||||
);
|
||||
if (replayCompaction.recordSummary(assistantMessageText(message.message))) {
|
||||
await replayCompaction.finish(message.uuid, "completed");
|
||||
return;
|
||||
}
|
||||
replayCompaction.recordSummary(assistantMessageText(message.message));
|
||||
await replayCompaction.finish(message.uuid, "completed");
|
||||
return;
|
||||
}
|
||||
|
||||
for (const notification of toAcpNotifications(
|
||||
|
||||
@@ -67,7 +67,7 @@ const ANALYSIS_BLOCKS = [
|
||||
* it as the post-compaction user message. */
|
||||
const CONTINUATION_PREAMBLE = /^\s*This session is being continued from a previous conversation/i;
|
||||
const PERSISTED_PREAMBLE =
|
||||
/^\s*This session is being continued from a previous conversation[^\n]*\n+Summary:[ \t]*\r?\n/;
|
||||
/^\s*This session is being continued from a previous conversation[^\n]*\n(?:[\s\S]*?\n)?Summary:[ \t]*\r?\n/;
|
||||
const PERSISTED_TRAILERS = [
|
||||
/\n+If you need specific details from before compaction[\s\S]*$/,
|
||||
/\n+(?:Please )?[Cc]ontinue the conversation from where it left (?:it )?off[\s\S]*$/,
|
||||
|
||||
@@ -18610,8 +18610,8 @@ describe("assembled assistant text fallback", () => {
|
||||
]);
|
||||
expect(child.some((update) => update.sessionUpdate === "compaction_update")).toBe(false);
|
||||
|
||||
// Unrecognized framing: the text stays visible rather than vanishing
|
||||
// behind a summary-less entity.
|
||||
// Unrecognized framing: still the compaction isCompactSummary says it is,
|
||||
// without summary text, and never a message from the user.
|
||||
const unknownFraming = await replay(true, [
|
||||
{
|
||||
...summaryMessage("odd-summary"),
|
||||
@@ -18622,12 +18622,14 @@ describe("assembled assistant text fallback", () => {
|
||||
},
|
||||
},
|
||||
]);
|
||||
expect(unknownFraming.some((update) => update.sessionUpdate === "compaction_update")).toBe(
|
||||
false,
|
||||
);
|
||||
expect(unknownFraming.some((update) => update.sessionUpdate === "user_message_chunk")).toBe(
|
||||
true,
|
||||
);
|
||||
expect(unknownFraming).toEqual([
|
||||
{
|
||||
sessionUpdate: "compaction_update",
|
||||
compactionId: "odd-summary",
|
||||
status: "completed",
|
||||
_meta: { jetbrains: { air: { version: 1, contextCompaction: { version: 1 } } } },
|
||||
},
|
||||
]);
|
||||
});
|
||||
|
||||
it("preserves the model response after multiple compactions in one turn", async () => {
|
||||
|
||||
@@ -130,6 +130,17 @@ describe("compactionSummaryText", () => {
|
||||
);
|
||||
});
|
||||
|
||||
it("strips the framing when other content sits before the Summary line", () => {
|
||||
expect(
|
||||
compactionSummaryText(
|
||||
"This session is being continued from a previous conversation that ran out of context. The summary below covers the earlier portion of the conversation.\n\n" +
|
||||
"<system-reminder>\n<tools-used-before-summary>Read, Bash</tools-used-before-summary>\n</system-reminder>\n" +
|
||||
"Summary:\n1. Primary Request and Intent:\n Count upward.\n\n" +
|
||||
"Continue the conversation from where it left off without asking the user any further questions.",
|
||||
),
|
||||
).toBe("1. Primary Request and Intent:\n Count upward.");
|
||||
});
|
||||
|
||||
it("strips the older tagged transcript framing", () => {
|
||||
expect(
|
||||
compactionSummaryText(
|
||||
|
||||
Reference in New Issue
Block a user