docs: drop ring buffer mentions from ws spec and plan

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
tao.chen
2026-07-03 14:12:35 +08:00
co-authored by Claude
parent 4a4028486c
commit 0cfe8955a4
2 changed files with 11 additions and 15 deletions
@@ -12,7 +12,7 @@
**Decisions (from spec, confirmed):** **Decisions (from spec, confirmed):**
- WebSocket library: `gorilla/websocket` - WebSocket library: `gorilla/websocket`
- Replay buffer: not in v1 (deferred; ring buffer is implemented but not exposed) - Replay buffer: not in v1 (deferred; a future task can add it behind a separate accessor)
- Reconnect: up to 3 attempts with exponential backoff on transport error; never on exit banner - Reconnect: up to 3 attempts with exponential backoff on transport error; never on exit banner
## File Structure Map ## File Structure Map
@@ -88,7 +88,7 @@
2. Modify internal/process/session.go: add Stdin io.WriteCloser, Subscribers []Subscription, Exit process.ExitInfo (Code int, Signal string), mu sync.Mutex. Use a slice, not a map. 2. Modify internal/process/session.go: add Stdin io.WriteCloser, Subscribers []Subscription, Exit process.ExitInfo (Code int, Signal string), mu sync.Mutex. Use a slice, not a map.
3. Modify internal/process/manager.go: 3. Modify internal/process/manager.go:
- In LocalManager.Start: open a pipe for cmd.Stdin, capture the write end. Open a pipe for cmd.Stdout AND cmd.Stderr, point both at the same reader, capture the reader. - In LocalManager.Start: open a pipe for cmd.Stdin, capture the write end. Open a pipe for cmd.Stdout AND cmd.Stderr, point both at the same reader, capture the reader.
- Spawn goroutine A: read from the merged pipe in a 4 KiB loop, append to a 64 KiB ring buffer (truncate oldest when full, do not expose the buffer in this task), and for each Subscription in session.Subscribers do a non-blocking send (select with default that drops the chunk). Stop when the pipe returns EOF. - Spawn goroutine A: read from the merged pipe in a 4 KiB loop, and for each Subscription in session.Subscribers do a non-blocking send (select with default that drops the chunk). Stop when the pipe returns EOF. No replay buffer in v1.
- Spawn goroutine B: call cmd.Wait(), populate session.Exit (use ProcessState.ExitCode() and Sys().(syscall.WaitStatus).Signal() if signal), then close every Subscription's channel exactly once. Do not delete the session from the map. - Spawn goroutine B: call cmd.Wait(), populate session.Exit (use ProcessState.ExitCode() and Sys().(syscall.WaitStatus).Signal() if signal), then close every Subscription's channel exactly once. Do not delete the session from the map.
- Add LocalManager.Subscribe(workspaceID) (Subscription, error): if no session, return util.New(util.CodeNotFound, "no running process"). Allocate a channel, append a subscription backed by it, return it. - Add LocalManager.Subscribe(workspaceID) (Subscription, error): if no session, return util.New(util.CodeNotFound, "no running process"). Allocate a channel, append a subscription backed by it, return it.
4. Keep existing tests compiling. New Subscribe method is additive. 4. Keep existing tests compiling. New Subscribe method is additive.
@@ -472,7 +472,7 @@ Reconnect: 1s, 2s, 4s backoff. Stop after 3 failed attempts → "error". Reset o
- Process model grows `Subscribe` and `Stdin`: Tasks 1, 2. - Process model grows `Subscribe` and `Stdin`: Tasks 1, 2.
- 409 when process not running: Task 3. - 409 when process not running: Task 3.
- Exit banner on close: Task 3. - Exit banner on close: Task 3.
- Replay buffer: explicitly deferred (Task 1 implements it but does not expose it). - Replay buffer: not in v1. The manager's reader goroutine reads and fans out directly; no history is kept.
- Frontend hook with reconnect policy: Task 4. - Frontend hook with reconnect policy: Task 4.
- TerminalPanel wired to live process: Task 5. - TerminalPanel wired to live process: Task 5.
- Backward compatibility: existing routes unchanged, Manager interface is additively grown, no wire format changes. - Backward compatibility: existing routes unchanged, Manager interface is additively grown, no wire format changes.
@@ -25,9 +25,9 @@ path.
directions are text frames carrying raw bytes (UTF-8). We do not use a directions are text frames carrying raw bytes (UTF-8). We do not use a
custom envelope or JSON framing in v1. custom envelope or JSON framing in v1.
- Backend process model gets two new responsibilities: capture child - Backend process model gets two new responsibilities: capture child
stdout/stderr into an in-memory ring buffer plus a list of subscriber stdout/stderr into a list of subscriber channels, and accept stdin
channels, and accept stdin writes. This stays inside `internal/process`; writes. This stays inside `internal/process`; the API layer is a thin
the API layer is a thin WebSocket adapter. WebSocket adapter.
- The existing `Manager` interface grows one method: `Subscribe(workspaceID) - The existing `Manager` interface grows one method: `Subscribe(workspaceID)
(Subscription, error)`. We also add an `Input` accessor on the service (Subscription, error)`. We also add an `Input` accessor on the service
layer for stdin. The surface stays small. layer for stdin. The surface stays small.
@@ -61,14 +61,13 @@ path.
Buffered to a small constant (e.g. 32) to avoid blocking the producer. Buffered to a small constant (e.g. 32) to avoid blocking the producer.
- `Close() error` — drops the subscription. Idempotent. - `Close() error` — drops the subscription. Idempotent.
- `LocalManager` keeps per-session: - `LocalManager` keeps per-session:
- a `bytes.Buffer` of recent output (cap, e.g. 64 KiB) for any future - a `map[Subscription]struct{}` of subscribers, guarded by `sync.Mutex`.
replay — implemented but **not exposed in v1**.
- a `[]chan []byte` of subscribers, guarded by `sync.Mutex`.
- a `io.WriteCloser` for stdin (created via `cmd.Stdin = ...`). - a `io.WriteCloser` for stdin (created via `cmd.Stdin = ...`).
- On `Start`, set `cmd.Stdout` and `cmd.Stderr` to a pipe. A new goroutine - On `Start`, set `cmd.Stdout` and `cmd.Stderr` to a pipe. A new goroutine
per session reads from the pipe, writes to the ring buffer, and fans out per session reads from the pipe and fans out to every subscriber channel.
to every subscriber channel. A non-blocking send (`select` with A non-blocking send (`select` with `default`) drops output for slow
`default`) drops output for slow clients. A 4 KiB read buffer is fine. clients. A 4 KiB read buffer is fine. Replay of past output is **not** in
v1; a future task can add a ring buffer behind a separate accessor.
- `Wait()` is called in a separate goroutine; on exit, the manager closes - `Wait()` is called in a separate goroutine; on exit, the manager closes
all subscriber channels and stores the exit status on the session. all subscriber channels and stores the exit status on the session.
@@ -196,9 +195,6 @@ path.
- The `Subscription` type and the `Input` writer are the two new - The `Subscription` type and the `Input` writer are the two new
capabilities the manager must grow. They are small and orthogonal. capabilities the manager must grow. They are small and orthogonal.
- The ring buffer for replay is implemented but unused in v1. This is
dead weight. **Defer**: the spec keeps it for symmetry with future
replay work. Plan may drop it if not needed.
- The exit banner format (`\r\n[process exited with code N]\r\n`) is a - The exit banner format (`\r\n[process exited with code N]\r\n`) is a
simple, debuggable string. We are not building a structured protocol. simple, debuggable string. We are not building a structured protocol.
- All v1 design decisions aim to keep the backend diff small, the - All v1 design decisions aim to keep the backend diff small, the