@copilotkit/channels-core 0.1.1 → 0.2.1-canary.1784916581

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (37) hide show
  1. package/README.md +45 -2
  2. package/dist/create-channel.d.ts +20 -10
  3. package/dist/create-channel.d.ts.map +1 -1
  4. package/dist/create-channel.foundations.test.js +14 -14
  5. package/dist/create-channel.js +143 -124
  6. package/dist/create-channel.test.js +34 -34
  7. package/dist/modal-submit.test.js +4 -4
  8. package/dist/open-modal.test.js +4 -4
  9. package/dist/platform-adapter.d.ts +9 -0
  10. package/dist/platform-adapter.d.ts.map +1 -1
  11. package/dist/reactions.test.js +30 -10
  12. package/dist/telemetry/create-channel-telemetry.test.js +7 -4
  13. package/dist/telemetry/e2e-telemetry.test.js +1 -1
  14. package/dist/testing/fake-adapter.d.ts.map +1 -1
  15. package/dist/testing/fake-adapter.js +1 -1
  16. package/dist/thread-capabilities.test.js +5 -5
  17. package/dist/thread-ephemeral.test.js +1 -1
  18. package/dist/thread-reactions.test.js +2 -2
  19. package/package.json +4 -4
  20. package/dist/create-bot.d.ts +0 -171
  21. package/dist/create-bot.d.ts.map +0 -1
  22. package/dist/create-bot.foundations.test.d.ts +0 -2
  23. package/dist/create-bot.foundations.test.d.ts.map +0 -1
  24. package/dist/create-bot.foundations.test.js +0 -147
  25. package/dist/create-bot.js +0 -579
  26. package/dist/create-bot.test.d.ts +0 -2
  27. package/dist/create-bot.test.d.ts.map +0 -1
  28. package/dist/create-bot.test.js +0 -936
  29. package/dist/telemetry/bot-telemetry.d.ts +0 -31
  30. package/dist/telemetry/bot-telemetry.d.ts.map +0 -1
  31. package/dist/telemetry/bot-telemetry.js +0 -70
  32. package/dist/telemetry/bot-telemetry.test.d.ts +0 -2
  33. package/dist/telemetry/bot-telemetry.test.d.ts.map +0 -1
  34. package/dist/telemetry/bot-telemetry.test.js +0 -56
  35. package/dist/telemetry/create-bot-telemetry.test.d.ts +0 -2
  36. package/dist/telemetry/create-bot-telemetry.test.d.ts.map +0 -1
  37. package/dist/telemetry/create-bot-telemetry.test.js +0 -71
package/README.md CHANGED
@@ -4,6 +4,11 @@ The supported platform-neutral foundation behind `@copilotkit/channels`.
4
4
  Most applications should use the batteries-included `@copilotkit/channels` package;
5
5
  install core directly when building an adapter or intentionally selecting one platform.
6
6
 
7
+ **Every Channel requires a CopilotKit Intelligence connection** (an API key — a
8
+ free tier is available). There is no standalone / DIY run path: a Channel is
9
+ started and owned by the `CopilotRuntime` once Intelligence is configured, not
10
+ by calling a method on the Channel itself. See "Running a Channel" below.
11
+
7
12
  ## Selective install
8
13
 
9
14
  ```sh
@@ -45,12 +50,50 @@ import { slack } from "@copilotkit/channels-slack";
45
50
  (e.g. Discord). Forwarded to adapters that support commands and ignored
46
51
  elsewhere — also pass them up front via `commands` in `CreateChannelOptions`.
47
52
  - `tool(t)` — register a `ChannelTool` (alternative to `opts.tools`); must be
48
- added before `start()`.
49
- - `start()` / `stop()` — bring adapters up / down.
53
+ added before the runtime activates the channel.
50
54
 
51
55
  `agent` is optional. If omitted, calling `thread.runAgent()` throws; supply
52
56
  an `AbstractAgent` or a `(threadId) => AbstractAgent` factory.
53
57
 
58
+ A `Channel` has no public `start()` / `stop()` — lifecycle is runtime-owned
59
+ (see below).
60
+
61
+ ## Running a Channel
62
+
63
+ A Channel only runs when it's declared on an Intelligence-configured
64
+ `CopilotRuntime`; there is no `channel.start()` and no standalone/DIY runner.
65
+ Pass the `Channel` in `channels`, then drive activation through the returned
66
+ handler:
67
+
68
+ ```ts
69
+ import { createChannel } from "@copilotkit/channels-core";
70
+ import { slack } from "@copilotkit/channels-slack";
71
+ import {
72
+ CopilotRuntime,
73
+ CopilotKitIntelligence,
74
+ createCopilotRuntimeHandler,
75
+ } from "@copilotkit/runtime/v2";
76
+
77
+ const channel = createChannel({
78
+ name: "support-bot", // project-unique Intelligence Channel name
79
+ adapters: [slack({ botToken, appToken })],
80
+ });
81
+
82
+ const runtime = new CopilotRuntime({
83
+ intelligence: new CopilotKitIntelligence({
84
+ apiUrl: "https://api.copilotkit.ai",
85
+ wsUrl: "wss://api.copilotkit.ai",
86
+ apiKey: process.env.COPILOTKIT_INTELLIGENCE_API_KEY!, // free tier available
87
+ }),
88
+ identifyUser: async () => ({ id: "support-bot", name: "Support Bot" }),
89
+ channels: [channel],
90
+ });
91
+
92
+ const handler = createCopilotRuntimeHandler({ runtime });
93
+ await handler.channels.ready(); // starts every declared channel
94
+ // await handler.channels.stop(); // tears them down
95
+ ```
96
+
54
97
  ## `Thread`
55
98
 
56
99
  A `Thread` is the per-conversation handle handed to your handlers and tool
@@ -26,9 +26,10 @@ export type LockConflictDecision = "drop" | "force";
26
26
  * (Intelligence OSS-450 / #511).
27
27
  *
28
28
  * Distinct from a {@link PlatformAdapter} attached via
29
- * `createChannel({ adapters })` / {@link Channel.addAdapter}: an adapter is a
30
- * *direct*, developer-owned connection this handler does not manage, whereas
31
- * `provider` selects the *managed* platform for a Channel with no adapters.
29
+ * `createChannel({ adapters })` / `channel.ɵruntime.addAdapter`: an adapter is
30
+ * a *direct*, developer-owned connection this handler does not manage,
31
+ * whereas `provider` selects the *managed* platform for a Channel with no
32
+ * adapters.
32
33
  */
33
34
  export type ManagedChannelProvider = "slack" | "teams";
34
35
  /**
@@ -124,7 +125,8 @@ export interface CreateChannelOptions<TStateSchema extends StandardSchemaV1 | un
124
125
  name?: string;
125
126
  /**
126
127
  * Adapters supplied at construction. Optional — adapters can also be attached
127
- * before `start()` via {@link Channel.addAdapter} (the Channel runtime uses this).
128
+ * before the runner starts the channel, via `channel.ɵruntime.addAdapter`
129
+ * (the Channel runtime uses this).
128
130
  */
129
131
  adapters?: PlatformAdapter[];
130
132
  /**
@@ -139,8 +141,8 @@ export interface CreateChannelOptions<TStateSchema extends StandardSchemaV1 | un
139
141
  * {@link ManagedChannelProvider}.
140
142
  *
141
143
  * Ignored for direct-adapter Channels (those created with `adapters` /
142
- * {@link Channel.addAdapter}) — a direct Channel is owned by the developer's
143
- * own adapter, not by managed activation.
144
+ * `channel.ɵruntime.addAdapter`) — a direct Channel is owned by the
145
+ * developer's own adapter, not by managed activation.
144
146
  */
145
147
  provider?: ManagedChannelProvider;
146
148
  agent?: AbstractAgent | ((threadId: string) => AbstractAgent);
@@ -205,12 +207,20 @@ export interface Channel<TState = unknown> {
205
207
  /** Handle a modal dismissal for `callbackId` (Slack `view_closed`). */
206
208
  onModalClose(callbackId: string, handler: ModalCloseHandler): void;
207
209
  tool(t: ChannelTool): void;
208
- /** Attach an adapter before `start()`. Throws if called after the channel has started. */
209
- addAdapter(adapter: PlatformAdapter): void;
210
- start(): Promise<void>;
211
- stop(): Promise<void>;
212
210
  /** Cross-platform transcript store. Append, list, and delete entries per user. */
213
211
  transcripts: Transcripts;
212
+ /**
213
+ * Internal lifecycle seam. Holds the `start`/`stop`/`addAdapter`
214
+ * implementations that the runtime uses to drive the lifecycle directly —
215
+ * there is no public equivalent; channels are runtime-driven only. (Read the
216
+ * managed provider off the top-level `channel.provider`, not here.)
217
+ * @internal
218
+ */
219
+ ɵruntime: {
220
+ start(): Promise<void>;
221
+ stop(): Promise<void>;
222
+ addAdapter(adapter: PlatformAdapter): void;
223
+ };
214
224
  }
215
225
  export declare function createChannel<TStateSchema extends StandardSchemaV1 | undefined = undefined>(opts: CreateChannelOptions<TStateSchema>): Channel<ThreadStateOf<TStateSchema>>;
216
226
  export {};
@@ -1 +1 @@
1
- {"version":3,"file":"create-channel.d.ts","sourceRoot":"","sources":["../src/create-channel.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,eAAe,EASf,iBAAiB,EAClB,MAAM,uBAAuB,CAAC;AAE/B,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAGrD,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,wBAAwB,CAAC;AAEzD,OAAO,KAAK,EAAE,WAAW,EAAE,YAAY,EAAE,MAAM,YAAY,CAAC;AAE5D,OAAO,KAAK,EAAE,cAAc,EAAE,cAAc,EAAE,MAAM,eAAe,CAAC;AACpE,OAAO,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;AAErC,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,eAAe,CAAC;AACnD,OAAO,KAAK,EACV,kBAAkB,EAClB,eAAe,EACf,YAAY,EACZ,UAAU,EAGV,WAAW,EACX,UAAU,EACX,MAAM,yBAAyB,CAAC;AAMjC,OAAO,EAAE,WAAW,EAAE,MAAM,kBAAkB,CAAC;AAC/C,OAAO,KAAK,EAAE,QAAQ,EAAE,iBAAiB,EAAE,MAAM,kBAAkB,CAAC;AACpE,OAAO,KAAK,EAAE,gBAAgB,EAAE,iBAAiB,EAAE,MAAM,sBAAsB,CAAC;AA4BhF,MAAM,MAAM,oBAAoB,GAAG,MAAM,GAAG,OAAO,CAAC;AAEpD;;;;;;;;;;;;;;;;;;;GAmBG;AACH,MAAM,MAAM,sBAAsB,GAAG,OAAO,GAAG,OAAO,CAAC;AAEvD;;;;;;GAMG;AACH,MAAM,MAAM,gBAAgB,GAAG,CAAC,KAAK,EAAE,KAAK,KAAK,UAAU,CAAC,WAAW,CAAC,CAAC;AAEzE,MAAM,MAAM,cAAc,CAAC,MAAM,GAAG,OAAO,IAAI,CAAC,GAAG,EAAE;IACnD,MAAM,EAAE,cAAc,CAAC,MAAM,CAAC,CAAC;IAC/B,OAAO,EAAE,eAAe,CAAC;CAC1B,KAAK,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;AAE3B,2FAA2F;AAC3F,MAAM,MAAM,kBAAkB,CAAC,MAAM,GAAG,OAAO,IAAI,CAAC,GAAG,EAAE;IACvD,MAAM,EAAE,cAAc,CAAC,MAAM,CAAC,CAAC;IAC/B,IAAI,CAAC,EAAE,YAAY,CAAC;CACrB,KAAK,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;AAE3B,+CAA+C;AAC/C,MAAM,WAAW,aAAa;IAC5B,oEAAoE;IACpE,KAAK,EAAE,UAAU,CAAC;IAClB,6BAA6B;IAC7B,QAAQ,EAAE,MAAM,CAAC;IACjB,qCAAqC;IACrC,KAAK,EAAE,OAAO,CAAC;IACf,wDAAwD;IACxD,IAAI,CAAC,EAAE,YAAY,CAAC;IACpB,SAAS,EAAE,MAAM,CAAC;IAClB,mFAAmF;IACnF,UAAU,EAAE,UAAU,CAAC;IACvB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,MAAM,EAAE,MAAM,CAAC;IACf,OAAO,EAAE,eAAe,CAAC;IACzB,GAAG,EAAE,OAAO,CAAC;CACd;AACD,MAAM,MAAM,eAAe,GAAG,CAAC,GAAG,EAAE,aAAa,KAAK,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;AAE3E,kDAAkD;AAClD,MAAM,WAAW,gBAAgB;IAC/B,UAAU,EAAE,MAAM,CAAC;IACnB,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAChC,IAAI,CAAC,EAAE,YAAY,CAAC;IACpB,kEAAkE;IAClE,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,GAAG,EAAE,OAAO,CAAC;CACd;AACD,MAAM,MAAM,kBAAkB,GAAG,CAC/B,GAAG,EAAE,gBAAgB,KAClB,iBAAiB,GAAG,IAAI,GAAG,OAAO,CAAC,iBAAiB,GAAG,IAAI,CAAC,CAAC;AAElE,iDAAiD;AACjD,MAAM,WAAW,eAAe;IAC9B,UAAU,EAAE,MAAM,CAAC;IACnB,IAAI,CAAC,EAAE,YAAY,CAAC;IACpB,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,GAAG,EAAE,OAAO,CAAC;CACd;AACD,MAAM,MAAM,iBAAiB,GAAG,CAAC,GAAG,EAAE,eAAe,KAAK,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;AAE/E,gFAAgF;AAChF,KAAK,aAAa,CAAC,OAAO,SAAS,gBAAgB,GAAG,SAAS,IAC7D,OAAO,SAAS,gBAAgB,GAAG,iBAAiB,CAAC,OAAO,CAAC,GAAG,OAAO,CAAC;AAE1E,mFAAmF;AACnF,MAAM,MAAM,cAAc,CAAC,MAAM,IAAI,IAAI,CAAC,MAAM,EAAE,UAAU,GAAG,OAAO,CAAC,GAAG;IACxE,QAAQ,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IACvC,KAAK,IAAI,OAAO,CAAC,MAAM,GAAG,SAAS,CAAC,CAAC;CACtC,CAAC;AAEF;;;;GAIG;AACH,MAAM,WAAW,WAAW,CAC1B,YAAY,SAAS,gBAAgB,GAAG,SAAS,GAAG,SAAS;IAE7D,0FAA0F;IAC1F,OAAO,CAAC,EAAE,UAAU,CAAC;IACrB,2IAA2I;IAC3I,KAAK,CAAC,EAAE,YAAY,CAAC;IACrB,4FAA4F;IAC5F,QAAQ,CAAC,EAAE,QAAQ,CAAC;IACpB,wEAAwE;IACxE,WAAW,CAAC,EAAE,iBAAiB,CAAC;IAChC,mGAAmG;IACnG,cAAc,CAAC,EACX,oBAAoB,GACpB,CAAC,CACC,eAAe,EAAE,MAAM,EACvB,OAAO,EAAE,eAAe,KACrB,oBAAoB,GAAG,OAAO,CAAC,oBAAoB,CAAC,CAAC,CAAC;IAC/D,mEAAmE;IACnE,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,oEAAoE;IACpE,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB;AAED,MAAM,WAAW,oBAAoB,CACnC,YAAY,SAAS,gBAAgB,GAAG,SAAS,GAAG,SAAS;IAE7D;;;;;OAKG;IACH,IAAI,CAAC,EAAE,MAAM,CAAC;IACd;;;OAGG;IACH,QAAQ,CAAC,EAAE,eAAe,EAAE,CAAC;IAC7B;;;;;;;;;;;;;;OAcG;IACH,QAAQ,CAAC,EAAE,sBAAsB,CAAC;IAClC,KAAK,CAAC,EAAE,aAAa,GAAG,CAAC,CAAC,QAAQ,EAAE,MAAM,KAAK,aAAa,CAAC,CAAC;IAC9D,gDAAgD;IAChD,WAAW,CAAC,EAAE,WAAW,CAAC;IAC1B,KAAK,CAAC,EAAE,WAAW,EAAE,CAAC;IACtB,OAAO,CAAC,EAAE,YAAY,EAAE,CAAC;IACzB;;;;;OAKG;IACH,UAAU,CAAC,EAAE,gBAAgB,EAAE,CAAC;IAChC,kFAAkF;IAClF,QAAQ,CAAC,EAAE,cAAc,EAAE,CAAC;IAC5B,gFAAgF;IAChF,KAAK,CAAC,EAAE,WAAW,CAAC,YAAY,CAAC,CAAC;CACnC;AAED,MAAM,WAAW,OAAO,CAAC,MAAM,GAAG,OAAO;IACvC,6FAA6F;IAC7F,QAAQ,CAAC,IAAI,CAAC,EAAE,MAAM,CAAC;IACvB,oNAAoN;IACpN,QAAQ,CAAC,QAAQ,EAAE,SAAS,eAAe,EAAE,CAAC;IAC9C;;;;;OAKG;IACH,QAAQ,CAAC,QAAQ,CAAC,EAAE,sBAAsB,CAAC;IAC3C,2FAA2F;IAC3F,QAAQ,CAAC,YAAY,EAAE,MAAM,EAAE,CAAC;IAChC,SAAS,CAAC,CAAC,EAAE,cAAc,CAAC,MAAM,CAAC,GAAG,IAAI,CAAC;IAC3C,SAAS,CAAC,CAAC,EAAE,cAAc,CAAC,MAAM,CAAC,GAAG,IAAI,CAAC;IAC3C;;;;OAIG;IACH,eAAe,CAAC,CAAC,EAAE,kBAAkB,CAAC,MAAM,CAAC,GAAG,IAAI,CAAC;IACrD,wFAAwF;IACxF,aAAa,CAAC,MAAM,GAAG,OAAO,EAC5B,EAAE,EAAE,MAAM,EACV,CAAC,EAAE,CAAC,GAAG,EAAE,kBAAkB,CAAC,MAAM,CAAC,KAAK,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,GAC3D,IAAI,CAAC;IACR;;;;OAIG;IACH,WAAW,CAAC,QAAQ,GAAG,OAAO,EAC5B,SAAS,EAAE,MAAM,EACjB,CAAC,EAAE,CAAC,IAAI,EAAE;QACR,OAAO,EAAE,QAAQ,CAAC;QAClB,MAAM,EAAE,cAAc,CAAC,MAAM,CAAC,CAAC;KAChC,KAAK,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,GACzB,IAAI,CAAC;IACR,8DAA8D;IAC9D,SAAS,CAAC,OAAO,EAAE,cAAc,GAAG,IAAI,CAAC;IACzC,kDAAkD;IAClD,SAAS,CACP,IAAI,EAAE,MAAM,EACZ,OAAO,EAAE,CAAC,GAAG,EAAE,cAAc,KAAK,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,GACrD,IAAI,CAAC;IACR,kGAAkG;IAClG,UAAU,CAAC,OAAO,EAAE,eAAe,GAAG,IAAI,CAAC;IAC3C,UAAU,CAAC,KAAK,EAAE,UAAU,GAAG,UAAU,EAAE,EAAE,OAAO,EAAE,eAAe,GAAG,IAAI,CAAC;IAC7E,uFAAuF;IACvF,aAAa,CAAC,UAAU,EAAE,MAAM,EAAE,OAAO,EAAE,kBAAkB,GAAG,IAAI,CAAC;IACrE,uEAAuE;IACvE,YAAY,CAAC,UAAU,EAAE,MAAM,EAAE,OAAO,EAAE,iBAAiB,GAAG,IAAI,CAAC;IACnE,IAAI,CAAC,CAAC,EAAE,WAAW,GAAG,IAAI,CAAC;IAC3B,0FAA0F;IAC1F,UAAU,CAAC,OAAO,EAAE,eAAe,GAAG,IAAI,CAAC;IAC3C,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;IACvB,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;IACtB,kFAAkF;IAClF,WAAW,EAAE,WAAW,CAAC;CAC1B;AAuDD,wBAAgB,aAAa,CAC3B,YAAY,SAAS,gBAAgB,GAAG,SAAS,GAAG,SAAS,EAE7D,IAAI,EAAE,oBAAoB,CAAC,YAAY,CAAC,GACvC,OAAO,CAAC,aAAa,CAAC,YAAY,CAAC,CAAC,CAqnBtC"}
1
+ {"version":3,"file":"create-channel.d.ts","sourceRoot":"","sources":["../src/create-channel.ts"],"names":[],"mappings":"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"}
@@ -8,8 +8,8 @@ describe("createChannel — optional adapters + addAdapter", () => {
8
8
  it("starts with no adapters and runs one added before start()", async () => {
9
9
  const fake = new FakeAdapter();
10
10
  const channel = createChannel({ agent: () => new FakeAgent() });
11
- channel.addAdapter(fake);
12
- await channel.start();
11
+ channel.ɵruntime.addAdapter(fake);
12
+ await channel.ɵruntime.start();
13
13
  expect(fake.started).toBe(true);
14
14
  });
15
15
  it("throws when addAdapter is called after start()", async () => {
@@ -17,8 +17,8 @@ describe("createChannel — optional adapters + addAdapter", () => {
17
17
  adapters: [new FakeAdapter()],
18
18
  agent: () => new FakeAgent(),
19
19
  });
20
- await channel.start();
21
- expect(() => channel.addAdapter(new FakeAdapter())).toThrow(/start/i);
20
+ await channel.ɵruntime.start();
21
+ expect(() => channel.ɵruntime.addAdapter(new FakeAdapter())).toThrow(/start/i);
22
22
  });
23
23
  it("is idempotent: a second start() does not re-start adapters or rebuild state", async () => {
24
24
  const fake = new FakeAdapter();
@@ -27,9 +27,9 @@ describe("createChannel — optional adapters + addAdapter", () => {
27
27
  agent: () => new FakeAgent(),
28
28
  });
29
29
  const startSpy = vi.spyOn(fake, "start");
30
- await channel.start();
30
+ await channel.ɵruntime.start();
31
31
  const transcriptsAfterFirst = channel.transcripts;
32
- await channel.start(); // second call must be a no-op
32
+ await channel.ɵruntime.start(); // second call must be a no-op
33
33
  expect(startSpy).toHaveBeenCalledTimes(1);
34
34
  // Same transcript-store instance → state (locks/dedup/actions) not wiped.
35
35
  expect(channel.transcripts).toBe(transcriptsAfterFirst);
@@ -41,9 +41,9 @@ describe("createChannel — optional adapters + addAdapter", () => {
41
41
  agent: () => new FakeAgent(),
42
42
  });
43
43
  const startSpy = vi.spyOn(fake, "start");
44
- await channel.start();
45
- await channel.stop();
46
- await channel.start(); // stop() cleared `started`, so this is a real restart
44
+ await channel.ɵruntime.start();
45
+ await channel.ɵruntime.stop();
46
+ await channel.ɵruntime.start(); // stop() cleared `started`, so this is a real restart
47
47
  expect(startSpy).toHaveBeenCalledTimes(2);
48
48
  });
49
49
  });
@@ -75,7 +75,7 @@ describe("createChannel — store resolution", () => {
75
75
  transcripts: {},
76
76
  },
77
77
  });
78
- await seeder.start();
78
+ await seeder.ɵruntime.start();
79
79
  await seeder.transcripts.append({ platform: "fake", conversationKey: "c" }, { role: "user", text: "seeded" }, { userKey: "u@x.com" });
80
80
  const fake = new FakeAdapter();
81
81
  fake.stateStore = adapterStore;
@@ -84,7 +84,7 @@ describe("createChannel — store resolution", () => {
84
84
  agent: () => new FakeAgent(),
85
85
  store: { identity: () => "u@x.com", transcripts: {} },
86
86
  });
87
- await channel.start();
87
+ await channel.ɵruntime.start();
88
88
  const entries = await channel.transcripts.list({ userKey: "u@x.com" });
89
89
  expect(entries.map((e) => e.text)).toContain("seeded");
90
90
  });
@@ -102,7 +102,7 @@ describe("createChannel — store resolution", () => {
102
102
  transcripts: {},
103
103
  },
104
104
  });
105
- await channel.start();
105
+ await channel.ɵruntime.start();
106
106
  expect(warn).not.toHaveBeenCalled();
107
107
  warn.mockRestore();
108
108
  });
@@ -116,7 +116,7 @@ describe("createChannel — store resolution", () => {
116
116
  adapters: [a, b],
117
117
  agent: () => new FakeAgent(),
118
118
  });
119
- await channel.start();
119
+ await channel.ɵruntime.start();
120
120
  expect(warn).toHaveBeenCalledWith(expect.stringContaining("state store"));
121
121
  warn.mockRestore();
122
122
  });
@@ -136,7 +136,7 @@ describe("createChannel — id propagation to handler context", () => {
136
136
  eventId: message.eventId,
137
137
  };
138
138
  });
139
- await channel.start();
139
+ await channel.ɵruntime.start();
140
140
  fake.emitTurn({
141
141
  userText: "hi",
142
142
  conversationKey: "c1",
@@ -25,6 +25,8 @@ const EMOJI_PLATFORMS = new Set([
25
25
  "slack",
26
26
  "discord",
27
27
  "telegram",
28
+ "teams",
29
+ "whatsapp",
28
30
  ]);
29
31
  function isEmojiPlatform(platform) {
30
32
  return EMOJI_PLATFORMS.has(platform);
@@ -77,16 +79,18 @@ export function createChannel(opts) {
77
79
  (!cfg.identity && cfg.transcripts)) {
78
80
  throw new Error("createChannel: `identity` and `transcripts` must be configured together.");
79
81
  }
80
- // Adapters can be supplied up front or added later via `channel.addAdapter`
81
- // (before `start()`). The runtime uses the latter to attach Channel delivery.
82
+ // Adapters can be supplied up front or added later via
83
+ // `channel.ɵruntime.addAdapter` (before `channel.ɵruntime.start()`). The
84
+ // runtime uses the latter to attach Channel delivery.
82
85
  const adapters = [...(opts.adapters ?? [])];
83
86
  assertExclusive(adapters);
84
87
  let started = false;
85
88
  // Backend, transcripts, telemetry, the action registry, and component
86
- // registration are resolved in `start()` — not at construction — so an
87
- // adapter added via `addAdapter` after `createChannel` can still supply the
88
- // persistence backend (see `resolveBackend`). Nothing reads these before the
89
- // first event, which can only arrive after `start()`.
89
+ // registration are resolved in `ɵruntime.start()` — not at construction —
90
+ // so an adapter added via `ɵruntime.addAdapter` after `createChannel` can
91
+ // still supply the persistence backend (see `resolveBackend`). Nothing
92
+ // reads these before the first event, which can only arrive after
93
+ // `ɵruntime.start()`.
90
94
  let backend;
91
95
  let transcripts;
92
96
  let registry;
@@ -121,7 +125,7 @@ export function createChannel(opts) {
121
125
  let toolDescriptors = toAgentToolDescriptors([...toolMap.values()]);
122
126
  function makeThread(adapter, replyTarget, conversationKey, extras) {
123
127
  if (!backend || !registry || !telemetry) {
124
- throw new Error("channel not started: call channel.start() before handling events");
128
+ throw new Error("channel not started: the runner must start the channel (channel.ɵruntime.start()) before handling events");
125
129
  }
126
130
  const deps = {
127
131
  adapter,
@@ -326,10 +330,15 @@ export function createChannel(opts) {
326
330
  await h({ thread, user: evt.user });
327
331
  },
328
332
  async onReaction(evt) {
329
- // Only normalize when the adapter's platform is one the emoji table
333
+ // Normalize by the reaction's SOURCE platform: direct adapters omit
334
+ // `evt.platform`, so it falls back to `adapter.platform`; the managed
335
+ // path (adapter.platform === "intelligence") sets `evt.platform` to the
336
+ // originating provider (e.g. "teams"/"slack") so central normalization
337
+ // still runs. Only normalize when that platform is one the emoji table
330
338
  // knows; otherwise the raw token passes through unchanged.
331
- const normalized = isEmojiPlatform(adapter.platform)
332
- ? normalizeEmoji(evt.rawEmoji, adapter.platform)
339
+ const sourcePlatform = evt.platform ?? adapter.platform;
340
+ const normalized = isEmojiPlatform(sourcePlatform)
341
+ ? normalizeEmoji(evt.rawEmoji, sourcePlatform)
333
342
  : undefined;
334
343
  const value = normalized ?? evt.rawEmoji;
335
344
  const thread = makeThread(adapter, evt.replyTarget, evt.conversationKey);
@@ -412,16 +421,133 @@ export function createChannel(opts) {
412
421
  },
413
422
  get transcripts() {
414
423
  if (!transcripts) {
415
- throw new Error("channel.transcripts is available after channel.start()");
424
+ throw new Error("channel.transcripts is available after the runner starts the channel (channel.ɵruntime.start())");
416
425
  }
417
426
  return transcripts;
418
427
  },
419
- addAdapter(adapter) {
420
- if (started) {
421
- throw new Error("channel.addAdapter must be called before channel.start()");
422
- }
423
- assertExclusive([...adapters, adapter]);
424
- adapters.push(adapter);
428
+ ɵruntime: {
429
+ addAdapter(adapter) {
430
+ if (started) {
431
+ throw new Error("channel.ɵruntime.addAdapter must be called before channel.ɵruntime.start()");
432
+ }
433
+ assertExclusive([...adapters, adapter]);
434
+ adapters.push(adapter);
435
+ },
436
+ async start() {
437
+ // Idempotent: a second start() must not re-resolve the backend and
438
+ // rebuild Transcripts/Telemetry/ActionRegistry or re-call adapter.start()
439
+ // — with a MemoryStore that wipes all lock/dedup/transcript/action state,
440
+ // and real adapters would connect/port-bind twice.
441
+ if (started)
442
+ return;
443
+ started = true;
444
+ assertExclusive(adapters);
445
+ // Resolve persistence now that all adapters (including any attached via
446
+ // addAdapter) are known, then build the transcript store, action
447
+ // registry, and register components against it.
448
+ backend = resolveBackend(cfg.adapter, adapters);
449
+ transcripts = new Transcripts(backend, cfg.transcripts ?? {});
450
+ const tel = new ChannelTelemetry({
451
+ backend,
452
+ packageName: pkg.name,
453
+ packageVersion: pkg.version,
454
+ });
455
+ telemetry = tel;
456
+ const registryInstance = new ActionRegistry({
457
+ store: opts.actionStore ?? kvActionStore(backend),
458
+ });
459
+ registry = registryInstance;
460
+ for (const c of opts.components ?? []) {
461
+ if (!c.name) {
462
+ console.warn("[channel] createChannel: skipping anonymous component — give it a name to enable durable actions after restart.");
463
+ continue;
464
+ }
465
+ registryInstance.registerComponent(c.name, c);
466
+ }
467
+ toolDescriptors = toAgentToolDescriptors([...toolMap.values()]);
468
+ tel.capture("oss.channel.configured", {
469
+ platforms: adapters.map((a) => normalizePlatform(a.platform)),
470
+ adapterCount: adapters.length,
471
+ store: storeKind(backend),
472
+ hasComponents: (opts.components?.length ?? 0) > 0,
473
+ componentsCount: opts.components?.length ?? 0,
474
+ toolsCount: toolMap.size,
475
+ commandsCount: commandHandlers.size,
476
+ contextCount: context.length,
477
+ transcripts: !!cfg.transcripts,
478
+ identity: !!cfg.identity,
479
+ });
480
+ // Isolate per-adapter startup failures: one adapter rejecting (e.g.
481
+ // Telegram's setMyCommands rejecting a hyphenated command name, a revoked
482
+ // token, a port already in use) must NOT crash the channel or prevent the
483
+ // other adapters from starting. Log + degrade, never throw.
484
+ const startResults = await Promise.allSettled(adapters.map((a) => a.start(makeSink(a), { channelName: opts.name })));
485
+ const startedPlatforms = [];
486
+ const failedPlatforms = [];
487
+ startResults.forEach((r, i) => {
488
+ const rawPlatform = adapters[i].platform;
489
+ // Raw label for the human-facing log; normalized label for telemetry.
490
+ const platform = normalizePlatform(rawPlatform);
491
+ if (r.status === "rejected") {
492
+ failedPlatforms.push(platform);
493
+ console.error(`[channel] adapter "${rawPlatform}" failed to start:`, r.reason);
494
+ tel.capture("oss.channel.start_failed", {
495
+ platform,
496
+ errorClass: errorClass(r.reason),
497
+ });
498
+ }
499
+ else {
500
+ startedPlatforms.push(platform);
501
+ }
502
+ });
503
+ if (startedPlatforms.length > 0) {
504
+ tel.capture("oss.channel.started", {
505
+ platforms: startedPlatforms,
506
+ startedCount: startedPlatforms.length,
507
+ failedCount: failedPlatforms.length,
508
+ hasMentionHandler: mentionHandlers.length > 0,
509
+ hasMessageHandler: messageHandlers.length > 0,
510
+ interruptHandlers: interruptHandlers.size,
511
+ commandsCount: commandHandlers.size,
512
+ toolsCount: toolMap.size,
513
+ });
514
+ }
515
+ // A channel that has adapters but where NONE started is dead — surface an
516
+ // error so the runtime reports status "error", not a false "online". A
517
+ // PARTIAL start (>=1 adapter live) still counts as started. Reset the
518
+ // `started` guard so a caller can retry after fixing the misconfiguration.
519
+ if (adapters.length > 0 && startedPlatforms.length === 0) {
520
+ started = false;
521
+ throw new Error(`channel "${opts.name ?? "(unnamed)"}" failed to start: all ${failedPlatforms.length} adapter(s) failed to connect (${failedPlatforms.join(", ")}) — see the logged errors above`);
522
+ }
523
+ // Hand declared commands to adapters that register them up front (e.g.
524
+ // Discord); adapters without `registerCommands` are skipped. Per-adapter
525
+ // failures are isolated the same way as start().
526
+ const commandSpecs = [...commandHandlers.values()].map(toCommandSpec);
527
+ if (commandSpecs.length > 0) {
528
+ const registerResults = await Promise.allSettled(adapters.map((a) => a.registerCommands?.(commandSpecs)));
529
+ registerResults.forEach((r, i) => {
530
+ if (r.status === "rejected") {
531
+ console.error(`[channel] adapter "${adapters[i].platform}" failed to register commands:`, r.reason);
532
+ }
533
+ });
534
+ }
535
+ },
536
+ async stop() {
537
+ // Clear the started flag so a later start() is a real restart (re-resolve
538
+ // backend, rebuild components, reconnect adapters) rather than a silent
539
+ // no-op. The idempotency guard in start() only exists to block a DOUBLE
540
+ // start() while running — not a legitimate start→stop→start cycle.
541
+ started = false;
542
+ // Isolate per-adapter shutdown failures: one adapter's stop() rejecting
543
+ // must not prevent the others from being stopped.
544
+ const stopResults = await Promise.allSettled(adapters.map((a) => a.stop()));
545
+ stopResults.forEach((r, i) => {
546
+ if (r.status === "rejected") {
547
+ console.error(`[channel] adapter "${adapters[i].platform}" failed to stop:`, r.reason);
548
+ }
549
+ });
550
+ },
425
551
  },
426
552
  onMention(h) {
427
553
  // The public surface narrows `thread` to StatefulThread<TState>; the
@@ -476,113 +602,6 @@ export function createChannel(opts) {
476
602
  tool(t) {
477
603
  toolMap.set(t.name, t);
478
604
  },
479
- async start() {
480
- // Idempotent: a second start() must not re-resolve the backend and
481
- // rebuild Transcripts/Telemetry/ActionRegistry or re-call adapter.start()
482
- // — with a MemoryStore that wipes all lock/dedup/transcript/action state,
483
- // and real adapters would connect/port-bind twice.
484
- if (started)
485
- return;
486
- started = true;
487
- assertExclusive(adapters);
488
- // Resolve persistence now that all adapters (including any attached via
489
- // addAdapter) are known, then build the transcript store, action
490
- // registry, and register components against it.
491
- backend = resolveBackend(cfg.adapter, adapters);
492
- transcripts = new Transcripts(backend, cfg.transcripts ?? {});
493
- const tel = new ChannelTelemetry({
494
- backend,
495
- packageName: pkg.name,
496
- packageVersion: pkg.version,
497
- });
498
- telemetry = tel;
499
- const registryInstance = new ActionRegistry({
500
- store: opts.actionStore ?? kvActionStore(backend),
501
- });
502
- registry = registryInstance;
503
- for (const c of opts.components ?? []) {
504
- if (!c.name) {
505
- console.warn("[channel] createChannel: skipping anonymous component — give it a name to enable durable actions after restart.");
506
- continue;
507
- }
508
- registryInstance.registerComponent(c.name, c);
509
- }
510
- toolDescriptors = toAgentToolDescriptors([...toolMap.values()]);
511
- tel.capture("oss.channel.configured", {
512
- platforms: adapters.map((a) => normalizePlatform(a.platform)),
513
- adapterCount: adapters.length,
514
- store: storeKind(backend),
515
- hasComponents: (opts.components?.length ?? 0) > 0,
516
- componentsCount: opts.components?.length ?? 0,
517
- toolsCount: toolMap.size,
518
- commandsCount: commandHandlers.size,
519
- contextCount: context.length,
520
- transcripts: !!cfg.transcripts,
521
- identity: !!cfg.identity,
522
- });
523
- // Isolate per-adapter startup failures: one adapter rejecting (e.g.
524
- // Telegram's setMyCommands rejecting a hyphenated command name, a revoked
525
- // token, a port already in use) must NOT crash the channel or prevent the
526
- // other adapters from starting. Log + degrade, never throw.
527
- const startResults = await Promise.allSettled(adapters.map((a) => a.start(makeSink(a), { channelName: opts.name })));
528
- const startedPlatforms = [];
529
- const failedPlatforms = [];
530
- startResults.forEach((r, i) => {
531
- const rawPlatform = adapters[i].platform;
532
- // Raw label for the human-facing log; normalized label for telemetry.
533
- const platform = normalizePlatform(rawPlatform);
534
- if (r.status === "rejected") {
535
- failedPlatforms.push(platform);
536
- console.error(`[channel] adapter "${rawPlatform}" failed to start:`, r.reason);
537
- tel.capture("oss.channel.start_failed", {
538
- platform,
539
- errorClass: errorClass(r.reason),
540
- });
541
- }
542
- else {
543
- startedPlatforms.push(platform);
544
- }
545
- });
546
- if (startedPlatforms.length > 0) {
547
- tel.capture("oss.channel.started", {
548
- platforms: startedPlatforms,
549
- startedCount: startedPlatforms.length,
550
- failedCount: failedPlatforms.length,
551
- hasMentionHandler: mentionHandlers.length > 0,
552
- hasMessageHandler: messageHandlers.length > 0,
553
- interruptHandlers: interruptHandlers.size,
554
- commandsCount: commandHandlers.size,
555
- toolsCount: toolMap.size,
556
- });
557
- }
558
- // Hand declared commands to adapters that register them up front (e.g.
559
- // Discord); adapters without `registerCommands` are skipped. Per-adapter
560
- // failures are isolated the same way as start().
561
- const commandSpecs = [...commandHandlers.values()].map(toCommandSpec);
562
- if (commandSpecs.length > 0) {
563
- const registerResults = await Promise.allSettled(adapters.map((a) => a.registerCommands?.(commandSpecs)));
564
- registerResults.forEach((r, i) => {
565
- if (r.status === "rejected") {
566
- console.error(`[channel] adapter "${adapters[i].platform}" failed to register commands:`, r.reason);
567
- }
568
- });
569
- }
570
- },
571
- async stop() {
572
- // Clear the started flag so a later start() is a real restart (re-resolve
573
- // backend, rebuild components, reconnect adapters) rather than a silent
574
- // no-op. The idempotency guard in start() only exists to block a DOUBLE
575
- // start() while running — not a legitimate start→stop→start cycle.
576
- started = false;
577
- // Isolate per-adapter shutdown failures: one adapter's stop() rejecting
578
- // must not prevent the others from being stopped.
579
- const stopResults = await Promise.allSettled(adapters.map((a) => a.stop()));
580
- stopResults.forEach((r, i) => {
581
- if (r.status === "rejected") {
582
- console.error(`[channel] adapter "${adapters[i].platform}" failed to stop:`, r.reason);
583
- }
584
- });
585
- },
586
605
  };
587
606
  return channel;
588
607
  }