@dobby.ai/dobby 0.1.0 → 0.1.2

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 (156) hide show
  1. package/README.md +84 -39
  2. package/dist/src/agent/event-forwarder.js +185 -16
  3. package/dist/src/cli/commands/cron.js +39 -35
  4. package/dist/src/cli/commands/doctor.js +81 -2
  5. package/dist/src/cli/commands/extension.js +3 -1
  6. package/dist/src/cli/commands/init.js +43 -173
  7. package/dist/src/cli/commands/topology.js +38 -14
  8. package/dist/src/cli/program.js +15 -137
  9. package/dist/src/cli/shared/config-io.js +3 -31
  10. package/dist/src/cli/shared/config-mutators.js +33 -9
  11. package/dist/src/cli/shared/configure-sections.js +52 -12
  12. package/dist/src/cli/shared/init-catalog.js +89 -46
  13. package/dist/src/cli/shared/local-extension-specs.js +85 -0
  14. package/dist/src/cli/shared/schema-prompts.js +26 -2
  15. package/dist/src/core/gateway.js +3 -1
  16. package/dist/src/core/routing.js +53 -38
  17. package/dist/src/core/types.js +2 -0
  18. package/dist/src/cron/config.js +2 -2
  19. package/dist/src/cron/service.js +87 -23
  20. package/dist/src/cron/store.js +1 -1
  21. package/dist/src/main.js +0 -0
  22. package/dist/src/shared/dobby-repo.js +40 -0
  23. package/package.json +11 -4
  24. package/.env.example +0 -9
  25. package/AGENTS.md +0 -267
  26. package/ROADMAP.md +0 -34
  27. package/config/cron.example.json +0 -9
  28. package/config/gateway.example.json +0 -128
  29. package/config/models.custom.example.json +0 -27
  30. package/dist/src/agent/tests/event-forwarder.test.js +0 -113
  31. package/dist/src/cli/shared/config-path.js +0 -207
  32. package/dist/src/cli/shared/init-models-file.js +0 -65
  33. package/dist/src/cli/shared/presets.js +0 -86
  34. package/dist/src/cli/tests/config-command.test.js +0 -42
  35. package/dist/src/cli/tests/config-io.test.js +0 -64
  36. package/dist/src/cli/tests/config-mutators.test.js +0 -47
  37. package/dist/src/cli/tests/config-path.test.js +0 -21
  38. package/dist/src/cli/tests/discord-config.test.js +0 -23
  39. package/dist/src/cli/tests/doctor.test.js +0 -107
  40. package/dist/src/cli/tests/init-catalog.test.js +0 -87
  41. package/dist/src/cli/tests/presets.test.js +0 -41
  42. package/dist/src/cli/tests/program-options.test.js +0 -92
  43. package/dist/src/cli/tests/routing-config.test.js +0 -199
  44. package/dist/src/cli/tests/routing-legacy.test.js +0 -191
  45. package/dist/src/core/tests/control-command.test.js +0 -17
  46. package/dist/src/core/tests/gateway-update-strategy.test.js +0 -167
  47. package/dist/src/core/tests/runtime-registry.test.js +0 -116
  48. package/dist/src/core/tests/typing-controller.test.js +0 -103
  49. package/docs/BOXLITE_SANDBOX_FEASIBILITY.md +0 -175
  50. package/docs/CRON_SCHEDULER_DESIGN.md +0 -374
  51. package/docs/DOCKER_SANDBOX_vs_BOXLITE.md +0 -77
  52. package/docs/EXTENSION_SYSTEM_ARCHITECTURE.md +0 -119
  53. package/docs/MVP.md +0 -135
  54. package/docs/RUNBOOK.md +0 -242
  55. package/docs/TEAMWORK_HANDOFF_DESIGN.md +0 -440
  56. package/plugins/connector-discord/dobby.manifest.json +0 -18
  57. package/plugins/connector-discord/index.js +0 -1
  58. package/plugins/connector-discord/package-lock.json +0 -360
  59. package/plugins/connector-discord/package.json +0 -38
  60. package/plugins/connector-discord/src/connector.ts +0 -350
  61. package/plugins/connector-discord/src/contribution.ts +0 -21
  62. package/plugins/connector-discord/src/mapper.ts +0 -102
  63. package/plugins/connector-discord/tsconfig.json +0 -19
  64. package/plugins/connector-feishu/dobby.manifest.json +0 -18
  65. package/plugins/connector-feishu/index.js +0 -1
  66. package/plugins/connector-feishu/package-lock.json +0 -618
  67. package/plugins/connector-feishu/package.json +0 -38
  68. package/plugins/connector-feishu/src/connector.ts +0 -343
  69. package/plugins/connector-feishu/src/contribution.ts +0 -26
  70. package/plugins/connector-feishu/src/mapper.ts +0 -401
  71. package/plugins/connector-feishu/tsconfig.json +0 -19
  72. package/plugins/plugin-sdk/index.d.ts +0 -261
  73. package/plugins/plugin-sdk/index.js +0 -1
  74. package/plugins/plugin-sdk/package-lock.json +0 -12
  75. package/plugins/plugin-sdk/package.json +0 -22
  76. package/plugins/provider-claude/dobby.manifest.json +0 -17
  77. package/plugins/provider-claude/index.js +0 -1
  78. package/plugins/provider-claude/package-lock.json +0 -3398
  79. package/plugins/provider-claude/package.json +0 -39
  80. package/plugins/provider-claude/src/contribution.ts +0 -1018
  81. package/plugins/provider-claude/tsconfig.json +0 -19
  82. package/plugins/provider-claude-cli/dobby.manifest.json +0 -17
  83. package/plugins/provider-claude-cli/index.js +0 -1
  84. package/plugins/provider-claude-cli/package-lock.json +0 -2898
  85. package/plugins/provider-claude-cli/package.json +0 -38
  86. package/plugins/provider-claude-cli/src/contribution.ts +0 -1673
  87. package/plugins/provider-claude-cli/tsconfig.json +0 -19
  88. package/plugins/provider-pi/dobby.manifest.json +0 -17
  89. package/plugins/provider-pi/index.js +0 -1
  90. package/plugins/provider-pi/package-lock.json +0 -3877
  91. package/plugins/provider-pi/package.json +0 -40
  92. package/plugins/provider-pi/src/contribution.ts +0 -476
  93. package/plugins/provider-pi/tsconfig.json +0 -19
  94. package/plugins/sandbox-core/boxlite.js +0 -1
  95. package/plugins/sandbox-core/dobby.manifest.json +0 -17
  96. package/plugins/sandbox-core/docker.js +0 -1
  97. package/plugins/sandbox-core/package-lock.json +0 -136
  98. package/plugins/sandbox-core/package.json +0 -39
  99. package/plugins/sandbox-core/src/boxlite-context.ts +0 -2
  100. package/plugins/sandbox-core/src/boxlite-contribution.ts +0 -53
  101. package/plugins/sandbox-core/src/boxlite-executor.ts +0 -911
  102. package/plugins/sandbox-core/src/docker-contribution.ts +0 -43
  103. package/plugins/sandbox-core/src/docker-executor.ts +0 -217
  104. package/plugins/sandbox-core/tsconfig.json +0 -19
  105. package/scripts/local-extensions.mjs +0 -168
  106. package/src/agent/event-forwarder.ts +0 -414
  107. package/src/cli/commands/config.ts +0 -328
  108. package/src/cli/commands/configure.ts +0 -92
  109. package/src/cli/commands/cron.ts +0 -410
  110. package/src/cli/commands/doctor.ts +0 -230
  111. package/src/cli/commands/extension.ts +0 -205
  112. package/src/cli/commands/init.ts +0 -396
  113. package/src/cli/commands/start.ts +0 -223
  114. package/src/cli/commands/topology.ts +0 -383
  115. package/src/cli/index.ts +0 -9
  116. package/src/cli/program.ts +0 -465
  117. package/src/cli/shared/config-io.ts +0 -277
  118. package/src/cli/shared/config-mutators.ts +0 -440
  119. package/src/cli/shared/config-schema.ts +0 -228
  120. package/src/cli/shared/config-types.ts +0 -121
  121. package/src/cli/shared/configure-sections.ts +0 -551
  122. package/src/cli/shared/discord-config.ts +0 -14
  123. package/src/cli/shared/init-catalog.ts +0 -189
  124. package/src/cli/shared/init-models-file.ts +0 -77
  125. package/src/cli/shared/runtime.ts +0 -33
  126. package/src/cli/shared/schema-prompts.ts +0 -414
  127. package/src/cli/tests/config-command.test.ts +0 -56
  128. package/src/cli/tests/config-io.test.ts +0 -92
  129. package/src/cli/tests/config-mutators.test.ts +0 -59
  130. package/src/cli/tests/doctor.test.ts +0 -120
  131. package/src/cli/tests/init-catalog.test.ts +0 -96
  132. package/src/cli/tests/program-options.test.ts +0 -113
  133. package/src/cli/tests/routing-config.test.ts +0 -209
  134. package/src/core/control-command.ts +0 -12
  135. package/src/core/dedup-store.ts +0 -103
  136. package/src/core/gateway.ts +0 -607
  137. package/src/core/routing.ts +0 -379
  138. package/src/core/runtime-registry.ts +0 -141
  139. package/src/core/tests/control-command.test.ts +0 -20
  140. package/src/core/tests/runtime-registry.test.ts +0 -140
  141. package/src/core/tests/typing-controller.test.ts +0 -129
  142. package/src/core/types.ts +0 -318
  143. package/src/core/typing-controller.ts +0 -119
  144. package/src/cron/config.ts +0 -154
  145. package/src/cron/schedule.ts +0 -61
  146. package/src/cron/service.ts +0 -249
  147. package/src/cron/store.ts +0 -155
  148. package/src/cron/types.ts +0 -60
  149. package/src/extension/loader.ts +0 -145
  150. package/src/extension/manager.ts +0 -355
  151. package/src/extension/manifest.ts +0 -26
  152. package/src/extension/registry.ts +0 -229
  153. package/src/main.ts +0 -8
  154. package/src/sandbox/executor.ts +0 -44
  155. package/src/sandbox/host-executor.ts +0 -118
  156. package/tsconfig.json +0 -18
@@ -1,350 +0,0 @@
1
- import {
2
- AttachmentBuilder,
3
- Client,
4
- GatewayIntentBits,
5
- Partials,
6
- type Message,
7
- type MessageCreateOptions,
8
- type SendableChannels,
9
- } from "discord.js";
10
- import type {
11
- ConnectorCapabilities,
12
- ConnectorContext,
13
- ConnectorPlugin,
14
- ConnectorSendResult,
15
- ConnectorTypingEnvelope,
16
- GatewayLogger,
17
- OutboundEnvelope,
18
- } from "@dobby.ai/plugin-sdk";
19
- import { mapDiscordMessage } from "./mapper.js";
20
-
21
- const DISCORD_MAX_CONTENT_LENGTH = 2000;
22
- const DEFAULT_RECONNECT_STALE_MS = 60_000;
23
- const DEFAULT_RECONNECT_CHECK_INTERVAL_MS = 10_000;
24
-
25
- export interface DiscordConnectorConfig {
26
- botName: string;
27
- botToken: string;
28
- reconnectStaleMs?: number;
29
- reconnectCheckIntervalMs?: number;
30
- }
31
-
32
- function clampDiscordContent(text: string): string {
33
- if (text.length <= DISCORD_MAX_CONTENT_LENGTH) {
34
- return text;
35
- }
36
-
37
- const suffix = "\n...(truncated)";
38
- const budget = DISCORD_MAX_CONTENT_LENGTH - suffix.length;
39
- if (budget <= 0) {
40
- return text.slice(0, DISCORD_MAX_CONTENT_LENGTH);
41
- }
42
-
43
- return `${text.slice(0, budget)}${suffix}`;
44
- }
45
-
46
- function isRecord(value: unknown): value is Record<string, unknown> {
47
- return Boolean(value) && typeof value === "object";
48
- }
49
-
50
- export class DiscordConnector implements ConnectorPlugin {
51
- readonly id: string;
52
- readonly platform = "discord" as const;
53
- readonly name = "discord";
54
- readonly capabilities: ConnectorCapabilities = {
55
- updateStrategy: "edit",
56
- supportedSources: ["channel"],
57
- supportsThread: true,
58
- supportsTyping: true,
59
- supportsFileUpload: true,
60
- maxTextLength: DISCORD_MAX_CONTENT_LENGTH,
61
- };
62
-
63
- private client: Client | null = null;
64
- private ctx: ConnectorContext | null = null;
65
- private botUserId: string | null = null;
66
- private botToken: string | null = null;
67
- private reconnectWatchdog: NodeJS.Timeout | null = null;
68
- private reconnectInFlight = false;
69
- private lastHealthyAtMs = 0;
70
- private stopped = false;
71
-
72
- constructor(
73
- id: string,
74
- private readonly config: DiscordConnectorConfig,
75
- private readonly attachmentsRoot: string,
76
- private readonly logger: GatewayLogger,
77
- ) {
78
- this.id = id;
79
- }
80
-
81
- async start(ctx: ConnectorContext): Promise<void> {
82
- if (this.client) {
83
- this.logger.warn({ connectorId: this.id }, "Discord connector start called while already started");
84
- return;
85
- }
86
-
87
- const token = this.config.botToken.trim();
88
- if (!token) {
89
- throw new Error("Discord bot token is empty");
90
- }
91
-
92
- this.ctx = ctx;
93
- this.botToken = token;
94
- this.stopped = false;
95
- this.lastHealthyAtMs = Date.now();
96
-
97
- this.client = this.createClient();
98
- this.bindClientEventHandlers(this.client);
99
- await this.client.login(token);
100
- this.startReconnectWatchdog();
101
- }
102
-
103
- async send(message: OutboundEnvelope): Promise<ConnectorSendResult> {
104
- if (!this.client) {
105
- throw new Error("Discord connector is not started");
106
- }
107
-
108
- const channel = await this.fetchTextChannel(message.chatId);
109
- const content = clampDiscordContent(message.text);
110
- if (content !== message.text) {
111
- this.logger.warn(
112
- {
113
- originalLength: message.text.length,
114
- truncatedLength: content.length,
115
- mode: message.mode,
116
- chatId: message.chatId,
117
- },
118
- "Outbound Discord message exceeded 2000 characters and was truncated",
119
- );
120
- }
121
-
122
- if (message.mode === "update") {
123
- if (!message.targetMessageId) {
124
- throw new Error("targetMessageId is required for update mode");
125
- }
126
-
127
- const existing = await channel.messages.fetch(message.targetMessageId);
128
- const edited = await existing.edit({ content });
129
- return { messageId: edited.id };
130
- }
131
-
132
- const options: MessageCreateOptions = {
133
- content,
134
- };
135
-
136
- if (message.replyToMessageId) {
137
- options.reply = { messageReference: message.replyToMessageId, failIfNotExists: false };
138
- }
139
-
140
- if (message.attachments && message.attachments.length > 0) {
141
- options.files = message.attachments.map((attachment) =>
142
- attachment.title
143
- ? new AttachmentBuilder(attachment.localPath, { name: attachment.title })
144
- : new AttachmentBuilder(attachment.localPath),
145
- );
146
- }
147
-
148
- const sent = await channel.send(options);
149
- return { messageId: sent.id };
150
- }
151
-
152
- async sendTyping(message: ConnectorTypingEnvelope): Promise<void> {
153
- if (!this.client) {
154
- throw new Error("Discord connector is not started");
155
- }
156
-
157
- const channel = await this.fetchTextChannel(message.chatId);
158
- await channel.sendTyping();
159
- }
160
-
161
- async stop(): Promise<void> {
162
- this.stopped = true;
163
- this.stopReconnectWatchdog();
164
- this.reconnectInFlight = false;
165
- if (!this.client) return;
166
-
167
- const client = this.client;
168
- this.client = null;
169
- client.removeAllListeners();
170
- client.destroy();
171
- this.ctx = null;
172
- this.botUserId = null;
173
- this.botToken = null;
174
- this.lastHealthyAtMs = 0;
175
- }
176
-
177
- private createClient(): Client {
178
- return new Client({
179
- intents: [
180
- GatewayIntentBits.Guilds,
181
- GatewayIntentBits.GuildMessages,
182
- GatewayIntentBits.MessageContent,
183
- GatewayIntentBits.DirectMessages,
184
- ],
185
- partials: [Partials.Channel, Partials.Message],
186
- });
187
- }
188
-
189
- private bindClientEventHandlers(client: Client): void {
190
- client.once("clientReady", () => {
191
- if (client !== this.client || !client.user) return;
192
- this.botUserId = client.user.id;
193
- this.lastHealthyAtMs = Date.now();
194
- this.logger.info(
195
- {
196
- userId: this.botUserId,
197
- userName: client.user.username,
198
- configuredBotName: this.config.botName,
199
- },
200
- "Discord connector ready",
201
- );
202
- });
203
-
204
- client.on("shardDisconnect", (event, shardId) => {
205
- if (client !== this.client) return;
206
- this.logger.warn(
207
- {
208
- shardId,
209
- reconnecting: client.ws.shards.get(shardId)?.status === 5,
210
- ...this.parseCloseEvent(event),
211
- },
212
- "Discord shard disconnected",
213
- );
214
- });
215
-
216
- client.on("shardReconnecting", (shardId) => {
217
- if (client !== this.client) return;
218
- this.logger.warn({ shardId }, "Discord shard reconnecting");
219
- });
220
-
221
- client.on("shardResume", (shardId, replayedEvents) => {
222
- if (client !== this.client) return;
223
- this.lastHealthyAtMs = Date.now();
224
- this.logger.info({ shardId, replayedEvents }, "Discord shard resumed");
225
- });
226
-
227
- client.on("error", (error) => {
228
- if (client !== this.client) return;
229
- this.logger.warn({ err: error }, "Discord client error");
230
- });
231
-
232
- client.on("shardError", (error, shardId) => {
233
- if (client !== this.client) return;
234
- this.logger.warn({ err: error, shardId }, "Discord shard error");
235
- });
236
-
237
- client.on("invalidated", () => {
238
- if (client !== this.client) return;
239
- this.logger.error("Discord session invalidated; forcing reconnect");
240
- void this.forceReconnect("session_invalidated");
241
- });
242
-
243
- client.on("messageCreate", async (message: Message) => {
244
- if (client !== this.client || !client.user || !this.ctx || !this.botUserId) return;
245
-
246
- // v1 explicitly disables DM handling; only bound guild channels are processed.
247
- if (!message.guildId) {
248
- return;
249
- }
250
-
251
- if (message.author.bot) return;
252
-
253
- const sourceId = message.channel.isThread() && message.channel.parentId ? message.channel.parentId : message.channelId;
254
-
255
- const inbound = await mapDiscordMessage(
256
- message,
257
- this.id,
258
- this.botUserId,
259
- sourceId,
260
- this.attachmentsRoot,
261
- this.logger,
262
- );
263
- if (!inbound) return;
264
-
265
- await this.ctx.emitInbound(inbound);
266
- });
267
- }
268
-
269
- private startReconnectWatchdog(): void {
270
- if (this.reconnectWatchdog) return;
271
- const intervalMs = this.config.reconnectCheckIntervalMs ?? DEFAULT_RECONNECT_CHECK_INTERVAL_MS;
272
- this.reconnectWatchdog = setInterval(() => {
273
- void this.ensureConnected();
274
- }, intervalMs);
275
- }
276
-
277
- private stopReconnectWatchdog(): void {
278
- if (!this.reconnectWatchdog) return;
279
- clearInterval(this.reconnectWatchdog);
280
- this.reconnectWatchdog = null;
281
- }
282
-
283
- private async ensureConnected(): Promise<void> {
284
- const client = this.client;
285
- if (!client || this.stopped || this.reconnectInFlight) return;
286
-
287
- if (client.isReady()) {
288
- this.lastHealthyAtMs = Date.now();
289
- return;
290
- }
291
-
292
- const staleMs = Date.now() - this.lastHealthyAtMs;
293
- const thresholdMs = this.config.reconnectStaleMs ?? DEFAULT_RECONNECT_STALE_MS;
294
- if (staleMs < thresholdMs) {
295
- return;
296
- }
297
-
298
- this.logger.warn({ staleMs, thresholdMs }, "Discord connector remained not-ready for too long; forcing reconnect");
299
- await this.forceReconnect("watchdog_not_ready");
300
- }
301
-
302
- private async forceReconnect(reason: string): Promise<void> {
303
- if (this.stopped || this.reconnectInFlight || !this.botToken) {
304
- return;
305
- }
306
-
307
- this.reconnectInFlight = true;
308
- const previousClient = this.client;
309
-
310
- try {
311
- if (previousClient) {
312
- previousClient.removeAllListeners();
313
- previousClient.destroy();
314
- }
315
-
316
- this.botUserId = null;
317
- this.lastHealthyAtMs = Date.now();
318
-
319
- const nextClient = this.createClient();
320
- this.client = nextClient;
321
- this.bindClientEventHandlers(nextClient);
322
- await nextClient.login(this.botToken);
323
- this.logger.info({ reason }, "Discord reconnect login submitted");
324
- } catch (error) {
325
- this.logger.error({ err: error, reason }, "Failed to force Discord reconnect");
326
- } finally {
327
- this.reconnectInFlight = false;
328
- }
329
- }
330
-
331
- private parseCloseEvent(event: unknown): Record<string, unknown> {
332
- if (!isRecord(event)) return {};
333
- const result: Record<string, unknown> = {};
334
- if (typeof event.code === "number") result.code = event.code;
335
- if (typeof event.reason === "string" && event.reason.length > 0) result.reason = event.reason;
336
- if (typeof event.wasClean === "boolean") result.wasClean = event.wasClean;
337
- return result;
338
- }
339
-
340
- private async fetchTextChannel(channelId: string): Promise<SendableChannels> {
341
- if (!this.client) throw new Error("Discord connector is not started");
342
-
343
- const channel = await this.client.channels.fetch(channelId);
344
- if (!channel || !channel.isSendable()) {
345
- throw new Error(`Discord channel '${channelId}' is not text-based`);
346
- }
347
-
348
- return channel;
349
- }
350
- }
@@ -1,21 +0,0 @@
1
- import { z } from "zod";
2
- import type { ConnectorContributionModule } from "@dobby.ai/plugin-sdk";
3
- import { DiscordConnector, type DiscordConnectorConfig } from "./connector.js";
4
-
5
- const discordConnectorConfigSchema = z.object({
6
- botName: z.string().min(1),
7
- botToken: z.string().min(1),
8
- reconnectStaleMs: z.number().int().positive().default(60_000),
9
- reconnectCheckIntervalMs: z.number().int().positive().default(10_000),
10
- });
11
-
12
- export const connectorDiscordContribution: ConnectorContributionModule = {
13
- kind: "connector",
14
- configSchema: z.toJSONSchema(discordConnectorConfigSchema),
15
- createInstance(options) {
16
- const config = discordConnectorConfigSchema.parse(options.config) as DiscordConnectorConfig;
17
- return new DiscordConnector(options.instanceId, config, options.attachmentsRoot, options.host.logger);
18
- },
19
- };
20
-
21
- export default connectorDiscordContribution;
@@ -1,102 +0,0 @@
1
- import { mkdir, writeFile } from "node:fs/promises";
2
- import { join } from "node:path";
3
- import type { Message } from "discord.js";
4
- import type { GatewayLogger, InboundAttachment, InboundEnvelope } from "@dobby.ai/plugin-sdk";
5
-
6
- function stripBotMention(text: string, botUserId: string): string {
7
- const mentionRegex = new RegExp(`<@!?${botUserId}>`, "g");
8
- return text.replace(mentionRegex, "").trim();
9
- }
10
-
11
- function sanitizeFileName(value: string): string {
12
- return value.replaceAll(/[^a-zA-Z0-9._-]/g, "_");
13
- }
14
-
15
- async function downloadAttachment(url: string, targetPath: string): Promise<void> {
16
- const response = await fetch(url);
17
- if (!response.ok) {
18
- throw new Error(`Failed to download attachment from ${url}: ${response.status}`);
19
- }
20
-
21
- const data = await response.arrayBuffer();
22
- await writeFile(targetPath, Buffer.from(data));
23
- }
24
-
25
- function mapAttachmentBase(messageAttachment: {
26
- id: string;
27
- name: string | null;
28
- contentType: string | null;
29
- size: number;
30
- url: string;
31
- }): InboundAttachment {
32
- return {
33
- id: messageAttachment.id,
34
- size: messageAttachment.size,
35
- remoteUrl: messageAttachment.url,
36
- ...(messageAttachment.name ? { fileName: messageAttachment.name } : {}),
37
- ...(messageAttachment.contentType ? { mimeType: messageAttachment.contentType } : {}),
38
- };
39
- }
40
-
41
- export async function mapDiscordMessage(
42
- message: Message,
43
- connectorId: string,
44
- botUserId: string,
45
- sourceId: string,
46
- attachmentsRoot: string,
47
- logger: GatewayLogger,
48
- ): Promise<InboundEnvelope | null> {
49
- if (message.author.bot) return null;
50
-
51
- const isDirectMessage = message.guildId == null;
52
- const mentionedBot = message.mentions.users.has(botUserId);
53
-
54
- const chatId = message.channelId;
55
- const threadId = message.channel.isThread() ? message.channelId : undefined;
56
-
57
- const cleanedText = stripBotMention(message.content ?? "", botUserId);
58
-
59
- const attachmentDir = join(attachmentsRoot, sourceId, message.id);
60
- await mkdir(attachmentDir, { recursive: true });
61
-
62
- const attachments: InboundAttachment[] = [];
63
-
64
- for (const attachment of message.attachments.values()) {
65
- const base = mapAttachmentBase(attachment);
66
- const fileName = sanitizeFileName(attachment.name ?? attachment.id);
67
- const localPath = join(attachmentDir, fileName);
68
-
69
- try {
70
- await downloadAttachment(attachment.url, localPath);
71
- attachments.push({
72
- ...base,
73
- localPath,
74
- });
75
- } catch (error) {
76
- logger.warn({ err: error, attachmentUrl: attachment.url }, "Failed to download Discord attachment; keeping metadata only");
77
- attachments.push(base);
78
- }
79
- }
80
-
81
- return {
82
- connectorId,
83
- platform: "discord",
84
- accountId: botUserId,
85
- source: {
86
- type: "channel",
87
- id: sourceId,
88
- },
89
- chatId,
90
- messageId: message.id,
91
- userId: message.author.id,
92
- userName: message.author.username,
93
- text: cleanedText,
94
- attachments,
95
- timestampMs: message.createdTimestamp,
96
- raw: message.toJSON(),
97
- isDirectMessage,
98
- mentionedBot,
99
- ...(message.guildId ? { guildId: message.guildId } : {}),
100
- ...(threadId ? { threadId } : {}),
101
- };
102
- }
@@ -1,19 +0,0 @@
1
- {
2
- "compilerOptions": {
3
- "target": "ES2022",
4
- "module": "NodeNext",
5
- "moduleResolution": "NodeNext",
6
- "outDir": "./dist",
7
- "rootDir": "./src",
8
- "types": ["node"],
9
- "strict": true,
10
- "esModuleInterop": true,
11
- "forceConsistentCasingInFileNames": true,
12
- "skipLibCheck": true,
13
- "noUncheckedIndexedAccess": true,
14
- "exactOptionalPropertyTypes": true,
15
- "resolveJsonModule": true
16
- },
17
- "include": ["src/**/*.ts"],
18
- "exclude": ["dist", "node_modules"]
19
- }
@@ -1,18 +0,0 @@
1
- {
2
- "apiVersion": "1.0",
3
- "name": "@dobby.ai/connector-feishu",
4
- "version": "0.1.0",
5
- "contributions": [
6
- {
7
- "id": "connector.feishu",
8
- "kind": "connector",
9
- "entry": "./dist/contribution.js",
10
- "capabilities": {
11
- "updateStrategy": "edit",
12
- "supportsThread": true,
13
- "supportsTyping": false,
14
- "supportsFileUpload": false
15
- }
16
- }
17
- ]
18
- }
@@ -1 +0,0 @@
1
- export { connectorFeishuContribution as contribution, connectorFeishuContribution as default } from "./dist/contribution.js";