@mastra/core 1.2.1-alpha.0 → 1.3.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 (403) hide show
  1. package/CHANGELOG.md +341 -0
  2. package/dist/_types/@internal_ai-sdk-v5/dist/index.d.ts +2093 -262
  3. package/dist/agent/agent-legacy.d.ts.map +1 -1
  4. package/dist/agent/agent.d.ts +12 -3
  5. package/dist/agent/agent.d.ts.map +1 -1
  6. package/dist/agent/index.cjs +13 -13
  7. package/dist/agent/index.js +2 -2
  8. package/dist/agent/message-list/index.cjs +18 -18
  9. package/dist/agent/message-list/index.js +1 -1
  10. package/dist/agent/message-list/message-list.d.ts.map +1 -1
  11. package/dist/agent/types.d.ts +6 -1
  12. package/dist/agent/types.d.ts.map +1 -1
  13. package/dist/agent/workflows/prepare-stream/index.d.ts +3 -1
  14. package/dist/agent/workflows/prepare-stream/index.d.ts.map +1 -1
  15. package/dist/agent/workflows/prepare-stream/map-results-step.d.ts.map +1 -1
  16. package/dist/agent/workflows/prepare-stream/stream-step.d.ts +3 -1
  17. package/dist/agent/workflows/prepare-stream/stream-step.d.ts.map +1 -1
  18. package/dist/base.cjs +2 -2
  19. package/dist/base.d.ts +13 -1
  20. package/dist/base.d.ts.map +1 -1
  21. package/dist/base.js +1 -1
  22. package/dist/bundler/index.cjs +2 -2
  23. package/dist/bundler/index.js +1 -1
  24. package/dist/cache/index.cjs +3 -3
  25. package/dist/cache/index.js +1 -1
  26. package/dist/{chunk-ENCTSDWC.js → chunk-2GWTJFVM.js} +2410 -1402
  27. package/dist/chunk-2GWTJFVM.js.map +1 -0
  28. package/dist/{chunk-OB4V67IX.cjs → chunk-2K5PNW2U.cjs} +4 -4
  29. package/dist/{chunk-OB4V67IX.cjs.map → chunk-2K5PNW2U.cjs.map} +1 -1
  30. package/dist/{chunk-VZXYBFCX.cjs → chunk-2P6DD7M5.cjs} +10 -10
  31. package/dist/{chunk-VZXYBFCX.cjs.map → chunk-2P6DD7M5.cjs.map} +1 -1
  32. package/dist/{chunk-Q2D7LERO.cjs → chunk-2VD5OGOT.cjs} +4 -4
  33. package/dist/{chunk-Q2D7LERO.cjs.map → chunk-2VD5OGOT.cjs.map} +1 -1
  34. package/dist/{chunk-2DMSFLJY.cjs → chunk-6TZKQI4R.cjs} +8 -8
  35. package/dist/chunk-6TZKQI4R.cjs.map +1 -0
  36. package/dist/{chunk-4NG7CKEG.js → chunk-6VGCVSP4.js} +3 -3
  37. package/dist/{chunk-4NG7CKEG.js.map → chunk-6VGCVSP4.js.map} +1 -1
  38. package/dist/{chunk-IIZF4W7B.cjs → chunk-7UWHFWST.cjs} +52 -5
  39. package/dist/chunk-7UWHFWST.cjs.map +1 -0
  40. package/dist/{chunk-VW7YQWDW.cjs → chunk-BFIOQFGF.cjs} +28 -2
  41. package/dist/chunk-BFIOQFGF.cjs.map +1 -0
  42. package/dist/{chunk-E3VFKTIA.js → chunk-BXLLXTT4.js} +2245 -179
  43. package/dist/chunk-BXLLXTT4.js.map +1 -0
  44. package/dist/{chunk-HMCXNOF6.cjs → chunk-CGPH7CMG.cjs} +2431 -1417
  45. package/dist/chunk-CGPH7CMG.cjs.map +1 -0
  46. package/dist/{chunk-GVLPTDJA.cjs → chunk-D5JZT6EK.cjs} +236 -63
  47. package/dist/chunk-D5JZT6EK.cjs.map +1 -0
  48. package/dist/{chunk-5SOS47PH.cjs → chunk-FLHFB23E.cjs} +454 -222
  49. package/dist/chunk-FLHFB23E.cjs.map +1 -0
  50. package/dist/{chunk-XCPEEIHI.cjs → chunk-GCTAD6B7.cjs} +3012 -927
  51. package/dist/chunk-GCTAD6B7.cjs.map +1 -0
  52. package/dist/{chunk-W3AQUG66.js → chunk-GIY5BINT.js} +4 -4
  53. package/dist/{chunk-W3AQUG66.js.map → chunk-GIY5BINT.js.map} +1 -1
  54. package/dist/{chunk-M6VFJX5A.js → chunk-GMSAGYTB.js} +3 -3
  55. package/dist/{chunk-M6VFJX5A.js.map → chunk-GMSAGYTB.js.map} +1 -1
  56. package/dist/{chunk-JRJJ5VQE.cjs → chunk-GZD6443M.cjs} +99 -55
  57. package/dist/chunk-GZD6443M.cjs.map +1 -0
  58. package/dist/{chunk-Z6NRYYOH.js → chunk-ILQXPZCD.js} +9 -4
  59. package/dist/chunk-ILQXPZCD.js.map +1 -0
  60. package/dist/{chunk-5YVR7B4R.js → chunk-JQNREL64.js} +29 -9
  61. package/dist/chunk-JQNREL64.js.map +1 -0
  62. package/dist/{chunk-QOFI2WBM.cjs → chunk-JU6K7UDX.cjs} +991 -229
  63. package/dist/chunk-JU6K7UDX.cjs.map +1 -0
  64. package/dist/{chunk-HN2MSTR6.cjs → chunk-KAJNBNWP.cjs} +283 -38
  65. package/dist/chunk-KAJNBNWP.cjs.map +1 -0
  66. package/dist/{chunk-MSWXEOZC.js → chunk-KL2JPSRX.js} +5 -5
  67. package/dist/chunk-KL2JPSRX.js.map +1 -0
  68. package/dist/{chunk-MQB7XFXP.js → chunk-LHRHOPUC.js} +3 -3
  69. package/dist/{chunk-MQB7XFXP.js.map → chunk-LHRHOPUC.js.map} +1 -1
  70. package/dist/{chunk-VX7UA3SO.js → chunk-MOOJ3H3C.js} +974 -217
  71. package/dist/chunk-MOOJ3H3C.js.map +1 -0
  72. package/dist/{chunk-HU2ONA2W.cjs → chunk-ON2KVIUJ.cjs} +17 -17
  73. package/dist/{chunk-HU2ONA2W.cjs.map → chunk-ON2KVIUJ.cjs.map} +1 -1
  74. package/dist/{chunk-I66TMZJ3.cjs → chunk-OOCEAC6U.cjs} +21 -18
  75. package/dist/chunk-OOCEAC6U.cjs.map +1 -0
  76. package/dist/{chunk-MNWW2R3U.js → chunk-OV7OOUUR.js} +90 -54
  77. package/dist/chunk-OV7OOUUR.js.map +1 -0
  78. package/dist/{chunk-C4WWWQHT.cjs → chunk-RO47SMI7.cjs} +23 -3
  79. package/dist/chunk-RO47SMI7.cjs.map +1 -0
  80. package/dist/{chunk-6FG6FU5Y.cjs → chunk-RQ56ZSIR.cjs} +4 -4
  81. package/dist/{chunk-6FG6FU5Y.cjs.map → chunk-RQ56ZSIR.cjs.map} +1 -1
  82. package/dist/{chunk-IW3BNL7A.js → chunk-RS6CZXGA.js} +50 -3
  83. package/dist/chunk-RS6CZXGA.js.map +1 -0
  84. package/dist/{chunk-AIJLACR2.js → chunk-S53FKKVL.js} +27 -3
  85. package/dist/chunk-S53FKKVL.js.map +1 -0
  86. package/dist/{chunk-G6E3QNJC.js → chunk-SBPPGJL6.js} +4078 -4333
  87. package/dist/chunk-SBPPGJL6.js.map +1 -0
  88. package/dist/{chunk-UZL4H5P2.cjs → chunk-SH4PCZ3X.cjs} +5375 -5648
  89. package/dist/chunk-SH4PCZ3X.cjs.map +1 -0
  90. package/dist/{chunk-ZWM2CAIM.js → chunk-STKNQDVA.js} +4 -4
  91. package/dist/{chunk-ZWM2CAIM.js.map → chunk-STKNQDVA.js.map} +1 -1
  92. package/dist/{chunk-A5QFWX67.cjs → chunk-U2CABSMC.cjs} +79 -59
  93. package/dist/chunk-U2CABSMC.cjs.map +1 -0
  94. package/dist/{chunk-7MDVYPWX.cjs → chunk-UE2G2LRP.cjs} +9 -4
  95. package/dist/chunk-UE2G2LRP.cjs.map +1 -0
  96. package/dist/{chunk-AUF6U2BL.js → chunk-VM25PDSW.js} +5 -5
  97. package/dist/{chunk-AUF6U2BL.js.map → chunk-VM25PDSW.js.map} +1 -1
  98. package/dist/{chunk-RXD5EGQF.js → chunk-VVD56FI4.js} +228 -55
  99. package/dist/chunk-VVD56FI4.js.map +1 -0
  100. package/dist/{chunk-JIT2OY3X.js → chunk-WCAFTXGK.js} +23 -3
  101. package/dist/chunk-WCAFTXGK.js.map +1 -0
  102. package/dist/{chunk-P62OJXQ4.js → chunk-WL3AW3YA.js} +282 -37
  103. package/dist/chunk-WL3AW3YA.js.map +1 -0
  104. package/dist/{chunk-YLODOPYM.cjs → chunk-XDD5V446.cjs} +4163 -5352
  105. package/dist/chunk-XDD5V446.cjs.map +1 -0
  106. package/dist/{chunk-B4M33FCS.cjs → chunk-XQVYEOI7.cjs} +7 -7
  107. package/dist/{chunk-B4M33FCS.cjs.map → chunk-XQVYEOI7.cjs.map} +1 -1
  108. package/dist/{chunk-BP2TSCBW.js → chunk-ZATLLPIH.js} +4391 -5561
  109. package/dist/chunk-ZATLLPIH.js.map +1 -0
  110. package/dist/{chunk-T6PRRKMW.js → chunk-ZHFM7HCQ.js} +9 -6
  111. package/dist/chunk-ZHFM7HCQ.js.map +1 -0
  112. package/dist/{chunk-WFUNLRQX.js → chunk-ZRUTE56J.js} +366 -134
  113. package/dist/chunk-ZRUTE56J.js.map +1 -0
  114. package/dist/deployer/index.cjs +2 -2
  115. package/dist/deployer/index.js +1 -1
  116. package/dist/docs/SKILL.md +2 -9
  117. package/dist/docs/assets/SOURCE_MAP.json +399 -342
  118. package/dist/docs/references/docs-agents-agent-memory.md +45 -1
  119. package/dist/docs/references/docs-agents-network-approval.md +1 -1
  120. package/dist/docs/references/docs-agents-networks.md +3 -3
  121. package/dist/docs/references/docs-agents-overview.md +8 -0
  122. package/dist/docs/references/docs-agents-using-tools.md +82 -72
  123. package/dist/docs/references/docs-memory-observational-memory.md +11 -8
  124. package/dist/docs/references/docs-observability-overview.md +1 -1
  125. package/dist/docs/references/docs-observability-tracing-exporters-langsmith.md +70 -0
  126. package/dist/docs/references/docs-observability-tracing-overview.md +1 -1
  127. package/dist/docs/references/docs-server-middleware.md +0 -2
  128. package/dist/docs/references/docs-server-request-context.md +17 -0
  129. package/dist/docs/references/docs-workflows-agents-and-tools.md +2 -2
  130. package/dist/docs/references/docs-workflows-overview.md +1 -1
  131. package/dist/docs/references/docs-workspace-filesystem.md +2 -0
  132. package/dist/docs/references/docs-workspace-overview.md +3 -1
  133. package/dist/docs/references/docs-workspace-sandbox.md +2 -0
  134. package/dist/docs/references/docs-workspace-search.md +2 -0
  135. package/dist/docs/references/docs-workspace-skills.md +3 -1
  136. package/dist/docs/references/reference-agents-getTools.md +1 -6
  137. package/dist/docs/references/reference-agents-listAgents.md +1 -1
  138. package/dist/docs/references/reference-agents-network.md +0 -2
  139. package/dist/docs/references/reference-cli-mastra.md +29 -4
  140. package/dist/docs/references/reference-client-js-agents.md +1 -1
  141. package/dist/docs/references/reference-configuration.md +1 -1
  142. package/dist/docs/references/reference-core-getStoredAgentById.md +2 -2
  143. package/dist/docs/references/reference-core-listStoredAgents.md +1 -1
  144. package/dist/docs/references/reference-memory-observational-memory.md +2 -0
  145. package/dist/docs/references/reference-tools-mcp-client.md +0 -2
  146. package/dist/docs/references/reference-workflows-step.md +2 -0
  147. package/dist/docs/references/reference-workflows-workflow-methods-map.md +2 -2
  148. package/dist/docs/references/reference-workspace-filesystem.md +2 -0
  149. package/dist/docs/references/reference-workspace-local-filesystem.md +2 -0
  150. package/dist/docs/references/reference-workspace-local-sandbox.md +2 -0
  151. package/dist/docs/references/reference-workspace-sandbox.md +2 -0
  152. package/dist/docs/references/reference-workspace-workspace-class.md +2 -0
  153. package/dist/docs/references/reference.md +7 -2
  154. package/dist/editor/index.d.ts +1 -1
  155. package/dist/editor/index.d.ts.map +1 -1
  156. package/dist/editor/types.d.ts +53 -48
  157. package/dist/editor/types.d.ts.map +1 -1
  158. package/dist/evals/base.d.ts +15 -0
  159. package/dist/evals/base.d.ts.map +1 -1
  160. package/dist/evals/index.cjs +20 -20
  161. package/dist/evals/index.js +3 -3
  162. package/dist/evals/run/index.d.ts +3 -3
  163. package/dist/evals/run/index.d.ts.map +1 -1
  164. package/dist/evals/scoreTraces/index.cjs +5 -5
  165. package/dist/evals/scoreTraces/index.js +2 -2
  166. package/dist/features/index.cjs +1 -1
  167. package/dist/features/index.cjs.map +1 -1
  168. package/dist/features/index.d.ts.map +1 -1
  169. package/dist/features/index.js +1 -1
  170. package/dist/features/index.js.map +1 -1
  171. package/dist/index.cjs +4 -4
  172. package/dist/index.d.ts +3 -3
  173. package/dist/index.d.ts.map +1 -1
  174. package/dist/index.js +2 -2
  175. package/dist/integration/index.cjs +2 -2
  176. package/dist/integration/index.js +1 -1
  177. package/dist/llm/index.cjs +16 -16
  178. package/dist/llm/index.js +5 -5
  179. package/dist/llm/model/gateways/constants.d.ts.map +1 -1
  180. package/dist/llm/model/gateways/models-dev.d.ts.map +1 -1
  181. package/dist/llm/model/model.loop.d.ts +1 -1
  182. package/dist/llm/model/model.loop.d.ts.map +1 -1
  183. package/dist/llm/model/provider-types.generated.d.ts +128 -10
  184. package/dist/llm/model/resolve-model.d.ts.map +1 -1
  185. package/dist/loop/index.cjs +12 -12
  186. package/dist/loop/index.js +1 -1
  187. package/dist/loop/network/index.d.ts +3 -3
  188. package/dist/loop/network/index.d.ts.map +1 -1
  189. package/dist/loop/network/run-command-tool.d.ts +1 -1
  190. package/dist/loop/types.d.ts +13 -0
  191. package/dist/loop/types.d.ts.map +1 -1
  192. package/dist/loop/workflows/agentic-execution/index.d.ts +9 -9
  193. package/dist/loop/workflows/agentic-execution/llm-execution-step.d.ts +7 -7
  194. package/dist/loop/workflows/agentic-execution/llm-execution-step.d.ts.map +1 -1
  195. package/dist/loop/workflows/agentic-execution/llm-mapping-step.d.ts +3 -3
  196. package/dist/loop/workflows/agentic-execution/tool-call-step.d.ts.map +1 -1
  197. package/dist/loop/workflows/agentic-loop/index.d.ts +9 -9
  198. package/dist/loop/workflows/schema.d.ts +12 -12
  199. package/dist/mastra/index.cjs +2 -2
  200. package/dist/mastra/index.d.ts +91 -5
  201. package/dist/mastra/index.d.ts.map +1 -1
  202. package/dist/mastra/index.js +1 -1
  203. package/dist/mcp/index.cjs +2 -2
  204. package/dist/mcp/index.js +1 -1
  205. package/dist/memory/index.cjs +14 -14
  206. package/dist/memory/index.js +1 -1
  207. package/dist/memory/mock.d.ts.map +1 -1
  208. package/dist/memory/types.d.ts +128 -0
  209. package/dist/memory/types.d.ts.map +1 -1
  210. package/dist/models-dev-FQVUTQ7L.js +3 -0
  211. package/dist/{models-dev-Z45JSLWD.js.map → models-dev-FQVUTQ7L.js.map} +1 -1
  212. package/dist/models-dev-PPIXUUCU.cjs +12 -0
  213. package/dist/{models-dev-OQKVMEIT.cjs.map → models-dev-PPIXUUCU.cjs.map} +1 -1
  214. package/dist/netlify-4RIKF7Y3.js +3 -0
  215. package/dist/{netlify-SSWMYSAX.js.map → netlify-4RIKF7Y3.js.map} +1 -1
  216. package/dist/netlify-V5F7JEJH.cjs +12 -0
  217. package/dist/{netlify-TXZZCT6N.cjs.map → netlify-V5F7JEJH.cjs.map} +1 -1
  218. package/dist/processors/index.cjs +41 -41
  219. package/dist/processors/index.js +1 -1
  220. package/dist/processors/step-schema.d.ts +44 -44
  221. package/dist/provider-registry-C6XCYX44.cjs +40 -0
  222. package/dist/{provider-registry-NR7FXV2Q.cjs.map → provider-registry-C6XCYX44.cjs.map} +1 -1
  223. package/dist/provider-registry-NWU4YFQW.js +3 -0
  224. package/dist/{provider-registry-RPOTQNHI.js.map → provider-registry-NWU4YFQW.js.map} +1 -1
  225. package/dist/provider-registry.json +278 -33
  226. package/dist/relevance/index.cjs +3 -3
  227. package/dist/relevance/index.js +1 -1
  228. package/dist/server/index.cjs +3 -3
  229. package/dist/server/index.js +1 -1
  230. package/dist/storage/base.d.ts +3 -1
  231. package/dist/storage/base.d.ts.map +1 -1
  232. package/dist/storage/constants.cjs +51 -19
  233. package/dist/storage/constants.d.ts +10 -2
  234. package/dist/storage/constants.d.ts.map +1 -1
  235. package/dist/storage/constants.js +1 -1
  236. package/dist/storage/domains/agents/base.d.ts +13 -164
  237. package/dist/storage/domains/agents/base.d.ts.map +1 -1
  238. package/dist/storage/domains/agents/index.d.ts +0 -1
  239. package/dist/storage/domains/agents/index.d.ts.map +1 -1
  240. package/dist/storage/domains/agents/inmemory.d.ts +6 -10
  241. package/dist/storage/domains/agents/inmemory.d.ts.map +1 -1
  242. package/dist/storage/domains/index.d.ts +3 -0
  243. package/dist/storage/domains/index.d.ts.map +1 -1
  244. package/dist/storage/domains/inmemory-db.d.ts +7 -1
  245. package/dist/storage/domains/inmemory-db.d.ts.map +1 -1
  246. package/dist/storage/domains/memory/base.d.ts +45 -5
  247. package/dist/storage/domains/memory/base.d.ts.map +1 -1
  248. package/dist/storage/domains/memory/inmemory.d.ts +7 -7
  249. package/dist/storage/domains/memory/inmemory.d.ts.map +1 -1
  250. package/dist/storage/domains/observability/types.d.ts +62 -62
  251. package/dist/storage/domains/operations/inmemory.d.ts.map +1 -1
  252. package/dist/storage/domains/prompt-blocks/base.d.ts +47 -0
  253. package/dist/storage/domains/prompt-blocks/base.d.ts.map +1 -0
  254. package/dist/storage/domains/prompt-blocks/index.d.ts +3 -0
  255. package/dist/storage/domains/prompt-blocks/index.d.ts.map +1 -0
  256. package/dist/storage/domains/prompt-blocks/inmemory.d.ts +31 -0
  257. package/dist/storage/domains/prompt-blocks/inmemory.d.ts.map +1 -0
  258. package/dist/storage/domains/scorer-definitions/base.d.ts +47 -0
  259. package/dist/storage/domains/scorer-definitions/base.d.ts.map +1 -0
  260. package/dist/storage/domains/scorer-definitions/index.d.ts +3 -0
  261. package/dist/storage/domains/scorer-definitions/index.d.ts.map +1 -0
  262. package/dist/storage/domains/scorer-definitions/inmemory.d.ts +31 -0
  263. package/dist/storage/domains/scorer-definitions/inmemory.d.ts.map +1 -0
  264. package/dist/storage/domains/versioned.d.ts +136 -0
  265. package/dist/storage/domains/versioned.d.ts.map +1 -0
  266. package/dist/storage/index.cjs +156 -104
  267. package/dist/storage/index.js +2 -2
  268. package/dist/storage/mock.d.ts.map +1 -1
  269. package/dist/storage/types.d.ts +481 -27
  270. package/dist/storage/types.d.ts.map +1 -1
  271. package/dist/stream/MastraAgentNetworkStream.d.ts +1 -1
  272. package/dist/stream/MastraWorkflowStream.d.ts +1 -1
  273. package/dist/stream/RunOutput.d.ts +1 -1
  274. package/dist/stream/RunOutput.d.ts.map +1 -1
  275. package/dist/stream/base/output.d.ts +4 -0
  276. package/dist/stream/base/output.d.ts.map +1 -1
  277. package/dist/stream/index.cjs +11 -11
  278. package/dist/stream/index.js +2 -2
  279. package/dist/test-utils/llm-mock.cjs +4 -4
  280. package/dist/test-utils/llm-mock.js +1 -1
  281. package/dist/tool-loop-agent/index.cjs +4 -4
  282. package/dist/tool-loop-agent/index.js +1 -1
  283. package/dist/tools/index.cjs +4 -4
  284. package/dist/tools/index.js +1 -1
  285. package/dist/tools/is-vercel-tool.cjs +2 -2
  286. package/dist/tools/is-vercel-tool.js +1 -1
  287. package/dist/tools/tool-builder/builder.d.ts.map +1 -1
  288. package/dist/tools/types.d.ts +15 -0
  289. package/dist/tools/types.d.ts.map +1 -1
  290. package/dist/tools/validation.d.ts.map +1 -1
  291. package/dist/tts/index.cjs +2 -2
  292. package/dist/tts/index.js +1 -1
  293. package/dist/types/zod-compat.d.ts +2 -2
  294. package/dist/types/zod-compat.d.ts.map +1 -1
  295. package/dist/utils/zod-utils.d.ts +19 -0
  296. package/dist/utils/zod-utils.d.ts.map +1 -1
  297. package/dist/utils.cjs +28 -28
  298. package/dist/utils.d.ts +7 -1
  299. package/dist/utils.d.ts.map +1 -1
  300. package/dist/utils.js +2 -2
  301. package/dist/vector/index.cjs +9 -9
  302. package/dist/vector/index.js +2 -2
  303. package/dist/voice/index.cjs +6 -6
  304. package/dist/voice/index.js +1 -1
  305. package/dist/workflows/evented/index.cjs +10 -10
  306. package/dist/workflows/evented/index.js +1 -1
  307. package/dist/workflows/evented/step-executor.d.ts.map +1 -1
  308. package/dist/workflows/evented/workflow.d.ts +3 -1
  309. package/dist/workflows/evented/workflow.d.ts.map +1 -1
  310. package/dist/workflows/execution-engine.d.ts.map +1 -1
  311. package/dist/workflows/index.cjs +25 -25
  312. package/dist/workflows/index.js +1 -1
  313. package/dist/workflows/step.d.ts +4 -3
  314. package/dist/workflows/step.d.ts.map +1 -1
  315. package/dist/workflows/types.d.ts +2 -1
  316. package/dist/workflows/types.d.ts.map +1 -1
  317. package/dist/workflows/workflow.d.ts +12 -4
  318. package/dist/workflows/workflow.d.ts.map +1 -1
  319. package/dist/workspace/errors.d.ts +6 -0
  320. package/dist/workspace/errors.d.ts.map +1 -1
  321. package/dist/workspace/filesystem/composite-filesystem.d.ts +93 -0
  322. package/dist/workspace/filesystem/composite-filesystem.d.ts.map +1 -0
  323. package/dist/workspace/filesystem/filesystem.d.ts +35 -0
  324. package/dist/workspace/filesystem/filesystem.d.ts.map +1 -1
  325. package/dist/workspace/filesystem/index.d.ts +2 -0
  326. package/dist/workspace/filesystem/index.d.ts.map +1 -1
  327. package/dist/workspace/filesystem/local-filesystem.d.ts +9 -1
  328. package/dist/workspace/filesystem/local-filesystem.d.ts.map +1 -1
  329. package/dist/workspace/filesystem/mastra-filesystem.d.ts +92 -3
  330. package/dist/workspace/filesystem/mastra-filesystem.d.ts.map +1 -1
  331. package/dist/workspace/filesystem/mount.d.ts +34 -0
  332. package/dist/workspace/filesystem/mount.d.ts.map +1 -0
  333. package/dist/workspace/index.cjs +62 -34
  334. package/dist/workspace/index.d.ts +6 -2
  335. package/dist/workspace/index.d.ts.map +1 -1
  336. package/dist/workspace/index.js +1 -1
  337. package/dist/workspace/lifecycle.d.ts +29 -0
  338. package/dist/workspace/lifecycle.d.ts.map +1 -1
  339. package/dist/workspace/sandbox/errors.d.ts +50 -0
  340. package/dist/workspace/sandbox/errors.d.ts.map +1 -0
  341. package/dist/workspace/sandbox/index.d.ts +3 -0
  342. package/dist/workspace/sandbox/index.d.ts.map +1 -1
  343. package/dist/workspace/sandbox/local-sandbox.d.ts +17 -2
  344. package/dist/workspace/sandbox/local-sandbox.d.ts.map +1 -1
  345. package/dist/workspace/sandbox/mastra-sandbox.d.ts +153 -6
  346. package/dist/workspace/sandbox/mastra-sandbox.d.ts.map +1 -1
  347. package/dist/workspace/sandbox/mount-manager.d.ts +195 -0
  348. package/dist/workspace/sandbox/mount-manager.d.ts.map +1 -0
  349. package/dist/workspace/sandbox/sandbox.d.ts +37 -84
  350. package/dist/workspace/sandbox/sandbox.d.ts.map +1 -1
  351. package/dist/workspace/sandbox/types.d.ts +92 -0
  352. package/dist/workspace/sandbox/types.d.ts.map +1 -0
  353. package/dist/workspace/tools/tools.d.ts.map +1 -1
  354. package/dist/workspace/tools/tree-formatter.d.ts.map +1 -1
  355. package/dist/workspace/workspace.d.ts +73 -3
  356. package/dist/workspace/workspace.d.ts.map +1 -1
  357. package/package.json +16 -16
  358. package/src/llm/model/provider-types.generated.d.ts +128 -10
  359. package/dist/chunk-2DMSFLJY.cjs.map +0 -1
  360. package/dist/chunk-5SOS47PH.cjs.map +0 -1
  361. package/dist/chunk-5YVR7B4R.js.map +0 -1
  362. package/dist/chunk-7MDVYPWX.cjs.map +0 -1
  363. package/dist/chunk-A5QFWX67.cjs.map +0 -1
  364. package/dist/chunk-AIJLACR2.js.map +0 -1
  365. package/dist/chunk-BP2TSCBW.js.map +0 -1
  366. package/dist/chunk-C4WWWQHT.cjs.map +0 -1
  367. package/dist/chunk-E3VFKTIA.js.map +0 -1
  368. package/dist/chunk-ENCTSDWC.js.map +0 -1
  369. package/dist/chunk-G6E3QNJC.js.map +0 -1
  370. package/dist/chunk-GVLPTDJA.cjs.map +0 -1
  371. package/dist/chunk-HMCXNOF6.cjs.map +0 -1
  372. package/dist/chunk-HN2MSTR6.cjs.map +0 -1
  373. package/dist/chunk-I66TMZJ3.cjs.map +0 -1
  374. package/dist/chunk-IIZF4W7B.cjs.map +0 -1
  375. package/dist/chunk-IW3BNL7A.js.map +0 -1
  376. package/dist/chunk-JIT2OY3X.js.map +0 -1
  377. package/dist/chunk-JRJJ5VQE.cjs.map +0 -1
  378. package/dist/chunk-MNWW2R3U.js.map +0 -1
  379. package/dist/chunk-MSWXEOZC.js.map +0 -1
  380. package/dist/chunk-P62OJXQ4.js.map +0 -1
  381. package/dist/chunk-QOFI2WBM.cjs.map +0 -1
  382. package/dist/chunk-RXD5EGQF.js.map +0 -1
  383. package/dist/chunk-T6PRRKMW.js.map +0 -1
  384. package/dist/chunk-UZL4H5P2.cjs.map +0 -1
  385. package/dist/chunk-VW7YQWDW.cjs.map +0 -1
  386. package/dist/chunk-VX7UA3SO.js.map +0 -1
  387. package/dist/chunk-WFUNLRQX.js.map +0 -1
  388. package/dist/chunk-XCPEEIHI.cjs.map +0 -1
  389. package/dist/chunk-YLODOPYM.cjs.map +0 -1
  390. package/dist/chunk-Z6NRYYOH.js.map +0 -1
  391. package/dist/docs/references/docs-tools-mcp-advanced-usage.md +0 -143
  392. package/dist/docs/references/docs-tools-mcp-mcp-overview.md +0 -383
  393. package/dist/docs/references/docs-tools-mcp-overview.md +0 -78
  394. package/dist/docs/references/docs-workflows-input-data-mapping.md +0 -102
  395. package/dist/docs/references/reference-deployer-netlify.md +0 -14
  396. package/dist/docs/references/reference-deployer-vercel.md +0 -39
  397. package/dist/docs/references/reference-tools-client.md +0 -228
  398. package/dist/models-dev-OQKVMEIT.cjs +0 -12
  399. package/dist/models-dev-Z45JSLWD.js +0 -3
  400. package/dist/netlify-SSWMYSAX.js +0 -3
  401. package/dist/netlify-TXZZCT6N.cjs +0 -12
  402. package/dist/provider-registry-NR7FXV2Q.cjs +0 -40
  403. package/dist/provider-registry-RPOTQNHI.js +0 -3
@@ -1,14 +1,14 @@
1
1
  'use strict';
2
2
 
3
- var chunkIIZF4W7B_cjs = require('./chunk-IIZF4W7B.cjs');
4
- var chunkC4WWWQHT_cjs = require('./chunk-C4WWWQHT.cjs');
3
+ var chunk7UWHFWST_cjs = require('./chunk-7UWHFWST.cjs');
4
+ var chunkRO47SMI7_cjs = require('./chunk-RO47SMI7.cjs');
5
5
  var chunk7XAECHYL_cjs = require('./chunk-7XAECHYL.cjs');
6
+ var fs = require('fs');
6
7
  var fs2 = require('fs/promises');
7
8
  var nodePath = require('path');
8
- var fs = require('fs');
9
+ var crypto = require('crypto');
9
10
  var matter = require('gray-matter');
10
11
  var childProcess = require('child_process');
11
- var crypto = require('crypto');
12
12
  var os = require('os');
13
13
  var zod = require('zod');
14
14
 
@@ -34,9 +34,9 @@ function _interopNamespace(e) {
34
34
 
35
35
  var fs2__namespace = /*#__PURE__*/_interopNamespace(fs2);
36
36
  var nodePath__namespace = /*#__PURE__*/_interopNamespace(nodePath);
37
+ var crypto__namespace = /*#__PURE__*/_interopNamespace(crypto);
37
38
  var matter__default = /*#__PURE__*/_interopDefault(matter);
38
39
  var childProcess__namespace = /*#__PURE__*/_interopNamespace(childProcess);
39
- var crypto__namespace = /*#__PURE__*/_interopNamespace(crypto);
40
40
  var os__namespace = /*#__PURE__*/_interopNamespace(os);
41
41
 
42
42
  // src/workspace/errors.ts
@@ -141,1495 +141,2521 @@ var FileReadRequiredError = class extends FilesystemError {
141
141
  this.name = "FileReadRequiredError";
142
142
  }
143
143
  };
144
-
145
- // src/workspace/filesystem/mastra-filesystem.ts
146
- var MastraFilesystem = class extends chunkC4WWWQHT_cjs.MastraBase {
147
- constructor(options) {
148
- super({ name: options.name, component: chunk7XAECHYL_cjs.RegisteredLogger.WORKSPACE });
149
- }
150
- };
151
-
152
- // src/workspace/sandbox/mastra-sandbox.ts
153
- var MastraSandbox = class extends chunkC4WWWQHT_cjs.MastraBase {
154
- constructor(options) {
155
- super({ name: options.name, component: chunk7XAECHYL_cjs.RegisteredLogger.WORKSPACE });
144
+ var FilesystemNotReadyError = class extends FilesystemError {
145
+ constructor(id) {
146
+ super(`Filesystem "${id}" is not ready. Call init() first or use ensureReady().`, "ENOTREADY", id);
147
+ this.name = "FilesystemNotReadyError";
156
148
  }
157
149
  };
158
150
 
159
- // src/workspace/line-utils.ts
160
- function extractLines(content, startLine, endLine) {
161
- const allLines = content.split("\n");
162
- const totalLines = allLines.length;
163
- const start = Math.max(1, startLine ?? 1);
164
- const end = Math.min(totalLines, endLine ?? totalLines);
165
- const extractedLines = allLines.slice(start - 1, end);
166
- return {
167
- content: extractedLines.join("\n"),
168
- lines: { start, end },
169
- totalLines
170
- };
171
- }
172
- function extractLinesWithLimit(content, offset, limit) {
173
- const startLine = offset ?? 1;
174
- const endLine = limit ? startLine + limit - 1 : void 0;
175
- return extractLines(content, startLine, endLine);
176
- }
177
- function formatWithLineNumbers(content, startLineNumber = 1) {
178
- const lines = content.split("\n");
179
- const maxLineNum = startLineNumber + lines.length - 1;
180
- const padWidth = Math.max(6, String(maxLineNum).length + 1);
181
- return lines.map((line, i) => {
182
- const lineNum = startLineNumber + i;
183
- return `${String(lineNum).padStart(padWidth)}\u2192${line}`;
184
- }).join("\n");
185
- }
186
- function countOccurrences(content, searchString) {
187
- if (!searchString) return 0;
188
- let count = 0;
189
- let position = 0;
190
- while ((position = content.indexOf(searchString, position)) !== -1) {
191
- count++;
192
- position += searchString.length;
193
- }
194
- return count;
195
- }
196
- function replaceString(content, oldString, newString, replaceAll = false) {
197
- const count = countOccurrences(content, oldString);
198
- if (count === 0) {
199
- throw new StringNotFoundError(oldString);
200
- }
201
- if (!replaceAll && count > 1) {
202
- throw new StringNotUniqueError(oldString, count);
203
- }
204
- if (replaceAll) {
205
- const result = content.split(oldString).join(newString);
206
- return { content: result, replacements: count };
151
+ // src/workspace/lifecycle.ts
152
+ async function callLifecycle(provider, method) {
153
+ const wrapped = `_${method}`;
154
+ const wrappedFn = provider[wrapped];
155
+ if (typeof wrappedFn === "function") {
156
+ await wrappedFn.call(provider);
207
157
  } else {
208
- const result = content.replace(oldString, newString);
209
- return { content: result, replacements: 1 };
158
+ const plainFn = provider[method];
159
+ if (typeof plainFn === "function") {
160
+ await plainFn.call(provider);
161
+ }
210
162
  }
211
163
  }
212
- var StringNotFoundError = class extends Error {
213
- constructor(searchString) {
214
- super(`The specified text was not found. Make sure you use the exact text from the file.`);
215
- this.searchString = searchString;
216
- this.name = "StringNotFoundError";
217
- }
218
- };
219
- var StringNotUniqueError = class extends Error {
220
- constructor(searchString, occurrences) {
221
- super(
222
- `The specified text appears ${occurrences} times. Provide more surrounding context to make the match unique, or use replace_all to replace all occurrences.`
223
- );
224
- this.searchString = searchString;
225
- this.occurrences = occurrences;
226
- this.name = "StringNotUniqueError";
227
- }
228
- };
229
164
 
230
- // src/workspace/search/bm25.ts
231
- var DEFAULT_STOPWORDS = /* @__PURE__ */ new Set([
232
- "a",
233
- "an",
234
- "and",
235
- "are",
236
- "as",
237
- "at",
238
- "be",
239
- "by",
240
- "for",
241
- "from",
242
- "has",
243
- "he",
244
- "in",
245
- "is",
246
- "it",
247
- "its",
248
- "of",
249
- "on",
250
- "or",
251
- "that",
252
- "the",
253
- "to",
254
- "was",
255
- "were",
256
- "will",
257
- "with"
258
- ]);
259
- var DEFAULT_TOKENIZE_OPTIONS = {
260
- lowercase: true,
261
- removePunctuation: true,
262
- minLength: 2,
263
- stopwords: DEFAULT_STOPWORDS,
264
- splitPattern: /\s+/
265
- };
266
- function tokenize(text, options = {}) {
267
- const opts = { ...DEFAULT_TOKENIZE_OPTIONS, ...options };
268
- let processed = text;
269
- if (opts.lowercase) {
270
- processed = processed.toLowerCase();
271
- }
272
- if (opts.removePunctuation) {
273
- processed = processed.replace(/[^\w\s]/g, " ");
274
- }
275
- const tokens = processed.split(opts.splitPattern).filter((token) => {
276
- if (token.length < opts.minLength) {
277
- return false;
278
- }
279
- if (opts.stopwords?.has(token)) {
280
- return false;
281
- }
282
- return true;
283
- });
284
- return tokens;
285
- }
286
- function findLineRange(content, queryTerms, options = {}) {
287
- if (queryTerms.length === 0) return void 0;
288
- const lines = content.split("\n");
289
- const defaultOpts = { lowercase: true, removePunctuation: true, minLength: 2 };
290
- const opts = { ...defaultOpts, ...options };
291
- const normalizedTerms = new Set(queryTerms.map((t) => opts.lowercase ? t.toLowerCase() : t));
292
- let firstMatchLine;
293
- let lastMatchLine;
294
- for (let i = 0; i < lines.length; i++) {
295
- const lineTokens = tokenize(lines[i], options);
296
- for (const token of lineTokens) {
297
- if (normalizedTerms.has(token)) {
298
- const lineNum = i + 1;
299
- if (firstMatchLine === void 0) {
300
- firstMatchLine = lineNum;
165
+ // src/workspace/filesystem/composite-filesystem.ts
166
+ var CompositeFilesystem = class {
167
+ id;
168
+ name = "CompositeFilesystem";
169
+ provider = "composite";
170
+ status = "ready";
171
+ _mounts;
172
+ constructor(config) {
173
+ this.id = `cfs-${Date.now().toString(36)}`;
174
+ this._mounts = /* @__PURE__ */ new Map();
175
+ for (const [path4, fs5] of Object.entries(config.mounts)) {
176
+ const normalized = this.normalizePath(path4);
177
+ this._mounts.set(normalized, fs5);
178
+ }
179
+ if (this._mounts.size === 0) {
180
+ throw new Error("CompositeFilesystem requires at least one mount");
181
+ }
182
+ const mountPaths = [...this._mounts.keys()];
183
+ for (const a of mountPaths) {
184
+ for (const b of mountPaths) {
185
+ if (a !== b && b.startsWith(a + "/")) {
186
+ throw new Error(`Nested mount paths are not supported: "${b}" is nested under "${a}"`);
301
187
  }
302
- lastMatchLine = lineNum;
303
- break;
304
188
  }
305
189
  }
306
190
  }
307
- if (firstMatchLine !== void 0 && lastMatchLine !== void 0) {
308
- return { start: firstMatchLine, end: lastMatchLine };
309
- }
310
- return void 0;
311
- }
312
- function computeTermFrequencies(tokens) {
313
- const frequencies = /* @__PURE__ */ new Map();
314
- for (const token of tokens) {
315
- frequencies.set(token, (frequencies.get(token) || 0) + 1);
191
+ /**
192
+ * Get all mount paths.
193
+ */
194
+ get mountPaths() {
195
+ return Array.from(this._mounts.keys());
316
196
  }
317
- return frequencies;
318
- }
319
- var BM25Index = class _BM25Index {
320
- /** BM25 k1 parameter */
321
- k1;
322
- /** BM25 b parameter */
323
- b;
324
- /** Documents in the index */
325
- #documents = /* @__PURE__ */ new Map();
326
- /** Inverted index: term -> document IDs containing the term */
327
- #invertedIndex = /* @__PURE__ */ new Map();
328
- /** Document frequency: term -> number of documents containing the term */
329
- #documentFrequency = /* @__PURE__ */ new Map();
330
- /** Average document length */
331
- #avgDocLength = 0;
332
- /** Total number of documents */
333
- #docCount = 0;
334
- /** Tokenization options */
335
- #tokenizeOptions;
336
- constructor(config = {}, tokenizeOptions = {}) {
337
- this.k1 = config.k1 ?? 1.5;
338
- this.b = config.b ?? 0.75;
339
- this.#tokenizeOptions = tokenizeOptions;
197
+ /**
198
+ * Get the mounts map.
199
+ */
200
+ get mounts() {
201
+ return this._mounts;
340
202
  }
341
203
  /**
342
- * Add a document to the index
204
+ * Get the underlying filesystem for a given path.
205
+ * Returns undefined if the path doesn't resolve to any mount.
343
206
  */
344
- add(id, content, metadata) {
345
- if (this.#documents.has(id)) {
346
- this.remove(id);
347
- }
348
- const tokens = tokenize(content, this.#tokenizeOptions);
349
- const termFrequencies = computeTermFrequencies(tokens);
350
- const doc = {
351
- id,
352
- content,
353
- tokens,
354
- termFrequencies,
355
- length: tokens.length,
356
- metadata
357
- };
358
- this.#documents.set(id, doc);
359
- this.#docCount++;
360
- for (const term of termFrequencies.keys()) {
361
- if (!this.#invertedIndex.has(term)) {
362
- this.#invertedIndex.set(term, /* @__PURE__ */ new Set());
363
- }
364
- this.#invertedIndex.get(term).add(id);
365
- this.#documentFrequency.set(term, (this.#documentFrequency.get(term) || 0) + 1);
366
- }
367
- this.#updateAvgDocLength();
207
+ getFilesystemForPath(path4) {
208
+ const resolved = this.resolveMount(path4);
209
+ return resolved?.fs;
368
210
  }
369
211
  /**
370
- * Remove a document from the index
212
+ * Get the mount path for a given path.
213
+ * Returns undefined if the path doesn't resolve to any mount.
371
214
  */
372
- remove(id) {
373
- const doc = this.#documents.get(id);
374
- if (!doc) {
375
- return false;
215
+ getMountPathForPath(path4) {
216
+ const resolved = this.resolveMount(path4);
217
+ return resolved?.mountPath;
218
+ }
219
+ normalizePath(path4) {
220
+ if (!path4 || path4 === "/") return "/";
221
+ let n = path4.startsWith("/") ? path4 : `/${path4}`;
222
+ if (n.length > 1 && n.endsWith("/")) n = n.slice(0, -1);
223
+ return n;
224
+ }
225
+ resolveMount(path4) {
226
+ const normalized = this.normalizePath(path4);
227
+ let best = null;
228
+ for (const [mountPath, fs5] of this._mounts) {
229
+ if (normalized === mountPath || normalized.startsWith(mountPath + "/")) {
230
+ if (!best || mountPath.length > best.mountPath.length) {
231
+ best = { mountPath, fs: fs5 };
232
+ }
233
+ }
376
234
  }
377
- for (const term of doc.termFrequencies.keys()) {
378
- const docIds = this.#invertedIndex.get(term);
379
- if (docIds) {
380
- docIds.delete(id);
381
- if (docIds.size === 0) {
382
- this.#invertedIndex.delete(term);
383
- this.#documentFrequency.delete(term);
384
- } else {
385
- this.#documentFrequency.set(term, (this.#documentFrequency.get(term) || 1) - 1);
235
+ if (!best) return null;
236
+ let fsPath = normalized.slice(best.mountPath.length);
237
+ if (!fsPath) fsPath = "/";
238
+ if (!fsPath.startsWith("/")) fsPath = "/" + fsPath;
239
+ return { fs: best.fs, fsPath, mountPath: best.mountPath };
240
+ }
241
+ getVirtualEntries(path4) {
242
+ const normalized = this.normalizePath(path4);
243
+ if (this.resolveMount(normalized)) return null;
244
+ const entriesMap = /* @__PURE__ */ new Map();
245
+ for (const [mountPath, fs5] of this._mounts.entries()) {
246
+ const isUnder = normalized === "/" ? mountPath.startsWith("/") : mountPath.startsWith(normalized + "/");
247
+ if (isUnder) {
248
+ const remaining = normalized === "/" ? mountPath.slice(1) : mountPath.slice(normalized.length + 1);
249
+ const next = remaining.split("/")[0];
250
+ if (next && !entriesMap.has(next)) {
251
+ const isDirectMount = remaining === next;
252
+ const entry = { name: next, type: "directory" };
253
+ if (isDirectMount) {
254
+ entry.mount = {
255
+ provider: fs5.provider,
256
+ icon: fs5.icon,
257
+ displayName: fs5.displayName,
258
+ description: fs5.description,
259
+ status: fs5.status,
260
+ error: fs5.error
261
+ };
262
+ }
263
+ entriesMap.set(next, entry);
386
264
  }
387
265
  }
388
266
  }
389
- this.#documents.delete(id);
390
- this.#docCount--;
391
- this.#updateAvgDocLength();
392
- return true;
267
+ return entriesMap.size > 0 ? Array.from(entriesMap.values()) : null;
393
268
  }
394
- /**
395
- * Clear all documents from the index
396
- */
397
- clear() {
398
- this.#documents.clear();
399
- this.#invertedIndex.clear();
400
- this.#documentFrequency.clear();
401
- this.#docCount = 0;
402
- this.#avgDocLength = 0;
269
+ isVirtualPath(path4) {
270
+ const normalized = this.normalizePath(path4);
271
+ if (normalized === "/" && !this._mounts.has("/")) return true;
272
+ for (const mountPath of this._mounts.keys()) {
273
+ if (mountPath.startsWith(normalized + "/")) return true;
274
+ }
275
+ return false;
403
276
  }
404
277
  /**
405
- * Search for documents matching the query
278
+ * Assert that a filesystem is writable (not read-only).
279
+ * @throws {PermissionError} if the filesystem is read-only
406
280
  */
407
- search(query, topK = 10, minScore = 0) {
408
- const queryTokens = tokenize(query, this.#tokenizeOptions);
409
- if (queryTokens.length === 0 || this.#docCount === 0) {
410
- return [];
281
+ assertWritable(fs5, path4, operation) {
282
+ if (fs5.readOnly) {
283
+ throw new PermissionError(path4, `${operation} (filesystem is read-only)`);
411
284
  }
412
- const scores = /* @__PURE__ */ new Map();
413
- for (const queryTerm of queryTokens) {
414
- const docIds = this.#invertedIndex.get(queryTerm);
415
- if (!docIds) {
416
- continue;
417
- }
418
- const df = this.#documentFrequency.get(queryTerm) || 0;
419
- const idf = this.#computeIDF(df);
420
- for (const docId of docIds) {
421
- const doc = this.#documents.get(docId);
422
- const tf = doc.termFrequencies.get(queryTerm) || 0;
423
- const termScore = this.#computeTermScore(tf, doc.length, idf);
424
- scores.set(docId, (scores.get(docId) || 0) + termScore);
285
+ }
286
+ // ===========================================================================
287
+ // WorkspaceFilesystem Implementation
288
+ // ===========================================================================
289
+ async init() {
290
+ this.status = "initializing";
291
+ for (const [mountPath, fs5] of this._mounts.entries()) {
292
+ try {
293
+ await callLifecycle(fs5, "init");
294
+ } catch (e) {
295
+ const message = e instanceof Error ? e.message : String(e);
296
+ console.warn(`[CompositeFilesystem] Mount "${mountPath}" failed to initialize: ${message}`);
425
297
  }
426
298
  }
427
- const results = [];
428
- for (const [docId, score] of scores.entries()) {
429
- if (score >= minScore) {
430
- const doc = this.#documents.get(docId);
431
- results.push({
432
- id: docId,
433
- content: doc.content,
434
- score,
435
- metadata: doc.metadata
436
- });
299
+ this.status = "ready";
300
+ }
301
+ async destroy() {
302
+ this.status = "destroying";
303
+ const errors = [];
304
+ for (const fs5 of this._mounts.values()) {
305
+ try {
306
+ await callLifecycle(fs5, "destroy");
307
+ } catch (e) {
308
+ errors.push(e instanceof Error ? e : new Error(String(e)));
437
309
  }
438
310
  }
439
- results.sort((a, b) => b.score - a.score);
440
- return results.slice(0, topK);
311
+ if (errors.length > 0) {
312
+ this.status = "error";
313
+ throw new AggregateError(errors, "Some filesystems failed to destroy");
314
+ }
315
+ this.status = "destroyed";
441
316
  }
442
- /**
443
- * Get a document by ID
444
- */
445
- get(id) {
446
- return this.#documents.get(id);
317
+ async readFile(path4, options) {
318
+ const r = this.resolveMount(path4);
319
+ if (!r) throw new Error(`No mount for path: ${path4}`);
320
+ return r.fs.readFile(r.fsPath, options);
447
321
  }
448
- /**
449
- * Check if a document exists in the index
450
- */
451
- has(id) {
452
- return this.#documents.has(id);
322
+ async writeFile(path4, content, options) {
323
+ const r = this.resolveMount(path4);
324
+ if (!r) throw new Error(`No mount for path: ${path4}`);
325
+ this.assertWritable(r.fs, path4, "writeFile");
326
+ return r.fs.writeFile(r.fsPath, content, options);
453
327
  }
454
- /**
455
- * Get the number of documents in the index
456
- */
457
- get size() {
458
- return this.#docCount;
328
+ async appendFile(path4, content) {
329
+ const r = this.resolveMount(path4);
330
+ if (!r) throw new Error(`No mount for path: ${path4}`);
331
+ this.assertWritable(r.fs, path4, "appendFile");
332
+ return r.fs.appendFile(r.fsPath, content);
459
333
  }
460
- /**
461
- * Get all document IDs
462
- */
463
- get documentIds() {
464
- return Array.from(this.#documents.keys());
334
+ async deleteFile(path4, options) {
335
+ const r = this.resolveMount(path4);
336
+ if (!r) throw new Error(`No mount for path: ${path4}`);
337
+ this.assertWritable(r.fs, path4, "deleteFile");
338
+ return r.fs.deleteFile(r.fsPath, options);
465
339
  }
466
- /**
467
- * Serialize the index to a JSON-compatible object
468
- */
469
- serialize() {
470
- const documents = [];
471
- for (const [id, doc] of this.#documents.entries()) {
472
- documents.push({
473
- id,
474
- content: doc.content,
475
- tokens: doc.tokens,
476
- termFrequencies: Object.fromEntries(doc.termFrequencies),
477
- length: doc.length,
478
- metadata: doc.metadata
479
- });
340
+ async copyFile(src, dest, options) {
341
+ const srcR = this.resolveMount(src);
342
+ const destR = this.resolveMount(dest);
343
+ if (!srcR) throw new Error(`No mount for source: ${src}`);
344
+ if (!destR) throw new Error(`No mount for dest: ${dest}`);
345
+ this.assertWritable(destR.fs, dest, "copyFile");
346
+ if (srcR.mountPath === destR.mountPath) {
347
+ return srcR.fs.copyFile(srcR.fsPath, destR.fsPath, options);
480
348
  }
481
- return {
482
- k1: this.k1,
483
- b: this.b,
484
- documents,
485
- avgDocLength: this.#avgDocLength
486
- };
349
+ const content = await srcR.fs.readFile(srcR.fsPath);
350
+ await destR.fs.writeFile(destR.fsPath, content, { overwrite: options?.overwrite });
487
351
  }
488
- /**
489
- * Deserialize an index from a JSON object
490
- */
491
- static deserialize(data, tokenizeOptions = {}) {
492
- const index = new _BM25Index({ k1: data.k1, b: data.b }, tokenizeOptions);
493
- for (const doc of data.documents) {
494
- const termFrequencies = new Map(Object.entries(doc.termFrequencies));
495
- const document = {
496
- id: doc.id,
497
- content: doc.content,
498
- tokens: doc.tokens,
499
- termFrequencies,
500
- length: doc.length,
501
- metadata: doc.metadata
352
+ async moveFile(src, dest, options) {
353
+ const srcR = this.resolveMount(src);
354
+ const destR = this.resolveMount(dest);
355
+ if (!srcR) throw new Error(`No mount for source: ${src}`);
356
+ if (!destR) throw new Error(`No mount for dest: ${dest}`);
357
+ this.assertWritable(destR.fs, dest, "moveFile");
358
+ this.assertWritable(srcR.fs, src, "moveFile");
359
+ if (srcR.mountPath === destR.mountPath) {
360
+ return srcR.fs.moveFile(srcR.fsPath, destR.fsPath, options);
361
+ }
362
+ await this.copyFile(src, dest, options);
363
+ await srcR.fs.deleteFile(srcR.fsPath);
364
+ }
365
+ async readdir(path4, options) {
366
+ const virtual = this.getVirtualEntries(path4);
367
+ if (virtual) return virtual;
368
+ const r = this.resolveMount(path4);
369
+ if (!r) throw new Error(`No mount for path: ${path4}`);
370
+ return r.fs.readdir(r.fsPath, options);
371
+ }
372
+ async mkdir(path4, options) {
373
+ const r = this.resolveMount(path4);
374
+ if (!r) throw new Error(`No mount for path: ${path4}`);
375
+ this.assertWritable(r.fs, path4, "mkdir");
376
+ return r.fs.mkdir(r.fsPath, options);
377
+ }
378
+ async rmdir(path4, options) {
379
+ const r = this.resolveMount(path4);
380
+ if (!r) throw new Error(`No mount for path: ${path4}`);
381
+ this.assertWritable(r.fs, path4, "rmdir");
382
+ return r.fs.rmdir(r.fsPath, options);
383
+ }
384
+ async exists(path4) {
385
+ if (this.isVirtualPath(path4)) return true;
386
+ const r = this.resolveMount(path4);
387
+ if (!r) return false;
388
+ if (r.fsPath === "/") return true;
389
+ return r.fs.exists(r.fsPath);
390
+ }
391
+ async stat(path4) {
392
+ const normalized = this.normalizePath(path4);
393
+ if (this.isVirtualPath(path4)) {
394
+ const parts = normalized.split("/").filter(Boolean);
395
+ const now = /* @__PURE__ */ new Date();
396
+ return {
397
+ name: parts[parts.length - 1] || "",
398
+ path: normalized,
399
+ type: "directory",
400
+ size: 0,
401
+ createdAt: now,
402
+ modifiedAt: now
502
403
  };
503
- index.#documents.set(doc.id, document);
504
- index.#docCount++;
505
- for (const term of termFrequencies.keys()) {
506
- if (!index.#invertedIndex.has(term)) {
507
- index.#invertedIndex.set(term, /* @__PURE__ */ new Set());
508
- }
509
- index.#invertedIndex.get(term).add(doc.id);
510
- index.#documentFrequency.set(term, (index.#documentFrequency.get(term) || 0) + 1);
511
- }
512
404
  }
513
- index.#avgDocLength = data.avgDocLength;
514
- return index;
405
+ const r = this.resolveMount(path4);
406
+ if (!r) throw new Error(`No mount for path: ${path4}`);
407
+ if (r.fsPath === "/") {
408
+ const parts = normalized.split("/").filter(Boolean);
409
+ const now = /* @__PURE__ */ new Date();
410
+ return {
411
+ name: parts[parts.length - 1] || "",
412
+ path: normalized,
413
+ type: "directory",
414
+ size: 0,
415
+ createdAt: now,
416
+ modifiedAt: now
417
+ };
418
+ }
419
+ return r.fs.stat(r.fsPath);
420
+ }
421
+ async isFile(path4) {
422
+ if (this.isVirtualPath(path4)) return false;
423
+ const r = this.resolveMount(path4);
424
+ if (!r) return false;
425
+ try {
426
+ const stat3 = await r.fs.stat(r.fsPath);
427
+ return stat3.type === "file";
428
+ } catch {
429
+ return false;
430
+ }
431
+ }
432
+ async isDirectory(path4) {
433
+ if (this.isVirtualPath(path4)) return true;
434
+ const r = this.resolveMount(path4);
435
+ if (!r) return false;
436
+ if (r.fsPath === "/") return true;
437
+ try {
438
+ const stat3 = await r.fs.stat(r.fsPath);
439
+ return stat3.type === "directory";
440
+ } catch {
441
+ return false;
442
+ }
515
443
  }
516
444
  /**
517
- * Update average document length after add/remove operations
445
+ * Get instructions describing the mounted filesystems.
446
+ * Used by agents to understand available storage locations.
518
447
  */
519
- #updateAvgDocLength() {
520
- if (this.#docCount === 0) {
521
- this.#avgDocLength = 0;
448
+ getInstructions() {
449
+ const mountDescriptions = Array.from(this._mounts.entries()).map(([mountPath, fs5]) => {
450
+ const name = fs5.displayName || fs5.provider;
451
+ const access2 = fs5.readOnly ? "(read-only)" : "(read-write)";
452
+ return `- ${mountPath}: ${name} ${access2}`;
453
+ }).join("\n");
454
+ return `Mounted filesystems:
455
+ ${mountDescriptions}
456
+ Files written via workspace tools are accessible at the same paths in sandbox commands.`;
457
+ }
458
+ };
459
+
460
+ // src/workspace/filesystem/mastra-filesystem.ts
461
+ var MastraFilesystem = class extends chunkRO47SMI7_cjs.MastraBase {
462
+ /** Error message when status is 'error' */
463
+ error;
464
+ // ---------------------------------------------------------------------------
465
+ // Lifecycle Promise Tracking (prevents race conditions)
466
+ // ---------------------------------------------------------------------------
467
+ /** Promise for _init() to prevent race conditions from concurrent calls */
468
+ _initPromise;
469
+ /** Promise for _destroy() to prevent race conditions from concurrent calls */
470
+ _destroyPromise;
471
+ constructor(options) {
472
+ super({ name: options.name, component: chunk7XAECHYL_cjs.RegisteredLogger.WORKSPACE });
473
+ }
474
+ // ---------------------------------------------------------------------------
475
+ // Lifecycle Wrappers (race-condition-safe)
476
+ // ---------------------------------------------------------------------------
477
+ /**
478
+ * Initialize the filesystem (wrapper with status management and race-condition safety).
479
+ *
480
+ * This method is race-condition-safe - concurrent calls will return the same promise.
481
+ * Handles status management automatically.
482
+ *
483
+ * Subclasses override `init()` to provide their initialization logic.
484
+ */
485
+ async _init() {
486
+ if (this.status === "ready") {
522
487
  return;
523
488
  }
524
- let totalLength = 0;
525
- for (const doc of this.#documents.values()) {
526
- totalLength += doc.length;
489
+ if (this._destroyPromise) {
490
+ try {
491
+ await this._destroyPromise;
492
+ } catch {
493
+ }
494
+ }
495
+ if (this._initPromise) {
496
+ return this._initPromise;
497
+ }
498
+ this._initPromise = this._executeInit();
499
+ try {
500
+ await this._initPromise;
501
+ } finally {
502
+ this._initPromise = void 0;
527
503
  }
528
- this.#avgDocLength = totalLength / this.#docCount;
529
504
  }
530
505
  /**
531
- * Compute IDF (Inverse Document Frequency) for a term
506
+ * Internal init execution - handles status.
532
507
  */
533
- #computeIDF(df) {
534
- return Math.log((this.#docCount - df + 0.5) / (df + 0.5) + 1);
508
+ async _executeInit() {
509
+ this.status = "initializing";
510
+ this.error = void 0;
511
+ try {
512
+ await this.init();
513
+ this.status = "ready";
514
+ } catch (error) {
515
+ this.status = "error";
516
+ this.error = error instanceof Error ? error.message : String(error);
517
+ this.logger.error("Failed to initialize filesystem", { error, id: this.id });
518
+ throw error;
519
+ }
535
520
  }
536
521
  /**
537
- * Compute the BM25 score component for a single term
522
+ * Override this method to implement filesystem initialization logic.
523
+ *
524
+ * Called by `_init()` after status is set to 'initializing'.
525
+ * Status will be set to 'ready' on success, 'error' on failure.
526
+ *
527
+ * @example
528
+ * ```typescript
529
+ * async init(): Promise<void> {
530
+ * this._client = new StorageClient({ ... });
531
+ * await this._client.connect();
532
+ * }
533
+ * ```
538
534
  */
539
- #computeTermScore(tf, docLength, idf) {
540
- const numerator = tf * (this.k1 + 1);
541
- const denominator = tf + this.k1 * (1 - this.b + this.b * (docLength / this.#avgDocLength));
542
- return idf * (numerator / denominator);
535
+ async init() {
543
536
  }
544
- };
545
-
546
- // src/workspace/search/search-engine.ts
547
- var SearchEngine = class {
548
- /** BM25 index for keyword search */
549
- #bm25Index;
550
- /** Tokenization options (stored for lineRange computation) */
551
- #tokenizeOptions;
552
- /** Vector configuration */
553
- #vectorConfig;
554
- /** Whether to use lazy vector indexing */
555
- #lazyVectorIndex;
556
- /** Documents pending vector indexing (for lazy mode) */
557
- #pendingVectorDocs = [];
558
- /** Whether vector index has been built (for lazy mode) */
559
- #vectorIndexBuilt = false;
560
- constructor(config = {}) {
561
- if (config.bm25 !== void 0) {
562
- this.#tokenizeOptions = config.bm25.tokenize;
563
- this.#bm25Index = new BM25Index(config.bm25.bm25, this.#tokenizeOptions);
537
+ /**
538
+ * Ensure the filesystem is ready.
539
+ *
540
+ * Calls `_init()` if status is not 'ready'. Useful for lazy initialization
541
+ * where operations should automatically initialize the filesystem if needed.
542
+ *
543
+ * @throws {FilesystemNotReadyError} if the filesystem fails to reach 'ready' status
544
+ *
545
+ * @example
546
+ * ```typescript
547
+ * async readFile(path: string): Promise<string | Buffer> {
548
+ * await this.ensureReady();
549
+ * // Now safe to use the filesystem
550
+ * }
551
+ * ```
552
+ */
553
+ async ensureReady() {
554
+ if (this.status !== "ready") {
555
+ await this._init();
564
556
  }
565
- if (config.vector) {
566
- this.#vectorConfig = config.vector;
557
+ if (this.status !== "ready") {
558
+ throw new FilesystemNotReadyError(this.id);
567
559
  }
568
- this.#lazyVectorIndex = config.lazyVectorIndex ?? false;
569
560
  }
570
- // ===========================================================================
571
- // Public API
572
- // ===========================================================================
573
561
  /**
574
- * Index a document for search
562
+ * Destroy the filesystem and clean up all resources (wrapper with status management).
563
+ *
564
+ * This method is race-condition-safe - concurrent calls will return the same promise.
565
+ * Handles status management.
566
+ *
567
+ * Subclasses override `destroy()` to provide their destroy logic.
575
568
  */
576
- async index(doc) {
577
- const metadata = {
578
- ...doc.metadata
579
- };
580
- if (doc.startLineOffset !== void 0) {
581
- metadata._startLineOffset = doc.startLineOffset;
569
+ async _destroy() {
570
+ if (this.status === "destroyed") {
571
+ return;
582
572
  }
583
- if (this.#bm25Index) {
584
- this.#bm25Index.add(doc.id, doc.content, metadata);
573
+ if (this.status === "pending") {
574
+ this.status = "destroyed";
575
+ return;
585
576
  }
586
- if (this.#vectorConfig) {
587
- const docWithMergedMetadata = { ...doc, metadata };
588
- if (this.#lazyVectorIndex) {
589
- this.#pendingVectorDocs.push(docWithMergedMetadata);
590
- this.#vectorIndexBuilt = false;
591
- } else {
592
- await this.#indexVector(docWithMergedMetadata);
593
- }
577
+ if (this._destroyPromise) {
578
+ return this._destroyPromise;
594
579
  }
595
- }
596
- /**
597
- * Index multiple documents
598
- */
599
- async indexMany(docs) {
600
- for (const doc of docs) {
601
- await this.index(doc);
580
+ this._destroyPromise = this._executeDestroy();
581
+ try {
582
+ await this._destroyPromise;
583
+ } finally {
584
+ this._destroyPromise = void 0;
602
585
  }
603
586
  }
604
587
  /**
605
- * Remove a document from the index
588
+ * Internal destroy execution - handles status.
606
589
  */
607
- async remove(id) {
608
- if (this.#bm25Index) {
609
- this.#bm25Index.remove(id);
610
- }
611
- if (this.#vectorConfig) {
590
+ async _executeDestroy() {
591
+ if (this._initPromise) {
612
592
  try {
613
- await this.#vectorConfig.vectorStore.deleteVector({
614
- indexName: this.#vectorConfig.indexName,
615
- id
616
- });
593
+ await this._initPromise;
617
594
  } catch {
618
595
  }
619
- if (this.#lazyVectorIndex) {
620
- this.#pendingVectorDocs = this.#pendingVectorDocs.filter((d) => d.id !== id);
621
- }
596
+ }
597
+ this.status = "destroying";
598
+ try {
599
+ await this.destroy();
600
+ this.status = "destroyed";
601
+ } catch (error) {
602
+ this.status = "error";
603
+ this.logger.error("Failed to destroy filesystem", { error, id: this.id });
604
+ throw error;
622
605
  }
623
606
  }
624
607
  /**
625
- * Clear all indexed documents
608
+ * Override this method to implement filesystem destroy logic.
609
+ *
610
+ * Called by `_destroy()` after status is set to 'destroying'.
611
+ * Status will be set to 'destroyed' on success, 'error' on failure.
626
612
  */
627
- clear() {
628
- if (this.#bm25Index) {
629
- this.#bm25Index.clear();
630
- }
631
- this.#pendingVectorDocs = [];
632
- this.#vectorIndexBuilt = false;
613
+ async destroy() {
633
614
  }
634
- /**
635
- * Search for documents
636
- */
637
- async search(query, options = {}) {
638
- const { topK = 10, minScore, mode, vectorWeight = 0.5, filter } = options;
639
- const effectiveMode = this.#determineSearchMode(mode);
640
- if (effectiveMode === "bm25") {
641
- return this.#searchBM25(query, topK, minScore);
642
- }
643
- if (effectiveMode === "vector") {
644
- return this.#searchVector(query, topK, minScore, filter);
615
+ };
616
+ function isEnoentError(error) {
617
+ return error !== null && typeof error === "object" && "code" in error && error.code === "ENOENT";
618
+ }
619
+ function isEexistError(error) {
620
+ return error !== null && typeof error === "object" && "code" in error && error.code === "EEXIST";
621
+ }
622
+ var MIME_TYPES = {
623
+ // Text
624
+ txt: "text/plain",
625
+ html: "text/html",
626
+ htm: "text/html",
627
+ css: "text/css",
628
+ csv: "text/csv",
629
+ md: "text/markdown",
630
+ // Code
631
+ js: "application/javascript",
632
+ mjs: "application/javascript",
633
+ ts: "application/typescript",
634
+ tsx: "application/typescript",
635
+ jsx: "application/javascript",
636
+ json: "application/json",
637
+ xml: "application/xml",
638
+ yaml: "text/yaml",
639
+ yml: "text/yaml",
640
+ // Programming languages
641
+ py: "text/x-python",
642
+ rb: "text/x-ruby",
643
+ go: "text/x-go",
644
+ rs: "text/x-rust",
645
+ java: "text/x-java",
646
+ c: "text/x-c",
647
+ cpp: "text/x-c++",
648
+ h: "text/x-c",
649
+ hpp: "text/x-c++",
650
+ sh: "text/x-sh",
651
+ bash: "text/x-sh",
652
+ zsh: "text/x-sh",
653
+ // Config
654
+ toml: "text/toml",
655
+ ini: "text/plain",
656
+ env: "text/plain",
657
+ // Database/Query
658
+ sql: "text/x-sql",
659
+ graphql: "application/graphql",
660
+ gql: "application/graphql",
661
+ // Frameworks
662
+ vue: "text/x-vue",
663
+ // Images
664
+ png: "image/png",
665
+ jpg: "image/jpeg",
666
+ jpeg: "image/jpeg",
667
+ gif: "image/gif",
668
+ svg: "image/svg+xml",
669
+ webp: "image/webp",
670
+ ico: "image/x-icon",
671
+ // Documents
672
+ pdf: "application/pdf"
673
+ };
674
+ function getMimeType(filename) {
675
+ const ext = nodePath__namespace.extname(filename).slice(1).toLowerCase();
676
+ return MIME_TYPES[ext] ?? "application/octet-stream";
677
+ }
678
+ var TEXT_EXTENSIONS = /* @__PURE__ */ new Set([
679
+ ".md",
680
+ ".txt",
681
+ ".json",
682
+ ".yaml",
683
+ ".yml",
684
+ ".js",
685
+ ".mjs",
686
+ ".ts",
687
+ ".tsx",
688
+ ".jsx",
689
+ ".py",
690
+ ".rb",
691
+ ".go",
692
+ ".rs",
693
+ ".java",
694
+ ".c",
695
+ ".cpp",
696
+ ".h",
697
+ ".hpp",
698
+ ".sh",
699
+ ".bash",
700
+ ".zsh",
701
+ ".html",
702
+ ".htm",
703
+ ".css",
704
+ ".xml",
705
+ ".toml",
706
+ ".ini",
707
+ ".env",
708
+ ".csv",
709
+ ".sql",
710
+ ".graphql",
711
+ ".gql",
712
+ ".vue",
713
+ ".svg"
714
+ ]);
715
+ function isTextFile(filename) {
716
+ const ext = nodePath__namespace.extname(filename).toLowerCase();
717
+ return TEXT_EXTENSIONS.has(ext);
718
+ }
719
+ async function fsExists(absolutePath) {
720
+ try {
721
+ await fs2__namespace.access(absolutePath);
722
+ return true;
723
+ } catch {
724
+ return false;
725
+ }
726
+ }
727
+ async function fsStat(absolutePath, userPath) {
728
+ try {
729
+ const stats = await fs2__namespace.stat(absolutePath);
730
+ return {
731
+ name: nodePath__namespace.basename(absolutePath),
732
+ type: stats.isDirectory() ? "directory" : "file",
733
+ size: stats.size,
734
+ createdAt: stats.birthtime,
735
+ modifiedAt: stats.mtime,
736
+ mimeType: stats.isFile() ? getMimeType(absolutePath) : void 0
737
+ };
738
+ } catch (error) {
739
+ if (isEnoentError(error)) {
740
+ throw new FileNotFoundError(userPath);
645
741
  }
646
- return this.#searchHybrid(query, topK, minScore, vectorWeight, filter);
742
+ throw error;
647
743
  }
744
+ }
745
+
746
+ // src/workspace/filesystem/local-filesystem.ts
747
+ var LocalFilesystem = class extends MastraFilesystem {
748
+ id;
749
+ name = "LocalFilesystem";
750
+ provider = "local";
751
+ readOnly;
752
+ status = "pending";
753
+ _basePath;
754
+ _contained;
648
755
  /**
649
- * Check if BM25 search is available
756
+ * The absolute base path on disk where files are stored.
757
+ * Useful for understanding how workspace paths map to disk paths.
650
758
  */
651
- get canBM25() {
652
- return !!this.#bm25Index;
759
+ get basePath() {
760
+ return this._basePath;
653
761
  }
654
- /**
655
- * Check if vector search is available
656
- */
657
- get canVector() {
658
- return !!this.#vectorConfig;
762
+ constructor(options) {
763
+ super({ name: "LocalFilesystem" });
764
+ this.id = options.id ?? this.generateId();
765
+ this._basePath = nodePath__namespace.resolve(options.basePath);
766
+ this._contained = options.contained ?? true;
767
+ this.readOnly = options.readOnly;
659
768
  }
660
- /**
661
- * Check if hybrid search is available
662
- */
663
- get canHybrid() {
664
- return this.canBM25 && this.canVector;
769
+ generateId() {
770
+ return `local-fs-${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 8)}`;
665
771
  }
666
- /**
667
- * Get the BM25 index (for serialization/debugging)
668
- */
669
- get bm25Index() {
670
- return this.#bm25Index;
772
+ toBuffer(content) {
773
+ if (Buffer.isBuffer(content)) return content;
774
+ if (content instanceof Uint8Array) return Buffer.from(content);
775
+ return Buffer.from(content, "utf-8");
671
776
  }
672
- // ===========================================================================
673
- // Private Methods
674
- // ===========================================================================
675
- /**
676
- * Determine the effective search mode
677
- */
678
- #determineSearchMode(requestedMode) {
679
- if (requestedMode) {
680
- if (requestedMode === "vector" && !this.canVector) {
681
- throw new Error("Vector search requires vector configuration.");
682
- }
683
- if (requestedMode === "bm25" && !this.canBM25) {
684
- throw new Error("BM25 search requires BM25 configuration.");
685
- }
686
- if (requestedMode === "hybrid" && !this.canHybrid) {
687
- throw new Error("Hybrid search requires both vector and BM25 configuration.");
777
+ resolvePath(inputPath) {
778
+ const cleanedPath = inputPath.replace(/^\/+/, "");
779
+ const normalizedInput = nodePath__namespace.normalize(cleanedPath);
780
+ const absolutePath = nodePath__namespace.resolve(this._basePath, normalizedInput);
781
+ if (this._contained) {
782
+ const relative2 = nodePath__namespace.relative(this._basePath, absolutePath);
783
+ if (relative2.startsWith("..") || nodePath__namespace.isAbsolute(relative2)) {
784
+ throw new PermissionError(inputPath, "access");
688
785
  }
689
- return requestedMode;
690
- }
691
- if (this.canHybrid) {
692
- return "hybrid";
693
- }
694
- if (this.canVector) {
695
- return "vector";
696
786
  }
697
- if (this.canBM25) {
698
- return "bm25";
699
- }
700
- throw new Error("No search configuration available. Provide bm25 or vector config.");
787
+ return absolutePath;
701
788
  }
702
- /**
703
- * Index a single document in the vector store
704
- */
705
- async #indexVector(doc) {
706
- if (!this.#vectorConfig) return;
707
- const { vectorStore, embedder, indexName } = this.#vectorConfig;
708
- const embedding = await embedder(doc.content);
709
- await vectorStore.upsert({
710
- indexName,
711
- vectors: [embedding],
712
- metadata: [
713
- {
714
- id: doc.id,
715
- text: doc.content,
716
- ...doc.metadata
717
- }
718
- ],
719
- ids: [doc.id]
720
- });
789
+ toRelativePath(absolutePath) {
790
+ return "/" + nodePath__namespace.relative(this._basePath, absolutePath).replace(/\\/g, "/");
721
791
  }
722
- /**
723
- * Ensure vector index is built (for lazy mode)
724
- */
725
- async #ensureVectorIndex() {
726
- if (!this.#lazyVectorIndex || this.#vectorIndexBuilt || this.#pendingVectorDocs.length === 0) {
727
- return;
728
- }
729
- for (const doc of this.#pendingVectorDocs) {
730
- await this.#indexVector(doc);
792
+ assertWritable(operation) {
793
+ if (this.readOnly) {
794
+ throw new WorkspaceReadOnlyError(operation);
731
795
  }
732
- this.#pendingVectorDocs = [];
733
- this.#vectorIndexBuilt = true;
734
796
  }
735
797
  /**
736
- * BM25 keyword search
737
- */
738
- #searchBM25(query, topK, minScore) {
739
- if (!this.#bm25Index) {
740
- throw new Error("BM25 search requires BM25 configuration.");
741
- }
742
- const results = this.#bm25Index.search(query, topK, minScore);
743
- const queryTokens = tokenize(query, this.#tokenizeOptions);
744
- return results.map((result) => {
745
- const rawLineRange = findLineRange(result.content, queryTokens, this.#tokenizeOptions);
746
- const lineRange = this.#adjustLineRange(rawLineRange, result.metadata);
747
- const { _startLineOffset, ...cleanMetadata } = result.metadata ?? {};
748
- return {
749
- id: result.id,
750
- content: result.content,
751
- score: result.score,
752
- lineRange,
753
- metadata: Object.keys(cleanMetadata).length > 0 ? cleanMetadata : void 0,
754
- scoreDetails: { bm25: result.score }
755
- };
756
- });
757
- }
758
- /**
759
- * Vector semantic search
798
+ * Verify that the resolved path doesn't escape basePath via symlinks.
799
+ * Uses realpath to resolve symlinks and check the actual target.
760
800
  */
761
- async #searchVector(query, topK, minScore, filter) {
762
- if (!this.#vectorConfig) {
763
- throw new Error("Vector search requires vector configuration.");
801
+ async assertPathContained(absolutePath) {
802
+ if (!this._contained) return;
803
+ let baseReal;
804
+ try {
805
+ baseReal = await fs2__namespace.realpath(this._basePath);
806
+ } catch (error) {
807
+ if (isEnoentError(error)) {
808
+ throw new DirectoryNotFoundError(this._basePath);
809
+ }
810
+ throw error;
764
811
  }
765
- await this.#ensureVectorIndex();
766
- const { vectorStore, embedder, indexName } = this.#vectorConfig;
767
- const queryEmbedding = await embedder(query);
768
- const vectorResults = await vectorStore.query({
769
- indexName,
770
- queryVector: queryEmbedding,
771
- topK,
772
- filter
773
- });
774
- const queryTokens = tokenize(query, this.#tokenizeOptions);
775
- const results = [];
776
- for (const result of vectorResults) {
777
- if (minScore !== void 0 && result.score < minScore) {
778
- continue;
812
+ let targetReal;
813
+ try {
814
+ targetReal = await fs2__namespace.realpath(absolutePath);
815
+ } catch (error) {
816
+ if (isEnoentError(error)) {
817
+ let parentPath = absolutePath;
818
+ while (true) {
819
+ const nextParent = nodePath__namespace.dirname(parentPath);
820
+ if (nextParent === parentPath) {
821
+ throw new DirectoryNotFoundError(absolutePath);
822
+ }
823
+ parentPath = nextParent;
824
+ try {
825
+ targetReal = await fs2__namespace.realpath(parentPath);
826
+ break;
827
+ } catch (parentError) {
828
+ if (!isEnoentError(parentError)) {
829
+ throw parentError;
830
+ }
831
+ }
832
+ }
833
+ } else {
834
+ throw error;
779
835
  }
780
- const id = result.metadata?.id ?? result.id;
781
- const content = result.metadata?.text ?? "";
782
- const { id: _id, text: _text, _startLineOffset, ...restMetadata } = result.metadata ?? {};
783
- const rawLineRange = findLineRange(content, queryTokens, this.#tokenizeOptions);
784
- const lineRange = this.#adjustLineRange(rawLineRange, result.metadata);
785
- results.push({
786
- id,
787
- content,
788
- score: result.score,
789
- lineRange,
790
- metadata: Object.keys(restMetadata).length > 0 ? restMetadata : void 0,
791
- scoreDetails: { vector: result.score }
792
- });
793
836
  }
794
- return results;
795
- }
796
- /**
797
- * Hybrid search combining vector and BM25 scores
798
- */
799
- async #searchHybrid(query, topK, minScore, vectorWeight = 0.5, filter) {
800
- const expandedTopK = Math.min(topK * 2, 50);
801
- const [vectorResults, bm25Results] = await Promise.all([
802
- this.#searchVector(query, expandedTopK, void 0, filter),
803
- Promise.resolve(this.#searchBM25(query, expandedTopK, void 0))
804
- ]);
805
- const normalizedBM25 = this.#normalizeBM25Scores(bm25Results);
806
- const bm25Map = /* @__PURE__ */ new Map();
807
- for (const result of normalizedBM25) {
808
- bm25Map.set(result.id, result);
837
+ if (targetReal !== baseReal && !targetReal.startsWith(baseReal + nodePath__namespace.sep)) {
838
+ throw new PermissionError(absolutePath, "access");
809
839
  }
810
- const vectorMap = /* @__PURE__ */ new Map();
811
- for (const result of vectorResults) {
812
- vectorMap.set(result.id, result);
840
+ }
841
+ async readFile(inputPath, options) {
842
+ this.logger.debug("Reading file", { path: inputPath, encoding: options?.encoding });
843
+ await this.ensureReady();
844
+ const absolutePath = this.resolvePath(inputPath);
845
+ await this.assertPathContained(absolutePath);
846
+ try {
847
+ const stats = await fs2__namespace.stat(absolutePath);
848
+ if (stats.isDirectory()) {
849
+ throw new IsDirectoryError(inputPath);
850
+ }
851
+ if (options?.encoding) {
852
+ return await fs2__namespace.readFile(absolutePath, { encoding: options.encoding });
853
+ }
854
+ return await fs2__namespace.readFile(absolutePath);
855
+ } catch (error) {
856
+ if (error instanceof IsDirectoryError) throw error;
857
+ if (isEnoentError(error)) {
858
+ throw new FileNotFoundError(inputPath);
859
+ }
860
+ throw error;
813
861
  }
814
- const combinedResults = /* @__PURE__ */ new Map();
815
- const allIds = /* @__PURE__ */ new Set([...vectorMap.keys(), ...bm25Map.keys()]);
816
- const bm25Weight = 1 - vectorWeight;
817
- for (const id of allIds) {
818
- const vectorResult = vectorMap.get(id);
819
- const bm25Result = bm25Map.get(id);
820
- const vectorScore = vectorResult?.scoreDetails?.vector ?? 0;
821
- const bm25Score = bm25Result?.score ?? 0;
822
- const combinedScore = vectorWeight * vectorScore + bm25Weight * bm25Score;
823
- const baseResult = vectorResult ?? bm25Result;
824
- combinedResults.set(id, {
825
- id,
826
- content: baseResult.content,
827
- score: combinedScore,
828
- lineRange: bm25Result?.lineRange ?? vectorResult?.lineRange,
829
- metadata: baseResult.metadata,
830
- scoreDetails: {
831
- vector: vectorResult?.scoreDetails?.vector,
832
- bm25: bm25Result?.scoreDetails?.bm25
862
+ }
863
+ async writeFile(inputPath, content, options) {
864
+ const contentSize = Buffer.isBuffer(content) ? content.length : content.length;
865
+ this.logger.debug("Writing file", { path: inputPath, size: contentSize, recursive: options?.recursive });
866
+ await this.ensureReady();
867
+ this.assertWritable("writeFile");
868
+ const absolutePath = this.resolvePath(inputPath);
869
+ await this.assertPathContained(absolutePath);
870
+ if (options?.recursive === false) {
871
+ const dir = nodePath__namespace.dirname(absolutePath);
872
+ const parentPath = nodePath__namespace.dirname(inputPath);
873
+ try {
874
+ const stat3 = await fs2__namespace.stat(dir);
875
+ if (!stat3.isDirectory()) {
876
+ throw new NotDirectoryError(parentPath);
833
877
  }
834
- });
878
+ } catch (error) {
879
+ if (error instanceof NotDirectoryError) throw error;
880
+ if (isEnoentError(error)) {
881
+ throw new DirectoryNotFoundError(parentPath);
882
+ }
883
+ throw error;
884
+ }
835
885
  }
836
- let results = Array.from(combinedResults.values());
837
- results.sort((a, b) => b.score - a.score);
838
- if (minScore !== void 0) {
839
- results = results.filter((r) => r.score >= minScore);
886
+ if (options?.recursive !== false) {
887
+ const dir = nodePath__namespace.dirname(absolutePath);
888
+ await fs2__namespace.mkdir(dir, { recursive: true });
840
889
  }
841
- return results.slice(0, topK);
842
- }
843
- /**
844
- * Normalize BM25 scores to 0-1 range using min-max normalization
845
- */
846
- #normalizeBM25Scores(results) {
847
- if (results.length === 0) return results;
848
- const scores = results.map((r) => r.scoreDetails?.bm25 ?? r.score);
849
- const maxScore = Math.max(...scores);
850
- const minScore = Math.min(...scores);
851
- const range = maxScore - minScore;
852
- if (range === 0) {
853
- return results.map((r) => ({ ...r, score: 1 }));
890
+ const writeFlag = options?.overwrite === false ? "wx" : "w";
891
+ try {
892
+ await fs2__namespace.writeFile(absolutePath, this.toBuffer(content), { flag: writeFlag });
893
+ } catch (error) {
894
+ if (options?.overwrite === false && isEexistError(error)) {
895
+ throw new FileExistsError(inputPath);
896
+ }
897
+ throw error;
854
898
  }
855
- return results.map((r) => ({
856
- ...r,
857
- score: ((r.scoreDetails?.bm25 ?? r.score) - minScore) / range
858
- }));
859
899
  }
860
- /**
861
- * Adjust line range for chunked documents.
862
- * If the document has a _startLineOffset in metadata, adjust the line range
863
- * to reflect the original document's line numbers.
864
- */
865
- #adjustLineRange(lineRange, metadata) {
866
- if (!lineRange) return void 0;
867
- const startLineOffset = metadata?._startLineOffset;
868
- if (typeof startLineOffset !== "number") {
869
- return lineRange;
900
+ async appendFile(inputPath, content) {
901
+ const contentSize = Buffer.isBuffer(content) ? content.length : content.length;
902
+ this.logger.debug("Appending to file", { path: inputPath, size: contentSize });
903
+ await this.ensureReady();
904
+ this.assertWritable("appendFile");
905
+ const absolutePath = this.resolvePath(inputPath);
906
+ await this.assertPathContained(absolutePath);
907
+ const dir = nodePath__namespace.dirname(absolutePath);
908
+ await fs2__namespace.mkdir(dir, { recursive: true });
909
+ await fs2__namespace.appendFile(absolutePath, this.toBuffer(content));
910
+ }
911
+ async deleteFile(inputPath, options) {
912
+ this.logger.debug("Deleting file", { path: inputPath, force: options?.force });
913
+ await this.ensureReady();
914
+ this.assertWritable("deleteFile");
915
+ const absolutePath = this.resolvePath(inputPath);
916
+ await this.assertPathContained(absolutePath);
917
+ try {
918
+ const stats = await fs2__namespace.stat(absolutePath);
919
+ if (stats.isDirectory()) {
920
+ throw new IsDirectoryError(inputPath);
921
+ }
922
+ await fs2__namespace.unlink(absolutePath);
923
+ } catch (error) {
924
+ if (error instanceof IsDirectoryError) throw error;
925
+ if (isEnoentError(error)) {
926
+ if (!options?.force) {
927
+ throw new FileNotFoundError(inputPath);
928
+ }
929
+ } else {
930
+ throw error;
931
+ }
870
932
  }
871
- return {
872
- start: lineRange.start + startLineOffset - 1,
873
- end: lineRange.end + startLineOffset - 1
874
- };
875
933
  }
876
- };
877
-
878
- // src/workspace/skills/schemas.ts
879
- var SKILL_LIMITS = {
880
- /** Recommended max tokens for instructions */
881
- MAX_INSTRUCTION_TOKENS: 5e3,
882
- /** Recommended max lines for SKILL.md */
883
- MAX_INSTRUCTION_LINES: 500,
884
- /** Max characters for name field */
885
- MAX_NAME_LENGTH: 64,
886
- /** Max characters for description field */
887
- MAX_DESCRIPTION_LENGTH: 1024};
888
- function validateSkillName(name) {
889
- const errors = [];
890
- const fieldPath = "name";
891
- if (typeof name !== "string") {
892
- errors.push(`${fieldPath}: Expected string, received ${typeof name}`);
893
- return errors;
934
+ async copyFile(src, dest, options) {
935
+ this.logger.debug("Copying file", { src, dest, recursive: options?.recursive });
936
+ await this.ensureReady();
937
+ this.assertWritable("copyFile");
938
+ const srcPath = this.resolvePath(src);
939
+ const destPath = this.resolvePath(dest);
940
+ await this.assertPathContained(srcPath);
941
+ await this.assertPathContained(destPath);
942
+ try {
943
+ const stats = await fs2__namespace.stat(srcPath);
944
+ if (stats.isDirectory()) {
945
+ if (!options?.recursive) {
946
+ throw new IsDirectoryError(src);
947
+ }
948
+ await this.copyDirectory(srcPath, destPath, options);
949
+ } else {
950
+ await fs2__namespace.mkdir(nodePath__namespace.dirname(destPath), { recursive: true });
951
+ const copyFlags = options?.overwrite === false ? fs.constants.COPYFILE_EXCL : 0;
952
+ try {
953
+ await fs2__namespace.copyFile(srcPath, destPath, copyFlags);
954
+ } catch (error) {
955
+ if (options?.overwrite === false && isEexistError(error)) {
956
+ throw new FileExistsError(dest);
957
+ }
958
+ throw error;
959
+ }
960
+ }
961
+ } catch (error) {
962
+ if (error instanceof IsDirectoryError || error instanceof FileExistsError) throw error;
963
+ if (isEnoentError(error)) {
964
+ throw new FileNotFoundError(src);
965
+ }
966
+ throw error;
967
+ }
894
968
  }
895
- if (name.length === 0) {
896
- errors.push(`${fieldPath}: Skill name cannot be empty`);
897
- return errors;
969
+ async copyDirectory(src, dest, options) {
970
+ await this.ensureReady();
971
+ await fs2__namespace.mkdir(dest, { recursive: true });
972
+ const entries = await fs2__namespace.readdir(src, { withFileTypes: true });
973
+ for (const entry of entries) {
974
+ const srcEntry = nodePath__namespace.join(src, entry.name);
975
+ const destEntry = nodePath__namespace.join(dest, entry.name);
976
+ await this.assertPathContained(srcEntry);
977
+ await this.assertPathContained(destEntry);
978
+ if (entry.isDirectory()) {
979
+ await this.copyDirectory(srcEntry, destEntry, options);
980
+ } else {
981
+ const copyFlags = options?.overwrite === false ? fs.constants.COPYFILE_EXCL : 0;
982
+ try {
983
+ await fs2__namespace.copyFile(srcEntry, destEntry, copyFlags);
984
+ } catch (error) {
985
+ if (options?.overwrite === false && isEexistError(error)) {
986
+ continue;
987
+ }
988
+ throw error;
989
+ }
990
+ }
991
+ }
898
992
  }
899
- if (name.length > SKILL_LIMITS.MAX_NAME_LENGTH) {
900
- errors.push(`${fieldPath}: Skill name must be ${SKILL_LIMITS.MAX_NAME_LENGTH} characters or less`);
993
+ async moveFile(src, dest, options) {
994
+ this.logger.debug("Moving file", { src, dest, overwrite: options?.overwrite });
995
+ await this.ensureReady();
996
+ this.assertWritable("moveFile");
997
+ const srcPath = this.resolvePath(src);
998
+ const destPath = this.resolvePath(dest);
999
+ await this.assertPathContained(srcPath);
1000
+ await this.assertPathContained(destPath);
1001
+ try {
1002
+ await fs2__namespace.mkdir(nodePath__namespace.dirname(destPath), { recursive: true });
1003
+ if (options?.overwrite === false) {
1004
+ await this.copyFile(src, dest, { ...options, overwrite: false });
1005
+ await fs2__namespace.rm(srcPath, { recursive: true, force: true });
1006
+ return;
1007
+ }
1008
+ try {
1009
+ await fs2__namespace.rename(srcPath, destPath);
1010
+ } catch (error) {
1011
+ const code = error.code;
1012
+ if (code !== "EXDEV") {
1013
+ throw error;
1014
+ }
1015
+ await this.copyFile(src, dest, options);
1016
+ await fs2__namespace.rm(srcPath, { recursive: true, force: true });
1017
+ }
1018
+ } catch (error) {
1019
+ if (error instanceof FileExistsError) throw error;
1020
+ if (isEnoentError(error)) {
1021
+ throw new FileNotFoundError(src);
1022
+ }
1023
+ throw error;
1024
+ }
901
1025
  }
902
- if (!/^[a-z0-9-]+$/.test(name)) {
903
- errors.push(`${fieldPath}: Skill name must contain only lowercase letters, numbers, and hyphens`);
1026
+ async mkdir(inputPath, options) {
1027
+ this.logger.debug("Creating directory", { path: inputPath, recursive: options?.recursive });
1028
+ await this.ensureReady();
1029
+ this.assertWritable("mkdir");
1030
+ const absolutePath = this.resolvePath(inputPath);
1031
+ await this.assertPathContained(absolutePath);
1032
+ try {
1033
+ await fs2__namespace.mkdir(absolutePath, { recursive: options?.recursive ?? true });
1034
+ } catch (error) {
1035
+ if (isEexistError(error)) {
1036
+ const stats = await fs2__namespace.stat(absolutePath);
1037
+ if (!stats.isDirectory()) {
1038
+ throw new FileExistsError(inputPath);
1039
+ }
1040
+ } else if (isEnoentError(error)) {
1041
+ const parentPath = nodePath__namespace.dirname(inputPath);
1042
+ throw new DirectoryNotFoundError(parentPath);
1043
+ } else {
1044
+ throw error;
1045
+ }
1046
+ }
904
1047
  }
905
- if (name.startsWith("-") || name.endsWith("-")) {
906
- errors.push(`${fieldPath}: Skill name must not start or end with a hyphen`);
1048
+ async rmdir(inputPath, options) {
1049
+ this.logger.debug("Removing directory", { path: inputPath, recursive: options?.recursive, force: options?.force });
1050
+ await this.ensureReady();
1051
+ this.assertWritable("rmdir");
1052
+ const absolutePath = this.resolvePath(inputPath);
1053
+ await this.assertPathContained(absolutePath);
1054
+ try {
1055
+ const stats = await fs2__namespace.stat(absolutePath);
1056
+ if (!stats.isDirectory()) {
1057
+ throw new NotDirectoryError(inputPath);
1058
+ }
1059
+ if (options?.recursive) {
1060
+ await fs2__namespace.rm(absolutePath, { recursive: true, force: options?.force ?? false });
1061
+ } else {
1062
+ const entries = await fs2__namespace.readdir(absolutePath);
1063
+ if (entries.length > 0) {
1064
+ throw new DirectoryNotEmptyError(inputPath);
1065
+ }
1066
+ await fs2__namespace.rmdir(absolutePath);
1067
+ }
1068
+ } catch (error) {
1069
+ if (error instanceof NotDirectoryError || error instanceof DirectoryNotEmptyError) {
1070
+ throw error;
1071
+ }
1072
+ if (isEnoentError(error)) {
1073
+ if (!options?.force) {
1074
+ throw new DirectoryNotFoundError(inputPath);
1075
+ }
1076
+ } else {
1077
+ throw error;
1078
+ }
1079
+ }
907
1080
  }
908
- if (name.includes("--")) {
909
- errors.push(`${fieldPath}: Skill name must not contain consecutive hyphens`);
1081
+ async readdir(inputPath, options) {
1082
+ this.logger.debug("Reading directory", { path: inputPath, recursive: options?.recursive });
1083
+ await this.ensureReady();
1084
+ const absolutePath = this.resolvePath(inputPath);
1085
+ await this.assertPathContained(absolutePath);
1086
+ try {
1087
+ const stats = await fs2__namespace.stat(absolutePath);
1088
+ if (!stats.isDirectory()) {
1089
+ throw new NotDirectoryError(inputPath);
1090
+ }
1091
+ const entries = await fs2__namespace.readdir(absolutePath, { withFileTypes: true });
1092
+ const result = [];
1093
+ for (const entry of entries) {
1094
+ const entryPath = nodePath__namespace.join(absolutePath, entry.name);
1095
+ if (options?.extension) {
1096
+ const extensions = Array.isArray(options.extension) ? options.extension : [options.extension];
1097
+ if (entry.isFile()) {
1098
+ const ext = nodePath__namespace.extname(entry.name);
1099
+ if (!extensions.some((e) => e === ext || e === ext.slice(1))) {
1100
+ continue;
1101
+ }
1102
+ }
1103
+ }
1104
+ const isSymlink = entry.isSymbolicLink();
1105
+ let symlinkTarget;
1106
+ let resolvedType = "file";
1107
+ if (isSymlink) {
1108
+ try {
1109
+ symlinkTarget = await fs2__namespace.readlink(entryPath);
1110
+ const targetStat = await fs2__namespace.stat(entryPath);
1111
+ resolvedType = targetStat.isDirectory() ? "directory" : "file";
1112
+ } catch {
1113
+ resolvedType = "file";
1114
+ }
1115
+ } else {
1116
+ resolvedType = entry.isDirectory() ? "directory" : "file";
1117
+ }
1118
+ const fileEntry = {
1119
+ name: entry.name,
1120
+ type: resolvedType,
1121
+ isSymlink: isSymlink || void 0,
1122
+ symlinkTarget
1123
+ };
1124
+ if (resolvedType === "file" && !isSymlink) {
1125
+ try {
1126
+ const stat3 = await fs2__namespace.stat(entryPath);
1127
+ fileEntry.size = stat3.size;
1128
+ } catch {
1129
+ }
1130
+ }
1131
+ result.push(fileEntry);
1132
+ if (options?.recursive && resolvedType === "directory") {
1133
+ const depth = options.maxDepth ?? 100;
1134
+ if (depth > 0) {
1135
+ const subEntries = await this.readdir(this.toRelativePath(entryPath), { ...options, maxDepth: depth - 1 });
1136
+ result.push(
1137
+ ...subEntries.map((e) => ({
1138
+ ...e,
1139
+ name: `${entry.name}/${e.name}`
1140
+ }))
1141
+ );
1142
+ }
1143
+ }
1144
+ }
1145
+ return result;
1146
+ } catch (error) {
1147
+ if (error instanceof NotDirectoryError) throw error;
1148
+ if (isEnoentError(error)) {
1149
+ throw new DirectoryNotFoundError(inputPath);
1150
+ }
1151
+ throw error;
1152
+ }
910
1153
  }
911
- return errors;
912
- }
913
- function validateSkillDescription(description) {
914
- const errors = [];
915
- const fieldPath = "description";
916
- if (typeof description !== "string") {
917
- errors.push(`${fieldPath}: Expected string, received ${typeof description}`);
918
- return errors;
1154
+ async exists(inputPath) {
1155
+ await this.ensureReady();
1156
+ const absolutePath = this.resolvePath(inputPath);
1157
+ await this.assertPathContained(absolutePath);
1158
+ return fsExists(absolutePath);
919
1159
  }
920
- if (description.length === 0) {
921
- errors.push(`${fieldPath}: Skill description cannot be empty`);
922
- return errors;
1160
+ async stat(inputPath) {
1161
+ await this.ensureReady();
1162
+ const absolutePath = this.resolvePath(inputPath);
1163
+ await this.assertPathContained(absolutePath);
1164
+ const result = await fsStat(absolutePath, inputPath);
1165
+ return {
1166
+ ...result,
1167
+ path: this.toRelativePath(absolutePath)
1168
+ };
923
1169
  }
924
- if (description.length > SKILL_LIMITS.MAX_DESCRIPTION_LENGTH) {
925
- errors.push(`${fieldPath}: Skill description must be ${SKILL_LIMITS.MAX_DESCRIPTION_LENGTH} characters or less`);
1170
+ /**
1171
+ * Initialize the local filesystem by creating the base directory.
1172
+ * Status management is handled by the base class.
1173
+ */
1174
+ async init() {
1175
+ this.logger.debug("Initializing filesystem", { basePath: this._basePath });
1176
+ await fs2__namespace.mkdir(this._basePath, { recursive: true });
1177
+ this.logger.debug("Filesystem initialized", { basePath: this._basePath });
926
1178
  }
927
- if (description.trim().length === 0) {
928
- errors.push(`${fieldPath}: Skill description cannot be only whitespace`);
929
- }
930
- return errors;
931
- }
932
- function validateSkillLicense(license) {
933
- const errors = [];
934
- const fieldPath = "license";
935
- if (license === void 0 || license === null) {
936
- return errors;
937
- }
938
- if (typeof license !== "string") {
939
- errors.push(`${fieldPath}: Expected string, received ${typeof license}`);
1179
+ /**
1180
+ * Clean up the local filesystem.
1181
+ * LocalFilesystem doesn't delete files on destroy by default.
1182
+ * Status management is handled by the base class.
1183
+ */
1184
+ async destroy() {
940
1185
  }
941
- return errors;
942
- }
943
- function validateSkillCompatibility(_compatibility) {
944
- return [];
945
- }
946
- function validateSkillMetadataField(metadata) {
947
- const errors = [];
948
- const fieldPath = "metadata";
949
- if (metadata === void 0 || metadata === null) {
950
- return errors;
1186
+ getInfo() {
1187
+ return {
1188
+ id: this.id,
1189
+ name: this.name,
1190
+ provider: this.provider,
1191
+ readOnly: this.readOnly,
1192
+ basePath: this.basePath,
1193
+ status: this.status
1194
+ };
951
1195
  }
952
- if (typeof metadata !== "object" || Array.isArray(metadata)) {
953
- errors.push(`${fieldPath}: Expected object, received ${Array.isArray(metadata) ? "array" : typeof metadata}`);
954
- return errors;
1196
+ getInstructions() {
1197
+ return `Local filesystem at "${this.basePath}". Files at workspace path "/foo" are stored at "${this.basePath}/foo" on disk.`;
955
1198
  }
956
- return errors;
957
- }
958
- function estimateTokens(text) {
959
- const words = text.split(/\s+/).filter(Boolean).length;
960
- return Math.ceil(words * 1.3);
961
- }
962
- function countLines(text) {
963
- return text.split("\n").length;
964
- }
965
- function validateSkillMetadata(metadata, dirName, instructions) {
966
- const errors = [];
967
- const warnings = [];
968
- if (typeof metadata !== "object" || metadata === null || Array.isArray(metadata)) {
969
- errors.push(
970
- `Expected object, received ${metadata === null ? "null" : Array.isArray(metadata) ? "array" : typeof metadata}`
971
- );
972
- return { valid: false, errors, warnings };
1199
+ };
1200
+ var InMemoryFileReadTracker = class {
1201
+ records = /* @__PURE__ */ new Map();
1202
+ recordRead(path4, modifiedAt) {
1203
+ const normalizedPath = this.normalizePath(path4);
1204
+ this.records.set(normalizedPath, {
1205
+ path: normalizedPath,
1206
+ readAt: /* @__PURE__ */ new Date(),
1207
+ modifiedAtRead: modifiedAt
1208
+ });
973
1209
  }
974
- const data = metadata;
975
- errors.push(...validateSkillName(data.name));
976
- errors.push(...validateSkillDescription(data.description));
977
- errors.push(...validateSkillLicense(data.license));
978
- errors.push(...validateSkillCompatibility());
979
- errors.push(...validateSkillMetadataField(data.metadata));
980
- if (dirName && typeof data.name === "string" && data.name !== dirName) {
981
- errors.push(`Skill name "${data.name}" must match directory name "${dirName}"`);
1210
+ getReadRecord(path4) {
1211
+ return this.records.get(this.normalizePath(path4));
982
1212
  }
983
- if (instructions) {
984
- const lineCount = countLines(instructions);
985
- const tokenEstimate = estimateTokens(instructions);
986
- if (lineCount > SKILL_LIMITS.MAX_INSTRUCTION_LINES) {
987
- warnings.push(
988
- `Instructions have ${lineCount} lines (recommended: <${SKILL_LIMITS.MAX_INSTRUCTION_LINES}). Consider moving content to references/.`
989
- );
1213
+ needsReRead(path4, currentModifiedAt) {
1214
+ const record = this.getReadRecord(path4);
1215
+ if (!record) {
1216
+ return {
1217
+ needsReRead: true,
1218
+ reason: `File "${path4}" has not been read. You must read a file before writing to it.`
1219
+ };
990
1220
  }
991
- if (tokenEstimate > SKILL_LIMITS.MAX_INSTRUCTION_TOKENS) {
992
- warnings.push(
993
- `Instructions have ~${tokenEstimate} estimated tokens (recommended: <${SKILL_LIMITS.MAX_INSTRUCTION_TOKENS}). Consider moving content to references/.`
994
- );
1221
+ if (currentModifiedAt.getTime() > record.modifiedAtRead.getTime()) {
1222
+ return {
1223
+ needsReRead: true,
1224
+ reason: `File "${path4}" was modified since last read (read at: ${record.modifiedAtRead.toISOString()}, current: ${currentModifiedAt.toISOString()}). Please re-read the file to get the latest contents.`
1225
+ };
995
1226
  }
1227
+ return { needsReRead: false };
1228
+ }
1229
+ clearReadRecord(path4) {
1230
+ this.records.delete(this.normalizePath(path4));
1231
+ }
1232
+ clear() {
1233
+ this.records.clear();
1234
+ }
1235
+ normalizePath(pathStr) {
1236
+ const normalized = nodePath__namespace.posix.normalize(pathStr.replace(/\\/g, "/"));
1237
+ return normalized.replace(/\/$/, "") || "/";
996
1238
  }
997
- return {
998
- valid: errors.length === 0,
999
- errors,
1000
- warnings
1001
- };
1002
- }
1003
- function isEnoentError(error) {
1004
- return error !== null && typeof error === "object" && "code" in error && error.code === "ENOENT";
1005
- }
1006
- function isEexistError(error) {
1007
- return error !== null && typeof error === "object" && "code" in error && error.code === "EEXIST";
1008
- }
1009
- var MIME_TYPES = {
1010
- // Text
1011
- txt: "text/plain",
1012
- html: "text/html",
1013
- htm: "text/html",
1014
- css: "text/css",
1015
- csv: "text/csv",
1016
- md: "text/markdown",
1017
- // Code
1018
- js: "application/javascript",
1019
- mjs: "application/javascript",
1020
- ts: "application/typescript",
1021
- tsx: "application/typescript",
1022
- jsx: "application/javascript",
1023
- json: "application/json",
1024
- xml: "application/xml",
1025
- yaml: "text/yaml",
1026
- yml: "text/yaml",
1027
- // Programming languages
1028
- py: "text/x-python",
1029
- rb: "text/x-ruby",
1030
- go: "text/x-go",
1031
- rs: "text/x-rust",
1032
- java: "text/x-java",
1033
- c: "text/x-c",
1034
- cpp: "text/x-c++",
1035
- h: "text/x-c",
1036
- hpp: "text/x-c++",
1037
- sh: "text/x-sh",
1038
- bash: "text/x-sh",
1039
- zsh: "text/x-sh",
1040
- // Config
1041
- toml: "text/toml",
1042
- ini: "text/plain",
1043
- env: "text/plain",
1044
- // Database/Query
1045
- sql: "text/x-sql",
1046
- graphql: "application/graphql",
1047
- gql: "application/graphql",
1048
- // Frameworks
1049
- vue: "text/x-vue",
1050
- // Images
1051
- png: "image/png",
1052
- jpg: "image/jpeg",
1053
- jpeg: "image/jpeg",
1054
- gif: "image/gif",
1055
- svg: "image/svg+xml",
1056
- webp: "image/webp",
1057
- ico: "image/x-icon",
1058
- // Documents
1059
- pdf: "application/pdf"
1060
1239
  };
1061
- function getMimeType(filename) {
1062
- const ext = nodePath__namespace.extname(filename).slice(1).toLowerCase();
1063
- return MIME_TYPES[ext] ?? "application/octet-stream";
1064
- }
1065
- var TEXT_EXTENSIONS = /* @__PURE__ */ new Set([
1066
- ".md",
1067
- ".txt",
1068
- ".json",
1069
- ".yaml",
1070
- ".yml",
1071
- ".js",
1072
- ".mjs",
1073
- ".ts",
1074
- ".tsx",
1075
- ".jsx",
1076
- ".py",
1077
- ".rb",
1078
- ".go",
1079
- ".rs",
1080
- ".java",
1081
- ".c",
1082
- ".cpp",
1083
- ".h",
1084
- ".hpp",
1085
- ".sh",
1086
- ".bash",
1087
- ".zsh",
1088
- ".html",
1089
- ".htm",
1090
- ".css",
1091
- ".xml",
1092
- ".toml",
1093
- ".ini",
1094
- ".env",
1095
- ".csv",
1096
- ".sql",
1097
- ".graphql",
1098
- ".gql",
1099
- ".vue",
1100
- ".svg"
1101
- ]);
1102
- function isTextFile(filename) {
1103
- const ext = nodePath__namespace.extname(filename).toLowerCase();
1104
- return TEXT_EXTENSIONS.has(ext);
1105
- }
1106
- async function fsExists(absolutePath) {
1107
- try {
1108
- await fs2__namespace.access(absolutePath);
1109
- return true;
1110
- } catch {
1111
- return false;
1240
+
1241
+ // src/workspace/sandbox/errors.ts
1242
+ var SandboxError = class extends Error {
1243
+ constructor(message, code, details) {
1244
+ super(message);
1245
+ this.code = code;
1246
+ this.details = details;
1247
+ this.name = "SandboxError";
1112
1248
  }
1113
- }
1114
- async function fsStat(absolutePath, userPath) {
1115
- try {
1116
- const stats = await fs2__namespace.stat(absolutePath);
1117
- return {
1118
- name: nodePath__namespace.basename(absolutePath),
1119
- type: stats.isDirectory() ? "directory" : "file",
1120
- size: stats.size,
1121
- createdAt: stats.birthtime,
1122
- modifiedAt: stats.mtime,
1123
- mimeType: stats.isFile() ? getMimeType(absolutePath) : void 0
1124
- };
1125
- } catch (error) {
1126
- if (isEnoentError(error)) {
1127
- throw new FileNotFoundError(userPath);
1249
+ };
1250
+ var SandboxExecutionError = class extends SandboxError {
1251
+ constructor(message, exitCode, stdout, stderr) {
1252
+ super(message, "EXECUTION_FAILED", { exitCode, stdout, stderr });
1253
+ this.exitCode = exitCode;
1254
+ this.stdout = stdout;
1255
+ this.stderr = stderr;
1256
+ this.name = "SandboxExecutionError";
1257
+ }
1258
+ };
1259
+ var SandboxTimeoutError = class extends SandboxError {
1260
+ constructor(timeoutMs, operation) {
1261
+ super(`Execution timed out after ${timeoutMs}ms`, "TIMEOUT", { timeoutMs, operation });
1262
+ this.timeoutMs = timeoutMs;
1263
+ this.operation = operation;
1264
+ this.name = "SandboxTimeoutError";
1265
+ }
1266
+ };
1267
+ var SandboxNotReadyError = class extends SandboxError {
1268
+ constructor(idOrStatus) {
1269
+ super(`Sandbox is not ready: ${idOrStatus}`, "NOT_READY", { id: idOrStatus });
1270
+ this.name = "SandboxNotReadyError";
1271
+ }
1272
+ };
1273
+ var IsolationUnavailableError = class extends SandboxError {
1274
+ constructor(backend, reason) {
1275
+ super(`Isolation backend '${backend}' is not available: ${reason}`, "ISOLATION_UNAVAILABLE", { backend, reason });
1276
+ this.backend = backend;
1277
+ this.reason = reason;
1278
+ this.name = "IsolationUnavailableError";
1279
+ }
1280
+ };
1281
+ var MountError = class extends SandboxError {
1282
+ constructor(message, mountPath, details) {
1283
+ super(message, "MOUNT_ERROR", { ...details, mountPath });
1284
+ this.mountPath = mountPath;
1285
+ this.name = "MountError";
1286
+ }
1287
+ };
1288
+ var MountNotSupportedError = class extends SandboxError {
1289
+ constructor(sandboxProvider) {
1290
+ super(`Sandbox provider '${sandboxProvider}' does not support mounting`, "MOUNT_NOT_SUPPORTED", {
1291
+ sandboxProvider
1292
+ });
1293
+ this.name = "MountNotSupportedError";
1294
+ }
1295
+ };
1296
+ var FilesystemNotMountableError = class extends SandboxError {
1297
+ constructor(filesystemProvider, reason) {
1298
+ const message = reason ? `Filesystem '${filesystemProvider}' cannot be mounted: ${reason}` : `Filesystem '${filesystemProvider}' does not support mounting`;
1299
+ super(message, "FILESYSTEM_NOT_MOUNTABLE", { filesystemProvider, reason });
1300
+ this.name = "FilesystemNotMountableError";
1301
+ }
1302
+ };
1303
+ var MountManager = class {
1304
+ _entries = /* @__PURE__ */ new Map();
1305
+ _mountFn;
1306
+ _onMount;
1307
+ _sandbox;
1308
+ _workspace;
1309
+ logger;
1310
+ constructor(config) {
1311
+ this._mountFn = config.mount;
1312
+ this.logger = config.logger;
1313
+ }
1314
+ /**
1315
+ * Set the sandbox and workspace references for onMount hook args.
1316
+ * Called by Workspace during construction.
1317
+ */
1318
+ setContext(context) {
1319
+ this._sandbox = context.sandbox;
1320
+ this._workspace = context.workspace;
1321
+ }
1322
+ /**
1323
+ * Set the onMount hook for custom mount handling.
1324
+ * Called before each mount - can skip, handle, or defer to default.
1325
+ */
1326
+ setOnMount(hook) {
1327
+ this._onMount = hook;
1328
+ }
1329
+ /**
1330
+ * Update the logger instance.
1331
+ * Called when the sandbox receives a logger from Mastra.
1332
+ * @internal
1333
+ */
1334
+ __setLogger(logger) {
1335
+ this.logger = logger;
1336
+ }
1337
+ // ---------------------------------------------------------------------------
1338
+ // Entry Access
1339
+ // ---------------------------------------------------------------------------
1340
+ /**
1341
+ * Get all mount entries.
1342
+ */
1343
+ get entries() {
1344
+ return this._entries;
1345
+ }
1346
+ /**
1347
+ * Get a mount entry by path.
1348
+ */
1349
+ get(path4) {
1350
+ return this._entries.get(path4);
1351
+ }
1352
+ /**
1353
+ * Check if a mount exists at the given path.
1354
+ */
1355
+ has(path4) {
1356
+ return this._entries.has(path4);
1357
+ }
1358
+ // ---------------------------------------------------------------------------
1359
+ // Entry Modification
1360
+ // ---------------------------------------------------------------------------
1361
+ /**
1362
+ * Add pending mounts from workspace config.
1363
+ * These will be processed when `processPending()` is called.
1364
+ */
1365
+ add(mounts) {
1366
+ const paths = Object.keys(mounts);
1367
+ this.logger.debug(`Adding ${paths.length} pending mount(s)`, { paths });
1368
+ for (const [path4, filesystem] of Object.entries(mounts)) {
1369
+ this._entries.set(path4, {
1370
+ filesystem,
1371
+ state: "pending"
1372
+ });
1373
+ }
1374
+ }
1375
+ /**
1376
+ * Update a mount entry's state.
1377
+ * Creates the entry if it doesn't exist.
1378
+ */
1379
+ set(path4, updates) {
1380
+ const existing = this._entries.get(path4);
1381
+ if (existing) {
1382
+ existing.state = updates.state;
1383
+ if (updates.config) {
1384
+ existing.config = updates.config;
1385
+ existing.configHash = this.hashConfig(updates.config);
1386
+ }
1387
+ if ("error" in updates) {
1388
+ existing.error = updates.error;
1389
+ }
1390
+ } else if (updates.filesystem) {
1391
+ this._entries.set(path4, {
1392
+ filesystem: updates.filesystem,
1393
+ state: updates.state,
1394
+ config: updates.config,
1395
+ configHash: updates.config ? this.hashConfig(updates.config) : void 0,
1396
+ error: updates.error
1397
+ });
1398
+ } else {
1399
+ this.logger.debug(`set() called for unknown path "${path4}" without filesystem \u2014 no entry created`);
1400
+ }
1401
+ }
1402
+ /**
1403
+ * Delete a mount entry.
1404
+ */
1405
+ delete(path4) {
1406
+ return this._entries.delete(path4);
1407
+ }
1408
+ /**
1409
+ * Clear all mount entries.
1410
+ */
1411
+ clear() {
1412
+ this._entries.clear();
1413
+ }
1414
+ // ---------------------------------------------------------------------------
1415
+ // Mount Processing
1416
+ // ---------------------------------------------------------------------------
1417
+ /**
1418
+ * Process all pending mounts.
1419
+ * Call this after sandbox is ready (in start()).
1420
+ */
1421
+ async processPending() {
1422
+ const pendingCount = [...this._entries.values()].filter((e) => e.state === "pending").length;
1423
+ if (pendingCount === 0) {
1424
+ return;
1425
+ }
1426
+ this.logger.debug(`Processing ${pendingCount} pending mount(s)`);
1427
+ for (const [path4, entry] of this._entries) {
1428
+ if (entry.state !== "pending") {
1429
+ continue;
1430
+ }
1431
+ const fsProvider = entry.filesystem.provider;
1432
+ const config = entry.filesystem.getMountConfig?.();
1433
+ if (this._onMount) {
1434
+ try {
1435
+ const hookResult = await this._onMount({
1436
+ filesystem: entry.filesystem,
1437
+ mountPath: path4,
1438
+ config,
1439
+ sandbox: this._sandbox,
1440
+ workspace: this._workspace
1441
+ });
1442
+ if (hookResult === false) {
1443
+ entry.state = "unsupported";
1444
+ entry.error = "Skipped by onMount hook";
1445
+ this.logger.debug(`Mount skipped by onMount hook`, { path: path4, provider: fsProvider });
1446
+ continue;
1447
+ }
1448
+ if (hookResult && typeof hookResult === "object") {
1449
+ if (hookResult.success) {
1450
+ entry.state = "mounted";
1451
+ entry.config = config;
1452
+ entry.configHash = config ? this.hashConfig(config) : void 0;
1453
+ this.logger.info(`Mount handled by onMount hook`, { path: path4, provider: fsProvider });
1454
+ } else {
1455
+ entry.state = "error";
1456
+ entry.error = hookResult.error ?? "Mount hook failed";
1457
+ this.logger.error(`Mount hook failed`, { path: path4, provider: fsProvider, error: entry.error });
1458
+ }
1459
+ continue;
1460
+ }
1461
+ } catch (err) {
1462
+ entry.state = "error";
1463
+ entry.error = `Mount hook error: ${String(err)}`;
1464
+ this.logger.error(`Mount hook threw error`, { path: path4, provider: fsProvider, error: entry.error });
1465
+ continue;
1466
+ }
1467
+ }
1468
+ if (!config) {
1469
+ entry.state = "unsupported";
1470
+ entry.error = "Filesystem does not support mounting";
1471
+ this.logger.debug(`Filesystem does not support mounting`, { path: path4, provider: fsProvider });
1472
+ continue;
1473
+ }
1474
+ entry.config = config;
1475
+ entry.configHash = this.hashConfig(config);
1476
+ entry.state = "mounting";
1477
+ this.logger.debug(`Mounting filesystem`, { path: path4, provider: fsProvider, type: config.type });
1478
+ try {
1479
+ const result = await this._mountFn(entry.filesystem, path4);
1480
+ if (result.success) {
1481
+ entry.state = "mounted";
1482
+ this.logger.info(`Mount successful`, { path: path4, provider: fsProvider });
1483
+ } else {
1484
+ entry.state = "error";
1485
+ entry.error = result.error ?? "Mount failed";
1486
+ this.logger.error(`Mount failed`, { path: path4, provider: fsProvider, error: entry.error });
1487
+ }
1488
+ } catch (err) {
1489
+ entry.state = "error";
1490
+ entry.error = String(err);
1491
+ this.logger.error(`Mount threw error`, { path: path4, provider: fsProvider, error: entry.error });
1492
+ }
1493
+ }
1494
+ }
1495
+ // ---------------------------------------------------------------------------
1496
+ // Marker File Helpers
1497
+ // ---------------------------------------------------------------------------
1498
+ /**
1499
+ * Generate a marker filename for a mount path.
1500
+ * Used by sandboxes to store mount metadata for reconnection detection.
1501
+ *
1502
+ * @param mountPath - The mount path to generate a filename for
1503
+ * @returns A safe filename like "mount-abc123"
1504
+ */
1505
+ markerFilename(mountPath) {
1506
+ let hash = 0;
1507
+ for (let i = 0; i < mountPath.length; i++) {
1508
+ const char = mountPath.charCodeAt(i);
1509
+ hash = (hash << 5) - hash + char;
1510
+ hash |= 0;
1511
+ }
1512
+ return `mount-${Math.abs(hash).toString(36)}`;
1513
+ }
1514
+ /**
1515
+ * Generate marker file content for a mount path.
1516
+ * Format: "path|configHash" - used for detecting config changes on reconnect.
1517
+ *
1518
+ * @param mountPath - The mount path
1519
+ * @returns Marker content string, or null if no config hash available
1520
+ */
1521
+ getMarkerContent(mountPath) {
1522
+ const entry = this._entries.get(mountPath);
1523
+ if (!entry?.configHash) {
1524
+ return null;
1525
+ }
1526
+ return `${mountPath}|${entry.configHash}`;
1527
+ }
1528
+ /**
1529
+ * Parse marker file content.
1530
+ *
1531
+ * @param content - The marker file content (format: "path|configHash")
1532
+ * @returns Parsed path and configHash, or null if invalid format
1533
+ */
1534
+ parseMarkerContent(content) {
1535
+ const separatorIndex = content.lastIndexOf("|");
1536
+ if (separatorIndex <= 0) {
1537
+ return null;
1538
+ }
1539
+ const path4 = content.slice(0, separatorIndex);
1540
+ const configHash = content.slice(separatorIndex + 1);
1541
+ if (!path4 || !configHash) return null;
1542
+ return { path: path4, configHash };
1543
+ }
1544
+ /**
1545
+ * Check if a config hash matches the expected hash for a mount path.
1546
+ *
1547
+ * @param mountPath - The mount path to check
1548
+ * @param storedHash - The hash from the marker file
1549
+ * @returns true if the hashes match
1550
+ */
1551
+ isConfigMatching(mountPath, storedHash) {
1552
+ const entry = this._entries.get(mountPath);
1553
+ return entry?.configHash === storedHash;
1554
+ }
1555
+ /**
1556
+ * Compute a hash for a mount config. Used for comparing configs across mounts.
1557
+ *
1558
+ * @param config - The config to hash
1559
+ * @returns A hash string suitable for comparison
1560
+ */
1561
+ computeConfigHash(config) {
1562
+ return this.hashConfig(config);
1563
+ }
1564
+ // ---------------------------------------------------------------------------
1565
+ // Internal
1566
+ // ---------------------------------------------------------------------------
1567
+ /**
1568
+ * Hash a mount config for comparison.
1569
+ */
1570
+ hashConfig(config) {
1571
+ const normalized = JSON.stringify(this.sortKeysDeep(config));
1572
+ return crypto.createHash("sha256").update(normalized).digest("hex").slice(0, 16);
1573
+ }
1574
+ sortKeysDeep(obj) {
1575
+ if (obj === null || typeof obj !== "object") return obj;
1576
+ if (Array.isArray(obj)) return obj.map((item) => this.sortKeysDeep(item));
1577
+ return Object.keys(obj).sort().reduce(
1578
+ (acc, key) => {
1579
+ acc[key] = this.sortKeysDeep(obj[key]);
1580
+ return acc;
1581
+ },
1582
+ {}
1583
+ );
1584
+ }
1585
+ };
1586
+
1587
+ // src/workspace/sandbox/mastra-sandbox.ts
1588
+ var MastraSandbox = class extends chunkRO47SMI7_cjs.MastraBase {
1589
+ /** Mount manager - automatically created if subclass implements mount() */
1590
+ mounts;
1591
+ // ---------------------------------------------------------------------------
1592
+ // Lifecycle Promise Tracking (prevents race conditions)
1593
+ // ---------------------------------------------------------------------------
1594
+ /** Promise for _start() to prevent race conditions from concurrent calls */
1595
+ _startPromise;
1596
+ /** Promise for _stop() to prevent race conditions from concurrent calls */
1597
+ _stopPromise;
1598
+ /** Promise for _destroy() to prevent race conditions from concurrent calls */
1599
+ _destroyPromise;
1600
+ /** Lifecycle callbacks */
1601
+ _onStart;
1602
+ _onStop;
1603
+ _onDestroy;
1604
+ constructor(options) {
1605
+ super({ name: options.name, component: chunk7XAECHYL_cjs.RegisteredLogger.WORKSPACE });
1606
+ this._onStart = options.onStart;
1607
+ this._onStop = options.onStop;
1608
+ this._onDestroy = options.onDestroy;
1609
+ if (this.mount) {
1610
+ this.mounts = new MountManager({
1611
+ mount: this.mount.bind(this),
1612
+ logger: this.logger
1613
+ });
1614
+ }
1615
+ }
1616
+ // ---------------------------------------------------------------------------
1617
+ // Lifecycle Wrappers (race-condition-safe)
1618
+ // ---------------------------------------------------------------------------
1619
+ /**
1620
+ * Start the sandbox (wrapper with status management and race-condition safety).
1621
+ *
1622
+ * This method is race-condition-safe - concurrent calls will return the same promise.
1623
+ * Handles status management and automatically processes pending mounts after startup.
1624
+ *
1625
+ * Subclasses override `start()` to provide their startup logic.
1626
+ */
1627
+ async _start() {
1628
+ if (this.status === "running") {
1629
+ return;
1630
+ }
1631
+ if (this._stopPromise) await this._stopPromise;
1632
+ if (this._destroyPromise) await this._destroyPromise;
1633
+ if (this.status === "destroyed") {
1634
+ throw new Error("Cannot start a destroyed sandbox");
1635
+ }
1636
+ if (this._startPromise) {
1637
+ return this._startPromise;
1638
+ }
1639
+ this._startPromise = this._executeStart();
1640
+ try {
1641
+ await this._startPromise;
1642
+ } finally {
1643
+ this._startPromise = void 0;
1644
+ }
1645
+ }
1646
+ /**
1647
+ * Internal start execution - handles status and mount processing.
1648
+ */
1649
+ async _executeStart() {
1650
+ this.status = "starting";
1651
+ try {
1652
+ await this.start();
1653
+ this.status = "running";
1654
+ try {
1655
+ await this._onStart?.({ sandbox: this });
1656
+ } catch (error) {
1657
+ this.logger.warn("onStart callback failed", { error });
1658
+ }
1659
+ } catch (error) {
1660
+ this.status = "error";
1661
+ throw error;
1662
+ }
1663
+ try {
1664
+ await this.mounts?.processPending();
1665
+ } catch (error) {
1666
+ this.logger.warn("Unexpected error processing pending mounts", { error });
1667
+ }
1668
+ }
1669
+ /**
1670
+ * Override this method to implement sandbox startup logic.
1671
+ *
1672
+ * Called by `_start()` after status is set to 'starting'.
1673
+ * Status will be set to 'running' on success, 'error' on failure.
1674
+ *
1675
+ * @example
1676
+ * ```typescript
1677
+ * async start(): Promise<void> {
1678
+ * this._sandbox = await Sandbox.create({ ... });
1679
+ * }
1680
+ * ```
1681
+ */
1682
+ async start() {
1683
+ }
1684
+ /**
1685
+ * Ensure the sandbox is running.
1686
+ *
1687
+ * Calls `_start()` if status is not 'running'. Useful for lazy initialization
1688
+ * where operations should automatically start the sandbox if needed.
1689
+ *
1690
+ * @throws {SandboxNotReadyError} if the sandbox fails to reach 'running' status
1691
+ *
1692
+ * @example
1693
+ * ```typescript
1694
+ * async executeCommand(command: string): Promise<CommandResult> {
1695
+ * await this.ensureRunning();
1696
+ * // Now safe to use the sandbox
1697
+ * }
1698
+ * ```
1699
+ */
1700
+ async ensureRunning() {
1701
+ if (this.status !== "running") {
1702
+ await this._start();
1703
+ }
1704
+ if (this.status !== "running") {
1705
+ throw new SandboxNotReadyError(this.id);
1706
+ }
1707
+ }
1708
+ /**
1709
+ * Stop the sandbox (wrapper with status management and race-condition safety).
1710
+ *
1711
+ * This method is race-condition-safe - concurrent calls will return the same promise.
1712
+ * Handles status management.
1713
+ *
1714
+ * Subclasses override `stop()` to provide their stop logic.
1715
+ */
1716
+ async _stop() {
1717
+ if (this.status === "stopped") {
1718
+ return;
1719
+ }
1720
+ if (this._startPromise) await this._startPromise.catch(() => {
1721
+ });
1722
+ if (this._stopPromise) {
1723
+ return this._stopPromise;
1724
+ }
1725
+ this._stopPromise = this._executeStop();
1726
+ try {
1727
+ await this._stopPromise;
1728
+ } finally {
1729
+ this._stopPromise = void 0;
1730
+ }
1731
+ }
1732
+ /**
1733
+ * Internal stop execution - handles status.
1734
+ */
1735
+ async _executeStop() {
1736
+ this.status = "stopping";
1737
+ try {
1738
+ await this._onStop?.({ sandbox: this });
1739
+ await this.stop();
1740
+ this.status = "stopped";
1741
+ } catch (error) {
1742
+ this.status = "error";
1743
+ throw error;
1744
+ }
1745
+ }
1746
+ /**
1747
+ * Override this method to implement sandbox stop logic.
1748
+ *
1749
+ * Called by `_stop()` after status is set to 'stopping'.
1750
+ * Status will be set to 'stopped' on success, 'error' on failure.
1751
+ */
1752
+ async stop() {
1753
+ }
1754
+ /**
1755
+ * Destroy the sandbox and clean up all resources (wrapper with status management).
1756
+ *
1757
+ * This method is race-condition-safe - concurrent calls will return the same promise.
1758
+ * Handles status management.
1759
+ *
1760
+ * Subclasses override `destroy()` to provide their destroy logic.
1761
+ */
1762
+ async _destroy() {
1763
+ if (this.status === "destroyed") {
1764
+ return;
1765
+ }
1766
+ if (this._startPromise) await this._startPromise.catch(() => {
1767
+ });
1768
+ if (this._stopPromise) await this._stopPromise.catch(() => {
1769
+ });
1770
+ if (this._destroyPromise) {
1771
+ return this._destroyPromise;
1772
+ }
1773
+ this._destroyPromise = this._executeDestroy();
1774
+ try {
1775
+ await this._destroyPromise;
1776
+ } finally {
1777
+ this._destroyPromise = void 0;
1778
+ }
1779
+ }
1780
+ /**
1781
+ * Internal destroy execution - handles status.
1782
+ */
1783
+ async _executeDestroy() {
1784
+ this.status = "destroying";
1785
+ try {
1786
+ await this._onDestroy?.({ sandbox: this });
1787
+ await this.destroy();
1788
+ this.status = "destroyed";
1789
+ } catch (error) {
1790
+ this.status = "error";
1791
+ throw error;
1792
+ }
1793
+ }
1794
+ /**
1795
+ * Override this method to implement sandbox destroy logic.
1796
+ *
1797
+ * Called by `_destroy()` after status is set to 'destroying'.
1798
+ * Status will be set to 'destroyed' on success, 'error' on failure.
1799
+ */
1800
+ async destroy() {
1801
+ }
1802
+ // ---------------------------------------------------------------------------
1803
+ // Logger Propagation
1804
+ // ---------------------------------------------------------------------------
1805
+ /**
1806
+ * Override to propagate logger to MountManager.
1807
+ * @internal
1808
+ */
1809
+ __setLogger(logger) {
1810
+ super.__setLogger(logger);
1811
+ this.mounts?.__setLogger(logger);
1812
+ }
1813
+ };
1814
+
1815
+ // src/workspace/line-utils.ts
1816
+ function extractLines(content, startLine, endLine) {
1817
+ const allLines = content.split("\n");
1818
+ const totalLines = allLines.length;
1819
+ const start = Math.max(1, startLine ?? 1);
1820
+ const end = Math.min(totalLines, endLine ?? totalLines);
1821
+ const extractedLines = allLines.slice(start - 1, end);
1822
+ return {
1823
+ content: extractedLines.join("\n"),
1824
+ lines: { start, end },
1825
+ totalLines
1826
+ };
1827
+ }
1828
+ function extractLinesWithLimit(content, offset, limit) {
1829
+ const startLine = offset ?? 1;
1830
+ const endLine = limit ? startLine + limit - 1 : void 0;
1831
+ return extractLines(content, startLine, endLine);
1832
+ }
1833
+ function formatWithLineNumbers(content, startLineNumber = 1) {
1834
+ const lines = content.split("\n");
1835
+ const maxLineNum = startLineNumber + lines.length - 1;
1836
+ const padWidth = Math.max(6, String(maxLineNum).length + 1);
1837
+ return lines.map((line, i) => {
1838
+ const lineNum = startLineNumber + i;
1839
+ return `${String(lineNum).padStart(padWidth)}\u2192${line}`;
1840
+ }).join("\n");
1841
+ }
1842
+ function countOccurrences(content, searchString) {
1843
+ if (!searchString) return 0;
1844
+ let count = 0;
1845
+ let position = 0;
1846
+ while ((position = content.indexOf(searchString, position)) !== -1) {
1847
+ count++;
1848
+ position += searchString.length;
1849
+ }
1850
+ return count;
1851
+ }
1852
+ function replaceString(content, oldString, newString, replaceAll = false) {
1853
+ const count = countOccurrences(content, oldString);
1854
+ if (count === 0) {
1855
+ throw new StringNotFoundError(oldString);
1856
+ }
1857
+ if (!replaceAll && count > 1) {
1858
+ throw new StringNotUniqueError(oldString, count);
1859
+ }
1860
+ if (replaceAll) {
1861
+ const result = content.split(oldString).join(newString);
1862
+ return { content: result, replacements: count };
1863
+ } else {
1864
+ const result = content.replace(oldString, newString);
1865
+ return { content: result, replacements: 1 };
1866
+ }
1867
+ }
1868
+ var StringNotFoundError = class extends Error {
1869
+ constructor(searchString) {
1870
+ super(`The specified text was not found. Make sure you use the exact text from the file.`);
1871
+ this.searchString = searchString;
1872
+ this.name = "StringNotFoundError";
1873
+ }
1874
+ };
1875
+ var StringNotUniqueError = class extends Error {
1876
+ constructor(searchString, occurrences) {
1877
+ super(
1878
+ `The specified text appears ${occurrences} times. Provide more surrounding context to make the match unique, or use replace_all to replace all occurrences.`
1879
+ );
1880
+ this.searchString = searchString;
1881
+ this.occurrences = occurrences;
1882
+ this.name = "StringNotUniqueError";
1883
+ }
1884
+ };
1885
+
1886
+ // src/workspace/search/bm25.ts
1887
+ var DEFAULT_STOPWORDS = /* @__PURE__ */ new Set([
1888
+ "a",
1889
+ "an",
1890
+ "and",
1891
+ "are",
1892
+ "as",
1893
+ "at",
1894
+ "be",
1895
+ "by",
1896
+ "for",
1897
+ "from",
1898
+ "has",
1899
+ "he",
1900
+ "in",
1901
+ "is",
1902
+ "it",
1903
+ "its",
1904
+ "of",
1905
+ "on",
1906
+ "or",
1907
+ "that",
1908
+ "the",
1909
+ "to",
1910
+ "was",
1911
+ "were",
1912
+ "will",
1913
+ "with"
1914
+ ]);
1915
+ var DEFAULT_TOKENIZE_OPTIONS = {
1916
+ lowercase: true,
1917
+ removePunctuation: true,
1918
+ minLength: 2,
1919
+ stopwords: DEFAULT_STOPWORDS,
1920
+ splitPattern: /\s+/
1921
+ };
1922
+ function tokenize(text, options = {}) {
1923
+ const opts = { ...DEFAULT_TOKENIZE_OPTIONS, ...options };
1924
+ let processed = text;
1925
+ if (opts.lowercase) {
1926
+ processed = processed.toLowerCase();
1927
+ }
1928
+ if (opts.removePunctuation) {
1929
+ processed = processed.replace(/[^\w\s]/g, " ");
1930
+ }
1931
+ const tokens = processed.split(opts.splitPattern).filter((token) => {
1932
+ if (token.length < opts.minLength) {
1933
+ return false;
1934
+ }
1935
+ if (opts.stopwords?.has(token)) {
1936
+ return false;
1937
+ }
1938
+ return true;
1939
+ });
1940
+ return tokens;
1941
+ }
1942
+ function findLineRange(content, queryTerms, options = {}) {
1943
+ if (queryTerms.length === 0) return void 0;
1944
+ const lines = content.split("\n");
1945
+ const defaultOpts = { lowercase: true, removePunctuation: true, minLength: 2 };
1946
+ const opts = { ...defaultOpts, ...options };
1947
+ const normalizedTerms = new Set(queryTerms.map((t) => opts.lowercase ? t.toLowerCase() : t));
1948
+ let firstMatchLine;
1949
+ let lastMatchLine;
1950
+ for (let i = 0; i < lines.length; i++) {
1951
+ const lineTokens = tokenize(lines[i], options);
1952
+ for (const token of lineTokens) {
1953
+ if (normalizedTerms.has(token)) {
1954
+ const lineNum = i + 1;
1955
+ if (firstMatchLine === void 0) {
1956
+ firstMatchLine = lineNum;
1957
+ }
1958
+ lastMatchLine = lineNum;
1959
+ break;
1960
+ }
1961
+ }
1962
+ }
1963
+ if (firstMatchLine !== void 0 && lastMatchLine !== void 0) {
1964
+ return { start: firstMatchLine, end: lastMatchLine };
1965
+ }
1966
+ return void 0;
1967
+ }
1968
+ function computeTermFrequencies(tokens) {
1969
+ const frequencies = /* @__PURE__ */ new Map();
1970
+ for (const token of tokens) {
1971
+ frequencies.set(token, (frequencies.get(token) || 0) + 1);
1972
+ }
1973
+ return frequencies;
1974
+ }
1975
+ var BM25Index = class _BM25Index {
1976
+ /** BM25 k1 parameter */
1977
+ k1;
1978
+ /** BM25 b parameter */
1979
+ b;
1980
+ /** Documents in the index */
1981
+ #documents = /* @__PURE__ */ new Map();
1982
+ /** Inverted index: term -> document IDs containing the term */
1983
+ #invertedIndex = /* @__PURE__ */ new Map();
1984
+ /** Document frequency: term -> number of documents containing the term */
1985
+ #documentFrequency = /* @__PURE__ */ new Map();
1986
+ /** Average document length */
1987
+ #avgDocLength = 0;
1988
+ /** Total number of documents */
1989
+ #docCount = 0;
1990
+ /** Tokenization options */
1991
+ #tokenizeOptions;
1992
+ constructor(config = {}, tokenizeOptions = {}) {
1993
+ this.k1 = config.k1 ?? 1.5;
1994
+ this.b = config.b ?? 0.75;
1995
+ this.#tokenizeOptions = tokenizeOptions;
1996
+ }
1997
+ /**
1998
+ * Add a document to the index
1999
+ */
2000
+ add(id, content, metadata) {
2001
+ if (this.#documents.has(id)) {
2002
+ this.remove(id);
2003
+ }
2004
+ const tokens = tokenize(content, this.#tokenizeOptions);
2005
+ const termFrequencies = computeTermFrequencies(tokens);
2006
+ const doc = {
2007
+ id,
2008
+ content,
2009
+ tokens,
2010
+ termFrequencies,
2011
+ length: tokens.length,
2012
+ metadata
2013
+ };
2014
+ this.#documents.set(id, doc);
2015
+ this.#docCount++;
2016
+ for (const term of termFrequencies.keys()) {
2017
+ if (!this.#invertedIndex.has(term)) {
2018
+ this.#invertedIndex.set(term, /* @__PURE__ */ new Set());
2019
+ }
2020
+ this.#invertedIndex.get(term).add(id);
2021
+ this.#documentFrequency.set(term, (this.#documentFrequency.get(term) || 0) + 1);
2022
+ }
2023
+ this.#updateAvgDocLength();
2024
+ }
2025
+ /**
2026
+ * Remove a document from the index
2027
+ */
2028
+ remove(id) {
2029
+ const doc = this.#documents.get(id);
2030
+ if (!doc) {
2031
+ return false;
2032
+ }
2033
+ for (const term of doc.termFrequencies.keys()) {
2034
+ const docIds = this.#invertedIndex.get(term);
2035
+ if (docIds) {
2036
+ docIds.delete(id);
2037
+ if (docIds.size === 0) {
2038
+ this.#invertedIndex.delete(term);
2039
+ this.#documentFrequency.delete(term);
2040
+ } else {
2041
+ this.#documentFrequency.set(term, (this.#documentFrequency.get(term) || 1) - 1);
2042
+ }
2043
+ }
2044
+ }
2045
+ this.#documents.delete(id);
2046
+ this.#docCount--;
2047
+ this.#updateAvgDocLength();
2048
+ return true;
2049
+ }
2050
+ /**
2051
+ * Clear all documents from the index
2052
+ */
2053
+ clear() {
2054
+ this.#documents.clear();
2055
+ this.#invertedIndex.clear();
2056
+ this.#documentFrequency.clear();
2057
+ this.#docCount = 0;
2058
+ this.#avgDocLength = 0;
2059
+ }
2060
+ /**
2061
+ * Search for documents matching the query
2062
+ */
2063
+ search(query, topK = 10, minScore = 0) {
2064
+ const queryTokens = tokenize(query, this.#tokenizeOptions);
2065
+ if (queryTokens.length === 0 || this.#docCount === 0) {
2066
+ return [];
2067
+ }
2068
+ const scores = /* @__PURE__ */ new Map();
2069
+ for (const queryTerm of queryTokens) {
2070
+ const docIds = this.#invertedIndex.get(queryTerm);
2071
+ if (!docIds) {
2072
+ continue;
2073
+ }
2074
+ const df = this.#documentFrequency.get(queryTerm) || 0;
2075
+ const idf = this.#computeIDF(df);
2076
+ for (const docId of docIds) {
2077
+ const doc = this.#documents.get(docId);
2078
+ const tf = doc.termFrequencies.get(queryTerm) || 0;
2079
+ const termScore = this.#computeTermScore(tf, doc.length, idf);
2080
+ scores.set(docId, (scores.get(docId) || 0) + termScore);
2081
+ }
1128
2082
  }
1129
- throw error;
2083
+ const results = [];
2084
+ for (const [docId, score] of scores.entries()) {
2085
+ if (score >= minScore) {
2086
+ const doc = this.#documents.get(docId);
2087
+ results.push({
2088
+ id: docId,
2089
+ content: doc.content,
2090
+ score,
2091
+ metadata: doc.metadata
2092
+ });
2093
+ }
2094
+ }
2095
+ results.sort((a, b) => b.score - a.score);
2096
+ return results.slice(0, topK);
1130
2097
  }
1131
- }
1132
-
1133
- // src/workspace/filesystem/local-filesystem.ts
1134
- var LocalFilesystem = class extends MastraFilesystem {
1135
- id;
1136
- name = "LocalFilesystem";
1137
- provider = "local";
1138
- readOnly;
1139
- status = "stopped";
1140
- _basePath;
1141
- _contained;
1142
2098
  /**
1143
- * The absolute base path on disk where files are stored.
1144
- * Useful for understanding how workspace paths map to disk paths.
2099
+ * Get a document by ID
1145
2100
  */
1146
- get basePath() {
1147
- return this._basePath;
1148
- }
1149
- constructor(options) {
1150
- super({ name: "LocalFilesystem" });
1151
- this.id = options.id ?? this.generateId();
1152
- this._basePath = nodePath__namespace.resolve(options.basePath);
1153
- this._contained = options.contained ?? true;
1154
- this.readOnly = options.readOnly;
1155
- }
1156
- generateId() {
1157
- return `local-fs-${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 8)}`;
2101
+ get(id) {
2102
+ return this.#documents.get(id);
1158
2103
  }
1159
- toBuffer(content) {
1160
- if (Buffer.isBuffer(content)) return content;
1161
- if (content instanceof Uint8Array) return Buffer.from(content);
1162
- return Buffer.from(content, "utf-8");
2104
+ /**
2105
+ * Check if a document exists in the index
2106
+ */
2107
+ has(id) {
2108
+ return this.#documents.has(id);
1163
2109
  }
1164
- resolvePath(inputPath) {
1165
- const cleanedPath = inputPath.replace(/^\/+/, "");
1166
- const normalizedInput = nodePath__namespace.normalize(cleanedPath);
1167
- const absolutePath = nodePath__namespace.resolve(this._basePath, normalizedInput);
1168
- if (this._contained) {
1169
- const relative2 = nodePath__namespace.relative(this._basePath, absolutePath);
1170
- if (relative2.startsWith("..") || nodePath__namespace.isAbsolute(relative2)) {
1171
- throw new PermissionError(inputPath, "access");
1172
- }
1173
- }
1174
- return absolutePath;
2110
+ /**
2111
+ * Get the number of documents in the index
2112
+ */
2113
+ get size() {
2114
+ return this.#docCount;
1175
2115
  }
1176
- toRelativePath(absolutePath) {
1177
- return "/" + nodePath__namespace.relative(this._basePath, absolutePath).replace(/\\/g, "/");
2116
+ /**
2117
+ * Get all document IDs
2118
+ */
2119
+ get documentIds() {
2120
+ return Array.from(this.#documents.keys());
1178
2121
  }
1179
- assertWritable(operation) {
1180
- if (this.readOnly) {
1181
- throw new WorkspaceReadOnlyError(operation);
2122
+ /**
2123
+ * Serialize the index to a JSON-compatible object
2124
+ */
2125
+ serialize() {
2126
+ const documents = [];
2127
+ for (const [id, doc] of this.#documents.entries()) {
2128
+ documents.push({
2129
+ id,
2130
+ content: doc.content,
2131
+ tokens: doc.tokens,
2132
+ termFrequencies: Object.fromEntries(doc.termFrequencies),
2133
+ length: doc.length,
2134
+ metadata: doc.metadata
2135
+ });
1182
2136
  }
2137
+ return {
2138
+ k1: this.k1,
2139
+ b: this.b,
2140
+ documents,
2141
+ avgDocLength: this.#avgDocLength
2142
+ };
1183
2143
  }
1184
2144
  /**
1185
- * Verify that the resolved path doesn't escape basePath via symlinks.
1186
- * Uses realpath to resolve symlinks and check the actual target.
2145
+ * Deserialize an index from a JSON object
1187
2146
  */
1188
- async assertPathContained(absolutePath) {
1189
- if (!this._contained) return;
1190
- let baseReal;
1191
- try {
1192
- baseReal = await fs2__namespace.realpath(this._basePath);
1193
- } catch (error) {
1194
- if (isEnoentError(error)) {
1195
- throw new DirectoryNotFoundError(this._basePath);
1196
- }
1197
- throw error;
1198
- }
1199
- let targetReal;
1200
- try {
1201
- targetReal = await fs2__namespace.realpath(absolutePath);
1202
- } catch (error) {
1203
- if (isEnoentError(error)) {
1204
- let parentPath = absolutePath;
1205
- while (true) {
1206
- const nextParent = nodePath__namespace.dirname(parentPath);
1207
- if (nextParent === parentPath) {
1208
- throw new DirectoryNotFoundError(absolutePath);
1209
- }
1210
- parentPath = nextParent;
1211
- try {
1212
- targetReal = await fs2__namespace.realpath(parentPath);
1213
- break;
1214
- } catch (parentError) {
1215
- if (!isEnoentError(parentError)) {
1216
- throw parentError;
1217
- }
1218
- }
2147
+ static deserialize(data, tokenizeOptions = {}) {
2148
+ const index = new _BM25Index({ k1: data.k1, b: data.b }, tokenizeOptions);
2149
+ for (const doc of data.documents) {
2150
+ const termFrequencies = new Map(Object.entries(doc.termFrequencies));
2151
+ const document = {
2152
+ id: doc.id,
2153
+ content: doc.content,
2154
+ tokens: doc.tokens,
2155
+ termFrequencies,
2156
+ length: doc.length,
2157
+ metadata: doc.metadata
2158
+ };
2159
+ index.#documents.set(doc.id, document);
2160
+ index.#docCount++;
2161
+ for (const term of termFrequencies.keys()) {
2162
+ if (!index.#invertedIndex.has(term)) {
2163
+ index.#invertedIndex.set(term, /* @__PURE__ */ new Set());
1219
2164
  }
1220
- } else {
1221
- throw error;
2165
+ index.#invertedIndex.get(term).add(doc.id);
2166
+ index.#documentFrequency.set(term, (index.#documentFrequency.get(term) || 0) + 1);
1222
2167
  }
1223
2168
  }
1224
- if (targetReal !== baseReal && !targetReal.startsWith(baseReal + nodePath__namespace.sep)) {
1225
- throw new PermissionError(absolutePath, "access");
1226
- }
2169
+ index.#avgDocLength = data.avgDocLength;
2170
+ return index;
1227
2171
  }
1228
- async ensureInitialized() {
1229
- if (this.status !== "running") {
1230
- await this.init();
2172
+ /**
2173
+ * Update average document length after add/remove operations
2174
+ */
2175
+ #updateAvgDocLength() {
2176
+ if (this.#docCount === 0) {
2177
+ this.#avgDocLength = 0;
2178
+ return;
1231
2179
  }
1232
- }
1233
- async readFile(inputPath, options) {
1234
- this.logger.debug("Reading file", { path: inputPath, encoding: options?.encoding });
1235
- await this.ensureInitialized();
1236
- const absolutePath = this.resolvePath(inputPath);
1237
- await this.assertPathContained(absolutePath);
1238
- try {
1239
- const stats = await fs2__namespace.stat(absolutePath);
1240
- if (stats.isDirectory()) {
1241
- throw new IsDirectoryError(inputPath);
1242
- }
1243
- if (options?.encoding) {
1244
- return await fs2__namespace.readFile(absolutePath, { encoding: options.encoding });
1245
- }
1246
- return await fs2__namespace.readFile(absolutePath);
1247
- } catch (error) {
1248
- if (error instanceof IsDirectoryError) throw error;
1249
- if (isEnoentError(error)) {
1250
- throw new FileNotFoundError(inputPath);
1251
- }
1252
- throw error;
2180
+ let totalLength = 0;
2181
+ for (const doc of this.#documents.values()) {
2182
+ totalLength += doc.length;
1253
2183
  }
2184
+ this.#avgDocLength = totalLength / this.#docCount;
1254
2185
  }
1255
- async writeFile(inputPath, content, options) {
1256
- const contentSize = Buffer.isBuffer(content) ? content.length : content.length;
1257
- this.logger.debug("Writing file", { path: inputPath, size: contentSize, recursive: options?.recursive });
1258
- await this.ensureInitialized();
1259
- this.assertWritable("writeFile");
1260
- const absolutePath = this.resolvePath(inputPath);
1261
- await this.assertPathContained(absolutePath);
1262
- if (options?.recursive === false) {
1263
- const dir = nodePath__namespace.dirname(absolutePath);
1264
- const parentPath = nodePath__namespace.dirname(inputPath);
1265
- try {
1266
- const stat3 = await fs2__namespace.stat(dir);
1267
- if (!stat3.isDirectory()) {
1268
- throw new NotDirectoryError(parentPath);
1269
- }
1270
- } catch (error) {
1271
- if (error instanceof NotDirectoryError) throw error;
1272
- if (isEnoentError(error)) {
1273
- throw new DirectoryNotFoundError(parentPath);
1274
- }
1275
- throw error;
1276
- }
2186
+ /**
2187
+ * Compute IDF (Inverse Document Frequency) for a term
2188
+ */
2189
+ #computeIDF(df) {
2190
+ return Math.log((this.#docCount - df + 0.5) / (df + 0.5) + 1);
2191
+ }
2192
+ /**
2193
+ * Compute the BM25 score component for a single term
2194
+ */
2195
+ #computeTermScore(tf, docLength, idf) {
2196
+ const numerator = tf * (this.k1 + 1);
2197
+ const denominator = tf + this.k1 * (1 - this.b + this.b * (docLength / this.#avgDocLength));
2198
+ return idf * (numerator / denominator);
2199
+ }
2200
+ };
2201
+
2202
+ // src/workspace/search/search-engine.ts
2203
+ var SearchEngine = class {
2204
+ /** BM25 index for keyword search */
2205
+ #bm25Index;
2206
+ /** Tokenization options (stored for lineRange computation) */
2207
+ #tokenizeOptions;
2208
+ /** Vector configuration */
2209
+ #vectorConfig;
2210
+ /** Whether to use lazy vector indexing */
2211
+ #lazyVectorIndex;
2212
+ /** Documents pending vector indexing (for lazy mode) */
2213
+ #pendingVectorDocs = [];
2214
+ /** Whether vector index has been built (for lazy mode) */
2215
+ #vectorIndexBuilt = false;
2216
+ constructor(config = {}) {
2217
+ if (config.bm25 !== void 0) {
2218
+ this.#tokenizeOptions = config.bm25.tokenize;
2219
+ this.#bm25Index = new BM25Index(config.bm25.bm25, this.#tokenizeOptions);
1277
2220
  }
1278
- if (options?.recursive !== false) {
1279
- const dir = nodePath__namespace.dirname(absolutePath);
1280
- await fs2__namespace.mkdir(dir, { recursive: true });
2221
+ if (config.vector) {
2222
+ this.#vectorConfig = config.vector;
1281
2223
  }
1282
- const writeFlag = options?.overwrite === false ? "wx" : "w";
1283
- try {
1284
- await fs2__namespace.writeFile(absolutePath, this.toBuffer(content), { flag: writeFlag });
1285
- } catch (error) {
1286
- if (options?.overwrite === false && isEexistError(error)) {
1287
- throw new FileExistsError(inputPath);
2224
+ this.#lazyVectorIndex = config.lazyVectorIndex ?? false;
2225
+ }
2226
+ // ===========================================================================
2227
+ // Public API
2228
+ // ===========================================================================
2229
+ /**
2230
+ * Index a document for search
2231
+ */
2232
+ async index(doc) {
2233
+ const metadata = {
2234
+ ...doc.metadata
2235
+ };
2236
+ if (doc.startLineOffset !== void 0) {
2237
+ metadata._startLineOffset = doc.startLineOffset;
2238
+ }
2239
+ if (this.#bm25Index) {
2240
+ this.#bm25Index.add(doc.id, doc.content, metadata);
2241
+ }
2242
+ if (this.#vectorConfig) {
2243
+ const docWithMergedMetadata = { ...doc, metadata };
2244
+ if (this.#lazyVectorIndex) {
2245
+ this.#pendingVectorDocs.push(docWithMergedMetadata);
2246
+ this.#vectorIndexBuilt = false;
2247
+ } else {
2248
+ await this.#indexVector(docWithMergedMetadata);
1288
2249
  }
1289
- throw error;
1290
2250
  }
1291
2251
  }
1292
- async appendFile(inputPath, content) {
1293
- const contentSize = Buffer.isBuffer(content) ? content.length : content.length;
1294
- this.logger.debug("Appending to file", { path: inputPath, size: contentSize });
1295
- await this.ensureInitialized();
1296
- this.assertWritable("appendFile");
1297
- const absolutePath = this.resolvePath(inputPath);
1298
- await this.assertPathContained(absolutePath);
1299
- const dir = nodePath__namespace.dirname(absolutePath);
1300
- await fs2__namespace.mkdir(dir, { recursive: true });
1301
- await fs2__namespace.appendFile(absolutePath, this.toBuffer(content));
2252
+ /**
2253
+ * Index multiple documents
2254
+ */
2255
+ async indexMany(docs) {
2256
+ for (const doc of docs) {
2257
+ await this.index(doc);
2258
+ }
1302
2259
  }
1303
- async deleteFile(inputPath, options) {
1304
- this.logger.debug("Deleting file", { path: inputPath, force: options?.force });
1305
- await this.ensureInitialized();
1306
- this.assertWritable("deleteFile");
1307
- const absolutePath = this.resolvePath(inputPath);
1308
- await this.assertPathContained(absolutePath);
1309
- try {
1310
- const stats = await fs2__namespace.stat(absolutePath);
1311
- if (stats.isDirectory()) {
1312
- throw new IsDirectoryError(inputPath);
2260
+ /**
2261
+ * Remove a document from the index
2262
+ */
2263
+ async remove(id) {
2264
+ if (this.#bm25Index) {
2265
+ this.#bm25Index.remove(id);
2266
+ }
2267
+ if (this.#vectorConfig) {
2268
+ try {
2269
+ await this.#vectorConfig.vectorStore.deleteVector({
2270
+ indexName: this.#vectorConfig.indexName,
2271
+ id
2272
+ });
2273
+ } catch {
1313
2274
  }
1314
- await fs2__namespace.unlink(absolutePath);
1315
- } catch (error) {
1316
- if (error instanceof IsDirectoryError) throw error;
1317
- if (isEnoentError(error)) {
1318
- if (!options?.force) {
1319
- throw new FileNotFoundError(inputPath);
1320
- }
1321
- } else {
1322
- throw error;
2275
+ if (this.#lazyVectorIndex) {
2276
+ this.#pendingVectorDocs = this.#pendingVectorDocs.filter((d) => d.id !== id);
1323
2277
  }
1324
2278
  }
1325
2279
  }
1326
- async copyFile(src, dest, options) {
1327
- this.logger.debug("Copying file", { src, dest, recursive: options?.recursive });
1328
- await this.ensureInitialized();
1329
- this.assertWritable("copyFile");
1330
- const srcPath = this.resolvePath(src);
1331
- const destPath = this.resolvePath(dest);
1332
- await this.assertPathContained(srcPath);
1333
- await this.assertPathContained(destPath);
1334
- try {
1335
- const stats = await fs2__namespace.stat(srcPath);
1336
- if (stats.isDirectory()) {
1337
- if (!options?.recursive) {
1338
- throw new IsDirectoryError(src);
1339
- }
1340
- await this.copyDirectory(srcPath, destPath, options);
1341
- } else {
1342
- await fs2__namespace.mkdir(nodePath__namespace.dirname(destPath), { recursive: true });
1343
- const copyFlags = options?.overwrite === false ? fs.constants.COPYFILE_EXCL : 0;
1344
- try {
1345
- await fs2__namespace.copyFile(srcPath, destPath, copyFlags);
1346
- } catch (error) {
1347
- if (options?.overwrite === false && isEexistError(error)) {
1348
- throw new FileExistsError(dest);
1349
- }
1350
- throw error;
1351
- }
1352
- }
1353
- } catch (error) {
1354
- if (error instanceof IsDirectoryError || error instanceof FileExistsError) throw error;
1355
- if (isEnoentError(error)) {
1356
- throw new FileNotFoundError(src);
1357
- }
1358
- throw error;
2280
+ /**
2281
+ * Clear all indexed documents
2282
+ */
2283
+ clear() {
2284
+ if (this.#bm25Index) {
2285
+ this.#bm25Index.clear();
1359
2286
  }
2287
+ this.#pendingVectorDocs = [];
2288
+ this.#vectorIndexBuilt = false;
1360
2289
  }
1361
- async copyDirectory(src, dest, options) {
1362
- await this.ensureInitialized();
1363
- await fs2__namespace.mkdir(dest, { recursive: true });
1364
- const entries = await fs2__namespace.readdir(src, { withFileTypes: true });
1365
- for (const entry of entries) {
1366
- const srcEntry = nodePath__namespace.join(src, entry.name);
1367
- const destEntry = nodePath__namespace.join(dest, entry.name);
1368
- await this.assertPathContained(srcEntry);
1369
- await this.assertPathContained(destEntry);
1370
- if (entry.isDirectory()) {
1371
- await this.copyDirectory(srcEntry, destEntry, options);
1372
- } else {
1373
- const copyFlags = options?.overwrite === false ? fs.constants.COPYFILE_EXCL : 0;
1374
- try {
1375
- await fs2__namespace.copyFile(srcEntry, destEntry, copyFlags);
1376
- } catch (error) {
1377
- if (options?.overwrite === false && isEexistError(error)) {
1378
- continue;
1379
- }
1380
- throw error;
1381
- }
1382
- }
2290
+ /**
2291
+ * Search for documents
2292
+ */
2293
+ async search(query, options = {}) {
2294
+ const { topK = 10, minScore, mode, vectorWeight = 0.5, filter } = options;
2295
+ const effectiveMode = this.#determineSearchMode(mode);
2296
+ if (effectiveMode === "bm25") {
2297
+ return this.#searchBM25(query, topK, minScore);
1383
2298
  }
2299
+ if (effectiveMode === "vector") {
2300
+ return this.#searchVector(query, topK, minScore, filter);
2301
+ }
2302
+ return this.#searchHybrid(query, topK, minScore, vectorWeight, filter);
1384
2303
  }
1385
- async moveFile(src, dest, options) {
1386
- this.logger.debug("Moving file", { src, dest, overwrite: options?.overwrite });
1387
- await this.ensureInitialized();
1388
- this.assertWritable("moveFile");
1389
- const srcPath = this.resolvePath(src);
1390
- const destPath = this.resolvePath(dest);
1391
- await this.assertPathContained(srcPath);
1392
- await this.assertPathContained(destPath);
1393
- try {
1394
- await fs2__namespace.mkdir(nodePath__namespace.dirname(destPath), { recursive: true });
1395
- if (options?.overwrite === false) {
1396
- await this.copyFile(src, dest, { ...options, overwrite: false });
1397
- await fs2__namespace.rm(srcPath, { recursive: true, force: true });
1398
- return;
2304
+ /**
2305
+ * Check if BM25 search is available
2306
+ */
2307
+ get canBM25() {
2308
+ return !!this.#bm25Index;
2309
+ }
2310
+ /**
2311
+ * Check if vector search is available
2312
+ */
2313
+ get canVector() {
2314
+ return !!this.#vectorConfig;
2315
+ }
2316
+ /**
2317
+ * Check if hybrid search is available
2318
+ */
2319
+ get canHybrid() {
2320
+ return this.canBM25 && this.canVector;
2321
+ }
2322
+ /**
2323
+ * Get the BM25 index (for serialization/debugging)
2324
+ */
2325
+ get bm25Index() {
2326
+ return this.#bm25Index;
2327
+ }
2328
+ // ===========================================================================
2329
+ // Private Methods
2330
+ // ===========================================================================
2331
+ /**
2332
+ * Determine the effective search mode
2333
+ */
2334
+ #determineSearchMode(requestedMode) {
2335
+ if (requestedMode) {
2336
+ if (requestedMode === "vector" && !this.canVector) {
2337
+ throw new Error("Vector search requires vector configuration.");
1399
2338
  }
1400
- try {
1401
- await fs2__namespace.rename(srcPath, destPath);
1402
- } catch (error) {
1403
- const code = error.code;
1404
- if (code !== "EXDEV") {
1405
- throw error;
1406
- }
1407
- await this.copyFile(src, dest, options);
1408
- await fs2__namespace.rm(srcPath, { recursive: true, force: true });
2339
+ if (requestedMode === "bm25" && !this.canBM25) {
2340
+ throw new Error("BM25 search requires BM25 configuration.");
1409
2341
  }
1410
- } catch (error) {
1411
- if (error instanceof FileExistsError) throw error;
1412
- if (isEnoentError(error)) {
1413
- throw new FileNotFoundError(src);
2342
+ if (requestedMode === "hybrid" && !this.canHybrid) {
2343
+ throw new Error("Hybrid search requires both vector and BM25 configuration.");
1414
2344
  }
1415
- throw error;
2345
+ return requestedMode;
2346
+ }
2347
+ if (this.canHybrid) {
2348
+ return "hybrid";
2349
+ }
2350
+ if (this.canVector) {
2351
+ return "vector";
2352
+ }
2353
+ if (this.canBM25) {
2354
+ return "bm25";
2355
+ }
2356
+ throw new Error("No search configuration available. Provide bm25 or vector config.");
2357
+ }
2358
+ /**
2359
+ * Index a single document in the vector store
2360
+ */
2361
+ async #indexVector(doc) {
2362
+ if (!this.#vectorConfig) return;
2363
+ const { vectorStore, embedder, indexName } = this.#vectorConfig;
2364
+ const embedding = await embedder(doc.content);
2365
+ await vectorStore.upsert({
2366
+ indexName,
2367
+ vectors: [embedding],
2368
+ metadata: [
2369
+ {
2370
+ id: doc.id,
2371
+ text: doc.content,
2372
+ ...doc.metadata
2373
+ }
2374
+ ],
2375
+ ids: [doc.id]
2376
+ });
2377
+ }
2378
+ /**
2379
+ * Ensure vector index is built (for lazy mode)
2380
+ */
2381
+ async #ensureVectorIndex() {
2382
+ if (!this.#lazyVectorIndex || this.#vectorIndexBuilt || this.#pendingVectorDocs.length === 0) {
2383
+ return;
1416
2384
  }
2385
+ for (const doc of this.#pendingVectorDocs) {
2386
+ await this.#indexVector(doc);
2387
+ }
2388
+ this.#pendingVectorDocs = [];
2389
+ this.#vectorIndexBuilt = true;
1417
2390
  }
1418
- async mkdir(inputPath, options) {
1419
- this.logger.debug("Creating directory", { path: inputPath, recursive: options?.recursive });
1420
- await this.ensureInitialized();
1421
- this.assertWritable("mkdir");
1422
- const absolutePath = this.resolvePath(inputPath);
1423
- await this.assertPathContained(absolutePath);
1424
- try {
1425
- await fs2__namespace.mkdir(absolutePath, { recursive: options?.recursive ?? true });
1426
- } catch (error) {
1427
- if (isEexistError(error)) {
1428
- const stats = await fs2__namespace.stat(absolutePath);
1429
- if (!stats.isDirectory()) {
1430
- throw new FileExistsError(inputPath);
1431
- }
1432
- } else if (isEnoentError(error)) {
1433
- const parentPath = nodePath__namespace.dirname(inputPath);
1434
- throw new DirectoryNotFoundError(parentPath);
1435
- } else {
1436
- throw error;
1437
- }
2391
+ /**
2392
+ * BM25 keyword search
2393
+ */
2394
+ #searchBM25(query, topK, minScore) {
2395
+ if (!this.#bm25Index) {
2396
+ throw new Error("BM25 search requires BM25 configuration.");
1438
2397
  }
2398
+ const results = this.#bm25Index.search(query, topK, minScore);
2399
+ const queryTokens = tokenize(query, this.#tokenizeOptions);
2400
+ return results.map((result) => {
2401
+ const rawLineRange = findLineRange(result.content, queryTokens, this.#tokenizeOptions);
2402
+ const lineRange = this.#adjustLineRange(rawLineRange, result.metadata);
2403
+ const { _startLineOffset, ...cleanMetadata } = result.metadata ?? {};
2404
+ return {
2405
+ id: result.id,
2406
+ content: result.content,
2407
+ score: result.score,
2408
+ lineRange,
2409
+ metadata: Object.keys(cleanMetadata).length > 0 ? cleanMetadata : void 0,
2410
+ scoreDetails: { bm25: result.score }
2411
+ };
2412
+ });
1439
2413
  }
1440
- async rmdir(inputPath, options) {
1441
- this.logger.debug("Removing directory", { path: inputPath, recursive: options?.recursive, force: options?.force });
1442
- await this.ensureInitialized();
1443
- this.assertWritable("rmdir");
1444
- const absolutePath = this.resolvePath(inputPath);
1445
- await this.assertPathContained(absolutePath);
1446
- try {
1447
- const stats = await fs2__namespace.stat(absolutePath);
1448
- if (!stats.isDirectory()) {
1449
- throw new NotDirectoryError(inputPath);
1450
- }
1451
- if (options?.recursive) {
1452
- await fs2__namespace.rm(absolutePath, { recursive: true, force: options?.force ?? false });
1453
- } else {
1454
- const entries = await fs2__namespace.readdir(absolutePath);
1455
- if (entries.length > 0) {
1456
- throw new DirectoryNotEmptyError(inputPath);
1457
- }
1458
- await fs2__namespace.rmdir(absolutePath);
1459
- }
1460
- } catch (error) {
1461
- if (error instanceof NotDirectoryError || error instanceof DirectoryNotEmptyError) {
1462
- throw error;
1463
- }
1464
- if (isEnoentError(error)) {
1465
- if (!options?.force) {
1466
- throw new DirectoryNotFoundError(inputPath);
1467
- }
1468
- } else {
1469
- throw error;
2414
+ /**
2415
+ * Vector semantic search
2416
+ */
2417
+ async #searchVector(query, topK, minScore, filter) {
2418
+ if (!this.#vectorConfig) {
2419
+ throw new Error("Vector search requires vector configuration.");
2420
+ }
2421
+ await this.#ensureVectorIndex();
2422
+ const { vectorStore, embedder, indexName } = this.#vectorConfig;
2423
+ const queryEmbedding = await embedder(query);
2424
+ const vectorResults = await vectorStore.query({
2425
+ indexName,
2426
+ queryVector: queryEmbedding,
2427
+ topK,
2428
+ filter
2429
+ });
2430
+ const queryTokens = tokenize(query, this.#tokenizeOptions);
2431
+ const results = [];
2432
+ for (const result of vectorResults) {
2433
+ if (minScore !== void 0 && result.score < minScore) {
2434
+ continue;
1470
2435
  }
2436
+ const id = result.metadata?.id ?? result.id;
2437
+ const content = result.metadata?.text ?? "";
2438
+ const { id: _id, text: _text, _startLineOffset, ...restMetadata } = result.metadata ?? {};
2439
+ const rawLineRange = findLineRange(content, queryTokens, this.#tokenizeOptions);
2440
+ const lineRange = this.#adjustLineRange(rawLineRange, result.metadata);
2441
+ results.push({
2442
+ id,
2443
+ content,
2444
+ score: result.score,
2445
+ lineRange,
2446
+ metadata: Object.keys(restMetadata).length > 0 ? restMetadata : void 0,
2447
+ scoreDetails: { vector: result.score }
2448
+ });
1471
2449
  }
2450
+ return results;
1472
2451
  }
1473
- async readdir(inputPath, options) {
1474
- this.logger.debug("Reading directory", { path: inputPath, recursive: options?.recursive });
1475
- await this.ensureInitialized();
1476
- const absolutePath = this.resolvePath(inputPath);
1477
- await this.assertPathContained(absolutePath);
1478
- try {
1479
- const stats = await fs2__namespace.stat(absolutePath);
1480
- if (!stats.isDirectory()) {
1481
- throw new NotDirectoryError(inputPath);
1482
- }
1483
- const entries = await fs2__namespace.readdir(absolutePath, { withFileTypes: true });
1484
- const result = [];
1485
- for (const entry of entries) {
1486
- const entryPath = nodePath__namespace.join(absolutePath, entry.name);
1487
- if (options?.extension) {
1488
- const extensions = Array.isArray(options.extension) ? options.extension : [options.extension];
1489
- if (entry.isFile()) {
1490
- const ext = nodePath__namespace.extname(entry.name);
1491
- if (!extensions.some((e) => e === ext || e === ext.slice(1))) {
1492
- continue;
1493
- }
1494
- }
1495
- }
1496
- const isSymlink = entry.isSymbolicLink();
1497
- let symlinkTarget;
1498
- let resolvedType = "file";
1499
- if (isSymlink) {
1500
- try {
1501
- symlinkTarget = await fs2__namespace.readlink(entryPath);
1502
- const targetStat = await fs2__namespace.stat(entryPath);
1503
- resolvedType = targetStat.isDirectory() ? "directory" : "file";
1504
- } catch {
1505
- resolvedType = "file";
1506
- }
1507
- } else {
1508
- resolvedType = entry.isDirectory() ? "directory" : "file";
1509
- }
1510
- const fileEntry = {
1511
- name: entry.name,
1512
- type: resolvedType,
1513
- isSymlink: isSymlink || void 0,
1514
- symlinkTarget
1515
- };
1516
- if (resolvedType === "file" && !isSymlink) {
1517
- try {
1518
- const stat3 = await fs2__namespace.stat(entryPath);
1519
- fileEntry.size = stat3.size;
1520
- } catch {
1521
- }
1522
- }
1523
- result.push(fileEntry);
1524
- if (options?.recursive && resolvedType === "directory") {
1525
- const depth = options.maxDepth ?? 100;
1526
- if (depth > 0) {
1527
- const subEntries = await this.readdir(this.toRelativePath(entryPath), { ...options, maxDepth: depth - 1 });
1528
- result.push(
1529
- ...subEntries.map((e) => ({
1530
- ...e,
1531
- name: `${entry.name}/${e.name}`
1532
- }))
1533
- );
1534
- }
2452
+ /**
2453
+ * Hybrid search combining vector and BM25 scores
2454
+ */
2455
+ async #searchHybrid(query, topK, minScore, vectorWeight = 0.5, filter) {
2456
+ const expandedTopK = Math.min(topK * 2, 50);
2457
+ const [vectorResults, bm25Results] = await Promise.all([
2458
+ this.#searchVector(query, expandedTopK, void 0, filter),
2459
+ Promise.resolve(this.#searchBM25(query, expandedTopK, void 0))
2460
+ ]);
2461
+ const normalizedBM25 = this.#normalizeBM25Scores(bm25Results);
2462
+ const bm25Map = /* @__PURE__ */ new Map();
2463
+ for (const result of normalizedBM25) {
2464
+ bm25Map.set(result.id, result);
2465
+ }
2466
+ const vectorMap = /* @__PURE__ */ new Map();
2467
+ for (const result of vectorResults) {
2468
+ vectorMap.set(result.id, result);
2469
+ }
2470
+ const combinedResults = /* @__PURE__ */ new Map();
2471
+ const allIds = /* @__PURE__ */ new Set([...vectorMap.keys(), ...bm25Map.keys()]);
2472
+ const bm25Weight = 1 - vectorWeight;
2473
+ for (const id of allIds) {
2474
+ const vectorResult = vectorMap.get(id);
2475
+ const bm25Result = bm25Map.get(id);
2476
+ const vectorScore = vectorResult?.scoreDetails?.vector ?? 0;
2477
+ const bm25Score = bm25Result?.score ?? 0;
2478
+ const combinedScore = vectorWeight * vectorScore + bm25Weight * bm25Score;
2479
+ const baseResult = vectorResult ?? bm25Result;
2480
+ combinedResults.set(id, {
2481
+ id,
2482
+ content: baseResult.content,
2483
+ score: combinedScore,
2484
+ lineRange: bm25Result?.lineRange ?? vectorResult?.lineRange,
2485
+ metadata: baseResult.metadata,
2486
+ scoreDetails: {
2487
+ vector: vectorResult?.scoreDetails?.vector,
2488
+ bm25: bm25Result?.scoreDetails?.bm25
1535
2489
  }
1536
- }
1537
- return result;
1538
- } catch (error) {
1539
- if (error instanceof NotDirectoryError) throw error;
1540
- if (isEnoentError(error)) {
1541
- throw new DirectoryNotFoundError(inputPath);
1542
- }
1543
- throw error;
2490
+ });
1544
2491
  }
2492
+ let results = Array.from(combinedResults.values());
2493
+ results.sort((a, b) => b.score - a.score);
2494
+ if (minScore !== void 0) {
2495
+ results = results.filter((r) => r.score >= minScore);
2496
+ }
2497
+ return results.slice(0, topK);
1545
2498
  }
1546
- async exists(inputPath) {
1547
- await this.ensureInitialized();
1548
- const absolutePath = this.resolvePath(inputPath);
1549
- await this.assertPathContained(absolutePath);
1550
- return fsExists(absolutePath);
2499
+ /**
2500
+ * Normalize BM25 scores to 0-1 range using min-max normalization
2501
+ */
2502
+ #normalizeBM25Scores(results) {
2503
+ if (results.length === 0) return results;
2504
+ const scores = results.map((r) => r.scoreDetails?.bm25 ?? r.score);
2505
+ const maxScore = Math.max(...scores);
2506
+ const minScore = Math.min(...scores);
2507
+ const range = maxScore - minScore;
2508
+ if (range === 0) {
2509
+ return results.map((r) => ({ ...r, score: 1 }));
2510
+ }
2511
+ return results.map((r) => ({
2512
+ ...r,
2513
+ score: ((r.scoreDetails?.bm25 ?? r.score) - minScore) / range
2514
+ }));
1551
2515
  }
1552
- async stat(inputPath) {
1553
- await this.ensureInitialized();
1554
- const absolutePath = this.resolvePath(inputPath);
1555
- await this.assertPathContained(absolutePath);
1556
- const result = await fsStat(absolutePath, inputPath);
2516
+ /**
2517
+ * Adjust line range for chunked documents.
2518
+ * If the document has a _startLineOffset in metadata, adjust the line range
2519
+ * to reflect the original document's line numbers.
2520
+ */
2521
+ #adjustLineRange(lineRange, metadata) {
2522
+ if (!lineRange) return void 0;
2523
+ const startLineOffset = metadata?._startLineOffset;
2524
+ if (typeof startLineOffset !== "number") {
2525
+ return lineRange;
2526
+ }
1557
2527
  return {
1558
- ...result,
1559
- path: this.toRelativePath(absolutePath)
2528
+ start: lineRange.start + startLineOffset - 1,
2529
+ end: lineRange.end + startLineOffset - 1
1560
2530
  };
1561
2531
  }
1562
- async init() {
1563
- this.logger.debug("Initializing filesystem", { basePath: this._basePath });
1564
- this.status = "starting";
1565
- try {
1566
- await fs2__namespace.mkdir(this._basePath, { recursive: true });
1567
- this.status = "running";
1568
- this.logger.debug("Filesystem initialized", { basePath: this._basePath, status: this.status });
1569
- } catch (error) {
1570
- this.status = "error";
1571
- this.logger.error("Failed to initialize filesystem", { basePath: this._basePath, error });
1572
- throw error;
1573
- }
2532
+ };
2533
+
2534
+ // src/workspace/skills/schemas.ts
2535
+ var SKILL_LIMITS = {
2536
+ /** Recommended max tokens for instructions */
2537
+ MAX_INSTRUCTION_TOKENS: 5e3,
2538
+ /** Recommended max lines for SKILL.md */
2539
+ MAX_INSTRUCTION_LINES: 500,
2540
+ /** Max characters for name field */
2541
+ MAX_NAME_LENGTH: 64,
2542
+ /** Max characters for description field */
2543
+ MAX_DESCRIPTION_LENGTH: 1024};
2544
+ function validateSkillName(name) {
2545
+ const errors = [];
2546
+ const fieldPath = "name";
2547
+ if (typeof name !== "string") {
2548
+ errors.push(`${fieldPath}: Expected string, received ${typeof name}`);
2549
+ return errors;
1574
2550
  }
1575
- async destroy() {
2551
+ if (name.length === 0) {
2552
+ errors.push(`${fieldPath}: Skill name cannot be empty`);
2553
+ return errors;
1576
2554
  }
1577
- getInfo() {
1578
- return {
1579
- id: this.id,
1580
- name: this.name,
1581
- provider: this.provider,
1582
- readOnly: this.readOnly,
1583
- basePath: this.basePath,
1584
- status: this.status
1585
- };
2555
+ if (name.length > SKILL_LIMITS.MAX_NAME_LENGTH) {
2556
+ errors.push(`${fieldPath}: Skill name must be ${SKILL_LIMITS.MAX_NAME_LENGTH} characters or less`);
1586
2557
  }
1587
- getInstructions() {
1588
- return `Local filesystem at "${this.basePath}". Files at workspace path "/foo" are stored at "${this.basePath}/foo" on disk.`;
2558
+ if (!/^[a-z0-9-]+$/.test(name)) {
2559
+ errors.push(`${fieldPath}: Skill name must contain only lowercase letters, numbers, and hyphens`);
1589
2560
  }
1590
- };
1591
- var InMemoryFileReadTracker = class {
1592
- records = /* @__PURE__ */ new Map();
1593
- recordRead(path4, modifiedAt) {
1594
- const normalizedPath = this.normalizePath(path4);
1595
- this.records.set(normalizedPath, {
1596
- path: normalizedPath,
1597
- readAt: /* @__PURE__ */ new Date(),
1598
- modifiedAtRead: modifiedAt
1599
- });
2561
+ if (name.startsWith("-") || name.endsWith("-")) {
2562
+ errors.push(`${fieldPath}: Skill name must not start or end with a hyphen`);
1600
2563
  }
1601
- getReadRecord(path4) {
1602
- return this.records.get(this.normalizePath(path4));
2564
+ if (name.includes("--")) {
2565
+ errors.push(`${fieldPath}: Skill name must not contain consecutive hyphens`);
1603
2566
  }
1604
- needsReRead(path4, currentModifiedAt) {
1605
- const record = this.getReadRecord(path4);
1606
- if (!record) {
1607
- return {
1608
- needsReRead: true,
1609
- reason: `File "${path4}" has not been read. You must read a file before writing to it.`
1610
- };
1611
- }
1612
- if (currentModifiedAt.getTime() > record.modifiedAtRead.getTime()) {
1613
- return {
1614
- needsReRead: true,
1615
- reason: `File "${path4}" was modified since last read (read at: ${record.modifiedAtRead.toISOString()}, current: ${currentModifiedAt.toISOString()}). Please re-read the file to get the latest contents.`
1616
- };
1617
- }
1618
- return { needsReRead: false };
2567
+ return errors;
2568
+ }
2569
+ function validateSkillDescription(description) {
2570
+ const errors = [];
2571
+ const fieldPath = "description";
2572
+ if (typeof description !== "string") {
2573
+ errors.push(`${fieldPath}: Expected string, received ${typeof description}`);
2574
+ return errors;
1619
2575
  }
1620
- clearReadRecord(path4) {
1621
- this.records.delete(this.normalizePath(path4));
2576
+ if (description.length === 0) {
2577
+ errors.push(`${fieldPath}: Skill description cannot be empty`);
2578
+ return errors;
1622
2579
  }
1623
- clear() {
1624
- this.records.clear();
2580
+ if (description.length > SKILL_LIMITS.MAX_DESCRIPTION_LENGTH) {
2581
+ errors.push(`${fieldPath}: Skill description must be ${SKILL_LIMITS.MAX_DESCRIPTION_LENGTH} characters or less`);
1625
2582
  }
1626
- normalizePath(pathStr) {
1627
- const normalized = nodePath__namespace.posix.normalize(pathStr.replace(/\\/g, "/"));
1628
- return normalized.replace(/\/$/, "") || "/";
2583
+ if (description.trim().length === 0) {
2584
+ errors.push(`${fieldPath}: Skill description cannot be only whitespace`);
1629
2585
  }
1630
- };
1631
-
1632
- // src/workspace/skills/local-skill-source.ts
2586
+ return errors;
2587
+ }
2588
+ function validateSkillLicense(license) {
2589
+ const errors = [];
2590
+ const fieldPath = "license";
2591
+ if (license === void 0 || license === null) {
2592
+ return errors;
2593
+ }
2594
+ if (typeof license !== "string") {
2595
+ errors.push(`${fieldPath}: Expected string, received ${typeof license}`);
2596
+ }
2597
+ return errors;
2598
+ }
2599
+ function validateSkillCompatibility(_compatibility) {
2600
+ return [];
2601
+ }
2602
+ function validateSkillMetadataField(metadata) {
2603
+ const errors = [];
2604
+ const fieldPath = "metadata";
2605
+ if (metadata === void 0 || metadata === null) {
2606
+ return errors;
2607
+ }
2608
+ if (typeof metadata !== "object" || Array.isArray(metadata)) {
2609
+ errors.push(`${fieldPath}: Expected object, received ${Array.isArray(metadata) ? "array" : typeof metadata}`);
2610
+ return errors;
2611
+ }
2612
+ return errors;
2613
+ }
2614
+ function estimateTokens(text) {
2615
+ const words = text.split(/\s+/).filter(Boolean).length;
2616
+ return Math.ceil(words * 1.3);
2617
+ }
2618
+ function countLines(text) {
2619
+ return text.split("\n").length;
2620
+ }
2621
+ function validateSkillMetadata(metadata, dirName, instructions) {
2622
+ const errors = [];
2623
+ const warnings = [];
2624
+ if (typeof metadata !== "object" || metadata === null || Array.isArray(metadata)) {
2625
+ errors.push(
2626
+ `Expected object, received ${metadata === null ? "null" : Array.isArray(metadata) ? "array" : typeof metadata}`
2627
+ );
2628
+ return { valid: false, errors, warnings };
2629
+ }
2630
+ const data = metadata;
2631
+ errors.push(...validateSkillName(data.name));
2632
+ errors.push(...validateSkillDescription(data.description));
2633
+ errors.push(...validateSkillLicense(data.license));
2634
+ errors.push(...validateSkillCompatibility());
2635
+ errors.push(...validateSkillMetadataField(data.metadata));
2636
+ if (dirName && typeof data.name === "string" && data.name !== dirName) {
2637
+ errors.push(`Skill name "${data.name}" must match directory name "${dirName}"`);
2638
+ }
2639
+ if (instructions) {
2640
+ const lineCount = countLines(instructions);
2641
+ const tokenEstimate = estimateTokens(instructions);
2642
+ if (lineCount > SKILL_LIMITS.MAX_INSTRUCTION_LINES) {
2643
+ warnings.push(
2644
+ `Instructions have ${lineCount} lines (recommended: <${SKILL_LIMITS.MAX_INSTRUCTION_LINES}). Consider moving content to references/.`
2645
+ );
2646
+ }
2647
+ if (tokenEstimate > SKILL_LIMITS.MAX_INSTRUCTION_TOKENS) {
2648
+ warnings.push(
2649
+ `Instructions have ~${tokenEstimate} estimated tokens (recommended: <${SKILL_LIMITS.MAX_INSTRUCTION_TOKENS}). Consider moving content to references/.`
2650
+ );
2651
+ }
2652
+ }
2653
+ return {
2654
+ valid: errors.length === 0,
2655
+ errors,
2656
+ warnings
2657
+ };
2658
+ }
1633
2659
  var LocalSkillSource = class {
1634
2660
  #basePath;
1635
2661
  constructor(options = {}) {
@@ -2194,8 +3220,22 @@ var Workspace = class {
2194
3220
  this.createdAt = /* @__PURE__ */ new Date();
2195
3221
  this.lastAccessedAt = /* @__PURE__ */ new Date();
2196
3222
  this._config = config;
2197
- this._fs = config.filesystem;
2198
3223
  this._sandbox = config.sandbox;
3224
+ if (config.mounts && Object.keys(config.mounts).length > 0) {
3225
+ if (config.filesystem) {
3226
+ throw new WorkspaceError('Cannot use both "filesystem" and "mounts"', "INVALID_CONFIG");
3227
+ }
3228
+ this._fs = new CompositeFilesystem({ mounts: config.mounts });
3229
+ if (this._sandbox?.mounts) {
3230
+ this._sandbox.mounts.setContext({ sandbox: this._sandbox, workspace: this });
3231
+ this._sandbox.mounts.add(config.mounts);
3232
+ if (config.onMount) {
3233
+ this._sandbox.mounts.setOnMount(config.onMount);
3234
+ }
3235
+ }
3236
+ } else {
3237
+ this._fs = config.filesystem;
3238
+ }
2199
3239
  if (config.vectorStore && !config.embedder) {
2200
3240
  throw new WorkspaceError("vectorStore requires an embedder", "INVALID_SEARCH_CONFIG");
2201
3241
  }
@@ -2400,16 +3440,16 @@ var Workspace = class {
2400
3440
  // ---------------------------------------------------------------------------
2401
3441
  /**
2402
3442
  * Initialize the workspace.
2403
- * Starts the sandbox and initializes the filesystem.
3443
+ * Starts the sandbox, initializes the filesystem, and auto-mounts filesystems.
2404
3444
  */
2405
3445
  async init() {
2406
3446
  this._status = "initializing";
2407
3447
  try {
2408
- if (this._fs?.init) {
2409
- await this._fs.init();
3448
+ if (this._fs) {
3449
+ await callLifecycle(this._fs, "init");
2410
3450
  }
2411
- if (this._sandbox?.start) {
2412
- await this._sandbox.start();
3451
+ if (this._sandbox) {
3452
+ await callLifecycle(this._sandbox, "start");
2413
3453
  }
2414
3454
  if (this._searchEngine && this._config.autoIndexPaths && this._config.autoIndexPaths.length > 0) {
2415
3455
  await this.rebuildSearchIndex(this._config.autoIndexPaths ?? []);
@@ -2426,11 +3466,11 @@ var Workspace = class {
2426
3466
  async destroy() {
2427
3467
  this._status = "destroying";
2428
3468
  try {
2429
- if (this._sandbox?.destroy) {
2430
- await this._sandbox.destroy();
3469
+ if (this._sandbox) {
3470
+ await callLifecycle(this._sandbox, "destroy");
2431
3471
  }
2432
- if (this._fs?.destroy) {
2433
- await this._fs.destroy();
3472
+ if (this._fs) {
3473
+ await callLifecycle(this._fs, "destroy");
2434
3474
  }
2435
3475
  this._status = "destroyed";
2436
3476
  } catch (error) {
@@ -2454,6 +3494,8 @@ var Workspace = class {
2454
3494
  const fsInfo = await this._fs.getInfo?.();
2455
3495
  info.filesystem = {
2456
3496
  provider: this._fs.provider,
3497
+ name: fsInfo?.name ?? this._fs.name,
3498
+ icon: fsInfo?.icon,
2457
3499
  basePath: fsInfo?.basePath ?? this._fs.basePath,
2458
3500
  readOnly: fsInfo?.readOnly ?? this._fs.readOnly,
2459
3501
  status: fsInfo?.status,
@@ -2516,47 +3558,6 @@ var Workspace = class {
2516
3558
  }
2517
3559
  }
2518
3560
  };
2519
-
2520
- // src/workspace/sandbox/sandbox.ts
2521
- var SandboxError = class extends Error {
2522
- constructor(message, code, details) {
2523
- super(message);
2524
- this.code = code;
2525
- this.details = details;
2526
- this.name = "SandboxError";
2527
- }
2528
- };
2529
- var SandboxExecutionError = class extends SandboxError {
2530
- constructor(message, exitCode, stdout, stderr) {
2531
- super(message, "EXECUTION_FAILED", { exitCode, stdout, stderr });
2532
- this.exitCode = exitCode;
2533
- this.stdout = stdout;
2534
- this.stderr = stderr;
2535
- this.name = "SandboxExecutionError";
2536
- }
2537
- };
2538
- var SandboxTimeoutError = class extends SandboxError {
2539
- constructor(timeoutMs, operation) {
2540
- super(`Execution timed out after ${timeoutMs}ms`, "TIMEOUT", { timeoutMs, operation });
2541
- this.timeoutMs = timeoutMs;
2542
- this.operation = operation;
2543
- this.name = "SandboxTimeoutError";
2544
- }
2545
- };
2546
- var SandboxNotReadyError = class extends SandboxError {
2547
- constructor(idOrStatus) {
2548
- super(`Sandbox is not ready: ${idOrStatus}`, "NOT_READY", { id: idOrStatus });
2549
- this.name = "SandboxNotReadyError";
2550
- }
2551
- };
2552
- var IsolationUnavailableError = class extends SandboxError {
2553
- constructor(backend, reason) {
2554
- super(`Isolation backend '${backend}' is not available: ${reason}`, "ISOLATION_UNAVAILABLE", { backend, reason });
2555
- this.backend = backend;
2556
- this.reason = reason;
2557
- this.name = "IsolationUnavailableError";
2558
- }
2559
- };
2560
3561
  function commandExists(command) {
2561
3562
  try {
2562
3563
  childProcess.execFileSync("which", [command], { stdio: "ignore" });
@@ -2833,7 +3834,7 @@ var LocalSandbox = class extends MastraSandbox {
2833
3834
  id;
2834
3835
  name = "LocalSandbox";
2835
3836
  provider = "local";
2836
- status = "stopped";
3837
+ status = "pending";
2837
3838
  _workingDirectory;
2838
3839
  env;
2839
3840
  timeout;
@@ -2872,7 +3873,7 @@ var LocalSandbox = class extends MastraSandbox {
2872
3873
  return detectIsolation();
2873
3874
  }
2874
3875
  constructor(options = {}) {
2875
- super({ name: "LocalSandbox" });
3876
+ super({ name: "LocalSandbox", onStart: options.onStart, onStop: options.onStop, onDestroy: options.onDestroy });
2876
3877
  this.id = options.id ?? this.generateId();
2877
3878
  this._createdAt = /* @__PURE__ */ new Date();
2878
3879
  this._workingDirectory = options.workingDirectory ?? nodePath__namespace.join(process.cwd(), ".sandbox");
@@ -2902,44 +3903,52 @@ var LocalSandbox = class extends MastraSandbox {
2902
3903
  ...additionalEnv
2903
3904
  };
2904
3905
  }
3906
+ /**
3907
+ * Start the local sandbox.
3908
+ * Creates working directory and sets up seatbelt profile if using macOS isolation.
3909
+ * Status management is handled by the base class.
3910
+ */
2905
3911
  async start() {
2906
3912
  this.logger.debug("Starting sandbox", { workingDirectory: this._workingDirectory, isolation: this._isolation });
2907
- this.status = "starting";
2908
- try {
2909
- await fs2__namespace.mkdir(this.workingDirectory, { recursive: true });
2910
- if (this._isolation === "seatbelt") {
2911
- const userProvidedPath = this._nativeSandboxConfig.seatbeltProfilePath;
2912
- if (userProvidedPath) {
2913
- this._seatbeltProfilePath = userProvidedPath;
2914
- this._userProvidedProfilePath = true;
2915
- try {
2916
- this._seatbeltProfile = await fs2__namespace.readFile(userProvidedPath, "utf-8");
2917
- } catch {
2918
- this._seatbeltProfile = generateSeatbeltProfile(this.workingDirectory, this._nativeSandboxConfig);
2919
- await fs2__namespace.mkdir(nodePath__namespace.dirname(userProvidedPath), { recursive: true });
2920
- await fs2__namespace.writeFile(userProvidedPath, this._seatbeltProfile, "utf-8");
3913
+ await fs2__namespace.mkdir(this.workingDirectory, { recursive: true });
3914
+ if (this._isolation === "seatbelt") {
3915
+ const userProvidedPath = this._nativeSandboxConfig.seatbeltProfilePath;
3916
+ if (userProvidedPath) {
3917
+ this._seatbeltProfilePath = userProvidedPath;
3918
+ this._userProvidedProfilePath = true;
3919
+ try {
3920
+ this._seatbeltProfile = await fs2__namespace.readFile(userProvidedPath, "utf-8");
3921
+ } catch (err) {
3922
+ if (err instanceof Error && "code" in err && err.code !== "ENOENT") {
3923
+ throw err;
2921
3924
  }
2922
- } else {
2923
3925
  this._seatbeltProfile = generateSeatbeltProfile(this.workingDirectory, this._nativeSandboxConfig);
2924
- const configHash = crypto__namespace.createHash("sha256").update(this.workingDirectory).update(JSON.stringify(this._nativeSandboxConfig)).digest("hex").slice(0, 8);
2925
- this._sandboxFolderPath = nodePath__namespace.join(process.cwd(), ".sandbox-profiles");
2926
- await fs2__namespace.mkdir(this._sandboxFolderPath, { recursive: true });
2927
- this._seatbeltProfilePath = nodePath__namespace.join(this._sandboxFolderPath, `seatbelt-${configHash}.sb`);
2928
- await fs2__namespace.writeFile(this._seatbeltProfilePath, this._seatbeltProfile, "utf-8");
3926
+ await fs2__namespace.mkdir(nodePath__namespace.dirname(userProvidedPath), { recursive: true });
3927
+ await fs2__namespace.writeFile(userProvidedPath, this._seatbeltProfile, "utf-8");
2929
3928
  }
3929
+ } else {
3930
+ this._seatbeltProfile = generateSeatbeltProfile(this.workingDirectory, this._nativeSandboxConfig);
3931
+ const configHash = crypto__namespace.createHash("sha256").update(this.workingDirectory).update(JSON.stringify(this._nativeSandboxConfig)).digest("hex").slice(0, 8);
3932
+ this._sandboxFolderPath = nodePath__namespace.join(process.cwd(), ".sandbox-profiles");
3933
+ await fs2__namespace.mkdir(this._sandboxFolderPath, { recursive: true });
3934
+ this._seatbeltProfilePath = nodePath__namespace.join(this._sandboxFolderPath, `seatbelt-${configHash}.sb`);
3935
+ await fs2__namespace.writeFile(this._seatbeltProfilePath, this._seatbeltProfile, "utf-8");
2930
3936
  }
2931
- this.status = "running";
2932
- this.logger.debug("Sandbox started", { workingDirectory: this._workingDirectory, status: this.status });
2933
- } catch (error) {
2934
- this.status = "error";
2935
- this.logger.error("Failed to start sandbox", { workingDirectory: this._workingDirectory, error });
2936
- throw error;
2937
3937
  }
3938
+ this.logger.debug("Sandbox started", { workingDirectory: this._workingDirectory });
2938
3939
  }
3940
+ /**
3941
+ * Stop the local sandbox.
3942
+ * Status management is handled by the base class.
3943
+ */
2939
3944
  async stop() {
2940
3945
  this.logger.debug("Stopping sandbox", { workingDirectory: this._workingDirectory });
2941
- this.status = "stopped";
2942
3946
  }
3947
+ /**
3948
+ * Destroy the local sandbox and clean up resources.
3949
+ * Cleans up seatbelt profile if auto-generated.
3950
+ * Status management is handled by the base class.
3951
+ */
2943
3952
  async destroy() {
2944
3953
  this.logger.debug("Destroying sandbox", { workingDirectory: this._workingDirectory });
2945
3954
  if (this._seatbeltProfilePath && !this._userProvidedProfilePath) {
@@ -2958,7 +3967,6 @@ var LocalSandbox = class extends MastraSandbox {
2958
3967
  }
2959
3968
  this._sandboxFolderPath = void 0;
2960
3969
  }
2961
- await this.stop();
2962
3970
  }
2963
3971
  async isReady() {
2964
3972
  return this.status === "running";
@@ -3009,15 +4017,13 @@ var LocalSandbox = class extends MastraSandbox {
3009
4017
  }
3010
4018
  async executeCommand(command, args = [], options = {}) {
3011
4019
  this.logger.debug("Executing command", { command, args, cwd: options.cwd ?? this.workingDirectory });
3012
- if (this.status !== "running") {
3013
- await this.start();
3014
- }
4020
+ await this.ensureRunning();
3015
4021
  const startTime = Date.now();
3016
4022
  const wrapped = this.wrapCommandForIsolation(command, args);
3017
4023
  try {
3018
4024
  const result = await execWithStreaming(wrapped.command, wrapped.args, {
3019
4025
  cwd: options.cwd ?? this.workingDirectory,
3020
- timeout: this.timeout ?? options.timeout ?? 3e4,
4026
+ timeout: options.timeout ?? this.timeout ?? 3e4,
3021
4027
  env: this.buildEnv(options.env),
3022
4028
  onStdout: options.onStdout,
3023
4029
  onStderr: options.onStderr
@@ -3093,7 +4099,10 @@ async function formatAsTree(fs5, path4, options) {
3093
4099
  let entries;
3094
4100
  try {
3095
4101
  entries = await fs5.readdir(currentPath);
3096
- } catch {
4102
+ } catch (error) {
4103
+ if (depth === 0) {
4104
+ throw error;
4105
+ }
3097
4106
  return;
3098
4107
  }
3099
4108
  let filtered = entries;
@@ -3204,7 +4213,7 @@ function createWorkspaceTools(workspace) {
3204
4213
  if (workspace.filesystem) {
3205
4214
  const readFileConfig = resolveToolConfig(toolsConfig, WORKSPACE_TOOLS.FILESYSTEM.READ_FILE);
3206
4215
  if (readFileConfig.enabled) {
3207
- tools[WORKSPACE_TOOLS.FILESYSTEM.READ_FILE] = chunkIIZF4W7B_cjs.createTool({
4216
+ tools[WORKSPACE_TOOLS.FILESYSTEM.READ_FILE] = chunk7UWHFWST_cjs.createTool({
3208
4217
  id: WORKSPACE_TOOLS.FILESYSTEM.READ_FILE,
3209
4218
  description: "Read the contents of a file from the workspace filesystem. Use offset/limit parameters to read specific line ranges for large files.",
3210
4219
  requireApproval: readFileConfig.requireApproval,
@@ -3266,7 +4275,7 @@ function createWorkspaceTools(workspace) {
3266
4275
  });
3267
4276
  }
3268
4277
  if (!isReadOnly && writeFileConfig.enabled) {
3269
- tools[WORKSPACE_TOOLS.FILESYSTEM.WRITE_FILE] = chunkIIZF4W7B_cjs.createTool({
4278
+ tools[WORKSPACE_TOOLS.FILESYSTEM.WRITE_FILE] = chunk7UWHFWST_cjs.createTool({
3270
4279
  id: WORKSPACE_TOOLS.FILESYSTEM.WRITE_FILE,
3271
4280
  description: "Write content to a file in the workspace filesystem. Creates parent directories if needed.",
3272
4281
  requireApproval: writeFileConfig.requireApproval,
@@ -3307,7 +4316,7 @@ function createWorkspaceTools(workspace) {
3307
4316
  });
3308
4317
  }
3309
4318
  if (!isReadOnly && editFileConfig.enabled) {
3310
- tools[WORKSPACE_TOOLS.FILESYSTEM.EDIT_FILE] = chunkIIZF4W7B_cjs.createTool({
4319
+ tools[WORKSPACE_TOOLS.FILESYSTEM.EDIT_FILE] = chunk7UWHFWST_cjs.createTool({
3311
4320
  id: WORKSPACE_TOOLS.FILESYSTEM.EDIT_FILE,
3312
4321
  description: `Edit a file by replacing specific text. The old_string must match exactly and be unique in the file.
3313
4322
 
@@ -3384,7 +4393,7 @@ Usage:
3384
4393
  }
3385
4394
  const listFilesConfig = resolveToolConfig(toolsConfig, WORKSPACE_TOOLS.FILESYSTEM.LIST_FILES);
3386
4395
  if (listFilesConfig.enabled) {
3387
- tools[WORKSPACE_TOOLS.FILESYSTEM.LIST_FILES] = chunkIIZF4W7B_cjs.createTool({
4396
+ tools[WORKSPACE_TOOLS.FILESYSTEM.LIST_FILES] = chunk7UWHFWST_cjs.createTool({
3388
4397
  id: WORKSPACE_TOOLS.FILESYSTEM.LIST_FILES,
3389
4398
  description: `List files and directories in the workspace filesystem.
3390
4399
  Returns a tree-style view (like the Unix "tree" command) for easy visualization.
@@ -3450,7 +4459,7 @@ Examples:
3450
4459
  }
3451
4460
  const deleteConfig = resolveToolConfig(toolsConfig, WORKSPACE_TOOLS.FILESYSTEM.DELETE);
3452
4461
  if (!isReadOnly && deleteConfig.enabled) {
3453
- tools[WORKSPACE_TOOLS.FILESYSTEM.DELETE] = chunkIIZF4W7B_cjs.createTool({
4462
+ tools[WORKSPACE_TOOLS.FILESYSTEM.DELETE] = chunk7UWHFWST_cjs.createTool({
3454
4463
  id: WORKSPACE_TOOLS.FILESYSTEM.DELETE,
3455
4464
  description: "Delete a file or directory from the workspace filesystem",
3456
4465
  requireApproval: deleteConfig.requireApproval,
@@ -3477,7 +4486,7 @@ Examples:
3477
4486
  }
3478
4487
  const fileStatConfig = resolveToolConfig(toolsConfig, WORKSPACE_TOOLS.FILESYSTEM.FILE_STAT);
3479
4488
  if (fileStatConfig.enabled) {
3480
- tools[WORKSPACE_TOOLS.FILESYSTEM.FILE_STAT] = chunkIIZF4W7B_cjs.createTool({
4489
+ tools[WORKSPACE_TOOLS.FILESYSTEM.FILE_STAT] = chunk7UWHFWST_cjs.createTool({
3481
4490
  id: WORKSPACE_TOOLS.FILESYSTEM.FILE_STAT,
3482
4491
  description: "Get file or directory metadata from the workspace. Returns existence, type, size, and modification time.",
3483
4492
  requireApproval: fileStatConfig.requireApproval,
@@ -3510,7 +4519,7 @@ Examples:
3510
4519
  }
3511
4520
  const mkdirConfig = resolveToolConfig(toolsConfig, WORKSPACE_TOOLS.FILESYSTEM.MKDIR);
3512
4521
  if (!isReadOnly && mkdirConfig.enabled) {
3513
- tools[WORKSPACE_TOOLS.FILESYSTEM.MKDIR] = chunkIIZF4W7B_cjs.createTool({
4522
+ tools[WORKSPACE_TOOLS.FILESYSTEM.MKDIR] = chunk7UWHFWST_cjs.createTool({
3514
4523
  id: WORKSPACE_TOOLS.FILESYSTEM.MKDIR,
3515
4524
  description: "Create a directory in the workspace filesystem",
3516
4525
  requireApproval: mkdirConfig.requireApproval,
@@ -3532,7 +4541,7 @@ Examples:
3532
4541
  if (workspace.canBM25 || workspace.canVector) {
3533
4542
  const searchConfig = resolveToolConfig(toolsConfig, WORKSPACE_TOOLS.SEARCH.SEARCH);
3534
4543
  if (searchConfig.enabled) {
3535
- tools[WORKSPACE_TOOLS.SEARCH.SEARCH] = chunkIIZF4W7B_cjs.createTool({
4544
+ tools[WORKSPACE_TOOLS.SEARCH.SEARCH] = chunk7UWHFWST_cjs.createTool({
3536
4545
  id: WORKSPACE_TOOLS.SEARCH.SEARCH,
3537
4546
  description: "Search indexed content in the workspace. Supports keyword (BM25), semantic (vector), and hybrid search modes.",
3538
4547
  requireApproval: searchConfig.requireApproval,
@@ -3578,7 +4587,7 @@ Examples:
3578
4587
  }
3579
4588
  const indexConfig = resolveToolConfig(toolsConfig, WORKSPACE_TOOLS.SEARCH.INDEX);
3580
4589
  if (!isReadOnly && indexConfig.enabled) {
3581
- tools[WORKSPACE_TOOLS.SEARCH.INDEX] = chunkIIZF4W7B_cjs.createTool({
4590
+ tools[WORKSPACE_TOOLS.SEARCH.INDEX] = chunk7UWHFWST_cjs.createTool({
3582
4591
  id: WORKSPACE_TOOLS.SEARCH.INDEX,
3583
4592
  description: "Index content for search. The path becomes the document ID in search results.",
3584
4593
  requireApproval: indexConfig.requireApproval,
@@ -3603,22 +4612,20 @@ Examples:
3603
4612
  const pathInfo = pathContext.instructions ? ` ${pathContext.instructions}` : "";
3604
4613
  const executeCommandConfig = resolveToolConfig(toolsConfig, WORKSPACE_TOOLS.SANDBOX.EXECUTE_COMMAND);
3605
4614
  if (workspace.sandbox.executeCommand && executeCommandConfig.enabled) {
3606
- tools[WORKSPACE_TOOLS.SANDBOX.EXECUTE_COMMAND] = chunkIIZF4W7B_cjs.createTool({
4615
+ tools[WORKSPACE_TOOLS.SANDBOX.EXECUTE_COMMAND] = chunk7UWHFWST_cjs.createTool({
3607
4616
  id: WORKSPACE_TOOLS.SANDBOX.EXECUTE_COMMAND,
3608
4617
  description: `Execute a shell command in the workspace sandbox.${pathInfo}
3609
4618
 
3610
4619
  Usage:
3611
4620
  - Verify parent directories exist before running commands that create files or directories.
3612
4621
  - Always quote file paths that contain spaces (e.g., cd "/path/with spaces").
3613
- - Commands timeout after 30 seconds by default. Use the timeout parameter for longer operations.
4622
+ - Use the timeout parameter to limit execution time. Behavior when omitted depends on the sandbox provider.
3614
4623
  - Use cwd to set the working directory, or commands run from the sandbox default.`,
3615
4624
  requireApproval: executeCommandConfig.requireApproval,
3616
4625
  inputSchema: zod.z.object({
3617
4626
  command: zod.z.string().describe('The command to execute (e.g., "ls", "npm", "python")'),
3618
4627
  args: zod.z.array(zod.z.string()).nullish().default([]).describe("Arguments to pass to the command"),
3619
- timeout: zod.z.number().nullish().default(3e4).describe(
3620
- "Maximum execution time in milliseconds. Default is 30000 (30 seconds). Example: 60000 for 1 minute."
3621
- ),
4628
+ timeout: zod.z.number().nullish().describe("Maximum execution time in milliseconds. Example: 60000 for 1 minute."),
3622
4629
  cwd: zod.z.string().nullish().describe("Working directory for the command")
3623
4630
  }),
3624
4631
  outputSchema: zod.z.object({
@@ -3644,7 +4651,7 @@ Usage:
3644
4651
  const startedAt = Date.now();
3645
4652
  try {
3646
4653
  const result = await workspace.sandbox.executeCommand(command, args ?? [], {
3647
- timeout: timeout ?? 3e4,
4654
+ timeout: timeout ?? void 0,
3648
4655
  cwd: cwd ?? void 0,
3649
4656
  // Stream stdout/stderr as tool-output chunks for proper UI integration
3650
4657
  onStdout: async (data) => {
@@ -3696,6 +4703,7 @@ Usage:
3696
4703
  }
3697
4704
 
3698
4705
  exports.BM25Index = BM25Index;
4706
+ exports.CompositeFilesystem = CompositeFilesystem;
3699
4707
  exports.DirectoryNotEmptyError = DirectoryNotEmptyError;
3700
4708
  exports.DirectoryNotFoundError = DirectoryNotFoundError;
3701
4709
  exports.FileExistsError = FileExistsError;
@@ -3703,12 +4711,17 @@ exports.FileNotFoundError = FileNotFoundError;
3703
4711
  exports.FileReadRequiredError = FileReadRequiredError;
3704
4712
  exports.FilesystemError = FilesystemError;
3705
4713
  exports.FilesystemNotAvailableError = FilesystemNotAvailableError;
4714
+ exports.FilesystemNotMountableError = FilesystemNotMountableError;
4715
+ exports.FilesystemNotReadyError = FilesystemNotReadyError;
3706
4716
  exports.IsDirectoryError = IsDirectoryError;
3707
4717
  exports.IsolationUnavailableError = IsolationUnavailableError;
3708
4718
  exports.LocalFilesystem = LocalFilesystem;
3709
4719
  exports.LocalSandbox = LocalSandbox;
3710
4720
  exports.MastraFilesystem = MastraFilesystem;
3711
4721
  exports.MastraSandbox = MastraSandbox;
4722
+ exports.MountError = MountError;
4723
+ exports.MountManager = MountManager;
4724
+ exports.MountNotSupportedError = MountNotSupportedError;
3712
4725
  exports.NotDirectoryError = NotDirectoryError;
3713
4726
  exports.PermissionError = PermissionError;
3714
4727
  exports.SandboxError = SandboxError;
@@ -3724,11 +4737,12 @@ exports.Workspace = Workspace;
3724
4737
  exports.WorkspaceError = WorkspaceError;
3725
4738
  exports.WorkspaceNotReadyError = WorkspaceNotReadyError;
3726
4739
  exports.WorkspaceReadOnlyError = WorkspaceReadOnlyError;
4740
+ exports.callLifecycle = callLifecycle;
3727
4741
  exports.createWorkspaceTools = createWorkspaceTools;
3728
4742
  exports.detectIsolation = detectIsolation;
3729
4743
  exports.extractLines = extractLines;
3730
4744
  exports.getRecommendedIsolation = getRecommendedIsolation;
3731
4745
  exports.isIsolationAvailable = isIsolationAvailable;
3732
4746
  exports.resolveToolConfig = resolveToolConfig;
3733
- //# sourceMappingURL=chunk-HMCXNOF6.cjs.map
3734
- //# sourceMappingURL=chunk-HMCXNOF6.cjs.map
4747
+ //# sourceMappingURL=chunk-CGPH7CMG.cjs.map
4748
+ //# sourceMappingURL=chunk-CGPH7CMG.cjs.map