Browse Source

Merge origin/master into fix/background-task-persist-across-reinit

Alvin Unreal 3 months ago
parent
commit
a10b6d91ae
50 changed files with 1741 additions and 755 deletions
  1. 9 0
      .all-contributorsrc
  2. 2 0
      .gitignore
  3. 8 4
      AGENTS.md
  4. 115 56
      README.md
  5. 42 0
      docs/configuration.md
  6. 29 16
      docs/installation.md
  7. 2 2
      docs/mcps.md
  8. 81 454
      docs/multiplexer-integration.md
  9. 15 15
      docs/provider-configurations.md
  10. 1 0
      docs/quick-reference.md
  11. 6 4
      docs/skills.md
  12. 45 0
      docs/todo-continuation.md
  13. 3 37
      docs/tools.md
  14. BIN
      img/team.jpeg
  15. 12 0
      oh-my-opencode-slim.schema.json
  16. 1 0
      scripts/verify-release-artifact.ts
  17. 203 0
      src/agents/display-name.test.ts
  18. 9 0
      src/agents/index.test.ts
  19. 118 25
      src/agents/index.ts
  20. 23 22
      src/agents/orchestrator.ts
  21. 125 1
      src/background/background-manager.test.ts
  22. 15 2
      src/background/background-manager.ts
  23. 35 0
      src/background/multiplexer-session-manager.test.ts
  24. 10 2
      src/background/multiplexer-session-manager.ts
  25. 3 0
      src/cli/config-io.test.ts
  26. 6 0
      src/cli/custom-skills.ts
  27. 3 5
      src/cli/index.ts
  28. 26 29
      src/cli/install.ts
  29. 16 1
      src/cli/providers.test.ts
  30. 17 5
      src/cli/providers.ts
  31. 1 1
      src/cli/skills.test.ts
  32. 0 7
      src/cli/skills.ts
  33. 0 1
      src/cli/types.ts
  34. 2 2
      src/config/agent-mcps.test.ts
  35. 1 1
      src/config/agent-mcps.ts
  36. 15 0
      src/config/loader.test.ts
  37. 7 0
      src/config/schema.ts
  38. 93 26
      src/hooks/image-hook.ts
  39. 27 2
      src/index.ts
  40. 71 0
      src/multiplexer/factory.test.ts
  41. 6 12
      src/multiplexer/factory.ts
  42. 45 10
      src/multiplexer/tmux/index.ts
  43. 2 1
      src/multiplexer/types.ts
  44. 48 8
      src/multiplexer/zellij/index.ts
  45. 19 0
      src/skills/simplify/README.md
  46. 138 0
      src/skills/simplify/SKILL.md
  47. 100 0
      src/tools/background.test.ts
  48. 2 1
      src/tools/background.ts
  49. 104 0
      src/utils/agent-variant.test.ts
  50. 80 3
      src/utils/agent-variant.ts

+ 9 - 0
.all-contributorsrc

@@ -353,6 +353,15 @@
       "contributions": [
         "code"
       ]
+    },
+    {
+      "login": "ZenStudioLab",
+      "name": "Nguyen Canh Toan",
+      "avatar_url": "https://avatars.githubusercontent.com/u/10528635?v=4",
+      "profile": "https://zenstudio.cv/",
+      "contributions": [
+        "code"
+      ]
     }
   ],
   "commitConvention": "angular"

+ 2 - 0
.gitignore

@@ -56,6 +56,8 @@ GOAL.md
 GOALS.md
 PR-NOTES.md
 REVIEW.md
+docs/plans
+docs/superpowers
 
 # Python
 __pycache__/

+ 8 - 4
AGENTS.md

@@ -92,10 +92,14 @@ oh-my-opencode-slim/
 ## Development Workflow
 
 1. Make code changes
-2. Run `bun run check:ci` to verify linting and formatting
-3. Run `bun run typecheck` to verify types
-4. Run `bun test` to verify tests pass
-5. Commit changes
+2. Update docs when behavior, commands, configuration, workflows, or user-facing output changes
+   - Check `README.md` plus relevant files in `docs/`
+   - Keep examples, command snippets, and feature lists in sync with the code
+   - If no doc update is needed, explicitly confirm that in your final summary
+3. Run `bun run check:ci` to verify linting and formatting
+4. Run `bun run typecheck` to verify types
+5. Run `bun test` to verify tests pass
+6. Commit changes
 
 ## Tmux Session Lifecycle Management
 

+ 115 - 56
README.md

@@ -1,8 +1,8 @@
 <div align="center">
-  <img src="img/team.png" alt="Pantheon agents" style="border-radius: 10px;" width="620">
+  <img src="img/team.jpeg" alt="Pantheon agents" style="border-radius: 10px;">
   <p><i>Seven divine beings emerged from the dawn of code, each an immortal master of their craft await your command to forge order from chaos and build what was once thought impossible.</i></p>
   <p><b>Open Multi Agent Suite</b> · Mix any models · Auto delegate tasks</p>
-  <p><a href="https://moltfounders.com/jobs/09d1c6e7-9e0e-4683-8d78-e2376aaa2333"><img src="https://moltfounders.com/badges/4.png" alt="MoltFounders" height="30"></a></p>
+
   <p><sub>by <b>Boring Dystopia Development</b></sub></p>
   <p>
     <a href="https://boringdystopia.ai/"><img src="https://img.shields.io/badge/boringdystopia.ai-111111?style=for-the-badge&logo=vercel&logoColor=white" alt="boringdystopia.ai"></a>&nbsp;
@@ -13,7 +13,13 @@
 
 ---
 
-## 📦 Installation
+## What's This Plugin
+
+oh-my-opencode-slim is an agent orchestration plugin for OpenCode. It includes a built-in team of specialized agents that can scout a codebase, look up fresh documentation, review architecture, handle UI work, and execute well-scoped implementation tasks under one orchestrator.
+
+The main idea is simple: instead of forcing one model to do everything, the plugin routes each part of the job to the agent best suited for it, balancing **quality, speed and cost**.
+
+To explore the agents themselves, see **[Meet the Pantheon](#meet-the-pantheon)**. For the full feature set, see **[Features & Workflows](#features-and-workflows)** below.
 
 ### Quick Start
 
@@ -21,49 +27,53 @@
 bunx oh-my-opencode-slim@latest install
 ```
 
-The installer generates an OpenAI configuration by default (using `gpt-5.4` and `gpt-5.4-mini`). No provider questions asked.
+### Getting Started
 
-For non-interactive mode:
+The installer generates an OpenAI preset by default, using `openai/gpt-5.4` for the higher-judgment agents and `openai/gpt-5.4-mini` for the faster scoped agents.
 
-```bash
-bunx oh-my-opencode-slim@latest install --no-tui --tmux=yes --skills=yes
-```
+Then:
 
-To force overwrite of an existing configuration:
-```bash
-bunx oh-my-opencode-slim@latest install --reset
-```
+1. **Log in to the providers you want to use if you haven't already**:
 
-### For Alternative Providers
+   ```bash
+   opencode auth login
+   ```
+2. **Refresh and list the models OpenCode can see**:
 
-The default configuration uses OpenAI. To use Kimi, GitHub Copilot, or ZAI Coding Plan, see **[Provider Configurations](docs/provider-configurations.md)** for step-by-step instructions and config examples.
+   ```bash
+   opencode models --refresh
+   ```
+3. **Open your plugin config** at `~/.config/opencode/oh-my-opencode-slim.json`
 
-> [!TIP]
-> Want to see the latest models OpenCode knows about? Run `opencode models --refresh` to refresh the cache and list currently available models.
+4. **Update the models you want for each agent**
 
-### JSON Schema
+> [!TIP]
+> Want to understand how automatic delegation works in practice? Review the **[Orchestrator prompt](https://github.com/alvinunreal/oh-my-opencode-slim/blob/master/src/agents/orchestrator.ts#L28)** — it contains the delegation rules, specialist routing logic, and the thresholds for when the main agent should hand work off to subagents.
 
-An official JSON Schema is included in the package for editor validation and autocomplete. Add a `$schema` reference to your config file:
+The default generated configuration looks like this:
 
 ```jsonc
 {
   "$schema": "https://unpkg.com/oh-my-opencode-slim@latest/oh-my-opencode-slim.schema.json",
-  // your config...
+  "preset": "openai",
+  "presets": {
+    "openai": {
+      "orchestrator": { "model": "openai/gpt-5.4", "variant": "high", "skills": ["*"], "mcps": ["*", "!context7"] },
+      "oracle": { "model": "openai/gpt-5.4", "variant": "high", "skills": ["simplify"], "mcps": [] },
+      "librarian": { "model": "openai/gpt-5.4-mini", "variant": "low", "skills": [], "mcps": ["websearch", "context7", "grep_app"] },
+      "explorer": { "model": "openai/gpt-5.4-mini", "variant": "low", "skills": ["cartography"], "mcps": [] },
+      "designer": { "model": "openai/gpt-5.4-mini", "variant": "medium", "skills": ["agent-browser"], "mcps": [] },
+      "fixer": { "model": "openai/gpt-5.4-mini", "variant": "low", "skills": [], "mcps": [] }
+    }
+  }
 }
 ```
 
-This enables autocomplete and inline validation in VS Code, Neovim, and other editors that support JSON Schema.
+### For Alternative Providers
 
-### For LLM Agents
+To use Kimi, GitHub Copilot, ZAI Coding Plan, or a mixed-provider setup, see **[Provider Configurations](docs/provider-configurations.md)** for step-by-step config examples. If you want a ready-made starting point, check the **[Author's Preset](docs/authors-preset.md)** and **[$30 Preset](docs/thirty-dollars-preset.md)** - the `$30` preset is the best cheap setup.
 
-Paste this into any coding agent:
-
-```
-Install and configure by following the instructions here:
-https://raw.githubusercontent.com/alvinunreal/oh-my-opencode-slim/refs/heads/master/README.md
-```
-
-**Detailed installation guide:** [docs/installation.md](docs/installation.md)
+You can also mix and match any models per agent. For model suggestions, see the **Recommended Models** listed under each agent below.
 
 ### ✅ Verify Your Setup
 
@@ -88,6 +98,8 @@ If any agent fails to respond, check your provider authentication and config fil
 
 ---
 
+<a id="meet-the-pantheon"></a>
+
 ## 🏛️ Meet the Pantheon
 
 ### 01. Orchestrator: The Embodiment Of Order
@@ -122,6 +134,11 @@ If any agent fails to respond, check your provider authentication and config fil
       <b>Recommended Models:</b> <code>openai/gpt-5.4</code> <code>anthropic/claude-opus-4.6</code>
     </td>
   </tr>
+  <tr>
+    <td colspan="2">
+      <b>Model Guidance:</b> Choose your default, strongest all-around coding model. Orchestrator is both the main coding agent and the delegator, so it needs strong implementation ability, good judgment, and reliable instruction-following.
+    </td>
+  </tr>
 </table>
 
 ---
@@ -158,6 +175,11 @@ If any agent fails to respond, check your provider authentication and config fil
       <b>Recommended Models:</b> <code>cerebras/zai-glm-4.7</code> <code>fireworks-ai/accounts/fireworks/routers/kimi-k2p5-turbo</code> <code>openai/gpt-5.4-mini</code>
     </td>
   </tr>
+  <tr>
+    <td colspan="2">
+      <b>Model Guidance:</b> Choose a fast, low-cost model. Explorer handles broad scouting work, so speed and efficiency usually matter more than using your strongest reasoning model.
+    </td>
+  </tr>
 </table>
 
 ---
@@ -194,12 +216,20 @@ If any agent fails to respond, check your provider authentication and config fil
       <b>Recommended Models:</b> <code>openai/gpt-5.4 (high)</code> <code>google/gemini-3.1-pro-preview (high)</code>
     </td>
   </tr>
+  <tr>
+    <td colspan="2">
+      <b>Model Guidance:</b> Choose your strongest high-reasoning model for architecture, hard debugging, trade-offs, and code review.
+    </td>
+  </tr>
 </table>
 
 ---
 
 ### Council: The Chorus of Minds
 
+> [!NOTE]
+> **Why doesn't Orchestrator auto-call Council more often?** This is intentional. Council runs multiple models at once, so automatic delegation is kept strict because it is usually the highest-cost path in the system. In practice, Council is meant to be used manually when you want it, for example: <code>@council compare these two architectures</code>.
+
 <table>
   <tr>
     <td width="30%" align="center" valign="top">
@@ -235,6 +265,11 @@ If any agent fails to respond, check your provider authentication and config fil
       <b>Recommended Setup:</b> <code>Strong master</code> + <code>diverse councillors</code> across providers
     </td>
   </tr>
+  <tr>
+    <td colspan="2">
+      <b>Model Guidance:</b> Use a strong synthesis model as the master and diverse models as councillors. The value of Council comes from comparing different model perspectives, not just picking the single strongest model everywhere.
+    </td>
+  </tr>
 </table>
 
 ---
@@ -271,6 +306,11 @@ If any agent fails to respond, check your provider authentication and config fil
       <b>Recommended Models:</b> <code>cerebras/zai-glm-4.7</code> <code>fireworks-ai/accounts/fireworks/routers/kimi-k2p5-turbo</code> <code>openai/gpt-5.4-mini</code>
     </td>
   </tr>
+  <tr>
+    <td colspan="2">
+      <b>Model Guidance:</b> Choose a fast, low-cost model. Librarian handles research and documentation lookups, so speed and efficiency usually matter more than using your strongest reasoning model.
+    </td>
+  </tr>
 </table>
 
 ---
@@ -307,6 +347,11 @@ If any agent fails to respond, check your provider authentication and config fil
       <b>Recommended Models:</b> <code>google/gemini-3.1-pro-preview</code> <code>kimi-for-coding/k2p5</code> 
     </td>
   </tr>
+  <tr>
+    <td colspan="2">
+      <b>Model Guidance:</b> Choose a model that is strong at UI/UX judgment, frontend implementation, and visual polish.
+    </td>
+  </tr>
 </table>
 
 ---
@@ -343,12 +388,17 @@ If any agent fails to respond, check your provider authentication and config fil
       <b>Recommended Models:</b> <code>cerebras/zai-glm-4.7</code> <code>fireworks-ai/accounts/fireworks/routers/kimi-k2p5-turbo</code> <code>openai/gpt-5.4-mini</code>
     </td>
   </tr>
+  <tr>
+    <td colspan="2">
+      <b>Model Guidance:</b> Choose a fast, reliable coding model for routine, scoped implementation work. Fixer usually receives a concrete plan or bounded instructions from Orchestrator, making it a good place for efficient execution tasks such as tests, test updates, and straightforward code changes.
+    </td>
+  </tr>
 </table>
 
 ### 07. Observer: The Silent Witness
 
 > [!NOTE]
-> **Why a separate agent?** Not all models support vision. Your strongest coding model (e.g. for design decisions) may not be able to read images, while a vision-capable model may not be the best for reasoning. Observer solves this by having its **own model** — configure a vision-capable model for it while keeping Designer on your strongest reasoning model. Disabled by default; enable via `disabled_agents: []` in config.
+> **Why a separate agent?** If your Orchestrator model is not multimodal, enable Observer to handle images, screenshots, PDFs, and other visual files. Observer is disabled by default and gives the Orchestrator a dedicated multimodal reader without forcing you to change your main reasoning model. Set `disabled_agents: []` and an `observer` model in your configuration.
 
 <table>
   <tr>
@@ -376,45 +426,53 @@ If any agent fails to respond, check your provider authentication and config fil
       <b>Default Model:</b> <code>openai/gpt-5.4-mini</code> — <i>configure a vision-capable model to enable</i>
     </td>
   </tr>
+  <tr>
+    <td colspan="2">
+      <b>Model Guidance:</b> Choose a vision-capable model if you want the agent to read screenshots, images, PDFs, and other visual files.
+    </td>
+  </tr>
 </table>
 
 ---
 
 ## 📚 Documentation
 
-### 🚀 Getting Started
+Use this section as a map: start with installation, then jump to features, configuration, or example presets depending on what you need.
 
-| Doc | Contents |
-|-----|----------|
-| **[Installation Guide](docs/installation.md)** | CLI flags, `--reset`, auth, troubleshooting |
-| **[Provider Configurations](docs/provider-configurations.md)** | OpenAI, Kimi, Copilot, ZAI, Fireworks AI — mixing providers, fallback chains |
+### 🚀 Start Here
 
-### ✨ Features
+| Doc | What it covers |
+|-----|----------------|
+| **[Installation Guide](docs/installation.md)** | Install the plugin, use CLI flags, reset config, and troubleshoot setup |
+| **[Provider Configurations](docs/provider-configurations.md)** | Configure OpenAI, Kimi, GitHub Copilot, ZAI, Fireworks AI, or mixed-provider presets |
 
-| Feature | Doc | What it does |
-|---------|-----|--------------|
-| **Council** | [council.md](docs/council.md) | Run N models in parallel, synthesize one answer (`@council`) |
-| **Interview** | [interview.md](docs/interview.md) | Browser-based Q&A flow for turning rough ideas into a live markdown spec |
-| **Multiplexer Integration** | [multiplexer-integration.md](docs/multiplexer-integration.md) | Watch agents work in real-time with auto-spawned panes (Tmux/Zellij) |
-| **Cartography Skill** | [cartography.md](docs/cartography.md) | Auto-generate hierarchical codemaps for any codebase |
+<a id="features-and-workflows"></a>
 
-### ⚙️ Config & Reference
+### ✨ Features & Workflows
+
+| Doc | What it covers |
+|-----|----------------|
+| **[Council](docs/council.md)** | Run multiple models in parallel and synthesize a single answer with `@council` |
+| **[Interview](docs/interview.md)** | Turn rough ideas into a structured markdown spec through a browser-based Q&A flow |
+| **[Multiplexer Integration](docs/multiplexer-integration.md)** | Watch agents work live in Tmux or Zellij panes |
+| **[Todo Continuation](docs/todo-continuation.md)** | Auto-continue orchestrator sessions with cooldowns and safety checks |
+| **[Cartography](docs/cartography.md)** | Generate hierarchical codemaps to understand large codebases faster |
 
-| Doc | Contents |
-|-----|----------|
-| **[Skills](docs/skills.md)** | `simplify`, `agent-browser`, `cartography` — assignment syntax |
-| **[MCPs](docs/mcps.md)** | `websearch`, `context7`, `grep_app` — permissions per agent |
-| **[Tools](docs/tools.md)** | Background tasks, LSP, code search, formatters |
-| **[Configuration](docs/configuration.md)** | Config files, prompt overriding, JSONC, full option reference |
+### ⚙️ Config & Reference
 
-Slim only intercepts `apply_patch` before native execution. It rewrites recoverable stale patches, canonizes safe tolerant matches against the real file when unicode/trim drift is the only mismatch, keeps the authored `new_lines` bytes intact, preserves existing file EOL/final-newline state for updates, validates malformed patches strictly before helper execution, uses a conservative bounded LCS fallback, supports sequential `Update File` hunks on the same path through accumulated helper state, and blocks `apply_patch` before the native tool runs if any patch path falls outside the allowed root/worktree. This rescue does not extend to `edit` or `write`.
+| Doc | What it covers |
+|-----|----------------|
+| **[Configuration](docs/configuration.md)** | Config file locations, JSONC support, prompt overrides, and full option reference |
+| **[Skills](docs/skills.md)** | Built-in and recommended skills such as `simplify`, `agent-browser`, and `cartography` |
+| **[MCPs](docs/mcps.md)** | `websearch`, `context7`, `grep_app`, and how MCP permissions work per agent |
+| **[Tools](docs/tools.md)** | Built-in tool capabilities like background tasks, `webfetch`, LSP tools, code search, and formatters |
 
-### 💡 Presets
+### 💡 Example Presets
 
-| Doc | Contents |
-|-----|----------|
-| **[Author's Preset](docs/authors-preset.md)** | The exact config the author runs daily — OpenAI Pro + GitHub Copilot |
-| **[$30 Preset](docs/thirty-dollars-preset.md)** | A mixed setup using Codex Plus ($20) + GitHub Copilot Pro ($10) for about $30/month total |
+| Doc | What it covers |
+|-----|----------------|
+| **[Author's Preset](docs/authors-preset.md)** | The author's daily mixed-provider setup |
+| **[$30 Preset](docs/thirty-dollars-preset.md)** | A budget mixed-provider setup for around $30/month |
 
 ---
 
@@ -425,7 +483,7 @@ Slim only intercepts `apply_patch` before native execution. It rewrites recovera
   <p><sub>Every merged contribution leaves a mark on the realm.</sub></p>
 
   <!-- ALL-CONTRIBUTORS-BADGE:START - Do not remove or modify this section -->
-[![All Contributors](https://img.shields.io/badge/all_contributors-38-orange.svg?style=flat-square)](#contributors-)
+[![All Contributors](https://img.shields.io/badge/all_contributors-39-orange.svg?style=flat-square)](#contributors-)
 <!-- ALL-CONTRIBUTORS-BADGE:END -->
 </div>
 
@@ -487,6 +545,7 @@ Slim only intercepts `apply_patch` before native execution. It rewrites recovera
     <tr>
       <td align="center" valign="top" width="16.66%"><a href="https://github.com/dkovacevic15"><img src="https://avatars.githubusercontent.com/u/24757821?v=4?s=100" width="100px;" alt="Dusan Kovacevic"/><br /><sub><b>Dusan Kovacevic</b></sub></a><br /><a href="https://github.com/alvinunreal/oh-my-opencode-slim/commits?author=dkovacevic15" title="Code">💻</a></td>
       <td align="center" valign="top" width="16.66%"><a href="https://github.com/jwcrystal"><img src="https://avatars.githubusercontent.com/u/121911854?v=4?s=100" width="100px;" alt="jwcrystal"/><br /><sub><b>jwcrystal</b></sub></a><br /><a href="https://github.com/alvinunreal/oh-my-opencode-slim/commits?author=jwcrystal" title="Code">💻</a></td>
+      <td align="center" valign="top" width="16.66%"><a href="https://zenstudio.cv/"><img src="https://avatars.githubusercontent.com/u/10528635?v=4?s=100" width="100px;" alt="Nguyen Canh Toan"/><br /><sub><b>Nguyen Canh Toan</b></sub></a><br /><a href="https://github.com/alvinunreal/oh-my-opencode-slim/commits?author=ZenStudioLab" title="Code">💻</a></td>
     </tr>
   </tbody>
 </table>

+ 42 - 0
docs/configuration.md

@@ -86,9 +86,12 @@ All config files support **JSONC** (JSON with Comments):
 | `presets.<name>.<agent>.model` | string | — | Model ID in `provider/model` format |
 | `presets.<name>.<agent>.temperature` | number | — | Temperature (0–2) |
 | `presets.<name>.<agent>.variant` | string | — | Reasoning effort: `"low"`, `"medium"`, `"high"` |
+| `presets.<name>.<agent>.displayName` | string | — | Custom user-facing alias for the agent (e.g. `"advisor"` for `oracle`) |
 | `presets.<name>.<agent>.skills` | string[] | — | Skills the agent can use (`"*"`, `"!item"`, explicit list) |
 | `presets.<name>.<agent>.mcps` | string[] | — | MCPs the agent can use (`"*"`, `"!item"`, explicit list) |
 | `presets.<name>.<agent>.options` | object | — | Provider-specific model options passed to the AI SDK (e.g., `textVerbosity`, `thinking` budget) |
+| `agents.<agent>.displayName` | string | — | Custom user-facing alias for the agent in the active config |
+| `showStartupToast` | boolean | `true` | Show the startup activation toast (`oh-my-opencode-slim is active`) when OpenCode starts |
 | `tmux.enabled` | boolean | `false` | Enable tmux pane spawning |
 | `tmux.layout` | string | `"main-vertical"` | Layout: `main-vertical`, `main-horizontal`, `tiled`, `even-horizontal`, `even-vertical` |
 | `tmux.main_pane_size` | number | `60` | Main pane size as percentage (20–80) |
@@ -122,3 +125,42 @@ All config files support **JSONC** (JSON with Comments):
 | `interview.autoOpenBrowser` | boolean | `true` | Automatically open the interview UI in your default browser |
 | `interview.port` | integer | `0` | Interview server port (0–65535). `0` = OS-assigned random port (per-session mode). Any value > 0 enables [dashboard mode](interview.md#dashboard-mode) |
 | `interview.dashboard` | boolean | `false` | Enable [dashboard mode](interview.md#dashboard-mode) on the default port (43211). Setting `port` > 0 also enables dashboard mode. If both are set, `port` takes precedence |
+
+### Startup Toast
+
+Set `showStartupToast` to `false` if you want to disable the startup toast that
+appears when the plugin activates.
+
+```jsonc
+{
+  "showStartupToast": false
+}
+```
+
+### Agent Display Names
+
+Use `displayName` to give an agent a user-facing alias while keeping the
+internal agent name unchanged.
+
+```jsonc
+{
+  "agents": {
+    "oracle": {
+      "displayName": "advisor"
+    },
+    "explorer": {
+      "displayName": "researcher"
+    }
+  }
+}
+```
+
+With this config, users can refer to `@advisor` and `@researcher`, while the
+plugin still routes them to `oracle` and `explorer` internally.
+
+Notes:
+
+- `displayName` works in both top-level `agents` overrides and inside `presets`
+- `@` prefixes and surrounding whitespace are normalized automatically
+- Display names must be unique
+- Display names cannot conflict with internal agent names like `oracle` or `explorer`

+ 29 - 16
docs/installation.md

@@ -24,7 +24,7 @@ bunx oh-my-opencode-slim@latest install
 Or use non-interactive mode:
 
 ```bash
-bunx oh-my-opencode-slim@latest install --no-tui --tmux=no --skills=yes
+bunx oh-my-opencode-slim@latest install --no-tui --skills=yes
 ```
 
 ### Configuration Options
@@ -33,8 +33,7 @@ The installer supports the following options:
 
 | Option | Description |
 |--------|-------------|
-| `--tmux=yes|no` | Enable tmux integration (yes/no) |
-| `--skills=yes|no` | Install recommended skills (yes/no) |
+| `--skills=yes|no` | Install recommended and bundled skills (default: yes) |
 | `--no-tui` | Non-interactive mode |
 | `--dry-run` | Simulate install without writing files |
 | `--reset` | Force overwrite of existing configuration |
@@ -44,7 +43,7 @@ The installer supports the following options:
 By default, the installer is non-destructive. If an `oh-my-opencode-slim.json` configuration file already exists, the installer will **not** overwrite it. Instead, it will display a message:
 
 ```
- Configuration already exists at ~/.config/opencode/oh-my-opencode-slim.json. Use --reset to overwrite.
+[i] Configuration already exists at ~/.config/opencode/oh-my-opencode-slim.json. Use --reset to overwrite.
 ```
 
 To force overwrite of your existing configuration, use the `--reset` flag:
@@ -57,16 +56,27 @@ bunx oh-my-opencode-slim@latest install --reset
 
 ### After Installation
 
-The installer generates an OpenAI configuration by default (using `gpt-5.4` and `gpt-5-codex` models). To use alternative providers like Kimi, GitHub Copilot, or ZAI Coding Plan, see **[Provider Configurations](provider-configurations.md)** for step-by-step instructions.
+The installer generates an OpenAI configuration by default (using `gpt-5.4` and `gpt-5.4-mini` models). To use alternative providers like Kimi, GitHub Copilot, or ZAI Coding Plan, see **[Provider Configurations](provider-configurations.md)** for step-by-step instructions.
 
-Authenticate with your provider:
+Then:
 
 ```bash
 opencode auth login
-# Select your provider → Complete OAuth flow
+# Select your provider and complete OAuth flow
+```
+
+```bash
+opencode models --refresh
 ```
 
-Once authenticated, run OpenCode and `ping all agents` to verify all agents respond.
+Open your generated config at `~/.config/opencode/oh-my-opencode-slim.json`
+and adjust models if needed.
+
+Then run OpenCode and verify the agents:
+
+```text
+ping all agents
+```
 
 > **💡 Tip: Models are fully customizable.** The installer sets sensible defaults, but you can assign *any* model to *any* agent. Edit `~/.config/opencode/oh-my-opencode-slim.json` (or `.jsonc` for comments support) to override models, adjust reasoning effort, or disable agents entirely.
 
@@ -98,7 +108,7 @@ If not installed, direct the user to https://opencode.ai/docs first.
 The installer generates an OpenAI configuration by default:
 
 ```bash
-bunx oh-my-opencode-slim@latest install --no-tui --tmux=no --skills=yes
+bunx oh-my-opencode-slim@latest install --no-tui --skills=yes
 ```
 
 **Examples:**
@@ -106,11 +116,11 @@ bunx oh-my-opencode-slim@latest install --no-tui --tmux=no --skills=yes
 # Interactive install (asks about tmux and skills)
 bunx oh-my-opencode-slim@latest install
 
-# Non-interactive with tmux and skills
-bunx oh-my-opencode-slim@latest install --no-tui --tmux=yes --skills=yes
+# Non-interactive with default skills
+bunx oh-my-opencode-slim@latest install --no-tui --skills=yes
 
-# Non-interactive without tmux or skills
-bunx oh-my-opencode-slim@latest install --no-tui --tmux=no --skills=no
+# Non-interactive without skills
+bunx oh-my-opencode-slim@latest install --no-tui --skills=no
 
 # Force overwrite existing configuration
 bunx oh-my-opencode-slim@latest install --reset
@@ -134,8 +144,10 @@ opencode auth login
 
 Ask the user to:
 
-1. Start OpenCode: `opencode`
-2. Run: `ping all agents`
+1. Authenticate: `opencode auth login`
+2. Refresh models: `opencode models --refresh`
+3. Start OpenCode: `opencode`
+4. Run: `ping all agents`
 
 Verify all agents respond successfully.
 
@@ -244,6 +256,7 @@ See the [Multiplexer Integration Guide](multiplexer-integration.md) for more det
 
 3. **Remove skills (optional)**:
    ```bash
-   npx skills remove simplify
    npx skills remove agent-browser
+   rm -rf ~/.config/opencode/skills/simplify
+   rm -rf ~/.config/opencode/skills/cartography
    ```

+ 2 - 2
docs/mcps.md

@@ -18,7 +18,7 @@ Built-in Model Context Protocol (MCP) servers ship with oh-my-opencode-slim and
 
 | Agent | Default MCPs |
 |-------|-------------|
-| `orchestrator` | `*` |
+| `orchestrator` | `*`, `!context7` |
 | `librarian` | `websearch`, `context7`, `grep_app` |
 | `designer` | none |
 | `oracle` | none |
@@ -53,7 +53,7 @@ Control which MCPs each agent can use via the `mcps` array in your preset config
   "presets": {
     "my-preset": {
       "orchestrator": {
-        "mcps": ["*"]
+        "mcps": ["*", "!context7"]
       },
       "librarian": {
         "mcps": ["websearch", "context7", "grep_app"]

+ 81 - 454
docs/multiplexer-integration.md

@@ -1,15 +1,13 @@
 # Multiplexer Integration Guide
 
-Complete guide for using terminal multiplexer integration (Tmux or Zellij) with oh-my-opencode-slim to watch agents work in real-time through automatic pane spawning.
+Use tmux or Zellij to watch subagents work in live panes while OpenCode keeps running in your main session.
 
 ## Table of Contents
 
 - [Overview](#overview)
-- [Supported Multiplexers](#supported-multiplexers)
-- [Quick Setup](#quick-setup)
+- [Quick Start](#quick-start)
 - [Configuration](#configuration)
-- [Layout Options](#layout-options)
-- [Usage Examples](#usage-examples)
+- [Layouts](#layouts)
 - [Troubleshooting](#troubleshooting)
 - [Advanced Usage](#advanced-usage)
 
@@ -17,87 +15,99 @@ Complete guide for using terminal multiplexer integration (Tmux or Zellij) with
 
 ## Overview
 
-**Watch your agents work in real-time.** When the Orchestrator launches sub-agents or initiates background tasks, new panes automatically spawn in a dedicated tab showing each agent's live progress. No more waiting in the dark.
+When the Orchestrator launches subagents or background tasks, oh-my-opencode-slim can open panes for those sessions automatically.
 
-### Key Benefits
+- **Real-time visibility** into agent activity
+- **Automatic pane management** while tasks run
+- **Easy debugging** by jumping into live sessions
+- **Support for multiple projects** on different sessions or ports
 
-- **Real-time visibility** into agent activities
-- **Automatic pane management** - panes spawn and organize automatically
-- **Interactive debugging** - you can jump into any agent's session
-- **Background task monitoring** - see long-running work as it happens
-- **Multi-session support** - different projects can have separate environments
+![Tmux multiplexer view](../img/tmux.png)
 
-> ⚠️ **Temporary workaround:** Start OpenCode with `--port` to enable multiplexer integration. The port must match the `OPENCODE_PORT` environment variable (default: 4096). This is required until the upstream issue is resolved. [opencode#9099](https://github.com/anomalyco/opencode/issues/9099).
+*OpenCode running in tmux with live subagent panes.*
 
----
+> ⚠️ **Current workaround:** Start OpenCode with `--port` to enable multiplexer integration. The port must match the `OPENCODE_PORT` environment variable. This is required until [opencode#9099](https://github.com/anomalyco/opencode/issues/9099) is resolved.
 
-## Supported Multiplexers
+If you open multiple OpenCode sessions, use a random high port for each launch instead of hard-coding `4096`.
 
-| Multiplexer | Status | Notes |
-|-------------|--------|-------|
-| **Tmux** | ✅ Supported | Full layout control with `main-vertical`, `main-horizontal`, `tiled`, etc. |
-| **Zellij** | ✅ Supported | Creates dedicated "opencode-agents" tab, reuses default pane |
+**Bash helper:**
+
+```bash
+omos() {
+  local port
+  port=$(jot -r 1 49152 65535)
+  OPENCODE_PORT="$port" \
+  opencode --port "$port" "$@"
+}
+```
 
 ---
 
-## Quick Setup
+## Quick Start
 
-### Step 1: Enable Multiplexer Integration
+### 1. Enable the multiplexer
 
 Edit `~/.config/opencode/oh-my-opencode-slim.json` (or `.jsonc`):
 
-**For Tmux:**
-```json
+**Auto-detect (recommended):**
+
+```jsonc
 {
   "multiplexer": {
-    "type": "tmux",
+    "type": "auto",
     "layout": "main-vertical",
     "main_pane_size": 60
   }
 }
 ```
 
-**For Zellij:**
-```json
+**Tmux only:**
+
+```jsonc
 {
   "multiplexer": {
-    "type": "zellij"
+    "type": "tmux",
+    "layout": "main-vertical",
+    "main_pane_size": 60
   }
 }
 ```
 
-**Auto-detect (recommended):**
-```json
+**Zellij only:**
+
+```jsonc
 {
   "multiplexer": {
-    "type": "auto",
-    "layout": "main-vertical",
-    "main_pane_size": 60
+    "type": "zellij"
   }
 }
 ```
 
-### Step 2: Run OpenCode Inside Your Multiplexer
+### 2. Start OpenCode inside tmux or Zellij
 
 **Tmux:**
+
 ```bash
-# Start a new tmux session
 tmux
-
-# Start OpenCode with the default port (4096)
 opencode --port 4096
 ```
 
 **Zellij:**
+
 ```bash
-# Start a new zellij session
 zellij
-
-# Start OpenCode with the default port (4096)
 opencode --port 4096
 ```
 
-That's it! Your agents will now spawn panes automatically.
+### 3. Trigger delegated work
+
+Ask OpenCode to do something that launches subagents. New panes should appear automatically.
+
+Example:
+
+```text
+Please analyze this codebase and create a documentation structure.
+```
 
 ---
 
@@ -105,9 +115,7 @@ That's it! Your agents will now spawn panes automatically.
 
 ### Multiplexer Settings
 
-Configure multiplexer behavior in `~/.config/opencode/oh-my-opencode-slim.json` (or `.jsonc`):
-
-```json
+```jsonc
 {
   "multiplexer": {
     "type": "auto",
@@ -120,12 +128,21 @@ Configure multiplexer behavior in `~/.config/opencode/oh-my-opencode-slim.json`
 | Setting | Type | Default | Description |
 |---------|------|---------|-------------|
 | `type` | string | `"none"` | `"auto"`, `"tmux"`, `"zellij"`, or `"none"` |
-| `layout` | string | `"main-vertical"` | Layout preset (tmux only, see [Layout Options](#layout-options)) |
-| `main_pane_size` | number | `60` | Main pane size as percentage (tmux only, 20-80) |
+| `layout` | string | `"main-vertical"` | Layout preset for tmux only |
+| `main_pane_size` | number | `60` | Main pane size percentage for tmux only (`20`-`80`) |
 
-### Legacy Tmux Config (still supported)
+### Supported Multiplexers
 
-```json
+| Multiplexer | Status | Notes |
+|-------------|--------|-------|
+| **Tmux** | ✅ Supported | Full layout control with `main-vertical`, `main-horizontal`, `tiled`, and more |
+| **Zellij** | ✅ Supported | Creates a dedicated `opencode-agents` tab and reuses the default pane |
+
+### Legacy tmux config
+
+Older configs still work:
+
+```jsonc
 {
   "tmux": {
     "enabled": true,
@@ -135,432 +152,42 @@ Configure multiplexer behavior in `~/.config/opencode/oh-my-opencode-slim.json`
 }
 ```
 
-This is automatically converted to `multiplexer.type: "tmux"`.
+This is converted automatically to `multiplexer.type: "tmux"`.
 
-### Layout Options (Tmux only)
+---
 
-Choose how panes are arranged:
+## Layouts
 
-Choose how panes are arranged:
+These layouts apply to **tmux only**:
 
 | Layout | Description |
 |--------|-------------|
-| `main-vertical` | Your session on the left (60%), agents stacked on the right |
-| `main-horizontal` | Your session on top (60%), agents stacked below |
-| `tiled` | All panes in equal-sized grid |
+| `main-vertical` | Your session on the left, agents stacked on the right |
+| `main-horizontal` | Your session on top, agents stacked below |
+| `tiled` | All panes in an equal-sized grid |
 | `even-horizontal` | All panes side by side |
 | `even-vertical` | All panes stacked vertically |
 
-**Example: Horizontal layout for wide screens:**
-```json
+**Example: wide-screen layout**
+
+```jsonc
 {
-  "tmux": {
-    "enabled": true,
+  "multiplexer": {
+    "type": "tmux",
     "layout": "main-horizontal",
     "main_pane_size": 50
   }
 }
 ```
 
-**Example: Tiled layout for maximum parallelism:**
-```json
+**Example: maximum parallel visibility**
+
+```jsonc
 {
-  "tmux": {
-    "enabled": true,
+  "multiplexer": {
+    "type": "tmux",
     "layout": "tiled",
     "main_pane_size": 50
   }
 }
 ```
-
----
-
-## Usage Examples
-
-### Basic Usage
-
-1. **Start tmux and OpenCode:**
-   ```bash
-   tmux
-   opencode --port 4096
-   ```
-
-2. **Ask the Orchestrator to delegate work:**
-   ```
-   Please analyze this codebase and create a documentation structure.
-   ```
-
-3. **Watch panes spawn automatically:**
-   - The main OpenCode session stays visible
-   - New panes appear showing agent activities
-   - You can switch between panes to monitor progress
-
-### Navigating Panes
-
-```bash
-# List all panes (in another terminal)
-tmux list-panes -a
-
-# Switch to specific pane (in tmux)
-Ctrl+B Arrow Keys    # Navigate between panes
-Ctrl+B %             # Split pane horizontally
-Ctrl+B "             # Split pane vertically
-Ctrl+B z             # Zoom/unzoom pane
-Ctrl+B c             # Create new window
-Ctrl+B n/p           # Next/previous window
-```
-
-### Detaching and Reattaching
-
-```bash
-# Detach from tmux session (keep it running)
-Ctrl+B d
-
-# Reattach to your session later
-tmux attach
-
-# Or specify which session
-tmux attach -t 0
-```
-
-### Monitoring Multiple Projects
-
-For different projects on different ports:
-
-```bash
-# Project 1
-tmux new -s project1
-opencode --port 4096
-
-# Detach and create Project 2
-Ctrl+B d
-tmux new -s project2
-opencode --port 4097
-
-# Switch between projects
-tmux switch -t project1
-tmux switch -t project2
-```
-
-### Navigating Sessions
-
-```bash
-# List all sessions (in another terminal)
-tmux list-sessions
-
-# Switch to specific session (in tmux)
-Ctrl+B s      # list sessions and select
-Ctrl+B (      # switch to previous session
-Ctrl+B )      # switch to next session
-Ctrl+B $      # rename current session
-Ctrl+B d      # detach from current session
-```
-
----
-
-## Troubleshooting
-
-### Tmux Integration Not Working
-
-**Problem:** No panes are spawning
-
-**Solutions:**
-1. **Verify tmux integration is enabled:**
-   ```bash
-    cat ~/.config/opencode/oh-my-opencode-slim.json | grep tmux # (or .jsonc)
-   ```
-
-2. **Check port configuration:**
-   ```bash
-   # Ensure port matches
-   echo $OPENCODE_PORT  # Should be 4096 by default
-   opencode --port 4096 # Should match
-   ```
-
-3. **Verify you're running inside tmux:**
-   ```bash
-   echo $TMUX  # Should show something, not empty
-   ```
-
-4. **Check OpenCode logs:**
-   ```bash
-   tail -f ~/.config/opencode/logs/opencode.log
-   ```
-
-### Ghost Panes and Orphaned Processes
-
-**Problem:** Tmux panes remain open after tasks complete, or `opencode attach` processes accumulate
-
-**This issue is fixed in the latest version.** The session lifecycle now properly closes panes and terminates processes.
-
-**To verify the fix is working:**
-```bash
-# After running some background tasks, check for orphans
-ps aux | grep "opencode attach" | grep -v grep
-# Should return no results
-
-# Check active tmux panes
-tmux list-panes
-# Should only show your main session pane(s)
-```
-
-**If you still see orphaned processes:**
-1. **Kill all orphaned processes:**
-   ```bash
-   pkill -f "opencode attach"
-   ```
-
-2. **Close all ghost panes:**
-   ```bash
-   # In tmux, close panes manually
-   tmux kill-pane -t <pane-id>
-   ```
-
-3. **Restart OpenCode** with the updated plugin
-
-**Technical Details:**
-The fix implements proper session lifecycle management:
-- `session.abort()` is called after task completion
-- Graceful shutdown with Ctrl+C before killing panes
-- Event handlers for `session.deleted` events
-- Automatic cleanup of tmux panes and processes
-
-See [AGENTS.md](../AGENTS.md) for implementation details.
-
-### Port Conflicts
-
-**Problem:** "Port already in use" or agents not connecting
-
-**Solutions:**
-1. **Use a different port:**
-   ```bash
-   export OPENCODE_PORT=5000
-   opencode --port 5000
-   ```
-
-2. **Kill existing OpenCode processes:**
-   ```bash
-   pkill -f "opencode"
-   ```
-
-3. **Check for conflicting services:**
-   ```bash
-   netstat -tulpn | grep 4096
-   ```
-
-### Tmux Session Issues
-
-**Problem:** Can't create or attach to tmux sessions
-
-**Solutions:**
-1. **Install tmux:**
-   ```bash
-   # Ubuntu/Debian
-   sudo apt install tmux
-
-   # macOS
-   brew install tmux
-
-   # Or use the package manager that comes with your distribution
-   ```
-
-2. **Check tmux version:**
-   ```bash
-   tmux -V  # Should be 1.8 or higher
-   ```
-
-3. **Reset tmux configuration:**
-   ```bash
-   rm -f ~/.tmux.conf
-   tmux kill-server
-   tmux
-   ```
-
-### Layout Problems
-
-**Problem:** Panes don't arrange as expected
-
-**Solutions:**
-1. **Try different layouts:**
-   ```json
-   {
-     "tmux": {
-       "enabled": true,
-       "layout": "tiled",
-       "main_pane_size": 40
-     }
-   }
-   ```
-
-2. **Manual layout adjustment:**
-   ```bash
-   # In tmux, resize panes
-   Ctrl+B Alt+Arrow Keys
-   ```
-
-3. **Clear all panes and restart:**
-   ```bash
-   tmux kill-pane -a
-   # Restart OpenCode
-   ```
-
-### Performance Issues
-
-**Problem:** Too many panes or slow performance
-
-**Solutions:**
-1. **Reduce pane size for main window:**
-   ```json
-   {
-     "tmux": {
-       "enabled": true,
-       "layout": "main-vertical",
-       "main_pane_size": 40
-     }
-   }
-   ```
-
-2. **Limit background tasks:**
-   ```bash
-   # In OpenCode, use fewer parallel operations
-   # Or configure agents to be more sequential
-   ```
-
-3. **Clean up old panes:**
-   ```bash
-   # Kill all panes except current
-   tmux kill-pane -a
-   ```
-
----
-
-## Advanced Usage
-
-### Custom Tmux Configuration
-
-Create `~/.tmux.conf` for custom behavior:
-
-```bash
-# Enable mouse support
-set -g mouse on
-
-# Custom key bindings
-bind-key r source-file ~/.tmux.conf
-
-# Better colors
-set -g default-terminal "screen-256color"
-
-# Status bar customization
-set -g status-right "#H %Y-%m-%d %H:%M"
-
-# Pane navigation
-bind h select-pane -L
-bind j select-pane -D
-bind k select-pane -U
-bind l select-pane -R
-```
-
-### Monitoring Multiple Agents
-
-Watch specific agents more closely:
-
-```bash
-# Monitor all agent panes
-watch -n 1 'tmux list-panes -a -F "#{pane_current_command} (#{pane_index})"'
-
-# Watch OpenCode output specifically
-tmux list-panes -a | grep opencode
-
-# Monitor background tasks
-tmux list-panes -a | grep background
-```
-
-### Integration with Background Tasks
-
-The plugin provides background task tools that work seamlessly with tmux:
-
-| Tool | Description | Tmux Integration |
-|------|-------------|------------------|
-| `background_task` | Launch agents asynchronously | Spawns panes for monitoring |
-| `background_output` | Check task results | Output appears in panes |
-| `background_cancel` | Stop running tasks | Cleans up panes |
-
-### Scripting and Automation
-
-```bash
-#!/bin/bash
-# Auto-start script for OpenCode with tmux
-
-# Create dedicated session
-tmux new -d -s opencode
-
-# Start OpenCode in the session
-tmux send-keys -t opencode:0 'opencode --port 4096' Enter
-
-# Wait and attach
-sleep 2
-tmux attach -t opencode
-```
-
-### Log Monitoring
-
-```bash
-# Monitor OpenCode logs in real-time
-tmux split-window -h
-tmux send-keys 'tail -f ~/.config/opencode/logs/opencode.log' Enter
-
-# Switch back to main pane
-tmux select-pane -L
-```
-
-### Custom Layouts
-
-Create custom pane arrangements:
-
-```bash
-# In tmux, create a 3-pane layout
-tmux split-window -h
-tmux split-window -v
-tmux select-pane -L
-
-# Save the layout
-tmux save-buffer ~/my-layout.txt
-
-# Restore later
-tmux load-buffer ~/my-layout.txt
-```
-
----
-
-## Best Practices
-
-### Session Management
-- Use named sessions for different projects
-- Detach when not actively monitoring
-- Clean up unused sessions periodically
-
-### Performance
-- Use `main-vertical` or `main-horizontal` layouts for better focus
-- Adjust `main_pane_size` based on your screen resolution
-- Limit parallel agent operations for complex tasks
-
-### Development Workflow
-1. Start tmux session for your project
-2. Launch OpenCode with tmux integration enabled
-3. Monitor agent activities as they work
-4. Detach when done, reattach to check progress
-5. Clean up panes after completing work
-
-### Debugging
-- Use `Ctrl+B z` to zoom into specific panes
-- Check logs when agents aren't responding
-- Verify port configuration when switching between projects
-
----
-
-## Additional Resources
-
-- **Official Tmux Documentation:** https://github.com/tmux/tmux/wiki
-- **Quick Reference:** [docs/quick-reference.md#tmux-integration](quick-reference.md#tmux-integration)
-- **Background Tasks:** [docs/quick-reference.md#background-tasks](quick-reference.md#background-tasks)
-- **OpenCode Documentation:** https://opencode.ai/docs

+ 15 - 15
docs/provider-configurations.md

@@ -15,10 +15,10 @@ The installer generates this configuration automatically:
   "preset": "openai",
   "presets": {
     "openai": {
-      "orchestrator": { "model": "openai/gpt-5.4", "variant": "high", "skills": ["*"], "mcps": ["*"] },
-      "oracle": { "model": "openai/gpt-5.4", "variant": "high", "skills": [], "mcps": [] },
+      "orchestrator": { "model": "openai/gpt-5.4", "variant": "high", "skills": ["*"], "mcps": ["*", "!context7"] },
+      "oracle": { "model": "openai/gpt-5.4", "variant": "high", "skills": ["simplify"], "mcps": [] },
       "librarian": { "model": "openai/gpt-5.4-mini", "variant": "low", "skills": [], "mcps": ["websearch", "context7", "grep_app"] },
-      "explorer": { "model": "openai/gpt-5.4-mini", "variant": "low", "skills": [], "mcps": [] },
+      "explorer": { "model": "openai/gpt-5.4-mini", "variant": "low", "skills": ["cartography"], "mcps": [] },
       "designer": { "model": "openai/gpt-5.4-mini", "variant": "medium", "skills": ["agent-browser"], "mcps": [] },
       "fixer": { "model": "openai/gpt-5.4-mini", "variant": "low", "skills": [], "mcps": [] }
     }
@@ -35,10 +35,10 @@ To use Kimi, add a `kimi` preset and set it as active:
   "preset": "kimi",
   "presets": {
     "kimi": {
-      "orchestrator": { "model": "kimi-for-coding/k2p5", "variant": "high", "skills": ["*"], "mcps": ["*"] },
-      "oracle": { "model": "kimi-for-coding/k2p5", "variant": "high", "skills": [], "mcps": [] },
+      "orchestrator": { "model": "kimi-for-coding/k2p5", "variant": "high", "skills": ["*"], "mcps": ["*", "!context7"] },
+      "oracle": { "model": "kimi-for-coding/k2p5", "variant": "high", "skills": ["simplify"], "mcps": [] },
       "librarian": { "model": "kimi-for-coding/k2p5", "variant": "low", "skills": [], "mcps": ["websearch", "context7", "grep_app"] },
-      "explorer": { "model": "kimi-for-coding/k2p5", "variant": "low", "skills": [], "mcps": [] },
+      "explorer": { "model": "kimi-for-coding/k2p5", "variant": "low", "skills": ["cartography"], "mcps": [] },
       "designer": { "model": "kimi-for-coding/k2p5", "variant": "medium", "skills": ["agent-browser"], "mcps": [] },
       "fixer": { "model": "kimi-for-coding/k2p5", "variant": "low", "skills": [], "mcps": [] }
     }
@@ -61,10 +61,10 @@ To use GitHub Copilot with Grok Code Fast:
   "preset": "copilot",
   "presets": {
     "copilot": {
-      "orchestrator": { "model": "github-copilot/claude-opus-4.6", "variant": "high", "skills": ["*"], "mcps": ["*"] },
-      "oracle": { "model": "github-copilot/claude-opus-4.6", "variant": "high", "skills": [], "mcps": [] },
+      "orchestrator": { "model": "github-copilot/claude-opus-4.6", "variant": "high", "skills": ["*"], "mcps": ["*", "!context7"] },
+      "oracle": { "model": "github-copilot/claude-opus-4.6", "variant": "high", "skills": ["simplify"], "mcps": [] },
       "librarian": { "model": "github-copilot/grok-code-fast-1", "variant": "low", "skills": [], "mcps": ["websearch", "context7", "grep_app"] },
-      "explorer": { "model": "github-copilot/grok-code-fast-1", "variant": "low", "skills": [], "mcps": [] },
+      "explorer": { "model": "github-copilot/grok-code-fast-1", "variant": "low", "skills": ["cartography"], "mcps": [] },
       "designer": { "model": "github-copilot/gemini-3.1-pro-preview", "variant": "medium", "skills": ["agent-browser"], "mcps": [] },
       "fixer": { "model": "github-copilot/claude-sonnet-4.6", "variant": "low", "skills": [], "mcps": [] }
     }
@@ -87,10 +87,10 @@ To use ZAI Coding Plan with GLM 5:
   "preset": "zai-plan",
   "presets": {
     "zai-plan": {
-      "orchestrator": { "model": "zai-coding-plan/glm-5", "variant": "high", "skills": ["*"], "mcps": ["*"] },
-      "oracle": { "model": "zai-coding-plan/glm-5", "variant": "high", "skills": [], "mcps": [] },
+      "orchestrator": { "model": "zai-coding-plan/glm-5", "variant": "high", "skills": ["*"], "mcps": ["*", "!context7"] },
+      "oracle": { "model": "zai-coding-plan/glm-5", "variant": "high", "skills": ["simplify"], "mcps": [] },
       "librarian": { "model": "zai-coding-plan/glm-5", "variant": "low", "skills": [], "mcps": ["websearch", "context7", "grep_app"] },
-      "explorer": { "model": "zai-coding-plan/glm-5", "variant": "low", "skills": [], "mcps": [] },
+      "explorer": { "model": "zai-coding-plan/glm-5", "variant": "low", "skills": ["cartography"], "mcps": [] },
       "designer": { "model": "zai-coding-plan/glm-5", "variant": "medium", "skills": ["agent-browser"], "mcps": [] },
       "fixer": { "model": "zai-coding-plan/glm-5", "variant": "low", "skills": [], "mcps": [] }
     }
@@ -113,10 +113,10 @@ You can mix models from different providers across agents. Create a custom prese
   "preset": "my-mix",
   "presets": {
     "my-mix": {
-      "orchestrator": { "model": "openai/gpt-5.4", "skills": ["*"], "mcps": ["*"] },
-      "oracle": { "model": "openai/gpt-5.4", "variant": "high", "skills": [], "mcps": [] },
+      "orchestrator": { "model": "openai/gpt-5.4", "skills": ["*"], "mcps": ["*", "!context7"] },
+      "oracle": { "model": "openai/gpt-5.4", "variant": "high", "skills": ["simplify"], "mcps": [] },
       "librarian": { "model": "kimi-for-coding/k2p5", "variant": "low", "skills": [], "mcps": ["websearch", "context7", "grep_app"] },
-      "explorer": { "model": "github-copilot/grok-code-fast-1", "variant": "low", "skills": [], "mcps": [] },
+      "explorer": { "model": "github-copilot/grok-code-fast-1", "variant": "low", "skills": ["cartography"], "mcps": [] },
       "designer": { "model": "kimi-for-coding/k2p5", "variant": "medium", "skills": ["agent-browser"], "mcps": [] },
       "fixer": { "model": "openai/gpt-5.4-mini", "variant": "low", "skills": [], "mcps": [] }
     }

+ 1 - 0
docs/quick-reference.md

@@ -16,6 +16,7 @@
 | [Council Agent](council.md) | Multi-LLM consensus, presets, role prompts, timeouts |
 | [Interview](interview.md) | `/interview` command, browser UI, dashboard mode, multi-session coordination |
 | [Multiplexer Integration](multiplexer-integration.md) | Real-time pane monitoring, layouts, troubleshooting |
+| [Todo Continuation](todo-continuation.md) | `auto_continue`, `/auto-continue`, cooldowns, safety gates |
 | [Cartography Skill](cartography.md) | Hierarchical codemap generation |
 
 ## ⚙️ Config & Reference

+ 6 - 4
docs/skills.md

@@ -12,25 +12,27 @@ Skills are installed via the `oh-my-opencode-slim` installer or manually with `n
 
 | Skill | Description | Assigned to by default |
 |-------|-------------|----------------------|
-| [`simplify`](#simplify) | YAGNI code simplification expert | `oracle` |
 | [`agent-browser`](#agent-browser) | High-performance browser automation | `designer` |
 
 ### Bundled in repo
 
 | Skill | Description | Assigned to by default |
 |-------|-------------|----------------------|
-| [`cartography`](#cartography) | Repository codemap generation | `orchestrator` |
+| [`simplify`](#simplify) | Behavior-preserving code simplification | `oracle` |
+| [`cartography`](#cartography) | Repository codemap generation | `orchestrator`, `explorer` |
 
 ---
 
 ## simplify
 
-**The Minimalist's sacred truth: every line of code is a liability.**
+**Behavior-preserving simplification for readability and maintainability.**
 
-`simplify` is a specialized skill for complexity analysis and YAGNI enforcement. It identifies unnecessary abstractions, over-engineered patterns, and redundant code — and suggests minimal, direct implementations instead.
+`simplify` is a bundled skill for clarity-focused refactoring without behavior changes. It helps `oracle` reduce unnecessary complexity, improve naming and structure, and keep simplification work scoped and reviewable.
 
 By default, this skill is assigned to `oracle`, which owns code review, maintainability review, and simplification guidance. The `orchestrator` should route simplification requests to `oracle` instead of handling them as a top-level specialty itself.
 
+Source: adapted from Addy Osmani's `code-simplification` skill and bundled locally as `simplify`.
+
 ---
 
 ## agent-browser

+ 45 - 0
docs/todo-continuation.md

@@ -0,0 +1,45 @@
+# Todo Continuation
+
+Auto-continue the orchestrator when it stops with incomplete todos. Opt-in only — nothing resumes automatically unless you enable it.
+
+## Controls
+
+| Tool / Command | Description |
+|----------------|-------------|
+| `auto_continue` | Toggle auto-continuation. Call with `{ enabled: true }` to activate, `{ enabled: false }` to disable |
+| `/auto-continue` | Slash command shortcut. Accepts `on`, `off`, or toggles with no argument |
+
+## How It Works
+
+1. When the orchestrator goes idle with incomplete todos, a countdown notification appears
+2. After the cooldown (default 3s), a continuation prompt is injected and the orchestrator resumes work
+3. Press Esc×2 during the cooldown or after injection to stop it
+
+## Safety Gates
+
+All of these must pass before continuation happens:
+
+- Auto-continue is enabled
+- The session is the orchestrator
+- Incomplete todos exist
+- The last assistant message is not a question
+- The consecutive continuation count is under the limit
+- The session is not in the post-abort suppress window (5s)
+- No pending injection is already in flight
+
+## Configuration
+
+Configure it in `~/.config/opencode/oh-my-opencode-slim.json` or `~/.config/opencode/oh-my-opencode-slim.jsonc`:
+
+```jsonc
+{
+  "todoContinuation": {
+    "maxContinuations": 5,      // Max consecutive auto-continuations (1–50)
+    "cooldownMs": 3000,         // Delay before each continuation (0–30000)
+    "autoEnable": false,        // Auto-enable when session has enough todos
+    "autoEnableThreshold": 4    // Number of todos to trigger auto-enable
+  }
+}
+```
+
+> See [Configuration](configuration.md) for the full option reference.

+ 3 - 37
docs/tools.md

@@ -75,40 +75,6 @@ Includes Prettier, Biome, `gofmt`, `rustfmt`, `ruff`, and 20+ others.
 
 ## Todo Continuation
 
-Auto-continue the orchestrator when it stops with incomplete todos. Opt-in — no automatic behavior unless enabled.
-
-| Tool / Command | Description |
-|----------------|-------------|
-| `auto_continue` | Toggle auto-continuation. Call with `{ enabled: true }` to activate, `{ enabled: false }` to disable |
-| `/auto-continue` | Slash command shortcut. Accepts `on`, `off`, or toggles with no argument |
-
-**How it works:**
-
-1. When the orchestrator goes idle with incomplete todos, a countdown notification appears
-2. After the cooldown (default 3s), a continuation prompt is injected — the orchestrator resumes work
-3. Press Esc×2 during cooldown or after injection to stop
-
-**Safety gates** (all must pass before continuation):
-
-- Auto-continue is enabled
-- Session is the orchestrator
-- Incomplete todos exist
-- Last assistant message is not a question
-- Consecutive continuation count is under the limit
-- Not in post-abort suppress window (5s)
-- No pending injection already in flight
-
-**Configuration** in `oh-my-opencode-slim.json`:
-
-```jsonc
-{
-  "todoContinuation": {
-    "maxContinuations": 5,      // Max consecutive auto-continuations (1–50)
-    "cooldownMs": 3000,         // Delay before each continuation (0–30000)
-    "autoEnable": false,        // Auto-enable when session has enough todos
-    "autoEnableThreshold": 4    // Number of todos to trigger auto-enable
-  }
-}
-```
-
-> See [Configuration](configuration.md) for the full option reference.
+Auto-continue has its own guide now:
+
+- [Todo Continuation](todo-continuation.md) — controls, safety gates, behavior, and config

BIN
img/team.jpeg


+ 12 - 0
oh-my-opencode-slim.schema.json

@@ -19,6 +19,10 @@
     "balanceProviderUsage": {
       "type": "boolean"
     },
+    "showStartupToast": {
+      "description": "Show the startup activation toast when OpenCode starts. Defaults to true.",
+      "type": "boolean"
+    },
     "manualPlan": {
       "type": "object",
       "properties": {
@@ -265,6 +269,10 @@
                 "type": "string"
               },
               "additionalProperties": {}
+            },
+            "displayName": {
+              "type": "string",
+              "minLength": 1
             }
           }
         }
@@ -335,6 +343,10 @@
               "type": "string"
             },
             "additionalProperties": {}
+          },
+          "displayName": {
+            "type": "string",
+            "minLength": 1
           }
         }
       }

+ 1 - 0
scripts/verify-release-artifact.ts

@@ -29,6 +29,7 @@ const packagedRequiredFiles = [
   'dist/index.d.ts',
   'dist/cli/index.js',
   'oh-my-opencode-slim.schema.json',
+  'src/skills/simplify/SKILL.md',
   'src/skills/cartography/SKILL.md',
 ];
 

+ 203 - 0
src/agents/display-name.test.ts

@@ -0,0 +1,203 @@
+import { describe, expect, test } from 'bun:test';
+import type { PluginConfig } from '../config';
+import { createAgents, getAgentConfigs } from './index';
+
+describe('displayName', () => {
+  test('stores displayName on agent when configured', () => {
+    const config: PluginConfig = {
+      agents: {
+        explorer: { displayName: 'researcher' },
+      },
+    };
+
+    const agents = createAgents(config);
+    const explorer = agents.find((a) => a.name === 'explorer');
+    expect(explorer?.displayName).toBe('researcher');
+
+    const sdkConfigs = getAgentConfigs(config);
+    expect((sdkConfigs.explorer as { displayName?: string }).displayName).toBe(
+      'researcher',
+    );
+  });
+
+  test('injects configured displayName into orchestrator prompt mentions', () => {
+    const config: PluginConfig = {
+      agents: {
+        explorer: { displayName: 'researcher' },
+      },
+    };
+
+    const agents = createAgents(config);
+    const orchestrator = agents.find((a) => a.name === 'orchestrator');
+    const prompt = orchestrator?.config.prompt ?? '';
+
+    expect(prompt).toContain('@researcher');
+    expect(prompt).not.toMatch(/@explorer\b/);
+  });
+
+  test('normalizes @-prefixed displayName in prompt injection', () => {
+    const config: PluginConfig = {
+      agents: {
+        explorer: { displayName: '@researcher' },
+      },
+    };
+
+    const agents = createAgents(config);
+    const orchestrator = agents.find((a) => a.name === 'orchestrator');
+    const prompt = orchestrator?.config.prompt ?? '';
+
+    expect(prompt).toContain('@researcher');
+    expect(prompt).not.toContain('@@researcher');
+    expect(prompt).not.toMatch(/@explorer\b/);
+  });
+
+  test('normalizes whitespace-padded displayName in prompt injection', () => {
+    const config: PluginConfig = {
+      agents: {
+        explorer: { displayName: '  researcher  ' },
+      },
+    };
+
+    const agents = createAgents(config);
+    const orchestrator = agents.find((a) => a.name === 'orchestrator');
+    const prompt = orchestrator?.config.prompt ?? '';
+
+    expect(prompt).toContain('@researcher');
+    expect(prompt).not.toContain('@ researcher ');
+    expect(prompt).not.toMatch(/@explorer\b/);
+  });
+
+  test('throws when duplicate displayName is assigned', () => {
+    const config: PluginConfig = {
+      agents: {
+        explorer: { displayName: 'helper' },
+        librarian: { displayName: 'helper' },
+      },
+    };
+
+    expect(() => createAgents(config)).toThrow(
+      "Duplicate displayName 'helper' assigned to multiple agents",
+    );
+  });
+
+  test('throws when normalized duplicate displayName is assigned', () => {
+    const config: PluginConfig = {
+      agents: {
+        explorer: { displayName: 'advisor' },
+        librarian: { displayName: ' @advisor ' },
+      },
+    };
+
+    expect(() => createAgents(config)).toThrow(
+      "Duplicate displayName 'advisor' assigned to multiple agents",
+    );
+  });
+
+  test('throws when displayName conflicts with internal agent name', () => {
+    const config: PluginConfig = {
+      agents: {
+        explorer: { displayName: 'oracle' },
+      },
+    };
+
+    expect(() => createAgents(config)).toThrow(
+      "displayName 'oracle' conflicts with internal agent name",
+    );
+  });
+
+  test('throws when normalized displayName conflicts with internal agent name', () => {
+    const config: PluginConfig = {
+      agents: {
+        explorer: { displayName: ' @oracle ' },
+      },
+    };
+
+    expect(() => createAgents(config)).toThrow(
+      "displayName 'oracle' conflicts with internal agent name",
+    );
+  });
+
+  test('throws when orchestrator displayName conflicts with internal agent name', () => {
+    const config: PluginConfig = {
+      agents: {
+        orchestrator: { displayName: 'oracle' },
+      },
+    };
+
+    expect(() => createAgents(config)).toThrow(
+      /displayName.*conflicts with internal agent name/,
+    );
+  });
+
+  test('resolves legacy alias for explorer displayName override', () => {
+    const config: PluginConfig = {
+      agents: {
+        explore: { displayName: 'researcher' },
+      },
+    };
+
+    const agents = createAgents(config);
+    const explorer = agents.find((a) => a.name === 'explorer');
+
+    expect(explorer?.displayName).toBe('researcher');
+  });
+
+  test('uses displayName as host-facing registry key with hidden internal alias', () => {
+    const config: PluginConfig = {
+      agents: {
+        oracle: { displayName: 'advisor' },
+      },
+    };
+
+    const sdkConfigs = getAgentConfigs(config) as Record<
+      string,
+      { hidden?: boolean; mode?: string }
+    >;
+
+    expect(sdkConfigs.advisor).toBeDefined();
+    expect(sdkConfigs.advisor.mode).toBe('subagent');
+    expect(sdkConfigs.advisor.hidden).toBeUndefined();
+
+    expect(sdkConfigs.oracle).toBeDefined();
+    expect(sdkConfigs.oracle.mode).toBe('subagent');
+    expect(sdkConfigs.oracle.hidden).toBe(true);
+  });
+
+  test('uses orchestrator displayName as host-facing key with hidden internal alias', () => {
+    const config: PluginConfig = {
+      agents: {
+        orchestrator: { displayName: 'engineer' },
+      },
+    };
+
+    const sdkConfigs = getAgentConfigs(config) as Record<
+      string,
+      { hidden?: boolean; mode?: string }
+    >;
+
+    expect(sdkConfigs.engineer).toBeDefined();
+    expect(sdkConfigs.engineer.mode).toBe('primary');
+    expect(sdkConfigs.engineer.hidden).toBeUndefined();
+
+    expect(sdkConfigs.orchestrator).toBeDefined();
+    expect(sdkConfigs.orchestrator.mode).toBe('primary');
+    expect(sdkConfigs.orchestrator.hidden).toBe(true);
+  });
+
+  test('keeps internal-only council agents hidden even with displayName configured', () => {
+    const config: PluginConfig = {
+      disabled_agents: [],
+      agents: {
+        councillor: { displayName: 'reviewer' },
+        'council-master': { displayName: 'arbiter' },
+      },
+    };
+
+    const sdkConfigs = getAgentConfigs(config);
+
+    expect(sdkConfigs.reviewer).toBeUndefined();
+    expect(sdkConfigs.arbiter).toBeUndefined();
+    expect(sdkConfigs.councillor?.hidden).toBe(true);
+    expect(sdkConfigs['council-master']?.hidden).toBe(true);
+  });
+});

+ 9 - 0
src/agents/index.test.ts

@@ -249,6 +249,15 @@ describe('skill permissions', () => {
       ?.skill as Record<string, string>;
     expect(skillPerm?.['requesting-code-review']).toBe('allow');
   });
+
+  test('oracle gets simplify skill allowed by default', () => {
+    const agents = createAgents();
+    const oracle = agents.find((a) => a.name === 'oracle');
+    expect(oracle).toBeDefined();
+    const skillPerm = (oracle?.config.permission as Record<string, unknown>)
+      ?.skill as Record<string, string>;
+    expect(skillPerm?.simplify).toBe('allow');
+  });
 });
 
 describe('isSubagent type guard', () => {

+ 118 - 25
src/agents/index.ts

@@ -32,6 +32,11 @@ type AgentFactory = (
   customAppendPrompt?: string,
 ) => AgentDefinition;
 
+function normalizeDisplayName(displayName: string): string {
+  const trimmed = displayName.trim();
+  return trimmed.startsWith('@') ? trimmed.slice(1) : trimmed;
+}
+
 // Agent Configuration Helpers
 
 /**
@@ -63,6 +68,27 @@ function applyOverrides(
       ...override.options,
     };
   }
+  if (override.displayName) {
+    agent.displayName = override.displayName;
+  }
+}
+
+function injectDisplayNames(
+  orchestrator: AgentDefinition,
+  nameMap: Map<string, string>,
+): void {
+  if (nameMap.size === 0) return;
+  let prompt = orchestrator.config.prompt;
+  if (!prompt) return;
+
+  for (const [internalName, displayName] of nameMap) {
+    prompt = prompt.replace(
+      new RegExp(`@${internalName}\\b`, 'g'),
+      `@${normalizeDisplayName(displayName)}`,
+    );
+  }
+
+  orchestrator.config.prompt = prompt;
 }
 
 /**
@@ -207,6 +233,39 @@ export function createAgents(config?: PluginConfig): AgentDefinition[] {
     applyOverrides(orchestrator, orchestratorOverride);
   }
 
+  // Collect all display names from orchestrator and all subagents
+  const displayNameMap = new Map<string, string>();
+  if (orchestrator.displayName) {
+    displayNameMap.set('orchestrator', orchestrator.displayName);
+  }
+  for (const agent of allSubAgents) {
+    if (agent.displayName) {
+      displayNameMap.set(agent.name, agent.displayName);
+    }
+  }
+
+  // Validate display names
+  const usedDisplayNames = new Set<string>();
+  for (const [, displayName] of displayNameMap) {
+    const normalizedDisplayName = normalizeDisplayName(displayName);
+    if (usedDisplayNames.has(normalizedDisplayName)) {
+      throw new Error(
+        `Duplicate displayName '${normalizedDisplayName}' assigned to multiple agents`,
+      );
+    }
+    usedDisplayNames.add(normalizedDisplayName);
+  }
+  for (const displayName of usedDisplayNames) {
+    if ((ALL_AGENT_NAMES as readonly string[]).includes(displayName)) {
+      throw new Error(
+        `displayName '${displayName}' conflicts with internal agent name`,
+      );
+    }
+  }
+
+  // Inject display names into orchestrator prompt (complete map)
+  injectDisplayNames(orchestrator, displayNameMap);
+
   return [orchestrator, ...allSubAgents];
 }
 
@@ -221,32 +280,66 @@ export function getAgentConfigs(
   config?: PluginConfig,
 ): Record<string, SDKAgentConfig> {
   const agents = createAgents(config);
-  return Object.fromEntries(
-    agents.map((a) => {
-      const sdkConfig: SDKAgentConfig & { mcps?: string[] } = {
-        ...a.config,
-        description: a.description,
-        mcps: getAgentMcpList(a.name, config),
-      };
-
-      // Apply classification-based visibility and mode
-      if (a.name === 'council') {
-        // Council is callable both as a primary agent (user-facing)
-        // and as a subagent (orchestrator can delegate to it)
-        sdkConfig.mode = 'all';
-      } else if (a.name === 'councillor' || a.name === 'council-master') {
-        // Internal agents — subagent mode, hidden from @ autocomplete
-        sdkConfig.mode = 'subagent';
-        sdkConfig.hidden = true;
-      } else if (isSubagent(a.name)) {
-        sdkConfig.mode = 'subagent';
-      } else if (a.name === 'orchestrator') {
-        sdkConfig.mode = 'primary';
-      }
 
-      return [a.name, sdkConfig];
-    }),
-  );
+  const applyClassification = (
+    name: string,
+    sdkConfig: SDKAgentConfig & {
+      mcps?: string[];
+      displayName?: string;
+      hidden?: boolean;
+    },
+  ): void => {
+    if (name === 'council') {
+      // Council is callable both as a primary agent (user-facing)
+      // and as a subagent (orchestrator can delegate to it)
+      sdkConfig.mode = 'all';
+    } else if (name === 'councillor' || name === 'council-master') {
+      // Internal agents — subagent mode, hidden from @ autocomplete
+      sdkConfig.mode = 'subagent';
+      sdkConfig.hidden = true;
+    } else if (isSubagent(name)) {
+      sdkConfig.mode = 'subagent';
+    } else if (name === 'orchestrator') {
+      sdkConfig.mode = 'primary';
+    }
+  };
+
+  const isInternalOnly = (name: string): boolean =>
+    name === 'councillor' || name === 'council-master';
+
+  const entries: Array<[string, SDKAgentConfig]> = [];
+
+  for (const a of agents) {
+    const sdkConfig: SDKAgentConfig & {
+      mcps?: string[];
+      displayName?: string;
+      hidden?: boolean;
+    } = {
+      ...a.config,
+      description: a.description,
+      mcps: getAgentMcpList(a.name, config),
+    };
+
+    if (a.displayName) {
+      sdkConfig.displayName = a.displayName;
+    }
+
+    applyClassification(a.name, sdkConfig);
+
+    const normalizedDisplayName = a.displayName
+      ? normalizeDisplayName(a.displayName)
+      : undefined;
+
+    if (normalizedDisplayName && !isInternalOnly(a.name)) {
+      entries.push([normalizedDisplayName, sdkConfig]);
+      entries.push([a.name, { ...sdkConfig, hidden: true }]);
+      continue;
+    }
+
+    entries.push([a.name, sdkConfig]);
+  }
+
+  return Object.fromEntries(entries);
 }
 
 /**

+ 23 - 22
src/agents/orchestrator.ts

@@ -1,7 +1,8 @@
-import type { AgentConfig } from '@opencode-ai/sdk/v2';
+import type { AgentConfig } from "@opencode-ai/sdk/v2";
 
 export interface AgentDefinition {
   name: string;
+  displayName?: string;
   description?: string;
   config: AgentConfig;
   /** Priority-ordered model entries for runtime fallback resolution. */
@@ -16,7 +17,7 @@ export interface AgentDefinition {
 export function resolvePrompt(
   base: string,
   customPrompt?: string,
-  customAppendPrompt?: string,
+  customAppendPrompt?: string
 ): string {
   if (customPrompt) return customPrompt;
   if (customAppendPrompt) return `${base}\n\n${customAppendPrompt}`;
@@ -60,9 +61,9 @@ const AGENT_DESCRIPTIONS: Record<string, string> = {
 - Role: Fast execution specialist for well-defined tasks, which empowers orchestrator with parallel, speedy executions
 - Stats: 2x faster code edits, 1/2 cost of orchestrator, 0.8x quality of orchestrator
 - Tools/Constraints: Execution-focused—no research, no architectural decisions
-- **Delegate when:** For implementation work, think and triage first. If the change is non-trivial or multi-file, hand bounded execution to @fixer • Writing or updating tests • Tasks that touch test files, fixtures, mocks, or test helpers
+- **Delegate when:** For implementation work, think and triage first. If the change is non-trivial or multi-file, hand bounded execution to @fixer • Writing or updating tests • Tasks that touch test files, fixtures, mocks, or test helpers. Parallelization benefits: Task involves multiple folders and multiple files modificaiton, scoping work per folder and spawning parallel @fixers for each folder.
 - **Don't delegate when:** Needs discovery/research/decisions • Single small change (<20 lines, one file) • Unclear requirements needing iteration • Explaining to fixer > doing • Tight integration with your current work • Sequential dependencies
-- **Rule of thumb:** Explaining > doing? → yourself. Test file modifications and bounded implementation work usually go to @fixer. Orchestrator paths selection is vastly improved by Fixer. eg it can reduce overall speed if Orchestrator splits what's usually a single task into multiple subtasks and parallelize it with fixer.`,
+- **Rule of thumb:** Explaining > doing? → yourself. Test file modifications and bounded implementation work usually go to @fixer. Bigger or lots of edits, splitting makes sense, parallelized by spawning @fixers per certain scope.`,
 
   council: `@council
 - Role: Multi-LLM consensus engine for high-confidence answers
@@ -85,19 +86,19 @@ const AGENT_DESCRIPTIONS: Record<string, string> = {
 
 // Validation routing lines that reference agents
 const VALIDATION_ROUTING = [
-  '- Route UI/UX validation and review to @designer',
-  '- Route code review, simplification, maintainability review, and YAGNI checks to @oracle',
-  '- Route test writing, test updates, and changes touching test files to @fixer',
-  '- Route visual/media analysis and interpretation to @observer',
-  '- If a request spans multiple lanes, delegate only the lanes that add clear value',
+  "- Route UI/UX validation and review to @designer",
+  "- Route code review, simplification, maintainability review, and YAGNI checks to @oracle",
+  "- Route test writing, test updates, and changes touching test files to @fixer",
+  "- Route visual/media analysis and interpretation to @observer",
+  "- If a request spans multiple lanes, delegate only the lanes that add clear value",
 ];
 
 // Parallel delegation examples
 const PARALLEL_DELEGATION_EXAMPLES = [
-  '- Multiple @explorer searches across different domains?',
-  '- @explorer + @librarian research in parallel?',
-  '- Multiple @fixer instances for faster, scoped implementation?',
-  '- @observer + @explorer in parallel (visual analysis + code search)?',
+  "- Multiple @explorer searches across different domains?",
+  "- @explorer + @librarian research in parallel?",
+  "- Multiple @fixer instances for faster, scoped implementation?",
+  "- @observer + @explorer in parallel (visual analysis + code search)?",
 ];
 
 /**
@@ -110,14 +111,14 @@ export function buildOrchestratorPrompt(disabledAgents?: Set<string>): string {
   const enabledAgents = Object.entries(AGENT_DESCRIPTIONS)
     .filter(([name]) => !disabledAgents?.has(name))
     .map(([, desc]) => desc)
-    .join('\n\n');
+    .join("\n\n");
 
   // Filter validation routing lines — remove lines mentioning any disabled agent
   const enabledValidationRouting = VALIDATION_ROUTING.filter((line) => {
     const mentions = [...line.matchAll(/@(\w+)/g)].map((m) => m[1]);
     if (mentions.length === 0) return true;
     return mentions.every((name) => !disabledAgents?.has(name));
-  }).join('\n');
+  }).join("\n");
 
   // Filter parallel delegation examples — remove lines mentioning any disabled agent
   const enabledParallelExamples = PARALLEL_DELEGATION_EXAMPLES.filter(
@@ -125,8 +126,8 @@ export function buildOrchestratorPrompt(disabledAgents?: Set<string>): string {
       const mentions = [...line.matchAll(/@(\w+)/g)].map((m) => m[1]);
       if (mentions.length === 0) return true;
       return mentions.every((name) => !disabledAgents?.has(name));
-    },
-  ).join('\n');
+    }
+  ).join("\n");
 
   return `<Role>
 You are an AI coding orchestrator that optimizes for quality, speed, cost, and reliability by delegating to specialists when it provides net efficiency gains.
@@ -231,15 +232,15 @@ export function createOrchestratorAgent(
   model?: string | Array<string | { id: string; variant?: string }>,
   customPrompt?: string,
   customAppendPrompt?: string,
-  disabledAgents?: Set<string>,
+  disabledAgents?: Set<string>
 ): AgentDefinition {
   const basePrompt = buildOrchestratorPrompt(disabledAgents);
   const prompt = resolvePrompt(basePrompt, customPrompt, customAppendPrompt);
 
   const definition: AgentDefinition = {
-    name: 'orchestrator',
+    name: "orchestrator",
     description:
-      'AI coding orchestrator that delegates tasks to specialist agents for optimal quality, speed, and cost',
+      "AI coding orchestrator that delegates tasks to specialist agents for optimal quality, speed, and cost",
     config: {
       temperature: 0.1,
       prompt,
@@ -248,9 +249,9 @@ export function createOrchestratorAgent(
 
   if (Array.isArray(model)) {
     definition._modelArray = model.map((m) =>
-      typeof m === 'string' ? { id: m } : m,
+      typeof m === "string" ? { id: m } : m
     );
-  } else if (typeof model === 'string' && model) {
+  } else if (typeof model === "string" && model) {
     definition.config.model = model;
   }
 

+ 125 - 1
src/background/background-manager.test.ts

@@ -1,7 +1,7 @@
+import { afterEach, beforeEach, describe, expect, mock, test } from 'bun:test';
 import * as fs from 'node:fs';
 import * as os from 'node:os';
 import * as path from 'node:path';
-import { afterEach, beforeEach, describe, expect, mock, test } from 'bun:test';
 import type { PluginConfig } from '../config';
 import { SLIM_INTERNAL_INITIATOR_MARKER } from '../utils';
 import { BackgroundTaskManager } from './background-manager';
@@ -170,6 +170,24 @@ describe('BackgroundTaskManager', () => {
       expect(['pending', 'starting']).toContain(task2.status);
       expect(['pending', 'starting']).toContain(task3.status);
     });
+
+    test('resolves displayName alias to internal agent name on launch', () => {
+      const ctx = createMockContext();
+      const manager = new BackgroundTaskManager(ctx, undefined, {
+        agents: {
+          oracle: { displayName: 'advisor' },
+        },
+      });
+
+      const task = manager.launch({
+        agent: 'advisor',
+        prompt: 'test',
+        description: 'test',
+        parentSessionId: 'parent-123',
+      });
+
+      expect(task.agent).toBe('oracle');
+    });
   });
 
   describe('handleSessionStatus', () => {
@@ -833,6 +851,112 @@ describe('BackgroundTaskManager', () => {
       ).toBe(true);
     });
 
+    test('sends completion notification to parent with parent agent', async () => {
+      const ctx = createMockContext({
+        sessionMessagesResult: {
+          data: [
+            {
+              info: { role: 'assistant' },
+              parts: [{ type: 'text', text: 'done' }],
+            },
+          ],
+        },
+      });
+
+      const manager = new BackgroundTaskManager(ctx);
+
+      // Create a tracked orchestrator parent session
+      const parentTask = manager.launch({
+        agent: 'orchestrator',
+        prompt: 'orchestrate',
+        description: 'parent orchestration',
+        parentSessionId: 'root-session',
+      });
+
+      await Promise.resolve();
+      await Promise.resolve();
+
+      const parentSessionId = parentTask.sessionId;
+      if (!parentSessionId) throw new Error('Expected parent session id');
+
+      // Launch nested subagent under orchestrator
+      const childTask = manager.launch({
+        agent: 'explorer',
+        prompt: 'collect tests',
+        description: 'nested subagent',
+        parentSessionId,
+      });
+
+      await Promise.resolve();
+      await Promise.resolve();
+
+      await manager.handleSessionStatus({
+        type: 'session.status',
+        properties: {
+          sessionID: childTask.sessionId,
+          status: { type: 'idle' },
+        },
+      });
+
+      const promptCalls = ctx.client.session.prompt.mock.calls as Array<{
+        path: { id: string };
+        body: { agent?: string; parts: Array<{ text?: string }> };
+      }>;
+
+      const notificationCall = promptCalls.find(
+        (c) => c[0].path.id === parentSessionId,
+      );
+
+      expect(notificationCall).toBeDefined();
+      expect(notificationCall?.[0].body.agent).toBe('orchestrator');
+    });
+
+    test('sends completion notification using orchestrator for untracked parent', async () => {
+      const ctx = createMockContext({
+        sessionMessagesResult: {
+          data: [
+            {
+              info: { role: 'assistant' },
+              parts: [{ type: 'text', text: 'done' }],
+            },
+          ],
+        },
+      });
+
+      const manager = new BackgroundTaskManager(ctx);
+      const untrackedParentId = 'unknown-root';
+
+      const childTask = manager.launch({
+        agent: 'explorer',
+        prompt: 'collect tests',
+        description: 'orphan child',
+        parentSessionId: untrackedParentId,
+      });
+
+      await Promise.resolve();
+      await Promise.resolve();
+
+      await manager.handleSessionStatus({
+        type: 'session.status',
+        properties: {
+          sessionID: childTask.sessionId,
+          status: { type: 'idle' },
+        },
+      });
+
+      const promptCalls = ctx.client.session.prompt.mock.calls as Array<{
+        path: { id: string };
+        body: { agent?: string };
+      }>;
+
+      const notificationCall = promptCalls.find(
+        (c) => c[0].path.id === untrackedParentId,
+      );
+
+      expect(notificationCall).toBeDefined();
+      expect(notificationCall?.[0].body.agent).toBe('orchestrator');
+    });
+
     test('retries next fallback model when first model returns empty response', async () => {
       let messagesCallCount = 0;
       const ctx = createMockContext({

+ 15 - 2
src/background/background-manager.ts

@@ -28,6 +28,7 @@ import {
   applyAgentVariant,
   createInternalAgentTextPart,
   resolveAgentVariant,
+  resolveRuntimeAgentName,
 } from '../utils';
 import { getLogDir, log } from '../utils/logger';
 import {
@@ -205,6 +206,14 @@ export class BackgroundTaskManager {
     );
   }
 
+  /**
+   * Resolve the agent associated with a session.
+   * Untracked sessions are treated as orchestrator sessions by default.
+   */
+  private getSessionAgent(sessionId: string): string {
+    return this.agentBySessionId.get(sessionId) ?? 'orchestrator';
+  }
+
   /**
    * Check if a parent session is allowed to delegate to a specific agent type.
    * @param parentSessionId - The session ID of the parent
@@ -254,11 +263,13 @@ export class BackgroundTaskManager {
    * @returns The created background task with pending status
    */
   launch(opts: LaunchOptions): BackgroundTask {
+    const resolvedAgent = resolveRuntimeAgentName(this.config, opts.agent);
+
     const task: BackgroundTask = {
       id: generateTaskId(),
       sessionId: undefined,
       description: opts.description,
-      agent: opts.agent,
+      agent: resolvedAgent,
       status: 'pending',
       startedAt: new Date(),
       config: {
@@ -274,7 +285,7 @@ export class BackgroundTaskManager {
     this.enqueueStart(task);
 
     log(`[background-manager] task launched: ${task.id}`, {
-      agent: opts.agent,
+      agent: resolvedAgent,
       description: opts.description,
     });
 
@@ -695,6 +706,7 @@ export class BackgroundTaskManager {
   private async sendCompletionNotification(
     task: BackgroundTask,
   ): Promise<void> {
+    const parentAgent = this.getSessionAgent(task.parentSessionId);
     const message =
       task.status === 'completed'
         ? `[Background task "${task.description}" completed]`
@@ -703,6 +715,7 @@ export class BackgroundTaskManager {
     await this.client.session.prompt({
       path: { id: task.parentSessionId },
       body: {
+        agent: parentAgent,
         parts: [createInternalAgentTextPart(message)],
       },
     });

+ 35 - 0
src/background/multiplexer-session-manager.test.ts

@@ -24,6 +24,7 @@ mock.module('../multiplexer', () => ({
 // Mock the plugin context
 function createMockContext(overrides?: {
   sessionStatusResult?: { data?: Record<string, { type: string }> };
+  directory?: string;
 }) {
   const defaultPort = process.env.OPENCODE_PORT ?? '4096';
   return {
@@ -34,6 +35,7 @@ function createMockContext(overrides?: {
         ),
       },
     },
+    directory: overrides?.directory ?? '/test/directory',
     serverUrl: new URL(`http://localhost:${defaultPort}`),
   } as any;
 }
@@ -83,6 +85,12 @@ describe('MultiplexerSessionManager', () => {
       });
 
       expect(mockMultiplexer.spawnPane).toHaveBeenCalled();
+      expect(mockMultiplexer.spawnPane).toHaveBeenCalledWith(
+        'child-123',
+        'Test Worker',
+        `http://localhost:${process.env.OPENCODE_PORT ?? '4096'}/`,
+        '/test/directory',
+      );
     });
 
     test('ignores sessions without parentID', async () => {
@@ -105,6 +113,33 @@ describe('MultiplexerSessionManager', () => {
       expect(mockMultiplexer.spawnPane).not.toHaveBeenCalled();
     });
 
+    test('prefers child session directory when present', async () => {
+      const ctx = createMockContext({ directory: '/parent/directory' });
+      const manager = new MultiplexerSessionManager(
+        ctx,
+        defaultMultiplexerConfig,
+      );
+
+      await manager.onSessionCreated({
+        type: 'session.created',
+        properties: {
+          info: {
+            id: 'child-456',
+            parentID: 'parent-456',
+            title: 'Nested Worker',
+            directory: '/child/directory',
+          },
+        },
+      });
+
+      expect(mockMultiplexer.spawnPane).toHaveBeenCalledWith(
+        'child-456',
+        'Nested Worker',
+        `http://localhost:${process.env.OPENCODE_PORT ?? '4096'}/`,
+        '/child/directory',
+      );
+    });
+
     test('ignores if disabled in config', async () => {
       const ctx = createMockContext();
       const manager = new MultiplexerSessionManager(ctx, {

+ 10 - 2
src/background/multiplexer-session-manager.ts

@@ -26,7 +26,12 @@ interface TrackedSession {
 interface SessionEvent {
   type: string;
   properties?: {
-    info?: { id?: string; parentID?: string; title?: string };
+    info?: {
+      id?: string;
+      parentID?: string;
+      title?: string;
+      directory?: string;
+    };
     sessionID?: string;
     status?: { type: string };
   };
@@ -44,6 +49,7 @@ const SESSION_MISSING_GRACE_MS = POLL_INTERVAL_BACKGROUND_MS * 3;
 export class MultiplexerSessionManager {
   private client: OpencodeClient;
   private serverUrl: string;
+  private directory: string;
   private multiplexer: Multiplexer | null = null;
   private sessions = new Map<string, TrackedSession>();
   private pollInterval?: ReturnType<typeof setInterval>;
@@ -51,6 +57,7 @@ export class MultiplexerSessionManager {
 
   constructor(ctx: PluginInput, config: MultiplexerConfig) {
     this.client = ctx.client;
+    this.directory = ctx.directory;
     const defaultPort = process.env.OPENCODE_PORT ?? '4096';
     this.serverUrl =
       ctx.serverUrl?.toString() ?? `http://localhost:${defaultPort}`;
@@ -88,6 +95,7 @@ export class MultiplexerSessionManager {
     const sessionId = info.id;
     const parentId = info.parentID;
     const title = info.title ?? 'Subagent';
+    const directory = info.directory ?? this.directory;
 
     // Skip if we're already tracking this session
     if (this.sessions.has(sessionId)) {
@@ -113,7 +121,7 @@ export class MultiplexerSessionManager {
     });
 
     const paneResult = await this.multiplexer
-      .spawnPane(sessionId, title, this.serverUrl)
+      .spawnPane(sessionId, title, this.serverUrl, directory)
       .catch((err) => {
         log('[multiplexer-session-manager] failed to spawn pane', {
           error: String(err),

+ 3 - 0
src/cli/config-io.test.ts

@@ -216,6 +216,9 @@ describe('config-io', () => {
     expect(result.success).toBe(true);
 
     const saved = JSON.parse(readFileSync(litePath, 'utf-8'));
+    expect(saved.$schema).toBe(
+      'https://unpkg.com/oh-my-opencode-slim@latest/oh-my-opencode-slim.schema.json',
+    );
     expect(saved.preset).toBe('openai');
     expect(saved.presets.openai).toBeDefined();
     expect(saved.tmux.enabled).toBe(true);

+ 6 - 0
src/cli/custom-skills.ts

@@ -28,6 +28,12 @@ export interface CustomSkill {
  * Registry of custom skills bundled in this repository.
  */
 export const CUSTOM_SKILLS: CustomSkill[] = [
+  {
+    name: 'simplify',
+    description: 'Code simplification and readability-focused refactoring',
+    allowedAgents: ['oracle'],
+    sourcePath: 'src/skills/simplify',
+  },
   {
     name: 'cartography',
     description: 'Repository understanding and hierarchical codemap generation',

+ 3 - 5
src/cli/index.ts

@@ -5,13 +5,12 @@ import type { BooleanArg, InstallArgs } from './types';
 function parseArgs(args: string[]): InstallArgs {
   const result: InstallArgs = {
     tui: true,
+    skills: 'yes',
   };
 
   for (const arg of args) {
     if (arg === '--no-tui') {
       result.tui = false;
-    } else if (arg.startsWith('--tmux=')) {
-      result.tmux = arg.split('=')[1] as BooleanArg;
     } else if (arg.startsWith('--skills=')) {
       result.skills = arg.split('=')[1] as BooleanArg;
     } else if (arg === '--dry-run') {
@@ -34,8 +33,7 @@ oh-my-opencode-slim installer
 Usage: bunx oh-my-opencode-slim install [OPTIONS]
 
 Options:
-  --tmux=yes|no          Enable tmux integration (yes/no)
-  --skills=yes|no        Install recommended skills (yes/no)
+  --skills=yes|no        Install recommended and bundled skills (default: yes)
   --no-tui               Non-interactive mode
   --dry-run              Simulate install without writing files
   --reset                Force overwrite of existing configuration
@@ -46,7 +44,7 @@ For alternative providers, see docs/provider-configurations.md.
 
 Examples:
   bunx oh-my-opencode-slim install
-  bunx oh-my-opencode-slim install --no-tui --tmux=no --skills=yes
+  bunx oh-my-opencode-slim install --no-tui --skills=yes
   bunx oh-my-opencode-slim install --reset
 `);
 }

+ 26 - 29
src/cli/install.ts

@@ -24,13 +24,13 @@ const DIM = '\x1b[2m';
 const RESET = '\x1b[0m';
 
 const SYMBOLS = {
-  check: `${GREEN}${RESET}`,
-  cross: `${RED}${RESET}`,
-  arrow: `${BLUE}${RESET}`,
-  bullet: `${DIM}${RESET}`,
-  info: `${BLUE}${RESET}`,
-  warn: `${YELLOW}${RESET}`,
-  star: `${YELLOW}${RESET}`,
+  check: `${GREEN}[ok]${RESET}`,
+  cross: `${RED}[x]${RESET}`,
+  arrow: `${BLUE}->${RESET}`,
+  bullet: `${DIM}-${RESET}`,
+  info: `${BLUE}[i]${RESET}`,
+  warn: `${YELLOW}[!]${RESET}`,
+  star: `${YELLOW}*${RESET}`,
 };
 
 function printHeader(isUpdate: boolean): void {
@@ -98,19 +98,6 @@ function handleStepResult(
   return true;
 }
 
-function formatConfigSummary(): string {
-  const lines: string[] = [];
-  lines.push(`${BOLD}Configuration Summary${RESET}`);
-  lines.push('');
-  lines.push(`  ${BOLD}Preset:${RESET} ${BLUE}openai${RESET}`);
-  lines.push(`  ${SYMBOLS.check} OpenAI (default)`);
-  const seeDocs = 'see docs/provider-configurations.md';
-  lines.push(`  ${DIM}○ Kimi — ${seeDocs}${RESET}`);
-  lines.push(`  ${DIM}○ GitHub Copilot — ${seeDocs}${RESET}`);
-  lines.push(`  ${DIM}○ ZAI Coding Plan — ${seeDocs}${RESET}`);
-  return lines.join('\n');
-}
-
 async function runInstall(config: InstallConfig): Promise<number> {
   const detected = detectCurrentConfig();
   const isUpdate = detected.isInstalled;
@@ -225,11 +212,6 @@ async function runInstall(config: InstallConfig): Promise<number> {
     }
   }
 
-  // Summary
-  console.log();
-  console.log(formatConfigSummary());
-  console.log();
-
   const statusMsg = isUpdate
     ? 'Configuration updated!'
     : 'Installation complete!';
@@ -238,15 +220,30 @@ async function runInstall(config: InstallConfig): Promise<number> {
   console.log(`${BOLD}Next steps:${RESET}`);
   console.log();
 
-  console.log(`  1. Start OpenCode:`);
+  const configPath = getExistingLiteConfigPath();
+
+  console.log('  1. Log in to the provider(s) you want to use:');
+  console.log(`     ${BLUE}$ opencode auth login${RESET}`);
+  console.log();
+  console.log('  2. Refresh the models OpenCode can see:');
+  console.log(`     ${BLUE}$ opencode models --refresh${RESET}`);
+  console.log();
+  console.log('  3. Review your generated config:');
+  console.log(`     ${BLUE}${configPath}${RESET}`);
+  console.log();
+  console.log('  4. Start OpenCode:');
   console.log(`     ${BLUE}$ opencode${RESET}`);
   console.log();
+  console.log('  5. Verify the agents are responding:');
+  console.log(`     ${BLUE}> ping all agents${RESET}`);
+  console.log();
+
   const modelsInfo =
     'Default configuration uses OpenAI models (gpt-5.4 / gpt-5.4-mini).';
-  console.log(`${BOLD}${modelsInfo}${RESET}`);
+  console.log(`${modelsInfo}`);
   const altProviders =
     'For alternative providers (Kimi, GitHub Copilot, ZAI Coding Plan)';
-  console.log(`${BOLD}${altProviders}, see:${RESET}`);
+  console.log(`${altProviders}, see:`);
   const docsUrl =
     'https://github.com/alvinunreal/oh-my-opencode-slim/' +
     'blob/master/docs/provider-configurations.md';
@@ -258,7 +255,7 @@ async function runInstall(config: InstallConfig): Promise<number> {
 
 export async function install(args: InstallArgs): Promise<number> {
   const config: InstallConfig = {
-    hasTmux: args.tmux === 'yes',
+    hasTmux: false,
     installSkills: args.skills === 'yes',
     installCustomSkills: args.skills === 'yes',
     dryRun: args.dryRun,

+ 16 - 1
src/cli/providers.test.ts

@@ -14,8 +14,12 @@ describe('providers', () => {
       hasTmux: false,
       installSkills: false,
       installCustomSkills: false,
+      reset: false,
     });
 
+    expect(config.$schema).toBe(
+      'https://unpkg.com/oh-my-opencode-slim@latest/oh-my-opencode-slim.schema.json',
+    );
     expect(config.preset).toBe('openai');
     const agents = (config.presets as any).openai;
     expect(agents).toBeDefined();
@@ -30,6 +34,7 @@ describe('providers', () => {
       hasTmux: false,
       installSkills: false,
       installCustomSkills: false,
+      reset: false,
     });
 
     const agents = (config.presets as any).openai;
@@ -51,6 +56,7 @@ describe('providers', () => {
       hasTmux: true,
       installSkills: false,
       installCustomSkills: false,
+      reset: false,
     });
 
     expect(config.tmux).toBeDefined();
@@ -63,12 +69,19 @@ describe('providers', () => {
       hasTmux: false,
       installSkills: true,
       installCustomSkills: false,
+      reset: false,
     });
 
     const agents = (config.presets as any).openai;
     // Orchestrator should always have '*'
     expect(agents.orchestrator.skills).toEqual(['*']);
 
+    // Oracle should have bundled simplify
+    expect(agents.oracle.skills).toContain('simplify');
+
+    // Explorer should have bundled cartography
+    expect(agents.explorer.skills).toContain('cartography');
+
     // Designer should have 'agent-browser'
     expect(agents.designer.skills).toContain('agent-browser');
 
@@ -81,6 +94,7 @@ describe('providers', () => {
       hasTmux: false,
       installSkills: false,
       installCustomSkills: false,
+      reset: false,
     });
 
     const agents = (config.presets as any).openai;
@@ -95,10 +109,11 @@ describe('providers', () => {
       hasTmux: false,
       installSkills: false,
       installCustomSkills: false,
+      reset: false,
     });
 
     const agents = (config.presets as any).openai;
-    expect(agents.orchestrator.mcps).toContain('*');
+    expect(agents.orchestrator.mcps).toEqual(['*', '!context7']);
     expect(agents.librarian.mcps).toContain('websearch');
     expect(agents.librarian.mcps).toContain('context7');
     expect(agents.librarian.mcps).toContain('grep_app');

+ 17 - 5
src/cli/providers.ts

@@ -1,7 +1,11 @@
 import { DEFAULT_AGENT_MCPS } from '../config/agent-mcps';
+import { CUSTOM_SKILLS } from './custom-skills';
 import { RECOMMENDED_SKILLS } from './skills';
 import type { InstallConfig } from './types';
 
+const SCHEMA_URL =
+  'https://unpkg.com/oh-my-opencode-slim@latest/oh-my-opencode-slim.schema.json';
+
 // Model mappings by provider - only 4 supported providers
 export const MODEL_MAPPINGS = {
   openai: {
@@ -45,6 +49,7 @@ export function generateLiteConfig(
   installConfig: InstallConfig,
 ): Record<string, unknown> {
   const config: Record<string, unknown> = {
+    $schema: SCHEMA_URL,
     preset: 'openai',
     presets: {},
   };
@@ -57,11 +62,18 @@ export function generateLiteConfig(
 
     const skills = isOrchestrator
       ? ['*']
-      : RECOMMENDED_SKILLS.filter(
-          (s) =>
-            s.allowedAgents.includes('*') ||
-            s.allowedAgents.includes(agentName),
-        ).map((s) => s.skillName);
+      : [
+          ...RECOMMENDED_SKILLS.filter(
+            (s) =>
+              s.allowedAgents.includes('*') ||
+              s.allowedAgents.includes(agentName),
+          ).map((s) => s.skillName),
+          ...CUSTOM_SKILLS.filter(
+            (s) =>
+              s.allowedAgents.includes('*') ||
+              s.allowedAgents.includes(agentName),
+          ).map((s) => s.name),
+        ];
 
     if (agentName === 'designer' && !skills.includes('agent-browser')) {
       skills.push('agent-browser');

+ 1 - 1
src/cli/skills.test.ts

@@ -17,7 +17,7 @@ describe('skills permissions', () => {
     const designerPerms = getSkillPermissionsForAgent('designer');
     expect(designerPerms['agent-browser']).toBe('allow');
 
-    // Oracle should have simplify allowed by recommendation
+    // Oracle should have simplify allowed by default
     const oraclePerms = getSkillPermissionsForAgent('oracle');
     expect(oraclePerms.simplify).toBe('allow');
   });

+ 0 - 7
src/cli/skills.ts

@@ -37,13 +37,6 @@ export interface PermissionOnlySkill {
  * Add new skills here to include them in the installation flow.
  */
 export const RECOMMENDED_SKILLS: RecommendedSkill[] = [
-  {
-    name: 'simplify',
-    repo: 'https://github.com/brianlovin/claude-config',
-    skillName: 'simplify',
-    allowedAgents: ['oracle'],
-    description: 'YAGNI code simplification expert',
-  },
   {
     name: 'agent-browser',
     repo: 'https://github.com/vercel-labs/agent-browser',

+ 0 - 1
src/cli/types.ts

@@ -2,7 +2,6 @@ export type BooleanArg = 'yes' | 'no';
 
 export interface InstallArgs {
   tui: boolean;
-  tmux?: BooleanArg;
   skills?: BooleanArg;
   dryRun?: boolean;
   reset?: boolean;

+ 2 - 2
src/config/agent-mcps.test.ts

@@ -14,7 +14,7 @@ describe('parseList', () => {
     ]);
   });
 
-  test('orchestrator wildcard includes custom mcps too', () => {
+  test('orchestrator wildcard excludes context7 but includes custom mcps', () => {
     expect(
       parseList(DEFAULT_AGENT_MCPS.orchestrator, [
         'websearch',
@@ -22,7 +22,7 @@ describe('parseList', () => {
         'grep_app',
         'custom-mcp',
       ]),
-    ).toEqual(['websearch', 'context7', 'grep_app', 'custom-mcp']);
+    ).toEqual(['websearch', 'grep_app', 'custom-mcp']);
   });
 
   test('wildcard with exclusions', () => {

+ 1 - 1
src/config/agent-mcps.ts

@@ -8,7 +8,7 @@ import {
 /** Default MCPs per agent - "*" means all MCPs, "!item" excludes specific MCPs */
 
 export const DEFAULT_AGENT_MCPS: Record<AgentName, string[]> = {
-  orchestrator: ['*'],
+  orchestrator: ['*', '!context7'],
   designer: [],
   oracle: [],
   librarian: ['websearch', 'context7', 'grep_app'],

+ 15 - 0
src/config/loader.test.ts

@@ -80,6 +80,21 @@ describe('loadPluginConfig', () => {
     expect(config.balanceProviderUsage).toBe(true);
   });
 
+  test('loads showStartupToast flag when configured', () => {
+    const projectDir = path.join(tempDir, 'project');
+    const projectConfigDir = path.join(projectDir, '.opencode');
+    fs.mkdirSync(projectConfigDir, { recursive: true });
+    fs.writeFileSync(
+      path.join(projectConfigDir, 'oh-my-opencode-slim.json'),
+      JSON.stringify({
+        showStartupToast: false,
+      }),
+    );
+
+    const config = loadPluginConfig(projectDir);
+    expect(config.showStartupToast).toBe(false);
+  });
+
   test('loads manual plan structure when configured', () => {
     const projectDir = path.join(tempDir, 'project');
     const projectConfigDir = path.join(projectDir, '.opencode');

+ 7 - 0
src/config/schema.ts

@@ -99,6 +99,7 @@ export const AgentOverrideConfigSchema = z.object({
   skills: z.array(z.string()).optional(), // skills this agent can use ("*" = all, "!item" = exclude)
   mcps: z.array(z.string()).optional(), // MCPs this agent can use ("*" = all, "!item" = exclude)
   options: z.record(z.string(), z.unknown()).optional(), // provider-specific model options (e.g., textVerbosity, thinking budget)
+  displayName: z.string().min(1).optional(),
 });
 
 // Multiplexer type options
@@ -236,6 +237,12 @@ export const PluginConfigSchema = z.object({
   setDefaultAgent: z.boolean().optional(),
   scoringEngineVersion: z.enum(['v1', 'v2-shadow', 'v2']).optional(),
   balanceProviderUsage: z.boolean().optional(),
+  showStartupToast: z
+    .boolean()
+    .optional()
+    .describe(
+      'Show the startup activation toast when OpenCode starts. Defaults to true.',
+    ),
   manualPlan: ManualPlanSchema.optional(),
   presets: z.record(z.string(), PresetSchema).optional(),
   agents: z.record(z.string(), AgentOverrideConfigSchema).optional(),

+ 93 - 26
src/hooks/image-hook.ts

@@ -3,14 +3,15 @@ import {
   existsSync,
   mkdirSync,
   readdirSync,
+  rmdirSync,
   statSync,
   unlinkSync,
   writeFileSync,
 } from 'node:fs';
-import { join } from 'node:path';
+import { basename, extname, join } from 'node:path';
 
-// Debounce: only run cleanup every 10 minutes
-let lastCleanup = 0;
+// Debounce: only run cleanup every 10 minutes per directory
+const lastCleanupByDir = new Map<string, number>();
 const CLEANUP_INTERVAL = 10 * 60 * 1000; // 10 minutes
 
 interface ImagePart {
@@ -66,6 +67,71 @@ function extFromMime(mime: string): string {
   return map[mime] ?? '.png';
 }
 
+function sanitizeFilename(name: string): string {
+  return name.replace(/[^a-zA-Z0-9._-]/g, '_');
+}
+
+function cleanupOldImages(dir: string, saveDir: string): void {
+  const now = Date.now();
+  const lastCleanup = lastCleanupByDir.get(dir) ?? 0;
+  if (now - lastCleanup < CLEANUP_INTERVAL) return;
+  lastCleanupByDir.set(dir, now);
+
+  try {
+    const maxAge = 60 * 60 * 1000;
+    for (const f of readdirSync(dir)) {
+      const fp = join(dir, f);
+      try {
+        if (now - statSync(fp).mtimeMs > maxAge) unlinkSync(fp);
+      } catch {}
+    }
+    // Remove empty session subdirectory and prune its debounce entry
+    if (dir !== saveDir) {
+      try {
+        rmdirSync(dir);
+        lastCleanupByDir.delete(dir);
+      } catch {}
+    }
+  } catch {}
+}
+
+function writeUniqueFile(
+  dir: string,
+  name: string,
+  data: Buffer,
+  log: (msg: string) => void,
+): string | null {
+  const ext = extname(name);
+  const base = basename(name, ext) || name;
+  let candidate = join(dir, name);
+  let counter = 0;
+
+  const MAX_ATTEMPTS = 1000;
+  for (let attempt = 0; attempt < MAX_ATTEMPTS; attempt++) {
+    try {
+      writeFileSync(candidate, data, { flag: 'wx' });
+      return candidate;
+    } catch (e) {
+      if (
+        e instanceof Error &&
+        (e as NodeJS.ErrnoException).code === 'EEXIST'
+      ) {
+        counter += 1;
+        candidate = join(dir, `${base}-${counter}${ext}`);
+        continue;
+      }
+
+      log(`[image-hook] failed to save image: ${e}`);
+      return null;
+    }
+  }
+
+  log(
+    `[image-hook] failed to save image: max attempts (${MAX_ATTEMPTS}) reached`,
+  );
+  return null;
+}
+
 export function processImageAttachments(args: {
   messages: MessageWithParts[];
   workDir: string;
@@ -88,26 +154,23 @@ export function processImageAttachments(args: {
     log(`[image-hook] failed to create image directory: ${e}`);
   }
 
-  // Clean up images older than 1 hour (debounced: only check every 10 minutes)
-  const now = Date.now();
-  if (now - lastCleanup > CLEANUP_INTERVAL) {
-    lastCleanup = now;
-    try {
-      const maxAge = 60 * 60 * 1000;
-      for (const f of readdirSync(saveDir)) {
-        const fp = join(saveDir, f);
-        try {
-          if (now - statSync(fp).mtimeMs > maxAge) unlinkSync(fp);
-        } catch {}
-      }
-    } catch {}
-  }
-
   for (const msg of messages) {
     if (msg.info.role !== 'user') continue;
     const imageParts = msg.parts.filter(isImagePart);
     if (imageParts.length === 0) continue;
 
+    const sessionSubdir = msg.info.sessionID
+      ? sanitizeFilename(msg.info.sessionID)
+      : undefined;
+    const targetDir = sessionSubdir ? join(saveDir, sessionSubdir) : saveDir;
+    try {
+      mkdirSync(targetDir, { recursive: true });
+    } catch (e) {
+      log(`[image-hook] failed to create target image directory: ${e}`);
+    }
+
+    cleanupOldImages(targetDir, saveDir);
+
     // Save each image to .opencode/images/ and collect paths
     const savedPaths: string[] = [];
     for (const p of imageParts) {
@@ -121,14 +184,18 @@ export function processImageAttachments(args: {
             .update(decoded.data)
             .digest('hex')
             .slice(0, 8);
-          const name = filename ?? `image-${hash}${extFromMime(decoded.mime)}`;
-          const filePath = join(saveDir, name);
-          try {
-            writeFileSync(filePath, decoded.data);
-            savedPaths.push(filePath);
-          } catch (e) {
-            log(`[image-hook] failed to save image: ${e}`);
-          }
+          const sanitizedFilename = filename
+            ? sanitizeFilename(filename)
+            : undefined;
+          const baseName = sanitizedFilename
+            ? sanitizedFilename.replace(/\.[^.]+$/, '') || 'image'
+            : 'image';
+          const ext = sanitizedFilename
+            ? extname(sanitizedFilename) || extFromMime(decoded.mime)
+            : extFromMime(decoded.mime);
+          const name = `${baseName}-${hash}${ext}`;
+          const filePath = writeUniqueFile(targetDir, name, decoded.data, log);
+          if (filePath) savedPaths.push(filePath);
         }
       }
     }

+ 27 - 2
src/index.ts

@@ -32,6 +32,7 @@ import {
   lsp_rename,
   setUserLspConfig,
 } from './tools';
+import { resolveRuntimeAgentName, rewriteDisplayNameMentions } from './utils';
 import { initLogger, log } from './utils/logger';
 
 const OhMyOpenCodeLite: Plugin = async (ctx) => {
@@ -138,7 +139,7 @@ const OhMyOpenCodeLite: Plugin = async (ctx) => {
 
   // Initialize auto-update checker hook
   const autoUpdateChecker = createAutoUpdateCheckerHook(ctx, {
-    showStartupToast: true,
+    showStartupToast: config.showStartupToast ?? true,
     autoUpdate: true,
   });
 
@@ -524,7 +525,19 @@ const OhMyOpenCodeLite: Plugin = async (ctx) => {
       input: { sessionID: string; agent?: string },
       output?: { message?: { agent?: string } },
     ) => {
-      const agent = input.agent ?? output?.message?.agent;
+      const rawAgent = input.agent ?? output?.message?.agent;
+      const agent = rawAgent
+        ? resolveRuntimeAgentName(config, rawAgent)
+        : undefined;
+
+      if (
+        agent &&
+        output?.message &&
+        typeof output.message.agent === 'string'
+      ) {
+        output.message.agent = agent;
+      }
+
       if (agent) {
         sessionAgentMap.set(input.sessionID, agent);
       }
@@ -590,6 +603,18 @@ const OhMyOpenCodeLite: Plugin = async (ctx) => {
         }>;
       };
 
+      for (const message of typedOutput.messages) {
+        if (message.info.role !== 'user') {
+          continue;
+        }
+        for (const part of message.parts) {
+          if (part.type !== 'text' || typeof part.text !== 'string') {
+            continue;
+          }
+          part.text = rewriteDisplayNameMentions(config, part.text);
+        }
+      }
+
       // Strip image parts from orchestrator messages when @observer is available.
       // When the orchestrator's model doesn't support image input, the API call
       // fails before the LLM can respond. We replace image bytes with a text

+ 71 - 0
src/multiplexer/factory.test.ts

@@ -0,0 +1,71 @@
+import { afterEach, describe, expect, test } from 'bun:test';
+
+async function importFreshFactory(suffix: string) {
+  return import(`./factory?test=${suffix}-${Date.now()}-${Math.random()}`);
+}
+
+describe('multiplexer factory', () => {
+  const originalTmux = process.env.TMUX;
+  const originalTmuxPane = process.env.TMUX_PANE;
+
+  afterEach(() => {
+    process.env.TMUX = originalTmux;
+    process.env.TMUX_PANE = originalTmuxPane;
+  });
+
+  test('returns a fresh tmux instance per call', async () => {
+    process.env.TMUX = '/tmp/tmux-1000/default,123,0';
+    process.env.TMUX_PANE = '%1';
+
+    const { getMultiplexer } = await importFreshFactory('tmux-first');
+
+    const first = getMultiplexer({
+      type: 'tmux',
+      layout: 'main-vertical',
+      main_pane_size: 60,
+    });
+
+    process.env.TMUX_PANE = '%2';
+
+    const { getMultiplexer: getMultiplexerAgain } =
+      await importFreshFactory('tmux-second');
+
+    const second = getMultiplexerAgain({
+      type: 'tmux',
+      layout: 'main-vertical',
+      main_pane_size: 60,
+    });
+
+    expect(first).not.toBeNull();
+    expect(second).not.toBeNull();
+    expect(Object.is(first, second)).toBe(false);
+  });
+
+  test('returns a fresh auto-detected tmux instance per call', async () => {
+    process.env.TMUX = '/tmp/tmux-1000/default,123,0';
+    process.env.TMUX_PANE = '%1';
+
+    const { getMultiplexer } = await importFreshFactory('auto-first');
+
+    const first = getMultiplexer({
+      type: 'auto',
+      layout: 'main-vertical',
+      main_pane_size: 60,
+    });
+
+    process.env.TMUX_PANE = '%2';
+
+    const { getMultiplexer: getMultiplexerAgain } =
+      await importFreshFactory('auto-second');
+
+    const second = getMultiplexerAgain({
+      type: 'auto',
+      layout: 'main-vertical',
+      main_pane_size: 60,
+    });
+
+    expect(first).not.toBeNull();
+    expect(second).not.toBeNull();
+    expect(Object.is(first, second)).toBe(false);
+  });
+});

+ 6 - 12
src/multiplexer/factory.ts

@@ -8,10 +8,12 @@ import { TmuxMultiplexer } from './tmux';
 import type { Multiplexer } from './types';
 import { ZellijMultiplexer } from './zellij';
 
-const multiplexerCache = new Map<MultiplexerType | 'auto', Multiplexer>();
-
 /**
- * Create or retrieve a multiplexer instance based on config
+ * Create a multiplexer instance based on config.
+ *
+ * Do not cache instances: tmux/zellij integrations may depend on
+ * per-process environment like TMUX_PANE/ZELLIJ, which should be captured
+ * fresh for each plugin context.
  */
 export function getMultiplexer(config: MultiplexerConfig): Multiplexer | null {
   const { type } = config;
@@ -20,12 +22,6 @@ export function getMultiplexer(config: MultiplexerConfig): Multiplexer | null {
     return null;
   }
 
-  // Return cached instance if available
-  const cached = multiplexerCache.get(type);
-  if (cached) {
-    return cached;
-  }
-
   // Create new instance
   let multiplexer: Multiplexer;
   let actualType: MultiplexerType;
@@ -63,8 +59,6 @@ export function getMultiplexer(config: MultiplexerConfig): Multiplexer | null {
       return null;
   }
 
-  // Cache the instance under the actual type (not 'auto')
-  multiplexerCache.set(actualType, multiplexer);
   log(`[multiplexer] Created ${actualType} instance`);
 
   return multiplexer;
@@ -74,7 +68,7 @@ export function getMultiplexer(config: MultiplexerConfig): Multiplexer | null {
  * Clear the multiplexer cache (useful for testing)
  */
 export function clearMultiplexerCache(): void {
-  multiplexerCache.clear();
+  // No-op: multiplexers are no longer cached.
 }
 
 /**

+ 45 - 10
src/multiplexer/tmux/index.ts

@@ -14,6 +14,7 @@ export class TmuxMultiplexer implements Multiplexer {
   private hasChecked = false;
   private storedLayout: MultiplexerLayout;
   private storedMainPaneSize: number;
+  private targetPane = process.env.TMUX_PANE;
 
   constructor(layout: MultiplexerLayout = 'main-vertical', mainPaneSize = 60) {
     this.storedLayout = layout;
@@ -38,6 +39,7 @@ export class TmuxMultiplexer implements Multiplexer {
     sessionId: string,
     description: string,
     serverUrl: string,
+    directory: string,
   ): Promise<PaneResult> {
     const tmux = await this.getBinary();
     if (!tmux) {
@@ -47,7 +49,19 @@ export class TmuxMultiplexer implements Multiplexer {
 
     try {
       // Build the attach command
-      const opencodeCmd = `opencode attach ${serverUrl} --session ${sessionId}`;
+      const quotedDirectory = quoteShellArg(directory);
+      const quotedUrl = quoteShellArg(serverUrl);
+      const quotedSessionId = quoteShellArg(sessionId);
+
+      const opencodeCmd = [
+        'opencode',
+        'attach',
+        quotedUrl,
+        '--session',
+        quotedSessionId,
+        '--dir',
+        quotedDirectory,
+      ].join(' ');
 
       // tmux split-window -h -d -P -F '#{pane_id}' <cmd>
       const args = [
@@ -57,6 +71,7 @@ export class TmuxMultiplexer implements Multiplexer {
         '-P', // Print pane info
         '-F',
         '#{pane_id}', // Format: just the pane ID
+        ...this.targetArgs(),
         opencodeCmd,
       ];
 
@@ -164,10 +179,13 @@ export class TmuxMultiplexer implements Multiplexer {
 
     try {
       // Apply the layout
-      const layoutProc = crossSpawn([tmux, 'select-layout', layout], {
-        stdout: 'pipe',
-        stderr: 'pipe',
-      });
+      const layoutProc = crossSpawn(
+        [tmux, 'select-layout', ...this.targetArgs(), layout],
+        {
+          stdout: 'pipe',
+          stderr: 'pipe',
+        },
+      );
       await layoutProc.exited;
 
       // For main-* layouts, set the main pane size
@@ -176,7 +194,13 @@ export class TmuxMultiplexer implements Multiplexer {
           layout === 'main-horizontal' ? 'main-pane-height' : 'main-pane-width';
 
         const sizeProc = crossSpawn(
-          [tmux, 'set-window-option', sizeOption, `${mainPaneSize}%`],
+          [
+            tmux,
+            'set-window-option',
+            ...this.targetArgs(),
+            sizeOption,
+            `${mainPaneSize}%`,
+          ],
           {
             stdout: 'pipe',
             stderr: 'pipe',
@@ -185,10 +209,13 @@ export class TmuxMultiplexer implements Multiplexer {
         await sizeProc.exited;
 
         // Reapply layout to use the new size
-        const reapplyProc = crossSpawn([tmux, 'select-layout', layout], {
-          stdout: 'pipe',
-          stderr: 'pipe',
-        });
+        const reapplyProc = crossSpawn(
+          [tmux, 'select-layout', ...this.targetArgs(), layout],
+          {
+            stdout: 'pipe',
+            stderr: 'pipe',
+          },
+        );
         await reapplyProc.exited;
       }
 
@@ -203,6 +230,10 @@ export class TmuxMultiplexer implements Multiplexer {
     return this.binaryPath;
   }
 
+  private targetArgs(): string[] {
+    return this.targetPane ? ['-t', this.targetPane] : [];
+  }
+
   private async findBinary(): Promise<string | null> {
     const isWindows = process.platform === 'win32';
     const cmd = isWindows ? 'where' : 'which';
@@ -245,3 +276,7 @@ export class TmuxMultiplexer implements Multiplexer {
     }
   }
 }
+
+function quoteShellArg(value: string): string {
+  return `'${value.replace(/'/g, `'\\''`)}'`;
+}

+ 2 - 1
src/multiplexer/types.ts

@@ -34,12 +34,13 @@ export interface Multiplexer {
    * @param sessionId - The OpenCode session ID to attach to
    * @param description - Human-readable description for the pane
    * @param serverUrl - The OpenCode server URL to attach to
-   * @returns PaneResult with pane ID for later cleanup
+   * @param directory - The project directory to attach from
    */
   spawnPane(
     sessionId: string,
     description: string,
     serverUrl: string,
+    directory: string,
   ): Promise<PaneResult>;
 
   /**

+ 48 - 8
src/multiplexer/zellij/index.ts

@@ -52,6 +52,7 @@ export class ZellijMultiplexer implements Multiplexer {
     sessionId: string,
     description: string,
     serverUrl: string,
+    directory: string,
   ): Promise<PaneResult> {
     const zellij = await this.getBinary();
     if (!zellij) return { success: false };
@@ -72,6 +73,7 @@ export class ZellijMultiplexer implements Multiplexer {
           this.firstPaneId,
           sessionId,
           serverUrl,
+          directory,
           description,
         );
         if (success) {
@@ -86,6 +88,7 @@ export class ZellijMultiplexer implements Multiplexer {
         zellij,
         sessionId,
         serverUrl,
+        directory,
         description,
       );
     } catch {
@@ -97,9 +100,14 @@ export class ZellijMultiplexer implements Multiplexer {
     zellij: string,
     sessionId: string,
     serverUrl: string,
+    directory: string,
     description: string,
   ): Promise<PaneResult> {
-    const opencodeCmd = `opencode attach ${serverUrl} --session ${sessionId}`;
+    const opencodeCmd = buildOpencodeAttachCommand(
+      sessionId,
+      serverUrl,
+      directory,
+    );
     const paneName = description.slice(0, 30).replace(/"/g, '\\"');
 
     const currentTabId = await this.getCurrentTabId(zellij);
@@ -115,7 +123,7 @@ export class ZellijMultiplexer implements Multiplexer {
         '--close-on-exit',
         '--',
         'sh',
-        '-c',
+        '-lc',
         opencodeCmd,
       ];
 
@@ -157,7 +165,7 @@ export class ZellijMultiplexer implements Multiplexer {
       '--close-on-exit',
       '--',
       'sh',
-      '-c',
+      '-lc',
       opencodeCmd,
     ];
 
@@ -193,10 +201,15 @@ export class ZellijMultiplexer implements Multiplexer {
     paneId: string,
     sessionId: string,
     serverUrl: string,
+    directory: string,
     description: string,
   ): Promise<boolean> {
     try {
-      const opencodeCmd = `opencode attach ${serverUrl} --session ${sessionId}`;
+      const opencodeCmd = buildOpencodeAttachCommand(
+        sessionId,
+        serverUrl,
+        directory,
+      );
 
       await crossSpawn([zellij, 'action', 'focus-pane', '--pane-id', paneId], {
         stdout: 'ignore',
@@ -208,10 +221,13 @@ export class ZellijMultiplexer implements Multiplexer {
         { stdout: 'ignore', stderr: 'ignore' },
       ).exited;
 
-      await crossSpawn([zellij, 'action', 'write-chars', opencodeCmd], {
-        stdout: 'ignore',
-        stderr: 'ignore',
-      }).exited;
+      await crossSpawn(
+        [zellij, 'action', 'write-chars', buildShellLaunchCommand(opencodeCmd)],
+        {
+          stdout: 'ignore',
+          stderr: 'ignore',
+        },
+      ).exited;
 
       await crossSpawn([zellij, 'action', 'write-chars', '\n'], {
         stdout: 'ignore',
@@ -456,3 +472,27 @@ export class ZellijMultiplexer implements Multiplexer {
     }
   }
 }
+
+function buildOpencodeAttachCommand(
+  sessionId: string,
+  serverUrl: string,
+  directory: string,
+): string {
+  return [
+    'opencode',
+    'attach',
+    quoteShellArg(serverUrl),
+    '--session',
+    quoteShellArg(sessionId),
+    '--dir',
+    quoteShellArg(directory),
+  ].join(' ');
+}
+
+function buildShellLaunchCommand(command: string): string {
+  return ['sh', '-lc', quoteShellArg(command)].join(' ');
+}
+
+function quoteShellArg(value: string): string {
+  return `'${value.replace(/'/g, `'\\''`)}'`;
+}

+ 19 - 0
src/skills/simplify/README.md

@@ -0,0 +1,19 @@
+# Simplify Skill
+
+Behavior-preserving code simplification and readability-focused refactoring.
+
+## Overview
+
+This bundled skill helps the `oracle` agent review and simplify code without
+changing behavior. It focuses on readability, maintainability, and reducing
+unnecessary complexity.
+
+## Source
+
+Adapted from Addy Osmani's
+[`code-simplification` skill](https://github.com/addyosmani/agent-skills/blob/main/skills/code-simplification/SKILL.md).
+
+## Installation
+
+Bundled with `oh-my-opencode-slim` and installed automatically when bundled
+skills are enabled via the installer.

+ 138 - 0
src/skills/simplify/SKILL.md

@@ -0,0 +1,138 @@
+---
+name: simplify
+description: Simplifies code for clarity without changing behavior. Use for readability, maintainability, and complexity reduction after behavior is understood.
+---
+
+# Code Simplification
+
+## Overview
+
+Simplify code by reducing complexity while preserving exact behavior. The goal is not fewer lines — it's code that is easier to read, understand, modify, and debug. Every simplification must pass a simple test: "Would a new team member understand this faster than the original?"
+
+## When to Use
+
+- After a feature is working and tests pass, but the implementation feels heavier than it needs to be
+- During code review when readability or complexity issues are flagged
+- When you encounter deeply nested logic, long functions, or unclear names
+- When refactoring code written under time pressure
+- When consolidating related logic scattered across files
+- After merging changes that introduced duplication or inconsistency
+
+**When NOT to use:**
+
+- Code is already clean and readable — don't simplify for the sake of it
+- You don't understand what the code does yet — comprehend before you simplify
+- The code is performance-critical and the "simpler" version would be measurably slower
+- You're about to rewrite the module entirely — simplifying throwaway code wastes effort
+
+## The Five Principles
+
+### 1. Preserve Behavior Exactly
+
+Don't change what the code does — only how it expresses it. All inputs, outputs, side effects, error behavior, and edge cases must remain identical. If you're not sure a simplification preserves behavior, don't make it.
+
+Before every change, ask:
+
+- Does this produce the same output for every input?
+- Does this maintain the same error behavior?
+- Does this preserve the same side effects and ordering?
+- Do all existing tests still pass without modification?
+
+### 2. Follow Project Conventions
+
+Simplification means making code more consistent with the codebase, not imposing external preferences.
+
+Before simplifying:
+
+1. Read `AGENTS.md` / project conventions
+2. Study how neighboring code handles similar patterns
+3. Match the project's style for imports, naming, function style, error handling, and type annotations
+
+Simplification that breaks project consistency is not simplification — it's churn.
+
+### 3. Prefer Clarity Over Cleverness
+
+Explicit code is better than compact code when the compact version requires a mental pause to parse.
+
+- Replace nested ternaries with readable control flow
+- Replace dense inline transforms with named intermediate steps when they clarify intent
+- Keep helpful names even if they cost a few extra lines
+
+### 4. Maintain Balance
+
+Watch for over-simplification:
+
+- Don't inline away names that carry meaning
+- Don't merge unrelated logic into one larger function
+- Don't remove abstractions that serve testability or extensibility
+- Don't optimize for line count over comprehension
+
+### 5. Scope to What Changed
+
+Default to simplifying recently modified code. Avoid unrelated drive-by refactors unless explicitly asked.
+
+## Process
+
+### Step 1: Understand Before Touching
+
+Before changing or removing anything, understand why it exists.
+
+Answer:
+
+- What is this code's responsibility?
+- What calls it? What does it call?
+- What are the edge cases and error paths?
+- Are there tests that define expected behavior?
+- Why might it have been written this way?
+
+If you can't answer these, read more context first.
+
+### Step 2: Look for Simplification Opportunities
+
+Signals:
+
+- Deep nesting
+- Long functions with mixed responsibilities
+- Nested ternaries
+- Boolean flag arguments
+- Repeated conditionals
+- Generic or misleading names
+- Duplicated logic
+- Dead code
+- Wrappers or abstractions that add no value
+
+### Step 3: Apply Changes Incrementally
+
+Make one simplification at a time.
+
+For each simplification:
+
+1. Make the change
+2. Run relevant tests
+3. Keep it only if behavior is preserved
+
+Separate refactoring from feature work whenever possible.
+
+### Step 4: Verify the Result
+
+After simplifying, confirm:
+
+- The code is genuinely easier to understand
+- The diff is clean and reviewable
+- Project conventions still match
+- No behavior, error handling, or side effects changed
+
+## Guidance for This Repository
+
+- Prefer straightforward TypeScript over clever compression
+- Preserve existing runtime behavior, tests, and hooks
+- Favor explicit names and smaller focused helpers when they improve readability
+- Keep refactors tightly scoped to the task or review feedback
+
+## Verification Checklist
+
+- [ ] Existing tests pass without modification
+- [ ] Build/typecheck/lint still pass
+- [ ] No unrelated files were refactored
+- [ ] No error handling was weakened or removed
+- [ ] The result is simpler to review than the original

+ 100 - 0
src/tools/background.test.ts

@@ -0,0 +1,100 @@
+import { describe, expect, mock, test } from 'bun:test';
+import type { PluginConfig } from '../config';
+import { createBackgroundTools } from './background';
+
+function createMockManager() {
+  return {
+    isAgentAllowed: mock(() => true),
+    getAllowedSubagents: mock(() => ['oracle']),
+    launch: mock(
+      (opts: {
+        agent: string;
+        prompt: string;
+        description: string;
+        parentSessionId: string;
+      }) => ({
+        id: 'bg_test1234',
+        sessionId: undefined,
+        description: opts.description,
+        agent: opts.agent,
+        status: 'pending',
+        startedAt: new Date(),
+        config: { maxConcurrentStarts: 10 },
+        parentSessionId: opts.parentSessionId,
+        prompt: opts.prompt,
+      }),
+    ),
+    getResult: mock(() => null),
+    waitForCompletion: mock(async () => null),
+    cancel: mock(() => 0),
+  };
+}
+
+describe('createBackgroundTools displayName runtime aliasing', () => {
+  test('resolves displayName alias for background_task direct invocation', async () => {
+    const manager = createMockManager();
+    const config: PluginConfig = {
+      agents: {
+        oracle: { displayName: 'advisor' },
+      },
+    };
+
+    const tools = createBackgroundTools(
+      {} as any,
+      manager as any,
+      undefined,
+      config,
+    );
+
+    const result = await tools.background_task.execute(
+      {
+        agent: 'advisor',
+        prompt: 'Analyze this architecture',
+        description: 'Architecture analysis',
+      },
+      { sessionID: 'session-1' } as any,
+    );
+
+    expect(manager.isAgentAllowed).toHaveBeenCalledWith('session-1', 'oracle');
+    expect(manager.launch).toHaveBeenCalledWith({
+      agent: 'oracle',
+      prompt: 'Analyze this architecture',
+      description: 'Architecture analysis',
+      parentSessionId: 'session-1',
+    });
+    expect(result).toContain('Agent: oracle');
+  });
+
+  test('keeps internal agent names working for background_task', async () => {
+    const manager = createMockManager();
+    const config: PluginConfig = {
+      agents: {
+        oracle: { displayName: 'advisor' },
+      },
+    };
+
+    const tools = createBackgroundTools(
+      {} as any,
+      manager as any,
+      undefined,
+      config,
+    );
+
+    await tools.background_task.execute(
+      {
+        agent: 'oracle',
+        prompt: 'Analyze this architecture',
+        description: 'Architecture analysis',
+      },
+      { sessionID: 'session-1' } as any,
+    );
+
+    expect(manager.isAgentAllowed).toHaveBeenCalledWith('session-1', 'oracle');
+    expect(manager.launch).toHaveBeenCalledWith({
+      agent: 'oracle',
+      prompt: 'Analyze this architecture',
+      description: 'Architecture analysis',
+      parentSessionId: 'session-1',
+    });
+  });
+});

+ 2 - 1
src/tools/background.ts

@@ -8,6 +8,7 @@ import type { BackgroundTaskManager } from '../background';
 import type { PluginConfig } from '../config';
 import { SUBAGENT_NAMES } from '../config';
 import type { MultiplexerConfig } from '../config/schema';
+import { resolveRuntimeAgentName } from '../utils';
 
 const z = tool.schema;
 
@@ -55,7 +56,7 @@ Key behaviors:
         throw new Error('Invalid toolContext: missing sessionID');
       }
 
-      const agent = String(args.agent);
+      const agent = resolveRuntimeAgentName(_pluginConfig, String(args.agent));
       const prompt = String(args.prompt);
       const description = String(args.description);
       const parentSessionId = (toolContext as { sessionID: string }).sessionID;

+ 104 - 0
src/utils/agent-variant.test.ts

@@ -4,6 +4,8 @@ import {
   applyAgentVariant,
   normalizeAgentName,
   resolveAgentVariant,
+  resolveRuntimeAgentName,
+  rewriteDisplayNameMentions,
 } from './agent-variant';
 
 describe('normalizeAgentName', () => {
@@ -100,6 +102,108 @@ describe('resolveAgentVariant', () => {
     } as PluginConfig;
     expect(resolveAgentVariant(config, 'oracle')).toBeUndefined();
   });
+
+  test('resolves displayName alias to internal agent for variant lookup', () => {
+    const config = {
+      agents: {
+        oracle: { displayName: 'advisor', variant: 'high' },
+      },
+    } as PluginConfig;
+    expect(resolveAgentVariant(config, '@advisor')).toBe('high');
+  });
+});
+
+describe('resolveRuntimeAgentName', () => {
+  test('keeps internal agent names unchanged', () => {
+    const config = {
+      agents: {
+        oracle: { displayName: 'advisor' },
+      },
+    } as PluginConfig;
+
+    expect(resolveRuntimeAgentName(config, 'oracle')).toBe('oracle');
+  });
+
+  test('resolves displayName to internal name', () => {
+    const config = {
+      agents: {
+        oracle: { displayName: 'advisor' },
+      },
+    } as PluginConfig;
+
+    expect(resolveRuntimeAgentName(config, 'advisor')).toBe('oracle');
+  });
+
+  test('resolves displayName with @ prefix and whitespace', () => {
+    const config = {
+      agents: {
+        oracle: { displayName: 'advisor' },
+      },
+    } as PluginConfig;
+
+    expect(resolveRuntimeAgentName(config, '  @advisor  ')).toBe('oracle');
+  });
+
+  test('resolves displayName configured via legacy alias key', () => {
+    const config = {
+      agents: {
+        explore: { displayName: 'researcher' },
+      },
+    } as PluginConfig;
+
+    expect(resolveRuntimeAgentName(config, 'researcher')).toBe('explorer');
+  });
+
+  test('returns normalized name when no displayName match exists', () => {
+    const config = {
+      agents: {
+        oracle: { displayName: 'advisor' },
+      },
+    } as PluginConfig;
+
+    expect(resolveRuntimeAgentName(config, '  @unknown  ')).toBe('unknown');
+  });
+});
+
+describe('rewriteDisplayNameMentions', () => {
+  test('rewrites displayName mentions to internal names for direct invocation', () => {
+    const config = {
+      agents: {
+        oracle: { displayName: 'advisor' },
+      },
+    } as PluginConfig;
+
+    expect(rewriteDisplayNameMentions(config, 'ask @advisor about this')).toBe(
+      'ask @oracle about this',
+    );
+  });
+
+  test('keeps internal mentions working while rewriting aliases', () => {
+    const config = {
+      agents: {
+        oracle: { displayName: 'advisor' },
+      },
+    } as PluginConfig;
+
+    expect(
+      rewriteDisplayNameMentions(config, 'compare @advisor with @oracle'),
+    ).toBe('compare @oracle with @oracle');
+  });
+
+  test('does not rewrite embedded text such as email addresses', () => {
+    const config = {
+      agents: {
+        oracle: { displayName: 'advisor' },
+      },
+    } as PluginConfig;
+
+    expect(
+      rewriteDisplayNameMentions(
+        config,
+        'email foo@advisor.com and ask @advisor directly',
+      ),
+    ).toBe('email foo@advisor.com and ask @oracle directly');
+  });
 });
 
 describe('applyAgentVariant', () => {

+ 80 - 3
src/utils/agent-variant.ts

@@ -1,4 +1,8 @@
-import type { PluginConfig } from '../config';
+import {
+  ALL_AGENT_NAMES,
+  getAgentOverride,
+  type PluginConfig,
+} from '../config';
 import { log } from './logger';
 
 /**
@@ -36,8 +40,8 @@ export function resolveAgentVariant(
   config: PluginConfig | undefined,
   agentName: string,
 ): string | undefined {
-  const normalized = normalizeAgentName(agentName);
-  const rawVariant = config?.agents?.[normalized]?.variant;
+  const normalized = resolveRuntimeAgentName(config, agentName);
+  const rawVariant = getAgentOverride(config, normalized)?.variant;
 
   if (typeof rawVariant !== 'string') {
     return undefined;
@@ -52,6 +56,79 @@ export function resolveAgentVariant(
   return trimmed;
 }
 
+/**
+ * Resolve a runtime-provided agent name to an internal agent name.
+ *
+ * Supports:
+ * - internal names (e.g. "oracle")
+ * - @-prefixed names (e.g. "@oracle")
+ * - displayName aliases (e.g. "advisor" -> "oracle")
+ */
+export function resolveRuntimeAgentName(
+  config: PluginConfig | undefined,
+  agentName: string,
+): string {
+  const normalized = normalizeAgentName(agentName);
+  if (!normalized) {
+    return normalized;
+  }
+
+  if ((ALL_AGENT_NAMES as readonly string[]).includes(normalized)) {
+    return normalized;
+  }
+
+  for (const internalName of ALL_AGENT_NAMES) {
+    const displayName = getAgentOverride(config, internalName)?.displayName;
+    if (!displayName) {
+      continue;
+    }
+
+    if (normalizeAgentName(displayName) === normalized) {
+      return internalName;
+    }
+  }
+
+  return normalized;
+}
+
+function escapeRegExp(value: string): string {
+  return value.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
+}
+
+/**
+ * Rewrites user-facing display-name mentions (e.g. @advisor) into internal
+ * agent mentions (e.g. @oracle) for runtime routing.
+ */
+export function rewriteDisplayNameMentions(
+  config: PluginConfig | undefined,
+  text: string,
+): string {
+  if (!text.includes('@')) {
+    return text;
+  }
+
+  let rewritten = text;
+
+  for (const internalName of ALL_AGENT_NAMES) {
+    const displayName = getAgentOverride(config, internalName)?.displayName;
+    if (!displayName) {
+      continue;
+    }
+
+    const normalizedDisplayName = normalizeAgentName(displayName);
+    if (!normalizedDisplayName || normalizedDisplayName === internalName) {
+      continue;
+    }
+
+    rewritten = rewritten.replace(
+      new RegExp(`(^|[^\\w.])@${escapeRegExp(normalizedDisplayName)}\\b`, 'g'),
+      `$1@${internalName}`,
+    );
+  }
+
+  return rewritten;
+}
+
 /**
  * Applies a variant to a request body if the body doesn't already have one.
  *