feat(workspaces): generalize WebPi into a multi-runtime Web conversation surface - #1390
Merged
luokerenx4 merged 4 commits intoSep 6, 2026
Conversation
Extract the Pi RPC session host into a transport-driven WebSessionHost with pi-rpc, acp, claude-stream-json, and codex-app-server transports. Declare web-surface capabilities on every adapter that exposes a structured stdio protocol and expose the surface through /api/workspaces/:id/web/* routes. Co-authored-by: RainMona <RainMona@users.noreply.github.com>
…face Rename the WebPi browser modules to runtime-neutral names, read the new /web/* snapshot shape, gate Web launch on the runtime's declared capability, and add a pinned request card so permission prompts from Claude, Codex, and ACP runtimes can be answered from the browser. Demo handlers mirror the capability table and script a permission turn for prompting runtimes. Co-authored-by: RainMona <RainMona@users.noreply.github.com>
Add docs/web-conversation-surface.md, register it in the owner-guide index, retire WebPi wording from the guides that referenced it, and record plan progress. Co-authored-by: RainMona <RainMona@users.noreply.github.com>
…real app-server; settle rejected prompts
Real-binary smoke against codex 0.153.4, pi 0.85.1, opencode 1.18.29 and
claude 2.1.263 found three contract gaps:
- Codex thread/start rejected approvalPolicy 'onRequest' / sandbox
'workspaceWrite'; AskForApproval and SandboxMode are kebab-case on the
wire ('on-request', 'workspace-write'), verified via
codex app-server generate-json-schema.
- item/permissions/requestApproval answers with { permissions, scope }, not
{ decision }; grant now echoes the requested profile (turn or session
scope) and decline/cancel send an empty grant. Cancel payloads are now
per-request so user-input questions are not answered with a decision enum.
- Pi rejects prompts synchronously when no model credential is configured;
the transport left the snapshot in 'working'. Pi, Codex and Claude now
record the rejection in snapshot.error and return to idle.
Plan and owner guide record the smoke matrix and the remaining credentialed
acceptance gap.
Co-authored-by: RainMona <RainMona@users.noreply.github.com>
|
@cursoragent is attempting to deploy a commit to the luokerenx4's Team Team on Vercel. A member of the Team first needs to authorize it. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
WebPi proved that a browser conversation over a long-lived structured CLI process beats a PTY for many tasks, but it shipped as a Pi-only special case: the host spoke Pi RPC, routes checked
agent === 'pi', and the UI gated every affordance on the same literal.This PR turns it into one Web conversation surface that any Agent runtime joins by declaring a wire protocol. Claude Code, Codex, Cursor Agent, Grok Build, opencode, Oh My Pi, and Pi now all open in the browser; permission prompts from the runtimes that ask before running tools are answered from a card in the UI.
Plan:
plans/web-conversation-surface.md. Owner guide:docs/web-conversation-surface.md. Based on currentdev(6ec3966b).Design alternatives considered
_meta). Rejected as the only path; adopted as one transport.WebSessionHostwith pluggable transports:pi-rpc(pi, omp),acp(cursor, grok, opencode),claude-stream-json,codex-app-server. Pi's minimal message shape becomes the neutral model because every protocol maps onto it for presentation and the browser already groups it into turns.UI decision for permission prompts: (a) inline transcript item, (b) modal, (c) card pinned above the composer. (c) keeps the transcript an audit trail, doesn't steal focus, and matches the compaction status already pinned in that slot. This was chosen autonomously per the UI design workflow, not maintainer-approved.
Backend
src/workspaces/web-session/— neutralmodel.ts(messages,WebPermissionRequest,awaiting-inputphase, snapshot),transport.tscontract,json-rpc.ts,transcript-builder.ts, and four transports.src/workspaces/web-session-host.tsreplaceswebpi-session-host.ts: spawn/supervise, revision, prompt/abort/respond, stderr tail; no agent or wire branching.capabilities.web = { wire, permissionPrompts, freshSession }and implementcomposeWebCommand(claude-p --input-format stream-json … --permission-prompt-tool stdio, codexapp-server --listen stdio://,cursor-agent acp,grok agent stdio,opencode acp, omp--mode rpc --auto-approve).agystays TUI-only./api/workspaces/:id/sessions/:sid/web/{open,prompt,abort,respond}+GET /web;opencheckscapabilities.weband returns409 unsupported_surfaceotherwise.SessionRecord.surface: 'webpi'(shipped, migration 0040) is kept as the persisted value for every runtime; only free-floating identifiers and copy move to "Web". No migration.UI
WebSessionView/useWebConversation/web-presentation.ts/web-transcript.ts(renamed fromWebPi*); runtime identity only drives copy (placeholder, stop label, wire tooltip).ConversationRequestCardpinned in the composerstatusslot; options verbatim from the runtime,allow/deny/neutraltones; queued count for further requests; inline error keeps the card.noticeconversation item for system remarks (stopped turn, etc.).agentSupportsWeb(agents, agent)in Resume CTA, Workspace header surface toggle, Manager page; noagent === 'pi'literals remain.Docs
docs/web-conversation-surface.md, indexed indocs/README.md.ui-interaction-and-motion.md,workspace-manager.md,project-structure.md, and incidental mentions updated.Real-binary smoke (commit
2b6cf119)Installed the actual CLIs under a throwaway
HOME(@earendil-works/pi-coding-agent0.85.1,opencode-ai1.18.29,@openai/codex0.153.4,@anthropic-ai/claude-code2.1.263) and droveWebSessionHostwith the argv each adapter composes. No API keys were available, so this covers handshake + prompt rejection, not tool turns.pi-rpcget_state/get_messagesOK, session id boundidle+error(was stuck inworking; fixed)acp(opencode)initialize→session/newOKpongcodex-app-serverthread/startOK after fixerror, phase back toidleclaude-stream-jsonsystem/initbinds session id; "Not logged in" →idle+errorContract fixes this uncovered (verified against
codex app-server generate-json-schema):approvalPolicy/sandboxare kebab-case (on-request,workspace-write), notonRequest/workspaceWrite—thread/startwas rejected outright.item/permissions/requestApprovalanswers with{ permissions, scope }(granted profile), not{ decision }. Grant echoes the requested profile forturnorsession; decline/cancel send an empty grant. Cancel payloads are per-request soitem/tool/requestUserInputis never answered with a decision enum.snapshot.errorand return toidlerather than leaving the browser spinner on.Verification
npx tsc --noEmit✓,cd ui && npx tsc -b✓.web-session-host.spec.tsdrives a fake child over stdio for all four wires (17 tests);web-command.spec.tspins argv per runtime; routes/service/public-session/adapter specs green.ui/src/components/workspace,demo,contexts,conversation, Workspace/Manager page specs green through the root config; new coverage for the request card (render, respond, error, queue), runtime labels, notice items, capability gating, and demo handlers.pnpm -F open-alice-ui dev:demo): Claude quick chat → permission card → Allow → activity + answer; Deny → failed activity; Stop → notice; Pi answers directly; Antigravity stays on the terminal placeholder; no console errors.pnpm testfull run: 3 files fail identically on an untoucheddevworktree in the authoring sandbox (template-upgrade.spec,workspace-absorb.spec,project-transfer.specgit hook timeouts;supervisor-tui.pty.specneeds Node ≥ 22.19, sandbox has 22.14). Not related to this branch.Remaining gap
Credentialed acceptance needs a maintainer machine: per runtime, open one Session in Web, send a prompt that needs a tool, answer the permission card, stop mid-turn, then reopen the same Session in the TUI and confirm the native transcript is shared.
cursor-agentandgrokwere not smoke-tested (not installable via npm in the sandbox); their ACP path is the same client code that completed a real turn against opencode. Recorded as the open item in the plan.Suggested labels (a fork contributor cannot apply them):
workflow:parallel,theme:reliability,area:workspace,review:deep(cross-surface structure, adapter argv, permission handling).