Skip to content

Commit 4672eca

Browse files
authored
Merge pull request #25 from Tarquinen/feat/context-pruning-tool
Add context_pruning tool and refactor to strategies-based config
2 parents 8267728 + 09ccfb4 commit 4672eca

File tree

8 files changed

+464
-102
lines changed

8 files changed

+464
-102
lines changed

.gitignore

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,3 +30,6 @@ Thumbs.db
3030

3131
# Tests (local development only)
3232
tests/
33+
34+
# Development notes
35+
notes/

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -78,7 +78,7 @@ If you want to ensure a specific version is always used or update your version,
7878
```json
7979
{
8080
"plugin": [
81-
"@tarquinen/[email protected].14"
81+
"@tarquinen/[email protected].15"
8282
]
8383
}
8484
```

index.ts

Lines changed: 84 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
// index.ts - Main plugin entry point for Dynamic Context Pruning
22
import type { Plugin } from "@opencode-ai/plugin"
3+
import { tool } from "@opencode-ai/plugin"
34
import { getConfig } from "./lib/config"
45
import { Logger } from "./lib/logger"
56
import { Janitor, type SessionStats } from "./lib/janitor"
@@ -20,7 +21,7 @@ async function isSubagentSession(client: any, sessionID: string): Promise<boolea
2021
}
2122

2223
const plugin: Plugin = (async (ctx) => {
23-
const config = getConfig(ctx)
24+
const { config, migrations } = getConfig(ctx)
2425

2526
// Exit early if plugin is disabled
2627
if (!config.enabled) {
@@ -38,7 +39,7 @@ const plugin: Plugin = (async (ctx) => {
3839
const statsState = new Map<string, SessionStats>()
3940
const toolParametersCache = new Map<string, any>() // callID -> parameters
4041
const modelCache = new Map<string, { providerID: string; modelID: string }>() // sessionID -> model info
41-
const janitor = new Janitor(ctx.client, prunedIdsState, statsState, logger, toolParametersCache, config.protectedTools, modelCache, config.model, config.showModelErrorToasts, config.pruningMode, config.pruning_summary, ctx.directory)
42+
const janitor = new Janitor(ctx.client, prunedIdsState, statsState, logger, toolParametersCache, config.protectedTools, modelCache, config.model, config.showModelErrorToasts, config.pruning_summary, ctx.directory)
4243

4344
const cacheToolParameters = (messages: any[]) => {
4445
for (const message of messages) {
@@ -142,13 +143,31 @@ const plugin: Plugin = (async (ctx) => {
142143
}
143144

144145
logger.info("plugin", "DCP initialized", {
145-
mode: config.pruningMode,
146+
strategies: config.strategies,
146147
model: config.model || "auto"
147148
})
148149

149150
// Check for updates on launch (fire and forget)
150151
checkForUpdates(ctx.client, logger).catch(() => {})
151152

153+
// Show migration toast if config was migrated (delayed to not overlap with version toast)
154+
if (migrations.length > 0) {
155+
setTimeout(async () => {
156+
try {
157+
await ctx.client.tui.showToast({
158+
body: {
159+
title: "DCP: Config upgraded",
160+
message: migrations.join('\n'),
161+
variant: "info",
162+
duration: 8000
163+
}
164+
})
165+
} catch {
166+
// Silently fail - toast is non-critical
167+
}
168+
}, 7000) // 7s delay to show after version toast (6s) completes
169+
}
170+
152171
return {
153172
/**
154173
* Event Hook: Triggers janitor analysis when session becomes idle
@@ -158,8 +177,11 @@ const plugin: Plugin = (async (ctx) => {
158177
// Skip pruning for subagent sessions
159178
if (await isSubagentSession(ctx.client, event.properties.sessionID)) return
160179

180+
// Skip if no idle strategies configured
181+
if (config.strategies.onIdle.length === 0) return
182+
161183
// Fire and forget the janitor - don't block the event handler
162-
janitor.run(event.properties.sessionID).catch(err => {
184+
janitor.runOnIdle(event.properties.sessionID, config.strategies.onIdle).catch(err => {
163185
logger.error("janitor", "Failed", { error: err.message })
164186
})
165187
}
@@ -168,7 +190,7 @@ const plugin: Plugin = (async (ctx) => {
168190
/**
169191
* Chat Params Hook: Caches model info for janitor
170192
*/
171-
"chat.params": async (input, output) => {
193+
"chat.params": async (input, _output) => {
172194
const sessionId = input.sessionID
173195

174196
// Cache model information for this session so janitor can access it
@@ -188,6 +210,63 @@ const plugin: Plugin = (async (ctx) => {
188210
})
189211
}
190212
},
213+
214+
/**
215+
* Tool Hook: Exposes context_pruning tool to AI (if configured)
216+
*/
217+
tool: config.strategies.onTool.length > 0 ? {
218+
context_pruning: tool({
219+
description: `Performs semantic pruning on session tool outputs that are no longer relevant to the current task. Use this to declutter the conversation context and filter signal from noise when you notice the context is getting cluttered with outdated information.
220+
221+
## When to Use This Tool
222+
223+
- After completing a debugging session or fixing a bug
224+
- When switching focus to a new task or feature
225+
- After exploring multiple files that didn't lead to changes
226+
- When you've been iterating on a difficult problem and some approaches didn't pan out
227+
- When old file reads, greps, or bash outputs are no longer relevant
228+
229+
## Examples
230+
231+
<example>
232+
Working through a list of bugs to fix:
233+
User: Please fix these 5 type errors in the codebase.
234+
Assistant: I'll work through each error. [Fixes first error]
235+
First error fixed. Let me prune the debugging context before moving to the next one.
236+
[Uses context_pruning with reason: "first bug fixed, moving to next task"]
237+
</example>
238+
239+
<example>
240+
After exploring the codebase to understand it:
241+
Assistant: I've reviewed the relevant files. Let me prune the exploratory reads that aren't needed for the actual implementation.
242+
[Uses context_pruning with reason: "exploration complete, pruning unrelated file reads"]
243+
</example>
244+
245+
<example>
246+
After trying multiple approaches that didn't work:
247+
Assistant: I've been trying several approaches to fix this issue. Let me prune the failed attempts to keep focus on the working solution.
248+
[Uses context_pruning with reason: "pruning failed iteration attempts, keeping working solution context"]
249+
</example>`,
250+
args: {
251+
reason: tool.schema.string().optional().describe(
252+
"Brief reason for triggering pruning (e.g., 'task complete', 'switching focus')"
253+
),
254+
},
255+
async execute(args, ctx) {
256+
const result = await janitor.runForTool(
257+
ctx.sessionID,
258+
config.strategies.onTool,
259+
args.reason
260+
)
261+
262+
if (!result || result.prunedCount === 0) {
263+
return "No prunable tool outputs found. Context is already optimized."
264+
}
265+
266+
return janitor.formatPruningResultForTool(result)
267+
},
268+
}),
269+
} : undefined,
191270
}
192271
}) satisfies Plugin
193272

0 commit comments

Comments
 (0)