@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,410 +0,0 @@
1
- import { loadGatewayConfig } from "../../core/routing.js";
2
- import type { GatewayConfig } from "../../core/types.js";
3
- import { loadCronConfig } from "../../cron/config.js";
4
- import { computeInitialNextRunAtMs, describeSchedule } from "../../cron/schedule.js";
5
- import { CronStore } from "../../cron/store.js";
6
- import type { CronSessionPolicy, JobSchedule, ScheduledJob } from "../../cron/types.js";
7
- import { resolveConfigPath } from "../shared/config-io.js";
8
- import { createLogger } from "../shared/runtime.js";
9
-
10
- interface CronCommandSharedOptions {
11
- cronConfigPath?: string;
12
- }
13
-
14
- interface LoadedCronContext {
15
- configPath: string;
16
- gatewayConfig: GatewayConfig;
17
- cronConfigPath: string;
18
- store: CronStore;
19
- }
20
-
21
- interface ScheduleInput {
22
- at?: string;
23
- everyMs?: number;
24
- cronExpr?: string;
25
- tz?: string;
26
- }
27
-
28
- function slugify(value: string): string {
29
- const normalized = value
30
- .trim()
31
- .toLowerCase()
32
- .replaceAll(/[^a-z0-9]+/g, "-")
33
- .replaceAll(/^-+|-+$/g, "");
34
- return normalized.length > 0 ? normalized : "job";
35
- }
36
-
37
- function parseSchedule(input: ScheduleInput): JobSchedule {
38
- const variants = [input.at ? "at" : null, input.everyMs !== undefined ? "every" : null, input.cronExpr ? "cron" : null]
39
- .filter((item): item is "at" | "every" | "cron" => item !== null);
40
- if (variants.length !== 1) {
41
- throw new Error("Exactly one schedule option is required: --at | --every-ms | --cron");
42
- }
43
-
44
- if (input.at) {
45
- return { kind: "at", at: input.at };
46
- }
47
-
48
- if (input.everyMs !== undefined) {
49
- if (!Number.isFinite(input.everyMs) || input.everyMs <= 0) {
50
- throw new Error("--every-ms must be a positive integer");
51
- }
52
- return { kind: "every", everyMs: Math.floor(input.everyMs) };
53
- }
54
-
55
- if (!input.cronExpr) {
56
- throw new Error("Missing --cron expression");
57
- }
58
-
59
- return {
60
- kind: "cron",
61
- expr: input.cronExpr,
62
- ...(input.tz ? { tz: input.tz } : {}),
63
- };
64
- }
65
-
66
- function parseSessionPolicy(value: CronSessionPolicy | string | undefined): CronSessionPolicy | undefined {
67
- if (value === undefined) {
68
- return undefined;
69
- }
70
- if (value === "stateless" || value === "shared-session") {
71
- return value;
72
- }
73
- throw new Error(`Invalid session policy '${value}'. Expected 'stateless' or 'shared-session'.`);
74
- }
75
-
76
- function assertDeliveryReferences(
77
- config: GatewayConfig,
78
- input: {
79
- connectorId: string;
80
- routeId: string;
81
- },
82
- ): void {
83
- if (!config.connectors.items[input.connectorId]) {
84
- throw new Error(`Unknown connectorId '${input.connectorId}'`);
85
- }
86
- if (!config.routes.items[input.routeId]) {
87
- throw new Error(`Unknown routeId '${input.routeId}'`);
88
- }
89
- }
90
-
91
- async function loadCronContext(options?: CronCommandSharedOptions): Promise<LoadedCronContext> {
92
- const configPath = resolveConfigPath();
93
- const gatewayConfig = await loadGatewayConfig(configPath);
94
- const loadedCronConfig = await loadCronConfig({
95
- gatewayConfigPath: configPath,
96
- gatewayConfig,
97
- ...(options?.cronConfigPath ? { explicitCronConfigPath: options.cronConfigPath } : {}),
98
- });
99
-
100
- const logger = createLogger();
101
- const store = new CronStore(loadedCronConfig.config.storeFile, loadedCronConfig.config.runLogFile, logger);
102
- await store.load();
103
-
104
- return {
105
- configPath,
106
- gatewayConfig,
107
- cronConfigPath: loadedCronConfig.configPath,
108
- store,
109
- };
110
- }
111
-
112
- export async function runCronAddCommand(options: {
113
- name: string;
114
- prompt: string;
115
- connectorId: string;
116
- routeId: string;
117
- channelId: string;
118
- threadId?: string;
119
- sessionPolicy?: CronSessionPolicy;
120
- at?: string;
121
- everyMs?: number;
122
- cronExpr?: string;
123
- tz?: string;
124
- cronConfigPath?: string;
125
- }): Promise<void> {
126
- const context = await loadCronContext(
127
- options.cronConfigPath ? { cronConfigPath: options.cronConfigPath } : undefined,
128
- );
129
- assertDeliveryReferences(context.gatewayConfig, {
130
- connectorId: options.connectorId,
131
- routeId: options.routeId,
132
- });
133
-
134
- const schedule = parseSchedule({
135
- ...(options.at ? { at: options.at } : {}),
136
- ...(options.everyMs !== undefined ? { everyMs: options.everyMs } : {}),
137
- ...(options.cronExpr ? { cronExpr: options.cronExpr } : {}),
138
- ...(options.tz ? { tz: options.tz } : {}),
139
- });
140
- const now = Date.now();
141
- const nextRunAtMs = computeInitialNextRunAtMs(schedule, now);
142
- const id = `${slugify(options.name)}-${Math.random().toString(36).slice(2, 8)}`;
143
- const sessionPolicy = parseSessionPolicy(options.sessionPolicy) ?? "stateless";
144
-
145
- const job: ScheduledJob = {
146
- id,
147
- name: options.name,
148
- enabled: true,
149
- schedule,
150
- sessionPolicy,
151
- prompt: options.prompt,
152
- delivery: {
153
- connectorId: options.connectorId,
154
- routeId: options.routeId,
155
- channelId: options.channelId,
156
- ...(options.threadId ? { threadId: options.threadId } : {}),
157
- },
158
- createdAtMs: now,
159
- updatedAtMs: now,
160
- state: {
161
- ...(nextRunAtMs !== undefined ? { nextRunAtMs } : {}),
162
- consecutiveErrors: 0,
163
- },
164
- };
165
-
166
- await context.store.upsertJob(job);
167
- console.log(`Added cron job ${job.id}`);
168
- console.log(`- schedule: ${describeSchedule(job.schedule)}`);
169
- console.log(`- delivery: ${job.delivery.connectorId}/${job.delivery.routeId}/${job.delivery.channelId}`);
170
- console.log(`- cron config: ${context.cronConfigPath}`);
171
- }
172
-
173
- export async function runCronListCommand(options: {
174
- cronConfigPath?: string;
175
- json?: boolean;
176
- }): Promise<void> {
177
- const context = await loadCronContext(
178
- options.cronConfigPath ? { cronConfigPath: options.cronConfigPath } : undefined,
179
- );
180
- const jobs = context.store.listJobs();
181
-
182
- if (options.json) {
183
- console.log(JSON.stringify({ jobs, cronConfigPath: context.cronConfigPath }, null, 2));
184
- return;
185
- }
186
-
187
- if (jobs.length === 0) {
188
- console.log(`No cron jobs configured (${context.cronConfigPath})`);
189
- return;
190
- }
191
-
192
- console.log(`Cron jobs (${context.cronConfigPath}):`);
193
- for (const job of jobs) {
194
- const next = job.state.nextRunAtMs ? new Date(job.state.nextRunAtMs).toISOString() : "-";
195
- const last = job.state.lastRunAtMs ? new Date(job.state.lastRunAtMs).toISOString() : "-";
196
- const schedule = describeSchedule(job.schedule);
197
- console.log(`- ${job.id} [${job.enabled ? "enabled" : "paused"}] ${job.name}`);
198
- console.log(` schedule=${schedule}`);
199
- console.log(` next=${next} last=${last} status=${job.state.lastStatus ?? "-"}`);
200
- console.log(` delivery=${job.delivery.connectorId}/${job.delivery.routeId}/${job.delivery.channelId}`);
201
- }
202
- }
203
-
204
- export async function runCronStatusCommand(options: {
205
- jobId?: string;
206
- cronConfigPath?: string;
207
- json?: boolean;
208
- }): Promise<void> {
209
- const context = await loadCronContext(
210
- options.cronConfigPath ? { cronConfigPath: options.cronConfigPath } : undefined,
211
- );
212
- const target = options.jobId ? context.store.getJob(options.jobId) : null;
213
-
214
- if (options.jobId && !target) {
215
- throw new Error(`Cron job '${options.jobId}' does not exist`);
216
- }
217
-
218
- if (options.json) {
219
- if (target) {
220
- console.log(JSON.stringify(target, null, 2));
221
- return;
222
- }
223
- console.log(JSON.stringify(context.store.listJobs(), null, 2));
224
- return;
225
- }
226
-
227
- if (target) {
228
- console.log(`Cron status for ${target.id}`);
229
- console.log(`- name: ${target.name}`);
230
- console.log(`- enabled: ${target.enabled}`);
231
- console.log(`- schedule: ${describeSchedule(target.schedule)}`);
232
- console.log(`- sessionPolicy: ${target.sessionPolicy ?? "stateless"}`);
233
- console.log(`- nextRun: ${target.state.nextRunAtMs ? new Date(target.state.nextRunAtMs).toISOString() : "-"}`);
234
- console.log(`- lastRun: ${target.state.lastRunAtMs ? new Date(target.state.lastRunAtMs).toISOString() : "-"}`);
235
- console.log(`- lastStatus: ${target.state.lastStatus ?? "-"}`);
236
- if (target.state.lastError) {
237
- console.log(`- lastError: ${target.state.lastError}`);
238
- }
239
- return;
240
- }
241
-
242
- await runCronListCommand({
243
- ...(options.cronConfigPath ? { cronConfigPath: options.cronConfigPath } : {}),
244
- json: false,
245
- });
246
- }
247
-
248
- export async function runCronRunCommand(options: {
249
- jobId: string;
250
- cronConfigPath?: string;
251
- }): Promise<void> {
252
- const context = await loadCronContext(
253
- options.cronConfigPath ? { cronConfigPath: options.cronConfigPath } : undefined,
254
- );
255
- const now = Date.now();
256
- await context.store.updateJob(options.jobId, (current) => ({
257
- ...current,
258
- enabled: true,
259
- updatedAtMs: now,
260
- state: {
261
- ...current.state,
262
- nextRunAtMs: now,
263
- },
264
- }));
265
- console.log(`Scheduled cron job ${options.jobId} to run on next scheduler tick.`);
266
- console.log("Ensure the gateway process is running for execution.");
267
- }
268
-
269
- export async function runCronUpdateCommand(options: {
270
- jobId: string;
271
- name?: string;
272
- prompt?: string;
273
- connectorId?: string;
274
- routeId?: string;
275
- channelId?: string;
276
- threadId?: string;
277
- clearThread?: boolean;
278
- sessionPolicy?: CronSessionPolicy;
279
- at?: string;
280
- everyMs?: number;
281
- cronExpr?: string;
282
- tz?: string;
283
- cronConfigPath?: string;
284
- }): Promise<void> {
285
- const context = await loadCronContext(
286
- options.cronConfigPath ? { cronConfigPath: options.cronConfigPath } : undefined,
287
- );
288
- const now = Date.now();
289
- const hasScheduleUpdate = options.at !== undefined || options.everyMs !== undefined || options.cronExpr !== undefined;
290
- const nextSchedule = hasScheduleUpdate
291
- ? parseSchedule({
292
- ...(options.at ? { at: options.at } : {}),
293
- ...(options.everyMs !== undefined ? { everyMs: options.everyMs } : {}),
294
- ...(options.cronExpr ? { cronExpr: options.cronExpr } : {}),
295
- ...(options.tz ? { tz: options.tz } : {}),
296
- })
297
- : null;
298
-
299
- await context.store.updateJob(options.jobId, (current) => {
300
- const updatedDelivery: ScheduledJob["delivery"] = {
301
- connectorId: options.connectorId ?? current.delivery.connectorId,
302
- routeId: options.routeId ?? current.delivery.routeId,
303
- channelId: options.channelId ?? current.delivery.channelId,
304
- };
305
- const resolvedThreadId = options.clearThread ? undefined : (options.threadId ?? current.delivery.threadId);
306
- if (resolvedThreadId !== undefined) {
307
- updatedDelivery.threadId = resolvedThreadId;
308
- }
309
-
310
- assertDeliveryReferences(context.gatewayConfig, {
311
- connectorId: updatedDelivery.connectorId,
312
- routeId: updatedDelivery.routeId,
313
- });
314
-
315
- const schedule = nextSchedule ?? current.schedule;
316
- const nextRunAtMs = nextSchedule
317
- ? computeInitialNextRunAtMs(schedule, now)
318
- : current.state.nextRunAtMs;
319
- const nextState = {
320
- ...current.state,
321
- };
322
- if (nextSchedule) {
323
- if (nextRunAtMs === undefined) {
324
- delete nextState.nextRunAtMs;
325
- } else {
326
- nextState.nextRunAtMs = nextRunAtMs;
327
- }
328
- }
329
-
330
- const parsedSessionPolicy = parseSessionPolicy(options.sessionPolicy);
331
- const nextJob: ScheduledJob = {
332
- ...current,
333
- name: options.name ?? current.name,
334
- prompt: options.prompt ?? current.prompt,
335
- schedule,
336
- delivery: updatedDelivery,
337
- updatedAtMs: now,
338
- state: nextState,
339
- };
340
- const nextSessionPolicy = parsedSessionPolicy ?? current.sessionPolicy;
341
- if (nextSessionPolicy !== undefined) {
342
- nextJob.sessionPolicy = nextSessionPolicy;
343
- } else {
344
- delete nextJob.sessionPolicy;
345
- }
346
-
347
- return nextJob;
348
- });
349
-
350
- console.log(`Updated cron job ${options.jobId}`);
351
- }
352
-
353
- export async function runCronRemoveCommand(options: {
354
- jobId: string;
355
- cronConfigPath?: string;
356
- }): Promise<void> {
357
- const context = await loadCronContext(
358
- options.cronConfigPath ? { cronConfigPath: options.cronConfigPath } : undefined,
359
- );
360
- const removed = await context.store.removeJob(options.jobId);
361
- if (!removed) {
362
- throw new Error(`Cron job '${options.jobId}' does not exist`);
363
- }
364
- console.log(`Removed cron job ${options.jobId}`);
365
- }
366
-
367
- export async function runCronPauseCommand(options: {
368
- jobId: string;
369
- cronConfigPath?: string;
370
- }): Promise<void> {
371
- const context = await loadCronContext(
372
- options.cronConfigPath ? { cronConfigPath: options.cronConfigPath } : undefined,
373
- );
374
- const now = Date.now();
375
- await context.store.updateJob(options.jobId, (current) => ({
376
- ...current,
377
- enabled: false,
378
- updatedAtMs: now,
379
- }));
380
- console.log(`Paused cron job ${options.jobId}`);
381
- }
382
-
383
- export async function runCronResumeCommand(options: {
384
- jobId: string;
385
- cronConfigPath?: string;
386
- }): Promise<void> {
387
- const context = await loadCronContext(
388
- options.cronConfigPath ? { cronConfigPath: options.cronConfigPath } : undefined,
389
- );
390
- const now = Date.now();
391
- await context.store.updateJob(options.jobId, (current) => {
392
- const nextState = {
393
- ...current.state,
394
- };
395
- const nextRunAtMs = computeInitialNextRunAtMs(current.schedule, now);
396
- if (nextRunAtMs === undefined) {
397
- delete nextState.nextRunAtMs;
398
- } else {
399
- nextState.nextRunAtMs = nextRunAtMs;
400
- }
401
-
402
- return {
403
- ...current,
404
- enabled: true,
405
- updatedAtMs: now,
406
- state: nextState,
407
- };
408
- });
409
- console.log(`Resumed cron job ${options.jobId}`);
410
- }
@@ -1,230 +0,0 @@
1
- import { access, mkdir } from "node:fs/promises";
2
- import { dirname, isAbsolute, join, resolve } from "node:path";
3
- import { homedir } from "node:os";
4
- import { loadGatewayConfig } from "../../core/routing.js";
5
- import { ExtensionStoreManager } from "../../extension/manager.js";
6
- import {
7
- ensureGatewayConfigShape,
8
- setDefaultProviderIfMissingOrInvalid,
9
- } from "../shared/config-mutators.js";
10
- import { DISCORD_CONNECTOR_CONTRIBUTION_ID } from "../shared/discord-config.js";
11
- import { readRawConfig, resolveConfigPath, resolveDataRootDir, writeConfigWithValidation } from "../shared/config-io.js";
12
- import { createLogger } from "../shared/runtime.js";
13
-
14
- interface DoctorIssue {
15
- level: "error" | "warning";
16
- message: string;
17
- }
18
-
19
- function expandHome(value: string): string {
20
- if (value === "~") {
21
- return homedir();
22
- }
23
-
24
- if (value.startsWith("~/") || value.startsWith("~\\")) {
25
- return resolve(homedir(), value.slice(2));
26
- }
27
-
28
- return value;
29
- }
30
-
31
- function resolveRouteProjectRoot(configPath: string, projectRoot: string): string {
32
- const expanded = expandHome(projectRoot);
33
- if (isAbsolute(expanded)) {
34
- return resolve(expanded);
35
- }
36
-
37
- return resolve(dirname(resolve(configPath)), expanded);
38
- }
39
-
40
- export async function runDoctorCommand(options: {
41
- fix?: boolean;
42
- }): Promise<void> {
43
- const configPath = resolveConfigPath();
44
- const issues: DoctorIssue[] = [];
45
-
46
- const rawConfig = await readRawConfig(configPath);
47
- if (!rawConfig) {
48
- throw new Error(`Config '${configPath}' does not exist`);
49
- }
50
-
51
- try {
52
- await loadGatewayConfig(configPath);
53
- } catch (error) {
54
- issues.push({
55
- level: "error",
56
- message: `Config schema/reference validation failed: ${error instanceof Error ? error.message : String(error)}`,
57
- });
58
- }
59
-
60
- const normalized = ensureGatewayConfigShape(structuredClone(rawConfig));
61
- const logger = createLogger();
62
- const manager = new ExtensionStoreManager(logger, join(resolveDataRootDir(configPath, normalized), "extensions"));
63
- const installedExtensions = await manager.listInstalled();
64
-
65
- const installedPackages = new Set(installedExtensions.map((item) => item.packageName));
66
- const enabledPackages = new Set(
67
- normalized.extensions.allowList.filter((item) => item.enabled).map((item) => item.package),
68
- );
69
-
70
- for (const packageName of enabledPackages) {
71
- if (!installedPackages.has(packageName)) {
72
- issues.push({
73
- level: "error",
74
- message: `extensions.allowList enables '${packageName}' but it is not installed`,
75
- });
76
- }
77
- }
78
-
79
- const availableContributionIds = new Set<string>();
80
- for (const item of installedExtensions) {
81
- if (item.error) {
82
- issues.push({
83
- level: "warning",
84
- message: `Installed extension '${item.packageName}' has invalid manifest: ${item.error}`,
85
- });
86
- continue;
87
- }
88
-
89
- if (!enabledPackages.has(item.packageName)) {
90
- continue;
91
- }
92
-
93
- for (const contribution of item.manifest?.contributions ?? []) {
94
- availableContributionIds.add(contribution.id);
95
- }
96
- }
97
-
98
- for (const [instanceId, instance] of Object.entries(normalized.providers.items)) {
99
- if (!availableContributionIds.has(instance.type)) {
100
- issues.push({
101
- level: "error",
102
- message: `providers.items['${instanceId}'] references missing contribution '${instance.type}'`,
103
- });
104
- }
105
- }
106
-
107
- for (const [instanceId, instance] of Object.entries(normalized.connectors.items)) {
108
- if (!availableContributionIds.has(instance.type)) {
109
- issues.push({
110
- level: "error",
111
- message: `connectors.items['${instanceId}'] references missing contribution '${instance.type}'`,
112
- });
113
- }
114
-
115
- if (instance.type === DISCORD_CONNECTOR_CONTRIBUTION_ID) {
116
- const botName = typeof instance.botName === "string" ? instance.botName.trim() : "";
117
- const botToken = typeof instance.botToken === "string" ? instance.botToken.trim() : "";
118
- if (botName.length === 0) {
119
- issues.push({
120
- level: "error",
121
- message: `connectors.items['${instanceId}'].botName is required`,
122
- });
123
- }
124
- if (botToken.length === 0) {
125
- issues.push({
126
- level: "error",
127
- message: `connectors.items['${instanceId}'].botToken is required`,
128
- });
129
- }
130
- }
131
- }
132
-
133
- for (const [instanceId, instance] of Object.entries(normalized.sandboxes.items)) {
134
- if (!availableContributionIds.has(instance.type)) {
135
- issues.push({
136
- level: "error",
137
- message: `sandboxes.items['${instanceId}'] references missing contribution '${instance.type}'`,
138
- });
139
- }
140
- }
141
-
142
- for (const [routeId, route] of Object.entries(normalized.routes.items)) {
143
- try {
144
- const projectRootPath = resolveRouteProjectRoot(configPath, route.projectRoot);
145
- await access(projectRootPath);
146
- } catch {
147
- issues.push({
148
- level: "warning",
149
- message: `routes.items['${routeId}'].projectRoot does not exist: ${route.projectRoot}`,
150
- });
151
- }
152
- }
153
-
154
- const seenBindingSources = new Map<string, string>();
155
- for (const [bindingId, binding] of Object.entries(normalized.bindings.items)) {
156
- if (!normalized.connectors.items[binding.connector]) {
157
- issues.push({
158
- level: "error",
159
- message: `bindings.items['${bindingId}'].connector references unknown connector '${binding.connector}'`,
160
- });
161
- }
162
- if (!normalized.routes.items[binding.route]) {
163
- issues.push({
164
- level: "error",
165
- message: `bindings.items['${bindingId}'].route references unknown route '${binding.route}'`,
166
- });
167
- }
168
-
169
- const bindingKey = `${binding.connector}:${binding.source.type}:${binding.source.id}`;
170
- const existingBindingId = seenBindingSources.get(bindingKey);
171
- if (existingBindingId) {
172
- issues.push({
173
- level: "error",
174
- message:
175
- `bindings.items['${bindingId}'] duplicates source '${bindingKey}' already used by bindings.items['${existingBindingId}']`,
176
- });
177
- } else {
178
- seenBindingSources.set(bindingKey, bindingId);
179
- }
180
- }
181
-
182
- if (options.fix) {
183
- const fixTarget = ensureGatewayConfigShape(structuredClone(rawConfig));
184
- const rootDir = resolveDataRootDir(configPath, fixTarget);
185
-
186
- await mkdir(rootDir, { recursive: true });
187
- await mkdir(join(rootDir, "sessions"), { recursive: true });
188
- await mkdir(join(rootDir, "attachments"), { recursive: true });
189
- await mkdir(join(rootDir, "logs"), { recursive: true });
190
- await mkdir(join(rootDir, "state"), { recursive: true });
191
- await mkdir(join(rootDir, "extensions"), { recursive: true });
192
-
193
- const installedSet = new Set(installedExtensions.map((item) => item.packageName));
194
- for (const item of fixTarget.extensions.allowList) {
195
- if (item.enabled && !installedSet.has(item.package)) {
196
- item.enabled = false;
197
- }
198
- }
199
-
200
- setDefaultProviderIfMissingOrInvalid(fixTarget);
201
-
202
- await writeConfigWithValidation(configPath, fixTarget, {
203
- validate: true,
204
- createBackup: true,
205
- });
206
-
207
- console.log("Applied doctor --fix actions:");
208
- console.log("- ensured data directories exist");
209
- console.log("- disabled allowList entries for packages not installed");
210
- console.log("- repaired default provider when missing or invalid");
211
- }
212
-
213
- const errorCount = issues.filter((issue) => issue.level === "error").length;
214
- const warningCount = issues.filter((issue) => issue.level === "warning").length;
215
-
216
- if (issues.length === 0) {
217
- console.log(`Doctor found no issues (${configPath})`);
218
- return;
219
- }
220
-
221
- console.log(`Doctor results (${configPath}):`);
222
- for (const issue of issues) {
223
- console.log(`- [${issue.level}] ${issue.message}`);
224
- }
225
- console.log(`Summary: ${errorCount} error(s), ${warningCount} warning(s)`);
226
-
227
- if (errorCount > 0) {
228
- throw new Error(`Doctor found ${errorCount} error(s)`);
229
- }
230
- }