Commit Graph
5 Commits
Author SHA1 Message Date
pastilhasandClaude Opus 4.8 d60c73b3a3 chat: root the OpenCode server at the chat cwd (email chat now runs in the account dir)
OpenCode has no per-session `directory` — a session inherits the server's cwd (POST
/session ignores extra fields). So the previous "pass directory on create" was a no-op
and every OpenCode chat ran in the fixed server's dir (~), including the email chat.

Fix: hybrid server model. server-manager.ensureServer(cwd?, home?) returns the fixed
pm2 server (OPENCODE_SERVER_URL, :4096) for the general /chat, but for a context-scoped
cwd (email account dir, project dir) it spawns/pools an `opencode serve` rooted at that
directory — so the session's agent actually operates there. send-opencode passes the
resolved cwd + the user's home; createSession drops the ignored directory param.

Verified live: a cwd-scoped server reports the session directory as the target dir
(not ~), while /chat still uses :4096.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-24 20:20:15 +00:00
pastilhasandClaude Opus 4.8 cbe83f39a9 chat: run the /email chat from the email account's storage dir
The email chat's working directory now resolves to
DATA_PATH/<owner>/email_accounts/<accountEmail> (created if missing), so the agent
operates in the selected account's dir (emails.db, attachment_cache, …).

- websocket.ts: new resolveChatCwd — context 'email' → the account dir (via a new
  resolveEmailCwd), 'chat' → the pwd/claude_sessions dir, else the given cwd. Both the
  Claude and OpenCode handlers use it. The account defaults to the owner's first enabled
  account for now; the account selector will pass it as contextId later.
- OpenCode honors the cwd again: send-opencode passes it as the session `directory`
  (client.createSession(directory?)) for context-scoped chats; the general /chat still
  omits it and uses the fixed server's default project. Verified against the live server
  that directory-bound sessions create + list.

No frontend change — the /email panel already sends context:'email'.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-24 17:37:48 +00:00
pastilhasandClaude Opus 4.8 7b16f3bc4c chat: point OpenCode at a fixed pm2-managed server (fixes empty model picker)
The per-cwd `opencode serve` spawning is replaced by a single fixed server
(http://127.0.0.1:4096, OPENCODE_SERVER_URL) managed by pm2 — added as
`officer-opencode` in ecosystem.config.cjs (cwd = home).

Root-cause fix for the empty model selector: list-models shelled out to
`opencode models`, which failed at runtime on the deployed server (the picker got
only Claude tiers). It now reads the fixed server's GET /config/providers over HTTP —
11ms and reliable — so the curated OpenCode models (Big Pickle, Claude Haiku) show up.

- server-manager.ts — drops spawning; exposes OPENCODE_SERVER_URL + a health check.
- client.ts — createSession no longer binds a directory (sessions live in the one
  server's project).
- send-opencode.ts / opencode-sessions.ts / chat.ts — use the fixed server; drop the
  cwd/home plumbing. Session list/load/delete/rename now hit :4096.

Verified end-to-end against the live server: model list, streaming turn, session
list, and transcript load all work with no spawning.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-24 17:12:53 +00:00
pastilhasandClaude Opus 4.8 059539a64a chat: unified session history across Claude + OpenCode (Phase 3)
/chat's session list, transcript load, delete, and rename now span both harnesses.

- opencode-sessions.ts — REST-backed reader (OpenCode's SQLite via its HTTP API, never
  the DB): listOpenCodeSessions / loadOpenCodeSession / delete / rename, returning the
  same shapes as the Claude reader, tagged harness:'opencode'. A serve is directory-
  scoped, so listing a cwd = asking the serve rooted there. Transcript rebuild maps
  user/assistant/tool parts and drops reasoning (parity with the delta filter).
- client.ts — adds listSessions/getMessages/deleteSession/renameSession over /session/*.
- chat.ts — /sessions merges both (newest first); /sessions/:id, DELETE, and
  /title route by id shape (ses_ = OpenCode). ClaudeSessionSummary gains an optional
  `harness` tag.
- send-opencode.ts — resuming from history: when the sessionKey is itself a ses_ id,
  reuse that OpenCode session instead of creating a new one.
- SessionList.tsx — shows an "OpenCode" badge for OpenCode sessions.

Verified end-to-end against a live serve: list (5 sessions, tagged), load (transcript
rebuilt, reasoning filtered), and rename all work. Phases 1-3 complete; needs a restart.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-24 16:56:20 +00:00
pastilhasandClaude Opus 4.8 ad32c7516e chat: add OpenCode as a second harness — live turn (Phase 1)
Introduces an OpenCode chat harness alongside Claude, driven over HTTP + SSE against
a persistent `opencode serve`, emitting the same ChatEvent contract so the entire
chat UI and createEventHandler pipeline are unchanged.

New servers/api/chat/opencode/:
- server-manager.ts — one warm `opencode serve` per cwd (free port, health-gated,
  respawn on exit; HOME set so it reads the user's ~/.local/share/opencode auth).
  Binary pinned via OPENCODE_BIN (installed is 1.17.9; the 1.18.4 upgrade never landed).
- client.ts — per-server HTTP calls (/session create, /message, /abort) + a single
  reconnecting `/event` SSE stream demuxed to per-session listeners.
- event-mapper.ts — SSE → ChatEvent. Verified live against 1.17.9: message.part.delta
  → delta, tool parts → tool:start/tool:result, message.updated → cost, session.idle
  → result. Crucially, deltas are gated on partID being a `text` part (declared before
  its deltas) so the model's reasoning — which also streams as field:'text' — is
  dropped, matching the Claude harness hiding thinking.
- state.ts — sessionKey ↔ opencode ses_ id map for resume.

channels/send-opencode.ts — the OpenCode analog of send-claude-code: ensure serve,
create/reuse session, subscribe, post the message, forward mapped events; kill = abort.

websocket.ts — replaces the Claude-only coercion with harness routing:
provider 'claude-code' → Claude sidecar, everything else → handleOpenCodeChat.
handleStop aborts the right harness.

Verified end-to-end (streaming text, tool call/result, cost, abort) against a
throwaway serve using the free deepseek model — no prod restart involved. UI-level
model selection + session history follow in Phases 2–3.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-24 16:29:55 +00:00