pantheon-opencode 1.6.0-beta.5 → 1.6.0-beta.6

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/AGENTS.md CHANGED
@@ -271,7 +271,7 @@ Each agent file defines overrides in its `## 🧠 Memory Protocol` section:
271
271
 
272
272
  Para otimizar decisoes de delegacao e reduzir gasto de tokens:
273
273
 
274
- 1. **memory_search(task_prompt, top_k=2)** antes de aplicar a arvore de roteamento
274
+ 1. **memory_search(query=task_prompt, top_k=2)** antes de aplicar a arvore de roteamento
275
275
  2. Se score > 0.85 → reutiliza agente + background_mode do cache
276
276
  3. Se score ≤ 0.85 → aplica regras estaticas e memory_store() com:
277
277
  - key: deleg:<task_type>
@@ -284,8 +284,8 @@ Para otimizar decisoes de delegacao e reduzir gasto de tokens:
284
284
  as a string (got object). json.dumps it first. Example: metadata='{"type":
285
285
  "decision", "score": 0.9}'`.
286
286
 
287
- 4. **kv_store("deleg:<pattern>", ...)** para padroes recorrentes de delegacao
288
- 5. **kv_get("deleg:<pattern>")** para reusar decisoes ja tomadas
287
+ 4. **kv_store(namespace="deleg", key="deleg:<pattern>", value=...)** para padroes recorrentes de delegacao
288
+ 5. **kv_get(namespace="deleg", key="deleg:<pattern>")** para reusar decisoes ja tomadas
289
289
 
290
290
  Isso elimina ~300 tokens de reasoning por delegacao quando o cache acerta.
291
291
 
@@ -299,7 +299,7 @@ After every `/pantheon` council synthesis completes, Zeus stores:
299
299
  memory_store({
300
300
  namespace: "council_decisions",
301
301
  key: "council:<yyyy-mm-dd>:<slug>",
302
- value: {
302
+ value: JSON.stringify({
303
303
  question: "original question",
304
304
  specialists: ["@agent1", "@agent2"],
305
305
  recommendation: "final recommendation",
@@ -310,7 +310,7 @@ memory_store({
310
310
  themis_audit: "approved|issues",
311
311
  precedent_used: false,
312
312
  timestamp: "<ISO-8601>"
313
- },
313
+ }),
314
314
  metadata: JSON.stringify({
315
315
  type: "council_decision",
316
316
  specialist_count: N,
@@ -328,7 +328,7 @@ encoded as a string (got object). json.dumps it first. Example: metadata='{"type
328
328
  ### Read Path (Precedent Fast-Path)
329
329
  Before dispatching a new council, Zeus runs:
330
330
  ```
331
- memory_search(question, top_k=2, namespace="council_decisions")
331
+ memory_search(query=question, top_k=2, namespace="council_decisions")
332
332
  ```
333
333
 
334
334
  Result interpretation:
@@ -426,16 +426,16 @@ All session state lives in **pantheon-persistence** (namespace `checkpoint:<slug
426
426
  No file I/O, no checkpoint_session.py — TTL (4h) handles cleanup automatically.
427
427
 
428
428
  ### Heartbeat Check
429
- - If `context_get(slug, "heartbeat")` returns a checkin older than 300s, log a stall warning and resume
429
+ - If `context_get(slug=slug, key="heartbeat", session_id=SESSION_ID)` returns a checkin older than 300s, log a stall warning and resume
430
430
  - Write heartbeat after every anti-stall recovery action:
431
431
  ```
432
- context_save(slug, "heartbeat", json({"status": "alive", "last_action": "...", "turn_count": N}))
432
+ context_save(slug=slug, key="heartbeat", content=JSON.stringify({"status": "alive", "last_action": "...", "turn_count": N}), session_id=SESSION_ID)
433
433
  ```
434
434
 
435
435
  ### Checkpoint Auto-Save (Pré-Compactação)
436
436
  Before ANY delegate dispatch, save a checkpoint:
437
437
  ```
438
- context_save(slug, "phase:N", json({
438
+ context_save(slug=slug, key="phase:N", content=JSON.stringify({
439
439
  "phase": {"current": N, "total": M, "name": "..."},
440
440
  "turn_count": N, "agent": "...", "summary": "..."
441
441
  }), session_id=SESSION_ID)
@@ -456,15 +456,15 @@ o Zeus DEVE salvar o estado atual:
456
456
  2. Salve heartbeat + phase atual + tarefas pendentes
457
457
  3. Só então permita que a compactação prossiga
458
458
  ```
459
- # Ao iniciar sessão:
460
- result = context_save(slug, "init", session_state)
459
+ # Ao iniciar sessão (session_id é REQUIRED — use o id da sessão corrente):
460
+ result = context_save(slug=slug, key="init", content=JSON.stringify(session_state), session_id=SESSION_ID)
461
461
  SESSION_ID = result.session_id # ← guarde para toda a sessão
462
462
 
463
463
  # Antes de CADA delegação:
464
- context_save(slug, f"pre:{agent}", current_state, session_id=SESSION_ID)
464
+ context_save(slug=slug, key=f"pre:{agent}", content=JSON.stringify(current_state), session_id=SESSION_ID)
465
465
 
466
466
  # Após retorno do agente:
467
- context_save(slug, f"post:{agent}", result_state, session_id=SESSION_ID)
467
+ context_save(slug=slug, key=f"post:{agent}", content=JSON.stringify(result_state), session_id=SESSION_ID)
468
468
  ```
469
469
  Isso garante que o estado sobreviva à compactação — o "latest" pointer
470
470
  sempre aponta para o checkpoint mais recente, mesmo após compactação.
@@ -473,9 +473,9 @@ sempre aponta para o checkpoint mais recente, mesmo após compactação.
473
473
  ### Context Retrieval
474
474
  Next-phase agents retrieve previous context via:
475
475
  ```
476
- context_get(slug, "latest") # most recent checkpoint
477
- context_get(slug, "phase:3") # specific phase
478
- context_list(slug) # all checkpoints
476
+ context_get(slug=slug, session_id=SESSION_ID) # most recent checkpoint ("latest" is the default key)
477
+ context_get(slug=slug, key="phase:3", session_id=SESSION_ID) # specific phase
478
+ context_list(slug=slug, session_id=SESSION_ID) # all checkpoints
479
479
  ```
480
480
 
481
481
  ### Long-Session Progress
@@ -535,7 +535,7 @@ sequenceDiagram
535
535
  participant T as Themis (Audit + Moderator)
536
536
 
537
537
  U->>Z: /pantheon [--research] question
538
- Z->>M: memory_search(council_decisions)
538
+ Z->>M: memory_search(query, namespace="council_decisions")
539
539
  alt Precedent found (score > 0.85)
540
540
  M-->>Z: ⚠️ Cached decision
541
541
  Z->>U: Fast-path precedent
@@ -584,7 +584,7 @@ sequenceDiagram
584
584
  ## Dispatch Sequence (9-Step Protocol)
585
585
 
586
586
  ### Step 0 — Precedent Fast-Path (Fase 1)
587
- Run the precedent read path defined in `## Memory Protocol > Council Decisions Namespace` (`memory_search(query, top_k=2, namespace="council_decisions")`). If no precedent applies, proceed to Step 0b.
587
+ Run the precedent read path defined in `## Memory Protocol > Council Decisions Namespace` (`memory_search(query=question, top_k=2, namespace="council_decisions")`). If no precedent applies, proceed to Step 0b.
588
588
 
589
589
  ### Step 0b — Apollo Pre-Scan (Fase 2, --research flag)
590
590
  If `/pantheon --research <question>`: dispatch @apollo with 30s timeout. Inject findings as `shared_context` into ALL specialist prompts. Skip if flag absent.
package/CHANGELOG.md CHANGED
@@ -18,6 +18,20 @@ 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.6] - 2026-10-05
22
+
23
+ &lt;!-- Add new changes here. Running `node scripts/versioning.mjs apply` will
24
+ move this section to a versioned entry and reset the template below. --&gt;
25
+
26
+ ## 🆕 What's New
27
+
28
+ - **Política de suporte documentada: a linha 1.6.x é a ÚLTIMA compatível com OpenCode 1.X.** A partir de **1.7, toda release é breaking change com alvo em OpenCode 2.** O limite está escrito no `README.md` (seção "Compatibility and support policy", com matriz de suporte) e em `docs/INSTALLATION.md` (seção "OpenCode V1/V2 — contrato de plugin"), não duplicado em um documento novo: os dois arquivos já eram o ponto de entrada do contrato de plugin, e um terceiro documento seria um terceiro lugar para divergir. O texto distingue as duas células da linha 1.6.x — a célula 1.X é o contrato V1 (6 tools, lifecycle do board, hooks V1, compaction V1) e a célula OpenCode 2 é o contrato V2, que é real porém **menor**: 3 tools, sem as `pantheon_goal_*` e sem a matriz de delegação caller→target. **O suporte a OpenCode 2 na linha 1.6 é documentado como parcial, e não como completo**, por duas razões verificadas: (a) o canário de ferramentas V2 foi escrito contra `opencode 2.0.22`, host que não é publicado em nenhum canal npm — contra o `@opencode-ai/cli` instalável o mesmo canário falha 12/12, porque `/api/experimental/session/{id}/wait` foi promovido para `/api/session/{id}/wait` (404 no caminho antigo) e `ctx.tool.list` não existe mais; corrigindo apenas a rota, 10/12 passam no mesmo host, o que caracteriza descasamento canário/host e não regressão medida do plugin (issue #216, PR #213); (b) não existe perna V1 de validação end-to-end — `scripts/test-opencode-v2-sandbox.sh` é V2-only e rejeita `--run v1` — portanto a versão exata de host 1.X exercitada end-to-end pela linha 1.6 fica registrada como **não verificada**, e o caminho 1.X é sustentado pelo pin de dependência (`@opencode-ai/plugin` / `@opencode-ai/sdk` `1.18.33`), pelos testes unitários do gate de geração e pelos testes end-to-end do installer sobre a config emitida.
29
+
30
+ ## 🐞 Fixed
31
+
32
+ - **Os exemplos de chamada MCP nos arquivos de prompt ensinavam um contrato que não existe no schema real.** Um `pantheon-persistence.kv_get` ao vivo foi rejeitado com `namespace: Missing key`; a linha que o produziu foi `src/agents/zeus.md:189` (`depth = kv_get("deleg:depth") ?? 0`), cuja cópia instalada em `~/.config/opencode/agents/zeus.md` é a que executou. A causa não era o servidor MCP: os arquivos de agente e de instrução ensinavam chamadas posicionais sem `namespace`, enquanto o schema real é `kv_get({ namespace /* obrigatório, sem default */, key, scope? })` — `kv_store.namespace` é igualmente obrigatório e `kv_store.value` é `str`. **A correção está nos docs, e não em adicionar um `namespace` default:** um default escreveria estado de coordenação no namespace errado silenciosamente, então *obrigatório* é o desenho correto. A mesma classe de defeito foi corrigida em `context_save` (posicional, com um helper `json()` inexistente e sem o `session_id` obrigatório → forma por palavra-chave com `content=JSON.stringify(...)`), em `memory_store` (`value` precisa ser uma string JSON codificada → agora iguala o padrão já correto de `metadata`, o que resolveu uma aut contradição no mesmo arquivo), na aritmética de profundidade (envolvida em `String(...)`, porque `kv_store.value` é `str` e não `int`) e em `memory_search(query, ...)` → `query=` em todas as instâncias. `AGENTS.md` é artefato gerado e foi reconstruído, nunca editado à mão.
33
+ - **Um bypass de permissão vivo: `edit: deny` não cobria `hashline_edit`.** A regra `edit: deny` é avaliada por nome de ferramenta, e `hashline_edit` não é `edit` — então os agentes restritos a `edit: deny` (`zeus`, `athena`, `iris`, `nyx`, `themis`) podiam editar arquivos assim mesmo, escrevendo no disco por um caminho que a negação não cobria. `hashline_edit: deny` foi adicionado a esses cinco agentes. **Não era uma condição de corrida nem um erro de configuração: era uma tabela de permissões incompleta**, o que significa que a negação nunca esteve de fato aplicada a quem só deveria ler.
34
+ - **`test:node` reportava sucesso tendo coletado zero testes.** `node --test <glob>` sai com 0 quando o glob não expande (Node >= 22), e foi exatamente o que aconteceu: um glob que nunca expandiu produziu um gate verde que não executou nada. Pior, `tests/` estava ausente da allow-list `files` do pacote, então um consumidor que rodasse `npm test` recebia esse verde. Agora os scripts de teste falham em voz alta quando a coleta é zero, e a guarda tem teste próprio (`tests/run-node-tests.test.mjs`) que a dirige como subprocesso — o contrato é o código de saída, então testá-la em processo não provaria nada.
21
35
  ## [v1.6.0-beta.5] - 2026-10-05
22
36
 
23
37
  &lt;!-- Add new changes here. Running `node scripts/versioning.mjs apply` will
package/README.md CHANGED
@@ -31,7 +31,9 @@ way to plan work, make progress, check results, and keep useful project context.
31
31
  ## Start in 2 minutes
32
32
 
33
33
  Requirements: [OpenCode 1.18.4+](https://opencode.ai/docs/) and Node.js
34
- 22.22.2+ (or 24.15.0+ / 26+).
34
+ 22.22.2+ (or 24.15.0+ / 26+). For which OpenCode generation each Pantheon
35
+ release line supports, see
36
+ [Compatibility and support policy](#compatibility-and-support-policy).
35
37
 
36
38
  Pantheon declares `engines.node` as `^22.22.2 || ^24.15.0 || >=26.0.0`. The
37
39
  floor reflects what the dependency tree actually needs — the transitive
@@ -89,6 +91,81 @@ services you choose to use. Check the
89
91
  [releases](https://github.com/ils15/pantheon-opencode/releases) and
90
92
  [changelog](CHANGELOG.md) for the latest published changes.
91
93
 
94
+ ## Compatibility and support policy
95
+
96
+ > ### ⚠️ 1.6.x is the LAST line that supports OpenCode 1.X
97
+ >
98
+ > **OpenCode 1.X support ends after the 1.6 line.** From **1.7 onward, every
99
+ > release is a breaking change targeting OpenCode 2.** A 1.6.x install is the
100
+ > last one that runs on an OpenCode 1.X host; do not plan an upgrade across the
101
+ > 1.6 → 1.7 boundary while your host is still on OpenCode 1.X.
102
+
103
+ | Pantheon line | OpenCode 1.X host | OpenCode 2 host |
104
+ |---|---|---|
105
+ | **1.6.x** (current line) | **Supported.** V1 plugin contract | Supported, reduced surface — V2 contract |
106
+ | **1.7 and later** | **Not supported.** Breaking change | Target of the 1.7+ work |
107
+
108
+ Both columns of the 1.6.x row are real, and they are not the same claim. Read
109
+ [the plugin contract](#opencode-v1v2--dual-version-160-beta1) for what each
110
+ generation actually registers.
111
+
112
+ ### What "supported on OpenCode 1.X" concretely means
113
+
114
+ On the 1.6 line, an OpenCode 1.X host gets the **V1 plugin**:
115
+ `src/plugin.ts` plus `src/plugins/pantheon-hooks.ts`, registered under the
116
+ singular `plugin` config key. That surface registers 6 tools
117
+ (`hashline_edit`, the 3 `pantheon_goal_*` tools, `pantheon_cost`,
118
+ `pantheon_model`), the BackgroundJobBoard lifecycle, the V1 event/tool hooks and
119
+ the V1 compaction path. It is selected automatically: the generation gate
120
+ (`--opencode-version`, else `OPENCODE_VERSION`, else a `OPENCODE_BIN` basename of
121
+ `opencode2`, else a `--version` probe where major ≥ 2 selects V2) resolves a 1.X
122
+ host to V1.
123
+
124
+ The two generations are exclusive. The installer strips Pantheon references from
125
+ both config shapes and registers only the selected one, so a 1.X host never
126
+ loads the V2 plugin and a V2 host never loads the V1 plugin. Forcing V2 on a 1.X
127
+ host is an unsupported mixed/wrong-generation configuration, not a supported mode.
128
+
129
+ ### What the OpenCode 2 path is — and is not
130
+
131
+ **Not inert.** The V2 path (`src/plugin-v2`, registered as a directory because
132
+ the V2 loader rejects bare file paths) registers real tools with real
133
+ `input`/`output` schemas via `ctx.tool.transform()`, 5 event subscriptions,
134
+ session hooks and a tool `execute.before` hook that enforces read-only sessions.
135
+ It is a smaller surface than V1 by design, not a stub: the 3 goal tools and the
136
+ V1 caller→target delegation matrix are absent, and `getUnsupportedFeatures()`
137
+ reports that as `goal-tools` and `delegation-matrix`.
138
+
139
+ **Not complete either, and the V2 support claim is currently narrowed.** Two
140
+ facts, both recorded in this repository rather than smoothed over:
141
+
142
+ - **The host-backed V2 gate is red on the installable OpenCode CLI.** The V2 tool
143
+ canary (`tests/canary/plugin-v2-tool-canary.test.mjs`) was written against
144
+ `opencode 2.0.22`, which no npm channel publishes. Against the installable
145
+ `@opencode-ai/cli` host, `/api/experimental/session/{id}/wait` has been
146
+ promoted to `/api/session/{id}/wait` (the old path 404s) and `ctx.tool.list`
147
+ is absent, so the canary fails 12/12 there. That is a canary/host-generation
148
+ mismatch, not a measured plugin regression — with only the route renamed, 10/12
149
+ pass on the same host, including the edit being applied and the mutant still
150
+ caught. Tracked as [issue
151
+ #216](https://github.com/ils15/pantheon-opencode/issues/216); the fail-closed
152
+ CI change is [PR #213](https://github.com/ils15/pantheon-opencode/pull/213).
153
+ - **The 2.x host claims in `src/plugin-v2.ts` are measured against `2.0.22`
154
+ specifically**, not against the current installable CLI. Re-measurement
155
+ against the current host is outstanding and is tracked in the same issue.
156
+
157
+ Treat the 1.6 line's OpenCode 2 support as **real but partial**, and do not read
158
+ the 1.6.x row as "OpenCode 2 is finished".
159
+
160
+ ### Verified against which OpenCode versions
161
+
162
+ | What | Verified against | How |
163
+ |---|---|---|
164
+ | V1 plugin + generation gate | The OpenCode 1.18.x line. This package pins `@opencode-ai/plugin` and `@opencode-ai/sdk` to `1.18.33`; the highest published `opencode-ai` host package (the one shipping the `opencode` binary) is `1.18.34` | Dependency pin (`package.json`); generation-gate unit tests over injected host banners; installer end-to-end tests asserting the emitted `opencode.json` shape |
165
+ | V2 plugin tool surface | `opencode 2.0.22` (the host the canary was written against) | `tests/canary/plugin-v2-tool-canary.test.mjs`, driving `hashline_edit` through a real `opencode serve` |
166
+ | V2 against the current installable CLI | **Not verified.** Canary fails 12/12; see issue #216 | — |
167
+ | Exact 1.X host version the 1.6 line is exercised against end-to-end | **Unverified.** No V1 host leg exists: the sandbox runner is V2-only and `--run v1` is rejected | — |
168
+
92
169
  ## Delegation (native `task()`)
93
170
 
94
171
  Pantheon delegates exclusively through OpenCode's native `task()` child-session
@@ -301,6 +378,11 @@ Key order does not matter.
301
378
 
302
379
  ## Updating between releases (beta.5+)
303
380
 
381
+ > **Read [Compatibility and support policy](#compatibility-and-support-policy)
382
+ > before upgrading across a line boundary.** 1.6.x is the last line that
383
+ > supports an OpenCode 1.X host; 1.7+ is breaking-change territory targeting
384
+ > OpenCode 2. Within the 1.6 line, `update` is the supported path.
385
+
304
386
  One command keeps an existing installation current:
305
387
 
306
388
  ```bash
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pantheon-opencode",
3
- "version": "1.6.0-beta.5",
3
+ "version": "1.6.0-beta.6",
4
4
  "description": "Multi-agent orchestration for OpenCode — 14 specialized agents, TDD, Themis review gates.",
5
5
  "type": "module",
6
6
  "workspaces": [
@@ -41,12 +41,12 @@
41
41
  "hooks:install": "pre-commit install",
42
42
  "hooks:update": "pre-commit autoupdate",
43
43
  "test:security": "npm run secret-scan",
44
- "test:node": "node --test tests/*.mjs tests/pantheon/*.mjs",
45
- "coverage": "echo 'Node coverage (report-only, NO threshold is enforced).' && echo 'Covers: the .mjs/.js modules under scripts/, src/ and bin/ that test:node exercises.' && echo 'Does NOT cover: test:ts (hand-rolled tsx loop, no node --test runner) and test:ci (pytest).' && echo ' TypeScript under src/ (41 files) is therefore entirely unmeasured here.' && echo '' && node --test --experimental-test-coverage --test-coverage-include='scripts/**/*.mjs' --test-coverage-include='src/**/*.mjs' --test-coverage-include='src/**/*.js' --test-coverage-include='bin/**/*.mjs' tests/*.mjs tests/pantheon/*.mjs",
44
+ "test:node": "node scripts/run-node-tests.mjs --label 'test:node' 'tests/*.mjs' 'tests/pantheon/*.mjs'",
45
+ "coverage": "echo 'Node coverage (report-only, NO threshold is enforced).' && echo 'Covers: the .mjs/.js modules under scripts/, src/ and bin/ that test:node exercises.' && echo 'Does NOT cover: test:ts (hand-rolled tsx loop, no node --test runner) and test:ci (pytest).' && echo ' TypeScript under src/ (41 files) is therefore entirely unmeasured here.' && echo '' && node scripts/run-node-tests.mjs --label 'coverage' -- --experimental-test-coverage --test-coverage-include='scripts/**/*.mjs' --test-coverage-include='src/**/*.mjs' --test-coverage-include='src/**/*.js' --test-coverage-include='bin/**/*.mjs' 'tests/*.mjs' 'tests/pantheon/*.mjs'",
46
46
  "coverage:plugin-v2": "node scripts/check-plugin-v2-coverage.mjs",
47
47
  "test:ts": "for f in tests/pantheon/*.test.ts; do if [ \"$f\" = tests/pantheon/plugin-v2-contract.test.ts ]; then NODE_OPTIONS=--conditions=import tsx \"$f\"; else tsx \"$f\"; fi || exit 1; done",
48
- "test:hooks": "node --test tests/pantheon/plugin-v2-hook-canary.test.mjs",
49
- "test:tool-canary": "node --test tests/canary/plugin-v2-tool-canary.test.mjs",
48
+ "test:hooks": "node scripts/run-node-tests.mjs --label 'test:hooks' 'tests/pantheon/plugin-v2-hook-canary.test.mjs'",
49
+ "test:tool-canary": "node scripts/run-node-tests.mjs --label 'test:tool-canary' 'tests/canary/plugin-v2-tool-canary.test.mjs'",
50
50
  "test:all": "npm run test:ci && npm run test:node && npm run test:ts",
51
51
  "test": "npm run test:all",
52
52
  "test:ci": "python3 -m pytest -q --tb=short 2>&1"
package/plugin.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "pantheon",
3
3
  "publisher": "ils15",
4
- "version": "1.6.0-beta.5",
4
+ "version": "1.6.0-beta.6",
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
@@ -4,7 +4,7 @@ build-backend = "setuptools.backends._legacy:_Backend"
4
4
 
5
5
  [project]
6
6
  name = "pantheon"
7
- version = "1.6.0-beta.5"
7
+ version = "1.6.0-beta.6"
8
8
  description = "Multi-agent orchestration framework"
9
9
  requires-python = ">=3.11"
10
10
  dependencies = []
@@ -0,0 +1,189 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * Run `node --test` over an explicit file list, and FAIL when zero tests are collected.
4
+ *
5
+ * Why this exists: `node --test <glob>` exits 0 when the glob matches nothing (Node >= 22).
6
+ * A test script whose file set is absent from the checkout -- or absent from the published
7
+ * package, because the `files` allow-list omits it -- therefore reports success while
8
+ * collecting nothing. That is the worst possible failure mode for a release gate: it
9
+ * manufactures confidence.
10
+ *
11
+ * This runner closes that hole from two independent sides:
12
+ * 1. It expands the globs ITSELF and refuses to start the runner with zero files, so the
13
+ * runner is never handed an unexpanded pattern that silently matches nothing.
14
+ * 2. After a run that exited 0, it re-reads the collected count from the machine-readable
15
+ * TAP summary and fails closed when that count is 0, unparseable, or missing.
16
+ *
17
+ * Side 2 is deliberately independent of side 1: a future Node change to `--test` glob
18
+ * handling, a glob that expands to a directory of empty files, or an emptied test file all
19
+ * still get caught.
20
+ *
21
+ * Human-readable output is unchanged: the default `spec` reporter still goes to stdout, and
22
+ * the TAP reporter is diverted to a scratch file that is read once and deleted.
23
+ *
24
+ * Usage:
25
+ * node scripts/run-node-tests.mjs [--label <name>] [-- <node flags...>] <glob>...
26
+ *
27
+ * Examples:
28
+ * node scripts/run-node-tests.mjs 'tests/*.mjs' 'tests/pantheon/*.mjs'
29
+ * node scripts/run-node-tests.mjs -- --experimental-test-coverage 'tests/*.mjs'
30
+ */
31
+ import { spawnSync } from 'node:child_process'
32
+ import { globSync, mkdtempSync, readFileSync, rmSync } from 'node:fs'
33
+ import { tmpdir } from 'node:os'
34
+ import { join } from 'node:path'
35
+ import { fileURLToPath } from 'node:url'
36
+
37
+ const ROOT = join(fileURLToPath(new URL('.', import.meta.url)), '..')
38
+
39
+ function usage(message) {
40
+ console.error(`zero-test-guard: ${message}`)
41
+ console.error(
42
+ 'usage: node scripts/run-node-tests.mjs [--label <name>] [-- <node flags...>] <glob>...',
43
+ )
44
+ process.exit(1)
45
+ }
46
+
47
+ /** Parse `[--label <name>] [-- <node flags...>] <glob>...`. */
48
+ function parseArgs(argv) {
49
+ const separator = argv.indexOf('--')
50
+ const own = separator === -1 ? argv : argv.slice(0, separator)
51
+ const nodeFlags = separator === -1 ? [] : argv.slice(separator + 1)
52
+
53
+ let label = 'node tests'
54
+ const globs = []
55
+ for (let i = 0; i < own.length; i++) {
56
+ if (own[i] === '--label') {
57
+ const value = own[i + 1]
58
+ if (value === undefined) usage('--label requires a value')
59
+ label = value
60
+ i += 1
61
+ continue
62
+ }
63
+ if (own[i].startsWith('-')) usage(`unknown option ${own[i]}`)
64
+ globs.push(own[i])
65
+ }
66
+ if (globs.length === 0) usage('at least one glob is required')
67
+ return { label, globs, nodeFlags }
68
+ }
69
+
70
+ /**
71
+ * Expand globs to a deduped, ordered file list.
72
+ * Order follows the positional order of the globs so that the suite runs in the same order
73
+ * as the shell used to; within a glob, matches are sorted for determinism.
74
+ */
75
+ function expand(globs) {
76
+ const files = []
77
+ const seen = new Set()
78
+ for (const pattern of globs) {
79
+ const matches = globSync(pattern, { cwd: ROOT })
80
+ .filter((match) => !match.includes('node_modules') && !match.includes('__pycache__'))
81
+ .sort()
82
+ for (const match of matches) {
83
+ if (seen.has(match)) continue
84
+ seen.add(match)
85
+ files.push(match)
86
+ }
87
+ }
88
+ return files
89
+ }
90
+
91
+ function reportMissingFiles(label, globs) {
92
+ console.error(`zero-test-guard: ${label} matched 0 test files — refusing to report success.`)
93
+ console.error(` globs: ${globs.join(' ')}`)
94
+ console.error(' This is the "test suite silently passes when it collects nothing" failure.')
95
+ console.error(' Either the test files are missing from this checkout, or they are missing')
96
+ console.error(' from the published package (check the "files" allow-list in package.json).')
97
+ }
98
+
99
+ function reportZeroCollected(label, files) {
100
+ console.error(`zero-test-guard: ${label} collected 0 tests — refusing to report success.`)
101
+ console.error(` ${files.length} file(s) matched, but not one test case was collected:`)
102
+ for (const file of files) console.error(` ${file}`)
103
+ }
104
+
105
+ /**
106
+ * Count the real test cases collected in a TAP summary.
107
+ *
108
+ * Node counts a test FILE that declares no test cases as one passing "test"
109
+ * (`ok 1 - tests/foo.test.mjs`), so the summary's `# tests N` is >= the file count and
110
+ * can never reach 0 once any file matched. Relying on it alone would let a suite whose
111
+ * every test file was emptied out report success.
112
+ *
113
+ * The reliable discriminator is the subtest NAME: a file-level placeholder is named with
114
+ * the file path that was passed to the runner, a real test case is named with its test
115
+ * name. So a top-level subtest whose name is one of our matched paths is a placeholder
116
+ * and is not counted as a collected case.
117
+ *
118
+ * Returns null when the summary cannot be read or parsed -- the caller fails closed.
119
+ */
120
+ function countCollectedTests(tapPath, files) {
121
+ let tap = ''
122
+ try {
123
+ tap = readFileSync(tapPath, 'utf8')
124
+ } catch {
125
+ return null
126
+ }
127
+ // The summary count is still asserted to exist: without it the stream is not TAP.
128
+ if (!/^#\s*tests\s+\d+\s*$/m.test(tap)) return null
129
+
130
+ const placeholders = new Set(files)
131
+ let collected = 0
132
+ for (const match of tap.matchAll(/^# Subtest: (.+)$/gm)) {
133
+ if (placeholders.has(match[1].trim())) continue
134
+ collected += 1
135
+ }
136
+ return collected
137
+ }
138
+
139
+ const { label, globs, nodeFlags } = parseArgs(process.argv.slice(2))
140
+ const files = expand(globs)
141
+
142
+ if (files.length === 0) {
143
+ reportMissingFiles(label, globs)
144
+ process.exit(1)
145
+ }
146
+
147
+ const scratch = mkdtempSync(join(tmpdir(), 'pantheon-node-tests-'))
148
+ const tapPath = join(scratch, 'summary.tap')
149
+
150
+ try {
151
+ const run = spawnSync(
152
+ process.execPath,
153
+ [
154
+ '--test',
155
+ '--test-reporter=spec',
156
+ '--test-reporter-destination=stdout',
157
+ '--test-reporter=tap',
158
+ `--test-reporter-destination=${tapPath}`,
159
+ ...nodeFlags,
160
+ ...files,
161
+ ],
162
+ { cwd: ROOT, stdio: 'inherit' },
163
+ )
164
+
165
+ if (run.error) {
166
+ console.error(`zero-test-guard: could not start the test runner: ${run.error.message}`)
167
+ process.exit(1)
168
+ }
169
+ if (run.signal) {
170
+ console.error(`zero-test-guard: the test runner was killed by ${run.signal}`)
171
+ process.exit(1)
172
+ }
173
+ // Real failures keep the runner's own exit code; the guard only speaks up when the
174
+ // runner claimed success it cannot justify.
175
+ if (run.status !== 0) process.exit(run.status)
176
+
177
+ const collected = countCollectedTests(tapPath, files)
178
+ if (collected === null) {
179
+ console.error('zero-test-guard: could not read the collected test count from the TAP summary.')
180
+ console.error(' Failing closed: an unreadable summary cannot certify that any test ran.')
181
+ process.exit(1)
182
+ }
183
+ if (collected === 0) {
184
+ reportZeroCollected(label, files)
185
+ process.exit(1)
186
+ }
187
+ } finally {
188
+ rmSync(scratch, { recursive: true, force: true })
189
+ }
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": "4fe256d44ee9828d622a0d8b63cfc6bb39a317189a9ccf19494a6efd4527a7a9",
4
+ "src/skills/auto-continue/SKILL.md": "a7a4ec7ab35970bec36bcc286b95881b43a75e3bcdb626a17ebb9e397dd1a708",
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",
@@ -122,7 +122,7 @@ Frontend: `tdd-with-agents`, `visual-review-pipeline`, `incremental-implementati
122
122
 
123
123
 
124
124
  ## Session Context Retrieval
125
- When dispatched by Zeus, call context_get(slug, "latest", session_id=SESSION_ID)
125
+ When dispatched by Zeus, call context_get(slug=slug, key="latest", session_id=SESSION_ID)
126
126
  if session_slug is provided in dispatch metadata. Apply any "remaining_tasks",
127
127
  "current_phase", or "gotchas" from the retrieved context.
128
128
  If context_get returns None, proceed fresh (first phase or expired session).
@@ -15,6 +15,7 @@ skills:
15
15
  - memory-bank
16
16
  permission:
17
17
  edit: deny
18
+ hashline_edit: deny
18
19
  bash: deny
19
20
  read: allow
20
21
  grep: allow
@@ -111,7 +111,7 @@ Compress working context with the `context-compression` skill (L1, Pantheon-nati
111
111
 
112
112
 
113
113
  ## Session Context Retrieval
114
- When dispatched by Zeus, call context_get(slug, "latest", session_id=SESSION_ID)
114
+ When dispatched by Zeus, call context_get(slug=slug, key="latest", session_id=SESSION_ID)
115
115
  if session_slug is provided in dispatch metadata. Apply any "remaining_tasks",
116
116
  "current_phase", or "gotchas" from the retrieved context.
117
117
  If context_get returns None, proceed fresh (first phase or expired session).
@@ -64,7 +64,7 @@ Compress working context with the `context-compression` skill (L1, Pantheon-nati
64
64
 
65
65
 
66
66
  ## Session Context Retrieval
67
- When dispatched by Zeus, call context_get(slug, "latest", session_id=SESSION_ID)
67
+ When dispatched by Zeus, call context_get(slug=slug, key="latest", session_id=SESSION_ID)
68
68
  if session_slug is provided in dispatch metadata. Apply any "remaining_tasks",
69
69
  "current_phase", or "gotchas" from the retrieved context.
70
70
  If context_get returns None, proceed fresh (first phase or expired session).
@@ -270,7 +270,7 @@ Implementação: `tdd-with-agents`, `incremental-implementation`, `code-review-c
270
270
 
271
271
 
272
272
  ## Session Context Retrieval
273
- When dispatched by Zeus, call context_get(slug, "latest", session_id=SESSION_ID)
273
+ When dispatched by Zeus, call context_get(slug=slug, key="latest", session_id=SESSION_ID)
274
274
  if session_slug is provided in dispatch metadata. Apply any "remaining_tasks",
275
275
  "current_phase", or "gotchas" from the retrieved context.
276
276
  If context_get returns None, proceed fresh (first phase or expired session).
@@ -11,6 +11,7 @@ permission:
11
11
  gh *: allow
12
12
  webfetch: allow
13
13
  edit: deny
14
+ hashline_edit: deny
14
15
  task:
15
16
  "*": deny
16
17
  temperature: 0.2
package/src/agents/nyx.md CHANGED
@@ -9,6 +9,7 @@ permission:
9
9
  bash: allow
10
10
  webfetch: allow
11
11
  edit: deny
12
+ hashline_edit: deny
12
13
  task:
13
14
  "*": deny
14
15
  temperature: 0.1
@@ -8,6 +8,7 @@ permission:
8
8
  grep: allow
9
9
  bash: allow
10
10
  edit: deny
11
+ hashline_edit: deny
11
12
  task:
12
13
  "*": deny
13
14
  temperature: 0.1
@@ -5,6 +5,7 @@ reasoning_effort: medium
5
5
  permission:
6
6
  read: allow
7
7
  edit: deny
8
+ hashline_edit: deny
8
9
  bash: deny
9
10
  task:
10
11
  "*": allow
@@ -32,7 +33,7 @@ skills:
32
33
  ## Memory Protocol
33
34
 
34
35
  **Auto-Store:** Ao receber subtask_summary, chame `memory_store()` com summary/files_changed/tests/status. Sempre.
35
- **Pre-work:** `memory_search("<feature>", top_k=3)` antes de planejar qualquer coisa.
36
+ **Pre-work:** `memory_search(query="<feature>", top_k=3)` antes de planejar qualquer coisa.
36
37
 
37
38
  ## Golden Rule
38
39
 
@@ -79,7 +80,7 @@ O fluxo é SEMPRE: **Planejar → Especificar → Delegar → Revisar**. Zeus nu
79
80
  Antes de usar a arvore de roteamento, consulte o memory:
80
81
 
81
82
  ```
82
- memory_search(task_prompt, top_k=2)
83
+ memory_search(query=task_prompt, top_k=2)
83
84
  → score > 0.85?
84
85
  SIM → usa resultado cacheado (agent, background, pattern)
85
86
  NAO → aplica arvore de roteamento + memory_store() pra proxima vez
@@ -90,8 +91,8 @@ memory_search(task_prompt, top_k=2)
90
91
  Para padroes de delegacao recorrentes, grave no KV:
91
92
 
92
93
  ```
93
- kv_store("delegation:<pattern>", "{agent: ..., background: true/false}")
94
- kv_get("delegation:<pattern>") → reusa decisao sem memory_search
94
+ kv_store(namespace="deleg", key="delegation:<pattern>", value="{agent: ..., background: true/false}")
95
+ kv_get(namespace="deleg", key="delegation:<pattern>") → reusa decisao sem memory_search
95
96
  ```
96
97
 
97
98
  ### Telemetria de delegacao (Nyx P1-3)
@@ -186,12 +187,12 @@ Wave announcement obrigatorio.
186
187
  Limite maximo de 2 niveis de nesting: Zeus -> subagente -> sub-subagente.
187
188
 
188
189
  ```
189
- depth = kv_get("deleg:depth") ?? 0
190
+ depth = kv_get(namespace="deleg", key="deleg:depth") ?? 0
190
191
  if depth >= 2 → NAO delegar, ESCALAR para o usuario
191
- else → kv_store("deleg:depth", depth + 1)
192
+ else → kv_store(namespace="deleg", key="deleg:depth", value=String(depth + 1))
192
193
 
193
194
  Quando subagente retornar:
194
- kv_store("deleg:depth", max(0, depth - 1))
195
+ kv_store(namespace="deleg", key="deleg:depth", value=String(max(0, depth - 1)))
195
196
  ```
196
197
 
197
198
  Zeus (nivel 0) -> Apollo/Hermes (nivel 1) -> sub-subagente (nivel 2 max).
@@ -220,7 +221,7 @@ Quando um subagente RECUSA ou retorna "escopo fora do meu dominio" (ex: edit: de
220
221
 
221
222
  - **Causa provavel:** agente errado selecionado no roteamento (match por palavra-chave, nao por capacidade).
222
223
  - **ACAO:** RE-ROTEAR imediatamente para o agente correto — NAO retry com prompt reformulado.
223
- - Registre o caso no cache de delegacao (`kv_store("deleg:<pattern>", ...)` para aprendizado futuro).
224
+ - Registre o caso no cache de delegacao (`kv_store(namespace="deleg", key="deleg:<pattern>", value=...)` para aprendizado futuro).
224
225
  - Recusa legitima do agente = comportamento correto do guard; o defeito esta na selecao (zeus), nao no agente.
225
226
 
226
227
  ## TODO Enforcer (Auto-Retry)
@@ -237,7 +238,7 @@ Apos task_status(wait=true), verifique:
237
238
  "reviewer-only", "fora do meu dominio", ou similar negativa de dominio)?
238
239
  SIM → NAO retry com prompt rephrased.
239
240
  RE-ROTEAR para o agente correto (ver "Taxonomia de Recusa (P0-3)").
240
- Registre o caso: kv_store("deleg:<pattern>", "{agente_correto, ...}").
241
+ Registre o caso: kv_store(namespace="deleg", key="deleg:<pattern>", value="{agente_correto, ...}").
241
242
 
242
243
  FALHA REAL (timeout, crash, resposta vazia, context exceeded)?
243
244
  SIM → retry 1x com prompt rephrased (diferente, mais especifico)
@@ -54,7 +54,7 @@ Each agent file defines overrides in its `## 🧠 Memory Protocol` section:
54
54
 
55
55
  Para otimizar decisoes de delegacao e reduzir gasto de tokens:
56
56
 
57
- 1. **memory_search(task_prompt, top_k=2)** antes de aplicar a arvore de roteamento
57
+ 1. **memory_search(query=task_prompt, top_k=2)** antes de aplicar a arvore de roteamento
58
58
  2. Se score > 0.85 → reutiliza agente + background_mode do cache
59
59
  3. Se score ≤ 0.85 → aplica regras estaticas e memory_store() com:
60
60
  - key: deleg:<task_type>
@@ -67,8 +67,8 @@ Para otimizar decisoes de delegacao e reduzir gasto de tokens:
67
67
  as a string (got object). json.dumps it first. Example: metadata='{"type":
68
68
  "decision", "score": 0.9}'`.
69
69
 
70
- 4. **kv_store("deleg:<pattern>", ...)** para padroes recorrentes de delegacao
71
- 5. **kv_get("deleg:<pattern>")** para reusar decisoes ja tomadas
70
+ 4. **kv_store(namespace="deleg", key="deleg:<pattern>", value=...)** para padroes recorrentes de delegacao
71
+ 5. **kv_get(namespace="deleg", key="deleg:<pattern>")** para reusar decisoes ja tomadas
72
72
 
73
73
  Isso elimina ~300 tokens de reasoning por delegacao quando o cache acerta.
74
74
 
@@ -82,7 +82,7 @@ After every `/pantheon` council synthesis completes, Zeus stores:
82
82
  memory_store({
83
83
  namespace: "council_decisions",
84
84
  key: "council:<yyyy-mm-dd>:<slug>",
85
- value: {
85
+ value: JSON.stringify({
86
86
  question: "original question",
87
87
  specialists: ["@agent1", "@agent2"],
88
88
  recommendation: "final recommendation",
@@ -93,7 +93,7 @@ memory_store({
93
93
  themis_audit: "approved|issues",
94
94
  precedent_used: false,
95
95
  timestamp: "<ISO-8601>"
96
- },
96
+ }),
97
97
  metadata: JSON.stringify({
98
98
  type: "council_decision",
99
99
  specialist_count: N,
@@ -111,7 +111,7 @@ encoded as a string (got object). json.dumps it first. Example: metadata='{"type
111
111
  ### Read Path (Precedent Fast-Path)
112
112
  Before dispatching a new council, Zeus runs:
113
113
  ```
114
- memory_search(question, top_k=2, namespace="council_decisions")
114
+ memory_search(query=question, top_k=2, namespace="council_decisions")
115
115
  ```
116
116
 
117
117
  Result interpretation:
@@ -56,16 +56,16 @@ All session state lives in **pantheon-persistence** (namespace `checkpoint:<slug
56
56
  No file I/O, no checkpoint_session.py — TTL (4h) handles cleanup automatically.
57
57
 
58
58
  ### Heartbeat Check
59
- - If `context_get(slug, "heartbeat")` returns a checkin older than 300s, log a stall warning and resume
59
+ - If `context_get(slug=slug, key="heartbeat", session_id=SESSION_ID)` returns a checkin older than 300s, log a stall warning and resume
60
60
  - Write heartbeat after every anti-stall recovery action:
61
61
  ```
62
- context_save(slug, "heartbeat", json({"status": "alive", "last_action": "...", "turn_count": N}))
62
+ context_save(slug=slug, key="heartbeat", content=JSON.stringify({"status": "alive", "last_action": "...", "turn_count": N}), session_id=SESSION_ID)
63
63
  ```
64
64
 
65
65
  ### Checkpoint Auto-Save (Pré-Compactação)
66
66
  Before ANY delegate dispatch, save a checkpoint:
67
67
  ```
68
- context_save(slug, "phase:N", json({
68
+ context_save(slug=slug, key="phase:N", content=JSON.stringify({
69
69
  "phase": {"current": N, "total": M, "name": "..."},
70
70
  "turn_count": N, "agent": "...", "summary": "..."
71
71
  }), session_id=SESSION_ID)
@@ -86,15 +86,15 @@ o Zeus DEVE salvar o estado atual:
86
86
  2. Salve heartbeat + phase atual + tarefas pendentes
87
87
  3. Só então permita que a compactação prossiga
88
88
  ```
89
- # Ao iniciar sessão:
90
- result = context_save(slug, "init", session_state)
89
+ # Ao iniciar sessão (session_id é REQUIRED — use o id da sessão corrente):
90
+ result = context_save(slug=slug, key="init", content=JSON.stringify(session_state), session_id=SESSION_ID)
91
91
  SESSION_ID = result.session_id # ← guarde para toda a sessão
92
92
 
93
93
  # Antes de CADA delegação:
94
- context_save(slug, f"pre:{agent}", current_state, session_id=SESSION_ID)
94
+ context_save(slug=slug, key=f"pre:{agent}", content=JSON.stringify(current_state), session_id=SESSION_ID)
95
95
 
96
96
  # Após retorno do agente:
97
- context_save(slug, f"post:{agent}", result_state, session_id=SESSION_ID)
97
+ context_save(slug=slug, key=f"post:{agent}", content=JSON.stringify(result_state), session_id=SESSION_ID)
98
98
  ```
99
99
  Isso garante que o estado sobreviva à compactação — o "latest" pointer
100
100
  sempre aponta para o checkpoint mais recente, mesmo após compactação.
@@ -103,9 +103,9 @@ sempre aponta para o checkpoint mais recente, mesmo após compactação.
103
103
  ### Context Retrieval
104
104
  Next-phase agents retrieve previous context via:
105
105
  ```
106
- context_get(slug, "latest") # most recent checkpoint
107
- context_get(slug, "phase:3") # specific phase
108
- context_list(slug) # all checkpoints
106
+ context_get(slug=slug, session_id=SESSION_ID) # most recent checkpoint ("latest" is the default key)
107
+ context_get(slug=slug, key="phase:3", session_id=SESSION_ID) # specific phase
108
+ context_list(slug=slug, session_id=SESSION_ID) # all checkpoints
109
109
  ```
110
110
 
111
111
  ### Long-Session Progress
@@ -36,7 +36,7 @@ sequenceDiagram
36
36
  participant T as Themis (Audit + Moderator)
37
37
 
38
38
  U->>Z: /pantheon [--research] question
39
- Z->>M: memory_search(council_decisions)
39
+ Z->>M: memory_search(query, namespace="council_decisions")
40
40
  alt Precedent found (score > 0.85)
41
41
  M-->>Z: ⚠️ Cached decision
42
42
  Z->>U: Fast-path precedent
@@ -85,7 +85,7 @@ sequenceDiagram
85
85
  ## Dispatch Sequence (9-Step Protocol)
86
86
 
87
87
  ### Step 0 — Precedent Fast-Path (Fase 1)
88
- Run the precedent read path defined in `## Memory Protocol > Council Decisions Namespace` (`memory_search(query, top_k=2, namespace="council_decisions")`). If no precedent applies, proceed to Step 0b.
88
+ Run the precedent read path defined in `## Memory Protocol > Council Decisions Namespace` (`memory_search(query=question, top_k=2, namespace="council_decisions")`). If no precedent applies, proceed to Step 0b.
89
89
 
90
90
  ### Step 0b — Apollo Pre-Scan (Fase 2, --research flag)
91
91
  If `/pantheon --research <question>`: dispatch @apollo with 30s timeout. Inject findings as `shared_context` into ALL specialist prompts. Skip if flag absent.
@@ -167,7 +167,7 @@ async function detectVersion(api) {
167
167
  if (ver) return ver;
168
168
  }
169
169
  } catch {}
170
- return "1.6.0-beta.5";
170
+ return "1.6.0-beta.6";
171
171
  }
172
172
  /**
173
173
  * usage-bar — AI subscription usage gauge for the opencode TUI.
@@ -1,12 +1,12 @@
1
1
  {
2
2
  "name": "pantheon-tui",
3
- "version": "1.6.0-beta.5",
3
+ "version": "1.6.0-beta.6",
4
4
  "lockfileVersion": 3,
5
5
  "requires": true,
6
6
  "packages": {
7
7
  "": {
8
8
  "name": "pantheon-tui",
9
- "version": "1.6.0-beta.5",
9
+ "version": "1.6.0-beta.6",
10
10
  "license": "MIT",
11
11
  "dependencies": {
12
12
  "@opencode-ai/plugin": "1.18.33",
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "pantheon-tui",
3
3
  "type": "module",
4
- "version": "1.6.0-beta.5",
4
+ "version": "1.6.0-beta.6",
5
5
  "description": "Pantheon TUI plugin — sidebar (version, preset, sessions, real-time delegations) + AI subscription usage gauges incl. OpenCode Go/Zen dollar limits (vendored, MIT)",
6
6
  "license": "MIT",
7
7
  "engines": {
@@ -163,19 +163,19 @@ Should I now run the migration tests? [waiting]
163
163
  Session state is **not** file-based. It lives in **pantheon-persistence** under the
164
164
  `checkpoint:<slug>` namespace and is cleaned up automatically by a 4h TTL.
165
165
 
166
- Write state with `context_save(slug, key, value, session_id=...)` and read it back with
167
- `context_get(slug, "latest", session_id=...)`. Capture `session_id` from the first `context_save`
166
+ Write state with `context_save(slug=slug, key=key, content=JSON.stringify(state), session_id=...)` and read it back with
167
+ `context_get(slug=slug, key="latest", session_id=...)`. Capture `session_id` from the first `context_save`
168
168
  of the session and reuse it for the rest of the session, so the `latest` pointer survives
169
169
  compaction.
170
170
 
171
171
  ### Heartbeat Check
172
- - If `context_get(slug, "heartbeat")` returns a checkin older than 300s, log a stall warning and resume
172
+ - If `context_get(slug=slug, key="heartbeat", session_id=...)` returns a checkin older than 300s, log a stall warning and resume
173
173
  - Write a heartbeat after every anti-stall recovery action
174
174
 
175
175
  ### Checkpoint Rules
176
176
  1. Save a checkpoint before **every** delegate dispatch and after every agent returns
177
177
  2. Include current phase, turn count, and remaining tasks
178
- 3. On resume: `context_get(slug, "latest")` restores the most recent checkpoint
178
+ 3. On resume: `context_get(slug=slug, key="latest", session_id=...)` restores the most recent checkpoint
179
179
 
180
180
  > Do not create `heartbeat.json`, `checkpoint-<N>.json`, or `session.json` under
181
181
  > `.pantheon/deepwork/`. That file-based mechanism is retired — TTL handles cleanup, and reading