@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,331 +0,0 @@
|
|
|
1
|
-
import { access, mkdir } from "node:fs/promises";
|
|
2
|
-
import { dirname, isAbsolute, join, resolve } from "node:path";
|
|
3
|
-
import { homedir } from "node:os";
|
|
4
|
-
import { loadGatewayConfig } from "../../core/routing.js";
|
|
5
|
-
import { ExtensionStoreManager } from "../../extension/manager.js";
|
|
6
|
-
import {
|
|
7
|
-
ensureGatewayConfigShape,
|
|
8
|
-
setDefaultProviderIfMissingOrInvalid,
|
|
9
|
-
} from "../shared/config-mutators.js";
|
|
10
|
-
import { DISCORD_CONNECTOR_CONTRIBUTION_ID } from "../shared/discord-config.js";
|
|
11
|
-
import { readRawConfig, resolveConfigPath, resolveDataRootDir, writeConfigWithValidation } from "../shared/config-io.js";
|
|
12
|
-
import { createLogger } from "../shared/runtime.js";
|
|
13
|
-
|
|
14
|
-
interface DoctorIssue {
|
|
15
|
-
level: "error" | "warning";
|
|
16
|
-
message: string;
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
interface PlaceholderHit {
|
|
20
|
-
path: string;
|
|
21
|
-
value: string;
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
function isPlaceholderValue(value: unknown): value is string {
|
|
25
|
-
if (typeof value !== "string") {
|
|
26
|
-
return false;
|
|
27
|
-
}
|
|
28
|
-
const normalized = value.trim().toUpperCase();
|
|
29
|
-
return normalized.includes("REPLACE_WITH_") || normalized.includes("YOUR_");
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
function isCredentialLikeKey(key: string): boolean {
|
|
33
|
-
return /(?:token|secret|api[-_]?key|appid|appsecret)/i.test(key);
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
function walkPlaceholders(value: unknown, path: string): PlaceholderHit[] {
|
|
37
|
-
if (isPlaceholderValue(value)) {
|
|
38
|
-
return [{ path, value }];
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
if (Array.isArray(value)) {
|
|
42
|
-
return value.flatMap((item, index) => walkPlaceholders(item, `${path}[${index}]`));
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
if (!value || typeof value !== "object") {
|
|
46
|
-
return [];
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
return Object.entries(value).flatMap(([key, nested]) => walkPlaceholders(nested, `${path}.${key}`));
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
function lastPathSegment(path: string): string {
|
|
53
|
-
const withoutIndexes = path.replaceAll(/\[\d+\]/g, "");
|
|
54
|
-
const segments = withoutIndexes.split(".");
|
|
55
|
-
return segments[segments.length - 1] ?? withoutIndexes;
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
function expandHome(value: string): string {
|
|
59
|
-
if (value === "~") {
|
|
60
|
-
return homedir();
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
if (value.startsWith("~/") || value.startsWith("~\\")) {
|
|
64
|
-
return resolve(homedir(), value.slice(2));
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
return value;
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
function resolveRouteProjectRoot(configPath: string, projectRoot: string): string {
|
|
71
|
-
const expanded = expandHome(projectRoot);
|
|
72
|
-
if (isAbsolute(expanded)) {
|
|
73
|
-
return resolve(expanded);
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
return resolve(dirname(resolve(configPath)), expanded);
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
export async function runDoctorCommand(options: {
|
|
80
|
-
fix?: boolean;
|
|
81
|
-
}): Promise<void> {
|
|
82
|
-
const configPath = resolveConfigPath();
|
|
83
|
-
const issues: DoctorIssue[] = [];
|
|
84
|
-
|
|
85
|
-
const rawConfig = await readRawConfig(configPath);
|
|
86
|
-
if (!rawConfig) {
|
|
87
|
-
throw new Error(`Config '${configPath}' does not exist`);
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
try {
|
|
91
|
-
await loadGatewayConfig(configPath);
|
|
92
|
-
} catch (error) {
|
|
93
|
-
issues.push({
|
|
94
|
-
level: "error",
|
|
95
|
-
message: `Config schema/reference validation failed: ${error instanceof Error ? error.message : String(error)}`,
|
|
96
|
-
});
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
const normalized = ensureGatewayConfigShape(structuredClone(rawConfig));
|
|
100
|
-
const logger = createLogger();
|
|
101
|
-
const manager = new ExtensionStoreManager(logger, join(resolveDataRootDir(configPath, normalized), "extensions"));
|
|
102
|
-
const installedExtensions = await manager.listInstalled();
|
|
103
|
-
|
|
104
|
-
const installedPackages = new Set(installedExtensions.map((item) => item.packageName));
|
|
105
|
-
const enabledPackages = new Set(
|
|
106
|
-
normalized.extensions.allowList.filter((item) => item.enabled).map((item) => item.package),
|
|
107
|
-
);
|
|
108
|
-
|
|
109
|
-
for (const packageName of enabledPackages) {
|
|
110
|
-
if (!installedPackages.has(packageName)) {
|
|
111
|
-
issues.push({
|
|
112
|
-
level: "error",
|
|
113
|
-
message: `extensions.allowList enables '${packageName}' but it is not installed`,
|
|
114
|
-
});
|
|
115
|
-
}
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
const availableContributionIds = new Set<string>();
|
|
119
|
-
for (const item of installedExtensions) {
|
|
120
|
-
if (item.error) {
|
|
121
|
-
issues.push({
|
|
122
|
-
level: "warning",
|
|
123
|
-
message: `Installed extension '${item.packageName}' has invalid manifest: ${item.error}`,
|
|
124
|
-
});
|
|
125
|
-
continue;
|
|
126
|
-
}
|
|
127
|
-
|
|
128
|
-
if (!enabledPackages.has(item.packageName)) {
|
|
129
|
-
continue;
|
|
130
|
-
}
|
|
131
|
-
|
|
132
|
-
for (const contribution of item.manifest?.contributions ?? []) {
|
|
133
|
-
availableContributionIds.add(contribution.id);
|
|
134
|
-
}
|
|
135
|
-
}
|
|
136
|
-
|
|
137
|
-
for (const [instanceId, instance] of Object.entries(normalized.providers.items)) {
|
|
138
|
-
if (!availableContributionIds.has(instance.type)) {
|
|
139
|
-
issues.push({
|
|
140
|
-
level: "error",
|
|
141
|
-
message: `providers.items['${instanceId}'] references missing contribution '${instance.type}'`,
|
|
142
|
-
});
|
|
143
|
-
}
|
|
144
|
-
|
|
145
|
-
for (const hit of walkPlaceholders(instance, `providers.items['${instanceId}']`)) {
|
|
146
|
-
if (hit.path.endsWith(".type")) {
|
|
147
|
-
continue;
|
|
148
|
-
}
|
|
149
|
-
|
|
150
|
-
issues.push({
|
|
151
|
-
level: isCredentialLikeKey(lastPathSegment(hit.path)) ? "error" : "warning",
|
|
152
|
-
message: `${hit.path} still uses placeholder value '${hit.value}'`,
|
|
153
|
-
});
|
|
154
|
-
}
|
|
155
|
-
}
|
|
156
|
-
|
|
157
|
-
for (const [instanceId, instance] of Object.entries(normalized.connectors.items)) {
|
|
158
|
-
if (!availableContributionIds.has(instance.type)) {
|
|
159
|
-
issues.push({
|
|
160
|
-
level: "error",
|
|
161
|
-
message: `connectors.items['${instanceId}'] references missing contribution '${instance.type}'`,
|
|
162
|
-
});
|
|
163
|
-
}
|
|
164
|
-
|
|
165
|
-
for (const hit of walkPlaceholders(instance, `connectors.items['${instanceId}']`)) {
|
|
166
|
-
if (hit.path.endsWith(".type")) {
|
|
167
|
-
continue;
|
|
168
|
-
}
|
|
169
|
-
|
|
170
|
-
issues.push({
|
|
171
|
-
level: isCredentialLikeKey(lastPathSegment(hit.path)) ? "error" : "warning",
|
|
172
|
-
message: `${hit.path} still uses placeholder value '${hit.value}'`,
|
|
173
|
-
});
|
|
174
|
-
}
|
|
175
|
-
|
|
176
|
-
if (instance.type === DISCORD_CONNECTOR_CONTRIBUTION_ID) {
|
|
177
|
-
const botName = typeof instance.botName === "string" ? instance.botName.trim() : "";
|
|
178
|
-
const botToken = typeof instance.botToken === "string" ? instance.botToken.trim() : "";
|
|
179
|
-
if (botName.length === 0) {
|
|
180
|
-
issues.push({
|
|
181
|
-
level: "error",
|
|
182
|
-
message: `connectors.items['${instanceId}'].botName is required`,
|
|
183
|
-
});
|
|
184
|
-
}
|
|
185
|
-
if (botToken.length === 0) {
|
|
186
|
-
issues.push({
|
|
187
|
-
level: "error",
|
|
188
|
-
message: `connectors.items['${instanceId}'].botToken is required`,
|
|
189
|
-
});
|
|
190
|
-
}
|
|
191
|
-
}
|
|
192
|
-
}
|
|
193
|
-
|
|
194
|
-
for (const [instanceId, instance] of Object.entries(normalized.sandboxes.items)) {
|
|
195
|
-
if (!availableContributionIds.has(instance.type)) {
|
|
196
|
-
issues.push({
|
|
197
|
-
level: "error",
|
|
198
|
-
message: `sandboxes.items['${instanceId}'] references missing contribution '${instance.type}'`,
|
|
199
|
-
});
|
|
200
|
-
}
|
|
201
|
-
}
|
|
202
|
-
|
|
203
|
-
if (normalized.routes.default.projectRoot && isPlaceholderValue(normalized.routes.default.projectRoot)) {
|
|
204
|
-
issues.push({
|
|
205
|
-
level: "warning",
|
|
206
|
-
message: `routes.default.projectRoot still uses placeholder value '${normalized.routes.default.projectRoot}'`,
|
|
207
|
-
});
|
|
208
|
-
}
|
|
209
|
-
|
|
210
|
-
for (const [routeId, route] of Object.entries(normalized.routes.items)) {
|
|
211
|
-
const effectiveProjectRoot = route.projectRoot ?? normalized.routes.default.projectRoot;
|
|
212
|
-
const projectRootSource = route.projectRoot ? `routes.items['${routeId}'].projectRoot` : "routes.default.projectRoot";
|
|
213
|
-
|
|
214
|
-
if (!effectiveProjectRoot) {
|
|
215
|
-
issues.push({
|
|
216
|
-
level: "error",
|
|
217
|
-
message: `routes.items['${routeId}'].projectRoot is required when routes.default.projectRoot is not set`,
|
|
218
|
-
});
|
|
219
|
-
continue;
|
|
220
|
-
}
|
|
221
|
-
|
|
222
|
-
if (isPlaceholderValue(effectiveProjectRoot)) {
|
|
223
|
-
issues.push({
|
|
224
|
-
level: "warning",
|
|
225
|
-
message: `${projectRootSource} still uses placeholder value '${effectiveProjectRoot}'`,
|
|
226
|
-
});
|
|
227
|
-
continue;
|
|
228
|
-
}
|
|
229
|
-
|
|
230
|
-
try {
|
|
231
|
-
const projectRootPath = resolveRouteProjectRoot(configPath, effectiveProjectRoot);
|
|
232
|
-
await access(projectRootPath);
|
|
233
|
-
} catch {
|
|
234
|
-
issues.push({
|
|
235
|
-
level: "warning",
|
|
236
|
-
message: `${projectRootSource} does not exist: ${effectiveProjectRoot}`,
|
|
237
|
-
});
|
|
238
|
-
}
|
|
239
|
-
}
|
|
240
|
-
|
|
241
|
-
if (normalized.bindings.default && !normalized.routes.items[normalized.bindings.default.route]) {
|
|
242
|
-
issues.push({
|
|
243
|
-
level: "error",
|
|
244
|
-
message: `bindings.default.route references unknown route '${normalized.bindings.default.route}'`,
|
|
245
|
-
});
|
|
246
|
-
}
|
|
247
|
-
|
|
248
|
-
const seenBindingSources = new Map<string, string>();
|
|
249
|
-
for (const [bindingId, binding] of Object.entries(normalized.bindings.items)) {
|
|
250
|
-
if (!normalized.connectors.items[binding.connector]) {
|
|
251
|
-
issues.push({
|
|
252
|
-
level: "error",
|
|
253
|
-
message: `bindings.items['${bindingId}'].connector references unknown connector '${binding.connector}'`,
|
|
254
|
-
});
|
|
255
|
-
}
|
|
256
|
-
if (!normalized.routes.items[binding.route]) {
|
|
257
|
-
issues.push({
|
|
258
|
-
level: "error",
|
|
259
|
-
message: `bindings.items['${bindingId}'].route references unknown route '${binding.route}'`,
|
|
260
|
-
});
|
|
261
|
-
}
|
|
262
|
-
|
|
263
|
-
if (isPlaceholderValue(binding.source.id)) {
|
|
264
|
-
issues.push({
|
|
265
|
-
level: "warning",
|
|
266
|
-
message: `bindings.items['${bindingId}'].source.id still uses placeholder value '${binding.source.id}'`,
|
|
267
|
-
});
|
|
268
|
-
}
|
|
269
|
-
|
|
270
|
-
const bindingKey = `${binding.connector}:${binding.source.type}:${binding.source.id}`;
|
|
271
|
-
const existingBindingId = seenBindingSources.get(bindingKey);
|
|
272
|
-
if (existingBindingId) {
|
|
273
|
-
issues.push({
|
|
274
|
-
level: "error",
|
|
275
|
-
message:
|
|
276
|
-
`bindings.items['${bindingId}'] duplicates source '${bindingKey}' already used by bindings.items['${existingBindingId}']`,
|
|
277
|
-
});
|
|
278
|
-
} else {
|
|
279
|
-
seenBindingSources.set(bindingKey, bindingId);
|
|
280
|
-
}
|
|
281
|
-
}
|
|
282
|
-
|
|
283
|
-
if (options.fix) {
|
|
284
|
-
const fixTarget = ensureGatewayConfigShape(structuredClone(rawConfig));
|
|
285
|
-
const rootDir = resolveDataRootDir(configPath, fixTarget);
|
|
286
|
-
|
|
287
|
-
await mkdir(rootDir, { recursive: true });
|
|
288
|
-
await mkdir(join(rootDir, "sessions"), { recursive: true });
|
|
289
|
-
await mkdir(join(rootDir, "attachments"), { recursive: true });
|
|
290
|
-
await mkdir(join(rootDir, "logs"), { recursive: true });
|
|
291
|
-
await mkdir(join(rootDir, "state"), { recursive: true });
|
|
292
|
-
await mkdir(join(rootDir, "extensions"), { recursive: true });
|
|
293
|
-
|
|
294
|
-
const installedSet = new Set(installedExtensions.map((item) => item.packageName));
|
|
295
|
-
for (const item of fixTarget.extensions.allowList) {
|
|
296
|
-
if (item.enabled && !installedSet.has(item.package)) {
|
|
297
|
-
item.enabled = false;
|
|
298
|
-
}
|
|
299
|
-
}
|
|
300
|
-
|
|
301
|
-
setDefaultProviderIfMissingOrInvalid(fixTarget);
|
|
302
|
-
|
|
303
|
-
await writeConfigWithValidation(configPath, fixTarget, {
|
|
304
|
-
validate: true,
|
|
305
|
-
createBackup: true,
|
|
306
|
-
});
|
|
307
|
-
|
|
308
|
-
console.log("Applied doctor --fix actions:");
|
|
309
|
-
console.log("- ensured data directories exist");
|
|
310
|
-
console.log("- disabled allowList entries for packages not installed");
|
|
311
|
-
console.log("- repaired default provider when missing or invalid");
|
|
312
|
-
}
|
|
313
|
-
|
|
314
|
-
const errorCount = issues.filter((issue) => issue.level === "error").length;
|
|
315
|
-
const warningCount = issues.filter((issue) => issue.level === "warning").length;
|
|
316
|
-
|
|
317
|
-
if (issues.length === 0) {
|
|
318
|
-
console.log(`Doctor found no issues (${configPath})`);
|
|
319
|
-
return;
|
|
320
|
-
}
|
|
321
|
-
|
|
322
|
-
console.log(`Doctor results (${configPath}):`);
|
|
323
|
-
for (const issue of issues) {
|
|
324
|
-
console.log(`- [${issue.level}] ${issue.message}`);
|
|
325
|
-
}
|
|
326
|
-
console.log(`Summary: ${errorCount} error(s), ${warningCount} warning(s)`);
|
|
327
|
-
|
|
328
|
-
if (errorCount > 0) {
|
|
329
|
-
throw new Error(`Doctor found ${errorCount} error(s)`);
|
|
330
|
-
}
|
|
331
|
-
}
|
|
@@ -1,207 +0,0 @@
|
|
|
1
|
-
import { join } from "node:path";
|
|
2
|
-
import { loadGatewayConfig } from "../../core/routing.js";
|
|
3
|
-
import type { GatewayConfig } from "../../core/types.js";
|
|
4
|
-
import { ExtensionStoreManager } from "../../extension/manager.js";
|
|
5
|
-
import {
|
|
6
|
-
applyContributionTemplates,
|
|
7
|
-
buildContributionTemplates,
|
|
8
|
-
ensureGatewayConfigShape,
|
|
9
|
-
listContributionIds,
|
|
10
|
-
setDefaultProviderIfMissingOrInvalid,
|
|
11
|
-
upsertAllowListPackage,
|
|
12
|
-
} from "../shared/config-mutators.js";
|
|
13
|
-
import { readRawConfig, requireRawConfig, resolveConfigPath, resolveDataRootDir, writeConfigWithValidation } from "../shared/config-io.js";
|
|
14
|
-
import type { RawGatewayConfig } from "../shared/config-types.js";
|
|
15
|
-
import { resolveExtensionInstallSpecs } from "../shared/local-extension-specs.js";
|
|
16
|
-
import { createLogger } from "../shared/runtime.js";
|
|
17
|
-
|
|
18
|
-
/**
|
|
19
|
-
* Resolves extension store directory from normalized gateway config.
|
|
20
|
-
*/
|
|
21
|
-
function extensionStoreDir(config: GatewayConfig): string {
|
|
22
|
-
return join(config.data.rootDir, "extensions");
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
/**
|
|
26
|
-
* Resolves extension store directory directly from raw config plus config file location.
|
|
27
|
-
*/
|
|
28
|
-
function extensionStoreDirFromRaw(configPath: string, rawConfig: RawGatewayConfig): string {
|
|
29
|
-
const rootDir = resolveDataRootDir(configPath, rawConfig);
|
|
30
|
-
return join(rootDir, "extensions");
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
/**
|
|
34
|
-
* Installs an extension package, optionally enabling it in config and creating instance templates.
|
|
35
|
-
*/
|
|
36
|
-
export async function runExtensionInstallCommand(options: {
|
|
37
|
-
spec: string;
|
|
38
|
-
enable?: boolean;
|
|
39
|
-
json?: boolean;
|
|
40
|
-
}): Promise<void> {
|
|
41
|
-
const configPath = resolveConfigPath();
|
|
42
|
-
const logger = createLogger();
|
|
43
|
-
|
|
44
|
-
const rawConfig = (await readRawConfig(configPath)) ?? {};
|
|
45
|
-
const manager = new ExtensionStoreManager(logger, extensionStoreDirFromRaw(configPath, rawConfig));
|
|
46
|
-
const [resolvedSpec] = await resolveExtensionInstallSpecs([options.spec]);
|
|
47
|
-
const installed = await manager.install(resolvedSpec ?? options.spec);
|
|
48
|
-
|
|
49
|
-
if (!options.enable) {
|
|
50
|
-
const templates = buildContributionTemplates(installed.manifest.contributions);
|
|
51
|
-
if (options.json) {
|
|
52
|
-
console.log(
|
|
53
|
-
JSON.stringify(
|
|
54
|
-
{
|
|
55
|
-
package: installed.packageName,
|
|
56
|
-
version: installed.version,
|
|
57
|
-
contributions: installed.manifest.contributions,
|
|
58
|
-
templates,
|
|
59
|
-
},
|
|
60
|
-
null,
|
|
61
|
-
2,
|
|
62
|
-
),
|
|
63
|
-
);
|
|
64
|
-
return;
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
console.log(`Installed ${installed.packageName}@${installed.version}`);
|
|
68
|
-
console.log("Contributions:");
|
|
69
|
-
for (const contribution of installed.manifest.contributions) {
|
|
70
|
-
console.log(`- ${contribution.kind}:${contribution.id} (${contribution.entry})`);
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
console.log("");
|
|
74
|
-
console.log("allowList template:");
|
|
75
|
-
console.log(JSON.stringify({ package: installed.packageName, enabled: true }, null, 2));
|
|
76
|
-
|
|
77
|
-
console.log("");
|
|
78
|
-
console.log("instances template:");
|
|
79
|
-
console.log(JSON.stringify(templates, null, 2));
|
|
80
|
-
return;
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
const next = ensureGatewayConfigShape(structuredClone(rawConfig));
|
|
84
|
-
upsertAllowListPackage(next, installed.packageName, true);
|
|
85
|
-
|
|
86
|
-
const templates = buildContributionTemplates(installed.manifest.contributions);
|
|
87
|
-
const addedInstanceIds = applyContributionTemplates(next, templates);
|
|
88
|
-
setDefaultProviderIfMissingOrInvalid(next);
|
|
89
|
-
|
|
90
|
-
await writeConfigWithValidation(configPath, next, {
|
|
91
|
-
validate: true,
|
|
92
|
-
createBackup: true,
|
|
93
|
-
});
|
|
94
|
-
|
|
95
|
-
if (options.json) {
|
|
96
|
-
console.log(
|
|
97
|
-
JSON.stringify(
|
|
98
|
-
{
|
|
99
|
-
package: installed.packageName,
|
|
100
|
-
version: installed.version,
|
|
101
|
-
enabled: true,
|
|
102
|
-
addedInstanceIds,
|
|
103
|
-
},
|
|
104
|
-
null,
|
|
105
|
-
2,
|
|
106
|
-
),
|
|
107
|
-
);
|
|
108
|
-
return;
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
console.log(`Installed and enabled ${installed.packageName}@${installed.version}`);
|
|
112
|
-
console.log(`Updated ${configPath}`);
|
|
113
|
-
if (addedInstanceIds.providers.length > 0 || addedInstanceIds.connectors.length > 0 || addedInstanceIds.sandboxes.length > 0) {
|
|
114
|
-
console.log("Added instances:");
|
|
115
|
-
for (const providerId of addedInstanceIds.providers) {
|
|
116
|
-
console.log(`- provider: ${providerId}`);
|
|
117
|
-
}
|
|
118
|
-
for (const connectorId of addedInstanceIds.connectors) {
|
|
119
|
-
console.log(`- connector: ${connectorId}`);
|
|
120
|
-
}
|
|
121
|
-
for (const sandboxId of addedInstanceIds.sandboxes) {
|
|
122
|
-
console.log(`- sandbox: ${sandboxId}`);
|
|
123
|
-
}
|
|
124
|
-
}
|
|
125
|
-
}
|
|
126
|
-
|
|
127
|
-
/**
|
|
128
|
-
* Uninstalls an extension package from store without mutating config references.
|
|
129
|
-
*/
|
|
130
|
-
export async function runExtensionUninstallCommand(options: {
|
|
131
|
-
packageName: string;
|
|
132
|
-
}): Promise<void> {
|
|
133
|
-
const configPath = resolveConfigPath();
|
|
134
|
-
const logger = createLogger();
|
|
135
|
-
const rawConfig = await requireRawConfig(configPath);
|
|
136
|
-
const manager = new ExtensionStoreManager(logger, extensionStoreDirFromRaw(configPath, rawConfig));
|
|
137
|
-
|
|
138
|
-
await manager.uninstall(options.packageName);
|
|
139
|
-
console.log(`Uninstalled ${options.packageName}`);
|
|
140
|
-
console.log("Remember to remove this package from extensions.allowList and related instance references.");
|
|
141
|
-
}
|
|
142
|
-
|
|
143
|
-
/**
|
|
144
|
-
* Lists installed extension packages with enablement and contribution reference status.
|
|
145
|
-
*/
|
|
146
|
-
export async function runExtensionListCommand(options: {
|
|
147
|
-
json?: boolean;
|
|
148
|
-
}): Promise<void> {
|
|
149
|
-
const configPath = resolveConfigPath();
|
|
150
|
-
const logger = createLogger();
|
|
151
|
-
|
|
152
|
-
const rawConfig = await requireRawConfig(configPath);
|
|
153
|
-
const manager = new ExtensionStoreManager(logger, extensionStoreDirFromRaw(configPath, rawConfig));
|
|
154
|
-
const listed = await manager.listInstalled();
|
|
155
|
-
|
|
156
|
-
const normalized = ensureGatewayConfigShape(rawConfig);
|
|
157
|
-
const allowList = new Map((normalized.extensions?.allowList ?? []).map((item) => [item.package, item.enabled]));
|
|
158
|
-
|
|
159
|
-
const configuredContributionIds = listContributionIds(normalized);
|
|
160
|
-
const configuredContributionSet = new Set([
|
|
161
|
-
...configuredContributionIds.providers,
|
|
162
|
-
...configuredContributionIds.connectors,
|
|
163
|
-
...configuredContributionIds.sandboxes,
|
|
164
|
-
]);
|
|
165
|
-
|
|
166
|
-
const items = listed.map((item) => {
|
|
167
|
-
const contributions = item.manifest?.contributions ?? [];
|
|
168
|
-
const referencedContributions = contributions
|
|
169
|
-
.filter((contribution) => configuredContributionSet.has(contribution.id))
|
|
170
|
-
.map((contribution) => `${contribution.kind}:${contribution.id}`);
|
|
171
|
-
|
|
172
|
-
return {
|
|
173
|
-
package: item.packageName,
|
|
174
|
-
version: item.version,
|
|
175
|
-
enabled: allowList.get(item.packageName) ?? false,
|
|
176
|
-
contributions: contributions.map((contribution) => `${contribution.kind}:${contribution.id}`),
|
|
177
|
-
referencedContributions,
|
|
178
|
-
...(item.error ? { error: item.error } : {}),
|
|
179
|
-
};
|
|
180
|
-
});
|
|
181
|
-
|
|
182
|
-
if (options.json) {
|
|
183
|
-
console.log(JSON.stringify({ configPath, items }, null, 2));
|
|
184
|
-
return;
|
|
185
|
-
}
|
|
186
|
-
|
|
187
|
-
if (items.length === 0) {
|
|
188
|
-
const config = await loadGatewayConfig(configPath);
|
|
189
|
-
console.log(`No extensions installed in ${extensionStoreDir(config)}`);
|
|
190
|
-
return;
|
|
191
|
-
}
|
|
192
|
-
|
|
193
|
-
console.log(`Extensions in ${extensionStoreDirFromRaw(configPath, normalized)}:`);
|
|
194
|
-
for (const item of items) {
|
|
195
|
-
const suffix = item.enabled ? "enabled" : "disabled";
|
|
196
|
-
if (item.error) {
|
|
197
|
-
console.log(`- ${item.package}@${item.version} (${suffix}, invalid: ${item.error})`);
|
|
198
|
-
continue;
|
|
199
|
-
}
|
|
200
|
-
|
|
201
|
-
console.log(`- ${item.package}@${item.version} (${suffix})`);
|
|
202
|
-
for (const contribution of item.contributions) {
|
|
203
|
-
const isReferenced = item.referencedContributions.includes(contribution);
|
|
204
|
-
console.log(` * ${contribution}${isReferenced ? " [referenced]" : ""}`);
|
|
205
|
-
}
|
|
206
|
-
}
|
|
207
|
-
}
|