Compare commits

...

26 Commits

Author SHA1 Message Date
YeonGyu-Kim
21ddd55162 fix: prevent agents from duplicating delegated subagent work
Addresses user reports where Sisyphus/Hephaestus would delegate tasks
to explore/librarian subagents but then immediately perform the same
search/work themselves, wasting context and defeating the purpose of
delegation.

Changes:
- Add Anti-Duplication section to dynamic-agent-prompt-builder with
  clear rules: once you delegate, do NOT manually re-do the same search
- Update all agent prompts (sisyphus, hephaestus, sis-junior gemini/gpt)
  to use 'non-overlapping work' instead of 'keep working' after delegation
- Add buildAntiDuplicationSection() with explicit examples of forbidden
  vs allowed behavior after delegation
- Add 'Delegation Trust Rule' to explore section
- Add 'Delegation Duplication' to anti-patterns list

Atlas fixes:
- Add AUTO-CONTINUE POLICY to all Atlas variants (default, gemini, gpt)
  preventing the 'should I continue?' confirmation loop between plan steps
- Fix plan file path in default atlas (.sisyphus/tasks/ -> .sisyphus/plans/)
- Update GPT atlas uncertainty section to only ask questions during initial
  plan analysis, not during execution

Fixes: subagent delegation duplication, Atlas continuation prompting
2026-03-09 12:26:15 +09:00
github-actions[bot]
5137df72d8 @mrosnerr has signed the CLA in code-yeongyu/oh-my-opencode#2328 2026-03-05 18:11:22 +00:00
github-actions[bot]
dd70ce37f0 @hkc5 has signed the CLA in code-yeongyu/oh-my-opencode#2327 2026-03-05 17:56:52 +00:00
github-actions[bot]
7e0a1a133c @mInrOz has signed the CLA in code-yeongyu/oh-my-opencode#2321 2026-03-05 12:42:40 +00:00
YeonGyu-Kim
be606cdfbe Merge pull request #2315 from ualtinok/fix/bgoutputdesc
fix(background-task): clarify timeout unit is milliseconds in description
2026-03-05 20:58:29 +09:00
github-actions[bot]
6a29a373f4 @Wangmerlyn has signed the CLA in code-yeongyu/oh-my-opencode#2318 2026-03-05 11:08:20 +00:00
ismeth
389625cb20 Update constants.ts 2026-03-05 11:41:39 +01:00
ismeth
e916d564a9 fix(background-task): clarify timeout unit is milliseconds in description 2026-03-05 09:05:29 +01:00
github-actions[bot]
3d8f390b9e @Vacbo has signed the CLA in code-yeongyu/oh-my-opencode#2310 2026-03-05 04:20:01 +00:00
YeonGyu-Kim
a61f8bb853 Update @opencode-ai/plugin and SDK to v1.2.x and align system transform handler signature
- Bump @opencode-ai/plugin ^1.1.19 → ^1.2.16, @opencode-ai/sdk ^1.1.19 → ^1.2.17
- Update system-transform handler input type to match new plugin contract (optional sessionID, required model)
- Add @opencode-ai/sdk override in bun.lock

🤖 Generated with assistance of [OhMyOpenCode](https://github.com/code-yeongyu/oh-my-opencode)
2026-03-05 11:18:12 +09:00
YeonGyu-Kim
c8c99445ea fix(look-at): add catch block to prevent TUI crash on unexpected errors 2026-03-05 11:11:53 +09:00
YeonGyu-Kim
fc41a389c5 Merge pull request #2309 from code-yeongyu/fix/task-tui-session-metadata-sync
fix(task): align background delegate-task output with OpenCode TUI session metadata contract
2026-03-05 11:06:11 +09:00
YeonGyu-Kim
39d94a4af6 fix(task): disambiguate background task_id metadata 2026-03-05 11:02:49 +09:00
YeonGyu-Kim
acf4c46439 fix(task): align background output task_id with opencode contract 2026-03-05 11:02:49 +09:00
YeonGyu-Kim
5cbf7828f0 fix(task): avoid pending sessionId metadata in background delegate output 2026-03-05 11:02:49 +09:00
github-actions[bot]
0efd1b65bb @Romanok2805 has signed the CLA in code-yeongyu/oh-my-opencode#2306 2026-03-04 23:51:14 +00:00
github-actions[bot]
f8d2bd55b9 @RaviTharuma has signed the CLA in code-yeongyu/oh-my-opencode#2302 2026-03-04 21:53:50 +00:00
github-actions[bot]
1ef8d73ce5 @brandonwebb-vista has signed the CLA in code-yeongyu/oh-my-opencode#2299 2026-03-04 17:30:54 +00:00
github-actions[bot]
2b7524b1cb @guazi04 has signed the CLA in code-yeongyu/oh-my-opencode#2293 2026-03-04 10:31:56 +00:00
YeonGyu-Kim
d6b0e564bf feat(delegate-task): unify TUI metadata by adding model field to all 5 executor paths 2026-03-04 18:31:19 +09:00
github-actions[bot]
6897761b21 @SeeYouCowboi has signed the CLA in code-yeongyu/oh-my-opencode#2291 2026-03-04 08:50:49 +00:00
github-actions[bot]
fe66b68baa @chan1103 has signed the CLA in code-yeongyu/oh-my-opencode#2288 2026-03-04 08:41:04 +00:00
YeonGyu-Kim
a7f794c7a3 Merge pull request #2280 from code-yeongyu/feat/multimodal-looker-gpt53-codex-first
feat: make gpt-5.3-codex medium the primary model for multimodal-looker
2026-03-04 11:33:27 +09:00
YeonGyu-Kim
85690b69a8 test: update snapshots and assertions for kimi-k2.5-free removal 2026-03-04 11:33:11 +09:00
YeonGyu-Kim
8c2dcb75cb refactor: remove kimi-k2.5-free from all fallback chains and reorder multimodal-looker
kimi-k2.5-free is no longer available. Remove from all agent and category
fallback chains (sisyphus, multimodal-looker, prometheus, metis, atlas,
writing). Reorder multimodal-looker to: gpt-5.3-codex medium -> k2p5 ->
gemini-3-flash -> glm-4.6v -> gpt-5-nano.
2026-03-04 11:24:39 +09:00
YeonGyu-Kim
1ef5c17c35 feat: make gpt-5.3-codex medium the primary model for multimodal-looker
GPT-5.3 Codex has strong multimodal capabilities. Promote it to first
candidate in multimodal-looker fallback chain, with gemini-3-flash
following (matching the ULW pattern of gpt-5.3-codex -> gemini).
2026-03-04 11:20:55 +09:00
46 changed files with 4576 additions and 143 deletions

View File

View File

@@ -1,6 +1,6 @@
{
"lockfileVersion": 1,
"configVersion": 0,
"configVersion": 1,
"workspaces": {
"": {
"name": "oh-my-opencode",
@@ -10,8 +10,8 @@
"@clack/prompts": "^0.11.0",
"@code-yeongyu/comment-checker": "^0.7.0",
"@modelcontextprotocol/sdk": "^1.25.2",
"@opencode-ai/plugin": "^1.1.19",
"@opencode-ai/sdk": "^1.1.19",
"@opencode-ai/plugin": "^1.2.16",
"@opencode-ai/sdk": "^1.2.17",
"commander": "^14.0.2",
"detect-libc": "^2.0.0",
"diff": "^8.0.3",
@@ -48,42 +48,45 @@
"@ast-grep/napi",
"@code-yeongyu/comment-checker",
],
"overrides": {
"@opencode-ai/sdk": "^1.2.17",
},
"packages": {
"@ast-grep/cli": ["@ast-grep/cli@0.40.0", "", { "dependencies": { "detect-libc": "2.1.2" }, "optionalDependencies": { "@ast-grep/cli-darwin-arm64": "0.40.0", "@ast-grep/cli-darwin-x64": "0.40.0", "@ast-grep/cli-linux-arm64-gnu": "0.40.0", "@ast-grep/cli-linux-x64-gnu": "0.40.0", "@ast-grep/cli-win32-arm64-msvc": "0.40.0", "@ast-grep/cli-win32-ia32-msvc": "0.40.0", "@ast-grep/cli-win32-x64-msvc": "0.40.0" }, "bin": { "sg": "sg", "ast-grep": "ast-grep" } }, "sha512-L8AkflsfI2ZP70yIdrwqvjR02ScCuRmM/qNGnJWUkOFck+e6gafNVJ4e4jjGQlEul+dNdBpx36+O2Op629t47A=="],
"@ast-grep/cli": ["@ast-grep/cli@0.40.5", "", { "dependencies": { "detect-libc": "2.1.2" }, "optionalDependencies": { "@ast-grep/cli-darwin-arm64": "0.40.5", "@ast-grep/cli-darwin-x64": "0.40.5", "@ast-grep/cli-linux-arm64-gnu": "0.40.5", "@ast-grep/cli-linux-x64-gnu": "0.40.5", "@ast-grep/cli-win32-arm64-msvc": "0.40.5", "@ast-grep/cli-win32-ia32-msvc": "0.40.5", "@ast-grep/cli-win32-x64-msvc": "0.40.5" }, "bin": { "sg": "sg", "ast-grep": "ast-grep" } }, "sha512-yVXL7Gz0WIHerQLf+MVaVSkhIhidtWReG5akNVr/JS9OVCVkSdz7gWm7H8jVv2M9OO1tauuG76K3UaRGBPu5lQ=="],
"@ast-grep/cli-darwin-arm64": ["@ast-grep/cli-darwin-arm64@0.40.0", "", { "os": "darwin", "cpu": "arm64" }, "sha512-UehY2MMUkdJbsriP7NKc6+uojrqPn7d1Cl0em+WAkee7Eij81VdyIjRsRxtZSLh440ZWQBHI3PALZ9RkOO8pKQ=="],
"@ast-grep/cli-darwin-arm64": ["@ast-grep/cli-darwin-arm64@0.40.5", "", { "os": "darwin", "cpu": "arm64" }, "sha512-T9CzwJ1GqQhnANdsu6c7iT1akpvTVMK+AZrxnhIPv33Ze5hrXUUkqan+j4wUAukRJDqU7u94EhXLSLD+5tcJ8g=="],
"@ast-grep/cli-darwin-x64": ["@ast-grep/cli-darwin-x64@0.40.0", "", { "os": "darwin", "cpu": "x64" }, "sha512-RFDJ2ZxUbT0+grntNlOLJx7wa9/ciVCeaVtQpQy8WJJTvXvkY0etl8Qlh2TmO2x2yr+i0Z6aMJi4IG/Yx5ghTQ=="],
"@ast-grep/cli-darwin-x64": ["@ast-grep/cli-darwin-x64@0.40.5", "", { "os": "darwin", "cpu": "x64" }, "sha512-ez9b2zKvXU8f4ghhjlqYvbx6tWCKJTuVlNVqDDfjqwwhGeiTYfnzMlSVat4ElYRMd21gLtXZIMy055v2f21Ztg=="],
"@ast-grep/cli-linux-arm64-gnu": ["@ast-grep/cli-linux-arm64-gnu@0.40.0", "", { "os": "linux", "cpu": "arm64" }, "sha512-4p55gnTQ1mMFCyqjtM7bH9SB9r16mkwXtUcJQGX1YgFG4WD+QG8rC4GwSuNNZcdlYaOQuTWrgUEQ9z5K06UXfg=="],
"@ast-grep/cli-linux-arm64-gnu": ["@ast-grep/cli-linux-arm64-gnu@0.40.5", "", { "os": "linux", "cpu": "arm64" }, "sha512-VXa2L1IEYD66AMb0GuG7VlMMbPmEGoJUySWDcwSZo/D9neiry3MJ41LQR5oTG2HyhIPBsf9umrXnmuRq66BviA=="],
"@ast-grep/cli-linux-x64-gnu": ["@ast-grep/cli-linux-x64-gnu@0.40.0", "", { "os": "linux", "cpu": "x64" }, "sha512-u2MXFceuwvrO+OQ6zFGoJ6wbATXn46HWwW79j4UPrXYJzVl97jRyjJOIQTJOzTflsk02fjP98DQkfvbXt2dl3Q=="],
"@ast-grep/cli-linux-x64-gnu": ["@ast-grep/cli-linux-x64-gnu@0.40.5", "", { "os": "linux", "cpu": "x64" }, "sha512-GQC5162eIOWXR2eQQ6Knzg7/8Trp5E1ODJkaErf0IubdQrZBGqj5AAcQPcWgPbbnmktjIp0H4NraPpOJ9eJ22A=="],
"@ast-grep/cli-win32-arm64-msvc": ["@ast-grep/cli-win32-arm64-msvc@0.40.0", "", { "os": "win32", "cpu": "arm64" }, "sha512-E/I1xpF/RQL2fo1CQsQfTxyDLnChsbZ+ERrQHKuF1FI4WrkaPOBibpqda60QgVmUcgOGZyZ/GRb3iKEVWPsQNQ=="],
"@ast-grep/cli-win32-arm64-msvc": ["@ast-grep/cli-win32-arm64-msvc@0.40.5", "", { "os": "win32", "cpu": "arm64" }, "sha512-YiZdnQZsSlXQTMsZJop/Ux9MmUGfuRvC2x/UbFgrt5OBSYxND+yoiMc0WcA3WG+wU+tt4ZkB5HUea3r/IkOLYA=="],
"@ast-grep/cli-win32-ia32-msvc": ["@ast-grep/cli-win32-ia32-msvc@0.40.0", "", { "os": "win32", "cpu": "ia32" }, "sha512-9h12OQu1BR0GxHEtT+Z4QkJk3LLWLiKwjBkjXUGlASHYDPTyLcs85KwDLeFHs4BwarF8TDdF+KySvB9WPGl/nQ=="],
"@ast-grep/cli-win32-ia32-msvc": ["@ast-grep/cli-win32-ia32-msvc@0.40.5", "", { "os": "win32", "cpu": "ia32" }, "sha512-MHkCxCITVTr8sY9CcVqNKbfUzMa3Hc6IilGXad0Clnw2vNmPfWqSky+hU/UTerr5YHWwWfAVURH7ANZgirtx0Q=="],
"@ast-grep/cli-win32-x64-msvc": ["@ast-grep/cli-win32-x64-msvc@0.40.0", "", { "os": "win32", "cpu": "x64" }, "sha512-n2+3WynEWFHhXg6KDgjwWQ0UEtIvqUITFbKEk5cDkUYrzYhg/A6kj0qauPwRbVMoJms49vtsNpLkzzqyunio5g=="],
"@ast-grep/cli-win32-x64-msvc": ["@ast-grep/cli-win32-x64-msvc@0.40.5", "", { "os": "win32", "cpu": "x64" }, "sha512-/MJ5un7yxlClaaxou9eYl+Kr2xr/yTtYtTq5aLBWjPWA6dmmJ1nAJgx5zKHVuplFXFBrFDQk3paEgAETMTGcrA=="],
"@ast-grep/napi": ["@ast-grep/napi@0.40.0", "", { "optionalDependencies": { "@ast-grep/napi-darwin-arm64": "0.40.0", "@ast-grep/napi-darwin-x64": "0.40.0", "@ast-grep/napi-linux-arm64-gnu": "0.40.0", "@ast-grep/napi-linux-arm64-musl": "0.40.0", "@ast-grep/napi-linux-x64-gnu": "0.40.0", "@ast-grep/napi-linux-x64-musl": "0.40.0", "@ast-grep/napi-win32-arm64-msvc": "0.40.0", "@ast-grep/napi-win32-ia32-msvc": "0.40.0", "@ast-grep/napi-win32-x64-msvc": "0.40.0" } }, "sha512-tq6nO/8KwUF/mHuk1ECaAOSOlz2OB/PmygnvprJzyAHGRVzdcffblaOOWe90M9sGz5MAasXoF+PTcayQj9TKKA=="],
"@ast-grep/napi": ["@ast-grep/napi@0.40.5", "", { "optionalDependencies": { "@ast-grep/napi-darwin-arm64": "0.40.5", "@ast-grep/napi-darwin-x64": "0.40.5", "@ast-grep/napi-linux-arm64-gnu": "0.40.5", "@ast-grep/napi-linux-arm64-musl": "0.40.5", "@ast-grep/napi-linux-x64-gnu": "0.40.5", "@ast-grep/napi-linux-x64-musl": "0.40.5", "@ast-grep/napi-win32-arm64-msvc": "0.40.5", "@ast-grep/napi-win32-ia32-msvc": "0.40.5", "@ast-grep/napi-win32-x64-msvc": "0.40.5" } }, "sha512-hJA62OeBKUQT68DD2gDyhOqJxZxycqg8wLxbqjgqSzYttCMSDL9tiAQ9abgekBYNHudbJosm9sWOEbmCDfpX2A=="],
"@ast-grep/napi-darwin-arm64": ["@ast-grep/napi-darwin-arm64@0.40.0", "", { "os": "darwin", "cpu": "arm64" }, "sha512-ZMjl5yLhKjxdwbqEEdMizgQdWH2NrWsM6Px+JuGErgCDe6Aedq9yurEPV7veybGdLVJQhOah6htlSflXxjHnYA=="],
"@ast-grep/napi-darwin-arm64": ["@ast-grep/napi-darwin-arm64@0.40.5", "", { "os": "darwin", "cpu": "arm64" }, "sha512-2F072fGN0WTq7KI3okuEnkGJVEHLbi56Bw1H6NAMf7j2mJJeQWsRyGOMcyNnUXZDeNdvoMH0OB2a5wwUegY/nQ=="],
"@ast-grep/napi-darwin-x64": ["@ast-grep/napi-darwin-x64@0.40.0", "", { "os": "darwin", "cpu": "x64" }, "sha512-f9Ol5oQKNRMBkvDtzBK1WiNn2/3eejF2Pn9xwTj7PhXuSFseedOspPYllxQo0gbwUlw/DJqGFTce/jarhR/rBw=="],
"@ast-grep/napi-darwin-x64": ["@ast-grep/napi-darwin-x64@0.40.5", "", { "os": "darwin", "cpu": "x64" }, "sha512-dJMidHZhhxuLBYNi6/FKI812jQ7wcFPSKkVPwviez2D+KvYagapUMAV/4dJ7FCORfguVk8Y0jpPAlYmWRT5nvA=="],
"@ast-grep/napi-linux-arm64-gnu": ["@ast-grep/napi-linux-arm64-gnu@0.40.0", "", { "os": "linux", "cpu": "arm64" }, "sha512-+tO+VW5GDhT9jGkKOK+3b8+ohKjC98WTzn7wSskd/myyhK3oYL1WTKqCm07WSYBZOJvb3z+WaX+wOUrc4bvtyQ=="],
"@ast-grep/napi-linux-arm64-gnu": ["@ast-grep/napi-linux-arm64-gnu@0.40.5", "", { "os": "linux", "cpu": "arm64" }, "sha512-nBRCbyoS87uqkaw4Oyfe5VO+SRm2B+0g0T8ME69Qry9ShMf41a2bTdpcQx9e8scZPogq+CTwDHo3THyBV71l9w=="],
"@ast-grep/napi-linux-arm64-musl": ["@ast-grep/napi-linux-arm64-musl@0.40.0", "", { "os": "linux", "cpu": "arm64" }, "sha512-MS9qalLRjUnF2PCzuTKTvCMVSORYHxxe3Qa0+SSaVULsXRBmuy5C/b1FeWwMFnwNnC0uie3VDet31Zujwi8q6A=="],
"@ast-grep/napi-linux-arm64-musl": ["@ast-grep/napi-linux-arm64-musl@0.40.5", "", { "os": "linux", "cpu": "arm64" }, "sha512-/qKsmds5FMoaEj6FdNzepbmLMtlFuBLdrAn9GIWCqOIcVcYvM1Nka8+mncfeXB/MFZKOrzQsQdPTWqrrQzXLrA=="],
"@ast-grep/napi-linux-x64-gnu": ["@ast-grep/napi-linux-x64-gnu@0.40.0", "", { "os": "linux", "cpu": "x64" }, "sha512-BeHZVMNXhM3WV3XE2yghO0fRxhMOt8BTN972p5piYEQUvKeSHmS8oeGcs6Ahgx5znBclqqqq37ZfioYANiTqJA=="],
"@ast-grep/napi-linux-x64-gnu": ["@ast-grep/napi-linux-x64-gnu@0.40.5", "", { "os": "linux", "cpu": "x64" }, "sha512-DP4oDbq7f/1A2hRTFLhJfDFR6aI5mRWdEfKfHzRItmlKsR9WlcEl1qDJs/zX9R2EEtIDsSKRzuJNfJllY3/W8Q=="],
"@ast-grep/napi-linux-x64-musl": ["@ast-grep/napi-linux-x64-musl@0.40.0", "", { "os": "linux", "cpu": "x64" }, "sha512-rG1YujF7O+lszX8fd5u6qkFTuv4FwHXjWvt1CCvCxXwQLSY96LaCW88oVKg7WoEYQh54y++Fk57F+Wh9Gv9nVQ=="],
"@ast-grep/napi-linux-x64-musl": ["@ast-grep/napi-linux-x64-musl@0.40.5", "", { "os": "linux", "cpu": "x64" }, "sha512-BRZUvVBPUNpWPo6Ns8chXVzxHPY+k9gpsubGTHy92Q26ecZULd/dTkWWdnvfhRqttsSQ9Pe/XQdi5+hDQ6RYcg=="],
"@ast-grep/napi-win32-arm64-msvc": ["@ast-grep/napi-win32-arm64-msvc@0.40.0", "", { "os": "win32", "cpu": "arm64" }, "sha512-9SqmnQqd4zTEUk6yx0TuW2ycZZs2+e569O/R0QnhSiQNpgwiJCYOe/yPS0BC9HkiaozQm6jjAcasWpFtz/dp+w=="],
"@ast-grep/napi-win32-arm64-msvc": ["@ast-grep/napi-win32-arm64-msvc@0.40.5", "", { "os": "win32", "cpu": "arm64" }, "sha512-y95zSEwc7vhxmcrcH0GnK4ZHEBQrmrszRBNQovzaciF9GUqEcCACNLoBesn4V47IaOp4fYgD2/EhGRTIBFb2Ug=="],
"@ast-grep/napi-win32-ia32-msvc": ["@ast-grep/napi-win32-ia32-msvc@0.40.0", "", { "os": "win32", "cpu": "ia32" }, "sha512-0JkdBZi5l9vZhGEO38A1way0LmLRDU5Vos6MXrLIOVkymmzDTDlCdY394J1LMmmsfwWcyJg6J7Yv2dw41MCxDQ=="],
"@ast-grep/napi-win32-ia32-msvc": ["@ast-grep/napi-win32-ia32-msvc@0.40.5", "", { "os": "win32", "cpu": "ia32" }, "sha512-K/u8De62iUnFCzVUs7FBdTZ2Jrgc5/DLHqjpup66KxZ7GIM9/HGME/O8aSoPkpcAeCD4TiTZ11C1i5p5H98hTg=="],
"@ast-grep/napi-win32-x64-msvc": ["@ast-grep/napi-win32-x64-msvc@0.40.0", "", { "os": "win32", "cpu": "x64" }, "sha512-Hk2IwfPqMFGZt5SRxsoWmGLxBXxprow4LRp1eG6V8EEiJCNHxZ9ZiEaIc5bNvMDBjHVSnqZAXT22dROhrcSKQg=="],
"@ast-grep/napi-win32-x64-msvc": ["@ast-grep/napi-win32-x64-msvc@0.40.5", "", { "os": "win32", "cpu": "x64" }, "sha512-dqm5zg/o4Nh4VOQPEpMS23ot8HVd22gG0eg01t4CFcZeuzyuSgBlOL3N7xLbz3iH2sVkk7keuBwAzOIpTqziNQ=="],
"@clack/core": ["@clack/core@0.5.0", "", { "dependencies": { "picocolors": "^1.0.0", "sisteransi": "^1.0.5" } }, "sha512-p3y0FIOwaYRUPRcMO7+dlmLh8PSRcrjuTndsiA0WAFbWES0mLZlrjVoBRZ9DzkPFJZG6KGkJmoEAY0ZcVWTkow=="],
@@ -91,29 +94,29 @@
"@code-yeongyu/comment-checker": ["@code-yeongyu/comment-checker@0.7.0", "", { "os": [ "linux", "win32", "darwin", ], "cpu": [ "x64", "arm64", ], "bin": { "comment-checker": "bin/comment-checker" } }, "sha512-AOic1jPHY3CpNraOuO87YZHO3uRzm9eLd0wyYYN89/76Ugk2TfdUYJ6El/Oe8fzOnHKiOF0IfBeWRo0IUjrHHg=="],
"@hono/node-server": ["@hono/node-server@1.19.9", "", { "peerDependencies": { "hono": "^4" } }, "sha512-vHL6w3ecZsky+8P5MD+eFfaGTyCeOHUIFYMGpQGbrBTSmNNoxv0if69rEZ5giu36weC5saFuznL411gRX7bJDw=="],
"@hono/node-server": ["@hono/node-server@1.19.10", "", { "peerDependencies": { "hono": "^4" } }, "sha512-hZ7nOssGqRgyV3FVVQdfi+U4q02uB23bpnYpdvNXkYTRRyWx84b7yf1ans+dnJ/7h41sGL3CeQTfO+ZGxuO+Iw=="],
"@modelcontextprotocol/sdk": ["@modelcontextprotocol/sdk@1.26.0", "", { "dependencies": { "@hono/node-server": "^1.19.9", "ajv": "^8.17.1", "ajv-formats": "^3.0.1", "content-type": "^1.0.5", "cors": "^2.8.5", "cross-spawn": "^7.0.5", "eventsource": "^3.0.2", "eventsource-parser": "^3.0.0", "express": "^5.2.1", "express-rate-limit": "^8.2.1", "hono": "^4.11.4", "jose": "^6.1.3", "json-schema-typed": "^8.0.2", "pkce-challenge": "^5.0.0", "raw-body": "^3.0.0", "zod": "^3.25 || ^4.0", "zod-to-json-schema": "^3.25.1" }, "peerDependencies": { "@cfworker/json-schema": "^4.1.1" }, "optionalPeers": ["@cfworker/json-schema"] }, "sha512-Y5RmPncpiDtTXDbLKswIJzTqu2hyBKxTNsgKqKclDbhIgg1wgtf1fRuvxgTnRfcnxtvvgbIEcqUOzZrJ6iSReg=="],
"@modelcontextprotocol/sdk": ["@modelcontextprotocol/sdk@1.27.1", "", { "dependencies": { "@hono/node-server": "^1.19.9", "ajv": "^8.17.1", "ajv-formats": "^3.0.1", "content-type": "^1.0.5", "cors": "^2.8.5", "cross-spawn": "^7.0.5", "eventsource": "^3.0.2", "eventsource-parser": "^3.0.0", "express": "^5.2.1", "express-rate-limit": "^8.2.1", "hono": "^4.11.4", "jose": "^6.1.3", "json-schema-typed": "^8.0.2", "pkce-challenge": "^5.0.0", "raw-body": "^3.0.0", "zod": "^3.25 || ^4.0", "zod-to-json-schema": "^3.25.1" }, "peerDependencies": { "@cfworker/json-schema": "^4.1.1" }, "optionalPeers": ["@cfworker/json-schema"] }, "sha512-sr6GbP+4edBwFndLbM60gf07z0FQ79gaExpnsjMGePXqFcSSb7t6iscpjk9DhFhwd+mTEQrzNafGP8/iGGFYaA=="],
"@opencode-ai/plugin": ["@opencode-ai/plugin@1.1.19", "", { "dependencies": { "@opencode-ai/sdk": "1.1.19", "zod": "4.1.8" } }, "sha512-Q6qBEjHb/dJMEw4BUqQxEswTMxCCHUpFMMb6jR8HTTs8X/28XRkKt5pHNPA82GU65IlSoPRph+zd8LReBDN53Q=="],
"@opencode-ai/plugin": ["@opencode-ai/plugin@1.2.16", "", { "dependencies": { "@opencode-ai/sdk": "1.2.16", "zod": "4.1.8" } }, "sha512-9Kb7BQIC2P3oKCvI8K3thP5YP0vE7yLvcmBmgyACUIqc3e5UL6U+4umLpTvgQa2eQdjxtOXznuGTNwgcGMHUHg=="],
"@opencode-ai/sdk": ["@opencode-ai/sdk@1.1.19", "", {}, "sha512-XhZhFuvlLCqDpvNtUEjOsi/wvFj3YCXb1dySp+OONQRMuHlorNYnNa7P2A2ntKuhRdGT1Xt5na0nFzlUyNw+4A=="],
"@opencode-ai/sdk": ["@opencode-ai/sdk@1.2.17", "", {}, "sha512-HdeLeyJ2/Yl/NBHqw9pGFBnkIXuf0Id1kX1GMXDcnZwbJROUJ6TtrW/wLngTYW478E4CCm1jwknjxxmDuxzVMQ=="],
"@types/js-yaml": ["@types/js-yaml@4.0.9", "", {}, "sha512-k4MGaQl5TGo/iipqb2UDG2UwjXziSWkh0uysQelTlJpX1qGlpUZYm8PnO4DxG1qBomtJUdYJ6qR6xdIah10JLg=="],
"@types/node": ["@types/node@24.10.1", "", { "dependencies": { "undici-types": "~7.16.0" } }, "sha512-GNWcUTRBgIRJD5zj+Tq0fKOJ5XZajIiBroOF0yvj2bSU1WvNdYS/dn9UxwsujGW4JX06dnHyjV2y9rRaybH0iQ=="],
"@types/node": ["@types/node@25.3.3", "", { "dependencies": { "undici-types": "~7.18.0" } }, "sha512-DpzbrH7wIcBaJibpKo9nnSQL0MTRdnWttGyE5haGwK86xgMOkFLp7vEyfQPGLOJh5wNYiJ3V9PmUMDhV9u8kkQ=="],
"@types/picomatch": ["@types/picomatch@3.0.2", "", {}, "sha512-n0i8TD3UDB7paoMMxA3Y65vUncFJXjcUf7lQY7YyKGl6031FNjfsLs6pdLFCy2GNFxItPJG8GvvpbZc2skH7WA=="],
"accepts": ["accepts@2.0.0", "", { "dependencies": { "mime-types": "^3.0.0", "negotiator": "^1.0.0" } }, "sha512-5cvg6CtKwfgdmVqY1WIiXKc3Q1bkRqGLi+2W/6ao+6Y7gu/RCwRuAhGEzh5B4KlszSuTLgZYuqFqo5bImjNKng=="],
"ajv": ["ajv@8.17.1", "", { "dependencies": { "fast-deep-equal": "^3.1.3", "fast-uri": "^3.0.1", "json-schema-traverse": "^1.0.0", "require-from-string": "^2.0.2" } }, "sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g=="],
"ajv": ["ajv@8.18.0", "", { "dependencies": { "fast-deep-equal": "^3.1.3", "fast-uri": "^3.0.1", "json-schema-traverse": "^1.0.0", "require-from-string": "^2.0.2" } }, "sha512-PlXPeEWMXMZ7sPYOHqmDyCJzcfNrUr3fGNKtezX14ykXOEIvyK81d+qydx89KY5O71FKMPaQ2vBfBFI5NHR63A=="],
"ajv-formats": ["ajv-formats@3.0.1", "", { "dependencies": { "ajv": "^8.0.0" } }, "sha512-8iUql50EUR+uUcdRQ3HDqa6EVyo3docL8g5WJ3FNcWmu62IbkGUue/pEyLBW8VGKKucTPgqeks4fIU1DA4yowQ=="],
"argparse": ["argparse@2.0.1", "", {}, "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q=="],
"body-parser": ["body-parser@2.2.1", "", { "dependencies": { "bytes": "^3.1.2", "content-type": "^1.0.5", "debug": "^4.4.3", "http-errors": "^2.0.0", "iconv-lite": "^0.7.0", "on-finished": "^2.4.1", "qs": "^6.14.0", "raw-body": "^3.0.1", "type-is": "^2.0.1" } }, "sha512-nfDwkulwiZYQIGwxdy0RUmowMhKcFVcYXUU7m4QlKYim1rUtg83xm2yjZ40QjDuc291AJjjeSc9b++AWHSgSHw=="],
"body-parser": ["body-parser@2.2.2", "", { "dependencies": { "bytes": "^3.1.2", "content-type": "^1.0.5", "debug": "^4.4.3", "http-errors": "^2.0.0", "iconv-lite": "^0.7.0", "on-finished": "^2.4.1", "qs": "^6.14.1", "raw-body": "^3.0.1", "type-is": "^2.0.1" } }, "sha512-oP5VkATKlNwcgvxi0vM0p/D3n2C3EReYVX+DNYs5TjZFn/oQt2j+4sVJtSMr18pdRr8wjTcBl6LoV+FUwzPmNA=="],
"bun-types": ["bun-types@1.3.6", "", { "dependencies": { "@types/node": "*" } }, "sha512-OlFwHcnNV99r//9v5IIOgQ9Uk37gZqrNMCcqEaExdkVq3Avwqok1bJFmvGMCkCE0FqzdY8VMOZpfpR3lwI+CsQ=="],
@@ -123,7 +126,7 @@
"call-bound": ["call-bound@1.0.4", "", { "dependencies": { "call-bind-apply-helpers": "^1.0.2", "get-intrinsic": "^1.3.0" } }, "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg=="],
"commander": ["commander@14.0.2", "", {}, "sha512-TywoWNNRbhoD0BXs1P3ZEScW8W5iKrnbithIl0YH+uCmBd0QpPOA8yc82DS3BIE5Ma6FnBVUsJ7wVUDz4dvOWQ=="],
"commander": ["commander@14.0.3", "", {}, "sha512-H+y0Jo/T1RZ9qPP4Eh1pkcQcLRglraJaSLoyOtHxu6AapkjWVCy2Sit1QQ4x3Dng8qDlSsZEet7g5Pq06MvTgw=="],
"content-disposition": ["content-disposition@1.0.1", "", {}, "sha512-oIXISMynqSqm241k6kcQ5UwttDILMK4BiurCfGEREw6+X9jkkpEe5T9FZaApyLGGOnFuyMWZpdolTXMtvEJ08Q=="],
@@ -133,7 +136,7 @@
"cookie-signature": ["cookie-signature@1.2.2", "", {}, "sha512-D76uU73ulSXrD1UXF4KE2TMxVVwhsnCgfAyTg9k8P6KGZjlXKrOLe4dJQKI3Bxi5wjesZoFXJWElNWBjPZMbhg=="],
"cors": ["cors@2.8.5", "", { "dependencies": { "object-assign": "^4", "vary": "^1" } }, "sha512-KIHbLJqu73RGr/hnbrO9uBeixNGuvSQjul/jdFvS/KFSIH1hWVd1ng7zOHx+YrEfInLG7q4n6GHQ9cDtxv/P6g=="],
"cors": ["cors@2.8.6", "", { "dependencies": { "object-assign": "^4", "vary": "^1" } }, "sha512-tJtZBBHA6vjIAaF6EnIaq6laBBP9aq/Y3ouVJjEfoHbRBcHBAHYcMh/w8LDrk2PvIMMq8gmopa5D4V8RmbrxGw=="],
"cross-spawn": ["cross-spawn@7.0.6", "", { "dependencies": { "path-key": "^3.1.0", "shebang-command": "^2.0.0", "which": "^2.0.1" } }, "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA=="],
@@ -191,11 +194,11 @@
"hasown": ["hasown@2.0.2", "", { "dependencies": { "function-bind": "^1.1.2" } }, "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ=="],
"hono": ["hono@4.12.0", "", {}, "sha512-NekXntS5M94pUfiVZ8oXXK/kkri+5WpX2/Ik+LVsl+uvw+soj4roXIsPqO+XsWrAw20mOzaXOZf3Q7PfB9A/IA=="],
"hono": ["hono@4.12.5", "", {}, "sha512-3qq+FUBtlTHhtYxbxheZgY8NIFnkkC/MR8u5TTsr7YZ3wixryQ3cCwn3iZbg8p8B88iDBBAYSfZDS75t8MN7Vg=="],
"http-errors": ["http-errors@2.0.1", "", { "dependencies": { "depd": "~2.0.0", "inherits": "~2.0.4", "setprototypeof": "~1.2.0", "statuses": "~2.0.2", "toidentifier": "~1.0.1" } }, "sha512-4FbRdAX+bSdmo4AUFuS0WNiPz8NgFt+r8ThgNWmlrjQjt1Q7ZR9+zTlce2859x4KSXrwIsaeTqDoKQmtP8pLmQ=="],
"iconv-lite": ["iconv-lite@0.7.1", "", { "dependencies": { "safer-buffer": ">= 2.1.2 < 3.0.0" } }, "sha512-2Tth85cXwGFHfvRgZWszZSvdo+0Xsqmw8k8ZwxScfcBneNUraK+dxRxRm24nszx80Y0TVio8kKLt5sLE7ZCLlw=="],
"iconv-lite": ["iconv-lite@0.7.2", "", { "dependencies": { "safer-buffer": ">= 2.1.2 < 3.0.0" } }, "sha512-im9DjEDQ55s9fL4EYzOAv0yMqmMBSZp6G0VvFyTMPKWxiSBHUj9NW/qqLmXUwXrrM7AvqSlTCfvqRb0cM8yYqw=="],
"inherits": ["inherits@2.0.4", "", {}, "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ=="],
@@ -275,7 +278,7 @@
"proxy-addr": ["proxy-addr@2.0.7", "", { "dependencies": { "forwarded": "0.2.0", "ipaddr.js": "1.9.1" } }, "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg=="],
"qs": ["qs@6.14.1", "", { "dependencies": { "side-channel": "^1.1.0" } }, "sha512-4EK3+xJl8Ts67nLYNwqw/dsFVnCf+qR7RgXSK9jEEm9unao3njwMDdmsdvoKBKHzxd7tCYz5e5M+SnMjdtXGQQ=="],
"qs": ["qs@6.15.0", "", { "dependencies": { "side-channel": "^1.1.0" } }, "sha512-mAZTtNCeetKMH+pSjrb76NAM8V9a05I9aBZOHztWy/UqcJdQYNsf59vrRKWnojAT9Y+GbIvoTBC++CPHqpDBhQ=="],
"range-parser": ["range-parser@1.2.1", "", {}, "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg=="],
@@ -315,7 +318,7 @@
"typescript": ["typescript@5.9.3", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw=="],
"undici-types": ["undici-types@7.16.0", "", {}, "sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw=="],
"undici-types": ["undici-types@7.18.2", "", {}, "sha512-AsuCzffGHJybSaRrmr5eHr81mwJU3kjw6M+uprWvCXiNeN9SOGwQ3Jn8jb8m3Z6izVgknn1R0FTCEAP2QrLY/w=="],
"unpipe": ["unpipe@1.0.0", "", {}, "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ=="],
@@ -327,8 +330,10 @@
"wrappy": ["wrappy@1.0.2", "", {}, "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ=="],
"zod": ["zod@4.1.8", "", {}, "sha512-5R1P+WwQqmmMIEACyzSvo4JXHY5WiAFHRMg+zBZKgKS+Q1viRa0C1hmUKtHltoIFKtIdki3pRxkmpP74jnNYHQ=="],
"zod": ["zod@4.3.6", "", {}, "sha512-rftlrkhHZOcjDwkGlnUtZZkvaPHCsDATp4pGpuOOMDaTdDDXF91wuVDJoWoPsKX/3YPQ5fHuF3STjcYyKr+Qhg=="],
"zod-to-json-schema": ["zod-to-json-schema@3.25.1", "", { "peerDependencies": { "zod": "^3.25 || ^4" } }, "sha512-pM/SU9d3YAggzi6MtR4h7ruuQlqKtad8e9S0fmxcMi+ueAK5Korys/aWcV9LIIHTVbj01NdzxcnXSN+O74ZIVA=="],
"@opencode-ai/plugin/zod": ["zod@4.1.8", "", {}, "sha512-5R1P+WwQqmmMIEACyzSvo4JXHY5WiAFHRMg+zBZKgKS+Q1viRa0C1hmUKtHltoIFKtIdki3pRxkmpP74jnNYHQ=="],
}
}

View File

@@ -56,8 +56,8 @@
"@clack/prompts": "^0.11.0",
"@code-yeongyu/comment-checker": "^0.7.0",
"@modelcontextprotocol/sdk": "^1.25.2",
"@opencode-ai/plugin": "^1.1.19",
"@opencode-ai/sdk": "^1.1.19",
"@opencode-ai/plugin": "^1.2.16",
"@opencode-ai/sdk": "^1.2.17",
"commander": "^14.0.2",
"detect-libc": "^2.0.0",
"diff": "^8.0.3",
@@ -87,6 +87,9 @@
"oh-my-opencode-windows-x64": "3.10.0",
"oh-my-opencode-windows-x64-baseline": "3.10.0"
},
"overrides": {
"@opencode-ai/sdk": "^1.2.17"
},
"trustedDependencies": [
"@ast-grep/cli",
"@ast-grep/napi",

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -1895,6 +1895,94 @@
"created_at": "2026-03-04T00:43:53Z",
"repoId": 1108837393,
"pullRequestNo": 2277
},
{
"name": "chan1103",
"id": 241870013,
"comment_id": 3996082243,
"created_at": "2026-03-04T08:40:54Z",
"repoId": 1108837393,
"pullRequestNo": 2288
},
{
"name": "SeeYouCowboi",
"id": 103308766,
"comment_id": 3996126396,
"created_at": "2026-03-04T08:50:32Z",
"repoId": 1108837393,
"pullRequestNo": 2291
},
{
"name": "guazi04",
"id": 134621827,
"comment_id": 3996644267,
"created_at": "2026-03-04T10:31:44Z",
"repoId": 1108837393,
"pullRequestNo": 2293
},
{
"name": "brandonwebb-vista",
"id": 237281185,
"comment_id": 3998901238,
"created_at": "2026-03-04T17:07:00Z",
"repoId": 1108837393,
"pullRequestNo": 2299
},
{
"name": "RaviTharuma",
"id": 25951435,
"comment_id": 4000536638,
"created_at": "2026-03-04T21:53:38Z",
"repoId": 1108837393,
"pullRequestNo": 2302
},
{
"name": "Romanok2805",
"id": 37216910,
"comment_id": 4001032410,
"created_at": "2026-03-04T23:51:02Z",
"repoId": 1108837393,
"pullRequestNo": 2306
},
{
"name": "Vacbo",
"id": 53411412,
"comment_id": 4002083771,
"created_at": "2026-03-05T04:19:50Z",
"repoId": 1108837393,
"pullRequestNo": 2310
},
{
"name": "Wangmerlyn",
"id": 29993182,
"comment_id": 4004271570,
"created_at": "2026-03-05T11:08:09Z",
"repoId": 1108837393,
"pullRequestNo": 2318
},
{
"name": "mInrOz",
"id": 14320143,
"comment_id": 4004791744,
"created_at": "2026-03-05T12:42:30Z",
"repoId": 1108837393,
"pullRequestNo": 2321
},
{
"name": "hkc5",
"id": 142545736,
"comment_id": 4006670642,
"created_at": "2026-03-05T17:49:07Z",
"repoId": 1108837393,
"pullRequestNo": 2327
},
{
"name": "mrosnerr",
"id": 3758430,
"comment_id": 4006707281,
"created_at": "2026-03-05T17:55:33Z",
"repoId": 1108837393,
"pullRequestNo": 2328
}
]
}

View File

@@ -0,0 +1,92 @@
/// <reference types="bun-types" />
import { describe, it, expect } from "bun:test"
import { buildAntiDuplicationSection } from "./dynamic-agent-prompt-builder"
describe("buildAntiDuplicationSection", () => {
it("#given no arguments #when building anti-duplication section #then returns comprehensive rule section", () => {
//#given: no special configuration needed
//#when: building the anti-duplication section
const result = buildAntiDuplicationSection()
//#then: should contain the anti-duplication rule with all key concepts
expect(result).toContain("Anti-Duplication Rule")
expect(result).toContain("CRITICAL")
expect(result).toContain("DO NOT perform the same search yourself")
})
it("#given no arguments #when building #then explicitly forbids manual re-search after delegation", () => {
//#given: no special configuration
//#when: building the section
const result = buildAntiDuplicationSection()
//#then: should explicitly list forbidden behaviors
expect(result).toContain("FORBIDDEN")
expect(result).toContain("manually grep/search for the same information")
expect(result).toContain("Re-doing the research")
})
it("#given no arguments #when building #then allows non-overlapping work", () => {
//#given: no special configuration
//#when: building the section
const result = buildAntiDuplicationSection()
//#then: should explicitly allow non-overlapping work
expect(result).toContain("ALLOWED")
expect(result).toContain("non-overlapping work")
expect(result).toContain("work that doesn't depend on the delegated research")
})
it("#given no arguments #when building #then includes wait-for-results instructions", () => {
//#given: no special configuration
//#when: building the section
const result = buildAntiDuplicationSection()
//#then: should include instructions for waiting properly
expect(result).toContain("Wait for Results Properly")
expect(result).toContain("End your response")
expect(result).toContain("Wait for the completion notification")
expect(result).toContain("background_output")
})
it("#given no arguments #when building #then explains why this matters", () => {
//#given: no special configuration
//#when: building the section
const result = buildAntiDuplicationSection()
//#then: should explain the purpose
expect(result).toContain("Why This Matters")
expect(result).toContain("Wasted tokens")
expect(result).toContain("Confusion")
expect(result).toContain("Efficiency")
})
it("#given no arguments #when building #then provides code examples", () => {
//#given: no special configuration
//#when: building the section
const result = buildAntiDuplicationSection()
//#then: should include examples
expect(result).toContain("Example")
expect(result).toContain("WRONG")
expect(result).toContain("CORRECT")
expect(result).toContain("task(subagent_type=")
})
it("#given no arguments #when building #then uses proper markdown formatting", () => {
//#given: no special configuration
//#when: building the section
const result = buildAntiDuplicationSection()
//#then: should be wrapped in Anti_Duplication tag
expect(result).toContain("<Anti_Duplication>")
expect(result).toContain("</Anti_Duplication>")
})
})

View File

@@ -0,0 +1,118 @@
import { describe, test, expect } from "bun:test"
import { ATLAS_SYSTEM_PROMPT } from "./default"
import { ATLAS_GPT_SYSTEM_PROMPT } from "./gpt"
import { ATLAS_GEMINI_SYSTEM_PROMPT } from "./gemini"
describe("Atlas prompts auto-continue policy", () => {
test("default variant should forbid asking user for continuation confirmation", () => {
// given
const prompt = ATLAS_SYSTEM_PROMPT
// when
const lowerPrompt = prompt.toLowerCase()
// then
expect(lowerPrompt).toContain("auto-continue policy")
expect(lowerPrompt).toContain("never ask the user")
expect(lowerPrompt).toContain("should i continue")
expect(lowerPrompt).toContain("proceed to next task")
expect(lowerPrompt).toContain("approval-style")
expect(lowerPrompt).toContain("auto-continue immediately")
})
test("gpt variant should forbid asking user for continuation confirmation", () => {
// given
const prompt = ATLAS_GPT_SYSTEM_PROMPT
// when
const lowerPrompt = prompt.toLowerCase()
// then
expect(lowerPrompt).toContain("auto-continue policy")
expect(lowerPrompt).toContain("never ask the user")
expect(lowerPrompt).toContain("should i continue")
expect(lowerPrompt).toContain("proceed to next task")
expect(lowerPrompt).toContain("approval-style")
expect(lowerPrompt).toContain("auto-continue immediately")
})
test("gemini variant should forbid asking user for continuation confirmation", () => {
// given
const prompt = ATLAS_GEMINI_SYSTEM_PROMPT
// when
const lowerPrompt = prompt.toLowerCase()
// then
expect(lowerPrompt).toContain("auto-continue policy")
expect(lowerPrompt).toContain("never ask the user")
expect(lowerPrompt).toContain("should i continue")
expect(lowerPrompt).toContain("proceed to next task")
expect(lowerPrompt).toContain("approval-style")
expect(lowerPrompt).toContain("auto-continue immediately")
})
test("all variants should require immediate continuation after verification passes", () => {
// given
const prompts = [ATLAS_SYSTEM_PROMPT, ATLAS_GPT_SYSTEM_PROMPT, ATLAS_GEMINI_SYSTEM_PROMPT]
// when / then
for (const prompt of prompts) {
const lowerPrompt = prompt.toLowerCase()
expect(lowerPrompt).toMatch(/auto-continue immediately after verification/)
expect(lowerPrompt).toMatch(/immediately delegate next task/)
}
})
test("all variants should define when user interaction is actually needed", () => {
// given
const prompts = [ATLAS_SYSTEM_PROMPT, ATLAS_GPT_SYSTEM_PROMPT, ATLAS_GEMINI_SYSTEM_PROMPT]
// when / then
for (const prompt of prompts) {
const lowerPrompt = prompt.toLowerCase()
expect(lowerPrompt).toMatch(/only pause.*truly blocked/)
expect(lowerPrompt).toMatch(/plan needs clarification|blocked by external/)
}
})
})
describe("Atlas prompts plan path consistency", () => {
test("default variant should use .sisyphus/plans/{plan-name}.md path", () => {
// given
const prompt = ATLAS_SYSTEM_PROMPT
// when / then
expect(prompt).toContain(".sisyphus/plans/{plan-name}.md")
expect(prompt).not.toContain(".sisyphus/tasks/{plan-name}.yaml")
expect(prompt).not.toContain(".sisyphus/tasks/")
})
test("gpt variant should use .sisyphus/plans/{plan-name}.md path", () => {
// given
const prompt = ATLAS_GPT_SYSTEM_PROMPT
// when / then
expect(prompt).toContain(".sisyphus/plans/{plan-name}.md")
expect(prompt).not.toContain(".sisyphus/tasks/")
})
test("gemini variant should use .sisyphus/plans/{plan-name}.md path", () => {
// given
const prompt = ATLAS_GEMINI_SYSTEM_PROMPT
// when / then
expect(prompt).toContain(".sisyphus/plans/{plan-name}.md")
expect(prompt).not.toContain(".sisyphus/tasks/")
})
test("all variants should read plan file after verification", () => {
// given
const prompts = [ATLAS_SYSTEM_PROMPT, ATLAS_GPT_SYSTEM_PROMPT, ATLAS_GEMINI_SYSTEM_PROMPT]
// when / then
for (const prompt of prompts) {
expect(prompt).toMatch(/read[\s\S]*?\.sisyphus\/plans\//)
}
})
})

View File

@@ -99,6 +99,29 @@ Every \`task()\` prompt MUST include ALL 6 sections:
**If your prompt is under 30 lines, it's TOO SHORT.**
</delegation_system>
<auto_continue>
## AUTO-CONTINUE POLICY (STRICT)
**CRITICAL: NEVER ask the user "should I continue", "proceed to next task", or any approval-style questions between plan steps.**
**You MUST auto-continue immediately after verification passes:**
- After any delegation completes and passes verification → Immediately delegate next task
- Do NOT wait for user input, do NOT ask "should I continue"
- Only pause or ask if you are truly blocked by missing information, an external dependency, or a critical failure
**The only time you ask the user:**
- Plan needs clarification or modification before execution
- Blocked by an external dependency beyond your control
- Critical failure prevents any further progress
**Auto-continue examples:**
- Task A done → Verify → Pass → Immediately start Task B
- Task fails → Retry 3x → Still fails → Document → Move to next independent task
- NEVER: "Should I continue to the next task?"
**This is NOT optional. This is core to your role as orchestrator.**
</auto_continue>
<workflow>
## Step 0: Register Tracking
@@ -214,7 +237,7 @@ After EVERY delegation, complete ALL of these steps — no shortcuts:
After verification, READ the plan file directly — every time, no exceptions:
\`\`\`
Read(".sisyphus/tasks/{plan-name}.yaml")
Read(".sisyphus/plans/{plan-name}.md")
\`\`\`
Count remaining \`- [ ]\` tasks. This is your ground truth for what comes next.

View File

@@ -116,6 +116,29 @@ Every \`task()\` prompt MUST include ALL 6 sections:
**Minimum 30 lines per delegation prompt. Under 30 lines = the subagent WILL fail.**
</delegation_system>
<auto_continue>
## AUTO-CONTINUE POLICY (STRICT)
**CRITICAL: NEVER ask the user "should I continue", "proceed to next task", or any approval-style questions between plan steps.**
**You MUST auto-continue immediately after verification passes:**
- After any delegation completes and passes verification → Immediately delegate next task
- Do NOT wait for user input, do NOT ask "should I continue"
- Only pause or ask if you are truly blocked by missing information, an external dependency, or a critical failure
**The only time you ask the user:**
- Plan needs clarification or modification before execution
- Blocked by an external dependency beyond your control
- Critical failure prevents any further progress
**Auto-continue examples:**
- Task A done → Verify → Pass → Immediately start Task B
- Task fails → Retry 3x → Still fails → Document → Move to next independent task
- NEVER: "Should I continue to the next task?"
**This is NOT optional. This is core to your role as orchestrator.**
</auto_continue>
<workflow>
## Step 0: Register Tracking

View File

@@ -48,9 +48,10 @@ Complete ALL tasks in a work plan via \`task()\` until fully done.
</scope_and_design_constraints>
<uncertainty_and_ambiguity>
- If a task is ambiguous or underspecified:
- During initial plan analysis, if a task is ambiguous or underspecified:
- Ask 1-3 precise clarifying questions, OR
- State your interpretation explicitly and proceed with the simplest approach.
- Once execution has started, do NOT stop to ask for continuation or approval between steps.
- Never fabricate task details, file paths, or requirements.
- Prefer language like "Based on the plan..." instead of absolute claims.
- When unsure about parallelization, default to sequential execution.
@@ -134,6 +135,29 @@ Every \`task()\` prompt MUST include ALL 6 sections:
**Minimum 30 lines per delegation prompt.**
</delegation_system>
<auto_continue>
## AUTO-CONTINUE POLICY (STRICT)
**CRITICAL: NEVER ask the user "should I continue", "proceed to next task", or any approval-style questions between plan steps.**
**You MUST auto-continue immediately after verification passes:**
- After any delegation completes and passes verification → Immediately delegate next task
- Do NOT wait for user input, do NOT ask "should I continue"
- Only pause or ask if you are truly blocked by missing information, an external dependency, or a critical failure
**The only time you ask the user:**
- Plan needs clarification or modification before execution
- Blocked by an external dependency beyond your control
- Critical failure prevents any further progress
**Auto-continue examples:**
- Task A done → Verify → Pass → Immediately start Task B
- Task fails → Retry 3x → Still fails → Document → Move to next independent task
- NEVER: "Should I continue to the next task?"
**This is NOT optional. This is core to your role as orchestrator.**
</auto_continue>
<workflow>
## Step 0: Register Tracking

View File

@@ -0,0 +1,88 @@
import { describe, expect, test } from "bun:test"
import { createSisyphusAgent } from "./sisyphus"
import { createHephaestusAgent } from "./hephaestus"
import { buildSisyphusJuniorPrompt } from "./sisyphus-junior/agent"
import {
buildAntiDuplicationSection,
buildExploreSection,
type AvailableAgent,
} from "./dynamic-agent-prompt-builder"
const exploreAgent = {
name: "explore",
description: "Contextual grep specialist",
metadata: {
category: "advisor",
cost: "FREE",
promptAlias: "Explore",
triggers: [],
useWhen: ["Multiple search angles needed"],
avoidWhen: ["Single keyword search is enough"],
},
} satisfies AvailableAgent
describe("delegation trust prompt rules", () => {
test("buildAntiDuplicationSection explains overlap is forbidden", () => {
// given
const section = buildAntiDuplicationSection()
// when / then
expect(section).toContain("DO NOT perform the same search yourself")
expect(section).toContain("non-overlapping work")
expect(section).toContain("End your response")
})
test("buildExploreSection includes delegation trust rule", () => {
// given
const agents = [exploreAgent]
// when
const section = buildExploreSection(agents)
// then
expect(section).toContain("Delegation Trust Rule")
expect(section).toContain("do **not** manually perform that same search yourself")
})
test("Sisyphus prompt forbids duplicate delegated exploration", () => {
// given
const agent = createSisyphusAgent("anthropic/claude-sonnet-4-6", [exploreAgent])
// when
const prompt = agent.prompt
// then
expect(prompt).toContain("Continue only with non-overlapping work")
expect(prompt).toContain("DO NOT perform the same search yourself")
})
test("Hephaestus prompt forbids duplicate delegated exploration", () => {
// given
const agent = createHephaestusAgent("openai/gpt-5.2", [exploreAgent])
// when
const prompt = agent.prompt
// then
expect(prompt).toContain("Continue only with non-overlapping work after launching background agents")
expect(prompt).toContain("DO NOT perform the same search yourself")
})
test("Sisyphus-Junior GPT prompt forbids duplicate delegated exploration", () => {
// given
const prompt = buildSisyphusJuniorPrompt("openai/gpt-5.2", false)
// when / then
expect(prompt).toContain("continue only with non-overlapping work while they search")
expect(prompt).toContain("DO NOT perform the same search yourself")
})
test("Sisyphus-Junior Gemini prompt forbids duplicate delegated exploration", () => {
// given
const prompt = buildSisyphusJuniorPrompt("google/gemini-3.1-pro", false)
// when / then
expect(prompt).toContain("continue only with non-overlapping work while they search")
expect(prompt).toContain("DO NOT perform the same search yourself")
})
})

View File

@@ -118,6 +118,8 @@ export function buildExploreSection(agents: AvailableAgent[]): string {
Use it as a **peer tool**, not a fallback. Fire liberally.
**Delegation Trust Rule:** Once you fire an explore agent for a search, do **not** manually perform that same search yourself. Use direct tools only for non-overlapping work or when you intentionally skipped delegation.
**Use Direct Tools when:**
${avoidWhen.map((w) => `- ${w}`).join("\n")}
@@ -308,6 +310,7 @@ export function buildAntiPatternsSection(): string {
"- **Search**: Firing agents for single-line typos or obvious syntax errors",
"- **Debugging**: Shotgun debugging, random changes",
"- **Background Tasks**: Polling `background_output` on running tasks — end response and wait for notification",
"- **Delegation Duplication**: Delegating exploration to explore/librarian and then manually doing the same search yourself",
"- **Oracle**: Delivering answer without collecting Oracle results",
]
@@ -409,3 +412,52 @@ export function buildUltraworkSection(
return lines.join("\n")
}
// Anti-duplication section for agent prompts
export function buildAntiDuplicationSection(): string {
return `<Anti_Duplication>
## Anti-Duplication Rule (CRITICAL)
Once you delegate exploration to explore/librarian agents, **DO NOT perform the same search yourself**.
### What this means:
**FORBIDDEN:**
- After firing explore/librarian, manually grep/search for the same information
- Re-doing the research the agents were just tasked with
- "Just quickly checking" the same files the background agents are checking
**ALLOWED:**
- Continue with **non-overlapping work** — work that doesn't depend on the delegated research
- Work on unrelated parts of the codebase
- Preparation work (e.g., setting up files, configs) that can proceed independently
### Wait for Results Properly:
When you need the delegated results but they're not ready:
1. **End your response** — do NOT continue with work that depends on those results
2. **Wait for the completion notification** — the system will trigger your next turn
3. **Then** collect results via \`background_output(task_id="...")\`
4. **Do NOT** impatiently re-search the same topics while waiting
### Why This Matters:
- **Wasted tokens**: Duplicate exploration wastes your context budget
- **Confusion**: You might contradict the agent's findings
- **Efficiency**: The whole point of delegation is parallel throughput
### Example:
\`\`\`typescript
// WRONG: After delegating, re-doing the search
task(subagent_type="explore", run_in_background=true, ...)
// Then immediately grep for the same thing yourself — FORBIDDEN
// CORRECT: Continue non-overlapping work
task(subagent_type="explore", run_in_background=true, ...)
// Work on a different, unrelated file while they search
// End your response and wait for the notification
\`\`\`
</Anti_Duplication>`
}

View File

@@ -16,6 +16,7 @@ import {
buildOracleSection,
buildHardBlocksSection,
buildAntiPatternsSection,
buildAntiDuplicationSection,
categorizeTools,
} from "./dynamic-agent-prompt-builder";
@@ -290,11 +291,13 @@ Prompt structure for each agent:
- Fire 2-5 explore agents in parallel for any non-trivial codebase question
- Parallelize independent file reads — don't read files one at a time
- NEVER use \`run_in_background=false\` for explore/librarian
- Continue your work immediately after launching background agents
- Continue only with non-overlapping work after launching background agents
- Collect results with \`background_output(task_id="...")\` when needed
- BEFORE final answer, cancel DISPOSABLE tasks individually: \`background_cancel(taskId="bg_explore_xxx")\`, \`background_cancel(taskId="bg_librarian_xxx")\`
- **NEVER use \`background_cancel(all=true)\`** — it kills tasks whose results you haven't collected yet
${buildAntiDuplicationSection()}
### Search Stop Conditions
STOP searching when:

View File

@@ -9,6 +9,7 @@
*/
import { resolvePromptAppend } from "../builtin-agents/resolve-file-uri"
import { buildAntiDuplicationSection } from "../dynamic-agent-prompt-builder"
export function buildGeminiSisyphusJuniorPrompt(
useTaskSystem: boolean,
@@ -58,7 +59,7 @@ Before responding, ask yourself: What tools do I need to call? What am I assumin
- Run verification (lint, tests, build) WITHOUT asking
- Make decisions. Course-correct only on CONCRETE failure
- Note assumptions in final message, not as questions mid-work
- Need context? Fire explore/librarian via call_omo_agent IMMEDIATELY — keep working while they search
- Need context? Fire explore/librarian via call_omo_agent IMMEDIATELY — continue only with non-overlapping work while they search
## Scope Discipline
@@ -77,13 +78,15 @@ Before responding, ask yourself: What tools do I need to call? What am I assumin
<tool_usage_rules>
- Parallelize independent tool calls: multiple file reads, grep searches, agent fires — all at once
- Explore/Librarian via call_omo_agent = background research. Fire them and keep working
- Explore/Librarian via call_omo_agent = background research. Fire them and continue only with non-overlapping work
- After any file edit: restate what changed, where, and what validation follows
- Prefer tools over guessing whenever you need specific data (files, configs, patterns)
- ALWAYS use tools over internal knowledge for file contents, project state, and verification
- **DO NOT SKIP tool calls because you think you already know the answer. You DON'T.**
</tool_usage_rules>
${buildAntiDuplicationSection()}
${taskDiscipline}
## Progress Updates

View File

@@ -7,6 +7,7 @@
*/
import { resolvePromptAppend } from "../builtin-agents/resolve-file-uri"
import { buildAntiDuplicationSection } from "../dynamic-agent-prompt-builder"
export function buildGptSisyphusJuniorPrompt(
useTaskSystem: boolean,
@@ -40,7 +41,7 @@ When blocked: try a different approach → decompose the problem → challenge a
- Run verification (lint, tests, build) WITHOUT asking
- Make decisions. Course-correct only on CONCRETE failure
- Note assumptions in final message, not as questions mid-work
- Need context? Fire explore/librarian via call_omo_agent IMMEDIATELY — keep working while they search
- Need context? Fire explore/librarian via call_omo_agent IMMEDIATELY — continue only with non-overlapping work while they search
## Scope Discipline
@@ -58,12 +59,14 @@ When blocked: try a different approach → decompose the problem → challenge a
<tool_usage_rules>
- Parallelize independent tool calls: multiple file reads, grep searches, agent fires — all at once
- Explore/Librarian via call_omo_agent = background research. Fire them and keep working
- Explore/Librarian via call_omo_agent = background research. Fire them and continue only with non-overlapping work
- After any file edit: restate what changed, where, and what validation follows
- Prefer tools over guessing whenever you need specific data (files, configs, patterns)
- ALWAYS use tools over internal knowledge for file contents, project state, and verification
</tool_usage_rules>
${buildAntiDuplicationSection()}
${taskDiscipline}
## Progress Updates

View File

@@ -35,6 +35,7 @@ import {
buildAntiPatternsSection,
buildDeepParallelSection,
buildNonClaudePlannerSection,
buildAntiDuplicationSection,
categorizeTools,
} from "./dynamic-agent-prompt-builder";
@@ -333,7 +334,7 @@ task(subagent_type="explore", run_in_background=true, load_skills=[], descriptio
// Reference Grep (external)
task(subagent_type="librarian", run_in_background=true, load_skills=[], description="Find JWT security docs", prompt="I'm implementing JWT auth and need current security best practices to choose token storage (httpOnly cookies vs localStorage) and set expiration policy. Find: OWASP auth guidelines, recommended token lifetimes, refresh token rotation strategies, common JWT vulnerabilities. Skip 'what is JWT' tutorials — production security guidance only.")
task(subagent_type="librarian", run_in_background=true, load_skills=[], description="Find Express auth patterns", prompt="I'm building Express auth middleware and need production-quality patterns to structure my middleware chain. Find how established Express apps (1000+ stars) handle: middleware ordering, token refresh, role-based access control, auth error propagation. Skip basic tutorials — I need battle-tested patterns with proper error handling.")
// Continue working immediately. System notifies on completion — collect with background_output then.
// Continue only with non-overlapping work. System notifies on completion — collect with background_output then.
// WRONG: Sequential or blocking
result = task(..., run_in_background=false) // Never wait synchronously for explore/librarian
@@ -341,11 +342,13 @@ result = task(..., run_in_background=false) // Never wait synchronously for exp
### Background Result Collection:
1. Launch parallel agents \u2192 receive task_ids
2. Continue immediate work
2. Continue only with non-overlapping work
3. System sends \`<system-reminder>\` on each task completion — then call \`background_output(task_id="...")\`
4. Need results not yet ready? **End your response.** The notification will trigger your next turn.
5. Cleanup: Cancel disposable tasks individually via \`background_cancel(taskId="...")\`
${buildAntiDuplicationSection()}
### Search Stop Conditions
STOP searching when:

View File

@@ -83,7 +83,7 @@ exports[`generateModelConfig single native provider uses Claude models when only
"variant": "max",
},
"multimodal-looker": {
"model": "anthropic/claude-haiku-4-5",
"model": "opencode/glm-4.7-free",
},
"oracle": {
"model": "anthropic/claude-opus-4-6",
@@ -145,7 +145,7 @@ exports[`generateModelConfig single native provider uses Claude models with isMa
"variant": "max",
},
"multimodal-looker": {
"model": "anthropic/claude-haiku-4-5",
"model": "opencode/glm-4.7-free",
},
"oracle": {
"model": "anthropic/claude-opus-4-6",
@@ -212,7 +212,8 @@ exports[`generateModelConfig single native provider uses OpenAI models when only
"variant": "medium",
},
"multimodal-looker": {
"model": "openai/gpt-5.2",
"model": "openai/gpt-5.3-codex",
"variant": "medium",
},
"oracle": {
"model": "openai/gpt-5.2",
@@ -279,7 +280,8 @@ exports[`generateModelConfig single native provider uses OpenAI models with isMa
"variant": "medium",
},
"multimodal-looker": {
"model": "openai/gpt-5.2",
"model": "openai/gpt-5.3-codex",
"variant": "medium",
},
"oracle": {
"model": "openai/gpt-5.2",
@@ -468,7 +470,8 @@ exports[`generateModelConfig all native providers uses preferred models from fal
"variant": "medium",
},
"multimodal-looker": {
"model": "google/gemini-3-flash-preview",
"model": "openai/gpt-5.3-codex",
"variant": "medium",
},
"oracle": {
"model": "openai/gpt-5.2",
@@ -542,7 +545,8 @@ exports[`generateModelConfig all native providers uses preferred models with isM
"variant": "medium",
},
"multimodal-looker": {
"model": "google/gemini-3-flash-preview",
"model": "openai/gpt-5.3-codex",
"variant": "medium",
},
"oracle": {
"model": "openai/gpt-5.2",
@@ -596,7 +600,7 @@ exports[`generateModelConfig fallback providers uses OpenCode Zen models when on
"$schema": "https://raw.githubusercontent.com/code-yeongyu/oh-my-opencode/dev/assets/oh-my-opencode.schema.json",
"agents": {
"atlas": {
"model": "opencode/kimi-k2.5-free",
"model": "opencode/claude-sonnet-4-5",
},
"explore": {
"model": "opencode/claude-haiku-4-5",
@@ -617,7 +621,8 @@ exports[`generateModelConfig fallback providers uses OpenCode Zen models when on
"variant": "medium",
},
"multimodal-looker": {
"model": "opencode/gemini-3-flash",
"model": "opencode/gpt-5.3-codex",
"variant": "medium",
},
"oracle": {
"model": "opencode/gpt-5.2",
@@ -670,7 +675,7 @@ exports[`generateModelConfig fallback providers uses OpenCode Zen models with is
"$schema": "https://raw.githubusercontent.com/code-yeongyu/oh-my-opencode/dev/assets/oh-my-opencode.schema.json",
"agents": {
"atlas": {
"model": "opencode/kimi-k2.5-free",
"model": "opencode/claude-sonnet-4-5",
},
"explore": {
"model": "opencode/claude-haiku-4-5",
@@ -691,7 +696,8 @@ exports[`generateModelConfig fallback providers uses OpenCode Zen models with is
"variant": "medium",
},
"multimodal-looker": {
"model": "opencode/gemini-3-flash",
"model": "opencode/gpt-5.3-codex",
"variant": "medium",
},
"oracle": {
"model": "opencode/gpt-5.2",
@@ -988,7 +994,7 @@ exports[`generateModelConfig mixed provider scenarios uses Claude + OpenCode Zen
"$schema": "https://raw.githubusercontent.com/code-yeongyu/oh-my-opencode/dev/assets/oh-my-opencode.schema.json",
"agents": {
"atlas": {
"model": "opencode/kimi-k2.5-free",
"model": "anthropic/claude-sonnet-4-5",
},
"explore": {
"model": "anthropic/claude-haiku-4-5",
@@ -1009,7 +1015,8 @@ exports[`generateModelConfig mixed provider scenarios uses Claude + OpenCode Zen
"variant": "medium",
},
"multimodal-looker": {
"model": "opencode/gemini-3-flash",
"model": "opencode/gpt-5.3-codex",
"variant": "medium",
},
"oracle": {
"model": "opencode/gpt-5.2",
@@ -1083,7 +1090,8 @@ exports[`generateModelConfig mixed provider scenarios uses OpenAI + Copilot comb
"variant": "medium",
},
"multimodal-looker": {
"model": "github-copilot/gemini-3-flash-preview",
"model": "openai/gpt-5.3-codex",
"variant": "medium",
},
"oracle": {
"model": "openai/gpt-5.2",
@@ -1263,7 +1271,7 @@ exports[`generateModelConfig mixed provider scenarios uses all fallback provider
"$schema": "https://raw.githubusercontent.com/code-yeongyu/oh-my-opencode/dev/assets/oh-my-opencode.schema.json",
"agents": {
"atlas": {
"model": "opencode/kimi-k2.5-free",
"model": "github-copilot/claude-sonnet-4.5",
},
"explore": {
"model": "opencode/claude-haiku-4-5",
@@ -1284,7 +1292,8 @@ exports[`generateModelConfig mixed provider scenarios uses all fallback provider
"variant": "medium",
},
"multimodal-looker": {
"model": "github-copilot/gemini-3-flash-preview",
"model": "opencode/gpt-5.3-codex",
"variant": "medium",
},
"oracle": {
"model": "github-copilot/gpt-5.2",
@@ -1337,7 +1346,7 @@ exports[`generateModelConfig mixed provider scenarios uses all providers togethe
"$schema": "https://raw.githubusercontent.com/code-yeongyu/oh-my-opencode/dev/assets/oh-my-opencode.schema.json",
"agents": {
"atlas": {
"model": "opencode/kimi-k2.5-free",
"model": "anthropic/claude-sonnet-4-5",
},
"explore": {
"model": "anthropic/claude-haiku-4-5",
@@ -1358,7 +1367,8 @@ exports[`generateModelConfig mixed provider scenarios uses all providers togethe
"variant": "medium",
},
"multimodal-looker": {
"model": "google/gemini-3-flash-preview",
"model": "openai/gpt-5.3-codex",
"variant": "medium",
},
"oracle": {
"model": "openai/gpt-5.2",
@@ -1411,7 +1421,7 @@ exports[`generateModelConfig mixed provider scenarios uses all providers with is
"$schema": "https://raw.githubusercontent.com/code-yeongyu/oh-my-opencode/dev/assets/oh-my-opencode.schema.json",
"agents": {
"atlas": {
"model": "opencode/kimi-k2.5-free",
"model": "anthropic/claude-sonnet-4-5",
},
"explore": {
"model": "anthropic/claude-haiku-4-5",
@@ -1432,7 +1442,8 @@ exports[`generateModelConfig mixed provider scenarios uses all providers with is
"variant": "medium",
},
"multimodal-looker": {
"model": "google/gemini-3-flash-preview",
"model": "openai/gpt-5.3-codex",
"variant": "medium",
},
"oracle": {
"model": "openai/gpt-5.2",

View File

@@ -323,8 +323,8 @@ describe("generateOmoConfig - model fallback system", () => {
expect((result.agents as Record<string, { model: string }>).sisyphus).toBeUndefined()
// #then Oracle should use native OpenAI (first fallback entry)
expect((result.agents as Record<string, { model: string }>).oracle.model).toBe("openai/gpt-5.2")
// #then multimodal-looker should use native OpenAI (fallback within native tier)
expect((result.agents as Record<string, { model: string }>)["multimodal-looker"].model).toBe("openai/gpt-5.2")
// #then multimodal-looker should use native OpenAI (first fallback entry is gpt-5.3-codex)
expect((result.agents as Record<string, { model: string }>)["multimodal-looker"].model).toBe("openai/gpt-5.3-codex")
})
test("uses haiku for explore when Claude max20", () => {

View File

@@ -9,7 +9,6 @@ export const CLI_AGENT_MODEL_REQUIREMENTS: Record<string, ModelRequirement> = {
fallbackChain: [
{ providers: ["anthropic", "github-copilot", "opencode"], model: "claude-opus-4-6", variant: "max" },
{ providers: ["kimi-for-coding"], model: "k2p5" },
{ providers: ["opencode"], model: "kimi-k2.5-free" },
{ providers: ["zai-coding-plan"], model: "glm-4.7" },
{ providers: ["opencode"], model: "glm-4.7-free" },
],
@@ -44,12 +43,10 @@ export const CLI_AGENT_MODEL_REQUIREMENTS: Record<string, ModelRequirement> = {
},
"multimodal-looker": {
fallbackChain: [
{ providers: ["google", "github-copilot", "opencode"], model: "gemini-3-flash" },
{ providers: ["openai", "github-copilot", "opencode"], model: "gpt-5.2" },
{ providers: ["zai-coding-plan"], model: "glm-4.6v" },
{ providers: ["openai", "opencode"], model: "gpt-5.3-codex", variant: "medium" },
{ providers: ["kimi-for-coding"], model: "k2p5" },
{ providers: ["opencode"], model: "kimi-k2.5-free" },
{ providers: ["anthropic", "github-copilot", "opencode"], model: "claude-haiku-4-5" },
{ providers: ["google", "github-copilot", "opencode"], model: "gemini-3-flash" },
{ providers: ["zai-coding-plan"], model: "glm-4.6v" },
{ providers: ["opencode"], model: "gpt-5-nano" },
],
},
@@ -57,7 +54,6 @@ export const CLI_AGENT_MODEL_REQUIREMENTS: Record<string, ModelRequirement> = {
fallbackChain: [
{ providers: ["anthropic", "github-copilot", "opencode"], model: "claude-opus-4-6", variant: "max" },
{ providers: ["kimi-for-coding"], model: "k2p5" },
{ providers: ["opencode"], model: "kimi-k2.5-free" },
{ providers: ["openai", "github-copilot", "opencode"], model: "gpt-5.2", variant: "high" },
{ providers: ["google", "github-copilot", "opencode"], model: "gemini-3.1-pro" },
],
@@ -66,7 +62,6 @@ export const CLI_AGENT_MODEL_REQUIREMENTS: Record<string, ModelRequirement> = {
fallbackChain: [
{ providers: ["anthropic", "github-copilot", "opencode"], model: "claude-opus-4-6", variant: "max" },
{ providers: ["kimi-for-coding"], model: "k2p5" },
{ providers: ["opencode"], model: "kimi-k2.5-free" },
{ providers: ["openai", "github-copilot", "opencode"], model: "gpt-5.2", variant: "high" },
{ providers: ["google", "github-copilot", "opencode"], model: "gemini-3.1-pro", variant: "high" },
],
@@ -81,7 +76,6 @@ export const CLI_AGENT_MODEL_REQUIREMENTS: Record<string, ModelRequirement> = {
atlas: {
fallbackChain: [
{ providers: ["kimi-for-coding"], model: "k2p5" },
{ providers: ["opencode"], model: "kimi-k2.5-free" },
{ providers: ["anthropic", "github-copilot", "opencode"], model: "claude-sonnet-4-5" },
{ providers: ["openai", "github-copilot", "opencode"], model: "gpt-5.2" },
{ providers: ["google", "github-copilot", "opencode"], model: "gemini-3.1-pro" },

View File

@@ -134,8 +134,8 @@ describe("model fallback hook", () => {
//#then - chain should progress to entry[1], not repeat entry[0]
expect(secondOutput.message["model"]).toEqual({
providerID: "opencode",
modelID: "kimi-k2.5-free",
providerID: "zai-coding-plan",
modelID: "glm-5",
})
expect(secondOutput.message["variant"]).toBeUndefined()
})

View File

@@ -334,8 +334,8 @@ describe("createEventHandler - model fallback", () => {
//#then - second fallback entry applied (chain advanced)
expect(second.message["model"]).toEqual({
providerID: "opencode",
modelID: "kimi-k2.5-free",
providerID: "zai-coding-plan",
modelID: "glm-5",
})
expect(second.message["variant"]).toBeUndefined()
expect(abortCalls).toEqual([sessionID, sessionID])

View File

@@ -1,5 +1,5 @@
export function createSystemTransformHandler(): (
input: { sessionID: string },
input: { sessionID?: string; model: { id: string; providerID: string; [key: string]: unknown } },
output: { system: string[] },
) => Promise<void> {
return async (): Promise<void> => {}

View File

@@ -31,7 +31,7 @@ describe("AGENT_MODEL_REQUIREMENTS", () => {
// #then - fallbackChain has claude-opus-4-6 first, big-pickle last
expect(sisyphus).toBeDefined()
expect(sisyphus.fallbackChain).toBeArray()
expect(sisyphus.fallbackChain).toHaveLength(4)
expect(sisyphus.fallbackChain).toHaveLength(3)
expect(sisyphus.requiresAnyModel).toBe(true)
const primary = sisyphus.fallbackChain[0]
@@ -39,7 +39,7 @@ describe("AGENT_MODEL_REQUIREMENTS", () => {
expect(primary.model).toBe("claude-opus-4-6")
expect(primary.variant).toBe("max")
const last = sisyphus.fallbackChain[3]
const last = sisyphus.fallbackChain[2]
expect(last.providers[0]).toBe("opencode")
expect(last.model).toBe("big-pickle")
})
@@ -86,19 +86,27 @@ describe("AGENT_MODEL_REQUIREMENTS", () => {
expect(quaternary.model).toBe("gpt-5-nano")
})
test("multimodal-looker has valid fallbackChain with kimi-k2.5-free as primary", () => {
test("multimodal-looker has valid fallbackChain with gpt-5.3-codex as primary", () => {
// given - multimodal-looker agent requirement
const multimodalLooker = AGENT_MODEL_REQUIREMENTS["multimodal-looker"]
// when - accessing multimodal-looker requirement
// then - fallbackChain exists with kimi-k2.5-free first, gpt-5-nano last
// then - fallbackChain: gpt-5.3-codex -> k2p5 -> gemini-3-flash -> glm-4.6v -> gpt-5-nano
expect(multimodalLooker).toBeDefined()
expect(multimodalLooker.fallbackChain).toBeArray()
expect(multimodalLooker.fallbackChain).toHaveLength(5)
const primary = multimodalLooker.fallbackChain[0]
expect(primary.providers[0]).toBe("opencode")
expect(primary.model).toBe("kimi-k2.5-free")
expect(primary.providers).toEqual(["openai", "opencode"])
expect(primary.model).toBe("gpt-5.3-codex")
expect(primary.variant).toBe("medium")
const secondary = multimodalLooker.fallbackChain[1]
expect(secondary.providers).toEqual(["kimi-for-coding"])
expect(secondary.model).toBe("k2p5")
const tertiary = multimodalLooker.fallbackChain[2]
expect(tertiary.model).toBe("gemini-3-flash")
const last = multimodalLooker.fallbackChain[4]
expect(last.providers).toEqual(["openai", "github-copilot", "opencode"])
@@ -153,19 +161,19 @@ describe("AGENT_MODEL_REQUIREMENTS", () => {
expect(primary.providers[0]).toBe("openai")
})
test("atlas has valid fallbackChain with kimi-k2.5-free as primary", () => {
test("atlas has valid fallbackChain with claude-sonnet-4-6 as primary", () => {
// given - atlas agent requirement
const atlas = AGENT_MODEL_REQUIREMENTS["atlas"]
// when - accessing Atlas requirement
// then - fallbackChain exists with kimi-k2.5-free as first entry
// then - fallbackChain exists with claude-sonnet-4-6 as first entry
expect(atlas).toBeDefined()
expect(atlas.fallbackChain).toBeArray()
expect(atlas.fallbackChain.length).toBeGreaterThan(0)
const primary = atlas.fallbackChain[0]
expect(primary.model).toBe("kimi-k2.5-free")
expect(primary.providers[0]).toBe("opencode")
expect(primary.model).toBe("claude-sonnet-4-6")
expect(primary.providers[0]).toBe("anthropic")
})
test("hephaestus supports openai, github-copilot, venice, and opencode providers", () => {
@@ -335,27 +343,23 @@ describe("CATEGORY_MODEL_REQUIREMENTS", () => {
expect(primary.providers[0]).toBe("google")
})
test("writing has valid fallbackChain with kimi-k2.5-free as primary", () => {
test("writing has valid fallbackChain with gemini-3-flash as primary", () => {
// given - writing category requirement
const writing = CATEGORY_MODEL_REQUIREMENTS["writing"]
// when - accessing writing requirement
// then - fallbackChain: kimi-k2.5-free -> gemini-3-flash -> claude-sonnet-4-6
// then - fallbackChain: gemini-3-flash -> claude-sonnet-4-6
expect(writing).toBeDefined()
expect(writing.fallbackChain).toBeArray()
expect(writing.fallbackChain).toHaveLength(3)
expect(writing.fallbackChain).toHaveLength(2)
const primary = writing.fallbackChain[0]
expect(primary.model).toBe("kimi-k2.5-free")
expect(primary.providers[0]).toBe("opencode")
expect(primary.model).toBe("gemini-3-flash")
expect(primary.providers[0]).toBe("google")
const second = writing.fallbackChain[1]
expect(second.model).toBe("gemini-3-flash")
expect(second.providers[0]).toBe("google")
const third = writing.fallbackChain[2]
expect(third.model).toBe("claude-sonnet-4-6")
expect(third.providers[0]).toBe("anthropic")
expect(second.model).toBe("claude-sonnet-4-6")
expect(second.providers[0]).toBe("anthropic")
})
test("all 8 categories have valid fallbackChain arrays", () => {

View File

@@ -16,7 +16,6 @@ export const AGENT_MODEL_REQUIREMENTS: Record<string, ModelRequirement> = {
sisyphus: {
fallbackChain: [
{ providers: ["anthropic", "github-copilot", "opencode"], model: "claude-opus-4-6", variant: "max" },
{ providers: ["opencode"], model: "kimi-k2.5-free" },
{ providers: ["zai-coding-plan", "opencode"], model: "glm-5" },
{ providers: ["opencode"], model: "big-pickle" },
],
@@ -53,9 +52,9 @@ export const AGENT_MODEL_REQUIREMENTS: Record<string, ModelRequirement> = {
},
"multimodal-looker": {
fallbackChain: [
{ providers: ["opencode"], model: "kimi-k2.5-free" },
{ providers: ["openai", "opencode"], model: "gpt-5.3-codex", variant: "medium" },
{ providers: ["kimi-for-coding"], model: "k2p5" },
{ providers: ["google", "github-copilot", "opencode"], model: "gemini-3-flash" },
{ providers: ["openai", "github-copilot", "opencode"], model: "gpt-5.2" },
{ providers: ["zai-coding-plan"], model: "glm-4.6v" },
{ providers: ["openai", "github-copilot", "opencode"], model: "gpt-5-nano" },
],
@@ -64,14 +63,12 @@ export const AGENT_MODEL_REQUIREMENTS: Record<string, ModelRequirement> = {
fallbackChain: [
{ providers: ["anthropic", "github-copilot", "opencode"], model: "claude-opus-4-6", variant: "max" },
{ providers: ["openai", "github-copilot", "opencode"], model: "gpt-5.2", variant: "high" },
{ providers: ["opencode"], model: "kimi-k2.5-free" },
{ providers: ["google", "github-copilot", "opencode"], model: "gemini-3.1-pro" },
],
},
metis: {
fallbackChain: [
{ providers: ["anthropic", "github-copilot", "opencode"], model: "claude-opus-4-6", variant: "max" },
{ providers: ["opencode"], model: "kimi-k2.5-free" },
{ providers: ["openai", "github-copilot", "opencode"], model: "gpt-5.2", variant: "high" },
{ providers: ["google", "github-copilot", "opencode"], model: "gemini-3.1-pro", variant: "high" },
],
@@ -85,7 +82,6 @@ export const AGENT_MODEL_REQUIREMENTS: Record<string, ModelRequirement> = {
},
atlas: {
fallbackChain: [
{ providers: ["opencode"], model: "kimi-k2.5-free" },
{ providers: ["anthropic", "github-copilot", "opencode"], model: "claude-sonnet-4-6" },
{ providers: ["openai", "github-copilot", "opencode"], model: "gpt-5.2" },
],
@@ -146,7 +142,6 @@ export const CATEGORY_MODEL_REQUIREMENTS: Record<string, ModelRequirement> = {
},
writing: {
fallbackChain: [
{ providers: ["opencode"], model: "kimi-k2.5-free" },
{ providers: ["google", "github-copilot", "opencode"], model: "gemini-3-flash" },
{ providers: ["anthropic", "github-copilot", "opencode"], model: "claude-sonnet-4-6" },
],

View File

@@ -2,6 +2,6 @@ export const BACKGROUND_TASK_DESCRIPTION = `Run agent task in background. Return
Use \`background_output\` to get results. Prompts MUST be in English.`
export const BACKGROUND_OUTPUT_DESCRIPTION = `Get output from background task. Use full_session=true to fetch session messages with filters. System notifies on completion, so block=true rarely needed.`
export const BACKGROUND_OUTPUT_DESCRIPTION = `Get output from background task. Use full_session=true to fetch session messages with filters. System notifies on completion, so block=true rarely needed. - Timeout values are in milliseconds (ms), NOT seconds.`
export const BACKGROUND_CANCEL_DESCRIPTION = `Cancel running background task(s). Use all=true to cancel ALL before final answer.`

View File

@@ -87,7 +87,6 @@ export function createBackgroundOutput(manager: BackgroundOutputManager, client:
const shouldBlock = args.block === true
const timeoutMs = Math.min(args.timeout ?? 60000, 600000)
const fullSession = args.full_session ?? true
let resolvedTask = task
@@ -123,6 +122,10 @@ export function createBackgroundOutput(manager: BackgroundOutputManager, client:
}
const isActive = isTaskActiveStatus(resolvedTask.status)
const fullSessionProvided = args.full_session !== undefined
const fullSession = fullSessionProvided
? (args.full_session ?? true)
: !isActive
const includeThinking = isActive || (args.include_thinking ?? false)
const includeToolResults = isActive || (args.include_tool_results ?? false)

View File

@@ -33,6 +33,7 @@ export async function executeBackgroundContinuation(
run_in_background: args.run_in_background,
sessionId: task.sessionID,
command: args.command,
model: task.model ? { providerID: task.model.providerID, modelID: task.model.modelID } : undefined,
},
}
await ctx.metadata?.(bgContMeta)

View File

@@ -0,0 +1,158 @@
const bunTest = require("bun:test")
const describeFn = bunTest.describe
const testFn = bunTest.test
const expectFn = bunTest.expect
const beforeEachFn = bunTest.beforeEach
const afterEachFn = bunTest.afterEach
const { executeBackgroundTask } = require("./background-task")
const { __setTimingConfig, __resetTimingConfig } = require("./timing")
describeFn("executeBackgroundTask output/session metadata compatibility", () => {
beforeEachFn(() => {
//#given - reduce waiting to keep tests fast
__setTimingConfig({
WAIT_FOR_SESSION_INTERVAL_MS: 1,
WAIT_FOR_SESSION_TIMEOUT_MS: 2,
})
})
afterEachFn(() => {
__resetTimingConfig()
})
testFn("does not emit synthetic pending session metadata when session id is unresolved", async () => {
//#given - launched task without resolved subagent session id
const metadataCalls: any[] = []
const manager = {
launch: async () => ({
id: "bg_unresolved",
sessionID: undefined,
description: "Unresolved session",
agent: "explore",
status: "running",
}),
getTask: () => undefined,
}
const result = await executeBackgroundTask(
{
description: "Unresolved session",
prompt: "check",
run_in_background: true,
load_skills: [],
},
{
sessionID: "ses_parent",
callID: "call_1",
metadata: async (value: any) => metadataCalls.push(value),
abort: new AbortController().signal,
},
{ manager },
{ sessionID: "ses_parent", messageID: "msg_1" },
"explore",
undefined,
undefined,
undefined,
)
//#then - output and metadata should avoid fake session markers
expectFn(result).not.toContain("<task_metadata>")
expectFn(result).not.toContain("session_id: undefined")
expectFn(result).not.toContain("session_id: pending")
expectFn(metadataCalls).toHaveLength(1)
expectFn("sessionId" in metadataCalls[0].metadata).toBe(false)
})
testFn("emits task metadata session_id when real session id is available", async () => {
//#given - launched task with resolved subagent session id
const metadataCalls: any[] = []
const manager = {
launch: async () => ({
id: "bg_resolved",
sessionID: "ses_sub_123",
description: "Resolved session",
agent: "explore",
status: "running",
}),
getTask: () => ({ sessionID: "ses_sub_123" }),
}
const result = await executeBackgroundTask(
{
description: "Resolved session",
prompt: "check",
run_in_background: true,
load_skills: [],
},
{
sessionID: "ses_parent",
callID: "call_2",
metadata: async (value: any) => metadataCalls.push(value),
abort: new AbortController().signal,
},
{ manager },
{ sessionID: "ses_parent", messageID: "msg_2" },
"explore",
undefined,
undefined,
undefined,
)
//#then - output and metadata should include canonical session linkage
expectFn(result).toContain("<task_metadata>")
expectFn(result).toContain("session_id: ses_sub_123")
expectFn(result).toContain("task_id: ses_sub_123")
expectFn(result).toContain("background_task_id: bg_resolved")
expectFn(result).toContain("Background Task ID: bg_resolved")
expectFn(metadataCalls).toHaveLength(1)
expectFn(metadataCalls[0].metadata.sessionId).toBe("ses_sub_123")
})
testFn("captures late-resolved session id and emits synced metadata", async () => {
//#given - background task session id appears after launch via manager polling
const metadataCalls: any[] = []
let reads = 0
const manager = {
launch: async () => ({
id: "bg_late",
sessionID: undefined,
description: "Late session",
agent: "explore",
status: "running",
}),
getTask: () => {
reads += 1
return reads >= 2 ? { sessionID: "ses_late_123" } : undefined
},
}
const result = await executeBackgroundTask(
{
description: "Late session",
prompt: "check",
run_in_background: true,
load_skills: [],
},
{
sessionID: "ses_parent",
callID: "call_3",
metadata: async (value: any) => metadataCalls.push(value),
abort: new AbortController().signal,
},
{ manager },
{ sessionID: "ses_parent", messageID: "msg_3" },
"explore",
undefined,
undefined,
undefined,
)
//#then - late session id still propagates to task metadata contract
expectFn(result).toContain("session_id: ses_late_123")
expectFn(result).toContain("task_id: ses_late_123")
expectFn(result).toContain("background_task_id: bg_late")
expectFn(metadataCalls).toHaveLength(1)
expectFn(metadataCalls[0].metadata.sessionId).toBe("ses_late_123")
})
})

View File

@@ -56,36 +56,39 @@ export async function executeBackgroundTask(
SessionCategoryRegistry.register(sessionId, args.category)
}
const metadata = {
prompt: args.prompt,
agent: task.agent,
category: args.category,
load_skills: args.load_skills,
description: args.description,
run_in_background: args.run_in_background,
command: args.command,
...(sessionId ? { sessionId } : {}),
...(categoryModel ? { model: { providerID: categoryModel.providerID, modelID: categoryModel.modelID } } : {}),
}
const unstableMeta = {
title: args.description,
metadata: {
prompt: args.prompt,
agent: task.agent,
category: args.category,
load_skills: args.load_skills,
description: args.description,
run_in_background: args.run_in_background,
sessionId: sessionId ?? "pending",
command: args.command,
},
metadata,
}
await ctx.metadata?.(unstableMeta)
if (ctx.callID) {
storeToolMetadata(ctx.sessionID, ctx.callID, unstableMeta)
}
const taskMetadataBlock = sessionId
? `\n\n<task_metadata>\nsession_id: ${sessionId}\ntask_id: ${sessionId}\nbackground_task_id: ${task.id}\n</task_metadata>`
: ""
return `Background task launched.
Task ID: ${task.id}
Background Task ID: ${task.id}
Description: ${task.description}
Agent: ${task.agent}${args.category ? ` (category: ${args.category})` : ""}
Status: ${task.status}
System notifies on completion. Use \`background_output\` with task_id="${task.id}" to check.
<task_metadata>
session_id: ${sessionId}
</task_metadata>`
System notifies on completion. Use \`background_output\` with task_id="${task.id}" to check.${taskMetadataBlock}`
} catch (error) {
return formatDetailedError(error, {
operation: "Launch background task",

View File

@@ -0,0 +1,172 @@
const { describe, test, expect, mock } = require("bun:test")
import type { DelegateTaskArgs, ToolContextWithMetadata } from "./types"
import type { ParentContext } from "./executor-types"
const MODEL = { providerID: "anthropic", modelID: "claude-sonnet-4-6" }
function makeMockCtx(): ToolContextWithMetadata & { captured: any[] } {
const captured: any[] = []
return {
sessionID: "ses_parent",
messageID: "msg_parent",
agent: "sisyphus",
abort: new AbortController().signal,
callID: "call_001",
metadata: async (input: any) => { captured.push(input) },
captured,
}
}
const parentContext: ParentContext = {
sessionID: "ses_parent",
messageID: "msg_parent",
agent: "sisyphus",
model: MODEL,
}
describe("metadata model unification", () => {
describe("#given delegate-task executors", () => {
describe("#when metadata is set during execution", () => {
test("#then sync-task metadata includes model", async () => {
const { executeSyncTask } = require("./sync-task")
const ctx = makeMockCtx()
const deps = {
createSyncSession: async () => ({ ok: true, sessionID: "ses_sync" }),
sendSyncPrompt: async () => null,
pollSyncSession: async () => null,
fetchSyncResult: async () => ({ ok: true as const, textContent: "done" }),
}
const args: DelegateTaskArgs = {
description: "test", prompt: "do it",
category: "quick", load_skills: [], run_in_background: false,
}
await executeSyncTask(args, ctx, {
client: { session: { create: async () => ({ data: { id: "ses_sync" } }) } },
directory: "/tmp",
onSyncSessionCreated: null,
}, parentContext, "explore", MODEL, undefined, undefined, undefined, deps)
const meta = ctx.captured.find((m: any) => m.metadata?.sessionId)
expect(meta).toBeDefined()
expect(meta.metadata.model).toEqual(MODEL)
})
test("#then background-task metadata includes model", async () => {
const { executeBackgroundTask } = require("./background-task")
const ctx = makeMockCtx()
const args: DelegateTaskArgs = {
description: "test", prompt: "do it",
load_skills: [], run_in_background: true, subagent_type: "explore",
}
await executeBackgroundTask(args, ctx, {
manager: {
launch: async () => ({
id: "bg_1", description: "test", agent: "explore",
status: "pending", sessionID: "ses_bg", model: MODEL,
}),
getTask: () => undefined,
},
} as any, parentContext, "explore", MODEL, undefined)
const meta = ctx.captured.find((m: any) => m.metadata?.sessionId)
expect(meta).toBeDefined()
expect(meta.metadata.model).toEqual(MODEL)
})
test("#then unstable-agent-task metadata includes model", async () => {
const { executeUnstableAgentTask } = require("./unstable-agent-task")
const ctx = makeMockCtx()
const args: DelegateTaskArgs = {
description: "test", prompt: "do it",
category: "quick", load_skills: [], run_in_background: false,
}
const launchedTask = {
id: "bg_unstable", description: "test", agent: "explore",
status: "completed", sessionID: "ses_unstable", model: MODEL,
}
const result = await executeUnstableAgentTask(
args, ctx,
{
manager: {
launch: async () => launchedTask,
getTask: () => launchedTask,
},
client: {
session: {
status: async () => ({ data: { ses_unstable: { type: "idle" } } }),
messages: async () => ({
data: [{
info: { role: "assistant", time: { created: 1 } },
parts: [{ type: "text", text: "done" }],
}],
}),
},
},
syncPollTimeoutMs: 100,
} as any,
parentContext, "explore", MODEL, undefined, "anthropic/claude-sonnet-4-6",
)
const meta = ctx.captured.find((m: any) => m.metadata?.sessionId)
expect(meta).toBeDefined()
expect(meta.metadata.model).toEqual(MODEL)
})
test("#then background-continuation metadata includes model from task", async () => {
const { executeBackgroundContinuation } = require("./background-continuation")
const ctx = makeMockCtx()
const args: DelegateTaskArgs = {
description: "continue", prompt: "keep going",
load_skills: [], run_in_background: true, session_id: "ses_resumed",
}
await executeBackgroundContinuation(args, ctx, {
manager: {
resume: async () => ({
id: "bg_2", description: "continue", agent: "explore",
status: "running", sessionID: "ses_resumed", model: MODEL,
}),
},
} as any, parentContext)
const meta = ctx.captured.find((m: any) => m.metadata?.sessionId)
expect(meta).toBeDefined()
expect(meta.metadata.model).toEqual(MODEL)
})
test("#then sync-continuation metadata includes model from resumed session", async () => {
const { executeSyncContinuation } = require("./sync-continuation")
const ctx = makeMockCtx()
const args: DelegateTaskArgs = {
description: "continue", prompt: "keep going",
load_skills: [], run_in_background: false, session_id: "ses_cont",
}
const deps = {
pollSyncSession: async () => null,
fetchSyncResult: async () => ({ ok: true as const, textContent: "done" }),
}
await executeSyncContinuation(args, ctx, {
client: {
session: {
messages: async () => ({
data: [{ info: { agent: "explore", model: MODEL, providerID: "anthropic", modelID: "claude-sonnet-4-6" } }],
}),
prompt: async () => ({}),
},
},
} as any, deps)
const meta = ctx.captured.find((m: any) => m.metadata?.sessionId)
expect(meta).toBeDefined()
expect(meta.metadata.model).toEqual(MODEL)
})
})
})
})

View File

@@ -32,22 +32,7 @@ export async function executeSyncContinuation(
})
}
const syncContMeta = {
title: `Continue: ${args.description}`,
metadata: {
prompt: args.prompt,
load_skills: args.load_skills,
description: args.description,
run_in_background: args.run_in_background,
sessionId: args.session_id,
sync: true,
command: args.command,
},
}
await ctx.metadata?.(syncContMeta)
if (ctx.callID) {
storeToolMetadata(ctx.sessionID, ctx.callID, syncContMeta)
}
let syncContMeta: { title: string; metadata: Record<string, unknown> } | undefined
let resumeAgent: string | undefined
let resumeModel: { providerID: string; modelID: string } | undefined
@@ -78,6 +63,24 @@ export async function executeSyncContinuation(
resumeVariant = resumeMessage?.model?.variant
}
syncContMeta = {
title: `Continue: ${args.description}`,
metadata: {
prompt: args.prompt,
load_skills: args.load_skills,
description: args.description,
run_in_background: args.run_in_background,
sessionId: args.session_id,
sync: true,
command: args.command,
model: resumeModel,
},
}
await ctx.metadata?.(syncContMeta)
if (ctx.callID) {
storeToolMetadata(ctx.sessionID, ctx.callID, syncContMeta)
}
const allowTask = isPlanFamily(resumeAgent)
const tools = {
...(resumeAgent ? getAgentToolRestrictions(resumeAgent) : {}),

View File

@@ -91,6 +91,7 @@ export async function executeSyncTask(
sessionId: sessionID,
sync: true,
command: args.command,
model: categoryModel ? { providerID: categoryModel.providerID, modelID: categoryModel.modelID } : undefined,
},
}
await ctx.metadata?.(syncTaskMeta)

View File

@@ -66,6 +66,7 @@ export async function executeUnstableAgentTask(
run_in_background: args.run_in_background,
sessionId: sessionID,
command: args.command,
model: categoryModel ? { providerID: categoryModel.providerID, modelID: categoryModel.modelID } : undefined,
},
}
await ctx.metadata?.(bgTaskMeta)

View File

@@ -456,6 +456,96 @@ describe("look-at tool", () => {
})
})
describe("createLookAt unhandled error resilience", () => {
const createToolContext = (): ToolContext => ({
sessionID: "parent-session",
messageID: "parent-message",
agent: "sisyphus",
directory: "/project",
worktree: "/project",
abort: new AbortController().signal,
metadata: () => {},
ask: async () => {},
})
// given session.create throws (network error, not error response)
// when LookAt tool executed
// then returns error string instead of crashing
test("catches session.create throw and returns error string", async () => {
const mockClient = {
session: {
get: async () => ({ data: { directory: "/project" } }),
create: async () => { throw new Error("ECONNREFUSED: connection refused") },
},
}
const tool = createLookAt({
client: mockClient,
directory: "/project",
} as any)
const result = await tool.execute(
{ file_path: "/test/file.png", goal: "analyze" },
createToolContext(),
)
expect(result).toContain("Error")
expect(result).toContain("ECONNREFUSED")
})
// given session.messages throws unexpectedly
// when LookAt tool executed
// then returns error string instead of crashing
test("catches session.messages throw and returns error string", async () => {
const mockClient = {
app: {
agents: async () => ({ data: [] }),
},
session: {
get: async () => ({ data: { directory: "/project" } }),
create: async () => ({ data: { id: "ses_msg_throw" } }),
prompt: async () => ({}),
messages: async () => { throw new Error("Unexpected server error") },
},
}
const tool = createLookAt({
client: mockClient,
directory: "/project",
} as any)
const result = await tool.execute(
{ file_path: "/test/file.png", goal: "analyze" },
createToolContext(),
)
expect(result).toContain("Error")
expect(result).toContain("Unexpected server error")
})
// given a non-Error object is thrown
// when LookAt tool executed
// then still returns error string
test("handles non-Error thrown objects gracefully", async () => {
const mockClient = {
session: {
get: async () => ({ data: { directory: "/project" } }),
create: async () => { throw "string error thrown" },
},
}
const tool = createLookAt({
client: mockClient,
directory: "/project",
} as any)
const result = await tool.execute(
{ file_path: "/test/file.png", goal: "analyze" },
createToolContext(),
)
expect(result).toContain("Error")
expect(result).toContain("string error thrown")
})
})
describe("createLookAt with image_data", () => {
// given base64 image data is provided
// when LookAt tool executed

View File

@@ -217,6 +217,10 @@ Original error: ${createResult.error}`
log(`[look_at] Got response, length: ${responseText.length}`)
return responseText
} catch (error) {
const errorMessage = error instanceof Error ? error.message : String(error)
log(`[look_at] Unexpected error analyzing ${sourceDescription}:`, error)
return `Error: Failed to analyze ${sourceDescription}: ${errorMessage}`
} finally {
if (tempConversionPath) {
cleanupConvertedImage(tempConversionPath)