@dobby.ai/dobby 0.1.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 (174) hide show
  1. package/.env.example +9 -0
  2. package/AGENTS.md +267 -0
  3. package/README.md +382 -0
  4. package/ROADMAP.md +34 -0
  5. package/config/cron.example.json +9 -0
  6. package/config/gateway.example.json +128 -0
  7. package/config/models.custom.example.json +27 -0
  8. package/dist/src/agent/event-forwarder.js +341 -0
  9. package/dist/src/agent/tests/event-forwarder.test.js +113 -0
  10. package/dist/src/cli/commands/config.js +243 -0
  11. package/dist/src/cli/commands/configure.js +61 -0
  12. package/dist/src/cli/commands/cron.js +288 -0
  13. package/dist/src/cli/commands/doctor.js +189 -0
  14. package/dist/src/cli/commands/extension.js +151 -0
  15. package/dist/src/cli/commands/init.js +286 -0
  16. package/dist/src/cli/commands/start.js +177 -0
  17. package/dist/src/cli/commands/topology.js +254 -0
  18. package/dist/src/cli/index.js +8 -0
  19. package/dist/src/cli/program.js +386 -0
  20. package/dist/src/cli/shared/config-io.js +223 -0
  21. package/dist/src/cli/shared/config-mutators.js +345 -0
  22. package/dist/src/cli/shared/config-path.js +207 -0
  23. package/dist/src/cli/shared/config-schema.js +159 -0
  24. package/dist/src/cli/shared/config-types.js +1 -0
  25. package/dist/src/cli/shared/configure-sections.js +429 -0
  26. package/dist/src/cli/shared/discord-config.js +12 -0
  27. package/dist/src/cli/shared/init-catalog.js +115 -0
  28. package/dist/src/cli/shared/init-models-file.js +65 -0
  29. package/dist/src/cli/shared/presets.js +86 -0
  30. package/dist/src/cli/shared/runtime.js +29 -0
  31. package/dist/src/cli/shared/schema-prompts.js +325 -0
  32. package/dist/src/cli/tests/config-command.test.js +42 -0
  33. package/dist/src/cli/tests/config-io.test.js +64 -0
  34. package/dist/src/cli/tests/config-mutators.test.js +47 -0
  35. package/dist/src/cli/tests/config-path.test.js +21 -0
  36. package/dist/src/cli/tests/discord-config.test.js +23 -0
  37. package/dist/src/cli/tests/doctor.test.js +107 -0
  38. package/dist/src/cli/tests/init-catalog.test.js +87 -0
  39. package/dist/src/cli/tests/presets.test.js +41 -0
  40. package/dist/src/cli/tests/program-options.test.js +92 -0
  41. package/dist/src/cli/tests/routing-config.test.js +199 -0
  42. package/dist/src/cli/tests/routing-legacy.test.js +191 -0
  43. package/dist/src/core/control-command.js +12 -0
  44. package/dist/src/core/dedup-store.js +92 -0
  45. package/dist/src/core/gateway.js +432 -0
  46. package/dist/src/core/routing.js +306 -0
  47. package/dist/src/core/runtime-registry.js +119 -0
  48. package/dist/src/core/tests/control-command.test.js +17 -0
  49. package/dist/src/core/tests/gateway-update-strategy.test.js +167 -0
  50. package/dist/src/core/tests/runtime-registry.test.js +116 -0
  51. package/dist/src/core/tests/typing-controller.test.js +103 -0
  52. package/dist/src/core/types.js +1 -0
  53. package/dist/src/core/typing-controller.js +88 -0
  54. package/dist/src/cron/config.js +114 -0
  55. package/dist/src/cron/schedule.js +49 -0
  56. package/dist/src/cron/service.js +196 -0
  57. package/dist/src/cron/store.js +142 -0
  58. package/dist/src/cron/types.js +1 -0
  59. package/dist/src/extension/loader.js +97 -0
  60. package/dist/src/extension/manager.js +269 -0
  61. package/dist/src/extension/manifest.js +21 -0
  62. package/dist/src/extension/registry.js +137 -0
  63. package/dist/src/main.js +6 -0
  64. package/dist/src/sandbox/executor.js +1 -0
  65. package/dist/src/sandbox/host-executor.js +111 -0
  66. package/docs/BOXLITE_SANDBOX_FEASIBILITY.md +175 -0
  67. package/docs/CRON_SCHEDULER_DESIGN.md +374 -0
  68. package/docs/DOCKER_SANDBOX_vs_BOXLITE.md +77 -0
  69. package/docs/EXTENSION_SYSTEM_ARCHITECTURE.md +119 -0
  70. package/docs/MVP.md +135 -0
  71. package/docs/RUNBOOK.md +242 -0
  72. package/docs/TEAMWORK_HANDOFF_DESIGN.md +440 -0
  73. package/package.json +43 -0
  74. package/plugins/connector-discord/dobby.manifest.json +18 -0
  75. package/plugins/connector-discord/index.js +1 -0
  76. package/plugins/connector-discord/package-lock.json +360 -0
  77. package/plugins/connector-discord/package.json +38 -0
  78. package/plugins/connector-discord/src/connector.ts +350 -0
  79. package/plugins/connector-discord/src/contribution.ts +21 -0
  80. package/plugins/connector-discord/src/mapper.ts +102 -0
  81. package/plugins/connector-discord/tsconfig.json +19 -0
  82. package/plugins/connector-feishu/dobby.manifest.json +18 -0
  83. package/plugins/connector-feishu/index.js +1 -0
  84. package/plugins/connector-feishu/package-lock.json +618 -0
  85. package/plugins/connector-feishu/package.json +38 -0
  86. package/plugins/connector-feishu/src/connector.ts +343 -0
  87. package/plugins/connector-feishu/src/contribution.ts +26 -0
  88. package/plugins/connector-feishu/src/mapper.ts +401 -0
  89. package/plugins/connector-feishu/tsconfig.json +19 -0
  90. package/plugins/plugin-sdk/index.d.ts +261 -0
  91. package/plugins/plugin-sdk/index.js +1 -0
  92. package/plugins/plugin-sdk/package-lock.json +12 -0
  93. package/plugins/plugin-sdk/package.json +22 -0
  94. package/plugins/provider-claude/dobby.manifest.json +17 -0
  95. package/plugins/provider-claude/index.js +1 -0
  96. package/plugins/provider-claude/package-lock.json +3398 -0
  97. package/plugins/provider-claude/package.json +39 -0
  98. package/plugins/provider-claude/src/contribution.ts +1018 -0
  99. package/plugins/provider-claude/tsconfig.json +19 -0
  100. package/plugins/provider-claude-cli/dobby.manifest.json +17 -0
  101. package/plugins/provider-claude-cli/index.js +1 -0
  102. package/plugins/provider-claude-cli/package-lock.json +2898 -0
  103. package/plugins/provider-claude-cli/package.json +38 -0
  104. package/plugins/provider-claude-cli/src/contribution.ts +1673 -0
  105. package/plugins/provider-claude-cli/tsconfig.json +19 -0
  106. package/plugins/provider-pi/dobby.manifest.json +17 -0
  107. package/plugins/provider-pi/index.js +1 -0
  108. package/plugins/provider-pi/package-lock.json +3877 -0
  109. package/plugins/provider-pi/package.json +40 -0
  110. package/plugins/provider-pi/src/contribution.ts +476 -0
  111. package/plugins/provider-pi/tsconfig.json +19 -0
  112. package/plugins/sandbox-core/boxlite.js +1 -0
  113. package/plugins/sandbox-core/dobby.manifest.json +17 -0
  114. package/plugins/sandbox-core/docker.js +1 -0
  115. package/plugins/sandbox-core/package-lock.json +136 -0
  116. package/plugins/sandbox-core/package.json +39 -0
  117. package/plugins/sandbox-core/src/boxlite-context.ts +2 -0
  118. package/plugins/sandbox-core/src/boxlite-contribution.ts +53 -0
  119. package/plugins/sandbox-core/src/boxlite-executor.ts +911 -0
  120. package/plugins/sandbox-core/src/docker-contribution.ts +43 -0
  121. package/plugins/sandbox-core/src/docker-executor.ts +217 -0
  122. package/plugins/sandbox-core/tsconfig.json +19 -0
  123. package/scripts/local-extensions.mjs +168 -0
  124. package/src/agent/event-forwarder.ts +414 -0
  125. package/src/cli/commands/config.ts +328 -0
  126. package/src/cli/commands/configure.ts +92 -0
  127. package/src/cli/commands/cron.ts +410 -0
  128. package/src/cli/commands/doctor.ts +230 -0
  129. package/src/cli/commands/extension.ts +205 -0
  130. package/src/cli/commands/init.ts +396 -0
  131. package/src/cli/commands/start.ts +223 -0
  132. package/src/cli/commands/topology.ts +383 -0
  133. package/src/cli/index.ts +9 -0
  134. package/src/cli/program.ts +465 -0
  135. package/src/cli/shared/config-io.ts +277 -0
  136. package/src/cli/shared/config-mutators.ts +440 -0
  137. package/src/cli/shared/config-schema.ts +228 -0
  138. package/src/cli/shared/config-types.ts +121 -0
  139. package/src/cli/shared/configure-sections.ts +551 -0
  140. package/src/cli/shared/discord-config.ts +14 -0
  141. package/src/cli/shared/init-catalog.ts +189 -0
  142. package/src/cli/shared/init-models-file.ts +77 -0
  143. package/src/cli/shared/runtime.ts +33 -0
  144. package/src/cli/shared/schema-prompts.ts +414 -0
  145. package/src/cli/tests/config-command.test.ts +56 -0
  146. package/src/cli/tests/config-io.test.ts +92 -0
  147. package/src/cli/tests/config-mutators.test.ts +59 -0
  148. package/src/cli/tests/doctor.test.ts +120 -0
  149. package/src/cli/tests/init-catalog.test.ts +96 -0
  150. package/src/cli/tests/program-options.test.ts +113 -0
  151. package/src/cli/tests/routing-config.test.ts +209 -0
  152. package/src/core/control-command.ts +12 -0
  153. package/src/core/dedup-store.ts +103 -0
  154. package/src/core/gateway.ts +607 -0
  155. package/src/core/routing.ts +379 -0
  156. package/src/core/runtime-registry.ts +141 -0
  157. package/src/core/tests/control-command.test.ts +20 -0
  158. package/src/core/tests/runtime-registry.test.ts +140 -0
  159. package/src/core/tests/typing-controller.test.ts +129 -0
  160. package/src/core/types.ts +318 -0
  161. package/src/core/typing-controller.ts +119 -0
  162. package/src/cron/config.ts +154 -0
  163. package/src/cron/schedule.ts +61 -0
  164. package/src/cron/service.ts +249 -0
  165. package/src/cron/store.ts +155 -0
  166. package/src/cron/types.ts +60 -0
  167. package/src/extension/loader.ts +145 -0
  168. package/src/extension/manager.ts +355 -0
  169. package/src/extension/manifest.ts +26 -0
  170. package/src/extension/registry.ts +229 -0
  171. package/src/main.ts +8 -0
  172. package/src/sandbox/executor.ts +44 -0
  173. package/src/sandbox/host-executor.ts +118 -0
  174. package/tsconfig.json +18 -0
@@ -0,0 +1,343 @@
1
+ import { randomUUID } from "node:crypto";
2
+ import * as Lark from "@larksuiteoapi/node-sdk";
3
+ import type {
4
+ ConnectorCapabilities,
5
+ ConnectorContext,
6
+ ConnectorPlugin,
7
+ ConnectorSendResult,
8
+ GatewayLogger,
9
+ OutboundEnvelope,
10
+ } from "@dobby.ai/plugin-sdk";
11
+ import { mapFeishuMessageEvent, type FeishuMessageEvent } from "./mapper.js";
12
+
13
+ export interface FeishuConnectorConfig {
14
+ appId: string;
15
+ appSecret: string;
16
+ domain?: "feishu" | "lark";
17
+ botName?: string;
18
+ botOpenId?: string;
19
+ messageFormat?: "text" | "card_markdown";
20
+ replyMode?: "direct" | "reply";
21
+ cardTitle?: string;
22
+ downloadAttachments?: boolean;
23
+ }
24
+
25
+ const FEISHU_CARD_MAX_TEXT_LENGTH = 8_000;
26
+ const FEISHU_TEXT_MAX_TEXT_LENGTH = 12_000;
27
+
28
+ function resolveDomain(domain?: "feishu" | "lark"): string {
29
+ return domain === "lark" ? "https://open.larksuite.com" : "https://open.feishu.cn";
30
+ }
31
+
32
+ function textContent(text: string): string {
33
+ return JSON.stringify({ text });
34
+ }
35
+
36
+ function cardContent(text: string, title: string): string {
37
+ return JSON.stringify({
38
+ schema: "2.0",
39
+ config: {
40
+ wide_screen_mode: true,
41
+ update_multi: true,
42
+ },
43
+ header: {
44
+ template: "blue",
45
+ title: {
46
+ tag: "plain_text",
47
+ content: title,
48
+ },
49
+ },
50
+ body: {
51
+ direction: "vertical",
52
+ padding: "12px 12px 12px 12px",
53
+ elements: [
54
+ {
55
+ tag: "markdown",
56
+ content: text.trim().length > 0 ? text : "(empty response)",
57
+ },
58
+ ],
59
+ },
60
+ });
61
+ }
62
+
63
+ function toSendResult(messageId?: string): ConnectorSendResult {
64
+ return messageId ? { messageId } : {};
65
+ }
66
+
67
+ export class FeishuConnector implements ConnectorPlugin {
68
+ readonly id: string;
69
+ readonly platform = "feishu" as const;
70
+ readonly name = "feishu";
71
+ readonly capabilities: ConnectorCapabilities;
72
+
73
+ private ctx: ConnectorContext | null = null;
74
+ private client: Lark.Client | null = null;
75
+ private wsClient: Lark.WSClient | null = null;
76
+
77
+ constructor(
78
+ id: string,
79
+ private readonly config: FeishuConnectorConfig,
80
+ private readonly attachmentsRoot: string,
81
+ private readonly logger: GatewayLogger,
82
+ ) {
83
+ this.id = id;
84
+ this.capabilities = {
85
+ updateStrategy: this.messageFormat === "card_markdown" ? "final_only" : "edit",
86
+ supportedSources: ["chat"],
87
+ supportsThread: this.replyMode === "reply",
88
+ supportsTyping: false,
89
+ supportsFileUpload: false,
90
+ maxTextLength: this.messageFormat === "card_markdown" ? FEISHU_CARD_MAX_TEXT_LENGTH : FEISHU_TEXT_MAX_TEXT_LENGTH,
91
+ };
92
+ }
93
+
94
+ async start(ctx: ConnectorContext): Promise<void> {
95
+ if (this.wsClient) {
96
+ this.logger.warn({ connectorId: this.id }, "Feishu connector start called while already started");
97
+ return;
98
+ }
99
+
100
+ const baseConfig = {
101
+ appId: this.config.appId,
102
+ appSecret: this.config.appSecret,
103
+ domain: resolveDomain(this.config.domain),
104
+ };
105
+
106
+ this.ctx = ctx;
107
+ this.client = new Lark.Client(baseConfig);
108
+ this.wsClient = new Lark.WSClient(baseConfig);
109
+ await this.logAppSubscriptionState();
110
+
111
+ const dispatcher = new Lark.EventDispatcher({}).register({
112
+ "im.message.receive_v1": async (event: FeishuMessageEvent) => {
113
+ if (!this.ctx || !this.client) {
114
+ return;
115
+ }
116
+
117
+ this.logger.info(
118
+ {
119
+ connectorId: this.id,
120
+ chatId: event.message.chat_id,
121
+ messageId: event.message.message_id,
122
+ chatType: event.message.chat_type,
123
+ messageType: event.message.message_type,
124
+ mentionCount: event.message.mentions?.length ?? 0,
125
+ },
126
+ "Feishu inbound event received",
127
+ );
128
+
129
+ const inbound = await mapFeishuMessageEvent({
130
+ event,
131
+ connectorId: this.id,
132
+ attachmentsRoot: this.attachmentsRoot,
133
+ client: this.client,
134
+ logger: this.logger,
135
+ downloadAttachments: this.config.downloadAttachments !== false,
136
+ ...(this.config.botOpenId ? { botOpenId: this.config.botOpenId } : {}),
137
+ ...(this.config.botName ? { botName: this.config.botName } : {}),
138
+ });
139
+ if (!inbound) {
140
+ return;
141
+ }
142
+
143
+ this.logger.info(
144
+ {
145
+ connectorId: this.id,
146
+ chatId: inbound.chatId,
147
+ messageId: inbound.messageId,
148
+ isDirectMessage: inbound.isDirectMessage,
149
+ mentionedBot: inbound.mentionedBot,
150
+ textLength: inbound.text.length,
151
+ attachmentCount: inbound.attachments.length,
152
+ },
153
+ "Feishu inbound event mapped",
154
+ );
155
+
156
+ await this.ctx.emitInbound(inbound);
157
+ },
158
+ });
159
+
160
+ await this.wsClient.start({
161
+ eventDispatcher: dispatcher,
162
+ });
163
+
164
+ this.logger.info(
165
+ {
166
+ connectorId: this.id,
167
+ domain: this.config.domain ?? "feishu",
168
+ messageFormat: this.messageFormat,
169
+ replyMode: this.replyMode,
170
+ },
171
+ "Feishu connector ready",
172
+ );
173
+ }
174
+
175
+ async send(message: OutboundEnvelope): Promise<ConnectorSendResult> {
176
+ if (!this.client) {
177
+ throw new Error("Feishu connector is not started");
178
+ }
179
+
180
+ if (message.attachments && message.attachments.length > 0) {
181
+ this.logger.warn(
182
+ {
183
+ connectorId: this.id,
184
+ chatId: message.chatId,
185
+ attachmentCount: message.attachments.length,
186
+ },
187
+ "Outbound Feishu attachments are not supported yet; sending text only",
188
+ );
189
+ }
190
+
191
+ if (message.mode === "update") {
192
+ if (!message.targetMessageId) {
193
+ throw new Error("targetMessageId is required for update mode");
194
+ }
195
+
196
+ if (this.messageFormat === "card_markdown") {
197
+ await this.client.im.v1.message.patch({
198
+ path: {
199
+ message_id: message.targetMessageId,
200
+ },
201
+ data: {
202
+ content: this.renderContent(message.text),
203
+ },
204
+ });
205
+ return { messageId: message.targetMessageId };
206
+ }
207
+
208
+ const response = await this.client.im.v1.message.update({
209
+ path: {
210
+ message_id: message.targetMessageId,
211
+ },
212
+ data: {
213
+ msg_type: "text",
214
+ content: this.renderContent(message.text),
215
+ },
216
+ });
217
+ return { messageId: response.data?.message_id ?? message.targetMessageId };
218
+ }
219
+
220
+ if (this.replyMode === "reply" && message.replyToMessageId) {
221
+ const response = await this.client.im.v1.message.reply({
222
+ path: {
223
+ message_id: message.replyToMessageId,
224
+ },
225
+ data: {
226
+ msg_type: this.renderMessageType(),
227
+ content: this.renderContent(message.text),
228
+ reply_in_thread: Boolean(message.threadId),
229
+ uuid: randomUUID(),
230
+ },
231
+ });
232
+ return toSendResult(response.data?.message_id);
233
+ }
234
+
235
+ if (this.replyMode === "reply" && message.threadId) {
236
+ const response = await this.client.im.v1.message.reply({
237
+ path: {
238
+ message_id: message.threadId,
239
+ },
240
+ data: {
241
+ msg_type: this.renderMessageType(),
242
+ content: this.renderContent(message.text),
243
+ reply_in_thread: true,
244
+ uuid: randomUUID(),
245
+ },
246
+ });
247
+ return toSendResult(response.data?.message_id);
248
+ }
249
+
250
+ const response = await this.client.im.v1.message.create({
251
+ params: {
252
+ receive_id_type: "chat_id",
253
+ },
254
+ data: {
255
+ receive_id: message.chatId,
256
+ msg_type: this.renderMessageType(),
257
+ content: this.renderContent(message.text),
258
+ uuid: randomUUID(),
259
+ },
260
+ });
261
+ return toSendResult(response.data?.message_id);
262
+ }
263
+
264
+ async stop(): Promise<void> {
265
+ const wsClient = this.wsClient;
266
+ this.wsClient = null;
267
+ this.client = null;
268
+ this.ctx = null;
269
+ wsClient?.close({ force: true });
270
+ }
271
+
272
+ private async logAppSubscriptionState(): Promise<void> {
273
+ if (!this.client) {
274
+ return;
275
+ }
276
+
277
+ try {
278
+ const response = await this.client.application.v6.application.get({
279
+ path: {
280
+ app_id: this.config.appId,
281
+ },
282
+ params: {
283
+ lang: "zh_cn",
284
+ },
285
+ });
286
+
287
+ const subscribedEvents = response.data?.app?.event?.subscribed_events ?? [];
288
+ this.logger.info(
289
+ {
290
+ connectorId: this.id,
291
+ appId: this.config.appId,
292
+ appName: response.data?.app?.app_name ?? this.config.botName ?? null,
293
+ onlineVersionId: response.data?.app?.online_version_id ?? null,
294
+ draftVersionId: response.data?.app?.unaudit_version_id ?? null,
295
+ subscriptionType: response.data?.app?.event?.subscription_type ?? null,
296
+ subscribedEvents,
297
+ },
298
+ "Feishu published application info loaded",
299
+ );
300
+
301
+ if (!subscribedEvents.includes("im.message.receive_v1")) {
302
+ this.logger.warn(
303
+ {
304
+ connectorId: this.id,
305
+ appId: this.config.appId,
306
+ onlineVersionId: response.data?.app?.online_version_id ?? null,
307
+ draftVersionId: response.data?.app?.unaudit_version_id ?? null,
308
+ subscribedEvents,
309
+ },
310
+ "Published Feishu app config does not show im.message.receive_v1",
311
+ );
312
+ }
313
+ } catch (error) {
314
+ this.logger.warn(
315
+ {
316
+ err: error,
317
+ connectorId: this.id,
318
+ appId: this.config.appId,
319
+ },
320
+ "Failed to load Feishu application info",
321
+ );
322
+ }
323
+ }
324
+
325
+ private get messageFormat(): "text" | "card_markdown" {
326
+ return this.config.messageFormat ?? "card_markdown";
327
+ }
328
+
329
+ private get replyMode(): "direct" | "reply" {
330
+ return this.config.replyMode ?? "direct";
331
+ }
332
+
333
+ private renderMessageType(): "text" | "interactive" {
334
+ return this.messageFormat === "card_markdown" ? "interactive" : "text";
335
+ }
336
+
337
+ private renderContent(text: string): string {
338
+ if (this.messageFormat === "card_markdown") {
339
+ return cardContent(text, this.config.cardTitle ?? this.config.botName ?? "dobby");
340
+ }
341
+ return textContent(text);
342
+ }
343
+ }
@@ -0,0 +1,26 @@
1
+ import { z } from "zod";
2
+ import type { ConnectorContributionModule } from "@dobby.ai/plugin-sdk";
3
+ import { FeishuConnector, type FeishuConnectorConfig } from "./connector.js";
4
+
5
+ const feishuConnectorConfigSchema = z.object({
6
+ appId: z.string().min(1),
7
+ appSecret: z.string().min(1),
8
+ domain: z.enum(["feishu", "lark"]).default("feishu"),
9
+ botName: z.string().min(1).optional(),
10
+ botOpenId: z.string().min(1).optional(),
11
+ messageFormat: z.enum(["text", "card_markdown"]).default("card_markdown"),
12
+ replyMode: z.enum(["direct", "reply"]).default("direct"),
13
+ cardTitle: z.string().min(1).optional(),
14
+ downloadAttachments: z.boolean().default(true),
15
+ });
16
+
17
+ export const connectorFeishuContribution: ConnectorContributionModule = {
18
+ kind: "connector",
19
+ configSchema: z.toJSONSchema(feishuConnectorConfigSchema),
20
+ createInstance(options) {
21
+ const config = feishuConnectorConfigSchema.parse(options.config) as FeishuConnectorConfig;
22
+ return new FeishuConnector(options.instanceId, config, options.attachmentsRoot, options.host.logger);
23
+ },
24
+ };
25
+
26
+ export default connectorFeishuContribution;