fix(athena): parallelize council launches and gate handoff actions
This commit is contained in:
@@ -5,6 +5,109 @@ const { builtinTools } = require("../tools")
|
||||
const { resetStorageClient } = require("../tools/session-manager/storage")
|
||||
|
||||
describe("createToolExecuteBeforeHandler", () => {
|
||||
test("blocks Athena question tool while council members are still running", async () => {
|
||||
//#given
|
||||
const ctx = {
|
||||
client: {
|
||||
session: {
|
||||
messages: async () => ({
|
||||
data: [{ info: { role: "assistant", agent: "Athena (Council)" } }],
|
||||
}),
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
const backgroundManager = {
|
||||
getTasksByParentSession: () => [
|
||||
{ agent: "council-member", status: "running" },
|
||||
],
|
||||
}
|
||||
|
||||
const handler = createToolExecuteBeforeHandler({
|
||||
ctx,
|
||||
hooks: {},
|
||||
backgroundManager,
|
||||
})
|
||||
|
||||
//#when
|
||||
const run = handler(
|
||||
{ tool: "question", sessionID: "ses_athena", callID: "call_1" },
|
||||
{ args: { questions: [] } }
|
||||
)
|
||||
|
||||
//#then
|
||||
await expect(run).rejects.toThrow("Council members are still running")
|
||||
})
|
||||
|
||||
test("blocks Athena switch_agent while council members are still running", async () => {
|
||||
//#given
|
||||
const ctx = {
|
||||
client: {
|
||||
session: {
|
||||
messages: async () => ({
|
||||
data: [{ info: { role: "assistant", agent: "Athena (Council)" } }],
|
||||
}),
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
const backgroundManager = {
|
||||
getTasksByParentSession: () => [
|
||||
{ agent: "council-member", status: "pending" },
|
||||
],
|
||||
}
|
||||
|
||||
const handler = createToolExecuteBeforeHandler({
|
||||
ctx,
|
||||
hooks: {},
|
||||
backgroundManager,
|
||||
})
|
||||
|
||||
//#when
|
||||
const run = handler(
|
||||
{ tool: "switch_agent", sessionID: "ses_athena", callID: "call_1" },
|
||||
{ args: { agent: "atlas", context: "ctx" } }
|
||||
)
|
||||
|
||||
//#then
|
||||
await expect(run).rejects.toThrow("Council members are still running")
|
||||
})
|
||||
|
||||
test("allows Athena question tool when no council members are pending", async () => {
|
||||
//#given
|
||||
const ctx = {
|
||||
client: {
|
||||
session: {
|
||||
messages: async () => ({
|
||||
data: [{ info: { role: "assistant", agent: "Athena (Council)" } }],
|
||||
}),
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
const backgroundManager = {
|
||||
getTasksByParentSession: () => [
|
||||
{ agent: "council-member", status: "completed" },
|
||||
{ agent: "council-member", status: "cancelled" },
|
||||
],
|
||||
}
|
||||
|
||||
const handler = createToolExecuteBeforeHandler({
|
||||
ctx,
|
||||
hooks: {},
|
||||
backgroundManager,
|
||||
})
|
||||
|
||||
//#when
|
||||
const run = handler(
|
||||
{ tool: "question", sessionID: "ses_athena", callID: "call_1" },
|
||||
{ args: { questions: [] } }
|
||||
)
|
||||
|
||||
//#then
|
||||
await expect(run).resolves.toBeUndefined()
|
||||
})
|
||||
|
||||
test("does not execute subagent question blocker hook for question tool", async () => {
|
||||
//#given
|
||||
const ctx = {
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
import type { PluginContext } from "./types"
|
||||
import { randomUUID } from "node:crypto"
|
||||
import type { BackgroundManager } from "../features/background-agent"
|
||||
|
||||
import { getMainSessionID } from "../features/claude-code-session-state"
|
||||
import { clearBoulderState } from "../features/boulder-state"
|
||||
import { log } from "../shared"
|
||||
import { stripInvisibleAgentCharacters } from "../shared/agent-display-names"
|
||||
import { stripInvisibleAgentCharacters, getAgentConfigKey } from "../shared/agent-display-names"
|
||||
import { resolveSessionAgent } from "./session-agent-resolver"
|
||||
import { parseRalphLoopArguments } from "../hooks/ralph-loop/command-arguments"
|
||||
import { ULTRAWORK_VERIFICATION_PROMISE } from "../hooks/ralph-loop/constants"
|
||||
@@ -25,11 +26,24 @@ function getLoopCommandArguments(args: Record<string, unknown>, command: "ralph-
|
||||
export function createToolExecuteBeforeHandler(args: {
|
||||
ctx: PluginContext
|
||||
hooks: CreatedHooks
|
||||
backgroundManager?: Pick<BackgroundManager, "getTasksByParentSession">
|
||||
}): (
|
||||
input: { tool: string; sessionID: string; callID: string },
|
||||
output: { args: Record<string, unknown> },
|
||||
) => Promise<void> {
|
||||
const { ctx, hooks } = args
|
||||
const { ctx, hooks, backgroundManager } = args
|
||||
|
||||
function hasPendingCouncilMembers(sessionID: string): boolean {
|
||||
if (!backgroundManager) {
|
||||
return false
|
||||
}
|
||||
|
||||
const tasks = backgroundManager.getTasksByParentSession(sessionID)
|
||||
return tasks.some((task) =>
|
||||
task.agent === "council-member" &&
|
||||
(task.status === "pending" || task.status === "running")
|
||||
)
|
||||
}
|
||||
|
||||
function buildUltraworkOracleVerificationPrompt(prompt: string, originalTask: string, verificationAttemptId: string): string {
|
||||
const verificationPrompt = [
|
||||
@@ -62,6 +76,19 @@ export function createToolExecuteBeforeHandler(args: {
|
||||
}
|
||||
}
|
||||
|
||||
const toolNameLower = input.tool?.toLowerCase()
|
||||
|
||||
if (toolNameLower === "question" || toolNameLower === "askuserquestion" || toolNameLower === "ask_user_question" || toolNameLower === "switch_agent") {
|
||||
const sessionAgent = await resolveSessionAgent(ctx.client, input.sessionID)
|
||||
const sessionAgentKey = sessionAgent ? getAgentConfigKey(sessionAgent) : undefined
|
||||
|
||||
if (sessionAgentKey === "athena" && hasPendingCouncilMembers(input.sessionID)) {
|
||||
throw new Error(
|
||||
"Council members are still running. Wait for all launched members to finish and collect their outputs before asking next-step questions or switching agents."
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
await hooks.writeExistingFileGuard?.["tool.execute.before"]?.(input, output)
|
||||
await hooks.questionLabelTruncator?.["tool.execute.before"]?.(input, output)
|
||||
await hooks.claudeCodeHooks?.["tool.execute.before"]?.(input, output)
|
||||
|
||||
Reference in New Issue
Block a user