pantheon-opencode 1.6.0-beta.3 → 1.6.0-beta.4

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.
Files changed (36) hide show
  1. package/CHANGELOG.md +22 -0
  2. package/README.md +19 -7
  3. package/README.pt-BR.md +17 -7
  4. package/package.json +12 -10
  5. package/plugin.json +1 -1
  6. package/pyproject.toml +1 -1
  7. package/scripts/code_mode_server.py +1 -1
  8. package/scripts/cost.mjs +244 -41
  9. package/scripts/install/agents-md.mjs +2 -2
  10. package/scripts/install/health-check.mjs +9 -2
  11. package/scripts/install/shared.mjs +5 -5
  12. package/scripts/mcp_persistence_server.py +1 -1
  13. package/scripts/mcp_resources_server.py +1 -1
  14. package/scripts/memory_mcp_server.py +1 -1
  15. package/scripts/probe-context-rehydrate.mjs +6 -0
  16. package/scripts/probe-pantheon-cost.mjs +71 -15
  17. package/scripts/validate-routing.mjs +3 -3
  18. package/src/mcp/code_mode_server.py +1 -1
  19. package/src/mcp/mcp_persistence_server.py +1 -1
  20. package/src/mcp/mcp_resources_server.py +1 -1
  21. package/src/mcp/memory_mcp_server.py +1 -1
  22. package/src/mcp/pantheon_vision_server.py +1 -1
  23. package/src/mcp/requirements-mcp.txt +6 -4
  24. package/src/mcp/requirements-vision.txt +2 -2
  25. package/src/pantheon/cost-command.ts +399 -101
  26. package/src/pantheon/presets.mjs +3 -3
  27. package/src/plugin-v2.ts +131 -58
  28. package/src/plugins/tui/dist/server.d.ts +1 -2
  29. package/src/plugins/tui/dist/server.d.ts.map +1 -1
  30. package/src/plugins/tui/dist/tui.d.ts +74 -74
  31. package/src/plugins/tui/dist/tui.d.ts.map +1 -1
  32. package/src/plugins/tui/dist/tui.js +17 -20
  33. package/src/plugins/tui/package-lock.json +364 -317
  34. package/src/plugins/tui/package.json +8 -5
  35. package/src/plugins/tui/tsconfig.build.json +5 -0
  36. package/src/plugins/tui/tsdown.config.ts +3 -0
@@ -56,11 +56,48 @@ function setEnvironment(values) {
56
56
  }
57
57
  }
58
58
 
59
+ /**
60
+ * Write the rows in the family the requested host generation actually uses.
61
+ *
62
+ * Call sites describe an assistant turn the way they always have
63
+ * (`role: 'assistant'`, plus a `phase`). This function translates that
64
+ * descriptor into what is on disk: for V2, `role` becomes the `type` COLUMN and
65
+ * `phase` is dropped, because a real V2 payload carries neither — measured, the
66
+ * `role` key is absent from 40052 of 40052 rows on a host 2.0.22 database.
67
+ *
68
+ * Keeping that translation in ONE place is the point: a fixture that wrote
69
+ * `role` into a session_message row asserts the V1 format under a V2 name, which
70
+ * is exactly how the stale V1 predicate in the CLI mirror stayed green — the
71
+ * synthetic database agreed with the bug instead of contradicting it.
72
+ */
59
73
  function createSyntheticDb(DatabaseSync, path, rows) {
60
74
  const db = new DatabaseSync(path)
61
- db.exec('CREATE TABLE message (data TEXT NOT NULL, time_created INTEGER NOT NULL)')
62
- const insert = db.prepare('INSERT INTO message (data, time_created) VALUES (?, ?)')
63
- for (const data of rows) insert.run(JSON.stringify(data), Date.now())
75
+ if (requestedVersion === 'v2') {
76
+ db.exec(
77
+ 'CREATE TABLE session_message (id TEXT, session_id TEXT, type TEXT, seq INTEGER, time_created INTEGER, time_updated INTEGER, data TEXT)',
78
+ )
79
+ const insert = db.prepare(
80
+ 'INSERT INTO session_message (id, session_id, type, seq, time_created, time_updated, data) VALUES (?,?,?,?,?,?,?)',
81
+ )
82
+ rows.forEach((row, index) => {
83
+ const payload = { ...row }
84
+ delete payload.role
85
+ delete payload.phase
86
+ insert.run(
87
+ `msg_${index + 1}`,
88
+ 'ses_probe',
89
+ row.role,
90
+ index + 1,
91
+ Date.now(),
92
+ Date.now(),
93
+ JSON.stringify(payload),
94
+ )
95
+ })
96
+ } else {
97
+ db.exec('CREATE TABLE message (data TEXT NOT NULL, time_created INTEGER NOT NULL)')
98
+ const insert = db.prepare('INSERT INTO message (data, time_created) VALUES (?, ?)')
99
+ for (const data of rows) insert.run(JSON.stringify(data), Date.now())
100
+ }
64
101
  db.close()
65
102
  }
66
103
 
@@ -129,7 +166,10 @@ async function main() {
129
166
  const root = mkdtempSync(join(tmpdir(), 'pantheon-cost-probe-'))
130
167
  const dataHome = join(root, 'data')
131
168
  const opencodeDir = join(dataHome, 'opencode')
132
- const selectedDb = join(opencodeDir, requestedVersion === 'v2' ? 'opencode-v2.db' : 'opencode.db')
169
+ // Every real installation writes `opencode.db`; the v1/v2 distinction is the
170
+ // SCHEMA of that file, never its name. Deriving `opencode-v2.db` here made the
171
+ // probe assert a path that exists on no real machine.
172
+ const selectedDb = join(opencodeDir, 'opencode.db')
133
173
  const otherDb = join(root, 'other.db')
134
174
  const envDb = join(root, 'env.db')
135
175
  const explicitDb = join(root, 'explicit.db')
@@ -139,7 +179,9 @@ async function main() {
139
179
  try {
140
180
  mkdirSync(opencodeDir, { recursive: true })
141
181
  // These rows intentionally include a monetary-looking field to prove it is
142
- // ignored; only input/output/total tokens may appear in the report.
182
+ // ignored; only input/output/total tokens may appear in the report. Every
183
+ // V2 phase reads back as `unknown` because the V2 reader emits that literal:
184
+ // a V2 payload carries no phase field for a reader to recover.
143
185
  const selectedRows =
144
186
  requestedVersion === 'v2'
145
187
  ? [
@@ -224,8 +266,8 @@ async function main() {
224
266
  selectedOutput,
225
267
  requestedVersion === 'v2'
226
268
  ? [
227
- { agent: 'aphrodite', phase: 'plan', input: 8, output: 10, total: 18 },
228
- { agent: 'zeus', phase: 'audit', input: 4, output: 6, total: 10 },
269
+ { agent: 'aphrodite', phase: 'unknown', input: 8, output: 10, total: 18 },
270
+ { agent: 'zeus', phase: 'unknown', input: 4, output: 6, total: 10 },
229
271
  ]
230
272
  : [
231
273
  { agent: 'hermes', phase: 'plan', input: 13, output: 24, total: 37 },
@@ -250,7 +292,12 @@ async function main() {
250
292
  {},
251
293
  { sessionID: 'cost-probe' },
252
294
  )
253
- requireOutput(output, '| env-agent | override | 1 | 2 | 3 |', 'PANTHEON_COST_DB')
295
+ // V2 has no phase to report, so the same row reads back as 'unknown'.
296
+ requireOutput(
297
+ output,
298
+ `| env-agent | ${requestedVersion === 'v2' ? 'unknown' : 'override'} | 1 | 2 | 3 |`,
299
+ 'PANTHEON_COST_DB',
300
+ )
254
301
  if (output.includes('wrong-version') || output.includes('env-agent') === false) {
255
302
  throw new Error('PANTHEON_COST_DB did not override the version-selected database')
256
303
  }
@@ -269,7 +316,11 @@ async function main() {
269
316
  {},
270
317
  { sessionID: 'cost-probe' },
271
318
  )
272
- requireOutput(output, '| explicit-agent | explicit | 9 | 1 | 10 |', 'explicit dbPath')
319
+ requireOutput(
320
+ output,
321
+ `| explicit-agent | ${requestedVersion === 'v2' ? 'unknown' : 'explicit'} | 9 | 1 | 10 |`,
322
+ 'explicit dbPath',
323
+ )
273
324
  if (output.includes('env-agent')) throw new Error('explicit dbPath did not take precedence')
274
325
  checks.push('explicit dbPath precedence: PASS')
275
326
  } finally {
@@ -287,11 +338,7 @@ async function main() {
287
338
  { sessionID: 'cost-probe' },
288
339
  )
289
340
  requireOutput(output, 'not found', 'missing DB')
290
- requireOutput(
291
- output,
292
- requestedVersion === 'v2' ? 'opencode-v2.db' : 'opencode.db',
293
- 'selected missing DB path',
294
- )
341
+ requireOutput(output, 'opencode.db', 'selected missing DB path')
295
342
  checks.push('missing DB: PASS')
296
343
  } finally {
297
344
  missingRestore()
@@ -302,7 +349,16 @@ async function main() {
302
349
  }).pantheon_cost.execute({}, { sessionID: 'cost-probe' })
303
350
  requireOutput(incompatibleOutput, 'incompatible opencode.db schema', 'incompatible schema')
304
351
  checks.push('incompatible schema: PASS')
305
- emit('PASS', 'offline synthetic DB checks passed; no real DB was read', checks)
352
+ // Names the synthetic fixture that was built, because the previous wording
353
+ // ("no real DB was read") was true of every run and therefore true of the run
354
+ // in which the V2 ledger was read as V1 and silently dropped. A verdict that
355
+ // cannot fail is not a verdict: it stayed green across the exact defect it
356
+ // was meant to cover.
357
+ emit(
358
+ 'PASS',
359
+ `${checks.length} synthetic ${requestedVersion} checks passed against a temporary opencode.db (no user database was read)`,
360
+ checks,
361
+ )
306
362
  } catch (error) {
307
363
  emit('FAIL', diagnostic(error), checks)
308
364
  } finally {
@@ -19,7 +19,7 @@ import { createHash } from 'node:crypto'
19
19
  import { existsSync, readdirSync, readFileSync } from 'node:fs'
20
20
  import { dirname, join } from 'node:path'
21
21
  import { fileURLToPath } from 'node:url'
22
- import yaml from 'js-yaml'
22
+ import { load } from 'js-yaml'
23
23
  import { validatePresetDefs } from '../src/pantheon/presets.mjs'
24
24
 
25
25
  const __dirname = dirname(fileURLToPath(import.meta.url))
@@ -52,7 +52,7 @@ if (!existsSync(routingPath)) {
52
52
  console.error('❌ routing.yml not found')
53
53
  process.exit(1)
54
54
  }
55
- const routing = yaml.load(readFileSync(routingPath, 'utf8'))
55
+ const routing = load(readFileSync(routingPath, 'utf8'))
56
56
 
57
57
  console.log('🔍 Validating routing.yml...\n')
58
58
 
@@ -253,7 +253,7 @@ function parseFrontmatter(filePath) {
253
253
  const match = content.match(FRONTMATTER_RE)
254
254
  if (!match) return null
255
255
  try {
256
- return yaml.load(match[1])
256
+ return load(match[1])
257
257
  } catch {
258
258
  return null
259
259
  }
@@ -36,7 +36,7 @@ from typing import Any
36
36
 
37
37
  import yaml
38
38
  from _pantheon_paths import pantheon_home, pantheon_project
39
- from mcp.server.fastmcp import FastMCP
39
+ from fastmcp import FastMCP
40
40
 
41
41
  # ── Constants ─────────────────────────────────────────────────────────────────
42
42
  ALLOWED_EXTENSIONS: frozenset[str] = frozenset({".sh", ".py"})
@@ -25,7 +25,7 @@ from pathlib import Path
25
25
  from typing import Annotated
26
26
 
27
27
  from _pantheon_paths import pantheon_home, pantheon_project
28
- from mcp.server.fastmcp import FastMCP
28
+ from fastmcp import FastMCP
29
29
  from pydantic import Field
30
30
 
31
31
  # ── Schema ──────────────────────────────────────────────────────────────────────
@@ -19,7 +19,7 @@ from typing import Any
19
19
  import yaml
20
20
  from _pantheon_paths import has_symlink_component, pantheon_home, pantheon_project
21
21
  from eval_store import get_latest_eval, list_evals
22
- from mcp.server.fastmcp import FastMCP
22
+ from fastmcp import FastMCP
23
23
 
24
24
  # ── Path Resolution ──────────────────────────────────────────────────────────
25
25
  _PANTHEON_HOME: Path = pantheon_home()
@@ -34,7 +34,7 @@ from pathlib import Path
34
34
  from typing import Annotated, Any
35
35
 
36
36
  from _pantheon_paths import pantheon_home
37
- from mcp.server.fastmcp import FastMCP
37
+ from fastmcp import FastMCP
38
38
  from pydantic import BeforeValidator, Field
39
39
 
40
40
  try:
@@ -24,7 +24,7 @@ from typing import Any
24
24
  from urllib.parse import unquote, urlparse
25
25
 
26
26
  import httpx
27
- from mcp.server.fastmcp import FastMCP
27
+ from fastmcp import FastMCP
28
28
 
29
29
  mcp = FastMCP("pantheon-vision")
30
30
 
@@ -1,10 +1,12 @@
1
1
  # Pantheon MCP runtime dependencies — EXACT pins (issue #21)
2
- # Pinned to the versions validated in the sandbox venv (2026-08-05).
3
- # Update deliberately via: pip install -U <pkg> && pip freeze | grep -iE '^(mcp|fastmcp|pyyaml)'
2
+ # Coordinated MCP SDK/FastMCP major migration (Dependabot PRs #140 and #188).
3
+ # Update deliberately via: pip install -U <pkg> && pip freeze | grep -iE '^(mcp|fastmcp|pyyaml|httpx)=='
4
4
  # Memory search is FTS5 (SQLite stdlib): sqlite-vec and fastembed were removed
5
5
  # with the vector pipeline — the server no longer imports either.
6
- mcp==1.29.0
7
- fastmcp==3.4.6
6
+ mcp==2.2.0
7
+ fastmcp==4.0.10
8
8
  pydantic==2.12.5
9
9
  pydantic-settings==2.14.2
10
10
  pyyaml==6.0.3
11
+ # Vision imports httpx; MCP 2.2.0 provides httpx2, a different package.
12
+ httpx==0.28.1
@@ -1,5 +1,5 @@
1
- mcp==1.29.0
2
- fastmcp==3.4.6
1
+ mcp==2.2.0
2
+ fastmcp==4.0.10
3
3
  pydantic==2.12.5
4
4
  pydantic-settings==2.14.2
5
5
  httpx==0.28.1