@copilotkit/channels-core 0.1.1 → 0.2.1-canary.1784916581
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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 +143 -124
- 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/platform-adapter.d.ts +9 -0
- package/dist/platform-adapter.d.ts.map +1 -1
- package/dist/reactions.test.js +30 -10
- 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/dist/create-bot.d.ts +0 -171
- package/dist/create-bot.d.ts.map +0 -1
- package/dist/create-bot.foundations.test.d.ts +0 -2
- package/dist/create-bot.foundations.test.d.ts.map +0 -1
- package/dist/create-bot.foundations.test.js +0 -147
- package/dist/create-bot.js +0 -579
- package/dist/create-bot.test.d.ts +0 -2
- package/dist/create-bot.test.d.ts.map +0 -1
- package/dist/create-bot.test.js +0 -936
- package/dist/telemetry/bot-telemetry.d.ts +0 -31
- package/dist/telemetry/bot-telemetry.d.ts.map +0 -1
- package/dist/telemetry/bot-telemetry.js +0 -70
- package/dist/telemetry/bot-telemetry.test.d.ts +0 -2
- package/dist/telemetry/bot-telemetry.test.d.ts.map +0 -1
- package/dist/telemetry/bot-telemetry.test.js +0 -56
- package/dist/telemetry/create-bot-telemetry.test.d.ts +0 -2
- package/dist/telemetry/create-bot-telemetry.test.d.ts.map +0 -1
- package/dist/telemetry/create-bot-telemetry.test.js +0 -71
package/README.md
CHANGED
|
@@ -4,6 +4,11 @@ The supported platform-neutral foundation behind `@copilotkit/channels`.
|
|
|
4
4
|
Most applications should use the batteries-included `@copilotkit/channels` package;
|
|
5
5
|
install core directly when building an adapter or intentionally selecting one platform.
|
|
6
6
|
|
|
7
|
+
**Every Channel requires a CopilotKit Intelligence connection** (an API key — a
|
|
8
|
+
free tier is available). There is no standalone / DIY run path: a Channel is
|
|
9
|
+
started and owned by the `CopilotRuntime` once Intelligence is configured, not
|
|
10
|
+
by calling a method on the Channel itself. See "Running a Channel" below.
|
|
11
|
+
|
|
7
12
|
## Selective install
|
|
8
13
|
|
|
9
14
|
```sh
|
|
@@ -45,12 +50,50 @@ import { slack } from "@copilotkit/channels-slack";
|
|
|
45
50
|
(e.g. Discord). Forwarded to adapters that support commands and ignored
|
|
46
51
|
elsewhere — also pass them up front via `commands` in `CreateChannelOptions`.
|
|
47
52
|
- `tool(t)` — register a `ChannelTool` (alternative to `opts.tools`); must be
|
|
48
|
-
added before
|
|
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;
|
|
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
|
@@ -25,6 +25,8 @@ const EMOJI_PLATFORMS = new Set([
|
|
|
25
25
|
"slack",
|
|
26
26
|
"discord",
|
|
27
27
|
"telegram",
|
|
28
|
+
"teams",
|
|
29
|
+
"whatsapp",
|
|
28
30
|
]);
|
|
29
31
|
function isEmojiPlatform(platform) {
|
|
30
32
|
return EMOJI_PLATFORMS.has(platform);
|
|
@@ -77,16 +79,18 @@ export function createChannel(opts) {
|
|
|
77
79
|
(!cfg.identity && cfg.transcripts)) {
|
|
78
80
|
throw new Error("createChannel: `identity` and `transcripts` must be configured together.");
|
|
79
81
|
}
|
|
80
|
-
// Adapters can be supplied up front or added later via
|
|
81
|
-
// (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.
|
|
82
85
|
const adapters = [...(opts.adapters ?? [])];
|
|
83
86
|
assertExclusive(adapters);
|
|
84
87
|
let started = false;
|
|
85
88
|
// Backend, transcripts, telemetry, the action registry, and component
|
|
86
|
-
// registration are resolved in
|
|
87
|
-
// adapter added via
|
|
88
|
-
// persistence backend (see `resolveBackend`). Nothing
|
|
89
|
-
// 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()`.
|
|
90
94
|
let backend;
|
|
91
95
|
let transcripts;
|
|
92
96
|
let registry;
|
|
@@ -121,7 +125,7 @@ export function createChannel(opts) {
|
|
|
121
125
|
let toolDescriptors = toAgentToolDescriptors([...toolMap.values()]);
|
|
122
126
|
function makeThread(adapter, replyTarget, conversationKey, extras) {
|
|
123
127
|
if (!backend || !registry || !telemetry) {
|
|
124
|
-
throw new Error("channel not started:
|
|
128
|
+
throw new Error("channel not started: the runner must start the channel (channel.ɵruntime.start()) before handling events");
|
|
125
129
|
}
|
|
126
130
|
const deps = {
|
|
127
131
|
adapter,
|
|
@@ -326,10 +330,15 @@ export function createChannel(opts) {
|
|
|
326
330
|
await h({ thread, user: evt.user });
|
|
327
331
|
},
|
|
328
332
|
async onReaction(evt) {
|
|
329
|
-
//
|
|
333
|
+
// Normalize by the reaction's SOURCE platform: direct adapters omit
|
|
334
|
+
// `evt.platform`, so it falls back to `adapter.platform`; the managed
|
|
335
|
+
// path (adapter.platform === "intelligence") sets `evt.platform` to the
|
|
336
|
+
// originating provider (e.g. "teams"/"slack") so central normalization
|
|
337
|
+
// still runs. Only normalize when that platform is one the emoji table
|
|
330
338
|
// knows; otherwise the raw token passes through unchanged.
|
|
331
|
-
const
|
|
332
|
-
|
|
339
|
+
const sourcePlatform = evt.platform ?? adapter.platform;
|
|
340
|
+
const normalized = isEmojiPlatform(sourcePlatform)
|
|
341
|
+
? normalizeEmoji(evt.rawEmoji, sourcePlatform)
|
|
333
342
|
: undefined;
|
|
334
343
|
const value = normalized ?? evt.rawEmoji;
|
|
335
344
|
const thread = makeThread(adapter, evt.replyTarget, evt.conversationKey);
|
|
@@ -412,16 +421,133 @@ export function createChannel(opts) {
|
|
|
412
421
|
},
|
|
413
422
|
get transcripts() {
|
|
414
423
|
if (!transcripts) {
|
|
415
|
-
throw new Error("channel.transcripts is available after channel.start()");
|
|
424
|
+
throw new Error("channel.transcripts is available after the runner starts the channel (channel.ɵruntime.start())");
|
|
416
425
|
}
|
|
417
426
|
return transcripts;
|
|
418
427
|
},
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
428
|
+
ɵruntime: {
|
|
429
|
+
addAdapter(adapter) {
|
|
430
|
+
if (started) {
|
|
431
|
+
throw new Error("channel.ɵruntime.addAdapter must be called before channel.ɵruntime.start()");
|
|
432
|
+
}
|
|
433
|
+
assertExclusive([...adapters, adapter]);
|
|
434
|
+
adapters.push(adapter);
|
|
435
|
+
},
|
|
436
|
+
async start() {
|
|
437
|
+
// Idempotent: a second start() must not re-resolve the backend and
|
|
438
|
+
// rebuild Transcripts/Telemetry/ActionRegistry or re-call adapter.start()
|
|
439
|
+
// — with a MemoryStore that wipes all lock/dedup/transcript/action state,
|
|
440
|
+
// and real adapters would connect/port-bind twice.
|
|
441
|
+
if (started)
|
|
442
|
+
return;
|
|
443
|
+
started = true;
|
|
444
|
+
assertExclusive(adapters);
|
|
445
|
+
// Resolve persistence now that all adapters (including any attached via
|
|
446
|
+
// addAdapter) are known, then build the transcript store, action
|
|
447
|
+
// registry, and register components against it.
|
|
448
|
+
backend = resolveBackend(cfg.adapter, adapters);
|
|
449
|
+
transcripts = new Transcripts(backend, cfg.transcripts ?? {});
|
|
450
|
+
const tel = new ChannelTelemetry({
|
|
451
|
+
backend,
|
|
452
|
+
packageName: pkg.name,
|
|
453
|
+
packageVersion: pkg.version,
|
|
454
|
+
});
|
|
455
|
+
telemetry = tel;
|
|
456
|
+
const registryInstance = new ActionRegistry({
|
|
457
|
+
store: opts.actionStore ?? kvActionStore(backend),
|
|
458
|
+
});
|
|
459
|
+
registry = registryInstance;
|
|
460
|
+
for (const c of opts.components ?? []) {
|
|
461
|
+
if (!c.name) {
|
|
462
|
+
console.warn("[channel] createChannel: skipping anonymous component — give it a name to enable durable actions after restart.");
|
|
463
|
+
continue;
|
|
464
|
+
}
|
|
465
|
+
registryInstance.registerComponent(c.name, c);
|
|
466
|
+
}
|
|
467
|
+
toolDescriptors = toAgentToolDescriptors([...toolMap.values()]);
|
|
468
|
+
tel.capture("oss.channel.configured", {
|
|
469
|
+
platforms: adapters.map((a) => normalizePlatform(a.platform)),
|
|
470
|
+
adapterCount: adapters.length,
|
|
471
|
+
store: storeKind(backend),
|
|
472
|
+
hasComponents: (opts.components?.length ?? 0) > 0,
|
|
473
|
+
componentsCount: opts.components?.length ?? 0,
|
|
474
|
+
toolsCount: toolMap.size,
|
|
475
|
+
commandsCount: commandHandlers.size,
|
|
476
|
+
contextCount: context.length,
|
|
477
|
+
transcripts: !!cfg.transcripts,
|
|
478
|
+
identity: !!cfg.identity,
|
|
479
|
+
});
|
|
480
|
+
// Isolate per-adapter startup failures: one adapter rejecting (e.g.
|
|
481
|
+
// Telegram's setMyCommands rejecting a hyphenated command name, a revoked
|
|
482
|
+
// token, a port already in use) must NOT crash the channel or prevent the
|
|
483
|
+
// other adapters from starting. Log + degrade, never throw.
|
|
484
|
+
const startResults = await Promise.allSettled(adapters.map((a) => a.start(makeSink(a), { channelName: opts.name })));
|
|
485
|
+
const startedPlatforms = [];
|
|
486
|
+
const failedPlatforms = [];
|
|
487
|
+
startResults.forEach((r, i) => {
|
|
488
|
+
const rawPlatform = adapters[i].platform;
|
|
489
|
+
// Raw label for the human-facing log; normalized label for telemetry.
|
|
490
|
+
const platform = normalizePlatform(rawPlatform);
|
|
491
|
+
if (r.status === "rejected") {
|
|
492
|
+
failedPlatforms.push(platform);
|
|
493
|
+
console.error(`[channel] adapter "${rawPlatform}" failed to start:`, r.reason);
|
|
494
|
+
tel.capture("oss.channel.start_failed", {
|
|
495
|
+
platform,
|
|
496
|
+
errorClass: errorClass(r.reason),
|
|
497
|
+
});
|
|
498
|
+
}
|
|
499
|
+
else {
|
|
500
|
+
startedPlatforms.push(platform);
|
|
501
|
+
}
|
|
502
|
+
});
|
|
503
|
+
if (startedPlatforms.length > 0) {
|
|
504
|
+
tel.capture("oss.channel.started", {
|
|
505
|
+
platforms: startedPlatforms,
|
|
506
|
+
startedCount: startedPlatforms.length,
|
|
507
|
+
failedCount: failedPlatforms.length,
|
|
508
|
+
hasMentionHandler: mentionHandlers.length > 0,
|
|
509
|
+
hasMessageHandler: messageHandlers.length > 0,
|
|
510
|
+
interruptHandlers: interruptHandlers.size,
|
|
511
|
+
commandsCount: commandHandlers.size,
|
|
512
|
+
toolsCount: toolMap.size,
|
|
513
|
+
});
|
|
514
|
+
}
|
|
515
|
+
// A channel that has adapters but where NONE started is dead — surface an
|
|
516
|
+
// error so the runtime reports status "error", not a false "online". A
|
|
517
|
+
// PARTIAL start (>=1 adapter live) still counts as started. Reset the
|
|
518
|
+
// `started` guard so a caller can retry after fixing the misconfiguration.
|
|
519
|
+
if (adapters.length > 0 && startedPlatforms.length === 0) {
|
|
520
|
+
started = false;
|
|
521
|
+
throw new Error(`channel "${opts.name ?? "(unnamed)"}" failed to start: all ${failedPlatforms.length} adapter(s) failed to connect (${failedPlatforms.join(", ")}) — see the logged errors above`);
|
|
522
|
+
}
|
|
523
|
+
// Hand declared commands to adapters that register them up front (e.g.
|
|
524
|
+
// Discord); adapters without `registerCommands` are skipped. Per-adapter
|
|
525
|
+
// failures are isolated the same way as start().
|
|
526
|
+
const commandSpecs = [...commandHandlers.values()].map(toCommandSpec);
|
|
527
|
+
if (commandSpecs.length > 0) {
|
|
528
|
+
const registerResults = await Promise.allSettled(adapters.map((a) => a.registerCommands?.(commandSpecs)));
|
|
529
|
+
registerResults.forEach((r, i) => {
|
|
530
|
+
if (r.status === "rejected") {
|
|
531
|
+
console.error(`[channel] adapter "${adapters[i].platform}" failed to register commands:`, r.reason);
|
|
532
|
+
}
|
|
533
|
+
});
|
|
534
|
+
}
|
|
535
|
+
},
|
|
536
|
+
async stop() {
|
|
537
|
+
// Clear the started flag so a later start() is a real restart (re-resolve
|
|
538
|
+
// backend, rebuild components, reconnect adapters) rather than a silent
|
|
539
|
+
// no-op. The idempotency guard in start() only exists to block a DOUBLE
|
|
540
|
+
// start() while running — not a legitimate start→stop→start cycle.
|
|
541
|
+
started = false;
|
|
542
|
+
// Isolate per-adapter shutdown failures: one adapter's stop() rejecting
|
|
543
|
+
// must not prevent the others from being stopped.
|
|
544
|
+
const stopResults = await Promise.allSettled(adapters.map((a) => a.stop()));
|
|
545
|
+
stopResults.forEach((r, i) => {
|
|
546
|
+
if (r.status === "rejected") {
|
|
547
|
+
console.error(`[channel] adapter "${adapters[i].platform}" failed to stop:`, r.reason);
|
|
548
|
+
}
|
|
549
|
+
});
|
|
550
|
+
},
|
|
425
551
|
},
|
|
426
552
|
onMention(h) {
|
|
427
553
|
// The public surface narrows `thread` to StatefulThread<TState>; the
|
|
@@ -476,113 +602,6 @@ export function createChannel(opts) {
|
|
|
476
602
|
tool(t) {
|
|
477
603
|
toolMap.set(t.name, t);
|
|
478
604
|
},
|
|
479
|
-
async start() {
|
|
480
|
-
// Idempotent: a second start() must not re-resolve the backend and
|
|
481
|
-
// rebuild Transcripts/Telemetry/ActionRegistry or re-call adapter.start()
|
|
482
|
-
// — with a MemoryStore that wipes all lock/dedup/transcript/action state,
|
|
483
|
-
// and real adapters would connect/port-bind twice.
|
|
484
|
-
if (started)
|
|
485
|
-
return;
|
|
486
|
-
started = true;
|
|
487
|
-
assertExclusive(adapters);
|
|
488
|
-
// Resolve persistence now that all adapters (including any attached via
|
|
489
|
-
// addAdapter) are known, then build the transcript store, action
|
|
490
|
-
// registry, and register components against it.
|
|
491
|
-
backend = resolveBackend(cfg.adapter, adapters);
|
|
492
|
-
transcripts = new Transcripts(backend, cfg.transcripts ?? {});
|
|
493
|
-
const tel = new ChannelTelemetry({
|
|
494
|
-
backend,
|
|
495
|
-
packageName: pkg.name,
|
|
496
|
-
packageVersion: pkg.version,
|
|
497
|
-
});
|
|
498
|
-
telemetry = tel;
|
|
499
|
-
const registryInstance = new ActionRegistry({
|
|
500
|
-
store: opts.actionStore ?? kvActionStore(backend),
|
|
501
|
-
});
|
|
502
|
-
registry = registryInstance;
|
|
503
|
-
for (const c of opts.components ?? []) {
|
|
504
|
-
if (!c.name) {
|
|
505
|
-
console.warn("[channel] createChannel: skipping anonymous component — give it a name to enable durable actions after restart.");
|
|
506
|
-
continue;
|
|
507
|
-
}
|
|
508
|
-
registryInstance.registerComponent(c.name, c);
|
|
509
|
-
}
|
|
510
|
-
toolDescriptors = toAgentToolDescriptors([...toolMap.values()]);
|
|
511
|
-
tel.capture("oss.channel.configured", {
|
|
512
|
-
platforms: adapters.map((a) => normalizePlatform(a.platform)),
|
|
513
|
-
adapterCount: adapters.length,
|
|
514
|
-
store: storeKind(backend),
|
|
515
|
-
hasComponents: (opts.components?.length ?? 0) > 0,
|
|
516
|
-
componentsCount: opts.components?.length ?? 0,
|
|
517
|
-
toolsCount: toolMap.size,
|
|
518
|
-
commandsCount: commandHandlers.size,
|
|
519
|
-
contextCount: context.length,
|
|
520
|
-
transcripts: !!cfg.transcripts,
|
|
521
|
-
identity: !!cfg.identity,
|
|
522
|
-
});
|
|
523
|
-
// Isolate per-adapter startup failures: one adapter rejecting (e.g.
|
|
524
|
-
// Telegram's setMyCommands rejecting a hyphenated command name, a revoked
|
|
525
|
-
// token, a port already in use) must NOT crash the channel or prevent the
|
|
526
|
-
// other adapters from starting. Log + degrade, never throw.
|
|
527
|
-
const startResults = await Promise.allSettled(adapters.map((a) => a.start(makeSink(a), { channelName: opts.name })));
|
|
528
|
-
const startedPlatforms = [];
|
|
529
|
-
const failedPlatforms = [];
|
|
530
|
-
startResults.forEach((r, i) => {
|
|
531
|
-
const rawPlatform = adapters[i].platform;
|
|
532
|
-
// Raw label for the human-facing log; normalized label for telemetry.
|
|
533
|
-
const platform = normalizePlatform(rawPlatform);
|
|
534
|
-
if (r.status === "rejected") {
|
|
535
|
-
failedPlatforms.push(platform);
|
|
536
|
-
console.error(`[channel] adapter "${rawPlatform}" failed to start:`, r.reason);
|
|
537
|
-
tel.capture("oss.channel.start_failed", {
|
|
538
|
-
platform,
|
|
539
|
-
errorClass: errorClass(r.reason),
|
|
540
|
-
});
|
|
541
|
-
}
|
|
542
|
-
else {
|
|
543
|
-
startedPlatforms.push(platform);
|
|
544
|
-
}
|
|
545
|
-
});
|
|
546
|
-
if (startedPlatforms.length > 0) {
|
|
547
|
-
tel.capture("oss.channel.started", {
|
|
548
|
-
platforms: startedPlatforms,
|
|
549
|
-
startedCount: startedPlatforms.length,
|
|
550
|
-
failedCount: failedPlatforms.length,
|
|
551
|
-
hasMentionHandler: mentionHandlers.length > 0,
|
|
552
|
-
hasMessageHandler: messageHandlers.length > 0,
|
|
553
|
-
interruptHandlers: interruptHandlers.size,
|
|
554
|
-
commandsCount: commandHandlers.size,
|
|
555
|
-
toolsCount: toolMap.size,
|
|
556
|
-
});
|
|
557
|
-
}
|
|
558
|
-
// Hand declared commands to adapters that register them up front (e.g.
|
|
559
|
-
// Discord); adapters without `registerCommands` are skipped. Per-adapter
|
|
560
|
-
// failures are isolated the same way as start().
|
|
561
|
-
const commandSpecs = [...commandHandlers.values()].map(toCommandSpec);
|
|
562
|
-
if (commandSpecs.length > 0) {
|
|
563
|
-
const registerResults = await Promise.allSettled(adapters.map((a) => a.registerCommands?.(commandSpecs)));
|
|
564
|
-
registerResults.forEach((r, i) => {
|
|
565
|
-
if (r.status === "rejected") {
|
|
566
|
-
console.error(`[channel] adapter "${adapters[i].platform}" failed to register commands:`, r.reason);
|
|
567
|
-
}
|
|
568
|
-
});
|
|
569
|
-
}
|
|
570
|
-
},
|
|
571
|
-
async stop() {
|
|
572
|
-
// Clear the started flag so a later start() is a real restart (re-resolve
|
|
573
|
-
// backend, rebuild components, reconnect adapters) rather than a silent
|
|
574
|
-
// no-op. The idempotency guard in start() only exists to block a DOUBLE
|
|
575
|
-
// start() while running — not a legitimate start→stop→start cycle.
|
|
576
|
-
started = false;
|
|
577
|
-
// Isolate per-adapter shutdown failures: one adapter's stop() rejecting
|
|
578
|
-
// must not prevent the others from being stopped.
|
|
579
|
-
const stopResults = await Promise.allSettled(adapters.map((a) => a.stop()));
|
|
580
|
-
stopResults.forEach((r, i) => {
|
|
581
|
-
if (r.status === "rejected") {
|
|
582
|
-
console.error(`[channel] adapter "${adapters[i].platform}" failed to stop:`, r.reason);
|
|
583
|
-
}
|
|
584
|
-
});
|
|
585
|
-
},
|
|
586
605
|
};
|
|
587
606
|
return channel;
|
|
588
607
|
}
|