import type { PluginInput } from "@opencode-ai/plugin"; import { loadAgentUsageState, saveAgentUsageState, clearAgentUsageState, } from "./storage"; import { TARGET_TOOLS, AGENT_TOOLS, REMINDER_MESSAGE } from "./constants"; import type { AgentUsageState } from "./types"; import { getSessionAgent } from "../../features/claude-code-session-state"; import { getAgentConfigKey } from "../../shared/agent-display-names"; import { resolveSessionEventID } from "../../shared/event-session-id"; interface ToolExecuteInput { tool: string; sessionID: string; callID: string; } interface ToolExecuteOutput { title: string; output: string; metadata: unknown; } interface EventInput { event: { type: string; properties?: unknown; }; } /** * Only orchestrator agents should receive usage reminders. * Subagents (explore, librarian, oracle, etc.) are the targets of delegation, * so reminding them to delegate to themselves is counterproductive. */ const ORCHESTRATOR_AGENTS = new Set([ "sisyphus", "sisyphus-junior", "atlas", "hephaestus", "prometheus", ]); const MAX_REMINDERS = 3; function isOrchestratorAgent(agentName: string): boolean { return ORCHESTRATOR_AGENTS.has(getAgentConfigKey(agentName)); } export function createAgentUsageReminderHook(_ctx: PluginInput) { const sessionStates = new Map(); function getOrCreateState(sessionID: string): AgentUsageState { if (!sessionStates.has(sessionID)) { const persisted = loadAgentUsageState(sessionID); const state: AgentUsageState = persisted ?? { sessionID, agentUsed: false, reminderCount: 0, updatedAt: Date.now(), }; sessionStates.set(sessionID, state); } return sessionStates.get(sessionID)!; } function markAgentUsed(sessionID: string): void { const state = getOrCreateState(sessionID); state.agentUsed = true; state.updatedAt = Date.now(); saveAgentUsageState(state); } function resetState(sessionID: string): void { sessionStates.delete(sessionID); clearAgentUsageState(sessionID); } const toolExecuteAfter = async ( input: ToolExecuteInput, output: ToolExecuteOutput, ) => { const { tool, sessionID } = input; const agent = getSessionAgent(sessionID); if (agent && !isOrchestratorAgent(agent)) { return; } const toolLower = tool.toLowerCase(); if (AGENT_TOOLS.has(toolLower)) { markAgentUsed(sessionID); return; } if (!TARGET_TOOLS.has(toolLower)) { return; } const state = getOrCreateState(sessionID); if (state.agentUsed || state.reminderCount >= MAX_REMINDERS) { return; } output.output += REMINDER_MESSAGE; state.reminderCount++; state.updatedAt = Date.now(); saveAgentUsageState(state); }; const eventHandler = async ({ event }: EventInput) => { const props = event.properties as Record | undefined; if (event.type === "session.deleted") { const sessionID = resolveSessionEventID(props); if (sessionID) { resetState(sessionID); } } }; return { "tool.execute.after": toolExecuteAfter, event: eventHandler, }; }