Compare commits

...

374 Commits
v3.12.1 ... dev

Author SHA1 Message Date
github-actions[bot]
3dc11ea620 @HOYALIM has signed the CLA in code-yeongyu/oh-my-openagent#2935 2026-03-29 07:31:49 +00:00
github-actions[bot]
5a28ee1bef @quangtran88 has signed the CLA in code-yeongyu/oh-my-openagent#2929 2026-03-29 03:21:54 +00:00
github-actions[bot]
5d4e57ce96 @lorenzo-dallamuta has signed the CLA in code-yeongyu/oh-my-openagent#2925 2026-03-28 21:43:40 +00:00
YeonGyu-Kim
b2497f1327 fix: resolve 3 community-reported bugs (#2915, #2917, #2918)
- background_output: snapshot read cursor before consuming, restore on
  /undo message removal so re-reads return data (fixes #2915)
- MCP loader: preserve oauth field in transformMcpServer, add scope/
  projectPath filtering so local-scoped MCPs only load in matching
  directories (fixes #2917)
- runtime-fallback: add 'reached your usage limit' to retryable error
  patterns so quota exhaustion triggers model fallback (fixes #2918)

Verified: bun test (4606 pass / 0 fail), tsc --noEmit clean
2026-03-29 04:53:43 +09:00
github-actions[bot]
9fc56ab544 @ryandielhenn has signed the CLA in code-yeongyu/oh-my-openagent#2919 2026-03-28 17:47:04 +00:00
github-actions[bot]
448a8dc93d @AlexDochioiu has signed the CLA in code-yeongyu/oh-my-openagent#2916 2026-03-28 12:20:53 +00:00
YeonGyu-Kim
9cbcf17dde Merge PR #2913: fix(recovery): ignore empty summary-only assistant messages 2026-03-28 17:15:04 +09:00
Ravi Tharuma
4e214cba4e fix(recovery): ignore empty summary-only assistant messages 2026-03-28 08:57:36 +01:00
YeonGyu-Kim
4a029258a4 fix: resolve 5 remaining pre-publish blockers (14, 15, 17, 21, 25c)
- completion-promise-detector: restrict to assistant text parts only,
  remove tool_result from completion detection (blocker 14)
- ralph-loop tests: flip tool_result completion expectations to negative
  coverage, add false-positive rejection tests (blocker 15)
- skill tools: merge nativeSkills into initial cachedDescription
  synchronously before any execute() call (blocker 17)
- skill tools test: add assertion for initial description including
  native skills before execute() (blocker 25c)
- docs: sync all 4 fallback-chain docs with model-requirements.ts
  runtime source of truth (blocker 21)

Verified: bun test (4599 pass / 0 fail), tsc --noEmit clean
2026-03-28 15:57:27 +09:00
YeonGyu-Kim
d2c576c510 fix: resolve 25 pre-publish blockers
- postinstall.mjs: fix alias package detection
- migrate-legacy-plugin-entry: dedupe + regression tests
- task_system: default consistency across runtime paths
- task() contract: consistent tool behavior
- runtime model selection, tool cap, stale-task cancellation
- recovery sanitization, context-limit gating
- Ralph semantic DONE hardening, Atlas fallback persistence
- native-skill description/content, skill path traversal guard
- publish workflow: platform awaited via reusable workflow job
- release: version edits reapplied before commit/tag
- JSONC plugin migration: top-level plugin key safety
- cold-cache: user fallback models skip disconnected providers
- docs/version/release framing updates

Verified: bun test (4599 pass), tsc --noEmit clean, bun run build clean
2026-03-28 15:24:18 +09:00
YeonGyu-Kim
44b039bef6 Merge pull request #2705 from MoerAI/fix/sisyphus-premature-implementation
fix(sisyphus): block premature implementation before context is complete (fixes #2274)
2026-03-28 01:46:31 +09:00
YeonGyu-Kim
aeec5ef98d Merge pull request #2773 from MoerAI/fix/ralph-loop-fuzzy-completion
fix(ralph-loop): add semantic completion detection as fallback for natural language (fixes #2489)
2026-03-28 01:44:56 +09:00
YeonGyu-Kim
7b2b8be181 Merge pull request #2771 from MoerAI/fix/bash-file-read-guard
fix(hooks): add bash-file-read-guard to warn agents against cat/head/tail (fixes #2096)
2026-03-28 01:44:44 +09:00
YeonGyu-Kim
adc55138c8 Merge pull request #2850 from octo-patch/feature/upgrade-minimax-m2.7
feat: upgrade remaining MiniMax M2.5 fallbacks to M2.7-highspeed
2026-03-28 01:42:25 +09:00
YeonGyu-Kim
3715fb79b9 Merge pull request #2871 from Jholly2008/kkk/fix-on-complete-hook-shell
fix(cli): respect platform shell for --on-complete
2026-03-28 01:42:13 +09:00
YeonGyu-Kim
b9ed0ca30b Merge pull request #2877 from WhiteGiverMa/fix/atlas-agent-not-found
fix: use getAgentDisplayName in injectBoulderContinuation
2026-03-28 01:40:38 +09:00
YeonGyu-Kim
45e9fcd776 Merge pull request #2894 from codivedev/fix/issue-2881
fix: detect and warn about opencode-skills conflict
2026-03-28 01:40:04 +09:00
YeonGyu-Kim
49687a654a Merge pull request #2895 from MoerAI/fix/session-recovery-missing-messageid
fix(session-recovery): fallback to fetching messageID from session messages (fixes #2046)
2026-03-28 01:39:44 +09:00
YeonGyu-Kim
364550038a Merge pull request #2892 from MoerAI/fix/subagent-model-config-ignored
fix(delegate-task): honor user model override in category-resolver cold cache (fixes #2712)
2026-03-28 01:39:33 +09:00
YeonGyu-Kim
9e6f2d9977 Merge pull request #2893 from MoerAI/fix/keyword-detector-silent-skips
fix(keyword-detector): add logging for silent skip paths (fixes #2058)
2026-03-28 01:39:20 +09:00
YeonGyu-Kim
4fa7d48c04 Merge pull request #2890 from MoerAI/fix/start-work-atlas-not-found
fix(start-work): gracefully handle missing Atlas agent (fixes #2132)
2026-03-28 01:38:53 +09:00
YeonGyu-Kim
b4a5189a07 Merge pull request #2889 from MoerAI/fix/git-master-config-ignored
fix(config): apply git_master defaults when section is missing (fixes #2040)
2026-03-28 01:38:38 +09:00
YeonGyu-Kim
631092461c Merge pull request #2891 from codivedev/fix/issue-2882
fix: use display name in runtime-fallback retry
2026-03-28 01:38:14 +09:00
YeonGyu-Kim
c5068d37d2 fix(#2885): add model_not_supported to RETRYABLE error patterns
model_not_supported errors from providers (e.g. OpenAI returning
{"error": {"code": "model_not_supported"}}) were not recognized as
retryable. Subagents would silently fail with no response, hanging the
parent session.

Fix:
- Add "model_not_supported", "model not supported", "model is not
  supported" to RETRYABLE_MESSAGE_PATTERNS in model-error-classifier.ts
- Add regex patterns to RETRYABLE_ERROR_PATTERNS in
  runtime-fallback/constants.ts to match "model ... is ... not ...
  supported" with flexible spacing
- Add regression test covering all three variations

Now model_not_supported errors trigger the normal fallback chain instead
of silent failure.
2026-03-28 00:42:52 +09:00
YeonGyu-Kim
1434313bd7 Merge pull request #2717 from gtg7784/fix/analyze-mode-load-skills-hint
Verified: tsc clean, 27/27 keyword-detector tests pass. Fix is correct — analyze-mode message was missing delegate_task required params which caused load_skills validation errors.
2026-03-28 00:29:53 +09:00
codivedev
38347a396e fix: address review comments - remove duplicates and respect skills config 2026-03-27 13:49:58 +01:00
codivedev
885d3a2462 fix: detect and warn about opencode-skills conflict 2026-03-27 13:40:00 +01:00
codivedev
b4d4d30fa8 fix: use display name in runtime-fallback retry 2026-03-27 13:39:50 +01:00
github-actions[bot]
9d9365901b @codivedev has signed the CLA in code-yeongyu/oh-my-openagent#2888 2026-03-27 12:39:50 +00:00
MoerAI
2b2b280895 fix: apply Zod defaults to empty config fallback 2026-03-27 21:30:56 +09:00
MoerAI
fee60d2def fix(session-recovery): fallback to fetching messageID from session messages (fixes #2046) 2026-03-27 21:26:09 +09:00
MoerAI
f030e0d78d fix(keyword-detector): add logging for silent skip paths (fixes #2058) 2026-03-27 21:24:03 +09:00
MoerAI
5d5eb46f19 fix(delegate-task): honor user model override in category-resolver cold cache (fixes #2712) 2026-03-27 21:21:17 +09:00
User
787ce99eda fix: detect and warn about opencode-skills conflict
When opencode-skills plugin is registered alongside oh-my-openagent,
all user skills are loaded twice, causing 'Duplicate tool names detected'
warnings and HTTP 400 errors.

This fix:
1. Detects if opencode-skills plugin is loaded in opencode.json
2. Emits a startup warning explaining the conflict
3. Suggests fixes: either remove opencode-skills or disable skills in oh-my-openagent

Fixes #2881
2026-03-27 13:21:08 +01:00
MoerAI
d09af86ea7 fix(start-work): gracefully handle missing Atlas agent (fixes #2132) 2026-03-27 21:13:44 +09:00
MoerAI
5b9b6eb0b8 fix(config): apply git_master defaults when section is missing (fixes #2040) 2026-03-27 21:00:01 +09:00
YeonGyu-Kim
324dbb119c fix(#2791): await session.abort() in all subagent completion/cancel paths
Fire-and-forget session.abort() calls during subagent completion left
dangling promises that raced with parent session teardown. In Bun on
WSL2/Linux, this triggered a StringImplShape assertion (SIGABRT) as
WebKit GC collected string data still referenced by the inflight request.

Fix: await session.abort() in all four completion/error paths:
- startTask promptAsync error handler (launch path)
- resume promptAsync error handler (resume path)
- cancelTask (explicit cancel path)
- tryCompleteTask (normal completion path)

Also marks the two .catch() error callbacks as async so the await is
valid.

Test: update session.deleted cascade test to flush two microtask rounds
since cancelTask now awaits abort before cleanupPendingByParent.
2026-03-27 19:57:57 +09:00
YeonGyu-Kim
ab0b084199 Merge pull request #2884 from RaviTharuma/fix/runtime-fallback-hook-isolation
Verified: bun test src/plugin/event.test.ts src/hooks/runtime-fallback/index.test.ts -- 68/68 pass. tsc clean.
2026-03-27 19:10:06 +09:00
YeonGyu-Kim
f1f099fde9 fix(#2849): resolve platform binaries using current package name
The installer wrapper and postinstall script still hardcoded the old
oh-my-opencode-* platform package family. When users installed the renamed
npm package oh-my-openagent (for example via npx oh-my-openagent install on
WSL2/Linux), the main package installed correctly but the wrapper looked for
nonexistent optional binaries like oh-my-opencode-linux-x64.

Fix:
- derive packageBaseName from package.json at runtime in wrapper/postinstall
- thread packageBaseName through platform package candidate resolution
- keep legacy oh-my-opencode default as fallback
- add regression test for renamed package family
2026-03-27 18:29:36 +09:00
YeonGyu-Kim
6662205646 fix(#2748): pass browserProvider into skill() discovery
skill-context already filtered browser-related skills using the configured
browser provider, but the skill tool rebuilt discovery without forwarding
browserProvider. That caused skills like agent-browser to be prompt-visible
while skill() could still fail to resolve them unless browser_automation_engine.provider
was explicitly threaded through both paths.

Fix:
- pass skillContext.browserProvider from tool-registry into createSkillTool
- extend SkillLoadOptions with browserProvider
- forward browserProvider to getAllSkills()
- add regression tests for execution and description visibility
2026-03-27 17:58:38 +09:00
YeonGyu-Kim
76bf269b39 fix(#2754): include native PluginInput skills in skill() discovery
The skill tool previously only merged disk-discovered skills and preloaded
options.skills, so skills registered natively via ctx.skills (for example
from config.skills.paths or other plugins) were prompt-visible but not
discoverable by skill().

Fix:
- pass ctx.skills from tool-registry into createSkillTool
- extend SkillLoadOptions with nativeSkills accessor
- merge nativeSkills.all() results into getSkills()
- add test covering native PluginInput skill discovery
2026-03-27 17:42:43 +09:00
Ravi Tharuma
3e4b988860 fix: isolate event hook failures during dispatch 2026-03-27 09:30:43 +01:00
YeonGyu-Kim
d3dbb4976e fix(#2854): enable task system by default (oracle/subagent delegation)
task_system now defaults to true instead of false. Users on opencode-go
or other plans were unable to invoke oracle or use subagent delegation
because experimental.task_system defaulted off.

The task system is the primary mechanism for oracle, explore, and other
subagent invocations — it should not be behind an experimental flag.

Users can still explicitly set experimental.task_system: false to opt out.
2026-03-27 17:03:33 +09:00
YeonGyu-Kim
ec7a2e3eae fix(#2857): prevent npm scoped package paths from being resolved as skill paths
resolveSkillPathReferences: add looksLikeFilePath() guard that requires
a file extension or trailing slash before resolving @scope/package
references. npm packages like @mycom/my_mcp_tools@beta were incorrectly
being rewritten to absolute paths in skill templates.

2 new tests.
2026-03-27 16:59:04 +09:00
YeonGyu-Kim
c41e59e9ab fix(#2825): secondary agents no longer pruned after 30 min of total runtime
TTL (pruneStaleTasksAndNotifications) now resets on last activity:
- Uses task.progress.lastUpdate as TTL anchor for running tasks
  (was always using startedAt, causing 30-min hard deadline)
- Added taskTtlMs config option for user-adjustable TTL
- Error message shows actual TTL duration, not hardcoded '30 minutes'
- 3 new tests for the new behavior
2026-03-27 16:06:38 +09:00
YeonGyu-Kim
3b4420bc23 fix(#2735): check model availability before using custom subagent default model
subagent-resolver: when falling back to matchedAgent.model for custom
subagents, verify the model is actually available via fuzzyMatchModel
before setting it as categoryModel. Prevents delegate_task from using
an unavailable model when the user's custom agent config references
a model they don't have access to.

Test updated to include the target model in available models mock.
2026-03-27 15:50:16 +09:00
YeonGyu-Kim
3be26cb97f fix(#2732): enhance notification for failed/crashed subagent tasks
- completedTaskSummaries now includes status and error info
- notifyParentSession: noReply=false for failed tasks so parent reacts
- Batch notification distinguishes successful vs failed/cancelled tasks
- notification-template updated to show task errors
- task-poller: session-gone tests (85 new lines)
- CI: add Bun shim to PATH for legacy plugin migration tests
2026-03-27 15:48:07 +09:00
YeonGyu-Kim
e22e13cd29 fix(#2732): detect crashed subagent sessions with shorter timeout
When a subagent session disappears from the status registry (process
crashed), the main agent was waiting the full stale timeout before
acting. Fix:

- Add sessionGoneTimeoutMs config option (default 60s, vs 30min normal)
- task-poller: use shorter timeout when session is gone from status
- manager: verify session existence when gone, fail crashed tasks
  immediately with descriptive error
- Add legacy-plugin-toast hook for #2823 migration warnings
- Update schema with new config option
2026-03-27 15:43:01 +09:00
YeonGyu-Kim
6a733c9dde fix(#2823): auto-migrate legacy plugin name and warn users at startup
- logLegacyPluginStartupWarning now emits console.warn (visible to user,
  not just log file) when oh-my-opencode is detected in opencode.json
- Auto-migrates opencode.json plugin entry from oh-my-opencode to
  oh-my-openagent (with backup)
- plugin-config.ts: add console.warn when loading legacy config filename
- test: 10 tests covering migration, console output, edge cases
2026-03-27 15:40:04 +09:00
YeonGyu-Kim
127626a122 fix(#2822): properly cleanup tmux sessions on process shutdown
Two issues fixed:
1. process-cleanup.ts used fire-and-forget void Promise for shutdown
   handlers — now properly collects and awaits all cleanup promises
   via Promise.allSettled, with dedup guard to prevent double cleanup
2. TmuxSessionManager was never registered for process cleanup —
   now registered in create-managers.ts via registerManagerForCleanup

Also fixed setTimeout().unref() which could let the process exit
before cleanup completes.
2026-03-27 15:23:48 +09:00
YeonGyu-Kim
5765168af4 fix(#2731): skip unauthenticated providers when resolving subagent model
Background subagents (explore/librarian) failed with auth errors because
resolveModelForDelegateTask() always picked the first fallback entry when
availableModels was empty — often an unauthenticated provider like xai
or opencode-go.

Fix: when connectedProvidersCache is populated, iterate fallback chain
and pick the first entry whose provider is in the connected set.
Legacy behavior preserved when cache is null (not yet populated).

- model-selection.ts: use readConnectedProvidersCache to filter fallback chain
- test: 4 new tests for connected-provider-aware resolution
2026-03-27 14:38:02 +09:00
github-actions[bot]
e65a0ed10d @WhiteGiverMa has signed the CLA in code-yeongyu/oh-my-openagent#2877 2026-03-27 05:26:49 +00:00
YeonGyu-Kim
041770ff42 fix(#2736): prevent infinite compaction loop by setting cooldown before try
lastCompactionTime was only set on successful compaction. When compaction
failed (rate limit, timeout, etc.), no cooldown was recorded, causing
immediate retries in an infinite loop.

Fix: set lastCompactionTime before the try block so both success and
failure respect the cooldown window.

- test: add failed-compaction cooldown enforcement test
- test: fix timeout retry test to advance past cooldown
2026-03-27 14:25:38 +09:00
WhiteGiverMa
a3b84ec5f9 fix: use getAgentDisplayName in injectBoulderContinuation
The agent parameter was using raw config key "atlas" but the SDK
expects the display name "Atlas (Plan Executor)". This caused
"Agent not found: 'atlas'" errors when auto-compact tried to
continue boulder execution.

Root cause: injectBoulderContinuation passed raw agent key to
session.promptAsync, but SDK's agent matching logic compares
against display names registered in the system.

Fix: Use getAgentDisplayName() to convert the config key to
the expected display name before passing to the SDK.
2026-03-27 13:22:58 +08:00
YeonGyu-Kim
7ce7a85768 fix(#2855): tmux health check fails across module instances in same process
The server-health module used module-level state for inProcessServerRunning,
which doesn't survive when Bun loads separate module instances in the same
process. Fix: use globalThis with Symbol.for key so the flag is truly
process-global.

- server-health.ts: replace module-level boolean with globalThis[Symbol.for()]
- export markServerRunningInProcess from tmux-utils barrel
- test: verify flag skips HTTP fetch, verify globalThis persistence
2026-03-27 14:17:06 +09:00
YeonGyu-Kim
19ab3b5656 fix(#2853): sync .sisyphus state from worktree to main repo before removal
When .sisyphus/ is gitignored, task state written during worktree execution
is lost when the worktree is removed. Fix:

- add worktree-sync.ts: syncSisyphusStateFromWorktree() copies .sisyphus/
  contents from worktree to main repo directory
- update start-work.ts template: documents the sync step as CRITICAL when
  worktree_path is set in boulder.json
- update work-with-pr/SKILL.md: adds explicit sync step before worktree removal
- export from boulder-state index
- test: 5 scenarios covering no-.sisyphus, nested dirs, overwrite stale state
2026-03-27 14:11:45 +09:00
YeonGyu-Kim
670d8ab175 fix(#2852): forward model overrides from categories/agent config to subagents
- fix(switcher): use lastIndexOf for multi-slash model IDs (e.g. aws/anthropic/claude-sonnet-4)
- fix(model-resolution): same lastIndexOf fix in doctor parseProviderModel
- fix(call-omo-agent): resolve model from agent config and forward to both
  background and sync executors via DelegatedModelConfig
- fix(subagent-resolver): inherit category model/variant when agent uses
  category reference without explicit model override
- test: add model override forwarding tests for call-omo-agent
- test: add multi-slash model ID test for switcher
2026-03-27 13:59:06 +09:00
YeonGyu-Kim
40a92138ea fix: resolve three open bugs (#2836, #2858, #2873)
- fix(context-limit): check modelContextLimitsCache for all Anthropic
  models, not just GA-model set; user config/cache wins over 200K default
  (fixes #2836)

- fix(agent-key-remapper): preserve config key aliases alongside display
  names so `opencode run --agent sisyphus` resolves correctly
  (fixes #2858)

- fix(tool-config): respect host permission.skill=deny by disabling
  skill/skill_mcp tools when host denies them (fixes #2873)

- test: update context-limit and agent-key-remapper tests to match new
  behavior
2026-03-27 13:32:18 +09:00
YeonGyu-Kim
a081ddcefb docs: update documentation for v3.13.1 feature changes
- Document object-style fallback_models with per-model settings
- Add model-settings compatibility normalization docs
- Document file:// URI support for prompt and prompt_append
- Add deterministic core-agent order (Tab cycling) docs
- Update rename compatibility notes (legacy plugin warning)
- Document doctor legacy package name detection
- Add models.dev-backed capability cache documentation
- Update Hephaestus default to gpt-5.4 (medium)
- Correct MiniMax M2.7/M2.5 usage across all docs
- Update all agent/category provider chain tables
- Fix stale CLI install/doctor options to match source
- Add refresh-model-capabilities command to CLI reference

Co-authored-by: Sisyphus <sisyphus@oh-my-opencode>
2026-03-27 12:59:50 +09:00
YeonGyu-Kim
8f4554e115 fix(lsp): accept directory as alias for filePath in lsp_diagnostics
Models frequently hallucinate a 'directory' parameter alongside filePath,
causing hard failures. Instead of rejecting, accept directory as an alias
for filePath and gracefully handle when both are provided (prefer filePath).

This prevents the 'filePath and directory are mutually exclusive' error
that users hit when models pass both parameters.

Fixes model confusion with lsp_diagnostics tool parameters.
2026-03-27 12:59:50 +09:00
github-actions[bot]
07793f35a7 @Jholly2008 has signed the CLA in code-yeongyu/oh-my-openagent#2871 2026-03-27 03:37:37 +00:00
YeonGyu-Kim
8ca93c7a27 Merge pull request #2863 from MoerAI/fix/task-schema-mutual-exclusion
fix(delegate-task): reject when both category and subagent_type provided (fixes #2847)
2026-03-27 12:30:46 +09:00
YeonGyu-Kim
a1b4e97e74 Merge pull request #2856 from potb/fix/publish-version-commitback
fix(publish): restore version commit-back to dev after npm release
2026-03-27 12:30:34 +09:00
YeonGyu-Kim
47e7d4afbb Merge pull request #2861 from MoerAI/fix/category-config-params
fix(delegate-task): apply category config temperature/maxTokens/top_p to categoryModel (fixes #2831)
2026-03-27 12:30:31 +09:00
YeonGyu-Kim
6d3172adc9 Merge pull request #2862 from MoerAI/fix/empty-text-with-tool-calls
fix(recovery): detect empty text parts alongside tool calls in fixEmptyMessages (fixes #2830)
2026-03-27 12:30:29 +09:00
YeonGyu-Kim
65dc3e4a3b Merge pull request #2865 from LTS2/fix/2803-hook-task-examples-missing-load-skills
Fix missing load_skills parameter in hook-injected delegate_task examples
2026-03-27 12:30:26 +09:00
YeonGyu-Kim
587ee704e8 Merge pull request #2866 from LTS2/fix/2830-empty-message-recovery-with-tool-calls
Fix empty message recovery when tool calls coexist with empty text parts
2026-03-27 12:30:23 +09:00
YeonGyu-Kim
3bafa88204 Merge pull request #2867 from MoerAI/fix/openai-tool-limit
fix(tools): add max_tools config to cap registered tools for OpenAI compatibility (fixes #2848)
2026-03-27 12:30:21 +09:00
YeonGyu-Kim
f2496158e8 Merge pull request #2859 from RaviTharuma/docs/fallback-model-objects
docs(config): document object-style fallback_models
2026-03-27 12:25:07 +09:00
YeonGyu-Kim
a7ac2e7aba merge: resolve conflicts with dev docs update 2026-03-27 12:24:51 +09:00
YeonGyu-Kim
a2c7fed9d4 docs: comprehensive update for v3.14.0 features
- Document object-style fallback_models with per-model settings
- Add package rename compatibility layer docs (oh-my-opencode → oh-my-openagent)
- Update agent-model-matching with Hephaestus gpt-5.4 default
- Document MiniMax M2.5 → M2.7 upgrade across agents
- Add agent priority/order deterministic Tab cycling docs
- Document file:// URI support for agent prompt field
- Add doctor legacy package name warning docs
- Update CLI reference with new doctor checks
- Document model settings compatibility resolver
2026-03-27 12:20:40 +09:00
MoerAI
98572c8dac fix: guard fallback override to preserve category config params when fallback fields are undefined 2026-03-27 12:20:19 +09:00
孔祥俊
661737b95a fix(cli): respect platform shell for --on-complete 2026-03-27 11:11:58 +08:00
MoerAI
8136679b1c test: update test to expect mutual exclusion error for category+subagent_type 2026-03-27 11:53:54 +09:00
MoerAI
82d89fd5fc fix(tools): add max_tools config to cap registered tools for OpenAI compatibility (fixes #2848) 2026-03-27 11:11:56 +09:00
ewjin
b1735d4004 fix: detect empty text parts in messages with tool calls during session recovery 2026-03-27 11:04:42 +09:00
ewjin
8bde294978 fix: add missing load_skills parameter to hook-injected delegate_task examples 2026-03-27 10:56:49 +09:00
MoerAI
a476e557c9 fix(delegate-task): reject when both category and subagent_type provided (fixes #2847) 2026-03-27 10:35:48 +09:00
MoerAI
404390efda fix(recovery): detect empty text parts alongside tool calls in fixEmptyMessages (fixes #2830) 2026-03-27 10:18:20 +09:00
MoerAI
944cf429a7 fix(delegate-task): apply category config temperature/maxTokens/top_p to categoryModel (fixes #2831) 2026-03-27 10:01:23 +09:00
Ravi Tharuma
241224f7ab docs(config): document object-style fallback_models 2026-03-26 18:55:22 +01:00
YeonGyu-Kim
1c9f4148d0 fix(publish-ci): sync mock-heavy test isolation with ci.yml
Apply the same mock.module() isolation fixes to publish.yml:
- Move shared and session-recovery mock-heavy tests to isolated section
- Use dynamic find + exclusion for remaining src/shared tests
- Include session-recovery tests in remaining batch

Ensures publish workflow has the same test config as main CI run.
2026-03-27 00:56:55 +09:00
YeonGyu-Kim
8dd0191ea5 fix(ci): isolate mock-heavy shared tests to prevent cross-file contamination
Move 4 src/shared tests that use mock.module() to the isolated test section:
- model-capabilities.test.ts (mocks ./connected-providers-cache)
- log-legacy-plugin-startup-warning.test.ts (mocks ./legacy-plugin-warning)
- model-error-classifier.test.ts
- opencode-message-dir.test.ts

Also isolate recover-tool-result-missing.test.ts (mocks ./storage).

Use find + exclusion pattern in remaining tests to dynamically build the
src/shared file list without the isolated mock-heavy files.

Fixes 6 Linux CI failures caused by bun's mock.module() cache pollution
when running in parallel.
2026-03-27 00:08:27 +09:00
YeonGyu-Kim
9daaeedc50 fix(test): restore shared Bun mocks after suite cleanup
Prevent src/shared batch runs from leaking module mocks into later files, which was breaking Linux CI cache metadata and legacy plugin warning assertions.
2026-03-27 00:08:20 +09:00
YeonGyu-Kim
3e13a4cf57 fix(session-recovery): filter invalid prt_* part IDs from tool_use_id reconstruction
When recovering missing tool results, the session recovery hook was using
raw part.id (prt_* format) as tool_use_id when callID was absent, causing
ZodError validation failures from the API.

Added isValidToolUseID() guard that only accepts toolu_* and call_* prefixed
IDs, and normalizeMessagePart() that returns null for parts without valid
callIDs. Both the SQLite fallback and stored-parts paths now filter out
invalid entries before constructing tool_result payloads.

Includes 4 regression tests covering both valid/invalid callID paths for
both SQLite and stored-parts backends.
2026-03-26 20:48:33 +09:00
Peïo Thibault
fb837db90d fix(publish): restore version commit-back to dev after npm release 2026-03-26 12:44:27 +01:00
YeonGyu-Kim
8e65d6cf2c fix(test): make legacy-plugin-warning tests isolation-safe
Pass explicit config dir to checkForLegacyPluginEntry instead of relying
on XDG_CONFIG_HOME env var, which gets contaminated by parallel tests on
Linux CI. Also adds missing 'join' import.
2026-03-26 19:54:05 +09:00
YeonGyu-Kim
f419a3a925 fix(test): use Bun.spawnSync in command discovery test to avoid execFileSync mock leakage
The opencode-project-command-discovery test used execFileSync for git init,
which collided with image-converter.test.ts's global execFileSync mock when
running in parallel on Linux CI. Switching to Bun.spawnSync avoids the mock
entirely since spyOn(childProcess, 'execFileSync') doesn't affect Bun APIs.

Fixes CI flake that only reproduced on Linux.
2026-03-26 19:47:25 +09:00
YeonGyu-Kim
1c54fdad26 feat(compat): package rename compatibility layer for oh-my-opencode → oh-my-openagent
- Add legacy plugin startup warning when oh-my-opencode config detected
- Update CLI installer and TUI installer for new package name
- Split monolithic config-manager.test.ts into focused test modules
- Add plugin config detection tests for legacy name fallback
- Update processed-command-store to use plugin-identity constants
- Add claude-code-plugin-loader discovery test for both config names
- Update chat-params and ultrawork-db tests for plugin identity

Part of #2823
2026-03-26 19:44:55 +09:00
YeonGyu-Kim
d39891fcab docs: update hephaestus default model references from gpt-5.3-codex to gpt-5.4
Updated across README (all locales), docs/guide/, docs/reference/,
docs/examples/, AGENTS.md files, and test expectations/snapshots.

The deep category and multimodal-looker still use gpt-5.3-codex as
those are separate from the hephaestus agent.
2026-03-26 19:25:26 +09:00
YeonGyu-Kim
d57ed97386 feat(hephaestus): upgrade default model from gpt-5.3-codex to gpt-5.4
Hephaestus now uses gpt-5.4 as its default model across all providers
(openai, github-copilot, venice, opencode), matching Sisyphus's GPT 5.4
support. The separate gpt-5.3-codex → github-copilot fallback entry is
removed since gpt-5.4 is available on all required providers.
2026-03-26 19:02:37 +09:00
github-actions[bot]
6a510c01e0 @kuitos has signed the CLA in code-yeongyu/oh-my-openagent#2833 2026-03-26 09:56:02 +00:00
MoerAI
95801a4850 fix(ralph-loop): extract text from parsed entry instead of testing raw JSONL 2026-03-26 18:16:07 +09:00
YeonGyu-Kim
b34eab3884 fix(test): isolate model-capabilities from local provider cache
Mock connected-providers-cache in model-capabilities.test.ts to prevent
findProviderModelMetadata from reading disk-cached model metadata.

Without this mock, the 'prefers runtime models.dev cache' test gets
polluted by real cached data from opencode serve runs, causing the
test to receive different maxOutputTokens/supportsTemperature values
than the mock runtime snapshot provides.

This was the last CI-only failure — passes locally with cache, fails
on CI without cache, now passes everywhere via mock isolation.

Full suite: 4484 pass, 0 fail.
2026-03-26 18:14:51 +09:00
YeonGyu-Kim
4efc181390 fix(ci): resolve all test failures + complete rename compat layer
Sisyphus-authored fixes across 15 files:

- plugin-identity: align CONFIG_BASENAME with actual config file name
- add-plugin-to-opencode-config: handle legacy→canonical name migration
- plugin-detection tests: update expectations for new identity constants
- doctor/system: fix legacy name warning test assertions
- install tests: align with new plugin name
- chat-params tests: fix mock isolation
- model-capabilities tests: fix snapshot expectations
- image-converter: fix platform-dependent test assertions (Linux CI)
- example configs: expanded with more detailed comments

Full suite: 4484 pass, 0 fail, typecheck clean.
2026-03-26 18:04:31 +09:00
PR Bot
3601061da0 feat: upgrade remaining MiniMax M2.5 fallbacks to M2.7-highspeed
Upgrade the secondary MiniMax fallback entries in librarian and explore
agents from M2.5 to M2.7-highspeed, completing the M2.5→M2.7 migration.

- librarian: minimax-m2.5 → minimax-m2.7-highspeed (2nd fallback)
- explore: minimax-m2.7 → minimax-m2.7-highspeed (2nd), minimax-m2.5 → minimax-m2.7 (3rd)
- Update corresponding test assertions
2026-03-26 16:49:31 +08:00
YeonGyu-Kim
e86edca633 feat(doctor): warn on legacy package name + add example configs
- Doctor now detects when opencode.json references 'oh-my-opencode'
  (legacy name) and warns users to switch to 'oh-my-openagent' with
  the exact replacement string.

- Added 3 example config files in docs/examples/:
  - default.jsonc: balanced setup with all agents documented
  - coding-focused.jsonc: Sisyphus + Hephaestus heavy
  - planning-focused.jsonc: Prometheus + Atlas heavy

All examples include every agent (sisyphus, hephaestus, atlas,
prometheus, explore, librarian) with model recommendations.

Helps with #2823
2026-03-26 17:01:42 +09:00
YeonGyu-Kim
a8ec92748c fix(model-resolution): honor user config overrides on cold cache
When provider-models cache is cold (first run / cache miss),
resolveModelForDelegateTask returns {skipped: true}. Previously this
caused the subagent resolver to:

1. Ignore the user's explicit model override (e.g. explore.model)
2. Fall through to the hardcoded fallback chain which may contain
   model IDs that don't exist in the provider catalog

Now:
- subagent-resolver: if resolution is skipped but user explicitly
  configured a model, use it directly
- subagent-resolver: don't assign hardcoded fallback chain on skip
- category-resolver: same — don't leak hardcoded chain on skip
- general-agents: if user model fails resolution, use it as-is
  instead of falling back to hardcoded chain first entry

Closes #2820
2026-03-26 16:54:04 +09:00
YeonGyu-Kim
dd85d1451a fix(model-requirements): align fallback models with available provider catalogs
- opencode/minimax-m2.7-highspeed → opencode/minimax-m2.5 (provider lacks m2.7 variants)
- opencode-go/minimax-m2.7-highspeed → opencode-go/minimax-m2.7 (provider lacks -highspeed)
- opencode/minimax-m2.7 → opencode/minimax-m2.5 (provider only has m2.5)
- added xai as alternative provider for grok-code-fast-1 (prevents wrong provider prefix)
2026-03-26 16:00:02 +09:00
YeonGyu-Kim
682eead61b Merge pull request #2845 from code-yeongyu/fix/path-discovery-parity-followup
fix: add remaining path discovery parity coverage
2026-03-26 13:13:15 +09:00
YeonGyu-Kim
42f5386100 fix(tests): drop duplicate tilde config regression
Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent)

Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
2026-03-26 13:08:53 +09:00
YeonGyu-Kim
5bc019eb7c fix(skills): remove duplicate homedir import
Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent)

Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
2026-03-26 13:06:32 +09:00
YeonGyu-Kim
097e2be7e8 fix(slashcommand): discover nested opencode commands with slash names
Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent)

Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
2026-03-26 13:05:03 +09:00
YeonGyu-Kim
c637d77965 fix(commands): discover ancestor opencode project commands
Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent)

Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
2026-03-26 13:04:39 +09:00
YeonGyu-Kim
4c8aacef48 fix(agents): include .agents skills in agent awareness
Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent)

Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
2026-03-26 13:02:52 +09:00
YeonGyu-Kim
8413bc6a91 fix(skills): expand tilde config source paths
Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent)

Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
2026-03-26 13:02:36 +09:00
YeonGyu-Kim
86a62aef45 fix(skills): discover ancestor project skill directories
Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent)

Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
2026-03-26 13:02:36 +09:00
YeonGyu-Kim
961cc788f6 fix(shared): support opencode directory aliases
Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent)

Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
2026-03-26 13:02:12 +09:00
YeonGyu-Kim
19838b78a7 fix(shared): add bounded project discovery helpers
Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent)

Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
2026-03-26 13:01:06 +09:00
YeonGyu-Kim
9d4a8f2183 Merge pull request #2844 from code-yeongyu/fix/opencode-followup-gaps
fix: close remaining upstream path discovery gaps
2026-03-26 12:35:39 +09:00
YeonGyu-Kim
7f742723b5 fix(slashcommand): use slash separator for nested commands
Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent)

Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
2026-03-26 12:29:42 +09:00
YeonGyu-Kim
b20a34bfa7 fix(slashcommand): discover nested opencode commands
Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent)

Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
2026-03-26 12:15:47 +09:00
YeonGyu-Kim
12a4318439 fix(commands): load .agents skills into command config
Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent)

Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
2026-03-26 12:15:47 +09:00
YeonGyu-Kim
e4a5973b16 fix(agents): include .agents skills in agent awareness
Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent)

Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
2026-03-26 12:15:47 +09:00
YeonGyu-Kim
83819a15d3 fix(shared): stop ancestor discovery at worktree root
Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent)

Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
2026-03-26 12:15:47 +09:00
YeonGyu-Kim
a391f44420 Merge pull request #2842 from code-yeongyu/fix/opencode-skill-override-gaps
fix: align path discovery with upstream opencode
2026-03-26 11:54:08 +09:00
YeonGyu-Kim
94b4a4f850 fix(slashcommand): deduplicate opencode command aliases
Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent)

Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
2026-03-26 11:36:59 +09:00
YeonGyu-Kim
9fde370838 fix(commands): preserve nearest opencode command precedence
Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent)

Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
2026-03-26 11:36:59 +09:00
YeonGyu-Kim
b6ee7f09b1 fix(slashcommand): discover ancestor opencode commands
Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent)

Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
2026-03-26 11:22:00 +09:00
YeonGyu-Kim
28bcab066e fix(commands): load opencode command dirs from aliases
Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent)

Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
2026-03-26 11:22:00 +09:00
YeonGyu-Kim
b5cb50b561 fix(skills): discover ancestor project skill directories
Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent)

Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
2026-03-26 11:22:00 +09:00
YeonGyu-Kim
8242500856 fix(skills): expand tilde config source paths
Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent)

Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
2026-03-26 11:22:00 +09:00
YeonGyu-Kim
6d688ac0ae fix(shared): support opencode directory aliases
Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent)

Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
2026-03-26 11:22:00 +09:00
YeonGyu-Kim
da3e80464d fix(shared): add ancestor project discovery helpers
Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent)

Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
2026-03-26 11:22:00 +09:00
YeonGyu-Kim
23df6bd255 Merge pull request #2841 from code-yeongyu/fix/model-fallback-test-isolation
fix(tests): resolve 5 cross-file test isolation failures
2026-03-26 09:31:09 +09:00
YeonGyu-Kim
7895361f42 fix(tests): resolve 5 cross-file test isolation failures
- model-fallback hook: mock selectFallbackProvider and add _resetForTesting()
  to test-setup.ts to clear module-level state between files
- fallback-retry-handler: add afterAll(mock.restore) and use mockReturnValueOnce
  to prevent connected-providers mock leaking to subsequent test files
- opencode-config-dir: use win32.join for Windows APPDATA path construction
  so tests pass on macOS (path.join uses POSIX semantics regardless of
  process.platform override)
- system-loaded-version: use resolveSymlink from file-utils instead of
  realpathSync to handle macOS /var -> /private/var symlink consistently

All 4456 tests pass (0 failures) on full bun test suite.
2026-03-26 09:30:34 +09:00
YeonGyu-Kim
90919bf359 Merge pull request #2664 from kilhyeonjun/fix/anthropic-1m-ga-context-limit
fix(shared): respect cached model context limits for Anthropic providers post-GA
2026-03-26 08:55:04 +09:00
YeonGyu-Kim
32f2c688e7 Merge pull request #2707 from MoerAI/fix/windows-symlink-config
fix(windows): resolve symlinked config paths and plugin name parsing (fixes #2271)
2026-03-26 08:54:45 +09:00
YeonGyu-Kim
e6d0484e57 Merge pull request #2710 from MoerAI/fix/rate-limit-hang
fix(runtime-fallback): detect bare 429 rate-limit signals (fixes #2677)
2026-03-26 08:53:41 +09:00
YeonGyu-Kim
abd62472cf Merge pull request #2752 from MoerAI/fix/quota-error-fallback-detection
fix(runtime-fallback): detect prettified quota errors without HTTP status codes (fixes #2747)
2026-03-26 08:50:58 +09:00
YeonGyu-Kim
b1e099130a Merge pull request #2756 from MoerAI/fix/plugin-display-name
fix(plugin): display friendly name in configuration UI instead of file path (fixes #2644)
2026-03-26 08:50:29 +09:00
YeonGyu-Kim
09fb364bfb Merge pull request #2833 from kuitos/feat/agent-order-support
feat(agent-priority): inject order field for deterministic agent Tab cycling
2026-03-26 08:49:58 +09:00
YeonGyu-Kim
d1ff8b1e3f Merge pull request #2727 from octo-patch/feature/upgrade-minimax-m2.7
feat: upgrade MiniMax from M2.5 to M2.7 and expand to more agents/categories
2026-03-26 08:49:11 +09:00
YeonGyu-Kim
6e42b553cc Merge origin/dev into feature/upgrade-minimax-m2.7 (resolve conflicts) 2026-03-26 08:48:53 +09:00
YeonGyu-Kim
02ab83f4d4 Merge pull request #2834 from RaviTharuma/feat/model-capabilities-canonical-guardrails
fix(model-capabilities): harden canonical alias guardrails
2026-03-26 08:46:43 +09:00
github-actions[bot]
ce1bffbc4d @ventsislav-georgiev has signed the CLA in code-yeongyu/oh-my-openagent#2840 2026-03-25 23:11:43 +00:00
github-actions[bot]
4d4680be3c @clansty has signed the CLA in code-yeongyu/oh-my-openagent#2839 2026-03-25 21:33:49 +00:00
Ravi Tharuma
ce877ec0d8 test(atlas): avoid shared barrel mock pollution 2026-03-25 22:27:26 +01:00
Ravi Tharuma
ec20a82b4e fix(model-capabilities): align gemini aliases and alias lookup 2026-03-25 22:19:51 +01:00
Ravi Tharuma
5043cc21ac fix(model-capabilities): harden canonical alias guardrails 2026-03-25 22:11:45 +01:00
github-actions[bot]
8df3a2876a @anas-asghar4831 has signed the CLA in code-yeongyu/oh-my-openagent#2837 2026-03-25 18:48:32 +00:00
YeonGyu-Kim
087e33d086 Merge pull request #2832 from RaviTharuma/fix/todo-sync-priority-default
test(todo-sync): match required priority fallback
2026-03-26 01:30:50 +09:00
Ravi Tharuma
46c6e1dcf6 test(todo-sync): match required priority fallback 2026-03-25 16:38:21 +01:00
kuitos
5befb60229 feat(agent-priority): inject order field for deterministic agent Tab cycling
Inject an explicit `order` field (1-4) into the four core agents
(Sisyphus, Hephaestus, Prometheus, Atlas) via reorderAgentsByPriority().
This pre-empts OpenCode's alphabetical agent sorting so the intended
Tab cycle order is preserved once OpenCode merges order field support
(anomalyco/opencode#19127).

Refs anomalyco/opencode#7372
2026-03-25 23:35:40 +08:00
Ravi Tharuma
55df2179b8 fix(todo-sync): preserve missing task priority 2026-03-25 16:26:23 +01:00
YeonGyu-Kim
76420b36ab Merge pull request #2829 from RaviTharuma/fix/model-capabilities-review-followup
fix(model-capabilities): harden runtime capability handling
2026-03-26 00:25:07 +09:00
Ravi Tharuma
a15f6076bc feat(model-capabilities): add maintenance guardrails 2026-03-25 16:14:19 +01:00
Ravi Tharuma
7c0289d7bc fix(model-capabilities): honor root thinking flags 2026-03-25 15:41:12 +01:00
YeonGyu-Kim
5e9231e251 Merge pull request #2828 from code-yeongyu/fix/content-based-thinking-gating-v2
fix(thinking-block-validator): replace model-name gating with content-based history detection
2026-03-25 23:26:52 +09:00
YeonGyu-Kim
f04cc0fa9c fix(thinking-block-validator): replace model-name gating with content-based history detection
Replace isExtendedThinkingModel() model-name check with hasSignedThinkingBlocksInHistory()
which scans message history for real Anthropic-signed thinking blocks.

Content-based gating is more robust than model-name checks — works correctly
with custom model IDs, proxied models, and new model releases without code changes.

- Add isSignedThinkingPart() that matches type thinking/redacted_thinking with valid signature
- Skip synthetic parts (injected by previous hook runs)
- GPT reasoning blocks (type=reasoning, no signature) correctly excluded
- Add comprehensive tests: signed injection, redacted_thinking, reasoning negative case, synthetic skip

Inspired by PR #2653 content-based approach, combined with redacted_thinking support from 0732cb85.

Ultraworked with Sisyphus
Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
2026-03-25 23:23:46 +09:00
Ravi Tharuma
613ef8eee8 fix(model-capabilities): harden runtime capability handling 2026-03-25 15:09:25 +01:00
YeonGyu-Kim
99b398063c Merge pull request #2826 from RaviTharuma/feat/model-capabilities-models-dev
feat(model-capabilities): add models.dev snapshot and runtime capability refresh
2026-03-25 23:08:17 +09:00
Ravi Tharuma
2af9324400 feat: add models.dev-backed model capabilities 2026-03-25 14:47:46 +01:00
YeonGyu-Kim
7a52639a1b Merge pull request #2673 from sanoyphilippe/fix/oauth-discovery-root-fallback
fix(mcp-oauth): fall back to root well-known URL for non-root resource paths (fixes #2675)
2026-03-25 21:48:13 +09:00
YeonGyu-Kim
5df54bced4 Merge pull request #2725 from cphoward/fix/spawn-budget-lifetime-semantics-clean
fix(background-agent): decrement spawn budget on task completion, cancellation, error, and interrupt
2026-03-25 21:46:51 +09:00
YeonGyu-Kim
cd04e6a19e Merge pull request #2751 from sjawhar/fix/atlas-subagent-agent-guard
fix(atlas): restore agent mismatch guard for subagent boulder continuation
2026-03-25 21:46:37 +09:00
YeonGyu-Kim
e974b151c1 Merge pull request #2701 from tonymfer/fix/lsp-initialization-options
fix(lsp): wrap initialization config in initializationOptions field
2026-03-25 21:46:16 +09:00
YeonGyu-Kim
6f213a0ac9 Merge pull request #2686 from sjawhar/fix/look-at-respect-configured-model
fix(look-at): respect configured multimodal-looker model instead of overriding via dynamic fallback
2026-03-25 21:46:11 +09:00
YeonGyu-Kim
71004e88d3 Merge pull request #2583 from Jrakru/fix/start-work-atlas-handoff
fix: preserve Atlas handoff metadata on /start-work
2026-03-25 21:46:06 +09:00
YeonGyu-Kim
5898d36321 Merge pull request #2575 from apple-ouyang/fix/issue-2571-subagent-safeguards
fix(delegate-task): add subagent turn limit and model routing transparency
2026-03-25 21:46:01 +09:00
YeonGyu-Kim
90aa3e4489 Merge pull request #2589 from MoerAI/fix/plan-agent-continuation-loop
fix(todo-continuation-enforcer): add plan agent to DEFAULT_SKIP_AGENTS (fixes #2526)
2026-03-25 21:45:58 +09:00
YeonGyu-Kim
2268ba45f9 Merge pull request #2262 from Stranmor/feat/prompt-file-uri-support
feat: support file:// URIs in agent prompt field
2026-03-25 21:45:53 +09:00
YeonGyu-Kim
aca9342722 Merge pull request #2345 from DarkFunct/fix/todo-sync-priority-null
fix(todo-sync): provide default priority to prevent SQLite NOT NULL violation
2026-03-25 21:45:48 +09:00
YeonGyu-Kim
a3519c3a14 Merge pull request #2544 from djdembeck/fix/quick-anti-loop-v2
fix(agents): add termination criteria to Sisyphus-Junior default
2026-03-25 21:45:43 +09:00
YeonGyu-Kim
e610d88558 Merge pull request #2594 from MoerAI/fix/subagent-fallback-model-v2
fix(agent-registration): always attempt fallback when model resolution fails (fixes #2427, supersedes #2517)
2026-03-25 21:45:40 +09:00
YeonGyu-Kim
ed09bf5462 Merge pull request #2674 from RaviTharuma/fix/dedup-delegated-model-config
refactor: deduplicate DelegatedModelConfig into shared module
2026-03-25 21:43:31 +09:00
YeonGyu-Kim
1d48518b41 Merge pull request #2643 from RaviTharuma/feat/model-settings-compatibility-resolver
feat(settings): add model settings compatibility resolver
2026-03-25 21:43:28 +09:00
YeonGyu-Kim
d6d4cece9d Merge pull request #2622 from RaviTharuma/feat/object-style-fallback-models
feat(config): object-style fallback_models with per-model settings
2026-03-25 21:43:22 +09:00
Ravi Tharuma
9d930656da test(restack): drop stale compatibility expectations 2026-03-25 11:14:04 +01:00
Ravi Tharuma
f86b8b3336 fix(review): align model compatibility and prompt param helpers 2026-03-25 11:14:04 +01:00
Ravi Tharuma
1f5d7702ff refactor(delegate-task): deduplicate DelegatedModelConfig + registry refactor
- Move DelegatedModelConfig to src/shared/model-resolution-types.ts
- Re-export from delegate-task/types.ts (preserving import paths)
- Replace background-agent/types.ts local duplicate with shared import
- Consolidate model-settings-compatibility.ts registry patterns
2026-03-25 11:14:04 +01:00
Ravi Tharuma
1e70f64001 chore(schema): refresh generated fallback model schema 2026-03-25 11:13:53 +01:00
Ravi Tharuma
d4f962b55d feat(model-settings-compat): add variant/reasoningEffort compatibility resolver
- Registry-based model family detection (provider-agnostic)
- Variant and reasoningEffort ladder downgrade logic
- Three-tier resolution: metadata override → family heuristic → unknown drop
- Comprehensive test suite covering all model families
2026-03-25 11:13:53 +01:00
Ravi Tharuma
fb085538eb test(background-agent): restore spawner createTask import 2026-03-25 11:13:28 +01:00
Ravi Tharuma
e5c5438a44 fix(delegate-task): gate fallback settings to real fallback matches 2026-03-25 11:04:49 +01:00
Ravi Tharuma
a77a16c494 feat(config): support object-style fallback_models with per-model settings
Add support for object-style entries in fallback_models arrays, enabling
per-model configuration of variant, reasoningEffort, temperature, top_p,
maxTokens, and thinking settings.

- Zod schema for FallbackModelObject with full validation
- normalizeFallbackModels() and flattenToFallbackModelStrings() utilities
- Provider-agnostic model resolution pipeline with fallback chain
- Session prompt params state management
- Fallback chain construction with prefix-match lookup
- Integration across delegate-task, background-agent, and plugin layers
2026-03-25 11:04:49 +01:00
YeonGyu-Kim
7761e48dca Merge pull request #2592 from MoerAI/fix/gemini-quota-fallback
fix(runtime-fallback): detect Gemini quota errors in session.status retry events (fixes #2454)
2026-03-25 18:14:21 +09:00
MoerAI
d7a1945b27 fix(plugin-loader): preserve scoped npm package names in plugin key parsing
Scoped packages like @scope/pkg were truncated to just 'pkg' because
basename() strips the scope prefix. Fix:
- Detect scoped packages (starting with @) and find version separator
  after the scope slash, not at the leading @
- Return full scoped name (@scope/pkg) instead of calling basename
- Add regression test for scoped package name preservation
2026-03-25 17:10:07 +09:00
MoerAI
44fb114370 fix(runtime-fallback): rename misleading test to match actual behavior
The test name claimed it exercised RETRYABLE_ERROR_PATTERNS directly,
but classifyErrorType actually matches 'payment required' via the
quota_exceeded path first. Rename to 'detects payment required errors
as retryable' to accurately describe end-to-end behavior.
2026-03-25 16:58:49 +09:00
MoerAI
774d0bd84d fix(ralph-loop): restrict semantic completion to DONE promise and assistant entries
- Gate semantic detection on promise === 'DONE' in both transcript and
  session message paths to prevent false positives on VERIFIED promises
- Restrict transcript semantic fallback to assistant/text entries only,
  skipping tool_use/tool_result to avoid matching file content
- Add regression test for VERIFIED promise not triggering semantic detection
2026-03-25 16:49:53 +09:00
YeonGyu-Kim
bf804b0626 fix(shared): restrict cached Anthropic 1M context to GA 4.6 models only 2026-03-25 14:29:59 +09:00
YeonGyu-Kim
c4aa380855 Merge pull request #2734 from ndaemy/fix/remove-duplicate-ultrawork-separator
fix(keyword-detector): remove duplicate separator from ultrawork templates
2026-03-25 13:22:41 +09:00
YeonGyu-Kim
993bd51eac Merge pull request #2524 from Gujiassh/fix/session-todo-filename-match
fix(session-manager): match todo filenames exactly
2026-03-25 13:22:39 +09:00
YeonGyu-Kim
732743960f Merge pull request #2533 from Gujiassh/fix/background-task-metadata-id
fix(delegate-task): report the real background task id
2026-03-25 13:22:37 +09:00
YeonGyu-Kim
bff573488c Merge pull request #2443 from tc9011/fix/github-copilot-model-version
fix: github copilot model version for Sisyphus agent
2026-03-25 13:22:34 +09:00
YeonGyu-Kim
77424f86c8 Merge pull request #2816 from code-yeongyu/fix/keep-agent-with-explicit-model
fix: always keep agent with explicit model, robust port binding & writable dir fallback
2026-03-25 11:48:26 +09:00
YeonGyu-Kim
919f7e4092 fix(data-path): writable directory fallback for data/cache paths
getDataDir() and getCacheDir() now verify the directory is writable and
fall back to os.tmpdir() if not.

🤖 GENERATED WITH ASSISTANCE OF [OhMyOpenCode](https://github.com/code-yeongyu/oh-my-opencode)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-25 11:46:07 +09:00
YeonGyu-Kim
78a3e985be fix(mcp-oauth): robust port binding for callback server
Use port 0 fallback when findAvailablePort fails, read the actual bound
port from server.port. Tests refactored to use mock server when real
socket binding is unavailable in CI.

🤖 GENERATED WITH ASSISTANCE OF [OhMyOpenCode](https://github.com/code-yeongyu/oh-my-opencode)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-25 11:46:07 +09:00
YeonGyu-Kim
42fb2548d6 fix(agent): always keep agent when model is explicitly configured
Previously, when an explicit model was configured, the agent name was
omitted to prevent opencode's built-in agent fallback chain from
overriding the user-specified model. This removes that conditional logic
and always passes the agent name alongside the model. Tests are updated
to reflect this behavior change.

🤖 GENERATED WITH ASSISTANCE OF [OhMyOpenCode](https://github.com/code-yeongyu/oh-my-opencode)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-25 11:46:07 +09:00
YeonGyu-Kim
bff74f4237 Merge pull request #2695 from MoerAI/fix/provider-agnostic-fallback
fix(runtime-fallback): make fallback provider selection provider-agnostic (fixes #2303)
2026-03-25 11:36:50 +09:00
YeonGyu-Kim
038b8a79ec Revert "Merge pull request #2611 from MoerAI/fix/keep-default-builder-agent"
This reverts commit 0aa8bfe839, reversing
changes made to 422eaa9ae0.
2026-03-25 11:13:05 +09:00
YeonGyu-Kim
0aa8bfe839 Merge pull request #2611 from MoerAI/fix/keep-default-builder-agent
fix(config): keep default OpenCode Build agent enabled by default (fixes #2545)
2026-03-25 11:11:34 +09:00
YeonGyu-Kim
422eaa9ae0 Merge pull request #2753 from MoerAI/fix/prometheus-model-override
fix(prometheus): respect agent model override instead of using global opencode.json model (fixes #2693)
2026-03-25 11:09:48 +09:00
YeonGyu-Kim
63ebedc9a2 Merge pull request #2606 from RaviTharuma/fix/clamp-variant-on-non-opus-fallback
fix: clamp unsupported max variant for non-Opus Claude models
2026-03-25 11:06:31 +09:00
YeonGyu-Kim
f0b5835459 fix(publish): correct repo guard to oh-my-openagent (GitHub renamed repo) 2026-03-25 09:21:38 +09:00
YeonGyu-Kim
2a495c2e8d Merge pull request #2813 from code-yeongyu/fix/tmux-test-flake-20260325
test(tmux): remove flaky live env wrapper assertion
2026-03-25 02:08:05 +09:00
YeonGyu-Kim
0edb87b1c1 test(tmux): remove flaky live env wrapper assertion
Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent)

Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
2026-03-25 02:05:51 +09:00
YeonGyu-Kim
cca057dc0f Merge pull request #2812 from code-yeongyu/fix/non-interactive-env-win-bash-prefix
fix(non-interactive-env): force unix prefix for bash git commands
2026-03-25 01:24:18 +09:00
YeonGyu-Kim
e000a3bb0d fix(non-interactive-env): force unix prefix for bash git commands
Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent)

Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
2026-03-25 01:23:02 +09:00
YeonGyu-Kim
c19fc4ba22 Merge pull request #2811 from code-yeongyu/fix/publish-workflow-guard-topology-20260325
fix(publish): align repo guard and test topology
2026-03-25 01:19:29 +09:00
YeonGyu-Kim
e0de06851d fix(publish): align repo guard and test topology
Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent)

Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
2026-03-25 01:17:42 +09:00
YeonGyu-Kim
26ac413dd9 Merge pull request #2801 from MoerAI/fix/null-byte-sanitization
fix(tool-execute-before): strip null bytes from bash commands to prevent crash (fixes #2220)
2026-03-25 01:12:45 +09:00
YeonGyu-Kim
81c912cf04 Merge pull request #2800 from MoerAI/fix/background-task-fallback-chain
fix(background-task): register fallback chain for background sessions (fixes #2203)
2026-03-25 01:12:41 +09:00
YeonGyu-Kim
9c348db450 Merge pull request #2799 from MoerAI/fix/unstable-agent-config-override
fix(category-resolver): respect is_unstable_agent config override (fixes #2061)
2026-03-25 01:12:36 +09:00
YeonGyu-Kim
2993b3255d Merge pull request #2796 from guazi04/fix/circuit-breaker-false-positive-upstream
fix(circuit-breaker): treat unknown tool input as non-comparable to prevent false positives on flat events
2026-03-25 01:12:31 +09:00
YeonGyu-Kim
0b77e2def0 Merge pull request #2810 from code-yeongyu/fix/webfetch-redirect-loop
fix(webfetch): guard redirect loops in built-in flow
2026-03-25 00:40:54 +09:00
YeonGyu-Kim
bfa8fa2378 Merge pull request #2804 from code-yeongyu/fix/b2-hashline-formatter-cache-per-project
fix(hashline-edit): scope formatter cache by directory
2026-03-25 00:32:41 +09:00
YeonGyu-Kim
6ee680af99 Merge pull request #2809 from code-yeongyu/fix/2330-recursive-subagent-spawn
fix(task): preserve restricted agent tools in sync continuation
2026-03-25 00:32:14 +09:00
YeonGyu-Kim
d327334ded Merge pull request #2808 from code-yeongyu/fix-gemini-3-pro-cleanup
fix(models): remove stale Gemini 3 Pro references
2026-03-25 00:32:10 +09:00
YeonGyu-Kim
07d120a78d Merge pull request #2807 from code-yeongyu/fix/b4-manager-model-override-1774351606
fix(background-task): apply model override omission to manager live path
2026-03-25 00:31:49 +09:00
YeonGyu-Kim
8b7b1c843a Merge pull request #2806 from code-yeongyu/fix/b5-permission-merge-order
fix(plugin): restore permission merge order precedence
2026-03-25 00:31:43 +09:00
YeonGyu-Kim
a1786f469d Merge pull request #2805 from code-yeongyu/fix/b3-config-filename-precedence
fix(config): prefer canonical plugin config filenames
2026-03-25 00:31:18 +09:00
YeonGyu-Kim
da77d8addf Merge pull request #2802 from code-yeongyu/fix/b1-preemptive-compaction-epoch-guard
fix: handle repeated compaction epochs in continuation guard
2026-03-25 00:30:54 +09:00
YeonGyu-Kim
971912e065 fix(webfetch): avoid rewriting successful redirect content 2026-03-24 23:59:57 +09:00
YeonGyu-Kim
af301ab29a fix(webfetch): guard redirect loops in built-in flow 2026-03-24 23:58:53 +09:00
YeonGyu-Kim
984464470c fix(task): preserve restricted agent tools in sync continuation
Restore sync continuation to apply agent tool restrictions after permissive defaults so resumed explore and librarian sessions cannot regain nested delegation. Add regression tests for resumed restricted agents while keeping plan-family continuation behavior intact.

Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent)

Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
2026-03-24 23:54:29 +09:00
YeonGyu-Kim
535ecee318 fix(models): remove stale Gemini 3 Pro references
Keep repo-owned CLI, docs, and test fixtures aligned with current Gemini 3.1 naming while leaving upstream catalog behavior untouched.

Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent)

Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
2026-03-24 23:53:56 +09:00
YeonGyu-Kim
32035d153e fix(config): prefer canonical plugin config filenames
Ensure oh-my-opencode filenames always win over legacy oh-my-openagent files so readers match canonical writer behavior.

Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent)

Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
2026-03-24 20:38:54 +09:00
YeonGyu-Kim
a0649616bf fix(todo-continuation-enforcer): acknowledge compaction epochs during idle
Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent)

Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
2026-03-24 20:36:22 +09:00
YeonGyu-Kim
cb12b286c8 fix(todo-continuation-enforcer): arm compaction epochs on compaction
Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent)

Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
2026-03-24 20:36:22 +09:00
YeonGyu-Kim
8e239e134c fix(todo-continuation-enforcer): make compaction guard epoch-aware
Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent)

Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
2026-03-24 20:36:22 +09:00
YeonGyu-Kim
733676f1a9 fix(todo-continuation-enforcer): add compaction epoch state
Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent)

Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
2026-03-24 20:36:22 +09:00
YeonGyu-Kim
d2e566ba9d fix(preemptive-compaction): mock session history in degradation test
Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent)

Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
2026-03-24 20:36:22 +09:00
YeonGyu-Kim
6da4d2dae0 fix(hashline-edit): scope formatter cache by directory
Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent)

Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
2026-03-24 20:30:16 +09:00
YeonGyu-Kim
3b41191980 fix(background-agent): honor explicit model override in manager
Keep BackgroundManager launch and resume from sending both agent and model so OpenCode does not override configured subagent models. Add launch and resume regressions for the live production path.

Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent)

Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
2026-03-24 20:28:01 +09:00
YeonGyu-Kim
0b614b751c fix(permissions): preserve explicit deny over OmO defaults
Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent)

Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
2026-03-24 20:24:14 +09:00
MoerAI
c56a01c15d fix(tool-execute-before): strip null bytes from bash commands to prevent crash (fixes #2220) 2026-03-24 19:17:05 +09:00
MoerAI
d2d48fc9ff fix(background-task): register fallback chain for background sessions (fixes #2203) 2026-03-24 19:11:13 +09:00
MoerAI
41a43c62fc fix(category-resolver): respect is_unstable_agent config override (fixes #2061) 2026-03-24 19:08:21 +09:00
YeonGyu-Kim
cea8769a7f Merge pull request #2798 from code-yeongyu/fix/2353-model-selection-v2
fix(plugin): persist selected model only for main session
2026-03-24 18:57:50 +09:00
YeonGyu-Kim
7fa2417c42 fix(plugin): persist selected model only for main session
Reuse the stored model only for subsequent main-session messages when the UI provides no model, while preserving first-message behavior, explicit overrides, and subagent isolation.

Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent)

Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
2026-03-24 18:11:27 +09:00
YeonGyu-Kim
4bba924dad Revert "Merge pull request #2797 from code-yeongyu/fix/2353-model-selection-persistence"
This reverts commit e691303919, reversing
changes made to d4aee20743.
2026-03-24 17:59:21 +09:00
YeonGyu-Kim
e691303919 Merge pull request #2797 from code-yeongyu/fix/2353-model-selection-persistence
fix(plugin): preserve selected model across messages
2026-03-24 17:54:34 +09:00
YeonGyu-Kim
d4aee20743 Merge pull request #2794 from code-yeongyu/fix/2775-thinking-block-signatures
fix(thinking-block-validator): reuse signed thinking blocks instead of synthetic placeholders
2026-03-24 17:54:31 +09:00
YeonGyu-Kim
bad70f5e24 fix(plugin): preserve selected model across messages
Reuse the current session's selected model during config-time agent rebuilds when config.model is missing, so desktop sessions do not snap back to the default model after each send.

Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent)

Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
2026-03-24 17:47:08 +09:00
Mou
b9fa2a3ebc fix(background-agent): prevent circuit breaker false positives on flat-format events 2026-03-24 16:35:54 +08:00
YeonGyu-Kim
0e7bd595f8 fix(session-recovery): reuse signed thinking blocks safely
Reuse signed Anthropic thinking blocks only when they can still sort before the target message's parts, otherwise skip recovery instead of reintroducing invalid loops.

Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent)

Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
2026-03-24 17:22:07 +09:00
YeonGyu-Kim
0732cb85f9 fix(thinking-block-validator): reuse signed thinking parts
Preserve prior signed Anthropic thinking blocks instead of creating unsigned synthetic placeholders, and skip injection when no signed block exists.

Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent)

Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
2026-03-24 17:22:07 +09:00
YeonGyu-Kim
500784a9b9 Merge pull request #2790 from code-yeongyu/fix/2666-mcp-schema-sanitization
fix(schema): strip contentEncoding from MCP tool schemas for Gemini (fixes #2200)
2026-03-24 16:24:57 +09:00
YeonGyu-Kim
5e856b4fde fix(schema): strip contentEncoding from MCP tool schemas for Gemini compatibility
The existing normalizeToolArgSchemas only applies to omo plugin tools
(via tool-registry.ts), but MCP server tool schemas bypass this
sanitization entirely. MCP schemas with contentEncoding/contentMediaType
cause Gemini 400 errors.

Add sanitizeJsonSchema() to strip unsupported keywords from MCP tool
inputSchema before serialization in formatMcpCapabilities.

Fixes #2200
Supersedes #2666
2026-03-24 16:24:44 +09:00
YeonGyu-Kim
03dc903e8e Merge pull request #2789 from code-yeongyu/fix/2671-clearSessionState
fix(anthropic-recovery): clear session state after successful summarize (fixes #2225)
2026-03-24 16:23:25 +09:00
YeonGyu-Kim
69d0b23ab6 fix(anthropic-recovery): clear session state after successful summarize and fix timing test
- Add missing clearSessionState() call after successful summarize (line 117)
  Without this, retry state persisted even after success, potentially causing
  unnecessary retries on subsequent compaction events.

- Fix timing-sensitive test: adjust attempt=0 and firstAttemptTime to give
  proper remainingTimeMs buffer for capped delay calculation.

Fixes #2225
Supersedes #2671
2026-03-24 16:23:11 +09:00
YeonGyu-Kim
ee8735cd2c Merge pull request #2788 from code-yeongyu/fix/2670-uiSelectedModel-nullification
fix(agents): preserve uiSelectedModel when agent override has no model (fixes #2351)
2026-03-24 16:22:15 +09:00
YeonGyu-Kim
d8fe61131c fix(agents): preserve uiSelectedModel when agent override has no model
Three agent builder files used falsy checks that incorrectly nullified
uiSelectedModel when override objects existed but had no model set:

- sisyphus-agent.ts: `?.model ?` → `?.model !== undefined ?`
- atlas-agent.ts: `?.model ?` → `?.model !== undefined ?`
- general-agents.ts: `!override?.model` → `override?.model === undefined`

This caused user model selection in web mode to revert to defaults.

Fixes #2351
2026-03-24 16:22:03 +09:00
YeonGyu-Kim
935995d270 Merge pull request #2668 from MoerAI/fix/session-degradation-detection
fix(session): detect post-compaction no-text degradation and trigger recovery (fixes #2232)
2026-03-24 16:21:30 +09:00
YeonGyu-Kim
23d8b88c4a Merge pull request #2669 from MoerAI/fix/atlas-worktree-verification
fix(atlas): use worktree path for git verification when available (fixes #2229)
2026-03-24 16:21:27 +09:00
YeonGyu-Kim
b4285ce565 Merge pull request #2787 from code-yeongyu/fix/review-fixes
fix(permissions): ensure omo permission overrides take precedence over opencode defaults
2026-03-24 16:20:27 +09:00
YeonGyu-Kim
f9d354b63e fix(permissions): ensure omo permission overrides take precedence over opencode defaults
The spread order in applyToolConfig was incorrect - omo's external_directory: 'allow'
was placed BEFORE the config.permission spread, allowing opencode's default 'ask' to
overwrite it. This caused write/edit tools to hang on headless opencode serve sessions
(no TUI to approve permission prompts).

Move omo's permission overrides AFTER the base config spread so they always win.

Fixes write/edit tool hangs when running opencode serve headlessly.
2026-03-24 16:19:56 +09:00
YeonGyu-Kim
370eb945ee Merge pull request #2786 from code-yeongyu/docs/rename-opencode-to-openagent
docs: rename oh-my-opencode to oh-my-openagent
2026-03-24 15:39:00 +09:00
YeonGyu-Kim
6387065e6f docs: rename oh-my-opencode to oh-my-openagent 2026-03-24 15:31:54 +09:00
YeonGyu-Kim
bebdb97c21 Merge pull request #2784 from code-yeongyu/fix/remove-openclaw-hyperlink
docs: remove OpenClaw hyperlink
2026-03-24 13:35:12 +09:00
YeonGyu-Kim
b5e2ead4e1 docs: remove OpenClaw hyperlink from Building in Public 2026-03-24 13:34:57 +09:00
YeonGyu-Kim
91922dae36 Merge pull request #2783 from code-yeongyu/fix/building-in-public-image
docs: add screenshot to Building in Public section
2026-03-24 13:34:14 +09:00
YeonGyu-Kim
cb3d8af995 docs: add screenshot to Building in Public section
Added the actual Discord screenshot showing real-time development
with Jobdori in #building-in-public channel.
2026-03-24 13:34:04 +09:00
YeonGyu-Kim
0fb3e2063a Merge pull request #2782 from code-yeongyu/feat/building-in-public-readme
docs: add Building in Public section to all READMEs
2026-03-24 13:23:46 +09:00
YeonGyu-Kim
b37b877c45 docs: add Building in Public section to all READMEs
- Added TIP box linking to #building-in-public Discord channel
- Mentions Jobdori AI assistant (built on heavily customized OpenClaw)
- Added to all 5 language variants (EN, KO, JA, ZH-CN, RU)
- Positioned above waitlist section for visibility
2026-03-24 13:23:21 +09:00
YeonGyu-Kim
f854246d7f Merge pull request #2772 from MoerAI/fix/custom-model-resolution
fix(delegate-task): trust user-configured category models without fuzzy validation (fixes #2740)
2026-03-24 12:38:22 +09:00
YeonGyu-Kim
f1eaa7bf9b fix(shell): detect csh/tcsh and use setenv syntax (#2769)
fix(non-interactive-env): detect shell type for csh/tcsh env var syntax (fixes #2089)
2026-03-24 12:30:49 +09:00
YeonGyu-Kim
ed9b4a6329 Merge pull request #2780 from code-yeongyu/fix/issues-2741-2648-2779
fix: resolve subagent model override, empty plan completion, deep task refusal (#2741, #2648, #2779)
2026-03-24 10:28:24 +09:00
YeonGyu-Kim
a00a22ac4c fix: remove copy-paste artifacts in hephaestus gpt-5-3-codex prompt
Same issue as gpt.ts and gpt-5-4.ts: duplicated CORRECT block with pipe
characters and duplicated Hard Constraints/Task Scope Clarification sections.
2026-03-24 10:14:53 +09:00
YeonGyu-Kim
8879581fc1 fix: remove copy-paste artifacts in hephaestus GPT prompts
- Remove leading pipe characters (|) from duplicated CORRECT block
- Remove duplicated ## Hard Constraints and ### Task Scope Clarification sections
- Properly place Task Scope Clarification section between CORRECT list and Hard Constraints

Addresses review comments by cubic-dev-ai[bot] on PR #2780
2026-03-24 09:57:30 +09:00
YeonGyu-Kim
230ce835e5 fix: resolve 3 bugs - subagent model override, empty plan completion, deep task refusal
- #2741: Pass inheritedModel as fallback in subagent-resolver when user hasn't
  configured an override, ensuring custom provider models take priority
- #2648: Fix getPlanProgress to treat plans with 0 checkboxes as incomplete
  instead of complete (total > 0 && completed === total)
- #2779: Relax Hephaestus single-task guard to accept multi-step sub-tasks
  from Atlas delegation, only rejecting genuinely independent tasks

Fixes #2741, fixes #2648, fixes #2779
2026-03-24 09:45:11 +09:00
YeonGyu-Kim
10e56badb3 Merge pull request #2776 from code-yeongyu/fix/background-agent-timeout-defaults
fix: stabilize background-agent stale timeout tests (Date.now race condition)
2026-03-24 03:29:35 +09:00
YeonGyu-Kim
cddf78434c Merge pull request #2770 from code-yeongyu/fix/ci-test-timeout
fix: add fetch mock to install test to prevent CI timeout
2026-03-24 03:29:23 +09:00
YeonGyu-Kim
0078b736b9 fix: stabilize stale timeout tests with fixed Date.now()
Tests 'should use default timeout when config not provided' (manager.test.ts)
and 'should use DEFAULT_MESSAGE_STALENESS_TIMEOUT_MS when not configured'
(task-poller.test.ts) failed in CI because Date.now() drifted between
test setup (when creating timestamps like Date.now() - 46*60*1000) and
actual execution inside checkAndInterruptStaleTasks().

On slower CI machines, this drift pushed borderline values across
the threshold, causing tasks that should be stale to remain 'running'.

Fix: Mock Date.now with spyOn to return a fixed time, ensuring
consistent timeout calculations regardless of execution speed.
2026-03-23 22:17:03 +09:00
MoerAI
6d7f69625b fix: update stale timeout test fixtures for new 45/60 min defaults 2026-03-23 21:00:59 +09:00
MoerAI
fda17dd161 fix(background-agent): increase default stale timeouts and improve cancellation messages (fixes #2684) 2026-03-23 20:49:43 +09:00
MoerAI
aaaeb6997c fix(ralph-loop): add semantic completion detection as fallback for natural language (fixes #2489) 2026-03-23 20:46:10 +09:00
MoerAI
c41d6fd912 fix(delegate-task): trust user-configured category models without fuzzy validation (fixes #2740) 2026-03-23 20:39:47 +09:00
MoerAI
2f801f6c28 fix(hooks): add bash-file-read-guard to warn agents against cat/head/tail (fixes #2096) 2026-03-23 20:15:20 +09:00
YeonGyu-Kim
6e9128e060 fix: add fetch mock to install test to prevent CI timeout
The first test case 'non-TUI mode: should show warning but continue when
OpenCode binary not found' was missing a globalThis.fetch mock, causing it
to make a real HTTP request to npm registry via fetchNpmDistTags().
The npm fetch timeout (5s) collided with the test timeout (5s), causing
flaky CI failures.

Added the same fetch mock pattern already used by the other two test cases.
Test runtime dropped from 5000ms+ to ~2ms.
2026-03-23 20:03:45 +09:00
MoerAI
92509d8cfb fix(non-interactive-env): detect shell type for csh/tcsh env var syntax (fixes #2089) 2026-03-23 19:33:54 +09:00
YeonGyu-Kim
331f7ec52b Merge pull request #2768 from code-yeongyu/fix/issue-2117
fix: emit formatter events from hashline-edit tool (fixes #2117)
2026-03-23 18:49:10 +09:00
YeonGyu-Kim
4ba2da7ebb fix: add tests and fix typing for formatter trigger (#2768) 2026-03-23 18:46:44 +09:00
YeonGyu-Kim
f95d3b1ef5 fix: emit formatter events from hashline-edit tool (fixes #2117) 2026-03-23 18:40:27 +09:00
YeonGyu-Kim
d5d7c7dd26 Merge pull request #2767 from code-yeongyu/fix/issue-2742
fix: respect disabled_tools config in agent prompts (fixes #2742)
2026-03-23 18:39:51 +09:00
YeonGyu-Kim
6a56c0e241 Merge pull request #2766 from code-yeongyu/fix/issue-390
fix: trigger compaction before continue after session error recovery (fixes #390)
2026-03-23 18:39:50 +09:00
YeonGyu-Kim
94c234c88c Merge pull request #2765 from code-yeongyu/fix/issue-2024
fix: skip keyword injection for non-OMO agents (fixes #2024)
2026-03-23 18:39:48 +09:00
YeonGyu-Kim
2ab976c511 Merge pull request #2764 from code-yeongyu/fix/issue-2624
fix: add oh-my-openagent.jsonc config file detection (fixes #2624)
2026-03-23 18:39:46 +09:00
YeonGyu-Kim
dc66088483 Merge pull request #2763 from code-yeongyu/fix/issue-2037
fix: respect OPENCODE_DISABLE_CLAUDE_CODE env vars (fixes #2037)
2026-03-23 18:39:45 +09:00
YeonGyu-Kim
67b5f46a7c Merge pull request #2762 from code-yeongyu/fix/issue-2150
fix: clarify Prometheus file permission error message (fixes #2150)
2026-03-23 18:39:43 +09:00
YeonGyu-Kim
0e483d27ac Merge pull request #2761 from code-yeongyu/fix/issue-2729
fix: validate serverUrl port before tmux pane spawn (fixes #2729)
2026-03-23 18:39:41 +09:00
YeonGyu-Kim
f5eaa648e9 fix: respect disabled_tools config in agent prompts (fixes #2742)
- Check disabled_tools for 'question' in tool-config-handler permission logic
- Strip Question tool code examples from Prometheus prompts when disabled
- Pass disabled_tools through prometheus agent config builder pipeline
- Add tests for disabled_tools question permission handling
2026-03-23 18:13:38 +09:00
YeonGyu-Kim
4c4760a4ee fix: trigger compaction before continue after session error recovery (fixes #390) 2026-03-23 18:12:51 +09:00
YeonGyu-Kim
7f20dd6ff5 fix: add oh-my-openagent.jsonc config file detection (fixes #2624) 2026-03-23 18:11:01 +09:00
YeonGyu-Kim
de371be236 fix: skip keyword injection for non-OMO agents (fixes #2024) 2026-03-23 18:10:44 +09:00
YeonGyu-Kim
f3c2138ef4 fix: respect OPENCODE_DISABLE_CLAUDE_CODE env vars (fixes #2037) 2026-03-23 18:10:08 +09:00
YeonGyu-Kim
0810e37240 fix: validate serverUrl port before tmux pane spawn (fixes #2729) 2026-03-23 18:09:31 +09:00
YeonGyu-Kim
a64e364fa6 fix: clarify Prometheus file permission error message (fixes #2150) 2026-03-23 18:07:59 +09:00
MoerAI
f16d55ad95 fix: add errorName-based quota detection and strengthen test coverage 2026-03-23 15:19:09 +09:00
github-actions[bot]
d886ac701f @hunghoang3011 has signed the CLA in code-yeongyu/oh-my-openagent#2758 2026-03-23 04:28:31 +00:00
Philippe Oscar Sanoy
3c49bf3a8c Merge branch 'code-yeongyu:dev' into fix/oauth-discovery-root-fallback 2026-03-23 09:45:54 +08:00
MoerAI
29a7bc2d31 fix(plugin): display friendly name in configuration UI instead of file path (fixes #2644) 2026-03-23 10:41:37 +09:00
MoerAI
11f1d71c93 fix(prometheus): respect agent model override instead of using global opencode.json model (fixes #2693) 2026-03-23 10:36:59 +09:00
MoerAI
62d2704009 fix(runtime-fallback): detect prettified quota errors without HTTP status codes (fixes #2747) 2026-03-23 10:34:22 +09:00
Sami Jawhar
db32bad004 fix(look-at): respect configured multimodal-looker model instead of overriding via dynamic fallback 2026-03-23 01:12:24 +00:00
Sami Jawhar
5777bf9894 fix(atlas): restore agent mismatch guard for subagent boulder continuation (#18681) 2026-03-23 01:04:36 +00:00
github-actions[bot]
30dc50d880 @0xYiliu has signed the CLA in code-yeongyu/oh-my-openagent#2738 2026-03-21 23:05:07 +00:00
github-actions[bot]
b17e633464 @ndaemy has signed the CLA in code-yeongyu/oh-my-openagent#2734 2026-03-21 10:18:31 +00:00
ndaemy
07ea8debdc fix(keyword-detector): remove duplicate separator from ultrawork templates 2026-03-21 19:09:51 +09:00
YeonGyu-Kim
eec268ee42 fix: use find() instead of calls[0] in wakeGateway test to handle background fetch calls 2026-03-21 18:01:39 +09:00
github-actions[bot]
363661c0d6 @whackur has signed the CLA in code-yeongyu/oh-my-openagent#2733 2026-03-21 05:27:27 +00:00
PR Bot
0d52519293 feat: upgrade MiniMax from M2.5 to M2.7 and expand to more agents/categories
- Upgrade minimax-m2.5 → minimax-m2.7 (latest model) across all agents and categories
- Replace minimax-m2.5-free with minimax-m2.7-highspeed (optimized speed variant)
- Expand MiniMax fallback coverage to atlas, sisyphus-junior, writing, and unspecified-low
- Add isMiniMaxModel() detection function in types.ts for model family detection
- Update all tests (58 passing) and documentation
2026-03-21 01:29:53 +08:00
Casey Howard
031503bb8c test(background-agent): add regression tests for spawn budget decrement on task completion
Tests prove rootDescendantCounts is never decremented on task completion,
cancellation, or error — making maxDescendants a lifetime quota instead of
a concurrent-active cap. All 4 tests fail (RED phase) before the fix.

Refs: code-yeongyu/oh-my-openagent#2700
2026-03-20 12:52:06 -04:00
Casey Howard
5986583641 fix(background-agent): decrement spawn budget on task completion, cancellation, error, and interrupt
rootDescendantCounts was incremented on every spawn but never decremented
when tasks reached terminal states (completed, cancelled, error, interrupt,
stale-pruned). This made maxDescendants=50 a session-lifetime quota instead
of its intended semantics as a concurrent-active agent cap.

Fix: add unregisterRootDescendant() in five terminal-state handlers:
- tryCompleteTask(): task completes successfully
- cancelTask(): running task cancelled (wasRunning guard prevents
  double-decrement for pending tasks already handled by
  rollbackPreStartDescendantReservation)
- session.error handler: task errors
- promptAsync catch (startTask): task interrupted on launch
- promptAsync catch (resume): task interrupted on resume
- onTaskPruned callback: stale task pruned (wasPending guard)

Fixes: code-yeongyu/oh-my-openagent#2700
2026-03-20 12:51:21 -04:00
github-actions[bot]
261bbdf4dc @nguyentamdat has signed the CLA in code-yeongyu/oh-my-openagent#2718 2026-03-20 07:34:31 +00:00
Taegeon Alan Go
10eb3a07e0 Merge branch 'dev' into fix/analyze-mode-load-skills-hint 2026-03-20 16:29:13 +09:00
Taegeon Go (Alan)
03feaa0594 fix(analyze-mode): add mandatory load_skills param hint to prevent delegate_task errors 2026-03-20 16:26:17 +09:00
YeonGyu-Kim
8aec4c5cb3 feat(hooks/todo-continuation-enforcer): enhance continuation message with skeptical verification guidance 2026-03-20 16:13:02 +09:00
YeonGyu-Kim
16cbc847ac fix(cli/run): set OPENCODE_CLIENT to 'run' to exclude question tool from registry 2026-03-20 16:12:58 +09:00
YeonGyu-Kim
436ce71dc8 docs(skills/github-triage): fix Phase 1 JSON parsing and large repo handling 2026-03-20 16:12:54 +09:00
MoerAI
3773e370ec fix(runtime-fallback): detect bare 429 rate-limit signals (fixes #2677) 2026-03-20 11:00:00 +09:00
MoerAI
23a30e86f2 fix(windows): resolve symlinked config paths for plugin detection (fixes #2271) 2026-03-20 10:44:19 +09:00
MoerAI
d2d65fbf99 fix(sisyphus): block premature implementation before context is complete (fixes #2274) 2026-03-20 10:16:30 +09:00
MoerAI
0e610a72bc fix(runtime-fallback): make fallback provider selection provider-agnostic (fixes #2303) 2026-03-20 09:53:24 +09:00
github-actions[bot]
d2a49428b9 @tonymfer has signed the CLA in code-yeongyu/oh-my-openagent#2701 2026-03-19 17:14:04 +00:00
Tony Park
04637ff0f1 fix(lsp): wrap initialization config in initializationOptions field
The LSP `initialize` request expects custom server options in the
`initializationOptions` field, but the code was spreading
`this.server.initialization` directly into the root params object.
This caused LSP servers that depend on `initializationOptions`
(like ets-language-server, pyright, etc.) to not receive their
configuration.

Closes #2665

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-20 02:11:54 +09:00
github-actions[bot]
c3b23bf603 @trafgals has signed the CLA in code-yeongyu/oh-my-openagent#2690 2026-03-19 04:22:43 +00:00
YeonGyu-Kim
50094de73e docs: fix remaining AGENTS hook composition text
Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent)

Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
2026-03-19 12:02:52 +09:00
YeonGyu-Kim
3aa2748c04 docs: sync hook counts after continuation hook removal
Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent)

Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
2026-03-19 12:02:52 +09:00
YeonGyu-Kim
ccaf759b6b fix(hooks): remove gpt permission continuation hook
Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent)

Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
2026-03-19 12:02:52 +09:00
YeonGyu-Kim
521a1f76a9 fix(atlas): stop only after 10 consecutive prompt failures
Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent)

Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
2026-03-19 12:02:52 +09:00
github-actions[bot]
490f0f2090 @walioo has signed the CLA in code-yeongyu/oh-my-openagent#2688 2026-03-19 02:35:04 +00:00
YeonGyu-Kim
caf595e727 fix(build-binaries): prevent test imports from triggering binary builds
Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent)

Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
2026-03-19 10:47:33 +09:00
YeonGyu-Kim
1f64a45113 Merge pull request #2620 from code-yeongyu/feat/openclaw-bidirectional
feat: port OpenClaw bidirectional integration from omx
2026-03-19 10:47:07 +09:00
YeonGyu-Kim
9b2dc2189c fix(ralph-loop): detect promise tags in tool_result parts for ulw verification
Oracle's <promise>VERIFIED</promise> arrives as a tool_result part from the
task() tool call, not as a text part. Both detectCompletionInSessionMessages
and collectAssistantText only scanned type=text parts, missing the
verification signal entirely. This caused ulw loops to fail verification
even when Oracle successfully emitted VERIFIED.

Include tool_result parts in promise detection alongside text parts.
Exclude tool_use parts to avoid false positives from instructional text.
2026-03-18 19:09:59 +09:00
MoerAI
071fab1618 fix: match existing codebase session.messages() parameter shape 2026-03-18 19:08:05 +09:00
YeonGyu-Kim
f6c24e42af fix(ralph-loop): detect promise tags in tool_result parts for ulw verification
Oracle's <promise>VERIFIED</promise> arrives as a tool_result part from the
task() tool call, not as a text part. Both detectCompletionInSessionMessages
and collectAssistantText only scanned type=text parts, missing the
verification signal entirely. This caused ulw loops to fail verification
even when Oracle successfully emitted VERIFIED.

Include tool_result parts in promise detection alongside text parts.
Exclude tool_use parts to avoid false positives from instructional text.
2026-03-18 19:03:30 +09:00
YeonGyu-Kim
22fd976eb9 feat(categories): change quick category default model from claude-haiku-4-5 to gpt-5.4-mini
GPT-5.4-mini provides stronger reasoning at comparable speed and cost.
Haiku remains as the next fallback priority in the chain.

Changes:
- DEFAULT_CATEGORIES quick model: anthropic/claude-haiku-4-5 → openai/gpt-5.4-mini
- Fallback chain: gpt-5.4-mini → haiku → gemini-3-flash → minimax-m2.5 → gpt-5-nano
- OpenAI-only catalog: quick uses gpt-5.4-mini directly
- Think-mode: add gpt-5-4-mini and gpt-5-4-nano high variants
- Update all documentation references
2026-03-18 19:03:30 +09:00
YeonGyu-Kim
826284f3d9 Merge pull request #2676 from code-yeongyu/fix/atlas-task-session-review-followup
fix(atlas): address review findings for task session reuse
2026-03-18 18:50:45 +09:00
YeonGyu-Kim
3c7e6a3940 fix(atlas): address review findings for task session reuse 2026-03-18 18:44:42 +09:00
YeonGyu-Kim
33ef4db502 Merge pull request #2640 from HaD0Yun/had0yun/atlas-task-session-reuse
feat(atlas): persist preferred task session reuse
2026-03-18 18:37:16 +09:00
YeonGyu-Kim
458ec06b0e fix: extract question text from questions array per opencode tool schema 2026-03-18 18:27:09 +09:00
YeonGyu-Kim
6b66f69433 feat(gpt-permission-continuation): add context-aware continuation prompts
- Add buildContextualContinuationPrompt to include assistant message context
- Move extractPermissionPhrase to detector module for better separation
- Block continuation injection in subagent sessions
- Update handler to use contextual prompts with last response context
- Add tests for subagent session blocking and contextual prompts
- Update todo coordination test to verify new prompt format

🤖 Generated with assistance of OhMyOpenCode
2026-03-18 17:52:32 +09:00
YeonGyu-Kim
ce8957e1e1 fix(ralph-loop): harden oracle verification flow
Capture oracle verification sessions more reliably and accept parent-session VERIFIED evidence so ULW loops do not retry after successful review.

Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent)

Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
2026-03-18 17:45:59 +09:00
sanoyphilippe
0d96e0d3bc Fix OAuth discovery for servers with non-root resource paths
When the resource URL has a sub-path (e.g. https://mcp.sentry.dev/mcp),
the RFC 8414 path-suffixed well-known URL may not exist. Fall back to
the root well-known URL before giving up.

This matches OpenCode core's behavior and fixes authentication for
servers like Sentry that serve OAuth metadata only at the root path.
2026-03-18 16:45:54 +08:00
MoerAI
a3db64b931 fix: address cubic review — SDK compatibility and race condition fixes 2026-03-18 17:42:17 +09:00
HaD0Yun
8859da5fef fix(atlas): harden task session reuse 2026-03-18 17:31:27 +09:00
YeonGyu-Kim
23c0ff60f2 feat(background-agent): increase default max tool calls to 4000
Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent)

Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
2026-03-18 16:36:55 +09:00
MoerAI
4723319eef fix(atlas): use worktree path for git verification when available (fixes #2229) 2026-03-18 16:23:37 +09:00
MoerAI
b8f3186d65 fix(session): detect post-compaction no-text degradation and trigger recovery (fixes #2232) 2026-03-18 16:13:23 +09:00
YeonGyu-Kim
01e18f8773 chore: remove console.* debug logging from non-CLI source files 2026-03-18 15:29:50 +09:00
YeonGyu-Kim
1669c83782 revert(todo-continuation): remove [TODO-DIAG] console.error debug logging 2026-03-18 15:10:51 +09:00
YeonGyu-Kim
09cfd0b408 diag(todo-continuation): add comprehensive debug logging for session idle handling
Add [TODO-DIAG] console.error statements throughout the todo continuation
enforcer to help diagnose why continuation prompts aren't being injected.

Changes:
- Add session.idle event handler diagnostic in handler.ts
- Add detailed blocking reason logging in idle-event.ts for all gate checks
- Update JSON schema to reflect circuit breaker config changes

🤖 Generated with assistance of [OhMyOpenCode](https://github.com/code-yeongyu/oh-my-opencode)
2026-03-18 14:45:14 +09:00
YeonGyu-Kim
d48ea025f0 refactor(circuit-breaker): replace sliding window with consecutive call detection
Switch background task loop detection from percentage-based sliding window
(80% of 20-call window) to consecutive same-tool counting. Triggers when
same tool signature is called 20+ times in a row; a different tool resets
the counter.
2026-03-18 14:32:27 +09:00
YeonGyu-Kim
c5c7ba4eed perf: pre-compile regex patterns and optimize hot-path string operations
- error-classifier: pre-compile default retry pattern regex
- think-mode/detector: combine multilingual patterns into single regex
- parser: skip redundant toLowerCase on pre-lowered keywords
- edit-operations: use fast arraysEqual instead of JSON comparison
- hash-computation: optimize streaming line extraction with index tracking
2026-03-18 14:19:23 +09:00
YeonGyu-Kim
90aa3a306c perf(hooks,tools): optimize string operations and reduce redundant iterations
- output-renderer, hashline-edit-diff: replace str += with array join (H2)
- auto-slash-command: single-pass Map grouping instead of 6x filter (M1)
- comment-checker: hoist Zod schema to module scope (M2)
- session-last-agent: reverse iterate sorted array instead of sort+reverse (L2)
2026-03-18 14:19:12 +09:00
YeonGyu-Kim
c2f7d059d2 perf(shared): optimize hot-path utilities across plugin
- task-list: replace O(n³) blocker resolution with Map lookup (C4)
- logger: buffer log entries and flush periodically to reduce sync I/O (C5)
- plugin-interface: create chatParamsHandler once at init (H3)
- pattern-matcher: cache compiled RegExp for wildcard matchers (H6)
- file-reference-resolver: use replaceAll instead of split/join (M9)
- connected-providers-cache: add in-memory cache for read operations (L4)
2026-03-18 14:19:00 +09:00
YeonGyu-Kim
7a96a167e6 perf(claude-code-hooks): defer config loading until after disabled check
Move loadClaudeHooksConfig and loadPluginExtendedConfig after isHookDisabled check
in both tool-execute-before and tool-execute-after handlers to skip 5 file reads
per tool call when hooks are disabled (C1)
2026-03-18 14:18:49 +09:00
YeonGyu-Kim
2da19fe608 perf(background-agent): use Set for countedToolPartIDs, cache circuit breaker settings, optimize loop detector
- Replace countedToolPartIDs string[] with Set<string> for O(1) has/add vs O(n) includes/spread (C2)
- Cache resolveCircuitBreakerSettings at manager level to avoid repeated object creation (C3)
- Optimize recordToolCall to avoid full array copy with slice (L1)
2026-03-18 14:18:38 +09:00
YeonGyu-Kim
952bd5338d fix(background-agent): treat non-active session statuses as terminal to prevent parent session hang
Previously, pollRunningTasks() and checkAndInterruptStaleTasks() treated
any non-"idle" session status as "still running", which caused tasks with
terminal statuses like "interrupted" to be skipped indefinitely — both
for completion detection AND stale timeout. This made the parent session
hang forever waiting for an ALL COMPLETE notification that never came.

Extract isActiveSessionStatus() and isTerminalSessionStatus() that
classify session statuses explicitly. Only known active statuses
("busy", "retry", "running") protect tasks from completion/stale checks.
Known terminal statuses ("interrupted") trigger immediate completion.
Unknown statuses fall through to the standard idle/gone path with output
validation as a conservative default.

Introduced by: a0c93816 (2026-02-14), dc370f7f (2026-03-08)
2026-03-18 14:06:23 +09:00
kilhyeonjun
719a58270b fix(shared): respect cached model context limits for Anthropic providers post-GA
After Anthropic's 1M context GA (2026-03-13), the beta header is no
longer sent. The existing detection relied solely on the beta header
to set anthropicContext1MEnabled, causing all Anthropic models to
fall back to the 200K default despite models.dev reporting 1M.

Update resolveActualContextLimit to check per-model cached limits
from provider config (populated from models.dev data) when the
explicit 1M flag is not set. Priority order:
1. Explicit 1M mode (beta header or env var) - all Anthropic models
2. Per-model cached limit from provider config
3. Default 200K fallback

This preserves the #2460 fix (explicit 1M flag always wins over
cached values) while allowing GA models to use their correct limits.

Fixes premature context warnings at 140K and unnecessary compaction
at 156K for opus-4-6 and sonnet-4-6 users without env var workaround.
2026-03-18 12:21:08 +09:00
Ravi Tharuma
71b1f7e807 fix(anthropic-effort): clamp variant against mutable request message 2026-03-17 11:57:56 +01:00
HaD0Yun
8adf6a2c47 fix(atlas): tighten session reuse metadata parsing 2026-03-17 18:14:17 +09:00
HaD0Yun
5c6194372e feat(atlas): persist preferred task session reuse 2026-03-17 17:25:46 +09:00
YeonGyu-Kim
399796cbe4 fix(openclaw): add comment clarifying proc.exited race condition avoidance
cubic identified potential race condition where Bun's proc.exitCode
may be null immediately after stdout closes. Added clarifying
comment that await proc.exited ensures exitCode is set before
checking.

fixes: cubic review on PR #2620
2026-03-17 17:14:52 +09:00
YeonGyu-Kim
77c3ed1a1f chore: remove omx state files and add .omx/ to gitignore 2026-03-17 17:00:29 +09:00
YeonGyu-Kim
82e25c845b fix: address cubic re-review — remove non-existent session.stop event, fix env var fallback test 2026-03-17 17:00:18 +09:00
YeonGyu-Kim
c644930753 Fix OpenClaw review issues 2026-03-16 22:28:54 +09:00
YeonGyu-Kim
b79df5e018 feat: port OpenClaw bidirectional integration from omx
Ports the complete OpenClaw integration system from oh-my-codex:

Outbound (opencode→OpenClaw):
- wakeOpenClaw() fire-and-forget gateway notifications
- HTTP and command gateway dispatchers
- Template variable interpolation
- Config from oh-my-opencode.jsonc (no env gate needed)

Inbound (OpenClaw→opencode):
- Reply listener daemon (Discord/Telegram polling)
- Session registry for message↔tmux pane correlation
- Tmux pane detection, content capture, and text injection
- Input sanitization and rate limiting
- Pane verification before injection

Files:
- src/openclaw/ (types, config, dispatcher, index, reply-listener, session-registry, tmux, daemon)
- src/config/schema/openclaw.ts (Zod v4 schema)
- src/hooks/openclaw.ts (session hook)
- Tests: 12 pass (config + dispatcher)
2026-03-16 21:55:10 +09:00
MoerAI
6455b851b8 fix(config): keep default OpenCode Build agent enabled by default
The default_builder_enabled config defaults to false, which removes
the default OpenCode Build agent on OMO install. This forces users
into the full OMO orchestration for every task, including simple ones
where the lightweight Build agent would be more appropriate.

Changed the default to true so the Build agent remains available
alongside Sisyphus. Users who prefer the previous behavior can set
default_builder_enabled: false in their config.

Fixes #2545
2026-03-16 19:18:46 +09:00
Ravi Tharuma
9346bc8379 fix: clamp variant "max" to "high" for non-Opus Claude models on fallback
When an agent configured with variant: "max" falls back from Opus to
Sonnet (or Haiku), the "max" variant was passed through unchanged.
OpenCode sends this as level: "max" to the Anthropic API, which rejects
it with: level "max" not supported, valid levels: low, medium, high

The anthropic-effort hook previously only handled Opus (inject effort=max)
and skipped all other Claude models. Now it actively clamps "max" → "high"
for non-Opus Claude models and mutates message.variant so OpenCode
doesn't pass the unsupported level to the API.
2026-03-16 07:49:55 +01:00
MoerAI
7e3c36ee03 ci: retrigger CI 2026-03-16 11:08:14 +09:00
MoerAI
11d942f3a2 fix(runtime-fallback): detect Gemini quota errors in session.status retry events
When Gemini returns a quota exhausted error, OpenCode auto-retries and
fires session.status with type='retry'. The extractAutoRetrySignal
function requires BOTH 'retrying in' text AND a quota pattern to match,
but some providers (like Gemini) include only the error text in the
retry message without the 'retrying in' phrase.

Since status.type='retry' already confirms this is a retry event, the
fix adds a fallback check: if extractAutoRetrySignal fails, check the
message directly against RETRYABLE_ERROR_PATTERNS. This ensures quota
errors like 'exhausted your capacity' trigger the fallback chain even
when the retry message format differs from expected.

Fixes #2454
2026-03-16 11:08:14 +09:00
MoerAI
2b6b08345a fix(todo-continuation-enforcer): add plan agent to DEFAULT_SKIP_AGENTS to prevent infinite loop
The todo-continuation-enforcer injects continuation prompts when
sessions go idle with pending todos. When Plan Mode agents (which are
read-only) create todo items, the continuation prompt contradicts
Plan Mode's STRICTLY FORBIDDEN directive, causing an infinite loop
where the agent acknowledges the conflict then goes idle, triggering
another injection.

Adding 'plan' to DEFAULT_SKIP_AGENTS prevents continuation injection
into Plan Mode sessions, matching the same exclusion pattern already
used for prometheus and compaction agents.

Fixes #2526
2026-03-16 11:07:28 +09:00
MoerAI
abdd39da00 fix(agent-registration): always attempt fallback when model resolution fails
Removes both the isFirstRunNoCache and override?.model guards from
the fallback logic in collectPendingBuiltinAgents(). Previously, when
a user configured a model like minimax/MiniMax-M2.5 that wasn't in
availableModels, the agent was silently excluded and --agent Librarian
would crash with 'undefined is not an object'.

Now: if applyModelResolution() fails for ANY reason (cache state,
unavailable model, config merge issue), getFirstFallbackModel() is
always attempted. A log warning is emitted when a user-configured
model couldn't be resolved, making the previously silent failure
visible.

Supersedes #2517
Fixes #2427
2026-03-16 11:06:00 +09:00
Jean Philippe Wan
711aac0f0a fix: preserve atlas handoff on start-work 2026-03-15 19:04:20 -04:00
Ouyang Xingyuan
f2b26e5346 fix(delegate-task): add subagent turn limit and model routing transparency
原因:
- subagent 无最大步数限制,陷入 tool-call 死循环时可无限运行,造成巨额 API 费用
- category 路由将 subagent 静默切换到与父 session 不同的模型,用户完全无感知

改动:
- sync-session-poller: 新增 maxAssistantTurns 参数(默认 300),每检测到新 assistant 消息
  计数一次,超限后调用 abortSyncSession 并返回明确错误信息
- sync-task: task 完成时在返回字符串中显示实际使用的模型;若与父 session 模型不同,
  加 ⚠️ 警告提示用户发生了静默路由

影响:
- 现有行为不变,maxAssistantTurns 为可选参数,默认值 300 远高于正常任务所需轮次
- 修复 #2571:用户一个下午因 Sisyphus-Junior 死循环 + 静默路由到 Gemini 3.1 Pro
  烧掉 $350+,且 OpenCode 显示费用仅为实际的一半
2026-03-15 12:05:42 +08:00
djdembeck
a7a7799b44 fix(agents): add termination criteria to Sisyphus-Junior default 2026-03-12 16:09:51 -05:00
Gujiassh
1e0823a0fc fix(delegate-task): report the real background task id
Keep background task metadata aligned with the background_output contract so callers do not pass a session id where the task manager expects a background task id.

Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-opencode)

Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
2026-03-13 01:25:13 +09:00
Gujiassh
edfa411684 fix(session-manager): match todo filenames exactly
Stop sibling session IDs from colliding in stable JSON storage by requiring an exact todo filename match instead of a substring filter.

Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-opencode)

Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
2026-03-12 19:58:57 +09:00
tc9011
6d8bc95fa6 fix: github copilot model version for Sisyphus agent 2026-03-11 10:34:25 +08:00
韩澍
229c6b0cdb fix(todo-sync): provide default priority to prevent SQLite NOT NULL violation
extractPriority() returns undefined when task metadata has no priority
field, but OpenCode's TodoTable requires priority as NOT NULL. This
causes a silent SQLiteError that prevents all Task→Todo syncing.

Add ?? "medium" fallback so todos always have a valid priority.
2026-03-06 23:28:58 +08:00
Stranmor
3eb97110c6 feat: support file:// URIs in agent prompt field 2026-03-03 03:32:07 +03:00
468 changed files with 65381 additions and 3365 deletions

BIN
.github/assets/building-in-public.png vendored Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 278 KiB

View File

@@ -60,16 +60,33 @@ jobs:
bun test src/features/opencode-skill-loader/loader.test.ts
bun test src/hooks/anthropic-context-window-limit-recovery/recovery-hook.test.ts
bun test src/hooks/anthropic-context-window-limit-recovery/executor.test.ts
# src/shared mock-heavy files (mock.module pollutes connected-providers-cache and legacy-plugin-warning)
bun test src/shared/model-capabilities.test.ts
bun test src/shared/log-legacy-plugin-startup-warning.test.ts
bun test src/shared/model-error-classifier.test.ts
bun test src/shared/opencode-message-dir.test.ts
# session-recovery mock isolation (recover-tool-result-missing mocks ./storage)
bun test src/hooks/session-recovery/recover-tool-result-missing.test.ts
# legacy-plugin-toast mock isolation (hook.test.ts mocks ./auto-migrate)
bun test src/hooks/legacy-plugin-toast/hook.test.ts
- name: Run remaining tests
run: |
# Enumerate subdirectories/files explicitly to EXCLUDE mock-heavy files
# that were already run in isolation above.
# Excluded from src/shared: model-capabilities, log-legacy-plugin-startup-warning, model-error-classifier, opencode-message-dir
# Excluded from src/cli: doctor/formatter.test.ts, doctor/format-default.test.ts
# Excluded from src/tools: call-omo-agent/sync-executor.test.ts, call-omo-agent/session-creator.test.ts, session-manager (all)
# Excluded from src/hooks/anthropic-context-window-limit-recovery: recovery-hook.test.ts, executor.test.ts
# Build src/shared file list excluding mock-heavy files already run in isolation
SHARED_FILES=$(find src/shared -name '*.test.ts' \
! -name 'model-capabilities.test.ts' \
! -name 'log-legacy-plugin-startup-warning.test.ts' \
! -name 'model-error-classifier.test.ts' \
! -name 'opencode-message-dir.test.ts' \
| sort | tr '\n' ' ')
bun test bin script src/config src/mcp src/index.test.ts \
src/agents src/shared \
src/agents $SHARED_FILES \
src/cli/run src/cli/config-manager src/cli/mcp-oauth \
src/cli/index.test.ts src/cli/install.test.ts src/cli/model-fallback.test.ts \
src/cli/config-manager.test.ts \
@@ -82,6 +99,8 @@ jobs:
src/tools/call-omo-agent/background-executor.test.ts \
src/tools/call-omo-agent/subagent-session-creator.test.ts \
src/hooks/anthropic-context-window-limit-recovery/empty-content-recovery-sdk.test.ts src/hooks/anthropic-context-window-limit-recovery/parser.test.ts src/hooks/anthropic-context-window-limit-recovery/pruning-deduplication.test.ts src/hooks/anthropic-context-window-limit-recovery/recovery-deduplication.test.ts src/hooks/anthropic-context-window-limit-recovery/storage.test.ts \
src/hooks/session-recovery/detect-error-type.test.ts src/hooks/session-recovery/index.test.ts src/hooks/session-recovery/recover-empty-content-message-sdk.test.ts src/hooks/session-recovery/resume.test.ts src/hooks/session-recovery/storage \
src/hooks/legacy-plugin-toast/auto-migrate.test.ts \
src/hooks/claude-code-compatibility \
src/hooks/context-injection \
src/hooks/provider-toast \

View File

@@ -56,10 +56,33 @@ jobs:
env:
BUN_INSTALL_ALLOW_SCRIPTS: "@ast-grep/napi"
- name: Validate release inputs
id: validate
env:
INPUT_VERSION: ${{ inputs.version }}
INPUT_DIST_TAG: ${{ inputs.dist_tag }}
run: |
VERSION="$INPUT_VERSION"
DIST_TAG="$INPUT_DIST_TAG"
if ! [[ "$VERSION" =~ ^[0-9]+\.[0-9]+\.[0-9]+(-[0-9A-Za-z]+(\.[0-9A-Za-z]+)*)?$ ]]; then
echo "::error::Invalid version: $VERSION"
exit 1
fi
if [ -n "$DIST_TAG" ] && ! [[ "$DIST_TAG" =~ ^[a-z][a-z0-9-]*$ ]]; then
echo "::error::Invalid dist_tag: $DIST_TAG"
exit 1
fi
echo "version=$VERSION" >> $GITHUB_OUTPUT
echo "dist_tag=$DIST_TAG" >> $GITHUB_OUTPUT
- name: Check if already published
id: check
env:
VERSION: ${{ steps.validate.outputs.version }}
run: |
VERSION="${{ inputs.version }}"
PLATFORM_KEY="${{ matrix.platform }}"
PLATFORM_KEY="${PLATFORM_KEY//-/_}"
@@ -96,15 +119,18 @@ jobs:
- name: Update version in package.json
if: steps.check.outputs.skip != 'true'
env:
VERSION: ${{ steps.validate.outputs.version }}
run: |
VERSION="${{ inputs.version }}"
cd packages/${{ matrix.platform }}
jq --arg v "$VERSION" '.version = $v' package.json > tmp.json && mv tmp.json package.json
- name: Set root package version
if: steps.check.outputs.skip != 'true'
env:
VERSION: ${{ steps.validate.outputs.version }}
run: |
jq --arg v "${{ inputs.version }}" '.version = $v' package.json > tmp.json && mv tmp.json package.json
jq --arg v "$VERSION" '.version = $v' package.json > tmp.json && mv tmp.json package.json
- name: Pre-download baseline compile target
if: steps.check.outputs.skip != 'true' && endsWith(matrix.platform, '-baseline')
@@ -226,11 +252,33 @@ jobs:
matrix:
platform: [darwin-arm64, darwin-x64, darwin-x64-baseline, linux-x64, linux-x64-baseline, linux-arm64, linux-x64-musl, linux-x64-musl-baseline, linux-arm64-musl, windows-x64, windows-x64-baseline]
steps:
- name: Validate release inputs
id: validate
env:
INPUT_VERSION: ${{ inputs.version }}
INPUT_DIST_TAG: ${{ inputs.dist_tag }}
run: |
VERSION="$INPUT_VERSION"
DIST_TAG="$INPUT_DIST_TAG"
if ! [[ "$VERSION" =~ ^[0-9]+\.[0-9]+\.[0-9]+(-[0-9A-Za-z]+(\.[0-9A-Za-z]+)*)?$ ]]; then
echo "::error::Invalid version: $VERSION"
exit 1
fi
if [ -n "$DIST_TAG" ] && ! [[ "$DIST_TAG" =~ ^[a-z][a-z0-9-]*$ ]]; then
echo "::error::Invalid dist_tag: $DIST_TAG"
exit 1
fi
echo "version=$VERSION" >> $GITHUB_OUTPUT
echo "dist_tag=$DIST_TAG" >> $GITHUB_OUTPUT
- name: Check if already published
id: check
env:
VERSION: ${{ steps.validate.outputs.version }}
run: |
VERSION="${{ inputs.version }}"
OC_STATUS=$(curl -s -o /dev/null -w "%{http_code}" "https://registry.npmjs.org/oh-my-opencode-${{ matrix.platform }}/${VERSION}")
OA_STATUS=$(curl -s -o /dev/null -w "%{http_code}" "https://registry.npmjs.org/oh-my-openagent-${{ matrix.platform }}/${VERSION}")
@@ -288,38 +336,38 @@ jobs:
- name: Publish oh-my-opencode-${{ matrix.platform }}
if: steps.check.outputs.skip_opencode != 'true' && steps.download.outcome == 'success'
run: |
cd packages/${{ matrix.platform }}
TAG_ARG=""
if [ -n "${{ inputs.dist_tag }}" ]; then
TAG_ARG="--tag ${{ inputs.dist_tag }}"
fi
npm publish --access public --provenance $TAG_ARG
env:
DIST_TAG: ${{ steps.validate.outputs.dist_tag }}
NODE_AUTH_TOKEN: ${{ secrets.NODE_AUTH_TOKEN }}
NPM_CONFIG_PROVENANCE: true
run: |
cd packages/${{ matrix.platform }}
if [ -n "$DIST_TAG" ]; then
npm publish --access public --provenance --tag "$DIST_TAG"
else
npm publish --access public --provenance
fi
timeout-minutes: 15
- name: Publish oh-my-openagent-${{ matrix.platform }}
if: steps.check.outputs.skip_openagent != 'true' && steps.download.outcome == 'success'
env:
DIST_TAG: ${{ steps.validate.outputs.dist_tag }}
NODE_AUTH_TOKEN: ${{ secrets.NODE_AUTH_TOKEN }}
NPM_CONFIG_PROVENANCE: true
run: |
cd packages/${{ matrix.platform }}
# Rename package for oh-my-openagent
jq --arg name "oh-my-openagent-${{ matrix.platform }}" \
--arg desc "Platform-specific binary for oh-my-openagent (${{ matrix.platform }})" \
'.name = $name | .description = $desc | .bin = {"oh-my-openagent": (.bin | to_entries | .[0].value)}' \
package.json > tmp.json && mv tmp.json package.json
TAG_ARG=""
if [ -n "${{ inputs.dist_tag }}" ]; then
TAG_ARG="--tag ${{ inputs.dist_tag }}"
if [ -n "$DIST_TAG" ]; then
npm publish --access public --provenance --tag "$DIST_TAG"
else
npm publish --access public --provenance
fi
npm publish --access public --provenance $TAG_ARG
env:
NODE_AUTH_TOKEN: ${{ secrets.NODE_AUTH_TOKEN }}
NPM_CONFIG_PROVENANCE: true
timeout-minutes: 15

View File

@@ -57,32 +57,51 @@ jobs:
bun test src/cli/doctor/format-default.test.ts
bun test src/tools/call-omo-agent/sync-executor.test.ts
bun test src/tools/call-omo-agent/session-creator.test.ts
bun test src/tools/session-manager
bun test src/features/opencode-skill-loader/loader.test.ts
bun test src/hooks/anthropic-context-window-limit-recovery/recovery-hook.test.ts
bun test src/hooks/anthropic-context-window-limit-recovery/executor.test.ts
# src/shared mock-heavy files (mock.module pollutes connected-providers-cache and legacy-plugin-warning)
bun test src/shared/model-capabilities.test.ts
bun test src/shared/log-legacy-plugin-startup-warning.test.ts
bun test src/shared/model-error-classifier.test.ts
bun test src/shared/opencode-message-dir.test.ts
# session-recovery mock isolation (recover-tool-result-missing mocks ./storage)
bun test src/hooks/session-recovery/recover-tool-result-missing.test.ts
# legacy-plugin-toast mock isolation (hook.test.ts mocks ./auto-migrate)
bun test src/hooks/legacy-plugin-toast/hook.test.ts
- name: Run remaining tests
run: |
# Enumerate subdirectories/files explicitly to EXCLUDE mock-heavy files
# that were already run in isolation above.
# Excluded from src/shared: model-capabilities, log-legacy-plugin-startup-warning, model-error-classifier, opencode-message-dir
# Excluded from src/cli: doctor/formatter.test.ts, doctor/format-default.test.ts
# Excluded from src/tools: call-omo-agent/sync-executor.test.ts, call-omo-agent/session-creator.test.ts
# Excluded from src/tools: call-omo-agent/sync-executor.test.ts, call-omo-agent/session-creator.test.ts, session-manager (all)
# Excluded from src/hooks/anthropic-context-window-limit-recovery: recovery-hook.test.ts, executor.test.ts
# Excluded from src/tools: call-omo-agent/sync-executor.test.ts, call-omo-agent/session-creator.test.ts
# Build src/shared file list excluding mock-heavy files already run in isolation
SHARED_FILES=$(find src/shared -name '*.test.ts' \
! -name 'model-capabilities.test.ts' \
! -name 'log-legacy-plugin-startup-warning.test.ts' \
! -name 'model-error-classifier.test.ts' \
! -name 'opencode-message-dir.test.ts' \
| sort | tr '\n' ' ')
bun test bin script src/config src/mcp src/index.test.ts \
src/agents src/shared \
src/agents $SHARED_FILES \
src/cli/run src/cli/config-manager src/cli/mcp-oauth \
src/cli/index.test.ts src/cli/install.test.ts src/cli/model-fallback.test.ts \
src/cli/config-manager.test.ts \
src/cli/doctor/runner.test.ts src/cli/doctor/checks \
src/tools/ast-grep src/tools/background-task src/tools/delegate-task \
src/tools/glob src/tools/grep src/tools/interactive-bash \
src/tools/look-at src/tools/lsp src/tools/session-manager \
src/tools/look-at src/tools/lsp \
src/tools/skill src/tools/skill-mcp src/tools/slashcommand src/tools/task \
src/tools/call-omo-agent/background-agent-executor.test.ts \
src/tools/call-omo-agent/background-executor.test.ts \
src/tools/call-omo-agent/subagent-session-creator.test.ts \
src/hooks/anthropic-context-window-limit-recovery/empty-content-recovery-sdk.test.ts src/hooks/anthropic-context-window-limit-recovery/parser.test.ts src/hooks/anthropic-context-window-limit-recovery/pruning-deduplication.test.ts src/hooks/anthropic-context-window-limit-recovery/recovery-deduplication.test.ts src/hooks/anthropic-context-window-limit-recovery/storage.test.ts \
src/hooks/session-recovery/detect-error-type.test.ts src/hooks/session-recovery/index.test.ts src/hooks/session-recovery/recover-empty-content-message-sdk.test.ts src/hooks/session-recovery/resume.test.ts src/hooks/session-recovery/storage \
src/hooks/legacy-plugin-toast/auto-migrate.test.ts \
src/hooks/claude-code-compatibility \
src/hooks/context-injection \
src/hooks/provider-toast \
@@ -148,33 +167,47 @@ jobs:
- name: Calculate version
id: version
env:
RAW_VERSION: ${{ inputs.version }}
BUMP: ${{ inputs.bump }}
run: |
VERSION="${{ inputs.version }}"
VERSION="$RAW_VERSION"
if [ -z "$VERSION" ]; then
PREV=$(curl -s https://registry.npmjs.org/oh-my-opencode/latest | jq -r '.version // "0.0.0"')
BASE="${PREV%%-*}"
IFS='.' read -r MAJOR MINOR PATCH <<< "$BASE"
case "${{ inputs.bump }}" in
case "$BUMP" in
major) VERSION="$((MAJOR+1)).0.0" ;;
minor) VERSION="${MAJOR}.$((MINOR+1)).0" ;;
*) VERSION="${MAJOR}.${MINOR}.$((PATCH+1))" ;;
esac
fi
if ! [[ "$VERSION" =~ ^[0-9]+\.[0-9]+\.[0-9]+(-[0-9A-Za-z]+(\.[0-9A-Za-z]+)*)?$ ]]; then
echo "::error::Invalid version: $VERSION"
exit 1
fi
echo "version=$VERSION" >> $GITHUB_OUTPUT
if [[ "$VERSION" == *"-"* ]]; then
DIST_TAG=$(echo "$VERSION" | cut -d'-' -f2 | cut -d'.' -f1)
DIST_TAG=$(printf '%s' "$VERSION" | cut -d'-' -f2 | cut -d'.' -f1)
if ! [[ "$DIST_TAG" =~ ^[a-z][a-z0-9-]*$ ]]; then
echo "::error::Invalid dist_tag: $DIST_TAG"
exit 1
fi
echo "dist_tag=${DIST_TAG:-next}" >> $GITHUB_OUTPUT
else
echo "dist_tag=" >> $GITHUB_OUTPUT
fi
echo "Version: $VERSION"
- name: Check if already published
id: check
env:
VERSION: ${{ steps.version.outputs.version }}
run: |
VERSION="${{ steps.version.outputs.version }}"
STATUS=$(curl -s -o /dev/null -w "%{http_code}" "https://registry.npmjs.org/oh-my-opencode/${VERSION}")
if [ "$STATUS" = "200" ]; then
echo "skip=true" >> $GITHUB_OUTPUT
@@ -185,15 +218,16 @@ jobs:
- name: Update version
if: steps.check.outputs.skip != 'true'
env:
VERSION: ${{ steps.version.outputs.version }}
run: |
VERSION="${{ steps.version.outputs.version }}"
jq --arg v "$VERSION" '.version = $v' package.json > tmp.json && mv tmp.json package.json
for platform in darwin-arm64 darwin-x64 darwin-x64-baseline linux-x64 linux-x64-baseline linux-arm64 linux-x64-musl linux-x64-musl-baseline linux-arm64-musl windows-x64 windows-x64-baseline; do
jq --arg v "$VERSION" '.version = $v' "packages/${platform}/package.json" > tmp.json
mv tmp.json "packages/${platform}/package.json"
done
jq --arg v "$VERSION" '.optionalDependencies = (.optionalDependencies | to_entries | map(.value = $v) | from_entries)' package.json > tmp.json && mv tmp.json package.json
- name: Build main package
@@ -206,20 +240,22 @@ jobs:
- name: Publish oh-my-opencode
if: steps.check.outputs.skip != 'true'
run: |
TAG_ARG=""
if [ -n "${{ steps.version.outputs.dist_tag }}" ]; then
TAG_ARG="--tag ${{ steps.version.outputs.dist_tag }}"
fi
npm publish --access public --provenance $TAG_ARG
env:
DIST_TAG: ${{ steps.version.outputs.dist_tag }}
NODE_AUTH_TOKEN: ${{ secrets.NODE_AUTH_TOKEN }}
NPM_CONFIG_PROVENANCE: true
run: |
if [ -n "$DIST_TAG" ]; then
npm publish --access public --provenance --tag "$DIST_TAG"
else
npm publish --access public --provenance
fi
- name: Check if oh-my-openagent already published
id: check-openagent
env:
VERSION: ${{ steps.version.outputs.version }}
run: |
VERSION="${{ steps.version.outputs.version }}"
STATUS=$(curl -s -o /dev/null -w "%{http_code}" "https://registry.npmjs.org/oh-my-openagent/${VERSION}")
if [ "$STATUS" = "200" ]; then
echo "skip=true" >> $GITHUB_OUTPUT
@@ -230,9 +266,12 @@ jobs:
- name: Publish oh-my-openagent
if: steps.check-openagent.outputs.skip != 'true'
env:
VERSION: ${{ steps.version.outputs.version }}
DIST_TAG: ${{ steps.version.outputs.dist_tag }}
NODE_AUTH_TOKEN: ${{ secrets.NODE_AUTH_TOKEN }}
NPM_CONFIG_PROVENANCE: true
run: |
VERSION="${{ steps.version.outputs.version }}"
# Update package name, version, and optionalDependencies for oh-my-openagent
jq --arg v "$VERSION" '
.name = "oh-my-openagent" |
@@ -243,39 +282,31 @@ jobs:
from_entries
)
' package.json > tmp.json && mv tmp.json package.json
TAG_ARG=""
if [ -n "${{ steps.version.outputs.dist_tag }}" ]; then
TAG_ARG="--tag ${{ steps.version.outputs.dist_tag }}"
if [ -n "$DIST_TAG" ]; then
npm publish --access public --provenance --tag "$DIST_TAG"
else
npm publish --access public --provenance
fi
npm publish --access public --provenance $TAG_ARG || echo "::warning::oh-my-openagent publish failed"
env:
NODE_AUTH_TOKEN: ${{ secrets.NODE_AUTH_TOKEN }}
NPM_CONFIG_PROVENANCE: true
- name: Restore package.json
if: steps.check-openagent.outputs.skip != 'true'
if: always() && steps.check-openagent.outputs.skip != 'true'
run: |
git checkout -- package.json
trigger-platform:
runs-on: ubuntu-latest
publish-platform:
needs: publish-main
if: inputs.skip_platform != true
steps:
- name: Trigger platform publish workflow
run: |
gh workflow run publish-platform.yml \
--repo ${{ github.repository }} \
--ref ${{ github.ref }} \
-f version=${{ needs.publish-main.outputs.version }} \
-f dist_tag=${{ needs.publish-main.outputs.dist_tag }}
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
uses: ./.github/workflows/publish-platform.yml
with:
version: ${{ needs.publish-main.outputs.version }}
dist_tag: ${{ needs.publish-main.outputs.dist_tag }}
secrets: inherit
release:
runs-on: ubuntu-latest
needs: publish-main
needs: [publish-main, publish-platform]
if: always() && needs.publish-main.result == 'success' && (inputs.skip_platform == true || needs.publish-platform.result == 'success')
steps:
- uses: actions/checkout@v4
with:
@@ -299,13 +330,53 @@ jobs:
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- name: Create GitHub release
- name: Apply release version to source tree
env:
VERSION: ${{ needs.publish-main.outputs.version }}
run: |
jq --arg v "$VERSION" '.version = $v' package.json > tmp.json && mv tmp.json package.json
for platform in darwin-arm64 darwin-x64 darwin-x64-baseline linux-x64 linux-x64-baseline linux-arm64 linux-x64-musl linux-x64-musl-baseline linux-arm64-musl windows-x64 windows-x64-baseline; do
jq --arg v "$VERSION" '.version = $v' "packages/${platform}/package.json" > tmp.json
mv tmp.json "packages/${platform}/package.json"
done
jq --arg v "$VERSION" '.optionalDependencies = (.optionalDependencies | to_entries | map(.value = $v) | from_entries)' package.json > tmp.json && mv tmp.json package.json
- name: Commit version bump
env:
VERSION: ${{ needs.publish-main.outputs.version }}
run: |
git config user.email "github-actions[bot]@users.noreply.github.com"
git config user.name "github-actions[bot]"
git add package.json packages/*/package.json
git diff --cached --quiet || git commit -m "release: v${VERSION}"
- name: Create release tag
env:
VERSION: ${{ needs.publish-main.outputs.version }}
run: |
if git rev-parse "v${VERSION}" >/dev/null 2>&1; then
echo "::error::Tag v${VERSION} already exists"
exit 1
fi
git tag "v${VERSION}"
- name: Push release state
env:
VERSION: ${{ needs.publish-main.outputs.version }}
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
git push origin HEAD
git push origin "v${VERSION}"
- name: Create GitHub release
env:
VERSION: ${{ needs.publish-main.outputs.version }}
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
VERSION="${{ needs.publish-main.outputs.version }}"
gh release view "v${VERSION}" >/dev/null 2>&1 || \
gh release create "v${VERSION}" --title "v${VERSION}" --notes-file /tmp/changelog.md
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- name: Delete draft release
run: gh release delete next --yes 2>/dev/null || true
@@ -314,13 +385,13 @@ jobs:
- name: Merge to master
continue-on-error: true
env:
VERSION: ${{ needs.publish-main.outputs.version }}
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
git config user.name "github-actions[bot]"
git config user.email "github-actions[bot]@users.noreply.github.com"
VERSION="${{ needs.publish-main.outputs.version }}"
git stash --include-untracked || true
git checkout master
git reset --hard "v${VERSION}"
git push -f origin master || echo "::warning::Failed to push to master"
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

View File

@@ -0,0 +1,46 @@
name: Refresh Model Capabilities
on:
schedule:
- cron: "17 4 * * 1"
workflow_dispatch:
permissions:
contents: write
pull-requests: write
jobs:
refresh:
runs-on: ubuntu-latest
if: github.repository == 'code-yeongyu/oh-my-openagent'
steps:
- uses: actions/checkout@v4
- uses: oven-sh/setup-bun@v2
with:
bun-version: latest
- name: Install dependencies
run: bun install
env:
BUN_INSTALL_ALLOW_SCRIPTS: "@ast-grep/napi"
- name: Refresh bundled model capabilities snapshot
run: bun run build:model-capabilities
- name: Validate capability guardrails
run: bun run test:model-capabilities
- name: Create refresh pull request
uses: peter-evans/create-pull-request@v7
with:
commit-message: "chore: refresh model capabilities snapshot"
title: "chore: refresh model capabilities snapshot"
body: |
Automated refresh of `src/generated/model-capabilities.generated.json` from `https://models.dev/api.json`.
This keeps the bundled capability snapshot aligned with upstream model metadata without relying on manual refreshes.
branch: automation/refresh-model-capabilities
delete-branch: true
labels: |
maintenance

1
.gitignore vendored
View File

@@ -36,3 +36,4 @@ test-injection/
notepad.md
oauth-success.html
*.bun-build
.omx/

View File

@@ -79,47 +79,65 @@ Pass `REPO`, `REPORT_DIR`, and `COMMIT_SHA` to every subagent.
---
## Phase 1: Fetch All Open Items
---
<fetch>
Paginate if 500 results returned.
## Phase 1: Fetch All Open Items (CORRECTED)
**IMPORTANT:** `body` and `comments` fields may contain control characters that break jq parsing. Fetch basic metadata first, then fetch full details per-item in subagents.
```bash
ISSUES=$(gh issue list --repo $REPO --state open --limit 500 \
--json number,title,state,createdAt,updatedAt,labels,author,body,comments)
ISSUE_LEN=$(echo "$ISSUES" | jq length)
if [ "$ISSUE_LEN" -eq 500 ]; then
LAST_DATE=$(echo "$ISSUES" | jq -r '.[-1].createdAt')
# Step 1: Fetch basic metadata (without body/comments to avoid JSON parsing issues)
ISSUES_LIST=$(gh issue list --repo $REPO --state open --limit 500 \
--json number,title,labels,author,createdAt)
ISSUE_COUNT=$(echo "$ISSUES_LIST" | jq length)
# Paginate if needed
if [ "$ISSUE_COUNT" -eq 500 ]; then
LAST_DATE=$(echo "$ISSUES_LIST" | jq -r '.[-1].createdAt')
while true; do
PAGE=$(gh issue list --repo $REPO --state open --limit 500 \
--search "created:<$LAST_DATE" \
--json number,title,state,createdAt,updatedAt,labels,author,body,comments)
PAGE_LEN=$(echo "$PAGE" | jq length)
[ "$PAGE_LEN" -eq 0 ] && break
ISSUES=$(echo "[$ISSUES, $PAGE]" | jq -s 'add | unique_by(.number)')
[ "$PAGE_LEN" -lt 500 ] && break
--json number,title,labels,author,createdAt)
PAGE_COUNT=$(echo "$PAGE" | jq length)
[ "$PAGE_COUNT" -eq 0 ] && break
ISSUES_LIST=$(echo "$ISSUES_LIST" "$PAGE" | jq -s '.[0] + .[1] | unique_by(.number)')
ISSUE_COUNT=$(echo "$ISSUES_LIST" | jq length)
[ "$PAGE_COUNT" -lt 500 ] && break
LAST_DATE=$(echo "$PAGE" | jq -r '.[-1].createdAt')
done
fi
PRS=$(gh pr list --repo $REPO --state open --limit 500 \
--json number,title,state,createdAt,updatedAt,labels,author,body,headRefName,baseRefName,isDraft,mergeable,reviewDecision,statusCheckRollup)
PR_LEN=$(echo "$PRS" | jq length)
if [ "$PR_LEN" -eq 500 ]; then
LAST_DATE=$(echo "$PRS" | jq -r '.[-1].createdAt')
# Same for PRs
PRS_LIST=$(gh pr list --repo $REPO --state open --limit 500 \
--json number,title,labels,author,headRefName,baseRefName,isDraft,createdAt)
PR_COUNT=$(echo "$PRS_LIST" | jq length)
if [ "$PR_COUNT" -eq 500 ]; then
LAST_DATE=$(echo "$PRS_LIST" | jq -r '.[-1].createdAt')
while true; do
PAGE=$(gh pr list --repo $REPO --state open --limit 500 \
--search "created:<$LAST_DATE" \
--json number,title,state,createdAt,updatedAt,labels,author,body,headRefName,baseRefName,isDraft,mergeable,reviewDecision,statusCheckRollup)
PAGE_LEN=$(echo "$PAGE" | jq length)
[ "$PAGE_LEN" -eq 0 ] && break
PRS=$(echo "[$PRS, $PAGE]" | jq -s 'add | unique_by(.number)')
[ "$PAGE_LEN" -lt 500 ] && break
--json number,title,labels,author,headRefName,baseRefName,isDraft,createdAt)
PAGE_COUNT=$(echo "$PAGE" | jq length)
[ "$PAGE_COUNT" -eq 0 ] && break
PRS_LIST=$(echo "$PRS_LIST" "$PAGE" | jq -s '.[0] + .[1] | unique_by(.number)')
PR_COUNT=$(echo "$PRS_LIST" | jq length)
[ "$PAGE_COUNT" -lt 500 ] && break
LAST_DATE=$(echo "$PAGE" | jq -r '.[-1].createdAt')
done
fi
echo "Total issues: $ISSUE_COUNT, Total PRs: $PR_COUNT"
```
</fetch>
**LARGE REPOSITORY HANDLING:**
If total items exceeds 50, you MUST process ALL items. Use the pagination code above to fetch every single open issue and PR.
**DO NOT** sample or limit to 50 items - process the entire backlog.
Example: If there are 500 open issues, spawn 500 subagents. If there are 1000 open PRs, spawn 1000 subagents.
**Note:** Background task system will queue excess tasks automatically.
---

View File

@@ -282,6 +282,18 @@ Once all three gates pass:
gh pr merge "$PR_NUMBER" --squash --delete-branch
```
### Sync .sisyphus state back to main repo
Before removing the worktree, copy `.sisyphus/` state back. When `.sisyphus/` is gitignored, files written there during worktree execution are not committed or merged — they would be lost on worktree removal.
```bash
# Sync .sisyphus state from worktree to main repo (preserves task state, plans, notepads)
if [ -d "$WORKTREE_PATH/.sisyphus" ]; then
mkdir -p "$ORIGINAL_DIR/.sisyphus"
cp -r "$WORKTREE_PATH/.sisyphus/"* "$ORIGINAL_DIR/.sisyphus/" 2>/dev/null || true
fi
```
### Clean up the worktree
The worktree served its purpose — remove it to avoid disk bloat:

View File

@@ -4,7 +4,7 @@
## OVERVIEW
OpenCode plugin (npm: `oh-my-opencode`) that extends Claude Code (OpenCode fork) with multi-agent orchestration, 46 lifecycle hooks, 26 tools, skill/command/MCP systems, and Claude Code compatibility. 1268 TypeScript files, 160k LOC.
OpenCode plugin (npm: `oh-my-opencode`) that extends Claude Code (OpenCode fork) with multi-agent orchestration, 48 lifecycle hooks, 26 tools, skill/command/MCP systems, and Claude Code compatibility. 1268 TypeScript files, 160k LOC.
## STRUCTURE
@@ -14,14 +14,14 @@ oh-my-opencode/
│ ├── index.ts # Plugin entry: loadConfig → createManagers → createTools → createHooks → createPluginInterface
│ ├── plugin-config.ts # JSONC multi-level config: user → project → defaults (Zod v4)
│ ├── agents/ # 11 agents (Sisyphus, Hephaestus, Oracle, Librarian, Explore, Atlas, Prometheus, Metis, Momus, Multimodal-Looker, Sisyphus-Junior)
│ ├── hooks/ # 46 hooks across 45 directories + 11 standalone files
│ ├── hooks/ # 48 lifecycle hooks across dedicated modules and standalone files
│ ├── tools/ # 26 tools across 15 directories
│ ├── features/ # 19 feature modules (background-agent, skill-loader, tmux, MCP-OAuth, etc.)
│ ├── shared/ # 95+ utility files in 13 categories
│ ├── config/ # Zod v4 schema system (24 files)
│ ├── cli/ # CLI: install, run, doctor, mcp-oauth (Commander.js)
│ ├── mcp/ # 3 built-in remote MCPs (websearch, context7, grep_app)
│ ├── plugin/ # 8 OpenCode hook handlers + 46 hook composition
│ ├── plugin/ # 8 OpenCode hook handlers + 48 hook composition
│ └── plugin-handlers/ # 6-phase config loading pipeline
├── packages/ # Monorepo: cli-runner, 12 platform binaries
└── local-ignore/ # Dev-only test fixtures
@@ -34,7 +34,7 @@ OhMyOpenCodePlugin(ctx)
├─→ loadPluginConfig() # JSONC parse → project/user merge → Zod validate → migrate
├─→ createManagers() # TmuxSessionManager, BackgroundManager, SkillMcpManager, ConfigHandler
├─→ createTools() # SkillContext + AvailableCategories + ToolRegistry (26 tools)
├─→ createHooks() # 3-tier: Core(37) + Continuation(7) + Skill(2) = 46 hooks
├─→ createHooks() # 3-tier: Core(39) + Continuation(7) + Skill(2) = 48 hooks
└─→ createPluginInterface() # 8 OpenCode hook handlers → PluginInterface
```
@@ -97,7 +97,7 @@ Fields: agents (14 overridable, 21 fields each), categories (8 built-in + custom
- **Test pattern**: Bun test (`bun:test`), co-located `*.test.ts`, given/when/then style (nested describe with `#given`/`#when`/`#then` prefixes)
- **CI test split**: mock-heavy tests run in isolation (separate `bun test` processes), rest in batch
- **Factory pattern**: `createXXX()` for all tools, hooks, agents
- **Hook tiers**: Session (23) → Tool-Guard (10) → Transform (4) → Continuation (7) → Skill (2)
- **Hook tiers**: Session (23) → Tool-Guard (12) → Transform (4) → Continuation (7) → Skill (2)
- **Agent modes**: `primary` (respects UI model) vs `subagent` (own fallback chain) vs `all`
- **Model resolution**: 4-step: override → category-default → provider-fallback → system-default
- **Config format**: JSONC with comments, Zod v4 validation, snake_case keys

View File

@@ -4,6 +4,17 @@
> コアメンテナーのQが負傷したため、今週は Issue/PR への返信とリリースが遅れる可能性があります。
> ご理解とご支援に感謝します。
> [!TIP]
> **Building in Public**
>
> メンテナーが Jobdori を使い、oh-my-opencode をリアルタイムで開発・メンテナンスしています。Jobdori は OpenClaw をベースに大幅カスタマイズされた AI アシスタントです。
> すべての機能開発、修正、Issue トリアージを Discord でライブでご覧いただけます。
>
> [![Building in Public](./.github/assets/building-in-public.png)](https://discord.gg/PUwSMR9XNk)
>
> [**→ #building-in-public で確認する**](https://discord.gg/PUwSMR9XNk)
> [!NOTE]
>
> [![Sisyphus Labs - Sisyphus is the agent that codes like your team.](./.github/assets/sisyphuslabs.png?v=2)](https://sisyphuslabs.ai)
@@ -157,7 +168,7 @@ Read this and tell me why it's not just another boilerplate: https://raw.githubu
**Sisyphus** (`claude-opus-4-6` / **`kimi-k2.5`** / **`glm-5`**) はあなたのメインのオーケストレーターです。計画を立て、専門家に委任し、攻撃的な並列実行でタスクを完了まで推進します。途中で投げ出すことはありません。
**Hephaestus** (`gpt-5.3-codex`) はあなたの自律的なディープワーカーです。レシピではなく、目標を与えてください。手取り足取り教えなくても、コードベースを探索し、パターンを研究し、端から端まで実行します。*正当なる職人 (The Legitimate Craftsman).*
**Hephaestus** (`gpt-5.4`) はあなたの自律的なディープワーカーです。レシピではなく、目標を与えてください。手取り足取り教えなくても、コードベースを探索し、パターンを研究し、端から端まで実行します。*正当なる職人 (The Legitimate Craftsman).*
**Prometheus** (`claude-opus-4-6` / **`kimi-k2.5`** / **`glm-5`**) はあなたの戦略プランナーです。インタビューモードで動作し、コードに触れる前に質問をしてスコープを特定し、詳細な計画を構築します。
@@ -165,7 +176,7 @@ Read this and tell me why it's not just another boilerplate: https://raw.githubu
> Anthropicが[私たちのせいでOpenCodeをブロックしました。](https://x.com/thdxr/status/2010149530486911014) だからこそHephaestusは「正当なる職人 (The Legitimate Craftsman)」と呼ばれているのです。皮肉を込めています。
>
> Opusで最もよく動きますが、Kimi K2.5 + GPT-5.3 Codexの組み合わせだけでも、バニラのClaude Codeを軽く凌駕します。設定は一切不要です。
> Opusで最もよく動きますが、Kimi K2.5 + GPT-5.4の組み合わせだけでも、バニラのClaude Codeを軽く凌駕します。設定は一切不要です。
### エージェントの<E38388><E381AE>ーケストレーション

View File

@@ -4,6 +4,17 @@
> 핵심 메인테이너 Q가 부상을 입어, 이번 주에는 이슈/PR 응답 및 릴리스가 지연될 수 있습니다.
> 양해와 응원에 감사드립니다.
> [!TIP]
> **Building in Public**
>
> 메인테이너가 Jobdori를 통해 oh-my-opencode를 실시간으로 개발하고 있습니다. Jobdori는 OpenClaw를 기반으로 대폭 커스터마이징된 AI 어시스턴트입니다.
> 모든 기능 개발, 버그 수정, 이슈 트리아지를 Discord에서 실시간으로 확인하세요.
>
> [![Building in Public](./.github/assets/building-in-public.png)](https://discord.gg/PUwSMR9XNk)
>
> [**→ #building-in-public에서 확인하기**](https://discord.gg/PUwSMR9XNk)
> [!TIP]
> 저희와 함께 하세요!
>
@@ -151,7 +162,7 @@ Read this and tell me why it's not just another boilerplate: https://raw.githubu
**Sisyphus** (`claude-opus-4-6` / **`kimi-k2.5`** / **`glm-5`**)는 당신의 메인 오케스트레이터입니다. 공격적인 병렬 실행으로 계획을 세우고, 전문가들에게 위임하며, 완료될 때까지 밀어붙입니다. 중간에 포기하는 법이 없습니다.
**Hephaestus** (`gpt-5.3-codex`)는 당신의 자율 딥 워커입니다. 레시피가 아니라 목표를 주세요. 베이비시터 없이 알아서 코드베이스를 탐색하고, 패턴을 연구하며, 끝에서 끝까지 전부 해냅니다. *진정한 장인(The Legitimate Craftsman).*
**Hephaestus** (`gpt-5.4`)는 당신의 자율 딥 워커입니다. 레시피가 아니라 목표를 주세요. 베이비시터 없이 알아서 코드베이스를 탐색하고, 패턴을 연구하며, 끝에서 끝까지 전부 해냅니다. *진정한 장인(The Legitimate Craftsman).*
**Prometheus** (`claude-opus-4-6` / **`kimi-k2.5`** / **`glm-5`**)는 당신의 전략 플래너입니다. 인터뷰 모드로 작동합니다. 코드 한 줄 만지기 전에 질문을 던져 스코프를 파악하고 상세한 계획부터 세웁니다.
@@ -159,7 +170,7 @@ Read this and tell me why it's not just another boilerplate: https://raw.githubu
> Anthropic이 [우리 때문에 OpenCode를 막아버렸습니다.](https://x.com/thdxr/status/2010149530486911014) 그래서 Hephaestus의 별명이 "진정한 장인(The Legitimate Craftsman)"인 겁니다. (어디서 많이 들어본 이름이죠?) 아이러니를 노렸습니다.
>
> Opus에서 제일 잘 돌아가긴 하지만, Kimi K2.5 + GPT-5.3 Codex 조합만으로도 바닐라 Claude Code는 가볍게 바릅니다. 설정도 필요 없습니다.
> Opus에서 제일 잘 돌아가긴 하지만, Kimi K2.5 + GPT-5.4 조합만으로도 바닐라 Claude Code는 가볍게 바릅니다. 설정도 필요 없습니다.
### 에이전트 오케스트레이션

View File

@@ -1,3 +1,13 @@
> [!TIP]
> **Building in Public**
>
> The maintainer builds and maintains oh-my-opencode in real-time with Jobdori, an AI assistant built on a heavily customized fork of OpenClaw.
> Every feature, every fix, every issue triage — live in our Discord.
>
> [![Building in Public](./.github/assets/building-in-public.png)](https://discord.gg/PUwSMR9XNk)
>
> [**→ Watch it happen in #building-in-public**](https://discord.gg/PUwSMR9XNk)
> [!NOTE]
>
> [![Sisyphus Labs - Sisyphus is the agent that codes like your team.](./.github/assets/sisyphuslabs.png?v=2)](https://sisyphuslabs.ai)
@@ -101,6 +111,8 @@ Fetch the installation guide and follow it:
curl -s https://raw.githubusercontent.com/code-yeongyu/oh-my-openagent/refs/heads/dev/docs/guide/installation.md
```
**Note**: Use the published package and binary name `oh-my-opencode`. Inside `opencode.json`, the compatibility layer now prefers the plugin entry `oh-my-openagent`, while legacy `oh-my-opencode` entries still load with a warning. Plugin config files still commonly use `oh-my-opencode.json` or `oh-my-opencode.jsonc`, and both legacy and renamed basenames are recognized during the transition.
---
## Skip This README
@@ -154,7 +166,7 @@ Even only with following subscriptions, ultrawork will work well (this project i
**Sisyphus** (`claude-opus-4-6` / **`kimi-k2.5`** / **`glm-5`** ) is your main orchestrator. He plans, delegates to specialists, and drives tasks to completion with aggressive parallel execution. He does not stop halfway.
**Hephaestus** (`gpt-5.3-codex`) is your autonomous deep worker. Give him a goal, not a recipe. He explores the codebase, researches patterns, and executes end-to-end without hand-holding. *The Legitimate Craftsman.*
**Hephaestus** (`gpt-5.4`) is your autonomous deep worker. Give him a goal, not a recipe. He explores the codebase, researches patterns, and executes end-to-end without hand-holding. *The Legitimate Craftsman.*
**Prometheus** (`claude-opus-4-6` / **`kimi-k2.5`** / **`glm-5`** ) is your strategic planner. Interview mode: it questions, identifies scope, and builds a detailed plan before a single line of code is touched.
@@ -162,7 +174,7 @@ Every agent is tuned to its model's specific strengths. No manual model-juggling
> Anthropic [blocked OpenCode because of us.](https://x.com/thdxr/status/2010149530486911014) That's why Hephaestus is called "The Legitimate Craftsman." The irony is intentional.
>
> We run best on Opus, but Kimi K2.5 + GPT-5.3 Codex already beats vanilla Claude Code. Zero config needed.
> We run best on Opus, but Kimi K2.5 + GPT-5.4 already beats vanilla Claude Code. Zero config needed.
### Agent Orchestration
@@ -263,11 +275,11 @@ To remove oh-my-opencode:
1. **Remove the plugin from your OpenCode config**
Edit `~/.config/opencode/opencode.json` (or `opencode.jsonc`) and remove `"oh-my-opencode"` from the `plugin` array:
Edit `~/.config/opencode/opencode.json` (or `opencode.jsonc`) and remove either `"oh-my-openagent"` or the legacy `"oh-my-opencode"` entry from the `plugin` array:
```bash
# Using jq
jq '.plugin = [.plugin[] | select(. != "oh-my-opencode")]' \
jq '.plugin = [.plugin[] | select(. != "oh-my-openagent" and . != "oh-my-opencode")]' \
~/.config/opencode/opencode.json > /tmp/oc.json && \
mv /tmp/oc.json ~/.config/opencode/opencode.json
```
@@ -275,11 +287,13 @@ To remove oh-my-opencode:
2. **Remove configuration files (optional)**
```bash
# Remove user config
rm -f ~/.config/opencode/oh-my-opencode.json ~/.config/opencode/oh-my-opencode.jsonc
# Remove plugin config files recognized during the compatibility window
rm -f ~/.config/opencode/oh-my-openagent.jsonc ~/.config/opencode/oh-my-openagent.json \
~/.config/opencode/oh-my-opencode.jsonc ~/.config/opencode/oh-my-opencode.json
# Remove project config (if exists)
rm -f .opencode/oh-my-opencode.json .opencode/oh-my-opencode.jsonc
rm -f .opencode/oh-my-openagent.jsonc .opencode/oh-my-openagent.json \
.opencode/oh-my-opencode.jsonc .opencode/oh-my-opencode.json
```
3. **Verify removal**
@@ -304,7 +318,11 @@ See full [Features Documentation](docs/reference/features.md).
- **Claude Code Compatibility**: Full hook system, commands, skills, agents, MCPs
- **Built-in MCPs**: websearch (Exa), context7 (docs), grep_app (GitHub search)
- **Session Tools**: List, read, search, and analyze session history
- **Productivity Features**: Ralph Loop, Todo Enforcer, GPT permission-tail continuation, Comment Checker, Think Mode, and more
- **Productivity Features**: Ralph Loop, Todo Enforcer, Comment Checker, Think Mode, and more
- **Doctor Command**: Built-in diagnostics (`bunx oh-my-opencode doctor`) verify plugin registration, config, models, and environment
- **Model Fallbacks**: `fallback_models` can mix plain model strings with per-fallback object settings in the same array
- **File Prompts**: Load prompts from files with `file://` support in agent configurations
- **Session Recovery**: Automatic recovery from session errors, context window limits, and API failures
- **Model Setup**: Agent-model matching is built into the [Installation Guide](docs/guide/installation.md#step-5-understand-your-model-setup)
## Configuration
@@ -314,14 +332,14 @@ Opinionated defaults, adjustable if you insist.
See [Configuration Documentation](docs/reference/configuration.md).
**Quick Overview:**
- **Config Locations**: `.opencode/oh-my-opencode.jsonc` or `.opencode/oh-my-opencode.json` (project), `~/.config/opencode/oh-my-opencode.jsonc` or `~/.config/opencode/oh-my-opencode.json` (user)
- **Config Locations**: The compatibility layer recognizes both `oh-my-openagent.json[c]` and legacy `oh-my-opencode.json[c]` plugin config files. Existing installs still commonly use the legacy basename.
- **JSONC Support**: Comments and trailing commas supported
- **Agents**: Override models, temperatures, prompts, and permissions for any agent
- **Built-in Skills**: `playwright` (browser automation), `git-master` (atomic commits)
- **Sisyphus Agent**: Main orchestrator with Prometheus (Planner) and Metis (Plan Consultant)
- **Background Tasks**: Configure concurrency limits per provider/model
- **Categories**: Domain-specific task delegation (`visual`, `business-logic`, custom)
- **Hooks**: 25+ built-in hooks, including `gpt-permission-continuation`, all configurable via `disabled_hooks`
- **Hooks**: 25+ built-in hooks, all configurable via `disabled_hooks`
- **MCPs**: Built-in websearch (Exa), context7 (docs), grep_app (GitHub search)
- **LSP**: Full LSP support with refactoring tools
- **Experimental**: Aggressive truncation, auto-resume, and more

View File

@@ -4,6 +4,17 @@
> Ключевой мейнтейнер Q получил травму, поэтому на этой неделе ответы по issue/PR и релизы могут задерживаться.
> Спасибо за терпение и поддержку.
> [!TIP]
> **Building in Public**
>
> Мейнтейнер разрабатывает и поддерживает oh-my-opencode в режиме реального времени с помощью Jobdori — ИИ-ассистента на базе глубоко кастомизированной версии OpenClaw.
> Каждая фича, каждый фикс, каждый триаж issue — в прямом эфире в нашем Discord.
>
> [![Building in Public](./.github/assets/building-in-public.png)](https://discord.gg/PUwSMR9XNk)
>
> [**→ Смотрите в #building-in-public**](https://discord.gg/PUwSMR9XNk)
> [!NOTE]
>
> [![Sisyphus Labs - Sisyphus is the agent that codes like your team.](./.github/assets/sisyphuslabs.png?v=2)](https://sisyphuslabs.ai)
@@ -141,7 +152,7 @@ Read this and tell me why it's not just another boilerplate: https://raw.githubu
**Sisyphus** (`claude-opus-4-6` / **`kimi-k2.5`** / **`glm-5`**) — главный оркестратор. Он планирует, делегирует задачи специалистам и доводит их до завершения с агрессивным параллельным выполнением. Он не останавливается на полпути.
**Hephaestus** (`gpt-5.3-codex`) — автономный глубокий исполнитель. Дайте ему цель, а не рецепт. Он исследует кодовую базу, изучает паттерны и выполняет задачи сквозным образом без лишних подсказок. *Законный Мастер.*
**Hephaestus** (`gpt-5.4`) — автономный глубокий исполнитель. Дайте ему цель, а не рецепт. Он исследует кодовую базу, изучает паттерны и выполняет задачи сквозным образом без лишних подсказок. *Законный Мастер.*
**Prometheus** (`claude-opus-4-6` / **`kimi-k2.5`** / **`glm-5`**) — стратегический планировщик. Режим интервью: задаёт вопросы, определяет объём работ и формирует детальный план до того, как написана хотя бы одна строка кода.
@@ -149,7 +160,7 @@ Read this and tell me why it's not just another boilerplate: https://raw.githubu
> Anthropic [заблокировал OpenCode из-за нас.](https://x.com/thdxr/status/2010149530486911014) Именно поэтому Hephaestus зовётся «Законным Мастером». Ирония намеренная.
>
> Мы работаем лучше всего на Opus, но Kimi K2.5 + GPT-5.3 Codex уже превосходят ванильный Claude Code. Никакой настройки не требуется.
> Мы работаем лучше всего на Opus, но Kimi K2.5 + GPT-5.4 уже превосходят ванильный Claude Code. Никакой настройки не требуется.
### Оркестрация агентов

View File

@@ -4,6 +4,17 @@
> 核心维护者 Q 因受伤,本周 issue/PR 回复和发布可能会延迟。
> 感谢你的耐心与支持。
> [!TIP]
> **Building in Public**
>
> 维护者正在使用 Jobdori 实时开发和维护 oh-my-opencode。Jobdori 是基于 OpenClaw 深度定制的 AI 助手。
> 每个功能开发、每次修复、每次 Issue 分类,都在 Discord 上实时进行。
>
> [![Building in Public](./.github/assets/building-in-public.png)](https://discord.gg/PUwSMR9XNk)
>
> [**→ 在 #building-in-public 频道中查看**](https://discord.gg/PUwSMR9XNk)
> [!NOTE]
>
> [![Sisyphus Labs - Sisyphus is the agent that codes like your team.](./.github/assets/sisyphuslabs.png?v=2)](https://sisyphuslabs.ai)
@@ -158,7 +169,7 @@ Read this and tell me why it's not just another boilerplate: https://raw.githubu
**Sisyphus** (`claude-opus-4-6` / **`kimi-k2.5`** / **`glm-5`**) 是你的主指挥官。他负责制定计划、分配任务给专家团队,并以极其激进的并行策略推动任务直至完成。他从不半途而废。
**Hephaestus** (`gpt-5.3-codex`) 是你的自主深度工作者。你只需要给他目标,不要给他具体做法。他会自动探索代码库模式,从头到尾独立执行任务,绝不会中途要你当保姆。*名副其实的正牌工匠。*
**Hephaestus** (`gpt-5.4`) 是你的自主深度工作者。你只需要给他目标,不要给他具体做法。他会自动探索代码库模式,从头到尾独立执行任务,绝不会中途要你当保姆。*名副其实的正牌工匠。*
**Prometheus** (`claude-opus-4-6` / **`kimi-k2.5`** / **`glm-5`**) 是你的战略规划师。他通过访谈模式,在动一行代码之前,先通过提问确定范围并构建详尽的执行计划。
@@ -166,7 +177,7 @@ Read this and tell me why it's not just another boilerplate: https://raw.githubu
> Anthropic [因为我们屏蔽了 OpenCode](https://x.com/thdxr/status/2010149530486911014)。这就是为什么我们将 Hephaestus 命名为“正牌工匠 (The Legitimate Craftsman)”。这是一个故意的讽刺。
>
> 我们在 Opus 上运行得最好,但仅仅使用 Kimi K2.5 + GPT-5.3 Codex 就足以碾压原版的 Claude Code。完全不需要配置。
> 我们在 Opus 上运行得最好,但仅仅使用 Kimi K2.5 + GPT-5.4 就足以碾压原版的 Claude Code。完全不需要配置。
### 智能体调度机制

File diff suppressed because it is too large Load Diff

View File

@@ -71,9 +71,19 @@ function getSignalExitCode(signal) {
return 128 + (signalCodeByName[signal] ?? 1);
}
function getPackageBaseName() {
try {
const packageJson = JSON.parse(readFileSync(new URL("../package.json", import.meta.url), "utf8"));
return packageJson.name || "oh-my-opencode";
} catch {
return "oh-my-opencode";
}
}
function main() {
const { platform, arch } = process;
const libcFamily = getLibcFamily();
const packageBaseName = getPackageBaseName();
const avx2Supported = supportsAvx2();
let packageCandidates;
@@ -83,6 +93,7 @@ function main() {
arch,
libcFamily,
preferBaseline: avx2Supported === false,
packageBaseName,
});
} catch (error) {
console.error(`\noh-my-opencode: ${error.message}\n`);

View File

@@ -3,11 +3,11 @@
/**
* Get the platform-specific package name
* @param {{ platform: string, arch: string, libcFamily?: string | null }} options
* @param {{ platform: string, arch: string, libcFamily?: string | null, packageBaseName?: string }} options
* @returns {string} Package name like "oh-my-opencode-darwin-arm64"
* @throws {Error} If libc cannot be detected on Linux
*/
export function getPlatformPackage({ platform, arch, libcFamily }) {
export function getPlatformPackage({ platform, arch, libcFamily, packageBaseName = "oh-my-opencode" }) {
let suffix = "";
if (platform === "linux") {
if (libcFamily === null || libcFamily === undefined) {
@@ -23,13 +23,13 @@ export function getPlatformPackage({ platform, arch, libcFamily }) {
// Map platform names: win32 -> windows (for package name)
const os = platform === "win32" ? "windows" : platform;
return `oh-my-opencode-${os}-${arch}${suffix}`;
return `${packageBaseName}-${os}-${arch}${suffix}`;
}
/** @param {{ platform: string, arch: string, libcFamily?: string | null, preferBaseline?: boolean }} options */
export function getPlatformPackageCandidates({ platform, arch, libcFamily, preferBaseline = false }) {
const primaryPackage = getPlatformPackage({ platform, arch, libcFamily });
const baselinePackage = getBaselinePlatformPackage({ platform, arch, libcFamily });
/** @param {{ platform: string, arch: string, libcFamily?: string | null, preferBaseline?: boolean, packageBaseName?: string }} options */
export function getPlatformPackageCandidates({ platform, arch, libcFamily, preferBaseline = false, packageBaseName = "oh-my-opencode" }) {
const primaryPackage = getPlatformPackage({ platform, arch, libcFamily, packageBaseName });
const baselinePackage = getBaselinePlatformPackage({ platform, arch, libcFamily, packageBaseName });
if (!baselinePackage) {
return [primaryPackage];
@@ -38,18 +38,18 @@ export function getPlatformPackageCandidates({ platform, arch, libcFamily, prefe
return preferBaseline ? [baselinePackage, primaryPackage] : [primaryPackage, baselinePackage];
}
/** @param {{ platform: string, arch: string, libcFamily?: string | null }} options */
function getBaselinePlatformPackage({ platform, arch, libcFamily }) {
/** @param {{ platform: string, arch: string, libcFamily?: string | null, packageBaseName?: string }} options */
function getBaselinePlatformPackage({ platform, arch, libcFamily, packageBaseName = "oh-my-opencode" }) {
if (arch !== "x64") {
return null;
}
if (platform === "darwin") {
return "oh-my-opencode-darwin-x64-baseline";
return `${packageBaseName}-darwin-x64-baseline`;
}
if (platform === "win32") {
return "oh-my-opencode-windows-x64-baseline";
return `${packageBaseName}-windows-x64-baseline`;
}
if (platform === "linux") {
@@ -61,10 +61,10 @@ function getBaselinePlatformPackage({ platform, arch, libcFamily }) {
}
if (libcFamily === "musl") {
return "oh-my-opencode-linux-x64-musl-baseline";
return `${packageBaseName}-linux-x64-musl-baseline`;
}
return "oh-my-opencode-linux-x64-baseline";
return `${packageBaseName}-linux-x64-baseline`;
}
return null;

View File

@@ -190,6 +190,21 @@ describe("getPlatformPackageCandidates", () => {
]);
});
test("supports renamed package family via packageBaseName override", () => {
// #given Linux x64 with glibc and renamed package base
const input = { platform: "linux", arch: "x64", libcFamily: "glibc", packageBaseName: "oh-my-openagent" };
// #when getting package candidates
const result = getPlatformPackageCandidates(input);
// #then returns renamed package family candidates
expect(result).toEqual([
"oh-my-openagent-linux-x64",
"oh-my-openagent-linux-x64-baseline",
]);
});
test("returns only one candidate for ARM64", () => {
// #given non-x64 platform
const input = { platform: "linux", arch: "arm64", libcFamily: "glibc" };

View File

@@ -0,0 +1,88 @@
{
"$schema": "https://raw.githubusercontent.com/code-yeongyu/oh-my-opencode/dev/assets/oh-my-opencode.schema.json",
// Optimized for intensive coding sessions.
// Prioritizes deep implementation agents and fast feedback loops.
"agents": {
// Primary orchestrator: aggressive parallel delegation
"sisyphus": {
"model": "kimi-for-coding/k2p5",
"ultrawork": { "model": "anthropic/claude-opus-4-6", "variant": "max" },
"prompt_append": "Delegate heavily to hephaestus for implementation. Parallelize exploration.",
},
// Heavy lifter: maximum autonomy for coding tasks
"hephaestus": {
"model": "openai/gpt-5.4",
"prompt_append": "You are the primary implementation agent. Own the codebase. Explore, decide, execute. Use LSP and AST-grep aggressively.",
"permission": { "edit": "allow", "bash": { "git": "allow", "test": "allow" } },
},
// Lightweight planner: quick planning for coding tasks
"prometheus": {
"model": "opencode/gpt-5-nano",
"prompt_append": "Keep plans concise. Focus on file structure and key decisions.",
},
// Debugging and architecture
"oracle": { "model": "openai/gpt-5.4", "variant": "high" },
// Fast docs lookup
"librarian": { "model": "github-copilot/grok-code-fast-1" },
// Rapid codebase navigation
"explore": { "model": "github-copilot/grok-code-fast-1" },
// Frontend and visual work
"multimodal-looker": { "model": "google/gemini-3.1-pro" },
// Plan review: minimal overhead
"metis": { "model": "opencode/gpt-5-nano" },
// Code review focus
"momus": { "prompt_append": "Focus on code quality, edge cases, and test coverage." },
// Long-running coding sessions
"atlas": {},
// Quick fixes and small tasks
"sisyphus-junior": { "model": "opencode/gpt-5-nano" },
},
"categories": {
// Trivial changes: fastest possible
"quick": { "model": "opencode/gpt-5-nano" },
// Standard coding tasks: good quality, fast
"unspecified-low": { "model": "anthropic/claude-sonnet-4-6" },
// Complex refactors: best quality
"unspecified-high": { "model": "openai/gpt-5.3-codex" },
// Visual work
"visual-engineering": { "model": "google/gemini-3.1-pro", "variant": "high" },
// Deep autonomous work
"deep": { "model": "openai/gpt-5.3-codex" },
// Architecture decisions
"ultrabrain": { "model": "openai/gpt-5.4", "variant": "xhigh" },
},
// High concurrency for parallel agent work
"background_task": {
"defaultConcurrency": 8,
"providerConcurrency": {
"anthropic": 5,
"openai": 5,
"google": 10,
"github-copilot": 10,
"opencode": 15,
},
},
// Enable all coding aids
"hashline_edit": true,
"experimental": { "aggressive_truncation": true, "task_system": true },
}

View File

@@ -0,0 +1,71 @@
{
"$schema": "https://raw.githubusercontent.com/code-yeongyu/oh-my-opencode/dev/assets/oh-my-opencode.schema.json",
// Balanced defaults for general development.
// Tuned for reliability across diverse tasks without overspending.
"agents": {
// Main orchestrator: handles delegation and drives tasks to completion
"sisyphus": {
"model": "anthropic/claude-opus-4-6",
"ultrawork": { "model": "anthropic/claude-opus-4-6", "variant": "max" },
},
// Deep autonomous worker: end-to-end implementation
"hephaestus": {
"model": "openai/gpt-5.4",
"prompt_append": "Explore thoroughly, then implement. Prefer small, testable changes.",
},
// Strategic planner: interview mode before execution
"prometheus": {
"prompt_append": "Always interview first. Validate scope before planning.",
},
// Architecture consultant: complex design and debugging
"oracle": { "model": "openai/gpt-5.4", "variant": "high" },
// Documentation and code search
"librarian": { "model": "google/gemini-3-flash" },
// Fast codebase exploration
"explore": { "model": "github-copilot/grok-code-fast-1" },
// Visual tasks: UI/UX, images, diagrams
"multimodal-looker": { "model": "google/gemini-3.1-pro" },
// Plan consultant: reviews and improves plans
"metis": {},
// Critic and reviewer
"momus": {},
// Continuation and long-running task handler
"atlas": {},
// Lightweight task executor for simple jobs
"sisyphus-junior": { "model": "opencode/gpt-5-nano" },
},
"categories": {
"quick": { "model": "opencode/gpt-5-nano" },
"unspecified-low": { "model": "anthropic/claude-sonnet-4-6" },
"unspecified-high": { "model": "anthropic/claude-opus-4-6", "variant": "max" },
"writing": { "model": "google/gemini-3-flash" },
"visual-engineering": { "model": "google/gemini-3.1-pro", "variant": "high" },
"deep": { "model": "openai/gpt-5.3-codex" },
"ultrabrain": { "model": "openai/gpt-5.4", "variant": "xhigh" },
},
// Conservative concurrency for cost control
"background_task": {
"providerConcurrency": {
"anthropic": 3,
"openai": 3,
"google": 5,
"opencode": 10,
},
},
"experimental": { "aggressive_truncation": true },
}

View File

@@ -0,0 +1,112 @@
{
"$schema": "https://raw.githubusercontent.com/code-yeongyu/oh-my-opencode/dev/assets/oh-my-opencode.schema.json",
// Optimized for strategic planning, architecture, and complex project design.
// Prioritizes deep thinking agents and thorough analysis before execution.
"agents": {
// Orchestrator: delegates to planning agents first
"sisyphus": {
"model": "anthropic/claude-opus-4-6",
"ultrawork": { "model": "anthropic/claude-opus-4-6", "variant": "max" },
"prompt_append": "Always consult prometheus and atlas for planning. Never rush to implementation.",
},
// Implementation: uses planning outputs
"hephaestus": {
"model": "openai/gpt-5.4",
"prompt_append": "Follow established plans precisely. Ask for clarification when plans are ambiguous.",
},
// Primary planner: deep interview mode
"prometheus": {
"model": "anthropic/claude-opus-4-6",
"thinking": { "type": "enabled", "budgetTokens": 160000 },
"prompt_append": "Interview extensively. Question assumptions. Build exhaustive plans with milestones, risks, and contingencies. Use deep & quick agents heavily in parallel for research.",
},
// Architecture consultant
"oracle": {
"model": "openai/gpt-5.4",
"variant": "xhigh",
"thinking": { "type": "enabled", "budgetTokens": 120000 },
},
// Research and documentation
"librarian": { "model": "google/gemini-3-flash" },
// Exploration for research phase
"explore": { "model": "github-copilot/grok-code-fast-1" },
// Visual planning and diagrams
"multimodal-looker": { "model": "google/gemini-3.1-pro", "variant": "high" },
// Plan review and refinement: heavily utilized
"metis": {
"model": "anthropic/claude-opus-4-6",
"prompt_append": "Critically evaluate plans. Identify gaps, risks, and improvements. Be thorough.",
},
// Critic: challenges assumptions
"momus": {
"model": "openai/gpt-5.4",
"prompt_append": "Challenge all assumptions in plans. Look for edge cases, failure modes, and overlooked requirements.",
},
// Long-running planning sessions
"atlas": {
"prompt_append": "Preserve context across long planning sessions. Track evolving decisions.",
},
// Quick research tasks
"sisyphus-junior": { "model": "opencode/gpt-5-nano" },
},
"categories": {
"quick": { "model": "opencode/gpt-5-nano" },
"unspecified-low": { "model": "anthropic/claude-sonnet-4-6" },
// High-effort planning tasks: maximum reasoning
"unspecified-high": {
"model": "openai/gpt-5.4",
"variant": "xhigh",
},
// Documentation from plans
"writing": { "model": "google/gemini-3-flash" },
// Visual architecture
"visual-engineering": { "model": "google/gemini-3.1-pro", "variant": "high" },
// Deep research and analysis
"deep": { "model": "openai/gpt-5.3-codex" },
// Strategic reasoning
"ultrabrain": { "model": "openai/gpt-5.4", "variant": "xhigh" },
// Creative approaches to problems
"artistry": { "model": "google/gemini-3.1-pro", "variant": "high" },
},
// Moderate concurrency: planning is sequential by nature
"background_task": {
"defaultConcurrency": 5,
"staleTimeoutMs": 300000,
"providerConcurrency": {
"anthropic": 3,
"openai": 3,
},
"modelConcurrency": {
"anthropic/claude-opus-4-6": 2,
"openai/gpt-5.4": 2,
},
},
"sisyphus_agent": {
"planner_enabled": true,
"replace_plan": true,
},
"experimental": { "aggressive_truncation": true },
}

View File

@@ -8,7 +8,7 @@ Think of AI models as developers on a team. Each has a different brain, differen
This isn't a bug. It's the foundation of the entire system.
Oh My OpenCode assigns each agent a model that matches its _working style_ — like building a team where each person is in the role that fits their personality.
Oh My OpenAgent assigns each agent a model that matches its _working style_ — like building a team where each person is in the role that fits their personality.
### Sisyphus: The Sociable Lead
@@ -27,7 +27,7 @@ Using Sisyphus with older GPT models would be like taking your best project mana
Hephaestus is the developer who stays in their room coding all day. Doesn't talk much. Might seem socially awkward. But give them a hard technical problem and they'll emerge three hours later with a solution nobody else could have found.
**This is why Hephaestus uses GPT-5.3 Codex.** Codex is built for exactly this:
**This is why Hephaestus uses GPT-5.4.** GPT-5.4 is built for exactly this:
- Deep, autonomous exploration without hand-holding
- Multi-file reasoning across complex codebases
@@ -64,8 +64,8 @@ These agents have Claude-optimized prompts — long, detailed, mechanics-driven.
| Agent | Role | Fallback Chain | Notes |
| ------------ | ----------------- | -------------------------------------- | ------------------------------------------------------------------------------------------------- |
| **Sisyphus** | Main orchestrator | Claude Opus → opencode-go/kimi-k2.5 → K2P5 → Kimi K2.5 → GPT-5.4 → GLM-5 → Big Pickle | Claude-family first. GPT-5.4 has dedicated prompt support. Kimi available through multiple providers. |
| **Metis** | Plan gap analyzer | Claude Opus → GPT-5.4 → opencode-go/glm-5 → K2P5 | Claude preferred. GPT-5.4 as secondary before GLM-5 fallback. |
| **Sisyphus** | Main orchestrator | anthropic\|github-copilot\|opencode/claude-opus-4-6 (max) → opencode-go/kimi-k2.5 → kimi-for-coding/k2p5 → opencode\|moonshotai\|moonshotai-cn\|firmware\|ollama-cloud\|aihubmix/kimi-k2.5 → openai\|github-copilot\|opencode/gpt-5.4 (medium) → zai-coding-plan\|opencode/glm-5 → opencode/big-pickle | Exact runtime chain from `src/shared/model-requirements.ts`. |
| **Metis** | Plan gap analyzer | anthropic\|github-copilot\|opencode/claude-opus-4-6 (max) → openai\|github-copilot\|opencode/gpt-5.4 (high) → opencode-go/glm-5 → kimi-for-coding/k2p5 | Exact runtime chain from `src/shared/model-requirements.ts`. |
### Dual-Prompt Agents → Claude preferred, GPT supported
@@ -73,8 +73,8 @@ These agents ship separate prompts for Claude and GPT families. They auto-detect
| Agent | Role | Fallback Chain | Notes |
| -------------- | ----------------- | -------------------------------------- | -------------------------------------------------------------------- |
| **Prometheus** | Strategic planner | Claude Opus → GPT-5.4 → opencode-go/glm-5 → Gemini 3.1 Pro | Interview-mode planning. GPT prompt is compact and principle-driven. |
| **Atlas** | Todo orchestrator | Claude Sonnet → opencode-go/kimi-k2.5 → GPT-5.4 | Claude first, opencode-go as intermediate, GPT-5.4 as last resort. |
| **Prometheus** | Strategic planner | anthropic\|github-copilot\|opencode/claude-opus-4-6 (max) → openai\|github-copilot\|opencode/gpt-5.4 (high) → opencode-go/glm-5 → google\|github-copilot\|opencode/gemini-3.1-pro | Exact runtime chain from `src/shared/model-requirements.ts`. |
| **Atlas** | Todo orchestrator | anthropic\|github-copilot\|opencode/claude-sonnet-4-6 → opencode-go/kimi-k2.5 → openai\|github-copilot\|opencode/gpt-5.4 (medium) → opencode-go/minimax-m2.7 | Exact runtime chain from `src/shared/model-requirements.ts`. |
### Deep Specialists → GPT
@@ -82,9 +82,9 @@ These agents are built for GPT's principle-driven style. Their prompts assume au
| Agent | Role | Fallback Chain | Notes |
| -------------- | ----------------------- | -------------------------------------- | ------------------------------------------------ |
| **Hephaestus** | Autonomous deep worker | GPT-5.3 Codex → GPT-5.4 (Copilot) | Requires GPT access. GPT-5.4 via Copilot as fallback. The craftsman. |
| **Oracle** | Architecture consultant | GPT-5.4 → Gemini 3.1 Pro → Claude Opus → opencode-go/glm-5 | Read-only high-IQ consultation. |
| **Momus** | Ruthless reviewer | GPT-5.4 → Claude Opus → Gemini 3.1 Pro → opencode-go/glm-5 | Verification and plan review. GPT-5.4 uses xhigh variant. |
| **Hephaestus** | Autonomous deep worker | GPT-5.4 (medium) | Requires a GPT-capable provider. The craftsman. |
| **Oracle** | Architecture consultant | openai\|github-copilot\|opencode/gpt-5.4 (high) → google\|github-copilot\|opencode/gemini-3.1-pro (high) → anthropic\|github-copilot\|opencode/claude-opus-4-6 (max) → opencode-go/glm-5 | Exact runtime chain from `src/shared/model-requirements.ts`. |
| **Momus** | Ruthless reviewer | openai\|github-copilot\|opencode/gpt-5.4 (xhigh) → anthropic\|github-copilot\|opencode/claude-opus-4-6 (max) → google\|github-copilot\|opencode/gemini-3.1-pro (high) → opencode-go/glm-5 | Exact runtime chain from `src/shared/model-requirements.ts`. |
### Utility Runners → Speed over Intelligence
@@ -92,10 +92,10 @@ These agents do grep, search, and retrieval. They intentionally use the fastest,
| Agent | Role | Fallback Chain | Notes |
| --------------------- | ------------------ | ---------------------------------------------- | ----------------------------------------------------- |
| **Explore** | Fast codebase grep | Grok Code Fast → opencode-go/minimax-m2.5 → MiniMax Free → Haiku → GPT-5-Nano | Speed is everything. Fire 10 in parallel. |
| **Librarian** | Docs/code search | opencode-go/minimax-m2.5MiniMax Free → Haiku → GPT-5-Nano | Doc retrieval doesn't need deep reasoning. |
| **Multimodal Looker** | Vision/screenshots | GPT-5.4 → opencode-go/kimi-k2.5 → GLM-4.6v → GPT-5-Nano | Uses the first available multimodal-capable fallback. |
| **Sisyphus-Junior** | Category executor | Claude Sonnet → opencode-go/kimi-k2.5 → GPT-5.4 → Big Pickle | Handles delegated category tasks. Sonnet-tier default. |
| **Explore** | Fast codebase grep | github-copilot\|xai/grok-code-fast-1 → opencode-go/minimax-m2.7-highspeed → opencode/minimax-m2.7 → anthropic\|opencode/claude-haiku-4-5 → opencode/gpt-5-nano | Exact runtime chain from `src/shared/model-requirements.ts`. |
| **Librarian** | Docs/code search | opencode-go/minimax-m2.7opencode/minimax-m2.7-highspeedanthropic\|opencode/claude-haiku-4-5 → opencode/gpt-5-nano | Exact runtime chain from `src/shared/model-requirements.ts`. |
| **Multimodal Looker** | Vision/screenshots | openai\|opencode/gpt-5.4 (medium) → opencode-go/kimi-k2.5 → zai-coding-plan/glm-4.6v → openai\|github-copilot\|opencode/gpt-5-nano | Exact runtime chain from `src/shared/model-requirements.ts`. |
| **Sisyphus-Junior** | Category executor | anthropic\|github-copilot\|opencode/claude-sonnet-4-6 → opencode-go/kimi-k2.5 → openai\|github-copilot\|opencode/gpt-5.4 (medium) → opencode-go/minimax-m2.7 → opencode/big-pickle | Exact runtime chain from `src/shared/model-requirements.ts`. |
---
@@ -119,8 +119,9 @@ Principle-driven, explicit reasoning, deep technical capability. Best for agents
| Model | Strengths |
| ----------------- | ----------------------------------------------------------------------------------------------- |
| **GPT-5.3 Codex** | Deep coding powerhouse. Autonomous exploration. Required for Hephaestus. |
| **GPT-5.3 Codex** | Deep coding powerhouse. Autonomous exploration. Still available for deep category and explicit overrides. |
| **GPT-5.4** | High intelligence, strategic reasoning. Default for Oracle, Momus, and a key fallback for Prometheus / Atlas. Uses xhigh variant for Momus. |
| **GPT-5.4 Mini** | Fast + strong reasoning. Good for lightweight autonomous tasks. Default for quick category. |
| **GPT-5-Nano** | Ultra-cheap, fast. Good for simple utility tasks. |
### Other Models
@@ -130,7 +131,8 @@ Principle-driven, explicit reasoning, deep technical capability. Best for agents
| **Gemini 3.1 Pro** | Excels at visual/frontend tasks. Different reasoning style. Default for `visual-engineering` and `artistry`. |
| **Gemini 3 Flash** | Fast. Good for doc search and light tasks. |
| **Grok Code Fast 1** | Blazing fast code grep. Default for Explore agent. |
| **MiniMax M2.5** | Fast and smart. Good for utility tasks and search/retrieval. |
| **MiniMax M2.7** | Fast and smart. Used in OpenCode Go and OpenCode Zen utility fallback chains. |
| **MiniMax M2.7 Highspeed** | High-speed OpenCode catalog entry used in utility fallback chains that prefer the fastest available MiniMax path. |
### OpenCode Go
@@ -142,11 +144,12 @@ A premium subscription tier ($10/month) that provides reliable access to Chinese
| ------------------------ | --------------------------------------------------------------------- |
| **opencode-go/kimi-k2.5** | Vision-capable, Claude-like reasoning. Used by Sisyphus, Atlas, Sisyphus-Junior, Multimodal Looker. |
| **opencode-go/glm-5** | Text-only orchestration model. Used by Oracle, Prometheus, Metis, Momus. |
| **opencode-go/minimax-m2.5** | Ultra-cheap, fast responses. Used by Librarian, Explore for utility work. |
| **opencode-go/minimax-m2.7** | Ultra-cheap, fast responses. Used by Librarian, Atlas, and Sisyphus-Junior for utility work. |
| **opencode-go/minimax-m2.7-highspeed** | Even faster OpenCode Go MiniMax entry used by Explore when the high-speed catalog entry is available. |
**When It Gets Used:**
OpenCode Go models appear in fallback chains as intermediate options. They bridge the gap between premium Claude access and free-tier alternatives. The system tries OpenCode Go models before falling back to free tiers (MiniMax Free, Big Pickle) or GPT alternatives.
OpenCode Go models appear throughout the fallback chains as intermediate options. Depending on the agent, they can sit before GPT, after GPT, or act as the last structured-model fallback before cheaper utility paths.
**Go-Only Scenarios:**
@@ -154,7 +157,7 @@ Some model identifiers like `k2p5` (paid Kimi K2.5) and `glm-5` may only be avai
### About Free-Tier Fallbacks
You may see model names like `kimi-k2.5-free`, `minimax-m2.5-free`, or `big-pickle` (GLM 4.6) in the source code or logs. These are free-tier versions of the same model families, served through the OpenCode Zen provider. They exist as lower-priority entries in fallback chains.
You may see model names like `kimi-k2.5-free`, `minimax-m2.7`, `minimax-m2.7-highspeed`, or `big-pickle` (GLM 4.6) in the source code or logs. These are provider-specific or speed-optimized entries in fallback chains.
You don't need to configure them. The system includes them so it degrades gracefully when you don't have every paid subscription. If you have the paid version, the paid version is always preferred.
@@ -166,14 +169,14 @@ When agents delegate work, they don't pick a model name — they pick a **catego
| Category | When Used | Fallback Chain |
| -------------------- | -------------------------- | -------------------------------------------- |
| `visual-engineering` | Frontend, UI, CSS, design | Gemini 3.1 Pro → GLM 5 → Claude Opus → opencode-go/glm-5 → K2P5 |
| `ultrabrain` | Maximum reasoning needed | GPT-5.4 → Gemini 3.1 Pro → Claude Opus → opencode-go/glm-5 |
| `deep` | Deep coding, complex logic | GPT-5.3 Codex → Claude Opus → Gemini 3.1 Pro |
| `artistry` | Creative, novel approaches | Gemini 3.1 Pro → Claude Opus → GPT-5.4 |
| `quick` | Simple, fast tasks | Claude Haiku → Gemini Flash → opencode-go/minimax-m2.5GPT-5-Nano |
| `unspecified-high` | General complex work | Claude Opus → GPT-5.4 → GLM 5 → K2P5 → opencode-go/glm-5 → Kimi K2.5 |
| `unspecified-low` | General standard work | Claude Sonnet → GPT-5.3 Codex → opencode-go/kimi-k2.5 → Gemini Flash |
| `writing` | Text, docs, prose | Gemini Flash → opencode-go/kimi-k2.5 → Claude Sonnet |
| `visual-engineering` | Frontend, UI, CSS, design | google\|github-copilot\|opencode/gemini-3.1-pro (high) → zai-coding-plan\|opencode/glm-5 → anthropic\|github-copilot\|opencode/claude-opus-4-6 (max) → opencode-go/glm-5 → kimi-for-coding/k2p5 |
| `ultrabrain` | Maximum reasoning needed | openai\|opencode/gpt-5.4 (xhigh) → google\|github-copilot\|opencode/gemini-3.1-pro (high) → anthropic\|github-copilot\|opencode/claude-opus-4-6 (max) → opencode-go/glm-5 |
| `deep` | Deep coding, complex logic | openai\|opencode/gpt-5.3-codex (medium) → anthropic\|github-copilot\|opencode/claude-opus-4-6 (max) → google\|github-copilot\|opencode/gemini-3.1-pro (high) |
| `artistry` | Creative, novel approaches | google\|github-copilot\|opencode/gemini-3.1-pro (high) → anthropic\|github-copilot\|opencode/claude-opus-4-6 (max) → openai\|github-copilot\|opencode/gpt-5.4 |
| `quick` | Simple, fast tasks | openai\|github-copilot\|opencode/gpt-5.4-mini → anthropic\|github-copilot\|opencode/claude-haiku-4-5 → google\|github-copilot\|opencode/gemini-3-flash → opencode-go/minimax-m2.7opencode/gpt-5-nano |
| `unspecified-high` | General complex work | anthropic\|github-copilot\|opencode/claude-opus-4-6 (max) → openai\|github-copilot\|opencode/gpt-5.4 (high) → zai-coding-plan\|opencode/glm-5 → kimi-for-coding/k2p5 → opencode-go/glm-5 → opencode/kimi-k2.5 → opencode\|moonshotai\|moonshotai-cn\|firmware\|ollama-cloud\|aihubmix/kimi-k2.5 |
| `unspecified-low` | General standard work | anthropic\|github-copilot\|opencode/claude-sonnet-4-6 → openai\|opencode/gpt-5.3-codex (medium) → opencode-go/kimi-k2.5 → google\|github-copilot\|opencode/gemini-3-flash → opencode-go/minimax-m2.7 |
| `writing` | Text, docs, prose | google\|github-copilot\|opencode/gemini-3-flash → opencode-go/kimi-k2.5 → anthropic\|github-copilot\|opencode/claude-sonnet-4-6 → opencode-go/minimax-m2.7 |
See the [Orchestration System Guide](./orchestration.md) for how agents dispatch tasks to categories.
@@ -253,12 +256,46 @@ Run `opencode models` to see available models, `opencode auth login` to authenti
### How Model Resolution Works
Each agent has a fallback chain. The system tries models in priority order until it finds one available through your connected providers. You don't need to configure providers per model — just authenticate (`opencode auth login`) and the system figures out which models are available and where.
Each agent has a fallback chain. The system tries models in priority order until it finds one available through your connected providers. You don't need to configure providers per model. Just authenticate (`opencode auth login`) and the system figures out which models are available and where.
Core-agent tab cycling is deterministic via injected runtime order field. The fixed priority order is Sisyphus (order: 1), Hephaestus (order: 2), Prometheus (order: 3), and Atlas (order: 4), then the remaining agents follow.
Your explicit configuration always wins. If you set a specific model for an agent, that choice takes precedence even when resolution data is cold.
Variant and `reasoningEffort` overrides are normalized to model-supported values, so cross-provider overrides degrade gracefully instead of failing hard.
Model capabilities are models.dev-backed, with a refreshable cache and capability diagnostics. Use `bunx oh-my-opencode refresh-model-capabilities` to update the cache, or configure `model_capabilities.auto_refresh_on_start` to refresh at startup.
To see which models your agents will actually use, run `bunx oh-my-opencode doctor`. This shows effective model resolution based on your current authentication and config.
```
Agent Request → User Override (if configured) → Fallback Chain → System Default
```
### File-Based Prompts
You can load agent system prompts from external files using `file://` URLs in the `prompt` field, or append additional content with `prompt_append`. The `prompt_append` field also works on categories.
```jsonc
{
"agents": {
"sisyphus": {
"prompt": "file:///path/to/custom-prompt.md"
},
"oracle": {
"prompt_append": "file:///path/to/additional-context.md"
}
},
"categories": {
"deep": {
"prompt_append": "file:///path/to/deep-category-append.md"
}
}
}
```
The file content is loaded at runtime and injected into the agent's system prompt. Supports `~` expansion for home directory and relative `file://` paths.
---
## See Also

View File

@@ -26,6 +26,8 @@ Follow the prompts to configure your Claude, ChatGPT, and Gemini subscriptions.
After you install it, you can read this [overview guide](./overview.md) to understand more.
The published package and local binary are still `oh-my-opencode`. Inside `opencode.json`, the compatibility layer now prefers the plugin entry `oh-my-openagent`, while legacy `oh-my-opencode` entries still load with a warning. Plugin config loading recognizes both `oh-my-openagent.json[c]` and `oh-my-opencode.json[c]` during the transition. If you see a "Using legacy package name" warning from `bunx oh-my-opencode doctor`, update your `opencode.json` plugin entry from `"oh-my-opencode"` to `"oh-my-openagent"`.
## For LLM Agents
> **IMPORTANT: Use `curl` to fetch this file, NOT WebFetch.** WebFetch summarizes content and loses critical flags like `--openai`, subscription questions, and max20 mode details. Always use:
@@ -34,7 +36,7 @@ After you install it, you can read this [overview guide](./overview.md) to under
> curl -fsSL https://raw.githubusercontent.com/code-yeongyu/oh-my-openagent/refs/heads/dev/docs/guide/installation.md
> ```
If you're an LLM Agent helping set up oh-my-opencode, Welcome! You'll now help your user to install the best agent harness, OhMyOpenCode. Tell user this with greetings, with test 'oMoMoMoMo...'
If you're an LLM Agent helping set up oh-my-opencode, welcome. You'll help your user install the harness and verify the rename-compat setup cleanly. Tell user this with greetings, with text 'oMoMoMoMo...'
follow these steps:
@@ -69,11 +71,11 @@ Ask the user these questions to determine CLI options:
- If **no**`--zai-coding-plan=no` (default)
7. **Do you have an OpenCode Go subscription?**
- OpenCode Go is a $10/month subscription providing access to GLM-5, Kimi K2.5, and MiniMax M2.5 models
- OpenCode Go is a $10/month subscription providing access to GLM-5, Kimi K2.5, and MiniMax M2.7 models
- If **yes**`--opencode-go=yes`
- If **no**`--opencode-go=no` (default)
**Provider Priority**: Native (anthropic/, openai/, google/) > Kimi for Coding > GitHub Copilot > OpenCode Go > OpenCode Zen > Z.ai Coding Plan
**Provider selection is agent-specific.** The installer and runtime do not use one single global provider priority. Each agent resolves against its own fallback chain.
MUST STRONGLY WARNING, WHEN USER SAID THEY DON'T HAVE CLAUDE SUBSCRIPTION, SISYPHUS AGENT MIGHT NOT WORK IDEALLY.
@@ -120,8 +122,17 @@ The CLI will:
```bash
opencode --version # Should be 1.0.150 or higher
cat ~/.config/opencode/opencode.json # Should contain "oh-my-opencode" in plugin array
cat ~/.config/opencode/opencode.json # Should contain "oh-my-openagent" in plugin array, or the legacy "oh-my-opencode" entry while you are still migrating
```
#### Run Doctor Verification
After installation, verify everything is working correctly:
```bash
bunx oh-my-opencode doctor
```
This checks system, config, tools, and model resolution, including legacy package name warnings and compatibility-fallback diagnostics.
### Step 4: Configure Authentication
@@ -145,7 +156,7 @@ First, add the opencode-antigravity-auth plugin:
```json
{
"plugin": ["oh-my-opencode", "opencode-antigravity-auth@latest"]
"plugin": ["oh-my-openagent", "opencode-antigravity-auth@latest"]
}
```
@@ -154,9 +165,9 @@ First, add the opencode-antigravity-auth plugin:
You'll also need full model settings in `opencode.json`.
Read the [opencode-antigravity-auth documentation](https://github.com/NoeFabris/opencode-antigravity-auth), copy the full model configuration from the README, and merge carefully to avoid breaking the user's existing setup. The plugin now uses a **variant system** — models like `antigravity-gemini-3-pro` support `low`/`high` variants instead of separate `-low`/`-high` model entries.
##### oh-my-opencode Agent Model Override
##### Plugin config model override
The `opencode-antigravity-auth` plugin uses different model names than the built-in Google auth. Override the agent models in `oh-my-opencode.json` (or `.opencode/oh-my-opencode.json`):
The `opencode-antigravity-auth` plugin uses different model names than the built-in Google auth. Override the agent models in your plugin config file. Existing installs still commonly use `oh-my-opencode.json` or `.opencode/oh-my-opencode.json`, while the compatibility layer also recognizes `oh-my-openagent.json[c]`.
```json
{
@@ -176,7 +187,7 @@ The `opencode-antigravity-auth` plugin uses different model names than the built
**Available models (Gemini CLI quota)**:
- `google/gemini-2.5-flash`, `google/gemini-2.5-pro`, `google/gemini-3-flash-preview`, `google/gemini-3-pro-preview`
- `google/gemini-2.5-flash`, `google/gemini-2.5-pro`, `google/gemini-3-flash-preview`, `google/gemini-3.1-pro-preview`
> **Note**: Legacy tier-suffixed names like `google/antigravity-gemini-3-pro-high` still work but variants are recommended. Use `--variant=high` with the base model name instead.
@@ -201,16 +212,16 @@ GitHub Copilot is supported as a **fallback provider** when native providers are
##### Model Mappings
When GitHub Copilot is the best available provider, oh-my-opencode uses these model assignments:
When GitHub Copilot is the best available provider, install-time defaults are agent-specific. Common examples are:
| Agent | Model |
| ------------- | --------------------------------- |
| **Sisyphus** | `github-copilot/claude-opus-4-6` |
| **Oracle** | `github-copilot/gpt-5.4` |
| **Explore** | `github-copilot/grok-code-fast-1` |
| **Librarian** | `github-copilot/gemini-3-flash` |
| Agent | Model |
| ------------- | ---------------------------------- |
| **Sisyphus** | `github-copilot/claude-opus-4.6` |
| **Oracle** | `github-copilot/gpt-5.4` |
| **Explore** | `github-copilot/grok-code-fast-1` |
| **Atlas** | `github-copilot/claude-sonnet-4.6` |
GitHub Copilot acts as a proxy provider, routing requests to underlying models based on your subscription.
GitHub Copilot acts as a proxy provider, routing requests to underlying models based on your subscription. Some agents, like Librarian, are not installed from Copilot alone and instead rely on other configured providers or runtime fallback behavior.
#### Z.ai Coding Plan
@@ -227,39 +238,33 @@ If Z.ai is your main provider, the most important fallbacks are:
#### OpenCode Zen
OpenCode Zen provides access to `opencode/` prefixed models including `opencode/claude-opus-4-6`, `opencode/gpt-5.4`, `opencode/gpt-5.3-codex`, `opencode/gpt-5-nano`, `opencode/glm-5`, `opencode/big-pickle`, and `opencode/minimax-m2.5-free`.
OpenCode Zen provides access to `opencode/` prefixed models including `opencode/claude-opus-4-6`, `opencode/gpt-5.4`, `opencode/gpt-5.3-codex`, `opencode/gpt-5-nano`, `opencode/glm-5`, `opencode/big-pickle`, `opencode/minimax-m2.7`, and `opencode/minimax-m2.7-highspeed`.
When OpenCode Zen is the best available provider (no native or Copilot), these models are used:
When OpenCode Zen is the best available provider, these are the most relevant source-backed examples:
| Agent | Model |
| ------------- | ---------------------------------------------------- |
| **Sisyphus** | `opencode/claude-opus-4-6` |
| **Oracle** | `opencode/gpt-5.4` |
| **Explore** | `opencode/gpt-5-nano` |
| **Librarian** | `opencode/minimax-m2.5-free` / `opencode/big-pickle` |
| **Explore** | `opencode/minimax-m2.7` |
##### Setup
Run the installer and select "Yes" for GitHub Copilot:
Run the installer and select "Yes" for OpenCode Zen:
```bash
bunx oh-my-opencode install
# Select your subscriptions (Claude, ChatGPT, Gemini)
# When prompted: "Do you have a GitHub Copilot subscription?" → Select "Yes"
# Select your subscriptions (Claude, ChatGPT, Gemini, OpenCode Zen, etc.)
# When prompted: "Do you have access to OpenCode Zen (opencode/ models)?" → Select "Yes"
```
Or use non-interactive mode:
```bash
bunx oh-my-opencode install --no-tui --claude=no --openai=no --gemini=no --copilot=yes
bunx oh-my-opencode install --no-tui --claude=no --openai=no --gemini=no --opencode-zen=yes
```
Then authenticate with GitHub:
```bash
opencode auth login
# Select: GitHub → Authenticate via OAuth
```
This provider uses the `opencode/` model catalog. If your OpenCode environment prompts for provider authentication, follow the OpenCode provider flow for `opencode/` models instead of reusing the fallback-provider auth steps above.
### Step 5: Understand Your Model Setup
@@ -276,7 +281,7 @@ Not all models behave the same way. Understanding which models are "similar" hel
| **Claude Opus 4.6** | anthropic, github-copilot, opencode | Best overall. Default for Sisyphus. |
| **Claude Sonnet 4.6** | anthropic, github-copilot, opencode | Faster, cheaper. Good balance. |
| **Claude Haiku 4.5** | anthropic, opencode | Fast and cheap. Good for quick tasks. |
| **Kimi K2.5** | kimi-for-coding | Behaves very similarly to Claude. Great all-rounder. Default for Atlas. |
| **Kimi K2.5** | kimi-for-coding, opencode-go, opencode, moonshotai, moonshotai-cn, firmware, ollama-cloud, aihubmix | Behaves very similarly to Claude. Great all-rounder that appears in several orchestration fallback chains. |
| **Kimi K2.5 Free** | opencode | Free-tier Kimi. Rate-limited but functional. |
| **GLM 5** | zai-coding-plan, opencode | Claude-like behavior. Good for broad tasks. |
| **Big Pickle (GLM 4.6)** | opencode | Free-tier GLM. Decent fallback. |
@@ -285,27 +290,28 @@ Not all models behave the same way. Understanding which models are "similar" hel
| Model | Provider(s) | Notes |
| ----------------- | -------------------------------- | ------------------------------------------------- |
| **GPT-5.3-codex** | openai, github-copilot, opencode | Deep coding powerhouse. Required for Hephaestus. |
| **GPT-5.3-codex** | openai, github-copilot, opencode | Deep coding powerhouse. Still available for deep category and explicit overrides. |
| **GPT-5.4** | openai, github-copilot, opencode | High intelligence. Default for Oracle. |
| **GPT-5.4 Mini** | openai, github-copilot, opencode | Fast + strong reasoning. Default for quick category. |
| **GPT-5-Nano** | opencode | Ultra-cheap, fast. Good for simple utility tasks. |
**Different-Behavior Models**:
| Model | Provider(s) | Notes |
| --------------------- | -------------------------------- | ----------------------------------------------------------- |
| **Gemini 3 Pro** | google, github-copilot, opencode | Excels at visual/frontend tasks. Different reasoning style. |
| **Gemini 3.1 Pro** | google, github-copilot, opencode | Excels at visual/frontend tasks. Different reasoning style. |
| **Gemini 3 Flash** | google, github-copilot, opencode | Fast, good for doc search and light tasks. |
| **MiniMax M2.5** | venice | Fast and smart. Good for utility tasks. |
| **MiniMax M2.5 Free** | opencode | Free-tier MiniMax. Fast for search/retrieval. |
| **MiniMax M2.7** | opencode-go, opencode | Fast and smart. Utility fallbacks use `minimax-m2.7` or `minimax-m2.7-highspeed` depending on the chain. |
| **MiniMax M2.7 Highspeed** | opencode-go, opencode | Faster utility variant used in Explore and other retrieval-heavy fallback chains. |
**Speed-Focused Models**:
| Model | Provider(s) | Speed | Notes |
| ----------------------- | ---------------------- | -------------- | --------------------------------------------------------------------------------------------------------------------------------------------- |
| **Grok Code Fast 1** | github-copilot, venice | Very fast | Optimized for code grep/search. Default for Explore. |
| **Grok Code Fast 1** | github-copilot, xai | Very fast | Optimized for code grep/search. Default for Explore. |
| **Claude Haiku 4.5** | anthropic, opencode | Fast | Good balance of speed and intelligence. |
| **MiniMax M2.5 (Free)** | opencode, venice | Fast | Smart for its speed class. |
| **GPT-5.3-codex-spark** | openai | Extremely fast | Blazing fast but compacts so aggressively that oh-my-opencode's context management doesn't work well with it. Not recommended for omo agents. |
| **MiniMax M2.7 Highspeed** | opencode-go, opencode | Very fast | High-speed MiniMax utility fallback used by runtime chains such as Explore and, on the OpenCode catalog, Librarian. |
| **GPT-5.3-codex-spark** | openai | Extremely fast | Blazing fast but compacts so aggressively that oh-my-openagent's context management doesn't work well with it. Not recommended for omo agents. |
#### What Each Agent Does and Which Model It Got
@@ -315,8 +321,8 @@ Based on your subscriptions, here's how the agents were configured:
| Agent | Role | Default Chain | What It Does |
| ------------ | ---------------- | ----------------------------------------------- | ---------------------------------------------------------------------------------------- |
| **Sisyphus** | Main ultraworker | Opus (max) → Kimi K2.5 → GLM 5 → Big Pickle | Primary coding agent. Orchestrates everything. **Never use GPT — no GPT prompt exists.** |
| **Metis** | Plan review | Opus (max) → Kimi K2.5 → GPT-5.4 → Gemini 3 Pro | Reviews Prometheus plans for gaps. |
| **Sisyphus** | Main ultraworker | anthropic\|github-copilot\|opencode/claude-opus-4-6 (max) → opencode-go/kimi-k2.5 → kimi-for-coding/k2p5 → opencode\|moonshotai\|moonshotai-cn\|firmware\|ollama-cloud\|aihubmix/kimi-k2.5 → openai\|github-copilot\|opencode/gpt-5.4 (medium) → zai-coding-plan\|opencode/glm-5 → opencode/big-pickle | Primary coding agent. Exact runtime chain from `src/shared/model-requirements.ts`. |
| **Metis** | Plan review | anthropic\|github-copilot\|opencode/claude-opus-4-6 (max) → openai\|github-copilot\|opencode/gpt-5.4 (high) → opencode-go/glm-5 → kimi-for-coding/k2p5 | Reviews Prometheus plans for gaps. Exact runtime chain from `src/shared/model-requirements.ts`. |
**Dual-Prompt Agents** (auto-switch between Claude and GPT prompts):
@@ -326,16 +332,16 @@ Priority: **Claude > GPT > Claude-like models**
| Agent | Role | Default Chain | GPT Prompt? |
| -------------- | ----------------- | ---------------------------------------------------------- | ---------------------------------------------------------------- |
| **Prometheus** | Strategic planner | Opus (max) → **GPT-5.4 (high)** → Kimi K2.5 → Gemini 3 Pro | Yes — XML-tagged, principle-driven (~300 lines vs ~1,100 Claude) |
| **Atlas** | Todo orchestrator | **Kimi K2.5** → Sonnet → GPT-5.4 | Yes GPT-optimized todo management |
| **Prometheus** | Strategic planner | anthropic\|github-copilot\|opencode/claude-opus-4-6 (max) → openai\|github-copilot\|opencode/gpt-5.4 (high) → opencode-go/glm-5 → google\|github-copilot\|opencode/gemini-3.1-pro | Yes — XML-tagged, principle-driven (~300 lines vs ~1,100 Claude) |
| **Atlas** | Todo orchestrator | anthropic\|github-copilot\|opencode/claude-sonnet-4-6 → opencode-go/kimi-k2.5 → openai\|github-copilot\|opencode/gpt-5.4 (medium) → opencode-go/minimax-m2.7 | Yes - GPT-optimized todo management |
**GPT-Native Agents** (built for GPT, don't override to Claude):
| Agent | Role | Default Chain | Notes |
| -------------- | ---------------------- | -------------------------------------- | ------------------------------------------------------ |
| **Hephaestus** | Deep autonomous worker | GPT-5.3-codex (medium) only | "Codex on steroids." No fallback. Requires GPT access. |
| **Oracle** | Architecture/debugging | GPT-5.4 (high) → Gemini 3 Pro → Opus | High-IQ strategic backup. GPT preferred. |
| **Momus** | High-accuracy reviewer | GPT-5.4 (medium) → Opus → Gemini 3 Pro | Verification agent. GPT preferred. |
| **Hephaestus** | Deep autonomous worker | GPT-5.4 (medium) only | "Codex on steroids." No fallback. Requires GPT access. |
| **Oracle** | Architecture/debugging | openai\|github-copilot\|opencode/gpt-5.4 (high) → google\|github-copilot\|opencode/gemini-3.1-pro (high) → anthropic\|github-copilot\|opencode/claude-opus-4-6 (max) → opencode-go/glm-5 | High-IQ strategic backup. GPT preferred. |
| **Momus** | High-accuracy reviewer | openai\|github-copilot\|opencode/gpt-5.4 (xhigh) → anthropic\|github-copilot\|opencode/claude-opus-4-6 (max) → google\|github-copilot\|opencode/gemini-3.1-pro (high) → opencode-go/glm-5 | Verification agent. GPT preferred. |
**Utility Agents** (speed over intelligence):
@@ -343,9 +349,9 @@ These agents do search, grep, and retrieval. They intentionally use fast, cheap
| Agent | Role | Default Chain | Design Rationale |
| --------------------- | ------------------ | ---------------------------------------------------------------------- | -------------------------------------------------------------- |
| **Explore** | Fast codebase grep | MiniMax M2.5 Free → Grok Code Fast → MiniMax M2.5Haiku → GPT-5-Nano | Speed is everything. Grok is blazing fast for grep. |
| **Librarian** | Docs/code search | MiniMax M2.5 Free → Gemini Flash → Big Pickle | Entirely free-tier. Doc retrieval doesn't need deep reasoning. |
| **Multimodal Looker** | Vision/screenshots | Kimi K2.5 → Kimi Free → Gemini Flash → GPT-5.4 → GLM-4.6v | Kimi excels at multimodal understanding. |
| **Explore** | Fast codebase grep | github-copilot\|xai/grok-code-fast-1 → opencode-go/minimax-m2.7-highspeed → opencode/minimax-m2.7anthropic\|opencode/claude-haiku-4-5 → opencode/gpt-5-nano | Speed is everything. Exact runtime chain from `src/shared/model-requirements.ts`. |
| **Librarian** | Docs/code search | opencode-go/minimax-m2.7 → opencode/minimax-m2.7-highspeed → anthropic\|opencode/claude-haiku-4-5 → opencode/gpt-5-nano | Doc retrieval doesn't need deep reasoning. Exact runtime chain from `src/shared/model-requirements.ts`. |
| **Multimodal Looker** | Vision/screenshots | openai\|opencode/gpt-5.4 (medium) → opencode-go/kimi-k2.5 → zai-coding-plan/glm-4.6v → openai\|github-copilot\|opencode/gpt-5-nano | GPT-5.4 now leads the default vision path when available. |
#### Why Different Models Need Different Prompts
@@ -364,7 +370,7 @@ This is why Prometheus and Atlas ship separate prompts per model family — they
#### Custom Model Configuration
If the user wants to override which model an agent uses, you can customize in `oh-my-opencode.json`:
If the user wants to override which model an agent uses, you can customize in your plugin config file. Existing installs still commonly use `oh-my-opencode.json`, while the compatibility layer also recognizes `oh-my-openagent.json[c]`.
```jsonc
{
@@ -399,18 +405,14 @@ GPT (5.3-codex, 5.2) > Claude Opus (decent fallback) > Gemini (acceptable)
**Dangerous** (no prompt support):
- Sisyphus → GPT: **No GPT prompt. Will degrade significantly.**
- Sisyphus → older GPT models: **Still a bad fit. GPT-5.4 is the only dedicated GPT prompt path.**
- Hephaestus → Claude: **Built for Codex. Claude can't replicate this.**
- Explore → Opus: **Massive cost waste. Explore needs speed, not intelligence.**
- Librarian → Opus: **Same. Doc search doesn't need Opus-level reasoning.**
#### Provider Priority Chain
#### Provider Resolution
When multiple providers are available, oh-my-opencode uses this priority:
```
Native (anthropic/, openai/, google/) > Kimi for Coding > GitHub Copilot > Venice > OpenCode Zen > Z.ai Coding Plan
```
There is no single global provider priority. The installer and runtime resolve each agent against its own fallback chain, so the winning provider depends on the agent and the subscriptions you enabled.
### ⚠️ Warning
@@ -424,7 +426,7 @@ Read this document again, think about you have done everything correctly.
#### Say 'Congratulations! 🎉' to the user
Say to user: Congratulations! 🎉 You have successfully set up OhMyOpenCode! Type `opencode` in your terminal to start using it.
Say to user: Congratulations! 🎉 You have successfully set up OhMyOpenAgent! Type `opencode` in your terminal to start using it.
#### Free advertising
@@ -461,3 +463,7 @@ Tell the user of following:
4. You wanna have your own agent- catalog setup? I can read the [docs](docs/guide/agent-model-matching.md) and set up for you after interviewing!
That's it. The agent will figure out the rest and handle everything automatically.
#### Advanced Configuration
You can customize agent models and fallback chains in your config. The `fallback_models` field accepts either a single string or an array that mixes strings and per-model objects with settings like `variant` and `temperature`. See the [Configuration Reference](../reference/configuration.md) and example configs in `docs/examples/` for details.

View File

@@ -1,6 +1,6 @@
# Orchestration System Guide
Oh My OpenCode's orchestration system transforms a simple AI agent into a coordinated development team through **separation of planning and execution**.
Oh My OpenAgent's orchestration system transforms a simple AI agent into a coordinated development team through **separation of planning and execution**.
---
@@ -298,7 +298,7 @@ task({ category: "quick", prompt: "..." }); // "Just get it done fast"
| `visual-engineering` | Gemini 3.1 Pro | Frontend, UI/UX, design, styling, animation |
| `ultrabrain` | GPT-5.4 (xhigh) | Deep logical reasoning, complex architecture decisions |
| `artistry` | Gemini 3.1 Pro (high) | Highly creative or artistic tasks, novel ideas |
| `quick` | Claude Haiku 4.5 | Trivial tasks - single file changes, typo fixes |
| `quick` | GPT-5.4 Mini | Trivial tasks - single file changes, typo fixes |
| `deep` | GPT-5.3 Codex (medium) | Goal-oriented autonomous problem-solving, thorough research |
| `unspecified-low` | Claude Sonnet 4.6 | Tasks that don't fit other categories, low effort |
| `unspecified-high` | Claude Opus 4.6 (max) | Tasks that don't fit other categories, high effort |
@@ -420,7 +420,7 @@ Atlas is automatically activated when you run `/start-work`. You don't need to m
| Aspect | Hephaestus | Sisyphus + `ulw` / `ultrawork` |
| --------------- | ------------------------------------------ | ---------------------------------------------------- |
| **Model** | GPT-5.3 Codex (medium reasoning) | Claude Opus 4.6 / GPT-5.4 / GLM 5 depending on setup |
| **Model** | GPT-5.4 (medium reasoning) | Claude Opus 4.6 / GPT-5.4 / GLM 5 depending on setup |
| **Approach** | Autonomous deep worker | Keyword-activated ultrawork mode |
| **Best For** | Complex architectural work, deep reasoning | General complex tasks, "just do it" scenarios |
| **Planning** | Self-plans during execution | Uses Prometheus plans if available |
@@ -443,8 +443,8 @@ Switch to Hephaestus (Tab → Select Hephaestus) when:
- "Integrate our Rust core with the TypeScript frontend"
- "Migrate from MongoDB to PostgreSQL with zero downtime"
4. **You specifically want GPT-5.3 Codex reasoning**
- Some problems benefit from GPT-5.3 Codex's training characteristics
4. **You specifically want GPT-5.4 reasoning**
- Some problems benefit from GPT-5.4's training characteristics
**When to Use Sisyphus + `ulw`:**
@@ -469,13 +469,13 @@ Use the `ulw` keyword in Sisyphus when:
**Recommendation:**
- **For most users**: Use `ulw` keyword in Sisyphus. It's the default path and works excellently for 90% of complex tasks.
- **For power users**: Switch to Hephaestus when you specifically need GPT-5.3 Codex's reasoning style or want the "AmpCode deep mode" experience of fully autonomous exploration and execution.
- **For power users**: Switch to Hephaestus when you specifically need GPT-5.4's reasoning style or want the "AmpCode deep mode" experience of fully autonomous exploration and execution.
---
## Configuration
You can control related features in `oh-my-opencode.json`:
You can control related features in `oh-my-openagent.json`:
```jsonc
{
@@ -520,7 +520,7 @@ Type `exit` or start a new session. Atlas is primarily entered via `/start-work`
**For most tasks**: Type `ulw` in Sisyphus.
**Use Hephaestus when**: You specifically need GPT-5.3 Codex's reasoning style for deep architectural work or complex debugging.
**Use Hephaestus when**: You specifically need GPT-5.4's reasoning style for deep architectural work or complex debugging.
---

View File

@@ -1,6 +1,6 @@
# What Is Oh My OpenCode?
# What Is Oh My OpenAgent?
Oh My OpenCode is a multi-model agent orchestration harness for OpenCode. It transforms a single AI agent into a coordinated development team that actually ships code.
Oh My OpenAgent is a multi-model agent orchestration harness for OpenCode. It transforms a single AI agent into a coordinated development team that actually ships code.
Not locked to Claude. Not locked to OpenAI. Not locked to anyone.
@@ -15,7 +15,7 @@ Just better results, cheaper models, real orchestration.
Paste this into your LLM agent session:
```
Install and configure oh-my-opencode by following the instructions here:
Install and configure oh-my-openagent by following the instructions here:
https://raw.githubusercontent.com/code-yeongyu/oh-my-openagent/refs/heads/dev/docs/guide/installation.md
```
@@ -41,13 +41,13 @@ We used to call this "Claude Code on steroids." That was wrong.
This isn't about making Claude Code better. It's about breaking free from the idea that one model, one provider, one way of working is enough. Anthropic wants you locked in. OpenAI wants you locked in. Everyone wants you locked in.
Oh My OpenCode doesn't play that game. It orchestrates across models, picking the right brain for the right job. Claude for orchestration. GPT for deep reasoning. Gemini for frontend. Haiku for quick tasks. All working together, automatically.
Oh My OpenAgent doesn't play that game. It orchestrates across models, picking the right brain for the right job. Claude for orchestration. GPT for deep reasoning. Gemini for frontend. GPT-5.4 Mini for quick tasks. All working together, automatically.
---
## How It Works: Agent Orchestration
Instead of one agent doing everything, Oh My OpenCode uses **specialized agents that delegate to each other** based on task type.
Instead of one agent doing everything, Oh My OpenAgent uses **specialized agents that delegate to each other** based on task type.
**The Architecture:**
@@ -93,15 +93,15 @@ Sisyphus still works best on Claude-family models, Kimi, and GLM. GPT-5.4 now ha
Named with intentional irony. Anthropic blocked OpenCode from using their API because of this project. So the team built an autonomous GPT-native agent instead.
Hephaestus runs on GPT-5.3 Codex. Give him a goal, not a recipe. He explores the codebase, researches patterns, and executes end-to-end without hand-holding. He is the legitimate craftsman because he was born from necessity, not privilege.
Hephaestus runs on GPT-5.4. Give him a goal, not a recipe. He explores the codebase, researches patterns, and executes end-to-end without hand-holding. He is the legitimate craftsman because he was born from necessity, not privilege.
Use Hephaestus when you need deep architectural reasoning, complex debugging across many files, or cross-domain knowledge synthesis. Switch to him explicitly when the work demands GPT-5.3 Codex's particular strengths.
Use Hephaestus when you need deep architectural reasoning, complex debugging across many files, or cross-domain knowledge synthesis. Switch to him explicitly when the work demands GPT-5.4's particular strengths.
**Why this beats vanilla Codex CLI:**
- **Multi-model orchestration.** Pure Codex is single-model. OmO routes different tasks to different models automatically. GPT for deep reasoning. Gemini for frontend. Haiku for speed. The right brain for the right job.
- **Multi-model orchestration.** Pure Codex is single-model. OmO routes different tasks to different models automatically. GPT for deep reasoning. Gemini for frontend. GPT-5.4 Mini for speed. The right brain for the right job.
- **Background agents.** Fire 5+ agents in parallel. Something Codex simply cannot do. While one agent writes code, another researches patterns, another checks documentation. Like a real dev team.
- **Category system.** Tasks are routed by intent, not model name. `visual-engineering` gets Gemini. `ultrabrain` gets GPT-5.4. `quick` gets Haiku. No manual juggling.
- **Category system.** Tasks are routed by intent, not model name. `visual-engineering` gets Gemini. `ultrabrain` gets GPT-5.4. `quick` gets GPT-5.4 Mini. No manual juggling.
- **Accumulated wisdom.** Subagents learn from previous results. Conventions discovered in task 1 are passed to task 5. Mistakes made early aren't repeated. The system gets smarter as it works.
### Prometheus: The Strategic Planner
@@ -154,7 +154,7 @@ Use Prometheus for multi-day projects, critical production changes, complex refa
## Agent Model Matching
Different agents work best with different models. Oh My OpenCode automatically assigns optimal models, but you can customize everything.
Different agents work best with different models. Oh My OpenAgent automatically assigns optimal models, but you can customize everything.
### Default Configuration
@@ -168,7 +168,7 @@ You can override specific agents or categories in your config:
```jsonc
{
"$schema": "https://raw.githubusercontent.com/code-yeongyu/oh-my-openagent/dev/assets/oh-my-opencode.schema.json",
"$schema": "https://raw.githubusercontent.com/code-yeongyu/oh-my-openagent/dev/assets/oh-my-openagent.schema.json",
"agents": {
// Main orchestrator: Claude Opus or Kimi K2.5 work best
@@ -195,8 +195,8 @@ You can override specific agents or categories in your config:
// General high-effort work
"unspecified-high": { "model": "anthropic/claude-opus-4-6", "variant": "max" },
// Quick tasks: use the cheapest models
"quick": { "model": "anthropic/claude-haiku-4-5" },
// Quick tasks: use GPT-5.4-mini (fast and cheap)
"quick": { "model": "openai/gpt-5.4-mini" },
// Deep reasoning: GPT-5.4
"ultrabrain": { "model": "openai/gpt-5.4", "variant": "xhigh" },
@@ -214,14 +214,13 @@ You can override specific agents or categories in your config:
**GPT models** (explicit reasoning, principle-driven):
- GPT-5.3-codex — deep coding powerhouse, required for Hephaestus
- GPT-5.4 — high intelligence, default for Oracle
- GPT-5.4 — deep coding powerhouse, required for Hephaestus and default for Oracle
- GPT-5-Nano — ultra-cheap, fast utility tasks
**Different-behavior models**:
- Gemini 3 Pro — excels at visual/frontend tasks
- MiniMax M2.5 — fast and smart for utility tasks
- Gemini 3.1 Pro — excels at visual/frontend tasks
- MiniMax M2.7 / M2.7-highspeed — fast and smart for utility tasks
- Grok Code Fast 1 — optimized for code grep/search
See the [Agent-Model Matching Guide](./agent-model-matching.md) for complete details on which models work best for each agent, safe vs dangerous overrides, and provider priority chains.
@@ -232,7 +231,7 @@ See the [Agent-Model Matching Guide](./agent-model-matching.md) for complete det
Claude Code is good. But it's a single agent running a single model doing everything alone.
Oh My OpenCode turns that into a coordinated team:
Oh My OpenAgent turns that into a coordinated team:
**Parallel execution.** Claude Code processes one thing at a time. OmO fires background agents in parallel — research, implementation, and verification happening simultaneously. Like having 5 engineers instead of 1.
@@ -246,7 +245,7 @@ Oh My OpenCode turns that into a coordinated team:
**Discipline enforcement.** Todo enforcer yanks idle agents back to work. Comment checker strips AI slop. Ralph Loop keeps going until 100% done. The system doesn't let the agent slack off.
**The fundamental advantage.** Models have different temperaments. Claude thinks deeply. GPT reasons architecturally. Gemini visualizes. Haiku moves fast. Single-model tools force you to pick one personality for all tasks. Oh My OpenCode leverages them all, routing by task type. This isn't a temporary hack — it's the only architecture that makes sense as models specialize further. The gap between multi-model orchestration and single-model limitation widens every month. We're betting on that future.
**The fundamental advantage.** Models have different temperaments. Claude thinks deeply. GPT reasons architecturally. Gemini visualizes. Haiku moves fast. Single-model tools force you to pick one personality for all tasks. Oh My OpenAgent leverages them all, routing by task type. This isn't a temporary hack — it's the only architecture that makes sense as models specialize further. The gap between multi-model orchestration and single-model limitation widens every month. We're betting on that future.
---
@@ -256,7 +255,7 @@ Before acting on any request, Sisyphus classifies your true intent.
Are you asking for research? Implementation? Investigation? A fix? The Intent Gate figures out what you actually want, not just the literal words you typed. This means the agent understands context, nuance, and the real goal behind your request.
Claude Code doesn't have this. It takes your prompt and runs. Oh My OpenCode thinks first, then acts.
Claude Code doesn't have this. It takes your prompt and runs. Oh My OpenAgent thinks first, then acts.
---

View File

@@ -1,6 +1,6 @@
# Manifesto
The principles and philosophy behind Oh My OpenCode.
The principles and philosophy behind Oh My OpenAgent.
---
@@ -20,7 +20,7 @@ When you find yourself:
That's not "human-AI collaboration." That's the AI failing to do its job.
**Oh My OpenCode is built on this premise**: Human intervention during agentic work is fundamentally a wrong signal. If the system is designed correctly, the agent should complete the work without requiring you to babysit it.
**Oh My OpenAgent is built on this premise**: Human intervention during agentic work is fundamentally a wrong signal. If the system is designed correctly, the agent should complete the work without requiring you to babysit it.
---
@@ -144,7 +144,7 @@ Human Intent → Agent Execution → Verified Result
(intervention only on true failure)
```
Everything in Oh My OpenCode is designed to make this loop work:
Everything in Oh My OpenAgent is designed to make this loop work:
| Feature | Purpose |
|---------|---------|

View File

@@ -0,0 +1,33 @@
# Model Capabilities Maintenance
This project treats model capability resolution as a layered system:
1. runtime metadata from connected providers
2. `models.dev` bundled/runtime snapshot data
3. explicit compatibility aliases
4. heuristic fallback as the last resort
## Internal policy
- Built-in OmO agent/category requirement models must use canonical model IDs.
- Aliases exist only to preserve compatibility with historical OmO names or provider-specific decorations.
- New decorated names like `-high`, `-low`, or `-thinking` should not be added to built-in requirements when a canonical model ID plus structured settings can express the same thing.
- If a provider or config input still uses an alias, normalize it at the edge and continue internally with the canonical ID.
## When adding an alias
- Add the alias rule to `src/shared/model-capability-aliases.ts`.
- Include a rationale for why the alias exists.
- Add or update tests so the alias is covered explicitly.
- Ensure the alias canonical target exists in the bundled `models.dev` snapshot.
## Guardrails
`bun run test:model-capabilities` enforces the following invariants:
- exact alias targets must exist in the bundled snapshot
- exact alias keys must not silently become canonical `models.dev` IDs
- pattern aliases must not rewrite canonical snapshot IDs
- built-in requirement models must stay canonical and snapshot-backed
The scheduled `refresh-model-capabilities` workflow runs these guardrails before opening an automated snapshot refresh PR.

View File

@@ -1,6 +1,6 @@
# CLI Reference
Complete reference for the `oh-my-opencode` command-line interface.
Complete reference for the published `oh-my-opencode` CLI. During the rename transition, OpenCode plugin registration now prefers `oh-my-openagent` inside `opencode.json`.
## Basic Usage
@@ -14,20 +14,21 @@ npx oh-my-opencode
## Commands
| Command | Description |
| ------------------- | ----------------------------------------- |
| `install` | Interactive setup wizard |
| `doctor` | Environment diagnostics and health checks |
| `run` | OpenCode session runner |
| `mcp oauth` | MCP OAuth authentication management |
| `auth` | Google Antigravity OAuth authentication |
| `get-local-version` | Display local version information |
| Command | Description |
| ----------------------------- | ------------------------------------------------------ |
| `install` | Interactive setup wizard |
| `doctor` | Environment diagnostics and health checks |
| `run` | OpenCode session runner with task completion enforcement |
| `get-local-version` | Display local version information and update check |
| `refresh-model-capabilities` | Refresh the cached models.dev-based model capabilities |
| `version` | Show version information |
| `mcp oauth` | MCP OAuth authentication management |
---
## install
Interactive installation tool for initial Oh-My-OpenCode setup. Provides a TUI based on `@clack/prompts`.
Interactive installation tool for initial Oh My OpenCode setup. Provides a TUI based on `@clack/prompts`.
### Usage
@@ -37,24 +38,37 @@ bunx oh-my-opencode install
### Installation Process
1. **Provider Selection**: Choose your AI provider (Claude, ChatGPT, or Gemini)
2. **API Key Input**: Enter the API key for your selected provider
3. **Configuration File Creation**: Generates `opencode.json` or `oh-my-opencode.json` files
4. **Plugin Registration**: Automatically registers the oh-my-opencode plugin in OpenCode settings
1. **Subscription Selection**: Choose which providers and subscriptions you actually have
2. **Plugin Registration**: Registers `oh-my-openagent` in OpenCode settings, or upgrades a legacy `oh-my-opencode` entry during the compatibility window
3. **Configuration File Creation**: Writes the generated OmO config to `oh-my-opencode.json` in the active OpenCode config directory
4. **Authentication Hints**: Shows the `opencode auth login` steps for the providers you selected, unless `--skip-auth` is set
### Options
| Option | Description |
| ----------- | ---------------------------------------------------------------- |
| `--no-tui` | Run in non-interactive mode without TUI (for CI/CD environments) |
| `--verbose` | Display detailed logs |
| Option | Description |
| ------ | ----------- |
| `--no-tui` | Run in non-interactive mode without TUI |
| `--claude <no\|yes\|max20>` | Claude subscription mode |
| `--openai <no\|yes>` | OpenAI / ChatGPT subscription |
| `--gemini <no\|yes>` | Gemini integration |
| `--copilot <no\|yes>` | GitHub Copilot subscription |
| `--opencode-zen <no\|yes>` | OpenCode Zen access |
| `--zai-coding-plan <no\|yes>` | Z.ai Coding Plan subscription |
| `--kimi-for-coding <no\|yes>` | Kimi for Coding subscription |
| `--opencode-go <no\|yes>` | OpenCode Go subscription |
| `--skip-auth` | Skip authentication setup hints |
---
## doctor
Diagnoses your environment to ensure Oh-My-OpenCode is functioning correctly. Performs 17+ health checks.
Diagnoses your environment to ensure Oh My OpenCode is functioning correctly. The current checks are grouped into system, config, tools, and models.
The doctor command detects common issues including:
- Legacy plugin entry references in `opencode.json` (warns when `oh-my-opencode` is still used instead of `oh-my-openagent`)
- Configuration file validity and JSONC parsing errors
- Model resolution and fallback chain verification
- Missing or misconfigured MCP servers
### Usage
```bash
@@ -63,22 +77,20 @@ bunx oh-my-opencode doctor
### Diagnostic Categories
| Category | Check Items |
| ------------------ | --------------------------------------------------------- |
| **Installation** | OpenCode version (>= 1.0.150), plugin registration status |
| **Configuration** | Configuration file validity, JSONC parsing |
| **Authentication** | Anthropic, OpenAI, Google API key validity |
| **Dependencies** | Bun, Node.js, Git installation status |
| **Tools** | LSP server status, MCP server status |
| **Updates** | Latest version check |
| Category | Check Items |
| ----------------- | ------------------------------------------------------------------------------------ |
| **System** | OpenCode binary, version (>= 1.0.150), plugin registration, legacy package name warning |
| **Config** | Configuration file validity, JSONC parsing, Zod schema validation |
| **Tools** | AST-Grep, LSP servers, GitHub CLI, MCP servers |
| **Models** | Model capabilities cache, model resolution, agent/category overrides, availability |
### Options
| Option | Description |
| ------------------- | ---------------------------------------------------------------- |
| `--category <name>` | Check specific category only (e.g., `--category authentication`) |
| `--json` | Output results in JSON format |
| `--verbose` | Include detailed information |
| Option | Description |
| ------------ | ----------------------------------------- |
| `--status` | Show compact system dashboard |
| `--verbose` | Show detailed diagnostic information |
| `--json` | Output results in JSON format |
### Example Output
@@ -86,57 +98,95 @@ bunx oh-my-opencode doctor
oh-my-opencode doctor
┌──────────────────────────────────────────────────┐
│ Oh-My-OpenCode Doctor │
│ Oh-My-OpenAgent Doctor │
└──────────────────────────────────────────────────┘
Installation
System
✓ OpenCode version: 1.0.155 (>= 1.0.150)
✓ Plugin registered in opencode.json
Configuration
✓ oh-my-opencode.json is valid
Config
✓ oh-my-opencode.jsonc is valid
✓ Model resolution: all agents have valid fallback chains
⚠ categories.visual-engineering: using default model
Authentication
✓ Anthropic API key configured
OpenAI API key configured
✗ Google API key not found
Tools
✓ AST-Grep available
LSP servers configured
Dependencies
Bun 1.2.5 installed
✓ Node.js 22.0.0 installed
✓ Git 2.45.0 installed
Models
11 agents, 8 categories, 0 overrides
⚠ Some configured models rely on compatibility fallback
Summary: 10 passed, 1 warning, 1 failed
Summary: 10 passed, 1 warning, 0 failed
```
---
## run
Executes OpenCode sessions and monitors task completion.
Run opencode with todo/background task completion enforcement. Unlike 'opencode run', this command waits until all todos are completed or cancelled, and all child sessions (background tasks) are idle.
### Usage
```bash
bunx oh-my-opencode run [prompt]
bunx oh-my-opencode run <message>
```
### Options
| Option | Description |
| ------------------------ | ------------------------------------------------- |
| `--enforce-completion` | Keep session active until all TODOs are completed |
| `--timeout <seconds>` | Set maximum execution time |
| `--agent <name>` | Specify agent to use |
| `--directory <path>` | Set working directory |
| `--port <number>` | Set port for session |
| `--attach` | Attach to existing session |
| `--json` | Output in JSON format |
| `--no-timestamp` | Disable timestamped output |
| `--session-id <id>` | Resume existing session |
| `--on-complete <action>` | Action on completion |
| `--verbose` | Enable verbose logging |
| Option | Description |
| --------------------- | ------------------------------------------------------------------- |
| `-a, --agent <name>` | Agent to use (default: from CLI/env/config, fallback: Sisyphus) |
| `-m, --model <provider/model>` | Model override (e.g., anthropic/claude-sonnet-4) |
| `-d, --directory <path>` | Working directory |
| `-p, --port <port>` | Server port (attaches if port already in use) |
| `--attach <url>` | Attach to existing opencode server URL |
| `--on-complete <command>` | Shell command to run after completion |
| `--json` | Output structured JSON result to stdout |
| `--no-timestamp` | Disable timestamp prefix in run output |
| `--verbose` | Show full event stream (default: messages/tools only) |
| `--session-id <id>` | Resume existing session instead of creating new one |
---
## get-local-version
Show current installed version and check for updates.
### Usage
```bash
bunx oh-my-opencode get-local-version
```
### Options
| Option | Description |
| ----------------- | ---------------------------------------------- |
| `-d, --directory` | Working directory to check config from |
| `--json` | Output in JSON format for scripting |
### Output
Shows:
- Current installed version
- Latest available version on npm
- Whether you're up to date
- Special modes (local dev, pinned version)
---
## version
Show version information.
### Usage
```bash
bunx oh-my-opencode version
```
`--on-complete` runs through your current shell when possible: `sh` on Unix shells, `pwsh` for PowerShell on non-Windows, `powershell.exe` for PowerShell on Windows, and `cmd.exe` as the Windows fallback.
---
@@ -151,10 +201,10 @@ Manages OAuth 2.1 authentication for remote MCP servers.
bunx oh-my-opencode mcp oauth login <server-name> --server-url https://api.example.com
# Login with explicit client ID and scopes
bunx oh-my-opencode mcp oauth login my-api --server-url https://api.example.com --client-id my-client --scopes "read,write"
bunx oh-my-opencode mcp oauth login my-api --server-url https://api.example.com --client-id my-client --scopes read write
# Remove stored OAuth tokens
bunx oh-my-opencode mcp oauth logout <server-name>
bunx oh-my-opencode mcp oauth logout <server-name> --server-url https://api.example.com
# Check OAuth token status
bunx oh-my-opencode mcp oauth status [server-name]
@@ -166,7 +216,7 @@ bunx oh-my-opencode mcp oauth status [server-name]
| -------------------- | ------------------------------------------------------------------------- |
| `--server-url <url>` | MCP server URL (required for login) |
| `--client-id <id>` | OAuth client ID (optional if server supports Dynamic Client Registration) |
| `--scopes <scopes>` | Comma-separated OAuth scopes |
| `--scopes <scopes>` | OAuth scopes as separate variadic arguments (for example: `--scopes read write`) |
### Token Storage
@@ -176,10 +226,20 @@ Tokens are stored in `~/.config/opencode/mcp-oauth.json` with `0600` permissions
## Configuration Files
The CLI searches for configuration files in the following locations (in priority order):
The runtime loads user config as the base config, then merges project config on top:
1. **Project Level**: `.opencode/oh-my-opencode.json`
2. **User Level**: `~/.config/opencode/oh-my-opencode.json`
1. **Project Level**: `.opencode/oh-my-openagent.jsonc`, `.opencode/oh-my-openagent.json`, `.opencode/oh-my-opencode.jsonc`, or `.opencode/oh-my-opencode.json`
2. **User Level**: `~/.config/opencode/oh-my-openagent.jsonc`, `~/.config/opencode/oh-my-openagent.json`, `~/.config/opencode/oh-my-opencode.jsonc`, or `~/.config/opencode/oh-my-opencode.json`
**Naming Note**: The published package and binary are still `oh-my-opencode`. Inside `opencode.json`, the compatibility layer now prefers the plugin entry `oh-my-openagent`. Plugin config loading recognizes both `oh-my-openagent.*` and legacy `oh-my-opencode.*` basenames. If both basenames exist in the same directory, the legacy `oh-my-opencode.*` file currently wins.
### Filename Compatibility
Both `.jsonc` and `.json` extensions are supported. JSONC (JSON with Comments) is preferred as it allows:
- Comments (both `//` and `/* */` styles)
- Trailing commas in arrays and objects
If both `.jsonc` and `.json` exist in the same directory, the `.jsonc` file takes precedence.
### JSONC Support
@@ -228,19 +288,66 @@ bunx oh-my-opencode install
# Diagnose with detailed information
bunx oh-my-opencode doctor --verbose
# Check specific category only
bunx oh-my-opencode doctor --category authentication
# Show compact system dashboard
bunx oh-my-opencode doctor --status
# JSON output for scripting
bunx oh-my-opencode doctor --json
```
### "Using legacy package name" Warning
The doctor warns if it finds the legacy plugin entry `oh-my-opencode` in `opencode.json`. Update the plugin array to the canonical `oh-my-openagent` entry:
```bash
# Replace the legacy plugin entry in user config
jq '.plugin = (.plugin // [] | map(if . == "oh-my-opencode" then "oh-my-openagent" else . end))' \
~/.config/opencode/opencode.json > /tmp/opencode.json && mv /tmp/opencode.json ~/.config/opencode/opencode.json
```
---
## refresh-model-capabilities
Refreshes the cached model capabilities snapshot from models.dev. This updates the local cache used by capability resolution and compatibility diagnostics.
### Usage
```bash
bunx oh-my-opencode refresh-model-capabilities
```
### Options
| Option | Description |
| ----------------- | --------------------------------------------------- |
| `-d, --directory` | Working directory to read oh-my-opencode config from |
| `--source-url <url>` | Override the models.dev source URL |
| `--json` | Output refresh summary as JSON |
### Configuration
Configure automatic refresh behavior in your plugin config:
```jsonc
{
"model_capabilities": {
"enabled": true,
"auto_refresh_on_start": true,
"refresh_timeout_ms": 5000,
"source_url": "https://models.dev/api.json"
}
}
```
---
## Non-Interactive Mode
Use the `--no-tui` option for CI/CD environments.
Use JSON output for CI or scripted diagnostics.
```bash
# Run doctor in CI environment
bunx oh-my-opencode doctor --no-tui --json
bunx oh-my-opencode doctor --json
# Save results to file
bunx oh-my-opencode doctor --json > doctor-report.json

View File

@@ -1,6 +1,6 @@
# Configuration Reference
Complete reference for `oh-my-opencode.jsonc` configuration. This document covers every available option with examples.
Complete reference for Oh My OpenCode plugin configuration. During the rename transition, the runtime recognizes both `oh-my-openagent.json[c]` and legacy `oh-my-opencode.json[c]` files.
---
@@ -30,6 +30,7 @@ Complete reference for `oh-my-opencode.jsonc` configuration. This document cover
- [LSP](#lsp)
- [Advanced](#advanced)
- [Runtime Fallback](#runtime-fallback)
- [Model Capabilities](#model-capabilities)
- [Hashline Edit](#hashline-edit)
- [Experimental](#experimental)
- [Reference](#reference)
@@ -42,16 +43,17 @@ Complete reference for `oh-my-opencode.jsonc` configuration. This document cover
### File Locations
Priority order (project overrides user):
User config is loaded first, then project config overrides it. In each directory, the compatibility layer recognizes both the renamed and legacy basenames.
1. `.opencode/oh-my-opencode.jsonc` / `.opencode/oh-my-opencode.json`
1. Project config: `.opencode/oh-my-openagent.json[c]` or `.opencode/oh-my-opencode.json[c]`
2. User config (`.jsonc` preferred over `.json`):
| Platform | Path |
| ----------- | ----------------------------------------- |
| macOS/Linux | `~/.config/opencode/oh-my-opencode.jsonc` |
| Windows | `%APPDATA%\opencode\oh-my-opencode.jsonc` |
| Platform | Path candidates |
| ----------- | --------------- |
| macOS/Linux | `~/.config/opencode/oh-my-openagent.json[c]`, `~/.config/opencode/oh-my-opencode.json[c]` |
| Windows | `%APPDATA%\opencode\oh-my-openagent.json[c]`, `%APPDATA%\opencode\oh-my-opencode.json[c]` |
**Rename compatibility:** The published package and CLI binary remain `oh-my-opencode`. OpenCode plugin registration prefers `oh-my-openagent`, while legacy `oh-my-opencode` entries and config basenames still load during the transition. Config detection checks `oh-my-opencode` before `oh-my-openagent`, so if both plugin config basenames exist in the same directory, the legacy `oh-my-opencode.*` file currently wins.
JSONC supports `// line comments`, `/* block comments */`, and trailing commas.
Enable schema autocomplete:
@@ -93,19 +95,19 @@ Here's a practical starting configuration:
},
"categories": {
// quick trivial tasks
// quick - trivial tasks
"quick": { "model": "opencode/gpt-5-nano" },
// unspecified-low moderate tasks
// unspecified-low - moderate tasks
"unspecified-low": { "model": "anthropic/claude-sonnet-4-6" },
// unspecified-high complex work
// unspecified-high - complex work
"unspecified-high": { "model": "anthropic/claude-opus-4-6", "variant": "max" },
// writing docs/prose
// writing - docs/prose
"writing": { "model": "google/gemini-3-flash" },
// visual-engineering Gemini dominates visual tasks
// visual-engineering - Gemini dominates visual tasks
"visual-engineering": {
"model": "google/gemini-3.1-pro",
"variant": "high",
@@ -157,26 +159,28 @@ Override built-in agent settings. Available agents: `sisyphus`, `hephaestus`, `p
Disable agents entirely: `{ "disabled_agents": ["oracle", "multimodal-looker"] }`
Core agents receive an injected runtime `order` field for deterministic Tab cycling in the UI: Sisyphus = 1, Hephaestus = 2, Prometheus = 3, Atlas = 4. This is not a user-configurable config key.
#### Agent Options
| Option | Type | Description |
| ----------------- | ------------- | ------------------------------------------------------ |
| `model` | string | Model override (`provider/model`) |
| `fallback_models` | string\|array | Fallback models on API errors |
| `temperature` | number | Sampling temperature |
| `top_p` | number | Top-p sampling |
| `prompt` | string | Replace system prompt |
| `prompt_append` | string | Append to system prompt |
| Option | Type | Description |
| ----------------- | -------------- | --------------------------------------------------------------- |
| `model` | string | Model override (`provider/model`) |
| `fallback_models` | string\|array | Fallback models on API errors. Supports strings or mixed arrays of strings and object entries with per-model settings |
| `temperature` | number | Sampling temperature |
| `top_p` | number | Top-p sampling |
| `prompt` | string | Replace system prompt. Supports `file://` URIs |
| `prompt_append` | string | Append to system prompt. Supports `file://` URIs |
| `tools` | array | Allowed tools list |
| `disable` | boolean | Disable this agent |
| `mode` | string | Agent mode |
| `color` | string | UI color |
| `permission` | object | Per-tool permissions (see below) |
| `category` | string | Inherit model from category |
| `variant` | string | Model variant: `max`, `high`, `medium`, `low`, `xhigh` |
| `variant` | string | Model variant: `max`, `high`, `medium`, `low`, `xhigh`. Normalized to supported values |
| `maxTokens` | number | Max response tokens |
| `thinking` | object | Anthropic extended thinking |
| `reasoningEffort` | string | OpenAI reasoning: `low`, `medium`, `high`, `xhigh` |
| `reasoningEffort` | string | OpenAI reasoning: `none`, `minimal`, `low`, `medium`, `high`, `xhigh`. Normalized to supported values |
| `textVerbosity` | string | Text verbosity: `low`, `medium`, `high` |
| `providerOptions` | object | Provider-specific options |
@@ -216,6 +220,65 @@ Control what tools an agent can use:
| `doom_loop` | `ask` / `allow` / `deny` |
| `external_directory` | `ask` / `allow` / `deny` |
#### Fallback Models with Per-Model Settings
`fallback_models` accepts either a single model string or an array. Array entries can be plain strings or objects with individual model settings:
```jsonc
{
"agents": {
"sisyphus": {
"model": "anthropic/claude-opus-4-6",
"fallback_models": [
// Simple string fallback
"openai/gpt-5.4",
// Object with per-model settings
{
"model": "google/gemini-3.1-pro",
"variant": "high",
"temperature": 0.2
},
{
"model": "anthropic/claude-sonnet-4-6",
"thinking": { "type": "enabled", "budgetTokens": 64000 }
}
]
}
}
}
```
Object entries support: `model`, `variant`, `reasoningEffort`, `temperature`, `top_p`, `maxTokens`, `thinking`.
#### File URIs for Prompts
Both `prompt` and `prompt_append` support loading content from files via `file://` URIs. Category-level `prompt_append` supports the same URI forms.
```jsonc
{
"agents": {
"sisyphus": {
"prompt_append": "file:///absolute/path/to/prompt.txt"
},
"oracle": {
"prompt": "file://./relative/to/project/prompt.md"
},
"explore": {
"prompt_append": "file://~/home/dir/prompt.txt"
}
},
"categories": {
"custom": {
"model": "anthropic/claude-sonnet-4-6",
"prompt_append": "file://./category-context.md"
}
}
}
```
Paths can be absolute (`file:///abs/path`), relative to project root (`file://./rel/path`), or home-relative (`file://~/home/path`). If a file URI cannot be decoded, resolved, or read, OmO inserts a warning placeholder into the prompt instead of failing hard.
### Categories
Domain-specific model delegation used by the `task()` tool. When Sisyphus delegates work, it picks a category, not a model name.
@@ -228,7 +291,7 @@ Domain-specific model delegation used by the `task()` tool. When Sisyphus delega
| `ultrabrain` | `openai/gpt-5.4` (xhigh) | Deep logical reasoning, complex architecture |
| `deep` | `openai/gpt-5.3-codex` (medium) | Autonomous problem-solving, thorough research |
| `artistry` | `google/gemini-3.1-pro` (high) | Creative/unconventional approaches |
| `quick` | `anthropic/claude-haiku-4-5` | Trivial tasks, typo fixes, single-file changes |
| `quick` | `openai/gpt-5.4-mini` | Trivial tasks, typo fixes, single-file changes |
| `unspecified-low` | `anthropic/claude-sonnet-4-6` | General tasks, low effort |
| `unspecified-high` | `anthropic/claude-opus-4-6` (max) | General tasks, high effort |
| `writing` | `google/gemini-3-flash` | Documentation, prose, technical writing |
@@ -240,16 +303,16 @@ Domain-specific model delegation used by the `task()` tool. When Sisyphus delega
| Option | Type | Default | Description |
| ------------------- | ------------- | ------- | ------------------------------------------------------------------- |
| `model` | string | - | Model override |
| `fallback_models` | string\|array | - | Fallback models on API errors |
| `fallback_models` | string\|array | - | Fallback models on API errors. Supports strings or mixed arrays of strings and object entries with per-model settings |
| `temperature` | number | - | Sampling temperature |
| `top_p` | number | - | Top-p sampling |
| `maxTokens` | number | - | Max response tokens |
| `thinking` | object | - | Anthropic extended thinking |
| `reasoningEffort` | string | - | OpenAI reasoning effort |
| `reasoningEffort` | string | - | OpenAI reasoning effort. Unsupported values are normalized |
| `textVerbosity` | string | - | Text verbosity |
| `tools` | array | - | Allowed tools |
| `prompt_append` | string | - | Append to system prompt |
| `variant` | string | - | Model variant |
| `variant` | string | - | Model variant. Unsupported values are normalized |
| `description` | string | - | Shown in `task()` tool prompt |
| `is_unstable_agent` | boolean | `false` | Force background mode + monitoring. Auto-enabled for Gemini models. |
@@ -257,39 +320,64 @@ Disable categories: `{ "disabled_categories": ["ultrabrain"] }`
### Model Resolution
3-step priority at runtime:
Runtime priority:
1. **UI-selected model** - model chosen in the OpenCode UI, for primary agents
2. **User override** - model set in config → used exactly as-is. Even on cold cache, explicit user configuration takes precedence over hardcoded fallback chains
3. **Category default** - model inherited from the assigned category config
4. **User `fallback_models`** - user-configured fallback list is tried before built-in fallback chains
5. **Provider fallback chain** - built-in provider/model chain from OmO source
6. **System default** - OpenCode's configured default model
#### Model Settings Compatibility
Model settings are compatibility-normalized against model capabilities instead of failing hard.
Normalized fields:
- `variant` - downgraded to the closest supported value
- `reasoningEffort` - downgraded to the closest supported value, or removed if unsupported
- `temperature` - removed if unsupported by the model metadata
- `top_p` - removed if unsupported by the model metadata
- `maxTokens` - capped to the model's reported max output limit
- `thinking` - removed if the target model does not support thinking
Examples:
- Claude models do not support `reasoningEffort` - it is removed automatically
- GPT-4.1 does not support reasoning - `reasoningEffort` is removed
- o-series models support `none` through `high` - `xhigh` is downgraded to `high`
- GPT-5 supports `none`, `minimal`, `low`, `medium`, `high`, `xhigh` - all pass through
Capability data comes from provider runtime metadata first. OmO also ships bundled models.dev-backed capability data, supports a refreshable local models.dev cache, and falls back to heuristic family detection plus alias rules when exact metadata is unavailable. `bunx oh-my-opencode doctor` surfaces capability diagnostics and warns when a configured model relies on compatibility fallback.
1. **User override** — model set in config → used exactly as-is
2. **Provider fallback chain** — tries each provider in priority order until available
3. **System default** — falls back to OpenCode's configured default model
#### Agent Provider Chains
| Agent | Default Model | Provider Priority |
| --------------------- | ------------------- | ---------------------------------------------------------------------------- |
| **Sisyphus** | `claude-opus-4-6` | `claude-opus-4-6``glm-5``big-pickle` |
| **Hephaestus** | `gpt-5.3-codex` | `gpt-5.3-codex``gpt-5.4` (GitHub Copilot fallback) |
| **oracle** | `gpt-5.4` | `gpt-5.4``gemini-3.1-pro``claude-opus-4-6` |
| **librarian** | `gemini-3-flash` | `gemini-3-flash``minimax-m2.5-free``big-pickle` |
| **explore** | `grok-code-fast-1` | `grok-code-fast-1``minimax-m2.5-free``claude-haiku-4-5``gpt-5-nano` |
| **multimodal-looker** | `gpt-5.3-codex` | `gpt-5.3-codex``k2p5``gemini-3-flash``glm-4.6v``gpt-5-nano` |
| **Prometheus** | `claude-opus-4-6` | `claude-opus-4-6``gpt-5.4``gemini-3.1-pro` |
| **Metis** | `claude-opus-4-6` | `claude-opus-4-6``gpt-5.4``gemini-3.1-pro` |
| **Momus** | `gpt-5.4` | `gpt-5.4``claude-opus-4-6``gemini-3.1-pro` |
| **Atlas** | `claude-sonnet-4-6` | `claude-sonnet-4-6``gpt-5.4` |
| **Sisyphus** | `claude-opus-4-6` | `anthropic\|github-copilot\|opencode/claude-opus-4-6 (max)``opencode-go/kimi-k2.5``kimi-for-coding/k2p5``opencode\|moonshotai\|moonshotai-cn\|firmware\|ollama-cloud\|aihubmix/kimi-k2.5``openai\|github-copilot\|opencode/gpt-5.4 (medium)``zai-coding-plan\|opencode/glm-5``opencode/big-pickle` |
| **Hephaestus** | `gpt-5.4` | `gpt-5.4 (medium)` |
| **oracle** | `gpt-5.4` | `openai\|github-copilot\|opencode/gpt-5.4 (high)``google\|github-copilot\|opencode/gemini-3.1-pro (high)``anthropic\|github-copilot\|opencode/claude-opus-4-6 (max)``opencode-go/glm-5` |
| **librarian** | `minimax-m2.7` | `opencode-go/minimax-m2.7``opencode/minimax-m2.7-highspeed``anthropic\|opencode/claude-haiku-4-5``opencode/gpt-5-nano` |
| **explore** | `grok-code-fast-1` | `github-copilot\|xai/grok-code-fast-1``opencode-go/minimax-m2.7-highspeed``opencode/minimax-m2.7``anthropic\|opencode/claude-haiku-4-5``opencode/gpt-5-nano` |
| **multimodal-looker** | `gpt-5.4` | `openai\|opencode/gpt-5.4 (medium)``opencode-go/kimi-k2.5``zai-coding-plan/glm-4.6v``openai\|github-copilot\|opencode/gpt-5-nano` |
| **Prometheus** | `claude-opus-4-6` | `anthropic\|github-copilot\|opencode/claude-opus-4-6 (max)``openai\|github-copilot\|opencode/gpt-5.4 (high)``opencode-go/glm-5``google\|github-copilot\|opencode/gemini-3.1-pro` |
| **Metis** | `claude-opus-4-6` | `anthropic\|github-copilot\|opencode/claude-opus-4-6 (max)``openai\|github-copilot\|opencode/gpt-5.4 (high)``opencode-go/glm-5``kimi-for-coding/k2p5` |
| **Momus** | `gpt-5.4` | `openai\|github-copilot\|opencode/gpt-5.4 (xhigh)``anthropic\|github-copilot\|opencode/claude-opus-4-6 (max)``google\|github-copilot\|opencode/gemini-3.1-pro (high)``opencode-go/glm-5` |
| **Atlas** | `claude-sonnet-4-6` | `anthropic\|github-copilot\|opencode/claude-sonnet-4-6``opencode-go/kimi-k2.5``openai\|github-copilot\|opencode/gpt-5.4 (medium)``opencode-go/minimax-m2.7` |
#### Category Provider Chains
| Category | Default Model | Provider Priority |
| ---------------------- | ------------------- | -------------------------------------------------------------- |
| **visual-engineering** | `gemini-3.1-pro` | `gemini-3.1-pro``glm-5``claude-opus-4-6` |
| **ultrabrain** | `gpt-5.4` | `gpt-5.4``gemini-3.1-pro``claude-opus-4-6` |
| **deep** | `gpt-5.3-codex` | `gpt-5.3-codex``claude-opus-4-6``gemini-3.1-pro` |
| **artistry** | `gemini-3.1-pro` | `gemini-3.1-pro``claude-opus-4-6``gpt-5.4` |
| **quick** | `claude-haiku-4-5` | `claude-haiku-4-5``gemini-3-flash``gpt-5-nano` |
| **unspecified-low** | `claude-sonnet-4-6` | `claude-sonnet-4-6``gpt-5.3-codex``gemini-3-flash` |
| **unspecified-high** | `claude-opus-4-6` | `claude-opus-4-6``gpt-5.4 (high)``glm-5``k2p5``kimi-k2.5` |
| **writing** | `gemini-3-flash` | `gemini-3-flash``claude-sonnet-4-6` |
| **visual-engineering** | `gemini-3.1-pro` | `google\|github-copilot\|opencode/gemini-3.1-pro (high)``zai-coding-plan\|opencode/glm-5``anthropic\|github-copilot\|opencode/claude-opus-4-6 (max)``opencode-go/glm-5``kimi-for-coding/k2p5` |
| **ultrabrain** | `gpt-5.4` | `openai\|opencode/gpt-5.4 (xhigh)``google\|github-copilot\|opencode/gemini-3.1-pro (high)``anthropic\|github-copilot\|opencode/claude-opus-4-6 (max)``opencode-go/glm-5` |
| **deep** | `gpt-5.3-codex` | `openai\|opencode/gpt-5.3-codex (medium)``anthropic\|github-copilot\|opencode/claude-opus-4-6 (max)``google\|github-copilot\|opencode/gemini-3.1-pro (high)` |
| **artistry** | `gemini-3.1-pro` | `google\|github-copilot\|opencode/gemini-3.1-pro (high)``anthropic\|github-copilot\|opencode/claude-opus-4-6 (max)``openai\|github-copilot\|opencode/gpt-5.4` |
| **quick** | `gpt-5.4-mini` | `openai\|github-copilot\|opencode/gpt-5.4-mini` `anthropic\|github-copilot\|opencode/claude-haiku-4-5``google\|github-copilot\|opencode/gemini-3-flash``opencode-go/minimax-m2.7``opencode/gpt-5-nano` |
| **unspecified-low** | `claude-sonnet-4-6` | `anthropic\|github-copilot\|opencode/claude-sonnet-4-6``openai\|opencode/gpt-5.3-codex (medium)``opencode-go/kimi-k2.5``google\|github-copilot\|opencode/gemini-3-flash``opencode-go/minimax-m2.7` |
| **unspecified-high** | `claude-opus-4-6` | `anthropic\|github-copilot\|opencode/claude-opus-4-6 (max)``openai\|github-copilot\|opencode/gpt-5.4 (high)``zai-coding-plan\|opencode/glm-5``kimi-for-coding/k2p5``opencode-go/glm-5``opencode/kimi-k2.5``opencode\|moonshotai\|moonshotai-cn\|firmware\|ollama-cloud\|aihubmix/kimi-k2.5` |
| **writing** | `gemini-3-flash` | `google\|github-copilot\|opencode/gemini-3-flash``opencode-go/kimi-k2.5``anthropic\|github-copilot\|opencode/claude-sonnet-4-6``opencode-go/minimax-m2.7` |
Run `bunx oh-my-opencode doctor --verbose` to see effective model resolution for your config.
@@ -418,17 +506,17 @@ Disable built-in skills: `{ "disabled_skills": ["playwright"] }`
Disable built-in hooks via `disabled_hooks`:
```json
{ "disabled_hooks": ["comment-checker", "gpt-permission-continuation"] }
{ "disabled_hooks": ["comment-checker"] }
```
Available hooks: `gpt-permission-continuation`, `todo-continuation-enforcer`, `context-window-monitor`, `session-recovery`, `session-notification`, `comment-checker`, `grep-output-truncator`, `tool-output-truncator`, `directory-agents-injector`, `directory-readme-injector`, `empty-task-response-detector`, `think-mode`, `anthropic-context-window-limit-recovery`, `rules-injector`, `background-notification`, `auto-update-checker`, `startup-toast`, `keyword-detector`, `agent-usage-reminder`, `non-interactive-env`, `interactive-bash-session`, `compaction-context-injector`, `thinking-block-validator`, `claude-code-hooks`, `ralph-loop`, `preemptive-compaction`, `auto-slash-command`, `sisyphus-junior-notepad`, `no-sisyphus-gpt`, `start-work`, `runtime-fallback`
Available hooks: `todo-continuation-enforcer`, `context-window-monitor`, `session-recovery`, `session-notification`, `comment-checker`, `grep-output-truncator`, `tool-output-truncator`, `directory-agents-injector`, `directory-readme-injector`, `empty-task-response-detector`, `think-mode`, `anthropic-context-window-limit-recovery`, `rules-injector`, `background-notification`, `auto-update-checker`, `startup-toast`, `keyword-detector`, `agent-usage-reminder`, `non-interactive-env`, `interactive-bash-session`, `compaction-context-injector`, `thinking-block-validator`, `claude-code-hooks`, `ralph-loop`, `preemptive-compaction`, `auto-slash-command`, `sisyphus-junior-notepad`, `no-sisyphus-gpt`, `start-work`, `runtime-fallback`
**Notes:**
- `directory-agents-injector` auto-disabled on OpenCode 1.1.37+ (native AGENTS.md support)
- `gpt-permission-continuation` — resumes GPT sessions only when the last assistant reply ends with a permission-seeking tail like `If you want, ...`. Disable it if you prefer GPT sessions to wait for explicit user follow-up.
- `no-sisyphus-gpt`**do not disable**. It blocks incompatible GPT models for Sisyphus while allowing the dedicated GPT-5.4 prompt path.
- `directory-agents-injector` - auto-disabled on OpenCode 1.1.37+ (native AGENTS.md support)
- `no-sisyphus-gpt` - **do not disable**. It blocks incompatible GPT models for Sisyphus while allowing the dedicated GPT-5.4 prompt path.
- `startup-toast` is a sub-feature of `auto-update-checker`. Disable just the toast by adding `startup-toast` to `disabled_hooks`.
- `session-recovery` - automatically recovers from recoverable session errors (missing tool results, unavailable tools, thinking block violations). Shows toast notifications during recovery. Enable `experimental.auto_resume` for automatic retry after recovery.
### Commands
@@ -505,7 +593,7 @@ Force-enable session notifications:
{ "notification": { "force_enable": true } }
```
`force_enable` (`false`) force session-notification even if external notification plugins are detected.
`force_enable` (`false`) - force session-notification even if external notification plugins are detected.
### MCPs
@@ -591,12 +679,233 @@ Define `fallback_models` per agent or category:
"agents": {
"sisyphus": {
"model": "anthropic/claude-opus-4-6",
"fallback_models": ["openai/gpt-5.4", "google/gemini-3.1-pro"]
"fallback_models": [
"openai/gpt-5.4",
{
"model": "google/gemini-3.1-pro",
"variant": "high"
}
]
}
}
}
```
`fallback_models` also supports object-style entries so you can attach settings to a specific fallback model:
```json
{
"agents": {
"sisyphus": {
"model": "anthropic/claude-opus-4-6",
"fallback_models": [
"openai/gpt-5.4",
{
"model": "anthropic/claude-sonnet-4-6",
"variant": "high",
"thinking": { "type": "enabled", "budgetTokens": 12000 }
},
{
"model": "openai/gpt-5.3-codex",
"reasoningEffort": "high",
"temperature": 0.2,
"top_p": 0.95,
"maxTokens": 8192
}
]
}
}
}
```
Mixed arrays are allowed, so string entries and object entries can appear together in the same fallback chain.
#### Object-style `fallback_models`
Object entries use the following shape:
| Field | Type | Description |
| ----- | ---- | ----------- |
| `model` | string | Fallback model ID. Provider prefix is optional when OmO can inherit the current/default provider. |
| `variant` | string | Explicit variant override for this fallback entry. |
| `reasoningEffort` | string | OpenAI reasoning effort override for this fallback entry. |
| `temperature` | number | Temperature applied if this fallback model becomes active. |
| `top_p` | number | Top-p applied if this fallback model becomes active. |
| `maxTokens` | number | Max response tokens applied if this fallback model becomes active. |
| `thinking` | object | Anthropic thinking config applied if this fallback model becomes active. |
Per-model settings are **fallback-only**. They are promoted only when that specific fallback model is actually selected, so they do not override your primary model settings when the primary model resolves successfully.
`thinking` uses the same shape as the normal agent/category option:
| Field | Type | Description |
| ----- | ---- | ----------- |
| `type` | string | `enabled` or `disabled` |
| `budgetTokens` | number | Optional Anthropic thinking budget |
Object entries can also omit the provider prefix when OmO can infer it from the current/default provider. If you provide both inline variant syntax in `model` and an explicit `variant` field, the explicit `variant` field wins.
#### Full examples
**1. Simple string chain**
Use strings when you only need an ordered fallback chain:
```json
{
"agents": {
"atlas": {
"model": "anthropic/claude-sonnet-4-6",
"fallback_models": [
"anthropic/claude-haiku-4-5",
"openai/gpt-5.4",
"google/gemini-3.1-pro"
]
}
}
}
```
**2. Same-provider shorthand**
If the primary model already establishes the provider, fallback entries can omit the prefix:
```json
{
"agents": {
"atlas": {
"model": "openai/gpt-5.4",
"fallback_models": [
"gpt-5.4-mini",
{
"model": "gpt-5.3-codex",
"reasoningEffort": "medium",
"maxTokens": 4096
}
]
}
}
}
```
In this example OmO treats `gpt-5.4-mini` and `gpt-5.3-codex` as OpenAI fallback entries because the current/default provider is already `openai`.
**3. Mixed cross-provider chain**
Mix string entries and object entries when only some fallback models need special settings:
```json
{
"agents": {
"sisyphus": {
"model": "anthropic/claude-opus-4-6",
"fallback_models": [
"openai/gpt-5.4",
{
"model": "anthropic/claude-sonnet-4-6",
"variant": "high",
"thinking": { "type": "enabled", "budgetTokens": 12000 }
},
{
"model": "google/gemini-3.1-pro",
"variant": "high"
}
]
}
}
}
```
**4. Category-level fallback chain**
`fallback_models` works the same way under `categories`:
```json
{
"categories": {
"deep": {
"model": "openai/gpt-5.3-codex",
"fallback_models": [
{
"model": "openai/gpt-5.4",
"reasoningEffort": "xhigh",
"maxTokens": 12000
},
{
"model": "anthropic/claude-opus-4-6",
"variant": "max",
"temperature": 0.2
},
"google/gemini-3.1-pro(high)"
]
}
}
}
```
**5. Full object entry with every supported field**
This shows every supported object-style parameter in one place:
```json
{
"agents": {
"oracle": {
"model": "openai/gpt-5.4",
"fallback_models": [
{
"model": "openai/gpt-5.3-codex(low)",
"variant": "xhigh",
"reasoningEffort": "high",
"temperature": 0.3,
"top_p": 0.9,
"maxTokens": 8192,
"thinking": {
"type": "disabled"
}
}
]
}
}
}
```
In this example the explicit `"variant": "xhigh"` overrides the inline `(low)` suffix in `"model"`.
This final example is a **complete shape reference**. In real configs, prefer provider-appropriate settings:
- use `reasoningEffort` for OpenAI reasoning models
- use `thinking` for Anthropic thinking-capable models
- use `variant`, `temperature`, `top_p`, and `maxTokens` only when that fallback model supports them
### Model Capabilities
OmO can refresh a local models.dev capability snapshot on startup. This cache is controlled by `model_capabilities`.
```jsonc
{
"model_capabilities": {
"enabled": true,
"auto_refresh_on_start": true,
"refresh_timeout_ms": 5000,
"source_url": "https://models.dev/api.json"
}
}
```
| Option | Default behavior | Description |
| ------ | ---------------- | ----------- |
| `enabled` | enabled unless explicitly set to `false` | Master switch for model capability refresh behavior |
| `auto_refresh_on_start` | refresh on startup unless explicitly set to `false` | Refresh the local models.dev cache during startup checks |
| `refresh_timeout_ms` | `5000` | Timeout for the startup refresh attempt |
| `source_url` | `https://models.dev/api.json` | Override the models.dev source URL |
Notes:
- Startup refresh runs through the auto-update checker hook.
- Manual refresh is available via `bunx oh-my-opencode refresh-model-capabilities`.
- Provider runtime metadata still takes priority when OmO resolves capabilities for compatibility checks.
### Hashline Edit
Replaces the built-in `Edit` tool with a hash-anchored version using `LINE#ID` references to prevent stale-line edits. Disabled by default.
@@ -616,7 +925,7 @@ When enabled, two companion hooks are active: `hashline-read-enhancer` (annotate
"aggressive_truncation": false,
"auto_resume": false,
"disable_omo_env": false,
"task_system": false,
"task_system": true,
"dynamic_context_pruning": {
"enabled": false,
"notification": "detailed",
@@ -646,7 +955,7 @@ When enabled, two companion hooks are active: `hashline-read-enhancer` (annotate
| `aggressive_truncation` | `false` | Aggressively truncate when token limit exceeded |
| `auto_resume` | `false` | Auto-resume after thinking block recovery |
| `disable_omo_env` | `false` | Disable auto-injected `<omo-env>` block (date/time/locale). Improves cache hit rate. |
| `task_system` | `false` | Enable Sisyphus task system |
| `task_system` | `true` | Enable Sisyphus task system |
| `dynamic_context_pruning.enabled` | `false` | Auto-prune old tool outputs to manage context window |
| `dynamic_context_pruning.notification` | `detailed` | Pruning notifications: `off` / `minimal` / `detailed` |
| `turn_protection.turns` | `3` | Recent turns protected from pruning (110) |

View File

@@ -1,34 +1,35 @@
# Oh-My-OpenCode Features Reference
# Oh-My-OpenAgent Features Reference
## Agents
Oh-My-OpenCode provides 11 specialized AI agents. Each has distinct expertise, optimized models, and tool permissions.
Oh-My-OpenAgent provides 11 specialized AI agents. Each has distinct expertise, optimized models, and tool permissions.
### Core Agents
Core-agent tab cycling is deterministic via injected runtime order field. The fixed priority order is Sisyphus (order: 1), Hephaestus (order: 2), Prometheus (order: 3), and Atlas (order: 4). Remaining agents follow after that stable core ordering.
| Agent | Model | Purpose |
| --------------------- | ------------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **Sisyphus** | `claude-opus-4-6` | The default orchestrator. Plans, delegates, and executes complex tasks using specialized subagents with aggressive parallel execution. Todo-driven workflow with extended thinking (32k budget). Fallback: `glm-5``big-pickle`. |
| **Hephaestus** | `gpt-5.3-codex` | The Legitimate Craftsman. Autonomous deep worker inspired by AmpCode's deep mode. Goal-oriented execution with thorough research before action. Explores codebase patterns, completes tasks end-to-end without premature stopping. Named after the Greek god of forge and craftsmanship. Fallback: `gpt-5.4` on GitHub Copilot. Requires a GPT-capable provider. |
| **Oracle** | `gpt-5.4` | Architecture decisions, code review, debugging. Read-only consultation with stellar logical reasoning and deep analysis. Inspired by AmpCode. Fallback: `gemini-3.1-pro``claude-opus-4-6`. |
| **Librarian** | `gemini-3-flash` | Multi-repo analysis, documentation lookup, OSS implementation examples. Deep codebase understanding with evidence-based answers. Fallback: `minimax-m2.5-free``big-pickle`. |
| **Explore** | `grok-code-fast-1` | Fast codebase exploration and contextual grep. Fallback: `minimax-m2.5-free``claude-haiku-4-5``gpt-5-nano`. |
| **Multimodal-Looker** | `gpt-5.3-codex` | Visual content specialist. Analyzes PDFs, images, diagrams to extract information. Fallback: `k2p5``gemini-3-flash``glm-4.6v``gpt-5-nano`. |
| **Sisyphus** | `claude-opus-4-6` | The default orchestrator. Plans, delegates, and executes complex tasks using specialized subagents with aggressive parallel execution. Todo-driven workflow with extended thinking (32k budget). Fallback: `opencode-go/kimi-k2.5``kimi-for-coding/k2p5``opencode\|moonshotai\|moonshotai-cn\|firmware\|ollama-cloud\|aihubmix/kimi-k2.5``openai\|github-copilot\|opencode/gpt-5.4 (medium)``zai-coding-plan\|opencode/glm-5``opencode/big-pickle`. |
| **Hephaestus** | `gpt-5.4` | The Legitimate Craftsman. Autonomous deep worker inspired by AmpCode's deep mode. Goal-oriented execution with thorough research before action. Explores codebase patterns, completes tasks end-to-end without premature stopping. Named after the Greek god of forge and craftsmanship. Requires a GPT-capable provider. |
| **Oracle** | `gpt-5.4` | Architecture decisions, code review, debugging. Read-only consultation with stellar logical reasoning and deep analysis. Inspired by AmpCode. Fallback: `google\|github-copilot\|opencode/gemini-3.1-pro (high)``anthropic\|github-copilot\|opencode/claude-opus-4-6 (max)``opencode-go/glm-5`. |
| **Librarian** | `minimax-m2.7` | Multi-repo analysis, documentation lookup, OSS implementation examples. Deep codebase understanding with evidence-based answers. Fallback: `opencode/minimax-m2.7-highspeed``anthropic\|opencode/claude-haiku-4-5``opencode/gpt-5-nano`. |
| **Explore** | `grok-code-fast-1` | Fast codebase exploration and contextual grep. Fallback: `opencode-go/minimax-m2.7-highspeed``opencode/minimax-m2.7``anthropic\|opencode/claude-haiku-4-5``opencode/gpt-5-nano`. |
| **Multimodal-Looker** | `gpt-5.4` | Visual content specialist. Analyzes PDFs, images, diagrams to extract information. Fallback: `opencode-go/kimi-k2.5``zai-coding-plan/glm-4.6v``openai\|github-copilot\|opencode/gpt-5-nano`. |
### Planning Agents
| Agent | Model | Purpose |
| -------------- | ----------------- | -------------------------------------------------------------------------------------------------------------------------------------------------- |
| **Prometheus** | `claude-opus-4-6` | Strategic planner with interview mode. Creates detailed work plans through iterative questioning. Fallback: `gpt-5.4``gemini-3.1-pro`. |
| **Metis** | `claude-opus-4-6` | Plan consultant — pre-planning analysis. Identifies hidden intentions, ambiguities, and AI failure points. Fallback: `gpt-5.4``gemini-3.1-pro`. |
| **Momus** | `gpt-5.4` | Plan reviewer — validates plans against clarity, verifiability, and completeness standards. Fallback: `claude-opus-4-6``gemini-3.1-pro`. |
| **Prometheus** | `claude-opus-4-6` | Strategic planner with interview mode. Creates detailed work plans through iterative questioning. Fallback: `openai\|github-copilot\|opencode/gpt-5.4 (high)``opencode-go/glm-5``google\|github-copilot\|opencode/gemini-3.1-pro`. |
| **Metis** | `claude-opus-4-6` | Plan consultant — pre-planning analysis. Identifies hidden intentions, ambiguities, and AI failure points. Fallback: `openai\|github-copilot\|opencode/gpt-5.4 (high)``opencode-go/glm-5``kimi-for-coding/k2p5`. |
| **Momus** | `gpt-5.4` | Plan reviewer — validates plans against clarity, verifiability, and completeness standards. Fallback: `anthropic\|github-copilot\|opencode/claude-opus-4-6 (max)``google\|github-copilot\|opencode/gemini-3.1-pro (high)``opencode-go/glm-5`. |
### Orchestration Agents
| Agent | Model | Purpose |
| ------------------- | ---------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **Atlas** | `claude-sonnet-4-6` | Todo-list orchestrator. Executes planned tasks systematically, managing todo items and coordinating work. Fallback: `gpt-5.4` (medium). |
| **Sisyphus-Junior** | _(category-dependent)_ | Category-spawned executor. Model is selected automatically based on the task category (visual-engineering, quick, deep, etc.). Used when the main agent delegates work via the `task` tool. |
| **Atlas** | `claude-sonnet-4-6` | Todo-list orchestrator. Executes planned tasks systematically, managing todo items and coordinating work. Fallback: `opencode-go/kimi-k2.5``openai\|github-copilot\|opencode/gpt-5.4 (medium)``opencode-go/minimax-m2.7`. |
| **Sisyphus-Junior** | _(category-dependent)_ | Category-spawned executor. Model is selected automatically based on the task category (visual-engineering, quick, deep, etc.). Its built-in general fallback chain is `anthropic\|github-copilot\|opencode/claude-sonnet-4-6``opencode-go/kimi-k2.5``openai\|github-copilot\|opencode/gpt-5.4 (medium)``opencode-go/minimax-m2.7``opencode/big-pickle`. |
### Invoking Agents
@@ -89,8 +90,9 @@ When running inside tmux:
- Watch multiple agents work in real-time
- Each pane shows agent output live
- Auto-cleanup when agents complete
- **Stable agent ordering**: core-agent tab cycling is deterministic via injected runtime order field (Sisyphus: 1, Hephaestus: 2, Prometheus: 3, Atlas: 4)
Customize agent models, prompts, and permissions in `oh-my-opencode.json`.
Customize agent models, prompts, and permissions in `oh-my-opencode.jsonc`.
## Category System
@@ -111,7 +113,7 @@ By combining these two concepts, you can generate optimal agents through `task`.
| `ultrabrain` | `openai/gpt-5.4` (xhigh) | Deep logical reasoning, complex architecture decisions requiring extensive analysis |
| `deep` | `openai/gpt-5.3-codex` (medium) | Goal-oriented autonomous problem-solving. Thorough research before action. For hairy problems requiring deep understanding. |
| `artistry` | `google/gemini-3.1-pro` (high) | Highly creative/artistic tasks, novel ideas |
| `quick` | `anthropic/claude-haiku-4-5` | Trivial tasks - single file changes, typo fixes, simple modifications |
| `quick` | `openai/gpt-5.4-mini` | Trivial tasks - single file changes, typo fixes, simple modifications |
| `unspecified-low` | `anthropic/claude-sonnet-4-6` | Tasks that don't fit other categories, low effort required |
| `unspecified-high` | `anthropic/claude-opus-4-6` (max) | Tasks that don't fit other categories, high effort required |
| `writing` | `google/gemini-3-flash` | Documentation, prose, technical writing |
@@ -129,7 +131,7 @@ task({
### Custom Categories
You can define custom categories in `oh-my-opencode.json`.
You can define custom categories in your plugin config file. During the rename transition, both `oh-my-openagent.json[c]` and legacy `oh-my-opencode.json[c]` basenames are recognized.
#### Category Configuration Schema
@@ -188,6 +190,75 @@ When you use a Category, a special agent called **Sisyphus-Junior** performs the
- **Characteristic**: Cannot **re-delegate** tasks to other agents.
- **Purpose**: Prevents infinite delegation loops and ensures focus on the assigned task.
## Advanced Configuration
### Rename Compatibility
The published package and binary remain `oh-my-opencode`. Inside `opencode.json`, the compatibility layer now prefers the plugin entry `oh-my-openagent`, while legacy `oh-my-opencode` entries still load with a warning. Plugin config files (`oh-my-openagent.json[c]` or legacy `oh-my-opencode.json[c]`) are recognized during the transition. Run `bunx oh-my-opencode doctor` to check for legacy package name warnings.
### Fallback Models
Configure per-agent fallback chains with arrays that can mix plain model strings and per-model objects:
```jsonc
{
"agents": {
"sisyphus": {
"fallback_models": [
"opencode/glm-5",
{ "model": "openai/gpt-5.4", "variant": "high" },
{ "model": "anthropic/claude-sonnet-4-6", "thinking": { "type": "enabled", "budgetTokens": 64000 } }
]
}
}
}
```
When a model errors, the runtime can move through the configured fallback array. Object entries let you tune the backup model itself instead of only swapping the model name.
### File-Based Prompts
Load agent system prompts from external files using `file://` URLs in the `prompt` field, or append additional content with `prompt_append`. The `prompt_append` field also works on categories.
```jsonc
{
"agents": {
"sisyphus": {
"prompt": "file:///path/to/custom-prompt.md"
},
"oracle": {
"prompt_append": "file:///path/to/additional-context.md"
}
},
"categories": {
"deep": {
"prompt_append": "file:///path/to/deep-category-append.md"
}
}
}
```
Supports `~` expansion for home directory and relative `file://` paths.
Useful for:
- Version controlling prompts separately from config
- Sharing prompts across projects
- Keeping configuration files concise
- Adding category-specific context without duplicating base prompts
The file content is loaded at runtime and injected into the agent's system prompt.
### Session Recovery
The system automatically recovers from common session failures without user intervention:
- **Missing tool results**: reconstructs recoverable tool state and skips invalid tool-part IDs instead of failing the whole recovery pass
- **Thinking block violations**: Recovers from API thinking block mismatches
- **Empty messages**: Reconstructs message history when content is missing
- **Context window limits**: Gracefully handles Claude context window exceeded errors with intelligent compaction
- **JSON parse errors**: Recovers from malformed tool outputs
Recovery happens transparently during agent execution. You see the result, not the failure.
## Skills
Skills provide specialized workflows with embedded MCP servers and detailed instructions. A Skill is a mechanism that injects **specialized knowledge (Context)** and **tools (MCP)** for specific domains into agents.
@@ -237,7 +308,7 @@ Skills provide specialized workflows with embedded MCP servers and detailed inst
### Browser Automation Options
Oh-My-OpenCode provides two browser automation providers, configurable via `browser_automation_engine.provider`.
Oh-My-OpenAgent provides two browser automation providers, configurable via `browser_automation_engine.provider`.
#### Option 1: Playwright MCP (Default)
@@ -558,7 +629,7 @@ Requires `experimental.task_system: true` in config.
#### Task System Details
**Note on Claude Code Alignment**: This implementation follows Claude Code's internal Task tool signatures (`TaskCreate`, `TaskUpdate`, `TaskList`, `TaskGet`) and field naming conventions (`subject`, `blockedBy`, `blocks`, etc.). However, Anthropic has not published official documentation for these tools. This is Oh My OpenCode's own implementation based on observed Claude Code behavior and internal specifications.
**Note on Claude Code Alignment**: This implementation follows Claude Code's internal Task tool signatures (`TaskCreate`, `TaskUpdate`, `TaskList`, `TaskGet`) and field naming conventions (`subject`, `blockedBy`, `blocks`, etc.). However, Anthropic has not published official documentation for these tools. This is Oh My OpenAgent's own implementation based on observed Claude Code behavior and internal specifications.
**Task Schema**:
@@ -680,7 +751,6 @@ Hooks intercept and modify behavior at key points in the agent lifecycle across
| **ralph-loop** | Event + Message | Manages self-referential loop continuation. |
| **start-work** | Message | Handles /start-work command execution. |
| **auto-slash-command** | Message | Automatically executes slash commands from prompts. |
| **gpt-permission-continuation** | Event | Auto-continues GPT sessions when the final assistant reply ends with a permission-seeking tail such as `If you want, ...`. |
| **stop-continuation-guard** | Event + Message | Guards the stop-continuation mechanism. |
| **category-skill-reminder** | Event + PostToolUse | Reminds agents about available category skills for delegation. |
| **anthropic-effort** | Params | Adjusts Anthropic API effort level based on context. |
@@ -735,7 +805,6 @@ Hooks intercept and modify behavior at key points in the agent lifecycle across
| Hook | Event | Description |
| ------------------------------ | ----- | ---------------------------------------------------------- |
| **gpt-permission-continuation** | Event | Continues GPT replies that end in a permission-seeking tail. |
| **todo-continuation-enforcer** | Event | Enforces todo completion — yanks idle agents back to work. |
| **compaction-todo-preserver** | Event | Preserves todo state during session compaction. |
| **unstable-agent-babysitter** | Event | Handles unstable agent behavior with recovery strategies. |
@@ -787,12 +856,10 @@ Disable specific hooks in config:
```json
{
"disabled_hooks": ["comment-checker", "gpt-permission-continuation"]
"disabled_hooks": ["comment-checker"]
}
```
Use `gpt-permission-continuation` when you want GPT sessions to stop at permission-seeking endings instead of auto-resuming.
## MCPs
### Built-in MCPs
@@ -851,6 +918,38 @@ Pre-authenticate via CLI:
bunx oh-my-opencode mcp oauth login <server-name> --server-url https://api.example.com
```
## Model Capabilities
Model capabilities are models.dev-backed, with a refreshable cache and compatibility diagnostics. The system combines bundled models.dev snapshot data, optional refreshed cache data, provider runtime metadata, and heuristics when exact metadata is unavailable.
### Refreshing Capabilities
Update the local cache with the latest model information:
```bash
bunx oh-my-opencode refresh-model-capabilities
```
Configure automatic refresh at startup:
```jsonc
{
"model_capabilities": {
"enabled": true,
"auto_refresh_on_start": true,
"refresh_timeout_ms": 5000,
"source_url": "https://models.dev/api.json"
}
}
```
### Capability Diagnostics
Run `bunx oh-my-opencode doctor` to see capability diagnostics including:
- effective model resolution for agents and categories
- warnings when configured models rely on compatibility fallback
- override compatibility details alongside model resolution output
## Context Injection
### Directory AGENTS.md

View File

@@ -0,0 +1,86 @@
# Model Settings Compatibility Resolver Implementation Plan
> **For agentic workers:** REQUIRED: Use superpowers:subagent-driven-development (if subagents available) or superpowers:executing-plans to implement this plan. Steps use checkbox (`- [ ]`) syntax for tracking.
**Goal:** Centralize compatibility handling for `variant` and `reasoningEffort` so an already-selected model receives the best valid settings for that exact model.
**Architecture:** Introduce a pure shared resolver in `src/shared/` that computes compatible settings and records downgrades/removals. Integrate it first in `chat.params`, then keep Claude-specific effort logic as a thin layer rather than a special-case policy owner.
**Tech Stack:** TypeScript, Bun test, existing shared model normalization/utilities, OpenCode plugin `chat.params` path.
---
### Task 1: Create the pure compatibility resolver
**Files:**
- Create: `src/shared/model-settings-compatibility.ts`
- Create: `src/shared/model-settings-compatibility.test.ts`
- Modify: `src/shared/index.ts`
- [ ] **Step 1: Write failing tests for exact keep behavior**
- [ ] **Step 2: Write failing tests for downgrade behavior (`max` -> `high`, `xhigh` -> `high` where needed)**
- [ ] **Step 3: Write failing tests for unsupported-value removal**
- [ ] **Step 4: Write failing tests for model-family distinctions (Opus vs Sonnet/Haiku, GPT-family variants)**
- [ ] **Step 5: Implement the pure resolver with explicit capability ladders**
- [ ] **Step 6: Export the resolver from `src/shared/index.ts`**
- [ ] **Step 7: Run `bun test src/shared/model-settings-compatibility.test.ts`**
- [ ] **Step 8: Commit**
### Task 2: Integrate resolver into chat.params
**Files:**
- Modify: `src/plugin/chat-params.ts`
- Modify: `src/plugin/chat-params.test.ts`
- [ ] **Step 1: Write failing tests showing `chat.params` applies resolver output to runtime settings**
- [ ] **Step 2: Ensure tests cover both `variant` and `reasoningEffort` decisions**
- [ ] **Step 3: Update `chat-params.ts` to call the shared resolver before hook-specific adjustments**
- [ ] **Step 4: Preserve existing prompt-param-store merging behavior**
- [ ] **Step 5: Run `bun test src/plugin/chat-params.test.ts`**
- [ ] **Step 6: Commit**
### Task 3: Re-scope anthropic-effort around the resolver
**Files:**
- Modify: `src/hooks/anthropic-effort/hook.ts`
- Modify: `src/hooks/anthropic-effort/index.test.ts`
- [ ] **Step 1: Write failing tests that codify the intended remaining Anthropic-specific behavior after centralization**
- [ ] **Step 2: Reduce `anthropic-effort` to Claude/Anthropic-specific effort injection where still needed**
- [ ] **Step 3: Remove duplicated compatibility policy from the hook if the shared resolver now owns it**
- [ ] **Step 4: Run `bun test src/hooks/anthropic-effort/index.test.ts`**
- [ ] **Step 5: Commit**
### Task 4: Add integration/regression coverage across real request paths
**Files:**
- Modify: `src/plugin/chat-params.test.ts`
- Modify: `src/hooks/anthropic-effort/index.test.ts`
- Add tests only where needed in nearby suites
- [ ] **Step 1: Add regression test for non-Opus Claude with `variant=max` resolving to compatible settings without ad hoc path-only logic**
- [ ] **Step 2: Add regression test for GPT-style `reasoningEffort` compatibility**
- [ ] **Step 3: Add regression test showing supported values remain unchanged**
- [ ] **Step 4: Run the focused test set**
- [ ] **Step 5: Commit**
### Task 5: Verify full quality bar
**Files:**
- No intended code changes
- [ ] **Step 1: Run `bun run typecheck`**
- [ ] **Step 2: Run a focused suite for the touched files**
- [ ] **Step 3: If clean, run `bun test`**
- [ ] **Step 4: Review diff for accidental scope creep**
- [ ] **Step 5: Commit any final cleanup**
### Task 6: Prepare PR metadata
**Files:**
- No repo file change required unless docs are updated further
- [ ] **Step 1: Write a human summary explaining this is settings compatibility, not model fallback**
- [ ] **Step 2: Document scope: Phase 1 covers `variant` and `reasoningEffort` only**
- [ ] **Step 3: Document explicit non-goals: no model switching, no automatic upscaling in Phase 1**
- [ ] **Step 4: Request review**

View File

@@ -0,0 +1,164 @@
# Model Settings Compatibility Resolver Design
## Goal
Introduce a central resolver that takes an already-selected model and a set of desired model settings, then returns the best compatible configuration for that exact model.
This is explicitly separate from model fallback.
## Problem
Today, logic for `variant` and `reasoningEffort` compatibility is scattered across multiple places:
- `hooks/anthropic-effort`
- `plugin/chat-params`
- agent/category/fallback config layers
- delegate/background prompt plumbing
That creates inconsistent behavior:
- some paths clamp unsupported levels
- some paths pass them through unchanged
- some paths silently drop them
- some paths use model-family-specific assumptions that do not generalize
The result is brittle request behavior even when the chosen model itself is valid.
## Scope
Phase 1 covers only:
- `variant`
- `reasoningEffort`
Out of scope for Phase 1:
- model fallback itself
- `thinking`
- `maxTokens`
- `temperature`
- `top_p`
- automatic upward remapping of settings
## Desired behavior
Given a fixed model and desired settings:
1. If a desired value is supported, keep it.
2. If not supported, downgrade to the nearest lower compatible value.
3. If no compatible value exists, drop the field.
4. Do not switch models.
5. Do not automatically upgrade settings in Phase 1.
## Architecture
Add a central module:
- `src/shared/model-settings-compatibility.ts`
Core API:
```ts
type DesiredModelSettings = {
variant?: string
reasoningEffort?: string
}
type ModelSettingsCompatibilityInput = {
providerID: string
modelID: string
desired: DesiredModelSettings
}
type ModelSettingsCompatibilityChange = {
field: "variant" | "reasoningEffort"
from: string
to?: string
reason: string
}
type ModelSettingsCompatibilityResult = {
variant?: string
reasoningEffort?: string
changes: ModelSettingsCompatibilityChange[]
}
```
## Compatibility model
Phase 1 should be **metadata-first where the platform exposes reliable capability data**, and only fall back to family-based rules when that metadata is absent.
### Variant compatibility
Preferred source of truth:
- OpenCode/provider model metadata (`variants`)
Fallback when metadata is unavailable:
- family-based ladders
Examples of fallback ladders:
- Claude Opus family: `low`, `medium`, `high`, `max`
- Claude Sonnet/Haiku family: `low`, `medium`, `high`
- OpenAI GPT family: conservative family fallback only when metadata is missing
- Unknown family: drop unsupported values conservatively
### Reasoning effort compatibility
Current Phase 1 source of truth:
- conservative model/provider family heuristics
Reason:
- the currently available OpenCode SDK/provider metadata exposes model `variants`, but does not expose an equivalent per-model capability list for `reasoningEffort` levels
Examples:
- GPT/OpenAI-style models: `low`, `medium`, `high`, `xhigh` where supported by family heuristics
- Claude family via current OpenCode path: treat `reasoningEffort` as unsupported in Phase 1 and remove it
The resolver should remain pure model/settings logic only. Transport restrictions remain the responsibility of the request-building path.
## Separation of concerns
This design intentionally separates:
- model selection (`resolveModel...`, fallback chains)
- settings compatibility (this resolver)
- request transport compatibility (`chat.params`, prompt body constraints)
That keeps responsibilities clear:
- choose model first
- normalize settings second
- build request third
## First integration point
Phase 1 should first integrate into `chat.params`.
Why:
- it is already the centralized path for request-time tuning
- it can influence provider-facing options without leaking unsupported fields into prompt payload bodies
- it avoids trying to patch every prompt constructor at once
## Rollout plan
### Phase 1
- add resolver module and tests
- integrate into `chat.params`
- migrate `anthropic-effort` to either use the resolver or become a thin Claude-specific supplement around it
### Phase 2
- expand to `thinking`, `maxTokens`, `temperature`, `top_p`
- formalize request-path capability tables if needed
### Phase 3
- centralize all variant/reasoning normalization away from scattered hooks and ad hoc callers
## Risks
- Overfitting family rules to current model naming conventions
- Accidentally changing request semantics on paths that currently rely on implicit behavior
- Mixing provider transport limitations with model capability logic
## Mitigations
- Keep resolver pure and narrowly scoped in Phase 1
- Add explicit regression tests for keep/downgrade/drop decisions
- Integrate at one central point first (`chat.params`)
- Preserve existing behavior where desired values are already valid
## Recommendation
Proceed with the central resolver as a new, isolated implementation in a dedicated branch/worktree.
This is the clean long-term path and is more reviewable than continuing to add special-case clamps in hooks.

View File

@@ -4,7 +4,7 @@
### Problem
When using Ollama as a provider with oh-my-opencode agents, you may encounter:
When using Ollama as a provider with oh-my-openagent agents, you may encounter:
```
JSON Parse error: Unexpected EOF
@@ -26,7 +26,7 @@ Claude Code SDK expects a single JSON object, not multiple NDJSON lines, causing
**Why this happens:**
- **Ollama API**: Returns streaming responses as NDJSON by design
- **Claude Code SDK**: Doesn't properly handle NDJSON responses for tool calls
- **oh-my-opencode**: Passes through the SDK's behavior (can't fix at this layer)
- **oh-my-openagent**: Passes through the SDK's behavior (can't fix at this layer)
## Solutions
@@ -114,7 +114,7 @@ curl -s http://localhost:11434/api/chat \
## Related Issues
- **oh-my-opencode**: https://github.com/code-yeongyu/oh-my-openagent/issues/1124
- **oh-my-openagent**: https://github.com/code-yeongyu/oh-my-openagent/issues/1124
- **Ollama API Docs**: https://github.com/ollama/ollama/blob/main/docs/api.md
## Getting Help

View File

@@ -1,6 +1,6 @@
{
"name": "oh-my-opencode",
"version": "3.11.0",
"version": "3.14.0",
"description": "The Best AI Agent Harness - Batteries-Included OpenCode Plugin with Multi-Model Orchestration, Parallel Background Agents, and Crafted LSP/AST Tools",
"main": "dist/index.js",
"types": "dist/index.d.ts",
@@ -25,10 +25,12 @@
"build:all": "bun run build && bun run build:binaries",
"build:binaries": "bun run script/build-binaries.ts",
"build:schema": "bun run script/build-schema.ts",
"build:model-capabilities": "bun run script/build-model-capabilities.ts",
"clean": "rm -rf dist",
"prepare": "bun run build",
"postinstall": "node postinstall.mjs",
"prepublishOnly": "bun run clean && bun run build",
"test:model-capabilities": "bun test src/shared/model-capability-aliases.test.ts src/shared/model-capability-guardrails.test.ts src/shared/model-capabilities.test.ts src/cli/doctor/checks/model-resolution.test.ts --bail",
"typecheck": "tsc --noEmit",
"test": "bun test"
},
@@ -76,17 +78,17 @@
"typescript": "^5.7.3"
},
"optionalDependencies": {
"oh-my-opencode-darwin-arm64": "3.11.0",
"oh-my-opencode-darwin-x64": "3.11.0",
"oh-my-opencode-darwin-x64-baseline": "3.11.0",
"oh-my-opencode-linux-arm64": "3.11.0",
"oh-my-opencode-linux-arm64-musl": "3.11.0",
"oh-my-opencode-linux-x64": "3.11.0",
"oh-my-opencode-linux-x64-baseline": "3.11.0",
"oh-my-opencode-linux-x64-musl": "3.11.0",
"oh-my-opencode-linux-x64-musl-baseline": "3.11.0",
"oh-my-opencode-windows-x64": "3.11.0",
"oh-my-opencode-windows-x64-baseline": "3.11.0"
"oh-my-opencode-darwin-arm64": "3.14.0",
"oh-my-opencode-darwin-x64": "3.14.0",
"oh-my-opencode-darwin-x64-baseline": "3.14.0",
"oh-my-opencode-linux-arm64": "3.14.0",
"oh-my-opencode-linux-arm64-musl": "3.14.0",
"oh-my-opencode-linux-x64": "3.14.0",
"oh-my-opencode-linux-x64-baseline": "3.14.0",
"oh-my-opencode-linux-x64-musl": "3.14.0",
"oh-my-opencode-linux-x64-musl-baseline": "3.14.0",
"oh-my-opencode-windows-x64": "3.14.0",
"oh-my-opencode-windows-x64-baseline": "3.14.0"
},
"overrides": {
"@opencode-ai/sdk": "^1.2.24"

View File

@@ -1,6 +1,6 @@
{
"name": "oh-my-opencode-darwin-arm64",
"version": "3.11.0",
"version": "3.14.0",
"description": "Platform-specific binary for oh-my-opencode (darwin-arm64)",
"license": "MIT",
"repository": {

View File

@@ -1,6 +1,6 @@
{
"name": "oh-my-opencode-darwin-x64-baseline",
"version": "3.11.0",
"version": "3.14.0",
"description": "Platform-specific binary for oh-my-opencode (darwin-x64-baseline, no AVX2)",
"license": "MIT",
"repository": {

View File

@@ -1,6 +1,6 @@
{
"name": "oh-my-opencode-darwin-x64",
"version": "3.11.0",
"version": "3.14.0",
"description": "Platform-specific binary for oh-my-opencode (darwin-x64)",
"license": "MIT",
"repository": {

View File

@@ -1,6 +1,6 @@
{
"name": "oh-my-opencode-linux-arm64-musl",
"version": "3.11.0",
"version": "3.14.0",
"description": "Platform-specific binary for oh-my-opencode (linux-arm64-musl)",
"license": "MIT",
"repository": {

View File

@@ -1,6 +1,6 @@
{
"name": "oh-my-opencode-linux-arm64",
"version": "3.11.0",
"version": "3.14.0",
"description": "Platform-specific binary for oh-my-opencode (linux-arm64)",
"license": "MIT",
"repository": {

View File

@@ -1,6 +1,6 @@
{
"name": "oh-my-opencode-linux-x64-baseline",
"version": "3.11.0",
"version": "3.14.0",
"description": "Platform-specific binary for oh-my-opencode (linux-x64-baseline, no AVX2)",
"license": "MIT",
"repository": {

View File

@@ -1,6 +1,6 @@
{
"name": "oh-my-opencode-linux-x64-musl-baseline",
"version": "3.11.0",
"version": "3.14.0",
"description": "Platform-specific binary for oh-my-opencode (linux-x64-musl-baseline, no AVX2)",
"license": "MIT",
"repository": {

View File

@@ -1,6 +1,6 @@
{
"name": "oh-my-opencode-linux-x64-musl",
"version": "3.11.0",
"version": "3.14.0",
"description": "Platform-specific binary for oh-my-opencode (linux-x64-musl)",
"license": "MIT",
"repository": {

View File

@@ -1,6 +1,6 @@
{
"name": "oh-my-opencode-linux-x64",
"version": "3.11.0",
"version": "3.14.0",
"description": "Platform-specific binary for oh-my-opencode (linux-x64)",
"license": "MIT",
"repository": {

View File

@@ -1,6 +1,6 @@
{
"name": "oh-my-opencode-windows-x64-baseline",
"version": "3.11.0",
"version": "3.14.0",
"description": "Platform-specific binary for oh-my-opencode (windows-x64-baseline, no AVX2)",
"license": "MIT",
"repository": {

View File

@@ -1,6 +1,6 @@
{
"name": "oh-my-opencode-windows-x64",
"version": "3.11.0",
"version": "3.14.0",
"description": "Platform-specific binary for oh-my-opencode (windows-x64)",
"license": "MIT",
"repository": {

View File

@@ -1,6 +1,7 @@
// postinstall.mjs
// Runs after npm install to verify platform binary is available
import { readFileSync } from "node:fs";
import { createRequire } from "node:module";
import { getPlatformPackageCandidates, getBinaryPath } from "./bin/platform.js";
@@ -22,15 +23,26 @@ function getLibcFamily() {
}
}
function getPackageBaseName() {
try {
const packageJson = JSON.parse(readFileSync(new URL("./package.json", import.meta.url), "utf8"));
return packageJson.name || "oh-my-opencode";
} catch {
return "oh-my-opencode";
}
}
function main() {
const { platform, arch } = process;
const libcFamily = getLibcFamily();
const packageBaseName = getPackageBaseName();
try {
const packageCandidates = getPlatformPackageCandidates({
platform,
arch,
libcFamily,
packageBaseName,
});
const resolvedPackage = packageCandidates.find((pkg) => {

View File

@@ -101,7 +101,9 @@ async function main() {
console.log("\n✅ All platform binaries built successfully!\n");
}
main().catch((error) => {
console.error("Fatal error:", error);
process.exit(1);
});
if (import.meta.main) {
main().catch((error) => {
console.error("Fatal error:", error);
process.exit(1);
});
}

View File

@@ -0,0 +1,13 @@
import { writeFileSync } from "fs"
import { resolve } from "path"
import {
fetchModelCapabilitiesSnapshot,
MODELS_DEV_SOURCE_URL,
} from "../src/shared/model-capabilities-cache"
const OUTPUT_PATH = resolve(import.meta.dir, "../src/generated/model-capabilities.generated.json")
console.log(`Fetching model capabilities snapshot from ${MODELS_DEV_SOURCE_URL}...`)
const snapshot = await fetchModelCapabilitiesSnapshot()
writeFileSync(OUTPUT_PATH, `${JSON.stringify(snapshot, null, 2)}\n`)
console.log(`Generated ${OUTPUT_PATH} with ${Object.keys(snapshot.models).length} models`)

View File

@@ -34,6 +34,72 @@ async function generateChangelog(previousTag: string): Promise<string[]> {
return notes
}
async function getChangedFiles(previousTag: string): Promise<string[]> {
try {
const diff = await $`git diff --name-only ${previousTag}..HEAD`.text()
return diff
.split("\n")
.map((line) => line.trim())
.filter(Boolean)
} catch {
return []
}
}
function touchesAnyPath(files: string[], candidates: string[]): boolean {
return files.some((file) => candidates.some((candidate) => file === candidate || file.startsWith(`${candidate}/`)))
}
function buildReleaseFraming(files: string[]): string[] {
const bullets: string[] = []
if (
touchesAnyPath(files, [
"src/index.ts",
"src/plugin-config.ts",
"bin/platform.js",
"postinstall.mjs",
"docs",
])
) {
bullets.push("Rename transition updates across package detection, plugin/config compatibility, and install surfaces.")
}
if (touchesAnyPath(files, ["src/tools/delegate-task", "src/plugin/tool-registry.ts"])) {
bullets.push("Task and tool behavior updates, including delegate-task contract and runtime registration behavior.")
}
if (
touchesAnyPath(files, [
"src/plugin/tool-registry.ts",
"src/plugin-handlers/agent-config-handler.ts",
"src/plugin-handlers/tool-config-handler.ts",
"src/hooks/tasks-todowrite-disabler",
])
) {
bullets.push("Task-system default behavior alignment so omitted configuration behaves consistently across runtime paths.")
}
if (touchesAnyPath(files, [".github/workflows", "docs/guide/installation.md", "postinstall.mjs"])) {
bullets.push("Install and publish workflow hardening, including safer release sequencing and package/install fixes.")
}
if (bullets.length === 0) {
return []
}
return [
"## Minor Compatibility and Stability Release",
"",
"This release carries compatibility-facing behavior changes and operational hardening. Read the summary below before upgrading or publishing.",
"",
...bullets.map((bullet) => `- ${bullet}`),
"",
"## Commit Summary",
"",
]
}
async function getContributors(previousTag: string): Promise<string[]> {
const notes: string[] = []
@@ -78,9 +144,11 @@ async function main() {
process.exit(0)
}
const changedFiles = await getChangedFiles(previousTag)
const changelog = await generateChangelog(previousTag)
const contributors = await getContributors(previousTag)
const notes = [...changelog, ...contributors]
const framing = buildReleaseFraming(changedFiles)
const notes = [...framing, ...changelog, ...contributors]
if (notes.length === 0) {
console.log("No notable changes")

View File

@@ -2239,6 +2239,166 @@
"created_at": "2026-03-17T20:42:42Z",
"repoId": 1108837393,
"pullRequestNo": 2656
},
{
"name": "walioo",
"id": 25835823,
"comment_id": 4087098221,
"created_at": "2026-03-19T02:13:02Z",
"repoId": 1108837393,
"pullRequestNo": 2688
},
{
"name": "trafgals",
"id": 6454757,
"comment_id": 4087725932,
"created_at": "2026-03-19T04:22:32Z",
"repoId": 1108837393,
"pullRequestNo": 2690
},
{
"name": "tonymfer",
"id": 66512584,
"comment_id": 4091847232,
"created_at": "2026-03-19T17:13:49Z",
"repoId": 1108837393,
"pullRequestNo": 2701
},
{
"name": "nguyentamdat",
"id": 16253213,
"comment_id": 4096267323,
"created_at": "2026-03-20T07:34:22Z",
"repoId": 1108837393,
"pullRequestNo": 2718
},
{
"name": "whackur",
"id": 26926041,
"comment_id": 4102330445,
"created_at": "2026-03-21T05:27:17Z",
"repoId": 1108837393,
"pullRequestNo": 2733
},
{
"name": "ndaemy",
"id": 18691542,
"comment_id": 4103008804,
"created_at": "2026-03-21T10:18:22Z",
"repoId": 1108837393,
"pullRequestNo": 2734
},
{
"name": "0xYiliu",
"id": 3838688,
"comment_id": 4104738337,
"created_at": "2026-03-21T22:59:33Z",
"repoId": 1108837393,
"pullRequestNo": 2738
},
{
"name": "hunghoang3011",
"id": 65234777,
"comment_id": 4107900881,
"created_at": "2026-03-23T04:28:20Z",
"repoId": 1108837393,
"pullRequestNo": 2758
},
{
"name": "anas-asghar4831",
"id": 110368394,
"comment_id": 4128950310,
"created_at": "2026-03-25T18:48:19Z",
"repoId": 1108837393,
"pullRequestNo": 2837
},
{
"name": "clansty",
"id": 18461360,
"comment_id": 4129934858,
"created_at": "2026-03-25T21:33:35Z",
"repoId": 1108837393,
"pullRequestNo": 2839
},
{
"name": "ventsislav-georgiev",
"id": 5616486,
"comment_id": 4130417794,
"created_at": "2026-03-25T23:11:32Z",
"repoId": 1108837393,
"pullRequestNo": 2840
},
{
"name": "kuitos",
"id": 5206843,
"comment_id": 4133207953,
"created_at": "2026-03-26T09:55:49Z",
"repoId": 1108837393,
"pullRequestNo": 2833
},
{
"name": "Jholly2008",
"id": 29773273,
"comment_id": 4139918265,
"created_at": "2026-03-27T03:37:00Z",
"repoId": 1108837393,
"pullRequestNo": 2871
},
{
"name": "WhiteGiverMa",
"id": 152406589,
"comment_id": 4140294245,
"created_at": "2026-03-27T05:26:37Z",
"repoId": 1108837393,
"pullRequestNo": 2877
},
{
"name": "codivedev",
"id": 249558739,
"comment_id": 4142164072,
"created_at": "2026-03-27T12:11:45Z",
"repoId": 1108837393,
"pullRequestNo": 2888
},
{
"name": "AlexDochioiu",
"id": 38853913,
"comment_id": 4147980685,
"created_at": "2026-03-28T12:20:42Z",
"repoId": 1108837393,
"pullRequestNo": 2916
},
{
"name": "ryandielhenn",
"id": 35785891,
"comment_id": 4148508024,
"created_at": "2026-03-28T17:46:50Z",
"repoId": 1108837393,
"pullRequestNo": 2919
},
{
"name": "lorenzo-dallamuta",
"id": 66994937,
"comment_id": 4148848505,
"created_at": "2026-03-28T21:30:15Z",
"repoId": 1108837393,
"pullRequestNo": 2925
},
{
"name": "quangtran88",
"id": 107824159,
"comment_id": 4149327240,
"created_at": "2026-03-29T03:21:39Z",
"repoId": 1108837393,
"pullRequestNo": 2929
},
{
"name": "HOYALIM",
"id": 166576253,
"comment_id": 4149626853,
"created_at": "2026-03-29T07:31:36Z",
"repoId": 1108837393,
"pullRequestNo": 2935
}
]
}

View File

@@ -14,7 +14,7 @@ Entry point `index.ts` orchestrates 5-step initialization: loadConfig → create
| `plugin-config.ts` | JSONC parse, multi-level merge, Zod v4 validation |
| `create-managers.ts` | TmuxSessionManager, BackgroundManager, SkillMcpManager, ConfigHandler |
| `create-tools.ts` | SkillContext + AvailableCategories + ToolRegistry (26 tools) |
| `create-hooks.ts` | 3-tier: Core(37) + Continuation(7) + Skill(2) = 46 hooks |
| `create-hooks.ts` | 3-tier: Core(39) + Continuation(7) + Skill(2) = 48 hooks |
| `plugin-interface.ts` | 8 OpenCode hook handlers: config, tool, chat.message, chat.params, chat.headers, event, tool.execute.before, tool.execute.after |
## CONFIG LOADING
@@ -32,10 +32,10 @@ loadPluginConfig(directory, ctx)
```
createHooks()
├─→ createCoreHooks() # 37 hooks
├─→ createCoreHooks() # 39 hooks
│ ├─ createSessionHooks() # 23: contextWindowMonitor, thinkMode, ralphLoop, modelFallback, runtimeFallback, noSisyphusGpt, noHephaestusNonGpt, anthropicEffort, intentGate...
│ ├─ createToolGuardHooks() # 10: commentChecker, rulesInjector, writeExistingFileGuard, jsonErrorRecovery, hashlineReadEnhancer...
│ ├─ createToolGuardHooks() # 12: commentChecker, rulesInjector, writeExistingFileGuard, jsonErrorRecovery, hashlineReadEnhancer...
│ └─ createTransformHooks() # 4: claudeCodeHooks, keywordDetector, contextInjector, thinkingBlockValidator
├─→ createContinuationHooks() # 7: todoContinuationEnforcer, atlas, stopContinuationGuard, ralphLoopActivator...
├─→ createContinuationHooks() # 7: todoContinuationEnforcer, atlas, stopContinuationGuard, compactionContextInjector...
└─→ createSkillHooks() # 2: categorySkillReminder, autoSlashCommand
```

View File

@@ -11,10 +11,10 @@ Agent factories following `createXXXAgent(model) → AgentConfig` pattern. Each
| Agent | Model | Temp | Mode | Fallback Chain | Purpose |
|-------|-------|------|------|----------------|---------|
| **Sisyphus** | claude-opus-4-6 max | 0.1 | all | k2p5 → kimi-k2.5 → gpt-5.4 medium → glm-5 → big-pickle | Main orchestrator, plans + delegates |
| **Hephaestus** | gpt-5.3-codex medium | 0.1 | all | gpt-5.4 medium (copilot) | Autonomous deep worker |
| **Hephaestus** | gpt-5.4 medium | 0.1 | all | | Autonomous deep worker |
| **Oracle** | gpt-5.4 high | 0.1 | subagent | gemini-3.1-pro high → claude-opus-4-6 max | Read-only consultation |
| **Librarian** | gemini-3-flash | 0.1 | subagent | minimax-m2.5-free → big-pickle | External docs/code search |
| **Explore** | grok-code-fast-1 | 0.1 | subagent | minimax-m2.5-free → claude-haiku-4-5 → gpt-5-nano | Contextual grep |
| **Librarian** | minimax-m2.7 | 0.1 | subagent | minimax-m2.7-highspeedclaude-haiku-4-5 → gpt-5-nano | External docs/code search |
| **Explore** | grok-code-fast-1 | 0.1 | subagent | minimax-m2.7-highspeed → minimax-m2.7 → claude-haiku-4-5 → gpt-5-nano | Contextual grep |
| **Multimodal-Looker** | gpt-5.3-codex medium | 0.1 | subagent | k2p5 → gemini-3-flash → glm-4.6v → gpt-5-nano | PDF/image analysis |
| **Metis** | claude-opus-4-6 max | **0.3** | subagent | gpt-5.4 high → gemini-3.1-pro high | Pre-planning consultant |
| **Momus** | gpt-5.4 xhigh | 0.1 | subagent | claude-opus-4-6 max → gemini-3.1-pro high | Plan reviewer |

View File

@@ -44,6 +44,10 @@ export function mergeAgentConfig(
const { prompt_append, ...rest } = migratedOverride
const merged = deepMerge(base, rest as Partial<AgentConfig>)
if (merged.prompt && typeof merged.prompt === 'string' && merged.prompt.startsWith('file://')) {
merged.prompt = resolvePromptAppend(merged.prompt, directory)
}
if (prompt_append && merged.prompt) {
merged.prompt = merged.prompt + "\n" + resolvePromptAppend(prompt_append, directory)
}

View File

@@ -39,7 +39,7 @@ export function maybeCreateAtlasConfig(input: {
const atlasRequirement = AGENT_MODEL_REQUIREMENTS["atlas"]
const atlasResolution = applyModelResolution({
uiSelectedModel: orchestratorOverride?.model ? undefined : uiSelectedModel,
uiSelectedModel: orchestratorOverride?.model !== undefined ? undefined : uiSelectedModel,
userModel: orchestratorOverride?.model,
requirement: atlasRequirement,
availableModels,

View File

@@ -8,6 +8,7 @@ import { buildAgent, isFactory } from "../agent-builder"
import { applyOverrides } from "./agent-overrides"
import { applyEnvironmentContext } from "./environment-context"
import { applyModelResolution, getFirstFallbackModel } from "./model-resolution"
import { log } from "../../shared/logger"
export function collectPendingBuiltinAgents(input: {
agentSources: Record<BuiltinAgentName, import("../agent-builder").AgentSource>
@@ -69,14 +70,24 @@ export function collectPendingBuiltinAgents(input: {
const isPrimaryAgent = isFactory(source) && source.mode === "primary"
let resolution = applyModelResolution({
uiSelectedModel: (isPrimaryAgent && !override?.model) ? uiSelectedModel : undefined,
uiSelectedModel: (isPrimaryAgent && override?.model === undefined) ? uiSelectedModel : undefined,
userModel: override?.model,
requirement,
availableModels,
systemDefaultModel,
})
if (!resolution && isFirstRunNoCache && !override?.model) {
resolution = getFirstFallbackModel(requirement)
if (!resolution) {
if (override?.model) {
// User explicitly configured a model but resolution failed (e.g., cold cache).
// Honor the user's choice directly instead of falling back to hardcoded chain.
log("[agent-registration] User-configured model not resolved, using as-is", {
agent: agentName,
configuredModel: override.model,
})
resolution = { model: override.model, provenance: "override" as const }
} else {
resolution = getFirstFallbackModel(requirement)
}
}
if (!resolution) continue
const { model, variant: resolvedVariant } = resolution

View File

@@ -52,7 +52,7 @@ export function maybeCreateSisyphusConfig(input: {
if (disabledAgents.includes("sisyphus") || !meetsSisyphusAnyModelRequirement) return undefined
let sisyphusResolution = applyModelResolution({
uiSelectedModel: sisyphusOverride?.model ? undefined : uiSelectedModel,
uiSelectedModel: sisyphusOverride?.model !== undefined ? undefined : uiSelectedModel,
userModel: sisyphusOverride?.model,
requirement: sisyphusRequirement,
availableModels,

View File

@@ -181,7 +181,7 @@ describe("buildParallelDelegationSection", () => {
it("#given non-Claude model with deep category #when building #then returns aggressive delegation section", () => {
//#given
const model = "google/gemini-3-pro"
const model = "google/gemini-3.1-pro"
const categories = [deepCategory, otherCategory]
//#when
@@ -237,7 +237,7 @@ describe("buildParallelDelegationSection", () => {
describe("buildNonClaudePlannerSection", () => {
it("#given non-Claude model #when building #then returns plan agent section", () => {
//#given
const model = "google/gemini-3-pro"
const model = "google/gemini-3.1-pro"
//#when
const result = buildNonClaudePlannerSection(model)
@@ -272,4 +272,3 @@ describe("buildNonClaudePlannerSection", () => {
})
})

View File

@@ -308,6 +308,12 @@ Briefly announce "Consulting Oracle for [reason]" before invocation.
**Collect Oracle results before your final answer. No exceptions.**
**Oracle-dependent implementation is BLOCKED until Oracle finishes.**
- If you asked Oracle for architecture/debugging direction that affects the fix, do not implement before Oracle result arrives.
- While waiting, only do non-overlapping prep work. Never ship implementation decisions Oracle was asked to decide.
- Never "time out and continue anyway" for Oracle-dependent tasks.
- Oracle takes minutes. When done with your own work: **end your response** — wait for the \`<system-reminder>\`.
- Do NOT poll \`background_output\` on a running Oracle. The notification will come.
- Never cancel Oracle.

View File

@@ -162,6 +162,10 @@ Asking the user is the LAST resort after exhausting creative alternatives.
- User asks a question implying work → Answer briefly, DO the implied work in the same turn
- You wrote a plan in your response → EXECUTE the plan before ending turn — plans are starting lines, not finish lines
### Task Scope Clarification
You handle multi-step sub-tasks of a SINGLE GOAL. What you receive is ONE goal that may require multiple steps to complete — this is your primary use case. Only reject when given MULTIPLE INDEPENDENT goals in one request.
## Hard Constraints
${hardBlocks}

View File

@@ -121,6 +121,10 @@ When blocked: try a different approach → decompose the problem → challenge a
- User asks a question implying work → Answer briefly, DO the implied work in the same turn
- You wrote a plan in your response → EXECUTE the plan before ending turn — plans are starting lines, not finish lines
### Task Scope Clarification
You handle multi-step sub-tasks of a SINGLE GOAL. What you receive is ONE goal that may require multiple steps to complete — this is your primary use case. Only reject when given MULTIPLE INDEPENDENT goals in one request.
## Hard Constraints
${hardBlocks}

View File

@@ -112,6 +112,10 @@ Asking the user is the LAST resort after exhausting creative alternatives.
- Note assumptions in final message, not as questions mid-work
- Need context? Fire explore/librarian in background IMMEDIATELY — continue only with non-overlapping work while they search
### Task Scope Clarification
You handle multi-step sub-tasks of a SINGLE GOAL. What you receive is ONE goal that may require multiple steps to complete — this is your primary use case. Only reject when given MULTIPLE INDEPENDENT goals in one request.
## Hard Constraints
${hardBlocks}

View File

@@ -0,0 +1,42 @@
import { describe, it, expect } from "bun:test"
import { getPrometheusPrompt } from "./system-prompt"
describe("getPrometheusPrompt", () => {
describe("#given question tool is not disabled", () => {
describe("#when generating prompt", () => {
it("#then should include Question tool references", () => {
const prompt = getPrometheusPrompt(undefined, [])
expect(prompt).toContain("Question({")
})
})
})
describe("#given question tool is disabled via disabled_tools", () => {
describe("#when generating prompt", () => {
it("#then should strip Question tool code examples", () => {
const prompt = getPrometheusPrompt(undefined, ["question"])
expect(prompt).not.toContain("Question({")
})
})
describe("#when disabled_tools includes question among other tools", () => {
it("#then should strip Question tool code examples", () => {
const prompt = getPrometheusPrompt(undefined, ["todowrite", "question", "interactive_bash"])
expect(prompt).not.toContain("Question({")
})
})
})
describe("#given no disabled_tools provided", () => {
describe("#when generating prompt with undefined", () => {
it("#then should include Question tool references", () => {
const prompt = getPrometheusPrompt(undefined, undefined)
expect(prompt).toContain("Question({")
})
})
})
})

View File

@@ -52,16 +52,34 @@ export function getPrometheusPromptSource(model?: string): PrometheusPromptSourc
* Gemini models → Gemini-optimized prompt (aggressive tool-call enforcement, thinking checkpoints)
* Default (Claude, etc.) → Claude-optimized prompt (modular sections)
*/
export function getPrometheusPrompt(model?: string): string {
export function getPrometheusPrompt(model?: string, disabledTools?: readonly string[]): string {
const source = getPrometheusPromptSource(model)
const isQuestionDisabled = disabledTools?.includes("question") ?? false
let prompt: string
switch (source) {
case "gpt":
return getGptPrometheusPrompt()
prompt = getGptPrometheusPrompt()
break
case "gemini":
return getGeminiPrometheusPrompt()
prompt = getGeminiPrometheusPrompt()
break
case "default":
default:
return PROMETHEUS_SYSTEM_PROMPT
prompt = PROMETHEUS_SYSTEM_PROMPT
}
if (isQuestionDisabled) {
prompt = stripQuestionToolReferences(prompt)
}
return prompt
}
/**
* Removes Question tool usage examples from prompt text when question tool is disabled.
*/
function stripQuestionToolReferences(prompt: string): string {
// Remove Question({...}) code blocks (multi-line)
return prompt.replace(/```typescript\n\s*Question\(\{[\s\S]*?\}\)\s*\n```/g, "")
}

View File

@@ -35,6 +35,11 @@ Task NOT complete without:
- ${verificationText}
</Verification>
<Termination>
STOP after first successful verification. Do NOT re-verify.
Maximum status checks: 2. Then stop regardless.
</Termination>
<Style>
- Start immediately. No acknowledgments.
- Match user's communication style.

View File

@@ -127,6 +127,12 @@ This verbalization anchors your routing decision and makes your reasoning transp
- **Open-ended** ("Improve", "Refactor", "Add feature") → Assess codebase first
- **Ambiguous** (unclear scope, multiple interpretations) → Ask ONE clarifying question
### Step 1.5: Turn-Local Intent Reset (MANDATORY)
- Reclassify intent from the CURRENT user message only. Never auto-carry "implementation mode" from prior turns.
- If current message is a question/explanation/investigation request, answer/analyze only. Do NOT create todos or edit files.
- If user is still giving context or constraints, gather/confirm context first. Do NOT start implementation yet.
### Step 2: Check for Ambiguity
- Single valid interpretation → Proceed
@@ -135,6 +141,15 @@ This verbalization anchors your routing decision and makes your reasoning transp
- Missing critical info (file, error, context) → **MUST ask**
- User's design seems flawed or suboptimal → **MUST raise concern** before implementing
### Step 2.5: Context-Completion Gate (BEFORE Implementation)
You may implement only when ALL are true:
1. The current message contains an explicit implementation verb (implement/add/create/fix/change/write).
2. Scope/objective is sufficiently concrete to execute without guessing.
3. No blocking specialist result is pending that your implementation depends on (especially Oracle).
If any condition fails, do research/clarification only, then wait.
### Step 3: Validate Before Acting
**Assumptions Check:**

View File

@@ -167,6 +167,11 @@ Complexity:
- Open-ended ("improve", "refactor") → assess codebase first, then propose
- Ambiguous (multiple interpretations with 2x+ effort difference) → ask ONE question
Turn-local reset (mandatory): classify from the CURRENT user message, not conversation momentum.
- Never carry implementation mode from prior turns.
- If current turn is question/explanation/investigation, answer or analyze only.
- If user appears to still be providing context, gather/confirm context first and wait.
Domain guess (provisional — finalized in ROUTE after exploration):
- Visual (UI, CSS, styling, layout, design, animation) → likely visual-engineering
- Logic (algorithms, architecture, complex business logic) → likely ultrabrain
@@ -184,6 +189,11 @@ Step 2 — Check before acting:
- Missing critical info → ask
- User's design seems flawed → raise concern concisely, propose alternative, ask if they want to proceed anyway
Context-completion gate before implementation:
- Implement only when the current message explicitly requests implementation (implement/add/create/fix/change/write),
scope is concrete enough to execute without guessing, and no blocking specialist result is pending.
- If any condition fails, continue with research/clarification only and wait.
<ask_gate>
Proceed unless:
(a) the action is irreversible,

View File

@@ -1,5 +1,5 @@
import { describe, test, expect } from "bun:test";
import { isGptModel, isGeminiModel, isGpt5_4Model } from "./types";
import { isGptModel, isGeminiModel, isGpt5_4Model, isMiniMaxModel } from "./types";
describe("isGpt5_4Model", () => {
test("detects gpt-5.4 models", () => {
@@ -79,6 +79,28 @@ describe("isGptModel", () => {
});
});
describe("isMiniMaxModel", () => {
test("detects minimax models with provider prefix", () => {
expect(isMiniMaxModel("opencode-go/minimax-m2.7")).toBe(true);
expect(isMiniMaxModel("opencode/minimax-m2.7-highspeed")).toBe(true);
expect(isMiniMaxModel("opencode-go/minimax-m2.5")).toBe(true);
expect(isMiniMaxModel("opencode/minimax-m2.5-free")).toBe(true);
});
test("detects minimax models without provider prefix", () => {
expect(isMiniMaxModel("minimax-m2.7")).toBe(true);
expect(isMiniMaxModel("minimax-m2.7-highspeed")).toBe(true);
expect(isMiniMaxModel("minimax-m2.5")).toBe(true);
});
test("does not match non-minimax models", () => {
expect(isMiniMaxModel("openai/gpt-5.4")).toBe(false);
expect(isMiniMaxModel("anthropic/claude-opus-4-6")).toBe(false);
expect(isMiniMaxModel("google/gemini-3.1-pro")).toBe(false);
expect(isMiniMaxModel("opencode-go/kimi-k2.5")).toBe(false);
});
});
describe("isGeminiModel", () => {
test("#given google provider models #then returns true", () => {
expect(isGeminiModel("google/gemini-3.1-pro")).toBe(true);

View File

@@ -91,6 +91,11 @@ export function isGpt5_3CodexModel(model: string): boolean {
const GEMINI_PROVIDERS = ["google/", "google-vertex/"];
export function isMiniMaxModel(model: string): boolean {
const modelName = extractModelName(model).toLowerCase();
return modelName.includes("minimax");
}
export function isGeminiModel(model: string): boolean {
if (GEMINI_PROVIDERS.some((prefix) => model.startsWith(prefix))) return true;
@@ -123,7 +128,7 @@ export type AgentName = BuiltinAgentName;
export type AgentOverrideConfig = Partial<AgentConfig> & {
prompt_append?: string;
variant?: string;
fallback_models?: string | string[];
fallback_models?: string | (string | import("../config/schema/fallback-models").FallbackModelObject)[];
};
export type AgentOverrides = Partial<

View File

@@ -642,7 +642,7 @@ describe("createBuiltinAgents with requiresProvider gating (hephaestus)", () =>
// #then
expect(agents.hephaestus).toBeDefined()
expect(agents.hephaestus.model).toBe("openai/gpt-5.3-codex")
expect(agents.hephaestus.model).toBe("openai/gpt-5.4")
} finally {
cacheSpy.mockRestore()
fetchSpy.mockRestore()

View File

@@ -202,7 +202,7 @@ exports[`generateModelConfig single native provider uses OpenAI models when only
"variant": "medium",
},
"hephaestus": {
"model": "openai/gpt-5.3-codex",
"model": "openai/gpt-5.4",
"variant": "medium",
},
"librarian": {
@@ -248,8 +248,7 @@ exports[`generateModelConfig single native provider uses OpenAI models when only
"variant": "medium",
},
"quick": {
"model": "openai/gpt-5.3-codex",
"variant": "low",
"model": "openai/gpt-5.4-mini",
},
"ultrabrain": {
"model": "openai/gpt-5.4",
@@ -288,7 +287,7 @@ exports[`generateModelConfig single native provider uses OpenAI models with isMa
"variant": "medium",
},
"hephaestus": {
"model": "openai/gpt-5.3-codex",
"model": "openai/gpt-5.4",
"variant": "medium",
},
"librarian": {
@@ -334,8 +333,7 @@ exports[`generateModelConfig single native provider uses OpenAI models with isMa
"variant": "medium",
},
"quick": {
"model": "openai/gpt-5.3-codex",
"variant": "low",
"model": "openai/gpt-5.4-mini",
},
"ultrabrain": {
"model": "openai/gpt-5.4",
@@ -492,7 +490,7 @@ exports[`generateModelConfig all native providers uses preferred models from fal
"model": "anthropic/claude-haiku-4-5",
},
"hephaestus": {
"model": "openai/gpt-5.3-codex",
"model": "openai/gpt-5.4",
"variant": "medium",
},
"metis": {
@@ -533,7 +531,7 @@ exports[`generateModelConfig all native providers uses preferred models from fal
"variant": "medium",
},
"quick": {
"model": "anthropic/claude-haiku-4-5",
"model": "openai/gpt-5.4-mini",
},
"ultrabrain": {
"model": "openai/gpt-5.4",
@@ -567,7 +565,7 @@ exports[`generateModelConfig all native providers uses preferred models with isM
"model": "anthropic/claude-haiku-4-5",
},
"hephaestus": {
"model": "openai/gpt-5.3-codex",
"model": "openai/gpt-5.4",
"variant": "medium",
},
"metis": {
@@ -608,7 +606,7 @@ exports[`generateModelConfig all native providers uses preferred models with isM
"variant": "medium",
},
"quick": {
"model": "anthropic/claude-haiku-4-5",
"model": "openai/gpt-5.4-mini",
},
"ultrabrain": {
"model": "openai/gpt-5.4",
@@ -643,7 +641,7 @@ exports[`generateModelConfig fallback providers uses OpenCode Zen models when on
"model": "opencode/claude-haiku-4-5",
},
"hephaestus": {
"model": "opencode/gpt-5.3-codex",
"model": "opencode/gpt-5.4",
"variant": "medium",
},
"metis": {
@@ -684,7 +682,7 @@ exports[`generateModelConfig fallback providers uses OpenCode Zen models when on
"variant": "medium",
},
"quick": {
"model": "opencode/claude-haiku-4-5",
"model": "opencode/gpt-5.4-mini",
},
"ultrabrain": {
"model": "opencode/gpt-5.4",
@@ -718,7 +716,7 @@ exports[`generateModelConfig fallback providers uses OpenCode Zen models with is
"model": "opencode/claude-haiku-4-5",
},
"hephaestus": {
"model": "opencode/gpt-5.3-codex",
"model": "opencode/gpt-5.4",
"variant": "medium",
},
"metis": {
@@ -759,7 +757,7 @@ exports[`generateModelConfig fallback providers uses OpenCode Zen models with is
"variant": "medium",
},
"quick": {
"model": "opencode/claude-haiku-4-5",
"model": "opencode/gpt-5.4-mini",
},
"ultrabrain": {
"model": "opencode/gpt-5.4",
@@ -830,7 +828,7 @@ exports[`generateModelConfig fallback providers uses GitHub Copilot models when
"variant": "high",
},
"quick": {
"model": "github-copilot/claude-haiku-4.5",
"model": "github-copilot/gpt-5.4-mini",
},
"ultrabrain": {
"model": "github-copilot/gemini-3.1-pro-preview",
@@ -900,7 +898,7 @@ exports[`generateModelConfig fallback providers uses GitHub Copilot models with
"variant": "high",
},
"quick": {
"model": "github-copilot/claude-haiku-4.5",
"model": "github-copilot/gpt-5.4-mini",
},
"ultrabrain": {
"model": "github-copilot/gemini-3.1-pro-preview",
@@ -1051,7 +1049,7 @@ exports[`generateModelConfig mixed provider scenarios uses Claude + OpenCode Zen
"model": "anthropic/claude-haiku-4-5",
},
"hephaestus": {
"model": "opencode/gpt-5.3-codex",
"model": "opencode/gpt-5.4",
"variant": "medium",
},
"metis": {
@@ -1092,7 +1090,7 @@ exports[`generateModelConfig mixed provider scenarios uses Claude + OpenCode Zen
"variant": "medium",
},
"quick": {
"model": "anthropic/claude-haiku-4-5",
"model": "opencode/gpt-5.4-mini",
},
"ultrabrain": {
"model": "opencode/gpt-5.4",
@@ -1126,7 +1124,7 @@ exports[`generateModelConfig mixed provider scenarios uses OpenAI + Copilot comb
"model": "github-copilot/gpt-5-mini",
},
"hephaestus": {
"model": "openai/gpt-5.3-codex",
"model": "openai/gpt-5.4",
"variant": "medium",
},
"metis": {
@@ -1167,7 +1165,7 @@ exports[`generateModelConfig mixed provider scenarios uses OpenAI + Copilot comb
"variant": "medium",
},
"quick": {
"model": "github-copilot/claude-haiku-4.5",
"model": "openai/gpt-5.4-mini",
},
"ultrabrain": {
"model": "openai/gpt-5.4",
@@ -1331,7 +1329,7 @@ exports[`generateModelConfig mixed provider scenarios uses all fallback provider
"model": "opencode/claude-haiku-4-5",
},
"hephaestus": {
"model": "opencode/gpt-5.3-codex",
"model": "github-copilot/gpt-5.4",
"variant": "medium",
},
"librarian": {
@@ -1375,7 +1373,7 @@ exports[`generateModelConfig mixed provider scenarios uses all fallback provider
"variant": "medium",
},
"quick": {
"model": "github-copilot/claude-haiku-4.5",
"model": "github-copilot/gpt-5.4-mini",
},
"ultrabrain": {
"model": "opencode/gpt-5.4",
@@ -1409,7 +1407,7 @@ exports[`generateModelConfig mixed provider scenarios uses all providers togethe
"model": "anthropic/claude-haiku-4-5",
},
"hephaestus": {
"model": "openai/gpt-5.3-codex",
"model": "openai/gpt-5.4",
"variant": "medium",
},
"librarian": {
@@ -1453,7 +1451,7 @@ exports[`generateModelConfig mixed provider scenarios uses all providers togethe
"variant": "medium",
},
"quick": {
"model": "anthropic/claude-haiku-4-5",
"model": "openai/gpt-5.4-mini",
},
"ultrabrain": {
"model": "openai/gpt-5.4",
@@ -1487,7 +1485,7 @@ exports[`generateModelConfig mixed provider scenarios uses all providers with is
"model": "anthropic/claude-haiku-4-5",
},
"hephaestus": {
"model": "openai/gpt-5.3-codex",
"model": "openai/gpt-5.4",
"variant": "medium",
},
"librarian": {
@@ -1531,7 +1529,7 @@ exports[`generateModelConfig mixed provider scenarios uses all providers with is
"variant": "medium",
},
"quick": {
"model": "anthropic/claude-haiku-4-5",
"model": "openai/gpt-5.4-mini",
},
"ultrabrain": {
"model": "openai/gpt-5.4",

View File

@@ -1,4 +1,5 @@
import color from "picocolors"
import { PLUGIN_NAME } from "../shared"
import type { InstallArgs } from "./types"
import {
addPluginToOpenCodeConfig,
@@ -32,7 +33,7 @@ export async function runCliInstaller(args: InstallArgs, version: string): Promi
}
console.log()
printInfo(
"Usage: bunx oh-my-opencode install --no-tui --claude=<no|yes|max20> --gemini=<no|yes> --copilot=<no|yes>",
`Usage: bunx ${PLUGIN_NAME} install --no-tui --claude=<no|yes|max20> --gemini=<no|yes> --copilot=<no|yes>`,
)
console.log()
return 1
@@ -65,7 +66,7 @@ export async function runCliInstaller(args: InstallArgs, version: string): Promi
const config = argsToConfig(args)
printStep(step++, totalSteps, "Adding oh-my-opencode plugin...")
printStep(step++, totalSteps, `Adding ${PLUGIN_NAME} plugin...`)
const pluginResult = await addPluginToOpenCodeConfig(version)
if (!pluginResult.success) {
printError(`Failed: ${pluginResult.error}`)
@@ -75,7 +76,7 @@ export async function runCliInstaller(args: InstallArgs, version: string): Promi
`Plugin ${isUpdate ? "verified" : "added"} ${SYMBOLS.arrow} ${color.dim(pluginResult.configPath)}`,
)
printStep(step++, totalSteps, "Writing oh-my-opencode configuration...")
printStep(step++, totalSteps, `Writing ${PLUGIN_NAME} configuration...`)
const omoResult = writeOmoConfig(config)
if (!omoResult.success) {
printError(`Failed: ${omoResult.error}`)

View File

@@ -3,6 +3,7 @@ import { install } from "./install"
import { run } from "./run"
import { getLocalVersion } from "./get-local-version"
import { doctor } from "./doctor"
import { refreshModelCapabilities } from "./refresh-model-capabilities"
import { createMcpOAuthCommand } from "./mcp-oauth"
import type { InstallArgs } from "./types"
import type { RunOptions } from "./run"
@@ -42,7 +43,7 @@ Examples:
Model Providers (Priority: Native > Copilot > OpenCode Zen > Z.ai > Kimi):
Claude Native anthropic/ models (Opus, Sonnet, Haiku)
OpenAI Native openai/ models (GPT-5.4 for Oracle)
Gemini Native google/ models (Gemini 3 Pro, Flash)
Gemini Native google/ models (Gemini 3.1 Pro, Flash)
Copilot github-copilot/ models (fallback)
OpenCode Zen opencode/ models (opencode/claude-opus-4-6, etc.)
Z.ai zai-coding-plan/glm-5 (visual-engineering fallback)
@@ -176,6 +177,21 @@ Examples:
process.exit(exitCode)
})
program
.command("refresh-model-capabilities")
.description("Refresh the cached models.dev-based model capabilities snapshot")
.option("-d, --directory <path>", "Working directory to read oh-my-opencode config from")
.option("--source-url <url>", "Override the models.dev source URL")
.option("--json", "Output refresh summary as JSON")
.action(async (options) => {
const exitCode = await refreshModelCapabilities({
directory: options.directory,
sourceUrl: options.sourceUrl,
json: options.json ?? false,
})
process.exit(exitCode)
})
program
.command("version")
.description("Show version information")

View File

@@ -1,300 +0,0 @@
import { describe, expect, test, mock, afterEach } from "bun:test"
import { getPluginNameWithVersion, fetchNpmDistTags, generateOmoConfig } from "./config-manager"
import type { InstallConfig } from "./types"
describe("getPluginNameWithVersion", () => {
const originalFetch = globalThis.fetch
afterEach(() => {
globalThis.fetch = originalFetch
})
test("returns @latest when current version matches latest tag", async () => {
// #given npm dist-tags with latest=2.14.0
globalThis.fetch = mock(() =>
Promise.resolve({
ok: true,
json: () => Promise.resolve({ latest: "2.14.0", beta: "3.0.0-beta.3" }),
} as Response)
) as unknown as typeof fetch
// #when current version is 2.14.0
const result = await getPluginNameWithVersion("2.14.0")
// #then should use @latest tag
expect(result).toBe("oh-my-opencode@latest")
})
test("returns @beta when current version matches beta tag", async () => {
// #given npm dist-tags with beta=3.0.0-beta.3
globalThis.fetch = mock(() =>
Promise.resolve({
ok: true,
json: () => Promise.resolve({ latest: "2.14.0", beta: "3.0.0-beta.3" }),
} as Response)
) as unknown as typeof fetch
// #when current version is 3.0.0-beta.3
const result = await getPluginNameWithVersion("3.0.0-beta.3")
// #then should use @beta tag
expect(result).toBe("oh-my-opencode@beta")
})
test("returns @next when current version matches next tag", async () => {
// #given npm dist-tags with next=3.1.0-next.1
globalThis.fetch = mock(() =>
Promise.resolve({
ok: true,
json: () => Promise.resolve({ latest: "2.14.0", beta: "3.0.0-beta.3", next: "3.1.0-next.1" }),
} as Response)
) as unknown as typeof fetch
// #when current version is 3.1.0-next.1
const result = await getPluginNameWithVersion("3.1.0-next.1")
// #then should use @next tag
expect(result).toBe("oh-my-opencode@next")
})
test("returns prerelease channel tag when no dist-tag matches prerelease version", async () => {
// #given npm dist-tags with beta=3.0.0-beta.3
globalThis.fetch = mock(() =>
Promise.resolve({
ok: true,
json: () => Promise.resolve({ latest: "2.14.0", beta: "3.0.0-beta.3" }),
} as Response)
) as unknown as typeof fetch
// #when current version is old beta 3.0.0-beta.2
const result = await getPluginNameWithVersion("3.0.0-beta.2")
// #then should preserve prerelease channel
expect(result).toBe("oh-my-opencode@beta")
})
test("returns prerelease channel tag when fetch fails", async () => {
// #given network failure
globalThis.fetch = mock(() => Promise.reject(new Error("Network error"))) as unknown as typeof fetch
// #when current version is 3.0.0-beta.3
const result = await getPluginNameWithVersion("3.0.0-beta.3")
// #then should preserve prerelease channel
expect(result).toBe("oh-my-opencode@beta")
})
test("returns bare package name when npm returns non-ok response for stable version", async () => {
// #given npm returns 404
globalThis.fetch = mock(() =>
Promise.resolve({
ok: false,
status: 404,
} as Response)
) as unknown as typeof fetch
// #when current version is 2.14.0
const result = await getPluginNameWithVersion("2.14.0")
// #then should fall back to bare package entry
expect(result).toBe("oh-my-opencode")
})
test("prioritizes latest over other tags when version matches multiple", async () => {
// #given version matches both latest and beta (during release promotion)
globalThis.fetch = mock(() =>
Promise.resolve({
ok: true,
json: () => Promise.resolve({ beta: "3.0.0", latest: "3.0.0", next: "3.1.0-alpha.1" }),
} as Response)
) as unknown as typeof fetch
// #when current version matches both
const result = await getPluginNameWithVersion("3.0.0")
// #then should prioritize @latest
expect(result).toBe("oh-my-opencode@latest")
})
})
describe("fetchNpmDistTags", () => {
const originalFetch = globalThis.fetch
afterEach(() => {
globalThis.fetch = originalFetch
})
test("returns dist-tags on success", async () => {
// #given npm returns dist-tags
globalThis.fetch = mock(() =>
Promise.resolve({
ok: true,
json: () => Promise.resolve({ latest: "2.14.0", beta: "3.0.0-beta.3" }),
} as Response)
) as unknown as typeof fetch
// #when fetching dist-tags
const result = await fetchNpmDistTags("oh-my-opencode")
// #then should return the tags
expect(result).toEqual({ latest: "2.14.0", beta: "3.0.0-beta.3" })
})
test("returns null on network failure", async () => {
// #given network failure
globalThis.fetch = mock(() => Promise.reject(new Error("Network error"))) as unknown as typeof fetch
// #when fetching dist-tags
const result = await fetchNpmDistTags("oh-my-opencode")
// #then should return null
expect(result).toBeNull()
})
test("returns null on non-ok response", async () => {
// #given npm returns 404
globalThis.fetch = mock(() =>
Promise.resolve({
ok: false,
status: 404,
} as Response)
) as unknown as typeof fetch
// #when fetching dist-tags
const result = await fetchNpmDistTags("oh-my-opencode")
// #then should return null
expect(result).toBeNull()
})
})
describe("generateOmoConfig - model fallback system", () => {
test("uses github-copilot sonnet fallback when only copilot available", () => {
// #given user has only copilot (no max plan)
const config: InstallConfig = {
hasClaude: false,
isMax20: false,
hasOpenAI: false,
hasGemini: false,
hasCopilot: true,
hasOpencodeZen: false,
hasZaiCodingPlan: false,
hasKimiForCoding: false,
}
// #when generating config
const result = generateOmoConfig(config)
// #then Sisyphus uses Copilot (OR logic - copilot is in claude-opus-4-6 providers)
expect((result.agents as Record<string, { model: string }>).sisyphus.model).toBe("github-copilot/claude-opus-4.6")
})
test("uses ultimate fallback when no providers configured", () => {
// #given user has no providers
const config: InstallConfig = {
hasClaude: false,
isMax20: false,
hasOpenAI: false,
hasGemini: false,
hasCopilot: false,
hasOpencodeZen: false,
hasZaiCodingPlan: false,
hasKimiForCoding: false,
}
// #when generating config
const result = generateOmoConfig(config)
// #then Sisyphus is omitted (requires all fallback providers)
expect(result.$schema).toBe("https://raw.githubusercontent.com/code-yeongyu/oh-my-openagent/dev/assets/oh-my-opencode.schema.json")
expect((result.agents as Record<string, { model: string }>).sisyphus).toBeUndefined()
})
test("uses ZAI model for librarian when Z.ai is available", () => {
// #given user has Z.ai and Claude max20
const config: InstallConfig = {
hasClaude: true,
isMax20: true,
hasOpenAI: false,
hasGemini: false,
hasCopilot: false,
hasOpencodeZen: false,
hasZaiCodingPlan: true,
hasKimiForCoding: false,
}
// #when generating config
const result = generateOmoConfig(config)
// #then librarian should use ZAI model
expect((result.agents as Record<string, { model: string }>).librarian.model).toBe("zai-coding-plan/glm-4.7")
// #then Sisyphus uses Claude (OR logic)
expect((result.agents as Record<string, { model: string }>).sisyphus.model).toBe("anthropic/claude-opus-4-6")
})
test("uses native OpenAI models when only ChatGPT available", () => {
// #given user has only ChatGPT subscription
const config: InstallConfig = {
hasClaude: false,
isMax20: false,
hasOpenAI: true,
hasGemini: false,
hasCopilot: false,
hasOpencodeZen: false,
hasZaiCodingPlan: false,
hasKimiForCoding: false,
}
// #when generating config
const result = generateOmoConfig(config)
// #then Sisyphus resolves to gpt-5.4 medium (openai is now in sisyphus chain)
expect((result.agents as Record<string, { model: string; variant?: string }>).sisyphus.model).toBe("openai/gpt-5.4")
expect((result.agents as Record<string, { model: string; variant?: string }>).sisyphus.variant).toBe("medium")
// #then Oracle should use native OpenAI (first fallback entry)
expect((result.agents as Record<string, { model: string }>).oracle.model).toBe("openai/gpt-5.4")
// #then multimodal-looker should use native OpenAI (first fallback entry is gpt-5.4)
expect((result.agents as Record<string, { model: string }>)["multimodal-looker"].model).toBe("openai/gpt-5.4")
})
test("uses haiku for explore when Claude max20", () => {
// #given user has Claude max20
const config: InstallConfig = {
hasClaude: true,
isMax20: true,
hasOpenAI: false,
hasGemini: false,
hasCopilot: false,
hasOpencodeZen: false,
hasZaiCodingPlan: false,
hasKimiForCoding: false,
}
// #when generating config
const result = generateOmoConfig(config)
// #then explore should use haiku (max20 plan uses Claude quota)
expect((result.agents as Record<string, { model: string }>).explore.model).toBe("anthropic/claude-haiku-4-5")
})
test("uses haiku for explore regardless of max20 flag", () => {
// #given user has Claude but not max20
const config: InstallConfig = {
hasClaude: true,
isMax20: false,
hasOpenAI: false,
hasGemini: false,
hasCopilot: false,
hasOpencodeZen: false,
hasZaiCodingPlan: false,
hasKimiForCoding: false,
}
// #when generating config
const result = generateOmoConfig(config)
// #then explore should use haiku (isMax20 doesn't affect explore anymore)
expect((result.agents as Record<string, { model: string }>).explore.model).toBe("anthropic/claude-haiku-4-5")
})
})

View File

@@ -41,37 +41,39 @@ export async function addPluginToOpenCodeConfig(currentVersion: string): Promise
const config = parseResult.config
const plugins = config.plugin ?? []
// Check for existing plugin (either current or legacy name)
const currentNameIndex = plugins.findIndex(
const canonicalEntries = plugins.filter(
(plugin) => plugin === PLUGIN_NAME || plugin.startsWith(`${PLUGIN_NAME}@`)
)
const legacyNameIndex = plugins.findIndex(
const legacyEntries = plugins.filter(
(plugin) => plugin === LEGACY_PLUGIN_NAME || plugin.startsWith(`${LEGACY_PLUGIN_NAME}@`)
)
const otherPlugins = plugins.filter(
(plugin) => !(plugin === PLUGIN_NAME || plugin.startsWith(`${PLUGIN_NAME}@`))
&& !(plugin === LEGACY_PLUGIN_NAME || plugin.startsWith(`${LEGACY_PLUGIN_NAME}@`))
)
// If either name exists, update to new name
if (currentNameIndex !== -1) {
if (plugins[currentNameIndex] === pluginEntry) {
return { success: true, configPath: path }
}
plugins[currentNameIndex] = pluginEntry
} else if (legacyNameIndex !== -1) {
// Upgrade legacy name to new name
plugins[legacyNameIndex] = pluginEntry
const normalizedPlugins = [...otherPlugins]
if (canonicalEntries.length > 0) {
normalizedPlugins.push(canonicalEntries[0])
} else if (legacyEntries.length > 0) {
const versionMatch = legacyEntries[0].match(/@(.+)$/)
const preservedVersion = versionMatch ? versionMatch[1] : null
normalizedPlugins.push(preservedVersion ? `${PLUGIN_NAME}@${preservedVersion}` : pluginEntry)
} else {
plugins.push(pluginEntry)
normalizedPlugins.push(pluginEntry)
}
config.plugin = plugins
config.plugin = normalizedPlugins
if (format === "jsonc") {
const content = readFileSync(path, "utf-8")
const pluginArrayRegex = /"plugin"\s*:\s*\[([\s\S]*?)\]/
const pluginArrayRegex = /((?:"plugin"|plugin)\s*:\s*)\[([\s\S]*?)\]/
const match = content.match(pluginArrayRegex)
if (match) {
const formattedPlugins = plugins.map((p) => `"${p}"`).join(",\n ")
const newContent = content.replace(pluginArrayRegex, `"plugin": [\n ${formattedPlugins}\n ]`)
const formattedPlugins = normalizedPlugins.map((p) => `"${p}"`).join(",\n ")
const newContent = content.replace(pluginArrayRegex, `$1[\n ${formattedPlugins}\n ]`)
writeFileSync(path, newContent)
} else {
const newContent = content.replace(/(\{)/, `$1\n "plugin": ["${pluginEntry}"],`)

View File

@@ -0,0 +1,142 @@
/// <reference types="bun-types" />
import { describe, expect, test } from "bun:test"
import { generateOmoConfig } from "../config-manager"
import type { InstallConfig } from "../types"
describe("generateOmoConfig - model fallback system", () => {
test("uses github-copilot sonnet fallback when only copilot available", () => {
//#given
const config: InstallConfig = {
hasClaude: false,
isMax20: false,
hasOpenAI: false,
hasGemini: false,
hasCopilot: true,
hasOpencodeZen: false,
hasZaiCodingPlan: false,
hasKimiForCoding: false,
hasOpencodeGo: false,
}
//#when
const result = generateOmoConfig(config)
//#then
expect([
"github-copilot/claude-opus-4.6",
"github-copilot/claude-opus-4-6",
]).toContain((result.agents as Record<string, { model: string }>).sisyphus.model)
})
test("uses ultimate fallback when no providers configured", () => {
//#given
const config: InstallConfig = {
hasClaude: false,
isMax20: false,
hasOpenAI: false,
hasGemini: false,
hasCopilot: false,
hasOpencodeZen: false,
hasZaiCodingPlan: false,
hasKimiForCoding: false,
hasOpencodeGo: false,
}
//#when
const result = generateOmoConfig(config)
//#then
expect(result.$schema).toBe("https://raw.githubusercontent.com/code-yeongyu/oh-my-openagent/dev/assets/oh-my-opencode.schema.json")
expect((result.agents as Record<string, { model: string }>).sisyphus).toBeUndefined()
})
test("uses ZAI model for librarian when Z.ai is available", () => {
//#given
const config: InstallConfig = {
hasClaude: true,
isMax20: true,
hasOpenAI: false,
hasGemini: false,
hasCopilot: false,
hasOpencodeZen: false,
hasZaiCodingPlan: true,
hasKimiForCoding: false,
hasOpencodeGo: false,
}
//#when
const result = generateOmoConfig(config)
//#then
expect((result.agents as Record<string, { model: string }>).librarian.model).toBe("zai-coding-plan/glm-4.7")
expect((result.agents as Record<string, { model: string }>).sisyphus.model).toBe("anthropic/claude-opus-4-6")
})
test("uses native OpenAI models when only ChatGPT available", () => {
//#given
const config: InstallConfig = {
hasClaude: false,
isMax20: false,
hasOpenAI: true,
hasGemini: false,
hasCopilot: false,
hasOpencodeZen: false,
hasZaiCodingPlan: false,
hasKimiForCoding: false,
hasOpencodeGo: false,
}
//#when
const result = generateOmoConfig(config)
//#then
expect((result.agents as Record<string, { model: string; variant?: string }>).sisyphus.model).toBe("openai/gpt-5.4")
expect((result.agents as Record<string, { model: string; variant?: string }>).sisyphus.variant).toBe("medium")
expect((result.agents as Record<string, { model: string }>).oracle.model).toBe("openai/gpt-5.4")
expect((result.agents as Record<string, { model: string }>)['multimodal-looker'].model).toBe("openai/gpt-5.4")
})
test("uses haiku for explore when Claude max20", () => {
//#given
const config: InstallConfig = {
hasClaude: true,
isMax20: true,
hasOpenAI: false,
hasGemini: false,
hasCopilot: false,
hasOpencodeZen: false,
hasZaiCodingPlan: false,
hasKimiForCoding: false,
hasOpencodeGo: false,
}
//#when
const result = generateOmoConfig(config)
//#then
expect((result.agents as Record<string, { model: string }>).explore.model).toBe("anthropic/claude-haiku-4-5")
})
test("uses haiku for explore regardless of max20 flag", () => {
//#given
const config: InstallConfig = {
hasClaude: true,
isMax20: false,
hasOpenAI: false,
hasGemini: false,
hasCopilot: false,
hasOpencodeZen: false,
hasZaiCodingPlan: false,
hasKimiForCoding: false,
hasOpencodeGo: false,
}
//#when
const result = generateOmoConfig(config)
//#then
expect((result.agents as Record<string, { model: string }>).explore.model).toBe("anthropic/claude-haiku-4-5")
})
})

View File

@@ -0,0 +1,56 @@
/// <reference types="bun-types" />
import { afterEach, describe, expect, mock, test } from "bun:test"
import { fetchNpmDistTags } from "../config-manager"
describe("fetchNpmDistTags", () => {
const originalFetch = globalThis.fetch
afterEach(() => {
globalThis.fetch = originalFetch
})
test("returns dist-tags on success", async () => {
//#given
globalThis.fetch = mock(() =>
Promise.resolve({
ok: true,
json: () => Promise.resolve({ latest: "3.13.1", beta: "3.14.0-beta.1" }),
} as Response)
) as unknown as typeof fetch
//#when
const result = await fetchNpmDistTags("oh-my-openagent")
//#then
expect(result).toEqual({ latest: "3.13.1", beta: "3.14.0-beta.1" })
})
test("returns null on network failure", async () => {
//#given
globalThis.fetch = mock(() => Promise.reject(new Error("Network error"))) as unknown as typeof fetch
//#when
const result = await fetchNpmDistTags("oh-my-openagent")
//#then
expect(result).toBeNull()
})
test("returns null on non-ok response", async () => {
//#given
globalThis.fetch = mock(() =>
Promise.resolve({
ok: false,
status: 404,
} as Response)
) as unknown as typeof fetch
//#when
const result = await fetchNpmDistTags("oh-my-openagent")
//#then
expect(result).toBeNull()
})
})

View File

@@ -28,10 +28,9 @@ describe("detectCurrentConfig - single package detection", () => {
delete process.env.OPENCODE_CONFIG_DIR
})
it("detects oh-my-opencode in plugin array", () => {
it("detects both legacy and canonical plugin entries", () => {
// given
const config = { plugin: ["oh-my-opencode"] }
writeFileSync(testConfigPath, JSON.stringify(config, null, 2) + "\n", "utf-8")
writeFileSync(testConfigPath, JSON.stringify({ plugin: ["oh-my-opencode", "oh-my-openagent@3.11.0"] }, null, 2) + "\n", "utf-8")
// when
const result = detectCurrentConfig()
@@ -40,58 +39,9 @@ describe("detectCurrentConfig - single package detection", () => {
expect(result.isInstalled).toBe(true)
})
it("detects oh-my-opencode with version pin", () => {
it("returns false when plugin not present with similar name", () => {
// given
const config = { plugin: ["oh-my-opencode@3.11.0"] }
writeFileSync(testConfigPath, JSON.stringify(config, null, 2) + "\n", "utf-8")
// when
const result = detectCurrentConfig()
// then
expect(result.isInstalled).toBe(true)
})
it("detects oh-my-openagent as installed (legacy name)", () => {
// given
const config = { plugin: ["oh-my-openagent"] }
writeFileSync(testConfigPath, JSON.stringify(config, null, 2) + "\n", "utf-8")
// when
const result = detectCurrentConfig()
// then
expect(result.isInstalled).toBe(true)
})
it("detects oh-my-openagent with version pin as installed (legacy name)", () => {
// given
const config = { plugin: ["oh-my-openagent@3.11.0"] }
writeFileSync(testConfigPath, JSON.stringify(config, null, 2) + "\n", "utf-8")
// when
const result = detectCurrentConfig()
// then
expect(result.isInstalled).toBe(true)
})
it("returns false when plugin not present", () => {
// given
const config = { plugin: ["some-other-plugin"] }
writeFileSync(testConfigPath, JSON.stringify(config, null, 2) + "\n", "utf-8")
// when
const result = detectCurrentConfig()
// then
expect(result.isInstalled).toBe(false)
})
it("returns false when plugin not present (even with similar name)", () => {
// given - not exactly oh-my-openagent
const config = { plugin: ["oh-my-openagent-extra"] }
writeFileSync(testConfigPath, JSON.stringify(config, null, 2) + "\n", "utf-8")
writeFileSync(testConfigPath, JSON.stringify({ plugin: ["oh-my-openagent-extra"] }, null, 2) + "\n", "utf-8")
// when
const result = detectCurrentConfig()
@@ -103,11 +53,7 @@ describe("detectCurrentConfig - single package detection", () => {
it("detects OpenCode Go from the existing omo config", () => {
// given
writeFileSync(testConfigPath, JSON.stringify({ plugin: ["oh-my-opencode"] }, null, 2) + "\n", "utf-8")
writeFileSync(
testOmoConfigPath,
JSON.stringify({ agents: { atlas: { model: "opencode-go/kimi-k2.5" } } }, null, 2) + "\n",
"utf-8",
)
writeFileSync(testOmoConfigPath, JSON.stringify({ agents: { atlas: { model: "opencode-go/kimi-k2.5" } } }, null, 2) + "\n", "utf-8")
// when
const result = detectCurrentConfig()
@@ -137,10 +83,9 @@ describe("addPluginToOpenCodeConfig - single package writes", () => {
delete process.env.OPENCODE_CONFIG_DIR
})
it("keeps oh-my-opencode when it already exists", async () => {
it("writes canonical plugin entry for new installs", async () => {
// given
const config = { plugin: ["oh-my-opencode"] }
writeFileSync(testConfigPath, JSON.stringify(config, null, 2) + "\n", "utf-8")
writeFileSync(testConfigPath, JSON.stringify({}, null, 2) + "\n", "utf-8")
// when
const result = await addPluginToOpenCodeConfig("3.11.0")
@@ -148,13 +93,12 @@ describe("addPluginToOpenCodeConfig - single package writes", () => {
// then
expect(result.success).toBe(true)
const savedConfig = JSON.parse(readFileSync(testConfigPath, "utf-8"))
expect(savedConfig.plugin).toContain("oh-my-opencode")
expect(savedConfig.plugin).toEqual(["oh-my-openagent"])
})
it("replaces version-pinned oh-my-opencode@X.Y.Z", async () => {
it("upgrades a bare legacy plugin entry to canonical", async () => {
// given
const config = { plugin: ["oh-my-opencode@3.10.0"] }
writeFileSync(testConfigPath, JSON.stringify(config, null, 2) + "\n", "utf-8")
writeFileSync(testConfigPath, JSON.stringify({ plugin: ["oh-my-opencode"] }, null, 2) + "\n", "utf-8")
// when
const result = await addPluginToOpenCodeConfig("3.11.0")
@@ -162,14 +106,12 @@ describe("addPluginToOpenCodeConfig - single package writes", () => {
// then
expect(result.success).toBe(true)
const savedConfig = JSON.parse(readFileSync(testConfigPath, "utf-8"))
expect(savedConfig.plugin).toContain("oh-my-opencode")
expect(savedConfig.plugin).not.toContain("oh-my-opencode@3.10.0")
expect(savedConfig.plugin).toEqual(["oh-my-openagent"])
})
it("recognizes oh-my-openagent as already installed (legacy name)", async () => {
it("upgrades a version-pinned legacy entry to canonical", async () => {
// given
const config = { plugin: ["oh-my-openagent"] }
writeFileSync(testConfigPath, JSON.stringify(config, null, 2) + "\n", "utf-8")
writeFileSync(testConfigPath, JSON.stringify({ plugin: ["oh-my-opencode@3.10.0"] }, null, 2) + "\n", "utf-8")
// when
const result = await addPluginToOpenCodeConfig("3.11.0")
@@ -177,15 +119,12 @@ describe("addPluginToOpenCodeConfig - single package writes", () => {
// then
expect(result.success).toBe(true)
const savedConfig = JSON.parse(readFileSync(testConfigPath, "utf-8"))
// Should upgrade to new name
expect(savedConfig.plugin).toContain("oh-my-opencode")
expect(savedConfig.plugin).not.toContain("oh-my-openagent")
expect(savedConfig.plugin).toEqual(["oh-my-openagent@3.10.0"])
})
it("replaces version-pinned oh-my-openagent@X.Y.Z with new name", async () => {
it("removes stale legacy entry when canonical and legacy entries both exist", async () => {
// given
const config = { plugin: ["oh-my-openagent@3.10.0"] }
writeFileSync(testConfigPath, JSON.stringify(config, null, 2) + "\n", "utf-8")
writeFileSync(testConfigPath, JSON.stringify({ plugin: ["oh-my-openagent", "oh-my-opencode"] }, null, 2) + "\n", "utf-8")
// when
const result = await addPluginToOpenCodeConfig("3.11.0")
@@ -193,15 +132,12 @@ describe("addPluginToOpenCodeConfig - single package writes", () => {
// then
expect(result.success).toBe(true)
const savedConfig = JSON.parse(readFileSync(testConfigPath, "utf-8"))
// Legacy should be replaced with new name
expect(savedConfig.plugin).toContain("oh-my-opencode")
expect(savedConfig.plugin).not.toContain("oh-my-openagent")
expect(savedConfig.plugin).toEqual(["oh-my-openagent"])
})
it("adds new plugin when none exists", async () => {
it("preserves a canonical entry when it already exists", async () => {
// given
const config = {}
writeFileSync(testConfigPath, JSON.stringify(config, null, 2) + "\n", "utf-8")
writeFileSync(testConfigPath, JSON.stringify({ plugin: ["oh-my-openagent@3.10.0"] }, null, 2) + "\n", "utf-8")
// when
const result = await addPluginToOpenCodeConfig("3.11.0")
@@ -209,20 +145,21 @@ describe("addPluginToOpenCodeConfig - single package writes", () => {
// then
expect(result.success).toBe(true)
const savedConfig = JSON.parse(readFileSync(testConfigPath, "utf-8"))
expect(savedConfig.plugin).toContain("oh-my-opencode")
expect(savedConfig.plugin).toEqual(["oh-my-openagent@3.10.0"])
})
it("adds plugin when plugin array is empty", async () => {
it("rewrites quoted jsonc plugin field in place", async () => {
// given
const config = { plugin: [] }
writeFileSync(testConfigPath, JSON.stringify(config, null, 2) + "\n", "utf-8")
testConfigPath = join(testConfigDir, "opencode.jsonc")
writeFileSync(testConfigPath, '{\n "plugin": ["oh-my-opencode"]\n}\n', "utf-8")
// when
const result = await addPluginToOpenCodeConfig("3.11.0")
// then
expect(result.success).toBe(true)
const savedConfig = JSON.parse(readFileSync(testConfigPath, "utf-8"))
expect(savedConfig.plugin).toContain("oh-my-opencode")
const savedContent = readFileSync(testConfigPath, "utf-8")
expect(savedContent.includes('"plugin": [\n "oh-my-openagent"\n ]')).toBe(true)
expect(savedContent.includes("oh-my-opencode")).toBe(false)
})
})

View File

@@ -0,0 +1,56 @@
/// <reference types="bun-types" />
import { afterEach, describe, expect, mock, test } from "bun:test"
import { getPluginNameWithVersion } from "../config-manager"
describe("getPluginNameWithVersion", () => {
const originalFetch = globalThis.fetch
afterEach(() => {
globalThis.fetch = originalFetch
})
test("returns the canonical latest tag when current version matches latest", async () => {
//#given
globalThis.fetch = mock(() =>
Promise.resolve({
ok: true,
json: () => Promise.resolve({ latest: "3.13.1", beta: "3.14.0-beta.1" }),
} as Response)
) as unknown as typeof fetch
//#when
const result = await getPluginNameWithVersion("3.13.1")
//#then
expect(result).toBe("oh-my-openagent@latest")
})
test("preserves the canonical prerelease channel when fetch fails", async () => {
//#given
globalThis.fetch = mock(() => Promise.reject(new Error("Network error"))) as unknown as typeof fetch
//#when
const result = await getPluginNameWithVersion("3.14.0-beta.1")
//#then
expect(result).toBe("oh-my-openagent@beta")
})
test("returns the canonical bare package name for stable fallback", async () => {
//#given
globalThis.fetch = mock(() =>
Promise.resolve({
ok: false,
status: 404,
} as Response)
) as unknown as typeof fetch
//#when
const result = await getPluginNameWithVersion("3.13.1")
//#then
expect(result).toBe("oh-my-openagent")
})
})

View File

@@ -1,6 +1,7 @@
import { PLUGIN_NAME } from "../../shared"
import { fetchNpmDistTags } from "./npm-dist-tags"
const DEFAULT_PACKAGE_NAME = "oh-my-opencode"
const DEFAULT_PACKAGE_NAME = PLUGIN_NAME
const PRIORITIZED_TAGS = ["latest", "beta", "next"] as const
function getFallbackEntry(version: string, packageName: string): string {

View File

@@ -2,15 +2,15 @@ import { readFileSync } from "node:fs"
import { join } from "node:path"
import { OhMyOpenCodeConfigSchema } from "../../../config"
import { detectConfigFile, getOpenCodeConfigDir, parseJsonc } from "../../../shared"
import { detectPluginConfigFile, getOpenCodeConfigDir, parseJsonc } from "../../../shared"
import { CHECK_IDS, CHECK_NAMES, PACKAGE_NAME } from "../constants"
import type { CheckResult, DoctorIssue } from "../types"
import { loadAvailableModelsFromCache } from "./model-resolution-cache"
import { getModelResolutionInfoWithOverrides } from "./model-resolution"
import type { OmoConfig } from "./model-resolution-types"
const USER_CONFIG_BASE = join(getOpenCodeConfigDir({ binary: "opencode" }), PACKAGE_NAME)
const PROJECT_CONFIG_BASE = join(process.cwd(), ".opencode", PACKAGE_NAME)
const USER_CONFIG_DIR = getOpenCodeConfigDir({ binary: "opencode" })
const PROJECT_CONFIG_DIR = join(process.cwd(), ".opencode")
interface ConfigValidationResult {
exists: boolean
@@ -21,10 +21,10 @@ interface ConfigValidationResult {
}
function findConfigPath(): string | null {
const projectConfig = detectConfigFile(PROJECT_CONFIG_BASE)
const projectConfig = detectPluginConfigFile(PROJECT_CONFIG_DIR)
if (projectConfig.format !== "none") return projectConfig.path
const userConfig = detectConfigFile(USER_CONFIG_BASE)
const userConfig = detectPluginConfigFile(USER_CONFIG_DIR)
if (userConfig.format !== "none") return userConfig.path
return null

View File

@@ -1,17 +1,13 @@
import { readFileSync } from "node:fs"
import { join } from "node:path"
import { detectConfigFile, getOpenCodeConfigPaths, parseJsonc } from "../../../shared"
import { detectPluginConfigFile, getOpenCodeConfigPaths, parseJsonc } from "../../../shared"
import type { OmoConfig } from "./model-resolution-types"
const PACKAGE_NAME = "oh-my-opencode"
const USER_CONFIG_BASE = join(
getOpenCodeConfigPaths({ binary: "opencode", version: null }).configDir,
PACKAGE_NAME
)
const PROJECT_CONFIG_BASE = join(process.cwd(), ".opencode", PACKAGE_NAME)
const USER_CONFIG_DIR = getOpenCodeConfigPaths({ binary: "opencode", version: null }).configDir
const PROJECT_CONFIG_DIR = join(process.cwd(), ".opencode")
export function loadOmoConfig(): OmoConfig | null {
const projectDetected = detectConfigFile(PROJECT_CONFIG_BASE)
const projectDetected = detectPluginConfigFile(PROJECT_CONFIG_DIR)
if (projectDetected.format !== "none") {
try {
const content = readFileSync(projectDetected.path, "utf-8")
@@ -21,7 +17,7 @@ export function loadOmoConfig(): OmoConfig | null {
}
}
const userDetected = detectConfigFile(USER_CONFIG_BASE)
const userDetected = detectPluginConfigFile(USER_CONFIG_DIR)
if (userDetected.format !== "none") {
try {
const content = readFileSync(userDetected.path, "utf-8")

View File

@@ -4,6 +4,10 @@ import { getOpenCodeCacheDir } from "../../../shared"
import type { AvailableModelsInfo, ModelResolutionInfo, OmoConfig } from "./model-resolution-types"
import { formatModelWithVariant, getCategoryEffectiveVariant, getEffectiveVariant } from "./model-resolution-variant"
function formatCapabilityResolutionLabel(mode: string | undefined): string {
return mode ?? "unknown"
}
export function buildModelResolutionDetails(options: {
info: ModelResolutionInfo
available: AvailableModelsInfo
@@ -37,7 +41,7 @@ export function buildModelResolutionDetails(options: {
agent.effectiveModel,
getEffectiveVariant(agent.name, agent.requirement, options.config)
)
details.push(` ${marker} ${agent.name}: ${display}`)
details.push(` ${marker} ${agent.name}: ${display} [capabilities: ${formatCapabilityResolutionLabel(agent.capabilityDiagnostics?.resolutionMode)}]`)
}
details.push("")
details.push("Categories:")
@@ -47,7 +51,7 @@ export function buildModelResolutionDetails(options: {
category.effectiveModel,
getCategoryEffectiveVariant(category.name, category.requirement, options.config)
)
details.push(` ${marker} ${category.name}: ${display}`)
details.push(` ${marker} ${category.name}: ${display} [capabilities: ${formatCapabilityResolutionLabel(category.capabilityDiagnostics?.resolutionMode)}]`)
}
details.push("")
details.push("● = user override, ○ = provider fallback")

View File

@@ -1,3 +1,4 @@
import type { ModelCapabilitiesDiagnostics } from "../../../shared/model-capabilities"
import type { ModelRequirement } from "../../../shared/model-requirements"
export interface AgentResolutionInfo {
@@ -7,6 +8,7 @@ export interface AgentResolutionInfo {
userVariant?: string
effectiveModel: string
effectiveResolution: string
capabilityDiagnostics?: ModelCapabilitiesDiagnostics
}
export interface CategoryResolutionInfo {
@@ -16,6 +18,7 @@ export interface CategoryResolutionInfo {
userVariant?: string
effectiveModel: string
effectiveResolution: string
capabilityDiagnostics?: ModelCapabilitiesDiagnostics
}
export interface ModelResolutionInfo {

View File

@@ -129,6 +129,19 @@ describe("model-resolution check", () => {
expect(visual!.userOverride).toBe("google/gemini-3-flash-preview")
expect(visual!.userVariant).toBe("high")
})
it("attaches snapshot-backed capability diagnostics for built-in models", async () => {
const { getModelResolutionInfoWithOverrides } = await import("./model-resolution")
const info = getModelResolutionInfoWithOverrides({})
const sisyphus = info.agents.find((a) => a.name === "sisyphus")
expect(sisyphus).toBeDefined()
expect(sisyphus!.capabilityDiagnostics).toMatchObject({
resolutionMode: "snapshot-backed",
snapshot: { source: "bundled-snapshot" },
})
})
})
describe("checkModelResolution", () => {
@@ -162,6 +175,23 @@ describe("model-resolution check", () => {
expect(result.details!.some((d) => d.includes("Categories:"))).toBe(true)
// Should have legend
expect(result.details!.some((d) => d.includes("user override"))).toBe(true)
expect(result.details!.some((d) => d.includes("capabilities: snapshot-backed"))).toBe(true)
})
it("collects warnings when configured models rely on compatibility fallback", async () => {
const { collectCapabilityResolutionIssues, getModelResolutionInfoWithOverrides } = await import("./model-resolution")
const info = getModelResolutionInfoWithOverrides({
agents: {
oracle: { model: "custom/unknown-llm" },
},
})
const issues = collectCapabilityResolutionIssues(info)
expect(issues).toHaveLength(1)
expect(issues[0]?.title).toContain("compatibility fallback")
expect(issues[0]?.description).toContain("oracle=custom/unknown-llm")
})
})

View File

@@ -1,4 +1,5 @@
import { AGENT_MODEL_REQUIREMENTS, CATEGORY_MODEL_REQUIREMENTS } from "../../../shared/model-requirements"
import { getModelCapabilities } from "../../../shared/model-capabilities"
import { CHECK_IDS, CHECK_NAMES } from "../constants"
import type { CheckResult, DoctorIssue } from "../types"
import { loadAvailableModelsFromCache } from "./model-resolution-cache"
@@ -7,16 +8,36 @@ import { buildModelResolutionDetails } from "./model-resolution-details"
import { buildEffectiveResolution, getEffectiveModel } from "./model-resolution-effective-model"
import type { AgentResolutionInfo, CategoryResolutionInfo, ModelResolutionInfo, OmoConfig } from "./model-resolution-types"
export function getModelResolutionInfo(): ModelResolutionInfo {
const agents: AgentResolutionInfo[] = Object.entries(AGENT_MODEL_REQUIREMENTS).map(([name, requirement]) => ({
name,
requirement,
effectiveModel: getEffectiveModel(requirement),
effectiveResolution: buildEffectiveResolution(requirement),
}))
function parseProviderModel(value: string): { providerID: string; modelID: string } | null {
const slashIndex = value.lastIndexOf("/")
if (slashIndex <= 0 || slashIndex === value.length - 1) {
return null
}
const categories: CategoryResolutionInfo[] = Object.entries(CATEGORY_MODEL_REQUIREMENTS).map(
([name, requirement]) => ({
return {
providerID: value.slice(0, slashIndex),
modelID: value.slice(slashIndex + 1),
}
}
function attachCapabilityDiagnostics<T extends AgentResolutionInfo | CategoryResolutionInfo>(entry: T): T {
const parsed = parseProviderModel(entry.effectiveModel)
if (!parsed) {
return entry
}
return {
...entry,
capabilityDiagnostics: getModelCapabilities({
providerID: parsed.providerID,
modelID: parsed.modelID,
}).diagnostics,
}
}
export function getModelResolutionInfo(): ModelResolutionInfo {
const agents: AgentResolutionInfo[] = Object.entries(AGENT_MODEL_REQUIREMENTS).map(([name, requirement]) =>
attachCapabilityDiagnostics({
name,
requirement,
effectiveModel: getEffectiveModel(requirement),
@@ -24,6 +45,16 @@ export function getModelResolutionInfo(): ModelResolutionInfo {
})
)
const categories: CategoryResolutionInfo[] = Object.entries(CATEGORY_MODEL_REQUIREMENTS).map(
([name, requirement]) =>
attachCapabilityDiagnostics({
name,
requirement,
effectiveModel: getEffectiveModel(requirement),
effectiveResolution: buildEffectiveResolution(requirement),
})
)
return { agents, categories }
}
@@ -31,34 +62,60 @@ export function getModelResolutionInfoWithOverrides(config: OmoConfig): ModelRes
const agents: AgentResolutionInfo[] = Object.entries(AGENT_MODEL_REQUIREMENTS).map(([name, requirement]) => {
const userOverride = config.agents?.[name]?.model
const userVariant = config.agents?.[name]?.variant
return {
return attachCapabilityDiagnostics({
name,
requirement,
userOverride,
userVariant,
effectiveModel: getEffectiveModel(requirement, userOverride),
effectiveResolution: buildEffectiveResolution(requirement, userOverride),
}
})
})
const categories: CategoryResolutionInfo[] = Object.entries(CATEGORY_MODEL_REQUIREMENTS).map(
([name, requirement]) => {
const userOverride = config.categories?.[name]?.model
const userVariant = config.categories?.[name]?.variant
return {
return attachCapabilityDiagnostics({
name,
requirement,
userOverride,
userVariant,
effectiveModel: getEffectiveModel(requirement, userOverride),
effectiveResolution: buildEffectiveResolution(requirement, userOverride),
}
})
}
)
return { agents, categories }
}
export function collectCapabilityResolutionIssues(info: ModelResolutionInfo): DoctorIssue[] {
const issues: DoctorIssue[] = []
const allEntries = [...info.agents, ...info.categories]
const fallbackEntries = allEntries.filter((entry) => {
const mode = entry.capabilityDiagnostics?.resolutionMode
return mode === "alias-backed" || mode === "heuristic-backed" || mode === "unknown"
})
if (fallbackEntries.length === 0) {
return issues
}
const summary = fallbackEntries
.map((entry) => `${entry.name}=${entry.effectiveModel} (${entry.capabilityDiagnostics?.resolutionMode ?? "unknown"})`)
.join(", ")
issues.push({
title: "Configured models rely on compatibility fallback",
description: summary,
severity: "warning",
affects: fallbackEntries.map((entry) => entry.name),
})
return issues
}
export async function checkModels(): Promise<CheckResult> {
const config = loadOmoConfig() ?? {}
const info = getModelResolutionInfoWithOverrides(config)
@@ -75,6 +132,8 @@ export async function checkModels(): Promise<CheckResult> {
})
}
issues.push(...collectCapabilityResolutionIssues(info))
const overrideCount =
info.agents.filter((agent) => Boolean(agent.userOverride)).length +
info.categories.filter((category) => Boolean(category.userOverride)).length

View File

@@ -1,9 +1,10 @@
import { afterEach, describe, expect, it } from "bun:test"
import { mkdirSync, mkdtempSync, rmSync, writeFileSync } from "node:fs"
import { mkdirSync, mkdtempSync, rmSync, symlinkSync, writeFileSync } from "node:fs"
import { tmpdir } from "node:os"
import { dirname, join } from "node:path"
import { PACKAGE_NAME } from "../constants"
import { resolveSymlink } from "../../../shared/file-utils"
const systemLoadedVersionModulePath = "./system-loaded-version?system-loaded-version-test"
@@ -104,6 +105,31 @@ describe("system loaded version", () => {
expect(loadedVersion.expectedVersion).toBe("2.3.4")
expect(loadedVersion.loadedVersion).toBe("2.3.4")
})
it("resolves symlinked config directories before selecting install path", () => {
//#given
const realConfigDir = createTemporaryDirectory("omo-real-config-")
const symlinkBaseDir = createTemporaryDirectory("omo-symlink-base-")
const symlinkConfigDir = join(symlinkBaseDir, "config-link")
symlinkSync(realConfigDir, symlinkConfigDir, process.platform === "win32" ? "junction" : "dir")
process.env.OPENCODE_CONFIG_DIR = symlinkConfigDir
writeJson(join(realConfigDir, "package.json"), {
dependencies: { [PACKAGE_NAME]: "4.5.6" },
})
writeJson(join(realConfigDir, "node_modules", PACKAGE_NAME, "package.json"), {
version: "4.5.6",
})
//#when
const loadedVersion = getLoadedPluginVersion()
//#then
expect(loadedVersion.cacheDir).toBe(resolveSymlink(symlinkConfigDir))
expect(loadedVersion.expectedVersion).toBe("4.5.6")
expect(loadedVersion.loadedVersion).toBe("4.5.6")
})
})
describe("getSuggestedInstallTag", () => {

View File

@@ -1,7 +1,7 @@
import { existsSync, readFileSync } from "node:fs"
import { homedir } from "node:os"
import { join } from "node:path"
import { resolveSymlink } from "../../../shared/file-utils"
import { getLatestVersion } from "../../../hooks/auto-update-checker/checker"
import { extractChannel } from "../../../hooks/auto-update-checker"
import { PACKAGE_NAME } from "../constants"
@@ -36,6 +36,11 @@ function resolveOpenCodeCacheDir(): string {
return platformDefault
}
function resolveExistingDir(dirPath: string): string {
if (!existsSync(dirPath)) return dirPath
return resolveSymlink(dirPath)
}
function readPackageJson(filePath: string): PackageJsonShape | null {
if (!existsSync(filePath)) return null
@@ -55,12 +60,13 @@ function normalizeVersion(value: string | undefined): string | null {
export function getLoadedPluginVersion(): LoadedVersionInfo {
const configPaths = getOpenCodeConfigPaths({ binary: "opencode" })
const cacheDir = resolveOpenCodeCacheDir()
const configDir = resolveExistingDir(configPaths.configDir)
const cacheDir = resolveExistingDir(resolveOpenCodeCacheDir())
const candidates = [
{
cacheDir: configPaths.configDir,
cachePackagePath: configPaths.packageJson,
installedPackagePath: join(configPaths.configDir, "node_modules", PACKAGE_NAME, "package.json"),
cacheDir: configDir,
cachePackagePath: join(configDir, "package.json"),
installedPackagePath: join(configDir, "node_modules", PACKAGE_NAME, "package.json"),
},
{
cacheDir,

View File

@@ -1,9 +1,19 @@
/// <reference types="bun-types" />
import { beforeEach, describe, expect, it, mock } from "bun:test"
import { PLUGIN_NAME } from "../../../shared"
import type { PluginInfo } from "./system-plugin"
type SystemModule = typeof import("./system")
async function importFreshSystemModule(): Promise<SystemModule> {
return import(`./system?test=${Date.now()}-${Math.random()}`)
}
const mockFindOpenCodeBinary = mock(async () => ({ path: "/usr/local/bin/opencode" }))
const mockGetOpenCodeVersion = mock(async () => "1.0.200")
const mockCompareVersions = mock(() => true)
const mockGetPluginInfo = mock(() => ({
const mockCompareVersions = mock((_leftVersion?: string, _rightVersion?: string) => true)
const mockGetPluginInfo = mock((): PluginInfo => ({
registered: true,
entry: "oh-my-opencode",
isPinned: false,
@@ -18,7 +28,8 @@ const mockGetLoadedPluginVersion = mock(() => ({
expectedVersion: "3.0.0",
loadedVersion: "3.1.0",
}))
const mockGetLatestPluginVersion = mock(async () => null)
const mockGetLatestPluginVersion = mock(async (_currentVersion: string | null) => null as string | null)
const mockGetSuggestedInstallTag = mock(() => "latest")
mock.module("./system-binary", () => ({
findOpenCodeBinary: mockFindOpenCodeBinary,
@@ -33,10 +44,9 @@ mock.module("./system-plugin", () => ({
mock.module("./system-loaded-version", () => ({
getLoadedPluginVersion: mockGetLoadedPluginVersion,
getLatestPluginVersion: mockGetLatestPluginVersion,
getSuggestedInstallTag: mockGetSuggestedInstallTag,
}))
const { checkSystem } = await import("./system?test")
describe("system check", () => {
beforeEach(() => {
mockFindOpenCodeBinary.mockReset()
@@ -45,6 +55,7 @@ describe("system check", () => {
mockGetPluginInfo.mockReset()
mockGetLoadedPluginVersion.mockReset()
mockGetLatestPluginVersion.mockReset()
mockGetSuggestedInstallTag.mockReset()
mockFindOpenCodeBinary.mockResolvedValue({ path: "/usr/local/bin/opencode" })
mockGetOpenCodeVersion.mockResolvedValue("1.0.200")
@@ -65,10 +76,14 @@ describe("system check", () => {
loadedVersion: "3.1.0",
})
mockGetLatestPluginVersion.mockResolvedValue(null)
mockGetSuggestedInstallTag.mockReturnValue("latest")
})
describe("#given cache directory contains spaces", () => {
it("uses a quoted cache directory in mismatch fix command", async () => {
//#given
const { checkSystem } = await importFreshSystemModule()
//#when
const result = await checkSystem()
@@ -87,9 +102,11 @@ describe("system check", () => {
loadedVersion: "3.0.0-canary.1",
})
mockGetLatestPluginVersion.mockResolvedValue("3.0.0-canary.2")
mockCompareVersions.mockImplementation((leftVersion: string, rightVersion: string) => {
mockGetSuggestedInstallTag.mockReturnValue("canary")
mockCompareVersions.mockImplementation((leftVersion?: string, rightVersion?: string) => {
return !(leftVersion === "3.0.0-canary.1" && rightVersion === "3.0.0-canary.2")
})
const { checkSystem } = await importFreshSystemModule()
//#when
const result = await checkSystem()
@@ -97,8 +114,94 @@ describe("system check", () => {
//#then
const outdatedIssue = result.issues.find((issue) => issue.title === "Loaded plugin is outdated")
expect(outdatedIssue?.fix).toBe(
'Update: cd "/Users/test/Library/Caches/opencode with spaces" && bun add oh-my-opencode@canary'
`Update: cd "/Users/test/Library/Caches/opencode with spaces" && bun add ${PLUGIN_NAME}@canary`
)
})
})
describe("#given OpenCode plugin entry uses legacy package name", () => {
it("adds a warning for a bare legacy entry", async () => {
//#given
mockGetPluginInfo.mockReturnValue({
registered: true,
entry: "oh-my-opencode",
isPinned: false,
pinnedVersion: null,
configPath: null,
isLocalDev: false,
})
const { checkSystem } = await importFreshSystemModule()
//#when
const result = await checkSystem()
//#then
const legacyEntryIssue = result.issues.find((issue) => issue.title === "Using legacy package name")
expect(legacyEntryIssue?.severity).toBe("warning")
expect(legacyEntryIssue?.fix).toBe(
'Update your opencode.json plugin entry: "oh-my-opencode" → "oh-my-openagent"'
)
})
it("adds a warning for a version-pinned legacy entry", async () => {
//#given
mockGetPluginInfo.mockReturnValue({
registered: true,
entry: "oh-my-opencode@3.0.0",
isPinned: true,
pinnedVersion: "3.0.0",
configPath: null,
isLocalDev: false,
})
const { checkSystem } = await importFreshSystemModule()
//#when
const result = await checkSystem()
//#then
const legacyEntryIssue = result.issues.find((issue) => issue.title === "Using legacy package name")
expect(legacyEntryIssue?.severity).toBe("warning")
expect(legacyEntryIssue?.fix).toBe(
'Update your opencode.json plugin entry: "oh-my-opencode@3.0.0" → "oh-my-openagent@3.0.0"'
)
})
it("does not warn for a canonical plugin entry", async () => {
//#given
mockGetPluginInfo.mockReturnValue({
registered: true,
entry: PLUGIN_NAME,
isPinned: false,
pinnedVersion: null,
configPath: null,
isLocalDev: false,
})
const { checkSystem } = await importFreshSystemModule()
//#when
const result = await checkSystem()
//#then
expect(result.issues.some((issue) => issue.title === "Using legacy package name")).toBe(false)
})
it("does not warn for a local-dev legacy entry", async () => {
//#given
mockGetPluginInfo.mockReturnValue({
registered: true,
entry: "oh-my-opencode",
isPinned: false,
pinnedVersion: null,
configPath: null,
isLocalDev: true,
})
const { checkSystem } = await importFreshSystemModule()
//#when
const result = await checkSystem()
//#then
expect(result.issues.some((issue) => issue.title === "Using legacy package name")).toBe(false)
})
})
})

View File

@@ -6,6 +6,7 @@ import { findOpenCodeBinary, getOpenCodeVersion, compareVersions } from "./syste
import { getPluginInfo } from "./system-plugin"
import { getLatestPluginVersion, getLoadedPluginVersion, getSuggestedInstallTag } from "./system-loaded-version"
import { parseJsonc } from "../../../shared"
import { PLUGIN_NAME, LEGACY_PLUGIN_NAME } from "../../../shared/plugin-identity"
function isConfigValid(configPath: string | null): boolean {
if (!configPath) return true
@@ -82,14 +83,30 @@ export async function checkSystem(): Promise<CheckResult> {
if (!pluginInfo.registered) {
issues.push({
title: "oh-my-opencode is not registered",
title: `${PLUGIN_NAME} is not registered`,
description: "Plugin entry is missing from OpenCode configuration.",
fix: "Run: bunx oh-my-opencode install",
fix: `Run: bunx ${PLUGIN_NAME} install`,
severity: "error",
affects: ["all agents"],
})
}
if (pluginInfo.entry && !pluginInfo.isLocalDev) {
const isLegacyName = pluginInfo.entry === LEGACY_PLUGIN_NAME
|| pluginInfo.entry.startsWith(`${LEGACY_PLUGIN_NAME}@`)
if (isLegacyName) {
const suggestedEntry = pluginInfo.entry.replace(LEGACY_PLUGIN_NAME, PLUGIN_NAME)
issues.push({
title: "Using legacy package name",
description: `Your opencode.json references "${LEGACY_PLUGIN_NAME}" which has been renamed to "${PLUGIN_NAME}". The old name may stop working in a future release.`,
fix: `Update your opencode.json plugin entry: "${pluginInfo.entry}" → "${suggestedEntry}"`,
severity: "warning",
affects: ["plugin loading"],
})
}
}
if (loadedInfo.expectedVersion && loadedInfo.loadedVersion && loadedInfo.expectedVersion !== loadedInfo.loadedVersion) {
issues.push({
title: "Loaded plugin version mismatch",
@@ -108,7 +125,7 @@ export async function checkSystem(): Promise<CheckResult> {
issues.push({
title: "Loaded plugin is outdated",
description: `Loaded ${systemInfo.loadedVersion}, latest ${latestVersion}.`,
fix: `Update: cd "${loadedInfo.cacheDir}" && bun add oh-my-opencode@${installTag}`,
fix: `Update: cd "${loadedInfo.cacheDir}" && bun add ${PLUGIN_NAME}@${installTag}`,
severity: "warning",
affects: ["plugin features"],
})

View File

@@ -1,4 +1,5 @@
import color from "picocolors"
import { PLUGIN_NAME } from "../../shared"
export const SYMBOLS = {
check: color.green("\u2713"),
@@ -38,6 +39,6 @@ export const EXIT_CODES = {
export const MIN_OPENCODE_VERSION = "1.0.150"
export const PACKAGE_NAME = "oh-my-opencode"
export const PACKAGE_NAME = PLUGIN_NAME
export const OPENCODE_BINARIES = ["opencode", "opencode-desktop"] as const

View File

@@ -53,6 +53,14 @@ describe("install CLI - binary check behavior", () => {
isOpenCodeInstalledSpy = spyOn(configManager, "isOpenCodeInstalled").mockResolvedValue(false)
getOpenCodeVersionSpy = spyOn(configManager, "getOpenCodeVersion").mockResolvedValue(null)
// given mock npm fetch
globalThis.fetch = mock(() =>
Promise.resolve({
ok: true,
json: () => Promise.resolve({ latest: "3.0.0" }),
} as Response)
) as unknown as typeof fetch
const args: InstallArgs = {
tui: false,
claude: "yes",
@@ -105,10 +113,10 @@ describe("install CLI - binary check behavior", () => {
const configPath = join(tempDir, "opencode.json")
expect(existsSync(configPath)).toBe(true)
// then opencode.json should have plugin entry
const config = JSON.parse(readFileSync(configPath, "utf-8"))
expect(config.plugin).toBeDefined()
expect(config.plugin.some((p: string) => p.includes("oh-my-opencode"))).toBe(true)
expect(config.plugin.some((p: string) => p.includes("oh-my-openagent"))).toBe(true)
expect(config.plugin.some((p: string) => p.includes("oh-my-opencode"))).toBe(false)
// then exit code should be 0 (success)
expect(exitCode).toBe(0)

View File

@@ -458,7 +458,7 @@ describe("generateModelConfig", () => {
const result = generateModelConfig(config)
// #then
expect(result.agents?.hephaestus?.model).toBe("openai/gpt-5.3-codex")
expect(result.agents?.hephaestus?.model).toBe("openai/gpt-5.4")
expect(result.agents?.hephaestus?.variant).toBe("medium")
})
@@ -484,7 +484,7 @@ describe("generateModelConfig", () => {
const result = generateModelConfig(config)
// #then
expect(result.agents?.hephaestus?.model).toBe("opencode/gpt-5.3-codex")
expect(result.agents?.hephaestus?.model).toBe("opencode/gpt-5.4")
expect(result.agents?.hephaestus?.variant).toBe("medium")
})

View File

@@ -55,7 +55,7 @@ export function generateModelConfig(config: InstallConfig): GeneratedOmoConfig {
for (const [role, req] of Object.entries(CLI_AGENT_MODEL_REQUIREMENTS)) {
if (role === "librarian") {
if (avail.opencodeGo) {
agents[role] = { model: "opencode-go/minimax-m2.5" }
agents[role] = { model: "opencode-go/minimax-m2.7" }
} else if (avail.zai) {
agents[role] = { model: ZAI_MODEL }
}
@@ -68,7 +68,7 @@ export function generateModelConfig(config: InstallConfig): GeneratedOmoConfig {
} else if (avail.opencodeZen) {
agents[role] = { model: "opencode/claude-haiku-4-5" }
} else if (avail.opencodeGo) {
agents[role] = { model: "opencode-go/minimax-m2.5" }
agents[role] = { model: "opencode-go/minimax-m2.7" }
} else if (avail.copilot) {
agents[role] = { model: "github-copilot/gpt-5-mini" }
} else {

View File

@@ -40,7 +40,7 @@ describe("generateModelConfig OpenAI-only model catalog", () => {
// #then
expect(result.categories?.artistry).toEqual({ model: "openai/gpt-5.4", variant: "xhigh" })
expect(result.categories?.quick).toEqual({ model: "openai/gpt-5.3-codex", variant: "low" })
expect(result.categories?.quick).toEqual({ model: "openai/gpt-5.4-mini" })
expect(result.categories?.["visual-engineering"]).toEqual({ model: "openai/gpt-5.4", variant: "high" })
expect(result.categories?.writing).toEqual({ model: "openai/gpt-5.4", variant: "medium" })
})
@@ -53,8 +53,8 @@ describe("generateModelConfig OpenAI-only model catalog", () => {
const result = generateModelConfig(config)
// #then
expect(result.agents?.explore).toEqual({ model: "opencode-go/minimax-m2.5" })
expect(result.agents?.librarian).toEqual({ model: "opencode-go/minimax-m2.5" })
expect(result.categories?.quick).toEqual({ model: "opencode-go/minimax-m2.5" })
expect(result.agents?.explore).toEqual({ model: "opencode-go/minimax-m2.7" })
expect(result.agents?.librarian).toEqual({ model: "opencode-go/minimax-m2.7" })
expect(result.categories?.quick).toEqual({ model: "openai/gpt-5.4-mini" })
})
})

View File

@@ -7,7 +7,7 @@ const OPENAI_ONLY_AGENT_OVERRIDES: Record<string, AgentConfig> = {
const OPENAI_ONLY_CATEGORY_OVERRIDES: Record<string, CategoryConfig> = {
artistry: { model: "openai/gpt-5.4", variant: "xhigh" },
quick: { model: "openai/gpt-5.3-codex", variant: "low" },
quick: { model: "openai/gpt-5.4-mini" },
"visual-engineering": { model: "openai/gpt-5.4", variant: "high" },
writing: { model: "openai/gpt-5.4", variant: "medium" },
}

View File

@@ -0,0 +1,114 @@
import { describe, expect, it, mock } from "bun:test"
import { refreshModelCapabilities } from "./refresh-model-capabilities"
describe("refreshModelCapabilities", () => {
it("uses config source_url when CLI override is absent", async () => {
const loadConfig = mock(() => ({
model_capabilities: {
source_url: "https://mirror.example/api.json",
},
}))
const refreshCache = mock(async () => ({
generatedAt: "2026-03-25T00:00:00.000Z",
sourceUrl: "https://mirror.example/api.json",
models: {
"gpt-5.4": { id: "gpt-5.4" },
},
}))
let stdout = ""
const exitCode = await refreshModelCapabilities(
{ directory: "/repo", json: false },
{
loadConfig,
refreshCache,
stdout: {
write: (chunk: string) => {
stdout += chunk
return true
},
} as never,
stderr: {
write: () => true,
} as never,
},
)
expect(exitCode).toBe(0)
expect(loadConfig).toHaveBeenCalledWith("/repo", null)
expect(refreshCache).toHaveBeenCalledWith({
sourceUrl: "https://mirror.example/api.json",
})
expect(stdout).toContain("Refreshed model capabilities cache (1 models)")
})
it("CLI sourceUrl overrides config and supports json output", async () => {
const refreshCache = mock(async () => ({
generatedAt: "2026-03-25T00:00:00.000Z",
sourceUrl: "https://override.example/api.json",
models: {
"gpt-5.4": { id: "gpt-5.4" },
"claude-opus-4-6": { id: "claude-opus-4-6" },
},
}))
let stdout = ""
const exitCode = await refreshModelCapabilities(
{
directory: "/repo",
json: true,
sourceUrl: "https://override.example/api.json",
},
{
loadConfig: () => ({}),
refreshCache,
stdout: {
write: (chunk: string) => {
stdout += chunk
return true
},
} as never,
stderr: {
write: () => true,
} as never,
},
)
expect(exitCode).toBe(0)
expect(refreshCache).toHaveBeenCalledWith({
sourceUrl: "https://override.example/api.json",
})
expect(JSON.parse(stdout)).toEqual({
sourceUrl: "https://override.example/api.json",
generatedAt: "2026-03-25T00:00:00.000Z",
modelCount: 2,
})
})
it("returns exit code 1 when refresh fails", async () => {
let stderr = ""
const exitCode = await refreshModelCapabilities(
{ directory: "/repo" },
{
loadConfig: () => ({}),
refreshCache: async () => {
throw new Error("boom")
},
stdout: {
write: () => true,
} as never,
stderr: {
write: (chunk: string) => {
stderr += chunk
return true
},
} as never,
},
)
expect(exitCode).toBe(1)
expect(stderr).toContain("Failed to refresh model capabilities cache")
})
})

View File

@@ -0,0 +1,51 @@
import { loadPluginConfig } from "../plugin-config"
import { refreshModelCapabilitiesCache } from "../shared/model-capabilities-cache"
export type RefreshModelCapabilitiesOptions = {
directory?: string
json?: boolean
sourceUrl?: string
}
type RefreshModelCapabilitiesDeps = {
loadConfig?: typeof loadPluginConfig
refreshCache?: typeof refreshModelCapabilitiesCache
stdout?: Pick<typeof process.stdout, "write">
stderr?: Pick<typeof process.stderr, "write">
}
export async function refreshModelCapabilities(
options: RefreshModelCapabilitiesOptions,
deps: RefreshModelCapabilitiesDeps = {},
): Promise<number> {
const directory = options.directory ?? process.cwd()
const loadConfig = deps.loadConfig ?? loadPluginConfig
const refreshCache = deps.refreshCache ?? refreshModelCapabilitiesCache
const stdout = deps.stdout ?? process.stdout
const stderr = deps.stderr ?? process.stderr
try {
const config = loadConfig(directory, null)
const sourceUrl = options.sourceUrl ?? config.model_capabilities?.source_url
const snapshot = await refreshCache({ sourceUrl })
const summary = {
sourceUrl: snapshot.sourceUrl,
generatedAt: snapshot.generatedAt,
modelCount: Object.keys(snapshot.models).length,
}
if (options.json) {
stdout.write(`${JSON.stringify(summary, null, 2)}\n`)
} else {
stdout.write(
`Refreshed model capabilities cache (${summary.modelCount} models) from ${summary.sourceUrl}\n`,
)
}
return 0
} catch (error) {
stderr.write(`Failed to refresh model capabilities cache: ${String(error)}\n`)
return 1
}
}

View File

@@ -159,8 +159,15 @@ describe("integration: --session-id", () => {
describe("integration: --on-complete", () => {
let spawnSpy: ReturnType<typeof spyOn>
let originalPlatform: NodeJS.Platform
let originalEnv: Record<string, string | undefined>
beforeEach(() => {
originalPlatform = process.platform
originalEnv = {
SHELL: process.env.SHELL,
PSModulePath: process.env.PSModulePath,
}
spyOn(console, "error").mockImplementation(() => {})
spawnSpy = spyOn(spawnWithWindowsHideModule, "spawnWithWindowsHide").mockReturnValue({
exited: Promise.resolve(0),
@@ -172,11 +179,22 @@ describe("integration: --on-complete", () => {
})
afterEach(() => {
Object.defineProperty(process, "platform", { value: originalPlatform })
for (const [key, value] of Object.entries(originalEnv)) {
if (value !== undefined) {
process.env[key] = value
} else {
delete process.env[key]
}
}
spawnSpy.mockRestore()
})
it("passes all 4 env vars as strings to spawned process", async () => {
// given
Object.defineProperty(process, "platform", { value: "linux" })
process.env.SHELL = "/bin/bash"
delete process.env.PSModulePath
spawnSpy.mockClear()
// when
@@ -206,8 +224,15 @@ describe("integration: option combinations", () => {
let mockStdout: MockWriteStream
let mockStderr: MockWriteStream
let spawnSpy: ReturnType<typeof spyOn>
let originalPlatform: NodeJS.Platform
let originalEnv: Record<string, string | undefined>
beforeEach(() => {
originalPlatform = process.platform
originalEnv = {
SHELL: process.env.SHELL,
PSModulePath: process.env.PSModulePath,
}
spyOn(console, "log").mockImplementation(() => {})
spyOn(console, "error").mockImplementation(() => {})
mockStdout = createMockWriteStream()
@@ -222,11 +247,22 @@ describe("integration: option combinations", () => {
})
afterEach(() => {
Object.defineProperty(process, "platform", { value: originalPlatform })
for (const [key, value] of Object.entries(originalEnv)) {
if (value !== undefined) {
process.env[key] = value
} else {
delete process.env[key]
}
}
spawnSpy?.mockRestore?.()
})
it("json output and on-complete hook can both execute", async () => {
// given - json manager active + on-complete hook ready
Object.defineProperty(process, "platform", { value: "linux" })
process.env.SHELL = "/bin/bash"
delete process.env.PSModulePath
const result: RunResult = {
sessionId: "session-123",
success: true,

View File

@@ -4,6 +4,9 @@ import * as loggerModule from "../../shared/logger"
import { executeOnCompleteHook } from "./on-complete-hook"
describe("executeOnCompleteHook", () => {
let originalPlatform: NodeJS.Platform
let originalEnv: Record<string, string | undefined>
function createStream(text: string): ReadableStream<Uint8Array> | undefined {
if (text.length === 0) {
return undefined
@@ -31,15 +34,32 @@ describe("executeOnCompleteHook", () => {
let logSpy: ReturnType<typeof spyOn<typeof loggerModule, "log">>
beforeEach(() => {
originalPlatform = process.platform
originalEnv = {
SHELL: process.env.SHELL,
PSModulePath: process.env.PSModulePath,
ComSpec: process.env.ComSpec,
}
logSpy = spyOn(loggerModule, "log").mockImplementation(() => {})
})
afterEach(() => {
Object.defineProperty(process, "platform", { value: originalPlatform })
for (const [key, value] of Object.entries(originalEnv)) {
if (value !== undefined) {
process.env[key] = value
} else {
delete process.env[key]
}
}
logSpy.mockRestore()
})
it("executes command with correct env vars", async () => {
it("uses sh on unix shells and passes correct env vars", async () => {
// given
Object.defineProperty(process, "platform", { value: "linux" })
process.env.SHELL = "/bin/bash"
delete process.env.PSModulePath
const spawnSpy = spyOn(spawnWithWindowsHideModule, "spawnWithWindowsHide").mockReturnValue(createProc(0))
try {
@@ -68,6 +88,82 @@ describe("executeOnCompleteHook", () => {
}
})
it("uses powershell when PowerShell is detected on Windows", async () => {
// given
Object.defineProperty(process, "platform", { value: "win32" })
process.env.PSModulePath = "C:\\Program Files\\PowerShell\\Modules"
delete process.env.SHELL
const spawnSpy = spyOn(spawnWithWindowsHideModule, "spawnWithWindowsHide").mockReturnValue(createProc(0))
try {
// when
await executeOnCompleteHook({
command: "Write-Host done",
sessionId: "session-123",
exitCode: 0,
durationMs: 5000,
messageCount: 10,
})
// then
const [args] = spawnSpy.mock.calls[0] as Parameters<typeof spawnWithWindowsHideModule.spawnWithWindowsHide>
expect(args).toEqual(["powershell.exe", "-NoProfile", "-Command", "Write-Host done"])
} finally {
spawnSpy.mockRestore()
}
})
it("uses pwsh when PowerShell is detected on non-Windows platforms", async () => {
// given
Object.defineProperty(process, "platform", { value: "linux" })
process.env.PSModulePath = "/usr/local/share/powershell/Modules"
delete process.env.SHELL
const spawnSpy = spyOn(spawnWithWindowsHideModule, "spawnWithWindowsHide").mockReturnValue(createProc(0))
try {
// when
await executeOnCompleteHook({
command: "Write-Host done",
sessionId: "session-123",
exitCode: 0,
durationMs: 5000,
messageCount: 10,
})
// then
const [args] = spawnSpy.mock.calls[0] as Parameters<typeof spawnWithWindowsHideModule.spawnWithWindowsHide>
expect(args).toEqual(["pwsh", "-NoProfile", "-Command", "Write-Host done"])
} finally {
spawnSpy.mockRestore()
}
})
it("falls back to cmd.exe on Windows when PowerShell is not detected", async () => {
// given
Object.defineProperty(process, "platform", { value: "win32" })
delete process.env.PSModulePath
delete process.env.SHELL
process.env.ComSpec = "C:\\Windows\\System32\\cmd.exe"
const spawnSpy = spyOn(spawnWithWindowsHideModule, "spawnWithWindowsHide").mockReturnValue(createProc(0))
try {
// when
await executeOnCompleteHook({
command: "echo done",
sessionId: "session-123",
exitCode: 0,
durationMs: 5000,
messageCount: 10,
})
// then
const [args] = spawnSpy.mock.calls[0] as Parameters<typeof spawnWithWindowsHideModule.spawnWithWindowsHide>
expect(args).toEqual(["C:\\Windows\\System32\\cmd.exe", "/d", "/s", "/c", "echo done"])
} finally {
spawnSpy.mockRestore()
}
})
it("env var values are strings", async () => {
// given
const spawnSpy = spyOn(spawnWithWindowsHideModule, "spawnWithWindowsHide").mockReturnValue(createProc(0))

View File

@@ -1,5 +1,5 @@
import { spawnWithWindowsHide } from "../../shared/spawn-with-windows-hide"
import { log } from "../../shared"
import { detectShellType, log } from "../../shared"
async function readOutput(
stream: ReadableStream<Uint8Array> | undefined,
@@ -20,6 +20,24 @@ async function readOutput(
}
}
function resolveHookShellCommand(command: string): string[] {
const shellType = detectShellType()
switch (shellType) {
case "powershell": {
const powershellExecutable = process.platform === "win32" ? "powershell.exe" : "pwsh"
return [powershellExecutable, "-NoProfile", "-Command", command]
}
case "cmd":
return [process.env.ComSpec || "cmd.exe", "/d", "/s", "/c", command]
case "csh":
return ["csh", "-c", command]
case "unix":
default:
return ["sh", "-c", command]
}
}
export async function executeOnCompleteHook(options: {
command: string
sessionId: string
@@ -37,7 +55,8 @@ export async function executeOnCompleteHook(options: {
log("Running on-complete hook", { command: trimmedCommand })
try {
const proc = spawnWithWindowsHide(["sh", "-c", trimmedCommand], {
const shellCommand = resolveHookShellCommand(trimmedCommand)
const proc = spawnWithWindowsHide(shellCommand, {
env: {
...process.env,
SESSION_ID: sessionId,

Some files were not shown because too many files have changed in this diff Show More