Skip to content

Move Agent Web Socket Server into singleton to avoid port collisions#2171

Draft
GeorgeNgMsft wants to merge 16 commits intomainfrom
dev/georgeng/move_websocketserver_creation
Draft

Move Agent Web Socket Server into singleton to avoid port collisions#2171
GeorgeNgMsft wants to merge 16 commits intomainfrom
dev/georgeng/move_websocketserver_creation

Conversation

@GeorgeNgMsft
Copy link
Copy Markdown
Contributor

The browser agent's AgentWebSocketServer was being instantiated once per session context (initializeAgentContext call). When multiple agent-cli clients connected to the agent-server simultaneously, each triggered a separate initializeAgentContext RPC call on the shared browser agent process, resulting in two new AgentWebSocketServer(8081) calls and an EADDRINUSE crash on the second one.

The root cause is that the browser agent runs as a single out-of-process RPC server (agentProcess.ts) shared across all sessions, but session contexts are created independently per session — there was no deduplication of the port bind.

Fix: promote AgentWebSocketServer to a module-level singleton in browserActionHandler.mts, constructed once when the agent process loads. All session contexts reference the same instance. Construction was also moved from updateAgentContext (called once per action schema enable, raceable) to initializeAgentContext (called once per session, guarded by sessionContextP).

Copy link
Copy Markdown
Contributor

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

This PR aims to prevent port-collision crashes when multiple agent-cli clients connect concurrently by (1) sharing the browser agent’s AgentWebSocketServer as a singleton per agent process and (2) switching local view servers to OS-assigned ports (listen(0)) with the bound port reported back to the dispatcher via IPC.

Changes:

  • Removes portBase-based port assignment and instead reserves port 0 for manifest.localView agents, with bound ports reported back via SessionContext.setLocalHostPort().
  • Promotes the browser agent’s AgentWebSocketServer(8081) to a module-level singleton and wires it into browser session contexts.
  • Updates browser/markdown view server startup IPC to return the bound port (and updates callers to persist it back to the dispatcher).

Reviewed changes

Copilot reviewed 1 out of 1 changed files in this pull request and generated 1 comment.

Show a summary per file
File Description
ts/packages/shell/src/main/instance.ts Removes portBase option usage when creating the dispatcher.
ts/packages/dispatcher/dispatcher/src/execute/sessionContext.ts Adds SessionContext.setLocalHostPort() for agents to report OS-assigned ports.
ts/packages/dispatcher/dispatcher/src/context/commandHandlerContext.ts Updates docs/types to remove portBase and document OS-assigned port flow.
ts/packages/dispatcher/dispatcher/src/context/appAgentManager.ts Switches localView “reserved port” to 0 and adds setLocalHostPort() storage.
ts/packages/agentSdk/src/agentInterface.ts Extends SessionContext with setLocalHostPort(port).
ts/packages/agents/taskflow/benchmark/run-benchmark.mts Removes portBase usage in benchmark dispatcher creation.
ts/packages/agents/scriptflow/benchmark/run-benchmark.mts Removes portBase usage in benchmark dispatcher creation.
ts/packages/agents/montage/src/route/route.ts Adds server error handling; still starts the HTTP server for montage local view.
ts/packages/agents/montage/src/agent/montageActionHandler.ts Ensures montage view child process is killed on context close.
ts/packages/agents/markdown/src/view/route/service.ts Reports OS-bound port to parent via IPC and adds startup error handling.
ts/packages/agents/markdown/src/agent/markdownActionHandler.ts Consumes IPC {type:'Success', port} and persists port back via setLocalHostPort().
ts/packages/agents/browser/src/views/server/server.mts Reports OS-bound port to parent via IPC {type:'Success', port}.
ts/packages/agents/browser/src/views/server/core/baseServer.ts Captures and exposes the bound port after listen, improves startup/runtime error handling.
ts/packages/agents/browser/src/agent/websiteMemory.mts Updates a SessionContext stub to include setLocalHostPort (currently with a type mismatch).
ts/packages/agents/browser/src/agent/browserActionHandler.mts Introduces module-level AgentWebSocketServer singleton and adds closeAgentContext.
ts/packages/agents/browser/benchmark/test-webflow-grammar.mts Removes portBase usage in benchmark dispatcher creation.
ts/packages/agentRpc/src/types.ts Adds setLocalHostPort RPC method to agent RPC surface.
ts/packages/agentRpc/src/server.ts Exposes SessionContext.setLocalHostPort() through RPC.
ts/packages/agentRpc/src/client.ts Implements setLocalHostPort invoke handler to call session context method.
ts/docs/architecture/dispatcher.md Documents OS-assigned local view port flow and setLocalHostPort() reporting.
ts/.vscode/settings.json Adds watcher/search excludes for node_modules and dist.
Comments suppressed due to low confidence (1)

ts/packages/agents/browser/src/agent/browserActionHandler.mts:552

  • closeBrowserContext() stops the AgentWebSocketServer, but the server is a module-level singleton shared by all session contexts. Closing any one session would shut down the shared WebSocket server and break other active sessions. Consider not stopping the singleton here (or add ref-counting / process-lifetime ownership so the server only stops when the agent process exits).
async function closeBrowserContext(
    context: SessionContext<BrowserActionContext>,
) {
    if (context.agentContext.agentWebSocketServer) {
        context.agentContext.agentWebSocketServer.stop();
        delete context.agentContext.agentWebSocketServer;
    }

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

Comment on lines +399 to 403
// Register agentRpc invoke handlers for channel-multiplexed messages
context.agentContext.agentWebSocketServer.setAgentInvokeHandlers(
createAgentInvokeHandlers(context),
);

Copy link

Copilot AI Apr 9, 2026

Choose a reason for hiding this comment

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

AgentWebSocketServer is now a process-wide singleton, but updateBrowserContext() sets its invoke handlers and connection/message callbacks using the current SessionContext. With multiple sessions, each enable will overwrite these callbacks, causing client traffic to be handled by the wrong session context. To support concurrent sessions, keep per-session handlers in a map keyed by session/client identity, or move these handlers into the singleton and route based on an explicit session identifier.

Copilot uses AI. Check for mistakes.
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