Skip to content

feat: server-scoped workspaces, browser-session auth and sidebar drafts - #463

Open
hsteude wants to merge 11 commits into
mainfrom
feature/multi-server-sessions
Open

hsteude wants to merge 11 commits into
mainfrom
feature/multi-server-sessions

Conversation

@hsteude

@hsteude hsteude commented Sep 29, 2026 •

Copy link
Copy Markdown
Contributor

Summary

  • Manage named OpenCode connections in Settings → Servers: None, Browser-Session, Basic and Bearer authentication. Saving a connection retains the current workspace.
  • Use a simple active-server selector: top of the project sidebar, including collapsed/mobile views; bottom of Home beside Terminal and Settings. No cross-server session tab strip.
  • Restore each server's workspace/project route. Switching servers inside Settings retains the selected settings tab and scopes providers/MCP to the target server and its project; Back to workspace restores that server's workspace.
  • Show New Session immediately as a removable local sidebar draft, reuse empty drafts and promote the same entry in place on first send. Preserve typing during pending creation and recover failed creation.
  • Keep provider credentials, OAuth completion, model preferences, background SSE and terminal connections server-scoped. Detached provider requests cannot refresh/dispose another active scope.
  • Address review findings: disable unsupported remote MCP removal, purge removed connections' remembered routes/composers, retire promoted draft keys, add draft option/keyboard semantics, restore routes through all project pickers, preserve newer typing on first-prompt failure, and reject credential-bearing PTY ticket redirects.

Authentication and provenance

  • Imports the approved Browser-Session layer and notebook Makefile from the original feature/browser-session sibling, preserved untouched at base e234d7475 with its original content hashes.
  • Browser-Session uses existing same-origin browser login directly for HTTP/SSE/PTY, without relay credentials or tickets. Expired/denied sessions prompt sign-in; cross-origin cookie authentication is rejected.
  • Basic/Bearer transports retain the prefix-aware, credential-isolating same-origin relay and single-use PTY tickets. Remote connections never fall back to the local backend; local-only filesystem settings remain unavailable remotely.
  • Connection metadata lives in localStorage; Basic/Bearer secrets stay in sessionStorage. Provider credentials belong to the selected backend.

Validation

  • Final head 24937c43187bb3cf812c2957194b9c72af76970b: 264 unit tests, 60 browser tests, TypeScript typecheck and production build pass; ESLint 0 errors / 58 warnings. CI successful.
  • Browser coverage includes root and notebook-prefix routes, Home/project selector placement on desktop/mobile, same-tab Settings switching, provider/MCP response races, OAuth lifecycle, model preferences, local drafts/promotion, auth rejection, Browser-Session HTTP/SSE/PTY and cookie isolation.
  • Publication checks: clean explicit-path commits, git diff --check, remote ancestry verification, Makefile build/push from an exact committed Git archive. Original Browser-Session sibling diff and complete inventory hashes remain unchanged.
  • Fresh linux/amd64 image deployed through the owning Helm release to tight-ermine, developer1/opencode-browser-session, revision 10, Ready with zero restarts. Notebook spec and Helm values preserved except image.
  • Tag: dev-approved-ux-20261005T081232Z-24937c43187b in europe-west3-docker.pkg.dev/prokube-internal/prokube-customer/pk-opencode-webui.
  • Deployed digest: sha256:628c1e46c30da12f2f040b1e8b5977948793604cd79bcfd348e9af6a2d2976ae. Live imageID matches; hashes of 708 deployed UI/shared files match the freshly built image.
  • Final review follow-up retains the existing bounded five-retry terminal reconnect policy through transient ticket failures; browser regressions prove recovery and the retry limit.
  • Live backend health passes (OpenCode 1.18.23). Isolated live browser verifies Home bottom / project top selectors on desktop and mobile, immediate draft/reuse/removal and Browser-Session controls. Deployed-bundle browser fixtures verify Providers/MCP/Appearance same-tab switching, target project scope, Back to workspace, and remote MCP removal disabled. Zero page errors or backend mutations.
  • Copilot requested natively with gh pr edit --add-reviewer @copilot; all actionable findings received have fixes and regression coverage. Final-head re-review is running as of publication.

Known limits and live-test boundaries

  • Composer contents remain in memory with the existing 40-draft limit and are not reload-persistent.
  • Mock gateway/browser fixtures prove remote Browser-Session and provider flows; they are not a claim of real authenticated remote sandbox testing. Live checks use isolated browser storage and disclose intercepted responses separately from real backend health.
  • This PR is not merged.

Copilot AI 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.

Copilot review overview

🟡 Changes recommended

Server SSE streams remain active indefinitely after their final associated tab is closed.

Review effort: Balanced
Findings: 1 Medium severity

Open (1)
What changed in this PR

Adds multi-server OpenCode connections with server-scoped sessions, settings, credentials, SSE streams, and terminal relaying.

Changes:

  • Adds authenticated, prefix-aware HTTP/SSE/WebSocket transport for remote servers.
  • Introduces server selection and server-bound session/draft tabs.
  • Adds unit, browser, and CI coverage for multi-server behavior.
File Description
.gitignore Ignores browser-test output.
.github/​workflows/​ui-tests.yml Adds UI test CI.
README.md Documents remote connections and testing.
app-prefixable/​bun.lock Locks Playwright dependencies.
app-prefixable/​package.json Adds browser-test tooling.
app-prefixable/​playwright.config.ts Configures prefixed and root tests.
app-prefixable/​dev.ts Integrates remote HTTP and socket relays.
app-prefixable/​e2e/​fixture.ts Provides mock servers and UI fixtures.
app-prefixable/​e2e/​multi-server.spec.ts Tests multi-server browser workflows.
app-prefixable/​tests/​provider-auth.test.ts Tests remote OAuth restrictions.
app-prefixable/​tests/​remote-server.test.ts Tests relay security and tickets.
app-prefixable/​tests/​servers.test.ts Tests server and tab utilities.
app-prefixable/​tests/​socket-relay.test.ts Tests authenticated socket buffering.
app-prefixable/​src/​app.tsx Adds server-aware application shell and tabs.
app-prefixable/​src/​components/​command-palette.tsx Preserves server scope during navigation.
app-prefixable/​src/​components/​message-turn.tsx Scopes child-session navigation.
app-prefixable/​src/​components/​project-dialog.tsx Adds remote directory browsing.
app-prefixable/​src/​components/​server-manager.tsx Adds server management UI.
app-prefixable/​src/​components/​session-header.tsx Preserves server-aware navigation.
app-prefixable/​src/​components/​session-info.tsx Uses server-scoped links.
app-prefixable/​src/​components/​terminal.tsx Adds ticketed remote terminals.
app-prefixable/​src/​components/​tool-part.tsx Scopes tool navigation.
app-prefixable/​src/​context/​browser-notifications.tsx Scopes notification tags and routes.
app-prefixable/​src/​context/​layout.tsx Scopes layout persistence.
app-prefixable/​src/​context/​mcp.tsx Restricts remote filesystem removal.
app-prefixable/​src/​context/​permission.tsx Scopes permission preferences.
app-prefixable/​src/​context/​projects.tsx Discovers and scopes remote projects.
app-prefixable/​src/​context/​providers.tsx Scopes model preferences.
app-prefixable/​src/​context/​saved-prompts.tsx Disables local-only remote prompts.
app-prefixable/​src/​context/​server-events.tsx Supports reusable per-server SSE.
app-prefixable/​src/​context/​server-navigation.tsx Adds server-preserving navigation.
app-prefixable/​src/​context/​server.tsx Implements connection, tab, and stream registry.
app-prefixable/​src/​pages/​directory-layout.tsx Preserves server scope on fallback.
app-prefixable/​src/​pages/​home-layout.tsx Adapts layout and navigation.
app-prefixable/​src/​pages/​home.tsx Preserves server scope.
app-prefixable/​src/​pages/​layout.tsx Scopes sidebar state and links.
app-prefixable/​src/​pages/​project-picker.tsx Preserves server selection.
app-prefixable/​src/​pages/​session.tsx Scopes sessions, drafts, and mutations.
app-prefixable/​src/​pages/​settings.tsx Adds Servers settings and remote restrictions.
app-prefixable/​src/​utils/​provider-auth.ts Handles remote OAuth limitations.
app-prefixable/​src/​utils/​servers.ts Adds connection and tab utilities.
app-prefixable/​src/​utils/​terminal-connection.ts Builds terminal URLs and parses frames.
docker/​serve-ui.ts Integrates production remote relays.
shared/​remote-server.ts Implements secured remote API transport.
shared/​remote-url.ts Validates remote server URLs.
shared/​socket-relay.ts Implements buffered WebSocket relaying.

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment thread app-prefixable/src/context/server.tsx

Copilot AI 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.

Copilot review overview

🔵 Needs a closer look

The security-sensitive relay and broad state-management changes warrant final human validation despite comprehensive tests.

Review effort: Balanced
Findings: None

Resolved since last review (1)

Copilot AI 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.

Copilot review overview

🔵 Needs a closer look

Remote MCP removal remains misleadingly enabled, and duplicate tab titles produce ambiguous accessible close controls.

Review effort: Balanced
Findings: None

Previously missed (2)

In code that hasn't changed since last review

Medium severity Include server name in close button accessible label

app-prefixable/​src/​app.tsx:266

The close button's accessible name omits the server, so two common tabs such as “New session” on Alpha and Beta both expose exactly “Close New session”. Screen-reader users cannot distinguish which server tab will be closed. Include the connection name in this label, as the adjacent tab button already does visually.

Medium severity Disable MCP removal for remote servers

app-prefixable/​src/​context/​mcp.tsx:196

On remote connections this guard only fails after the user has clicked the still-enabled “Remove server” action and confirmed it (pages/settings.tsx:1693 and components/mcp-dialog.tsx:249). The action is therefore guaranteed to end in an error instead of being marked unavailable as described by the PR. Disable or hide MCP removal for remote servers and direct users to Disconnect before opening the confirmation dialog.

Import the approved Browser-Session HTTP/SSE/PTY transport and Makefile from the preserved browser-session worktree. Combine the tested server selector, scoped provider OAuth/model preferences, immediate sidebar draft promotion, and same-tab settings server navigation.
@hsteude
hsteude requested a balanced review from Copilot October 5, 2026 07:29
@hsteude hsteude changed the title feat: add upstream-style multi-server session connections feat: server-scoped workspaces, browser-session auth and sidebar drafts Oct 5, 2026
@hsteude

hsteude commented Oct 5, 2026

Copy link
Copy Markdown
Contributor Author

Addressed the remaining actionable prior Copilot feedback: remote MCP removal is now disabled in Settings and the session dialog, with guidance to disconnect instead. Two new browser checks (root and notebook prefix) verify both remote-disabled and local-enabled behavior; typecheck, production build and changed-file lint passed (zero errors). The prior ambiguous cross-server tab close controls were removed by the approved simple-selector UX.

Copilot AI 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.

Copilot review overview

🟡 Changes recommended

Connection removal and draft promotion retain stale draft state, and new draft rows do not follow the session list’s accessibility model.

Review effort: Balanced
Findings: 3 Medium severity

Open (3)

Comment thread app-prefixable/src/context/server.tsx
Comment thread app-prefixable/src/pages/layout.tsx Outdated
Comment thread app-prefixable/src/pages/session.tsx
@hsteude
hsteude requested a balanced review from Copilot October 5, 2026 07:43
@hsteude

hsteude commented Oct 5, 2026

Copy link
Copy Markdown
Contributor Author

Addressed all three new Copilot findings: removing a connection now forgets workspace/project routes and its module-level composer/version keys; promotion retires the temporary draft key and coordinates the route-change saver; draft rows now expose option/selection semantics and participate in arrow/Home/End/Enter/Space navigation while remaining outside backend bulk selection. Eight focused browser checks passed across root and notebook prefix (including removal/re-add/history, pending continuation/history, keyboard draft activation and in-place promotion); typecheck/build and changed-file lint passed with zero errors. Re-requested Copilot on the follow-up head.

Copilot AI 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.

Copilot review overview

🟡 Changes recommended

Project picker routes bypass remembered workspaces, and failed session creation can overwrite typing entered while creation is pending.

Review effort: Balanced
Findings: 2 Medium severity

Open (2)
Resolved since last review (2)
Previously missed (2)

In code that hasn't changed since last review

Medium severity Restore remembered project routes from the command palette

app-prefixable/​src/​components/​command-palette.tsx:3

The project entry in the command palette still opens /<dir>/session directly. Even with this server-aware navigator, that discards the per-server route recorded by rememberProject, so a selected local draft (including its composer) is not restored when the project is reopened from the palette. Use connections.projectRoute(server.id, project.worktree) with the current URL as the fallback, as the Home project selectors do.

Medium severity Route project selections through the connection registry

app-prefixable/​src/​pages/​project-picker.tsx:2

Project navigation from this picker is only server-scoped; both project handlers still navigate to /<dir>/session directly. That bypasses connections.projectRoute(...), so returning to a project from Home loses its remembered draft/session route and composer, unlike the Home sidebar paths in home-layout.tsx:133-138. Route project selections through the connection registry before falling back to the plain session URL.

Comment thread app-prefixable/src/pages/session.tsx
@hsteude

hsteude commented Oct 5, 2026

Copy link
Copy Markdown
Contributor Author

Follow-up review fixes: Home recent-project selections, the project dialog and command palette now restore remembered server/project routes. Added root/prefix coverage for all three entry points. Newer typing was already protected against create failure by draft versions; promotion now preserves that version protection when the first prompt fails too. Both create-failure and prompt-failure continuation cases pass, alongside the unchanged no-newer-text restore case. Eight focused browser checks, typecheck/build and changed-file lint passed. The keyboard browser focus race is resolved; CI on ac71b58 passed all 50 browser tests before these six new scenarios.

Copilot AI 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.

Copilot review overview

🟡 Changes recommended

Redirect handling can expose terminal credentials, and several navigation and draft-failure paths lose remembered user state.

Review effort: Balanced
Findings: 1 High severity · 1 Medium severity

Open (2)
Resolved since last review (1)
Previously missed (2)

In code that hasn't changed since last review

Medium severity Command palette project selection bypasses remembered routes

app-prefixable/​src/​components/​command-palette.tsx:3

Selecting a project from the command palette still builds a bare /<dir>/session URL; this wrapper only adds the active server ID. As a result, this project-switch path bypasses connections.projectRoute and discards the remembered session/draft route that the sidebar selectors restore. Route project commands through the same registry lookup used by HomeLayout and Layout.

Medium severity Project picker ignores remembered server project routes

app-prefixable/​src/​pages/​project-picker.tsx:2

The server-aware navigator preserves the server query, but this picker’s handleProjectSelect and openRecentProject still navigate to a newly constructed /<dir>/session route. Unlike the sidebar project selectors, they never consult connections.projectRoute, so choosing a project here loses its remembered session/draft and composer route, contrary to project-route restoration elsewhere in this PR.

Comment thread app-prefixable/src/utils/terminal-connection.ts
@hsteude
hsteude requested a balanced review from Copilot October 5, 2026 08:02
@hsteude

hsteude commented Oct 5, 2026

Copy link
Copy Markdown
Contributor Author

Fixed the ticket-redirect finding: terminal ticket fetch now rejects redirects and explicitly retains same-origin credential policy. A real HTTP redirect regression verifies that no request or credential reaches the redirect target. Browser-Session direct PTY and Basic/Bearer terminal scenarios remain passing. Also made the Home recent-project test accept its legitimate home-relative display (~) after the path response arrives. All actionable feedback received so far has corresponding fixes/tests; latest review re-requested.

Copilot AI 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.

Copilot review overview

🔵 Needs a closer look

A transient PTY ticket failure permanently stops the terminal’s bounded reconnect flow.

Review effort: Balanced
Findings: 1 Medium severity

Open (1)
Resolved since last review (1)
Previously missed (1)

In code that hasn't changed since last review

Medium severity Reconnect loop stops permanently after connect-ticket failure

app-prefixable/​src/​components/​terminal.tsx:140

If connect-ticket times out or returns a transient 429/5xx during a reconnect, this branch stops without scheduling another attempt, so the terminal's existing reconnect loop is permanently abandoned after one ticket failure. Please route this failure through the same bounded backoff used for abnormal WebSocket closes (while still stopping after the retry limit).

@hsteude

hsteude commented Oct 5, 2026

Copy link
Copy Markdown
Contributor Author

Published and deployed final committed head 4c68057. CI SUCCESS: 264 unit / 56 browser tests, typecheck/build, lint 0 errors / 58 warnings. Fresh Makefile linux/amd64 tag dev-approved-ux-20261005T080305Z-4c680570bda7; deployed digest sha256:329d2f6ecc6d7e6a9d24a863da47279d9391c6a318a9beb63fdb662ff9a12eb2. Owning Helm release developer1/opencode-browser-session on tight-ermine is revision 9, Ready, zero restarts; Notebook spec/values preserved except image. Live backend health and desktop/mobile Home-bottom/project-top controls, local draft reuse/removal and auth controls pass. Deployed-bundle fixture checks cover settings same-tab target scope/back navigation; real remote browser login was not available. Live pod hashes of 708 UI/shared files match the built image. Source is clean/pushed and original sibling hashes preserved. Immediate rollback revision 8 (image 1c97b15e...), original pre-task rollback revision 6 (67afac6b...). Final Copilot review is requested/running; PR remains unmerged.

@hsteude

hsteude commented Oct 5, 2026

Copy link
Copy Markdown
Contributor Author

Addressed the final re-review terminal finding: transient ticket acquisition failures now use the same five-retry exponential backoff as abnormal WebSocket closes. Added root/prefix browser scenarios proving recovery after an initial 503 and stopping after exactly five retries. All four scenarios pass; typecheck/build and changed-file lint pass. Re-requesting review on this follow-up head.

@hsteude

hsteude commented Oct 5, 2026

Copy link
Copy Markdown
Contributor Author

Final deployment supersedes revision 9: head 24937c4 is clean/pushed and deployed at Helm revision 10, Ready with zero restarts. CI SUCCESS: 264 unit tests / 60 browser tests, typecheck/build, lint 0 errors / 58 warnings. Makefile linux/amd64 tag dev-approved-ux-20261005T081232Z-24937c43187b; digest sha256:628c1e46c30da12f2f040b1e8b5977948793604cd79bcfd348e9af6a2d2976ae. Pod imageID and all 708 UI/shared file hashes match the built image. Live health/UI/isolated Settings-fixture checks pass, zero page errors/backend mutations. Notebook spec and Helm values preserved except image; original sibling hashes preserved. Immediate rollback is revision 9, image sha256:329d2f6ecc6d7e6a9d24a863da47279d9391c6a318a9beb63fdb662ff9a12eb2; pre-task rollback remains revision 6. All received actionable Copilot findings are fixed/tested; final re-review requested and still pending. No merge.

Copilot AI 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.

Copilot review overview

🟡 Changes recommended

The relay can self-proxy into local backend/filesystem routes, and direct Browser-Session activity is invisible to Kubeflow idle tracking.

Review effort: Balanced
Findings: 2 High severity

Open (2)
Resolved since last review (1)

const url = normalizeServerUrl(connection.url)
if (new URL(url).origin !== new URL(local).origin)
throw new Error("Browser-Session requires a server on the same origin as this WebUI")
return url
Comment thread shared/remote-server.ts
Comment on lines +55 to +56
if (api.startsWith("/api/ext/"))
return response("This feature requires the local UI filesystem API and is unavailable on external servers", 501)
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