-
Notifications
You must be signed in to change notification settings - Fork 47
🤖 feat: add remote mux server workspaces #2133
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Conversation
There was a problem hiding this comment.
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: e8aa2ba726
ℹ️ 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".
25a0e96 to
8247d26
Compare
Remote workspaces can have paths that refer to a different machine. Avoid generating local editor deep links unless the workspace is an SSH runtime. --- _Generated with `mux` • Model: `openai:gpt-5.2` • Thinking: `xhigh` • Cost: ``_ <!-- mux-attribution: model=openai:gpt-5.2 thinking=xhigh costs=unknown -->
9a8874b to
0611881
Compare
Add remoteServers.listRemoteProjects, which proxies a remote mux server's
projects.list and returns lightweight { path, label } suggestions for UI pickers.
- Hide add/edit forms by default; show the add form only after clicking Add. - Inline edit within the selected server card (no separate bottom editor). - Clarify auth token storage: local ~/.mux/secrets.json vs server-side ~/.mux/server.lock. - Add local/remote project path suggestions for project mappings.
Summary
Adds a “Remote Mux server” workflow so the desktop app can show local + remote workspaces in one UI, while running agent loops on an always-on
mux server.Key capabilities:
Background
Users want long-running agent work to continue when a laptop sleeps/closes, without syncing local uncommitted state. A remote mux backend is the simplest way to keep agent loops alive while keeping a single desktop UI.
Implementation
~/.mux/config.jsonand store auth tokens in~/.mux/secrets.jsonunder a reserved__remoteMuxServer:<id>key.remoteServers.*oRPC endpoints (list/upsert/remove/clearAuthToken/ping + workspaceCreate).onChat,sendMessage,answerAskUserQuestion,resumeStream,interruptStream,getInfo,getFullReplay,getSubagentTranscript).workspace.list,workspace.onMetadata, andworkspace.activity.*with retry/backoff on remote subscription failures.remoteServers.workspaceCreate.Validation
make static-checkRisks
📋 Implementation Plan
Plan: Remote workspaces via “Remote Mux server” (desktop backend proxy)
Context / Why
You want the desktop app to show both local and remote workspaces in one UI, while allowing some workspaces (and their agent loops) to run on an always-on remote
mux serverso they continue working after you close your laptop.Key constraint decisions (from the request):
Important architectural clarification (current codebase):
local|worktree|ssh|docker|devcontainer) controls where tools execute.So the feature is best implemented as remote backends + local proxy/aggregator, not as a new
Runtimevariant.Goals (MVP)
workspace.sendMessage, streaming viaworkspace.onChatworkspace.answerAskUserQuestion(otherwise remote agents can’t proceed)workspace.interruptStream/resumeStreamworkspace.onMetadata,workspace.activity.subscribeNon-goals (initially)
Evidence (repo facts consulted)
mux server(src/cli/server.ts) + oRPC over HTTP/WS (src/node/orpc/server.ts).src/browser/contexts/API.tsx).src/common/orpc/schemas/api.ts(notablyworkspace.onChat,workspace.onMetadata,workspace.activity.subscribe).workspaceIdand sometimestaskId/sourceWorkspaceIdthat must be rewritten (src/common/orpc/schemas/stream.ts).~/.mux/config.jsonviaConfig.loadConfigOrDefault()/saveConfig()(src/node/config.ts) andProjectsConfig(src/common/types/project.ts).workspaceIdand session dirs are~/.mux/sessions/<workspaceId>(Config.getSessionDir()insrc/node/config.ts).src/cli/proxifyOrpc.ts) and streaming works over HTTP in tests (src/cli/server.test.ts).src/node/orpc/context.ts,src/node/services/serviceContainer.ts.workspaceIdand filesystem-safe ID constraints.src/browser/components/ChatInput/CreationControls.tsx+useCreationWorkspace.ts.Approaches (with net LoC estimate)
Approach A (recommended): Local backend proxy + ID rewriting (single desktop UI)
Net LoC (product code): ~1,800–2,800 (plus tests)
Implementation plan (Approach A)
Phase 1 — Remote server registry + config persistence
Backend + schema
Add a persisted remote server registry to
ProjectsConfig.Files:
src/common/types/project.ts(extendProjectsConfig)src/node/config.ts(loadConfigOrDefault+saveConfigparse/write)Suggested shape:
Store per-server auth tokens in
~/.mux/secrets.jsonunder a reserved key prefix keyed by serverid(soconfig.jsoncan be committed/shared). Example:"__remoteMuxServer:server-id": [{ "key": "authToken", "value": "..." }].Add an oRPC API namespace for managing servers.
Security note (auth token storage)
Store remote server auth tokens in
~/.mux/secrets.json(plaintext by default) soconfig.jsonremains shareable/committable (andsecrets.jsoncan be encrypted or omitted from dotfiles). For single-user MVP this is likely acceptable, but if the remote server is reachable beyond localhost/LAN we should add warnings + recommend TLS + token rotation. Longer-term, consider OS keychain integration.src/common/orpc/schemas/api.ts(addremoteServers.*entries)src/node/orpc/router.ts(handlers)remoteServers.list(): Array<RemoteMuxServerConfig & { hasAuthToken: boolean }>remoteServers.upsert({ config, authToken? }): Result<void, string>(stores token in~/.mux/secrets.jsonunder a reserved key)remoteServers.clearAuthToken({ id }): Result<void, string>remoteServers.remove({ id }): Result<void, string>(also removes token)remoteServers.ping({ id }): Result<{ version: string }, string>(calls remotegeneral.ping+/versionoptionally)remoteServers.getProjectContext({ serverId, localProjectPath }): Result<{ remoteProjectPath: string; branches: string[]; recommendedTrunk: string | null; runtimeAvailability: RuntimeAvailability }, string>remoteServers.workspace.create({ serverId, localProjectPath, branchName, trunkBranch?, title?, runtimeConfig? }): Result<{ metadata: FrontendWorkspaceMetadata }, string>Implement
RemoteServersServicein Node.src/node/services/remoteServersService.tsConfig.editConfig~/.mux/secrets.jsonunder a reserved key (never inconfig.json; only exposehasAuthToken)Wire the service into the oRPC context so router handlers can access it.
src/node/orpc/context.ts(extendORPCContext)src/node/services/serviceContainer.ts(instantiate + expose)src/desktop/main.tsandsrc/cli/server.ts(include inorpcContextobject)Phase 2 — Remote client + ID/Path rewriting utilities
Implement a remote oRPC client factory.
New file:
src/node/remote/remoteOrpcClient.tsFor MVP, prefer HTTP transport (simpler) using
@orpc/client/fetch:Reconnect behavior: remote subscriptions (streams) should resubscribe on disconnect using exponential backoff + jitter until the caller’s
AbortSignalaborts.Note: the server also exposes a WebSocket endpoint at
${baseUrl}/orpc/ws(seesrc/browser/contexts/API.tsx) if HTTP streaming proves unreliable through proxies.Add a filesystem-safe namespacing scheme for remote workspace IDs.
New file:
src/common/utils/remoteMuxIds.tsRequirements:
:or/(Windows + session dir safety)Example:
Implement rewrite helpers for:
FrontendWorkspaceMetadata(id, parentWorkspaceId, projectPath/projectName)WorkspaceChatMessageevents (rewrite all ID fields)WorkspaceStatsSnapshot(hasworkspaceId)workspace.activityrecords (map keys)Why this matters:
WorkspaceChatMessageSchemacontains fields like:workspaceIdin almost every eventtaskIdintask-createdsourceWorkspaceIdinsession-usage-deltaIf these aren’t rewritten, the UI won’t be able to correlate events with the correct workspace in its stores.
Phase 3 — Backend proxying in the existing API surface
Core idea: local
src/node/orpc/router.tsdetects remote workspace IDs and delegates to the right remote client.Proxy coverage checklist (MVP)
Treat a workspace as remote if
decodeRemoteWorkspaceId(workspaceId)succeeds.Must proxy for a usable remote agent loop
workspace.onChat(and rewrite IDs inside every event)workspace.sendMessageworkspace.answerAskUserQuestion(Ask-mode Q&A)workspace.resumeStream/workspace.interruptStreamworkspace.list(so remote workspaces appear in UI)workspace.onMetadata(UI uses this for real-time updates)workspace.activity.list+workspace.activity.subscribe(sidebar streaming indicator)Likely needed for basic UI navigation (confirm via a quick grep of UI callsites during implementation)
workspace.getInfoworkspace.getFullReplayDefer unless UI breaks
workspace.getPlanContent,workspace.getPostCompactionState,workspace.stats.*,workspace.backgroundBashes.*,terminal.*general.openInEditor(remote paths don’t exist locally; probably show an explicit error)Merge remote workspaces into
workspace.list.File:
src/node/orpc/router.ts(workspace.listhandler)Steps:
context.workspaceService.list()workspace.list({ archived })projectPathis in the server’sprojectMappingsprojectPath -> localProjectPathReliability: wrap each remote fetch in a short timeout; on failure, log + return local workspaces (do not block UI render). Track per-server status for display in Settings.
Proxy
workspace.onChat.decodeRemoteWorkspaceId(input.workspaceId):remote.workspace.onChat({ workspaceId: remoteId })for awaityield rewritten eventsonChatreplays full history and emitscaught-up).Proxy workspace mutations required for a functioning remote agent loop:
workspace.sendMessageworkspace.answerAskUserQuestionworkspace.interruptStream/resumeStreamgetInfo, etc.)Multiplex global subscriptions:
workspace.onMetadata(merge local + each remote’sworkspace.onMetadata)workspace.activity.subscribe+workspace.activity.listImplementation sketch for multiplexing:
Add remote workspace creation (UI-facing API).
remoteServers.workspace.createprocedure:{ serverId, localProjectPath, branchName, trunkBranch?, title?, runtimeConfig?, sectionId? }localProjectPath -> remoteProjectPathusing the server configworkspace.create({ projectPath: remoteProjectPath, ... })This avoids overloading
workspace.createwith a “serverId” field, keeping local creation unchanged.Phase 4 — Frontend UI updates
Remote server management UI.
Workspace creation UI: add “Remote Mux server” option.
src/browser/components/ChatInput/CreationControls.tsxsrc/browser/components/ChatInput/useCreationWorkspace.tsremoteServers.getProjectContextcall)remoteServers.workspace.createRemote indicators + guardrails.
isRemoteWorkspaceId()helper usage to disable local-only UX affordances.Phase 5 — Testing
Unit tests
stream-start,task-created,session-usage-delta)Integration tests
createOrpcServer()(pattern insrc/cli/server.test.ts).workspace.listreturns a rewritten remote IDworkspace.onChatyields events whoseworkspaceIdmatches the rewritten IDOperational requirements (remote host)
mux serveron an always-on machine with a persistentMUX_ROOT.--auth-token/MUX_SERVER_AUTH_TOKEN.Recommended initial rollout
Generated with
mux• Model:openai:gpt-5.2• Thinking:xhigh• Cost:$29.98