mirror of
https://github.com/cline/cline.git
synced 2026-09-12 17:19:32 +08:00
Compare commits
1
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
49373322be |
+1
-1
@@ -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) |
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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");
|
||||
});
|
||||
});
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
@@ -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,
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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 }
|
||||
|
||||
@@ -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>
|
||||
)}
|
||||
|
||||
+6
-6
@@ -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,
|
||||
|
||||
Reference in New Issue
Block a user