@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.
- package/README.md +84 -39
- package/dist/src/agent/event-forwarder.js +185 -16
- package/dist/src/cli/commands/cron.js +39 -35
- package/dist/src/cli/commands/doctor.js +81 -2
- package/dist/src/cli/commands/extension.js +3 -1
- package/dist/src/cli/commands/init.js +43 -173
- package/dist/src/cli/commands/topology.js +38 -14
- package/dist/src/cli/program.js +15 -137
- package/dist/src/cli/shared/config-io.js +3 -31
- package/dist/src/cli/shared/config-mutators.js +33 -9
- package/dist/src/cli/shared/configure-sections.js +52 -12
- package/dist/src/cli/shared/init-catalog.js +89 -46
- package/dist/src/cli/shared/local-extension-specs.js +85 -0
- package/dist/src/cli/shared/schema-prompts.js +26 -2
- package/dist/src/core/gateway.js +3 -1
- package/dist/src/core/routing.js +53 -38
- package/dist/src/core/types.js +2 -0
- package/dist/src/cron/config.js +2 -2
- package/dist/src/cron/service.js +87 -23
- package/dist/src/cron/store.js +1 -1
- package/dist/src/main.js +0 -0
- package/dist/src/shared/dobby-repo.js +40 -0
- package/package.json +11 -4
- package/.env.example +0 -9
- package/AGENTS.md +0 -267
- package/ROADMAP.md +0 -34
- package/config/cron.example.json +0 -9
- package/config/gateway.example.json +0 -128
- package/config/models.custom.example.json +0 -27
- package/dist/src/agent/tests/event-forwarder.test.js +0 -113
- package/dist/src/cli/shared/config-path.js +0 -207
- package/dist/src/cli/shared/init-models-file.js +0 -65
- package/dist/src/cli/shared/presets.js +0 -86
- package/dist/src/cli/tests/config-command.test.js +0 -42
- package/dist/src/cli/tests/config-io.test.js +0 -64
- package/dist/src/cli/tests/config-mutators.test.js +0 -47
- package/dist/src/cli/tests/config-path.test.js +0 -21
- package/dist/src/cli/tests/discord-config.test.js +0 -23
- package/dist/src/cli/tests/doctor.test.js +0 -107
- package/dist/src/cli/tests/init-catalog.test.js +0 -87
- package/dist/src/cli/tests/presets.test.js +0 -41
- package/dist/src/cli/tests/program-options.test.js +0 -92
- package/dist/src/cli/tests/routing-config.test.js +0 -199
- package/dist/src/cli/tests/routing-legacy.test.js +0 -191
- package/dist/src/core/tests/control-command.test.js +0 -17
- package/dist/src/core/tests/gateway-update-strategy.test.js +0 -167
- package/dist/src/core/tests/runtime-registry.test.js +0 -116
- package/dist/src/core/tests/typing-controller.test.js +0 -103
- package/docs/BOXLITE_SANDBOX_FEASIBILITY.md +0 -175
- package/docs/CRON_SCHEDULER_DESIGN.md +0 -374
- package/docs/DOCKER_SANDBOX_vs_BOXLITE.md +0 -77
- package/docs/EXTENSION_SYSTEM_ARCHITECTURE.md +0 -119
- package/docs/MVP.md +0 -135
- package/docs/RUNBOOK.md +0 -242
- package/docs/TEAMWORK_HANDOFF_DESIGN.md +0 -440
- package/plugins/connector-discord/dobby.manifest.json +0 -18
- package/plugins/connector-discord/index.js +0 -1
- package/plugins/connector-discord/package-lock.json +0 -360
- package/plugins/connector-discord/package.json +0 -38
- package/plugins/connector-discord/src/connector.ts +0 -350
- package/plugins/connector-discord/src/contribution.ts +0 -21
- package/plugins/connector-discord/src/mapper.ts +0 -102
- package/plugins/connector-discord/tsconfig.json +0 -19
- package/plugins/connector-feishu/dobby.manifest.json +0 -18
- package/plugins/connector-feishu/index.js +0 -1
- package/plugins/connector-feishu/package-lock.json +0 -618
- package/plugins/connector-feishu/package.json +0 -38
- package/plugins/connector-feishu/src/connector.ts +0 -343
- package/plugins/connector-feishu/src/contribution.ts +0 -26
- package/plugins/connector-feishu/src/mapper.ts +0 -401
- package/plugins/connector-feishu/tsconfig.json +0 -19
- package/plugins/plugin-sdk/index.d.ts +0 -261
- package/plugins/plugin-sdk/index.js +0 -1
- package/plugins/plugin-sdk/package-lock.json +0 -12
- package/plugins/plugin-sdk/package.json +0 -22
- package/plugins/provider-claude/dobby.manifest.json +0 -17
- package/plugins/provider-claude/index.js +0 -1
- package/plugins/provider-claude/package-lock.json +0 -3398
- package/plugins/provider-claude/package.json +0 -39
- package/plugins/provider-claude/src/contribution.ts +0 -1018
- package/plugins/provider-claude/tsconfig.json +0 -19
- package/plugins/provider-claude-cli/dobby.manifest.json +0 -17
- package/plugins/provider-claude-cli/index.js +0 -1
- package/plugins/provider-claude-cli/package-lock.json +0 -2898
- package/plugins/provider-claude-cli/package.json +0 -38
- package/plugins/provider-claude-cli/src/contribution.ts +0 -1673
- package/plugins/provider-claude-cli/tsconfig.json +0 -19
- package/plugins/provider-pi/dobby.manifest.json +0 -17
- package/plugins/provider-pi/index.js +0 -1
- package/plugins/provider-pi/package-lock.json +0 -3877
- package/plugins/provider-pi/package.json +0 -40
- package/plugins/provider-pi/src/contribution.ts +0 -476
- package/plugins/provider-pi/tsconfig.json +0 -19
- package/plugins/sandbox-core/boxlite.js +0 -1
- package/plugins/sandbox-core/dobby.manifest.json +0 -17
- package/plugins/sandbox-core/docker.js +0 -1
- package/plugins/sandbox-core/package-lock.json +0 -136
- package/plugins/sandbox-core/package.json +0 -39
- package/plugins/sandbox-core/src/boxlite-context.ts +0 -2
- package/plugins/sandbox-core/src/boxlite-contribution.ts +0 -53
- package/plugins/sandbox-core/src/boxlite-executor.ts +0 -911
- package/plugins/sandbox-core/src/docker-contribution.ts +0 -43
- package/plugins/sandbox-core/src/docker-executor.ts +0 -217
- package/plugins/sandbox-core/tsconfig.json +0 -19
- package/scripts/local-extensions.mjs +0 -168
- package/src/agent/event-forwarder.ts +0 -414
- package/src/cli/commands/config.ts +0 -328
- package/src/cli/commands/configure.ts +0 -92
- package/src/cli/commands/cron.ts +0 -410
- package/src/cli/commands/doctor.ts +0 -230
- package/src/cli/commands/extension.ts +0 -205
- package/src/cli/commands/init.ts +0 -396
- package/src/cli/commands/start.ts +0 -223
- package/src/cli/commands/topology.ts +0 -383
- package/src/cli/index.ts +0 -9
- package/src/cli/program.ts +0 -465
- package/src/cli/shared/config-io.ts +0 -277
- package/src/cli/shared/config-mutators.ts +0 -440
- package/src/cli/shared/config-schema.ts +0 -228
- package/src/cli/shared/config-types.ts +0 -121
- package/src/cli/shared/configure-sections.ts +0 -551
- package/src/cli/shared/discord-config.ts +0 -14
- package/src/cli/shared/init-catalog.ts +0 -189
- package/src/cli/shared/init-models-file.ts +0 -77
- package/src/cli/shared/runtime.ts +0 -33
- package/src/cli/shared/schema-prompts.ts +0 -414
- package/src/cli/tests/config-command.test.ts +0 -56
- package/src/cli/tests/config-io.test.ts +0 -92
- package/src/cli/tests/config-mutators.test.ts +0 -59
- package/src/cli/tests/doctor.test.ts +0 -120
- package/src/cli/tests/init-catalog.test.ts +0 -96
- package/src/cli/tests/program-options.test.ts +0 -113
- package/src/cli/tests/routing-config.test.ts +0 -209
- package/src/core/control-command.ts +0 -12
- package/src/core/dedup-store.ts +0 -103
- package/src/core/gateway.ts +0 -607
- package/src/core/routing.ts +0 -379
- package/src/core/runtime-registry.ts +0 -141
- package/src/core/tests/control-command.test.ts +0 -20
- package/src/core/tests/runtime-registry.test.ts +0 -140
- package/src/core/tests/typing-controller.test.ts +0 -129
- package/src/core/types.ts +0 -318
- package/src/core/typing-controller.ts +0 -119
- package/src/cron/config.ts +0 -154
- package/src/cron/schedule.ts +0 -61
- package/src/cron/service.ts +0 -249
- package/src/cron/store.ts +0 -155
- package/src/cron/types.ts +0 -60
- package/src/extension/loader.ts +0 -145
- package/src/extension/manager.ts +0 -355
- package/src/extension/manifest.ts +0 -26
- package/src/extension/registry.ts +0 -229
- package/src/main.ts +0 -8
- package/src/sandbox/executor.ts +0 -44
- package/src/sandbox/host-executor.ts +0 -118
- package/tsconfig.json +0 -18
|
@@ -1,223 +0,0 @@
|
|
|
1
|
-
import { dirname, join } from "node:path";
|
|
2
|
-
import { loadCronConfig } from "../../cron/config.js";
|
|
3
|
-
import { CronService } from "../../cron/service.js";
|
|
4
|
-
import { CronStore } from "../../cron/store.js";
|
|
5
|
-
import { DedupStore } from "../../core/dedup-store.js";
|
|
6
|
-
import { Gateway } from "../../core/gateway.js";
|
|
7
|
-
import { BindingResolver, loadGatewayConfig, RouteResolver } from "../../core/routing.js";
|
|
8
|
-
import { RuntimeRegistry } from "../../core/runtime-registry.js";
|
|
9
|
-
import { BUILTIN_HOST_SANDBOX_ID } from "../../core/types.js";
|
|
10
|
-
import type {
|
|
11
|
-
GatewayLogger,
|
|
12
|
-
ProviderInstance,
|
|
13
|
-
ProvidersConfig,
|
|
14
|
-
SandboxInstance,
|
|
15
|
-
SandboxesConfig,
|
|
16
|
-
} from "../../core/types.js";
|
|
17
|
-
import { ExtensionLoader } from "../../extension/loader.js";
|
|
18
|
-
import { ExtensionRegistry } from "../../extension/registry.js";
|
|
19
|
-
import type { Executor } from "../../sandbox/executor.js";
|
|
20
|
-
import { HostExecutor } from "../../sandbox/host-executor.js";
|
|
21
|
-
import { resolveConfigPath } from "../shared/config-io.js";
|
|
22
|
-
import { createLogger, ensureDataDirs, extensionStoreDir } from "../shared/runtime.js";
|
|
23
|
-
|
|
24
|
-
/**
|
|
25
|
-
* Closes provider instances best-effort so shutdown does not stop on one failed provider.
|
|
26
|
-
*/
|
|
27
|
-
async function closeProviderInstances(providers: Map<string, ProviderInstance>, logger: GatewayLogger): Promise<void> {
|
|
28
|
-
for (const [providerId, provider] of providers.entries()) {
|
|
29
|
-
if (!provider.close) {
|
|
30
|
-
continue;
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
try {
|
|
34
|
-
await provider.close();
|
|
35
|
-
} catch (error) {
|
|
36
|
-
logger.warn({ err: error, providerId }, "Failed to close provider instance");
|
|
37
|
-
}
|
|
38
|
-
}
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
/**
|
|
42
|
-
* Closes sandbox executors and optional sandbox lifecycle hooks during shutdown.
|
|
43
|
-
*/
|
|
44
|
-
async function closeSandboxInstances(sandboxes: Map<string, SandboxInstance>, logger: GatewayLogger): Promise<void> {
|
|
45
|
-
for (const [sandboxId, sandbox] of sandboxes.entries()) {
|
|
46
|
-
try {
|
|
47
|
-
await sandbox.executor.close();
|
|
48
|
-
} catch (error) {
|
|
49
|
-
logger.warn({ err: error, sandboxId }, "Failed to close sandbox executor");
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
if (!sandbox.close) {
|
|
53
|
-
continue;
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
try {
|
|
57
|
-
await sandbox.close();
|
|
58
|
-
} catch (error) {
|
|
59
|
-
logger.warn({ err: error, sandboxId }, "Failed to close sandbox instance");
|
|
60
|
-
}
|
|
61
|
-
}
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
/**
|
|
65
|
-
* Narrows provider instances to only those referenced by default provider or any route override.
|
|
66
|
-
*/
|
|
67
|
-
function selectProviderInstances(config: Awaited<ReturnType<typeof loadGatewayConfig>>): ProvidersConfig {
|
|
68
|
-
const requiredProviderIds = new Set<string>([config.providers.default]);
|
|
69
|
-
for (const route of Object.values(config.routes.items)) {
|
|
70
|
-
requiredProviderIds.add(route.provider);
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
const instances = Object.fromEntries(
|
|
74
|
-
Object.entries(config.providers.items).filter(([instanceId]) => requiredProviderIds.has(instanceId)),
|
|
75
|
-
);
|
|
76
|
-
|
|
77
|
-
return {
|
|
78
|
-
default: config.providers.default,
|
|
79
|
-
items: instances,
|
|
80
|
-
};
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
/**
|
|
84
|
-
* Narrows sandbox instances to only those referenced by default/route sandbox settings.
|
|
85
|
-
*/
|
|
86
|
-
function selectSandboxInstances(config: Awaited<ReturnType<typeof loadGatewayConfig>>): SandboxesConfig {
|
|
87
|
-
const defaultSandboxId = config.sandboxes.default ?? BUILTIN_HOST_SANDBOX_ID;
|
|
88
|
-
const requiredSandboxIds = new Set<string>();
|
|
89
|
-
|
|
90
|
-
if (defaultSandboxId !== BUILTIN_HOST_SANDBOX_ID) {
|
|
91
|
-
requiredSandboxIds.add(defaultSandboxId);
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
for (const route of Object.values(config.routes.items)) {
|
|
95
|
-
if (route.sandbox !== BUILTIN_HOST_SANDBOX_ID) {
|
|
96
|
-
requiredSandboxIds.add(route.sandbox);
|
|
97
|
-
}
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
const instances = Object.fromEntries(
|
|
101
|
-
Object.entries(config.sandboxes.items).filter(([instanceId]) => requiredSandboxIds.has(instanceId)),
|
|
102
|
-
);
|
|
103
|
-
|
|
104
|
-
return {
|
|
105
|
-
...(config.sandboxes.default ? { default: config.sandboxes.default } : {}),
|
|
106
|
-
items: instances,
|
|
107
|
-
};
|
|
108
|
-
}
|
|
109
|
-
|
|
110
|
-
/**
|
|
111
|
-
* Starts the gateway runtime from config and wires graceful shutdown handlers.
|
|
112
|
-
*/
|
|
113
|
-
export async function runStartCommand(): Promise<void> {
|
|
114
|
-
const configPath = resolveConfigPath();
|
|
115
|
-
const config = await loadGatewayConfig(configPath);
|
|
116
|
-
|
|
117
|
-
await ensureDataDirs(config.data.rootDir);
|
|
118
|
-
|
|
119
|
-
const logger = createLogger();
|
|
120
|
-
const loader = new ExtensionLoader(logger, {
|
|
121
|
-
extensionsDir: extensionStoreDir(config),
|
|
122
|
-
});
|
|
123
|
-
|
|
124
|
-
const loadedPackages = await loader.loadAllowList(config.extensions.allowList);
|
|
125
|
-
const registry = new ExtensionRegistry();
|
|
126
|
-
registry.registerPackages(loadedPackages);
|
|
127
|
-
|
|
128
|
-
logger.info(
|
|
129
|
-
{
|
|
130
|
-
extensionStoreDir: extensionStoreDir(config),
|
|
131
|
-
packages: loadedPackages.map((item) => ({
|
|
132
|
-
package: item.packageName,
|
|
133
|
-
manifestName: item.manifest.name,
|
|
134
|
-
version: item.manifest.version,
|
|
135
|
-
contributions: item.manifest.contributions.map((contribution) => `${contribution.kind}:${contribution.id}`),
|
|
136
|
-
})),
|
|
137
|
-
},
|
|
138
|
-
"Extension packages loaded",
|
|
139
|
-
);
|
|
140
|
-
|
|
141
|
-
const extensionHostContext = {
|
|
142
|
-
logger,
|
|
143
|
-
configBaseDir: dirname(configPath),
|
|
144
|
-
};
|
|
145
|
-
|
|
146
|
-
const activeProvidersConfig = selectProviderInstances(config);
|
|
147
|
-
const activeSandboxesConfig = selectSandboxInstances(config);
|
|
148
|
-
|
|
149
|
-
const providers = await registry.createProviderInstances(activeProvidersConfig, extensionHostContext, config.data);
|
|
150
|
-
const connectors = await registry.createConnectorInstances(config.connectors, extensionHostContext, config.data.attachmentsDir);
|
|
151
|
-
const sandboxes = await registry.createSandboxInstances(activeSandboxesConfig, extensionHostContext);
|
|
152
|
-
|
|
153
|
-
const hostExecutor = new HostExecutor(logger);
|
|
154
|
-
const executors = new Map<string, Executor>();
|
|
155
|
-
executors.set(BUILTIN_HOST_SANDBOX_ID, hostExecutor);
|
|
156
|
-
|
|
157
|
-
for (const [sandboxId, sandbox] of sandboxes.entries()) {
|
|
158
|
-
executors.set(sandboxId, sandbox.executor);
|
|
159
|
-
}
|
|
160
|
-
|
|
161
|
-
if (connectors.length === 0) {
|
|
162
|
-
throw new Error("No connectors are configured. Add connector instances in your dobby config.");
|
|
163
|
-
}
|
|
164
|
-
|
|
165
|
-
const dedupStore = new DedupStore(join(config.data.stateDir, "dedup.json"), config.data.dedupTtlMs, logger);
|
|
166
|
-
const routeResolver = new RouteResolver(config.routes);
|
|
167
|
-
const bindingResolver = new BindingResolver(config.bindings);
|
|
168
|
-
const runtimeRegistry = new RuntimeRegistry(logger);
|
|
169
|
-
|
|
170
|
-
const gateway = new Gateway({
|
|
171
|
-
config,
|
|
172
|
-
connectors,
|
|
173
|
-
providers,
|
|
174
|
-
executors,
|
|
175
|
-
routeResolver,
|
|
176
|
-
bindingResolver,
|
|
177
|
-
dedupStore,
|
|
178
|
-
runtimeRegistry,
|
|
179
|
-
logger,
|
|
180
|
-
});
|
|
181
|
-
|
|
182
|
-
const loadedCronConfig = await loadCronConfig({
|
|
183
|
-
gatewayConfigPath: configPath,
|
|
184
|
-
gatewayConfig: config,
|
|
185
|
-
});
|
|
186
|
-
const cronStore = new CronStore(loadedCronConfig.config.storeFile, loadedCronConfig.config.runLogFile, logger);
|
|
187
|
-
const cronService = new CronService({
|
|
188
|
-
config: loadedCronConfig.config,
|
|
189
|
-
store: cronStore,
|
|
190
|
-
gateway,
|
|
191
|
-
logger,
|
|
192
|
-
});
|
|
193
|
-
|
|
194
|
-
await gateway.start();
|
|
195
|
-
await cronService.start();
|
|
196
|
-
logger.info(
|
|
197
|
-
{
|
|
198
|
-
configPath,
|
|
199
|
-
cronConfigPath: loadedCronConfig.configPath,
|
|
200
|
-
cronConfigSource: loadedCronConfig.source,
|
|
201
|
-
cronEnabled: loadedCronConfig.config.enabled,
|
|
202
|
-
},
|
|
203
|
-
"Gateway started",
|
|
204
|
-
);
|
|
205
|
-
|
|
206
|
-
const shutdown = async (signal: string) => {
|
|
207
|
-
logger.info({ signal }, "Shutting down gateway");
|
|
208
|
-
await cronService.stop();
|
|
209
|
-
await gateway.stop();
|
|
210
|
-
await hostExecutor.close();
|
|
211
|
-
await closeProviderInstances(providers, logger);
|
|
212
|
-
await closeSandboxInstances(sandboxes, logger);
|
|
213
|
-
process.exit(0);
|
|
214
|
-
};
|
|
215
|
-
|
|
216
|
-
process.on("SIGINT", () => {
|
|
217
|
-
void shutdown("SIGINT");
|
|
218
|
-
});
|
|
219
|
-
|
|
220
|
-
process.on("SIGTERM", () => {
|
|
221
|
-
void shutdown("SIGTERM");
|
|
222
|
-
});
|
|
223
|
-
}
|
|
@@ -1,383 +0,0 @@
|
|
|
1
|
-
import { BUILTIN_HOST_SANDBOX_ID } from "../../core/types.js";
|
|
2
|
-
import {
|
|
3
|
-
ensureGatewayConfigShape,
|
|
4
|
-
upsertBinding,
|
|
5
|
-
upsertRoute,
|
|
6
|
-
} from "../shared/config-mutators.js";
|
|
7
|
-
import { DISCORD_CONNECTOR_CONTRIBUTION_ID } from "../shared/discord-config.js";
|
|
8
|
-
import { requireRawConfig, resolveConfigPath, writeConfigWithValidation } from "../shared/config-io.js";
|
|
9
|
-
import type { RawBindingConfig, RawGatewayConfig } from "../shared/config-types.js";
|
|
10
|
-
|
|
11
|
-
interface DiscordConnectorView {
|
|
12
|
-
connectorId: string;
|
|
13
|
-
type: string;
|
|
14
|
-
botName: string;
|
|
15
|
-
hasToken: boolean;
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
interface BindingView {
|
|
19
|
-
bindingId: string;
|
|
20
|
-
connectorId: string;
|
|
21
|
-
sourceType: "channel" | "chat";
|
|
22
|
-
sourceId: string;
|
|
23
|
-
routeId: string;
|
|
24
|
-
routeExists: boolean;
|
|
25
|
-
projectRoot?: string;
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
interface RouteView {
|
|
29
|
-
routeId: string;
|
|
30
|
-
projectRoot: string;
|
|
31
|
-
tools: "full" | "readonly";
|
|
32
|
-
mentions: "required" | "optional";
|
|
33
|
-
provider?: string;
|
|
34
|
-
sandbox?: string;
|
|
35
|
-
bindings: number;
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
function listDiscordConnectors(rawConfig: unknown): DiscordConnectorView[] {
|
|
39
|
-
const normalized = ensureGatewayConfigShape(rawConfig as RawGatewayConfig);
|
|
40
|
-
const items: DiscordConnectorView[] = [];
|
|
41
|
-
|
|
42
|
-
for (const [connectorId, connector] of Object.entries(normalized.connectors.items)) {
|
|
43
|
-
if (connector.type !== DISCORD_CONNECTOR_CONTRIBUTION_ID) {
|
|
44
|
-
continue;
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
const botName = typeof connector.botName === "string" ? connector.botName : "";
|
|
48
|
-
const botToken = typeof connector.botToken === "string" ? connector.botToken.trim() : "";
|
|
49
|
-
items.push({
|
|
50
|
-
connectorId,
|
|
51
|
-
type: connector.type,
|
|
52
|
-
botName,
|
|
53
|
-
hasToken: botToken.length > 0,
|
|
54
|
-
});
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
return items.sort((a, b) => a.connectorId.localeCompare(b.connectorId));
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
function getDiscordConnectorOrThrow(
|
|
61
|
-
rawConfig: unknown,
|
|
62
|
-
connectorId: string,
|
|
63
|
-
): {
|
|
64
|
-
normalized: ReturnType<typeof ensureGatewayConfigShape>;
|
|
65
|
-
connector: Record<string, unknown> & { type: string };
|
|
66
|
-
} {
|
|
67
|
-
const normalized = ensureGatewayConfigShape(rawConfig as RawGatewayConfig);
|
|
68
|
-
const connector = normalized.connectors.items[connectorId];
|
|
69
|
-
if (!connector) {
|
|
70
|
-
throw new Error(`Connector instance '${connectorId}' not found`);
|
|
71
|
-
}
|
|
72
|
-
if (connector.type !== DISCORD_CONNECTOR_CONTRIBUTION_ID) {
|
|
73
|
-
throw new Error(
|
|
74
|
-
`Connector '${connectorId}' uses contribution '${connector.type}'. This command currently supports only '${DISCORD_CONNECTOR_CONTRIBUTION_ID}'.`,
|
|
75
|
-
);
|
|
76
|
-
}
|
|
77
|
-
return {
|
|
78
|
-
normalized,
|
|
79
|
-
connector,
|
|
80
|
-
};
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
function listBindings(rawConfig: unknown, connectorFilter?: string): BindingView[] {
|
|
84
|
-
const normalized = ensureGatewayConfigShape(rawConfig as RawGatewayConfig);
|
|
85
|
-
const routes = normalized.routes.items;
|
|
86
|
-
|
|
87
|
-
return Object.entries(normalized.bindings.items)
|
|
88
|
-
.filter(([, binding]) => !connectorFilter || binding.connector === connectorFilter)
|
|
89
|
-
.map(([bindingId, binding]) => {
|
|
90
|
-
const route = routes[binding.route];
|
|
91
|
-
return {
|
|
92
|
-
bindingId,
|
|
93
|
-
connectorId: binding.connector,
|
|
94
|
-
sourceType: binding.source.type,
|
|
95
|
-
sourceId: binding.source.id,
|
|
96
|
-
routeId: binding.route,
|
|
97
|
-
routeExists: Boolean(route),
|
|
98
|
-
...(route ? { projectRoot: route.projectRoot } : {}),
|
|
99
|
-
};
|
|
100
|
-
})
|
|
101
|
-
.sort((a, b) => a.bindingId.localeCompare(b.bindingId));
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
function buildRouteBindingCounts(rawConfig: unknown): Map<string, number> {
|
|
105
|
-
const counts = new Map<string, number>();
|
|
106
|
-
for (const binding of listBindings(rawConfig)) {
|
|
107
|
-
counts.set(binding.routeId, (counts.get(binding.routeId) ?? 0) + 1);
|
|
108
|
-
}
|
|
109
|
-
return counts;
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
function listRoutes(rawConfig: unknown): RouteView[] {
|
|
113
|
-
const normalized = ensureGatewayConfigShape(rawConfig as RawGatewayConfig);
|
|
114
|
-
const counts = buildRouteBindingCounts(normalized);
|
|
115
|
-
|
|
116
|
-
return Object.entries(normalized.routes.items)
|
|
117
|
-
.map(([routeId, route]): RouteView => ({
|
|
118
|
-
routeId,
|
|
119
|
-
projectRoot: route.projectRoot,
|
|
120
|
-
tools: route.tools === "readonly" ? "readonly" : "full",
|
|
121
|
-
mentions: route.mentions === "optional" ? "optional" : "required",
|
|
122
|
-
...(route.provider ? { provider: route.provider } : {}),
|
|
123
|
-
...(route.sandbox ? { sandbox: route.sandbox } : {}),
|
|
124
|
-
bindings: counts.get(routeId) ?? 0,
|
|
125
|
-
}))
|
|
126
|
-
.sort((a, b) => a.routeId.localeCompare(b.routeId));
|
|
127
|
-
}
|
|
128
|
-
|
|
129
|
-
async function saveConfig(configPath: string, normalized: ReturnType<typeof ensureGatewayConfigShape>): Promise<void> {
|
|
130
|
-
await writeConfigWithValidation(configPath, normalized, {
|
|
131
|
-
validate: true,
|
|
132
|
-
createBackup: true,
|
|
133
|
-
});
|
|
134
|
-
}
|
|
135
|
-
|
|
136
|
-
export async function runBotListCommand(options: {
|
|
137
|
-
json?: boolean;
|
|
138
|
-
}): Promise<void> {
|
|
139
|
-
const configPath = resolveConfigPath();
|
|
140
|
-
const rawConfig = await requireRawConfig(configPath);
|
|
141
|
-
const bots = listDiscordConnectors(rawConfig);
|
|
142
|
-
|
|
143
|
-
if (options.json) {
|
|
144
|
-
console.log(JSON.stringify({ configPath, bots }, null, 2));
|
|
145
|
-
return;
|
|
146
|
-
}
|
|
147
|
-
|
|
148
|
-
if (bots.length === 0) {
|
|
149
|
-
console.log("No Discord connector instances configured.");
|
|
150
|
-
return;
|
|
151
|
-
}
|
|
152
|
-
|
|
153
|
-
console.log(`Bots (${configPath}):`);
|
|
154
|
-
for (const bot of bots) {
|
|
155
|
-
console.log(
|
|
156
|
-
`- ${bot.connectorId}: botName='${bot.botName || "(empty)"}', token=${bot.hasToken ? "set" : "missing"}`,
|
|
157
|
-
);
|
|
158
|
-
}
|
|
159
|
-
}
|
|
160
|
-
|
|
161
|
-
export async function runBotSetCommand(options: {
|
|
162
|
-
connectorId: string;
|
|
163
|
-
name?: string;
|
|
164
|
-
token?: string;
|
|
165
|
-
}): Promise<void> {
|
|
166
|
-
const configPath = resolveConfigPath();
|
|
167
|
-
const rawConfig = await requireRawConfig(configPath);
|
|
168
|
-
const { normalized, connector } = getDiscordConnectorOrThrow(rawConfig, options.connectorId);
|
|
169
|
-
|
|
170
|
-
const nextName = typeof options.name === "string" ? options.name.trim() : undefined;
|
|
171
|
-
const nextToken = typeof options.token === "string" ? options.token.trim() : undefined;
|
|
172
|
-
if (!nextName && !nextToken) {
|
|
173
|
-
throw new Error("At least one of --name or --token must be provided");
|
|
174
|
-
}
|
|
175
|
-
|
|
176
|
-
normalized.connectors.items[options.connectorId] = {
|
|
177
|
-
...connector,
|
|
178
|
-
...(nextName !== undefined ? { botName: nextName } : {}),
|
|
179
|
-
...(nextToken !== undefined ? { botToken: nextToken } : {}),
|
|
180
|
-
};
|
|
181
|
-
|
|
182
|
-
await saveConfig(configPath, normalized);
|
|
183
|
-
console.log(`Updated bot settings for connector '${options.connectorId}'`);
|
|
184
|
-
}
|
|
185
|
-
|
|
186
|
-
export async function runBindingListCommand(options: {
|
|
187
|
-
connectorId?: string;
|
|
188
|
-
json?: boolean;
|
|
189
|
-
}): Promise<void> {
|
|
190
|
-
const configPath = resolveConfigPath();
|
|
191
|
-
const rawConfig = await requireRawConfig(configPath);
|
|
192
|
-
const bindings = listBindings(rawConfig, options.connectorId);
|
|
193
|
-
|
|
194
|
-
if (options.connectorId) {
|
|
195
|
-
const normalized = ensureGatewayConfigShape(rawConfig);
|
|
196
|
-
if (!normalized.connectors.items[options.connectorId]) {
|
|
197
|
-
throw new Error(`Connector '${options.connectorId}' not found`);
|
|
198
|
-
}
|
|
199
|
-
}
|
|
200
|
-
|
|
201
|
-
if (options.json) {
|
|
202
|
-
console.log(JSON.stringify({ configPath, bindings }, null, 2));
|
|
203
|
-
return;
|
|
204
|
-
}
|
|
205
|
-
|
|
206
|
-
if (bindings.length === 0) {
|
|
207
|
-
console.log("No bindings configured.");
|
|
208
|
-
return;
|
|
209
|
-
}
|
|
210
|
-
|
|
211
|
-
console.log(`Bindings (${configPath}):`);
|
|
212
|
-
for (const binding of bindings) {
|
|
213
|
-
const routeSuffix = binding.routeExists ? "" : " [missing route]";
|
|
214
|
-
const projectSuffix = binding.projectRoot ? ` (${binding.projectRoot})` : "";
|
|
215
|
-
console.log(
|
|
216
|
-
`- ${binding.bindingId}: ${binding.connectorId}/${binding.sourceType}:${binding.sourceId} -> ${binding.routeId}${routeSuffix}${projectSuffix}`,
|
|
217
|
-
);
|
|
218
|
-
}
|
|
219
|
-
}
|
|
220
|
-
|
|
221
|
-
export async function runBindingSetCommand(options: {
|
|
222
|
-
bindingId: string;
|
|
223
|
-
connectorId: string;
|
|
224
|
-
routeId: string;
|
|
225
|
-
sourceType: "channel" | "chat";
|
|
226
|
-
sourceId: string;
|
|
227
|
-
}): Promise<void> {
|
|
228
|
-
const configPath = resolveConfigPath();
|
|
229
|
-
const rawConfig = await requireRawConfig(configPath);
|
|
230
|
-
const normalized = ensureGatewayConfigShape(structuredClone(rawConfig));
|
|
231
|
-
|
|
232
|
-
if (!normalized.connectors.items[options.connectorId]) {
|
|
233
|
-
throw new Error(`Connector '${options.connectorId}' does not exist`);
|
|
234
|
-
}
|
|
235
|
-
if (!normalized.routes.items[options.routeId]) {
|
|
236
|
-
throw new Error(`Route '${options.routeId}' does not exist`);
|
|
237
|
-
}
|
|
238
|
-
|
|
239
|
-
const duplicate = Object.entries(normalized.bindings.items).find(([bindingId, binding]) =>
|
|
240
|
-
bindingId !== options.bindingId
|
|
241
|
-
&& binding.connector === options.connectorId
|
|
242
|
-
&& binding.source.type === options.sourceType
|
|
243
|
-
&& binding.source.id === options.sourceId
|
|
244
|
-
);
|
|
245
|
-
if (duplicate) {
|
|
246
|
-
throw new Error(
|
|
247
|
-
`Binding source '${options.connectorId}/${options.sourceType}:${options.sourceId}' is already used by '${duplicate[0]}'`,
|
|
248
|
-
);
|
|
249
|
-
}
|
|
250
|
-
|
|
251
|
-
const binding: RawBindingConfig = {
|
|
252
|
-
connector: options.connectorId,
|
|
253
|
-
source: {
|
|
254
|
-
type: options.sourceType,
|
|
255
|
-
id: options.sourceId,
|
|
256
|
-
},
|
|
257
|
-
route: options.routeId,
|
|
258
|
-
};
|
|
259
|
-
upsertBinding(normalized, options.bindingId, binding);
|
|
260
|
-
|
|
261
|
-
await saveConfig(configPath, normalized);
|
|
262
|
-
console.log(`Upserted binding '${options.bindingId}'`);
|
|
263
|
-
}
|
|
264
|
-
|
|
265
|
-
export async function runBindingRemoveCommand(options: {
|
|
266
|
-
bindingId: string;
|
|
267
|
-
}): Promise<void> {
|
|
268
|
-
const configPath = resolveConfigPath();
|
|
269
|
-
const rawConfig = await requireRawConfig(configPath);
|
|
270
|
-
const normalized = ensureGatewayConfigShape(structuredClone(rawConfig));
|
|
271
|
-
|
|
272
|
-
if (!normalized.bindings.items[options.bindingId]) {
|
|
273
|
-
throw new Error(`Binding '${options.bindingId}' not found`);
|
|
274
|
-
}
|
|
275
|
-
|
|
276
|
-
delete normalized.bindings.items[options.bindingId];
|
|
277
|
-
await saveConfig(configPath, normalized);
|
|
278
|
-
console.log(`Removed binding '${options.bindingId}'`);
|
|
279
|
-
}
|
|
280
|
-
|
|
281
|
-
export async function runRouteListCommand(options: {
|
|
282
|
-
json?: boolean;
|
|
283
|
-
}): Promise<void> {
|
|
284
|
-
const configPath = resolveConfigPath();
|
|
285
|
-
const rawConfig = await requireRawConfig(configPath);
|
|
286
|
-
const routes = listRoutes(rawConfig);
|
|
287
|
-
|
|
288
|
-
if (options.json) {
|
|
289
|
-
console.log(JSON.stringify({ configPath, routes }, null, 2));
|
|
290
|
-
return;
|
|
291
|
-
}
|
|
292
|
-
|
|
293
|
-
if (routes.length === 0) {
|
|
294
|
-
console.log("No routes configured.");
|
|
295
|
-
return;
|
|
296
|
-
}
|
|
297
|
-
|
|
298
|
-
console.log(`Routes (${configPath}):`);
|
|
299
|
-
for (const route of routes) {
|
|
300
|
-
const providerInfo = route.provider ? route.provider : "(route default)";
|
|
301
|
-
const sandboxInfo = route.sandbox ? route.sandbox : BUILTIN_HOST_SANDBOX_ID;
|
|
302
|
-
console.log(
|
|
303
|
-
`- ${route.routeId}: ${route.projectRoot}, tools=${route.tools}, mentions=${route.mentions}, provider=${providerInfo}, sandbox=${sandboxInfo}, bindings=${route.bindings}`,
|
|
304
|
-
);
|
|
305
|
-
}
|
|
306
|
-
}
|
|
307
|
-
|
|
308
|
-
export async function runRouteSetCommand(options: {
|
|
309
|
-
routeId: string;
|
|
310
|
-
projectRoot?: string;
|
|
311
|
-
tools?: string;
|
|
312
|
-
providerId?: string;
|
|
313
|
-
sandboxId?: string;
|
|
314
|
-
mentions?: "required" | "optional";
|
|
315
|
-
}): Promise<void> {
|
|
316
|
-
const configPath = resolveConfigPath();
|
|
317
|
-
const rawConfig = await requireRawConfig(configPath);
|
|
318
|
-
const normalized = ensureGatewayConfigShape(structuredClone(rawConfig));
|
|
319
|
-
const existing = normalized.routes.items[options.routeId];
|
|
320
|
-
|
|
321
|
-
const projectRoot = options.projectRoot?.trim() || existing?.projectRoot;
|
|
322
|
-
if (!projectRoot) {
|
|
323
|
-
throw new Error("--project-root is required when creating a new route");
|
|
324
|
-
}
|
|
325
|
-
|
|
326
|
-
const toolsRaw = options.tools ?? existing?.tools;
|
|
327
|
-
if (toolsRaw !== undefined && toolsRaw !== "full" && toolsRaw !== "readonly") {
|
|
328
|
-
throw new Error(`Invalid --tools '${toolsRaw}'. Allowed: full, readonly`);
|
|
329
|
-
}
|
|
330
|
-
|
|
331
|
-
const provider = options.providerId ?? existing?.provider;
|
|
332
|
-
if (provider && !normalized.providers.items[provider]) {
|
|
333
|
-
throw new Error(`Provider '${provider}' does not exist`);
|
|
334
|
-
}
|
|
335
|
-
|
|
336
|
-
const sandbox = options.sandboxId ?? existing?.sandbox;
|
|
337
|
-
if (sandbox && sandbox !== BUILTIN_HOST_SANDBOX_ID && !normalized.sandboxes.items[sandbox]) {
|
|
338
|
-
throw new Error(`Sandbox '${sandbox}' does not exist`);
|
|
339
|
-
}
|
|
340
|
-
|
|
341
|
-
upsertRoute(normalized, options.routeId, {
|
|
342
|
-
projectRoot,
|
|
343
|
-
...(toolsRaw ? { tools: toolsRaw } : {}),
|
|
344
|
-
...((options.mentions ?? existing?.mentions) ? { mentions: (options.mentions ?? existing?.mentions)! } : {}),
|
|
345
|
-
...(provider ? { provider } : {}),
|
|
346
|
-
...(sandbox ? { sandbox } : {}),
|
|
347
|
-
...(typeof existing?.systemPromptFile === "string" ? { systemPromptFile: existing.systemPromptFile } : {}),
|
|
348
|
-
});
|
|
349
|
-
|
|
350
|
-
await saveConfig(configPath, normalized);
|
|
351
|
-
console.log(`Upserted route '${options.routeId}'`);
|
|
352
|
-
}
|
|
353
|
-
|
|
354
|
-
export async function runRouteRemoveCommand(options: {
|
|
355
|
-
routeId: string;
|
|
356
|
-
cascadeBindings?: boolean;
|
|
357
|
-
}): Promise<void> {
|
|
358
|
-
const configPath = resolveConfigPath();
|
|
359
|
-
const rawConfig = await requireRawConfig(configPath);
|
|
360
|
-
const normalized = ensureGatewayConfigShape(structuredClone(rawConfig));
|
|
361
|
-
|
|
362
|
-
if (!normalized.routes.items[options.routeId]) {
|
|
363
|
-
throw new Error(`Route '${options.routeId}' not found`);
|
|
364
|
-
}
|
|
365
|
-
|
|
366
|
-
const bindingRefs = listBindings(normalized).filter((binding) => binding.routeId === options.routeId);
|
|
367
|
-
if (bindingRefs.length > 0 && !options.cascadeBindings) {
|
|
368
|
-
const refList = bindingRefs.map((binding) => binding.bindingId).join(", ");
|
|
369
|
-
throw new Error(
|
|
370
|
-
`Route '${options.routeId}' is referenced by bindings (${refList}). Re-run with --cascade-bindings to remove these bindings automatically.`,
|
|
371
|
-
);
|
|
372
|
-
}
|
|
373
|
-
|
|
374
|
-
if (bindingRefs.length > 0 && options.cascadeBindings) {
|
|
375
|
-
for (const binding of bindingRefs) {
|
|
376
|
-
delete normalized.bindings.items[binding.bindingId];
|
|
377
|
-
}
|
|
378
|
-
}
|
|
379
|
-
|
|
380
|
-
delete normalized.routes.items[options.routeId];
|
|
381
|
-
await saveConfig(configPath, normalized);
|
|
382
|
-
console.log(`Removed route '${options.routeId}'`);
|
|
383
|
-
}
|
package/src/cli/index.ts
DELETED