diff --git a/mobile/src/utils/slashCommandHelpers.test.ts b/mobile/src/utils/slashCommandHelpers.test.ts index 8593b23647..fd38609c95 100644 --- a/mobile/src/utils/slashCommandHelpers.test.ts +++ b/mobile/src/utils/slashCommandHelpers.test.ts @@ -54,6 +54,7 @@ describe("buildMobileCompactionPayload", () => { const payload = buildMobileCompactionPayload(parsed, baseOptions); expect(payload.messageText).toContain("approximately 615 words"); + expect(payload.messageText).toContain(parsed.continueMessage); expect(payload.metadata.type).toBe("compaction-request"); expect(payload.metadata.rawCommand).toContain("/compact -t 800 -m anthropic:claude-opus-4-1"); diff --git a/mobile/src/utils/slashCommandHelpers.ts b/mobile/src/utils/slashCommandHelpers.ts index ce9ad9df12..a9a0e9f9dd 100644 --- a/mobile/src/utils/slashCommandHelpers.ts +++ b/mobile/src/utils/slashCommandHelpers.ts @@ -2,14 +2,17 @@ import type { MuxFrontendMetadata } from "@/common/types/message"; import type { ParsedCommand, SlashSuggestion } from "@/browser/utils/slashCommands/types"; import type { InferClientInputs } from "@orpc/client"; import type { ORPCClient } from "../orpc/client"; +import { + DEFAULT_COMPACTION_WORD_TARGET, + WORDS_TO_TOKENS_RATIO, + buildCompactionPrompt, +} from "@/common/constants/ui"; type SendMessageOptions = NonNullable< InferClientInputs["workspace"]["sendMessage"]["options"] >; export const MOBILE_HIDDEN_COMMANDS = new Set(["telemetry", "vim"]); -const WORDS_PER_TOKEN = 1.3; -const DEFAULT_WORD_TARGET = 2000; export function extractRootCommand(replacement: string): string | null { if (typeof replacement !== "string") { @@ -44,12 +47,10 @@ export function buildMobileCompactionPayload( baseOptions: SendMessageOptions ): MobileCompactionPayload { const targetWords = parsed.maxOutputTokens - ? Math.round(parsed.maxOutputTokens / WORDS_PER_TOKEN) - : DEFAULT_WORD_TARGET; + ? Math.round(parsed.maxOutputTokens / WORDS_TO_TOKENS_RATIO) + : DEFAULT_COMPACTION_WORD_TARGET; - let messageText = - `Summarize this conversation into a compact form for a new Assistant to continue helping the user. ` + - `Use approximately ${targetWords} words.`; + let messageText = buildCompactionPrompt(targetWords); if (parsed.continueMessage) { messageText += `\n\nThe user wants to continue with: ${parsed.continueMessage}`; diff --git a/src/browser/utils/chatCommands.test.ts b/src/browser/utils/chatCommands.test.ts index b102f2c0be..9e4fd2c8fa 100644 --- a/src/browser/utils/chatCommands.test.ts +++ b/src/browser/utils/chatCommands.test.ts @@ -128,18 +128,6 @@ describe("prepareCompactionMessage", () => { expect(metadata.parsed.continueMessage?.model).toBe(sendMessageOptions.model); }); - test("generates correct prompt text with strict summary instructions", () => { - const sendMessageOptions = createBaseOptions(); - const { messageText } = prepareCompactionMessage({ - workspaceId: "ws-1", - maxOutputTokens: 4096, - sendMessageOptions, - }); - - expect(messageText).toContain("Focus entirely on the summary"); - expect(messageText).toContain("Do not suggest next steps or future actions"); - }); - test("does not create continueMessage when no text or images provided", () => { const sendMessageOptions = createBaseOptions(); const { metadata } = prepareCompactionMessage({ diff --git a/src/browser/utils/chatCommands.ts b/src/browser/utils/chatCommands.ts index 2632049593..1283e6646b 100644 --- a/src/browser/utils/chatCommands.ts +++ b/src/browser/utils/chatCommands.ts @@ -25,7 +25,11 @@ import { resolveCompactionModel } from "@/browser/utils/messages/compactionModel import type { ImageAttachment } from "../components/ImageAttachments"; import { dispatchWorkspaceSwitch } from "./workspaceEvents"; import { getRuntimeKey, copyWorkspaceStorage } from "@/common/constants/storage"; -import { DEFAULT_COMPACTION_WORD_TARGET, WORDS_TO_TOKENS_RATIO } from "@/common/constants/ui"; +import { + DEFAULT_COMPACTION_WORD_TARGET, + WORDS_TO_TOKENS_RATIO, + buildCompactionPrompt, +} from "@/common/constants/ui"; // ============================================================================ // Workspace Creation @@ -593,7 +597,7 @@ export function prepareCompactionMessage(options: CompactionOptions): { : DEFAULT_COMPACTION_WORD_TARGET; // Build compaction message with optional continue context - let messageText = `Summarize this conversation into a compact form for a new Assistant to continue helping the user. Focus entirely on the summary of what has happened. Do not suggest next steps or future actions. Use approximately ${targetWords} words.`; + let messageText = buildCompactionPrompt(targetWords); if (options.continueMessage) { messageText += `\n\nThe user wants to continue with: ${options.continueMessage.text}`; diff --git a/src/common/constants/ui.ts b/src/common/constants/ui.ts index 5ce8a4de6a..d56c881167 100644 --- a/src/common/constants/ui.ts +++ b/src/common/constants/ui.ts @@ -40,6 +40,31 @@ export const DEFAULT_COMPACTION_WORD_TARGET = 2000; */ export const WORDS_TO_TOKENS_RATIO = 1.3; +/** + * Build the compaction prompt for a given word target. + * Shared across desktop and mobile clients. + */ +export function buildCompactionPrompt(targetWords: number): string { + return `Summarize this conversation for a new Assistant to continue helping the user. + +Your summary must be approximately ${targetWords} words. + +Include: +- The user's overall goal and current task +- Key decisions made and their rationale +- Current state of the work (what's done, what's in progress) +- Important technical details (file paths, function names, configurations) +- Any errors encountered and how they were resolved +- Unresolved issues or blockers + +Do not include: +- Suggestions for next steps +- Conversational filler or pleasantries +- Redundant information + +Write in a factual, dense style. Every sentence should convey essential context.`; +} + /** * Force-compact this many percentage points after threshold. * Gives user a buffer zone between warning and force-compaction. diff --git a/src/node/services/mock/scenarios/slashCommands.ts b/src/node/services/mock/scenarios/slashCommands.ts index 0defa15d86..bfa107b83b 100644 --- a/src/node/services/mock/scenarios/slashCommands.ts +++ b/src/node/services/mock/scenarios/slashCommands.ts @@ -1,13 +1,13 @@ import type { ScenarioTurn } from "@/node/services/mock/scenarioTypes"; import { KNOWN_MODELS } from "@/common/constants/knownModels"; import { STREAM_BASE_DELAY } from "@/node/services/mock/scenarioTypes"; +import { buildCompactionPrompt } from "@/common/constants/ui"; export const SLASH_COMMAND_PROMPTS = { MODEL_STATUS: "Please confirm which model is currently active for this conversation.", } as const; -export const COMPACTION_MESSAGE = - "Summarize this conversation into a compact form for a new Assistant to continue helping the user. Focus entirely on the summary of what has happened. Do not suggest next steps or future actions. Use approximately 385 words."; +export const COMPACTION_MESSAGE = buildCompactionPrompt(385); export const COMPACT_SUMMARY_TEXT = "Compact summary: The assistant read project files, listed directory contents, created and inspected test.txt, then confirmed the contents remained 'hello'. Technical details preserved.";