@mastra/code-sdk 1.0.3-alpha.1 → 1.1.0-alpha.2

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 (258) hide show
  1. package/CHANGELOG.md +38 -0
  2. package/dist/acp/agent.js +155 -167
  3. package/dist/acp/agent.js.map +1 -1
  4. package/dist/acp/event-mapper.js +193 -168
  5. package/dist/acp/event-mapper.js.map +1 -1
  6. package/dist/acp/index.js +60 -49
  7. package/dist/acp/index.js.map +1 -1
  8. package/dist/acp/server.js +31 -30
  9. package/dist/acp/server.js.map +1 -1
  10. package/dist/acp.js +1 -4
  11. package/dist/agents/credential-resolver.js +40 -29
  12. package/dist/agents/credential-resolver.js.map +1 -1
  13. package/dist/agents/custom-provider-source.js +16 -9
  14. package/dist/agents/custom-provider-source.js.map +1 -1
  15. package/dist/agents/instructions.js +27 -36
  16. package/dist/agents/instructions.js.map +1 -1
  17. package/dist/agents/mastracode-gateway.js +355 -428
  18. package/dist/agents/mastracode-gateway.js.map +1 -1
  19. package/dist/agents/memory.js +87 -78
  20. package/dist/agents/memory.js.map +1 -1
  21. package/dist/agents/model.js +114 -119
  22. package/dist/agents/model.js.map +1 -1
  23. package/dist/agents/modes/build.js +12 -13
  24. package/dist/agents/modes/build.js.map +1 -1
  25. package/dist/agents/modes/explore.js +14 -13
  26. package/dist/agents/modes/explore.js.map +1 -1
  27. package/dist/agents/modes/plan.js +14 -15
  28. package/dist/agents/modes/plan.js.map +1 -1
  29. package/dist/agents/prompts/agent-instructions.js +78 -69
  30. package/dist/agents/prompts/agent-instructions.js.map +1 -1
  31. package/dist/agents/prompts/build.js +19 -16
  32. package/dist/agents/prompts/build.js.map +1 -1
  33. package/dist/agents/prompts/fast.js +8 -4
  34. package/dist/agents/prompts/fast.js.map +1 -1
  35. package/dist/agents/prompts/index.js +48 -47
  36. package/dist/agents/prompts/index.js.map +1 -1
  37. package/dist/agents/prompts/model.js +6 -5
  38. package/dist/agents/prompts/model.js.map +1 -1
  39. package/dist/agents/prompts/plan.js +20 -16
  40. package/dist/agents/prompts/plan.js.map +1 -1
  41. package/dist/agents/prompts/tool-guidance.js +77 -109
  42. package/dist/agents/prompts/tool-guidance.js.map +1 -1
  43. package/dist/agents/sandbox-filesystem.js +245 -259
  44. package/dist/agents/sandbox-filesystem.js.map +1 -1
  45. package/dist/agents/sandbox-reattach.js +9 -11
  46. package/dist/agents/sandbox-reattach.js.map +1 -1
  47. package/dist/agents/thread-caveman-state.js +92 -81
  48. package/dist/agents/thread-caveman-state.js.map +1 -1
  49. package/dist/agents/tool-availability.js +62 -80
  50. package/dist/agents/tool-availability.js.map +1 -1
  51. package/dist/agents/tools.js +99 -139
  52. package/dist/agents/tools.js.map +1 -1
  53. package/dist/agents/workspace.js +232 -225
  54. package/dist/agents/workspace.js.map +1 -1
  55. package/dist/analytics.js +139 -128
  56. package/dist/analytics.js.map +1 -1
  57. package/dist/auth/authorization-input.js +39 -25
  58. package/dist/auth/authorization-input.js.map +1 -1
  59. package/dist/auth/device-code.js +112 -80
  60. package/dist/auth/device-code.js.map +1 -1
  61. package/dist/auth/index.js +2 -9
  62. package/dist/auth/pkce.js +27 -16
  63. package/dist/auth/pkce.js.map +1 -1
  64. package/dist/auth/providers/anthropic.js +107 -93
  65. package/dist/auth/providers/anthropic.js.map +1 -1
  66. package/dist/auth/providers/github-copilot.js +304 -293
  67. package/dist/auth/providers/github-copilot.js.map +1 -1
  68. package/dist/auth/providers/openai-codex.js +442 -460
  69. package/dist/auth/providers/openai-codex.js.map +1 -1
  70. package/dist/auth/providers/xai.js +179 -145
  71. package/dist/auth/providers/xai.js.map +1 -1
  72. package/dist/auth/storage.js +182 -175
  73. package/dist/auth/storage.js.map +1 -1
  74. package/dist/auth/types.js +1 -1
  75. package/dist/clipboard/index.js +206 -172
  76. package/dist/clipboard/index.js.map +1 -1
  77. package/dist/constants.js +11 -16
  78. package/dist/constants.js.map +1 -1
  79. package/dist/error-classification.js +19 -11
  80. package/dist/error-classification.js.map +1 -1
  81. package/dist/evals/context-builder.js +145 -104
  82. package/dist/evals/context-builder.js.map +1 -1
  83. package/dist/evals/index.js +4 -7
  84. package/dist/evals/scorers/classify-command.js +62 -41
  85. package/dist/evals/scorers/classify-command.js.map +1 -1
  86. package/dist/evals/scorers/efficiency.js +239 -181
  87. package/dist/evals/scorers/efficiency.js.map +1 -1
  88. package/dist/evals/scorers/extract-tools.js +51 -47
  89. package/dist/evals/scorers/extract-tools.js.map +1 -1
  90. package/dist/evals/scorers/index.js +1 -5
  91. package/dist/evals/scorers/outcome.js +321 -212
  92. package/dist/evals/scorers/outcome.js.map +1 -1
  93. package/dist/goal-manager.js +266 -261
  94. package/dist/goal-manager.js.map +1 -1
  95. package/dist/goal-signal.js +14 -13
  96. package/dist/goal-signal.js.map +1 -1
  97. package/dist/headless/cli.js +153 -154
  98. package/dist/headless/cli.js.map +1 -1
  99. package/dist/headless/flags.js +180 -178
  100. package/dist/headless/flags.js.map +1 -1
  101. package/dist/headless/format.js +72 -71
  102. package/dist/headless/format.js.map +1 -1
  103. package/dist/headless/index.js +5 -31
  104. package/dist/headless/policy.js +35 -31
  105. package/dist/headless/policy.js.map +1 -1
  106. package/dist/headless/run-mc.js +332 -334
  107. package/dist/headless/run-mc.js.map +1 -1
  108. package/dist/headless/types.js +16 -7
  109. package/dist/headless/types.js.map +1 -1
  110. package/dist/hooks/config.js +56 -61
  111. package/dist/hooks/config.js.map +1 -1
  112. package/dist/hooks/executor.js +118 -116
  113. package/dist/hooks/executor.js.map +1 -1
  114. package/dist/hooks/index.js +4 -15
  115. package/dist/hooks/manager.js +221 -280
  116. package/dist/hooks/manager.js.map +1 -1
  117. package/dist/hooks/types.js +5 -4
  118. package/dist/hooks/types.js.map +1 -1
  119. package/dist/index.d.ts.map +1 -1
  120. package/dist/index.js +632 -712
  121. package/dist/index.js.map +1 -1
  122. package/dist/ipc/ipc-reporter.js +19 -22
  123. package/dist/ipc/ipc-reporter.js.map +1 -1
  124. package/dist/lsp/client.js +248 -306
  125. package/dist/lsp/client.js.map +1 -1
  126. package/dist/lsp/index.js +1 -6
  127. package/dist/lsp/language.js +39 -43
  128. package/dist/lsp/language.js.map +1 -1
  129. package/dist/lsp/manager.js +78 -93
  130. package/dist/lsp/manager.js.map +1 -1
  131. package/dist/lsp/server.js +151 -120
  132. package/dist/lsp/server.js.map +1 -1
  133. package/dist/lsp/workspace.js +50 -47
  134. package/dist/lsp/workspace.js.map +1 -1
  135. package/dist/mcp/config.js +210 -199
  136. package/dist/mcp/config.js.map +1 -1
  137. package/dist/mcp/index.js +2 -9
  138. package/dist/mcp/manager.js +482 -475
  139. package/dist/mcp/manager.js.map +1 -1
  140. package/dist/mcp/types.js +1 -1
  141. package/dist/onboarding/custom-packs.js +14 -17
  142. package/dist/onboarding/custom-packs.js.map +1 -1
  143. package/dist/onboarding/custom-providers.js +22 -20
  144. package/dist/onboarding/custom-providers.js.map +1 -1
  145. package/dist/onboarding/index.d.ts +1 -1
  146. package/dist/onboarding/index.d.ts.map +1 -1
  147. package/dist/onboarding/index.js +3 -21
  148. package/dist/onboarding/om-settings.d.ts +5 -0
  149. package/dist/onboarding/om-settings.d.ts.map +1 -1
  150. package/dist/onboarding/om-settings.js +41 -20
  151. package/dist/onboarding/om-settings.js.map +1 -1
  152. package/dist/onboarding/packs.d.ts +4 -6
  153. package/dist/onboarding/packs.d.ts.map +1 -1
  154. package/dist/onboarding/packs.js +154 -105
  155. package/dist/onboarding/packs.js.map +1 -1
  156. package/dist/onboarding/settings.js +575 -517
  157. package/dist/onboarding/settings.js.map +1 -1
  158. package/dist/permissions.js +112 -99
  159. package/dist/permissions.js.map +1 -1
  160. package/dist/plugin.js +21 -23
  161. package/dist/plugin.js.map +1 -1
  162. package/dist/plugins/dependencies.js +64 -87
  163. package/dist/plugins/dependencies.js.map +1 -1
  164. package/dist/plugins/install.js +159 -169
  165. package/dist/plugins/install.js.map +1 -1
  166. package/dist/plugins/loader.js +170 -185
  167. package/dist/plugins/loader.js.map +1 -1
  168. package/dist/plugins/manager.js +374 -380
  169. package/dist/plugins/manager.js.map +1 -1
  170. package/dist/plugins/manifest.js +38 -63
  171. package/dist/plugins/manifest.js.map +1 -1
  172. package/dist/plugins/package-link.js +48 -64
  173. package/dist/plugins/package-link.js.map +1 -1
  174. package/dist/plugins/paths.js +18 -19
  175. package/dist/plugins/paths.js.map +1 -1
  176. package/dist/plugins/registry.js +97 -81
  177. package/dist/plugins/registry.js.map +1 -1
  178. package/dist/plugins/scaffold.js +62 -98
  179. package/dist/plugins/scaffold.js.map +1 -1
  180. package/dist/plugins/types.js +1 -1
  181. package/dist/processors/plan-rejection-abort.js +45 -44
  182. package/dist/processors/plan-rejection-abort.js.map +1 -1
  183. package/dist/providers/amazon-bedrock-gateway.js +134 -125
  184. package/dist/providers/amazon-bedrock-gateway.js.map +1 -1
  185. package/dist/providers/amazon-bedrock.js +68 -47
  186. package/dist/providers/amazon-bedrock.js.map +1 -1
  187. package/dist/providers/claude-max.js +153 -133
  188. package/dist/providers/claude-max.js.map +1 -1
  189. package/dist/providers/github-copilot.js +232 -230
  190. package/dist/providers/github-copilot.js.map +1 -1
  191. package/dist/providers/openai-codex.js +271 -260
  192. package/dist/providers/openai-codex.js.map +1 -1
  193. package/dist/providers/xai.js +57 -50
  194. package/dist/providers/xai.js.map +1 -1
  195. package/dist/schema.js +91 -89
  196. package/dist/schema.js.map +1 -1
  197. package/dist/theme-palette.js +18 -14
  198. package/dist/theme-palette.js.map +1 -1
  199. package/dist/tool-names.js +46 -37
  200. package/dist/tool-names.js.map +1 -1
  201. package/dist/tools/index.js +2 -8
  202. package/dist/tools/request-sandbox-access.js +71 -80
  203. package/dist/tools/request-sandbox-access.js.map +1 -1
  204. package/dist/tools/utils.js +26 -16
  205. package/dist/tools/utils.js.map +1 -1
  206. package/dist/tools/web-search.js +51 -55
  207. package/dist/tools/web-search.js.map +1 -1
  208. package/dist/utils/binaries.js +65 -61
  209. package/dist/utils/binaries.js.map +1 -1
  210. package/dist/utils/debug-log.js +51 -47
  211. package/dist/utils/debug-log.js.map +1 -1
  212. package/dist/utils/errors.js +157 -192
  213. package/dist/utils/errors.js.map +1 -1
  214. package/dist/utils/gateway-sync.js +49 -37
  215. package/dist/utils/gateway-sync.js.map +1 -1
  216. package/dist/utils/path-security.js +7 -6
  217. package/dist/utils/path-security.js.map +1 -1
  218. package/dist/utils/plan-diff.js +58 -39
  219. package/dist/utils/plan-diff.js.map +1 -1
  220. package/dist/utils/plans.js +92 -74
  221. package/dist/utils/plans.js.map +1 -1
  222. package/dist/utils/project.js +318 -256
  223. package/dist/utils/project.js.map +1 -1
  224. package/dist/utils/signals-pubsub.js +117 -99
  225. package/dist/utils/signals-pubsub.js.map +1 -1
  226. package/dist/utils/slash-command-loader.js +118 -154
  227. package/dist/utils/slash-command-loader.js.map +1 -1
  228. package/dist/utils/slash-command-processor.js +92 -83
  229. package/dist/utils/slash-command-processor.js.map +1 -1
  230. package/dist/utils/stdin-pipe.js +87 -63
  231. package/dist/utils/stdin-pipe.js.map +1 -1
  232. package/dist/utils/storage-factory.js +104 -84
  233. package/dist/utils/storage-factory.js.map +1 -1
  234. package/dist/utils/storage-maintenance.js +249 -211
  235. package/dist/utils/storage-maintenance.js.map +1 -1
  236. package/dist/utils/thread-lock.js +96 -92
  237. package/dist/utils/thread-lock.js.map +1 -1
  238. package/dist/utils/token-estimator.js +12 -12
  239. package/dist/utils/token-estimator.js.map +1 -1
  240. package/dist/utils/update-check.d.ts +1 -1
  241. package/dist/utils/update-check.js +341 -259
  242. package/dist/utils/update-check.js.map +1 -1
  243. package/dist/voice/stt-registry.js +131 -113
  244. package/dist/voice/stt-registry.js.map +1 -1
  245. package/package.json +12 -12
  246. package/dist/acp.js.map +0 -1
  247. package/dist/auth/index.js.map +0 -1
  248. package/dist/auth/types.js.map +0 -1
  249. package/dist/evals/index.js.map +0 -1
  250. package/dist/evals/scorers/index.js.map +0 -1
  251. package/dist/headless/index.js.map +0 -1
  252. package/dist/hooks/index.js.map +0 -1
  253. package/dist/lsp/index.js.map +0 -1
  254. package/dist/mcp/index.js.map +0 -1
  255. package/dist/mcp/types.js.map +0 -1
  256. package/dist/onboarding/index.js.map +0 -1
  257. package/dist/plugins/types.js.map +0 -1
  258. package/dist/tools/index.js.map +0 -1
@@ -1,268 +1,254 @@
1
- import { posix as posixPath } from "node:path";
1
+ import { posix } from "path";
2
+ //#region src/agents/sandbox-filesystem.ts
3
+ /**
4
+ * SandboxFilesystem
5
+ *
6
+ * A `WorkspaceFilesystem` that stores files inside a remote `MastraSandbox`
7
+ * (e.g. a Railway VM) rather than on the server host. File operations are
8
+ * implemented by shelling out through the sandbox's `executeCommand`, so the
9
+ * agent's file tools and command tools share one VM and one view of the repo.
10
+ *
11
+ * Paths are workspace-relative (`/src/foo.ts`) and resolve under the sandbox
12
+ * working directory (`basePath`). A traversal guard rejects any path that
13
+ * escapes the workdir, mirroring `LocalFilesystem`'s contained mode.
14
+ *
15
+ * Reads/writes use base64 over the wire so binary content survives the shell.
16
+ */
17
+ /** Default per-command deadline so a hung sandbox can't block file tools forever. */
2
18
  const COMMAND_TIMEOUT_MS = 3e4;
19
+ /** Single-quote a string for safe POSIX shell interpolation. */
3
20
  function shellQuote(value) {
4
- return `'${value.replace(/'/g, `'\\''`)}'`;
21
+ return `'${value.replace(/'/g, `'\\''`)}'`;
5
22
  }
6
23
  function isFileContentString(content) {
7
- return typeof content === "string";
24
+ return typeof content === "string";
8
25
  }
9
26
  function toBuffer(content) {
10
- if (isFileContentString(content)) return Buffer.from(content, "utf8");
11
- return Buffer.from(content);
27
+ if (isFileContentString(content)) return Buffer.from(content, "utf8");
28
+ return Buffer.from(content);
12
29
  }
13
- class SandboxFilesystem {
14
- id;
15
- name = "SandboxFilesystem";
16
- provider = "sandbox";
17
- basePath;
18
- status = "ready";
19
- sandbox;
20
- constructor(options) {
21
- this.sandbox = options.sandbox;
22
- this.basePath = options.workdir;
23
- this.id = options.id ?? `sandbox-fs:${options.sandbox.id}`;
24
- }
25
- // ── Path handling ──────────────────────────────────────────────────────
26
- /**
27
- * Resolve a workspace path to an absolute path inside the sandbox, enforcing
28
- * that it stays within the workdir.
29
- */
30
- resolve(inputPath) {
31
- const rel = inputPath.startsWith("/") ? inputPath.slice(1) : inputPath;
32
- const resolved = posixPath.normalize(posixPath.join(this.basePath, rel));
33
- const root = posixPath.normalize(this.basePath);
34
- if (resolved !== root && !resolved.startsWith(`${root}/`)) {
35
- throw new Error(`Path escapes workspace root: ${inputPath}`);
36
- }
37
- return resolved;
38
- }
39
- resolveAbsolutePath(inputPath) {
40
- return this.resolve(inputPath);
41
- }
42
- // ── Command helper ─────────────────────────────────────────────────────
43
- async exec(script) {
44
- return this.sandbox.executeCommand("sh", ["-c", script], { timeout: COMMAND_TIMEOUT_MS });
45
- }
46
- /**
47
- * Lexical guard catches `..` traversal, but a symlink inside the workdir can
48
- * still point outside it. After resolving a path that refers to an existing
49
- * entry, verify its realpath is still contained in the workdir.
50
- */
51
- async assertContainedRealpath(abs, inputPath) {
52
- const result = await this.exec(`readlink -f -- ${shellQuote(abs)} 2>/dev/null`);
53
- const real = result.stdout.trim();
54
- if (result.exitCode !== 0 || !real) return;
55
- const root = posixPath.normalize(this.basePath);
56
- if (real !== root && !real.startsWith(`${root}/`)) {
57
- throw new Error(`Path escapes workspace root (symlink): ${inputPath}`);
58
- }
59
- }
60
- /**
61
- * Guard for write destinations. The lexical guard catches `..`, but a symlink
62
- * inside the workdir can redirect a write outside it. For an existing target
63
- * we check its realpath; for a not-yet-existing target we check the realpath
64
- * of its nearest existing ancestor directory, since a symlinked parent is the
65
- * escape vector (e.g. `link -> /etc` then writing `link/passwd`).
66
- */
67
- async assertContainedDest(abs, inputPath) {
68
- await this.assertContainedRealpath(abs, inputPath);
69
- const parent = posixPath.dirname(abs);
70
- if (parent && parent !== abs) {
71
- await this.assertContainedRealpath(parent, inputPath);
72
- }
73
- }
74
- async execOk(script, context) {
75
- const result = await this.exec(script);
76
- if (result.exitCode !== 0) {
77
- throw new Error(`${context} failed (exit ${result.exitCode}): ${result.stderr.trim() || result.stdout.trim()}`);
78
- }
79
- return result;
80
- }
81
- // ── File operations ────────────────────────────────────────────────────
82
- async readFile(path, options) {
83
- const abs = this.resolve(path);
84
- await this.assertContainedRealpath(abs, path);
85
- const result = await this.exec(`base64 < ${shellQuote(abs)}`);
86
- if (result.exitCode !== 0) {
87
- throw new Error(`File not found: ${path}`);
88
- }
89
- const buffer = Buffer.from(result.stdout.replace(/\s/g, ""), "base64");
90
- if (options?.encoding) {
91
- return buffer.toString(options.encoding);
92
- }
93
- return buffer;
94
- }
95
- async writeFile(path, content, options) {
96
- const abs = this.resolve(path);
97
- await this.assertContainedDest(abs, path);
98
- const b64 = toBuffer(content).toString("base64");
99
- const dir = posixPath.dirname(abs);
100
- const mkdir = options?.recursive === false ? "" : `mkdir -p ${shellQuote(dir)} && `;
101
- if (options?.overwrite === false) {
102
- const exists = await this.exists(path);
103
- if (exists) throw new Error(`File already exists: ${path}`);
104
- }
105
- await this.execOk(`${mkdir}printf %s ${shellQuote(b64)} | base64 -d > ${shellQuote(abs)}`, `writeFile ${path}`);
106
- }
107
- async appendFile(path, content) {
108
- const abs = this.resolve(path);
109
- await this.assertContainedDest(abs, path);
110
- const b64 = toBuffer(content).toString("base64");
111
- await this.execOk(
112
- `mkdir -p ${shellQuote(posixPath.dirname(abs))} && printf %s ${shellQuote(b64)} | base64 -d >> ${shellQuote(abs)}`,
113
- `appendFile ${path}`
114
- );
115
- }
116
- async deleteFile(path, options) {
117
- const abs = this.resolve(path);
118
- const force = options?.force ? "-f " : "";
119
- const result = await this.exec(`rm ${force}${shellQuote(abs)}`);
120
- if (result.exitCode !== 0 && !options?.force) {
121
- throw new Error(`File not found: ${path}`);
122
- }
123
- }
124
- async copyFile(src, dest, options) {
125
- const srcAbs = this.resolve(src);
126
- const destAbs = this.resolve(dest);
127
- await this.assertContainedRealpath(srcAbs, src);
128
- await this.assertContainedDest(destAbs, dest);
129
- const recursive = options?.recursive ? "-r " : "";
130
- if (options?.overwrite === false) {
131
- const exists = await this.exists(dest);
132
- if (exists) throw new Error(`Destination exists: ${dest}`);
133
- }
134
- await this.execOk(`cp ${recursive}${shellQuote(srcAbs)} ${shellQuote(destAbs)}`, `copyFile ${src} -> ${dest}`);
135
- }
136
- async moveFile(src, dest, options) {
137
- const srcAbs = this.resolve(src);
138
- const destAbs = this.resolve(dest);
139
- await this.assertContainedRealpath(srcAbs, src);
140
- await this.assertContainedDest(destAbs, dest);
141
- if (options?.overwrite === false) {
142
- const exists = await this.exists(dest);
143
- if (exists) throw new Error(`Destination exists: ${dest}`);
144
- }
145
- await this.execOk(`mv ${shellQuote(srcAbs)} ${shellQuote(destAbs)}`, `moveFile ${src} -> ${dest}`);
146
- }
147
- // ── Directory operations ───────────────────────────────────────────────
148
- async mkdir(path, options) {
149
- const abs = this.resolve(path);
150
- await this.assertContainedDest(abs, path);
151
- const flag = options?.recursive === false ? "" : "-p ";
152
- await this.execOk(`mkdir ${flag}${shellQuote(abs)}`, `mkdir ${path}`);
153
- }
154
- async rmdir(path, options) {
155
- const abs = this.resolve(path);
156
- if (options?.recursive) {
157
- const force = options?.force ? "-f " : "";
158
- await this.execOk(`rm -r ${force}${shellQuote(abs)}`, `rmdir ${path}`);
159
- return;
160
- }
161
- const result = await this.exec(`rmdir ${shellQuote(abs)}`);
162
- if (result.exitCode !== 0 && !options?.force) {
163
- throw new Error(`Directory not empty or not found: ${path}`);
164
- }
165
- }
166
- async readdir(path, options) {
167
- const abs = this.resolve(path);
168
- await this.assertContainedRealpath(abs, path);
169
- if (options?.recursive) {
170
- const result2 = await this.exec(
171
- `find ${shellQuote(abs)} -mindepth 1 ${options.maxDepth ? `-maxdepth ${Number(options.maxDepth)} ` : ""}-printf '%y\\t%p\\n' 2>/dev/null`
172
- );
173
- if (result2.exitCode !== 0) throw new Error(`Directory not found: ${path}`);
174
- return this.parseFindOutput(result2.stdout, abs, options);
175
- }
176
- const result = await this.exec(
177
- `cd ${shellQuote(abs)} 2>/dev/null && for f in * .[!.]*; do [ -e "$f" ] || continue; if [ -d "$f" ]; then echo "d $f"; else echo "f $f"; fi; done`
178
- );
179
- if (result.exitCode !== 0) throw new Error(`Directory not found: ${path}`);
180
- return this.parseListOutput(result.stdout, options);
181
- }
182
- parseListOutput(stdout, options) {
183
- const entries = [];
184
- for (const line of stdout.split("\n")) {
185
- if (!line) continue;
186
- const tab = line.indexOf(" ");
187
- if (tab < 0) continue;
188
- const type = line.slice(0, tab) === "d" ? "directory" : "file";
189
- const name = line.slice(tab + 1);
190
- if (!name || name === "." || name === "..") continue;
191
- if (type === "file" && !this.matchesExtension(name, options?.extension)) continue;
192
- entries.push({ name, type });
193
- }
194
- return entries;
195
- }
196
- parseFindOutput(stdout, base, options) {
197
- const entries = [];
198
- for (const line of stdout.split("\n")) {
199
- if (!line) continue;
200
- const tab = line.indexOf(" ");
201
- if (tab < 0) continue;
202
- const type = line.slice(0, tab) === "d" ? "directory" : "file";
203
- const fullPath = line.slice(tab + 1);
204
- const name = posixPath.relative(base, fullPath);
205
- if (!name) continue;
206
- if (type === "file" && !this.matchesExtension(name, options?.extension)) continue;
207
- entries.push({ name, type });
208
- }
209
- return entries;
210
- }
211
- matchesExtension(name, extension) {
212
- if (!extension) return true;
213
- const exts = Array.isArray(extension) ? extension : [extension];
214
- return exts.some((ext) => name.endsWith(ext));
215
- }
216
- // ── Path / metadata ────────────────────────────────────────────────────
217
- async exists(path) {
218
- const abs = this.resolve(path);
219
- const result = await this.exec(`test -e ${shellQuote(abs)}`);
220
- return result.exitCode === 0;
221
- }
222
- async stat(path) {
223
- const abs = this.resolve(path);
224
- await this.assertContainedRealpath(abs, path);
225
- const result = await this.exec(`stat -c '%F\\t%s\\t%Y\\t%W' ${shellQuote(abs)}`);
226
- if (result.exitCode !== 0) {
227
- throw new Error(`Path not found: ${path}`);
228
- }
229
- const [kind, sizeStr, mtimeStr, ctimeStr] = result.stdout.trim().split(" ");
230
- const type = kind && kind.includes("directory") ? "directory" : "file";
231
- const size = Number(sizeStr) || 0;
232
- const mtime = Number(mtimeStr) || 0;
233
- const ctime = Number(ctimeStr);
234
- return {
235
- name: posixPath.basename(abs),
236
- path: `/${posixPath.relative(this.basePath, abs)}`,
237
- type,
238
- size: type === "directory" ? 0 : size,
239
- modifiedAt: new Date(mtime * 1e3),
240
- createdAt: new Date((ctime > 0 ? ctime : mtime) * 1e3)
241
- };
242
- }
243
- // ── Lifecycle ──────────────────────────────────────────────────────────
244
- async init() {
245
- await this.execOk(`mkdir -p ${shellQuote(this.basePath)}`, "init workdir");
246
- }
247
- async destroy() {
248
- }
249
- async isReady() {
250
- const result = await this.exec(`test -d ${shellQuote(this.basePath)}`);
251
- return result.exitCode === 0;
252
- }
253
- getInfo() {
254
- return {
255
- id: this.id,
256
- name: this.name,
257
- provider: this.provider,
258
- metadata: { basePath: this.basePath, sandboxId: this.sandbox.id }
259
- };
260
- }
261
- getInstructions() {
262
- return `Files are stored in a remote sandbox at ${this.basePath}. Use absolute workspace paths like /src/index.ts. All reads, writes and commands run inside the same sandbox.`;
263
- }
264
- }
265
- export {
266
- SandboxFilesystem
30
+ var SandboxFilesystem = class {
31
+ id;
32
+ name = "SandboxFilesystem";
33
+ provider = "sandbox";
34
+ basePath;
35
+ status = "ready";
36
+ sandbox;
37
+ constructor(options) {
38
+ this.sandbox = options.sandbox;
39
+ this.basePath = options.workdir;
40
+ this.id = options.id ?? `sandbox-fs:${options.sandbox.id}`;
41
+ }
42
+ /**
43
+ * Resolve a workspace path to an absolute path inside the sandbox, enforcing
44
+ * that it stays within the workdir.
45
+ */
46
+ resolve(inputPath) {
47
+ const rel = inputPath.startsWith("/") ? inputPath.slice(1) : inputPath;
48
+ const resolved = posix.normalize(posix.join(this.basePath, rel));
49
+ const root = posix.normalize(this.basePath);
50
+ if (resolved !== root && !resolved.startsWith(`${root}/`)) throw new Error(`Path escapes workspace root: ${inputPath}`);
51
+ return resolved;
52
+ }
53
+ resolveAbsolutePath(inputPath) {
54
+ return this.resolve(inputPath);
55
+ }
56
+ async exec(script) {
57
+ return this.sandbox.executeCommand("sh", ["-c", script], { timeout: COMMAND_TIMEOUT_MS });
58
+ }
59
+ /**
60
+ * Lexical guard catches `..` traversal, but a symlink inside the workdir can
61
+ * still point outside it. After resolving a path that refers to an existing
62
+ * entry, verify its realpath is still contained in the workdir.
63
+ */
64
+ async assertContainedRealpath(abs, inputPath) {
65
+ const result = await this.exec(`readlink -f -- ${shellQuote(abs)} 2>/dev/null`);
66
+ const real = result.stdout.trim();
67
+ if (result.exitCode !== 0 || !real) return;
68
+ const root = posix.normalize(this.basePath);
69
+ if (real !== root && !real.startsWith(`${root}/`)) throw new Error(`Path escapes workspace root (symlink): ${inputPath}`);
70
+ }
71
+ /**
72
+ * Guard for write destinations. The lexical guard catches `..`, but a symlink
73
+ * inside the workdir can redirect a write outside it. For an existing target
74
+ * we check its realpath; for a not-yet-existing target we check the realpath
75
+ * of its nearest existing ancestor directory, since a symlinked parent is the
76
+ * escape vector (e.g. `link -> /etc` then writing `link/passwd`).
77
+ */
78
+ async assertContainedDest(abs, inputPath) {
79
+ await this.assertContainedRealpath(abs, inputPath);
80
+ const parent = posix.dirname(abs);
81
+ if (parent && parent !== abs) await this.assertContainedRealpath(parent, inputPath);
82
+ }
83
+ async execOk(script, context) {
84
+ const result = await this.exec(script);
85
+ if (result.exitCode !== 0) throw new Error(`${context} failed (exit ${result.exitCode}): ${result.stderr.trim() || result.stdout.trim()}`);
86
+ return result;
87
+ }
88
+ async readFile(path, options) {
89
+ const abs = this.resolve(path);
90
+ await this.assertContainedRealpath(abs, path);
91
+ const result = await this.exec(`base64 < ${shellQuote(abs)}`);
92
+ if (result.exitCode !== 0) throw new Error(`File not found: ${path}`);
93
+ const buffer = Buffer.from(result.stdout.replace(/\s/g, ""), "base64");
94
+ if (options?.encoding) return buffer.toString(options.encoding);
95
+ return buffer;
96
+ }
97
+ async writeFile(path, content, options) {
98
+ const abs = this.resolve(path);
99
+ await this.assertContainedDest(abs, path);
100
+ const b64 = toBuffer(content).toString("base64");
101
+ const dir = posix.dirname(abs);
102
+ const mkdir = options?.recursive === false ? "" : `mkdir -p ${shellQuote(dir)} && `;
103
+ if (options?.overwrite === false) {
104
+ if (await this.exists(path)) throw new Error(`File already exists: ${path}`);
105
+ }
106
+ await this.execOk(`${mkdir}printf %s ${shellQuote(b64)} | base64 -d > ${shellQuote(abs)}`, `writeFile ${path}`);
107
+ }
108
+ async appendFile(path, content) {
109
+ const abs = this.resolve(path);
110
+ await this.assertContainedDest(abs, path);
111
+ const b64 = toBuffer(content).toString("base64");
112
+ await this.execOk(`mkdir -p ${shellQuote(posix.dirname(abs))} && printf %s ${shellQuote(b64)} | base64 -d >> ${shellQuote(abs)}`, `appendFile ${path}`);
113
+ }
114
+ async deleteFile(path, options) {
115
+ const abs = this.resolve(path);
116
+ const force = options?.force ? "-f " : "";
117
+ if ((await this.exec(`rm ${force}${shellQuote(abs)}`)).exitCode !== 0 && !options?.force) throw new Error(`File not found: ${path}`);
118
+ }
119
+ async copyFile(src, dest, options) {
120
+ const srcAbs = this.resolve(src);
121
+ const destAbs = this.resolve(dest);
122
+ await this.assertContainedRealpath(srcAbs, src);
123
+ await this.assertContainedDest(destAbs, dest);
124
+ const recursive = options?.recursive ? "-r " : "";
125
+ if (options?.overwrite === false) {
126
+ if (await this.exists(dest)) throw new Error(`Destination exists: ${dest}`);
127
+ }
128
+ await this.execOk(`cp ${recursive}${shellQuote(srcAbs)} ${shellQuote(destAbs)}`, `copyFile ${src} -> ${dest}`);
129
+ }
130
+ async moveFile(src, dest, options) {
131
+ const srcAbs = this.resolve(src);
132
+ const destAbs = this.resolve(dest);
133
+ await this.assertContainedRealpath(srcAbs, src);
134
+ await this.assertContainedDest(destAbs, dest);
135
+ if (options?.overwrite === false) {
136
+ if (await this.exists(dest)) throw new Error(`Destination exists: ${dest}`);
137
+ }
138
+ await this.execOk(`mv ${shellQuote(srcAbs)} ${shellQuote(destAbs)}`, `moveFile ${src} -> ${dest}`);
139
+ }
140
+ async mkdir(path, options) {
141
+ const abs = this.resolve(path);
142
+ await this.assertContainedDest(abs, path);
143
+ const flag = options?.recursive === false ? "" : "-p ";
144
+ await this.execOk(`mkdir ${flag}${shellQuote(abs)}`, `mkdir ${path}`);
145
+ }
146
+ async rmdir(path, options) {
147
+ const abs = this.resolve(path);
148
+ if (options?.recursive) {
149
+ const force = options?.force ? "-f " : "";
150
+ await this.execOk(`rm -r ${force}${shellQuote(abs)}`, `rmdir ${path}`);
151
+ return;
152
+ }
153
+ if ((await this.exec(`rmdir ${shellQuote(abs)}`)).exitCode !== 0 && !options?.force) throw new Error(`Directory not empty or not found: ${path}`);
154
+ }
155
+ async readdir(path, options) {
156
+ const abs = this.resolve(path);
157
+ await this.assertContainedRealpath(abs, path);
158
+ if (options?.recursive) {
159
+ const result = await this.exec(`find ${shellQuote(abs)} -mindepth 1 ${options.maxDepth ? `-maxdepth ${Number(options.maxDepth)} ` : ""}-printf '%y\\t%p\\n' 2>/dev/null`);
160
+ if (result.exitCode !== 0) throw new Error(`Directory not found: ${path}`);
161
+ return this.parseFindOutput(result.stdout, abs, options);
162
+ }
163
+ const result = await this.exec(`cd ${shellQuote(abs)} 2>/dev/null && for f in * .[!.]*; do [ -e "$f" ] || continue; if [ -d "$f" ]; then echo "d\t$f"; else echo "f\t$f"; fi; done`);
164
+ if (result.exitCode !== 0) throw new Error(`Directory not found: ${path}`);
165
+ return this.parseListOutput(result.stdout, options);
166
+ }
167
+ parseListOutput(stdout, options) {
168
+ const entries = [];
169
+ for (const line of stdout.split("\n")) {
170
+ if (!line) continue;
171
+ const tab = line.indexOf(" ");
172
+ if (tab < 0) continue;
173
+ const type = line.slice(0, tab) === "d" ? "directory" : "file";
174
+ const name = line.slice(tab + 1);
175
+ if (!name || name === "." || name === "..") continue;
176
+ if (type === "file" && !this.matchesExtension(name, options?.extension)) continue;
177
+ entries.push({
178
+ name,
179
+ type
180
+ });
181
+ }
182
+ return entries;
183
+ }
184
+ parseFindOutput(stdout, base, options) {
185
+ const entries = [];
186
+ for (const line of stdout.split("\n")) {
187
+ if (!line) continue;
188
+ const tab = line.indexOf(" ");
189
+ if (tab < 0) continue;
190
+ const type = line.slice(0, tab) === "d" ? "directory" : "file";
191
+ const fullPath = line.slice(tab + 1);
192
+ const name = posix.relative(base, fullPath);
193
+ if (!name) continue;
194
+ if (type === "file" && !this.matchesExtension(name, options?.extension)) continue;
195
+ entries.push({
196
+ name,
197
+ type
198
+ });
199
+ }
200
+ return entries;
201
+ }
202
+ matchesExtension(name, extension) {
203
+ if (!extension) return true;
204
+ return (Array.isArray(extension) ? extension : [extension]).some((ext) => name.endsWith(ext));
205
+ }
206
+ async exists(path) {
207
+ const abs = this.resolve(path);
208
+ return (await this.exec(`test -e ${shellQuote(abs)}`)).exitCode === 0;
209
+ }
210
+ async stat(path) {
211
+ const abs = this.resolve(path);
212
+ await this.assertContainedRealpath(abs, path);
213
+ const result = await this.exec(`stat -c '%F\\t%s\\t%Y\\t%W' ${shellQuote(abs)}`);
214
+ if (result.exitCode !== 0) throw new Error(`Path not found: ${path}`);
215
+ const [kind, sizeStr, mtimeStr, ctimeStr] = result.stdout.trim().split(" ");
216
+ const type = kind && kind.includes("directory") ? "directory" : "file";
217
+ const size = Number(sizeStr) || 0;
218
+ const mtime = Number(mtimeStr) || 0;
219
+ const ctime = Number(ctimeStr);
220
+ return {
221
+ name: posix.basename(abs),
222
+ path: `/${posix.relative(this.basePath, abs)}`,
223
+ type,
224
+ size: type === "directory" ? 0 : size,
225
+ modifiedAt: /* @__PURE__ */ new Date(mtime * 1e3),
226
+ createdAt: /* @__PURE__ */ new Date((ctime > 0 ? ctime : mtime) * 1e3)
227
+ };
228
+ }
229
+ async init() {
230
+ await this.execOk(`mkdir -p ${shellQuote(this.basePath)}`, "init workdir");
231
+ }
232
+ async destroy() {}
233
+ async isReady() {
234
+ return (await this.exec(`test -d ${shellQuote(this.basePath)}`)).exitCode === 0;
235
+ }
236
+ getInfo() {
237
+ return {
238
+ id: this.id,
239
+ name: this.name,
240
+ provider: this.provider,
241
+ metadata: {
242
+ basePath: this.basePath,
243
+ sandboxId: this.sandbox.id
244
+ }
245
+ };
246
+ }
247
+ getInstructions() {
248
+ return `Files are stored in a remote sandbox at ${this.basePath}. Use absolute workspace paths like /src/index.ts. All reads, writes and commands run inside the same sandbox.`;
249
+ }
267
250
  };
251
+ //#endregion
252
+ export { SandboxFilesystem };
253
+
268
254
  //# sourceMappingURL=sandbox-filesystem.js.map