Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -30,3 +30,6 @@ Thumbs.db

# Tests (local development only)
tests/

# Development notes
notes/
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,7 @@ If you want to ensure a specific version is always used or update your version,
```json
{
"plugin": [
"@tarquinen/[email protected].14"
"@tarquinen/[email protected].15"
]
}
```
Expand Down
89 changes: 84 additions & 5 deletions index.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
// index.ts - Main plugin entry point for Dynamic Context Pruning
import type { Plugin } from "@opencode-ai/plugin"
import { tool } from "@opencode-ai/plugin"
import { getConfig } from "./lib/config"
import { Logger } from "./lib/logger"
import { Janitor, type SessionStats } from "./lib/janitor"
Expand All @@ -20,7 +21,7 @@ async function isSubagentSession(client: any, sessionID: string): Promise<boolea
}

const plugin: Plugin = (async (ctx) => {
const config = getConfig(ctx)
const { config, migrations } = getConfig(ctx)

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

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

logger.info("plugin", "DCP initialized", {
mode: config.pruningMode,
strategies: config.strategies,
model: config.model || "auto"
})

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

// Show migration toast if config was migrated (delayed to not overlap with version toast)
if (migrations.length > 0) {
setTimeout(async () => {
try {
await ctx.client.tui.showToast({
body: {
title: "DCP: Config upgraded",
message: migrations.join('\n'),
variant: "info",
duration: 8000
}
})
} catch {
// Silently fail - toast is non-critical
}
}, 7000) // 7s delay to show after version toast (6s) completes
}

return {
/**
* Event Hook: Triggers janitor analysis when session becomes idle
Expand All @@ -158,8 +177,11 @@ const plugin: Plugin = (async (ctx) => {
// Skip pruning for subagent sessions
if (await isSubagentSession(ctx.client, event.properties.sessionID)) return

// Skip if no idle strategies configured
if (config.strategies.onIdle.length === 0) return

// Fire and forget the janitor - don't block the event handler
janitor.run(event.properties.sessionID).catch(err => {
janitor.runOnIdle(event.properties.sessionID, config.strategies.onIdle).catch(err => {
logger.error("janitor", "Failed", { error: err.message })
})
}
Expand All @@ -168,7 +190,7 @@ const plugin: Plugin = (async (ctx) => {
/**
* Chat Params Hook: Caches model info for janitor
*/
"chat.params": async (input, output) => {
"chat.params": async (input, _output) => {
const sessionId = input.sessionID

// Cache model information for this session so janitor can access it
Expand All @@ -188,6 +210,63 @@ const plugin: Plugin = (async (ctx) => {
})
}
},

/**
* Tool Hook: Exposes context_pruning tool to AI (if configured)
*/
tool: config.strategies.onTool.length > 0 ? {
context_pruning: tool({
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.

## When to Use This Tool

- After completing a debugging session or fixing a bug
- When switching focus to a new task or feature
- After exploring multiple files that didn't lead to changes
- When you've been iterating on a difficult problem and some approaches didn't pan out
- When old file reads, greps, or bash outputs are no longer relevant

## Examples

<example>
Working through a list of bugs to fix:
User: Please fix these 5 type errors in the codebase.
Assistant: I'll work through each error. [Fixes first error]
First error fixed. Let me prune the debugging context before moving to the next one.
[Uses context_pruning with reason: "first bug fixed, moving to next task"]
</example>

<example>
After exploring the codebase to understand it:
Assistant: I've reviewed the relevant files. Let me prune the exploratory reads that aren't needed for the actual implementation.
[Uses context_pruning with reason: "exploration complete, pruning unrelated file reads"]
</example>

<example>
After trying multiple approaches that didn't work:
Assistant: I've been trying several approaches to fix this issue. Let me prune the failed attempts to keep focus on the working solution.
[Uses context_pruning with reason: "pruning failed iteration attempts, keeping working solution context"]
</example>`,
args: {
reason: tool.schema.string().optional().describe(
"Brief reason for triggering pruning (e.g., 'task complete', 'switching focus')"
),
},
async execute(args, ctx) {
const result = await janitor.runForTool(
ctx.sessionID,
config.strategies.onTool,
args.reason
)

if (!result || result.prunedCount === 0) {
return "No prunable tool outputs found. Context is already optimized."
}

return janitor.formatPruningResultForTool(result)
},
}),
} : undefined,
}
}) satisfies Plugin

Expand Down
Loading