nandi/oripublic Fork 0
db7446cc6b52b6bd9196294c1e66e5eb56754d5e
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

summary.md · 68 lines · 12.4 KBmarkdown Blame HistoryRaw
✨ Workspace panel, selectors, previews, desktop app, sandbox template, resizable file tree, light/dark theme 76d62ac k33g yesterday1# Ori — project summary
2
3## What it is
4
5Ori 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`.
6
7## Architecture
8
9```
10browser ⇆ WebSocket /ws ⇆ Go backend ⇆ stdio (ACP) ⇆ agent subprocess
11```
12
13- Go module: `rickub.com/bots-garden/ori` (NOT github — the repo is not hosted on GitHub; the user corrected this explicitly).
14- `cmd/ori` — server binary; flags `--addr` (default `:8888`), `--cwd`, `--agent-cmd` (whitespace-split).
15- `cmd/ori-mock-agent` — deterministic demo ACP agent on stdio (thought → plan → read tool call → permission → edit tool call with diff → answer); used by `make run-mock` and the e2e test.
16- `internal/config` — flag parsing/validation.
17- `internal/httpserver` — embedded SPA (via `ui/embed.go`, `go:embed all:dist`), `/healthz`, SPA fallback, extra-route injection point for `/ws`.
18- `internal/agent` — spawns the agent, drives the ACP lifecycle through `github.com/coder/acp-go-sdk` v0.13.5 (community SDK listed by the official ACP docs; the zed-industries repo itself has NO Go library); generic `Handler` interface (session updates + blocking permission requests); real fs read/write; terminals refused (capability not announced).
19- `internal/files` — workspace file API (`GET /api/files`, `GET /api/file`, `PUT /api/file`, plus `GET /api/files/search?q=&limit=` — recursive, case-insensitive, skips `.git`/`node_modules`, default 50 / max 500 results, walk capped at 50 000 entries — and `GET /api/raw?path=` streaming bytes with pinned image Content-Types, Range support, no size cap): list/read/write, relative paths against `--cwd`, absolute allowed; access deliberately unrestricted (user's choice: ori targets already-isolated sandboxes); binary → 415, >5MiB → 413.
20- `internal/terminal` — one shell per WebSocket on `GET /ws/terminal` in a PTY (`creack/pty` v1.1.24); binary frames out, JSON `input`/`resize` frames in; shell from `$SHELL`→bash→sh.
21- `internal/skills` — `GET /api/skills`: discovers `<cwd>/.claude/skills/*/SKILL.md` and `~/.claude/skills/*/SKILL.md` (frontmatter `name`/`description`, directory-name fallback, project shadows user, sorted).
22- `internal/bridge` — hub between agent and browsers: JSON protocol (see `docs/*/reference/websocket-protocol.md`), broadcast with per-client buffers (slow client = dropped), append-only history (cap 4096) replayed on every connection, `user_message` echo so the server is the single source of truth, permission routing by requestId (pending requests replayed to late joiners; agent-side ctx cancellation handled). The `prompt` message carries optional `attachments: [{path,name}]`, turned server-side into ACP `resource_link` blocks (`file://<abs>`) next to the text block — `Prompter.Prompt(ctx, []acp.ContentBlock)` replaced `PromptText`; relative paths are joined to the root given by `Bridge.SetWorkspaceRoot` (wired in `cmd/ori/main.go`); `user_message` echoes the attachments. Uses `github.com/coder/websocket` v1.8.15; origins restricted to localhost.
23- `ui/` — Vite + React 19 + TS strict; zustand store around a pure reducer (`ui/src/reducer.ts`, handler-table style); WS client with backoff reconnect; thread resets on `hello` so replays are idempotent; unknown ACP update kinds ignored by design. Workspace panel (separate zustand store `ui/src/workspace.ts`): file tree (click=preview, double-click=edit; column resizable via `ResizeHandle` — ARIA separator, pointer drag / arrows / Home-End / double-click reset — width `fileTreeWidth` in the store, bounded 120–800 px, default 224, applied through the `--filetree-width` CSS custom property, persisted in localStorage `ori.fileTreeWidth`). Colour theme (`ui/src/theme.ts`, own zustand store): `light` (default) | `dark`, stamped as `data-theme` on `<html>` at module load and on change, remembered in localStorage `ori.theme`, toggled by the header ☾/☀ button; `app.css` keys the dark palette on `:root[data-theme="dark"]` (the former `prefers-color-scheme` media query is gone — decision: explicit choice, light default), `MonacoViewer` reads the store (`vs-dark`/`light`), the terminal stays dark. Preview tab (Monaco read-only for code — bundled workers, no CDN, lazy chunk ~1MB gzip; rendered Markdown; rendered AsciiDoc via lazily-imported `@asciidoctor/core`, Rendered/Source toggle), Editor tab (Monaco, dirty ●, Save/Ctrl+S via PUT /api/file), Terminal tab (xterm.js on /ws/terminal, starts on first activation); inactive panes are hidden not unmounted so shell/edit state survives tab switches; `execute` tool-call output renders in a monospace `<pre>`, never markdown. Composer completions (`ui/src/mentions.ts` pure helpers, `ui/src/useCompletion.ts`, `CompletionPopup`): `@` opens a workspace file search (→ attachments), `/` opens a merged list of local skills (`/api/skills`) and ACP `available_commands_update` commands (stored as `commands` in the reducer, cleared on `hello`; deduped by name, skill wins) and inserts `/<name> ` as plain text — the text is the invocation mechanism, there is no ACP "run command" method. Preview kinds `image` (`ImageView`, `<img>` over checkerboard via `/api/raw`, natural size shown) and `drawio` (`DrawioView`: iframe on `embed.diagrams.net` fed by postMessage, Rendered/Source toggle, 8 s timeout → Source fallback; `.drawio.svg/.png` are images). The mock agent emits `available_commands_update` (`review`, `compact`) on session start and echoes `[attached: name]`.
24- `ori-desktop/` — separate Go module `rickub.com/bots-garden/ori-desktop` (Wails v2.16.0, Go ≥ 1.25, "plain" template, `//go:embed all:frontend/src`, no npm; `frontend/src/main.js` is loaded as an ES module — `type="module"` — since the 2026-09-17 lint cleanup): connection screen (URL remembered in `<UserConfigDir>/ori-desktop/settings.json`, key `serverUrl`; auto-connect at startup), `/healthz` probe via bound Go methods (GetConfig, SaveConfig, CheckHealth, Connect, OpenInBrowser, SettingsPath), then the ori SPA loaded in an `<iframe>` (Wails v2 cannot navigate the main webview to an external URL; `runtime.BrowserOpenURL` is the fallback). Ticket 0005. `make desktop` / `make desktop-test`; root `go test ./...` unaffected (nested module).
25
26## Key decisions in force
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.
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.
31- One agent session shared by all browsers in v1; multi-session is a planned evolution seam (`bridge.Prompter` interface, per-connection subscriptions).
32- The server owns the thread (history + user_message echo); the SPA rebuilds from replay on every (re)connection.
33- Quality config decisions (all user-approved): `.claude/**` excluded from qlty (vendored kit sources, the measurement instrument itself); `radarlint-python` plugin removed (its ~300MB JDK cannot fit the dedicated 488MB `~/.qlty` volume — diagnosis in `.qlty/qlty.toml` comments); `golangci-lint` + `biome` added manually (init missed both languages); `biome.json` at root parses `.vscode/**` as JSONC.
34
35## Build / test / run
36
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`.
39- `make test` — `go test ./...` + `vitest run`. 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).
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- **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.
43- 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).
44- `docs/source-code-analysis.adoc` — AsciiDoc snapshot analysis of the codebase (2026-09-17), outside the bilingual Diátaxis set.
45
46## Environment facts (sandbox)
47
48- `~/.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.)
49- `~/.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`.
50- 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>` (e.g. `/home/agent/.local/share/claude/versions/2.1.275`): the Zed adapter 0.16.2 bundles Claude Code 2.1.44, which rejects the `fable[1m]` model from `~/.claude/settings.json`.
51- npm blocks install scripts by default here; esbuild's postinstall was approved via `npm approve-scripts` (recorded in `ui/package.json` `allowScripts`).
52
53## Known limitations
54
55- `--agent-cmd` is split on whitespace; arguments containing spaces need a wrapper script.
56- One prompt turn at a time; a second `prompt` while a turn runs is rejected with an error event.
57- Terminal ACP methods are not supported (declared absent; answered with JSON-RPC method-not-found).
58- `user_message` / thought / message chunks only render text content blocks; images/audio/resources are not rendered yet; `user_message.attachments` are not rendered as chips (only the `@path` text mention).
59- Skills list is fetched once per page load; the file search walks whatever sits under `--cwd` (an untracked `sandboxes/` clone at the repo root gets walked).
60
📝 Update project memory after the merge into main 3017563 k33g yesterday61- Git: remote `origin` = `https://git.rickub.com/bots-garden/ori.git` (Gitea-style self-hosted, not GitHub). `main` fast-forwarded to `feat/acp-webapp` at commit `76d62ac` on 2026-09-17; **not pushed** yet. `hello.md` (a story generated during an ori test) and `.claude/settings.local.json` are deliberately left untracked.
62
✨ Workspace panel, selectors, previews, desktop app, sandbox template, resizable file tree, light/dark theme 76d62ac k33g yesterday63## Not yet established
64
65- 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`).
66
67- 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.
📝 Update project memory after the merge into main 3017563 k33g yesterday68