Skip to content

Commit b3ddab5

Browse files
committed
fix: use correct tool policy for continue message after compaction
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. 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. Files changed: - 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)
1 parent f267209 commit b3ddab5

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)