@mastra/core 1.4.0 → 1.5.0-alpha.1

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 (369) hide show
  1. package/CHANGELOG.md +368 -0
  2. package/dist/agent/agent.d.ts +3 -2
  3. package/dist/agent/agent.d.ts.map +1 -1
  4. package/dist/agent/index.cjs +13 -13
  5. package/dist/agent/index.js +2 -2
  6. package/dist/agent/message-list/conversion/output-converter.d.ts.map +1 -1
  7. package/dist/agent/message-list/index.cjs +18 -18
  8. package/dist/agent/message-list/index.js +1 -1
  9. package/dist/agent/message-list/merge/MessageMerger.d.ts.map +1 -1
  10. package/dist/agent/message-list/message-list.d.ts.map +1 -1
  11. package/dist/agent/types.d.ts +2 -2
  12. package/dist/agent/types.d.ts.map +1 -1
  13. package/dist/agent/workflows/prepare-stream/index.d.ts +1 -0
  14. package/dist/agent/workflows/prepare-stream/index.d.ts.map +1 -1
  15. package/dist/agent/workflows/prepare-stream/prepare-tools-step.d.ts +1 -0
  16. package/dist/agent/workflows/prepare-stream/prepare-tools-step.d.ts.map +1 -1
  17. package/dist/agent/workflows/prepare-stream/schema.d.ts +8 -0
  18. package/dist/agent/workflows/prepare-stream/schema.d.ts.map +1 -1
  19. package/dist/{chunk-Y3TQ52UE.js → chunk-33TGTTTS.js} +4 -3
  20. package/dist/chunk-33TGTTTS.js.map +1 -0
  21. package/dist/{chunk-3X3CZUXI.js → chunk-3KJW4EMO.js} +660 -206
  22. package/dist/chunk-3KJW4EMO.js.map +1 -0
  23. package/dist/{chunk-YNNJLLFN.cjs → chunk-3YMDR4OL.cjs} +661 -207
  24. package/dist/chunk-3YMDR4OL.cjs.map +1 -0
  25. package/dist/{chunk-NJ7TL3LQ.js → chunk-5EOLBHHS.js} +26 -15
  26. package/dist/chunk-5EOLBHHS.js.map +1 -0
  27. package/dist/{chunk-RZ4CIIZR.js → chunk-6DUTLERJ.js} +4 -4
  28. package/dist/{chunk-RZ4CIIZR.js.map → chunk-6DUTLERJ.js.map} +1 -1
  29. package/dist/{chunk-VTE2OBKS.cjs → chunk-A6EWCOGA.cjs} +417 -77
  30. package/dist/chunk-A6EWCOGA.cjs.map +1 -0
  31. package/dist/{chunk-4XSAZPPS.js → chunk-A7V2NSY3.js} +313 -137
  32. package/dist/chunk-A7V2NSY3.js.map +1 -0
  33. package/dist/{chunk-FLPEGTEK.js → chunk-AIRMLZ43.js} +5 -5
  34. package/dist/{chunk-FLPEGTEK.js.map → chunk-AIRMLZ43.js.map} +1 -1
  35. package/dist/{chunk-DBSVT6AR.cjs → chunk-BKQAP27M.cjs} +9 -9
  36. package/dist/{chunk-DBSVT6AR.cjs.map → chunk-BKQAP27M.cjs.map} +1 -1
  37. package/dist/{chunk-RS6CZXGA.js → chunk-BQHWJLXU.js} +15 -4
  38. package/dist/chunk-BQHWJLXU.js.map +1 -0
  39. package/dist/{chunk-V2MLGA7T.js → chunk-CXVMDV2B.js} +417 -78
  40. package/dist/chunk-CXVMDV2B.js.map +1 -0
  41. package/dist/{chunk-NKYWDNCI.cjs → chunk-E2FHTXAI.cjs} +7 -7
  42. package/dist/{chunk-NKYWDNCI.cjs.map → chunk-E2FHTXAI.cjs.map} +1 -1
  43. package/dist/{chunk-7UWHFWST.cjs → chunk-EAZ6YDCQ.cjs} +15 -3
  44. package/dist/chunk-EAZ6YDCQ.cjs.map +1 -0
  45. package/dist/{chunk-64WGYTQK.cjs → chunk-FTBLAVTF.cjs} +55 -55
  46. package/dist/{chunk-64WGYTQK.cjs.map → chunk-FTBLAVTF.cjs.map} +1 -1
  47. package/dist/{chunk-4EHGOATH.js → chunk-FZ5DRHKE.js} +1337 -547
  48. package/dist/chunk-FZ5DRHKE.js.map +1 -0
  49. package/dist/{chunk-4TQ4EBYX.js → chunk-GEDGDKQ6.js} +9 -9
  50. package/dist/chunk-GEDGDKQ6.js.map +1 -0
  51. package/dist/{chunk-QTTWRCB5.js → chunk-I3AWF54W.js} +5 -5
  52. package/dist/{chunk-QTTWRCB5.js.map → chunk-I3AWF54W.js.map} +1 -1
  53. package/dist/{chunk-U2HKJZCI.js → chunk-IBNCZTNQ.js} +6 -6
  54. package/dist/{chunk-U2HKJZCI.js.map → chunk-IBNCZTNQ.js.map} +1 -1
  55. package/dist/{chunk-3JVFFAJX.cjs → chunk-IJIE3ZID.cjs} +27 -16
  56. package/dist/chunk-IJIE3ZID.cjs.map +1 -0
  57. package/dist/{chunk-NZG2JAKS.cjs → chunk-JWG272ZZ.cjs} +19 -19
  58. package/dist/chunk-JWG272ZZ.cjs.map +1 -0
  59. package/dist/{chunk-BP7VYTOP.cjs → chunk-JZ6TH4HQ.cjs} +954 -401
  60. package/dist/chunk-JZ6TH4HQ.cjs.map +1 -0
  61. package/dist/{chunk-SU5APAM6.cjs → chunk-KNXZ7KYL.cjs} +94 -6
  62. package/dist/chunk-KNXZ7KYL.cjs.map +1 -0
  63. package/dist/{chunk-CYUP7QWT.cjs → chunk-KRAGJ433.cjs} +4 -3
  64. package/dist/chunk-KRAGJ433.cjs.map +1 -0
  65. package/dist/{chunk-XDD5V446.cjs → chunk-MDC6VYA6.cjs} +6 -2
  66. package/dist/{chunk-XDD5V446.cjs.map → chunk-MDC6VYA6.cjs.map} +1 -1
  67. package/dist/{chunk-AXHBJ4GX.js → chunk-NN26FSKL.js} +10 -8
  68. package/dist/chunk-NN26FSKL.js.map +1 -0
  69. package/dist/{chunk-AY6DBRS3.js → chunk-OHLVZVIK.js} +36 -2
  70. package/dist/chunk-OHLVZVIK.js.map +1 -0
  71. package/dist/{chunk-5Q5Y34SS.js → chunk-PECKKR4C.js} +4 -4
  72. package/dist/{chunk-5Q5Y34SS.js.map → chunk-PECKKR4C.js.map} +1 -1
  73. package/dist/{chunk-HYRYTTMT.cjs → chunk-PHHJLGZU.cjs} +9 -9
  74. package/dist/{chunk-HYRYTTMT.cjs.map → chunk-PHHJLGZU.cjs.map} +1 -1
  75. package/dist/{chunk-65PHUUMF.cjs → chunk-QDH6MVJ7.cjs} +24 -22
  76. package/dist/chunk-QDH6MVJ7.cjs.map +1 -0
  77. package/dist/{chunk-VD5YA6RH.cjs → chunk-QSN5KQXZ.cjs} +18 -18
  78. package/dist/{chunk-VD5YA6RH.cjs.map → chunk-QSN5KQXZ.cjs.map} +1 -1
  79. package/dist/{chunk-4IJ4UDZX.cjs → chunk-RH2K66O2.cjs} +399 -223
  80. package/dist/chunk-RH2K66O2.cjs.map +1 -0
  81. package/dist/{chunk-4KFEMXTV.cjs → chunk-S4VVZI4E.cjs} +1361 -546
  82. package/dist/chunk-S4VVZI4E.cjs.map +1 -0
  83. package/dist/{chunk-ZATLLPIH.js → chunk-TPDMP7OD.js} +6 -2
  84. package/dist/chunk-TPDMP7OD.js.map +1 -0
  85. package/dist/{chunk-PS5ONCXY.js → chunk-UZFGMMKU.js} +82 -4
  86. package/dist/chunk-UZFGMMKU.js.map +1 -0
  87. package/dist/{chunk-7NKUSQEV.js → chunk-YIN5F7VO.js} +936 -389
  88. package/dist/chunk-YIN5F7VO.js.map +1 -0
  89. package/dist/{chunk-CZ4NQANZ.cjs → chunk-YW54RH77.cjs} +36 -2
  90. package/dist/chunk-YW54RH77.cjs.map +1 -0
  91. package/dist/datasets/experiment/executor.d.ts.map +1 -1
  92. package/dist/datasets/index.cjs +17 -17
  93. package/dist/datasets/index.js +2 -2
  94. package/dist/docs/SKILL.md +27 -1
  95. package/dist/docs/assets/SOURCE_MAP.json +463 -389
  96. package/dist/docs/references/docs-agents-processors.md +52 -0
  97. package/dist/docs/references/docs-observability-datasets-overview.md +188 -0
  98. package/dist/docs/references/docs-observability-datasets-running-experiments.md +266 -0
  99. package/dist/docs/references/docs-observability-tracing-exporters-cloud.md +7 -4
  100. package/dist/docs/references/reference-agents-generate.md +1 -1
  101. package/dist/docs/references/reference-configuration.md +3 -4
  102. package/dist/docs/references/reference-datasets-addItem.md +35 -0
  103. package/dist/docs/references/reference-datasets-addItems.md +33 -0
  104. package/dist/docs/references/reference-datasets-compareExperiments.md +48 -0
  105. package/dist/docs/references/reference-datasets-create.md +49 -0
  106. package/dist/docs/references/reference-datasets-dataset.md +78 -0
  107. package/dist/docs/references/reference-datasets-datasets-manager.md +84 -0
  108. package/dist/docs/references/reference-datasets-delete.md +23 -0
  109. package/dist/docs/references/reference-datasets-deleteExperiment.md +25 -0
  110. package/dist/docs/references/reference-datasets-deleteItem.md +25 -0
  111. package/dist/docs/references/reference-datasets-deleteItems.md +27 -0
  112. package/dist/docs/references/reference-datasets-get.md +29 -0
  113. package/dist/docs/references/reference-datasets-getDetails.md +45 -0
  114. package/dist/docs/references/reference-datasets-getExperiment.md +28 -0
  115. package/dist/docs/references/reference-datasets-getItem.md +31 -0
  116. package/dist/docs/references/reference-datasets-getItemHistory.md +29 -0
  117. package/dist/docs/references/reference-datasets-list.md +29 -0
  118. package/dist/docs/references/reference-datasets-listExperimentResults.md +37 -0
  119. package/dist/docs/references/reference-datasets-listExperiments.md +31 -0
  120. package/dist/docs/references/reference-datasets-listItems.md +44 -0
  121. package/dist/docs/references/reference-datasets-listVersions.md +31 -0
  122. package/dist/docs/references/reference-datasets-startExperiment.md +60 -0
  123. package/dist/docs/references/reference-datasets-startExperimentAsync.md +41 -0
  124. package/dist/docs/references/reference-datasets-update.md +46 -0
  125. package/dist/docs/references/reference-datasets-updateItem.md +36 -0
  126. package/dist/docs/references/reference-memory-observational-memory.md +36 -0
  127. package/dist/docs/references/reference-processors-processor-interface.md +4 -0
  128. package/dist/docs/references/reference-tools-create-tool.md +1 -1
  129. package/dist/docs/references/reference.md +24 -0
  130. package/dist/editor/index.d.ts +1 -1
  131. package/dist/editor/index.d.ts.map +1 -1
  132. package/dist/editor/types.d.ts +108 -2
  133. package/dist/editor/types.d.ts.map +1 -1
  134. package/dist/evals/index.cjs +20 -20
  135. package/dist/evals/index.js +3 -3
  136. package/dist/evals/scoreTraces/index.cjs +5 -5
  137. package/dist/evals/scoreTraces/index.js +2 -2
  138. package/dist/harness/harness.d.ts +281 -0
  139. package/dist/harness/harness.d.ts.map +1 -0
  140. package/dist/harness/index.cjs +1728 -0
  141. package/dist/harness/index.cjs.map +1 -0
  142. package/dist/harness/index.d.ts +4 -0
  143. package/dist/harness/index.d.ts.map +1 -0
  144. package/dist/harness/index.js +1723 -0
  145. package/dist/harness/index.js.map +1 -0
  146. package/dist/harness/tools.d.ts +65 -0
  147. package/dist/harness/tools.d.ts.map +1 -0
  148. package/dist/harness/types.d.ts +561 -0
  149. package/dist/harness/types.d.ts.map +1 -0
  150. package/dist/index.cjs +2 -2
  151. package/dist/index.js +1 -1
  152. package/dist/integration/index.cjs +2 -2
  153. package/dist/integration/index.js +1 -1
  154. package/dist/llm/index.cjs +20 -20
  155. package/dist/llm/index.js +3 -3
  156. package/dist/llm/model/gateways/constants.d.ts.map +1 -1
  157. package/dist/llm/model/gateways/models-dev.d.ts +2 -3
  158. package/dist/llm/model/gateways/models-dev.d.ts.map +1 -1
  159. package/dist/llm/model/provider-types.generated.d.ts +312 -93
  160. package/dist/loop/index.cjs +12 -12
  161. package/dist/loop/index.js +1 -1
  162. package/dist/loop/network/index.d.ts.map +1 -1
  163. package/dist/loop/test-utils/options.d.ts.map +1 -1
  164. package/dist/loop/test-utils/tools.d.ts.map +1 -1
  165. package/dist/loop/workflows/agentic-execution/llm-mapping-step.d.ts.map +1 -1
  166. package/dist/loop/workflows/agentic-execution/tool-call-step.d.ts.map +1 -1
  167. package/dist/loop/workflows/stream.d.ts.map +1 -1
  168. package/dist/mastra/index.cjs +2 -2
  169. package/dist/mastra/index.d.ts +3 -3
  170. package/dist/mastra/index.d.ts.map +1 -1
  171. package/dist/mastra/index.js +1 -1
  172. package/dist/memory/index.cjs +14 -14
  173. package/dist/memory/index.js +1 -1
  174. package/dist/memory/memory.d.ts +10 -0
  175. package/dist/memory/memory.d.ts.map +1 -1
  176. package/dist/memory/types.d.ts +24 -0
  177. package/dist/memory/types.d.ts.map +1 -1
  178. package/dist/models-dev-BW2GAM3K.cjs +12 -0
  179. package/dist/{models-dev-PPIXUUCU.cjs.map → models-dev-BW2GAM3K.cjs.map} +1 -1
  180. package/dist/models-dev-MDI5E2YA.js +3 -0
  181. package/dist/{models-dev-FQVUTQ7L.js.map → models-dev-MDI5E2YA.js.map} +1 -1
  182. package/dist/observability/index.cjs +11 -11
  183. package/dist/observability/index.js +1 -1
  184. package/dist/observability/utils.d.ts.map +1 -1
  185. package/dist/processors/index.cjs +41 -41
  186. package/dist/processors/index.js +1 -1
  187. package/dist/processors/processors/skills.d.ts +5 -0
  188. package/dist/processors/processors/skills.d.ts.map +1 -1
  189. package/dist/processors/runner.d.ts +2 -2
  190. package/dist/processors/runner.d.ts.map +1 -1
  191. package/dist/processors/step-schema.d.ts +218 -49453
  192. package/dist/processors/step-schema.d.ts.map +1 -1
  193. package/dist/provider-registry-4PH2JPIA.cjs +40 -0
  194. package/dist/{provider-registry-6LZAGQET.cjs.map → provider-registry-4PH2JPIA.cjs.map} +1 -1
  195. package/dist/provider-registry-VEJ3PN4S.js +3 -0
  196. package/dist/{provider-registry-QUNT7S55.js.map → provider-registry-VEJ3PN4S.js.map} +1 -1
  197. package/dist/provider-registry.json +657 -203
  198. package/dist/relevance/index.cjs +3 -3
  199. package/dist/relevance/index.js +1 -1
  200. package/dist/server/composite-auth.d.ts.map +1 -1
  201. package/dist/server/index.cjs +6 -1
  202. package/dist/server/index.cjs.map +1 -1
  203. package/dist/server/index.js +6 -1
  204. package/dist/server/index.js.map +1 -1
  205. package/dist/storage/base.d.ts +4 -1
  206. package/dist/storage/base.d.ts.map +1 -1
  207. package/dist/storage/constants.cjs +82 -42
  208. package/dist/storage/constants.d.ts +11 -1
  209. package/dist/storage/constants.d.ts.map +1 -1
  210. package/dist/storage/constants.js +1 -1
  211. package/dist/storage/domains/agents/inmemory.d.ts.map +1 -1
  212. package/dist/storage/domains/blobs/base.d.ts +47 -0
  213. package/dist/storage/domains/blobs/base.d.ts.map +1 -0
  214. package/dist/storage/domains/blobs/index.d.ts +3 -0
  215. package/dist/storage/domains/blobs/index.d.ts.map +1 -0
  216. package/dist/storage/domains/blobs/inmemory.d.ts +17 -0
  217. package/dist/storage/domains/blobs/inmemory.d.ts.map +1 -0
  218. package/dist/storage/domains/datasets/inmemory.d.ts.map +1 -1
  219. package/dist/storage/domains/index.d.ts +3 -0
  220. package/dist/storage/domains/index.d.ts.map +1 -1
  221. package/dist/storage/domains/inmemory-db.d.ts +7 -1
  222. package/dist/storage/domains/inmemory-db.d.ts.map +1 -1
  223. package/dist/storage/domains/mcp-clients/inmemory.d.ts.map +1 -1
  224. package/dist/storage/domains/operations/inmemory.d.ts.map +1 -1
  225. package/dist/storage/domains/prompt-blocks/inmemory.d.ts.map +1 -1
  226. package/dist/storage/domains/scorer-definitions/inmemory.d.ts.map +1 -1
  227. package/dist/storage/domains/skills/base.d.ts +47 -0
  228. package/dist/storage/domains/skills/base.d.ts.map +1 -0
  229. package/dist/storage/domains/skills/index.d.ts +3 -0
  230. package/dist/storage/domains/skills/index.d.ts.map +1 -0
  231. package/dist/storage/domains/skills/inmemory.d.ts +31 -0
  232. package/dist/storage/domains/skills/inmemory.d.ts.map +1 -0
  233. package/dist/storage/domains/versioned.d.ts +12 -3
  234. package/dist/storage/domains/versioned.d.ts.map +1 -1
  235. package/dist/storage/domains/workspaces/base.d.ts +47 -0
  236. package/dist/storage/domains/workspaces/base.d.ts.map +1 -0
  237. package/dist/storage/domains/workspaces/index.d.ts +3 -0
  238. package/dist/storage/domains/workspaces/index.d.ts.map +1 -0
  239. package/dist/storage/domains/workspaces/inmemory.d.ts +31 -0
  240. package/dist/storage/domains/workspaces/inmemory.d.ts.map +1 -0
  241. package/dist/storage/index.cjs +202 -138
  242. package/dist/storage/index.js +2 -2
  243. package/dist/storage/mock.d.ts.map +1 -1
  244. package/dist/storage/types.d.ts +422 -12
  245. package/dist/storage/types.d.ts.map +1 -1
  246. package/dist/stream/base/output.d.ts.map +1 -1
  247. package/dist/stream/index.cjs +11 -11
  248. package/dist/stream/index.js +2 -2
  249. package/dist/test-utils/llm-mock.cjs +4 -4
  250. package/dist/test-utils/llm-mock.js +1 -1
  251. package/dist/tool-loop-agent/index.cjs +4 -4
  252. package/dist/tool-loop-agent/index.js +1 -1
  253. package/dist/tools/index.cjs +8 -4
  254. package/dist/tools/index.js +1 -1
  255. package/dist/tools/is-vercel-tool.cjs +2 -2
  256. package/dist/tools/is-vercel-tool.js +1 -1
  257. package/dist/tools/tool-builder/builder.d.ts.map +1 -1
  258. package/dist/tools/tool.d.ts +13 -0
  259. package/dist/tools/tool.d.ts.map +1 -1
  260. package/dist/tools/toolchecks.d.ts.map +1 -1
  261. package/dist/tools/types.d.ts +24 -0
  262. package/dist/tools/types.d.ts.map +1 -1
  263. package/dist/types/zod-compat.d.ts +27 -6
  264. package/dist/types/zod-compat.d.ts.map +1 -1
  265. package/dist/utils.cjs +23 -23
  266. package/dist/utils.js +1 -1
  267. package/dist/vector/index.cjs +12 -12
  268. package/dist/vector/index.js +2 -2
  269. package/dist/workflows/evented/index.cjs +10 -10
  270. package/dist/workflows/evented/index.js +1 -1
  271. package/dist/workflows/handlers/entry.d.ts.map +1 -1
  272. package/dist/workflows/index.cjs +25 -25
  273. package/dist/workflows/index.js +1 -1
  274. package/dist/workflows/workflow.d.ts +2 -2
  275. package/dist/workflows/workflow.d.ts.map +1 -1
  276. package/dist/workspace/constants/index.d.ts +1 -0
  277. package/dist/workspace/constants/index.d.ts.map +1 -1
  278. package/dist/workspace/errors.d.ts +3 -0
  279. package/dist/workspace/errors.d.ts.map +1 -1
  280. package/dist/workspace/filesystem/composite-filesystem.d.ts +75 -6
  281. package/dist/workspace/filesystem/composite-filesystem.d.ts.map +1 -1
  282. package/dist/workspace/filesystem/local-filesystem.d.ts +42 -0
  283. package/dist/workspace/filesystem/local-filesystem.d.ts.map +1 -1
  284. package/dist/workspace/glob.d.ts +61 -0
  285. package/dist/workspace/glob.d.ts.map +1 -0
  286. package/dist/workspace/index.cjs +133 -41
  287. package/dist/workspace/index.d.ts +8 -1
  288. package/dist/workspace/index.d.ts.map +1 -1
  289. package/dist/workspace/index.js +1 -1
  290. package/dist/workspace/sandbox/local-sandbox.d.ts.map +1 -1
  291. package/dist/workspace/skills/composite-versioned-skill-source.d.ts +44 -0
  292. package/dist/workspace/skills/composite-versioned-skill-source.d.ts.map +1 -0
  293. package/dist/workspace/skills/index.d.ts +3 -0
  294. package/dist/workspace/skills/index.d.ts.map +1 -1
  295. package/dist/workspace/skills/local-skill-source.d.ts.map +1 -1
  296. package/dist/workspace/skills/publish.d.ts +34 -0
  297. package/dist/workspace/skills/publish.d.ts.map +1 -0
  298. package/dist/workspace/skills/skill-source.d.ts +2 -0
  299. package/dist/workspace/skills/skill-source.d.ts.map +1 -1
  300. package/dist/workspace/skills/types.d.ts +16 -0
  301. package/dist/workspace/skills/types.d.ts.map +1 -1
  302. package/dist/workspace/skills/versioned-skill-source.d.ts +20 -0
  303. package/dist/workspace/skills/versioned-skill-source.d.ts.map +1 -0
  304. package/dist/workspace/skills/workspace-skills.d.ts +5 -0
  305. package/dist/workspace/skills/workspace-skills.d.ts.map +1 -1
  306. package/dist/workspace/tools/delete-file.d.ts +5 -0
  307. package/dist/workspace/tools/delete-file.d.ts.map +1 -0
  308. package/dist/workspace/tools/edit-file.d.ts +7 -0
  309. package/dist/workspace/tools/edit-file.d.ts.map +1 -0
  310. package/dist/workspace/tools/execute-command.d.ts +7 -0
  311. package/dist/workspace/tools/execute-command.d.ts.map +1 -0
  312. package/dist/workspace/tools/file-stat.d.ts +4 -0
  313. package/dist/workspace/tools/file-stat.d.ts.map +1 -0
  314. package/dist/workspace/tools/grep.d.ts +9 -0
  315. package/dist/workspace/tools/grep.d.ts.map +1 -0
  316. package/dist/workspace/tools/helpers.d.ts +36 -0
  317. package/dist/workspace/tools/helpers.d.ts.map +1 -0
  318. package/dist/workspace/tools/index-content.d.ts +6 -0
  319. package/dist/workspace/tools/index-content.d.ts.map +1 -0
  320. package/dist/workspace/tools/index.d.ts +13 -1
  321. package/dist/workspace/tools/index.d.ts.map +1 -1
  322. package/dist/workspace/tools/list-files.d.ts +10 -0
  323. package/dist/workspace/tools/list-files.d.ts.map +1 -0
  324. package/dist/workspace/tools/mkdir.d.ts +5 -0
  325. package/dist/workspace/tools/mkdir.d.ts.map +1 -0
  326. package/dist/workspace/tools/read-file.d.ts +8 -0
  327. package/dist/workspace/tools/read-file.d.ts.map +1 -0
  328. package/dist/workspace/tools/search.d.ts +7 -0
  329. package/dist/workspace/tools/search.d.ts.map +1 -0
  330. package/dist/workspace/tools/tools.d.ts +5 -7
  331. package/dist/workspace/tools/tools.d.ts.map +1 -1
  332. package/dist/workspace/tools/tree-formatter.d.ts +2 -0
  333. package/dist/workspace/tools/tree-formatter.d.ts.map +1 -1
  334. package/dist/workspace/tools/write-file.d.ts +6 -0
  335. package/dist/workspace/tools/write-file.d.ts.map +1 -0
  336. package/dist/workspace/workspace.d.ts +56 -10
  337. package/dist/workspace/workspace.d.ts.map +1 -1
  338. package/harness.d.ts +1 -0
  339. package/package.json +8 -6
  340. package/src/llm/model/provider-types.generated.d.ts +312 -93
  341. package/dist/chunk-3JVFFAJX.cjs.map +0 -1
  342. package/dist/chunk-3X3CZUXI.js.map +0 -1
  343. package/dist/chunk-4EHGOATH.js.map +0 -1
  344. package/dist/chunk-4IJ4UDZX.cjs.map +0 -1
  345. package/dist/chunk-4KFEMXTV.cjs.map +0 -1
  346. package/dist/chunk-4TQ4EBYX.js.map +0 -1
  347. package/dist/chunk-4XSAZPPS.js.map +0 -1
  348. package/dist/chunk-65PHUUMF.cjs.map +0 -1
  349. package/dist/chunk-7NKUSQEV.js.map +0 -1
  350. package/dist/chunk-7UWHFWST.cjs.map +0 -1
  351. package/dist/chunk-AXHBJ4GX.js.map +0 -1
  352. package/dist/chunk-AY6DBRS3.js.map +0 -1
  353. package/dist/chunk-BP7VYTOP.cjs.map +0 -1
  354. package/dist/chunk-CYUP7QWT.cjs.map +0 -1
  355. package/dist/chunk-CZ4NQANZ.cjs.map +0 -1
  356. package/dist/chunk-NJ7TL3LQ.js.map +0 -1
  357. package/dist/chunk-NZG2JAKS.cjs.map +0 -1
  358. package/dist/chunk-PS5ONCXY.js.map +0 -1
  359. package/dist/chunk-RS6CZXGA.js.map +0 -1
  360. package/dist/chunk-SU5APAM6.cjs.map +0 -1
  361. package/dist/chunk-V2MLGA7T.js.map +0 -1
  362. package/dist/chunk-VTE2OBKS.cjs.map +0 -1
  363. package/dist/chunk-Y3TQ52UE.js.map +0 -1
  364. package/dist/chunk-YNNJLLFN.cjs.map +0 -1
  365. package/dist/chunk-ZATLLPIH.js.map +0 -1
  366. package/dist/models-dev-FQVUTQ7L.js +0 -3
  367. package/dist/models-dev-PPIXUUCU.cjs +0 -12
  368. package/dist/provider-registry-6LZAGQET.cjs +0 -40
  369. package/dist/provider-registry-QUNT7S55.js +0 -3
@@ -1,9 +1,11 @@
1
- import { createTool } from './chunk-RS6CZXGA.js';
1
+ import { createTool } from './chunk-BQHWJLXU.js';
2
2
  import { MastraBase } from './chunk-WCAFTXGK.js';
3
3
  import { RegisteredLogger } from './chunk-X2WMFSPB.js';
4
+ import posixPath from 'path/posix';
4
5
  import { constants } from 'fs';
5
6
  import * as fs2 from 'fs/promises';
6
7
  import * as nodePath from 'path';
8
+ import picomatch from 'picomatch';
7
9
  import * as crypto from 'crypto';
8
10
  import { createHash } from 'crypto';
9
11
  import matter from 'gray-matter';
@@ -22,6 +24,12 @@ var WorkspaceError = class extends Error {
22
24
  this.name = "WorkspaceError";
23
25
  }
24
26
  };
27
+ var WorkspaceNotAvailableError = class extends WorkspaceError {
28
+ constructor() {
29
+ super("Workspace not available. Ensure the agent has a workspace configured.", "NO_WORKSPACE");
30
+ this.name = "WorkspaceNotAvailableError";
31
+ }
32
+ };
25
33
  var FilesystemNotAvailableError = class extends WorkspaceError {
26
34
  constructor() {
27
35
  super("Workspace does not have a filesystem configured", "NO_FILESYSTEM");
@@ -141,6 +149,7 @@ var CompositeFilesystem = class {
141
149
  id;
142
150
  name = "CompositeFilesystem";
143
151
  provider = "composite";
152
+ readOnly;
144
153
  status = "ready";
145
154
  _mounts;
146
155
  constructor(config) {
@@ -153,6 +162,7 @@ var CompositeFilesystem = class {
153
162
  if (this._mounts.size === 0) {
154
163
  throw new Error("CompositeFilesystem requires at least one mount");
155
164
  }
165
+ this.readOnly = [...this._mounts.values()].every((fs5) => fs5.readOnly) || void 0;
156
166
  const mountPaths = [...this._mounts.keys()];
157
167
  for (const a of mountPaths) {
158
168
  for (const b of mountPaths) {
@@ -170,10 +180,29 @@ var CompositeFilesystem = class {
170
180
  }
171
181
  /**
172
182
  * Get the mounts map.
183
+ * Returns a typed map where `get()` preserves the concrete filesystem type per mount path.
173
184
  */
174
185
  get mounts() {
175
186
  return this._mounts;
176
187
  }
188
+ /**
189
+ * Get status and metadata for this composite filesystem.
190
+ * Includes info from each mounted filesystem in `metadata.mounts`.
191
+ */
192
+ async getInfo() {
193
+ const mounts = {};
194
+ for (const [mountPath, fs5] of this._mounts) {
195
+ mounts[mountPath] = await fs5.getInfo?.() ?? null;
196
+ }
197
+ return {
198
+ id: this.id,
199
+ name: this.name,
200
+ provider: this.provider,
201
+ status: this.status,
202
+ readOnly: this.readOnly,
203
+ metadata: { mounts }
204
+ };
205
+ }
177
206
  /**
178
207
  * Get the underlying filesystem for a given path.
179
208
  * Returns undefined if the path doesn't resolve to any mount.
@@ -192,7 +221,8 @@ var CompositeFilesystem = class {
192
221
  }
193
222
  normalizePath(path4) {
194
223
  if (!path4 || path4 === "/") return "/";
195
- let n = path4.startsWith("/") ? path4 : `/${path4}`;
224
+ let n = posixPath.normalize(path4);
225
+ if (!n.startsWith("/")) n = `/${n}`;
196
226
  if (n.length > 1 && n.endsWith("/")) n = n.slice(0, -1);
197
227
  return n;
198
228
  }
@@ -426,8 +456,7 @@ var CompositeFilesystem = class {
426
456
  return `- ${mountPath}: ${name} ${access2}`;
427
457
  }).join("\n");
428
458
  return `Mounted filesystems:
429
- ${mountDescriptions}
430
- Files written via workspace tools are accessible at the same paths in sandbox commands.`;
459
+ ${mountDescriptions}`;
431
460
  }
432
461
  };
433
462
 
@@ -737,6 +766,7 @@ var LocalFilesystem = class extends MastraFilesystem {
737
766
  status = "pending";
738
767
  _basePath;
739
768
  _contained;
769
+ _allowedPaths;
740
770
  /**
741
771
  * The absolute base path on disk where files are stored.
742
772
  * Useful for understanding how workspace paths map to disk paths.
@@ -744,16 +774,51 @@ var LocalFilesystem = class extends MastraFilesystem {
744
774
  get basePath() {
745
775
  return this._basePath;
746
776
  }
777
+ /**
778
+ * Current set of additional allowed paths (absolute, resolved).
779
+ * These paths are permitted beyond basePath when containment is enabled.
780
+ */
781
+ get allowedPaths() {
782
+ return this._allowedPaths;
783
+ }
784
+ /**
785
+ * Update allowed paths. Accepts a direct array or an updater callback
786
+ * receiving the current paths (React setState pattern).
787
+ *
788
+ * @example
789
+ * ```typescript
790
+ * // Set directly
791
+ * fs.setAllowedPaths(['/home/user/.config']);
792
+ *
793
+ * // Update with callback
794
+ * fs.setAllowedPaths(prev => [...prev, '/home/user/.ssh']);
795
+ * ```
796
+ */
797
+ setAllowedPaths(pathsOrUpdater) {
798
+ const newPaths = typeof pathsOrUpdater === "function" ? pathsOrUpdater(this._allowedPaths) : pathsOrUpdater;
799
+ this._allowedPaths = newPaths.map((p) => nodePath.resolve(p));
800
+ }
747
801
  constructor(options) {
748
802
  super({ ...options, name: "LocalFilesystem" });
749
803
  this.id = options.id ?? this.generateId();
750
804
  this._basePath = nodePath.resolve(options.basePath);
751
805
  this._contained = options.contained ?? true;
752
806
  this.readOnly = options.readOnly;
807
+ this._allowedPaths = (options.allowedPaths ?? []).map((p) => nodePath.resolve(p));
753
808
  }
754
809
  generateId() {
755
810
  return `local-fs-${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 8)}`;
756
811
  }
812
+ /**
813
+ * Check if an absolute path falls within basePath or any allowed path.
814
+ */
815
+ _isWithinAnyRoot(absolutePath) {
816
+ const roots = [this._basePath, ...this._allowedPaths];
817
+ return roots.some((root) => {
818
+ const relative2 = nodePath.relative(root, absolutePath);
819
+ return !relative2.startsWith("..") && !nodePath.isAbsolute(relative2);
820
+ });
821
+ }
757
822
  toBuffer(content) {
758
823
  if (Buffer.isBuffer(content)) return content;
759
824
  if (content instanceof Uint8Array) return Buffer.from(content);
@@ -765,8 +830,7 @@ var LocalFilesystem = class extends MastraFilesystem {
765
830
  absolutePath = nodePath.normalize(inputPath);
766
831
  } else if (this._contained && nodePath.isAbsolute(inputPath)) {
767
832
  const normalized = nodePath.normalize(inputPath);
768
- const relative2 = nodePath.relative(this._basePath, normalized);
769
- if (!relative2.startsWith("..") && !nodePath.isAbsolute(relative2)) {
833
+ if (this._isWithinAnyRoot(normalized)) {
770
834
  absolutePath = normalized;
771
835
  } else {
772
836
  const cleanedPath = inputPath.replace(/^\/+/, "");
@@ -777,8 +841,7 @@ var LocalFilesystem = class extends MastraFilesystem {
777
841
  absolutePath = nodePath.resolve(this._basePath, nodePath.normalize(cleanedPath));
778
842
  }
779
843
  if (this._contained) {
780
- const relative2 = nodePath.relative(this._basePath, absolutePath);
781
- if (relative2.startsWith("..") || nodePath.isAbsolute(relative2)) {
844
+ if (!this._isWithinAnyRoot(absolutePath)) {
782
845
  throw new PermissionError(inputPath, "access");
783
846
  }
784
847
  }
@@ -798,14 +861,19 @@ var LocalFilesystem = class extends MastraFilesystem {
798
861
  */
799
862
  async assertPathContained(absolutePath) {
800
863
  if (!this._contained) return;
801
- let baseReal;
802
- try {
803
- baseReal = await fs2.realpath(this._basePath);
804
- } catch (error) {
805
- if (isEnoentError(error)) {
806
- throw new DirectoryNotFoundError(this._basePath);
864
+ const rootReals = [];
865
+ for (const root of [this._basePath, ...this._allowedPaths]) {
866
+ try {
867
+ rootReals.push(await fs2.realpath(root));
868
+ } catch (error) {
869
+ if (isEnoentError(error)) {
870
+ continue;
871
+ }
872
+ throw error;
807
873
  }
808
- throw error;
874
+ }
875
+ if (rootReals.length === 0) {
876
+ throw new DirectoryNotFoundError(this._basePath);
809
877
  }
810
878
  let targetReal;
811
879
  try {
@@ -832,7 +900,10 @@ var LocalFilesystem = class extends MastraFilesystem {
832
900
  throw error;
833
901
  }
834
902
  }
835
- if (targetReal !== baseReal && !targetReal.startsWith(baseReal + nodePath.sep)) {
903
+ const isWithinRoot = rootReals.some(
904
+ (rootReal) => targetReal === rootReal || targetReal.startsWith(rootReal + nodePath.sep)
905
+ );
906
+ if (!isWithinRoot) {
836
907
  throw new PermissionError(absolutePath, "access");
837
908
  }
838
909
  }
@@ -1191,15 +1262,17 @@ var LocalFilesystem = class extends MastraFilesystem {
1191
1262
  error: this.error,
1192
1263
  metadata: {
1193
1264
  basePath: this.basePath,
1194
- contained: this._contained
1265
+ contained: this._contained,
1266
+ ...this._allowedPaths.length > 0 && { allowedPaths: [...this._allowedPaths] }
1195
1267
  }
1196
1268
  };
1197
1269
  }
1198
1270
  getInstructions() {
1271
+ const allowedNote = this._allowedPaths.length > 0 ? ` Additionally, the following paths outside basePath are accessible: ${this._allowedPaths.join(", ")}.` : "";
1199
1272
  if (this._contained) {
1200
- return `Local filesystem at "${this.basePath}". Files at workspace path "/foo" are stored at "${this.basePath}/foo" on disk.`;
1273
+ return `Local filesystem at "${this.basePath}". Files at workspace path "/foo" are stored at "${this.basePath}/foo" on disk.${allowedNote}`;
1201
1274
  }
1202
- return `Local filesystem rooted at "${this.basePath}". Containment is disabled so absolute paths access the real filesystem. Use paths relative to "${this.basePath}" (e.g. "foo/bar.txt") for workspace files. Avoid unnecessary listing "/" as it would traverse the entire host filesystem.`;
1275
+ return `Local filesystem rooted at "${this.basePath}". Containment is disabled so absolute paths access the real filesystem. Use paths relative to "${this.basePath}" (e.g. "foo/bar.txt") for workspace files. Avoid unnecessary listing "/" as it would traverse the entire host filesystem.${allowedNote}`;
1203
1276
  }
1204
1277
  };
1205
1278
  var InMemoryFileReadTracker = class {
@@ -1242,6 +1315,38 @@ var InMemoryFileReadTracker = class {
1242
1315
  return normalized.replace(/\/$/, "") || "/";
1243
1316
  }
1244
1317
  };
1318
+ var GLOB_CHARS = /[*?{}[\]]/;
1319
+ function isGlobPattern(input) {
1320
+ return GLOB_CHARS.test(input);
1321
+ }
1322
+ function extractGlobBase(pattern) {
1323
+ const firstMeta = pattern.search(GLOB_CHARS);
1324
+ if (firstMeta === -1) {
1325
+ return pattern;
1326
+ }
1327
+ const prefix = pattern.slice(0, firstMeta);
1328
+ const lastSlash = prefix.lastIndexOf("/");
1329
+ if (lastSlash <= 0) {
1330
+ return "/";
1331
+ }
1332
+ return prefix.slice(0, lastSlash);
1333
+ }
1334
+ function normalizeForMatch(input) {
1335
+ if (input.startsWith("./")) return input.slice(2);
1336
+ if (input.startsWith("/")) return input.slice(1);
1337
+ return input;
1338
+ }
1339
+ function createGlobMatcher(patterns, options) {
1340
+ const patternArray = (Array.isArray(patterns) ? patterns : [patterns]).map(normalizeForMatch);
1341
+ const matcher = picomatch(patternArray, {
1342
+ posix: true,
1343
+ dot: options?.dot ?? false
1344
+ });
1345
+ return (path4) => matcher(normalizeForMatch(path4));
1346
+ }
1347
+ function matchGlob(path4, pattern, options) {
1348
+ return createGlobMatcher(pattern, options)(path4);
1349
+ }
1245
1350
 
1246
1351
  // src/workspace/sandbox/errors.ts
1247
1352
  var SandboxError = class extends Error {
@@ -2695,11 +2800,211 @@ var LocalSkillSource = class {
2695
2800
  const entries = await fs2.readdir(resolved, { withFileTypes: true });
2696
2801
  return entries.map((entry) => ({
2697
2802
  name: entry.name,
2698
- type: entry.isDirectory() ? "directory" : "file"
2803
+ type: entry.isDirectory() ? "directory" : "file",
2804
+ isSymlink: entry.isSymbolicLink() || void 0
2699
2805
  }));
2700
2806
  }
2701
2807
  };
2702
- var WorkspaceSkillsImpl = class {
2808
+
2809
+ // src/workspace/skills/versioned-skill-source.ts
2810
+ var VersionedSkillSource = class {
2811
+ #tree;
2812
+ #blobStore;
2813
+ #versionCreatedAt;
2814
+ /** Computed set of directory paths from the tree entries */
2815
+ #directories;
2816
+ constructor(tree, blobStore, versionCreatedAt) {
2817
+ this.#tree = tree;
2818
+ this.#blobStore = blobStore;
2819
+ this.#versionCreatedAt = versionCreatedAt;
2820
+ this.#directories = this.#computeDirectories();
2821
+ }
2822
+ /**
2823
+ * Compute all directory paths implied by the file tree.
2824
+ * For a file at "references/api.md", this adds "" (root), "references".
2825
+ */
2826
+ #computeDirectories() {
2827
+ const dirs = /* @__PURE__ */ new Set();
2828
+ dirs.add("");
2829
+ dirs.add(".");
2830
+ for (const filePath of Object.keys(this.#tree.entries)) {
2831
+ const parts = filePath.split("/");
2832
+ for (let i = 1; i < parts.length; i++) {
2833
+ dirs.add(parts.slice(0, i).join("/"));
2834
+ }
2835
+ }
2836
+ return dirs;
2837
+ }
2838
+ /**
2839
+ * Normalize a path by stripping leading/trailing slashes and dots.
2840
+ */
2841
+ #normalizePath(path4) {
2842
+ let normalized = path4.replace(/^[./\\]+|[/\\]+$/g, "");
2843
+ if (normalized === "") return "";
2844
+ return normalized;
2845
+ }
2846
+ async exists(path4) {
2847
+ const normalized = this.#normalizePath(path4);
2848
+ if (this.#tree.entries[normalized]) return true;
2849
+ return this.#directories.has(normalized);
2850
+ }
2851
+ async stat(path4) {
2852
+ const normalized = this.#normalizePath(path4);
2853
+ const name = normalized.split("/").pop() || normalized || ".";
2854
+ const entry = this.#tree.entries[normalized];
2855
+ if (entry) {
2856
+ return {
2857
+ name,
2858
+ type: "file",
2859
+ size: entry.size,
2860
+ createdAt: this.#versionCreatedAt,
2861
+ modifiedAt: this.#versionCreatedAt,
2862
+ mimeType: entry.mimeType
2863
+ };
2864
+ }
2865
+ if (this.#directories.has(normalized)) {
2866
+ return {
2867
+ name,
2868
+ type: "directory",
2869
+ size: 0,
2870
+ createdAt: this.#versionCreatedAt,
2871
+ modifiedAt: this.#versionCreatedAt
2872
+ };
2873
+ }
2874
+ throw new Error(`Path not found in skill version tree: ${path4}`);
2875
+ }
2876
+ async readFile(path4) {
2877
+ const normalized = this.#normalizePath(path4);
2878
+ const entry = this.#tree.entries[normalized];
2879
+ if (!entry) {
2880
+ throw new Error(`File not found in skill version tree: ${path4}`);
2881
+ }
2882
+ const blob = await this.#blobStore.get(entry.blobHash);
2883
+ if (!blob) {
2884
+ throw new Error(`Blob not found for hash ${entry.blobHash} (file: ${path4})`);
2885
+ }
2886
+ if (entry.encoding === "base64") {
2887
+ return Buffer.from(blob.content, "base64");
2888
+ }
2889
+ return blob.content;
2890
+ }
2891
+ async readdir(path4) {
2892
+ const normalized = this.#normalizePath(path4);
2893
+ if (!this.#directories.has(normalized)) {
2894
+ throw new Error(`Directory not found in skill version tree: ${path4}`);
2895
+ }
2896
+ const prefix = normalized === "" ? "" : normalized + "/";
2897
+ const seen = /* @__PURE__ */ new Set();
2898
+ const entries = [];
2899
+ for (const filePath of Object.keys(this.#tree.entries)) {
2900
+ if (!filePath.startsWith(prefix)) continue;
2901
+ const remaining = filePath.slice(prefix.length);
2902
+ const nextSegment = remaining.split("/")[0];
2903
+ if (!nextSegment || seen.has(nextSegment)) continue;
2904
+ seen.add(nextSegment);
2905
+ const isDirectory = remaining.includes("/");
2906
+ entries.push({
2907
+ name: nextSegment,
2908
+ type: isDirectory ? "directory" : "file"
2909
+ });
2910
+ }
2911
+ return entries;
2912
+ }
2913
+ };
2914
+
2915
+ // src/workspace/skills/composite-versioned-skill-source.ts
2916
+ var CompositeVersionedSkillSource = class {
2917
+ #sources = /* @__PURE__ */ new Map();
2918
+ #fallback;
2919
+ #fallbackSkills;
2920
+ constructor(entries, blobStore, options) {
2921
+ for (const entry of entries) {
2922
+ this.#sources.set(entry.dirName, new VersionedSkillSource(entry.tree, blobStore, entry.versionCreatedAt));
2923
+ }
2924
+ this.#fallback = options?.fallback;
2925
+ this.#fallbackSkills = new Set(options?.fallbackSkills ?? []);
2926
+ }
2927
+ #normalizePath(path4) {
2928
+ return path4.replace(/^[./\\]+|[/\\]+$/g, "");
2929
+ }
2930
+ /**
2931
+ * Route a path to the correct source.
2932
+ * Returns the source and the remaining path within that source.
2933
+ */
2934
+ #routePath(path4) {
2935
+ const normalized = this.#normalizePath(path4);
2936
+ if (normalized === "") return null;
2937
+ const segments = normalized.split("/");
2938
+ const skillDir = segments[0];
2939
+ const subPath = segments.slice(1).join("/");
2940
+ if (this.#fallbackSkills.has(skillDir) && this.#fallback) {
2941
+ return { source: this.#fallback, subPath: normalized };
2942
+ }
2943
+ const versionedSource = this.#sources.get(skillDir);
2944
+ if (versionedSource) {
2945
+ return { source: versionedSource, subPath };
2946
+ }
2947
+ if (this.#fallback) {
2948
+ return { source: this.#fallback, subPath: normalized };
2949
+ }
2950
+ return null;
2951
+ }
2952
+ async exists(path4) {
2953
+ const normalized = this.#normalizePath(path4);
2954
+ if (normalized === "") return true;
2955
+ const route = this.#routePath(path4);
2956
+ if (!route) return false;
2957
+ return route.source.exists(route.subPath);
2958
+ }
2959
+ async stat(path4) {
2960
+ const normalized = this.#normalizePath(path4);
2961
+ if (normalized === "") {
2962
+ return {
2963
+ name: ".",
2964
+ type: "directory",
2965
+ size: 0,
2966
+ createdAt: /* @__PURE__ */ new Date(),
2967
+ modifiedAt: /* @__PURE__ */ new Date()
2968
+ };
2969
+ }
2970
+ const route = this.#routePath(path4);
2971
+ if (!route) {
2972
+ throw new Error(`Path not found in composite skill source: ${path4}`);
2973
+ }
2974
+ return route.source.stat(route.subPath);
2975
+ }
2976
+ async readFile(path4) {
2977
+ const route = this.#routePath(path4);
2978
+ if (!route) {
2979
+ throw new Error(`File not found in composite skill source: ${path4}`);
2980
+ }
2981
+ return route.source.readFile(route.subPath);
2982
+ }
2983
+ async readdir(path4) {
2984
+ const normalized = this.#normalizePath(path4);
2985
+ if (normalized === "") {
2986
+ const entries = [];
2987
+ const seen = /* @__PURE__ */ new Set();
2988
+ for (const dirName of this.#sources.keys()) {
2989
+ entries.push({ name: dirName, type: "directory" });
2990
+ seen.add(dirName);
2991
+ }
2992
+ for (const dirName of this.#fallbackSkills) {
2993
+ if (!seen.has(dirName)) {
2994
+ entries.push({ name: dirName, type: "directory" });
2995
+ seen.add(dirName);
2996
+ }
2997
+ }
2998
+ return entries;
2999
+ }
3000
+ const route = this.#routePath(path4);
3001
+ if (!route) {
3002
+ throw new Error(`Directory not found in composite skill source: ${path4}`);
3003
+ }
3004
+ return route.source.readdir(route.subPath);
3005
+ }
3006
+ };
3007
+ var WorkspaceSkillsImpl = class _WorkspaceSkillsImpl {
2703
3008
  #source;
2704
3009
  #skillsResolver;
2705
3010
  #searchEngine;
@@ -2714,6 +3019,13 @@ var WorkspaceSkillsImpl = class {
2714
3019
  #lastDiscoveryTime = 0;
2715
3020
  /** Currently resolved skills paths (used to detect changes) */
2716
3021
  #resolvedPaths = [];
3022
+ /** Cached glob-resolved directories and per-pattern resolve timestamps */
3023
+ #globDirCache = /* @__PURE__ */ new Map();
3024
+ #globResolveTimes = /* @__PURE__ */ new Map();
3025
+ static GLOB_RESOLVE_INTERVAL = 5e3;
3026
+ // Re-walk glob dirs every 5s
3027
+ static STALENESS_CHECK_COOLDOWN = 2e3;
3028
+ // Skip staleness check for 2s after discovery
2717
3029
  constructor(config) {
2718
3030
  this.#source = config.source;
2719
3031
  this.#skillsResolver = config.skills;
@@ -2766,6 +3078,35 @@ var WorkspaceSkillsImpl = class {
2766
3078
  await this.refresh();
2767
3079
  }
2768
3080
  }
3081
+ async addSkill(skillPath) {
3082
+ await this.#ensureInitialized();
3083
+ let skillFilePath;
3084
+ let dirName;
3085
+ if (skillPath.endsWith("/SKILL.md") || skillPath === "SKILL.md") {
3086
+ skillFilePath = skillPath;
3087
+ dirName = this.#getParentPath(skillPath).split("/").pop() || "unknown";
3088
+ } else {
3089
+ skillFilePath = this.#joinPath(skillPath, "SKILL.md");
3090
+ dirName = skillPath.split("/").pop() || "unknown";
3091
+ }
3092
+ const source = this.#inferSource(skillPath);
3093
+ const skill = await this.#parseSkillFile(skillFilePath, dirName, source);
3094
+ const existing = this.#skills.get(skill.name);
3095
+ if (existing) {
3096
+ await this.#removeSkillFromIndex(existing);
3097
+ }
3098
+ this.#skills.set(skill.name, skill);
3099
+ await this.#indexSkill(skill);
3100
+ this.#lastDiscoveryTime = Date.now();
3101
+ }
3102
+ async removeSkill(skillName) {
3103
+ await this.#ensureInitialized();
3104
+ const skill = this.#skills.get(skillName);
3105
+ if (!skill) return;
3106
+ await this.#removeSkillFromIndex(skill);
3107
+ this.#skills.delete(skillName);
3108
+ this.#lastDiscoveryTime = Date.now();
3109
+ }
2769
3110
  /**
2770
3111
  * Resolve skills paths from the resolver (static array or function).
2771
3112
  */
@@ -2922,19 +3263,82 @@ var WorkspaceSkillsImpl = class {
2922
3263
  /**
2923
3264
  * Discover skills from all skills paths.
2924
3265
  * Uses currently resolved paths (must be set before calling).
3266
+ *
3267
+ * Paths can be plain directories (e.g., '/skills') or glob patterns
3268
+ * (e.g., '**\/skills'). Glob patterns resolve to directories that match
3269
+ * the pattern, each of which is then scanned for skills.
2925
3270
  */
2926
3271
  async #discoverSkills() {
3272
+ this.#globDirCache.clear();
3273
+ this.#globResolveTimes.clear();
2927
3274
  for (const skillsPath of this.#resolvedPaths) {
2928
3275
  const source = this.#determineSource(skillsPath);
2929
- await this.#discoverSkillsInPath(skillsPath, source);
3276
+ if (isGlobPattern(skillsPath)) {
3277
+ const matchingDirs = await this.#resolveGlobToDirectories(skillsPath);
3278
+ this.#globDirCache.set(skillsPath, matchingDirs);
3279
+ this.#globResolveTimes.set(skillsPath, Date.now());
3280
+ for (const dir of matchingDirs) {
3281
+ await this.#discoverSkillsInPath(dir, source);
3282
+ }
3283
+ } else {
3284
+ const isDirect = await this.#discoverDirectSkill(skillsPath, source);
3285
+ if (!isDirect) {
3286
+ await this.#discoverSkillsInPath(skillsPath, source);
3287
+ }
3288
+ }
2930
3289
  }
2931
3290
  this.#lastDiscoveryTime = Date.now();
2932
3291
  }
3292
+ /**
3293
+ * Resolve a glob pattern to a list of matching directories.
3294
+ * Walks from extractGlobBase() and tests each directory against the pattern.
3295
+ *
3296
+ * Note: Broad patterns like `/** /skills` resolve to a walk root of `/`,
3297
+ * scanning the entire workspace tree. This is cached per-pattern with a
3298
+ * TTL (GLOB_RESOLVE_INTERVAL) to limit I/O. For large workspaces, prefer
3299
+ * more specific patterns like `/src/** /skills` to narrow the walk root.
3300
+ */
3301
+ async #resolveGlobToDirectories(pattern) {
3302
+ const walkRoot = extractGlobBase(pattern);
3303
+ const matcher = createGlobMatcher(pattern, { dot: true });
3304
+ const matchingDirs = [];
3305
+ await this.#walkForDirectories(walkRoot, (dirPath) => {
3306
+ if (matcher(dirPath)) {
3307
+ matchingDirs.push(dirPath);
3308
+ }
3309
+ });
3310
+ return matchingDirs;
3311
+ }
3312
+ /**
3313
+ * Walk a directory tree and call callback for each directory found.
3314
+ */
3315
+ async #walkForDirectories(basePath, callback, depth = 0, maxDepth = 4) {
3316
+ if (depth >= maxDepth) return;
3317
+ try {
3318
+ const entries = await this.#source.readdir(basePath);
3319
+ for (const entry of entries) {
3320
+ if (entry.type !== "directory" || entry.isSymlink) continue;
3321
+ const entryPath = basePath === "/" ? `/${entry.name}` : `${basePath}/${entry.name}`;
3322
+ callback(entryPath);
3323
+ await this.#walkForDirectories(entryPath, callback, depth + 1, maxDepth);
3324
+ }
3325
+ } catch {
3326
+ }
3327
+ }
2933
3328
  /**
2934
3329
  * Discover skills in a single path
2935
3330
  */
2936
3331
  async #discoverSkillsInPath(skillsPath, source) {
2937
- if (!await this.#source.exists(skillsPath)) {
3332
+ try {
3333
+ if (!await this.#source.exists(skillsPath)) {
3334
+ return;
3335
+ }
3336
+ } catch (error) {
3337
+ if (error instanceof Error) {
3338
+ console.warn(`[WorkspaceSkills] Cannot access skills path "${skillsPath}": ${error.message}`);
3339
+ } else {
3340
+ console.warn(`[WorkspaceSkills] Cannot access skills path "${skillsPath}": ${String(error)}`);
3341
+ }
2938
3342
  return;
2939
3343
  }
2940
3344
  try {
@@ -2961,35 +3365,107 @@ var WorkspaceSkillsImpl = class {
2961
3365
  }
2962
3366
  }
2963
3367
  }
3368
+ /**
3369
+ * Attempt to discover a skill from a direct path reference.
3370
+ *
3371
+ * Handles two cases:
3372
+ * - Path ends with `/SKILL.md` → parse directly, extract dirName from parent
3373
+ * - Path is a directory containing `SKILL.md` → parse it as a single skill
3374
+ *
3375
+ * Returns `true` if the path was a direct skill reference (skip subdirectory scan),
3376
+ * `false` to fall through to the normal subdirectory scan.
3377
+ */
3378
+ async #discoverDirectSkill(skillsPath, source) {
3379
+ try {
3380
+ if (skillsPath.endsWith("/SKILL.md") || skillsPath === "SKILL.md") {
3381
+ if (!await this.#source.exists(skillsPath)) {
3382
+ return true;
3383
+ }
3384
+ const skillDir = this.#getParentPath(skillsPath);
3385
+ const dirName = skillDir.split("/").pop() || skillDir;
3386
+ try {
3387
+ const skill = await this.#parseSkillFile(skillsPath, dirName, source);
3388
+ this.#skills.set(skill.name, skill);
3389
+ await this.#indexSkill(skill);
3390
+ } catch (error) {
3391
+ if (error instanceof Error) {
3392
+ console.error(`[WorkspaceSkills] Failed to load skill from ${skillsPath}:`, error.message);
3393
+ }
3394
+ }
3395
+ return true;
3396
+ }
3397
+ if (await this.#source.exists(skillsPath)) {
3398
+ const skillFilePath = this.#joinPath(skillsPath, "SKILL.md");
3399
+ if (await this.#source.exists(skillFilePath)) {
3400
+ const dirName = skillsPath.split("/").pop() || skillsPath;
3401
+ try {
3402
+ const skill = await this.#parseSkillFile(skillFilePath, dirName, source);
3403
+ this.#skills.set(skill.name, skill);
3404
+ await this.#indexSkill(skill);
3405
+ } catch (error) {
3406
+ if (error instanceof Error) {
3407
+ console.error(`[WorkspaceSkills] Failed to load skill from ${skillFilePath}:`, error.message);
3408
+ }
3409
+ }
3410
+ return true;
3411
+ }
3412
+ }
3413
+ return false;
3414
+ } catch {
3415
+ return false;
3416
+ }
3417
+ }
2964
3418
  /**
2965
3419
  * Check if any skills path directory has been modified since last discovery.
2966
3420
  * Compares directory mtime to lastDiscoveryTime.
3421
+ * For glob patterns, checks the walk root and expanded directories.
2967
3422
  */
2968
3423
  async #isSkillsPathStale() {
2969
3424
  if (this.#lastDiscoveryTime === 0) {
2970
3425
  return true;
2971
3426
  }
3427
+ if (Date.now() - this.#lastDiscoveryTime < _WorkspaceSkillsImpl.STALENESS_CHECK_COOLDOWN) {
3428
+ return false;
3429
+ }
2972
3430
  for (const skillsPath of this.#resolvedPaths) {
2973
- try {
2974
- const stat3 = await this.#source.stat(skillsPath);
2975
- const mtime = stat3.modifiedAt.getTime();
2976
- if (mtime > this.#lastDiscoveryTime) {
2977
- return true;
3431
+ let pathsToCheck;
3432
+ if (isGlobPattern(skillsPath)) {
3433
+ const now = Date.now();
3434
+ const lastResolved = this.#globResolveTimes.get(skillsPath) ?? 0;
3435
+ if (now - lastResolved > _WorkspaceSkillsImpl.GLOB_RESOLVE_INTERVAL || !this.#globDirCache.has(skillsPath)) {
3436
+ const dirs = await this.#resolveGlobToDirectories(skillsPath);
3437
+ this.#globDirCache.set(skillsPath, dirs);
3438
+ this.#globResolveTimes.set(skillsPath, now);
2978
3439
  }
2979
- const entries = await this.#source.readdir(skillsPath);
2980
- for (const entry of entries) {
2981
- if (entry.type !== "directory") continue;
2982
- const entryPath = this.#joinPath(skillsPath, entry.name);
2983
- try {
2984
- const entryStat = await this.#source.stat(entryPath);
2985
- if (entryStat.modifiedAt.getTime() > this.#lastDiscoveryTime) {
2986
- return true;
3440
+ pathsToCheck = this.#globDirCache.get(skillsPath) ?? [];
3441
+ } else {
3442
+ pathsToCheck = [skillsPath];
3443
+ }
3444
+ for (const pathToCheck of pathsToCheck) {
3445
+ try {
3446
+ const stat3 = await this.#source.stat(pathToCheck);
3447
+ const mtime = stat3.modifiedAt.getTime();
3448
+ if (mtime > this.#lastDiscoveryTime) {
3449
+ return true;
3450
+ }
3451
+ if (stat3.type !== "directory") {
3452
+ continue;
3453
+ }
3454
+ const entries = await this.#source.readdir(pathToCheck);
3455
+ for (const entry of entries) {
3456
+ if (entry.type !== "directory") continue;
3457
+ const entryPath = this.#joinPath(pathToCheck, entry.name);
3458
+ try {
3459
+ const entryStat = await this.#source.stat(entryPath);
3460
+ if (entryStat.modifiedAt.getTime() > this.#lastDiscoveryTime) {
3461
+ return true;
3462
+ }
3463
+ } catch {
2987
3464
  }
2988
- } catch {
2989
3465
  }
3466
+ } catch {
3467
+ continue;
2990
3468
  }
2991
- } catch {
2992
- continue;
2993
3469
  }
2994
3470
  }
2995
3471
  return false;
@@ -3073,7 +3549,7 @@ ${validation.errors.join("\n")}`);
3073
3549
  const entries = await this.#source.readdir(dirPath);
3074
3550
  for (const entry of entries) {
3075
3551
  const entryPath = this.#joinPath(dirPath, entry.name);
3076
- if (entry.type === "directory") {
3552
+ if (entry.type === "directory" && !entry.isSymlink) {
3077
3553
  await this.#walkDirectory(basePath, entryPath, callback, depth + 1, maxDepth);
3078
3554
  } else {
3079
3555
  const relativePath = entryPath.substring(basePath.length + 1);
@@ -3097,6 +3573,30 @@ ${validation.errors.join("\n")}`);
3097
3573
  }
3098
3574
  return parts.join("\n\n");
3099
3575
  }
3576
+ /**
3577
+ * Remove a skill's entries from the search index.
3578
+ */
3579
+ async #removeSkillFromIndex(skill) {
3580
+ if (!this.#searchEngine?.remove) return;
3581
+ const ids = [`skill:${skill.name}:SKILL.md`, ...skill.references.map((r) => `skill:${skill.name}:${r}`)];
3582
+ for (const id of ids) {
3583
+ try {
3584
+ await this.#searchEngine.remove(id);
3585
+ } catch {
3586
+ }
3587
+ }
3588
+ }
3589
+ /**
3590
+ * Infer the ContentSource for a skill path by matching against resolved paths.
3591
+ */
3592
+ #inferSource(skillPath) {
3593
+ for (const rp of this.#resolvedPaths) {
3594
+ if (skillPath === rp || skillPath.startsWith(rp + "/")) {
3595
+ return this.#determineSource(rp);
3596
+ }
3597
+ }
3598
+ return this.#determineSource(skillPath);
3599
+ }
3100
3600
  /**
3101
3601
  * Index a skill for search
3102
3602
  */
@@ -3206,6 +3706,151 @@ ${validation.errors.join("\n")}`);
3206
3706
  return lastSlash > 0 ? path4.substring(0, lastSlash) : "/";
3207
3707
  }
3208
3708
  };
3709
+ function hashContent(content) {
3710
+ if (Buffer.isBuffer(content)) {
3711
+ return createHash("sha256").update(content).digest("hex");
3712
+ }
3713
+ return createHash("sha256").update(content, "utf-8").digest("hex");
3714
+ }
3715
+ function detectMimeType(filename) {
3716
+ const ext = filename.slice(filename.lastIndexOf(".")).toLowerCase();
3717
+ const mimeTypes = {
3718
+ ".md": "text/markdown",
3719
+ ".txt": "text/plain",
3720
+ ".json": "application/json",
3721
+ ".yaml": "text/yaml",
3722
+ ".yml": "text/yaml",
3723
+ ".sh": "text/x-shellscript",
3724
+ ".py": "text/x-python",
3725
+ ".js": "text/javascript",
3726
+ ".ts": "text/typescript",
3727
+ ".html": "text/html",
3728
+ ".css": "text/css",
3729
+ ".png": "image/png",
3730
+ ".jpg": "image/jpeg",
3731
+ ".jpeg": "image/jpeg",
3732
+ ".svg": "image/svg+xml"
3733
+ };
3734
+ return mimeTypes[ext];
3735
+ }
3736
+ function isBinaryMimeType(mimeType) {
3737
+ if (!mimeType) return false;
3738
+ if (mimeType.startsWith("text/")) return false;
3739
+ if (mimeType === "application/json") return false;
3740
+ if (mimeType === "image/svg+xml") return false;
3741
+ return true;
3742
+ }
3743
+ async function walkSkillDirectory(source, basePath, currentPath = basePath) {
3744
+ const entries = await source.readdir(currentPath);
3745
+ const files = [];
3746
+ for (const entry of entries) {
3747
+ const entryPath = joinPath(currentPath, entry.name);
3748
+ if (entry.type === "directory") {
3749
+ const subFiles = await walkSkillDirectory(source, basePath, entryPath);
3750
+ files.push(...subFiles);
3751
+ } else {
3752
+ const rawContent = await source.readFile(entryPath);
3753
+ const relativePath = entryPath.substring(basePath.length + 1);
3754
+ const mimeType = detectMimeType(entry.name);
3755
+ const isBinary = isBinaryMimeType(mimeType);
3756
+ if (isBinary) {
3757
+ const buf = Buffer.isBuffer(rawContent) ? rawContent : Buffer.from(rawContent, "utf-8");
3758
+ files.push({ path: relativePath, content: buf, isBinary: true });
3759
+ } else {
3760
+ const content = typeof rawContent === "string" ? rawContent : rawContent.toString("utf-8");
3761
+ files.push({ path: relativePath, content, isBinary: false });
3762
+ }
3763
+ }
3764
+ }
3765
+ return files;
3766
+ }
3767
+ function joinPath(...segments) {
3768
+ return segments.map((seg, i) => {
3769
+ if (i === 0) return seg.replace(/\/+$/, "");
3770
+ return seg.replace(/^\/+|\/+$/g, "");
3771
+ }).filter(Boolean).join("/");
3772
+ }
3773
+ function collectSubdirPaths(allPaths, subdir) {
3774
+ const prefix = subdir + "/";
3775
+ return allPaths.filter((p) => p.startsWith(prefix)).map((p) => p.substring(prefix.length));
3776
+ }
3777
+ async function collectSkillForPublish(source, skillPath) {
3778
+ const files = await walkSkillDirectory(source, skillPath);
3779
+ const treeEntries = {};
3780
+ const blobMap = /* @__PURE__ */ new Map();
3781
+ const now = /* @__PURE__ */ new Date();
3782
+ for (const file of files) {
3783
+ const hash = hashContent(file.content);
3784
+ const mimeType = detectMimeType(file.path);
3785
+ if (file.isBinary) {
3786
+ const buf = Buffer.isBuffer(file.content) ? file.content : Buffer.from(file.content);
3787
+ const size = buf.length;
3788
+ const base64Content = buf.toString("base64");
3789
+ treeEntries[file.path] = {
3790
+ blobHash: hash,
3791
+ size,
3792
+ mimeType,
3793
+ encoding: "base64"
3794
+ };
3795
+ if (!blobMap.has(hash)) {
3796
+ blobMap.set(hash, {
3797
+ hash,
3798
+ content: base64Content,
3799
+ size,
3800
+ mimeType,
3801
+ createdAt: now
3802
+ });
3803
+ }
3804
+ } else {
3805
+ const content = file.content;
3806
+ const size = Buffer.byteLength(content, "utf-8");
3807
+ treeEntries[file.path] = {
3808
+ blobHash: hash,
3809
+ size,
3810
+ mimeType
3811
+ };
3812
+ if (!blobMap.has(hash)) {
3813
+ blobMap.set(hash, {
3814
+ hash,
3815
+ content,
3816
+ size,
3817
+ mimeType,
3818
+ createdAt: now
3819
+ });
3820
+ }
3821
+ }
3822
+ }
3823
+ const tree = { entries: treeEntries };
3824
+ const blobs = Array.from(blobMap.values());
3825
+ const skillMdFile = files.find((f) => f.path === "SKILL.md");
3826
+ if (!skillMdFile) {
3827
+ throw new Error(`SKILL.md not found in ${skillPath}`);
3828
+ }
3829
+ const parsed = matter(skillMdFile.content);
3830
+ const frontmatter = parsed.data;
3831
+ const instructions = parsed.content.trim();
3832
+ const allPaths = files.map((f) => f.path);
3833
+ const references = collectSubdirPaths(allPaths, "references");
3834
+ const scripts = collectSubdirPaths(allPaths, "scripts");
3835
+ const assets = collectSubdirPaths(allPaths, "assets");
3836
+ const snapshot = {
3837
+ name: frontmatter.name,
3838
+ description: frontmatter.description,
3839
+ instructions,
3840
+ license: frontmatter.license,
3841
+ compatibility: frontmatter.compatibility,
3842
+ metadata: frontmatter.metadata,
3843
+ ...references.length > 0 ? { references } : {},
3844
+ ...scripts.length > 0 ? { scripts } : {},
3845
+ ...assets.length > 0 ? { assets } : {}
3846
+ };
3847
+ return { snapshot, tree, blobs };
3848
+ }
3849
+ async function publishSkillFromSource(source, skillPath, blobStore) {
3850
+ const result = await collectSkillForPublish(source, skillPath);
3851
+ await blobStore.putMany(result.blobs);
3852
+ return result;
3853
+ }
3209
3854
 
3210
3855
  // src/workspace/workspace.ts
3211
3856
  var Workspace = class {
@@ -3290,12 +3935,18 @@ var Workspace = class {
3290
3935
  }
3291
3936
  /**
3292
3937
  * The filesystem provider (if configured).
3938
+ *
3939
+ * Returns the concrete type you passed to the constructor.
3940
+ * When `mounts` is used instead of `filesystem`, returns `CompositeFilesystem`
3941
+ * parameterized with the concrete mount types.
3293
3942
  */
3294
3943
  get filesystem() {
3295
3944
  return this._fs;
3296
3945
  }
3297
3946
  /**
3298
3947
  * The sandbox provider (if configured).
3948
+ *
3949
+ * Returns the concrete type you passed to the constructor.
3299
3950
  */
3300
3951
  get sandbox() {
3301
3952
  return this._sandbox;
@@ -3325,7 +3976,7 @@ var Workspace = class {
3325
3976
  return void 0;
3326
3977
  }
3327
3978
  if (!this._skills) {
3328
- const source = this._fs ?? new LocalSkillSource();
3979
+ const source = this._config.skillSource ?? this._fs ?? new LocalSkillSource();
3329
3980
  this._skills = new WorkspaceSkillsImpl({
3330
3981
  source,
3331
3982
  skills: this._config.skills,
@@ -3403,31 +4054,51 @@ var Workspace = class {
3403
4054
  /**
3404
4055
  * Rebuild the search index from filesystem paths.
3405
4056
  * Used internally for auto-indexing on init.
4057
+ *
4058
+ * Paths can be plain directories (e.g., '/docs') or glob patterns
4059
+ * (e.g., '/docs/**\/*.md'). Glob patterns are resolved to a walk root
4060
+ * via extractGlobBase, then files are filtered by the pattern.
3406
4061
  */
3407
4062
  async rebuildSearchIndex(paths) {
3408
4063
  if (!this._searchEngine || !this._fs || paths.length === 0) {
3409
4064
  return;
3410
4065
  }
3411
4066
  this._searchEngine.clear();
3412
- for (const basePath of paths) {
4067
+ for (const pathOrGlob of paths) {
3413
4068
  try {
3414
- const files = await this.getAllFiles(basePath);
3415
- for (const filePath of files) {
3416
- try {
3417
- const content = await this._fs.readFile(filePath, { encoding: "utf-8" });
3418
- await this._searchEngine.index({
3419
- id: filePath,
3420
- content
3421
- });
3422
- } catch {
4069
+ if (isGlobPattern(pathOrGlob)) {
4070
+ const walkRoot = extractGlobBase(pathOrGlob);
4071
+ const matcher = createGlobMatcher(pathOrGlob);
4072
+ const files = await this.getAllFiles(walkRoot);
4073
+ for (const filePath of files) {
4074
+ if (!matcher(filePath)) continue;
4075
+ await this.indexFileForSearch(filePath);
4076
+ }
4077
+ } else {
4078
+ const files = await this.getAllFiles(pathOrGlob);
4079
+ for (const filePath of files) {
4080
+ await this.indexFileForSearch(filePath);
3423
4081
  }
3424
4082
  }
3425
4083
  } catch {
3426
4084
  }
3427
4085
  }
3428
4086
  }
3429
- async getAllFiles(dir) {
3430
- if (!this._fs) return [];
4087
+ /**
4088
+ * Index a single file for search. Skips files that can't be read as text.
4089
+ */
4090
+ async indexFileForSearch(filePath) {
4091
+ try {
4092
+ const content = await this._fs.readFile(filePath, { encoding: "utf-8" });
4093
+ await this._searchEngine.index({
4094
+ id: filePath,
4095
+ content
4096
+ });
4097
+ } catch {
4098
+ }
4099
+ }
4100
+ async getAllFiles(dir, depth = 0, maxDepth = 10) {
4101
+ if (!this._fs || depth >= maxDepth) return [];
3431
4102
  const files = [];
3432
4103
  const entries = await this._fs.readdir(dir);
3433
4104
  for (const entry of entries) {
@@ -3435,7 +4106,7 @@ var Workspace = class {
3435
4106
  if (entry.type === "file") {
3436
4107
  files.push(fullPath);
3437
4108
  } else if (entry.type === "directory" && !entry.isSymlink) {
3438
- files.push(...await this.getAllFiles(fullPath));
4109
+ files.push(...await this.getAllFiles(fullPath, depth + 1, maxDepth));
3439
4110
  }
3440
4111
  }
3441
4112
  return files;
@@ -3915,7 +4586,10 @@ var LocalSandbox = class extends MastraSandbox {
3915
4586
  * Status management is handled by the base class.
3916
4587
  */
3917
4588
  async start() {
3918
- this.logger.debug("Starting sandbox", { workingDirectory: this._workingDirectory, isolation: this._isolation });
4589
+ this.logger.debug("[LocalSandbox] Starting sandbox", {
4590
+ workingDirectory: this._workingDirectory,
4591
+ isolation: this._isolation
4592
+ });
3919
4593
  await fs2.mkdir(this.workingDirectory, { recursive: true });
3920
4594
  if (this._isolation === "seatbelt") {
3921
4595
  const userProvidedPath = this._nativeSandboxConfig.seatbeltProfilePath;
@@ -3941,14 +4615,14 @@ var LocalSandbox = class extends MastraSandbox {
3941
4615
  await fs2.writeFile(this._seatbeltProfilePath, this._seatbeltProfile, "utf-8");
3942
4616
  }
3943
4617
  }
3944
- this.logger.debug("Sandbox started", { workingDirectory: this._workingDirectory });
4618
+ this.logger.debug("[LocalSandbox] Sandbox started", { workingDirectory: this._workingDirectory });
3945
4619
  }
3946
4620
  /**
3947
4621
  * Stop the local sandbox.
3948
4622
  * Status management is handled by the base class.
3949
4623
  */
3950
4624
  async stop() {
3951
- this.logger.debug("Stopping sandbox", { workingDirectory: this._workingDirectory });
4625
+ this.logger.debug("[LocalSandbox] Stopping sandbox", { workingDirectory: this._workingDirectory });
3952
4626
  }
3953
4627
  /**
3954
4628
  * Destroy the local sandbox and clean up resources.
@@ -3956,7 +4630,7 @@ var LocalSandbox = class extends MastraSandbox {
3956
4630
  * Status management is handled by the base class.
3957
4631
  */
3958
4632
  async destroy() {
3959
- this.logger.debug("Destroying sandbox", { workingDirectory: this._workingDirectory });
4633
+ this.logger.debug("[LocalSandbox] Destroying sandbox", { workingDirectory: this._workingDirectory });
3960
4634
  if (this._seatbeltProfilePath && !this._userProvidedProfilePath) {
3961
4635
  try {
3962
4636
  await fs2.unlink(this._seatbeltProfilePath);
@@ -4022,7 +4696,7 @@ var LocalSandbox = class extends MastraSandbox {
4022
4696
  });
4023
4697
  }
4024
4698
  async executeCommand(command, args = [], options = {}) {
4025
- this.logger.debug("Executing command", { command, args, cwd: options.cwd ?? this.workingDirectory });
4699
+ this.logger.debug("[LocalSandbox] Executing command", { command, args, cwd: options.cwd ?? this.workingDirectory });
4026
4700
  await this.ensureRunning();
4027
4701
  const startTime = Date.now();
4028
4702
  const wrapped = this.wrapCommandForIsolation(command, args);
@@ -4041,7 +4715,7 @@ var LocalSandbox = class extends MastraSandbox {
4041
4715
  exitCode: result.exitCode,
4042
4716
  executionTimeMs: Date.now() - startTime
4043
4717
  };
4044
- this.logger.info("Command completed", {
4718
+ this.logger.debug("[LocalSandbox] Command completed", {
4045
4719
  command,
4046
4720
  exitCode: commandResult.exitCode,
4047
4721
  executionTimeMs: commandResult.executionTimeMs
@@ -4049,7 +4723,7 @@ var LocalSandbox = class extends MastraSandbox {
4049
4723
  return commandResult;
4050
4724
  } catch (error) {
4051
4725
  const executionTimeMs = Date.now() - startTime;
4052
- this.logger.error("Command failed", { command, error, executionTimeMs });
4726
+ this.logger.error("[LocalSandbox] Command failed", { command, error, executionTimeMs });
4053
4727
  return {
4054
4728
  success: false,
4055
4729
  stdout: "",
@@ -4071,7 +4745,8 @@ var WORKSPACE_TOOLS = {
4071
4745
  LIST_FILES: `${WORKSPACE_TOOLS_PREFIX}_list_files`,
4072
4746
  DELETE: `${WORKSPACE_TOOLS_PREFIX}_delete`,
4073
4747
  FILE_STAT: `${WORKSPACE_TOOLS_PREFIX}_file_stat`,
4074
- MKDIR: `${WORKSPACE_TOOLS_PREFIX}_mkdir`
4748
+ MKDIR: `${WORKSPACE_TOOLS_PREFIX}_mkdir`,
4749
+ GREP: `${WORKSPACE_TOOLS_PREFIX}_grep`
4075
4750
  },
4076
4751
  SANDBOX: {
4077
4752
  EXECUTE_COMMAND: `${WORKSPACE_TOOLS_PREFIX}_execute_command`
@@ -4082,6 +4757,357 @@ var WORKSPACE_TOOLS = {
4082
4757
  }
4083
4758
  };
4084
4759
 
4760
+ // src/workspace/tools/helpers.ts
4761
+ function requireWorkspace(context) {
4762
+ if (!context?.workspace) {
4763
+ throw new WorkspaceNotAvailableError();
4764
+ }
4765
+ return context.workspace;
4766
+ }
4767
+ function requireFilesystem(context) {
4768
+ const workspace = requireWorkspace(context);
4769
+ if (!workspace.filesystem) {
4770
+ throw new FilesystemNotAvailableError();
4771
+ }
4772
+ return { workspace, filesystem: workspace.filesystem };
4773
+ }
4774
+ function requireSandbox(context) {
4775
+ const workspace = requireWorkspace(context);
4776
+ if (!workspace.sandbox) {
4777
+ throw new SandboxNotAvailableError();
4778
+ }
4779
+ return { workspace, sandbox: workspace.sandbox };
4780
+ }
4781
+ async function emitWorkspaceMetadata(context, toolName) {
4782
+ const workspace = requireWorkspace(context);
4783
+ const info = await workspace.getInfo();
4784
+ const toolCallId = context?.agent?.toolCallId;
4785
+ await context?.writer?.custom({
4786
+ type: "data-workspace-metadata",
4787
+ data: { toolName, toolCallId, ...info }
4788
+ });
4789
+ }
4790
+
4791
+ // src/workspace/tools/delete-file.ts
4792
+ var deleteFileTool = createTool({
4793
+ id: WORKSPACE_TOOLS.FILESYSTEM.DELETE,
4794
+ description: "Delete a file or directory from the workspace filesystem",
4795
+ inputSchema: z.object({
4796
+ path: z.string().describe("The path to the file or directory to delete"),
4797
+ recursive: z.boolean().optional().default(false).describe("If true, delete directories and their contents recursively. Required for non-empty directories.")
4798
+ }),
4799
+ execute: async ({ path: path4, recursive }, context) => {
4800
+ const { filesystem } = requireFilesystem(context);
4801
+ await emitWorkspaceMetadata(context, WORKSPACE_TOOLS.FILESYSTEM.DELETE);
4802
+ if (filesystem.readOnly) {
4803
+ throw new WorkspaceReadOnlyError("delete");
4804
+ }
4805
+ const stat3 = await filesystem.stat(path4);
4806
+ if (stat3.type === "directory") {
4807
+ await filesystem.rmdir(path4, { recursive, force: recursive });
4808
+ } else {
4809
+ await filesystem.deleteFile(path4);
4810
+ }
4811
+ return `Deleted ${path4}`;
4812
+ }
4813
+ });
4814
+ var editFileTool = createTool({
4815
+ id: WORKSPACE_TOOLS.FILESYSTEM.EDIT_FILE,
4816
+ description: `Edit a file by replacing specific text. The old_string must match exactly and be unique in the file.
4817
+
4818
+ Usage:
4819
+ - Read the file first to get the exact text to replace.
4820
+ - By default, ${WORKSPACE_TOOLS.FILESYSTEM.READ_FILE} output includes line number prefixes (e.g., " 1\u2192"). Ensure you preserve the exact indentation as it appears AFTER the arrow. Never include any part of the line number prefix in old_string or new_string.
4821
+ - Include enough surrounding context (multiple lines) to make old_string unique. If it still isn't unique, include more lines.
4822
+ - Use replace_all only when intentionally replacing all occurrences.`,
4823
+ inputSchema: z.object({
4824
+ path: z.string().describe("The path to the file to edit"),
4825
+ old_string: z.string().describe("The exact text to find and replace. Must be unique in the file."),
4826
+ new_string: z.string().describe("The text to replace old_string with"),
4827
+ replace_all: z.boolean().optional().default(false).describe("If true, replace all occurrences. If false (default), old_string must be unique.")
4828
+ }),
4829
+ execute: async ({ path: path4, old_string, new_string, replace_all }, context) => {
4830
+ const { filesystem } = requireFilesystem(context);
4831
+ await emitWorkspaceMetadata(context, WORKSPACE_TOOLS.FILESYSTEM.EDIT_FILE);
4832
+ if (filesystem.readOnly) {
4833
+ throw new WorkspaceReadOnlyError("edit_file");
4834
+ }
4835
+ try {
4836
+ const content = await filesystem.readFile(path4, { encoding: "utf-8" });
4837
+ if (typeof content !== "string") {
4838
+ return `Cannot edit binary files. Use ${WORKSPACE_TOOLS.FILESYSTEM.WRITE_FILE} instead.`;
4839
+ }
4840
+ const result = replaceString(content, old_string, new_string, replace_all);
4841
+ await filesystem.writeFile(path4, result.content, { overwrite: true });
4842
+ return `Replaced ${result.replacements} occurrence${result.replacements !== 1 ? "s" : ""} in ${path4}`;
4843
+ } catch (error) {
4844
+ if (error instanceof StringNotFoundError) {
4845
+ return error.message;
4846
+ }
4847
+ if (error instanceof StringNotUniqueError) {
4848
+ return error.message;
4849
+ }
4850
+ throw error;
4851
+ }
4852
+ }
4853
+ });
4854
+ var executeCommandTool = createTool({
4855
+ id: WORKSPACE_TOOLS.SANDBOX.EXECUTE_COMMAND,
4856
+ description: `Execute a shell command in the workspace sandbox.
4857
+
4858
+ Usage:
4859
+ - Verify parent directories exist before running commands that create files or directories.
4860
+ - Always quote file paths that contain spaces (e.g., cd "/path/with spaces").
4861
+ - Use the timeout parameter to limit execution time. Behavior when omitted depends on the sandbox provider.
4862
+ - Optionally use cwd to override the working directory. Commands run from the sandbox default if omitted.`,
4863
+ inputSchema: z.object({
4864
+ command: z.string().describe('The command to execute (e.g., "ls", "npm", "python")'),
4865
+ args: z.array(z.string()).nullish().default([]).describe("Arguments to pass to the command"),
4866
+ timeout: z.number().nullish().describe("Maximum execution time in milliseconds. Example: 60000 for 1 minute."),
4867
+ cwd: z.string().nullish().describe("Working directory for the command")
4868
+ }),
4869
+ execute: async ({ command, args, timeout, cwd }, context) => {
4870
+ const { sandbox } = requireSandbox(context);
4871
+ if (!sandbox.executeCommand) {
4872
+ throw new SandboxFeatureNotSupportedError("executeCommand");
4873
+ }
4874
+ await emitWorkspaceMetadata(context, WORKSPACE_TOOLS.SANDBOX.EXECUTE_COMMAND);
4875
+ const toolCallId = context?.agent?.toolCallId;
4876
+ const startedAt = Date.now();
4877
+ let stdout = "";
4878
+ let stderr = "";
4879
+ try {
4880
+ const result = await sandbox.executeCommand(command, args ?? [], {
4881
+ timeout: timeout ?? void 0,
4882
+ cwd: cwd ?? void 0,
4883
+ onStdout: async (data) => {
4884
+ stdout += data;
4885
+ await context?.writer?.custom({
4886
+ type: "data-sandbox-stdout",
4887
+ data: { output: data, timestamp: Date.now(), toolCallId }
4888
+ });
4889
+ },
4890
+ onStderr: async (data) => {
4891
+ stderr += data;
4892
+ await context?.writer?.custom({
4893
+ type: "data-sandbox-stderr",
4894
+ data: { output: data, timestamp: Date.now(), toolCallId }
4895
+ });
4896
+ }
4897
+ });
4898
+ await context?.writer?.custom({
4899
+ type: "data-sandbox-exit",
4900
+ data: {
4901
+ exitCode: result.exitCode,
4902
+ success: result.success,
4903
+ executionTimeMs: result.executionTimeMs,
4904
+ toolCallId
4905
+ }
4906
+ });
4907
+ if (!result.success) {
4908
+ const parts = [result.stdout, result.stderr].filter(Boolean);
4909
+ parts.push(`Exit code: ${result.exitCode}`);
4910
+ return parts.join("\n");
4911
+ }
4912
+ return result.stdout || "(no output)";
4913
+ } catch (error) {
4914
+ await context?.writer?.custom({
4915
+ type: "data-sandbox-exit",
4916
+ data: {
4917
+ exitCode: -1,
4918
+ success: false,
4919
+ executionTimeMs: Date.now() - startedAt,
4920
+ toolCallId
4921
+ }
4922
+ });
4923
+ const parts = [stdout, stderr].filter(Boolean);
4924
+ const errorMessage = error instanceof Error ? error.message : String(error);
4925
+ parts.push(`Error: ${errorMessage}`);
4926
+ return parts.join("\n");
4927
+ }
4928
+ }
4929
+ });
4930
+ var fileStatTool = createTool({
4931
+ id: WORKSPACE_TOOLS.FILESYSTEM.FILE_STAT,
4932
+ description: "Get file or directory metadata from the workspace. Returns existence, type, size, and modification time.",
4933
+ inputSchema: z.object({
4934
+ path: z.string().describe("The path to check")
4935
+ }),
4936
+ execute: async ({ path: path4 }, context) => {
4937
+ const { filesystem } = requireFilesystem(context);
4938
+ await emitWorkspaceMetadata(context, WORKSPACE_TOOLS.FILESYSTEM.FILE_STAT);
4939
+ try {
4940
+ const stat3 = await filesystem.stat(path4);
4941
+ const modifiedAt = stat3.modifiedAt.toISOString();
4942
+ const parts = [`${path4}`, `Type: ${stat3.type}`];
4943
+ if (stat3.size !== void 0) parts.push(`Size: ${stat3.size} bytes`);
4944
+ parts.push(`Modified: ${modifiedAt}`);
4945
+ return parts.join(" ");
4946
+ } catch (error) {
4947
+ if (error instanceof FileNotFoundError) {
4948
+ return `${path4}: not found`;
4949
+ }
4950
+ throw error;
4951
+ }
4952
+ }
4953
+ });
4954
+ var grepTool = createTool({
4955
+ id: WORKSPACE_TOOLS.FILESYSTEM.GREP,
4956
+ description: `Search file contents using a regex pattern. Walks the filesystem and returns matching lines with file paths and line numbers.
4957
+
4958
+ Usage:
4959
+ - Basic search: { pattern: "TODO" }
4960
+ - Regex: { pattern: "function\\s+\\w+\\(" }
4961
+ - Multiple terms: { pattern: "TODO|FIXME|HACK" }
4962
+ - Case-insensitive: { pattern: "error", caseSensitive: false }
4963
+ - Search in directory: { pattern: "import", path: "./src" }
4964
+ - Filter by glob: { pattern: "import", path: "**/*.ts" }
4965
+ - Combined path + glob: { pattern: "import", path: "src/**/*.ts" }
4966
+ - Multiple file types: { pattern: "import", path: "**/*.{ts,tsx,js}" }
4967
+ - Multiple directories: { pattern: "TODO", path: "{src,lib}/**/*.ts" }
4968
+ - With context: { pattern: "function", contextLines: 2 }`,
4969
+ inputSchema: z.object({
4970
+ pattern: z.string().describe("Regex pattern to search for"),
4971
+ path: z.string().optional().default("./").describe(
4972
+ 'File, directory, or glob pattern to search within (default: "./"). A plain path searches that file or directory. A glob pattern (e.g., "**/*.ts", "src/**/*.test.ts") filters which files to search.'
4973
+ ),
4974
+ contextLines: z.number().optional().default(0).describe("Number of lines of context to include before and after each match (default: 0)"),
4975
+ maxCount: z.number().optional().describe(
4976
+ "Maximum matches per file. Moves on to the next file after this many matches. Similar to grep -m flag."
4977
+ ),
4978
+ caseSensitive: z.boolean().optional().default(true).describe("Whether the search is case-sensitive (default: true)"),
4979
+ includeHidden: z.boolean().optional().default(false).describe('Include hidden files and directories (names starting with ".") in the search (default: false)')
4980
+ }),
4981
+ execute: async ({ pattern, path: inputPath = "./", contextLines = 0, maxCount, caseSensitive = true, includeHidden = false }, context) => {
4982
+ const { filesystem } = requireFilesystem(context);
4983
+ await emitWorkspaceMetadata(context, WORKSPACE_TOOLS.FILESYSTEM.GREP);
4984
+ const MAX_PATTERN_LENGTH = 1e3;
4985
+ if (pattern.length > MAX_PATTERN_LENGTH) {
4986
+ return `Error: Pattern too long (${pattern.length} chars, max ${MAX_PATTERN_LENGTH}). Use a shorter pattern.`;
4987
+ }
4988
+ let regex;
4989
+ try {
4990
+ regex = new RegExp(pattern, caseSensitive ? "g" : "gi");
4991
+ } catch (e) {
4992
+ return `Error: Invalid regex pattern: ${e.message}`;
4993
+ }
4994
+ let searchPath;
4995
+ let globMatcher;
4996
+ if (isGlobPattern(inputPath)) {
4997
+ searchPath = extractGlobBase(inputPath);
4998
+ globMatcher = createGlobMatcher(inputPath, { dot: includeHidden });
4999
+ } else {
5000
+ searchPath = inputPath;
5001
+ }
5002
+ let filePaths;
5003
+ try {
5004
+ const stat3 = await filesystem.stat(searchPath);
5005
+ if (stat3.type === "file") {
5006
+ filePaths = isTextFile(searchPath) ? [searchPath] : [];
5007
+ } else {
5008
+ const collectFiles = async (dir) => {
5009
+ const files = [];
5010
+ let entries;
5011
+ try {
5012
+ entries = await filesystem.readdir(dir);
5013
+ } catch {
5014
+ return files;
5015
+ }
5016
+ for (const entry of entries) {
5017
+ if (!includeHidden && entry.name.startsWith(".")) continue;
5018
+ const fullPath = dir.endsWith("/") ? `${dir}${entry.name}` : `${dir}/${entry.name}`;
5019
+ if (entry.type === "file") {
5020
+ if (!isTextFile(entry.name)) continue;
5021
+ if (globMatcher && !globMatcher(fullPath)) continue;
5022
+ files.push(fullPath);
5023
+ } else if (entry.type === "directory" && !entry.isSymlink) {
5024
+ files.push(...await collectFiles(fullPath));
5025
+ }
5026
+ }
5027
+ return files;
5028
+ };
5029
+ filePaths = await collectFiles(searchPath);
5030
+ }
5031
+ } catch {
5032
+ filePaths = [];
5033
+ }
5034
+ const outputLines = [];
5035
+ const filesWithMatches = /* @__PURE__ */ new Set();
5036
+ let totalMatchCount = 0;
5037
+ let truncated = false;
5038
+ const MAX_LINE_LENGTH = 500;
5039
+ const GLOBAL_CAP = 1e3;
5040
+ for (const filePath of filePaths) {
5041
+ if (truncated) break;
5042
+ let content;
5043
+ try {
5044
+ const raw = await filesystem.readFile(filePath, { encoding: "utf-8" });
5045
+ if (typeof raw !== "string") continue;
5046
+ content = raw;
5047
+ } catch {
5048
+ continue;
5049
+ }
5050
+ const lines = content.split("\n");
5051
+ let fileMatchCount = 0;
5052
+ for (let i = 0; i < lines.length; i++) {
5053
+ const currentLine = lines[i];
5054
+ regex.lastIndex = 0;
5055
+ const lineMatch = regex.exec(currentLine);
5056
+ if (!lineMatch) continue;
5057
+ filesWithMatches.add(filePath);
5058
+ let lineContent = currentLine;
5059
+ if (lineContent.length > MAX_LINE_LENGTH) {
5060
+ lineContent = lineContent.slice(0, MAX_LINE_LENGTH) + "...";
5061
+ }
5062
+ if (contextLines > 0) {
5063
+ const beforeStart = Math.max(0, i - contextLines);
5064
+ for (let b = beforeStart; b < i; b++) {
5065
+ outputLines.push(`${filePath}:${b + 1}- ${lines[b]}`);
5066
+ }
5067
+ }
5068
+ outputLines.push(`${filePath}:${i + 1}:${lineMatch.index + 1}: ${lineContent}`);
5069
+ if (contextLines > 0) {
5070
+ const afterEnd = Math.min(lines.length - 1, i + contextLines);
5071
+ for (let a = i + 1; a <= afterEnd; a++) {
5072
+ outputLines.push(`${filePath}:${a + 1}- ${lines[a]}`);
5073
+ }
5074
+ outputLines.push("--");
5075
+ }
5076
+ totalMatchCount++;
5077
+ fileMatchCount++;
5078
+ if (maxCount !== void 0 && fileMatchCount >= maxCount) break;
5079
+ if (totalMatchCount >= GLOBAL_CAP) {
5080
+ truncated = true;
5081
+ break;
5082
+ }
5083
+ }
5084
+ }
5085
+ outputLines.push("---");
5086
+ const parts = [`${totalMatchCount} match${totalMatchCount !== 1 ? "es" : ""}`];
5087
+ parts.push(`across ${filesWithMatches.size} file${filesWithMatches.size !== 1 ? "s" : ""}`);
5088
+ if (truncated) {
5089
+ parts.push(`(truncated at ${GLOBAL_CAP})`);
5090
+ }
5091
+ outputLines.push(parts.join(" "));
5092
+ return outputLines.join("\n");
5093
+ }
5094
+ });
5095
+ var indexContentTool = createTool({
5096
+ id: WORKSPACE_TOOLS.SEARCH.INDEX,
5097
+ description: "Index content for search. The path becomes the document ID in search results.",
5098
+ inputSchema: z.object({
5099
+ path: z.string().describe("The document ID/path for search results"),
5100
+ content: z.string().describe("The text content to index"),
5101
+ metadata: z.record(z.unknown()).optional().describe("Optional metadata to store with the document")
5102
+ }),
5103
+ execute: async ({ path: path4, content, metadata }, context) => {
5104
+ const workspace = requireWorkspace(context);
5105
+ await emitWorkspaceMetadata(context, WORKSPACE_TOOLS.SEARCH.INDEX);
5106
+ await workspace.index(path4, content, { metadata });
5107
+ return `Indexed ${path4}`;
5108
+ }
5109
+ });
5110
+
4085
5111
  // src/workspace/tools/tree-formatter.ts
4086
5112
  var BRANCH = "\u251C\u2500\u2500 ";
4087
5113
  var LAST_BRANCH = "\u2514\u2500\u2500 ";
@@ -4093,6 +5119,12 @@ async function formatAsTree(fs5, path4, options) {
4093
5119
  const dirsOnly = options?.dirsOnly ?? false;
4094
5120
  const exclude = options?.exclude;
4095
5121
  const extension = options?.extension;
5122
+ const pattern = options?.pattern;
5123
+ let globMatcher;
5124
+ if (pattern) {
5125
+ const patterns = Array.isArray(pattern) ? pattern : [pattern];
5126
+ globMatcher = createGlobMatcher(patterns, { dot: showHidden });
5127
+ }
4096
5128
  const lines = ["."];
4097
5129
  let dirCount = 0;
4098
5130
  let fileCount = 0;
@@ -4118,7 +5150,7 @@ async function formatAsTree(fs5, path4, options) {
4118
5150
  if (exclude) {
4119
5151
  const patterns = Array.isArray(exclude) ? exclude : [exclude];
4120
5152
  filtered = filtered.filter((e) => {
4121
- return !patterns.some((pattern) => e.name.includes(pattern));
5153
+ return !patterns.some((pattern2) => e.name.includes(pattern2));
4122
5154
  });
4123
5155
  }
4124
5156
  if (dirsOnly) {
@@ -4134,6 +5166,20 @@ async function formatAsTree(fs5, path4, options) {
4134
5166
  });
4135
5167
  });
4136
5168
  }
5169
+ if (globMatcher && !dirsOnly) {
5170
+ filtered = filtered.filter((e) => {
5171
+ if (e.type === "directory") return true;
5172
+ const entryPath = currentPath === path4 ? e.name : `${currentPath === "/" ? "" : currentPath}/${e.name}`;
5173
+ let relativePath;
5174
+ if (path4 === "/" || path4 === "") {
5175
+ relativePath = entryPath.startsWith("/") ? entryPath.slice(1) : entryPath;
5176
+ } else {
5177
+ relativePath = entryPath.startsWith(path4 + "/") ? entryPath.slice(path4.length + 1) : entryPath;
5178
+ if (!relativePath) relativePath = entryPath;
5179
+ }
5180
+ return globMatcher(relativePath);
5181
+ });
5182
+ }
4137
5183
  filtered.sort((a, b) => {
4138
5184
  if (a.type === "directory" && b.type !== "directory") return -1;
4139
5185
  if (a.type !== "directory" && b.type === "directory") return 1;
@@ -4149,7 +5195,7 @@ async function formatAsTree(fs5, path4, options) {
4149
5195
  if (entry.type === "directory") {
4150
5196
  dirCount++;
4151
5197
  if (!entry.isSymlink) {
4152
- const childPath = joinPath(currentPath, entry.name);
5198
+ const childPath = joinPath2(currentPath, entry.name);
4153
5199
  await buildTree(childPath, childPrefix, depth + 1);
4154
5200
  }
4155
5201
  } else {
@@ -4172,13 +5218,159 @@ async function formatAsTree(fs5, path4, options) {
4172
5218
  truncated
4173
5219
  };
4174
5220
  }
4175
- function joinPath(base, name) {
5221
+ function joinPath2(base, name) {
4176
5222
  if (base === "/" || base === "") {
4177
5223
  return `/${name}`;
4178
5224
  }
4179
5225
  return `${base}/${name}`;
4180
5226
  }
4181
5227
 
5228
+ // src/workspace/tools/list-files.ts
5229
+ var listFilesTool = createTool({
5230
+ id: WORKSPACE_TOOLS.FILESYSTEM.LIST_FILES,
5231
+ description: `List files and directories in the workspace filesystem.
5232
+ Returns a tree-style view (like the Unix "tree" command) for easy visualization.
5233
+ The output is displayed to the user as a tree-like structure in the tool result.
5234
+ Options mirror common tree command flags for familiarity.
5235
+
5236
+ Examples:
5237
+ - List root: { path: "./" }
5238
+ - Deep listing: { path: "./src", maxDepth: 5 }
5239
+ - Directories only: { path: "./", dirsOnly: true }
5240
+ - Exclude node_modules: { path: "./", exclude: "node_modules" }
5241
+ - Find TypeScript files: { path: "./src", pattern: "**/*.ts" }
5242
+ - Find config files: { path: "./", pattern: "*.config.{js,ts}" }
5243
+ - Multiple patterns: { path: "./", pattern: ["**/*.ts", "**/*.tsx"] }`,
5244
+ inputSchema: z.object({
5245
+ path: z.string().default("./").describe("Directory path to list"),
5246
+ maxDepth: z.number().optional().default(3).describe("Maximum depth to descend (default: 3). Similar to tree -L flag."),
5247
+ showHidden: z.boolean().optional().default(false).describe('Show hidden files starting with "." (default: false). Similar to tree -a flag.'),
5248
+ dirsOnly: z.boolean().optional().default(false).describe("List directories only, no files (default: false). Similar to tree -d flag."),
5249
+ exclude: z.string().optional().describe('Pattern to exclude (e.g., "node_modules"). Similar to tree -I flag.'),
5250
+ extension: z.string().optional().describe('Filter by file extension (e.g., ".ts"). Similar to tree -P flag.'),
5251
+ pattern: z.union([z.string(), z.array(z.string())]).optional().describe(
5252
+ 'Glob pattern(s) to filter files. Examples: "**/*.ts", "src/**/*.test.ts", "*.config.{js,ts}". Directories always pass through.'
5253
+ )
5254
+ }),
5255
+ execute: async ({ path: path4 = "./", maxDepth = 3, showHidden, dirsOnly, exclude, extension, pattern }, context) => {
5256
+ const { filesystem } = requireFilesystem(context);
5257
+ await emitWorkspaceMetadata(context, WORKSPACE_TOOLS.FILESYSTEM.LIST_FILES);
5258
+ const result = await formatAsTree(filesystem, path4, {
5259
+ maxDepth,
5260
+ showHidden,
5261
+ dirsOnly,
5262
+ exclude: exclude || void 0,
5263
+ extension: extension || void 0,
5264
+ pattern: pattern || void 0
5265
+ });
5266
+ return `${result.tree}
5267
+
5268
+ ${result.summary}`;
5269
+ }
5270
+ });
5271
+ var mkdirTool = createTool({
5272
+ id: WORKSPACE_TOOLS.FILESYSTEM.MKDIR,
5273
+ description: "Create a directory in the workspace filesystem",
5274
+ inputSchema: z.object({
5275
+ path: z.string().describe("The path of the directory to create"),
5276
+ recursive: z.boolean().optional().default(true).describe("Whether to create parent directories if they do not exist")
5277
+ }),
5278
+ execute: async ({ path: path4, recursive }, context) => {
5279
+ const { filesystem } = requireFilesystem(context);
5280
+ await emitWorkspaceMetadata(context, WORKSPACE_TOOLS.FILESYSTEM.MKDIR);
5281
+ if (filesystem.readOnly) {
5282
+ throw new WorkspaceReadOnlyError("mkdir");
5283
+ }
5284
+ await filesystem.mkdir(path4, { recursive });
5285
+ return `Created directory ${path4}`;
5286
+ }
5287
+ });
5288
+ var readFileTool = createTool({
5289
+ id: WORKSPACE_TOOLS.FILESYSTEM.READ_FILE,
5290
+ description: "Read the contents of a file from the workspace filesystem. Use offset/limit parameters to read specific line ranges for large files.",
5291
+ inputSchema: z.object({
5292
+ path: z.string().describe('The path to the file to read (e.g., "/data/config.json")'),
5293
+ encoding: z.enum(["utf-8", "utf8", "base64", "hex", "binary"]).optional().describe("The encoding to use when reading the file. Defaults to utf-8 for text files."),
5294
+ offset: z.number().optional().describe("Line number to start reading from (1-indexed). If omitted, starts from line 1."),
5295
+ limit: z.number().optional().describe("Maximum number of lines to read. If omitted, reads to the end of the file."),
5296
+ showLineNumbers: z.boolean().optional().default(true).describe("Whether to prefix each line with its line number (default: true)")
5297
+ }),
5298
+ execute: async ({ path: path4, encoding, offset, limit, showLineNumbers }, context) => {
5299
+ const { filesystem } = requireFilesystem(context);
5300
+ await emitWorkspaceMetadata(context, WORKSPACE_TOOLS.FILESYSTEM.READ_FILE);
5301
+ const effectiveEncoding = encoding ?? "utf-8";
5302
+ const fullContent = await filesystem.readFile(path4, { encoding: effectiveEncoding });
5303
+ const stat3 = await filesystem.stat(path4);
5304
+ const isTextEncoding = !encoding || encoding === "utf-8" || encoding === "utf8";
5305
+ if (!isTextEncoding) {
5306
+ return `${stat3.path} (${stat3.size} bytes, ${effectiveEncoding})
5307
+ ${fullContent}`;
5308
+ }
5309
+ if (typeof fullContent !== "string") {
5310
+ return `${stat3.path} (${stat3.size} bytes, base64)
5311
+ ${fullContent.toString("base64")}`;
5312
+ }
5313
+ const hasLineRange = offset !== void 0 || limit !== void 0;
5314
+ const result = extractLinesWithLimit(fullContent, offset, limit);
5315
+ const shouldShowLineNumbers = showLineNumbers !== false;
5316
+ const formattedContent = shouldShowLineNumbers ? formatWithLineNumbers(result.content, result.lines.start) : result.content;
5317
+ let header;
5318
+ if (hasLineRange) {
5319
+ header = `${stat3.path} (lines ${result.lines.start}-${result.lines.end} of ${result.totalLines}, ${stat3.size} bytes)`;
5320
+ } else {
5321
+ header = `${stat3.path} (${stat3.size} bytes)`;
5322
+ }
5323
+ return `${header}
5324
+ ${formattedContent}`;
5325
+ }
5326
+ });
5327
+ var searchTool = createTool({
5328
+ id: WORKSPACE_TOOLS.SEARCH.SEARCH,
5329
+ description: "Search indexed content in the workspace. Supports keyword (BM25), semantic (vector), and hybrid search modes.",
5330
+ inputSchema: z.object({
5331
+ query: z.string().describe("The search query string"),
5332
+ topK: z.number().optional().default(5).describe("Maximum number of results to return"),
5333
+ mode: z.enum(["bm25", "vector", "hybrid"]).optional().describe("Search mode: bm25 for keyword search, vector for semantic search, hybrid for both combined"),
5334
+ minScore: z.number().optional().describe("Minimum score threshold (0-1 for normalized scores)")
5335
+ }),
5336
+ execute: async ({ query, topK, mode, minScore }, context) => {
5337
+ const workspace = requireWorkspace(context);
5338
+ await emitWorkspaceMetadata(context, WORKSPACE_TOOLS.SEARCH.SEARCH);
5339
+ const results = await workspace.search(query, {
5340
+ topK,
5341
+ mode,
5342
+ minScore
5343
+ });
5344
+ const effectiveMode = mode ?? (workspace.canHybrid ? "hybrid" : workspace.canVector ? "vector" : "bm25");
5345
+ const lines = results.map((r) => {
5346
+ const lineInfo = r.lineRange ? `:${r.lineRange.start}-${r.lineRange.end}` : "";
5347
+ return `${r.id}${lineInfo}: ${r.content}`;
5348
+ });
5349
+ lines.push("---");
5350
+ lines.push(`${results.length} result${results.length !== 1 ? "s" : ""} (${effectiveMode} search)`);
5351
+ return lines.join("\n");
5352
+ }
5353
+ });
5354
+ var writeFileTool = createTool({
5355
+ id: WORKSPACE_TOOLS.FILESYSTEM.WRITE_FILE,
5356
+ description: "Write content to a file in the workspace filesystem. Creates parent directories if needed.",
5357
+ inputSchema: z.object({
5358
+ path: z.string().describe('The path where to write the file (e.g., "/data/output.txt")'),
5359
+ content: z.string().describe("The content to write to the file"),
5360
+ overwrite: z.boolean().optional().default(true).describe("Whether to overwrite the file if it already exists")
5361
+ }),
5362
+ execute: async ({ path: path4, content, overwrite }, context) => {
5363
+ const { filesystem } = requireFilesystem(context);
5364
+ await emitWorkspaceMetadata(context, WORKSPACE_TOOLS.FILESYSTEM.WRITE_FILE);
5365
+ if (filesystem.readOnly) {
5366
+ throw new WorkspaceReadOnlyError("write_file");
5367
+ }
5368
+ await filesystem.writeFile(path4, content, { overwrite });
5369
+ const size = Buffer.byteLength(content, "utf-8");
5370
+ return `Wrote ${size} bytes to ${path4}`;
5371
+ }
5372
+ });
5373
+
4182
5374
  // src/workspace/tools/tools.ts
4183
5375
  function resolveToolConfig(toolsConfig, toolName) {
4184
5376
  let enabled = true;
@@ -4206,6 +5398,49 @@ function resolveToolConfig(toolsConfig, toolName) {
4206
5398
  }
4207
5399
  return { enabled, requireApproval, requireReadBeforeWrite };
4208
5400
  }
5401
+ function wrapTool(tool, workspace, config) {
5402
+ return {
5403
+ ...tool,
5404
+ requireApproval: config.requireApproval,
5405
+ execute: async (input, context = {}) => {
5406
+ const enrichedContext = { ...context, workspace: context?.workspace ?? workspace };
5407
+ return tool.execute(input, enrichedContext);
5408
+ }
5409
+ };
5410
+ }
5411
+ function wrapWithReadTracker(tool, workspace, readTracker, config, mode) {
5412
+ return {
5413
+ ...tool,
5414
+ requireApproval: config.requireApproval,
5415
+ execute: async (input, context = {}) => {
5416
+ const enrichedContext = { ...context, workspace: context?.workspace ?? workspace };
5417
+ if (mode === "write" && config.requireReadBeforeWrite) {
5418
+ try {
5419
+ const stat3 = await workspace.filesystem.stat(input.path);
5420
+ const check = readTracker.needsReRead(input.path, stat3.modifiedAt);
5421
+ if (check.needsReRead) {
5422
+ throw new FileReadRequiredError(input.path, check.reason);
5423
+ }
5424
+ } catch (error) {
5425
+ if (!(error instanceof FileNotFoundError)) {
5426
+ throw error;
5427
+ }
5428
+ }
5429
+ }
5430
+ const result = await tool.execute(input, enrichedContext);
5431
+ if (mode === "read") {
5432
+ try {
5433
+ const stat3 = await workspace.filesystem.stat(input.path);
5434
+ readTracker.recordRead(input.path, stat3.modifiedAt);
5435
+ } catch {
5436
+ }
5437
+ } else if (mode === "write") {
5438
+ readTracker.clearReadRecord(input.path);
5439
+ }
5440
+ return result;
5441
+ }
5442
+ };
5443
+ }
4209
5444
  function createWorkspaceTools(workspace) {
4210
5445
  const tools = {};
4211
5446
  const toolsConfig = workspace.getToolsConfig();
@@ -4216,498 +5451,53 @@ function createWorkspaceTools(workspace) {
4216
5451
  if (writeFileConfig.requireReadBeforeWrite || editFileConfig.requireReadBeforeWrite) {
4217
5452
  readTracker = new InMemoryFileReadTracker();
4218
5453
  }
4219
- if (workspace.filesystem) {
4220
- const readFileConfig = resolveToolConfig(toolsConfig, WORKSPACE_TOOLS.FILESYSTEM.READ_FILE);
4221
- if (readFileConfig.enabled) {
4222
- tools[WORKSPACE_TOOLS.FILESYSTEM.READ_FILE] = createTool({
4223
- id: WORKSPACE_TOOLS.FILESYSTEM.READ_FILE,
4224
- description: "Read the contents of a file from the workspace filesystem. Use offset/limit parameters to read specific line ranges for large files.",
4225
- requireApproval: readFileConfig.requireApproval,
4226
- inputSchema: z.object({
4227
- path: z.string().describe('The path to the file to read (e.g., "/data/config.json")'),
4228
- encoding: z.enum(["utf-8", "utf8", "base64", "hex", "binary"]).optional().describe("The encoding to use when reading the file. Defaults to utf-8 for text files."),
4229
- offset: z.number().optional().describe("Line number to start reading from (1-indexed). If omitted, starts from line 1."),
4230
- limit: z.number().optional().describe("Maximum number of lines to read. If omitted, reads to the end of the file."),
4231
- showLineNumbers: z.boolean().optional().default(true).describe("Whether to prefix each line with its line number (default: true)")
4232
- }),
4233
- outputSchema: z.object({
4234
- content: z.string().describe("The file contents (with optional line number prefixes)"),
4235
- size: z.number().describe("The file size in bytes"),
4236
- path: z.string().describe("The full path to the file"),
4237
- lines: z.object({
4238
- start: z.number().describe("First line number returned"),
4239
- end: z.number().describe("Last line number returned")
4240
- }).optional().describe("Line range information (when offset/limit used)"),
4241
- totalLines: z.number().optional().describe("Total number of lines in the file")
4242
- }),
4243
- execute: async ({ path: path4, encoding, offset, limit, showLineNumbers }) => {
4244
- const effectiveEncoding = encoding ?? "utf-8";
4245
- const fullContent = await workspace.filesystem.readFile(path4, {
4246
- encoding: effectiveEncoding
4247
- });
4248
- const stat3 = await workspace.filesystem.stat(path4);
4249
- if (readTracker) {
4250
- readTracker.recordRead(path4, stat3.modifiedAt);
4251
- }
4252
- const isTextEncoding = !encoding || encoding === "utf-8" || encoding === "utf8";
4253
- if (!isTextEncoding) {
4254
- return {
4255
- content: fullContent,
4256
- size: stat3.size,
4257
- path: stat3.path
4258
- };
4259
- }
4260
- if (typeof fullContent !== "string") {
4261
- return {
4262
- content: fullContent.toString("base64"),
4263
- size: stat3.size,
4264
- path: stat3.path
4265
- };
4266
- }
4267
- const hasLineRange = offset !== void 0 || limit !== void 0;
4268
- const result = extractLinesWithLimit(fullContent, offset, limit);
4269
- const shouldShowLineNumbers = showLineNumbers !== false;
4270
- const formattedContent = shouldShowLineNumbers ? formatWithLineNumbers(result.content, result.lines.start) : result.content;
4271
- return {
4272
- content: formattedContent,
4273
- size: stat3.size,
4274
- path: stat3.path,
4275
- ...hasLineRange && {
4276
- lines: result.lines,
4277
- totalLines: result.totalLines
4278
- }
4279
- };
4280
- }
4281
- });
4282
- }
4283
- if (!isReadOnly && writeFileConfig.enabled) {
4284
- tools[WORKSPACE_TOOLS.FILESYSTEM.WRITE_FILE] = createTool({
4285
- id: WORKSPACE_TOOLS.FILESYSTEM.WRITE_FILE,
4286
- description: "Write content to a file in the workspace filesystem. Creates parent directories if needed.",
4287
- requireApproval: writeFileConfig.requireApproval,
4288
- inputSchema: z.object({
4289
- path: z.string().describe('The path where to write the file (e.g., "/data/output.txt")'),
4290
- content: z.string().describe("The content to write to the file"),
4291
- overwrite: z.boolean().optional().default(true).describe("Whether to overwrite the file if it already exists")
4292
- }),
4293
- outputSchema: z.object({
4294
- success: z.boolean(),
4295
- path: z.string().describe("The path where the file was written"),
4296
- size: z.number().describe("The size of the written content in bytes")
4297
- }),
4298
- execute: async ({ path: path4, content, overwrite }) => {
4299
- if (readTracker && writeFileConfig.requireReadBeforeWrite) {
4300
- try {
4301
- const stat3 = await workspace.filesystem.stat(path4);
4302
- const check = readTracker.needsReRead(path4, stat3.modifiedAt);
4303
- if (check.needsReRead) {
4304
- throw new FileReadRequiredError(path4, check.reason);
4305
- }
4306
- } catch (error) {
4307
- if (!(error instanceof FileNotFoundError)) {
4308
- throw error;
4309
- }
4310
- }
4311
- }
4312
- await workspace.filesystem.writeFile(path4, content, { overwrite });
4313
- if (readTracker) {
4314
- readTracker.clearReadRecord(path4);
4315
- }
4316
- return {
4317
- success: true,
4318
- path: path4,
4319
- size: Buffer.byteLength(content, "utf-8")
4320
- };
4321
- }
4322
- });
4323
- }
4324
- if (!isReadOnly && editFileConfig.enabled) {
4325
- tools[WORKSPACE_TOOLS.FILESYSTEM.EDIT_FILE] = createTool({
4326
- id: WORKSPACE_TOOLS.FILESYSTEM.EDIT_FILE,
4327
- description: `Edit a file by replacing specific text. The old_string must match exactly and be unique in the file.
4328
-
4329
- Usage:
4330
- - Read the file first to get the exact text to replace.
4331
- - By default, ${WORKSPACE_TOOLS.FILESYSTEM.READ_FILE} output includes line number prefixes (e.g., " 1\u2192"). Ensure you preserve the exact indentation as it appears AFTER the arrow. Never include any part of the line number prefix in old_string or new_string.
4332
- - Include enough surrounding context (multiple lines) to make old_string unique. If it still isn't unique, include more lines.
4333
- - Use replace_all only when intentionally replacing all occurrences.`,
4334
- requireApproval: editFileConfig.requireApproval,
4335
- inputSchema: z.object({
4336
- path: z.string().describe("The path to the file to edit"),
4337
- old_string: z.string().describe("The exact text to find and replace. Must be unique in the file."),
4338
- new_string: z.string().describe("The text to replace old_string with"),
4339
- replace_all: z.boolean().optional().default(false).describe("If true, replace all occurrences. If false (default), old_string must be unique.")
4340
- }),
4341
- outputSchema: z.object({
4342
- success: z.boolean(),
4343
- path: z.string().describe("The path to the edited file"),
4344
- replacements: z.number().describe("Number of replacements made"),
4345
- error: z.string().optional().describe("Error message if the edit failed")
4346
- }),
4347
- execute: async ({ path: path4, old_string, new_string, replace_all }) => {
4348
- try {
4349
- if (readTracker && editFileConfig.requireReadBeforeWrite) {
4350
- const stat3 = await workspace.filesystem.stat(path4);
4351
- const check = readTracker.needsReRead(path4, stat3.modifiedAt);
4352
- if (check.needsReRead) {
4353
- throw new FileReadRequiredError(path4, check.reason);
4354
- }
4355
- }
4356
- const content = await workspace.filesystem.readFile(path4, { encoding: "utf-8" });
4357
- if (typeof content !== "string") {
4358
- return {
4359
- success: false,
4360
- path: path4,
4361
- replacements: 0,
4362
- error: "Cannot edit binary files. Use workspace_write_file instead."
4363
- };
4364
- }
4365
- const result = replaceString(content, old_string, new_string, replace_all);
4366
- await workspace.filesystem.writeFile(path4, result.content, { overwrite: true });
4367
- if (readTracker) {
4368
- readTracker.clearReadRecord(path4);
4369
- }
4370
- return {
4371
- success: true,
4372
- path: path4,
4373
- replacements: result.replacements
4374
- };
4375
- } catch (error) {
4376
- if (error instanceof FileReadRequiredError) {
4377
- throw error;
4378
- }
4379
- if (error instanceof StringNotFoundError) {
4380
- return {
4381
- success: false,
4382
- path: path4,
4383
- replacements: 0,
4384
- error: error.message
4385
- };
4386
- }
4387
- if (error instanceof StringNotUniqueError) {
4388
- return {
4389
- success: false,
4390
- path: path4,
4391
- replacements: 0,
4392
- error: error.message
4393
- };
4394
- }
4395
- throw error;
4396
- }
4397
- }
4398
- });
4399
- }
4400
- const listFilesConfig = resolveToolConfig(toolsConfig, WORKSPACE_TOOLS.FILESYSTEM.LIST_FILES);
4401
- if (listFilesConfig.enabled) {
4402
- tools[WORKSPACE_TOOLS.FILESYSTEM.LIST_FILES] = createTool({
4403
- id: WORKSPACE_TOOLS.FILESYSTEM.LIST_FILES,
4404
- description: `List files and directories in the workspace filesystem.
4405
- Returns a tree-style view (like the Unix "tree" command) for easy visualization.
4406
- The output is displayed to the user as a tree-like structure in the tool result.
4407
- Options mirror common tree command flags for familiarity.
4408
-
4409
- Examples:
4410
- - List root: { path: "/" }
4411
- - Deep listing: { path: "/src", maxDepth: 5 }
4412
- - Directories only: { path: "/", dirsOnly: true }
4413
- - Exclude node_modules: { path: "/", exclude: "node_modules" }`,
4414
- requireApproval: listFilesConfig.requireApproval,
4415
- inputSchema: z.object({
4416
- path: z.string().default("/").describe("Directory path to list"),
4417
- maxDepth: z.number().optional().default(3).describe("Maximum depth to descend (default: 3). Similar to tree -L flag."),
4418
- showHidden: z.boolean().optional().default(false).describe('Show hidden files starting with "." (default: false). Similar to tree -a flag.'),
4419
- dirsOnly: z.boolean().optional().default(false).describe("List directories only, no files (default: false). Similar to tree -d flag."),
4420
- exclude: z.string().optional().describe('Pattern to exclude (e.g., "node_modules"). Similar to tree -I flag.'),
4421
- extension: z.string().optional().describe('Filter by file extension (e.g., ".ts"). Similar to tree -P flag.')
4422
- }),
4423
- outputSchema: z.object({
4424
- tree: z.string().describe("Tree-style directory listing"),
4425
- summary: z.string().describe('Summary of directories and files (e.g., "3 directories, 12 files")'),
4426
- metadata: z.object({
4427
- workspace: z.object({
4428
- id: z.string().optional(),
4429
- name: z.string().optional()
4430
- }).optional(),
4431
- filesystem: z.object({
4432
- id: z.string().optional(),
4433
- name: z.string().optional(),
4434
- provider: z.string().optional()
4435
- }).optional()
4436
- }).optional().describe("Metadata about the workspace and filesystem")
4437
- }),
4438
- execute: async ({ path: path4 = "/", maxDepth = 3, showHidden, dirsOnly, exclude, extension }) => {
4439
- const result = await formatAsTree(workspace.filesystem, path4, {
4440
- maxDepth,
4441
- showHidden,
4442
- dirsOnly,
4443
- exclude: exclude || void 0,
4444
- extension: extension || void 0
4445
- });
4446
- const fs5 = workspace.filesystem;
4447
- const metadata = {
4448
- workspace: {
4449
- id: workspace.id,
4450
- name: workspace.name
4451
- },
4452
- filesystem: {
4453
- id: fs5.id,
4454
- name: fs5.name,
4455
- provider: fs5.provider
4456
- }
4457
- };
4458
- return {
4459
- tree: result.tree,
4460
- summary: result.summary,
4461
- metadata
4462
- };
4463
- }
4464
- });
4465
- }
4466
- const deleteConfig = resolveToolConfig(toolsConfig, WORKSPACE_TOOLS.FILESYSTEM.DELETE);
4467
- if (!isReadOnly && deleteConfig.enabled) {
4468
- tools[WORKSPACE_TOOLS.FILESYSTEM.DELETE] = createTool({
4469
- id: WORKSPACE_TOOLS.FILESYSTEM.DELETE,
4470
- description: "Delete a file or directory from the workspace filesystem",
4471
- requireApproval: deleteConfig.requireApproval,
4472
- inputSchema: z.object({
4473
- path: z.string().describe("The path to the file or directory to delete"),
4474
- recursive: z.boolean().optional().default(false).describe(
4475
- "If true, delete directories and their contents recursively. Required for non-empty directories."
4476
- )
4477
- }),
4478
- outputSchema: z.object({
4479
- success: z.boolean(),
4480
- path: z.string()
4481
- }),
4482
- execute: async ({ path: path4, recursive }) => {
4483
- const stat3 = await workspace.filesystem.stat(path4);
4484
- if (stat3.type === "directory") {
4485
- await workspace.filesystem.rmdir(path4, { recursive, force: recursive });
4486
- } else {
4487
- await workspace.filesystem.deleteFile(path4);
4488
- }
4489
- return { success: true, path: path4 };
4490
- }
4491
- });
4492
- }
4493
- const fileStatConfig = resolveToolConfig(toolsConfig, WORKSPACE_TOOLS.FILESYSTEM.FILE_STAT);
4494
- if (fileStatConfig.enabled) {
4495
- tools[WORKSPACE_TOOLS.FILESYSTEM.FILE_STAT] = createTool({
4496
- id: WORKSPACE_TOOLS.FILESYSTEM.FILE_STAT,
4497
- description: "Get file or directory metadata from the workspace. Returns existence, type, size, and modification time.",
4498
- requireApproval: fileStatConfig.requireApproval,
4499
- inputSchema: z.object({
4500
- path: z.string().describe("The path to check")
4501
- }),
4502
- outputSchema: z.object({
4503
- exists: z.boolean().describe("Whether the path exists"),
4504
- type: z.enum(["file", "directory", "none"]).describe("The type of the path if it exists"),
4505
- size: z.number().optional().describe("Size in bytes (for files)"),
4506
- modifiedAt: z.string().optional().describe("Last modification time (ISO string)")
4507
- }),
4508
- execute: async ({ path: path4 }) => {
4509
- try {
4510
- const stat3 = await workspace.filesystem.stat(path4);
4511
- return {
4512
- exists: true,
4513
- type: stat3.type,
4514
- size: stat3.size,
4515
- modifiedAt: stat3.modifiedAt.toISOString()
4516
- };
4517
- } catch (error) {
4518
- if (error instanceof FileNotFoundError) {
4519
- return { exists: false, type: "none" };
4520
- }
4521
- throw error;
4522
- }
4523
- }
4524
- });
4525
- }
4526
- const mkdirConfig = resolveToolConfig(toolsConfig, WORKSPACE_TOOLS.FILESYSTEM.MKDIR);
4527
- if (!isReadOnly && mkdirConfig.enabled) {
4528
- tools[WORKSPACE_TOOLS.FILESYSTEM.MKDIR] = createTool({
4529
- id: WORKSPACE_TOOLS.FILESYSTEM.MKDIR,
4530
- description: "Create a directory in the workspace filesystem",
4531
- requireApproval: mkdirConfig.requireApproval,
4532
- inputSchema: z.object({
4533
- path: z.string().describe("The path of the directory to create"),
4534
- recursive: z.boolean().optional().default(true).describe("Whether to create parent directories if they do not exist")
4535
- }),
4536
- outputSchema: z.object({
4537
- success: z.boolean(),
4538
- path: z.string()
4539
- }),
4540
- execute: async ({ path: path4, recursive }) => {
4541
- await workspace.filesystem.mkdir(path4, { recursive });
4542
- return { success: true, path: path4 };
4543
- }
4544
- });
5454
+ const addTool = (name, tool, opts) => {
5455
+ const config = resolveToolConfig(toolsConfig, name);
5456
+ if (!config.enabled) return;
5457
+ if (opts?.requireWrite && isReadOnly) return;
5458
+ if (readTracker && opts?.readTrackerMode) {
5459
+ tools[name] = wrapWithReadTracker(tool, workspace, readTracker, config, opts.readTrackerMode);
5460
+ } else {
5461
+ tools[name] = wrapTool(tool, workspace, config);
4545
5462
  }
5463
+ };
5464
+ if (workspace.filesystem) {
5465
+ addTool(WORKSPACE_TOOLS.FILESYSTEM.READ_FILE, readFileTool, { readTrackerMode: "read" });
5466
+ addTool(WORKSPACE_TOOLS.FILESYSTEM.WRITE_FILE, writeFileTool, {
5467
+ requireWrite: true,
5468
+ readTrackerMode: "write"
5469
+ });
5470
+ addTool(WORKSPACE_TOOLS.FILESYSTEM.EDIT_FILE, editFileTool, {
5471
+ requireWrite: true,
5472
+ readTrackerMode: "write"
5473
+ });
5474
+ addTool(WORKSPACE_TOOLS.FILESYSTEM.LIST_FILES, listFilesTool);
5475
+ addTool(WORKSPACE_TOOLS.FILESYSTEM.DELETE, deleteFileTool, { requireWrite: true });
5476
+ addTool(WORKSPACE_TOOLS.FILESYSTEM.FILE_STAT, fileStatTool);
5477
+ addTool(WORKSPACE_TOOLS.FILESYSTEM.MKDIR, mkdirTool, { requireWrite: true });
5478
+ addTool(WORKSPACE_TOOLS.FILESYSTEM.GREP, grepTool);
4546
5479
  }
4547
5480
  if (workspace.canBM25 || workspace.canVector) {
4548
- const searchConfig = resolveToolConfig(toolsConfig, WORKSPACE_TOOLS.SEARCH.SEARCH);
4549
- if (searchConfig.enabled) {
4550
- tools[WORKSPACE_TOOLS.SEARCH.SEARCH] = createTool({
4551
- id: WORKSPACE_TOOLS.SEARCH.SEARCH,
4552
- description: "Search indexed content in the workspace. Supports keyword (BM25), semantic (vector), and hybrid search modes.",
4553
- requireApproval: searchConfig.requireApproval,
4554
- inputSchema: z.object({
4555
- query: z.string().describe("The search query string"),
4556
- topK: z.number().optional().default(5).describe("Maximum number of results to return"),
4557
- mode: z.enum(["bm25", "vector", "hybrid"]).optional().describe("Search mode: bm25 for keyword search, vector for semantic search, hybrid for both combined"),
4558
- minScore: z.number().optional().describe("Minimum score threshold (0-1 for normalized scores)")
4559
- }),
4560
- outputSchema: z.object({
4561
- results: z.array(
4562
- z.object({
4563
- id: z.string().describe("Document/file path"),
4564
- content: z.string().describe("The matching content"),
4565
- score: z.number().describe("Relevance score"),
4566
- lineRange: z.object({
4567
- start: z.number(),
4568
- end: z.number()
4569
- }).optional().describe("Line range where query terms were found")
4570
- })
4571
- ),
4572
- count: z.number().describe("Number of results returned"),
4573
- mode: z.string().describe("The search mode that was used")
4574
- }),
4575
- execute: async ({ query, topK, mode, minScore }) => {
4576
- const results = await workspace.search(query, {
4577
- topK,
4578
- mode,
4579
- minScore
4580
- });
4581
- return {
4582
- results: results.map((r) => ({
4583
- id: r.id,
4584
- content: r.content,
4585
- score: r.score,
4586
- lineRange: r.lineRange
4587
- })),
4588
- count: results.length,
4589
- mode: mode ?? (workspace.canHybrid ? "hybrid" : workspace.canVector ? "vector" : "bm25")
4590
- };
4591
- }
4592
- });
4593
- }
4594
- const indexConfig = resolveToolConfig(toolsConfig, WORKSPACE_TOOLS.SEARCH.INDEX);
4595
- if (!isReadOnly && indexConfig.enabled) {
4596
- tools[WORKSPACE_TOOLS.SEARCH.INDEX] = createTool({
4597
- id: WORKSPACE_TOOLS.SEARCH.INDEX,
4598
- description: "Index content for search. The path becomes the document ID in search results.",
4599
- requireApproval: indexConfig.requireApproval,
4600
- inputSchema: z.object({
4601
- path: z.string().describe("The document ID/path for search results"),
4602
- content: z.string().describe("The text content to index"),
4603
- metadata: z.record(z.unknown()).optional().describe("Optional metadata to store with the document")
4604
- }),
4605
- outputSchema: z.object({
4606
- success: z.boolean(),
4607
- path: z.string().describe("The indexed document ID")
4608
- }),
4609
- execute: async ({ path: path4, content, metadata }) => {
4610
- await workspace.index(path4, content, { metadata });
4611
- return { success: true, path: path4 };
4612
- }
4613
- });
4614
- }
5481
+ addTool(WORKSPACE_TOOLS.SEARCH.SEARCH, searchTool);
5482
+ addTool(WORKSPACE_TOOLS.SEARCH.INDEX, indexContentTool, { requireWrite: true });
4615
5483
  }
4616
5484
  if (workspace.sandbox) {
4617
- const pathContext = workspace.getPathContext();
4618
- const pathInfo = pathContext.instructions ? ` ${pathContext.instructions}` : "";
4619
5485
  const executeCommandConfig = resolveToolConfig(toolsConfig, WORKSPACE_TOOLS.SANDBOX.EXECUTE_COMMAND);
4620
5486
  if (workspace.sandbox.executeCommand && executeCommandConfig.enabled) {
4621
- tools[WORKSPACE_TOOLS.SANDBOX.EXECUTE_COMMAND] = createTool({
4622
- id: WORKSPACE_TOOLS.SANDBOX.EXECUTE_COMMAND,
4623
- description: `Execute a shell command in the workspace sandbox.${pathInfo}
5487
+ const pathContext = workspace.getPathContext();
5488
+ const pathInfo = pathContext.instructions ? `
4624
5489
 
4625
- Usage:
4626
- - Verify parent directories exist before running commands that create files or directories.
4627
- - Always quote file paths that contain spaces (e.g., cd "/path/with spaces").
4628
- - Use the timeout parameter to limit execution time. Behavior when omitted depends on the sandbox provider.
4629
- - Use cwd to set the working directory, or commands run from the sandbox default.`,
4630
- requireApproval: executeCommandConfig.requireApproval,
4631
- inputSchema: z.object({
4632
- command: z.string().describe('The command to execute (e.g., "ls", "npm", "python")'),
4633
- args: z.array(z.string()).nullish().default([]).describe("Arguments to pass to the command"),
4634
- timeout: z.number().nullish().describe("Maximum execution time in milliseconds. Example: 60000 for 1 minute."),
4635
- cwd: z.string().nullish().describe("Working directory for the command")
4636
- }),
4637
- outputSchema: z.object({
4638
- success: z.boolean().describe("Whether the command executed successfully (exit code 0)"),
4639
- stdout: z.string().describe("Standard output from the command"),
4640
- stderr: z.string().describe("Standard error output"),
4641
- exitCode: z.number().describe("Exit code (0 = success)"),
4642
- executionTimeMs: z.number().describe("How long the execution took in milliseconds")
4643
- }),
4644
- execute: async ({ command, args, timeout, cwd }, context) => {
4645
- const getExecutionMetadata = () => ({
4646
- workspace: {
4647
- id: workspace.id,
4648
- name: workspace.name
4649
- },
4650
- sandbox: {
4651
- id: workspace.sandbox?.id,
4652
- name: workspace.sandbox?.name,
4653
- provider: workspace.sandbox?.provider,
4654
- status: workspace.sandbox?.status
4655
- }
4656
- });
4657
- const startedAt = Date.now();
4658
- try {
4659
- const result = await workspace.sandbox.executeCommand(command, args ?? [], {
4660
- timeout: timeout ?? void 0,
4661
- cwd: cwd ?? void 0,
4662
- // Stream stdout/stderr as tool-output chunks for proper UI integration
4663
- onStdout: async (data) => {
4664
- await context?.writer?.write({
4665
- type: "sandbox-stdout",
4666
- data,
4667
- timestamp: Date.now(),
4668
- metadata: getExecutionMetadata()
4669
- });
4670
- },
4671
- onStderr: async (data) => {
4672
- await context?.writer?.write({
4673
- type: "sandbox-stderr",
4674
- data,
4675
- timestamp: Date.now(),
4676
- metadata: getExecutionMetadata()
4677
- });
4678
- }
4679
- });
4680
- await context?.writer?.write({
4681
- type: "sandbox-exit",
4682
- exitCode: result.exitCode,
4683
- success: result.success,
4684
- executionTimeMs: result.executionTimeMs,
4685
- metadata: getExecutionMetadata()
4686
- });
4687
- return {
4688
- success: result.success,
4689
- stdout: result.stdout,
4690
- stderr: result.stderr,
4691
- exitCode: result.exitCode,
4692
- executionTimeMs: result.executionTimeMs
4693
- };
4694
- } catch (error) {
4695
- await context?.writer?.write({
4696
- type: "sandbox-exit",
4697
- exitCode: -1,
4698
- success: false,
4699
- executionTimeMs: Date.now() - startedAt,
4700
- metadata: getExecutionMetadata()
4701
- });
4702
- throw error;
4703
- }
4704
- }
4705
- });
5490
+ ${pathContext.instructions}` : "";
5491
+ const description = pathInfo ? `${executeCommandTool.description}${pathInfo}` : executeCommandTool.description;
5492
+ tools[WORKSPACE_TOOLS.SANDBOX.EXECUTE_COMMAND] = {
5493
+ ...wrapTool(executeCommandTool, workspace, executeCommandConfig),
5494
+ description
5495
+ };
4706
5496
  }
4707
5497
  }
4708
5498
  return tools;
4709
5499
  }
4710
5500
 
4711
- 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 };
4712
- //# sourceMappingURL=chunk-4EHGOATH.js.map
4713
- //# sourceMappingURL=chunk-4EHGOATH.js.map
5501
+ export { BM25Index, CompositeFilesystem, CompositeVersionedSkillSource, DirectoryNotEmptyError, DirectoryNotFoundError, FileExistsError, FileNotFoundError, FileReadRequiredError, FilesystemError, FilesystemNotAvailableError, FilesystemNotMountableError, FilesystemNotReadyError, IsDirectoryError, IsolationUnavailableError, LocalFilesystem, LocalSandbox, LocalSkillSource, MastraFilesystem, MastraSandbox, MountError, MountManager, MountNotSupportedError, NotDirectoryError, PermissionError, SandboxError, SandboxExecutionError, SandboxFeatureNotSupportedError, SandboxNotAvailableError, SandboxNotReadyError, SandboxTimeoutError, SearchNotAvailableError, VersionedSkillSource, WORKSPACE_TOOLS, WORKSPACE_TOOLS_PREFIX, Workspace, WorkspaceError, WorkspaceNotAvailableError, WorkspaceNotReadyError, WorkspaceReadOnlyError, callLifecycle, collectSkillForPublish, createGlobMatcher, createWorkspaceTools, deleteFileTool, detectIsolation, editFileTool, executeCommandTool, extractGlobBase, extractLines, fileStatTool, getRecommendedIsolation, indexContentTool, isGlobPattern, isIsolationAvailable, listFilesTool, matchGlob, mkdirTool, publishSkillFromSource, readFileTool, requireFilesystem, requireSandbox, requireWorkspace, resolveToolConfig, searchTool, writeFileTool };
5502
+ //# sourceMappingURL=chunk-FZ5DRHKE.js.map
5503
+ //# sourceMappingURL=chunk-FZ5DRHKE.js.map