feat(athena): split into athena (interactive) and athena-junior (subagent)
Split the Athena council orchestrator into two distinct agents: - athena: primary mode, interactive prompt with Question tool and switch_agent - athena-junior: subagent mode, non-interactive prompt with structured JSON output Key changes: - Create athena-junior-agent.ts factory (mode=subagent, denies question+call_omo_agent) - Revert athena agent.ts to primary mode (no env var switching) - Make buildAthenaRuntimeGuidance mode-aware (strips action_paths for non-interactive) - Add mode parameter to council_finalize tool - Register athena-junior in builtin-agents, tool-config, model-requirements - Replace "athena" with "athena-junior" in call_omo_agent ALLOWED_AGENTS - Update all tests for new architecture Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-opencode) Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
This commit is contained in:
@@ -1,27 +1,14 @@
|
|||||||
/// <reference types="bun-types" />
|
/// <reference types="bun-types" />
|
||||||
|
|
||||||
import { afterEach, beforeEach, describe, expect, it } from "bun:test"
|
import { describe, expect, it } from "bun:test"
|
||||||
import { createAthenaAgent } from "./agent"
|
import { createAthenaAgent } from "./agent"
|
||||||
|
import { createAthenaJuniorAgent } from "./athena-junior-agent"
|
||||||
|
|
||||||
describe("createAthenaAgent", () => {
|
describe("createAthenaAgent", () => {
|
||||||
const originalEnv = process.env.OPENCODE_CLI_RUN_MODE
|
|
||||||
|
|
||||||
beforeEach(() => {
|
|
||||||
delete process.env.OPENCODE_CLI_RUN_MODE
|
|
||||||
})
|
|
||||||
|
|
||||||
afterEach(() => {
|
|
||||||
if (originalEnv !== undefined) {
|
|
||||||
process.env.OPENCODE_CLI_RUN_MODE = originalEnv
|
|
||||||
} else {
|
|
||||||
delete process.env.OPENCODE_CLI_RUN_MODE
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
describe("#given the agent mode", () => {
|
describe("#given the agent mode", () => {
|
||||||
describe("#when accessing the static mode property", () => {
|
describe("#when accessing the static mode property", () => {
|
||||||
it("#then equals 'all' to support both primary and subagent contexts", () => {
|
it("#then equals 'primary'", () => {
|
||||||
expect(createAthenaAgent.mode).toBe("all")
|
expect(createAthenaAgent.mode).toBe("primary")
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
@@ -43,23 +30,27 @@ describe("createAthenaAgent", () => {
|
|||||||
expect(config.permission).toBeDefined()
|
expect(config.permission).toBeDefined()
|
||||||
})
|
})
|
||||||
|
|
||||||
it("#then includes a description", () => {
|
it("#then includes a description containing synthesis strategist", () => {
|
||||||
const config = createAthenaAgent("anthropic/claude-opus-4-6")
|
const config = createAthenaAgent("anthropic/claude-opus-4-6")
|
||||||
expect(config.description).toContain("synthesis strategist")
|
expect(config.description).toContain("synthesis strategist")
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
describe("#given default invocation (no CLI mode)", () => {
|
describe("#given the interactive prompt", () => {
|
||||||
describe("#when OPENCODE_CLI_RUN_MODE is not set", () => {
|
describe("#when checking prompt content", () => {
|
||||||
it("#then selects the interactive prompt", () => {
|
it("#then contains Question tool references", () => {
|
||||||
const config = createAthenaAgent("anthropic/claude-opus-4-6")
|
const config = createAthenaAgent("anthropic/claude-opus-4-6")
|
||||||
expect(config.prompt).toContain("Question tool")
|
expect(config.prompt).toContain("Question tool")
|
||||||
})
|
})
|
||||||
|
|
||||||
it("#then contains interactive workflow sections", () => {
|
it("#then contains Step 2: Council setup section", () => {
|
||||||
|
const config = createAthenaAgent("anthropic/claude-opus-4-6")
|
||||||
|
expect(config.prompt).toContain("Step 2: Council setup")
|
||||||
|
})
|
||||||
|
|
||||||
|
it("#then contains agent_handoff section", () => {
|
||||||
const config = createAthenaAgent("anthropic/claude-opus-4-6")
|
const config = createAthenaAgent("anthropic/claude-opus-4-6")
|
||||||
expect(config.prompt).toContain("Step 2: Council setup (default flow before launch).")
|
|
||||||
expect(config.prompt).toContain("<agent_handoff>")
|
expect(config.prompt).toContain("<agent_handoff>")
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -67,40 +58,68 @@ describe("createAthenaAgent", () => {
|
|||||||
const config = createAthenaAgent("anthropic/claude-opus-4-6")
|
const config = createAthenaAgent("anthropic/claude-opus-4-6")
|
||||||
expect(config.prompt).not.toContain("<athena_council_result>")
|
expect(config.prompt).not.toContain("<athena_council_result>")
|
||||||
})
|
})
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
describe("#given CLI run mode", () => {
|
it("#then does not contain NEVER use the Question tool constraint", () => {
|
||||||
describe("#when OPENCODE_CLI_RUN_MODE is 'true'", () => {
|
|
||||||
beforeEach(() => {
|
|
||||||
process.env.OPENCODE_CLI_RUN_MODE = "true"
|
|
||||||
})
|
|
||||||
|
|
||||||
it("#then selects the non-interactive prompt", () => {
|
|
||||||
const config = createAthenaAgent("anthropic/claude-opus-4-6")
|
const config = createAthenaAgent("anthropic/claude-opus-4-6")
|
||||||
expect(config.prompt).toContain("<athena_council_result>")
|
expect(config.prompt).not.toContain("NEVER use the Question tool")
|
||||||
})
|
})
|
||||||
|
})
|
||||||
it("#then contains non-interactive constraints", () => {
|
})
|
||||||
const config = createAthenaAgent("anthropic/claude-opus-4-6")
|
})
|
||||||
expect(config.prompt).toContain("NEVER use the Question tool")
|
|
||||||
})
|
describe("createAthenaJuniorAgent", () => {
|
||||||
|
describe("#given the agent mode", () => {
|
||||||
it("#then does not contain interactive agent handoff section", () => {
|
describe("#when accessing the static mode property", () => {
|
||||||
const config = createAthenaAgent("anthropic/claude-opus-4-6")
|
it("#then equals 'subagent'", () => {
|
||||||
expect(config.prompt).not.toContain("<agent_handoff>")
|
expect(createAthenaJuniorAgent.mode).toBe("subagent")
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
})
|
||||||
describe("#when OPENCODE_CLI_RUN_MODE is 'false'", () => {
|
|
||||||
beforeEach(() => {
|
describe("#given the agent config", () => {
|
||||||
process.env.OPENCODE_CLI_RUN_MODE = "false"
|
describe("#when creating the agent with a model", () => {
|
||||||
})
|
it("#then returns config with the specified model", () => {
|
||||||
|
const config = createAthenaJuniorAgent("anthropic/claude-opus-4-6")
|
||||||
it("#then selects the interactive prompt", () => {
|
expect(config.model).toBe("anthropic/claude-opus-4-6")
|
||||||
const config = createAthenaAgent("anthropic/claude-opus-4-6")
|
})
|
||||||
expect(config.prompt).toContain("Question tool")
|
|
||||||
expect(config.prompt).not.toContain("<athena_council_result>")
|
it("#then sets temperature to 0.1", () => {
|
||||||
|
const config = createAthenaJuniorAgent("anthropic/claude-opus-4-6")
|
||||||
|
expect(config.temperature).toBe(0.1)
|
||||||
|
})
|
||||||
|
|
||||||
|
it("#then includes tool restrictions denying call_omo_agent and question", () => {
|
||||||
|
const config = createAthenaJuniorAgent("anthropic/claude-opus-4-6")
|
||||||
|
expect(config.permission).toBeDefined()
|
||||||
|
})
|
||||||
|
|
||||||
|
it("#then includes a description containing Non-interactive", () => {
|
||||||
|
const config = createAthenaJuniorAgent("anthropic/claude-opus-4-6")
|
||||||
|
expect(config.description).toContain("Non-interactive")
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe("#given the non-interactive prompt", () => {
|
||||||
|
describe("#when checking prompt content", () => {
|
||||||
|
it("#then contains athena_council_result output contract", () => {
|
||||||
|
const config = createAthenaJuniorAgent("anthropic/claude-opus-4-6")
|
||||||
|
expect(config.prompt).toContain("<athena_council_result>")
|
||||||
|
})
|
||||||
|
|
||||||
|
it("#then contains NEVER use the Question tool constraint", () => {
|
||||||
|
const config = createAthenaJuniorAgent("anthropic/claude-opus-4-6")
|
||||||
|
expect(config.prompt).toContain("NEVER use the Question tool")
|
||||||
|
})
|
||||||
|
|
||||||
|
it("#then does not contain agent_handoff section", () => {
|
||||||
|
const config = createAthenaJuniorAgent("anthropic/claude-opus-4-6")
|
||||||
|
expect(config.prompt).not.toContain("<agent_handoff>")
|
||||||
|
})
|
||||||
|
|
||||||
|
it("#then does not contain switch_agent references", () => {
|
||||||
|
const config = createAthenaJuniorAgent("anthropic/claude-opus-4-6")
|
||||||
|
expect(config.prompt).not.toContain("switch_agent")
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
import { describe, expect, it } from "bun:test"
|
import { describe, expect, it } from "bun:test"
|
||||||
import { ATHENA_PROMPT_METADATA, createAthenaAgent } from "./agent"
|
import { ATHENA_PROMPT_METADATA, createAthenaAgent } from "./agent"
|
||||||
|
import { ATHENA_JUNIOR_PROMPT_METADATA } from "./athena-junior-agent"
|
||||||
import { ATHENA_NON_INTERACTIVE_PROMPT } from "./non-interactive-prompt"
|
import { ATHENA_NON_INTERACTIVE_PROMPT } from "./non-interactive-prompt"
|
||||||
|
|
||||||
describe("Athena prompt config injection placeholders", () => {
|
describe("Athena prompt config injection placeholders", () => {
|
||||||
@@ -136,24 +137,62 @@ describe("Athena prompt config injection placeholders", () => {
|
|||||||
describe("Athena prompt metadata", () => {
|
describe("Athena prompt metadata", () => {
|
||||||
describe("#given ATHENA_PROMPT_METADATA", () => {
|
describe("#given ATHENA_PROMPT_METADATA", () => {
|
||||||
describe("#when checking triggers", () => {
|
describe("#when checking triggers", () => {
|
||||||
it("#then includes a Non-interactive council trigger", () => {
|
it("#then does NOT include a Non-interactive council trigger", () => {
|
||||||
const hasNonInteractiveTrigger = ATHENA_PROMPT_METADATA.triggers.some((t) =>
|
const hasNonInteractiveTrigger = ATHENA_PROMPT_METADATA.triggers.some((t) =>
|
||||||
t.domain.includes("Non-interactive"),
|
t.domain.includes("Non-interactive"),
|
||||||
)
|
)
|
||||||
|
expect(hasNonInteractiveTrigger).toBe(false)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe("#when checking useWhen entries", () => {
|
||||||
|
it("#then does NOT include an entry mentioning oh-my-opencode run", () => {
|
||||||
|
const hasCLIEntry = ATHENA_PROMPT_METADATA.useWhen.some((entry) =>
|
||||||
|
entry.includes("oh-my-opencode run"),
|
||||||
|
)
|
||||||
|
expect(hasCLIEntry).toBe(false)
|
||||||
|
})
|
||||||
|
|
||||||
|
it("#then does NOT include an entry mentioning structured council output", () => {
|
||||||
|
const hasStructuredEntry = ATHENA_PROMPT_METADATA.useWhen.some((entry) =>
|
||||||
|
entry.includes("structured") || entry.includes("agent-to-agent"),
|
||||||
|
)
|
||||||
|
expect(hasStructuredEntry).toBe(false)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe("#when checking avoidWhen entries", () => {
|
||||||
|
it("#then includes an entry referencing athena-junior", () => {
|
||||||
|
const hasAthenaJuniorRef = ATHENA_PROMPT_METADATA.avoidWhen?.some((entry) =>
|
||||||
|
entry.includes("athena-junior"),
|
||||||
|
)
|
||||||
|
expect(hasAthenaJuniorRef).toBe(true)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe("Athena-Junior prompt metadata", () => {
|
||||||
|
describe("#given ATHENA_JUNIOR_PROMPT_METADATA", () => {
|
||||||
|
describe("#when checking triggers", () => {
|
||||||
|
it("#then includes a Non-interactive council trigger", () => {
|
||||||
|
const hasNonInteractiveTrigger = ATHENA_JUNIOR_PROMPT_METADATA.triggers.some((t) =>
|
||||||
|
t.domain.includes("Non-interactive"),
|
||||||
|
)
|
||||||
expect(hasNonInteractiveTrigger).toBe(true)
|
expect(hasNonInteractiveTrigger).toBe(true)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
describe("#when checking useWhen entries", () => {
|
describe("#when checking useWhen entries", () => {
|
||||||
it("#then includes an entry mentioning oh-my-opencode run", () => {
|
it("#then includes an entry mentioning oh-my-opencode run", () => {
|
||||||
const hasCLIEntry = ATHENA_PROMPT_METADATA.useWhen.some((entry) =>
|
const hasCLIEntry = ATHENA_JUNIOR_PROMPT_METADATA.useWhen.some((entry) =>
|
||||||
entry.includes("oh-my-opencode run"),
|
entry.includes("oh-my-opencode run"),
|
||||||
)
|
)
|
||||||
expect(hasCLIEntry).toBe(true)
|
expect(hasCLIEntry).toBe(true)
|
||||||
})
|
})
|
||||||
|
|
||||||
it("#then includes an entry mentioning structured council output", () => {
|
it("#then includes an entry mentioning structured or agent-to-agent", () => {
|
||||||
const hasStructuredEntry = ATHENA_PROMPT_METADATA.useWhen.some((entry) =>
|
const hasStructuredEntry = ATHENA_JUNIOR_PROMPT_METADATA.useWhen.some((entry) =>
|
||||||
entry.includes("structured") || entry.includes("agent-to-agent"),
|
entry.includes("structured") || entry.includes("agent-to-agent"),
|
||||||
)
|
)
|
||||||
expect(hasStructuredEntry).toBe(true)
|
expect(hasStructuredEntry).toBe(true)
|
||||||
|
|||||||
@@ -0,0 +1,40 @@
|
|||||||
|
import type { AgentConfig } from "@opencode-ai/sdk"
|
||||||
|
import type { AgentMode, AgentPromptMetadata } from "../types"
|
||||||
|
import { createAgentToolRestrictions } from "../../shared/permission-compat"
|
||||||
|
import { ATHENA_NON_INTERACTIVE_PROMPT } from "./non-interactive-prompt"
|
||||||
|
|
||||||
|
const MODE: AgentMode = "subagent"
|
||||||
|
|
||||||
|
export const ATHENA_JUNIOR_PROMPT_METADATA: AgentPromptMetadata = {
|
||||||
|
category: "advisor",
|
||||||
|
cost: "EXPENSIVE",
|
||||||
|
promptAlias: "Athena-Junior",
|
||||||
|
triggers: [
|
||||||
|
{ domain: "Non-interactive council", trigger: "Agent needs multi-model analysis without user interaction" },
|
||||||
|
{ domain: "Programmatic synthesis", trigger: "Need structured council output for automated processing" },
|
||||||
|
],
|
||||||
|
useWhen: [
|
||||||
|
"CLI invocation via oh-my-opencode run needing structured council output",
|
||||||
|
"Agent-to-agent invocation where structured <athena_council_result> JSON is required",
|
||||||
|
"Automated pipelines needing multi-model consensus",
|
||||||
|
],
|
||||||
|
avoidWhen: [
|
||||||
|
"Interactive sessions where user can confirm actions (use athena instead)",
|
||||||
|
"Single-model questions that do not need council synthesis",
|
||||||
|
],
|
||||||
|
}
|
||||||
|
|
||||||
|
export function createAthenaJuniorAgent(model: string): AgentConfig {
|
||||||
|
const restrictions = createAgentToolRestrictions(["call_omo_agent", "question"])
|
||||||
|
return {
|
||||||
|
description:
|
||||||
|
"Non-interactive council orchestrator for programmatic multi-model synthesis. Returns structured <athena_council_result> JSON without user interaction. (Athena-Junior - OhMyOpenCode)",
|
||||||
|
mode: MODE,
|
||||||
|
model,
|
||||||
|
temperature: 0.1,
|
||||||
|
permission: restrictions.permission,
|
||||||
|
prompt: ATHENA_NON_INTERACTIVE_PROMPT,
|
||||||
|
color: "#1F8EFA",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
createAthenaJuniorAgent.mode = MODE
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
import { describe, expect, it } from "bun:test"
|
import { describe, expect, it } from "bun:test"
|
||||||
import { resolveCouncilIntent, buildAthenaRuntimeGuidance, getValidCouncilIntents } from "./council-runtime-guidance"
|
import { resolveCouncilIntent, buildAthenaRuntimeGuidance, getValidCouncilIntents, type CouncilGuidanceMode } from "./council-runtime-guidance"
|
||||||
|
|
||||||
describe("council-runtime-guidance", () => {
|
describe("council-runtime-guidance", () => {
|
||||||
describe("resolveCouncilIntent", () => {
|
describe("resolveCouncilIntent", () => {
|
||||||
@@ -116,7 +116,7 @@ describe("council-runtime-guidance", () => {
|
|||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
describe("#when building guidance for each intent", () => {
|
describe("#when building guidance for each intent in interactive mode (default)", () => {
|
||||||
const allIntents = ["DIAGNOSE", "AUDIT", "PLAN", "EVALUATE", "EXPLAIN", "CREATE", "PERSPECTIVES", "FREEFORM"] as const
|
const allIntents = ["DIAGNOSE", "AUDIT", "PLAN", "EVALUATE", "EXPLAIN", "CREATE", "PERSPECTIVES", "FREEFORM"] as const
|
||||||
|
|
||||||
for (const intent of allIntents) {
|
for (const intent of allIntents) {
|
||||||
@@ -125,7 +125,7 @@ describe("council-runtime-guidance", () => {
|
|||||||
expect(result).toContain("runtime_synthesis_rules")
|
expect(result).toContain("runtime_synthesis_rules")
|
||||||
})
|
})
|
||||||
|
|
||||||
it(`#then ${intent} guidance contains runtime_action_paths`, () => {
|
it(`#then ${intent} guidance contains runtime_action_paths in interactive mode`, () => {
|
||||||
const result = buildAthenaRuntimeGuidance(intent)
|
const result = buildAthenaRuntimeGuidance(intent)
|
||||||
expect(result).toContain("runtime_action_paths")
|
expect(result).toContain("runtime_action_paths")
|
||||||
})
|
})
|
||||||
@@ -139,6 +139,62 @@ describe("council-runtime-guidance", () => {
|
|||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
describe("#given non-interactive mode", () => {
|
||||||
|
const allIntents = ["DIAGNOSE", "AUDIT", "PLAN", "EVALUATE", "EXPLAIN", "CREATE", "PERSPECTIVES", "FREEFORM"] as const
|
||||||
|
|
||||||
|
describe("#when building guidance with non-interactive mode", () => {
|
||||||
|
for (const intent of allIntents) {
|
||||||
|
it(`#then ${intent} guidance contains runtime_synthesis_rules`, () => {
|
||||||
|
const result = buildAthenaRuntimeGuidance(intent, "non-interactive")
|
||||||
|
expect(result).toContain("runtime_synthesis_rules")
|
||||||
|
})
|
||||||
|
|
||||||
|
it(`#then ${intent} guidance does NOT contain runtime_action_paths`, () => {
|
||||||
|
const result = buildAthenaRuntimeGuidance(intent, "non-interactive")
|
||||||
|
expect(result).not.toContain("runtime_action_paths")
|
||||||
|
})
|
||||||
|
|
||||||
|
it(`#then ${intent} guidance contains mode: non-interactive`, () => {
|
||||||
|
const result = buildAthenaRuntimeGuidance(intent, "non-interactive")
|
||||||
|
expect(result).toContain("mode: non-interactive")
|
||||||
|
})
|
||||||
|
|
||||||
|
it(`#then ${intent} guidance contains source: council_finalize`, () => {
|
||||||
|
const result = buildAthenaRuntimeGuidance(intent, "non-interactive")
|
||||||
|
expect(result).toContain("source: council_finalize")
|
||||||
|
})
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
describe("#when building guidance with explicit interactive mode", () => {
|
||||||
|
for (const intent of allIntents) {
|
||||||
|
it(`#then ${intent} guidance contains both synthesis_rules and action_paths`, () => {
|
||||||
|
const result = buildAthenaRuntimeGuidance(intent, "interactive")
|
||||||
|
expect(result).toContain("runtime_synthesis_rules")
|
||||||
|
expect(result).toContain("runtime_action_paths")
|
||||||
|
})
|
||||||
|
|
||||||
|
it(`#then ${intent} guidance contains mode: interactive`, () => {
|
||||||
|
const result = buildAthenaRuntimeGuidance(intent, "interactive")
|
||||||
|
expect(result).toContain("mode: interactive")
|
||||||
|
})
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe("CouncilGuidanceMode type", () => {
|
||||||
|
describe("#given the type is imported", () => {
|
||||||
|
describe("#when used as a type annotation", () => {
|
||||||
|
it("#then accepts interactive and non-interactive values", () => {
|
||||||
|
const interactive: CouncilGuidanceMode = "interactive"
|
||||||
|
const nonInteractive: CouncilGuidanceMode = "non-interactive"
|
||||||
|
expect(interactive).toBe("interactive")
|
||||||
|
expect(nonInteractive).toBe("non-interactive")
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
describe("getValidCouncilIntents", () => {
|
describe("getValidCouncilIntents", () => {
|
||||||
describe("#given the function is called", () => {
|
describe("#given the function is called", () => {
|
||||||
describe("#when retrieving valid intents", () => {
|
describe("#when retrieving valid intents", () => {
|
||||||
|
|||||||
@@ -50,12 +50,25 @@ export function resolveCouncilIntent(intent?: string): CouncilIntent | null {
|
|||||||
: null
|
: null
|
||||||
}
|
}
|
||||||
|
|
||||||
export function buildAthenaRuntimeGuidance(intent: CouncilIntent): string {
|
export type CouncilGuidanceMode = "interactive" | "non-interactive"
|
||||||
|
|
||||||
|
export function buildAthenaRuntimeGuidance(intent: CouncilIntent, mode: CouncilGuidanceMode = "interactive"): string {
|
||||||
|
let guidanceContent = RUNTIME_GUIDANCE_BY_INTENT[intent].trim()
|
||||||
|
|
||||||
|
if (mode === "non-interactive") {
|
||||||
|
guidanceContent = stripActionPaths(guidanceContent)
|
||||||
|
}
|
||||||
|
|
||||||
return [
|
return [
|
||||||
"<athena_runtime_guidance>",
|
"<athena_runtime_guidance>",
|
||||||
"source: council_finalize",
|
"source: council_finalize",
|
||||||
`intent: ${intent}`,
|
`intent: ${intent}`,
|
||||||
RUNTIME_GUIDANCE_BY_INTENT[intent].trim(),
|
`mode: ${mode}`,
|
||||||
|
guidanceContent,
|
||||||
"</athena_runtime_guidance>",
|
"</athena_runtime_guidance>",
|
||||||
].join("\n\n")
|
].join("\n\n")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function stripActionPaths(guidance: string): string {
|
||||||
|
return guidance.replace(/<runtime_action_paths>[\s\S]*?<\/runtime_action_paths>/g, "").trim()
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
export { createAthenaAgent, ATHENA_PROMPT_METADATA } from "./agent"
|
export { createAthenaAgent, ATHENA_PROMPT_METADATA } from "./agent"
|
||||||
|
export { createAthenaJuniorAgent, ATHENA_JUNIOR_PROMPT_METADATA } from "./athena-junior-agent"
|
||||||
export { createCouncilMemberAgent, COUNCIL_MEMBER_PROMPT, COUNCIL_SOLO_ADDENDUM, COUNCIL_DELEGATION_ADDENDUM } from "./council-member-agent"
|
export { createCouncilMemberAgent, COUNCIL_MEMBER_PROMPT, COUNCIL_SOLO_ADDENDUM, COUNCIL_DELEGATION_ADDENDUM } from "./council-member-agent"
|
||||||
export { COUNCIL_INTENT_ADDENDUMS } from "./council-intent-addendums"
|
export { COUNCIL_INTENT_ADDENDUMS } from "./council-intent-addendums"
|
||||||
export {
|
export {
|
||||||
@@ -6,7 +7,7 @@ export {
|
|||||||
getValidCouncilIntents,
|
getValidCouncilIntents,
|
||||||
resolveCouncilIntent,
|
resolveCouncilIntent,
|
||||||
} from "./council-runtime-guidance"
|
} from "./council-runtime-guidance"
|
||||||
export type { CouncilIntent } from "./council-runtime-guidance"
|
export type { CouncilIntent, CouncilGuidanceMode } from "./council-runtime-guidance"
|
||||||
export { COUNCIL_DEFAULTS } from "./constants"
|
export { COUNCIL_DEFAULTS } from "./constants"
|
||||||
export { ATHENA_INTERACTIVE_PROMPT } from "./interactive-prompt"
|
export { ATHENA_INTERACTIVE_PROMPT } from "./interactive-prompt"
|
||||||
export { ATHENA_NON_INTERACTIVE_PROMPT } from "./non-interactive-prompt"
|
export { ATHENA_NON_INTERACTIVE_PROMPT } from "./non-interactive-prompt"
|
||||||
|
|||||||
@@ -72,7 +72,7 @@ Launch ALL members before collecting results. Track every task_id.
|
|||||||
- Repeat until ALL members reach terminal state.
|
- Repeat until ALL members reach terminal state.
|
||||||
|
|
||||||
### Step 6: Collect results with council_finalize.
|
### Step 6: Collect results with council_finalize.
|
||||||
- Call: council_finalize(task_ids=[...], name="{topic-slug}", intent="{intent}", question="{original question}", prompt_file="{path from Step 4.1}")
|
- Call: council_finalize(task_ids=[...], name="{topic-slug}", intent="{intent}", question="{original question}", prompt_file="{path from Step 4.1}", mode="non-interactive")
|
||||||
- council_finalize extracts responses, writes archives, returns structured JSON with archive_dir and members array.
|
- council_finalize extracts responses, writes archives, returns structured JSON with archive_dir and members array.
|
||||||
- Read each member's archive_file using Read tool for synthesis input.
|
- Read each member's archive_file using Read tool for synthesis input.
|
||||||
|
|
||||||
|
|||||||
@@ -136,6 +136,16 @@ export function applyToolConfig(params: {
|
|||||||
question: questionPermission,
|
question: questionPermission,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
const athenaJunior = agentByKey(params.agentResult, "athena-junior");
|
||||||
|
if (athenaJunior) {
|
||||||
|
athenaJunior.permission = {
|
||||||
|
...athenaJunior.permission,
|
||||||
|
task: "allow",
|
||||||
|
prepare_council_prompt: "allow",
|
||||||
|
council_finalize: "allow",
|
||||||
|
question: "deny",
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
params.config.permission = {
|
params.config.permission = {
|
||||||
webfetch: "allow",
|
webfetch: "allow",
|
||||||
|
|||||||
@@ -51,6 +51,11 @@ const AGENT_RESTRICTIONS: Record<string, Record<string, boolean>> = {
|
|||||||
call_omo_agent: false,
|
call_omo_agent: false,
|
||||||
},
|
},
|
||||||
|
|
||||||
|
"athena-junior": {
|
||||||
|
call_omo_agent: false,
|
||||||
|
question: false,
|
||||||
|
},
|
||||||
|
|
||||||
// NOTE: Athena/council tool restrictions are also defined in:
|
// NOTE: Athena/council tool restrictions are also defined in:
|
||||||
// - src/agents/athena/agent.ts (AgentConfig permission format)
|
// - src/agents/athena/agent.ts (AgentConfig permission format)
|
||||||
// - src/agents/athena/council-member-agent.ts (AgentConfig permission format — allow-list)
|
// - src/agents/athena/council-member-agent.ts (AgentConfig permission format — allow-list)
|
||||||
|
|||||||
@@ -190,6 +190,16 @@ export const AGENT_MODEL_REQUIREMENTS: Record<string, ModelRequirement> = {
|
|||||||
{ providers: ["google", "github-copilot", "opencode"], model: "gemini-3-pro", variant: "high" },
|
{ providers: ["google", "github-copilot", "opencode"], model: "gemini-3-pro", variant: "high" },
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
|
"athena-junior": {
|
||||||
|
fallbackChain: [
|
||||||
|
{ providers: ["anthropic", "github-copilot", "opencode"], model: "claude-opus-4-6", variant: "max" },
|
||||||
|
{ providers: ["opencode"], model: "kimi-k2.5-free" },
|
||||||
|
{ providers: ["zai-coding-plan"], model: "glm-4.7" },
|
||||||
|
{ providers: ["opencode"], model: "glm-4.7-free" },
|
||||||
|
{ providers: ["openai", "github-copilot", "opencode"], model: "gpt-5.2", variant: "high" },
|
||||||
|
{ providers: ["google", "github-copilot", "opencode"], model: "gemini-3-pro", variant: "high" },
|
||||||
|
],
|
||||||
|
},
|
||||||
"council-member": {
|
"council-member": {
|
||||||
fallbackChain: [
|
fallbackChain: [
|
||||||
{ providers: ["opencode"], model: "gpt-5-nano" },
|
{ providers: ["opencode"], model: "gpt-5-nano" },
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ export const ALLOWED_AGENTS = [
|
|||||||
"metis",
|
"metis",
|
||||||
"momus",
|
"momus",
|
||||||
"multimodal-looker",
|
"multimodal-looker",
|
||||||
"athena",
|
"athena-junior",
|
||||||
] as const
|
] as const
|
||||||
|
|
||||||
export const CALL_OMO_AGENT_DESCRIPTION = `Spawn explore/librarian agent or custom agents. run_in_background REQUIRED (true=async with task_id, false=sync).
|
export const CALL_OMO_AGENT_DESCRIPTION = `Spawn explore/librarian agent or custom agents. run_in_background REQUIRED (true=async with task_id, false=sync).
|
||||||
|
|||||||
@@ -512,7 +512,7 @@ describe("createCallOmoAgent", () => {
|
|||||||
expect(result).toContain("background_task.maxDepth=3")
|
expect(result).toContain("background_task.maxDepth=3")
|
||||||
})
|
})
|
||||||
|
|
||||||
test("should accept athena as a valid agent type", async () => {
|
test("should accept athena-junior as a valid agent type", async () => {
|
||||||
//#given
|
//#given
|
||||||
const toolDef = createCallOmoAgent(mockCtx, mockBackgroundManager, [])
|
const toolDef = createCallOmoAgent(mockCtx, mockBackgroundManager, [])
|
||||||
const executeFunc = toolDef.execute as Function
|
const executeFunc = toolDef.execute as Function
|
||||||
@@ -522,7 +522,7 @@ describe("createCallOmoAgent", () => {
|
|||||||
{
|
{
|
||||||
description: "Test",
|
description: "Test",
|
||||||
prompt: "Test prompt",
|
prompt: "Test prompt",
|
||||||
subagent_type: "athena",
|
subagent_type: "athena-junior",
|
||||||
run_in_background: true,
|
run_in_background: true,
|
||||||
},
|
},
|
||||||
{ sessionID: "test", messageID: "msg", agent: "test", abort: new AbortController().signal }
|
{ sessionID: "test", messageID: "msg", agent: "test", abort: new AbortController().signal }
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ import {
|
|||||||
resolveCouncilIntent,
|
resolveCouncilIntent,
|
||||||
COUNCIL_DEFAULTS,
|
COUNCIL_DEFAULTS,
|
||||||
} from "../../agents/athena"
|
} from "../../agents/athena"
|
||||||
|
import type { CouncilGuidanceMode } from "../../agents/athena"
|
||||||
import type { CouncilFinalizeArgs, CouncilMemberResult, CouncilFinalizeResult } from "./types"
|
import type { CouncilFinalizeArgs, CouncilMemberResult, CouncilFinalizeResult } from "./types"
|
||||||
|
|
||||||
export function createCouncilFinalize(
|
export function createCouncilFinalize(
|
||||||
@@ -30,6 +31,10 @@ export function createCouncilFinalize(
|
|||||||
.describe(`Classified question intent used for runtime Athena guidance injection. Valid intents: ${getValidCouncilIntents().join(", ")}`),
|
.describe(`Classified question intent used for runtime Athena guidance injection. Valid intents: ${getValidCouncilIntents().join(", ")}`),
|
||||||
question: tool.schema.string().optional().describe("Original user question that triggered the council"),
|
question: tool.schema.string().optional().describe("Original user question that triggered the council"),
|
||||||
prompt_file: tool.schema.string().optional().describe("Path to the council prompt temp file (will be moved into the archive)"),
|
prompt_file: tool.schema.string().optional().describe("Path to the council prompt temp file (will be moved into the archive)"),
|
||||||
|
mode: tool.schema
|
||||||
|
.string()
|
||||||
|
.optional()
|
||||||
|
.describe('Council guidance mode: "interactive" (default) includes action paths with Question tool, "non-interactive" strips action paths for programmatic use'),
|
||||||
},
|
},
|
||||||
async execute(args: CouncilFinalizeArgs, toolContext) {
|
async execute(args: CouncilFinalizeArgs, toolContext) {
|
||||||
const resolvedIntent = resolveCouncilIntent(args.intent)
|
const resolvedIntent = resolveCouncilIntent(args.intent)
|
||||||
@@ -154,7 +159,8 @@ export function createCouncilFinalize(
|
|||||||
members,
|
members,
|
||||||
}
|
}
|
||||||
|
|
||||||
const guidance = buildAthenaRuntimeGuidance(resolvedIntent)
|
const resolvedMode = (args.mode === "non-interactive" ? "non-interactive" : "interactive") as CouncilGuidanceMode
|
||||||
|
const guidance = buildAthenaRuntimeGuidance(resolvedIntent, resolvedMode)
|
||||||
return JSON.stringify(result, null, 2) + "\n\n" + guidance
|
return JSON.stringify(result, null, 2) + "\n\n" + guidance
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ export interface CouncilFinalizeArgs {
|
|||||||
intent: string
|
intent: string
|
||||||
question?: string
|
question?: string
|
||||||
prompt_file?: string
|
prompt_file?: string
|
||||||
|
mode?: string
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface CouncilMemberResult {
|
export interface CouncilMemberResult {
|
||||||
|
|||||||
Reference in New Issue
Block a user