diff --git a/packages/extension/opencode.lock.json b/packages/extension/opencode.lock.json index 2346f4cf..02808283 100644 --- a/packages/extension/opencode.lock.json +++ b/packages/extension/opencode.lock.json @@ -1,19 +1,21 @@ { "version": "1.18.29", "source": "release", - "ref": "16747470f976aca3d362ad730bcd3fe82ecc2c9a", + "ref": "2144c84418663876456129f3dbaead164de5ef07", "platforms": { "darwin-arm64": { "asset": "opencode-darwin-arm64.zip", - "sha256": "fe764f7f360c584a83e18dd5f23fb1a6b2725f5ee8854b0252fe558f7798e946" + "sha256": "684972c5cc5c801111256eadf1390cd9f7d58ac560b1b8397a0922964e262345" }, "linux-arm64": { "asset": "opencode-linux-arm64.tar.gz", - "sha256": "70baf769395ca4e7a68924026530c390eace194f3b7e4919d4efcb2aa2eed3c0" + "sha256": "a666bd008966a6819507dfdb6cc54f7738f0b962b925dd3a355312e8acd2642c" }, "linux-x64": { "asset": "opencode-linux-x64.tar.gz", - "sha256": "ea800b7ff56226b70952126c9fc1e2517ca4c4b5682fd9d3f9e87449697a1194" + "sha256": "6511e18db3495ba6cf2709237ba4ba3a2f6ef34e237b93543a3473e20ba42e84" } - } + }, + "tag": "v1.18.29-amicode.24", + "repo": "harmoniqs/opencode" } diff --git a/packages/extension/package.json b/packages/extension/package.json index b035a7ad..bbccd518 100644 --- a/packages/extension/package.json +++ b/packages/extension/package.json @@ -2,7 +2,7 @@ "name": "amicode", "displayName": "Amicode", "description": "Open autonomous research in VS Code \u2014 vaults, fleet, and live solves for quantum control and physical intelligence.", - "version": "0.3.2", + "version": "0.3.4", "publisher": "harmoniqs", "license": "Apache-2.0", "icon": "media/icon.png", diff --git a/packages/extension/scripts/opencode_probe.mjs b/packages/extension/scripts/opencode_probe.mjs index 459379c4..74003492 100644 --- a/packages/extension/scripts/opencode_probe.mjs +++ b/packages/extension/scripts/opencode_probe.mjs @@ -52,8 +52,14 @@ export async function bootOpencodeAndProbe({ bin = vendoredOpencodeBin(), timeou ); const port = await freePort(); + const password = "amicode-probe-password"; + const headers = { Authorization: `Basic ${Buffer.from(`opencode:${password}`).toString("base64")}` }; let log = ""; - const child = spawn(bin, ["serve", "--port", String(port)], { cwd: proj, stdio: ["ignore", "pipe", "pipe"] }); + const child = spawn(bin, ["serve", "--port", String(port)], { + cwd: proj, + env: { ...process.env, OPENCODE_SERVER_PASSWORD: password }, + stdio: ["ignore", "pipe", "pipe"], + }); const onData = (d) => { log += d; }; @@ -78,7 +84,7 @@ export async function bootOpencodeAndProbe({ bin = vendoredOpencodeBin(), timeou let up = false; while (Date.now() < deadline && !up) { try { - const r = await fetch(`http://127.0.0.1:${port}/`, { signal: AbortSignal.timeout(500) }); + const r = await fetch(`http://127.0.0.1:${port}/`, { headers, signal: AbortSignal.timeout(500) }); if (r.status < 500) up = true; } catch { /* not up yet */ @@ -92,7 +98,7 @@ export async function bootOpencodeAndProbe({ bin = vendoredOpencodeBin(), timeou eventCtype, eventOk = false; try { - const ev = await fetch(`http://127.0.0.1:${port}/event`, { signal: AbortSignal.timeout(10000) }); + const ev = await fetch(`http://127.0.0.1:${port}/event`, { headers, signal: AbortSignal.timeout(10000) }); eventStatus = ev.status; eventCtype = ev.headers.get("content-type") ?? ""; eventOk = ev.status === 200 && eventCtype.includes("text/event-stream"); @@ -105,7 +111,7 @@ export async function bootOpencodeAndProbe({ bin = vendoredOpencodeBin(), timeou // still resolving providers right after the port opens (→ a false "creds // unverifiable"); give it up to half the budget, capped at 15s. const signalTimeoutMs = Math.min(15000, Math.max(4000, Math.floor(timeoutMs / 2))); - const signal = await fetchProviderSignal(`http://127.0.0.1:${port}`, { timeoutMs: signalTimeoutMs }); + const signal = await fetchProviderSignal(`http://127.0.0.1:${port}`, { headers, timeoutMs: signalTimeoutMs }); return { up: true, eventOk, eventStatus, eventCtype, signal, log }; } finally { diff --git a/packages/extension/test/amicode_service_contract.test.ts b/packages/extension/test/amicode_service_contract.test.ts index 70ef1cf3..b296adfa 100644 --- a/packages/extension/test/amicode_service_contract.test.ts +++ b/packages/extension/test/amicode_service_contract.test.ts @@ -217,11 +217,17 @@ describe("amicode service — golden-fixture parity with the fork", () => { // recorded parity contract. expect(meta.fork.tag).toBe("v1.18.10-amicode.21"); expect(meta.entries.length).toBeGreaterThan(0); - // And the vendored pin must never regress to the fork lineage: the stock - // shape (no repo/tag — the fetch path's anomalyco/opencode default). + // A normal mainline build uses the stock shape (no repo/tag — the fetch + // path's anomalyco/opencode default). A release candidate may instead pin + // one fully specified fork release before its alpha is cut. const lock = JSON.parse(readFileSync(fileURLToPath(new URL("../opencode.lock.json", import.meta.url)), "utf8")); - expect(lock.repo).toBeUndefined(); - expect(lock.tag).toBeUndefined(); + if (!lock.repo) { + expect(lock.tag).toBeUndefined(); + return; + } + expect(lock.repo).toBe("harmoniqs/opencode"); + expect(lock.tag).toMatch(/^v1\.18\.29-amicode\.\d+$/); + expect(lock.ref).toMatch(/^[0-9a-f]{40}$/); }); for (const [i, entry] of meta.entries.entries()) { diff --git a/packages/extension/test/fetch_opencode.test.ts b/packages/extension/test/fetch_opencode.test.ts index 0a6a26bd..8ad3ba58 100644 --- a/packages/extension/test/fetch_opencode.test.ts +++ b/packages/extension/test/fetch_opencode.test.ts @@ -34,16 +34,16 @@ describe("loadManifest", () => { it("the COMMITTED manifest's platforms match the runtime allow-list exactly", () => { expect(Object.keys(loadManifest().platforms).sort()).toEqual([...SUPPORTED].sort()); }); - // The #823 M3 cutover shape, pinned: the vendored engine is STOCK canonical - // opencode — the lock names no fork lineage (no repo/tag), so releaseCoords - // resolves the anomalyco/opencode@v default and the download is a - // tokenless public fetch. A regression back to a fork pin would resurrect - // the retiring lineage through the default vendoring path. - it("the COMMITTED manifest is the stock canonical pin (#823: no fork repo/tag)", () => { + it("the committed manifest is either the stock pin or a fully specified release candidate", () => { const m = loadManifest(); // defaults to the real packages/extension root - expect(m.repo).toBeUndefined(); - expect(m.tag).toBeUndefined(); expect(m.version).toBe("1.18.29"); + if (!m.repo) { + expect(m.tag).toBeUndefined(); + return; + } + expect(m.repo).toBe("harmoniqs/opencode"); + expect(m.tag).toMatch(/^v1\.18\.29-amicode\.\d+$/); + expect(m.ref).toMatch(/^[0-9a-f]{40}$/); }); it("rejects missing version and short hashes", () => { expect(() => loadManifest(rootWith({ ...GOOD, version: "" }))).toThrow(/version/);