@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.
Files changed (41) hide show
  1. package/LICENSE +201 -0
  2. package/README.md +28 -29
  3. package/cordis.patch.yml +38 -6
  4. package/dist/{chunk-M4JOF35U.js → chunk-FRF6WPLN.js} +133 -44
  5. package/dist/chunk-FRF6WPLN.js.map +1 -0
  6. package/dist/chunk-G2GHA2F6.js +211 -0
  7. package/dist/chunk-G2GHA2F6.js.map +1 -0
  8. package/dist/{chunk-QTBMLMKT.js → chunk-KUIV2Z3M.js} +243 -326
  9. package/dist/chunk-KUIV2Z3M.js.map +1 -0
  10. package/dist/{chunk-4CZV24HW.js → chunk-OHZACFWO.js} +36 -14
  11. package/dist/chunk-OHZACFWO.js.map +1 -0
  12. package/dist/{chunk-2F2PJ7D7.js → chunk-OZ2F4R6T.js} +74 -17
  13. package/dist/chunk-OZ2F4R6T.js.map +1 -0
  14. package/dist/chunk-QH5H3VND.js +110 -0
  15. package/dist/chunk-QH5H3VND.js.map +1 -0
  16. package/dist/chunk-T4BSJAGN.js +64 -0
  17. package/dist/chunk-T4BSJAGN.js.map +1 -0
  18. package/dist/{fs-B3Zi9oo6.d.ts → fs-BNOIFi9o.d.ts} +48 -4
  19. package/dist/index.d.ts +8 -3
  20. package/dist/index.js +15 -6
  21. package/dist/plugin/fs.d.ts +1 -1
  22. package/dist/plugin/fs.js +2 -2
  23. package/dist/plugin/service.d.ts +2 -1
  24. package/dist/plugin/service.js +1 -1
  25. package/dist/plugin/shell.d.ts +1 -1
  26. package/dist/plugin/shell.js +3 -2
  27. package/dist/plugin/spill-store.d.ts +3 -0
  28. package/dist/plugin/spill-store.js +8 -0
  29. package/dist/plugin/spill-store.js.map +1 -0
  30. package/dist/plugin/subprocess.d.ts +8 -0
  31. package/dist/plugin/subprocess.js +9 -0
  32. package/dist/plugin/subprocess.js.map +1 -0
  33. package/dist/{service-BK6NR_RX.d.ts → service-vCjLPYMI.d.ts} +29 -17
  34. package/dist/{shell-BTf2wsDy.d.ts → shell-BAyBocjm.d.ts} +25 -35
  35. package/dist/spill-store-BfljgRi0.d.ts +38 -0
  36. package/dist/subprocess-CInesMpR.d.ts +23 -0
  37. package/package.json +24 -12
  38. package/dist/chunk-2F2PJ7D7.js.map +0 -1
  39. package/dist/chunk-4CZV24HW.js.map +0 -1
  40. package/dist/chunk-M4JOF35U.js.map +0 -1
  41. package/dist/chunk-QTBMLMKT.js.map +0 -1
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/spill-store.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 { createHash, randomBytes } from 'node:crypto'\nimport { posix } from 'node:path'\nimport type { Context } from '@deepseek-ai/cordis'\nimport { SpillLocator, SpillStore } from '@deepseek-ai/dsh-spill'\nimport type { SaveTextSpill, SpillRef } from '@deepseek-ai/dsh-spill'\nimport { ensureDirPath } from './spill.ts'\n\n/** The workspace directory spill artifacts land under when none is configured. */\nconst DEFAULT_SPILL_DIR = '/tmp/dsh-spill'\n\n/** Bytes of randomness prefixed to a spill file name; 12 hex characters. */\nconst NAME_PREFIX_BYTES = 6\n\n/** Hex characters of the session-id digest that names a session's directory. */\nconst SESSION_DIGEST_CHARS = 12\n\n/** Configuration for the mirage spill store. */\nexport interface MirageSpillConfig {\n /**\n * Workspace directory the artifacts are written under, one\n * subdirectory per session. Absolute, and normalized before use; must\n * fall inside a mount this world can write, and the default sits on\n * the `/tmp` ram mount the bundle patch provides.\n */\n dir?: string\n}\n\n/**\n * Encode one caller-supplied string as a single safe path segment.\n *\n * `suggestedName` is a hint from a tool, never a path: it may be empty,\n * may spell `..`, and may carry separators or control characters. Only\n * the portable filename characters survive as themselves; everything\n * else, `~` included, becomes `~XXXX` so the encoding stays reversible\n * and cannot collide with a name that was already safe.\n *\n * @param raw the caller's suggested name.\n * @returns one path segment containing no separator and no traversal.\n */\nexport function encodeSegment(raw: string): string {\n if (raw.length === 0) return '~'\n // `.` is a portable filename character, so the loop below would pass\n // both of these through unchanged and hand a traversal to the join.\n if (raw === '.') return '~002E'\n if (raw === '..') return '~002E~002E'\n let out = ''\n for (const ch of raw) {\n const code = ch.codePointAt(0) ?? 0\n out +=\n ch !== '~' && /^[A-Za-z0-9._-]$/.test(ch)\n ? ch\n : `~${code.toString(16).toUpperCase().padStart(4, '0')}`\n }\n return out\n}\n\n/**\n * The directory name one session's artifacts share.\n *\n * The id is digested rather than spelled out: a session id is an\n * identifier the host chose, and a directory listing an agent can read\n * is no place to republish it.\n *\n * @param sessionId the owning session's id.\n * @returns a single path segment naming that session's directory.\n */\nexport function sessionDirName(sessionId: string): string {\n const digest = createHash('sha256').update(sessionId).digest('hex')\n return `session-${digest.slice(0, SESSION_DIGEST_CHARS)}`\n}\n\n/**\n * Mirage-backed implementation of `ctx.spillStore`.\n *\n * The spill policy replaces an oversized tool result with a preview and\n * a locator, telling the model to read or grep it for the rest. That\n * only works if the locator names a file the model can reach, and in a\n * mirage world the model reaches the workspace, not the host: a spill\n * written to the harness's own disk hands it a path its next `grep`\n * cannot open, which reads as output the harness lost. So the artifact\n * is written through the same dispatcher every other effect in this world\n * passes, and the locator is an ordinary workspace path.\n *\n * The seam asks for a private location. mirage has no permission bits\n * to offer: the workspace is the boundary, and a deployment that needs\n * the artifacts out of the agent's reach puts `dir` on a mount the\n * session has no grant for.\n */\nexport class MirageSpillStore extends SpillStore {\n static readonly inject = ['mirage']\n\n private readonly dir: string\n\n constructor(ctx: Context, config: MirageSpillConfig = {}) {\n super(ctx)\n const dir = config.dir ?? DEFAULT_SPILL_DIR\n if (!posix.isAbsolute(dir)) {\n throw new Error(`mirage: spill dir must be an absolute workspace path, got ${dir}`)\n }\n // Canonical once, here, rather than per save: a `.` or `..` left in\n // the path is written literally by the dispatcher and normalized away\n // by everything that reads a locator back, so the artifact lands on\n // a key neither the shell nor the fs seam can address. That is the\n // dead locator this store exists to prevent.\n this.dir = posix.resolve('/', dir)\n }\n\n async saveText(input: SaveTextSpill): Promise<SpillRef> {\n const workspace = await this.ctx.mirage.ready\n const dir = posix.join(this.dir, sessionDirName(input.owner.sessionId))\n // Derived from the suggested name, never equal to it: the random\n // prefix is what keeps two results with one suggested name apart.\n const name = `${randomBytes(NAME_PREFIX_BYTES).toString('hex')}-${encodeSegment(input.suggestedName)}`\n const path = posix.join(dir, name)\n try {\n await ensureDirPath(\n { exists: (p) => workspace.vfs.exists(p), mkdir: (p) => workspace.vfs.mkdir(p) },\n dir,\n )\n await workspace.vfs.write(path, input.content)\n } catch (err) {\n // Rejecting is the contract: the spill policy keeps the inline\n // result on a failure, which is a bounded answer, where a locator\n // pointing at a file that was never written is a dead end the\n // model cannot tell from a real one.\n throw new Error(`mirage: cannot write spill artifact to ${path}`, { cause: err })\n }\n return {\n locator: SpillLocator(path),\n bytes: Buffer.byteLength(input.content, 'utf8'),\n retrievalHint: 'Use read with offset/limit, or grep this workspace path to search within it.',\n }\n }\n}\n"],"mappings":";;;;;AAcA,SAAS,cAAc;AACvB,SAAS,YAAY,mBAAmB;AACxC,SAAS,aAAa;AAEtB,SAAS,cAAc,kBAAkB;AAKzC,IAAM,oBAAoB;AAG1B,IAAM,oBAAoB;AAG1B,IAAM,uBAAuB;AAyBtB,SAAS,cAAc,KAAqB;AACjD,MAAI,IAAI,WAAW,EAAG,QAAO;AAG7B,MAAI,QAAQ,IAAK,QAAO;AACxB,MAAI,QAAQ,KAAM,QAAO;AACzB,MAAI,MAAM;AACV,aAAW,MAAM,KAAK;AACpB,UAAM,OAAO,GAAG,YAAY,CAAC,KAAK;AAClC,WACE,OAAO,OAAO,mBAAmB,KAAK,EAAE,IACpC,KACA,IAAI,KAAK,SAAS,EAAE,EAAE,YAAY,EAAE,SAAS,GAAG,GAAG,CAAC;AAAA,EAC5D;AACA,SAAO;AACT;AAYO,SAAS,eAAe,WAA2B;AACxD,QAAM,SAAS,WAAW,QAAQ,EAAE,OAAO,SAAS,EAAE,OAAO,KAAK;AAClE,SAAO,WAAW,OAAO,MAAM,GAAG,oBAAoB,CAAC;AACzD;AAmBO,IAAM,mBAAN,cAA+B,WAAW;AAAA,EAC/C,OAAgB,SAAS,CAAC,QAAQ;AAAA,EAEjB;AAAA,EAEjB,YAAY,KAAc,SAA4B,CAAC,GAAG;AACxD,UAAM,GAAG;AACT,UAAM,MAAM,OAAO,OAAO;AAC1B,QAAI,CAAC,MAAM,WAAW,GAAG,GAAG;AAC1B,YAAM,IAAI,MAAM,6DAA6D,GAAG,EAAE;AAAA,IACpF;AAMA,SAAK,MAAM,MAAM,QAAQ,KAAK,GAAG;AAAA,EACnC;AAAA,EAEA,MAAM,SAAS,OAAyC;AACtD,UAAM,YAAY,MAAM,KAAK,IAAI,OAAO;AACxC,UAAM,MAAM,MAAM,KAAK,KAAK,KAAK,eAAe,MAAM,MAAM,SAAS,CAAC;AAGtE,UAAM,OAAO,GAAG,YAAY,iBAAiB,EAAE,SAAS,KAAK,CAAC,IAAI,cAAc,MAAM,aAAa,CAAC;AACpG,UAAM,OAAO,MAAM,KAAK,KAAK,IAAI;AACjC,QAAI;AACF,YAAM;AAAA,QACJ,EAAE,QAAQ,CAAC,MAAM,UAAU,IAAI,OAAO,CAAC,GAAG,OAAO,CAAC,MAAM,UAAU,IAAI,MAAM,CAAC,EAAE;AAAA,QAC/E;AAAA,MACF;AACA,YAAM,UAAU,IAAI,MAAM,MAAM,MAAM,OAAO;AAAA,IAC/C,SAAS,KAAK;AAKZ,YAAM,IAAI,MAAM,0CAA0C,IAAI,IAAI,EAAE,OAAO,IAAI,CAAC;AAAA,IAClF;AACA,WAAO;AAAA,MACL,SAAS,aAAa,IAAI;AAAA,MAC1B,OAAO,OAAO,WAAW,MAAM,SAAS,MAAM;AAAA,MAC9C,eAAe;AAAA,IACjB;AAAA,EACF;AACF;","names":[]}
@@ -6,18 +6,24 @@ type SandboxPolicy = Parameters<FileSystem['writeText']>[4];
6
6
  interface MirageFsConfig {
7
7
  /** Virtual base directory for relative paths. Defaults to `/`. */
8
8
  cwd?: string;
9
+ /**
10
+ * Read and write as this named workspace session, so the profile
11
+ * that confines the session's shell confines its file tools too. The
12
+ * workspace's default session otherwise.
13
+ */
14
+ sessionId?: string;
9
15
  /** Exclusive byte limit on each overwrite-diff side. Defaults to 10 MiB. */
10
16
  diffBasisMaxBytes?: number;
11
17
  }
12
18
  /**
13
19
  * Mirage-backed implementation of `ctx.fs`. Targets are canonical virtual
14
20
  * paths (namespace symlinks followed), every operation walks the workspace
15
- * op door — session grants, admission policies, cache read-through and
21
+ * dispatcher — session grants, admission policies, cache read-through and
16
22
  * post-write invalidation all fire exactly as they do for a shell command —
17
23
  * and `processPath` answers in the same virtual path space the mirage shell
18
24
  * executes in, so the two providers share one execution world.
19
25
  *
20
- * One limit worth stating: mirage's op facade takes no `AbortSignal`, so
26
+ * One limit worth stating: mirage's `ws.vfs` takes no `AbortSignal`, so
21
27
  * cancellation is honored at this adapter's own boundaries (before a
22
28
  * dispatch, between listing entries) and not inside a single op. A long
23
29
  * read from a remote backend therefore runs to completion after the
@@ -27,11 +33,18 @@ declare class MirageFileSystem extends FileSystem {
27
33
  static readonly inject: string[];
28
34
  private fsOps;
29
35
  private readonly cwd;
36
+ private readonly sessionId;
30
37
  private readonly diffBasisMaxBytes;
31
38
  private readonly locks;
32
39
  constructor(ctx: Context, config?: MirageFsConfig);
33
- private ops;
34
- private get links();
40
+ private files;
41
+ /**
42
+ * The namespace as the session this adapter's ops run as sees it: the
43
+ * dispatcher's reader, so a link the session cannot see is never followed
44
+ * (the typed path reaches the dispatcher and is refused as absent, not
45
+ * resolved to the visible target it points at), and never listed.
46
+ */
47
+ private get reader();
35
48
  /**
36
49
  * The same confinement claim `MirageShellExecutor` makes, off the same
37
50
  * fact and for the same reason: with every runtime reaching only the
@@ -82,6 +95,22 @@ declare class MirageFileSystem extends FileSystem {
82
95
  signal?: AbortSignal;
83
96
  }): Promise<FsTarget>;
84
97
  processPath(target: FsTarget): string;
98
+ /**
99
+ * The workspace path naming the same file as `hostPath`, when a mount
100
+ * puts one there.
101
+ *
102
+ * Most of this world has no host spelling at all: an S3 key, a Slack
103
+ * message and a Notion row are not files the harness could open, and
104
+ * claiming otherwise would hand the caller a path that reads as the
105
+ * wrong bytes. A disk mount is the one place the two worlds hold the
106
+ * same file, so it is the one place a host path is answerable; every
107
+ * other mount declines, and so does a workspace still building.
108
+ *
109
+ * @param hostPath absolute path in the harness host filesystem.
110
+ * @returns the virtual path for the same file, or undefined when no
111
+ * mount maps it.
112
+ */
113
+ processPathFromHostPath(hostPath: string): string | undefined;
85
114
  fileUrl(target: FsTarget): string;
86
115
  contains(parent: FsTarget, child: FsTarget): boolean;
87
116
  stat(target: FsTarget, signal?: AbortSignal): Promise<FsInfo | undefined>;
@@ -90,7 +119,22 @@ declare class MirageFileSystem extends FileSystem {
90
119
  }, signal?: AbortSignal): Promise<FsPathInfo | undefined>;
91
120
  readText(target: FsTarget, signal?: AbortSignal): Promise<string>;
92
121
  streamText(target: FsTarget, signal?: AbortSignal): Promise<AsyncIterable<string>>;
122
+ /**
123
+ * Stat a read target and refuse anything that is not a regular file.
124
+ *
125
+ * A backend read of a directory key surfaces as a missing path, which
126
+ * would report a path that plainly exists as absent.
127
+ *
128
+ * @param target the resolved target about to be read.
129
+ * @param signal aborts the stat.
130
+ * @returns the stat row, or undefined when the mount reports none.
131
+ */
132
+ private statRegularFile;
93
133
  readBytes(target: FsTarget, signal: AbortSignal | undefined, maxBytes: number): Promise<Uint8Array>;
134
+ readByteRange(target: FsTarget, range: {
135
+ offset: number;
136
+ length: number;
137
+ }, signal?: AbortSignal): Promise<Uint8Array>;
94
138
  listDir(target: FsTarget, signal?: AbortSignal): Promise<FsDirEntry[]>;
95
139
  private dirEntry;
96
140
  writeText(target: FsTarget, content: string, expected?: FsWriteIntent, signal?: AbortSignal, sandboxPolicy?: SandboxPolicy): Promise<FsWriteOutcome>;
package/dist/index.d.ts CHANGED
@@ -1,13 +1,18 @@
1
- export { M as MirageConfig, a as MirageMount, b as MirageMountBlock, c as MirageRuntimeBlock, d as MirageService } from './service-BK6NR_RX.js';
2
- export { M as MirageFileSystem, a as MirageFsConfig } from './fs-B3Zi9oo6.js';
3
- export { M as MirageShellConfig, a as MirageShellExecutor } from './shell-BTf2wsDy.js';
1
+ export { M as MirageConfig, a as MirageMount, b as MirageMountBlock, c as MirageRuntimeBlock, d as MirageService } from './service-vCjLPYMI.js';
2
+ export { M as MirageFileSystem, a as MirageFsConfig } from './fs-BNOIFi9o.js';
3
+ export { M as MirageSubprocess, a as MirageSubprocessConfig } from './subprocess-CInesMpR.js';
4
+ export { M as MirageShellConfig, a as MirageShellExecutor } from './shell-BAyBocjm.js';
5
+ export { M as MirageSpillConfig, a as MirageSpillStore } from './spill-store-BfljgRi0.js';
4
6
  import { Context } from '@deepseek-ai/cordis';
5
7
  import { AskHandler } from '@struktoai/mirage-core/policy/decisions';
6
8
  import { Decision } from '@struktoai/mirage-core/policy/types';
9
+ import '@struktoai/mirage-core/workspace/mount/spec';
7
10
  import '@struktoai/mirage-core/workspace/workspace/workspace';
8
11
  import '@struktoai/mirage-node';
9
12
  import '@deepseek-ai/dsh-fs';
13
+ import '@deepseek-ai/dsh-subprocess';
10
14
  import '@deepseek-ai/dsh-shell';
15
+ import '@deepseek-ai/dsh-spill';
11
16
 
12
17
  /**
13
18
  * The tool an approval request is attributed to. A mirage ask is raised
package/dist/index.js CHANGED
@@ -1,22 +1,31 @@
1
1
  import {
2
2
  MirageFileSystem
3
- } from "./chunk-M4JOF35U.js";
3
+ } from "./chunk-FRF6WPLN.js";
4
+ import {
5
+ MirageShellExecutor
6
+ } from "./chunk-KUIV2Z3M.js";
7
+ import {
8
+ MirageSpillStore
9
+ } from "./chunk-T4BSJAGN.js";
10
+ import "./chunk-QH5H3VND.js";
11
+ import {
12
+ MirageSubprocess
13
+ } from "./chunk-G2GHA2F6.js";
4
14
  import {
5
15
  APPROVAL_TOOL_NAME,
6
16
  MirageService,
7
17
  approvalReason,
8
18
  approverOf,
9
19
  askThroughApproval
10
- } from "./chunk-4CZV24HW.js";
11
- import {
12
- MirageShellExecutor
13
- } from "./chunk-QTBMLMKT.js";
14
- import "./chunk-2F2PJ7D7.js";
20
+ } from "./chunk-OHZACFWO.js";
21
+ import "./chunk-OZ2F4R6T.js";
15
22
  export {
16
23
  APPROVAL_TOOL_NAME,
17
24
  MirageFileSystem,
18
25
  MirageService,
19
26
  MirageShellExecutor,
27
+ MirageSpillStore,
28
+ MirageSubprocess,
20
29
  approvalReason,
21
30
  approverOf,
22
31
  askThroughApproval
@@ -1,3 +1,3 @@
1
- export { M as default } from '../fs-B3Zi9oo6.js';
1
+ export { M as default } from '../fs-BNOIFi9o.js';
2
2
  import '@deepseek-ai/cordis';
3
3
  import '@deepseek-ai/dsh-fs';
package/dist/plugin/fs.js CHANGED
@@ -1,7 +1,7 @@
1
1
  import {
2
2
  MirageFileSystem
3
- } from "../chunk-M4JOF35U.js";
4
- import "../chunk-2F2PJ7D7.js";
3
+ } from "../chunk-FRF6WPLN.js";
4
+ import "../chunk-OZ2F4R6T.js";
5
5
  export {
6
6
  MirageFileSystem as default
7
7
  };
@@ -1,5 +1,6 @@
1
- export { d as default } from '../service-BK6NR_RX.js';
1
+ export { d as default } from '../service-vCjLPYMI.js';
2
2
  import '@deepseek-ai/cordis';
3
3
  import '@struktoai/mirage-core/policy/types';
4
+ import '@struktoai/mirage-core/workspace/mount/spec';
4
5
  import '@struktoai/mirage-core/workspace/workspace/workspace';
5
6
  import '@struktoai/mirage-node';
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  MirageService
3
- } from "../chunk-4CZV24HW.js";
3
+ } from "../chunk-OHZACFWO.js";
4
4
  export {
5
5
  MirageService as default
6
6
  };
@@ -1,3 +1,3 @@
1
- export { a as default } from '../shell-BTf2wsDy.js';
1
+ export { a as default } from '../shell-BAyBocjm.js';
2
2
  import '@deepseek-ai/cordis';
3
3
  import '@deepseek-ai/dsh-shell';
@@ -1,7 +1,8 @@
1
1
  import {
2
2
  MirageShellExecutor
3
- } from "../chunk-QTBMLMKT.js";
4
- import "../chunk-2F2PJ7D7.js";
3
+ } from "../chunk-KUIV2Z3M.js";
4
+ import "../chunk-QH5H3VND.js";
5
+ import "../chunk-OZ2F4R6T.js";
5
6
  export {
6
7
  MirageShellExecutor as default
7
8
  };
@@ -0,0 +1,3 @@
1
+ export { a as default } from '../spill-store-BfljgRi0.js';
2
+ import '@deepseek-ai/cordis';
3
+ import '@deepseek-ai/dsh-spill';
@@ -0,0 +1,8 @@
1
+ import {
2
+ MirageSpillStore
3
+ } from "../chunk-T4BSJAGN.js";
4
+ import "../chunk-QH5H3VND.js";
5
+ export {
6
+ MirageSpillStore as default
7
+ };
8
+ //# sourceMappingURL=spill-store.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":[],"sourcesContent":[],"mappings":"","names":[]}
@@ -0,0 +1,8 @@
1
+ export { M as default } from '../subprocess-CInesMpR.js';
2
+ import '@deepseek-ai/cordis';
3
+ import '@deepseek-ai/dsh-subprocess';
4
+ import '../service-vCjLPYMI.js';
5
+ import '@struktoai/mirage-core/policy/types';
6
+ import '@struktoai/mirage-core/workspace/mount/spec';
7
+ import '@struktoai/mirage-core/workspace/workspace/workspace';
8
+ import '@struktoai/mirage-node';
@@ -0,0 +1,9 @@
1
+ import {
2
+ MirageSubprocess
3
+ } from "../chunk-G2GHA2F6.js";
4
+ import "../chunk-OHZACFWO.js";
5
+ import "../chunk-OZ2F4R6T.js";
6
+ export {
7
+ MirageSubprocess as default
8
+ };
9
+ //# sourceMappingURL=subprocess.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":[],"sourcesContent":[],"mappings":"","names":[]}
@@ -1,7 +1,8 @@
1
1
  import { Service, Context } from '@deepseek-ai/cordis';
2
- import { Explanation } from '@struktoai/mirage-core/policy/types';
2
+ import { ShellExplanation } from '@struktoai/mirage-core/policy/types';
3
+ import { Mount } from '@struktoai/mirage-core/workspace/mount/spec';
3
4
  import { MountSpec } from '@struktoai/mirage-core/workspace/workspace/workspace';
4
- import { Workspace, Mount, NodeWorkspaceOptions } from '@struktoai/mirage-node';
5
+ import { Workspace, NodeWorkspaceOptions } from '@struktoai/mirage-node';
5
6
 
6
7
  declare module '@deepseek-ai/cordis' {
7
8
  interface Context {
@@ -9,28 +10,30 @@ declare module '@deepseek-ai/cordis' {
9
10
  }
10
11
  }
11
12
  /**
12
- * A declarative mount, YAML-friendly: the resource is named by its
13
- * registry type and built through `buildResource`, so a dsh bundle
13
+ * A declarative mount, YAML-friendly: the VFS is named by its
14
+ * registry type and built through `buildVfs`, so a dsh bundle
14
15
  * patch can mount anything without holding a live instance. Told apart
15
- * from a live `Mount` by the type of `resource`: a string names a
16
+ * from a live `Mount` by the type of `vfs`: a string names a
16
17
  * registry entry, an object is the instance itself.
17
18
  */
18
19
  interface MirageMountBlock {
19
- /** Resource registry name (`ram`, `s3`, `slack`, `redis`, ...). */
20
- resource: string;
20
+ /** VFS registry name (`ram`, `s3`, `slack`, `redis`, ...). */
21
+ vfs: string;
21
22
  /** Mount mode: `read`, `write`, or `exec`. Omitted = the workspace default. */
22
23
  mode?: string;
23
- /** Resource constructor config, passed to the registry factory. */
24
+ /** VFS constructor config, passed to the registry factory. */
24
25
  config?: Record<string, unknown>;
25
26
  }
26
27
  /** One mount entry: a live spec or instance, or a declarative block. */
27
28
  type MirageMount = MountSpec | Mount | MirageMountBlock;
28
- /** A runtime entry, YAML-friendly: a name, or a name with captures. */
29
+ /** A runtime entry, YAML-friendly: a name, or a name with its options. */
29
30
  interface MirageRuntimeBlock {
30
31
  /** Runtime registry name (`monty`, `pyodide`, `quickjs`, ...). */
31
32
  name: string;
32
33
  /** Command words this runtime captures (e.g. `['python', 'python3']`). */
33
34
  captures?: string[];
35
+ /** The runtime's own config block; a key it does not have is refused. */
36
+ config?: Record<string, unknown>;
34
37
  }
35
38
  /**
36
39
  * Configuration for the shared mirage execution world. Exactly one of
@@ -80,8 +83,8 @@ interface MirageConfig {
80
83
  * which is what keeps `ctx.fs` targets and `ctx.shell` commands in one
81
84
  * execution world: a `processPath` handed to the shell resolves there.
82
85
  *
83
- * Construction with live resources is synchronous; a declarative mount
84
- * block defers to `buildResource`, so consumers await `ready` (the
86
+ * Construction with live mounts is synchronous; a declarative mount
87
+ * block defers to `buildVfs`, so consumers await `ready` (the
85
88
  * adapters do) and `workspace` throws until it resolves, mirroring how
86
89
  * the E2B service owner exposes its sandbox.
87
90
  */
@@ -94,6 +97,15 @@ declare class MirageService extends Service {
94
97
  constructor(ctx: Context, config?: MirageConfig);
95
98
  /** The live workspace; throws until `ready` resolves. */
96
99
  get workspace(): Workspace;
100
+ /**
101
+ * The live workspace, or null while it is still building.
102
+ *
103
+ * The non-throwing twin of {@link workspace}, for a synchronous seam
104
+ * method whose honest answer before `ready` is "this world cannot tell
105
+ * you" rather than an exception. Reads `built` directly, as
106
+ * {@link vfsOnly} does.
107
+ */
108
+ get workspaceIfReady(): Workspace | null;
97
109
  /**
98
110
  * The decision ledger: every ask this world has raised and every
99
111
  * answer given, questions still waiting included.
@@ -106,9 +118,9 @@ declare class MirageService extends Service {
106
118
  */
107
119
  get decisions(): Workspace['decisions'];
108
120
  /**
109
- * What a line would do here, without doing it: one `Explanation` per
110
- * command, carrying the outcome, the rule that spoke and the exact
111
- * exit code and stderr the agent would get.
121
+ * What a line would do here, without doing it: the line's verdict, with
122
+ * the exact exit code and stderr the agent would get, and its parse tree,
123
+ * every command carrying its own outcome and the policies that spoke.
112
124
  *
113
125
  * A dry run through the same gate the dispatcher uses, so a host
114
126
  * reading this and an agent typing the line cannot be told different
@@ -119,9 +131,9 @@ declare class MirageService extends Service {
119
131
  *
120
132
  * @param line the command line to judge.
121
133
  * @param sessionId the session to judge it in; the default when empty.
122
- * @returns one explanation per command of the line.
134
+ * @returns the line's explanation.
123
135
  */
124
- explain(line: string, sessionId?: string): Promise<Explanation[]>;
136
+ explain(line: string, sessionId?: string): Promise<ShellExplanation>;
125
137
  /**
126
138
  * True while every runtime in the world reaches only the vfs
127
139
  * (`Runtime.reach`), meaning the workspace dispatch is the single
@@ -132,7 +144,7 @@ declare class MirageService extends Service {
132
144
  * sandbox can act around the gate. Answers before `ready` from the
133
145
  * constructor-resolved entries (mounts resolve asynchronously,
134
146
  * runtimes never do) and from the live workspace afterwards, so a
135
- * later `addRuntime` is seen.
147
+ * later `addRuntime` or `removeRuntime` is seen.
136
148
  */
137
149
  get vfsOnly(): boolean;
138
150
  private open;
@@ -1,5 +1,5 @@
1
1
  import { Context } from '@deepseek-ai/cordis';
2
- import { ShellExecutor, ShellExecRequest, ShellExecSpec, ShellRunResult, ShellProcess } from '@deepseek-ai/dsh-shell';
2
+ import { ShellExecutor, ShellExecRequest, ShellExecSpec, ShellExecution } from '@deepseek-ai/dsh-shell';
3
3
 
4
4
  /** Configuration for the mirage shell executor. */
5
5
  interface MirageShellConfig {
@@ -75,6 +75,9 @@ declare class MirageShellExecutor extends ShellExecutor {
75
75
  private readonly spillDir;
76
76
  private sessionReady;
77
77
  private readOnlyReady;
78
+ private readonly seeding;
79
+ private issued;
80
+ private readonly seeded;
78
81
  constructor(ctx: Context, config?: MirageShellConfig);
79
82
  private workspace;
80
83
  /**
@@ -101,7 +104,7 @@ declare class MirageShellExecutor extends ShellExecutor {
101
104
  * workspace-write sandbox: reads and writes land only where mounts
102
105
  * (and their modes) allow. Declaring it lets sandbox-aware plugins
103
106
  * (dsh's permission presets) compose over this executor. A world
104
- * holding a runtime with doors around the gate (the host `local`
107
+ * holding a runtime with entry points around the gate (the host `local`
105
108
  * python, a remote sandbox) voids that claim, so this answers
106
109
  * undefined then (the base contract's "does not sandbox") and those
107
110
  * plugins refuse to compose instead of trusting a lie.
@@ -145,36 +148,6 @@ declare class MirageShellExecutor extends ShellExecutor {
145
148
  * @returns the facts to stamp, or undefined when nothing is claimed.
146
149
  */
147
150
  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
151
  /**
179
152
  * Whether this run was refused, by the session's permission document
180
153
  * or by the read-only narrowing.
@@ -214,7 +187,7 @@ declare class MirageShellExecutor extends ShellExecutor {
214
187
  *
215
188
  * @param ws the live workspace holding the session.
216
189
  * @param sessionId the session this call runs in.
217
- * @param spec the resolved spec carrying the snapshot.
190
+ * @param managed the call's managed snapshot.
218
191
  */
219
192
  private applyManagedEnv;
220
193
  private readOnlySession;
@@ -251,8 +224,25 @@ declare class MirageShellExecutor extends ShellExecutor {
251
224
  */
252
225
  private provisionReadOnly;
253
226
  private provisionSession;
254
- run(spec: ShellExecSpec): Promise<ShellRunResult>;
255
- start(spec: ShellExecSpec): ShellProcess;
227
+ /**
228
+ * Where and as whom one command runs, settled before it starts: the
229
+ * session binding, the workdir in this world, and its managed env.
230
+ *
231
+ * Seeding the env is the one step that writes, so it comes last and
232
+ * waits for any seed already running on the same session (the bound
233
+ * one and its read-only twin queue apart). Calls are numbered as they
234
+ * arrive, and a seed is skipped once a later call has seeded the same
235
+ * session, so the newest snapshot wins: a slow or abandoned preparation
236
+ * never lands an old one over it, and a stall before the seed holds up
237
+ * no other call. A call carrying no snapshot seeds nothing, so it
238
+ * never counts as the newest, but it still waits for the seeds already
239
+ * running, so its command never sees one half applied.
240
+ *
241
+ * @param spec the resolved spec being prepared.
242
+ * @returns the workspace, session and workdir the command runs under.
243
+ */
244
+ private prepare;
245
+ execute(spec: ShellExecSpec): Promise<ShellExecution>;
256
246
  }
257
247
 
258
248
  export { type MirageShellConfig as M, MirageShellExecutor as a };
@@ -0,0 +1,38 @@
1
+ import { Context } from '@deepseek-ai/cordis';
2
+ import { SpillStore, SaveTextSpill, SpillRef } from '@deepseek-ai/dsh-spill';
3
+
4
+ /** Configuration for the mirage spill store. */
5
+ interface MirageSpillConfig {
6
+ /**
7
+ * Workspace directory the artifacts are written under, one
8
+ * subdirectory per session. Absolute, and normalized before use; must
9
+ * fall inside a mount this world can write, and the default sits on
10
+ * the `/tmp` ram mount the bundle patch provides.
11
+ */
12
+ dir?: string;
13
+ }
14
+ /**
15
+ * Mirage-backed implementation of `ctx.spillStore`.
16
+ *
17
+ * The spill policy replaces an oversized tool result with a preview and
18
+ * a locator, telling the model to read or grep it for the rest. That
19
+ * only works if the locator names a file the model can reach, and in a
20
+ * mirage world the model reaches the workspace, not the host: a spill
21
+ * written to the harness's own disk hands it a path its next `grep`
22
+ * cannot open, which reads as output the harness lost. So the artifact
23
+ * is written through the same dispatcher every other effect in this world
24
+ * passes, and the locator is an ordinary workspace path.
25
+ *
26
+ * The seam asks for a private location. mirage has no permission bits
27
+ * to offer: the workspace is the boundary, and a deployment that needs
28
+ * the artifacts out of the agent's reach puts `dir` on a mount the
29
+ * session has no grant for.
30
+ */
31
+ declare class MirageSpillStore extends SpillStore {
32
+ static readonly inject: string[];
33
+ private readonly dir;
34
+ constructor(ctx: Context, config?: MirageSpillConfig);
35
+ saveText(input: SaveTextSpill): Promise<SpillRef>;
36
+ }
37
+
38
+ export { type MirageSpillConfig as M, MirageSpillStore as a };
@@ -0,0 +1,23 @@
1
+ import { Context } from '@deepseek-ai/cordis';
2
+ import { SubprocessRuntime, SubprocessSpawnSpec, SubprocessHandle, SubprocessTerminalEnvironment, SubprocessTerminalSpawnSpec, SubprocessTerminalHandle } from '@deepseek-ai/dsh-subprocess';
3
+ import './service-vCjLPYMI.js';
4
+
5
+ interface MirageSubprocessConfig {
6
+ /** Existing workspace session; omitted selects the default session. */
7
+ sessionId?: string;
8
+ }
9
+ /** One workspace execution world. No host PATH, native descendants, or PTY claims. */
10
+ declare class MirageSubprocess extends SubprocessRuntime {
11
+ private readonly config;
12
+ static readonly inject: string[];
13
+ private readonly handles;
14
+ private closed;
15
+ constructor(ctx: Context, config?: MirageSubprocessConfig);
16
+ resolveExecutable(command: string, env?: Readonly<Record<string, string>>, signal?: AbortSignal): Promise<string>;
17
+ spawn(spec: SubprocessSpawnSpec): SubprocessHandle;
18
+ terminalEnvironment(signal?: AbortSignal): Promise<SubprocessTerminalEnvironment>;
19
+ spawnTerminal(_spec: SubprocessTerminalSpawnSpec): Promise<SubprocessTerminalHandle>;
20
+ private checkWorld;
21
+ }
22
+
23
+ export { MirageSubprocess as M, type MirageSubprocessConfig as a };
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@struktoai/mirage-dsh",
3
- "version": "0.0.7-alpha.2",
4
- "description": "DeepSeek Harness (dsh) providers backed by a mirage workspace: ctx.fs and ctx.shell over mounted resources",
3
+ "version": "0.0.7",
4
+ "description": "DeepSeek Harness (dsh) providers backed by a mirage workspace: ctx.fs and ctx.shell over mounts",
5
5
  "homepage": "https://github.com/strukto-ai/mirage#readme",
6
6
  "bugs": {
7
7
  "url": "https://github.com/strukto-ai/mirage/issues"
@@ -18,7 +18,7 @@
18
18
  },
19
19
  "type": "module",
20
20
  "engines": {
21
- "node": ">=20.10.0"
21
+ "node": "^22.20 || ^24.12 || >=25"
22
22
  },
23
23
  "main": "./dist/index.js",
24
24
  "types": "./dist/index.d.ts",
@@ -38,6 +38,14 @@
38
38
  "./shell": {
39
39
  "types": "./dist/plugin/shell.d.ts",
40
40
  "import": "./dist/plugin/shell.js"
41
+ },
42
+ "./spill-store": {
43
+ "types": "./dist/plugin/spill-store.d.ts",
44
+ "import": "./dist/plugin/spill-store.js"
45
+ },
46
+ "./subprocess": {
47
+ "types": "./dist/plugin/subprocess.d.ts",
48
+ "import": "./dist/plugin/subprocess.js"
41
49
  }
42
50
  },
43
51
  "dsh": {
@@ -50,21 +58,25 @@
50
58
  "cordis.patch.yml"
51
59
  ],
52
60
  "dependencies": {
53
- "@struktoai/mirage-node": "0.0.7-alpha.2",
54
- "@struktoai/mirage-core": "0.0.7-alpha.2"
61
+ "@struktoai/mirage-core": "0.0.7",
62
+ "@struktoai/mirage-node": "0.0.7"
55
63
  },
56
64
  "peerDependencies": {
57
- "@deepseek-ai/cordis": "^4.0.1",
58
- "@deepseek-ai/dsh-fs": "0.1.1-rc.2",
59
- "@deepseek-ai/dsh-shell": "0.1.1-rc.2"
65
+ "@deepseek-ai/cordis": "~4.0.4",
66
+ "@deepseek-ai/dsh-fs": "0.2.0-rc.2",
67
+ "@deepseek-ai/dsh-shell": "0.2.0-rc.2",
68
+ "@deepseek-ai/dsh-spill": "0.2.0-rc.2",
69
+ "@deepseek-ai/dsh-subprocess": "0.2.0-rc.2"
60
70
  },
61
71
  "devDependencies": {
62
- "@deepseek-ai/cordis": "^4.0.1",
63
- "@deepseek-ai/dsh-fs": "0.1.1-rc.2",
64
- "@deepseek-ai/dsh-shell": "0.1.1-rc.2",
72
+ "@deepseek-ai/cordis": "~4.0.4",
73
+ "@deepseek-ai/dsh-fs": "0.2.0-rc.2",
74
+ "@deepseek-ai/dsh-shell": "0.2.0-rc.2",
75
+ "@deepseek-ai/dsh-spill": "0.2.0-rc.2",
65
76
  "tsup": "^8.5.0",
66
77
  "typescript": "^6.0.0",
67
- "vitest": "^3.2.6"
78
+ "vitest": "^4.1.11",
79
+ "@deepseek-ai/dsh-subprocess": "0.2.0-rc.2"
68
80
  },
69
81
  "scripts": {
70
82
  "build": "tsup",
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/text.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 { FsError } from '@deepseek-ai/dsh-fs'\n\nconst BINARY_SAMPLE_BYTES = 8192\n\n// dsh text semantics: a NUL in the leading sample or invalid UTF-8 is a\n// binary file, refused as FS_NOT_TEXT rather than decoded lossily (the\n// mirage facade's readFileText decodes with fatal: false, which the dsh\n// seam contract forbids).\nexport function decodeStrictText(bytes: Uint8Array, displayPath: string): string {\n if (bytes.subarray(0, BINARY_SAMPLE_BYTES).includes(0)) {\n throw new FsError(`cannot read \"${displayPath}\": binary file`, 'FS_NOT_TEXT')\n }\n try {\n return new TextDecoder('utf-8', { fatal: true }).decode(bytes)\n } catch (error: unknown) {\n throw new FsError(`cannot read \"${displayPath}\": invalid UTF-8 text`, 'FS_NOT_TEXT', {\n cause: error,\n })\n }\n}\n\nexport function normalizeLineEndings(value: string): string {\n return value.replaceAll('\\r\\n', '\\n')\n}\n\n// CRLF-majority sniff over a bounded sample, the same heuristic dsh's own\n// backends use to keep a rewritten file in its original line-ending style.\nexport function detectsCrlf(value: string): boolean {\n const sample = value.slice(0, 4096)\n const crlf = sample.split('\\r\\n').length - 1\n const lf = sample.split('\\n').length - 1 - crlf\n return crlf > lf\n}\n\nexport function restoreLineEndings(value: string, crlf: boolean): string {\n return crlf ? normalizeLineEndings(value).replaceAll('\\n', '\\r\\n') : value\n}\n\nfunction countOccurrences(haystack: string, needle: string): number {\n if (needle.length === 0) return 0\n let count = 0\n let index = haystack.indexOf(needle)\n while (index !== -1) {\n count++\n index = haystack.indexOf(needle, index + needle.length)\n }\n return count\n}\n\n// Literal replacement with dsh's edit taxonomy: zero matches and an empty\n// oldString are FS_EDIT_NOT_FOUND, several matches without replaceAll is\n// FS_AMBIGUOUS_EDIT.\nexport function applyLiteralEdit(\n content: string,\n oldString: string,\n newString: string,\n replaceAll: boolean,\n displayPath: string,\n): string {\n const matches = countOccurrences(content, oldString)\n if (matches === 0) {\n throw new FsError(`cannot edit \"${displayPath}\": oldString not found`, 'FS_EDIT_NOT_FOUND')\n }\n if (matches > 1 && !replaceAll) {\n throw new FsError(\n `cannot edit \"${displayPath}\": oldString matches ${String(matches)} locations; pass replaceAll or a longer unique string`,\n 'FS_AMBIGUOUS_EDIT',\n )\n }\n return replaceAll\n ? content.replaceAll(oldString, newString)\n : content.replace(oldString, newString)\n}\n\n// The first index at or after `from` that starts a UTF-8 sequence, so a tail\n// taken from there decodes as characters rather than as replacement marks.\nfunction charBoundary(bytes: Uint8Array, from: number): number {\n let start = from\n while (start < bytes.byteLength && ((bytes[start] ?? 0) & 0xc0) === 0x80) start++\n return start\n}\n\n// Byte-accurate tail cap: keep the LAST maxBytes bytes of the encoded text,\n// re-aligned to a UTF-8 sequence boundary so the kept tail still decodes.\nexport function tailCap(text: string, maxBytes: number): { text: string; truncated: boolean } {\n const bytes = new TextEncoder().encode(text)\n if (bytes.byteLength <= maxBytes) return { text, truncated: false }\n const start = charBoundary(bytes, bytes.byteLength - maxBytes)\n return {\n text: new TextDecoder('utf-8', { fatal: false }).decode(bytes.subarray(start)),\n truncated: true,\n }\n}\n\n/**\n * A bounded backlog of output bytes: the newest `budget` bytes, with the\n * oldest dropped as they overflow.\n *\n * Held as bytes rather than as a string because the string form had to\n * re-encode everything already buffered on every append to measure it,\n * which is quadratic in a command's output and lands on the same event\n * loop that serves the workspace. Appending here costs the chunk, not\n * the backlog.\n */\nexport class TailBuffer {\n private readonly budget: number\n private parts: Uint8Array[] = []\n private bytes = 0\n\n constructor(budget: number) {\n this.budget = budget\n }\n\n /**\n * Append a chunk, dropping the oldest bytes that no longer fit.\n *\n * @param data the bytes to append.\n * @returns true when bytes were dropped to make room.\n */\n append(data: Uint8Array): boolean {\n this.parts.push(data)\n this.bytes += data.byteLength\n if (this.bytes <= this.budget) return false\n let excess = this.bytes - this.budget\n while (excess > 0) {\n const head = this.parts[0]\n if (head === undefined) break\n if (head.byteLength <= excess) {\n this.parts.shift()\n excess -= head.byteLength\n this.bytes -= head.byteLength\n } else {\n this.parts[0] = head.subarray(excess)\n this.bytes -= excess\n excess = 0\n }\n }\n return true\n }\n\n /**\n * Drain everything held, decoded as text.\n *\n * The head may sit mid-character, since dropping is byte-exact, so it\n * is re-aligned before decoding rather than rendered as a replacement\n * mark.\n *\n * @returns the buffered text; the buffer is left empty.\n */\n take(): string {\n if (this.parts.length === 0) return ''\n const joined = new Uint8Array(this.bytes)\n let at = 0\n for (const part of this.parts) {\n joined.set(part, at)\n at += part.byteLength\n }\n this.parts = []\n this.bytes = 0\n return new TextDecoder('utf-8', { fatal: false }).decode(\n joined.subarray(charBoundary(joined, 0)),\n )\n }\n}\n"],"mappings":"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","names":[]}