Compare commits

...
Author SHA1 Message Date
Saoud Rizwan 49373322be fix: use agentic compaction by default 2026-06-27 09:59:15 -07:00
28 changed files with 141 additions and 168 deletions
+1 -1
View File
@@ -257,7 +257,7 @@ Schedules can route results back to chat surfaces with `--delivery-adapter`, `--
| `--hooks-dir <path>` | Additional hooks directory hint for runtime hook injection |
| `--acp` | ACP (Agent Client Protocol) mode |
| `--thinking [none\|low\|medium\|high\|xhigh]` | Model thinking level when supported. Defaults to `medium` when the flag is provided without a level; thinking is off when the flag is omitted. |
| `--compaction <agentic\|basic\|off>` | Context compaction mode. Defaults to `basic`; use `agentic` for LLM compaction or `off` to disable. |
| `--compaction <agentic\|basic\|off>` | Context compaction mode. Defaults to `agentic`; use `basic` for truncation compaction or `off` to disable. |
| `--retries <count>` | Maximum consecutive mistakes (retries) before halting (default: `3`) |
| `--json` | Output NDJSON instead of styled text |
| `--data-dir <path>` | Use isolated local state at `<path>` instead of `~/.cline` (enables sandbox mode automatically) |
+3 -3
View File
@@ -1183,7 +1183,7 @@ describe("runCli lightweight command dispatch", () => {
expect.objectContaining({
compaction: {
enabled: true,
strategy: "basic",
strategy: "agentic",
},
thinking: true,
reasoningEffort: "medium",
@@ -1270,7 +1270,7 @@ describe("runCli lightweight command dispatch", () => {
);
});
it("enables truncation compaction by default for prompt runs", async () => {
it("enables agentic compaction by default for prompt runs", async () => {
mockState.runAgentCalls = 0;
runtimeMocks.runAgent.mockClear();
@@ -1285,7 +1285,7 @@ describe("runCli lightweight command dispatch", () => {
expect.objectContaining({
compaction: {
enabled: true,
strategy: "basic",
strategy: "agentic",
},
}),
expect.anything(),
@@ -166,25 +166,25 @@ describe("compactInteractiveMessages", () => {
role: index % 2 === 0 ? ("user" as const) : ("assistant" as const),
content: `message ${index} ${longText}`,
}));
const config = createConfig();
const compact = vi.fn((context: CoreCompactionContext) => {
expect(context.maxInputTokens).toBe(64_000);
expect(context.triggerTokens).toBeGreaterThan(1_000);
expect(context.triggerTokens).toBeLessThan(context.maxInputTokens);
return { messages: messages.slice(0, 2) };
});
config.compaction = { compact };
const result = await compactInteractiveMessages({
config: createConfig(),
config,
providerSettingsManager: createProviderSettingsManager(),
sessionId: "sess-compact",
messages,
});
const compactedTextLength = result.messages.reduce(
(total, message) =>
total +
(typeof message.content === "string" ? message.content.length : 0),
0,
);
expect(compact).toHaveBeenCalledTimes(1);
expect(result.compacted).toBe(true);
expect(result.messages.length).toBeGreaterThan(1);
expect(result.messages.length).toBeLessThan(messages.length);
expect(compactedTextLength).toBeGreaterThan(1_000);
expect(result.messages).toEqual(messages.slice(0, 2));
});
it("reports compaction when core returns changed messages with the same count", async () => {
@@ -70,6 +70,7 @@ export async function compactInteractiveMessages(input: {
compaction: {
...input.config.compaction,
enabled: true,
strategy: "agentic",
},
logger: input.config.logger,
// Forward telemetry + sessionId so manual compactions emit
+10 -2
View File
@@ -11,6 +11,15 @@ function getDisplayRole(msg: PersistedMessage): string | undefined {
return typeof role === "string" ? role.trim().toLowerCase() : undefined;
}
function shouldHydrateMessage(msg: PersistedMessage): boolean {
const displayRole = getDisplayRole(msg);
return (
displayRole !== "system" &&
displayRole !== "status" &&
msg.metadata?.kind !== "compaction_summary"
);
}
function stringifyToolResult(
content: string | Array<{ type: string; text?: string; path?: string }>,
): string {
@@ -32,8 +41,7 @@ export function hydrateSessionMessages(messages: Message[]): ChatEntry[] {
const toolUseMap = new Map<string, number>();
for (const msg of messages as PersistedMessage[]) {
const displayRole = getDisplayRole(msg);
if (displayRole === "system" || displayRole === "status") {
if (!shouldHydrateMessage(msg)) {
continue;
}
+10 -12
View File
@@ -15,14 +15,12 @@ function createConfig(compaction?: Config["compaction"]): Config {
}
describe("CLI compaction mode helpers", () => {
it("defaults enabled compaction to basic truncation", () => {
expect(DEFAULT_CLI_COMPACTION_MODE).toBe("basic");
it("defaults enabled compaction to agentic summarization", () => {
expect(DEFAULT_CLI_COMPACTION_MODE).toBe("agentic");
expect(getCliCompactionMode(createConfig())).toBe(
DEFAULT_CLI_COMPACTION_MODE,
);
expect(formatCliCompactionMode(DEFAULT_CLI_COMPACTION_MODE)).toBe(
"Truncation",
);
expect(formatCliCompactionMode(DEFAULT_CLI_COMPACTION_MODE)).toBe("LLM");
});
it("maps basic and off modes to core compaction config", () => {
@@ -46,13 +44,13 @@ describe("CLI compaction mode helpers", () => {
it("builds default and explicit core compaction config", () => {
expect(buildCliCompactionConfig()).toEqual({
enabled: true,
strategy: "basic",
});
expect(buildCliCompactionConfig("agentic")).toEqual({
enabled: true,
strategy: "agentic",
});
expect(buildCliCompactionConfig("basic")).toEqual({
enabled: true,
strategy: "basic",
});
expect(buildCliCompactionConfig("off")).toEqual({ enabled: false });
});
@@ -79,8 +77,8 @@ describe("CLI compaction mode helpers", () => {
});
it("cycles TUI choices in a stable order", () => {
expect(getNextCliCompactionMode("basic")).toBe("agentic");
expect(getNextCliCompactionMode("agentic")).toBe("off");
expect(getNextCliCompactionMode("off")).toBe("basic");
expect(getNextCliCompactionMode("agentic")).toBe("basic");
expect(getNextCliCompactionMode("basic")).toBe("off");
expect(getNextCliCompactionMode("off")).toBe("agentic");
});
});
+5 -5
View File
@@ -1,11 +1,11 @@
import type { CliCompactionMode, Config } from "./types";
export const CLI_COMPACTION_MODES = ["basic", "agentic", "off"] as const;
export const CLI_COMPACTION_MODES = ["agentic", "basic", "off"] as const;
export const DEFAULT_CLI_COMPACTION_MODE: Extract<
CliCompactionMode,
"agentic" | "basic"
> = "basic";
> = "agentic";
const CLI_COMPACTION_MODE_ALIASES: Record<string, CliCompactionMode> = {
agentic: "agentic",
@@ -20,7 +20,7 @@ const CLI_COMPACTION_MODE_LABELS = {
} as const satisfies Record<CliCompactionMode, string>;
export const CLI_COMPACTION_MODE_OPTION_DESCRIPTION =
"Context compaction mode: agentic|basic|off (default: basic)";
"Context compaction mode: agentic|basic|off (default: agentic)";
export const CLI_COMPACTION_MODE_EXPECTED_TEXT = '"agentic", "basic", or "off"';
@@ -43,8 +43,8 @@ export function getCliCompactionMode(config: Config): CliCompactionMode {
if (config.compaction?.enabled === false) {
return "off";
}
return config.compaction?.strategy === "agentic"
? "agentic"
return config.compaction?.strategy === "basic"
? "basic"
: DEFAULT_CLI_COMPACTION_MODE;
}
+13 -1
View File
@@ -1,6 +1,6 @@
import type { AgentEvent, TeamEvent } from "@cline/core";
import { beforeEach, describe, expect, it, vi } from "vitest";
import { handleEvent, handleTeamEvent } from "./events";
import { handleEvent, handleTeamEvent, resolveStatusNoticeLabel } from "./events";
import { setCurrentOutputMode } from "./output";
import type { Config } from "./types";
@@ -160,6 +160,18 @@ describe("handleEvent text formatting", () => {
expect(output).toContain("── aborted (2 iterations) ──");
});
it("uses stable copy for compaction status notices", () => {
expect(
resolveStatusNoticeLabel({
type: "notice",
noticeType: "status",
displayRole: "status",
reason: "auto_compaction",
message: "Summarizing context...",
} as AgentEvent),
).toBe("Compacting context...");
});
it("suppresses heartbeat-only team progress messages", () => {
handleTeamEvent({
type: "run_progress",
+1 -1
View File
@@ -28,7 +28,7 @@ export function resolveStatusNoticeLabel(
return undefined;
}
if (event.reason === "auto_compaction") {
return "auto-compacting";
return "Compacting context...";
}
return event.message.trim() || undefined;
}
@@ -34,7 +34,7 @@ const mocks = vi.hoisted(() => {
apiKey: "test-key",
})),
getGlobalSettingsKey: vi.fn((key: string): boolean | undefined => {
if (key === "subagentsEnabled" || key === "useAutoCondense") {
if (key === "subagentsEnabled") {
return false
}
return undefined
@@ -81,7 +81,7 @@ beforeEach(() => {
apiKey: "test-key",
})
mocks.stateManager.getGlobalSettingsKey.mockImplementation((key: string) => {
if (key === "subagentsEnabled" || key === "useAutoCondense") {
if (key === "subagentsEnabled") {
return false
}
return undefined
@@ -632,36 +632,19 @@ describe("buildSessionConfig", () => {
expect(config.providerConfig).not.toHaveProperty("apiKey")
})
it("enables basic SDK compaction when global useAutoCondense is true", async () => {
mocks.stateManager.getGlobalSettingsKey.mockImplementation((key: string) => {
if (key === "useAutoCondense") {
return true
}
if (key === "subagentsEnabled") {
return false
}
return undefined
})
it("enables agentic SDK compaction by default", async () => {
const config = await buildSessionConfig({ cwd: "/tmp/workspace" })
expect(config.compaction).toEqual({
enabled: true,
strategy: "basic",
strategy: "agentic",
})
})
it("does not enable SDK compaction when global useAutoCondense is false", async () => {
const config = await buildSessionConfig({ cwd: "/tmp/workspace" })
expect(config.compaction).toBeUndefined()
})
it("lets task useAutoCondense override the global setting", async () => {
let globalUseAutoCondense = true
it("ignores legacy useAutoCondense global and task settings", async () => {
mocks.stateManager.getGlobalSettingsKey.mockImplementation((key: string) => {
if (key === "useAutoCondense") {
return globalUseAutoCondense
return false
}
if (key === "subagentsEnabled") {
return false
@@ -669,23 +652,14 @@ describe("buildSessionConfig", () => {
return undefined
})
// Task `false` overrides global `true`.
const disabledConfig = await buildSessionConfig({
const config = await buildSessionConfig({
cwd: "/tmp/workspace",
taskSettings: { useAutoCondense: false },
})
// Task `true` overrides global `false`.
globalUseAutoCondense = false
const enabledConfig = await buildSessionConfig({
cwd: "/tmp/workspace",
taskSettings: { useAutoCondense: true },
})
expect(disabledConfig.compaction).toBeUndefined()
expect(enabledConfig.compaction).toEqual({
expect(config.compaction).toEqual({
enabled: true,
strategy: "basic",
strategy: "agentic",
})
})
})
+4 -10
View File
@@ -652,9 +652,7 @@ export async function buildSessionConfig(input: SessionConfigInput): Promise<Cor
}
const stateManager = StateManager.get()
const globalUseAutoCondense = stateManager.getGlobalSettingsKey("useAutoCondense") ?? false
const enableCheckpoints = stateManager.getGlobalSettingsKey("enableCheckpointsSetting") ?? true
const useAutoCondense = input.taskSettings?.useAutoCondense ?? globalUseAutoCondense
// Core resolves providers against the SDK registry, which uses the SDK's
// own provider id spelling (e.g. "openai-compatible" rather than the
@@ -693,14 +691,10 @@ export async function buildSessionConfig(input: SessionConfigInput): Promise<Cor
},
enableSpawnAgent: false,
enableAgentTeams: false,
...(useAutoCondense
? {
compaction: {
enabled: true,
strategy: "basic",
},
}
: {}),
compaction: {
enabled: true,
strategy: "agentic",
},
disableMcpSettingsTools: true,
mode: mode === "plan" ? "plan" : "act",
...reasoningConfig,
@@ -1639,6 +1639,7 @@ export function translateSessionEvent(event: CoreSessionEvent, state: MessageTra
type SdkContentBlock = Exclude<SdkMessage["content"], string>[number]
type SdkToolUseBlock = Extract<SdkContentBlock, { type: "tool_use" }>
type SdkMessageWithMetrics = SdkMessage & {
metadata?: Record<string, unknown>
metrics?: {
inputTokens?: number
outputTokens?: number
@@ -1834,6 +1835,7 @@ export function sdkMessagesToClineMessages(messages: SdkMessageWithMetrics[], mi
say: clineMessages.length === 0 ? "task" : "user_feedback",
text,
partial: false,
metadata: message.metadata,
})
}
continue
@@ -1847,6 +1849,7 @@ export function sdkMessagesToClineMessages(messages: SdkMessageWithMetrics[], mi
say: clineMessages.length === 0 ? "task" : "user_feedback",
text: userText,
partial: false,
metadata: message.metadata,
})
}
+1
View File
@@ -65,6 +65,7 @@ export async function compactSessionMessages(input: CompactSessionMessagesInput)
compaction: {
...input.config.compaction,
enabled: true,
strategy: "agentic",
},
logger: input.config.logger,
// Forward telemetry + sessionId so manual compactions emit
+35 -2
View File
@@ -1,4 +1,5 @@
import type { SessionHistoryRecord } from "@cline/core"
import type { MessageWithMetadata } from "@cline/shared"
import type { HistoryItem } from "@shared/HistoryItem"
import getFolderSize from "get-folder-size"
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest"
@@ -137,6 +138,33 @@ describe("SdkTaskHistory", () => {
])
})
it("preserves internal compaction summary messages for UI filtering", async () => {
const { history } = makeHistory([makeSessionRecord("task-1")], undefined, undefined, [
{ role: "user", content: "Build the feature" },
{
role: "user",
content: "Context summary:\n\nThe user asked to build the feature.",
metadata: { kind: "compaction_summary" },
},
{ role: "assistant", content: [{ type: "text", text: "Done" }] },
])
const result = await history.getClineMessages("task-1")
expect(result).toMatchObject([
{ type: "say", say: "task", text: "Build the feature", partial: false },
{
type: "say",
say: "user_feedback",
text: "Context summary:\n\nThe user asked to build the feature.",
partial: false,
metadata: { kind: "compaction_summary" },
},
{ type: "say", say: "text", text: "Done", partial: false },
{ type: "ask", ask: "completion_result", partial: false },
])
})
it("includes persisted SDK message metrics for task header pricing", () => {
const result = sdkMessagesToClineMessages([
{ role: "user", content: "Build the feature" },
@@ -591,7 +619,12 @@ function makeTelemetry(): TelemetryService {
} as unknown as TelemetryService
}
function makeHistory(records: SessionHistoryRecord[], telemetry?: TelemetryService, legacyExtensionStorageDir?: string) {
function makeHistory(
records: SessionHistoryRecord[],
telemetry?: TelemetryService,
legacyExtensionStorageDir?: string,
messages: MessageWithMetadata[] = [],
) {
let currentRecords = records
const updateSession = vi.fn(
async (
@@ -620,7 +653,7 @@ function makeHistory(records: SessionHistoryRecord[], telemetry?: TelemetryServi
})
const getSession = vi.fn(async (sessionId: string) => currentRecords.find((record) => record.sessionId === sessionId))
const listHistory = vi.fn(async () => currentRecords)
const readMessages = vi.fn(async () => [])
const readMessages = vi.fn(async () => messages)
const startSession = vi.fn(async (input: { config: { sessionId?: string } }) => {
currentRecords = [makeSessionRecord(input.config.sessionId ?? "started"), ...currentRecords]
return { sessionId: input.config.sessionId }
+1 -1
View File
@@ -463,7 +463,7 @@ export class SdkTaskHistory {
async getClineMessages(taskId: string): Promise<ClineMessage[]> {
await this.migrateLegacyTaskIfNeeded(taskId)
const sdkMessages = await this.withHistoryHost((host) => host.readMessages(taskId) as Promise<SdkMessage[]>)
const sdkMessages = await this.withHistoryHost((host) => host.readMessages(taskId) as Promise<MessageWithMetadata[]>)
const clineMessages = sdkMessagesToClineMessages(
sanitizeSdkUserMessagesForDisplay(sdkMessages),
this.options.getMinter?.(),
@@ -106,25 +106,6 @@ describe("SdkTaskStartCoordinator", () => {
expect(options.postStateToWebview).toHaveBeenCalledOnce()
})
it.each([true, false])("forwards task useAutoCondense=%s into SDK session config inputs", async (useAutoCondense) => {
const { coordinator, options } = makeCoordinator()
const taskSettings = { useAutoCondense }
await coordinator.initTask("hello", undefined, undefined, undefined, taskSettings)
expect(options.sessionConfigBuilder.build).toHaveBeenCalledWith(
expect.objectContaining({
taskSettings,
}),
)
expect(options.buildStartSessionInput).toHaveBeenCalledWith(
expect.any(Object),
expect.objectContaining({
taskSettings,
}),
)
})
it("reinitializes an existing task with preserved initial messages", async () => {
const historyItem: HistoryItem = {
id: "task-1",
@@ -169,6 +169,7 @@ export interface ClineMessage {
say?: ClineSay
text?: string
reasoning?: string
metadata?: Record<string, unknown>
images?: string[]
files?: string[]
partial?: boolean
@@ -38,7 +38,6 @@ export const TaskSection: React.FC<TaskSectionProps> = ({
doesModelSupportPromptCache={selectedModelInfo.supportsPromptCache}
lastApiReqTotalTokens={lastApiReqTotalTokens}
onClose={messageHandlers.handleTaskCloseButtonClick}
onSendMessage={messageHandlers.handleSendMessage}
task={task}
tokensIn={apiMetrics.totalTokensIn}
tokensOut={apiMetrics.totalTokensOut}
@@ -52,6 +52,19 @@ const createAskMessage = (
})
describe("filterVisibleMessages", () => {
it("hides internal compaction summaries from chat rendering", () => {
const visibleMessage = createTaskMessage(1, "Build the feature")
const compactionSummary: ClineMessage = {
type: "say",
say: "user_feedback",
text: "Context summary:\n\nThe user asked to build the feature.",
metadata: { kind: "compaction_summary" },
ts: 2,
}
expect(filterVisibleMessages([visibleMessage, compactionSummary])).toEqual([visibleMessage])
})
it("hides exact user feedback echoes for selected follow-up options", () => {
const askMessage = createAskMessage(1, "followup", ["Use this", "Use that"], "Use this")
const visible = filterVisibleMessages([askMessage, createUserFeedbackMessage(2, "Use this")])
@@ -115,6 +115,10 @@ export function canRestoreWorkspaceFromMessage(messages: ClineMessage[], message
*/
export function filterVisibleMessages(messages: ClineMessage[]): ClineMessage[] {
return messages.filter((message, index, arr) => {
if (message.metadata?.kind === "compaction_summary") {
return false
}
if (isDuplicateAskOptionEcho(message, arr[index - 1])) {
return false
}
@@ -55,21 +55,11 @@ describe("ContextWindow compact button", () => {
})
it("runs the compact RPC after confirmation instead of sending /compact as a message", async () => {
const onSendMessage = vi.fn()
render(
<ContextWindow
contextWindow={200_000}
lastApiReqTotalTokens={120_000}
onSendMessage={onSendMessage}
useAutoCondense={false}
/>,
)
render(<ContextWindow contextWindow={200_000} lastApiReqTotalTokens={120_000} />)
fireEvent.click(screen.getByRole("button", { name: /compact task/i }))
fireEvent.click(screen.getByRole("button", { name: /yes/i }))
await waitFor(() => expect(condense).toHaveBeenCalledWith({ value: "compact" }))
expect(onSendMessage).not.toHaveBeenCalled()
})
})
@@ -19,10 +19,8 @@ interface ContextWindowInfoProps {
}
interface ContextWindowProgressProps extends ContextWindowInfoProps {
useAutoCondense: boolean
lastApiReqTotalTokens?: number
contextWindow?: number
onSendMessage?: (command: string, files: string[], images: string[]) => void
}
const ConfirmationDialog = memo<{
@@ -57,8 +55,6 @@ ConfirmationDialog.displayName = "ConfirmationDialog"
const ContextWindow: React.FC<ContextWindowProgressProps> = ({
contextWindow = 0,
lastApiReqTotalTokens = 0,
onSendMessage,
useAutoCondense,
tokensIn,
tokensOut,
cacheWrites,
@@ -26,7 +26,6 @@ interface TaskHeaderProps {
totalCost: number
lastApiReqTotalTokens?: number
onClose: () => void
onSendMessage?: (command: string, files: string[], images: string[]) => void
}
const BUTTON_CLASS = "max-h-3 border-0 font-bold bg-transparent hover:opacity-100 text-foreground"
@@ -40,7 +39,6 @@ const TaskHeader: React.FC<TaskHeaderProps> = ({
totalCost,
lastApiReqTotalTokens,
onClose,
onSendMessage,
}) => {
const {
apiConfiguration,
@@ -206,10 +204,8 @@ const TaskHeader: React.FC<TaskHeaderProps> = ({
cacheWrites={cacheWrites}
contextWindow={selectedModelInfo?.contextWindow}
lastApiReqTotalTokens={lastApiReqTotalTokens}
onSendMessage={onSendMessage}
tokensIn={tokensIn}
tokensOut={tokensOut}
useAutoCondense={false} // Disable auto-condense configuration in UI for now
/>
</div>
)}
@@ -11,8 +11,6 @@ vi.mock("@/context/ExtensionStateContext", () => ({
showFeatureTips: false,
mcpDisplayMode: "rich",
yoloModeToggled: false,
useAutoCondense: false,
subagentsEnabled: false,
worktreesEnabled: { user: true, featureFlag: true },
focusChainSettings: { enabled: false, remindClineInterval: 6 },
remoteConfigSettings: {},
@@ -31,10 +29,8 @@ describe("FeatureSettingsSection", () => {
expect(screen.getByText("Hooks")).toBeTruthy()
const advancedSection = container.querySelector("#advanced-features")
const agentSection = container.querySelector("#agent-features")
expect(advancedSection?.querySelector("#Hooks")).toBeTruthy()
expect(agentSection?.querySelector("#Hooks")).toBeNull()
})
it("renders Feature Tips toggle in the Editor section", () => {
@@ -43,10 +39,14 @@ describe("FeatureSettingsSection", () => {
expect(screen.getByText("Feature Tips")).toBeTruthy()
const editorSection = container.querySelector("#optional-features")
const agentSection = container.querySelector("#agent-features")
expect(editorSection?.querySelector('[id="Feature Tips"]')).toBeTruthy()
expect(agentSection?.querySelector('[id="Feature Tips"]')).toBeNull()
})
it("does not render the removed Auto Compact setting", () => {
render(<FeatureSettingsSection renderSectionHeader={() => null} />)
expect(screen.queryByText("Auto Compact")).toBeNull()
})
it("calls updateSetting with hooksEnabled when toggled", () => {
@@ -29,16 +29,6 @@ interface FeatureToggle {
stateKey: string
}
const agentFeatures: FeatureToggle[] = [
{
id: "auto-compact",
label: "Auto Compact",
description: "Automatically compress conversation history.",
stateKey: "useAutoCondense",
settingKey: "useAutoCondense",
},
]
const editorFeatures: FeatureToggle[] = [
{
id: "show-feature-tips",
@@ -153,8 +143,6 @@ const FeatureSettingsSection = ({ renderSectionHeader }: FeatureSettingsSectionP
hooksEnabled,
mcpDisplayMode,
yoloModeToggled,
useAutoCondense,
subagentsEnabled,
worktreesEnabled,
remoteConfigSettings,
backgroundEditEnabled,
@@ -168,8 +156,6 @@ const FeatureSettingsSection = ({ renderSectionHeader }: FeatureSettingsSectionP
showFeatureTips,
enableCheckpointsSetting,
hooksEnabled,
useAutoCondense,
subagentsEnabled,
worktreesEnabled: worktreesEnabled?.user,
backgroundEditEnabled,
yoloModeToggled: isYoloRemoteLocked ? remoteConfigSettings?.yoloModeToggled : yoloModeToggled,
@@ -185,25 +171,6 @@ const FeatureSettingsSection = ({ renderSectionHeader }: FeatureSettingsSectionP
{renderSectionHeader("features")}
<Section>
<div className="mb-5 flex flex-col gap-3">
{/* Core features */}
<div>
<div className="text-xs font-medium text-foreground/80 uppercase tracking-wider mb-3">Agent</div>
<div
className="relative p-3 pt-0 my-3 rounded-md border border-editor-widget-border/50"
id="agent-features">
{agentFeatures.map((feature) => (
<FeatureRow
checked={featureState[feature.stateKey]}
description={feature.description}
isVisible={featureVisibility[feature.stateKey] ?? true}
key={feature.id}
label={feature.label}
onChange={(checked) => updateSetting(feature.settingKey, checked)}
/>
))}
</div>
</div>
{/* Editor features */}
<div>
<div className="text-xs font-medium text-foreground/80 uppercase tracking-wider mb-3">Editor</div>
@@ -1193,7 +1193,7 @@ describe("AgentRuntime", () => {
expect(context.messages[0]?.content).toEqual([
{ type: "text", text: "large context" },
]);
context.emitStatusNotice?.("auto-compacting", {
context.emitStatusNotice?.("Summarizing context...", {
reason: "auto_compaction",
});
return {
@@ -1232,7 +1232,7 @@ describe("AgentRuntime", () => {
expect(prepareTurn).toHaveBeenCalledTimes(1);
expect(beforeModel).toHaveBeenCalledTimes(1);
expect(notices).toEqual(["auto-compacting"]);
expect(notices).toEqual(["Summarizing context..."]);
expect(result.messages[0]).toEqual(compactedMessage);
expect(result.messages).toHaveLength(2);
expect(model.requests).toHaveLength(1);
@@ -501,7 +501,7 @@ describe("createContextCompactionPrepareTurn", () => {
expect(createHandlerMock).toHaveBeenCalledTimes(1);
expect(emitStatusNotice).toHaveBeenCalledWith(
"auto-compacting",
"Compacting context...",
expect.objectContaining({
kind: "auto_compaction",
reason: "auto_compaction",
@@ -955,7 +955,7 @@ describe("createContextCompactionPrepareTurn", () => {
expect(createHandlerMock).not.toHaveBeenCalled();
expect(emitStatusNotice).toHaveBeenCalledWith(
"auto-compacting",
"Compacting context...",
expect.objectContaining({
kind: "auto_compaction",
reason: "auto_compaction",
@@ -138,9 +138,11 @@ const BUILTIN_COMPACTION_STRATEGIES = {
DEFAULT_PRESERVE_RECENT_TOKENS),
estimateMessageTokens,
logger,
}),
}),
} satisfies Record<CoreCompactionStrategy, BuiltinCompactionStrategyRunner>;
const COMPACTION_STATUS_NOTICE = "Compacting context...";
function resolveTriggerState(input: {
inputTokens: number;
maxInputTokens: number;
@@ -330,7 +332,7 @@ export function createContextCompactionPrepareTurn(
const statusReason =
mode === "manual" ? "manual_compaction" : "auto_compaction";
context.emitStatusNotice?.(
mode === "manual" ? "compacting" : "auto-compacting",
COMPACTION_STATUS_NOTICE,
{
kind: statusReason,
reason: statusReason,