@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,25 +1,26 @@
|
|
|
1
|
-
import { DEFAULT_DISCORD_CONNECTOR_INSTANCE_ID, DISCORD_CONNECTOR_CONTRIBUTION_ID, } from "./discord-config.js";
|
|
1
|
+
import { DEFAULT_DISCORD_BOT_NAME, DEFAULT_DISCORD_CONNECTOR_INSTANCE_ID, DISCORD_CONNECTOR_CONTRIBUTION_ID, } from "./discord-config.js";
|
|
2
|
+
export const DEFAULT_INIT_ROUTE_ID = "main";
|
|
3
|
+
export const DEFAULT_INIT_PROJECT_ROOT = "./REPLACE_WITH_PROJECT_ROOT";
|
|
2
4
|
const PROVIDER_CATALOG = {
|
|
3
5
|
"provider.pi": {
|
|
4
6
|
id: "provider.pi",
|
|
5
7
|
label: "Pi provider",
|
|
6
|
-
|
|
8
|
+
package: "@dobby.ai/provider-pi",
|
|
7
9
|
instanceId: "pi.main",
|
|
8
10
|
contributionId: "provider.pi",
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
modelsFile: "./models.custom.json",
|
|
11
|
+
defaultConfig: {
|
|
12
|
+
model: "REPLACE_WITH_PROVIDER_MODEL_ID",
|
|
13
|
+
baseUrl: "REPLACE_WITH_PROVIDER_BASE_URL",
|
|
14
|
+
apiKey: "REPLACE_WITH_PROVIDER_API_KEY_OR_ENV",
|
|
14
15
|
},
|
|
15
16
|
},
|
|
16
17
|
"provider.claude-cli": {
|
|
17
18
|
id: "provider.claude-cli",
|
|
18
19
|
label: "Claude CLI provider",
|
|
19
|
-
|
|
20
|
+
package: "@dobby.ai/provider-claude-cli",
|
|
20
21
|
instanceId: "claude-cli.main",
|
|
21
22
|
contributionId: "provider.claude-cli",
|
|
22
|
-
|
|
23
|
+
defaultConfig: {
|
|
23
24
|
model: "claude-sonnet-4-5",
|
|
24
25
|
maxTurns: 20,
|
|
25
26
|
command: "claude",
|
|
@@ -34,11 +35,52 @@ const CONNECTOR_CATALOG = {
|
|
|
34
35
|
"connector.discord": {
|
|
35
36
|
id: "connector.discord",
|
|
36
37
|
label: "Discord connector",
|
|
37
|
-
|
|
38
|
+
package: "@dobby.ai/connector-discord",
|
|
38
39
|
instanceId: DEFAULT_DISCORD_CONNECTOR_INSTANCE_ID,
|
|
39
40
|
contributionId: DISCORD_CONNECTOR_CONTRIBUTION_ID,
|
|
41
|
+
defaultConfig: {
|
|
42
|
+
botName: DEFAULT_DISCORD_BOT_NAME,
|
|
43
|
+
botToken: "REPLACE_WITH_DISCORD_BOT_TOKEN",
|
|
44
|
+
reconnectStaleMs: 60_000,
|
|
45
|
+
reconnectCheckIntervalMs: 10_000,
|
|
46
|
+
},
|
|
47
|
+
bindingTemplate: {
|
|
48
|
+
sourceType: "channel",
|
|
49
|
+
sourceId: "YOUR_DISCORD_CHANNEL_ID",
|
|
50
|
+
},
|
|
51
|
+
},
|
|
52
|
+
"connector.feishu": {
|
|
53
|
+
id: "connector.feishu",
|
|
54
|
+
label: "Feishu connector",
|
|
55
|
+
package: "@dobby.ai/connector-feishu",
|
|
56
|
+
instanceId: "feishu.main",
|
|
57
|
+
contributionId: "connector.feishu",
|
|
58
|
+
defaultConfig: {
|
|
59
|
+
appId: "REPLACE_WITH_FEISHU_APP_ID",
|
|
60
|
+
appSecret: "REPLACE_WITH_FEISHU_APP_SECRET",
|
|
61
|
+
domain: "feishu",
|
|
62
|
+
messageFormat: "card_markdown",
|
|
63
|
+
replyMode: "direct",
|
|
64
|
+
downloadAttachments: true,
|
|
65
|
+
},
|
|
66
|
+
bindingTemplate: {
|
|
67
|
+
sourceType: "chat",
|
|
68
|
+
sourceId: "YOUR_FEISHU_CHAT_ID",
|
|
69
|
+
},
|
|
40
70
|
},
|
|
41
71
|
};
|
|
72
|
+
function dedupeChoiceIds(choiceIds) {
|
|
73
|
+
const dedupedChoiceIds = [];
|
|
74
|
+
const seenChoiceIds = new Set();
|
|
75
|
+
for (const choiceId of choiceIds) {
|
|
76
|
+
if (seenChoiceIds.has(choiceId)) {
|
|
77
|
+
continue;
|
|
78
|
+
}
|
|
79
|
+
seenChoiceIds.add(choiceId);
|
|
80
|
+
dedupedChoiceIds.push(choiceId);
|
|
81
|
+
}
|
|
82
|
+
return dedupedChoiceIds;
|
|
83
|
+
}
|
|
42
84
|
export function listInitProviderChoices() {
|
|
43
85
|
return Object.values(PROVIDER_CATALOG);
|
|
44
86
|
}
|
|
@@ -51,65 +93,66 @@ export function isInitProviderChoiceId(value) {
|
|
|
51
93
|
export function isInitConnectorChoiceId(value) {
|
|
52
94
|
return Object.prototype.hasOwnProperty.call(CONNECTOR_CATALOG, value);
|
|
53
95
|
}
|
|
54
|
-
export function createInitSelectionConfig(providerChoiceIds,
|
|
55
|
-
const dedupedProviderChoiceIds =
|
|
56
|
-
const seenProviderChoiceIds = new Set();
|
|
57
|
-
for (const providerChoiceId of providerChoiceIds) {
|
|
58
|
-
if (!seenProviderChoiceIds.has(providerChoiceId)) {
|
|
59
|
-
seenProviderChoiceIds.add(providerChoiceId);
|
|
60
|
-
dedupedProviderChoiceIds.push(providerChoiceId);
|
|
61
|
-
}
|
|
62
|
-
}
|
|
96
|
+
export function createInitSelectionConfig(providerChoiceIds, connectorChoiceIds, context) {
|
|
97
|
+
const dedupedProviderChoiceIds = dedupeChoiceIds(providerChoiceIds);
|
|
63
98
|
if (dedupedProviderChoiceIds.length === 0) {
|
|
64
99
|
throw new Error("At least one provider choice is required");
|
|
65
100
|
}
|
|
101
|
+
const dedupedConnectorChoiceIds = dedupeChoiceIds(connectorChoiceIds);
|
|
102
|
+
if (dedupedConnectorChoiceIds.length === 0) {
|
|
103
|
+
throw new Error("At least one connector choice is required");
|
|
104
|
+
}
|
|
66
105
|
if (!dedupedProviderChoiceIds.includes(context.routeProviderChoiceId)) {
|
|
67
106
|
throw new Error(`route provider choice '${context.routeProviderChoiceId}' must be one of selected providers: ${dedupedProviderChoiceIds.join(", ")}`);
|
|
68
107
|
}
|
|
69
108
|
const providerChoices = dedupedProviderChoiceIds.map((providerChoiceId) => PROVIDER_CATALOG[providerChoiceId]);
|
|
109
|
+
const connectorChoices = dedupedConnectorChoiceIds.map((connectorChoiceId) => CONNECTOR_CATALOG[connectorChoiceId]);
|
|
70
110
|
const primaryProviderChoice = PROVIDER_CATALOG[context.routeProviderChoiceId];
|
|
71
|
-
const connectorChoice = CONNECTOR_CATALOG[connectorChoiceId];
|
|
72
111
|
return {
|
|
73
112
|
providerChoiceIds: dedupedProviderChoiceIds,
|
|
74
113
|
routeProviderChoiceId: primaryProviderChoice.id,
|
|
75
|
-
|
|
76
|
-
connectorChoiceId,
|
|
114
|
+
connectorChoiceIds: dedupedConnectorChoiceIds,
|
|
77
115
|
extensionPackages: [
|
|
78
|
-
...new Set([
|
|
116
|
+
...new Set([
|
|
117
|
+
...providerChoices.map((item) => item.package),
|
|
118
|
+
...connectorChoices.map((item) => item.package),
|
|
119
|
+
]),
|
|
79
120
|
],
|
|
80
121
|
providerInstances: providerChoices.map((providerChoice) => ({
|
|
81
122
|
choiceId: providerChoice.id,
|
|
82
123
|
instanceId: providerChoice.instanceId,
|
|
83
124
|
contributionId: providerChoice.contributionId,
|
|
84
|
-
config: structuredClone(providerChoice.
|
|
125
|
+
config: structuredClone(providerChoice.defaultConfig),
|
|
126
|
+
})),
|
|
127
|
+
connectorInstances: connectorChoices.map((connectorChoice) => ({
|
|
128
|
+
choiceId: connectorChoice.id,
|
|
129
|
+
instanceId: connectorChoice.instanceId,
|
|
130
|
+
contributionId: connectorChoice.contributionId,
|
|
131
|
+
config: structuredClone(connectorChoice.defaultConfig),
|
|
85
132
|
})),
|
|
86
133
|
providerInstanceId: primaryProviderChoice.instanceId,
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
connectorContributionId: connectorChoice.contributionId,
|
|
91
|
-
connectorConfig: {
|
|
92
|
-
botName: context.botName,
|
|
93
|
-
botToken: context.botToken,
|
|
94
|
-
reconnectStaleMs: 60_000,
|
|
95
|
-
reconnectCheckIntervalMs: 10_000,
|
|
96
|
-
},
|
|
97
|
-
routeProfile: {
|
|
98
|
-
projectRoot: context.projectRoot,
|
|
134
|
+
routeId: DEFAULT_INIT_ROUTE_ID,
|
|
135
|
+
routeDefaults: {
|
|
136
|
+
projectRoot: context.defaultProjectRoot ?? DEFAULT_INIT_PROJECT_ROOT,
|
|
99
137
|
tools: "full",
|
|
100
|
-
|
|
101
|
-
mentions: context.allowAllMessages ? "optional" : "required",
|
|
138
|
+
mentions: "required",
|
|
102
139
|
provider: primaryProviderChoice.instanceId,
|
|
103
140
|
sandbox: "host.builtin",
|
|
104
141
|
},
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
source: {
|
|
109
|
-
type: "channel",
|
|
110
|
-
id: context.channelId,
|
|
111
|
-
},
|
|
112
|
-
route: context.routeId,
|
|
142
|
+
routeProfile: {},
|
|
143
|
+
defaultBinding: {
|
|
144
|
+
route: DEFAULT_INIT_ROUTE_ID,
|
|
113
145
|
},
|
|
146
|
+
bindings: connectorChoices.map((connectorChoice) => ({
|
|
147
|
+
id: `${connectorChoice.instanceId}.${DEFAULT_INIT_ROUTE_ID}`,
|
|
148
|
+
config: {
|
|
149
|
+
connector: connectorChoice.instanceId,
|
|
150
|
+
source: {
|
|
151
|
+
type: connectorChoice.bindingTemplate.sourceType,
|
|
152
|
+
id: connectorChoice.bindingTemplate.sourceId,
|
|
153
|
+
},
|
|
154
|
+
route: DEFAULT_INIT_ROUTE_ID,
|
|
155
|
+
},
|
|
156
|
+
})),
|
|
114
157
|
};
|
|
115
158
|
}
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
import { access, readFile } from "node:fs/promises";
|
|
2
|
+
import { readdir } from "node:fs/promises";
|
|
3
|
+
import { resolve } from "node:path";
|
|
4
|
+
import { findDobbyRepoRoot } from "../../shared/dobby-repo.js";
|
|
5
|
+
function isExplicitInstallSpec(value) {
|
|
6
|
+
return value.startsWith("file:")
|
|
7
|
+
|| value.startsWith("git+")
|
|
8
|
+
|| value.startsWith("http://")
|
|
9
|
+
|| value.startsWith("https://")
|
|
10
|
+
|| value.startsWith("./")
|
|
11
|
+
|| value.startsWith("../")
|
|
12
|
+
|| value.startsWith("/");
|
|
13
|
+
}
|
|
14
|
+
async function listRepoLocalExtensionPackages(repoRoot) {
|
|
15
|
+
const pluginsRoot = resolve(repoRoot, "plugins");
|
|
16
|
+
const entries = await readdir(pluginsRoot, { withFileTypes: true });
|
|
17
|
+
const packages = new Map();
|
|
18
|
+
for (const entry of entries) {
|
|
19
|
+
if (!entry.isDirectory() || entry.name === "plugin-sdk") {
|
|
20
|
+
continue;
|
|
21
|
+
}
|
|
22
|
+
const packageDir = resolve(pluginsRoot, entry.name);
|
|
23
|
+
const packageJsonPath = resolve(packageDir, "package.json");
|
|
24
|
+
const manifestPath = resolve(packageDir, "dobby.manifest.json");
|
|
25
|
+
try {
|
|
26
|
+
await access(packageJsonPath);
|
|
27
|
+
await access(manifestPath);
|
|
28
|
+
const raw = await readFile(packageJsonPath, "utf-8");
|
|
29
|
+
const parsed = JSON.parse(raw);
|
|
30
|
+
if (typeof parsed.name !== "string" || parsed.name.trim().length === 0) {
|
|
31
|
+
continue;
|
|
32
|
+
}
|
|
33
|
+
packages.set(parsed.name, {
|
|
34
|
+
packageName: parsed.name,
|
|
35
|
+
packageDir,
|
|
36
|
+
});
|
|
37
|
+
}
|
|
38
|
+
catch {
|
|
39
|
+
continue;
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
return packages;
|
|
43
|
+
}
|
|
44
|
+
async function assertLocalExtensionBuildReady(localPackage) {
|
|
45
|
+
const manifestPath = resolve(localPackage.packageDir, "dobby.manifest.json");
|
|
46
|
+
const rawManifest = await readFile(manifestPath, "utf-8");
|
|
47
|
+
const parsed = JSON.parse(rawManifest);
|
|
48
|
+
for (const contribution of parsed.contributions ?? []) {
|
|
49
|
+
if (typeof contribution.entry !== "string" || contribution.entry.trim().length === 0) {
|
|
50
|
+
continue;
|
|
51
|
+
}
|
|
52
|
+
const entryPath = resolve(localPackage.packageDir, contribution.entry);
|
|
53
|
+
try {
|
|
54
|
+
await access(entryPath);
|
|
55
|
+
}
|
|
56
|
+
catch {
|
|
57
|
+
const contributionId = typeof contribution.id === "string" ? contribution.id : "unknown";
|
|
58
|
+
throw new Error(`Local extension '${localPackage.packageName}' is not built for contribution '${contributionId}'. `
|
|
59
|
+
+ `Missing '${entryPath}'. Run 'npm run build --prefix ${localPackage.packageDir}' first.`);
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
export async function resolveExtensionInstallSpecs(packageSpecs, cwd = process.cwd()) {
|
|
64
|
+
const repoRoot = findDobbyRepoRoot(cwd);
|
|
65
|
+
if (!repoRoot) {
|
|
66
|
+
return packageSpecs;
|
|
67
|
+
}
|
|
68
|
+
const repoPackages = await listRepoLocalExtensionPackages(repoRoot);
|
|
69
|
+
const resolvedSpecs = [];
|
|
70
|
+
for (const rawSpec of packageSpecs) {
|
|
71
|
+
const packageSpec = rawSpec.trim();
|
|
72
|
+
if (packageSpec.length === 0 || isExplicitInstallSpec(packageSpec)) {
|
|
73
|
+
resolvedSpecs.push(packageSpec);
|
|
74
|
+
continue;
|
|
75
|
+
}
|
|
76
|
+
const localPackage = repoPackages.get(packageSpec);
|
|
77
|
+
if (!localPackage) {
|
|
78
|
+
resolvedSpecs.push(packageSpec);
|
|
79
|
+
continue;
|
|
80
|
+
}
|
|
81
|
+
await assertLocalExtensionBuildReady(localPackage);
|
|
82
|
+
resolvedSpecs.push(`file:${localPackage.packageDir}`);
|
|
83
|
+
}
|
|
84
|
+
return resolvedSpecs;
|
|
85
|
+
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { cancel, confirm, isCancel, multiselect, note, select, text, } from "@clack/prompts";
|
|
1
|
+
import { cancel, confirm, isCancel, multiselect, note, password, select, text, } from "@clack/prompts";
|
|
2
2
|
import JSON5 from "json5";
|
|
3
3
|
function isRecord(value) {
|
|
4
4
|
return Boolean(value) && typeof value === "object" && !Array.isArray(value);
|
|
@@ -61,11 +61,14 @@ function shouldPromptInMinimalMode(field) {
|
|
|
61
61
|
if (!field.hasDefault && field.required) {
|
|
62
62
|
return true;
|
|
63
63
|
}
|
|
64
|
-
if (
|
|
64
|
+
if (field.existingValue !== undefined) {
|
|
65
65
|
return true;
|
|
66
66
|
}
|
|
67
67
|
return false;
|
|
68
68
|
}
|
|
69
|
+
function isSensitiveStringField(key) {
|
|
70
|
+
return /(token|secret|api[-_]?key)$/i.test(key);
|
|
71
|
+
}
|
|
69
72
|
async function promptNumberField(params) {
|
|
70
73
|
while (true) {
|
|
71
74
|
const result = await text({
|
|
@@ -232,6 +235,27 @@ async function promptFieldValue(params) {
|
|
|
232
235
|
existingValue,
|
|
233
236
|
});
|
|
234
237
|
}
|
|
238
|
+
if (isSensitiveStringField(key)) {
|
|
239
|
+
while (true) {
|
|
240
|
+
const result = await password({
|
|
241
|
+
message,
|
|
242
|
+
mask: "*",
|
|
243
|
+
});
|
|
244
|
+
if (isCancel(result)) {
|
|
245
|
+
cancel("Configuration cancelled.");
|
|
246
|
+
throw new Error("Configuration cancelled.");
|
|
247
|
+
}
|
|
248
|
+
const raw = String(result ?? "").trim();
|
|
249
|
+
if (raw.length === 0) {
|
|
250
|
+
if (required && existingValue === undefined) {
|
|
251
|
+
await note("This field is required.", "Validation");
|
|
252
|
+
continue;
|
|
253
|
+
}
|
|
254
|
+
return existingValue;
|
|
255
|
+
}
|
|
256
|
+
return raw;
|
|
257
|
+
}
|
|
258
|
+
}
|
|
235
259
|
while (true) {
|
|
236
260
|
const result = await text({
|
|
237
261
|
message,
|
package/dist/src/core/gateway.js
CHANGED
|
@@ -129,7 +129,9 @@ export class Gateway {
|
|
|
129
129
|
route,
|
|
130
130
|
};
|
|
131
131
|
}
|
|
132
|
-
const binding = this.options.bindingResolver.resolve(message.connectorId, message.source
|
|
132
|
+
const binding = this.options.bindingResolver.resolve(message.connectorId, message.source, {
|
|
133
|
+
isDirectMessage: message.isDirectMessage,
|
|
134
|
+
});
|
|
133
135
|
if (!binding) {
|
|
134
136
|
if (handling.origin === "connector") {
|
|
135
137
|
this.options.logger.debug({
|
package/dist/src/core/routing.js
CHANGED
|
@@ -1,20 +1,21 @@
|
|
|
1
|
-
import { existsSync, readFileSync } from "node:fs";
|
|
2
1
|
import { readFile } from "node:fs/promises";
|
|
3
2
|
import { homedir } from "node:os";
|
|
4
3
|
import { dirname, isAbsolute, resolve } from "node:path";
|
|
5
4
|
import { z } from "zod";
|
|
5
|
+
import { isDobbyRepoRoot } from "../shared/dobby-repo.js";
|
|
6
6
|
import { BUILTIN_HOST_SANDBOX_ID } from "./types.js";
|
|
7
7
|
const extensionItemSchema = z.object({
|
|
8
8
|
type: z.string().trim().min(1),
|
|
9
9
|
}).catchall(z.unknown());
|
|
10
|
-
const
|
|
10
|
+
const routeDefaultSchema = z.object({
|
|
11
|
+
projectRoot: z.string().trim().min(1).optional(),
|
|
11
12
|
provider: z.string().trim().min(1).optional(),
|
|
12
13
|
sandbox: z.string().trim().min(1).optional(),
|
|
13
14
|
tools: z.enum(["full", "readonly"]).optional(),
|
|
14
15
|
mentions: z.enum(["required", "optional"]).optional(),
|
|
15
16
|
}).strict();
|
|
16
17
|
const routeItemSchema = z.object({
|
|
17
|
-
projectRoot: z.string().trim().min(1),
|
|
18
|
+
projectRoot: z.string().trim().min(1).optional(),
|
|
18
19
|
tools: z.enum(["full", "readonly"]).optional(),
|
|
19
20
|
mentions: z.enum(["required", "optional"]).optional(),
|
|
20
21
|
provider: z.string().trim().min(1).optional(),
|
|
@@ -33,6 +34,9 @@ const bindingItemSchema = z.object({
|
|
|
33
34
|
source: bindingSourceSchema,
|
|
34
35
|
route: z.string().trim().min(1),
|
|
35
36
|
}).strict();
|
|
37
|
+
const defaultBindingSchema = z.object({
|
|
38
|
+
route: z.string().trim().min(1),
|
|
39
|
+
}).strict();
|
|
36
40
|
const gatewayConfigSchema = z.object({
|
|
37
41
|
extensions: z.object({
|
|
38
42
|
allowList: z
|
|
@@ -54,10 +58,11 @@ const gatewayConfigSchema = z.object({
|
|
|
54
58
|
items: z.record(z.string(), extensionItemSchema).default({}),
|
|
55
59
|
}).strict(),
|
|
56
60
|
routes: z.object({
|
|
57
|
-
|
|
61
|
+
default: routeDefaultSchema.default({}),
|
|
58
62
|
items: z.record(z.string(), routeItemSchema),
|
|
59
63
|
}).strict(),
|
|
60
64
|
bindings: z.object({
|
|
65
|
+
default: defaultBindingSchema.optional(),
|
|
61
66
|
items: z.record(z.string(), bindingItemSchema).default({}),
|
|
62
67
|
}).strict(),
|
|
63
68
|
data: z.object({
|
|
@@ -70,22 +75,6 @@ const FORBIDDEN_CONNECTOR_CONFIG_KEYS = {
|
|
|
70
75
|
chatRouteMap: "Use bindings.items to map connector sources to routes.",
|
|
71
76
|
botTokenEnv: "Set botToken directly in connector config or inject it before the config is loaded.",
|
|
72
77
|
};
|
|
73
|
-
function isDobbyRepoRoot(candidateDir) {
|
|
74
|
-
const packageJsonPath = resolve(candidateDir, "package.json");
|
|
75
|
-
const repoConfigPath = resolve(candidateDir, "config", "gateway.json");
|
|
76
|
-
const localExtensionsScriptPath = resolve(candidateDir, "scripts", "local-extensions.mjs");
|
|
77
|
-
if (!existsSync(packageJsonPath) || !existsSync(repoConfigPath) || !existsSync(localExtensionsScriptPath)) {
|
|
78
|
-
return false;
|
|
79
|
-
}
|
|
80
|
-
try {
|
|
81
|
-
const packageJsonRaw = readFileSync(packageJsonPath, "utf-8");
|
|
82
|
-
const parsed = JSON.parse(packageJsonRaw);
|
|
83
|
-
return parsed.name === "dobby";
|
|
84
|
-
}
|
|
85
|
-
catch {
|
|
86
|
-
return false;
|
|
87
|
-
}
|
|
88
|
-
}
|
|
89
78
|
function resolveConfigBaseDir(configPath) {
|
|
90
79
|
const absoluteConfigPath = resolve(configPath);
|
|
91
80
|
const configDir = dirname(absoluteConfigPath);
|
|
@@ -147,9 +136,13 @@ function normalizeSandboxes(parsed) {
|
|
|
147
136
|
items: normalizeInstances(parsed.items),
|
|
148
137
|
};
|
|
149
138
|
}
|
|
150
|
-
function normalizeRouteProfile(baseDir, profile, defaults) {
|
|
139
|
+
function normalizeRouteProfile(routeId, baseDir, profile, defaults) {
|
|
140
|
+
const resolvedProjectRoot = profile.projectRoot ?? defaults.projectRoot;
|
|
141
|
+
if (!resolvedProjectRoot) {
|
|
142
|
+
throw new Error(`routes.items['${routeId}'].projectRoot is required when routes.default.projectRoot is not set`);
|
|
143
|
+
}
|
|
151
144
|
const normalized = {
|
|
152
|
-
projectRoot: resolveMaybeAbsolute(baseDir,
|
|
145
|
+
projectRoot: resolveMaybeAbsolute(baseDir, resolvedProjectRoot),
|
|
153
146
|
tools: profile.tools ?? defaults.tools,
|
|
154
147
|
mentions: profile.mentions ?? defaults.mentions,
|
|
155
148
|
provider: profile.provider ?? defaults.provider,
|
|
@@ -163,10 +156,10 @@ function normalizeRouteProfile(baseDir, profile, defaults) {
|
|
|
163
156
|
function normalizeRoutes(parsed, baseDir, defaults) {
|
|
164
157
|
const items = {};
|
|
165
158
|
for (const [routeId, profile] of Object.entries(parsed.items)) {
|
|
166
|
-
items[routeId] = normalizeRouteProfile(baseDir, profile, defaults);
|
|
159
|
+
items[routeId] = normalizeRouteProfile(routeId, baseDir, profile, defaults);
|
|
167
160
|
}
|
|
168
161
|
return {
|
|
169
|
-
defaults,
|
|
162
|
+
default: defaults,
|
|
170
163
|
items,
|
|
171
164
|
};
|
|
172
165
|
}
|
|
@@ -182,7 +175,10 @@ function normalizeBindings(parsed) {
|
|
|
182
175
|
route: binding.route,
|
|
183
176
|
};
|
|
184
177
|
}
|
|
185
|
-
return {
|
|
178
|
+
return {
|
|
179
|
+
...(parsed.default ? { default: { route: parsed.default.route } } : {}),
|
|
180
|
+
items,
|
|
181
|
+
};
|
|
186
182
|
}
|
|
187
183
|
function validateConnectorConfigKeys(parsed) {
|
|
188
184
|
for (const [instanceId, item] of Object.entries(parsed.items)) {
|
|
@@ -202,16 +198,16 @@ function validateReferences(parsed, normalizedRoutes) {
|
|
|
202
198
|
throw new Error(`sandboxes.default '${defaultSandbox}' does not exist in sandboxes.items`);
|
|
203
199
|
}
|
|
204
200
|
const resolvedDefaults = {
|
|
205
|
-
provider: parsed.routes.
|
|
206
|
-
sandbox: parsed.routes.
|
|
207
|
-
tools: parsed.routes.
|
|
208
|
-
mentions: parsed.routes.
|
|
201
|
+
provider: parsed.routes.default.provider ?? parsed.providers.default,
|
|
202
|
+
sandbox: parsed.routes.default.sandbox ?? parsed.sandboxes.default ?? BUILTIN_HOST_SANDBOX_ID,
|
|
203
|
+
tools: parsed.routes.default.tools ?? "full",
|
|
204
|
+
mentions: parsed.routes.default.mentions ?? "required",
|
|
209
205
|
};
|
|
210
206
|
if (!parsed.providers.items[resolvedDefaults.provider]) {
|
|
211
|
-
throw new Error(`routes.
|
|
207
|
+
throw new Error(`routes.default.provider references unknown provider '${resolvedDefaults.provider}'`);
|
|
212
208
|
}
|
|
213
209
|
if (resolvedDefaults.sandbox !== BUILTIN_HOST_SANDBOX_ID && !parsed.sandboxes.items[resolvedDefaults.sandbox]) {
|
|
214
|
-
throw new Error(`routes.
|
|
210
|
+
throw new Error(`routes.default.sandbox references unknown sandbox '${resolvedDefaults.sandbox}'`);
|
|
215
211
|
}
|
|
216
212
|
for (const [routeId, profile] of Object.entries(normalizedRoutes.items)) {
|
|
217
213
|
if (!parsed.providers.items[profile.provider]) {
|
|
@@ -236,6 +232,9 @@ function validateReferences(parsed, normalizedRoutes) {
|
|
|
236
232
|
}
|
|
237
233
|
seenSources.set(bindingKey, bindingId);
|
|
238
234
|
}
|
|
235
|
+
if (parsed.bindings.default && !normalizedRoutes.items[parsed.bindings.default.route]) {
|
|
236
|
+
throw new Error(`bindings.default.route references unknown route '${parsed.bindings.default.route}'`);
|
|
237
|
+
}
|
|
239
238
|
}
|
|
240
239
|
export async function loadGatewayConfig(configPath) {
|
|
241
240
|
const absoluteConfigPath = resolve(configPath);
|
|
@@ -244,10 +243,11 @@ export async function loadGatewayConfig(configPath) {
|
|
|
244
243
|
const parsed = gatewayConfigSchema.parse(JSON.parse(raw));
|
|
245
244
|
validateConnectorConfigKeys(parsed.connectors);
|
|
246
245
|
const routeDefaults = {
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
246
|
+
...(parsed.routes.default.projectRoot ? { projectRoot: resolveMaybeAbsolute(configBaseDir, parsed.routes.default.projectRoot) } : {}),
|
|
247
|
+
provider: parsed.routes.default.provider ?? parsed.providers.default,
|
|
248
|
+
sandbox: parsed.routes.default.sandbox ?? parsed.sandboxes.default ?? BUILTIN_HOST_SANDBOX_ID,
|
|
249
|
+
tools: parsed.routes.default.tools ?? "full",
|
|
250
|
+
mentions: parsed.routes.default.mentions ?? "required",
|
|
251
251
|
};
|
|
252
252
|
const normalizedRoutes = normalizeRoutes(parsed.routes, configBaseDir, routeDefaults);
|
|
253
253
|
validateReferences(parsed, normalizedRoutes);
|
|
@@ -286,6 +286,7 @@ export class RouteResolver {
|
|
|
286
286
|
}
|
|
287
287
|
export class BindingResolver {
|
|
288
288
|
bindingsBySource = new Map();
|
|
289
|
+
defaultBinding;
|
|
289
290
|
constructor(bindings) {
|
|
290
291
|
for (const [bindingId, binding] of Object.entries(bindings.items)) {
|
|
291
292
|
this.bindingsBySource.set(this.buildKey(binding.connector, binding.source), {
|
|
@@ -293,12 +294,26 @@ export class BindingResolver {
|
|
|
293
294
|
config: binding,
|
|
294
295
|
});
|
|
295
296
|
}
|
|
297
|
+
this.defaultBinding = bindings.default
|
|
298
|
+
? {
|
|
299
|
+
bindingId: "__default__",
|
|
300
|
+
config: {
|
|
301
|
+
connector: "__default__",
|
|
302
|
+
source: {
|
|
303
|
+
type: "chat",
|
|
304
|
+
id: "__direct_message__",
|
|
305
|
+
},
|
|
306
|
+
route: bindings.default.route,
|
|
307
|
+
},
|
|
308
|
+
}
|
|
309
|
+
: null;
|
|
296
310
|
}
|
|
297
|
-
resolve(connectorId, source) {
|
|
311
|
+
resolve(connectorId, source, options) {
|
|
298
312
|
if (!connectorId.trim() || !source.id.trim()) {
|
|
299
|
-
return null;
|
|
313
|
+
return options?.isDirectMessage ? this.defaultBinding : null;
|
|
300
314
|
}
|
|
301
|
-
return this.bindingsBySource.get(this.buildKey(connectorId, source))
|
|
315
|
+
return this.bindingsBySource.get(this.buildKey(connectorId, source))
|
|
316
|
+
?? (options?.isDirectMessage ? this.defaultBinding : null);
|
|
302
317
|
}
|
|
303
318
|
buildKey(connectorId, source) {
|
|
304
319
|
return `${connectorId.trim()}:${source.type}:${source.id.trim()}`;
|
package/dist/src/core/types.js
CHANGED
package/dist/src/cron/config.js
CHANGED
|
@@ -7,7 +7,7 @@ const rawCronConfigSchema = z.object({
|
|
|
7
7
|
storeFile: z.string().min(1).optional(),
|
|
8
8
|
runLogFile: z.string().min(1).optional(),
|
|
9
9
|
pollIntervalMs: z.number().int().positive().default(10_000),
|
|
10
|
-
maxConcurrentRuns: z.number().int().positive().default(
|
|
10
|
+
maxConcurrentRuns: z.number().int().positive().default(2),
|
|
11
11
|
runMissedOnStartup: z.boolean().default(true),
|
|
12
12
|
jobTimeoutMs: z.number().int().positive().default(10 * 60 * 1000),
|
|
13
13
|
});
|
|
@@ -45,7 +45,7 @@ function defaultCronConfigPayload() {
|
|
|
45
45
|
storeFile: "./data/state/cron-jobs.json",
|
|
46
46
|
runLogFile: "./data/state/cron-runs.jsonl",
|
|
47
47
|
pollIntervalMs: 10_000,
|
|
48
|
-
maxConcurrentRuns:
|
|
48
|
+
maxConcurrentRuns: 2,
|
|
49
49
|
runMissedOnStartup: true,
|
|
50
50
|
jobTimeoutMs: 10 * 60 * 1000,
|
|
51
51
|
};
|