diff --git a/src/tools/omo-task/constants.ts b/src/tools/omo-task/constants.ts new file mode 100644 index 0000000..9245d87 --- /dev/null +++ b/src/tools/omo-task/constants.ts @@ -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` diff --git a/src/tools/omo-task/index.ts b/src/tools/omo-task/index.ts new file mode 100644 index 0000000..d39a38a --- /dev/null +++ b/src/tools/omo-task/index.ts @@ -0,0 +1,3 @@ +export * from "./types" +export * from "./constants" +export { createOmoTask } from "./tools" diff --git a/src/tools/omo-task/tools.ts b/src/tools/omo-task/tools.ts new file mode 100644 index 0000000..d8fa317 --- /dev/null +++ b/src/tools/omo-task/tools.ts @@ -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\nsession_id: ${sessionID}\n` + } + + 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" + ["", `session_id: ${sessionID}`, ""].join("\n") + + return output + }, + }) +} diff --git a/src/tools/omo-task/types.ts b/src/tools/omo-task/types.ts new file mode 100644 index 0000000..3af015b --- /dev/null +++ b/src/tools/omo-task/types.ts @@ -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 +}