@copilotkit/channels-core 0.4.1-canary.1785366148 → 0.4.1-canary.1785375021

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.
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=canonical-run-loop.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"canonical-run-loop.test.d.ts","sourceRoot":"","sources":["../src/canonical-run-loop.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,393 @@
1
+ import { EventType } from "@ag-ui/client";
2
+ import { expect, test } from "vitest";
3
+ import { z } from "zod";
4
+ import { runAgentLoop } from "./run-loop.js";
5
+ import { FakeAgent } from "./testing/fake-agent.js";
6
+ const canonicalRun = {
7
+ threadId: "canonical-thread",
8
+ runId: "canonical-run",
9
+ };
10
+ function lifecycleBatch(runId, middle) {
11
+ return [
12
+ {
13
+ type: EventType.RUN_STARTED,
14
+ threadId: "inner-thread",
15
+ runId,
16
+ },
17
+ ...middle,
18
+ {
19
+ type: EventType.RUN_FINISHED,
20
+ threadId: "inner-thread",
21
+ runId,
22
+ },
23
+ ];
24
+ }
25
+ function textEvent(messageId, text) {
26
+ return {
27
+ type: EventType.TEXT_MESSAGE_CONTENT,
28
+ messageId,
29
+ delta: text,
30
+ };
31
+ }
32
+ async function emitBatch(subscriber, agent, runId, middle) {
33
+ const input = {
34
+ threadId: agent.threadId,
35
+ runId,
36
+ messages: agent.messages,
37
+ state: agent.state,
38
+ tools: [],
39
+ context: [],
40
+ forwardedProps: {},
41
+ };
42
+ const params = {
43
+ messages: agent.messages,
44
+ state: agent.state,
45
+ agent,
46
+ input,
47
+ };
48
+ for (const event of lifecycleBatch(runId, middle)) {
49
+ await subscriber.onEvent?.({ ...params, event });
50
+ switch (event.type) {
51
+ case EventType.RUN_STARTED:
52
+ await subscriber.onRunStartedEvent?.({ ...params, event });
53
+ break;
54
+ case EventType.RUN_FINISHED:
55
+ await subscriber.onRunFinishedEvent?.({
56
+ ...params,
57
+ event,
58
+ outcome: "success",
59
+ });
60
+ break;
61
+ case EventType.RUN_ERROR:
62
+ await subscriber.onRunErrorEvent?.({ ...params, event });
63
+ break;
64
+ case EventType.TEXT_MESSAGE_CONTENT:
65
+ await subscriber.onTextMessageContentEvent?.({
66
+ ...params,
67
+ event,
68
+ textMessageBuffer: event.delta,
69
+ });
70
+ break;
71
+ case EventType.TOOL_CALL_END:
72
+ await subscriber.onToolCallEndEvent?.({
73
+ ...params,
74
+ event,
75
+ toolCallName: "echo",
76
+ toolCallArgs: { value: "ok" },
77
+ });
78
+ break;
79
+ }
80
+ }
81
+ }
82
+ function setupRenderer(options = {}) {
83
+ const renderedEvents = [];
84
+ const renderedFinishMetadata = [];
85
+ const toolCalls = [];
86
+ const subscriber = {
87
+ onRunStartedEvent: ({ event }) => {
88
+ renderedEvents.push(event);
89
+ },
90
+ onRunFinishedEvent: ({ event }) => {
91
+ renderedEvents.push(event);
92
+ renderedFinishMetadata.push(event.metadata);
93
+ if (options.failOnFinish) {
94
+ throw new Error("renderer finalization failed");
95
+ }
96
+ },
97
+ onRunErrorEvent: ({ event }) => {
98
+ renderedEvents.push(event);
99
+ },
100
+ onTextMessageContentEvent: ({ event }) => {
101
+ renderedEvents.push(event);
102
+ if (options.failOnContent) {
103
+ throw new Error("renderer failed");
104
+ }
105
+ },
106
+ onToolCallEndEvent: ({ event, toolCallName, toolCallArgs }) => {
107
+ toolCalls.push({
108
+ toolCallId: event.toolCallId,
109
+ toolCallName,
110
+ toolCallArgs,
111
+ });
112
+ },
113
+ };
114
+ return {
115
+ renderer: {
116
+ subscriber,
117
+ markInterrupted: async () => { },
118
+ getCapturedToolCalls: () => toolCalls,
119
+ getPendingInterrupt: () => undefined,
120
+ clearPendingInterrupt: () => { },
121
+ },
122
+ renderedEvents,
123
+ renderedFinishMetadata,
124
+ };
125
+ }
126
+ test("managed runAgentLoop emits one canonical lifecycle and shares stamped events with ingestion and rendering", async () => {
127
+ let agent;
128
+ agent = new FakeAgent([
129
+ (subscriber) => emitBatch(subscriber, agent, "inner-run-1", [
130
+ textEvent("message-1", "first"),
131
+ {
132
+ type: EventType.TOOL_CALL_END,
133
+ toolCallId: "tool-call-1",
134
+ },
135
+ ]),
136
+ (subscriber) => emitBatch(subscriber, agent, "inner-run-2", [
137
+ textEvent("message-2", "second"),
138
+ ]),
139
+ ]);
140
+ const { renderer, renderedEvents } = setupRenderer();
141
+ const ingestedEvents = [];
142
+ const echo = {
143
+ name: "echo",
144
+ description: "Return the value.",
145
+ parameters: z.object({ value: z.string() }),
146
+ handler: ({ value }) => value,
147
+ };
148
+ const result = await runAgentLoop({
149
+ agent,
150
+ renderer,
151
+ tools: new Map([["echo", echo]]),
152
+ toolDescriptors: [],
153
+ context: [],
154
+ makeToolCtx: () => {
155
+ throw new Error("tool context is not used by this test");
156
+ },
157
+ subscriber: {
158
+ onEvent: ({ event }) => {
159
+ ingestedEvents.push(event);
160
+ },
161
+ },
162
+ canonicalRun,
163
+ });
164
+ const lifecycleTypes = ingestedEvents
165
+ .filter(({ type }) => type === EventType.RUN_STARTED ||
166
+ type === EventType.RUN_FINISHED ||
167
+ type === EventType.RUN_ERROR)
168
+ .map(({ type }) => type);
169
+ const ingestedContent = ingestedEvents.filter(({ type }) => type === EventType.TEXT_MESSAGE_CONTENT);
170
+ const renderedContent = renderedEvents.filter(({ type }) => type === EventType.TEXT_MESSAGE_CONTENT);
171
+ expect(result).toEqual({ iterations: 2, interrupted: false });
172
+ expect(lifecycleTypes).toEqual([
173
+ EventType.RUN_STARTED,
174
+ EventType.RUN_FINISHED,
175
+ ]);
176
+ expect(renderedEvents
177
+ .filter(({ type }) => type === EventType.RUN_STARTED ||
178
+ type === EventType.RUN_FINISHED ||
179
+ type === EventType.RUN_ERROR)
180
+ .map(({ type }) => type)).toEqual([EventType.RUN_STARTED, EventType.RUN_FINISHED]);
181
+ expect(ingestedContent).toHaveLength(2);
182
+ expect(renderedContent).toHaveLength(2);
183
+ expect(renderedContent[0]).toBe(ingestedContent[0]);
184
+ expect(renderedContent[1]).toBe(ingestedContent[1]);
185
+ expect(ingestedEvents).toEqual(expect.arrayContaining([
186
+ expect.objectContaining(canonicalRun),
187
+ expect.objectContaining({
188
+ ...canonicalRun,
189
+ type: EventType.TEXT_MESSAGE_CONTENT,
190
+ messageId: "message-1",
191
+ }),
192
+ expect.objectContaining({
193
+ ...canonicalRun,
194
+ type: EventType.TEXT_MESSAGE_CONTENT,
195
+ messageId: "message-2",
196
+ }),
197
+ ]));
198
+ });
199
+ test("managed renderer failure is deferred until canonical RUN_FINISHED reaches ingestion", async () => {
200
+ let agent;
201
+ agent = new FakeAgent([
202
+ (subscriber) => emitBatch(subscriber, agent, "inner-run", [
203
+ textEvent("message-1", "hello"),
204
+ ]),
205
+ ]);
206
+ const { renderer } = setupRenderer({ failOnContent: true });
207
+ const ingestedByTypedCallback = [];
208
+ const ingestedLifecycle = [];
209
+ const result = await runAgentLoop({
210
+ agent,
211
+ renderer,
212
+ tools: new Map(),
213
+ toolDescriptors: [],
214
+ context: [],
215
+ makeToolCtx: () => {
216
+ throw new Error("no tool calls are expected");
217
+ },
218
+ subscriber: {
219
+ onEvent: ({ event }) => {
220
+ if (event.type === EventType.RUN_STARTED ||
221
+ event.type === EventType.RUN_FINISHED ||
222
+ event.type === EventType.RUN_ERROR) {
223
+ ingestedLifecycle.push(event);
224
+ }
225
+ },
226
+ onTextMessageContentEvent: ({ event }) => {
227
+ ingestedByTypedCallback.push(event);
228
+ },
229
+ },
230
+ canonicalRun,
231
+ });
232
+ expect(result).toMatchObject({
233
+ iterations: 1,
234
+ interrupted: false,
235
+ deliveryError: { message: "renderer failed" },
236
+ });
237
+ expect(ingestedByTypedCallback).toEqual([
238
+ expect.objectContaining({
239
+ ...canonicalRun,
240
+ type: EventType.TEXT_MESSAGE_CONTENT,
241
+ messageId: "message-1",
242
+ }),
243
+ ]);
244
+ expect(ingestedLifecycle.map(({ type }) => type)).toEqual([
245
+ EventType.RUN_STARTED,
246
+ EventType.RUN_FINISHED,
247
+ ]);
248
+ });
249
+ test("managed renderer failure freezes later rendering while canonical ingestion finishes", async () => {
250
+ let agent;
251
+ agent = new FakeAgent([
252
+ (subscriber) => emitBatch(subscriber, agent, "inner-run", [
253
+ textEvent("message-1", "first"),
254
+ textEvent("message-2", "second"),
255
+ ]),
256
+ ]);
257
+ const { renderer, renderedEvents } = setupRenderer({
258
+ failOnContent: true,
259
+ });
260
+ const ingestedEvents = [];
261
+ const result = await runAgentLoop({
262
+ agent,
263
+ renderer,
264
+ tools: new Map(),
265
+ toolDescriptors: [],
266
+ context: [],
267
+ makeToolCtx: () => {
268
+ throw new Error("no tool calls are expected");
269
+ },
270
+ subscriber: {
271
+ onEvent: ({ event }) => {
272
+ ingestedEvents.push(event);
273
+ },
274
+ },
275
+ canonicalRun,
276
+ });
277
+ expect(result).toMatchObject({
278
+ deliveryError: { message: "renderer failed" },
279
+ });
280
+ expect(renderedEvents.map(({ type, ...event }) => ({
281
+ type,
282
+ messageId: "messageId" in event ? event.messageId : undefined,
283
+ }))).toEqual([
284
+ { type: EventType.RUN_STARTED, messageId: undefined },
285
+ { type: EventType.TEXT_MESSAGE_CONTENT, messageId: "message-1" },
286
+ ]);
287
+ expect(ingestedEvents
288
+ .filter(({ type }) => type === EventType.TEXT_MESSAGE_CONTENT ||
289
+ type === EventType.RUN_FINISHED)
290
+ .map(({ type, ...event }) => ({
291
+ type,
292
+ messageId: "messageId" in event ? event.messageId : undefined,
293
+ }))).toEqual([
294
+ { type: EventType.TEXT_MESSAGE_CONTENT, messageId: "message-1" },
295
+ { type: EventType.TEXT_MESSAGE_CONTENT, messageId: "message-2" },
296
+ { type: EventType.RUN_FINISHED, messageId: undefined },
297
+ ]);
298
+ });
299
+ test("inner RUN_ERROR becomes one canonical outer RUN_ERROR", async () => {
300
+ let agent;
301
+ agent = new FakeAgent([
302
+ (subscriber) => emitBatch(subscriber, agent, "inner-run", [
303
+ {
304
+ type: EventType.RUN_ERROR,
305
+ message: "inner agent failed",
306
+ code: "INNER_FAILED",
307
+ },
308
+ ]),
309
+ ]);
310
+ const { renderer, renderedEvents } = setupRenderer();
311
+ const ingestedEvents = [];
312
+ await expect(runAgentLoop({
313
+ agent,
314
+ renderer,
315
+ tools: new Map(),
316
+ toolDescriptors: [],
317
+ context: [],
318
+ makeToolCtx: () => {
319
+ throw new Error("no tool calls are expected");
320
+ },
321
+ subscriber: {
322
+ onEvent: ({ event }) => {
323
+ ingestedEvents.push(event);
324
+ },
325
+ },
326
+ canonicalRun,
327
+ })).rejects.toMatchObject({
328
+ message: "inner agent failed",
329
+ code: "INNER_FAILED",
330
+ });
331
+ const expectedLifecycle = [
332
+ expect.objectContaining({
333
+ ...canonicalRun,
334
+ type: EventType.RUN_STARTED,
335
+ }),
336
+ expect.objectContaining({
337
+ ...canonicalRun,
338
+ type: EventType.RUN_ERROR,
339
+ message: "inner agent failed",
340
+ code: "INNER_FAILED",
341
+ }),
342
+ ];
343
+ expect(ingestedEvents.filter(({ type }) => type === EventType.RUN_STARTED ||
344
+ type === EventType.RUN_FINISHED ||
345
+ type === EventType.RUN_ERROR)).toEqual(expectedLifecycle);
346
+ expect(renderedEvents.filter(({ type }) => type === EventType.RUN_STARTED ||
347
+ type === EventType.RUN_FINISHED ||
348
+ type === EventType.RUN_ERROR)).toEqual(expectedLifecycle);
349
+ });
350
+ test("managed renderer finalization sees runner metadata without replacing canonical RUN_FINISHED", async () => {
351
+ let agent;
352
+ agent = new FakeAgent([
353
+ (subscriber) => emitBatch(subscriber, agent, "inner-run", []),
354
+ ]);
355
+ const { renderer, renderedFinishMetadata } = setupRenderer({
356
+ failOnFinish: true,
357
+ });
358
+ const ingestedEvents = [];
359
+ const runnerMetadata = {
360
+ cpki_event_id: "runner-event-finished",
361
+ cpki_event_seq: 1,
362
+ };
363
+ const result = await runAgentLoop({
364
+ agent,
365
+ renderer,
366
+ tools: new Map(),
367
+ toolDescriptors: [],
368
+ context: [],
369
+ makeToolCtx: () => {
370
+ throw new Error("no tool calls are expected");
371
+ },
372
+ subscriber: {
373
+ onEvent: ({ event }) => {
374
+ if (event.type === EventType.RUN_FINISHED) {
375
+ event.metadata = runnerMetadata;
376
+ }
377
+ ingestedEvents.push(event);
378
+ },
379
+ },
380
+ canonicalRun,
381
+ });
382
+ expect(result).toMatchObject({
383
+ iterations: 1,
384
+ interrupted: false,
385
+ deliveryError: { message: "renderer finalization failed" },
386
+ });
387
+ expect(ingestedEvents
388
+ .filter(({ type }) => type === EventType.RUN_STARTED ||
389
+ type === EventType.RUN_FINISHED ||
390
+ type === EventType.RUN_ERROR)
391
+ .map(({ type }) => type)).toEqual([EventType.RUN_STARTED, EventType.RUN_FINISHED]);
392
+ expect(renderedFinishMetadata).toEqual([runnerMetadata]);
393
+ });
package/dist/codec.d.ts CHANGED
@@ -4,13 +4,12 @@ import type { ChannelNode } from "@copilotkit/channels-ui";
4
4
  * the Intelligence side. It exists so platform *semantics* (how to render IR to
5
5
  * a native payload, and — later — how to normalize a native event to the
6
6
  * neutral ingress shape) live in ONE place, instead of being duplicated between
7
- * a credentialed local adapter (Bolt/discord.js) and the Connector Outbox /
8
- * webhook ingress.
7
+ * a credentialed local adapter and the Gateway-owned live delivery session.
9
8
  *
10
9
  * Only the two creds/connection-bound concerns stay per-side: the transport
11
10
  * (who holds the platform connection) and the credentialed send. The codec
12
11
  * excludes both — `renderEgress` is pure (IR → native payload); the actual
13
- * send happens in the Connector Outbox with Intelligence-owned credentials.
12
+ * send happens in the Gateway with Intelligence-owned credentials.
14
13
  *
15
14
  * TODO(OSS-363): add `normalizeIngress(raw): NeutralEvent` once the pure Slack
16
15
  * ingress mapping (mention stripping, stable event-id derivation, real-user
@@ -1 +1 @@
1
- {"version":3,"file":"codec.d.ts","sourceRoot":"","sources":["../src/codec.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,yBAAyB,CAAC;AAE3D;;;;;;;;;;;;;;;;;GAiBG;AACH,MAAM,WAAW,aAAa;IAC5B,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,yDAAyD;IACzD,YAAY,CAAC,EAAE,EAAE,WAAW,EAAE,GAAG,OAAO,CAAC;CAC1C"}
1
+ {"version":3,"file":"codec.d.ts","sourceRoot":"","sources":["../src/codec.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,yBAAyB,CAAC;AAE3D;;;;;;;;;;;;;;;;GAgBG;AACH,MAAM,WAAW,aAAa;IAC5B,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,yDAAyD;IACzD,YAAY,CAAC,EAAE,EAAE,WAAW,EAAE,GAAG,OAAO,CAAC;CAC1C"}
@@ -1 +1 @@
1
- {"version":3,"file":"create-channel.d.ts","sourceRoot":"","sources":["../src/create-channel.ts"],"names":[],"mappings":"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"}
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;AA8BhF,MAAM,MAAM,oBAAoB,GAAG,MAAM,GAAG,OAAO,CAAC;AAEpD;;;;;;;;;;;;;;;;;;;;GAoBG;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;;;;OAIG;IACH,QAAQ,CAAC,EAAE,eAAe,EAAE,CAAC;IAC7B;;;;;;;;;;;;;;OAcG;IACH,QAAQ,CAAC,EAAE,sBAAsB,CAAC;IAClC;;;;;;;OAOG;IACH,cAAc,CAAC,EAAE,OAAO,CAAC;IACzB,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;;;;OAIG;IACH,QAAQ,CAAC,cAAc,CAAC,EAAE,OAAO,CAAC;IAClC,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,kFAAkF;IAClF,WAAW,EAAE,WAAW,CAAC;IACzB;;;;;;OAMG;IACH,QAAQ,EAAE;QACR,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;QACvB,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;QACtB,UAAU,CAAC,OAAO,EAAE,eAAe,GAAG,IAAI,CAAC;KAC5C,CAAC;CACH;AA6DD,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,CAwpBtC"}
@@ -44,17 +44,19 @@ function msgFromTurn(turn) {
44
44
  deliveryId: turn.deliveryId,
45
45
  };
46
46
  }
47
+ /** Resolve the provider that produced an event without changing adapter identity. */
48
+ function ingressPlatform(adapter, event) {
49
+ return event.platform ?? adapter.platform;
50
+ }
47
51
  /**
48
- * Enforce V1 Intelligence Channel exclusivity: an Intelligence Channel adapter
49
- * (`intelligenceAdapter`) must be the only adapter on a Channel. Channel and direct delivery are
50
- * alternative modes on a Channel — Intelligence holds the platform creds, or
51
- * the runtime does, never both.
52
+ * Enforce managed Intelligence Channel exclusivity. Managed and direct
53
+ * delivery are alternative modes on a Channel: Intelligence holds provider
54
+ * credentials, or the runtime does, never both.
52
55
  */
53
56
  function assertExclusive(adapters) {
54
57
  if (adapters.some((a) => a.__intelligenceChannel) && adapters.length > 1) {
55
- throw new Error("intelligenceAdapter() must be the only adapter on a Channel — Channel and " +
56
- "direct delivery are alternative modes. Use intelligenceAdapter() OR " +
57
- "direct adapters (slack/discord/...), not both.");
58
+ throw new Error("The managed Intelligence adapter must be the only adapter on a Channel — " +
59
+ "managed and direct delivery are alternative modes.");
58
60
  }
59
61
  }
60
62
  /**
@@ -129,6 +131,7 @@ export function createChannel(opts) {
129
131
  }
130
132
  const deps = {
131
133
  adapter,
134
+ platform: extras?.platform,
132
135
  replyTarget,
133
136
  conversationKey,
134
137
  registry,
@@ -163,6 +166,7 @@ export function createChannel(opts) {
163
166
  const store = backend;
164
167
  return {
165
168
  async onTurn(turn) {
169
+ const platform = ingressPlatform(adapter, turn);
166
170
  const lockKey = `turn:${turn.conversationKey}`;
167
171
  const acquired = await store.lock.acquire(lockKey, {
168
172
  ttlMs: cfg.lockTtl ?? 60_000,
@@ -182,13 +186,13 @@ export function createChannel(opts) {
182
186
  // that throws still leaves its event marked seen — dedup drops duplicate DELIVERIES,
183
187
  // it is not retry-of-failed-turns.)
184
188
  if (turn.eventId && !adapter.skipIngressDedup) {
185
- const dupKey = `evt:${adapter.platform}:${turn.eventId}`;
189
+ const dupKey = `evt:${platform}:${turn.eventId}`;
186
190
  try {
187
191
  if (await store.dedup.seen(dupKey, cfg.dedupTtl ?? 300_000))
188
192
  return;
189
193
  }
190
194
  catch (err) {
191
- console.warn(`[channel] dedup check failed for ${adapter.platform}; processing without dedup`, err);
195
+ console.warn(`[channel] dedup check failed for ${platform}; processing without dedup`, err);
192
196
  }
193
197
  }
194
198
  // Resolve cross-platform identity key (if configured) and stamp it on
@@ -199,18 +203,18 @@ export function createChannel(opts) {
199
203
  if (cfg.identity) {
200
204
  try {
201
205
  const resolved = await cfg.identity({
202
- adapter: adapter.platform,
206
+ adapter: platform,
203
207
  author: turn.user ?? { id: "" },
204
208
  message: msgFromTurn(turn),
205
209
  });
206
210
  userKey = resolved ?? undefined;
207
211
  }
208
212
  catch (err) {
209
- console.warn(`[channel] identity resolution failed for ${adapter.platform}; continuing without userKey`, err);
213
+ console.warn(`[channel] identity resolution failed for ${platform}; continuing without userKey`, err);
210
214
  }
211
215
  }
212
216
  const message = { ...msgFromTurn(turn), userKey };
213
- const thread = makeThread(adapter, turn.replyTarget, turn.conversationKey, { userKey, message });
217
+ const thread = makeThread(adapter, turn.replyTarget, turn.conversationKey, { platform, userKey, message });
214
218
  // v1 routing: there is no turn `kind`, so prefer mention handlers; if
215
219
  // none are registered, fall back to message handlers. (The reference
216
220
  // example registers identical handlers on both, so this avoids
@@ -226,18 +230,19 @@ export function createChannel(opts) {
226
230
  }
227
231
  },
228
232
  async onInteraction(evt) {
233
+ const platform = ingressPlatform(adapter, evt);
229
234
  // Dedup guard: drop duplicate deliveries of the same event within the TTL window.
230
235
  if (evt.eventId && !adapter.skipIngressDedup) {
231
- const dupKey = `evt:${adapter.platform}:${evt.eventId}`;
236
+ const dupKey = `evt:${platform}:${evt.eventId}`;
232
237
  try {
233
238
  if (await store.dedup.seen(dupKey, cfg.dedupTtl ?? 300_000))
234
239
  return;
235
240
  }
236
241
  catch (err) {
237
- console.warn(`[channel] dedup check failed for ${adapter.platform}; processing without dedup`, err);
242
+ console.warn(`[channel] dedup check failed for ${platform}; processing without dedup`, err);
238
243
  }
239
244
  }
240
- const thread = makeThread(adapter, evt.replyTarget, evt.conversationKey);
245
+ const thread = makeThread(adapter, evt.replyTarget, evt.conversationKey, { platform });
241
246
  const user = evt.user ?? { id: "" };
242
247
  const ctx = {
243
248
  thread,
@@ -245,12 +250,12 @@ export function createChannel(opts) {
245
250
  text: "",
246
251
  user,
247
252
  ref: evt.messageRef ?? { id: "" },
248
- platform: adapter.platform,
253
+ platform,
249
254
  },
250
255
  action: { id: evt.id, value: evt.value },
251
256
  values: {},
252
257
  user,
253
- platform: adapter.platform,
258
+ platform,
254
259
  };
255
260
  const openModal = makeOpenModal(adapter, evt.replyTarget, evt.triggerId);
256
261
  if (openModal)
@@ -284,21 +289,22 @@ export function createChannel(opts) {
284
289
  }
285
290
  },
286
291
  async onCommand(cmd) {
292
+ const platform = ingressPlatform(adapter, cmd);
287
293
  // Dedup guard: drop duplicate deliveries of the same event within the TTL window.
288
294
  if (cmd.eventId && !adapter.skipIngressDedup) {
289
- const dupKey = `evt:${adapter.platform}:${cmd.eventId}`;
295
+ const dupKey = `evt:${platform}:${cmd.eventId}`;
290
296
  try {
291
297
  if (await store.dedup.seen(dupKey, cfg.dedupTtl ?? 300_000))
292
298
  return;
293
299
  }
294
300
  catch (err) {
295
- console.warn(`[channel] dedup check failed for ${adapter.platform}; processing without dedup`, err);
301
+ console.warn(`[channel] dedup check failed for ${platform}; processing without dedup`, err);
296
302
  }
297
303
  }
298
304
  const command = commandHandlers.get(normalizeCommandName(cmd.command));
299
305
  if (!command)
300
306
  return; // unregistered command → skip
301
- const thread = makeThread(adapter, cmd.replyTarget, cmd.conversationKey);
307
+ const thread = makeThread(adapter, cmd.replyTarget, cmd.conversationKey, { platform });
302
308
  // Resolve typed options from any structured args the surface supplied
303
309
  // (e.g. Discord); text-only surfaces (Slack) leave `options` empty and
304
310
  // the handler reads `text`.
@@ -314,7 +320,7 @@ export function createChannel(opts) {
314
320
  text: cmd.text,
315
321
  options,
316
322
  user: cmd.user,
317
- platform: cmd.platform,
323
+ platform,
318
324
  };
319
325
  const openModal = makeOpenModal(adapter, cmd.replyTarget, cmd.triggerId);
320
326
  if (openModal)
@@ -325,23 +331,19 @@ export function createChannel(opts) {
325
331
  // The adapter has already applied its static defaults (greeting /
326
332
  // prompts) before emitting this, so handlers layer on top and never
327
333
  // race. Zero handlers → no-op.
328
- const thread = makeThread(adapter, evt.replyTarget, evt.conversationKey);
334
+ const thread = makeThread(adapter, evt.replyTarget, evt.conversationKey, { platform: ingressPlatform(adapter, evt) });
329
335
  for (const h of threadStartedHandlers)
330
336
  await h({ thread, user: evt.user });
331
337
  },
332
338
  async onReaction(evt) {
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
338
- // knows; otherwise the raw token passes through unchanged.
339
- const sourcePlatform = evt.platform ?? adapter.platform;
339
+ // Normalize by source platform, falling back to adapter identity for
340
+ // direct adapters that do not need per-event provider routing.
341
+ const sourcePlatform = ingressPlatform(adapter, evt);
340
342
  const normalized = isEmojiPlatform(sourcePlatform)
341
343
  ? normalizeEmoji(evt.rawEmoji, sourcePlatform)
342
344
  : undefined;
343
345
  const value = normalized ?? evt.rawEmoji;
344
- const thread = makeThread(adapter, evt.replyTarget, evt.conversationKey);
346
+ const thread = makeThread(adapter, evt.replyTarget, evt.conversationKey, { platform: sourcePlatform });
345
347
  // Prefer the adapter's update-capable ref; fall back to the bare id.
346
348
  const messageRef = evt.messageRef ?? { id: evt.messageId };
347
349
  const reactionEvt = {
@@ -383,7 +385,9 @@ export function createChannel(opts) {
383
385
  if (!handler)
384
386
  return; // unregistered → closes
385
387
  const thread = evt.conversationKey !== undefined && evt.replyTarget !== undefined
386
- ? makeThread(adapter, evt.replyTarget, evt.conversationKey)
388
+ ? makeThread(adapter, evt.replyTarget, evt.conversationKey, {
389
+ platform: evt.platform,
390
+ })
387
391
  : undefined;
388
392
  const result = await handler({
389
393
  callbackId: evt.callbackId,