7 Commits
Author SHA1 Message Date
tao.chen 5e694aec12 refactor(process): sync.RWMutex for sessions + sync.Map for exited
Apply the suggested optimization on top of the previous race fix.

- internal/process/manager.go: LocalManager.sessionsMu is now an
  RWMutex. Read paths (Status, Subscribe lookup, Stdin lookup,
  ExitStatus lookup) take RLock; mutating paths (Start, Stop,
  Restart) take Lock. exited is now a sync.Map instead of a
  hand-rolled map+mutex; reads (IsExited) are lock-free, writes
  (MarkAsExited from waitExit, ClearExited on Start/Stop/Restart)
  are atomic. Added IsExited / MarkAsExited / ClearExited helpers.

No behavior change. The previous race fix (the only one in the
package) is preserved: waitExit remains the sole caller of
sess.Cmd.Wait(), and all other code paths read the exited marker
rather than cmd.ProcessState.

- ACP: nothing to do. The ACP service talks to opencode through
  process.Manager; it never reads cmd.ProcessState directly. The
  process package race fix already covers the ACP code path.
  Verified: go test -race -count=2 ./... clean across the repo.

Note: at this app's concurrency level (tens of workspaces, accessed
occasionally), neither sync.RWMutex nor sync.Map measurably beats
the previous map+mutex pair — the critical sections are O(1)
lookups with no contention. The change is mostly stylistic
(removes one lock, fewer deadlock surfaces, more idiomatic Go).

E2E: start / status(running) / duplicate(409) / stop / status(stopped)
/ restart / status(running) / stop all behave correctly.
2026-07-06 15:59:36 +08:00
tao.chen 568423751f fix(process): remove data race on cmd.ProcessState (was read across goroutines)
The race: os/exec writes cmd.ProcessState inside cmd.Wait(), which
runs in the waitExit goroutine. Start, Status, Subscribe, and
Restart all read cmd.ProcessState from other goroutines to decide
'still running?'. That's a textbook Go data race — fixed by
TestStartRejectsDuplicateRunningSession under -race.

Fix: port the same pattern the shell package uses for its multi-shell
refactor. The manager now owns an exited map[string]struct{} guarded
by exitedMu. waitExit is the ONLY place that calls cmd.Wait(); after
Wait() returns it records exited[workspaceID]. Everyone else checks
the exited map instead of reading cmd.ProcessState.

- internal/process/manager.go: add exited + exitedMu to LocalManager;
  Start conflict check, Status, Subscribe, Restart, Stop all consult
  the exited map; Start clears the entry on new process; Stop and
  Restart delete the entry on stop; waitExit sets it after Wait().
- go.mod / go.sum: tidied (uuid v1.6.0 was already direct dep from
  the shell refactor).

go test -race -count=3 ./...  clean across the whole repo.
go test -race -count=5 ./internal/process/...  clean.
2026-07-06 15:52:07 +08:00
tao.chen 2597d0d60c feat(backend): add opencode ACP (Agent Client Protocol) client
Adds a minimal but real ACP stack for the opencode process:

- pkg/config: process.args default ["acp"] (opencodeCommand still "opencode")
- internal/process: NewManager(command, args) — exec.Command uses args
- internal/acp (new): NDJSON transport + JSON-RPC client over the existing
  process stdio. Implements initialize / session/new / session/prompt /
  session/cancel. Serves fs/read_text_file and fs/write_text_file from the
  workspace's fs.FileSystem. terminal/* requests get MethodNotFound.
- internal/service/acp_service: per-workspace Client + mutex; starts the
  process on first prompt; transparently re-init on restart.
- internal/api/acp_handler: GET /acp/status, GET /acp/history,
  POST /acp/prompt, POST /acp/cancel.
- internal/model/acp: API DTOs.
- internal/acp/client_test: NDJSON split-lines, request/response correlation,
  notification dispatch, agent-initiated request handling (fs + terminal).

Existing process WS endpoint and Shell subsystem are unchanged.

Conversation: 019f354c-a51b-7ec3-83ad-c647e9b50b19
2026-07-06 11:03:59 +08:00
tao.chenandClaude 4a4028486c refactor(process): use map for subscribers and drop dead ring buffer
Co-Authored-By: Claude <noreply@anthropic.com>
2026-07-03 14:08:20 +08:00
tao.chenandClaude 11ccdda579 feat(service): expose subscribe, input, exit status for ws handler
Co-Authored-By: Claude <noreply@anthropic.com>
2026-07-03 12:10:50 +08:00
tao.chenandClaude 13a5aeda2c feat(process): capture stdout/stderr and fan out to subscribers
Co-Authored-By: Claude <noreply@anthropic.com>
2026-07-03 12:05:40 +08:00
tao.chenandClaude Fable 5 411ed1f8ba feat: scaffold codespace backend
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-02 15:07:54 +08:00