nandi/oripublic Fork 0
4166f8f
Commits
Clone
git clone https://git.rickub.com/nandi/ori.git
git clone ssh://git@rickub.com/nandi/ori.git

Host key fingerprint (ed25519): SHA256:iycHnxEyq0Q7uyVpB7JlznP0G7JrTPXLYRcAU5CSLhc — verify it before your first connect.

forked from bots-garden/ori

📝 Update project memory after the adapter switch (summary, history, handoff)

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
k33g committed 2026-09-18T10:06:30+02:00 Browse files
4166f8f parent: 5d476ff
added .memory/handoffs/2026-09-18-claude-agent-acp-adapter.md +27 -0
new file mode 100644
@@ -0,0 +1,27 @@
1+# Handoff — 2026-09-18 — ACP adapter switched to @agentclientprotocol/claude-agent-acp
2+
3+## State
4+
5+Done and merged into `main` (`5d476ff` + this memory commit): backend default, template Dockerfile, kit, docs EN+FR, launcher, quality gate PASS (run #18). Published: `k33g/ori:0.0.2` (Docker Hub, multi-arch, pushed by the user) and `docker.io/k33g/ori-kit:latest` (pushed by this session, `claude-agent-acp`). The user confirmed a real prompt works in sandbox `hello-world` (template 0.0.2 + published kit, port 5555 dual-stack).
6+
7+## In flight
8+
9+- Sandbox `hello-world` was created with the **old** kit: its baked startup script still says `claude-code-acp`; ori was started by hand. On restart it dies again → `sbx rm hello-world`, recreate with the published kit and `-p 5555:8888/tcp`. Sandbox `ori-acp` (local image `k33g/ori:0.0.2-local`, port 7777) and that template can be removed.
10+- amd64 `adapter` stage: verified in a `node:24-bookworm` container, not inspected inside the pushed amd64 image (the background `buildx --target adapter --platform linux/amd64` was still running when the session moved on).
11+- `scripts/Launch Ori.app` rebuilt from the updated script; still never run end to end.
12+
13+## Next steps
14+
15+1. `sbx template rm k33g/ori:0.0.2-local`, `sbx rm ori-acp` (housekeeping).
16+2. Ticket for an automated real-prompt smoke test (see `summary.md` "Not yet established").
17+3. `docs/.assets/ori-architecture.drawio.svg` / `.json` (talk assets) still say `claude-code-acp` — cosmetic, not updated.
18+
19+## Watch out for
20+
21+- `npm install --cpu=amd64` installs no native SDK and fails silently; use `x64`. The Dockerfile asserts the package directory exists.
22+- Image and kit must be published together: the kit's `--agent-cmd` must name the binary the image ships.
23+- `-p H:8888` = IPv4 only; use `/tcp`. Chrome blocks 6665–6669.
24+- qlty on this Mac: delete dangling `.qlty/{logs,out,plugin_cachedir,results}` symlinks created inside a sandbox; golangci-lint must be a build with Go ≥ 1.26 (pinned 2.13.2). qlty leaves a stray `.shellcheckrc` copy at the repo root; safe to delete (identical to `.qlty/configs/.shellcheckrc`).
25+- `scripts/launch-ori.applescript` came back UTF-16 after a Script Editor save; keep it UTF-8 (`iconv -f UTF-16 -t UTF-8`).
26+- Node 25: run vitest through `npm run test` (the script carries the webstorage flag); a bare `npx vitest run` fails 15 tests.
27+- A Python edit script that asserts on text must run under `&&` before any `git commit`: this session's first memory write failed on an anchor and the commit went ahead without it.
new file mode 100644
@@ -0,0 +1,27 @@
1+# Handoff — 2026-09-18 — ACP adapter switched to @agentclientprotocol/claude-agent-acp
2+
3+## State
4+
5+Done and merged into `main` (`5d476ff` + this memory commit): backend default, template Dockerfile, kit, docs EN+FR, launcher, quality gate PASS (run #18). Published: `k33g/ori:0.0.2` (Docker Hub, multi-arch, pushed by the user) and `docker.io/k33g/ori-kit:latest` (pushed by this session, `claude-agent-acp`). The user confirmed a real prompt works in sandbox `hello-world` (template 0.0.2 + published kit, port 5555 dual-stack).
6+
7+## In flight
8+
9+- Sandbox `hello-world` was created with the **old** kit: its baked startup script still says `claude-code-acp`; ori was started by hand. On restart it dies again → `sbx rm hello-world`, recreate with the published kit and `-p 5555:8888/tcp`. Sandbox `ori-acp` (local image `k33g/ori:0.0.2-local`, port 7777) and that template can be removed.
10+- amd64 `adapter` stage: verified in a `node:24-bookworm` container, not inspected inside the pushed amd64 image (the background `buildx --target adapter --platform linux/amd64` was still running when the session moved on).
11+- `scripts/Launch Ori.app` rebuilt from the updated script; still never run end to end.
12+
13+## Next steps
14+
15+1. `sbx template rm k33g/ori:0.0.2-local`, `sbx rm ori-acp` (housekeeping).
16+2. Ticket for an automated real-prompt smoke test (see `summary.md` "Not yet established").
17+3. `docs/.assets/ori-architecture.drawio.svg` / `.json` (talk assets) still say `claude-code-acp` — cosmetic, not updated.
18+
19+## Watch out for
20+
21+- `npm install --cpu=amd64` installs no native SDK and fails silently; use `x64`. The Dockerfile asserts the package directory exists.
22+- Image and kit must be published together: the kit's `--agent-cmd` must name the binary the image ships.
23+- `-p H:8888` = IPv4 only; use `/tcp`. Chrome blocks 6665–6669.
24+- qlty on this Mac: delete dangling `.qlty/{logs,out,plugin_cachedir,results}` symlinks created inside a sandbox; golangci-lint must be a build with Go ≥ 1.26 (pinned 2.13.2). qlty leaves a stray `.shellcheckrc` copy at the repo root; safe to delete (identical to `.qlty/configs/.shellcheckrc`).
25+- `scripts/launch-ori.applescript` came back UTF-16 after a Script Editor save; keep it UTF-8 (`iconv -f UTF-16 -t UTF-8`).
26+- Node 25: run vitest through `npm run test` (the script carries the webstorage flag); a bare `npx vitest run` fails 15 tests.
27+- A Python edit script that asserts on text must run under `&&` before any `git commit`: this session's first memory write failed on an anchor and the commit went ahead without it.
modified .memory/history.md +8 -0
@@ -170,3 +170,11 @@
170170 - **Asked**: reminder of what to do to avoid this prompt failure in the sandbox (kit `docker.io/k33g/ori-kit:latest`, template `k33g/ori:0.0.1`).
171171 - **Diagnosis (read-only, verified)**: ori in sandbox `ori` runs with no `CLAUDE_CODE_EXECUTABLE`, so the Zed adapter 0.16.2 uses its bundled CLI 2.1.44 (SDK 0.2.44). The model comes from the host `~/.claude/settings.json` (`claude-fable-5-1[1m]`), which the API only serves to CLI ≥ 2.1.251. Yesterday's workaround (point the adapter at the template's system CLI) **no longer suffices**: the base image `docker/sandbox-templates:claude-code` (built 2026-08-26) ships CLI 2.1.246. Sandbox egress reaches the Claude Code release bucket (latest = 2.1.276) and npm, so `claude update` inside is feasible. `@agentclientprotocol/claude-agent-acp@0.79.0` (bin `claude-agent-acp`) depends on `@anthropic-ai/claude-agent-sdk@0.3.274`, whose `manifest.json` says CLI **2.1.274**, delivered through per-platform optional deps (`…-sdk-linux-arm64`, `…-linux-x64`, …) — no `cli.js` any more.
172172 - **Nothing changed**: options given to the user (immediate: `claude update` in the sandbox + restart ori with `CLAUDE_CODE_EXECUTABLE`; permanent: switch the template to the new adapter, or bake a recent CLI + the env var into template/kit). Their call.
173+
174+## 2026-09-18 — Switch the ACP adapter to @agentclientprotocol/claude-agent-acp (template 0.0.2), Node 25 test fix, MIME pin
175+
176+- **Goal**: option 1 of the "Claude Code 2.1.44 too old" diagnosis, approved by the user: stop depending on the Zed adapter's embedded CLI; rebuild template 0.0.2; republish the kit. Worked on `feature/copy-paste-functionality` (user's choice), commit `5d476ff`, fast-forwarded into `main`; this memory update is a follow-up commit on `main`.
177+- **Changes**: `internal/config/config.go` `DefaultAgentCommand``npx -y @agentclientprotocol/claude-agent-acp` (+ `TestDefaultAgentCommandIsClaudeAgentACP`); doc comments in `internal/agent/agent.go`, `cmd/ori/main.go`. `template/Dockerfile`: `adapter` stage installs the new adapter with `npm install --os=linux --cpu=$npm_cpu`, Docker's `amd64` mapped to npm's `x64` (with `--cpu=amd64` npm silently installs **no** native SDK), and asserts `claude-agent-sdk-linux-<cpu>` exists; CMD `--agent-cmd claude-agent-acp`. `kits/ori/spec.yaml`: startup + agentInstructions → `claude-agent-acp`, template 0.0.2. `internal/files/raw.go`: `textTypes` pins `.md`/`.markdown``text/markdown; charset=utf-8` (Go's mime table comes from the host; macOS leaves `text/markdown` commented out, so `TestRawAcceptsAbsolutePathsLikeTheOtherEndpoints` failed on the Mac) + 2 table cases. `ui/package.json` test script gains `NODE_OPTIONS=--no-experimental-webstorage`. `ui/src/components/Markdown.tsx`: `React.ReactElement<{ children?: unknown }>` cast — the copy-paste commit did not compile (TS2339 ×3) and blocked `docker build`. `ui/src/components/ToolCallCard.tsx`: copy text extracted into pure `contentText` / `toolContentText` (+5 tests), clearing two function-complexity smells. `.qlty/qlty.toml`: golangci-lint pinned 2.13.2. Docs EN+FR (architecture explanation, how-to run-with-claude-code / run-in-a-sandbox / run-the-tests, reference cli), `docs/diagrams/packages.drawio`, `docs/source-code-analysis.adoc`, kit README, root README, quickstart, `scripts/launch-ori.applescript` (0.0.2, `/tcp`, converted back from UTF-16) + its READMEs.
178+- **Decisions**: (1) new adapter over "bake `claude update` + `CLAUDE_CODE_EXECUTABLE`": the SDK ships a current CLI per platform, no version chasing. (2) `adapter` stage stays `--platform=$BUILDPLATFORM` (native npm) and targets the CPU explicitly, instead of npm under qemu. (3) Backend default changed too (user: "backend + template") so `make run` gets the same fix. (4) Pre-existing failures fixed along the way with the user's explicit OK: MIME test, Node 25, Markdown.tsx types, ToolCallCard complexity. (5) Left untracked as the user's artefacts/memos: `kits/.backup/`, `demo/` (the three prompt-generated stories the user moved there — their root copies are deleted in `5d476ff`), `kits/ori/publish.kit.md`, `template/publish.template.md`.
179+- **Verification**: `go vet ./... && go test ./...` green; ui `tsc -b` + 134 vitest green; `docker build --check` clean; arm64 image built locally — `claude-agent-acp` resolves for root and via `su … agent`, native SDK `linux-arm64` with the 230 MB CLI, manifest 2.1.274, ACP `initialize` answered by adapter 0.79.0, Zed adapter gone; amd64 install verified in a `node:24` container (`-linux-x64` present). Test sandbox `ori-acp` (image via `sbx template load`) served ori with CLI 2.1.274; the user pushed `k33g/ori:0.0.2` (multi-arch) and confirmed a real prompt in sandbox `hello-world` after the kit re-push. **Quality**: run #18 PASS (0/0/0, complex 532) after repairing qlty on the Mac (dangling `.qlty/*` symlinks from the sandbox; golangci-lint too old for Go 1.26).
180+- **Docs**: as listed, EN + FR in sync. `diataxis-doc` skill not invoked: edits to existing pages, done directly.
@@ -170,3 +170,11 @@
170 - **Asked**: reminder of what to do to avoid this prompt failure in the sandbox (kit `docker.io/k33g/ori-kit:latest`, template `k33g/ori:0.0.1`).170 - **Asked**: reminder of what to do to avoid this prompt failure in the sandbox (kit `docker.io/k33g/ori-kit:latest`, template `k33g/ori:0.0.1`).
171 - **Diagnosis (read-only, verified)**: ori in sandbox `ori` runs with no `CLAUDE_CODE_EXECUTABLE`, so the Zed adapter 0.16.2 uses its bundled CLI 2.1.44 (SDK 0.2.44). The model comes from the host `~/.claude/settings.json` (`claude-fable-5-1[1m]`), which the API only serves to CLI ≥ 2.1.251. Yesterday's workaround (point the adapter at the template's system CLI) **no longer suffices**: the base image `docker/sandbox-templates:claude-code` (built 2026-08-26) ships CLI 2.1.246. Sandbox egress reaches the Claude Code release bucket (latest = 2.1.276) and npm, so `claude update` inside is feasible. `@agentclientprotocol/claude-agent-acp@0.79.0` (bin `claude-agent-acp`) depends on `@anthropic-ai/claude-agent-sdk@0.3.274`, whose `manifest.json` says CLI **2.1.274**, delivered through per-platform optional deps (`…-sdk-linux-arm64`, `…-linux-x64`, …) — no `cli.js` any more.171 - **Diagnosis (read-only, verified)**: ori in sandbox `ori` runs with no `CLAUDE_CODE_EXECUTABLE`, so the Zed adapter 0.16.2 uses its bundled CLI 2.1.44 (SDK 0.2.44). The model comes from the host `~/.claude/settings.json` (`claude-fable-5-1[1m]`), which the API only serves to CLI ≥ 2.1.251. Yesterday's workaround (point the adapter at the template's system CLI) **no longer suffices**: the base image `docker/sandbox-templates:claude-code` (built 2026-08-26) ships CLI 2.1.246. Sandbox egress reaches the Claude Code release bucket (latest = 2.1.276) and npm, so `claude update` inside is feasible. `@agentclientprotocol/claude-agent-acp@0.79.0` (bin `claude-agent-acp`) depends on `@anthropic-ai/claude-agent-sdk@0.3.274`, whose `manifest.json` says CLI **2.1.274**, delivered through per-platform optional deps (`…-sdk-linux-arm64`, `…-linux-x64`, …) — no `cli.js` any more.
172 - **Nothing changed**: options given to the user (immediate: `claude update` in the sandbox + restart ori with `CLAUDE_CODE_EXECUTABLE`; permanent: switch the template to the new adapter, or bake a recent CLI + the env var into template/kit). Their call.172 - **Nothing changed**: options given to the user (immediate: `claude update` in the sandbox + restart ori with `CLAUDE_CODE_EXECUTABLE`; permanent: switch the template to the new adapter, or bake a recent CLI + the env var into template/kit). Their call.
173+
174+## 2026-09-18 — Switch the ACP adapter to @agentclientprotocol/claude-agent-acp (template 0.0.2), Node 25 test fix, MIME pin
175+
176+- **Goal**: option 1 of the "Claude Code 2.1.44 too old" diagnosis, approved by the user: stop depending on the Zed adapter's embedded CLI; rebuild template 0.0.2; republish the kit. Worked on `feature/copy-paste-functionality` (user's choice), commit `5d476ff`, fast-forwarded into `main`; this memory update is a follow-up commit on `main`.
177+- **Changes**: `internal/config/config.go` `DefaultAgentCommand``npx -y @agentclientprotocol/claude-agent-acp` (+ `TestDefaultAgentCommandIsClaudeAgentACP`); doc comments in `internal/agent/agent.go`, `cmd/ori/main.go`. `template/Dockerfile`: `adapter` stage installs the new adapter with `npm install --os=linux --cpu=$npm_cpu`, Docker's `amd64` mapped to npm's `x64` (with `--cpu=amd64` npm silently installs **no** native SDK), and asserts `claude-agent-sdk-linux-<cpu>` exists; CMD `--agent-cmd claude-agent-acp`. `kits/ori/spec.yaml`: startup + agentInstructions → `claude-agent-acp`, template 0.0.2. `internal/files/raw.go`: `textTypes` pins `.md`/`.markdown``text/markdown; charset=utf-8` (Go's mime table comes from the host; macOS leaves `text/markdown` commented out, so `TestRawAcceptsAbsolutePathsLikeTheOtherEndpoints` failed on the Mac) + 2 table cases. `ui/package.json` test script gains `NODE_OPTIONS=--no-experimental-webstorage`. `ui/src/components/Markdown.tsx`: `React.ReactElement<{ children?: unknown }>` cast — the copy-paste commit did not compile (TS2339 ×3) and blocked `docker build`. `ui/src/components/ToolCallCard.tsx`: copy text extracted into pure `contentText` / `toolContentText` (+5 tests), clearing two function-complexity smells. `.qlty/qlty.toml`: golangci-lint pinned 2.13.2. Docs EN+FR (architecture explanation, how-to run-with-claude-code / run-in-a-sandbox / run-the-tests, reference cli), `docs/diagrams/packages.drawio`, `docs/source-code-analysis.adoc`, kit README, root README, quickstart, `scripts/launch-ori.applescript` (0.0.2, `/tcp`, converted back from UTF-16) + its READMEs.
178+- **Decisions**: (1) new adapter over "bake `claude update` + `CLAUDE_CODE_EXECUTABLE`": the SDK ships a current CLI per platform, no version chasing. (2) `adapter` stage stays `--platform=$BUILDPLATFORM` (native npm) and targets the CPU explicitly, instead of npm under qemu. (3) Backend default changed too (user: "backend + template") so `make run` gets the same fix. (4) Pre-existing failures fixed along the way with the user's explicit OK: MIME test, Node 25, Markdown.tsx types, ToolCallCard complexity. (5) Left untracked as the user's artefacts/memos: `kits/.backup/`, `demo/` (the three prompt-generated stories the user moved there — their root copies are deleted in `5d476ff`), `kits/ori/publish.kit.md`, `template/publish.template.md`.
179+- **Verification**: `go vet ./... && go test ./...` green; ui `tsc -b` + 134 vitest green; `docker build --check` clean; arm64 image built locally — `claude-agent-acp` resolves for root and via `su … agent`, native SDK `linux-arm64` with the 230 MB CLI, manifest 2.1.274, ACP `initialize` answered by adapter 0.79.0, Zed adapter gone; amd64 install verified in a `node:24` container (`-linux-x64` present). Test sandbox `ori-acp` (image via `sbx template load`) served ori with CLI 2.1.274; the user pushed `k33g/ori:0.0.2` (multi-arch) and confirmed a real prompt in sandbox `hello-world` after the kit re-push. **Quality**: run #18 PASS (0/0/0, complex 532) after repairing qlty on the Mac (dangling `.qlty/*` symlinks from the sandbox; golangci-lint too old for Go 1.26).
180+- **Docs**: as listed, EN + FR in sync. `diataxis-doc` skill not invoked: edits to existing pages, done directly.
modified .memory/summary.md +10 -9
@@ -2,7 +2,7 @@
22
33 ## What it is
44
5-Ori is a web client for ACP (Agent Client Protocol, https://agentclientprotocol.com) code agents: a Go backend serves an embedded React SPA and connects over ACP (JSON-RPC on stdio) to an agent subprocess — Claude Code via the `@zed-industries/claude-code-acp` adapter by default. The browser gets a Zed-style agent panel: streamed markdown answers, collapsible thoughts, tool call cards (with statuses, locations and diffs), the agent's plan, and permission prompts answered from the UI. Implements ticket `.tickets/issues/0004-specifications.yaml`.
5+Ori is a web client for ACP (Agent Client Protocol, https://agentclientprotocol.com) code agents: a Go backend serves an embedded React SPA and connects over ACP (JSON-RPC on stdio) to an agent subprocess — Claude Code via the `@agentclientprotocol/claude-agent-acp` adapter by default (since 2026-09-18). The browser gets a Zed-style agent panel: streamed markdown answers, collapsible thoughts, tool call cards (with statuses, locations and diffs), the agent's plan, and permission prompts answered from the UI. Implements ticket `.tickets/issues/0004-specifications.yaml`.
66
77 ## Architecture
88
@@ -25,7 +25,7 @@ browser ⇆ WebSocket /ws ⇆ Go backend ⇆ stdio (ACP) ⇆ agent subprocess
2525
2626 ## Key decisions in force
2727
28-- Claude Code is reached through the official Zed adapter (`npx -y @zed-industries/claude-code-acp`) — the `claude` CLI (v2.1.274) has no native ACP mode; verified.
28+- Claude Code is reached through `@agentclientprotocol/claude-agent-acp` (default `npx -y @agentclientprotocol/claude-agent-acp`, since 2026-09-18; `internal/config.DefaultAgentCommand`, pinned by `TestDefaultAgentCommandIsClaudeAgentACP`). It bundles its own Claude Code CLI via `@anthropic-ai/claude-agent-sdk` 0.3.274 → CLI 2.1.274 delivered as per-platform optional deps (`…-sdk-linux-arm64`, `-linux-x64`, …; no `cli.js` any more). Replaced Zed's `@zed-industries/claude-code-acp` 0.16.2, whose embedded CLI 2.1.44 the API rejects for current models ("version 2.1.251 or newer is required"). The `claude` CLI itself has no native ACP mode; verified.
2929 - draw.io diagrams render online via `embed.diagrams.net` (no bundlable offline renderer exists: `viewer.min.js` is not on npm, `mxgraph` is archived and lacks draw.io's shapes, `drawio2svg` is GPL); future offline path = vendoring `viewer-static.min.js` (~3 MB, Apache-2.0). Documented in the architecture explanations.
3030 - ACP payloads cross the WebSocket verbatim (raw `SessionUpdate` / `RequestPermissionRequest`), keeping the front forward-compatible.
3131 - One agent session shared by all browsers in v1; multi-session is a planned evolution seam (`bridge.Prompter` interface, per-connection subscriptions).
@@ -36,22 +36,23 @@ browser ⇆ WebSocket /ws ⇆ Go backend ⇆ stdio (ACP) ⇆ agent subprocess
3636
3737 - `make deps` — npm install in `ui/`.
3838 - `make build` — SPA build then Go binaries (`bin/ori`, `bin/ori-mock-agent`). Order matters: the Go build embeds `ui/dist`.
39-- `make test` — `go test ./...` + `vitest run`. Also useful: `go test -race ./...`, `go test -short ./...` (skips e2e).
39+- `make test` — `go test ./...` + `vitest run` (the ui `test` script sets `NODE_OPTIONS=--no-experimental-webstorage`: Node 25's built-in `localStorage` lacks `clear()` and shadows jsdom's — 15 store tests fail without it; verified 2026-09-18 on Node 25.9). Also useful: `go test -race ./...`, `go test -short ./...` (skips e2e).
4040 - `make run` — with Claude Code adapter; `make run-mock` — with the demo agent (no Claude/network needed).
41-- `make template` — builds the `k33g/ori` sandbox template image (`template/Dockerfile`, FROM `docker/sandbox-templates:claude-code`); launch with `sbx run -d claude <project> --template k33g/ori:0.0.0 --kit <ori repo>/kits/ori --name ori -p 8888:8888` (detached — see the auto-stop trap below) (see `kits/ori/README.md` and docs how-to `run-in-a-sandbox`).
42-- `scripts/launch-ori.applescript` (2026-09-18) — macOS launcher: `sbx ls -q` → restart by name or create (`k33g/ori:0.0.1`, `./kits/ori`, `-p 5555:8888`), poll `http://localhost:5555/healthz` up to 90 s, then `open ori-desktop/build/bin/ori-desktop.app`. Run with `osascript`, or `osacompile` into `scripts/Launch Ori.app`; repo root derived from the script's own location. Compile-checked and handlers unit-tested; full end-to-end run not yet done.
41+- `make template` — builds the `k33g/ori` sandbox template image (`template/Dockerfile`, FROM `docker/sandbox-templates:claude-code`); launch with `sbx run -d claude <project> --template k33g/ori:0.0.2 --kit docker.io/k33g/ori-kit:latest --name ori -p 8888:8888/tcp` (detached — see the auto-stop trap below) (see `kits/ori/README.md` and docs how-to `run-in-a-sandbox`).
42+- `scripts/launch-ori.applescript` (2026-09-18) — macOS launcher: `sbx ls -q` → restart by name or create (`k33g/ori:0.0.2`, `./kits/ori`, `-p 5555:8888/tcp`), poll `http://localhost:5555/healthz` up to 90 s, then `open ori-desktop/build/bin/ori-desktop.app`. Run with `osascript`, or `osacompile` into `scripts/Launch Ori.app`; repo root derived from the script's own location. Compile-checked and handlers unit-tested; full end-to-end run not yet done.
4343 - Kit publishing (sbx v0.43.0, verified 2026-09-18): `sbx kit validate kits/ori`, `sbx kit pack kits/ori -o ori-kit.zip`, `sbx kit push kits/ori docker.io/k33g/ori-kit:<tag>` — directory argument, not `spec.yaml`; there is no `sbx kit package`. Auth: `sbx login` session, then `sbx secret set --registry`, then the Docker credential store.
4444 - **Kit packaging trap (verified 2026-09-18)**: `pack`/`push` decode `spec.yaml` raw (no `${{ kit.args }}` expansion) — an arg placeholder in an integer field (`ports[].container`) breaks them even though `validate`/`run` accept it. `ports[0].container` is therefore a literal 8888; the `port` arg only drives the startup command and agentInstructions.
45-- Published kit: `docker.io/k33g/ori-kit:latest` (pushed 2026-09-18, verified with `sbx kit inspect`); usable as `--kit docker.io/k33g/ori-kit:latest` instead of `--kit ./kits/ori`.
45+- Published artefacts (2026-09-18): template `k33g/ori:0.0.2` on Docker Hub (multi-arch amd64+arm64, pushed by the user with `./template/build.sh`, ships only `claude-agent-acp`); kit `docker.io/k33g/ori-kit:latest` re-pushed with `--agent-cmd claude-agent-acp` + template 0.0.2. **They must move together**: the first `hello-world` sandbox paired image 0.0.2 with the morning's kit (`claude-code-acp`) and ori died with "executable file not found".
46+- **Port publishing traps**: `-p H:8888` without protocol binds `tcp4` only — browsers resolving `localhost` to `::1` fail; always `-p H:8888/tcp` (dual-stack). Chrome refuses ports 6665–6669 (`ERR_UNSAFE_PORT`) whatever the server does. `sbx ports NAME` lists; `sbx ports ls` looks for a sandbox called `ls`.
4647 - **sbx auto-stop trap (verified in the sbx source + daemon log, 2026-09-17)**: sandboxd stops a sandbox 30 s (hard-coded `WithAutoStopDelay(30*time.Second)`, `sandboxd/pkg/server/backend_dockernext.go`) after its last CLI "sentinel" session closes; `sbx create`/`sbx exec`/`sbx run` all hold one for their own duration, browser traffic on a published port counts for nothing. Only a sandbox created with the spec field `detached: true` (CLI: `sbx run -d …`, absent from `sbx create`) is exempt; the field is create-time only (no PATCH), so an existing sandbox must be `sbx rm`'d and recreated with `sbx run -d`. No settings/env knob for the delay.
47-- Quality: `python3 ~/.claude/skills/quality/scripts/quality_report.py --workspace .` (gate PASS, run #16, 2026-09-17 late evening: 0 errors, 0 warnings, 0 smells, complex 514 — covers the selectors/previews/desktop work, the resizable file tree and the light/dark theme).
48+- Quality: `python3 .claude/skills/quality/scripts/quality_report.py --workspace .` (the script lives in the repo, not `~/.claude`). Gate PASS, run #18, 2026-09-18: 0 errors, 0 warnings, 0 smells, complex 532 (covers the adapter switch, the MIME pin, the copy-paste feature). **Mac trap**: `.qlty/{logs,out,plugin_cachedir,results}` are symlinks qlty creates into its cache; ones created inside the sandbox point at `/home/agent/…` and dangle on the Mac, making every plugin invocation "FATAL" with no detail — delete the dangling links, qlty recreates them. `.qlty/qlty.toml` pins golangci-lint 2.13.2 (built with Go 1.27): qlty's default 1.61.0 and its latest-known 2.6.2 refuse this repo's `go 1.26.5`.
4849 - `docs/source-code-analysis.adoc` — AsciiDoc snapshot analysis of the codebase (2026-09-17), outside the bilingual Diátaxis set.
4950
5051 ## Environment facts (sandbox)
5152
5253 - `~/.qlty` is a dedicated 488MB volume, too small for qlty's tool cache; the cache was moved to `~/.qlty-cache` on the main disk with a symlink `~/.qlty/cache -> /home/agent/.qlty-cache`. If qlty reports "No space left on device", check this symlink survived. (It did NOT survive a sandbox recreation on 2026-09-17: the volume came back empty and the first run failed exactly that way; recreating the symlink fixed it.)
5354 - `~/.npm` is the same kind of 488MB dedicated volume and filled up when installing Monaco; the npm cache now lives at `~/.npm-big` via `NPM_CONFIG_CACHE` exported in `/etc/sandbox-persistent.sh`.
54-- Running ori inside this Claude-Code-driven sandbox requires `env -u CLAUDECODE ./bin/ori` (Claude refuses nested sessions). Also set `CLAUDE_CODE_EXECUTABLE=<current claude binary>`: the Zed adapter 0.16.2 bundles Claude Code 2.1.44, which rejects the `claude-fable-5-1[1m]` model from the host `~/.claude/settings.json`. **Since 2026-09-18 the API requires CLI ≥ 2.1.251 for that model, and the template's own CLI is 2.1.246 (base image built 2026-08-26)** — so the env var alone is not enough; run `claude update` in the sandbox first, or switch the template to `@agentclientprotocol/claude-agent-acp` (SDK 0.3.274 → CLI 2.1.274). Handoff `2026-09-18-claude-version-too-old.md`.
55+- Running ori inside this Claude-Code-driven sandbox requires `env -u CLAUDECODE ./bin/ori` (Claude refuses nested sessions). With the current template/kit (`claude-agent-acp`, bundled CLI 2.1.274) no `CLAUDE_CODE_EXECUTABLE` is needed. It remains the lever if something spawns the old Zed adapter (points it at another `claude` binary) — but the template's own CLI is 2.1.246 (base image 2026-08-26), below the 2.1.251 floor the API enforces for `claude-fable-5-1[1m]` (host `~/.claude/settings.json`), so `claude update` inside the sandbox would come first. Handoffs `2026-09-18-claude-version-too-old.md`, `2026-09-18-claude-agent-acp-adapter.md`.
5556 - npm blocks install scripts by default here; esbuild's postinstall was approved via `npm approve-scripts` (recorded in `ui/package.json` `allowScripts`).
5657
5758 ## Known limitations
@@ -68,7 +69,7 @@ browser ⇆ WebSocket /ws ⇆ Go backend ⇆ stdio (ACP) ⇆ agent subprocess
6869
6970 - ori-desktop: the window was launched by the user on their Mac (build OK, Check OK); after the CSS specificity fix (`#connect-screen` outranked `.screen[hidden]`, so the connect screen never hid and the viewer rendered below the fold) it has not yet been confirmed that the iframe actually shows ori inside WKWebView (`wails://` origin loading `http://localhost`; `NSAllowsLocalNetworking` set in `build/darwin/Info.plist`).
7071
71-- Behaviour against a real Claude Code session (auth flow, real tool calls) has not been exercised in this sandbox — only against the mock agent and unit doubles.
72+- Real Claude Code sessions: exercised by the user on 2026-09-18 (sandbox `hello-world`, template 0.0.2 + published kit, real prompt answered). Still missing: an automated end-to-end test that drives a real prompt.
7273
7374 ## Tickets (`.tickets/`)
7475
@@ -2,7 +2,7 @@
2 2
3 ## What it is3 ## What it is
4 4
5-Ori is a web client for ACP (Agent Client Protocol, https://agentclientprotocol.com) code agents: a Go backend serves an embedded React SPA and connects over ACP (JSON-RPC on stdio) to an agent subprocess — Claude Code via the `@zed-industries/claude-code-acp` adapter by default. The browser gets a Zed-style agent panel: streamed markdown answers, collapsible thoughts, tool call cards (with statuses, locations and diffs), the agent's plan, and permission prompts answered from the UI. Implements ticket `.tickets/issues/0004-specifications.yaml`.5+Ori is a web client for ACP (Agent Client Protocol, https://agentclientprotocol.com) code agents: a Go backend serves an embedded React SPA and connects over ACP (JSON-RPC on stdio) to an agent subprocess — Claude Code via the `@agentclientprotocol/claude-agent-acp` adapter by default (since 2026-09-18). The browser gets a Zed-style agent panel: streamed markdown answers, collapsible thoughts, tool call cards (with statuses, locations and diffs), the agent's plan, and permission prompts answered from the UI. Implements ticket `.tickets/issues/0004-specifications.yaml`.
6 6
7 ## Architecture7 ## Architecture
8 8
@@ -25,7 +25,7 @@ browser ⇆ WebSocket /ws ⇆ Go backend ⇆ stdio (ACP) ⇆ agent subprocess
25 25
26 ## Key decisions in force26 ## Key decisions in force
27 27
28-- Claude Code is reached through the official Zed adapter (`npx -y @zed-industries/claude-code-acp`) — the `claude` CLI (v2.1.274) has no native ACP mode; verified.28+- Claude Code is reached through `@agentclientprotocol/claude-agent-acp` (default `npx -y @agentclientprotocol/claude-agent-acp`, since 2026-09-18; `internal/config.DefaultAgentCommand`, pinned by `TestDefaultAgentCommandIsClaudeAgentACP`). It bundles its own Claude Code CLI via `@anthropic-ai/claude-agent-sdk` 0.3.274 → CLI 2.1.274 delivered as per-platform optional deps (`…-sdk-linux-arm64`, `-linux-x64`, …; no `cli.js` any more). Replaced Zed's `@zed-industries/claude-code-acp` 0.16.2, whose embedded CLI 2.1.44 the API rejects for current models ("version 2.1.251 or newer is required"). The `claude` CLI itself has no native ACP mode; verified.
29 - draw.io diagrams render online via `embed.diagrams.net` (no bundlable offline renderer exists: `viewer.min.js` is not on npm, `mxgraph` is archived and lacks draw.io's shapes, `drawio2svg` is GPL); future offline path = vendoring `viewer-static.min.js` (~3 MB, Apache-2.0). Documented in the architecture explanations.29 - draw.io diagrams render online via `embed.diagrams.net` (no bundlable offline renderer exists: `viewer.min.js` is not on npm, `mxgraph` is archived and lacks draw.io's shapes, `drawio2svg` is GPL); future offline path = vendoring `viewer-static.min.js` (~3 MB, Apache-2.0). Documented in the architecture explanations.
30 - ACP payloads cross the WebSocket verbatim (raw `SessionUpdate` / `RequestPermissionRequest`), keeping the front forward-compatible.30 - ACP payloads cross the WebSocket verbatim (raw `SessionUpdate` / `RequestPermissionRequest`), keeping the front forward-compatible.
31 - One agent session shared by all browsers in v1; multi-session is a planned evolution seam (`bridge.Prompter` interface, per-connection subscriptions).31 - One agent session shared by all browsers in v1; multi-session is a planned evolution seam (`bridge.Prompter` interface, per-connection subscriptions).
@@ -36,22 +36,23 @@ browser ⇆ WebSocket /ws ⇆ Go backend ⇆ stdio (ACP) ⇆ agent subprocess
36 36
37 - `make deps` — npm install in `ui/`.37 - `make deps` — npm install in `ui/`.
38 - `make build` — SPA build then Go binaries (`bin/ori`, `bin/ori-mock-agent`). Order matters: the Go build embeds `ui/dist`.38 - `make build` — SPA build then Go binaries (`bin/ori`, `bin/ori-mock-agent`). Order matters: the Go build embeds `ui/dist`.
39-- `make test` — `go test ./...` + `vitest run`. Also useful: `go test -race ./...`, `go test -short ./...` (skips e2e).39+- `make test` — `go test ./...` + `vitest run` (the ui `test` script sets `NODE_OPTIONS=--no-experimental-webstorage`: Node 25's built-in `localStorage` lacks `clear()` and shadows jsdom's — 15 store tests fail without it; verified 2026-09-18 on Node 25.9). Also useful: `go test -race ./...`, `go test -short ./...` (skips e2e).
40 - `make run` — with Claude Code adapter; `make run-mock` — with the demo agent (no Claude/network needed).40 - `make run` — with Claude Code adapter; `make run-mock` — with the demo agent (no Claude/network needed).
41-- `make template` — builds the `k33g/ori` sandbox template image (`template/Dockerfile`, FROM `docker/sandbox-templates:claude-code`); launch with `sbx run -d claude <project> --template k33g/ori:0.0.0 --kit <ori repo>/kits/ori --name ori -p 8888:8888` (detached — see the auto-stop trap below) (see `kits/ori/README.md` and docs how-to `run-in-a-sandbox`).41+- `make template` — builds the `k33g/ori` sandbox template image (`template/Dockerfile`, FROM `docker/sandbox-templates:claude-code`); launch with `sbx run -d claude <project> --template k33g/ori:0.0.2 --kit docker.io/k33g/ori-kit:latest --name ori -p 8888:8888/tcp` (detached — see the auto-stop trap below) (see `kits/ori/README.md` and docs how-to `run-in-a-sandbox`).
42-- `scripts/launch-ori.applescript` (2026-09-18) — macOS launcher: `sbx ls -q` → restart by name or create (`k33g/ori:0.0.1`, `./kits/ori`, `-p 5555:8888`), poll `http://localhost:5555/healthz` up to 90 s, then `open ori-desktop/build/bin/ori-desktop.app`. Run with `osascript`, or `osacompile` into `scripts/Launch Ori.app`; repo root derived from the script's own location. Compile-checked and handlers unit-tested; full end-to-end run not yet done.42+- `scripts/launch-ori.applescript` (2026-09-18) — macOS launcher: `sbx ls -q` → restart by name or create (`k33g/ori:0.0.2`, `./kits/ori`, `-p 5555:8888/tcp`), poll `http://localhost:5555/healthz` up to 90 s, then `open ori-desktop/build/bin/ori-desktop.app`. Run with `osascript`, or `osacompile` into `scripts/Launch Ori.app`; repo root derived from the script's own location. Compile-checked and handlers unit-tested; full end-to-end run not yet done.
43 - Kit publishing (sbx v0.43.0, verified 2026-09-18): `sbx kit validate kits/ori`, `sbx kit pack kits/ori -o ori-kit.zip`, `sbx kit push kits/ori docker.io/k33g/ori-kit:<tag>` — directory argument, not `spec.yaml`; there is no `sbx kit package`. Auth: `sbx login` session, then `sbx secret set --registry`, then the Docker credential store.43 - Kit publishing (sbx v0.43.0, verified 2026-09-18): `sbx kit validate kits/ori`, `sbx kit pack kits/ori -o ori-kit.zip`, `sbx kit push kits/ori docker.io/k33g/ori-kit:<tag>` — directory argument, not `spec.yaml`; there is no `sbx kit package`. Auth: `sbx login` session, then `sbx secret set --registry`, then the Docker credential store.
44 - **Kit packaging trap (verified 2026-09-18)**: `pack`/`push` decode `spec.yaml` raw (no `${{ kit.args }}` expansion) — an arg placeholder in an integer field (`ports[].container`) breaks them even though `validate`/`run` accept it. `ports[0].container` is therefore a literal 8888; the `port` arg only drives the startup command and agentInstructions.44 - **Kit packaging trap (verified 2026-09-18)**: `pack`/`push` decode `spec.yaml` raw (no `${{ kit.args }}` expansion) — an arg placeholder in an integer field (`ports[].container`) breaks them even though `validate`/`run` accept it. `ports[0].container` is therefore a literal 8888; the `port` arg only drives the startup command and agentInstructions.
45-- Published kit: `docker.io/k33g/ori-kit:latest` (pushed 2026-09-18, verified with `sbx kit inspect`); usable as `--kit docker.io/k33g/ori-kit:latest` instead of `--kit ./kits/ori`.45+- Published artefacts (2026-09-18): template `k33g/ori:0.0.2` on Docker Hub (multi-arch amd64+arm64, pushed by the user with `./template/build.sh`, ships only `claude-agent-acp`); kit `docker.io/k33g/ori-kit:latest` re-pushed with `--agent-cmd claude-agent-acp` + template 0.0.2. **They must move together**: the first `hello-world` sandbox paired image 0.0.2 with the morning's kit (`claude-code-acp`) and ori died with "executable file not found".
46+- **Port publishing traps**: `-p H:8888` without protocol binds `tcp4` only — browsers resolving `localhost` to `::1` fail; always `-p H:8888/tcp` (dual-stack). Chrome refuses ports 6665–6669 (`ERR_UNSAFE_PORT`) whatever the server does. `sbx ports NAME` lists; `sbx ports ls` looks for a sandbox called `ls`.
46 - **sbx auto-stop trap (verified in the sbx source + daemon log, 2026-09-17)**: sandboxd stops a sandbox 30 s (hard-coded `WithAutoStopDelay(30*time.Second)`, `sandboxd/pkg/server/backend_dockernext.go`) after its last CLI "sentinel" session closes; `sbx create`/`sbx exec`/`sbx run` all hold one for their own duration, browser traffic on a published port counts for nothing. Only a sandbox created with the spec field `detached: true` (CLI: `sbx run -d …`, absent from `sbx create`) is exempt; the field is create-time only (no PATCH), so an existing sandbox must be `sbx rm`'d and recreated with `sbx run -d`. No settings/env knob for the delay.47 - **sbx auto-stop trap (verified in the sbx source + daemon log, 2026-09-17)**: sandboxd stops a sandbox 30 s (hard-coded `WithAutoStopDelay(30*time.Second)`, `sandboxd/pkg/server/backend_dockernext.go`) after its last CLI "sentinel" session closes; `sbx create`/`sbx exec`/`sbx run` all hold one for their own duration, browser traffic on a published port counts for nothing. Only a sandbox created with the spec field `detached: true` (CLI: `sbx run -d …`, absent from `sbx create`) is exempt; the field is create-time only (no PATCH), so an existing sandbox must be `sbx rm`'d and recreated with `sbx run -d`. No settings/env knob for the delay.
47-- Quality: `python3 ~/.claude/skills/quality/scripts/quality_report.py --workspace .` (gate PASS, run #16, 2026-09-17 late evening: 0 errors, 0 warnings, 0 smells, complex 514 — covers the selectors/previews/desktop work, the resizable file tree and the light/dark theme).48+- Quality: `python3 .claude/skills/quality/scripts/quality_report.py --workspace .` (the script lives in the repo, not `~/.claude`). Gate PASS, run #18, 2026-09-18: 0 errors, 0 warnings, 0 smells, complex 532 (covers the adapter switch, the MIME pin, the copy-paste feature). **Mac trap**: `.qlty/{logs,out,plugin_cachedir,results}` are symlinks qlty creates into its cache; ones created inside the sandbox point at `/home/agent/…` and dangle on the Mac, making every plugin invocation "FATAL" with no detail — delete the dangling links, qlty recreates them. `.qlty/qlty.toml` pins golangci-lint 2.13.2 (built with Go 1.27): qlty's default 1.61.0 and its latest-known 2.6.2 refuse this repo's `go 1.26.5`.
48 - `docs/source-code-analysis.adoc` — AsciiDoc snapshot analysis of the codebase (2026-09-17), outside the bilingual Diátaxis set.49 - `docs/source-code-analysis.adoc` — AsciiDoc snapshot analysis of the codebase (2026-09-17), outside the bilingual Diátaxis set.
49 50
50 ## Environment facts (sandbox)51 ## Environment facts (sandbox)
51 52
52 - `~/.qlty` is a dedicated 488MB volume, too small for qlty's tool cache; the cache was moved to `~/.qlty-cache` on the main disk with a symlink `~/.qlty/cache -> /home/agent/.qlty-cache`. If qlty reports "No space left on device", check this symlink survived. (It did NOT survive a sandbox recreation on 2026-09-17: the volume came back empty and the first run failed exactly that way; recreating the symlink fixed it.)53 - `~/.qlty` is a dedicated 488MB volume, too small for qlty's tool cache; the cache was moved to `~/.qlty-cache` on the main disk with a symlink `~/.qlty/cache -> /home/agent/.qlty-cache`. If qlty reports "No space left on device", check this symlink survived. (It did NOT survive a sandbox recreation on 2026-09-17: the volume came back empty and the first run failed exactly that way; recreating the symlink fixed it.)
53 - `~/.npm` is the same kind of 488MB dedicated volume and filled up when installing Monaco; the npm cache now lives at `~/.npm-big` via `NPM_CONFIG_CACHE` exported in `/etc/sandbox-persistent.sh`.54 - `~/.npm` is the same kind of 488MB dedicated volume and filled up when installing Monaco; the npm cache now lives at `~/.npm-big` via `NPM_CONFIG_CACHE` exported in `/etc/sandbox-persistent.sh`.
54-- Running ori inside this Claude-Code-driven sandbox requires `env -u CLAUDECODE ./bin/ori` (Claude refuses nested sessions). Also set `CLAUDE_CODE_EXECUTABLE=<current claude binary>`: the Zed adapter 0.16.2 bundles Claude Code 2.1.44, which rejects the `claude-fable-5-1[1m]` model from the host `~/.claude/settings.json`. **Since 2026-09-18 the API requires CLI ≥ 2.1.251 for that model, and the template's own CLI is 2.1.246 (base image built 2026-08-26)** — so the env var alone is not enough; run `claude update` in the sandbox first, or switch the template to `@agentclientprotocol/claude-agent-acp` (SDK 0.3.274 → CLI 2.1.274). Handoff `2026-09-18-claude-version-too-old.md`.55+- Running ori inside this Claude-Code-driven sandbox requires `env -u CLAUDECODE ./bin/ori` (Claude refuses nested sessions). With the current template/kit (`claude-agent-acp`, bundled CLI 2.1.274) no `CLAUDE_CODE_EXECUTABLE` is needed. It remains the lever if something spawns the old Zed adapter (points it at another `claude` binary) — but the template's own CLI is 2.1.246 (base image 2026-08-26), below the 2.1.251 floor the API enforces for `claude-fable-5-1[1m]` (host `~/.claude/settings.json`), so `claude update` inside the sandbox would come first. Handoffs `2026-09-18-claude-version-too-old.md`, `2026-09-18-claude-agent-acp-adapter.md`.
55 - npm blocks install scripts by default here; esbuild's postinstall was approved via `npm approve-scripts` (recorded in `ui/package.json` `allowScripts`).56 - npm blocks install scripts by default here; esbuild's postinstall was approved via `npm approve-scripts` (recorded in `ui/package.json` `allowScripts`).
56 57
57 ## Known limitations58 ## Known limitations
@@ -68,7 +69,7 @@ browser ⇆ WebSocket /ws ⇆ Go backend ⇆ stdio (ACP) ⇆ agent subprocess
68 69
69 - ori-desktop: the window was launched by the user on their Mac (build OK, Check OK); after the CSS specificity fix (`#connect-screen` outranked `.screen[hidden]`, so the connect screen never hid and the viewer rendered below the fold) it has not yet been confirmed that the iframe actually shows ori inside WKWebView (`wails://` origin loading `http://localhost`; `NSAllowsLocalNetworking` set in `build/darwin/Info.plist`).70 - ori-desktop: the window was launched by the user on their Mac (build OK, Check OK); after the CSS specificity fix (`#connect-screen` outranked `.screen[hidden]`, so the connect screen never hid and the viewer rendered below the fold) it has not yet been confirmed that the iframe actually shows ori inside WKWebView (`wails://` origin loading `http://localhost`; `NSAllowsLocalNetworking` set in `build/darwin/Info.plist`).
70 71
71-- Behaviour against a real Claude Code session (auth flow, real tool calls) has not been exercised in this sandbox — only against the mock agent and unit doubles.72+- Real Claude Code sessions: exercised by the user on 2026-09-18 (sandbox `hello-world`, template 0.0.2 + published kit, real prompt answered). Still missing: an automated end-to-end test that drives a real prompt.
72 73
73 ## Tickets (`.tickets/`)74 ## Tickets (`.tickets/`)
74 75