{"id":49766314,"url":"https://github.com/aiwhiteteam/harnessgate","last_synced_at":"2026-05-11T10:51:40.719Z","repository":{"id":350785660,"uuid":"1208261424","full_name":"aiwhiteteam/harnessgate","owner":"aiwhiteteam","description":"Connect Claude Managed Agents or any harness runtime to any messaging platform.","archived":false,"fork":false,"pushed_at":"2026-04-24T17:25:52.000Z","size":201,"stargazers_count":5,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-04-24T18:28:51.917Z","etag":null,"topics":["ai-agent","claude","discord-bot","gateway","managed-agents","multi-channel","slack-bot","telegram-bot","typescript"],"latest_commit_sha":null,"homepage":"","language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/aiwhiteteam.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":"CONTRIBUTING.md","funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2026-04-12T03:18:38.000Z","updated_at":"2026-04-24T17:26:32.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/aiwhiteteam/harnessgate","commit_stats":null,"previous_names":["aiwhiteteam/harnessgate"],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/aiwhiteteam/harnessgate","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/aiwhiteteam%2Fharnessgate","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/aiwhiteteam%2Fharnessgate/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/aiwhiteteam%2Fharnessgate/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/aiwhiteteam%2Fharnessgate/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/aiwhiteteam","download_url":"https://codeload.github.com/aiwhiteteam/harnessgate/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/aiwhiteteam%2Fharnessgate/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":32891966,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-10T13:40:02.631Z","status":"online","status_checked_at":"2026-05-11T02:00:05.975Z","response_time":120,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["ai-agent","claude","discord-bot","gateway","managed-agents","multi-channel","slack-bot","telegram-bot","typescript"],"created_at":"2026-05-11T10:51:37.853Z","updated_at":"2026-05-11T10:51:40.708Z","avatar_url":"https://github.com/aiwhiteteam.png","language":"TypeScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"\u003ch1 align=\"center\"\u003eHarnessGate\u003c/h1\u003e\n\n\u003cp align=\"center\"\u003e\n  \u003cstrong\u003eConnect any messaging platform to Claude Managed Agents.\u003c/strong\u003e\n\u003c/p\u003e\n\n\u003cp align=\"center\"\u003e\n  \u003ca href=\"LICENSE\"\u003e\u003cimg src=\"https://img.shields.io/badge/license-MIT-blue.svg\" alt=\"License\" /\u003e\u003c/a\u003e\n  \u003ca href=\"https://github.com/aiwhiteteam/harnessgate/actions\"\u003e\u003cimg src=\"https://img.shields.io/github/actions/workflow/status/aiwhiteteam/harnessgate/ci.yml?branch=main\u0026label=CI\" alt=\"CI\" /\u003e\u003c/a\u003e\n  \u003cimg src=\"https://img.shields.io/badge/node-%3E%3D22-brightgreen\" alt=\"Node\" /\u003e\n  \u003cimg src=\"https://img.shields.io/badge/TypeScript-strict-blue\" alt=\"TypeScript\" /\u003e\n\u003c/p\u003e\n\n---\n\n## Why HarnessGate and Claude Managed Agents?\n\nStandard agent runtimes like the Anthropic ecosystem, E2B, and the OpenAI Agents SDK give you a secure, production-grade harness out of the box. Existing chatbot frameworks (OpenClaw, Botpress) ship their **own agent loop** instead — so to plug into one of these runtimes, you end up **bypassing the framework entirely** just to pipe messages through.\n\nHarnessGate takes a different approach: **no local agent loop.** It's a pure bridge that connects social media platforms to a provider runtime such as Claude Managed Agents or E2B. The gateway just routes messages between platforms and the agent.\n\nThis means:\n- Claude Managed Agents features work out of the box (tool confirmation, custom tools, multi-agent threads, extended thinking)\n- Any future agent runtime plugs in with 4 methods\n- No competing agent loops, no bypassed infrastructure, no wasted abstractions\n\n```\n[Telegram] [Discord] [Slack] [WhatsApp] [Teams] [Web UI]\n     |         |        |       |          |       |\n     +----+----+----+---+-------+----------+------+\n          |         |\n     PlatformAdapter interface (per platform)\n          |         |\n          +----+----+\n               |\n          Bridge (orchestrator)\n          SessionMap + StreamManager\n               |\n        Provider interface\n               |\n    +----------+----------+----------+\n    | Claude   | HTTP     | Custom   |\n    | Managed  | (any     | (npm pkg |\n    | Agents   |  server) |  or file)|\n    +----------+----------+----------+\n```\n\n## Features\n\n- **Provider-agnostic** — Claude Managed Agents, any HTTP server, or bring your own\n- **Platform adapters** — Telegram, Discord, Slack, WhatsApp, Teams, Web UI\n- **Multi-app** — run multiple app instances per platform, each mapped to a different agent\n- **Session management** — automatic session creation, SQLite persistence, multi-turn conversations\n- **Buffer-then-send** — accumulates agent responses, sends as one message per turn\n- **Auto-split** — respects per-platform message length limits\n- **Event passthrough** — provider-specific events forwarded via `bridge.onEvent()` listeners\n\n## Quick Start\n\n```bash\nnpm install harnessgate\n```\n\n```typescript\nimport { Bridge, ClaudeProvider, TelegramAdapter } from \"harnessgate\";\n\nconst provider = new ClaudeProvider(process.env.ANTHROPIC_API_KEY!);\nconst bridge = new Bridge(provider, {\n  provider: { type: \"claude\" },\n  platforms: { telegram: { botToken: process.env.TELEGRAM_BOT_TOKEN! } },\n});\n\n// Route users to agents (agentId + environmentId from your DB)\nbridge.setUserResolver(async (sender) =\u003e ({\n  userId: sender.id,\n  agentId: \"agent_01XXXX\",\n  environmentId: \"env_01XXXX\",\n}));\n\nbridge.addPlatform(new TelegramAdapter());\nawait bridge.start();\n```\n\nSee [`examples/demo-web/`](examples/demo-web/) for a minimal starter or [`examples/demo-telegram/`](examples/demo-telegram/) for a Telegram bot.\n\n### Run an example from the repo\n\n```bash\ngit clone https://github.com/aiwhiteteam/harnessgate.git\ncd harnessgate\npnpm install\n\ncd examples/demo-telegram\ncp .env.example .env\n# Add ANTHROPIC_API_KEY in .env\n\n# Fill in botToken in src/main.ts\n# Implement your agentId/environmentId lookup in the user resolver\n\npnpm build\nnode --env-file=.env dist/main.js\n```\n\n## Multi-Bot / appId\n\nEvery platform adapter supports running multiple app instances simultaneously. Each app connects to the platform and receives a platform-assigned `appId` — an opaque identifier that flows through every `InboundMessage` and `ChannelTarget`.\n\n```typescript\n// Add multiple Telegram bots at runtime\nconst supportBotId = await bridge.connect(\"telegram\", { botToken: process.env.SUPPORT_BOT_TOKEN });\nconst salesBotId = await bridge.connect(\"telegram\", { botToken: process.env.SALES_BOT_TOKEN });\n\n// Route based on which bot received the message\nbridge.setUserResolver(async (sender, platform, message) =\u003e {\n  const agentId = await db.getAgentForBot(message.appId);\n  const environmentId = await db.getEnvironmentForBot(message.appId);\n  return { userId: sender.id, agentId, environmentId };\n});\n```\n\n### appId per platform\n\nEach platform exposes a different identifier as `appId`. The adapter reads it from the platform SDK after connecting:\n\n| Platform | Source | Example value |\n|----------|--------|---------------|\n| Telegram | `bot.botInfo.id` | `\"123456789\"` |\n| Discord | `client.application.id` | `\"1098765432101234567\"` |\n| Slack | `event.api_app_id` | `\"A0123456789\"` |\n| WhatsApp | WABA phone number ID | `\"106540352267890\"` |\n| Teams | `activity.recipient.id` | `\"28:abc123...\"` |\n| Web | N/A (single instance) | — |\n\nThe `appId` is included in session keys as `app:\u003cappId\u003e`, so each bot maintains separate conversation sessions even in the same channel.\n\n## Session Management\n\nEach conversation context gets its own Claude session:\n\n| Context | Session scope | Example key |\n|---------|--------------|-------------|\n| DM | Per user | `telegram:direct:123:u:user99` |\n| Group/Channel | Shared (all users) | `slack:group:ch1` |\n| Thread | Per thread | `discord:thread:ch1:t:thread99` |\n\nSessions persist to SQLite when you wire one in (survives restarts), otherwise the bridge falls back to in-memory storage:\n\n```typescript\nimport { SqliteSessionStore } from \"harnessgate\";\n\nbridge.setSessionStore(new SqliteSessionStore(\"./harnessgate.db\"));\n```\n\nFor custom stores (Supabase, Postgres, Redis), implement the `SessionStore` interface directly:\n\n```typescript\nbridge.setSessionStore({\n  async get(key) { /* query your DB */ },\n  async set(key, entry) { /* upsert */ },\n  async delete(key) { /* delete */ },\n  async touch(key) { /* update lastActiveAt */ },\n});\n```\n\nSee [`examples/with-supabase/main.ts`](examples/with-supabase/main.ts) for a complete example with Supabase for both auth and session persistence.\n\n## Provider Setup\n\nHarnessGate supports three ways to connect an agent runtime:\n\n### 1. Claude Managed Agents\n\n```bash\n# .env\nANTHROPIC_API_KEY=sk-ant-...\n```\n\n```typescript\nimport { ClaudeProvider } from \"harnessgate\";\n\nconst provider = new ClaudeProvider(process.env.ANTHROPIC_API_KEY!);\n```\n\nConnects to [Claude Managed Agents](https://docs.anthropic.com/en/docs/managed-agents/overview). Full support for streaming, tool confirmation, custom tools, extended thinking, and multi-agent threads.\n\nFor Claude, `agentId` and `environmentId` come from your `UserResolver`, not static provider config:\n\n```typescript\nbridge.setUserResolver(async (sender) =\u003e ({\n  userId: sender.id,\n  agentId: \"agent_01XXXX\",\n  environmentId: \"env_01XXXX\",\n}));\n```\n\n### 2. HTTP — any server\n\n```bash\n# .env\nMY_TOKEN=...\n```\n\n```typescript\nimport { HttpProvider } from \"harnessgate\";\n\nconst provider = new HttpProvider({\n  baseUrl: \"http://localhost:8080\",\n  headers: { Authorization: `Bearer ${process.env.MY_TOKEN}` },\n});\n```\n\nConnects to **any** HTTP server that implements these endpoints:\n\n| Endpoint | Request | Response |\n|----------|---------|----------|\n| `POST /sessions` | `{ systemPrompt? }` | `{ id: \"session-123\" }` |\n| `POST /sessions/{id}/message` | `{ message: \"hello\", sessionId: \"...\" }` | `200 OK` |\n| `GET /sessions/{id}/stream` | SSE stream | `data: {\"type\": \"message\", \"text\": \"...\"}` |\n| `DELETE /sessions/{id}` | — | `200 OK` |\n\nYour server can be written in any language. SSE events can use either format:\n\n```\n# HarnessGate-native format (recommended)\ndata: {\"type\": \"message\", \"text\": \"Hello!\"}\ndata: {\"type\": \"status\", \"status\": \"idle\"}\n\n# Simple format (auto-detected)\ndata: {\"response\": \"Hello!\"}\ndata: {\"text\": \"Hello!\"}\n```\n\nCustom endpoint paths:\n\n```typescript\nconst provider = new HttpProvider({\n  baseUrl: \"http://localhost:8080\",\n  endpoints: {\n    createSession: \"POST /api/conversations\",\n    sendMessage: \"POST /api/conversations/{sessionId}/chat\",\n    stream: \"GET /api/conversations/{sessionId}/events\",\n    destroySession: \"DELETE /api/conversations/{sessionId}\",\n  },\n});\n```\n\n### 3. Custom provider — npm package or local file\n\n```bash\n# .env\nMY_PROVIDER_API_KEY=...\n```\n\n```typescript\n// npm package\nimport MyProvider from \"@my-org/my-langgraph-provider\";\n\n// or local file\nimport MyProvider from \"./my-provider.js\";\n\nconst provider = new MyProvider({ apiKey: process.env.MY_PROVIDER_API_KEY! });\n```\n\nThe package/file must default-export a class implementing the `Provider` interface:\n\n```typescript\nimport type { Provider } from \"harnessgate\";\n\nexport default class MyProvider implements Provider {\n  readonly id = \"my-provider\";\n  readonly capabilities = {\n    interrupt: false,\n    toolConfirmation: false,\n    customTools: false,\n    thinking: false,\n  };\n\n  constructor(config: Record\u003cstring, unknown\u003e) {\n    // config contains whatever you pass to `new MyProvider({ ... })`\n  }\n\n  async createSession(opts) { /* ... */ }\n  async sendMessage(sessionId, message) { /* ... */ }\n  async *stream(sessionId, signal) { /* ... */ }\n  async destroySession(sessionId) { /* ... */ }\n}\n```\n\n## User Auth\n\nHarnessGate supports per-user access control and agent routing via a `UserResolver`:\n\n```typescript\nbridge.setUserResolver(async (sender, platform, message) =\u003e {\n  const user = await db.findUser(platform, sender.id);\n  if (!user?.isActive) return null; // reject\n\n  return {\n    userId: user.id,\n    agentId: user.agentId,         // which agent template\n    environmentId: user.envId,     // which environment\n    metadata: { plan: user.plan }, // passed to provider session\n  };\n});\n```\n\nReturn `null` to reject. When no resolver is set, all users are allowed with their platform ID as the user ID.\n\nClaude requires the resolver to return both `agentId` and `environmentId` for session creation.\n\n## Platform Configuration\n\nPass per-platform config to the `Bridge` constructor under `platforms`, keyed by platform id. Each entry is forwarded to that adapter's `start()`. Add an adapter via `bridge.addPlatform(...)` for each platform you want to enable.\n\n```bash\n# .env\nTELEGRAM_BOT_TOKEN=...\nDISCORD_BOT_TOKEN=...\nSLACK_BOT_TOKEN=...\nSLACK_APP_TOKEN=...\nWHATSAPP_PHONE_NUMBER_ID=...\nWHATSAPP_ACCESS_TOKEN=...\nWHATSAPP_VERIFY_TOKEN=...\nTEAMS_APP_ID=...\nTEAMS_APP_PASSWORD=...\n```\n\n```typescript\nimport {\n  Bridge,\n  TelegramAdapter,\n  DiscordAdapter,\n  SlackAdapter,\n  WhatsAppAdapter,\n  TeamsAdapter,\n  WebAdapter,\n} from \"harnessgate\";\n\nconst bridge = new Bridge(provider, {\n  provider: { type: \"claude\" },\n  platforms: {\n    web: { port: 3000 },\n    telegram: { botToken: process.env.TELEGRAM_BOT_TOKEN! },\n    discord: { token: process.env.DISCORD_BOT_TOKEN! },\n    slack: {\n      botToken: process.env.SLACK_BOT_TOKEN!,\n      appToken: process.env.SLACK_APP_TOKEN!,\n    },\n    whatsapp: {\n      phoneNumberId: process.env.WHATSAPP_PHONE_NUMBER_ID!,\n      accessToken: process.env.WHATSAPP_ACCESS_TOKEN!,\n      verifyToken: process.env.WHATSAPP_VERIFY_TOKEN!,\n      port: 8080,\n    },\n    teams: {\n      appId: process.env.TEAMS_APP_ID!,\n      appPassword: process.env.TEAMS_APP_PASSWORD!,\n      port: 3978,\n    },\n  },\n});\n\n// Only adapters you addPlatform() are started — omit any you don't need.\nbridge.addPlatform(new WebAdapter());\nbridge.addPlatform(new TelegramAdapter());\n// bridge.addPlatform(new DiscordAdapter());\n// bridge.addPlatform(new SlackAdapter());\n// bridge.addPlatform(new WhatsAppAdapter());\n// bridge.addPlatform(new TeamsAdapter());\n\nawait bridge.start();\n```\n\nLoad `.env` via `node --env-file=.env dist/main.js` or any `dotenv`-style loader.\n\n## Platform Setup Guides\n\nDetailed setup instructions for each platform, including SaaS multi-tenant distribution:\n\n| Platform | Guide | Library |\n|----------|-------|---------|\n| Telegram | [`docs/setup-telegram.md`](docs/setup-telegram.md) | grammY |\n| Discord | [`docs/setup-discord.md`](docs/setup-discord.md) | discord.js |\n| Slack | [`docs/setup-slack.md`](docs/setup-slack.md) | @slack/bolt |\n| WhatsApp | [`docs/setup-whatsapp.md`](docs/setup-whatsapp.md) | Cloud API (fetch) |\n| Teams | [`docs/setup-teams.md`](docs/setup-teams.md) | botbuilder |\n| Web | [`docs/setup-web.md`](docs/setup-web.md) | Built-in HTTP |\n\n## Project Structure\n\n```\nharnessgate/\n├── src/\n│   ├── index.ts                # Barrel exports\n│   ├── bridge.ts               # Orchestrator\n│   ├── session-map.ts          # Session persistence\n│   ├── stream-manager.ts       # SSE stream lifecycle\n│   ├── platforms/\n│   │   ├── telegram-adapter.ts # grammY\n│   │   ├── discord-adapter.ts  # discord.js\n│   │   ├── slack-adapter.ts    # @slack/bolt\n│   │   ├── whatsapp-adapter.ts # Cloud API (fetch)\n│   │   ├── teams-adapter.ts    # Bot Framework SDK\n│   │   └── web-adapter.ts      # Built-in HTTP + SSE\n│   └── providers/\n│       ├── claude-provider.ts  # Claude Managed Agents\n│       └── http-provider.ts    # Generic HTTP\n├── docs/                       # Platform setup guides\n└── examples/                   # Starter projects\n```\n\n## Extending HarnessGate\n\nSee [CONTRIBUTING.md](CONTRIBUTING.md) for step-by-step guides on adding platforms and providers.\n\n### Provider interface\n\n```typescript\ninterface Provider {\n  readonly id: string;\n  readonly capabilities: ProviderCapabilities;\n\n  // Required — every provider\n  createSession(opts: CreateSessionOpts): Promise\u003cProviderSession\u003e;\n  sendMessage(sessionId: string, message: MessagePayload): Promise\u003cvoid\u003e;\n  stream(sessionId: string, signal: AbortSignal): AsyncIterable\u003cProviderEvent\u003e;\n  destroySession(sessionId: string): Promise\u003cvoid\u003e;\n\n  // Optional — capability-gated\n  interrupt?(sessionId: string): Promise\u003cvoid\u003e;\n  confirmTool?(sessionId: string, toolUseId: string, approved: boolean): Promise\u003cvoid\u003e;\n  submitToolResult?(sessionId: string, toolUseId: string, result: unknown): Promise\u003cvoid\u003e;\n}\n```\n\n### Platform interface\n\n```typescript\ninterface PlatformAdapter {\n  readonly id: string;\n  readonly capabilities: PlatformCapabilities;\n  start(ctx: PlatformContext): Promise\u003cvoid\u003e;\n  stop(): Promise\u003cvoid\u003e;\n  send(target: ChannelTarget, message: OutboundMessage): Promise\u003cSendResult\u003e;\n  sendTyping?(target: ChannelTarget): Promise\u003cvoid\u003e;\n  connect?(credentials: Record\u003cstring, unknown\u003e, ctx: PlatformContext): Promise\u003cstring\u003e;\n  disconnect?(appId: string): Promise\u003cvoid\u003e;\n  activeConnections?(): string[];\n}\n```\n\n## Requirements\n\n- Node.js \u003e= 22\n- pnpm\n\n## License\n\nMIT\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Faiwhiteteam%2Fharnessgate","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Faiwhiteteam%2Fharnessgate","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Faiwhiteteam%2Fharnessgate/lists"}