Skip to content

Commit fd1bf0b

Browse files
authored
🤖 fix: use correct tool policy for continue message after compaction (#1270)
When a `/compact` command includes a continue message, the continue message was being queued with the compaction mode's tool policy (all tools disabled) instead of the intended execution mode's tool policy. ## Root Cause The bug existed since the feature was first implemented in PR #650 (Nov 21). The code spread compaction options into the queued message, which included the disabled-all tool policy from compact mode. ## Fix Add `mode` field to `ContinueMessage` type so the backend can derive the correct tool policy via `modeToToolPolicy()` instead of inheriting from compaction options. ## Changes - `src/common/types/message.ts`: Add `mode` field to `ContinueMessage` - `src/browser/utils/chatCommands.ts`: Set mode when building continueMessage - `src/browser/hooks/useResumeManager.ts`: Preserve mode during resume - `src/node/services/agentSession.ts`: Use `modeToToolPolicy(continueMessage.mode)` - `src/node/services/agentSession.continueMessageToolPolicy.test.ts`: Regression test --- _Generated with `mux` • Model: `anthropic:claude-opus-4-5` • Thinking: `high`_
1 parent f267209 commit fd1bf0b

File tree

5 files changed

+88
-2
lines changed

5 files changed

+88
-2
lines changed

‎src/browser/hooks/useResumeManager.ts‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -180,6 +180,7 @@ export function useResumeManager() {
180180
text: lastUserMsg.compactionRequest.parsed.continueMessage?.text ?? "",
181181
imageParts: lastUserMsg.compactionRequest.parsed.continueMessage?.imageParts,
182182
model: lastUserMsg.compactionRequest.parsed.continueMessage?.model ?? options.model,
183+
mode: lastUserMsg.compactionRequest.parsed.continueMessage?.mode ?? "exec",
183184
},
184185
});
185186
}

‎src/browser/utils/chatCommands.ts‎

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -655,6 +655,9 @@ export function prepareCompactionMessage(options: CompactionOptions): {
655655
// Create compaction metadata (will be stored in user message)
656656
// Only include continueMessage if there's text, images, or reviews to queue after compaction
657657
const hasText = continueText;
658+
// Determine mode for continue message - use mode from sendMessageOptions if it's exec/plan, otherwise default to exec
659+
const continueMode = options.sendMessageOptions.mode === "plan" ? "plan" : "exec";
660+
658661
const compactData: CompactionRequestData = {
659662
model: effectiveModel,
660663
maxOutputTokens: options.maxOutputTokens,
@@ -664,6 +667,7 @@ export function prepareCompactionMessage(options: CompactionOptions): {
664667
text: options.continueMessage?.text ?? "",
665668
imageParts: options.continueMessage?.imageParts,
666669
model: options.continueMessage?.model ?? options.sendMessageOptions.model,
670+
mode: continueMode,
667671
reviews: options.continueMessage?.reviews,
668672
}
669673
: undefined,

‎src/common/types/message.ts‎

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,10 +26,12 @@ export interface UserMessageContent {
2626

2727
/**
2828
* Message to continue with after compaction.
29-
* Extends UserMessageContent with model preference.
29+
* Extends UserMessageContent with model and mode preferences.
3030
*/
3131
export interface ContinueMessage extends UserMessageContent {
3232
model?: string;
33+
/** Mode for the continue message (determines tool policy). Defaults to 'exec'. */
34+
mode?: "exec" | "plan";
3335
}
3436

3537
// Parsed compaction request data (shared type for consistency)
Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
import { describe, expect, test } from "bun:test";
2+
import { modeToToolPolicy } from "@/common/utils/ui/modeUtils";
3+
4+
/**
5+
* Regression test for continue message tool policy bug.
6+
*
7+
* Bug: When a /compact command includes a continue message, the continue message
8+
* was being queued with the compaction mode's tool policy (all tools disabled)
9+
* instead of the intended execution mode's tool policy.
10+
*
11+
* Fix: Continue message now carries its own mode field, and the backend uses
12+
* modeToToolPolicy(continueMessage.mode) instead of copying options.toolPolicy.
13+
*
14+
* This test verifies that the mode-to-policy transformation produces the expected
15+
* tool policies for continue messages. The actual integration of this logic into
16+
* agentSession.ts is verified by the type system and manual testing.
17+
*/
18+
describe("Continue message tool policy derivation", () => {
19+
test("exec mode enables tools (not disabled-all like compaction)", () => {
20+
const execPolicy = modeToToolPolicy("exec");
21+
const compactionPolicy = [{ regex_match: ".*", action: "disable" }];
22+
23+
// Exec mode should NOT disable all tools like compaction does
24+
expect(execPolicy).not.toEqual(compactionPolicy);
25+
26+
// Exec mode specifically disables propose_plan (the plan mode tool)
27+
expect(execPolicy).toEqual([{ regex_match: "propose_plan", action: "disable" }]);
28+
});
29+
30+
test("plan mode has different policy than compaction", () => {
31+
const planPolicy = modeToToolPolicy("plan");
32+
const compactionPolicy = [{ regex_match: ".*", action: "disable" }];
33+
34+
// Plan mode should NOT disable all tools like compaction does
35+
expect(planPolicy).not.toEqual(compactionPolicy);
36+
37+
// Plan mode enables propose_plan
38+
expect(planPolicy).toEqual([{ regex_match: "propose_plan", action: "enable" }]);
39+
});
40+
41+
test("verifies fix: mode field determines policy, not inherited compaction policy", () => {
42+
// This test documents the fix behavior:
43+
// Before fix: continueMessage used options.toolPolicy (compaction's disabled-all)
44+
// After fix: continueMessage.mode determines the policy via modeToToolPolicy()
45+
46+
// Simulating the fixed logic from agentSession.ts:
47+
// const continueMode = continueMessage.mode ?? "exec";
48+
// toolPolicy: modeToToolPolicy(continueMode)
49+
50+
const simulateContinueMessagePolicy = (mode?: "exec" | "plan") => {
51+
const continueMode = mode ?? "exec"; // Default to exec as in the fix
52+
return modeToToolPolicy(continueMode);
53+
};
54+
55+
// Explicit exec mode
56+
expect(simulateContinueMessagePolicy("exec")).toEqual([
57+
{ regex_match: "propose_plan", action: "disable" },
58+
]);
59+
60+
// Explicit plan mode
61+
expect(simulateContinueMessagePolicy("plan")).toEqual([
62+
{ regex_match: "propose_plan", action: "enable" },
63+
]);
64+
65+
// No mode specified (defaults to exec)
66+
expect(simulateContinueMessagePolicy(undefined)).toEqual([
67+
{ regex_match: "propose_plan", action: "disable" },
68+
]);
69+
70+
// None of these should be the compaction policy
71+
const compactionPolicy = [{ regex_match: ".*", action: "disable" }];
72+
expect(simulateContinueMessagePolicy("exec")).not.toEqual(compactionPolicy);
73+
expect(simulateContinueMessagePolicy("plan")).not.toEqual(compactionPolicy);
74+
expect(simulateContinueMessagePolicy(undefined)).not.toEqual(compactionPolicy);
75+
});
76+
});

‎src/node/services/agentSession.ts‎

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@ import type { PostCompactionAttachment, PostCompactionExclusions } from "@/commo
3939
import { TURNS_BETWEEN_ATTACHMENTS } from "@/common/constants/attachments";
4040
import { extractEditedFileDiffs } from "@/common/utils/messages/extractEditedFiles";
4141
import { isValidModelFormat } from "@/common/utils/ai/models";
42+
import { modeToToolPolicy } from "@/common/utils/ui/modeUtils";
4243

4344
/**
4445
* Tracked file state for detecting external edits.
@@ -525,13 +526,15 @@ export class AgentSession {
525526
const { finalText, metadata } = prepareUserMessageForSend(continueMessage);
526527

527528
// Build options for the queued message (strip compaction-specific fields)
529+
// Use the mode from continueMessage to derive correct tool policy, not the compaction mode's disabled-all policy
530+
const continueMode = continueMessage.mode ?? "exec";
528531
const sanitizedOptions: Omit<
529532
SendMessageOptions,
530533
"muxMetadata" | "mode" | "editMessageId" | "imageParts" | "maxOutputTokens"
531534
> & { imageParts?: typeof continueMessage.imageParts; muxMetadata?: typeof metadata } = {
532535
model: continueMessage.model ?? options.model,
533536
thinkingLevel: options.thinkingLevel,
534-
toolPolicy: options.toolPolicy,
537+
toolPolicy: modeToToolPolicy(continueMode),
535538
additionalSystemInstructions: options.additionalSystemInstructions,
536539
providerOptions: options.providerOptions,
537540
experiments: options.experiments,

0 commit comments

Comments
 (0)