@copilotkit/channels-core 0.2.1 → 0.3.0
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 +45 -2
- package/dist/create-channel.d.ts +20 -10
- package/dist/create-channel.d.ts.map +1 -1
- package/dist/create-channel.foundations.test.js +14 -14
- package/dist/create-channel.js +133 -121
- package/dist/create-channel.test.js +34 -34
- package/dist/modal-submit.test.js +4 -4
- package/dist/open-modal.test.js +4 -4
- package/dist/reactions.test.js +11 -11
- package/dist/telemetry/create-channel-telemetry.test.js +7 -4
- package/dist/telemetry/e2e-telemetry.test.js +1 -1
- package/dist/testing/fake-adapter.d.ts.map +1 -1
- package/dist/testing/fake-adapter.js +1 -1
- package/dist/thread-capabilities.test.js +5 -5
- package/dist/thread-ephemeral.test.js +1 -1
- package/dist/thread-reactions.test.js +2 -2
- package/package.json +4 -4
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
|
|
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
|
package/dist/create-channel.d.ts
CHANGED
|
@@ -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 })` /
|
|
30
|
-
* *direct*, developer-owned connection this handler does not manage,
|
|
31
|
-
* `provider` selects the *managed* platform for a Channel with no
|
|
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
|
|
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
|
-
*
|
|
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;AA8BhF,MAAM,MAAM,oBAAoB,GAAG,MAAM,GAAG,OAAO,CAAC;AAEpD
|
|
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",
|
package/dist/create-channel.js
CHANGED
|
@@ -79,16 +79,18 @@ export function createChannel(opts) {
|
|
|
79
79
|
(!cfg.identity && cfg.transcripts)) {
|
|
80
80
|
throw new Error("createChannel: `identity` and `transcripts` must be configured together.");
|
|
81
81
|
}
|
|
82
|
-
// Adapters can be supplied up front or added later via
|
|
83
|
-
// (before `start()`). The
|
|
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.
|
|
84
85
|
const adapters = [...(opts.adapters ?? [])];
|
|
85
86
|
assertExclusive(adapters);
|
|
86
87
|
let started = false;
|
|
87
88
|
// Backend, transcripts, telemetry, the action registry, and component
|
|
88
|
-
// registration are resolved in
|
|
89
|
-
// adapter added via
|
|
90
|
-
// persistence backend (see `resolveBackend`). Nothing
|
|
91
|
-
// first event, which can only arrive after
|
|
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()`.
|
|
92
94
|
let backend;
|
|
93
95
|
let transcripts;
|
|
94
96
|
let registry;
|
|
@@ -123,7 +125,7 @@ export function createChannel(opts) {
|
|
|
123
125
|
let toolDescriptors = toAgentToolDescriptors([...toolMap.values()]);
|
|
124
126
|
function makeThread(adapter, replyTarget, conversationKey, extras) {
|
|
125
127
|
if (!backend || !registry || !telemetry) {
|
|
126
|
-
throw new Error("channel not started:
|
|
128
|
+
throw new Error("channel not started: the runner must start the channel (channel.ɵruntime.start()) before handling events");
|
|
127
129
|
}
|
|
128
130
|
const deps = {
|
|
129
131
|
adapter,
|
|
@@ -419,16 +421,133 @@ export function createChannel(opts) {
|
|
|
419
421
|
},
|
|
420
422
|
get transcripts() {
|
|
421
423
|
if (!transcripts) {
|
|
422
|
-
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())");
|
|
423
425
|
}
|
|
424
426
|
return transcripts;
|
|
425
427
|
},
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
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
|
+
},
|
|
432
551
|
},
|
|
433
552
|
onMention(h) {
|
|
434
553
|
// The public surface narrows `thread` to StatefulThread<TState>; the
|
|
@@ -483,113 +602,6 @@ export function createChannel(opts) {
|
|
|
483
602
|
tool(t) {
|
|
484
603
|
toolMap.set(t.name, t);
|
|
485
604
|
},
|
|
486
|
-
async start() {
|
|
487
|
-
// Idempotent: a second start() must not re-resolve the backend and
|
|
488
|
-
// rebuild Transcripts/Telemetry/ActionRegistry or re-call adapter.start()
|
|
489
|
-
// — with a MemoryStore that wipes all lock/dedup/transcript/action state,
|
|
490
|
-
// and real adapters would connect/port-bind twice.
|
|
491
|
-
if (started)
|
|
492
|
-
return;
|
|
493
|
-
started = true;
|
|
494
|
-
assertExclusive(adapters);
|
|
495
|
-
// Resolve persistence now that all adapters (including any attached via
|
|
496
|
-
// addAdapter) are known, then build the transcript store, action
|
|
497
|
-
// registry, and register components against it.
|
|
498
|
-
backend = resolveBackend(cfg.adapter, adapters);
|
|
499
|
-
transcripts = new Transcripts(backend, cfg.transcripts ?? {});
|
|
500
|
-
const tel = new ChannelTelemetry({
|
|
501
|
-
backend,
|
|
502
|
-
packageName: pkg.name,
|
|
503
|
-
packageVersion: pkg.version,
|
|
504
|
-
});
|
|
505
|
-
telemetry = tel;
|
|
506
|
-
const registryInstance = new ActionRegistry({
|
|
507
|
-
store: opts.actionStore ?? kvActionStore(backend),
|
|
508
|
-
});
|
|
509
|
-
registry = registryInstance;
|
|
510
|
-
for (const c of opts.components ?? []) {
|
|
511
|
-
if (!c.name) {
|
|
512
|
-
console.warn("[channel] createChannel: skipping anonymous component — give it a name to enable durable actions after restart.");
|
|
513
|
-
continue;
|
|
514
|
-
}
|
|
515
|
-
registryInstance.registerComponent(c.name, c);
|
|
516
|
-
}
|
|
517
|
-
toolDescriptors = toAgentToolDescriptors([...toolMap.values()]);
|
|
518
|
-
tel.capture("oss.channel.configured", {
|
|
519
|
-
platforms: adapters.map((a) => normalizePlatform(a.platform)),
|
|
520
|
-
adapterCount: adapters.length,
|
|
521
|
-
store: storeKind(backend),
|
|
522
|
-
hasComponents: (opts.components?.length ?? 0) > 0,
|
|
523
|
-
componentsCount: opts.components?.length ?? 0,
|
|
524
|
-
toolsCount: toolMap.size,
|
|
525
|
-
commandsCount: commandHandlers.size,
|
|
526
|
-
contextCount: context.length,
|
|
527
|
-
transcripts: !!cfg.transcripts,
|
|
528
|
-
identity: !!cfg.identity,
|
|
529
|
-
});
|
|
530
|
-
// Isolate per-adapter startup failures: one adapter rejecting (e.g.
|
|
531
|
-
// Telegram's setMyCommands rejecting a hyphenated command name, a revoked
|
|
532
|
-
// token, a port already in use) must NOT crash the channel or prevent the
|
|
533
|
-
// other adapters from starting. Log + degrade, never throw.
|
|
534
|
-
const startResults = await Promise.allSettled(adapters.map((a) => a.start(makeSink(a), { channelName: opts.name })));
|
|
535
|
-
const startedPlatforms = [];
|
|
536
|
-
const failedPlatforms = [];
|
|
537
|
-
startResults.forEach((r, i) => {
|
|
538
|
-
const rawPlatform = adapters[i].platform;
|
|
539
|
-
// Raw label for the human-facing log; normalized label for telemetry.
|
|
540
|
-
const platform = normalizePlatform(rawPlatform);
|
|
541
|
-
if (r.status === "rejected") {
|
|
542
|
-
failedPlatforms.push(platform);
|
|
543
|
-
console.error(`[channel] adapter "${rawPlatform}" failed to start:`, r.reason);
|
|
544
|
-
tel.capture("oss.channel.start_failed", {
|
|
545
|
-
platform,
|
|
546
|
-
errorClass: errorClass(r.reason),
|
|
547
|
-
});
|
|
548
|
-
}
|
|
549
|
-
else {
|
|
550
|
-
startedPlatforms.push(platform);
|
|
551
|
-
}
|
|
552
|
-
});
|
|
553
|
-
if (startedPlatforms.length > 0) {
|
|
554
|
-
tel.capture("oss.channel.started", {
|
|
555
|
-
platforms: startedPlatforms,
|
|
556
|
-
startedCount: startedPlatforms.length,
|
|
557
|
-
failedCount: failedPlatforms.length,
|
|
558
|
-
hasMentionHandler: mentionHandlers.length > 0,
|
|
559
|
-
hasMessageHandler: messageHandlers.length > 0,
|
|
560
|
-
interruptHandlers: interruptHandlers.size,
|
|
561
|
-
commandsCount: commandHandlers.size,
|
|
562
|
-
toolsCount: toolMap.size,
|
|
563
|
-
});
|
|
564
|
-
}
|
|
565
|
-
// Hand declared commands to adapters that register them up front (e.g.
|
|
566
|
-
// Discord); adapters without `registerCommands` are skipped. Per-adapter
|
|
567
|
-
// failures are isolated the same way as start().
|
|
568
|
-
const commandSpecs = [...commandHandlers.values()].map(toCommandSpec);
|
|
569
|
-
if (commandSpecs.length > 0) {
|
|
570
|
-
const registerResults = await Promise.allSettled(adapters.map((a) => a.registerCommands?.(commandSpecs)));
|
|
571
|
-
registerResults.forEach((r, i) => {
|
|
572
|
-
if (r.status === "rejected") {
|
|
573
|
-
console.error(`[channel] adapter "${adapters[i].platform}" failed to register commands:`, r.reason);
|
|
574
|
-
}
|
|
575
|
-
});
|
|
576
|
-
}
|
|
577
|
-
},
|
|
578
|
-
async stop() {
|
|
579
|
-
// Clear the started flag so a later start() is a real restart (re-resolve
|
|
580
|
-
// backend, rebuild components, reconnect adapters) rather than a silent
|
|
581
|
-
// no-op. The idempotency guard in start() only exists to block a DOUBLE
|
|
582
|
-
// start() while running — not a legitimate start→stop→start cycle.
|
|
583
|
-
started = false;
|
|
584
|
-
// Isolate per-adapter shutdown failures: one adapter's stop() rejecting
|
|
585
|
-
// must not prevent the others from being stopped.
|
|
586
|
-
const stopResults = await Promise.allSettled(adapters.map((a) => a.stop()));
|
|
587
|
-
stopResults.forEach((r, i) => {
|
|
588
|
-
if (r.status === "rejected") {
|
|
589
|
-
console.error(`[channel] adapter "${adapters[i].platform}" failed to stop:`, r.reason);
|
|
590
|
-
}
|
|
591
|
-
});
|
|
592
|
-
},
|
|
593
605
|
};
|
|
594
606
|
return channel;
|
|
595
607
|
}
|
|
@@ -60,7 +60,7 @@ describe("createChannel", () => {
|
|
|
60
60
|
channel.onMention(async ({ thread }) => {
|
|
61
61
|
await thread.post(Section({ children: "hi" }));
|
|
62
62
|
});
|
|
63
|
-
await channel.start();
|
|
63
|
+
await channel.ɵruntime.start();
|
|
64
64
|
fake.emitTurn({ userText: "yo", conversationKey: "c1" });
|
|
65
65
|
await tick();
|
|
66
66
|
expect(fake.posted.length).toBe(1);
|
|
@@ -75,7 +75,7 @@ describe("createChannel", () => {
|
|
|
75
75
|
channel.onMention(async ({ thread }) => {
|
|
76
76
|
await thread.runAgent();
|
|
77
77
|
});
|
|
78
|
-
await channel.start();
|
|
78
|
+
await channel.ɵruntime.start();
|
|
79
79
|
fake.emitTurn({ userText: "yo", conversationKey: "c1" });
|
|
80
80
|
await tick();
|
|
81
81
|
const renderer = fake.lastRunRenderer;
|
|
@@ -107,7 +107,7 @@ describe("createChannel", () => {
|
|
|
107
107
|
: message.text,
|
|
108
108
|
});
|
|
109
109
|
});
|
|
110
|
-
await channel.start();
|
|
110
|
+
await channel.ɵruntime.start();
|
|
111
111
|
fake.emitTurn({
|
|
112
112
|
userText: "look",
|
|
113
113
|
conversationKey: "c1",
|
|
@@ -138,7 +138,7 @@ describe("createChannel", () => {
|
|
|
138
138
|
],
|
|
139
139
|
}));
|
|
140
140
|
});
|
|
141
|
-
await channel.start();
|
|
141
|
+
await channel.ɵruntime.start();
|
|
142
142
|
fake.emitTurn({ userText: "yo", conversationKey: "c1" });
|
|
143
143
|
await tick();
|
|
144
144
|
const button = findNode(fake.posted[0], "button");
|
|
@@ -164,7 +164,7 @@ describe("createChannel", () => {
|
|
|
164
164
|
],
|
|
165
165
|
}));
|
|
166
166
|
});
|
|
167
|
-
await channel.start();
|
|
167
|
+
await channel.ɵruntime.start();
|
|
168
168
|
fake.emitTurn({ userText: "create a thing", conversationKey: "c1" });
|
|
169
169
|
await tick();
|
|
170
170
|
const button = findNode(fake.posted[0], "button");
|
|
@@ -201,7 +201,7 @@ describe("createChannel", () => {
|
|
|
201
201
|
context: [{ description: "who", value: "user U1" }],
|
|
202
202
|
});
|
|
203
203
|
});
|
|
204
|
-
await channel.start();
|
|
204
|
+
await channel.ɵruntime.start();
|
|
205
205
|
fake.emitTurn({ userText: "go", conversationKey: "c1" });
|
|
206
206
|
await tick();
|
|
207
207
|
expect(seenContext).toEqual([
|
|
@@ -221,7 +221,7 @@ describe("createChannel", () => {
|
|
|
221
221
|
filename: "x.png",
|
|
222
222
|
});
|
|
223
223
|
});
|
|
224
|
-
await channel.start();
|
|
224
|
+
await channel.ɵruntime.start();
|
|
225
225
|
fake.emitTurn({ userText: "hi", conversationKey: "c1" });
|
|
226
226
|
await tick();
|
|
227
227
|
expect(result).toEqual({
|
|
@@ -243,7 +243,7 @@ describe("createChannel", () => {
|
|
|
243
243
|
history = await thread.getMessages();
|
|
244
244
|
resolved = await thread.lookupUser("Ada");
|
|
245
245
|
});
|
|
246
|
-
await channel.start();
|
|
246
|
+
await channel.ɵruntime.start();
|
|
247
247
|
fake.emitTurn({ userText: "hi", conversationKey: "c1" });
|
|
248
248
|
await tick();
|
|
249
249
|
expect(history).toEqual([
|
|
@@ -267,7 +267,7 @@ describe("createChannel", () => {
|
|
|
267
267
|
],
|
|
268
268
|
}));
|
|
269
269
|
});
|
|
270
|
-
await channel.start();
|
|
270
|
+
await channel.ɵruntime.start();
|
|
271
271
|
fake.emitTurn({ userText: "decide", conversationKey: "c1" });
|
|
272
272
|
await tick();
|
|
273
273
|
const button = findNode(fake.posted[0], "button");
|
|
@@ -297,7 +297,7 @@ describe("createChannel", () => {
|
|
|
297
297
|
runs++;
|
|
298
298
|
await gate;
|
|
299
299
|
});
|
|
300
|
-
await channel.start();
|
|
300
|
+
await channel.ɵruntime.start();
|
|
301
301
|
const sink = fake.getSink();
|
|
302
302
|
const turn = {
|
|
303
303
|
conversationKey: "c1",
|
|
@@ -329,7 +329,7 @@ describe("createChannel", () => {
|
|
|
329
329
|
runs++;
|
|
330
330
|
await gate;
|
|
331
331
|
});
|
|
332
|
-
await channel.start();
|
|
332
|
+
await channel.ɵruntime.start();
|
|
333
333
|
const sink = fake.getSink();
|
|
334
334
|
const turn = {
|
|
335
335
|
conversationKey: "c1",
|
|
@@ -358,7 +358,7 @@ describe("createChannel", () => {
|
|
|
358
358
|
channel.onMention(async () => {
|
|
359
359
|
runs++;
|
|
360
360
|
});
|
|
361
|
-
await channel.start();
|
|
361
|
+
await channel.ɵruntime.start();
|
|
362
362
|
const sink = fake.getSink();
|
|
363
363
|
const base = {
|
|
364
364
|
conversationKey: "c",
|
|
@@ -406,7 +406,7 @@ describe("createChannel", () => {
|
|
|
406
406
|
channel.onMention(async ({ message }) => {
|
|
407
407
|
capturedKey = message.userKey;
|
|
408
408
|
});
|
|
409
|
-
await channel.start();
|
|
409
|
+
await channel.ɵruntime.start();
|
|
410
410
|
const sink = fake.getSink();
|
|
411
411
|
await sink.onTurn({
|
|
412
412
|
conversationKey: "c1",
|
|
@@ -430,7 +430,7 @@ describe("createChannel", () => {
|
|
|
430
430
|
transcripts: { maxPerUser: 50 },
|
|
431
431
|
},
|
|
432
432
|
});
|
|
433
|
-
await channel.start();
|
|
433
|
+
await channel.ɵruntime.start();
|
|
434
434
|
const sink = fake.getSink();
|
|
435
435
|
// Drive a turn so identity is resolved and we can verify transcripts exist
|
|
436
436
|
const thread = { platform: "fake", conversationKey: "c1" };
|
|
@@ -481,7 +481,7 @@ describe("createChannel", () => {
|
|
|
481
481
|
channel.onMention(async ({ thread }) => {
|
|
482
482
|
await thread.runAgent({ transcript: true });
|
|
483
483
|
});
|
|
484
|
-
await channel.start();
|
|
484
|
+
await channel.ɵruntime.start();
|
|
485
485
|
// Seed one prior cross-platform entry (different platform label) so we can
|
|
486
486
|
// assert it shows up in the injected context. Seeded post-start: transcripts
|
|
487
487
|
// are only available once the backend is resolved in start().
|
|
@@ -535,7 +535,7 @@ describe("createChannel", () => {
|
|
|
535
535
|
rejected = true;
|
|
536
536
|
}
|
|
537
537
|
});
|
|
538
|
-
await channel.start();
|
|
538
|
+
await channel.ɵruntime.start();
|
|
539
539
|
fake.emitTurn({ userText: "go", conversationKey: "c1" });
|
|
540
540
|
await tick();
|
|
541
541
|
expect(roundTripped).toEqual({ step: "x" });
|
|
@@ -559,7 +559,7 @@ describe("createChannel lock and dedup edge cases", () => {
|
|
|
559
559
|
await gate;
|
|
560
560
|
throw new Error("boom");
|
|
561
561
|
});
|
|
562
|
-
await bot1.start();
|
|
562
|
+
await bot1.ɵruntime.start();
|
|
563
563
|
const sink = fake.getSink();
|
|
564
564
|
const turn = {
|
|
565
565
|
conversationKey: "c1",
|
|
@@ -601,7 +601,7 @@ describe("createChannel lock and dedup edge cases", () => {
|
|
|
601
601
|
runs++;
|
|
602
602
|
await gate;
|
|
603
603
|
});
|
|
604
|
-
await channel.start();
|
|
604
|
+
await channel.ɵruntime.start();
|
|
605
605
|
const sink = fake.getSink();
|
|
606
606
|
const turn = {
|
|
607
607
|
conversationKey: "c1",
|
|
@@ -634,7 +634,7 @@ describe("createChannel lock and dedup edge cases", () => {
|
|
|
634
634
|
runs++;
|
|
635
635
|
await gate;
|
|
636
636
|
});
|
|
637
|
-
await channel.start();
|
|
637
|
+
await channel.ɵruntime.start();
|
|
638
638
|
const sink = fake.getSink();
|
|
639
639
|
const turn = {
|
|
640
640
|
conversationKey: "c1",
|
|
@@ -665,7 +665,7 @@ describe("createChannel lock and dedup edge cases", () => {
|
|
|
665
665
|
channel.onMention(async ({ message }) => {
|
|
666
666
|
capturedUserKey = message.userKey;
|
|
667
667
|
});
|
|
668
|
-
await channel.start();
|
|
668
|
+
await channel.ɵruntime.start();
|
|
669
669
|
const sink = fake.getSink();
|
|
670
670
|
await sink.onTurn({
|
|
671
671
|
conversationKey: "c1",
|
|
@@ -686,7 +686,7 @@ describe("createChannel lock and dedup edge cases", () => {
|
|
|
686
686
|
channel.onMention(async ({ message }) => {
|
|
687
687
|
capturedUserKey = message.userKey;
|
|
688
688
|
});
|
|
689
|
-
await channel.start();
|
|
689
|
+
await channel.ɵruntime.start();
|
|
690
690
|
const sink = fake.getSink();
|
|
691
691
|
await sink.onTurn({
|
|
692
692
|
conversationKey: "c1",
|
|
@@ -707,7 +707,7 @@ describe("createChannel lock and dedup edge cases", () => {
|
|
|
707
707
|
channel.onMention(async () => {
|
|
708
708
|
runs++;
|
|
709
709
|
});
|
|
710
|
-
await channel.start();
|
|
710
|
+
await channel.ɵruntime.start();
|
|
711
711
|
const sink = fake.getSink();
|
|
712
712
|
const base = {
|
|
713
713
|
conversationKey: "c1",
|
|
@@ -746,7 +746,7 @@ describe("createChannel lock and dedup edge cases", () => {
|
|
|
746
746
|
channel.onMention(async () => {
|
|
747
747
|
runs++;
|
|
748
748
|
});
|
|
749
|
-
await channel.start();
|
|
749
|
+
await channel.ɵruntime.start();
|
|
750
750
|
const sink = fake.getSink();
|
|
751
751
|
await sink.onTurn({
|
|
752
752
|
conversationKey: "c1",
|
|
@@ -772,7 +772,7 @@ describe("createChannel lock and dedup edge cases", () => {
|
|
|
772
772
|
runs++;
|
|
773
773
|
await gate;
|
|
774
774
|
});
|
|
775
|
-
await channel.start();
|
|
775
|
+
await channel.ɵruntime.start();
|
|
776
776
|
const sink = fake.getSink();
|
|
777
777
|
const turnA = {
|
|
778
778
|
conversationKey: "c1",
|
|
@@ -813,7 +813,7 @@ describe("createChannel lock and dedup edge cases", () => {
|
|
|
813
813
|
channel.onMention(async () => {
|
|
814
814
|
runs++;
|
|
815
815
|
});
|
|
816
|
-
await channel.start();
|
|
816
|
+
await channel.ɵruntime.start();
|
|
817
817
|
const sink = fake.getSink();
|
|
818
818
|
const turn = {
|
|
819
819
|
conversationKey: "c2",
|
|
@@ -838,7 +838,7 @@ describe("createChannel slash commands", () => {
|
|
|
838
838
|
channel.onCommand("triage", ({ command, text }) => {
|
|
839
839
|
seen = { command, text };
|
|
840
840
|
});
|
|
841
|
-
await channel.start();
|
|
841
|
+
await channel.ɵruntime.start();
|
|
842
842
|
await fake.emitCommand({ command: "/Triage", text: "db is down" });
|
|
843
843
|
expect(seen).toEqual({ command: "triage", text: "db is down" });
|
|
844
844
|
});
|
|
@@ -849,7 +849,7 @@ describe("createChannel slash commands", () => {
|
|
|
849
849
|
channel.onCommand("triage", () => {
|
|
850
850
|
fired = true;
|
|
851
851
|
});
|
|
852
|
-
await channel.start();
|
|
852
|
+
await channel.ɵruntime.start();
|
|
853
853
|
await fake.emitCommand({ command: "unknown", text: "x" });
|
|
854
854
|
expect(fired).toBe(false);
|
|
855
855
|
});
|
|
@@ -868,7 +868,7 @@ describe("createChannel slash commands", () => {
|
|
|
868
868
|
}),
|
|
869
869
|
],
|
|
870
870
|
});
|
|
871
|
-
await channel.start();
|
|
871
|
+
await channel.ɵruntime.start();
|
|
872
872
|
await fake.emitCommand({
|
|
873
873
|
command: "book",
|
|
874
874
|
text: "raw",
|
|
@@ -881,7 +881,7 @@ describe("createChannel slash commands", () => {
|
|
|
881
881
|
const channel = createChannel({ adapters: [fake] });
|
|
882
882
|
channel.onCommand("triage", () => { });
|
|
883
883
|
channel.onCommand("status", () => { });
|
|
884
|
-
await channel.start();
|
|
884
|
+
await channel.ɵruntime.start();
|
|
885
885
|
expect(fake.registeredCommands?.map((c) => c.name).sort()).toEqual([
|
|
886
886
|
"status",
|
|
887
887
|
"triage",
|
|
@@ -893,7 +893,7 @@ describe("createChannel slash commands", () => {
|
|
|
893
893
|
const bad = new FakeAdapter({ platform: "telegram", failStart: true });
|
|
894
894
|
const good = new FakeAdapter({ platform: "slack" });
|
|
895
895
|
const channel = createChannel({ adapters: [bad, good] });
|
|
896
|
-
await expect(channel.start()).resolves.toBeUndefined();
|
|
896
|
+
await expect(channel.ɵruntime.start()).resolves.toBeUndefined();
|
|
897
897
|
expect(good.started).toBe(true);
|
|
898
898
|
expect(errSpy.mock.calls.some((c) => String(c[0]).includes("telegram"))).toBe(true);
|
|
899
899
|
}
|
|
@@ -911,7 +911,7 @@ describe("createChannel slash commands", () => {
|
|
|
911
911
|
const good = new FakeAdapter({ platform: "slack" });
|
|
912
912
|
const channel = createChannel({ adapters: [bad, good] });
|
|
913
913
|
channel.onCommand("triage", () => { });
|
|
914
|
-
await expect(channel.start()).resolves.toBeUndefined();
|
|
914
|
+
await expect(channel.ɵruntime.start()).resolves.toBeUndefined();
|
|
915
915
|
expect(good.started).toBe(true);
|
|
916
916
|
expect(good.registeredCommands?.map((c) => c.name)).toEqual(["triage"]);
|
|
917
917
|
expect(errSpy.mock.calls.some((c) => String(c[0]).includes("telegram"))).toBe(true);
|
|
@@ -927,8 +927,8 @@ describe("createChannel slash commands", () => {
|
|
|
927
927
|
const good = new FakeAdapter({ platform: "slack" });
|
|
928
928
|
const stopSpy = vi.spyOn(good, "stop");
|
|
929
929
|
const channel = createChannel({ adapters: [bad, good] });
|
|
930
|
-
await channel.start();
|
|
931
|
-
await expect(channel.stop()).resolves.toBeUndefined();
|
|
930
|
+
await channel.ɵruntime.start();
|
|
931
|
+
await expect(channel.ɵruntime.stop()).resolves.toBeUndefined();
|
|
932
932
|
expect(stopSpy).toHaveBeenCalled();
|
|
933
933
|
expect(errSpy.mock.calls.some((c) => String(c[0]).includes("telegram"))).toBe(true);
|
|
934
934
|
}
|
|
@@ -949,7 +949,7 @@ describe("createChannel slash commands", () => {
|
|
|
949
949
|
const channel = createChannel({});
|
|
950
950
|
expect(channel.adapters).toHaveLength(0);
|
|
951
951
|
const fake = new FakeAdapter();
|
|
952
|
-
channel.addAdapter(fake);
|
|
952
|
+
channel.ɵruntime.addAdapter(fake);
|
|
953
953
|
expect(channel.adapters).toEqual([fake]);
|
|
954
954
|
});
|
|
955
955
|
});
|
|
@@ -15,7 +15,7 @@ describe("channel.onModalSubmit / onModalClose", () => {
|
|
|
15
15
|
hasThread: !!evt.thread,
|
|
16
16
|
});
|
|
17
17
|
});
|
|
18
|
-
await channel.start();
|
|
18
|
+
await channel.ɵruntime.start();
|
|
19
19
|
const res = await fake.emitModalSubmit({
|
|
20
20
|
callbackId: "triage",
|
|
21
21
|
values: { summary: "boom", prio: "high" },
|
|
@@ -37,7 +37,7 @@ describe("channel.onModalSubmit / onModalClose", () => {
|
|
|
37
37
|
const fake = new FakeAdapter();
|
|
38
38
|
const channel = createChannel({ adapters: [fake] });
|
|
39
39
|
channel.onModalSubmit("triage", (evt) => evt.values.summary ? undefined : { errors: { summary: "Required" } });
|
|
40
|
-
await channel.start();
|
|
40
|
+
await channel.ɵruntime.start();
|
|
41
41
|
const res = await fake.emitModalSubmit({
|
|
42
42
|
callbackId: "triage",
|
|
43
43
|
values: {},
|
|
@@ -47,7 +47,7 @@ describe("channel.onModalSubmit / onModalClose", () => {
|
|
|
47
47
|
it("ignores submissions with no registered handler", async () => {
|
|
48
48
|
const fake = new FakeAdapter();
|
|
49
49
|
const channel = createChannel({ adapters: [fake] });
|
|
50
|
-
await channel.start();
|
|
50
|
+
await channel.ɵruntime.start();
|
|
51
51
|
const res = await fake.emitModalSubmit({
|
|
52
52
|
callbackId: "unknown",
|
|
53
53
|
values: {},
|
|
@@ -61,7 +61,7 @@ describe("channel.onModalSubmit / onModalClose", () => {
|
|
|
61
61
|
channel.onModalClose("triage", (evt) => {
|
|
62
62
|
closed.push(evt.callbackId);
|
|
63
63
|
});
|
|
64
|
-
await channel.start();
|
|
64
|
+
await channel.ɵruntime.start();
|
|
65
65
|
await fake.emitModalClose({ callbackId: "triage", user: { id: "U2" } });
|
|
66
66
|
expect(closed).toEqual(["triage"]);
|
|
67
67
|
});
|
package/dist/open-modal.test.js
CHANGED
|
@@ -18,7 +18,7 @@ describe("ctx.openModal", () => {
|
|
|
18
18
|
channel.onInteraction("ck:open", async (ctx) => {
|
|
19
19
|
res = await ctx.openModal(view);
|
|
20
20
|
});
|
|
21
|
-
await channel.start();
|
|
21
|
+
await channel.ɵruntime.start();
|
|
22
22
|
fake.emitInteraction({ id: "ck:open", triggerId: "T123" });
|
|
23
23
|
await tick();
|
|
24
24
|
expect(res).toEqual({ ok: true });
|
|
@@ -33,7 +33,7 @@ describe("ctx.openModal", () => {
|
|
|
33
33
|
channel.onCommand("triage", async (ctx) => {
|
|
34
34
|
res = await ctx.openModal(view);
|
|
35
35
|
});
|
|
36
|
-
await channel.start();
|
|
36
|
+
await channel.ɵruntime.start();
|
|
37
37
|
await fake.emitCommand({ command: "triage", triggerId: "T999" });
|
|
38
38
|
expect(res).toEqual({ ok: true });
|
|
39
39
|
expect(fake.openedModals[0].triggerId).toBe("T999");
|
|
@@ -45,7 +45,7 @@ describe("ctx.openModal", () => {
|
|
|
45
45
|
channel.onInteraction("ck:noop", (ctx) => {
|
|
46
46
|
hasOpen = typeof ctx.openModal === "function";
|
|
47
47
|
});
|
|
48
|
-
await channel.start();
|
|
48
|
+
await channel.ɵruntime.start();
|
|
49
49
|
fake.emitInteraction({ id: "ck:noop" });
|
|
50
50
|
await tick();
|
|
51
51
|
expect(hasOpen).toBe(false);
|
|
@@ -57,7 +57,7 @@ describe("ctx.openModal", () => {
|
|
|
57
57
|
channel.onInteraction("ck:x", (ctx) => {
|
|
58
58
|
hasOpen = typeof ctx.openModal === "function";
|
|
59
59
|
});
|
|
60
|
-
await channel.start();
|
|
60
|
+
await channel.ɵruntime.start();
|
|
61
61
|
fake.emitInteraction({ id: "ck:x", triggerId: "T1" });
|
|
62
62
|
await tick();
|
|
63
63
|
expect(hasOpen).toBe(false);
|
package/dist/reactions.test.js
CHANGED
|
@@ -13,7 +13,7 @@ describe("channel.onReaction", () => {
|
|
|
13
13
|
channel.onReaction([emoji.thumbs_up], (evt) => {
|
|
14
14
|
seen.push({ emoji: evt.emoji, raw: evt.rawEmoji, added: evt.added });
|
|
15
15
|
});
|
|
16
|
-
await channel.start();
|
|
16
|
+
await channel.ɵruntime.start();
|
|
17
17
|
// FakeAdapter.platform === "fake": normalizeEmoji falls through, but engine
|
|
18
18
|
// normalizes by adapter.platform. Use a Slack-style token via a fake whose
|
|
19
19
|
// platform normalizes — here we assert passthrough + catch-all instead.
|
|
@@ -35,7 +35,7 @@ describe("channel.onReaction", () => {
|
|
|
35
35
|
platform: evt.thread.platform,
|
|
36
36
|
});
|
|
37
37
|
});
|
|
38
|
-
await channel.start();
|
|
38
|
+
await channel.ɵruntime.start();
|
|
39
39
|
fake.emitReaction({
|
|
40
40
|
rawEmoji: "🎉",
|
|
41
41
|
added: true,
|
|
@@ -64,7 +64,7 @@ describe("channel.onReaction", () => {
|
|
|
64
64
|
channel.onReaction(["thumbs_up"], (evt) => {
|
|
65
65
|
hits.push(evt.emoji);
|
|
66
66
|
});
|
|
67
|
-
await channel.start();
|
|
67
|
+
await channel.ɵruntime.start();
|
|
68
68
|
fake.emitReaction({ rawEmoji: "thumbsup", added: true }); // Slack alias
|
|
69
69
|
await tick();
|
|
70
70
|
expect(hits).toEqual(["thumbs_up"]);
|
|
@@ -80,7 +80,7 @@ describe("channel.onReaction", () => {
|
|
|
80
80
|
channel.onReaction(["refresh"], (evt) => {
|
|
81
81
|
hits.push(evt.emoji);
|
|
82
82
|
});
|
|
83
|
-
await channel.start();
|
|
83
|
+
await channel.ɵruntime.start();
|
|
84
84
|
fake.emitReaction({
|
|
85
85
|
rawEmoji: "1f504_refresh",
|
|
86
86
|
added: true,
|
|
@@ -101,7 +101,7 @@ describe("channel.onReaction", () => {
|
|
|
101
101
|
children: "hi",
|
|
102
102
|
}));
|
|
103
103
|
});
|
|
104
|
-
await channel.start();
|
|
104
|
+
await channel.ɵruntime.start();
|
|
105
105
|
fake.emitTurn({});
|
|
106
106
|
await tick();
|
|
107
107
|
// The handler is a closure, never serialized into the native payload.
|
|
@@ -128,7 +128,7 @@ describe("channel.onReaction", () => {
|
|
|
128
128
|
children: "hi",
|
|
129
129
|
}));
|
|
130
130
|
});
|
|
131
|
-
await channel.start();
|
|
131
|
+
await channel.ɵruntime.start();
|
|
132
132
|
fake.emitTurn({});
|
|
133
133
|
await tick();
|
|
134
134
|
// Channel delivery: the reaction arrives keyed by the provider ts (NOT the
|
|
@@ -165,7 +165,7 @@ describe("channel.onReaction", () => {
|
|
|
165
165
|
// pre-rendered Message() node.
|
|
166
166
|
await thread.post({ type: Card, props: {} });
|
|
167
167
|
});
|
|
168
|
-
await bot1.start();
|
|
168
|
+
await bot1.ɵruntime.start();
|
|
169
169
|
fake1.emitTurn({});
|
|
170
170
|
await tick();
|
|
171
171
|
// "Restart": a fresh channel + registry sharing the same store, Card re-registered.
|
|
@@ -176,7 +176,7 @@ describe("channel.onReaction", () => {
|
|
|
176
176
|
store: { adapter: backend },
|
|
177
177
|
components: [Card],
|
|
178
178
|
});
|
|
179
|
-
await bot2.start();
|
|
179
|
+
await bot2.ɵruntime.start();
|
|
180
180
|
fake2.emitReaction({ rawEmoji: "🎉", added: true, messageId: "msg-1" });
|
|
181
181
|
await tick();
|
|
182
182
|
expect(seen).toEqual(["🎉"]);
|
|
@@ -194,7 +194,7 @@ describe("channel.onReaction", () => {
|
|
|
194
194
|
children: "hi",
|
|
195
195
|
}));
|
|
196
196
|
});
|
|
197
|
-
await channel.start();
|
|
197
|
+
await channel.ɵruntime.start();
|
|
198
198
|
fake.emitTurn({});
|
|
199
199
|
await tick();
|
|
200
200
|
const before = fake.posted.length;
|
|
@@ -217,7 +217,7 @@ describe("channel.onReaction", () => {
|
|
|
217
217
|
children: "hi",
|
|
218
218
|
}));
|
|
219
219
|
});
|
|
220
|
-
await channel.start();
|
|
220
|
+
await channel.ɵruntime.start();
|
|
221
221
|
fake.emitTurn({});
|
|
222
222
|
await tick();
|
|
223
223
|
fake.emitReaction({ rawEmoji: "🎉", added: true, messageId: "other" });
|
|
@@ -237,7 +237,7 @@ describe("channel.onReaction", () => {
|
|
|
237
237
|
channel.onReaction(["thumbsup"], (evt) => {
|
|
238
238
|
hits.push(`alias:${evt.emoji}`);
|
|
239
239
|
});
|
|
240
|
-
await channel.start();
|
|
240
|
+
await channel.ɵruntime.start();
|
|
241
241
|
fake.emitReaction({ rawEmoji: "thumbsup", added: true }); // Slack alias
|
|
242
242
|
await tick();
|
|
243
243
|
expect(hits).toEqual(["thumbs_up", "alias:thumbs_up"]);
|
|
@@ -28,7 +28,7 @@ describe("createChannel telemetry wiring", () => {
|
|
|
28
28
|
},
|
|
29
29
|
],
|
|
30
30
|
});
|
|
31
|
-
await channel.start();
|
|
31
|
+
await channel.ɵruntime.start();
|
|
32
32
|
const call = capture.mock.calls.find((c) => c[0] === "oss.channel.configured");
|
|
33
33
|
expect(call).toBeDefined();
|
|
34
34
|
expect(call[1].platforms).toEqual(["custom"]); // FakeAdapter.platform "fake" → normalized
|
|
@@ -38,14 +38,17 @@ describe("createChannel telemetry wiring", () => {
|
|
|
38
38
|
it("emits oss.channel.started on start, start_failed (category only) on a throwing adapter", async () => {
|
|
39
39
|
const ok = new FakeAdapter();
|
|
40
40
|
const channel = createChannel({ adapters: [ok] });
|
|
41
|
-
await channel.start();
|
|
41
|
+
await channel.ɵruntime.start();
|
|
42
42
|
expect(capture.mock.calls.find((c) => c[0] === "oss.channel.started")?.[1]
|
|
43
43
|
.startedCount).toBe(1);
|
|
44
44
|
capture.mockClear();
|
|
45
45
|
const bad = new FakeAdapter();
|
|
46
46
|
bad.start = () => Promise.reject(Object.assign(new Error("xoxb-SECRET token bad"), { code: "EAUTH" }));
|
|
47
47
|
const bot2 = createChannel({ adapters: [bad] });
|
|
48
|
-
|
|
48
|
+
// All adapters failed → start() rejects (the channel is dead; the runtime
|
|
49
|
+
// reports status "error"). The start_failed telemetry is still captured
|
|
50
|
+
// before the throw.
|
|
51
|
+
await expect(bot2.ɵruntime.start()).rejects.toThrow(/failed to start/i);
|
|
49
52
|
const f = capture.mock.calls.find((c) => c[0] === "oss.channel.start_failed");
|
|
50
53
|
expect(f).toBeDefined();
|
|
51
54
|
expect(f[1].errorClass).toBe("auth");
|
|
@@ -60,7 +63,7 @@ describe("createChannel telemetry wiring", () => {
|
|
|
60
63
|
channel.onMention(async ({ thread }) => {
|
|
61
64
|
await thread.runAgent();
|
|
62
65
|
});
|
|
63
|
-
await channel.start();
|
|
66
|
+
await channel.ɵruntime.start();
|
|
64
67
|
capture.mockClear();
|
|
65
68
|
fake.emitTurn({ userText: "hi", conversationKey: "c1" });
|
|
66
69
|
await tick();
|
|
@@ -40,7 +40,7 @@ describe("oss.channel.* end-to-end (real ChannelTelemetry, only network boundary
|
|
|
40
40
|
channel.onMention(async ({ thread }) => {
|
|
41
41
|
await thread.runAgent();
|
|
42
42
|
});
|
|
43
|
-
await channel.start();
|
|
43
|
+
await channel.ɵruntime.start();
|
|
44
44
|
fake.emitTurn({ userText: "hi", conversationKey: "c1" });
|
|
45
45
|
await waitFor(() => sendSpy.mock.calls.some((c) => c[0].event === "oss.channel.agent_run"));
|
|
46
46
|
const events = sendSpy.mock.calls.map((c) => c[0].event);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"fake-adapter.d.ts","sourceRoot":"","sources":["../../src/testing/fake-adapter.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EACV,WAAW,EACX,UAAU,EACV,YAAY,EACZ,aAAa,EACd,MAAM,yBAAyB,CAAC;AACjC,OAAO,KAAK,EACV,eAAe,EACf,mBAAmB,EACnB,WAAW,EACX,YAAY,EACZ,mBAAmB,EACnB,gBAAgB,EAChB,eAAe,EACf,WAAW,EAGX,WAAW,EACX,aAAa,EACb,SAAS,EACT,iBAAiB,EACjB,gBAAgB,EAChB,mBAAmB,EACnB,kBAAkB,EAClB,iBAAiB,EAClB,MAAM,wBAAwB,CAAC;AAChC,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,gBAAgB,CAAC;AAClD,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,yBAAyB,CAAC;AAE1D,oHAAoH;AACpH,wBAAgB,mBAAmB,IAAI,WAAW,CAgCjD;AAED,qBAAa,WAAY,YAAW,eAAe;IACjD,QAAQ,SAAU;IAClB,QAAQ,CAAC,YAAY,EAAE,mBAAmB,CAAC;IAC3C,QAAQ,CAAC,aAAa,QAAQ;IAE9B,sEAAsE;IACtE,QAAQ,CAAC,SAAS,EAAE,OAAO,CAAC;IAC5B,4FAA4F;IAC5F,QAAQ,CAAC,oBAAoB,EAAE,OAAO,CAAC;IACvC,oEAAoE;IACpE,QAAQ,CAAC,QAAQ,EAAE,OAAO,CAAC;IAC3B,mGAAmG;IACnG,OAAO,UAAS;IAEhB;;;;;;;;;;;;;OAaG;gBAED,QAAQ,GAAE;QACR,WAAW,CAAC,EAAE,OAAO,CAAC;QACtB,SAAS,CAAC,EAAE,OAAO,CAAC;QACpB,eAAe,CAAC,EAAE,OAAO,CAAC;QAC1B,MAAM,CAAC,EAAE,OAAO,CAAC;QACjB,+FAA+F;QAC/F,QAAQ,CAAC,EAAE,MAAM,CAAC;QAClB,iFAAiF;QACjF,SAAS,CAAC,EAAE,OAAO,CAAC;QACpB,0FAA0F;QAC1F,oBAAoB,CAAC,EAAE,OAAO,CAAC;QAC/B,kEAAkE;QAClE,QAAQ,CAAC,EAAE,OAAO,CAAC;KACf;IA+DR,QAAQ,CAAC,iBAAiB,EAAE,iBAAiB,CAI3C;IAEF,MAAM,EAAE,WAAW,EAAE,EAAE,CAAM;IAC7B,OAAO,EAAE;QAAE,GAAG,EAAE,UAAU,CAAC;QAAC,EAAE,EAAE,WAAW,EAAE,CAAA;KAAE,EAAE,CAAM;IACvD,gBAAgB,EAAE,gBAAgB,EAAE,CAAM;IAC1C,eAAe,CAAC,EAAE,WAAW,CAAC;IAC9B,4DAA4D;IAC5D,QAAQ,EAAE,aAAa,EAAE,CAAM;IAC/B,wDAAwD;IACxD,IAAI,CAAC,EAAE,YAAY,CAAC;IACpB,iHAAiH;IACjH,UAAU,CAAC,EAAE,UAAU,CAAC;IACxB,OAAO,CAAC,IAAI,CAAC,CAAc;IAC3B,OAAO,CAAC,OAAO,CAAK;IAEpB,+FAA+F;IAC/F,OAAO,IAAI,WAAW;
|
|
1
|
+
{"version":3,"file":"fake-adapter.d.ts","sourceRoot":"","sources":["../../src/testing/fake-adapter.ts"],"names":[],"mappings":"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"}
|
|
@@ -142,7 +142,7 @@ export class FakeAdapter {
|
|
|
142
142
|
/** Expose the registered sink so tests can invoke onTurn() directly for overlap/lock tests. */
|
|
143
143
|
getSink() {
|
|
144
144
|
if (!this.sink)
|
|
145
|
-
throw new Error("FakeAdapter: sink not set —
|
|
145
|
+
throw new Error("FakeAdapter: sink not set — start the channel (channel.ɵruntime.start()) first");
|
|
146
146
|
return this.sink;
|
|
147
147
|
}
|
|
148
148
|
async start(sink) {
|
|
@@ -9,7 +9,7 @@ describe("onThreadStarted routing", () => {
|
|
|
9
9
|
channel.onThreadStarted(({ thread, user }) => {
|
|
10
10
|
seen.push({ user: user?.id, platform: thread.platform });
|
|
11
11
|
});
|
|
12
|
-
await channel.start();
|
|
12
|
+
await channel.ɵruntime.start();
|
|
13
13
|
await fake.emitThreadStarted({ user: { id: "U1", name: "Ada" } });
|
|
14
14
|
expect(seen).toEqual([{ user: "U1", platform: "fake" }]);
|
|
15
15
|
});
|
|
@@ -23,14 +23,14 @@ describe("onThreadStarted routing", () => {
|
|
|
23
23
|
channel.onThreadStarted(() => {
|
|
24
24
|
order.push(2);
|
|
25
25
|
});
|
|
26
|
-
await channel.start();
|
|
26
|
+
await channel.ɵruntime.start();
|
|
27
27
|
await fake.emitThreadStarted();
|
|
28
28
|
expect(order).toEqual([1, 2]);
|
|
29
29
|
});
|
|
30
30
|
it("is a no-op when no handler is registered", async () => {
|
|
31
31
|
const fake = new FakeAdapter();
|
|
32
32
|
const channel = createChannel({ adapters: [fake] });
|
|
33
|
-
await channel.start();
|
|
33
|
+
await channel.ɵruntime.start();
|
|
34
34
|
// Should not throw.
|
|
35
35
|
await expect(Promise.resolve(fake.emitThreadStarted())).resolves.toBeUndefined();
|
|
36
36
|
});
|
|
@@ -44,7 +44,7 @@ describe("Thread.setSuggestedPrompts / setTitle capability gating", () => {
|
|
|
44
44
|
results.push(await thread.setSuggestedPrompts([{ title: "Triage", message: "Triage my issues" }], { title: "Try" }));
|
|
45
45
|
results.push(await thread.setTitle("My conversation"));
|
|
46
46
|
});
|
|
47
|
-
await channel.start();
|
|
47
|
+
await channel.ɵruntime.start();
|
|
48
48
|
await fake.emitThreadStarted({ replyTarget: { channel: "D1" } });
|
|
49
49
|
expect(results).toEqual([{ ok: true }, { ok: true }]);
|
|
50
50
|
expect(fake.suggestedPromptsCalls).toHaveLength(1);
|
|
@@ -65,7 +65,7 @@ describe("Thread.setSuggestedPrompts / setTitle capability gating", () => {
|
|
|
65
65
|
results.push(await thread.setSuggestedPrompts([]));
|
|
66
66
|
results.push(await thread.setTitle("nope"));
|
|
67
67
|
});
|
|
68
|
-
await channel.start();
|
|
68
|
+
await channel.ɵruntime.start();
|
|
69
69
|
await fake.emitThreadStarted();
|
|
70
70
|
expect(results[0].ok).toBe(false);
|
|
71
71
|
expect(results[0].error).toMatch(/does not support suggested prompts/);
|
|
@@ -4,7 +4,7 @@ import { FakeAdapter } from "./testing/fake-adapter.js";
|
|
|
4
4
|
async function runOnMessage(fake, fn) {
|
|
5
5
|
const channel = createChannel({ adapters: [fake] });
|
|
6
6
|
channel.onMessage(fn);
|
|
7
|
-
await channel.start();
|
|
7
|
+
await channel.ɵruntime.start();
|
|
8
8
|
fake.emitTurn({ userText: "hi", user: { id: "U1" } });
|
|
9
9
|
await new Promise((r) => setTimeout(r, 0));
|
|
10
10
|
}
|
|
@@ -11,7 +11,7 @@ describe("Thread.react / unreact", () => {
|
|
|
11
11
|
results.push(await thread.react(message.ref, emoji.thumbs_up));
|
|
12
12
|
results.push(await thread.unreact(message.ref, emoji.thumbs_up));
|
|
13
13
|
});
|
|
14
|
-
await channel.start();
|
|
14
|
+
await channel.ɵruntime.start();
|
|
15
15
|
fake.emitTurn({ userText: "hi" });
|
|
16
16
|
await new Promise((r) => setTimeout(r, 0));
|
|
17
17
|
expect(results).toEqual([{ ok: true }, { ok: true }]);
|
|
@@ -29,7 +29,7 @@ describe("Thread.react / unreact", () => {
|
|
|
29
29
|
channel.onMessage(async ({ thread, message }) => {
|
|
30
30
|
res = await thread.react(message.ref, emoji.heart);
|
|
31
31
|
});
|
|
32
|
-
await channel.start();
|
|
32
|
+
await channel.ɵruntime.start();
|
|
33
33
|
fake.emitTurn({ userText: "hi" });
|
|
34
34
|
await new Promise((r) => setTimeout(r, 0));
|
|
35
35
|
expect(res.ok).toBe(false);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@copilotkit/channels-core",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.0",
|
|
4
4
|
"description": "Platform-agnostic JSX channel engine for CopilotKit (createChannel, Thread, PlatformAdapter, ActionStore).",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"repository": {
|
|
@@ -51,9 +51,9 @@
|
|
|
51
51
|
"@ag-ui/client": "0.0.57",
|
|
52
52
|
"@ag-ui/core": "0.0.57",
|
|
53
53
|
"zod-to-json-schema": "^3.24.1",
|
|
54
|
-
"@copilotkit/channels-ui": "~0.
|
|
55
|
-
"@copilotkit/
|
|
56
|
-
"@copilotkit/
|
|
54
|
+
"@copilotkit/channels-ui": "~0.3.0",
|
|
55
|
+
"@copilotkit/shared": "^1.63.2",
|
|
56
|
+
"@copilotkit/core": "^1.63.2"
|
|
57
57
|
},
|
|
58
58
|
"devDependencies": {
|
|
59
59
|
"@types/node": "^22.10.0",
|