The agent-logs WebSocket proxy injected the agent token raw into the upstream
query string (?token=<token>). Tokens containing base64/url-special characters
(+, /, =) were then mangled by the query parser on the agent side (e.g. "+"
decoded to a space), so the agent rejected the stream with close code 4401 even
though the same token works for the HTTP API (where it travels in the
Authorization header). URL-encode the token with urllib.parse.quote so it
round-trips intact.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Remote-stack log streaming showed only "disconnected, 0 lines" whenever the
agent log proxy failed, because the LogViewer ignored type:"error" messages and
the proxy swallowed connection errors.
- ws.py: the agent-logs proxy now reports a clear, logged reason on failure —
distinguishes "cannot reach agent <url>" from a handshake rejection (HTTP 404
hints the agent is outdated and lacks live-log support) and forwards abnormal
upstream close codes (e.g. 4401 bad agent token).
- LogViewer: renders type:"error" messages (red) and surfaces a 4401 close as an
authorization error, instead of silently showing "Waiting for log output…".
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- Live remote-stack logs over a WebSocket proxied through the central app to
the agent (/ws/agent-logs/{agent}/{stack}); agent gains a WS log endpoint.
- Deploy to a remote host from the UI: host selector in the New Stack editor
and template dialog; templates instantiate onto an agent via the proxy.
- Network attach/detach: expandable inspect view per network with
connect/disconnect + container picker; GET /{id}/containers, POST connect/disconnect.
- Remove dead pages/Placeholder.tsx.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>