refactor(omo-task): rename to call_omo_agent with run_in_background parameter

- Rename omo-task to call-omo-agent with mandatory run_in_background parameter
- Implement background mode using BackgroundManager (fire-and-forget abort)
- Implement sync mode with existing subagent logic
- Fix background_cancel: use fire-and-forget abort to prevent parent session interruption
- Add call_omo_agent to tool disable list in explore/librarian agents
- Add call_omo_agent to tool disable list in BackgroundManager

🤖 GENERATED WITH ASSISTANCE OF [OhMyOpenCode](https://github.com/code-yeongyu/oh-my-opencode)
This commit is contained in:
YeonGyu-Kim
2025-12-12 18:39:53 +09:00
parent f007437991
commit 12c0b7b6c0
13 changed files with 262 additions and 136 deletions

View File

@@ -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"
}
}
]

View File

@@ -8,7 +8,8 @@
"@ast-grep/cli": "^0.40.0", "@ast-grep/cli": "^0.40.0",
"@ast-grep/napi": "^0.40.0", "@ast-grep/napi": "^0.40.0",
"@code-yeongyu/comment-checker": "^0.5.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", "picomatch": "^4.0.2",
"xdg-basedir": "^5.1.0", "xdg-basedir": "^5.1.0",
"zod": "^4.1.8", "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=="], "@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=="], "@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=="], "@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/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=="], "@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": ["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=="], "bun-types": ["bun-types@1.3.3", "", { "dependencies": { "@types/node": "*" } }, "sha512-z3Xwlg7j2l9JY27x5Qn3Wlyos8YAp0kKRlrePAOjgjMGS5IG6E7Jnlx736vH9UVI4wUICwwhC9anYL++XeOgTQ=="],
"detect-libc": ["detect-libc@2.1.2", "", {}, "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ=="], "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=="], "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=="], "picomatch": ["picomatch@4.0.3", "", {}, "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q=="],
"typescript": ["typescript@5.9.3", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw=="], "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=="], "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/@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=="],
} }
} }

View File

@@ -13,10 +13,14 @@
"types": "./dist/index.d.ts", "types": "./dist/index.d.ts",
"import": "./dist/index.js" "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" "./schema.json": "./dist/oh-my-opencode.schema.json"
}, },
"scripts": { "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", "build:schema": "bun run script/build-schema.ts",
"clean": "rm -rf dist", "clean": "rm -rf dist",
"prepublishOnly": "bun run clean && bun run build", "prepublishOnly": "bun run clean && bun run build",
@@ -45,7 +49,8 @@
"@ast-grep/cli": "^0.40.0", "@ast-grep/cli": "^0.40.0",
"@ast-grep/napi": "^0.40.0", "@ast-grep/napi": "^0.40.0",
"@code-yeongyu/comment-checker": "^0.5.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", "picomatch": "^4.0.2",
"xdg-basedir": "^5.1.0", "xdg-basedir": "^5.1.0",
"zod": "^4.1.8" "zod": "^4.1.8"

1
src/codex-auth.ts Normal file
View File

@@ -0,0 +1 @@
export * from "opencode-openai-codex-auth";

View File

@@ -81,6 +81,7 @@ export class BackgroundManager {
background_task: false, background_task: false,
background_output: false, background_output: false,
background_cancel: false, background_cancel: false,
call_omo_agent: false,
}, },
parts: [{ type: "text", text: input.prompt }], parts: [{ type: "text", text: input.prompt }],
}, },
@@ -248,7 +249,7 @@ export class BackgroundManager {
setTimeout(async () => { setTimeout(async () => {
try { try {
await this.client.session.promptAsync({ await this.client.session.prompt({
path: { id: mainSessionID }, path: { id: mainSessionID },
body: { body: {
parts: [{ type: "text", text: message }], parts: [{ type: "text", text: message }],
@@ -256,9 +257,9 @@ export class BackgroundManager {
query: { directory: this.directory }, query: { directory: this.directory },
}) })
this.clearNotificationsForTask(task.id) this.clearNotificationsForTask(task.id)
log("[background-agent] Successfully sent promptAsync to main session") log("[background-agent] Successfully sent prompt to main session")
} catch (error) { } catch (error) {
log("[background-agent] promptAsync failed:", String(error)) log("[background-agent] prompt failed:", String(error))
} }
}, 200) }, 200)
} }

View File

@@ -37,7 +37,7 @@ import {
getCurrentSessionTitle, getCurrentSessionTitle,
} from "./features/claude-code-session-state"; } from "./features/claude-code-session-state";
import { updateTerminalTitle } from "./features/terminal"; import { updateTerminalTitle } from "./features/terminal";
import { builtinTools, createOmoTask, createBackgroundTools } from "./tools"; import { builtinTools, createCallOmoAgent, createBackgroundTools } from "./tools";
import { BackgroundManager } from "./features/background-agent"; import { BackgroundManager } from "./features/background-agent";
import { createBuiltinMcps } from "./mcp"; import { createBuiltinMcps } from "./mcp";
import { OhMyOpenCodeConfigSchema, type OhMyOpenCodeConfig } from "./config"; import { OhMyOpenCodeConfigSchema, type OhMyOpenCodeConfig } from "./config";
@@ -169,13 +169,13 @@ const OhMyOpenCodePlugin: Plugin = async (ctx) => {
const backgroundNotificationHook = createBackgroundNotificationHook(backgroundManager); const backgroundNotificationHook = createBackgroundNotificationHook(backgroundManager);
const backgroundTools = createBackgroundTools(backgroundManager, ctx.client); const backgroundTools = createBackgroundTools(backgroundManager, ctx.client);
const omoTask = createOmoTask(ctx); const callOmoAgent = createCallOmoAgent(ctx, backgroundManager);
return { return {
tool: { tool: {
...builtinTools, ...builtinTools,
...backgroundTools, ...backgroundTools,
omo_task: omoTask, call_omo_agent: callOmoAgent,
}, },
"chat.message": async (input, output) => { "chat.message": async (input, output) => {
@@ -205,13 +205,13 @@ const OhMyOpenCodePlugin: Plugin = async (ctx) => {
if (config.agent.explore) { if (config.agent.explore) {
config.agent.explore.tools = { config.agent.explore.tools = {
...config.agent.explore.tools, ...config.agent.explore.tools,
omo_task: false, call_omo_agent: false,
}; };
} }
if (config.agent.librarian) { if (config.agent.librarian) {
config.agent.librarian.tools = { config.agent.librarian.tools = {
...config.agent.librarian.tools, ...config.agent.librarian.tools,
omo_task: false, call_omo_agent: false,
}; };
} }

View File

@@ -196,13 +196,11 @@ export function createBackgroundCancel(manager: BackgroundManager, client: Openc
Only running tasks can be cancelled.` 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 }, path: { id: task.sessionID },
}) }).catch(() => {})
if (abortResult.error) {
return `❌ Failed to abort session: ${(abortResult.error as any).message || String(abortResult.error)}`
}
task.status = "cancelled" task.status = "cancelled"
task.completedAt = new Date() task.completedAt = new Date()

View File

@@ -1,6 +1,6 @@
export const ALLOWED_AGENTS = ["explore", "librarian"] as const 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. 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. 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: Usage notes:
1. Launch multiple agents concurrently whenever possible, to maximize performance 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 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 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 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`

View File

@@ -1,3 +1,3 @@
export * from "./types" export * from "./types"
export * from "./constants" export * from "./constants"
export { createOmoTask } from "./tools" export { createCallOmoAgent } from "./tools"

View File

@@ -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<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}
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<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))
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<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
}

View File

@@ -2,14 +2,15 @@ import type { ALLOWED_AGENTS } from "./constants"
export type AllowedAgentType = (typeof ALLOWED_AGENTS)[number] export type AllowedAgentType = (typeof ALLOWED_AGENTS)[number]
export interface OmoTaskArgs { export interface CallOmoAgentArgs {
description: string description: string
prompt: string prompt: string
subagent_type: string subagent_type: string
run_in_background: boolean
session_id?: string session_id?: string
} }
export interface OmoTaskResult { export interface CallOmoAgentSyncResult {
title: string title: string
metadata: { metadata: {
summary?: Array<{ summary?: Array<{

View File

@@ -33,7 +33,7 @@ import type { BackgroundManager } from "../features/background-agent"
type OpencodeClient = PluginInput["client"] type OpencodeClient = PluginInput["client"]
export { createOmoTask } from "./omo-task" export { createCallOmoAgent } from "./call-omo-agent"
export function createBackgroundTools(manager: BackgroundManager, client: OpencodeClient) { export function createBackgroundTools(manager: BackgroundManager, client: OpencodeClient) {
return { return {

View File

@@ -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<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
},
})
}