@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
package/src/extension/manager.ts
DELETED
|
@@ -1,355 +0,0 @@
|
|
|
1
|
-
import { spawn } from "node:child_process";
|
|
2
|
-
import { access, mkdir, readFile, writeFile } from "node:fs/promises";
|
|
3
|
-
import { createRequire } from "node:module";
|
|
4
|
-
import { dirname, join, resolve } from "node:path";
|
|
5
|
-
import type { ExtensionManifest, GatewayLogger } from "../core/types.js";
|
|
6
|
-
import { readExtensionManifest } from "./manifest.js";
|
|
7
|
-
|
|
8
|
-
const STORE_PACKAGE_NAME = "dobby-extension-store";
|
|
9
|
-
|
|
10
|
-
interface StorePackageJson {
|
|
11
|
-
name: string;
|
|
12
|
-
private: boolean;
|
|
13
|
-
description?: string;
|
|
14
|
-
dependencies?: Record<string, string>;
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
export interface InstalledExtensionInfo {
|
|
18
|
-
packageName: string;
|
|
19
|
-
version: string;
|
|
20
|
-
manifest: ExtensionManifest;
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
export interface ListedExtensionInfo {
|
|
24
|
-
packageName: string;
|
|
25
|
-
version: string;
|
|
26
|
-
manifest?: ExtensionManifest;
|
|
27
|
-
error?: string;
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
function isJavaScriptEntry(entry: string): boolean {
|
|
31
|
-
return entry.endsWith(".js") || entry.endsWith(".mjs") || entry.endsWith(".cjs");
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
function assertWithinRoot(pathToCheck: string, rootDir: string): void {
|
|
35
|
-
const normalizedRoot = resolve(rootDir);
|
|
36
|
-
const normalizedPath = resolve(pathToCheck);
|
|
37
|
-
if (normalizedPath === normalizedRoot) {
|
|
38
|
-
return;
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
const rootPrefix = normalizedRoot.endsWith("/") || normalizedRoot.endsWith("\\")
|
|
42
|
-
? normalizedRoot
|
|
43
|
-
: `${normalizedRoot}${process.platform === "win32" ? "\\" : "/"}`;
|
|
44
|
-
if (!normalizedPath.startsWith(rootPrefix)) {
|
|
45
|
-
throw new Error(`Path '${normalizedPath}' escapes package root '${normalizedRoot}'`);
|
|
46
|
-
}
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
function parsePackageNameFromSpec(packageSpec: string): string | null {
|
|
50
|
-
const trimmed = packageSpec.trim();
|
|
51
|
-
if (!trimmed) return null;
|
|
52
|
-
|
|
53
|
-
const scopedMatch = /^(@[^/]+\/[^@]+)(?:@.+)?$/.exec(trimmed);
|
|
54
|
-
if (scopedMatch?.[1]) {
|
|
55
|
-
return scopedMatch[1];
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
if (
|
|
59
|
-
trimmed.startsWith("file:")
|
|
60
|
-
|| trimmed.startsWith("git+")
|
|
61
|
-
|| trimmed.startsWith("http://")
|
|
62
|
-
|| trimmed.startsWith("https://")
|
|
63
|
-
|| trimmed.startsWith("./")
|
|
64
|
-
|| trimmed.startsWith("../")
|
|
65
|
-
|| trimmed.startsWith("/")
|
|
66
|
-
|| trimmed.includes("/")
|
|
67
|
-
) {
|
|
68
|
-
return null;
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
const unscopedMatch = /^([^@]+)(?:@.+)?$/.exec(trimmed);
|
|
72
|
-
return unscopedMatch?.[1] ?? null;
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
async function parsePackageNameFromLocalSpec(packageSpec: string): Promise<string | null> {
|
|
76
|
-
const trimmed = packageSpec.trim();
|
|
77
|
-
if (!trimmed) return null;
|
|
78
|
-
|
|
79
|
-
let localPath: string | null = null;
|
|
80
|
-
if (trimmed.startsWith("file:")) {
|
|
81
|
-
localPath = trimmed.slice("file:".length);
|
|
82
|
-
} else if (trimmed.startsWith("./") || trimmed.startsWith("../") || trimmed.startsWith("/")) {
|
|
83
|
-
localPath = trimmed;
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
if (!localPath) {
|
|
87
|
-
return null;
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
const packageJsonPath = resolve(process.cwd(), localPath, "package.json");
|
|
91
|
-
try {
|
|
92
|
-
const raw = await readFile(packageJsonPath, "utf-8");
|
|
93
|
-
const parsed = JSON.parse(raw) as { name?: unknown };
|
|
94
|
-
return typeof parsed.name === "string" && parsed.name.length > 0 ? parsed.name : null;
|
|
95
|
-
} catch {
|
|
96
|
-
return null;
|
|
97
|
-
}
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
async function pickInstalledPackageName(
|
|
101
|
-
packageSpec: string,
|
|
102
|
-
beforeDeps: Record<string, string>,
|
|
103
|
-
afterDeps: Record<string, string>,
|
|
104
|
-
): Promise<string> {
|
|
105
|
-
const changedPackages = Object.entries(afterDeps)
|
|
106
|
-
.filter(([name, version]) => beforeDeps[name] !== version)
|
|
107
|
-
.map(([name]) => name);
|
|
108
|
-
|
|
109
|
-
if (changedPackages.length === 1) {
|
|
110
|
-
return changedPackages[0]!;
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
const inferred = parsePackageNameFromSpec(packageSpec);
|
|
114
|
-
if (inferred && afterDeps[inferred]) {
|
|
115
|
-
return inferred;
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
const localInferred = await parsePackageNameFromLocalSpec(packageSpec);
|
|
119
|
-
if (localInferred && afterDeps[localInferred]) {
|
|
120
|
-
return localInferred;
|
|
121
|
-
}
|
|
122
|
-
|
|
123
|
-
if (changedPackages.length > 0) {
|
|
124
|
-
return changedPackages[0]!;
|
|
125
|
-
}
|
|
126
|
-
|
|
127
|
-
throw new Error(
|
|
128
|
-
`Could not determine installed package from spec '${packageSpec}'. Run 'extension list' to inspect extension store state.`,
|
|
129
|
-
);
|
|
130
|
-
}
|
|
131
|
-
|
|
132
|
-
export class ExtensionStoreManager {
|
|
133
|
-
constructor(
|
|
134
|
-
private readonly logger: GatewayLogger,
|
|
135
|
-
private readonly extensionsDir: string,
|
|
136
|
-
) { }
|
|
137
|
-
|
|
138
|
-
async ensureStoreInitialized(): Promise<void> {
|
|
139
|
-
await mkdir(this.extensionsDir, { recursive: true });
|
|
140
|
-
const storePackageJsonPath = this.storePackageJsonPath();
|
|
141
|
-
try {
|
|
142
|
-
await access(storePackageJsonPath);
|
|
143
|
-
return;
|
|
144
|
-
} catch {
|
|
145
|
-
const payload: StorePackageJson = {
|
|
146
|
-
name: STORE_PACKAGE_NAME,
|
|
147
|
-
private: true,
|
|
148
|
-
description: "Managed extension store for dobby",
|
|
149
|
-
};
|
|
150
|
-
await writeFile(storePackageJsonPath, `${JSON.stringify(payload, null, 2)}\n`, "utf-8");
|
|
151
|
-
}
|
|
152
|
-
}
|
|
153
|
-
|
|
154
|
-
async install(packageSpec: string): Promise<InstalledExtensionInfo> {
|
|
155
|
-
const installed = await this.installMany([packageSpec]);
|
|
156
|
-
if (installed.length === 0) {
|
|
157
|
-
throw new Error(`Failed to install package from spec '${packageSpec}'`);
|
|
158
|
-
}
|
|
159
|
-
|
|
160
|
-
return installed[0]!;
|
|
161
|
-
}
|
|
162
|
-
|
|
163
|
-
async installMany(packageSpecs: string[]): Promise<InstalledExtensionInfo[]> {
|
|
164
|
-
const normalizedSpecs = packageSpecs
|
|
165
|
-
.map((item) => item.trim())
|
|
166
|
-
.filter((item) => item.length > 0);
|
|
167
|
-
|
|
168
|
-
if (normalizedSpecs.length === 0) {
|
|
169
|
-
return [];
|
|
170
|
-
}
|
|
171
|
-
|
|
172
|
-
await this.ensureStoreInitialized();
|
|
173
|
-
const beforeDeps = await this.readDependencies();
|
|
174
|
-
|
|
175
|
-
await this.runNpm(["install", "--prefix", this.extensionsDir, "--save-exact", ...normalizedSpecs]);
|
|
176
|
-
|
|
177
|
-
const afterDeps = await this.readDependencies();
|
|
178
|
-
const seenPackages = new Set<string>();
|
|
179
|
-
const installedPackages: InstalledExtensionInfo[] = [];
|
|
180
|
-
|
|
181
|
-
for (const spec of normalizedSpecs) {
|
|
182
|
-
const packageName = await this.resolveInstalledPackageName(spec, beforeDeps, afterDeps, normalizedSpecs.length > 1);
|
|
183
|
-
if (seenPackages.has(packageName)) {
|
|
184
|
-
continue;
|
|
185
|
-
}
|
|
186
|
-
seenPackages.add(packageName);
|
|
187
|
-
|
|
188
|
-
const version = afterDeps[packageName];
|
|
189
|
-
if (!version) {
|
|
190
|
-
throw new Error(`Package '${packageName}' is not present in extension store after installation`);
|
|
191
|
-
}
|
|
192
|
-
|
|
193
|
-
const installed = await this.readInstalledExtension(packageName, version);
|
|
194
|
-
this.logger.info(
|
|
195
|
-
{
|
|
196
|
-
package: installed.packageName,
|
|
197
|
-
version: installed.version,
|
|
198
|
-
contributions: installed.manifest.contributions.map((item) => `${item.kind}:${item.id}`),
|
|
199
|
-
},
|
|
200
|
-
"Extension installed",
|
|
201
|
-
);
|
|
202
|
-
installedPackages.push(installed);
|
|
203
|
-
}
|
|
204
|
-
|
|
205
|
-
return installedPackages;
|
|
206
|
-
}
|
|
207
|
-
|
|
208
|
-
async uninstall(packageName: string): Promise<void> {
|
|
209
|
-
await this.ensureStoreInitialized();
|
|
210
|
-
await this.runNpm(["uninstall", "--prefix", this.extensionsDir, packageName]);
|
|
211
|
-
this.logger.info({ package: packageName }, "Extension uninstalled");
|
|
212
|
-
}
|
|
213
|
-
|
|
214
|
-
async listInstalled(): Promise<ListedExtensionInfo[]> {
|
|
215
|
-
await this.ensureStoreInitialized();
|
|
216
|
-
const dependencies = await this.readDependencies();
|
|
217
|
-
|
|
218
|
-
const listed: ListedExtensionInfo[] = [];
|
|
219
|
-
const names = Object.keys(dependencies).sort((a, b) => a.localeCompare(b));
|
|
220
|
-
for (const packageName of names) {
|
|
221
|
-
const version = dependencies[packageName]!;
|
|
222
|
-
try {
|
|
223
|
-
const installed = await this.readInstalledExtension(packageName, version);
|
|
224
|
-
listed.push({
|
|
225
|
-
packageName,
|
|
226
|
-
version,
|
|
227
|
-
manifest: installed.manifest,
|
|
228
|
-
});
|
|
229
|
-
} catch (error) {
|
|
230
|
-
listed.push({
|
|
231
|
-
packageName,
|
|
232
|
-
version,
|
|
233
|
-
error: error instanceof Error ? error.message : String(error),
|
|
234
|
-
});
|
|
235
|
-
}
|
|
236
|
-
}
|
|
237
|
-
|
|
238
|
-
return listed;
|
|
239
|
-
}
|
|
240
|
-
|
|
241
|
-
private storePackageJsonPath(): string {
|
|
242
|
-
return join(this.extensionsDir, "package.json");
|
|
243
|
-
}
|
|
244
|
-
|
|
245
|
-
private createStoreRequire(): NodeRequire {
|
|
246
|
-
return createRequire(this.storePackageJsonPath());
|
|
247
|
-
}
|
|
248
|
-
|
|
249
|
-
private async readDependencies(): Promise<Record<string, string>> {
|
|
250
|
-
const path = this.storePackageJsonPath();
|
|
251
|
-
try {
|
|
252
|
-
const raw = await readFile(path, "utf-8");
|
|
253
|
-
const parsed = JSON.parse(raw) as StorePackageJson;
|
|
254
|
-
return parsed.dependencies ?? {};
|
|
255
|
-
} catch {
|
|
256
|
-
return {};
|
|
257
|
-
}
|
|
258
|
-
}
|
|
259
|
-
|
|
260
|
-
private async readInstalledExtension(packageName: string, version: string): Promise<InstalledExtensionInfo> {
|
|
261
|
-
const storeRequire = this.createStoreRequire();
|
|
262
|
-
|
|
263
|
-
let packageJsonPath: string;
|
|
264
|
-
try {
|
|
265
|
-
packageJsonPath = storeRequire.resolve(`${packageName}/package.json`);
|
|
266
|
-
} catch (error) {
|
|
267
|
-
throw new Error(
|
|
268
|
-
`Package '${packageName}' is declared in extension store but cannot be resolved: ${error instanceof Error ? error.message : String(error)}`,
|
|
269
|
-
);
|
|
270
|
-
}
|
|
271
|
-
|
|
272
|
-
const packageRoot = dirname(packageJsonPath);
|
|
273
|
-
const manifestPath = resolve(packageRoot, "dobby.manifest.json");
|
|
274
|
-
const manifest = await readExtensionManifest(manifestPath);
|
|
275
|
-
|
|
276
|
-
for (const contribution of manifest.contributions) {
|
|
277
|
-
if (!isJavaScriptEntry(contribution.entry)) {
|
|
278
|
-
throw new Error(
|
|
279
|
-
`Contribution '${contribution.id}' in package '${packageName}' must use a built JavaScript entry, got '${contribution.entry}'`,
|
|
280
|
-
);
|
|
281
|
-
}
|
|
282
|
-
|
|
283
|
-
const entryPath = resolve(packageRoot, contribution.entry);
|
|
284
|
-
assertWithinRoot(entryPath, packageRoot);
|
|
285
|
-
try {
|
|
286
|
-
await access(entryPath);
|
|
287
|
-
} catch {
|
|
288
|
-
throw new Error(
|
|
289
|
-
`Contribution '${contribution.id}' in package '${packageName}' points to missing entry '${contribution.entry}'`,
|
|
290
|
-
);
|
|
291
|
-
}
|
|
292
|
-
}
|
|
293
|
-
|
|
294
|
-
return {
|
|
295
|
-
packageName,
|
|
296
|
-
version,
|
|
297
|
-
manifest,
|
|
298
|
-
};
|
|
299
|
-
}
|
|
300
|
-
|
|
301
|
-
private async resolveInstalledPackageName(
|
|
302
|
-
packageSpec: string,
|
|
303
|
-
beforeDeps: Record<string, string>,
|
|
304
|
-
afterDeps: Record<string, string>,
|
|
305
|
-
isBatchInstall: boolean,
|
|
306
|
-
): Promise<string> {
|
|
307
|
-
const inferred = parsePackageNameFromSpec(packageSpec);
|
|
308
|
-
if (inferred && afterDeps[inferred]) {
|
|
309
|
-
return inferred;
|
|
310
|
-
}
|
|
311
|
-
|
|
312
|
-
const inferredLocal = await parsePackageNameFromLocalSpec(packageSpec);
|
|
313
|
-
if (inferredLocal && afterDeps[inferredLocal]) {
|
|
314
|
-
return inferredLocal;
|
|
315
|
-
}
|
|
316
|
-
|
|
317
|
-
if (!isBatchInstall) {
|
|
318
|
-
return pickInstalledPackageName(packageSpec, beforeDeps, afterDeps);
|
|
319
|
-
}
|
|
320
|
-
|
|
321
|
-
const changedPackages = Object.entries(afterDeps)
|
|
322
|
-
.filter(([name, version]) => beforeDeps[name] !== version)
|
|
323
|
-
.map(([name]) => name);
|
|
324
|
-
if (changedPackages.length === 1) {
|
|
325
|
-
return changedPackages[0]!;
|
|
326
|
-
}
|
|
327
|
-
|
|
328
|
-
throw new Error(
|
|
329
|
-
`Could not determine installed package from spec '${packageSpec}' in batch install mode. ` +
|
|
330
|
-
"Use explicit npm package names for init/installMany.",
|
|
331
|
-
);
|
|
332
|
-
}
|
|
333
|
-
|
|
334
|
-
private async runNpm(args: string[]): Promise<void> {
|
|
335
|
-
await new Promise<void>((resolvePromise, rejectPromise) => {
|
|
336
|
-
const command = process.platform === "win32" ? "npm.cmd" : "npm";
|
|
337
|
-
const child = spawn(command, args, {
|
|
338
|
-
stdio: "inherit",
|
|
339
|
-
env: process.env,
|
|
340
|
-
});
|
|
341
|
-
|
|
342
|
-
child.once("error", (error) => {
|
|
343
|
-
rejectPromise(error);
|
|
344
|
-
});
|
|
345
|
-
|
|
346
|
-
child.once("exit", (code) => {
|
|
347
|
-
if (code === 0) {
|
|
348
|
-
resolvePromise();
|
|
349
|
-
return;
|
|
350
|
-
}
|
|
351
|
-
rejectPromise(new Error(`npm ${args.join(" ")} failed with exit code ${code ?? "unknown"}`));
|
|
352
|
-
});
|
|
353
|
-
});
|
|
354
|
-
}
|
|
355
|
-
}
|
|
@@ -1,26 +0,0 @@
|
|
|
1
|
-
import { readFile } from "node:fs/promises";
|
|
2
|
-
import { z } from "zod";
|
|
3
|
-
import type { ExtensionManifest } from "../core/types.js";
|
|
4
|
-
|
|
5
|
-
const contributionManifestSchema = z.object({
|
|
6
|
-
id: z.string().min(1),
|
|
7
|
-
kind: z.enum(["provider", "connector", "sandbox"]),
|
|
8
|
-
entry: z.string().min(1),
|
|
9
|
-
capabilities: z.record(z.string(), z.unknown()).optional(),
|
|
10
|
-
});
|
|
11
|
-
|
|
12
|
-
const manifestSchema = z.object({
|
|
13
|
-
apiVersion: z.string().min(1),
|
|
14
|
-
name: z.string().min(1),
|
|
15
|
-
version: z.string().min(1),
|
|
16
|
-
contributions: z.array(contributionManifestSchema).min(1),
|
|
17
|
-
});
|
|
18
|
-
|
|
19
|
-
export function parseExtensionManifest(value: unknown): ExtensionManifest {
|
|
20
|
-
return manifestSchema.parse(value) as ExtensionManifest;
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
export async function readExtensionManifest(manifestPath: string): Promise<ExtensionManifest> {
|
|
24
|
-
const raw = await readFile(manifestPath, "utf-8");
|
|
25
|
-
return parseExtensionManifest(JSON.parse(raw));
|
|
26
|
-
}
|
|
@@ -1,229 +0,0 @@
|
|
|
1
|
-
import { join } from "node:path";
|
|
2
|
-
import type {
|
|
3
|
-
ConnectorContributionModule,
|
|
4
|
-
ConnectorPlugin,
|
|
5
|
-
ConnectorsConfig,
|
|
6
|
-
DataConfig,
|
|
7
|
-
ExtensionHostContext,
|
|
8
|
-
ProviderContributionModule,
|
|
9
|
-
ProvidersConfig,
|
|
10
|
-
ProviderInstance,
|
|
11
|
-
SandboxContributionModule,
|
|
12
|
-
SandboxesConfig,
|
|
13
|
-
SandboxInstance,
|
|
14
|
-
} from "../core/types.js";
|
|
15
|
-
import type { LoadedExtensionPackage } from "./loader.js";
|
|
16
|
-
|
|
17
|
-
interface ProviderContributionRegistration {
|
|
18
|
-
contributionId: string;
|
|
19
|
-
packageName: string;
|
|
20
|
-
createInstance: (options: {
|
|
21
|
-
instanceId: string;
|
|
22
|
-
config: Record<string, unknown>;
|
|
23
|
-
host: ExtensionHostContext;
|
|
24
|
-
data: DataConfig;
|
|
25
|
-
}) => Promise<ProviderInstance> | ProviderInstance;
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
interface ConnectorContributionRegistration {
|
|
29
|
-
contributionId: string;
|
|
30
|
-
packageName: string;
|
|
31
|
-
createInstance: (options: {
|
|
32
|
-
instanceId: string;
|
|
33
|
-
config: Record<string, unknown>;
|
|
34
|
-
host: ExtensionHostContext;
|
|
35
|
-
attachmentsRoot: string;
|
|
36
|
-
}) => Promise<ConnectorPlugin> | ConnectorPlugin;
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
interface SandboxContributionRegistration {
|
|
40
|
-
contributionId: string;
|
|
41
|
-
packageName: string;
|
|
42
|
-
createInstance: (options: {
|
|
43
|
-
instanceId: string;
|
|
44
|
-
config: Record<string, unknown>;
|
|
45
|
-
host: ExtensionHostContext;
|
|
46
|
-
}) => Promise<SandboxInstance> | SandboxInstance;
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
export interface RegisteredContributionSchemaInfo {
|
|
50
|
-
contributionId: string;
|
|
51
|
-
packageName: string;
|
|
52
|
-
kind: "provider" | "connector" | "sandbox";
|
|
53
|
-
configSchema?: Record<string, unknown>;
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
function isRecord(value: unknown): value is Record<string, unknown> {
|
|
57
|
-
return Boolean(value) && typeof value === "object" && !Array.isArray(value);
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
function normalizeContributionSchema(value: unknown): Record<string, unknown> | undefined {
|
|
61
|
-
if (!isRecord(value)) {
|
|
62
|
-
return undefined;
|
|
63
|
-
}
|
|
64
|
-
return value;
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
export class ExtensionRegistry {
|
|
68
|
-
private readonly providers = new Map<string, ProviderContributionRegistration>();
|
|
69
|
-
private readonly connectors = new Map<string, ConnectorContributionRegistration>();
|
|
70
|
-
private readonly sandboxes = new Map<string, SandboxContributionRegistration>();
|
|
71
|
-
private readonly contributionSchemas = new Map<string, RegisteredContributionSchemaInfo>();
|
|
72
|
-
|
|
73
|
-
registerPackages(loadedPackages: LoadedExtensionPackage[]): void {
|
|
74
|
-
for (const extensionPackage of loadedPackages) {
|
|
75
|
-
for (const contribution of extensionPackage.contributions) {
|
|
76
|
-
if (this.contributionSchemas.has(contribution.manifest.id)) {
|
|
77
|
-
throw new Error(`Duplicate contribution id '${contribution.manifest.id}'`);
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
const normalizedSchema = normalizeContributionSchema(
|
|
81
|
-
(contribution.module as { configSchema?: unknown }).configSchema,
|
|
82
|
-
);
|
|
83
|
-
this.contributionSchemas.set(contribution.manifest.id, {
|
|
84
|
-
contributionId: contribution.manifest.id,
|
|
85
|
-
packageName: extensionPackage.packageName,
|
|
86
|
-
kind: contribution.manifest.kind,
|
|
87
|
-
...(normalizedSchema ? { configSchema: normalizedSchema } : {}),
|
|
88
|
-
});
|
|
89
|
-
|
|
90
|
-
if (contribution.manifest.kind === "provider") {
|
|
91
|
-
const module = contribution.module as ProviderContributionModule;
|
|
92
|
-
if (this.providers.has(contribution.manifest.id)) {
|
|
93
|
-
throw new Error(`Duplicate provider contribution id '${contribution.manifest.id}'`);
|
|
94
|
-
}
|
|
95
|
-
this.providers.set(contribution.manifest.id, {
|
|
96
|
-
contributionId: contribution.manifest.id,
|
|
97
|
-
packageName: extensionPackage.packageName,
|
|
98
|
-
createInstance: module.createInstance,
|
|
99
|
-
});
|
|
100
|
-
continue;
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
if (contribution.manifest.kind === "connector") {
|
|
104
|
-
const module = contribution.module as ConnectorContributionModule;
|
|
105
|
-
if (this.connectors.has(contribution.manifest.id)) {
|
|
106
|
-
throw new Error(`Duplicate connector contribution id '${contribution.manifest.id}'`);
|
|
107
|
-
}
|
|
108
|
-
this.connectors.set(contribution.manifest.id, {
|
|
109
|
-
contributionId: contribution.manifest.id,
|
|
110
|
-
packageName: extensionPackage.packageName,
|
|
111
|
-
createInstance: module.createInstance,
|
|
112
|
-
});
|
|
113
|
-
continue;
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
const module = contribution.module as SandboxContributionModule;
|
|
117
|
-
if (this.sandboxes.has(contribution.manifest.id)) {
|
|
118
|
-
throw new Error(`Duplicate sandbox contribution id '${contribution.manifest.id}'`);
|
|
119
|
-
}
|
|
120
|
-
this.sandboxes.set(contribution.manifest.id, {
|
|
121
|
-
contributionId: contribution.manifest.id,
|
|
122
|
-
packageName: extensionPackage.packageName,
|
|
123
|
-
createInstance: module.createInstance,
|
|
124
|
-
});
|
|
125
|
-
}
|
|
126
|
-
}
|
|
127
|
-
}
|
|
128
|
-
|
|
129
|
-
listContributionSchemas(): RegisteredContributionSchemaInfo[] {
|
|
130
|
-
return [...this.contributionSchemas.values()]
|
|
131
|
-
.sort((a, b) => a.contributionId.localeCompare(b.contributionId))
|
|
132
|
-
.map((item) => ({
|
|
133
|
-
contributionId: item.contributionId,
|
|
134
|
-
packageName: item.packageName,
|
|
135
|
-
kind: item.kind,
|
|
136
|
-
...(item.configSchema ? { configSchema: item.configSchema } : {}),
|
|
137
|
-
}));
|
|
138
|
-
}
|
|
139
|
-
|
|
140
|
-
getContributionSchema(contributionId: string): RegisteredContributionSchemaInfo | null {
|
|
141
|
-
const found = this.contributionSchemas.get(contributionId);
|
|
142
|
-
if (!found) {
|
|
143
|
-
return null;
|
|
144
|
-
}
|
|
145
|
-
|
|
146
|
-
return {
|
|
147
|
-
contributionId: found.contributionId,
|
|
148
|
-
packageName: found.packageName,
|
|
149
|
-
kind: found.kind,
|
|
150
|
-
...(found.configSchema ? { configSchema: found.configSchema } : {}),
|
|
151
|
-
};
|
|
152
|
-
}
|
|
153
|
-
|
|
154
|
-
async createProviderInstances(
|
|
155
|
-
config: ProvidersConfig,
|
|
156
|
-
context: ExtensionHostContext,
|
|
157
|
-
data: DataConfig,
|
|
158
|
-
): Promise<Map<string, ProviderInstance>> {
|
|
159
|
-
const instances = new Map<string, ProviderInstance>();
|
|
160
|
-
|
|
161
|
-
for (const [instanceId, instanceConfig] of Object.entries(config.items)) {
|
|
162
|
-
const contribution = this.providers.get(instanceConfig.type);
|
|
163
|
-
if (!contribution) {
|
|
164
|
-
throw new Error(
|
|
165
|
-
`Provider instance '${instanceId}' references unknown contribution '${instanceConfig.type}'`,
|
|
166
|
-
);
|
|
167
|
-
}
|
|
168
|
-
const instance = await contribution.createInstance({
|
|
169
|
-
instanceId,
|
|
170
|
-
config: instanceConfig.config,
|
|
171
|
-
host: context,
|
|
172
|
-
data,
|
|
173
|
-
});
|
|
174
|
-
instances.set(instanceId, instance);
|
|
175
|
-
}
|
|
176
|
-
|
|
177
|
-
return instances;
|
|
178
|
-
}
|
|
179
|
-
|
|
180
|
-
async createConnectorInstances(
|
|
181
|
-
config: ConnectorsConfig,
|
|
182
|
-
context: ExtensionHostContext,
|
|
183
|
-
attachmentsBaseDir: string,
|
|
184
|
-
): Promise<ConnectorPlugin[]> {
|
|
185
|
-
const instances: ConnectorPlugin[] = [];
|
|
186
|
-
|
|
187
|
-
for (const [instanceId, instanceConfig] of Object.entries(config.items)) {
|
|
188
|
-
const contribution = this.connectors.get(instanceConfig.type);
|
|
189
|
-
if (!contribution) {
|
|
190
|
-
throw new Error(
|
|
191
|
-
`Connector instance '${instanceId}' references unknown contribution '${instanceConfig.type}'`,
|
|
192
|
-
);
|
|
193
|
-
}
|
|
194
|
-
const connector = await contribution.createInstance({
|
|
195
|
-
instanceId,
|
|
196
|
-
config: instanceConfig.config,
|
|
197
|
-
host: context,
|
|
198
|
-
attachmentsRoot: join(attachmentsBaseDir, instanceId),
|
|
199
|
-
});
|
|
200
|
-
instances.push(connector);
|
|
201
|
-
}
|
|
202
|
-
|
|
203
|
-
return instances;
|
|
204
|
-
}
|
|
205
|
-
|
|
206
|
-
async createSandboxInstances(
|
|
207
|
-
config: SandboxesConfig,
|
|
208
|
-
context: ExtensionHostContext,
|
|
209
|
-
): Promise<Map<string, SandboxInstance>> {
|
|
210
|
-
const instances = new Map<string, SandboxInstance>();
|
|
211
|
-
|
|
212
|
-
for (const [instanceId, instanceConfig] of Object.entries(config.items)) {
|
|
213
|
-
const contribution = this.sandboxes.get(instanceConfig.type);
|
|
214
|
-
if (!contribution) {
|
|
215
|
-
throw new Error(
|
|
216
|
-
`Sandbox instance '${instanceId}' references unknown contribution '${instanceConfig.type}'`,
|
|
217
|
-
);
|
|
218
|
-
}
|
|
219
|
-
const sandbox = await contribution.createInstance({
|
|
220
|
-
instanceId,
|
|
221
|
-
config: instanceConfig.config,
|
|
222
|
-
host: context,
|
|
223
|
-
});
|
|
224
|
-
instances.set(instanceId, sandbox);
|
|
225
|
-
}
|
|
226
|
-
|
|
227
|
-
return instances;
|
|
228
|
-
}
|
|
229
|
-
}
|
package/src/main.ts
DELETED
package/src/sandbox/executor.ts
DELETED
|
@@ -1,44 +0,0 @@
|
|
|
1
|
-
import type { GatewayLogger } from "../core/types.js";
|
|
2
|
-
|
|
3
|
-
export interface ExecOptions {
|
|
4
|
-
timeoutSeconds?: number;
|
|
5
|
-
signal?: AbortSignal;
|
|
6
|
-
env?: NodeJS.ProcessEnv;
|
|
7
|
-
}
|
|
8
|
-
|
|
9
|
-
export interface ExecResult {
|
|
10
|
-
stdout: string;
|
|
11
|
-
stderr: string;
|
|
12
|
-
code: number;
|
|
13
|
-
killed: boolean;
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
export interface SpawnOptions {
|
|
17
|
-
command: string;
|
|
18
|
-
args: string[];
|
|
19
|
-
cwd?: string;
|
|
20
|
-
env?: NodeJS.ProcessEnv;
|
|
21
|
-
signal?: AbortSignal;
|
|
22
|
-
tty?: boolean;
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
export interface SpawnedProcess {
|
|
26
|
-
stdin: NodeJS.WritableStream;
|
|
27
|
-
stdout: NodeJS.ReadableStream;
|
|
28
|
-
stderr: NodeJS.ReadableStream;
|
|
29
|
-
readonly killed: boolean;
|
|
30
|
-
readonly exitCode: number | null;
|
|
31
|
-
kill(signal?: NodeJS.Signals): boolean;
|
|
32
|
-
on(event: "exit", listener: (code: number | null, signal: NodeJS.Signals | null) => void): void;
|
|
33
|
-
on(event: "error", listener: (error: Error) => void): void;
|
|
34
|
-
once(event: "exit", listener: (code: number | null, signal: NodeJS.Signals | null) => void): void;
|
|
35
|
-
once(event: "error", listener: (error: Error) => void): void;
|
|
36
|
-
off(event: "exit", listener: (code: number | null, signal: NodeJS.Signals | null) => void): void;
|
|
37
|
-
off(event: "error", listener: (error: Error) => void): void;
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
export interface Executor {
|
|
41
|
-
exec(command: string, cwd: string, options?: ExecOptions): Promise<ExecResult>;
|
|
42
|
-
spawn(options: SpawnOptions): SpawnedProcess;
|
|
43
|
-
close(): Promise<void>;
|
|
44
|
-
}
|