Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
50 commits
Select commit Hold shift + click to select a range
8b3325e
docs: v2 Intelligence migration spec + plan
jerelvelarde Jul 24, 2026
d0dfc2a
chore(deps): bump @copilotkit/channels* to ^0.2.1 (v2 managed-channels)
jerelvelarde Jul 24, 2026
ae6a3b1
chore(deps): raise @copilotkit/runtime floor to ^1.63.2 (v2 channels …
jerelvelarde Jul 24, 2026
701d70e
refactor(app): migrate app/** to @copilotkit/channels 0.2 (createChan…
jerelvelarde Jul 24, 2026
54ad077
docs+config: v2 Intelligence env/IaC/docs (API_URL/WS_URL/API_KEY, ki…
jerelvelarde Jul 24, 2026
3a1a062
feat(channel): migrate entry points to @copilotkit/channels 0.2 + v2 …
jerelvelarde Jul 24, 2026
67df75a
chore: drop redundant direct @copilotkit/channels-intelligence dep + …
jerelvelarde Jul 24, 2026
2037c55
fix(cr): address round-1 review findings
jerelvelarde Jul 25, 2026
05953ae
fix(cr): round-2 corrective batch — empty-section + newline-cell regr…
jerelvelarde Jul 27, 2026
078d4a5
fix(railway): install Chromium on the channel service for inline char…
jerelvelarde Jul 27, 2026
7f40b1d
fix(channel): three managed-path blockers from the PR 8/10 cross-review
jerelvelarde Jul 27, 2026
2d8c313
docs: move the migration discovery spike out of the repo root
jerelvelarde Jul 27, 2026
818638b
fix(railway): pin the channel build to the lockfile, drop redundant -…
jerelvelarde Jul 27, 2026
90b6290
ci: run check-types and tests on every PR
jerelvelarde Jul 27, 2026
b5b0221
fix(channel): gate the boot success line on channel status, not just …
jerelvelarde Jul 27, 2026
c875a88
docs: add the PR #10 review-fix implementation plan
jerelvelarde Jul 27, 2026
13de509
fix(channel): watch managed-channel health after boot, exit on a dead…
jerelvelarde Jul 27, 2026
60dad18
fix(channel): await server close on shutdown, stop mislabeling post-b…
jerelvelarde Jul 27, 2026
d84522f
test(channel): catch arrow-declared interactive components in the reg…
jerelvelarde Jul 27, 2026
3b0d7dd
docs(channel): scope the durability + identity claims, document CHANN…
jerelvelarde Jul 27, 2026
d699771
fix(channel): force-close in-flight connections on shutdown, harden t…
jerelvelarde Jul 27, 2026
4b34d42
docs(channel): document the fail-fast activation posture and the chan…
jerelvelarde Jul 27, 2026
bb20bb8
fix(channel): make shutdown authoritative — guard boot-time exits, bo…
jerelvelarde Jul 27, 2026
1dc4637
test(channel): make the closeServer tests reject order and timing mut…
jerelvelarde Jul 27, 2026
8785b24
fix(railway): grant the channel host a drain window, pin Railpack, wi…
jerelvelarde Jul 27, 2026
de17c7a
ci: pin the toolchain, scope the token, and bound the job
jerelvelarde Jul 27, 2026
cd9368c
fix(render): surface browser-close failures instead of swallowing them
jerelvelarde Jul 27, 2026
271f229
docs(channel): correct activation, CORS, restart-budget, PORT, and po…
jerelvelarde Jul 27, 2026
a9e690e
fix(channel): close the interactive-component scanner's open and clos…
jerelvelarde Jul 27, 2026
b0fd263
fix(channel): reject blank env, gate on overall channel status, make …
jerelvelarde Jul 27, 2026
06b9a81
fix(channel): make every fatal path exit through teardown, escalate s…
jerelvelarde Jul 27, 2026
fac620a
fix(channel): memoize sync throws in onceShutdown, route post-bind er…
jerelvelarde Jul 27, 2026
fdb2926
docs(channel): correct the registry, closeServer, and activation clai…
jerelvelarde Jul 27, 2026
5c12f65
ci: keep main runs uncancelled, name both Chromium-free mechanisms
jerelvelarde Jul 27, 2026
5f4f614
fix(railway): give notion-mcp and agent the drain windows their shutd…
jerelvelarde Jul 27, 2026
fb6bcea
docs: correct the restart budget, document the degraded escalation an…
jerelvelarde Jul 27, 2026
0e33f21
refactor(channel): parse the component scan with the TypeScript AST i…
jerelvelarde Jul 27, 2026
34c566a
fix(render): claim shutdown unconditionally, stop caching failed and …
jerelvelarde Jul 27, 2026
411ceb6
fix(channel): guard the listen callback, assert the declared channel …
jerelvelarde Jul 27, 2026
7c11146
docs(channel): fix the provider and post-stop citations, test the deg…
jerelvelarde Jul 27, 2026
6a90d7e
docs(channel): drop the last review-numbering label from a log message
jerelvelarde Jul 27, 2026
cf63847
fix(render): read CDN overrides lazily so .env works in channel mode,…
jerelvelarde Jul 28, 2026
4f1d641
fix(railway): derive Chromium's apt list from Playwright's native dep…
jerelvelarde Jul 28, 2026
b366314
fix(render): retain close failures and catch an already-dead browser …
jerelvelarde Jul 28, 2026
de719b4
docs: blank the committed sidecar secret, stop the template auto-enab…
jerelvelarde Jul 28, 2026
f175279
docs: sync the documented MERMAID_URL default with the pinned version
jerelvelarde Jul 28, 2026
e755397
fix(channel): climb to the component root, reject non-function export…
jerelvelarde Jul 28, 2026
0aa3274
fix(channel): one definition of channel liveness, shared by the boot …
jerelvelarde Jul 28, 2026
de49b18
fix(channel): make every exit path reachable, attributable, and consi…
jerelvelarde Jul 28, 2026
d158323
fix(channel): reject unresolved env templates in AGENT_URL, tighten P…
jerelvelarde Jul 28, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
110 changes: 83 additions & 27 deletions .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,10 @@
# ── Slack credentials (from api.slack.com/apps → your app) ──────────────
# Set both to run on Slack; leave blank to skip Slack.
# Bot Token: OAuth & Permissions page, "Bot User OAuth Token" (xoxb-...)
SLACK_BOT_TOKEN=xoxb-...
# SLACK_BOT_TOKEN=xoxb-...
# App-Level Token: Basic Information → App-Level Tokens → generate with the
# "connections:write" scope (xapp-...)
SLACK_APP_TOKEN=xapp-...
# SLACK_APP_TOKEN=xapp-...

# ── Discord credentials (from discord.com/developers/applications → your app) ──
# Set both to run on Discord; leave blank to skip Discord. Bot page → "Reset
Expand All @@ -27,38 +27,73 @@ SLACK_APP_TOKEN=xapp-...

# ── Agent backend (runtime.ts) ──────────────────────────────────────────
# The AG-UI endpoint the bridge POSTs to. REQUIRED — the bridge exits at
# startup if this is unset; there is no code-level fallback. The value below
# is a template pointing at the local CopilotKit runtime started by
# `pnpm runtime`; change it if you deploy the runtime elsewhere.
# startup if this is unset; there is no code-level fallback. Must include an
# explicit http:// or https:// scheme — a bare "host:port" (e.g. the Railway
# private-hostname shape `agent:8123`) fails boot, since it parses as a URL
# with the host folded into the protocol instead of a valid http(s) URL. Use
# e.g. `http://agent.railway.internal:8123/` on Railway. The value below is a
# template pointing at the local CopilotKit runtime started by `pnpm runtime`;
# change it if you deploy the runtime elsewhere.
AGENT_URL=http://localhost:8200/api/copilotkit/agent/triage/run
# Optional auth header forwarded to the agent (for a deployed runtime).
# AGENT_AUTH_HEADER=Bearer ...

# ── Intelligence channel mode — app/managed.ts (`pnpm channel`) ──────────
# Runs the SAME bot over the CopilotKit Intelligence Realtime Gateway instead
# of a native adapter — this process holds NO Slack tokens (Intelligence owns
# the Slack edge). The agent brain reuses AGENT_URL / AGENT_AUTH_HEADER above.
# All values come from your CopilotKit Intelligence project + channel setup.
# Runs the SAME KiteBot over the CopilotKit Intelligence Realtime Gateway
# instead of a native adapter — this process holds NO Slack tokens
# (Intelligence owns the Slack edge). Wired up via `createChannel` +
# `CopilotRuntime({ channels })` + `createCopilotNodeListener`
# (@copilotkit/runtime/v2) — there are NO org/project/channel IDs; the
# runtime derives its infra ids from the API creds below plus the channel
# name. AGENT_URL / AGENT_AUTH_HEADER (above) still point the channel at
# the agent brain.
# NOTE: the example values below point at CopilotKit's own dev Intelligence
# host (dev.intelligence.copilotkit.ai) — replace both with YOUR project's
# Intelligence endpoint before deploying.
# Base URL of your CopilotKit Intelligence deployment's app API.
# INTELLIGENCE_API_URL=https://dev.intelligence.copilotkit.ai
# Gateway runner WebSocket URL (the /runner endpoint).
# INTELLIGENCE_GATEWAY_WS_URL=wss://dev.intelligence.copilotkit.ai/runner
# Project runtime API key (cpk-{projectId}_...), presented as the socket auth token.
# Project runtime API key (cpk-{projectId}_...), presented as the socket auth
# token; secret — set in your secret store, not committed here.
# INTELLIGENCE_API_KEY=cpk-...
# Authoritative org/project/channel scope (from the Intelligence dashboard):
# INTELLIGENCE_ORG_ID=org_...
# INTELLIGENCE_PROJECT_ID=123
# INTELLIGENCE_CHANNEL_ID=channel_...
# The registered channel name (lowercase kebab). Defaults to "kitebot".
# INTELLIGENCE_CHANNEL_NAME=kitebot
# Optional stable runtime instance id; auto-generated (rti_...) if unset.
# INTELLIGENCE_RUNTIME_INSTANCE_ID=rti_...
# Registered channel name (lowercase kebab); defaults to kite-opentag.
# INTELLIGENCE_CHANNEL_NAME=kite-opentag
# PORT is read from this SAME .env by every entry point in this repo, each
# with its own default if unset — setting it here affects ALL of them, not
# just the channel host:
# app/managed.ts (`pnpm channel`) → 8300
# app/index.ts (`pnpm dev`) → 3000 (also used by the WhatsApp webhook, below)
# runtime.ts (`pnpm runtime`)→ 8200
# agent/main.py (`pnpm agent`) → 8123 (reads PORT before SERVER_PORT — see setup.md)
# You normally run only ONE of these processes per terminal, each reading its
# own default, so leave PORT unset unless you need to override one of them —
# uncommenting it here moves that process off its default (e.g. it would move
# `runtime.ts` off :8200 while AGENT_URL above still points at :8200, breaking
# the two-terminal workflow with no obvious error). On Railway each service
# supplies its own injected $PORT, which always wins over this file.
# PORT=8300
# The channel host's HTTP routes (agent/run, threads/*, memories/*) are CLOSED
# by default — the managed channel activates over the gateway WebSocket, not
# over HTTP, so nothing needs them and an open port would be an unauthenticated
# proxy to AGENT_URL. Set this to open them behind `Authorization: Bearer <token>`
# for server-to-server or curl use (e.g. scripting agent/run from another
# backend or hitting it manually). This is NOT a browser-facing surface: no
# `cors` option is passed to the listener, so preflight `OPTIONS` requests get
# no `Access-Control-Allow-Origin` and a browser can't call it — pointing a
# CopilotKit frontend directly at this runtime will not work. Secret.
# CHANNEL_HTTP_TOKEN=

# Model for the BuiltInAgent. The runtime is OpenAI-only (it uses OpenAI's
# Responses API via TanStack AI's `openaiText` adapter, needed for the
# `web_search` provider tool) — there is no multi-provider resolution.
# AGENT_MODEL only accepts an OpenAI model id, optionally prefixed with
# "openai/" (the prefix is stripped); defaults to openai/gpt-5.5.
# AGENT_MODEL=openai/gpt-5.5
OPENAI_API_KEY=sk-...
# Required — the OpenAI adapter throws if this is not found in the
# environment (fails loudly, not silently). Set your own key; the example
# shape is sk-...
OPENAI_API_KEY=
# ANTHROPIC_API_KEY / GOOGLE_API_KEY are NOT supported by this runtime today
# (runtime.ts never reads them) — do not set AGENT_MODEL to an anthropic/... or
# google/... value.
Expand All @@ -83,7 +118,7 @@ OPENAI_API_KEY=sk-...
# 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.
# Leave LINEAR_API_KEY blank to run without Linear.
LINEAR_API_KEY=lin_api_...
# LINEAR_API_KEY=lin_api_...
# Override only if you front the MCP yourself; default is the hosted server.
# LINEAR_MCP_URL=https://mcp.linear.app/mcp
# Default Linear team the bot files/queries against.
Expand All @@ -94,11 +129,13 @@ LINEAR_TEAM_KEY=CPK
# NOTION_TOKEN is the Notion integration secret the sidecar uses to call the
# Notion API (notion.so → Settings → Connections → develop integrations).
# NOTION_MCP_AUTH_TOKEN is the bearer the sidecar requires on its HTTP
# transport; the agent sends it. Pick any strong string and use it in both
# `pnpm notion-mcp` and here. Leave NOTION_MCP_AUTH_TOKEN blank to run
# without Notion.
NOTION_TOKEN=ntn_...
NOTION_MCP_AUTH_TOKEN=choose-a-strong-shared-secret
# transport; the agent sends it. Pick your own strong string and use the SAME
# value in both `pnpm notion-mcp` and here — do not use a well-known or
# example value, since this token is the only thing protecting the sidecar
# (and the real NOTION_TOKEN behind it). Leave NOTION_MCP_AUTH_TOKEN blank to
# run without Notion.
# NOTION_TOKEN=ntn_...
NOTION_MCP_AUTH_TOKEN=
# Override only if the sidecar runs elsewhere; default is the local sidecar.
# NOTION_MCP_URL=http://127.0.0.1:3001/mcp
# Port the `pnpm notion-mcp` sidecar listens on (scripts/start-notion-mcp.ts).
Expand All @@ -107,6 +144,21 @@ NOTION_MCP_AUTH_TOKEN=choose-a-strong-shared-secret
# Notion-Version header the sidecar sends to the Notion API. Defaults to
# 2022-06-28; override only if you need a newer Notion API version.
# NOTION_VERSION=2022-06-28
# Host the sidecar binds to (scripts/start-notion-mcp.ts). Defaults to
# 127.0.0.1 (loopback), matching the upstream server's own default — fine for
# local dev. Railway's IaC (.railway/railway.ts) sets this to "::" so the
# sidecar listens on all interfaces and is reachable over the (IPv6) private
# network from the agent service; you shouldn't need to touch this locally.
# NOTION_MCP_HOST=127.0.0.1

# ── Chart & diagram rendering (app/render/) ──────────────────────────────
# Chart.js and Mermaid are loaded from a CDN into a LOCAL headless browser
# (app/render/chart.ts, app/render/diagram.ts) to draw charts/diagrams — your
# chart/diagram DATA is rendered locally and never sent anywhere; only the
# rendering library itself is fetched from the CDN. Override either to pin a
# different version or point at a self-hosted mirror.
# CHART_JS_URL=https://cdn.jsdelivr.net/npm/chart.js@4.4.3/dist/chart.umd.js
# MERMAID_URL=https://cdn.jsdelivr.net/npm/mermaid@11.16.0/dist/mermaid.min.js

# ── WhatsApp Cloud API (optional — omit to run Slack-only) ──────────────
# From your Meta App → WhatsApp → API Setup. Leave WHATSAPP_ACCESS_TOKEN blank
Expand All @@ -116,6 +168,10 @@ WHATSAPP_ACCESS_TOKEN=
WHATSAPP_PHONE_NUMBER_ID=
WHATSAPP_APP_SECRET=
WHATSAPP_VERIFY_TOKEN=choose-any-string-and-paste-it-in-the-webhook-config
# Webhook path (default /webhook). The server listens on $PORT — Railway injects
# it and routes the service's public domain there; locally it defaults to 3000.
# Webhook path (default /webhook). WhatsApp runs inside app/index.ts
# (`pnpm dev`), which listens on $PORT — Railway injects it and routes the
# service's public domain there; locally it defaults to 3000 if PORT is unset.
# See the shared PORT note above (Intelligence channel mode block) — PORT is
# one variable read by every entry point in this repo with a different
# default each.
# WHATSAPP_PATH=/webhook
43 changes: 43 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
name: CI

on:
push:
branches: [main]
pull_request:

permissions:
contents: read

concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
# main is exempt: it's the deploy source, so a superseding push must never
# cancel an in-flight run and leave an earlier commit without a green check.
cancel-in-progress: ${{ github.ref != 'refs/heads/main' }}

jobs:
check:
runs-on: ubuntu-latest
timeout-minutes: 10
steps:
- uses: actions/checkout@v4

# pnpm version comes from the `packageManager` field in package.json —
# no `version` input needed here; that's the single source of truth.
- uses: pnpm/action-setup@v4

- uses: actions/setup-node@v4
with:
node-version: 22
cache: pnpm

# No `npx playwright install` here on purpose: every test that would need
# a headless browser is mocked at one of two layers — the renderer mocks
# in app/tools/__tests__/render-tools.test.ts (mocks ../../render/chart.js
# and ../../render/diagram.js) and the playwright-module mock in
# app/render/__tests__/browser.test.ts (mocks the playwright module
# directly) — so CI needs no Chromium.
- run: pnpm install --frozen-lockfile

- run: pnpm check-types

- run: pnpm test
Loading
Loading