pantheon-opencode 1.4.2 → 1.4.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/README.md +27 -115
- package/package.json +1 -1
- package/src/agents/zeus.md +14 -25
- package/src/pantheon/cost-command.ts +7 -7
- package/src/pantheon/delegation-enforce.ts +12 -13
- package/src/pantheon/goal-loop.ts +2 -2
- package/src/pantheon/goal-store.ts +1 -1
- package/src/pantheon/hashline/tool.ts +2 -2
- package/src/pantheon/model-command.ts +87 -34
- package/src/pantheon/tool-context.ts +41 -0
- package/src/plugin.ts +41 -193
- package/src/plugins/tui/package-lock.json +2 -2
- package/src/plugins/tui/package.json +1 -1
- package/src/plugins/tui/src/index.tsx +8 -6
- package/src/pantheon/delegation-classifier.ts +0 -307
- package/src/pantheon/delegation-finalize.ts +0 -374
- package/src/pantheon/delegation-notify.ts +0 -197
- package/src/pantheon/delegation-toast.ts +0 -73
- package/src/pantheon/delegation.ts +0 -1120
- package/src/pantheon/permission-eval.ts +0 -284
- package/src/pantheon/step-cap.ts +0 -110
- package/src/pantheon/zeus-delegate-with-retry.ts +0 -406
package/README.md
CHANGED
|
@@ -218,113 +218,28 @@ incompatible schema.
|
|
|
218
218
|
|
|
219
219
|
### Background Delegation
|
|
220
220
|
|
|
221
|
-
Zeus (and other root sessions)
|
|
222
|
-
via
|
|
223
|
-
(`.pantheon/board/state.json`, 24h
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
| `pantheon_delegation_list` | `{}` | Lists the current session's delegations with `[unread]` on finished, unreconciled jobs. |
|
|
231
|
-
|
|
232
|
-
**Agent activity visibility.** While `pantheon_delegation_read` blocks on a
|
|
233
|
-
running job, it samples the child session's messages every ~2s and appends the
|
|
234
|
-
collected lines to the report as a trailing `## Agent Activity` section (latest
|
|
235
|
-
tool calls with truncated args, or user/assistant text, capped at ~200 chars per
|
|
236
|
-
line) — so you can see what the agent is doing during the wait. Likewise,
|
|
237
|
-
`pantheon_delegation_list` shows a `last activity:` line for running jobs.
|
|
238
|
-
Fail-open: if the child session messages are unavailable (or empty), the read
|
|
239
|
-
returns the report exactly as before — the activity sampling never breaks the
|
|
240
|
-
delegation read.
|
|
221
|
+
Zeus (and other root sessions) dispatch agents as **background child sessions**
|
|
222
|
+
via OpenCode's native `task(background=true, ...)` API. The plugin layer keeps a
|
|
223
|
+
persistent job board (`BackgroundJobBoard`, `.pantheon/board/state.json`, 24h
|
|
224
|
+
TTL) that tracks in-flight work and carries it forward across context
|
|
225
|
+
compaction, so a compacted session does not lose track of running delegations.
|
|
226
|
+
|
|
227
|
+
The plugin no longer ships a delegate toolset (`pantheon_delegate`,
|
|
228
|
+
`pantheon_delegation_read`, `pantheon_delegation_list` were removed in 1.4.3).
|
|
229
|
+
Dispatch and result collection use the native `task()` / `task_status()` API.
|
|
241
230
|
|
|
242
231
|
**Completion visibility — zero chat noise.** Delegation notifications are never
|
|
243
232
|
injected into the chat transcript: neither `chat.message` nor any other chat
|
|
244
233
|
hook or transcript marker is used as a delivery channel. When a job reaches a
|
|
245
|
-
terminal state
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
(`PANTHEON_TOASTS` gate), and compaction carry-forward. Reconcile is an
|
|
251
|
-
acknowledgment, not a completion — it never re-fires the terminal transition.
|
|
234
|
+
terminal state, the plugin writes a file-only audit log line (echo opt-in via
|
|
235
|
+
`PANTHEON_HOOKS_LOG`). Completion visibility lives in the legitimate channels:
|
|
236
|
+
the board `[unread]` marker, TUI toasts (`PANTHEON_TOASTS` gate), and compaction
|
|
237
|
+
carry-forward. Reconcile is an acknowledgment, not a completion — it never
|
|
238
|
+
re-fires the terminal transition.
|
|
252
239
|
|
|
253
240
|
**Timeout:** `background_delegation.timeout_ms` (default `900000` = 15 min). A job
|
|
254
241
|
that has not reached a terminal state is finalized as `error`/`timedOut`.
|
|
255
242
|
|
|
256
|
-
**Runtime delegation environment variables:**
|
|
257
|
-
|
|
258
|
-
| Variable | Default | Scope and behavior |
|
|
259
|
-
|---|---:|---|
|
|
260
|
-
| `PANTHEON_ATHENA_APOLLO_BUDGET` | `5` dispatches | In-memory per `createDelegationTools()` factory/process instance and parent session, for the `athena → apollo` read-only exception. |
|
|
261
|
-
| `PANTHEON_HERMES_APOLLO_BUDGET` | `5` dispatches | In-memory per `createDelegationTools()` factory/process instance and parent session, for the `hermes → apollo` read-only exception. |
|
|
262
|
-
| `PANTHEON_DELEGATION_TIMEOUT_MS` | `900000` ms (15 min) | Per delegated child wall-clock timeout. The value is read when the plugin factory is created; unset or invalid values use the default. |
|
|
263
|
-
|
|
264
|
-
Budgets are reserved before asynchronous child-session creation, so real
|
|
265
|
-
concurrent calls cannot overshoot them. They reset when the factory/process
|
|
266
|
-
restarts. If `ToolContext.agent` is missing, the default runtime matrix rejects
|
|
267
|
-
the delegation (fail-closed); no caller identity or budget is inferred from the
|
|
268
|
-
target, prompt, or session state. An explicitly legacy host using
|
|
269
|
-
`enforceRuntimeMatrix: false` skips the budget because the caller cannot be
|
|
270
|
-
identified and emits a warning. These budgets are separate from
|
|
271
|
-
`background_delegation.max_concurrent_per_agent`: concurrency limits the number
|
|
272
|
-
of currently running children, while an exception budget limits total dispatch
|
|
273
|
-
reservations for that parent session during the factory lifetime.
|
|
274
|
-
|
|
275
|
-
**Read-only enforcement:** delegating with `read_only: true`, or delegating to an
|
|
276
|
-
agent in `background_delegation.read_only_agents` (`apollo`, `gaia`), registers
|
|
277
|
-
the child session as read-only — the `tool.execute.before` guard denies
|
|
278
|
-
`edit`/`write`/`bash`/`task` inside that session.
|
|
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
|
-
|
|
296
|
-
**R1 — Per-error-type retry + provider cooldown (LiteLLM pattern):**
|
|
297
|
-
`retry_policy` in `src/routing.yml` maps an error class (`auth`, `rate_limit`,
|
|
298
|
-
`timeout`, `other`) to max retries before escalating — `auth` is never retried
|
|
299
|
-
(0), `rate_limit` gets 3, `timeout` 2, `other` 1. Retries use exponential
|
|
300
|
-
backoff (base 1s, doubling, capped 30s). `cooldown` (`allowed_fails`,
|
|
301
|
-
`cooldown_time_seconds`) skips a provider for the configured window after that
|
|
302
|
-
many consecutive failures; a success resets the counter. Enforcement lives in
|
|
303
|
-
`RetryPolicyEngine` (`src/pantheon/retry-policy.ts`) and the R1 path of
|
|
304
|
-
`zeusDelegateWithRetry` (`src/pantheon/zeus-delegate-with-retry.ts`) — pass
|
|
305
|
-
`retryPolicy`/`cooldown`/`provider` to `createZeusRetryHelper`. In-memory only
|
|
306
|
-
(no Redis).
|
|
307
|
-
|
|
308
|
-
**R4 — Per-agent step caps:** `agents.<name>.max_steps` in `src/routing.yml`
|
|
309
|
-
gives each agent a step budget (e.g. `apollo: 25`, `themis: 25`, `athena: 15`).
|
|
310
|
-
When an agent reaches `max_steps` it is forced to summarize-and-stop: a
|
|
311
|
-
delegation to an already-capped agent is skipped with a `[STEP CAP REACHED]`
|
|
312
|
-
summary (no child session created), and a dispatch that hits the cap appends a
|
|
313
|
-
stop instruction to the prompt. Enforcement: `StepCapTracker`
|
|
314
|
-
(`src/pantheon/step-cap.ts`) wired into `pantheon_delegate`. The tracker is
|
|
315
|
-
**permanent-per-process** — counters accumulate for the process lifetime and
|
|
316
|
-
are intentionally not reset on success/session end (a per-session reset would
|
|
317
|
-
let an agent exceed its process budget).
|
|
318
|
-
|
|
319
|
-
**O5 — Permission globs for delegation:** `permission.task` in `src/routing.yml`
|
|
320
|
-
controls which subagents an agent may invoke via glob patterns (last matching
|
|
321
|
-
rule wins). A deny removes the agent from the delegate tool description
|
|
322
|
-
entirely (not just blocks the call) — preventing circular delegation at the
|
|
323
|
-
permission layer. Default `"*": allow` keeps the existing runtime matrix
|
|
324
|
-
(zeus → anyone; athena/hermes → apollo only). Enforcement:
|
|
325
|
-
`src/pantheon/permission-globs.ts` + the `permissionTask` option on
|
|
326
|
-
`createDelegationTools` / `createEnforcementGuard`.
|
|
327
|
-
|
|
328
243
|
**Compaction carry-forward:** during context compaction, running and unread
|
|
329
244
|
terminal delegations (capped at `background_delegation.max_compaction_items`,
|
|
330
245
|
default 10) are carried into the compacted context so the parent doesn't lose
|
|
@@ -353,8 +268,7 @@ logged warn and pass-through.
|
|
|
353
268
|
|
|
354
269
|
**Post-compaction state re-assertion (1.3.4):** on `session.compacted`, fresh
|
|
355
270
|
state — running/unread delegations from the board + active goals, capped at 10
|
|
356
|
-
lines — remains available through compaction carry-forward
|
|
357
|
-
(`pantheon_delegation_list` / `pantheon_delegation_read`). A session with
|
|
271
|
+
lines — remains available through compaction carry-forward. A session with
|
|
358
272
|
nothing to assert is a silent skip; no chat transcript injection is performed.
|
|
359
273
|
|
|
360
274
|
**Preemptive compaction check (1.3.4):** a pure threshold core
|
|
@@ -378,8 +292,8 @@ omits the child model.
|
|
|
378
292
|
**Delegation log hygiene (1.3.4):** `delegations.log` now records the real
|
|
379
293
|
`task_id` (omitted when empty, never `""`) and a **numeric** `duration_ms`
|
|
380
294
|
(null when unset) so downstream aggregation works. Terminal events are
|
|
381
|
-
file-only audit entries; completion is surfaced by TUI toast (when enabled)
|
|
382
|
-
|
|
295
|
+
file-only audit entries; completion is surfaced by TUI toast (when enabled)
|
|
296
|
+
and the board — never by chat delivery.
|
|
383
297
|
|
|
384
298
|
**Env vars & config:**
|
|
385
299
|
|
|
@@ -387,14 +301,13 @@ the board, and the list/read tools — never by chat delivery.
|
|
|
387
301
|
- `PANTHEON_TOASTS` — TUI toast gate for the delegation lifecycle signals.
|
|
388
302
|
Default `{errors, delegations, council}`; `PANTHEON_TOASTS=off` disables all
|
|
389
303
|
TUI toasts (delegation signals then only surface via the board `[unread]`
|
|
390
|
-
marker
|
|
391
|
-
|
|
304
|
+
marker and the on-disk audit log — never via chat.message injection from the
|
|
305
|
+
plugin).
|
|
392
306
|
- `background_delegation` section in `src/routing.yml` — `timeout_ms`,
|
|
393
|
-
`poll_ms`, `max_compaction_items`, `prune_ttl_ms`, `
|
|
394
|
-
`
|
|
307
|
+
`poll_ms`, `max_compaction_items`, `prune_ttl_ms`, `session_max`,
|
|
308
|
+
`retry_count`.
|
|
395
309
|
|
|
396
|
-
See `src/agents/zeus.md` for the agent-facing delegation protocol
|
|
397
|
-
`src/pantheon/delegation.ts` for the tool implementations.
|
|
310
|
+
See `src/agents/zeus.md` for the agent-facing delegation protocol.
|
|
398
311
|
|
|
399
312
|
### Real-time Delegations panel (1.3.4)
|
|
400
313
|
|
|
@@ -402,7 +315,7 @@ The TUI sidebar ships a live **Delegations** panel (open by default, above the
|
|
|
402
315
|
collapsed Sessions list). Its primary source is `api.client.session.children`,
|
|
403
316
|
so it shows **both** kinds of background work in one place:
|
|
404
317
|
|
|
405
|
-
-
|
|
318
|
+
- **Board-tracked children** — rendered with their board alias tag
|
|
406
319
|
(`[apo-1]`) and enriched from the `.pantheon/delegations/` report.
|
|
407
320
|
- **Native `task()` children** — rendered with a distinct `[task]` tag (info
|
|
408
321
|
color) when no board report exists for the session.
|
|
@@ -674,7 +587,7 @@ and is pinned to concrete `provider/model` IDs in [src/routing.yml](src/routing.
|
|
|
674
587
|
The abstract tiers above are pinned to concrete `provider/model` IDs by **4 built-in model presets**.
|
|
675
588
|
Cada preset mapeia os 14 agentes para um modelo específico + `reasoning_effort` por papel (planners/reviewers, implementers, scouts).
|
|
676
589
|
|
|
677
|
-
> **Default = herdar do chat (sem `active-preset.json`)** — sem preset ativo, os delegates **herdam nativamente** o modelo do chat pai
|
|
590
|
+
> **Default = herdar do chat (sem `active-preset.json`)** — sem preset ativo, os delegates **herdam nativamente** o modelo do chat pai. 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: 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
591
|
|
|
679
592
|
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
593
|
|
|
@@ -1141,16 +1054,15 @@ Use the isolated sandbox (`~/pantheon-sandbox/`) — **never** the dev environme
|
|
|
1141
1054
|
**Happy path (clean edit):** any normal tool call (exit 0) is **silent** — no console output by design, even though the audit hooks append their log files (`sessions.log`, `delegations.log`). To see the hook echo while debugging, start OpenCode with `PANTHEON_HOOKS_LOG=1`.
|
|
1142
1055
|
|
|
1143
1056
|
**Delegation path (zero chat noise):** delegation signals are surfaced through
|
|
1144
|
-
TUI toasts (when enabled)
|
|
1145
|
-
`pantheon_delegation_list` / `pantheon_delegation_read` tools. Ask something
|
|
1057
|
+
TUI toasts (when enabled) and the board's `[unread]` marker. Ask something
|
|
1146
1058
|
that dispatches subagents (e.g. *"dispare 2 subagentes apollo em paralelo para
|
|
1147
1059
|
listar arquivos e comparar resultados"*) and expect:
|
|
1148
1060
|
|
|
1149
1061
|
- A TUI toast with `🚀 apollo em execução` / `✅ apollo concluiu` when
|
|
1150
1062
|
`PANTHEON_TOASTS` permits it
|
|
1151
|
-
- `[unread]`
|
|
1063
|
+
- `[unread]` on the board until the terminal result is read
|
|
1152
1064
|
- An honest append to `logs/agent-sessions/delegations.log`: the **real agent name** (extracted from `tool_input.subagent_type`, never `unknown` when present) and a **non-fabricated status** — `success` only on explicit completion evidence, `failure` for refusals/errors, `unknown` otherwise
|
|
1153
|
-
- With `export PANTHEON_TOASTS=off` before starting OpenCode: no toasts; board
|
|
1065
|
+
- With `export PANTHEON_TOASTS=off` before starting OpenCode: no toasts; board and audit-log visibility remain available
|
|
1154
1066
|
|
|
1155
1067
|
### Pre-commit hooks (local secret gate)
|
|
1156
1068
|
|
package/package.json
CHANGED
package/src/agents/zeus.md
CHANGED
|
@@ -144,33 +144,23 @@ task_status(task_id="ses_xxx", wait=true)
|
|
|
144
144
|
-> bloqueia ate completar: { state: "completed", task_result: "..." }
|
|
145
145
|
```
|
|
146
146
|
|
|
147
|
-
### Pantheon Delegation
|
|
147
|
+
### Pantheon Delegation — BackgroundJobBoard (plugin)
|
|
148
148
|
|
|
149
|
-
O plugin Pantheon
|
|
149
|
+
O plugin Pantheon mantém um **BackgroundJobBoard** persistente
|
|
150
|
+
(`.pantheon/board/state.json`) que rastreia trabalho em voo e o carrega para o
|
|
151
|
+
contexto de compactação. O toolset de delegação do plugin
|
|
152
|
+
(`pantheon_delegate`, `pantheon_delegation_read`, `pantheon_delegation_list`)
|
|
153
|
+
foi **removido em 1.4.3** — dispatch e coleta usam a API nativa
|
|
154
|
+
`task(background=true)` / `task_status(wait=true)`.
|
|
150
155
|
|
|
151
|
-
```
|
|
152
|
-
pantheon_delegate({prompt, agent, description?, read_only?})
|
|
153
|
-
-> cria sessao filha, registra no board, retorna alias: [apo-1]
|
|
154
|
-
pantheon_delegation_read({id: "apo-1"})
|
|
155
|
-
-> BLOQUEIA ate o job terminar, retorna o report markdown, marca reconciled
|
|
156
|
-
-> durante a espera, mostra o que o agente esta fazendo: seccao final "## Agent Activity"
|
|
157
|
-
(amostra as messages do child a cada ~2s; tool calls + args truncados / texto)
|
|
158
|
-
pantheon_delegation_list()
|
|
159
|
-
-> lista jobs da sessao, com [unread] para jobs terminados nao lidos
|
|
160
|
-
-> jobs RUNNING mostram "last activity:" (ultima acao visivel do child)
|
|
161
|
-
-> fail-open: sem suporte a messages, formato atual inalterado
|
|
162
|
-
```
|
|
163
|
-
|
|
164
|
-
- **Read-only enforcement:** agentes read-only (apollo, gaia — `read_only_agents` no routing.yml) tem `edit`, `write`, `bash`, `task` NEGADOS dentro da sessao delegada (o guard de `tool.execute.before` THROW com mensagem acionavel). Use `read_only: true` no delegate para qualquer agente de investigacao.
|
|
165
|
-
- **Depth-2 hard-enforced:** sessao read-only NAO cria subagentes (`task` bloqueado) — investigacao nunca vira arvore.
|
|
166
156
|
- **Compactacao:** jobs em voo (running + unread terminal) sao injetados no contexto de compactacao via `experimental.session.compacting` — delegacao em andamento nao se perde ao compactar.
|
|
167
157
|
|
|
168
|
-
### DELEGATION_RULES (
|
|
158
|
+
### DELEGATION_RULES (native task path)
|
|
169
159
|
|
|
170
|
-
1. **NUNCA** polle `
|
|
171
|
-
2. Voce NAO recebe notificacao injetada no chat — politica do usuario: ZERO `task-notification` no transcript. Visibilidade de conclusao: marcador `[unread]`
|
|
172
|
-
3. Use `
|
|
173
|
-
4. `
|
|
160
|
+
1. **NUNCA** polle `task_status` para checar se um job terminou — use `wait=true` para fan-in explicito.
|
|
161
|
+
2. Voce NAO recebe notificacao injetada no chat — politica do usuario: ZERO `task-notification` no transcript. Visibilidade de conclusao: marcador `[unread]` no board, toasts TUI (pantheon-hooks) e carry-forward de compactacao.
|
|
162
|
+
3. Use `task_status({task_id, wait: true})` APENAS para fan-in explicito / recuperacao de resultado sob demanda (bloqueia ate o fim).
|
|
163
|
+
4. `task_status` sem `wait` e para diagnostico, nao para polling.
|
|
174
164
|
|
|
175
165
|
### Background (sempre usar)
|
|
176
166
|
- **Apollo, Hermes, Aphrodite, Demeter, Hephaestus, Prometheus**
|
|
@@ -324,9 +314,8 @@ out = await guard.maybeRetry(result, async () => { ...task() de novo... })
|
|
|
324
314
|
Regra: retry 1x APENAS em `empty-mode1`/`empty-mode2`. Resultado com conteudo
|
|
325
315
|
nunca redispara. Apos 1 retry vazio → escalate (mesma regra do TODO Enforcer).
|
|
326
316
|
|
|
327
|
-
> **Waves DEVEM usar `
|
|
328
|
-
> Uso alto-nível: `import {
|
|
329
|
-
> Uso baixo-nível: `createZeusRetryHelper().executeWithRetry(first, () => secondRead())` — classifica empty e decide retry/escalate (mesmo cap 1).
|
|
317
|
+
> **Waves DEVEM usar `dispatch-guard`** (`src/pantheon/dispatch-guard.ts`) — encapsula `task → classify → retry 1x → escalate` com cap duro 1; se ainda vazio throw `ZeusEscalationError` (escale: tente outro agente, simplifique, manual).
|
|
318
|
+
> Uso alto-nível: `import { createDispatchGuard } from './pantheon/dispatch-guard.ts'` → `await guard.maybeRetry(result, () => task(...))`.
|
|
330
319
|
|
|
331
320
|
### /cost — pantheon_cost tool (WIRED no plugin)
|
|
332
321
|
`pantheon_cost({ days?: number })` le o `opencode.db` READ-ONLY (node:sqlite;
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Cost Command (Wave 4, PR #46) — `pantheon_cost` structural tool: cost +
|
|
3
|
-
* token visibility
|
|
3
|
+
* token visibility by agent, straight from opencode.db.
|
|
4
4
|
*
|
|
5
5
|
* Reads the opencode session database READ-ONLY (sum cost + tokens by
|
|
6
6
|
* agent over the last N days → markdown table). Zero new dependencies:
|
|
@@ -13,8 +13,8 @@
|
|
|
13
13
|
* dbPath resolution: explicit option > env PANTHEON_COST_DB > the version-aware
|
|
14
14
|
* default selected by PANTHEON_OPENCODE_VERSION (v1 or v2). An unset version
|
|
15
15
|
* deliberately preserves V1 behavior. The tool is wired in
|
|
16
|
-
* plugin.ts
|
|
17
|
-
*
|
|
16
|
+
* plugin.ts (usable by zeus and any agent with tool access — no routing change
|
|
17
|
+
* needed).
|
|
18
18
|
*
|
|
19
19
|
* @module cost-command
|
|
20
20
|
*/
|
|
@@ -25,7 +25,7 @@ import { join } from 'node:path'
|
|
|
25
25
|
import { promisify } from 'node:util'
|
|
26
26
|
import { z } from 'zod'
|
|
27
27
|
|
|
28
|
-
import type { ToolContextLike } from './
|
|
28
|
+
import type { ToolContextLike } from './tool-context.ts'
|
|
29
29
|
|
|
30
30
|
const execFileAsync = promisify(execFile)
|
|
31
31
|
|
|
@@ -44,7 +44,7 @@ export interface CostCommandOptions {
|
|
|
44
44
|
|
|
45
45
|
export type OpenCodeVersion = 'v1' | 'v2'
|
|
46
46
|
|
|
47
|
-
/** One cost tool: description + zod args shape + execute
|
|
47
|
+
/** One cost tool: description + zod args shape + execute. */
|
|
48
48
|
export interface CostTool<Args extends z.ZodRawShape = typeof costArgs> {
|
|
49
49
|
description: string
|
|
50
50
|
args: Args
|
|
@@ -62,7 +62,7 @@ const costArgs = {
|
|
|
62
62
|
.min(1)
|
|
63
63
|
.max(365)
|
|
64
64
|
.optional()
|
|
65
|
-
.describe('Number of days of
|
|
65
|
+
.describe('Number of days of cost history to include (default 7).'),
|
|
66
66
|
} satisfies z.ZodRawShape
|
|
67
67
|
|
|
68
68
|
/** Return the safe, isolated state DB name for an OpenCode version. */
|
|
@@ -239,7 +239,7 @@ export function createCostCommand(options?: CostCommandOptions): CostCommand {
|
|
|
239
239
|
return {
|
|
240
240
|
pantheon_cost: {
|
|
241
241
|
description:
|
|
242
|
-
'Report
|
|
242
|
+
'Report cost + token usage by agent over the last N days, read from opencode.db (read-only, no writes).',
|
|
243
243
|
args: costArgs,
|
|
244
244
|
execute,
|
|
245
245
|
},
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Read-Only Enforcement (Phase 4) — deny mutating tools inside sessions that
|
|
3
|
-
* were delegated as read-only (advisory `read_only: true` on
|
|
4
|
-
*
|
|
3
|
+
* were delegated as read-only (advisory `read_only: true` on the native
|
|
4
|
+
* `task` delegate, or the agent ∈ `readOnlyAgents` from routing.yml).
|
|
5
5
|
*
|
|
6
6
|
* OpenCode hook: `tool.execute.before`. The hook shape is
|
|
7
7
|
* `(input, output) => Promise<void>`; THROWING from the hook denies the tool
|
|
@@ -18,11 +18,11 @@
|
|
|
18
18
|
* - `task` is blocked too, which hard-enforces depth-2: a read-only agent
|
|
19
19
|
* cannot spawn its own subagents.
|
|
20
20
|
*
|
|
21
|
-
* Unknown sessions are allowed for ordinary tools.
|
|
22
|
-
* fail-closed when
|
|
21
|
+
* Unknown sessions are allowed for ordinary tools. The runtime matrix is
|
|
22
|
+
* fail-closed when wired: missing agent identity or
|
|
23
23
|
* target metadata is denied rather than inferred.
|
|
24
24
|
*
|
|
25
|
-
* The registry is populated
|
|
25
|
+
* The registry is populated at delegate time: the CHILD
|
|
26
26
|
* session is registered when the delegate call is read-only.
|
|
27
27
|
*
|
|
28
28
|
* @module delegation-enforce
|
|
@@ -147,7 +147,6 @@ export function isDelegationAllowed(
|
|
|
147
147
|
function targetFromTool(tool: string, args: unknown): string | undefined {
|
|
148
148
|
if (args === null || typeof args !== 'object') return undefined
|
|
149
149
|
const record = args as Record<string, unknown>
|
|
150
|
-
if (tool === 'pantheon_delegate') return normalizeDelegationAgent(record.agent)
|
|
151
150
|
if (tool === 'task') {
|
|
152
151
|
return normalizeDelegationAgent(record.subagent_type ?? record.subagentType)
|
|
153
152
|
}
|
|
@@ -236,7 +235,7 @@ export function zeusReadGuard(tool: string, args: unknown, agent: string | undef
|
|
|
236
235
|
// ─── Registry ──────────────────────────────────────────────────────────
|
|
237
236
|
|
|
238
237
|
/**
|
|
239
|
-
* Read-only session registry.
|
|
238
|
+
* Read-only session registry. The plugin's delegate wiring registers child
|
|
240
239
|
* sessions here; the plugin's `tool.execute.before` guard consults it.
|
|
241
240
|
* Process-wide singleton for plugin wiring + factory for test isolation.
|
|
242
241
|
*/
|
|
@@ -291,19 +290,19 @@ export function createEnforcementGuard(input: {
|
|
|
291
290
|
options?: EnforcementGuardOptions
|
|
292
291
|
}): ToolExecuteBeforeHandler {
|
|
293
292
|
const blockedTools = input.options?.blockedTools ?? DEFAULT_BLOCKED_TOOLS
|
|
294
|
-
const prefix = input.options?.messagePrefix ?? '
|
|
293
|
+
const prefix = input.options?.messagePrefix ?? 'delegation guard'
|
|
295
294
|
|
|
296
295
|
return async (hook: ToolExecuteBeforeInput, output?: ToolExecuteBeforeOutput): Promise<void> => {
|
|
297
|
-
const isDelegationTool = hook.tool === 'task'
|
|
296
|
+
const isDelegationTool = hook.tool === 'task'
|
|
298
297
|
if (isDelegationTool && input.options?.isChildSession?.(hook.sessionID) === true) {
|
|
299
298
|
throw new Error(
|
|
300
299
|
`${prefix}: session ${hook.sessionID} is a child session — delegation tools are denied ` +
|
|
301
300
|
'to prevent nested delegation',
|
|
302
301
|
)
|
|
303
302
|
}
|
|
304
|
-
//
|
|
305
|
-
// time, so its matrix check lives in
|
|
306
|
-
// session-scoped agent learned from chat.params
|
|
303
|
+
// The native task() delegate has the authoritative ToolContext.agent at
|
|
304
|
+
// execution time, so its matrix check lives in dispatch-guard.ts. The hook
|
|
305
|
+
// only has the session-scoped agent learned from chat.params.
|
|
307
306
|
if (
|
|
308
307
|
hook.tool === 'task' &&
|
|
309
308
|
input.options?.isRootSession !== undefined &&
|
|
@@ -336,7 +335,7 @@ export function createEnforcementGuard(input: {
|
|
|
336
335
|
`This session was delegated for investigation only (read-only agent); it cannot ` +
|
|
337
336
|
`modify files, run commands, or spawn subagents. ` +
|
|
338
337
|
`If a write result is needed, dispatch a write-capable agent ` +
|
|
339
|
-
`(e.g. hermes, aphrodite) with
|
|
338
|
+
`(e.g. hermes, aphrodite) with the native task() tool instead.`,
|
|
340
339
|
)
|
|
341
340
|
}
|
|
342
341
|
}
|
|
@@ -17,11 +17,11 @@
|
|
|
17
17
|
import { z } from 'zod'
|
|
18
18
|
|
|
19
19
|
import type { BackgroundJobBoard } from './background-job-board.ts'
|
|
20
|
-
import type { ToolContextLike } from './delegation.ts'
|
|
21
20
|
import { GoalStore } from './goal-store.ts'
|
|
22
21
|
import { createPantheonLogger } from './logger.ts'
|
|
23
22
|
import { safeSessionPath } from './session-guard.ts'
|
|
24
23
|
import type { TodoEnforcerMessage } from './todo-enforcer.ts'
|
|
24
|
+
import type { ToolContextLike } from './tool-context.ts'
|
|
25
25
|
|
|
26
26
|
// Silence-by-default TUI policy (pantheon-hooks L42-58): warn → hooks.log,
|
|
27
27
|
// console echo opt-in via PANTHEON_HOOKS_LOG=1. `deps.logger` stays for tests.
|
|
@@ -103,7 +103,7 @@ const goalUpdateArgs = {
|
|
|
103
103
|
|
|
104
104
|
const goalGetArgs = {} satisfies z.ZodRawShape
|
|
105
105
|
|
|
106
|
-
/** One structural goal tool: description + zod args shape + execute
|
|
106
|
+
/** One structural goal tool: description + zod args shape + execute. */
|
|
107
107
|
export type GoalTool<Args extends z.ZodRawShape> = {
|
|
108
108
|
description: string
|
|
109
109
|
args: Args
|
|
@@ -15,7 +15,7 @@
|
|
|
15
15
|
import { mkdir, readFile, rename, writeFile } from 'node:fs/promises'
|
|
16
16
|
import { join } from 'node:path'
|
|
17
17
|
|
|
18
|
-
import { assertSafeParentSessionID } from './
|
|
18
|
+
import { assertSafeParentSessionID } from './tool-context.ts'
|
|
19
19
|
|
|
20
20
|
export type GoalStatus = 'pending' | 'in_progress' | 'done'
|
|
21
21
|
|
|
@@ -2,8 +2,8 @@
|
|
|
2
2
|
* Hashline edit tool (Wave 2, PR #46) — structural `hashline_edit` tool that
|
|
3
3
|
* anchors edits to stable sha256 content tags instead of fragile line numbers.
|
|
4
4
|
*
|
|
5
|
-
* Tool shape
|
|
6
|
-
* zod-validated
|
|
5
|
+
* Tool shape follows the plugin tool convention (`{description, args, execute}`,
|
|
6
|
+
* zod-validated) used across the Pantheon toolset.
|
|
7
7
|
*
|
|
8
8
|
* Safety model (council-approved):
|
|
9
9
|
* 1. ALL refs are validated against the ORIGINAL file snapshot BEFORE any
|