Files
oh-my-opencode-free-fork/src/tools/call-omo-agent/tools.ts
YeonGyu-Kim ed76c502c3 feat(background-agent): restrict tool access in subagent execution to prevent recursive calls
- Disable 'task' and 'call_omo_agent' tools in BackgroundManager
- Disable recursive background operation tools in call_omo_agent sync execution
- Prevents agents from spawning background tasks or calling themselves

🤖 GENERATED WITH ASSISTANCE OF [OhMyOpenCode](https://github.com/code-yeongyu/oh-my-opencode)
2025-12-14 14:00:28 +09:00

179 lines
6.7 KiB
TypeScript

import { tool, type PluginInput } from "@opencode-ai/plugin"
import { ALLOWED_AGENTS, CALL_OMO_AGENT_DESCRIPTION } from "./constants"
import type { CallOmoAgentArgs } from "./types"
import type { BackgroundManager } from "../../features/background-agent"
import { log } from "../../shared/logger"
export function createCallOmoAgent(
ctx: PluginInput,
backgroundManager: BackgroundManager
) {
const agentDescriptions = ALLOWED_AGENTS.map(
(name) => `- ${name}: Specialized agent for ${name} tasks`
).join("\n")
const description = CALL_OMO_AGENT_DESCRIPTION.replace("{agents}", agentDescriptions)
return tool({
description,
args: {
description: tool.schema.string().describe("A short (3-5 words) description of the task"),
prompt: tool.schema.string().describe("The task for the agent to perform"),
subagent_type: tool.schema
.enum(ALLOWED_AGENTS)
.describe("The type of specialized agent to use for this task (explore or librarian only)"),
run_in_background: tool.schema
.boolean()
.describe("REQUIRED. true: run asynchronously (use background_output to get results), false: run synchronously and wait for completion"),
session_id: tool.schema.string().describe("Existing Task session to continue").optional(),
},
async execute(args: CallOmoAgentArgs, toolContext) {
log(`[call_omo_agent] Starting with agent: ${args.subagent_type}, background: ${args.run_in_background}`)
if (!ALLOWED_AGENTS.includes(args.subagent_type as typeof ALLOWED_AGENTS[number])) {
return `Error: Invalid agent type "${args.subagent_type}". Only ${ALLOWED_AGENTS.join(", ")} are allowed.`
}
if (args.run_in_background) {
if (args.session_id) {
return `Error: session_id is not supported in background mode. Use run_in_background=false to continue an existing session.`
}
return await executeBackground(args, toolContext, backgroundManager)
}
return await executeSync(args, toolContext, ctx)
},
})
}
async function executeBackground(
args: CallOmoAgentArgs,
toolContext: { sessionID: string; messageID: string },
manager: BackgroundManager
): Promise<string> {
try {
const task = await manager.launch({
description: args.description,
prompt: args.prompt,
agent: args.subagent_type,
parentSessionID: toolContext.sessionID,
parentMessageID: toolContext.messageID,
})
return `Background agent task launched successfully.
Task ID: ${task.id}
Session ID: ${task.sessionID}
Description: ${task.description}
Agent: ${task.agent} (subagent)
Status: ${task.status}
The system will notify you when the task completes.
Use \`background_output\` tool with task_id="${task.id}" to check progress:
- block=false (default): Check status immediately - returns full status info
- block=true: Wait for completion (rarely needed since system notifies)`
} catch (error) {
const message = error instanceof Error ? error.message : String(error)
return `Failed to launch background agent task: ${message}`
}
}
async function executeSync(
args: CallOmoAgentArgs,
toolContext: { sessionID: string },
ctx: PluginInput
): Promise<string> {
let sessionID: string
if (args.session_id) {
log(`[call_omo_agent] Using existing session: ${args.session_id}`)
const sessionResult = await ctx.client.session.get({
path: { id: args.session_id },
})
if (sessionResult.error) {
log(`[call_omo_agent] Session get error:`, sessionResult.error)
return `Error: Failed to get existing session: ${sessionResult.error}`
}
sessionID = args.session_id
} else {
log(`[call_omo_agent] Creating new session with parent: ${toolContext.sessionID}`)
const createResult = await ctx.client.session.create({
body: {
parentID: toolContext.sessionID,
title: `${args.description} (@${args.subagent_type} subagent)`,
},
})
if (createResult.error) {
log(`[call_omo_agent] Session create error:`, createResult.error)
return `Error: Failed to create session: ${createResult.error}`
}
sessionID = createResult.data.id
log(`[call_omo_agent] Created session: ${sessionID}`)
}
log(`[call_omo_agent] Sending prompt to session ${sessionID}`)
log(`[call_omo_agent] Prompt text:`, args.prompt.substring(0, 100))
try {
await ctx.client.session.prompt({
path: { id: sessionID },
body: {
agent: args.subagent_type,
tools: {
task: false,
call_omo_agent: false,
background_task: false,
},
parts: [{ type: "text", text: args.prompt }],
},
})
} catch (error) {
const errorMessage = error instanceof Error ? error.message : String(error)
log(`[call_omo_agent] Prompt error:`, errorMessage)
if (errorMessage.includes("agent.name") || errorMessage.includes("undefined")) {
return `Error: Agent "${args.subagent_type}" not found. Make sure the agent is registered in your opencode.json or provided by a plugin.\n\n<task_metadata>\nsession_id: ${sessionID}\n</task_metadata>`
}
return `Error: Failed to send prompt: ${errorMessage}\n\n<task_metadata>\nsession_id: ${sessionID}\n</task_metadata>`
}
log(`[call_omo_agent] Prompt sent, fetching messages...`)
const messagesResult = await ctx.client.session.messages({
path: { id: sessionID },
})
if (messagesResult.error) {
log(`[call_omo_agent] Messages error:`, messagesResult.error)
return `Error: Failed to get messages: ${messagesResult.error}`
}
const messages = messagesResult.data
log(`[call_omo_agent] Got ${messages.length} messages`)
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const lastAssistantMessage = messages
.filter((m: any) => m.info.role === "assistant")
.sort((a: any, b: any) => (b.info.time?.created || 0) - (a.info.time?.created || 0))[0]
if (!lastAssistantMessage) {
log(`[call_omo_agent] No assistant message found`)
log(`[call_omo_agent] All messages:`, JSON.stringify(messages, null, 2))
return `Error: No assistant response found\n\n<task_metadata>\nsession_id: ${sessionID}\n</task_metadata>`
}
log(`[call_omo_agent] Found assistant message with ${lastAssistantMessage.parts.length} parts`)
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const textParts = lastAssistantMessage.parts.filter((p: any) => p.type === "text")
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const responseText = textParts.map((p: any) => p.text).join("\n")
log(`[call_omo_agent] Got response, length: ${responseText.length}`)
const output =
responseText + "\n\n" + ["<task_metadata>", `session_id: ${sessionID}`, "</task_metadata>"].join("\n")
return output
}