@johpaz/hive-sdk 0.0.15 → 0.0.17
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/CHANGELOG.md +35 -1
- package/README.md +179 -57
- package/docs/API-AGENTS.md +9 -9
- package/docs/API-CONTEXT-COMPILER.md +13 -13
- package/docs/API-DAG-SCHEDULER.md +8 -8
- package/docs/API-TOOLS-SKILLS-CHANNELS.md +150 -93
- package/docs/API-WORKERS-EVENTS.md +206 -59
- package/docs/INDEX.md +99 -50
- package/docs/README.md +117 -24
- package/docs/TEMPLATE-HIVE-APP.md +360 -0
- package/package.json +13 -6
- package/packages/cli/bin/hives +2 -0
- package/packages/cli/src/commands/add-skill.ts +42 -0
- package/packages/cli/src/commands/add-tool.ts +45 -0
- package/packages/cli/src/commands/add-worker.ts +49 -0
- package/packages/cli/src/commands/create-app-utils.ts +32 -0
- package/packages/cli/src/commands/create-app.test.ts +151 -0
- package/packages/cli/src/commands/create-app.ts +35 -0
- package/packages/cli/src/index.ts +22 -6
- package/packages/cli/templates/hive-app/.env.example +17 -0
- package/packages/cli/templates/hive-app/docker-compose.yml +20 -0
- package/packages/cli/templates/hive-app/hive.config.ts +19 -0
- package/packages/cli/templates/hive-app/package.json +16 -0
- package/packages/cli/templates/hive-app/src/agents/coordinator.ts +9 -0
- package/packages/cli/templates/hive-app/src/main.ts +56 -0
- package/packages/core/src/auth/auth.ts +108 -0
- package/packages/core/src/auth/index.ts +1 -0
- package/packages/core/src/canvas/canvas.test.ts +32 -0
- package/packages/core/src/canvas/emitter.ts +1 -1
- package/packages/core/src/canvas/index.ts +3 -6
- package/packages/core/src/channels/base.ts +154 -0
- package/packages/core/src/channels/channels.test.ts +18 -0
- package/packages/core/src/channels/discord.ts +273 -0
- package/packages/core/src/channels/index.ts +7 -0
- package/packages/core/src/channels/manager.ts +450 -0
- package/packages/core/src/channels/slack.ts +323 -0
- package/packages/core/src/channels/telegram.ts +612 -0
- package/packages/core/src/channels/webchat.ts +139 -0
- package/packages/core/src/channels/whatsapp.ts +548 -0
- package/packages/core/src/events/agent-bus.ts +460 -0
- package/packages/core/src/events/event-bus.ts +169 -0
- package/packages/core/src/gateway/channel-notify.ts +32 -7
- package/packages/core/src/gateway/gateway.test.ts +38 -0
- package/packages/core/src/gateway/index.ts +2 -1
- package/packages/core/src/gateway/server.ts +139 -0
- package/packages/core/src/heartbeat/index.ts +157 -0
- package/packages/core/src/index.ts +44 -0
- package/packages/core/src/multimodal/index.ts +2 -2
- package/packages/core/src/multimodal/vision-service.ts +283 -0
- package/packages/core/src/plugins/api.ts +128 -0
- package/packages/core/src/plugins/index.ts +2 -0
- package/packages/core/src/plugins/loader.ts +365 -0
- package/packages/core/src/resilience/circuit-breaker.ts +225 -0
- package/packages/core/src/scheduler/CronScheduler.ts +699 -0
- package/packages/core/src/scheduler/dag/AgentExecutor.ts +53 -0
- package/packages/core/src/scheduler/dag/DAGScheduler.ts +250 -0
- package/packages/core/src/scheduler/dag/EventBridge.ts +122 -0
- package/packages/core/src/scheduler/dag/TaskGraph.ts +192 -0
- package/packages/core/src/scheduler/dag/TaskNode.ts +97 -0
- package/packages/core/src/scheduler/dag/TaskResult.ts +22 -0
- package/packages/core/src/scheduler/dag/errors.ts +37 -0
- package/packages/core/src/scheduler/dag/index.ts +26 -0
- package/packages/core/src/scheduler/dag/presets/ResearchPreset.ts +97 -0
- package/packages/core/src/scheduler/dag/strategies/ParallelStrategy.ts +21 -0
- package/packages/core/src/scheduler/dag/strategies/PriorityStrategy.ts +46 -0
- package/packages/core/src/scheduler/index.ts +22 -0
- package/packages/core/src/scheduler/integration.ts +237 -0
- package/packages/core/src/scheduler/scheduler.test.ts +19 -0
- package/packages/core/src/scheduler/types.ts +164 -0
- package/packages/core/src/security/google-chat.ts +269 -0
- package/packages/core/src/security/index.ts +192 -4
- package/packages/core/src/security/rate-limit.ts +270 -0
- package/packages/core/src/security/signal.ts +321 -0
- package/packages/core/src/storage/crypto.ts +198 -66
- package/packages/core/src/storage/storage.test.ts +37 -0
- package/packages/core/src/swarm/swarm.test.ts +24 -0
- package/packages/core/src/tool-runtime/index.ts +522 -0
- package/packages/core/src/tool-runtime/tool-runtime.test.ts +91 -0
- package/packages/core/src/tool-runtime/tool-worker.ts +125 -0
- package/packages/core/src/voice/index.ts +5 -18
- package/packages/core/src/workers/WorkerPool.ts +167 -0
- package/packages/core/src/workers/agent.worker.ts +68 -0
- package/packages/core/src/workers/createWorker.ts +144 -0
- package/packages/core/src/workers/index.ts +5 -0
- package/packages/core/src/workers/workers.test.ts +48 -0
- package/test/setup-db.ts +2 -2
- package/tsconfig.json +2 -1
- package/.github/CODEOWNERS +0 -9
- package/.github/workflows/publish.yml +0 -89
- package/.github/workflows/version-bump.yml +0 -102
- package/bun.lock +0 -543
- package/bunfig.toml +0 -7
- package/packages/core/src/agent/providers.ts +0 -1
- package/packages/core/src/gateway/channel-notify.test.ts +0 -14
|
@@ -0,0 +1,450 @@
|
|
|
1
|
+
import type { Config } from "../config/loader.ts";
|
|
2
|
+
import { logger } from "../utils/logger.ts";
|
|
3
|
+
import type { IChannel, IncomingMessage, MessageHandler } from "./base.ts";
|
|
4
|
+
import { createTelegramChannel, type TelegramConfig } from "./telegram.ts";
|
|
5
|
+
import { createDiscordChannel, type DiscordConfig } from "./discord.ts";
|
|
6
|
+
import { createWebChatChannel, type WebChatConfig } from "./webchat.ts";
|
|
7
|
+
import { createWhatsAppChannel, WhatsAppChannel, type WhatsAppConfig } from "./whatsapp.ts";
|
|
8
|
+
import { createSlackChannel, type SlackConfig } from "./slack.ts";
|
|
9
|
+
import { getDb } from "../storage/SQLiteStorage.ts";
|
|
10
|
+
import { loadChannelConfig } from "../storage/crypto.ts";
|
|
11
|
+
|
|
12
|
+
export class ChannelManager {
|
|
13
|
+
private config: Config;
|
|
14
|
+
private channels: Map<string, IChannel> = new Map();
|
|
15
|
+
private messageHandler?: MessageHandler;
|
|
16
|
+
private log = logger.child("channels");
|
|
17
|
+
|
|
18
|
+
constructor(config: Config) {
|
|
19
|
+
this.config = config;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
onMessage(handler: MessageHandler): void {
|
|
23
|
+
this.messageHandler = handler;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
async initialize(): Promise<void> {
|
|
27
|
+
// Primero, intentar cargar canales desde la BD
|
|
28
|
+
await this.initializeFromDB();
|
|
29
|
+
|
|
30
|
+
// Si no hay canales en la BD, usar config
|
|
31
|
+
if (this.channels.size === 0) {
|
|
32
|
+
await this.initializeFromConfig();
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
this.log.info(`Initialized ${this.channels.size} channel(s)`);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
private async initializeFromDB(): Promise<void> {
|
|
39
|
+
try {
|
|
40
|
+
const db = getDb();
|
|
41
|
+
// Load all active channels - config may be empty for webchat
|
|
42
|
+
const rows = db.query(`
|
|
43
|
+
SELECT id, type, enabled, active FROM channels WHERE enabled = 1 AND active = 1
|
|
44
|
+
`).all() as Array<{ id: string; type: string; enabled: number; active: number }>;
|
|
45
|
+
|
|
46
|
+
for (const row of rows) {
|
|
47
|
+
let config: Record<string, unknown> = {};
|
|
48
|
+
try {
|
|
49
|
+
config = await loadChannelConfig(row.id);
|
|
50
|
+
this.log.debug(`Loaded config for ${row.type}:${row.id}:`, Object.keys(config));
|
|
51
|
+
} catch (error) {
|
|
52
|
+
this.log.warn(`Failed to load config for channel ${row.id}:`, (error as Error).message);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
// Use channel id as accountId
|
|
56
|
+
const accountId = row.id;
|
|
57
|
+
this.log.info(`Creating channel ${row.type}:${accountId} with config keys:`, Object.keys(config));
|
|
58
|
+
await this.createChannel(row.type, accountId, config);
|
|
59
|
+
}
|
|
60
|
+
} catch (error) {
|
|
61
|
+
this.log.debug("No channels found in DB or DB not initialized:", (error as Error).message);
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
private async initializeFromConfig(): Promise<void> {
|
|
66
|
+
const channelConfigs = this.config.channels ?? {};
|
|
67
|
+
|
|
68
|
+
for (const [channelName, channelConfig] of Object.entries(channelConfigs)) {
|
|
69
|
+
// If enabled is explicitly false, skip
|
|
70
|
+
if (channelConfig.enabled === false) {
|
|
71
|
+
this.log.debug(`Channel ${channelName} is disabled`);
|
|
72
|
+
continue;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
const accounts = channelConfig.accounts;
|
|
76
|
+
if (!accounts || Object.keys(accounts).length === 0) {
|
|
77
|
+
this.log.warn(`Channel ${channelName} has no accounts configured`);
|
|
78
|
+
continue;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
for (const [accountId, accountConfig] of Object.entries(accounts)) {
|
|
82
|
+
const fullConfig = { ...channelConfig, ...accountConfig };
|
|
83
|
+
await this.createChannel(channelName, accountId, fullConfig);
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
private async createChannel(
|
|
89
|
+
channelName: string,
|
|
90
|
+
accountId: string,
|
|
91
|
+
config: Record<string, unknown>
|
|
92
|
+
): Promise<void> {
|
|
93
|
+
let channel: IChannel;
|
|
94
|
+
|
|
95
|
+
try {
|
|
96
|
+
switch (channelName) {
|
|
97
|
+
case "telegram":
|
|
98
|
+
channel = createTelegramChannel(accountId, {
|
|
99
|
+
enabled: true,
|
|
100
|
+
botToken: config.botToken as string,
|
|
101
|
+
dmPolicy: (config.dmPolicy as "open" | "pairing" | "allowlist") ?? "open",
|
|
102
|
+
allowFrom: (config.allowFrom as string[]) ?? [],
|
|
103
|
+
groups: (config.groups as boolean) ?? false,
|
|
104
|
+
} as TelegramConfig);
|
|
105
|
+
break;
|
|
106
|
+
|
|
107
|
+
case "discord":
|
|
108
|
+
channel = createDiscordChannel(accountId, {
|
|
109
|
+
enabled: true,
|
|
110
|
+
botToken: config.botToken as string,
|
|
111
|
+
applicationId: config.applicationId as string,
|
|
112
|
+
dmPolicy: (config.dmPolicy as "open" | "pairing" | "allowlist") ?? "allowlist",
|
|
113
|
+
allowFrom: (config.allowFrom as string[]) ?? [],
|
|
114
|
+
} as DiscordConfig);
|
|
115
|
+
break;
|
|
116
|
+
|
|
117
|
+
case "webchat":
|
|
118
|
+
channel = createWebChatChannel({
|
|
119
|
+
enabled: true,
|
|
120
|
+
dmPolicy: "open",
|
|
121
|
+
allowFrom: [],
|
|
122
|
+
} as WebChatConfig);
|
|
123
|
+
break;
|
|
124
|
+
|
|
125
|
+
case "whatsapp": {
|
|
126
|
+
let coordinatorId = "main";
|
|
127
|
+
try {
|
|
128
|
+
const coordinator = getDb().query(`SELECT id FROM agents WHERE role = 'coordinator' LIMIT 1`).get() as { id: string } | null;
|
|
129
|
+
if (coordinator?.id) coordinatorId = coordinator.id;
|
|
130
|
+
} catch { /* fallback to "main" */ }
|
|
131
|
+
channel = createWhatsAppChannel({
|
|
132
|
+
enabled: true,
|
|
133
|
+
accountId,
|
|
134
|
+
agentId: (config.agentId as string) ?? coordinatorId,
|
|
135
|
+
dmPolicy: (config.dmPolicy as "open" | "pairing" | "allowlist") ?? "allowlist",
|
|
136
|
+
allowFrom: (config.allowFrom as string[]) ?? [],
|
|
137
|
+
acceptGroups: (config.acceptGroups as boolean) ?? false,
|
|
138
|
+
reconnectMaxAttempts: (config.reconnectMaxAttempts as number) ?? 10,
|
|
139
|
+
reconnectBaseDelayMs: (config.reconnectBaseDelayMs as number) ?? 5000,
|
|
140
|
+
} as WhatsAppConfig);
|
|
141
|
+
break;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
case "slack":
|
|
145
|
+
channel = createSlackChannel({
|
|
146
|
+
enabled: true,
|
|
147
|
+
accountId,
|
|
148
|
+
botToken: config.botToken as string,
|
|
149
|
+
signingSecret: config.signingSecret as string,
|
|
150
|
+
port: (config.port as number) ?? 3000,
|
|
151
|
+
dmPolicy: (config.dmPolicy as "open" | "pairing" | "allowlist") ?? "allowlist",
|
|
152
|
+
allowFrom: (config.allowFrom as string[]) ?? [],
|
|
153
|
+
} as SlackConfig);
|
|
154
|
+
break;
|
|
155
|
+
|
|
156
|
+
default:
|
|
157
|
+
this.log.warn(`Unknown channel type: ${channelName}`);
|
|
158
|
+
return;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
channel.onMessage(async (message: IncomingMessage) => {
|
|
162
|
+
if (this.messageHandler) {
|
|
163
|
+
await this.messageHandler(message);
|
|
164
|
+
}
|
|
165
|
+
});
|
|
166
|
+
|
|
167
|
+
const key = `${channelName}:${accountId}`;
|
|
168
|
+
this.channels.set(key, channel);
|
|
169
|
+
|
|
170
|
+
this.log.info(`Created channel: ${key}`);
|
|
171
|
+
} catch (error) {
|
|
172
|
+
this.log.error(`Failed to create channel ${channelName}:${accountId}: ${(error as Error).message}`);
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
async startAll(): Promise<void> {
|
|
177
|
+
const promises: Promise<void>[] = [];
|
|
178
|
+
|
|
179
|
+
for (const [key, channel] of this.channels) {
|
|
180
|
+
if (channel.isRunning()) {
|
|
181
|
+
this.log.info(`Channel ${key} is already running, skipping`);
|
|
182
|
+
continue;
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
// WhatsApp: skip auto-start if no credentials exist.
|
|
186
|
+
// QR generation should only happen when the user explicitly requests connection.
|
|
187
|
+
if (channel instanceof WhatsAppChannel && !channel.hasCredentials()) {
|
|
188
|
+
this.log.info(`${key}: no credentials found, skipping auto-start — connect via UI to generate QR`);
|
|
189
|
+
continue;
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
promises.push(
|
|
193
|
+
channel.start().catch((error) => {
|
|
194
|
+
this.log.error(`Failed to start channel ${key}: ${error.message}`);
|
|
195
|
+
})
|
|
196
|
+
);
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
await Promise.allSettled(promises);
|
|
200
|
+
this.log.info("All channels started");
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
async stopAll(): Promise<void> {
|
|
204
|
+
const promises: Promise<void>[] = [];
|
|
205
|
+
|
|
206
|
+
for (const [key, channel] of this.channels) {
|
|
207
|
+
promises.push(
|
|
208
|
+
channel.stop().catch((error) => {
|
|
209
|
+
this.log.error(`Failed to stop channel ${key}: ${error.message}`);
|
|
210
|
+
})
|
|
211
|
+
);
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
await Promise.allSettled(promises);
|
|
215
|
+
this.log.info("All channels stopped");
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
getChannel(channelName: string, accountId?: string): IChannel | undefined {
|
|
219
|
+
if (accountId) {
|
|
220
|
+
return this.channels.get(`${channelName}:${accountId}`);
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
for (const [key, channel] of this.channels) {
|
|
224
|
+
if (key.startsWith(channelName)) {
|
|
225
|
+
return channel;
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
return undefined;
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
async removeChannel(channelName: string, accountId: string): Promise<void> {
|
|
233
|
+
const key = `${channelName}:${accountId}`;
|
|
234
|
+
await this.stopChannel(channelName, accountId);
|
|
235
|
+
this.channels.delete(key);
|
|
236
|
+
this.log.info(`Removed channel: ${key}`);
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
getAccountConfig(channelName: string, accountId: string): any {
|
|
240
|
+
const channelConfigs = (this.config.channels ?? {}) as Record<string, any>;
|
|
241
|
+
const channelConfig = channelConfigs[channelName];
|
|
242
|
+
if (!channelConfig) return null;
|
|
243
|
+
|
|
244
|
+
const accounts = channelConfig.accounts;
|
|
245
|
+
if (!accounts) return null;
|
|
246
|
+
return accounts[accountId] || null;
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
async startChannel(channelName: string, accountId: string): Promise<void> {
|
|
250
|
+
const key = `${channelName}:${accountId}`;
|
|
251
|
+
let channel = this.channels.get(key);
|
|
252
|
+
|
|
253
|
+
if (!channel) {
|
|
254
|
+
const channelConfigs = (this.config.channels ?? {}) as Record<string, any>;
|
|
255
|
+
const channelConfig = channelConfigs[channelName];
|
|
256
|
+
if (!channelConfig) {
|
|
257
|
+
throw new Error(`Channel configuration not found: ${channelName}`);
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
const accounts = channelConfig.accounts;
|
|
261
|
+
if (!accounts) {
|
|
262
|
+
throw new Error(`Accounts configuration not found for channel ${channelName}`);
|
|
263
|
+
}
|
|
264
|
+
const accountConfig = accounts[accountId];
|
|
265
|
+
if (!accountConfig) {
|
|
266
|
+
throw new Error(`Account configuration not found: ${accountId} for channel ${channelName}`);
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
const fullConfig = { ...channelConfig, ...(accountConfig ?? {}) };
|
|
270
|
+
await this.createChannel(channelName, accountId, fullConfig as any);
|
|
271
|
+
channel = this.channels.get(key);
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
if (!channel) {
|
|
275
|
+
throw new Error(`Failed to instantiate channel: ${key}`);
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
if (channel.isRunning()) {
|
|
279
|
+
this.log.info(`Channel ${key} is already running`);
|
|
280
|
+
return;
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
await channel.start();
|
|
284
|
+
this.log.info(`Started channel: ${key}`);
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
async addChannel(type: string, accountId: string, config: Record<string, unknown>): Promise<void> {
|
|
288
|
+
await this.createChannel(type, accountId, config);
|
|
289
|
+
const channel = this.channels.get(`${type}:${accountId}`);
|
|
290
|
+
if (channel && !channel.isRunning()) {
|
|
291
|
+
await channel.start();
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
getChannelStatus(type: string, accountId: string): { status: string; qrCode?: string } {
|
|
296
|
+
const key = `${type}:${accountId}`;
|
|
297
|
+
const channel = this.channels.get(key);
|
|
298
|
+
if (!channel) return { status: "not_found" };
|
|
299
|
+
|
|
300
|
+
if (type === "whatsapp" && "getConnectionState" in channel) {
|
|
301
|
+
const state = (channel as any).getConnectionState();
|
|
302
|
+
return { status: state.status, qrCode: state.qrCode };
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
if ("getState" in channel) {
|
|
306
|
+
const state = (channel as any).getState();
|
|
307
|
+
return { status: state.status };
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
return { status: channel.isRunning() ? "connected" : "disconnected" };
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
getWhatsAppDetails(accountId: string): {
|
|
314
|
+
status: string;
|
|
315
|
+
phoneNumber?: string;
|
|
316
|
+
waVersion?: string;
|
|
317
|
+
qrCode?: string;
|
|
318
|
+
lastConnected?: number;
|
|
319
|
+
reconnectAttempts: number;
|
|
320
|
+
reconnectMaxAttempts: number;
|
|
321
|
+
error?: string;
|
|
322
|
+
acceptGroups: boolean;
|
|
323
|
+
selfMessagesOnly: boolean;
|
|
324
|
+
dmPolicy?: string;
|
|
325
|
+
allowFrom: string[];
|
|
326
|
+
} | null {
|
|
327
|
+
const key = `whatsapp:${accountId}`;
|
|
328
|
+
const channel = this.channels.get(key) as WhatsAppChannel | undefined;
|
|
329
|
+
|
|
330
|
+
if (!channel) return null;
|
|
331
|
+
|
|
332
|
+
const state = channel.getState();
|
|
333
|
+
const config = channel.getConfig();
|
|
334
|
+
|
|
335
|
+
return {
|
|
336
|
+
status: state.status,
|
|
337
|
+
phoneNumber: state.phoneNumber,
|
|
338
|
+
waVersion: state.waVersion,
|
|
339
|
+
qrCode: state.qrCode,
|
|
340
|
+
lastConnected: state.lastConnected?.getTime(),
|
|
341
|
+
reconnectAttempts: state.reconnectAttempts,
|
|
342
|
+
reconnectMaxAttempts: config.reconnectMaxAttempts ?? 10,
|
|
343
|
+
error: state.error,
|
|
344
|
+
acceptGroups: config.acceptGroups ?? false,
|
|
345
|
+
selfMessagesOnly: config.selfMessagesOnly !== false,
|
|
346
|
+
dmPolicy: config.dmPolicy,
|
|
347
|
+
allowFrom: config.allowFrom ?? [],
|
|
348
|
+
};
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
async stopChannel(channelName: string, accountId: string): Promise<void> {
|
|
352
|
+
const key = `${channelName}:${accountId}`;
|
|
353
|
+
const channel = this.channels.get(key);
|
|
354
|
+
|
|
355
|
+
if (!channel) {
|
|
356
|
+
this.log.debug(`Channel ${key} not instantiated, skipping stop`);
|
|
357
|
+
return;
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
if (!channel.isRunning()) {
|
|
361
|
+
this.log.info(`Channel ${key} is not running`);
|
|
362
|
+
return;
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
await channel.stop();
|
|
366
|
+
this.log.info(`Stopped channel: ${key}`);
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
listAllAvailableChannels(): Array<{ name: string; accountId: string; running: boolean; enabled: boolean }> {
|
|
370
|
+
const available: Array<{ name: string; accountId: string; running: boolean; enabled: boolean }> = [];
|
|
371
|
+
const channelConfigs = (this.config.channels ?? {}) as Record<string, any>;
|
|
372
|
+
|
|
373
|
+
for (const [channelName, channelConfig] of Object.entries(channelConfigs)) {
|
|
374
|
+
const accounts = channelConfig.accounts;
|
|
375
|
+
if (!accounts) continue;
|
|
376
|
+
for (const accountId of Object.keys(accounts)) {
|
|
377
|
+
const key = `${channelName}:${accountId}`;
|
|
378
|
+
const channel = this.channels.get(key);
|
|
379
|
+
available.push({
|
|
380
|
+
name: channelName,
|
|
381
|
+
accountId: accountId,
|
|
382
|
+
running: channel ? channel.isRunning() : false,
|
|
383
|
+
enabled: channelConfig.enabled !== false,
|
|
384
|
+
});
|
|
385
|
+
}
|
|
386
|
+
}
|
|
387
|
+
return available;
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
listChannels(): Array<{ name: string; accountId: string; running: boolean }> {
|
|
391
|
+
return Array.from(this.channels.entries()).map(([key, channel]) => {
|
|
392
|
+
const [name, accountId] = key.split(":");
|
|
393
|
+
return {
|
|
394
|
+
name: name ?? "unknown",
|
|
395
|
+
accountId: accountId ?? "unknown",
|
|
396
|
+
running: channel.isRunning(),
|
|
397
|
+
};
|
|
398
|
+
});
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
async send(
|
|
402
|
+
channelName: string,
|
|
403
|
+
sessionId: string,
|
|
404
|
+
message: unknown
|
|
405
|
+
): Promise<void> {
|
|
406
|
+
const channel = this.getChannel(channelName);
|
|
407
|
+
|
|
408
|
+
if (!channel) {
|
|
409
|
+
throw new Error(`Channel not found: ${channelName}`);
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
await channel.send(sessionId, message as any);
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
async startTyping(channelName: string, sessionId: string): Promise<void> {
|
|
416
|
+
const channel = this.getChannel(channelName);
|
|
417
|
+
if (channel?.startTyping) {
|
|
418
|
+
await channel.startTyping(sessionId);
|
|
419
|
+
}
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
async stopTyping(channelName: string, sessionId: string): Promise<void> {
|
|
423
|
+
const channel = this.getChannel(channelName);
|
|
424
|
+
if (channel?.stopTyping) {
|
|
425
|
+
await channel.stopTyping(sessionId);
|
|
426
|
+
}
|
|
427
|
+
}
|
|
428
|
+
|
|
429
|
+
async markAsRead(channelName: string, sessionId: string, messageId?: string): Promise<void> {
|
|
430
|
+
const channel = this.getChannel(channelName);
|
|
431
|
+
if (channel?.markAsRead) {
|
|
432
|
+
await channel.markAsRead(sessionId, messageId);
|
|
433
|
+
}
|
|
434
|
+
}
|
|
435
|
+
|
|
436
|
+
async sendAudio(channelName: string, sessionId: string, audio: Buffer, mimeType: string): Promise<void> {
|
|
437
|
+
const channel = this.getChannel(channelName);
|
|
438
|
+
if (!channel) {
|
|
439
|
+
throw new Error(`Channel not found: ${channelName}`);
|
|
440
|
+
}
|
|
441
|
+
if (!channel.sendAudio) {
|
|
442
|
+
throw new Error(`Channel ${channelName} does not support audio`);
|
|
443
|
+
}
|
|
444
|
+
await channel.sendAudio(sessionId, audio, mimeType);
|
|
445
|
+
}
|
|
446
|
+
}
|
|
447
|
+
|
|
448
|
+
export function createChannelManager(config: Config): ChannelManager {
|
|
449
|
+
return new ChannelManager(config);
|
|
450
|
+
}
|