Add compaction sidecar kill switch

This commit is contained in:
Robin Newhouse
2026-06-26 10:05:06 -07:00
parent 9288570e7c
commit d40334a757
21 changed files with 398 additions and 31 deletions
@@ -327,6 +327,73 @@ describe("createInteractiveSessionRuntime", () => {
expect(runtime.getActiveSessionId()).toBe(sessionId);
});
it("manual compact skips sidecar writes when the sidecar flag is off", async () => {
const sessionId = "sess-active-sidecar-off";
const messages = [
{ id: "u1", role: "user" as const, content: "hello" },
{ id: "a1", role: "assistant" as const, content: "world" },
];
const compactionState = createSessionCompactionState({
sourceMessages: messages,
compactedMessages: [
{ id: "summary", role: "user" as const, content: "summary" },
],
updatedAt: "2026-01-01T00:00:00.000Z",
});
const manager = {
start: vi.fn().mockResolvedValue({
sessionId,
manifest: createManifest(sessionId),
manifestPath: "/tmp/session.json",
messagesPath: "/tmp/session.messages.json",
}),
readMessages: vi.fn().mockResolvedValue(messages),
updateSessionCompactionState: vi.fn(),
stop: vi.fn().mockResolvedValue(undefined),
dispose: vi.fn().mockResolvedValue(undefined),
ingestHookEvent: vi.fn().mockResolvedValue(undefined),
get: vi.fn(),
list: vi.fn(),
delete: vi.fn(),
send: vi.fn(),
getAccumulatedUsage: vi.fn(),
};
createCliCoreMock.mockResolvedValue(manager);
compactInteractiveMessagesMock.mockResolvedValue({
compacted: true,
canonicalMessages: messages,
compactionState,
});
const { createInteractiveSessionRuntime } = await importRuntime();
const runtime = createInteractiveSessionRuntime({
config: createConfig(),
providerSettingsManager: createProviderSettingsManager(),
chatCommandState: createChatCommandState(),
requestToolApproval: vi.fn(),
resolveToolPolicy: () => ({ autoApprove: true }),
askQuestionRef: { current: null },
resolveMistakeLimitDecision: undefined,
switchToActModeTool: {} as never,
onAgentEvent: vi.fn(),
onTeamEvent: vi.fn(),
onPendingPrompts: vi.fn(),
onPendingPromptSubmitted: vi.fn(),
isCompactionSidecarEnabled: () => false,
});
await runtime.ensureReady();
const result = await runtime.compactCurrentSession();
expect(result).toEqual({
messagesBefore: messages.length,
messagesAfter: messages.length,
workingContextMessagesAfter: compactionState.messages.length,
compacted: true,
});
expect(manager.updateSessionCompactionState).not.toHaveBeenCalled();
expect(runtime.getActiveSessionId()).toBe(sessionId);
});
it("rejects manual compact while the active session is running", async () => {
const sessionId = "sess-running";
const messages = [{ role: "user" as const, content: "hello" }];
@@ -3,6 +3,7 @@ import {
type AgentHooks,
type CheckpointEntry,
createSessionCompactionState,
createSessionCompactionSidecarEnabledResolver,
isSessionNotFoundError,
type PendingPromptMutationResult,
type ProviderSettingsManager,
@@ -96,8 +97,12 @@ export function createInteractiveSessionRuntime(input: {
onTeamEvent: (event: TeamEvent) => void;
onPendingPrompts: (event: PendingPromptSnapshot) => void;
onPendingPromptSubmitted: (event: PendingPromptSubmittedEvent) => void;
isCompactionSidecarEnabled?: () => boolean;
}) {
let sessionManager: CliCore | undefined;
const isCompactionSidecarEnabled =
input.isCompactionSidecarEnabled ??
createSessionCompactionSidecarEnabledResolver();
let runtimeHooks: RuntimeHooks | undefined;
let unsubscribeAgent = () => {};
let unsubscribePendingPrompts = () => {};
@@ -210,7 +215,9 @@ export function createInteractiveSessionRuntime(input: {
toolPolicies: input.config.toolPolicies,
interactive: true,
initialMessages: initial,
...(initialCompactionState ? { initialCompactionState } : {}),
...(isCompactionSidecarEnabled() && initialCompactionState
? { initialCompactionState }
: {}),
...(sessionMetadata ? { sessionMetadata } : {}),
localRuntime: {
onTeamRestored: () => {},
@@ -291,6 +298,9 @@ export function createInteractiveSessionRuntime(input: {
const readCompactionState = async (
sessionId: string,
): Promise<SessionCompactionState | undefined> => {
if (!isCompactionSidecarEnabled()) {
return undefined;
}
const manager = sessionManager;
if (!manager) {
return undefined;
@@ -624,6 +634,14 @@ export function createInteractiveSessionRuntime(input: {
compacted: false,
};
}
if (!isCompactionSidecarEnabled()) {
return {
messagesBefore,
messagesAfter: result.canonicalMessages.length,
workingContextMessagesAfter: result.compactionState.messages.length,
compacted: true,
};
}
const updated = await manager.updateSessionCompactionState(
sourceSessionId,
result.compactionState,
+4
View File
@@ -1,5 +1,6 @@
import {
getCurrentContextSize,
createSessionCompactionSidecarEnabledResolver,
type ProviderSettings,
ProviderSettingsManager,
type UserInstructionConfigService,
@@ -25,6 +26,7 @@ import { disableOpenTuiGraphicsProbe } from "../tui/opentui-env";
import type { QueuedPromptItem } from "../tui/types";
import { type ChatCommandState, chatCommandHost } from "../utils/chat-commands";
import { applyCliCompactionMode } from "../utils/compaction-mode";
import { getCliFeatureFlagsService } from "../utils/feature-flags";
import {
shouldZeroClineFreeModelCost,
zeroCliAgentEventCost,
@@ -201,6 +203,8 @@ export async function runInteractive(
onPendingPromptSubmitted: (event) => {
uiEvents.emit("pending-prompt-submitted", event);
},
isCompactionSidecarEnabled:
createSessionCompactionSidecarEnabledResolver(getCliFeatureFlagsService()),
});
let modeChangePromise: Promise<void> | undefined;
let modeChangeTarget: "plan" | "act" | undefined;