diff --git a/CHANGELOG.md b/CHANGELOG.md
index 6181d59a7b9..2d1bd8165e1 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -99,7 +99,7 @@ Docs: https://docs.openclaw.ai
- Models/fallback: resolve bare fallback model provider ids before model switching, so configured fallback chains keep working when a fallback is named without an explicit provider prefix. Thanks @steipete.
- Voice-call/Telnyx: preserve inbound/outbound callback metadata and read transcription text from Telnyx's current `transcription_data` payload. Thanks @steipete.
- Providers/DeepSeek: wire V4 thinking controls and OpenAI-compatible replay policy so follow-up turns preserve DeepSeek `reasoning_content`, while the None/off thinking path strips replayed reasoning fields. Fixes #70931. Thanks @lsdsjy.
-- Providers/GitHub Copilot: align Copilot request headers across Anthropic and Responses transports, including tool-result and image follow-up turns, without enabling unverified Responses continuation. Thanks @steipete.
+- Providers/GitHub Copilot: align Copilot request headers across Anthropic, Responses, and built-in compaction summarization paths, including tool-result and image follow-up turns, without enabling unverified Responses continuation. Thanks @steipete.
- Codex harness: send verbose tool progress to chat channels for native app-server runs, matching the Pi harness `/verbose on` and `/verbose full` behavior. (#70966) Thanks @jalehman.
- Codex models: fetch paginated Codex app-server model catalogs, mark truncated `/codex models` output, and keep ChatGPT OAuth defaults on the `openai-codex/gpt-5.5` route instead of the OpenAI API-key route. Thanks @steipete.
- Codex status: report Codex CLI OAuth as `oauth (codex-cli)` for native `codex/*` sessions instead of showing unknown auth. Fixes #70688. Thanks @jb510.
diff --git a/docs/providers/github-copilot.md b/docs/providers/github-copilot.md
index 33f95c3b57b..67c46df4ff9 100644
--- a/docs/providers/github-copilot.md
+++ b/docs/providers/github-copilot.md
@@ -92,9 +92,9 @@ openclaw models auth login --provider github-copilot --method device --set-defau
OpenClaw sends Copilot IDE-style request headers on Copilot transports,
- including tool-result and image follow-up turns. It does not enable
- provider-level Responses continuation for Copilot unless that behavior has
- been verified against Copilot's API.
+ including built-in compaction, tool-result, and image follow-up turns. It
+ does not enable provider-level Responses continuation for Copilot unless
+ that behavior has been verified against Copilot's API.
diff --git a/src/agents/pi-hooks/compaction-safeguard.test.ts b/src/agents/pi-hooks/compaction-safeguard.test.ts
index b1f4af27d8a..1f65bd3ca07 100644
--- a/src/agents/pi-hooks/compaction-safeguard.test.ts
+++ b/src/agents/pi-hooks/compaction-safeguard.test.ts
@@ -1286,6 +1286,52 @@ describe("compaction-safeguard recent-turn preservation", () => {
expect(droppedCall?.customInstructions).toContain("Keep security caveats.");
});
+ it("adds Copilot IDE headers to built-in compaction summarization", async () => {
+ mockSummarizeInStages.mockReset();
+ mockSummarizeInStages.mockResolvedValue("mock summary");
+
+ const sessionManager = stubSessionManager();
+ const model = createAnthropicModelFixture({
+ id: "gpt-5.4",
+ name: "gpt-5.4",
+ provider: "github-copilot",
+ api: "openai-responses" as const,
+ baseUrl: "https://api.githubcopilot.com",
+ });
+ setCompactionSafeguardRuntime(sessionManager, { model, recentTurnsPreserve: 0 });
+
+ const getApiKeyAndHeadersMock = vi.fn().mockResolvedValue({
+ ok: true,
+ apiKey: "github-token",
+ headers: { "X-Test": "1" },
+ });
+ const mockContext = createCompactionContext({
+ sessionManager,
+ getApiKeyAndHeadersMock,
+ });
+ const compactionHandler = createCompactionHandler();
+ const event = createCompactionEvent({
+ messageText: "summarize me",
+ tokensBefore: 1000,
+ });
+ (event.preparation as { settings?: { reserveTokens: number } }).settings = {
+ reserveTokens: 4000,
+ };
+
+ const result = (await compactionHandler(event, mockContext)) as { cancel?: boolean };
+
+ expect(result.cancel).not.toBe(true);
+ const summaryCall = mockSummarizeInStages.mock.calls.at(-1)?.[0];
+ expect(summaryCall?.headers).toMatchObject({
+ "Copilot-Integration-Id": "vscode-chat",
+ "Editor-Plugin-Version": "copilot-chat/0.35.0",
+ "Openai-Organization": "github-copilot",
+ "User-Agent": "GitHubCopilotChat/0.26.7",
+ "X-Test": "1",
+ "x-initiator": "user",
+ });
+ });
+
it("does not retry summaries unless quality guard is explicitly enabled", async () => {
mockSummarizeInStages.mockReset();
mockSummarizeInStages.mockResolvedValue("summary missing headings");
diff --git a/src/agents/pi-hooks/compaction-safeguard.ts b/src/agents/pi-hooks/compaction-safeguard.ts
index 2e3287ecea8..bbeac04f8e8 100644
--- a/src/agents/pi-hooks/compaction-safeguard.ts
+++ b/src/agents/pi-hooks/compaction-safeguard.ts
@@ -28,6 +28,7 @@ import {
summarizeInStages,
} from "../compaction.js";
import { collectTextContentBlocks } from "../content-blocks.js";
+import { buildCopilotDynamicHeaders, hasCopilotVisionInput } from "../copilot-dynamic-headers.js";
import { isTimeoutError } from "../failover-error.js";
import { repairToolUseResultPairing } from "../session-transcript-repair.js";
import { extractToolCallsFromAssistant, extractToolResultId } from "../tool-call-id.js";
@@ -236,6 +237,26 @@ async function resolveModelAuth(
return { ok: true, apiKey: requestAuth.apiKey, headers: requestAuth.headers };
}
+function buildCompactionSummaryHeaders(params: {
+ model: NonNullable;
+ messages: AgentMessage[];
+ headers?: Record;
+}): Record | undefined {
+ if (params.model.provider !== "github-copilot") {
+ return params.headers;
+ }
+ const messages = params.messages as unknown as Parameters<
+ typeof buildCopilotDynamicHeaders
+ >[0]["messages"];
+ return {
+ ...buildCopilotDynamicHeaders({
+ messages,
+ hasImages: hasCopilotVisionInput(messages),
+ }),
+ ...params.headers,
+ };
+}
+
function clampNonNegativeInt(value: unknown, fallback: number): number {
const normalized = typeof value === "number" && Number.isFinite(value) ? value : fallback;
return Math.max(0, Math.floor(normalized));
@@ -880,12 +901,17 @@ export default function compactionSafeguardExtension(api: ExtensionAPI): void {
return { cancel: true };
}
const apiKey = authResult.apiKey ?? "";
- const headers = authResult.headers;
+ const authHeaders = authResult.headers;
try {
const modelContextWindow = resolveContextWindowTokens(model);
const contextWindowTokens = runtime?.contextWindowTokens ?? modelContextWindow;
let messagesToSummarize = preparation.messagesToSummarize;
+ const headers = buildCompactionSummaryHeaders({
+ model,
+ messages: messagesToSummarize,
+ headers: authHeaders,
+ });
const qualityGuardEnabled = runtime?.qualityGuardEnabled ?? false;
const qualityGuardMaxRetries = resolveQualityGuardMaxRetries(runtime?.qualityGuardMaxRetries);