From 8b3325e912e9a394683ce9ce7418b5bec6a2e3df Mon Sep 17 00:00:00 2001 From: GeneralJerel <85066839+GeneralJerel@users.noreply.github.com> Date: Fri, 24 Jul 2026 08:59:02 -0700 Subject: [PATCH 01/50] docs: v2 Intelligence migration spec + plan Co-Authored-By: Claude Opus 4.8 --- ...07-24-opentag-intelligence-v2-migration.md | 328 ++++++++++++++++++ ...pentag-intelligence-v2-migration-design.md | 96 +++++ 2 files changed, 424 insertions(+) create mode 100644 docs/superpowers/plans/2026-07-24-opentag-intelligence-v2-migration.md create mode 100644 docs/superpowers/specs/2026-07-24-opentag-intelligence-v2-migration-design.md diff --git a/docs/superpowers/plans/2026-07-24-opentag-intelligence-v2-migration.md b/docs/superpowers/plans/2026-07-24-opentag-intelligence-v2-migration.md new file mode 100644 index 0000000..e607107 --- /dev/null +++ b/docs/superpowers/plans/2026-07-24-opentag-intelligence-v2-migration.md @@ -0,0 +1,328 @@ +# OpenTag Intelligence v2 Managed-Channels Migration — Implementation Plan + +> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking. + +**Goal:** Migrate OpenTag from the deprecated `@copilotkit/channels-intelligence` gateway launcher to the published v2 managed-channels runtime so `pnpm channel` connects to the `opentag-dev` Intelligence project and serves the `kite-opentag` channel. + +**Architecture:** Bump `@copilotkit/channels*` from `0.1.x` to `0.2.x` repo-wide; rewrite `app/managed.ts` to build a `createChannel({ name: "kite-opentag" })`, hand it to `new CopilotRuntime({ agents:{}, intelligence, identifyUser, channels })`, and mount it with `createCopilotNodeListener` (mounting activates the channel; the runtime derives org/project/channel ids from the Intelligence creds + channel name). The Python `agent/` (brain) is unchanged; the channel forwards turns to it over `AGENT_URL` via `SanitizingHttpAgent`. + +**Tech Stack:** TypeScript, tsx, `@copilotkit/channels@^0.2`, `@copilotkit/runtime@^1.63 (/v2, /v2/node)`, `@ag-ui/client`, vitest, pnpm. + +## Global Constraints + +- Channel name is **`kite-opentag`** (default via `INTELLIGENCE_CHANNEL_NAME`, verbatim — must match the registered Intelligence channel). +- Intelligence env is exactly **`INTELLIGENCE_API_URL`**, **`INTELLIGENCE_GATEWAY_WS_URL`**, **`INTELLIGENCE_API_KEY`**, **`AGENT_URL`** — no `ORG_ID`/`PROJECT_ID`/`CHANNEL_ID`. +- `INTELLIGENCE_API_URL` = `https://dev.intelligence.copilotkit.ai`; `INTELLIGENCE_GATEWAY_WS_URL` = `wss://dev.intelligence.copilotkit.ai/runner`. +- Both run modes must keep working: `pnpm dev`/`pnpm start` (self-hosted) AND `pnpm channel` (managed). +- Secrets (`INTELLIGENCE_API_KEY`, `OPENAI_API_KEY`, Slack tokens) are set by the operator — never hardcode or commit them. +- Keep changes minimal and follow existing file/style conventions; don't restructure unrelated code. + +--- + +### Task 1: Discovery spike — bump deps, enumerate real 0.1→0.2 breakage + +**Files:** +- Modify: `package.json` (dependency versions only) +- Produce (scratch, not committed): a breakage list used by Tasks 2–4 + +**Interfaces:** +- Produces: the authoritative list of (a) whether `createBot` still exists or is renamed `createChannel`; (b) `createChannel`/`CreateChannelOptions` exact signature in `@copilotkit/channels@0.2.x`; (c) `CopilotRuntime` v2 `channels` option + `createCopilotNodeListener` return type (`.channels?.stop()`); (d) the Slack helper import path (`@copilotkit/channels-slack` vs `@copilotkit/channels/slack`) and `SanitizingHttpAgent`/`defaultSlackTools`/`defaultSlackContext` availability; (e) the full `tsc --noEmit` error list across `app/**` and `runtime.ts`. + +- [ ] **Step 1: Create the feature branch/worktree** (execution-time; via superpowers:using-git-worktrees). Branch name: `jerel/opentag-intelligence-v2`. + +- [ ] **Step 2: Bump the CopilotKit channel deps in `package.json`** + +Set these versions (leave `@copilotkit/runtime` — `^1.62.3` already resolves to ≥1.63.2): +```jsonc +"@copilotkit/channels": "^0.2.1", +"@copilotkit/channels-discord": "^0.2.1", +"@copilotkit/channels-intelligence": "^0.2.1", +"@copilotkit/channels-slack": "^0.2.1", +"@copilotkit/channels-telegram": "^0.2.1", +"@copilotkit/channels-ui": "^0.2.1", +"@copilotkit/channels-whatsapp": "^0.2.1" +``` +(If any `-discord/-telegram/-whatsapp/-ui` package has no `0.2.x` on npm, run `npm view version` and pin the newest available; record it.) + +- [ ] **Step 3: Install** + +Run: `pnpm install` +Expected: resolves; note any peer-dep warnings. + +- [ ] **Step 4: Capture the real API from installed types** + +Run and read (do not guess): +```bash +sed -n '1,60p' node_modules/@copilotkit/channels-core/dist/index.d.ts | grep -nE 'createChannel|CreateChannelOptions|createBot' +ls node_modules/@copilotkit/runtime/dist/v2/ && grep -rnE 'channels\??:|createCopilotNodeListener|class CopilotRuntime' node_modules/@copilotkit/runtime/dist/v2/*.d.ts | head +grep -rnE 'SanitizingHttpAgent|defaultSlackTools|defaultSlackContext' node_modules/@copilotkit/channels-slack/dist/*.d.ts 2>/dev/null || ls node_modules/@copilotkit/channels/dist/slack* 2>/dev/null +``` +Record the exact `createChannel` options type, whether `createBot` still exists, the `CopilotRuntime` `channels` option, `createCopilotNodeListener` signature/return, and the Slack helper import path. + +- [ ] **Step 5: Enumerate breakage** + +Run: `pnpm check-types` (i.e. `tsc --noEmit -p tsconfig.json`) +Expected: a list of errors in `app/index.ts`, `runtime.ts`, `app/**`. **Save the full error list** — it drives Tasks 2–3. Do NOT fix yet. + +- [ ] **Step 6: Commit the dep bump** + +```bash +git add package.json pnpm-lock.yaml +git commit -m "chore(deps): bump @copilotkit/channels* to ^0.2.1 (v2 managed-channels)" +``` + +--- + +### Task 2: Restore self-hosted path + shared `app/` to compile on 0.2.x + +**Files:** +- Modify (per Task 1 breakage list): `app/index.ts`, `runtime.ts`, and any `app/**` files that fail typecheck (likely `app/tools/*`, `app/human-in-the-loop/*`, `app/components/*` if `createBot`→`createChannel` or tool-def types changed). +- Test: existing `app/**/__tests__/*` + `runtime`-adjacent tests. + +**Interfaces:** +- Consumes: Task 1's breakage list + exact signatures. +- Produces: a repo that typechecks and whose existing vitest suite passes on 0.2.x, with `app/index.ts` still starting the self-hosted bot. + +- [ ] **Step 1: Apply the mechanical rename/signature fixes** from Task 1 (e.g. if `createBot` is gone, switch `app/index.ts` to the 0.2.x self-hosted constructor — `createChannel({ adapters })` or the documented equivalent — using the exact signature captured in Task 1). Show the concrete edit for each failing file as you go; keep behavior identical. + +- [ ] **Step 2: Run typecheck** + +Run: `pnpm check-types` +Expected: PASS (0 errors). + +- [ ] **Step 3: Run the existing test suite** + +Run: `pnpm test` +Expected: PASS (fix any test that references renamed symbols to use the new names — do not weaken assertions). + +- [ ] **Step 4: Smoke the self-hosted entry compiles/starts** (no tokens needed to fail fast on import errors) + +Run: `node --check <(npx tsc --noEmit) ` is not valid; instead: `pnpm start` and confirm it fails only on missing Slack tokens (not on import/type errors), then Ctrl-C. +Expected: process reaches "missing SLACK_* / no adapters" rather than a module/type crash. + +- [ ] **Step 5: Commit** + +```bash +git add -A +git commit -m "fix: compile self-hosted path + app/ on @copilotkit/channels@0.2.x" +``` + +--- + +### Task 3: Rewrite `app/managed.ts` to the v2 managed-channels runtime + +**Files:** +- Modify: `app/managed.ts` +- Test: `app/managed.test.ts` + +**Interfaces:** +- Consumes: Task 1 signatures (`createChannel`, `CopilotRuntime` v2 `channels`, `createCopilotNodeListener`), and OpenTag's existing `appTools`/`appContext`/`appCommands`, `senderContext`, `fileIssueSubmit`/`FILE_ISSUE_CALLBACK`, `closeBrowser`, `SanitizingHttpAgent`/`defaultSlackTools`/`defaultSlackContext`. +- Produces: `createKiteChannel(opts)` (pure, testable, returns the `Channel`) + a `main()` that builds the runtime and mounts the node listener. + +- [ ] **Step 1: Update `app/managed.test.ts`** to assert the pure builder shape (mirrors the existing test's intent): a `createKiteChannel({ agentUrl, channelName })` returns a channel whose `name === "kite-opentag"` by default, and `parseProjectId` is removed (no longer used). Write the failing test first (import will fail). + +```ts +import { describe, it, expect } from "vitest"; +import { createKiteChannel } from "./managed.js"; +describe("createKiteChannel", () => { + it("defaults the channel name to kite-opentag", () => { + const ch = createKiteChannel({ agentUrl: "http://localhost:8123/" }); + expect(ch.name).toBe("kite-opentag"); + }); +}); +``` + +- [ ] **Step 2: Run it to confirm it fails** + +Run: `pnpm test -- app/managed.test.ts` +Expected: FAIL (`createKiteChannel` not exported). + +- [ ] **Step 3: Rewrite `app/managed.ts`** to this shape (adjust import paths/signatures to Task 1's captured reality): + +```ts +import "dotenv/config"; +import { createServer } from "node:http"; +import { createChannel } from "@copilotkit/channels"; +import type { Channel } from "@copilotkit/channels"; +import { SanitizingHttpAgent, defaultSlackTools, defaultSlackContext } from "@copilotkit/channels-slack"; +import { CopilotRuntime, CopilotKitIntelligence } from "@copilotkit/runtime/v2"; +import { createCopilotNodeListener } from "@copilotkit/runtime/v2/node"; +import { appTools } from "./tools/index.js"; +import { appContext } from "./context/app-context.js"; +import { appCommands } from "./commands/index.js"; +import { senderContext } from "./sender-context.js"; +import { fileIssueSubmit, FILE_ISSUE_CALLBACK } from "./modals/file-issue.js"; +import { closeBrowser } from "./render/browser.js"; + +const required = (name: string): string => { + const v = process.env[name]; + if (!v) { console.error(`Missing required env var: ${name}`); process.exit(1); } + return v; +}; + +export interface CreateKiteChannelOptions { + agentUrl: string; + agentAuthHeader?: string; + channelName?: string; +} + +export function createKiteChannel(opts: CreateKiteChannelOptions): Channel { + const channelName = opts.channelName ?? "kite-opentag"; + const headers = opts.agentAuthHeader ? { Authorization: opts.agentAuthHeader } : undefined; + const channel = createChannel({ + name: channelName, + agent: (threadId: string) => { + const a = new SanitizingHttpAgent({ url: opts.agentUrl, headers }); + a.threadId = threadId; + return a; + }, + tools: [...appTools, ...defaultSlackTools], + context: [...appContext, ...defaultSlackContext], + commands: appCommands, + }); + channel.onMention(async ({ thread, message }) => { + try { + await thread.runAgent({ + prompt: message.contentParts?.length ? message.contentParts : message.text, + context: senderContext(message.user, thread.platform), + }); + } catch (err) { + console.error("[channel] agent run failed", err); + await thread.post("Sorry — I hit an error handling that. Please try again.") + .catch((e: unknown) => console.error("[channel] failed to post agent error", e)); + } + }); + channel.onModalSubmit(FILE_ISSUE_CALLBACK, fileIssueSubmit); + channel.onThreadStarted(async ({ thread, user }) => { + if (!user?.name) return; + try { + await thread.setSuggestedPrompts([ + { title: `Triage ${user.name}'s issues`, message: "Triage my open issues" }, + { title: "What shipped this week?", message: "Summarize what shipped this week" }, + ]); + } catch (err) { console.error("[channel] onThreadStarted failed", err); } + }); + return channel; +} + +async function main() { + const channel = createKiteChannel({ + agentUrl: required("AGENT_URL"), + agentAuthHeader: process.env.AGENT_AUTH_HEADER, + channelName: process.env.INTELLIGENCE_CHANNEL_NAME, + }); + const intelligence = new CopilotKitIntelligence({ + apiUrl: required("INTELLIGENCE_API_URL"), + wsUrl: required("INTELLIGENCE_GATEWAY_WS_URL"), + apiKey: required("INTELLIGENCE_API_KEY"), + }); + const runtime = new CopilotRuntime({ + agents: {}, + intelligence, + identifyUser: () => ({ id: "opentag-runtime", name: "OpenTag" }), // refine per Task 1 identifyUser type + channels: [channel], + }); + const rawPort = process.env["PORT"]; + const port = rawPort && rawPort.trim() !== "" ? Number(rawPort) : 8300; + if (!Number.isInteger(port) || port < 1 || port > 65535) { + console.error(`Invalid PORT: "${rawPort}"`); process.exit(1); + } + const listener = createCopilotNodeListener({ runtime, basePath: "/api/copilotkit" }); + const server = createServer(listener).listen(port, () => { + console.log(`[channel] KiteBot channel "${channel.name}" mounted on :${port} → gateway`); + }); + const shutdown = async (signal: string) => { + console.log(`\n[channel] received ${signal}, stopping…`); + let code = 0; + try { await listener.channels?.stop(); } catch (e) { console.error("[channel] stop failed", e); code = 1; } + server.close(); + await closeBrowser().catch((e: unknown) => console.error("[channel] browser cleanup failed", e)); + process.exit(code); + }; + process.on("SIGINT", () => void shutdown("SIGINT")); + process.on("SIGTERM", () => void shutdown("SIGTERM")); +} + +process.on("unhandledRejection", (r) => console.error("[channel] unhandledRejection:", r)); +if (process.argv[1] && process.argv[1].endsWith("managed.ts")) { + main().catch((e: unknown) => { console.error("[channel] fatal:", e); process.exit(1); }); +} +``` + +- [ ] **Step 4: Run the test** + +Run: `pnpm test -- app/managed.test.ts` +Expected: PASS. + +- [ ] **Step 5: Typecheck the whole repo** + +Run: `pnpm check-types` +Expected: PASS. + +- [ ] **Step 6: Commit** + +```bash +git add app/managed.ts app/managed.test.ts +git commit -m "feat(channel): rewrite managed.ts to v2 managed-channels runtime" +``` + +--- + +### Task 4: Update env template, Railway IaC, and docs + +**Files:** +- Modify: `.env.example`, `.railway/railway.ts`, `README.md`, `setup.md` + +**Interfaces:** +- Consumes: the Global Constraints var set. +- Produces: operator-facing config matching the new runtime. + +- [ ] **Step 1: `.env.example`** — in the Intelligence block, remove `INTELLIGENCE_ORG_ID`, `INTELLIGENCE_PROJECT_ID`, `INTELLIGENCE_CHANNEL_ID`, `INTELLIGENCE_RUNTIME_INSTANCE_ID`; add `INTELLIGENCE_API_URL=https://dev.intelligence.copilotkit.ai`; keep `INTELLIGENCE_GATEWAY_WS_URL=wss://dev.intelligence.copilotkit.ai/runner`, `INTELLIGENCE_API_KEY=cpk-...`; set `INTELLIGENCE_CHANNEL_NAME=kite-opentag`. + +- [ ] **Step 2: `.railway/railway.ts`** — in the `channel` service `env`, replace the five `INTELLIGENCE_*` `preserve()` scope vars with `INTELLIGENCE_API_URL`, `INTELLIGENCE_GATEWAY_WS_URL`, `INTELLIGENCE_API_KEY` (`preserve()` for the key; the two URLs may be literals), keep `AGENT_URL` → agent service. Update the file's header comment to describe the v2 model. + +- [ ] **Step 3: `setup.md` + `README.md`** — update the "Intelligence channel mode" section: new var table, `@copilotkit/runtime/v2` + `createChannel` model, remove org/project/channel-id references, note `createCopilotNodeListener` mounting activates the channel. + +- [ ] **Step 4: Typecheck the IaC** + +Run: `pnpm check-types` (and, if available, `railway`'s IaC eval — otherwise skip). +Expected: PASS. + +- [ ] **Step 5: Commit** + +```bash +git add .env.example .railway/railway.ts README.md setup.md +git commit -m "docs+config: v2 Intelligence env (API_URL/WS_URL/API_KEY, kite-opentag)" +``` + +--- + +### Task 5: Live verification against `opentag-dev` + +**Files:** none (runtime verification) + +**Interfaces:** +- Consumes: a running Python `agent/` (`pnpm agent`, `:8123`) and operator-supplied `INTELLIGENCE_API_KEY`. + +- [ ] **Step 1: Operator sets secrets** in `.env`: `INTELLIGENCE_API_KEY` (from opentag-dev → API Keys), `INTELLIGENCE_API_URL`, `INTELLIGENCE_GATEWAY_WS_URL`, `AGENT_URL=http://localhost:8123/`. (Agent runs `OPENAI_API_KEY` already.) The implementer does NOT enter these — prompt the operator. + +- [ ] **Step 2: Start the brain** + +Run: `pnpm agent` (terminal 1) → `curl localhost:8123/health` → `{"status":"ok",...}`. + +- [ ] **Step 3: Start the channel host** + +Run: `pnpm channel` (terminal 2) +Expected: logs "channel kite-opentag mounted … → gateway"; the `opentag-dev` dashboard `kite-opentag` channel flips **Waiting for runtime → live** (RUNTIME "Last seen" updates). + +- [ ] **Step 4: End-to-end** + +@mention the bot in the channel's bound Slack workspace; expect a reply routed via the gateway → agent, with a generative-UI card and the confirm-write gate on a write action. + +- [ ] **Step 5: Finalize** — push branch, open PR referencing this plan and the design doc; DO NOT deploy the channel to Railway in this PR (separate follow-up). + +## Self-Review notes + +- Spec coverage: dep bump (T1–2), managed.ts rewrite (T3), env/Railway/docs (T4), live check (T5) — all acceptance criteria mapped. +- Discovery dependency is isolated to T1; T2's exact edits are intentionally driven by T1's typecheck output (a migration cannot know every break a priori) — the plan names the files and the known-likely rename, and requires showing each concrete edit at execution. +- Open items from the design doc (createBot existence, slack import path, identifyUser type, HITL API) are resolved concretely in Task 1 before dependent code is written. diff --git a/docs/superpowers/specs/2026-07-24-opentag-intelligence-v2-migration-design.md b/docs/superpowers/specs/2026-07-24-opentag-intelligence-v2-migration-design.md new file mode 100644 index 0000000..7eac1c3 --- /dev/null +++ b/docs/superpowers/specs/2026-07-24-opentag-intelligence-v2-migration-design.md @@ -0,0 +1,96 @@ +# OpenTag Intelligence channel → CopilotKit v2 managed-channels migration + +**Date:** 2026-07-24 +**Status:** Design (approved approach; plan pending) +**Owner:** Jerel + +## Problem + +OpenTag's Intelligence-Gateway mode (`app/managed.ts`, run via `pnpm channel`) targets the +**old** `@copilotkit/channels-intelligence` launcher: `startChannelsOverRealtimeGateway([bot], { wsUrl, apiKey, scope: { organizationId, projectId, channelId, channelName } })`. + +The live Intelligence project **`opentag-dev`** (channel `kite-opentag`, `dev.intelligence.copilotkit.ai`) +documents a **different, newer runtime contract** in its "Connect a runtime" panel: + +```ts +import { CopilotRuntime, CopilotKitIntelligence } from "@copilotkit/runtime/v2"; +import { createChannel } from "@copilotkit/channels"; // dashboard says @copilotkit/channel — typo +import { HttpAgent } from "@ag-ui/client"; +const intelligence = new CopilotKitIntelligence({ apiUrl, wsUrl, apiKey }); +const runtime = new CopilotRuntime({ agents: {}, intelligence, channels: [ createChannel({ name: "kite-opentag", agent }) ] }); +``` + +- Env collapses to **`INTELLIGENCE_API_URL` + `INTELLIGENCE_GATEWAY_WS_URL` + `INTELLIGENCE_API_KEY`**. +- **No** `ORG_ID` / `PROJECT_ID` / `CHANNEL_ID` — the runtime derives every infra id from the + Intelligence creds + the channel **`name`**. + +So `pnpm channel` as shipped **cannot connect to `opentag-dev`**: it hard-requires org/project/channel +IDs the platform no longer issues, and uses a superseded gateway launcher. + +## Findings (research against the CopilotKit monorepo + npm) + +- **`createChannel` is the renamed `createBot`.** Same options (`tools`, `context`, `commands`, + `components`) and handlers (`onMention`, `onModalSubmit`, `onThreadStarted`, HITL via + `thread.awaitChoice`). OpenTag's `app/` (tools, confirm-write gate, generative-UI components) ports + **~1:1**. +- **Activation model changed:** no `startChannelsOverRealtimeGateway`, no scope object. You pass + `channels: [channel]` to `new CopilotRuntime({ intelligence, identifyUser, channels })` and mount it + with `createCopilotNodeListener({ runtime })`; **creating the listener activates the channel**. There + is no `.start()`. +- **Published & installable (verified on npm):** + - `@copilotkit/runtime@1.63.2` — exports `./v2`, `./v2/node` (`CopilotRuntime`, `CopilotKitIntelligence`, `createCopilotNodeListener`). + - `@copilotkit/channels@0.2.1` / `@copilotkit/channels-core@0.2.1` (`createChannel`); `@copilotkit/channels-intelligence@0.2.1`. + - `@copilotkit/channel` (singular) does **not** exist — dashboard snippet typo. +- OpenTag currently pins `@copilotkit/channels*@^0.1.1` (and `@copilotkit/runtime@^1.62.3`, which + already resolves up to 1.63.2). Adopting `createChannel` requires bumping `channels*` **0.1 → 0.2**. + +## Decision + +Migrate OpenTag to `@copilotkit/channels@^0.2.x` and rewrite `app/managed.ts` to the v2 +managed-channels runtime (`CopilotRuntime({ channels }) + createCopilotNodeListener`), keeping the +self-hosted path (`app/index.ts`) and the TS `runtime.ts` agent backend working. The Python `agent/` +(the brain) is unchanged; the channel host points its `HttpAgent`/`SanitizingHttpAgent` at `AGENT_URL`. + +## Target design + +- **`app/managed.ts`** rewritten to the shape above; `name: "kite-opentag"` (configurable via + `INTELLIGENCE_CHANNEL_NAME`, default `kite-opentag`); reuse `appTools`/`appContext`/`appCommands` + + `defaultSlack*`; keep `onMention`/`onModalSubmit`/`onThreadStarted`; add the required `identifyUser`. +- **Env:** drop `INTELLIGENCE_ORG_ID`/`PROJECT_ID`/`CHANNEL_ID`; add `INTELLIGENCE_API_URL` + (`https://dev.intelligence.copilotkit.ai`); keep `INTELLIGENCE_GATEWAY_WS_URL` + (`wss://dev.intelligence.copilotkit.ai/runner`) + `INTELLIGENCE_API_KEY` (`cpk-…`, from the project's + API Keys tab) + `AGENT_URL`. +- **`.railway/railway.ts`** `channel` service env updated to the new var set. + +## Scope / affected files + +- `package.json` — bump `@copilotkit/channels`, `-slack`, `-discord`, `-telegram`, `-whatsapp`, `-ui`, + `-intelligence` to `^0.2.x`; confirm `@copilotkit/runtime` ≥1.63.2. +- `app/managed.ts` + `app/managed.test.ts` — rewrite + update. +- `.env.example`, `.railway/railway.ts`, `README.md`, `setup.md` — Intelligence var set. +- **Possibly** `app/index.ts`, `runtime.ts`, and `app/**` tools/HITL/components — only if the 0.1→0.2 + bump changed their APIs (the `createBot`→`createChannel` rename likely touches `app/index.ts`). + +## Open questions to resolve during implementation + +1. Exact 0.1→0.2 breakage: does `createBot` still exist (self-hosted path), or must `app/index.ts` + move to `createChannel({ adapters })`? +2. `@copilotkit/channels-slack@0.2.x` API + whether Slack helpers move to a `@copilotkit/channels/slack` + subpath. +3. Confirm the published `@copilotkit/runtime@1.63.2` `CopilotRuntime` accepts the `channels` option + (high confidence: `./v2/node` `createCopilotNodeListener` is published). +4. `identifyUser` semantics for a gateway-fronted channel (real Slack user vs stub). +5. HITL confirm-write in 0.2.x (`thread.awaitChoice`). + +## Acceptance criteria + +- Repo installs, typechecks, and `vitest` passes on `@copilotkit/channels@^0.2.x`. +- Both `pnpm dev` (self-hosted) and `pnpm channel` (managed) compile/start. +- `pnpm channel` connects to `opentag-dev`; the `kite-opentag` channel flips **Waiting for runtime → live**. +- An @mention in the bound Slack workspace gets a reply via the gateway, using the Python `agent/` as the brain, with generative-UI + the confirm-write gate intact. +- `.env.example`, `.railway/railway.ts`, README/setup docs updated to the new var set. + +## Non-goals + +- Deploying the channel to Railway (separate follow-up; the agent-only Railway deploy is already in flight). +- Changing the Python `agent/` or the self-hosted feature set beyond what the dep bump forces. From d0dfc2a818c03a094046be2df27401cb451e682e Mon Sep 17 00:00:00 2001 From: GeneralJerel <85066839+GeneralJerel@users.noreply.github.com> Date: Fri, 24 Jul 2026 09:24:36 -0700 Subject: [PATCH 02/50] chore(deps): bump @copilotkit/channels* to ^0.2.1 (v2 managed-channels) Co-Authored-By: Claude Opus 4.8 --- .migration-discovery.md | 571 ++++++++++++++++++++++++++++++++++++++++ package.json | 14 +- pnpm-lock.yaml | 353 +++++++++++++++++++++---- 3 files changed, 873 insertions(+), 65 deletions(-) create mode 100644 .migration-discovery.md diff --git a/.migration-discovery.md b/.migration-discovery.md new file mode 100644 index 0000000..2af80e2 --- /dev/null +++ b/.migration-discovery.md @@ -0,0 +1,571 @@ +# OpenTag `@copilotkit/channels` 0.1.x → 0.2.x Migration Discovery Spike + +Task 1 discovery only — no application code (`app/**`, `runtime.ts`) was modified. +Every signature quoted below was read directly from installed `.d.ts` files +under `node_modules/@copilotkit/*` (or, for one clearly-marked case in section +(c), from standalone `npm pack` tarballs pulled to the scratchpad for +comparison — never installed into this repo). Nothing here is guessed. + +## Versions + +| Package | Old range | New range | Resolved | +|---|---|---|---| +| `@copilotkit/channels` | `^0.1.1` | `^0.2.1` | `0.2.1` | +| `@copilotkit/channels-discord` | `^0.0.3` | `^0.2.1` | `0.2.1` | +| `@copilotkit/channels-intelligence` | `^0.1.1` | `^0.2.1` | `0.2.1` | +| `@copilotkit/channels-slack` | `^0.1.2` | `^0.2.1` | `0.2.1` | +| `@copilotkit/channels-telegram` | `^0.0.4` | `^0.2.1` | `0.2.1` | +| `@copilotkit/channels-ui` | `^0.1.1` | `^0.2.1` | `0.2.1` | +| `@copilotkit/channels-whatsapp` | `^0.0.2` | `^0.2.1` | `0.2.1` | +| `@copilotkit/runtime` | `^1.62.3` (left unchanged per spec) | `^1.62.3` | **`1.62.3`** | + +`npm view versions --json` (run before editing `package.json`) showed +every one of the seven `-discord/-telegram/-whatsapp/-ui`/etc. packages already +had a `0.2.1` published, so **no fallback pin was needed** — all seven are +exactly `^0.2.1`, matching the micro-spec's default instruction. + +`pnpm install` completed cleanly (`Done in 9.7s`, exit 0). Pre-existing, +unrelated peer-dependency warnings (not caused by this bump): +``` +├─┬ @notionhq/notion-mcp-server 2.4.1 +│ └─┬ @modelcontextprotocol/sdk 1.29.0 +│ ├── ✕ unmet peer zod@"^3.25 || ^4.0": found 3.24.1 in @notionhq/notion-mcp-server +│ └─┬ zod-to-json-schema 3.25.2 +│ └── ✕ unmet peer zod@"^3.25.28 || ^4": found 3.24.1 in @notionhq/notion-mcp-server +└─┬ @tanstack/ai-openai 0.15.10 + ├── ✕ unmet peer zod@^4.0.0: found 3.25.76 + ├── ✕ unmet peer @tanstack/ai@^0.39.0: found 0.32.0 + └─┬ @tanstack/openai-base 0.9.6 + └── ✕ unmet peer @tanstack/ai@^0.39.0: found 0.32.0 +``` +(Also: "Ignored build scripts: `@scarf/scarf@1.4.0`, `esbuild@0.28.1`" — the +pre-existing `pnpm approve-builds` gate, unrelated.) + +**Important:** `@copilotkit/runtime`'s `package.json` range (`^1.62.3`) was left +untouched per spec, and `pnpm-lock.yaml` already had `1.62.3` pinned from +before this spike. Since the range didn't change, plain `pnpm install` had no +reason to move that lock entry forward — it resolved to the exact +pre-existing **`1.62.3`**, not the newer `1.63.2` the migration plan assumed. +This turns out to matter a great deal — see section (c). + +Secondary observation: `@copilotkit/channels-ui@0.2.1` depends on +`@copilotkit/shared@^1.63.1` (resolves `1.63.2`), while +`@copilotkit/runtime@1.62.3`'s own tree resolves `@copilotkit/shared@1.62.3` — +two versions of `@copilotkit/shared` coexist in the pnpm store +(`node_modules/.pnpm/@copilotkit+shared@1.62.3_...` and `@1.63.2_...`). This did +not produce a `check-types` error in this run, but is worth knowing about, +especially if `runtime` gets bumped per point (c) below. + +--- + +## (a) `createBot` vs `createChannel` + +**`createBot` no longer exists anywhere in the installed 0.2.1 packages, with +no back-compat alias.** An exhaustive grep for `createBot`, `BotTool`, +`BotCommand`, `defineBotTool`, `defineBotCommand`, `BotNode`, and any `Bot` +type/interface/class across every installed `@copilotkit/channels*` package's +`.d.ts` output returned **zero matches**. + +It is renamed to `createChannel`, defined in `@copilotkit/channels-core` and +re-exported through the app-facing `@copilotkit/channels` package (the same +specifier the app already imports from): + +`node_modules/@copilotkit/channels/dist/index.d.ts`: +```ts +export * from "@copilotkit/channels-core"; +``` + +`@copilotkit/channels-core/dist/create-channel.d.ts` (source of truth; the +physical path under this repo is +`node_modules/.pnpm/@copilotkit+channels-core@0.2.1_.../node_modules/@copilotkit/channels-core/dist/create-channel.d.ts`): +```ts +export declare function createChannel( + opts: CreateChannelOptions +): Channel>; +``` + +So `import { createChannel } from "@copilotkit/channels"` is the correct 0.2.x +form — only the symbol name changes, not the import path. `Bot` (the type) is +likewise renamed to `Channel`, same file. + +--- + +## (b) `createChannel` / `CreateChannelOptions` exact signature + +Full options interface, `@copilotkit/channels-core/dist/create-channel.d.ts`: +```ts +export interface CreateChannelOptions { + /** Project-unique Intelligence Channel name. Required for Intelligence Channel + * Bots ...; optional for local/custom adapters. Validated by the Channel + * runtime (`startChannels`), not here. */ + name?: string; + /** Adapters supplied at construction. Optional — can also be attached before + * `start()` via `Channel.addAdapter`. */ + adapters?: PlatformAdapter[]; + /** The managed delivery provider this Channel targets when activated via + * CopilotKit Intelligence. Defaults to "slack" when unset. Ignored for + * direct-adapter Channels. */ + provider?: ManagedChannelProvider; // "slack" | "teams" + agent?: AbstractAgent | ((threadId: string) => AbstractAgent); + /** @deprecated Pass `store.adapter` instead. */ + actionStore?: ActionStore; + tools?: ChannelTool[]; + context?: ContextEntry[]; + /** Named JSX components used in interactive messages, for durable re-render + * across a restart. */ + components?: ChannelComponent[]; + /** Slash commands. Forwarded to adapters that support them; ignored elsewhere. */ + commands?: ChannelCommand[]; + /** Persistence, per-thread state schema, transcripts, and lock/dedup tuning. */ + store?: StoreConfig; +} + +export declare function createChannel( + opts: CreateChannelOptions +): Channel>; +``` + +Notes for the next wave: +- `name`, `adapters`, `agent`, `tools`, `context`, `commands` keep the same + field names as the 0.1.x `createBot` call sites already use in + `app/index.ts`/`app/managed.ts` — only the *element types* changed + (`BotTool`→`ChannelTool`, `BotCommand`→`ChannelCommand`; see (e)). +- Two **new** fields not present in 0.1.x: `provider?: "slack" | "teams"` + (managed-delivery provider selection, defaults `"slack"`) and + `components?: ChannelComponent[]` (durable JSX component registration). +- Returned `Channel` interface (same file) exposes: `onMention`, + `onMessage`, `onThreadStarted`, `onInteraction`, `onInterrupt`, `onCommand` + (2 overloads), `onReaction` (2 overloads), `onModalSubmit`, `onModalClose`, + `tool()`, `addAdapter()`, `start(): Promise`, `stop(): Promise`, + plus a **new** `transcripts: Transcripts` property not in 0.1.x's `Bot`. + +--- + +## (c) `CopilotRuntime` v2 / `channels` option / `identifyUser` / `createCopilotNodeListener` + +**Headline finding: at the runtime version actually installed +(`@copilotkit/runtime@1.62.3`), none of the "managed channels" API the +migration plan assumes exists.** A full-text, case-insensitive grep for +`channels` across every `.d.ts` file in +`node_modules/@copilotkit/runtime/dist/` (including all of `dist/v2/**`) +returned **zero matches**. + +Confirmed directly in the constructor-options source, +`node_modules/@copilotkit/runtime/dist/v2/runtime/core/runtime.d.mts`: +```ts +interface CopilotSseRuntimeOptions extends BaseCopilotRuntimeOptions { + runner?: AgentRunner; + intelligence?: undefined; + generateThreadNames?: undefined; +} +interface CopilotIntelligenceRuntimeOptions extends BaseCopilotRuntimeOptions { + intelligence: CopilotKitIntelligence; + identifyUser: IdentifyUserCallback; + generateThreadNames?: boolean; + maxReconnectMs?: number; + maxRejoinMs?: number; + lockTtlSeconds?: number; + lockKeyPrefix?: string; + lockHeartbeatIntervalSeconds?: number; +} +type CopilotRuntimeOptions = CopilotSseRuntimeOptions | CopilotIntelligenceRuntimeOptions; +``` +No `channels` field on either variant. `identifyUser`'s type IS present and +matches the plan's assumption exactly: +```ts +interface CopilotRuntimeUser { id: string; name: string; } +type IdentifyUserCallback = (request: Request) => MaybePromise; +``` +Also note: `agents` is **required and non-empty** — +`BaseCopilotRuntimeOptions.agents: AgentsConfig` where +`AgentsConfig = MaybePromise>> | AgentsFactory`. +The migration plan's example `new CopilotRuntime({ agents: {}, ... })` (an +empty object literal) will not satisfy `NonEmptyRecord` — that's its own +typecheck failure, independent of the channels question. + +And `node_modules/@copilotkit/runtime/dist/v2/runtime/endpoints/node.d.mts`: +```ts +declare function createCopilotNodeListener(options: CopilotRuntimeHandlerOptions): NodeFetchHandler; +``` +where `CopilotRuntimeHandlerOptions` +(`.../runtime/core/fetch-handler.d.mts`) has no `channels`-related field, and +`NodeFetchHandler` (`.../endpoints/node-fetch-handler.d.mts`) is a **bare +function type**: +```ts +type NodeFetchHandler = (req: IncomingMessage, res: ServerResponse) => Promise; +``` +**So `.channels?.stop()` does not exist on the installed +`createCopilotNodeListener`'s return value — the return isn't an object with +properties at all, just a plain callable.** + +### Where the assumed API actually lives + +`npm view @copilotkit/runtime versions` shows the `^1.62.3` range spans +`1.62.3` → `1.63.2` (highest 1.x) before `2.0.0-next.1`. To find out whether +the plan's assumed shape exists anywhere in that range, I pulled the `1.63.2` +and `2.0.0-next.1` tarballs standalone into the scratchpad via `npm pack` +(extracted only — **never installed into this repo's `node_modules`**) and +grepped their type declarations: + +- **`@copilotkit/runtime@1.63.2` has it, matching the plan's premise exactly.** + `runtime.d.mts` (1.63.2) adds `channels?: Channel[]` to + `CopilotIntelligenceRuntimeOptions` (`Channel` from + `@copilotkit/channels-core`), plus a `CopilotRuntimeConstructor` overload + that brands the result `RuntimeWithDeclaredChannels` when constructed with a + non-empty `channels` tuple. `channel-manager.d.mts` (1.63.2, new file — does + not exist at 1.62.3) defines: + ```ts + interface ChannelsControl { + ready(opts?: { timeoutMs?: number }): Promise; + status(): { overall: ChannelStatus; channels: Record }; + stop(): Promise; + } + ``` + and `node.d.mts` (1.63.2): + ```ts + type NodeCopilotListener = NodeFetchHandler & { channels?: ChannelsControl }; + declare function createCopilotNodeListener(options: CopilotRuntimeHandlerOptions): NodeCopilotListener; + ``` + i.e. **`listener.channels?.stop()` is valid at 1.63.2**, exactly as the plan's + Task 3 example assumes. `fetch-handler.d.mts` (1.63.2) also adds: an overload + where a runtime built with a non-empty `channels` array yields a handler + whose `.channels` is **non-optional** (`CopilotRuntimeFetchHandlerWithChannels`), + and a new `activateChannels?: boolean` option (default `true`) on + `CopilotRuntimeHandlerOptions` controlling whether the control surface is + built at all. +- `2.0.0-next.1` was also checked for completeness: its `dist/v2` is a single + flat `index.d.ts` (no `runtime/core/**` split like 1.62.3/1.63.2 have), and a + full-text grep for "channels" across its **entire** `dist/` tree returned + zero matches. It appears to be an unrelated, earlier-stage prerelease line, + not a superset of 1.63.2's channels work — irrelevant to this migration. + +**Conclusion:** the plan's Task 3 code is only valid from +`@copilotkit/runtime@1.63.2` onward. Since `^1.62.3` already covers `1.63.2`, +no `package.json` change is needed — but the **lockfile** must actually move +(e.g. `pnpm update @copilotkit/runtime`, or any action that forces +re-resolution within the existing range); a bare `pnpm install` will not do +this on its own when the declared range hasn't changed. **This is the single +most important handoff item for whoever executes Task 2/3**: bump the lockfile +resolution to ≥1.63.2 *before* attempting the `channels`/ +`createCopilotNodeListener(...).channels` rewrite, or none of it will +typecheck — `channels` and `.channels` simply don't exist at the version +currently sitting in `pnpm-lock.yaml`. + +--- + +## (d) Slack helper import path + `SanitizingHttpAgent`/`defaultSlackTools`/`defaultSlackContext` + +Both import paths resolve to the same code — `@copilotkit/channels/slack` is a +pure re-export of the standalone `@copilotkit/channels-slack` package: + +`node_modules/@copilotkit/channels/dist/slack.d.ts`: +```ts +export * from "@copilotkit/channels-slack"; +``` + +The app currently imports directly from `@copilotkit/channels-slack` (a real +`package.json` dependency) — **that remains correct and unchanged in 0.2.1.** + +All three symbols exist, unchanged in name, per +`node_modules/@copilotkit/channels-slack/dist/index.d.ts`: +```ts +export { slackTaggingContext, slackFormattingContext, slackConversationModelContext, defaultSlackContext } from "./built-in-context.js"; +export { SanitizingHttpAgent } from "./sanitizing-http-agent.js"; +export { lookupSlackUserTool, defaultSlackTools } from "./built-in-tools.js"; +export { slack, SlackAdapter } from "./adapter.js"; +``` + +Exact signatures: +- `dist/sanitizing-http-agent.d.ts`: + ```ts + export declare class SanitizingHttpAgent extends HttpAgent { + run(input: RunAgentInput): Observable; + } + ``` +- `dist/built-in-tools.d.ts`: + ```ts + export declare const defaultSlackTools: ReadonlyArray; + ``` + (element type renamed along with `BotTool`→`ChannelTool`; the constant name + and its being a `ReadonlyArray` are unchanged.) +- `dist/built-in-context.d.ts`: + ```ts + export type SlackContextEntry = ContextEntry; // alias of @copilotkit/channels-core's ContextEntry + export declare const defaultSlackContext: ReadonlyArray; + ``` + +No signature or import-path changes needed for +`app/index.ts`/`app/managed.ts`'s existing +`import { SanitizingHttpAgent, defaultSlackTools, defaultSlackContext } from "@copilotkit/channels-slack"`. + +--- + +## (e) Renamed / removed symbols + +### From `@copilotkit/channels` (i.e. `@copilotkit/channels-core`) + +| Old (0.1.x) | Status | New name / exact source | +|---|---|---| +| `defineBotTool` | **renamed** | `defineChannelTool` — `channels-core/dist/tools.d.ts`: `export declare function defineChannelTool(tool: ChannelTool): ChannelTool;` | +| `BotTool` | **renamed** | `ChannelTool` — `channels-core/dist/tools.d.ts`: `export type ChannelTool = { name: string; description: string; parameters: Schema; handler(args: InferSchemaOutput, ctx: ChannelToolContext): Promise \| unknown; };` | +| `defineBotCommand` | **renamed** | `defineChannelCommand` — `channels-core/dist/commands.d.ts`: `export declare function defineChannelCommand(command: ChannelCommand): ChannelCommand;` | +| `BotCommand` | **renamed** | `ChannelCommand` — `channels-core/dist/commands.d.ts`: `export interface ChannelCommand { name: string; description?: string; options?: Schema; handler(ctx: CommandContext>): void \| Promise; }` | +| `ContextEntry` | **unchanged** | `channels-core/dist/tools.d.ts`: `export interface ContextEntry { description: string; value: string; }` | +| `ModalSubmitHandler` | **unchanged** | `channels-core/dist/create-channel.d.ts`: `export type ModalSubmitHandler = (evt: ModalSubmitEvent) => ModalSubmitResult \| void \| Promise;` | +| `PlatformAdapter` | **unchanged** | `channels-core/dist/platform-adapter.d.ts`: large interface — `platform`, `capabilities`, `ackDeadlineMs`, `start`, `stop`, `render`, `post`, `update`, `stream`, `delete`, `createRunRenderer`, `decodeInteraction`, `lookupUser`, `conversationStore`, plus many optional capability hooks (`getMessages?`, `postFile?`, `registerCommands?`, `setSuggestedPrompts?`, `setThreadTitle?`, `addReaction?`/`removeReaction?`, `postEphemeral?`, `renderModal?`/`openModal?`). | +| `Bot` | **renamed** | `Channel` — `channels-core/dist/create-channel.d.ts`: `export interface Channel { readonly name?: string; readonly adapters: readonly PlatformAdapter[]; onMention(...); onMessage(...); onThreadStarted(...); onInteraction(...); onInterrupt(...); onCommand(...); onReaction(...); onModalSubmit(...); onModalClose(...); tool(...); addAdapter(...); start(): Promise; stop(): Promise; transcripts: Transcripts; }` (factory: `createChannel`, see (a)/(b)). | + +Confirmed by an exhaustive grep (zero hits) for `createBot`, `BotTool`, +`BotCommand`, `defineBotTool`, `defineBotCommand`, `BotNode`, and any `Bot` +type/interface/class across **every** installed channel package's `.d.ts` +output — there is no deprecated/back-compat alias for any of these; every call +site must switch to the new name. + +### From `@copilotkit/channels-ui` + +| Old (0.1.x) | Status | New name / exact source | +|---|---|---| +| `BotNode` | **renamed** | `ChannelNode` — `channels-ui/dist/ir.d.ts`: `export interface ChannelNode { type: string \| ComponentFn \| symbol; props: Record; key?: string \| number; }` | +| `ModalView` | unchanged | `channels-ui/dist/modal.d.ts`: `export type ModalView = ChannelNode & { type: "modal"; };` | +| `Modal` | unchanged | `channels-ui/dist/modal.d.ts`: `export declare const Modal: (props: ModalProps) => ModalView;` | +| `TextInput` | unchanged | `channels-ui/dist/modal.d.ts`: `export declare const TextInput: (props: TextInputProps) => ChannelNode;` | +| `ModalSelect` | unchanged | `channels-ui/dist/modal.d.ts`: `export declare const ModalSelect: (props: ModalSelectProps) => ChannelNode;` | +| `ModalSelectOption` | unchanged | `channels-ui/dist/modal.d.ts`: `export declare const ModalSelectOption: (props: ModalSelectOptionProps) => ChannelNode;` | +| `RadioButtons` | unchanged | `channels-ui/dist/modal.d.ts`: `export declare const RadioButtons: (props: RadioButtonsProps) => ChannelNode;` | +| `Message` | unchanged | `channels-ui/dist/components.d.ts`: `export declare const Message: (props: MessageProps) => ChannelNode;` | +| `Header` | unchanged | `channels-ui/dist/components.d.ts`: `export declare const Header: (props: HeaderProps) => ChannelNode;` | +| `Section` | unchanged | `channels-ui/dist/components.d.ts`: `export declare const Section: (props: SectionProps) => ChannelNode;` | +| `Context` | unchanged | `channels-ui/dist/components.d.ts`: `export declare const Context: (props: ContextProps) => ChannelNode;` | +| `Actions` | unchanged | `channels-ui/dist/components.d.ts`: `export declare const Actions: (props: ActionsProps) => ChannelNode;` | +| `Button` | unchanged | `channels-ui/dist/components.d.ts`: `export declare function Button(props: ButtonProps): ChannelNode;` | +| `InteractionContext` | unchanged | `channels-ui/dist/types.d.ts`: `export interface InteractionContext { thread: Thread; message: IncomingMessage; action: { id: string; value?: TValue }; values: Record; user: PlatformUser; platform: string; openModal?(view: ModalView): Promise<{ ok: boolean; error?: string }>; }` | +| `PlatformUser` | unchanged | `channels-ui/dist/types.d.ts`: `export interface PlatformUser { id: string; name?: string; handle?: string; email?: string; }` | +| `AgentContentPart` | unchanged | `channels-ui/dist/types.d.ts`: discriminated union — `{ type: "text"; text: string } \| { type: "image"\|"audio"\|"video"\|"document"; source: MediaDataSource }` | +| `Thread` | unchanged (interface) | `channels-ui/dist/types.d.ts` — full interface below. | + +`Thread.awaitChoice(...)` — **exists**, identically on both the public +interface and the concrete implementing class: +```ts +// channels-ui/dist/types.d.ts — the Thread interface +awaitChoice(ui: Renderable): Promise; + +// channels-core/dist/thread.d.ts — concrete `class Thread implements ThreadInterface` +awaitChoice(ui: Renderable): Promise; +``` +(The concrete class adds `runAgent(input?: { context?; tools?; prompt?; transcript? })` +and a `resume(value)` method not on the bare `channels-ui` interface, plus a +`supportsBlockingChoice?: boolean` capability flag mirrored from the adapter.) + +**Only `BotNode` was renamed** (`BotNode`→`ChannelNode`); every other +`channels-ui` symbol in the requested list kept its exact old name. This lines +up with the `check-types` output below — every `channels-ui`-sourced failure is +specifically `Module '"@copilotkit/channels-ui"' has no exported member +'BotNode'`, and nothing else from that package ever fails. + +--- + +## (f) Full `pnpm check-types` output, grouped by file + +Command: `pnpm check-types` (`tsc --noEmit -p tsconfig.json`). **Exit code 2. +74 errors across 19 files**: 22× `TS2305` (no exported member — the direct +rename breaks), 40× `TS7031` (implicit-any destructured binding element), 11× +`TS7006` (implicit-any parameter), 1× `TS2347` (untyped generic call). The 51 +implicit-any errors (`TS7031`+`TS7006`) are **cascades**: once +`createBot`/`Bot`/`BotTool`/`BotCommand`/`defineBotTool`/`defineBotCommand` +fail to import, TS can no longer infer the (now effectively `any`-typed) +generic helper's callback parameters, which trips `strict` +(`noImplicitAny`). Nothing below was fixed — this is the recorded breakage +list only, per spec. + +### `app/index.ts` — 7 errors (2 root-cause + 5 cascade) +``` +app/index.ts(20,10): error TS2305: Module '"@copilotkit/channels"' has no exported member 'createBot'. +app/index.ts(21,32): error TS2305: Module '"@copilotkit/channels"' has no exported member 'BotTool'. +app/index.ts(190,13): error TS7006: Parameter 'threadId' implicitly has an 'any' type. +app/index.ts(222,26): error TS7031: Binding element 'thread' implicitly has an 'any' type. +app/index.ts(222,34): error TS7031: Binding element 'message' implicitly has an 'any' type. +app/index.ts(247,32): error TS7031: Binding element 'thread' implicitly has an 'any' type. +app/index.ts(247,40): error TS7031: Binding element 'user' implicitly has an 'any' type. +``` + +### `app/managed.ts` — 6 errors (2 root-cause + 4 cascade) +``` +app/managed.ts(19,10): error TS2305: Module '"@copilotkit/channels"' has no exported member 'createBot'. +app/managed.ts(20,15): error TS2305: Module '"@copilotkit/channels"' has no exported member 'Bot'. +app/managed.ts(121,26): error TS7031: Binding element 'thread' implicitly has an 'any' type. +app/managed.ts(121,34): error TS7031: Binding element 'message' implicitly has an 'any' type. +app/managed.ts(139,32): error TS7031: Binding element 'thread' implicitly has an 'any' type. +app/managed.ts(139,40): error TS7031: Binding element 'user' implicitly has an 'any' type. +``` + +### `app/commands/index.ts` — 16 errors (2 root-cause + 14 cascade) +``` +app/commands/index.ts(14,10): error TS2305: Module '"@copilotkit/channels"' has no exported member 'defineBotCommand'. +app/commands/index.ts(15,15): error TS2305: Module '"@copilotkit/channels"' has no exported member 'BotCommand'. +app/commands/index.ts(53,21): error TS7031: Binding element 'thread' implicitly has an 'any' type. +app/commands/index.ts(53,29): error TS7031: Binding element 'text' implicitly has an 'any' type. +app/commands/index.ts(53,35): error TS7031: Binding element 'user' implicitly has an 'any' type. +app/commands/index.ts(71,21): error TS7031: Binding element 'thread' implicitly has an 'any' type. +app/commands/index.ts(71,29): error TS7031: Binding element 'text' implicitly has an 'any' type. +app/commands/index.ts(71,35): error TS7031: Binding element 'user' implicitly has an 'any' type. +app/commands/index.ts(91,21): error TS7031: Binding element 'thread' implicitly has an 'any' type. +app/commands/index.ts(91,29): error TS7031: Binding element 'text' implicitly has an 'any' type. +app/commands/index.ts(91,35): error TS7031: Binding element 'user' implicitly has an 'any' type. +app/commands/index.ts(91,41): error TS7031: Binding element 'platform' implicitly has an 'any' type. +app/commands/index.ts(136,21): error TS7031: Binding element 'thread' implicitly has an 'any' type. +app/commands/index.ts(136,29): error TS7031: Binding element 'openModal' implicitly has an 'any' type. +app/commands/index.ts(136,40): error TS7031: Binding element 'platform' implicitly has an 'any' type. +app/commands/index.ts(136,50): error TS7031: Binding element 'user' implicitly has an 'any' type. +``` + +### `app/commands/__tests__/commands.test.ts` — 1 error +``` +app/commands/__tests__/commands.test.ts(3,15): error TS2305: Module '"@copilotkit/channels-ui"' has no exported member 'BotNode'. +``` + +### `app/components/issue-card.tsx` — 1 error +``` +app/components/issue-card.tsx(23,15): error TS2305: Module '"@copilotkit/channels-ui"' has no exported member 'BotNode'. +``` + +### `app/components/issue-list.tsx` — 1 error +``` +app/components/issue-list.tsx(20,15): error TS2305: Module '"@copilotkit/channels-ui"' has no exported member 'BotNode'. +``` + +### `app/components/page-list.tsx` — 1 error +``` +app/components/page-list.tsx(20,15): error TS2305: Module '"@copilotkit/channels-ui"' has no exported member 'BotNode'. +``` + +### `app/human-in-the-loop/confirm-write-tool.tsx` — 5 errors (1 root-cause + 4 cascade) +``` +app/human-in-the-loop/confirm-write-tool.tsx(13,10): error TS2305: Module '"@copilotkit/channels"' has no exported member 'defineBotTool'. +app/human-in-the-loop/confirm-write-tool.tsx(38,19): error TS7031: Binding element 'action' implicitly has an 'any' type. +app/human-in-the-loop/confirm-write-tool.tsx(38,27): error TS7031: Binding element 'detail' implicitly has an 'any' type. +app/human-in-the-loop/confirm-write-tool.tsx(38,39): error TS7031: Binding element 'thread' implicitly has an 'any' type. +app/human-in-the-loop/confirm-write-tool.tsx(39,26): error TS2347: Untyped function calls may not accept type arguments. +``` + +### `app/human-in-the-loop/__tests__/confirm-write-tool.test.tsx` — 1 error +``` +app/human-in-the-loop/__tests__/confirm-write-tool.test.tsx(2,27): error TS2305: Module '"@copilotkit/channels-ui"' has no exported member 'BotNode'. +``` + +### `app/human-in-the-loop/__tests__/confirm-write.test.tsx` — 1 error +``` +app/human-in-the-loop/__tests__/confirm-write.test.tsx(4,8): error TS2305: Module '"@copilotkit/channels-ui"' has no exported member 'BotNode'. +``` + +### `app/modals/__tests__/file-issue.test.tsx` — 1 error +``` +app/modals/__tests__/file-issue.test.tsx(9,15): error TS2305: Module '"@copilotkit/channels-ui"' has no exported member 'BotNode'. +``` + +### `app/tools/__tests__/showcase-tools.test.tsx` — 1 error +``` +app/tools/__tests__/showcase-tools.test.tsx(12,3): error TS2305: Module '"@copilotkit/channels-ui"' has no exported member 'BotNode'. +``` + +### `app/tools/index.ts` — 1 error +``` +app/tools/index.ts(22,15): error TS2305: Module '"@copilotkit/channels"' has no exported member 'BotTool'. +``` + +### `app/tools/read-thread.ts` — 4 errors (1 root-cause + 3 cascade) +``` +app/tools/read-thread.ts(14,10): error TS2305: Module '"@copilotkit/channels"' has no exported member 'defineBotTool'. +app/tools/read-thread.ts(24,17): error TS7006: Parameter '_args' implicitly has an 'any' type. +app/tools/read-thread.ts(24,26): error TS7031: Binding element 'thread' implicitly has an 'any' type. +app/tools/read-thread.ts(28,31): error TS7006: Parameter 'm' implicitly has an 'any' type. +``` + +### `app/tools/render-chart.tsx` — 4 errors (1 root-cause + 3 cascade) +``` +app/tools/render-chart.tsx(11,10): error TS2305: Module '"@copilotkit/channels"' has no exported member 'defineBotTool'. +app/tools/render-chart.tsx(74,19): error TS7031: Binding element 'title' implicitly has an 'any' type. +app/tools/render-chart.tsx(74,26): error TS7031: Binding element 'chartSpec' implicitly has an 'any' type. +app/tools/render-chart.tsx(74,39): error TS7006: Parameter 'ctx' implicitly has an 'any' type. +``` + +### `app/tools/render-diagram.tsx` — 4 errors (1 root-cause + 3 cascade) +``` +app/tools/render-diagram.tsx(11,10): error TS2305: Module '"@copilotkit/channels"' has no exported member 'defineBotTool'. +app/tools/render-diagram.tsx(44,19): error TS7031: Binding element 'title' implicitly has an 'any' type. +app/tools/render-diagram.tsx(44,26): error TS7031: Binding element 'mermaid' implicitly has an 'any' type. +app/tools/render-diagram.tsx(44,37): error TS7006: Parameter 'ctx' implicitly has an 'any' type. +``` + +### `app/tools/render-table.tsx` — 5 errors (1 root-cause + 4 cascade) +``` +app/tools/render-table.tsx(23,10): error TS2305: Module '"@copilotkit/channels"' has no exported member 'defineBotTool'. +app/tools/render-table.tsx(132,19): error TS7031: Binding element 'title' implicitly has an 'any' type. +app/tools/render-table.tsx(132,26): error TS7031: Binding element 'columns' implicitly has an 'any' type. +app/tools/render-table.tsx(132,35): error TS7031: Binding element 'rows' implicitly has an 'any' type. +app/tools/render-table.tsx(132,45): error TS7031: Binding element 'thread' implicitly has an 'any' type. +``` + +### `app/tools/render-tools.tsx` — 7 errors (1 root-cause + 6 cascade) +``` +app/tools/render-tools.tsx(8,10): error TS2305: Module '"@copilotkit/channels"' has no exported member 'defineBotTool'. +app/tools/render-tools.tsx(26,17): error TS7006: Parameter 'props' implicitly has an 'any' type. +app/tools/render-tools.tsx(26,26): error TS7031: Binding element 'thread' implicitly has an 'any' type. +app/tools/render-tools.tsx(41,17): error TS7006: Parameter 'props' implicitly has an 'any' type. +app/tools/render-tools.tsx(41,26): error TS7031: Binding element 'thread' implicitly has an 'any' type. +app/tools/render-tools.tsx(55,17): error TS7006: Parameter 'props' implicitly has an 'any' type. +app/tools/render-tools.tsx(55,26): error TS7031: Binding element 'thread' implicitly has an 'any' type. +``` + +### `app/tools/showcase-tools.tsx` — 7 errors (1 root-cause + 6 cascade) +``` +app/tools/showcase-tools.tsx(25,10): error TS2305: Module '"@copilotkit/channels"' has no exported member 'defineBotTool'. +app/tools/showcase-tools.tsx(100,17): error TS7006: Parameter 'props' implicitly has an 'any' type. +app/tools/showcase-tools.tsx(100,26): error TS7031: Binding element 'thread' implicitly has an 'any' type. +app/tools/showcase-tools.tsx(143,17): error TS7006: Parameter 'props' implicitly has an 'any' type. +app/tools/showcase-tools.tsx(143,26): error TS7031: Binding element 'thread' implicitly has an 'any' type. +app/tools/showcase-tools.tsx(186,17): error TS7006: Parameter 'props' implicitly has an 'any' type. +app/tools/showcase-tools.tsx(186,26): error TS7031: Binding element 'thread' implicitly has an 'any' type. +``` + +(19 files, 74 errors total — matches `grep -c "error TS"` exactly.) + +--- + +## Summary for Task 2/3 implementers + +1. Rename, repo-wide: `createBot`→`createChannel`, `Bot`→`Channel`, + `BotTool`→`ChannelTool`, `BotCommand`→`ChannelCommand`, + `defineBotTool`→`defineChannelTool`, `defineBotCommand`→`defineChannelCommand`, + `BotNode`→`ChannelNode`. Every other symbol touched by this codebase + (`ContextEntry`, `ModalSubmitHandler`, `PlatformAdapter`, `Thread` incl. + `awaitChoice`, and every named `channels-ui` component/type besides + `BotNode`) keeps its exact old name and import path — fixing the 22 + `TS2305` root causes above should make most of the 51 cascade errors + disappear on their own (correct type inference returns once the generic + helpers resolve). +2. `@copilotkit/channels-slack`'s `SanitizingHttpAgent`/`defaultSlackTools`/ + `defaultSlackContext`/`slack` need zero changes — same names, same + signatures, same import path. +3. **Blocking for Task 3 specifically:** the `CopilotRuntime`/ + `createCopilotNodeListener` "managed channels" surface (`channels` option, + `.channels?.stop()`) does not exist at the currently-locked + `@copilotkit/runtime@1.62.3` — it first appears at `1.63.2`. `^1.62.3` + already permits `1.63.2`, but the lockfile needs an explicit + `pnpm update @copilotkit/runtime` (or equivalent forced re-resolution) to + actually move there; a bare `pnpm install` will not, since the declared + range in `package.json` didn't change. Also: `agents: {}` (empty) fails the + `NonEmptyRecord` constraint on `CopilotRuntimeOptions.agents` — the plan's + example construction needs a non-empty `agents` record. +4. `startChannelsOverRealtimeGateway(channels: Channel[], config)` — what the + current `app/managed.ts` calls today — **still exists unchanged** in + `@copilotkit/channels-intelligence@0.2.1` (just retyped `Bot[]`→`Channel[]`), + alongside a new lower-level `startChannels(opts: StartChannelsOptions)`. A + much smaller migration (rename-only, keep the realtime-gateway launcher) is + technically possible as a fallback if the `CopilotRuntime`/ + `createCopilotNodeListener` v2 rewrite in Task 3 proves too disruptive or + the runtime bump is undesirable. +5. `@copilotkit/channels-ui@0.2.1` pulls `@copilotkit/shared@1.63.2` while + `@copilotkit/runtime@1.62.3`'s tree pulls `@copilotkit/shared@1.62.3` — two + coexisting versions in the pnpm store today. Not currently causing a + `check-types` failure, but worth eliminating if `runtime` gets bumped to + 1.63.2 anyway per point 3 (it would naturally converge). diff --git a/package.json b/package.json index de0de42..e80d250 100644 --- a/package.json +++ b/package.json @@ -19,13 +19,13 @@ "e2e:telegram": "tsx e2e/telegram-run.ts" }, "dependencies": { - "@copilotkit/channels": "^0.1.1", - "@copilotkit/channels-discord": "^0.0.3", - "@copilotkit/channels-intelligence": "^0.1.1", - "@copilotkit/channels-slack": "^0.1.2", - "@copilotkit/channels-telegram": "^0.0.4", - "@copilotkit/channels-ui": "^0.1.1", - "@copilotkit/channels-whatsapp": "^0.0.2", + "@copilotkit/channels": "^0.2.1", + "@copilotkit/channels-discord": "^0.2.1", + "@copilotkit/channels-intelligence": "^0.2.1", + "@copilotkit/channels-slack": "^0.2.1", + "@copilotkit/channels-telegram": "^0.2.1", + "@copilotkit/channels-ui": "^0.2.1", + "@copilotkit/channels-whatsapp": "^0.2.1", "@copilotkit/runtime": "^1.62.3", "@notionhq/notion-mcp-server": "^2.4.0", "@slack/bolt": "^4.2.0", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 383baff..5f32559 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -12,26 +12,26 @@ importers: .: dependencies: '@copilotkit/channels': - specifier: ^0.1.1 - version: 0.1.1(vitest@4.1.10(@opentelemetry/api@1.9.1)(@types/node@22.20.1)(vite@8.1.4(@types/node@22.20.1)(esbuild@0.28.1)(tsx@4.23.1)))(zod@3.25.76) + specifier: ^0.2.1 + version: 0.2.1(@ag-ui/core@0.0.57)(@opentelemetry/api@1.9.1)(@types/express@5.0.6)(vitest@4.1.10(@opentelemetry/api@1.9.1)(@types/node@22.20.1)(vite@8.1.4(@types/node@22.20.1)(esbuild@0.28.1)(tsx@4.23.1)))(zod@3.25.76) '@copilotkit/channels-discord': - specifier: ^0.0.3 - version: 0.0.3(@ag-ui/core@0.0.57)(vitest@4.1.10(@opentelemetry/api@1.9.1)(@types/node@22.20.1)(vite@8.1.4(@types/node@22.20.1)(esbuild@0.28.1)(tsx@4.23.1))) + specifier: ^0.2.1 + version: 0.2.1(@ag-ui/core@0.0.57)(vitest@4.1.10(@opentelemetry/api@1.9.1)(@types/node@22.20.1)(vite@8.1.4(@types/node@22.20.1)(esbuild@0.28.1)(tsx@4.23.1))) '@copilotkit/channels-intelligence': - specifier: ^0.1.1 - version: 0.1.1(@ag-ui/core@0.0.57)(vitest@4.1.10(@opentelemetry/api@1.9.1)(@types/node@22.20.1)(vite@8.1.4(@types/node@22.20.1)(esbuild@0.28.1)(tsx@4.23.1)))(zod@3.25.76) + specifier: ^0.2.1 + version: 0.2.1(@ag-ui/core@0.0.57)(vitest@4.1.10(@opentelemetry/api@1.9.1)(@types/node@22.20.1)(vite@8.1.4(@types/node@22.20.1)(esbuild@0.28.1)(tsx@4.23.1)))(zod@3.25.76) '@copilotkit/channels-slack': - specifier: ^0.1.2 - version: 0.1.2(@types/express@5.0.6)(vitest@4.1.10(@opentelemetry/api@1.9.1)(@types/node@22.20.1)(vite@8.1.4(@types/node@22.20.1)(esbuild@0.28.1)(tsx@4.23.1))) + specifier: ^0.2.1 + version: 0.2.1(@types/express@5.0.6)(vitest@4.1.10(@opentelemetry/api@1.9.1)(@types/node@22.20.1)(vite@8.1.4(@types/node@22.20.1)(esbuild@0.28.1)(tsx@4.23.1))) '@copilotkit/channels-telegram': - specifier: ^0.0.4 - version: 0.0.4(@ag-ui/core@0.0.57)(vitest@4.1.10(@opentelemetry/api@1.9.1)(@types/node@22.20.1)(vite@8.1.4(@types/node@22.20.1)(esbuild@0.28.1)(tsx@4.23.1))) + specifier: ^0.2.1 + version: 0.2.1(@ag-ui/core@0.0.57)(vitest@4.1.10(@opentelemetry/api@1.9.1)(@types/node@22.20.1)(vite@8.1.4(@types/node@22.20.1)(esbuild@0.28.1)(tsx@4.23.1))) '@copilotkit/channels-ui': - specifier: ^0.1.1 - version: 0.1.1(@ag-ui/core@0.0.57) + specifier: ^0.2.1 + version: 0.2.1(@ag-ui/core@0.0.57) '@copilotkit/channels-whatsapp': - specifier: ^0.0.2 - version: 0.0.2(@ag-ui/core@0.0.57)(vitest@4.1.10(@opentelemetry/api@1.9.1)(@types/node@22.20.1)(vite@8.1.4(@types/node@22.20.1)(esbuild@0.28.1)(tsx@4.23.1)))(zod@3.25.76) + specifier: ^0.2.1 + version: 0.2.1(@ag-ui/core@0.0.57)(vitest@4.1.10(@opentelemetry/api@1.9.1)(@types/node@22.20.1)(vite@8.1.4(@types/node@22.20.1)(esbuild@0.28.1)(tsx@4.23.1)))(zod@3.25.76) '@copilotkit/runtime': specifier: ^1.62.3 version: 1.62.3(@cfworker/json-schema@4.1.1)(@langchain/core@1.2.3(@opentelemetry/api@1.9.1)(openai@6.47.0(ws@8.21.1)(zod@3.25.76))(ws@8.21.1))(@langchain/langgraph-sdk@1.9.27(@langchain/core@1.2.3(@opentelemetry/api@1.9.1)(openai@6.47.0(ws@8.21.1)(zod@3.25.76))(ws@8.21.1)))(@opentelemetry/api@1.9.1)(langchain@1.5.3(@langchain/core@1.2.3(@opentelemetry/api@1.9.1)(openai@6.47.0(ws@8.21.1)(zod@3.25.76))(ws@8.21.1))(@opentelemetry/api@1.9.1)(openai@6.47.0(ws@8.21.1)(zod@3.25.76))(ws@8.21.1))(openai@6.47.0(ws@8.21.1)(zod@3.25.76)) @@ -207,40 +207,71 @@ packages: resolution: {integrity: sha512-5X1k57JBJ4H7H1QjX7CnJYAB1I19r/trVZTMcSms7/kLNZ8RaU4Nt2agcwZzv82Hfx6Q7/TOLU7agAKeFfc8cA==} engines: {node: '>=18'} + '@azure/abort-controller@2.2.0': + resolution: {integrity: sha512-fNAjWnA/nZ2jz31kxR/AqRaUT8ewHBw/WuBIosK0moMy1C9e5ValbDfFdIxJzVOOYaYkV/b2F1S4H/aHiqfVQg==} + engines: {node: '>=22.0.0'} + + '@azure/core-auth@1.10.1': + resolution: {integrity: sha512-ykRMW8PjVAn+RS6ww5cmK9U2CyH9p4Q88YJwvUslfuMmN98w/2rdGRLPqJYObapBCdzBVeDgYWdJnFPFb7qzpg==} + engines: {node: '>=20.0.0'} + + '@azure/core-util@1.14.0': + resolution: {integrity: sha512-9n2pWK61veAuN0V20t9lOuoV4CFMdyAZ1ygZzvBGk/pBBJRib/PjL9PLXa/aI2CcPpyHfqVsxxqLCYl6uZlfDw==} + engines: {node: '>=22.0.0'} + + '@azure/msal-common@16.5.2': + resolution: {integrity: sha512-GkDEL6TYo3HgT3UuqakdgE9PZfc1hMki6+Hwgy1uddb/EauvAKfu85vVhuofRSo22D1xTnWt8Ucwfg4vSCVwvA==} + engines: {node: '>=0.8.0'} + + '@azure/msal-node@5.1.5': + resolution: {integrity: sha512-ObTeMoNPmq19X3z40et9Xvs4ZoWVeJg43PZMRLG5iwVL+2nCtAerG3YTDItqPp1CfXNwmCXBbg8jn1DOx65c3g==} + engines: {node: '>=20'} + '@bufbuild/protobuf@2.12.1': resolution: {integrity: sha512-BvAMfS6LrgZiryOAZ4pBYucu4wG/Ei/9o9DZ9akbREnMLbPJiom2i8b9C8IsKErQoiKqVhrerzt3kOT/RrzLHg==} '@cfworker/json-schema@4.1.1': resolution: {integrity: sha512-gAmrUZSGtKc3AiBL71iNWxDsyUC5uMaKKGdvzYsBoTW/xi42JQHl7eKV2OYzCUqvc+D2RCcf7EXY2iCyFIk6og==} - '@copilotkit/channels-discord@0.0.3': - resolution: {integrity: sha512-UXkWHcQJ9kLDkkGMXC3NyHuYwdVxWW09uyn1wm9er5tcL5/ZK12gvqgnj7G5j5pF7VI0qWXQ+ZvO3uZENo7o0A==} + '@copilotkit/channels-core@0.2.1': + resolution: {integrity: sha512-Uab1LRb5HVXlqMYhkVROucujLWUTVUlVOpRzwdYAjIRWR/GL4kgWM3YioZ9qJyJMXwvQnSYFK2Up3zv/Uojk8w==} + peerDependencies: + vitest: ^4.0.0 + peerDependenciesMeta: + vitest: + optional: true + + '@copilotkit/channels-discord@0.2.1': + resolution: {integrity: sha512-7VpdVd4Wo3JL9zOFSrRzUBjfkASxSmsfBmSSeyc8uxwx+zfaKfi7bV6lzZqq8DTcjdgnUGjNpPuVGNtwYPJuig==} - '@copilotkit/channels-intelligence@0.1.1': - resolution: {integrity: sha512-lS3vzbuRaeLTE+ucdsinBd9YHdm4lqUjvA57wowGMs+/WUZKzKCLGiE9BYOErQzo0PaM8MTx8TomTzNSny287w==} + '@copilotkit/channels-intelligence@0.2.1': + resolution: {integrity: sha512-kcT8GCDeFxeLuH99EvLSPrUL5wcJrd2Q23jC7lFYQO0N31fQMrtTwyJtE4XW4YfrqUsNc3FFLv9kqfNMVn5Seg==} - '@copilotkit/channels-slack@0.1.2': - resolution: {integrity: sha512-h8VR+raH1GvdNxEuNcpGMe7TayZxzj33xobq6RCCU4hLu15jadCyYj9CXArYIcwyManlbWE5jqi6n8cla23E0g==} + '@copilotkit/channels-slack@0.2.1': + resolution: {integrity: sha512-m6POLUDFKzRfMGMHFkQPo+DEmjOov9GPmxm9pwe+dgGo0ow144cISxvQvluiv8g36qbbk1Fhqr/jI8FRIT1cdg==} - '@copilotkit/channels-telegram@0.0.4': - resolution: {integrity: sha512-LyzGU9wHVPPc7MsR9+w1kBXcEqotI3k8XRJLola99ktzEpAtXTwCmBlnyh+j7UTCMXsRTjKz9eC6ULxDy82GbQ==} + '@copilotkit/channels-teams@0.2.1': + resolution: {integrity: sha512-d+X7cLHcNsuIlEzzsFWcQUfCp4TVXsPn9F+7yWy8R+EvuhqUwBYEucTx2kzcgMoVRv8nuuHYJek3Pd6c1QwEUQ==} - '@copilotkit/channels-ui@0.1.1': - resolution: {integrity: sha512-wQ099E62DEhIuKz8O9Iqy1VP4qCyLtS8d5EwfF2MK8m8amWtuyyTUxV8vC1q9ecJMgAwCtKmeW/fYQR8NsLiXQ==} + '@copilotkit/channels-telegram@0.2.1': + resolution: {integrity: sha512-MBkbxrC2qio9i0PSn4N1TDkJmSJvP7Wn6Fbc9hOUoR3hAGW/RhZbgCHPyE6j3VQfDWiPABZCHtrbPbL3hacqyw==} - '@copilotkit/channels-whatsapp@0.0.2': - resolution: {integrity: sha512-T2eCeY6NXSAZbdQsARhhpLGsXP667wWqgsWpMPXy+dULvGhemw/ueyijV5/kEzMnxlaBoZ4TuGClXweMtDxZfw==} + '@copilotkit/channels-ui@0.2.1': + resolution: {integrity: sha512-hHIOLic5a8Vx3Yr9TvZWv/LZ16vF8FPe/z7c9wiHIHrJnjsV7oxrEmJNv+WiOVUW4yV3CgEIH48/KesGBiK/kg==} - '@copilotkit/channels@0.1.1': - resolution: {integrity: sha512-v/cOFfRZWKXstrj06YZmR5kikE9XhXluuJa81hZEhZ4I9C3U+Lnao1K5pL5DtmhUbowynMvBedVUNqKwGFLCvQ==} + '@copilotkit/channels-whatsapp@0.2.1': + resolution: {integrity: sha512-xeV/By1j6F1YjnyOE4Zkk0lCPs/yZVLM1jNlDMauZYA+Ev3zsFRV+6QR7TKM8qG02pLR1OOYDa1pBhfH2SOuyA==} + + '@copilotkit/channels@0.2.1': + resolution: {integrity: sha512-1R2ItgaBj0Gwna9NMxwdc0KEa9A439/6mJDr/Kmv5ikNzQueYTInDlCgXLAHnfTsCcVk0Z4R75NJ8yAPdCUxdA==} peerDependencies: vitest: ^4.0.0 peerDependenciesMeta: vitest: optional: true - '@copilotkit/core@1.62.3': - resolution: {integrity: sha512-Iw0XTJDylh4DTL1qqqoDSvMeL4lXGWfn8UAunnvYtVZ2BF3gs6Lw6K/yOQ0gGQKMqIxeQmgLVclErxwBcaJ8KQ==} + '@copilotkit/core@1.63.2': + resolution: {integrity: sha512-Ak7JQ9ljuOvXUEnYmJVX/9q0oYjVh+cp7pR6fLqJZfUCvap2ec1Y2LHTyd+ZB5xurBCxwRIeqLnIAHxR5XONxw==} engines: {node: '>=18'} '@copilotkit/license-verifier@0.5.0': @@ -284,6 +315,11 @@ packages: peerDependencies: '@ag-ui/core': '>=0.0.48' + '@copilotkit/shared@1.63.2': + resolution: {integrity: sha512-vgpMY+YTkDYTEL4Slbj9S5kEXEYGOIuGTKdBkTVL1k8sPbj21w8OzqQ5No6TN+e+HJrbC3kr/r4CXM9QkHxezA==} + peerDependencies: + '@ag-ui/core': '>=0.0.48' + '@discordjs/builders@1.14.1': resolution: {integrity: sha512-gSKkhXLqs96TCzk66VZuHHl8z2bQMJFGwrXC0f33ngK+FLNau4hU1PYny3DNJfNdSH+gVMzE85/d5FQ2BpcNwQ==} engines: {node: '>=16.11.0'} @@ -604,6 +640,26 @@ packages: resolution: {integrity: sha512-qC72D4+CDdjGqJvkFMMEAtancHUQ7/d/tAiHf64z8MopFDmcrtbcJuerDtFceuAfQJ2pDSfCKCtbqoGBNnwg0w==} engines: {node: '>=8'} + '@microsoft/agents-activity@1.7.1': + resolution: {integrity: sha512-jbomRRTOWh1fxaEt4zb5ZTCVSBMMlH+/A7ws2RBEiCF3yGFioWbuLPHmwW+KAU+VkE8979IAbtagXXjd5x+XkQ==} + engines: {node: '>=20.0.0'} + + '@microsoft/agents-hosting@1.7.1': + resolution: {integrity: sha512-sWxAlNMpRHtuLfs5Z454HGpl/caFXksyo3MdJm5QBnkgM2cCETUAD5TaHzxYCnYM9DDW45wHJbzDOAX5mZnSoQ==} + engines: {node: '>=20.0.0'} + + '@microsoft/agents-telemetry@1.7.1': + resolution: {integrity: sha512-zCtoJaqefNSUzHrv7gBxy9AHdoMsXT5fIS+/43UEN3e1CPZjaYIdGU6eMkX/LGWUaVJaPpIlMoV79sKvecn2Ow==} + engines: {node: '>=20.0.0'} + peerDependencies: + '@opentelemetry/api': '1' + '@opentelemetry/api-logs': '>=0.214.0 <1' + peerDependenciesMeta: + '@opentelemetry/api': + optional: true + '@opentelemetry/api-logs': + optional: true + '@modelcontextprotocol/sdk@1.29.0': resolution: {integrity: sha512-zo37mZA9hJWpULgkRpowewez1y6ML5GsXJPY8FI0tBBCd77HEvza4jDqRKOXgHNn867PVGCyTdzqpz0izu5ZjQ==} engines: {node: '>=18'} @@ -918,6 +974,10 @@ packages: '@types/ws@8.18.1': resolution: {integrity: sha512-ThVF6DCVhA8kUGy+aazFQ4kXQ7E1Ty7A3ypFOe0IcJV8O/M511G99AW24irKrW56Wt44yG9+ij8FaqoBGkuBXg==} + '@typespec/ts-http-runtime@0.3.7': + resolution: {integrity: sha512-JVUD8X2tfDMWjcjLs4yVxxVrS8yR5vnh386GAXT9Qj79nBxxXSaHFQZg5FweLmT8HlPQ3kii6noUB+Z9RN7DvQ==} + engines: {node: '>=22.0.0'} + '@vercel/oidc@3.2.0': resolution: {integrity: sha512-UycprH3T6n3jH0k44NHMa7pnFHGu/N05MjojYr+Mc6I7obkoLIJujSWwin1pCvdy/eOxrI/l3uDLQsmcrOb4ug==} engines: {node: '>= 20'} @@ -1489,6 +1549,10 @@ packages: resolution: {integrity: sha512-4FbRdAX+bSdmo4AUFuS0WNiPz8NgFt+r8ThgNWmlrjQjt1Q7ZR9+zTlce2859x4KSXrwIsaeTqDoKQmtP8pLmQ==} engines: {node: '>= 0.8'} + http-proxy-agent@7.0.2: + resolution: {integrity: sha512-T1gkAiYYDWYx3V5Bmyu7HcfcvL7mUrTWiM6yOfa3PIphViJ/gFPbvidQ+veqSOHci/PxBcDabeUNCzpOODJZig==} + engines: {node: '>= 14'} + https-proxy-agent@5.0.1: resolution: {integrity: sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==} engines: {node: '>= 6'} @@ -1580,6 +1644,10 @@ packages: jwa@2.0.1: resolution: {integrity: sha512-hRF04fqJIP8Abbkq5NKGN0Bbr3JxlQ+qhZufXVr0DvujKy93ZCbXZMHDL4EOtodSbCWxOqR8MS1tXA5hwqCXDg==} + jwks-rsa@4.0.1: + resolution: {integrity: sha512-poXwUA8S4cP9P5N8tZS3xnUDJH8WmwSGfKK9gIaRPdjLHyJtd9iX/cngX9CUIe0Caof5JhK2EbN7N5lnnaf9NA==} + engines: {node: ^20.19.0 || ^22.12.0 || >= 23.0.0} + jws@4.0.1: resolution: {integrity: sha512-EKI/M/yqPncGUUh44xz0PxSidXFr/+r0pA70+gIYhjv+et7yxM+s29Y+VGDkovRofQem0fs7Uvf4+YmAdyRduA==} @@ -1686,6 +1754,12 @@ packages: resolution: {integrity: sha512-NXYBzinNrblfraPGyrbPoD19C1h9lfI/1mzgWYvXUTe414Gz/X1FD2XBZSZM7rRTrMA8JL3OtAaGifrIKhQ5yQ==} engines: {node: '>= 12.0.0'} + limiter@1.1.5: + resolution: {integrity: sha512-FWWMIEOxz3GwUI4Ts/IvgVy6LPvoMPgjMdQ185nN6psJyBJ4yOpzqm695/h5umdLJg2vW3GR5iG11MAkR2AzJA==} + + lodash.clonedeep@4.5.0: + resolution: {integrity: sha512-H5ZhCF25riFd9uB5UCkVKo61m3S/xZk1x4wA6yp/L3RFP6Z/eHH1ymQcGLo7J3GMPfm0V/7m1tryHuGVxpqEBQ==} + lodash.get@4.4.2: resolution: {integrity: sha512-z+Uw/vLuy6gQe8cfaFWD7p0wVv8fJl3mbzXh33RS+0oW2wvUqiRXiQ69gLWSLpgB5/6sU+r6BlQR0MBILadqTQ==} deprecated: This package is deprecated. Use the optional chaining (?.) operator instead. @@ -1723,6 +1797,13 @@ packages: lru-cache@10.4.3: resolution: {integrity: sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==} + lru-cache@11.5.2: + resolution: {integrity: sha512-4pfM1Ff0x50o0tQwb5ucw/RzNyD0/YJME6IVcStalZuMWxdt3sR3huStTtxz4PUmvZfRguvDejasvQ2kifR11g==} + engines: {node: 20 || >=22} + + lru-memoizer@3.0.0: + resolution: {integrity: sha512-m83w/cYXLdUIboKSPxzPAGfYnk+vqeDYXuoSrQRw1q+yVEd8IXhvMufN8Q5TIPe7e2jyX4SRNrDJI2Skw1yznQ==} + magic-bytes.js@1.13.0: resolution: {integrity: sha512-afO2mnxW7GDTXMm5/AoN1WuOcdoKhtgXjIvHmobqTD1grNplhGdv3PFOyjCVmrnOZBIT/gD/koDKpYG+0mvHcg==} @@ -2397,6 +2478,9 @@ packages: zod@3.24.1: resolution: {integrity: sha512-muH7gBL9sI1nciMZV67X5fTKKBLtwpZ5VBp1vsOQzj1MhrBZ4wlVCm3gedKZWLp0Oyel8sIGfeiz54Su+OVT+A==} + zod@3.25.75: + resolution: {integrity: sha512-OhpzAmVzabPOL6C3A3gpAifqr9MqihV/Msx3gor2b2kviCgcb+HM9SEOpMWwwNp9MRunWnhtAKUoo0AHhjyPPg==} + zod@3.25.76: resolution: {integrity: sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ==} @@ -2596,15 +2680,56 @@ snapshots: dependencies: json-schema: 0.4.0 + '@azure/abort-controller@2.2.0': + dependencies: + tslib: 2.8.1 + + '@azure/core-auth@1.10.1': + dependencies: + '@azure/abort-controller': 2.2.0 + '@azure/core-util': 1.14.0 + tslib: 2.8.1 + transitivePeerDependencies: + - supports-color + + '@azure/core-util@1.14.0': + dependencies: + '@azure/abort-controller': 2.2.0 + '@typespec/ts-http-runtime': 0.3.7 + tslib: 2.8.1 + transitivePeerDependencies: + - supports-color + + '@azure/msal-common@16.5.2': {} + + '@azure/msal-node@5.1.5': + dependencies: + '@azure/msal-common': 16.5.2 + jsonwebtoken: 9.0.3 + '@bufbuild/protobuf@2.12.1': {} '@cfworker/json-schema@4.1.1': {} - '@copilotkit/channels-discord@0.0.3(@ag-ui/core@0.0.57)(vitest@4.1.10(@opentelemetry/api@1.9.1)(@types/node@22.20.1)(vite@8.1.4(@types/node@22.20.1)(esbuild@0.28.1)(tsx@4.23.1)))': + '@copilotkit/channels-core@0.2.1(vitest@4.1.10(@opentelemetry/api@1.9.1)(@types/node@22.20.1)(vite@8.1.4(@types/node@22.20.1)(esbuild@0.28.1)(tsx@4.23.1)))(zod@3.25.76)': dependencies: '@ag-ui/client': 0.0.57 - '@copilotkit/channels': 0.1.1(vitest@4.1.10(@opentelemetry/api@1.9.1)(@types/node@22.20.1)(vite@8.1.4(@types/node@22.20.1)(esbuild@0.28.1)(tsx@4.23.1)))(zod@3.25.76) - '@copilotkit/channels-ui': 0.1.1(@ag-ui/core@0.0.57) + '@ag-ui/core': 0.0.57 + '@copilotkit/channels-ui': 0.2.1(@ag-ui/core@0.0.57) + '@copilotkit/core': 1.63.2(@ag-ui/core@0.0.57)(zod@3.25.76) + '@copilotkit/shared': 1.63.2(@ag-ui/core@0.0.57) + zod-to-json-schema: 3.25.2(zod@3.25.76) + optionalDependencies: + vitest: 4.1.10(@opentelemetry/api@1.9.1)(@types/node@22.20.1)(vite@8.1.4(@types/node@22.20.1)(esbuild@0.28.1)(tsx@4.23.1)) + transitivePeerDependencies: + - encoding + - zod + + '@copilotkit/channels-discord@0.2.1(@ag-ui/core@0.0.57)(vitest@4.1.10(@opentelemetry/api@1.9.1)(@types/node@22.20.1)(vite@8.1.4(@types/node@22.20.1)(esbuild@0.28.1)(tsx@4.23.1)))': + dependencies: + '@ag-ui/client': 0.0.57 + '@copilotkit/channels-core': 0.2.1(vitest@4.1.10(@opentelemetry/api@1.9.1)(@types/node@22.20.1)(vite@8.1.4(@types/node@22.20.1)(esbuild@0.28.1)(tsx@4.23.1)))(zod@3.25.76) + '@copilotkit/channels-ui': 0.2.1(@ag-ui/core@0.0.57) discord.js: 14.27.0 zod: 3.25.76 transitivePeerDependencies: @@ -2614,11 +2739,11 @@ snapshots: - utf-8-validate - vitest - '@copilotkit/channels-intelligence@0.1.1(@ag-ui/core@0.0.57)(vitest@4.1.10(@opentelemetry/api@1.9.1)(@types/node@22.20.1)(vite@8.1.4(@types/node@22.20.1)(esbuild@0.28.1)(tsx@4.23.1)))(zod@3.25.76)': + '@copilotkit/channels-intelligence@0.2.1(@ag-ui/core@0.0.57)(vitest@4.1.10(@opentelemetry/api@1.9.1)(@types/node@22.20.1)(vite@8.1.4(@types/node@22.20.1)(esbuild@0.28.1)(tsx@4.23.1)))(zod@3.25.76)': dependencies: '@ag-ui/client': 0.0.57 - '@copilotkit/channels': 0.1.1(vitest@4.1.10(@opentelemetry/api@1.9.1)(@types/node@22.20.1)(vite@8.1.4(@types/node@22.20.1)(esbuild@0.28.1)(tsx@4.23.1)))(zod@3.25.76) - '@copilotkit/channels-ui': 0.1.1(@ag-ui/core@0.0.57) + '@copilotkit/channels-core': 0.2.1(vitest@4.1.10(@opentelemetry/api@1.9.1)(@types/node@22.20.1)(vite@8.1.4(@types/node@22.20.1)(esbuild@0.28.1)(tsx@4.23.1)))(zod@3.25.76) + '@copilotkit/channels-ui': 0.2.1(@ag-ui/core@0.0.57) phoenix: 1.8.9 transitivePeerDependencies: - '@ag-ui/core' @@ -2626,14 +2751,14 @@ snapshots: - vitest - zod - '@copilotkit/channels-slack@0.1.2(@types/express@5.0.6)(vitest@4.1.10(@opentelemetry/api@1.9.1)(@types/node@22.20.1)(vite@8.1.4(@types/node@22.20.1)(esbuild@0.28.1)(tsx@4.23.1)))': + '@copilotkit/channels-slack@0.2.1(@types/express@5.0.6)(vitest@4.1.10(@opentelemetry/api@1.9.1)(@types/node@22.20.1)(vite@8.1.4(@types/node@22.20.1)(esbuild@0.28.1)(tsx@4.23.1)))': dependencies: '@ag-ui/client': 0.0.57 '@ag-ui/core': 0.0.57 - '@copilotkit/channels': 0.1.1(vitest@4.1.10(@opentelemetry/api@1.9.1)(@types/node@22.20.1)(vite@8.1.4(@types/node@22.20.1)(esbuild@0.28.1)(tsx@4.23.1)))(zod@3.25.76) - '@copilotkit/channels-ui': 0.1.1(@ag-ui/core@0.0.57) - '@copilotkit/core': 1.62.3(@ag-ui/core@0.0.57)(zod@3.25.76) - '@copilotkit/shared': 1.62.3(@ag-ui/core@0.0.57) + '@copilotkit/channels-core': 0.2.1(vitest@4.1.10(@opentelemetry/api@1.9.1)(@types/node@22.20.1)(vite@8.1.4(@types/node@22.20.1)(esbuild@0.28.1)(tsx@4.23.1)))(zod@3.25.76) + '@copilotkit/channels-ui': 0.2.1(@ag-ui/core@0.0.57) + '@copilotkit/core': 1.63.2(@ag-ui/core@0.0.57)(zod@3.25.76) + '@copilotkit/shared': 1.63.2(@ag-ui/core@0.0.57) '@slack/bolt': 4.7.3(@types/express@5.0.6) '@slack/types': 2.22.0 '@slack/web-api': 7.19.0 @@ -2649,11 +2774,32 @@ snapshots: - utf-8-validate - vitest - '@copilotkit/channels-telegram@0.0.4(@ag-ui/core@0.0.57)(vitest@4.1.10(@opentelemetry/api@1.9.1)(@types/node@22.20.1)(vite@8.1.4(@types/node@22.20.1)(esbuild@0.28.1)(tsx@4.23.1)))': + '@copilotkit/channels-teams@0.2.1(@opentelemetry/api@1.9.1)(vitest@4.1.10(@opentelemetry/api@1.9.1)(@types/node@22.20.1)(vite@8.1.4(@types/node@22.20.1)(esbuild@0.28.1)(tsx@4.23.1)))': + dependencies: + '@ag-ui/client': 0.0.57 + '@ag-ui/core': 0.0.57 + '@copilotkit/channels-core': 0.2.1(vitest@4.1.10(@opentelemetry/api@1.9.1)(@types/node@22.20.1)(vite@8.1.4(@types/node@22.20.1)(esbuild@0.28.1)(tsx@4.23.1)))(zod@3.25.76) + '@copilotkit/channels-ui': 0.2.1(@ag-ui/core@0.0.57) + '@copilotkit/core': 1.63.2(@ag-ui/core@0.0.57)(zod@3.25.76) + '@copilotkit/shared': 1.63.2(@ag-ui/core@0.0.57) + '@microsoft/agents-activity': 1.7.1 + '@microsoft/agents-hosting': 1.7.1(@opentelemetry/api@1.9.1) + express: 4.22.2 + rxjs: 7.8.2 + zod: 3.25.76 + zod-to-json-schema: 3.25.2(zod@3.25.76) + transitivePeerDependencies: + - '@opentelemetry/api' + - '@opentelemetry/api-logs' + - encoding + - supports-color + - vitest + + '@copilotkit/channels-telegram@0.2.1(@ag-ui/core@0.0.57)(vitest@4.1.10(@opentelemetry/api@1.9.1)(@types/node@22.20.1)(vite@8.1.4(@types/node@22.20.1)(esbuild@0.28.1)(tsx@4.23.1)))': dependencies: '@ag-ui/client': 0.0.57 - '@copilotkit/channels': 0.1.1(vitest@4.1.10(@opentelemetry/api@1.9.1)(@types/node@22.20.1)(vite@8.1.4(@types/node@22.20.1)(esbuild@0.28.1)(tsx@4.23.1)))(zod@3.25.76) - '@copilotkit/channels-ui': 0.1.1(@ag-ui/core@0.0.57) + '@copilotkit/channels-core': 0.2.1(vitest@4.1.10(@opentelemetry/api@1.9.1)(@types/node@22.20.1)(vite@8.1.4(@types/node@22.20.1)(esbuild@0.28.1)(tsx@4.23.1)))(zod@3.25.76) + '@copilotkit/channels-ui': 0.2.1(@ag-ui/core@0.0.57) grammy: 1.44.0 zod: 3.25.76 transitivePeerDependencies: @@ -2662,42 +2808,52 @@ snapshots: - supports-color - vitest - '@copilotkit/channels-ui@0.1.1(@ag-ui/core@0.0.57)': + '@copilotkit/channels-ui@0.2.1(@ag-ui/core@0.0.57)': dependencies: - '@copilotkit/shared': 1.62.3(@ag-ui/core@0.0.57) + '@copilotkit/shared': 1.63.2(@ag-ui/core@0.0.57) transitivePeerDependencies: - '@ag-ui/core' - encoding - '@copilotkit/channels-whatsapp@0.0.2(@ag-ui/core@0.0.57)(vitest@4.1.10(@opentelemetry/api@1.9.1)(@types/node@22.20.1)(vite@8.1.4(@types/node@22.20.1)(esbuild@0.28.1)(tsx@4.23.1)))(zod@3.25.76)': + '@copilotkit/channels-whatsapp@0.2.1(@ag-ui/core@0.0.57)(vitest@4.1.10(@opentelemetry/api@1.9.1)(@types/node@22.20.1)(vite@8.1.4(@types/node@22.20.1)(esbuild@0.28.1)(tsx@4.23.1)))(zod@3.25.76)': dependencies: '@ag-ui/client': 0.0.57 - '@copilotkit/channels': 0.1.1(vitest@4.1.10(@opentelemetry/api@1.9.1)(@types/node@22.20.1)(vite@8.1.4(@types/node@22.20.1)(esbuild@0.28.1)(tsx@4.23.1)))(zod@3.25.76) - '@copilotkit/channels-ui': 0.1.1(@ag-ui/core@0.0.57) + '@copilotkit/channels-core': 0.2.1(vitest@4.1.10(@opentelemetry/api@1.9.1)(@types/node@22.20.1)(vite@8.1.4(@types/node@22.20.1)(esbuild@0.28.1)(tsx@4.23.1)))(zod@3.25.76) + '@copilotkit/channels-ui': 0.2.1(@ag-ui/core@0.0.57) transitivePeerDependencies: - '@ag-ui/core' - encoding - vitest - zod - '@copilotkit/channels@0.1.1(vitest@4.1.10(@opentelemetry/api@1.9.1)(@types/node@22.20.1)(vite@8.1.4(@types/node@22.20.1)(esbuild@0.28.1)(tsx@4.23.1)))(zod@3.25.76)': + '@copilotkit/channels@0.2.1(@ag-ui/core@0.0.57)(@opentelemetry/api@1.9.1)(@types/express@5.0.6)(vitest@4.1.10(@opentelemetry/api@1.9.1)(@types/node@22.20.1)(vite@8.1.4(@types/node@22.20.1)(esbuild@0.28.1)(tsx@4.23.1)))(zod@3.25.76)': dependencies: - '@ag-ui/client': 0.0.57 - '@ag-ui/core': 0.0.57 - '@copilotkit/channels-ui': 0.1.1(@ag-ui/core@0.0.57) - '@copilotkit/core': 1.62.3(@ag-ui/core@0.0.57)(zod@3.25.76) - '@copilotkit/shared': 1.62.3(@ag-ui/core@0.0.57) - zod-to-json-schema: 3.25.2(zod@3.25.76) + '@copilotkit/channels-core': 0.2.1(vitest@4.1.10(@opentelemetry/api@1.9.1)(@types/node@22.20.1)(vite@8.1.4(@types/node@22.20.1)(esbuild@0.28.1)(tsx@4.23.1)))(zod@3.25.76) + '@copilotkit/channels-discord': 0.2.1(@ag-ui/core@0.0.57)(vitest@4.1.10(@opentelemetry/api@1.9.1)(@types/node@22.20.1)(vite@8.1.4(@types/node@22.20.1)(esbuild@0.28.1)(tsx@4.23.1))) + '@copilotkit/channels-intelligence': 0.2.1(@ag-ui/core@0.0.57)(vitest@4.1.10(@opentelemetry/api@1.9.1)(@types/node@22.20.1)(vite@8.1.4(@types/node@22.20.1)(esbuild@0.28.1)(tsx@4.23.1)))(zod@3.25.76) + '@copilotkit/channels-slack': 0.2.1(@types/express@5.0.6)(vitest@4.1.10(@opentelemetry/api@1.9.1)(@types/node@22.20.1)(vite@8.1.4(@types/node@22.20.1)(esbuild@0.28.1)(tsx@4.23.1))) + '@copilotkit/channels-teams': 0.2.1(@opentelemetry/api@1.9.1)(vitest@4.1.10(@opentelemetry/api@1.9.1)(@types/node@22.20.1)(vite@8.1.4(@types/node@22.20.1)(esbuild@0.28.1)(tsx@4.23.1))) + '@copilotkit/channels-telegram': 0.2.1(@ag-ui/core@0.0.57)(vitest@4.1.10(@opentelemetry/api@1.9.1)(@types/node@22.20.1)(vite@8.1.4(@types/node@22.20.1)(esbuild@0.28.1)(tsx@4.23.1))) + '@copilotkit/channels-ui': 0.2.1(@ag-ui/core@0.0.57) + '@copilotkit/channels-whatsapp': 0.2.1(@ag-ui/core@0.0.57)(vitest@4.1.10(@opentelemetry/api@1.9.1)(@types/node@22.20.1)(vite@8.1.4(@types/node@22.20.1)(esbuild@0.28.1)(tsx@4.23.1)))(zod@3.25.76) optionalDependencies: vitest: 4.1.10(@opentelemetry/api@1.9.1)(@types/node@22.20.1)(vite@8.1.4(@types/node@22.20.1)(esbuild@0.28.1)(tsx@4.23.1)) transitivePeerDependencies: + - '@ag-ui/core' + - '@opentelemetry/api' + - '@opentelemetry/api-logs' + - '@types/express' + - bufferutil + - debug - encoding + - supports-color + - utf-8-validate - zod - '@copilotkit/core@1.62.3(@ag-ui/core@0.0.57)(zod@3.25.76)': + '@copilotkit/core@1.63.2(@ag-ui/core@0.0.57)(zod@3.25.76)': dependencies: '@ag-ui/client': 0.0.57 - '@copilotkit/shared': 1.62.3(@ag-ui/core@0.0.57) + '@copilotkit/shared': 1.63.2(@ag-ui/core@0.0.57) '@tanstack/pacer': 0.20.1 phoenix: 1.8.9 rxjs: 7.8.2 @@ -2786,6 +2942,22 @@ snapshots: transitivePeerDependencies: - encoding + '@copilotkit/shared@1.63.2(@ag-ui/core@0.0.57)': + dependencies: + '@ag-ui/client': 0.0.57 + '@ag-ui/core': 0.0.57 + '@copilotkit/license-verifier': 0.5.0 + '@segment/analytics-node': 2.3.0 + '@standard-schema/spec': 1.1.0 + chalk: 4.1.2 + graphql: 16.14.2 + partial-json: 0.1.7 + uuid: 11.1.1 + zod: 3.25.76 + zod-to-json-schema: 3.25.2(zod@3.25.76) + transitivePeerDependencies: + - encoding + '@discordjs/builders@1.14.1': dependencies: '@discordjs/formatters': 0.6.2 @@ -3071,6 +3243,33 @@ snapshots: dependencies: '@lukeed/csprng': 1.1.0 + '@microsoft/agents-activity@1.7.1': + dependencies: + zod: 3.25.75 + + '@microsoft/agents-hosting@1.7.1(@opentelemetry/api@1.9.1)': + dependencies: + '@azure/core-auth': 1.10.1 + '@azure/msal-node': 5.1.5 + '@microsoft/agents-activity': 1.7.1 + '@microsoft/agents-telemetry': 1.7.1(@opentelemetry/api@1.9.1) + jsonwebtoken: 9.0.3 + jwks-rsa: 4.0.1 + zod: 3.25.75 + transitivePeerDependencies: + - '@opentelemetry/api' + - '@opentelemetry/api-logs' + - supports-color + + '@microsoft/agents-telemetry@1.7.1(@opentelemetry/api@1.9.1)': + dependencies: + '@microsoft/agents-activity': 1.7.1 + debug: 4.4.3 + optionalDependencies: + '@opentelemetry/api': 1.9.1 + transitivePeerDependencies: + - supports-color + '@modelcontextprotocol/sdk@1.29.0(@cfworker/json-schema@4.1.1)(zod@3.24.1)': dependencies: '@hono/node-server': 1.19.14(hono@4.12.30) @@ -3463,6 +3662,14 @@ snapshots: dependencies: '@types/node': 22.20.1 + '@typespec/ts-http-runtime@0.3.7': + dependencies: + http-proxy-agent: 7.0.2 + https-proxy-agent: 7.0.6 + tslib: 2.8.1 + transitivePeerDependencies: + - supports-color + '@vercel/oidc@3.2.0': {} '@vitest/expect@4.1.10': @@ -4136,6 +4343,13 @@ snapshots: statuses: 2.0.2 toidentifier: 1.0.1 + http-proxy-agent@7.0.2: + dependencies: + agent-base: 7.1.4 + debug: 4.4.3 + transitivePeerDependencies: + - supports-color + https-proxy-agent@5.0.1: dependencies: agent-base: 6.0.2 @@ -4223,6 +4437,16 @@ snapshots: ecdsa-sig-formatter: 1.0.11 safe-buffer: 5.2.1 + jwks-rsa@4.0.1: + dependencies: + '@types/jsonwebtoken': 9.0.10 + debug: 4.4.3 + jose: 6.2.3 + limiter: 1.1.5 + lru-memoizer: 3.0.0 + transitivePeerDependencies: + - supports-color + jws@4.0.1: dependencies: jwa: 2.0.1 @@ -4305,6 +4529,10 @@ snapshots: lightningcss-win32-arm64-msvc: 1.32.0 lightningcss-win32-x64-msvc: 1.32.0 + limiter@1.1.5: {} + + lodash.clonedeep@4.5.0: {} + lodash.get@4.4.2: {} lodash.includes@4.3.0: {} @@ -4329,6 +4557,13 @@ snapshots: lru-cache@10.4.3: {} + lru-cache@11.5.2: {} + + lru-memoizer@3.0.0: + dependencies: + lodash.clonedeep: 4.5.0 + lru-cache: 11.5.2 + magic-bytes.js@1.13.0: {} magic-string@0.30.21: @@ -4931,4 +5166,6 @@ snapshots: zod@3.24.1: {} + zod@3.25.75: {} + zod@3.25.76: {} From ae6a3b1686d5dca0893e0afa8a40069ddfee0986 Mon Sep 17 00:00:00 2001 From: GeneralJerel <85066839+GeneralJerel@users.noreply.github.com> Date: Fri, 24 Jul 2026 09:27:31 -0700 Subject: [PATCH 03/50] chore(deps): raise @copilotkit/runtime floor to ^1.63.2 (v2 channels API) Co-Authored-By: Claude Opus 4.8 --- package.json | 2 +- pnpm-lock.yaml | 36 +++++++++--------------------------- 2 files changed, 10 insertions(+), 28 deletions(-) diff --git a/package.json b/package.json index e80d250..3841002 100644 --- a/package.json +++ b/package.json @@ -26,7 +26,7 @@ "@copilotkit/channels-telegram": "^0.2.1", "@copilotkit/channels-ui": "^0.2.1", "@copilotkit/channels-whatsapp": "^0.2.1", - "@copilotkit/runtime": "^1.62.3", + "@copilotkit/runtime": "^1.63.2", "@notionhq/notion-mcp-server": "^2.4.0", "@slack/bolt": "^4.2.0", "@slack/types": "^2.21.1", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 5f32559..f6500ce 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -33,8 +33,8 @@ importers: specifier: ^0.2.1 version: 0.2.1(@ag-ui/core@0.0.57)(vitest@4.1.10(@opentelemetry/api@1.9.1)(@types/node@22.20.1)(vite@8.1.4(@types/node@22.20.1)(esbuild@0.28.1)(tsx@4.23.1)))(zod@3.25.76) '@copilotkit/runtime': - specifier: ^1.62.3 - version: 1.62.3(@cfworker/json-schema@4.1.1)(@langchain/core@1.2.3(@opentelemetry/api@1.9.1)(openai@6.47.0(ws@8.21.1)(zod@3.25.76))(ws@8.21.1))(@langchain/langgraph-sdk@1.9.27(@langchain/core@1.2.3(@opentelemetry/api@1.9.1)(openai@6.47.0(ws@8.21.1)(zod@3.25.76))(ws@8.21.1)))(@opentelemetry/api@1.9.1)(langchain@1.5.3(@langchain/core@1.2.3(@opentelemetry/api@1.9.1)(openai@6.47.0(ws@8.21.1)(zod@3.25.76))(ws@8.21.1))(@opentelemetry/api@1.9.1)(openai@6.47.0(ws@8.21.1)(zod@3.25.76))(ws@8.21.1))(openai@6.47.0(ws@8.21.1)(zod@3.25.76)) + specifier: ^1.63.2 + version: 1.63.2(@cfworker/json-schema@4.1.1)(@langchain/core@1.2.3(@opentelemetry/api@1.9.1)(openai@6.47.0(ws@8.21.1)(zod@3.25.76))(ws@8.21.1))(@langchain/langgraph-sdk@1.9.27(@langchain/core@1.2.3(@opentelemetry/api@1.9.1)(openai@6.47.0(ws@8.21.1)(zod@3.25.76))(ws@8.21.1)))(@opentelemetry/api@1.9.1)(langchain@1.5.3(@langchain/core@1.2.3(@opentelemetry/api@1.9.1)(openai@6.47.0(ws@8.21.1)(zod@3.25.76))(ws@8.21.1))(@opentelemetry/api@1.9.1)(openai@6.47.0(ws@8.21.1)(zod@3.25.76))(ws@8.21.1))(openai@6.47.0(ws@8.21.1)(zod@3.25.76))(vitest@4.1.10(@opentelemetry/api@1.9.1)(@types/node@22.20.1)(vite@8.1.4(@types/node@22.20.1)(esbuild@0.28.1)(tsx@4.23.1))) '@notionhq/notion-mcp-server': specifier: ^2.4.0 version: 2.4.1(@cfworker/json-schema@4.1.1)(js-yaml@4.3.0) @@ -277,8 +277,8 @@ packages: '@copilotkit/license-verifier@0.5.0': resolution: {integrity: sha512-vrwKtIpYwF0FT9ZoYASH8owa2cGV0dhDvJGaCRaRMStwDxpc6DRdydKkhx8cWZXyBRxEYcq/Vygv4JvevhQQdQ==} - '@copilotkit/runtime@1.62.3': - resolution: {integrity: sha512-pOyQbd0IGH/rmyZ7yoGodc2FRicO3AXBI6y7lQDGjbat2lI9SLhsGQve7O3QfeYH0UZ4T9019EgnnKtSN7ImUQ==} + '@copilotkit/runtime@1.63.2': + resolution: {integrity: sha512-3J4YMEH/6avP+OiNihKHSIxluUsBfyGz5UbIkreviqZLdEtGvZOesd2ITilb5ClrivQhSxlW7uX5j2Im+pQcfQ==} peerDependencies: '@anthropic-ai/sdk': ^0.57.0 '@langchain/aws': '>=0.1.9' @@ -310,11 +310,6 @@ packages: openai: optional: true - '@copilotkit/shared@1.62.3': - resolution: {integrity: sha512-dfqaYjfJzTIjMBUMOHGZGoZ+v9pumsBuM1FMi5Sb66WkzxbtIKN0CCbYXXIHd0xsEo1pa8u1vZSN30sBtJuQ7g==} - peerDependencies: - '@ag-ui/core': '>=0.0.48' - '@copilotkit/shared@1.63.2': resolution: {integrity: sha512-vgpMY+YTkDYTEL4Slbj9S5kEXEYGOIuGTKdBkTVL1k8sPbj21w8OzqQ5No6TN+e+HJrbC3kr/r4CXM9QkHxezA==} peerDependencies: @@ -2865,7 +2860,7 @@ snapshots: '@copilotkit/license-verifier@0.5.0': {} - '@copilotkit/runtime@1.62.3(@cfworker/json-schema@4.1.1)(@langchain/core@1.2.3(@opentelemetry/api@1.9.1)(openai@6.47.0(ws@8.21.1)(zod@3.25.76))(ws@8.21.1))(@langchain/langgraph-sdk@1.9.27(@langchain/core@1.2.3(@opentelemetry/api@1.9.1)(openai@6.47.0(ws@8.21.1)(zod@3.25.76))(ws@8.21.1)))(@opentelemetry/api@1.9.1)(langchain@1.5.3(@langchain/core@1.2.3(@opentelemetry/api@1.9.1)(openai@6.47.0(ws@8.21.1)(zod@3.25.76))(ws@8.21.1))(@opentelemetry/api@1.9.1)(openai@6.47.0(ws@8.21.1)(zod@3.25.76))(ws@8.21.1))(openai@6.47.0(ws@8.21.1)(zod@3.25.76))': + '@copilotkit/runtime@1.63.2(@cfworker/json-schema@4.1.1)(@langchain/core@1.2.3(@opentelemetry/api@1.9.1)(openai@6.47.0(ws@8.21.1)(zod@3.25.76))(ws@8.21.1))(@langchain/langgraph-sdk@1.9.27(@langchain/core@1.2.3(@opentelemetry/api@1.9.1)(openai@6.47.0(ws@8.21.1)(zod@3.25.76))(ws@8.21.1)))(@opentelemetry/api@1.9.1)(langchain@1.5.3(@langchain/core@1.2.3(@opentelemetry/api@1.9.1)(openai@6.47.0(ws@8.21.1)(zod@3.25.76))(ws@8.21.1))(@opentelemetry/api@1.9.1)(openai@6.47.0(ws@8.21.1)(zod@3.25.76))(ws@8.21.1))(openai@6.47.0(ws@8.21.1)(zod@3.25.76))(vitest@4.1.10(@opentelemetry/api@1.9.1)(@types/node@22.20.1)(vite@8.1.4(@types/node@22.20.1)(esbuild@0.28.1)(tsx@4.23.1)))': dependencies: '@ag-ui/a2ui-middleware': 0.0.10(@ag-ui/client@0.0.57)(rxjs@7.8.2) '@ag-ui/client': 0.0.57 @@ -2879,8 +2874,10 @@ snapshots: '@ai-sdk/google-vertex': 3.0.150(zod@3.25.76) '@ai-sdk/mcp': 1.0.62(zod@3.25.76) '@ai-sdk/openai': 3.0.85(zod@3.25.76) + '@copilotkit/channels-core': 0.2.1(vitest@4.1.10(@opentelemetry/api@1.9.1)(@types/node@22.20.1)(vite@8.1.4(@types/node@22.20.1)(esbuild@0.28.1)(tsx@4.23.1)))(zod@3.25.76) + '@copilotkit/channels-intelligence': 0.2.1(@ag-ui/core@0.0.57)(vitest@4.1.10(@opentelemetry/api@1.9.1)(@types/node@22.20.1)(vite@8.1.4(@types/node@22.20.1)(esbuild@0.28.1)(tsx@4.23.1)))(zod@3.25.76) '@copilotkit/license-verifier': 0.5.0 - '@copilotkit/shared': 1.62.3(@ag-ui/core@0.0.57) + '@copilotkit/shared': 1.63.2(@ag-ui/core@0.0.57) '@graphql-yoga/plugin-defer-stream': 3.21.2(graphql-yoga@5.21.2(graphql@16.14.2))(graphql@16.14.2) '@hono/node-server': 1.19.14(hono@4.12.30) '@langchain/core': 1.2.3(@opentelemetry/api@1.9.1)(openai@6.47.0(ws@8.21.1)(zod@3.25.76))(ws@8.21.1) @@ -2924,24 +2921,9 @@ snapshots: - supports-color - svelte - utf-8-validate + - vitest - vue - '@copilotkit/shared@1.62.3(@ag-ui/core@0.0.57)': - dependencies: - '@ag-ui/client': 0.0.57 - '@ag-ui/core': 0.0.57 - '@copilotkit/license-verifier': 0.5.0 - '@segment/analytics-node': 2.3.0 - '@standard-schema/spec': 1.1.0 - chalk: 4.1.2 - graphql: 16.14.2 - partial-json: 0.1.7 - uuid: 11.1.1 - zod: 3.25.76 - zod-to-json-schema: 3.25.2(zod@3.25.76) - transitivePeerDependencies: - - encoding - '@copilotkit/shared@1.63.2(@ag-ui/core@0.0.57)': dependencies: '@ag-ui/client': 0.0.57 From 701d70e5e856131465b93ed1dfd2d88855ae2e3b Mon Sep 17 00:00:00 2001 From: GeneralJerel <85066839+GeneralJerel@users.noreply.github.com> Date: Fri, 24 Jul 2026 09:39:48 -0700 Subject: [PATCH 04/50] refactor(app): migrate app/** to @copilotkit/channels 0.2 (createChannel rename set) Co-Authored-By: Claude Opus 4.8 --- app/commands/__tests__/commands.test.ts | 8 +++---- app/commands/index.ts | 16 +++++++------- app/components/issue-card.tsx | 4 ++-- app/components/issue-list.tsx | 4 ++-- app/components/page-list.tsx | 6 +++--- .../__tests__/confirm-write-tool.test.tsx | 4 ++-- .../__tests__/confirm-write.test.tsx | 21 +++++++++++-------- app/human-in-the-loop/confirm-write-tool.tsx | 4 ++-- app/modals/__tests__/file-issue.test.tsx | 8 +++---- app/modals/file-issue.tsx | 2 +- app/tools/__tests__/read-thread.test.ts | 4 ++-- app/tools/__tests__/render-tools.test.ts | 2 +- app/tools/__tests__/showcase-tools.test.tsx | 16 +++++++------- app/tools/index.ts | 12 +++++------ app/tools/read-thread.ts | 8 +++---- app/tools/render-chart.tsx | 4 ++-- app/tools/render-diagram.tsx | 4 ++-- app/tools/render-table.tsx | 4 ++-- app/tools/render-tools.tsx | 10 ++++----- app/tools/showcase-tools.tsx | 10 ++++----- 20 files changed, 77 insertions(+), 74 deletions(-) diff --git a/app/commands/__tests__/commands.test.ts b/app/commands/__tests__/commands.test.ts index 7a4a446..63dc323 100644 --- a/app/commands/__tests__/commands.test.ts +++ b/app/commands/__tests__/commands.test.ts @@ -1,14 +1,14 @@ import { describe, it, expect, vi } from "vitest"; import { renderToIR } from "@copilotkit/channels-ui"; -import type { BotNode } from "@copilotkit/channels-ui"; +import type { ChannelNode } from "@copilotkit/channels-ui"; import { appCommands } from "../index.js"; import type { CommandContext } from "@copilotkit/channels"; -function tags(node: BotNode | unknown, acc: string[] = []): string[] { +function tags(node: ChannelNode | unknown, acc: string[] = []): string[] { if (!node || typeof node !== "object") return acc; - const n = node as BotNode; + const n = node as ChannelNode; if (typeof n.type === "string") acc.push(n.type); - for (const c of (n.props?.children as BotNode[] | undefined) ?? []) { + for (const c of (n.props?.children as ChannelNode[] | undefined) ?? []) { tags(c, acc); } return acc; diff --git a/app/commands/index.ts b/app/commands/index.ts index aa0c216..579803c 100644 --- a/app/commands/index.ts +++ b/app/commands/index.ts @@ -1,6 +1,6 @@ /** * Slash commands for this bot. Each is registered with the engine via - * `createBot({ commands })`; the Slack adapter forwards every `/command` it + * `createChannel({ commands })`; the Slack adapter forwards every `/command` it * receives and the engine routes by name (ignoring unregistered ones). * * NOTE: a slash command only fires if it's also declared in the Slack app @@ -11,8 +11,8 @@ * surfaces with native structured args (e.g. Discord). The `options` schema * is optional and used there for registration/typing. */ -import { defineBotCommand } from "@copilotkit/channels"; -import type { BotCommand } from "@copilotkit/channels"; +import { defineChannelCommand } from "@copilotkit/channels"; +import type { ChannelCommand } from "@copilotkit/channels"; import type { Thread as BotThread } from "@copilotkit/channels-ui"; import { senderContext } from "../sender-context.js"; import { IssueCard } from "../components/index.js"; @@ -42,12 +42,12 @@ async function runAgentSafely( } } -export const appCommands: BotCommand[] = [ +export const appCommands: ChannelCommand[] = [ // `/agent ` — a mention-free entry point. (Previously hardcoded in the // adapter; now an ordinary, app-owned command.) Runs the agent with the // command text as the user prompt, since slash-command args are never // posted to the channel for the agent to read from history. - defineBotCommand({ + defineChannelCommand({ name: "agent", description: "Ask the triage agent anything (no @mention needed).", async handler({ thread, text, user }) { @@ -64,7 +64,7 @@ export const appCommands: BotCommand[] = [ // `/triage [note]` — summarize the current channel/thread and propose Linear // issues to file. Demonstrates a command with its own intent. - defineBotCommand({ + defineChannelCommand({ name: "triage", description: "Summarize the conversation and propose Linear issues to file.", @@ -85,7 +85,7 @@ export const appCommands: BotCommand[] = [ // a native only-you message; Discord and Telegram have no ephemeral surface, so // `fallbackToDM: true` sends it as a direct message instead. We narrate which // path was taken so the degradation is visible, never silent. - defineBotCommand({ + defineChannelCommand({ name: "preview", description: "Privately preview the issue I'd file (only you see it).", async handler({ thread, text, user, platform }) { @@ -130,7 +130,7 @@ export const appCommands: BotCommand[] = [ // dropdowns/radio drop and defaults apply (see FileIssueModal). // - Telegram→ no modal trigger at all (`ctx.openModal` is undefined), so we // say so and continue the same job conversationally via the agent. - defineBotCommand({ + defineChannelCommand({ name: "file-issue", description: "Open a form to file a Linear issue.", async handler({ thread, openModal, platform, user }) { diff --git a/app/components/issue-card.tsx b/app/components/issue-card.tsx index fbe9f2f..04f26a8 100644 --- a/app/components/issue-card.tsx +++ b/app/components/issue-card.tsx @@ -20,7 +20,7 @@ import { Message, Section, } from "@copilotkit/channels-ui"; -import type { BotNode } from "@copilotkit/channels-ui"; +import type { ChannelNode } from "@copilotkit/channels-ui"; import { accentForIssue, priorityGlyph, stateGlyph } from "./_status.js"; export const issueCardSchema = z.object({ @@ -51,7 +51,7 @@ export const issueCardSchema = z.object({ export type IssueCardProps = z.infer; /** Render ONE Linear issue as a rich Block Kit card. */ -export function IssueCard(issue: IssueCardProps): BotNode { +export function IssueCard(issue: IssueCardProps): ChannelNode { const titleText = issue.url ? `[**${issue.title}**](${issue.url})` : `**${issue.title}**`; diff --git a/app/components/issue-list.tsx b/app/components/issue-list.tsx index 9a3c71f..76bbbf2 100644 --- a/app/components/issue-list.tsx +++ b/app/components/issue-list.tsx @@ -17,7 +17,7 @@ */ import { z } from "zod"; import { Context, Header, Message, Section } from "@copilotkit/channels-ui"; -import type { BotNode } from "@copilotkit/channels-ui"; +import type { ChannelNode } from "@copilotkit/channels-ui"; import { accentForIssues, stateGlyph } from "./_status.js"; const issueSchema = z.object({ @@ -59,7 +59,7 @@ const MAX = 15; const TITLE_MAX = 70; /** Render a list of Linear issues as a compact, fixed-size Block Kit card. */ -export function IssueList({ heading, issues }: IssueListProps): BotNode { +export function IssueList({ heading, issues }: IssueListProps): ChannelNode { const lines = issues.slice(0, MAX).map((issue: Issue) => { const idLink = issue.url ? `[**${issue.identifier}**](${issue.url})` diff --git a/app/components/page-list.tsx b/app/components/page-list.tsx index f7dca33..127f126 100644 --- a/app/components/page-list.tsx +++ b/app/components/page-list.tsx @@ -17,7 +17,7 @@ */ import { z } from "zod"; import { Context, Divider, Header, Message, Section } from "@copilotkit/channels-ui"; -import type { BotNode } from "@copilotkit/channels-ui"; +import type { ChannelNode } from "@copilotkit/channels-ui"; import { ACCENT } from "./_status.js"; const pageSchema = z.object({ @@ -49,9 +49,9 @@ type Page = z.infer; const MAX = 15; /** Render a list of Notion pages as a Block Kit card. */ -export function PageList({ heading, pages }: PageListProps): BotNode { +export function PageList({ heading, pages }: PageListProps): ChannelNode { const shown = pages.slice(0, MAX); - const rows: BotNode[] = []; + const rows: ChannelNode[] = []; shown.forEach((page: Page, i: number) => { const titleLink = page.url ? `[**${page.title}**](${page.url})` diff --git a/app/human-in-the-loop/__tests__/confirm-write-tool.test.tsx b/app/human-in-the-loop/__tests__/confirm-write-tool.test.tsx index d223b44..ae92ddb 100644 --- a/app/human-in-the-loop/__tests__/confirm-write-tool.test.tsx +++ b/app/human-in-the-loop/__tests__/confirm-write-tool.test.tsx @@ -1,5 +1,5 @@ import { describe, it, expect, vi } from "vitest"; -import { renderToIR, type BotNode } from "@copilotkit/channels-ui"; +import { renderToIR, type ChannelNode } from "@copilotkit/channels-ui"; import { renderSlackMessage } from "@copilotkit/channels-slack"; import { confirmWriteTool } from "../confirm-write-tool.js"; @@ -32,7 +32,7 @@ describe("confirm_write tool", () => { // The posted UI is a ConfirmWrite picker: amber accent + header carrying the action. expect(awaited).toHaveLength(1); const { blocks, accent } = renderSlackMessage( - renderToIR(awaited[0] as BotNode), + renderToIR(awaited[0] as ChannelNode), ); expect(accent).toBe("#E2B340"); const header = blocks.find((b) => b.type === "header") as diff --git a/app/human-in-the-loop/__tests__/confirm-write.test.tsx b/app/human-in-the-loop/__tests__/confirm-write.test.tsx index fbe98bd..1c6dbc5 100644 --- a/app/human-in-the-loop/__tests__/confirm-write.test.tsx +++ b/app/human-in-the-loop/__tests__/confirm-write.test.tsx @@ -1,7 +1,7 @@ import { describe, it, expect, vi } from "vitest"; import { renderToIR, - type BotNode, + type ChannelNode, type InteractionContext, type ClickHandler, } from "@copilotkit/channels-ui"; @@ -9,27 +9,30 @@ import { renderSlackMessage } from "@copilotkit/channels-slack"; import { ConfirmWrite } from "../confirm-write.js"; /** Children of an IR node as an array (empty if none). */ -function childNodes(node: BotNode): BotNode[] { +function childNodes(node: ChannelNode): ChannelNode[] { const children = node.props?.children; - if (Array.isArray(children)) return children as BotNode[]; + if (Array.isArray(children)) return children as ChannelNode[]; if ( children && typeof children === "object" && "type" in (children as object) ) { - return [children as BotNode]; + return [children as ChannelNode]; } return []; } /** Concatenate the text of all descendant `text` nodes (depth-first). */ -function collectText(node: BotNode): string { +function collectText(node: ChannelNode): string { if (node.type === "text") return String(node.props?.value ?? ""); return childNodes(node).map(collectText).join(""); } /** Walk the whole tree to find the first node of a given intrinsic type. */ -function findByType(nodes: BotNode[], type: string): BotNode | undefined { +function findByType( + nodes: ChannelNode[], + type: string, +): ChannelNode | undefined { for (const n of nodes) { if (n.type === type) return n; const hit = findByType(childNodes(n), type); @@ -39,8 +42,8 @@ function findByType(nodes: BotNode[], type: string): BotNode | undefined { } /** All button nodes in the tree. */ -function findButtons(nodes: BotNode[]): BotNode[] { - const out: BotNode[] = []; +function findButtons(nodes: ChannelNode[]): ChannelNode[] { + const out: ChannelNode[] = []; for (const n of nodes) { if (n.type === "button") out.push(n); out.push(...findButtons(childNodes(n))); @@ -48,7 +51,7 @@ function findButtons(nodes: BotNode[]): BotNode[] { return out; } -function buttonByText(ir: BotNode[], text: string): BotNode { +function buttonByText(ir: ChannelNode[], text: string): ChannelNode { const btn = findButtons(ir).find((b) => collectText(b) === text); if (!btn) throw new Error(`button "${text}" not found`); return btn; diff --git a/app/human-in-the-loop/confirm-write-tool.tsx b/app/human-in-the-loop/confirm-write-tool.tsx index 71fa7b1..d47e3a3 100644 --- a/app/human-in-the-loop/confirm-write-tool.tsx +++ b/app/human-in-the-loop/confirm-write-tool.tsx @@ -10,7 +10,7 @@ * performs the write once this returns `{ confirmed: true }`. */ import { z } from "zod"; -import { defineBotTool } from "@copilotkit/channels"; +import { defineChannelTool } from "@copilotkit/channels"; import { ConfirmWrite } from "./confirm-write.js"; export const confirmWriteSchema = z.object({ @@ -27,7 +27,7 @@ export const confirmWriteSchema = z.object({ ), }); -export const confirmWriteTool = defineBotTool({ +export const confirmWriteTool = defineChannelTool({ name: "confirm_write", description: "Ask the user to approve a write before you perform it. Posts a " + diff --git a/app/modals/__tests__/file-issue.test.tsx b/app/modals/__tests__/file-issue.test.tsx index 066073a..edf712b 100644 --- a/app/modals/__tests__/file-issue.test.tsx +++ b/app/modals/__tests__/file-issue.test.tsx @@ -6,14 +6,14 @@ import { issueFromValues, FILE_ISSUE_CALLBACK, } from "../file-issue.js"; -import type { BotNode } from "@copilotkit/channels-ui"; +import type { ChannelNode } from "@copilotkit/channels-ui"; import { senderContext } from "../../sender-context.js"; -function tags(node: BotNode | unknown, acc: string[] = []): string[] { +function tags(node: ChannelNode | unknown, acc: string[] = []): string[] { if (!node || typeof node !== "object") return acc; - const n = node as BotNode; + const n = node as ChannelNode; if (typeof n.type === "string") acc.push(n.type); - for (const c of (n.props?.children as BotNode[] | undefined) ?? []) { + for (const c of (n.props?.children as ChannelNode[] | undefined) ?? []) { tags(c, acc); } return acc; diff --git a/app/modals/file-issue.tsx b/app/modals/file-issue.tsx index cebf48a..104406c 100644 --- a/app/modals/file-issue.tsx +++ b/app/modals/file-issue.tsx @@ -120,7 +120,7 @@ export function FileIssueModal({ rich }: { rich: boolean }): ModalView { ) : null} - // JSX expressions type as `JSX.Element` (= `BotNode`, not `ModalView`) per + // JSX expressions type as `JSX.Element` (= `ChannelNode`, not `ModalView`) per // the channels-ui jsx-runtime, so the narrower literal `type: "modal"` needs // an explicit assertion even though `Modal` itself returns `ModalView`. ) as ModalView; diff --git a/app/tools/__tests__/read-thread.test.ts b/app/tools/__tests__/read-thread.test.ts index 60198d2..fd29d6d 100644 --- a/app/tools/__tests__/read-thread.test.ts +++ b/app/tools/__tests__/read-thread.test.ts @@ -2,12 +2,12 @@ import { describe, it, expect, vi } from "vitest"; import { readThreadTool } from "../read-thread.js"; import type { ThreadMessage } from "@copilotkit/channels-ui"; -/** The ctx a BotTool handler receives. */ +/** The ctx a ChannelTool handler receives. */ type HandlerCtx = Parameters[1]; /** * Build a fake handler ctx. The handler only touches - * `thread.getMessages()`; the other `BotToolContext` fields are unused by + * `thread.getMessages()`; the other `ChannelToolContext` fields are unused by * this tool, so we cast the minimal literal to the handler ctx type. */ function makeCtx(messages: ThreadMessage[]): HandlerCtx { diff --git a/app/tools/__tests__/render-tools.test.ts b/app/tools/__tests__/render-tools.test.ts index 76383fb..d61f4ad 100644 --- a/app/tools/__tests__/render-tools.test.ts +++ b/app/tools/__tests__/render-tools.test.ts @@ -25,7 +25,7 @@ vi.mock("../../render/diagram.js", () => ({ renderDiagram })); const { renderChartTool } = await import("../render-chart.js"); const { renderDiagramTool } = await import("../render-diagram.js"); -/** The ctx a BotTool handler receives. */ +/** The ctx a ChannelTool handler receives. */ type HandlerCtx = Parameters[1]; function makeCtx(opts?: { diff --git a/app/tools/__tests__/showcase-tools.test.tsx b/app/tools/__tests__/showcase-tools.test.tsx index 9a70b23..86923e8 100644 --- a/app/tools/__tests__/showcase-tools.test.tsx +++ b/app/tools/__tests__/showcase-tools.test.tsx @@ -9,7 +9,7 @@ import { describe, it, expect, vi } from "vitest"; import { renderToIR } from "@copilotkit/channels-ui"; import type { - BotNode, + ChannelNode, InteractionContext, ClickHandler, } from "@copilotkit/channels-ui"; @@ -43,29 +43,29 @@ function fakeThread() { /** Depth-first: find the first IR node whose `type` matches and that has the named prop. */ function findWithProp( - nodes: BotNode[], + nodes: ChannelNode[], type: string, prop: string, -): BotNode | undefined { +): ChannelNode | undefined { return findAllWithProp(nodes, type, prop)[0]; } /** Depth-first: find every IR node whose `type` matches and that has the named prop. */ function findAllWithProp( - nodes: BotNode[], + nodes: ChannelNode[], type: string, prop: string, -): BotNode[] { - const matches: BotNode[] = []; +): ChannelNode[] { + const matches: ChannelNode[] = []; for (const node of nodes) { if (node.type === type && node.props && prop in node.props) { matches.push(node); } const children = node.props?.children; const childArr = Array.isArray(children) - ? (children as BotNode[]) + ? (children as ChannelNode[]) : children && typeof children === "object" - ? [children as BotNode] + ? [children as ChannelNode] : []; matches.push(...findAllWithProp(childArr, type, prop)); } diff --git a/app/tools/index.ts b/app/tools/index.ts index 4e4371f..e7fd4ec 100644 --- a/app/tools/index.ts +++ b/app/tools/index.ts @@ -5,7 +5,7 @@ * `defaultSlackTools` (spread in `app/index.ts`). * * Add new tools here and re-export them through `appTools`. Wire the - * array into `createBot({tools: [...defaultSlackTools, ...appTools]})` + * array into `createChannel({tools: [...defaultSlackTools, ...appTools]})` * in `app/index.ts`. */ import { readThreadTool } from "./read-thread.js"; @@ -19,17 +19,17 @@ import { showLinksTool, } from "./showcase-tools.js"; import { confirmWriteTool } from "../human-in-the-loop/index.js"; -import type { BotTool } from "@copilotkit/channels"; +import type { ChannelTool } from "@copilotkit/channels"; /** - * Every tool is a plain `BotTool`: its handler receives the generic - * `BotToolContext` (`{ thread, message?, user?, signal?, platform }`) the + * Every tool is a plain `ChannelTool`: its handler receives the generic + * `ChannelToolContext` (`{ thread, message?, user?, signal?, platform }`) the * adapter supplies at call time. Platform power (post/stream/postFile, * `thread.getMessages()`, `thread.lookupUser()`, …) is reached via the * `thread` methods, so there's no per-adapter context and no cast needed — - * the array assigns straight into `createBot({ tools })`. + * the array assigns straight into `createChannel({ tools })`. */ -export const appTools: BotTool[] = [ +export const appTools: ChannelTool[] = [ readThreadTool, renderChartTool, renderDiagramTool, diff --git a/app/tools/read-thread.ts b/app/tools/read-thread.ts index 7249acf..c397053 100644 --- a/app/tools/read-thread.ts +++ b/app/tools/read-thread.ts @@ -1,19 +1,19 @@ /** - * `read_thread` — an app-side `BotTool` that hands the agent the full + * `read_thread` — an app-side `ChannelTool` that hands the agent the full * conversation thread it's replying in. This is what makes "write this * incident thread up as a postmortem" possible: the agent calls * `read_thread`, gets the actual messages, and summarizes those instead * of inventing content. * - * It's a worked example of a `BotTool` that reads conversation history + * It's a worked example of a `ChannelTool` that reads conversation history * via the platform-agnostic `ctx.thread.getMessages()` capability — * the adapter targets the current thread, so no channel/ts plumbing is * needed. */ import { z } from "zod"; -import { defineBotTool } from "@copilotkit/channels"; +import { defineChannelTool } from "@copilotkit/channels"; -export const readThreadTool = defineBotTool({ +export const readThreadTool = defineChannelTool({ name: "read_thread", description: "Fetch the messages in the current conversation thread so you can " + diff --git a/app/tools/render-chart.tsx b/app/tools/render-chart.tsx index 61d3255..2d1e2d3 100644 --- a/app/tools/render-chart.tsx +++ b/app/tools/render-chart.tsx @@ -8,7 +8,7 @@ */ import { z } from "zod"; import { Context, Message } from "@copilotkit/channels-ui"; -import { defineBotTool } from "@copilotkit/channels"; +import { defineChannelTool } from "@copilotkit/channels"; import { renderChart } from "../render/chart.js"; const schema = z.object({ @@ -63,7 +63,7 @@ function slug(s: string): string { ); } -export const renderChartTool = defineBotTool({ +export const renderChartTool = defineChannelTool({ name: "render_chart", description: "Render a chart as an image and post it to the conversation thread. Pass " + diff --git a/app/tools/render-diagram.tsx b/app/tools/render-diagram.tsx index f302c87..f5e741b 100644 --- a/app/tools/render-diagram.tsx +++ b/app/tools/render-diagram.tsx @@ -8,7 +8,7 @@ */ import { z } from "zod"; import { Context, Message } from "@copilotkit/channels-ui"; -import { defineBotTool } from "@copilotkit/channels"; +import { defineChannelTool } from "@copilotkit/channels"; import { renderDiagram } from "../render/diagram.js"; const schema = z.object({ @@ -33,7 +33,7 @@ function slug(s: string): string { ); } -export const renderDiagramTool = defineBotTool({ +export const renderDiagramTool = defineChannelTool({ name: "render_diagram", description: "Render a Mermaid diagram as an image and post it to the conversation " + diff --git a/app/tools/render-table.tsx b/app/tools/render-table.tsx index ecba6c4..801e4b9 100644 --- a/app/tools/render-table.tsx +++ b/app/tools/render-table.tsx @@ -20,7 +20,7 @@ import { Cell, Context, } from "@copilotkit/channels-ui"; -import { defineBotTool } from "@copilotkit/channels"; +import { defineChannelTool } from "@copilotkit/channels"; const schema = z.object({ title: z @@ -120,7 +120,7 @@ export function toMonospaceTable(cols: Column[], dataRows: string[][]): string { return "```\n" + [fmt(header), ...body.map(fmt)].join("\n") + "\n```"; } -export const renderTableTool = defineBotTool({ +export const renderTableTool = defineChannelTool({ name: "render_table", description: "Render tabular data as a table posted to the conversation thread. Pass " + diff --git a/app/tools/render-tools.tsx b/app/tools/render-tools.tsx index c821a35..7b6ff9a 100644 --- a/app/tools/render-tools.tsx +++ b/app/tools/render-tools.tsx @@ -1,11 +1,11 @@ /** * Render-tools — the agent-facing wrappers that turn the JSX render - * components into `BotTool`s. The agent calls `issue_card` / `issue_list` / + * components into `ChannelTool`s. The agent calls `issue_card` / `issue_list` / * `page_list`; each handler renders the finished `@copilotkit/channels-ui` * component (`` etc.) and posts it to the thread via * `thread.post`. */ -import { defineBotTool } from "@copilotkit/channels"; +import { defineChannelTool } from "@copilotkit/channels"; import { IssueCard, IssueList, @@ -15,7 +15,7 @@ import { pageListSchema, } from "../components/index.js"; -export const issueCardTool = defineBotTool({ +export const issueCardTool = defineChannelTool({ name: "issue_card", description: "Render ONE Linear issue as a rich card with a status header, " + @@ -29,7 +29,7 @@ export const issueCardTool = defineBotTool({ }, }); -export const issueListTool = defineBotTool({ +export const issueListTool = defineChannelTool({ name: "issue_list", description: "Render a list of Linear issues as a card — a header plus one row per " + @@ -44,7 +44,7 @@ export const issueListTool = defineBotTool({ }, }); -export const pageListTool = defineBotTool({ +export const pageListTool = defineChannelTool({ name: "page_list", description: "Render a list of Notion pages as a card — a header plus one row per " + diff --git a/app/tools/showcase-tools.tsx b/app/tools/showcase-tools.tsx index 4e0b537..327c30d 100644 --- a/app/tools/showcase-tools.tsx +++ b/app/tools/showcase-tools.tsx @@ -1,5 +1,5 @@ /** - * Showcase render-tools — three small JSX `BotTool`s that demonstrate the + * Showcase render-tools — three small JSX `ChannelTool`s that demonstrate the * `@copilotkit/channels-ui` vocabulary end-to-end: * * - `show_incident` — an interactive card whose `Acknowledge`/`Escalate` @@ -22,7 +22,7 @@ import { Button, } from "@copilotkit/channels-ui"; import type { InteractionContext } from "@copilotkit/channels-ui"; -import { defineBotTool } from "@copilotkit/channels"; +import { defineChannelTool } from "@copilotkit/channels"; // ── show_incident ────────────────────────────────────────────────────────── @@ -89,7 +89,7 @@ export function IncidentCard({ id, title, severity, summary }: IncidentProps) { ); } -export const showIncidentTool = defineBotTool({ +export const showIncidentTool = defineChannelTool({ name: "show_incident", description: "Render an interactive incident card with Acknowledge/Escalate buttons. " + @@ -133,7 +133,7 @@ export function StatusCard({ heading, fields }: StatusProps) { ); } -export const showStatusTool = defineBotTool({ +export const showStatusTool = defineChannelTool({ name: "show_status", description: "Render a status card: a heading plus a grid of label/value fields " + @@ -177,7 +177,7 @@ export function LinksCard({ heading, links }: LinksProps) { ); } -export const showLinksTool = defineBotTool({ +export const showLinksTool = defineChannelTool({ name: "show_links", description: "Render a card of links: a heading plus a dot-separated row of clickable " + From 54ad0779a07ff22046c1d74a6b2a8734a6b99cb7 Mon Sep 17 00:00:00 2001 From: GeneralJerel <85066839+GeneralJerel@users.noreply.github.com> Date: Fri, 24 Jul 2026 09:44:03 -0700 Subject: [PATCH 05/50] docs+config: v2 Intelligence env/IaC/docs (API_URL/WS_URL/API_KEY, kite-opentag) Co-Authored-By: Claude Opus 4.8 --- .env.example | 27 ++++++++++++++------------- .railway/railway.ts | 16 +++++++++------- README.md | 15 ++++++--------- setup.md | 27 ++++++++++++++++----------- 4 files changed, 45 insertions(+), 40 deletions(-) diff --git a/.env.example b/.env.example index 02996b2..1ba1202 100644 --- a/.env.example +++ b/.env.example @@ -35,22 +35,23 @@ AGENT_URL=http://localhost:8200/api/copilotkit/agent/triage/run # 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. +# 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 # Model for the BuiltInAgent. The runtime is OpenAI-only (it uses OpenAI's # Responses API via TanStack AI's `openaiText` adapter, needed for the diff --git a/.railway/railway.ts b/.railway/railway.ts index de623ed..5f9ad7c 100644 --- a/.railway/railway.ts +++ b/.railway/railway.ts @@ -89,7 +89,11 @@ export default defineRailway(() => { }, }); - // KiteBot channel host — runs the bot over the Intelligence Realtime Gateway. + // KiteBot channel host — runs the bot via CopilotKit Intelligence v2: + // createChannel() feeds a CopilotRuntime({ channels }) into + // createCopilotNodeListener(). There are no org/project/channel IDs to wire + // up: the runtime derives that identity from the API credentials plus the + // channel name below. const channel = service("channel", { source: github(REPO), start: "pnpm channel", @@ -104,16 +108,14 @@ export default defineRailway(() => { // pins PORT=8123, so ${{agent.PORT}} resolves to the port uvicorn binds. AGENT_URL: "http://${{agent.RAILWAY_PRIVATE_DOMAIN}}:${{agent.PORT}}/", // INTELLIGENCE_CHANNEL_NAME is intentionally NOT set here: app/managed.ts - // already defaults it to "kitebot", and leaving it unmanaged means a + // already defaults it to "kite-opentag", and leaving it unmanaged means a // deployer can set their own channel name in the Railway UI without a // later `config apply` clobbering it. Set it in the channel service's // Variables if your Intelligence channel is named something else. - // secrets (set in Railway UI): - INTELLIGENCE_GATEWAY_WS_URL: preserve(), + INTELLIGENCE_API_URL: "https://dev.intelligence.copilotkit.ai", + INTELLIGENCE_GATEWAY_WS_URL: "wss://dev.intelligence.copilotkit.ai/runner", + // secret (set in Railway UI): INTELLIGENCE_API_KEY: preserve(), - INTELLIGENCE_ORG_ID: preserve(), - INTELLIGENCE_PROJECT_ID: preserve(), - INTELLIGENCE_CHANNEL_ID: preserve(), }, }); diff --git a/README.md b/README.md index a817c8d..1559f5a 100644 --- a/README.md +++ b/README.md @@ -81,11 +81,9 @@ SLACK_BOT_TOKEN=xoxb-... SLACK_APP_TOKEN=xapp-... # Intelligence Gateway mode — full list in .env.example: +INTELLIGENCE_API_URL=https://... INTELLIGENCE_GATEWAY_WS_URL=wss://... INTELLIGENCE_API_KEY=cpk-... -INTELLIGENCE_ORG_ID=org_... -INTELLIGENCE_PROJECT_ID=... -INTELLIGENCE_CHANNEL_ID=channel_... ``` **3. Run it:** @@ -117,7 +115,7 @@ OpenTag is deliberately small and hackable: - **Copy `app/` to start your own bot.** It's the platform-agnostic bot (tools, components, the human-in-the-loop gate). `runtime.ts` is the agent backend: one CopilotKit `BuiltInAgent` (an LLM + optional MCP tools — no Python, no LangGraph), served over AG-UI. -- **One platform, or all of them.** `createBot` takes an array of adapters; set the secrets for +- **One platform, or all of them.** `createChannel` takes an array of adapters; set the secrets for whichever platform(s) you want and the bot starts an adapter for each. The full architecture, the file-by-file map, and every integration live in @@ -193,15 +191,14 @@ railway config apply # from the repo root: provisions agent + notion-mcp service from `resources` in [`.railway/railway.ts`](./.railway/railway.ts) and delete the agent's `NOTION_MCP_URL` / `NOTION_MCP_AUTH_TOKEN` lines; the agent still runs (chat, UI, and — with `TAVILY_API_KEY` — web research). -- **`channel`** — `INTELLIGENCE_GATEWAY_WS_URL`, `INTELLIGENCE_API_KEY`, `INTELLIGENCE_ORG_ID`, - `INTELLIGENCE_PROJECT_ID`, `INTELLIGENCE_CHANNEL_ID` (from your CopilotKit Intelligence - project + channel). +- **`channel`** — `INTELLIGENCE_API_URL`, `INTELLIGENCE_GATEWAY_WS_URL`, `INTELLIGENCE_API_KEY` + (from your CopilotKit Intelligence project). The inter-service URLs/ports are wired for you in [`.railway/railway.ts`](./.railway/railway.ts). Two values are left unmanaged by the IaC so a UI override survives `railway config apply`: the agent's `OPENAI_MODEL` (defaults to `gpt-5.5`) and the channel's `INTELLIGENCE_CHANNEL_NAME` -(defaults to `kitebot`). Set either in that service's *Variables* to change it — e.g. set -`INTELLIGENCE_CHANNEL_NAME` if your Intelligence channel isn't named `kitebot`. +(defaults to `kite-opentag`). Set either in that service's *Variables* to change it — e.g. set +`INTELLIGENCE_CHANNEL_NAME` if your Intelligence channel isn't named `kite-opentag`. Applying the config creates the services and their wiring; **KiteBot goes live only once the secrets are set and the `channel` service connects** — that's when your Intelligence dashboard diff --git a/setup.md b/setup.md index 7ec428a..ed9db25 100644 --- a/setup.md +++ b/setup.md @@ -39,7 +39,7 @@ mode](#intelligence-channel-mode)). Both modes talk to the same agent backend | Concept | Where | | -------------------------------------------------------------------- | ------------------------------------------------------------------ | -| `createBot({ adapters, agent, tools, context, commands })` | [`app/index.ts`](./app/index.ts) | +| `createChannel({ adapters, agent, tools, context, commands })` | [`app/index.ts`](./app/index.ts) | | Multi-adapter wiring (Slack/Discord/Telegram/WhatsApp, secret-gated) | [`app/index.ts`](./app/index.ts) | | `read_thread` — grounds the agent in the real conversation | [`app/tools/read-thread.ts`](./app/tools/read-thread.ts) | | Render-tools + JSX components (issue card/list, Notion pages) | [`app/tools/render-tools.tsx`](./app/tools/render-tools.tsx), [`app/components/`](./app/components/) | @@ -54,7 +54,7 @@ mode](#intelligence-channel-mode)). Both modes talk to the same agent backend - **`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 + `createChannel` API; the Telegram harness is a working manual-trigger smoke test — see [`e2e/TELEGRAM-README.md`](./e2e/TELEGRAM-README.md)). It's built on: @@ -142,9 +142,14 @@ for the same bot; run whichever one `AGENT_URL` points at. `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. +frames back. It's wired up on the `@copilotkit/runtime/v2` managed-channels runtime: a +`createChannel({ name })` (`@copilotkit/channels`) is handed, alongside a `CopilotKitIntelligence` +built from the API vars below, to `new CopilotRuntime({ intelligence, channels })`, then mounted +with `createCopilotNodeListener` (`@copilotkit/runtime/v2/node`) — mounting is what activates the +channel, with no org/project/channel IDs to wire up; the runtime derives that identity from the +Intelligence API credentials plus the channel's registered name. 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. ```bash npm run runtime # terminal 1 — the agent backend on :8200 (same as self-hosted) @@ -155,10 +160,10 @@ Configure it with: | Variable | What it's for | | --- | --- | +| `INTELLIGENCE_API_URL` | Base URL of your CopilotKit Intelligence deployment's app API. | | `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`. | +| `INTELLIGENCE_CHANNEL_NAME` | The registered channel name (lowercase kebab). Defaults to `kite-opentag`. | 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` @@ -201,7 +206,7 @@ cp .env.example .env | `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](#intelligence-channel-mode) instead of holding platform tokens directly. | +| `INTELLIGENCE_API_URL` / `INTELLIGENCE_GATEWAY_WS_URL` / `INTELLIGENCE_API_KEY` / `INTELLIGENCE_CHANNEL_NAME` | Run in [Intelligence channel mode](#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 @@ -249,7 +254,7 @@ 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 +The same `app/` code runs on every platform — `createChannel` 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. @@ -265,7 +270,7 @@ Per-platform details are documented inline in [`.env.example`](./.env.example). ## Slash commands -Four app-owned commands, registered via `createBot({ commands })` +Four app-owned commands, registered via `createChannel({ commands })` ([`app/commands/index.ts`](./app/commands/index.ts)): - **`/agent `** — a mention-free entry point; runs the agent with the command text. @@ -296,6 +301,6 @@ npm test # unit: read_thread, render tools, components, confirm_wr npm run check-types # tsc --noEmit ``` -The live-Slack e2e harness (`npm run e2e`) is being migrated to the new `createBot` API and +The live-Slack e2e harness (`npm run e2e`) is being migrated to the new `createChannel` 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`](./e2e/TELEGRAM-README.md). From 3a1a0621726735f00e3119ff01bfa2825580c37f Mon Sep 17 00:00:00 2001 From: GeneralJerel <85066839+GeneralJerel@users.noreply.github.com> Date: Fri, 24 Jul 2026 09:55:20 -0700 Subject: [PATCH 06/50] feat(channel): migrate entry points to @copilotkit/channels 0.2 + v2 managed-channels runtime - app/index.ts: self-hosted createBot->createChannel (behavior unchanged) - app/managed.ts: rewrite to createChannel + CopilotRuntime({channels}) + createCopilotNodeListener; drop startChannelsOverRealtimeGateway + org/project/channel-id env Co-Authored-By: Claude Opus 4.8 --- app/index.ts | 14 ++-- app/managed.test.ts | 113 +++++------------------------ app/managed.ts | 168 +++++++++++++++++++++++--------------------- 3 files changed, 111 insertions(+), 184 deletions(-) diff --git a/app/index.ts b/app/index.ts index c2758a0..88004e7 100644 --- a/app/index.ts +++ b/app/index.ts @@ -5,7 +5,7 @@ * that runs on the chat-platform side of the bot for this deployment. * * MULTI-PLATFORM: this single app drives Slack, Discord, Telegram, and/or - * WhatsApp from one process. `@copilotkit/channels`'s `createBot` accepts an array + * WhatsApp from one process. `@copilotkit/channels`'s `createChannel` accepts an array * of adapters and starts them all, so we include each platform's adapter only * when its secrets are present. Drop in `SLACK_*` to run Slack, `DISCORD_*` for * Discord, `TELEGRAM_BOT_TOKEN` for Telegram, `WHATSAPP_*` for WhatsApp — or any @@ -17,8 +17,8 @@ * here in the file you copy from to start a new bot. */ import "dotenv/config"; -import { createBot } from "@copilotkit/channels"; -import type { PlatformAdapter, BotTool, ContextEntry } from "@copilotkit/channels"; +import { createChannel } from "@copilotkit/channels"; +import type { PlatformAdapter, ChannelTool, ContextEntry } from "@copilotkit/channels"; import { slack, defaultSlackTools, @@ -72,7 +72,7 @@ async function main() { // formatting guidance), added only when that platform is active so the model // isn't handed a different platform's conventions. const adapters: PlatformAdapter[] = []; - const tools: BotTool[] = [...appTools]; + const tools: ChannelTool[] = [...appTools]; const context: ContextEntry[] = [...appContext]; if (have("SLACK_BOT_TOKEN", "SLACK_APP_TOKEN")) { @@ -179,7 +179,7 @@ async function main() { process.exit(1); } - const bot = createBot({ + const bot = createChannel({ adapters, // One AG-UI agent per conversation. The backend is a CopilotKit // `BuiltInAgent` (CopilotSseRuntime), which does NOT require a UUID-format @@ -197,7 +197,7 @@ async function main() { }, // `appTools` adds this bot's tools (read_thread, render_*, issue/page // cards); the per-platform `default*Tools` add `lookup_*_user`. All are - // plain `BotTool`s — the active adapter supplies `thread`/`message`/`user` + // plain `ChannelTool`s — the active adapter supplies `thread`/`message`/`user` // per call. `default*Context` ships tagging/formatting/thread-model // guidance; `appContext` adds identity + triage policy. tools, @@ -211,7 +211,7 @@ async function main() { // The turn handler. Each adapter pre-filters ingress to the turns this bot // should answer — DMs, explicit mentions, and every WhatsApp message. - // createBot is mention-preferred: a single handler covers them across every + // createChannel is mention-preferred: a single handler covers them across every // active platform. `senderContext` names the // requesting user per `thread.platform`, so the label is correct on whichever // surface the turn arrived from. Additional feature demos below add their own diff --git a/app/managed.test.ts b/app/managed.test.ts index 6d92d2e..aebbf91 100644 --- a/app/managed.test.ts +++ b/app/managed.test.ts @@ -1,40 +1,37 @@ import { describe, it, expect } from "vitest"; import type { AgentContentPart } from "@copilotkit/channels-ui"; import { - createKiteBot, + createKiteChannel, promptFromMessage, buildAgentHeaders, - parseProjectId, } from "./managed.js"; -describe("createKiteBot", () => { - it("declares the kitebot channel name", () => { - const bot = createKiteBot({ agentUrl: "http://localhost:8200" }); - expect(bot.name).toBe("kitebot"); - }); - - it("registers the app's slash commands on the bot", () => { - const bot = createKiteBot({ agentUrl: "http://localhost:8200" }); - // createBot normalizes slash-command names (hyphens -> underscores): - // app/commands declares "file-issue"; commandNames reports "file_issue". - expect(bot.commandNames).toContain("file_issue"); +describe("createKiteChannel", () => { + it("defaults the channel name to kite-opentag", () => { + const ch = createKiteChannel({ agentUrl: "http://localhost:8123/" }); + expect(ch.name).toBe("kite-opentag"); }); it("honors a custom channel name", () => { - const bot = createKiteBot({ - agentUrl: "http://localhost:8200", - channelName: "kite-bot", + const ch = createKiteChannel({ + agentUrl: "http://localhost:8123/", + channelName: "kite-staging", }); - expect(bot.name).toBe("kite-bot"); + expect(ch.name).toBe("kite-staging"); + }); + + it("registers the app's slash commands on the channel", () => { + const ch = createKiteChannel({ agentUrl: "http://localhost:8123/" }); + // createChannel normalizes slash-command names (hyphens -> underscores): + // app/commands declares "file-issue"; commandNames reports "file_issue". + expect(ch.commandNames).toContain("file_issue"); }); }); describe("promptFromMessage", () => { it("returns contentParts when present", () => { const parts: AgentContentPart[] = [{ type: "text", text: "hi" }]; - expect( - promptFromMessage({ contentParts: parts, text: "hi" }), - ).toBe(parts); + expect(promptFromMessage({ contentParts: parts, text: "hi" })).toBe(parts); }); it("falls back to text when contentParts is empty", () => { @@ -59,79 +56,3 @@ describe("buildAgentHeaders", () => { }); }); }); - -describe("parseProjectId", () => { - it("parses a valid positive integer string", () => { - expect(parseProjectId("42")).toBe(42); - }); - - it("throws on \"0\"", () => { - expect(() => parseProjectId("0")).toThrow( - /Invalid INTELLIGENCE_PROJECT_ID/, - ); - }); - - it("throws on a non-numeric string", () => { - expect(() => parseProjectId("abc")).toThrow( - /Invalid INTELLIGENCE_PROJECT_ID/, - ); - }); - - it("throws when undefined", () => { - expect(() => parseProjectId(undefined)).toThrow( - /Invalid INTELLIGENCE_PROJECT_ID/, - ); - }); - - it("throws on a negative integer string", () => { - expect(() => parseProjectId("-5")).toThrow( - /Invalid INTELLIGENCE_PROJECT_ID/, - ); - }); - - it("throws on a non-integer float string", () => { - expect(() => parseProjectId("1.5")).toThrow( - /Invalid INTELLIGENCE_PROJECT_ID/, - ); - }); - - it("throws on an empty string", () => { - expect(() => parseProjectId("")).toThrow( - /Invalid INTELLIGENCE_PROJECT_ID/, - ); - }); - - it("throws on a whitespace-only string", () => { - expect(() => parseProjectId(" ")).toThrow( - /Invalid INTELLIGENCE_PROJECT_ID/, - ); - }); - - // Number("1e3") === 1000, Number("0x10") === 16, and Number("0b11") === 3 - // all pass Number.isInteger(n) && n > 0 — so a naive Number(raw) parse - // would silently accept a typo'd env var as the wrong project id instead - // of throwing. Only plain decimal-digit strings are valid. - it("throws on exponential notation", () => { - expect(() => parseProjectId("1e3")).toThrow( - /Invalid INTELLIGENCE_PROJECT_ID/, - ); - }); - - it("throws on hexadecimal notation", () => { - expect(() => parseProjectId("0x10")).toThrow( - /Invalid INTELLIGENCE_PROJECT_ID/, - ); - }); - - it("throws on binary notation", () => { - expect(() => parseProjectId("0b11")).toThrow( - /Invalid INTELLIGENCE_PROJECT_ID/, - ); - }); - - it("throws on a decimal float string", () => { - expect(() => parseProjectId("12.5")).toThrow( - /Invalid INTELLIGENCE_PROJECT_ID/, - ); - }); -}); diff --git a/app/managed.ts b/app/managed.ts index e0af66f..15d67c2 100644 --- a/app/managed.ts +++ b/app/managed.ts @@ -3,28 +3,33 @@ * * Unlike app/index.ts (self-hosted: holds Slack tokens, talks to Slack * directly), this process holds NO platform credentials. It runs the SAME bot - * over the CopilotKit Intelligence Realtime Gateway: it declares one channel - * ("kitebot") to the gateway and streams render frames back. Intelligence owns - * the Slack edge (signed ingress + Connector Outbox egress). + * over CopilotKit Intelligence: it declares one channel ("kite-opentag") to the + * Intelligence runtime and mounts an HTTP listener. Mounting the listener + * activates the channel — the runtime derives the org/project/channel binding + * from the Intelligence credentials + the channel name and streams render + * frames over the managed gateway. Intelligence owns the Slack edge (signed + * ingress + Connector Outbox egress). * * The bot's brain is an external AG-UI agent reached over HTTP at AGENT_URL — * for now the runtime.ts triage backend; in Phase 2 a LangGraph deep agent. - * intelligenceAdapter is exclusive, so the bot is created WITHOUT a native - * adapter and startChannelsOverRealtimeGateway attaches the channel transport. + * The channel is created WITHOUT a native adapter (no `adapters`), so it is a + * managed, no-adapter channel: the runtime attaches the managed transport at + * activation. * * Run: `pnpm channel` with INTELLIGENCE_* + AGENT_URL set (see .env.example). */ import "dotenv/config"; -import { randomUUID } from "node:crypto"; -import { createBot } from "@copilotkit/channels"; -import type { Bot } from "@copilotkit/channels"; +import { createServer } from "node:http"; +import { createChannel } from "@copilotkit/channels"; +import type { Channel } from "@copilotkit/channels"; import type { AgentContentPart } from "@copilotkit/channels-ui"; import { SanitizingHttpAgent, defaultSlackTools, defaultSlackContext, } from "@copilotkit/channels-slack"; -import { startChannelsOverRealtimeGateway } from "@copilotkit/channels-intelligence"; +import { CopilotRuntime, CopilotKitIntelligence } from "@copilotkit/runtime/v2"; +import { createCopilotNodeListener } from "@copilotkit/runtime/v2/node"; import { appTools } from "./tools/index.js"; import { appContext } from "./context/app-context.js"; import { appCommands } from "./commands/index.js"; @@ -41,12 +46,12 @@ const required = (name: string): string => { return v; }; -export interface CreateKiteBotOptions { - /** AG-UI agent endpoint the bot's HttpAgent posts to. */ +export interface CreateKiteChannelOptions { + /** AG-UI agent endpoint the channel's HttpAgent posts to. */ agentUrl: string; /** Optional Authorization header value forwarded to the agent. */ agentAuthHeader?: string; - /** Intelligence channel name (lowercase kebab). Defaults to "kitebot". */ + /** Intelligence channel name (lowercase kebab). Defaults to "kite-opentag". */ channelName?: string; } @@ -70,37 +75,16 @@ export function buildAgentHeaders( } /** - * Parse and validate INTELLIGENCE_PROJECT_ID, throwing on any invalid value. - * - * Only plain decimal-digit strings (e.g. "42") are accepted. `Number(raw)` - * alone would also accept hex ("0x10"), exponential ("1e3"), and binary - * ("0b11") notation — all of which pass `Number.isInteger(n) && n > 0` — - * so a typo'd env var could silently resolve to the wrong project. The - * `/^\d+$/` guard rejects anything that isn't a bare non-negative integer - * before handing it to `Number(...)`. + * Build the KiteBot channel: same tools/context/commands/handlers as the native + * bot, minus any platform adapter (the managed transport is attached at + * activation when the runtime's node listener is mounted). Pure — no network, + * no env reads — so it is unit-testable. */ -export function parseProjectId(raw: string | undefined): number { - if (raw === undefined || !/^\d+$/.test(raw.trim())) { - throw new Error(`Invalid INTELLIGENCE_PROJECT_ID: "${raw}"`); - } - const n = Number(raw.trim()); - if (!Number.isInteger(n) || n <= 0) { - throw new Error(`Invalid INTELLIGENCE_PROJECT_ID: "${raw}"`); - } - return n; -} - -/** - * Build the KiteBot bot: same tools/context/commands/handlers as the native - * bot, minus any platform adapter (the intelligenceAdapter is attached at - * activation by startChannelsOverRealtimeGateway). Pure — no network, no env - * reads — so it is unit-testable. - */ -export function createKiteBot(opts: CreateKiteBotOptions): Bot { - const channelName = opts.channelName ?? "kitebot"; +export function createKiteChannel(opts: CreateKiteChannelOptions): Channel { + const channelName = opts.channelName ?? "kite-opentag"; const agentHeaders = buildAgentHeaders(opts.agentAuthHeader); - const bot = createBot({ + const channel = createChannel({ name: channelName, agent: (threadId: string) => { const a = new SanitizingHttpAgent({ @@ -118,7 +102,7 @@ export function createKiteBot(opts: CreateKiteBotOptions): Bot { // Managed history does NOT include the in-flight turn, so pass the current // message explicitly as `prompt` (prefer multimodal parts). Mirrors the // native bot's onMention otherwise. - bot.onMention(async ({ thread, message }) => { + channel.onMention(async ({ thread, message }) => { try { await thread.runAgent({ prompt: promptFromMessage(message), @@ -134,9 +118,9 @@ export function createKiteBot(opts: CreateKiteBotOptions): Bot { } }); - bot.onModalSubmit(FILE_ISSUE_CALLBACK, fileIssueSubmit); + channel.onModalSubmit(FILE_ISSUE_CALLBACK, fileIssueSubmit); - bot.onThreadStarted(async ({ thread, user }) => { + channel.onThreadStarted(async ({ thread, user }) => { if (!user?.name) return; try { await thread.setSuggestedPrompts([ @@ -154,61 +138,80 @@ export function createKiteBot(opts: CreateKiteBotOptions): Bot { } }); - return bot; + return channel; } async function main() { - const channelName = process.env.INTELLIGENCE_CHANNEL_NAME ?? "kitebot"; + const agentUrl = required("AGENT_URL"); + const agentAuthHeader = process.env.AGENT_AUTH_HEADER; - const bot = createKiteBot({ - agentUrl: required("AGENT_URL"), - agentAuthHeader: process.env.AGENT_AUTH_HEADER, - channelName, + const channel = createKiteChannel({ + agentUrl, + agentAuthHeader, + channelName: process.env.INTELLIGENCE_CHANNEL_NAME, }); - let projectId: number; - try { - projectId = parseProjectId(process.env.INTELLIGENCE_PROJECT_ID); - } catch (e) { - const msg = e instanceof Error ? e.message : String(e); - console.error(msg); - process.exit(1); - } - - const handle = await startChannelsOverRealtimeGateway([bot], { + const intelligence = new CopilotKitIntelligence({ + apiUrl: required("INTELLIGENCE_API_URL"), wsUrl: required("INTELLIGENCE_GATEWAY_WS_URL"), apiKey: required("INTELLIGENCE_API_KEY"), - scope: { - organizationId: required("INTELLIGENCE_ORG_ID"), - projectId, - channelId: required("INTELLIGENCE_CHANNEL_ID"), - channelName, + }); + + const runtime = new CopilotRuntime({ + // `agents` is a required NON-EMPTY record (NonEmptyRecord) on the v2 + // runtime. This host's real work flows through `channel` (whose own + // `agent` factory posts to AGENT_URL per turn), so there is no separate + // top-level agent to register — but the type demands at least one entry. + // Register the SAME AG-UI backend under a stable name so the runtime's + // agent registry resolves to the identical brain the channel drives, + // rather than a semantically-empty placeholder. + agents: { + kite: new SanitizingHttpAgent({ + url: agentUrl, + headers: buildAgentHeaders(agentAuthHeader), + }), }, - runtimeInstanceId: - process.env.INTELLIGENCE_RUNTIME_INSTANCE_ID ?? - `rti_${randomUUID().replace(/-/g, "")}`, - adapter: "slack", - // meta can contain message content, so it is gated behind CHANNEL_DEBUG - // and omitted from stdout/logs by default. - log: (msg, meta) => - console.log( - `[channel] ${msg}`, - process.env.CHANNEL_DEBUG ? (meta ?? "") : "", - ), + intelligence, + // Per-turn platform user identity for channel messages is resolved by the + // gateway/Slack adapter and passed to the agent via `senderContext`; this + // callback only identifies the host for the runtime's own HTTP endpoints, + // so a stable stub is sufficient here. + identifyUser: () => ({ id: "opentag-kite", name: "OpenTag KiteBot" }), + channels: [channel], + }); + + const rawPort = process.env["PORT"]; + const port = rawPort && rawPort.trim() !== "" ? Number(rawPort) : 8300; + if (!Number.isInteger(port) || port < 1 || port > 65535) { + console.error( + `Invalid PORT: "${rawPort}" is not a valid port number (must be an integer between 1 and 65535)`, + ); + process.exit(1); + } + + const listener = createCopilotNodeListener({ + runtime, + basePath: "/api/copilotkit", + }); + const server = createServer(listener).listen(port, () => { + console.log( + `[channel] KiteBot channel "${channel.name}" mounted on :${port} → Intelligence gateway`, + ); }); - console.log( - `[channel] KiteBot channel "${channelName}" started over Realtime Gateway on project ${projectId}`, - ); const shutdown = async (signal: string) => { console.log(`\n[channel] received ${signal}, stopping…`); let exitCode = 0; + // Tear down managed channel activation first (present only when the runtime + // declared channels and activation wasn't opted out of). try { - await handle.stop(); + await listener.channels?.stop(); } catch (err) { console.error("[channel] error stopping channel runtime", err); exitCode = 1; } + server.close(); + // Tear down the shared headless browser used for chart/diagram rendering. await closeBrowser().catch((err: unknown) => console.error( "[channel] browser cleanup failed (continuing shutdown)", @@ -227,6 +230,9 @@ async function main() { process.on("SIGTERM", () => runShutdown("SIGTERM")); } +// Fail loud, not silent: surface any stray async error (e.g. a throw deep in an +// interaction/callback path) instead of letting it kill the process with no +// log. Log and keep running — one bad turn shouldn't take the host down. process.on("unhandledRejection", (reason) => { console.error("[channel] unhandledRejection:", reason); }); @@ -234,8 +240,8 @@ process.on("uncaughtException", (err) => { console.error("[channel] uncaughtException:", err); }); -// Only start the gateway connection when executed directly (`pnpm channel`), -// not when the test imports `createKiteBot`. +// Only build the runtime + mount the listener when executed directly +// (`pnpm channel`), not when the test imports `createKiteChannel`. if (process.argv[1] && process.argv[1].endsWith("managed.ts")) { main().catch((err: unknown) => { console.error("[channel] fatal: failed to start channel runtime", err); From 67df75aaec24ebbfbea9e45084fd44a9320176d7 Mon Sep 17 00:00:00 2001 From: GeneralJerel <85066839+GeneralJerel@users.noreply.github.com> Date: Fri, 24 Jul 2026 10:26:39 -0700 Subject: [PATCH 07/50] chore: drop redundant direct @copilotkit/channels-intelligence dep + doc/comment cleanup Now transitive via @copilotkit/channels + @copilotkit/runtime; v2 managed.ts no longer imports it directly. Co-Authored-By: Claude Opus 4.8 --- README.md | 1 - app/sender-context.ts | 2 +- package.json | 1 - pnpm-lock.yaml | 3 --- setup.md | 1 - 5 files changed, 1 insertion(+), 7 deletions(-) diff --git a/README.md b/README.md index 1559f5a..91a612a 100644 --- a/README.md +++ b/README.md @@ -58,7 +58,6 @@ OpenTag is a thin layer on top of a handful of CopilotKit packages. The `pnpm in | Package | When you need it | | --- | --- | | [`@copilotkit/channels-discord`](https://github.com/CopilotKit/CopilotKit/tree/main/packages/channels-discord) · [`-telegram`](https://github.com/CopilotKit/CopilotKit/tree/main/packages/channels-telegram) · [`-whatsapp`](https://github.com/CopilotKit/CopilotKit/tree/main/packages/channels-whatsapp) | Running on a platform other than Slack — one adapter per platform. | -| [`@copilotkit/channels-intelligence`](https://github.com/CopilotKit/CopilotKit/tree/main/packages/channels-intelligence) | Runs the bot over the CopilotKit Intelligence Realtime Gateway instead of holding platform tokens — see `app/managed.ts`. **Required for the recommended `pnpm channel` (Intelligence Gateway) mode**; omit it only if you run self-hosted mode exclusively. | **1. Create a Slack app.** At [api.slack.com/apps](https://api.slack.com/apps?new_app=1) → *From a manifest* → paste [`slack-app-manifest.yaml`](./slack-app-manifest.yaml). Install it, diff --git a/app/sender-context.ts b/app/sender-context.ts index 87e3d88..8852304 100644 --- a/app/sender-context.ts +++ b/app/sender-context.ts @@ -13,7 +13,7 @@ export function senderContext( user: PlatformUser | undefined, platform: string, ): ContextEntry[] { - // `createBot` substitutes `{ id: "" }` for an unresolved sender (a truthy + // `createChannel` substitutes `{ id: "" }` for an unresolved sender (a truthy // object), so guard on a usable id — not mere object presence — otherwise we // emit a "Requesting user (... id )" entry with nothing to attribute. if (!user?.id) return []; diff --git a/package.json b/package.json index 3841002..c1c5134 100644 --- a/package.json +++ b/package.json @@ -21,7 +21,6 @@ "dependencies": { "@copilotkit/channels": "^0.2.1", "@copilotkit/channels-discord": "^0.2.1", - "@copilotkit/channels-intelligence": "^0.2.1", "@copilotkit/channels-slack": "^0.2.1", "@copilotkit/channels-telegram": "^0.2.1", "@copilotkit/channels-ui": "^0.2.1", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index f6500ce..de4e904 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -17,9 +17,6 @@ importers: '@copilotkit/channels-discord': specifier: ^0.2.1 version: 0.2.1(@ag-ui/core@0.0.57)(vitest@4.1.10(@opentelemetry/api@1.9.1)(@types/node@22.20.1)(vite@8.1.4(@types/node@22.20.1)(esbuild@0.28.1)(tsx@4.23.1))) - '@copilotkit/channels-intelligence': - specifier: ^0.2.1 - version: 0.2.1(@ag-ui/core@0.0.57)(vitest@4.1.10(@opentelemetry/api@1.9.1)(@types/node@22.20.1)(vite@8.1.4(@types/node@22.20.1)(esbuild@0.28.1)(tsx@4.23.1)))(zod@3.25.76) '@copilotkit/channels-slack': specifier: ^0.2.1 version: 0.2.1(@types/express@5.0.6)(vitest@4.1.10(@opentelemetry/api@1.9.1)(@types/node@22.20.1)(vite@8.1.4(@types/node@22.20.1)(esbuild@0.28.1)(tsx@4.23.1))) diff --git a/setup.md b/setup.md index ed9db25..20290e6 100644 --- a/setup.md +++ b/setup.md @@ -62,7 +62,6 @@ It's built on: - **[`@copilotkit/channels`](https://github.com/CopilotKit/CopilotKit/tree/main/packages/channels)** — the platform-agnostic bot engine. - **[`@copilotkit/channels-slack`](https://github.com/CopilotKit/CopilotKit/tree/main/packages/channels-slack)** / **[`-discord`](https://github.com/CopilotKit/CopilotKit/tree/main/packages/channels-discord)** / **[`-telegram`](https://github.com/CopilotKit/CopilotKit/tree/main/packages/channels-telegram)** / **[`-whatsapp`](https://github.com/CopilotKit/CopilotKit/tree/main/packages/channels-whatsapp)** — the platform adapters. - **[`@copilotkit/channels-ui`](https://github.com/CopilotKit/CopilotKit/tree/main/packages/channels-ui)** — a cross-platform JSX vocabulary for rich messages (Block Kit on Slack, Components V2 on Discord, HTML on Telegram). -- **[`@copilotkit/channels-intelligence`](https://github.com/CopilotKit/CopilotKit/tree/main/packages/channels-intelligence)** — runs the same KiteBot over the CopilotKit Intelligence Realtime Gateway (Intelligence Gateway mode, no platform tokens in this process). - **[`@copilotkit/runtime`](https://github.com/CopilotKit/CopilotKit/tree/main/packages/runtime)** — the AG-UI agent backend. ## Running it From 2037c5598be14c53ceceb56fe2d377c7963b38d6 Mon Sep 17 00:00:00 2001 From: GeneralJerel <85066839+GeneralJerel@users.noreply.github.com> Date: Fri, 24 Jul 2026 19:32:32 -0700 Subject: [PATCH 08/50] fix(cr): address round-1 review findings managed.ts: await channels.ready() + exit-1 on activation reject; server 'error' handler; blank INTELLIGENCE_CHANNEL_NAME normalized; extension-agnostic entry guard. IaC/docs: INTELLIGENCE_API_URL/GATEWAY_WS_URL now preserve() (drift + dev-leak); .railway added to tsconfig. commands: shared safely()/safePost() guards for bare postEphemeral/openModal/post. HITL: confirm_write detail capped; file-issue log prefix. Slack char-budget clamps: issue-list count, showcase fields/links, render-table monospace fallback. Co-Authored-By: Claude Opus 4.8 --- .env.example | 3 + .railway/railway.ts | 11 +- app/commands/__tests__/commands.test.ts | 83 +++++++++++++ app/commands/index.ts | 111 +++++++++++++++--- app/components/__tests__/components.test.tsx | 111 +++++++++++++++++- app/components/issue-list.tsx | 100 +++++++++++++--- .../__tests__/confirm-write.test.tsx | 21 ++++ app/human-in-the-loop/confirm-write.tsx | 19 ++- app/managed.test.ts | 18 +++ app/managed.ts | 49 +++++++- app/modals/file-issue.tsx | 2 +- app/tools/__tests__/render-table.test.tsx | 109 ++++++++++++++++- app/tools/__tests__/showcase-tools.test.tsx | 71 +++++++++++ app/tools/render-table.tsx | 91 +++++++++++++- app/tools/showcase-tools.tsx | 98 +++++++++++++--- tsconfig.json | 2 +- 16 files changed, 833 insertions(+), 66 deletions(-) diff --git a/.env.example b/.env.example index 1ba1202..5423f98 100644 --- a/.env.example +++ b/.env.example @@ -43,6 +43,9 @@ AGENT_URL=http://localhost:8200/api/copilotkit/agent/triage/run # 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). diff --git a/.railway/railway.ts b/.railway/railway.ts index 5f9ad7c..8174e0f 100644 --- a/.railway/railway.ts +++ b/.railway/railway.ts @@ -112,9 +112,14 @@ export default defineRailway(() => { // deployer can set their own channel name in the Railway UI without a // later `config apply` clobbering it. Set it in the channel service's // Variables if your Intelligence channel is named something else. - INTELLIGENCE_API_URL: "https://dev.intelligence.copilotkit.ai", - INTELLIGENCE_GATEWAY_WS_URL: "wss://dev.intelligence.copilotkit.ai/runner", - // secret (set in Railway UI): + // + // All three INTELLIGENCE_* connection vars below are deployer-supplied + // secrets/values (from your CopilotKit Intelligence project) — declared + // with preserve() so they're never hardcoded here, and `railway config + // apply` never clobbers what you set in the Railway UI (see README + // "Deploy to Railway"): + INTELLIGENCE_API_URL: preserve(), + INTELLIGENCE_GATEWAY_WS_URL: preserve(), INTELLIGENCE_API_KEY: preserve(), }, }); diff --git a/app/commands/__tests__/commands.test.ts b/app/commands/__tests__/commands.test.ts index 63dc323..ee96cba 100644 --- a/app/commands/__tests__/commands.test.ts +++ b/app/commands/__tests__/commands.test.ts @@ -281,4 +281,87 @@ describe("example slash commands", () => { expect.stringMatching(/couldn.t send a private preview|file-issue/i), ); }); + + it("/preview logs and posts a fallback when postEphemeral rejects (network/API error)", async () => { + const preview = byName("preview"); + const postEphemeral = vi + .fn() + .mockRejectedValueOnce(new Error("slack api unavailable")); + const post = vi.fn().mockResolvedValue({ id: "1" }); + const consoleError = vi + .spyOn(console, "error") + .mockImplementation(() => {}); + + // Must not throw/reject — a bare rejection here would previously only + // surface as an unhandledRejection with no user feedback. + await preview.handler({ + thread: { postEphemeral, post } as never, + command: "preview", + text: "Login broken", + options: {}, + user: { id: "U1", name: "Ada" }, + platform: "discord", + } as never); + + expect(postEphemeral).toHaveBeenCalledTimes(1); + expect(consoleError).toHaveBeenCalled(); + expect(post).toHaveBeenCalledTimes(1); + expect(post).toHaveBeenCalledWith( + expect.stringMatching(/couldn.t send a private preview|file-issue/i), + ); + + consoleError.mockRestore(); + }); + + it("/file-issue logs and posts a fallback when openModal rejects (network/API error)", async () => { + const cmd = byName("file-issue"); + const post = vi.fn().mockResolvedValue({ id: "1" }); + const openModal = vi + .fn() + .mockRejectedValueOnce(new Error("view_id expired")); + const consoleError = vi + .spyOn(console, "error") + .mockImplementation(() => {}); + + // Must not throw/reject — a bare rejection here would previously only + // surface as an unhandledRejection with no user feedback. + await cmd.handler({ + thread: { post } as never, + command: "file-issue", + text: "", + options: {}, + user: { id: "U1" }, + platform: "slack", + openModal, + } as never); + + expect(openModal).toHaveBeenCalledTimes(1); + expect(consoleError).toHaveBeenCalled(); + expect(post).toHaveBeenCalledTimes(1); + expect(post).toHaveBeenCalledWith( + expect.stringMatching(/couldn.t open the form/i), + ); + + consoleError.mockRestore(); + }); + + it("/agent does not throw when the bare usage post itself rejects", async () => { + const thread = fakeThread(); + thread.post.mockRejectedValueOnce(new Error("channel archived")); + const consoleError = vi + .spyOn(console, "error") + .mockImplementation(() => {}); + + // Even the fallback-of-last-resort (posting the usage string) can reject; + // there's nowhere further to degrade to, but it must still be logged + // rather than thrown. + await byName("agent").handler( + ctx({ command: "agent", text: "", thread: thread as never }), + ); + + expect(thread.post).toHaveBeenCalledTimes(1); + expect(consoleError).toHaveBeenCalled(); + + consoleError.mockRestore(); + }); }); diff --git a/app/commands/index.ts b/app/commands/index.ts index 579803c..5a679ae 100644 --- a/app/commands/index.ts +++ b/app/commands/index.ts @@ -18,6 +18,61 @@ import { senderContext } from "../sender-context.js"; import { IssueCard } from "../components/index.js"; import { FileIssueModal } from "../modals/file-issue.js"; +/** + * Every awaited call in the handlers below — `runAgent`, `post`, + * `postEphemeral`, `openModal` — is a network round-trip to the platform API + * and can reject (backend failure, rate limit, network error). Left bare, a + * rejection only surfaces as an `unhandledRejection` and the user sees + * nothing, breaking these handlers' own "Degrade, never throw" contract. + * `safely` and `safePost` are the shared guards: they log the failure and + * turn it into the same user-facing feedback a normal "unavailable" result + * would get, instead of throwing. + */ + +/** + * Await a platform round-trip that can reject, logging the failure and + * resolving to `fallback` instead of letting the rejection propagate. + * `fallback` should be whatever resolved-but-unavailable value the caller + * already knows how to degrade for (e.g. `null`, `{ ok: false }`), so a + * rejection is absorbed by the caller's existing resolved-outcome handling + * rather than needing a separate branch. + */ +async function safely( + commandName: string, + op: string, + call: () => Promise, + fallback: T, +): Promise { + try { + return await call(); + } catch (err) { + console.error(`[command] ${commandName} ${op} failed`, err); + return fallback; + } +} + +/** + * Post to the thread, logging (never throwing) if the post itself rejects. + * These status/usage/error messages are fire-and-forget — nothing branches + * on the resulting `MessageRef` — so on rejection there's nowhere further to + * degrade to; this just guarantees it's logged instead of a silent + * unhandledRejection. + */ +async function safePost( + commandName: string, + thread: Pick, + ui: Parameters[0], +): Promise { + await safely( + commandName, + "post", + async () => { + await thread.post(ui); + }, + undefined, + ); +} + /** * `thread.runAgent` can reject (backend failure, network error, etc). Unlike * `onMention` (which wraps its own call), the slash-command handlers below @@ -34,11 +89,11 @@ async function runAgentSafely( await thread.runAgent(input); } catch (err) { console.error(`[command] ${commandName} run failed`, err); - await thread - .post("Sorry — I hit an error handling that. Please try again.") - .catch((postErr: unknown) => - console.error(`[command] ${commandName} failed to post error`, postErr), - ); + await safePost( + commandName, + thread, + "Sorry — I hit an error handling that. Please try again.", + ); } } @@ -52,7 +107,7 @@ export const appCommands: ChannelCommand[] = [ description: "Ask the triage agent anything (no @mention needed).", async handler({ thread, text, user }) { if (!text) { - await thread.post("Usage: `/agent `"); + await safePost("agent", thread, "Usage: `/agent `"); return; } await runAgentSafely("agent", thread, { @@ -90,33 +145,45 @@ export const appCommands: ChannelCommand[] = [ description: "Privately preview the issue I'd file (only you see it).", async handler({ thread, text, user, platform }) { if (!text) { - await thread.post("Usage: `/preview `"); + await safePost("preview", thread, "Usage: `/preview `"); return; } if (!user) { - await thread.post( + await safePost( + "preview", + thread, "I couldn't tell who you are, so I can't send a private preview here.", ); return; } + const invoker = user; const draft = IssueCard({ identifier: "DRAFT", title: text, state: "Triage", description: "_Draft — nothing is filed until you run_ `/file-issue`.", }); - const res = await thread.postEphemeral(user, draft, { - fallbackToDM: true, - }); - // Degrade, never throw: report what actually happened. + const res = await safely( + "preview", + "postEphemeral", + () => thread.postEphemeral(invoker, draft, { fallbackToDM: true }), + null, + ); + // Degrade, never throw: report what actually happened (this also + // covers a rejected postEphemeral, which `safely` normalizes to the + // same `null` this branch already handles). if (!res || !res.ok) { - await thread.post( + await safePost( + "preview", + thread, `I couldn't send a private preview on ${platform}. Run \`/file-issue\` to file it.`, ); return; } if (res.usedFallback) { - await thread.post( + await safePost( + "preview", + thread, "📬 I sent you the draft as a direct message (this surface has no private messages).", ); } @@ -135,7 +202,9 @@ export const appCommands: ChannelCommand[] = [ description: "Open a form to file a Linear issue.", async handler({ thread, openModal, platform, user }) { if (!openModal) { - await thread.post( + await safePost( + "file-issue", + thread, "Modals aren't supported here — let's do it in chat instead. " + "Tell me the issue title and a short description and I'll file it.", ); @@ -147,11 +216,17 @@ export const appCommands: ChannelCommand[] = [ }); return; } - const res = await openModal( - FileIssueModal({ rich: platform === "slack" }), + const openModalFn = openModal; + const res = await safely( + "file-issue", + "openModal", + () => openModalFn(FileIssueModal({ rich: platform === "slack" })), + { ok: false, error: "unexpected error" }, ); if (!res.ok) { - await thread.post( + await safePost( + "file-issue", + thread, `I couldn't open the form${res.error ? `: ${res.error}` : ""}.`, ); } diff --git a/app/components/__tests__/components.test.tsx b/app/components/__tests__/components.test.tsx index b3a9681..8bc7ed8 100644 --- a/app/components/__tests__/components.test.tsx +++ b/app/components/__tests__/components.test.tsx @@ -18,7 +18,13 @@ import { describe, it, expect } from "vitest"; import { renderToIR } from "@copilotkit/channels-ui"; import { renderSlackMessage } from "@copilotkit/channels-slack"; import { renderTelegram } from "@copilotkit/channels-telegram"; -import { IssueList } from "../issue-list.js"; +import { + IssueList, + fitLinesToBudget, + formatIssueLine, + SECTION_CHAR_BUDGET, + SECTION_CHAR_SAFETY_MARGIN, +} from "../issue-list.js"; import { IssueCard } from "../issue-card.js"; import { PageList } from "../page-list.js"; import { accentForIssues } from "../_status.js"; @@ -108,6 +114,61 @@ describe("IssueList component", () => { expect(JSON.stringify(blocks[2])).toContain("Showing 15 of 20 issues"); }); + it("stops adding lines before Slack's section char budget would be exceeded, and reports the ACTUAL shown count (not the raw MAX)", () => { + // Realistic Linear issues: a slugged URL + full metadata puts each line + // at ~230-250 chars (per the finding's "~190-220 chars" estimate), so + // with 30 of them, 15 lines (the raw count cap) would already be well + // past Slack's ~3000-char section-text budget — the exact + // silent-truncation scenario this fix addresses. + const LONG_TITLE = + "Investigate and fix the intermittent checkout failures under load"; + const LONG_SLUG = + "investigate-and-fix-the-intermittent-checkout-failures-under-load"; + const issues = Array.from({ length: 30 }, (_, i) => ({ + identifier: `CPK-${1000 + i}`, + title: LONG_TITLE, + url: `https://linear.app/copilotkit/issue/CPK-${1000 + i}/${LONG_SLUG}`, + assignee: "Alexandria Chen-Okonkwo", + priority: "Urgent", + updated: "2d ago", + })); + + const { blocks } = renderSlackMessage( + renderToIR(), + ); + + const section = blocks[1] as { text: { text: string } }; + const text = section.text.text; + const lines = text.split("\n"); + + // Well clear of Slack's hard per-section limit — our own char-budget + // cutoff (with safety margin) stopped us before the channel renderer's + // silent `truncateText(…, 3000)` ever had to act. + expect(text.length).toBeLessThan(3000); + + // (a) The char budget — not the raw MAX=15 — is what bound this list: + // strictly fewer lines made it in than the count-based cap would allow. + expect(lines.length).toBeLessThan(15); + expect(lines.length).toBeLessThan(issues.length); + + // Every rendered line is COMPLETE — ends with the full meta suffix — + // proving no line was cut off mid-way through by a silent truncation. + for (const line of lines) { + expect(line.endsWith("Alexandria Chen-Okonkwo · Urgent · 2d ago")).toBe( + true, + ); + } + + // (b) The footer's shown-count is the count ACTUALLY rendered, never a + // stale MAX that would overstate what's really there. + expect(JSON.stringify(blocks[2])).toContain( + `Showing ${lines.length} of ${issues.length} issues`, + ); + expect(JSON.stringify(blocks[2])).not.toContain( + `Showing 15 of ${issues.length} issues`, + ); + }); + it("falls back to an emphasized identifier and 'unassigned' when fields are missing", () => { const { blocks, accent } = renderSlackMessage( renderToIR( @@ -173,6 +234,54 @@ describe("accentForIssues", () => { }); }); +describe("fitLinesToBudget", () => { + it("stops before the section would exceed the margin-adjusted char budget, not at a fixed line count", () => { + const longLine = "x".repeat(250); + const lines = Array.from({ length: 20 }, () => longLine); + const budget = SECTION_CHAR_BUDGET - SECTION_CHAR_SAFETY_MARGIN; + const shown = fitLinesToBudget(lines); + + // 250 chars/line + 1 joining newline; the budget-bound cutoff lands + // well short of both the full list (20) and the MAX ceiling (15) — + // proving the char budget, not a count, decided the cutoff. + expect(shown.length).toBeLessThan(15); + expect(shown.length).toBeLessThan(lines.length); + expect(shown.join("\n").length).toBeLessThanOrEqual(budget); + // One more line would have crossed the budget. + expect(shown.concat([longLine]).join("\n").length).toBeGreaterThan( + budget, + ); + }); + + it("still honors the MAX=15 ceiling when lines are short enough to all fit under budget", () => { + const shortLines = Array.from({ length: 20 }, (_, i) => `line ${i}`); + const shown = fitLinesToBudget(shortLines); + expect(shown).toEqual(shortLines.slice(0, 15)); + }); + + it("always includes at least one line, even a single line that alone exceeds the budget", () => { + const oneHugeLine = "y".repeat(5000); + const shown = fitLinesToBudget([oneHugeLine, "second line"]); + expect(shown).toEqual([oneHugeLine]); + }); +}); + +describe("formatIssueLine", () => { + it("matches the existing per-item line format (glyph, linked id, title, em-dash meta)", () => { + const line = formatIssueLine({ + identifier: "CPK-1", + title: "Sample", + url: "https://linear.app/copilotkit/issue/CPK-1", + assignee: "Alem", + priority: "High", + updated: "2d ago", + }); + expect(line).toBe( + "🟠 [**CPK-1**](https://linear.app/copilotkit/issue/CPK-1) Sample — Alem · High · 2d ago", + ); + }); +}); + describe("IssueCard component", () => { it("renders a status header, linked title and a fields grid", () => { const { blocks, accent } = renderSlackMessage( diff --git a/app/components/issue-list.tsx b/app/components/issue-list.tsx index 76bbbf2..18f3be8 100644 --- a/app/components/issue-list.tsx +++ b/app/components/issue-list.tsx @@ -9,6 +9,18 @@ * `invalid_attachments`. We instead inline up to `MAX` issues into a single * section and surface the overflow in the footer. * + * That single section is itself budget-constrained: Slack's Block Kit caps a + * section's mrkdwn text at `SECTION_CHAR_BUDGET` (3000) chars, and if that's + * exceeded the `@copilotkit/channels-slack` renderer SILENTLY slices the text + * to that length and appends "…" — it does not fail loud, and the cut can + * land mid-line, mid-word. With realistic Linear URLs a single issue line + * runs ~190-220 chars, so as few as ~15 of them can already be at/over that + * budget — meaning a purely count-based cap (`issues.length > MAX`) could + * report "Showing 15 of N issues" while Slack had actually rendered fewer, + * partially-truncated lines. `fitLinesToBudget` below accumulates each + * line's real length and stops BEFORE crossing the budget, so the footer's + * count always matches what was actually rendered. + * * The agent fetches issues from the Linear MCP server and passes the fields * it wants shown; the Slack formatting lives here. For a single issue (or * right after creating one) prefer `issue_card`, which shows a full grid. @@ -53,34 +65,92 @@ export const issueListSchema = z.object({ export type IssueListProps = z.infer; type Issue = z.infer; -/** Max issues rendered inline; the rest are summarized in the footer. */ +/** + * Max issues rendered inline; the rest are summarized in the footer. This is + * a ceiling *on top of* the character budget below: with short lines (no + * URL/assignee/priority) far fewer than `SECTION_CHAR_BUDGET` chars are used + * by 15 of them, so this cap alone keeps the section a sane size. With long, + * realistic lines it's the character budget — not this count — that ends up + * binding first. + */ const MAX = 15; + +/** + * Slack's per-section mrkdwn text budget (`sectionText` in + * `@copilotkit/channels-slack`'s `render/budget.js`). See the module doc + * above for why silently exceeding it is dangerous. + */ +export const SECTION_CHAR_BUDGET = 3000; + +/** + * Headroom subtracted from `SECTION_CHAR_BUDGET` before we stop adding + * lines: covers the gap between our raw Markdown (`[**id**](url)`) and the + * Slack mrkdwn (``) the channel renderer converts it to (in + * practice slightly shorter, but we don't depend on the exact delta), and + * leaves general headroom so the cutoff is conservative rather than exact. + */ +export const SECTION_CHAR_SAFETY_MARGIN = 200; + /** Max title length before trimming (keeps each line scannable). */ const TITLE_MAX = 70; +/** + * Render one issue as a single scannable line: status dot, linked bold + * identifier, (trimmed) title, then an em-dash and assignee · priority · + * updated meta. Exported so `fitLinesToBudget`'s accounting can be + * unit-tested against real line text. + */ +export function formatIssueLine(issue: Issue): string { + const idLink = issue.url + ? `[**${issue.identifier}**](${issue.url})` + : `**${issue.identifier}**`; + const title = + issue.title.length > TITLE_MAX + ? `${issue.title.slice(0, TITLE_MAX)}…` + : issue.title; + const meta = `${issue.assignee ?? "unassigned"}${issue.priority ? ` · ${issue.priority}` : ""}${issue.updated ? ` · ${issue.updated}` : ""}`; + return `${stateGlyph(issue.state)} ${idLink} ${title} — ${meta}`; +} + +/** + * Pick a prefix of `lines` that fits a single Slack section: at most + * `maxLines`, and never so much text that the joined (`"\n"`-separated) + * result would exceed `budget` chars — stopping BEFORE we'd cross it, not + * after, so the channel renderer's own silent truncation never has to act. + * Always keeps at least one line, even one that alone exceeds the budget, + * rather than rendering an empty section. + */ +export function fitLinesToBudget( + lines: string[], + maxLines: number = MAX, + budget: number = SECTION_CHAR_BUDGET - SECTION_CHAR_SAFETY_MARGIN, +): string[] { + const shown: string[] = []; + let total = 0; + for (const line of lines) { + if (shown.length >= maxLines) break; + const joinCost = shown.length > 0 ? 1 : 0; // the "\n" this line would add + const nextTotal = total + joinCost + line.length; + if (shown.length > 0 && nextTotal > budget) break; + total = nextTotal; + shown.push(line); + } + return shown; +} + /** Render a list of Linear issues as a compact, fixed-size Block Kit card. */ export function IssueList({ heading, issues }: IssueListProps): ChannelNode { - const lines = issues.slice(0, MAX).map((issue: Issue) => { - const idLink = issue.url - ? `[**${issue.identifier}**](${issue.url})` - : `**${issue.identifier}**`; - const title = - issue.title.length > TITLE_MAX - ? `${issue.title.slice(0, TITLE_MAX)}…` - : issue.title; - const meta = `${issue.assignee ?? "unassigned"}${issue.priority ? ` · ${issue.priority}` : ""}${issue.updated ? ` · ${issue.updated}` : ""}`; - return `${stateGlyph(issue.state)} ${idLink} ${title} — ${meta}`; - }); + const shown = fitLinesToBudget(issues.map(formatIssueLine)); const footer = - issues.length > MAX - ? `Showing ${MAX} of ${issues.length} issues` + shown.length < issues.length + ? `Showing ${shown.length} of ${issues.length} issues` : `${issues.length} issue${issues.length === 1 ? "" : "s"}`; return (
{`📋 ${heading ?? "Linear issues"}`}
-
{lines.join("\n")}
+
{shown.join("\n")}
{footer}
); diff --git a/app/human-in-the-loop/__tests__/confirm-write.test.tsx b/app/human-in-the-loop/__tests__/confirm-write.test.tsx index 1c6dbc5..6a828d3 100644 --- a/app/human-in-the-loop/__tests__/confirm-write.test.tsx +++ b/app/human-in-the-loop/__tests__/confirm-write.test.tsx @@ -105,6 +105,27 @@ describe("ConfirmWrite", () => { expect(blocks.some((b) => b.type === "section")).toBe(false); }); + it("truncates a very long detail so the section text stays within the cap and the card still renders", () => { + // Well beyond both our 600-char cap and Slack's ~3000-char section-text + // budget — this is the "agent hands back a huge detail" regression case: + // before the cap, the confirm card could fail to post, and the blocking + // awaitChoice in confirm-write-tool.tsx would then never resolve. + const longDetail = "This is a very long piece of detail text. ".repeat(200); + expect(longDetail.length).toBeGreaterThan(3000); + + const ir = renderToIR( + , + ); + const { blocks } = renderSlackMessage(ir); + + const section = blocks.find((b) => b.type === "section") as + | { text: { text: string } } + | undefined; + expect(section).toBeDefined(); + expect(section!.text.text).toBe(`${longDetail.slice(0, 600)}…`); + expect(section!.text.text.length).toBeLessThanOrEqual(601); + }); + it("approve onClick updates the picker in place to the resolved (green) state", async () => { const ir = renderToIR( , diff --git a/app/human-in-the-loop/confirm-write.tsx b/app/human-in-the-loop/confirm-write.tsx index e180aed..bc8676d 100644 --- a/app/human-in-the-loop/confirm-write.tsx +++ b/app/human-in-the-loop/confirm-write.tsx @@ -32,11 +32,28 @@ export interface ConfirmWriteProps { detail?: string; } +/** + * Slack's section-text budget is ~3000 chars; an agent-supplied `detail` + * anywhere near that risks the confirm card failing to post — and since the + * blocking `awaitChoice` in confirm-write-tool.tsx can then never resolve, + * that would break the write GATE instead of degrading it. Cap well under + * the budget so the card always posts, on every channel (not just Slack's + * own renderer, which truncates section text but not every channel does). + * Same trim length as the `description` cap in issue-card.tsx, for + * consistency across cards. + */ +const MAX_DETAIL_LENGTH = 600; + export function ConfirmWrite({ action, detail }: ConfirmWriteProps) { + const detailText = detail + ? detail.length > MAX_DETAIL_LENGTH + ? `${detail.slice(0, MAX_DETAIL_LENGTH)}…` + : detail + : undefined; return (
{`📝 ${action}?`}
- {detail ?
{detail}
: null} + {detailText ?
{detailText}
: null} {"🔒 Nothing is written until you click **Create**."}