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
23 changes: 23 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,29 @@

## Unreleased

## v2.1.0 — 2026-08-31

VS Code adapter and shared client extraction on top of the v2.0.0 relay. The relay remains the core; OpenCode and VS Code are thin adapters on the same `/host` + `/ws` protocol.

GitHub tag `v2.1.0` is the source of truth for this release. `@chorus/plugin` remains install-from-git (packages are private). Rebuild `chorus-relay` from this tag when upgrading from v2.0.0.

### VS Code adapter

- New extension (`packages/vscode`) — share/join/chat, Session sidebar, approve/deny joiners
- Session access control matches OpenCode: required display name, host approval, optional email/repo gates
- Cross-adapter relay: VS Code can host for terminal joiners or join a terminal host

### Shared client

- Extract `@chorus/client` (`JoinClient` + `RelayServer`) used by OpenCode plugin and VS Code
- OpenCode plugin re-exports from `@chorus/client` (includes v2.0.0 network options: CIDR allowlists, async `stop()`)

### Tests & docs

- `bun run test:vscode-e2e` — email gate, pending approve, `collab.input`
- `bun run test:vscode-relay-e2e` — three-env gate + VS Code ↔ terminal cross-adapter
- README, STATUS, DECISIONS, and `packages/vscode/README.md` updated

## v2.0.0 — 2026-08-21

Enterprise network lockdown for Chorus: keep the relay off the open internet with CIDR allowlists, private bind, and VPN/VPC deployment docs.
Expand Down
2 changes: 1 addition & 1 deletion Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

47 changes: 41 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,16 +11,17 @@ Collaborative OpenCode session sharing. Pair-program a live AI session from anot

## How it works

Chorus is an [OpenCode](https://github.com/sst/opencode) plugin plus a **Rust WebSocket relay** (`chorus-relay`).
Chorus is an [OpenCode](https://github.com/sst/opencode) plugin, a **VS Code adapter**, and a **Rust WebSocket relay** (`chorus-relay`). The relay is the core: adapters are thin clients on the same `/host` + `/ws` wire protocol (`@chorus/shared`).

```
Host runs opencode → plugin loads
Host runs: /chorus-share → plugin spawns chorus-relay + issues join token
Joiner runs: /chorus-join → connects to relay /ws
Joiner prompts → relay → host control channel → host session.prompt
Host adapter (OpenCode or VS Code) → spawns chorus-relay + issues join token via /host
Joiner adapter (OpenCode or VS Code) → connects to relay /ws
Joiner prompts → relay → host control channel → host session (OpenCode) or host panel (VS Code)
```

Both sides need OpenCode + the chorus plugin. The host machine needs the `chorus-relay` binary on `PATH` or built in-repo (`target/release/chorus-relay`). Override with `CHORUS_RELAY_BIN`.
**OpenCode ↔ OpenCode** is the primary path (full LLM loop + transcript mirror). **VS Code** uses the same relay: it can host a session that terminal/OpenCode joiners connect to, or join a terminal/OpenCode host. VS Code does not drive OpenCode’s model when sharing — publish host/AI lines manually or pair with an OpenCode host. See [packages/vscode/README.md](packages/vscode/README.md).

The host machine needs the `chorus-relay` binary on `PATH` or built in-repo (`target/release/chorus-relay`). Override with `CHORUS_RELAY_BIN`.

## Installation

Expand All @@ -43,13 +44,25 @@ Then add to OpenCode config:
}
```

### VS Code adapter

```sh
cargo build -p chorus-relay --release
bun install
bun run build:ts
```

In VS Code: **Extensions: Install from Location…** → `packages/vscode`, then **Chorus: Share Session** / **Join Session**. Details: [packages/vscode/README.md](packages/vscode/README.md).

## Layout

One monorepo, two ecosystems, one wire contract:

| Path | Artifact | Description |
|---|---|---|
| `packages/plugin` | npm `@chorus/plugin` | OpenCode plugin — tools, hooks, spawns/manages relay |
| `packages/client` | npm `@chorus/client` | Shared `JoinClient` + `RelayServer` for host adapters |
| `packages/vscode` | VS Code extension `chorus` | Share/join Chorus sessions from VS Code |
| `packages/shared` | npm `@chorus/shared` | TypeScript types + codecs for joiner and host-control protocols |
| `crates/chorus-relay` | `chorus-relay` binary | Rust WebSocket relay (`/ws` joiners, `/host` control plane) |
| `protocol/` | fixtures (not published) | Canonical JSON examples both TS and Rust must deserialize |
Expand Down Expand Up @@ -105,6 +118,28 @@ For live mirrored context on joiners, open the **web UI** (not only `opencode at

`opencode attach` works for driving the session, but OpenCode’s attach TUI often does not live-render plugin-injected transcript lines.

### Adapter & relay e2e (no GUI)

Protocol-level tests for the shared `@chorus/client` stack (same code paths as the OpenCode plugin and VS Code extension):

```sh
bun run test:vscode-e2e # VS Code adapter: email gate, pending approve, collab.input
bun run test:vscode-relay-e2e # cross-adapter: VS Code host ↔ terminal joiner (both directions)
bun run test:adapters-e2e # both of the above
bun run test:security-e2e # OpenCode host + protocol joiner (requires multi-agent up)
bun run test:network-e2e # CIDR / source-port allowlist on chorus-relay
```

**Three-environment relay test** (`test:vscode-relay-e2e`): models host vscode, joiner vscode, and a disallowed joiner. Run all scenarios locally (default), or split across machines with `CHORUS_E2E_ROLE`:

| Env | Role | Command |
|-----|------|---------|
| env1 | host vscode | `CHORUS_E2E_ROLE=host-vscode bun scripts/vscode-relay-e2e.ts` |
| env2 | joiner vscode | `CHORUS_E2E_ROLE=joiner-vscode bun scripts/vscode-relay-e2e.ts` |
| env3 | disallowed joiner | `CHORUS_E2E_ROLE=disallowed-joiner bun scripts/vscode-relay-e2e.ts` |

Coordination file: `CHORUS_E2E_STATE` (default `/tmp/chorus-vscode-relay-e2e.json`).

## Configuration

Chorus reads a layered **config file** plus environment variables.
Expand Down
32 changes: 32 additions & 0 deletions bun.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion crates/chorus-relay/Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "chorus-relay"
version = "2.0.0"
version = "2.1.0"
edition.workspace = true
license.workspace = true
authors.workspace = true
Expand Down
5 changes: 3 additions & 2 deletions docs/DECISIONS.md
Original file line number Diff line number Diff line change
Expand Up @@ -48,8 +48,9 @@ Recommended shape:
```

- **v1:** OpenCode adapter = this repo’s plugin + local **Rust** `chorus-relay`
- **v1.x:** keep `@chorus/shared` as the stable joiner + host-control contract
- **v2:** optional second adapter (Codex app-server is the best #2) only after OpenCode UX is solid
- **v1.x:** keep `@chorus/shared` as the stable joiner + host-control contract; `@chorus/client` holds `JoinClient` / `RelayServer` for non-OpenCode hosts
- **v1.x adapters in-monorepo:** VS Code (`packages/vscode`) — same wire protocol and `@chorus/client` relay stack; host-specific UI only. Cross-adapter (VS Code host ↔ terminal joiner) is validated by `bun run test:vscode-relay-e2e`.
- **v2:** optional further adapters (Codex app-server remains a strong candidate) once OpenCode UX is solid; split adapter packages into separate repos only when publish/CI ownership diverges

**Do not** make Claude Code / Amp the primary host — large audiences, but proprietary control planes. Treat them as distribution adapters later, not the core.

Expand Down
32 changes: 24 additions & 8 deletions docs/STATUS.md
Original file line number Diff line number Diff line change
@@ -1,19 +1,22 @@
# Chorus status — what still needs to happen

Snapshot as of 2026-08-21 (toward `v2.0.0` network lockdown). Tests/typecheck/build are green.
Snapshot as of 2026-08-31 (`v2.1.0` — VS Code adapter + `@chorus/client`). Tests/typecheck/build are green.

## Current state

Chorus is an **OpenCode↔OpenCode** LAN collaboration stack:
Chorus is a **relay-first** LAN collaboration stack with two adapters on one wire protocol:

- Host: `/chorus-share` → spawns Rust `chorus-relay` + issues token via `/host`
- Joiner: `/chorus-join` → connects to relay `/ws`, forwards prompts into the host session
- Side channel: `/chorus-chat` + typing toasts
- **OpenCode** (terminal): `/chorus-share` → spawns Rust `chorus-relay` + issues token via `/host`; `/chorus-join` → `/ws`; full LLM loop + transcript mirror
- **VS Code** (`packages/vscode`): same `JoinClient` + `RelayServer` via `@chorus/client`; can host relay for terminal joiners or join a terminal host
- Side channel: `/chorus-chat` + typing toasts (OpenCode); VS Code panel chat
- Optional S3/R2 backup of user events
- **v2.0.0 network lockdown**: CIDR allow/deny, bind policy, source-port allowlist ([docs/NETWORK.md](./NETWORK.md))

The browser companion (`packages/web`) was intentionally removed. Joiners now mirror the host transcript into their OpenCode session (`[Host]:` / `[AI]:` lines via `noReply` inject); side-channel chat/typing remain toasts.
The browser companion (`packages/web`) was intentionally removed. Joiners mirror the host transcript into their OpenCode session (`[Host]:` / `[AI]:` lines via `noReply` inject); side-channel chat/typing remain toasts.
The in-process Bun relay has been replaced by `crates/chorus-relay`.

**Editor adapters:** VS Code shipped in-repo (`packages/vscode`); Zed adapter deferred to a separate branch/PR.

Differentiation vs nearby OpenCode plugins (`opencode-live`, `opencode-sessions`, `opencode-ensemble`, `opencode-relay`): those target **multi-agent / same-DB sync**. Chorus targets **multi-human** pair programming on one live AI session.

## Priority backlog
Expand Down Expand Up @@ -64,19 +67,32 @@ Enterprise gap analysis (what would actually pass a security review vs what ship

### P2 — backup & polish

### P2 — backup & polish

22. Backup AI + chat events; expose restore/list; set `endedAt` on stop.
23. Drop unused `@aws-sdk/lib-storage` or use it.
24. Broader tests: roles/view-forbid, kick/close, chat/typing, plugin entry, tunnel.
25. Native slash-command registration when [opencode#5305](https://github.com/sst/opencode/issues/5305) lands.

### Adapter e2e shipped

- `bun run test:vscode-e2e` — VS Code path: email gate, pending approve, `collab.input`
- `bun run test:vscode-relay-e2e` — three-env gate (host vscode / joiner vscode / disallowed joiner) + VS Code ↔ terminal cross-adapter
- `bun run test:adapters-e2e` — both of the above
- `bun run test:security-e2e` — OpenCode host + protocol joiner (approval flow)
- `bun run test:network-e2e` — CIDR / source-port allowlist on `chorus-relay`

## Local multi-agent harness

`scripts/multi-agent.ts` (via `bun run multi-agent`) can spawn N isolated `opencode serve` instances with the Chorus plugin, then automate `/chorus-share` + `/chorus-join` through OpenCode’s `/session/:id/command` API. Also includes `relay-stress` for concurrent protocol joiners without OpenCode.

## Adapter surface (monorepo)

- **OpenCode** (`packages/plugin`) — primary host; full LLM loop + transcript mirror. Re-exports `@chorus/client` for relay/join.
- **VS Code** (`packages/vscode`) — share/join via `@chorus/client`; sidebar transcript; session access control (approval, email gate, repo gate). Can host relay for terminal joiners or join a terminal host. Does **not** drive OpenCode’s model when sharing — publish host lines manually or pair with OpenCode.
- **Shared client** (`packages/client`) — `JoinClient` + `RelayServer`; used by both adapters. UI is secondary; relay host/joiner interaction is the contract.

## Explicit non-goals (for now)

- Rebuilding a full coding agent harness from scratch.
- Competing with multi-agent orchestration plugins.
- Making the browser app the primary UX again (optional later for non-OpenCode observers).
- Splitting the monorepo until adapter publish cadence actually diverges.
Binary file added docs/walkthrough/chorus_join_dialog.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added docs/walkthrough/chorus_pending_approve.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added docs/walkthrough/chorus_share_active.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
17 changes: 12 additions & 5 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -27,19 +27,26 @@
"scripts": {
"build:relay": "cargo build -p chorus-relay --release",
"build:shared": "bun run --filter @chorus/shared build",
"build:client": "bun run --filter @chorus/client build",
"build:plugin": "bun run --filter @chorus/plugin build",
"build:ts": "bun run build:shared && bun run build:plugin",
"build:vscode": "bun run --filter chorus build",
"build:ts": "bun run build:shared && bun run build:client && bun run build:plugin && bun run build:vscode",
"build": "bun run build:relay && bun run build:ts",
"test:relay": "cargo test -p chorus-relay",
"test:ts": "bun run build:shared && bun run --filter @chorus/shared test && bun run --filter @chorus/plugin test",
"test:ts": "bun run build:shared && bun run build:client && bun run --filter @chorus/shared test && bun run --filter @chorus/client test && bun run --filter @chorus/plugin test && bun run --filter chorus test",
"test": "bun run build:relay && bun run test:relay && bun run test:ts",
"typecheck": "bun run build:shared && bun run --filter @chorus/shared typecheck && bun run --filter @chorus/plugin typecheck",
"clean": "bun run --filter @chorus/shared clean && bun run --filter @chorus/plugin clean && cargo clean",
"typecheck": "bun run build:shared && bun run build:client && bun run --filter @chorus/shared typecheck && bun run --filter @chorus/client typecheck && bun run --filter @chorus/plugin typecheck && bun run --filter chorus typecheck",
"clean": "bun run --filter @chorus/shared clean && bun run --filter @chorus/client clean && bun run --filter @chorus/plugin clean && bun run --filter chorus clean && cargo clean",
"multi-agent": "bun scripts/multi-agent.ts",
"test:multi-agent": "bun scripts/multi-agent.ts setup --agents 2 && bun scripts/multi-agent.ts up --agents 2 && bun scripts/multi-agent.ts smoke && bun scripts/multi-agent.ts pair && bun scripts/multi-agent.ts down",
"test:integration": "bun run test && bun run typecheck && bun run test:multi-agent && bun run test:security-e2e && bun run test:relay-stress",
"test:integration": "bun run test && bun run typecheck && bun run test:multi-agent && bun run test:security-e2e && bun run test:relay-stress && bun run test:vscode-relay-e2e",
"test:relay-stress": "bun scripts/multi-agent.ts relay-stress --agents 5",
"test:network-e2e": "bun run build:relay && bun scripts/network-e2e.ts",
"test:security-e2e": "bun scripts/security-e2e.ts",
"test:vscode-e2e": "bun scripts/vscode-e2e.ts",
"test:vscode-relay-e2e": "bun scripts/vscode-relay-e2e.ts",
"test:plugin-e2e": "bun run test:multi-agent && bun run test:security-e2e",
"test:adapters-e2e": "bun run test:vscode-e2e && bun run test:vscode-relay-e2e",
"docker-agents": "bun scripts/docker-agents.ts",
"test:docker-agents": "bun scripts/docker-agents.ts up --agents 2 && bun scripts/docker-agents.ts smoke && bun scripts/docker-agents.ts pair && bun scripts/docker-agents.ts down"
},
Expand Down
3 changes: 3 additions & 0 deletions packages/client/dist/index.d.ts

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions packages/client/dist/index.d.ts.map

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 3 additions & 0 deletions packages/client/dist/index.js

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions packages/client/dist/index.js.map

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading
Loading