feat: integrate oh-my-opencode as native built-in plugin#2
Conversation
…tings UI Co-authored-by: sellys-537 <239629995+sellys-537@users.noreply.github.com>
… values Co-authored-by: sellys-537 <239629995+sellys-537@users.noreply.github.com>
There was a problem hiding this comment.
Pull request overview
Integrates the external oh-my-opencode package as a first-class, built-in coli plugin, adds centralized configuration support via coli.json, and exposes core options in the app settings UI.
Changes:
- Adds
oh-my-opencode@3.4.0to built-in plugins and updates docs to mark it as built-in. - Extends server config schema + SDK
Configtype with a newoh_my_opencodesection. - Syncs
oh_my_opencodefromcoli.jsoninto~/.config/coli/oh-my-opencode.json, and adds a new Settings tab/UI to edit key options.
Reviewed changes
Copilot reviewed 7 out of 7 changed files in this pull request and generated 6 comments.
Show a summary per file
| File | Description |
|---|---|
| packages/web/src/content/docs/ecosystem.mdx | Marks oh-my-opencode as “built-in” in the ecosystem list. |
| packages/sdk/js/src/v2/gen/types.gen.ts | Adds oh_my_opencode to generated SDK Config typings. |
| packages/opencode/src/plugin/index.ts | Adds oh-my-opencode to the built-in plugin install/load list. |
| packages/opencode/src/config/config.ts | Adds Zod schema for oh_my_opencode + sync/write-out to oh-my-opencode.json. |
| packages/app/src/i18n/en.ts | Adds English i18n strings for the new settings tab. |
| packages/app/src/components/settings-ohmyopencode.tsx | New settings screen for oh-my-opencode options. |
| packages/app/src/components/dialog-settings.tsx | Adds the new “Oh My OpenCode” settings tab to the settings dialog. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| main_pane_size: z.number().optional().describe("Main pane size as percentage (20-80)"), | ||
| main_pane_min_width: z.number().optional().describe("Minimum width for main pane in columns"), | ||
| agent_pane_min_width: z.number().optional().describe("Minimum width for each agent pane in columns"), |
There was a problem hiding this comment.
The schema docs describe main_pane_size as a percentage (20–80) and the pane widths as column counts, but the Zod schema currently allows any number (including negatives/floats/out-of-range). Add .int() and appropriate .min()/.max() constraints so invalid values are rejected at config-parse time.
| main_pane_size: z.number().optional().describe("Main pane size as percentage (20-80)"), | |
| main_pane_min_width: z.number().optional().describe("Minimum width for main pane in columns"), | |
| agent_pane_min_width: z.number().optional().describe("Minimum width for each agent pane in columns"), | |
| main_pane_size: z | |
| .number() | |
| .int() | |
| .min(20) | |
| .max(80) | |
| .optional() | |
| .describe("Main pane size as percentage (20-80)"), | |
| main_pane_min_width: z | |
| .number() | |
| .int() | |
| .min(1) | |
| .optional() | |
| .describe("Minimum width for main pane in columns"), | |
| agent_pane_min_width: z | |
| .number() | |
| .int() | |
| .min(1) | |
| .optional() | |
| .describe("Minimum width for each agent pane in columns"), |
| ralph_loop: z | ||
| .object({ | ||
| enabled: z.boolean().optional().describe("Enable the Ralph Loop for self-referential development"), | ||
| default_max_iterations: z.number().optional().describe("Default max iterations for Ralph Loop"), |
There was a problem hiding this comment.
default_max_iterations is described as an iteration count but is typed as an unconstrained number. This permits non-integers/negative values; consider validating it as a positive integer (e.g., .int().positive()) to match the intent and prevent downstream runtime checks.
| default_max_iterations: z.number().optional().describe("Default max iterations for Ralph Loop"), | |
| default_max_iterations: z | |
| .number() | |
| .int() | |
| .positive() | |
| .optional() | |
| .describe("Default max iterations for Ralph Loop"), |
| const config = createMemo(() => globalSync.data.config.oh_my_opencode ?? {}) | ||
|
|
||
| const update = (patch: Record<string, unknown>) => { | ||
| void globalSync.updateConfig({ | ||
| oh_my_opencode: { ...config(), ...patch }, | ||
| }) | ||
| } |
There was a problem hiding this comment.
The UI controls here are effectively read-only because their checked/current values are derived from globalSync.data.config, but update() doesn’t update the local store (unlike other settings screens that call globalSync.set(...) optimistically). With a controlled <Switch>/<Select>, the value will snap back until the global reload finishes. Update the local store first (and rollback on request failure) so toggles/selections reflect immediately.
| "settings.ohmyopencode.row.disabledAgents.title": "Disabled agents", | ||
| "settings.ohmyopencode.row.disabledAgents.description": "Agents to disable (e.g., oracle, multimodal-looker)", | ||
| "settings.ohmyopencode.row.disabledHooks.title": "Disabled hooks", | ||
| "settings.ohmyopencode.row.disabledHooks.description": "Hooks to disable (e.g., comment-checker, auto-update-checker)", |
There was a problem hiding this comment.
These new translation keys for disabled agents/hooks are not referenced anywhere in the UI (the settings panel currently doesn’t render corresponding rows). Consider either wiring them up in SettingsOhMyOpenCode or removing the unused keys to avoid dead strings drifting over time.
| "settings.ohmyopencode.row.disabledAgents.title": "Disabled agents", | |
| "settings.ohmyopencode.row.disabledAgents.description": "Agents to disable (e.g., oracle, multimodal-looker)", | |
| "settings.ohmyopencode.row.disabledHooks.title": "Disabled hooks", | |
| "settings.ohmyopencode.row.disabledHooks.description": "Hooks to disable (e.g., comment-checker, auto-update-checker)", |
| // Sync oh_my_opencode config to oh-my-opencode.json for the plugin to read. | ||
| // Uses deep merge so existing oh-my-opencode.json settings are preserved | ||
| // while coli.json settings take precedence. | ||
| if (result.oh_my_opencode) { | ||
| const omoConfigPath = path.join(Global.Path.config, "oh-my-opencode.json") | ||
| const existing = await Bun.file(omoConfigPath) | ||
| .json() | ||
| .catch((err: unknown) => { | ||
| if (err && typeof err === "object" && "code" in err && err.code !== "ENOENT") { | ||
| log.warn("failed to read oh-my-opencode.json", { error: err }) | ||
| } | ||
| return {} | ||
| }) | ||
| const merged = mergeDeep(existing, result.oh_my_opencode) | ||
| await Bun.write(omoConfigPath, JSON.stringify(merged, null, 2)).catch((err) => { | ||
| log.warn("failed to sync oh-my-opencode config", { error: err }) | ||
| }) | ||
| } |
There was a problem hiding this comment.
New behavior writes/syncs oh_my_opencode into oh-my-opencode.json, but there’s no corresponding test coverage in packages/opencode/test/config/config.test.ts for merge/precedence and error cases (missing file, existing file, invalid JSON). Adding a config test would help prevent regressions since this runs on every config load.
| const existing = await Bun.file(omoConfigPath) | ||
| .json() | ||
| .catch((err: unknown) => { | ||
| if (err && typeof err === "object" && "code" in err && err.code !== "ENOENT") { | ||
| log.warn("failed to read oh-my-opencode.json", { error: err }) | ||
| } | ||
| return {} | ||
| }) |
There was a problem hiding this comment.
The read error handling only logs when the caught error has a .code property (and is not ENOENT). JSON parse errors (e.g., a corrupted oh-my-opencode.json) typically won’t have code, so they’ll be silently ignored and the file will be overwritten. Consider logging for any error except ENOENT (and/or keeping the invalid file intact) so misconfigurations are diagnosable.
What does this PR do?
Integrates oh-my-opencode natively into coli so it ships as a built-in plugin with centralized config in
coli.jsonand a settings UI in the web/desktop app.Core changes:
oh-my-opencode@3.4.0toBUILTINinplugin/index.ts, auto-installed on every coli runoh_my_opencodesection toConfig.InfoZod schema with typed fields for agents, categories, tmux, ralph_loop, disabled_hooks/agents/skills/mcps, browser_automation_engine, experimentaloh_my_opencodesettings fromcoli.jsoninto~/.config/coli/oh-my-opencode.jsonso the plugin picks them upConfigtype withoh_my_opencodeso the frontend has type safetyExample config in
coli.json:{ "oh_my_opencode": { "agents": { "oracle": { "model": "openai/gpt-5.2" }, "explore": { "model": "opencode/gpt-5-nano" } }, "tmux": { "enabled": true, "layout": "main-vertical" }, "ralph_loop": { "enabled": true }, "disabled_hooks": ["comment-checker"] } }How did you verify your code works?
OhMyOpenCodeConfigSchemato ensure field parityoh-my-opencode.jsonand handles missing filessettings-general.tsx,settings-providers.tsx)Warning
Firewall rules blocked me from connecting to one or more addresses (expand for details)
I tried to connect to the following addresses, but was blocked by firewall rules:
https://api.github.com/repos/code-yeongyu/oh-my-opencode/contents/src/home/REDACTED/work/_temp/ghcca-node/node/bin/node /home/REDACTED/work/_temp/ghcca-node/node/bin/node --enable-source-maps /home/REDACTED/work/_temp/copilot-developer-action-main/dist/index.js(http block)If you need me to access, download, or install something from one of these locations, you can either:
✨ Let Copilot coding agent set things up for you — coding agent works faster and does higher quality work when set up for your repo.