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.
Files changed (223) hide show
  1. package/README.md +52 -111
  2. package/dist/index.js +1394 -581
  3. package/package.json +26 -54
  4. package/dist/capabilities/index.d.ts +0 -3
  5. package/dist/capabilities/index.js +0 -2
  6. package/dist/capabilities/manager.d.ts +0 -13
  7. package/dist/capabilities/manager.js +0 -115
  8. package/dist/capabilities/packs.d.ts +0 -2
  9. package/dist/capabilities/packs.js +0 -54
  10. package/dist/capabilities/types.d.ts +0 -21
  11. package/dist/capabilities/types.js +0 -1
  12. package/dist/channels/admin.d.ts +0 -10
  13. package/dist/channels/admin.js +0 -20
  14. package/dist/channels/command-handler.d.ts +0 -19
  15. package/dist/channels/command-handler.js +0 -235
  16. package/dist/channels/discord.d.ts +0 -17
  17. package/dist/channels/discord.js +0 -266
  18. package/dist/channels/index.d.ts +0 -7
  19. package/dist/channels/index.js +0 -6
  20. package/dist/channels/loader.d.ts +0 -7
  21. package/dist/channels/loader.js +0 -87
  22. package/dist/channels/pipeline.d.ts +0 -50
  23. package/dist/channels/pipeline.js +0 -92
  24. package/dist/channels/registry.d.ts +0 -36
  25. package/dist/channels/registry.js +0 -80
  26. package/dist/channels/send-helpers.d.ts +0 -19
  27. package/dist/channels/send-helpers.js +0 -21
  28. package/dist/channels/telegram.d.ts +0 -47
  29. package/dist/channels/telegram.js +0 -649
  30. package/dist/channels/types.d.ts +0 -41
  31. package/dist/channels/types.js +0 -1
  32. package/dist/channels/voice-state.d.ts +0 -29
  33. package/dist/channels/voice-state.js +0 -45
  34. package/dist/channels/webhook.d.ts +0 -16
  35. package/dist/channels/webhook.js +0 -222
  36. package/dist/channels/whatsapp.d.ts +0 -28
  37. package/dist/channels/whatsapp.js +0 -323
  38. package/dist/cli/capabilities.d.ts +0 -3
  39. package/dist/cli/capabilities.js +0 -72
  40. package/dist/cli/channel.d.ts +0 -3
  41. package/dist/cli/channel.js +0 -202
  42. package/dist/cli/commands.d.ts +0 -15
  43. package/dist/cli/commands.js +0 -279
  44. package/dist/cli/doctor.d.ts +0 -1
  45. package/dist/cli/doctor.js +0 -208
  46. package/dist/cli/handoff.d.ts +0 -8
  47. package/dist/cli/handoff.js +0 -33
  48. package/dist/cli/helpers.d.ts +0 -2
  49. package/dist/cli/helpers.js +0 -21
  50. package/dist/cli/mcp.d.ts +0 -3
  51. package/dist/cli/mcp.js +0 -47
  52. package/dist/cli/media.d.ts +0 -2
  53. package/dist/cli/media.js +0 -125
  54. package/dist/cli/setup.d.ts +0 -1
  55. package/dist/cli/setup.js +0 -239
  56. package/dist/cli/store.d.ts +0 -3
  57. package/dist/cli/store.js +0 -108
  58. package/dist/config.d.ts +0 -11
  59. package/dist/config.js +0 -115
  60. package/dist/daemon.d.ts +0 -1
  61. package/dist/daemon.js +0 -345
  62. package/dist/dashboard/html.d.ts +0 -1
  63. package/dist/dashboard/html.js +0 -849
  64. package/dist/dashboard/routes.d.ts +0 -17
  65. package/dist/dashboard/routes.js +0 -623
  66. package/dist/dashboard/server.d.ts +0 -1
  67. package/dist/dashboard/server.js +0 -104
  68. package/dist/db/connection.d.ts +0 -29
  69. package/dist/db/connection.js +0 -37
  70. package/dist/db/index.d.ts +0 -14
  71. package/dist/db/index.js +0 -127
  72. package/dist/db/migrations.d.ts +0 -2
  73. package/dist/db/migrations.js +0 -384
  74. package/dist/delegation/manager.d.ts +0 -18
  75. package/dist/delegation/manager.js +0 -89
  76. package/dist/index.d.ts +0 -2
  77. package/dist/knowledge/index.d.ts +0 -2
  78. package/dist/knowledge/index.js +0 -1
  79. package/dist/knowledge/manager.d.ts +0 -17
  80. package/dist/knowledge/manager.js +0 -160
  81. package/dist/knowledge/types.d.ts +0 -7
  82. package/dist/knowledge/types.js +0 -1
  83. package/dist/mcp/handlers.d.ts +0 -37
  84. package/dist/mcp/handlers.js +0 -520
  85. package/dist/mcp/server.d.ts +0 -2
  86. package/dist/mcp/server.js +0 -86
  87. package/dist/mcp/tool-definitions.d.ts +0 -1225
  88. package/dist/mcp/tool-definitions.js +0 -412
  89. package/dist/mcp/validate.d.ts +0 -23
  90. package/dist/mcp/validate.js +0 -65
  91. package/dist/media/factory.d.ts +0 -6
  92. package/dist/media/factory.js +0 -36
  93. package/dist/media/generators/dall-e.d.ts +0 -10
  94. package/dist/media/generators/dall-e.js +0 -41
  95. package/dist/media/generators/elevenlabs-music.d.ts +0 -9
  96. package/dist/media/generators/elevenlabs-music.js +0 -34
  97. package/dist/media/generators/elevenlabs-sfx.d.ts +0 -9
  98. package/dist/media/generators/elevenlabs-sfx.js +0 -33
  99. package/dist/media/generators/kling.d.ts +0 -9
  100. package/dist/media/generators/kling.js +0 -47
  101. package/dist/media/generators/lyria.d.ts +0 -10
  102. package/dist/media/generators/lyria.js +0 -41
  103. package/dist/media/generators/nano-banana.d.ts +0 -10
  104. package/dist/media/generators/nano-banana.js +0 -45
  105. package/dist/media/generators/poll-util.d.ts +0 -12
  106. package/dist/media/generators/poll-util.js +0 -22
  107. package/dist/media/generators/recraft.d.ts +0 -9
  108. package/dist/media/generators/recraft.js +0 -52
  109. package/dist/media/generators/runway.d.ts +0 -9
  110. package/dist/media/generators/runway.js +0 -49
  111. package/dist/media/generators/stable-diffusion.d.ts +0 -9
  112. package/dist/media/generators/stable-diffusion.js +0 -35
  113. package/dist/media/generators/veo.d.ts +0 -9
  114. package/dist/media/generators/veo.js +0 -32
  115. package/dist/media/index.d.ts +0 -6
  116. package/dist/media/index.js +0 -8
  117. package/dist/media/store.d.ts +0 -17
  118. package/dist/media/store.js +0 -69
  119. package/dist/media/types.d.ts +0 -43
  120. package/dist/media/types.js +0 -1
  121. package/dist/notifications/index.d.ts +0 -4
  122. package/dist/notifications/index.js +0 -21
  123. package/dist/notifications/ntfy.d.ts +0 -9
  124. package/dist/notifications/ntfy.js +0 -25
  125. package/dist/notifications/pushover.d.ts +0 -9
  126. package/dist/notifications/pushover.js +0 -26
  127. package/dist/notifications/types.d.ts +0 -5
  128. package/dist/notifications/types.js +0 -1
  129. package/dist/notifications/webhook-provider.d.ts +0 -9
  130. package/dist/notifications/webhook-provider.js +0 -24
  131. package/dist/observability/analytics.d.ts +0 -30
  132. package/dist/observability/analytics.js +0 -118
  133. package/dist/projects/index.d.ts +0 -4
  134. package/dist/projects/index.js +0 -2
  135. package/dist/projects/manager.d.ts +0 -13
  136. package/dist/projects/manager.js +0 -166
  137. package/dist/projects/router.d.ts +0 -17
  138. package/dist/projects/router.js +0 -246
  139. package/dist/projects/types.d.ts +0 -19
  140. package/dist/projects/types.js +0 -1
  141. package/dist/service/install.d.ts +0 -4
  142. package/dist/service/install.js +0 -146
  143. package/dist/service/templates.d.ts +0 -4
  144. package/dist/service/templates.js +0 -52
  145. package/dist/service/windows.d.ts +0 -8
  146. package/dist/service/windows.js +0 -59
  147. package/dist/session/budget-guard.d.ts +0 -20
  148. package/dist/session/budget-guard.js +0 -31
  149. package/dist/session/circuit-breaker.d.ts +0 -19
  150. package/dist/session/circuit-breaker.js +0 -91
  151. package/dist/session/context-compactor.d.ts +0 -32
  152. package/dist/session/context-compactor.js +0 -45
  153. package/dist/session/context-monitor.d.ts +0 -15
  154. package/dist/session/context-monitor.js +0 -44
  155. package/dist/session/handoff.d.ts +0 -21
  156. package/dist/session/handoff.js +0 -50
  157. package/dist/session/manager.d.ts +0 -60
  158. package/dist/session/manager.js +0 -379
  159. package/dist/session/memory-store.d.ts +0 -29
  160. package/dist/session/memory-store.js +0 -45
  161. package/dist/session/message-queue.d.ts +0 -28
  162. package/dist/session/message-queue.js +0 -52
  163. package/dist/session/pending-dispatcher.d.ts +0 -31
  164. package/dist/session/pending-dispatcher.js +0 -120
  165. package/dist/session/pending-store.d.ts +0 -60
  166. package/dist/session/pending-store.js +0 -118
  167. package/dist/session/stale-session.d.ts +0 -31
  168. package/dist/session/stale-session.js +0 -45
  169. package/dist/session/subprocess.d.ts +0 -25
  170. package/dist/session/subprocess.js +0 -189
  171. package/dist/session/tab-store.d.ts +0 -28
  172. package/dist/session/tab-store.js +0 -78
  173. package/dist/tasks/scheduler.d.ts +0 -45
  174. package/dist/tasks/scheduler.js +0 -329
  175. package/dist/tasks/store.d.ts +0 -11
  176. package/dist/tasks/store.js +0 -103
  177. package/dist/timeline/index.d.ts +0 -3
  178. package/dist/timeline/index.js +0 -2
  179. package/dist/timeline/logger.d.ts +0 -8
  180. package/dist/timeline/logger.js +0 -11
  181. package/dist/timeline/query.d.ts +0 -11
  182. package/dist/timeline/query.js +0 -67
  183. package/dist/timeline/types.d.ts +0 -12
  184. package/dist/timeline/types.js +0 -1
  185. package/dist/types.d.ts +0 -216
  186. package/dist/types.js +0 -3
  187. package/dist/util/auto-heal.d.ts +0 -43
  188. package/dist/util/auto-heal.js +0 -107
  189. package/dist/util/install-info.d.ts +0 -37
  190. package/dist/util/install-info.js +0 -80
  191. package/dist/util/logger.d.ts +0 -18
  192. package/dist/util/logger.js +0 -118
  193. package/dist/util/paths.d.ts +0 -13
  194. package/dist/util/paths.js +0 -55
  195. package/dist/util/platform.d.ts +0 -2
  196. package/dist/util/platform.js +0 -11
  197. package/dist/util/progress.d.ts +0 -34
  198. package/dist/util/progress.js +0 -133
  199. package/dist/util/rate-limiter.d.ts +0 -14
  200. package/dist/util/rate-limiter.js +0 -50
  201. package/dist/util/retry.d.ts +0 -2
  202. package/dist/util/retry.js +0 -18
  203. package/dist/util/text.d.ts +0 -32
  204. package/dist/util/text.js +0 -101
  205. package/dist/version.d.ts +0 -1
  206. package/dist/version.js +0 -4
  207. package/dist/voice/index.d.ts +0 -17
  208. package/dist/voice/index.js +0 -42
  209. package/dist/voice/stt.d.ts +0 -16
  210. package/dist/voice/stt.js +0 -57
  211. package/dist/voice/tts.d.ts +0 -23
  212. package/dist/voice/tts.js +0 -84
  213. package/dist/watchers/evaluator.d.ts +0 -5
  214. package/dist/watchers/evaluator.js +0 -45
  215. package/dist/watchers/index.d.ts +0 -4
  216. package/dist/watchers/index.js +0 -3
  217. package/dist/watchers/scheduler.d.ts +0 -20
  218. package/dist/watchers/scheduler.js +0 -183
  219. package/dist/watchers/store.d.ts +0 -10
  220. package/dist/watchers/store.js +0 -56
  221. package/dist/watchers/types.d.ts +0 -15
  222. package/dist/watchers/types.js +0 -1
  223. package/scripts/postinstall.mjs +0 -50
@@ -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
- }
@@ -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';
@@ -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[]>;
@@ -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>;