Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@

Your vaults, your fleet, your devices, your pulses — composed by conversation.

<sub>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</sub>
<sub>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</sub>

</div>

Expand All @@ -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.

Expand Down
110 changes: 110 additions & 0 deletions docs/migration-canonical-opencode.md
Original file line number Diff line number Diff line change
@@ -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:<pw>` → **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:<port>` + 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).
2 changes: 1 addition & 1 deletion packages/extension/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -34,4 +34,4 @@ Run **Amicode: Healthcheck** from the Command Palette any time to verify your se

---

<sub>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.</sub>
<sub>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.</sub>
4 changes: 2 additions & 2 deletions packages/extension/scripts/fetch_opencode.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -51,12 +51,12 @@ export function resolvePlatform(manifest, flag) {
return key;
}

/** Release coordinates: default = upstream sst/opencode at v<version>; a manifest
/** Release coordinates: default = upstream anomalyco/opencode at v<version>; 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
};
Expand Down
2 changes: 1 addition & 1 deletion packages/extension/scripts/opencode_dev.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
4 changes: 2 additions & 2 deletions packages/extension/skills/report-a-bug/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -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 <target> --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<base>-amicode.<n>` → `v<base>` 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<base>-amicode.<n>` → `v<base>` 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
Expand Down
4 changes: 2 additions & 2 deletions packages/extension/test/fetch_opencode.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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<version>, 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", () => {
Expand Down
Loading