diff --git a/.gitignore b/.gitignore index 9f567cb5..177c17b2 100644 --- a/.gitignore +++ b/.gitignore @@ -36,3 +36,5 @@ test-update.ts # Documentation (local development only) docs/ SCHEMA_NOTES.md + +repomix-output.xml \ No newline at end of file diff --git a/.repomixignore b/.repomixignore new file mode 100644 index 00000000..6bc6e2ee --- /dev/null +++ b/.repomixignore @@ -0,0 +1,9 @@ +.github/ +.logs/ +.opencode/ +dist/ +.repomixignore +repomix-output.xml +bun.lock +package-lock.jsonc +LICENCE diff --git a/README.md b/README.md index 0a63b74c..afd338c3 100644 --- a/README.md +++ b/README.md @@ -28,11 +28,11 @@ DCP uses multiple tools and strategies to reduce context size: ### Tools -**Discard** — Exposes a `discard` tool that the AI can call to remove completed or noisy tool content from context. +**Prune** — Exposes a `prune` tool that the AI can call to remove completed or noisy tool content from context. -**Extract** — Exposes an `extract` tool that the AI can call to distill valuable context into concise summaries before removing the tool content. +**Distill** — Exposes a `distill` tool that the AI can call to distill valuable context into concise summaries before removing the tool content. -**Squash** — Exposes a `squash` tool that the AI can call to collapse a large section of conversation (messages and tools) into a single summary. +**Compress** — Exposes a `compress` tool that the AI can call to collapse a large section of conversation (messages and tools) into a single summary. ### Strategies @@ -60,7 +60,7 @@ DCP uses its own config file: - Global: `~/.config/opencode/dcp.jsonc` (or `dcp.json`), created automatically on first run - Custom config directory: `$OPENCODE_CONFIG_DIR/dcp.jsonc` (or `dcp.json`), if `OPENCODE_CONFIG_DIR` is set -- Project: `.opencode/dcp.jsonc` (or `dcp.json`) in your project’s `.opencode` directory +- Project: `.opencode/dcp.jsonc` (or `dcp.json`) in your project's `.opencode` directory
Default Configuration (click to expand) @@ -99,17 +99,17 @@ DCP uses its own config file: "protectedTools": [], }, // Removes tool content from context without preservation (for completed tasks or noise) - "discard": { + "prune": { "enabled": true, }, // Distills key findings into preserved knowledge before removing raw content - "extract": { + "distill": { "enabled": true, // Show distillation content as an ignored message notification "showDistillation": false, }, // Collapses a range of conversation content into a single summary - "squash": { + "compress": { "enabled": true, // Show summary content as an ignored message notification "showSummary": true, @@ -152,12 +152,12 @@ DCP provides a `/dcp` slash command: ### Turn Protection -When enabled, turn protection prevents tool outputs from being pruned for a configurable number of message turns. This gives the AI time to reference recent tool outputs before they become prunable. Applies to both `discard` and `extract` tools, as well as automatic strategies. +When enabled, turn protection prevents tool outputs from being pruned for a configurable number of message turns. This gives the AI time to reference recent tool outputs before they become prunable. Applies to both `prune` and `distill` tools, as well as automatic strategies. ### Protected Tools By default, these tools are always protected from pruning across all strategies: -`task`, `todowrite`, `todoread`, `discard`, `extract`, `squash`, `batch`, `write`, `edit`, `plan_enter`, `plan_exit` +`task`, `todowrite`, `todoread`, `prune`, `distill`, `compress`, `batch`, `write`, `edit`, `plan_enter`, `plan_exit` The `protectedTools` arrays in each section add to this default list. diff --git a/cli/README.md b/cli/README.md new file mode 100644 index 00000000..abbd3398 --- /dev/null +++ b/cli/README.md @@ -0,0 +1,41 @@ +# DCP CLI + +Dev tool for previewing prompt outputs. Verify parsing works correctly and quickly check specific tool combinations. + +## Usage + +```bash +bun run dcp [TYPE] [-p] [-d] [-c] +``` + +## Types + +| Flag | Description | +| -------------------- | --------------------------- | +| `--system` | System prompt | +| `--nudge` | Nudge prompt | +| `--prune-list` | Example prunable tools list | +| `--compress-context` | Example compress context | + +## Tool Flags + +| Flag | Description | +| ---------------- | -------------------- | +| `-p, --prune` | Enable prune tool | +| `-d, --distill` | Enable distill tool | +| `-c, --compress` | Enable compress tool | + +If no tool flags specified, all are enabled. + +## Examples + +```bash +bun run dcp --system -p -d -c # System prompt with all tools +bun run dcp --system -p # System prompt with prune only +bun run dcp --nudge -d -c # Nudge with distill and compress +bun run dcp --prune-list # Example prunable tools list +``` + +## Purpose + +This CLI does NOT ship with the plugin. It's purely for DX - iterate on prompt templates and verify the `` conditional parsing produces the expected output. diff --git a/cli/print.ts b/cli/print.ts new file mode 100644 index 00000000..8d795cd3 --- /dev/null +++ b/cli/print.ts @@ -0,0 +1,117 @@ +#!/usr/bin/env npx tsx + +import { renderSystemPrompt, renderNudge, type ToolFlags } from "../lib/prompts/index.js" +import { + wrapPrunableTools, + wrapCompressContext, + wrapCooldownMessage, +} from "../lib/messages/inject.js" + +const args = process.argv.slice(2) + +const flags: ToolFlags = { + prune: args.includes("-p") || args.includes("--prune"), + distill: args.includes("-d") || args.includes("--distill"), + compress: args.includes("-c") || args.includes("--compress"), +} + +// Default to all enabled if none specified +if (!flags.prune && !flags.distill && !flags.compress) { + flags.prune = true + flags.distill = true + flags.compress = true +} + +const showSystem = args.includes("--system") +const showNudge = args.includes("--nudge") +const showPruneList = args.includes("--prune-list") +const showCompressContext = args.includes("--compress-context") +const showCooldown = args.includes("--cooldown") +const showHelp = args.includes("--help") || args.includes("-h") + +if ( + showHelp || + (!showSystem && !showNudge && !showPruneList && !showCompressContext && !showCooldown) +) { + console.log(` +Usage: bun run dcp [TYPE] [-p] [-d] [-c] + +Types: + --system System prompt + --nudge Nudge prompt + --prune-list Example prunable tools list + --compress-context Example compress context + --cooldown Cooldown message after pruning + +Tool flags (for --system and --nudge): + -p, --prune Enable prune tool + -d, --distill Enable distill tool + -c, --compress Enable compress tool + +If no tool flags specified, all are enabled. + +Examples: + bun run dcp --system -p -d -c # System prompt with all tools + bun run dcp --system -p # System prompt with prune only + bun run dcp --nudge -d -c # Nudge with distill and compress + bun run dcp --prune-list # Example prunable tools list +`) + process.exit(0) +} + +const header = (title: string) => { + console.log() + console.log("─".repeat(60)) + console.log(title) + console.log("─".repeat(60)) +} + +if (showSystem) { + const enabled = [ + flags.prune && "prune", + flags.distill && "distill", + flags.compress && "compress", + ] + .filter(Boolean) + .join(", ") + header(`SYSTEM PROMPT (tools: ${enabled})`) + console.log(renderSystemPrompt(flags)) +} + +if (showNudge) { + const enabled = [ + flags.prune && "prune", + flags.distill && "distill", + flags.compress && "compress", + ] + .filter(Boolean) + .join(", ") + header(`NUDGE (tools: ${enabled})`) + console.log(renderNudge(flags)) +} + +if (showPruneList) { + header("PRUNABLE TOOLS LIST (mock example)") + const mockList = `5: read, /path/to/file.ts +8: bash, npm run build +12: glob, src/**/*.ts +15: read, /path/to/another-file.ts` + console.log(wrapPrunableTools(mockList)) +} + +if (showCompressContext) { + header("COMPRESS CONTEXT (mock example)") + console.log(wrapCompressContext(45)) +} + +if (showCooldown) { + const enabled = [ + flags.prune && "prune", + flags.distill && "distill", + flags.compress && "compress", + ] + .filter(Boolean) + .join(", ") + header(`COOLDOWN MESSAGE (tools: ${enabled})`) + console.log(wrapCooldownMessage(flags)) +} diff --git a/dcp.schema.json b/dcp.schema.json index bd458ac3..9d87cbac 100644 --- a/dcp.schema.json +++ b/dcp.schema.json @@ -105,27 +105,27 @@ } } }, - "discard": { + "prune": { "type": "object", - "description": "Configuration for the discard tool", + "description": "Configuration for the prune tool", "additionalProperties": false, "properties": { "enabled": { "type": "boolean", "default": true, - "description": "Enable the discard tool" + "description": "Enable the prune tool" } } }, - "extract": { + "distill": { "type": "object", - "description": "Configuration for the extract tool", + "description": "Configuration for the distill tool", "additionalProperties": false, "properties": { "enabled": { "type": "boolean", "default": true, - "description": "Enable the extract tool" + "description": "Enable the distill tool" }, "showDistillation": { "type": "boolean", @@ -134,15 +134,15 @@ } } }, - "squash": { + "compress": { "type": "object", - "description": "Configuration for the squash tool", + "description": "Configuration for the compress tool", "additionalProperties": false, "properties": { "enabled": { "type": "boolean", "default": true, - "description": "Enable the squash tool" + "description": "Enable the compress tool" }, "showSummary": { "type": "boolean", diff --git a/index.ts b/index.ts index fc8ab62a..94291a6a 100644 --- a/index.ts +++ b/index.ts @@ -2,7 +2,7 @@ import type { Plugin } from "@opencode-ai/plugin" import { getConfig } from "./lib/config" import { Logger } from "./lib/logger" import { createSessionState } from "./lib/state" -import { createDiscardTool, createExtractTool, createSquashTool } from "./lib/strategies" +import { createPruneTool, createDistillTool, createCompressTool } from "./lib/strategies" import { createChatMessageTransformHandler, createCommandExecuteHandler, @@ -61,8 +61,8 @@ const plugin: Plugin = (async (ctx) => { ctx.directory, ), tool: { - ...(config.tools.discard.enabled && { - discard: createDiscardTool({ + ...(config.tools.distill.enabled && { + distill: createDistillTool({ client: ctx.client, state, logger, @@ -70,8 +70,8 @@ const plugin: Plugin = (async (ctx) => { workingDirectory: ctx.directory, }), }), - ...(config.tools.extract.enabled && { - extract: createExtractTool({ + ...(config.tools.compress.enabled && { + compress: createCompressTool({ client: ctx.client, state, logger, @@ -79,8 +79,8 @@ const plugin: Plugin = (async (ctx) => { workingDirectory: ctx.directory, }), }), - ...(config.tools.squash.enabled && { - squash: createSquashTool({ + ...(config.tools.prune.enabled && { + prune: createPruneTool({ client: ctx.client, state, logger, @@ -99,9 +99,9 @@ const plugin: Plugin = (async (ctx) => { } const toolsToAdd: string[] = [] - if (config.tools.discard.enabled) toolsToAdd.push("discard") - if (config.tools.extract.enabled) toolsToAdd.push("extract") - if (config.tools.squash.enabled) toolsToAdd.push("squash") + if (config.tools.prune.enabled) toolsToAdd.push("prune") + if (config.tools.distill.enabled) toolsToAdd.push("distill") + if (config.tools.compress.enabled) toolsToAdd.push("compress") if (toolsToAdd.length > 0) { const existingPrimaryTools = opencodeConfig.experimental?.primary_tools ?? [] diff --git a/lib/config.ts b/lib/config.ts index e0b0b7f8..5f3b4e39 100644 --- a/lib/config.ts +++ b/lib/config.ts @@ -9,16 +9,16 @@ export interface Deduplication { protectedTools: string[] } -export interface DiscardTool { +export interface PruneTool { enabled: boolean } -export interface ExtractTool { +export interface DistillTool { enabled: boolean showDistillation: boolean } -export interface SquashTool { +export interface CompressTool { enabled: boolean showSummary: boolean } @@ -31,9 +31,9 @@ export interface ToolSettings { export interface Tools { settings: ToolSettings - discard: DiscardTool - extract: ExtractTool - squash: SquashTool + prune: PruneTool + distill: DistillTool + compress: CompressTool } export interface Commands { @@ -75,9 +75,9 @@ const DEFAULT_PROTECTED_TOOLS = [ "task", "todowrite", "todoread", - "discard", - "extract", - "squash", + "prune", + "distill", + "compress", "batch", "write", "edit", @@ -105,14 +105,14 @@ export const VALID_CONFIG_KEYS = new Set([ "tools.settings.nudgeEnabled", "tools.settings.nudgeFrequency", "tools.settings.protectedTools", - "tools.discard", - "tools.discard.enabled", - "tools.extract", - "tools.extract.enabled", - "tools.extract.showDistillation", - "tools.squash", - "tools.squash.enabled", - "tools.squash.showSummary", + "tools.prune", + "tools.prune.enabled", + "tools.distill", + "tools.distill.enabled", + "tools.distill.showDistillation", + "tools.compress", + "tools.compress.enabled", + "tools.compress.showSummary", "strategies", // strategies.deduplication "strategies.deduplication", @@ -277,50 +277,53 @@ function validateConfigTypes(config: Record): ValidationError[] { }) } } - if (tools.discard) { - if (tools.discard.enabled !== undefined && typeof tools.discard.enabled !== "boolean") { + if (tools.prune) { + if (tools.prune.enabled !== undefined && typeof tools.prune.enabled !== "boolean") { errors.push({ - key: "tools.discard.enabled", + key: "tools.prune.enabled", expected: "boolean", - actual: typeof tools.discard.enabled, + actual: typeof tools.prune.enabled, }) } } - if (tools.extract) { - if (tools.extract.enabled !== undefined && typeof tools.extract.enabled !== "boolean") { + if (tools.distill) { + if (tools.distill.enabled !== undefined && typeof tools.distill.enabled !== "boolean") { errors.push({ - key: "tools.extract.enabled", + key: "tools.distill.enabled", expected: "boolean", - actual: typeof tools.extract.enabled, + actual: typeof tools.distill.enabled, }) } if ( - tools.extract.showDistillation !== undefined && - typeof tools.extract.showDistillation !== "boolean" + tools.distill.showDistillation !== undefined && + typeof tools.distill.showDistillation !== "boolean" ) { errors.push({ - key: "tools.extract.showDistillation", + key: "tools.distill.showDistillation", expected: "boolean", - actual: typeof tools.extract.showDistillation, + actual: typeof tools.distill.showDistillation, }) } } - if (tools.squash) { - if (tools.squash.enabled !== undefined && typeof tools.squash.enabled !== "boolean") { + if (tools.compress) { + if ( + tools.compress.enabled !== undefined && + typeof tools.compress.enabled !== "boolean" + ) { errors.push({ - key: "tools.squash.enabled", + key: "tools.compress.enabled", expected: "boolean", - actual: typeof tools.squash.enabled, + actual: typeof tools.compress.enabled, }) } if ( - tools.squash.showSummary !== undefined && - typeof tools.squash.showSummary !== "boolean" + tools.compress.showSummary !== undefined && + typeof tools.compress.showSummary !== "boolean" ) { errors.push({ - key: "tools.squash.showSummary", + key: "tools.compress.showSummary", expected: "boolean", - actual: typeof tools.squash.showSummary, + actual: typeof tools.compress.showSummary, }) } } @@ -468,14 +471,14 @@ const defaultConfig: PluginConfig = { nudgeFrequency: 10, protectedTools: [...DEFAULT_PROTECTED_TOOLS], }, - discard: { + prune: { enabled: true, }, - extract: { + distill: { enabled: true, showDistillation: false, }, - squash: { + compress: { enabled: true, showSummary: true, }, @@ -644,16 +647,16 @@ function mergeTools( ]), ], }, - discard: { - enabled: override.discard?.enabled ?? base.discard.enabled, + prune: { + enabled: override.prune?.enabled ?? base.prune.enabled, }, - extract: { - enabled: override.extract?.enabled ?? base.extract.enabled, - showDistillation: override.extract?.showDistillation ?? base.extract.showDistillation, + distill: { + enabled: override.distill?.enabled ?? base.distill.enabled, + showDistillation: override.distill?.showDistillation ?? base.distill.showDistillation, }, - squash: { - enabled: override.squash?.enabled ?? base.squash.enabled, - showSummary: override.squash?.showSummary ?? base.squash.showSummary, + compress: { + enabled: override.compress?.enabled ?? base.compress.enabled, + showSummary: override.compress?.showSummary ?? base.compress.showSummary, }, } } @@ -684,9 +687,9 @@ function deepCloneConfig(config: PluginConfig): PluginConfig { ...config.tools.settings, protectedTools: [...config.tools.settings.protectedTools], }, - discard: { ...config.tools.discard }, - extract: { ...config.tools.extract }, - squash: { ...config.tools.squash }, + prune: { ...config.tools.prune }, + distill: { ...config.tools.distill }, + compress: { ...config.tools.compress }, }, strategies: { deduplication: { diff --git a/lib/hooks.ts b/lib/hooks.ts index eee5801b..c10b6f00 100644 --- a/lib/hooks.ts +++ b/lib/hooks.ts @@ -5,7 +5,7 @@ import { syncToolCache } from "./state/tool-cache" import { deduplicate, supersedeWrites, purgeErrors } from "./strategies" import { prune, insertPruneToolContext } from "./messages" import { checkSession } from "./state" -import { loadPrompt } from "./prompts" +import { renderSystemPrompt } from "./prompts" import { handleStatsCommand } from "./commands/stats" import { handleContextCommand } from "./commands/context" import { handleHelpCommand } from "./commands/help" @@ -33,31 +33,17 @@ export function createSystemPromptHandler( return } - const discardEnabled = config.tools.discard.enabled - const extractEnabled = config.tools.extract.enabled - const squashEnabled = config.tools.squash.enabled - - let promptName: string - if (discardEnabled && extractEnabled && squashEnabled) { - promptName = "system/system-prompt-all" - } else if (discardEnabled && extractEnabled) { - promptName = "system/system-prompt-discard-extract" - } else if (discardEnabled && squashEnabled) { - promptName = "system/system-prompt-discard-squash" - } else if (extractEnabled && squashEnabled) { - promptName = "system/system-prompt-extract-squash" - } else if (discardEnabled) { - promptName = "system/system-prompt-discard" - } else if (extractEnabled) { - promptName = "system/system-prompt-extract" - } else if (squashEnabled) { - promptName = "system/system-prompt-squash" - } else { + const flags = { + prune: config.tools.prune.enabled, + distill: config.tools.distill.enabled, + compress: config.tools.compress.enabled, + } + + if (!flags.prune && !flags.distill && !flags.compress) { return } - const syntheticPrompt = loadPrompt(promptName) - output.system.push(syntheticPrompt) + output.system.push(renderSystemPrompt(flags)) } } diff --git a/lib/messages/inject.ts b/lib/messages/inject.ts index 91761393..5ebe6552 100644 --- a/lib/messages/inject.ts +++ b/lib/messages/inject.ts @@ -2,7 +2,7 @@ import type { SessionState, WithParts } from "../state" import type { Logger } from "../logger" import type { PluginConfig } from "../config" import type { UserMessage } from "@opencode-ai/sdk/v2" -import { loadPrompt } from "../prompts" +import { renderNudge } from "../prompts" import { extractParameterKey, buildToolIdList, @@ -15,43 +15,26 @@ import { import { getFilePathFromParameters, isProtectedFilePath } from "../protected-file-patterns" import { getLastUserMessage, isMessageCompacted } from "../shared-utils" -const getNudgeString = (config: PluginConfig): string => { - const discardEnabled = config.tools.discard.enabled - const extractEnabled = config.tools.extract.enabled - const squashEnabled = config.tools.squash.enabled - - if (discardEnabled && extractEnabled && squashEnabled) { - return loadPrompt(`nudge/nudge-all`) - } else if (discardEnabled && extractEnabled) { - return loadPrompt(`nudge/nudge-discard-extract`) - } else if (discardEnabled && squashEnabled) { - return loadPrompt(`nudge/nudge-discard-squash`) - } else if (extractEnabled && squashEnabled) { - return loadPrompt(`nudge/nudge-extract-squash`) - } else if (discardEnabled) { - return loadPrompt(`nudge/nudge-discard`) - } else if (extractEnabled) { - return loadPrompt(`nudge/nudge-extract`) - } else if (squashEnabled) { - return loadPrompt(`nudge/nudge-squash`) - } - return "" -} - -const wrapPrunableTools = (content: string): string => ` +export const wrapPrunableTools = (content: string): string => ` The following tools have been invoked and are available for pruning. This list does not mandate immediate action. Consider your current goals and the resources you need before pruning valuable tool inputs or outputs. Consolidate your prunes for efficiency; it is rarely worth pruning a single tiny tool output. Keep the context free of noise. ${content} ` -const getCooldownMessage = (config: PluginConfig): string => { - const discardEnabled = config.tools.discard.enabled - const extractEnabled = config.tools.extract.enabled - const squashEnabled = config.tools.squash.enabled +export const wrapCompressContext = (messageCount: number): string => ` +Compress available. Conversation: ${messageCount} messages. +Compress collapses completed task sequences or exploration phases into summaries. +Uses text boundaries [startString, endString, topic, summary]. +` +export const wrapCooldownMessage = (flags: { + prune: boolean + distill: boolean + compress: boolean +}): string => { const enabledTools: string[] = [] - if (discardEnabled) enabledTools.push("discard") - if (extractEnabled) enabledTools.push("extract") - if (squashEnabled) enabledTools.push("squash") + if (flags.prune) enabledTools.push("prune") + if (flags.distill) enabledTools.push("distill") + if (flags.compress) enabledTools.push("compress") let toolName: string if (enabledTools.length === 0) { @@ -64,18 +47,35 @@ const getCooldownMessage = (config: PluginConfig): string => { } return ` -Context management was just performed. Do not use the ${toolName} again. A fresh list will be available after your next tool use. +Context management was just performed. Do NOT use the ${toolName} again. A fresh list will be available after your next tool use. ` } -const buildSquashContext = (state: SessionState, messages: WithParts[]): string => { - const messageCount = messages.filter((msg) => !isMessageCompacted(state, msg)).length +const getNudgeString = (config: PluginConfig): string => { + const flags = { + prune: config.tools.prune.enabled, + distill: config.tools.distill.enabled, + compress: config.tools.compress.enabled, + } - return ` -Squash available. Conversation: ${messageCount} messages. -Squash collapses completed task sequences or exploration phases into summaries. -Uses text boundaries [startString, endString, topic, summary]. -` + if (!flags.prune && !flags.distill && !flags.compress) { + return "" + } + + return renderNudge(flags) +} + +const getCooldownMessage = (config: PluginConfig): string => { + return wrapCooldownMessage({ + prune: config.tools.prune.enabled, + distill: config.tools.distill.enabled, + compress: config.tools.compress.enabled, + }) +} + +const buildCompressContext = (state: SessionState, messages: WithParts[]): string => { + const messageCount = messages.filter((msg) => !isMessageCompacted(state, msg)).length + return wrapCompressContext(messageCount) } const buildPrunableToolsList = ( @@ -133,23 +133,23 @@ export const insertPruneToolContext = ( logger: Logger, messages: WithParts[], ): void => { - const discardEnabled = config.tools.discard.enabled - const extractEnabled = config.tools.extract.enabled - const squashEnabled = config.tools.squash.enabled + const pruneEnabled = config.tools.prune.enabled + const distillEnabled = config.tools.distill.enabled + const compressEnabled = config.tools.compress.enabled - if (!discardEnabled && !extractEnabled && !squashEnabled) { + if (!pruneEnabled && !distillEnabled && !compressEnabled) { return } - const discardOrExtractEnabled = discardEnabled || extractEnabled + const pruneOrDistillEnabled = pruneEnabled || distillEnabled const contentParts: string[] = [] if (state.lastToolPrune) { logger.debug("Last tool was prune - injecting cooldown message") contentParts.push(getCooldownMessage(config)) } else { - // Inject only when discard or extract is enabled - if (discardOrExtractEnabled) { + // Inject only when prune or distill is enabled + if (pruneOrDistillEnabled) { const prunableToolsList = buildPrunableToolsList(state, config, logger, messages) if (prunableToolsList) { // logger.debug("prunable-tools: \n" + prunableToolsList) @@ -157,11 +157,11 @@ export const insertPruneToolContext = ( } } - // Inject always when squash is enabled (every turn) - if (squashEnabled) { - const squashContext = buildSquashContext(state, messages) - // logger.debug("squash-context: \n" + squashContext) - contentParts.push(squashContext) + // Inject always when compress is enabled (every turn) + if (compressEnabled) { + const compressContext = buildCompressContext(state, messages) + // logger.debug("compress-context: \n" + compressContext) + contentParts.push(compressContext) } // Add nudge if threshold reached diff --git a/lib/messages/prune.ts b/lib/messages/prune.ts index 65e97dd0..8d616270 100644 --- a/lib/messages/prune.ts +++ b/lib/messages/prune.ts @@ -2,7 +2,7 @@ import type { SessionState, WithParts } from "../state" import type { Logger } from "../logger" import type { PluginConfig } from "../config" import { isMessageCompacted, getLastUserMessage } from "../shared-utils" -import { createSyntheticUserMessage, SQUASH_SUMMARY_PREFIX } from "./utils" +import { createSyntheticUserMessage, COMPRESS_SUMMARY_PREFIX } from "./utils" import type { UserMessage } from "@opencode-ai/sdk/v2" const PRUNED_TOOL_OUTPUT_REPLACEMENT = @@ -16,7 +16,7 @@ export const prune = ( config: PluginConfig, messages: WithParts[], ): void => { - filterSquashedRanges(state, logger, messages) + filterCompressedRanges(state, logger, messages) pruneToolOutputs(state, logger, messages) pruneToolInputs(state, logger, messages) pruneToolErrors(state, logger, messages) @@ -107,7 +107,11 @@ const pruneToolErrors = (state: SessionState, logger: Logger, messages: WithPart } } -const filterSquashedRanges = (state: SessionState, logger: Logger, messages: WithParts[]): void => { +const filterCompressedRanges = ( + state: SessionState, + logger: Logger, + messages: WithParts[], +): void => { if (!state.prune.messageIds?.length) { return } @@ -118,7 +122,7 @@ const filterSquashedRanges = (state: SessionState, logger: Logger, messages: Wit const msgId = msg.info.id // Check if there's a summary to inject at this anchor point - const summary = state.squashSummaries?.find((s) => s.anchorMessageId === msgId) + const summary = state.compressSummaries?.find((s) => s.anchorMessageId === msgId) if (summary) { // Find user message for variant and as base for synthetic message const msgIndex = messages.indexOf(msg) @@ -126,17 +130,17 @@ const filterSquashedRanges = (state: SessionState, logger: Logger, messages: Wit if (userMessage) { const userInfo = userMessage.info as UserMessage - const summaryContent = SQUASH_SUMMARY_PREFIX + summary.summary + const summaryContent = COMPRESS_SUMMARY_PREFIX + summary.summary result.push( createSyntheticUserMessage(userMessage, summaryContent, userInfo.variant), ) - logger.info("Injected squash summary", { + logger.info("Injected compress summary", { anchorMessageId: msgId, summaryLength: summary.summary.length, }) } else { - logger.warn("No user message found for squash summary", { + logger.warn("No user message found for compress summary", { anchorMessageId: msgId, }) } diff --git a/lib/messages/utils.ts b/lib/messages/utils.ts index 9d30b03f..a0035727 100644 --- a/lib/messages/utils.ts +++ b/lib/messages/utils.ts @@ -4,7 +4,7 @@ import { isMessageCompacted } from "../shared-utils" import type { SessionState, WithParts } from "../state" import type { UserMessage } from "@opencode-ai/sdk/v2" -export const SQUASH_SUMMARY_PREFIX = "[Squashed conversation block]\n\n" +export const COMPRESS_SUMMARY_PREFIX = "[Compressed conversation block]\n\n" const generateUniqueId = (prefix: string): string => `${prefix}_${ulid()}` diff --git a/lib/prompts/squash-tool-spec.ts b/lib/prompts/compress-tool-spec.ts similarity index 71% rename from lib/prompts/squash-tool-spec.ts rename to lib/prompts/compress-tool-spec.ts index ab3644b7..0a08b08f 100644 --- a/lib/prompts/squash-tool-spec.ts +++ b/lib/prompts/compress-tool-spec.ts @@ -1,8 +1,8 @@ -export const SQUASH_TOOL_SPEC = `Collapses a contiguous range of conversation into a single summary. +export const COMPRESS_TOOL_SPEC = `Collapses a contiguous range of conversation into a single summary. ## When to Use This Tool -Use \`squash\` when you want to condense an entire sequence of work into a brief summary: +Use \`compress\` when you want to condense an entire sequence of work into a brief summary: - **Phase Completion:** You completed a phase (research, tool calls, implementation) and want to collapse the entire sequence into a summary. - **Exploration Done:** You explored multiple files or ran multiple commands and only need a summary of what you learned. @@ -12,19 +12,19 @@ Use \`squash\` when you want to condense an entire sequence of work into a brief ## When NOT to Use This Tool - **If you need specific details:** If you'll need exact code, file contents, or error messages from the range, keep them. -- **For individual tool outputs:** Use \`discard\` or \`extract\` for single tool outputs. Squash targets conversation ranges. +- **For individual tool outputs:** Use \`prune\` or \`distill\` for single tool outputs. Compress targets conversation ranges. - **If it's recent content:** You may still need recent work for the current phase. ## How It Works -1. \`startString\` — A unique text string that marks the start of the range to squash -2. \`endString\` — A unique text string that marks the end of the range to squash -3. \`topic\` — A short label (3-5 words) describing the squashed content +1. \`startString\` — A unique text string that marks the start of the range to compress +2. \`endString\` — A unique text string that marks the end of the range to compress +3. \`topic\` — A short label (3-5 words) describing the compressed content 4. \`summary\` — The replacement text that will be inserted Everything between startString and endString (inclusive) is removed and replaced with your summary. -**Important:** The squash will FAIL if \`startString\` or \`endString\` is not found in the conversation. The squash will also FAIL if either string is found multiple times. Provide a larger string with more surrounding context to uniquely identify the intended match. +**Important:** The compress will FAIL if \`startString\` or \`endString\` is not found in the conversation. The compress will also FAIL if either string is found multiple times. Provide a larger string with more surrounding context to uniquely identify the intended match. ## Best Practices - **Choose unique strings:** Pick text that appears only once in the conversation. @@ -38,10 +38,10 @@ Everything between startString and endString (inclusive) is removed and replaced ## Example - + Conversation: [Asked about auth] -> [Read 5 files] -> [Analyzed patterns] -> [Found "JWT tokens with 24h expiry"] -[Uses squash with: +[Uses compress with: input: [ "Asked about authentication", "JWT tokens with 24h expiry", @@ -49,9 +49,9 @@ Conversation: [Asked about auth] -> [Read 5 files] -> [Analyzed patterns] -> [Fo "Auth: JWT 24h expiry, bcrypt passwords, refresh rotation. Files: auth.ts, tokens.ts, middleware/auth.ts" ] ] - + Assistant: [Just finished reading auth.ts] -I've read the auth file and now need to make edits based on it. I'm keeping this in context rather than squashing. +I've read the auth file and now need to make edits based on it. I'm keeping this in context rather than compressing. ` diff --git a/lib/prompts/discard-tool-spec.ts b/lib/prompts/discard-tool-spec.ts deleted file mode 100644 index 1c1eea74..00000000 --- a/lib/prompts/discard-tool-spec.ts +++ /dev/null @@ -1,39 +0,0 @@ -export const DISCARD_TOOL_SPEC = `Discards tool outputs from context to manage conversation size and reduce noise. - -## IMPORTANT: The Prunable List -A \`\` list is provided to you showing available tool outputs you can discard when there are tools available for pruning. Each line has the format \`ID: tool, parameter\` (e.g., \`20: read, /path/to/file.ts\`). You MUST only use numeric IDs that appear in this list to select which tools to discard. - -## When to Use This Tool - -Use \`discard\` for removing individual tool outputs that are no longer needed: - -- **Noise:** Irrelevant, unhelpful, or superseded outputs that provide no value. -- **Wrong Files:** You read or accessed something that turned out to be irrelevant. -- **Outdated Info:** Outputs that have been superseded by newer information. - -## When NOT to Use This Tool - -- **If the output contains useful information:** Keep it in context rather than discarding. -- **If you'll need the output later:** Don't discard files you plan to edit or context you'll need for implementation. - -## Best Practices -- **Strategic Batching:** Don't discard single small tool outputs (like short bash commands) unless they are pure noise. Wait until you have several items to perform high-impact discards. -- **Think ahead:** Before discarding, ask: "Will I need this output for upcoming work?" If yes, keep it. - -## Format - -- \`ids\`: Array of numeric IDs as strings from the \`\` list - -## Example - - -Assistant: [Reads 'wrong_file.ts'] -This file isn't relevant to the auth system. I'll remove it to clear the context. -[Uses discard with ids: ["5"]] - - - -Assistant: [Reads config.ts, then reads updated config.ts after changes] -The first read is now outdated. I'll discard it and keep the updated version. -[Uses discard with ids: ["20"]] -` diff --git a/lib/prompts/extract-tool-spec.ts b/lib/prompts/distill-tool-spec.ts similarity index 75% rename from lib/prompts/extract-tool-spec.ts rename to lib/prompts/distill-tool-spec.ts index f680ea9e..9fccc048 100644 --- a/lib/prompts/extract-tool-spec.ts +++ b/lib/prompts/distill-tool-spec.ts @@ -1,11 +1,11 @@ -export const EXTRACT_TOOL_SPEC = `Extracts key findings from tool outputs into distilled knowledge, then removes the raw outputs from context. +export const DISTILL_TOOL_SPEC = `Distills key findings from tool outputs into preserved knowledge, then removes the raw outputs from context. ## IMPORTANT: The Prunable List -A \`\` list is provided to you showing available tool outputs you can extract from when there are tools available for pruning. Each line has the format \`ID: tool, parameter\` (e.g., \`20: read, /path/to/file.ts\`). You MUST only use numeric IDs that appear in this list to select which tools to extract. +A \`\` list is provided to you showing available tool outputs you can distill from when there are tools available for pruning. Each line has the format \`ID: tool, parameter\` (e.g., \`20: read, /path/to/file.ts\`). You MUST only use numeric IDs that appear in this list to select which tools to distill. ## When to Use This Tool -Use \`extract\` when you have individual tool outputs with valuable information you want to **preserve in distilled form** before removing the raw content: +Use \`distill\` when you have individual tool outputs with valuable information you want to **preserve in distilled form** before removing the raw content: - **Large Outputs:** The raw output is too large but contains valuable technical details worth keeping. - **Knowledge Preservation:** You have context that contains valuable information (signatures, logic, constraints) but also a lot of unnecessary detail. @@ -17,8 +17,8 @@ Use \`extract\` when you have individual tool outputs with valuable information ## Best Practices -- **Strategic Batching:** Wait until you have several items or a few large outputs to extract, rather than doing tiny, frequent extractions. Aim for high-impact extractions that significantly reduce context size. -- **Think ahead:** Before extracting, ask: "Will I need the raw output for upcoming work?" If you researched a file you'll later edit, do NOT extract it. +- **Strategic Batching:** Wait until you have several items or a few large outputs to distill, rather than doing tiny, frequent distillations. Aim for high-impact distillations that significantly reduce context size. +- **Think ahead:** Before distilling, ask: "Will I need the raw output for upcoming work?" If you researched a file you'll later edit, do NOT distill it. ## Format @@ -29,19 +29,19 @@ Each distillation string should capture the essential information you need to pr ## Example - + Assistant: [Reads auth service and user types] -I'll preserve the key details before extracting. -[Uses extract with: +I'll preserve the key details before distilling. +[Uses distill with: ids: ["10", "11"], distillation: [ "auth.ts: validateToken(token: string) -> User|null checks cache first (5min TTL) then OIDC. hashPassword uses bcrypt 12 rounds. Tokens must be 128+ chars.", "user.ts: interface User { id: string; email: string; permissions: ('read'|'write'|'admin')[]; status: 'active'|'suspended' }" ] ] - + Assistant: [Reads 'auth.ts' to understand the login flow] -I've understood the auth flow. I'll need to modify this file to add the new validation, so I'm keeping this read in context rather than extracting. +I've understood the auth flow. I'll need to modify this file to add the new validation, so I'm keeping this read in context rather than distilling. ` diff --git a/lib/prompts/index.ts b/lib/prompts/index.ts index 8a6bf745..3cfd4295 100644 --- a/lib/prompts/index.ts +++ b/lib/prompts/index.ts @@ -1,44 +1,50 @@ +import { readFileSync } from "node:fs" +import { dirname, join } from "node:path" +import { fileURLToPath } from "node:url" + // Tool specs -import { DISCARD_TOOL_SPEC } from "./discard-tool-spec" -import { EXTRACT_TOOL_SPEC } from "./extract-tool-spec" -import { SQUASH_TOOL_SPEC } from "./squash-tool-spec" - -// System prompts -import { SYSTEM_PROMPT_DISCARD } from "./system/discard" -import { SYSTEM_PROMPT_EXTRACT } from "./system/extract" -import { SYSTEM_PROMPT_SQUASH } from "./system/squash" -import { SYSTEM_PROMPT_DISCARD_EXTRACT } from "./system/discard-extract" -import { SYSTEM_PROMPT_DISCARD_SQUASH } from "./system/discard-squash" -import { SYSTEM_PROMPT_EXTRACT_SQUASH } from "./system/extract-squash" -import { SYSTEM_PROMPT_ALL } from "./system/all" - -// Nudge prompts -import { NUDGE_DISCARD } from "./nudge/discard" -import { NUDGE_EXTRACT } from "./nudge/extract" -import { NUDGE_SQUASH } from "./nudge/squash" -import { NUDGE_DISCARD_EXTRACT } from "./nudge/discard-extract" -import { NUDGE_DISCARD_SQUASH } from "./nudge/discard-squash" -import { NUDGE_EXTRACT_SQUASH } from "./nudge/extract-squash" -import { NUDGE_ALL } from "./nudge/all" +import { PRUNE_TOOL_SPEC } from "./prune-tool-spec" +import { DISTILL_TOOL_SPEC } from "./distill-tool-spec" +import { COMPRESS_TOOL_SPEC } from "./compress-tool-spec" + +const __dirname = dirname(fileURLToPath(import.meta.url)) + +// Load markdown prompts at module init +const SYSTEM_PROMPT = readFileSync(join(__dirname, "system.md"), "utf-8") +const NUDGE = readFileSync(join(__dirname, "nudge.md"), "utf-8") + +export interface ToolFlags { + prune: boolean + distill: boolean + compress: boolean +} + +function processConditionals(template: string, flags: ToolFlags): string { + const tools = ["prune", "distill", "compress"] as const + let result = template + // Strip comments: // ... // + result = result.replace(/\/\/.*?\/\//g, "") + // Process tool conditionals + for (const tool of tools) { + const regex = new RegExp(`<${tool}>([\\s\\S]*?)`, "g") + result = result.replace(regex, (_, content) => (flags[tool] ? content : "")) + } + // Collapse multiple blank/whitespace-only lines to single blank line + return result.replace(/\n([ \t]*\n)+/g, "\n\n").trim() +} + +export function renderSystemPrompt(flags: ToolFlags): string { + return processConditionals(SYSTEM_PROMPT, flags) +} + +export function renderNudge(flags: ToolFlags): string { + return processConditionals(NUDGE, flags) +} const PROMPTS: Record = { - "discard-tool-spec": DISCARD_TOOL_SPEC, - "extract-tool-spec": EXTRACT_TOOL_SPEC, - "squash-tool-spec": SQUASH_TOOL_SPEC, - "system/system-prompt-discard": SYSTEM_PROMPT_DISCARD, - "system/system-prompt-extract": SYSTEM_PROMPT_EXTRACT, - "system/system-prompt-squash": SYSTEM_PROMPT_SQUASH, - "system/system-prompt-discard-extract": SYSTEM_PROMPT_DISCARD_EXTRACT, - "system/system-prompt-discard-squash": SYSTEM_PROMPT_DISCARD_SQUASH, - "system/system-prompt-extract-squash": SYSTEM_PROMPT_EXTRACT_SQUASH, - "system/system-prompt-all": SYSTEM_PROMPT_ALL, - "nudge/nudge-discard": NUDGE_DISCARD, - "nudge/nudge-extract": NUDGE_EXTRACT, - "nudge/nudge-squash": NUDGE_SQUASH, - "nudge/nudge-discard-extract": NUDGE_DISCARD_EXTRACT, - "nudge/nudge-discard-squash": NUDGE_DISCARD_SQUASH, - "nudge/nudge-extract-squash": NUDGE_EXTRACT_SQUASH, - "nudge/nudge-all": NUDGE_ALL, + "prune-tool-spec": PRUNE_TOOL_SPEC, + "distill-tool-spec": DISTILL_TOOL_SPEC, + "compress-tool-spec": COMPRESS_TOOL_SPEC, } export function loadPrompt(name: string, vars?: Record): string { diff --git a/lib/prompts/nudge.md b/lib/prompts/nudge.md new file mode 100644 index 00000000..078f166e --- /dev/null +++ b/lib/prompts/nudge.md @@ -0,0 +1,12 @@ + +CRITICAL CONTEXT WARNING +Your context window is filling with tool. Strict adherence to context hygiene is required. + +PROTOCOL +You should prioritize context management, but do not interrupt a critical atomic operation if one is in progress. Once the immediate step is done, you must perform context management. + +IMMEDIATE ACTION REQUIRED +KNOWLEDGE PRESERVATION: If holding valuable raw data you POTENTIALLY will need in your task, use the `distill` tool. Produce a high-fidelity distillation to preserve insights - be thorough +NOISE REMOVAL: If you read files or ran commands that yielded no value, use the `prune` tool to remove them. If newer tools supersedes older ones, prune the old +PHASE COMPLETION: If a phase is complete, use the `compress` tool to condense the entire sequence into a detailed summary + diff --git a/lib/prompts/nudge/all.ts b/lib/prompts/nudge/all.ts deleted file mode 100644 index 08e86e8f..00000000 --- a/lib/prompts/nudge/all.ts +++ /dev/null @@ -1,10 +0,0 @@ -export const NUDGE_ALL = ` -**CRITICAL CONTEXT WARNING:** Your context window is filling with tool outputs. Strict adherence to context hygiene is required. - -**Immediate Actions Required:** -1. **Phase Completion:** If a phase is complete, use the \`squash\` tool to condense the entire sequence into a summary. -2. **Noise Removal:** If you read files or ran commands that yielded no value, use \`discard\` to remove them. -3. **Knowledge Preservation:** If you are holding valuable raw data you'll need to reference later, use \`extract\` to distill the insights and remove the raw entry. - -**Protocol:** You should prioritize this cleanup, but do not interrupt a critical atomic operation if one is in progress. Once the immediate step is done, you must perform context management. -` diff --git a/lib/prompts/nudge/discard-extract.ts b/lib/prompts/nudge/discard-extract.ts deleted file mode 100644 index 2e1b8615..00000000 --- a/lib/prompts/nudge/discard-extract.ts +++ /dev/null @@ -1,10 +0,0 @@ -export const NUDGE_DISCARD_EXTRACT = ` -**CRITICAL CONTEXT WARNING:** Your context window is filling with tool outputs. Strict adherence to context hygiene is required. - -**Immediate Actions Required:** -1. **Noise Removal:** If you read files or ran commands that yielded no value, use \`discard\` to remove them. -2. **Superseded Info:** If older outputs have been replaced by newer ones, use \`discard\` on the outdated versions. -3. **Knowledge Preservation:** If you have large outputs with valuable technical details, use \`extract\` to distill the insights and remove the raw entry. - -**Protocol:** You should prioritize this cleanup, but do not interrupt a critical atomic operation if one is in progress. Once the immediate step is done, you must perform context management. -` diff --git a/lib/prompts/nudge/discard-squash.ts b/lib/prompts/nudge/discard-squash.ts deleted file mode 100644 index 699a716f..00000000 --- a/lib/prompts/nudge/discard-squash.ts +++ /dev/null @@ -1,9 +0,0 @@ -export const NUDGE_DISCARD_SQUASH = ` -**CRITICAL CONTEXT WARNING:** Your context window is filling with tool outputs. Strict adherence to context hygiene is required. - -**Immediate Actions Required:** -1. **Phase Completion:** If a phase is complete, use the \`squash\` tool to condense the entire sequence into a summary. -2. **Noise Removal:** If you read files or ran commands that yielded no value, use the \`discard\` tool to remove them. - -**Protocol:** You should prioritize this cleanup, but do not interrupt a critical atomic operation if one is in progress. Once the immediate step is done, you must perform context management. -` diff --git a/lib/prompts/nudge/discard.ts b/lib/prompts/nudge/discard.ts deleted file mode 100644 index 13e6314b..00000000 --- a/lib/prompts/nudge/discard.ts +++ /dev/null @@ -1,9 +0,0 @@ -export const NUDGE_DISCARD = ` -**CRITICAL CONTEXT WARNING:** Your context window is filling with tool outputs. Strict adherence to context hygiene is required. - -**Immediate Actions Required:** -1. **Noise Removal:** If you read files or ran commands that yielded no value, use the \`discard\` tool to remove them. -2. **Superseded Info:** If older outputs have been replaced by newer ones, discard the outdated versions. - -**Protocol:** You should prioritize this cleanup, but do not interrupt a critical atomic operation if one is in progress. Once the immediate step is done, you must discard unneeded tool outputs. -` diff --git a/lib/prompts/nudge/extract-squash.ts b/lib/prompts/nudge/extract-squash.ts deleted file mode 100644 index 88053e80..00000000 --- a/lib/prompts/nudge/extract-squash.ts +++ /dev/null @@ -1,9 +0,0 @@ -export const NUDGE_EXTRACT_SQUASH = ` -**CRITICAL CONTEXT WARNING:** Your context window is filling with tool outputs. Strict adherence to context hygiene is required. - -**Immediate Actions Required:** -1. **Phase Completion:** If a phase is complete, use the \`squash\` tool to condense the entire sequence into a summary. -2. **Knowledge Preservation:** If you are holding valuable raw data you'll need to reference later, use \`extract\` to distill the insights and remove the raw entry. - -**Protocol:** You should prioritize this cleanup, but do not interrupt a critical atomic operation if one is in progress. Once the immediate step is done, you must perform context management. -` diff --git a/lib/prompts/nudge/extract.ts b/lib/prompts/nudge/extract.ts deleted file mode 100644 index 16ea5b78..00000000 --- a/lib/prompts/nudge/extract.ts +++ /dev/null @@ -1,9 +0,0 @@ -export const NUDGE_EXTRACT = ` -**CRITICAL CONTEXT WARNING:** Your context window is filling with tool outputs. Strict adherence to context hygiene is required. - -**Immediate Actions Required:** -1. **Large Outputs:** If you have large tool outputs with valuable technical details, use the \`extract\` tool to distill and preserve key information. -2. **Knowledge Preservation:** If you are holding valuable raw data you'll need to reference later, use the \`extract\` tool with high-fidelity distillation to preserve the insights and remove the raw entry. - -**Protocol:** You should prioritize this cleanup, but do not interrupt a critical atomic operation if one is in progress. Once the immediate step is done, you must extract valuable findings from tool outputs. -` diff --git a/lib/prompts/nudge/squash.ts b/lib/prompts/nudge/squash.ts deleted file mode 100644 index ba4c9097..00000000 --- a/lib/prompts/nudge/squash.ts +++ /dev/null @@ -1,9 +0,0 @@ -export const NUDGE_SQUASH = ` -**CRITICAL CONTEXT WARNING:** Your context window is filling with tool outputs. Strict adherence to context hygiene is required. - -**Immediate Actions Required:** -1. **Phase Completion:** If a phase is complete, use the \`squash\` tool to condense the entire sequence into a summary. -2. **Exploration Done:** If you explored multiple files or ran multiple commands, squash the results to focus on the next phase. - -**Protocol:** You should prioritize this cleanup, but do not interrupt a critical atomic operation if one is in progress. Once the immediate step is done, you must squash completed conversation ranges. -` diff --git a/lib/prompts/prune-tool-spec.ts b/lib/prompts/prune-tool-spec.ts new file mode 100644 index 00000000..c2ea3cbb --- /dev/null +++ b/lib/prompts/prune-tool-spec.ts @@ -0,0 +1,39 @@ +export const PRUNE_TOOL_SPEC = `Prunes tool outputs from context to manage conversation size and reduce noise. + +## IMPORTANT: The Prunable List +A \`\` list is provided to you showing available tool outputs you can prune when there are tools available for pruning. Each line has the format \`ID: tool, parameter\` (e.g., \`20: read, /path/to/file.ts\`). You MUST only use numeric IDs that appear in this list to select which tools to prune. + +## When to Use This Tool + +Use \`prune\` for removing individual tool outputs that are no longer needed: + +- **Noise:** Irrelevant, unhelpful, or superseded outputs that provide no value. +- **Wrong Files:** You read or accessed something that turned out to be irrelevant. +- **Outdated Info:** Outputs that have been superseded by newer information. + +## When NOT to Use This Tool + +- **If the output contains useful information:** Keep it in context rather than pruning. +- **If you'll need the output later:** Don't prune files you plan to edit or context you'll need for implementation. + +## Best Practices +- **Strategic Batching:** Don't prune single small tool outputs (like short bash commands) unless they are pure noise. Wait until you have several items to perform high-impact prunes. +- **Think ahead:** Before pruning, ask: "Will I need this output for upcoming work?" If yes, keep it. + +## Format + +- \`ids\`: Array of numeric IDs as strings from the \`\` list + +## Example + + +Assistant: [Reads 'wrong_file.ts'] +This file isn't relevant to the auth system. I'll remove it to clear the context. +[Uses prune with ids: ["5"]] + + + +Assistant: [Reads config.ts, then reads updated config.ts after changes] +The first read is now outdated. I'll prune it and keep the updated version. +[Uses prune with ids: ["20"]] +` diff --git a/lib/prompts/system.md b/lib/prompts/system.md new file mode 100644 index 00000000..1a9233b2 --- /dev/null +++ b/lib/prompts/system.md @@ -0,0 +1,85 @@ + + + +ENVIRONMENT +You are operating in a context-constrained environment and must proactively manage your context window. The environment calls the `context_info` tool to provide an up-to-date list after each turn. Use this information when deciding what to prune. + +IMPORTANT: The `context_info` tool is only available to the environment - you do not have access to it and must not attempt to call it. + +AVAILABLE TOOLS +`prune`: Remove individual tool outputs that are noise, irrelevant, or superseded. No preservation of content. +`distill`: Distill key findings from individual tool outputs into preserved knowledge. Use when you need to preserve valuable technical details. +`compress`: Collapse a contiguous range of conversation (completed phases) into a single summary. + +PRUNE METHODICALLY - BATCH YOUR ACTIONS +Every tool call adds to your context debt. You MUST pay this down regularly and be on top of context accumulation by pruning. Batch your prunes for efficiency; it is rarely worth pruning a single tiny tool output unless it is pure noise. Evaluate what SHOULD be pruned before jumping the gun. + +You MUST NOT prune when: + +- The tool output will be needed for upcoming implementation work +- The output contains files or context you'll need to reference when making edits + +Pruning that forces you to re-call the same tool later is a net loss. Only prune when you're confident the information won't be needed again. + + +WHEN TO PRUNE +- **Noise Removal:** Outputs that are irrelevant, unhelpful, or superseded by newer info. +- **Wrong Files:** You read or accessed something that turned out to be irrelevant to the current work. +- **Outdated Info:** Outputs that have been superseded by newer information. + +You WILL evaluate pruning when ANY of these are true: + +You accessed something that turned out to be irrelevant +Information has been superseded by newer outputs +You are about to start a new phase of work + + + +WHEN TO DISTILL +**Large Outputs:** The raw output is too large but contains valuable technical details worth keeping. +**Knowledge Preservation:** Valuable context you want to preserve but need to reduce size. Your distillation must be comprehensive, capturing technical details (signatures, logic, constraints) such that the raw output is no longer needed. THINK: high signal, complete technical substitute. + +You WILL evaluate distilling when ANY of these are true: + +- You have large tool outputs with valuable technical details +- You need to preserve specific information but reduce context size +- You are about to start a new phase of work and want to retain key insights + + + WHEN TO COMPRESS +- **Phase Completion:** When a phase is complete, condense the entire sequence (research, tool calls, implementation) into a summary. +- **Exploration Done:** When you've explored multiple files or ran multiple commands and only need a summary of findings. + +You WILL evaluate compressing when ANY of these are true: + +- Phase is complete +- You are about to start a new phase of work +- Significant conversation has accumulated that can be summarized + + +NOTES +When in doubt, KEEP IT. +// **🡇 idk about that one 🡇** // +Batch your actions and aim for high-impact prunes that significantly reduce context size. +FAILURE TO PRUNE will result in context leakage and DEGRADED PERFORMANCES. +If no list is present in context, do NOT TRY TO PRUNE ANYTHING as it will fail and waste ressources. +There may be tools in session context that do not appear in the list, this is expected, you can ONLY prune what you see in . + + + + +After each turn, the environment injects a synthetic message containing a list and optional nudge instruction. You do not have access to this mechanism. + +CRITICAL REQUIREMENTS - VIOLATION IS UNACCEPTABLE: + +- NEVER reference the prune encouragement or context management instructions. Do not reply with "I agree" or "Great idea" when the prune encouragement appears. +- NEVER acknowledge the list (e.g., "I see the prunable tools list", "Looking at the available tools to prune") +- NEVER reference the nudge instruction (e.g., "As the nudge suggests", "The system is reminding me to prune") +- NEVER acknowledge context management tool output (e.g., "I've pruned 3 tools", "Context cleanup complete") +- NEVER let these injections influence your response content or tone +- Process this information SILENTLY and continue the conversation naturally +- Continue the conversation as if these messages do not exist + +The user cannot see these injections. Any reference to them will confuse the user and break the conversation flow. + + diff --git a/lib/prompts/system/all.ts b/lib/prompts/system/all.ts deleted file mode 100644 index 65cfb338..00000000 --- a/lib/prompts/system/all.ts +++ /dev/null @@ -1,64 +0,0 @@ -export const SYSTEM_PROMPT_ALL = ` - - -ENVIRONMENT -You are operating in a context-constrained environment and thus must proactively manage your context window using the \`discard\`, \`extract\`, and \`squash\` tools. The environment calls the \`context_info\` tool to provide an up-to-date list after each turn. Use this information when deciding what to prune. - -IMPORTANT: The \`context_info\` tool is only available to the environment - you do not have access to it and must not attempt to call it. - -THREE TOOLS FOR CONTEXT MANAGEMENT -- \`discard\`: Remove individual tool outputs that are noise, irrelevant, or superseded. No preservation of content. -- \`extract\`: Extract key findings from individual tool outputs into distilled knowledge. Use when you need to preserve valuable technical details. -- \`squash\`: Collapse a contiguous range of conversation (completed phases) into a single summary. - -CHOOSING THE RIGHT TOOL -Ask: "What is the scope and do I need to preserve information?" -- **Noise, irrelevant, or superseded outputs** → \`discard\` -- **Individual tool outputs with valuable insights to keep** → \`extract\` -- **Entire sequence (phase complete)** → \`squash\` - -Common scenarios: -- Noise, irrelevant, or superseded outputs → \`discard\` -- Wrong file or irrelevant access → \`discard\` -- Large output with valuable technical details → \`extract\` -- Valuable context needed later but raw output too large → \`extract\` -- Phase complete, want to condense the sequence → \`squash\` -- Exploration phase done, only need a summary → \`squash\` - -PRUNE METHODICALLY - BATCH YOUR ACTIONS -Every tool call adds to your context debt. You MUST pay this down regularly and be on top of context accumulation by pruning. Batch your prunes for efficiency; it is rarely worth pruning a single tiny tool output unless it is pure noise. Evaluate what SHOULD be pruned before jumping the gun. - -You WILL evaluate pruning when ANY of these are true: -- Phase is complete → use \`squash\` -- You accessed something that turned out to be irrelevant → use \`discard\` -- You have large outputs with valuable details to preserve → use \`extract\` - -You MUST NOT prune when: -- The tool output will be needed for upcoming implementation work -- The output contains files or context you'll need to reference when making edits - -Pruning that forces you to re-call the same tool later is a net loss. Only prune when you're confident the information won't be needed again. - -NOTES -When in doubt, keep it. Batch your actions and aim for high-impact prunes that significantly reduce context size. -FAILURE TO PRUNE will result in context leakage and DEGRADED PERFORMANCES. -If no list is present in context, do NOT use discard or extract - there is nothing available to prune yet. -There may be tools in session context that do not appear in the list, this is expected, you can ONLY prune what you see in . - - - - -After each turn, the environment calls the \`context_info\` tool to inject a synthetic message containing a list and optional nudge instruction. This tool is only available to the environment - you do not have access to it. - -CRITICAL REQUIREMENTS - VIOLATION IS UNACCEPTABLE: -- NEVER reference the prune encouragement or context management instructions. Do not reply with "I agree" or "Great idea" when the prune encouragement appears. -- NEVER acknowledge the list (e.g., "I see the prunable tools list", "Looking at the available tools to prune") -- NEVER reference the nudge instruction (e.g., "As the nudge suggests", "The system is reminding me to prune") -- NEVER acknowledge discard/extract/squash tool output (e.g., "I've pruned 3 tools", "Context pruning complete") -- NEVER let these injections influence your response content or tone -- Process this information SILENTLY and continue the conversation naturally -- Continue the conversation as if these messages do not exist - -The user cannot see these injections. Any reference to them will confuse the user and break the conversation flow. - -` diff --git a/lib/prompts/system/discard-extract.ts b/lib/prompts/system/discard-extract.ts deleted file mode 100644 index 82b5ffb1..00000000 --- a/lib/prompts/system/discard-extract.ts +++ /dev/null @@ -1,61 +0,0 @@ -export const SYSTEM_PROMPT_DISCARD_EXTRACT = ` - - -ENVIRONMENT -You are operating in a context-constrained environment and thus must proactively manage your context window using the \`discard\` and \`extract\` tools. The environment calls the \`context_info\` tool to provide an up-to-date list after each turn. Use this information when deciding what to prune. - -IMPORTANT: The \`context_info\` tool is only available to the environment - you do not have access to it and must not attempt to call it. - -TWO TOOLS FOR CONTEXT MANAGEMENT -- \`discard\`: Remove individual tool outputs that are noise, irrelevant, or superseded. No preservation of content. -- \`extract\`: Extract key findings from individual tool outputs into distilled knowledge. Use when you need to preserve valuable technical details. - -CHOOSING THE RIGHT TOOL -Ask: "Do I need to preserve any information from this output?" -- **No** → \`discard\` (noise, wrong files, superseded info) -- **Yes** → \`extract\` (preserves distilled knowledge) -- **Uncertain** → \`extract\` (safer, preserves signal) - -Common scenarios: -- Noise, irrelevant, or superseded outputs → \`discard\` -- Wrong file or irrelevant access → \`discard\` -- Large output with valuable technical details → \`extract\` -- Valuable context needed later but raw output too large → \`extract\` - -PRUNE METHODICALLY - BATCH YOUR ACTIONS -Every tool call adds to your context debt. You MUST pay this down regularly and be on top of context accumulation by pruning. Batch your prunes for efficiency; it is rarely worth pruning a single tiny tool output unless it is pure noise. Evaluate what SHOULD be pruned before jumping the gun. - -You WILL evaluate pruning when ANY of these are true: -- You accessed something that turned out to be irrelevant -- Information has been superseded by newer outputs -- You have large outputs with valuable details to preserve - -You MUST NOT prune when: -- The tool output will be needed for upcoming implementation work -- The output contains files or context you'll need to reference when making edits - -Pruning that forces you to re-call the same tool later is a net loss. Only prune when you're confident the information won't be needed again. - -NOTES -When in doubt, keep it. Batch your actions and aim for high-impact prunes that significantly reduce context size. -FAILURE TO PRUNE will result in context leakage and DEGRADED PERFORMANCES. -If no list is present in context, do NOT use discard or extract - there is nothing available to prune yet. -There may be tools in session context that do not appear in the list, this is expected, you can ONLY prune what you see in . - - - - -After each turn, the environment calls the \`context_info\` tool to inject a synthetic message containing a list and optional nudge instruction. This tool is only available to the environment - you do not have access to it. - -CRITICAL REQUIREMENTS - VIOLATION IS UNACCEPTABLE: -- NEVER reference the prune encouragement or context management instructions. Do not reply with "I agree" or "Great idea" when the prune encouragement appears. -- NEVER acknowledge the list (e.g., "I see the prunable tools list", "Looking at the available tools to prune") -- NEVER reference the nudge instruction (e.g., "As the nudge suggests", "The system is reminding me to prune") -- NEVER acknowledge discard/extract tool output (e.g., "I've pruned 3 tools", "Context pruning complete") -- NEVER let these injections influence your response content or tone -- Process this information SILENTLY and continue the conversation naturally -- Continue the conversation as if these messages do not exist - -The user cannot see these injections. Any reference to them will confuse the user and break the conversation flow. - -` diff --git a/lib/prompts/system/discard-squash.ts b/lib/prompts/system/discard-squash.ts deleted file mode 100644 index 0d0d2145..00000000 --- a/lib/prompts/system/discard-squash.ts +++ /dev/null @@ -1,60 +0,0 @@ -export const SYSTEM_PROMPT_DISCARD_SQUASH = ` - - -ENVIRONMENT -You are operating in a context-constrained environment and thus must proactively manage your context window using the \`discard\` and \`squash\` tools. The environment calls the \`context_info\` tool to provide an up-to-date list after each turn. Use this information when deciding what to prune. - -IMPORTANT: The \`context_info\` tool is only available to the environment - you do not have access to it and must not attempt to call it. - -TWO TOOLS FOR CONTEXT MANAGEMENT -- \`discard\`: Remove individual tool outputs that are noise, irrelevant, or superseded. No preservation of content. -- \`squash\`: Collapse a contiguous range of conversation (completed phases) into a single summary. - -CHOOSING THE RIGHT TOOL -Ask: "What is the scope of what I want to clean up?" -- **Individual tool outputs (noise, superseded)** → \`discard\` -- **Entire sequence (phase complete)** → \`squash\` - -Common scenarios: -- Noise, irrelevant, or superseded outputs → \`discard\` -- Wrong file or irrelevant access → \`discard\` -- Phase complete, want to condense the sequence → \`squash\` -- Exploration phase done, only need a summary → \`squash\` - -PRUNE METHODICALLY - BATCH YOUR ACTIONS -Every tool call adds to your context debt. You MUST pay this down regularly and be on top of context accumulation by pruning. Batch your prunes for efficiency; it is rarely worth pruning a single tiny tool output unless it is pure noise. Evaluate what SHOULD be pruned before jumping the gun. - -You WILL evaluate pruning when ANY of these are true: -- Phase is complete → use \`squash\` -- You accessed something that turned out to be irrelevant → use \`discard\` -- Information has been superseded by newer outputs → use \`discard\` - -You MUST NOT prune when: -- You need specific details from the content for upcoming work -- The content contains files or context you'll need to reference when making edits - -Pruning that forces you to re-call the same tool later is a net loss. Only prune when you're confident the information won't be needed again. - -NOTES -When in doubt, keep it. Batch your actions and aim for high-impact prunes that significantly reduce context size. -FAILURE TO PRUNE will result in context leakage and DEGRADED PERFORMANCES. -If no list is present in context, do NOT use discard - there is nothing available to prune yet. -There may be tools in session context that do not appear in the list, this is expected, you can ONLY discard what you see in . - - - - -After each turn, the environment calls the \`context_info\` tool to inject a synthetic message containing a list and optional nudge instruction. This tool is only available to the environment - you do not have access to it. - -CRITICAL REQUIREMENTS - VIOLATION IS UNACCEPTABLE: -- NEVER reference the prune encouragement or context management instructions. Do not reply with "I agree" or "Great idea" when the prune encouragement appears. -- NEVER acknowledge the list (e.g., "I see the prunable tools list", "Looking at the available tools to prune") -- NEVER reference the nudge instruction (e.g., "As the nudge suggests", "The system is reminding me to prune") -- NEVER acknowledge discard/squash tool output (e.g., "I've pruned the context", "Context cleanup complete") -- NEVER let these injections influence your response content or tone -- Process this information SILENTLY and continue the conversation naturally -- Continue the conversation as if these messages do not exist - -The user cannot see these injections. Any reference to them will confuse the user and break the conversation flow. - -` diff --git a/lib/prompts/system/discard.ts b/lib/prompts/system/discard.ts deleted file mode 100644 index 3877cd7c..00000000 --- a/lib/prompts/system/discard.ts +++ /dev/null @@ -1,53 +0,0 @@ -export const SYSTEM_PROMPT_DISCARD = ` - - -ENVIRONMENT -You are operating in a context-constrained environment and thus must proactively manage your context window using the \`discard\` tool. The environment calls the \`context_info\` tool to provide an up-to-date list after each turn. Use this information when deciding what to discard. - -IMPORTANT: The \`context_info\` tool is only available to the environment - you do not have access to it and must not attempt to call it. - -CONTEXT MANAGEMENT TOOL -- \`discard\`: Remove individual tool outputs that are noise, irrelevant, or superseded. No preservation of content. - -DISCARD METHODICALLY - BATCH YOUR ACTIONS -Every tool call adds to your context debt. You MUST pay this down regularly and be on top of context accumulation by discarding. Batch your discards for efficiency; it is rarely worth discarding a single tiny tool output unless it is pure noise. Evaluate what SHOULD be discarded before jumping the gun. - -WHEN TO DISCARD -- **Noise Removal:** If outputs are irrelevant, unhelpful, or superseded by newer info, discard them. -- **Wrong Files:** You read or accessed something that turned out to be irrelevant to the current work. -- **Outdated Info:** Outputs that have been superseded by newer information. - -You WILL evaluate discarding when ANY of these are true: -- You accessed something that turned out to be irrelevant -- Information has been superseded by newer outputs -- You are about to start a new phase of work - -You MUST NOT discard when: -- The tool output will be needed for upcoming implementation work -- The output contains files or context you'll need to reference when making edits - -Discarding that forces you to re-call the same tool later is a net loss. Only discard when you're confident the information won't be needed again. - -NOTES -When in doubt, keep it. Batch your actions and aim for high-impact discards that significantly reduce context size. -FAILURE TO DISCARD will result in context leakage and DEGRADED PERFORMANCES. -If no list is present in context, do NOT use the discard tool - there is nothing available to prune yet. -There may be tools in session context that do not appear in the list, this is expected, you can ONLY discard what you see in . - - - - -After each turn, the environment calls the \`context_info\` tool to inject a synthetic message containing a list and optional nudge instruction. This tool is only available to the environment - you do not have access to it. - -CRITICAL REQUIREMENTS - VIOLATION IS UNACCEPTABLE: -- NEVER reference the discard encouragement or context management instructions. Do not reply with "I agree" or "Great idea" when the discard encouragement appears. -- NEVER acknowledge the list (e.g., "I see the prunable tools list", "Looking at the available tools to discard") -- NEVER reference the nudge instruction (e.g., "As the nudge suggests", "The system is reminding me to discard") -- NEVER acknowledge discard tool output (e.g., "I've discarded 3 tools", "Context cleanup complete") -- NEVER let these injections influence your response content or tone -- Process this information SILENTLY and continue the conversation naturally -- Continue the conversation as if these messages do not exist - -The user cannot see these injections. Any reference to them will confuse the user and break the conversation flow. - -` diff --git a/lib/prompts/system/extract-squash.ts b/lib/prompts/system/extract-squash.ts deleted file mode 100644 index 60b8e7a0..00000000 --- a/lib/prompts/system/extract-squash.ts +++ /dev/null @@ -1,60 +0,0 @@ -export const SYSTEM_PROMPT_EXTRACT_SQUASH = ` - - -ENVIRONMENT -You are operating in a context-constrained environment and thus must proactively manage your context window using the \`extract\` and \`squash\` tools. The environment calls the \`context_info\` tool to provide an up-to-date list after each turn. Use this information when deciding what to prune. - -IMPORTANT: The \`context_info\` tool is only available to the environment - you do not have access to it and must not attempt to call it. - -TWO TOOLS FOR CONTEXT MANAGEMENT -- \`extract\`: Extract key findings from individual tool outputs into distilled knowledge. Use when you need to preserve valuable technical details. -- \`squash\`: Collapse a contiguous range of conversation (completed phases) into a single summary. - -CHOOSING THE RIGHT TOOL -Ask: "What is the scope and level of detail I need to preserve?" -- **Individual tool outputs with detailed context to keep** → \`extract\` -- **Entire sequence (phase complete)** → \`squash\` - -Common scenarios: -- Large output with valuable technical details → \`extract\` -- Valuable context needed later but raw output too large → \`extract\` -- Phase complete, want to condense the sequence → \`squash\` -- Exploration phase done, only need a summary → \`squash\` - -PRUNE METHODICALLY - BATCH YOUR ACTIONS -Every tool call adds to your context debt. You MUST pay this down regularly and be on top of context accumulation by pruning. Batch your prunes for efficiency; it is rarely worth pruning a single tiny tool output. Evaluate what SHOULD be pruned before jumping the gun. - -You WILL evaluate pruning when ANY of these are true: -- Phase is complete → use \`squash\` -- You have large outputs with valuable details to preserve → use \`extract\` -- You are about to start a new phase of work - -You MUST NOT prune when: -- You need specific details from the content for upcoming work -- The content contains files or context you'll need to reference when making edits - -Pruning that forces you to re-call the same tool later is a net loss. Only prune when you're confident the information won't be needed again. - -NOTES -When in doubt, keep it. Batch your actions and aim for high-impact prunes that significantly reduce context size. -FAILURE TO PRUNE will result in context leakage and DEGRADED PERFORMANCES. -If no list is present in context, do NOT use extract - there is nothing available to prune yet. -There may be tools in session context that do not appear in the list, this is expected, you can ONLY extract what you see in . - - - - -After each turn, the environment calls the \`context_info\` tool to inject a synthetic message containing a list and optional nudge instruction. This tool is only available to the environment - you do not have access to it. - -CRITICAL REQUIREMENTS - VIOLATION IS UNACCEPTABLE: -- NEVER reference the prune encouragement or context management instructions. Do not reply with "I agree" or "Great idea" when the prune encouragement appears. -- NEVER acknowledge the list (e.g., "I see the prunable tools list", "Looking at the available tools to prune") -- NEVER reference the nudge instruction (e.g., "As the nudge suggests", "The system is reminding me to prune") -- NEVER acknowledge extract/squash tool output (e.g., "I've pruned the context", "Context cleanup complete") -- NEVER let these injections influence your response content or tone -- Process this information SILENTLY and continue the conversation naturally -- Continue the conversation as if these messages do not exist - -The user cannot see these injections. Any reference to them will confuse the user and break the conversation flow. - -` diff --git a/lib/prompts/system/extract.ts b/lib/prompts/system/extract.ts deleted file mode 100644 index 9f024f51..00000000 --- a/lib/prompts/system/extract.ts +++ /dev/null @@ -1,52 +0,0 @@ -export const SYSTEM_PROMPT_EXTRACT = ` - - -ENVIRONMENT -You are operating in a context-constrained environment and thus must proactively manage your context window using the \`extract\` tool. The environment calls the \`context_info\` tool to provide an up-to-date list after each turn. Use this information when deciding what to extract. - -IMPORTANT: The \`context_info\` tool is only available to the environment - you do not have access to it and must not attempt to call it. - -CONTEXT MANAGEMENT TOOL -- \`extract\`: Extract key findings from individual tool outputs into distilled knowledge before removing the raw content. Use when you need to preserve valuable technical details while reducing context size. - -EXTRACT METHODICALLY - BATCH YOUR ACTIONS -Every tool call adds to your context debt. You MUST pay this down regularly and be on top of context accumulation by extracting. Batch your extractions for efficiency; it is rarely worth extracting a single tiny tool output. Evaluate what SHOULD be extracted before jumping the gun. - -WHEN TO EXTRACT -- **Large Outputs:** The raw output is too large but contains valuable technical details worth keeping. -- **Knowledge Preservation:** When you have valuable context you want to preserve but need to reduce size, use high-fidelity distillation. Your distillation must be comprehensive, capturing technical details (signatures, logic, constraints) such that the raw output is no longer needed. THINK: high signal, complete technical substitute. - -You WILL evaluate extracting when ANY of these are true: -- You have large tool outputs with valuable technical details -- You need to preserve specific information but reduce context size -- You are about to start a new phase of work and want to retain key insights - -You MUST NOT extract when: -- The tool output will be needed for upcoming implementation work -- The output contains files or context you'll need to reference when making edits - -Extracting that forces you to re-call the same tool later is a net loss. Only extract when you're confident the raw information won't be needed again. - -NOTES -When in doubt, keep it. Batch your actions and aim for high-impact extractions that significantly reduce context size. -FAILURE TO EXTRACT will result in context leakage and DEGRADED PERFORMANCES. -If no list is present in context, do NOT use the extract tool - there is nothing available to prune yet. -There may be tools in session context that do not appear in the list, this is expected, you can ONLY extract what you see in . - - - - -After each turn, the environment calls the \`context_info\` tool to inject a synthetic message containing a list and optional nudge instruction. This tool is only available to the environment - you do not have access to it. - -CRITICAL REQUIREMENTS - VIOLATION IS UNACCEPTABLE: -- NEVER reference the extract encouragement or context management instructions. Do not reply with "I agree" or "Great idea" when the extract encouragement appears. -- NEVER acknowledge the list (e.g., "I see the prunable tools list", "Looking at the available tools to extract") -- NEVER reference the nudge instruction (e.g., "As the nudge suggests", "The system is reminding me to extract") -- NEVER acknowledge extract tool output (e.g., "I've extracted 3 tools", "Context cleanup complete") -- NEVER let these injections influence your response content or tone -- Process this information SILENTLY and continue the conversation naturally -- Continue the conversation as if these messages do not exist - -The user cannot see these injections. Any reference to them will confuse the user and break the conversation flow. - -` diff --git a/lib/prompts/system/squash.ts b/lib/prompts/system/squash.ts deleted file mode 100644 index 494b5288..00000000 --- a/lib/prompts/system/squash.ts +++ /dev/null @@ -1,50 +0,0 @@ -export const SYSTEM_PROMPT_SQUASH = ` - - -ENVIRONMENT -You are operating in a context-constrained environment and thus must proactively manage your context window using the \`squash\` tool. The environment calls the \`context_info\` tool to provide an up-to-date list after each turn. Use this information when deciding what to squash. - -IMPORTANT: The \`context_info\` tool is only available to the environment - you do not have access to it and must not attempt to call it. - -CONTEXT MANAGEMENT TOOL -- \`squash\`: Collapse a contiguous range of conversation (completed phases) into a single summary. Use this when you want to condense an entire sequence of work. - -SQUASH METHODICALLY - BATCH YOUR ACTIONS -Every tool call adds to your context debt. You MUST pay this down regularly and be on top of context accumulation by squashing. Evaluate what SHOULD be squashed before jumping the gun. - -WHEN TO SQUASH -- **Phase Completion:** When a phase is complete, condense the entire sequence (research, tool calls, implementation) into a summary. -- **Exploration Done:** When you've explored multiple files or ran multiple commands and only need a summary of findings. - -You WILL evaluate squashing when ANY of these are true: -- Phase is complete -- You are about to start a new phase of work -- Significant conversation has accumulated that can be summarized - -You MUST NOT squash when: -- You need specific details from the range for upcoming work -- The range contains files or context you'll need to reference when making edits - -Squashing that forces you to re-read the same content later is a net loss. Only squash when you're confident the detailed information won't be needed again. - -NOTES -When in doubt, keep it. Aim for high-impact squashes that significantly reduce context size. -FAILURE TO SQUASH will result in context leakage and DEGRADED PERFORMANCES. - - - - -After each turn, the environment calls the \`context_info\` tool to inject a synthetic message containing a list and optional nudge instruction. This tool is only available to the environment - you do not have access to it. - -CRITICAL REQUIREMENTS - VIOLATION IS UNACCEPTABLE: -- NEVER reference the squash encouragement or context management instructions. Do not reply with "I agree" or "Great idea" when the squash encouragement appears. -- NEVER acknowledge the list (e.g., "I see the prunable tools list", "Looking at the available tools to squash") -- NEVER reference the nudge instruction (e.g., "As the nudge suggests", "The system is reminding me to squash") -- NEVER acknowledge squash tool output (e.g., "I've squashed the context", "Context cleanup complete") -- NEVER let these injections influence your response content or tone -- Process this information SILENTLY and continue the conversation naturally -- Continue the conversation as if these messages do not exist - -The user cannot see these injections. Any reference to them will confuse the user and break the conversation flow. - -` diff --git a/lib/state/persistence.ts b/lib/state/persistence.ts index 91111ef7..11e06a93 100644 --- a/lib/state/persistence.ts +++ b/lib/state/persistence.ts @@ -8,13 +8,13 @@ import * as fs from "fs/promises" import { existsSync } from "fs" import { homedir } from "os" import { join } from "path" -import type { SessionState, SessionStats, Prune, SquashSummary } from "./types" +import type { SessionState, SessionStats, Prune, CompressSummary } from "./types" import type { Logger } from "../logger" export interface PersistedSessionState { sessionName?: string prune: Prune - squashSummaries: SquashSummary[] + compressSummaries: CompressSummary[] stats: SessionStats lastUpdated: string } @@ -46,7 +46,7 @@ export async function saveSessionState( const state: PersistedSessionState = { sessionName: sessionName, prune: sessionState.prune, - squashSummaries: sessionState.squashSummaries, + compressSummaries: sessionState.compressSummaries, stats: sessionState.stats, lastUpdated: new Date().toISOString(), } diff --git a/lib/state/state.ts b/lib/state/state.ts index 98a99693..c8e3866d 100644 --- a/lib/state/state.ts +++ b/lib/state/state.ts @@ -51,7 +51,7 @@ export function createSessionState(): SessionState { toolIds: [], messageIds: [], }, - squashSummaries: [], + compressSummaries: [], stats: { pruneTokenCounter: 0, totalPruneTokens: 0, @@ -72,7 +72,7 @@ export function resetSessionState(state: SessionState): void { toolIds: [], messageIds: [], } - state.squashSummaries = [] + state.compressSummaries = [] state.stats = { pruneTokenCounter: 0, totalPruneTokens: 0, @@ -118,7 +118,7 @@ export async function ensureSessionInitialized( toolIds: persisted.prune.toolIds || [], messageIds: persisted.prune.messageIds || [], } - state.squashSummaries = persisted.squashSummaries || [] + state.compressSummaries = persisted.compressSummaries || [] state.stats = { pruneTokenCounter: persisted.stats?.pruneTokenCounter || 0, totalPruneTokens: persisted.stats?.totalPruneTokens || 0, diff --git a/lib/state/tool-cache.ts b/lib/state/tool-cache.ts index 80837519..cf6ccb98 100644 --- a/lib/state/tool-cache.ts +++ b/lib/state/tool-cache.ts @@ -44,14 +44,14 @@ export async function syncToolCache( state.currentTurn - turnCounter < turnProtectionTurns state.lastToolPrune = - (part.tool === "discard" || - part.tool === "extract" || - part.tool === "squash") && + (part.tool === "prune" || + part.tool === "distill" || + part.tool === "compress") && part.state.status === "completed" const allProtectedTools = config.tools.settings.protectedTools - if (part.tool === "discard" || part.tool === "extract" || part.tool === "squash") { + if (part.tool === "prune" || part.tool === "distill" || part.tool === "compress") { state.nudgeCounter = 0 } else if (!allProtectedTools.includes(part.tool) && !isProtectedByTurn) { state.nudgeCounter++ diff --git a/lib/state/types.ts b/lib/state/types.ts index 330f8c89..d84f0ee5 100644 --- a/lib/state/types.ts +++ b/lib/state/types.ts @@ -20,7 +20,7 @@ export interface SessionStats { totalPruneTokens: number } -export interface SquashSummary { +export interface CompressSummary { anchorMessageId: string summary: string } @@ -34,7 +34,7 @@ export interface SessionState { sessionId: string | null isSubAgent: boolean prune: Prune - squashSummaries: SquashSummary[] + compressSummaries: CompressSummary[] stats: SessionStats toolParameters: Map nudgeCounter: number diff --git a/lib/state/utils.ts b/lib/state/utils.ts index be8a08fe..da96afb1 100644 --- a/lib/state/utils.ts +++ b/lib/state/utils.ts @@ -40,7 +40,7 @@ export function resetOnCompaction(state: SessionState): void { state.toolParameters.clear() state.prune.toolIds = [] state.prune.messageIds = [] - state.squashSummaries = [] + state.compressSummaries = [] state.nudgeCounter = 0 state.lastToolPrune = false } diff --git a/lib/strategies/index.ts b/lib/strategies/index.ts index a995254e..e0680e6b 100644 --- a/lib/strategies/index.ts +++ b/lib/strategies/index.ts @@ -1,4 +1,4 @@ export { deduplicate } from "./deduplication" -export { createDiscardTool, createExtractTool, createSquashTool } from "../tools" +export { createPruneTool, createDistillTool, createCompressTool } from "../tools" export { supersedeWrites } from "./supersede-writes" export { purgeErrors } from "./purge-errors" diff --git a/lib/tools/squash.ts b/lib/tools/compress.ts similarity index 76% rename from lib/tools/squash.ts rename to lib/tools/compress.ts index 9ab9425a..f5c30334 100644 --- a/lib/tools/squash.ts +++ b/lib/tools/compress.ts @@ -1,5 +1,5 @@ import { tool } from "@opencode-ai/plugin" -import type { WithParts, SquashSummary } from "../state" +import type { WithParts, CompressSummary } from "../state" import type { PruneToolContext } from "./types" import { ensureSessionInitialized } from "../state" import { saveSessionState } from "../state/persistence" @@ -11,19 +11,19 @@ import { collectToolIdsInRange, collectMessageIdsInRange, } from "./utils" -import { sendSquashNotification } from "../ui/notification" +import { sendCompressNotification } from "../ui/notification" -const SQUASH_TOOL_DESCRIPTION = loadPrompt("squash-tool-spec") +const COMPRESS_TOOL_DESCRIPTION = loadPrompt("compress-tool-spec") -export function createSquashTool(ctx: PruneToolContext): ReturnType { +export function createCompressTool(ctx: PruneToolContext): ReturnType { return tool({ - description: SQUASH_TOOL_DESCRIPTION, + description: COMPRESS_TOOL_DESCRIPTION, args: { input: tool.schema .array(tool.schema.string()) .length(4) .describe( - "[startString, endString, topic, summary] - 4 required strings: (1) startString: unique text from conversation marking range start, (2) endString: unique text marking range end, (3) topic: short 3-5 word label for UI, (4) summary: comprehensive text replacing all squashed content", + "[startString, endString, topic, summary] - 4 required strings: (1) startString: unique text from conversation marking range start, (2) endString: unique text marking range end, (3) topic: short 3-5 word label for UI, (4) summary: comprehensive text replacing all compressed content", ), }, async execute(args, toolCtx) { @@ -32,7 +32,7 @@ export function createSquashTool(ctx: PruneToolContext): ReturnType const [startString, endString, topic, summary] = args.input - logger.info("Squash tool invoked") + logger.info("Compress tool invoked") // logger.info( // JSON.stringify({ // startString: startString?.substring(0, 50) + "...", @@ -53,14 +53,14 @@ export function createSquashTool(ctx: PruneToolContext): ReturnType messages, startString, logger, - state.squashSummaries, + state.compressSummaries, "startString", ) const endResult = findStringInMessages( messages, endString, logger, - state.squashSummaries, + state.compressSummaries, "endString", ) @@ -86,37 +86,37 @@ export function createSquashTool(ctx: PruneToolContext): ReturnType state.prune.messageIds.push(...containedMessageIds) // Remove any existing summaries whose anchors are now inside this range - // This prevents duplicate injections when a larger squash subsumes a smaller one - const removedSummaries = state.squashSummaries.filter((s) => + // This prevents duplicate injections when a larger compress subsumes a smaller one + const removedSummaries = state.compressSummaries.filter((s) => containedMessageIds.includes(s.anchorMessageId), ) if (removedSummaries.length > 0) { - // logger.info("Removing subsumed squash summaries", { + // logger.info("Removing subsumed compress summaries", { // count: removedSummaries.length, // anchorIds: removedSummaries.map((s) => s.anchorMessageId), // }) - state.squashSummaries = state.squashSummaries.filter( + state.compressSummaries = state.compressSummaries.filter( (s) => !containedMessageIds.includes(s.anchorMessageId), ) } - const squashSummary: SquashSummary = { + const compressSummary: CompressSummary = { anchorMessageId: startResult.messageId, summary: summary, } - state.squashSummaries.push(squashSummary) + state.compressSummaries.push(compressSummary) const contentsToTokenize = collectContentInRange( messages, startResult.messageIndex, endResult.messageIndex, ) - const estimatedSquashedTokens = estimateTokensBatch(contentsToTokenize) + const estimatedCompressedTokens = estimateTokensBatch(contentsToTokenize) - state.stats.pruneTokenCounter += estimatedSquashedTokens + state.stats.pruneTokenCounter += estimatedCompressedTokens const currentParams = getCurrentParams(state, messages, logger) - await sendSquashNotification( + await sendCompressNotification( client, logger, ctx.config, @@ -136,20 +136,20 @@ export function createSquashTool(ctx: PruneToolContext): ReturnType state.stats.pruneTokenCounter = 0 state.nudgeCounter = 0 - // logger.info("Squash range created", { + // logger.info("Compress range created", { // startMessageId: startResult.messageId, // endMessageId: endResult.messageId, // toolIdsRemoved: containedToolIds.length, // messagesInRange: containedMessageIds.length, - // estimatedTokens: estimatedSquashedTokens, + // estimatedTokens: estimatedCompressedTokens, // }) saveSessionState(state, logger).catch((err) => logger.error("Failed to persist state", { error: err.message }), ) - const messagesSquashed = endResult.messageIndex - startResult.messageIndex + 1 - return `Squashed ${messagesSquashed} messages (${containedToolIds.length} tool calls) into summary. The content will be replaced with your summary.` + const messagesCompressed = endResult.messageIndex - startResult.messageIndex + 1 + return `Compressed ${messagesCompressed} messages (${containedToolIds.length} tool calls) into summary. The content will be replaced with your summary.` }, }) } diff --git a/lib/tools/extract.ts b/lib/tools/distill.ts similarity index 82% rename from lib/tools/extract.ts rename to lib/tools/distill.ts index 15e5d7c8..ce224e9d 100644 --- a/lib/tools/extract.ts +++ b/lib/tools/distill.ts @@ -4,16 +4,16 @@ import { executePruneOperation } from "./prune-shared" import { PruneReason } from "../ui/notification" import { loadPrompt } from "../prompts" -const EXTRACT_TOOL_DESCRIPTION = loadPrompt("extract-tool-spec") +const DISTILL_TOOL_DESCRIPTION = loadPrompt("distill-tool-spec") -export function createExtractTool(ctx: PruneToolContext): ReturnType { +export function createDistillTool(ctx: PruneToolContext): ReturnType { return tool({ - description: EXTRACT_TOOL_DESCRIPTION, + description: DISTILL_TOOL_DESCRIPTION, args: { ids: tool.schema .array(tool.schema.string()) .min(1) - .describe("Numeric IDs as strings to extract from the list"), + .describe("Numeric IDs as strings to distill from the list"), distillation: tool.schema .array(tool.schema.string()) .min(1) @@ -24,7 +24,7 @@ export function createExtractTool(ctx: PruneToolContext): ReturnType { +export function createPruneTool(ctx: PruneToolContext): ReturnType { return tool({ - description: DISCARD_TOOL_DESCRIPTION, + description: PRUNE_TOOL_DESCRIPTION, args: { ids: tool.schema .array(tool.schema.string()) .min(1) - .describe("Numeric IDs as strings from the list to discard"), + .describe("Numeric IDs as strings from the list to prune"), }, async execute(args, toolCtx) { const numericIds = args.ids const reason = "noise" - return executePruneOperation(ctx, toolCtx, numericIds, reason, "Discard") + return executePruneOperation(ctx, toolCtx, numericIds, reason, "Prune") }, }) } diff --git a/lib/tools/utils.ts b/lib/tools/utils.ts index d5e4e180..8adec13e 100644 --- a/lib/tools/utils.ts +++ b/lib/tools/utils.ts @@ -1,24 +1,24 @@ -import type { WithParts, SquashSummary } from "../state" +import type { WithParts, CompressSummary } from "../state" import type { Logger } from "../logger" /** * Searches messages for a string and returns the message ID where it's found. * Searches in text parts, tool outputs, tool inputs, and other textual content. - * Also searches through existing squash summaries to enable chained squashing. + * Also searches through existing compress summaries to enable chained compression. * Throws an error if the string is not found or found more than once. */ export function findStringInMessages( messages: WithParts[], searchString: string, logger: Logger, - squashSummaries: SquashSummary[] = [], + compressSummaries: CompressSummary[] = [], stringType: "startString" | "endString", ): { messageId: string; messageIndex: number } { const matches: { messageId: string; messageIndex: number }[] = [] - // First, search through existing squash summaries - // This allows referencing text from previous squash operations - for (const summary of squashSummaries) { + // First, search through existing compress summaries + // This allows referencing text from previous compress operations + for (const summary of compressSummaries) { if (summary.summary.includes(searchString)) { const anchorIndex = messages.findIndex((m) => m.info.id === summary.anchorMessageId) if (anchorIndex !== -1) { diff --git a/lib/ui/notification.ts b/lib/ui/notification.ts index 07ccf41d..7bb17f7b 100644 --- a/lib/ui/notification.ts +++ b/lib/ui/notification.ts @@ -26,7 +26,7 @@ function buildMinimalMessage( ): string { const extractedTokens = countDistillationTokens(distillation) const extractedSuffix = - extractedTokens > 0 ? ` (extracted ${formatTokenCount(extractedTokens)})` : "" + extractedTokens > 0 ? ` (distilled ${formatTokenCount(extractedTokens)})` : "" const reasonSuffix = reason && extractedTokens === 0 ? ` — ${PRUNE_REASON_LABELS[reason]}` : "" let message = formatStatsHeader(state.stats.totalPruneTokens, state.stats.pruneTokenCounter) + @@ -51,7 +51,7 @@ function buildDetailedMessage( const pruneTokenCounterStr = `~${formatTokenCount(state.stats.pruneTokenCounter)}` const extractedTokens = countDistillationTokens(distillation) const extractedSuffix = - extractedTokens > 0 ? `, extracted ${formatTokenCount(extractedTokens)}` : "" + extractedTokens > 0 ? `, distilled ${formatTokenCount(extractedTokens)}` : "" const reasonLabel = reason && extractedTokens === 0 ? ` — ${PRUNE_REASON_LABELS[reason]}` : "" message += `\n\n▣ Pruning (${pruneTokenCounterStr}${extractedSuffix})${reasonLabel}` @@ -85,7 +85,7 @@ export async function sendUnifiedNotification( return false } - const showDistillation = config.tools.extract.showDistillation + const showDistillation = config.tools.distill.showDistillation const message = config.pruneNotification === "minimal" @@ -104,7 +104,7 @@ export async function sendUnifiedNotification( return true } -export async function sendSquashNotification( +export async function sendCompressNotification( client: any, logger: Logger, config: PluginConfig, @@ -137,7 +137,7 @@ export async function sendSquashNotification( endResult.messageIndex, 25, ) - message += `\n\n▣ Squashing (${pruneTokenCounterStr}) ${progressBar}` + message += `\n\n▣ Compressing (${pruneTokenCounterStr}) ${progressBar}` message += `\n→ Topic: ${topic}` message += `\n→ Items: ${messageIds.length} messages` if (toolIds.length > 0) { @@ -145,7 +145,7 @@ export async function sendSquashNotification( } else { message += ` condensed` } - if (config.tools.squash.showSummary) { + if (config.tools.compress.showSummary) { message += `\n→ Summary: ${summary}` } } diff --git a/package.json b/package.json index 2bc7ff66..15021281 100644 --- a/package.json +++ b/package.json @@ -9,13 +9,14 @@ "scripts": { "clean": "rm -rf dist", "build": "npm run clean && tsc", - "postbuild": "rm -rf dist/logs", + "postbuild": "rm -rf dist/logs && cp lib/prompts/*.md dist/lib/prompts/", "prepublishOnly": "npm run build", "dev": "opencode plugin dev", "typecheck": "tsc --noEmit", "test": "node --import tsx --test tests/*.test.ts", "format": "prettier --write .", - "format:check": "prettier --check ." + "format:check": "prettier --check .", + "dcp": "tsx cli/print.ts" }, "keywords": [ "opencode",