Symptom
The desktop app lists Pi as an available harness and launches it fine. The session then appears in the sidebar, but clicking it opens a workspace with no chat: a blank pane and the tab-strip note "This session has no terminal." The same session in the web UI shows the full conversation.
Seen on 2026-09-09 with kcap 0.11.40 / Pi 0.85.1, hosted Pi agents launched by the daemon.
Root cause
The app never had a chat path for Pi. Three independent gates, each enough on its own:
- Chat is only built for PTY vendors.
WorkspaceViewModel.cs:134 constructs ChatTabViewModel on the first status DTO that passes HostedHarnessCatalog.ShowsTerminal. PiRpcHostedAgentRuntime.EmitsTerminalOutput is false and the catalog classes "pi" as "rpc", so Chat stays null, ChatTabView is never shown, and there is no banner template for the no-terminal case, hence the blank pane. WorkspaceViewModelTests.cs:75-87 pins exactly this behaviour for vendor "pi".
- No local Pi projection or chat rules.
TranscriptChat.For (src/Capacitor.Cli.Core/TranscriptChat.cs:29) and TranscriptProjection.For know only claude and codex; src/Capacitor.Models.Transcripts/Harness/ has only Claude/ and Codex/. Pi's canonical events are produced server-side by PiTranscriptNormalizer. With a null projection the tab would sit in ChatTabPhase.Unavailable ("No chat view for this harness").
- Nothing to tail. The app's chat is a local JSONL tailer over
AgentStatusDto.TranscriptPath (ChatTabViewModel.SwitchPath). A hosted Pi session has no transcript file the app could find: the runtime streams AcpEventEnvelopes from the daemon to the server over the AcpSessionEvents hub (AgentOrchestrator.StartAcpForwardingAsync), PiLaunchEnvironment sets KCAP_PI_PURE=1 so the local extension stands down, and AgentOrchestrator.DetectSessionIdAsync has locators for claude and codex only, so the DTO carries no path.
The web works because it reads the server's canonical events, the only place a hosted Pi transcript exists. The app never consumes the daemon's envelope stream.
Scope
Not Pi-specific. Every non-PTY vendor the app offers (cursor, copilot, gemini, kiro, opencode, antigravity, pi) renders the same empty workspace. Offering a harness the workspace cannot display is the user-facing bug; the missing chat path is the cause.
Fix directions
- Preferred: have the app consume the daemon's
AcpEventEnvelope stream over local IPC for non-PTY sessions, and relax the ShowsTerminal gate in WorkspaceViewModel so a chat tab is built without a terminal. That matches how these runtimes actually produce their transcript.
- Alternative: add a Pi leaf projection under
Capacitor.Models.Transcripts/Harness/Pi/ mirroring the server normalizer, PiChatRules under Capacitor.Cli.Core/Harness/Pi/, and a Pi arm in DetectSessionIdAsync so a file exists to tail. This only covers Pi and duplicates the server's normalizer.
- Until either lands, the app should not offer harnesses whose sessions it cannot display, or the workspace should say so and deep-link to the web view the way remote rows already do (
RailSessionViewModel.cs:67).
Symptom
The desktop app lists Pi as an available harness and launches it fine. The session then appears in the sidebar, but clicking it opens a workspace with no chat: a blank pane and the tab-strip note "This session has no terminal." The same session in the web UI shows the full conversation.
Seen on 2026-09-09 with kcap 0.11.40 / Pi 0.85.1, hosted Pi agents launched by the daemon.
Root cause
The app never had a chat path for Pi. Three independent gates, each enough on its own:
WorkspaceViewModel.cs:134constructsChatTabViewModelon the first status DTO that passesHostedHarnessCatalog.ShowsTerminal.PiRpcHostedAgentRuntime.EmitsTerminalOutputisfalseand the catalog classes"pi"as"rpc", soChatstays null,ChatTabViewis never shown, and there is no banner template for the no-terminal case, hence the blank pane.WorkspaceViewModelTests.cs:75-87pins exactly this behaviour for vendor"pi".TranscriptChat.For(src/Capacitor.Cli.Core/TranscriptChat.cs:29) andTranscriptProjection.Forknow onlyclaudeandcodex;src/Capacitor.Models.Transcripts/Harness/has onlyClaude/andCodex/. Pi's canonical events are produced server-side byPiTranscriptNormalizer. With a null projection the tab would sit inChatTabPhase.Unavailable("No chat view for this harness").AgentStatusDto.TranscriptPath(ChatTabViewModel.SwitchPath). A hosted Pi session has no transcript file the app could find: the runtime streamsAcpEventEnvelopes from the daemon to the server over theAcpSessionEventshub (AgentOrchestrator.StartAcpForwardingAsync),PiLaunchEnvironmentsetsKCAP_PI_PURE=1so the local extension stands down, andAgentOrchestrator.DetectSessionIdAsynchas locators forclaudeandcodexonly, so the DTO carries no path.The web works because it reads the server's canonical events, the only place a hosted Pi transcript exists. The app never consumes the daemon's envelope stream.
Scope
Not Pi-specific. Every non-PTY vendor the app offers (cursor, copilot, gemini, kiro, opencode, antigravity, pi) renders the same empty workspace. Offering a harness the workspace cannot display is the user-facing bug; the missing chat path is the cause.
Fix directions
AcpEventEnvelopestream over local IPC for non-PTY sessions, and relax theShowsTerminalgate inWorkspaceViewModelso a chat tab is built without a terminal. That matches how these runtimes actually produce their transcript.Capacitor.Models.Transcripts/Harness/Pi/mirroring the server normalizer,PiChatRulesunderCapacitor.Cli.Core/Harness/Pi/, and a Pi arm inDetectSessionIdAsyncso a file exists to tail. This only covers Pi and duplicates the server's normalizer.RailSessionViewModel.cs:67).