The PTY was hardcoded to 80x24 at start, so full-screen programs
(htop, vim, less, tmux, top) drew themselves for 80x24 regardless
of the actual xterm window. Add a path for the frontend to push the
real cols/rows to the backend, which calls pty.Setsize on the pty
file.
Backend:
- internal/shell/manager.go: Resize(workspaceID, cols, rows) added to
the Manager interface and implemented on LocalManager. Looks up the
session, type-asserts sess.Stdin.(*os.File), calls pty.Setsize.
Validates cols/rows in [1, 10000] and returns CodeBadRequest on
bad input, CodeNotFound when no session.
- internal/service/shell_service.go: ShellService.Resize wrapper that
checks workspace existence first.
- internal/api/shell_handler.go: resize handler, 204 on success.
- internal/api/router.go: register POST /workspaces/:id/shell/resize.
- internal/model/shell.go: ShellResizeRequest{Cols, Rows}.
- internal/shell/manager_test.go: 3 new tests (valid resize via
pty.Getsize round-trip, invalid size, missing session).
Frontend:
- web/src/lib/api/process.ts: useShellResize mutation hook.
- web/src/components/terminal/TerminalPanel.tsx: terminal.onResize
subscription with 100ms debounce; filters 0x0; only fires when
workspaceId is set; cleanup clears timeout + disposes listener.
E2E: 'stty size' after POST /shell/resize {cols:200, rows:50} returns
'50 200'. 0x40 → 400, missing workspace → 404, valid → 204.
Conversation: 019f360a-5eba-7c81-94d3-e5d58ad3c026
The opencode acp binary streams agent_message_chunk notifications with
an EMPTY messageId. The backend was already correct (it groups when a
messageId is present), but the UI rendered every chunk as its own
bubble, so a single 'count 1 to 5' reply showed as 9 separate bubbles.
Backend:
- internal/acp/history.go: add MessageID to Message; new AppendOrCreate
method that finds or creates an entry by messageId and appends text
to it (preserving the original timestamp).
- internal/acp/client.go: agent_message_chunk and user_message_chunk
use AppendOrCreate.
- internal/model/acp.go: expose MessageID in the wire response.
- internal/service/acp_service.go: copy MessageID into the API DTO.
- internal/acp/client_test.go: add TestHistoryAppendOrCreateGroupsByMessageID.
Frontend:
- web/src/components/acp/groupMessages.ts: new pure helper that
coalesces consecutive same-role history entries into a single bubble
(keeps the first chunk's time + messageId).
- web/src/components/acp/AcpPanel.tsx: render coalesced messages;
auto-scroll effect now depends on coalesced so it fires as the
agent streams.
Verified via e2e: 'count from 1 to 5' now returns a single agent entry
with text '1\n2\n3\n4\n5' instead of 9 separate entries.
- terminal: on workspaceId change call terminal.clear() and
terminal.reset() so the previous workspace's scrollback is wiped
before the new shell's output appears.
- terminal: set macOptionIsMeta: false. With the default (true),
Mac Option+key sequences (e.g. Option+[ emits ESC) leak a literal
0x1b into the shell's stdin, which crashes downstream REPLs like
Python's with "invalid non-printable character U+001B".
- web: add react-markdown + remark-gfm + react-syntax-highlighter
- web: new lib/api/acp.ts (useAcpStatus/History/Prompt/Cancel)
- web: new components/acp/ (AcpPanel + MessageBubble + MarkdownView +
ThinkingIndicator). Markdown rendering with code highlighting via
Prism oneDark. Auto-scroll to bottom when near it; error toasts
fade after 5s.
- web: WorkspaceShell now renders a tabbed bottom panel (Terminal |
Agent) when the bottom panel is visible. Tab state in
workspace-ui-store.
- web: StatusBar shows agent status (ready / initializing / error)
when the ACP session is initialized.
- web: pnpm-workspace.yaml: add packages ['.'] so pnpm install works
in this workspace.
Conversation: 019f3562-9382-7e00-ad92-3162341e9274