feat(omo-task): add agent orchestration tool for subagent spawning
Implement omo_task tool that allows main agents (oracle, frontend-ui-ux-engineer, etc.) to spawn explore or librarian as subagents. - Add constants: ALLOWED_AGENTS, TASK_TOOL_DESCRIPTION_TEMPLATE - Add types: AllowedAgentType, OmoTaskArgs, OmoTaskResult - Implement createOmoTask function with session management - Support both new session creation and existing session continuation - Include proper error handling and logging 🤖 GENERATED WITH ASSISTANCE OF [OhMyOpenCode](https://github.com/code-yeongyu/oh-my-opencode)
This commit is contained in:
17
src/tools/omo-task/constants.ts
Normal file
17
src/tools/omo-task/constants.ts
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
export const ALLOWED_AGENTS = ["explore", "librarian"] as const
|
||||||
|
|
||||||
|
export const TASK_TOOL_DESCRIPTION_TEMPLATE = `Launch a new agent to handle complex, multi-step tasks autonomously.
|
||||||
|
|
||||||
|
This is a restricted version of the Task tool that only allows spawning explore and librarian agents.
|
||||||
|
|
||||||
|
Available agent types:
|
||||||
|
{agents}
|
||||||
|
|
||||||
|
When using this tool, you must specify a subagent_type parameter to select which agent type to use.
|
||||||
|
|
||||||
|
Usage notes:
|
||||||
|
1. Launch multiple agents concurrently whenever possible, to maximize performance
|
||||||
|
2. When the agent is done, it will return a single message back to you
|
||||||
|
3. Each agent invocation is stateless unless you provide a session_id
|
||||||
|
4. Your prompt should contain a highly detailed task description for the agent to perform autonomously
|
||||||
|
5. Clearly tell the agent whether you expect it to write code or just to do research`
|
||||||
3
src/tools/omo-task/index.ts
Normal file
3
src/tools/omo-task/index.ts
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
export * from "./types"
|
||||||
|
export * from "./constants"
|
||||||
|
export { createOmoTask } from "./tools"
|
||||||
111
src/tools/omo-task/tools.ts
Normal file
111
src/tools/omo-task/tools.ts
Normal file
@@ -0,0 +1,111 @@
|
|||||||
|
import { tool, type PluginInput } from "@opencode-ai/plugin"
|
||||||
|
import { ALLOWED_AGENTS, TASK_TOOL_DESCRIPTION_TEMPLATE } from "./constants"
|
||||||
|
import type { OmoTaskArgs } from "./types"
|
||||||
|
import { log } from "../../shared/logger"
|
||||||
|
|
||||||
|
export function createOmoTask(ctx: PluginInput) {
|
||||||
|
const agentDescriptions = ALLOWED_AGENTS.map((name) => `- ${name}: Specialized agent for ${name} tasks`).join(
|
||||||
|
"\n"
|
||||||
|
)
|
||||||
|
const description = TASK_TOOL_DESCRIPTION_TEMPLATE.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)"),
|
||||||
|
session_id: tool.schema.string().describe("Existing Task session to continue").optional(),
|
||||||
|
},
|
||||||
|
async execute(args: OmoTaskArgs, toolContext) {
|
||||||
|
log(`[omo_task] Starting with agent: ${args.subagent_type}`)
|
||||||
|
|
||||||
|
if (!ALLOWED_AGENTS.includes(args.subagent_type as any)) {
|
||||||
|
return `Error: Invalid agent type "${args.subagent_type}". Only ${ALLOWED_AGENTS.join(", ")} are allowed.`
|
||||||
|
}
|
||||||
|
|
||||||
|
let sessionID: string
|
||||||
|
|
||||||
|
if (args.session_id) {
|
||||||
|
log(`[omo_task] Using existing session: ${args.session_id}`)
|
||||||
|
const sessionResult = await ctx.client.session.get({
|
||||||
|
path: { id: args.session_id },
|
||||||
|
})
|
||||||
|
if (sessionResult.error) {
|
||||||
|
log(`[omo_task] Session get error:`, sessionResult.error)
|
||||||
|
return `Error: Failed to get existing session: ${sessionResult.error}`
|
||||||
|
}
|
||||||
|
sessionID = args.session_id
|
||||||
|
} else {
|
||||||
|
log(`[omo_task] 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(`[omo_task] Session create error:`, createResult.error)
|
||||||
|
return `Error: Failed to create session: ${createResult.error}`
|
||||||
|
}
|
||||||
|
|
||||||
|
sessionID = createResult.data.id
|
||||||
|
log(`[omo_task] Created session: ${sessionID}`)
|
||||||
|
}
|
||||||
|
|
||||||
|
log(`[omo_task] Sending prompt to session ${sessionID}`)
|
||||||
|
log(`[omo_task] Prompt text:`, args.prompt.substring(0, 100))
|
||||||
|
|
||||||
|
await ctx.client.session.prompt({
|
||||||
|
path: { id: sessionID },
|
||||||
|
body: {
|
||||||
|
agent: args.subagent_type,
|
||||||
|
tools: {
|
||||||
|
task: false,
|
||||||
|
omo_task: false,
|
||||||
|
},
|
||||||
|
parts: [{ type: "text", text: args.prompt }],
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
log(`[omo_task] Prompt sent, fetching messages...`)
|
||||||
|
|
||||||
|
const messagesResult = await ctx.client.session.messages({
|
||||||
|
path: { id: sessionID },
|
||||||
|
})
|
||||||
|
|
||||||
|
if (messagesResult.error) {
|
||||||
|
log(`[omo_task] Messages error:`, messagesResult.error)
|
||||||
|
return `Error: Failed to get messages: ${messagesResult.error}`
|
||||||
|
}
|
||||||
|
|
||||||
|
const messages = messagesResult.data
|
||||||
|
log(`[omo_task] Got ${messages.length} messages`)
|
||||||
|
|
||||||
|
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(`[omo_task] No assistant message found`)
|
||||||
|
log(`[omo_task] All messages:`, JSON.stringify(messages, null, 2))
|
||||||
|
return `Error: No assistant response found\n\n<task_metadata>\nsession_id: ${sessionID}\n</task_metadata>`
|
||||||
|
}
|
||||||
|
|
||||||
|
log(`[omo_task] Found assistant message with ${lastAssistantMessage.parts.length} parts`)
|
||||||
|
|
||||||
|
const textParts = lastAssistantMessage.parts.filter((p: any) => p.type === "text")
|
||||||
|
const responseText = textParts.map((p: any) => p.text).join("\n")
|
||||||
|
|
||||||
|
log(`[omo_task] Got response, length: ${responseText.length}`)
|
||||||
|
|
||||||
|
const output =
|
||||||
|
responseText + "\n\n" + ["<task_metadata>", `session_id: ${sessionID}`, "</task_metadata>"].join("\n")
|
||||||
|
|
||||||
|
return output
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
26
src/tools/omo-task/types.ts
Normal file
26
src/tools/omo-task/types.ts
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
import type { ALLOWED_AGENTS } from "./constants"
|
||||||
|
|
||||||
|
export type AllowedAgentType = (typeof ALLOWED_AGENTS)[number]
|
||||||
|
|
||||||
|
export interface OmoTaskArgs {
|
||||||
|
description: string
|
||||||
|
prompt: string
|
||||||
|
subagent_type: string
|
||||||
|
session_id?: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface OmoTaskResult {
|
||||||
|
title: string
|
||||||
|
metadata: {
|
||||||
|
summary?: Array<{
|
||||||
|
id: string
|
||||||
|
tool: string
|
||||||
|
state: {
|
||||||
|
status: string
|
||||||
|
title?: string
|
||||||
|
}
|
||||||
|
}>
|
||||||
|
sessionId: string
|
||||||
|
}
|
||||||
|
output: string
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user