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 +5 -0
- package/package.json +1 -1
- package/plugin.json +1 -1
- package/pyproject.toml +1 -1
- package/scripts/install/state.mjs +27 -2
- package/scripts/sync-tui.mjs +11 -5
- package/scripts/zenodo-artifact.mjs +77 -4
- package/src/plugins/tui/dist/tui.js +1 -1
- package/src/plugins/tui/package-lock.json +2 -2
- package/src/plugins/tui/package.json +1 -1
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
package/plugin.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pantheon",
|
|
3
3
|
"publisher": "ils15",
|
|
4
|
-
"version": "1.5.
|
|
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
|
@@ -108,7 +108,32 @@ export function upgradeStateV1toV2(state) {
|
|
|
108
108
|
}
|
|
109
109
|
|
|
110
110
|
/**
|
|
111
|
-
*
|
|
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
|
/**
|
package/scripts/sync-tui.mjs
CHANGED
|
@@ -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
|
-
|
|
210
|
-
|
|
211
|
-
|
|
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
|
|
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
|
|
17
|
-
|
|
18
|
-
|
|
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
|
}
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pantheon-tui",
|
|
3
|
-
"version": "1.5.
|
|
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.
|
|
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.
|
|
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": {
|