@dobby.ai/dobby 0.1.1 → 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 +20 -7
- package/dist/src/agent/event-forwarder.js +185 -16
- package/dist/src/cli/commands/cron.js +39 -35
- package/dist/src/cli/program.js +0 -6
- 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/package.json +9 -3
- package/.env.example +0 -8
- package/AGENTS.md +0 -267
- package/ROADMAP.md +0 -34
- package/config/cron.example.json +0 -9
- package/config/gateway.example.json +0 -132
- package/dist/plugins/connector-discord/src/mapper.js +0 -75
- 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/discord-mapper.test.js +0 -90
- package/dist/src/cli/tests/doctor.test.js +0 -252
- package/dist/src/cli/tests/init-catalog.test.js +0 -134
- package/dist/src/cli/tests/program-options.test.js +0 -78
- package/dist/src/cli/tests/routing-config.test.js +0 -254
- package/dist/src/core/tests/control-command.test.js +0 -17
- 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 -243
- 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 -345
- package/plugins/connector-discord/src/contribution.ts +0 -21
- package/plugins/connector-discord/src/mapper.ts +0 -101
- 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 -606
- 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 -331
- package/src/cli/commands/extension.ts +0 -207
- package/src/cli/commands/init.ts +0 -211
- package/src/cli/commands/start.ts +0 -223
- package/src/cli/commands/topology.ts +0 -415
- package/src/cli/index.ts +0 -9
- package/src/cli/program.ts +0 -314
- package/src/cli/shared/config-io.ts +0 -245
- package/src/cli/shared/config-mutators.ts +0 -470
- package/src/cli/shared/config-schema.ts +0 -228
- package/src/cli/shared/config-types.ts +0 -129
- package/src/cli/shared/configure-sections.ts +0 -595
- package/src/cli/shared/discord-config.ts +0 -14
- package/src/cli/shared/init-catalog.ts +0 -249
- package/src/cli/shared/local-extension-specs.ts +0 -108
- package/src/cli/shared/runtime.ts +0 -33
- package/src/cli/shared/schema-prompts.ts +0 -443
- 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/discord-mapper.test.ts +0 -128
- package/src/cli/tests/doctor.test.ts +0 -269
- package/src/cli/tests/init-catalog.test.ts +0 -144
- package/src/cli/tests/program-options.test.ts +0 -95
- package/src/cli/tests/routing-config.test.ts +0 -281
- package/src/core/control-command.ts +0 -12
- package/src/core/dedup-store.ts +0 -103
- package/src/core/gateway.ts +0 -609
- package/src/core/routing.ts +0 -404
- 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 -324
- 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/src/shared/dobby-repo.ts +0 -48
- package/tsconfig.json +0 -18
|
@@ -1,134 +0,0 @@
|
|
|
1
|
-
import assert from "node:assert/strict";
|
|
2
|
-
import test from "node:test";
|
|
3
|
-
import { createInitSelectionConfig } from "../shared/init-catalog.js";
|
|
4
|
-
test("createInitSelectionConfig writes Discord starter template for provider.pi", () => {
|
|
5
|
-
const selected = createInitSelectionConfig(["provider.pi"], ["connector.discord"], {
|
|
6
|
-
routeProviderChoiceId: "provider.pi",
|
|
7
|
-
defaultProjectRoot: "/Users/oolong/workspace/dobby",
|
|
8
|
-
});
|
|
9
|
-
assert.deepEqual(selected.providerChoiceIds, ["provider.pi"]);
|
|
10
|
-
assert.deepEqual(selected.connectorChoiceIds, ["connector.discord"]);
|
|
11
|
-
assert.equal(selected.routeId, "main");
|
|
12
|
-
assert.equal(selected.providerInstanceId, "pi.main");
|
|
13
|
-
assert.deepEqual(selected.providerInstances, [{
|
|
14
|
-
choiceId: "provider.pi",
|
|
15
|
-
instanceId: "pi.main",
|
|
16
|
-
contributionId: "provider.pi",
|
|
17
|
-
config: {
|
|
18
|
-
model: "REPLACE_WITH_PROVIDER_MODEL_ID",
|
|
19
|
-
baseUrl: "REPLACE_WITH_PROVIDER_BASE_URL",
|
|
20
|
-
apiKey: "REPLACE_WITH_PROVIDER_API_KEY_OR_ENV",
|
|
21
|
-
},
|
|
22
|
-
}]);
|
|
23
|
-
assert.deepEqual(selected.connectorInstances, [{
|
|
24
|
-
choiceId: "connector.discord",
|
|
25
|
-
instanceId: "discord.main",
|
|
26
|
-
contributionId: "connector.discord",
|
|
27
|
-
config: {
|
|
28
|
-
botName: "dobby-main",
|
|
29
|
-
botToken: "REPLACE_WITH_DISCORD_BOT_TOKEN",
|
|
30
|
-
reconnectStaleMs: 60_000,
|
|
31
|
-
reconnectCheckIntervalMs: 10_000,
|
|
32
|
-
},
|
|
33
|
-
}]);
|
|
34
|
-
assert.deepEqual(selected.routeDefaults, {
|
|
35
|
-
projectRoot: "/Users/oolong/workspace/dobby",
|
|
36
|
-
tools: "full",
|
|
37
|
-
mentions: "required",
|
|
38
|
-
provider: "pi.main",
|
|
39
|
-
sandbox: "host.builtin",
|
|
40
|
-
});
|
|
41
|
-
assert.deepEqual(selected.routeProfile, {});
|
|
42
|
-
assert.deepEqual(selected.defaultBinding, {
|
|
43
|
-
route: "main",
|
|
44
|
-
});
|
|
45
|
-
assert.deepEqual(selected.bindings, [{
|
|
46
|
-
id: "discord.main.main",
|
|
47
|
-
config: {
|
|
48
|
-
connector: "discord.main",
|
|
49
|
-
source: {
|
|
50
|
-
type: "channel",
|
|
51
|
-
id: "YOUR_DISCORD_CHANNEL_ID",
|
|
52
|
-
},
|
|
53
|
-
route: "main",
|
|
54
|
-
},
|
|
55
|
-
}]);
|
|
56
|
-
});
|
|
57
|
-
test("createInitSelectionConfig writes Feishu starter template for provider.claude-cli", () => {
|
|
58
|
-
const selected = createInitSelectionConfig(["provider.claude-cli"], ["connector.feishu"], {
|
|
59
|
-
routeProviderChoiceId: "provider.claude-cli",
|
|
60
|
-
defaultProjectRoot: "/Users/oolong/workspace/dobby",
|
|
61
|
-
});
|
|
62
|
-
assert.deepEqual(selected.providerChoiceIds, ["provider.claude-cli"]);
|
|
63
|
-
assert.deepEqual(selected.connectorChoiceIds, ["connector.feishu"]);
|
|
64
|
-
assert.equal(selected.routeId, "main");
|
|
65
|
-
assert.equal(selected.providerInstanceId, "claude-cli.main");
|
|
66
|
-
assert.deepEqual(selected.connectorInstances, [{
|
|
67
|
-
choiceId: "connector.feishu",
|
|
68
|
-
instanceId: "feishu.main",
|
|
69
|
-
contributionId: "connector.feishu",
|
|
70
|
-
config: {
|
|
71
|
-
appId: "REPLACE_WITH_FEISHU_APP_ID",
|
|
72
|
-
appSecret: "REPLACE_WITH_FEISHU_APP_SECRET",
|
|
73
|
-
domain: "feishu",
|
|
74
|
-
messageFormat: "card_markdown",
|
|
75
|
-
replyMode: "direct",
|
|
76
|
-
downloadAttachments: true,
|
|
77
|
-
},
|
|
78
|
-
}]);
|
|
79
|
-
assert.deepEqual(selected.bindings, [{
|
|
80
|
-
id: "feishu.main.main",
|
|
81
|
-
config: {
|
|
82
|
-
connector: "feishu.main",
|
|
83
|
-
source: {
|
|
84
|
-
type: "chat",
|
|
85
|
-
id: "YOUR_FEISHU_CHAT_ID",
|
|
86
|
-
},
|
|
87
|
-
route: "main",
|
|
88
|
-
},
|
|
89
|
-
}]);
|
|
90
|
-
});
|
|
91
|
-
test("createInitSelectionConfig supports multiple providers and connectors with one default provider", () => {
|
|
92
|
-
const selected = createInitSelectionConfig(["provider.pi", "provider.claude-cli"], ["connector.discord", "connector.feishu"], {
|
|
93
|
-
routeProviderChoiceId: "provider.claude-cli",
|
|
94
|
-
defaultProjectRoot: "/Users/oolong/workspace/dobby",
|
|
95
|
-
});
|
|
96
|
-
assert.deepEqual(selected.providerChoiceIds, ["provider.pi", "provider.claude-cli"]);
|
|
97
|
-
assert.deepEqual(selected.connectorChoiceIds, ["connector.discord", "connector.feishu"]);
|
|
98
|
-
assert.deepEqual(selected.extensionPackages, [
|
|
99
|
-
"@dobby.ai/provider-pi",
|
|
100
|
-
"@dobby.ai/provider-claude-cli",
|
|
101
|
-
"@dobby.ai/connector-discord",
|
|
102
|
-
"@dobby.ai/connector-feishu",
|
|
103
|
-
]);
|
|
104
|
-
assert.equal(selected.providerInstanceId, "claude-cli.main");
|
|
105
|
-
assert.equal(selected.routeDefaults.provider, "claude-cli.main");
|
|
106
|
-
assert.equal(selected.routeProviderChoiceId, "provider.claude-cli");
|
|
107
|
-
assert.deepEqual(selected.defaultBinding, {
|
|
108
|
-
route: "main",
|
|
109
|
-
});
|
|
110
|
-
assert.deepEqual(selected.bindings, [
|
|
111
|
-
{
|
|
112
|
-
id: "discord.main.main",
|
|
113
|
-
config: {
|
|
114
|
-
connector: "discord.main",
|
|
115
|
-
source: {
|
|
116
|
-
type: "channel",
|
|
117
|
-
id: "YOUR_DISCORD_CHANNEL_ID",
|
|
118
|
-
},
|
|
119
|
-
route: "main",
|
|
120
|
-
},
|
|
121
|
-
},
|
|
122
|
-
{
|
|
123
|
-
id: "feishu.main.main",
|
|
124
|
-
config: {
|
|
125
|
-
connector: "feishu.main",
|
|
126
|
-
source: {
|
|
127
|
-
type: "chat",
|
|
128
|
-
id: "YOUR_FEISHU_CHAT_ID",
|
|
129
|
-
},
|
|
130
|
-
route: "main",
|
|
131
|
-
},
|
|
132
|
-
},
|
|
133
|
-
]);
|
|
134
|
-
});
|
|
@@ -1,78 +0,0 @@
|
|
|
1
|
-
import assert from "node:assert/strict";
|
|
2
|
-
import test from "node:test";
|
|
3
|
-
import { CommanderError } from "commander";
|
|
4
|
-
import { buildProgram } from "../program.js";
|
|
5
|
-
test("CLI rejects --config option", async () => {
|
|
6
|
-
const program = buildProgram();
|
|
7
|
-
program.configureOutput({
|
|
8
|
-
writeErr: () => { },
|
|
9
|
-
writeOut: () => { },
|
|
10
|
-
});
|
|
11
|
-
program.exitOverride();
|
|
12
|
-
await assert.rejects(program.parseAsync(["node", "dobby", "--config", "./config/gateway.json"]), (error) => {
|
|
13
|
-
assert.equal(error instanceof CommanderError, true);
|
|
14
|
-
assert.equal(error.code, "commander.unknownOption");
|
|
15
|
-
assert.match(String(error.message), /unknown option '--config'/i);
|
|
16
|
-
return true;
|
|
17
|
-
});
|
|
18
|
-
});
|
|
19
|
-
test("init help has no merge/overwrite flags", () => {
|
|
20
|
-
const program = buildProgram();
|
|
21
|
-
const initCommand = program.commands.find((command) => command.name() === "init");
|
|
22
|
-
assert.ok(initCommand);
|
|
23
|
-
const help = initCommand.helpInformation();
|
|
24
|
-
assert.equal(help.includes("--merge"), false);
|
|
25
|
-
assert.equal(help.includes("--merge-strategy"), false);
|
|
26
|
-
assert.equal(help.includes("--overwrite"), false);
|
|
27
|
-
assert.equal(help.includes("--preset"), false);
|
|
28
|
-
assert.equal(help.includes("--non-interactive"), false);
|
|
29
|
-
assert.equal(help.includes("--yes"), false);
|
|
30
|
-
assert.equal(help.includes("--config"), false);
|
|
31
|
-
});
|
|
32
|
-
test("config help shows read-only inspect commands and schema", () => {
|
|
33
|
-
const program = buildProgram();
|
|
34
|
-
const configCommand = program.commands.find((command) => command.name() === "config");
|
|
35
|
-
assert.ok(configCommand);
|
|
36
|
-
const help = configCommand.helpInformation();
|
|
37
|
-
assert.match(help, /show \[options\] \[section\]/);
|
|
38
|
-
assert.match(help, /list \[options\] \[section\]/);
|
|
39
|
-
assert.match(help, /schema/);
|
|
40
|
-
assert.equal(help.includes("edit"), false);
|
|
41
|
-
assert.equal(help.includes("get"), false);
|
|
42
|
-
assert.equal(help.includes("set"), false);
|
|
43
|
-
assert.equal(help.includes("unset"), false);
|
|
44
|
-
});
|
|
45
|
-
test("config schema help shows list/show subcommands", () => {
|
|
46
|
-
const program = buildProgram();
|
|
47
|
-
const configCommand = program.commands.find((command) => command.name() === "config");
|
|
48
|
-
assert.ok(configCommand);
|
|
49
|
-
const schemaCommand = configCommand.commands.find((command) => command.name() === "schema");
|
|
50
|
-
assert.ok(schemaCommand);
|
|
51
|
-
const help = schemaCommand.helpInformation();
|
|
52
|
-
assert.match(help, /list \[options\]/);
|
|
53
|
-
assert.match(help, /show \[options\] <contributionId>/);
|
|
54
|
-
});
|
|
55
|
-
test("cron help shows core subcommands", () => {
|
|
56
|
-
const program = buildProgram();
|
|
57
|
-
const cronCommand = program.commands.find((command) => command.name() === "cron");
|
|
58
|
-
assert.ok(cronCommand);
|
|
59
|
-
const help = cronCommand.helpInformation();
|
|
60
|
-
assert.match(help, /add \[options\] <name>/);
|
|
61
|
-
assert.match(help, /list \[options\]/);
|
|
62
|
-
assert.match(help, /run \[options\] <jobId>/);
|
|
63
|
-
assert.match(help, /remove \[options\] <jobId>/);
|
|
64
|
-
});
|
|
65
|
-
test("top-level help keeps bootstrap, inspect, install, validate, and ops commands only", () => {
|
|
66
|
-
const program = buildProgram();
|
|
67
|
-
const help = program.helpInformation();
|
|
68
|
-
assert.match(help, /start/);
|
|
69
|
-
assert.match(help, /init/);
|
|
70
|
-
assert.match(help, /config/);
|
|
71
|
-
assert.match(help, /extension/);
|
|
72
|
-
assert.match(help, /doctor/);
|
|
73
|
-
assert.match(help, /cron/);
|
|
74
|
-
assert.equal(help.includes("configure"), false);
|
|
75
|
-
assert.equal(help.includes("bot"), false);
|
|
76
|
-
assert.equal(help.includes("binding"), false);
|
|
77
|
-
assert.equal(help.includes("route"), false);
|
|
78
|
-
});
|
|
@@ -1,254 +0,0 @@
|
|
|
1
|
-
import assert from "node:assert/strict";
|
|
2
|
-
import { mkdir, mkdtemp, rm, writeFile } from "node:fs/promises";
|
|
3
|
-
import { tmpdir } from "node:os";
|
|
4
|
-
import { dirname, join } from "node:path";
|
|
5
|
-
import test from "node:test";
|
|
6
|
-
import { BindingResolver, loadGatewayConfig } from "../../core/routing.js";
|
|
7
|
-
async function writeTempConfig(payload) {
|
|
8
|
-
const dir = await mkdtemp(join(tmpdir(), "dobby-routing-"));
|
|
9
|
-
const configPath = join(dir, "gateway.json");
|
|
10
|
-
await writeFile(configPath, `${JSON.stringify(payload, null, 2)}\n`, "utf-8");
|
|
11
|
-
return configPath;
|
|
12
|
-
}
|
|
13
|
-
async function writeRepoTempConfig(payload) {
|
|
14
|
-
const repoRoot = await mkdtemp(join(tmpdir(), "dobby-routing-repo-"));
|
|
15
|
-
const configDir = join(repoRoot, "config");
|
|
16
|
-
await mkdir(configDir, { recursive: true });
|
|
17
|
-
await mkdir(join(repoRoot, "scripts"), { recursive: true });
|
|
18
|
-
await writeFile(join(repoRoot, "package.json"), JSON.stringify({ name: "@dobby.ai/dobby" }), "utf-8");
|
|
19
|
-
await writeFile(join(repoRoot, "scripts", "local-extensions.mjs"), "#!/usr/bin/env node\n", "utf-8");
|
|
20
|
-
const configPath = join(configDir, "gateway.json");
|
|
21
|
-
await writeFile(configPath, `${JSON.stringify(payload, null, 2)}\n`, "utf-8");
|
|
22
|
-
return { repoRoot, configPath };
|
|
23
|
-
}
|
|
24
|
-
function validConfig() {
|
|
25
|
-
return {
|
|
26
|
-
extensions: { allowList: [] },
|
|
27
|
-
providers: {
|
|
28
|
-
default: "pi.main",
|
|
29
|
-
items: {
|
|
30
|
-
"pi.main": {
|
|
31
|
-
type: "provider.pi",
|
|
32
|
-
},
|
|
33
|
-
},
|
|
34
|
-
},
|
|
35
|
-
connectors: {
|
|
36
|
-
items: {
|
|
37
|
-
"discord.main": {
|
|
38
|
-
type: "connector.discord",
|
|
39
|
-
botName: "dobby-main",
|
|
40
|
-
botToken: "token",
|
|
41
|
-
},
|
|
42
|
-
},
|
|
43
|
-
},
|
|
44
|
-
sandboxes: {
|
|
45
|
-
default: "host.builtin",
|
|
46
|
-
items: {},
|
|
47
|
-
},
|
|
48
|
-
routes: {
|
|
49
|
-
default: {
|
|
50
|
-
provider: "pi.main",
|
|
51
|
-
sandbox: "host.builtin",
|
|
52
|
-
tools: "full",
|
|
53
|
-
mentions: "required",
|
|
54
|
-
},
|
|
55
|
-
items: {
|
|
56
|
-
main: {
|
|
57
|
-
projectRoot: "./workspace/project-a",
|
|
58
|
-
systemPromptFile: "./prompts/main.md",
|
|
59
|
-
},
|
|
60
|
-
},
|
|
61
|
-
},
|
|
62
|
-
bindings: {
|
|
63
|
-
items: {
|
|
64
|
-
"discord.main.main": {
|
|
65
|
-
connector: "discord.main",
|
|
66
|
-
source: {
|
|
67
|
-
type: "channel",
|
|
68
|
-
id: "123",
|
|
69
|
-
},
|
|
70
|
-
route: "main",
|
|
71
|
-
},
|
|
72
|
-
},
|
|
73
|
-
},
|
|
74
|
-
data: {
|
|
75
|
-
rootDir: "./data",
|
|
76
|
-
dedupTtlMs: 604800000,
|
|
77
|
-
},
|
|
78
|
-
};
|
|
79
|
-
}
|
|
80
|
-
test("loadGatewayConfig applies route defaults and resolves relative paths", async () => {
|
|
81
|
-
const payload = validConfig();
|
|
82
|
-
const configPath = await writeTempConfig(payload);
|
|
83
|
-
try {
|
|
84
|
-
const loaded = await loadGatewayConfig(configPath);
|
|
85
|
-
const configDir = dirname(configPath);
|
|
86
|
-
assert.equal(loaded.providers.default, "pi.main");
|
|
87
|
-
assert.deepEqual(loaded.routes.default, {
|
|
88
|
-
provider: "pi.main",
|
|
89
|
-
sandbox: "host.builtin",
|
|
90
|
-
tools: "full",
|
|
91
|
-
mentions: "required",
|
|
92
|
-
});
|
|
93
|
-
assert.deepEqual(loaded.routes.items.main, {
|
|
94
|
-
projectRoot: join(configDir, "workspace/project-a"),
|
|
95
|
-
systemPromptFile: join(configDir, "prompts/main.md"),
|
|
96
|
-
provider: "pi.main",
|
|
97
|
-
sandbox: "host.builtin",
|
|
98
|
-
tools: "full",
|
|
99
|
-
mentions: "required",
|
|
100
|
-
});
|
|
101
|
-
assert.equal(loaded.data.rootDir, join(configDir, "data"));
|
|
102
|
-
assert.deepEqual(loaded.bindings.items["discord.main.main"], {
|
|
103
|
-
connector: "discord.main",
|
|
104
|
-
source: {
|
|
105
|
-
type: "channel",
|
|
106
|
-
id: "123",
|
|
107
|
-
},
|
|
108
|
-
route: "main",
|
|
109
|
-
});
|
|
110
|
-
}
|
|
111
|
-
finally {
|
|
112
|
-
await rm(dirname(configPath), { recursive: true, force: true });
|
|
113
|
-
}
|
|
114
|
-
});
|
|
115
|
-
test("loadGatewayConfig resolves data.rootDir from repo root for repo-local config/gateway.json", async () => {
|
|
116
|
-
const payload = validConfig();
|
|
117
|
-
const { repoRoot, configPath } = await writeRepoTempConfig(payload);
|
|
118
|
-
try {
|
|
119
|
-
const loaded = await loadGatewayConfig(configPath);
|
|
120
|
-
const mainRoute = loaded.routes.items.main;
|
|
121
|
-
assert.ok(mainRoute);
|
|
122
|
-
assert.equal(loaded.data.rootDir, join(repoRoot, "data"));
|
|
123
|
-
assert.equal(mainRoute.projectRoot, join(repoRoot, "workspace/project-a"));
|
|
124
|
-
}
|
|
125
|
-
finally {
|
|
126
|
-
await rm(repoRoot, { recursive: true, force: true });
|
|
127
|
-
}
|
|
128
|
-
});
|
|
129
|
-
test("loadGatewayConfig applies routes.default.projectRoot and bindings.default for direct messages", async () => {
|
|
130
|
-
const payload = validConfig();
|
|
131
|
-
payload.routes = {
|
|
132
|
-
default: {
|
|
133
|
-
projectRoot: "./workspace/default-root",
|
|
134
|
-
provider: "pi.main",
|
|
135
|
-
sandbox: "host.builtin",
|
|
136
|
-
tools: "full",
|
|
137
|
-
mentions: "required",
|
|
138
|
-
},
|
|
139
|
-
items: {
|
|
140
|
-
main: {},
|
|
141
|
-
},
|
|
142
|
-
};
|
|
143
|
-
payload.bindings = {
|
|
144
|
-
default: {
|
|
145
|
-
route: "main",
|
|
146
|
-
},
|
|
147
|
-
items: {},
|
|
148
|
-
};
|
|
149
|
-
const configPath = await writeTempConfig(payload);
|
|
150
|
-
try {
|
|
151
|
-
const loaded = await loadGatewayConfig(configPath);
|
|
152
|
-
const configDir = dirname(configPath);
|
|
153
|
-
const resolver = new BindingResolver(loaded.bindings);
|
|
154
|
-
assert.deepEqual(loaded.routes.default, {
|
|
155
|
-
projectRoot: join(configDir, "workspace/default-root"),
|
|
156
|
-
provider: "pi.main",
|
|
157
|
-
sandbox: "host.builtin",
|
|
158
|
-
tools: "full",
|
|
159
|
-
mentions: "required",
|
|
160
|
-
});
|
|
161
|
-
assert.deepEqual(loaded.routes.items.main, {
|
|
162
|
-
projectRoot: join(configDir, "workspace/default-root"),
|
|
163
|
-
provider: "pi.main",
|
|
164
|
-
sandbox: "host.builtin",
|
|
165
|
-
tools: "full",
|
|
166
|
-
mentions: "required",
|
|
167
|
-
});
|
|
168
|
-
assert.deepEqual(loaded.bindings.default, {
|
|
169
|
-
route: "main",
|
|
170
|
-
});
|
|
171
|
-
assert.equal(resolver.resolve("discord.main", {
|
|
172
|
-
type: "channel",
|
|
173
|
-
id: "dm-123",
|
|
174
|
-
}, { isDirectMessage: true })?.config.route, "main");
|
|
175
|
-
assert.equal(resolver.resolve("discord.main", {
|
|
176
|
-
type: "channel",
|
|
177
|
-
id: "dm-123",
|
|
178
|
-
}, { isDirectMessage: false }), null);
|
|
179
|
-
}
|
|
180
|
-
finally {
|
|
181
|
-
await rm(dirname(configPath), { recursive: true, force: true });
|
|
182
|
-
}
|
|
183
|
-
});
|
|
184
|
-
test("loadGatewayConfig rejects connector fields reserved by the host", async () => {
|
|
185
|
-
const payload = validConfig();
|
|
186
|
-
payload.connectors = {
|
|
187
|
-
items: {
|
|
188
|
-
"discord.main": {
|
|
189
|
-
type: "connector.discord",
|
|
190
|
-
botName: "dobby-main",
|
|
191
|
-
botToken: "token",
|
|
192
|
-
botChannelMap: {
|
|
193
|
-
"123": "main",
|
|
194
|
-
},
|
|
195
|
-
},
|
|
196
|
-
},
|
|
197
|
-
};
|
|
198
|
-
const configPath = await writeTempConfig(payload);
|
|
199
|
-
try {
|
|
200
|
-
await assert.rejects(loadGatewayConfig(configPath), /must not include 'botChannelMap'/);
|
|
201
|
-
}
|
|
202
|
-
finally {
|
|
203
|
-
await rm(dirname(configPath), { recursive: true, force: true });
|
|
204
|
-
}
|
|
205
|
-
});
|
|
206
|
-
test("loadGatewayConfig rejects connector env indirection fields reserved by the host", async () => {
|
|
207
|
-
const payload = validConfig();
|
|
208
|
-
payload.connectors = {
|
|
209
|
-
items: {
|
|
210
|
-
"discord.main": {
|
|
211
|
-
type: "connector.discord",
|
|
212
|
-
botName: "dobby-main",
|
|
213
|
-
botTokenEnv: "DISCORD_BOT_TOKEN",
|
|
214
|
-
},
|
|
215
|
-
},
|
|
216
|
-
};
|
|
217
|
-
const configPath = await writeTempConfig(payload);
|
|
218
|
-
try {
|
|
219
|
-
await assert.rejects(loadGatewayConfig(configPath), /must not include 'botTokenEnv'/);
|
|
220
|
-
}
|
|
221
|
-
finally {
|
|
222
|
-
await rm(dirname(configPath), { recursive: true, force: true });
|
|
223
|
-
}
|
|
224
|
-
});
|
|
225
|
-
test("loadGatewayConfig fails fast on duplicate binding sources", async () => {
|
|
226
|
-
const payload = validConfig();
|
|
227
|
-
payload.bindings = {
|
|
228
|
-
items: {
|
|
229
|
-
"discord.main.main": {
|
|
230
|
-
connector: "discord.main",
|
|
231
|
-
source: {
|
|
232
|
-
type: "channel",
|
|
233
|
-
id: "123",
|
|
234
|
-
},
|
|
235
|
-
route: "main",
|
|
236
|
-
},
|
|
237
|
-
"discord.main.duplicate": {
|
|
238
|
-
connector: "discord.main",
|
|
239
|
-
source: {
|
|
240
|
-
type: "channel",
|
|
241
|
-
id: "123",
|
|
242
|
-
},
|
|
243
|
-
route: "main",
|
|
244
|
-
},
|
|
245
|
-
},
|
|
246
|
-
};
|
|
247
|
-
const configPath = await writeTempConfig(payload);
|
|
248
|
-
try {
|
|
249
|
-
await assert.rejects(loadGatewayConfig(configPath), /duplicates source 'discord\.main:channel:123'/);
|
|
250
|
-
}
|
|
251
|
-
finally {
|
|
252
|
-
await rm(dirname(configPath), { recursive: true, force: true });
|
|
253
|
-
}
|
|
254
|
-
});
|
|
@@ -1,17 +0,0 @@
|
|
|
1
|
-
import assert from "node:assert/strict";
|
|
2
|
-
import test from "node:test";
|
|
3
|
-
import { parseControlCommand } from "../control-command.js";
|
|
4
|
-
test("parseControlCommand recognizes cancel aliases", () => {
|
|
5
|
-
assert.equal(parseControlCommand("stop"), "cancel");
|
|
6
|
-
assert.equal(parseControlCommand(" /STOP "), "cancel");
|
|
7
|
-
assert.equal(parseControlCommand("/cancel"), "cancel");
|
|
8
|
-
});
|
|
9
|
-
test("parseControlCommand recognizes new session aliases", () => {
|
|
10
|
-
assert.equal(parseControlCommand("/new"), "new_session");
|
|
11
|
-
assert.equal(parseControlCommand(" /reset "), "new_session");
|
|
12
|
-
});
|
|
13
|
-
test("parseControlCommand ignores regular messages", () => {
|
|
14
|
-
assert.equal(parseControlCommand("please /new"), null);
|
|
15
|
-
assert.equal(parseControlCommand(""), null);
|
|
16
|
-
assert.equal(parseControlCommand("hello"), null);
|
|
17
|
-
});
|
|
@@ -1,116 +0,0 @@
|
|
|
1
|
-
import assert from "node:assert/strict";
|
|
2
|
-
import test from "node:test";
|
|
3
|
-
import { RuntimeRegistry } from "../runtime-registry.js";
|
|
4
|
-
function deferred() {
|
|
5
|
-
let resolve;
|
|
6
|
-
const promise = new Promise((res) => {
|
|
7
|
-
resolve = res;
|
|
8
|
-
});
|
|
9
|
-
return { promise, resolve };
|
|
10
|
-
}
|
|
11
|
-
function createLogger() {
|
|
12
|
-
return {
|
|
13
|
-
error() { },
|
|
14
|
-
warn() { },
|
|
15
|
-
info() { },
|
|
16
|
-
debug() { },
|
|
17
|
-
};
|
|
18
|
-
}
|
|
19
|
-
function createConversationRuntime(id, abortCalls, closeCalls) {
|
|
20
|
-
return {
|
|
21
|
-
key: id,
|
|
22
|
-
routeId: "route.main",
|
|
23
|
-
route: {
|
|
24
|
-
projectRoot: "/tmp/project",
|
|
25
|
-
tools: "full",
|
|
26
|
-
mentions: "required",
|
|
27
|
-
provider: "provider.main",
|
|
28
|
-
sandbox: "host.builtin",
|
|
29
|
-
},
|
|
30
|
-
providerId: "provider.main",
|
|
31
|
-
sandboxId: "host.builtin",
|
|
32
|
-
runtime: {
|
|
33
|
-
async prompt() { },
|
|
34
|
-
subscribe() {
|
|
35
|
-
return () => { };
|
|
36
|
-
},
|
|
37
|
-
async abort() {
|
|
38
|
-
abortCalls.push(id);
|
|
39
|
-
},
|
|
40
|
-
dispose() { },
|
|
41
|
-
},
|
|
42
|
-
async close() {
|
|
43
|
-
closeCalls.push(id);
|
|
44
|
-
},
|
|
45
|
-
};
|
|
46
|
-
}
|
|
47
|
-
test("cancel aborts the active run and drops queued turns", async () => {
|
|
48
|
-
const registry = new RuntimeRegistry(createLogger());
|
|
49
|
-
const abortCalls = [];
|
|
50
|
-
const closeCalls = [];
|
|
51
|
-
let runtimeCount = 0;
|
|
52
|
-
const createRuntime = async () => {
|
|
53
|
-
runtimeCount += 1;
|
|
54
|
-
return createConversationRuntime(`runtime-${runtimeCount}`, abortCalls, closeCalls);
|
|
55
|
-
};
|
|
56
|
-
const firstStarted = deferred();
|
|
57
|
-
const releaseFirst = deferred();
|
|
58
|
-
const started = [];
|
|
59
|
-
const firstTurn = registry.run("conversation", createRuntime, async (runtime) => {
|
|
60
|
-
started.push(runtime.key);
|
|
61
|
-
firstStarted.resolve(undefined);
|
|
62
|
-
await releaseFirst.promise;
|
|
63
|
-
});
|
|
64
|
-
await firstStarted.promise;
|
|
65
|
-
const secondTurn = registry.run("conversation", createRuntime, async (runtime) => {
|
|
66
|
-
started.push(runtime.key);
|
|
67
|
-
});
|
|
68
|
-
const thirdTurn = registry.run("conversation", createRuntime, async (runtime) => {
|
|
69
|
-
started.push(runtime.key);
|
|
70
|
-
});
|
|
71
|
-
assert.equal(await registry.cancel("conversation"), true);
|
|
72
|
-
assert.deepEqual(abortCalls, ["runtime-1"]);
|
|
73
|
-
releaseFirst.resolve(undefined);
|
|
74
|
-
await Promise.all([firstTurn, secondTurn, thirdTurn]);
|
|
75
|
-
assert.deepEqual(started, ["runtime-1"]);
|
|
76
|
-
assert.deepEqual(closeCalls, []);
|
|
77
|
-
await registry.run("conversation", createRuntime, async (runtime) => {
|
|
78
|
-
started.push(runtime.key);
|
|
79
|
-
});
|
|
80
|
-
assert.deepEqual(started, ["runtime-1", "runtime-1"]);
|
|
81
|
-
assert.equal(runtimeCount, 1);
|
|
82
|
-
});
|
|
83
|
-
test("reset closes the current runtime and recreates it on the next turn", async () => {
|
|
84
|
-
const registry = new RuntimeRegistry(createLogger());
|
|
85
|
-
const abortCalls = [];
|
|
86
|
-
const closeCalls = [];
|
|
87
|
-
let runtimeCount = 0;
|
|
88
|
-
const createRuntime = async () => {
|
|
89
|
-
runtimeCount += 1;
|
|
90
|
-
return createConversationRuntime(`runtime-${runtimeCount}`, abortCalls, closeCalls);
|
|
91
|
-
};
|
|
92
|
-
const firstStarted = deferred();
|
|
93
|
-
const releaseFirst = deferred();
|
|
94
|
-
const started = [];
|
|
95
|
-
const firstTurn = registry.run("conversation", createRuntime, async (runtime) => {
|
|
96
|
-
started.push(runtime.key);
|
|
97
|
-
firstStarted.resolve(undefined);
|
|
98
|
-
await releaseFirst.promise;
|
|
99
|
-
});
|
|
100
|
-
await firstStarted.promise;
|
|
101
|
-
const queuedTurn = registry.run("conversation", createRuntime, async (runtime) => {
|
|
102
|
-
started.push(runtime.key);
|
|
103
|
-
});
|
|
104
|
-
const resetPromise = registry.reset("conversation");
|
|
105
|
-
releaseFirst.resolve(undefined);
|
|
106
|
-
assert.equal(await resetPromise, true);
|
|
107
|
-
await Promise.all([firstTurn, queuedTurn]);
|
|
108
|
-
assert.deepEqual(abortCalls, ["runtime-1"]);
|
|
109
|
-
assert.deepEqual(closeCalls, ["runtime-1"]);
|
|
110
|
-
assert.deepEqual(started, ["runtime-1"]);
|
|
111
|
-
await registry.run("conversation", createRuntime, async (runtime) => {
|
|
112
|
-
started.push(runtime.key);
|
|
113
|
-
});
|
|
114
|
-
assert.deepEqual(started, ["runtime-1", "runtime-2"]);
|
|
115
|
-
assert.equal(runtimeCount, 2);
|
|
116
|
-
});
|