@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
|
@@ -118,3 +118,152 @@ test("doctor reports invalid binding route references", async () => {
|
|
|
118
118
|
await rm(homeDir, { recursive: true, force: true });
|
|
119
119
|
}
|
|
120
120
|
});
|
|
121
|
+
|
|
122
|
+
test("doctor reports invalid default binding route references", async () => {
|
|
123
|
+
const homeDir = await mkdtemp(join(tmpdir(), "dobby-doctor-default-binding-"));
|
|
124
|
+
|
|
125
|
+
try {
|
|
126
|
+
const configPath = await writeTempHomeConfig(homeDir, {
|
|
127
|
+
extensions: { allowList: [] },
|
|
128
|
+
providers: {
|
|
129
|
+
default: "pi.main",
|
|
130
|
+
items: {
|
|
131
|
+
"pi.main": {
|
|
132
|
+
type: "provider.pi",
|
|
133
|
+
},
|
|
134
|
+
},
|
|
135
|
+
},
|
|
136
|
+
connectors: {
|
|
137
|
+
items: {
|
|
138
|
+
"discord.main": {
|
|
139
|
+
type: "connector.discord",
|
|
140
|
+
botName: "dobby-main",
|
|
141
|
+
botToken: "token",
|
|
142
|
+
},
|
|
143
|
+
},
|
|
144
|
+
},
|
|
145
|
+
sandboxes: {
|
|
146
|
+
default: "host.builtin",
|
|
147
|
+
items: {},
|
|
148
|
+
},
|
|
149
|
+
routes: {
|
|
150
|
+
defaults: {
|
|
151
|
+
projectRoot: process.cwd(),
|
|
152
|
+
provider: "pi.main",
|
|
153
|
+
sandbox: "host.builtin",
|
|
154
|
+
tools: "full",
|
|
155
|
+
mentions: "required",
|
|
156
|
+
},
|
|
157
|
+
items: {
|
|
158
|
+
main: {},
|
|
159
|
+
},
|
|
160
|
+
},
|
|
161
|
+
bindings: {
|
|
162
|
+
default: {
|
|
163
|
+
route: "missing-route",
|
|
164
|
+
},
|
|
165
|
+
items: {},
|
|
166
|
+
},
|
|
167
|
+
data: {
|
|
168
|
+
rootDir: "./data",
|
|
169
|
+
dedupTtlMs: 604800000,
|
|
170
|
+
},
|
|
171
|
+
});
|
|
172
|
+
|
|
173
|
+
const result = await runDoctorWithHome(homeDir, configPath);
|
|
174
|
+
assert.equal(result.code, 1);
|
|
175
|
+
assert.equal(result.output.includes("bindings.default.route") && result.output.includes("missing-route"), true);
|
|
176
|
+
} finally {
|
|
177
|
+
await rm(homeDir, { recursive: true, force: true });
|
|
178
|
+
}
|
|
179
|
+
});
|
|
180
|
+
|
|
181
|
+
test("doctor reports init template placeholders as errors and warnings", async () => {
|
|
182
|
+
const homeDir = await mkdtemp(join(tmpdir(), "dobby-doctor-placeholders-"));
|
|
183
|
+
|
|
184
|
+
try {
|
|
185
|
+
const configPath = await writeTempHomeConfig(homeDir, {
|
|
186
|
+
extensions: { allowList: [] },
|
|
187
|
+
providers: {
|
|
188
|
+
default: "pi.main",
|
|
189
|
+
items: {
|
|
190
|
+
"pi.main": {
|
|
191
|
+
type: "provider.pi",
|
|
192
|
+
model: "REPLACE_WITH_PROVIDER_MODEL_ID",
|
|
193
|
+
baseUrl: "REPLACE_WITH_PROVIDER_BASE_URL",
|
|
194
|
+
apiKey: "REPLACE_WITH_PROVIDER_API_KEY_OR_ENV",
|
|
195
|
+
},
|
|
196
|
+
},
|
|
197
|
+
},
|
|
198
|
+
connectors: {
|
|
199
|
+
items: {
|
|
200
|
+
"discord.main": {
|
|
201
|
+
type: "connector.discord",
|
|
202
|
+
botName: "dobby-main",
|
|
203
|
+
botToken: "REPLACE_WITH_DISCORD_BOT_TOKEN",
|
|
204
|
+
},
|
|
205
|
+
"feishu.main": {
|
|
206
|
+
type: "connector.feishu",
|
|
207
|
+
appId: "REPLACE_WITH_FEISHU_APP_ID",
|
|
208
|
+
appSecret: "REPLACE_WITH_FEISHU_APP_SECRET",
|
|
209
|
+
},
|
|
210
|
+
},
|
|
211
|
+
},
|
|
212
|
+
sandboxes: {
|
|
213
|
+
default: "host.builtin",
|
|
214
|
+
items: {},
|
|
215
|
+
},
|
|
216
|
+
routes: {
|
|
217
|
+
defaults: {
|
|
218
|
+
provider: "pi.main",
|
|
219
|
+
sandbox: "host.builtin",
|
|
220
|
+
tools: "full",
|
|
221
|
+
mentions: "required",
|
|
222
|
+
},
|
|
223
|
+
items: {
|
|
224
|
+
main: {
|
|
225
|
+
projectRoot: "./REPLACE_WITH_PROJECT_ROOT",
|
|
226
|
+
},
|
|
227
|
+
},
|
|
228
|
+
},
|
|
229
|
+
bindings: {
|
|
230
|
+
items: {
|
|
231
|
+
"discord.main.main": {
|
|
232
|
+
connector: "discord.main",
|
|
233
|
+
source: {
|
|
234
|
+
type: "channel",
|
|
235
|
+
id: "YOUR_DISCORD_CHANNEL_ID",
|
|
236
|
+
},
|
|
237
|
+
route: "main",
|
|
238
|
+
},
|
|
239
|
+
"feishu.main.main": {
|
|
240
|
+
connector: "feishu.main",
|
|
241
|
+
source: {
|
|
242
|
+
type: "chat",
|
|
243
|
+
id: "YOUR_FEISHU_CHAT_ID",
|
|
244
|
+
},
|
|
245
|
+
route: "main",
|
|
246
|
+
},
|
|
247
|
+
},
|
|
248
|
+
},
|
|
249
|
+
data: {
|
|
250
|
+
rootDir: "./data",
|
|
251
|
+
dedupTtlMs: 604800000,
|
|
252
|
+
},
|
|
253
|
+
});
|
|
254
|
+
|
|
255
|
+
const result = await runDoctorWithHome(homeDir, configPath);
|
|
256
|
+
assert.equal(result.code, 1);
|
|
257
|
+
assert.equal(result.output.includes("providers.items['pi.main'].model still uses placeholder value"), true);
|
|
258
|
+
assert.equal(result.output.includes("providers.items['pi.main'].baseUrl still uses placeholder value"), true);
|
|
259
|
+
assert.equal(result.output.includes("providers.items['pi.main'].apiKey still uses placeholder value"), true);
|
|
260
|
+
assert.equal(result.output.includes("connectors.items['discord.main'].botToken still uses placeholder value"), true);
|
|
261
|
+
assert.equal(result.output.includes("connectors.items['feishu.main'].appId still uses placeholder value"), true);
|
|
262
|
+
assert.equal(result.output.includes("connectors.items['feishu.main'].appSecret still uses placeholder value"), true);
|
|
263
|
+
assert.equal(result.output.includes("routes.items['main'].projectRoot still uses placeholder value"), true);
|
|
264
|
+
assert.equal(result.output.includes("bindings.items['discord.main.main'].source.id still uses placeholder value"), true);
|
|
265
|
+
assert.equal(result.output.includes("bindings.items['feishu.main.main'].source.id still uses placeholder value"), true);
|
|
266
|
+
} finally {
|
|
267
|
+
await rm(homeDir, { recursive: true, force: true });
|
|
268
|
+
}
|
|
269
|
+
});
|
|
@@ -2,95 +2,143 @@ import assert from "node:assert/strict";
|
|
|
2
2
|
import test from "node:test";
|
|
3
3
|
import { createInitSelectionConfig } from "../shared/init-catalog.js";
|
|
4
4
|
|
|
5
|
-
test("createInitSelectionConfig
|
|
6
|
-
const selected = createInitSelectionConfig(["provider.pi"], "connector.discord", {
|
|
7
|
-
routeId: "main",
|
|
8
|
-
projectRoot: "/tmp/project",
|
|
9
|
-
allowAllMessages: false,
|
|
10
|
-
botName: "dobby-main",
|
|
11
|
-
botToken: "token-abc",
|
|
12
|
-
channelId: "123",
|
|
5
|
+
test("createInitSelectionConfig writes Discord starter template for provider.pi", () => {
|
|
6
|
+
const selected = createInitSelectionConfig(["provider.pi"], ["connector.discord"], {
|
|
13
7
|
routeProviderChoiceId: "provider.pi",
|
|
8
|
+
defaultProjectRoot: "/Users/oolong/workspace/dobby",
|
|
14
9
|
});
|
|
15
10
|
|
|
16
|
-
assert.deepEqual(selected.connectorConfig, {
|
|
17
|
-
botName: "dobby-main",
|
|
18
|
-
botToken: "token-abc",
|
|
19
|
-
reconnectStaleMs: 60_000,
|
|
20
|
-
reconnectCheckIntervalMs: 10_000,
|
|
21
|
-
});
|
|
22
11
|
assert.deepEqual(selected.providerChoiceIds, ["provider.pi"]);
|
|
23
|
-
assert.
|
|
12
|
+
assert.deepEqual(selected.connectorChoiceIds, ["connector.discord"]);
|
|
13
|
+
assert.equal(selected.routeId, "main");
|
|
24
14
|
assert.equal(selected.providerInstanceId, "pi.main");
|
|
25
|
-
assert.
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
15
|
+
assert.deepEqual(selected.providerInstances, [{
|
|
16
|
+
choiceId: "provider.pi",
|
|
17
|
+
instanceId: "pi.main",
|
|
18
|
+
contributionId: "provider.pi",
|
|
19
|
+
config: {
|
|
20
|
+
model: "REPLACE_WITH_PROVIDER_MODEL_ID",
|
|
21
|
+
baseUrl: "REPLACE_WITH_PROVIDER_BASE_URL",
|
|
22
|
+
apiKey: "REPLACE_WITH_PROVIDER_API_KEY_OR_ENV",
|
|
23
|
+
},
|
|
24
|
+
}]);
|
|
25
|
+
assert.deepEqual(selected.connectorInstances, [{
|
|
26
|
+
choiceId: "connector.discord",
|
|
27
|
+
instanceId: "discord.main",
|
|
28
|
+
contributionId: "connector.discord",
|
|
29
|
+
config: {
|
|
30
|
+
botName: "dobby-main",
|
|
31
|
+
botToken: "REPLACE_WITH_DISCORD_BOT_TOKEN",
|
|
32
|
+
reconnectStaleMs: 60_000,
|
|
33
|
+
reconnectCheckIntervalMs: 10_000,
|
|
34
34
|
},
|
|
35
|
+
}]);
|
|
36
|
+
assert.deepEqual(selected.routeDefaults, {
|
|
37
|
+
projectRoot: "/Users/oolong/workspace/dobby",
|
|
38
|
+
tools: "full",
|
|
39
|
+
mentions: "required",
|
|
40
|
+
provider: "pi.main",
|
|
41
|
+
sandbox: "host.builtin",
|
|
42
|
+
});
|
|
43
|
+
assert.deepEqual(selected.routeProfile, {});
|
|
44
|
+
assert.deepEqual(selected.defaultBinding, {
|
|
35
45
|
route: "main",
|
|
36
46
|
});
|
|
47
|
+
assert.deepEqual(selected.bindings, [{
|
|
48
|
+
id: "discord.main.main",
|
|
49
|
+
config: {
|
|
50
|
+
connector: "discord.main",
|
|
51
|
+
source: {
|
|
52
|
+
type: "channel",
|
|
53
|
+
id: "YOUR_DISCORD_CHANNEL_ID",
|
|
54
|
+
},
|
|
55
|
+
route: "main",
|
|
56
|
+
},
|
|
57
|
+
}]);
|
|
37
58
|
});
|
|
38
59
|
|
|
39
|
-
test("createInitSelectionConfig
|
|
40
|
-
const selected = createInitSelectionConfig(["provider.claude-cli"], "connector.
|
|
41
|
-
routeId: "support",
|
|
42
|
-
projectRoot: "/tmp/project",
|
|
43
|
-
allowAllMessages: true,
|
|
44
|
-
botName: "ops-bot",
|
|
45
|
-
botToken: "token-xyz",
|
|
46
|
-
channelId: "999",
|
|
60
|
+
test("createInitSelectionConfig writes Feishu starter template for provider.claude-cli", () => {
|
|
61
|
+
const selected = createInitSelectionConfig(["provider.claude-cli"], ["connector.feishu"], {
|
|
47
62
|
routeProviderChoiceId: "provider.claude-cli",
|
|
63
|
+
defaultProjectRoot: "/Users/oolong/workspace/dobby",
|
|
48
64
|
});
|
|
49
65
|
|
|
50
|
-
assert.deepEqual(selected.connectorConfig, {
|
|
51
|
-
botName: "ops-bot",
|
|
52
|
-
botToken: "token-xyz",
|
|
53
|
-
reconnectStaleMs: 60_000,
|
|
54
|
-
reconnectCheckIntervalMs: 10_000,
|
|
55
|
-
});
|
|
56
66
|
assert.deepEqual(selected.providerChoiceIds, ["provider.claude-cli"]);
|
|
57
|
-
assert.
|
|
67
|
+
assert.deepEqual(selected.connectorChoiceIds, ["connector.feishu"]);
|
|
68
|
+
assert.equal(selected.routeId, "main");
|
|
58
69
|
assert.equal(selected.providerInstanceId, "claude-cli.main");
|
|
59
|
-
assert.
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
70
|
+
assert.deepEqual(selected.connectorInstances, [{
|
|
71
|
+
choiceId: "connector.feishu",
|
|
72
|
+
instanceId: "feishu.main",
|
|
73
|
+
contributionId: "connector.feishu",
|
|
74
|
+
config: {
|
|
75
|
+
appId: "REPLACE_WITH_FEISHU_APP_ID",
|
|
76
|
+
appSecret: "REPLACE_WITH_FEISHU_APP_SECRET",
|
|
77
|
+
domain: "feishu",
|
|
78
|
+
messageFormat: "card_markdown",
|
|
79
|
+
replyMode: "direct",
|
|
80
|
+
downloadAttachments: true,
|
|
67
81
|
},
|
|
68
|
-
|
|
69
|
-
|
|
82
|
+
}]);
|
|
83
|
+
assert.deepEqual(selected.bindings, [{
|
|
84
|
+
id: "feishu.main.main",
|
|
85
|
+
config: {
|
|
86
|
+
connector: "feishu.main",
|
|
87
|
+
source: {
|
|
88
|
+
type: "chat",
|
|
89
|
+
id: "YOUR_FEISHU_CHAT_ID",
|
|
90
|
+
},
|
|
91
|
+
route: "main",
|
|
92
|
+
},
|
|
93
|
+
}]);
|
|
70
94
|
});
|
|
71
95
|
|
|
72
|
-
test("createInitSelectionConfig supports multiple providers and
|
|
73
|
-
const selected = createInitSelectionConfig(
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
});
|
|
96
|
+
test("createInitSelectionConfig supports multiple providers and connectors with one default provider", () => {
|
|
97
|
+
const selected = createInitSelectionConfig(
|
|
98
|
+
["provider.pi", "provider.claude-cli"],
|
|
99
|
+
["connector.discord", "connector.feishu"],
|
|
100
|
+
{
|
|
101
|
+
routeProviderChoiceId: "provider.claude-cli",
|
|
102
|
+
defaultProjectRoot: "/Users/oolong/workspace/dobby",
|
|
103
|
+
},
|
|
104
|
+
);
|
|
82
105
|
|
|
83
106
|
assert.deepEqual(selected.providerChoiceIds, ["provider.pi", "provider.claude-cli"]);
|
|
84
|
-
assert.deepEqual(
|
|
85
|
-
selected.providerInstances.map((item) => item.instanceId),
|
|
86
|
-
["pi.main", "claude-cli.main"],
|
|
87
|
-
);
|
|
107
|
+
assert.deepEqual(selected.connectorChoiceIds, ["connector.discord", "connector.feishu"]);
|
|
88
108
|
assert.deepEqual(selected.extensionPackages, [
|
|
89
109
|
"@dobby.ai/provider-pi",
|
|
90
110
|
"@dobby.ai/provider-claude-cli",
|
|
91
111
|
"@dobby.ai/connector-discord",
|
|
112
|
+
"@dobby.ai/connector-feishu",
|
|
92
113
|
]);
|
|
93
114
|
assert.equal(selected.providerInstanceId, "claude-cli.main");
|
|
94
|
-
assert.equal(selected.
|
|
115
|
+
assert.equal(selected.routeDefaults.provider, "claude-cli.main");
|
|
95
116
|
assert.equal(selected.routeProviderChoiceId, "provider.claude-cli");
|
|
117
|
+
assert.deepEqual(selected.defaultBinding, {
|
|
118
|
+
route: "main",
|
|
119
|
+
});
|
|
120
|
+
assert.deepEqual(selected.bindings, [
|
|
121
|
+
{
|
|
122
|
+
id: "discord.main.main",
|
|
123
|
+
config: {
|
|
124
|
+
connector: "discord.main",
|
|
125
|
+
source: {
|
|
126
|
+
type: "channel",
|
|
127
|
+
id: "YOUR_DISCORD_CHANNEL_ID",
|
|
128
|
+
},
|
|
129
|
+
route: "main",
|
|
130
|
+
},
|
|
131
|
+
},
|
|
132
|
+
{
|
|
133
|
+
id: "feishu.main.main",
|
|
134
|
+
config: {
|
|
135
|
+
connector: "feishu.main",
|
|
136
|
+
source: {
|
|
137
|
+
type: "chat",
|
|
138
|
+
id: "YOUR_FEISHU_CHAT_ID",
|
|
139
|
+
},
|
|
140
|
+
route: "main",
|
|
141
|
+
},
|
|
142
|
+
},
|
|
143
|
+
]);
|
|
96
144
|
});
|
|
@@ -38,7 +38,7 @@ test("init help has no merge/overwrite flags", () => {
|
|
|
38
38
|
assert.equal(help.includes("--config"), false);
|
|
39
39
|
});
|
|
40
40
|
|
|
41
|
-
test("config help shows
|
|
41
|
+
test("config help shows read-only inspect commands and schema", () => {
|
|
42
42
|
const program = buildProgram();
|
|
43
43
|
const configCommand = program.commands.find((command) => command.name() === "config");
|
|
44
44
|
assert.ok(configCommand);
|
|
@@ -46,8 +46,8 @@ test("config help shows show/list/edit and schema", () => {
|
|
|
46
46
|
const help = configCommand.helpInformation();
|
|
47
47
|
assert.match(help, /show \[options\] \[section\]/);
|
|
48
48
|
assert.match(help, /list \[options\] \[section\]/);
|
|
49
|
-
assert.match(help, /edit \[options\]/);
|
|
50
49
|
assert.match(help, /schema/);
|
|
50
|
+
assert.equal(help.includes("edit"), false);
|
|
51
51
|
|
|
52
52
|
assert.equal(help.includes("get"), false);
|
|
53
53
|
assert.equal(help.includes("set"), false);
|
|
@@ -79,35 +79,17 @@ test("cron help shows core subcommands", () => {
|
|
|
79
79
|
assert.match(help, /remove \[options\] <jobId>/);
|
|
80
80
|
});
|
|
81
81
|
|
|
82
|
-
test("
|
|
82
|
+
test("top-level help keeps bootstrap, inspect, install, validate, and ops commands only", () => {
|
|
83
83
|
const program = buildProgram();
|
|
84
|
-
const
|
|
85
|
-
assert.
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
assert.match(help, /
|
|
89
|
-
assert.match(help, /
|
|
90
|
-
assert.match(help, /
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
const routeCommand = program.commands.find((command) => command.name() === "route");
|
|
96
|
-
assert.ok(routeCommand);
|
|
97
|
-
|
|
98
|
-
const setCommand = routeCommand.commands.find((command) => command.name() === "set");
|
|
99
|
-
const removeCommand = routeCommand.commands.find((command) => command.name() === "remove");
|
|
100
|
-
assert.ok(setCommand);
|
|
101
|
-
assert.ok(removeCommand);
|
|
102
|
-
|
|
103
|
-
const setHelp = setCommand.helpInformation();
|
|
104
|
-
const removeHelp = removeCommand.helpInformation();
|
|
105
|
-
assert.match(setHelp, /--provider <id>/);
|
|
106
|
-
assert.match(setHelp, /--sandbox <id>/);
|
|
107
|
-
assert.match(setHelp, /--mentions <policy>/);
|
|
108
|
-
assert.equal(setHelp.includes("--provider-id"), false);
|
|
109
|
-
assert.equal(setHelp.includes("--sandbox-id"), false);
|
|
110
|
-
assert.equal(setHelp.includes("--mentions-only"), false);
|
|
111
|
-
assert.equal(setHelp.includes("--default"), false);
|
|
112
|
-
assert.match(removeHelp, /--cascade-bindings/);
|
|
84
|
+
const help = program.helpInformation();
|
|
85
|
+
assert.match(help, /start/);
|
|
86
|
+
assert.match(help, /init/);
|
|
87
|
+
assert.match(help, /config/);
|
|
88
|
+
assert.match(help, /extension/);
|
|
89
|
+
assert.match(help, /doctor/);
|
|
90
|
+
assert.match(help, /cron/);
|
|
91
|
+
assert.equal(help.includes("configure"), false);
|
|
92
|
+
assert.equal(help.includes("bot"), false);
|
|
93
|
+
assert.equal(help.includes("binding"), false);
|
|
94
|
+
assert.equal(help.includes("route"), false);
|
|
113
95
|
});
|
|
@@ -3,7 +3,7 @@ import { mkdir, mkdtemp, rm, writeFile } from "node:fs/promises";
|
|
|
3
3
|
import { tmpdir } from "node:os";
|
|
4
4
|
import { dirname, join } from "node:path";
|
|
5
5
|
import test from "node:test";
|
|
6
|
-
import { loadGatewayConfig } from "../../core/routing.js";
|
|
6
|
+
import { BindingResolver, loadGatewayConfig } from "../../core/routing.js";
|
|
7
7
|
|
|
8
8
|
async function writeTempConfig(payload: unknown): Promise<string> {
|
|
9
9
|
const dir = await mkdtemp(join(tmpdir(), "dobby-routing-"));
|
|
@@ -17,7 +17,7 @@ async function writeRepoTempConfig(payload: unknown): Promise<{ repoRoot: string
|
|
|
17
17
|
const configDir = join(repoRoot, "config");
|
|
18
18
|
await mkdir(configDir, { recursive: true });
|
|
19
19
|
await mkdir(join(repoRoot, "scripts"), { recursive: true });
|
|
20
|
-
await writeFile(join(repoRoot, "package.json"), JSON.stringify({ name: "dobby" }), "utf-8");
|
|
20
|
+
await writeFile(join(repoRoot, "package.json"), JSON.stringify({ name: "@dobby.ai/dobby" }), "utf-8");
|
|
21
21
|
await writeFile(join(repoRoot, "scripts", "local-extensions.mjs"), "#!/usr/bin/env node\n", "utf-8");
|
|
22
22
|
|
|
23
23
|
const configPath = join(configDir, "gateway.json");
|
|
@@ -50,7 +50,7 @@ function validConfig(): Record<string, unknown> {
|
|
|
50
50
|
items: {},
|
|
51
51
|
},
|
|
52
52
|
routes: {
|
|
53
|
-
|
|
53
|
+
default: {
|
|
54
54
|
provider: "pi.main",
|
|
55
55
|
sandbox: "host.builtin",
|
|
56
56
|
tools: "full",
|
|
@@ -91,7 +91,7 @@ test("loadGatewayConfig applies route defaults and resolves relative paths", asy
|
|
|
91
91
|
const configDir = dirname(configPath);
|
|
92
92
|
|
|
93
93
|
assert.equal(loaded.providers.default, "pi.main");
|
|
94
|
-
assert.deepEqual(loaded.routes.
|
|
94
|
+
assert.deepEqual(loaded.routes.default, {
|
|
95
95
|
provider: "pi.main",
|
|
96
96
|
sandbox: "host.builtin",
|
|
97
97
|
tools: "full",
|
|
@@ -134,6 +134,78 @@ test("loadGatewayConfig resolves data.rootDir from repo root for repo-local conf
|
|
|
134
134
|
}
|
|
135
135
|
});
|
|
136
136
|
|
|
137
|
+
test("loadGatewayConfig applies routes.default.projectRoot and bindings.default for direct messages", async () => {
|
|
138
|
+
const payload = validConfig();
|
|
139
|
+
payload.routes = {
|
|
140
|
+
default: {
|
|
141
|
+
projectRoot: "./workspace/default-root",
|
|
142
|
+
provider: "pi.main",
|
|
143
|
+
sandbox: "host.builtin",
|
|
144
|
+
tools: "full",
|
|
145
|
+
mentions: "required",
|
|
146
|
+
},
|
|
147
|
+
items: {
|
|
148
|
+
main: {},
|
|
149
|
+
},
|
|
150
|
+
};
|
|
151
|
+
payload.bindings = {
|
|
152
|
+
default: {
|
|
153
|
+
route: "main",
|
|
154
|
+
},
|
|
155
|
+
items: {},
|
|
156
|
+
};
|
|
157
|
+
|
|
158
|
+
const configPath = await writeTempConfig(payload);
|
|
159
|
+
|
|
160
|
+
try {
|
|
161
|
+
const loaded = await loadGatewayConfig(configPath);
|
|
162
|
+
const configDir = dirname(configPath);
|
|
163
|
+
const resolver = new BindingResolver(loaded.bindings);
|
|
164
|
+
|
|
165
|
+
assert.deepEqual(loaded.routes.default, {
|
|
166
|
+
projectRoot: join(configDir, "workspace/default-root"),
|
|
167
|
+
provider: "pi.main",
|
|
168
|
+
sandbox: "host.builtin",
|
|
169
|
+
tools: "full",
|
|
170
|
+
mentions: "required",
|
|
171
|
+
});
|
|
172
|
+
assert.deepEqual(loaded.routes.items.main, {
|
|
173
|
+
projectRoot: join(configDir, "workspace/default-root"),
|
|
174
|
+
provider: "pi.main",
|
|
175
|
+
sandbox: "host.builtin",
|
|
176
|
+
tools: "full",
|
|
177
|
+
mentions: "required",
|
|
178
|
+
});
|
|
179
|
+
assert.deepEqual(loaded.bindings.default, {
|
|
180
|
+
route: "main",
|
|
181
|
+
});
|
|
182
|
+
assert.equal(
|
|
183
|
+
resolver.resolve(
|
|
184
|
+
"discord.main",
|
|
185
|
+
{
|
|
186
|
+
type: "channel",
|
|
187
|
+
id: "dm-123",
|
|
188
|
+
},
|
|
189
|
+
{ isDirectMessage: true },
|
|
190
|
+
)?.config.route,
|
|
191
|
+
"main",
|
|
192
|
+
);
|
|
193
|
+
assert.equal(
|
|
194
|
+
resolver.resolve(
|
|
195
|
+
"discord.main",
|
|
196
|
+
{
|
|
197
|
+
type: "channel",
|
|
198
|
+
id: "dm-123",
|
|
199
|
+
},
|
|
200
|
+
{ isDirectMessage: false },
|
|
201
|
+
),
|
|
202
|
+
null,
|
|
203
|
+
);
|
|
204
|
+
} finally {
|
|
205
|
+
await rm(dirname(configPath), { recursive: true, force: true });
|
|
206
|
+
}
|
|
207
|
+
});
|
|
208
|
+
|
|
137
209
|
test("loadGatewayConfig rejects connector fields reserved by the host", async () => {
|
|
138
210
|
const payload = validConfig();
|
|
139
211
|
payload.connectors = {
|
package/src/core/gateway.ts
CHANGED
|
@@ -229,7 +229,9 @@ export class Gateway {
|
|
|
229
229
|
};
|
|
230
230
|
}
|
|
231
231
|
|
|
232
|
-
const binding = this.options.bindingResolver.resolve(message.connectorId, message.source
|
|
232
|
+
const binding = this.options.bindingResolver.resolve(message.connectorId, message.source, {
|
|
233
|
+
isDirectMessage: message.isDirectMessage,
|
|
234
|
+
});
|
|
233
235
|
if (!binding) {
|
|
234
236
|
if (handling.origin === "connector") {
|
|
235
237
|
this.options.logger.debug(
|