@geminixiang/mama 0.2.0-beta.0 → 0.2.0-beta.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 +94 -27
- package/dist/adapter.d.ts +9 -5
- package/dist/adapter.d.ts.map +1 -1
- package/dist/adapter.js.map +1 -1
- package/dist/adapters/discord/bot.d.ts.map +1 -1
- package/dist/adapters/discord/bot.js +9 -6
- package/dist/adapters/discord/bot.js.map +1 -1
- package/dist/adapters/discord/context.d.ts.map +1 -1
- package/dist/adapters/discord/context.js +16 -13
- package/dist/adapters/discord/context.js.map +1 -1
- package/dist/adapters/slack/bot.d.ts +10 -2
- package/dist/adapters/slack/bot.d.ts.map +1 -1
- package/dist/adapters/slack/bot.js +196 -32
- package/dist/adapters/slack/bot.js.map +1 -1
- package/dist/adapters/slack/context.d.ts.map +1 -1
- package/dist/adapters/slack/context.js +24 -17
- package/dist/adapters/slack/context.js.map +1 -1
- package/dist/adapters/telegram/bot.d.ts +2 -0
- package/dist/adapters/telegram/bot.d.ts.map +1 -1
- package/dist/adapters/telegram/bot.js +109 -29
- package/dist/adapters/telegram/bot.js.map +1 -1
- package/dist/adapters/telegram/context.d.ts.map +1 -1
- package/dist/adapters/telegram/context.js +8 -43
- package/dist/adapters/telegram/context.js.map +1 -1
- package/dist/adapters/telegram/html.d.ts +3 -0
- package/dist/adapters/telegram/html.d.ts.map +1 -0
- package/dist/adapters/telegram/html.js +98 -0
- package/dist/adapters/telegram/html.js.map +1 -0
- package/dist/agent.d.ts +4 -9
- package/dist/agent.d.ts.map +1 -1
- package/dist/agent.js +141 -92
- package/dist/agent.js.map +1 -1
- package/dist/bindings.d.ts +44 -0
- package/dist/bindings.d.ts.map +1 -0
- package/dist/bindings.js +74 -0
- package/dist/bindings.js.map +1 -0
- package/dist/config.d.ts +7 -0
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +53 -12
- package/dist/config.js.map +1 -1
- package/dist/context.d.ts +7 -7
- package/dist/context.d.ts.map +1 -1
- package/dist/context.js +9 -9
- package/dist/context.js.map +1 -1
- package/dist/events.d.ts +14 -5
- package/dist/events.d.ts.map +1 -1
- package/dist/events.js +45 -10
- package/dist/events.js.map +1 -1
- package/dist/execution-resolver.d.ts +20 -0
- package/dist/execution-resolver.d.ts.map +1 -0
- package/dist/execution-resolver.js +49 -0
- package/dist/execution-resolver.js.map +1 -0
- package/dist/instrument.d.ts.map +1 -1
- package/dist/instrument.js +2 -1
- package/dist/instrument.js.map +1 -1
- package/dist/link-server.d.ts +17 -0
- package/dist/link-server.d.ts.map +1 -0
- package/dist/link-server.js +899 -0
- package/dist/link-server.js.map +1 -0
- package/dist/link-token.d.ts +32 -0
- package/dist/link-token.d.ts.map +1 -0
- package/dist/link-token.js +68 -0
- package/dist/link-token.js.map +1 -0
- package/dist/log.d.ts +2 -2
- package/dist/log.d.ts.map +1 -1
- package/dist/log.js +7 -7
- package/dist/log.js.map +1 -1
- package/dist/login.d.ts +29 -0
- package/dist/login.d.ts.map +1 -0
- package/dist/login.js +164 -0
- package/dist/login.js.map +1 -0
- package/dist/main.d.ts.map +1 -1
- package/dist/main.js +226 -55
- package/dist/main.js.map +1 -1
- package/dist/provisioner.d.ts +52 -0
- package/dist/provisioner.d.ts.map +1 -0
- package/dist/provisioner.js +291 -0
- package/dist/provisioner.js.map +1 -0
- package/dist/sandbox/container.d.ts +15 -0
- package/dist/sandbox/container.d.ts.map +1 -0
- package/dist/sandbox/container.js +122 -0
- package/dist/sandbox/container.js.map +1 -0
- package/dist/sandbox/errors.d.ts +6 -0
- package/dist/sandbox/errors.d.ts.map +1 -0
- package/dist/sandbox/errors.js +11 -0
- package/dist/sandbox/errors.js.map +1 -0
- package/dist/sandbox/firecracker.d.ts +16 -0
- package/dist/sandbox/firecracker.d.ts.map +1 -0
- package/dist/sandbox/firecracker.js +206 -0
- package/dist/sandbox/firecracker.js.map +1 -0
- package/dist/sandbox/host.d.ts +10 -0
- package/dist/sandbox/host.d.ts.map +1 -0
- package/dist/sandbox/host.js +85 -0
- package/dist/sandbox/host.js.map +1 -0
- package/dist/sandbox/image.d.ts +5 -0
- package/dist/sandbox/image.d.ts.map +1 -0
- package/dist/sandbox/image.js +30 -0
- package/dist/sandbox/image.js.map +1 -0
- package/dist/sandbox/index.d.ts +20 -0
- package/dist/sandbox/index.d.ts.map +1 -0
- package/dist/sandbox/index.js +51 -0
- package/dist/sandbox/index.js.map +1 -0
- package/dist/sandbox/types.d.ts +51 -0
- package/dist/sandbox/types.d.ts.map +1 -0
- package/dist/sandbox/types.js +2 -0
- package/dist/sandbox/types.js.map +1 -0
- package/dist/sandbox/utils.d.ts +4 -0
- package/dist/sandbox/utils.d.ts.map +1 -0
- package/dist/sandbox/utils.js +51 -0
- package/dist/sandbox/utils.js.map +1 -0
- package/dist/sandbox.d.ts +1 -39
- package/dist/sandbox.d.ts.map +1 -1
- package/dist/sandbox.js +1 -286
- package/dist/sandbox.js.map +1 -1
- package/dist/sentry.d.ts +1 -1
- package/dist/sentry.d.ts.map +1 -1
- package/dist/sentry.js +4 -2
- package/dist/sentry.js.map +1 -1
- package/dist/session-store.d.ts +2 -6
- package/dist/session-store.d.ts.map +1 -1
- package/dist/session-store.js +3 -10
- package/dist/session-store.js.map +1 -1
- package/dist/store.d.ts +1 -1
- package/dist/store.d.ts.map +1 -1
- package/dist/store.js +8 -8
- package/dist/store.js.map +1 -1
- package/dist/tools/event.d.ts +22 -0
- package/dist/tools/event.d.ts.map +1 -0
- package/dist/tools/event.js +104 -0
- package/dist/tools/event.js.map +1 -0
- package/dist/tools/index.d.ts +7 -1
- package/dist/tools/index.d.ts.map +1 -1
- package/dist/tools/index.js +5 -1
- package/dist/tools/index.js.map +1 -1
- package/dist/ui-copy.d.ts +12 -0
- package/dist/ui-copy.d.ts.map +1 -0
- package/dist/ui-copy.js +36 -0
- package/dist/ui-copy.js.map +1 -0
- package/dist/vault-routing.d.ts +9 -0
- package/dist/vault-routing.d.ts.map +1 -0
- package/dist/vault-routing.js +52 -0
- package/dist/vault-routing.js.map +1 -0
- package/dist/vault.d.ts +106 -0
- package/dist/vault.d.ts.map +1 -0
- package/dist/vault.js +389 -0
- package/dist/vault.js.map +1 -0
- package/package.json +12 -11
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"image.js","sourceRoot":"","sources":["../../src/sandbox/image.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAC3C,OAAO,EAAE,UAAU,EAAE,MAAM,YAAY,CAAC;AAExC,MAAM,UAAU,oBAAoB,CAAC,KAAa;IAChD,IAAI,CAAC,KAAK,CAAC,UAAU,CAAC,QAAQ,CAAC,EAAE,CAAC;QAChC,OAAO,SAAS,CAAC;IACnB,CAAC;IAED,MAAM,KAAK,GAAG,KAAK,CAAC,KAAK,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC;IAC3C,IAAI,CAAC,KAAK,EAAE,CAAC;QACX,MAAM,IAAI,YAAY,CAAC,qEAAqE,CAAC,CAAC;IAChG,CAAC;IACD,OAAO,EAAE,IAAI,EAAE,OAAO,EAAE,KAAK,EAAE,CAAC;AAClC,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,oBAAoB,CAAC,MAA0B;IACnE,IAAI,CAAC;QACH,MAAM,UAAU,CAAC,QAAQ,EAAE,CAAC,WAAW,CAAC,CAAC,CAAC;IAC5C,CAAC;IAAC,MAAM,CAAC;QACP,MAAM,IAAI,YAAY,CAAC,+CAA+C,CAAC,CAAC;IAC1E,CAAC;IACD,OAAO,CAAC,GAAG,CAAC,6CAA6C,MAAM,CAAC,KAAK,EAAE,CAAC,CAAC;AAC3E,CAAC;AAED,MAAM,CAAC,MAAM,mBAAmB,GAAuC;IACrE,IAAI,EAAE,OAAO;IACb,KAAK,EAAE,oBAAoB;IAC3B,QAAQ,EAAE,oBAAoB;IAC9B,cAAc,EAAE,GAAG,EAAE;QACnB,MAAM,IAAI,YAAY,CAAC,oEAAoE,CAAC,CAAC;IAC/F,CAAC;CACF,CAAC","sourcesContent":["import type { ImageSandboxConfig, SandboxAdapter } from \"./types.js\";\nimport { SandboxError } from \"./errors.js\";\nimport { execSimple } from \"./utils.js\";\n\nexport function parseImageSandboxArg(value: string): ImageSandboxConfig | undefined {\n if (!value.startsWith(\"image:\")) {\n return undefined;\n }\n\n const image = value.slice(\"image:\".length);\n if (!image) {\n throw new SandboxError(\"Error: image sandbox requires image name (e.g., image:ubuntu:24.04)\");\n }\n return { type: \"image\", image };\n}\n\nexport async function validateImageSandbox(config: ImageSandboxConfig): Promise<void> {\n try {\n await execSimple(\"docker\", [\"--version\"]);\n } catch {\n throw new SandboxError(\"Error: Docker is not installed or not in PATH\");\n }\n console.log(` Image auto-provisioning enabled. Image: ${config.image}`);\n}\n\nexport const imageSandboxAdapter: SandboxAdapter<ImageSandboxConfig> = {\n type: \"image\",\n parse: parseImageSandboxArg,\n validate: validateImageSandbox,\n createExecutor: () => {\n throw new SandboxError(\"Error: image sandbox must resolve to a concrete container executor\");\n },\n};\n"]}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { ContainerExecutor } from "./container.js";
|
|
2
|
+
import { FirecrackerExecutor } from "./firecracker.js";
|
|
3
|
+
import { HostExecutor } from "./host.js";
|
|
4
|
+
import type { Executor, SandboxAdapter, SandboxConfig } from "./types.js";
|
|
5
|
+
export type { ContainerSandboxConfig, ExecOptions, ExecResult, Executor, FirecrackerSandboxConfig, HostSandboxConfig, ImageSandboxConfig, SandboxAdapter, SandboxConfig, } from "./types.js";
|
|
6
|
+
export { ContainerExecutor, FirecrackerExecutor, HostExecutor };
|
|
7
|
+
export { SandboxError } from "./errors.js";
|
|
8
|
+
export { buildContainerExecCommand, containerSandboxAdapter, parseContainerSandboxArg, validateContainerSandbox, } from "./container.js";
|
|
9
|
+
export { firecrackerSandboxAdapter, parseFirecrackerSandboxArg, validateFirecrackerSandbox, } from "./firecracker.js";
|
|
10
|
+
export { hostSandboxAdapter, parseHostSandboxArg, validateHostSandbox } from "./host.js";
|
|
11
|
+
export { imageSandboxAdapter, parseImageSandboxArg, validateImageSandbox } from "./image.js";
|
|
12
|
+
declare const sandboxAdapters: readonly [SandboxAdapter<import("./types.js").HostSandboxConfig>, SandboxAdapter<import("./types.js").ContainerSandboxConfig>, SandboxAdapter<import("./types.js").ImageSandboxConfig>, SandboxAdapter<import("./types.js").FirecrackerSandboxConfig>];
|
|
13
|
+
export declare function getSandboxAdapters(): readonly [...typeof sandboxAdapters];
|
|
14
|
+
export declare function parseSandboxArg(value: string): SandboxConfig;
|
|
15
|
+
export declare function validateSandbox(config: SandboxConfig): Promise<void>;
|
|
16
|
+
/**
|
|
17
|
+
* Create an executor that runs commands on host, in a Docker container, or in a Firecracker VM.
|
|
18
|
+
*/
|
|
19
|
+
export declare function createExecutor(config: SandboxConfig, env?: Record<string, string>, ensureReady?: () => Promise<void>): Executor;
|
|
20
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/sandbox/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,iBAAiB,EAIlB,MAAM,gBAAgB,CAAC;AACxB,OAAO,EACL,mBAAmB,EAIpB,MAAM,kBAAkB,CAAC;AAC1B,OAAO,EACL,YAAY,EAIb,MAAM,WAAW,CAAC;AAGnB,OAAO,KAAK,EAAE,QAAQ,EAAE,cAAc,EAAE,aAAa,EAAE,MAAM,YAAY,CAAC;AAE1E,YAAY,EACV,sBAAsB,EACtB,WAAW,EACX,UAAU,EACV,QAAQ,EACR,wBAAwB,EACxB,iBAAiB,EACjB,kBAAkB,EAClB,cAAc,EACd,aAAa,GACd,MAAM,YAAY,CAAC;AACpB,OAAO,EAAE,iBAAiB,EAAE,mBAAmB,EAAE,YAAY,EAAE,CAAC;AAChE,OAAO,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAC3C,OAAO,EACL,yBAAyB,EACzB,uBAAuB,EACvB,wBAAwB,EACxB,wBAAwB,GACzB,MAAM,gBAAgB,CAAC;AACxB,OAAO,EACL,yBAAyB,EACzB,0BAA0B,EAC1B,0BAA0B,GAC3B,MAAM,kBAAkB,CAAC;AAC1B,OAAO,EAAE,kBAAkB,EAAE,mBAAmB,EAAE,mBAAmB,EAAE,MAAM,WAAW,CAAC;AACzF,OAAO,EAAE,mBAAmB,EAAE,oBAAoB,EAAE,oBAAoB,EAAE,MAAM,YAAY,CAAC;AAE7F,QAAA,MAAM,eAAe,wPAKX,CAAC;AAKX,wBAAgB,kBAAkB,IAAI,SAAS,CAAC,GAAG,OAAO,eAAe,CAAC,CAEzE;AAED,wBAAgB,eAAe,CAAC,KAAK,EAAE,MAAM,GAAG,aAAa,CAiB5D;AAED,wBAAsB,eAAe,CAAC,MAAM,EAAE,aAAa,GAAG,OAAO,CAAC,IAAI,CAAC,CAO1E;AAED;;GAEG;AACH,wBAAgB,cAAc,CAC5B,MAAM,EAAE,aAAa,EACrB,GAAG,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,EAC5B,WAAW,CAAC,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,GAChC,QAAQ,CAMV","sourcesContent":["import {\n ContainerExecutor,\n containerSandboxAdapter,\n parseContainerSandboxArg,\n validateContainerSandbox,\n} from \"./container.js\";\nimport {\n FirecrackerExecutor,\n firecrackerSandboxAdapter,\n parseFirecrackerSandboxArg,\n validateFirecrackerSandbox,\n} from \"./firecracker.js\";\nimport {\n HostExecutor,\n hostSandboxAdapter,\n parseHostSandboxArg,\n validateHostSandbox,\n} from \"./host.js\";\nimport { imageSandboxAdapter, parseImageSandboxArg, validateImageSandbox } from \"./image.js\";\nimport { SandboxError } from \"./errors.js\";\nimport type { Executor, SandboxAdapter, SandboxConfig } from \"./types.js\";\n\nexport type {\n ContainerSandboxConfig,\n ExecOptions,\n ExecResult,\n Executor,\n FirecrackerSandboxConfig,\n HostSandboxConfig,\n ImageSandboxConfig,\n SandboxAdapter,\n SandboxConfig,\n} from \"./types.js\";\nexport { ContainerExecutor, FirecrackerExecutor, HostExecutor };\nexport { SandboxError } from \"./errors.js\";\nexport {\n buildContainerExecCommand,\n containerSandboxAdapter,\n parseContainerSandboxArg,\n validateContainerSandbox,\n} from \"./container.js\";\nexport {\n firecrackerSandboxAdapter,\n parseFirecrackerSandboxArg,\n validateFirecrackerSandbox,\n} from \"./firecracker.js\";\nexport { hostSandboxAdapter, parseHostSandboxArg, validateHostSandbox } from \"./host.js\";\nexport { imageSandboxAdapter, parseImageSandboxArg, validateImageSandbox } from \"./image.js\";\n\nconst sandboxAdapters = [\n hostSandboxAdapter,\n containerSandboxAdapter,\n imageSandboxAdapter,\n firecrackerSandboxAdapter,\n] as const;\nconst sandboxAdapterByType = new Map(\n sandboxAdapters.map((adapter) => [adapter.type, adapter]),\n) as Map<SandboxConfig[\"type\"], SandboxAdapter>;\n\nexport function getSandboxAdapters(): readonly [...typeof sandboxAdapters] {\n return sandboxAdapters;\n}\n\nexport function parseSandboxArg(value: string): SandboxConfig {\n for (const adapter of sandboxAdapters) {\n const config = adapter.parse(value);\n if (config) {\n return config;\n }\n }\n\n if (value.startsWith(\"docker:\")) {\n throw new SandboxError(\n `Error: '${value}' is not supported. Use 'container:<container-name>' for the shared-container mode or 'image:<image-name>' for mama-managed per-user containers.`,\n );\n }\n\n throw new SandboxError(\n `Error: Invalid sandbox type '${value}'. Use 'host', 'container:<container-name>', 'image:<image-name>', or 'firecracker:<vm-id>:<host-path>'`,\n );\n}\n\nexport async function validateSandbox(config: SandboxConfig): Promise<void> {\n const adapter = sandboxAdapterByType.get(config.type);\n if (!adapter) {\n throw new SandboxError(`Error: Unsupported sandbox type '${config.type}'`);\n }\n\n await adapter.validate(config);\n}\n\n/**\n * Create an executor that runs commands on host, in a Docker container, or in a Firecracker VM.\n */\nexport function createExecutor(\n config: SandboxConfig,\n env?: Record<string, string>,\n ensureReady?: () => Promise<void>,\n): Executor {\n const adapter = sandboxAdapterByType.get(config.type);\n if (!adapter) {\n throw new SandboxError(`Error: Unsupported sandbox type '${config.type}'`);\n }\n return adapter.createExecutor(config, env, ensureReady);\n}\n"]}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import { ContainerExecutor, containerSandboxAdapter, } from "./container.js";
|
|
2
|
+
import { FirecrackerExecutor, firecrackerSandboxAdapter, } from "./firecracker.js";
|
|
3
|
+
import { HostExecutor, hostSandboxAdapter, } from "./host.js";
|
|
4
|
+
import { imageSandboxAdapter } from "./image.js";
|
|
5
|
+
import { SandboxError } from "./errors.js";
|
|
6
|
+
export { ContainerExecutor, FirecrackerExecutor, HostExecutor };
|
|
7
|
+
export { SandboxError } from "./errors.js";
|
|
8
|
+
export { buildContainerExecCommand, containerSandboxAdapter, parseContainerSandboxArg, validateContainerSandbox, } from "./container.js";
|
|
9
|
+
export { firecrackerSandboxAdapter, parseFirecrackerSandboxArg, validateFirecrackerSandbox, } from "./firecracker.js";
|
|
10
|
+
export { hostSandboxAdapter, parseHostSandboxArg, validateHostSandbox } from "./host.js";
|
|
11
|
+
export { imageSandboxAdapter, parseImageSandboxArg, validateImageSandbox } from "./image.js";
|
|
12
|
+
const sandboxAdapters = [
|
|
13
|
+
hostSandboxAdapter,
|
|
14
|
+
containerSandboxAdapter,
|
|
15
|
+
imageSandboxAdapter,
|
|
16
|
+
firecrackerSandboxAdapter,
|
|
17
|
+
];
|
|
18
|
+
const sandboxAdapterByType = new Map(sandboxAdapters.map((adapter) => [adapter.type, adapter]));
|
|
19
|
+
export function getSandboxAdapters() {
|
|
20
|
+
return sandboxAdapters;
|
|
21
|
+
}
|
|
22
|
+
export function parseSandboxArg(value) {
|
|
23
|
+
for (const adapter of sandboxAdapters) {
|
|
24
|
+
const config = adapter.parse(value);
|
|
25
|
+
if (config) {
|
|
26
|
+
return config;
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
if (value.startsWith("docker:")) {
|
|
30
|
+
throw new SandboxError(`Error: '${value}' is not supported. Use 'container:<container-name>' for the shared-container mode or 'image:<image-name>' for mama-managed per-user containers.`);
|
|
31
|
+
}
|
|
32
|
+
throw new SandboxError(`Error: Invalid sandbox type '${value}'. Use 'host', 'container:<container-name>', 'image:<image-name>', or 'firecracker:<vm-id>:<host-path>'`);
|
|
33
|
+
}
|
|
34
|
+
export async function validateSandbox(config) {
|
|
35
|
+
const adapter = sandboxAdapterByType.get(config.type);
|
|
36
|
+
if (!adapter) {
|
|
37
|
+
throw new SandboxError(`Error: Unsupported sandbox type '${config.type}'`);
|
|
38
|
+
}
|
|
39
|
+
await adapter.validate(config);
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* Create an executor that runs commands on host, in a Docker container, or in a Firecracker VM.
|
|
43
|
+
*/
|
|
44
|
+
export function createExecutor(config, env, ensureReady) {
|
|
45
|
+
const adapter = sandboxAdapterByType.get(config.type);
|
|
46
|
+
if (!adapter) {
|
|
47
|
+
throw new SandboxError(`Error: Unsupported sandbox type '${config.type}'`);
|
|
48
|
+
}
|
|
49
|
+
return adapter.createExecutor(config, env, ensureReady);
|
|
50
|
+
}
|
|
51
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/sandbox/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,iBAAiB,EACjB,uBAAuB,GAGxB,MAAM,gBAAgB,CAAC;AACxB,OAAO,EACL,mBAAmB,EACnB,yBAAyB,GAG1B,MAAM,kBAAkB,CAAC;AAC1B,OAAO,EACL,YAAY,EACZ,kBAAkB,GAGnB,MAAM,WAAW,CAAC;AACnB,OAAO,EAAE,mBAAmB,EAA8C,MAAM,YAAY,CAAC;AAC7F,OAAO,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAc3C,OAAO,EAAE,iBAAiB,EAAE,mBAAmB,EAAE,YAAY,EAAE,CAAC;AAChE,OAAO,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAC3C,OAAO,EACL,yBAAyB,EACzB,uBAAuB,EACvB,wBAAwB,EACxB,wBAAwB,GACzB,MAAM,gBAAgB,CAAC;AACxB,OAAO,EACL,yBAAyB,EACzB,0BAA0B,EAC1B,0BAA0B,GAC3B,MAAM,kBAAkB,CAAC;AAC1B,OAAO,EAAE,kBAAkB,EAAE,mBAAmB,EAAE,mBAAmB,EAAE,MAAM,WAAW,CAAC;AACzF,OAAO,EAAE,mBAAmB,EAAE,oBAAoB,EAAE,oBAAoB,EAAE,MAAM,YAAY,CAAC;AAE7F,MAAM,eAAe,GAAG;IACtB,kBAAkB;IAClB,uBAAuB;IACvB,mBAAmB;IACnB,yBAAyB;CACjB,CAAC;AACX,MAAM,oBAAoB,GAAG,IAAI,GAAG,CAClC,eAAe,CAAC,GAAG,CAAC,CAAC,OAAO,EAAE,EAAE,CAAC,CAAC,OAAO,CAAC,IAAI,EAAE,OAAO,CAAC,CAAC,CACZ,CAAC;AAEhD,MAAM,UAAU,kBAAkB;IAChC,OAAO,eAAe,CAAC;AACzB,CAAC;AAED,MAAM,UAAU,eAAe,CAAC,KAAa;IAC3C,KAAK,MAAM,OAAO,IAAI,eAAe,EAAE,CAAC;QACtC,MAAM,MAAM,GAAG,OAAO,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC;QACpC,IAAI,MAAM,EAAE,CAAC;YACX,OAAO,MAAM,CAAC;QAChB,CAAC;IACH,CAAC;IAED,IAAI,KAAK,CAAC,UAAU,CAAC,SAAS,CAAC,EAAE,CAAC;QAChC,MAAM,IAAI,YAAY,CACpB,WAAW,KAAK,kJAAkJ,CACnK,CAAC;IACJ,CAAC;IAED,MAAM,IAAI,YAAY,CACpB,gCAAgC,KAAK,yGAAyG,CAC/I,CAAC;AACJ,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,eAAe,CAAC,MAAqB;IACzD,MAAM,OAAO,GAAG,oBAAoB,CAAC,GAAG,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC;IACtD,IAAI,CAAC,OAAO,EAAE,CAAC;QACb,MAAM,IAAI,YAAY,CAAC,oCAAoC,MAAM,CAAC,IAAI,GAAG,CAAC,CAAC;IAC7E,CAAC;IAED,MAAM,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC;AACjC,CAAC;AAED;;GAEG;AACH,MAAM,UAAU,cAAc,CAC5B,MAAqB,EACrB,GAA4B,EAC5B,WAAiC;IAEjC,MAAM,OAAO,GAAG,oBAAoB,CAAC,GAAG,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC;IACtD,IAAI,CAAC,OAAO,EAAE,CAAC;QACb,MAAM,IAAI,YAAY,CAAC,oCAAoC,MAAM,CAAC,IAAI,GAAG,CAAC,CAAC;IAC7E,CAAC;IACD,OAAO,OAAO,CAAC,cAAc,CAAC,MAAM,EAAE,GAAG,EAAE,WAAW,CAAC,CAAC;AAC1D,CAAC","sourcesContent":["import {\n ContainerExecutor,\n containerSandboxAdapter,\n parseContainerSandboxArg,\n validateContainerSandbox,\n} from \"./container.js\";\nimport {\n FirecrackerExecutor,\n firecrackerSandboxAdapter,\n parseFirecrackerSandboxArg,\n validateFirecrackerSandbox,\n} from \"./firecracker.js\";\nimport {\n HostExecutor,\n hostSandboxAdapter,\n parseHostSandboxArg,\n validateHostSandbox,\n} from \"./host.js\";\nimport { imageSandboxAdapter, parseImageSandboxArg, validateImageSandbox } from \"./image.js\";\nimport { SandboxError } from \"./errors.js\";\nimport type { Executor, SandboxAdapter, SandboxConfig } from \"./types.js\";\n\nexport type {\n ContainerSandboxConfig,\n ExecOptions,\n ExecResult,\n Executor,\n FirecrackerSandboxConfig,\n HostSandboxConfig,\n ImageSandboxConfig,\n SandboxAdapter,\n SandboxConfig,\n} from \"./types.js\";\nexport { ContainerExecutor, FirecrackerExecutor, HostExecutor };\nexport { SandboxError } from \"./errors.js\";\nexport {\n buildContainerExecCommand,\n containerSandboxAdapter,\n parseContainerSandboxArg,\n validateContainerSandbox,\n} from \"./container.js\";\nexport {\n firecrackerSandboxAdapter,\n parseFirecrackerSandboxArg,\n validateFirecrackerSandbox,\n} from \"./firecracker.js\";\nexport { hostSandboxAdapter, parseHostSandboxArg, validateHostSandbox } from \"./host.js\";\nexport { imageSandboxAdapter, parseImageSandboxArg, validateImageSandbox } from \"./image.js\";\n\nconst sandboxAdapters = [\n hostSandboxAdapter,\n containerSandboxAdapter,\n imageSandboxAdapter,\n firecrackerSandboxAdapter,\n] as const;\nconst sandboxAdapterByType = new Map(\n sandboxAdapters.map((adapter) => [adapter.type, adapter]),\n) as Map<SandboxConfig[\"type\"], SandboxAdapter>;\n\nexport function getSandboxAdapters(): readonly [...typeof sandboxAdapters] {\n return sandboxAdapters;\n}\n\nexport function parseSandboxArg(value: string): SandboxConfig {\n for (const adapter of sandboxAdapters) {\n const config = adapter.parse(value);\n if (config) {\n return config;\n }\n }\n\n if (value.startsWith(\"docker:\")) {\n throw new SandboxError(\n `Error: '${value}' is not supported. Use 'container:<container-name>' for the shared-container mode or 'image:<image-name>' for mama-managed per-user containers.`,\n );\n }\n\n throw new SandboxError(\n `Error: Invalid sandbox type '${value}'. Use 'host', 'container:<container-name>', 'image:<image-name>', or 'firecracker:<vm-id>:<host-path>'`,\n );\n}\n\nexport async function validateSandbox(config: SandboxConfig): Promise<void> {\n const adapter = sandboxAdapterByType.get(config.type);\n if (!adapter) {\n throw new SandboxError(`Error: Unsupported sandbox type '${config.type}'`);\n }\n\n await adapter.validate(config);\n}\n\n/**\n * Create an executor that runs commands on host, in a Docker container, or in a Firecracker VM.\n */\nexport function createExecutor(\n config: SandboxConfig,\n env?: Record<string, string>,\n ensureReady?: () => Promise<void>,\n): Executor {\n const adapter = sandboxAdapterByType.get(config.type);\n if (!adapter) {\n throw new SandboxError(`Error: Unsupported sandbox type '${config.type}'`);\n }\n return adapter.createExecutor(config, env, ensureReady);\n}\n"]}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
export type SandboxConfig = HostSandboxConfig | ContainerSandboxConfig | ImageSandboxConfig | FirecrackerSandboxConfig;
|
|
2
|
+
export interface HostSandboxConfig {
|
|
3
|
+
type: "host";
|
|
4
|
+
}
|
|
5
|
+
export interface ContainerSandboxConfig {
|
|
6
|
+
type: "container";
|
|
7
|
+
container: string;
|
|
8
|
+
}
|
|
9
|
+
export interface ImageSandboxConfig {
|
|
10
|
+
type: "image";
|
|
11
|
+
image: string;
|
|
12
|
+
}
|
|
13
|
+
export interface FirecrackerSandboxConfig {
|
|
14
|
+
type: "firecracker";
|
|
15
|
+
vmId: string;
|
|
16
|
+
hostPath: string;
|
|
17
|
+
sshUser?: string;
|
|
18
|
+
sshPort?: number;
|
|
19
|
+
}
|
|
20
|
+
export interface Executor {
|
|
21
|
+
/**
|
|
22
|
+
* Execute a bash command.
|
|
23
|
+
*/
|
|
24
|
+
exec(command: string, options?: ExecOptions): Promise<ExecResult>;
|
|
25
|
+
/**
|
|
26
|
+
* Get the workspace path prefix for this executor.
|
|
27
|
+
* Host: returns the actual path.
|
|
28
|
+
* Container/Firecracker: returns /workspace.
|
|
29
|
+
*/
|
|
30
|
+
getWorkspacePath(hostPath: string): string;
|
|
31
|
+
/**
|
|
32
|
+
* Get the current sandbox config used by this executor.
|
|
33
|
+
*/
|
|
34
|
+
getSandboxConfig(): SandboxConfig;
|
|
35
|
+
}
|
|
36
|
+
export interface ExecOptions {
|
|
37
|
+
timeout?: number;
|
|
38
|
+
signal?: AbortSignal;
|
|
39
|
+
}
|
|
40
|
+
export interface ExecResult {
|
|
41
|
+
stdout: string;
|
|
42
|
+
stderr: string;
|
|
43
|
+
code: number;
|
|
44
|
+
}
|
|
45
|
+
export interface SandboxAdapter<TConfig extends SandboxConfig = SandboxConfig> {
|
|
46
|
+
type: TConfig["type"];
|
|
47
|
+
parse(value: string): TConfig | undefined;
|
|
48
|
+
validate(config: TConfig): Promise<void>;
|
|
49
|
+
createExecutor(config: TConfig, env?: Record<string, string>, ensureReady?: () => Promise<void>): Executor;
|
|
50
|
+
}
|
|
51
|
+
//# sourceMappingURL=types.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/sandbox/types.ts"],"names":[],"mappings":"AAAA,MAAM,MAAM,aAAa,GACrB,iBAAiB,GACjB,sBAAsB,GACtB,kBAAkB,GAClB,wBAAwB,CAAC;AAE7B,MAAM,WAAW,iBAAiB;IAChC,IAAI,EAAE,MAAM,CAAC;CACd;AAED,MAAM,WAAW,sBAAsB;IACrC,IAAI,EAAE,WAAW,CAAC;IAClB,SAAS,EAAE,MAAM,CAAC;CACnB;AAED,MAAM,WAAW,kBAAkB;IACjC,IAAI,EAAE,OAAO,CAAC;IACd,KAAK,EAAE,MAAM,CAAC;CACf;AAED,MAAM,WAAW,wBAAwB;IACvC,IAAI,EAAE,aAAa,CAAC;IACpB,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,EAAE,MAAM,CAAC;IACjB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AAED,MAAM,WAAW,QAAQ;IACvB;;OAEG;IACH,IAAI,CAAC,OAAO,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,WAAW,GAAG,OAAO,CAAC,UAAU,CAAC,CAAC;IAElE;;;;OAIG;IACH,gBAAgB,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,CAAC;IAE3C;;OAEG;IACH,gBAAgB,IAAI,aAAa,CAAC;CACnC;AAED,MAAM,WAAW,WAAW;IAC1B,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,MAAM,CAAC,EAAE,WAAW,CAAC;CACtB;AAED,MAAM,WAAW,UAAU;IACzB,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,MAAM,CAAC;IACf,IAAI,EAAE,MAAM,CAAC;CACd;AAED,MAAM,WAAW,cAAc,CAAC,OAAO,SAAS,aAAa,GAAG,aAAa;IAC3E,IAAI,EAAE,OAAO,CAAC,MAAM,CAAC,CAAC;IACtB,KAAK,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,GAAG,SAAS,CAAC;IAC1C,QAAQ,CAAC,MAAM,EAAE,OAAO,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IACzC,cAAc,CACZ,MAAM,EAAE,OAAO,EACf,GAAG,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,EAC5B,WAAW,CAAC,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,GAChC,QAAQ,CAAC;CACb","sourcesContent":["export type SandboxConfig =\n | HostSandboxConfig\n | ContainerSandboxConfig\n | ImageSandboxConfig\n | FirecrackerSandboxConfig;\n\nexport interface HostSandboxConfig {\n type: \"host\";\n}\n\nexport interface ContainerSandboxConfig {\n type: \"container\";\n container: string;\n}\n\nexport interface ImageSandboxConfig {\n type: \"image\";\n image: string;\n}\n\nexport interface FirecrackerSandboxConfig {\n type: \"firecracker\";\n vmId: string;\n hostPath: string;\n sshUser?: string;\n sshPort?: number;\n}\n\nexport interface Executor {\n /**\n * Execute a bash command.\n */\n exec(command: string, options?: ExecOptions): Promise<ExecResult>;\n\n /**\n * Get the workspace path prefix for this executor.\n * Host: returns the actual path.\n * Container/Firecracker: returns /workspace.\n */\n getWorkspacePath(hostPath: string): string;\n\n /**\n * Get the current sandbox config used by this executor.\n */\n getSandboxConfig(): SandboxConfig;\n}\n\nexport interface ExecOptions {\n timeout?: number;\n signal?: AbortSignal;\n}\n\nexport interface ExecResult {\n stdout: string;\n stderr: string;\n code: number;\n}\n\nexport interface SandboxAdapter<TConfig extends SandboxConfig = SandboxConfig> {\n type: TConfig[\"type\"];\n parse(value: string): TConfig | undefined;\n validate(config: TConfig): Promise<void>;\n createExecutor(\n config: TConfig,\n env?: Record<string, string>,\n ensureReady?: () => Promise<void>,\n ): Executor;\n}\n"]}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.js","sourceRoot":"","sources":["../../src/sandbox/types.ts"],"names":[],"mappings":"","sourcesContent":["export type SandboxConfig =\n | HostSandboxConfig\n | ContainerSandboxConfig\n | ImageSandboxConfig\n | FirecrackerSandboxConfig;\n\nexport interface HostSandboxConfig {\n type: \"host\";\n}\n\nexport interface ContainerSandboxConfig {\n type: \"container\";\n container: string;\n}\n\nexport interface ImageSandboxConfig {\n type: \"image\";\n image: string;\n}\n\nexport interface FirecrackerSandboxConfig {\n type: \"firecracker\";\n vmId: string;\n hostPath: string;\n sshUser?: string;\n sshPort?: number;\n}\n\nexport interface Executor {\n /**\n * Execute a bash command.\n */\n exec(command: string, options?: ExecOptions): Promise<ExecResult>;\n\n /**\n * Get the workspace path prefix for this executor.\n * Host: returns the actual path.\n * Container/Firecracker: returns /workspace.\n */\n getWorkspacePath(hostPath: string): string;\n\n /**\n * Get the current sandbox config used by this executor.\n */\n getSandboxConfig(): SandboxConfig;\n}\n\nexport interface ExecOptions {\n timeout?: number;\n signal?: AbortSignal;\n}\n\nexport interface ExecResult {\n stdout: string;\n stderr: string;\n code: number;\n}\n\nexport interface SandboxAdapter<TConfig extends SandboxConfig = SandboxConfig> {\n type: TConfig[\"type\"];\n parse(value: string): TConfig | undefined;\n validate(config: TConfig): Promise<void>;\n createExecutor(\n config: TConfig,\n env?: Record<string, string>,\n ensureReady?: () => Promise<void>,\n ): Executor;\n}\n"]}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"utils.d.ts","sourceRoot":"","sources":["../../src/sandbox/utils.ts"],"names":[],"mappings":"AAEA,wBAAgB,UAAU,CAAC,GAAG,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,MAAM,CAAC,CAgBvE;AAED,wBAAgB,eAAe,CAAC,GAAG,EAAE,MAAM,GAAG,IAAI,CAqBjD;AAED,wBAAgB,WAAW,CAAC,CAAC,EAAE,MAAM,GAAG,MAAM,CAG7C","sourcesContent":["import { spawn } from \"child_process\";\n\nexport function execSimple(cmd: string, args: string[]): Promise<string> {\n return new Promise((resolve, reject) => {\n const child = spawn(cmd, args, { stdio: [\"ignore\", \"pipe\", \"pipe\"] });\n let stdout = \"\";\n let stderr = \"\";\n child.stdout?.on(\"data\", (d) => {\n stdout += d;\n });\n child.stderr?.on(\"data\", (d) => {\n stderr += d;\n });\n child.on(\"close\", (code) => {\n if (code === 0) resolve(stdout);\n else reject(new Error(stderr || `Exit code ${code}`));\n });\n });\n}\n\nexport function killProcessTree(pid: number): void {\n if (process.platform === \"win32\") {\n try {\n spawn(\"taskkill\", [\"/F\", \"/T\", \"/PID\", String(pid)], {\n stdio: \"ignore\",\n detached: true,\n });\n } catch {\n // Ignore errors\n }\n } else {\n try {\n process.kill(-pid, \"SIGKILL\");\n } catch {\n try {\n process.kill(pid, \"SIGKILL\");\n } catch {\n // Process already dead\n }\n }\n }\n}\n\nexport function shellEscape(s: string): string {\n // Escape for passing to sh -c\n return `'${s.replace(/'/g, \"'\\\\''\")}'`;\n}\n"]}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import { spawn } from "child_process";
|
|
2
|
+
export function execSimple(cmd, args) {
|
|
3
|
+
return new Promise((resolve, reject) => {
|
|
4
|
+
const child = spawn(cmd, args, { stdio: ["ignore", "pipe", "pipe"] });
|
|
5
|
+
let stdout = "";
|
|
6
|
+
let stderr = "";
|
|
7
|
+
child.stdout?.on("data", (d) => {
|
|
8
|
+
stdout += d;
|
|
9
|
+
});
|
|
10
|
+
child.stderr?.on("data", (d) => {
|
|
11
|
+
stderr += d;
|
|
12
|
+
});
|
|
13
|
+
child.on("close", (code) => {
|
|
14
|
+
if (code === 0)
|
|
15
|
+
resolve(stdout);
|
|
16
|
+
else
|
|
17
|
+
reject(new Error(stderr || `Exit code ${code}`));
|
|
18
|
+
});
|
|
19
|
+
});
|
|
20
|
+
}
|
|
21
|
+
export function killProcessTree(pid) {
|
|
22
|
+
if (process.platform === "win32") {
|
|
23
|
+
try {
|
|
24
|
+
spawn("taskkill", ["/F", "/T", "/PID", String(pid)], {
|
|
25
|
+
stdio: "ignore",
|
|
26
|
+
detached: true,
|
|
27
|
+
});
|
|
28
|
+
}
|
|
29
|
+
catch {
|
|
30
|
+
// Ignore errors
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
else {
|
|
34
|
+
try {
|
|
35
|
+
process.kill(-pid, "SIGKILL");
|
|
36
|
+
}
|
|
37
|
+
catch {
|
|
38
|
+
try {
|
|
39
|
+
process.kill(pid, "SIGKILL");
|
|
40
|
+
}
|
|
41
|
+
catch {
|
|
42
|
+
// Process already dead
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
export function shellEscape(s) {
|
|
48
|
+
// Escape for passing to sh -c
|
|
49
|
+
return `'${s.replace(/'/g, "'\\''")}'`;
|
|
50
|
+
}
|
|
51
|
+
//# sourceMappingURL=utils.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"utils.js","sourceRoot":"","sources":["../../src/sandbox/utils.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,EAAE,MAAM,eAAe,CAAC;AAEtC,MAAM,UAAU,UAAU,CAAC,GAAW,EAAE,IAAc;IACpD,OAAO,IAAI,OAAO,CAAC,CAAC,OAAO,EAAE,MAAM,EAAE,EAAE;QACrC,MAAM,KAAK,GAAG,KAAK,CAAC,GAAG,EAAE,IAAI,EAAE,EAAE,KAAK,EAAE,CAAC,QAAQ,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,CAAC,CAAC;QACtE,IAAI,MAAM,GAAG,EAAE,CAAC;QAChB,IAAI,MAAM,GAAG,EAAE,CAAC;QAChB,KAAK,CAAC,MAAM,EAAE,EAAE,CAAC,MAAM,EAAE,CAAC,CAAC,EAAE,EAAE;YAC7B,MAAM,IAAI,CAAC,CAAC;QACd,CAAC,CAAC,CAAC;QACH,KAAK,CAAC,MAAM,EAAE,EAAE,CAAC,MAAM,EAAE,CAAC,CAAC,EAAE,EAAE;YAC7B,MAAM,IAAI,CAAC,CAAC;QACd,CAAC,CAAC,CAAC;QACH,KAAK,CAAC,EAAE,CAAC,OAAO,EAAE,CAAC,IAAI,EAAE,EAAE;YACzB,IAAI,IAAI,KAAK,CAAC;gBAAE,OAAO,CAAC,MAAM,CAAC,CAAC;;gBAC3B,MAAM,CAAC,IAAI,KAAK,CAAC,MAAM,IAAI,aAAa,IAAI,EAAE,CAAC,CAAC,CAAC;QACxD,CAAC,CAAC,CAAC;IACL,CAAC,CAAC,CAAC;AACL,CAAC;AAED,MAAM,UAAU,eAAe,CAAC,GAAW;IACzC,IAAI,OAAO,CAAC,QAAQ,KAAK,OAAO,EAAE,CAAC;QACjC,IAAI,CAAC;YACH,KAAK,CAAC,UAAU,EAAE,CAAC,IAAI,EAAE,IAAI,EAAE,MAAM,EAAE,MAAM,CAAC,GAAG,CAAC,CAAC,EAAE;gBACnD,KAAK,EAAE,QAAQ;gBACf,QAAQ,EAAE,IAAI;aACf,CAAC,CAAC;QACL,CAAC;QAAC,MAAM,CAAC;YACP,gBAAgB;QAClB,CAAC;IACH,CAAC;SAAM,CAAC;QACN,IAAI,CAAC;YACH,OAAO,CAAC,IAAI,CAAC,CAAC,GAAG,EAAE,SAAS,CAAC,CAAC;QAChC,CAAC;QAAC,MAAM,CAAC;YACP,IAAI,CAAC;gBACH,OAAO,CAAC,IAAI,CAAC,GAAG,EAAE,SAAS,CAAC,CAAC;YAC/B,CAAC;YAAC,MAAM,CAAC;gBACP,uBAAuB;YACzB,CAAC;QACH,CAAC;IACH,CAAC;AACH,CAAC;AAED,MAAM,UAAU,WAAW,CAAC,CAAS;IACnC,8BAA8B;IAC9B,OAAO,IAAI,CAAC,CAAC,OAAO,CAAC,IAAI,EAAE,OAAO,CAAC,GAAG,CAAC;AACzC,CAAC","sourcesContent":["import { spawn } from \"child_process\";\n\nexport function execSimple(cmd: string, args: string[]): Promise<string> {\n return new Promise((resolve, reject) => {\n const child = spawn(cmd, args, { stdio: [\"ignore\", \"pipe\", \"pipe\"] });\n let stdout = \"\";\n let stderr = \"\";\n child.stdout?.on(\"data\", (d) => {\n stdout += d;\n });\n child.stderr?.on(\"data\", (d) => {\n stderr += d;\n });\n child.on(\"close\", (code) => {\n if (code === 0) resolve(stdout);\n else reject(new Error(stderr || `Exit code ${code}`));\n });\n });\n}\n\nexport function killProcessTree(pid: number): void {\n if (process.platform === \"win32\") {\n try {\n spawn(\"taskkill\", [\"/F\", \"/T\", \"/PID\", String(pid)], {\n stdio: \"ignore\",\n detached: true,\n });\n } catch {\n // Ignore errors\n }\n } else {\n try {\n process.kill(-pid, \"SIGKILL\");\n } catch {\n try {\n process.kill(pid, \"SIGKILL\");\n } catch {\n // Process already dead\n }\n }\n }\n}\n\nexport function shellEscape(s: string): string {\n // Escape for passing to sh -c\n return `'${s.replace(/'/g, \"'\\\\''\")}'`;\n}\n"]}
|
package/dist/sandbox.d.ts
CHANGED
|
@@ -1,40 +1,2 @@
|
|
|
1
|
-
export
|
|
2
|
-
type: "host";
|
|
3
|
-
} | {
|
|
4
|
-
type: "docker";
|
|
5
|
-
container: string;
|
|
6
|
-
} | {
|
|
7
|
-
type: "firecracker";
|
|
8
|
-
vmId: string;
|
|
9
|
-
hostPath: string;
|
|
10
|
-
sshUser?: string;
|
|
11
|
-
sshPort?: number;
|
|
12
|
-
};
|
|
13
|
-
export declare function parseSandboxArg(value: string): SandboxConfig;
|
|
14
|
-
export declare function validateSandbox(config: SandboxConfig): Promise<void>;
|
|
15
|
-
/**
|
|
16
|
-
* Create an executor that runs commands either on host, in Docker container, or in Firecracker VM
|
|
17
|
-
*/
|
|
18
|
-
export declare function createExecutor(config: SandboxConfig): Executor;
|
|
19
|
-
export interface Executor {
|
|
20
|
-
/**
|
|
21
|
-
* Execute a bash command
|
|
22
|
-
*/
|
|
23
|
-
exec(command: string, options?: ExecOptions): Promise<ExecResult>;
|
|
24
|
-
/**
|
|
25
|
-
* Get the workspace path prefix for this executor
|
|
26
|
-
* Host: returns the actual path
|
|
27
|
-
* Docker: returns /workspace
|
|
28
|
-
*/
|
|
29
|
-
getWorkspacePath(hostPath: string): string;
|
|
30
|
-
}
|
|
31
|
-
export interface ExecOptions {
|
|
32
|
-
timeout?: number;
|
|
33
|
-
signal?: AbortSignal;
|
|
34
|
-
}
|
|
35
|
-
export interface ExecResult {
|
|
36
|
-
stdout: string;
|
|
37
|
-
stderr: string;
|
|
38
|
-
code: number;
|
|
39
|
-
}
|
|
1
|
+
export * from "./sandbox/index.js";
|
|
40
2
|
//# sourceMappingURL=sandbox.d.ts.map
|
package/dist/sandbox.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"sandbox.d.ts","sourceRoot":"","sources":["../src/sandbox.ts"],"names":[],"mappings":"AAEA,MAAM,MAAM,aAAa,GACrB;IAAE,IAAI,EAAE,MAAM,CAAA;CAAE,GAChB;IAAE,IAAI,EAAE,QAAQ,CAAC;IAAC,SAAS,EAAE,MAAM,CAAA;CAAE,GACrC;IAAE,IAAI,EAAE,aAAa,CAAC;IAAC,IAAI,EAAE,MAAM,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAC;IAAC,OAAO,CAAC,EAAE,MAAM,CAAC;IAAC,OAAO,CAAC,EAAE,MAAM,CAAA;CAAE,CAAC;AAEhG,wBAAgB,eAAe,CAAC,KAAK,EAAE,MAAM,GAAG,aAAa,CA8C5D;AAED,wBAAsB,eAAe,CAAC,MAAM,EAAE,aAAa,GAAG,OAAO,CAAC,IAAI,CAAC,CAmF1E;AAoBD;;GAEG;AACH,wBAAgB,cAAc,CAAC,MAAM,EAAE,aAAa,GAAG,QAAQ,CAQ9D;AAED,MAAM,WAAW,QAAQ;IACvB;;OAEG;IACH,IAAI,CAAC,OAAO,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,WAAW,GAAG,OAAO,CAAC,UAAU,CAAC,CAAC;IAElE;;;;OAIG;IACH,gBAAgB,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,CAAC;CAC5C;AAED,MAAM,WAAW,WAAW;IAC1B,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,MAAM,CAAC,EAAE,WAAW,CAAC;CACtB;AAED,MAAM,WAAW,UAAU;IACzB,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,MAAM,CAAC;IACf,IAAI,EAAE,MAAM,CAAC;CACd","sourcesContent":["import { spawn } from \"child_process\";\n\nexport type SandboxConfig =\n | { type: \"host\" }\n | { type: \"docker\"; container: string }\n | { type: \"firecracker\"; vmId: string; hostPath: string; sshUser?: string; sshPort?: number };\n\nexport function parseSandboxArg(value: string): SandboxConfig {\n if (value === \"host\") {\n return { type: \"host\" };\n }\n if (value.startsWith(\"docker:\")) {\n const container = value.slice(\"docker:\".length);\n if (!container) {\n console.error(\"Error: docker sandbox requires container name (e.g., docker:mama-sandbox)\");\n process.exit(1);\n }\n return { type: \"docker\", container };\n }\n if (value.startsWith(\"firecracker:\")) {\n const arg = value.slice(\"firecracker:\".length);\n // Format: firecracker:<vm-id>:<host-path>[:<ssh-user>[:<ssh-port>]]\n // Example: firecracker:vm1:/home/user/workspace\n // firecracker:vm1:/home/user/workspace:root\n // firecracker:vm1:/home/user/workspace:root:22\n const parts = arg.split(\":\");\n if (parts.length < 2) {\n console.error(\n \"Error: firecracker sandbox requires vm-id and host-path\\n\" +\n \"Usage: firecracker:<vm-id>:<host-path>[:<ssh-user>[:<ssh-port>]]\\n\" +\n \"Example: firecracker:vm1:/home/user/workspace\",\n );\n process.exit(1);\n }\n const vmId = parts[0];\n const hostPath = parts[1];\n const sshUser = parts[2] || \"root\";\n const sshPort = parts[3] ? parseInt(parts[3], 10) : 22;\n\n if (!vmId || !hostPath) {\n console.error(\"Error: firecracker sandbox requires vm-id and host-path\");\n process.exit(1);\n }\n if (isNaN(sshPort) || sshPort <= 0 || sshPort > 65535) {\n console.error(\"Error: invalid SSH port\");\n process.exit(1);\n }\n return { type: \"firecracker\", vmId, hostPath, sshUser, sshPort };\n }\n console.error(\n `Error: Invalid sandbox type '${value}'. Use 'host', 'docker:<container-name>', or 'firecracker:<vm-id>:<host-path>'`,\n );\n process.exit(1);\n}\n\nexport async function validateSandbox(config: SandboxConfig): Promise<void> {\n if (config.type === \"host\") {\n return;\n }\n\n if (config.type === \"docker\") {\n // Check if Docker is available\n try {\n await execSimple(\"docker\", [\"--version\"]);\n } catch {\n console.error(\"Error: Docker is not installed or not in PATH\");\n process.exit(1);\n }\n\n // Check if container exists and is running\n try {\n const result = await execSimple(\"docker\", [\n \"inspect\",\n \"-f\",\n \"{{.State.Running}}\",\n config.container,\n ]);\n if (result.trim() !== \"true\") {\n console.error(`Error: Container '${config.container}' is not running.`);\n console.error(`Start it with: docker start ${config.container}`);\n process.exit(1);\n }\n } catch {\n console.error(`Error: Container '${config.container}' does not exist.`);\n console.error(\"Create it with: ./docker.sh create <data-dir>\");\n process.exit(1);\n }\n\n console.log(` Docker container '${config.container}' is running.`);\n return;\n }\n\n if (config.type === \"firecracker\") {\n // Check if fc-agent or firecracker CLI is available\n try {\n await execSimple(\"fc-agent\", [\"--version\"]);\n } catch {\n // Try alternative: firecracker\n try {\n await execSimple(\"firecracker\", [\"--version\"]);\n } catch {\n console.error(\"Error: Firecracker tools (fc-agent or firecracker) not found in PATH\");\n console.error(\"Install firecracker: https://github.com/firecracker-microvm/firecracker\");\n process.exit(1);\n }\n }\n\n // Check if VM is running using fc-agent\n try {\n const result = await execSimple(\"fc-agent\", [\"status\", config.vmId]);\n if (!result.includes(\"running\") && !result.includes(\"Running\")) {\n console.error(`Error: Firecracker VM '${config.vmId}' is not running.`);\n console.error(`Start it with: fc-agent start ${config.vmId}`);\n process.exit(1);\n }\n } catch {\n // Try alternative: firecracker-ctl or direct check\n try {\n await execSimple(\"firecracker-ctl\", [\"status\", config.vmId]);\n } catch {\n console.error(`Warning: Could not verify if VM '${config.vmId}' is running.`);\n console.error(\"Make sure the VM is started before running mama.\");\n }\n }\n\n // Verify host path exists\n try {\n await execSimple(\"ls\", [\"-d\", config.hostPath]);\n } catch {\n console.error(`Error: Host path '${config.hostPath}' does not exist.`);\n process.exit(1);\n }\n\n console.log(\n ` Firecracker VM '${config.vmId}' configured with workspace '${config.hostPath}'.`,\n );\n return;\n }\n}\n\nfunction execSimple(cmd: string, args: string[]): Promise<string> {\n return new Promise((resolve, reject) => {\n const child = spawn(cmd, args, { stdio: [\"ignore\", \"pipe\", \"pipe\"] });\n let stdout = \"\";\n let stderr = \"\";\n child.stdout?.on(\"data\", (d) => {\n stdout += d;\n });\n child.stderr?.on(\"data\", (d) => {\n stderr += d;\n });\n child.on(\"close\", (code) => {\n if (code === 0) resolve(stdout);\n else reject(new Error(stderr || `Exit code ${code}`));\n });\n });\n}\n\n/**\n * Create an executor that runs commands either on host, in Docker container, or in Firecracker VM\n */\nexport function createExecutor(config: SandboxConfig): Executor {\n if (config.type === \"host\") {\n return new HostExecutor();\n }\n if (config.type === \"docker\") {\n return new DockerExecutor(config.container);\n }\n return new FirecrackerExecutor(config.vmId, config.hostPath, config.sshUser, config.sshPort);\n}\n\nexport interface Executor {\n /**\n * Execute a bash command\n */\n exec(command: string, options?: ExecOptions): Promise<ExecResult>;\n\n /**\n * Get the workspace path prefix for this executor\n * Host: returns the actual path\n * Docker: returns /workspace\n */\n getWorkspacePath(hostPath: string): string;\n}\n\nexport interface ExecOptions {\n timeout?: number;\n signal?: AbortSignal;\n}\n\nexport interface ExecResult {\n stdout: string;\n stderr: string;\n code: number;\n}\n\nclass HostExecutor implements Executor {\n async exec(command: string, options?: ExecOptions): Promise<ExecResult> {\n return new Promise((resolve, reject) => {\n const shell = process.platform === \"win32\" ? \"cmd\" : \"sh\";\n const shellArgs = process.platform === \"win32\" ? [\"/c\"] : [\"-c\"];\n\n const child = spawn(shell, [...shellArgs, command], {\n detached: true,\n stdio: [\"ignore\", \"pipe\", \"pipe\"],\n });\n\n let stdout = \"\";\n let stderr = \"\";\n let timedOut = false;\n\n const timeoutHandle =\n options?.timeout && options.timeout > 0\n ? setTimeout(() => {\n timedOut = true;\n killProcessTree(child.pid!);\n }, options.timeout * 1000)\n : undefined;\n\n const onAbort = () => {\n if (child.pid) killProcessTree(child.pid);\n };\n\n if (options?.signal) {\n if (options.signal.aborted) {\n onAbort();\n } else {\n options.signal.addEventListener(\"abort\", onAbort, { once: true });\n }\n }\n\n child.stdout?.on(\"data\", (data) => {\n stdout += data.toString();\n if (stdout.length > 10 * 1024 * 1024) {\n stdout = stdout.slice(0, 10 * 1024 * 1024);\n }\n });\n\n child.stderr?.on(\"data\", (data) => {\n stderr += data.toString();\n if (stderr.length > 10 * 1024 * 1024) {\n stderr = stderr.slice(0, 10 * 1024 * 1024);\n }\n });\n\n child.on(\"close\", (code) => {\n if (timeoutHandle) clearTimeout(timeoutHandle);\n if (options?.signal) {\n options.signal.removeEventListener(\"abort\", onAbort);\n }\n\n if (options?.signal?.aborted) {\n reject(new Error(`${stdout}\\n${stderr}\\nCommand aborted`.trim()));\n return;\n }\n\n if (timedOut) {\n reject(\n new Error(\n `${stdout}\\n${stderr}\\nCommand timed out after ${options?.timeout} seconds`.trim(),\n ),\n );\n return;\n }\n\n resolve({ stdout, stderr, code: code ?? 0 });\n });\n });\n }\n\n getWorkspacePath(hostPath: string): string {\n return hostPath;\n }\n}\n\nclass DockerExecutor implements Executor {\n constructor(private container: string) {}\n\n async exec(command: string, options?: ExecOptions): Promise<ExecResult> {\n // Wrap command for docker exec\n const dockerCmd = `docker exec ${this.container} sh -c ${shellEscape(command)}`;\n const hostExecutor = new HostExecutor();\n return hostExecutor.exec(dockerCmd, options);\n }\n\n getWorkspacePath(_hostPath: string): string {\n // Docker container sees /workspace\n return \"/workspace\";\n }\n}\n\nclass FirecrackerExecutor implements Executor {\n constructor(\n private vmId: string,\n private hostPath: string,\n private sshUser: string = \"root\",\n private sshPort: number = 22,\n ) {}\n\n async exec(command: string, options?: ExecOptions): Promise<ExecResult> {\n // Use direct SSH to execute command in the Firecracker VM\n // The workspace inside the VM is expected to be mounted at /workspace\n const sshCmd =\n this.sshPort === 22\n ? `ssh -o StrictHostKeyChecking=no -o ConnectTimeout=10 ${this.sshUser}@${this.vmId} sh -c ${shellEscape(command)}`\n : `ssh -o StrictHostKeyChecking=no -o ConnectTimeout=10 -p ${this.sshPort} ${this.sshUser}@${this.vmId} sh -c ${shellEscape(command)}`;\n const hostExecutor = new HostExecutor();\n return hostExecutor.exec(sshCmd, options);\n }\n\n getWorkspacePath(_hostPath: string): string {\n // Firecracker VM sees /workspace (assumes hostPath is mounted there)\n return \"/workspace\";\n }\n}\n\nfunction killProcessTree(pid: number): void {\n if (process.platform === \"win32\") {\n try {\n spawn(\"taskkill\", [\"/F\", \"/T\", \"/PID\", String(pid)], {\n stdio: \"ignore\",\n detached: true,\n });\n } catch {\n // Ignore errors\n }\n } else {\n try {\n process.kill(-pid, \"SIGKILL\");\n } catch {\n try {\n process.kill(pid, \"SIGKILL\");\n } catch {\n // Process already dead\n }\n }\n }\n}\n\nfunction shellEscape(s: string): string {\n // Escape for passing to sh -c\n return `'${s.replace(/'/g, \"'\\\\''\")}'`;\n}\n"]}
|
|
1
|
+
{"version":3,"file":"sandbox.d.ts","sourceRoot":"","sources":["../src/sandbox.ts"],"names":[],"mappings":"AAAA,cAAc,oBAAoB,CAAC","sourcesContent":["export * from \"./sandbox/index.js\";\n"]}
|
package/dist/sandbox.js
CHANGED
|
@@ -1,287 +1,2 @@
|
|
|
1
|
-
|
|
2
|
-
export function parseSandboxArg(value) {
|
|
3
|
-
if (value === "host") {
|
|
4
|
-
return { type: "host" };
|
|
5
|
-
}
|
|
6
|
-
if (value.startsWith("docker:")) {
|
|
7
|
-
const container = value.slice("docker:".length);
|
|
8
|
-
if (!container) {
|
|
9
|
-
console.error("Error: docker sandbox requires container name (e.g., docker:mama-sandbox)");
|
|
10
|
-
process.exit(1);
|
|
11
|
-
}
|
|
12
|
-
return { type: "docker", container };
|
|
13
|
-
}
|
|
14
|
-
if (value.startsWith("firecracker:")) {
|
|
15
|
-
const arg = value.slice("firecracker:".length);
|
|
16
|
-
// Format: firecracker:<vm-id>:<host-path>[:<ssh-user>[:<ssh-port>]]
|
|
17
|
-
// Example: firecracker:vm1:/home/user/workspace
|
|
18
|
-
// firecracker:vm1:/home/user/workspace:root
|
|
19
|
-
// firecracker:vm1:/home/user/workspace:root:22
|
|
20
|
-
const parts = arg.split(":");
|
|
21
|
-
if (parts.length < 2) {
|
|
22
|
-
console.error("Error: firecracker sandbox requires vm-id and host-path\n" +
|
|
23
|
-
"Usage: firecracker:<vm-id>:<host-path>[:<ssh-user>[:<ssh-port>]]\n" +
|
|
24
|
-
"Example: firecracker:vm1:/home/user/workspace");
|
|
25
|
-
process.exit(1);
|
|
26
|
-
}
|
|
27
|
-
const vmId = parts[0];
|
|
28
|
-
const hostPath = parts[1];
|
|
29
|
-
const sshUser = parts[2] || "root";
|
|
30
|
-
const sshPort = parts[3] ? parseInt(parts[3], 10) : 22;
|
|
31
|
-
if (!vmId || !hostPath) {
|
|
32
|
-
console.error("Error: firecracker sandbox requires vm-id and host-path");
|
|
33
|
-
process.exit(1);
|
|
34
|
-
}
|
|
35
|
-
if (isNaN(sshPort) || sshPort <= 0 || sshPort > 65535) {
|
|
36
|
-
console.error("Error: invalid SSH port");
|
|
37
|
-
process.exit(1);
|
|
38
|
-
}
|
|
39
|
-
return { type: "firecracker", vmId, hostPath, sshUser, sshPort };
|
|
40
|
-
}
|
|
41
|
-
console.error(`Error: Invalid sandbox type '${value}'. Use 'host', 'docker:<container-name>', or 'firecracker:<vm-id>:<host-path>'`);
|
|
42
|
-
process.exit(1);
|
|
43
|
-
}
|
|
44
|
-
export async function validateSandbox(config) {
|
|
45
|
-
if (config.type === "host") {
|
|
46
|
-
return;
|
|
47
|
-
}
|
|
48
|
-
if (config.type === "docker") {
|
|
49
|
-
// Check if Docker is available
|
|
50
|
-
try {
|
|
51
|
-
await execSimple("docker", ["--version"]);
|
|
52
|
-
}
|
|
53
|
-
catch {
|
|
54
|
-
console.error("Error: Docker is not installed or not in PATH");
|
|
55
|
-
process.exit(1);
|
|
56
|
-
}
|
|
57
|
-
// Check if container exists and is running
|
|
58
|
-
try {
|
|
59
|
-
const result = await execSimple("docker", [
|
|
60
|
-
"inspect",
|
|
61
|
-
"-f",
|
|
62
|
-
"{{.State.Running}}",
|
|
63
|
-
config.container,
|
|
64
|
-
]);
|
|
65
|
-
if (result.trim() !== "true") {
|
|
66
|
-
console.error(`Error: Container '${config.container}' is not running.`);
|
|
67
|
-
console.error(`Start it with: docker start ${config.container}`);
|
|
68
|
-
process.exit(1);
|
|
69
|
-
}
|
|
70
|
-
}
|
|
71
|
-
catch {
|
|
72
|
-
console.error(`Error: Container '${config.container}' does not exist.`);
|
|
73
|
-
console.error("Create it with: ./docker.sh create <data-dir>");
|
|
74
|
-
process.exit(1);
|
|
75
|
-
}
|
|
76
|
-
console.log(` Docker container '${config.container}' is running.`);
|
|
77
|
-
return;
|
|
78
|
-
}
|
|
79
|
-
if (config.type === "firecracker") {
|
|
80
|
-
// Check if fc-agent or firecracker CLI is available
|
|
81
|
-
try {
|
|
82
|
-
await execSimple("fc-agent", ["--version"]);
|
|
83
|
-
}
|
|
84
|
-
catch {
|
|
85
|
-
// Try alternative: firecracker
|
|
86
|
-
try {
|
|
87
|
-
await execSimple("firecracker", ["--version"]);
|
|
88
|
-
}
|
|
89
|
-
catch {
|
|
90
|
-
console.error("Error: Firecracker tools (fc-agent or firecracker) not found in PATH");
|
|
91
|
-
console.error("Install firecracker: https://github.com/firecracker-microvm/firecracker");
|
|
92
|
-
process.exit(1);
|
|
93
|
-
}
|
|
94
|
-
}
|
|
95
|
-
// Check if VM is running using fc-agent
|
|
96
|
-
try {
|
|
97
|
-
const result = await execSimple("fc-agent", ["status", config.vmId]);
|
|
98
|
-
if (!result.includes("running") && !result.includes("Running")) {
|
|
99
|
-
console.error(`Error: Firecracker VM '${config.vmId}' is not running.`);
|
|
100
|
-
console.error(`Start it with: fc-agent start ${config.vmId}`);
|
|
101
|
-
process.exit(1);
|
|
102
|
-
}
|
|
103
|
-
}
|
|
104
|
-
catch {
|
|
105
|
-
// Try alternative: firecracker-ctl or direct check
|
|
106
|
-
try {
|
|
107
|
-
await execSimple("firecracker-ctl", ["status", config.vmId]);
|
|
108
|
-
}
|
|
109
|
-
catch {
|
|
110
|
-
console.error(`Warning: Could not verify if VM '${config.vmId}' is running.`);
|
|
111
|
-
console.error("Make sure the VM is started before running mama.");
|
|
112
|
-
}
|
|
113
|
-
}
|
|
114
|
-
// Verify host path exists
|
|
115
|
-
try {
|
|
116
|
-
await execSimple("ls", ["-d", config.hostPath]);
|
|
117
|
-
}
|
|
118
|
-
catch {
|
|
119
|
-
console.error(`Error: Host path '${config.hostPath}' does not exist.`);
|
|
120
|
-
process.exit(1);
|
|
121
|
-
}
|
|
122
|
-
console.log(` Firecracker VM '${config.vmId}' configured with workspace '${config.hostPath}'.`);
|
|
123
|
-
return;
|
|
124
|
-
}
|
|
125
|
-
}
|
|
126
|
-
function execSimple(cmd, args) {
|
|
127
|
-
return new Promise((resolve, reject) => {
|
|
128
|
-
const child = spawn(cmd, args, { stdio: ["ignore", "pipe", "pipe"] });
|
|
129
|
-
let stdout = "";
|
|
130
|
-
let stderr = "";
|
|
131
|
-
child.stdout?.on("data", (d) => {
|
|
132
|
-
stdout += d;
|
|
133
|
-
});
|
|
134
|
-
child.stderr?.on("data", (d) => {
|
|
135
|
-
stderr += d;
|
|
136
|
-
});
|
|
137
|
-
child.on("close", (code) => {
|
|
138
|
-
if (code === 0)
|
|
139
|
-
resolve(stdout);
|
|
140
|
-
else
|
|
141
|
-
reject(new Error(stderr || `Exit code ${code}`));
|
|
142
|
-
});
|
|
143
|
-
});
|
|
144
|
-
}
|
|
145
|
-
/**
|
|
146
|
-
* Create an executor that runs commands either on host, in Docker container, or in Firecracker VM
|
|
147
|
-
*/
|
|
148
|
-
export function createExecutor(config) {
|
|
149
|
-
if (config.type === "host") {
|
|
150
|
-
return new HostExecutor();
|
|
151
|
-
}
|
|
152
|
-
if (config.type === "docker") {
|
|
153
|
-
return new DockerExecutor(config.container);
|
|
154
|
-
}
|
|
155
|
-
return new FirecrackerExecutor(config.vmId, config.hostPath, config.sshUser, config.sshPort);
|
|
156
|
-
}
|
|
157
|
-
class HostExecutor {
|
|
158
|
-
async exec(command, options) {
|
|
159
|
-
return new Promise((resolve, reject) => {
|
|
160
|
-
const shell = process.platform === "win32" ? "cmd" : "sh";
|
|
161
|
-
const shellArgs = process.platform === "win32" ? ["/c"] : ["-c"];
|
|
162
|
-
const child = spawn(shell, [...shellArgs, command], {
|
|
163
|
-
detached: true,
|
|
164
|
-
stdio: ["ignore", "pipe", "pipe"],
|
|
165
|
-
});
|
|
166
|
-
let stdout = "";
|
|
167
|
-
let stderr = "";
|
|
168
|
-
let timedOut = false;
|
|
169
|
-
const timeoutHandle = options?.timeout && options.timeout > 0
|
|
170
|
-
? setTimeout(() => {
|
|
171
|
-
timedOut = true;
|
|
172
|
-
killProcessTree(child.pid);
|
|
173
|
-
}, options.timeout * 1000)
|
|
174
|
-
: undefined;
|
|
175
|
-
const onAbort = () => {
|
|
176
|
-
if (child.pid)
|
|
177
|
-
killProcessTree(child.pid);
|
|
178
|
-
};
|
|
179
|
-
if (options?.signal) {
|
|
180
|
-
if (options.signal.aborted) {
|
|
181
|
-
onAbort();
|
|
182
|
-
}
|
|
183
|
-
else {
|
|
184
|
-
options.signal.addEventListener("abort", onAbort, { once: true });
|
|
185
|
-
}
|
|
186
|
-
}
|
|
187
|
-
child.stdout?.on("data", (data) => {
|
|
188
|
-
stdout += data.toString();
|
|
189
|
-
if (stdout.length > 10 * 1024 * 1024) {
|
|
190
|
-
stdout = stdout.slice(0, 10 * 1024 * 1024);
|
|
191
|
-
}
|
|
192
|
-
});
|
|
193
|
-
child.stderr?.on("data", (data) => {
|
|
194
|
-
stderr += data.toString();
|
|
195
|
-
if (stderr.length > 10 * 1024 * 1024) {
|
|
196
|
-
stderr = stderr.slice(0, 10 * 1024 * 1024);
|
|
197
|
-
}
|
|
198
|
-
});
|
|
199
|
-
child.on("close", (code) => {
|
|
200
|
-
if (timeoutHandle)
|
|
201
|
-
clearTimeout(timeoutHandle);
|
|
202
|
-
if (options?.signal) {
|
|
203
|
-
options.signal.removeEventListener("abort", onAbort);
|
|
204
|
-
}
|
|
205
|
-
if (options?.signal?.aborted) {
|
|
206
|
-
reject(new Error(`${stdout}\n${stderr}\nCommand aborted`.trim()));
|
|
207
|
-
return;
|
|
208
|
-
}
|
|
209
|
-
if (timedOut) {
|
|
210
|
-
reject(new Error(`${stdout}\n${stderr}\nCommand timed out after ${options?.timeout} seconds`.trim()));
|
|
211
|
-
return;
|
|
212
|
-
}
|
|
213
|
-
resolve({ stdout, stderr, code: code ?? 0 });
|
|
214
|
-
});
|
|
215
|
-
});
|
|
216
|
-
}
|
|
217
|
-
getWorkspacePath(hostPath) {
|
|
218
|
-
return hostPath;
|
|
219
|
-
}
|
|
220
|
-
}
|
|
221
|
-
class DockerExecutor {
|
|
222
|
-
constructor(container) {
|
|
223
|
-
this.container = container;
|
|
224
|
-
}
|
|
225
|
-
async exec(command, options) {
|
|
226
|
-
// Wrap command for docker exec
|
|
227
|
-
const dockerCmd = `docker exec ${this.container} sh -c ${shellEscape(command)}`;
|
|
228
|
-
const hostExecutor = new HostExecutor();
|
|
229
|
-
return hostExecutor.exec(dockerCmd, options);
|
|
230
|
-
}
|
|
231
|
-
getWorkspacePath(_hostPath) {
|
|
232
|
-
// Docker container sees /workspace
|
|
233
|
-
return "/workspace";
|
|
234
|
-
}
|
|
235
|
-
}
|
|
236
|
-
class FirecrackerExecutor {
|
|
237
|
-
constructor(vmId, hostPath, sshUser = "root", sshPort = 22) {
|
|
238
|
-
this.vmId = vmId;
|
|
239
|
-
this.hostPath = hostPath;
|
|
240
|
-
this.sshUser = sshUser;
|
|
241
|
-
this.sshPort = sshPort;
|
|
242
|
-
}
|
|
243
|
-
async exec(command, options) {
|
|
244
|
-
// Use direct SSH to execute command in the Firecracker VM
|
|
245
|
-
// The workspace inside the VM is expected to be mounted at /workspace
|
|
246
|
-
const sshCmd = this.sshPort === 22
|
|
247
|
-
? `ssh -o StrictHostKeyChecking=no -o ConnectTimeout=10 ${this.sshUser}@${this.vmId} sh -c ${shellEscape(command)}`
|
|
248
|
-
: `ssh -o StrictHostKeyChecking=no -o ConnectTimeout=10 -p ${this.sshPort} ${this.sshUser}@${this.vmId} sh -c ${shellEscape(command)}`;
|
|
249
|
-
const hostExecutor = new HostExecutor();
|
|
250
|
-
return hostExecutor.exec(sshCmd, options);
|
|
251
|
-
}
|
|
252
|
-
getWorkspacePath(_hostPath) {
|
|
253
|
-
// Firecracker VM sees /workspace (assumes hostPath is mounted there)
|
|
254
|
-
return "/workspace";
|
|
255
|
-
}
|
|
256
|
-
}
|
|
257
|
-
function killProcessTree(pid) {
|
|
258
|
-
if (process.platform === "win32") {
|
|
259
|
-
try {
|
|
260
|
-
spawn("taskkill", ["/F", "/T", "/PID", String(pid)], {
|
|
261
|
-
stdio: "ignore",
|
|
262
|
-
detached: true,
|
|
263
|
-
});
|
|
264
|
-
}
|
|
265
|
-
catch {
|
|
266
|
-
// Ignore errors
|
|
267
|
-
}
|
|
268
|
-
}
|
|
269
|
-
else {
|
|
270
|
-
try {
|
|
271
|
-
process.kill(-pid, "SIGKILL");
|
|
272
|
-
}
|
|
273
|
-
catch {
|
|
274
|
-
try {
|
|
275
|
-
process.kill(pid, "SIGKILL");
|
|
276
|
-
}
|
|
277
|
-
catch {
|
|
278
|
-
// Process already dead
|
|
279
|
-
}
|
|
280
|
-
}
|
|
281
|
-
}
|
|
282
|
-
}
|
|
283
|
-
function shellEscape(s) {
|
|
284
|
-
// Escape for passing to sh -c
|
|
285
|
-
return `'${s.replace(/'/g, "'\\''")}'`;
|
|
286
|
-
}
|
|
1
|
+
export * from "./sandbox/index.js";
|
|
287
2
|
//# sourceMappingURL=sandbox.js.map
|