diff --git a/README.md b/README.md index dde34f24..96f043ef 100644 --- a/README.md +++ b/README.md @@ -11,7 +11,7 @@ Your vaults, your fleet, your devices, your pulses — composed by conversation. -A VS Code extension · built on [Piccolo.jl](https://github.com/harmoniqs/Piccolo.jl) · open [opencode](https://github.com/sst/opencode) chat harness · telaio research harness by subscription (arriving) · model-agnostic — Kimi K3, Muse Spark, open models +A VS Code extension · built on [Piccolo.jl](https://github.com/harmoniqs/Piccolo.jl) · open [opencode](https://github.com/anomalyco/opencode) chat harness · telaio research harness by subscription (arriving) · model-agnostic — Kimi K3, Muse Spark, open models @@ -21,7 +21,7 @@ Amicode is an **open autonomous research studio** that lives in your editor — Describe what you want in plain language — a gate, a state preparation, a calibration sweep — and Amicode designs the pulse, runs the solve, and shows you the result. Every run is captured, every pulse versioned for warm-start, and every session distilled into durable knowledge. The loop gets smarter as you use it. -Leveraging **Kimi K3** (Moonshot AI), **Muse Spark** (Meta), and other open models — on the open [opencode](https://github.com/sst/opencode) ecosystem (SST, vendored from `sst/opencode`). Model-agnostic by design: the loop, not the model, is the product. +Leveraging **Kimi K3** (Moonshot AI), **Muse Spark** (Meta), and other open models — on the open [opencode](https://github.com/anomalyco/opencode) ecosystem (vendored from `anomalyco/opencode`). Model-agnostic by design: the loop, not the model, is the product. We start with **quantum control** because it is the hardest physical system to prove the loop on. If the studio works here — arbitrary Hamiltonians, hard constraints, **hardware in the loop via Strumento.jl / QICK** — it generalizes to any physical system you can model. Bring your own Hamiltonian; the loop is the same. That's **physical intelligence**: not one device or platform, but a composable way to do experimental science. Plenty will sell you a closed “superintelligence” that never touches the hardware. We ship open, down to the RFSoC. diff --git a/docs/migration-canonical-opencode.md b/docs/migration-canonical-opencode.md new file mode 100644 index 00000000..0f107c45 --- /dev/null +++ b/docs/migration-canonical-opencode.md @@ -0,0 +1,110 @@ +# Migration manifest — ship canonical opencode, retire the fork binary + +Program of record: [harmoniqs/amicode#451](https://github.com/harmoniqs/amicode/issues/451) · +vault spec `amicode/specs/spec-20260820-044920-ship-canonical-opencode.md` · +plan `amicode/plans/plan-20260820-044920-ship-canonical-opencode.md`. + +Upstream (canonical) = **`anomalyco/opencode`** (post-rename; `sst/opencode` is a stale alias). +The fork = `harmoniqs/opencode`, branch `local/amicode`. Vocabulary per spec D4: never +user-facing "fork"; the shipped server is "canonical opencode", our ported surface is "the +Amicode service". + +This manifest is the M0 deliverable: feasibility-gate evidence, the full route × consumer +inventory, the app-surface extraction inventory, and the frozen e2e quarantine list. + +--- + +## 1. Feasibility gates — all five answered with evidence (2026-08-20) + +Evidence was gathered against **stock canonical `v1.18.19`** (upstream latest, released +2026-08-20) — a binary downloaded from the public release, sha256-verified, booted in an +isolated HOME. Not source spelunking: live probes. + +| Gate | Question | Verdict | Evidence | +|---|---|---|---| +| (a) | Plugin API custom server routes? | **No — M1 targets the extension-host HTTP service** | Upstream plugin surface is `Hooks` from `@opencode-ai/plugin` (tools + hooks only, loaded via `PluginLoader`); the server mounts routes exclusively through its internal Effect `HttpRouter`. The single-export legacy plugin contract we use is still supported (`getLegacyPlugins` throws on non-function exports — same constraint `amicode_tools.ts` documents). | +| (b) | Per-boot route-auth parity on a stock binary? | **Yes, natively** | Upstream `packages/opencode/src/server/auth.ts` reads `OPENCODE_SERVER_PASSWORD` / `OPENCODE_SERVER_USERNAME`. Live probe: no-auth `GET /` and `/doc` → **401**; with `-u opencode:` → **200** on `/`, `/doc`, `/config`. The extension's `server_auth.ts` flow (mint password → env → Basic header) works against stock canonical unmodified. | +| (c) | Verifiable release digests? | **Yes** | The GitHub releases API carries a `digest` field (`sha256:…`) per asset. Verified: downloaded `opencode-darwin-arm64.zip` hashes to exactly the API's `sha256:0026326b…`. All three required platform assets exist (`darwin-arm64.zip`, `linux-arm64.tar.gz`, `linux-x64.tar.gz`; sizes 46–60 MB). electron-builder `latest-*.yml` files exist as a secondary digest source. | +| (d) | Iframe origin model vs extension-host service? | **Workable — one-line CSP widening + per-frame origin tracking** | The deck webview CSP is built at `deck_panel.ts` as `frame-src ${opencodeUrl.origin}` — a single-origin allowlist; adding the service origin is additive. The shell's postMessage lane checks `e.origin` against the single server origin (`deck/shell.ts`) — M1/M2 must track origin per pane once the app bundle moves. No structural blocker. | +| (e) | Adopt-gate signals on stock canonical? | **Yes — all four** | (1) `--version` → `1.18.19`. (2) Boot liveness: stderr line `opencode server listening on http://127.0.0.1:` + HTTP 200 on `/doc` (authed). (3) **Plugin-load assert: a single-export legacy plugin with a module-level stamp write was imported at `serve` boot on the stock binary** — the stamp mechanism is our adopt-gate plugin check, no fork feature needed. (4) DB-compat probe rides the same boot against a DB copy (sqlite backup API), observable via (2). | + +Bonus finding: the `experimental.chat.system.transform` hook — the basis of the in-flight +`amicode_context` plugin (uncommitted WIP in `extension.ts` / `opencode_config.ts`) — **exists +in stock canonical** (`packages/plugin/src/index.ts`, consumed in `agent.ts` and +`session/llm/request.ts`, covered by `test/plugin/trigger.test.ts`). The WIP is portable; it +is NOT dead code and is left untouched. + +## 2. Route inventory — fork `httpapi/server.ts`, 31 routes × 18 modules + +All fork Amicode routes mount in one file: +`packages/opencode/src/server/routes/instance/httpapi/server.ts` (imports at lines 72–82; +`router.add` calls). Consumers: **ext** = VS Code extension (`packages/extension/src`), +**widgets** = home-dashboard widgets (`server/amicode/widgets-src`, same-origin `amico.fetch`), +**app** = fork app UI (`packages/ui/src/amicode/*`). + +| Module | Routes | Consumers | Destination | +|---|---|---|---| +| `vaults` | GET/POST `/amicode/vaults`, GET `/amicode/vault-files`, GET `/amicode/vault-file` | app (vault panel), ext | extension-host service | +| `warrants` | GET `/amicode/warrants`, POST `/amicode/approve` | app, ext | extension-host service | +| `file-resolve` | GET `/amicode/resolve-file` | app (file cards), ext | extension-host service | +| `problems` | GET `/amicode/problems`, `/amicode/problem`, `/amicode/run-status`, `/amicode/run-series`, `/amicode/run-cards` | app (entity rail, home cards, run window), ext (run inspector), widgets (now-solving, jump-back-in) | extension-host service | +| `widgets` + `widget-manifest` + `widget-runtime` + `widget-frame-html` | GET `/amicode/widgets`, `/amicode/widget-code`, `/amicode/widget-frame`; POST `/amicode/widget-fork` | app (widget grid), ext | extension-host service (serves widget frames + runtime) | +| `dashboard` | GET/POST `/amicode/dashboard` | widgets | extension-host service | +| `library` | GET/POST `/amicode/library` | widgets (library), ext | extension-host service | +| `profile` | GET/POST `/amicode/profile` | widgets (about-you, pulse-bank), ext | extension-host service | +| `connections` + `credentials` + `pasqal-secret` | GET `/amicode/connections`, `/amicode/connections/catalog`; POST `/amicode/connections/{auth,credential,revalidate,disconnect,remove,choose-project,add-custom}` | app (connections panel), ext (chat bridge) | extension-host service (credentials vault stays in the extension host — it already owns key storage) | +| `project` | POST `/amicode/project`, GET `/amicode/projects` | app (project picker) | extension-host service | +| `run-terminal` | (no dedicated amicode route — rides the PTY/httpapi surface) | ext (run controls) | extension-host service or canonical PTY API — decide in M1 | +| `solver-mode` | (no HTTP route — injected per-session via transform) | app (solver toggle) | already portable: rides the `amicode_context`-style plugin hook (confirmed on canonical) | +| `toml-lite` | (supporting lib — TOML parsing for the above) | — | ports with its consumers | + +Widget sources (7, served via `/amicode/widget-frame` + `widget-code`): `about-you`, +`jump-back-in`, `library`, `meet-amico`, `now-solving`, `pulse-bank`, `showcase` — all become +extension-service assets in M1. + +## 3. App-surface extraction inventory (M2) + +- **`packages/ui/src/amicode/*`** in the fork — the complete Amicode UI component set: + entity-rail, home-cards, connections (+test), widget-grid, widget-frame, widget-preview, + widget-allowlist (+schema/bridge tests), run-series, run-window, problem, card, + wave-geometry, and more. Extraction = move this directory into the amicode repo's app + bundle + adapt imports to canonical's public client SDK (`@opencode-ai/sdk`). +- **Patch-stack deltas** (per `AMICODE-PATCHES.md`, the authoritative log): branding/fonts/ + accents, KaTeX macros, AmicoSpinner sites, titlebar inline tab strip, ask-card/entity-rail + card dispatch, prompt-agnostic cassette matcher, markdown polish (~75 lines), the 72 + amicode-era i18n keys (move into our bundle — ends the upstream parity-test fight), + deletions (side panel, `debug-bar.tsx`), `OPENCODE_CHANNEL=dev` build gate. +- **Serving**: the app bundle is served for the deck's iframes by the extension-host service + (M1), composing upstream app components; CI pins it against canonical releases. + +## 4. Frozen e2e quarantine list (frozen here — M0; per spec, additions require replanning) + +1. `httpapi-v2-pty` — "serves location-wrapped PTY routes", ~1-in-3 timeout on clean upstream. +2. `project-picker-recent-search` — drives upstream's home-projects surface the fork doesn't + render (per AMICODE-PATCHES.md 2026-08-04 entry). + +Known-red-on-fork suites are NOT quarantined — they are the ported surfaces and get the +golden-fixture treatment per the spec (contract tests judged against fixtures recorded from +the running fork server): pasqal connections (8 reds), amicode widgets (15 reds). + +## 5. Touch-up revision (spec amendment, honest record) + +The spec's M0 item "remove the dead `amicode_context.ts` config reference" is **void**: the +reference is live WIP (the `experimental.chat.system.transform` hook plugin), the hook is +confirmed present in stock canonical, and the uncommitted `extension.ts`/`opencode_config.ts` +changes belong to it. Nothing removed. The one machine-level wrinkle — the server-bundle +config references `amicode_context.ts` while no installed VSIX ships it yet — resolves itself +when that WIP lands; not touched here. + +The stale-alias touch-up lands in the same branch: `sst/opencode` → `anomalyco/opencode` in +the vendoring script defaults (`fetch_opencode.mjs`, `opencode_dev.mjs`), the test that pins +those defaults, the README links, and the report-a-bug skill's upstream-check repo identity. + +## 6. Open items for M1 (carried, not blockers) + +- Per-pane origin tracking in the deck shell once panes can come from two origins. +- Auth story for the extension-host service: mint a per-boot token alongside the canonical + password (the service binds localhost; origin checks in the shell cover the iframe lane, but + the HTTP surface wants its own 401 path). +- Port allocation + lifecycle (server-manager owns it, like the canonical server today). +- `run-terminal` destination decision (service vs canonical PTY API). diff --git a/packages/extension/README.md b/packages/extension/README.md index f779e7fd..009f7030 100644 --- a/packages/extension/README.md +++ b/packages/extension/README.md @@ -34,4 +34,4 @@ Run **Amicode: Healthcheck** from the Command Palette any time to verify your se --- -A VS Code extension · built on [Piccolo.jl](https://github.com/harmoniqs/Piccolo.jl) · chat harness vendored from [opencode](https://github.com/sst/opencode) (MIT). Pre-release software. +A VS Code extension · built on [Piccolo.jl](https://github.com/harmoniqs/Piccolo.jl) · chat harness vendored from [opencode](https://github.com/anomalyco/opencode) (MIT). Pre-release software. diff --git a/packages/extension/scripts/fetch_opencode.mjs b/packages/extension/scripts/fetch_opencode.mjs index a4735f2b..b51df2a3 100644 --- a/packages/extension/scripts/fetch_opencode.mjs +++ b/packages/extension/scripts/fetch_opencode.mjs @@ -51,12 +51,12 @@ export function resolvePlatform(manifest, flag) { return key; } -/** Release coordinates: default = upstream sst/opencode at v; a manifest +/** Release coordinates: default = upstream anomalyco/opencode at v; a manifest * with `repo`/`tag` set points at our fork's release instead (harmoniqs/opencode, * private — downloads go through the authenticated `gh` path in that case). */ export function releaseCoords(manifest) { return { - repo: manifest.repo ?? "sst/opencode", + repo: manifest.repo ?? "anomalyco/opencode", tag: manifest.tag ?? `v${manifest.version}`, private: manifest.repo != null, // our mirror is private; upstream is not }; diff --git a/packages/extension/scripts/opencode_dev.mjs b/packages/extension/scripts/opencode_dev.mjs index 2109ec0d..3d9422f5 100755 --- a/packages/extension/scripts/opencode_dev.mjs +++ b/packages/extension/scripts/opencode_dev.mjs @@ -114,7 +114,7 @@ export function pinFromRelease({ root = PKG_ROOT, tag, ref, download = ghDownloa if (!tag) throw new Error("pin: a release tag is required (e.g. pnpm opencode:pin v1.17.3-amicode.5)"); const lockPath = join(root, "opencode.lock.json"); const m = JSON.parse(readFileSync(lockPath, "utf8")); - const repo = m.repo ?? "sst/opencode"; + const repo = m.repo ?? "anomalyco/opencode"; const shas = {}; for (const [key, p] of Object.entries(m.platforms ?? {})) { const bytes = download(repo, tag, p.asset); diff --git a/packages/extension/skills/report-a-bug/SKILL.md b/packages/extension/skills/report-a-bug/SKILL.md index 9f5db664..44caadfb 100644 --- a/packages/extension/skills/report-a-bug/SKILL.md +++ b/packages/extension/skills/report-a-bug/SKILL.md @@ -59,9 +59,9 @@ Scrubbing is keyed to **content taint, not destination repo** — even a misrout Both are read-only searches with **scrubbed terms only**, run before drafting so their results feed the footer. Silent on no match. If `gh` is unavailable or unauthenticated, skip both checks silently — the browser fallback in step 7 still files, and the footer records no upstream claim. - **Dedup.** Search the target repo's open issues (`gh search issues --repo --state open`). On a likely-match open issue, offer **comment-on-existing vs file-anyway** — the only conditional prompt besides the gates. -- **Upstream check (fork-vendored surfaces only — today, the vendored engine).** The pin makes "does the fix already exist upstream?" mechanically decidable: parse the release tag's upstream base (`v-amicode.` → `v` of `sst/opencode`), then search upstream issues **and** PRs with scrubbed terms: +- **Upstream check (fork-vendored surfaces only — today, the vendored engine).** The pin makes "does the fix already exist upstream?" mechanically decidable: parse the release tag's upstream base (`v-amicode.` → `v` of `anomalyco/opencode`), then search upstream issues **and** PRs with scrubbed terms: - **A matching merged fix** (merged PR / closed-as-fixed issue) at a release newer than the vendored base → offer an **upstream-bump chore issue instead of the `BUG:` filing** (merge the newer upstream; never re-implement what upstream already fixed). - - **A matching open upstream issue** → file the intake bug normally, footer `upstream: sst/opencode#N (open)` — maturation watches rather than implements. + - **A matching open upstream issue** → file the intake bug normally, footer `upstream: anomalyco/opencode#N (open)` — maturation watches rather than implements. - **No trace** → footer `upstream: none found`. ## 5. Compose the intake issue diff --git a/packages/extension/test/fetch_opencode.test.ts b/packages/extension/test/fetch_opencode.test.ts index 73f5516c..a4360d4d 100644 --- a/packages/extension/test/fetch_opencode.test.ts +++ b/packages/extension/test/fetch_opencode.test.ts @@ -194,9 +194,9 @@ describe("releaseCoords — fork-mirror pinning", async () => { const platforms = { "linux-x64": { asset: "opencode-linux-x64.tar.gz", sha256: "a".repeat(64) } }; it("defaults to upstream at v, public", () => { const m = { version: "1.17.3", platforms }; - expect(releaseCoords(m)).toEqual({ repo: "sst/opencode", tag: "v1.17.3", private: false }); + expect(releaseCoords(m)).toEqual({ repo: "anomalyco/opencode", tag: "v1.17.3", private: false }); expect(assetUrl(m, "linux-x64")).toBe( - "https://github.com/sst/opencode/releases/download/v1.17.3/opencode-linux-x64.tar.gz", + "https://github.com/anomalyco/opencode/releases/download/v1.17.3/opencode-linux-x64.tar.gz", ); }); it("repo+tag repoint to the private mirror", () => {