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