Skip to content

feat: gateway channel system — MCP server + router infrastructure - #72

Merged
aterrylu merged 2 commits into
mainfrom
terry/gateway-channel-server
Mar 26, 2026
Merged

feat: gateway channel system — MCP server + router infrastructure#72
aterrylu merged 2 commits into
mainfrom
terry/gateway-channel-server

Conversation

@aterrylu

Copy link
Copy Markdown
Owner

Summary

  • Built the autonomOS gateway infrastructure for structured messaging between dashboard, CC sessions, and external platforms
  • server:autonomos MCP channel server that CC spawns as a subprocess — connects back to the gateway over WebSocket
  • Gateway router routes messages between platform adapters and CC sessions
  • Platform adapter stubs for Discord, Telegram, Slack (interface only, implementations deferred)
  • Precompiled dist.mjs via esbuild so CC can spawn with plain node (no tsx dependency)
  • Session spawn injects --dangerously-load-development-channels and --mcp-config automatically
  • Settings panel simplified to single "autonomOS Gateway" toggle

Architecture

Dashboard (browser)
  ├─ /ws/terminal/:id   ← existing, raw PTY
  └─ /ws/gateway         ← NEW, structured JSON
                │
         autonomOS Server (Hono)
           GatewayRouter + channelRegistry
                │
     ┌──────────┴──────────┐
     │ server:autonomos    │  ← spawned by CC as MCP subprocess
     │ (channel-server)    │     connects BACK to server via WebSocket
     └─────────────────────┘

New Files

File Purpose
packages/core/src/types/gateway.ts Shared gateway types (GatewayMessage, PlatformAdapter, etc.)
packages/server/src/channel-server/index.ts MCP channel server — reply, send_to_agent, list_agents tools
packages/server/src/channel-server/dist.mjs Precompiled bundle for node spawning
packages/server/src/gateway/router.ts Message routing between adapters and sessions
packages/server/src/gateway/index.ts Gateway lifecycle (init/shutdown)
packages/server/src/gateway/adapters/stub.ts Base stub adapter class
packages/server/src/gateway/adapters/{discord,telegram,slack}.ts Platform adapter stubs
packages/server/src/routes/gateway.ts /ws/gateway WebSocket endpoint

Test plan

  • TypeScript compiles clean (core, server, dashboard)
  • Sessions spawn with --dangerously-load-development-channels server:autonomos --mcp-config {...}
  • Channel server subprocess starts (node dist.mjs)
  • Gateway initializes on server startup
  • Deployed and tested on forge
  • Verify /mcp shows autonomos as connected in a CC session
  • End-to-end: dashboard sends message → CC session receives <channel> tag → Claude replies

🤖 Generated with Claude Code

aterrylu and others added 2 commits March 25, 2026 22:17
Build the autonomOS gateway infrastructure for structured messaging
between the dashboard, CC sessions, and platform adapters (Discord,
Telegram, Slack).

Key components:
- server:autonomos MCP channel server — standalone subprocess spawned
  by CC via --mcp-config, connects back to the gateway over WebSocket,
  exposes reply/send_to_agent/list_agents tools
- Gateway router — routes messages between platform adapters and CC
  sessions, fans out to dashboard for observability
- Platform adapter stubs — Discord, Telegram, Slack (interface only,
  implementations deferred)
- /ws/gateway WebSocket endpoint for channel server ↔ server comms
- Session spawn integration — injects --dangerously-load-development-channels
  and --mcp-config with the channel server config
- Precompiled dist.mjs via esbuild so CC can spawn with plain `node`
- Settings panel simplified to autonomOS Gateway toggle only (removed
  Discord/Telegram plugin toggles — gateway handles platforms directly)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@@ -0,0 +1,248 @@
#!/usr/bin/env node

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

🟡 Warning

Problem: dist.mjs is a compiled artifact committed to source. It is built from index.ts but only regenerated by make prod — not make dev. Any change to index.ts that lands without a matching rebuild will silently deploy stale code in production.

Why it matters: sessions.ts hardcodes the path to this file and spawns it with node. If it drifts from index.ts, Claude sessions will run the old channel server with no error — behaviour mismatch will be subtle and hard to debug.

Suggested fix:

// Add dist.mjs to .gitignore:
// packages/server/src/channel-server/dist.mjs

// And add the build step to make dev (watch mode):
// dev:
//   @bunx esbuild packages/server/src/channel-server/index.ts --bundle ...
//   @bunx esbuild packages/server/src/channel-server/index.ts --bundle --watch &

Or at minimum add a make build-channel-server and call it from both dev and prod.

};
}
const platform = lastInboundFrom.platform;
const msg: GatewayWsMessage = {

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

🟡 Warning

Problem: lastInboundFrom.platform is global mutable state shared across all inbound messages. The reply tool derives the target platform from whichever message arrived last — not from the message Claude is actually responding to.

Why it matters: If two messages arrive from different platforms before Claude calls reply (e.g. Discord message then Telegram message), the platform used for routing will be telegram even if Claude is replying to the Discord message. The reply goes to the wrong platform. Since chat_id is already platform-specific, Claude knows where to reply — it just needs the platform encoded alongside it.

Suggested fix:

// Option A: encode platform in chat_id (e.g. "discord:guild:channel")
// then parse it back in the reply tool:
const [platform, ...rest] = chat_id.split(":");
const routedChatId = rest.join(":");

// Option B: add platform as an explicit tool parameter
{
  name: "platform",
  type: "string",
  enum: ["discord", "telegram", "slack"],
  description: "Platform the chat_id belongs to",
}

Either approach removes the ambient state dependency and makes reply routing explicit and safe.

@nox-0x nox-0x left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Solid architecture — the MCP channel server + gateway router pattern is clean and the type-sharing via @autonomos/core is the right call. Two warnings left inline: (1) dist.mjs should be gitignored and built by both dev and prod targets to prevent source/binary drift; (2) lastInboundFrom.platform is global mutable state that can misroute replies if two platform messages arrive before Claude responds — consider encoding platform into the chat_id or adding it as an explicit tool param. Neither blocks this PR; the stub adapter scaffolding and WebSocket reconnect logic are correct.

@aterrylu
aterrylu marked this pull request as ready for review March 26, 2026 05:22
@aterrylu
aterrylu merged commit b3d42e9 into main Mar 26, 2026
1 check passed
@aterrylu
aterrylu deleted the terry/gateway-channel-server branch March 26, 2026 05:22
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