@secure-exec/browser 0.0.0-agentos-dylib-base.edaa4a4

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 (66) hide show
  1. package/README.md +6 -0
  2. package/dist/child-process-bridge.d.ts +25 -0
  3. package/dist/child-process-bridge.js +50 -0
  4. package/dist/converged-base64.d.ts +2 -0
  5. package/dist/converged-base64.js +41 -0
  6. package/dist/converged-dgram-bridge.d.ts +11 -0
  7. package/dist/converged-dgram-bridge.js +147 -0
  8. package/dist/converged-driver-setup.d.ts +22 -0
  9. package/dist/converged-driver-setup.js +72 -0
  10. package/dist/converged-execution-host-bridge.d.ts +7 -0
  11. package/dist/converged-execution-host-bridge.js +85 -0
  12. package/dist/converged-executor-session.d.ts +60 -0
  13. package/dist/converged-executor-session.js +127 -0
  14. package/dist/converged-fs-bridge.d.ts +42 -0
  15. package/dist/converged-fs-bridge.js +245 -0
  16. package/dist/converged-module-servicer.d.ts +8 -0
  17. package/dist/converged-module-servicer.js +79 -0
  18. package/dist/converged-net-bridge.d.ts +28 -0
  19. package/dist/converged-net-bridge.js +155 -0
  20. package/dist/converged-permissions.d.ts +9 -0
  21. package/dist/converged-permissions.js +46 -0
  22. package/dist/converged-sync-bridge-handler.d.ts +47 -0
  23. package/dist/converged-sync-bridge-handler.js +140 -0
  24. package/dist/converged-sync-bridge-router.d.ts +33 -0
  25. package/dist/converged-sync-bridge-router.js +41 -0
  26. package/dist/driver.d.ts +91 -0
  27. package/dist/driver.js +386 -0
  28. package/dist/encoding.d.ts +4 -0
  29. package/dist/encoding.js +102 -0
  30. package/dist/generated/util-polyfill.d.ts +1 -0
  31. package/dist/generated/util-polyfill.js +2 -0
  32. package/dist/index.d.ts +9 -0
  33. package/dist/index.js +5 -0
  34. package/dist/kernel-backed-filesystem.d.ts +33 -0
  35. package/dist/kernel-backed-filesystem.js +205 -0
  36. package/dist/os-filesystem.d.ts +47 -0
  37. package/dist/os-filesystem.js +409 -0
  38. package/dist/permission-validation.d.ts +15 -0
  39. package/dist/permission-validation.js +62 -0
  40. package/dist/root-filesystem-from-vfs.d.ts +13 -0
  41. package/dist/root-filesystem-from-vfs.js +95 -0
  42. package/dist/runtime-driver.d.ts +66 -0
  43. package/dist/runtime-driver.js +611 -0
  44. package/dist/runtime.d.ts +248 -0
  45. package/dist/runtime.js +2296 -0
  46. package/dist/sidecar-wasm-module.d.ts +62 -0
  47. package/dist/sidecar-wasm-module.js +28 -0
  48. package/dist/sidecar-worker-protocol.d.ts +14 -0
  49. package/dist/sidecar-worker-protocol.js +9 -0
  50. package/dist/sidecar-worker.d.ts +19 -0
  51. package/dist/sidecar-worker.js +63 -0
  52. package/dist/signals.d.ts +13 -0
  53. package/dist/signals.js +89 -0
  54. package/dist/sync-bridge.d.ts +50 -0
  55. package/dist/sync-bridge.js +93 -0
  56. package/dist/wasi-polyfill.d.ts +1 -0
  57. package/dist/wasi-polyfill.js +2154 -0
  58. package/dist/worker-adapter.d.ts +21 -0
  59. package/dist/worker-adapter.js +41 -0
  60. package/dist/worker-protocol.d.ts +104 -0
  61. package/dist/worker-protocol.js +1 -0
  62. package/dist/worker-sidecar-client.d.ts +71 -0
  63. package/dist/worker-sidecar-client.js +152 -0
  64. package/dist/worker.d.ts +1 -0
  65. package/dist/worker.js +2125 -0
  66. package/package.json +111 -0
@@ -0,0 +1,33 @@
1
+ import type { ConvergedSidecarRequestTransport } from "./converged-sync-bridge-handler.js";
2
+ import type { VirtualDirEntry, VirtualFileSystem, VirtualStat } from "./runtime.js";
3
+ export declare class KernelBackedFilesystem implements VirtualFileSystem {
4
+ private readonly transport;
5
+ constructor(transport: ConvergedSidecarRequestTransport);
6
+ private call;
7
+ readFile(path: string): Promise<Uint8Array>;
8
+ readTextFile(path: string): Promise<string>;
9
+ readDir(path: string): Promise<string[]>;
10
+ readDirWithTypes(path: string): Promise<VirtualDirEntry[]>;
11
+ writeFile(path: string, content: string | Uint8Array): Promise<void>;
12
+ createDir(path: string): Promise<void>;
13
+ mkdir(path: string, options?: {
14
+ recursive?: boolean;
15
+ }): Promise<void>;
16
+ exists(path: string): Promise<boolean>;
17
+ stat(path: string): Promise<VirtualStat>;
18
+ lstat(path: string): Promise<VirtualStat>;
19
+ private requireStat;
20
+ removeFile(path: string): Promise<void>;
21
+ removeDir(path: string): Promise<void>;
22
+ rename(oldPath: string, newPath: string): Promise<void>;
23
+ realpath(path: string): Promise<string>;
24
+ symlink(target: string, linkPath: string): Promise<void>;
25
+ readlink(path: string): Promise<string>;
26
+ link(oldPath: string, newPath: string): Promise<void>;
27
+ chmod(path: string, mode: number): Promise<void>;
28
+ chown(path: string, uid: number, gid: number): Promise<void>;
29
+ utimes(path: string, atime: number, mtime: number): Promise<void>;
30
+ truncate(path: string, length: number): Promise<void>;
31
+ pread(path: string, offset: number, length: number): Promise<Uint8Array>;
32
+ pwrite(): Promise<void>;
33
+ }
@@ -0,0 +1,205 @@
1
+ // A VirtualFileSystem backed by the wasm kernel over the wire.
2
+ //
3
+ // Lets the existing naive-Node module resolver (`resolveModule` in runtime.ts)
4
+ // run unchanged in the converged path by giving it a filesystem whose every
5
+ // operation is a `guest_filesystem_call` to the kernel, instead of the legacy
6
+ // in-process TS filesystem. Resolution therefore reads exactly the kernel's
7
+ // view (mounts, symlinks, exports/conditions), keeping it faithful on both
8
+ // backends (the CLAUDE.md npm-compat rule).
9
+ //
10
+ // Methods are async to satisfy `VirtualFileSystem`, but the underlying transport
11
+ // is synchronous (`pushFrame`); the promises resolve on the microtask queue.
12
+ import { decodeBase64, encodeBase64 } from "./converged-base64.js";
13
+ import { wireStatToDirEntry, wireStatToVirtualStat, } from "./converged-fs-bridge.js";
14
+ export class KernelBackedFilesystem {
15
+ transport;
16
+ constructor(transport) {
17
+ this.transport = transport;
18
+ }
19
+ call(payload) {
20
+ const response = this.transport.sendRequest(payload);
21
+ if (response.type !== "guest_filesystem_result") {
22
+ throw new Error(`expected guest_filesystem_result, got ${response.type}`);
23
+ }
24
+ return response;
25
+ }
26
+ async readFile(path) {
27
+ const result = this.call({
28
+ type: "guest_filesystem_call",
29
+ operation: "read_file",
30
+ path,
31
+ });
32
+ const content = result.content ?? "";
33
+ return result.encoding === "base64"
34
+ ? decodeBase64(content)
35
+ : new TextEncoder().encode(content);
36
+ }
37
+ async readTextFile(path) {
38
+ const result = this.call({
39
+ type: "guest_filesystem_call",
40
+ operation: "read_file",
41
+ path,
42
+ });
43
+ const content = result.content ?? "";
44
+ return result.encoding === "base64"
45
+ ? new TextDecoder().decode(decodeBase64(content))
46
+ : content;
47
+ }
48
+ async readDir(path) {
49
+ const result = this.call({
50
+ type: "guest_filesystem_call",
51
+ operation: "read_dir",
52
+ path,
53
+ });
54
+ return [...(result.entries ?? [])];
55
+ }
56
+ async readDirWithTypes(path) {
57
+ const names = await this.readDir(path);
58
+ return names.map((name) => {
59
+ const child = this.call({
60
+ type: "guest_filesystem_call",
61
+ operation: "lstat",
62
+ path: joinPath(path, name),
63
+ });
64
+ if (!child.stat) {
65
+ throw new Error(`lstat for ${name} returned no stat`);
66
+ }
67
+ return wireStatToDirEntry(name, child.stat);
68
+ });
69
+ }
70
+ async writeFile(path, content) {
71
+ const payload = typeof content === "string"
72
+ ? { content, encoding: "utf8" }
73
+ : { content: encodeBase64(content), encoding: "base64" };
74
+ this.call({
75
+ type: "guest_filesystem_call",
76
+ operation: "write_file",
77
+ path,
78
+ content: payload.content,
79
+ encoding: payload.encoding,
80
+ });
81
+ }
82
+ async createDir(path) {
83
+ this.call({ type: "guest_filesystem_call", operation: "create_dir", path });
84
+ }
85
+ async mkdir(path, options) {
86
+ this.call({
87
+ type: "guest_filesystem_call",
88
+ operation: "mkdir",
89
+ path,
90
+ recursive: options?.recursive ?? false,
91
+ });
92
+ }
93
+ async exists(path) {
94
+ return this.call({ type: "guest_filesystem_call", operation: "exists", path })
95
+ .exists ?? false;
96
+ }
97
+ async stat(path) {
98
+ return this.requireStat("stat", path);
99
+ }
100
+ async lstat(path) {
101
+ return this.requireStat("lstat", path);
102
+ }
103
+ requireStat(operation, path) {
104
+ const result = this.call({
105
+ type: "guest_filesystem_call",
106
+ operation,
107
+ path,
108
+ });
109
+ if (!result.stat) {
110
+ throw new Error(`${operation} for ${path} returned no stat`);
111
+ }
112
+ return wireStatToVirtualStat(result.stat);
113
+ }
114
+ async removeFile(path) {
115
+ this.call({ type: "guest_filesystem_call", operation: "remove_file", path });
116
+ }
117
+ async removeDir(path) {
118
+ this.call({ type: "guest_filesystem_call", operation: "remove_dir", path });
119
+ }
120
+ async rename(oldPath, newPath) {
121
+ this.call({
122
+ type: "guest_filesystem_call",
123
+ operation: "rename",
124
+ path: oldPath,
125
+ destination_path: newPath,
126
+ });
127
+ }
128
+ async realpath(path) {
129
+ return (this.call({ type: "guest_filesystem_call", operation: "realpath", path })
130
+ .target ?? path);
131
+ }
132
+ async symlink(target, linkPath) {
133
+ this.call({
134
+ type: "guest_filesystem_call",
135
+ operation: "symlink",
136
+ path: linkPath,
137
+ target,
138
+ });
139
+ }
140
+ async readlink(path) {
141
+ return (this.call({
142
+ type: "guest_filesystem_call",
143
+ operation: "read_link",
144
+ path,
145
+ }).target ?? "");
146
+ }
147
+ async link(oldPath, newPath) {
148
+ this.call({
149
+ type: "guest_filesystem_call",
150
+ operation: "link",
151
+ path: oldPath,
152
+ destination_path: newPath,
153
+ });
154
+ }
155
+ async chmod(path, mode) {
156
+ this.call({ type: "guest_filesystem_call", operation: "chmod", path, mode });
157
+ }
158
+ async chown(path, uid, gid) {
159
+ this.call({
160
+ type: "guest_filesystem_call",
161
+ operation: "chown",
162
+ path,
163
+ uid,
164
+ gid,
165
+ });
166
+ }
167
+ async utimes(path, atime, mtime) {
168
+ this.call({
169
+ type: "guest_filesystem_call",
170
+ operation: "utimes",
171
+ path,
172
+ atime_ms: atime,
173
+ mtime_ms: mtime,
174
+ });
175
+ }
176
+ async truncate(path, length) {
177
+ this.call({
178
+ type: "guest_filesystem_call",
179
+ operation: "truncate",
180
+ path,
181
+ len: length,
182
+ });
183
+ }
184
+ async pread(path, offset, length) {
185
+ const result = this.call({
186
+ type: "guest_filesystem_call",
187
+ operation: "pread",
188
+ path,
189
+ offset,
190
+ len: length,
191
+ });
192
+ const content = result.content ?? "";
193
+ return result.encoding === "base64"
194
+ ? decodeBase64(content)
195
+ : new TextEncoder().encode(content);
196
+ }
197
+ async pwrite() {
198
+ // The kernel guest-filesystem wire surface has no positional write; the
199
+ // module resolver (the converged consumer) never calls it.
200
+ throw new Error("ENOSYS: pwrite is not supported by the kernel-backed filesystem");
201
+ }
202
+ }
203
+ function joinPath(parent, child) {
204
+ return parent.endsWith("/") ? `${parent}${child}` : `${parent}/${child}`;
205
+ }
@@ -0,0 +1,47 @@
1
+ /**
2
+ * In-memory filesystem for browser environments.
3
+ *
4
+ * In-memory filesystem with POSIX extensions (symlinks, hard links, chmod,
5
+ * chown, utimes, truncate) needed by the kernel VFS interface.
6
+ */
7
+ import type { VirtualDirEntry, VirtualFileSystem, VirtualStat } from "./runtime.js";
8
+ export declare class InMemoryFileSystem implements VirtualFileSystem {
9
+ private entries;
10
+ constructor();
11
+ readFile(path: string): Promise<Uint8Array>;
12
+ readTextFile(path: string): Promise<string>;
13
+ readDir(path: string): Promise<string[]>;
14
+ readDirWithTypes(path: string): Promise<VirtualDirEntry[]>;
15
+ writeFile(path: string, content: string | Uint8Array): Promise<void>;
16
+ createDir(path: string): Promise<void>;
17
+ mkdir(path: string, options?: {
18
+ recursive?: boolean;
19
+ }): Promise<void>;
20
+ exists(path: string): Promise<boolean>;
21
+ stat(path: string): Promise<VirtualStat>;
22
+ removeFile(path: string): Promise<void>;
23
+ removeDir(path: string): Promise<void>;
24
+ realpath(path: string): Promise<string>;
25
+ rename(oldPath: string, newPath: string): Promise<void>;
26
+ symlink(target: string, linkPath: string): Promise<void>;
27
+ readlink(path: string): Promise<string>;
28
+ lstat(path: string): Promise<VirtualStat>;
29
+ link(oldPath: string, newPath: string): Promise<void>;
30
+ chmod(path: string, mode: number): Promise<void>;
31
+ chown(path: string, uid: number, gid: number): Promise<void>;
32
+ utimes(path: string, atime: number, mtime: number): Promise<void>;
33
+ truncate(path: string, length: number): Promise<void>;
34
+ pread(path: string, offset: number, length: number): Promise<Uint8Array>;
35
+ pwrite(path: string, offset: number, data: Uint8Array): Promise<void>;
36
+ /**
37
+ * Resolve symlinks to get the final path. Returns the normalized path
38
+ * after following all symlinks.
39
+ */
40
+ private resolvePath;
41
+ /** Resolve a path and return the entry (following symlinks). */
42
+ private resolveEntry;
43
+ private newDir;
44
+ private toStat;
45
+ private enoent;
46
+ }
47
+ export declare function createInMemoryFileSystem(): InMemoryFileSystem;
@@ -0,0 +1,409 @@
1
+ /**
2
+ * In-memory filesystem for browser environments.
3
+ *
4
+ * In-memory filesystem with POSIX extensions (symlinks, hard links, chmod,
5
+ * chown, utimes, truncate) needed by the kernel VFS interface.
6
+ */
7
+ const S_IFREG = 0o100000;
8
+ const S_IFDIR = 0o040000;
9
+ const S_IFLNK = 0o120000;
10
+ const MAX_SYMLINK_DEPTH = 40;
11
+ function normalizePath(path) {
12
+ if (!path)
13
+ return "/";
14
+ let normalized = path.startsWith("/") ? path : `/${path}`;
15
+ normalized = normalized.replace(/\/+/g, "/");
16
+ if (normalized.length > 1 && normalized.endsWith("/")) {
17
+ normalized = normalized.slice(0, -1);
18
+ }
19
+ // Resolve . and ..
20
+ const parts = normalized.split("/");
21
+ const resolved = [];
22
+ for (const part of parts) {
23
+ if (part === "." || part === "")
24
+ continue;
25
+ if (part === "..") {
26
+ resolved.pop();
27
+ }
28
+ else {
29
+ resolved.push(part);
30
+ }
31
+ }
32
+ return `/${resolved.join("/")}` || "/";
33
+ }
34
+ function dirname(path) {
35
+ const parts = normalizePath(path).split("/").filter(Boolean);
36
+ if (parts.length <= 1)
37
+ return "/";
38
+ return `/${parts.slice(0, -1).join("/")}`;
39
+ }
40
+ let nextIno = 1;
41
+ export class InMemoryFileSystem {
42
+ entries = new Map();
43
+ constructor() {
44
+ // Root directory
45
+ this.entries.set("/", this.newDir());
46
+ }
47
+ // --- Core operations ---
48
+ async readFile(path) {
49
+ const entry = this.resolveEntry(path);
50
+ if (!entry || entry.type !== "file") {
51
+ throw this.enoent("open", path);
52
+ }
53
+ entry.atimeMs = Date.now();
54
+ return entry.data;
55
+ }
56
+ async readTextFile(path) {
57
+ const data = await this.readFile(path);
58
+ return new TextDecoder().decode(data);
59
+ }
60
+ async readDir(path) {
61
+ return (await this.readDirWithTypes(path)).map((e) => e.name);
62
+ }
63
+ async readDirWithTypes(path) {
64
+ const resolved = this.resolvePath(path);
65
+ const dir = this.entries.get(resolved);
66
+ if (!dir || dir.type !== "dir") {
67
+ throw this.enoent("scandir", path);
68
+ }
69
+ const prefix = resolved === "/" ? "/" : `${resolved}/`;
70
+ const names = new Map();
71
+ for (const [entryPath, entry] of this.entries) {
72
+ if (entryPath.startsWith(prefix)) {
73
+ const rest = entryPath.slice(prefix.length);
74
+ if (rest && !rest.includes("/")) {
75
+ names.set(rest, {
76
+ name: rest,
77
+ isDirectory: entry.type === "dir",
78
+ isSymbolicLink: entry.type === "symlink",
79
+ });
80
+ }
81
+ }
82
+ }
83
+ return Array.from(names.values());
84
+ }
85
+ async writeFile(path, content) {
86
+ const normalized = normalizePath(path);
87
+ // Ensure parent exists
88
+ await this.mkdir(dirname(normalized), { recursive: true });
89
+ const data = typeof content === "string" ? new TextEncoder().encode(content) : content;
90
+ const existing = this.entries.get(normalized);
91
+ if (existing && existing.type === "file") {
92
+ existing.data = data;
93
+ existing.mtimeMs = Date.now();
94
+ existing.ctimeMs = Date.now();
95
+ return;
96
+ }
97
+ const now = Date.now();
98
+ this.entries.set(normalized, {
99
+ type: "file",
100
+ data,
101
+ mode: S_IFREG | 0o644,
102
+ uid: 1000,
103
+ gid: 1000,
104
+ nlink: 1,
105
+ ino: nextIno++,
106
+ atimeMs: now,
107
+ mtimeMs: now,
108
+ ctimeMs: now,
109
+ birthtimeMs: now,
110
+ });
111
+ }
112
+ async createDir(path) {
113
+ const normalized = normalizePath(path);
114
+ const parent = dirname(normalized);
115
+ if (!this.entries.has(parent)) {
116
+ throw this.enoent("mkdir", path);
117
+ }
118
+ if (!this.entries.has(normalized)) {
119
+ this.entries.set(normalized, this.newDir());
120
+ }
121
+ }
122
+ async mkdir(path, options) {
123
+ const normalized = normalizePath(path);
124
+ if (options?.recursive !== false) {
125
+ // Recursive: create all missing parents
126
+ const parts = normalized.split("/").filter(Boolean);
127
+ let current = "";
128
+ for (const part of parts) {
129
+ current += `/${part}`;
130
+ if (!this.entries.has(current)) {
131
+ this.entries.set(current, this.newDir());
132
+ }
133
+ }
134
+ }
135
+ else {
136
+ await this.createDir(path);
137
+ }
138
+ }
139
+ async exists(path) {
140
+ try {
141
+ const resolved = this.resolvePath(path);
142
+ return this.entries.has(resolved);
143
+ }
144
+ catch {
145
+ return false;
146
+ }
147
+ }
148
+ async stat(path) {
149
+ const entry = this.resolveEntry(path);
150
+ if (!entry)
151
+ throw this.enoent("stat", path);
152
+ return this.toStat(entry);
153
+ }
154
+ async removeFile(path) {
155
+ const resolved = this.resolvePath(path);
156
+ const entry = this.entries.get(resolved);
157
+ if (!entry || entry.type === "dir") {
158
+ throw this.enoent("unlink", path);
159
+ }
160
+ this.entries.delete(resolved);
161
+ }
162
+ async removeDir(path) {
163
+ const resolved = this.resolvePath(path);
164
+ if (resolved === "/") {
165
+ throw new Error("EPERM: operation not permitted, rmdir '/'");
166
+ }
167
+ const entry = this.entries.get(resolved);
168
+ if (!entry || entry.type !== "dir") {
169
+ throw this.enoent("rmdir", path);
170
+ }
171
+ // Check if empty
172
+ const prefix = `${resolved}/`;
173
+ for (const key of this.entries.keys()) {
174
+ if (key.startsWith(prefix)) {
175
+ throw new Error(`ENOTEMPTY: directory not empty, rmdir '${path}'`);
176
+ }
177
+ }
178
+ this.entries.delete(resolved);
179
+ }
180
+ async realpath(path) {
181
+ return this.resolvePath(path);
182
+ }
183
+ async rename(oldPath, newPath) {
184
+ const oldResolved = this.resolvePath(oldPath);
185
+ const newNorm = normalizePath(newPath);
186
+ const entry = this.entries.get(oldResolved);
187
+ if (!entry)
188
+ throw this.enoent("rename", oldPath);
189
+ // Ensure parent of target exists
190
+ if (!this.entries.has(dirname(newNorm))) {
191
+ throw this.enoent("rename", newPath);
192
+ }
193
+ if (entry.type !== "dir") {
194
+ this.entries.set(newNorm, entry);
195
+ this.entries.delete(oldResolved);
196
+ return;
197
+ }
198
+ // Move directory and all children
199
+ const prefix = `${oldResolved}/`;
200
+ const toMove = [];
201
+ for (const [key, val] of this.entries) {
202
+ if (key === oldResolved || key.startsWith(prefix)) {
203
+ toMove.push([key, val]);
204
+ }
205
+ }
206
+ for (const [key] of toMove) {
207
+ this.entries.delete(key);
208
+ }
209
+ for (const [key, val] of toMove) {
210
+ const newKey = key === oldResolved ? newNorm : newNorm + key.slice(oldResolved.length);
211
+ this.entries.set(newKey, val);
212
+ }
213
+ }
214
+ // --- Symlinks ---
215
+ async symlink(target, linkPath) {
216
+ const normalized = normalizePath(linkPath);
217
+ if (this.entries.has(normalized)) {
218
+ throw new Error(`EEXIST: file already exists, symlink '${linkPath}'`);
219
+ }
220
+ const now = Date.now();
221
+ this.entries.set(normalized, {
222
+ type: "symlink",
223
+ target,
224
+ mode: S_IFLNK | 0o777,
225
+ uid: 1000,
226
+ gid: 1000,
227
+ nlink: 1,
228
+ ino: nextIno++,
229
+ atimeMs: now,
230
+ mtimeMs: now,
231
+ ctimeMs: now,
232
+ birthtimeMs: now,
233
+ });
234
+ }
235
+ async readlink(path) {
236
+ const normalized = normalizePath(path);
237
+ const entry = this.entries.get(normalized);
238
+ if (!entry || entry.type !== "symlink") {
239
+ throw this.enoent("readlink", path);
240
+ }
241
+ return entry.target;
242
+ }
243
+ async lstat(path) {
244
+ const normalized = normalizePath(path);
245
+ const entry = this.entries.get(normalized);
246
+ if (!entry)
247
+ throw this.enoent("lstat", path);
248
+ return this.toStat(entry);
249
+ }
250
+ // --- Links ---
251
+ async link(oldPath, newPath) {
252
+ const entry = this.resolveEntry(oldPath);
253
+ if (!entry || entry.type !== "file") {
254
+ throw this.enoent("link", oldPath);
255
+ }
256
+ const newNorm = normalizePath(newPath);
257
+ if (this.entries.has(newNorm)) {
258
+ throw new Error(`EEXIST: file already exists, link '${newPath}'`);
259
+ }
260
+ entry.nlink++;
261
+ this.entries.set(newNorm, entry);
262
+ }
263
+ // --- Permissions & Metadata ---
264
+ async chmod(path, mode) {
265
+ const entry = this.resolveEntry(path);
266
+ if (!entry)
267
+ throw this.enoent("chmod", path);
268
+ const callerTypeBits = mode & 0o170000;
269
+ if (callerTypeBits !== 0) {
270
+ entry.mode = mode;
271
+ }
272
+ else {
273
+ entry.mode = (entry.mode & 0o170000) | (mode & 0o7777);
274
+ }
275
+ entry.ctimeMs = Date.now();
276
+ }
277
+ async chown(path, uid, gid) {
278
+ const entry = this.resolveEntry(path);
279
+ if (!entry)
280
+ throw this.enoent("chown", path);
281
+ entry.uid = uid;
282
+ entry.gid = gid;
283
+ entry.ctimeMs = Date.now();
284
+ }
285
+ async utimes(path, atime, mtime) {
286
+ const entry = this.resolveEntry(path);
287
+ if (!entry)
288
+ throw this.enoent("utimes", path);
289
+ entry.atimeMs = atime;
290
+ entry.mtimeMs = mtime;
291
+ entry.ctimeMs = Date.now();
292
+ }
293
+ async truncate(path, length) {
294
+ const entry = this.resolveEntry(path);
295
+ if (!entry || entry.type !== "file") {
296
+ throw this.enoent("truncate", path);
297
+ }
298
+ if (length < entry.data.length) {
299
+ entry.data = entry.data.slice(0, length);
300
+ }
301
+ else if (length > entry.data.length) {
302
+ const newData = new Uint8Array(length);
303
+ newData.set(entry.data);
304
+ entry.data = newData;
305
+ }
306
+ entry.mtimeMs = Date.now();
307
+ entry.ctimeMs = Date.now();
308
+ }
309
+ async pread(path, offset, length) {
310
+ const entry = this.resolveEntry(path);
311
+ if (!entry || entry.type !== "file") {
312
+ throw this.enoent("open", path);
313
+ }
314
+ entry.atimeMs = Date.now();
315
+ if (offset >= entry.data.length)
316
+ return new Uint8Array(0);
317
+ return entry.data.slice(offset, Math.min(offset + length, entry.data.length));
318
+ }
319
+ async pwrite(path, offset, data) {
320
+ const entry = this.resolveEntry(path);
321
+ if (!entry || entry.type !== "file") {
322
+ throw this.enoent("open", path);
323
+ }
324
+ const endPos = offset + data.length;
325
+ const newContent = new Uint8Array(Math.max(entry.data.length, endPos));
326
+ newContent.set(entry.data);
327
+ newContent.set(data, offset);
328
+ entry.data = newContent;
329
+ const now = Date.now();
330
+ entry.mtimeMs = now;
331
+ entry.ctimeMs = now;
332
+ }
333
+ // --- Helpers ---
334
+ /**
335
+ * Resolve symlinks to get the final path. Returns the normalized path
336
+ * after following all symlinks.
337
+ */
338
+ resolvePath(path, depth = 0) {
339
+ if (depth > MAX_SYMLINK_DEPTH) {
340
+ throw new Error(`ELOOP: too many levels of symbolic links, '${path}'`);
341
+ }
342
+ const normalized = normalizePath(path);
343
+ if (normalized === "/") {
344
+ return "/";
345
+ }
346
+ const parts = normalized.split("/").filter(Boolean);
347
+ let current = "";
348
+ for (let index = 0; index < parts.length; index++) {
349
+ current = normalizePath(`${current}/${parts[index]}`);
350
+ const entry = this.entries.get(current);
351
+ if (entry?.type === "symlink") {
352
+ const target = entry.target.startsWith("/")
353
+ ? entry.target
354
+ : `${dirname(current)}/${entry.target}`;
355
+ const rest = parts.slice(index + 1).join("/");
356
+ return this.resolvePath(rest ? `${target}/${rest}` : target, depth + 1);
357
+ }
358
+ }
359
+ return normalized;
360
+ }
361
+ /** Resolve a path and return the entry (following symlinks). */
362
+ resolveEntry(path) {
363
+ const resolved = this.resolvePath(path);
364
+ return this.entries.get(resolved);
365
+ }
366
+ newDir() {
367
+ const now = Date.now();
368
+ return {
369
+ type: "dir",
370
+ mode: S_IFDIR | 0o755,
371
+ uid: 1000,
372
+ gid: 1000,
373
+ nlink: 2,
374
+ ino: nextIno++,
375
+ atimeMs: now,
376
+ mtimeMs: now,
377
+ ctimeMs: now,
378
+ birthtimeMs: now,
379
+ };
380
+ }
381
+ toStat(entry) {
382
+ const size = entry.type === "file" ? entry.data.length : 4096;
383
+ return {
384
+ mode: entry.mode,
385
+ size,
386
+ blocks: size === 0 ? 0 : Math.ceil(size / 512),
387
+ dev: 1,
388
+ rdev: 0,
389
+ isDirectory: entry.type === "dir",
390
+ isSymbolicLink: entry.type === "symlink",
391
+ atimeMs: entry.atimeMs,
392
+ mtimeMs: entry.mtimeMs,
393
+ ctimeMs: entry.ctimeMs,
394
+ birthtimeMs: entry.birthtimeMs,
395
+ ino: entry.ino,
396
+ nlink: entry.nlink,
397
+ uid: entry.uid,
398
+ gid: entry.gid,
399
+ };
400
+ }
401
+ enoent(op, path) {
402
+ const err = new Error(`ENOENT: no such file or directory, ${op} '${path}'`);
403
+ err.code = "ENOENT";
404
+ return err;
405
+ }
406
+ }
407
+ export function createInMemoryFileSystem() {
408
+ return new InMemoryFileSystem();
409
+ }