@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,191 @@
1
+ import assert from "node:assert/strict";
2
+ import { mkdtemp, rm, writeFile } from "node:fs/promises";
3
+ import { tmpdir } from "node:os";
4
+ import { dirname, join } from "node:path";
5
+ import test from "node:test";
6
+ import { loadGatewayConfig } from "../../core/routing.js";
7
+ async function writeTempConfig(payload) {
8
+ const dir = await mkdtemp(join(tmpdir(), "dobby-routing-"));
9
+ const configPath = join(dir, "gateway.json");
10
+ await writeFile(configPath, `${JSON.stringify(payload, null, 2)}\n`, "utf-8");
11
+ return configPath;
12
+ }
13
+ function validConfig() {
14
+ return {
15
+ extensions: { allowList: [] },
16
+ providers: {
17
+ default: "pi.main",
18
+ items: {
19
+ "pi.main": {
20
+ type: "provider.pi",
21
+ },
22
+ },
23
+ },
24
+ connectors: {
25
+ items: {
26
+ "discord.main": {
27
+ type: "connector.discord",
28
+ botName: "dobby-main",
29
+ botToken: "token",
30
+ },
31
+ },
32
+ },
33
+ sandboxes: {
34
+ default: "host.builtin",
35
+ items: {},
36
+ },
37
+ routes: {
38
+ defaults: {
39
+ provider: "pi.main",
40
+ sandbox: "host.builtin",
41
+ tools: "full",
42
+ mentions: "required",
43
+ },
44
+ items: {
45
+ main: {
46
+ projectRoot: "./workspace/project-a",
47
+ systemPromptFile: "./prompts/main.md",
48
+ },
49
+ },
50
+ },
51
+ bindings: {
52
+ items: {
53
+ "discord.main.main": {
54
+ connector: "discord.main",
55
+ source: {
56
+ type: "channel",
57
+ id: "123",
58
+ },
59
+ route: "main",
60
+ },
61
+ },
62
+ },
63
+ data: {
64
+ rootDir: "./data",
65
+ dedupTtlMs: 604800000,
66
+ },
67
+ };
68
+ }
69
+ test("loadGatewayConfig applies route defaults and resolves relative paths", async () => {
70
+ const payload = validConfig();
71
+ const configPath = await writeTempConfig(payload);
72
+ try {
73
+ const loaded = await loadGatewayConfig(configPath);
74
+ const configDir = dirname(configPath);
75
+ assert.equal(loaded.providers.default, "pi.main");
76
+ assert.deepEqual(loaded.routes.defaults, {
77
+ provider: "pi.main",
78
+ sandbox: "host.builtin",
79
+ tools: "full",
80
+ mentions: "required",
81
+ });
82
+ assert.deepEqual(loaded.routes.items.main, {
83
+ projectRoot: join(configDir, "workspace/project-a"),
84
+ systemPromptFile: join(configDir, "prompts/main.md"),
85
+ provider: "pi.main",
86
+ sandbox: "host.builtin",
87
+ tools: "full",
88
+ mentions: "required",
89
+ });
90
+ assert.equal(loaded.data.rootDir, join(configDir, "data"));
91
+ assert.deepEqual(loaded.bindings.items["discord.main.main"], {
92
+ connector: "discord.main",
93
+ source: {
94
+ type: "channel",
95
+ id: "123",
96
+ },
97
+ route: "main",
98
+ });
99
+ }
100
+ finally {
101
+ await rm(dirname(configPath), { recursive: true, force: true });
102
+ }
103
+ });
104
+ test("loadGatewayConfig fails fast on legacy top-level routing", async () => {
105
+ const payload = validConfig();
106
+ payload.routing = {
107
+ routes: {
108
+ main: {
109
+ projectRoot: process.cwd(),
110
+ },
111
+ },
112
+ };
113
+ const configPath = await writeTempConfig(payload);
114
+ try {
115
+ await assert.rejects(loadGatewayConfig(configPath), /top-level field 'routing'/);
116
+ }
117
+ finally {
118
+ await rm(dirname(configPath), { recursive: true, force: true });
119
+ }
120
+ });
121
+ test("loadGatewayConfig fails fast on legacy botTokenEnv", async () => {
122
+ const payload = validConfig();
123
+ payload.connectors = {
124
+ items: {
125
+ "discord.main": {
126
+ type: "connector.discord",
127
+ botName: "dobby-main",
128
+ botTokenEnv: "DISCORD_BOT_TOKEN",
129
+ },
130
+ },
131
+ };
132
+ const configPath = await writeTempConfig(payload);
133
+ try {
134
+ await assert.rejects(loadGatewayConfig(configPath), /botTokenEnv/);
135
+ }
136
+ finally {
137
+ await rm(dirname(configPath), { recursive: true, force: true });
138
+ }
139
+ });
140
+ test("loadGatewayConfig fails fast on legacy connector route maps", async () => {
141
+ const payload = validConfig();
142
+ payload.connectors = {
143
+ items: {
144
+ "discord.main": {
145
+ type: "connector.discord",
146
+ botName: "dobby-main",
147
+ botToken: "token",
148
+ botChannelMap: {
149
+ "123": "main",
150
+ },
151
+ },
152
+ },
153
+ };
154
+ const configPath = await writeTempConfig(payload);
155
+ try {
156
+ await assert.rejects(loadGatewayConfig(configPath), /botChannelMap/);
157
+ }
158
+ finally {
159
+ await rm(dirname(configPath), { recursive: true, force: true });
160
+ }
161
+ });
162
+ test("loadGatewayConfig fails fast on duplicate binding sources", async () => {
163
+ const payload = validConfig();
164
+ payload.bindings = {
165
+ items: {
166
+ "discord.main.main": {
167
+ connector: "discord.main",
168
+ source: {
169
+ type: "channel",
170
+ id: "123",
171
+ },
172
+ route: "main",
173
+ },
174
+ "discord.main.duplicate": {
175
+ connector: "discord.main",
176
+ source: {
177
+ type: "channel",
178
+ id: "123",
179
+ },
180
+ route: "main",
181
+ },
182
+ },
183
+ };
184
+ const configPath = await writeTempConfig(payload);
185
+ try {
186
+ await assert.rejects(loadGatewayConfig(configPath), /duplicates source 'discord\.main:channel:123'/);
187
+ }
188
+ finally {
189
+ await rm(dirname(configPath), { recursive: true, force: true });
190
+ }
191
+ });
@@ -0,0 +1,12 @@
1
+ const CANCEL_COMMANDS = new Set(["stop", "/stop", "/cancel"]);
2
+ const NEW_SESSION_COMMANDS = new Set(["/new", "/reset"]);
3
+ export function parseControlCommand(text) {
4
+ const normalized = text.trim().toLowerCase();
5
+ if (normalized.length === 0)
6
+ return null;
7
+ if (CANCEL_COMMANDS.has(normalized))
8
+ return "cancel";
9
+ if (NEW_SESSION_COMMANDS.has(normalized))
10
+ return "new_session";
11
+ return null;
12
+ }
@@ -0,0 +1,92 @@
1
+ import { mkdir, readFile, writeFile } from "node:fs/promises";
2
+ import { dirname } from "node:path";
3
+ export class DedupStore {
4
+ filePath;
5
+ ttlMs;
6
+ logger;
7
+ entries = new Map();
8
+ dirty = false;
9
+ flushTimer = null;
10
+ constructor(filePath, ttlMs, logger) {
11
+ this.filePath = filePath;
12
+ this.ttlMs = ttlMs;
13
+ this.logger = logger;
14
+ }
15
+ async load() {
16
+ try {
17
+ const raw = await readFile(this.filePath, "utf-8");
18
+ const snapshot = JSON.parse(raw);
19
+ const now = Date.now();
20
+ for (const [key, expiresAt] of Object.entries(snapshot.entries ?? {})) {
21
+ if (expiresAt > now) {
22
+ this.entries.set(key, expiresAt);
23
+ }
24
+ }
25
+ }
26
+ catch {
27
+ // First start or malformed file; start clean.
28
+ }
29
+ }
30
+ startAutoFlush(intervalMs = 15000) {
31
+ if (this.flushTimer)
32
+ return;
33
+ this.flushTimer = setInterval(() => {
34
+ void this.flush();
35
+ }, intervalMs);
36
+ }
37
+ stopAutoFlush() {
38
+ if (!this.flushTimer)
39
+ return;
40
+ clearInterval(this.flushTimer);
41
+ this.flushTimer = null;
42
+ }
43
+ has(key) {
44
+ const now = Date.now();
45
+ const expiresAt = this.entries.get(key);
46
+ if (!expiresAt)
47
+ return false;
48
+ if (expiresAt <= now) {
49
+ this.entries.delete(key);
50
+ this.dirty = true;
51
+ return false;
52
+ }
53
+ return true;
54
+ }
55
+ add(key) {
56
+ const expiresAt = Date.now() + this.ttlMs;
57
+ this.entries.set(key, expiresAt);
58
+ this.dirty = true;
59
+ if (this.entries.size % 100 === 0) {
60
+ this.sweepExpired();
61
+ }
62
+ }
63
+ sweepExpired() {
64
+ const now = Date.now();
65
+ let removed = 0;
66
+ for (const [key, expiresAt] of this.entries.entries()) {
67
+ if (expiresAt <= now) {
68
+ this.entries.delete(key);
69
+ removed += 1;
70
+ }
71
+ }
72
+ if (removed > 0) {
73
+ this.dirty = true;
74
+ }
75
+ }
76
+ async flush() {
77
+ if (!this.dirty)
78
+ return;
79
+ const snapshot = {
80
+ version: 1,
81
+ entries: Object.fromEntries(this.entries.entries()),
82
+ };
83
+ try {
84
+ await mkdir(dirname(this.filePath), { recursive: true });
85
+ await writeFile(this.filePath, JSON.stringify(snapshot, null, 2), "utf-8");
86
+ this.dirty = false;
87
+ }
88
+ catch (error) {
89
+ this.logger.error({ err: error, filePath: this.filePath }, "Failed to flush dedup store");
90
+ }
91
+ }
92
+ }