diff --git a/.github/workflows/build-desktop.yml b/.github/workflows/build-desktop.yml index c5b2d30cd..270d30bfc 100644 --- a/.github/workflows/build-desktop.yml +++ b/.github/workflows/build-desktop.yml @@ -60,23 +60,35 @@ jobs: - name: Download OpenCode sidecar shell: bash env: - OPENCODE_VERSION: 1.1.36 GITHUB_TOKEN: ${{ github.token }} run: | set -euo pipefail - version="${OPENCODE_VERSION}" - if [ -n "${GITHUB_TOKEN:-}" ]; then - latest=$(curl -fsSL \ - -H "Authorization: Bearer ${GITHUB_TOKEN}" \ - -H "X-GitHub-Api-Version: 2022-11-28" \ - https://api.github.com/repos/anomalyco/opencode/releases/latest \ - | sed -n 's/.*"tag_name": *"v\([^"]*\)".*/\1/p') + version="${OPENCODE_VERSION:-}" + if [ -z "$version" ] || [ "$version" = "latest" ]; then + latest="" + if [ -n "${GITHUB_TOKEN:-}" ]; then + latest=$(curl -fsSL \ + -H "Authorization: Bearer ${GITHUB_TOKEN}" \ + -H "X-GitHub-Api-Version: 2022-11-28" \ + https://api.github.com/repos/anomalyco/opencode/releases/latest \ + | sed -n 's/.*"tag_name": *"v\([^"]*\)".*/\1/p') + else + latest=$(curl -fsSL \ + -H "X-GitHub-Api-Version: 2022-11-28" \ + https://api.github.com/repos/anomalyco/opencode/releases/latest \ + | sed -n 's/.*"tag_name": *"v\([^"]*\)".*/\1/p') + fi if [ -n "$latest" ]; then version="$latest" fi fi + if [ -z "$version" ]; then + echo "Unable to resolve OpenCode version (set OPENCODE_VERSION to pin)." >&2 + exit 1 + fi + opencode_asset="opencode-linux-x64-baseline.tar.gz" url="https://github.com/anomalyco/opencode/releases/download/v${version}/${opencode_asset}" tmp_dir="$RUNNER_TEMP/opencode" diff --git a/.github/workflows/ci-tests.yml b/.github/workflows/ci-tests.yml index 4aa69e7d9..7a7f03c63 100644 --- a/.github/workflows/ci-tests.yml +++ b/.github/workflows/ci-tests.yml @@ -35,23 +35,36 @@ jobs: - name: Install OpenCode CLI shell: bash env: - OPENCODE_VERSION: 1.1.36 GITHUB_TOKEN: ${{ github.token }} run: | set -euo pipefail - version="${OPENCODE_VERSION}" - if [ -n "${GITHUB_TOKEN:-}" ]; then - latest=$(curl -fsSL \ - -H "Authorization: Bearer ${GITHUB_TOKEN}" \ - -H "X-GitHub-Api-Version: 2022-11-28" \ - https://api.github.com/repos/anomalyco/opencode/releases/latest \ - | sed -n 's/.*"tag_name": *"v\([^"]*\)".*/\1/p') + version="${OPENCODE_VERSION:-}" + + if [ -z "$version" ] || [ "$version" = "latest" ]; then + latest="" + if [ -n "${GITHUB_TOKEN:-}" ]; then + latest=$(curl -fsSL \ + -H "Authorization: Bearer ${GITHUB_TOKEN}" \ + -H "X-GitHub-Api-Version: 2022-11-28" \ + https://api.github.com/repos/anomalyco/opencode/releases/latest \ + | sed -n 's/.*"tag_name": *"v\([^"]*\)".*/\1/p') + else + latest=$(curl -fsSL \ + -H "X-GitHub-Api-Version: 2022-11-28" \ + https://api.github.com/repos/anomalyco/opencode/releases/latest \ + | sed -n 's/.*"tag_name": *"v\([^"]*\)".*/\1/p') + fi if [ -n "$latest" ]; then version="$latest" fi fi + if [ -z "$version" ]; then + echo "Unable to resolve OpenCode version (set OPENCODE_VERSION to pin)." >&2 + exit 1 + fi + curl -fsSL https://opencode.ai/install | bash -s -- --version "$version" echo "$HOME/.opencode/bin" >> "$GITHUB_PATH" diff --git a/.github/workflows/prerelease.yml b/.github/workflows/prerelease.yml index 70f1b7f9f..6e02e2916 100644 --- a/.github/workflows/prerelease.yml +++ b/.github/workflows/prerelease.yml @@ -157,7 +157,49 @@ jobs: id: opencode-version shell: bash run: | - node -e "const pkg=require('./packages/desktop/package.json'); if (!pkg.opencodeVersion) { throw new Error('opencodeVersion missing'); } console.log('version=' + pkg.opencodeVersion);" >> "$GITHUB_OUTPUT" + node <<'NODE' >> "$GITHUB_OUTPUT" + const fs = require('fs'); + + async function resolveLatest() { + const res = await fetch('https://api.github.com/repos/anomalyco/opencode/releases/latest', { + headers: { + 'Accept': 'application/vnd.github+json', + 'X-GitHub-Api-Version': '2022-11-28', + }, + }); + if (!res.ok) throw new Error(`Failed to resolve latest OpenCode version (HTTP ${res.status})`); + const data = await res.json(); + const tag = (typeof data.tag_name === 'string' ? data.tag_name : '').trim(); + let v = tag.startsWith('v') ? tag.slice(1) : tag; + v = v.trim(); + if (!v) throw new Error('OpenCode latest release tag missing'); + return v; + } + + async function main() { + const pkg = JSON.parse(fs.readFileSync('./packages/desktop/package.json', 'utf8')); + const configuredRaw = (process.env.OPENCODE_VERSION || pkg.opencodeVersion || '').toString().trim(); + if (configuredRaw && configuredRaw.toLowerCase() !== 'latest') { + const normalized = configuredRaw.startsWith('v') ? configuredRaw.slice(1) : configuredRaw; + const resolved = normalized.trim(); + if (process.env.GITHUB_ENV) { + fs.appendFileSync(process.env.GITHUB_ENV, `OPENCODE_VERSION=${resolved}\n`); + } + console.log('version=' + resolved); + return; + } + const latest = await resolveLatest(); + if (process.env.GITHUB_ENV) { + fs.appendFileSync(process.env.GITHUB_ENV, `OPENCODE_VERSION=${latest}\n`); + } + console.log('version=' + latest); + } + + main().catch((err) => { + console.error(err); + process.exit(1); + }); + NODE - name: Download OpenCode sidecar shell: bash diff --git a/.github/workflows/release-macos-aarch64.yml b/.github/workflows/release-macos-aarch64.yml index 92569d2f8..ec06fffa6 100644 --- a/.github/workflows/release-macos-aarch64.yml +++ b/.github/workflows/release-macos-aarch64.yml @@ -320,7 +320,49 @@ jobs: id: opencode-version shell: bash run: | - node -e "const pkg=require('./packages/desktop/package.json'); if (!pkg.opencodeVersion) { throw new Error('opencodeVersion missing'); } console.log('version=' + pkg.opencodeVersion);" >> "$GITHUB_OUTPUT" + node <<'NODE' >> "$GITHUB_OUTPUT" + const fs = require('fs'); + + async function resolveLatest() { + const res = await fetch('https://api.github.com/repos/anomalyco/opencode/releases/latest', { + headers: { + 'Accept': 'application/vnd.github+json', + 'X-GitHub-Api-Version': '2022-11-28', + }, + }); + if (!res.ok) throw new Error(`Failed to resolve latest OpenCode version (HTTP ${res.status})`); + const data = await res.json(); + const tag = (typeof data.tag_name === 'string' ? data.tag_name : '').trim(); + let v = tag.startsWith('v') ? tag.slice(1) : tag; + v = v.trim(); + if (!v) throw new Error('OpenCode latest release tag missing'); + return v; + } + + async function main() { + const pkg = JSON.parse(fs.readFileSync('./packages/desktop/package.json', 'utf8')); + const configuredRaw = (process.env.OPENCODE_VERSION || pkg.opencodeVersion || '').toString().trim(); + if (configuredRaw && configuredRaw.toLowerCase() !== 'latest') { + const normalized = configuredRaw.startsWith('v') ? configuredRaw.slice(1) : configuredRaw; + const resolved = normalized.trim(); + if (process.env.GITHUB_ENV) { + fs.appendFileSync(process.env.GITHUB_ENV, `OPENCODE_VERSION=${resolved}\n`); + } + console.log('version=' + resolved); + return; + } + const latest = await resolveLatest(); + if (process.env.GITHUB_ENV) { + fs.appendFileSync(process.env.GITHUB_ENV, `OPENCODE_VERSION=${latest}\n`); + } + console.log('version=' + latest); + } + + main().catch((err) => { + console.error(err); + process.exit(1); + }); + NODE - name: Download OpenCode sidecar shell: bash diff --git a/packages/desktop/package.json b/packages/desktop/package.json index 7820fec85..b398ab061 100644 --- a/packages/desktop/package.json +++ b/packages/desktop/package.json @@ -2,7 +2,6 @@ "name": "@different-ai/openwork", "private": true, "version": "0.11.22", - "opencodeVersion": "1.1.51", "owpenbotVersion": "0.1.19", "type": "module", "scripts": { diff --git a/packages/desktop/scripts/prepare-sidecar.mjs b/packages/desktop/scripts/prepare-sidecar.mjs index 0e1fd40a7..3326e74d3 100644 --- a/packages/desktop/scripts/prepare-sidecar.mjs +++ b/packages/desktop/scripts/prepare-sidecar.mjs @@ -41,6 +41,37 @@ const opencodeVersion = (() => { } return null; })(); + +const normalizeVersion = (value) => { + const raw = String(value ?? "").trim(); + if (!raw) return null; + if (raw.toLowerCase() === "latest") return null; + return raw.startsWith("v") ? raw.slice(1) : raw; +}; + +const fetchLatestOpencodeVersion = async () => { + // Use GitHub API (no auth required). If this fails, the caller can fall back + // to an explicitly configured version via OPENCODE_VERSION. + const controller = new AbortController(); + const timeout = setTimeout(() => controller.abort(), 10_000); + try { + const response = await fetch("https://api.github.com/repos/anomalyco/opencode/releases/latest", { + headers: { + Accept: "application/vnd.github+json", + "X-GitHub-Api-Version": "2022-11-28", + }, + signal: controller.signal, + }); + if (!response.ok) return null; + const data = await response.json(); + const tagName = typeof data?.tag_name === "string" ? data.tag_name : ""; + return normalizeVersion(tagName); + } catch { + return null; + } finally { + clearTimeout(timeout); + } +}; const opencodeAssetOverride = process.env.OPENCODE_ASSET?.trim() || null; const owpenbotVersion = (() => { if (process.env.OWPENBOT_VERSION?.trim()) return process.env.OWPENBOT_VERSION.trim(); @@ -303,13 +334,14 @@ if (existsSync(openworkServerBuildPath)) { } } -const normalizedOpencodeVersion = opencodeVersion?.startsWith("v") - ? opencodeVersion.slice(1) - : opencodeVersion; +let normalizedOpencodeVersion = normalizeVersion(opencodeVersion); +if (!normalizedOpencodeVersion) { + normalizedOpencodeVersion = await fetchLatestOpencodeVersion(); +} if (!normalizedOpencodeVersion) { console.error( - "OpenCode version is not configured. Set OPENCODE_VERSION or add opencodeVersion to packages/desktop/package.json." + "OpenCode version could not be resolved. Set OPENCODE_VERSION to pin a version, or ensure GitHub is reachable to use latest." ); process.exit(1); } diff --git a/packages/desktop/src-tauri/Cargo.lock b/packages/desktop/src-tauri/Cargo.lock index 9644c3a9d..3c3f8bf42 100644 --- a/packages/desktop/src-tauri/Cargo.lock +++ b/packages/desktop/src-tauri/Cargo.lock @@ -2731,7 +2731,7 @@ checksum = "7c87def4c32ab89d880effc9e097653c8da5d6ef28e6b539d313baaacfbafcbe" [[package]] name = "openwork" -version = "0.11.20" +version = "0.11.22" dependencies = [ "base64 0.22.1", "gethostname", diff --git a/packages/headless/package.json b/packages/headless/package.json index 73ff6ee72..4cbc0126f 100644 --- a/packages/headless/package.json +++ b/packages/headless/package.json @@ -3,7 +3,6 @@ "version": "0.11.22", "description": "Headless OpenWork host orchestrator for OpenCode + OpenWork server + Owpenbot", "type": "module", - "opencodeVersion": "1.1.51", "bin": { "openwrk": "dist/openwrk" }, diff --git a/packages/headless/src/cli.ts b/packages/headless/src/cli.ts index 0c32cf883..396f9a4d4 100644 --- a/packages/headless/src/cli.ts +++ b/packages/headless/src/cli.ts @@ -600,6 +600,8 @@ async function readVersionManifest(): Promise { const remoteManifestCache = new Map>(); +let latestOpencodeVersionTask: Promise | null = null; + function resolveSidecarTarget(): SidecarTarget | null { if (process.platform === "darwin") { if (process.arch === "arm64") return "darwin-arm64"; @@ -666,6 +668,34 @@ async function fetchRemoteManifest(url: string): Promise { + if (latestOpencodeVersionTask) return latestOpencodeVersionTask; + latestOpencodeVersionTask = (async () => { + const controller = new AbortController(); + const timeout = setTimeout(() => controller.abort(), 10_000); + try { + const response = await fetch("https://api.github.com/repos/anomalyco/opencode/releases/latest", { + headers: { + Accept: "application/vnd.github+json", + "X-GitHub-Api-Version": "2022-11-28", + }, + signal: controller.signal, + }); + if (!response.ok) return undefined; + const data = (await response.json()) as { tag_name?: unknown }; + const tag = typeof data.tag_name === "string" ? data.tag_name.trim() : ""; + if (!tag) return undefined; + const normalized = tag.startsWith("v") ? tag.slice(1) : tag; + return normalized || undefined; + } catch { + return undefined; + } finally { + clearTimeout(timeout); + } + })(); + return latestOpencodeVersionTask; +} + function resolveAssetUrl(baseUrl: string, asset?: string, url?: string): string | null { if (url && url.trim()) return url.trim(); if (asset && asset.trim()) return `${baseUrl.replace(/\/$/, "")}/${asset.trim()}`; @@ -922,9 +952,15 @@ async function resolveExpectedVersion( } if (name === "opencode") { const envVersion = process.env.OPENCODE_VERSION?.trim(); - if (envVersion) return envVersion.startsWith("v") ? envVersion.slice(1) : envVersion; + if (envVersion && envVersion.toLowerCase() !== "latest") { + return envVersion.startsWith("v") ? envVersion.slice(1) : envVersion; + } const pkgVersion = await readPackageField("opencodeVersion"); - if (pkgVersion) return pkgVersion.startsWith("v") ? pkgVersion.slice(1) : pkgVersion; + if (pkgVersion && pkgVersion.toLowerCase() !== "latest") { + return pkgVersion.startsWith("v") ? pkgVersion.slice(1) : pkgVersion; + } + const latest = await resolveLatestOpencodeVersion(); + if (latest) return latest; } } catch { // ignore @@ -1187,7 +1223,9 @@ async function resolveOpencodeBin(options: { if (opencodeDownloaded) { return { bin: opencodeDownloaded, source: "downloaded", expectedVersion }; } - throw new Error("opencode download failed. Check sidecar manifest or OPENCODE_VERSION."); + throw new Error( + "opencode download failed. Check sidecar manifest/network access, or set OPENCODE_VERSION to pin a version.", + ); } if (options.source === "external") { diff --git a/scripts/release/review.mjs b/scripts/release/review.mjs index b8ff1275b..02bd0b783 100644 --- a/scripts/release/review.mjs +++ b/scripts/release/review.mjs @@ -76,11 +76,17 @@ addCheck( versions.owpenbot && versions.owpenbotVersionPinned && versions.owpenbot === versions.owpenbotVersionPinned, `${versions.owpenbotVersionPinned ?? "?"} vs ${versions.owpenbot ?? "?"}`, ); -addCheck( - "OpenCode version matches (desktop/headless)", - versions.opencode.desktop && versions.opencode.headless && versions.opencode.desktop === versions.opencode.headless, - `${versions.opencode.desktop ?? "?"} vs ${versions.opencode.headless ?? "?"}`, -); +if (versions.opencode.desktop || versions.opencode.headless) { + addCheck( + "OpenCode version matches (desktop/headless)", + versions.opencode.desktop && versions.opencode.headless && versions.opencode.desktop === versions.opencode.headless, + `${versions.opencode.desktop ?? "?"} vs ${versions.opencode.headless ?? "?"}`, + ); +} else { + addWarning( + "OpenCode version is not pinned (packages/desktop + packages/headless). Sidecar bundling will default to the latest OpenCode release at build time.", + ); +} const openworkServerRange = versions.headlessOpenworkServerRange ?? ""; const openworkServerPinned = /^\d+\.\d+\.\d+/.test(openworkServerRange);