pantheon-opencode 1.3.4 → 1.3.5-beta.52.c75b04d

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.
@@ -10,8 +10,9 @@
10
10
  * - missing/unreadable db → FRIENDLY error string, never a crash (tools
11
11
  * return errors as TEXT).
12
12
  *
13
- * dbPath resolution: explicit option > env PANTHEON_COST_DB > XDG data
14
- * home > ~/.local/share/opencode/opencode.db. The tool is wired in
13
+ * dbPath resolution: explicit option > env PANTHEON_COST_DB > the version-aware
14
+ * default selected by PANTHEON_OPENCODE_VERSION (v1 or v2). An unset version
15
+ * deliberately preserves V1 behavior. The tool is wired in
15
16
  * plugin.ts alongside the delegation toolset (usable by zeus and any agent
16
17
  * with tool access — no routing change needed).
17
18
  *
@@ -41,6 +42,8 @@ export interface CostCommandOptions {
41
42
  dbPath?: string
42
43
  }
43
44
 
45
+ export type OpenCodeVersion = 'v1' | 'v2'
46
+
44
47
  /** One cost tool: description + zod args shape + execute (delegation.ts shape). */
45
48
  export interface CostTool<Args extends z.ZodRawShape = typeof costArgs> {
46
49
  description: string
@@ -62,15 +65,32 @@ const costArgs = {
62
65
  .describe('Number of days of delegation history to include (default 7).'),
63
66
  } satisfies z.ZodRawShape
64
67
 
65
- /** Resolve candidate db paths in priority order (env override first). */
66
- function dbCandidates(): string[] {
67
- const candidates: string[] = []
68
- const env = process.env.PANTHEON_COST_DB
69
- if (env !== undefined && env !== '') candidates.push(env)
68
+ /** Return the safe, isolated state DB name for an OpenCode version. */
69
+ function defaultDbPath(version: OpenCodeVersion | undefined): string {
70
70
  const xdg = process.env.XDG_DATA_HOME
71
71
  const dataHome = xdg !== undefined && xdg !== '' ? xdg : join(homedir(), '.local', 'share')
72
- candidates.push(join(dataHome, 'opencode', 'opencode.db'))
73
- return candidates
72
+ const filename = version === 'v2' ? 'opencode-v2.db' : 'opencode.db'
73
+ return join(dataHome, 'opencode', filename)
74
+ }
75
+
76
+ /**
77
+ * Resolve the database without probing another version's database.
78
+ *
79
+ * Explicit paths always win. A version selector changes exactly one default
80
+ * filename; it never falls back to V1, which prevents a V2 report from
81
+ * silently reading V1 history (or vice versa).
82
+ */
83
+ export function resolveCostDbPath(options?: CostCommandOptions): string {
84
+ const explicit = options?.dbPath || process.env.PANTHEON_COST_DB
85
+ if (explicit) return explicit
86
+
87
+ const configuredVersion = process.env.PANTHEON_OPENCODE_VERSION
88
+ if (configuredVersion !== undefined && configuredVersion !== 'v1' && configuredVersion !== 'v2') {
89
+ throw new Error(
90
+ `invalid PANTHEON_OPENCODE_VERSION "${configuredVersion}"; expected "v1" or "v2"`,
91
+ )
92
+ }
93
+ return defaultDbPath(configuredVersion as OpenCodeVersion | undefined)
74
94
  }
75
95
 
76
96
  /**
@@ -79,11 +99,28 @@ function dbCandidates(): string[] {
79
99
  * to the real opencode.db).
80
100
  */
81
101
  function findExistingDb(override?: string): string | undefined {
82
- const candidates = override !== undefined && override !== '' ? [override] : dbCandidates()
83
- for (const candidate of candidates) {
84
- if (existsSync(candidate)) return candidate
102
+ const candidate =
103
+ override === undefined ? resolveCostDbPath() : resolveCostDbPath({ dbPath: override })
104
+ return existsSync(candidate) ? candidate : undefined
105
+ }
106
+
107
+ function assertCompatibleSchema(db: {
108
+ prepare(sql: string): { get(): unknown; all(...args: unknown[]): unknown[] }
109
+ }): void {
110
+ const row = db
111
+ .prepare("SELECT name FROM sqlite_master WHERE type = 'table' AND name = 'message'")
112
+ .get()
113
+ if (row === undefined) {
114
+ throw new Error('incompatible opencode.db schema: required "message" table is missing')
115
+ }
116
+ const columns = db.prepare('PRAGMA table_info(message)').all() as Array<{ name?: unknown }>
117
+ const names = new Set(columns.map((column) => column.name))
118
+ const missing = ['data', 'time_created'].filter((name) => !names.has(name))
119
+ if (missing.length > 0) {
120
+ throw new Error(
121
+ `incompatible opencode.db schema: message table is missing ${missing.join(', ')}; select the matching V1/V2 database`,
122
+ )
85
123
  }
86
- return undefined
87
124
  }
88
125
 
89
126
  /** Aggregate cost + tokens by agent via node:sqlite (read-only). */
@@ -91,6 +128,7 @@ async function queryWithNodeSqlite(dbPath: string, days: number): Promise<CostRo
91
128
  const sqlite = await import('node:sqlite')
92
129
  const db = new sqlite.DatabaseSync(dbPath, { readOnly: true })
93
130
  try {
131
+ assertCompatibleSchema(db)
94
132
  const since = Date.now() - days * 86_400_000
95
133
  const rows = db.prepare('SELECT data FROM message WHERE time_created >= ?').all(since)
96
134
  return aggregateMessages(rows.map((row) => String(row.data)))
@@ -180,16 +218,17 @@ export function createCostCommand(options?: CostCommandOptions): CostCommand {
180
218
  try {
181
219
  const dbPath = findExistingDb(options?.dbPath)
182
220
  if (dbPath === undefined) {
183
- const expected =
184
- options?.dbPath ?? process.env.PANTHEON_COST_DB ?? '~/.local/share/opencode/opencode.db'
221
+ const expected = resolveCostDbPath(options)
185
222
  return (
186
223
  `pantheon_cost failed: opencode.db not found (expected at ${expected}). ` +
187
224
  `No cost history is available until opencode has stored session data.`
188
225
  )
189
226
  }
190
- const rows = await queryWithNodeSqlite(dbPath, days).catch(async () =>
191
- queryWithScript(scriptPath, dbPath, days),
192
- )
227
+ const rows = await queryWithNodeSqlite(dbPath, days).catch(async (err: unknown) => {
228
+ if (err instanceof Error && err.message.startsWith('incompatible opencode.db schema'))
229
+ throw err
230
+ return queryWithScript(scriptPath, dbPath, days)
231
+ })
193
232
  return renderMarkdown(rows, days)
194
233
  } catch (err: unknown) {
195
234
  const reason = err instanceof Error ? err.message : String(err)
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "pantheon-tui",
3
3
  "type": "module",
4
- "version": "1.3.4",
4
+ "version": "1.3.5-beta.52.c75b04d",
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
  "exports": {