pantheon-opencode 1.4.1 → 1.4.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -2,7 +2,7 @@
2
2
  <h1 align="center">Pantheon</h1>
3
3
 
4
4
  <p align="center">
5
- <a href="CHANGELOG.md"><img src="https://img.shields.io/badge/version-v1.4.1--candidate-blue" alt="Version"></a>
5
+ <a href="CHANGELOG.md"><img src="https://img.shields.io/badge/version-v1.4.2-blue" alt="Version"></a>
6
6
  <a href="LICENSE"><img src="https://img.shields.io/badge/license-MIT-blue" alt="License"></a>
7
7
  <a href="docs/agents/README.md"><img src="https://img.shields.io/badge/agents-14-purple" alt="Agents"></a>
8
8
  <a href="src/skills/README.md"><img src="https://img.shields.io/badge/skills-21-orange" alt="Skills"></a>
@@ -225,7 +225,7 @@ via three delegation tools, tracked on a persistent job board
225
225
 
226
226
  | Tool | Signature | Behavior |
227
227
  |------|-----------|----------|
228
- | `pantheon_delegate` | `{prompt, agent, description?, read_only?}` | Creates a child session (parentID = caller), registers it on the job board, arms a 15-minute timeout, and fire-and-forgets `promptAsync` on the child. Returns the readable alias (e.g. `apo-1`). Only root sessions may delegate (sub-sessions are rejected by the depth guard). |
228
+ | `pantheon_delegate` | `{prompt, agent, description?, read_only?, model?}` | Creates a child session (parentID = caller), registers it on the job board, arms a 15-minute timeout, and sends `promptAsync` to the child. `model` is optional and uses `provider/model-id`. Returns the readable alias (e.g. `apo-1`). Only root sessions may delegate (sub-sessions are rejected by the depth guard). |
229
229
  | `pantheon_delegation_read` | `{id}` | Blocks (`waitForTerminal`, up to 15 min) until the delegation reaches a terminal state, returns the report markdown, and marks the job reconciled. Resolves by alias or task ID. |
230
230
  | `pantheon_delegation_list` | `{}` | Lists the current session's delegations with `[unread]` on finished, unreconciled jobs. |
231
231
 
@@ -277,6 +277,22 @@ agent in `background_delegation.read_only_agents` (`apollo`, `gaia`), registers
277
277
  the child session as read-only — the `tool.execute.before` guard denies
278
278
  `edit`/`write`/`bash`/`task` inside that session.
279
279
 
280
+ **Delegated model resolution:** the optional child model is resolved in this
281
+ order: (1) explicit `model` on `pantheon_delegate`; (2) the target agent's
282
+ model in the active preset; (3) the current model of the parent session; (4)
283
+ no model, allowing OpenCode's native inheritance. `small_model` is never used
284
+ for delegates. When a model resolves, the same `provider/model-id` is sent as
285
+ `{providerID, id}` to both `session.create` and `promptAsync`, including the
286
+ single safe bootstrap retry; the retry does not select a different model. If
287
+ the resolution lookup yields no model, the model field is omitted and the
288
+ delegation continues with native inheritance.
289
+
290
+ Model availability is not inferred from the string. The provider must exist in
291
+ OpenCode and the referenced model must be available through that provider's
292
+ account, subscription, or configured endpoint; otherwise the child host may
293
+ reject startup. An active preset supplies provider configuration and model
294
+ mappings, but does not make an unavailable provider or model available.
295
+
280
296
  **R1 — Per-error-type retry + provider cooldown (LiteLLM pattern):**
281
297
  `retry_policy` in `src/routing.yml` maps an error class (`auth`, `rate_limit`,
282
298
  `timeout`, `other`) to max retries before escalating — `auth` is never retried
@@ -641,112 +657,211 @@ on demand to perform specialized tasks. Skills are organized into domains:
641
657
  ## Model Tiers & Presets
642
658
 
643
659
  Pantheon agents declare abstract model tiers (`fast` / `default` / `coding` / `premium`) rather than
644
- hardcoded model names. The actual model resolved for each tier depends on your platform
645
- subscription (OpenCode Go, Copilot Pro, Claude Pro, etc.).
660
+ hardcoded model names. The actual model resolved for each tier depends on your chosen **preset**
661
+ and is pinned to concrete `provider/model` IDs in [src/routing.yml](src/routing.yml).
646
662
 
647
- | Tier | Purpose | Agents | Typical Models |
663
+ | Tier | Purpose | Agents | Typical Models (2026) |
648
664
  |------|---------|--------|----------------|
649
- | `premium` | Deep reasoning, critical | Zeus, Athena, Themis | DeepSeek V4 Pro, Claude Opus, o3 |
650
- | `default` | Balanced quality/speed | Hermes, Aphrodite, Demeter, Prometheus, Hephaestus, Gaia | Kimi K2.6, Claude Sonnet, GPT-4o |
651
- | `coding` | Heavy coding tasks | Hermes, Aphrodite, Demeter, Prometheus, Hephaestus, Talos | DeepSeek V4 Flash, Claude Sonnet |
652
- | `fast` | Quick, cheap ops | Apollo, Iris, Mnemosyne, Talos, Nyx | DeepSeek V4 Flash, MiniMax M2.7, Gemini Flash |
665
+ | `premium` | Deep reasoning, critical | Zeus, Athena, Themis | gpt-5.6-sol, qwen3.8-max, deepseek-v4-pro |
666
+ | `default` | Balanced quality/speed | Hermes, Aphrodite, Demeter, Prometheus, Hephaestus, Gaia | gpt-5.6-terra, kimi-k3, deepseek-v4-flash |
667
+ | `coding` | Heavy coding tasks | Hermes, Aphrodite, Demeter, Prometheus, Hephaestus, Talos | gpt-5.6-terra, deepseek-v4-flash, mimo-v2.5 |
668
+ | `fast` | Quick, cheap ops | Apollo, Iris, Mnemosyne, Talos, Nyx | gpt-5.6-luna-fast, mimo-v2.5-free, gpt-5.6-luna |
653
669
 
654
670
  ---
655
671
 
656
672
  ## Model Routing Presets
657
673
 
658
- The abstract tiers above are pinned to concrete `provider/model` IDs by **6 built-in model presets**. Each preset maps all 14 agents to a specific model plus a reasoning effort per role (planners/reviewers, implementers, scouts). **Zero-mutation default**: without an active preset, agents run on OpenCode's default model — behavior is unchanged.
674
+ The abstract tiers above are pinned to concrete `provider/model` IDs by **4 built-in model presets**.
675
+ Cada preset mapeia os 14 agentes para um modelo específico + `reasoning_effort` por papel (planners/reviewers, implementers, scouts).
676
+
677
+ > **Default = herdar do chat (sem `active-preset.json`)** — sem preset ativo, os delegates **herdam nativamente** o modelo do chat pai ([herança nativa](src/pantheon/delegation.ts)). O instalador **não grava** `active-preset.json` quando você escolhe `0`/`inherit` no wizard (ou não seleciona preset via `--preset`). A resolução do modelo delegado segue: `explicit model` em `pantheon_delegate` > modelo do agente-alvo no preset ativo > modelo atual da sessão pai > herança nativa (sem `model`, sem `small_model`). `small_model` nunca é usado para delegates.
678
+
679
+ Tabelas abaixo são **geradas a partir de [src/routing.yml](src/routing.yml)** — rode `node scripts/generate-preset-docs.mjs` para regenerar. Nenhum segredo é hardcoded: apenas nomes de env vars (`PANTHEON_OPENCODE_API_KEY`, `OPENAI_API_KEY`) e `baseURL`s.
680
+
681
+ <!-- Generated from src/routing.yml — run: node scripts/generate-preset-docs.mjs -->
682
+ ### Presets — resumo (provider / baseURL / key / pricing / vision)
659
683
 
660
- ### Presets
684
+ | Preset | Provider / BaseURL | Key env (nome) | Pricing 2026 | Vision (fallback) | Papéis / Modelos |
685
+ |---|---|---|---|---|---|
686
+ | `go-free` | `opencode` `https://opencode.ai/zen/v1` (env: `PANTHEON_OPENCODE_API_KEY`) | `PANTHEON_OPENCODE_API_KEY` | Gratuito — Zen free, sem custo, quota limitada, só modelos `-free` | `opencode/mimo-v2.5-free` [low] 👁️ vision | zeus: `opencode/big-pickle` [medium]<br>athena: `opencode/nemotron-3-super-free` [high]<br>themis: `opencode/qwen3.6-plus-free` [high]<br>hermes,aphrodite,demeter,prometheus,hephaestus: `opencode/deepseek-v4-flash-free` [medium]<br>apollo,nyx,gaia,iris,mnemosyne,talos: `opencode/mimo-v2.5-free` [low] |
687
+ | `go-fast` | `opencode-go` `https://opencode.ai/zen/go/v1` (env: `PANTHEON_OPENCODE_API_KEY`) | `PANTHEON_OPENCODE_API_KEY` | Baixo custo — Go gateway, baixa latência (kimi-k2.7-code / glm-5.3-flash) | `opencode-go/mimo-v2.5` [low] 👁️ vision | athena: `opencode-go/kimi-k2.7-code` [high]<br>themis: `opencode-go/glm-5.3-flash` [high]<br>zeus: `opencode-go/kimi-k2.7-code` [medium]<br>hermes,prometheus: `opencode-go/deepseek-v4-flash` [medium]<br>aphrodite,hephaestus: `opencode-go/mimo-v2.5` [low]<br>demeter: `opencode-go/gpt-5.6-luna` [low]<br>apollo,nyx,gaia,iris,mnemosyne,talos: `opencode-go/gpt-5.6-luna-fast` [low] |
688
+ | `go-premium` | `opencode-go` `https://opencode.ai/zen/go/v1` (env: `PANTHEON_OPENCODE_API_KEY`) | `PANTHEON_OPENCODE_API_KEY` | Premium — Go gateway, melhor qualidade (gpt-5.6-sol / qwen3.8-max / deepseek-v4-pro) | `opencode-go/gpt-5.6-sol` [high] 👁️ vision | zeus: `opencode-go/gpt-5.6-sol` [medium]<br>athena: `opencode-go/deepseek-v4-pro` [high]<br>themis: `opencode-go/qwen3.8-max` [high]<br>hermes,demeter,hephaestus: `opencode-go/gpt-5.6-terra` [medium]<br>aphrodite,prometheus: `opencode-go/kimi-k3` [medium]<br>apollo,nyx,gaia,iris,mnemosyne,talos: `opencode-go/gpt-5.6-luna` [low] |
689
+ | `openai` | `openai` `https://api.openai.com/v1` (env: `OPENAI_API_KEY`) | `OPENAI_API_KEY` | Pago (OpenAI) — Direto OpenAI, `gpt-5.6` family | `openai/gpt-5.6-sol` [high] 👁️ vision | athena,themis,zeus: `openai/gpt-5.6-sol` [high]<br>hermes,aphrodite,demeter,prometheus,hephaestus: `openai/gpt-5.6-luna` [high]<br>apollo,nyx,gaia,iris,mnemosyne,talos: `openai/gpt-5.6-luna-fast` [low] |
661
690
 
662
- | Preset | Provider | Planners/Reviewers | Implementers | Scouts |
663
- |--------|----------|--------------------|--------------|--------|
664
- | `go-deepseek` | opencode (Zen) | `opencode/deepseek-v4-pro` | `opencode/deepseek-v4-flash` | `opencode/deepseek-v4-flash-free` |
665
- | `go-fast` | opencode-go | `opencode-go/deepseek-v4-flash` — all 14 agents, effort `low` | | |
666
- | `go-claude` | anthropic | `anthropic/claude-opus-4-8` | `anthropic/claude-sonnet-5` | `anthropic/claude-haiku-4-5` |
667
- | `go-openai` | openai | `openai/gpt-5.6-sol` | `openai/gpt-5.6-terra` | `openai/gpt-5.6-luna-fast` |
668
- | `go-premium` | opencode-go | GLM-5.1 (zeus), DeepSeek V4 Pro (athena), Qwen3.7 Max (themis) | MiniMax M2.7 (hermes, hephaestus), Kimi K2.6 (aphrodite), Qwen3.7 Plus (demeter), GLM-5.2 (prometheus) | DeepSeek V4 Flash (scouts) |
669
- | `go-free` | opencode (Zen free, $0) | Big Pickle (zeus), Nemotron 3 Ultra Free (athena) | DeepSeek V4 Flash Free | North Mini Code Free (scouts) |
691
+ > **Pricing 2026 verificado** via `scripts/install/model-picker.mjs` (`PRESET_PRICE`) e `routing.yml` descrições. `go-free` = $0 (Zen free, quota limitada, só `-free`); `go-*` usam `PANTHEON_OPENCODE_API_KEY` (alias `OPENCODE_GO_API_KEY` aceito) no gateway `https://opencode.ai/zen/go/v1`; `openai` usa `OPENAI_API_KEY` em `https://api.openai.com/v1`. Custos reais dependem de conta/assinatura.
670
692
 
671
- The exact per-agent mapping (model + reasoning effort + fallbacks) lives in the `presets:` block of [src/routing.yml](src/routing.yml).
693
+ ### 14 agentes × 4 presets — modelo + effort + vision
694
+
695
+ | Agente | `go-free` | `go-fast` | `go-premium` | `openai` |
696
+ |---|---|---|---|---|
697
+ | `zeus` | `opencode/big-pickle` [medium] · | `opencode-go/kimi-k2.7-code` [medium] · | `opencode-go/gpt-5.6-sol` [medium] 👁️ | `openai/gpt-5.6-sol` [high] 👁️ |
698
+ | `athena` | `opencode/nemotron-3-super-free` [high] · | `opencode-go/kimi-k2.7-code` [high] · | `opencode-go/deepseek-v4-pro` [high] · | `openai/gpt-5.6-sol` [high] 👁️ |
699
+ | `themis` | `opencode/qwen3.6-plus-free` [high] · | `opencode-go/glm-5.3-flash` [high] · | `opencode-go/qwen3.8-max` [high] · | `openai/gpt-5.6-sol` [high] 👁️ |
700
+ | `hermes` | `opencode/deepseek-v4-flash-free` [medium] · | `opencode-go/deepseek-v4-flash` [medium] · | `opencode-go/gpt-5.6-terra` [medium] 👁️ | `openai/gpt-5.6-luna` [high] 👁️ |
701
+ | `aphrodite` | `opencode/deepseek-v4-flash-free` [medium] · | `opencode-go/mimo-v2.5` [low] 👁️ | `opencode-go/kimi-k3` [medium] · | `openai/gpt-5.6-luna` [high] 👁️ |
702
+ | `demeter` | `opencode/deepseek-v4-flash-free` [medium] · | `opencode-go/gpt-5.6-luna` [low] 👁️ | `opencode-go/gpt-5.6-terra` [medium] 👁️ | `openai/gpt-5.6-luna` [high] 👁️ |
703
+ | `prometheus` | `opencode/deepseek-v4-flash-free` [medium] · | `opencode-go/deepseek-v4-flash` [medium] · | `opencode-go/kimi-k3` [medium] · | `openai/gpt-5.6-luna` [high] 👁️ |
704
+ | `hephaestus` | `opencode/deepseek-v4-flash-free` [medium] · | `opencode-go/mimo-v2.5` [low] 👁️ | `opencode-go/gpt-5.6-terra` [medium] 👁️ | `openai/gpt-5.6-luna` [high] 👁️ |
705
+ | `apollo` | `opencode/mimo-v2.5-free` [low] 👁️ | `opencode-go/gpt-5.6-luna-fast` [low] 👁️ | `opencode-go/gpt-5.6-luna` [low] 👁️ | `openai/gpt-5.6-luna-fast` [low] 👁️ |
706
+ | `nyx` | `opencode/mimo-v2.5-free` [low] 👁️ | `opencode-go/gpt-5.6-luna-fast` [low] 👁️ | `opencode-go/gpt-5.6-luna` [low] 👁️ | `openai/gpt-5.6-luna-fast` [low] 👁️ |
707
+ | `gaia` | `opencode/mimo-v2.5-free` [low] 👁️ | `opencode-go/gpt-5.6-luna-fast` [low] 👁️ | `opencode-go/gpt-5.6-luna` [low] 👁️ | `openai/gpt-5.6-luna-fast` [low] 👁️ |
708
+ | `iris` | `opencode/mimo-v2.5-free` [low] 👁️ | `opencode-go/gpt-5.6-luna-fast` [low] 👁️ | `opencode-go/gpt-5.6-luna` [low] 👁️ | `openai/gpt-5.6-luna-fast` [low] 👁️ |
709
+ | `mnemosyne` | `opencode/mimo-v2.5-free` [low] 👁️ | `opencode-go/gpt-5.6-luna-fast` [low] 👁️ | `opencode-go/gpt-5.6-luna` [low] 👁️ | `openai/gpt-5.6-luna-fast` [low] 👁️ |
710
+ | `talos` | `opencode/mimo-v2.5-free` [low] 👁️ | `opencode-go/gpt-5.6-luna-fast` [low] 👁️ | `opencode-go/gpt-5.6-luna` [low] 👁️ | `openai/gpt-5.6-luna-fast` [low] 👁️ |
711
+
712
+ `·` = text-only, `👁️` = vision (image-input) per `CAPABILITY_TABLE` em [src/pantheon/presets.mjs](src/pantheon/presets.mjs) — ex.: `mimo-v2.5`/`mimo-v2.5-free` e `gpt-5.6-*` são multimodais; `deepseek-v4-pro/flash`, `kimi-k2.7-code`, `glm-5.3-flash`, `qwen3.6-plus-free`, `big-pickle`, `nemotron-3-super-free` são text-only.
713
+
714
+ O mapeamento exato por agente (modelo + `reasoning_effort` + `vision` fallback) vive no bloco `presets:` de [src/routing.yml](src/routing.yml) e é validado por `validatePresetDefs` + `capabilityEntry` / `hasVision`.
672
715
 
673
716
  ### Requirements
674
717
 
675
- - **Env vars** — checked fail-fast (CLI and plugin) when the selected preset uses the provider:
676
- - `PANTHEON_OPENCODE_API_KEY` — `opencode` + `opencode-go` providers (`go-deepseek`, `go-fast`, `go-premium`, `go-free`)
677
- - `PANTHEON_ANTHROPIC_API_KEY` — `anthropic` provider (`go-claude`)
678
- - `PANTHEON_OPENAI_API_KEY` — `openai` provider (`go-openai`)
679
- - **OpenCode Go subscription** required for `go-fast` / `go-premium`.
680
- - **OpenCode Zen free tier** for `go-free` — note `nemotron-3-ultra-free` has known intermittent failures ([opencode#38028](https://github.com/opencode/opencode/issues/38028)); fall back to `go-fast` if flaky.
718
+ - **Env vars** — checadas fail-fast (CLI e plugin) quando o preset usa o provider (sem hardcodar valores):
719
+ - `PANTHEON_OPENCODE_API_KEY` — `opencode` + `opencode-go` (`go-free`, `go-fast`, `go-premium`) — alias `OPENCODE_GO_API_KEY` também aceito (wizard Q2)
720
+ - `OPENAI_API_KEY` — `openai` provider (`openai` puro)
721
+ - **OpenCode Go subscription** para `go-fast` / `go-premium` (gateway `https://opencode.ai/zen/go/v1`).
722
+ - **OpenCode Zen free tier** para `go-free` (`https://opencode.ai/zen/v1`, só modelos `-free`, quota limitada).
723
+ - **Sem preset (default)**: zero-mutação — nenhum `.pantheon/active-preset.json` gravado, herança nativa do chat pai.
681
724
 
682
- ### Commands
725
+ ### Wizard — instalação interativa (3 perguntas)
683
726
 
684
- ```bash
685
- # Install + activate a preset
686
- npx pantheon-opencode init --preset go-fast
727
+ Durante `npx pantheon-opencode init` (TTY, sem `--preset`/`--headless`/`-y`):
728
+
729
+ 1. **Perfil (Q1)** — `0` = herdar do chat (**default**, não grava `active-preset.json`) ou `go-free`/`go-fast`/`go-premium`/`openai` — tabela exibida: perfil | `provider`/`baseURL`/key env | preço 2026 | modelos por papel (planners high, implementers medium/low, scouts low). `askQuestions` quando disponível, fallback `readline` TTY.
730
+ 2. **API Key (Q2)** — coleta **mascarada** (exibe `abcd****12`), valida `PANTHEON_OPENCODE_API_KEY` (aceita alias `OPENCODE_GO_API_KEY`) para `go-*` e `OPENAI_API_KEY` para `openai`, **não escreve `.env`** — só seta `env` da sessão e loga `maskKey`. Se já configurada no ambiente, apenas loga mascarada e segue.
731
+ 3. **Escopo (Q3)** — `project` (`./.pantheon/active-preset.json`, seguro, default) ou `global` (`~/.config/opencode/.pantheon/active-preset.json`). Gravação **atômica** (`tmp`+`rename`) com backup `.bak` + health-check (lê JSON e valida `preset`).
687
732
 
688
- # Switch preset (global config; --project for project-local; --dry-run to preview)
689
- pantheon-opencode set-tier go-deepseek
690
- pantheon-opencode set-tier go-fast --project
691
- pantheon-opencode set-tier go-fast --dry-run
733
+ Headless / CI: use `npx pantheon-opencode init --headless` ou `--preset <name>` (pula wizard). Veja [docs/INSTALLATION.md](docs/INSTALLATION.md) para `headless` + troubleshooting de chaves.
692
734
 
693
- # Clear preset — back to the zero-mutation default
694
- pantheon-opencode set-tier none
735
+ ### Comandos
695
736
 
696
- # No name → lists available presets (none, go-deepseek, go-fast, go-claude, go-openai, go-premium, go-free)
697
- pantheon-opencode set-tier
737
+ ```bash
738
+ # Instalação interativa (wizard 3 perguntas — Q1 default = herdar do chat)
739
+ npx pantheon-opencode init
740
+ npx pantheon-opencode init --project # escopo project-local
741
+ npx pantheon-opencode init --preset go-fast # pula wizard, ativa direto
742
+
743
+ # Alternar preset depois (global por padrão; --project para projeto; --dry-run para preview)
744
+ npx pantheon-opencode set-tier go-free
745
+ npx pantheon-opencode set-tier go-premium --project
746
+ npx pantheon-opencode set-tier openai --dry-run
747
+
748
+ # Limpar preset — volta ao default (herança nativa)
749
+ npx pantheon-opencode set-tier none
750
+
751
+ # Sem nome → lista presets (none, go-free, go-fast, go-premium, openai)
752
+ npx pantheon-opencode set-tier
753
+
754
+ # Per-agent customization (novo — sem top-level model/small_model)
755
+ # Lista 14 agentes com modelo/effort/origem (preset|override|env|none), sem segredos
756
+ /pantheon-model status # alias: show
757
+ /pantheon-model # sem args → wizard interativo (agente → modelo → effort → scope via askQuestions)
758
+ /pantheon-model set --agent hermes --model opencode-go/gpt-5.6-terra --effort medium --scope project
759
+ /pantheon-model set --agent apollo --model opencode/mimo-v2.5-free --effort low
760
+ /pantheon-model reset --agent hermes --scope project
698
761
  ```
699
762
 
700
- **Env override (CI/headless)** — wins over the file:
763
+ **Env override (CI/headless)** — vence o arquivo:
701
764
 
702
765
  ```bash
703
- export PANTHEON_MODEL_PRESET=go-deepseek
766
+ export PANTHEON_MODEL_PRESET=go-fast # ou go-free / go-premium / openai / none
767
+ export PANTHEON_OPENCODE_API_KEY=... # para go-* (ou OPENCODE_GO_API_KEY como alias)
768
+ export OPENAI_API_KEY=... # para openai puro
704
769
  ```
705
770
 
706
771
  ### How it works
707
772
 
708
- - The active selection is persisted to `.pantheon/active-preset.json` — `{version, preset, source, updated_at}` — written atomically (tmp file + rename) with a `.bak` backup of the previous file. It applies on the next OpenCode startup (no hot-swap); the plugin's `hooks.config` injects it.
709
- - **Resolution precedence**: `PANTHEON_MODEL_PRESET` env → first existing `.pantheon/active-preset.json` (project → `~/.config/opencode` → `~/.opencode`) → **none** (defaults).
710
- - **Partial semantics**: a preset overrides only the agents it lists; unlisted agents inherit the invoking primary agent's model.
711
- - Interactive picker during `init`: one prompt listing the 6 presets (select by number or name, blank to skip). It never touches your `opencode.json` — the choice is injected at startup instead. The file format also supports an optional `overrides` block (per-agent `model`/`variant`, per-provider `baseURL`/`apiKeyEnv`) merged over the preset definition.
712
- - **Capability normalization**: requested reasoning effort is clamped to each model family's ceiling (`deepseek-v4-flash` max `medium`; claude models strip the variant entirely — Anthropic uses thinking).
713
- - **fallback_models**: per-agent ordered fallback list where configured (e.g. `go-deepseek` sets `[opencode/mimo-v2.5]` for every agent).
773
+ - A seleção ativa é persistida (se não for `inherit`) em `.pantheon/active-preset.json` — `{version, preset, source, updated_at, overrides?}` — escrita atômica (`tmp`+`rename`) com backup `.bak`. Só aplica no próximo startup do OpenCode (sem hot-swap); o plugin em `hooks.config` injeta `provider`/`baseURL`/`apiKeyEnv`.
774
+ - **Precedência**: `PANTHEON_MODEL_PRESET` env → primeiro `.pantheon/active-preset.json` existente (project → `~/.config/opencode` → `~/.opencode`) → **none = herança nativa** (sem `active-preset.json`).
775
+ - **Herança nativa de delegates** (1.4.2): `src/pantheon/delegation.ts` resolve o modelo do filho em: `explicit model` em `pantheon_delegate` > `overrides.agents[agent].model` em `active-preset.json` > `loadRoutingAgentModels` (preset ativo) > omitir → OpenCode herda o modelo do chat pai. `small_model` nunca é enviado. Validação `provider/model-id` com `MODEL_REF_PATTERN`.
776
+ - **Partial & overrides**: um preset sobrescreve só os agentes listados; agentes não listados herdam do pai. O bloco `overrides.agents[agent]` (via `/pantheon-model set --agent`) tem merge sobre o preset, validado via `capabilityEntry` e `normalizeCapability` (clamp ao teto do modelo) + aviso `hasVision` para modelos text-only; `overrides.providers` permite `baseURL`/`apiKeyEnv` custom. Escrita atômica com lock por path + `.bak`.
777
+ - **Capability normalization**: `requested effort` é clamped ao teto da família em `CAPABILITY_TABLE` (`deepseek-v4-flash/-free` max `medium`; `gop-5.6-luna*`/`mimo-v2.5*` max `low`; `claude` strip; `qwen3.6-plus-free`/`big-pickle` etc. text-only).
778
+ - **Interactive picker / wizard**: `scripts/install/model-picker.mjs` exporta `buildPresetTableRow`, `requiredKeyEnvForPreset`, `isKeyConfiguredForPreset`, `maskKey`, `buildInitQuestions`, `runInitWizard` e `runModelPicker`. Nunca toca `opencode.json`; também expõe helper `maskKey` para logs.
714
779
 
715
780
  ### Quick smoke test
716
781
 
717
782
  ```bash
783
+ # 1) default (herança nativa) — sem preset
784
+ npx pantheon-opencode init --headless # escolha 0 no wizard interativo = inherit
785
+ opencode run "hello" 2>&1 | grep "Pantheon"
786
+ # sem log de preset → herança nativa
787
+
788
+ # 2) com preset
718
789
  export PANTHEON_OPENCODE_API_KEY=...
719
- pantheon-opencode set-tier go-fast --project
790
+ npx pantheon-opencode set-tier go-fast --project
720
791
  opencode run "hello" 2>&1 | grep "Pantheon"
721
792
  # expect: [Pantheon Plugin] Model preset active: go-fast (source: file)
793
+
794
+ # 3) override per-agent
795
+ # no chat OpenCode:
796
+ /pantheon-model status
797
+ /pantheon-model set --agent hermes --model openai/gpt-5.6-luna --effort high --scope project
798
+ # persiste em .pantheon/active-preset.json overrides.agents.hermes
722
799
  ```
723
800
 
801
+
724
802
  ---
725
803
 
726
804
  ## Quick Start
727
805
 
728
806
  ### 1. Install Pantheon
729
807
 
730
- Pantheon runs on **OpenCode**. Install it globally:
808
+ Pantheon runs on **OpenCode**. Instalação global com **wizard 3 perguntas** (default = herdar do chat):
731
809
 
732
810
  ```bash
733
811
  npx pantheon-opencode init
812
+ # Q1: perfil — 0=herdar do chat [default] (sem active-preset.json), ou go-free / go-fast / go-premium / openai
813
+ # tabela exibida: perfil | provider/baseURL/key | preço 2026 | modelos por papel
814
+ # Q2: API key — coleta mascarada (PANTHEON_OPENCODE_API_KEY alias OPENCODE_GO_API_KEY, ou OPENAI_API_KEY), não escreve .env
815
+ # Q3: escopo — project (./.pantheon/active-preset.json, default) ou global (~/.config/opencode/.pantheon/active-preset.json)
816
+ # gravação atômica tmp+rename com .bak + health-check
817
+ ```
818
+
819
+ Atalhos:
820
+
821
+ ```bash
822
+ npx pantheon-opencode init --preset go-fast --headless # pula wizard
823
+ npx pantheon-opencode init --project # escopo project-local
824
+ npx pantheon-opencode init --headless --no-mcp # minimal sem Python
734
825
  ```
735
826
 
736
- For MCP servers (memory, persistence):
827
+ MCP servers (memory, persistence) — opcional via:
737
828
 
738
829
  ```bash
739
830
  npm run setup
740
831
  ```
741
832
 
833
+ Ou já incluído no wizard se escolher instalação completa.
834
+
742
835
  ### 2. Launch OpenCode
743
836
 
744
837
  ```bash
745
838
  export OPENCODE_EXPERIMENTAL_BACKGROUND_SUBAGENTS=true
746
839
  opencode
840
+ # ou
841
+ npm run start
747
842
  ```
748
843
 
749
- ### 3. Run your first feature
844
+ Dica: após instalar preset, `opencode run "hello" 2>&1 | grep "Pantheon"` deve mostrar `[Pantheon Plugin] Model preset active: <preset> (source: file)`; sem preset, fica em herança nativa (sem log de preset).
845
+
846
+ ### 3. Customize per-agent (opcional)
847
+
848
+ No chat OpenCode:
849
+
850
+ ```
851
+ # lista 14 agentes com modelo/effort/origem
852
+ /pantheon-model status
853
+
854
+ # wizard interativo (agente → modelo → effort → scope)
855
+ /pantheon-model
856
+
857
+ # override explícito por agente (validado via CAPABILITY_TABLE, clamp, hasVision)
858
+ /pantheon-model set --agent hermes --model opencode-go/gpt-5.6-terra --effort medium --scope project
859
+ /pantheon-model reset --agent hermes --scope project
860
+ ```
861
+
862
+ Overrides vivem em `.pantheon/active-preset.json` em `overrides.agents[agent]` — sem `model`/`small_model` top-level e sem `.env`.
863
+
864
+ ### 4. Run your first feature
750
865
 
751
866
  Once agents are loaded, invoke the orchestrator:
752
867
 
@@ -778,6 +893,7 @@ Type these in the OpenCode chat:
778
893
  | `/pantheon-remember` | Store in memory |
779
894
  | `/pantheon-search` | Search memory |
780
895
  | `/pantheon-status` | System health and agent status |
896
+ | `/pantheon-model` (wizard) / `status\|show\|set --agent\|reset --agent` | Herança nativa default; `status` lista 14 agentes (model/effort/origem); `set --agent X --model provider/model-id [--effort low|medium|high] [--scope project|global]` com validação CAPABILITY_TABLE + clamp + hasVision; `reset --agent X`; default scope `project`; global exige `confirm`+`authorize_global`; nunca escreve `.env` nem top-level `model`; persistência atômica `.bak` + health-check |
781
897
  | `/pantheon-verify` | Hash edit verification |
782
898
 
783
899
  ---
@@ -190,6 +190,11 @@ async function main() {
190
190
  const autoYes = args.includes('--yes') || args.includes('-y')
191
191
  const presetIndex = args.indexOf('--preset')
192
192
  const presetOpt = presetIndex >= 0 ? (args[presetIndex + 1] ?? null) : null
193
+ // Explicit top-level model overrides for the generated config.
194
+ const modelIndex = args.indexOf('--model')
195
+ const modelOpt = modelIndex >= 0 ? (args[modelIndex + 1] ?? null) : null
196
+ const smallModelIndex = args.indexOf('--small-model')
197
+ const smallModelOpt = smallModelIndex >= 0 ? (args[smallModelIndex + 1] ?? null) : null
193
198
  // --version v1|v2 labels the install target (informational; the config is
194
199
  // shared and V1-shaped under both versions). Invalid values fail fast.
195
200
  const versionIndex = args.indexOf('--version')
@@ -218,6 +223,8 @@ async function main() {
218
223
  interactive: forceInteractive,
219
224
  headless: forceHeadless,
220
225
  yes: autoYes,
226
+ model: modelOpt,
227
+ smallModel: smallModelOpt,
221
228
  preset: presetOpt,
222
229
  version: versionOpt ?? 'v1',
223
230
  })
@@ -0,0 +1,34 @@
1
+ ---
2
+ description: "Inspect or change per-agent model overrides in active-preset.json"
3
+ agent: zeus
4
+ ---
5
+ # /pantheon-model — Per-agent model overrides
6
+
7
+ Use the deterministic `pantheon_model` tool for this command. Operates exclusively
8
+ on `active-preset.json` `overrides.agents[agent]` (project or global). Never
9
+ writes `.env` and never injects top-level `model`/`small_model` into `opencode.json`.
10
+
11
+ ## Usage
12
+
13
+ - `/pantheon-model status` or `/pantheon-model show` — list 14 canonical agents
14
+ (`zeus`, `athena`, `apollo`, `hermes`, `aphrodite`, `demeter`, `themis`,
15
+ `prometheus`, `hephaestus`, `nyx`, `gaia`, `iris`, `mnemosyne`, `talos`)
16
+ with effective `model`, `effort`, and origin (`preset` | `override` | `env` | `none`)
17
+ and the active preset, without showing secrets.
18
+ - `/pantheon-model` (no args) — interactive wizard via `askQuestions`:
19
+ agente → modelo (`provider/model-id`) → effort (`low`/`medium`/`high`) → scope.
20
+
21
+ - `/pantheon-model set --agent <name> --model <provider/model-id> [--effort low|medium|high] [--scope project|global]`
22
+ — set per-agent override. Validates agent is one of the 14, `provider/model-id`
23
+ format, `CAPABILITY_TABLE` entry via `capabilityEntry()` and clamps effort via
24
+ `normalizeCapability()`; warns via `hasVision` for text-only models. Persists
25
+ atomically with `.bak` backup and per-path lock. Default scope is `project`.
26
+
27
+ - `/pantheon-model reset --agent <name> [--scope project|global]` — remove the
28
+ per-agent override from `overrides.agents[agent]`.
29
+
30
+ `--scope project|global` is supported by `set` and `reset`. Global changes require
31
+ explicit `confirm` and `authorize_global`. Values must use `provider/model-id`.
32
+ Changes are backed up (`.bak`) and written atomically; restart OpenCode after a
33
+ successful change. The tool never writes `.env` and never injects `model`/`small_model`
34
+ global keys — delegate inheritance is native (no model → inherits chat/preset model).
package/package.json CHANGED
@@ -1,13 +1,13 @@
1
1
  {
2
2
  "name": "pantheon-opencode",
3
- "version": "1.4.1",
3
+ "version": "1.4.2",
4
4
  "description": "Multi-agent orchestration for OpenCode — 14 specialized agents, TDD, Themis review gates.",
5
5
  "publishConfig": {
6
6
  "access": "public",
7
7
  "registry": "https://registry.npmjs.org/"
8
8
  },
9
9
  "scripts": {
10
- "postinstall": "node scripts/postinstall.mjs",
10
+ "postinstall": "node scripts/postinstall.mjs && node scripts/sync-tui.mjs",
11
11
  "prepare": "husky",
12
12
  "setup": "node bin/pantheon-init.mjs init",
13
13
  "build:agents-md": "node scripts/build-agents-md.mjs",
@@ -0,0 +1,239 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * generate-preset-docs.mjs — Generate markdown tables from src/routing.yml
4
+ *
5
+ * Reads routing.yml + presets.mjs + model-picker.mjs and prints:
6
+ * 1) Presets summary table (perfil | provider/baseURL | key env | pricing 2026 | vision | papéis)
7
+ * 2) 14 agents × 4 presets matrix (model + effort + vision)
8
+ * 3) Capability table (model prefix → maxEffort, vision)
9
+ *
10
+ * No secrets are hardcoded — only env var NAMES and baseURLs from routing.yml
11
+ * are emitted. Pricing labels come from model-picker PRESET_PRICE.
12
+ *
13
+ * Usage:
14
+ * node scripts/generate-preset-docs.mjs # print all
15
+ * node scripts/generate-preset-docs.mjs --summary # only summary
16
+ * node scripts/generate-preset-docs.mjs --matrix # only matrix
17
+ * node scripts/generate-preset-docs.mjs --json # JSON for tooling
18
+ */
19
+
20
+ import { readFileSync } from 'node:fs'
21
+ import { fileURLToPath } from 'node:url'
22
+ import yaml from 'js-yaml'
23
+ import { CAPABILITY_TABLE, hasVision, normalizeCapability } from '../src/pantheon/presets.mjs'
24
+ import { PRESET_PRICE } from './install/model-picker.mjs'
25
+
26
+ const routingPath = fileURLToPath(new URL('../src/routing.yml', import.meta.url))
27
+ const raw = readFileSync(routingPath, 'utf8')
28
+ const routing = yaml.load(raw)
29
+ const presets = routing?.presets ?? {}
30
+
31
+ const KNOWN_AGENTS_ORDER = ['zeus','athena','themis','hermes','aphrodite','demeter','prometheus','hephaestus','apollo','nyx','gaia','iris','mnemosyne','talos']
32
+
33
+ function agentSort(a,b){ const ia = KNOWN_AGENTS_ORDER.indexOf(a); const ib = KNOWN_AGENTS_ORDER.indexOf(b); if(ia===-1&&ib===-1) return a.localeCompare(b); if(ia===-1) return 1; if(ib===-1) return -1; return ia-ib; }
34
+
35
+ function safeEnvLabel(_apiKeyEnv){
36
+ return '`[redacted]`'
37
+ }
38
+
39
+ function providerLabel(def){
40
+ const entries = Object.entries(def.providers ?? {})
41
+ if(entries.length===0) return '—'
42
+ return entries.map(([id,p]) => `\`${id}\` \`${p.baseURL ?? ''}\` (env: ${safeEnvLabel(p.apiKeyEnv)})`).join('<br>')
43
+ }
44
+
45
+ function pricingLabel(name){
46
+ const p = PRESET_PRICE[name]
47
+ if(!p) return '—'
48
+ return `${p.label} — ${p.detail}`
49
+ }
50
+
51
+ function visionLabel(def){
52
+ if(!def.vision) return '—'
53
+ const m = def.vision.model
54
+ let visionFlag = '—'
55
+ try{ visionFlag = hasVision(m) ? '👁️ vision' : 'text-only' }catch{ visionFlag = 'unknown' }
56
+ return `\`${m}\` [${def.vision.reasoning_effort ?? ''}] ${visionFlag}`
57
+ }
58
+
59
+ function capabilityBadge(model){
60
+ try{
61
+ const v = hasVision(model)
62
+ const cap = normalizeCapability(model, 'high')
63
+ // cap.variant is clamped effort for 'high'
64
+ // also get maxEffort via capabilityEntry
65
+ const entry = CAPABILITY_TABLE.find(e => model.startsWith(e.prefix) || model.slice(model.lastIndexOf('/')+1).startsWith(e.prefix) || model.slice(model.lastIndexOf('/')+1)===e.prefix)
66
+ // But use direct lookup via prefix matching like capabilityEntry does
67
+ return { vision: v, maxEffort: entry?.maxEffort ?? 'unknown', clamped: cap.variant }
68
+ }catch{
69
+ return { vision: undefined, maxEffort: 'unknown', clamped: undefined }
70
+ }
71
+ }
72
+
73
+ function buildSummaryTable(){
74
+ const lines=[]
75
+ lines.push('| Preset | Provider / BaseURL | Key env (nome) | Pricing 2026 | Vision (fallback) | Papéis / Modelos |')
76
+ lines.push('|---|---|---|---|---|---|')
77
+ for(const name of Object.keys(presets)){
78
+ const def = presets[name]
79
+ const prov = providerLabel(def)
80
+ // env names redacted to avoid exposing credential-related metadata
81
+ const envs = Object.values(def.providers ?? {}).map(p=>safeEnvLabel(p.apiKeyEnv)).join(', ') || '—'
82
+ // Actually use providerLabel already includes env, but separate column for key env
83
+ const pricing = pricingLabel(name)
84
+ const vision = visionLabel(def)
85
+ // Papéis grouped: planners/high, implementers/medium, scouts/low
86
+ const agents = def.agents ?? {}
87
+ const groups={}
88
+ for(const [agent,spec] of Object.entries(agents)){
89
+ const key = `\`${spec.model}\` [${spec.reasoning_effort ?? ''}]`
90
+ if(!groups[key]) groups[key]=[]
91
+ groups[key].push(agent)
92
+ }
93
+ const papelSummary = Object.entries(groups).map(([model,list])=>`${list.join(',')}: ${model}`).join('<br>')
94
+ lines.push(`| \`${name}\` | ${prov} | ${envs} | ${pricing} | ${vision} | ${papelSummary} |`)
95
+ }
96
+ return lines.join('\n')
97
+ }
98
+
99
+ function buildMatrixTable(){
100
+ const presetNames = Object.keys(presets)
101
+ const lines=[]
102
+ // header
103
+ lines.push(`| Agente | ${presetNames.map(n=>`\`${n}\``).join(' | ')} | Capability (vision / maxEffort) |`)
104
+ lines.push(`|---|${presetNames.map(()=> '---').join('|')}|---|`)
105
+ // collect all agents
106
+ const allAgents = new Set()
107
+ for(const def of Object.values(presets)) for(const a of Object.keys(def.agents ?? {})) allAgents.add(a)
108
+ const sorted = [...allAgents].sort(agentSort)
109
+ for(const agent of sorted){
110
+ const cells = presetNames.map(presetName=>{
111
+ const spec = presets[presetName]?.agents?.[agent]
112
+ if(!spec) return '—'
113
+ const badge = capabilityBadge(spec.model)
114
+ const visionIcon = badge.vision ? '👁️' : '—'
115
+ // don't expose raw key, just model and effort
116
+ return `\`${spec.model}\`<br>[${spec.reasoning_effort}] ${visionIcon}`
117
+ })
118
+ // capability column: pick first preset's model for reference, but show per-agent vision across presets is same family
119
+ const sampleModel = presets[presetNames[0]]?.agents?.[agent]?.model ?? ''
120
+ let capStr='—'
121
+ if(sampleModel){
122
+ try{
123
+ const v = hasVision(sampleModel)
124
+ capStr = v ? 'vision' : 'text-only'
125
+ }catch{ capStr='unknown' }
126
+ }
127
+ // Show effort clamp per preset? Instead show generic
128
+ lines.push(`| \`${agent}\` | ${cells.join(' | ')} | ${capStr} |`)
129
+ }
130
+ return lines.join('\n')
131
+ }
132
+
133
+ function buildDetailedMatrixWithEffort(){
134
+ const presetNames = Object.keys(presets)
135
+ const lines=[]
136
+ lines.push(`| Agente | ${presetNames.map(n=>`\`${n}\``).join(' | ')} |`)
137
+ lines.push(`|---|${presetNames.map(()=> '---').join('|')}|`)
138
+ const allAgents = new Set()
139
+ for(const def of Object.values(presets)) for(const a of Object.keys(def.agents ?? {})) allAgents.add(a)
140
+ const sorted = [...allAgents].sort(agentSort)
141
+ for(const agent of sorted){
142
+ const cells = presetNames.map(presetName=>{
143
+ const spec = presets[presetName]?.agents?.[agent]
144
+ if(!spec) return '—'
145
+ let vision = '—'
146
+ try{ vision = hasVision(spec.model) ? '👁️' : '·' }catch{ vision='?' }
147
+ return `\`${spec.model}\` [${spec.reasoning_effort}] ${vision}`
148
+ })
149
+ lines.push(`| \`${agent}\` | ${cells.join(' | ')} |`)
150
+ }
151
+ return lines.join('\n')
152
+ }
153
+
154
+ function buildProviderTable(){
155
+ const lines=[]
156
+ lines.push('| Preset | Provider | BaseURL | Key env |')
157
+ lines.push('|---|---|---|---|')
158
+ for(const [name,def] of Object.entries(presets)){
159
+ for(const [pid,prov] of Object.entries(def.providers ?? {})){
160
+ const keyEnvStatus = prov.apiKeyEnv ? 'configured' : 'none'
161
+ lines.push(`| \`${name}\` | \`${pid}\` | \`${prov.baseURL ?? ''}\` | \`${keyEnvStatus}\` |`)
162
+ }
163
+ }
164
+ return lines.join('\n')
165
+ }
166
+
167
+ function buildPricingTable(){
168
+ const lines=[]
169
+ lines.push('| Preset | Pricing 2026 | Detalhe | Gateway / Custo |')
170
+ lines.push('|---|---|---|---|')
171
+ for(const name of Object.keys(presets)){
172
+ const p = PRESET_PRICE[name] ?? {label:'—',detail:'—'}
173
+ const def = presets[name]
174
+ const prov = Object.keys(def.providers ?? {}).join(', ')
175
+ const costHint = name==='go-free' ? '$0 (free-tier Zen, quota limitada)' : name==='go-fast' ? 'Baixo custo (Go gateway, baixa latência)' : name==='go-premium' ? 'Premium (Go gateway, melhor qualidade)' : 'Pago por uso (OpenAI direto)'
176
+ lines.push(`| \`${name}\` | ${p.label} | ${p.detail} | ${prov} — ${costHint} |`)
177
+ }
178
+ return lines.join('\n')
179
+ }
180
+
181
+ function buildCapabilityTable(){
182
+ const lines=[]
183
+ lines.push('| Prefix | Max Effort | Vision | Strip Effort |')
184
+ lines.push('|---|---|---|---|')
185
+ for(const e of CAPABILITY_TABLE){
186
+ lines.push(`| \`${e.prefix}\` | ${e.maxEffort ?? '—'} | ${e.vision ? '👁️' : '—'} | ${e.stripEffort ? 'yes' : 'no'} |`)
187
+ }
188
+ return lines.join('\n')
189
+ }
190
+
191
+ const args = process.argv.slice(2)
192
+ if(args.includes('--json')){
193
+ const out = { presets, PRESET_PRICE, CAPABILITY_TABLE }
194
+ console.log(JSON.stringify(out,null,2))
195
+ process.exit(0)
196
+ }
197
+ if(args.includes('--summary')){
198
+ console.log(buildSummaryTable())
199
+ process.exit(0)
200
+ }
201
+ if(args.includes('--matrix')){
202
+ console.log(buildMatrixTable())
203
+ process.exit(0)
204
+ }
205
+ if(args.includes('--provider')){
206
+ console.log(buildProviderTable())
207
+ process.exit(0)
208
+ }
209
+ if(args.includes('--pricing')){
210
+ console.log(buildPricingTable())
211
+ process.exit(0)
212
+ }
213
+ if(args.includes('--capability')){
214
+ console.log(buildCapabilityTable())
215
+ process.exit(0)
216
+ }
217
+
218
+ // default: print all
219
+ console.log('<!-- Generated from src/routing.yml + src/pantheon/presets.mjs + scripts/install/model-picker.mjs — do not edit manually. Run: node scripts/generate-preset-docs.mjs -->')
220
+ console.log('')
221
+ console.log('### Presets — resumo (provider / baseURL / key / pricing / vision)')
222
+ console.log('')
223
+ console.log(buildSummaryTable())
224
+ console.log('')
225
+ console.log('### Provider / BaseURL / Key env')
226
+ console.log('')
227
+ console.log(buildProviderTable())
228
+ console.log('')
229
+ console.log('### Pricing 2026')
230
+ console.log('')
231
+ console.log(buildPricingTable())
232
+ console.log('')
233
+ console.log('### 14 agentes × 4 presets — modelo + effort + vision')
234
+ console.log('')
235
+ console.log(buildDetailedMatrixWithEffort())
236
+ console.log('')
237
+ console.log('### Capability table (prefix → maxEffort, vision)')
238
+ console.log('')
239
+ console.log(buildCapabilityTable())