Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
18 commits
Select commit Hold shift + click to select a range
cf7e561
docs: add remote access design glossary and ADRs
william0wang Aug 16, 2026
38cebd0
chore: bump @agentclientprotocol/sdk to 1.3.0, add ws and hub bin entry
william0wang Aug 16, 2026
65342b1
feat: multi-client broadcast and loopback ACP endpoint behind ZCODE_A…
william0wang Aug 16, 2026
a96dbaa
feat: zcode-acp-hub discovery and proxy daemon
william0wang Aug 16, 2026
b3d0eed
docs: remote access guide and multi-client semantics
william0wang Aug 16, 2026
c3a750c
docs: remote client integration contract (discovery, transport,
william0wang Aug 16, 2026
1bf987b
fix: adopt stored session titles on load/resume for discovery
william0wang Aug 16, 2026
da450f8
docs: lock tail-replay proposal decisions and ADR-0003
william0wang Aug 17, 2026
05cd708
feat: tail replay limit, replayMeta, and load_earlier pagination
william0wang Aug 17, 2026
cdf270e
docs: remote client tail replay contract
william0wang Aug 17, 2026
5b79b6c
docs: frontend replay guide and cross-lin
william0wang Aug 17, 2026
2290de0
docs: add plan quota usage stats API proposal
william0wang Aug 17, 2026
eeb1bac
fix: treat non-advertised slash prompts as plain text
william0wang Aug 17, 2026
1b09d1c
feat: account/usage_stats plan quota API for remote clients
william0wang Aug 17, 2026
ed1fd20
feat: hub version handshake — stale hub self-exits and respawns on up…
william0wang Aug 17, 2026
846f9b4
feat: account/usage_stats combined GLM and Opencode Go quota
william0wang Aug 17, 2026
dae9131
feat: on-demand liveness probe for hub instance discovery
william0wang Aug 17, 2026
2e91115
fix: strip harness system-reminder blocks from replayed user messages
william0wang Aug 17, 2026
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
37 changes: 27 additions & 10 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,15 +9,15 @@ methods and streams events back as ACP `session/update` notifications.

## Commands

| Task | Command |
|------|---------|
| Build | `pnpm build` |
| Typecheck | `pnpm typecheck` |
| Test (all) | `pnpm test` |
| Test (single file) | `npx vitest run tests/<file>.test.ts` |
| Lint | `pnpm lint` |
| Format (changed files only) | `pnpm prettier --write <path>` |
| Smoke test | `pnpm smoke` |
| Task | Command |
| --------------------------- | ------------------------------------- |
| Build | `pnpm build` |
| Typecheck | `pnpm typecheck` |
| Test (all) | `pnpm test` |
| Test (single file) | `npx vitest run tests/<file>.test.ts` |
| Lint | `pnpm lint` |
| Format (changed files only) | `pnpm prettier --write <path>` |
| Smoke test | `pnpm smoke` |

**Package manager**: pnpm. **Node**: >=22. **Module system**: ESM (`"type": "module"`).

Expand All @@ -35,6 +35,7 @@ src/
├── handlers/ ACP method handlers
│ ├── session.ts session/new, session/prompt (turn loop), load, resume
│ ├── slash.ts Slash-command interception (/compact, /mcp, etc.)
│ ├── account.ts account/usage_stats — plan quota for remote clients
│ ├── extensions.ts ZCode extensions (fork, rewind, compact, steer, …)
│ ├── dispatch.ts InternalEvent → ACP session/update dispatch
│ ├── io.ts Client notification helpers
Expand All @@ -51,8 +52,15 @@ src/
│ ├── projection-differ.ts Snapshot diff for turn-completion reconciliation
│ └── tool-helpers.ts Diff builder, location extractor
├── interaction/ Permission, ExitPlanMode, AskUserQuestion handling
├── remote/ Remote access (opt-in via ZCODE_ACP_REMOTE=1)
│ ├── broadcast.ts ClientRegistry + broadcast proxy (notify fan-out, request first-wins)
│ ├── config.ts ENV parsing (gate, mandatory token, hub/bridge ports)
│ ├── endpoint.ts Loopback ACP endpoint + hub registration heartbeat
│ └── hub-server.ts zcode-acp-hub: auth, discovery, byte-level WS proxy, ?probe=1 liveness
├── quota/ GLM Coding Plan usage API client (/quota command)
└── bin/quota.ts Standalone zcode-quota CLI
└── bin/
├── hub.ts Standalone zcode-acp-hub daemon entry
└── quota.ts Standalone zcode-quota CLI
```

**Key boundary**: `backend/` talks to the ZCode subprocess. `handlers/` talks to
Expand Down Expand Up @@ -85,6 +93,15 @@ ZCode protocol types into ACP notifications directly — always translate.
`withPreemptLock`. Don't bypass it — two simultaneous turns corrupt the listener.
- **AGENTS.md is workspace-scoped**: the global `~/.zcode/AGENTS.md` also exists;
this file takes precedence for this repo.
- **WS proxy frame type**: the SDK's WS server drops non-text frames, and
`ws.send(buffer)` defaults to a BINARY frame. The hub proxy must forward with
`{ binary: isBinary }` — losing the flag silently eats every proxied message.
- **Broadcast loser promises settle late**: after first-response-wins, aborted
loser requests resolve/reject only when the peer answers the cancellation.
Every raced promise needs a no-op `.catch` or Node crashes on
unhandledRejection. See `src/remote/broadcast.ts`.
- **Remote failures never touch stdio**: any remote-side failure (port, hub,
token) must warn and disable remote only — the editor link stays up.

## Docs to read before sensitive changes

Expand Down
61 changes: 61 additions & 0 deletions CONTEXT.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
# zcode-acp-server

A bridge that connects the ZCode agent backend to ACP-compatible editors. The
bridge process is the session authority; editors and remote clients attach to
it.

## Language

**Bridge**:
A running zcode-acp-server process owning one ZCode backend subprocess, the
session registry, and the turn loops. One editor connection = one bridge.
_Avoid_: server (ambiguous with the ACP agent role), hub

**Primary Client**:
The editor connection over stdio that spawned the bridge and owns its
lifetime (Zed, JetBrains). When it disconnects, the bridge exits.
_Avoid_: host, master client

**Remote Client**:
Any additional ACP client attached over the network to watch and drive the
same sessions as the Primary Client.
_Avoid_: secondary client, web client (the web UI is just one kind)

**Session Authority**:
The property that session state (id mappings, turn loops, pending
interactions) lives inside the Bridge process, not in any client or external
store.
_Avoid_: session owner, session store

**Broadcast**:
Delivering every agent-originated notification to all attached clients
(Primary + Remote), and delivering interaction requests to all of them with
first-response-wins semantics.
_Avoid_: fan-out (fine informally, but Broadcast is the canonical term)

**Hub**:
The machine-level singleton daemon that is the only public entry point for
remote access. It does token auth, instance discovery, and byte-level
WebSocket proxying — it holds no session state and understands no ACP.
_Avoid_: gateway, broker

**Instance**:
One registered bridge as seen through the Hub. A remote connection binds to
exactly one instance; instance switching means reconnecting.
_Avoid_: agent, server, worker

**Replay**:
Delivering a session's stored history to an attaching client as session/update
notifications. Serves both the initial attach and reconnect catch-up.
_Avoid_: history sync, restore, backfill

**Turn**:
One span of session history from a user message up to (not including) the next
user message. The alignment unit for replay cuts — a replay never starts
mid-turn. Leading non-user messages belong to the first turn.
_Avoid_: round, exchange, message (a turn contains many messages)

**Cursor**:
An opaque handle identifying the oldest replayed Turn, used to page further
back into history. Valid only while the history it points into is unchanged.
_Avoid_: token (collides with the auth token), offset, bookmark
Loading
Loading