Skip to content

feat(spike): port pi's durable TUI as a remote client (P1) - #427

Merged
bubbleptr merged 1 commit into
feat/durable-multiview-spikefrom
feat/durable-multiview-tui
Oct 2, 2026
Merged

bubbleptr merged 1 commit into
feat/durable-multiview-spikefrom
feat/durable-multiview-tui

Conversation

@bubbleptr

@bubbleptr bubbleptr commented Oct 2, 2026 •

Copy link
Copy Markdown
Owner

No linked issue: second stage (P1) of the Pi Durable multi-client spike (.scratch/durable-multiview/HANDOFF.md), stacked on #426. Spike-only; no Pace app code changes.

Summary

  • tui/tui.ts ports pi's durable TUI (packages/coding-agent/src/experimental/durable/tui.ts at earendil-works/pi@7fbbd5f, MIT) to run as a remote client of the host. It renders RemoteDurable's view through the same DurableViewSource / DurableController contract. Upstream's formatting is kept so that a diff against upstream shows only the port: imports, a local agentOf, and a notice while the host connection is lost or closed.
  • tui/pi-internals.ts: the pi-coding-agent 1.0.0 internals that the TUI needs but the package exports map hides (createAllToolRenderers, WorkingStatusIndicator, getEditorTheme, InteractiveThemeController, formatTokens, theme, and the value KeybindingsManager, which the package root exports only as a type). They are imported by relative dist path, through the same symlink as the package root, so stateful modules such as the theme stay single instances. If a Pi upgrade moves one of them, typecheck fails here.
  • tui/main.ts opens the task panel at start, as upstream does, and uses this machine's SettingsManager for theme, keybindings and terminal capabilities.
  • cli/host-address.ts: --url / --token / --data-dir parsing shared by the observer and the TUI.
  • New dependencies: @earendil-works/pi-tui 1.0.0 (the same instance pi-coding-agent uses), and @lydell/node-pty as a dev dependency, at the version apps/desktop already pins.

Test plan

  • bun run typecheck
  • bun run lint
  • bun run test: 167 files, 1970 tests (spike excluded)
  • bun run build
  • Spike bun run test: 12 tests. New ones: steer from a second client while busy, abort keeping the partial as an aborted entry, switching conversations per client, an unknown conversation reported as a notice, and a pty smoke test where the TUI prompts the host, renders the answer, shows the reconnect notice and exits 0 on Ctrl+D. The TUI and crash tests passed 4 more consecutive runs.
  • Manual, screen captured with headless xterm: streaming, the task panel, steer, /agents, Esc abort, and kill -9 mid-stream. In the crash run the partial is shown, then reconnecting...; after the restart the partial becomes Operation aborted, the same pi.generation task streams again, and the full answer arrives.
  • Playwright E2E not run (no app change)

Checklist

  • Linked issue or a short reason this has none
  • Title follows Conventional Commits
  • bun run typecheck, bun run test, and bun run build are green
  • ADR added or updated if an architectural boundary or product term changed (none changed)
  • CONTEXT.md updated if vocabulary changed (none changed)
  • New or changed shared/ui/ components are registered on /design (none changed)
  • Branch is feat/; this is not a push to main

The upstream durable TUI renders a host's gateway through RemoteDurable
unchanged, apart from its imports and a host connection notice. pi internals
hidden by the package exports map are imported by relative dist path from one
module, keeping stateful modules such as the theme single instances. Adds
controller tests (steer, abort, conversation switch) and a pty smoke test.
@bubbleptr
bubbleptr added this pull request to stack #428 October 2, 2026 08:32
@bubbleptr
bubbleptr marked this pull request as ready for review October 2, 2026 11:39

@devin-ai-integration devin-ai-integration Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Devin Review found 1 potential issue.

Devin Review


const remote = await connectRemoteDurable(await hostAddress(process.argv.slice(2)));
// As in pi's durable TUI, the task panel starts open; /tasks hides it.
await remote.controller.toggleTasks();

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🟡 Host disconnect stalls TUI startup

If the host disconnects before the tasks snapshot arrives, toggleTasks() waits indefinitely. The snapshot waiter survives disconnection, leaving the TUI unable to show reconnection or accept input.

Learn more

The client requests a tasks snapshot when the task panel opens. A disconnect rejects outstanding RPC calls, but the disconnect handler does not settle snapshot waiters. Since startup awaits the task panel before starting the terminal UI, it remains blocked until a restarted host sends another tasks snapshot.

Example: The TUI connects and sends subscribe tasks. The host exits before sending the snapshot. The client enters reconnecting, but the terminal UI never starts; Ctrl+D cannot close it while the host remains down.

Recommended fix: Start the terminal UI without awaiting the optional task panel subscription, or reject and clear outstanding snapshot waiters on disconnect and let reconnect resubscribe. Ensure failed subscriptions do not leave #commands blocked.

Devin Review


Was this helpful? React with 👍 or 👎 to provide feedback.

@bubbleptr
bubbleptr merged commit 7bfcc16 into main Oct 2, 2026
2 checks passed
@bubbleptr
bubbleptr deleted the feat/durable-multiview-tui branch October 2, 2026 14:22
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.

1 participant