@copilotkit/channels-core 0.5.0 → 0.5.1-canary.1785633429
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.
- package/README.md +35 -14
- package/dist/action-registry.d.ts +15 -3
- package/dist/action-registry.d.ts.map +1 -1
- package/dist/action-registry.js +61 -9
- package/dist/action-registry.test.js +107 -2
- package/dist/action-store.d.ts +23 -0
- package/dist/action-store.d.ts.map +1 -1
- package/dist/action-store.js +11 -0
- package/dist/canonical-run-loop.test.js +6 -1
- package/dist/channel-identity.test.d.ts +2 -0
- package/dist/channel-identity.test.d.ts.map +1 -0
- package/dist/channel-identity.test.js +297 -0
- package/dist/channel-memory.test.d.ts +2 -0
- package/dist/channel-memory.test.d.ts.map +1 -0
- package/dist/channel-memory.test.js +199 -0
- package/dist/commands.d.ts +5 -3
- package/dist/commands.d.ts.map +1 -1
- package/dist/create-channel.d.ts +30 -56
- package/dist/create-channel.d.ts.map +1 -1
- package/dist/create-channel.foundations.test.js +30 -7
- package/dist/create-channel.js +209 -72
- package/dist/create-channel.test.js +293 -66
- package/dist/hitl-continuation.test.d.ts +2 -0
- package/dist/hitl-continuation.test.d.ts.map +1 -0
- package/dist/hitl-continuation.test.js +393 -0
- package/dist/identity.d.ts +51 -0
- package/dist/identity.d.ts.map +1 -0
- package/dist/identity.js +56 -0
- package/dist/identity.test.d.ts +2 -0
- package/dist/identity.test.d.ts.map +1 -0
- package/dist/identity.test.js +109 -0
- package/dist/index.d.ts +10 -5
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +5 -1
- package/dist/managed-v1-await-choice-guard.test.js +4 -1
- package/dist/memory.d.ts +23 -0
- package/dist/memory.d.ts.map +1 -0
- package/dist/memory.js +25 -0
- package/dist/modal-submit.test.js +21 -6
- package/dist/open-modal.test.js +16 -4
- package/dist/platform-adapter.d.ts +29 -6
- package/dist/platform-adapter.d.ts.map +1 -1
- package/dist/platform-adapter.test.js +2 -0
- package/dist/reactions.test.js +49 -11
- package/dist/run-loop.test.js +30 -5
- package/dist/sanitize-agent-events.test.js +2 -0
- package/dist/source-platform.test.js +24 -9
- package/dist/state/kv-action-store.d.ts.map +1 -1
- package/dist/state/kv-action-store.js +1 -0
- package/dist/state/kv-action-store.test.js +11 -0
- package/dist/state/memory-store.d.ts +1 -0
- package/dist/state/memory-store.d.ts.map +1 -1
- package/dist/state/memory-store.js +9 -0
- package/dist/state/state-store.d.ts +2 -0
- package/dist/state/state-store.d.ts.map +1 -1
- package/dist/telemetry/create-channel-telemetry.test.js +4 -2
- package/dist/telemetry/e2e-telemetry.test.js +1 -0
- package/dist/telemetry/install-id.test.js +5 -0
- package/dist/testing/fake-adapter.d.ts +26 -9
- package/dist/testing/fake-adapter.d.ts.map +1 -1
- package/dist/testing/fake-adapter.js +65 -21
- package/dist/testing/state-store-conformance.d.ts.map +1 -1
- package/dist/testing/state-store-conformance.js +16 -0
- package/dist/thread-capabilities.test.js +29 -9
- package/dist/thread-ephemeral.test.js +8 -5
- package/dist/thread-promise-contract.test.js +4 -0
- package/dist/thread-reactions.test.js +8 -2
- package/dist/thread.d.ts +50 -7
- package/dist/thread.d.ts.map +1 -1
- package/dist/thread.js +163 -17
- package/dist/thread.test.js +4 -0
- package/dist/tools.d.ts +5 -3
- package/dist/tools.d.ts.map +1 -1
- package/dist/tools.js +2 -1
- package/dist/transcripts.d.ts +6 -13
- package/dist/transcripts.d.ts.map +1 -1
- package/dist/transcripts.js +12 -12
- package/dist/transcripts.test.js +28 -29
- package/dist/welcome.test.d.ts +2 -0
- package/dist/welcome.test.d.ts.map +1 -0
- package/dist/welcome.test.js +53 -0
- package/package.json +5 -5
|
@@ -13,7 +13,10 @@ const tick = () => new Promise((r) => setTimeout(r, 0));
|
|
|
13
13
|
* `ctx.action.value`.
|
|
14
14
|
*/
|
|
15
15
|
const __handlerTypeGuards = () => {
|
|
16
|
-
const channel = createChannel({
|
|
16
|
+
const channel = createChannel({
|
|
17
|
+
identifyUser: "platform",
|
|
18
|
+
adapters: [new FakeAdapter()],
|
|
19
|
+
});
|
|
17
20
|
channel.onInterrupt("ask", ({ payload }) => {
|
|
18
21
|
payload.question.toUpperCase();
|
|
19
22
|
// @ts-expect-error 'missing' is not on the payload type
|
|
@@ -112,13 +115,20 @@ function captureSessionAgents(fake) {
|
|
|
112
115
|
describe("createChannel", () => {
|
|
113
116
|
it("rejects activation when a message-capable adapter has no eligible handler", async () => {
|
|
114
117
|
const fake = new FakeAdapter({ messageEvents: true });
|
|
115
|
-
const channel = createChannel({
|
|
118
|
+
const channel = createChannel({
|
|
119
|
+
identifyUser: "platform",
|
|
120
|
+
name: "support",
|
|
121
|
+
adapters: [fake],
|
|
122
|
+
});
|
|
116
123
|
await expect(channel.ɵruntime.start()).rejects.toThrow('channel "support" must register onMention or onMessage');
|
|
117
124
|
expect(fake.started).toBe(false);
|
|
118
125
|
});
|
|
119
126
|
it("routes an explicit mention only to onMention", async () => {
|
|
120
127
|
const fake = new FakeAdapter();
|
|
121
|
-
const channel = createChannel({
|
|
128
|
+
const channel = createChannel({
|
|
129
|
+
identifyUser: "platform",
|
|
130
|
+
adapters: [fake],
|
|
131
|
+
});
|
|
122
132
|
const mentions = vi.fn();
|
|
123
133
|
const messages = vi.fn();
|
|
124
134
|
channel.onMention(mentions);
|
|
@@ -141,7 +151,10 @@ describe("createChannel", () => {
|
|
|
141
151
|
});
|
|
142
152
|
it("falls an explicit mention back to onMessage", async () => {
|
|
143
153
|
const fake = new FakeAdapter();
|
|
144
|
-
const channel = createChannel({
|
|
154
|
+
const channel = createChannel({
|
|
155
|
+
identifyUser: "platform",
|
|
156
|
+
adapters: [fake],
|
|
157
|
+
});
|
|
145
158
|
const messages = vi.fn();
|
|
146
159
|
channel.onMessage(messages);
|
|
147
160
|
await channel.ɵruntime.start();
|
|
@@ -172,7 +185,10 @@ describe("createChannel", () => {
|
|
|
172
185
|
});
|
|
173
186
|
it("routes an ordinary message only to onMessage", async () => {
|
|
174
187
|
const fake = new FakeAdapter();
|
|
175
|
-
const channel = createChannel({
|
|
188
|
+
const channel = createChannel({
|
|
189
|
+
identifyUser: "platform",
|
|
190
|
+
adapters: [fake],
|
|
191
|
+
});
|
|
176
192
|
const mentions = vi.fn();
|
|
177
193
|
const messages = vi.fn();
|
|
178
194
|
channel.onMention(mentions);
|
|
@@ -196,7 +212,11 @@ describe("createChannel", () => {
|
|
|
196
212
|
it("routes a mention to a handler that posts UI", async () => {
|
|
197
213
|
const fake = new FakeAdapter();
|
|
198
214
|
const agent = new FakeAgent();
|
|
199
|
-
const channel = createChannel({
|
|
215
|
+
const channel = createChannel({
|
|
216
|
+
identifyUser: "platform",
|
|
217
|
+
adapters: [fake],
|
|
218
|
+
agent: () => agent,
|
|
219
|
+
});
|
|
200
220
|
channel.onMention(async ({ thread }) => {
|
|
201
221
|
await thread.post(Section({ children: "hi" }));
|
|
202
222
|
});
|
|
@@ -211,7 +231,11 @@ describe("createChannel", () => {
|
|
|
211
231
|
it("calls renderer.finish() once after a turn's run-loop resolves", async () => {
|
|
212
232
|
const fake = new FakeAdapter();
|
|
213
233
|
const agent = new FakeAgent();
|
|
214
|
-
const channel = createChannel({
|
|
234
|
+
const channel = createChannel({
|
|
235
|
+
identifyUser: "platform",
|
|
236
|
+
adapters: [fake],
|
|
237
|
+
agent: () => agent,
|
|
238
|
+
});
|
|
215
239
|
channel.onMention(async ({ thread }) => {
|
|
216
240
|
await thread.runAgent();
|
|
217
241
|
});
|
|
@@ -225,7 +249,11 @@ describe("createChannel", () => {
|
|
|
225
249
|
const fake = new FakeAdapter();
|
|
226
250
|
const agent = new FakeAgent();
|
|
227
251
|
const added = captureAddedMessages(agent);
|
|
228
|
-
const channel = createChannel({
|
|
252
|
+
const channel = createChannel({
|
|
253
|
+
identifyUser: "platform",
|
|
254
|
+
adapters: [fake],
|
|
255
|
+
agent: () => agent,
|
|
256
|
+
});
|
|
229
257
|
channel.onMention(async ({ thread }) => {
|
|
230
258
|
await thread.runAgent();
|
|
231
259
|
});
|
|
@@ -248,7 +276,11 @@ describe("createChannel", () => {
|
|
|
248
276
|
const agent = new FakeAgent();
|
|
249
277
|
const added = captureAddedMessages(agent);
|
|
250
278
|
const runs = trackRunAgentCalls(agent);
|
|
251
|
-
const channel = createChannel({
|
|
279
|
+
const channel = createChannel({
|
|
280
|
+
identifyUser: "platform",
|
|
281
|
+
adapters: [fake],
|
|
282
|
+
agent: () => agent,
|
|
283
|
+
});
|
|
252
284
|
channel.onMention(async ({ thread }) => {
|
|
253
285
|
await thread.runAgent();
|
|
254
286
|
await thread.runAgent();
|
|
@@ -295,6 +327,7 @@ describe("createChannel", () => {
|
|
|
295
327
|
]);
|
|
296
328
|
const sessionAgents = captureSessionAgents(fake);
|
|
297
329
|
const channel = createChannel({
|
|
330
|
+
identifyUser: "platform",
|
|
298
331
|
adapters: [fake],
|
|
299
332
|
agent: () => agent,
|
|
300
333
|
tools: [
|
|
@@ -342,7 +375,11 @@ describe("createChannel", () => {
|
|
|
342
375
|
});
|
|
343
376
|
return session;
|
|
344
377
|
};
|
|
345
|
-
const channel = createChannel({
|
|
378
|
+
const channel = createChannel({
|
|
379
|
+
identifyUser: "platform",
|
|
380
|
+
adapters: [fake],
|
|
381
|
+
agent: () => agent,
|
|
382
|
+
});
|
|
346
383
|
channel.onMention(async ({ thread }) => {
|
|
347
384
|
await thread.runAgent();
|
|
348
385
|
});
|
|
@@ -377,7 +414,11 @@ describe("createChannel", () => {
|
|
|
377
414
|
});
|
|
378
415
|
return session;
|
|
379
416
|
};
|
|
380
|
-
const channel = createChannel({
|
|
417
|
+
const channel = createChannel({
|
|
418
|
+
identifyUser: "platform",
|
|
419
|
+
adapters: [fake],
|
|
420
|
+
agent: () => agent,
|
|
421
|
+
});
|
|
381
422
|
channel.onMention(async ({ thread, message }) => {
|
|
382
423
|
await thread.runAgent({ prompt: message.text });
|
|
383
424
|
});
|
|
@@ -395,7 +436,11 @@ describe("createChannel", () => {
|
|
|
395
436
|
const fake = new FakeAdapter();
|
|
396
437
|
const agent = new FakeAgent();
|
|
397
438
|
const added = captureAddedMessages(agent);
|
|
398
|
-
const channel = createChannel({
|
|
439
|
+
const channel = createChannel({
|
|
440
|
+
identifyUser: "platform",
|
|
441
|
+
adapters: [fake],
|
|
442
|
+
agent: () => agent,
|
|
443
|
+
});
|
|
399
444
|
const parts = [
|
|
400
445
|
{ type: "text", text: "look" },
|
|
401
446
|
{
|
|
@@ -424,7 +469,11 @@ describe("createChannel", () => {
|
|
|
424
469
|
const fake = new FakeAdapter();
|
|
425
470
|
const agent = new FakeAgent();
|
|
426
471
|
const added = captureAddedMessages(agent);
|
|
427
|
-
const channel = createChannel({
|
|
472
|
+
const channel = createChannel({
|
|
473
|
+
identifyUser: "platform",
|
|
474
|
+
adapters: [fake],
|
|
475
|
+
agent: () => agent,
|
|
476
|
+
});
|
|
428
477
|
const explicitParts = [
|
|
429
478
|
{ type: "text", text: "use this instead" },
|
|
430
479
|
{
|
|
@@ -453,7 +502,11 @@ describe("createChannel", () => {
|
|
|
453
502
|
it("dispatches a bound onClick handler on interaction", async () => {
|
|
454
503
|
const fake = new FakeAdapter();
|
|
455
504
|
const agent = new FakeAgent();
|
|
456
|
-
const channel = createChannel({
|
|
505
|
+
const channel = createChannel({
|
|
506
|
+
identifyUser: "platform",
|
|
507
|
+
adapters: [fake],
|
|
508
|
+
agent: () => agent,
|
|
509
|
+
});
|
|
457
510
|
let clicked = false;
|
|
458
511
|
channel.onMention(async ({ thread }) => {
|
|
459
512
|
await thread.post(Actions({
|
|
@@ -481,7 +534,11 @@ describe("createChannel", () => {
|
|
|
481
534
|
it("resolves a HITL awaitChoice with the element value when the event carries none (Telegram)", async () => {
|
|
482
535
|
const fake = new FakeAdapter();
|
|
483
536
|
const agent = new FakeAgent();
|
|
484
|
-
const channel = createChannel({
|
|
537
|
+
const channel = createChannel({
|
|
538
|
+
identifyUser: "platform",
|
|
539
|
+
adapters: [fake],
|
|
540
|
+
agent: () => agent,
|
|
541
|
+
});
|
|
485
542
|
let chosen;
|
|
486
543
|
channel.onMention(async ({ thread }) => {
|
|
487
544
|
chosen = await thread.awaitChoice(Actions({
|
|
@@ -525,6 +582,7 @@ describe("createChannel", () => {
|
|
|
525
582
|
};
|
|
526
583
|
});
|
|
527
584
|
const channel = createChannel({
|
|
585
|
+
identifyUser: "platform",
|
|
528
586
|
adapters: [fake],
|
|
529
587
|
agent: () => agent,
|
|
530
588
|
context: [{ description: "channel-level", value: "always here" }],
|
|
@@ -546,7 +604,11 @@ describe("createChannel", () => {
|
|
|
546
604
|
it("thread.postFile returns a capability-gated error when the adapter can't upload", async () => {
|
|
547
605
|
const fake = new FakeAdapter();
|
|
548
606
|
const agent = new FakeAgent();
|
|
549
|
-
const channel = createChannel({
|
|
607
|
+
const channel = createChannel({
|
|
608
|
+
identifyUser: "platform",
|
|
609
|
+
adapters: [fake],
|
|
610
|
+
agent: () => agent,
|
|
611
|
+
});
|
|
550
612
|
let result;
|
|
551
613
|
channel.onMention(async ({ thread }) => {
|
|
552
614
|
result = await thread.postFile({
|
|
@@ -565,11 +627,20 @@ describe("createChannel", () => {
|
|
|
565
627
|
it("thread.getMessages and thread.lookupUser surface the adapter's data", async () => {
|
|
566
628
|
const fake = new FakeAdapter();
|
|
567
629
|
fake.messages = [
|
|
568
|
-
{
|
|
630
|
+
{
|
|
631
|
+
user: { id: "u1", kind: "human", name: "Ada" },
|
|
632
|
+
text: "hi",
|
|
633
|
+
ts: "1",
|
|
634
|
+
isBot: false,
|
|
635
|
+
},
|
|
569
636
|
];
|
|
570
|
-
fake.user = { id: "u1", name: "Ada" };
|
|
637
|
+
fake.user = { id: "u1", kind: "human", name: "Ada" };
|
|
571
638
|
const agent = new FakeAgent();
|
|
572
|
-
const channel = createChannel({
|
|
639
|
+
const channel = createChannel({
|
|
640
|
+
identifyUser: "platform",
|
|
641
|
+
adapters: [fake],
|
|
642
|
+
agent: () => agent,
|
|
643
|
+
});
|
|
573
644
|
let history;
|
|
574
645
|
let resolved;
|
|
575
646
|
channel.onMention(async ({ thread }) => {
|
|
@@ -580,14 +651,23 @@ describe("createChannel", () => {
|
|
|
580
651
|
fake.emitTurn({ userText: "hi", conversationKey: "c1" });
|
|
581
652
|
await tick();
|
|
582
653
|
expect(history).toEqual([
|
|
583
|
-
{
|
|
654
|
+
{
|
|
655
|
+
user: { id: "u1", kind: "human", name: "Ada" },
|
|
656
|
+
text: "hi",
|
|
657
|
+
ts: "1",
|
|
658
|
+
isBot: false,
|
|
659
|
+
},
|
|
584
660
|
]);
|
|
585
|
-
expect(resolved).toEqual({ id: "u1", name: "Ada" });
|
|
661
|
+
expect(resolved).toEqual({ id: "u1", kind: "human", name: "Ada" });
|
|
586
662
|
});
|
|
587
663
|
it("resolves awaitChoice when a matching interaction arrives", async () => {
|
|
588
664
|
const fake = new FakeAdapter();
|
|
589
665
|
const agent = new FakeAgent();
|
|
590
|
-
const channel = createChannel({
|
|
666
|
+
const channel = createChannel({
|
|
667
|
+
identifyUser: "platform",
|
|
668
|
+
adapters: [fake],
|
|
669
|
+
agent: () => agent,
|
|
670
|
+
});
|
|
591
671
|
let choicePromise;
|
|
592
672
|
channel.onMention(async ({ thread }) => {
|
|
593
673
|
choicePromise = thread.awaitChoice(Actions({
|
|
@@ -622,6 +702,7 @@ describe("createChannel", () => {
|
|
|
622
702
|
const fake = new FakeAdapter();
|
|
623
703
|
const agent = new FakeAgent();
|
|
624
704
|
const channel = createChannel({
|
|
705
|
+
identifyUser: "platform",
|
|
625
706
|
adapters: [fake],
|
|
626
707
|
agent: () => agent,
|
|
627
708
|
store: { adapter: state, onLockConflict: "drop" },
|
|
@@ -654,6 +735,7 @@ describe("createChannel", () => {
|
|
|
654
735
|
const fake = new FakeAdapter();
|
|
655
736
|
const agent = new FakeAgent();
|
|
656
737
|
const channel = createChannel({
|
|
738
|
+
identifyUser: "platform",
|
|
657
739
|
adapters: [fake],
|
|
658
740
|
agent: () => agent,
|
|
659
741
|
store: { adapter: state, onLockConflict: "force" },
|
|
@@ -685,6 +767,7 @@ describe("createChannel", () => {
|
|
|
685
767
|
const gate = new Promise((r) => (release = r));
|
|
686
768
|
const fake = new FakeAdapter();
|
|
687
769
|
const channel = createChannel({
|
|
770
|
+
identifyUser: "platform",
|
|
688
771
|
adapters: [fake],
|
|
689
772
|
agent: () => new FakeAgent(),
|
|
690
773
|
store: { adapter: state },
|
|
@@ -716,6 +799,7 @@ describe("createChannel", () => {
|
|
|
716
799
|
const gate1 = new Promise((r) => (release1 = r));
|
|
717
800
|
const fake = new FakeAdapter();
|
|
718
801
|
const channel = createChannel({
|
|
802
|
+
identifyUser: "platform",
|
|
719
803
|
adapters: [fake],
|
|
720
804
|
agent: () => new FakeAgent(),
|
|
721
805
|
store: { adapter: state, concurrency: "serial" },
|
|
@@ -750,6 +834,7 @@ describe("createChannel", () => {
|
|
|
750
834
|
const gate = new Promise((r) => (release = r));
|
|
751
835
|
const fake = new FakeAdapter();
|
|
752
836
|
const channel = createChannel({
|
|
837
|
+
identifyUser: "platform",
|
|
753
838
|
adapters: [fake],
|
|
754
839
|
agent: () => new FakeAgent(),
|
|
755
840
|
store: { adapter: state, concurrency: "drop" },
|
|
@@ -778,6 +863,7 @@ describe("createChannel", () => {
|
|
|
778
863
|
const fake = new FakeAdapter();
|
|
779
864
|
const { agents: seen } = captureSessionAgents(fake);
|
|
780
865
|
const channel = createChannel({
|
|
866
|
+
identifyUser: "platform",
|
|
781
867
|
adapters: [fake],
|
|
782
868
|
agent: prototype, // singleton, not factory
|
|
783
869
|
store: { adapter: state },
|
|
@@ -814,6 +900,7 @@ describe("createChannel", () => {
|
|
|
814
900
|
bad.clone = () => bad;
|
|
815
901
|
const fake = new FakeAdapter();
|
|
816
902
|
const channel = createChannel({
|
|
903
|
+
identifyUser: "platform",
|
|
817
904
|
adapters: [fake],
|
|
818
905
|
agent: bad,
|
|
819
906
|
store: { adapter: state },
|
|
@@ -840,6 +927,7 @@ describe("createChannel", () => {
|
|
|
840
927
|
// every call. Turn concurrency is parallel by default, so without isolation
|
|
841
928
|
// both turns would run on `shared` and append into its one `messages` array.
|
|
842
929
|
const channel = createChannel({
|
|
930
|
+
identifyUser: "platform",
|
|
843
931
|
adapters: [fake],
|
|
844
932
|
agent: (threadId) => {
|
|
845
933
|
shared.threadId = threadId;
|
|
@@ -908,6 +996,7 @@ describe("createChannel", () => {
|
|
|
908
996
|
const agent = new StatefulAgent();
|
|
909
997
|
const fake = new FakeAdapter();
|
|
910
998
|
const channel = createChannel({
|
|
999
|
+
identifyUser: "platform",
|
|
911
1000
|
adapters: [fake],
|
|
912
1001
|
agent: () => agent,
|
|
913
1002
|
store: { adapter: state },
|
|
@@ -934,6 +1023,7 @@ describe("createChannel", () => {
|
|
|
934
1023
|
agent.runAgent = async (parameters, subscriber) => FakeAgent.prototype.runAgent.call(agent, parameters, subscriber);
|
|
935
1024
|
const fake = new FakeAdapter();
|
|
936
1025
|
const channel = createChannel({
|
|
1026
|
+
identifyUser: "platform",
|
|
937
1027
|
adapters: [fake],
|
|
938
1028
|
agent: () => agent,
|
|
939
1029
|
store: { adapter: state },
|
|
@@ -956,6 +1046,7 @@ describe("createChannel", () => {
|
|
|
956
1046
|
const fake = new FakeAdapter();
|
|
957
1047
|
const agent = new FakeAgent();
|
|
958
1048
|
const channel = createChannel({
|
|
1049
|
+
identifyUser: "platform",
|
|
959
1050
|
adapters: [fake],
|
|
960
1051
|
agent: () => agent,
|
|
961
1052
|
store: { adapter: state },
|
|
@@ -980,36 +1071,32 @@ describe("createChannel", () => {
|
|
|
980
1071
|
await sink.onTurn({ ...base, eventId: "E2" });
|
|
981
1072
|
expect(runs).toBe(2);
|
|
982
1073
|
});
|
|
983
|
-
it("
|
|
1074
|
+
it("configures transcripts without a second identity callback", async () => {
|
|
984
1075
|
const fake = new FakeAdapter();
|
|
985
|
-
|
|
986
|
-
adapters: [fake],
|
|
987
|
-
store: { identity: () => "key" },
|
|
988
|
-
})).toThrow("createChannel: `identity` and `transcripts` must be configured together.");
|
|
989
|
-
});
|
|
990
|
-
it("throws when transcripts is set without identity", () => {
|
|
991
|
-
const fake = new FakeAdapter();
|
|
992
|
-
expect(() => createChannel({
|
|
1076
|
+
const channel = createChannel({
|
|
993
1077
|
adapters: [fake],
|
|
1078
|
+
identifyUser: "platform",
|
|
994
1079
|
store: { transcripts: { maxPerUser: 100 } },
|
|
995
|
-
})
|
|
1080
|
+
});
|
|
1081
|
+
await channel.ɵruntime.start();
|
|
1082
|
+
expect(channel.transcripts).toBeDefined();
|
|
996
1083
|
});
|
|
997
|
-
it("stamps message.
|
|
1084
|
+
it("stamps message.user when identifyUser resolves an application user", async () => {
|
|
998
1085
|
const state = new MemoryStore();
|
|
999
1086
|
const fake = new FakeAdapter();
|
|
1000
1087
|
const agent = new FakeAgent();
|
|
1001
1088
|
const channel = createChannel({
|
|
1002
1089
|
adapters: [fake],
|
|
1003
1090
|
agent: () => agent,
|
|
1091
|
+
identifyUser: () => ({ id: "user@example.com", name: "Alice" }),
|
|
1004
1092
|
store: {
|
|
1005
1093
|
adapter: state,
|
|
1006
|
-
identity: () => "user@example.com",
|
|
1007
1094
|
transcripts: {},
|
|
1008
1095
|
},
|
|
1009
1096
|
});
|
|
1010
1097
|
let capturedKey;
|
|
1011
1098
|
channel.onMention(async ({ message }) => {
|
|
1012
|
-
capturedKey = message.
|
|
1099
|
+
capturedKey = message.user?.id;
|
|
1013
1100
|
});
|
|
1014
1101
|
await channel.ɵruntime.start();
|
|
1015
1102
|
const sink = fake.getSink();
|
|
@@ -1018,7 +1105,7 @@ describe("createChannel", () => {
|
|
|
1018
1105
|
replyTarget: {},
|
|
1019
1106
|
userText: "hello",
|
|
1020
1107
|
platform: "fake",
|
|
1021
|
-
|
|
1108
|
+
actor: { id: "u1", kind: "human" },
|
|
1022
1109
|
});
|
|
1023
1110
|
expect(capturedKey).toBe("user@example.com");
|
|
1024
1111
|
});
|
|
@@ -1027,11 +1114,11 @@ describe("createChannel", () => {
|
|
|
1027
1114
|
const fake = new FakeAdapter();
|
|
1028
1115
|
const agent = new FakeAgent();
|
|
1029
1116
|
const channel = createChannel({
|
|
1117
|
+
identifyUser: "platform",
|
|
1030
1118
|
adapters: [fake],
|
|
1031
1119
|
agent: () => agent,
|
|
1032
1120
|
store: {
|
|
1033
1121
|
adapter: state,
|
|
1034
|
-
identity: () => "alice@example.com",
|
|
1035
1122
|
transcripts: { maxPerUser: 50 },
|
|
1036
1123
|
},
|
|
1037
1124
|
});
|
|
@@ -1041,11 +1128,11 @@ describe("createChannel", () => {
|
|
|
1041
1128
|
const thread = { platform: "fake", conversationKey: "c1" };
|
|
1042
1129
|
// Directly append via channel.transcripts
|
|
1043
1130
|
await channel.transcripts.append(thread, { role: "user", text: "hi there" }, {
|
|
1044
|
-
|
|
1131
|
+
userId: "alice@example.com",
|
|
1045
1132
|
});
|
|
1046
|
-
await channel.transcripts.append(thread, { role: "assistant", text: "hello!" }, {
|
|
1133
|
+
await channel.transcripts.append(thread, { role: "assistant", text: "hello!" }, { userId: "alice@example.com" });
|
|
1047
1134
|
const entries = await channel.transcripts.list({
|
|
1048
|
-
|
|
1135
|
+
userId: "alice@example.com",
|
|
1049
1136
|
});
|
|
1050
1137
|
expect(entries).toHaveLength(2);
|
|
1051
1138
|
expect(entries[0].role).toBe("user");
|
|
@@ -1060,9 +1147,9 @@ describe("createChannel", () => {
|
|
|
1060
1147
|
const channel = createChannel({
|
|
1061
1148
|
adapters: [fake],
|
|
1062
1149
|
agent: () => agent,
|
|
1150
|
+
identifyUser: () => ({ id: "u@x.com", name: "User" }),
|
|
1063
1151
|
store: {
|
|
1064
1152
|
adapter: state,
|
|
1065
|
-
identity: () => "u@x.com",
|
|
1066
1153
|
transcripts: {},
|
|
1067
1154
|
},
|
|
1068
1155
|
});
|
|
@@ -1094,18 +1181,18 @@ describe("createChannel", () => {
|
|
|
1094
1181
|
// Seed one prior cross-platform entry (different platform label) so we can
|
|
1095
1182
|
// assert it shows up in the injected context. Seeded post-start: transcripts
|
|
1096
1183
|
// are only available once the backend is resolved in start().
|
|
1097
|
-
await channel.transcripts.append({ platform: "discord", conversationKey: "other" }, { role: "user", text: "remembered from discord" }, {
|
|
1184
|
+
await channel.transcripts.append({ platform: "discord", conversationKey: "other" }, { role: "user", text: "remembered from discord" }, { userId: "u@x.com" });
|
|
1098
1185
|
const sink = fake.getSink();
|
|
1099
1186
|
await sink.onTurn({
|
|
1100
1187
|
conversationKey: "c1",
|
|
1101
1188
|
replyTarget: {},
|
|
1102
1189
|
userText: "hello from fake",
|
|
1103
1190
|
platform: "fake",
|
|
1104
|
-
|
|
1191
|
+
actor: { id: "u1", kind: "human" },
|
|
1105
1192
|
});
|
|
1106
1193
|
// Append side: both the user turn and the assistant reply are recorded,
|
|
1107
1194
|
// oldest-first (after the seeded discord entry).
|
|
1108
|
-
const entries = await channel.transcripts.list({
|
|
1195
|
+
const entries = await channel.transcripts.list({ userId: "u@x.com" });
|
|
1109
1196
|
const fakeEntries = entries.filter((e) => e.platform === "fake");
|
|
1110
1197
|
expect(fakeEntries).toHaveLength(2);
|
|
1111
1198
|
expect(fakeEntries[0].role).toBe("user");
|
|
@@ -1123,6 +1210,7 @@ describe("createChannel", () => {
|
|
|
1123
1210
|
const fake = new FakeAdapter();
|
|
1124
1211
|
const agent = new FakeAgent();
|
|
1125
1212
|
const channel = createChannel({
|
|
1213
|
+
identifyUser: "platform",
|
|
1126
1214
|
adapters: [fake],
|
|
1127
1215
|
agent: () => agent,
|
|
1128
1216
|
store: {
|
|
@@ -1159,6 +1247,7 @@ describe("createChannel lock and dedup edge cases", () => {
|
|
|
1159
1247
|
const fake = new FakeAdapter();
|
|
1160
1248
|
// Use a separate channel so the throwing handler is isolated.
|
|
1161
1249
|
const bot1 = createChannel({
|
|
1250
|
+
identifyUser: "platform",
|
|
1162
1251
|
adapters: [fake],
|
|
1163
1252
|
store: { adapter: state, onLockConflict: "drop", lockTtl: 5000 },
|
|
1164
1253
|
});
|
|
@@ -1196,6 +1285,7 @@ describe("createChannel lock and dedup edge cases", () => {
|
|
|
1196
1285
|
let callbackMessageText;
|
|
1197
1286
|
const fake = new FakeAdapter();
|
|
1198
1287
|
const channel = createChannel({
|
|
1288
|
+
identifyUser: "platform",
|
|
1199
1289
|
adapters: [fake],
|
|
1200
1290
|
store: {
|
|
1201
1291
|
adapter: state,
|
|
@@ -1233,6 +1323,7 @@ describe("createChannel lock and dedup edge cases", () => {
|
|
|
1233
1323
|
const gate = new Promise((r) => (release = r));
|
|
1234
1324
|
const fake = new FakeAdapter();
|
|
1235
1325
|
const channel = createChannel({
|
|
1326
|
+
identifyUser: "platform",
|
|
1236
1327
|
adapters: [fake],
|
|
1237
1328
|
store: {
|
|
1238
1329
|
adapter: state,
|
|
@@ -1257,43 +1348,48 @@ describe("createChannel lock and dedup edge cases", () => {
|
|
|
1257
1348
|
await Promise.all([p1, p2]);
|
|
1258
1349
|
expect(runs).toBe(2);
|
|
1259
1350
|
});
|
|
1260
|
-
it("
|
|
1351
|
+
it("identifyUser errors fail the trigger before its handler runs", async () => {
|
|
1261
1352
|
const state = new MemoryStore();
|
|
1262
1353
|
const fake = new FakeAdapter();
|
|
1263
1354
|
const channel = createChannel({
|
|
1264
1355
|
adapters: [fake],
|
|
1356
|
+
identifyUser: () => {
|
|
1357
|
+
throw new Error("identity failed");
|
|
1358
|
+
},
|
|
1265
1359
|
store: {
|
|
1266
1360
|
adapter: state,
|
|
1267
|
-
identity: () => {
|
|
1268
|
-
throw new Error("x");
|
|
1269
|
-
},
|
|
1270
1361
|
transcripts: {},
|
|
1271
1362
|
},
|
|
1272
1363
|
});
|
|
1273
|
-
let
|
|
1274
|
-
channel.onMention(async (
|
|
1275
|
-
|
|
1364
|
+
let handled = false;
|
|
1365
|
+
channel.onMention(async () => {
|
|
1366
|
+
handled = true;
|
|
1276
1367
|
});
|
|
1277
1368
|
await channel.ɵruntime.start();
|
|
1278
1369
|
const sink = fake.getSink();
|
|
1279
|
-
|
|
1370
|
+
const delivery = sink.onTurn({
|
|
1280
1371
|
conversationKey: "c1",
|
|
1281
1372
|
replyTarget: {},
|
|
1282
1373
|
userText: "hi",
|
|
1283
1374
|
platform: "fake",
|
|
1284
1375
|
});
|
|
1285
|
-
expect(
|
|
1376
|
+
await expect(delivery).rejects.toMatchObject({
|
|
1377
|
+
code: "channel_identity_failed",
|
|
1378
|
+
cause: expect.objectContaining({ message: "identity failed" }),
|
|
1379
|
+
});
|
|
1380
|
+
expect(handled).toBe(false);
|
|
1286
1381
|
});
|
|
1287
|
-
it("
|
|
1382
|
+
it("identifyUser may return null without substituting the provider actor", async () => {
|
|
1288
1383
|
const state = new MemoryStore();
|
|
1289
1384
|
const fake = new FakeAdapter();
|
|
1290
1385
|
const channel = createChannel({
|
|
1291
1386
|
adapters: [fake],
|
|
1292
|
-
|
|
1387
|
+
identifyUser: () => null,
|
|
1388
|
+
store: { adapter: state, transcripts: {} },
|
|
1293
1389
|
});
|
|
1294
|
-
let
|
|
1390
|
+
let capturedUser = "SENTINEL";
|
|
1295
1391
|
channel.onMention(async ({ message }) => {
|
|
1296
|
-
|
|
1392
|
+
capturedUser = message.user;
|
|
1297
1393
|
});
|
|
1298
1394
|
await channel.ɵruntime.start();
|
|
1299
1395
|
const sink = fake.getSink();
|
|
@@ -1303,13 +1399,14 @@ describe("createChannel lock and dedup edge cases", () => {
|
|
|
1303
1399
|
userText: "hi",
|
|
1304
1400
|
platform: "fake",
|
|
1305
1401
|
});
|
|
1306
|
-
expect(
|
|
1402
|
+
expect(capturedUser).toBeNull();
|
|
1307
1403
|
});
|
|
1308
1404
|
it("dedup+lock ordering: deduped turn never takes the lock", async () => {
|
|
1309
1405
|
const state = new MemoryStore();
|
|
1310
1406
|
let runs = 0;
|
|
1311
1407
|
const fake = new FakeAdapter();
|
|
1312
1408
|
const channel = createChannel({
|
|
1409
|
+
identifyUser: "platform",
|
|
1313
1410
|
adapters: [fake],
|
|
1314
1411
|
store: { adapter: state, onLockConflict: "drop" },
|
|
1315
1412
|
});
|
|
@@ -1349,6 +1446,7 @@ describe("createChannel lock and dedup edge cases", () => {
|
|
|
1349
1446
|
let runs = 0;
|
|
1350
1447
|
const fake = new FakeAdapter();
|
|
1351
1448
|
const channel = createChannel({
|
|
1449
|
+
identifyUser: "platform",
|
|
1352
1450
|
adapters: [fake],
|
|
1353
1451
|
store: { adapter: state },
|
|
1354
1452
|
});
|
|
@@ -1374,6 +1472,7 @@ describe("createChannel lock and dedup edge cases", () => {
|
|
|
1374
1472
|
const gate = new Promise((r) => (releaseGate = r));
|
|
1375
1473
|
const fake = new FakeAdapter();
|
|
1376
1474
|
const channel = createChannel({
|
|
1475
|
+
identifyUser: "platform",
|
|
1377
1476
|
adapters: [fake],
|
|
1378
1477
|
store: { adapter: state, onLockConflict: "drop" },
|
|
1379
1478
|
});
|
|
@@ -1416,6 +1515,7 @@ describe("createChannel lock and dedup edge cases", () => {
|
|
|
1416
1515
|
let runs = 0;
|
|
1417
1516
|
const fake = new FakeAdapter();
|
|
1418
1517
|
const channel = createChannel({
|
|
1518
|
+
identifyUser: "platform",
|
|
1419
1519
|
adapters: [fake],
|
|
1420
1520
|
store: { adapter: state },
|
|
1421
1521
|
});
|
|
@@ -1442,7 +1542,10 @@ describe("createChannel lock and dedup edge cases", () => {
|
|
|
1442
1542
|
describe("createChannel slash commands", () => {
|
|
1443
1543
|
it("routes a command to its handler with the raw text", async () => {
|
|
1444
1544
|
const fake = new FakeAdapter();
|
|
1445
|
-
const channel = createChannel({
|
|
1545
|
+
const channel = createChannel({
|
|
1546
|
+
identifyUser: "platform",
|
|
1547
|
+
adapters: [fake],
|
|
1548
|
+
});
|
|
1446
1549
|
let seen;
|
|
1447
1550
|
channel.onCommand("triage", ({ command, text }) => {
|
|
1448
1551
|
seen = { command, text };
|
|
@@ -1453,7 +1556,10 @@ describe("createChannel slash commands", () => {
|
|
|
1453
1556
|
});
|
|
1454
1557
|
it("ignores a command with no registered handler", async () => {
|
|
1455
1558
|
const fake = new FakeAdapter();
|
|
1456
|
-
const channel = createChannel({
|
|
1559
|
+
const channel = createChannel({
|
|
1560
|
+
identifyUser: "platform",
|
|
1561
|
+
adapters: [fake],
|
|
1562
|
+
});
|
|
1457
1563
|
let fired = false;
|
|
1458
1564
|
channel.onCommand("triage", () => {
|
|
1459
1565
|
fired = true;
|
|
@@ -1466,6 +1572,7 @@ describe("createChannel slash commands", () => {
|
|
|
1466
1572
|
let captured;
|
|
1467
1573
|
const fake = new FakeAdapter();
|
|
1468
1574
|
const channel = createChannel({
|
|
1575
|
+
identifyUser: "platform",
|
|
1469
1576
|
adapters: [fake],
|
|
1470
1577
|
commands: [
|
|
1471
1578
|
defineChannelCommand({
|
|
@@ -1487,7 +1594,10 @@ describe("createChannel slash commands", () => {
|
|
|
1487
1594
|
});
|
|
1488
1595
|
it("hands declared commands to adapters that implement registerCommands", async () => {
|
|
1489
1596
|
const fake = new FakeAdapter();
|
|
1490
|
-
const channel = createChannel({
|
|
1597
|
+
const channel = createChannel({
|
|
1598
|
+
identifyUser: "platform",
|
|
1599
|
+
adapters: [fake],
|
|
1600
|
+
});
|
|
1491
1601
|
channel.onCommand("triage", () => { });
|
|
1492
1602
|
channel.onCommand("status", () => { });
|
|
1493
1603
|
await channel.ɵruntime.start();
|
|
@@ -1500,16 +1610,124 @@ describe("createChannel slash commands", () => {
|
|
|
1500
1610
|
const errSpy = vi.spyOn(console, "error").mockImplementation(() => { });
|
|
1501
1611
|
try {
|
|
1502
1612
|
const bad = new FakeAdapter({ platform: "telegram", failStart: true });
|
|
1613
|
+
const badStop = vi.spyOn(bad, "stop");
|
|
1503
1614
|
const good = new FakeAdapter({ platform: "slack" });
|
|
1504
|
-
const channel = createChannel({
|
|
1615
|
+
const channel = createChannel({
|
|
1616
|
+
identifyUser: "platform",
|
|
1617
|
+
adapters: [bad, good],
|
|
1618
|
+
});
|
|
1505
1619
|
await expect(channel.ɵruntime.start()).resolves.toBeUndefined();
|
|
1506
1620
|
expect(good.started).toBe(true);
|
|
1621
|
+
expect(badStop).toHaveBeenCalledTimes(1);
|
|
1507
1622
|
expect(errSpy.mock.calls.some((c) => String(c[0]).includes("telegram"))).toBe(true);
|
|
1508
1623
|
}
|
|
1509
1624
|
finally {
|
|
1510
1625
|
errSpy.mockRestore();
|
|
1511
1626
|
}
|
|
1512
1627
|
});
|
|
1628
|
+
it("does not register commands on an adapter whose start failed", async () => {
|
|
1629
|
+
const errSpy = vi.spyOn(console, "error").mockImplementation(() => { });
|
|
1630
|
+
try {
|
|
1631
|
+
const failed = new FakeAdapter({ platform: "telegram", failStart: true });
|
|
1632
|
+
const failedRegister = vi.spyOn(failed, "registerCommands");
|
|
1633
|
+
const healthy = new FakeAdapter({ platform: "slack" });
|
|
1634
|
+
const channel = createChannel({
|
|
1635
|
+
identifyUser: "platform",
|
|
1636
|
+
adapters: [failed, healthy],
|
|
1637
|
+
});
|
|
1638
|
+
channel.onCommand("triage", () => { });
|
|
1639
|
+
await expect(channel.ɵruntime.start()).resolves.toBeUndefined();
|
|
1640
|
+
expect(failedRegister).not.toHaveBeenCalled();
|
|
1641
|
+
expect(healthy.registeredCommands?.map(({ name }) => name)).toEqual([
|
|
1642
|
+
"triage",
|
|
1643
|
+
]);
|
|
1644
|
+
}
|
|
1645
|
+
finally {
|
|
1646
|
+
errSpy.mockRestore();
|
|
1647
|
+
}
|
|
1648
|
+
});
|
|
1649
|
+
it("start() rejects and rolls back direct adapters when the managed adapter fails", async () => {
|
|
1650
|
+
const errSpy = vi.spyOn(console, "error").mockImplementation(() => { });
|
|
1651
|
+
try {
|
|
1652
|
+
const direct = new FakeAdapter({ platform: "slack" });
|
|
1653
|
+
const directStop = vi.spyOn(direct, "stop");
|
|
1654
|
+
const managed = Object.assign(new FakeAdapter({ platform: "intelligence", failStart: true }), { __intelligenceChannel: true });
|
|
1655
|
+
const managedStop = vi.spyOn(managed, "stop");
|
|
1656
|
+
const channel = createChannel({
|
|
1657
|
+
identifyUser: "platform",
|
|
1658
|
+
adapters: [direct, managed],
|
|
1659
|
+
});
|
|
1660
|
+
await expect(channel.ɵruntime.start()).rejects.toThrow("failed to start its managed Intelligence adapter");
|
|
1661
|
+
expect(direct.started).toBe(true);
|
|
1662
|
+
expect(directStop).toHaveBeenCalledTimes(1);
|
|
1663
|
+
expect(managedStop).toHaveBeenCalledTimes(1);
|
|
1664
|
+
}
|
|
1665
|
+
finally {
|
|
1666
|
+
errSpy.mockRestore();
|
|
1667
|
+
}
|
|
1668
|
+
});
|
|
1669
|
+
it("managed-start rollback keeps stopping adapters after a synchronous stop throw", async () => {
|
|
1670
|
+
const errSpy = vi.spyOn(console, "error").mockImplementation(() => { });
|
|
1671
|
+
try {
|
|
1672
|
+
const throwing = new FakeAdapter({ platform: "slack" });
|
|
1673
|
+
throwing.stop = vi.fn(() => {
|
|
1674
|
+
throw new Error("synchronous stop failure");
|
|
1675
|
+
});
|
|
1676
|
+
const healthy = new FakeAdapter({ platform: "teams" });
|
|
1677
|
+
const healthyStop = vi.spyOn(healthy, "stop");
|
|
1678
|
+
const managed = Object.assign(new FakeAdapter({ platform: "intelligence", failStart: true }), { __intelligenceChannel: true });
|
|
1679
|
+
const managedStop = vi.spyOn(managed, "stop");
|
|
1680
|
+
const channel = createChannel({
|
|
1681
|
+
identifyUser: "platform",
|
|
1682
|
+
adapters: [throwing, healthy, managed],
|
|
1683
|
+
});
|
|
1684
|
+
await expect(channel.ɵruntime.start()).rejects.toThrow("failed to start its managed Intelligence adapter");
|
|
1685
|
+
expect(throwing.stop).toHaveBeenCalledTimes(1);
|
|
1686
|
+
expect(healthyStop).toHaveBeenCalledTimes(1);
|
|
1687
|
+
expect(managedStop).toHaveBeenCalledTimes(1);
|
|
1688
|
+
}
|
|
1689
|
+
finally {
|
|
1690
|
+
errSpy.mockRestore();
|
|
1691
|
+
}
|
|
1692
|
+
});
|
|
1693
|
+
it("managed-start rollback times out a stop that never settles", async () => {
|
|
1694
|
+
vi.useFakeTimers();
|
|
1695
|
+
const errSpy = vi.spyOn(console, "error").mockImplementation(() => { });
|
|
1696
|
+
try {
|
|
1697
|
+
const stuck = new FakeAdapter({ platform: "slack" });
|
|
1698
|
+
stuck.stop = vi.fn(() => new Promise(() => undefined));
|
|
1699
|
+
const managed = Object.assign(new FakeAdapter({ platform: "intelligence", failStart: true }), { __intelligenceChannel: true });
|
|
1700
|
+
const channel = createChannel({
|
|
1701
|
+
identifyUser: "platform",
|
|
1702
|
+
adapters: [stuck, managed],
|
|
1703
|
+
});
|
|
1704
|
+
const starting = expect(channel.ɵruntime.start()).rejects.toThrow("failed to start its managed Intelligence adapter");
|
|
1705
|
+
await vi.advanceTimersByTimeAsync(5_000);
|
|
1706
|
+
await starting;
|
|
1707
|
+
expect(stuck.stop).toHaveBeenCalledOnce();
|
|
1708
|
+
expect(errSpy.mock.calls.some((call) => call.some((part) => String(part).includes("stop timed out")))).toBe(true);
|
|
1709
|
+
}
|
|
1710
|
+
finally {
|
|
1711
|
+
errSpy.mockRestore();
|
|
1712
|
+
vi.useRealTimers();
|
|
1713
|
+
}
|
|
1714
|
+
});
|
|
1715
|
+
it("start() stops a rejecting adapter when every adapter fails", async () => {
|
|
1716
|
+
const errSpy = vi.spyOn(console, "error").mockImplementation(() => { });
|
|
1717
|
+
try {
|
|
1718
|
+
const adapter = new FakeAdapter({ platform: "slack", failStart: true });
|
|
1719
|
+
const stop = vi.spyOn(adapter, "stop");
|
|
1720
|
+
const channel = createChannel({
|
|
1721
|
+
identifyUser: "platform",
|
|
1722
|
+
adapters: [adapter],
|
|
1723
|
+
});
|
|
1724
|
+
await expect(channel.ɵruntime.start()).rejects.toThrow("all 1 adapter(s) failed to connect");
|
|
1725
|
+
expect(stop).toHaveBeenCalledTimes(1);
|
|
1726
|
+
}
|
|
1727
|
+
finally {
|
|
1728
|
+
errSpy.mockRestore();
|
|
1729
|
+
}
|
|
1730
|
+
});
|
|
1513
1731
|
it("start() resolves and the healthy adapter still receives commands when another adapter's registerCommands() rejects", async () => {
|
|
1514
1732
|
const errSpy = vi.spyOn(console, "error").mockImplementation(() => { });
|
|
1515
1733
|
try {
|
|
@@ -1518,7 +1736,10 @@ describe("createChannel slash commands", () => {
|
|
|
1518
1736
|
failRegisterCommands: true,
|
|
1519
1737
|
});
|
|
1520
1738
|
const good = new FakeAdapter({ platform: "slack" });
|
|
1521
|
-
const channel = createChannel({
|
|
1739
|
+
const channel = createChannel({
|
|
1740
|
+
identifyUser: "platform",
|
|
1741
|
+
adapters: [bad, good],
|
|
1742
|
+
});
|
|
1522
1743
|
channel.onCommand("triage", () => { });
|
|
1523
1744
|
await expect(channel.ɵruntime.start()).resolves.toBeUndefined();
|
|
1524
1745
|
expect(good.started).toBe(true);
|
|
@@ -1535,7 +1756,10 @@ describe("createChannel slash commands", () => {
|
|
|
1535
1756
|
const bad = new FakeAdapter({ platform: "telegram", failStop: true });
|
|
1536
1757
|
const good = new FakeAdapter({ platform: "slack" });
|
|
1537
1758
|
const stopSpy = vi.spyOn(good, "stop");
|
|
1538
|
-
const channel = createChannel({
|
|
1759
|
+
const channel = createChannel({
|
|
1760
|
+
identifyUser: "platform",
|
|
1761
|
+
adapters: [bad, good],
|
|
1762
|
+
});
|
|
1539
1763
|
await channel.ɵruntime.start();
|
|
1540
1764
|
await expect(channel.ɵruntime.stop()).resolves.toBeUndefined();
|
|
1541
1765
|
expect(stopSpy).toHaveBeenCalled();
|
|
@@ -1547,7 +1771,10 @@ describe("createChannel slash commands", () => {
|
|
|
1547
1771
|
});
|
|
1548
1772
|
it("exposes attached adapters through a read-only, non-mutable-through accessor", () => {
|
|
1549
1773
|
const fake = new FakeAdapter();
|
|
1550
|
-
const channel = createChannel({
|
|
1774
|
+
const channel = createChannel({
|
|
1775
|
+
identifyUser: "platform",
|
|
1776
|
+
adapters: [fake],
|
|
1777
|
+
});
|
|
1551
1778
|
expect(channel.adapters).toContain(fake);
|
|
1552
1779
|
expect(channel.adapters).toHaveLength(1);
|
|
1553
1780
|
// The returned snapshot is a copy: mutating it must not affect the channel.
|
|
@@ -1555,7 +1782,7 @@ describe("createChannel slash commands", () => {
|
|
|
1555
1782
|
expect(channel.adapters).toHaveLength(1);
|
|
1556
1783
|
});
|
|
1557
1784
|
it("reflects an adapter attached via addAdapter in the adapters accessor", () => {
|
|
1558
|
-
const channel = createChannel({});
|
|
1785
|
+
const channel = createChannel({ identifyUser: "platform" });
|
|
1559
1786
|
expect(channel.adapters).toHaveLength(0);
|
|
1560
1787
|
const fake = new FakeAdapter();
|
|
1561
1788
|
channel.ɵruntime.addAdapter(fake);
|