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

source-code-analysis.adoc · 262 lines · 11.3 KBGDScript3 Blame HistoryRaw
✨ Workspace panel, selectors, previews, desktop app, sandbox template, resizable file tree, light/dark theme 76d62ac k33g yesterday1= Ori Source Code Analysis
2:toc: left
3:toclevels: 3
4:icons: font
5:source-highlighter: highlight.js
6:revdate: 2026-09-17 (updated 2026-09-17)
7
8== Overview
9
10Ori is a web client for https://agentclientprotocol.com[ACP (Agent Client Protocol)] code agents.
11A Go backend serves an embedded React SPA and connects over ACP (JSON-RPC on stdio) to an agent
✨ Switch the ACP adapter to @agentclientprotocol/claude-agent-acp (template 0.0.2) 5d476ff k33g 18h ago12subprocess Claude Code via the Claude Agent SDK ACP adapter (`npx -y @agentclientprotocol/claude-agent-acp`) by default.
✨ Workspace panel, selectors, previews, desktop app, sandbox template, resizable file tree, light/dark theme 76d62ac k33g yesterday13The browser gets a Zed-style agent panel: streamed markdown answers, collapsible thoughts (live
14during streaming, collapsible when complete), working spinner with rotating phrases, tool call
15cards (status, locations, diffs), the agent's plan, and permission prompts answered from the UI —
16plus a workspace panel with a VS Code-style file tree (Material icons, indent guides, chevrons),
17Monaco preview/editor with rendered Markdown and AsciiDoc, and an xterm.js terminal.
18
19[source]
20----
21browser WebSocket /ws Go backend stdio (ACP) agent subprocess
22----
23
24* Go module: `rickub.com/bots-garden/ori`
25* ~4,063 lines of source: Go 1,555 (21 files), TypeScript/TSX 2,508 (39 files)
26
27== Backend (Go)
28
29=== Entry points
30
31[cols="1,3"]
32|===
33| Package | Responsibility
34
35| `cmd/ori`
36| Server binary. Parses flags (`--addr`, default `:8888`; `--cwd`; `--agent-cmd`,
37whitespace-split), spawns the agent, wires the bridge and HTTP routes.
38
39| `cmd/ori-mock-agent`
40| Deterministic demo ACP agent on stdio (thought plan read tool call permission
41edit tool call with diff answer). Used by `make run-mock` and the e2e test.
42|===
43
44=== Internal packages
45
46[cols="1,3"]
47|===
48| Package | Responsibility
49
50| `internal/config`
51| CLI flag parsing and validation (`Config`, `FromArgs()`).
52
53| `internal/agent`
54| Spawns the agent subprocess and drives the ACP lifecycle via
55`github.com/coder/acp-go-sdk` v0.13.5. Exposes a generic `Handler` interface (session
56updates + blocking permission requests). Implements the `acp.Client` side: real
57filesystem read/write; terminal methods refused (capability not announced).
58
59| `internal/bridge`
60| Hub between the agent and the browsers (~550 LOC). Broadcast with per-client buffers
61(256 items; slow clients are dropped), append-only history capped at 4,096 events and
62replayed on every connection, `user_message` echo so the server is the single source of
63truth, permission routing by `requestId` (pending requests replayed to late joiners).
64Uses `github.com/coder/websocket` v1.8.15; origins restricted to localhost. A
65`Prompter` interface is the seam for future multi-session support.
66
67| `internal/httpserver`
68| Root HTTP mux: `/healthz`, embedded SPA with fallback routing, injection point for
69extra routes (`/ws`, `/ws/terminal`, `/api/*`).
70
71| `internal/files`
72| Workspace file API: list / read / write. Relative paths resolved against `--cwd`,
73absolute paths allowed access is deliberately unrestricted (ori targets
74already-isolated sandboxes). Binary content 415, files > 5 MiB 413.
75
76| `internal/terminal`
77| One shell per WebSocket on `/ws/terminal`, in a PTY (`creack/pty` v1.1.24). Binary
78frames out; JSON `input` / `resize` frames in. Shell chosen from `$SHELL` bash sh.
79
80| `internal/mockagent`
81| The scripted demo agent implementation behind `cmd/ori-mock-agent`.
82
83| `ui/embed.go`
84| `go:embed all:dist` the built SPA embedded into the Go binary.
85|===
86
87== Frontend (`ui/`)
88
89Vite 7 + React 19 + TypeScript strict. State is held in zustand stores wrapped around pure
90reducers, keeping the logic testable without React or WebSocket coupling.
91
92=== Core modules
93
94[cols="1,3"]
95|===
96| File | Role
97
98| `src/App.tsx`
99| Root layout: topbar (connection status, workspace toggle), chat column (thread, plan,
100permission prompts, input), workspace panel.
101
102| `src/reducer.ts`
103| Pure state machine (handler-table style) folding server messages and connection
104events into the chat state.
105
106| `src/store.ts`
107| Zustand wrapper around the reducer (`useChatState()`, `dispatch()`).
108
109| `src/ws.ts`
110| WebSocket client with exponential-backoff reconnect; the thread resets on `hello` so
111history replays are idempotent.
112
113| `src/protocol.ts`
114| Wire contract mirroring `internal/bridge/protocol.go`; ACP payloads are typed but
115relayed verbatim, keeping the front forward-compatible (unknown update kinds ignored).
116
117| `src/workspace.ts`
118| Separate zustand store for the workspace panel (file tree, tabs, open files).
119
120| `src/api.ts`
121| Client for `/api/files` and `/api/file`.
122
123| `src/lang.ts`
124| File extension language ID mapping for Monaco editor.
125
126| `src/monaco-setup.ts`
127| Monaco editor environment configuration (bundled workers, no CDN).
128|===
129
130=== Components
131
132* **Chat panel**: `ChatThread` (with `ThoughtEntry` for live/collapsed thoughts), `Markdown`,
133 `InlineText` (backtick spans `<code>` for tool titles), `ToolCallCard` (status badge,
134 collapsible content, diffs, file locations), `PermissionPrompt`, `PlanView`, `PromptInput`,
135 `WorkingIndicator` (spinner with rotating phrases).
136* **Workspace panel**: `Workspace` (tabs), `FileTree` (VS Code style with Material icons from
137 `vscode-material-icons` 910 SVGs copied to `public/material-icons/` by
138 `scripts/copy-icons.mjs`; click = preview, double-click = edit, chevrons, indent guides,
139 active highlight), `PreviewPane` (Monaco read-only for code, rendered Markdown via
140 `react-markdown` + `remark-gfm`, rendered AsciiDoc via lazily-imported `@asciidoctor/core`
141 v4 async API, Rendered/Source toggle), `EditorPane` (Monaco, dirty indicator, Save /
142 Ctrl+S), `TerminalPane` (xterm.js on `/ws/terminal` with `@xterm/addon-fit`, dark theme,
143 started on first activation), `MonacoViewer` (shared by preview and editor), `CodeView`
144 (syntax-highlighted code block for diffs).
145* Inactive panes are hidden, not unmounted, so shell and editor state survive tab switches.
146* `execute` tool-call output renders in a monospace `<pre>`, never as markdown.
147* Monaco workers are bundled (no CDN); the editor loads as a lazy chunk (~1 MB gzip).
148
149== HTTP / WebSocket endpoints
150
151[cols="1,1,3"]
152|===
153| Endpoint | Method | Purpose
154
155| `/healthz` | GET | Health check (`{"status":"ok"}`).
156| `/ws` | GET (upgrade) | Agent browser relay: history replay then live events.
157| `/ws/terminal` | GET (upgrade) | Interactive PTY shell.
158| `/api/files` | GET | List a directory (`?path=`).
159| `/api/file` | GET | Read a file (max 5 MiB).
160| `/api/file` | PUT | Write a file.
161| `/` | GET | Embedded SPA (index.html fallback).
162|===
163
164== WebSocket protocol (bridge)
165
166Browser server: `prompt`, `cancel`, `permission_response` (by `requestId`).
167
168Server browser: `hello` (session id, turn state resets the client thread),
169`user_message` (echo), `session_update` (raw ACP `SessionUpdate`), `permission_request`
170(raw ACP `RequestPermissionRequest` + `requestId`), `permission_resolved`,
171`turn_started`, `turn_ended` (stop reason), `error`.
172
173Reference: `internal/bridge/protocol.go`, `ui/src/protocol.ts`, and
174`docs/en/reference/websocket-protocol.md`.
175
176== Tests
177
178* *Go*: 55 tests across 8 packages plus one end-to-end test (`cmd/ori/e2e_test.go`,
179 full server + mock agent subprocess + real WebSocket; skipped with `-short`).
180 Race-clean (`go test -race ./...`).
181* *Frontend*: 75 vitest tests across 13 test files reducer (48), components
182 (WorkingIndicator, InlineText, AsciiDocView with real converter, ToolCallCard, FileTree,
183 EditorPane, PreviewPane, TerminalPane, App), WebSocket client, workspace store, language
184 detection.
185* Quality gate (qlty): PASS as of 2026-09-17 0 errors, 0 warnings, 0 smells (2 formatting
186 notes).
187
188== Build and run
189
190[cols="1,3"]
191|===
192| Target | Effect
193
194| `make deps` | `npm install` in `ui/`.
195| `make build` | SPA build then Go binaries order matters: the Go build embeds `ui/dist`.
196| `make test` | `go test ./...` + `vitest run`.
197| `make run` | Run with the Claude Code ACP adapter.
198| `make run-mock` | Run with the bundled demo agent (no Claude, no network needed).
199| `make dev` | Vite dev server on :5173.
200|===
201
202== Key design decisions
203
204* ACP payloads cross the WebSocket *verbatim*, keeping the frontend forward-compatible.
205* The server owns the thread (history + `user_message` echo); the SPA rebuilds from
206 replay on every (re)connection.
207* One agent session shared by all browsers in v1; a second `prompt` during an active
208 turn is rejected. Multi-session is a planned evolution behind the `bridge.Prompter`
209 seam.
210* File API access is deliberately unrestricted: ori targets already-isolated sandboxes.
✨ Switch the ACP adapter to @agentclientprotocol/claude-agent-acp (template 0.0.2) 5d476ff k33g 18h ago211* Claude Code is reached through the Agent Client Protocol adapter (`@agentclientprotocol/claude-agent-acp`,
212 which bundles its own Claude Code CLI via the Claude Agent SDK; default since 2026-09-18,
213 replacing Zed's `@zed-industries/claude-code-acp` whose embedded CLI 2.1.44 the API now
214 rejects) the `claude` CLI has no native ACP mode.
✨ Workspace panel, selectors, previews, desktop app, sandbox template, resizable file tree, light/dark theme 76d62ac k33g yesterday215* Material icons are copied from `vscode-material-icons` package to `public/` via an npm
216 pre-hook script, making them static assets bundled with the SPA.
217* AsciiDoc rendering uses `@asciidoctor/core` v4's async `convert` named export (v3's
218 factory function no longer exists).
219* Terminal always renders with a dark theme (`#1e1e1e`) regardless of app theme.
220
221== Known limitations
222
223* `--agent-cmd` is split on whitespace; arguments containing spaces need a wrapper script.
224* One prompt turn at a time per shared session.
225* ACP terminal methods are not supported (capability not announced; answered with JSON-RPC
226 method-not-found).
227* Only text content blocks are rendered in messages; images/audio/resources are not yet
228 supported.
229* Running ori inside a Claude Code sandbox requires `env -u CLAUDECODE ./bin/ori` to bypass
230 the nested-session guard.
231
232== Sandbox deployment
233
234Ori ships as a Docker Sandbox template (`template/Dockerfile`, FROM
235`docker/sandbox-templates:claude-code`) paired with a kit (`kits/ori/`):
236
✨ Switch the ACP adapter to @agentclientprotocol/claude-agent-acp (template 0.0.2) 5d476ff k33g 18h ago237* Create: `sbx run -d claude <project> --template k33g/ori:0.0.2 --kit <ori-repo>/kits/ori --name ori -p 8888:8888/tcp`
✨ Workspace panel, selectors, previews, desktop app, sandbox template, resizable file tree, light/dark theme 76d62ac k33g yesterday238* The `--detached` (`-d`) flag is **required**: sandboxd auto-stops a sandbox 30 seconds
239 after its last CLI sentinel session closes; without `-d`, the container stops shortly after
240 the prompt returns. Browser traffic on a published port does not hold the sandbox up.
241* The `detached` field is create-time only (no PATCH); an existing non-detached sandbox must
242 be `sbx rm`'d and recreated with `sbx run -d`.
243* Build the template image: `make template` (builds binaries first, copies to the image).
244
245=== Environment considerations (sandbox development)
246
247When developing ori inside a Claude Code sandbox:
248
249* `~/.qlty` and `~/.npm` are 488MB dedicated volumes. The qlty tool cache was moved to
250 `~/.qlty-cache` (symlinked from `~/.qlty/cache`), and npm cache to `~/.npm-big` (via
251 `NPM_CONFIG_CACHE` in `/etc/sandbox-persistent.sh`) to avoid "No space left" errors.
252* npm blocks install scripts by default; esbuild's postinstall was approved via
253 `npm approve-scripts` (recorded in `ui/package.json` `allowScripts` field).
254* The `radarlint-python` qlty plugin was removed (its ~300MB JDK cannot fit the dedicated
255 volume; diagnosis in `.qlty/qlty.toml` comments).
256
257== Documentation
258
259`docs/` follows the Diátaxis method, in English (`docs/en/`) and French (`docs/fr/`):
260tutorials (getting started), how-to guides (run the tests, run with Claude Code, run in a
261sandbox, use the workspace, use another agent), reference (CLI, WebSocket protocol, workspace
262API) and explanation (architecture).