Skip to content

feat: deep-link support (PID navigation + focus existing tab)#26

Merged
jesse23 merged 15 commits intomainfrom
feat/deep-link
Apr 6, 2026
Merged

feat: deep-link support (PID navigation + focus existing tab)#26
jesse23 merged 15 commits intomainfrom
feat/deep-link

Conversation

@jesse23
Copy link
Copy Markdown
Owner

@jesse23 jesse23 commented Apr 5, 2026

What

Implements the three deep-link features from docs/specs/deep-link.md, enabling third-party tools (e.g. Vibe Island) to integrate with webtty with no changes on their side.

Feature Description
Focus existing tab On /s/<id> load, client posts a focus-request on BroadcastChannel('webtty:focus:<sessionId>'). If an existing tab acks within 200ms, the new tab shows a fallback UI instead of mounting a second terminal to the same PTY.
PID in session API GET /api/sessions now includes pid: number | null per session — null before the first WebSocket connection spawns the PTY.
PID-based navigation GET /p/<pid> resolves the OS PTY PID to a session and issues 302 Location: /s/<id>. Returns 404 for unknown or non-integer PIDs.

Integration surface for external tools:

Action How
Discover running webtty GET http://127.0.0.1:2346/api/sessions
List sessions with PIDs Same endpoint — returns [{ id, createdAt, connected, pid }]
Jump by session ID open http://127.0.0.1:2346/s/<id>
Jump by PTY PID open http://127.0.0.1:2346/p/<pid>

Changes

File What
src/pty/types.ts Add pid: number to PtyProcess interface
src/pty/node-pty.d.ts Declare pid on IPty
src/pty/bun.ts Expose proc.pid
src/pty/node.ts Expose ptyProc.pid
src/server/session.ts Include pid in sessionToJson output
src/server/routes.ts Add GET /p/:pid handler
src/client/index.ts BroadcastChannel focus handshake
docs/specs/deep-link.md Mark all features ✅; correct /p/<pid> behaviour to 302

Manual test steps

bun run build && bunx webtty

Focus existing tab

  1. Open http://127.0.0.1:2346/s/main in a browser tab — terminal loads normally.
  2. Open the same URL in a second tab.
  3. The second tab should show Session already open in another tab. (no terminal mounts).
  4. The first tab should come to the foreground (window.focus()).

PID in session API

  1. With a session open and a terminal connected, run:
    curl http://127.0.0.1:2346/api/sessions
  2. Response should include "pid": <number> (not null) for connected sessions.
  3. Create a session via POST /api/sessions without connecting a WS — pid should be null.

PID-based navigation

  1. Note the pid from the step above (e.g. 12345).
  2. Run:
    open http://127.0.0.1:2346/p/12345
    Or with curl (manual redirect):
    curl -v http://127.0.0.1:2346/p/12345
  3. Should redirect 302 Location: /s/main and land on the terminal page.
  4. Try an unknown PID (/p/99999) — should return 404.

jesse23 and others added 12 commits April 5, 2026 17:52
Add pid: number to PtyProcess, declare it in node-pty.d.ts, and implement in both Bun and node-pty backends.

Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent)

Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
sessionToJson now returns pid: number | null — the OS PID if the PTY is live, null before first WS connection spawns the process.

Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent)

Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
Resolves a PTY PID to its session and issues a 302 to /s/<id>. Returns 404 for unknown or invalid PIDs. Enables external tools (e.g. Vibe Island) to jump to a webtty session by OS PID.

Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent)

Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
On load, the client posts a focus-request on webtty:focus:<sessionId>. If an existing tab acks within 200ms, the new tab shows a fallback UI instead of mounting a second terminal. The primary tab listens for future focus-requests and brings itself to the front.

Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent)

Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
Focus existing tab, PID in session API, and PID-based navigation are now implemented. Also corrects /p/<pid> behaviour to 302 redirect rather than direct render.

Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent)

Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
…features

- Expand sessionToJson multi-line to satisfy biome formatter
- Test pid null/set in sessionToJson unit tests
- Test GET /api/sessions includes pid field
- Test GET /p/:pid returns 404 for unknown and non-numeric PIDs
- Test GET /p/:pid redirects 302 to /s/<id> after PTY spawns

Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent)

Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
@jesse23 jesse23 changed the title feat: deep-link support — PID navigation, session API pid, and focus-existing-tab feat: deep-link support (PID navigation + focus existing tab) Apr 5, 2026
@jesse23 jesse23 requested a review from Copilot April 6, 2026 00:07
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Adds deep-link integration features to webtty so external tools can discover sessions, map PTY PIDs to sessions, and navigate/focus appropriately without requiring tool-side changes.

Changes:

  • Expose PTY pid through PTY backends → sessions → GET /api/sessions (pid: number | null).
  • Add GET /p/<pid> route that resolves a PTY PID to a session and redirects to /s/<id>.
  • Implement a client-side BroadcastChannel “focus existing tab” handshake to avoid mounting duplicate terminals for the same session.

Reviewed changes

Copilot reviewed 10 out of 11 changed files in this pull request and generated 5 comments.

Show a summary per file
File Description
src/server/websocket.test.ts Adds coverage ensuring /p/:pid redirects after a PTY is spawned via WS.
src/server/session.ts Extends sessionToJson output to include pid (or null).
src/server/session.test.ts Adds unit tests for pid serialization behavior.
src/server/routes.ts Implements GET /p/<pid> PID-to-session redirect logic.
src/server/routes.test.ts Adds route tests for /api/sessions pid presence and /p/:pid 404 cases.
src/pty/types.ts Extends PtyProcess interface with pid: number.
src/pty/node.ts Surfaces ptyProc.pid via the PtyProcess abstraction.
src/pty/node-pty.d.ts Updates local node-pty shim typing to include IPty.pid.
src/pty/bun.ts Surfaces proc.pid via the PtyProcess abstraction.
src/client/index.ts Adds BroadcastChannel focus handshake and fallback UI for secondary tabs.
docs/specs/deep-link.md Documents the deep-link behaviors and third-party integration surface.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

jesse23 and others added 3 commits April 5, 2026 20:44
…fixes

- Fix race condition: remove focus-request handler from handshake promise
  so only an established primary tab can reply with focus-ack
- Add BroadcastChannel feature detection with isPrimary=true fallback
  for environments that don't support it (older Safari, hardened webviews)
- Add e.data runtime guard (null/object/string checks) before branching
  on message type in both handshake and primary listeners
- Update sessionToJson @returns docstring to mention pid field
- Fix spec inconsistency: 'renders directly' -> '302 Location: /s/<id>'
  in both the features table and 3rd party integration table
window.focus() cannot switch tabs on macOS without a user gesture, and
window.close() is blocked for tabs not opened via window.open(). The
handshake produced worse UX (dead-end fallback UI) than doing nothing.

webtty go <id> opens a new tab unconditionally as before.

- Remove BroadcastChannel code from src/client/index.ts
- Update deep-link spec to mark focus-existing-tab as dropped
- Add ADR 024 documenting the constraints and decision
waitForPrompt was called after the resize message, but /bin/sh does not
redraw its prompt on SIGWINCH so the prompt never re-appeared, causing
a 3 s timeout. Move the waitForPrompt before the resize so the shell is
confirmed ready before dimensions are changed.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@jesse23 jesse23 merged commit 45b52d6 into main Apr 6, 2026
3 checks passed
@jesse23 jesse23 deleted the feat/deep-link branch April 6, 2026 03:01
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.

2 participants