diff --git a/.opencode/background-tasks.json b/.opencode/background-tasks.json new file mode 100644 index 0000000..92e412a --- /dev/null +++ b/.opencode/background-tasks.json @@ -0,0 +1,27 @@ +[ + { + "id": "bg_wzsdt60b", + "sessionID": "ses_4f3e89f0dffeooeXNVx5QCifse", + "parentSessionID": "ses_4f3e8d141ffeyfJ1taVVOdQTzx", + "parentMessageID": "msg_b0c172ee1001w2B52VSZrP08PJ", + "description": "Explore opencode in codebase", + "agent": "explore", + "status": "completed", + "startedAt": "2025-12-11T06:26:57.395Z", + "completedAt": "2025-12-11T06:27:36.778Z" + }, + { + "id": "bg_392b9c9b", + "sessionID": "ses_4f38ebf4fffeJZBocIn3UVv7vE", + "parentSessionID": "ses_4f38eefa0ffeKV0pVNnwT37P5L", + "parentMessageID": "msg_b0c7110d2001TMBlPeEYIrByvs", + "description": "Test explore agent", + "agent": "explore", + "status": "running", + "startedAt": "2025-12-11T08:05:07.378Z", + "progress": { + "toolCalls": 0, + "lastUpdate": "2025-12-11T08:05:07.378Z" + } + } +] \ No newline at end of file diff --git a/bun.lock b/bun.lock index daf00a1..2a796c0 100644 --- a/bun.lock +++ b/bun.lock @@ -8,7 +8,8 @@ "@ast-grep/cli": "^0.40.0", "@ast-grep/napi": "^0.40.0", "@code-yeongyu/comment-checker": "^0.5.0", - "@opencode-ai/plugin": "^1.0.7", + "@opencode-ai/plugin": "^1.0.150", + "opencode-openai-codex-auth": "^4.1.0", "picomatch": "^4.0.2", "xdg-basedir": "^5.1.0", "zod": "^4.1.8", @@ -68,9 +69,21 @@ "@code-yeongyu/comment-checker": ["@code-yeongyu/comment-checker@0.5.0", "", { "os": [ "linux", "win32", "darwin", ], "cpu": [ "x64", "arm64", ], "bin": { "comment-checker": "bin/comment-checker" } }, "sha512-rKD2qQnTVUacsVQtpu3I5Sxi09X/XpOwS9fcmbUv1yfUL6llraaPuLmmxMBMRcmm7Zu31yEPVKCeUkVODfRL1g=="], - "@opencode-ai/plugin": ["@opencode-ai/plugin@1.0.128", "", { "dependencies": { "@opencode-ai/sdk": "1.0.128", "zod": "4.1.8" } }, "sha512-M5vjz3I6KeoBSNduWmT5iHXRtTLCqICM5ocs+WrB3uxVorslcO3HVwcLzrERh/ntpxJ/1xhnHQaeG6Mg+P744A=="], + "@openauthjs/openauth": ["@openauthjs/openauth@0.4.3", "", { "dependencies": { "@standard-schema/spec": "1.0.0-beta.3", "aws4fetch": "1.0.20", "jose": "5.9.6" }, "peerDependencies": { "arctic": "^2.2.2", "hono": "^4.0.0" } }, "sha512-RlnjqvHzqcbFVymEwhlUEuac4utA5h4nhSK/i2szZuQmxTIqbGUxZ+nM+avM+VV4Ing+/ZaNLKILoXS3yrkOOw=="], - "@opencode-ai/sdk": ["@opencode-ai/sdk@1.0.128", "", {}, "sha512-Kow3Ivg8bR8dNRp8C0LwF9e8+woIrwFgw3ZALycwCfqS/UujDkJiBeYHdr1l/07GSHP9sZPmvJ6POuvfZ923EA=="], + "@opencode-ai/plugin": ["@opencode-ai/plugin@1.0.150", "", { "dependencies": { "@opencode-ai/sdk": "1.0.150", "zod": "4.1.8" } }, "sha512-XmY3yydk120GBv2KeLxSZlElFx4Zx9TYLa3bS9X1TxXot42UeoMLEi3Xa46yboYnWwp4bC9Fu+Gd1E7hypG8Jw=="], + + "@opencode-ai/sdk": ["@opencode-ai/sdk@1.0.150", "", {}, "sha512-Nz9Di8UD/GK01w3N+jpiGNB733pYkNY8RNLbuE/HUxEGSP5apbXBY0IdhbW7859sXZZK38kF1NqOx4UxwBf4Bw=="], + + "@oslojs/asn1": ["@oslojs/asn1@1.0.0", "", { "dependencies": { "@oslojs/binary": "1.0.0" } }, "sha512-zw/wn0sj0j0QKbIXfIlnEcTviaCzYOY3V5rAyjR6YtOByFtJiT574+8p9Wlach0lZH9fddD4yb9laEAIl4vXQA=="], + + "@oslojs/binary": ["@oslojs/binary@1.0.0", "", {}, "sha512-9RCU6OwXU6p67H4NODbuxv2S3eenuQ4/WFLrsq+K/k682xrznH5EVWA7N4VFk9VYVcbFtKqur5YQQZc0ySGhsQ=="], + + "@oslojs/crypto": ["@oslojs/crypto@1.0.1", "", { "dependencies": { "@oslojs/asn1": "1.0.0", "@oslojs/binary": "1.0.0" } }, "sha512-7n08G8nWjAr/Yu3vu9zzrd0L9XnrJfpMioQcvCMxBIiF5orECHe5/3J0jmXRVvgfqMm/+4oxlQ+Sq39COYLcNQ=="], + + "@oslojs/encoding": ["@oslojs/encoding@1.1.0", "", {}, "sha512-70wQhgYmndg4GCPxPPxPGevRKqTIJ2Nh4OkiMWmDAVYsTQ+Ta7Sq+rPevXyXGdzr30/qZBnyOalCszoMxlyldQ=="], + + "@oslojs/jwt": ["@oslojs/jwt@0.2.0", "", { "dependencies": { "@oslojs/encoding": "0.4.1" } }, "sha512-bLE7BtHrURedCn4Mco3ma9L4Y1GR2SMBuIvjWr7rmQ4/W/4Jy70TIAgZ+0nIlk0xHz1vNP8x8DCns45Sb2XRbg=="], "@oven/bun-darwin-aarch64": ["@oven/bun-darwin-aarch64@1.3.3", "", { "os": "darwin", "cpu": "arm64" }, "sha512-eJopQrUk0WR7jViYDC29+Rp50xGvs4GtWOXBeqCoFMzutkkO3CZvHehA4JqnjfWMTSS8toqvRhCSOpOz62Wf9w=="], @@ -94,18 +107,30 @@ "@oven/bun-windows-x64-baseline": ["@oven/bun-windows-x64-baseline@1.3.3", "", { "os": "win32", "cpu": "x64" }, "sha512-u5eZHKq6TPJSE282KyBOicGQ2trkFml0RoUfqkPOJVo7TXGrsGYYzdsugZRnVQY/WEmnxGtBy4T3PAaPqgQViA=="], + "@standard-schema/spec": ["@standard-schema/spec@1.0.0-beta.3", "", {}, "sha512-0ifF3BjA1E8SY9C+nUew8RefNOIq0cDlYALPty4rhUm8Rrl6tCM8hBT4bhGhx7I7iXD0uAgt50lgo8dD73ACMw=="], + "@types/node": ["@types/node@24.10.1", "", { "dependencies": { "undici-types": "~7.16.0" } }, "sha512-GNWcUTRBgIRJD5zj+Tq0fKOJ5XZajIiBroOF0yvj2bSU1WvNdYS/dn9UxwsujGW4JX06dnHyjV2y9rRaybH0iQ=="], "@types/picomatch": ["@types/picomatch@3.0.2", "", {}, "sha512-n0i8TD3UDB7paoMMxA3Y65vUncFJXjcUf7lQY7YyKGl6031FNjfsLs6pdLFCy2GNFxItPJG8GvvpbZc2skH7WA=="], + "arctic": ["arctic@2.3.4", "", { "dependencies": { "@oslojs/crypto": "1.0.1", "@oslojs/encoding": "1.1.0", "@oslojs/jwt": "0.2.0" } }, "sha512-+p30BOWsctZp+CVYCt7oAean/hWGW42sH5LAcRQX56ttEkFJWbzXBhmSpibbzwSJkRrotmsA+oAoJoVsU0f5xA=="], + + "aws4fetch": ["aws4fetch@1.0.20", "", {}, "sha512-/djoAN709iY65ETD6LKCtyyEI04XIBP5xVvfmNxsEP0uJB5tyaGBztSryRr4HqMStr9R06PisQE7m9zDTXKu6g=="], + "bun": ["bun@1.3.3", "", { "optionalDependencies": { "@oven/bun-darwin-aarch64": "1.3.3", "@oven/bun-darwin-x64": "1.3.3", "@oven/bun-darwin-x64-baseline": "1.3.3", "@oven/bun-linux-aarch64": "1.3.3", "@oven/bun-linux-aarch64-musl": "1.3.3", "@oven/bun-linux-x64": "1.3.3", "@oven/bun-linux-x64-baseline": "1.3.3", "@oven/bun-linux-x64-musl": "1.3.3", "@oven/bun-linux-x64-musl-baseline": "1.3.3", "@oven/bun-windows-x64": "1.3.3", "@oven/bun-windows-x64-baseline": "1.3.3" }, "os": [ "linux", "win32", "darwin", ], "cpu": [ "x64", "arm64", ], "bin": { "bun": "bin/bun.exe", "bunx": "bin/bunx.exe" } }, "sha512-2hJ4ocTZ634/Ptph4lysvO+LbbRZq8fzRvMwX0/CqaLBxrF2UB5D1LdMB8qGcdtCer4/VR9Bx5ORub0yn+yzmw=="], "bun-types": ["bun-types@1.3.3", "", { "dependencies": { "@types/node": "*" } }, "sha512-z3Xwlg7j2l9JY27x5Qn3Wlyos8YAp0kKRlrePAOjgjMGS5IG6E7Jnlx736vH9UVI4wUICwwhC9anYL++XeOgTQ=="], "detect-libc": ["detect-libc@2.1.2", "", {}, "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ=="], + "hono": ["hono@4.10.8", "", {}, "sha512-DDT0A0r6wzhe8zCGoYOmMeuGu3dyTAE40HHjwUsWFTEy5WxK1x2WDSsBPlEXgPbRIFY6miDualuUDbasPogIww=="], + + "jose": ["jose@5.9.6", "", {}, "sha512-AMlnetc9+CV9asI19zHmrgS/WYsWUwCn2R7RzlbJWD7F9eWYUTGyBmU9o6PxngtLGOiDGPRu+Uc4fhKzbpteZQ=="], + "oh-my-opencode": ["oh-my-opencode@0.1.30", "", { "dependencies": { "@ast-grep/cli": "^0.40.0", "@ast-grep/napi": "^0.40.0", "@code-yeongyu/comment-checker": "^0.4.1", "@opencode-ai/plugin": "^1.0.7", "xdg-basedir": "^5.1.0", "zod": "^4.1.8" }, "peerDependencies": { "bun": ">=1.0.0" } }, "sha512-pXGGgL/7Jcz3yuGJJTI72BKern2egwfRz2LQZTBq+jl+pNCybOvGvXtFmR+WGlF8O3ZjL1wIHypBbIVuHOBzxg=="], + "opencode-openai-codex-auth": ["opencode-openai-codex-auth@4.1.0", "", { "dependencies": { "@openauthjs/openauth": "^0.4.3", "hono": "^4.10.4" }, "peerDependencies": { "@opencode-ai/plugin": "^1.0.150" } }, "sha512-oTJTS6dJt6qokDDUedEvqcWcGl6byGDq4ZbfOHWQQaqiGdbmqoRf3zoQJELuhA3ceibkXv19zljeHbPH9m7FpA=="], + "picomatch": ["picomatch@4.0.3", "", {}, "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q=="], "typescript": ["typescript@5.9.3", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw=="], @@ -116,6 +141,12 @@ "zod": ["zod@4.1.8", "", {}, "sha512-5R1P+WwQqmmMIEACyzSvo4JXHY5WiAFHRMg+zBZKgKS+Q1viRa0C1hmUKtHltoIFKtIdki3pRxkmpP74jnNYHQ=="], + "@oslojs/jwt/@oslojs/encoding": ["@oslojs/encoding@0.4.1", "", {}, "sha512-hkjo6MuIK/kQR5CrGNdAPZhS01ZCXuWDRJ187zh6qqF2+yMHZpD9fAYpX8q2bOO6Ryhl3XpCT6kUX76N8hhm4Q=="], + "oh-my-opencode/@code-yeongyu/comment-checker": ["@code-yeongyu/comment-checker@0.4.1", "", { "os": [ "linux", "win32", "darwin", ], "cpu": [ "x64", "arm64", ], "bin": { "comment-checker": "bin/comment-checker" } }, "sha512-E7p1V8CsRj9hMbwENd9BfxZGWYu+lKS5tXGuNNcNtkRMhWvwM/ononysKpLB7LXdxfSYAn0j7heJydyzEmm+lg=="], + + "oh-my-opencode/@opencode-ai/plugin": ["@opencode-ai/plugin@1.0.128", "", { "dependencies": { "@opencode-ai/sdk": "1.0.128", "zod": "4.1.8" } }, "sha512-M5vjz3I6KeoBSNduWmT5iHXRtTLCqICM5ocs+WrB3uxVorslcO3HVwcLzrERh/ntpxJ/1xhnHQaeG6Mg+P744A=="], + + "oh-my-opencode/@opencode-ai/plugin/@opencode-ai/sdk": ["@opencode-ai/sdk@1.0.128", "", {}, "sha512-Kow3Ivg8bR8dNRp8C0LwF9e8+woIrwFgw3ZALycwCfqS/UujDkJiBeYHdr1l/07GSHP9sZPmvJ6POuvfZ923EA=="], } } diff --git a/package.json b/package.json index 3332e04..a9b7e05 100644 --- a/package.json +++ b/package.json @@ -13,10 +13,14 @@ "types": "./dist/index.d.ts", "import": "./dist/index.js" }, + "./codex-auth": { + "types": "./dist/codex-auth.d.ts", + "import": "./dist/codex-auth.js" + }, "./schema.json": "./dist/oh-my-opencode.schema.json" }, "scripts": { - "build": "bun build src/index.ts --outdir dist --target bun --format esm --external @ast-grep/napi && tsc --emitDeclarationOnly && bun run build:schema", + "build": "bun build src/index.ts src/codex-auth.ts --outdir dist --target bun --format esm --external @ast-grep/napi && tsc --emitDeclarationOnly && bun run build:schema", "build:schema": "bun run script/build-schema.ts", "clean": "rm -rf dist", "prepublishOnly": "bun run clean && bun run build", @@ -45,7 +49,8 @@ "@ast-grep/cli": "^0.40.0", "@ast-grep/napi": "^0.40.0", "@code-yeongyu/comment-checker": "^0.5.0", - "@opencode-ai/plugin": "^1.0.7", + "@opencode-ai/plugin": "^1.0.150", + "opencode-openai-codex-auth": "^4.1.0", "picomatch": "^4.0.2", "xdg-basedir": "^5.1.0", "zod": "^4.1.8" diff --git a/src/codex-auth.ts b/src/codex-auth.ts new file mode 100644 index 0000000..601b21f --- /dev/null +++ b/src/codex-auth.ts @@ -0,0 +1 @@ +export * from "opencode-openai-codex-auth"; diff --git a/src/features/background-agent/manager.ts b/src/features/background-agent/manager.ts index 51acedb..2ae6b3f 100644 --- a/src/features/background-agent/manager.ts +++ b/src/features/background-agent/manager.ts @@ -81,6 +81,7 @@ export class BackgroundManager { background_task: false, background_output: false, background_cancel: false, + call_omo_agent: false, }, parts: [{ type: "text", text: input.prompt }], }, @@ -248,7 +249,7 @@ export class BackgroundManager { setTimeout(async () => { try { - await this.client.session.promptAsync({ + await this.client.session.prompt({ path: { id: mainSessionID }, body: { parts: [{ type: "text", text: message }], @@ -256,9 +257,9 @@ export class BackgroundManager { query: { directory: this.directory }, }) this.clearNotificationsForTask(task.id) - log("[background-agent] Successfully sent promptAsync to main session") + log("[background-agent] Successfully sent prompt to main session") } catch (error) { - log("[background-agent] promptAsync failed:", String(error)) + log("[background-agent] prompt failed:", String(error)) } }, 200) } diff --git a/src/index.ts b/src/index.ts index a69fc6a..7b01f15 100644 --- a/src/index.ts +++ b/src/index.ts @@ -37,7 +37,7 @@ import { getCurrentSessionTitle, } from "./features/claude-code-session-state"; import { updateTerminalTitle } from "./features/terminal"; -import { builtinTools, createOmoTask, createBackgroundTools } from "./tools"; +import { builtinTools, createCallOmoAgent, createBackgroundTools } from "./tools"; import { BackgroundManager } from "./features/background-agent"; import { createBuiltinMcps } from "./mcp"; import { OhMyOpenCodeConfigSchema, type OhMyOpenCodeConfig } from "./config"; @@ -169,13 +169,13 @@ const OhMyOpenCodePlugin: Plugin = async (ctx) => { const backgroundNotificationHook = createBackgroundNotificationHook(backgroundManager); const backgroundTools = createBackgroundTools(backgroundManager, ctx.client); - const omoTask = createOmoTask(ctx); + const callOmoAgent = createCallOmoAgent(ctx, backgroundManager); return { tool: { ...builtinTools, ...backgroundTools, - omo_task: omoTask, + call_omo_agent: callOmoAgent, }, "chat.message": async (input, output) => { @@ -205,13 +205,13 @@ const OhMyOpenCodePlugin: Plugin = async (ctx) => { if (config.agent.explore) { config.agent.explore.tools = { ...config.agent.explore.tools, - omo_task: false, + call_omo_agent: false, }; } if (config.agent.librarian) { config.agent.librarian.tools = { ...config.agent.librarian.tools, - omo_task: false, + call_omo_agent: false, }; } diff --git a/src/tools/background-task/tools.ts b/src/tools/background-task/tools.ts index 52b978e..7ebd1c6 100644 --- a/src/tools/background-task/tools.ts +++ b/src/tools/background-task/tools.ts @@ -196,13 +196,11 @@ export function createBackgroundCancel(manager: BackgroundManager, client: Openc Only running tasks can be cancelled.` } - const abortResult = await client.session.abort({ + // Fire-and-forget: abort 요청을 보내고 await 하지 않음 + // await 하면 메인 세션까지 abort 되는 문제 발생 + client.session.abort({ path: { id: task.sessionID }, - }) - - if (abortResult.error) { - return `❌ Failed to abort session: ${(abortResult.error as any).message || String(abortResult.error)}` - } + }).catch(() => {}) task.status = "cancelled" task.completedAt = new Date() diff --git a/src/tools/omo-task/constants.ts b/src/tools/call-omo-agent/constants.ts similarity index 55% rename from src/tools/omo-task/constants.ts rename to src/tools/call-omo-agent/constants.ts index 9245d87..0765dcf 100644 --- a/src/tools/omo-task/constants.ts +++ b/src/tools/call-omo-agent/constants.ts @@ -1,6 +1,6 @@ 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. +export const CALL_OMO_AGENT_DESCRIPTION = `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. @@ -9,9 +9,15 @@ Available agent types: When using this tool, you must specify a subagent_type parameter to select which agent type to use. +**IMPORTANT: run_in_background parameter is REQUIRED** +- \`run_in_background=true\`: Task runs asynchronously in background. Returns immediately with task_id. + Use \`background_output\` tool with the returned task_id to check progress or retrieve results. +- \`run_in_background=false\`: Task runs synchronously. Waits for completion and returns full result. + 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` +5. Clearly tell the agent whether you expect it to write code or just to do research +6. For long-running research tasks, use run_in_background=true to avoid blocking` diff --git a/src/tools/omo-task/index.ts b/src/tools/call-omo-agent/index.ts similarity index 53% rename from src/tools/omo-task/index.ts rename to src/tools/call-omo-agent/index.ts index d39a38a..b57e87e 100644 --- a/src/tools/omo-task/index.ts +++ b/src/tools/call-omo-agent/index.ts @@ -1,3 +1,3 @@ export * from "./types" export * from "./constants" -export { createOmoTask } from "./tools" +export { createCallOmoAgent } from "./tools" diff --git a/src/tools/call-omo-agent/tools.ts b/src/tools/call-omo-agent/tools.ts new file mode 100644 index 0000000..2284bf1 --- /dev/null +++ b/src/tools/call-omo-agent/tools.ts @@ -0,0 +1,167 @@ +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 { + 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} + +Use \`background_output\` tool with task_id="${task.id}" to check progress or retrieve results. +- block=false: Check status without waiting +- block=true (default): Wait for completion and get result` + } 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 { + 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)) + + await ctx.client.session.prompt({ + path: { id: sessionID }, + body: { + agent: args.subagent_type, + tools: { + task: false, + call_omo_agent: false, + }, + parts: [{ type: "text", text: args.prompt }], + }, + }) + + 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\nsession_id: ${sessionID}\n` + } + + 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" + ["", `session_id: ${sessionID}`, ""].join("\n") + + return output +} diff --git a/src/tools/omo-task/types.ts b/src/tools/call-omo-agent/types.ts similarity index 79% rename from src/tools/omo-task/types.ts rename to src/tools/call-omo-agent/types.ts index 3af015b..814e355 100644 --- a/src/tools/omo-task/types.ts +++ b/src/tools/call-omo-agent/types.ts @@ -2,14 +2,15 @@ import type { ALLOWED_AGENTS } from "./constants" export type AllowedAgentType = (typeof ALLOWED_AGENTS)[number] -export interface OmoTaskArgs { +export interface CallOmoAgentArgs { description: string prompt: string subagent_type: string + run_in_background: boolean session_id?: string } -export interface OmoTaskResult { +export interface CallOmoAgentSyncResult { title: string metadata: { summary?: Array<{ diff --git a/src/tools/index.ts b/src/tools/index.ts index 906df3f..ccf2397 100644 --- a/src/tools/index.ts +++ b/src/tools/index.ts @@ -33,7 +33,7 @@ import type { BackgroundManager } from "../features/background-agent" type OpencodeClient = PluginInput["client"] -export { createOmoTask } from "./omo-task" +export { createCallOmoAgent } from "./call-omo-agent" export function createBackgroundTools(manager: BackgroundManager, client: OpencodeClient) { return { diff --git a/src/tools/omo-task/tools.ts b/src/tools/omo-task/tools.ts deleted file mode 100644 index d8fa317..0000000 --- a/src/tools/omo-task/tools.ts +++ /dev/null @@ -1,111 +0,0 @@ -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 - }, - }) -}