Skip to content

feat(server): implement xumux v0.2 binary transport#7

Closed
visionik wants to merge 2 commits into
millionco:mainfrom
visionik:agent1/feat/xumux-server-transport
Closed

feat(server): implement xumux v0.2 binary transport#7
visionik wants to merge 2 commits into
millionco:mainfrom
visionik:agent1/feat/xumux-server-transport

Conversation

@visionik
Copy link
Copy Markdown

@visionik visionik commented May 6, 2026

Summary

Implements the server-side xumux v0.2 binary transport for localterm, replacing JSON framing with binary multiplexed channels.

Changes

  • xumux protocol module (packages/server/src/xumux/): XumuxServer, WebSocketAdapter, frame codec, protocol constants
  • Binary codec (protocol.ts): TERMINAL_MSG_TYPE constants and encode/decode for all 6 message types (input, output, resize, exit, title, session-info)
  • SessionRegistry: Updated from Set<Session> to Map<number, Session> with register(channelId, session), unregister(channelId), getByChannelId(channelId)
  • /xumux endpoint (index.ts): WebSocket upgrade route with HELLO/WELCOME handshake, OPEN_CHANNEL/CHANNEL_ACK, session lifecycle wiring, backpressure enforcement, loopback security
  • Tests: 121 tests passing — protocol codec, xumux server, WebSocketAdapter, SessionRegistry, and full session lifecycle integration tests

References

  • Scope: vbrief/active/2026-05-06-xumux-server-transport.vbrief.json
  • Spec: vbrief/specification.vbrief.json

Notes

  • libxumux npm package does not exist — xumux v0.2 protocol implemented inline in packages/server/src/xumux/
  • Does NOT touch apps/terminal/* (Agent 2 scope)
  • Existing /ws JSON endpoint preserved (will be removed in Phase 4 cutover)

Note

Medium Risk
Adds a new binary, multiplexed WebSocket endpoint with custom framing/handshake and new session lifecycle paths, which could impact session cleanup and capacity/backpressure behavior. Existing /ws remains but its registry tracking changes (auto IDs) could affect session counting if misused.

Overview
Introduces a new /xumux WebSocket endpoint that speaks xumux v0.2: binary frame parsing, HELLO/WELCOME handshake, per-channel OPEN/CLOSE management, and multiplexed PTY sessions over a single connection (with loopback enforcement and per-channel backpressure closes).

Adds a binary terminal message codec in protocol.ts for input/output/resize/exit/title/session-info, including a safer exit-code null sentinel (INT32_MIN) and a non-throwing decodeResize on short payloads.

Refactors SessionRegistry from a Set to an ID-keyed Map with registerAuto() (used by both /ws and /xumux) and updates cleanup paths to unregister by ID; adds comprehensive unit/integration tests for the codecs, xumux framing/server, adapter behavior, and registry semantics.

Reviewed by Cursor Bugbot for commit 857de4e. Bugbot is set up for automated code reviews on this repo. Configure here.

@vercel
Copy link
Copy Markdown

vercel Bot commented May 6, 2026

@visionik is attempting to deploy a commit to the Million Team on Vercel.

A member of the Team first needs to authorize it.

Comment thread packages/server/src/index.ts
Comment thread packages/server/src/index.ts Outdated
Comment thread packages/server/src/protocol.ts Outdated
Comment thread packages/server/src/index.ts Outdated
Comment thread packages/server/src/index.ts

private handleHello(payload: Uint8Array): void {
if (this.handshakeComplete) return;
if (payload.length >= 1 && payload[0] !== XUMUX_VERSION) {
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Empty HELLO payload bypasses version negotiation

Medium Severity

The condition payload.length >= 1 && payload[0] !== XUMUX_VERSION uses short-circuit evaluation, so when the HELLO payload is empty (0 bytes), the version check is skipped entirely and the handshake succeeds. A client sending a HELLO with no version byte completes the handshake without declaring its protocol version, bypassing version negotiation. The condition likely needs to be payload.length < 1 || payload[0] !== XUMUX_VERSION to reject empty payloads.

Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit 3ce56b7. Configure here.

} else if (event.type === TERMINAL_MSG_TYPE.RESIZE) {
const dims = decodeResize(event.payload);
if (dims) session.resize(dims.cols, dims.rows);
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Binary xumux path lacks input size validation

Medium Severity

The /xumux binary onChannelMessage handler passes decodeInput and decodeResize results directly to session.write() and session.resize() without any bounds validation. The JSON /ws path enforces MAX_INPUT_BYTES (64 KB), MAX_COLS (1000), and MAX_ROWS (1000) via zod schemas. The binary path allows arbitrarily large input payloads and resize dimensions up to 65535 (uint16 max), creating an inconsistent security posture between the two transport paths.

Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit 3ce56b7. Configure here.

Comment thread packages/server/src/index.ts
if (!raw || typeof raw !== "object") return 0;
const candidate = Reflect.get(raw, "bufferedAmount");
return typeof candidate === "number" ? candidate : 0;
};
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Duplicated getRawBufferedAmount in adapter and index

Low Severity

The getRawBufferedAmount function in websocket-adapter.ts duplicates nearly identical logic already present in index.ts. Both extract a bufferedAmount number from a raw WebSocket object using Reflect.get. Consolidating into a single shared utility would reduce maintenance burden and risk of divergent bug fixes.

Additional Locations (1)
Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit 3ce56b7. Configure here.

visionik added 2 commits May 6, 2026 10:56
- Add xumux v0.2 protocol implementation (XumuxServer, WebSocketAdapter, frame codec)
- Add binary codec for terminal message types (INPUT, OUTPUT, RESIZE, EXIT, TITLE, SESSION_INFO)
- Add /xumux WebSocket endpoint with HELLO/WELCOME handshake and channel management
- Update SessionRegistry to Map<channelId, Session> with register/unregister/getByChannelId
- Enforce MAX_CONCURRENT_SESSIONS (64) and loopback security on /xumux
- Add backpressure: close channel when bufferedAmount exceeds threshold
- Add unit tests for protocol codec, xumux server, WebSocketAdapter, SessionRegistry
- Add integration tests for full session lifecycle via mock transport
- Add CHANGELOG.md entry under [Unreleased]
- fix: change exit-code null sentinel from -1 to INT32_MIN (-2147483648)
  so real exit code -1 is not silently corrupted
- fix: decodeResize returns null on short payload instead of throwing,
  preventing unhandled exception crash in onChannelMessage handler
- fix: replace global SessionRegistry keyed by channelId with per-connection
  local Map so concurrent xumux connections sharing channel 1 do not
  collide and overwrite each other's sessions
- fix: call session.dispose() in exit event handler (was missing, leaking
  event listeners and title polling timer for server-initiated closes)
- test: update protocol.test.ts for new sentinel and null-return behavior
@visionik visionik force-pushed the agent1/feat/xumux-server-transport branch from 3ce56b7 to 857de4e Compare May 6, 2026 14:56
@visionik
Copy link
Copy Markdown
Author

visionik commented May 6, 2026

Closing — PR submitted against wrong repo. Will re-open against visionik/localterm fork.

@visionik visionik closed this May 6, 2026
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.

1 participant