@struktoai/mirage-dsh 0.0.7-alpha.2 → 0.0.7
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/LICENSE +201 -0
- package/README.md +28 -29
- package/cordis.patch.yml +38 -6
- package/dist/{chunk-M4JOF35U.js → chunk-FRF6WPLN.js} +133 -44
- package/dist/chunk-FRF6WPLN.js.map +1 -0
- package/dist/chunk-G2GHA2F6.js +211 -0
- package/dist/chunk-G2GHA2F6.js.map +1 -0
- package/dist/{chunk-QTBMLMKT.js → chunk-KUIV2Z3M.js} +243 -326
- package/dist/chunk-KUIV2Z3M.js.map +1 -0
- package/dist/{chunk-4CZV24HW.js → chunk-OHZACFWO.js} +36 -14
- package/dist/chunk-OHZACFWO.js.map +1 -0
- package/dist/{chunk-2F2PJ7D7.js → chunk-OZ2F4R6T.js} +74 -17
- package/dist/chunk-OZ2F4R6T.js.map +1 -0
- package/dist/chunk-QH5H3VND.js +110 -0
- package/dist/chunk-QH5H3VND.js.map +1 -0
- package/dist/chunk-T4BSJAGN.js +64 -0
- package/dist/chunk-T4BSJAGN.js.map +1 -0
- package/dist/{fs-B3Zi9oo6.d.ts → fs-BNOIFi9o.d.ts} +48 -4
- package/dist/index.d.ts +8 -3
- package/dist/index.js +15 -6
- package/dist/plugin/fs.d.ts +1 -1
- package/dist/plugin/fs.js +2 -2
- package/dist/plugin/service.d.ts +2 -1
- package/dist/plugin/service.js +1 -1
- package/dist/plugin/shell.d.ts +1 -1
- package/dist/plugin/shell.js +3 -2
- package/dist/plugin/spill-store.d.ts +3 -0
- package/dist/plugin/spill-store.js +8 -0
- package/dist/plugin/spill-store.js.map +1 -0
- package/dist/plugin/subprocess.d.ts +8 -0
- package/dist/plugin/subprocess.js +9 -0
- package/dist/plugin/subprocess.js.map +1 -0
- package/dist/{service-BK6NR_RX.d.ts → service-vCjLPYMI.d.ts} +29 -17
- package/dist/{shell-BTf2wsDy.d.ts → shell-BAyBocjm.d.ts} +25 -35
- package/dist/spill-store-BfljgRi0.d.ts +38 -0
- package/dist/subprocess-CInesMpR.d.ts +23 -0
- package/package.json +24 -12
- package/dist/chunk-2F2PJ7D7.js.map +0 -1
- package/dist/chunk-4CZV24HW.js.map +0 -1
- package/dist/chunk-M4JOF35U.js.map +0 -1
- package/dist/chunk-QTBMLMKT.js.map +0 -1
|
@@ -4,19 +4,23 @@ import {
|
|
|
4
4
|
detectsCrlf,
|
|
5
5
|
normalizeLineEndings,
|
|
6
6
|
restoreLineEndings
|
|
7
|
-
} from "./chunk-
|
|
7
|
+
} from "./chunk-OZ2F4R6T.js";
|
|
8
8
|
|
|
9
9
|
// src/fs.ts
|
|
10
10
|
import { Buffer } from "buffer";
|
|
11
|
-
import { posix } from "path";
|
|
11
|
+
import { isAbsolute, posix, relative, resolve, sep } from "path";
|
|
12
12
|
import { FileSystem, FsError as FsError2, FsTargetKey, FsVersion } from "@deepseek-ai/dsh-fs";
|
|
13
|
+
import { DiskVFS } from "@struktoai/mirage-node";
|
|
14
|
+
import { Session } from "@struktoai/mirage-core/workspace/workspace/workspace";
|
|
15
|
+
import { readerOf } from "@struktoai/mirage-core/workspace/files";
|
|
13
16
|
import { FileType } from "@struktoai/mirage-core/types";
|
|
14
|
-
import { isMissingPath as isMissingPath2 } from "@struktoai/mirage-core/
|
|
17
|
+
import { isMissingPath as isMissingPath2 } from "@struktoai/mirage-core/errors/fs";
|
|
15
18
|
import { compareCodePoints } from "@struktoai/mirage-core/utils/sort";
|
|
16
19
|
|
|
17
20
|
// src/errors.ts
|
|
18
21
|
import { FsError } from "@deepseek-ai/dsh-fs";
|
|
19
|
-
import { isMissingPath } from "@struktoai/mirage-core/
|
|
22
|
+
import { isMissingPath } from "@struktoai/mirage-core/errors/fs";
|
|
23
|
+
import { withRefusal } from "@struktoai/mirage-core/workspace/tools/io_text";
|
|
20
24
|
function assertNotAborted(signal, operation) {
|
|
21
25
|
if (signal?.aborted === true) {
|
|
22
26
|
throw new FsError(`${operation} aborted`, "FS_ABORTED");
|
|
@@ -49,9 +53,9 @@ function mapMirageError(err, operation, displayPath) {
|
|
|
49
53
|
return new FsError(`${operation} aborted`, "FS_ABORTED", { cause: err });
|
|
50
54
|
}
|
|
51
55
|
const message = err instanceof Error ? err.message : String(err);
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
});
|
|
56
|
+
const refusal = err?.refusal ?? null;
|
|
57
|
+
const text = withRefusal(`cannot ${operation} "${displayPath}": ${message}`, refusal);
|
|
58
|
+
return new FsError(text.trimEnd(), codeFor(err), { cause: err });
|
|
55
59
|
}
|
|
56
60
|
|
|
57
61
|
// src/fs.ts
|
|
@@ -84,10 +88,31 @@ function tooLarge(displayPath, maxBytes, size) {
|
|
|
84
88
|
"FS_TOO_LARGE"
|
|
85
89
|
);
|
|
86
90
|
}
|
|
91
|
+
function escapesRoot(rel) {
|
|
92
|
+
return rel === ".." || rel.startsWith(`..${sep}`) || isAbsolute(rel);
|
|
93
|
+
}
|
|
94
|
+
function ownerPrefixOf(virtual, mounts) {
|
|
95
|
+
const probe = virtual.endsWith("/") ? virtual : `${virtual}/`;
|
|
96
|
+
let winner;
|
|
97
|
+
for (const mount of mounts) {
|
|
98
|
+
if (!probe.startsWith(mount.prefix)) continue;
|
|
99
|
+
if (winner === void 0 || mount.prefix.length > winner.length) winner = mount.prefix;
|
|
100
|
+
}
|
|
101
|
+
return winner;
|
|
102
|
+
}
|
|
103
|
+
function shadowedByLink(links, virtual) {
|
|
104
|
+
if (links === null) return false;
|
|
105
|
+
try {
|
|
106
|
+
return links.follow(virtual) !== virtual;
|
|
107
|
+
} catch {
|
|
108
|
+
return true;
|
|
109
|
+
}
|
|
110
|
+
}
|
|
87
111
|
var MirageFileSystem = class extends FileSystem {
|
|
88
112
|
static inject = ["mirage"];
|
|
89
113
|
fsOps = null;
|
|
90
114
|
cwd;
|
|
115
|
+
sessionId;
|
|
91
116
|
diffBasisMaxBytes;
|
|
92
117
|
// Per-targetKey tail promise: serializes mutating ops so the
|
|
93
118
|
// read -> guard -> write window cannot interleave (one concurrent writer
|
|
@@ -96,23 +121,41 @@ var MirageFileSystem = class extends FileSystem {
|
|
|
96
121
|
constructor(ctx, config = {}) {
|
|
97
122
|
super(ctx);
|
|
98
123
|
this.cwd = config.cwd ?? "/";
|
|
124
|
+
this.sessionId = config.sessionId;
|
|
99
125
|
this.diffBasisMaxBytes = config.diffBasisMaxBytes ?? DEFAULT_DIFF_BASIS_MAX_BYTES;
|
|
100
126
|
}
|
|
101
127
|
// The workspace may still be building (declarative mounts resolve
|
|
102
128
|
// asynchronously), so every entry point awaits the service's `ready`
|
|
103
|
-
// once and caches the
|
|
129
|
+
// once and caches the dispatcher. The caller's signal can fire during
|
|
104
130
|
// that wait, after its entry assertion passed, so it is asserted
|
|
105
131
|
// again here, before the op it guards dispatches.
|
|
106
|
-
|
|
107
|
-
|
|
132
|
+
//
|
|
133
|
+
// `ready` does not hydrate: a workspace freshly attached to a shared
|
|
134
|
+
// store still holds a minted default session and an empty link table
|
|
135
|
+
// until its first op loads both. This adapter reads the links off the
|
|
136
|
+
// dispatcher's reader outside a dispatch, so it hydrates before they are
|
|
137
|
+
// consulted, or a persisted hide would be judged by the wrong session.
|
|
138
|
+
async files(signal, operation = "ready") {
|
|
139
|
+
if (this.fsOps === null) {
|
|
140
|
+
const host = await this.ctx.mirage.ready;
|
|
141
|
+
await host.ensureSessionsLoaded();
|
|
142
|
+
await host.namespace.ensureLoaded();
|
|
143
|
+
this.fsOps = this.sessionId === void 0 ? host.vfs : new Session(host, this.sessionId).vfs;
|
|
144
|
+
}
|
|
108
145
|
assertNotAborted(signal, operation);
|
|
109
146
|
return this.fsOps;
|
|
110
147
|
}
|
|
111
|
-
|
|
148
|
+
/**
|
|
149
|
+
* The namespace as the session this adapter's ops run as sees it: the
|
|
150
|
+
* dispatcher's reader, so a link the session cannot see is never followed
|
|
151
|
+
* (the typed path reaches the dispatcher and is refused as absent, not
|
|
152
|
+
* resolved to the visible target it points at), and never listed.
|
|
153
|
+
*/
|
|
154
|
+
get reader() {
|
|
112
155
|
if (this.fsOps === null) {
|
|
113
156
|
throw new Error("mirage: filesystem used before the workspace is ready");
|
|
114
157
|
}
|
|
115
|
-
return this.fsOps
|
|
158
|
+
return readerOf(this.fsOps);
|
|
116
159
|
}
|
|
117
160
|
/**
|
|
118
161
|
* The same confinement claim `MirageShellExecutor` makes, off the same
|
|
@@ -165,17 +208,14 @@ var MirageFileSystem = class extends FileSystem {
|
|
|
165
208
|
*/
|
|
166
209
|
async resolveBase(path, cwd) {
|
|
167
210
|
if (cwd === void 0 || posix.isAbsolute(path)) return this.cwd;
|
|
168
|
-
|
|
169
|
-
return await ws.fs.isDir(cwd) ? cwd : this.cwd;
|
|
211
|
+
return await (await this.files()).isDir(cwd) ? cwd : this.cwd;
|
|
170
212
|
}
|
|
171
213
|
normalize(path, base) {
|
|
172
214
|
return posix.resolve(base, path);
|
|
173
215
|
}
|
|
174
216
|
follow(path) {
|
|
175
|
-
const links = this.links;
|
|
176
|
-
if (links === null) return path;
|
|
177
217
|
try {
|
|
178
|
-
return
|
|
218
|
+
return this.reader.resolve(path);
|
|
179
219
|
} catch (err) {
|
|
180
220
|
throw mapMirageError(err, "resolve", path);
|
|
181
221
|
}
|
|
@@ -196,13 +236,47 @@ var MirageFileSystem = class extends FileSystem {
|
|
|
196
236
|
}
|
|
197
237
|
async resolve(path, opts) {
|
|
198
238
|
assertNotAborted(opts?.signal, "resolve");
|
|
199
|
-
await this.
|
|
239
|
+
await this.files(opts?.signal, "resolve");
|
|
200
240
|
const followed = this.follow(this.normalize(path, await this.resolveBase(path, opts?.cwd)));
|
|
201
241
|
return { targetKey: FsTargetKey(followed), displayPath: followed };
|
|
202
242
|
}
|
|
203
243
|
processPath(target) {
|
|
204
244
|
return String(target.targetKey);
|
|
205
245
|
}
|
|
246
|
+
/**
|
|
247
|
+
* The workspace path naming the same file as `hostPath`, when a mount
|
|
248
|
+
* puts one there.
|
|
249
|
+
*
|
|
250
|
+
* Most of this world has no host spelling at all: an S3 key, a Slack
|
|
251
|
+
* message and a Notion row are not files the harness could open, and
|
|
252
|
+
* claiming otherwise would hand the caller a path that reads as the
|
|
253
|
+
* wrong bytes. A disk mount is the one place the two worlds hold the
|
|
254
|
+
* same file, so it is the one place a host path is answerable; every
|
|
255
|
+
* other mount declines, and so does a workspace still building.
|
|
256
|
+
*
|
|
257
|
+
* @param hostPath absolute path in the harness host filesystem.
|
|
258
|
+
* @returns the virtual path for the same file, or undefined when no
|
|
259
|
+
* mount maps it.
|
|
260
|
+
*/
|
|
261
|
+
processPathFromHostPath(hostPath) {
|
|
262
|
+
if (!isAbsolute(hostPath)) return void 0;
|
|
263
|
+
const workspace = this.ctx.mirage.workspaceIfReady;
|
|
264
|
+
if (workspace === null) return void 0;
|
|
265
|
+
const host = resolve(hostPath);
|
|
266
|
+
const mounts = workspace.mounts();
|
|
267
|
+
for (const entry of mounts) {
|
|
268
|
+
const { vfs } = entry;
|
|
269
|
+
if (!(vfs instanceof DiskVFS)) continue;
|
|
270
|
+
const rel = relative(vfs.root, host);
|
|
271
|
+
if (escapesRoot(rel)) continue;
|
|
272
|
+
const joined = `${entry.prefix}${rel.split(sep).join("/")}`;
|
|
273
|
+
const virtual = joined.length > 1 && joined.endsWith("/") ? joined.slice(0, -1) : joined;
|
|
274
|
+
if (ownerPrefixOf(virtual, mounts) !== entry.prefix) continue;
|
|
275
|
+
if (shadowedByLink(workspace.namespace, virtual)) continue;
|
|
276
|
+
return virtual;
|
|
277
|
+
}
|
|
278
|
+
return void 0;
|
|
279
|
+
}
|
|
206
280
|
fileUrl(target) {
|
|
207
281
|
const encoded = String(target.targetKey).split("/").map(encodeURIComponent).join("/");
|
|
208
282
|
return `file://${encoded}`;
|
|
@@ -219,7 +293,7 @@ var MirageFileSystem = class extends FileSystem {
|
|
|
219
293
|
const key = String(target.targetKey);
|
|
220
294
|
let stat;
|
|
221
295
|
try {
|
|
222
|
-
stat = await (await this.
|
|
296
|
+
stat = await (await this.files(signal, "stat")).stat(key);
|
|
223
297
|
} catch (err) {
|
|
224
298
|
if (isMissingPath2(err)) return void 0;
|
|
225
299
|
throw mapMirageError(err, "stat", target.displayPath);
|
|
@@ -232,12 +306,11 @@ var MirageFileSystem = class extends FileSystem {
|
|
|
232
306
|
}
|
|
233
307
|
async lstat(path, opts, signal) {
|
|
234
308
|
assertNotAborted(signal, "lstat");
|
|
235
|
-
await this.
|
|
309
|
+
await this.files(signal, "lstat");
|
|
236
310
|
const normalized = this.normalize(path, await this.resolveBase(path, opts?.cwd));
|
|
237
311
|
const parentFollowed = normalized === "/" ? "/" : posix.join(this.follow(posix.dirname(normalized)), posix.basename(normalized));
|
|
238
|
-
const
|
|
239
|
-
if (
|
|
240
|
-
const linkTarget = links.readlink(parentFollowed) ?? "";
|
|
312
|
+
const linkTarget = this.reader.readlink(parentFollowed);
|
|
313
|
+
if (linkTarget !== null) {
|
|
241
314
|
return {
|
|
242
315
|
version: FsVersion(`link:${linkTarget}`),
|
|
243
316
|
type: "symlink",
|
|
@@ -246,7 +319,7 @@ var MirageFileSystem = class extends FileSystem {
|
|
|
246
319
|
}
|
|
247
320
|
let stat;
|
|
248
321
|
try {
|
|
249
|
-
stat = await (await this.
|
|
322
|
+
stat = await (await this.files(signal, "lstat")).stat(parentFollowed);
|
|
250
323
|
} catch (err) {
|
|
251
324
|
if (isMissingPath2(err)) return void 0;
|
|
252
325
|
throw mapMirageError(err, "lstat", normalized);
|
|
@@ -262,7 +335,7 @@ var MirageFileSystem = class extends FileSystem {
|
|
|
262
335
|
const key = String(target.targetKey);
|
|
263
336
|
let bytes;
|
|
264
337
|
try {
|
|
265
|
-
bytes = await (await this.
|
|
338
|
+
bytes = await (await this.files(signal, "read")).read(key);
|
|
266
339
|
} catch (err) {
|
|
267
340
|
throw mapMirageError(err, "read", target.displayPath);
|
|
268
341
|
}
|
|
@@ -272,8 +345,17 @@ var MirageFileSystem = class extends FileSystem {
|
|
|
272
345
|
const text = await this.readText(target, signal);
|
|
273
346
|
return singleChunk(text);
|
|
274
347
|
}
|
|
275
|
-
|
|
276
|
-
|
|
348
|
+
/**
|
|
349
|
+
* Stat a read target and refuse anything that is not a regular file.
|
|
350
|
+
*
|
|
351
|
+
* A backend read of a directory key surfaces as a missing path, which
|
|
352
|
+
* would report a path that plainly exists as absent.
|
|
353
|
+
*
|
|
354
|
+
* @param target the resolved target about to be read.
|
|
355
|
+
* @param signal aborts the stat.
|
|
356
|
+
* @returns the stat row, or undefined when the mount reports none.
|
|
357
|
+
*/
|
|
358
|
+
async statRegularFile(target, signal) {
|
|
277
359
|
const info = await this.stat(target, signal);
|
|
278
360
|
if (info !== void 0 && info.type !== "file") {
|
|
279
361
|
throw new FsError2(
|
|
@@ -281,13 +363,18 @@ var MirageFileSystem = class extends FileSystem {
|
|
|
281
363
|
"FS_NOT_REGULAR_FILE"
|
|
282
364
|
);
|
|
283
365
|
}
|
|
366
|
+
return info;
|
|
367
|
+
}
|
|
368
|
+
async readBytes(target, signal, maxBytes) {
|
|
369
|
+
assertNotAborted(signal, "read");
|
|
370
|
+
const info = await this.statRegularFile(target, signal);
|
|
284
371
|
if (info?.size !== void 0 && info.size > maxBytes) {
|
|
285
372
|
throw tooLarge(target.displayPath, maxBytes, info.size);
|
|
286
373
|
}
|
|
287
374
|
const key = String(target.targetKey);
|
|
288
375
|
let bytes;
|
|
289
376
|
try {
|
|
290
|
-
bytes = await (await this.
|
|
377
|
+
bytes = await (await this.files(signal, "read")).read(key, { size: maxBytes + 1 });
|
|
291
378
|
} catch (err) {
|
|
292
379
|
throw mapMirageError(err, "read", target.displayPath);
|
|
293
380
|
}
|
|
@@ -296,6 +383,16 @@ var MirageFileSystem = class extends FileSystem {
|
|
|
296
383
|
}
|
|
297
384
|
return bytes;
|
|
298
385
|
}
|
|
386
|
+
async readByteRange(target, range, signal) {
|
|
387
|
+
assertNotAborted(signal, "read");
|
|
388
|
+
await this.statRegularFile(target, signal);
|
|
389
|
+
if (range.length === 0) return new Uint8Array(0);
|
|
390
|
+
try {
|
|
391
|
+
return await (await this.files(signal, "read")).read(String(target.targetKey), { offset: range.offset, size: range.length });
|
|
392
|
+
} catch (err) {
|
|
393
|
+
throw mapMirageError(err, "read", target.displayPath);
|
|
394
|
+
}
|
|
395
|
+
}
|
|
299
396
|
async listDir(target, signal) {
|
|
300
397
|
assertNotAborted(signal, "list");
|
|
301
398
|
const key = String(target.targetKey);
|
|
@@ -311,7 +408,7 @@ var MirageFileSystem = class extends FileSystem {
|
|
|
311
408
|
}
|
|
312
409
|
let children;
|
|
313
410
|
try {
|
|
314
|
-
children = await (await this.
|
|
411
|
+
children = await (await this.files(signal, "list")).readdir(key);
|
|
315
412
|
} catch (err) {
|
|
316
413
|
throw mapMirageError(err, "list", target.displayPath);
|
|
317
414
|
}
|
|
@@ -319,15 +416,7 @@ var MirageFileSystem = class extends FileSystem {
|
|
|
319
416
|
for (const childPath of children) {
|
|
320
417
|
names.add(posix.basename(childPath));
|
|
321
418
|
}
|
|
322
|
-
const
|
|
323
|
-
if (links !== null) {
|
|
324
|
-
const base = key === "/" ? "/" : `${key}/`;
|
|
325
|
-
for (const linkPath of links.symlinkTargets().keys()) {
|
|
326
|
-
if (linkPath.startsWith(base) && !linkPath.slice(base.length).includes("/")) {
|
|
327
|
-
names.add(linkPath.slice(base.length));
|
|
328
|
-
}
|
|
329
|
-
}
|
|
330
|
-
}
|
|
419
|
+
for (const name of this.reader.linkNames(key)) names.add(name);
|
|
331
420
|
const entries = [];
|
|
332
421
|
for (const name of [...names].sort(compareCodePoints)) {
|
|
333
422
|
assertNotAborted(signal, "list");
|
|
@@ -350,7 +439,7 @@ var MirageFileSystem = class extends FileSystem {
|
|
|
350
439
|
const target = { targetKey: FsTargetKey(followed), displayPath: childPath };
|
|
351
440
|
let stat;
|
|
352
441
|
try {
|
|
353
|
-
stat = await (await this.
|
|
442
|
+
stat = await (await this.files(signal, "list")).stat(followed);
|
|
354
443
|
} catch (err) {
|
|
355
444
|
if (err instanceof FsError2 && err.code === "FS_ABORTED") throw err;
|
|
356
445
|
return { name, type: "other", target };
|
|
@@ -392,7 +481,7 @@ var MirageFileSystem = class extends FileSystem {
|
|
|
392
481
|
const before = existing === void 0 ? null : await this.diffBasis(target, content);
|
|
393
482
|
const crlf = before !== null && detectsCrlf(before);
|
|
394
483
|
try {
|
|
395
|
-
await (await this.
|
|
484
|
+
await (await this.files(signal, "write")).write(key, restoreLineEndings(normalizeLineEndings(content), crlf));
|
|
396
485
|
} catch (err) {
|
|
397
486
|
throw mapMirageError(err, "write", target.displayPath);
|
|
398
487
|
}
|
|
@@ -411,7 +500,7 @@ var MirageFileSystem = class extends FileSystem {
|
|
|
411
500
|
if (Buffer.byteLength(content, "utf8") >= this.diffBasisMaxBytes) return null;
|
|
412
501
|
let bytes;
|
|
413
502
|
try {
|
|
414
|
-
bytes = await (await this.
|
|
503
|
+
bytes = await (await this.files()).read(String(target.targetKey));
|
|
415
504
|
} catch {
|
|
416
505
|
return null;
|
|
417
506
|
}
|
|
@@ -452,7 +541,7 @@ var MirageFileSystem = class extends FileSystem {
|
|
|
452
541
|
}
|
|
453
542
|
let bytes;
|
|
454
543
|
try {
|
|
455
|
-
bytes = await (await this.
|
|
544
|
+
bytes = await (await this.files(signal, "edit")).read(key);
|
|
456
545
|
} catch (err) {
|
|
457
546
|
throw mapMirageError(err, "edit", target.displayPath);
|
|
458
547
|
}
|
|
@@ -466,7 +555,7 @@ var MirageFileSystem = class extends FileSystem {
|
|
|
466
555
|
target.displayPath
|
|
467
556
|
);
|
|
468
557
|
try {
|
|
469
|
-
await (await this.
|
|
558
|
+
await (await this.files(signal, "edit")).write(key, restoreLineEndings(edited, detectsCrlf(raw)));
|
|
470
559
|
} catch (err) {
|
|
471
560
|
throw mapMirageError(err, "edit", target.displayPath);
|
|
472
561
|
}
|
|
@@ -482,4 +571,4 @@ var MirageFileSystem = class extends FileSystem {
|
|
|
482
571
|
export {
|
|
483
572
|
MirageFileSystem
|
|
484
573
|
};
|
|
485
|
-
//# sourceMappingURL=chunk-
|
|
574
|
+
//# sourceMappingURL=chunk-FRF6WPLN.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/fs.ts","../src/errors.ts"],"sourcesContent":["// ========= Copyright 2026 @ Strukto.AI All Rights Reserved. =========\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n// http://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n// ========= Copyright 2026 @ Strukto.AI All Rights Reserved. =========\n\nimport { Buffer } from 'node:buffer'\nimport { isAbsolute, posix, relative, resolve, sep } from 'node:path'\nimport type { Context } from '@deepseek-ai/cordis'\nimport { FileSystem, FsError, FsTargetKey, FsVersion } from '@deepseek-ai/dsh-fs'\nimport type {\n FsDirEntry,\n FsEditOutcome,\n FsEditRequest,\n FsInfo,\n FsPathInfo,\n FsTarget,\n FsWriteIntent,\n FsWriteOutcome,\n} from '@deepseek-ai/dsh-fs'\nimport { DiskVFS } from '@struktoai/mirage-node'\nimport type { MountEntry } from '@struktoai/mirage-core/workspace/mount/mount'\nimport { Session } from '@struktoai/mirage-core/workspace/workspace/workspace'\nimport { type Files, readerOf } from '@struktoai/mirage-core/workspace/files'\nimport type { NamespaceLinks } from '@struktoai/mirage-core/view/types'\nimport { FileType } from '@struktoai/mirage-core/types'\nimport type { FileStat } from '@struktoai/mirage-core/types'\nimport { isMissingPath } from '@struktoai/mirage-core/errors/fs'\nimport { compareCodePoints } from '@struktoai/mirage-core/utils/sort'\nimport { assertNotAborted, mapMirageError } from './errors.ts'\nimport {\n applyLiteralEdit,\n decodeStrictText,\n detectsCrlf,\n normalizeLineEndings,\n restoreLineEndings,\n} from './text.ts'\n\n// Read off the seam's own signature rather than imported: the policy type\n// lives in `@deepseek-ai/dsh-sandbox`, which reaches this package only as a\n// transitive dependency of dsh-fs, and naming a fourth exact-pinned peer to\n// spell one parameter would couple the adapter to a package it never calls.\ntype SandboxPolicy = Parameters<FileSystem['writeText']>[4]\n\nconst DEFAULT_DIFF_BASIS_MAX_BYTES = 10 * 1024 * 1024\n\n/** Configuration for the mirage filesystem backend. */\nexport interface MirageFsConfig {\n /** Virtual base directory for relative paths. Defaults to `/`. */\n cwd?: string\n /**\n * Read and write as this named workspace session, so the profile\n * that confines the session's shell confines its file tools too. The\n * workspace's default session otherwise.\n */\n sessionId?: string\n /** Exclusive byte limit on each overwrite-diff side. Defaults to 10 MiB. */\n diffBasisMaxBytes?: number\n}\n\n// The freshness token behind FsVersion. Backends that report a fingerprint\n// or revision get a content-derived token; the rest fall back to metadata\n// (modified + size), and a backend reporting neither yields a constant, so\n// stale guards degrade to always-fresh there instead of always-stale.\nfunction versionOf(stat: FileStat): FsVersion {\n if (stat.fingerprint !== null) return FsVersion(`fp:${stat.fingerprint}`)\n if (stat.revision !== null) return FsVersion(`rev:${stat.revision}`)\n if (stat.modified !== null || stat.size !== null) {\n return FsVersion(`meta:${stat.modified ?? ''}:${String(stat.size ?? '')}`)\n }\n return FsVersion('unversioned')\n}\n\nfunction typeOf(stat: FileStat): FsInfo['type'] {\n switch (stat.type) {\n case FileType.DIRECTORY:\n return 'directory'\n case FileType.SYMLINK:\n return 'other'\n default:\n return 'file'\n }\n}\n\nasync function* singleChunk(text: string): AsyncGenerator<string, void, void> {\n yield await Promise.resolve(text)\n}\n\nfunction tooLarge(displayPath: string, maxBytes: number, size?: number): FsError {\n const found = size === undefined ? '' : `${String(size)} bytes `\n return new FsError(\n `cannot read \"${displayPath}\": ${found}exceeds the ${String(maxBytes)} byte cap`,\n 'FS_TOO_LARGE',\n )\n}\n\n/**\n * Whether a `relative()` result leaves the directory it was taken from.\n *\n * Only a bare `..` or a leading `..` SEGMENT escapes: a name may itself\n * begin with two dots (`..draft`), and testing the prefix alone reads\n * that ordinary file as an escape.\n *\n * @param rel the result of `relative(root, target)`.\n * @returns true when the target lies outside the root.\n */\nfunction escapesRoot(rel: string): boolean {\n // An absolute answer means there was no relative route at all, which on\n // win32 is a different drive.\n return rel === '..' || rel.startsWith(`..${sep}`) || isAbsolute(rel)\n}\n\n/**\n * The prefix of the mount a virtual path actually dispatches to.\n *\n * The deepest mount whose prefix covers the path wins, which is the rule\n * dispatch itself follows; a mount nested inside another's tree owns its\n * subtree outright.\n *\n * @param virtual an absolute virtual path.\n * @param mounts the workspace's mount table.\n * @returns the winning mount's prefix, or undefined when none covers it.\n */\nfunction ownerPrefixOf(virtual: string, mounts: readonly MountEntry[]): string | undefined {\n const probe = virtual.endsWith('/') ? virtual : `${virtual}/`\n let winner: string | undefined\n for (const mount of mounts) {\n if (!probe.startsWith(mount.prefix)) continue\n if (winner === undefined || mount.prefix.length > winner.length) winner = mount.prefix\n }\n return winner\n}\n\n/**\n * Whether a namespace symlink stands between a virtual path and the backend.\n *\n * `follow` resolves link components including the final one, so a path it\n * rewrites is one an ordinary read would send somewhere else. A chain it\n * refuses to resolve (a cycle) is not mappable either, and is declined\n * rather than raised: this is a lookup, and the honest answer to \"does\n * this host file have a virtual path\" is then no.\n *\n * @param links the namespace link table, or null when none is wired.\n * @param virtual the candidate virtual path.\n * @returns true when the path must not be offered.\n */\nfunction shadowedByLink(links: NamespaceLinks | null, virtual: string): boolean {\n if (links === null) return false\n try {\n return links.follow(virtual) !== virtual\n } catch {\n return true\n }\n}\n\n/**\n * Mirage-backed implementation of `ctx.fs`. Targets are canonical virtual\n * paths (namespace symlinks followed), every operation walks the workspace\n * dispatcher — session grants, admission policies, cache read-through and\n * post-write invalidation all fire exactly as they do for a shell command —\n * and `processPath` answers in the same virtual path space the mirage shell\n * executes in, so the two providers share one execution world.\n *\n * One limit worth stating: mirage's `ws.vfs` takes no `AbortSignal`, so\n * cancellation is honored at this adapter's own boundaries (before a\n * dispatch, between listing entries) and not inside a single op. A long\n * read from a remote backend therefore runs to completion after the\n * signal fires, and the caller learns of the abort when it returns.\n */\nexport class MirageFileSystem extends FileSystem {\n static readonly inject = ['mirage']\n\n private fsOps: Files | null = null\n private readonly cwd: string\n private readonly sessionId: string | undefined\n private readonly diffBasisMaxBytes: number\n // Per-targetKey tail promise: serializes mutating ops so the\n // read -> guard -> write window cannot interleave (one concurrent writer\n // wins, the rest observe the new version and reject as stale).\n private readonly locks = new Map<string, Promise<void>>()\n\n constructor(ctx: Context, config: MirageFsConfig = {}) {\n super(ctx)\n this.cwd = config.cwd ?? '/'\n this.sessionId = config.sessionId\n this.diffBasisMaxBytes = config.diffBasisMaxBytes ?? DEFAULT_DIFF_BASIS_MAX_BYTES\n }\n\n // The workspace may still be building (declarative mounts resolve\n // asynchronously), so every entry point awaits the service's `ready`\n // once and caches the dispatcher. The caller's signal can fire during\n // that wait, after its entry assertion passed, so it is asserted\n // again here, before the op it guards dispatches.\n //\n // `ready` does not hydrate: a workspace freshly attached to a shared\n // store still holds a minted default session and an empty link table\n // until its first op loads both. This adapter reads the links off the\n // dispatcher's reader outside a dispatch, so it hydrates before they are\n // consulted, or a persisted hide would be judged by the wrong session.\n private async files(signal?: AbortSignal, operation = 'ready'): Promise<Files> {\n if (this.fsOps === null) {\n const host = await this.ctx.mirage.ready\n await host.ensureSessionsLoaded()\n await host.namespace.ensureLoaded()\n this.fsOps = this.sessionId === undefined ? host.vfs : new Session(host, this.sessionId).vfs\n }\n assertNotAborted(signal, operation)\n return this.fsOps\n }\n\n /**\n * The namespace as the session this adapter's ops run as sees it: the\n * dispatcher's reader, so a link the session cannot see is never followed\n * (the typed path reaches the dispatcher and is refused as absent, not\n * resolved to the visible target it points at), and never listed.\n */\n private get reader(): ReturnType<typeof readerOf> {\n if (this.fsOps === null) {\n throw new Error('mirage: filesystem used before the workspace is ready')\n }\n return readerOf(this.fsOps)\n }\n\n /**\n * The same confinement claim `MirageShellExecutor` makes, off the same\n * fact and for the same reason: with every runtime reaching only the\n * vfs, a mutation cannot land anywhere but a mount, under its mode.\n *\n * The two seams sit over one world, so answering differently here\n * would let dsh fence a bash write and wave an identical `ctx.fs`\n * write straight through.\n */\n override get sandboxMode(): FileSystem['sandboxMode'] {\n return this.ctx.mirage.vfsOnly ? 'workspace-write' : undefined\n }\n\n /**\n * Refuse a mutation the call's sandbox policy does not allow.\n *\n * `workspaceRoot` is deliberately not consulted: it is a directory on\n * the harness's own machine, so containment against it says nothing\n * about this world. The mounts and their modes are the boundary, and\n * `read-only` is the one mode that narrows them further. Wording and\n * code mirror `dsh-fs-sandbox`, so the tool layer renders one denial\n * marker whichever backend refused.\n *\n * @param policy the per-call policy, absent for an unguarded mutation.\n * @param displayPath the path to name in the refusal.\n */\n private assertMutable(policy: SandboxPolicy, displayPath: string): void {\n if (policy?.mode !== 'read-only') return\n throw new FsError(\n `cannot write \"${displayPath}\": file access denied under read-only mode`,\n 'FS_SANDBOX_DENIED',\n )\n }\n\n /**\n * The base a relative path resolves against.\n *\n * dsh hands `ctx.fs` either the calling session's cwd or the sandbox\n * policy's workspace root, and both are directories on the harness's\n * own machine that name nothing here. Resolving `notes.txt` against\n * one yields `/Users/.../notes.txt`, which every read then reports as\n * absent. So a base that is not a directory in this world falls back\n * to the configured one, the same rule the shell executor applies to\n * a workdir.\n *\n * An absolute path ignores its base, so it never pays for the probe.\n *\n * @param path the path being resolved.\n * @param cwd the caller's base, if any.\n * @returns the base to resolve against.\n */\n private async resolveBase(path: string, cwd: string | undefined): Promise<string> {\n if (cwd === undefined || posix.isAbsolute(path)) return this.cwd\n // Probed as the session this adapter reads as: a directory only the\n // named session can see is a base here, and one it cannot see is not.\n return (await (await this.files()).isDir(cwd)) ? cwd : this.cwd\n }\n\n private normalize(path: string, base: string): string {\n return posix.resolve(base, path)\n }\n\n private follow(path: string): string {\n try {\n return this.reader.resolve(path)\n } catch (err) {\n throw mapMirageError(err, 'resolve', path)\n }\n }\n\n private withLock<T>(key: FsTargetKey, body: () => Promise<T>): Promise<T> {\n const mapKey = String(key)\n const previous = this.locks.get(mapKey) ?? Promise.resolve()\n const next = previous.then(body, body)\n // The stored tail is void (an outcome would pin whole file contents in\n // the map) and drops on settlement unless a newer op queued behind it.\n const tail = next.then(\n () => undefined,\n () => undefined,\n )\n this.locks.set(mapKey, tail)\n void tail.then(() => {\n if (this.locks.get(mapKey) === tail) this.locks.delete(mapKey)\n })\n return next\n }\n\n async resolve(path: string, opts?: { cwd?: string; signal?: AbortSignal }): Promise<FsTarget> {\n assertNotAborted(opts?.signal, 'resolve')\n await this.files(opts?.signal, 'resolve')\n const followed = this.follow(this.normalize(path, await this.resolveBase(path, opts?.cwd)))\n return { targetKey: FsTargetKey(followed), displayPath: followed }\n }\n\n processPath(target: FsTarget): string {\n return String(target.targetKey)\n }\n\n /**\n * The workspace path naming the same file as `hostPath`, when a mount\n * puts one there.\n *\n * Most of this world has no host spelling at all: an S3 key, a Slack\n * message and a Notion row are not files the harness could open, and\n * claiming otherwise would hand the caller a path that reads as the\n * wrong bytes. A disk mount is the one place the two worlds hold the\n * same file, so it is the one place a host path is answerable; every\n * other mount declines, and so does a workspace still building.\n *\n * @param hostPath absolute path in the harness host filesystem.\n * @returns the virtual path for the same file, or undefined when no\n * mount maps it.\n */\n override processPathFromHostPath(hostPath: string): string | undefined {\n if (!isAbsolute(hostPath)) return undefined\n const workspace = this.ctx.mirage.workspaceIfReady\n if (workspace === null) return undefined\n const host = resolve(hostPath)\n const mounts = workspace.mounts()\n for (const entry of mounts) {\n const { vfs } = entry\n if (!(vfs instanceof DiskVFS)) continue\n const rel = relative(vfs.root, host)\n if (escapesRoot(rel)) continue\n // `prefix` always carries a trailing slash, and `rel` is empty for\n // the root itself, so the join is a concatenation and the slash is\n // trimmed back off unless the mount is the workspace root.\n const joined = `${entry.prefix}${rel.split(sep).join('/')}`\n const virtual = joined.length > 1 && joined.endsWith('/') ? joined.slice(0, -1) : joined\n // A mount nested under this one owns its own subtree, and dispatch\n // routes the path there, so the disk file at this host location is\n // not what the virtual path reads. Keep looking rather than name a\n // path that answers with another VFS's bytes.\n if (ownerPrefixOf(virtual, mounts) !== entry.prefix) continue\n // A namespace symlink at or above this path is followed before\n // dispatch, so a read would land on the link's target rather than\n // the disk file the caller named. Links are namespace state, so the\n // disk mount cannot see one and only this table can say.\n if (shadowedByLink(workspace.namespace, virtual)) continue\n return virtual\n }\n return undefined\n }\n\n fileUrl(target: FsTarget): string {\n const encoded = String(target.targetKey).split('/').map(encodeURIComponent).join('/')\n return `file://${encoded}`\n }\n\n contains(parent: FsTarget, child: FsTarget): boolean {\n const parentKey = String(parent.targetKey)\n const childKey = String(child.targetKey)\n if (parentKey === childKey) return true\n const prefix = parentKey === '/' ? '/' : `${parentKey}/`\n return childKey.startsWith(prefix)\n }\n\n async stat(target: FsTarget, signal?: AbortSignal): Promise<FsInfo | undefined> {\n assertNotAborted(signal, 'stat')\n const key = String(target.targetKey)\n let stat: FileStat\n try {\n stat = await (await this.files(signal, 'stat')).stat(key)\n } catch (err) {\n if (isMissingPath(err)) return undefined\n throw mapMirageError(err, 'stat', target.displayPath)\n }\n return {\n version: versionOf(stat),\n type: typeOf(stat),\n ...(stat.size !== null ? { size: stat.size } : {}),\n }\n }\n\n async lstat(\n path: string,\n opts?: { cwd?: string },\n signal?: AbortSignal,\n ): Promise<FsPathInfo | undefined> {\n assertNotAborted(signal, 'lstat')\n await this.files(signal, 'lstat')\n const normalized = this.normalize(path, await this.resolveBase(path, opts?.cwd))\n // Follow every component except the last: the probe is about the path\n // entry itself, so a link at the leaf must report as one.\n const parentFollowed =\n normalized === '/'\n ? '/'\n : posix.join(this.follow(posix.dirname(normalized)), posix.basename(normalized))\n // The leaf is read off the dispatcher's reader: a link the session cannot\n // see is not a link here, and the stat below reports it absent.\n const linkTarget = this.reader.readlink(parentFollowed)\n if (linkTarget !== null) {\n return {\n version: FsVersion(`link:${linkTarget}`),\n type: 'symlink',\n size: new TextEncoder().encode(linkTarget).byteLength,\n }\n }\n let stat: FileStat\n try {\n stat = await (await this.files(signal, 'lstat')).stat(parentFollowed)\n } catch (err) {\n if (isMissingPath(err)) return undefined\n throw mapMirageError(err, 'lstat', normalized)\n }\n return {\n version: versionOf(stat),\n type: typeOf(stat) === 'directory' ? 'directory' : typeOf(stat),\n ...(stat.size !== null ? { size: stat.size } : {}),\n }\n }\n\n async readText(target: FsTarget, signal?: AbortSignal): Promise<string> {\n assertNotAborted(signal, 'read')\n const key = String(target.targetKey)\n let bytes: Uint8Array\n try {\n bytes = await (await this.files(signal, 'read')).read(key)\n } catch (err) {\n throw mapMirageError(err, 'read', target.displayPath)\n }\n return decodeStrictText(bytes, target.displayPath)\n }\n\n async streamText(target: FsTarget, signal?: AbortSignal): Promise<AsyncIterable<string>> {\n // mirage reads land as one rendered buffer, so streaming is a single\n // chunk with readText's exact text semantics.\n const text = await this.readText(target, signal)\n return singleChunk(text)\n }\n\n /**\n * Stat a read target and refuse anything that is not a regular file.\n *\n * A backend read of a directory key surfaces as a missing path, which\n * would report a path that plainly exists as absent.\n *\n * @param target the resolved target about to be read.\n * @param signal aborts the stat.\n * @returns the stat row, or undefined when the mount reports none.\n */\n private async statRegularFile(\n target: FsTarget,\n signal: AbortSignal | undefined,\n ): Promise<FsInfo | undefined> {\n const info = await this.stat(target, signal)\n if (info !== undefined && info.type !== 'file') {\n throw new FsError(\n `cannot read \"${target.displayPath}\": not a regular file`,\n 'FS_NOT_REGULAR_FILE',\n )\n }\n return info\n }\n\n async readBytes(\n target: FsTarget,\n signal: AbortSignal | undefined,\n maxBytes: number,\n ): Promise<Uint8Array> {\n assertNotAborted(signal, 'read')\n const info = await this.statRegularFile(target, signal)\n if (info?.size !== undefined && info.size > maxBytes) {\n throw tooLarge(target.displayPath, maxBytes, info.size)\n }\n // Ask for one byte past the cap rather than the whole object: a backend\n // with a native range fetches only that window, and a full-length answer\n // is itself the proof the file is over the cap, which is what a mount\n // reporting no size (most API mounts) has no other way to establish.\n const key = String(target.targetKey)\n let bytes: Uint8Array\n try {\n bytes = await (await this.files(signal, 'read')).read(key, { size: maxBytes + 1 })\n } catch (err) {\n throw mapMirageError(err, 'read', target.displayPath)\n }\n if (bytes.byteLength > maxBytes) {\n throw tooLarge(target.displayPath, maxBytes)\n }\n return bytes\n }\n\n async readByteRange(\n target: FsTarget,\n range: { offset: number; length: number },\n signal?: AbortSignal,\n ): Promise<Uint8Array> {\n assertNotAborted(signal, 'read')\n await this.statRegularFile(target, signal)\n // No store can spell an empty range, so the known answer is given here\n // rather than sent to a backend that would have to refuse it.\n if (range.length === 0) return new Uint8Array(0)\n // The window is the bound, not the file: the dispatcher asks a native range\n // when the backend has one and slices a rendered read when it does not,\n // and turns a store's past-EOF refusal into the empty POSIX answer either\n // way, so every mount answers this the same.\n try {\n return await (\n await this.files(signal, 'read')\n ).read(String(target.targetKey), { offset: range.offset, size: range.length })\n } catch (err) {\n throw mapMirageError(err, 'read', target.displayPath)\n }\n }\n\n async listDir(target: FsTarget, signal?: AbortSignal): Promise<FsDirEntry[]> {\n assertNotAborted(signal, 'list')\n const key = String(target.targetKey)\n const info = await this.stat(target, signal)\n if (info === undefined) {\n throw new FsError(\n `cannot list \"${target.displayPath}\": no such file or directory`,\n 'FS_NOT_FOUND',\n )\n }\n if (info.type !== 'directory') {\n throw new FsError(`cannot list \"${target.displayPath}\": not a directory`, 'FS_NOT_DIRECTORY')\n }\n let children: string[]\n try {\n children = await (await this.files(signal, 'list')).readdir(key)\n } catch (err) {\n throw mapMirageError(err, 'list', target.displayPath)\n }\n const names = new Set<string>()\n for (const childPath of children) {\n names.add(posix.basename(childPath))\n }\n // Namespace symlinks live above every backend, so a listing merges\n // them in here exactly as mirage's own ls does.\n for (const name of this.reader.linkNames(key)) names.add(name)\n const entries: FsDirEntry[] = []\n for (const name of [...names].sort(compareCodePoints)) {\n // Per entry, not just at the entry point: a listing is one classification\n // round trip per child on a backend the readdir did not warm, and\n // a caller that gave up should stop paying for them.\n assertNotAborted(signal, 'list')\n entries.push(await this.dirEntry(key, name, signal))\n }\n return entries\n }\n\n private async dirEntry(\n parentKey: string,\n name: string,\n signal?: AbortSignal,\n ): Promise<FsDirEntry> {\n const childPath = parentKey === '/' ? `/${name}` : `${parentKey}/${name}`\n let followed: string\n try {\n followed = this.follow(childPath)\n } catch {\n // A cyclic link must not reject the whole listing; it lists as an\n // entry of unknown kind keyed on its own path.\n return {\n name,\n type: 'other',\n target: { targetKey: FsTargetKey(childPath), displayPath: childPath },\n }\n }\n const target: FsTarget = { targetKey: FsTargetKey(followed), displayPath: childPath }\n let stat: FileStat\n try {\n stat = await (await this.files(signal, 'list')).stat(followed)\n } catch (err) {\n // An abort is the caller withdrawing, not a child this listing\n // failed to classify, so it ends the walk instead of landing as\n // one more entry of unknown kind.\n if (err instanceof FsError && err.code === 'FS_ABORTED') throw err\n // A child the listing named but stat cannot classify (a broken link,\n // a race with a delete) still lists, as an entry of unknown kind.\n return { name, type: 'other', target }\n }\n return {\n name,\n type: typeOf(stat),\n target,\n version: versionOf(stat),\n ...(stat.size !== null ? { size: stat.size } : {}),\n }\n }\n\n async writeText(\n target: FsTarget,\n content: string,\n expected?: FsWriteIntent,\n signal?: AbortSignal,\n sandboxPolicy?: SandboxPolicy,\n ): Promise<FsWriteOutcome> {\n return this.withLock(target.targetKey, async () => {\n assertNotAborted(signal, 'write')\n this.assertMutable(sandboxPolicy, target.displayPath)\n const key = String(target.targetKey)\n const existing = await this.stat(target, signal)\n if (existing !== undefined && existing.type !== 'file') {\n throw new FsError(\n `cannot write \"${target.displayPath}\": not a regular file`,\n 'FS_NOT_REGULAR_FILE',\n )\n }\n if (expected?.kind === 'createIfAbsent' && existing !== undefined) {\n throw new FsError(\n `cannot write \"${target.displayPath}\": file already exists`,\n 'FS_NOT_OBSERVED',\n )\n }\n if (expected?.kind === 'replaceIfVersion') {\n if (existing?.version !== expected.version) {\n throw new FsError(\n `cannot write \"${target.displayPath}\": file changed since it was read`,\n 'FS_STALE_VERSION',\n )\n }\n }\n const before = existing === undefined ? null : await this.diffBasis(target, content)\n const crlf = before !== null && detectsCrlf(before)\n try {\n await (\n await this.files(signal, 'write')\n ).write(key, restoreLineEndings(normalizeLineEndings(content), crlf))\n } catch (err) {\n throw mapMirageError(err, 'write', target.displayPath)\n }\n return {\n operation: existing === undefined ? ('create' as const) : ('update' as const),\n version: await this.versionAfterWrite(target),\n before: before === null ? null : normalizeLineEndings(before),\n after: normalizeLineEndings(content),\n }\n })\n }\n\n // The overwrite-diff basis: the prior content when it is text and both\n // sides fit the exclusive limit, else null (dsh renders a whole-file\n // diff then). Never fails the write itself.\n private async diffBasis(target: FsTarget, content: string): Promise<string | null> {\n if (Buffer.byteLength(content, 'utf8') >= this.diffBasisMaxBytes) return null\n let bytes: Uint8Array\n try {\n bytes = await (await this.files()).read(String(target.targetKey))\n } catch {\n return null\n }\n if (bytes.byteLength >= this.diffBasisMaxBytes) return null\n try {\n return decodeStrictText(bytes, target.displayPath)\n } catch {\n return null\n }\n }\n\n private async versionAfterWrite(target: FsTarget): Promise<FsVersion> {\n const after = await this.stat(target)\n return after?.version ?? FsVersion(`missing:${String(target.targetKey)}`)\n }\n\n async editText(\n target: FsTarget,\n edit: FsEditRequest,\n expected?: { version: FsVersion },\n signal?: AbortSignal,\n sandboxPolicy?: SandboxPolicy,\n ): Promise<FsEditOutcome> {\n return this.withLock(target.targetKey, async () => {\n assertNotAborted(signal, 'edit')\n this.assertMutable(sandboxPolicy, target.displayPath)\n const key = String(target.targetKey)\n const existing = await this.stat(target, signal)\n // Stale guard before literal matching: an edit based on an old read\n // reports FS_STALE_VERSION, not a match failure against newer content.\n if (existing === undefined) {\n throw new FsError(\n `cannot edit \"${target.displayPath}\": file changed since it was read`,\n 'FS_STALE_VERSION',\n )\n }\n if (existing.type !== 'file') {\n throw new FsError(\n `cannot edit \"${target.displayPath}\": not a regular file`,\n 'FS_NOT_REGULAR_FILE',\n )\n }\n if (expected !== undefined && existing.version !== expected.version) {\n throw new FsError(\n `cannot edit \"${target.displayPath}\": file changed since it was read`,\n 'FS_STALE_VERSION',\n )\n }\n let bytes: Uint8Array\n try {\n bytes = await (await this.files(signal, 'edit')).read(key)\n } catch (err) {\n throw mapMirageError(err, 'edit', target.displayPath)\n }\n const raw = decodeStrictText(bytes, target.displayPath)\n const original = normalizeLineEndings(raw)\n const edited = applyLiteralEdit(\n original,\n normalizeLineEndings(edit.oldString),\n normalizeLineEndings(edit.newString),\n edit.replaceAll,\n target.displayPath,\n )\n try {\n await (\n await this.files(signal, 'edit')\n ).write(key, restoreLineEndings(edited, detectsCrlf(raw)))\n } catch (err) {\n throw mapMirageError(err, 'edit', target.displayPath)\n }\n return {\n version: await this.versionAfterWrite(target),\n before: original,\n after: edited,\n }\n })\n }\n}\n","// ========= Copyright 2026 @ Strukto.AI All Rights Reserved. =========\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n// http://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n// ========= Copyright 2026 @ Strukto.AI All Rights Reserved. =========\n\nimport { FsError } from '@deepseek-ai/dsh-fs'\nimport type { FsErrorCode } from '@deepseek-ai/dsh-fs'\nimport type { Refusal } from '@struktoai/mirage-core/types'\nimport { isMissingPath } from '@struktoai/mirage-core/errors/fs'\nimport { withRefusal } from '@struktoai/mirage-core/workspace/tools/io_text'\n\nexport function assertNotAborted(signal: AbortSignal | undefined, operation: string): void {\n if (signal?.aborted === true) {\n throw new FsError(`${operation} aborted`, 'FS_ABORTED')\n }\n}\n\nfunction codeOf(err: unknown): string | null {\n if (err === null || typeof err !== 'object') return null\n const stamped = err as { code?: unknown }\n return typeof stamped.code === 'string' ? stamped.code : null\n}\n\n// mirage stamps POSIX codes on its errors (ENOENT, EISDIR, ENOTDIR, EACCES,\n// EROFS, EXDEV; PolicyDenied carries EACCES too), so the dsh taxonomy is\n// derived from the stamp rather than from message sniffing or class checks\n// that would couple this adapter to mirage internals.\nfunction codeFor(err: unknown): FsErrorCode {\n if (isMissingPath(err)) return 'FS_NOT_FOUND'\n // An admission policy refused the op at the dispatcher: the session's\n // permission document, or a code policy registered beside it. EACCES is\n // too coarse to say so alone, since a plain mount-mode refusal carries\n // it too, and the two want different answers — a mode refusal is the\n // shape of this world, while a policy refusal is a confinement the call\n // may be entitled to escalate past. So it maps to the sandbox code,\n // which is what makes dsh's tool layer attach the escalation hint\n // (`FsSandboxController.mapError` keys on exactly this code) and render\n // the same denial marker bash gets for a refused command.\n //\n // Matched on `name`, like the AbortError case below and for the same\n // reason: core's PolicyDenied documents that name as the discriminator\n // for \"handlers that special-case mount-mode refusals\", and matching it\n // needs no import of a class this adapter never constructs.\n if (err instanceof Error && err.name === 'PolicyDenied') return 'FS_SANDBOX_DENIED'\n switch (codeOf(err)) {\n case 'EISDIR':\n return 'FS_NOT_REGULAR_FILE'\n case 'ENOTDIR':\n return 'FS_NOT_DIRECTORY'\n case 'EACCES':\n case 'EPERM':\n case 'EROFS':\n return 'FS_PERMISSION_DENIED'\n default:\n return 'FS_IO_ERROR'\n }\n}\n\nexport function mapMirageError(err: unknown, operation: string, displayPath: string): FsError {\n if (err instanceof FsError) return err\n // Matched by name, not by class: mirage's own executor throws a\n // `DOMException`, but an aborted fetch or a backend SDK may throw a\n // plain `Error` named the same, and reporting that as FS_IO_ERROR\n // would tell the caller its own cancellation was a backend failure.\n if (err instanceof Error && err.name === 'AbortError') {\n return new FsError(`${operation} aborted`, 'FS_ABORTED', { cause: err })\n }\n const message = err instanceof Error ? err.message : String(err)\n // A policy's refusal reads as a plain EACCES; its reason is one more\n // line, as on every other surface that hands the agent text.\n const refusal = (err as { refusal?: Refusal | null } | null)?.refusal ?? null\n const text = withRefusal(`cannot ${operation} \"${displayPath}\": ${message}`, refusal)\n return new FsError(text.trimEnd(), codeFor(err), { cause: err })\n}\n"],"mappings":"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","names":["FsError","isMissingPath","FsError","isMissingPath"]}
|