@dobby.ai/dobby 0.1.0 → 0.1.1
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/.env.example +0 -1
- package/AGENTS.md +7 -7
- package/README.md +64 -32
- package/config/gateway.example.json +10 -6
- package/dist/plugins/connector-discord/src/mapper.js +75 -0
- 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 -131
- 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/cli/tests/config-io.test.js +5 -5
- package/dist/src/cli/tests/discord-mapper.test.js +90 -0
- package/dist/src/cli/tests/doctor.test.js +145 -0
- package/dist/src/cli/tests/init-catalog.test.js +108 -61
- package/dist/src/cli/tests/program-options.test.js +14 -28
- package/dist/src/cli/tests/routing-config.test.js +59 -4
- package/dist/src/core/gateway.js +3 -1
- package/dist/src/core/routing.js +53 -38
- package/dist/src/main.js +0 -0
- package/dist/src/shared/dobby-repo.js +40 -0
- package/docs/RUNBOOK.md +28 -27
- package/package.json +3 -2
- package/plugins/connector-discord/package-lock.json +2 -2
- package/plugins/connector-discord/package.json +1 -1
- package/plugins/connector-discord/src/connector.ts +0 -5
- package/plugins/connector-discord/src/mapper.ts +3 -4
- package/plugins/connector-feishu/package-lock.json +2 -2
- package/plugins/connector-feishu/package.json +1 -1
- package/plugins/plugin-sdk/package-lock.json +2 -2
- package/plugins/plugin-sdk/package.json +1 -1
- package/plugins/provider-claude/package-lock.json +2 -2
- package/plugins/provider-claude/package.json +1 -1
- package/plugins/provider-claude-cli/package-lock.json +2 -2
- package/plugins/provider-claude-cli/package.json +1 -1
- package/plugins/provider-pi/package-lock.json +2 -2
- package/plugins/provider-pi/package.json +1 -1
- package/plugins/provider-pi/src/contribution.ts +139 -9
- package/src/cli/commands/doctor.ts +103 -2
- package/src/cli/commands/extension.ts +3 -1
- package/src/cli/commands/init.ts +45 -230
- package/src/cli/commands/topology.ts +48 -16
- package/src/cli/program.ts +16 -167
- package/src/cli/shared/config-io.ts +3 -35
- package/src/cli/shared/config-mutators.ts +39 -9
- package/src/cli/shared/config-types.ts +10 -2
- package/src/cli/shared/configure-sections.ts +55 -11
- package/src/cli/shared/init-catalog.ts +126 -66
- package/src/cli/shared/local-extension-specs.ts +108 -0
- package/src/cli/shared/schema-prompts.ts +30 -1
- package/src/cli/tests/config-io.test.ts +5 -5
- package/src/cli/tests/discord-mapper.test.ts +128 -0
- package/src/cli/tests/doctor.test.ts +149 -0
- package/src/cli/tests/init-catalog.test.ts +112 -64
- package/src/cli/tests/program-options.test.ts +14 -32
- package/src/cli/tests/routing-config.test.ts +76 -4
- package/src/core/gateway.ts +3 -1
- package/src/core/routing.ts +70 -45
- package/src/core/types.ts +8 -2
- package/src/shared/dobby-repo.ts +48 -0
- 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-path.test.js +0 -21
- package/dist/src/cli/tests/discord-config.test.js +0 -23
- package/dist/src/cli/tests/presets.test.js +0 -41
- package/dist/src/cli/tests/routing-legacy.test.js +0 -191
- package/dist/src/core/tests/gateway-update-strategy.test.js +0 -167
- package/src/cli/shared/init-models-file.ts +0 -77
package/src/cli/program.ts
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
|
+
import { existsSync, readFileSync } from "node:fs";
|
|
2
|
+
import { fileURLToPath } from "node:url";
|
|
1
3
|
import { Command } from "commander";
|
|
2
4
|
import {
|
|
3
|
-
runConfigEditCommand,
|
|
4
5
|
runConfigListCommand,
|
|
5
6
|
runConfigSchemaListCommand,
|
|
6
7
|
runConfigSchemaShowCommand,
|
|
7
8
|
runConfigShowCommand,
|
|
8
9
|
} from "./commands/config.js";
|
|
9
|
-
import { runConfigureCommand } from "./commands/configure.js";
|
|
10
10
|
import {
|
|
11
11
|
runCronAddCommand,
|
|
12
12
|
runCronListCommand,
|
|
@@ -25,16 +25,18 @@ import {
|
|
|
25
25
|
} from "./commands/extension.js";
|
|
26
26
|
import { runInitCommand } from "./commands/init.js";
|
|
27
27
|
import { runStartCommand } from "./commands/start.js";
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
}
|
|
28
|
+
|
|
29
|
+
function loadCliVersion(): string {
|
|
30
|
+
const candidates = [
|
|
31
|
+
fileURLToPath(new URL("../../package.json", import.meta.url)),
|
|
32
|
+
fileURLToPath(new URL("../../../package.json", import.meta.url)),
|
|
33
|
+
];
|
|
34
|
+
const fallbackCandidate = fileURLToPath(new URL("../../package.json", import.meta.url));
|
|
35
|
+
const packageJsonPath = candidates.find((candidate) => existsSync(candidate)) ?? fallbackCandidate;
|
|
36
|
+
return JSON.parse(readFileSync(packageJsonPath, "utf-8")).version as string;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
const CLI_VERSION = loadCliVersion();
|
|
38
40
|
|
|
39
41
|
/**
|
|
40
42
|
* Builds the top-level dobby CLI program and registers all subcommands.
|
|
@@ -43,6 +45,7 @@ export function buildProgram(): Command {
|
|
|
43
45
|
const program = new Command();
|
|
44
46
|
program
|
|
45
47
|
.name("dobby")
|
|
48
|
+
.version(CLI_VERSION)
|
|
46
49
|
.description("Discord-first local agent gateway")
|
|
47
50
|
.showHelpAfterError()
|
|
48
51
|
.action(async () => {
|
|
@@ -63,146 +66,7 @@ export function buildProgram(): Command {
|
|
|
63
66
|
await runInitCommand();
|
|
64
67
|
});
|
|
65
68
|
|
|
66
|
-
program
|
|
67
|
-
.command("configure")
|
|
68
|
-
.description("Interactive configuration wizard")
|
|
69
|
-
.option(
|
|
70
|
-
"--section <section>",
|
|
71
|
-
"Config section (repeatable): provider|connector|route|binding|sandbox|data",
|
|
72
|
-
(value: string, previous: string[]) => [...previous, value],
|
|
73
|
-
[] as string[],
|
|
74
|
-
)
|
|
75
|
-
.action(async (opts) => {
|
|
76
|
-
await runConfigureCommand({
|
|
77
|
-
sections: opts.section as string[],
|
|
78
|
-
});
|
|
79
|
-
});
|
|
80
|
-
|
|
81
|
-
const botCommand = program.command("bot").description("Manage bot connector settings");
|
|
82
|
-
|
|
83
|
-
botCommand
|
|
84
|
-
.command("list")
|
|
85
|
-
.description("List configured bot connectors")
|
|
86
|
-
.option("--json", "Output JSON", false)
|
|
87
|
-
.action(async (opts) => {
|
|
88
|
-
await runBotListCommand({
|
|
89
|
-
json: Boolean(opts.json),
|
|
90
|
-
});
|
|
91
|
-
});
|
|
92
|
-
|
|
93
|
-
botCommand
|
|
94
|
-
.command("set")
|
|
95
|
-
.description("Update one bot connector")
|
|
96
|
-
.argument("<connectorId>", "Connector instance ID")
|
|
97
|
-
.option("--name <name>", "Discord botName")
|
|
98
|
-
.option("--token <token>", "Discord botToken")
|
|
99
|
-
.action(async (connectorId: string, opts) => {
|
|
100
|
-
await runBotSetCommand({
|
|
101
|
-
connectorId,
|
|
102
|
-
...(typeof opts.name === "string" ? { name: opts.name as string } : {}),
|
|
103
|
-
...(typeof opts.token === "string" ? { token: opts.token as string } : {}),
|
|
104
|
-
});
|
|
105
|
-
});
|
|
106
|
-
|
|
107
|
-
const bindingCommand = program.command("binding").description("Manage connector source-route bindings");
|
|
108
|
-
|
|
109
|
-
bindingCommand
|
|
110
|
-
.command("list")
|
|
111
|
-
.description("List bindings")
|
|
112
|
-
.option("--connector <id>", "Filter by connector instance ID")
|
|
113
|
-
.option("--json", "Output JSON", false)
|
|
114
|
-
.action(async (opts) => {
|
|
115
|
-
await runBindingListCommand({
|
|
116
|
-
...(typeof opts.connector === "string" ? { connectorId: opts.connector as string } : {}),
|
|
117
|
-
json: Boolean(opts.json),
|
|
118
|
-
});
|
|
119
|
-
});
|
|
120
|
-
|
|
121
|
-
bindingCommand
|
|
122
|
-
.command("set")
|
|
123
|
-
.description("Create or update one binding")
|
|
124
|
-
.argument("<bindingId>", "Binding ID")
|
|
125
|
-
.requiredOption("--connector <id>", "Connector instance ID")
|
|
126
|
-
.requiredOption("--source-type <type>", "Source type: channel|chat")
|
|
127
|
-
.requiredOption("--source-id <id>", "Source ID")
|
|
128
|
-
.requiredOption("--route <id>", "Route ID")
|
|
129
|
-
.action(async (bindingId: string, opts) => {
|
|
130
|
-
if (opts.sourceType !== "channel" && opts.sourceType !== "chat") {
|
|
131
|
-
throw new Error("--source-type must be channel or chat");
|
|
132
|
-
}
|
|
133
|
-
|
|
134
|
-
await runBindingSetCommand({
|
|
135
|
-
bindingId,
|
|
136
|
-
connectorId: opts.connector as string,
|
|
137
|
-
sourceType: opts.sourceType as "channel" | "chat",
|
|
138
|
-
sourceId: opts.sourceId as string,
|
|
139
|
-
routeId: opts.route as string,
|
|
140
|
-
});
|
|
141
|
-
});
|
|
142
|
-
|
|
143
|
-
bindingCommand
|
|
144
|
-
.command("remove")
|
|
145
|
-
.description("Remove one binding")
|
|
146
|
-
.argument("<bindingId>", "Binding ID")
|
|
147
|
-
.action(async (bindingId: string) => {
|
|
148
|
-
await runBindingRemoveCommand({
|
|
149
|
-
bindingId,
|
|
150
|
-
});
|
|
151
|
-
});
|
|
152
|
-
|
|
153
|
-
const routeCommand = program.command("route").description("Manage route profiles");
|
|
154
|
-
|
|
155
|
-
routeCommand
|
|
156
|
-
.command("list")
|
|
157
|
-
.description("List route profiles")
|
|
158
|
-
.option("--json", "Output JSON", false)
|
|
159
|
-
.action(async (opts) => {
|
|
160
|
-
await runRouteListCommand({
|
|
161
|
-
json: Boolean(opts.json),
|
|
162
|
-
});
|
|
163
|
-
});
|
|
164
|
-
|
|
165
|
-
routeCommand
|
|
166
|
-
.command("set")
|
|
167
|
-
.description("Create or update one route")
|
|
168
|
-
.argument("<routeId>", "Route ID")
|
|
169
|
-
.option("--project-root <path>", "Route project root")
|
|
170
|
-
.option("--tools <profile>", "Route tools profile: full|readonly")
|
|
171
|
-
.option("--provider <id>", "Provider instance ID")
|
|
172
|
-
.option("--sandbox <id>", "Sandbox instance ID")
|
|
173
|
-
.option("--mentions <policy>", "Mention policy: required|optional")
|
|
174
|
-
.action(async (routeId: string, opts) => {
|
|
175
|
-
if (
|
|
176
|
-
typeof opts.mentions === "string"
|
|
177
|
-
&& opts.mentions !== "required"
|
|
178
|
-
&& opts.mentions !== "optional"
|
|
179
|
-
) {
|
|
180
|
-
throw new Error("--mentions must be required or optional");
|
|
181
|
-
}
|
|
182
|
-
|
|
183
|
-
await runRouteSetCommand({
|
|
184
|
-
routeId,
|
|
185
|
-
...(typeof opts.projectRoot === "string" ? { projectRoot: opts.projectRoot as string } : {}),
|
|
186
|
-
...(typeof opts.tools === "string" ? { tools: opts.tools as string } : {}),
|
|
187
|
-
...(typeof opts.provider === "string" ? { providerId: opts.provider as string } : {}),
|
|
188
|
-
...(typeof opts.sandbox === "string" ? { sandboxId: opts.sandbox as string } : {}),
|
|
189
|
-
...(typeof opts.mentions === "string" ? { mentions: opts.mentions as "required" | "optional" } : {}),
|
|
190
|
-
});
|
|
191
|
-
});
|
|
192
|
-
|
|
193
|
-
routeCommand
|
|
194
|
-
.command("remove")
|
|
195
|
-
.description("Remove one route")
|
|
196
|
-
.argument("<routeId>", "Route ID")
|
|
197
|
-
.option("--cascade-bindings", "Remove bindings that reference this route", false)
|
|
198
|
-
.action(async (routeId: string, opts) => {
|
|
199
|
-
await runRouteRemoveCommand({
|
|
200
|
-
routeId,
|
|
201
|
-
cascadeBindings: Boolean(opts.cascadeBindings),
|
|
202
|
-
});
|
|
203
|
-
});
|
|
204
|
-
|
|
205
|
-
const configCommand = program.command("config").description("Inspect and edit config");
|
|
69
|
+
const configCommand = program.command("config").description("Inspect config");
|
|
206
70
|
|
|
207
71
|
configCommand
|
|
208
72
|
.command("show")
|
|
@@ -228,21 +92,6 @@ export function buildProgram(): Command {
|
|
|
228
92
|
});
|
|
229
93
|
});
|
|
230
94
|
|
|
231
|
-
configCommand
|
|
232
|
-
.command("edit")
|
|
233
|
-
.description("Interactive edit for high-frequency sections")
|
|
234
|
-
.option(
|
|
235
|
-
"--section <section>",
|
|
236
|
-
"Edit section (repeatable): provider|connector|route|binding",
|
|
237
|
-
(value: string, previous: string[]) => [...previous, value],
|
|
238
|
-
[] as string[],
|
|
239
|
-
)
|
|
240
|
-
.action(async (opts) => {
|
|
241
|
-
await runConfigEditCommand({
|
|
242
|
-
sections: opts.section as string[],
|
|
243
|
-
});
|
|
244
|
-
});
|
|
245
|
-
|
|
246
95
|
const configSchemaCommand = configCommand.command("schema").description("Inspect extension config schemas");
|
|
247
96
|
|
|
248
97
|
configSchemaCommand
|
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
import { existsSync, readFileSync } from "node:fs";
|
|
2
1
|
import { access, mkdir, readFile, rename, rm, writeFile } from "node:fs/promises";
|
|
3
2
|
import { dirname, isAbsolute, resolve } from "node:path";
|
|
4
3
|
import { homedir } from "node:os";
|
|
5
4
|
import { loadGatewayConfig } from "../../core/routing.js";
|
|
5
|
+
import { findDobbyRepoRoot, isDobbyRepoRoot } from "../../shared/dobby-repo.js";
|
|
6
6
|
import type { RawGatewayConfig } from "./config-types.js";
|
|
7
7
|
|
|
8
8
|
/**
|
|
@@ -37,27 +37,6 @@ interface ResolvedConfigPathInfo {
|
|
|
37
37
|
source: ConfigPathSource;
|
|
38
38
|
}
|
|
39
39
|
|
|
40
|
-
/**
|
|
41
|
-
* Returns true when a directory looks like the dobby repository root.
|
|
42
|
-
*/
|
|
43
|
-
function isDobbyRepoRoot(candidateDir: string): boolean {
|
|
44
|
-
const packageJsonPath = resolve(candidateDir, "package.json");
|
|
45
|
-
const repoConfigPath = resolve(candidateDir, "config", "gateway.json");
|
|
46
|
-
const localExtensionsScriptPath = resolve(candidateDir, "scripts", "local-extensions.mjs");
|
|
47
|
-
|
|
48
|
-
if (!existsSync(packageJsonPath) || !existsSync(repoConfigPath) || !existsSync(localExtensionsScriptPath)) {
|
|
49
|
-
return false;
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
try {
|
|
53
|
-
const packageJsonRaw = readFileSync(packageJsonPath, "utf-8");
|
|
54
|
-
const parsed = JSON.parse(packageJsonRaw) as { name?: unknown };
|
|
55
|
-
return parsed.name === "dobby";
|
|
56
|
-
} catch {
|
|
57
|
-
return false;
|
|
58
|
-
}
|
|
59
|
-
}
|
|
60
|
-
|
|
61
40
|
function resolveConfigBaseDir(configPath: string): string {
|
|
62
41
|
const absoluteConfigPath = resolve(configPath);
|
|
63
42
|
const configDir = dirname(absoluteConfigPath);
|
|
@@ -74,19 +53,8 @@ function resolveConfigBaseDir(configPath: string): string {
|
|
|
74
53
|
* Scans current directory and ancestors to find a local dobby repo config path.
|
|
75
54
|
*/
|
|
76
55
|
function findDobbyRepoConfigPath(startDir: string): string | null {
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
while (true) {
|
|
80
|
-
if (isDobbyRepoRoot(currentDir)) {
|
|
81
|
-
return resolve(currentDir, "config", "gateway.json");
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
const parentDir = dirname(currentDir);
|
|
85
|
-
if (parentDir === currentDir) {
|
|
86
|
-
return null;
|
|
87
|
-
}
|
|
88
|
-
currentDir = parentDir;
|
|
89
|
-
}
|
|
56
|
+
const repoRoot = findDobbyRepoRoot(startDir);
|
|
57
|
+
return repoRoot ? resolve(repoRoot, "config", "gateway.json") : null;
|
|
90
58
|
}
|
|
91
59
|
|
|
92
60
|
/**
|
|
@@ -4,6 +4,7 @@ import type {
|
|
|
4
4
|
ContributionTemplatesByKind,
|
|
5
5
|
NormalizedGatewayConfig,
|
|
6
6
|
RawBindingConfig,
|
|
7
|
+
RawDefaultBindingConfig,
|
|
7
8
|
RawExtensionItemConfig,
|
|
8
9
|
RawGatewayConfig,
|
|
9
10
|
RawRouteDefaults,
|
|
@@ -65,6 +66,7 @@ function asRouteDefaults(value: unknown): RawRouteDefaults {
|
|
|
65
66
|
}
|
|
66
67
|
|
|
67
68
|
return {
|
|
69
|
+
...(typeof value.projectRoot === "string" && value.projectRoot.trim().length > 0 ? { projectRoot: value.projectRoot } : {}),
|
|
68
70
|
...(typeof value.provider === "string" && value.provider.trim().length > 0 ? { provider: value.provider } : {}),
|
|
69
71
|
...(typeof value.sandbox === "string" && value.sandbox.trim().length > 0 ? { sandbox: value.sandbox } : {}),
|
|
70
72
|
tools: value.tools === "readonly" ? "readonly" : "full",
|
|
@@ -79,13 +81,13 @@ function asRoutes(value: unknown): Record<string, RawRouteProfile> {
|
|
|
79
81
|
|
|
80
82
|
const normalized: Record<string, RawRouteProfile> = {};
|
|
81
83
|
for (const [routeId, route] of Object.entries(value)) {
|
|
82
|
-
if (!isRecord(route)
|
|
84
|
+
if (!isRecord(route)) {
|
|
83
85
|
continue;
|
|
84
86
|
}
|
|
85
87
|
|
|
86
88
|
normalized[routeId] = {
|
|
87
89
|
...route,
|
|
88
|
-
projectRoot: route.projectRoot,
|
|
90
|
+
...(typeof route.projectRoot === "string" && route.projectRoot.trim().length > 0 ? { projectRoot: route.projectRoot } : {}),
|
|
89
91
|
...(route.tools === "readonly" ? { tools: "readonly" as const } : {}),
|
|
90
92
|
...(route.mentions === "optional" ? { mentions: "optional" as const } : {}),
|
|
91
93
|
...(typeof route.provider === "string" && route.provider.trim().length > 0 ? { provider: route.provider } : {}),
|
|
@@ -97,6 +99,17 @@ function asRoutes(value: unknown): Record<string, RawRouteProfile> {
|
|
|
97
99
|
return normalized;
|
|
98
100
|
}
|
|
99
101
|
|
|
102
|
+
function asDefaultBinding(value: unknown): RawDefaultBindingConfig | undefined {
|
|
103
|
+
if (!isRecord(value) || typeof value.route !== "string" || value.route.trim().length === 0) {
|
|
104
|
+
return undefined;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
return {
|
|
108
|
+
...value,
|
|
109
|
+
route: value.route,
|
|
110
|
+
};
|
|
111
|
+
}
|
|
112
|
+
|
|
100
113
|
function asBindings(value: unknown): Record<string, RawBindingConfig> {
|
|
101
114
|
if (!isRecord(value)) {
|
|
102
115
|
return {};
|
|
@@ -147,7 +160,7 @@ export function ensureGatewayConfigShape(config: RawGatewayConfig): NormalizedGa
|
|
|
147
160
|
? config.sandboxes.default
|
|
148
161
|
: "host.builtin";
|
|
149
162
|
|
|
150
|
-
const routeDefaults = asRouteDefaults(config.routes?.
|
|
163
|
+
const routeDefaults = asRouteDefaults(config.routes?.default);
|
|
151
164
|
if (!routeDefaults.provider && normalizedProvidersDefault) {
|
|
152
165
|
routeDefaults.provider = normalizedProvidersDefault;
|
|
153
166
|
}
|
|
@@ -155,6 +168,8 @@ export function ensureGatewayConfigShape(config: RawGatewayConfig): NormalizedGa
|
|
|
155
168
|
routeDefaults.sandbox = normalizedSandboxesDefault;
|
|
156
169
|
}
|
|
157
170
|
|
|
171
|
+
const defaultBinding = asDefaultBinding(config.bindings?.default);
|
|
172
|
+
|
|
158
173
|
return {
|
|
159
174
|
...config,
|
|
160
175
|
extensions: {
|
|
@@ -177,11 +192,12 @@ export function ensureGatewayConfigShape(config: RawGatewayConfig): NormalizedGa
|
|
|
177
192
|
},
|
|
178
193
|
routes: {
|
|
179
194
|
...((isRecord(config.routes) ? config.routes : {}) as Record<string, unknown>),
|
|
180
|
-
|
|
195
|
+
default: routeDefaults,
|
|
181
196
|
items: asRoutes(config.routes?.items),
|
|
182
197
|
},
|
|
183
198
|
bindings: {
|
|
184
199
|
...((isRecord(config.bindings) ? config.bindings : {}) as Record<string, unknown>),
|
|
200
|
+
...(defaultBinding ? { default: defaultBinding } : {}),
|
|
185
201
|
items: asBindings(config.bindings?.items),
|
|
186
202
|
},
|
|
187
203
|
data: {
|
|
@@ -368,11 +384,11 @@ export function setDefaultProviderIfMissingOrInvalid(config: RawGatewayConfig):
|
|
|
368
384
|
|
|
369
385
|
if (defaultProvider && items[defaultProvider]) {
|
|
370
386
|
config.providers = next.providers;
|
|
371
|
-
if (!next.routes.
|
|
387
|
+
if (!next.routes.default.provider) {
|
|
372
388
|
config.routes = {
|
|
373
389
|
...next.routes,
|
|
374
390
|
defaults: {
|
|
375
|
-
...next.routes.
|
|
391
|
+
...next.routes.default,
|
|
376
392
|
provider: defaultProvider,
|
|
377
393
|
},
|
|
378
394
|
};
|
|
@@ -393,8 +409,8 @@ export function setDefaultProviderIfMissingOrInvalid(config: RawGatewayConfig):
|
|
|
393
409
|
};
|
|
394
410
|
config.routes = {
|
|
395
411
|
...next.routes,
|
|
396
|
-
|
|
397
|
-
...next.routes.
|
|
412
|
+
default: {
|
|
413
|
+
...next.routes.default,
|
|
398
414
|
provider: candidates[0]!,
|
|
399
415
|
},
|
|
400
416
|
};
|
|
@@ -403,7 +419,7 @@ export function setDefaultProviderIfMissingOrInvalid(config: RawGatewayConfig):
|
|
|
403
419
|
export function upsertRoute(config: RawGatewayConfig, routeId: string, profile: RawRouteProfile): void {
|
|
404
420
|
const next = ensureGatewayConfigShape(config);
|
|
405
421
|
next.routes.items[routeId] = {
|
|
406
|
-
projectRoot: profile.projectRoot,
|
|
422
|
+
...(typeof profile.projectRoot === "string" && profile.projectRoot.trim().length > 0 ? { projectRoot: profile.projectRoot } : {}),
|
|
407
423
|
...(profile.tools ? { tools: profile.tools } : {}),
|
|
408
424
|
...(profile.mentions ? { mentions: profile.mentions } : {}),
|
|
409
425
|
...(profile.provider ? { provider: profile.provider } : {}),
|
|
@@ -425,6 +441,20 @@ export function upsertBinding(config: RawGatewayConfig, bindingId: string, bindi
|
|
|
425
441
|
};
|
|
426
442
|
}
|
|
427
443
|
|
|
444
|
+
export function setDefaultBinding(config: RawGatewayConfig, binding: RawDefaultBindingConfig | undefined): void {
|
|
445
|
+
const next = ensureGatewayConfigShape(config);
|
|
446
|
+
const normalizedBinding = binding ? structuredClone(binding) : undefined;
|
|
447
|
+
config.bindings = {
|
|
448
|
+
...next.bindings,
|
|
449
|
+
...(normalizedBinding ? { default: normalizedBinding } : {}),
|
|
450
|
+
items: next.bindings.items,
|
|
451
|
+
};
|
|
452
|
+
|
|
453
|
+
if (!normalizedBinding) {
|
|
454
|
+
delete config.bindings.default;
|
|
455
|
+
}
|
|
456
|
+
}
|
|
457
|
+
|
|
428
458
|
export function listContributionIds(config: RawGatewayConfig): {
|
|
429
459
|
providers: string[];
|
|
430
460
|
connectors: string[];
|
|
@@ -9,6 +9,7 @@ export interface RawExtensionItemConfig {
|
|
|
9
9
|
}
|
|
10
10
|
|
|
11
11
|
export interface RawRouteDefaults {
|
|
12
|
+
projectRoot?: string;
|
|
12
13
|
provider?: string;
|
|
13
14
|
sandbox?: string;
|
|
14
15
|
tools?: "full" | "readonly";
|
|
@@ -17,7 +18,7 @@ export interface RawRouteDefaults {
|
|
|
17
18
|
}
|
|
18
19
|
|
|
19
20
|
export interface RawRouteProfile {
|
|
20
|
-
projectRoot
|
|
21
|
+
projectRoot?: string;
|
|
21
22
|
tools?: "full" | "readonly";
|
|
22
23
|
systemPromptFile?: string;
|
|
23
24
|
mentions?: "required" | "optional";
|
|
@@ -26,6 +27,11 @@ export interface RawRouteProfile {
|
|
|
26
27
|
[key: string]: unknown;
|
|
27
28
|
}
|
|
28
29
|
|
|
30
|
+
export interface RawDefaultBindingConfig {
|
|
31
|
+
route: string;
|
|
32
|
+
[key: string]: unknown;
|
|
33
|
+
}
|
|
34
|
+
|
|
29
35
|
export interface RawBindingConfig {
|
|
30
36
|
connector: string;
|
|
31
37
|
source: {
|
|
@@ -62,6 +68,7 @@ export interface RawGatewayConfig {
|
|
|
62
68
|
[key: string]: unknown;
|
|
63
69
|
};
|
|
64
70
|
bindings?: {
|
|
71
|
+
default?: RawDefaultBindingConfig;
|
|
65
72
|
items?: Record<string, RawBindingConfig>;
|
|
66
73
|
[key: string]: unknown;
|
|
67
74
|
};
|
|
@@ -93,11 +100,12 @@ export interface NormalizedGatewayConfig extends RawGatewayConfig {
|
|
|
93
100
|
[key: string]: unknown;
|
|
94
101
|
};
|
|
95
102
|
routes: {
|
|
96
|
-
|
|
103
|
+
default: RawRouteDefaults;
|
|
97
104
|
items: Record<string, RawRouteProfile>;
|
|
98
105
|
[key: string]: unknown;
|
|
99
106
|
};
|
|
100
107
|
bindings: {
|
|
108
|
+
default?: RawDefaultBindingConfig;
|
|
101
109
|
items: Record<string, RawBindingConfig>;
|
|
102
110
|
[key: string]: unknown;
|
|
103
111
|
};
|
|
@@ -10,6 +10,7 @@ import {
|
|
|
10
10
|
import JSON5 from "json5";
|
|
11
11
|
import {
|
|
12
12
|
ensureGatewayConfigShape,
|
|
13
|
+
setDefaultBinding,
|
|
13
14
|
setDefaultProviderIfMissingOrInvalid,
|
|
14
15
|
upsertBinding,
|
|
15
16
|
upsertConnectorInstance,
|
|
@@ -220,7 +221,7 @@ async function configureProviderSection(config: RawGatewayConfig, context?: Conf
|
|
|
220
221
|
throw new Error("Configure cancelled.");
|
|
221
222
|
}
|
|
222
223
|
next.providers.default = String(defaultProvider);
|
|
223
|
-
next.routes.
|
|
224
|
+
next.routes.default.provider = String(defaultProvider);
|
|
224
225
|
}
|
|
225
226
|
|
|
226
227
|
Object.assign(config, next);
|
|
@@ -321,11 +322,32 @@ async function configureRouteSection(config: RawGatewayConfig): Promise<void> {
|
|
|
321
322
|
const routeId = String(targetRoute) === "__new" ? await requiredText("New route ID", "main") : String(targetRoute);
|
|
322
323
|
const existing = routeItems[routeId];
|
|
323
324
|
|
|
324
|
-
const
|
|
325
|
+
const defaultProjectRoot = next.routes.default.projectRoot;
|
|
326
|
+
let projectRoot = existing?.projectRoot;
|
|
327
|
+
if (defaultProjectRoot) {
|
|
328
|
+
const projectRootMode = await select({
|
|
329
|
+
message: "projectRoot",
|
|
330
|
+
options: [
|
|
331
|
+
{ value: "__default", label: `Use route default (${defaultProjectRoot})` },
|
|
332
|
+
{ value: "__custom", label: "Set explicit projectRoot" },
|
|
333
|
+
],
|
|
334
|
+
initialValue: existing?.projectRoot ? "__custom" : "__default",
|
|
335
|
+
});
|
|
336
|
+
if (isCancel(projectRootMode)) {
|
|
337
|
+
cancel("Configure cancelled.");
|
|
338
|
+
throw new Error("Configure cancelled.");
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
projectRoot = projectRootMode === "__custom"
|
|
342
|
+
? await requiredText("projectRoot", existing?.projectRoot ?? defaultProjectRoot)
|
|
343
|
+
: undefined;
|
|
344
|
+
} else {
|
|
345
|
+
projectRoot = await requiredText("projectRoot", existing?.projectRoot ?? process.cwd());
|
|
346
|
+
}
|
|
325
347
|
const tools = await select({
|
|
326
348
|
message: "tools",
|
|
327
349
|
options: [
|
|
328
|
-
{ value: "__default", label: `Use route default (${next.routes.
|
|
350
|
+
{ value: "__default", label: `Use route default (${next.routes.default.tools ?? "full"})` },
|
|
329
351
|
{ value: "full", label: "full" },
|
|
330
352
|
{ value: "readonly", label: "readonly" },
|
|
331
353
|
],
|
|
@@ -339,7 +361,7 @@ async function configureRouteSection(config: RawGatewayConfig): Promise<void> {
|
|
|
339
361
|
const mentions = await select({
|
|
340
362
|
message: "mentions",
|
|
341
363
|
options: [
|
|
342
|
-
{ value: "__default", label: `Use route default (${next.routes.
|
|
364
|
+
{ value: "__default", label: `Use route default (${next.routes.default.mentions ?? "required"})` },
|
|
343
365
|
{ value: "required", label: "required" },
|
|
344
366
|
{ value: "optional", label: "optional" },
|
|
345
367
|
],
|
|
@@ -355,7 +377,7 @@ async function configureRouteSection(config: RawGatewayConfig): Promise<void> {
|
|
|
355
377
|
? await select({
|
|
356
378
|
message: "provider",
|
|
357
379
|
options: [
|
|
358
|
-
{ value: "__default", label: `Use route default (${(next.routes.
|
|
380
|
+
{ value: "__default", label: `Use route default (${(next.routes.default.provider ?? next.providers.default) || "(unset)"})` },
|
|
359
381
|
...providerIds.map((id) => ({ value: id, label: id })),
|
|
360
382
|
],
|
|
361
383
|
initialValue: existing?.provider ?? "__default",
|
|
@@ -370,7 +392,7 @@ async function configureRouteSection(config: RawGatewayConfig): Promise<void> {
|
|
|
370
392
|
const sandboxValue = await select({
|
|
371
393
|
message: "sandbox",
|
|
372
394
|
options: [
|
|
373
|
-
{ value: "__default", label: `Use route default (${next.routes.
|
|
395
|
+
{ value: "__default", label: `Use route default (${next.routes.default.sandbox ?? next.sandboxes.default})` },
|
|
374
396
|
...sandboxIds.map((id) => ({ value: id, label: id })),
|
|
375
397
|
],
|
|
376
398
|
initialValue: existing?.sandbox ?? "__default",
|
|
@@ -383,7 +405,7 @@ async function configureRouteSection(config: RawGatewayConfig): Promise<void> {
|
|
|
383
405
|
const systemPromptFile = await optionalText("systemPromptFile (optional)", existing?.systemPromptFile ?? "");
|
|
384
406
|
|
|
385
407
|
upsertRoute(next, routeId, {
|
|
386
|
-
projectRoot,
|
|
408
|
+
...(projectRoot ? { projectRoot } : {}),
|
|
387
409
|
...(tools !== "__default" ? { tools: String(tools) as "full" | "readonly" } : {}),
|
|
388
410
|
...(mentions !== "__default" ? { mentions: String(mentions) as "required" | "optional" } : {}),
|
|
389
411
|
...(providerValue !== "__default" ? { provider: String(providerValue) } : {}),
|
|
@@ -407,20 +429,43 @@ async function configureBindingSection(config: RawGatewayConfig): Promise<void>
|
|
|
407
429
|
}
|
|
408
430
|
|
|
409
431
|
const targetBinding = bindingChoices.length === 0
|
|
410
|
-
? "__new"
|
|
432
|
+
? (next.bindings.default ? "__default" : "__new")
|
|
411
433
|
: await select({
|
|
412
434
|
message: "Select binding",
|
|
413
435
|
options: [
|
|
436
|
+
{ value: "__default", label: next.bindings.default ? "Edit default direct-message binding" : "Create default direct-message binding" },
|
|
414
437
|
...bindingChoices.map((id) => ({ value: id, label: id })),
|
|
415
438
|
{ value: "__new", label: "Create new binding" },
|
|
416
439
|
],
|
|
417
|
-
initialValue: bindingChoices[0],
|
|
440
|
+
initialValue: next.bindings.default ? "__default" : bindingChoices[0],
|
|
418
441
|
});
|
|
419
442
|
if (isCancel(targetBinding)) {
|
|
420
443
|
cancel("Configure cancelled.");
|
|
421
444
|
throw new Error("Configure cancelled.");
|
|
422
445
|
}
|
|
423
446
|
|
|
447
|
+
const routeIds = Object.keys(next.routes.items).sort((a, b) => a.localeCompare(b));
|
|
448
|
+
if (String(targetBinding) === "__default") {
|
|
449
|
+
const defaultRouteId = await select({
|
|
450
|
+
message: "Default direct-message route",
|
|
451
|
+
options: routeIds.map((id) => ({ value: id, label: id })),
|
|
452
|
+
initialValue:
|
|
453
|
+
next.bindings.default?.route && routeIds.includes(next.bindings.default.route)
|
|
454
|
+
? next.bindings.default.route
|
|
455
|
+
: routeIds[0],
|
|
456
|
+
});
|
|
457
|
+
if (isCancel(defaultRouteId)) {
|
|
458
|
+
cancel("Configure cancelled.");
|
|
459
|
+
throw new Error("Configure cancelled.");
|
|
460
|
+
}
|
|
461
|
+
|
|
462
|
+
setDefaultBinding(next, {
|
|
463
|
+
route: String(defaultRouteId),
|
|
464
|
+
});
|
|
465
|
+
Object.assign(config, next);
|
|
466
|
+
return;
|
|
467
|
+
}
|
|
468
|
+
|
|
424
469
|
const bindingId = String(targetBinding) === "__new"
|
|
425
470
|
? await requiredText("New binding ID", "discord.main.main")
|
|
426
471
|
: String(targetBinding);
|
|
@@ -451,7 +496,6 @@ async function configureBindingSection(config: RawGatewayConfig): Promise<void>
|
|
|
451
496
|
}
|
|
452
497
|
|
|
453
498
|
const sourceId = await requiredText("source.id", existing?.source.id);
|
|
454
|
-
const routeIds = Object.keys(next.routes.items).sort((a, b) => a.localeCompare(b));
|
|
455
499
|
const routeId = await select({
|
|
456
500
|
message: "route",
|
|
457
501
|
options: routeIds.map((id) => ({ value: id, label: id })),
|
|
@@ -501,7 +545,7 @@ async function configureSandboxSection(config: RawGatewayConfig): Promise<void>
|
|
|
501
545
|
}
|
|
502
546
|
|
|
503
547
|
next.sandboxes.default = String(defaultSandbox);
|
|
504
|
-
next.routes.
|
|
548
|
+
next.routes.default.sandbox = String(defaultSandbox);
|
|
505
549
|
Object.assign(config, next);
|
|
506
550
|
}
|
|
507
551
|
|