Skip to content

Latest commit

 

History

History
301 lines (231 loc) · 17.3 KB

File metadata and controls

301 lines (231 loc) · 17.3 KB

OpenTag — setup & configuration

Everything beyond the quick start: the full Slack app walkthrough, the complete environment reference, running standalone vs. from the monorepo, the Intelligence Gateway channel mode, wiring up Linear / Notion / inline charts, the other chat platforms, slash commands, tests, and how the pieces fit together.

How it fits together

Slack / Discord / Telegram / WhatsApp ──@mention──▶  KiteBot (app/)  ──AG-UI──▶  runtime (runtime.ts)
                                                          │  BuiltInAgent (LLM)
                                                          ├── Linear  MCP  (hosted)
                                                          └── Notion  MCP  (sidecar)

Three moving parts: the chat-platform app(s) in app/, the agent (runtime.ts), and — if you use Notion — a small Notion MCP sidecar. KiteBot speaks to the agent over AG-UI; the agent is one CopilotKit BuiltInAgent (an LLM plus optional MCP tools — no Python, no LangGraph).

KiteBot runs in one of two modes: self-hosted (pnpm devapp/index.ts, holds the Slack tokens directly) or Intelligence Gateway (pnpm channelapp/managed.ts, over the CopilotKit Intelligence Realtime Gateway — see Intelligence channel mode). Both modes talk to the same agent backend (pnpm runtimeruntime.ts) via AGENT_URL.

Concept Where
createBot({ adapters, agent, tools, context, commands }) app/index.ts
Multi-adapter wiring (Slack/Discord/Telegram/WhatsApp, secret-gated) app/index.ts
read_thread — grounds the agent in the real conversation app/tools/read-thread.ts
Render-tools + JSX components (issue card/list, Notion pages) app/tools/render-tools.tsx, app/components/
Chart / diagram rendering (Playwright → PNG) app/tools/render-chart.tsx, render-diagram.tsx, app/render/
Table rendering (native <Table> block, monospace fallback) app/tools/render-table.tsx
Status / incident / links showcase cards app/tools/showcase-tools.tsx, app/components/_status.ts
Blocking human-in-the-loop gate (confirm_write) app/human-in-the-loop/confirm-write.tsx
Slash commands (/agent, /triage, /preview, /file-issue) app/commands/index.ts
A Block Kit modal (/file-issue) app/modals/file-issue.tsx
The agent backend — one BuiltInAgent (LLM + Linear/Notion MCP) runtime.ts
  • app/ is the platform-agnostic KiteBot code. This is the directory you copy to start your own bot.
  • runtime.ts is the agent backend, served over AG-UI.
  • e2e/ holds live test harnesses (the Slack harness is being migrated to the new createBot API; the Telegram harness is a working manual-trigger smoke test — see e2e/TELEGRAM-README.md).

It's built on:

Running it

From the monorepo

If you're working inside the CopilotKit monorepo, this code runs there as examples/slack, whose package.json name is slack-example (this standalone repo's package.json name is opentag — the --filter below only resolves inside the monorepo), building the @copilotkit/channels* adapters from source:

pnpm install                              # repo root
pnpm --filter slack-example notion-mcp    # only if using Notion → http://127.0.0.1:3001/mcp
pnpm --filter slack-example runtime       # CopilotKit runtime on :8200, agent "triage"
pnpm --filter slack-example dev           # KiteBot (tsx watch app/index.ts)

Standalone (npm)

The @copilotkit/channels* packages are published on npm, so a plain npm install here works as-is — no monorepo required:

npm install
npm run notion-mcp     # terminal 1 — only if using Notion
npm run runtime        # terminal 2 — the agent backend on :8200
npm run dev            # terminal 3 — KiteBot, self-hosted (holds the Slack tokens)

The chart/diagram renderers need a Chromium binary: npx playwright install chromium.

Deep research (LangGraph deep agent)

agent/ is an alternative agent backend to runtime.ts: a Python deepagents (LangGraph) planner with a virtual filesystem and OPTIONAL Tavily web research, served over AG-UI on :8123. Instead of runtime.ts's single system-prompted BuiltInAgent call, it plans with write_todos, reads/writes its own virtual files, and — when configured — researches the web before synthesizing an answer, all while still calling KiteBot's forwarded generative-UI tools the same way the TS runtime does.

Setup — requires uv and Python 3.12:

cd agent && uv sync

Copy agent/.env.example to agent/.env and fill it in:

Variable What it's for
OPENAI_API_KEY Required — the model.
TAVILY_API_KEY Optional — turns on live web research. Without it the agent still chats and generates UI components, answering from its own knowledge.
OPENAI_MODEL Defaults to gpt-5.5, matching the rest of OpenTag.
SERVER_HOST / SERVER_PORT Defaults to 0.0.0.0:8123.

Run it:

pnpm agent   # cd agent && uv run python main.py (port from SERVER_PORT/PORT env, default 8123)

Then point the bot at it instead of runtime.ts by setting in the root .env:

AGENT_URL=http://localhost:8123/

With the deep agent in the mix, a local setup is three processes: pnpm agent (the Python deep-research brain, :8123), the bot (pnpm channel or pnpm dev), and — if you're using runtime.ts instead — pnpm runtime (:8200). agent and runtime are two alternative brains for the same bot; run whichever one AGENT_URL points at.

Intelligence channel mode

pnpm channel (app/managed.ts) runs the same KiteBot over the CopilotKit Intelligence Realtime Gateway instead of a native platform adapter — this process holds no Slack tokens; Intelligence owns the Slack edge (signed ingress + Connector Outbox egress) and streams render frames back over @copilotkit/channels-intelligence. It's the Intelligence Gateway counterpart to the self-hosted pnpm dev mode described above — you still run this process yourself and bring your own CopilotKit Intelligence project.

npm run runtime        # terminal 1 — the agent backend on :8200 (same as self-hosted)
npm run channel        # terminal 2 — the Intelligence Gateway KiteBot (tsx app/managed.ts)

Configure it with:

Variable What it's for
INTELLIGENCE_GATEWAY_WS_URL The Intelligence Realtime Gateway websocket endpoint.
INTELLIGENCE_API_KEY Auth for the gateway connection.
INTELLIGENCE_ORG_ID / INTELLIGENCE_PROJECT_ID / INTELLIGENCE_CHANNEL_ID Scopes the connection to your Intelligence org/project/channel.
INTELLIGENCE_CHANNEL_NAME The registered channel name (lowercase kebab). Defaults to kitebot.

The agent backend is still required in this mode — pnpm runtime (runtime.ts) — the Intelligence channel host points its AGENT_URL at it exactly like the self-hosted KiteBot does. AGENT_URL itself is required in every mode (the process exits at startup if it's unset); .env.example ships it pre-filled with the local runtime URL as a template, not as a code-level default. See .env.example for the full annotated list.

1. Create a Slack app

  1. Go to https://api.slack.com/apps?new_app=1From a manifest → paste slack-app-manifest.yaml. The manifest declares all four slash commands, the assistant pane, the users:read.email scope, and Socket Mode (so KiteBot connects outbound — no public URL needed).
  2. OAuth & PermissionsInstall to Workspace → copy the xoxb- Bot User OAuth Token → this is your SLACK_BOT_TOKEN.
  3. Basic Information → App-Level Tokens → generate one with the connections:write scope → copy the xapp- token → this is your SLACK_APP_TOKEN.

(Discord, Telegram, and WhatsApp setup is documented inline in .env.example and summarized under Other platforms.)

2. Environment variables

Copy the template and fill in the platform(s) and integrations you want — KiteBot starts an adapter for each platform whose secrets are present, and the agent wires up whichever data sources have credentials. (Running in Intelligence channel mode instead uses the INTELLIGENCE_* variables in place of the platform tokens below.)

cp .env.example .env
Variable What it's for
SLACK_BOT_TOKEN / SLACK_APP_TOKEN Run on Slack (see step 1).
OPENAI_API_KEY The model. Required — the runtime is OpenAI-only (it runs on the OpenAI Responses API, needed for web_search); ANTHROPIC_API_KEY / GOOGLE_API_KEY are not read by this runtime.
AGENT_MODEL OpenAI model id override, optionally prefixed openai/ (stripped). Defaults to openai/gpt-5.5.
LINEAR_API_KEY / LINEAR_TEAM_KEY Wire up Linear (linear.app → Settings → API → Personal API keys).
NOTION_TOKEN / NOTION_MCP_AUTH_TOKEN Wire up Notion (see Notion).
DISCORD_BOT_TOKEN / DISCORD_APP_ID Run on Discord.
TELEGRAM_BOT_TOKEN Run on Telegram.
WHATSAPP_ACCESS_TOKEN (+ siblings) Run on WhatsApp Cloud API.
INTELLIGENCE_GATEWAY_WS_URL / INTELLIGENCE_API_KEY / INTELLIGENCE_ORG_ID / INTELLIGENCE_PROJECT_ID / INTELLIGENCE_CHANNEL_ID / INTELLIGENCE_CHANNEL_NAME Run in Intelligence channel mode instead of holding platform tokens directly.
AGENT_URL Where KiteBot POSTs. Required — the process exits at startup if unset; the template value points at the local runtime (…/agent/triage/run).

Every integration is independent — set only what you need. The full annotated list, including the WhatsApp webhook details, is in .env.example.

3. Integrations

Linear

The hosted Linear MCP accepts a raw API key as a bearer token (no OAuth dance). Create one at linear.app → Settings → API → Personal API keys, set LINEAR_API_KEY, and optionally LINEAR_TEAM_KEY (the default team to file/query against). Leave LINEAR_API_KEY blank to run without Linear. With it set, the agent can:

  • Query Linear"what's open in CPK this cycle?" → renders the issues as a rich card.
  • File a Linear issue"file this thread as a bug" → drafts it, asks you to confirm, then creates it.

Notion

Notion runs as a small Streamable-HTTP sidecar wrapping the official @notionhq/notion-mcp-server. Start it with pnpm notion-mcp (or npm run notion-mcp).

  • NOTION_TOKEN — the Notion integration secret the sidecar uses to call the Notion API (notion.so → Settings → Connections → develop integrations).
  • NOTION_MCP_AUTH_TOKEN — a bearer the sidecar requires on its HTTP transport; pick any strong string and set the same value here and when starting the sidecar. Leave it blank to run without Notion.

With it set, the agent can find pages ("find the runbook for the auth outage") and write a postmortem ("write this thread up as a Notion doc" → reads, summarizes, confirms, then creates the page).

The human-in-the-loop write gate

Every write — Linear or Notion — goes through a blocking confirm_write gate: the agent must call that tool and wait for a Create / Cancel click before it performs the write. See app/human-in-the-loop/confirm-write.tsx.

Charts, diagrams & tables

The chart/diagram libraries load from a CDN into a local headless browser (override CHART_JS_URL / MERMAID_URL) — your data is rendered locally and never sent to a rendering service. Requires a Chromium binary: npx playwright install chromium.

Other platforms

The same app/ code runs on every platform — createBot takes an array of adapters, and app/index.ts starts one for each platform whose secrets are present. Everything else (tools, components, the HITL gate, rendering) is shared verbatim.

  • Discord — set DISCORD_BOT_TOKEN + DISCORD_APP_ID (and optionally DISCORD_GUILD_ID for instant slash-command registration in dev). Enable the Message Content and Server Members privileged intents.
  • Telegram — message @BotFather/newbot → set TELEGRAM_BOT_TOKEN. Long-polling is the default ingress (no public URL needed).
  • WhatsApp — set WHATSAPP_ACCESS_TOKEN + siblings from your Meta App → WhatsApp → API Setup. The server listens on $PORT for the webhook.

Per-platform details are documented inline in .env.example.

Slash commands

Four app-owned commands, registered via createBot({ commands }) (app/commands/index.ts):

  • /agent <text> — a mention-free entry point; runs the agent with the command text.
  • /triage [note] — summarizes the conversation and proposes issues to file.
  • /preview <title> — privately previews the issue KiteBot would file (only you see it); degrades to a DM where ephemerals aren't supported.
  • /file-issue — opens a structured issue modal; degrades to a conversational flow on platforms without modals (e.g. Telegram).

On Slack, all four must be declared under Slash Commands — the manifest already does this.

Files → charts, diagrams & tables

Upload a file and KiteBot analyzes it: images and PDFs go straight to the model; CSV/JSON/text are decoded and handed over as text. Then ask it to visualize:

chart revenue by month · diagram this incident flow · show it as a table

PDFs and images need a vision/document-capable model. The runtime is OpenAI-only, and the default openai/gpt-5.5 reads both natively. If you override AGENT_MODEL, pick another vision/document-capable OpenAI model — non-OpenAI model ids (Claude, Gemini, etc.) are not supported by this runtime.

Tests

npm test               # unit: read_thread, render tools, components, confirm_write, modals, commands
npm run check-types    # tsc --noEmit

The live-Slack e2e harness (npm run e2e) is being migrated to the new createBot API and doesn't run against this code as-is. The Telegram harness (npm run e2e:telegram) is a working manual-trigger smoke test — see e2e/TELEGRAM-README.md.