@mastra/code-sdk 1.0.3-alpha.0 → 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 +49 -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 +11 -11
  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 +1 @@
1
- {"version":3,"sources":["../../src/agents/sandbox-filesystem.ts"],"sourcesContent":["/**\n * SandboxFilesystem\n *\n * A `WorkspaceFilesystem` that stores files inside a remote `MastraSandbox`\n * (e.g. a Railway VM) rather than on the server host. File operations are\n * implemented by shelling out through the sandbox's `executeCommand`, so the\n * agent's file tools and command tools share one VM and one view of the repo.\n *\n * Paths are workspace-relative (`/src/foo.ts`) and resolve under the sandbox\n * working directory (`basePath`). A traversal guard rejects any path that\n * escapes the workdir, mirroring `LocalFilesystem`'s contained mode.\n *\n * Reads/writes use base64 over the wire so binary content survives the shell.\n */\n\nimport { posix as posixPath } from 'node:path';\nimport type {\n CopyOptions,\n FileContent,\n FileEntry,\n FileStat,\n FilesystemInfo,\n ListOptions,\n ProviderStatus,\n ReadOptions,\n RemoveOptions,\n WorkspaceFilesystem,\n WriteOptions,\n} from '@mastra/core/workspace';\n\n/** Minimal command result shape we depend on. */\nexport interface SandboxCommandResult {\n exitCode: number;\n stdout: string;\n stderr: string;\n}\n\n/** Minimal sandbox surface the filesystem needs. */\nexport interface SandboxExec {\n readonly id: string;\n executeCommand(command: string, args?: string[], options?: { timeout?: number }): Promise<SandboxCommandResult>;\n}\n\nexport interface SandboxFilesystemOptions {\n /** Live sandbox to run commands in. */\n sandbox: SandboxExec;\n /** Absolute path inside the sandbox that is the workspace root. */\n workdir: string;\n /** Optional stable id; defaults to a sandbox-derived id. */\n id?: string;\n}\n\n/** Default per-command deadline so a hung sandbox can't block file tools forever. */\nconst COMMAND_TIMEOUT_MS = 30_000;\n\n/** Single-quote a string for safe POSIX shell interpolation. */\nfunction shellQuote(value: string): string {\n return `'${value.replace(/'/g, `'\\\\''`)}'`;\n}\n\nfunction isFileContentString(content: FileContent): content is string {\n return typeof content === 'string';\n}\n\nfunction toBuffer(content: FileContent): Buffer {\n if (isFileContentString(content)) return Buffer.from(content, 'utf8');\n return Buffer.from(content);\n}\n\nexport class SandboxFilesystem implements WorkspaceFilesystem {\n readonly id: string;\n readonly name = 'SandboxFilesystem';\n readonly provider = 'sandbox';\n readonly basePath: string;\n status: ProviderStatus = 'ready';\n\n private readonly sandbox: SandboxExec;\n\n constructor(options: SandboxFilesystemOptions) {\n this.sandbox = options.sandbox;\n this.basePath = options.workdir;\n this.id = options.id ?? `sandbox-fs:${options.sandbox.id}`;\n }\n\n // ── Path handling ──────────────────────────────────────────────────────\n\n /**\n * Resolve a workspace path to an absolute path inside the sandbox, enforcing\n * that it stays within the workdir.\n */\n private resolve(inputPath: string): string {\n const rel = inputPath.startsWith('/') ? inputPath.slice(1) : inputPath;\n const resolved = posixPath.normalize(posixPath.join(this.basePath, rel));\n const root = posixPath.normalize(this.basePath);\n if (resolved !== root && !resolved.startsWith(`${root}/`)) {\n throw new Error(`Path escapes workspace root: ${inputPath}`);\n }\n return resolved;\n }\n\n resolveAbsolutePath(inputPath: string): string | undefined {\n return this.resolve(inputPath);\n }\n\n // ── Command helper ─────────────────────────────────────────────────────\n\n private async exec(script: string): Promise<SandboxCommandResult> {\n return this.sandbox.executeCommand('sh', ['-c', script], { timeout: COMMAND_TIMEOUT_MS });\n }\n\n /**\n * Lexical guard catches `..` traversal, but a symlink inside the workdir can\n * still point outside it. After resolving a path that refers to an existing\n * entry, verify its realpath is still contained in the workdir.\n */\n private async assertContainedRealpath(abs: string, inputPath: string): Promise<void> {\n const result = await this.exec(`readlink -f -- ${shellQuote(abs)} 2>/dev/null`);\n const real = result.stdout.trim();\n // If readlink couldn't resolve (path doesn't exist yet), nothing to check.\n if (result.exitCode !== 0 || !real) return;\n const root = posixPath.normalize(this.basePath);\n if (real !== root && !real.startsWith(`${root}/`)) {\n throw new Error(`Path escapes workspace root (symlink): ${inputPath}`);\n }\n }\n\n /**\n * Guard for write destinations. The lexical guard catches `..`, but a symlink\n * inside the workdir can redirect a write outside it. For an existing target\n * we check its realpath; for a not-yet-existing target we check the realpath\n * of its nearest existing ancestor directory, since a symlinked parent is the\n * escape vector (e.g. `link -> /etc` then writing `link/passwd`).\n */\n private async assertContainedDest(abs: string, inputPath: string): Promise<void> {\n // First check the target itself (covers overwriting an existing symlink).\n await this.assertContainedRealpath(abs, inputPath);\n // Then check the parent directory's realpath; readlink -f resolves the\n // nearest existing ancestor when the leaf doesn't exist yet.\n const parent = posixPath.dirname(abs);\n if (parent && parent !== abs) {\n await this.assertContainedRealpath(parent, inputPath);\n }\n }\n\n private async execOk(script: string, context: string): Promise<SandboxCommandResult> {\n const result = await this.exec(script);\n if (result.exitCode !== 0) {\n throw new Error(`${context} failed (exit ${result.exitCode}): ${result.stderr.trim() || result.stdout.trim()}`);\n }\n return result;\n }\n\n // ── File operations ────────────────────────────────────────────────────\n\n async readFile(path: string, options?: ReadOptions): Promise<string | Buffer> {\n const abs = this.resolve(path);\n await this.assertContainedRealpath(abs, path);\n const result = await this.exec(`base64 < ${shellQuote(abs)}`);\n if (result.exitCode !== 0) {\n throw new Error(`File not found: ${path}`);\n }\n const buffer = Buffer.from(result.stdout.replace(/\\s/g, ''), 'base64');\n if (options?.encoding) {\n return buffer.toString(options.encoding);\n }\n return buffer;\n }\n\n async writeFile(path: string, content: FileContent, options?: WriteOptions): Promise<void> {\n const abs = this.resolve(path);\n await this.assertContainedDest(abs, path);\n const b64 = toBuffer(content).toString('base64');\n const dir = posixPath.dirname(abs);\n const mkdir = options?.recursive === false ? '' : `mkdir -p ${shellQuote(dir)} && `;\n if (options?.overwrite === false) {\n const exists = await this.exists(path);\n if (exists) throw new Error(`File already exists: ${path}`);\n }\n await this.execOk(`${mkdir}printf %s ${shellQuote(b64)} | base64 -d > ${shellQuote(abs)}`, `writeFile ${path}`);\n }\n\n async appendFile(path: string, content: FileContent): Promise<void> {\n const abs = this.resolve(path);\n await this.assertContainedDest(abs, path);\n const b64 = toBuffer(content).toString('base64');\n await this.execOk(\n `mkdir -p ${shellQuote(posixPath.dirname(abs))} && printf %s ${shellQuote(b64)} | base64 -d >> ${shellQuote(abs)}`,\n `appendFile ${path}`,\n );\n }\n\n async deleteFile(path: string, options?: RemoveOptions): Promise<void> {\n const abs = this.resolve(path);\n const force = options?.force ? '-f ' : '';\n const result = await this.exec(`rm ${force}${shellQuote(abs)}`);\n if (result.exitCode !== 0 && !options?.force) {\n throw new Error(`File not found: ${path}`);\n }\n }\n\n async copyFile(src: string, dest: string, options?: CopyOptions): Promise<void> {\n const srcAbs = this.resolve(src);\n const destAbs = this.resolve(dest);\n await this.assertContainedRealpath(srcAbs, src);\n await this.assertContainedDest(destAbs, dest);\n const recursive = options?.recursive ? '-r ' : '';\n if (options?.overwrite === false) {\n const exists = await this.exists(dest);\n if (exists) throw new Error(`Destination exists: ${dest}`);\n }\n await this.execOk(`cp ${recursive}${shellQuote(srcAbs)} ${shellQuote(destAbs)}`, `copyFile ${src} -> ${dest}`);\n }\n\n async moveFile(src: string, dest: string, options?: CopyOptions): Promise<void> {\n const srcAbs = this.resolve(src);\n const destAbs = this.resolve(dest);\n await this.assertContainedRealpath(srcAbs, src);\n await this.assertContainedDest(destAbs, dest);\n if (options?.overwrite === false) {\n const exists = await this.exists(dest);\n if (exists) throw new Error(`Destination exists: ${dest}`);\n }\n await this.execOk(`mv ${shellQuote(srcAbs)} ${shellQuote(destAbs)}`, `moveFile ${src} -> ${dest}`);\n }\n\n // ── Directory operations ───────────────────────────────────────────────\n\n async mkdir(path: string, options?: { recursive?: boolean }): Promise<void> {\n const abs = this.resolve(path);\n await this.assertContainedDest(abs, path);\n const flag = options?.recursive === false ? '' : '-p ';\n await this.execOk(`mkdir ${flag}${shellQuote(abs)}`, `mkdir ${path}`);\n }\n\n async rmdir(path: string, options?: RemoveOptions): Promise<void> {\n const abs = this.resolve(path);\n if (options?.recursive) {\n const force = options?.force ? '-f ' : '';\n await this.execOk(`rm -r ${force}${shellQuote(abs)}`, `rmdir ${path}`);\n return;\n }\n const result = await this.exec(`rmdir ${shellQuote(abs)}`);\n if (result.exitCode !== 0 && !options?.force) {\n throw new Error(`Directory not empty or not found: ${path}`);\n }\n }\n\n async readdir(path: string, options?: ListOptions): Promise<FileEntry[]> {\n const abs = this.resolve(path);\n await this.assertContainedRealpath(abs, path);\n if (options?.recursive) {\n // Use find for recursive listings; emit \"type\\tpath\".\n const result = await this.exec(\n `find ${shellQuote(abs)} -mindepth 1 ${options.maxDepth ? `-maxdepth ${Number(options.maxDepth)} ` : ''}-printf '%y\\\\t%p\\\\n' 2>/dev/null`,\n );\n if (result.exitCode !== 0) throw new Error(`Directory not found: ${path}`);\n return this.parseFindOutput(result.stdout, abs, options);\n }\n // Non-recursive: list with name + type via a portable loop.\n const result = await this.exec(\n `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`,\n );\n if (result.exitCode !== 0) throw new Error(`Directory not found: ${path}`);\n return this.parseListOutput(result.stdout, options);\n }\n\n private parseListOutput(stdout: string, options?: ListOptions): FileEntry[] {\n const entries: FileEntry[] = [];\n for (const line of stdout.split('\\n')) {\n if (!line) continue;\n const tab = line.indexOf('\\t');\n if (tab < 0) continue;\n const type = line.slice(0, tab) === 'd' ? 'directory' : 'file';\n const name = line.slice(tab + 1);\n if (!name || name === '.' || name === '..') continue;\n if (type === 'file' && !this.matchesExtension(name, options?.extension)) continue;\n entries.push({ name, type });\n }\n return entries;\n }\n\n private parseFindOutput(stdout: string, base: string, options?: ListOptions): FileEntry[] {\n const entries: FileEntry[] = [];\n for (const line of stdout.split('\\n')) {\n if (!line) continue;\n const tab = line.indexOf('\\t');\n if (tab < 0) continue;\n const type = line.slice(0, tab) === 'd' ? 'directory' : 'file';\n const fullPath = line.slice(tab + 1);\n const name = posixPath.relative(base, fullPath);\n if (!name) continue;\n if (type === 'file' && !this.matchesExtension(name, options?.extension)) continue;\n entries.push({ name, type });\n }\n return entries;\n }\n\n private matchesExtension(name: string, extension?: string | string[]): boolean {\n if (!extension) return true;\n const exts = Array.isArray(extension) ? extension : [extension];\n return exts.some(ext => name.endsWith(ext));\n }\n\n // ── Path / metadata ────────────────────────────────────────────────────\n\n async exists(path: string): Promise<boolean> {\n const abs = this.resolve(path);\n const result = await this.exec(`test -e ${shellQuote(abs)}`);\n return result.exitCode === 0;\n }\n\n async stat(path: string): Promise<FileStat> {\n const abs = this.resolve(path);\n await this.assertContainedRealpath(abs, path);\n // %F=type, %s=size, %X=atime, %Y=mtime (epoch seconds), %W=birth (or -1).\n const result = await this.exec(`stat -c '%F\\\\t%s\\\\t%Y\\\\t%W' ${shellQuote(abs)}`);\n if (result.exitCode !== 0) {\n throw new Error(`Path not found: ${path}`);\n }\n const [kind, sizeStr, mtimeStr, ctimeStr] = result.stdout.trim().split('\\t');\n const type = kind && kind.includes('directory') ? 'directory' : 'file';\n const size = Number(sizeStr) || 0;\n const mtime = Number(mtimeStr) || 0;\n const ctime = Number(ctimeStr);\n return {\n name: posixPath.basename(abs),\n path: `/${posixPath.relative(this.basePath, abs)}`,\n type,\n size: type === 'directory' ? 0 : size,\n modifiedAt: new Date(mtime * 1000),\n createdAt: new Date((ctime > 0 ? ctime : mtime) * 1000),\n };\n }\n\n // ── Lifecycle ──────────────────────────────────────────────────────────\n\n async init(): Promise<void> {\n await this.execOk(`mkdir -p ${shellQuote(this.basePath)}`, 'init workdir');\n }\n\n async destroy(): Promise<void> {\n // The sandbox lifecycle is owned by the caller; nothing to tear down here.\n }\n\n async isReady(): Promise<boolean> {\n const result = await this.exec(`test -d ${shellQuote(this.basePath)}`);\n return result.exitCode === 0;\n }\n\n getInfo(): FilesystemInfo {\n return {\n id: this.id,\n name: this.name,\n provider: this.provider,\n metadata: { basePath: this.basePath, sandboxId: this.sandbox.id },\n };\n }\n\n getInstructions(): string {\n 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.`;\n }\n}\n"],"mappings":"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","names":["result"]}
1
+ {"version":3,"file":"sandbox-filesystem.js","names":["posixPath"],"sources":["../../src/agents/sandbox-filesystem.ts"],"sourcesContent":["/**\n * SandboxFilesystem\n *\n * A `WorkspaceFilesystem` that stores files inside a remote `MastraSandbox`\n * (e.g. a Railway VM) rather than on the server host. File operations are\n * implemented by shelling out through the sandbox's `executeCommand`, so the\n * agent's file tools and command tools share one VM and one view of the repo.\n *\n * Paths are workspace-relative (`/src/foo.ts`) and resolve under the sandbox\n * working directory (`basePath`). A traversal guard rejects any path that\n * escapes the workdir, mirroring `LocalFilesystem`'s contained mode.\n *\n * Reads/writes use base64 over the wire so binary content survives the shell.\n */\n\nimport { posix as posixPath } from 'node:path';\nimport type {\n CopyOptions,\n FileContent,\n FileEntry,\n FileStat,\n FilesystemInfo,\n ListOptions,\n ProviderStatus,\n ReadOptions,\n RemoveOptions,\n WorkspaceFilesystem,\n WriteOptions,\n} from '@mastra/core/workspace';\n\n/** Minimal command result shape we depend on. */\nexport interface SandboxCommandResult {\n exitCode: number;\n stdout: string;\n stderr: string;\n}\n\n/** Minimal sandbox surface the filesystem needs. */\nexport interface SandboxExec {\n readonly id: string;\n executeCommand(command: string, args?: string[], options?: { timeout?: number }): Promise<SandboxCommandResult>;\n}\n\nexport interface SandboxFilesystemOptions {\n /** Live sandbox to run commands in. */\n sandbox: SandboxExec;\n /** Absolute path inside the sandbox that is the workspace root. */\n workdir: string;\n /** Optional stable id; defaults to a sandbox-derived id. */\n id?: string;\n}\n\n/** Default per-command deadline so a hung sandbox can't block file tools forever. */\nconst COMMAND_TIMEOUT_MS = 30_000;\n\n/** Single-quote a string for safe POSIX shell interpolation. */\nfunction shellQuote(value: string): string {\n return `'${value.replace(/'/g, `'\\\\''`)}'`;\n}\n\nfunction isFileContentString(content: FileContent): content is string {\n return typeof content === 'string';\n}\n\nfunction toBuffer(content: FileContent): Buffer {\n if (isFileContentString(content)) return Buffer.from(content, 'utf8');\n return Buffer.from(content);\n}\n\nexport class SandboxFilesystem implements WorkspaceFilesystem {\n readonly id: string;\n readonly name = 'SandboxFilesystem';\n readonly provider = 'sandbox';\n readonly basePath: string;\n status: ProviderStatus = 'ready';\n\n private readonly sandbox: SandboxExec;\n\n constructor(options: SandboxFilesystemOptions) {\n this.sandbox = options.sandbox;\n this.basePath = options.workdir;\n this.id = options.id ?? `sandbox-fs:${options.sandbox.id}`;\n }\n\n // ── Path handling ──────────────────────────────────────────────────────\n\n /**\n * Resolve a workspace path to an absolute path inside the sandbox, enforcing\n * that it stays within the workdir.\n */\n private resolve(inputPath: string): string {\n const rel = inputPath.startsWith('/') ? inputPath.slice(1) : inputPath;\n const resolved = posixPath.normalize(posixPath.join(this.basePath, rel));\n const root = posixPath.normalize(this.basePath);\n if (resolved !== root && !resolved.startsWith(`${root}/`)) {\n throw new Error(`Path escapes workspace root: ${inputPath}`);\n }\n return resolved;\n }\n\n resolveAbsolutePath(inputPath: string): string | undefined {\n return this.resolve(inputPath);\n }\n\n // ── Command helper ─────────────────────────────────────────────────────\n\n private async exec(script: string): Promise<SandboxCommandResult> {\n return this.sandbox.executeCommand('sh', ['-c', script], { timeout: COMMAND_TIMEOUT_MS });\n }\n\n /**\n * Lexical guard catches `..` traversal, but a symlink inside the workdir can\n * still point outside it. After resolving a path that refers to an existing\n * entry, verify its realpath is still contained in the workdir.\n */\n private async assertContainedRealpath(abs: string, inputPath: string): Promise<void> {\n const result = await this.exec(`readlink -f -- ${shellQuote(abs)} 2>/dev/null`);\n const real = result.stdout.trim();\n // If readlink couldn't resolve (path doesn't exist yet), nothing to check.\n if (result.exitCode !== 0 || !real) return;\n const root = posixPath.normalize(this.basePath);\n if (real !== root && !real.startsWith(`${root}/`)) {\n throw new Error(`Path escapes workspace root (symlink): ${inputPath}`);\n }\n }\n\n /**\n * Guard for write destinations. The lexical guard catches `..`, but a symlink\n * inside the workdir can redirect a write outside it. For an existing target\n * we check its realpath; for a not-yet-existing target we check the realpath\n * of its nearest existing ancestor directory, since a symlinked parent is the\n * escape vector (e.g. `link -> /etc` then writing `link/passwd`).\n */\n private async assertContainedDest(abs: string, inputPath: string): Promise<void> {\n // First check the target itself (covers overwriting an existing symlink).\n await this.assertContainedRealpath(abs, inputPath);\n // Then check the parent directory's realpath; readlink -f resolves the\n // nearest existing ancestor when the leaf doesn't exist yet.\n const parent = posixPath.dirname(abs);\n if (parent && parent !== abs) {\n await this.assertContainedRealpath(parent, inputPath);\n }\n }\n\n private async execOk(script: string, context: string): Promise<SandboxCommandResult> {\n const result = await this.exec(script);\n if (result.exitCode !== 0) {\n throw new Error(`${context} failed (exit ${result.exitCode}): ${result.stderr.trim() || result.stdout.trim()}`);\n }\n return result;\n }\n\n // ── File operations ────────────────────────────────────────────────────\n\n async readFile(path: string, options?: ReadOptions): Promise<string | Buffer> {\n const abs = this.resolve(path);\n await this.assertContainedRealpath(abs, path);\n const result = await this.exec(`base64 < ${shellQuote(abs)}`);\n if (result.exitCode !== 0) {\n throw new Error(`File not found: ${path}`);\n }\n const buffer = Buffer.from(result.stdout.replace(/\\s/g, ''), 'base64');\n if (options?.encoding) {\n return buffer.toString(options.encoding);\n }\n return buffer;\n }\n\n async writeFile(path: string, content: FileContent, options?: WriteOptions): Promise<void> {\n const abs = this.resolve(path);\n await this.assertContainedDest(abs, path);\n const b64 = toBuffer(content).toString('base64');\n const dir = posixPath.dirname(abs);\n const mkdir = options?.recursive === false ? '' : `mkdir -p ${shellQuote(dir)} && `;\n if (options?.overwrite === false) {\n const exists = await this.exists(path);\n if (exists) throw new Error(`File already exists: ${path}`);\n }\n await this.execOk(`${mkdir}printf %s ${shellQuote(b64)} | base64 -d > ${shellQuote(abs)}`, `writeFile ${path}`);\n }\n\n async appendFile(path: string, content: FileContent): Promise<void> {\n const abs = this.resolve(path);\n await this.assertContainedDest(abs, path);\n const b64 = toBuffer(content).toString('base64');\n await this.execOk(\n `mkdir -p ${shellQuote(posixPath.dirname(abs))} && printf %s ${shellQuote(b64)} | base64 -d >> ${shellQuote(abs)}`,\n `appendFile ${path}`,\n );\n }\n\n async deleteFile(path: string, options?: RemoveOptions): Promise<void> {\n const abs = this.resolve(path);\n const force = options?.force ? '-f ' : '';\n const result = await this.exec(`rm ${force}${shellQuote(abs)}`);\n if (result.exitCode !== 0 && !options?.force) {\n throw new Error(`File not found: ${path}`);\n }\n }\n\n async copyFile(src: string, dest: string, options?: CopyOptions): Promise<void> {\n const srcAbs = this.resolve(src);\n const destAbs = this.resolve(dest);\n await this.assertContainedRealpath(srcAbs, src);\n await this.assertContainedDest(destAbs, dest);\n const recursive = options?.recursive ? '-r ' : '';\n if (options?.overwrite === false) {\n const exists = await this.exists(dest);\n if (exists) throw new Error(`Destination exists: ${dest}`);\n }\n await this.execOk(`cp ${recursive}${shellQuote(srcAbs)} ${shellQuote(destAbs)}`, `copyFile ${src} -> ${dest}`);\n }\n\n async moveFile(src: string, dest: string, options?: CopyOptions): Promise<void> {\n const srcAbs = this.resolve(src);\n const destAbs = this.resolve(dest);\n await this.assertContainedRealpath(srcAbs, src);\n await this.assertContainedDest(destAbs, dest);\n if (options?.overwrite === false) {\n const exists = await this.exists(dest);\n if (exists) throw new Error(`Destination exists: ${dest}`);\n }\n await this.execOk(`mv ${shellQuote(srcAbs)} ${shellQuote(destAbs)}`, `moveFile ${src} -> ${dest}`);\n }\n\n // ── Directory operations ───────────────────────────────────────────────\n\n async mkdir(path: string, options?: { recursive?: boolean }): Promise<void> {\n const abs = this.resolve(path);\n await this.assertContainedDest(abs, path);\n const flag = options?.recursive === false ? '' : '-p ';\n await this.execOk(`mkdir ${flag}${shellQuote(abs)}`, `mkdir ${path}`);\n }\n\n async rmdir(path: string, options?: RemoveOptions): Promise<void> {\n const abs = this.resolve(path);\n if (options?.recursive) {\n const force = options?.force ? '-f ' : '';\n await this.execOk(`rm -r ${force}${shellQuote(abs)}`, `rmdir ${path}`);\n return;\n }\n const result = await this.exec(`rmdir ${shellQuote(abs)}`);\n if (result.exitCode !== 0 && !options?.force) {\n throw new Error(`Directory not empty or not found: ${path}`);\n }\n }\n\n async readdir(path: string, options?: ListOptions): Promise<FileEntry[]> {\n const abs = this.resolve(path);\n await this.assertContainedRealpath(abs, path);\n if (options?.recursive) {\n // Use find for recursive listings; emit \"type\\tpath\".\n const result = await this.exec(\n `find ${shellQuote(abs)} -mindepth 1 ${options.maxDepth ? `-maxdepth ${Number(options.maxDepth)} ` : ''}-printf '%y\\\\t%p\\\\n' 2>/dev/null`,\n );\n if (result.exitCode !== 0) throw new Error(`Directory not found: ${path}`);\n return this.parseFindOutput(result.stdout, abs, options);\n }\n // Non-recursive: list with name + type via a portable loop.\n const result = await this.exec(\n `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`,\n );\n if (result.exitCode !== 0) throw new Error(`Directory not found: ${path}`);\n return this.parseListOutput(result.stdout, options);\n }\n\n private parseListOutput(stdout: string, options?: ListOptions): FileEntry[] {\n const entries: FileEntry[] = [];\n for (const line of stdout.split('\\n')) {\n if (!line) continue;\n const tab = line.indexOf('\\t');\n if (tab < 0) continue;\n const type = line.slice(0, tab) === 'd' ? 'directory' : 'file';\n const name = line.slice(tab + 1);\n if (!name || name === '.' || name === '..') continue;\n if (type === 'file' && !this.matchesExtension(name, options?.extension)) continue;\n entries.push({ name, type });\n }\n return entries;\n }\n\n private parseFindOutput(stdout: string, base: string, options?: ListOptions): FileEntry[] {\n const entries: FileEntry[] = [];\n for (const line of stdout.split('\\n')) {\n if (!line) continue;\n const tab = line.indexOf('\\t');\n if (tab < 0) continue;\n const type = line.slice(0, tab) === 'd' ? 'directory' : 'file';\n const fullPath = line.slice(tab + 1);\n const name = posixPath.relative(base, fullPath);\n if (!name) continue;\n if (type === 'file' && !this.matchesExtension(name, options?.extension)) continue;\n entries.push({ name, type });\n }\n return entries;\n }\n\n private matchesExtension(name: string, extension?: string | string[]): boolean {\n if (!extension) return true;\n const exts = Array.isArray(extension) ? extension : [extension];\n return exts.some(ext => name.endsWith(ext));\n }\n\n // ── Path / metadata ────────────────────────────────────────────────────\n\n async exists(path: string): Promise<boolean> {\n const abs = this.resolve(path);\n const result = await this.exec(`test -e ${shellQuote(abs)}`);\n return result.exitCode === 0;\n }\n\n async stat(path: string): Promise<FileStat> {\n const abs = this.resolve(path);\n await this.assertContainedRealpath(abs, path);\n // %F=type, %s=size, %X=atime, %Y=mtime (epoch seconds), %W=birth (or -1).\n const result = await this.exec(`stat -c '%F\\\\t%s\\\\t%Y\\\\t%W' ${shellQuote(abs)}`);\n if (result.exitCode !== 0) {\n throw new Error(`Path not found: ${path}`);\n }\n const [kind, sizeStr, mtimeStr, ctimeStr] = result.stdout.trim().split('\\t');\n const type = kind && kind.includes('directory') ? 'directory' : 'file';\n const size = Number(sizeStr) || 0;\n const mtime = Number(mtimeStr) || 0;\n const ctime = Number(ctimeStr);\n return {\n name: posixPath.basename(abs),\n path: `/${posixPath.relative(this.basePath, abs)}`,\n type,\n size: type === 'directory' ? 0 : size,\n modifiedAt: new Date(mtime * 1000),\n createdAt: new Date((ctime > 0 ? ctime : mtime) * 1000),\n };\n }\n\n // ── Lifecycle ──────────────────────────────────────────────────────────\n\n async init(): Promise<void> {\n await this.execOk(`mkdir -p ${shellQuote(this.basePath)}`, 'init workdir');\n }\n\n async destroy(): Promise<void> {\n // The sandbox lifecycle is owned by the caller; nothing to tear down here.\n }\n\n async isReady(): Promise<boolean> {\n const result = await this.exec(`test -d ${shellQuote(this.basePath)}`);\n return result.exitCode === 0;\n }\n\n getInfo(): FilesystemInfo {\n return {\n id: this.id,\n name: this.name,\n provider: this.provider,\n metadata: { basePath: this.basePath, sandboxId: this.sandbox.id },\n };\n }\n\n getInstructions(): string {\n 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.`;\n }\n}\n"],"mappings":"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"}
@@ -1,17 +1,15 @@
1
+ //#region src/agents/sandbox-reattach.ts
1
2
  let reattachFn;
3
+ /** Register the sandbox reattach implementation (called by the web surface at startup). */
2
4
  function registerSandboxReattach(fn) {
3
- reattachFn = fn;
5
+ reattachFn = fn;
4
6
  }
7
+ /** Reattach to an already-provisioned sandbox by provider id. */
5
8
  async function reattachProjectSandbox(providerSandboxId) {
6
- if (!reattachFn) {
7
- throw new Error(
8
- "No sandbox reattach implementation registered. Sandbox-backed workspaces are only available when the web surface has called registerSandboxReattach()."
9
- );
10
- }
11
- return reattachFn(providerSandboxId);
9
+ if (!reattachFn) throw new Error("No sandbox reattach implementation registered. Sandbox-backed workspaces are only available when the web surface has called registerSandboxReattach().");
10
+ return reattachFn(providerSandboxId);
12
11
  }
13
- export {
14
- reattachProjectSandbox,
15
- registerSandboxReattach
16
- };
12
+ //#endregion
13
+ export { reattachProjectSandbox, registerSandboxReattach };
14
+
17
15
  //# sourceMappingURL=sandbox-reattach.js.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/agents/sandbox-reattach.ts"],"sourcesContent":["/**\n * Seam between the core workspace wiring and the web surface's sandbox\n * provisioning. GitHub/cloud-sandbox projects are only reachable through the\n * web tenant server, which owns the sandbox provider factory and its DB-backed\n * bookkeeping. The web package registers its `reattachProjectSandbox`\n * implementation here at startup so `getDynamicWorkspace` can reattach\n * sandboxes without core depending on the web stack.\n */\nimport type { SandboxExec } from './sandbox-filesystem.js';\n\n/** Minimal sandbox surface the workspace seam needs after reattach. */\nexport interface ReattachedSandbox extends SandboxExec {\n start(): Promise<void>;\n}\n\nexport type SandboxReattachFn = (providerSandboxId: string) => Promise<ReattachedSandbox>;\n\nlet reattachFn: SandboxReattachFn | undefined;\n\n/** Register the sandbox reattach implementation (called by the web surface at startup). */\nexport function registerSandboxReattach(fn: SandboxReattachFn): void {\n reattachFn = fn;\n}\n\n/** Reattach to an already-provisioned sandbox by provider id. */\nexport async function reattachProjectSandbox(providerSandboxId: string): Promise<ReattachedSandbox> {\n if (!reattachFn) {\n throw new Error(\n 'No sandbox reattach implementation registered. Sandbox-backed workspaces are only available when the web surface has called registerSandboxReattach().',\n );\n }\n return reattachFn(providerSandboxId);\n}\n"],"mappings":"AAiBA,IAAI;AAGG,SAAS,wBAAwB,IAA6B;AACnE,eAAa;AACf;AAGA,eAAsB,uBAAuB,mBAAuD;AAClG,MAAI,CAAC,YAAY;AACf,UAAM,IAAI;AAAA,MACR;AAAA,IACF;AAAA,EACF;AACA,SAAO,WAAW,iBAAiB;AACrC;","names":[]}
1
+ {"version":3,"file":"sandbox-reattach.js","names":[],"sources":["../../src/agents/sandbox-reattach.ts"],"sourcesContent":["/**\n * Seam between the core workspace wiring and the web surface's sandbox\n * provisioning. GitHub/cloud-sandbox projects are only reachable through the\n * web tenant server, which owns the sandbox provider factory and its DB-backed\n * bookkeeping. The web package registers its `reattachProjectSandbox`\n * implementation here at startup so `getDynamicWorkspace` can reattach\n * sandboxes without core depending on the web stack.\n */\nimport type { SandboxExec } from './sandbox-filesystem.js';\n\n/** Minimal sandbox surface the workspace seam needs after reattach. */\nexport interface ReattachedSandbox extends SandboxExec {\n start(): Promise<void>;\n}\n\nexport type SandboxReattachFn = (providerSandboxId: string) => Promise<ReattachedSandbox>;\n\nlet reattachFn: SandboxReattachFn | undefined;\n\n/** Register the sandbox reattach implementation (called by the web surface at startup). */\nexport function registerSandboxReattach(fn: SandboxReattachFn): void {\n reattachFn = fn;\n}\n\n/** Reattach to an already-provisioned sandbox by provider id. */\nexport async function reattachProjectSandbox(providerSandboxId: string): Promise<ReattachedSandbox> {\n if (!reattachFn) {\n throw new Error(\n 'No sandbox reattach implementation registered. Sandbox-backed workspaces are only available when the web surface has called registerSandboxReattach().',\n );\n }\n return reattachFn(providerSandboxId);\n}\n"],"mappings":";AAiBA,IAAI;;AAGJ,SAAgB,wBAAwB,IAA6B;CACnE,aAAa;AACf;;AAGA,eAAsB,uBAAuB,mBAAuD;CAClG,IAAI,CAAC,YACH,MAAM,IAAI,MACR,wJACF;CAEF,OAAO,WAAW,iBAAiB;AACrC"}
@@ -1,96 +1,107 @@
1
+ //#region src/agents/thread-caveman-state.ts
1
2
  const THREAD_STATE_SETTINGS = [
2
- {
3
- key: "cavemanObservations",
4
- isValid: (value) => typeof value === "boolean",
5
- seedMissingFromCurrentState: true
6
- },
7
- {
8
- key: "observeAttachments",
9
- isValid: (value) => value === "auto" || typeof value === "boolean",
10
- seedMissingFromCurrentState: true
11
- },
12
- {
13
- key: "sandboxAllowedPaths",
14
- isValid: (value) => Array.isArray(value) && value.every((item) => typeof item === "string"),
15
- clearValueWhenMissing: []
16
- }
3
+ {
4
+ key: "cavemanObservations",
5
+ isValid: (value) => typeof value === "boolean",
6
+ seedMissingFromCurrentState: true
7
+ },
8
+ {
9
+ key: "observeAttachments",
10
+ isValid: (value) => value === "auto" || typeof value === "boolean",
11
+ seedMissingFromCurrentState: true
12
+ },
13
+ {
14
+ key: "sandboxAllowedPaths",
15
+ isValid: (value) => Array.isArray(value) && value.every((item) => typeof item === "string"),
16
+ clearValueWhenMissing: []
17
+ }
17
18
  ];
18
19
  function getStateValue(session, setting) {
19
- const value = session.state.get()[setting.key];
20
- return setting.isValid(value) ? value : void 0;
20
+ const value = session.state.get()[setting.key];
21
+ return setting.isValid(value) ? value : void 0;
21
22
  }
22
23
  function stateValuesEqual(left, right) {
23
- if (Array.isArray(left) && Array.isArray(right)) {
24
- return left.length === right.length && left.every((item, index) => item === right[index]);
25
- }
26
- return left === right;
24
+ if (Array.isArray(left) && Array.isArray(right)) return left.length === right.length && left.every((item, index) => item === right[index]);
25
+ return left === right;
27
26
  }
28
27
  async function findThread(session, threadId) {
29
- const threads = await session.thread.list({ allResources: true });
30
- return threads.find((t) => t.id === threadId);
28
+ return (await session.thread.list({ allResources: true })).find((t) => t.id === threadId);
31
29
  }
30
+ /**
31
+ * Restores MastraCode-owned per-thread state for the given thread:
32
+ * - If the thread already has a valid value in metadata, mirror it into controller state.
33
+ * - Otherwise, persist the current session.state value to the thread so future
34
+ * sessions see the user's last-selected setting.
35
+ */
32
36
  async function restoreSettingsForThread(session, threadId) {
33
- const thread = await findThread(session, threadId);
34
- if (session.thread.getId() !== threadId) return;
35
- const updates = {};
36
- const settingsToSeed = [];
37
- for (const setting of THREAD_STATE_SETTINGS) {
38
- const persisted = thread?.metadata?.[setting.key];
39
- if (setting.isValid(persisted)) {
40
- if (getStateValue(session, setting) !== persisted) {
41
- updates[setting.key] = persisted;
42
- }
43
- continue;
44
- }
45
- if (setting.clearValueWhenMissing !== void 0) {
46
- const current = getStateValue(session, setting);
47
- if (current !== void 0 && !stateValuesEqual(current, setting.clearValueWhenMissing)) {
48
- updates[setting.key] = setting.clearValueWhenMissing;
49
- }
50
- continue;
51
- }
52
- if (setting.seedMissingFromCurrentState) {
53
- const current = getStateValue(session, setting);
54
- if (current !== void 0) {
55
- settingsToSeed.push({ key: setting.key, value: current });
56
- }
57
- }
58
- }
59
- if (Object.keys(updates).length > 0) {
60
- if (session.thread.getId() !== threadId) return;
61
- await session.state.set(updates);
62
- }
63
- for (const setting of settingsToSeed) {
64
- if (session.thread.getId() !== threadId) return;
65
- await session.thread.setSetting(setting);
66
- }
37
+ const thread = await findThread(session, threadId);
38
+ if (session.thread.getId() !== threadId) return;
39
+ const updates = {};
40
+ const settingsToSeed = [];
41
+ for (const setting of THREAD_STATE_SETTINGS) {
42
+ const persisted = thread?.metadata?.[setting.key];
43
+ if (setting.isValid(persisted)) {
44
+ if (getStateValue(session, setting) !== persisted) updates[setting.key] = persisted;
45
+ continue;
46
+ }
47
+ if (setting.clearValueWhenMissing !== void 0) {
48
+ const current = getStateValue(session, setting);
49
+ if (current !== void 0 && !stateValuesEqual(current, setting.clearValueWhenMissing)) updates[setting.key] = setting.clearValueWhenMissing;
50
+ continue;
51
+ }
52
+ if (setting.seedMissingFromCurrentState) {
53
+ const current = getStateValue(session, setting);
54
+ if (current !== void 0) settingsToSeed.push({
55
+ key: setting.key,
56
+ value: current
57
+ });
58
+ }
59
+ }
60
+ if (Object.keys(updates).length > 0) {
61
+ if (session.thread.getId() !== threadId) return;
62
+ await session.state.set(updates);
63
+ }
64
+ for (const setting of settingsToSeed) {
65
+ if (session.thread.getId() !== threadId) return;
66
+ await session.thread.setSetting(setting);
67
+ }
67
68
  }
69
+ /**
70
+ * Wires MastraCode-owned state into controller thread events so it persists
71
+ * per-thread and new threads inherit the most recent value.
72
+ *
73
+ * This is intentionally implemented in mastracode rather than core: these
74
+ * settings are mastracode-specific concepts, so persistence stays scoped to
75
+ * the host.
76
+ */
68
77
  function attachOMThreadStatePersistence(session) {
69
- session.subscribe((event) => {
70
- if (event.type === "thread_changed" || event.type === "thread_created") {
71
- const threadId = event.type === "thread_changed" ? event.threadId : event.thread.id;
72
- void restoreSettingsForThread(session, threadId).catch(() => {
73
- });
74
- return;
75
- }
76
- if (event.type === "state_changed") {
77
- for (const setting of THREAD_STATE_SETTINGS) {
78
- if (!event.changedKeys.includes(setting.key)) continue;
79
- const value = event.state[setting.key];
80
- if (!setting.isValid(value)) continue;
81
- void session.thread.setSetting({ key: setting.key, value }).catch(() => {
82
- });
83
- }
84
- }
85
- });
78
+ session.subscribe((event) => {
79
+ if (event.type === "thread_changed" || event.type === "thread_created") {
80
+ restoreSettingsForThread(session, event.type === "thread_changed" ? event.threadId : event.thread.id).catch(() => {});
81
+ return;
82
+ }
83
+ if (event.type === "state_changed") for (const setting of THREAD_STATE_SETTINGS) {
84
+ if (!event.changedKeys.includes(setting.key)) continue;
85
+ const value = event.state[setting.key];
86
+ if (!setting.isValid(value)) continue;
87
+ session.thread.setSetting({
88
+ key: setting.key,
89
+ value
90
+ }).catch(() => {});
91
+ }
92
+ });
86
93
  }
94
+ /**
95
+ * Eagerly restores MastraCode-owned OM settings for the currently-selected
96
+ * thread. Called once at TUI startup after the initial thread is selected,
97
+ * since the subscription set up later misses the startup `thread_changed` event.
98
+ */
87
99
  async function restoreOMThreadStateForCurrentThread(session) {
88
- const threadId = session.thread.getId();
89
- if (!threadId) return;
90
- await restoreSettingsForThread(session, threadId);
100
+ const threadId = session.thread.getId();
101
+ if (!threadId) return;
102
+ await restoreSettingsForThread(session, threadId);
91
103
  }
92
- export {
93
- attachOMThreadStatePersistence,
94
- restoreOMThreadStateForCurrentThread
95
- };
104
+ //#endregion
105
+ export { attachOMThreadStatePersistence, restoreOMThreadStateForCurrentThread };
106
+
96
107
  //# sourceMappingURL=thread-caveman-state.js.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/agents/thread-caveman-state.ts"],"sourcesContent":["import type { AgentControllerThread, Session } from '@mastra/core/agent-controller';\n\ninterface ThreadStateSetting {\n key: string;\n isValid(value: unknown): boolean;\n seedMissingFromCurrentState?: boolean;\n clearValueWhenMissing?: unknown;\n}\n\nconst THREAD_STATE_SETTINGS: ThreadStateSetting[] = [\n {\n key: 'cavemanObservations',\n isValid: (value: unknown): value is boolean => typeof value === 'boolean',\n seedMissingFromCurrentState: true,\n },\n {\n key: 'observeAttachments',\n isValid: (value: unknown): value is 'auto' | boolean => value === 'auto' || typeof value === 'boolean',\n seedMissingFromCurrentState: true,\n },\n {\n key: 'sandboxAllowedPaths',\n isValid: (value: unknown): value is string[] =>\n Array.isArray(value) && value.every(item => typeof item === 'string'),\n clearValueWhenMissing: [],\n },\n];\n\nfunction getStateValue(session: Session<Record<string, unknown>>, setting: ThreadStateSetting): unknown {\n const value = session.state.get()[setting.key];\n return setting.isValid(value) ? value : undefined;\n}\n\nfunction stateValuesEqual(left: unknown, right: unknown): boolean {\n if (Array.isArray(left) && Array.isArray(right)) {\n return left.length === right.length && left.every((item, index) => item === right[index]);\n }\n return left === right;\n}\n\nasync function findThread(\n session: Session<Record<string, unknown>>,\n threadId: string,\n): Promise<AgentControllerThread | undefined> {\n const threads = await session.thread.list({ allResources: true });\n return threads.find(t => t.id === threadId);\n}\n\n/**\n * Restores MastraCode-owned per-thread state for the given thread:\n * - If the thread already has a valid value in metadata, mirror it into controller state.\n * - Otherwise, persist the current session.state value to the thread so future\n * sessions see the user's last-selected setting.\n */\nasync function restoreSettingsForThread(session: Session<Record<string, unknown>>, threadId: string): Promise<void> {\n const thread = await findThread(session, threadId);\n if (session.thread.getId() !== threadId) return;\n\n const updates: Record<string, unknown> = {};\n const settingsToSeed: Array<{ key: string; value: unknown }> = [];\n\n for (const setting of THREAD_STATE_SETTINGS) {\n const persisted = thread?.metadata?.[setting.key];\n\n if (setting.isValid(persisted)) {\n if (getStateValue(session, setting) !== persisted) {\n updates[setting.key] = persisted;\n }\n continue;\n }\n\n if (setting.clearValueWhenMissing !== undefined) {\n const current = getStateValue(session, setting);\n if (current !== undefined && !stateValuesEqual(current, setting.clearValueWhenMissing)) {\n updates[setting.key] = setting.clearValueWhenMissing;\n }\n continue;\n }\n\n if (setting.seedMissingFromCurrentState) {\n const current = getStateValue(session, setting);\n if (current !== undefined) {\n settingsToSeed.push({ key: setting.key, value: current });\n }\n }\n }\n\n if (Object.keys(updates).length > 0) {\n if (session.thread.getId() !== threadId) return;\n await session.state.set(updates);\n }\n\n for (const setting of settingsToSeed) {\n if (session.thread.getId() !== threadId) return;\n await session.thread.setSetting(setting);\n }\n}\n\n/**\n * Wires MastraCode-owned state into controller thread events so it persists\n * per-thread and new threads inherit the most recent value.\n *\n * This is intentionally implemented in mastracode rather than core: these\n * settings are mastracode-specific concepts, so persistence stays scoped to\n * the host.\n */\nexport function attachOMThreadStatePersistence(session: Session<Record<string, unknown>>): void {\n session.subscribe(event => {\n if (event.type === 'thread_changed' || event.type === 'thread_created') {\n const threadId = event.type === 'thread_changed' ? event.threadId : event.thread.id;\n void restoreSettingsForThread(session, threadId).catch(() => {\n // Persistence is best-effort; don't crash the TUI if storage hiccups.\n });\n return;\n }\n\n if (event.type === 'state_changed') {\n for (const setting of THREAD_STATE_SETTINGS) {\n if (!event.changedKeys.includes(setting.key)) continue;\n const value = event.state[setting.key];\n if (!setting.isValid(value)) continue;\n void session.thread.setSetting({ key: setting.key, value }).catch(() => {\n // Persistence is best-effort; don't crash the TUI if storage hiccups.\n });\n }\n }\n });\n}\n\n/**\n * Eagerly restores MastraCode-owned OM settings for the currently-selected\n * thread. Called once at TUI startup after the initial thread is selected,\n * since the subscription set up later misses the startup `thread_changed` event.\n */\nexport async function restoreOMThreadStateForCurrentThread(session: Session<Record<string, unknown>>): Promise<void> {\n const threadId = session.thread.getId();\n if (!threadId) return;\n await restoreSettingsForThread(session, threadId);\n}\n"],"mappings":"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","names":[]}
1
+ {"version":3,"file":"thread-caveman-state.js","names":[],"sources":["../../src/agents/thread-caveman-state.ts"],"sourcesContent":["import type { AgentControllerThread, Session } from '@mastra/core/agent-controller';\n\ninterface ThreadStateSetting {\n key: string;\n isValid(value: unknown): boolean;\n seedMissingFromCurrentState?: boolean;\n clearValueWhenMissing?: unknown;\n}\n\nconst THREAD_STATE_SETTINGS: ThreadStateSetting[] = [\n {\n key: 'cavemanObservations',\n isValid: (value: unknown): value is boolean => typeof value === 'boolean',\n seedMissingFromCurrentState: true,\n },\n {\n key: 'observeAttachments',\n isValid: (value: unknown): value is 'auto' | boolean => value === 'auto' || typeof value === 'boolean',\n seedMissingFromCurrentState: true,\n },\n {\n key: 'sandboxAllowedPaths',\n isValid: (value: unknown): value is string[] =>\n Array.isArray(value) && value.every(item => typeof item === 'string'),\n clearValueWhenMissing: [],\n },\n];\n\nfunction getStateValue(session: Session<Record<string, unknown>>, setting: ThreadStateSetting): unknown {\n const value = session.state.get()[setting.key];\n return setting.isValid(value) ? value : undefined;\n}\n\nfunction stateValuesEqual(left: unknown, right: unknown): boolean {\n if (Array.isArray(left) && Array.isArray(right)) {\n return left.length === right.length && left.every((item, index) => item === right[index]);\n }\n return left === right;\n}\n\nasync function findThread(\n session: Session<Record<string, unknown>>,\n threadId: string,\n): Promise<AgentControllerThread | undefined> {\n const threads = await session.thread.list({ allResources: true });\n return threads.find(t => t.id === threadId);\n}\n\n/**\n * Restores MastraCode-owned per-thread state for the given thread:\n * - If the thread already has a valid value in metadata, mirror it into controller state.\n * - Otherwise, persist the current session.state value to the thread so future\n * sessions see the user's last-selected setting.\n */\nasync function restoreSettingsForThread(session: Session<Record<string, unknown>>, threadId: string): Promise<void> {\n const thread = await findThread(session, threadId);\n if (session.thread.getId() !== threadId) return;\n\n const updates: Record<string, unknown> = {};\n const settingsToSeed: Array<{ key: string; value: unknown }> = [];\n\n for (const setting of THREAD_STATE_SETTINGS) {\n const persisted = thread?.metadata?.[setting.key];\n\n if (setting.isValid(persisted)) {\n if (getStateValue(session, setting) !== persisted) {\n updates[setting.key] = persisted;\n }\n continue;\n }\n\n if (setting.clearValueWhenMissing !== undefined) {\n const current = getStateValue(session, setting);\n if (current !== undefined && !stateValuesEqual(current, setting.clearValueWhenMissing)) {\n updates[setting.key] = setting.clearValueWhenMissing;\n }\n continue;\n }\n\n if (setting.seedMissingFromCurrentState) {\n const current = getStateValue(session, setting);\n if (current !== undefined) {\n settingsToSeed.push({ key: setting.key, value: current });\n }\n }\n }\n\n if (Object.keys(updates).length > 0) {\n if (session.thread.getId() !== threadId) return;\n await session.state.set(updates);\n }\n\n for (const setting of settingsToSeed) {\n if (session.thread.getId() !== threadId) return;\n await session.thread.setSetting(setting);\n }\n}\n\n/**\n * Wires MastraCode-owned state into controller thread events so it persists\n * per-thread and new threads inherit the most recent value.\n *\n * This is intentionally implemented in mastracode rather than core: these\n * settings are mastracode-specific concepts, so persistence stays scoped to\n * the host.\n */\nexport function attachOMThreadStatePersistence(session: Session<Record<string, unknown>>): void {\n session.subscribe(event => {\n if (event.type === 'thread_changed' || event.type === 'thread_created') {\n const threadId = event.type === 'thread_changed' ? event.threadId : event.thread.id;\n void restoreSettingsForThread(session, threadId).catch(() => {\n // Persistence is best-effort; don't crash the TUI if storage hiccups.\n });\n return;\n }\n\n if (event.type === 'state_changed') {\n for (const setting of THREAD_STATE_SETTINGS) {\n if (!event.changedKeys.includes(setting.key)) continue;\n const value = event.state[setting.key];\n if (!setting.isValid(value)) continue;\n void session.thread.setSetting({ key: setting.key, value }).catch(() => {\n // Persistence is best-effort; don't crash the TUI if storage hiccups.\n });\n }\n }\n });\n}\n\n/**\n * Eagerly restores MastraCode-owned OM settings for the currently-selected\n * thread. Called once at TUI startup after the initial thread is selected,\n * since the subscription set up later misses the startup `thread_changed` event.\n */\nexport async function restoreOMThreadStateForCurrentThread(session: Session<Record<string, unknown>>): Promise<void> {\n const threadId = session.thread.getId();\n if (!threadId) return;\n await restoreSettingsForThread(session, threadId);\n}\n"],"mappings":"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"}
@@ -1,105 +1,87 @@
1
- import { WORKSPACE_TOOLS } from "@mastra/core/workspace";
2
- import { MC_TOOLS, TOOL_NAME_OVERRIDES } from "../tool-names.js";
3
1
  import { getLocalPlansRelativeDir, isPlanFilePath } from "../utils/plans.js";
4
- const PLAN_MODE_WRITE_TOOL_NAMES = /* @__PURE__ */ new Set([
5
- WORKSPACE_TOOLS.FILESYSTEM.WRITE_FILE,
6
- WORKSPACE_TOOLS.FILESYSTEM.EDIT_FILE
7
- ]);
2
+ import { MC_TOOLS, TOOL_NAME_OVERRIDES } from "../tool-names.js";
3
+ import { WORKSPACE_TOOLS } from "@mastra/core/workspace";
4
+ //#region src/agents/tool-availability.ts
5
+ const PLAN_MODE_WRITE_TOOL_NAMES = /* @__PURE__ */ new Set([WORKSPACE_TOOLS.FILESYSTEM.WRITE_FILE, WORKSPACE_TOOLS.FILESYSTEM.EDIT_FILE]);
8
6
  function getRequestContextValue(requestContext, key) {
9
- if (!requestContext) return void 0;
10
- if (typeof requestContext.get === "function") {
11
- return requestContext.get(key);
12
- }
13
- return requestContext[key];
7
+ if (!requestContext) return void 0;
8
+ if (typeof requestContext.get === "function") return requestContext.get(key);
9
+ return requestContext[key];
14
10
  }
15
11
  function getHarnessContext(context) {
16
- const requestContext = context?.requestContext;
17
- const harness = getRequestContextValue(requestContext, "harness");
18
- return harness && typeof harness === "object" ? harness : void 0;
12
+ const requestContext = context?.requestContext;
13
+ const harness = getRequestContextValue(requestContext, "harness");
14
+ return harness && typeof harness === "object" ? harness : void 0;
19
15
  }
20
16
  function getHarnessState(harness) {
21
- const state = harness?.session?.state?.get?.();
22
- return state && typeof state === "object" ? state : void 0;
17
+ const state = harness?.session?.state?.get?.();
18
+ return state && typeof state === "object" ? state : void 0;
23
19
  }
24
20
  function getHarnessModeId(harness) {
25
- const modeId = harness?.session?.modeId ?? getHarnessState(harness)?.modeId;
26
- return typeof modeId === "string" ? modeId : void 0;
21
+ const modeId = harness?.session?.modeId ?? getHarnessState(harness)?.modeId;
22
+ return typeof modeId === "string" ? modeId : void 0;
27
23
  }
28
24
  function getHarnessProjectPath(harness) {
29
- const projectPath = getHarnessState(harness)?.projectPath;
30
- return typeof projectPath === "string" ? projectPath : void 0;
25
+ const projectPath = getHarnessState(harness)?.projectPath;
26
+ return typeof projectPath === "string" ? projectPath : void 0;
31
27
  }
32
28
  function getToolInputPath(input) {
33
- const toolPath = input?.path;
34
- return typeof toolPath === "string" ? toolPath : void 0;
29
+ const toolPath = input?.path;
30
+ return typeof toolPath === "string" ? toolPath : void 0;
35
31
  }
36
32
  const PLAN_DIR_HINT = `${getLocalPlansRelativeDir()}/`;
37
33
  function guardPlanModePlanFileWrites({ workspaceToolName, input, context }) {
38
- if (!PLAN_MODE_WRITE_TOOL_NAMES.has(workspaceToolName)) return;
39
- const harness = getHarnessContext(context);
40
- if (getHarnessModeId(harness) !== "plan") return;
41
- const projectPath = getHarnessProjectPath(harness);
42
- const inputPath = getToolInputPath(input);
43
- if (!projectPath || !inputPath) {
44
- return {
45
- proceed: false,
46
- output: `Plan mode can only write plan files inside ${PLAN_DIR_HINT}.`
47
- };
48
- }
49
- if (isPlanFilePath(projectPath, inputPath)) return;
50
- return {
51
- proceed: false,
52
- output: `Plan mode can only write plan files inside ${PLAN_DIR_HINT}. Refusing to edit ${inputPath}.`
53
- };
34
+ if (!PLAN_MODE_WRITE_TOOL_NAMES.has(workspaceToolName)) return;
35
+ const harness = getHarnessContext(context);
36
+ if (getHarnessModeId(harness) !== "plan") return;
37
+ const projectPath = getHarnessProjectPath(harness);
38
+ const inputPath = getToolInputPath(input);
39
+ if (!projectPath || !inputPath) return {
40
+ proceed: false,
41
+ output: `Plan mode can only write plan files inside ${PLAN_DIR_HINT}.`
42
+ };
43
+ if (isPlanFilePath(projectPath, inputPath)) return;
44
+ return {
45
+ proceed: false,
46
+ output: `Plan mode can only write plan files inside ${PLAN_DIR_HINT}. Refusing to edit ${inputPath}.`
47
+ };
54
48
  }
55
49
  const MASTRACODE_WORKSPACE_TOOLS = {
56
- ...TOOL_NAME_OVERRIDES,
57
- hooks: {
58
- beforeToolCall: guardPlanModePlanFileWrites
59
- }
50
+ ...TOOL_NAME_OVERRIDES,
51
+ hooks: { beforeToolCall: guardPlanModePlanFileWrites }
60
52
  };
61
53
  const PLAN_MODE_AVAILABLE_TOOLS = [
62
- // Read-only exploration tools
63
- MC_TOOLS.VIEW,
64
- MC_TOOLS.FIND_FILES,
65
- MC_TOOLS.SEARCH_CONTENT,
66
- MC_TOOLS.FILE_STAT,
67
- MC_TOOLS.LSP_INSPECT,
68
- // Plan file writing. Tool hooks enforce that these can only write `.md`
69
- // files inside `.mastracode/plans/` while the session is in plan mode.
70
- MC_TOOLS.WRITE_FILE,
71
- MC_TOOLS.STRING_REPLACE_LSP,
72
- // Plan delivery tools
73
- "ask_user",
74
- "submit_plan",
75
- // Task tools for plan-stage tracking
76
- "task_write",
77
- "task_update",
78
- "task_complete",
79
- "task_check",
80
- // Notification access
81
- MC_TOOLS.NOTIFICATION_INBOX
54
+ MC_TOOLS.VIEW,
55
+ MC_TOOLS.FIND_FILES,
56
+ MC_TOOLS.SEARCH_CONTENT,
57
+ MC_TOOLS.FILE_STAT,
58
+ MC_TOOLS.LSP_INSPECT,
59
+ MC_TOOLS.WRITE_FILE,
60
+ MC_TOOLS.STRING_REPLACE_LSP,
61
+ "ask_user",
62
+ "submit_plan",
63
+ "task_write",
64
+ "task_update",
65
+ "task_complete",
66
+ "task_check",
67
+ MC_TOOLS.NOTIFICATION_INBOX
82
68
  ];
83
69
  const EXPLORE_MODE_AVAILABLE_TOOLS = [
84
- MC_TOOLS.VIEW,
85
- MC_TOOLS.FIND_FILES,
86
- MC_TOOLS.SEARCH_CONTENT,
87
- MC_TOOLS.FILE_STAT,
88
- MC_TOOLS.LSP_INSPECT,
89
- "ask_user"
70
+ MC_TOOLS.VIEW,
71
+ MC_TOOLS.FIND_FILES,
72
+ MC_TOOLS.SEARCH_CONTENT,
73
+ MC_TOOLS.FILE_STAT,
74
+ MC_TOOLS.LSP_INSPECT,
75
+ "ask_user"
90
76
  ];
91
77
  const GOAL_JUDGE_READONLY_TOOLS = [
92
- MC_TOOLS.VIEW,
93
- MC_TOOLS.SEARCH_CONTENT,
94
- MC_TOOLS.FIND_FILES,
95
- MC_TOOLS.FILE_STAT,
96
- MC_TOOLS.LSP_INSPECT
78
+ MC_TOOLS.VIEW,
79
+ MC_TOOLS.SEARCH_CONTENT,
80
+ MC_TOOLS.FIND_FILES,
81
+ MC_TOOLS.FILE_STAT,
82
+ MC_TOOLS.LSP_INSPECT
97
83
  ];
98
- export {
99
- EXPLORE_MODE_AVAILABLE_TOOLS,
100
- GOAL_JUDGE_READONLY_TOOLS,
101
- MASTRACODE_WORKSPACE_TOOLS,
102
- PLAN_MODE_AVAILABLE_TOOLS,
103
- guardPlanModePlanFileWrites
104
- };
84
+ //#endregion
85
+ export { EXPLORE_MODE_AVAILABLE_TOOLS, GOAL_JUDGE_READONLY_TOOLS, MASTRACODE_WORKSPACE_TOOLS, PLAN_MODE_AVAILABLE_TOOLS, guardPlanModePlanFileWrites };
86
+
105
87
  //# sourceMappingURL=tool-availability.js.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/agents/tool-availability.ts"],"sourcesContent":["import { WORKSPACE_TOOLS } from '@mastra/core/workspace';\nimport type { WorkspaceToolHookContext, WorkspaceToolsConfig } from '@mastra/core/workspace';\nimport { MC_TOOLS, TOOL_NAME_OVERRIDES } from '../tool-names.js';\nimport { getLocalPlansRelativeDir, isPlanFilePath } from '../utils/plans.js';\n\nconst PLAN_MODE_WRITE_TOOL_NAMES = new Set<string>([\n WORKSPACE_TOOLS.FILESYSTEM.WRITE_FILE,\n WORKSPACE_TOOLS.FILESYSTEM.EDIT_FILE,\n]);\n\nfunction getRequestContextValue(requestContext: unknown, key: string): unknown {\n if (!requestContext) return undefined;\n if (typeof (requestContext as { get?: unknown }).get === 'function') {\n return (requestContext as { get: (key: string) => unknown }).get(key);\n }\n return (requestContext as Record<string, unknown>)[key];\n}\n\n/**\n * Subset of the real `HarnessRequestContext` shape (see\n * packages/core/src/harness/types.ts). The mode is a string property\n * (`session.modeId`) and live state is read via `session.state.get()`.\n */\ninterface PlanModeGuardHarness {\n session?: {\n modeId?: unknown;\n state?: { get?: () => unknown };\n };\n}\n\nfunction getHarnessContext(context: unknown): PlanModeGuardHarness | undefined {\n const requestContext = (context as { requestContext?: unknown } | undefined)?.requestContext;\n const harness = getRequestContextValue(requestContext, 'harness');\n return harness && typeof harness === 'object' ? (harness as PlanModeGuardHarness) : undefined;\n}\n\nfunction getHarnessState(harness: PlanModeGuardHarness | undefined): Record<string, unknown> | undefined {\n const state = harness?.session?.state?.get?.();\n return state && typeof state === 'object' ? (state as Record<string, unknown>) : undefined;\n}\n\nfunction getHarnessModeId(harness: PlanModeGuardHarness | undefined): string | undefined {\n const modeId = harness?.session?.modeId ?? getHarnessState(harness)?.modeId;\n return typeof modeId === 'string' ? modeId : undefined;\n}\n\nfunction getHarnessProjectPath(harness: PlanModeGuardHarness | undefined): string | undefined {\n const projectPath = getHarnessState(harness)?.projectPath;\n return typeof projectPath === 'string' ? projectPath : undefined;\n}\n\nfunction getToolInputPath(input: unknown): string | undefined {\n const toolPath = (input as { path?: unknown } | undefined)?.path;\n return typeof toolPath === 'string' ? toolPath : undefined;\n}\n\nconst PLAN_DIR_HINT = `${getLocalPlansRelativeDir()}/`;\n\nexport function guardPlanModePlanFileWrites({ workspaceToolName, input, context }: WorkspaceToolHookContext) {\n if (!PLAN_MODE_WRITE_TOOL_NAMES.has(workspaceToolName)) return;\n\n const harness = getHarnessContext(context);\n if (getHarnessModeId(harness) !== 'plan') return;\n\n const projectPath = getHarnessProjectPath(harness);\n const inputPath = getToolInputPath(input);\n if (!projectPath || !inputPath) {\n return {\n proceed: false as const,\n output: `Plan mode can only write plan files inside ${PLAN_DIR_HINT}.`,\n };\n }\n\n // Plan mode may write any `.md` file directly inside `.mastracode/plans/`, but\n // nothing else in the project.\n if (isPlanFilePath(projectPath, inputPath)) return;\n\n return {\n proceed: false as const,\n output: `Plan mode can only write plan files inside ${PLAN_DIR_HINT}. Refusing to edit ${inputPath}.`,\n };\n}\n\nexport const MASTRACODE_WORKSPACE_TOOLS: WorkspaceToolsConfig = {\n ...TOOL_NAME_OVERRIDES,\n hooks: {\n beforeToolCall: guardPlanModePlanFileWrites,\n },\n};\n\nexport const PLAN_MODE_AVAILABLE_TOOLS: readonly string[] = [\n // Read-only exploration tools\n MC_TOOLS.VIEW,\n MC_TOOLS.FIND_FILES,\n MC_TOOLS.SEARCH_CONTENT,\n MC_TOOLS.FILE_STAT,\n MC_TOOLS.LSP_INSPECT,\n // Plan file writing. Tool hooks enforce that these can only write `.md`\n // files inside `.mastracode/plans/` while the session is in plan mode.\n MC_TOOLS.WRITE_FILE,\n MC_TOOLS.STRING_REPLACE_LSP,\n // Plan delivery tools\n 'ask_user',\n 'submit_plan',\n // Task tools for plan-stage tracking\n 'task_write',\n 'task_update',\n 'task_complete',\n 'task_check',\n // Notification access\n MC_TOOLS.NOTIFICATION_INBOX,\n];\n\nexport const EXPLORE_MODE_AVAILABLE_TOOLS: readonly string[] = [\n MC_TOOLS.VIEW,\n MC_TOOLS.FIND_FILES,\n MC_TOOLS.SEARCH_CONTENT,\n MC_TOOLS.FILE_STAT,\n MC_TOOLS.LSP_INSPECT,\n 'ask_user',\n];\n\nexport const GOAL_JUDGE_READONLY_TOOLS: readonly string[] = [\n MC_TOOLS.VIEW,\n MC_TOOLS.SEARCH_CONTENT,\n MC_TOOLS.FIND_FILES,\n MC_TOOLS.FILE_STAT,\n MC_TOOLS.LSP_INSPECT,\n];\n"],"mappings":"AAAA,SAAS,uBAAuB;AAEhC,SAAS,UAAU,2BAA2B;AAC9C,SAAS,0BAA0B,sBAAsB;AAEzD,MAAM,6BAA6B,oBAAI,IAAY;AAAA,EACjD,gBAAgB,WAAW;AAAA,EAC3B,gBAAgB,WAAW;AAC7B,CAAC;AAED,SAAS,uBAAuB,gBAAyB,KAAsB;AAC7E,MAAI,CAAC,eAAgB,QAAO;AAC5B,MAAI,OAAQ,eAAqC,QAAQ,YAAY;AACnE,WAAQ,eAAqD,IAAI,GAAG;AAAA,EACtE;AACA,SAAQ,eAA2C,GAAG;AACxD;AAcA,SAAS,kBAAkB,SAAoD;AAC7E,QAAM,iBAAkB,SAAsD;AAC9E,QAAM,UAAU,uBAAuB,gBAAgB,SAAS;AAChE,SAAO,WAAW,OAAO,YAAY,WAAY,UAAmC;AACtF;AAEA,SAAS,gBAAgB,SAAgF;AACvG,QAAM,QAAQ,SAAS,SAAS,OAAO,MAAM;AAC7C,SAAO,SAAS,OAAO,UAAU,WAAY,QAAoC;AACnF;AAEA,SAAS,iBAAiB,SAA+D;AACvF,QAAM,SAAS,SAAS,SAAS,UAAU,gBAAgB,OAAO,GAAG;AACrE,SAAO,OAAO,WAAW,WAAW,SAAS;AAC/C;AAEA,SAAS,sBAAsB,SAA+D;AAC5F,QAAM,cAAc,gBAAgB,OAAO,GAAG;AAC9C,SAAO,OAAO,gBAAgB,WAAW,cAAc;AACzD;AAEA,SAAS,iBAAiB,OAAoC;AAC5D,QAAM,WAAY,OAA0C;AAC5D,SAAO,OAAO,aAAa,WAAW,WAAW;AACnD;AAEA,MAAM,gBAAgB,GAAG,yBAAyB,CAAC;AAE5C,SAAS,4BAA4B,EAAE,mBAAmB,OAAO,QAAQ,GAA6B;AAC3G,MAAI,CAAC,2BAA2B,IAAI,iBAAiB,EAAG;AAExD,QAAM,UAAU,kBAAkB,OAAO;AACzC,MAAI,iBAAiB,OAAO,MAAM,OAAQ;AAE1C,QAAM,cAAc,sBAAsB,OAAO;AACjD,QAAM,YAAY,iBAAiB,KAAK;AACxC,MAAI,CAAC,eAAe,CAAC,WAAW;AAC9B,WAAO;AAAA,MACL,SAAS;AAAA,MACT,QAAQ,8CAA8C,aAAa;AAAA,IACrE;AAAA,EACF;AAIA,MAAI,eAAe,aAAa,SAAS,EAAG;AAE5C,SAAO;AAAA,IACL,SAAS;AAAA,IACT,QAAQ,8CAA8C,aAAa,sBAAsB,SAAS;AAAA,EACpG;AACF;AAEO,MAAM,6BAAmD;AAAA,EAC9D,GAAG;AAAA,EACH,OAAO;AAAA,IACL,gBAAgB;AAAA,EAClB;AACF;AAEO,MAAM,4BAA+C;AAAA;AAAA,EAE1D,SAAS;AAAA,EACT,SAAS;AAAA,EACT,SAAS;AAAA,EACT,SAAS;AAAA,EACT,SAAS;AAAA;AAAA;AAAA,EAGT,SAAS;AAAA,EACT,SAAS;AAAA;AAAA,EAET;AAAA,EACA;AAAA;AAAA,EAEA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA;AAAA,EAEA,SAAS;AACX;AAEO,MAAM,+BAAkD;AAAA,EAC7D,SAAS;AAAA,EACT,SAAS;AAAA,EACT,SAAS;AAAA,EACT,SAAS;AAAA,EACT,SAAS;AAAA,EACT;AACF;AAEO,MAAM,4BAA+C;AAAA,EAC1D,SAAS;AAAA,EACT,SAAS;AAAA,EACT,SAAS;AAAA,EACT,SAAS;AAAA,EACT,SAAS;AACX;","names":[]}
1
+ {"version":3,"file":"tool-availability.js","names":[],"sources":["../../src/agents/tool-availability.ts"],"sourcesContent":["import { WORKSPACE_TOOLS } from '@mastra/core/workspace';\nimport type { WorkspaceToolHookContext, WorkspaceToolsConfig } from '@mastra/core/workspace';\nimport { MC_TOOLS, TOOL_NAME_OVERRIDES } from '../tool-names.js';\nimport { getLocalPlansRelativeDir, isPlanFilePath } from '../utils/plans.js';\n\nconst PLAN_MODE_WRITE_TOOL_NAMES = new Set<string>([\n WORKSPACE_TOOLS.FILESYSTEM.WRITE_FILE,\n WORKSPACE_TOOLS.FILESYSTEM.EDIT_FILE,\n]);\n\nfunction getRequestContextValue(requestContext: unknown, key: string): unknown {\n if (!requestContext) return undefined;\n if (typeof (requestContext as { get?: unknown }).get === 'function') {\n return (requestContext as { get: (key: string) => unknown }).get(key);\n }\n return (requestContext as Record<string, unknown>)[key];\n}\n\n/**\n * Subset of the real `HarnessRequestContext` shape (see\n * packages/core/src/harness/types.ts). The mode is a string property\n * (`session.modeId`) and live state is read via `session.state.get()`.\n */\ninterface PlanModeGuardHarness {\n session?: {\n modeId?: unknown;\n state?: { get?: () => unknown };\n };\n}\n\nfunction getHarnessContext(context: unknown): PlanModeGuardHarness | undefined {\n const requestContext = (context as { requestContext?: unknown } | undefined)?.requestContext;\n const harness = getRequestContextValue(requestContext, 'harness');\n return harness && typeof harness === 'object' ? (harness as PlanModeGuardHarness) : undefined;\n}\n\nfunction getHarnessState(harness: PlanModeGuardHarness | undefined): Record<string, unknown> | undefined {\n const state = harness?.session?.state?.get?.();\n return state && typeof state === 'object' ? (state as Record<string, unknown>) : undefined;\n}\n\nfunction getHarnessModeId(harness: PlanModeGuardHarness | undefined): string | undefined {\n const modeId = harness?.session?.modeId ?? getHarnessState(harness)?.modeId;\n return typeof modeId === 'string' ? modeId : undefined;\n}\n\nfunction getHarnessProjectPath(harness: PlanModeGuardHarness | undefined): string | undefined {\n const projectPath = getHarnessState(harness)?.projectPath;\n return typeof projectPath === 'string' ? projectPath : undefined;\n}\n\nfunction getToolInputPath(input: unknown): string | undefined {\n const toolPath = (input as { path?: unknown } | undefined)?.path;\n return typeof toolPath === 'string' ? toolPath : undefined;\n}\n\nconst PLAN_DIR_HINT = `${getLocalPlansRelativeDir()}/`;\n\nexport function guardPlanModePlanFileWrites({ workspaceToolName, input, context }: WorkspaceToolHookContext) {\n if (!PLAN_MODE_WRITE_TOOL_NAMES.has(workspaceToolName)) return;\n\n const harness = getHarnessContext(context);\n if (getHarnessModeId(harness) !== 'plan') return;\n\n const projectPath = getHarnessProjectPath(harness);\n const inputPath = getToolInputPath(input);\n if (!projectPath || !inputPath) {\n return {\n proceed: false as const,\n output: `Plan mode can only write plan files inside ${PLAN_DIR_HINT}.`,\n };\n }\n\n // Plan mode may write any `.md` file directly inside `.mastracode/plans/`, but\n // nothing else in the project.\n if (isPlanFilePath(projectPath, inputPath)) return;\n\n return {\n proceed: false as const,\n output: `Plan mode can only write plan files inside ${PLAN_DIR_HINT}. Refusing to edit ${inputPath}.`,\n };\n}\n\nexport const MASTRACODE_WORKSPACE_TOOLS: WorkspaceToolsConfig = {\n ...TOOL_NAME_OVERRIDES,\n hooks: {\n beforeToolCall: guardPlanModePlanFileWrites,\n },\n};\n\nexport const PLAN_MODE_AVAILABLE_TOOLS: readonly string[] = [\n // Read-only exploration tools\n MC_TOOLS.VIEW,\n MC_TOOLS.FIND_FILES,\n MC_TOOLS.SEARCH_CONTENT,\n MC_TOOLS.FILE_STAT,\n MC_TOOLS.LSP_INSPECT,\n // Plan file writing. Tool hooks enforce that these can only write `.md`\n // files inside `.mastracode/plans/` while the session is in plan mode.\n MC_TOOLS.WRITE_FILE,\n MC_TOOLS.STRING_REPLACE_LSP,\n // Plan delivery tools\n 'ask_user',\n 'submit_plan',\n // Task tools for plan-stage tracking\n 'task_write',\n 'task_update',\n 'task_complete',\n 'task_check',\n // Notification access\n MC_TOOLS.NOTIFICATION_INBOX,\n];\n\nexport const EXPLORE_MODE_AVAILABLE_TOOLS: readonly string[] = [\n MC_TOOLS.VIEW,\n MC_TOOLS.FIND_FILES,\n MC_TOOLS.SEARCH_CONTENT,\n MC_TOOLS.FILE_STAT,\n MC_TOOLS.LSP_INSPECT,\n 'ask_user',\n];\n\nexport const GOAL_JUDGE_READONLY_TOOLS: readonly string[] = [\n MC_TOOLS.VIEW,\n MC_TOOLS.SEARCH_CONTENT,\n MC_TOOLS.FIND_FILES,\n MC_TOOLS.FILE_STAT,\n MC_TOOLS.LSP_INSPECT,\n];\n"],"mappings":";;;;AAKA,MAAM,6CAA6B,IAAI,IAAY,CACjD,gBAAgB,WAAW,YAC3B,gBAAgB,WAAW,SAC7B,CAAC;AAED,SAAS,uBAAuB,gBAAyB,KAAsB;CAC7E,IAAI,CAAC,gBAAgB,OAAO,KAAA;CAC5B,IAAI,OAAQ,eAAqC,QAAQ,YACvD,OAAQ,eAAqD,IAAI,GAAG;CAEtE,OAAQ,eAA2C;AACrD;AAcA,SAAS,kBAAkB,SAAoD;CAC7E,MAAM,iBAAkB,SAAsD;CAC9E,MAAM,UAAU,uBAAuB,gBAAgB,SAAS;CAChE,OAAO,WAAW,OAAO,YAAY,WAAY,UAAmC,KAAA;AACtF;AAEA,SAAS,gBAAgB,SAAgF;CACvG,MAAM,QAAQ,SAAS,SAAS,OAAO,MAAM;CAC7C,OAAO,SAAS,OAAO,UAAU,WAAY,QAAoC,KAAA;AACnF;AAEA,SAAS,iBAAiB,SAA+D;CACvF,MAAM,SAAS,SAAS,SAAS,UAAU,gBAAgB,OAAO,CAAC,EAAE;CACrE,OAAO,OAAO,WAAW,WAAW,SAAS,KAAA;AAC/C;AAEA,SAAS,sBAAsB,SAA+D;CAC5F,MAAM,cAAc,gBAAgB,OAAO,CAAC,EAAE;CAC9C,OAAO,OAAO,gBAAgB,WAAW,cAAc,KAAA;AACzD;AAEA,SAAS,iBAAiB,OAAoC;CAC5D,MAAM,WAAY,OAA0C;CAC5D,OAAO,OAAO,aAAa,WAAW,WAAW,KAAA;AACnD;AAEA,MAAM,gBAAgB,GAAG,yBAAyB,EAAE;AAEpD,SAAgB,4BAA4B,EAAE,mBAAmB,OAAO,WAAqC;CAC3G,IAAI,CAAC,2BAA2B,IAAI,iBAAiB,GAAG;CAExD,MAAM,UAAU,kBAAkB,OAAO;CACzC,IAAI,iBAAiB,OAAO,MAAM,QAAQ;CAE1C,MAAM,cAAc,sBAAsB,OAAO;CACjD,MAAM,YAAY,iBAAiB,KAAK;CACxC,IAAI,CAAC,eAAe,CAAC,WACnB,OAAO;EACL,SAAS;EACT,QAAQ,8CAA8C,cAAc;CACtE;CAKF,IAAI,eAAe,aAAa,SAAS,GAAG;CAE5C,OAAO;EACL,SAAS;EACT,QAAQ,8CAA8C,cAAc,qBAAqB,UAAU;CACrG;AACF;AAEA,MAAa,6BAAmD;CAC9D,GAAG;CACH,OAAO,EACL,gBAAgB,4BAClB;AACF;AAEA,MAAa,4BAA+C;CAE1D,SAAS;CACT,SAAS;CACT,SAAS;CACT,SAAS;CACT,SAAS;CAGT,SAAS;CACT,SAAS;CAET;CACA;CAEA;CACA;CACA;CACA;CAEA,SAAS;AACX;AAEA,MAAa,+BAAkD;CAC7D,SAAS;CACT,SAAS;CACT,SAAS;CACT,SAAS;CACT,SAAS;CACT;AACF;AAEA,MAAa,4BAA+C;CAC1D,SAAS;CACT,SAAS;CACT,SAAS;CACT,SAAS;CACT,SAAS;AACX"}