fix: resolve 5 remaining pre-publish blockers (14, 15, 17, 21, 25c)

- completion-promise-detector: restrict to assistant text parts only,
  remove tool_result from completion detection (blocker 14)
- ralph-loop tests: flip tool_result completion expectations to negative
  coverage, add false-positive rejection tests (blocker 15)
- skill tools: merge nativeSkills into initial cachedDescription
  synchronously before any execute() call (blocker 17)
- skill tools test: add assertion for initial description including
  native skills before execute() (blocker 25c)
- docs: sync all 4 fallback-chain docs with model-requirements.ts
  runtime source of truth (blocker 21)

Verified: bun test (4599 pass / 0 fail), tsc --noEmit clean
This commit is contained in:
YeonGyu-Kim
2026-03-28 15:57:27 +09:00
parent d2c576c510
commit 4a029258a4
11 changed files with 171 additions and 121 deletions

View File

@@ -64,8 +64,8 @@ These agents have Claude-optimized prompts — long, detailed, mechanics-driven.
| Agent | Role | Fallback Chain | Notes |
| ------------ | ----------------- | -------------------------------------- | ------------------------------------------------------------------------------------------------- |
| **Sisyphus** | Main orchestrator | Claude Opus → opencode-go/kimi-k2.5 → K2P5 → Kimi K2.5 → GPT-5.4 → GLM-5 → Big Pickle | Claude-family first. GPT-5.4 has dedicated prompt support. Kimi available through multiple providers. |
| **Metis** | Plan gap analyzer | Claude Opus → GPT-5.4 → opencode-go/glm-5 → K2P5 | Claude preferred. GPT-5.4 as secondary before GLM-5 fallback. |
| **Sisyphus** | Main orchestrator | anthropic\|github-copilot\|opencode/claude-opus-4-6 (max) → opencode-go/kimi-k2.5 → kimi-for-coding/k2p5 → opencode\|moonshotai\|moonshotai-cn\|firmware\|ollama-cloud\|aihubmix/kimi-k2.5 → openai\|github-copilot\|opencode/gpt-5.4 (medium) → zai-coding-plan\|opencode/glm-5 → opencode/big-pickle | Exact runtime chain from `src/shared/model-requirements.ts`. |
| **Metis** | Plan gap analyzer | anthropic\|github-copilot\|opencode/claude-opus-4-6 (max) → openai\|github-copilot\|opencode/gpt-5.4 (high) → opencode-go/glm-5 → kimi-for-coding/k2p5 | Exact runtime chain from `src/shared/model-requirements.ts`. |
### Dual-Prompt Agents → Claude preferred, GPT supported
@@ -73,8 +73,8 @@ These agents ship separate prompts for Claude and GPT families. They auto-detect
| Agent | Role | Fallback Chain | Notes |
| -------------- | ----------------- | -------------------------------------- | -------------------------------------------------------------------- |
| **Prometheus** | Strategic planner | Claude Opus → GPT-5.4 → opencode-go/glm-5 → Gemini 3.1 Pro | Interview-mode planning. GPT prompt is compact and principle-driven. |
| **Atlas** | Todo orchestrator | Claude Sonnet → opencode-go/kimi-k2.5 → GPT-5.4 | Claude first, opencode-go as intermediate, GPT-5.4 as last resort. |
| **Prometheus** | Strategic planner | anthropic\|github-copilot\|opencode/claude-opus-4-6 (max) → openai\|github-copilot\|opencode/gpt-5.4 (high) → opencode-go/glm-5 → google\|github-copilot\|opencode/gemini-3.1-pro | Exact runtime chain from `src/shared/model-requirements.ts`. |
| **Atlas** | Todo orchestrator | anthropic\|github-copilot\|opencode/claude-sonnet-4-6 → opencode-go/kimi-k2.5 → openai\|github-copilot\|opencode/gpt-5.4 (medium) → opencode-go/minimax-m2.7 | Exact runtime chain from `src/shared/model-requirements.ts`. |
### Deep Specialists → GPT
@@ -82,9 +82,9 @@ These agents are built for GPT's principle-driven style. Their prompts assume au
| Agent | Role | Fallback Chain | Notes |
| -------------- | ----------------------- | -------------------------------------- | ------------------------------------------------ |
| **Hephaestus** | Autonomous deep worker | GPT-5.4 (medium) | Requires GPT access. The craftsman. |
| **Oracle** | Architecture consultant | GPT-5.4 → Gemini 3.1 Pro → Claude Opus → opencode-go/glm-5 | Read-only high-IQ consultation. |
| **Momus** | Ruthless reviewer | GPT-5.4 → Claude Opus → Gemini 3.1 Pro → opencode-go/glm-5 | Verification and plan review. GPT-5.4 uses xhigh variant. |
| **Hephaestus** | Autonomous deep worker | GPT-5.4 (medium) | Requires a GPT-capable provider. The craftsman. |
| **Oracle** | Architecture consultant | openai\|github-copilot\|opencode/gpt-5.4 (high) → google\|github-copilot\|opencode/gemini-3.1-pro (high) → anthropic\|github-copilot\|opencode/claude-opus-4-6 (max) → opencode-go/glm-5 | Exact runtime chain from `src/shared/model-requirements.ts`. |
| **Momus** | Ruthless reviewer | openai\|github-copilot\|opencode/gpt-5.4 (xhigh) → anthropic\|github-copilot\|opencode/claude-opus-4-6 (max) → google\|github-copilot\|opencode/gemini-3.1-pro (high) → opencode-go/glm-5 | Exact runtime chain from `src/shared/model-requirements.ts`. |
### Utility Runners → Speed over Intelligence
@@ -92,10 +92,10 @@ These agents do grep, search, and retrieval. They intentionally use the fastest,
| Agent | Role | Fallback Chain | Notes |
| --------------------- | ------------------ | ---------------------------------------------- | ----------------------------------------------------- |
| **Explore** | Fast codebase grep | Grok Code Fast → opencode-go/minimax-m2.7-highspeed → opencode/minimax-m2.7 → Haiku → GPT-5-Nano | Speed is everything. Fire 10 in parallel. Uses the high-speed OpenCode Go MiniMax entry first, then the standard OpenCode Zen MiniMax fallback. |
| **Librarian** | Docs/code search | opencode-go/minimax-m2.7 → opencode/minimax-m2.7-highspeed → Haiku → GPT-5-Nano | Doc retrieval doesn't need deep reasoning. Uses OpenCode Go MiniMax first, then the OpenCode Zen high-speed MiniMax fallback. |
| **Multimodal Looker** | Vision/screenshots | GPT-5.4 → opencode-go/kimi-k2.5 → GLM-4.6v → GPT-5-Nano | Uses the first available multimodal-capable fallback. |
| **Sisyphus-Junior** | Category executor | Claude Sonnet → opencode-go/kimi-k2.5 → GPT-5.4 → MiniMax M2.7 → Big Pickle | Handles delegated category tasks. Sonnet-tier default. |
| **Explore** | Fast codebase grep | github-copilot\|xai/grok-code-fast-1 → opencode-go/minimax-m2.7-highspeed → opencode/minimax-m2.7 → anthropic\|opencode/claude-haiku-4-5 → opencode/gpt-5-nano | Exact runtime chain from `src/shared/model-requirements.ts`. |
| **Librarian** | Docs/code search | opencode-go/minimax-m2.7 → opencode/minimax-m2.7-highspeed → anthropic\|opencode/claude-haiku-4-5 → opencode/gpt-5-nano | Exact runtime chain from `src/shared/model-requirements.ts`. |
| **Multimodal Looker** | Vision/screenshots | openai\|opencode/gpt-5.4 (medium) → opencode-go/kimi-k2.5 → zai-coding-plan/glm-4.6v → openai\|github-copilot\|opencode/gpt-5-nano | Exact runtime chain from `src/shared/model-requirements.ts`. |
| **Sisyphus-Junior** | Category executor | anthropic\|github-copilot\|opencode/claude-sonnet-4-6 → opencode-go/kimi-k2.5 → openai\|github-copilot\|opencode/gpt-5.4 (medium) → opencode-go/minimax-m2.7 → opencode/big-pickle | Exact runtime chain from `src/shared/model-requirements.ts`. |
---
@@ -149,7 +149,7 @@ A premium subscription tier ($10/month) that provides reliable access to Chinese
**When It Gets Used:**
OpenCode Go models appear in fallback chains as intermediate options. They bridge the gap between premium Claude access and free-tier alternatives. The system tries OpenCode Go models before falling back to cheaper provider-specific entries like MiniMax or Big Pickle, then GPT alternatives where applicable.
OpenCode Go models appear throughout the fallback chains as intermediate options. Depending on the agent, they can sit before GPT, after GPT, or act as the last structured-model fallback before cheaper utility paths.
**Go-Only Scenarios:**
@@ -169,14 +169,14 @@ When agents delegate work, they don't pick a model name — they pick a **catego
| Category | When Used | Fallback Chain |
| -------------------- | -------------------------- | -------------------------------------------- |
| `visual-engineering` | Frontend, UI, CSS, design | Gemini 3.1 Pro → GLM 5 → Claude Opus → opencode-go/glm-5 → K2P5 |
| `ultrabrain` | Maximum reasoning needed | GPT-5.4 → Gemini 3.1 Pro → Claude Opus → opencode-go/glm-5 |
| `deep` | Deep coding, complex logic | GPT-5.3 Codex → Claude Opus → Gemini 3.1 Pro |
| `artistry` | Creative, novel approaches | Gemini 3.1 Pro → Claude Opus → GPT-5.4 |
| `quick` | Simple, fast tasks | GPT-5.4 Mini → Claude Haiku → Gemini Flash → opencode-go/minimax-m2.7 → GPT-5-Nano |
| `unspecified-high` | General complex work | Claude Opus → GPT-5.4 → GLM 5 → K2P5 → opencode-go/glm-5 → Kimi K2.5 |
| `unspecified-low` | General standard work | Claude Sonnet → GPT-5.3 Codex → opencode-go/kimi-k2.5 → Gemini Flash |
| `writing` | Text, docs, prose | Gemini Flash → opencode-go/kimi-k2.5 → Claude Sonnet |
| `visual-engineering` | Frontend, UI, CSS, design | google\|github-copilot\|opencode/gemini-3.1-pro (high) → zai-coding-plan\|opencode/glm-5 → anthropic\|github-copilot\|opencode/claude-opus-4-6 (max) → opencode-go/glm-5 → kimi-for-coding/k2p5 |
| `ultrabrain` | Maximum reasoning needed | openai\|opencode/gpt-5.4 (xhigh) → google\|github-copilot\|opencode/gemini-3.1-pro (high) → anthropic\|github-copilot\|opencode/claude-opus-4-6 (max) → opencode-go/glm-5 |
| `deep` | Deep coding, complex logic | openai\|opencode/gpt-5.3-codex (medium) → anthropic\|github-copilot\|opencode/claude-opus-4-6 (max) → google\|github-copilot\|opencode/gemini-3.1-pro (high) |
| `artistry` | Creative, novel approaches | google\|github-copilot\|opencode/gemini-3.1-pro (high) → anthropic\|github-copilot\|opencode/claude-opus-4-6 (max) → openai\|github-copilot\|opencode/gpt-5.4 |
| `quick` | Simple, fast tasks | openai\|github-copilot\|opencode/gpt-5.4-mini → anthropic\|github-copilot\|opencode/claude-haiku-4-5 → google\|github-copilot\|opencode/gemini-3-flash → opencode-go/minimax-m2.7 → opencode/gpt-5-nano |
| `unspecified-high` | General complex work | anthropic\|github-copilot\|opencode/claude-opus-4-6 (max) → openai\|github-copilot\|opencode/gpt-5.4 (high) → zai-coding-plan\|opencode/glm-5 → kimi-for-coding/k2p5 → opencode-go/glm-5 → opencode/kimi-k2.5 → opencode\|moonshotai\|moonshotai-cn\|firmware\|ollama-cloud\|aihubmix/kimi-k2.5 |
| `unspecified-low` | General standard work | anthropic\|github-copilot\|opencode/claude-sonnet-4-6 → openai\|opencode/gpt-5.3-codex (medium) → opencode-go/kimi-k2.5 → google\|github-copilot\|opencode/gemini-3-flash → opencode-go/minimax-m2.7 |
| `writing` | Text, docs, prose | google\|github-copilot\|opencode/gemini-3-flash → opencode-go/kimi-k2.5 → anthropic\|github-copilot\|opencode/claude-sonnet-4-6 → opencode-go/minimax-m2.7 |
See the [Orchestration System Guide](./orchestration.md) for how agents dispatch tasks to categories.

View File

@@ -218,7 +218,7 @@ When GitHub Copilot is the best available provider, install-time defaults are ag
| ------------- | ---------------------------------- |
| **Sisyphus** | `github-copilot/claude-opus-4.6` |
| **Oracle** | `github-copilot/gpt-5.4` |
| **Explore** | `github-copilot/gpt-5-mini` |
| **Explore** | `github-copilot/grok-code-fast-1` |
| **Atlas** | `github-copilot/claude-sonnet-4.6` |
GitHub Copilot acts as a proxy provider, routing requests to underlying models based on your subscription. Some agents, like Librarian, are not installed from Copilot alone and instead rely on other configured providers or runtime fallback behavior.
@@ -246,7 +246,7 @@ When OpenCode Zen is the best available provider, these are the most relevant so
| ------------- | ---------------------------------------------------- |
| **Sisyphus** | `opencode/claude-opus-4-6` |
| **Oracle** | `opencode/gpt-5.4` |
| **Explore** | `opencode/claude-haiku-4-5` |
| **Explore** | `opencode/minimax-m2.7` |
##### Setup
@@ -281,7 +281,7 @@ Not all models behave the same way. Understanding which models are "similar" hel
| **Claude Opus 4.6** | anthropic, github-copilot, opencode | Best overall. Default for Sisyphus. |
| **Claude Sonnet 4.6** | anthropic, github-copilot, opencode | Faster, cheaper. Good balance. |
| **Claude Haiku 4.5** | anthropic, opencode | Fast and cheap. Good for quick tasks. |
| **Kimi K2.5** | kimi-for-coding | Behaves very similarly to Claude. Great all-rounder. Default for Atlas. |
| **Kimi K2.5** | kimi-for-coding, opencode-go, opencode, moonshotai, moonshotai-cn, firmware, ollama-cloud, aihubmix | Behaves very similarly to Claude. Great all-rounder that appears in several orchestration fallback chains. |
| **Kimi K2.5 Free** | opencode | Free-tier Kimi. Rate-limited but functional. |
| **GLM 5** | zai-coding-plan, opencode | Claude-like behavior. Good for broad tasks. |
| **Big Pickle (GLM 4.6)** | opencode | Free-tier GLM. Decent fallback. |
@@ -302,15 +302,15 @@ Not all models behave the same way. Understanding which models are "similar" hel
| **Gemini 3.1 Pro** | google, github-copilot, opencode | Excels at visual/frontend tasks. Different reasoning style. |
| **Gemini 3 Flash** | google, github-copilot, opencode | Fast, good for doc search and light tasks. |
| **MiniMax M2.7** | opencode-go, opencode | Fast and smart. Utility fallbacks use `minimax-m2.7` or `minimax-m2.7-highspeed` depending on the chain. |
| **MiniMax M2.7 Highspeed** | opencode | Faster OpenCode Zen variant used in utility fallback chains where the runtime prefers the high-speed catalog entry. |
| **MiniMax M2.7 Highspeed** | opencode-go, opencode | Faster utility variant used in Explore and other retrieval-heavy fallback chains. |
**Speed-Focused Models**:
| Model | Provider(s) | Speed | Notes |
| ----------------------- | ---------------------- | -------------- | --------------------------------------------------------------------------------------------------------------------------------------------- |
| **Grok Code Fast 1** | github-copilot, venice | Very fast | Optimized for code grep/search. Default for Explore. |
| **Grok Code Fast 1** | github-copilot, xai | Very fast | Optimized for code grep/search. Default for Explore. |
| **Claude Haiku 4.5** | anthropic, opencode | Fast | Good balance of speed and intelligence. |
| **MiniMax M2.7 Highspeed** | opencode | Very fast | OpenCode Zen high-speed utility fallback used by runtime chains such as Librarian. |
| **MiniMax M2.7 Highspeed** | opencode-go, opencode | Very fast | High-speed MiniMax utility fallback used by runtime chains such as Explore and, on the OpenCode catalog, Librarian. |
| **GPT-5.3-codex-spark** | openai | Extremely fast | Blazing fast but compacts so aggressively that oh-my-openagent's context management doesn't work well with it. Not recommended for omo agents. |
#### What Each Agent Does and Which Model It Got
@@ -321,8 +321,8 @@ Based on your subscriptions, here's how the agents were configured:
| Agent | Role | Default Chain | What It Does |
| ------------ | ---------------- | ----------------------------------------------- | ---------------------------------------------------------------------------------------- |
| **Sisyphus** | Main ultraworker | Opus (max) → OpenCode Go Kimi K2.5 → K2P5 → Kimi K2.5 → GPT-5.4 (medium) → GLM 5 → Big Pickle | Primary coding agent. Orchestrates everything. Claude-family models are still preferred, but GPT-5.4 now has a dedicated prompt path. |
| **Metis** | Plan review | Opus (max) → GPT-5.4 (high) → GLM 5 → K2P5 | Reviews Prometheus plans for gaps. |
| **Sisyphus** | Main ultraworker | anthropic\|github-copilot\|opencode/claude-opus-4-6 (max) → opencode-go/kimi-k2.5 → kimi-for-coding/k2p5 → opencode\|moonshotai\|moonshotai-cn\|firmware\|ollama-cloud\|aihubmix/kimi-k2.5 → openai\|github-copilot\|opencode/gpt-5.4 (medium) → zai-coding-plan\|opencode/glm-5 → opencode/big-pickle | Primary coding agent. Exact runtime chain from `src/shared/model-requirements.ts`. |
| **Metis** | Plan review | anthropic\|github-copilot\|opencode/claude-opus-4-6 (max) → openai\|github-copilot\|opencode/gpt-5.4 (high) → opencode-go/glm-5 → kimi-for-coding/k2p5 | Reviews Prometheus plans for gaps. Exact runtime chain from `src/shared/model-requirements.ts`. |
**Dual-Prompt Agents** (auto-switch between Claude and GPT prompts):
@@ -332,16 +332,16 @@ Priority: **Claude > GPT > Claude-like models**
| Agent | Role | Default Chain | GPT Prompt? |
| -------------- | ----------------- | ---------------------------------------------------------- | ---------------------------------------------------------------- |
| **Prometheus** | Strategic planner | Opus (max) → **GPT-5.4 (high)** → GLM 5 → Gemini 3.1 Pro | Yes — XML-tagged, principle-driven (~300 lines vs ~1,100 Claude) |
| **Atlas** | Todo orchestrator | **Claude Sonnet 4.6** → Kimi K2.5 → GPT-5.4 (medium) → MiniMax M2.7 | Yes - GPT-optimized todo management |
| **Prometheus** | Strategic planner | anthropic\|github-copilot\|opencode/claude-opus-4-6 (max) → openai\|github-copilot\|opencode/gpt-5.4 (high) → opencode-go/glm-5 → google\|github-copilot\|opencode/gemini-3.1-pro | Yes — XML-tagged, principle-driven (~300 lines vs ~1,100 Claude) |
| **Atlas** | Todo orchestrator | anthropic\|github-copilot\|opencode/claude-sonnet-4-6 → opencode-go/kimi-k2.5 → openai\|github-copilot\|opencode/gpt-5.4 (medium) → opencode-go/minimax-m2.7 | Yes - GPT-optimized todo management |
**GPT-Native Agents** (built for GPT, don't override to Claude):
| Agent | Role | Default Chain | Notes |
| -------------- | ---------------------- | -------------------------------------- | ------------------------------------------------------ |
| **Hephaestus** | Deep autonomous worker | GPT-5.4 (medium) only | "Codex on steroids." No fallback. Requires GPT access. |
| **Oracle** | Architecture/debugging | GPT-5.4 (high) → Gemini 3.1 Pro (high) → Opus (max) → GLM 5 | High-IQ strategic backup. GPT preferred. |
| **Momus** | High-accuracy reviewer | GPT-5.4 (xhigh) → Opus (max) → Gemini 3.1 Pro (high) → GLM 5 | Verification agent. GPT preferred. |
| **Oracle** | Architecture/debugging | openai\|github-copilot\|opencode/gpt-5.4 (high) → google\|github-copilot\|opencode/gemini-3.1-pro (high) → anthropic\|github-copilot\|opencode/claude-opus-4-6 (max) → opencode-go/glm-5 | High-IQ strategic backup. GPT preferred. |
| **Momus** | High-accuracy reviewer | openai\|github-copilot\|opencode/gpt-5.4 (xhigh) → anthropic\|github-copilot\|opencode/claude-opus-4-6 (max) → google\|github-copilot\|opencode/gemini-3.1-pro (high) → opencode-go/glm-5 | Verification agent. GPT preferred. |
**Utility Agents** (speed over intelligence):
@@ -349,9 +349,9 @@ These agents do search, grep, and retrieval. They intentionally use fast, cheap
| Agent | Role | Default Chain | Design Rationale |
| --------------------- | ------------------ | ---------------------------------------------------------------------- | -------------------------------------------------------------- |
| **Explore** | Fast codebase grep | Grok Code Fast → OpenCode Go MiniMax M2.7 Highspeed → OpenCode MiniMax M2.7 → Haiku → GPT-5-Nano | Speed is everything. Grok is blazing fast for grep. |
| **Librarian** | Docs/code search | OpenCode Go MiniMax M2.7 → OpenCode MiniMax M2.7 Highspeed → Haiku → GPT-5-Nano | Doc retrieval doesn't need deep reasoning. MiniMax is fast where the provider catalog supports it. |
| **Multimodal Looker** | Vision/screenshots | GPT-5.4 (medium) → Kimi K2.5 → GLM-4.6v → GPT-5-Nano | GPT-5.4 now leads the default vision path when available. |
| **Explore** | Fast codebase grep | github-copilot\|xai/grok-code-fast-1opencode-go/minimax-m2.7-highspeed → opencode/minimax-m2.7 → anthropic\|opencode/claude-haiku-4-5 → opencode/gpt-5-nano | Speed is everything. Exact runtime chain from `src/shared/model-requirements.ts`. |
| **Librarian** | Docs/code search | opencode-go/minimax-m2.7 → opencode/minimax-m2.7-highspeed → anthropic\|opencode/claude-haiku-4-5 → opencode/gpt-5-nano | Doc retrieval doesn't need deep reasoning. Exact runtime chain from `src/shared/model-requirements.ts`. |
| **Multimodal Looker** | Vision/screenshots | openai\|opencode/gpt-5.4 (medium) → opencode-go/kimi-k2.5 → zai-coding-plan/glm-4.6v → openai\|github-copilot\|opencode/gpt-5-nano | GPT-5.4 now leads the default vision path when available. |
#### Why Different Models Need Different Prompts

View File

@@ -355,29 +355,29 @@ Capability data comes from provider runtime metadata first. OmO also ships bundl
| Agent | Default Model | Provider Priority |
| --------------------- | ------------------- | ---------------------------------------------------------------------------- |
| **Sisyphus** | `claude-opus-4-6` | `claude-opus-4-6 (max)``kimi-k2.5` via OpenCode Go / Kimi providers → `gpt-5.4 (medium)``glm-5``big-pickle` |
| **Sisyphus** | `claude-opus-4-6` | `anthropic\|github-copilot\|opencode/claude-opus-4-6 (max)``opencode-go/kimi-k2.5``kimi-for-coding/k2p5``opencode\|moonshotai\|moonshotai-cn\|firmware\|ollama-cloud\|aihubmix/kimi-k2.5``openai\|github-copilot\|opencode/gpt-5.4 (medium)``zai-coding-plan\|opencode/glm-5``opencode/big-pickle` |
| **Hephaestus** | `gpt-5.4` | `gpt-5.4 (medium)` |
| **oracle** | `gpt-5.4` | `gpt-5.4 (high)``gemini-3.1-pro (high)``claude-opus-4-6 (max)``glm-5` |
| **librarian** | `minimax-m2.7` | `opencode-go/minimax-m2.7``opencode/minimax-m2.7-highspeed``claude-haiku-4-5``gpt-5-nano` |
| **explore** | `grok-code-fast-1` | `grok-code-fast-1``opencode-go/minimax-m2.7-highspeed``opencode/minimax-m2.7``claude-haiku-4-5``gpt-5-nano` |
| **multimodal-looker** | `gpt-5.4` | `gpt-5.4 (medium)``kimi-k2.5``glm-4.6v``gpt-5-nano` |
| **Prometheus** | `claude-opus-4-6` | `claude-opus-4-6 (max)``gpt-5.4 (high)``glm-5``gemini-3.1-pro` |
| **Metis** | `claude-opus-4-6` | `claude-opus-4-6 (max)``gpt-5.4 (high)``glm-5``k2p5` |
| **Momus** | `gpt-5.4` | `gpt-5.4 (xhigh)``claude-opus-4-6 (max)``gemini-3.1-pro (high)``glm-5` |
| **Atlas** | `claude-sonnet-4-6` | `claude-sonnet-4-6``kimi-k2.5``gpt-5.4 (medium)``minimax-m2.7` |
| **oracle** | `gpt-5.4` | `openai\|github-copilot\|opencode/gpt-5.4 (high)``google\|github-copilot\|opencode/gemini-3.1-pro (high)``anthropic\|github-copilot\|opencode/claude-opus-4-6 (max)``opencode-go/glm-5` |
| **librarian** | `minimax-m2.7` | `opencode-go/minimax-m2.7``opencode/minimax-m2.7-highspeed``anthropic\|opencode/claude-haiku-4-5``opencode/gpt-5-nano` |
| **explore** | `grok-code-fast-1` | `github-copilot\|xai/grok-code-fast-1``opencode-go/minimax-m2.7-highspeed``opencode/minimax-m2.7``anthropic\|opencode/claude-haiku-4-5``opencode/gpt-5-nano` |
| **multimodal-looker** | `gpt-5.4` | `openai\|opencode/gpt-5.4 (medium)``opencode-go/kimi-k2.5``zai-coding-plan/glm-4.6v``openai\|github-copilot\|opencode/gpt-5-nano` |
| **Prometheus** | `claude-opus-4-6` | `anthropic\|github-copilot\|opencode/claude-opus-4-6 (max)``openai\|github-copilot\|opencode/gpt-5.4 (high)``opencode-go/glm-5``google\|github-copilot\|opencode/gemini-3.1-pro` |
| **Metis** | `claude-opus-4-6` | `anthropic\|github-copilot\|opencode/claude-opus-4-6 (max)``openai\|github-copilot\|opencode/gpt-5.4 (high)``opencode-go/glm-5``kimi-for-coding/k2p5` |
| **Momus** | `gpt-5.4` | `openai\|github-copilot\|opencode/gpt-5.4 (xhigh)``anthropic\|github-copilot\|opencode/claude-opus-4-6 (max)``google\|github-copilot\|opencode/gemini-3.1-pro (high)``opencode-go/glm-5` |
| **Atlas** | `claude-sonnet-4-6` | `anthropic\|github-copilot\|opencode/claude-sonnet-4-6``opencode-go/kimi-k2.5``openai\|github-copilot\|opencode/gpt-5.4 (medium)``opencode-go/minimax-m2.7` |
#### Category Provider Chains
| Category | Default Model | Provider Priority |
| ---------------------- | ------------------- | -------------------------------------------------------------- |
| **visual-engineering** | `gemini-3.1-pro` | `gemini-3.1-pro``glm-5``claude-opus-4-6` |
| **ultrabrain** | `gpt-5.4` | `gpt-5.4``gemini-3.1-pro``claude-opus-4-6` |
| **deep** | `gpt-5.3-codex` | `gpt-5.3-codex``claude-opus-4-6``gemini-3.1-pro` |
| **artistry** | `gemini-3.1-pro` | `gemini-3.1-pro``claude-opus-4-6``gpt-5.4` |
| **quick** | `gpt-5.4-mini` | `gpt-5.4-mini``claude-haiku-4-5``gemini-3-flash``minimax-m2.7``gpt-5-nano` |
| **unspecified-low** | `claude-sonnet-4-6` | `claude-sonnet-4-6``gpt-5.3-codex``kimi-k2.5``gemini-3-flash``minimax-m2.7` |
| **unspecified-high** | `claude-opus-4-6` | `claude-opus-4-6``gpt-5.4 (high)``glm-5``k2p5``kimi-k2.5` |
| **writing** | `gemini-3-flash` | `gemini-3-flash``kimi-k2.5``claude-sonnet-4-6``minimax-m2.7` |
| **visual-engineering** | `gemini-3.1-pro` | `google\|github-copilot\|opencode/gemini-3.1-pro (high)``zai-coding-plan\|opencode/glm-5``anthropic\|github-copilot\|opencode/claude-opus-4-6 (max)``opencode-go/glm-5``kimi-for-coding/k2p5` |
| **ultrabrain** | `gpt-5.4` | `openai\|opencode/gpt-5.4 (xhigh)``google\|github-copilot\|opencode/gemini-3.1-pro (high)``anthropic\|github-copilot\|opencode/claude-opus-4-6 (max)``opencode-go/glm-5` |
| **deep** | `gpt-5.3-codex` | `openai\|opencode/gpt-5.3-codex (medium)``anthropic\|github-copilot\|opencode/claude-opus-4-6 (max)``google\|github-copilot\|opencode/gemini-3.1-pro (high)` |
| **artistry** | `gemini-3.1-pro` | `google\|github-copilot\|opencode/gemini-3.1-pro (high)``anthropic\|github-copilot\|opencode/claude-opus-4-6 (max)``openai\|github-copilot\|opencode/gpt-5.4` |
| **quick** | `gpt-5.4-mini` | `openai\|github-copilot\|opencode/gpt-5.4-mini``anthropic\|github-copilot\|opencode/claude-haiku-4-5``google\|github-copilot\|opencode/gemini-3-flash``opencode-go/minimax-m2.7``opencode/gpt-5-nano` |
| **unspecified-low** | `claude-sonnet-4-6` | `anthropic\|github-copilot\|opencode/claude-sonnet-4-6``openai\|opencode/gpt-5.3-codex (medium)``opencode-go/kimi-k2.5``google\|github-copilot\|opencode/gemini-3-flash``opencode-go/minimax-m2.7` |
| **unspecified-high** | `claude-opus-4-6` | `anthropic\|github-copilot\|opencode/claude-opus-4-6 (max)``openai\|github-copilot\|opencode/gpt-5.4 (high)``zai-coding-plan\|opencode/glm-5``kimi-for-coding/k2p5``opencode-go/glm-5``opencode/kimi-k2.5``opencode\|moonshotai\|moonshotai-cn\|firmware\|ollama-cloud\|aihubmix/kimi-k2.5` |
| **writing** | `gemini-3-flash` | `google\|github-copilot\|opencode/gemini-3-flash``opencode-go/kimi-k2.5``anthropic\|github-copilot\|opencode/claude-sonnet-4-6``opencode-go/minimax-m2.7` |
Run `bunx oh-my-opencode doctor --verbose` to see effective model resolution for your config.

View File

@@ -10,26 +10,26 @@ Core-agent tab cycling is deterministic via injected runtime order field. The fi
| Agent | Model | Purpose |
| --------------------- | ------------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **Sisyphus** | `claude-opus-4-6` | The default orchestrator. Plans, delegates, and executes complex tasks using specialized subagents with aggressive parallel execution. Todo-driven workflow with extended thinking (32k budget). Fallback: `glm-5``big-pickle`. |
| **Sisyphus** | `claude-opus-4-6` | The default orchestrator. Plans, delegates, and executes complex tasks using specialized subagents with aggressive parallel execution. Todo-driven workflow with extended thinking (32k budget). Fallback: `opencode-go/kimi-k2.5``kimi-for-coding/k2p5``opencode\|moonshotai\|moonshotai-cn\|firmware\|ollama-cloud\|aihubmix/kimi-k2.5``openai\|github-copilot\|opencode/gpt-5.4 (medium)``zai-coding-plan\|opencode/glm-5``opencode/big-pickle`. |
| **Hephaestus** | `gpt-5.4` | The Legitimate Craftsman. Autonomous deep worker inspired by AmpCode's deep mode. Goal-oriented execution with thorough research before action. Explores codebase patterns, completes tasks end-to-end without premature stopping. Named after the Greek god of forge and craftsmanship. Requires a GPT-capable provider. |
| **Oracle** | `gpt-5.4` | Architecture decisions, code review, debugging. Read-only consultation with stellar logical reasoning and deep analysis. Inspired by AmpCode. Fallback: `gemini-3.1-pro``claude-opus-4-6`. |
| **Librarian** | `minimax-m2.7` | Multi-repo analysis, documentation lookup, OSS implementation examples. Deep codebase understanding with evidence-based answers. Primary OpenCode Go path uses MiniMax M2.7, then falls back to OpenCode Zen `minimax-m2.7-highspeed`, then `claude-haiku-4-5` and `gpt-5-nano`. |
| **Explore** | `grok-code-fast-1` | Fast codebase exploration and contextual grep. Primary path stays on Grok Code Fast 1, then uses OpenCode Go `minimax-m2.7-highspeed`, then OpenCode Zen `minimax-m2.7`, before falling through to Haiku and GPT-5-Nano. |
| **Multimodal-Looker** | `gpt-5.4` | Visual content specialist. Analyzes PDFs, images, diagrams to extract information. Fallback: `k2p5``glm-4.6v``gpt-5-nano`. |
| **Oracle** | `gpt-5.4` | Architecture decisions, code review, debugging. Read-only consultation with stellar logical reasoning and deep analysis. Inspired by AmpCode. Fallback: `google\|github-copilot\|opencode/gemini-3.1-pro (high)``anthropic\|github-copilot\|opencode/claude-opus-4-6 (max)``opencode-go/glm-5`. |
| **Librarian** | `minimax-m2.7` | Multi-repo analysis, documentation lookup, OSS implementation examples. Deep codebase understanding with evidence-based answers. Fallback: `opencode/minimax-m2.7-highspeed``anthropic\|opencode/claude-haiku-4-5``opencode/gpt-5-nano`. |
| **Explore** | `grok-code-fast-1` | Fast codebase exploration and contextual grep. Fallback: `opencode-go/minimax-m2.7-highspeed``opencode/minimax-m2.7``anthropic\|opencode/claude-haiku-4-5``opencode/gpt-5-nano`. |
| **Multimodal-Looker** | `gpt-5.4` | Visual content specialist. Analyzes PDFs, images, diagrams to extract information. Fallback: `opencode-go/kimi-k2.5``zai-coding-plan/glm-4.6v``openai\|github-copilot\|opencode/gpt-5-nano`. |
### Planning Agents
| Agent | Model | Purpose |
| -------------- | ----------------- | -------------------------------------------------------------------------------------------------------------------------------------------------- |
| **Prometheus** | `claude-opus-4-6` | Strategic planner with interview mode. Creates detailed work plans through iterative questioning. Fallback: `gpt-5.4``gemini-3.1-pro`. |
| **Metis** | `claude-opus-4-6` | Plan consultant — pre-planning analysis. Identifies hidden intentions, ambiguities, and AI failure points. Fallback: `gpt-5.4``gemini-3.1-pro`. |
| **Momus** | `gpt-5.4` | Plan reviewer — validates plans against clarity, verifiability, and completeness standards. Fallback: `claude-opus-4-6``gemini-3.1-pro`. |
| **Prometheus** | `claude-opus-4-6` | Strategic planner with interview mode. Creates detailed work plans through iterative questioning. Fallback: `openai\|github-copilot\|opencode/gpt-5.4 (high)``opencode-go/glm-5``google\|github-copilot\|opencode/gemini-3.1-pro`. |
| **Metis** | `claude-opus-4-6` | Plan consultant — pre-planning analysis. Identifies hidden intentions, ambiguities, and AI failure points. Fallback: `openai\|github-copilot\|opencode/gpt-5.4 (high)``opencode-go/glm-5``kimi-for-coding/k2p5`. |
| **Momus** | `gpt-5.4` | Plan reviewer — validates plans against clarity, verifiability, and completeness standards. Fallback: `anthropic\|github-copilot\|opencode/claude-opus-4-6 (max)``google\|github-copilot\|opencode/gemini-3.1-pro (high)``opencode-go/glm-5`. |
### Orchestration Agents
| Agent | Model | Purpose |
| ------------------- | ---------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **Atlas** | `claude-sonnet-4-6` | Todo-list orchestrator. Executes planned tasks systematically, managing todo items and coordinating work. Fallback: `gpt-5.4` (medium). |
| **Sisyphus-Junior** | _(category-dependent)_ | Category-spawned executor. Model is selected automatically based on the task category (visual-engineering, quick, deep, etc.). Used when the main agent delegates work via the `task` tool. |
| **Atlas** | `claude-sonnet-4-6` | Todo-list orchestrator. Executes planned tasks systematically, managing todo items and coordinating work. Fallback: `opencode-go/kimi-k2.5``openai\|github-copilot\|opencode/gpt-5.4 (medium)``opencode-go/minimax-m2.7`. |
| **Sisyphus-Junior** | _(category-dependent)_ | Category-spawned executor. Model is selected automatically based on the task category (visual-engineering, quick, deep, etc.). Its built-in general fallback chain is `anthropic\|github-copilot\|opencode/claude-sonnet-4-6``opencode-go/kimi-k2.5``openai\|github-copilot\|opencode/gpt-5.4 (medium)``opencode-go/minimax-m2.7``opencode/big-pickle`. |
### Invoking Agents

View File

@@ -110,7 +110,7 @@ describe("detectCompletionInSessionMessages", () => {
})
describe("#given promise appears in tool_result part (not text part)", () => {
test("#when Oracle returns VERIFIED via task() tool_result #then should detect completion", async () => {
test("#when Oracle returns VERIFIED via task() tool_result #then should NOT detect completion", async () => {
const messages: SessionMessage[] = [
{
info: { role: "assistant" },
@@ -137,10 +137,10 @@ describe("detectCompletionInSessionMessages", () => {
sinceMessageIndex: 0,
})
expect(detected).toBe(true)
expect(detected).toBe(false)
})
test("#when DONE appears only in tool_result part #then should detect completion", async () => {
test("#when DONE appears only in tool_result part #then should NOT detect completion", async () => {
const messages: SessionMessage[] = [
{
info: { role: "assistant" },
@@ -159,7 +159,7 @@ describe("detectCompletionInSessionMessages", () => {
directory: "/tmp",
})
expect(detected).toBe(true)
expect(detected).toBe(false)
})
test("#when promise appears in tool_use part (not tool_result) #then should NOT detect completion", async () => {

View File

@@ -63,6 +63,7 @@ export function detectCompletionInTranscript(
try {
const entry = JSON.parse(line) as TranscriptEntry
if (entry.type === "user") continue
if (entry.type !== "assistant" && entry.type !== "text") continue
if (startedAt && entry.timestamp && entry.timestamp < startedAt) continue
const entryText = extractTranscriptEntryText(entry)
if (!entryText) continue
@@ -128,7 +129,7 @@ export async function detectCompletionInSessionMessages(
let responseText = ""
for (const part of assistant.parts) {
if (part.type !== "text" && part.type !== "tool_result") continue
if (part.type !== "text") continue
responseText += `${responseText ? "\n" : ""}${part.text ?? ""}`
}

View File

@@ -562,7 +562,7 @@ describe("ralph-loop", () => {
})
hook.startLoop("session-123", "Build something", { completionPromise: "COMPLETE" })
writeFileSync(transcriptPath, JSON.stringify({ type: "tool_result", tool_name: "write", tool_output: { output: "Task done <promise>COMPLETE</promise>" } }) + "\n")
writeFileSync(transcriptPath, JSON.stringify({ type: "assistant", content: "Task done <promise>COMPLETE</promise>" }) + "\n")
// when - session goes idle (transcriptPath now derived from sessionID via getTranscriptPath)
await hook.event({
@@ -1020,7 +1020,7 @@ Original task: Build something`
expect(hook.getState()?.iteration).toBe(2)
})
test("should detect completion from tool_result entry in transcript", async () => {
test("should NOT detect completion from tool_result entry in transcript", async () => {
// given - transcript contains a tool_result with completion promise
const transcriptPath = join(TEST_DIR, "transcript.jsonl")
const toolResultEntry = JSON.stringify({
@@ -1044,16 +1044,15 @@ Original task: Build something`
},
})
// then - loop should complete (tool_result contains actual completion output)
expect(promptCalls.length).toBe(0)
expect(toastCalls.some((t) => t.title === "Ralph Loop Complete!")).toBe(true)
expect(hook.getState()).toBeNull()
expect(promptCalls.length).toBe(1)
expect(toastCalls.some((t) => t.title === "Ralph Loop Complete!")).toBe(false)
expect(hook.getState()?.iteration).toBe(2)
})
test("should check transcript BEFORE API to optimize performance", async () => {
// given - transcript has completion promise
const transcriptPath = join(TEST_DIR, "transcript.jsonl")
writeFileSync(transcriptPath, JSON.stringify({ type: "tool_result", tool_name: "write", tool_output: { output: "<promise>DONE</promise>" } }) + "\n")
writeFileSync(transcriptPath, JSON.stringify({ type: "assistant", content: "<promise>DONE</promise>" }) + "\n")
mockSessionMessages = [
{ info: { role: "assistant" }, parts: [{ type: "text", text: "No promise here" }] },
]
@@ -1083,7 +1082,7 @@ Original task: Build something`
const hook = createRalphLoopHook(createMockPluginInput(), {
getTranscriptPath: () => transcriptPath,
})
writeFileSync(transcriptPath, JSON.stringify({ type: "tool_result", tool_name: "write", tool_output: { output: "<promise>DONE</promise>" } }) + "\n")
writeFileSync(transcriptPath, JSON.stringify({ type: "assistant", content: "<promise>DONE</promise>" }) + "\n")
hook.startLoop("test-id", "Build API", { ultrawork: true })
// when - idle event triggered
@@ -1100,7 +1099,7 @@ Original task: Build something`
const hook = createRalphLoopHook(createMockPluginInput(), {
getTranscriptPath: () => transcriptPath,
})
writeFileSync(transcriptPath, JSON.stringify({ type: "tool_result", tool_name: "write", tool_output: { output: "<promise>DONE</promise>" } }) + "\n")
writeFileSync(transcriptPath, JSON.stringify({ type: "assistant", content: "<promise>DONE</promise>" }) + "\n")
hook.startLoop("test-id", "Build API")
// when - idle event triggered

View File

@@ -75,11 +75,11 @@ describe("ulw-loop verification", () => {
await hook.event({ event: { type: "session.idle", properties: { sessionID: "session-123" } } })
expect(hook.getState()?.verification_pending).toBe(true)
expect(hook.getState()?.completion_promise).toBe(ULTRAWORK_VERIFICATION_PROMISE)
expect(hook.getState()?.verification_session_id).toBeUndefined()
expect(hook.getState()?.verification_pending).toBeUndefined()
expect(hook.getState()?.completion_promise).toBe("DONE")
expect(hook.getState()?.iteration).toBe(2)
expect(promptCalls).toHaveLength(1)
expect(promptCalls[0].text).toContain('task(subagent_type="oracle"')
expect(promptCalls[0].text).not.toContain('task(subagent_type="oracle"')
expect(toastCalls.some((toast) => toast.title === "ULTRAWORK LOOP COMPLETE!")).toBe(false)
})
@@ -90,7 +90,7 @@ describe("ulw-loop verification", () => {
hook.startLoop("session-123", "Build API", { ultrawork: true })
writeFileSync(
parentTranscriptPath,
`${JSON.stringify({ type: "tool_result", timestamp: new Date().toISOString(), tool_output: { output: "done <promise>DONE</promise>" } })}\n`,
`${JSON.stringify({ type: "assistant", timestamp: new Date().toISOString(), content: "done <promise>DONE</promise>" })}\n`,
)
await hook.event({ event: { type: "session.idle", properties: { sessionID: "session-123" } } })
@@ -100,7 +100,7 @@ describe("ulw-loop verification", () => {
})
writeFileSync(
oracleTranscriptPath,
`${JSON.stringify({ type: "tool_result", timestamp: new Date().toISOString(), tool_output: { output: `verified <promise>${ULTRAWORK_VERIFICATION_PROMISE}</promise>` } })}\n`,
`${JSON.stringify({ type: "assistant", timestamp: new Date().toISOString(), content: `verified <promise>${ULTRAWORK_VERIFICATION_PROMISE}</promise>` })}\n`,
)
await hook.event({ event: { type: "session.idle", properties: { sessionID: "session-123" } } })
@@ -116,7 +116,7 @@ describe("ulw-loop verification", () => {
hook.startLoop("session-123", "Build API", { ultrawork: true })
writeFileSync(
parentTranscriptPath,
`${JSON.stringify({ type: "tool_result", timestamp: new Date().toISOString(), tool_output: { output: "done <promise>DONE</promise>" } })}\n`,
`${JSON.stringify({ type: "assistant", timestamp: new Date().toISOString(), content: "done <promise>DONE</promise>" })}\n`,
)
await hook.event({ event: { type: "session.idle", properties: { sessionID: "session-123" } } })
@@ -126,7 +126,7 @@ describe("ulw-loop verification", () => {
})
writeFileSync(
oracleTranscriptPath,
`${JSON.stringify({ type: "tool_result", timestamp: new Date().toISOString(), tool_output: { output: `verified <promise>${ULTRAWORK_VERIFICATION_PROMISE}</promise>` } })}\n`,
`${JSON.stringify({ type: "assistant", timestamp: new Date().toISOString(), content: `verified <promise>${ULTRAWORK_VERIFICATION_PROMISE}</promise>` })}\n`,
)
await hook.event({ event: { type: "session.idle", properties: { sessionID: "ses-oracle" } } })
@@ -142,7 +142,7 @@ describe("ulw-loop verification", () => {
hook.startLoop("session-123", "Build API", { ultrawork: true })
writeFileSync(
parentTranscriptPath,
`${JSON.stringify({ type: "tool_result", timestamp: new Date().toISOString(), tool_output: { output: "done <promise>DONE</promise>" } })}\n`,
`${JSON.stringify({ type: "assistant", timestamp: new Date().toISOString(), content: "done <promise>DONE</promise>" })}\n`,
)
await hook.event({ event: { type: "session.idle", properties: { sessionID: "session-123" } } })
@@ -166,7 +166,7 @@ describe("ulw-loop verification", () => {
hook.startLoop("session-123", "Build API", { ultrawork: true })
writeFileSync(
parentTranscriptPath,
`${JSON.stringify({ type: "tool_result", timestamp: new Date().toISOString(), tool_output: { output: "done <promise>DONE</promise>" } })}\n`,
`${JSON.stringify({ type: "assistant", timestamp: new Date().toISOString(), content: "done <promise>DONE</promise>" })}\n`,
)
await hook.event({ event: { type: "session.idle", properties: { sessionID: "session-123" } } })
@@ -213,7 +213,7 @@ describe("ulw-loop verification", () => {
hook.startLoop("session-123", "Build API", { ultrawork: true })
writeFileSync(
parentTranscriptPath,
`${JSON.stringify({ type: "tool_result", timestamp: new Date().toISOString(), tool_output: { output: "done <promise>DONE</promise>" } })}\n`,
`${JSON.stringify({ type: "assistant", timestamp: new Date().toISOString(), content: "done <promise>DONE</promise>" })}\n`,
)
await hook.event({ event: { type: "session.idle", properties: { sessionID: "session-123" } } })
@@ -256,7 +256,7 @@ describe("ulw-loop verification", () => {
test("#given prior transcript completion from older run #when new ulw loop starts #then old completion is ignored", async () => {
writeFileSync(
parentTranscriptPath,
`${JSON.stringify({ type: "tool_result", timestamp: "2000-01-01T00:00:00.000Z", tool_output: { output: "old <promise>DONE</promise>" } })}\n`,
`${JSON.stringify({ type: "assistant", timestamp: "2000-01-01T00:00:00.000Z", content: "old <promise>DONE</promise>" })}\n`,
)
const hook = createRalphLoopHook(createMockPluginInput(), {
getTranscriptPath: (sessionID) => sessionID === "ses-oracle" ? oracleTranscriptPath : parentTranscriptPath,
@@ -277,7 +277,7 @@ describe("ulw-loop verification", () => {
hook.startLoop("session-123", "Build API", { ultrawork: true })
writeFileSync(
parentTranscriptPath,
`${JSON.stringify({ type: "tool_result", timestamp: new Date().toISOString(), tool_output: { output: "done <promise>DONE</promise>" } })}\n`,
`${JSON.stringify({ type: "assistant", timestamp: new Date().toISOString(), content: "done <promise>DONE</promise>" })}\n`,
)
await hook.event({ event: { type: "session.idle", properties: { sessionID: "session-123" } } })
@@ -295,7 +295,7 @@ describe("ulw-loop verification", () => {
hook.startLoop("session-123", "Build API", { ultrawork: true })
writeFileSync(
parentTranscriptPath,
`${JSON.stringify({ type: "tool_result", timestamp: new Date().toISOString(), tool_output: { output: "done <promise>DONE</promise>" } })}\n`,
`${JSON.stringify({ type: "assistant", timestamp: new Date().toISOString(), content: "done <promise>DONE</promise>" })}\n`,
)
await hook.event({ event: { type: "session.idle", properties: { sessionID: "session-123" } } })
@@ -314,7 +314,7 @@ describe("ulw-loop verification", () => {
hook.startLoop("session-123", "Build API", { ultrawork: true })
writeFileSync(
parentTranscriptPath,
`${JSON.stringify({ type: "tool_result", timestamp: new Date().toISOString(), tool_output: { output: "done <promise>DONE</promise>" } })}\n`,
`${JSON.stringify({ type: "assistant", timestamp: new Date().toISOString(), content: "done <promise>DONE</promise>" })}\n`,
)
await hook.event({ event: { type: "session.idle", properties: { sessionID: "session-123" } } })
@@ -325,7 +325,7 @@ describe("ulw-loop verification", () => {
hook.startLoop("session-456", "Ship CLI", { ultrawork: true })
writeFileSync(
oracleTranscriptPath,
`${JSON.stringify({ type: "tool_result", timestamp: new Date().toISOString(), tool_output: { output: `verified <promise>${ULTRAWORK_VERIFICATION_PROMISE}</promise>` } })}\n`,
`${JSON.stringify({ type: "assistant", timestamp: new Date().toISOString(), content: `verified <promise>${ULTRAWORK_VERIFICATION_PROMISE}</promise>` })}\n`,
)
await hook.event({ event: { type: "session.idle", properties: { sessionID: "ses-oracle-old" } } })
@@ -343,7 +343,7 @@ describe("ulw-loop verification", () => {
hook.startLoop("session-123", "Build API", { ultrawork: true })
writeFileSync(
parentTranscriptPath,
`${JSON.stringify({ type: "tool_result", timestamp: new Date().toISOString(), tool_output: { output: "done <promise>DONE</promise>" } })}\n`,
`${JSON.stringify({ type: "assistant", timestamp: new Date().toISOString(), content: "done <promise>DONE</promise>" })}\n`,
)
await hook.event({ event: { type: "session.idle", properties: { sessionID: "session-123" } } })
@@ -354,7 +354,7 @@ describe("ulw-loop verification", () => {
hook.startLoop("session-123", "Restarted task", { ultrawork: true })
writeFileSync(
oracleTranscriptPath,
`${JSON.stringify({ type: "tool_result", timestamp: new Date().toISOString(), tool_output: { output: `verified <promise>${ULTRAWORK_VERIFICATION_PROMISE}</promise>` } })}\n`,
`${JSON.stringify({ type: "assistant", timestamp: new Date().toISOString(), content: `verified <promise>${ULTRAWORK_VERIFICATION_PROMISE}</promise>` })}\n`,
)
await hook.event({ event: { type: "session.idle", properties: { sessionID: "ses-oracle-old" } } })
@@ -373,13 +373,13 @@ describe("ulw-loop verification", () => {
hook.startLoop("session-123", "Build API", { ultrawork: true })
writeFileSync(
parentTranscriptPath,
`${JSON.stringify({ type: "tool_result", timestamp: new Date().toISOString(), tool_output: { output: "done <promise>DONE</promise>" } })}\n`,
`${JSON.stringify({ type: "assistant", timestamp: new Date().toISOString(), content: "done <promise>DONE</promise>" })}\n`,
)
await hook.event({ event: { type: "session.idle", properties: { sessionID: "session-123" } } })
writeFileSync(
parentTranscriptPath,
`${JSON.stringify({ type: "tool_result", timestamp: new Date().toISOString(), tool_output: { output: "done <promise>DONE</promise>" } })}\n${JSON.stringify({ type: "tool_result", timestamp: new Date().toISOString(), tool_output: { output: `verified <promise>${ULTRAWORK_VERIFICATION_PROMISE}</promise>` } })}\n`,
`${JSON.stringify({ type: "assistant", timestamp: new Date().toISOString(), content: "done <promise>DONE</promise>" })}\n${JSON.stringify({ type: "assistant", timestamp: new Date().toISOString(), content: `verified <promise>${ULTRAWORK_VERIFICATION_PROMISE}</promise>` })}\n`,
)
await hook.event({ event: { type: "session.idle", properties: { sessionID: "session-123" } } })
@@ -409,7 +409,7 @@ describe("ulw-loop verification", () => {
hook.startLoop("session-123", "Build API", { ultrawork: true })
writeFileSync(
parentTranscriptPath,
`${JSON.stringify({ type: "tool_result", timestamp: new Date().toISOString(), tool_output: { output: "done <promise>DONE</promise>" } })}\n`,
`${JSON.stringify({ type: "assistant", timestamp: new Date().toISOString(), content: "done <promise>DONE</promise>" })}\n`,
)
await hook.event({ event: { type: "session.idle", properties: { sessionID: "session-123" } } })
@@ -449,7 +449,7 @@ describe("ulw-loop verification", () => {
hook.startLoop("session-123", "Build API", { ultrawork: true })
writeFileSync(
parentTranscriptPath,
`${JSON.stringify({ type: "tool_result", timestamp: new Date().toISOString(), tool_output: { output: "done <promise>DONE</promise>" } })}\n`,
`${JSON.stringify({ type: "assistant", timestamp: new Date().toISOString(), content: "done <promise>DONE</promise>" })}\n`,
)
await hook.event({ event: { type: "session.idle", properties: { sessionID: "session-123" } } })
@@ -467,7 +467,7 @@ describe("ulw-loop verification", () => {
writeFileSync(
parentTranscriptPath,
`${JSON.stringify({ type: "tool_result", timestamp: new Date().toISOString(), tool_output: { output: "fixed it <promise>DONE</promise>" } })}\n`,
`${JSON.stringify({ type: "assistant", timestamp: new Date().toISOString(), content: "fixed it <promise>DONE</promise>" })}\n`,
)
writeState(testDir, {
...hook.getState()!,

View File

@@ -621,7 +621,7 @@ describe("skill tool - nativeSkills integration", () => {
const tool = createSkillTool({
skills: [createMockSkill("seeded-skill")],
nativeSkills: {
async all() {
all() {
return [{
name: "native-visible-skill",
description: "Native skill exposed from config",
@@ -629,13 +629,14 @@ describe("skill tool - nativeSkills integration", () => {
content: "Native visible skill body",
}]
},
async get() { return undefined },
async dirs() { return [] },
get() { return undefined },
dirs() { return [] },
},
})
//#when
expect(tool.description).toContain("seeded-skill")
expect(tool.description).toContain("native-visible-skill")
await tool.execute({ name: "native-visible-skill" }, mockContext)
//#then

View File

@@ -11,6 +11,13 @@ import { sanitizeJsonSchema } from "../../plugin/normalize-tool-arg-schemas"
import { discoverCommandsSync } from "../slashcommand/command-discovery"
import type { CommandInfo } from "../slashcommand/types"
import { formatLoadedCommand } from "../slashcommand/command-output-formatter"
type NativeSkillEntry = {
name: string
description: string
location: string
content: string
}
// Priority: project > user > opencode/opencode-project > builtin/config
const scopePriority: Record<string, number> = {
project: 4,
@@ -35,6 +42,46 @@ function loadedSkillToInfo(skill: LoadedSkill): SkillInfo {
}
}
function nativeSkillToLoadedSkill(native: NativeSkillEntry): LoadedSkill {
return {
name: native.name,
path: native.location,
definition: {
name: native.name,
description: native.description,
template: native.content,
},
scope: "config",
}
}
function mergeNativeSkills(skills: LoadedSkill[], nativeSkills: NativeSkillEntry[]): void {
const knownNames = new Set(skills.map(skill => skill.name))
for (const native of nativeSkills) {
if (knownNames.has(native.name)) continue
skills.push(nativeSkillToLoadedSkill(native))
knownNames.add(native.name)
}
}
function mergeNativeSkillInfos(skillInfos: SkillInfo[], nativeSkills: NativeSkillEntry[]): void {
const knownNames = new Set(skillInfos.map(skill => skill.name))
for (const native of nativeSkills) {
if (knownNames.has(native.name)) continue
skillInfos.push({
name: native.name,
description: native.description,
location: native.location,
scope: "config",
})
knownNames.add(native.name)
}
}
function isPromiseLike<T>(value: T | Promise<T>): value is Promise<T> {
return typeof value === "object" && value !== null && "then" in value
}
function formatCombinedDescription(skills: SkillInfo[], commands: CommandInfo[]): string {
const lines: string[] = []
@@ -200,22 +247,9 @@ export function createSkillTool(options: SkillLoadOptions = {}): ToolDefinition
: [...discovered, ...options.skills.filter(s => !new Set(discovered.map(d => d.name)).has(s.name))]
if (options.nativeSkills) {
const knownNames = new Set(allSkills.map(s => s.name))
try {
const nativeAll = await options.nativeSkills.all()
for (const native of nativeAll) {
if (knownNames.has(native.name)) continue
allSkills.push({
name: native.name,
path: native.location,
definition: {
name: native.name,
description: native.description,
template: native.content,
},
scope: "config",
})
}
mergeNativeSkills(allSkills, nativeAll)
} catch {
// Native skill discovery may not be available
}
@@ -243,8 +277,23 @@ export function createSkillTool(options: SkillLoadOptions = {}): ToolDefinition
if (options.skills !== undefined) {
const skillInfos = options.skills.map(loadedSkillToInfo)
const commandsForDescription = options.commands ?? []
cachedDescription = formatCombinedDescription(skillInfos, commandsForDescription)
let needsAsyncRefresh = false
if (options.nativeSkills) {
try {
const nativeAll = options.nativeSkills.all()
if (isPromiseLike(nativeAll)) {
needsAsyncRefresh = true
} else {
mergeNativeSkillInfos(skillInfos, nativeAll)
}
} catch {
// Native skill discovery may not be available
}
}
cachedDescription = formatCombinedDescription(skillInfos, commandsForDescription)
if (needsAsyncRefresh) {
void buildDescription()
}
} else if (options.commands !== undefined) {

View File

@@ -41,8 +41,8 @@ export interface SkillLoadOptions {
enabledPluginsOverride?: Record<string, boolean>
/** Native skill accessor from PluginInput for discovering skills registered via config.skills.paths */
nativeSkills?: {
all(): Promise<{ name: string; description: string; location: string; content: string }[]>
get(name: string): Promise<{ name: string; description: string; location: string; content: string } | undefined>
dirs(): Promise<string[]>
all(): { name: string; description: string; location: string; content: string }[] | Promise<{ name: string; description: string; location: string; content: string }[]>
get(name: string): { name: string; description: string; location: string; content: string } | undefined | Promise<{ name: string; description: string; location: string; content: string } | undefined>
dirs(): string[] | Promise<string[]>
}
}