pantheon-opencode 1.6.0-beta.1 → 1.6.0-beta.3
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/CHANGELOG.md +132 -0
- package/README.md +59 -13
- package/README.pt-BR.md +49 -6
- package/package.json +2 -1
- package/plugin.json +1 -1
- package/pyproject.toml +1 -1
- package/scripts/install/config-migration.mjs +122 -1
- package/scripts/install/opencode.mjs +43 -10
- package/skills-lock.json +1 -1
- package/src/agents/aphrodite.md +0 -1
- package/src/agents/apollo.md +0 -1
- package/src/agents/athena.md +0 -1
- package/src/agents/demeter.md +0 -1
- package/src/agents/gaia.md +0 -1
- package/src/agents/hephaestus.md +0 -1
- package/src/agents/hermes.md +0 -1
- package/src/agents/iris.md +0 -1
- package/src/agents/mnemosyne.md +0 -1
- package/src/agents/nyx.md +0 -1
- package/src/agents/prometheus.md +0 -1
- package/src/agents/talos.md +0 -1
- package/src/agents/themis.md +0 -1
- package/src/agents/zeus.md +0 -1
- package/src/pantheon/delegation-enforce.ts +10 -1
- package/src/pantheon/presets.d.mts +0 -8
- package/src/pantheon/presets.mjs +0 -29
- package/src/pantheon/v2-tools.ts +426 -0
- package/src/plugin-v2.ts +344 -134
- package/src/plugins/tui/dist/tui.js +1 -1
- package/src/plugins/tui/package-lock.json +5 -5
- package/src/plugins/tui/package.json +1 -1
- package/src/routing.yml +0 -15
- package/src/skills/auto-continue/SKILL.md +2 -2
package/CHANGELOG.md
CHANGED
|
@@ -18,6 +18,138 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/).
|
|
|
18
18
|
|
|
19
19
|
## ✅ Closed Issues
|
|
20
20
|
|
|
21
|
+
## [v1.6.0-beta.3] - 2026-10-03
|
|
22
|
+
|
|
23
|
+
<!-- Add new changes here. Running `node scripts/versioning.mjs apply` will
|
|
24
|
+
move this section to a versioned entry and reset the template below. -->
|
|
25
|
+
|
|
26
|
+
## 🆕 What's New
|
|
27
|
+
- **Três ferramentas V2 agora executam de verdade**: `hashline_edit`,
|
|
28
|
+
`pantheon_cost` e `pantheon_model` rodam no host `opencode v2.0.x`. Antes
|
|
29
|
+
eram placeholders que declaravam sucesso sem fazer nada.
|
|
30
|
+
- **Enforcement de sessão read-only no caminho V2**: `apollo` e `gaia` voltam
|
|
31
|
+
a ter escrita bloqueada, com o guard registrado no próprio plugin V2 — sem
|
|
32
|
+
depender de uma ponte que a instalação V2 não monta.
|
|
33
|
+
- **Varredura de segredos de alta confiança no caminho V2**: o único hard block
|
|
34
|
+
de segurança que a V1 tinha, e que estava inerte por viver num plugin que a
|
|
35
|
+
instalação V2 não carrega, passa a rodar.
|
|
36
|
+
|
|
37
|
+
## 🐞 Fixed
|
|
38
|
+
- **As 6 ferramentas de plugin do V2 falhavam em 100% das chamadas**:
|
|
39
|
+
`hashline_edit`, `pantheon_cost` e `pantheon_model` lançavam
|
|
40
|
+
`Tool result declared output without an output schema` em toda invocação. A
|
|
41
|
+
causa era assimetria local — `draft.add()` não declarava `output` enquanto o
|
|
42
|
+
`execute` resolvia `{ output: string }`. O host exige simetria nos dois
|
|
43
|
+
sentidos: resultado com `output` e nenhuma declaração lança, assim como
|
|
44
|
+
declaração sem `output` no resultado. `output` passou a ser **obrigatório** na
|
|
45
|
+
interface `V2ToolDef`, então o compilador exige a declaração daqui em diante.
|
|
46
|
+
- **A suíte exigia o defeito**: `tests/pantheon/plugin-v2-contract.test.ts`
|
|
47
|
+
afirmava que o resultado carregava `.output` string — literalmente a condição
|
|
48
|
+
de falha, contra um mock que não reproduzia a exigência do host. O teste foi
|
|
49
|
+
reescrito para o invariante de simetria, contra um mock que constrói o
|
|
50
|
+
`outputSchema` a partir de `def.output` como o host faz
|
|
51
|
+
(`output: I.outputSchema ?? {}`), cobrindo as duas direções do bicondicional.
|
|
52
|
+
- **A imposição read-only não existia na superfície V2**: `createEnforcementGuard`
|
|
53
|
+
só era instanciado em `src/plugin.ts`, que o `opencode.json` da release não
|
|
54
|
+
declara — V2 nunca carrega V1. Com `hashline_edit` funcional, uma sessão
|
|
55
|
+
somente-leitura podia escrever arquivos. O guard passou a ser registrado no
|
|
56
|
+
hook `execute.before` do V2, e `pantheon_model` — que escreve
|
|
57
|
+
`active-preset.json` em escopo project e global — foi acrescentado a
|
|
58
|
+
`DEFAULT_BLOCKED_TOOLS`, o que corrige também a V1. `pantheon_cost` ficou de
|
|
59
|
+
fora de propósito: só lê `opencode.db`.
|
|
60
|
+
- **O único hard block de segurança da V1 estava inerte**: o scan de segredos de
|
|
61
|
+
alta confiança vivia em `src/plugins/pantheon-hooks.ts`, um plugin que a
|
|
62
|
+
instalação V2 não carrega — inerte por construção, não por má configuração.
|
|
63
|
+
Passou a rodar dentro do `execute.before` do V2
|
|
64
|
+
(`scripts/hooks/scan-secrets.sh`; exit 2 bloqueia, exit 1 é advisory e só
|
|
65
|
+
registra). O caminho do script foi verificado a partir do tarball instalado,
|
|
66
|
+
não só do repo.
|
|
67
|
+
- **Cinco comentários afirmavam cobertura que não existe** ("wired through the
|
|
68
|
+
V1…"). Corrigidos — é o defeito "anunciado como vivo, morto no caminho", a
|
|
69
|
+
mesma classe do bug original, transferida do tool para o controle de segurança.
|
|
70
|
+
|
|
71
|
+
## ⚠️ Known Issues
|
|
72
|
+
- **A release entrega 3 ferramentas V2 funcionais, não 6**:
|
|
73
|
+
`pantheon_goal_create`, `pantheon_goal_get` e `pantheon_goal_update` foram
|
|
74
|
+
**removidos** da superfície V2. `GoalLoopDeps` exige `store`, `client` e
|
|
75
|
+
`board`, que não existem no `PluginContext` V2, e a bridge V1 resolve para
|
|
76
|
+
`null` fora da V1. Registrá-las como placeholders não funcionais seria pior
|
|
77
|
+
que a ausência: anunciá-las seria mentira silenciosa. Marcador `goal-tools`
|
|
78
|
+
em `V2_UNSUPPORTED_FEATURES`. **Breaking** para quem usava esses tools no
|
|
79
|
+
host V2 — o contrato V1 permanece disponível para o goal loop.
|
|
80
|
+
- **A matriz de delegação nativa da V1 não é replicada na V2**: o guard V2 cobre
|
|
81
|
+
a lista de ferramentas bloqueadas, mas não a matriz caller→target nem a negação
|
|
82
|
+
de `task` em sessão filha. A matriz precisa de uma hierarquia de sessões
|
|
83
|
+
semeada a partir dos metadados de sessão, e o V2 não expõe esse caminho de
|
|
84
|
+
semeadura — `SessionHierarchyRegistry.isRoot` reporta `true` para sessões
|
|
85
|
+
desconhecidas enquanto não semeada, então semeá-la pela metade negaria todo
|
|
86
|
+
`task()`. O branch é pulado em vez de negado indiscriminadamente. Marcador
|
|
87
|
+
`delegation-matrix`. `task` continua negada em sessão somente-leitura pela
|
|
88
|
+
lista de ferramentas bloqueadas, então a profundidade 2 se sustenta; o que
|
|
89
|
+
não existe é a matriz. Coberto por `experimental.subagent_depth` nesta
|
|
90
|
+
configuração, mas a garantia do plugin não existe.
|
|
91
|
+
- **O log do scan de segredos escreve só em `.pantheon/logs/hooks.log`**: o
|
|
92
|
+
`PluginContext` V2 não expõe `client` nem `directory`, então os canais de log
|
|
93
|
+
estruturado e de toast da V1 não existem nessa superfície. É menos
|
|
94
|
+
observabilidade, não menos bloqueio.
|
|
95
|
+
- **Falha de infraestrutura do scanner é fail-open por decisão**: script ausente
|
|
96
|
+
ou timeout não bloqueiam. `runHook` nunca rejeita e um erro de infraestrutura
|
|
97
|
+
não deve negar chamadas não relacionadas. Ausência do script não bloqueia.
|
|
98
|
+
|
|
99
|
+
## ✅ Closed Issues
|
|
100
|
+
- **Drift de documentação no `README.pt-BR.md`**: a tabela de contrato de
|
|
101
|
+
runtime e o parágrafo de superfície listavam 6 ferramentas V2 e as 3 de goal,
|
|
102
|
+
que não estão registradas. Corrigido para 3 ferramentas, com a razão da
|
|
103
|
+
ausência e o ponto de imposição read-only.
|
|
104
|
+
- **Cinco comentários de falsa cobertura** sobre a imposição read-only e o scan
|
|
105
|
+
de segredos.
|
|
106
|
+
- **Shim do loader JSON do ajv elevado a bootstrap de módulo**: o patch de
|
|
107
|
+
`Module._extensions['.json']` era aplicado de dentro de `loadAjv()` e ficava
|
|
108
|
+
vivo durante todo o run, podendo vazar entre arquivos de teste e se manifestar
|
|
109
|
+
longe da causa. Agora instala, carrega e restaura em nível de módulo, antes de
|
|
110
|
+
qualquer corpo de teste, com `finally`.
|
|
111
|
+
## [v1.6.0-beta.2] - 2026-10-02
|
|
112
|
+
|
|
113
|
+
## 🆕 What's New
|
|
114
|
+
- **`steps` não é mais definido pelo Pantheon**: removido dos 14 agentes em
|
|
115
|
+
`src/agents/`. No OpenCode 2 o campo é opcional e **sem default** — ausente
|
|
116
|
+
significa **sem teto nativo**. O host não impõe teto; o Pantheon estava
|
|
117
|
+
escrevendo um.
|
|
118
|
+
- **Instalador v2 remove `steps` residual**: configs já instalados passam a ter
|
|
119
|
+
o `steps` apagado dos agentes gerenciados. Escopo restrito aos agentes que o
|
|
120
|
+
Pantheon gerencia — um agente definido pelo usuário nunca é tocado. O bloco V1
|
|
121
|
+
singular `agent` é deliberadamente intocado (V1 em retirada).
|
|
122
|
+
- **Substitutos ao teto**: `task(background=true)` para trabalho fora da
|
|
123
|
+
foreground, `experimental.subagent_depth` para aninhamento de subagentes e
|
|
124
|
+
paralelismo entre delegações independentes.
|
|
125
|
+
- **Limpeza**: `max_steps` e `max_compaction_items` removidos de
|
|
126
|
+
`src/routing.yml`. `loadRoutingMaxSteps` nunca teve um único import — 2
|
|
127
|
+
matches no repo (definição e typedef), ambos removidos.
|
|
128
|
+
|
|
129
|
+
## 🐞 Fixed
|
|
130
|
+
- **`migrateV1toV2` descartava campos gerenciados**: com os blocos `agent` (V1)
|
|
131
|
+
e `agents` (V2) coexistindo — o caso de qualquer config já instalado — o
|
|
132
|
+
spread raso fazia um substituir o outro por inteiro. Na ordem de chaves do
|
|
133
|
+
`opencode.json` real, o lado perdedor era o do usuário. Agora **mescla**: a
|
|
134
|
+
base é o bloco V2, os campos gerenciados do V1 sobrescrevem, e os campos do
|
|
135
|
+
usuário são preservados. Mesma semântica já usada em `install/opencode.mjs`.
|
|
136
|
+
- **`docs/platforms/README.md` tinha tabela de step limits errada**: 14 linhas
|
|
137
|
+
listando limites por agente, **nenhum valor batendo** com `src/agents/`.
|
|
138
|
+
Tabela removida e a seção reescrita.
|
|
139
|
+
|
|
140
|
+
## ⚠️ Known Issues
|
|
141
|
+
- **Permissões de agente**: V1 usa `permission` e V2 usa `permissions`; ao mesclar esses blocos, a chave legada `permission` pode permanecer inerte ao lado de `permissions`.
|
|
142
|
+
- **Migração — rode o init uma vez**: `npx pantheon-opencode init
|
|
143
|
+
--opencode-version=v2` **uma única vez** limpa o `steps` residual. Sem isso o
|
|
144
|
+
config instalado continua carregando o teto antigo.
|
|
145
|
+
- **O `.md` de agente instalado é sobrescrito no próximo sync**: editar
|
|
146
|
+
`~/.config/opencode/agents/*.md` esperando persistência **não funciona**.
|
|
147
|
+
Comportamento **pré-existente**, não é regressão desta mudança — mas agora
|
|
148
|
+
está documentado para não ser reportado como uma.
|
|
149
|
+
- **`steps` definido à mão num agente gerenciado é removido a cada install v2**:
|
|
150
|
+
o instalador não distingue um valor que ele escreveu de um valor que você
|
|
151
|
+
escreveu (mesma chave, mesmo valor). Workaround: definir no frontmatter do
|
|
152
|
+
agente — e lembrar que ele **também** é sobrescrito no próximo sync.
|
|
21
153
|
## [v1.6.0-beta.1] - 2026-09-30
|
|
22
154
|
|
|
23
155
|
<!-- Add new changes here. Running `node scripts/versioning.mjs apply` will
|
package/README.md
CHANGED
|
@@ -78,11 +78,14 @@ clear handoff between stages of work.
|
|
|
78
78
|
|
|
79
79
|
## Status
|
|
80
80
|
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
and
|
|
81
|
+
Candidate version: the manifests in this checkout express the version being
|
|
82
|
+
prepared, not a confirmed publication. A beta is published only after the
|
|
83
|
+
Release workflow completes successfully. To check which version is actually
|
|
84
|
+
published, consult the npm `beta` dist-tag. Pantheon is designed for OpenCode
|
|
85
|
+
and depends on the availability and configuration of OpenCode and any optional
|
|
86
|
+
services you choose to use. Check the
|
|
87
|
+
[releases](https://github.com/ils15/pantheon-opencode/releases) and
|
|
88
|
+
[changelog](CHANGELOG.md) for the latest published changes.
|
|
86
89
|
|
|
87
90
|
## Delegation (native `task()`)
|
|
88
91
|
|
|
@@ -183,16 +186,36 @@ per installation; V1 and V2 Pantheon plugins must never be registered together.
|
|
|
183
186
|
|---|---|---|
|
|
184
187
|
| OpenCode config key | singular `plugin` | plural `plugins` |
|
|
185
188
|
| Pantheon registration | `src/plugin.ts` plus `src/plugins/pantheon-hooks.ts` | `<installed>/src/plugin-v2` directory (`index.ts` re-exports `src/plugin-v2.ts`) |
|
|
186
|
-
| Runtime contract | Pantheon V1 plugin: 6 tools (`hashline_edit`, the 3 goal tools, `pantheon_cost`, `pantheon_model`), event/tool hooks and V1 compaction handling | Full V2 plugin:
|
|
189
|
+
| Runtime contract | Pantheon V1 plugin: 6 tools (`hashline_edit`, the 3 goal tools, `pantheon_cost`, `pantheon_model`), event/tool hooks and V1 compaction handling | Full V2 plugin: 3 tools (`hashline_edit`, `pantheon_cost`, `pantheon_model`), 4 event subscriptions, session hooks (`prompt`, `context`), a read-only-enforcing tool `execute.before` hook, plus configuration transforms |
|
|
187
190
|
| V1 APIs | Registered | Own tool definitions via `ctx.tool.transform()` — not the V1 plugin path |
|
|
188
191
|
|
|
189
|
-
The V2 plugin provides
|
|
190
|
-
`
|
|
191
|
-
`
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
`
|
|
192
|
+
The V2 plugin provides 3 orchestration tools (`hashline_edit`, `pantheon_cost`,
|
|
193
|
+
`pantheon_model`), 4 event subscriptions (`session.created`, `session.idle`,
|
|
194
|
+
`session.error`, `session.compacted`), session hooks (`prompt`, `context`),
|
|
195
|
+
and a tool `execute.before` hook that enforces read-only sessions. The 3 goal
|
|
196
|
+
tools are **not**
|
|
197
|
+
registered on V2: the goal loop needs a `GoalStore`, a `GoalLoopClient` and a
|
|
198
|
+
`BackgroundJobBoard`, none of which the V2 `PluginContext` exposes, so the gap
|
|
199
|
+
is reported as the `goal-tools` unsupported-feature marker. Use the V1
|
|
200
|
+
contract if you need the goal loop.
|
|
201
|
+
|
|
202
|
+
The V2 `execute.before` hook is the read-only enforcement point for that
|
|
203
|
+
surface: the host includes the active agent on the event, a read-only agent
|
|
204
|
+
(`apollo`, `gaia`) registers its session, and the shared
|
|
205
|
+
`createEnforcementGuard` throws to deny `edit`, `write`, `bash`, `task`,
|
|
206
|
+
`hashline_edit` and `pantheon_model`. It does **not** rely on the V1
|
|
207
|
+
`tool.execute.before` hook, which lives in `src/plugin.ts` and is not loaded
|
|
208
|
+
when only `plugin-v2` is configured. The companion `execute.after` hook and the
|
|
209
|
+
`permission.evaluate` hook are registration points with no V2-side behaviour,
|
|
210
|
+
and the V1 caller/target delegation matrix for native `task()` is not enforced
|
|
211
|
+
on V2.
|
|
212
|
+
|
|
213
|
+
Every V2 tool declares an `output` schema. OpenCode 2.0.x requires the
|
|
214
|
+
declaration and the resolved result to agree in both directions, so an
|
|
215
|
+
undeclared tool — or one that returns `output` without declaring it — fails on
|
|
216
|
+
every call. Unsupported V2 features are `legacy-hooks` (the V1-specific hook
|
|
217
|
+
surface), `catalog-transform`, `integration-transform`, `skill-transform` and
|
|
218
|
+
`goal-tools`.
|
|
196
219
|
|
|
197
220
|
The package exposes both contracts as importable exports: `pantheon-opencode/plugin`
|
|
198
221
|
(V1), `pantheon-opencode/plugin-v2` (V2) and `pantheon-opencode/v2-bridge`
|
|
@@ -231,6 +254,29 @@ The installer still writes the compatibility settings required by the selected
|
|
|
231
254
|
OpenCode host, such as `experimental.subagent_depth`; this does not convert a
|
|
232
255
|
V1 plugin into V2 or provide V2 with V1 hooks.
|
|
233
256
|
|
|
257
|
+
Pantheon does not set a step ceiling on agents. On OpenCode 2 the `steps`
|
|
258
|
+
field is optional and has no default: absent means the host applies no native
|
|
259
|
+
limit. A config written before this change still carries the old value, and the
|
|
260
|
+
V2 install path strips it from every agent Pantheon manages — an agent you
|
|
261
|
+
define yourself is never touched, and the retiring V1 singular `agent` block
|
|
262
|
+
is left alone. Run the installer once to clear a stale value from an existing
|
|
263
|
+
config:
|
|
264
|
+
|
|
265
|
+
```bash
|
|
266
|
+
npx pantheon-opencode init --opencode-version=v2
|
|
267
|
+
```
|
|
268
|
+
|
|
269
|
+
Context control is not a Pantheon setting. Whatever budget an agent runs
|
|
270
|
+
against comes from the compaction settings of the OpenCode host you are
|
|
271
|
+
running, and Pantheon does not define them.
|
|
272
|
+
|
|
273
|
+
When a V1 `agent` block and a V2 `agents` block are both present — which is
|
|
274
|
+
what an installed `opencode.json` carries — the V1→V2 conversion merges them
|
|
275
|
+
instead of letting one replace the other. The V2 block is the base, the
|
|
276
|
+
framework-managed fields from the V1 block overwrite, and your own fields are
|
|
277
|
+
preserved, matching the precedence the installer already applies elsewhere.
|
|
278
|
+
Key order does not matter.
|
|
279
|
+
|
|
234
280
|
## Updating between releases (beta.5+)
|
|
235
281
|
|
|
236
282
|
One command keeps an existing installation current:
|
package/README.pt-BR.md
CHANGED
|
@@ -122,16 +122,36 @@ registrados juntos.
|
|
|
122
122
|
|---|---|---|
|
|
123
123
|
| Chave de config do OpenCode | `plugin` singular | `plugins` plural |
|
|
124
124
|
| Registro Pantheon | `src/plugin.ts` mais `src/plugins/pantheon-hooks.ts` | `pantheon-opencode/plugin-v2` (`src/plugin-v2.ts`) |
|
|
125
|
-
| Contrato de runtime | Plugin Pantheon V1: 6 ferramentas (`hashline_edit`, as 3 ferramentas de goal, `pantheon_cost`, `pantheon_model`), hooks de evento/ferramenta e tratamento de compactação V1 | Plugin V2 completo:
|
|
125
|
+
| Contrato de runtime | Plugin Pantheon V1: 6 ferramentas (`hashline_edit`, as 3 ferramentas de goal, `pantheon_cost`, `pantheon_model`), hooks de evento/ferramenta e tratamento de compactação V1 | Plugin V2 completo: 3 ferramentas (`hashline_edit`, `pantheon_cost`, `pantheon_model`), 4 assinaturas de eventos, session hooks (`prompt`, `context`), um hook de ferramenta `execute.before` que impõe sessões somente-leitura, além de transforms de configuração |
|
|
126
126
|
| APIs V1 | Registradas | Definições próprias de ferramentas via `ctx.tool.transform()` — não pelo caminho do plugin V1 |
|
|
127
127
|
|
|
128
|
-
O plugin V2 fornece
|
|
129
|
-
`pantheon_goal_create`, `pantheon_goal_get`, `pantheon_goal_update`,
|
|
128
|
+
O plugin V2 fornece 3 ferramentas de orquestração (`hashline_edit`,
|
|
130
129
|
`pantheon_cost`, `pantheon_model`), 4 assinaturas de eventos (`session.created`,
|
|
131
130
|
`session.idle`, `session.error`, `session.compacted`), session hooks (`prompt`,
|
|
132
|
-
`context`) e
|
|
133
|
-
|
|
134
|
-
`
|
|
131
|
+
`context`) e um hook de ferramenta `execute.before` que impõe sessões
|
|
132
|
+
somente-leitura. As 3 ferramentas de goal **não** são
|
|
133
|
+
registradas no V2: o goal loop precisa de um `GoalStore`, de um
|
|
134
|
+
`GoalLoopClient` e de um `BackgroundJobBoard`, nenhum dos quais o
|
|
135
|
+
`PluginContext` do V2 expõe, então a lacuna é reportada como o marcador de
|
|
136
|
+
recurso sem suporte `goal-tools`. Use o contrato V1 se precisar do goal loop.
|
|
137
|
+
|
|
138
|
+
O hook `execute.before` do V2 é o ponto de imposição de sessões somente-leitura
|
|
139
|
+
dessa superfície: o host inclui o agente ativo no evento, um agente somente-leitura
|
|
140
|
+
(`apollo`, `gaia`) registra sua sessão, e o `createEnforcementGuard`
|
|
141
|
+
compartilhado lança para negar `edit`, `write`, `bash`, `task`,
|
|
142
|
+
`hashline_edit` e `pantheon_model`. Ele **não** depende do hook
|
|
143
|
+
`tool.execute.before` do V1, que vive em `src/plugin.ts` e não é carregado
|
|
144
|
+
quando apenas `plugin-v2` está configurado. Os hooks companheiros
|
|
145
|
+
`execute.after` e `permission.evaluate` são pontos de registro sem comportamento
|
|
146
|
+
do lado V2, e a matriz de delegação caller/target da V1 para o `task()` nativo
|
|
147
|
+
não é imposta no V2.
|
|
148
|
+
|
|
149
|
+
Toda ferramenta V2 declara um schema `output`. O OpenCode 2.0.x exige que a
|
|
150
|
+
declaração e o resultado resolvido concordem nos dois sentidos, então uma
|
|
151
|
+
ferramenta sem declaração — ou que retorne `output` sem declará-lo — falha em
|
|
152
|
+
toda chamada. Os recursos V2 sem suporte são `legacy-hooks` (a superfície de
|
|
153
|
+
hooks específica do V1), `catalog-transform`, `integration-transform`,
|
|
154
|
+
`skill-transform` e `goal-tools`.
|
|
135
155
|
|
|
136
156
|
O pacote expõe os dois contratos como exports importáveis:
|
|
137
157
|
`pantheon-opencode/plugin` (V1), `pantheon-opencode/plugin-v2` (V2) e
|
|
@@ -171,6 +191,29 @@ O instalador continua gravando as configurações de compatibilidade exigidas
|
|
|
171
191
|
pelo host OpenCode selecionado, como `experimental.subagent_depth`; isso não
|
|
172
192
|
converte um plugin V1 em V2 nem dá hooks V1 ao V2.
|
|
173
193
|
|
|
194
|
+
O Pantheon não define mais um teto de passos para os agentes. No OpenCode 2 o
|
|
195
|
+
campo `steps` é opcional e não tem default: ausente significa que o host não
|
|
196
|
+
aplica nenhum limite nativo. Uma config escrita antes dessa mudança ainda
|
|
197
|
+
carrega o valor antigo, e o caminho de instalação v2 o remove de todos os
|
|
198
|
+
agentes que o Pantheon gerencia — um agente definido por você nunca é tocado,
|
|
199
|
+
e o bloco V1 singular `agent`, em retirada, é deixado intacto. Rode o
|
|
200
|
+
instalador uma vez para limpar um valor antigo de uma config existente:
|
|
201
|
+
|
|
202
|
+
```bash
|
|
203
|
+
npx pantheon-opencode init --opencode-version=v2
|
|
204
|
+
```
|
|
205
|
+
|
|
206
|
+
O controle de contexto não é uma configuração do Pantheon. Qualquer orçamento
|
|
207
|
+
que um agente terá vem das settings de compactação do próprio host OpenCode,
|
|
208
|
+
e o Pantheon não as define.
|
|
209
|
+
|
|
210
|
+
Quando um bloco V1 `agent` e um bloco V2 `agents` coexistem — o que acontece
|
|
211
|
+
em qualquer `opencode.json` já instalado — a conversão V1→V2 mescla os dois em
|
|
212
|
+
vez de deixar um substituir o outro. O bloco V2 é a base, os campos
|
|
213
|
+
gerenciados que vêm do V1 sobrescrevem, e os seus próprios campos são
|
|
214
|
+
preservados, com a mesma precedência que o instalador já aplica em outros
|
|
215
|
+
pontos. A ordem das chaves não muda o resultado.
|
|
216
|
+
|
|
174
217
|
## Atualizando entre releases (beta.5+)
|
|
175
218
|
|
|
176
219
|
Um comando mantém uma instalação existente em dia:
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pantheon-opencode",
|
|
3
|
-
"version": "1.6.0-beta.
|
|
3
|
+
"version": "1.6.0-beta.3",
|
|
4
4
|
"description": "Multi-agent orchestration for OpenCode — 14 specialized agents, TDD, Themis review gates.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"workspaces": [
|
|
@@ -105,6 +105,7 @@
|
|
|
105
105
|
"@biomejs/biome": "2.5.13",
|
|
106
106
|
"@commitlint/cli": "21.2.2",
|
|
107
107
|
"@commitlint/config-conventional": "21.2.2",
|
|
108
|
+
"ajv": "8.20.0",
|
|
108
109
|
"husky": "^9.1.7",
|
|
109
110
|
"tsx": "4.23.13",
|
|
110
111
|
"typescript": "6.0.3"
|
package/plugin.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pantheon",
|
|
3
3
|
"publisher": "ils15",
|
|
4
|
-
"version": "1.6.0-beta.
|
|
4
|
+
"version": "1.6.0-beta.3",
|
|
5
5
|
"displayName": "Pantheon",
|
|
6
6
|
"description": "Conductor-Delegate orchestration platform with 14 specialized agents, 21 skills, and OpenCode support.",
|
|
7
7
|
"agents": "./src/agents",
|
package/pyproject.toml
CHANGED
|
@@ -304,6 +304,114 @@ const V2_TO_V1_RENAMES = Object.fromEntries(
|
|
|
304
304
|
Object.entries(V1_TO_V2_RENAMES).map(([k, v]) => [v, k]),
|
|
305
305
|
)
|
|
306
306
|
|
|
307
|
+
/**
|
|
308
|
+
* Agent fields owned by the framework. Mirrors the exported MANAGED_FIELDS in
|
|
309
|
+
* install/opencode.mjs plus `source`, which the installer writes next to them.
|
|
310
|
+
* The migration runs before that merge, so it has to apply the same precedence
|
|
311
|
+
* itself when both `agent` and `agents` coexist: these come from the V1 block,
|
|
312
|
+
* every other field stays the user's.
|
|
313
|
+
*
|
|
314
|
+
* `permissions` is the V2 name — the converter renames the V1 `permission`
|
|
315
|
+
* block before the merge runs.
|
|
316
|
+
*
|
|
317
|
+
* `description` is deliberately absent, and this list is deliberately narrower
|
|
318
|
+
* than what the installer writes. The installer writes a description only when
|
|
319
|
+
* it creates an agent (install/opencode.mjs, the new-agent branch); on an agent
|
|
320
|
+
* that already exists it leaves the value alone, under its own comment "Existing
|
|
321
|
+
* agent fields belong to the user". So a description is framework-owned for a
|
|
322
|
+
* NEW agent and user-owned for an EXISTING one. Listing it here would make the
|
|
323
|
+
* migration stricter than the installer and would overwrite a description the
|
|
324
|
+
* installer deliberately preserves. mergeManagedAgents carries the value across
|
|
325
|
+
* instead, so it survives the merge without being treated as framework-owned.
|
|
326
|
+
*
|
|
327
|
+
* KNOWN DEBT: the installer still writes the V1 spelling `permission`, while V2
|
|
328
|
+
* reads `permissions`. An agent that goes through this merge can therefore end
|
|
329
|
+
* up carrying both keys, with `permission` left as dead weight that nothing
|
|
330
|
+
* reads. Tracked for the next slice; not fixed here, because picking a winner
|
|
331
|
+
* changes what an install writes.
|
|
332
|
+
*/
|
|
333
|
+
export const MANAGED_AGENT_FIELDS = Object.freeze([
|
|
334
|
+
'source',
|
|
335
|
+
'temperature',
|
|
336
|
+
'color',
|
|
337
|
+
'permissions',
|
|
338
|
+
'mode',
|
|
339
|
+
'hidden',
|
|
340
|
+
'disable_model_invocation',
|
|
341
|
+
])
|
|
342
|
+
|
|
343
|
+
/**
|
|
344
|
+
* Compare this module's managed-agent list against the installer's.
|
|
345
|
+
*
|
|
346
|
+
* The two merge paths have to agree: the migration reimplements the installer's
|
|
347
|
+
* merge because it runs before it. A field present on only one side is silent
|
|
348
|
+
* divergence — either the migration overwrites a field the installer leaves
|
|
349
|
+
* alone, or it stops mirroring one the installer now writes. Both directions
|
|
350
|
+
* matter, so the result is reported as a pair of differences rather than a
|
|
351
|
+
* boolean.
|
|
352
|
+
*
|
|
353
|
+
* Names are compared after the converter's renames, because that is the
|
|
354
|
+
* spelling the merge actually sees.
|
|
355
|
+
*
|
|
356
|
+
* @param {string[]} installerFields - MANAGED_FIELDS as exported by install/opencode.mjs
|
|
357
|
+
* @returns {{ onlyInMigration: string[], onlyInInstaller: string[] }}
|
|
358
|
+
*/
|
|
359
|
+
export function managedAgentFieldDrift(installerFields) {
|
|
360
|
+
const toV2Name = (field) => V1_TO_V2_RENAMES[field] ?? field
|
|
361
|
+
const migration = new Set(MANAGED_AGENT_FIELDS.map(toV2Name))
|
|
362
|
+
const installer = new Set(installerFields.map(toV2Name))
|
|
363
|
+
return {
|
|
364
|
+
onlyInMigration: [...migration].filter((f) => !installer.has(f)).sort(),
|
|
365
|
+
onlyInInstaller: [...installer].filter((f) => !migration.has(f)).sort(),
|
|
366
|
+
}
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
/** @returns {boolean} true for a plain name→config map (not null/array) */
|
|
370
|
+
function isAgentMap(value) {
|
|
371
|
+
return typeof value === 'object' && value !== null && !Array.isArray(value)
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
/**
|
|
375
|
+
* Merge the converted V1 `agent` block into an existing V2 `agents` base.
|
|
376
|
+
*
|
|
377
|
+
* Agents present in both are merged field by field: the base is kept, keys the
|
|
378
|
+
* base does not have are filled in from the V1 block, and MANAGED_AGENT_FIELDS
|
|
379
|
+
* are overwritten by the V1 values. Agents present in only one block are taken
|
|
380
|
+
* as they are — a union, not a replacement.
|
|
381
|
+
*
|
|
382
|
+
* Filling the gaps matters, and it is not description-specific: any key present
|
|
383
|
+
* only in the V1 block used to vanish here — `model`, `provider` and `mcp` too.
|
|
384
|
+
* `description` is the visible case, because the installer never writes one for
|
|
385
|
+
* an existing agent, so the V1 block is the only place that value can come from
|
|
386
|
+
* and the agent lost it on every install, permanently. Filling gaps can never
|
|
387
|
+
* destroy a base value; on conflict the base still wins, as before.
|
|
388
|
+
*
|
|
389
|
+
* @param {object} base - existing V2 `agents` map
|
|
390
|
+
* @param {object} managed - converted V1 `agent` map
|
|
391
|
+
* @returns {object} merged map
|
|
392
|
+
*/
|
|
393
|
+
function mergeManagedAgents(base, managed) {
|
|
394
|
+
const merged = { ...base }
|
|
395
|
+
|
|
396
|
+
for (const [name, agentConfig] of Object.entries(managed)) {
|
|
397
|
+
const existing = merged[name]
|
|
398
|
+
if (!isAgentMap(existing) || !isAgentMap(agentConfig)) {
|
|
399
|
+
merged[name] = agentConfig
|
|
400
|
+
continue
|
|
401
|
+
}
|
|
402
|
+
const entry = { ...existing }
|
|
403
|
+
for (const [field, value] of Object.entries(agentConfig)) {
|
|
404
|
+
if (!(field in entry)) entry[field] = value
|
|
405
|
+
}
|
|
406
|
+
for (const field of MANAGED_AGENT_FIELDS) {
|
|
407
|
+
if (field in agentConfig) entry[field] = agentConfig[field]
|
|
408
|
+
}
|
|
409
|
+
merged[name] = entry
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
return merged
|
|
413
|
+
}
|
|
414
|
+
|
|
307
415
|
// ---------------------------------------------------------------------------
|
|
308
416
|
// Public API
|
|
309
417
|
// ---------------------------------------------------------------------------
|
|
@@ -333,7 +441,20 @@ export function migrateV1toV2(config) {
|
|
|
333
441
|
}
|
|
334
442
|
agents[agentName] = ac
|
|
335
443
|
}
|
|
336
|
-
|
|
444
|
+
// An `agents` (V2) block may already exist — the installed opencode.json
|
|
445
|
+
// carries both, in either key order. Renaming onto it with a plain
|
|
446
|
+
// replace dropped the whole managed merge, so merge instead.
|
|
447
|
+
const base = result.agents
|
|
448
|
+
result.agents = isAgentMap(base) ? mergeManagedAgents(base, agents) : agents
|
|
449
|
+
} else if (key === 'agents') {
|
|
450
|
+
// V2 block: it is the base. The singular `agent` key may already have
|
|
451
|
+
// been converted above, in which case merge the two.
|
|
452
|
+
const managed = result.agents
|
|
453
|
+
if (isAgentMap(value)) {
|
|
454
|
+
result.agents = isAgentMap(managed) ? mergeManagedAgents(value, managed) : value
|
|
455
|
+
} else if (!isAgentMap(managed)) {
|
|
456
|
+
result.agents = value
|
|
457
|
+
}
|
|
337
458
|
} else if (key === 'provider' && typeof value === 'object' && value !== null) {
|
|
338
459
|
const providers = {}
|
|
339
460
|
for (const [provName, provConfig] of Object.entries(value)) {
|
|
@@ -61,6 +61,33 @@ const COMPONENT_NAMES = [
|
|
|
61
61
|
'runtime',
|
|
62
62
|
]
|
|
63
63
|
|
|
64
|
+
/**
|
|
65
|
+
* Per-agent fields owned by the framework: for an agent that already exists in
|
|
66
|
+
* the target config these are overwritten from the canonical source, and for a
|
|
67
|
+
* new agent they are the seed. Everything else on an existing entry is the
|
|
68
|
+
* user's and is left alone.
|
|
69
|
+
*
|
|
70
|
+
* `source` is written right next to these (for both new and existing agents) but
|
|
71
|
+
* is kept out of the loop so it can also be set independently — the migration in
|
|
72
|
+
* config-migration.mjs mirrors this list plus `source`, and tests assert the two
|
|
73
|
+
* stay equal as sets.
|
|
74
|
+
*
|
|
75
|
+
* KNOWN DEBT: `permission` is the V1 spelling. V2 reads `permissions`, so an
|
|
76
|
+
* agent can carry both keys after an install and the V1 one becomes dead weight.
|
|
77
|
+
* Tracked for the next slice; changing it here would change what an install
|
|
78
|
+
* writes.
|
|
79
|
+
*
|
|
80
|
+
* @type {readonly string[]}
|
|
81
|
+
*/
|
|
82
|
+
export const MANAGED_FIELDS = Object.freeze([
|
|
83
|
+
'temperature',
|
|
84
|
+
'color',
|
|
85
|
+
'permission',
|
|
86
|
+
'mode',
|
|
87
|
+
'hidden',
|
|
88
|
+
'disable_model_invocation',
|
|
89
|
+
])
|
|
90
|
+
|
|
64
91
|
function deepClone(value) {
|
|
65
92
|
return JSON.parse(JSON.stringify(value))
|
|
66
93
|
}
|
|
@@ -836,7 +863,6 @@ export async function installOpenCode(
|
|
|
836
863
|
if (fm.mode) agent.mode = fm.mode
|
|
837
864
|
if (fm.hidden) agent.hidden = fm.hidden
|
|
838
865
|
if (fm.temperature !== undefined) agent.temperature = fm.temperature
|
|
839
|
-
if (fm.steps !== undefined) agent.steps = fm.steps
|
|
840
866
|
// Support both hyphen (YAML) and underscore (JSON) key variants
|
|
841
867
|
if (fm['disable-model-invocation'] !== undefined)
|
|
842
868
|
agent.disable_model_invocation = fm['disable-model-invocation']
|
|
@@ -855,15 +881,6 @@ export async function installOpenCode(
|
|
|
855
881
|
|
|
856
882
|
const canonicalAgentConfig = readAgentConfigFromCanonical()
|
|
857
883
|
const agentSources = getAgentSources(agentPrefix)
|
|
858
|
-
const MANAGED_FIELDS = [
|
|
859
|
-
'steps',
|
|
860
|
-
'temperature',
|
|
861
|
-
'color',
|
|
862
|
-
'permission',
|
|
863
|
-
'mode',
|
|
864
|
-
'hidden',
|
|
865
|
-
'disable_model_invocation',
|
|
866
|
-
]
|
|
867
884
|
|
|
868
885
|
if (config.agent === undefined) config.agent = {}
|
|
869
886
|
|
|
@@ -1222,6 +1239,22 @@ export async function installOpenCode(
|
|
|
1222
1239
|
if (version === 'v2') {
|
|
1223
1240
|
info(S.v2Migrating)
|
|
1224
1241
|
configToWrite = migrateV1toV2(config)
|
|
1242
|
+
|
|
1243
|
+
// Pantheon no longer sets a step ceiling. Any `steps` sitting on a managed
|
|
1244
|
+
// agent was written by an earlier install and never chosen by the user —
|
|
1245
|
+
// the merge above is additive by design (no delete path), so drop it here
|
|
1246
|
+
// and let the host default apply. This runs after the migration because
|
|
1247
|
+
// migrateV1toV2 copies the V1 `agent` block across verbatim (including any
|
|
1248
|
+
// stale `steps`), which is exactly how the value reappears in V2.
|
|
1249
|
+
// User-defined agents are out of scope and keep their value; the V1
|
|
1250
|
+
// singular `agent` block is deliberately untouched (V1 is retiring).
|
|
1251
|
+
const v2Agents = configToWrite.agents
|
|
1252
|
+
if (v2Agents && typeof v2Agents === 'object' && !Array.isArray(v2Agents)) {
|
|
1253
|
+
for (const name of Object.keys(canonicalAgentConfig)) {
|
|
1254
|
+
const entry = v2Agents[name]
|
|
1255
|
+
if (entry && typeof entry === 'object' && !Array.isArray(entry)) delete entry.steps
|
|
1256
|
+
}
|
|
1257
|
+
}
|
|
1225
1258
|
}
|
|
1226
1259
|
|
|
1227
1260
|
const configContent = `${JSON.stringify(configToWrite, null, 2)}\n`
|
package/skills-lock.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"src/skills/agent-coordination/SKILL.md": "cfd040ef29e3dd0fd5e0e67773874facf7c06e3284898afe1fddba4456c3f1aa",
|
|
3
3
|
"src/skills/artifact-management/SKILL.md": "c60e493666bedf46e9113d0dd20106489d5c5a73905e1370fcfc1c0d71c59185",
|
|
4
|
-
"src/skills/auto-continue/SKILL.md": "
|
|
4
|
+
"src/skills/auto-continue/SKILL.md": "4fe256d44ee9828d622a0d8b63cfc6bb39a317189a9ccf19494a6efd4527a7a9",
|
|
5
5
|
"src/skills/clonedeps/SKILL.md": "3e0b43cf96da98c83699dc48db3fd58fbfda84b9022bb70d3747875c81c3e124",
|
|
6
6
|
"src/skills/code-review-checklist/SKILL.md": "0e59367c65ef4608db80a0674279f32cfc3f67dca7236f233b61a47fe506579f",
|
|
7
7
|
"src/skills/codemap/SKILL.md": "87af5271e1576e154cadff313eab16264b726e43d094520736b7a5b03b409a4a",
|
package/src/agents/aphrodite.md
CHANGED
package/src/agents/apollo.md
CHANGED
package/src/agents/athena.md
CHANGED
package/src/agents/demeter.md
CHANGED
package/src/agents/gaia.md
CHANGED
package/src/agents/hephaestus.md
CHANGED
package/src/agents/hermes.md
CHANGED
package/src/agents/iris.md
CHANGED