feat(todo-enforcer): add skipAgents option and improve permission check
- Add skipAgents option to skip continuation for specified agents - Default skip: Prometheus (Planner) - Improve tool permission check to handle 'allow'/'deny' string values - Add agent name detection from session messages 🤖 GENERATED WITH ASSISTANCE OF [OhMyOpenCode](https://github.com/code-yeongyu/oh-my-opencode)
This commit is contained in:
parent
30e5760e2f
commit
f49d92852a
@ -349,6 +349,25 @@ describe("todo-continuation-enforcer", () => {
|
|||||||
expect(promptCalls).toHaveLength(0)
|
expect(promptCalls).toHaveLength(0)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
test("should accept skipAgents option without error", async () => {
|
||||||
|
// #given - session with skipAgents configured for Prometheus
|
||||||
|
const sessionID = "main-prometheus-option"
|
||||||
|
setMainSession(sessionID)
|
||||||
|
|
||||||
|
// #when - create hook with skipAgents option (should not throw)
|
||||||
|
const hook = createTodoContinuationEnforcer(createMockPluginInput(), {
|
||||||
|
skipAgents: ["Prometheus (Planner)", "custom-agent"],
|
||||||
|
})
|
||||||
|
|
||||||
|
// #then - handler works without error
|
||||||
|
await hook.handler({
|
||||||
|
event: { type: "session.idle", properties: { sessionID } },
|
||||||
|
})
|
||||||
|
|
||||||
|
await new Promise(r => setTimeout(r, 100))
|
||||||
|
expect(toastCalls.length).toBeGreaterThanOrEqual(1)
|
||||||
|
})
|
||||||
|
|
||||||
test("should show countdown toast updates", async () => {
|
test("should show countdown toast updates", async () => {
|
||||||
// #given - session with incomplete todos
|
// #given - session with incomplete todos
|
||||||
const sessionID = "main-toast"
|
const sessionID = "main-toast"
|
||||||
|
|||||||
@ -11,8 +11,11 @@ import { log } from "../shared/logger"
|
|||||||
|
|
||||||
const HOOK_NAME = "todo-continuation-enforcer"
|
const HOOK_NAME = "todo-continuation-enforcer"
|
||||||
|
|
||||||
|
const DEFAULT_SKIP_AGENTS = ["Prometheus (Planner)"]
|
||||||
|
|
||||||
export interface TodoContinuationEnforcerOptions {
|
export interface TodoContinuationEnforcerOptions {
|
||||||
backgroundManager?: BackgroundManager
|
backgroundManager?: BackgroundManager
|
||||||
|
skipAgents?: string[]
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface TodoContinuationEnforcer {
|
export interface TodoContinuationEnforcer {
|
||||||
@ -89,7 +92,7 @@ export function createTodoContinuationEnforcer(
|
|||||||
ctx: PluginInput,
|
ctx: PluginInput,
|
||||||
options: TodoContinuationEnforcerOptions = {}
|
options: TodoContinuationEnforcerOptions = {}
|
||||||
): TodoContinuationEnforcer {
|
): TodoContinuationEnforcer {
|
||||||
const { backgroundManager } = options
|
const { backgroundManager, skipAgents = DEFAULT_SKIP_AGENTS } = options
|
||||||
const sessions = new Map<string, SessionState>()
|
const sessions = new Map<string, SessionState>()
|
||||||
|
|
||||||
function getState(sessionID: string): SessionState {
|
function getState(sessionID: string): SessionState {
|
||||||
@ -184,17 +187,19 @@ export function createTodoContinuationEnforcer(
|
|||||||
const messageDir = getMessageDir(sessionID)
|
const messageDir = getMessageDir(sessionID)
|
||||||
const prevMessage = messageDir ? findNearestMessageWithFields(messageDir) : null
|
const prevMessage = messageDir ? findNearestMessageWithFields(messageDir) : null
|
||||||
|
|
||||||
const hasWritePermission = !prevMessage?.tools ||
|
const agentName = prevMessage?.agent
|
||||||
(prevMessage.tools.write !== false && prevMessage.tools.edit !== false)
|
if (agentName && skipAgents.includes(agentName)) {
|
||||||
|
log(`[${HOOK_NAME}] Skipped: agent in skipAgents list`, { sessionID, agent: agentName })
|
||||||
if (!hasWritePermission) {
|
|
||||||
log(`[${HOOK_NAME}] Skipped: agent lacks write permission`, { sessionID, agent: prevMessage?.agent })
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
const agentName = prevMessage?.agent?.toLowerCase() ?? ""
|
const editPermission = prevMessage?.tools?.edit
|
||||||
if (agentName === "plan" || agentName === "planner-sisyphus") {
|
const writePermission = prevMessage?.tools?.write
|
||||||
log(`[${HOOK_NAME}] Skipped: plan mode agent`, { sessionID, agent: prevMessage?.agent })
|
const hasWritePermission = !prevMessage?.tools ||
|
||||||
|
((editPermission !== false && editPermission !== "deny") &&
|
||||||
|
(writePermission !== false && writePermission !== "deny"))
|
||||||
|
if (!hasWritePermission) {
|
||||||
|
log(`[${HOOK_NAME}] Skipped: agent lacks write permission`, { sessionID, agent: prevMessage?.agent })
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -324,6 +329,28 @@ export function createTodoContinuationEnforcer(
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let agentName: string | undefined
|
||||||
|
try {
|
||||||
|
const messagesResp = await ctx.client.session.messages({
|
||||||
|
path: { id: sessionID },
|
||||||
|
})
|
||||||
|
const messages = (messagesResp.data ?? []) as Array<{ info?: { agent?: string } }>
|
||||||
|
for (let i = messages.length - 1; i >= 0; i--) {
|
||||||
|
if (messages[i].info?.agent) {
|
||||||
|
agentName = messages[i].info?.agent
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
log(`[${HOOK_NAME}] Failed to fetch messages for agent check`, { sessionID, error: String(err) })
|
||||||
|
}
|
||||||
|
|
||||||
|
log(`[${HOOK_NAME}] Agent check`, { sessionID, agentName, skipAgents })
|
||||||
|
if (agentName && skipAgents.includes(agentName)) {
|
||||||
|
log(`[${HOOK_NAME}] Skipped: agent in skipAgents list`, { sessionID, agent: agentName })
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
startCountdown(sessionID, incompleteCount, todos.length)
|
startCountdown(sessionID, incompleteCount, todos.length)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user