beecork 1.7.0 → 2.0.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 +52 -111
- package/dist/index.js +1394 -581
- package/package.json +26 -54
- package/dist/capabilities/index.d.ts +0 -3
- package/dist/capabilities/index.js +0 -2
- package/dist/capabilities/manager.d.ts +0 -13
- package/dist/capabilities/manager.js +0 -115
- package/dist/capabilities/packs.d.ts +0 -2
- package/dist/capabilities/packs.js +0 -54
- package/dist/capabilities/types.d.ts +0 -21
- package/dist/capabilities/types.js +0 -1
- package/dist/channels/admin.d.ts +0 -10
- package/dist/channels/admin.js +0 -20
- package/dist/channels/command-handler.d.ts +0 -19
- package/dist/channels/command-handler.js +0 -235
- package/dist/channels/discord.d.ts +0 -17
- package/dist/channels/discord.js +0 -266
- package/dist/channels/index.d.ts +0 -7
- package/dist/channels/index.js +0 -6
- package/dist/channels/loader.d.ts +0 -7
- package/dist/channels/loader.js +0 -87
- package/dist/channels/pipeline.d.ts +0 -50
- package/dist/channels/pipeline.js +0 -92
- package/dist/channels/registry.d.ts +0 -36
- package/dist/channels/registry.js +0 -80
- package/dist/channels/send-helpers.d.ts +0 -19
- package/dist/channels/send-helpers.js +0 -21
- package/dist/channels/telegram.d.ts +0 -47
- package/dist/channels/telegram.js +0 -649
- package/dist/channels/types.d.ts +0 -41
- package/dist/channels/types.js +0 -1
- package/dist/channels/voice-state.d.ts +0 -29
- package/dist/channels/voice-state.js +0 -45
- package/dist/channels/webhook.d.ts +0 -16
- package/dist/channels/webhook.js +0 -222
- package/dist/channels/whatsapp.d.ts +0 -28
- package/dist/channels/whatsapp.js +0 -323
- package/dist/cli/capabilities.d.ts +0 -3
- package/dist/cli/capabilities.js +0 -72
- package/dist/cli/channel.d.ts +0 -3
- package/dist/cli/channel.js +0 -202
- package/dist/cli/commands.d.ts +0 -15
- package/dist/cli/commands.js +0 -279
- package/dist/cli/doctor.d.ts +0 -1
- package/dist/cli/doctor.js +0 -208
- package/dist/cli/handoff.d.ts +0 -8
- package/dist/cli/handoff.js +0 -33
- package/dist/cli/helpers.d.ts +0 -2
- package/dist/cli/helpers.js +0 -21
- package/dist/cli/mcp.d.ts +0 -3
- package/dist/cli/mcp.js +0 -47
- package/dist/cli/media.d.ts +0 -2
- package/dist/cli/media.js +0 -125
- package/dist/cli/setup.d.ts +0 -1
- package/dist/cli/setup.js +0 -239
- package/dist/cli/store.d.ts +0 -3
- package/dist/cli/store.js +0 -108
- package/dist/config.d.ts +0 -11
- package/dist/config.js +0 -115
- package/dist/daemon.d.ts +0 -1
- package/dist/daemon.js +0 -345
- package/dist/dashboard/html.d.ts +0 -1
- package/dist/dashboard/html.js +0 -849
- package/dist/dashboard/routes.d.ts +0 -17
- package/dist/dashboard/routes.js +0 -623
- package/dist/dashboard/server.d.ts +0 -1
- package/dist/dashboard/server.js +0 -104
- package/dist/db/connection.d.ts +0 -29
- package/dist/db/connection.js +0 -37
- package/dist/db/index.d.ts +0 -14
- package/dist/db/index.js +0 -127
- package/dist/db/migrations.d.ts +0 -2
- package/dist/db/migrations.js +0 -384
- package/dist/delegation/manager.d.ts +0 -18
- package/dist/delegation/manager.js +0 -89
- package/dist/index.d.ts +0 -2
- package/dist/knowledge/index.d.ts +0 -2
- package/dist/knowledge/index.js +0 -1
- package/dist/knowledge/manager.d.ts +0 -17
- package/dist/knowledge/manager.js +0 -160
- package/dist/knowledge/types.d.ts +0 -7
- package/dist/knowledge/types.js +0 -1
- package/dist/mcp/handlers.d.ts +0 -37
- package/dist/mcp/handlers.js +0 -520
- package/dist/mcp/server.d.ts +0 -2
- package/dist/mcp/server.js +0 -86
- package/dist/mcp/tool-definitions.d.ts +0 -1225
- package/dist/mcp/tool-definitions.js +0 -412
- package/dist/mcp/validate.d.ts +0 -23
- package/dist/mcp/validate.js +0 -65
- package/dist/media/factory.d.ts +0 -6
- package/dist/media/factory.js +0 -36
- package/dist/media/generators/dall-e.d.ts +0 -10
- package/dist/media/generators/dall-e.js +0 -41
- package/dist/media/generators/elevenlabs-music.d.ts +0 -9
- package/dist/media/generators/elevenlabs-music.js +0 -34
- package/dist/media/generators/elevenlabs-sfx.d.ts +0 -9
- package/dist/media/generators/elevenlabs-sfx.js +0 -33
- package/dist/media/generators/kling.d.ts +0 -9
- package/dist/media/generators/kling.js +0 -47
- package/dist/media/generators/lyria.d.ts +0 -10
- package/dist/media/generators/lyria.js +0 -41
- package/dist/media/generators/nano-banana.d.ts +0 -10
- package/dist/media/generators/nano-banana.js +0 -45
- package/dist/media/generators/poll-util.d.ts +0 -12
- package/dist/media/generators/poll-util.js +0 -22
- package/dist/media/generators/recraft.d.ts +0 -9
- package/dist/media/generators/recraft.js +0 -52
- package/dist/media/generators/runway.d.ts +0 -9
- package/dist/media/generators/runway.js +0 -49
- package/dist/media/generators/stable-diffusion.d.ts +0 -9
- package/dist/media/generators/stable-diffusion.js +0 -35
- package/dist/media/generators/veo.d.ts +0 -9
- package/dist/media/generators/veo.js +0 -32
- package/dist/media/index.d.ts +0 -6
- package/dist/media/index.js +0 -8
- package/dist/media/store.d.ts +0 -17
- package/dist/media/store.js +0 -69
- package/dist/media/types.d.ts +0 -43
- package/dist/media/types.js +0 -1
- package/dist/notifications/index.d.ts +0 -4
- package/dist/notifications/index.js +0 -21
- package/dist/notifications/ntfy.d.ts +0 -9
- package/dist/notifications/ntfy.js +0 -25
- package/dist/notifications/pushover.d.ts +0 -9
- package/dist/notifications/pushover.js +0 -26
- package/dist/notifications/types.d.ts +0 -5
- package/dist/notifications/types.js +0 -1
- package/dist/notifications/webhook-provider.d.ts +0 -9
- package/dist/notifications/webhook-provider.js +0 -24
- package/dist/observability/analytics.d.ts +0 -30
- package/dist/observability/analytics.js +0 -118
- package/dist/projects/index.d.ts +0 -4
- package/dist/projects/index.js +0 -2
- package/dist/projects/manager.d.ts +0 -13
- package/dist/projects/manager.js +0 -166
- package/dist/projects/router.d.ts +0 -17
- package/dist/projects/router.js +0 -246
- package/dist/projects/types.d.ts +0 -19
- package/dist/projects/types.js +0 -1
- package/dist/service/install.d.ts +0 -4
- package/dist/service/install.js +0 -146
- package/dist/service/templates.d.ts +0 -4
- package/dist/service/templates.js +0 -52
- package/dist/service/windows.d.ts +0 -8
- package/dist/service/windows.js +0 -59
- package/dist/session/budget-guard.d.ts +0 -20
- package/dist/session/budget-guard.js +0 -31
- package/dist/session/circuit-breaker.d.ts +0 -19
- package/dist/session/circuit-breaker.js +0 -91
- package/dist/session/context-compactor.d.ts +0 -32
- package/dist/session/context-compactor.js +0 -45
- package/dist/session/context-monitor.d.ts +0 -15
- package/dist/session/context-monitor.js +0 -44
- package/dist/session/handoff.d.ts +0 -21
- package/dist/session/handoff.js +0 -50
- package/dist/session/manager.d.ts +0 -60
- package/dist/session/manager.js +0 -379
- package/dist/session/memory-store.d.ts +0 -29
- package/dist/session/memory-store.js +0 -45
- package/dist/session/message-queue.d.ts +0 -28
- package/dist/session/message-queue.js +0 -52
- package/dist/session/pending-dispatcher.d.ts +0 -31
- package/dist/session/pending-dispatcher.js +0 -120
- package/dist/session/pending-store.d.ts +0 -60
- package/dist/session/pending-store.js +0 -118
- package/dist/session/stale-session.d.ts +0 -31
- package/dist/session/stale-session.js +0 -45
- package/dist/session/subprocess.d.ts +0 -25
- package/dist/session/subprocess.js +0 -189
- package/dist/session/tab-store.d.ts +0 -28
- package/dist/session/tab-store.js +0 -78
- package/dist/tasks/scheduler.d.ts +0 -45
- package/dist/tasks/scheduler.js +0 -329
- package/dist/tasks/store.d.ts +0 -11
- package/dist/tasks/store.js +0 -103
- package/dist/timeline/index.d.ts +0 -3
- package/dist/timeline/index.js +0 -2
- package/dist/timeline/logger.d.ts +0 -8
- package/dist/timeline/logger.js +0 -11
- package/dist/timeline/query.d.ts +0 -11
- package/dist/timeline/query.js +0 -67
- package/dist/timeline/types.d.ts +0 -12
- package/dist/timeline/types.js +0 -1
- package/dist/types.d.ts +0 -216
- package/dist/types.js +0 -3
- package/dist/util/auto-heal.d.ts +0 -43
- package/dist/util/auto-heal.js +0 -107
- package/dist/util/install-info.d.ts +0 -37
- package/dist/util/install-info.js +0 -80
- package/dist/util/logger.d.ts +0 -18
- package/dist/util/logger.js +0 -118
- package/dist/util/paths.d.ts +0 -13
- package/dist/util/paths.js +0 -55
- package/dist/util/platform.d.ts +0 -2
- package/dist/util/platform.js +0 -11
- package/dist/util/progress.d.ts +0 -34
- package/dist/util/progress.js +0 -133
- package/dist/util/rate-limiter.d.ts +0 -14
- package/dist/util/rate-limiter.js +0 -50
- package/dist/util/retry.d.ts +0 -2
- package/dist/util/retry.js +0 -18
- package/dist/util/text.d.ts +0 -32
- package/dist/util/text.js +0 -101
- package/dist/version.d.ts +0 -1
- package/dist/version.js +0 -4
- package/dist/voice/index.d.ts +0 -17
- package/dist/voice/index.js +0 -42
- package/dist/voice/stt.d.ts +0 -16
- package/dist/voice/stt.js +0 -57
- package/dist/voice/tts.d.ts +0 -23
- package/dist/voice/tts.js +0 -84
- package/dist/watchers/evaluator.d.ts +0 -5
- package/dist/watchers/evaluator.js +0 -45
- package/dist/watchers/index.d.ts +0 -4
- package/dist/watchers/index.js +0 -3
- package/dist/watchers/scheduler.d.ts +0 -20
- package/dist/watchers/scheduler.js +0 -183
- package/dist/watchers/store.d.ts +0 -10
- package/dist/watchers/store.js +0 -56
- package/dist/watchers/types.d.ts +0 -15
- package/dist/watchers/types.js +0 -1
- package/scripts/postinstall.mjs +0 -50
package/dist/channels/discord.js
DELETED
|
@@ -1,266 +0,0 @@
|
|
|
1
|
-
/*
|
|
2
|
-
* Discord integration via discord.js (peer-optional, dynamic-imported).
|
|
3
|
-
*
|
|
4
|
-
* discord.js's strict union types (PartialGroupDMChannel, TextChannel, etc.)
|
|
5
|
-
* require narrowing at every send/sendTyping callsite. The runtime channel
|
|
6
|
-
* objects we receive in handlers all support these methods, but expressing
|
|
7
|
-
* that to TypeScript via the published types is a significant refactor with
|
|
8
|
-
* no runtime benefit. We accept `any` at this library boundary — same pattern
|
|
9
|
-
* as the WhatsApp/baileys integration.
|
|
10
|
-
*/
|
|
11
|
-
/* eslint-disable @typescript-eslint/no-explicit-any */
|
|
12
|
-
import { logger } from '../util/logger.js';
|
|
13
|
-
import { parseTabMessage } from '../util/text.js';
|
|
14
|
-
import { sendChunkedResponse } from './send-helpers.js';
|
|
15
|
-
import { inboundLimiter } from '../util/rate-limiter.js';
|
|
16
|
-
import { saveMedia, isOversized } from '../media/store.js';
|
|
17
|
-
import { VoiceState } from './voice-state.js';
|
|
18
|
-
import { processInboundMessage } from './pipeline.js';
|
|
19
|
-
import { isChannelAdmin } from './admin.js';
|
|
20
|
-
export class DiscordChannel {
|
|
21
|
-
id = 'discord';
|
|
22
|
-
name = 'Discord';
|
|
23
|
-
maxMessageLength = 2000;
|
|
24
|
-
client = null; // Discord.js Client
|
|
25
|
-
ctx;
|
|
26
|
-
allowedUserIds;
|
|
27
|
-
voice = new VoiceState('discord');
|
|
28
|
-
constructor(ctx) {
|
|
29
|
-
this.ctx = ctx;
|
|
30
|
-
this.allowedUserIds = new Set((ctx.config.discord?.allowedUserIds ?? []).map(String));
|
|
31
|
-
}
|
|
32
|
-
async start() {
|
|
33
|
-
const discordConfig = this.ctx.config.discord;
|
|
34
|
-
if (!discordConfig?.token) {
|
|
35
|
-
logger.warn('No Discord token configured');
|
|
36
|
-
return;
|
|
37
|
-
}
|
|
38
|
-
// Dynamic import since discord.js might not be installed
|
|
39
|
-
const { Client, GatewayIntentBits, Events } = await import('discord.js');
|
|
40
|
-
this.client = new Client({
|
|
41
|
-
intents: [
|
|
42
|
-
GatewayIntentBits.Guilds,
|
|
43
|
-
GatewayIntentBits.GuildMessages,
|
|
44
|
-
GatewayIntentBits.DirectMessages,
|
|
45
|
-
GatewayIntentBits.MessageContent,
|
|
46
|
-
],
|
|
47
|
-
});
|
|
48
|
-
// Voice providers (STT + TTS)
|
|
49
|
-
this.voice.init(this.ctx.config);
|
|
50
|
-
this.client.on(Events.MessageCreate, async (message) => {
|
|
51
|
-
// Ignore bot messages
|
|
52
|
-
if (message.author.bot)
|
|
53
|
-
return;
|
|
54
|
-
const isDM = !message.guild;
|
|
55
|
-
const isMentioned = message.mentions.has(this.client.user);
|
|
56
|
-
// In DMs: only allow users in the allowlist
|
|
57
|
-
// In servers: only respond if @mentioned
|
|
58
|
-
if (isDM) {
|
|
59
|
-
if (!this.allowedUserIds.has(message.author.id))
|
|
60
|
-
return;
|
|
61
|
-
}
|
|
62
|
-
else {
|
|
63
|
-
if (!isMentioned)
|
|
64
|
-
return;
|
|
65
|
-
}
|
|
66
|
-
// Rate limit
|
|
67
|
-
if (!inboundLimiter.check(this.id)) {
|
|
68
|
-
await message
|
|
69
|
-
.reply("I'm receiving too many messages right now. Please wait a moment.")
|
|
70
|
-
.catch(() => { });
|
|
71
|
-
return;
|
|
72
|
-
}
|
|
73
|
-
const text = message.content
|
|
74
|
-
.replace(/<@!?\d+>/g, '') // Remove mentions
|
|
75
|
-
.trim();
|
|
76
|
-
// Warm up STT connection on first message with attachments.
|
|
77
|
-
// (Discord intentionally only warms up; it doesn't transcribe like Telegram/WhatsApp.)
|
|
78
|
-
if (message.attachments.size > 0)
|
|
79
|
-
await this.voice.warmup();
|
|
80
|
-
// Download attachments
|
|
81
|
-
const media = [];
|
|
82
|
-
for (const attachment of message.attachments.values()) {
|
|
83
|
-
try {
|
|
84
|
-
if (isOversized(attachment.size)) {
|
|
85
|
-
logger.warn(`Skipping oversized Discord attachment: ${attachment.size} bytes`);
|
|
86
|
-
continue;
|
|
87
|
-
}
|
|
88
|
-
const response = await fetch(attachment.url, { signal: AbortSignal.timeout(30000) });
|
|
89
|
-
if (!response.ok)
|
|
90
|
-
continue;
|
|
91
|
-
const buffer = Buffer.from(await response.arrayBuffer());
|
|
92
|
-
const ext = attachment.name?.split('.').pop() || 'bin';
|
|
93
|
-
const filePath = saveMedia(buffer, ext, attachment.name);
|
|
94
|
-
let type = 'document';
|
|
95
|
-
if (attachment.contentType?.startsWith('image/'))
|
|
96
|
-
type = 'image';
|
|
97
|
-
else if (attachment.contentType?.startsWith('video/'))
|
|
98
|
-
type = 'video';
|
|
99
|
-
else if (attachment.contentType?.startsWith('audio/'))
|
|
100
|
-
type = 'audio';
|
|
101
|
-
media.push({
|
|
102
|
-
type,
|
|
103
|
-
mimeType: attachment.contentType || 'application/octet-stream',
|
|
104
|
-
filePath,
|
|
105
|
-
fileName: attachment.name,
|
|
106
|
-
});
|
|
107
|
-
}
|
|
108
|
-
catch (err) {
|
|
109
|
-
logger.warn('Failed to download Discord attachment:', err);
|
|
110
|
-
}
|
|
111
|
-
}
|
|
112
|
-
if (!text && media.length === 0)
|
|
113
|
-
return;
|
|
114
|
-
try {
|
|
115
|
-
// Show typing
|
|
116
|
-
await message.channel.sendTyping().catch(() => { });
|
|
117
|
-
// Parse tab name (needed for command handling check)
|
|
118
|
-
const { tabName } = parseTabMessage(text || '');
|
|
119
|
-
// Shared command handler
|
|
120
|
-
if (text.startsWith('/')) {
|
|
121
|
-
const { handleSharedCommand } = await import('./command-handler.js');
|
|
122
|
-
const cmdResult = await handleSharedCommand({
|
|
123
|
-
userId: message.author.id,
|
|
124
|
-
text,
|
|
125
|
-
isAdmin: isChannelAdmin(this.allowedUserIds, message.author.id, this.ctx.config.discord?.adminUserId),
|
|
126
|
-
channelId: 'discord',
|
|
127
|
-
}, this.ctx.tabManager);
|
|
128
|
-
if (cmdResult.handled) {
|
|
129
|
-
if (cmdResult.response)
|
|
130
|
-
await message.reply(cmdResult.response);
|
|
131
|
-
return;
|
|
132
|
-
}
|
|
133
|
-
}
|
|
134
|
-
// Discord-specific: use thread name as tab if in a thread
|
|
135
|
-
let overrideTabName;
|
|
136
|
-
if (message.channel.isThread?.()) {
|
|
137
|
-
const sanitized = (message.channel.name || '')
|
|
138
|
-
.replace(/[^a-zA-Z0-9-]/g, '-')
|
|
139
|
-
.replace(/^-+|-+$/g, '')
|
|
140
|
-
.slice(0, 32);
|
|
141
|
-
// Run the synthesized name through validateTabName so weird thread
|
|
142
|
-
// names (empty, starts with hyphen, "default") don't blow up downstream.
|
|
143
|
-
const { validateTabName } = await import('../config.js');
|
|
144
|
-
if (sanitized && tabName === 'default' && !validateTabName(sanitized)) {
|
|
145
|
-
overrideTabName = sanitized;
|
|
146
|
-
}
|
|
147
|
-
}
|
|
148
|
-
// Typing indicator refresh
|
|
149
|
-
const typingInterval = setInterval(() => {
|
|
150
|
-
message.channel.sendTyping().catch(() => { });
|
|
151
|
-
}, 8000); // Discord typing lasts 10s
|
|
152
|
-
try {
|
|
153
|
-
// Shared message pipeline
|
|
154
|
-
const pipelineResult = await processInboundMessage({
|
|
155
|
-
text: text || '',
|
|
156
|
-
media,
|
|
157
|
-
channelId: 'discord',
|
|
158
|
-
tabManager: this.ctx.tabManager,
|
|
159
|
-
voiceReplyMode: this.ctx.config.voice?.replyMode,
|
|
160
|
-
ttsProvider: this.voice.tts,
|
|
161
|
-
userId: message.author.id,
|
|
162
|
-
sendProgress: (msg) => {
|
|
163
|
-
message.channel.send(msg).catch(() => { });
|
|
164
|
-
},
|
|
165
|
-
overrideTabName,
|
|
166
|
-
});
|
|
167
|
-
clearInterval(typingInterval);
|
|
168
|
-
// Empty result means no prompt and no media
|
|
169
|
-
if (!pipelineResult.responseText)
|
|
170
|
-
return;
|
|
171
|
-
// Voice reply if TTS generated audio
|
|
172
|
-
if (pipelineResult.audioPath) {
|
|
173
|
-
await message.reply({ files: [pipelineResult.audioPath] });
|
|
174
|
-
if (pipelineResult.voiceOnly)
|
|
175
|
-
return;
|
|
176
|
-
}
|
|
177
|
-
// Send text response
|
|
178
|
-
await this.sendResponse(message, pipelineResult.responseText, pipelineResult.tabName);
|
|
179
|
-
}
|
|
180
|
-
catch (err) {
|
|
181
|
-
clearInterval(typingInterval);
|
|
182
|
-
throw err;
|
|
183
|
-
}
|
|
184
|
-
}
|
|
185
|
-
catch (err) {
|
|
186
|
-
logger.error('Discord message handler error:', err);
|
|
187
|
-
await message.reply('Something went wrong processing your message.').catch(() => { });
|
|
188
|
-
}
|
|
189
|
-
});
|
|
190
|
-
this.client.on(Events.ClientReady, () => {
|
|
191
|
-
logger.info(`Discord bot ready as ${this.client.user?.tag}`);
|
|
192
|
-
});
|
|
193
|
-
await this.client.login(discordConfig.token);
|
|
194
|
-
}
|
|
195
|
-
stop() {
|
|
196
|
-
if (this.client) {
|
|
197
|
-
this.client.destroy();
|
|
198
|
-
this.client = null;
|
|
199
|
-
}
|
|
200
|
-
logger.info('Discord bot stopped');
|
|
201
|
-
}
|
|
202
|
-
async sendMessage(peerId, text, _options) {
|
|
203
|
-
if (!this.client)
|
|
204
|
-
return;
|
|
205
|
-
try {
|
|
206
|
-
const channel = await this.client.channels.fetch(peerId);
|
|
207
|
-
if (!channel?.isTextBased())
|
|
208
|
-
return;
|
|
209
|
-
await sendChunkedResponse({
|
|
210
|
-
text,
|
|
211
|
-
maxLength: this.maxMessageLength,
|
|
212
|
-
retryLabel: 'discord-send',
|
|
213
|
-
sendChunk: (chunk) => channel.send(chunk),
|
|
214
|
-
});
|
|
215
|
-
}
|
|
216
|
-
catch (err) {
|
|
217
|
-
logger.error(`Discord send failed for ${peerId}:`, err);
|
|
218
|
-
}
|
|
219
|
-
}
|
|
220
|
-
async sendNotification(message, _urgent) {
|
|
221
|
-
if (!this.client)
|
|
222
|
-
return;
|
|
223
|
-
// Send to all allowed users via DM
|
|
224
|
-
for (const userId of this.allowedUserIds) {
|
|
225
|
-
try {
|
|
226
|
-
const user = await this.client.users.fetch(userId);
|
|
227
|
-
if (user) {
|
|
228
|
-
await user.send(message);
|
|
229
|
-
}
|
|
230
|
-
}
|
|
231
|
-
catch (err) {
|
|
232
|
-
logger.error(`Discord notification failed for ${userId}:`, err);
|
|
233
|
-
}
|
|
234
|
-
}
|
|
235
|
-
}
|
|
236
|
-
async setTyping(peerId, active) {
|
|
237
|
-
if (!this.client || !active)
|
|
238
|
-
return;
|
|
239
|
-
try {
|
|
240
|
-
const channel = await this.client.channels.fetch(peerId);
|
|
241
|
-
if (channel?.isTextBased()) {
|
|
242
|
-
await channel.sendTyping();
|
|
243
|
-
}
|
|
244
|
-
}
|
|
245
|
-
catch { }
|
|
246
|
-
}
|
|
247
|
-
async sendResponse(message, text, tabName) {
|
|
248
|
-
// Discord quirk: first chunk uses message.reply so it threads under the
|
|
249
|
-
// original user message; follow-ups use channel.send. sendChunkedResponse
|
|
250
|
-
// handles prefix + chunking + retry envelope; the closure-tracked chunkIdx
|
|
251
|
-
// keeps the "first chunk replies, rest send" behavior.
|
|
252
|
-
let chunkIdx = 0;
|
|
253
|
-
await sendChunkedResponse({
|
|
254
|
-
text,
|
|
255
|
-
tabName,
|
|
256
|
-
maxLength: this.maxMessageLength,
|
|
257
|
-
retryDelays: [1000, 5000],
|
|
258
|
-
retryLabel: 'discord-send',
|
|
259
|
-
sendChunk: (chunk) => {
|
|
260
|
-
const isFirst = chunkIdx === 0;
|
|
261
|
-
chunkIdx++;
|
|
262
|
-
return isFirst ? message.reply(chunk) : message.channel.send(chunk);
|
|
263
|
-
},
|
|
264
|
-
});
|
|
265
|
-
}
|
|
266
|
-
}
|
package/dist/channels/index.d.ts
DELETED
|
@@ -1,7 +0,0 @@
|
|
|
1
|
-
export { ChannelRegistry } from './registry.js';
|
|
2
|
-
export { TelegramChannel } from './telegram.js';
|
|
3
|
-
export { WhatsAppChannel } from './whatsapp.js';
|
|
4
|
-
export { WebhookChannel } from './webhook.js';
|
|
5
|
-
export { DiscordChannel } from './discord.js';
|
|
6
|
-
export { loadCommunityChannels } from './loader.js';
|
|
7
|
-
export type { Channel, ChannelContext, MediaAttachment, SendOptions } from './types.js';
|
package/dist/channels/index.js
DELETED
|
@@ -1,6 +0,0 @@
|
|
|
1
|
-
export { ChannelRegistry } from './registry.js';
|
|
2
|
-
export { TelegramChannel } from './telegram.js';
|
|
3
|
-
export { WhatsAppChannel } from './whatsapp.js';
|
|
4
|
-
export { WebhookChannel } from './webhook.js';
|
|
5
|
-
export { DiscordChannel } from './discord.js';
|
|
6
|
-
export { loadCommunityChannels } from './loader.js';
|
|
@@ -1,7 +0,0 @@
|
|
|
1
|
-
import type { Channel, ChannelContext } from './types.js';
|
|
2
|
-
/**
|
|
3
|
-
* Discover and load community channel packages from node_modules.
|
|
4
|
-
* Convention: packages named `beecork-channel-<name>` must export a default
|
|
5
|
-
* class implementing the Channel interface.
|
|
6
|
-
*/
|
|
7
|
-
export declare function loadCommunityChannels(ctx: ChannelContext): Promise<Channel[]>;
|
package/dist/channels/loader.js
DELETED
|
@@ -1,87 +0,0 @@
|
|
|
1
|
-
import fs from 'node:fs';
|
|
2
|
-
import path from 'node:path';
|
|
3
|
-
import { logger } from '../util/logger.js';
|
|
4
|
-
const CHANNEL_PREFIX = 'beecork-channel-';
|
|
5
|
-
/**
|
|
6
|
-
* Discover and load community channel packages from node_modules.
|
|
7
|
-
* Convention: packages named `beecork-channel-<name>` must export a default
|
|
8
|
-
* class implementing the Channel interface.
|
|
9
|
-
*/
|
|
10
|
-
export async function loadCommunityChannels(ctx) {
|
|
11
|
-
const channels = [];
|
|
12
|
-
// Require explicit opt-in via config to prevent supply-chain attacks
|
|
13
|
-
const allowlist = ctx.config.communityChannels;
|
|
14
|
-
if (!allowlist || allowlist.length === 0)
|
|
15
|
-
return channels;
|
|
16
|
-
// Look in global and local node_modules
|
|
17
|
-
const searchPaths = [
|
|
18
|
-
path.join(process.cwd(), 'node_modules'),
|
|
19
|
-
// Global npm modules path varies by OS — try common locations
|
|
20
|
-
];
|
|
21
|
-
// Also check if beecork is installed globally
|
|
22
|
-
try {
|
|
23
|
-
const globalPath = path.dirname(require.resolve('beecork/package.json'));
|
|
24
|
-
const globalNodeModules = path.join(globalPath, '..');
|
|
25
|
-
if (fs.existsSync(globalNodeModules)) {
|
|
26
|
-
searchPaths.push(globalNodeModules);
|
|
27
|
-
}
|
|
28
|
-
}
|
|
29
|
-
catch { }
|
|
30
|
-
for (const searchPath of searchPaths) {
|
|
31
|
-
if (!fs.existsSync(searchPath))
|
|
32
|
-
continue;
|
|
33
|
-
try {
|
|
34
|
-
const dirs = fs.readdirSync(searchPath);
|
|
35
|
-
for (const dir of dirs) {
|
|
36
|
-
if (!dir.startsWith(CHANNEL_PREFIX))
|
|
37
|
-
continue;
|
|
38
|
-
if (!allowlist.includes(dir))
|
|
39
|
-
continue;
|
|
40
|
-
const pkgPath = path.join(searchPath, dir);
|
|
41
|
-
try {
|
|
42
|
-
// Read package.json to find the main entry
|
|
43
|
-
const pkgJson = JSON.parse(fs.readFileSync(path.join(pkgPath, 'package.json'), 'utf-8'));
|
|
44
|
-
const main = pkgJson.main || 'index.js';
|
|
45
|
-
const entryPath = path.join(pkgPath, main);
|
|
46
|
-
if (!fs.existsSync(entryPath)) {
|
|
47
|
-
logger.warn(`Community channel ${dir}: entry point not found at ${entryPath}`);
|
|
48
|
-
continue;
|
|
49
|
-
}
|
|
50
|
-
// Dynamic import — community channels run with full daemon access:
|
|
51
|
-
// they can read every config token (Telegram, Discord, WhatsApp,
|
|
52
|
-
// notification provider keys) AND every environment variable the
|
|
53
|
-
// daemon inherits (including SSH-agent sockets, GitHub tokens,
|
|
54
|
-
// anything sourced from your shell profile). A compromised
|
|
55
|
-
// community-channel package is effectively a root credential drop.
|
|
56
|
-
// Pin versions in package.json, audit before opting in, and treat
|
|
57
|
-
// every entry in `communityChannels` as a trust anchor.
|
|
58
|
-
logger.warn(`Loading community channel "${dir}" — this package will have FULL access to your channel tokens (Telegram/Discord/WhatsApp) and every environment variable inherited by the daemon (including SSH keys). Only enable if you trust the package author and have audited the version.`);
|
|
59
|
-
const module = await import(entryPath);
|
|
60
|
-
const ChannelClass = module.default || module[Object.keys(module)[0]];
|
|
61
|
-
if (!ChannelClass || typeof ChannelClass !== 'function') {
|
|
62
|
-
logger.warn(`Community channel ${dir}: no default export found`);
|
|
63
|
-
continue;
|
|
64
|
-
}
|
|
65
|
-
const instance = new ChannelClass(ctx);
|
|
66
|
-
// Validate it implements Channel interface (duck typing)
|
|
67
|
-
if (!instance.id ||
|
|
68
|
-
!instance.name ||
|
|
69
|
-
typeof instance.start !== 'function' ||
|
|
70
|
-
typeof instance.stop !== 'function') {
|
|
71
|
-
logger.warn(`Community channel ${dir}: does not implement Channel interface`);
|
|
72
|
-
continue;
|
|
73
|
-
}
|
|
74
|
-
channels.push(instance);
|
|
75
|
-
logger.info(`Community channel loaded: ${instance.name} (${instance.id}) from ${dir}`);
|
|
76
|
-
}
|
|
77
|
-
catch (err) {
|
|
78
|
-
logger.warn(`Failed to load community channel ${dir}:`, err);
|
|
79
|
-
}
|
|
80
|
-
}
|
|
81
|
-
}
|
|
82
|
-
catch (err) {
|
|
83
|
-
logger.warn(`Failed to scan ${searchPath}:`, err);
|
|
84
|
-
}
|
|
85
|
-
}
|
|
86
|
-
return channels;
|
|
87
|
-
}
|
|
@@ -1,50 +0,0 @@
|
|
|
1
|
-
import type { MediaAttachment } from './types.js';
|
|
2
|
-
import type { TabManager } from '../session/manager.js';
|
|
3
|
-
import type { TTSProvider } from '../voice/tts.js';
|
|
4
|
-
export interface PipelineOptions {
|
|
5
|
-
/** Raw message text (may include /tab prefix) */
|
|
6
|
-
text: string;
|
|
7
|
-
/** Downloaded media attachments */
|
|
8
|
-
media: MediaAttachment[];
|
|
9
|
-
/** Channel identifier for logging */
|
|
10
|
-
channelId: string;
|
|
11
|
-
/** Tab manager instance */
|
|
12
|
-
tabManager: TabManager;
|
|
13
|
-
/** Voice reply mode from config */
|
|
14
|
-
voiceReplyMode?: 'voice' | 'both' | 'text';
|
|
15
|
-
/** TTS provider (if configured) */
|
|
16
|
-
ttsProvider?: TTSProvider | null;
|
|
17
|
-
/** User ID for project routing */
|
|
18
|
-
userId: string;
|
|
19
|
-
/** Callback to send progress messages to the user */
|
|
20
|
-
sendProgress: (message: string) => void;
|
|
21
|
-
/**
|
|
22
|
-
* Override the tab name (e.g., group tabs in Telegram, thread tabs in Discord).
|
|
23
|
-
* When set, parseTabMessage result is ignored for the tab name.
|
|
24
|
-
*/
|
|
25
|
-
overrideTabName?: string;
|
|
26
|
-
/**
|
|
27
|
-
* Callback invoked on each text chunk from Claude (for streaming).
|
|
28
|
-
* Only Telegram uses this currently.
|
|
29
|
-
*/
|
|
30
|
-
onTextChunk?: (chunk: string) => void;
|
|
31
|
-
}
|
|
32
|
-
export interface PipelineResult {
|
|
33
|
-
/** The final response text (already includes "Error: " prefix on errors) */
|
|
34
|
-
responseText: string;
|
|
35
|
-
/** The tab name that was actually used */
|
|
36
|
-
tabName: string;
|
|
37
|
-
/** Whether the response was an error */
|
|
38
|
-
isError: boolean;
|
|
39
|
-
/** Path to TTS audio file, if voice reply was generated */
|
|
40
|
-
audioPath?: string;
|
|
41
|
-
/** Whether voice-only mode is active (caller should skip text response) */
|
|
42
|
-
voiceOnly?: boolean;
|
|
43
|
-
}
|
|
44
|
-
/**
|
|
45
|
-
* Process an inbound message through the shared pipeline.
|
|
46
|
-
*
|
|
47
|
-
* Returns the response text and optional audio path. The caller (channel)
|
|
48
|
-
* is responsible for delivering the response using its platform API.
|
|
49
|
-
*/
|
|
50
|
-
export declare function processInboundMessage(opts: PipelineOptions): Promise<PipelineResult>;
|
|
@@ -1,92 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Shared message-processing pipeline for all channels.
|
|
3
|
-
*
|
|
4
|
-
* Handles the common flow: project routing -> media prompt building ->
|
|
5
|
-
* tab message sending -> progress tracking -> TTS voice reply.
|
|
6
|
-
*
|
|
7
|
-
* Each channel remains responsible for:
|
|
8
|
-
* - Platform-specific message parsing and media download
|
|
9
|
-
* - Typing indicators (passed as a callback)
|
|
10
|
-
* - Sending the final response via its own API
|
|
11
|
-
* - Any channel-specific features (Telegram streaming, Discord threads, etc.)
|
|
12
|
-
*/
|
|
13
|
-
import { parseTabMessage, buildMediaPrompt } from '../util/text.js';
|
|
14
|
-
import { ProgressTracker } from '../util/progress.js';
|
|
15
|
-
import { logger } from '../util/logger.js';
|
|
16
|
-
/**
|
|
17
|
-
* Process an inbound message through the shared pipeline.
|
|
18
|
-
*
|
|
19
|
-
* Returns the response text and optional audio path. The caller (channel)
|
|
20
|
-
* is responsible for delivering the response using its platform API.
|
|
21
|
-
*/
|
|
22
|
-
export async function processInboundMessage(opts) {
|
|
23
|
-
const { text, media, channelId, tabManager, voiceReplyMode, ttsProvider, userId, sendProgress, overrideTabName, onTextChunk, } = opts;
|
|
24
|
-
// 1. Parse tab name and prompt from the message text
|
|
25
|
-
const parsed = parseTabMessage(text);
|
|
26
|
-
const rawPrompt = parsed.prompt;
|
|
27
|
-
let tabName = parsed.tabName;
|
|
28
|
-
// Allow channel to override tab name (group tabs, thread tabs, etc.).
|
|
29
|
-
// Validate centrally so each channel doesn't need its own check — keeps the
|
|
30
|
-
// CLAUDE.md "validation is centralized" rule honest. `default` is allowed.
|
|
31
|
-
if (overrideTabName) {
|
|
32
|
-
if (overrideTabName !== 'default') {
|
|
33
|
-
const { validateTabName } = await import('../config.js');
|
|
34
|
-
const overrideErr = validateTabName(overrideTabName);
|
|
35
|
-
if (overrideErr) {
|
|
36
|
-
return { responseText: `Invalid tab name: ${overrideErr}`, tabName, isError: true };
|
|
37
|
-
}
|
|
38
|
-
}
|
|
39
|
-
tabName = overrideTabName;
|
|
40
|
-
}
|
|
41
|
-
if (!rawPrompt && media.length === 0) {
|
|
42
|
-
return { responseText: '', tabName, isError: false };
|
|
43
|
-
}
|
|
44
|
-
// 2. Smart project routing
|
|
45
|
-
const { resolveProjectRoute } = await import('./command-handler.js');
|
|
46
|
-
const route = await resolveProjectRoute(rawPrompt, tabName, text, userId);
|
|
47
|
-
if (route.confirmationMessage) {
|
|
48
|
-
return {
|
|
49
|
-
responseText: route.confirmationMessage,
|
|
50
|
-
tabName,
|
|
51
|
-
isError: false,
|
|
52
|
-
};
|
|
53
|
-
}
|
|
54
|
-
const effectiveTabName = route.effectiveTabName;
|
|
55
|
-
const projectPath = route.projectPath;
|
|
56
|
-
// 3. Build prompt with media references
|
|
57
|
-
const prompt = buildMediaPrompt(media, rawPrompt);
|
|
58
|
-
logger.info(`[${channelId}] Pipeline processing for tab "${effectiveTabName}"`);
|
|
59
|
-
// 4. Create progress tracker
|
|
60
|
-
const progressTracker = new ProgressTracker(effectiveTabName, sendProgress);
|
|
61
|
-
// 5. Send message to tab
|
|
62
|
-
const result = await tabManager.sendMessage(effectiveTabName, prompt, {
|
|
63
|
-
onTextChunk,
|
|
64
|
-
onToolUse: (name, input) => progressTracker.record(name, input),
|
|
65
|
-
projectPath,
|
|
66
|
-
});
|
|
67
|
-
progressTracker.stop();
|
|
68
|
-
// 6. Build response text
|
|
69
|
-
const isError = result.error;
|
|
70
|
-
const responseText = isError ? `Error: ${result.text}` : result.text || '(empty response)';
|
|
71
|
-
// 7. TTS voice reply (shared logic)
|
|
72
|
-
let audioPath;
|
|
73
|
-
let voiceOnly = false;
|
|
74
|
-
if (ttsProvider && (voiceReplyMode === 'voice' || voiceReplyMode === 'both')) {
|
|
75
|
-
try {
|
|
76
|
-
audioPath = await ttsProvider.synthesize(responseText);
|
|
77
|
-
if (voiceReplyMode === 'voice') {
|
|
78
|
-
voiceOnly = true;
|
|
79
|
-
}
|
|
80
|
-
}
|
|
81
|
-
catch (err) {
|
|
82
|
-
logger.warn(`[${channelId}] TTS failed, falling back to text:`, err);
|
|
83
|
-
}
|
|
84
|
-
}
|
|
85
|
-
return {
|
|
86
|
-
responseText,
|
|
87
|
-
tabName: effectiveTabName,
|
|
88
|
-
isError,
|
|
89
|
-
audioPath,
|
|
90
|
-
voiceOnly,
|
|
91
|
-
};
|
|
92
|
-
}
|
|
@@ -1,36 +0,0 @@
|
|
|
1
|
-
import type { Channel } from './types.js';
|
|
2
|
-
import type { MediaAttachment } from '../types.js';
|
|
3
|
-
/**
|
|
4
|
-
* ChannelRegistry manages the lifecycle of all channels.
|
|
5
|
-
* Channels register themselves, and the registry handles start/stop/broadcast.
|
|
6
|
-
*/
|
|
7
|
-
export declare class ChannelRegistry {
|
|
8
|
-
private channels;
|
|
9
|
-
/** Register a channel */
|
|
10
|
-
register(channel: Channel): void;
|
|
11
|
-
/**
|
|
12
|
-
* Start all registered channels in parallel. Each channel's start() is an
|
|
13
|
-
* independent network handshake (Telegram getMe, Discord login, WhatsApp
|
|
14
|
-
* Baileys boot) — running them sequentially adds 2-5s to cold start with no
|
|
15
|
-
* coordination benefit.
|
|
16
|
-
*/
|
|
17
|
-
start(): Promise<void>;
|
|
18
|
-
/** Stop all channels */
|
|
19
|
-
stop(): void;
|
|
20
|
-
/** Get a channel by ID */
|
|
21
|
-
get(id: string): Channel | undefined;
|
|
22
|
-
/** Get all registered channels */
|
|
23
|
-
getAll(): Channel[];
|
|
24
|
-
/** Broadcast a notification to all channels */
|
|
25
|
-
broadcastNotify(text: string, urgent?: boolean): Promise<void>;
|
|
26
|
-
/**
|
|
27
|
-
* Broadcast a media attachment to every channel that supports it. Used by
|
|
28
|
-
* the pending-message dispatcher to deliver media queued by
|
|
29
|
-
* `beecork_send_media` and the `beecork_generate_*` tools — without this,
|
|
30
|
-
* those tools queue a row whose JSON envelope is never rendered.
|
|
31
|
-
*
|
|
32
|
-
* Channels that don't implement broadcastMedia get a sendNotification
|
|
33
|
-
* fallback so the user at least learns a file was generated.
|
|
34
|
-
*/
|
|
35
|
-
broadcastMedia(media: MediaAttachment): Promise<void>;
|
|
36
|
-
}
|
|
@@ -1,80 +0,0 @@
|
|
|
1
|
-
import { logger } from '../util/logger.js';
|
|
2
|
-
/**
|
|
3
|
-
* ChannelRegistry manages the lifecycle of all channels.
|
|
4
|
-
* Channels register themselves, and the registry handles start/stop/broadcast.
|
|
5
|
-
*/
|
|
6
|
-
export class ChannelRegistry {
|
|
7
|
-
channels = new Map();
|
|
8
|
-
/** Register a channel */
|
|
9
|
-
register(channel) {
|
|
10
|
-
if (this.channels.has(channel.id)) {
|
|
11
|
-
throw new Error(`Channel "${channel.id}" is already registered`);
|
|
12
|
-
}
|
|
13
|
-
this.channels.set(channel.id, channel);
|
|
14
|
-
logger.info(`Channel registered: ${channel.name} (${channel.id})`);
|
|
15
|
-
}
|
|
16
|
-
/**
|
|
17
|
-
* Start all registered channels in parallel. Each channel's start() is an
|
|
18
|
-
* independent network handshake (Telegram getMe, Discord login, WhatsApp
|
|
19
|
-
* Baileys boot) — running them sequentially adds 2-5s to cold start with no
|
|
20
|
-
* coordination benefit.
|
|
21
|
-
*/
|
|
22
|
-
async start() {
|
|
23
|
-
await Promise.all(Array.from(this.channels.entries()).map(async ([id, channel]) => {
|
|
24
|
-
try {
|
|
25
|
-
await channel.start();
|
|
26
|
-
logger.info(`Channel started: ${channel.name}`);
|
|
27
|
-
}
|
|
28
|
-
catch (err) {
|
|
29
|
-
logger.error(`Failed to start channel ${id}:`, err);
|
|
30
|
-
}
|
|
31
|
-
}));
|
|
32
|
-
}
|
|
33
|
-
/** Stop all channels */
|
|
34
|
-
stop() {
|
|
35
|
-
for (const [id, channel] of this.channels) {
|
|
36
|
-
try {
|
|
37
|
-
channel.stop();
|
|
38
|
-
}
|
|
39
|
-
catch (err) {
|
|
40
|
-
logger.error(`Failed to stop channel ${id}:`, err);
|
|
41
|
-
}
|
|
42
|
-
}
|
|
43
|
-
}
|
|
44
|
-
/** Get a channel by ID */
|
|
45
|
-
get(id) {
|
|
46
|
-
return this.channels.get(id);
|
|
47
|
-
}
|
|
48
|
-
/** Get all registered channels */
|
|
49
|
-
getAll() {
|
|
50
|
-
return Array.from(this.channels.values());
|
|
51
|
-
}
|
|
52
|
-
/** Broadcast a notification to all channels */
|
|
53
|
-
async broadcastNotify(text, urgent) {
|
|
54
|
-
await Promise.all(Array.from(this.channels.values()).map((channel) => channel
|
|
55
|
-
.sendNotification(text, urgent)
|
|
56
|
-
.catch((err) => logger.warn(`${channel.name} notify failed:`, err))));
|
|
57
|
-
}
|
|
58
|
-
/**
|
|
59
|
-
* Broadcast a media attachment to every channel that supports it. Used by
|
|
60
|
-
* the pending-message dispatcher to deliver media queued by
|
|
61
|
-
* `beecork_send_media` and the `beecork_generate_*` tools — without this,
|
|
62
|
-
* those tools queue a row whose JSON envelope is never rendered.
|
|
63
|
-
*
|
|
64
|
-
* Channels that don't implement broadcastMedia get a sendNotification
|
|
65
|
-
* fallback so the user at least learns a file was generated.
|
|
66
|
-
*/
|
|
67
|
-
async broadcastMedia(media) {
|
|
68
|
-
await Promise.all(Array.from(this.channels.values()).map((channel) => {
|
|
69
|
-
if (channel.broadcastMedia) {
|
|
70
|
-
return channel
|
|
71
|
-
.broadcastMedia(media)
|
|
72
|
-
.catch((err) => logger.warn(`${channel.name} broadcastMedia failed:`, err));
|
|
73
|
-
}
|
|
74
|
-
const fallback = `📎 Media generated: ${media.filePath}${media.caption ? `\n${media.caption}` : ''}`;
|
|
75
|
-
return channel
|
|
76
|
-
.sendNotification(fallback)
|
|
77
|
-
.catch((err) => logger.warn(`${channel.name} media-fallback notify failed:`, err));
|
|
78
|
-
}));
|
|
79
|
-
}
|
|
80
|
-
}
|
|
@@ -1,19 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Send a tab-prefixed, chunked, retried response through any channel.
|
|
3
|
-
*
|
|
4
|
-
* Each channel just provides:
|
|
5
|
-
* - sendChunk(text): the platform-specific send call for one chunk
|
|
6
|
-
* - maxLength: the platform's per-message limit
|
|
7
|
-
* - retryLabel: label for logger (e.g. "telegram-send")
|
|
8
|
-
*
|
|
9
|
-
* Replaces near-identical per-channel implementations that had already
|
|
10
|
-
* drifted in subtle ways (different prefix gates, different retry envelopes).
|
|
11
|
-
*/
|
|
12
|
-
export declare function sendChunkedResponse(opts: {
|
|
13
|
-
text: string;
|
|
14
|
-
tabName?: string;
|
|
15
|
-
maxLength: number;
|
|
16
|
-
retryLabel: string;
|
|
17
|
-
retryDelays?: number[];
|
|
18
|
-
sendChunk: (chunk: string) => Promise<unknown>;
|
|
19
|
-
}): Promise<void>;
|