WebChat - Transform CLI into a Conversation
WebChat makes any agent CLI available through a chat-style interface that users can reach from their browser.
What WebChat Delivers
Conversational UI
Stream CLI input/output through a WhatsApp-inspired interface with bubbles, timestamps, and typing indicators.
Router Login
WebChat uses the router login flow, so access follows the same local or SSO session model as other authenticated router surfaces.
Agent Ready
When a static workspace agent is configured, WebChat automatically connects to that agentβs manifest cli command.
Access
Use ploinky webchat to print the current access URL. The command reports the router login URL; --rotate is accepted for compatibility but WebChat does not use a surface-specific token.
ploinky webchat # show the router login URL
ploinky webchat --rotate # show the same login URL
http://127.0.0.1:8080/webchat
How Commands Are Chosen
WebChat derives its runtime command from the static workspace agent:
- Static agent β Configure one with
ploinky start <agent> <port>. The router records the agentβs manifest path. - Manifest CLI β When the agentβs
manifest.jsondefines aclicommand, WebChat runs it inside the agent container. - No CLI? β Sessions still open, but the chat will display a notice that no command is bound.
This flow depends entirely on the static workspace agent: keep its manifest updated to control which CLI the chat session runs.
URL Parameters and CLI Arguments
WebChat accepts arbitrary query parameters in its URL. When an explicit agent query parameter is present, the router keeps the remaining query parameters attached to the WebChat session and forwards them to that agent's CLI command as long-form CLI flags encoded as single tokens.
/webchat?agent=achilles-cli&path=/absolute/path
# launches the selected agent as:
ploinky cli achilles-cli --path=/absolute/path
The router reserves agent for agent selection, tabId for the browser client, and sessionId for folder-history routing. These internal values are not forwarded as agent CLI flags. Agent CLIs remain responsible for interpreting ordinary forwarded flags and values.
Session Experience
Folder Sessions
WebChat stores its selectable continuation history under <cwd>/.copilot_history/. The folder contains current_session.json plus one JSON file per conversation. Refreshing the page reuses the selected folder session and a surviving agent process, while the browser keeps its client-only tabId in sessionStorage.
Each submitted turn is stored as the user message followed immediately by an assistant placeholder.
The placeholder starts with empty final text and an optional progress array of strings. Task references are separate conversation items rather than properties of the assistant message.
Agent progress reasons are appended to that array while the request is running, and final output updates the same message.
Tool names, step indexes, and other progress-envelope metadata are not stored.
WebChat removes transport echoes such as its structured input envelope and an explicit
you> submitted text readline prompt before storing or displaying output.
Ordinary assistant text remains valid when it happens to equal the submitted user message.
- Click to load session history appears as a centered standalone button inside the scrollable conversation, rather than as a chat message, and renders the current session only when requested. It is browser-only, is never stored as a message or sent to the agent, and disappears immediately when activated; the composer remains available while older messages are hidden.
- Sessions opens the session selector. Its first, emphasized New item creates an empty session and makes it current for the folder.
- Existing session items show the first user-message preview and relative activity time, such as
2 days ago, without message counts or agent metadata. - The Tasks and Sessions header controls use a darker green hover state that stays visually integrated with the header.
- Connected browser clients using the same working directory receive session changes and new turns through the shared EventSource runtime.
If the original process no longer exists, the next conversational message carries the prior user content and final assistant text in a delimited context block. Historical inputs are not replayed as commands. Persisted progress and assistant placeholders without final text remain UI-only and are excluded from that context. Slash commands remain unchanged and defer restoration until a later conversational message.
A newly started CLI receives PLOINKY_WEBCHAT_HAS_HISTORY. A value of 1 tells a generic agent that this is an existing conversation, so it should not emit new-conversation startup content. Empty sessions, including those created through Sessions β New, receive 0. The selected folder session id remains private to the router instead of being forwarded into the agent process environment.
Runtime Model Status
A compatible CLI may publish a generic __webchatRuntimeState line envelope containing its currently selected model. WebChat removes that control line from assistant output and history, keeps the latest value only in the live runtime, and shows a compact model badge beside the agent name. The runtime-state snapshot is sent again after an EventSource reconnect. A null model hides the badge.
The badge describes agent-declared configuration, not the effective provider model used for an individual answer. WebChat does not read agent settings files, persist the badge in folder history, or attach model metadata to messages.
Background Tasks
CLIs may publish generic asynchronous task lifecycle envelopes through their stdout stream. WebChat stores task metadata in .copilot_history/agent_tasks, keeps logs in .copilot_history/task_logs/, and presents both through the authenticated Tasks overlay. Logs are bounded by default, while tools may opt into full retention for multi-turn task transcripts. Conversation history stores only a small task-reference item shaped as { "type": "task", "taskId": "task_..." }; task state and logs are not copied there.
Every started task appears immediately as its own compact incoming-style chat item. One assistant turn may create multiple task items, kept after its assistant message in task-start order even when final assistant text arrives later. The first row shows the exact agent id, description, current status, and elapsed seconds; the second row provides a View live logs link instead of inline expansion. The link opens an authenticated task page in WebChat's right-side panel with the same task header, error, and live-log presentation as the former expanded module. The parent WebChat page forwards updates for the active task from its existing EventSource through a same-origin message bridge, while the task page uses the task APIs for initial state and missing-offset recovery. It does not open a second live stream. The item survives completion and history reload. Old assistant messages carrying a taskId property are ignored instead of migrated. Pending tasks show QUEUED, active tasks show RUNNING, and terminal tasks show COMPLETED, STOPPED, or FAILED.
The right-side panel is generic for task pages and all other links opened from chat. It ends above the floating composer, follows the composer's dynamic height, and scrolls its own content or embedded page in the remaining space, so no part of the opened page is hidden underneath the input pill.
Asynchronous AgentServer tools keep stdout as their final result channel and stream stderr into the bounded live task log. Structured legacy wrapper results expose only outputText, so project paths, model fields, and success metadata do not appear in task results. WebChat persists only the supplied live tail and never appends the terminal MCP result to it. Providers that want a final answer in the task item emit it through their live output before completion. The browser strips ANSI control sequences and retains display compatibility for historical prefixed logs; new raw provider output is otherwise unchanged.
Closing the browser tab does not stop an ongoing delegated task. WebChat retains the watcher runtime while it owns ongoing work so polling and log capture continue. The runtime returns to the normal reconnect cleanup policy after its tasks reach finished, stopped, or error. A recreated compatible CLI can reattach by target agent and remote task id.
A completed task may advertise a generic continuation capability. Its task page then shows a message field that resumes the provider session through the stored target agent, tool name, and opaque handle. Enter sends the message; Ctrl/Cmd+Enter or Shift+Enter inserts a newline. The field cannot be resized manually: it grows upward with its content to the same bounded height as the main composer, then becomes scrollable, and shrinks again when content is removed. Every message starts a new remote AgentServer execution, but the WebChat taskId and original title remain unchanged; only the task's turn increases. The task returns to RUNNING, and new output is appended to the same full-retention log. If the stored provider is a stopped startup: manual agent, continuation activates that exact provider in global mode through Ploinky's internal lifecycle and waits for readiness before invoking it. Concurrent requests share the same activation. Continuation still uses normal authenticated MCP policy and request-bound router authorization; startup failures remain explicit and the provider is never replaced silently.
Message Rendering
- β Automatic timestamps for each exchange
- β Rich markdown rendering (tables, code blocks, lists)
- β Long output folding with βShow moreβ expansion
- β Agent progress reasons persisted as an ordered string array and displayed as a collapsible block above the final answer
- β Side panel to inspect full message history
- β Workspace-wide Tasks overlay with live bounded logs and completion notifications
Input Controls
- β Auto-resizing composer with keyboard shortcuts (Enter to send, Shift+Enter for new line)
- β Light/Dark/Explorer/Obsidian Dark theme selector with persistent preference (Explorer loads by default; switch via Settings β Appearance)
- β
Slash command autocomplete β start the composer with
/to open a fixed-height menu; later slashes remain argument text, Arrow Up/Down keeps the active item visible, and large agent-declared result sets remain fully accessible through progressively rendered scroll batches - β
Composer
@path autocomplete β typing@opens the files and folders in the working directory selected by the WebChatdirlaunch parameter, and every additional path character refreshes the filtered suggestions. Selecting a folder drills into it; typing a space after the folder ends autocomplete so the folder can be referenced directly. Selecting a file inserts a cwd-relative token such as@src/index.jswith a structured workspace reference and closes the menu, so the next Enter sends the message. - β Cancel button β appears during active processing; sends an interrupt signal to the agent
Session Uploads
WebChat stores uploaded files in a per-session directory under the current working directory: <cwd>/uploads/<webchat_sid>/. webchat_sid is the WebChat session cookie issued to the authenticated browser; it is not the browser tab id.
- The attachment menu has Upload file and Upload folder entries. Folder uploads preserve
file.webkitRelativePathas the relative path inside the session upload directory. - Composer
@suggestions search the WebChat working directory, so uploaded content appears only when its session folder is reached through normal cwd-relative folder navigation. - Uploads use
POST /webchat/uploadsand are read back viaGET /webchat/uploads?path=<session-relative-path>. Downloads reuse stored MIME metadata and setX-Content-Type-Options: nosniff. The server rejects absolute paths, traversal segments, NUL bytes, symlink escapes, and reserved.secrets/*.secretsnames.
Security Practices
- Use HTTPS or SSH tunnels when exposing the WebChat URL beyond localhost.
- Protect router session cookies and the encrypted
.ploinky/.secretsstore used by local auth, dashboard tokens, and manifest env. .copilot_historyis authenticated, workspace-confined project state but is plaintext. It receives a local.gitignoreto reduce accidental commits.
Troubleshooting
- Blank session? Verify the static agent has a
clicommand in its manifest. - 401 errors? Run
ploinky webchatagain to confirm the token and URL. - Need a different command? Update the agent manifest and restart the workspace so WebChat picks up the change.
- Custom launch flags ignored? Confirm you opened
/webchatwith?agent=<name>; without an explicit agent selection the router uses the default WebChat target and does not synthesize agent-specific CLI flags.