135 lines
3.3 KiB
TypeScript
135 lines
3.3 KiB
TypeScript
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";
|
|
|
|
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",
|
|
]);
|
|
|
|
function isOrchestratorAgent(agentName: string): boolean {
|
|
return ORCHESTRATOR_AGENTS.has(getAgentConfigKey(agentName));
|
|
}
|
|
|
|
export function createAgentUsageReminderHook(_ctx: PluginInput) {
|
|
const sessionStates = new Map<string, AgentUsageState>();
|
|
|
|
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) {
|
|
return;
|
|
}
|
|
|
|
output.output += REMINDER_MESSAGE;
|
|
state.reminderCount++;
|
|
state.updatedAt = Date.now();
|
|
saveAgentUsageState(state);
|
|
};
|
|
|
|
const eventHandler = async ({ event }: EventInput) => {
|
|
const props = event.properties as Record<string, unknown> | undefined;
|
|
|
|
if (event.type === "session.deleted") {
|
|
const sessionInfo = props?.info as { id?: string } | undefined;
|
|
if (sessionInfo?.id) {
|
|
resetState(sessionInfo.id);
|
|
}
|
|
}
|
|
|
|
if (event.type === "session.compacted") {
|
|
const sessionID = (props?.sessionID ??
|
|
(props?.info as { id?: string } | undefined)?.id) as string | undefined;
|
|
if (sessionID) {
|
|
resetState(sessionID);
|
|
}
|
|
}
|
|
};
|
|
|
|
return {
|
|
"tool.execute.after": toolExecuteAfter,
|
|
event: eventHandler,
|
|
};
|
|
}
|