@struktoai/mirage-dsh 0.0.1 → 0.0.6
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/cordis.patch.yml +49 -0
- package/dist/{chunk-CATVBGGR.js → chunk-2F2PJ7D7.js} +66 -4
- package/dist/{chunk-CATVBGGR.js.map → chunk-2F2PJ7D7.js.map} +1 -1
- package/dist/chunk-4CZV24HW.js +196 -0
- package/dist/chunk-4CZV24HW.js.map +1 -0
- package/dist/{chunk-C67TSSYB.js → chunk-M4JOF35U.js} +108 -15
- package/dist/chunk-M4JOF35U.js.map +1 -0
- package/dist/chunk-QTBMLMKT.js +698 -0
- package/dist/chunk-QTBMLMKT.js.map +1 -0
- package/dist/fs-B3Zi9oo6.d.ts +104 -0
- package/dist/index.d.ts +137 -5
- package/dist/index.js +14 -6
- package/dist/plugin/fs.d.ts +1 -1
- package/dist/plugin/fs.js +2 -2
- package/dist/plugin/service.d.ts +3 -2
- package/dist/plugin/service.js +1 -1
- package/dist/plugin/shell.d.ts +1 -1
- package/dist/plugin/shell.js +2 -2
- package/dist/{service-CxFYAvoV.d.ts → service-BK6NR_RX.d.ts} +51 -1
- package/dist/shell-BTf2wsDy.d.ts +258 -0
- package/package.json +10 -7
- package/dist/chunk-C67TSSYB.js.map +0 -1
- package/dist/chunk-J6F4OX6Z.js +0 -102
- package/dist/chunk-J6F4OX6Z.js.map +0 -1
- package/dist/chunk-ZSZVHBSL.js +0 -455
- package/dist/chunk-ZSZVHBSL.js.map +0 -1
- package/dist/fs-CuiUEA7A.d.ts +0 -54
- package/dist/shell-B0N_jppN.d.ts +0 -108
|
@@ -0,0 +1,258 @@
|
|
|
1
|
+
import { Context } from '@deepseek-ai/cordis';
|
|
2
|
+
import { ShellExecutor, ShellExecRequest, ShellExecSpec, ShellRunResult, ShellProcess } from '@deepseek-ai/dsh-shell';
|
|
3
|
+
|
|
4
|
+
/** Configuration for the mirage shell executor. */
|
|
5
|
+
interface MirageShellConfig {
|
|
6
|
+
/**
|
|
7
|
+
* Default working directory for commands. Defaults to `/`. With
|
|
8
|
+
* `sessionId` it instead seeds the bound session's initial cwd, and the
|
|
9
|
+
* session's own cwd is the default from then on.
|
|
10
|
+
*/
|
|
11
|
+
workdir?: string;
|
|
12
|
+
/** Default foreground timeout in milliseconds. Defaults to 120000. */
|
|
13
|
+
defaultTimeoutMs?: number;
|
|
14
|
+
/** Upper cap on any requested timeout. Defaults to 600000. */
|
|
15
|
+
maxTimeoutMs?: number;
|
|
16
|
+
/** Default stdout capture budget in bytes. Defaults to 200000. */
|
|
17
|
+
stdoutMaxBytes?: number;
|
|
18
|
+
/** stderr capture budget in bytes. Defaults to 64000. */
|
|
19
|
+
stderrMaxBytes?: number;
|
|
20
|
+
/**
|
|
21
|
+
* Bind every command to this named workspace session. By default each
|
|
22
|
+
* command runs in an ephemeral fork of the workspace's default session,
|
|
23
|
+
* so nothing persists between calls, which is the one-shot contract of
|
|
24
|
+
* dsh's bash tool. With a session bound, `cd`, `export`, and function
|
|
25
|
+
* definitions persist across calls, the persistent-shell contract. The
|
|
26
|
+
* session is created on first use if the workspace does not have it; an
|
|
27
|
+
* existing session is adopted as is.
|
|
28
|
+
*
|
|
29
|
+
* A spec carrying an explicit `env`, or a `workdir` that names a real
|
|
30
|
+
* directory in this world, still runs as a one-call subshell of the
|
|
31
|
+
* bound session, per mirage's `ExecuteOptions` semantics: both say
|
|
32
|
+
* "just for this command". The two things dsh injects on every call
|
|
33
|
+
* are deliberately not read that way, since neither carries that
|
|
34
|
+
* intent and either would fork every command and leave the binding
|
|
35
|
+
* with nothing to persist. A workdir resolved on the harness's own
|
|
36
|
+
* machine names nothing here and is dropped; the managed `DSH_*`
|
|
37
|
+
* snapshot is seeded into the session instead.
|
|
38
|
+
*/
|
|
39
|
+
sessionId?: string;
|
|
40
|
+
/**
|
|
41
|
+
* When set, a background command whose streamed output overruns its
|
|
42
|
+
* delta budget spills its full stdout and stderr to files under this
|
|
43
|
+
* workspace directory, and `readOutput()` points at them so a reader
|
|
44
|
+
* can recover what the delta dropped. The directory is a workspace
|
|
45
|
+
* path (e.g. `/tmp` on a ram mount), so the agent reads the spill
|
|
46
|
+
* through the same VFS as everything else; the writes go through the
|
|
47
|
+
* workspace, so they appear in history like any other write. Unset
|
|
48
|
+
* (the default) means no spill: output that overruns is simply
|
|
49
|
+
* flagged `lossy`, the honest "no safe path available" answer.
|
|
50
|
+
*/
|
|
51
|
+
spillDir?: string;
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* Mirage-backed implementation of `ctx.shell`: `run` executes the command
|
|
55
|
+
* line with mirage's own shell (coreutils-faithful commands, installed
|
|
56
|
+
* CLIs, the policy layer) against the shared `ctx.mirage` workspace, so a
|
|
57
|
+
* path from `ctx.fs` means the same file here. There is no OS process
|
|
58
|
+
* behind a command: `signal` in results is a compatibility value for kills,
|
|
59
|
+
* and abort/timeout act cooperatively at the executor's own boundaries.
|
|
60
|
+
*
|
|
61
|
+
* Every command runs in an ephemeral fork of the workspace's default
|
|
62
|
+
* session, so no shell state survives from one call to the next, matching
|
|
63
|
+
* the one-shot contract of dsh's bash tool. Configuring a `sessionId`
|
|
64
|
+
* binds all commands to one named session instead, whose cwd, exports,
|
|
65
|
+
* and functions persist across calls.
|
|
66
|
+
*/
|
|
67
|
+
declare class MirageShellExecutor extends ShellExecutor {
|
|
68
|
+
static readonly inject: string[];
|
|
69
|
+
private readonly workdir;
|
|
70
|
+
private readonly defaultTimeoutMs;
|
|
71
|
+
private readonly maxTimeoutMs;
|
|
72
|
+
private readonly stdoutMaxBytes;
|
|
73
|
+
private readonly stderrMaxBytes;
|
|
74
|
+
private readonly sessionId;
|
|
75
|
+
private readonly spillDir;
|
|
76
|
+
private sessionReady;
|
|
77
|
+
private readOnlyReady;
|
|
78
|
+
constructor(ctx: Context, config?: MirageShellConfig);
|
|
79
|
+
private workspace;
|
|
80
|
+
/**
|
|
81
|
+
* The directory this command actually runs in.
|
|
82
|
+
*
|
|
83
|
+
* dsh fills an unspecified workdir from the calling session's cwd (by
|
|
84
|
+
* way of the sandbox policy's workspace root), and that is a directory
|
|
85
|
+
* on the harness's own machine, which names nothing here. Running
|
|
86
|
+
* there leaves `pwd` reporting a path the agent cannot reach and every
|
|
87
|
+
* relative path failing, and, because a per-call cwd forks a subshell,
|
|
88
|
+
* it also defeats a bound session on every call. So a workdir that is
|
|
89
|
+
* not a directory in this world is treated as unset: the configured
|
|
90
|
+
* default when unbound, the session's own cwd when bound.
|
|
91
|
+
*
|
|
92
|
+
* @param spec the resolved spec whose workdir is being placed.
|
|
93
|
+
* @returns the workdir to execute under, `''` meaning the session's own.
|
|
94
|
+
*/
|
|
95
|
+
private worldWorkdir;
|
|
96
|
+
private newSpill;
|
|
97
|
+
/**
|
|
98
|
+
* With every runtime in the world reaching only the vfs
|
|
99
|
+
* (`ctx.mirage.vfsOnly`), the workspace dispatch is the single gate
|
|
100
|
+
* for anything a command can do, so this executor behaves like a
|
|
101
|
+
* workspace-write sandbox: reads and writes land only where mounts
|
|
102
|
+
* (and their modes) allow. Declaring it lets sandbox-aware plugins
|
|
103
|
+
* (dsh's permission presets) compose over this executor. A world
|
|
104
|
+
* holding a runtime with doors around the gate (the host `local`
|
|
105
|
+
* python, a remote sandbox) voids that claim, so this answers
|
|
106
|
+
* undefined then (the base contract's "does not sandbox") and those
|
|
107
|
+
* plugins refuse to compose instead of trusting a lie.
|
|
108
|
+
*/
|
|
109
|
+
get sandboxMode(): ShellExecutor['sandboxMode'];
|
|
110
|
+
/**
|
|
111
|
+
* The mode this one call runs under: the policy the caller resolved
|
|
112
|
+
* for it, or this executor's own default when the call carried none.
|
|
113
|
+
* Undefined keeps the "no claim" answer for a world some runtime can
|
|
114
|
+
* act outside of, where no mode would be true.
|
|
115
|
+
*
|
|
116
|
+
* @param spec the resolved spec whose policy is being read.
|
|
117
|
+
* @returns the effective mode, or undefined when nothing is claimed.
|
|
118
|
+
*/
|
|
119
|
+
private modeFor;
|
|
120
|
+
/**
|
|
121
|
+
* The session this call runs in: the read-only twin when the policy
|
|
122
|
+
* confines it to reads, else this executor's own binding.
|
|
123
|
+
*
|
|
124
|
+
* `workspace-write` and `danger-full-access` both run in the ordinary
|
|
125
|
+
* session, because the mounts and their modes already are the
|
|
126
|
+
* workspace boundary and mirage has nothing wider to grant.
|
|
127
|
+
*
|
|
128
|
+
* @param spec the resolved spec whose policy selects the session.
|
|
129
|
+
* @returns the session id to execute under, or undefined for the default.
|
|
130
|
+
*/
|
|
131
|
+
private sessionFor;
|
|
132
|
+
/**
|
|
133
|
+
* The sandbox facts to stamp on this run's result and process handle,
|
|
134
|
+
* or undefined when the world is not fully workspace-bound (no claim).
|
|
135
|
+
*
|
|
136
|
+
* `enforcement` is 'full': when every runtime reaches only the vfs, the
|
|
137
|
+
* workspace gate cannot be bypassed, so unlike an OS sandbox on an older
|
|
138
|
+
* kernel there is no promised effect it fails to govern. `runnerFailed`
|
|
139
|
+
* is false because the workspace executor is the runner and a failure to
|
|
140
|
+
* run surfaces as a rejected/aborted execution, not a runner that never
|
|
141
|
+
* started.
|
|
142
|
+
*
|
|
143
|
+
* @param spec the resolved spec this run was built from.
|
|
144
|
+
* @param denied whether the run was refused a write by the narrowing.
|
|
145
|
+
* @returns the facts to stamp, or undefined when nothing is claimed.
|
|
146
|
+
*/
|
|
147
|
+
private sandboxInfo;
|
|
148
|
+
/**
|
|
149
|
+
* The retention budget of a background command's console.
|
|
150
|
+
*
|
|
151
|
+
* Only the background path caps retention: there a follow loop drains
|
|
152
|
+
* the console while the command still runs, so the budget bounds what
|
|
153
|
+
* the loop has not reached yet, and a chunk lost to it is reported as
|
|
154
|
+
* a gap in the sequence. A foreground console is read once, after the
|
|
155
|
+
* fact, with nothing to bound.
|
|
156
|
+
*
|
|
157
|
+
* @param spec the resolved spec carrying the stdout budget.
|
|
158
|
+
* @returns the retention budget in bytes.
|
|
159
|
+
*/
|
|
160
|
+
private retentionFor;
|
|
161
|
+
/**
|
|
162
|
+
* Take a finished run's two streams off its console, capped, and spill
|
|
163
|
+
* whichever one lost bytes.
|
|
164
|
+
*
|
|
165
|
+
* A foreground run streams into a console rather than returning its
|
|
166
|
+
* output whole, because mirage throws on abort: bytes a killed command
|
|
167
|
+
* had already printed are recoverable from a sink and nowhere else.
|
|
168
|
+
* Only a truncated stream spills, since an untruncated one is already
|
|
169
|
+
* whole in `text` and writing a file for it would put a copy of every
|
|
170
|
+
* command's output on a mount. The console this reads is untrimmed, so
|
|
171
|
+
* a spill is the whole stream rather than the tail of one.
|
|
172
|
+
*
|
|
173
|
+
* @param console_ the console this run streamed into.
|
|
174
|
+
* @param spec the resolved spec carrying the stdout budget.
|
|
175
|
+
* @returns the capped streams, each with a spill path when it lost bytes.
|
|
176
|
+
*/
|
|
177
|
+
private collectFrom;
|
|
178
|
+
/**
|
|
179
|
+
* Whether this run was refused, by the session's permission document
|
|
180
|
+
* or by the read-only narrowing.
|
|
181
|
+
*
|
|
182
|
+
* The document's refusals ride the result itself: a `Deny`, an
|
|
183
|
+
* unanswered ask and a policy that raised all leave `refusal` on the
|
|
184
|
+
* `ExecuteResult`, whatever the line did with its streams (`2>&1`, a
|
|
185
|
+
* trailing command that owns the status). That record is read for
|
|
186
|
+
* every call, because a role's `commands.deny` and `commands.ask`
|
|
187
|
+
* rules bind under `workspace-write` and `danger-full-access` alike:
|
|
188
|
+
* a mode says what the mounts allow, and says nothing about whether a
|
|
189
|
+
* rule forbids the line. The narrowing has no record, since it is
|
|
190
|
+
* EROFS/EACCES from the mounts, so its signatures are still read off
|
|
191
|
+
* stderr, and only for a call that ran read-only, where this executor
|
|
192
|
+
* is what imposed it.
|
|
193
|
+
*
|
|
194
|
+
* @param spec the resolved spec this run was built from.
|
|
195
|
+
* @param result what the workspace answered.
|
|
196
|
+
* @param stderr the run's captured standard error.
|
|
197
|
+
* @returns true when something refused the run.
|
|
198
|
+
*/
|
|
199
|
+
private wasDenied;
|
|
200
|
+
resolve(request: ShellExecRequest): ShellExecSpec;
|
|
201
|
+
private ensureSession;
|
|
202
|
+
/**
|
|
203
|
+
* Seed this call's managed `DSH_*` snapshot into the bound session.
|
|
204
|
+
*
|
|
205
|
+
* These are harness facts about the session (its home, its id), not
|
|
206
|
+
* overrides for one command, and on a bound session they have to live
|
|
207
|
+
* in the session: handed over as a per-call `env` they would fork a
|
|
208
|
+
* subshell on every call, since dsh never sends an empty snapshot.
|
|
209
|
+
*
|
|
210
|
+
* The snapshot replaces rather than merges, per the seam's own rule
|
|
211
|
+
* that a fact absent from the current snapshot must not inherit a
|
|
212
|
+
* stale value from an earlier one. Only the managed namespace is
|
|
213
|
+
* touched, so a variable the agent exported itself is left alone.
|
|
214
|
+
*
|
|
215
|
+
* @param ws the live workspace holding the session.
|
|
216
|
+
* @param sessionId the session this call runs in.
|
|
217
|
+
* @param spec the resolved spec carrying the snapshot.
|
|
218
|
+
*/
|
|
219
|
+
private applyManagedEnv;
|
|
220
|
+
private readOnlySession;
|
|
221
|
+
/**
|
|
222
|
+
* Create (once) the session a read-only call runs in: a twin of the
|
|
223
|
+
* session this executor would otherwise use, with every grant it holds
|
|
224
|
+
* narrowed to `read`, so mirage's own dispatch is what refuses the
|
|
225
|
+
* write rather than a second permission layer bolted on here.
|
|
226
|
+
*
|
|
227
|
+
* The twin narrows, never widens, and that takes every part of the
|
|
228
|
+
* source's view, which is what `narrow` in core stamps: modes, hidden
|
|
229
|
+
* paths, hidden variables, command rules. Its modes cover every mount
|
|
230
|
+
* at `read` (the one exception is the null sink, per
|
|
231
|
+
* {@link SINK_PREFIX}), which is at least as narrow as whatever the
|
|
232
|
+
* source held, since `read` is the weakest mode there is; naming a
|
|
233
|
+
* mount only narrows it, so a prefix the map omits would keep its own
|
|
234
|
+
* mode rather than disappear. The other three are copied from the
|
|
235
|
+
* source session rather than recompiled, because the profile it was
|
|
236
|
+
* created under is not something a session records.
|
|
237
|
+
*
|
|
238
|
+
* Leaving any of them behind widens. Hides are the obvious one: a
|
|
239
|
+
* binding confined to `/allowed` would read `/secret` in read-only
|
|
240
|
+
* mode although the same command is refused outside it. Command rules
|
|
241
|
+
* are the one modes cannot stand in for, because a mode bounds a
|
|
242
|
+
* mount and an account CLI reaches a service: a profile that denies
|
|
243
|
+
* `slack message send` or `git push` still denies it here, where
|
|
244
|
+
* every mount being `read` says nothing at all about it.
|
|
245
|
+
*
|
|
246
|
+
* The policy's `workspaceRoot` is deliberately not consulted anywhere:
|
|
247
|
+
* it is a directory on the harness's machine, so containment against
|
|
248
|
+
* it says nothing about this world. The mounts are the boundary.
|
|
249
|
+
*
|
|
250
|
+
* @returns the id of the read-only session.
|
|
251
|
+
*/
|
|
252
|
+
private provisionReadOnly;
|
|
253
|
+
private provisionSession;
|
|
254
|
+
run(spec: ShellExecSpec): Promise<ShellRunResult>;
|
|
255
|
+
start(spec: ShellExecSpec): ShellProcess;
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
export { type MirageShellConfig as M, MirageShellExecutor as a };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@struktoai/mirage-dsh",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.6",
|
|
4
4
|
"description": "DeepSeek Harness (dsh) providers backed by a mirage workspace: ctx.fs and ctx.shell over mounted resources",
|
|
5
5
|
"homepage": "https://github.com/strukto-ai/mirage#readme",
|
|
6
6
|
"bugs": {
|
|
@@ -17,6 +17,9 @@
|
|
|
17
17
|
"access": "public"
|
|
18
18
|
},
|
|
19
19
|
"type": "module",
|
|
20
|
+
"engines": {
|
|
21
|
+
"node": ">=20.10.0"
|
|
22
|
+
},
|
|
20
23
|
"main": "./dist/index.js",
|
|
21
24
|
"types": "./dist/index.d.ts",
|
|
22
25
|
"exports": {
|
|
@@ -47,18 +50,18 @@
|
|
|
47
50
|
"cordis.patch.yml"
|
|
48
51
|
],
|
|
49
52
|
"dependencies": {
|
|
50
|
-
"@struktoai/mirage-core": "0.0.
|
|
51
|
-
"@struktoai/mirage-node": "0.0.
|
|
53
|
+
"@struktoai/mirage-core": "0.0.6",
|
|
54
|
+
"@struktoai/mirage-node": "0.0.6"
|
|
52
55
|
},
|
|
53
56
|
"peerDependencies": {
|
|
54
57
|
"@deepseek-ai/cordis": "^4.0.1",
|
|
55
|
-
"@deepseek-ai/dsh-fs": "0.
|
|
56
|
-
"@deepseek-ai/dsh-shell": "0.
|
|
58
|
+
"@deepseek-ai/dsh-fs": "0.1.1-rc.2",
|
|
59
|
+
"@deepseek-ai/dsh-shell": "0.1.1-rc.2"
|
|
57
60
|
},
|
|
58
61
|
"devDependencies": {
|
|
59
62
|
"@deepseek-ai/cordis": "^4.0.1",
|
|
60
|
-
"@deepseek-ai/dsh-fs": "0.
|
|
61
|
-
"@deepseek-ai/dsh-shell": "0.
|
|
63
|
+
"@deepseek-ai/dsh-fs": "0.1.1-rc.2",
|
|
64
|
+
"@deepseek-ai/dsh-shell": "0.1.1-rc.2",
|
|
62
65
|
"tsup": "^8.5.0",
|
|
63
66
|
"typescript": "^6.0.0",
|
|
64
67
|
"vitest": "^3.2.6"
|
|
@@ -1 +0,0 @@
|
|
|
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 { posix } 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 { compareCodePoints, FileType, isMissingPath } from '@struktoai/mirage-core'\nimport type { FileStat, Ops } from '@struktoai/mirage-core'\nimport { assertNotAborted, mapMirageError } from './errors.ts'\nimport {\n applyLiteralEdit,\n decodeStrictText,\n detectsCrlf,\n normalizeLineEndings,\n restoreLineEndings,\n} from './text.ts'\nimport type {} from './service.ts'\n\ntype LinksSeam = NonNullable<Ops['links']>\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 /** 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\n/**\n * Mirage-backed implementation of `ctx.fs`. Targets are canonical virtual\n * paths (namespace symlinks followed), every operation walks the workspace\n * op door — 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 */\nexport class MirageFileSystem extends FileSystem {\n static readonly inject = ['mirage']\n\n private fsOps: Ops | null = null\n private readonly cwd: string\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.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 op door. 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 private async ops(signal?: AbortSignal, operation = 'ready'): Promise<Ops> {\n this.fsOps ??= (await this.ctx.mirage.ready).fs\n assertNotAborted(signal, operation)\n return this.fsOps\n }\n\n private get links(): LinksSeam | null {\n if (this.fsOps === null) {\n throw new Error('mirage: filesystem used before the workspace is ready')\n }\n return this.fsOps.links\n }\n\n private normalize(path: string, cwd?: string): string {\n return posix.resolve(cwd ?? this.cwd, path)\n }\n\n private follow(path: string): string {\n const links = this.links\n if (links === null) return path\n try {\n return links.follow(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.ops(opts?.signal, 'resolve')\n const followed = this.follow(this.normalize(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 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.ops(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.ops(signal, 'lstat')\n const normalized = this.normalize(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 const links = this.links\n if (links?.isLink(parentFollowed) === true) {\n const linkTarget = links.readlink(parentFollowed) ?? ''\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.ops(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.ops(signal, 'read')).readFile(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 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.ops(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 const links = this.links\n if (links !== null) {\n const base = key === '/' ? '/' : `${key}/`\n for (const linkPath of links.symlinkTargets().keys()) {\n if (linkPath.startsWith(base) && !linkPath.slice(base.length).includes('/')) {\n names.add(linkPath.slice(base.length))\n }\n }\n }\n const entries: FsDirEntry[] = []\n for (const name of [...names].sort(compareCodePoints)) {\n entries.push(await this.dirEntry(key, name))\n }\n return entries\n }\n\n private async dirEntry(parentKey: string, name: string): 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.ops()).stat(followed)\n } catch {\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 ): Promise<FsWriteOutcome> {\n return this.withLock(target.targetKey, async () => {\n assertNotAborted(signal, 'write')\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.ops(signal, 'write')\n ).writeFile(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.ops()).readFile(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 ): Promise<FsEditOutcome> {\n return this.withLock(target.targetKey, async () => {\n assertNotAborted(signal, 'edit')\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.ops(signal, 'edit')).readFile(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.ops(signal, 'edit')\n ).writeFile(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 { isMissingPath } from '@struktoai/mirage-core'\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// EXDEV; PolicyDenied carries EACCES too), so the dsh taxonomy is derived\n// from the stamp rather than from message sniffing or class checks that\n// would couple this adapter to mirage internals.\nfunction codeFor(err: unknown): FsErrorCode {\n if (isMissingPath(err)) return 'FS_NOT_FOUND'\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 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 if (err instanceof DOMException && 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 return new FsError(`cannot ${operation} \"${displayPath}\": ${message}`, codeFor(err), {\n cause: err,\n })\n}\n"],"mappings":"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","names":["FsError","isMissingPath","isMissingPath","FsError"]}
|
package/dist/chunk-J6F4OX6Z.js
DELETED
|
@@ -1,102 +0,0 @@
|
|
|
1
|
-
// src/service.ts
|
|
2
|
-
import { Service } from "@deepseek-ai/cordis";
|
|
3
|
-
import { buildRuntime, parseMountMode } from "@struktoai/mirage-core";
|
|
4
|
-
import { buildResource, Workspace } from "@struktoai/mirage-node";
|
|
5
|
-
function isMountBlock(entry) {
|
|
6
|
-
if (Array.isArray(entry)) return false;
|
|
7
|
-
return typeof entry.resource === "string";
|
|
8
|
-
}
|
|
9
|
-
async function resolveMount(entry) {
|
|
10
|
-
const resource = await buildResource(entry.resource, entry.config ?? {});
|
|
11
|
-
if (entry.mode === void 0) return resource;
|
|
12
|
-
return [resource, parseMountMode(entry.mode)];
|
|
13
|
-
}
|
|
14
|
-
function toRuntimeEntry(entry) {
|
|
15
|
-
if (typeof entry === "string") return entry;
|
|
16
|
-
return buildRuntime(entry.name, entry.captures === void 0 ? {} : { captures: entry.captures });
|
|
17
|
-
}
|
|
18
|
-
function builtEntry(entry) {
|
|
19
|
-
return typeof entry === "string" ? buildRuntime(entry) : entry;
|
|
20
|
-
}
|
|
21
|
-
var MirageService = class extends Service {
|
|
22
|
-
static provide = "mirage";
|
|
23
|
-
/** Resolves to the shared workspace once every mount is built. */
|
|
24
|
-
ready;
|
|
25
|
-
built = null;
|
|
26
|
-
plannedRuntimes;
|
|
27
|
-
constructor(ctx, config = {}) {
|
|
28
|
-
super(ctx, "mirage");
|
|
29
|
-
if (config.workspace !== void 0 && config.mounts !== void 0) {
|
|
30
|
-
throw new Error("mirage: pass either workspace or mounts, not both");
|
|
31
|
-
}
|
|
32
|
-
if (config.workspace !== void 0) {
|
|
33
|
-
this.built = config.workspace;
|
|
34
|
-
this.ready = Promise.resolve(config.workspace);
|
|
35
|
-
return;
|
|
36
|
-
}
|
|
37
|
-
if (config.mounts === void 0) {
|
|
38
|
-
throw new Error("mirage: one of workspace or mounts is required");
|
|
39
|
-
}
|
|
40
|
-
const options = { ...config.workspaceOptions ?? {} };
|
|
41
|
-
if (config.runtimes !== void 0) {
|
|
42
|
-
if (options.runtimes !== void 0) {
|
|
43
|
-
throw new Error("mirage: pass runtimes or workspaceOptions.runtimes, not both");
|
|
44
|
-
}
|
|
45
|
-
options.runtimes = config.runtimes.map(toRuntimeEntry);
|
|
46
|
-
}
|
|
47
|
-
this.plannedRuntimes = options.runtimes?.map(builtEntry);
|
|
48
|
-
if (this.plannedRuntimes !== void 0) options.runtimes = [...this.plannedRuntimes];
|
|
49
|
-
const blocks = Object.values(config.mounts).some(isMountBlock);
|
|
50
|
-
if (blocks) {
|
|
51
|
-
this.ready = this.open(config.mounts, options);
|
|
52
|
-
} else {
|
|
53
|
-
const owned = new Workspace(config.mounts, options);
|
|
54
|
-
this.built = owned;
|
|
55
|
-
this.ready = Promise.resolve(owned);
|
|
56
|
-
}
|
|
57
|
-
ctx.effect(
|
|
58
|
-
() => async () => {
|
|
59
|
-
const ws = await this.ready.catch(() => null);
|
|
60
|
-
if (ws !== null) await ws.close();
|
|
61
|
-
},
|
|
62
|
-
"mirage workspace"
|
|
63
|
-
);
|
|
64
|
-
}
|
|
65
|
-
/** The live workspace; throws until `ready` resolves. */
|
|
66
|
-
get workspace() {
|
|
67
|
-
if (this.built === null) {
|
|
68
|
-
throw new Error("mirage: workspace is not ready yet; await ctx.mirage.ready");
|
|
69
|
-
}
|
|
70
|
-
return this.built;
|
|
71
|
-
}
|
|
72
|
-
/**
|
|
73
|
-
* True while every runtime in the world reaches only the vfs
|
|
74
|
-
* (`Runtime.reach`), meaning the workspace dispatch is the single
|
|
75
|
-
* gate for anything the agent can execute: every effect passes
|
|
76
|
-
* mount modes, session grants, and policy before touching the real
|
|
77
|
-
* backend behind a mount. The default world qualifies, and so do
|
|
78
|
-
* the bridged engines, while the host `local` python or a remote
|
|
79
|
-
* sandbox can act around the gate. Answers before `ready` from the
|
|
80
|
-
* constructor-resolved entries (mounts resolve asynchronously,
|
|
81
|
-
* runtimes never do) and from the live workspace afterwards, so a
|
|
82
|
-
* later `addRuntime` is seen.
|
|
83
|
-
*/
|
|
84
|
-
get vfsOnly() {
|
|
85
|
-
const entries = this.built === null ? this.plannedRuntimes ?? [] : this.built.runtimeEntries;
|
|
86
|
-
return entries.every((entry) => entry.reach === "vfs");
|
|
87
|
-
}
|
|
88
|
-
async open(mounts, options) {
|
|
89
|
-
const resolved = {};
|
|
90
|
-
for (const [prefix, entry] of Object.entries(mounts)) {
|
|
91
|
-
resolved[prefix] = isMountBlock(entry) ? await resolveMount(entry) : entry;
|
|
92
|
-
}
|
|
93
|
-
const ws = new Workspace(resolved, options);
|
|
94
|
-
this.built = ws;
|
|
95
|
-
return ws;
|
|
96
|
-
}
|
|
97
|
-
};
|
|
98
|
-
|
|
99
|
-
export {
|
|
100
|
-
MirageService
|
|
101
|
-
};
|
|
102
|
-
//# sourceMappingURL=chunk-J6F4OX6Z.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/service.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 { Service } from '@deepseek-ai/cordis'\nimport type { Context } from '@deepseek-ai/cordis'\nimport type { MountSpec, Runtime, RuntimeEntry } from '@struktoai/mirage-core'\nimport { buildRuntime, parseMountMode } from '@struktoai/mirage-core'\nimport { buildResource, Workspace } from '@struktoai/mirage-node'\nimport type { Mount, NodeWorkspaceOptions } from '@struktoai/mirage-node'\n\ndeclare module '@deepseek-ai/cordis' {\n interface Context {\n mirage: MirageService\n }\n}\n\n/**\n * A declarative mount, YAML-friendly: the resource is named by its\n * registry type and built through `buildResource`, so a dsh bundle\n * patch can mount anything without holding a live instance. Told apart\n * from a live `Mount` by the type of `resource`: a string names a\n * registry entry, an object is the instance itself.\n */\nexport interface MirageMountBlock {\n /** Resource registry name (`ram`, `s3`, `slack`, `redis`, ...). */\n resource: string\n /** Mount mode: `read`, `write`, or `exec`. Omitted = the workspace default. */\n mode?: string\n /** Resource constructor config, passed to the registry factory. */\n config?: Record<string, unknown>\n}\n\n/** One mount entry: a live spec or instance, or a declarative block. */\nexport type MirageMount = MountSpec | Mount | MirageMountBlock\n\n/** A runtime entry, YAML-friendly: a name, or a name with captures. */\nexport interface MirageRuntimeBlock {\n /** Runtime registry name (`monty`, `pyodide`, `quickjs`, ...). */\n name: string\n /** Command words this runtime captures (e.g. `['python', 'python3']`). */\n captures?: string[]\n}\n\n/**\n * Configuration for the shared mirage execution world. Exactly one of\n * `workspace` (adopt a live workspace the embedder owns and closes) or\n * `mounts` (construct a workspace here; the service closes it when its\n * plugin unloads) must be given.\n */\nexport interface MirageConfig {\n /** A live workspace to adopt; its lifecycle stays with the embedder. */\n workspace?: Workspace\n /** Mounts for a service-owned workspace, keyed by mount prefix. */\n mounts?: Record<string, MirageMount>\n /**\n * Runtimes for a service-owned workspace, built via `buildRuntime`.\n * The YAML-friendly twin of `workspaceOptions.runtimes`; pass one or\n * the other, not both.\n */\n runtimes?: (string | MirageRuntimeBlock)[]\n /** Options forwarded to the service-owned workspace's constructor. */\n workspaceOptions?: NodeWorkspaceOptions\n}\n\nfunction isMountBlock(entry: MirageMount): entry is MirageMountBlock {\n if (Array.isArray(entry)) return false\n return typeof (entry as MirageMountBlock).resource === 'string'\n}\n\nasync function resolveMount(entry: MirageMountBlock): Promise<MountSpec> {\n const resource = await buildResource(entry.resource, entry.config ?? {})\n if (entry.mode === undefined) return resource\n return [resource, parseMountMode(entry.mode)]\n}\n\nfunction toRuntimeEntry(entry: string | MirageRuntimeBlock): RuntimeEntry {\n if (typeof entry === 'string') return entry\n return buildRuntime(entry.name, entry.captures === undefined ? {} : { captures: entry.captures })\n}\n\n// A name shorthand builds exactly as the workspace would build it; holding\n// the instance early is what lets `vfsOnly` classify the world before the\n// (asynchronous) mounts finish resolving.\nfunction builtEntry(entry: RuntimeEntry): Runtime {\n return typeof entry === 'string' ? buildRuntime(entry) : entry\n}\n\n/**\n * The one mirage `Workspace` behind every mirage-backed dsh provider\n * (`ctx.mirage`, mirroring how `ctx.e2b` owns one sandbox for the E2B\n * adapters). `MirageFileSystem` and `MirageShellExecutor` both inject it,\n * which is what keeps `ctx.fs` targets and `ctx.shell` commands in one\n * execution world: a `processPath` handed to the shell resolves there.\n *\n * Construction with live resources is synchronous; a declarative mount\n * block defers to `buildResource`, so consumers await `ready` (the\n * adapters do) and `workspace` throws until it resolves, mirroring how\n * the E2B service owner exposes its sandbox.\n */\nexport class MirageService extends Service {\n static readonly provide = 'mirage'\n\n /** Resolves to the shared workspace once every mount is built. */\n readonly ready: Promise<Workspace>\n\n private built: Workspace | null = null\n private readonly plannedRuntimes: readonly Runtime[] | undefined\n\n constructor(ctx: Context, config: MirageConfig = {}) {\n super(ctx, 'mirage')\n if (config.workspace !== undefined && config.mounts !== undefined) {\n throw new Error('mirage: pass either workspace or mounts, not both')\n }\n if (config.workspace !== undefined) {\n this.built = config.workspace\n this.ready = Promise.resolve(config.workspace)\n return\n }\n if (config.mounts === undefined) {\n throw new Error('mirage: one of workspace or mounts is required')\n }\n const options = { ...(config.workspaceOptions ?? {}) }\n if (config.runtimes !== undefined) {\n if (options.runtimes !== undefined) {\n throw new Error('mirage: pass runtimes or workspaceOptions.runtimes, not both')\n }\n options.runtimes = config.runtimes.map(toRuntimeEntry)\n }\n this.plannedRuntimes = options.runtimes?.map(builtEntry)\n if (this.plannedRuntimes !== undefined) options.runtimes = [...this.plannedRuntimes]\n const blocks = Object.values(config.mounts).some(isMountBlock)\n if (blocks) {\n this.ready = this.open(config.mounts, options)\n } else {\n const owned = new Workspace(config.mounts as Record<string, MountSpec | Mount>, options)\n this.built = owned\n this.ready = Promise.resolve(owned)\n }\n ctx.effect(\n () => async () => {\n const ws = await this.ready.catch(() => null)\n if (ws !== null) await ws.close()\n },\n 'mirage workspace',\n )\n }\n\n /** The live workspace; throws until `ready` resolves. */\n get workspace(): Workspace {\n if (this.built === null) {\n throw new Error('mirage: workspace is not ready yet; await ctx.mirage.ready')\n }\n return this.built\n }\n\n /**\n * True while every runtime in the world reaches only the vfs\n * (`Runtime.reach`), meaning the workspace dispatch is the single\n * gate for anything the agent can execute: every effect passes\n * mount modes, session grants, and policy before touching the real\n * backend behind a mount. The default world qualifies, and so do\n * the bridged engines, while the host `local` python or a remote\n * sandbox can act around the gate. Answers before `ready` from the\n * constructor-resolved entries (mounts resolve asynchronously,\n * runtimes never do) and from the live workspace afterwards, so a\n * later `addRuntime` is seen.\n */\n get vfsOnly(): boolean {\n const entries = this.built === null ? (this.plannedRuntimes ?? []) : this.built.runtimeEntries\n return entries.every((entry) => entry.reach === 'vfs')\n }\n\n private async open(\n mounts: Record<string, MirageMount>,\n options: NodeWorkspaceOptions,\n ): Promise<Workspace> {\n const resolved: Record<string, MountSpec | Mount> = {}\n for (const [prefix, entry] of Object.entries(mounts)) {\n resolved[prefix] = isMountBlock(entry) ? await resolveMount(entry) : entry\n }\n const ws = new Workspace(resolved, options)\n this.built = ws\n return ws\n }\n}\n"],"mappings":"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","names":[]}
|