diff --git a/.claude/settings.local.json b/.claude/settings.local.json deleted file mode 100644 index 6fccb8f..0000000 --- a/.claude/settings.local.json +++ /dev/null @@ -1,15 +0,0 @@ -{ - "permissions": { - "allow": [ - "Bash(cat:*)", - "Bash(for f in ~/.local/share/opencode/storage/part/*/*)", - "Bash(do grep -l \"\"type\"\":\"\"reasoning\"\" $f)", - "Bash(done)", - "WebSearch", - "WebFetch(domain:ai-sdk.dev)", - "Bash(npm run typecheck:*)" - ], - "deny": [], - "ask": [] - } -} diff --git a/.gitignore b/.gitignore index 6fcd193..c4c6365 100644 --- a/.gitignore +++ b/.gitignore @@ -27,7 +27,6 @@ Thumbs.db # OpenCode .opencode/ -AGENTS.md # Tests (local development only) tests/ diff --git a/index.ts b/index.ts index 0b32e31..f30b041 100644 --- a/index.ts +++ b/index.ts @@ -42,6 +42,18 @@ const plugin: Plugin = (async (ctx) => { workingDirectory: ctx.directory }), } : undefined, + config: async (opencodeConfig) => { + // Add prune to primary_tools by mutating the opencode config + // This works because config is cached and passed by reference + if (config.strategies.pruneTool.enabled) { + const existingPrimaryTools = opencodeConfig.experimental?.primary_tools ?? [] + opencodeConfig.experimental = { + ...opencodeConfig.experimental, + primary_tools: [...existingPrimaryTools, "prune"], + } + logger.info("Added 'prune' to experimental.primary_tools via config mutation") + } + }, } }) satisfies Plugin diff --git a/lib/hooks.ts b/lib/hooks.ts index b3dc9da..88acd88 100644 --- a/lib/hooks.ts +++ b/lib/hooks.ts @@ -17,11 +17,15 @@ export function createChatMessageTransformHandler( input: {}, output: { messages: WithParts[] } ) => { - checkSession(state, logger, output.messages); + checkSession(client, state, logger, output.messages); + if (state.isSubAgent) { + return + } + syncToolCache(state, config, logger, output.messages); - deduplicate(state, logger, config, output.messages) + deduplicate(client, state, logger, config, output.messages) prune(state, logger, config, output.messages) diff --git a/lib/state/state.ts b/lib/state/state.ts index 6682d6f..8edbf52 100644 --- a/lib/state/state.ts +++ b/lib/state/state.ts @@ -2,8 +2,10 @@ import type { SessionState, ToolParameterEntry, WithParts } from "./types" import type { Logger } from "../logger" import { loadSessionState } from "./persistence" import { getLastUserMessage } from "../messages/utils" +import { isSubAgentSession } from "../utils" export const checkSession = ( + client: any, state: SessionState, logger: Logger, messages: WithParts[] @@ -19,6 +21,7 @@ export const checkSession = ( if (state.sessionId === null || state.sessionId !== lastSessionId) { logger.info(`Session changed: ${state.sessionId} -> ${lastSessionId}`) ensureSessionInitialized( + client, state, lastSessionId, logger @@ -31,6 +34,7 @@ export const checkSession = ( export function createSessionState(): SessionState { return { sessionId: null, + isSubAgent: false, prune: { toolIds: [] }, @@ -45,6 +49,7 @@ export function createSessionState(): SessionState { export function resetSessionState(state: SessionState): void { state.sessionId = null + state.isSubAgent = false state.prune = { toolIds: [] } @@ -57,6 +62,7 @@ export function resetSessionState(state: SessionState): void { } export async function ensureSessionInitialized( + client: any, state: SessionState, sessionId: string, logger: Logger @@ -72,6 +78,10 @@ export async function ensureSessionInitialized( resetSessionState(state) state.sessionId = sessionId + const isSubAgent = await isSubAgentSession(client, sessionId) + state.isSubAgent = isSubAgent + logger.info("isSubAgent = " + isSubAgent) + // Load session data from storage const persisted = await loadSessionState(sessionId, logger) if (persisted === null) { diff --git a/lib/state/types.ts b/lib/state/types.ts index 750ca38..f7353e0 100644 --- a/lib/state/types.ts +++ b/lib/state/types.ts @@ -25,6 +25,7 @@ export interface Prune { export interface SessionState { sessionId: string | null + isSubAgent: boolean prune: Prune stats: SessionStats toolParameters: Map diff --git a/lib/strategies/deduplication.ts b/lib/strategies/deduplication.ts index f58a13a..f0887c2 100644 --- a/lib/strategies/deduplication.ts +++ b/lib/strategies/deduplication.ts @@ -9,6 +9,7 @@ import { calculateTokensSaved } from "../utils" * Modifies the session state in place to add pruned tool call IDs. */ export const deduplicate = ( + client: any, state: SessionState, logger: Logger, config: PluginConfig, diff --git a/lib/strategies/on-idle.ts b/lib/strategies/on-idle.ts new file mode 100644 index 0000000..e69de29 diff --git a/lib/strategies/prune-tool.ts b/lib/strategies/prune-tool.ts index 08e680c..83d4ff5 100644 --- a/lib/strategies/prune-tool.ts +++ b/lib/strategies/prune-tool.ts @@ -60,7 +60,7 @@ export function createPruneTool( return "No numeric IDs provided. Format: [reason, id1, id2, ...] where reason is 'completion', 'noise', or 'consolidation'." } - await ensureSessionInitialized(state, sessionId, logger) + await ensureSessionInitialized(ctx.client, state, sessionId, logger) // Fetch messages to calculate tokens and find current agent const messagesResponse = await client.session.messages({ diff --git a/lib/utils.ts b/lib/utils.ts index 844edfb..471729c 100644 --- a/lib/utils.ts +++ b/lib/utils.ts @@ -60,7 +60,7 @@ export function formatTokenCount(tokens: number): string { /** * Checks if a session is a subagent session by looking for a parentID. */ -export async function isSubagentSession(client: any, sessionID: string): Promise { +export async function isSubAgentSession(client: any, sessionID: string): Promise { try { const result = await client.session.get({ path: { id: sessionID } }) return !!result.data?.parentID