pantheon-opencode 1.5.1 → 1.5.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -18,6 +18,11 @@ 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.5.2] - 2026-09-22
22
+
23
+ ## 🐞 Fixed
24
+ - **install** — guard undefined collection in init (#174)
25
+ - **release** — compare Zenodo file checksum in the format the API returns (#173)
21
26
  ## [v1.5.1] - 2026-09-22
22
27
 
23
28
  ## 🐞 Fixed
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pantheon-opencode",
3
- "version": "1.5.1",
3
+ "version": "1.5.2",
4
4
  "description": "Multi-agent orchestration for OpenCode — 14 specialized agents, TDD, Themis review gates.",
5
5
  "type": "module",
6
6
  "publishConfig": {
package/plugin.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "pantheon",
3
3
  "publisher": "ils15",
4
- "version": "1.5.1",
4
+ "version": "1.5.2",
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.5.1"
7
+ version = "1.5.2"
8
8
  description = "Multi-agent orchestration framework"
9
9
  requires-python = ">=3.11"
10
10
  dependencies = []
@@ -108,7 +108,32 @@ export function upgradeStateV1toV2(state) {
108
108
  }
109
109
 
110
110
  /**
111
- * Normalize any state to v2 (handles v1 → v2 upgrade).
111
+ * Guarantee the required v2 collections exist on a state object.
112
+ *
113
+ * A schema-v2 entry may be written by a partial writer — e.g. the postinstall
114
+ * version marker in sync-tui.mjs persisted only `pantheon_version` /
115
+ * `previous_version`, and writeState() then stamped it `schema_version: 2`.
116
+ * Such an entry has no `applied_migrations`, so the next `init` crashed in
117
+ * runMigrations() with
118
+ * "Cannot read properties of undefined (reading 'push')".
119
+ * Backfill the collections on read instead of trusting every writer.
120
+ * @param {object} state
121
+ * @returns {object} the same state object with required collections present
122
+ */
123
+ function ensureV2Shape(state) {
124
+ if (!Array.isArray(state.applied_migrations)) state.applied_migrations = []
125
+ if (
126
+ !state.components ||
127
+ typeof state.components !== 'object' ||
128
+ Array.isArray(state.components)
129
+ ) {
130
+ state.components = upgradeStateV1toV2(state).components
131
+ }
132
+ return state
133
+ }
134
+
135
+ /**
136
+ * Normalize any state to v2 (handles v1 → v2 upgrade and partial v2 markers).
112
137
  * @param {object} state
113
138
  * @returns {object|null}
114
139
  */
@@ -116,7 +141,7 @@ function normalizeState(state) {
116
141
  if (!state || typeof state !== 'object') return null
117
142
  if (isV1State(state)) return upgradeStateV1toV2(state)
118
143
  if (!state.schema_version || state.schema_version < 2) return upgradeStateV1toV2(state)
119
- return state
144
+ return ensureV2Shape(state)
120
145
  }
121
146
 
122
147
  /**
@@ -193,7 +193,7 @@ async function main() {
193
193
  // The plugin references in opencode.json already point INTO this package,
194
194
  // so only the copies would go stale after a package update.
195
195
  const { syncCopyArtifacts } = await import('./sync-artifacts.mjs')
196
- const { readState, writeState } = await import('./install/state.mjs')
196
+ const { createInitialState, readState, writeState } = await import('./install/state.mjs')
197
197
  const pkgVersion = readVersion(join(ROOT, 'package.json')) ?? 'unknown'
198
198
  const synced = syncCopyArtifacts(configDir)
199
199
  if (synced.errors.length > 0) {
@@ -206,10 +206,16 @@ async function main() {
206
206
  // venv, MCP entries).
207
207
  try {
208
208
  const previous = readState(configDir)
209
- writeState(configDir, {
210
- pantheon_version: pkgVersion,
211
- previous_version: previous?.pantheon_version ?? null,
212
- })
209
+ // Preserve the existing v2 manifest (applied_migrations, components, …).
210
+ // Writing a bare { pantheon_version, previous_version } object stamped
211
+ // it `schema_version: 2` while dropping `applied_migrations`, and the
212
+ // next `init` crashed in runMigrations() reading `.push` on undefined.
213
+ const state = previous ?? createInitialState(pkgVersion)
214
+ if (previous?.pantheon_version && previous.pantheon_version !== pkgVersion) {
215
+ state.previous_version = previous.pantheon_version
216
+ }
217
+ state.pantheon_version = pkgVersion
218
+ writeState(configDir, state)
213
219
  if (previous?.pantheon_version && previous.pantheon_version !== pkgVersion) {
214
220
  console.log(
215
221
  ` ⚠️ Installation moved ${previous.pantheon_version} → ${pkgVersion}. ` +
@@ -1,11 +1,75 @@
1
1
  import { createHash } from 'node:crypto'
2
2
  import { readFileSync } from 'node:fs'
3
3
 
4
+ /** Hex digest length for every algorithm the Zenodo deposition API may report. */
5
+ const DIGEST_LENGTH = { md5: 32, sha256: 64 }
6
+
4
7
  export function sha256File(path) {
5
8
  return createHash('sha256').update(readFileSync(path)).digest('hex')
6
9
  }
7
10
 
8
- export function findZenodoFile(record, filename, checksum) {
11
+ export function md5File(path) {
12
+ return createHash('md5').update(readFileSync(path)).digest('hex')
13
+ }
14
+
15
+ /**
16
+ * Normalize a file checksum as returned by the Zenodo deposition files API.
17
+ *
18
+ * Zenodo computes an MD5 for every deposited file (developers.zenodo.org,
19
+ * "Deposition File" representation: "MD5 checksum of file, computed by our
20
+ * system"). The API emits it both as `md5:<hex>` and, because of a legacy
21
+ * serialization bug kept for backward compatibility (zenodo/zenodo#628), as a
22
+ * bare 32-character hex string without any algorithm prefix — the exact payload
23
+ * that broke the v1.5.1 run (e.g. `d3359dca2f998da8461abb3b84a32b6c`).
24
+ *
25
+ * A bare 64-character digest is treated as SHA-256, so if the API ever upgrades
26
+ * its algorithm the upload is still verified with a strong digest rather than
27
+ * accepted blindly. Anything unrecognized fails closed.
28
+ */
29
+ export function parseZenodoChecksum(value) {
30
+ if (typeof value !== 'string') throw new Error('Zenodo file checksum is missing.')
31
+ const match = value
32
+ .trim()
33
+ .toLowerCase()
34
+ .match(/^(?:(md5|sha256):)?([0-9a-f]+)$/)
35
+ if (!match) throw new Error(`Unrecognized Zenodo checksum format: ${value}.`)
36
+ const algorithm =
37
+ match[1] ??
38
+ (match[2].length === DIGEST_LENGTH.md5
39
+ ? 'md5'
40
+ : match[2].length === DIGEST_LENGTH.sha256
41
+ ? 'sha256'
42
+ : null)
43
+ if (!algorithm) throw new Error(`Unrecognized Zenodo checksum length: ${value}.`)
44
+ if (match[2].length !== DIGEST_LENGTH[algorithm])
45
+ throw new Error(`Zenodo checksum length does not match ${algorithm}: ${value}.`)
46
+ return { algorithm, digest: match[2] }
47
+ }
48
+
49
+ function expectedDigests(expected) {
50
+ const provided = {}
51
+ if (typeof expected === 'string') {
52
+ const { algorithm, digest } = parseZenodoChecksum(expected)
53
+ provided[algorithm] = digest
54
+ } else if (expected && typeof expected === 'object') {
55
+ for (const [algorithm, digest] of Object.entries(expected)) {
56
+ if (digest === undefined || digest === null || digest === '') continue
57
+ if (!(algorithm in DIGEST_LENGTH))
58
+ throw new Error(`Unsupported local checksum algorithm: ${algorithm}.`)
59
+ if (
60
+ typeof digest !== 'string' ||
61
+ !new RegExp(`^[0-9a-f]{${DIGEST_LENGTH[algorithm]}}$`, 'i').test(digest.trim())
62
+ )
63
+ throw new Error(`Invalid local ${algorithm} checksum for verification.`)
64
+ provided[algorithm] = digest.trim().toLowerCase()
65
+ }
66
+ }
67
+ if (Object.keys(provided).length === 0)
68
+ throw new Error('A local checksum is required to verify the Zenodo upload.')
69
+ return provided
70
+ }
71
+
72
+ export function findZenodoFile(record, filename, expected) {
9
73
  if (!record || typeof record !== 'object' || !Array.isArray(record.files)) {
10
74
  throw new Error('Zenodo record did not contain a files array.')
11
75
  }
@@ -13,9 +77,18 @@ export function findZenodoFile(record, filename, checksum) {
13
77
  if (matches.length > 1) throw new Error(`Zenodo record contains duplicate file: ${filename}.`)
14
78
  if (matches.length === 0) return null
15
79
  const file = matches[0]
16
- const expected = `sha256:${checksum}`
17
- if (file.checksum !== expected) {
18
- throw new Error(`Zenodo checksum mismatch for ${filename}.`)
80
+ const { algorithm, digest } = parseZenodoChecksum(file.checksum)
81
+ const provided = expectedDigests(expected)
82
+ const reference = provided[algorithm]
83
+ if (!reference) {
84
+ throw new Error(
85
+ `Zenodo reported a ${algorithm} checksum for ${filename} but no local ${algorithm} checksum was provided.`,
86
+ )
87
+ }
88
+ if (digest !== reference) {
89
+ throw new Error(
90
+ `Zenodo ${algorithm} checksum mismatch for ${filename}: expected ${reference}, received ${digest}.`,
91
+ )
19
92
  }
20
93
  return file
21
94
  }
@@ -165,7 +165,7 @@ async function detectVersion(api) {
165
165
  if (ver) return ver;
166
166
  }
167
167
  } catch {}
168
- return "1.5.1";
168
+ return "1.5.2";
169
169
  }
170
170
  /**
171
171
  * usage-bar — AI subscription usage gauge for the opencode TUI.
@@ -1,12 +1,12 @@
1
1
  {
2
2
  "name": "pantheon-tui",
3
- "version": "1.5.1",
3
+ "version": "1.5.2",
4
4
  "lockfileVersion": 3,
5
5
  "requires": true,
6
6
  "packages": {
7
7
  "": {
8
8
  "name": "pantheon-tui",
9
- "version": "1.5.1",
9
+ "version": "1.5.2",
10
10
  "license": "MIT",
11
11
  "dependencies": {
12
12
  "@opencode-ai/plugin": ">=1.14.0",
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "pantheon-tui",
3
3
  "type": "module",
4
- "version": "1.5.1",
4
+ "version": "1.5.2",
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": {