fix(reminder-hooks): preserve suppression state across compaction
This commit is contained in:
committed by
YeonGyu-Kim
parent
ae7ff3bb7e
commit
3db1da1e5b
@@ -42,6 +42,8 @@ const ORCHESTRATOR_AGENTS = new Set([
|
||||
"prometheus",
|
||||
]);
|
||||
|
||||
const MAX_REMINDERS = 3;
|
||||
|
||||
function isOrchestratorAgent(agentName: string): boolean {
|
||||
return ORCHESTRATOR_AGENTS.has(getAgentConfigKey(agentName));
|
||||
}
|
||||
@@ -99,7 +101,7 @@ export function createAgentUsageReminderHook(_ctx: PluginInput) {
|
||||
|
||||
const state = getOrCreateState(sessionID);
|
||||
|
||||
if (state.agentUsed) {
|
||||
if (state.agentUsed || state.reminderCount >= MAX_REMINDERS) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -119,12 +121,6 @@ export function createAgentUsageReminderHook(_ctx: PluginInput) {
|
||||
}
|
||||
}
|
||||
|
||||
if (event.type === "session.compacted") {
|
||||
const sessionID = resolveSessionEventID(props);
|
||||
if (sessionID) {
|
||||
resetState(sessionID);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
return {
|
||||
|
||||
@@ -0,0 +1,80 @@
|
||||
import type { PluginInput } from "@opencode-ai/plugin";
|
||||
import { afterEach, beforeEach, describe, expect, mock, spyOn, test } from "bun:test";
|
||||
import { createAgentUsageReminderHook } from "./index";
|
||||
import { clearSessionAgent, updateSessionAgent, _resetForTesting } from "../../features/claude-code-session-state";
|
||||
import * as storage from "./storage";
|
||||
|
||||
describe("agent-usage-reminder hook", () => {
|
||||
let loadStateSpy: ReturnType<typeof spyOn>;
|
||||
let saveStateSpy: ReturnType<typeof spyOn>;
|
||||
let clearStateSpy: ReturnType<typeof spyOn>;
|
||||
|
||||
beforeEach(() => {
|
||||
_resetForTesting();
|
||||
loadStateSpy = spyOn(storage, "loadAgentUsageState").mockReturnValue(null);
|
||||
saveStateSpy = spyOn(storage, "saveAgentUsageState").mockImplementation(mock(() => {}));
|
||||
clearStateSpy = spyOn(storage, "clearAgentUsageState").mockImplementation(mock(() => {}));
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
loadStateSpy?.mockRestore();
|
||||
saveStateSpy?.mockRestore();
|
||||
clearStateSpy?.mockRestore();
|
||||
});
|
||||
|
||||
function createHook() {
|
||||
return createAgentUsageReminderHook({} as PluginInput);
|
||||
}
|
||||
|
||||
test("caps reminders and does not re-arm after session.compacted", async () => {
|
||||
// given - an orchestrator session has already hit the reminder cap
|
||||
const hook = createHook();
|
||||
const sessionID = "agent-usage-compact-session";
|
||||
updateSessionAgent(sessionID, "Sisyphus");
|
||||
|
||||
const output1 = { title: "", output: "result-1", metadata: {} };
|
||||
const output2 = { title: "", output: "result-2", metadata: {} };
|
||||
const output3 = { title: "", output: "result-3", metadata: {} };
|
||||
const output4 = { title: "", output: "result-4", metadata: {} };
|
||||
|
||||
await hook["tool.execute.after"]({ tool: "grep", sessionID, callID: "1" }, output1);
|
||||
await hook["tool.execute.after"]({ tool: "grep", sessionID, callID: "2" }, output2);
|
||||
await hook["tool.execute.after"]({ tool: "grep", sessionID, callID: "3" }, output3);
|
||||
|
||||
// then - the first three reminders are shown
|
||||
expect(output1.output).toContain("[Agent Usage Reminder]");
|
||||
expect(output2.output).toContain("[Agent Usage Reminder]");
|
||||
expect(output3.output).toContain("[Agent Usage Reminder]");
|
||||
|
||||
// when - compaction happens and another target tool runs
|
||||
await hook.event({ event: { type: "session.compacted", properties: { sessionID } } });
|
||||
await hook["tool.execute.after"]({ tool: "grep", sessionID, callID: "4" }, output4);
|
||||
|
||||
// then - compaction does not reset the reminder cap
|
||||
expect(output4.output).not.toContain("[Agent Usage Reminder]");
|
||||
|
||||
clearSessionAgent(sessionID);
|
||||
});
|
||||
|
||||
test("resets reminder state on session.deleted", async () => {
|
||||
// given - an orchestrator session has reminder state
|
||||
const hook = createHook();
|
||||
const sessionID = "agent-usage-delete-session";
|
||||
updateSessionAgent(sessionID, "Sisyphus");
|
||||
|
||||
const output1 = { title: "", output: "result-1", metadata: {} };
|
||||
const output2 = { title: "", output: "result-2", metadata: {} };
|
||||
|
||||
await hook["tool.execute.after"]({ tool: "grep", sessionID, callID: "1" }, output1);
|
||||
expect(output1.output).toContain("[Agent Usage Reminder]");
|
||||
|
||||
// when - the session is deleted and another target tool runs
|
||||
await hook.event({ event: { type: "session.deleted", properties: { info: { id: sessionID } } } });
|
||||
await hook["tool.execute.after"]({ tool: "grep", sessionID, callID: "2" }, output2);
|
||||
|
||||
// then - deletion still resets the state
|
||||
expect(output2.output).toContain("[Agent Usage Reminder]");
|
||||
|
||||
clearSessionAgent(sessionID);
|
||||
});
|
||||
});
|
||||
@@ -127,12 +127,6 @@ export function createCategorySkillReminderHook(
|
||||
}
|
||||
}
|
||||
|
||||
if (event.type === "session.compacted") {
|
||||
const sessionID = resolveSessionEventID(props)
|
||||
if (sessionID) {
|
||||
sessionStates.delete(sessionID)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
|
||||
@@ -282,7 +282,7 @@ describe("category-skill-reminder hook", () => {
|
||||
clearSessionAgent(sessionID)
|
||||
})
|
||||
|
||||
test("should reset state on session.compacted event", async () => {
|
||||
test("should preserve suppression state on session.compacted event", async () => {
|
||||
// given - sisyphus agent with reminder already shown
|
||||
const hook = createHook()
|
||||
const sessionID = "compact-session"
|
||||
@@ -302,8 +302,30 @@ describe("category-skill-reminder hook", () => {
|
||||
await hook["tool.execute.after"]({ tool: "edit", sessionID, callID: "5" }, output2)
|
||||
await hook["tool.execute.after"]({ tool: "edit", sessionID, callID: "6" }, output2)
|
||||
|
||||
// then - reminder should be shown again (state was reset)
|
||||
expect(output2.output).toContain("[Category+Skill Reminder]")
|
||||
// then - reminder should NOT be shown again (state remains suppressed)
|
||||
expect(output2.output).not.toContain("[Category+Skill Reminder]")
|
||||
|
||||
clearSessionAgent(sessionID)
|
||||
})
|
||||
|
||||
test("should preserve partial tool-call count across session.compacted", async () => {
|
||||
// given - sisyphus agent with 2 delegatable tool calls
|
||||
const hook = createHook()
|
||||
const sessionID = "compact-partial-count-session"
|
||||
updateSessionAgent(sessionID, "Sisyphus")
|
||||
|
||||
const output = { title: "", output: "result", metadata: {} }
|
||||
|
||||
await hook["tool.execute.after"]({ tool: "edit", sessionID, callID: "1" }, output)
|
||||
await hook["tool.execute.after"]({ tool: "edit", sessionID, callID: "2" }, output)
|
||||
expect(output.output).not.toContain("[Category+Skill Reminder]")
|
||||
|
||||
// when - the session compacts before the third tool call
|
||||
await hook.event({ event: { type: "session.compacted", properties: { sessionID } } })
|
||||
await hook["tool.execute.after"]({ tool: "edit", sessionID, callID: "3" }, output)
|
||||
|
||||
// then - the third call should still trigger the reminder
|
||||
expect(output.output).toContain("[Category+Skill Reminder]")
|
||||
|
||||
clearSessionAgent(sessionID)
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user