Skip to content

Open a remote session's chat and terminal in the desktop app - #951

Merged
alexeyzimarev merged 43 commits into
mainfrom
desktop-remote-workspace-slice3
Sep 15, 2026
Merged

alexeyzimarev merged 43 commits into
mainfrom
desktop-remote-workspace-slice3

Conversation

@alexeyzimarev

Copy link
Copy Markdown
Member

Closes #806 — AI-2554

What & why

A remote session in the desktop app's server list needs a workspace behind it. This adds one with a Chat tab and a read-only Terminal tab sourced from the server rather than the local daemon: the transcript is seeded from the session detail endpoint and tailed over the Eventuous stream subscription, prompts and special keys reach the agent through the hub, server-queued prompts show as queue rows, and the terminal replays the agent's PTY output and then follows it live. A session that moves between a local daemon and the server rebinds in place instead of reading as ended. The server-side items 1 and 2 of AI-2537 stay open.

Where to look

RemoteTerminalViewModel reports the viewport only while the Terminal tab is showing and releases it when the tab hides: the server takes the minimum size across viewers, so an unmeasured hidden pane would clamp the agent's PTY for everyone. MainWindowViewModel.Rebind demands the session-id proof in both directions; a registry row without one keeps the workspace it has.

Verification

  • dotnet build Capacitor.slnx: 0 warnings, on the branch and on the branch merged with current main.
  • App suite 1988/1988 (2006/2006 merged with main), Remote.Models 10/10, Cli.Core 3288 + 9 skipped, Transcripts 144/144, Integration 286/286, Cli 4076 + 19 skipped, Daemon 3194 + 38 skipped with one pre-existing environmental failure (installed Codex 0.154.0 against the vendored schema pin).
  • dotnet publish src/Capacitor.Cli/Capacitor.Cli.csproj -c Release: no IL2026/IL3050 warnings.
  • Not yet done: a live smoke check against a real server.

🤖 Generated with Claude Code

alexeyzimarev and others added 30 commits September 15, 2026 08:47
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…#806)

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…ap (#806)

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
)

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…pts (#806)

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
The server clamps the source PTY to the smallest viewer, so an off-screen pane reporting its unmeasured constructor size shrinks the agent for every viewer. A release is sent only for a viewport that was reported, and a superseded subscribe leaves the group alone while a newer attach is receiving on the same connection.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
The access banner occupies its own row above the panes, so an Offline lease reads over the transcript it already has rather than blanking it. A refusal still replaces the panes with the note: Denied, a session that has not started, and an agent that moved to this machine all hide.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Every hub refusal of the tail is terminal for the run, not just the not-authorized one: a stream method the hub does not have would otherwise be retried on the ladder forever behind an unexplained empty pane.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
alexeyzimarev and others added 3 commits September 15, 2026 14:49
Only the host-stop test passes one, a single immediate retry, so its bound measures the tail ending rather than the client's default 0/2/10/30s wait. Every other lane keeps that default.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
A watch that fires as it is armed swaps the workspace and arms the next one while the outer arming is still returning, so the arming that stands must be the one whose workspace is open.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
The CLI publishes NativeAOT and references them, so the analysers belong where a reflective serializer path would be written rather than only where it is linked.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Sep 15, 2026 •

Copy link
Copy Markdown

Codex Review Summary

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
📝 Code Review ✅ Completed 2026-09-15T13:35:43.938397Z f11c6c0 PR opened
ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

@qodo-code-review

Copy link
Copy Markdown

PR Summary by Qodo

Open remote sessions with live chat and terminal workspaces

✨ Enhancement 🧪 Tests 📝 Documentation ⚙️ Configuration changes 🕐 40+ Minutes

Grey Divider

AI Description

• Opens remote sessions with server-backed chat and read-only terminal tabs.
• Resumes transcripts, queued prompts, terminal output, and authorized input across reconnects.
• Rebinds open workspaces when sessions move between local and remote daemons.
Diagram

sequenceDiagram
    actor User
    participant Workspace as Remote Workspace
    participant Feed as Transcript Feed
    participant Lane as Server Lane
    participant Server as Remote Server
    participant Agent as Remote Agent
    User->>Workspace: Open session
    Workspace->>Feed: Start chat
    Feed->>Lane: Request seed and tail
    Lane->>Server: Fetch detail and subscribe
    Server-->>Lane: Seed and live events
    Lane-->>Feed: Projected event source
    Feed-->>Workspace: Chat rows
    User->>Workspace: Send prompt or key
    Workspace->>Lane: Hub input
    Lane->>Server: Forward input
    Server->>Agent: Deliver input
    Agent-->>Server: PTY and queue updates
    Server-->>Lane: Terminal and prompts
    Lane-->>Workspace: Refresh active panes
Loading
High-Level Assessment

The current approach is appropriate. A transport-neutral transcript feed avoids duplicating the mature chat projection and delivery-confirmation behavior, while the Eventuous client preserves the server’s established subscription protocol. Separate remote-only chat rendering or a custom stream client would increase behavioral drift and lifecycle risk without a compensating architectural benefit.

Files changed (53) +6780 / -248

Enhancement (24) +1121 / -72
App.axaml.csCompose remote chat and terminal dependencies +3/-2

Compose remote chat and terminal dependencies

• Builds remote workspaces with the server lane, detail reader, URL opener, time provider, and terminal surface. It also supplies the merged agent directory to main-window rebinding.

src/Capacitor.App/App.axaml.cs

IServerLane.csExpand the server lane for remote workspace traffic +15/-0

Expand the server lane for remote workspace traffic

• Adds session-stream tails, pending-input updates, terminal broadcasts, viewport operations, user input, and special-key calls.

src/Capacitor.App/Services/IServerLane.cs

ITerminalSurface.csSupport source-driven terminal resizing +8/-6

Support source-driven terminal resizing

• Adds a resize operation so remote terminal surfaces can follow the source PTY dimensions.

src/Capacitor.App/Services/ITerminalSurface.cs

NoRemoteAgents.csImplement no-op remote workspace lane operations +12/-0

Implement no-op remote workspace lane operations

• Extends the disconnected server-lane implementation with empty streams and not-connected outcomes for all new chat and terminal operations.

src/Capacitor.App/Services/NoRemoteAgents.cs

PendingInputUpdate.csModel server queue snapshots +6/-0

Model server queue snapshots

• Introduces a session-keyed update containing the complete pending-input queue received from the server.

src/Capacitor.App/Services/PendingInputUpdate.cs

ServerConnectionService.csCarry streams, terminal data, queues, and input over SignalR +108/-5

Carry streams, terminal data, queues, and input over SignalR

• Integrates Eventuous stream subscriptions and exposes terminal and pending-input broadcasts. Adds remote input, terminal lifecycle, viewport calls, typed chat snapshots, reconnect behavior, and safe stream shutdown.

src/Capacitor.App/Services/ServerConnectionService.cs

SpecialKeyMapper.csMap terminal bytes to supported remote keys +27/-0

Map terminal bytes to supported remote keys

• Defines the seven allowed remote special keys, their display labels, and exact terminal-byte mappings while dropping all other input.

src/Capacitor.App/Services/SpecialKeyMapper.cs

TerminalOutputFrame.csRepresent remote terminal output frames +4/-0

Represent remote terminal output frames

• Adds an agent-keyed record for base64 terminal output delivered by the hub.

src/Capacitor.App/Services/TerminalOutputFrame.cs

TerminalSize.csRepresent remote source terminal dimensions +4/-0

Represent remote source terminal dimensions

• Adds an agent-keyed terminal size record for source PTY dimension broadcasts.

src/Capacitor.App/Services/TerminalSize.cs

XtermTerminalSurface.csResize Xterm surfaces from remote source dimensions +14/-12

Resize Xterm surfaces from remote source dimensions

• Implements the new terminal-surface resize contract using the underlying terminal model and refreshes related documentation.

src/Capacitor.App/Services/XtermTerminalSurface.cs

MainWindowViewModel.csRebind open workspaces across daemon origins +65/-1

Rebind open workspaces across daemon origins

• Watches merged agent rows and replaces local or remote workspaces when matching session IDs prove a lane transition. The active shell view and terminal tab are retained where supported.

src/Capacitor.App/ViewModels/MainWindowViewModel.cs

QueuedChatMessage.csTrack server-owned queued chat messages +17/-0

Track server-owned queued chat messages

• Adds server dispatch identity, foreign-client attribution, and queue matching state so remote queue snapshots can reconcile without falsely acknowledging sends.

src/Capacitor.App/ViewModels/QueuedChatMessage.cs

RemoteSessionViewModel.csTurn remote session cards into full workspaces +115/-25

Turn remote session cards into full workspaces

• Adds server-backed Chat and optional Terminal tabs, access-aware pane visibility, queued prompts, and coordinated teardown. It publishes origin changes so the main window can rebind the workspace safely.

src/Capacitor.App/ViewModels/RemoteSessionViewModel.cs

RemoteTerminalViewModel.csImplement the read-only remote terminal lifecycle +220/-0

Implement the read-only remote terminal lifecycle

• Subscribes after access is established, replays and follows PTY output on fresh surfaces, and forwards only supported keys. Viewports are reported only while visible and released on hide, disconnect, or teardown.

src/Capacitor.App/ViewModels/RemoteTerminalViewModel.cs

RemoteTranscriptFeed.csSeed and resume remote transcript streams +219/-0

Seed and resume remote transcript streams

• Seeds canonical events from session detail, then tails the normalized Eventuous stream from the last position. It preserves seed ordering, resumes across access restoration, backs off cleanly, and surfaces authorization failures.

src/Capacitor.App/ViewModels/RemoteTranscriptFeed.cs

ServerChatInput.csSend remote chat input through the server hub +111/-0

Send remote chat input through the server hub

• Provides an access- and lane-aware composer channel with accepted, rejected, and unconfirmed delivery outcomes. PTY sessions can also interrupt through the remote Escape key.

src/Capacitor.App/ViewModels/ServerChatInput.cs

ChatTabView.axamlLabel prompts queued by another client +2/-0

Label prompts queued by another client

• Adds a visual annotation for server queue rows originating outside the current desktop pane.

src/Capacitor.App/Views/ChatTabView.axaml

RemoteSessionView.axamlRender remote Chat and Terminal tabs +88/-20

Render remote Chat and Terminal tabs

• Replaces the card-only remote host with a tabbed workspace containing shared chat, access banners, terminal output, source-size status, and special-key controls.

src/Capacitor.App/Views/RemoteSessionView.axaml

RemoteSessionView.axaml.csManage focus across remote workspace tabs +29/-1

Manage focus across remote workspace tabs

• Focuses the composer or terminal when tabs and terminal models become active, and disposes focus subscriptions when the view detaches.

src/Capacitor.App/Views/RemoteSessionView.axaml.cs

CanonicalEventJson.csParse persisted canonical conversational events +28/-0

Parse persisted canonical conversational events

• Uses tolerant protobuf JSON parsing for the five canonical event types rendered by chat, returning null for unsupported or malformed payloads.

src/Capacitor.Models.Transcripts/CanonicalEventJson.cs

QueuedInputItem.csDefine the queued-input wire contract +12/-0

Define the queued-input wire contract

• Adds the tolerant snake_case contract for server-held prompts, including dispatch ID, sender, text, and dispatch time.

src/Capacitor.Remote.Models/QueuedInputItem.cs

RemoteModelsJsonContext.csGenerate queued-input serialization metadata +1/-0

Generate queued-input serialization metadata

• Registers queued-input arrays with the source-generated JSON context for trimming- and AOT-safe deserialization.

src/Capacitor.Remote.Models/RemoteModelsJsonContext.cs

RemoteWire.csDefine canonical session stream names +12/-0

Define canonical session stream names

• Adds server-compatible session ID normalization, AgentSession stream naming, and the stream authorization-denial token.

src/Capacitor.Remote.Models/RemoteWire.cs

SessionEventDto.csCarry persisted event timestamps +1/-0

Carry persisted event timestamps

• Adds the optional timestamp field required to preserve canonical event timing when rebuilding remote chat rows.

src/Capacitor.Remote.Models/SessionEventDto.cs

Bug fix (1) +7 / -1
WorkspaceViewModel.csPrevent unsupported terminal tabs after rebinding +7/-1

Prevent unsupported terminal tabs after rebinding

• Returns an active local workspace to Chat when authoritative agent presence reports no terminal, avoiding a blank carried-over pane.

src/Capacitor.App/ViewModels/WorkspaceViewModel.cs

Refactor (5) +276 / -127
ChatSessionInfo.csUnify local and remote chat session facts +26/-0

Unify local and remote chat session facts

• Introduces a transport-neutral session snapshot containing status, vendor, workspace root, lifecycle state, notices, and feed identity.

src/Capacitor.App/ViewModels/ChatSessionInfo.cs

ChatTabViewModel.csMake chat feeds transport-neutral and queue-aware +134/-115

Make chat feeds transport-neutral and queue-aware

• Refactors chat polling from direct file tails to disposable transcript feeds and observable session facts. It also reconciles server-queued prompts with local sends while preserving transcript-based delivery confirmation.

src/Capacitor.App/ViewModels/ChatTabViewModel.cs

IChatTranscriptFeed.csDefine the transcript feed abstraction +25/-0

Define the transcript feed abstraction

• Adds transport-neutral feed status, projected-line offsets, reset boundaries, and the disposable polling contract shared by local and remote chat.

src/Capacitor.App/ViewModels/IChatTranscriptFeed.cs

LocalTranscriptFeed.csAdapt local transcript files to the shared feed +58/-0

Adapt local transcript files to the shared feed

• Wraps JSONL tailing and projection behind the new feed interface while preserving reset, missing-file, offset, and projection-context behavior.

src/Capacitor.App/ViewModels/LocalTranscriptFeed.cs

TranscriptChat.csShare canonical chat projection across transports +33/-12

Share canonical chat projection across transports

• Extracts vendor display-rule lookup and canonical-event projection so file transcripts and server events produce identical rows and submitted-input acknowledgements.

src/Capacitor.Cli.Core/TranscriptChat.cs

Tests (17) +1673 / -47
ChatTabViewModelTests.csCover transport-neutral chat and remote queues +165/-0

Cover transport-neutral chat and remote queues

• Tests removed-agent lifecycle behavior, server queue reconciliation, foreign prompt retirement, session changes, and protection from inappropriate rebasing or unconfirmed state.

test/Capacitor.App.Tests.Unit/ChatTabViewModelTests.cs

FakeServerLane.csSimulate remote streams, terminal traffic, and input +89/-2

Simulate remote streams, terminal traffic, and input

• Extends the fake lane with controllable stream channels, terminal broadcasts, queue updates, handlers, and thread-safe call recordings.

test/Capacitor.App.Tests.Unit/FakeServerLane.cs

FakeTerminalSurface.csRecord source-driven terminal resizes +2/-0

Record source-driven terminal resizes

• Implements the resize contract and records applied dimensions for remote terminal assertions.

test/Capacitor.App.Tests.Unit/FakeTerminalSurface.cs

HubTestHost.csEmulate remote workspace hub contracts +57/-2

Emulate remote workspace hub contracts

• Adds Eventuous subscription methods, terminal replay and commands, queued chat snapshots, and server-side call recording to the in-process SignalR host.

test/Capacitor.App.Tests.Unit/HubTestHost.cs

LocalTranscriptFeedTests.csVerify the local transcript feed adapter +51/-0

Verify the local transcript feed adapter

• Covers missing files, ordered append projection with byte offsets, and reset behavior after truncation.

test/Capacitor.App.Tests.Unit/LocalTranscriptFeedTests.cs

MainWindowViewModelTests.csVerify bidirectional workspace rebinding +205/-14

Verify bidirectional workspace rebinding

• Covers remote-to-local and local-to-remote transitions, session-ID proof, active view and tab retention, terminal capability changes, and shutdown latching.

test/Capacitor.App.Tests.Unit/MainWindowViewModelTests.cs

RemoteFixtures.csProvide server-shaped remote event fixtures +26/-0

Provide server-shaped remote event fixtures

• Adds reusable session-detail events and Eventuous envelopes for remote chat tests.

test/Capacitor.App.Tests.Unit/RemoteFixtures.cs

RemoteSessionViewModelTests.csExercise the complete remote workspace +139/-14

Exercise the complete remote workspace

• Tests access transitions, transcript seeding and live events, prompt sending and confirmation, lane loss, terminal availability, queued prompts, and origin changes.

test/Capacitor.App.Tests.Unit/RemoteSessionViewModelTests.cs

RemoteSessionViewSmokeTests.csSmoke-test remote workspace rendering +42/-15

Smoke-test remote workspace rendering

• Verifies shared question cards inside Chat, denial banners, tab switching, terminal visibility, and special-key controls in headless Avalonia.

test/Capacitor.App.Tests.Unit/RemoteSessionViewSmokeTests.cs

RemoteTerminalViewModelTests.csCover remote terminal lifecycle edge cases +274/-0

Cover remote terminal lifecycle edge cases

• Tests replay timing, live output, visibility-scoped viewport reporting, bounds, reconnects, stale subscriptions, teardown races, key gating, and refusal behavior.

test/Capacitor.App.Tests.Unit/RemoteTerminalViewModelTests.cs

RemoteTranscriptFeedTests.csCover remote transcript seeding and resumption +209/-0

Cover remote transcript seeding and resumption

• Tests seed boundaries, live-event ordering, ignored events, authorization failures, reconnect positions, escalating retries, and disposal behavior.

test/Capacitor.App.Tests.Unit/RemoteTranscriptFeedTests.cs

ServerChatInputTests.csVerify remote composer outcomes +128/-0

Verify remote composer outcomes

• Covers availability prerequisites, accepted and unconfirmed sends, denial, cancellation, transport failures, PTY interruption, and pre-ready rejection.

test/Capacitor.App.Tests.Unit/ServerChatInputTests.cs

ServerConnectionServiceTests.csVerify server lane workspace protocols +147/-0

Verify server lane workspace protocols

• Adds integration-style SignalR tests for Eventuous tails, clean lane loss, terminal replay and commands, pending-input snapshots, and malformed queue handling.

test/Capacitor.App.Tests.Unit/ServerConnectionServiceTests.cs

SpecialKeyMapperTests.csVerify the remote special-key vocabulary +34/-0

Verify the remote special-key vocabulary

• Covers every supported byte sequence and confirms unsupported or incomplete terminal input is dropped.

test/Capacitor.App.Tests.Unit/SpecialKeyMapperTests.cs

TranscriptChatCanonicalTests.csVerify canonical chat projection +47/-0

Verify canonical chat projection

• Tests user messages, tool calls and results, vendor filtering, and display-rule selection independently of transcript transport.

test/Capacitor.Cli.Core.Tests.Unit/TranscriptChatCanonicalTests.cs

CanonicalEventJsonTests.csVerify tolerant canonical event parsing +32/-0

Verify tolerant canonical event parsing

• Covers all conversational protobuf payloads, unknown fields, unsupported types, malformed JSON, and invalid field shapes.

test/Capacitor.Models.Transcripts.Tests.Unit/CanonicalEventJsonTests.cs

WireShapeTests.csVerify new remote wire shapes +26/-0

Verify new remote wire shapes

• Tests canonical stream naming, optional event timestamps, and tolerant snake_case queued-input deserialization.

test/Capacitor.Remote.Models.Tests.Unit/WireShapeTests.cs

Documentation (3) +3697 / -1
README.mdDocument remote desktop workspaces +1/-1

Document remote desktop workspaces

• Explains that macOS desktop users can open remote sessions with chat, prompts, and read-only terminal viewing.

README.md

CHANGES.mdRecord remote workspace invariants +22/-0

Record remote workspace invariants

• Documents transcript seeding and resumption, explicit authorization behavior, and terminal viewport release requirements.

docs/CHANGES.md

2026-09-14-desktop-remote-daemons-slice3.mdAdd the remote workspace implementation plan +3674/-0

Add the remote workspace implementation plan

• Provides the detailed design, staged tasks, contracts, tests, verification steps, and self-review notes for the remote workspace slice.

docs/superpowers/plans/2026-09-14-desktop-remote-daemons-slice3.md

Other (3) +6 / -0
Directory.Packages.propsPin the Eventuous SignalR client version +1/-0

Pin the Eventuous SignalR client version

• Adds the centrally managed Eventuous SignalR client package version used for raw session-stream subscriptions.

Directory.Packages.props

Capacitor.App.csprojReference the Eventuous SignalR client +1/-0

Reference the Eventuous SignalR client

• Adds the application-level package reference required for session event-stream subscriptions.

src/Capacitor.App/Capacitor.App.csproj

Capacitor.Remote.Models.csprojEnable AOT analysis for remote models +4/-0

Enable AOT analysis for remote models

• Marks the shared wire-model project as AOT-compatible and trimmable so reflective serialization issues are detected at their source.

src/Capacitor.Remote.Models/Capacitor.Remote.Models.csproj

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: f11c6c0376

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread src/Capacitor.App/ViewModels/ChatTabViewModel.cs
Comment thread src/Capacitor.App/ViewModels/RemoteTerminalViewModel.cs Outdated
@qodo-code-review

qodo-code-review Bot commented Sep 15, 2026 •

Copy link
Copy Markdown

Code Review by Qodo

🐞 Bugs (0) 📘 Rule violations (2) 🔗 Cross-repo conflicts (0) 📜 Skill insights (0)

Grey Divider


Action required

1. Chat access failures leave users waiting ✓ Resolved 🐞 Bug ☼ Reliability
Description
ChatTabViewModel.Apply handles FeedStatus.Failed by logging and returning without changing
Phase or exposing the failure text. When RemoteTranscriptFeed emits this terminal status for a
denied initial detail read or rejected stream subscription, the separate access lease remains
established while the tab stays on “Waiting for the transcript…” or retains stale rows, with no
visible explanation.
Code

src/Capacitor.App/ViewModels/ChatTabViewModel.cs[R520-522]

+            case FeedStatus.Failed:
                LogOnce(read.Failure ?? "read failed");
                return;
-            case TailStatus.Reset:
-                // Skip everything already present in the new file, including appends that landed
-                // while this read was being projected. They may be replayed history, not receipts.
-                RebaseQueuedMessages(Math.Max(read.SnapshotLength ?? 0, TranscriptLength(_path) ?? 0));
Relevance

●●● Strong

Recent transcript and UI findings involving stale or misleading state were accepted for correction.

PR-#889
PR-#790

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
RemoteTranscriptFeed deliberately converts detail authorization errors and rejected stream
subscriptions into FeedStatus.Failed, but the corresponding consumer branch in
ChatTabViewModel.Apply only logs the result and returns. Because the visible phase has no failure
state or dynamic failure message, and access state comes from a separate session-access lease that
is not changed by the feed failure, neither the chat pane nor the remote workspace banner
communicates the failure.

src/Capacitor.App/ViewModels/RemoteTranscriptFeed.cs[126-139]
src/Capacitor.App/ViewModels/ChatTabViewModel.cs[120-123]
src/Capacitor.App/ViewModels/ChatTabViewModel.cs[516-522]
src/Capacitor.App/ViewModels/RemoteSessionViewModel.cs[259-274]
src/Capacitor.App/ViewModels/ChatTabViewModel.cs[119-124]
src/Capacitor.App/ViewModels/ChatTabViewModel.cs[513-526]
src/Capacitor.App/ViewModels/RemoteTranscriptFeed.cs[151-164]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
`FeedStatus.Failed` results from remote transcript authorization and subscription failures are logged but never reflected in the chat phase or shown to the user, leaving the pane permanently waiting or displaying stale transcript rows without an explanation.

## Fix Focus Areas
- src/Capacitor.App/ViewModels/ChatTabViewModel.cs[119-124]
- src/Capacitor.App/ViewModels/ChatTabViewModel.cs[513-526]
- src/Capacitor.App/ViewModels/RemoteTranscriptFeed.cs[126-139]

## Recommended Fix
Store a user-safe failure note and transition the chat to an unavailable or dedicated failed phase when `FeedStatus.Failed` is applied. Preserve already rendered transcript rows if desired, but ensure an initial failure cannot remain in the waiting state and that a terminal stream failure is visibly represented in the pane; add tests for both an initially unauthorized detail response and a stream subscription refusal after seeding.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


2. Denied remote actions appear successful ✗ Dismissed 🔗 Cross-repo conflict ≡ Correctness
Description
ServerConnectionService.InvokeAsync treats every normally completed terminal or input invocation
as successful, but kcap-server silently returns without acting when the caller lacks Full access.
Activity-level viewers therefore see the terminal marked live and submitted chat text accepted even
though terminal subscription, input, key, and resize operations were refused.
Code

src/Capacitor.App/Services/ServerConnectionService.cs[R336-339]

+    public Task<HubCallOutcome> SubscribeToTerminalAsync(string agentId, CancellationToken ct) => InvokeAsync(HubMethods.SubscribeToTerminal, ct, agentId);
+    public Task<HubCallOutcome> UnsubscribeFromTerminalAsync(string agentId, CancellationToken ct) => InvokeAsync(HubMethods.UnsubscribeFromTerminal, ct, agentId);
+    public Task<HubCallOutcome> RequestResizeTerminalAsync(string agentId, int cols, int rows, CancellationToken ct) => InvokeAsync(HubMethods.RequestResizeTerminal, ct, agentId, cols, rows);
+    public Task<HubCallOutcome> ReleaseResizeTerminalAsync(string agentId, CancellationToken ct) => InvokeAsync(HubMethods.ReleaseResizeTerminal, ct, agentId);
Relevance

●●● Strong

Accepted reliability and error-state findings are consistently fixed when silent failures mislead
users.

PR-#909

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
The PR routes the new terminal operations through a helper that returns Ok after any non-throwing
invocation, then uses that result to mark the terminal live and chat input accepted. The server
contract instead completes denied calls normally while skipping group membership and command
dispatch, as its authorization tests explicitly verify.

src/Capacitor.App/Services/ServerConnectionService.cs[336-359]
src/Capacitor.App/ViewModels/RemoteTerminalViewModel.cs[135-155]
src/Capacitor.App/ViewModels/ServerChatInput.cs[61-75]
External repo: kurrent-io/kcap-server, src/Capacitor.Server/Sessions/CapacitorHub.cs [764-772]
External repo: kurrent-io/kcap-server, test/Capacitor.Server.Tests.Agents/HostedAgentFloorTests.cs [95-101]
External repo: kurrent-io/kcap-server, test/Capacitor.Server.Tests.Agents/HostedAgentFloorTests.cs [198-204]
External repo: kurrent-io/kcap-server, test/Capacitor.Server.Tests.Agents/HostedAgentFloorTests.cs [229-235]
External repo: kurrent-io/kcap-server, test/Capacitor.Server.Tests.Agents/HostedAgentFloorTests.cs [328-336]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
The desktop client assumes completed remote-terminal and input hub calls were authorized, while kcap-server represents authorization denial as a successful void invocation with no effect. Introduce an explicit acknowledgement contract and coordinate deployment so the client can distinguish accepted actions from silent refusals.

## Fix Focus Areas
- src/Capacitor.App/Services/ServerConnectionService.cs[336-390]
- src/Capacitor.App/ViewModels/RemoteTerminalViewModel.cs[135-155]
- src/Capacitor.App/ViewModels/ServerChatInput.cs[61-76]

## Recommended Fix
Update the corresponding kcap-server hub methods to return an explicit authorization result or throw a stable denial token instead of silently returning. Consume that result in `ServerConnectionService`, map denial to `HubCallResult.Denied`, and only mark the terminal live or a chat send accepted after a positive acknowledgement; release the server and desktop changes compatibly or enforce the required minimum server version.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


3. Reconnecting can silence the terminal ✓ Resolved 🐞 Bug ☼ Reliability
Description
Detach and ReleaseViewportAsync start unsequenced release work that later clears shared
_reported viewport ownership, while a subsequent attachment or visible transition can immediately
subscribe and send a new resize. When a pane is hidden and shown or access is re-established before
unsubscribe and release complete, the delayed old release can reach the server after the new report
and remove the current terminal pane’s active viewer state, potentially interrupting live delivery.
Code

src/Capacitor.App/ViewModels/RemoteTerminalViewModel.cs[R179-181]

+        if (!_subscribed) return;
+        _subscribed = false;
+        _ = ReleaseAsync();
Relevance

●● Moderate

Race findings are often accepted, but this specific delayed-release interleaving lacks a close
precedent.

PR-#730

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
Visibility and access-state changes allow a new attachment or resize report to begin immediately,
while the previous release is deliberately fire-and-forget and delayed until asynchronous
unsubscribe completes. Subscribe, unsubscribe, resize, and release are independent hub invocations;
although subscribe completion is generation-scoped, the old release still mutates shared
subscription and viewport flags, and the existing stale-subscribe test does not cover a delayed
release from a previously live attachment arriving after a newer subscribe and viewport report.

src/Capacitor.App/ViewModels/RemoteTerminalViewModel.cs[97-101]
src/Capacitor.App/ViewModels/RemoteTerminalViewModel.cs[115-132]
src/Capacitor.App/ViewModels/RemoteTerminalViewModel.cs[147-172]
src/Capacitor.App/ViewModels/RemoteTerminalViewModel.cs[175-188]
src/Capacitor.App/Services/ServerConnectionService.cs[336-343]
test/Capacitor.App.Tests.Unit/RemoteTerminalViewModelTests.cs[217-238]
src/Capacitor.App/ViewModels/RemoteTerminalViewModel.cs[87-90]
src/Capacitor.App/ViewModels/RemoteTerminalViewModel.cs[161-172]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
An older fire-and-forget release from a hidden or detached terminal attachment can complete after a newer attachment or visible resize and clear the current subscription or server-side viewport state.

## Fix Focus Areas
- src/Capacitor.App/ViewModels/RemoteTerminalViewModel.cs[87-90]
- src/Capacitor.App/ViewModels/RemoteTerminalViewModel.cs[115-156]
- src/Capacitor.App/ViewModels/RemoteTerminalViewModel.cs[159-188]
- src/Capacitor.App/ViewModels/RemoteTerminalViewModel.cs[161-172]
- src/Capacitor.App/ViewModels/RemoteTerminalViewModel.cs[184-188]
- test/Capacitor.App.Tests.Unit/RemoteTerminalViewModelTests.cs[217-238]

## Recommended Fix
Serialize attach, release, and viewport mutations per terminal viewer, or associate subscription and viewport ownership with a generation so stale work cannot mutate the current generation. Apply only the latest desired visibility and size state, ensure a hide-triggered release cannot run after a newer visible resize when detach and re-establishment overlap, and retain a final ordered release during teardown. Add a test that delays unsubscribe, emits a new established state, completes the new subscribe and viewport report, then completes the old release and verifies that the current subscription and viewport remain active.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools



Remediation recommended

4. An output comment repeats the record ✓ Resolved 📘 Rule violation ⚙ Maintainability
Description
The documentation on TerminalOutputFrame restates that its two positional members carry an agent
identifier and base64 output. The record declaration immediately below already communicates that
complete shape, leaving no constraint or rationale for a later change.
Code

src/Capacitor.App/Services/TerminalOutputFrame.cs[3]

+/// One TerminalOutput push: the agent's bytes, base64 as the wire carries them.
Relevance

●●● Strong

Recent history accepts removing redundant comments that restate self-explanatory declarations.

PR-#904
PR-#703

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
Rule 2762993 disallows comments that duplicate type or signature information, and this comment
paraphrases the complete positional record directly beneath it.

Rule 2762993: Restrict comments to documenting non-obvious, behavior‑critical constraints
src/Capacitor.App/Services/TerminalOutputFrame.cs[3-4]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
The new XML comment merely repeats the positional record's visible members and encoding name without documenting a non-obvious constraint.

## Fix Focus Areas
- src/Capacitor.App/Services/TerminalOutputFrame.cs[3-4]

## Recommended Fix
Remove the XML comment, or replace it only if there is a behavior-critical encoding invariant not apparent from the record declaration.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


5. A size comment repeats the record ✓ Resolved 📘 Rule violation ⚙ Maintainability
Description
The documentation on TerminalSize paraphrases the agent, column, and row values visible in its
positional declaration. It provides no invariant, boundary, or compatibility requirement that
affects how this record must be used.
Code

src/Capacitor.App/Services/TerminalSize.cs[3]

+/// The source PTY's size, as TerminalDimensions reports it to a viewer.
Relevance

●●● Strong

Recent history accepts removing comments that duplicate plainly visible record members.

PR-#904
PR-#703

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
Rule 2762993 limits comments to non-obvious behavior-critical information, while this comment only
describes the record's plainly named fields.

Rule 2762993: Restrict comments to documenting non-obvious, behavior‑critical constraints
src/Capacitor.App/Services/TerminalSize.cs[3-4]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
The new XML comment repeats the terminal-size record's self-describing positional fields without adding a behavior-critical constraint.

## Fix Focus Areas
- src/Capacitor.App/Services/TerminalSize.cs[3-4]

## Recommended Fix
Remove the XML comment, or rewrite it only to capture a non-obvious invariant such as applicable bounds or lifecycle semantics.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


6. A key comment repeats the record ✓ Resolved 📘 Rule violation ⚙ Maintainability
Description
The documentation on SpecialKeyChoice says that the record contains a wire key and button label,
exactly matching its Key and Label positional members. Nothing in the comment records a
constraint that would guide a future modification of the choice model.
Code

src/Capacitor.App/Services/SpecialKeyMapper.cs[5]

+/// One of the daemon's special keys as a button: the wire token and what the button says.
Relevance

●●● Strong

Recent history accepts removing comments that merely restate obvious declarations.

PR-#904
PR-#703

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
Rule 2762993 explicitly rejects comments that duplicate type or signature information, which this
comment does for the positional record immediately below it.

Rule 2762993: Restrict comments to documenting non-obvious, behavior‑critical constraints
src/Capacitor.App/Services/SpecialKeyMapper.cs[5-6]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
The new XML comment simply expands the names of the record's `Key` and `Label` members.

## Fix Focus Areas
- src/Capacitor.App/Services/SpecialKeyMapper.cs[5-6]

## Recommended Fix
Remove this comment and retain the separate mapper documentation that explains the exact-sequence behavioral constraint.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


View medium (2)
7. Queue parsing skips shape helpers ✓ Resolved 📘 Rule violation ⚙ Maintainability
Description
ParseQueue pattern-matches JsonElement.ValueKind against JsonValueKind.Array instead of using
the shared array-shape extension. Every chat subscription snapshot passes through this check before
queued prompts are deserialized and published.
Code

src/Capacitor.App/Services/ServerConnectionService.cs[374]

+        if (items is not { ValueKind: JsonValueKind.Array } array) return null;
Relevance

●●● Strong

Direct shape checks violate an explicit repository rule and are a trivial local replacement.

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
Rule 2270023 prohibits direct JsonElement.ValueKind comparisons when an equivalent extension
exists, and the added queue parser directly matches JsonValueKind.Array.

Rule 2270023: Use JsonElementExtensions helpers instead of direct JsonValueKind comparisons
src/Capacitor.App/Services/ServerConnectionService.cs[373-376]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
`ParseQueue` directly compares `JsonElement.ValueKind` rather than using the shared JSON shape helper.

## Fix Focus Areas
- src/Capacitor.App/Services/ServerConnectionService.cs[373-376]

## Recommended Fix
Replace the `ValueKind` property pattern with the equivalent `JsonElementExtensions` array helper while preserving nullable handling and deserialization behavior.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


8. The key mapper file has two types ✓ Resolved 📘 Rule violation ⚙ Maintainability
Description
SpecialKeyMapper.cs adds both SpecialKeyChoice and SpecialKeyMapper as public top-level types.
RemoteTerminalViewModel exposes the choice type through its own public property, so the record is
not confined to a private descriptor implementation.
Code

src/Capacitor.App/Services/SpecialKeyMapper.cs[6]

+public sealed record SpecialKeyChoice(string Key, string Label);
Evidence
Rule 3162234 requires the filename to match the sole primary type unless a narrow exception applies;
the added record is publicly consumed by the terminal view model rather than being registry-private.

Rule 3162234: One primary type per file, with only narrow documented exceptions
src/Capacitor.App/Services/SpecialKeyMapper.cs[5-10]
src/Capacitor.App/ViewModels/RemoteTerminalViewModel.cs[73-73]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
The new key mapper file contains two public top-level types, and the choice record is exposed outside the mapper.

## Fix Focus Areas
- src/Capacitor.App/Services/SpecialKeyMapper.cs[5-10]

## Recommended Fix
Move `SpecialKeyChoice` into `SpecialKeyChoice.cs` in the same namespace and retain `SpecialKeyMapper` as the sole primary type here.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools



Informational

9. The feed file has four primary types 📘 Rule violation ⚙ Maintainability
Description
IChatTranscriptFeed.cs declares FeedStatus, ProjectedLine, FeedRead, and
IChatTranscriptFeed as separate public top-level types. Consumers across both feed implementations
and the chat view model use these declarations independently, so they do not fit the narrow
hierarchy exception.
Code

src/Capacitor.App/ViewModels/IChatTranscriptFeed.cs[10]

+public readonly record struct ProjectedLine(ChatProjectionResult Projection, long Offset);
Relevance

● Weak

Recent precedent rejects strict one-type-per-file findings for independently useful partial or
grouped declarations.

PR-#865

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
Rule 3162234 requires one primary top-level type per file outside narrow exceptions; this new file
contains an enum, two records, and an interface that are independently consumed.

Rule 3162234: One primary type per file, with only narrow documented exceptions
src/Capacitor.App/ViewModels/IChatTranscriptFeed.cs[5-21]
src/Capacitor.App/ViewModels/LocalTranscriptFeed.cs[22-51]
src/Capacitor.App/ViewModels/RemoteTranscriptFeed.cs[67-87]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
The new feed contract file contains four public top-level primary types rather than one type matching the filename.

## Fix Focus Areas
- src/Capacitor.App/ViewModels/IChatTranscriptFeed.cs[5-21]

## Recommended Fix
Move `FeedStatus`, `ProjectedLine`, and `FeedRead` into individually named files in the same namespace, leaving only `IChatTranscriptFeed` in this file.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


10. The wire file gains another primary type 📘 Rule violation ⚙ Maintainability
Description
RemoteWire.cs adds StreamNames as another public top-level class alongside the existing wire
constant classes. The stream-name behavior is independently called by consumers and is not an
extension, tiny hierarchy implementation, or registry descriptor.
Code

src/Capacitor.Remote.Models/RemoteWire.cs[86]

+public static class StreamNames {
Relevance

● Weak

Recent precedent rejects strict one-type-per-file enforcement in multi-type or partial file
organization.

PR-#865

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
Rule 3162234 requires one primary type per file; the added StreamNames class joins multiple
existing top-level classes and matches none of the documented exceptions.

Rule 3162234: One primary type per file, with only narrow documented exceptions
src/Capacitor.Remote.Models/RemoteWire.cs[83-91]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
The change adds another public top-level primary type to a file already containing several primary wire types.

## Fix Focus Areas
- src/Capacitor.Remote.Models/RemoteWire.cs[83-91]

## Recommended Fix
Move `StreamNames` and its documentation into `StreamNames.cs` under the same namespace, without changing its public API.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


Grey Divider

Context sources
✅ Compliance rules (platform): 64 rules
✅ Cross-repo context — repo relationships
  Explored: repo: kurrent-io/kcap-server (sha: a1d78b81)
Review mode: 🧠 Deep: This is a broad, logic-dense remote-session feature spanning networking, streaming, terminal state, chat queuing, rebinding, UI lifecycle, wire models, and many independent code paths, making multiple subtle defects plausible.

Grey Divider

Tip of the day
💡 Did you know, you can reply 'qodo' on any finding to push back, ask questions, or dig deeper

More tips ↗ | Customize Qodo ↗ | Qodo docs ↗

Grey Divider

Qodo Logo

Comment thread src/Capacitor.App/Services/ServerConnectionService.cs Outdated
Comment thread src/Capacitor.App/Services/SpecialKeyMapper.cs Outdated
Comment thread src/Capacitor.App/Services/TerminalOutputFrame.cs Outdated
Comment thread src/Capacitor.App/Services/TerminalSize.cs Outdated
Comment thread src/Capacitor.App/Services/SpecialKeyMapper.cs Outdated
Comment thread src/Capacitor.App/ViewModels/RemoteTerminalViewModel.cs
Comment thread src/Capacitor.App/ViewModels/ChatTabViewModel.cs
Comment thread src/Capacitor.App/Services/ServerConnectionService.cs
alexeyzimarev and others added 4 commits September 15, 2026 16:06
A remote seed reads Ok and empty before its run lands, so the pane is already Reading when a refusal arrives: the gate is the rows on screen, not the phase.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…806)

Deciding the release after the unsubscribe returned could take back a viewport the next attach had reported meanwhile.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
@alexeyzimarev

Copy link
Copy Markdown
Member Author

Follow-ups from the branch review, filed as issues so they outlive the PR: #954 (a send before the seed strands its queue row), #955 (terminal subscription lifecycle), #956 (attachments-only queued row is blank), #957 (silent hub denials, needs a kcap-server contract change), #958 (batched tidy-ups).

alexeyzimarev and others added 6 commits September 15, 2026 17:08
)

The subscription client removes a stream's registration by name when an enumeration ends, so a replacement that registered first lost its own and received nothing; ending an enumeration also never told the server.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
A refused feed answers every later poll with an empty Ok, which is not a recovery; and a verdict reaching the feed after its run was stopped belongs to that run, not the current one.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…#806)

A transient registry snapshot drops the row and the next refresh restores the same live session; the host withdraws its verdict then and the chat recovers, so the terminal must too.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…#806)

The registry can list the twin before it knows its session id; the proof completes when the id arrives, so the dropped row's id is held against the twin's later revisions until the local row returns.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
The pane polls, so a verdict answered once is gone by the next poll; the feed now repeats it until a new attempt seeds, and the pane clears its note on any read that is not a refusal, which keeps a recovered local file readable.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…ing (#806)

A queued revision can describe a twin the directory has since dropped, with the local row back; and a swap the host cannot complete must not spend the watch.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
@alexeyzimarev
alexeyzimarev merged commit 76a6201 into main Sep 15, 2026
14 of 15 checks passed
@alexeyzimarev
alexeyzimarev deleted the desktop-remote-workspace-slice3 branch September 15, 2026 15:45
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Desktop shell: remote workspace — chat and read-only terminal

1 participant