noumen 0.3.0 → 0.5.0

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.
Files changed (44) hide show
  1. package/README.md +63 -8
  2. package/dist/a2a/index.d.ts +4 -2
  3. package/dist/acp/index.d.ts +5 -3
  4. package/dist/{agent-1nFVUP9E.d.ts → agent-C3eDRsxs.d.ts} +19 -508
  5. package/dist/chunk-I5SBSOS6.js +40 -0
  6. package/dist/chunk-I5SBSOS6.js.map +1 -0
  7. package/dist/{chunk-4HW6LN6D.js → chunk-WPCYGZOE.js} +58 -1228
  8. package/dist/chunk-WPCYGZOE.js.map +1 -0
  9. package/dist/{chunk-5JN4SPI7.js → chunk-WTLK2ZAR.js} +1 -1
  10. package/dist/{chunk-HL6JCRZJ.js → chunk-XZN4QZLK.js} +4 -4
  11. package/dist/cli/index.js +10 -10
  12. package/dist/computer-BPdxSo6X.d.ts +88 -0
  13. package/dist/docker.d.ts +129 -0
  14. package/dist/docker.js +401 -0
  15. package/dist/docker.js.map +1 -0
  16. package/dist/e2b.d.ts +157 -0
  17. package/dist/e2b.js +202 -0
  18. package/dist/e2b.js.map +1 -0
  19. package/dist/freestyle.d.ts +174 -0
  20. package/dist/freestyle.js +240 -0
  21. package/dist/freestyle.js.map +1 -0
  22. package/dist/index.d.ts +9 -201
  23. package/dist/index.js +24 -48
  24. package/dist/lsp/index.d.ts +3 -2
  25. package/dist/mcp/index.d.ts +4 -3
  26. package/dist/mcp/index.js +2 -2
  27. package/dist/{provider-factory-KCLIF34X.js → provider-factory-KI7OZUY3.js} +2 -2
  28. package/dist/{resolve-4JA2BBDA.js → resolve-GDSHNMG6.js} +2 -2
  29. package/dist/sandbox-9qeMTNrD.d.ts +126 -0
  30. package/dist/server/index.d.ts +4 -2
  31. package/dist/{server-CHMxuWKq.d.ts → server-Cu9gv1dk.d.ts} +1 -1
  32. package/dist/sprites.d.ts +136 -0
  33. package/dist/sprites.js +334 -0
  34. package/dist/sprites.js.map +1 -0
  35. package/dist/ssh.d.ts +187 -0
  36. package/dist/ssh.js +392 -0
  37. package/dist/ssh.js.map +1 -0
  38. package/dist/{types-RPKUTu1k.d.ts → types-BA87bHPV.d.ts} +2 -88
  39. package/package.json +28 -1
  40. package/dist/chunk-4HW6LN6D.js.map +0 -1
  41. /package/dist/{chunk-5JN4SPI7.js.map → chunk-WTLK2ZAR.js.map} +0 -0
  42. /package/dist/{chunk-HL6JCRZJ.js.map → chunk-XZN4QZLK.js.map} +0 -0
  43. /package/dist/{provider-factory-KCLIF34X.js.map → provider-factory-KI7OZUY3.js.map} +0 -0
  44. /package/dist/{resolve-4JA2BBDA.js.map → resolve-GDSHNMG6.js.map} +0 -0
@@ -91,4 +91,4 @@ export {
91
91
  resolveProvider,
92
92
  detectProvider
93
93
  };
94
- //# sourceMappingURL=chunk-5JN4SPI7.js.map
94
+ //# sourceMappingURL=chunk-WTLK2ZAR.js.map
@@ -1,12 +1,12 @@
1
- import {
2
- formatZodValidationError
3
- } from "./chunk-3SK5GCI6.js";
4
1
  import {
5
2
  IMAGE_EXTENSIONS,
6
3
  compressImageBufferWithTokenLimit,
7
4
  createImageMetadataText,
8
5
  maybeResizeAndDownsampleImageBuffer
9
6
  } from "./chunk-5GEX6ZSB.js";
7
+ import {
8
+ formatZodValidationError
9
+ } from "./chunk-3SK5GCI6.js";
10
10
  import {
11
11
  contentToString
12
12
  } from "./chunk-JACGEMTF.js";
@@ -3109,4 +3109,4 @@ export {
3109
3109
  resolveToolFlag,
3110
3110
  ToolRegistry
3111
3111
  };
3112
- //# sourceMappingURL=chunk-HL6JCRZJ.js.map
3112
+ //# sourceMappingURL=chunk-XZN4QZLK.js.map
package/dist/cli/index.js CHANGED
@@ -16,21 +16,21 @@ import {
16
16
  Agent,
17
17
  LocalSandbox,
18
18
  UnsandboxedLocal
19
- } from "../chunk-4HW6LN6D.js";
20
- import "../chunk-42PHHZUA.js";
19
+ } from "../chunk-WPCYGZOE.js";
21
20
  import {
22
21
  DEFAULT_MODELS,
23
22
  SUPPORTED_PROVIDERS,
24
23
  detectProvider,
25
24
  resolveProvider
26
- } from "../chunk-5JN4SPI7.js";
25
+ } from "../chunk-WTLK2ZAR.js";
26
+ import "../chunk-42PHHZUA.js";
27
+ import "../chunk-XZN4QZLK.js";
28
+ import "../chunk-5GEX6ZSB.js";
29
+ import "../chunk-4SQA2UCV.js";
30
+ import "../chunk-3SK5GCI6.js";
27
31
  import "../chunk-HEQQQGK5.js";
28
32
  import "../chunk-L3L3FG5T.js";
29
33
  import "../chunk-3HEYCV26.js";
30
- import "../chunk-HL6JCRZJ.js";
31
- import "../chunk-3SK5GCI6.js";
32
- import "../chunk-5GEX6ZSB.js";
33
- import "../chunk-4SQA2UCV.js";
34
34
  import "../chunk-JACGEMTF.js";
35
35
  import "../chunk-DGUM43GV.js";
36
36
 
@@ -532,7 +532,7 @@ async function runAgent(config) {
532
532
  const { createInterface: createInterface3 } = await import("readline/promises");
533
533
  const rl = createInterface3({ input: process.stdin, output: process.stderr, terminal: true });
534
534
  try {
535
- const { SUPPORTED_PROVIDERS: SUPPORTED_PROVIDERS2, isOllamaRunning: isOllamaRunning2, ollamaBaseURL: ollamaBaseURL2 } = await import("../provider-factory-KCLIF34X.js");
535
+ const { SUPPORTED_PROVIDERS: SUPPORTED_PROVIDERS2, isOllamaRunning: isOllamaRunning2, ollamaBaseURL: ollamaBaseURL2 } = await import("../provider-factory-KI7OZUY3.js");
536
536
  const providerAnswer = await rl.question(
537
537
  ` Provider (${SUPPORTED_PROVIDERS2.join(", ")}) [${chalk3.bold("ollama")}]: `
538
538
  );
@@ -589,9 +589,9 @@ async function runAgent(config) {
589
589
  return runAgent(config);
590
590
  }
591
591
  if (!config.model) {
592
- const { DEFAULT_MODELS: DEFAULT_MODELS2 } = await import("../provider-factory-KCLIF34X.js");
592
+ const { DEFAULT_MODELS: DEFAULT_MODELS2 } = await import("../provider-factory-KI7OZUY3.js");
593
593
  if (providerName === "ollama") {
594
- const { ollamaBaseURL: ollamaBaseURL2 } = await import("../provider-factory-KCLIF34X.js");
594
+ const { ollamaBaseURL: ollamaBaseURL2 } = await import("../provider-factory-KI7OZUY3.js");
595
595
  const models = await listLocalOllamaModels(ollamaBaseURL2());
596
596
  const preferred = DEFAULT_MODELS2[providerName];
597
597
  config.model = models.includes(preferred) ? preferred : models[0] ?? preferred;
@@ -0,0 +1,88 @@
1
+ interface FileEntry {
2
+ name: string;
3
+ path: string;
4
+ isDirectory: boolean;
5
+ isFile: boolean;
6
+ size?: number;
7
+ }
8
+ interface FileStat {
9
+ size: number;
10
+ isDirectory: boolean;
11
+ isFile: boolean;
12
+ createdAt?: Date;
13
+ modifiedAt?: Date;
14
+ }
15
+ interface ReadOptions {
16
+ encoding?: BufferEncoding;
17
+ /**
18
+ * Maximum number of bytes to read. When set, only the first `maxBytes`
19
+ * bytes are returned (decoded as a string). Implementations that do not
20
+ * support this option may ignore it and return the full content.
21
+ */
22
+ maxBytes?: number;
23
+ }
24
+ /**
25
+ * Sandboxed filesystem interface.
26
+ *
27
+ * `VirtualFs` is noumen's primary isolation boundary for file I/O. Every
28
+ * built-in tool that touches the filesystem (ReadFile, WriteFile, EditFile)
29
+ * delegates to this interface — the agent never accesses `node:fs` directly.
30
+ *
31
+ * Swap implementations to control where files live and what the agent can reach:
32
+ * - `LocalFs` — reads/writes on the host filesystem (no isolation, for local dev)
33
+ * - `SpritesFs` — reads/writes inside a remote sprites.dev container (full sandbox)
34
+ * - Custom — implement this interface for Docker volumes, E2B, S3, in-memory, etc.
35
+ */
36
+ interface VirtualFs {
37
+ readFile(path: string, opts?: ReadOptions): Promise<string>;
38
+ /**
39
+ * Read raw bytes from a file. Used for binary content (images, PDFs).
40
+ * Implementations SHOULD cap the read at `maxBytes` to prevent OOM on
41
+ * very large files. When `maxBytes` is omitted, the entire file is read.
42
+ *
43
+ * Returns a Buffer (Node.js) or Uint8Array.
44
+ */
45
+ readFileBytes?(path: string, maxBytes?: number): Promise<Buffer>;
46
+ writeFile(path: string, content: string): Promise<void>;
47
+ appendFile(path: string, content: string): Promise<void>;
48
+ deleteFile(path: string, opts?: {
49
+ recursive?: boolean;
50
+ }): Promise<void>;
51
+ mkdir(path: string, opts?: {
52
+ recursive?: boolean;
53
+ }): Promise<void>;
54
+ readdir(path: string, opts?: {
55
+ recursive?: boolean;
56
+ }): Promise<FileEntry[]>;
57
+ exists(path: string): Promise<boolean>;
58
+ stat(path: string): Promise<FileStat>;
59
+ }
60
+
61
+ interface ExecOptions {
62
+ timeout?: number;
63
+ cwd?: string;
64
+ env?: Record<string, string>;
65
+ signal?: AbortSignal;
66
+ }
67
+ interface CommandResult {
68
+ exitCode: number;
69
+ stdout: string;
70
+ stderr: string;
71
+ }
72
+ /**
73
+ * Sandboxed shell execution interface.
74
+ *
75
+ * `VirtualComputer` is noumen's primary isolation boundary for command
76
+ * execution. Every built-in tool that runs shell commands (Bash, Glob, Grep)
77
+ * delegates to this interface — the agent never spawns processes directly.
78
+ *
79
+ * Swap implementations to control where and how commands run:
80
+ * - `LocalComputer` — runs on the host machine (no isolation, for local dev)
81
+ * - `SpritesComputer` — runs in a remote sprites.dev container (full sandbox)
82
+ * - Custom — implement this interface for Docker, E2B, Daytona, etc.
83
+ */
84
+ interface VirtualComputer {
85
+ executeCommand(command: string, opts?: ExecOptions): Promise<CommandResult>;
86
+ }
87
+
88
+ export type { CommandResult as C, ExecOptions as E, FileEntry as F, ReadOptions as R, VirtualComputer as V, VirtualFs as a, FileStat as b };
@@ -0,0 +1,129 @@
1
+ import { S as Sandbox } from './sandbox-9qeMTNrD.js';
2
+ import { V as VirtualComputer, E as ExecOptions, C as CommandResult, a as VirtualFs, R as ReadOptions, F as FileEntry, b as FileStat } from './computer-BPdxSo6X.js';
3
+
4
+ /**
5
+ * Minimal subset of the dockerode Container interface used by DockerComputer.
6
+ * Avoids a hard import of dockerode at the module level.
7
+ */
8
+ interface DockerContainer {
9
+ exec(options: Record<string, unknown>): Promise<{
10
+ start(opts?: Record<string, unknown>): Promise<NodeJS.ReadableStream>;
11
+ inspect(): Promise<{
12
+ ExitCode: number;
13
+ }>;
14
+ }>;
15
+ }
16
+ interface DockerComputerOptions {
17
+ /** A dockerode Container instance for the target container. */
18
+ container: DockerContainer;
19
+ /** Default working directory for commands (default: /). */
20
+ defaultCwd?: string;
21
+ /** Default timeout in ms for commands (default: 30000). */
22
+ defaultTimeout?: number;
23
+ }
24
+ /**
25
+ * VirtualComputer backed by command execution inside a Docker container.
26
+ *
27
+ * Requires `dockerode` as an optional peer dependency.
28
+ * The user is responsible for container lifecycle (create, start, stop).
29
+ */
30
+ declare class DockerComputer implements VirtualComputer {
31
+ private container;
32
+ private defaultCwd;
33
+ private defaultTimeout;
34
+ constructor(opts: DockerComputerOptions);
35
+ executeCommand(command: string, opts?: ExecOptions): Promise<CommandResult>;
36
+ }
37
+
38
+ interface DockerSandboxOptions {
39
+ /**
40
+ * A pre-existing dockerode Container instance. When provided the sandbox
41
+ * attaches to this container directly — no auto-creation occurs and
42
+ * `dispose()` will **not** stop or remove it.
43
+ *
44
+ * When omitted, a new container is created from `image` on the first
45
+ * `init()` call via a dynamic import of `dockerode`. The auto-created
46
+ * container is stopped and removed when `dispose()` is called.
47
+ */
48
+ container?: DockerContainer;
49
+ /**
50
+ * Docker image to use for auto-creation (e.g. `"ubuntu:22.04"`).
51
+ * Required when `container` is omitted; ignored when `container` is provided.
52
+ */
53
+ image?: string;
54
+ /** Command to run in the auto-created container (default: `["sleep", "infinity"]`). */
55
+ cmd?: string[];
56
+ /** Environment variables for the auto-created container. */
57
+ env?: string[];
58
+ /** Extra options passed to dockerode `createContainer`. */
59
+ dockerOptions?: Record<string, unknown>;
60
+ /** Working directory inside the container. */
61
+ cwd?: string;
62
+ /** Default timeout (ms) for shell commands. */
63
+ defaultTimeout?: number;
64
+ }
65
+ /**
66
+ * Create a `Sandbox` backed by a Docker container.
67
+ * Requires `dockerode` as an optional peer dependency.
68
+ *
69
+ * **Auto-creation:** When `container` is omitted and `image` is provided,
70
+ * the container is created and started lazily on the first `init()` call.
71
+ * The container ID is available through `sandboxId()` for session
72
+ * persistence. Pass the stored ID back through `init(storedId)` to
73
+ * reattach to an existing container on resume.
74
+ *
75
+ * **Explicit container:** When `container` is provided, `init()` binds
76
+ * it immediately. `dispose()` is a no-op — the caller owns the
77
+ * container's lifecycle.
78
+ *
79
+ * @example
80
+ * ```ts
81
+ * // Auto-create from image
82
+ * const sandbox = DockerSandbox({ image: "ubuntu:22.04", cwd: "/workspace" });
83
+ *
84
+ * // Explicit container (lifecycle managed externally)
85
+ * const sandbox = DockerSandbox({ container: myDockerodeContainer });
86
+ * ```
87
+ */
88
+ declare function DockerSandbox(opts: DockerSandboxOptions): Sandbox;
89
+
90
+ interface DockerFsOptions {
91
+ /** A dockerode Container instance for the target container. */
92
+ container: DockerContainer;
93
+ /** Working directory for relative path resolution (default: /). */
94
+ workingDir?: string;
95
+ }
96
+ /**
97
+ * VirtualFs backed by file operations inside a Docker container.
98
+ *
99
+ * Uses `container.exec()` to run filesystem commands (cat, tee, rm, mkdir,
100
+ * stat, etc.) inside the container. File writes use exec + tee to avoid
101
+ * tar archive overhead for text content.
102
+ *
103
+ * Requires `dockerode` as an optional peer dependency.
104
+ * The user is responsible for container lifecycle.
105
+ */
106
+ declare class DockerFs implements VirtualFs {
107
+ private container;
108
+ private workingDir;
109
+ constructor(opts: DockerFsOptions);
110
+ private resolvePath;
111
+ private exec;
112
+ readFile(path: string, _opts?: ReadOptions): Promise<string>;
113
+ readFileBytes(path: string, maxBytes?: number): Promise<Buffer>;
114
+ writeFile(path: string, content: string): Promise<void>;
115
+ appendFile(path: string, content: string): Promise<void>;
116
+ deleteFile(path: string, opts?: {
117
+ recursive?: boolean;
118
+ }): Promise<void>;
119
+ mkdir(path: string, opts?: {
120
+ recursive?: boolean;
121
+ }): Promise<void>;
122
+ readdir(path: string, _opts?: {
123
+ recursive?: boolean;
124
+ }): Promise<FileEntry[]>;
125
+ exists(path: string): Promise<boolean>;
126
+ stat(path: string): Promise<FileStat>;
127
+ }
128
+
129
+ export { DockerComputer, type DockerComputerOptions, type DockerContainer, DockerFs, type DockerFsOptions, DockerSandbox, type DockerSandboxOptions };
package/dist/docker.js ADDED
@@ -0,0 +1,401 @@
1
+ import {
2
+ createComputerProxy,
3
+ createFsProxy
4
+ } from "./chunk-I5SBSOS6.js";
5
+ import "./chunk-DGUM43GV.js";
6
+
7
+ // src/virtual/docker-fs.ts
8
+ import * as path from "path";
9
+ var DockerFs = class {
10
+ container;
11
+ workingDir;
12
+ constructor(opts) {
13
+ this.container = opts.container;
14
+ this.workingDir = opts.workingDir ?? "/";
15
+ }
16
+ resolvePath(p) {
17
+ if (p.includes("\0")) {
18
+ throw new Error("Path contains null bytes");
19
+ }
20
+ const normalizedBase = this.workingDir.endsWith("/") ? this.workingDir : this.workingDir + "/";
21
+ if (p.startsWith("/")) {
22
+ const normalized = path.normalize(p);
23
+ if (normalized !== this.workingDir && !normalized.startsWith(normalizedBase)) {
24
+ throw new Error(`Absolute path "${p}" is outside working directory "${this.workingDir}"`);
25
+ }
26
+ return normalized;
27
+ }
28
+ const resolved = path.resolve(this.workingDir, p);
29
+ if (resolved !== this.workingDir && !resolved.startsWith(normalizedBase)) {
30
+ throw new Error(`Path "${p}" escapes working directory "${this.workingDir}"`);
31
+ }
32
+ return resolved;
33
+ }
34
+ async exec(cmd) {
35
+ const execInstance = await this.container.exec({
36
+ Cmd: cmd,
37
+ AttachStdout: true,
38
+ AttachStderr: true,
39
+ Tty: false
40
+ });
41
+ const stream = await execInstance.start({ hijack: true, stdin: false });
42
+ const result = await collectExecStream(stream);
43
+ const inspection = await execInstance.inspect();
44
+ return { exitCode: inspection.ExitCode, ...result };
45
+ }
46
+ async readFile(path2, _opts) {
47
+ const resolved = this.resolvePath(path2);
48
+ const { exitCode, stdout, stderr } = await this.exec([
49
+ "cat",
50
+ resolved
51
+ ]);
52
+ if (exitCode !== 0) {
53
+ throw new Error(`DockerFs readFile failed: ${stderr.trim() || `exit code ${exitCode}`}`);
54
+ }
55
+ return stdout;
56
+ }
57
+ async readFileBytes(path2, maxBytes) {
58
+ const resolved = this.resolvePath(path2);
59
+ const cmd = maxBytes !== void 0 ? ["head", "-c", String(maxBytes), resolved] : ["cat", resolved];
60
+ const { exitCode, stdout, stderr } = await this.exec([
61
+ "bash",
62
+ "-c",
63
+ `${cmd.map(shellEscape).join(" ")} | base64`
64
+ ]);
65
+ if (exitCode !== 0) {
66
+ throw new Error(`DockerFs readFileBytes failed: ${stderr.trim() || `exit code ${exitCode}`}`);
67
+ }
68
+ return Buffer.from(stdout.trim(), "base64");
69
+ }
70
+ async writeFile(path2, content) {
71
+ const resolved = this.resolvePath(path2);
72
+ const dir = resolved.substring(0, resolved.lastIndexOf("/"));
73
+ if (dir) {
74
+ await this.exec(["mkdir", "-p", dir]);
75
+ }
76
+ const encoded = Buffer.from(content, "utf-8").toString("base64");
77
+ const MAX_INLINE_LEN = 1e5;
78
+ if (encoded.length <= MAX_INLINE_LEN) {
79
+ const { exitCode, stderr } = await this.exec([
80
+ "bash",
81
+ "-c",
82
+ `echo ${shellEscape(encoded)} | base64 -d > ${shellEscape(resolved)}`
83
+ ]);
84
+ if (exitCode !== 0) {
85
+ throw new Error(`DockerFs writeFile failed: ${stderr.trim()}`);
86
+ }
87
+ } else {
88
+ const execInstance = await this.container.exec({
89
+ Cmd: ["bash", "-c", `base64 -d > ${shellEscape(resolved)}`],
90
+ AttachStdout: true,
91
+ AttachStderr: true,
92
+ AttachStdin: true,
93
+ Tty: false
94
+ });
95
+ const stream = await execInstance.start({ hijack: true, stdin: true });
96
+ const writable = stream;
97
+ writable.write(encoded);
98
+ writable.end();
99
+ const result = await collectExecStream(stream);
100
+ const inspection = await execInstance.inspect();
101
+ if (inspection.ExitCode !== 0) {
102
+ throw new Error(`DockerFs writeFile failed: ${result.stderr.trim()}`);
103
+ }
104
+ }
105
+ }
106
+ async appendFile(path2, content) {
107
+ const resolved = this.resolvePath(path2);
108
+ const dir = resolved.substring(0, resolved.lastIndexOf("/"));
109
+ if (dir) {
110
+ await this.exec(["mkdir", "-p", dir]);
111
+ }
112
+ const encoded = Buffer.from(content, "utf-8").toString("base64");
113
+ const { exitCode, stderr } = await this.exec([
114
+ "bash",
115
+ "-c",
116
+ `echo ${shellEscape(encoded)} | base64 -d >> ${shellEscape(resolved)}`
117
+ ]);
118
+ if (exitCode !== 0) {
119
+ throw new Error(`DockerFs appendFile failed: ${stderr.trim()}`);
120
+ }
121
+ }
122
+ async deleteFile(path2, opts) {
123
+ const resolved = this.resolvePath(path2);
124
+ const args = opts?.recursive ? ["rm", "-rf", resolved] : ["rm", "-f", resolved];
125
+ await this.exec(args);
126
+ }
127
+ async mkdir(path2, opts) {
128
+ const resolved = this.resolvePath(path2);
129
+ const args = opts?.recursive ? ["mkdir", "-p", resolved] : ["mkdir", resolved];
130
+ await this.exec(args);
131
+ }
132
+ async readdir(path2, _opts) {
133
+ const resolved = this.resolvePath(path2);
134
+ const { exitCode, stdout, stderr } = await this.exec([
135
+ "bash",
136
+ "-c",
137
+ `find ${shellEscape(resolved)} -maxdepth 1 -mindepth 1 -printf '%y %p\\n' 2>/dev/null`
138
+ ]);
139
+ if (exitCode !== 0 && stderr.trim()) {
140
+ throw new Error(`DockerFs readdir failed: ${stderr.trim()}`);
141
+ }
142
+ const entries = [];
143
+ for (const line of stdout.trim().split("\n")) {
144
+ if (!line) continue;
145
+ const spaceIdx = line.indexOf(" ");
146
+ const type = line.substring(0, spaceIdx);
147
+ const fullPath = line.substring(spaceIdx + 1);
148
+ const name = fullPath.substring(fullPath.lastIndexOf("/") + 1);
149
+ entries.push({
150
+ name,
151
+ path: fullPath,
152
+ isDirectory: type === "d",
153
+ isFile: type === "f"
154
+ });
155
+ }
156
+ return entries;
157
+ }
158
+ async exists(path2) {
159
+ const resolved = this.resolvePath(path2);
160
+ const { exitCode } = await this.exec(["test", "-e", resolved]);
161
+ return exitCode === 0;
162
+ }
163
+ async stat(path2) {
164
+ const resolved = this.resolvePath(path2);
165
+ const { exitCode, stdout, stderr } = await this.exec([
166
+ "stat",
167
+ "-c",
168
+ "%s %F %W %Y",
169
+ resolved
170
+ ]);
171
+ if (exitCode !== 0) {
172
+ throw new Error(`DockerFs stat failed: ${stderr.trim() || `exit code ${exitCode}`}`);
173
+ }
174
+ const parts = stdout.trim().split(" ");
175
+ const size = parseInt(parts[0], 10);
176
+ const fileType = parts[1];
177
+ const createdEpoch = parseInt(parts[2], 10);
178
+ const modifiedEpoch = parseInt(parts[3], 10);
179
+ return {
180
+ size,
181
+ isDirectory: fileType === "directory",
182
+ isFile: fileType.startsWith("regular"),
183
+ createdAt: createdEpoch > 0 ? new Date(createdEpoch * 1e3) : void 0,
184
+ modifiedAt: modifiedEpoch > 0 ? new Date(modifiedEpoch * 1e3) : void 0
185
+ };
186
+ }
187
+ };
188
+ function shellEscape(s) {
189
+ return `'${s.replace(/'/g, "'\\''")}'`;
190
+ }
191
+ function collectExecStream(stream) {
192
+ return new Promise((resolve2, reject) => {
193
+ const stdoutBufs = [];
194
+ const stderrBufs = [];
195
+ let pending = Buffer.alloc(0);
196
+ stream.on("data", (chunk) => {
197
+ let buf = pending.length > 0 ? Buffer.concat([pending, chunk]) : chunk;
198
+ let offset = 0;
199
+ while (offset + 8 <= buf.length) {
200
+ const payloadLen = buf.readUInt32BE(offset + 4);
201
+ if (offset + 8 + payloadLen > buf.length) break;
202
+ const streamType = buf[offset];
203
+ const payload = buf.subarray(offset + 8, offset + 8 + payloadLen);
204
+ if (streamType === 2) {
205
+ stderrBufs.push(payload);
206
+ } else {
207
+ stdoutBufs.push(payload);
208
+ }
209
+ offset += 8 + payloadLen;
210
+ }
211
+ pending = offset < buf.length ? buf.subarray(offset) : Buffer.alloc(0);
212
+ });
213
+ stream.on("end", () => {
214
+ resolve2({
215
+ stdout: Buffer.concat(stdoutBufs).toString("utf-8"),
216
+ stderr: Buffer.concat(stderrBufs).toString("utf-8")
217
+ });
218
+ });
219
+ stream.on("error", (err) => reject(err));
220
+ });
221
+ }
222
+
223
+ // src/virtual/docker-computer.ts
224
+ var DockerComputer = class {
225
+ container;
226
+ defaultCwd;
227
+ defaultTimeout;
228
+ constructor(opts) {
229
+ this.container = opts.container;
230
+ this.defaultCwd = opts.defaultCwd ?? "/";
231
+ this.defaultTimeout = opts.defaultTimeout ?? 3e4;
232
+ }
233
+ async executeCommand(command, opts) {
234
+ const cwd = opts?.cwd ?? this.defaultCwd;
235
+ const timeout = opts?.timeout ?? this.defaultTimeout;
236
+ const execOpts = {
237
+ Cmd: ["bash", "-c", `cd ${shellEscape2(cwd)} && ${command}`],
238
+ AttachStdout: true,
239
+ AttachStderr: true,
240
+ Tty: false
241
+ };
242
+ if (opts?.env) {
243
+ execOpts.Env = Object.entries(opts.env).map(
244
+ ([k, v]) => `${k}=${v}`
245
+ );
246
+ }
247
+ const exec = await this.container.exec(execOpts);
248
+ const stream = await exec.start({ hijack: true, stdin: false });
249
+ const { stdout, stderr } = await collectStream(stream, timeout);
250
+ const inspection = await exec.inspect();
251
+ return {
252
+ exitCode: inspection.ExitCode,
253
+ stdout,
254
+ stderr
255
+ };
256
+ }
257
+ };
258
+ function shellEscape2(s) {
259
+ return `'${s.replace(/'/g, "'\\''")}'`;
260
+ }
261
+ function collectStream(stream, timeout) {
262
+ return new Promise((resolve2, reject) => {
263
+ const stdoutBufs = [];
264
+ const stderrBufs = [];
265
+ let pending = Buffer.alloc(0);
266
+ const timer = setTimeout(() => {
267
+ stream.destroy?.();
268
+ resolve2({
269
+ stdout: Buffer.concat(stdoutBufs).toString("utf-8"),
270
+ stderr: Buffer.concat(stderrBufs).toString("utf-8") + "\n[timeout after " + timeout + "ms]"
271
+ });
272
+ }, timeout);
273
+ stream.on("data", (chunk) => {
274
+ let buf = pending.length > 0 ? Buffer.concat([pending, chunk]) : chunk;
275
+ let offset = 0;
276
+ while (offset + 8 <= buf.length) {
277
+ const payloadLen = buf.readUInt32BE(offset + 4);
278
+ if (offset + 8 + payloadLen > buf.length) break;
279
+ const streamType = buf[offset];
280
+ const payload = buf.subarray(offset + 8, offset + 8 + payloadLen);
281
+ if (streamType === 2) {
282
+ stderrBufs.push(payload);
283
+ } else {
284
+ stdoutBufs.push(payload);
285
+ }
286
+ offset += 8 + payloadLen;
287
+ }
288
+ pending = offset < buf.length ? buf.subarray(offset) : Buffer.alloc(0);
289
+ });
290
+ stream.on("end", () => {
291
+ clearTimeout(timer);
292
+ resolve2({
293
+ stdout: Buffer.concat(stdoutBufs).toString("utf-8"),
294
+ stderr: Buffer.concat(stderrBufs).toString("utf-8")
295
+ });
296
+ });
297
+ stream.on("error", (err) => {
298
+ clearTimeout(timer);
299
+ reject(err);
300
+ });
301
+ });
302
+ }
303
+
304
+ // src/virtual/docker-sandbox.ts
305
+ function DockerSandbox(opts) {
306
+ if (opts.container) {
307
+ const c = opts.container;
308
+ return {
309
+ fs: new DockerFs({ container: c, workingDir: opts.cwd }),
310
+ computer: new DockerComputer({
311
+ container: c,
312
+ defaultCwd: opts.cwd,
313
+ defaultTimeout: opts.defaultTimeout
314
+ }),
315
+ sandboxId: () => c.id
316
+ };
317
+ }
318
+ if (!opts.image) {
319
+ throw new Error("DockerSandbox requires either `container` or `image`");
320
+ }
321
+ const fsProxy = createFsProxy();
322
+ const computerProxy = createComputerProxy();
323
+ let containerId;
324
+ let containerRef;
325
+ let autoCreated = false;
326
+ let initPromise = null;
327
+ async function doInit(reconnectId) {
328
+ const Docker = (await import("dockerode")).default;
329
+ const docker = new Docker();
330
+ let container;
331
+ if (reconnectId) {
332
+ container = docker.getContainer(reconnectId);
333
+ try {
334
+ await container.inspect();
335
+ } catch {
336
+ container = await docker.createContainer({
337
+ Image: opts.image,
338
+ Cmd: opts.cmd ?? ["sleep", "infinity"],
339
+ Env: opts.env,
340
+ Tty: false,
341
+ ...opts.dockerOptions
342
+ });
343
+ await container.start();
344
+ autoCreated = true;
345
+ }
346
+ } else {
347
+ container = await docker.createContainer({
348
+ Image: opts.image,
349
+ Cmd: opts.cmd ?? ["sleep", "infinity"],
350
+ Env: opts.env,
351
+ Tty: false,
352
+ ...opts.dockerOptions
353
+ });
354
+ await container.start();
355
+ autoCreated = true;
356
+ }
357
+ containerRef = container;
358
+ containerId = container.id;
359
+ fsProxy.setTarget(new DockerFs({ container, workingDir: opts.cwd }));
360
+ computerProxy.setTarget(new DockerComputer({
361
+ container,
362
+ defaultCwd: opts.cwd,
363
+ defaultTimeout: opts.defaultTimeout
364
+ }));
365
+ }
366
+ return {
367
+ fs: fsProxy,
368
+ computer: computerProxy,
369
+ sandboxId: () => containerId,
370
+ init(sandboxId) {
371
+ if (!initPromise) {
372
+ initPromise = doInit(sandboxId).catch((err) => {
373
+ initPromise = null;
374
+ throw err;
375
+ });
376
+ }
377
+ return initPromise;
378
+ },
379
+ async dispose() {
380
+ if (initPromise) {
381
+ await initPromise.catch(() => {
382
+ });
383
+ }
384
+ if (!autoCreated || !containerRef) return;
385
+ try {
386
+ await containerRef.stop();
387
+ } catch {
388
+ }
389
+ try {
390
+ await containerRef.remove();
391
+ } catch {
392
+ }
393
+ }
394
+ };
395
+ }
396
+ export {
397
+ DockerComputer,
398
+ DockerFs,
399
+ DockerSandbox
400
+ };
401
+ //# sourceMappingURL=docker.js.map