Skip to content

Desktop app offers Pi but shows an empty chat for Pi sessions #839

Description

@alexeyzimarev

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:

  1. 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".
  2. 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").
  3. 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).

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions