refactor(athena): rename session_handoff to switch_agent to avoid confusion with /handoff command

Rename across all layers to eliminate naming ambiguity:
- Tool: session_handoff → switch_agent
- Hook: agent-handoff → agent-switch
- Feature: agent-handoff/ → agent-switch/
- Types: SessionHandoffArgs → SwitchAgentArgs, PendingHandoff → PendingSwitch
- Functions: setPendingHandoff → setPendingSwitch, consumePendingHandoff → consumePendingSwitch

/handoff = inter-session context summary (existing command)
switch_agent = intra-session active agent change (our new tool)
This commit is contained in:
ismeth
2026-02-13 13:18:07 +01:00
committed by YeonGyu-Kim
parent 7db0f110ee
commit 84185490f9
22 changed files with 139 additions and 139 deletions
+1
View File
@@ -0,0 +1 @@
export { createSwitchAgentTool } from "./tools"
+91
View File
@@ -0,0 +1,91 @@
import { describe, test, expect, beforeEach } from "bun:test"
import { createSwitchAgentTool } from "./tools"
import { consumePendingSwitch, _resetForTesting as resetSwitch } from "../../features/agent-switch"
import { getSessionAgent, _resetForTesting as resetSession } from "../../features/claude-code-session-state"
describe("switch_agent tool", () => {
const sessionID = "test-session-123"
const messageID = "msg-456"
const agent = "athena"
const toolContext = {
sessionID,
messageID,
agent,
abort: new AbortController().signal,
}
beforeEach(() => {
resetSwitch()
resetSession()
})
//#given valid atlas switch args
//#when execute is called
//#then it stores pending switch and updates session agent
test("should queue switch to atlas", async () => {
const tool = createSwitchAgentTool()
const result = await tool.execute(
{ agent: "atlas", context: "Fix the auth bug based on council findings" },
toolContext
)
expect(result).toContain("atlas")
expect(result).toContain("switch")
const entry = consumePendingSwitch(sessionID)
expect(entry).toEqual({
agent: "atlas",
context: "Fix the auth bug based on council findings",
})
expect(getSessionAgent(sessionID)).toBe("atlas")
})
//#given valid prometheus switch args
//#when execute is called
//#then it stores pending switch for prometheus
test("should queue switch to prometheus", async () => {
const tool = createSwitchAgentTool()
const result = await tool.execute(
{ agent: "Prometheus", context: "Create a plan for the refactoring" },
toolContext
)
expect(result).toContain("prometheus")
expect(result).toContain("switch")
const entry = consumePendingSwitch(sessionID)
expect(entry?.agent).toBe("prometheus")
})
//#given an invalid agent name
//#when execute is called
//#then it returns an error
test("should reject invalid agent names", async () => {
const tool = createSwitchAgentTool()
const result = await tool.execute(
{ agent: "librarian", context: "Some context" },
toolContext
)
expect(result).toContain("Invalid switch target")
expect(result).toContain("librarian")
expect(consumePendingSwitch(sessionID)).toBeUndefined()
})
//#given agent name with different casing
//#when execute is called
//#then it normalizes to lowercase
test("should handle case-insensitive agent names", async () => {
const tool = createSwitchAgentTool()
await tool.execute(
{ agent: "ATLAS", context: "Fix things" },
toolContext
)
const entry = consumePendingSwitch(sessionID)
expect(entry?.agent).toBe("atlas")
expect(getSessionAgent(sessionID)).toBe("atlas")
})
})
+37
View File
@@ -0,0 +1,37 @@
import { tool, type ToolDefinition } from "@opencode-ai/plugin"
import { setPendingSwitch } from "../../features/agent-switch"
import { updateSessionAgent } from "../../features/claude-code-session-state"
import type { SwitchAgentArgs } from "./types"
const DESCRIPTION =
"Switch the active session agent. After calling this tool, the session will transition to the specified agent " +
"with the provided context as its starting prompt. Use this to route work to another agent " +
"(e.g., Atlas for fixes, Prometheus for planning). The switch executes when the current agent's turn completes."
const ALLOWED_AGENTS = new Set(["atlas", "prometheus", "sisyphus", "hephaestus"])
export function createSwitchAgentTool(): ToolDefinition {
return tool({
description: DESCRIPTION,
args: {
agent: tool.schema
.string()
.describe("Target agent name to switch to (e.g., 'atlas', 'prometheus')"),
context: tool.schema
.string()
.describe("Context message for the target agent — include confirmed findings, the original question, and what action to take"),
},
async execute(args: SwitchAgentArgs, toolContext) {
const agentName = args.agent.toLowerCase()
if (!ALLOWED_AGENTS.has(agentName)) {
return `Invalid switch target: "${args.agent}". Allowed agents: ${[...ALLOWED_AGENTS].join(", ")}`
}
updateSessionAgent(toolContext.sessionID, agentName)
setPendingSwitch(toolContext.sessionID, agentName, args.context)
return `Agent switch queued. Session will switch to ${agentName} when your turn completes.`
},
})
}
+4
View File
@@ -0,0 +1,4 @@
export interface SwitchAgentArgs {
agent: string
context: string
}