@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,11 +1,13 @@
1
1
  'use strict';
2
2
 
3
- var chunk7UWHFWST_cjs = require('./chunk-7UWHFWST.cjs');
3
+ var chunkEAZ6YDCQ_cjs = require('./chunk-EAZ6YDCQ.cjs');
4
4
  var chunkRO47SMI7_cjs = require('./chunk-RO47SMI7.cjs');
5
5
  var chunk7XAECHYL_cjs = require('./chunk-7XAECHYL.cjs');
6
+ var posixPath = require('path/posix');
6
7
  var fs = require('fs');
7
8
  var fs2 = require('fs/promises');
8
9
  var nodePath = require('path');
10
+ var picomatch = require('picomatch');
9
11
  var crypto = require('crypto');
10
12
  var matter = require('gray-matter');
11
13
  var childProcess = require('child_process');
@@ -32,8 +34,10 @@ function _interopNamespace(e) {
32
34
  return Object.freeze(n);
33
35
  }
34
36
 
37
+ var posixPath__default = /*#__PURE__*/_interopDefault(posixPath);
35
38
  var fs2__namespace = /*#__PURE__*/_interopNamespace(fs2);
36
39
  var nodePath__namespace = /*#__PURE__*/_interopNamespace(nodePath);
40
+ var picomatch__default = /*#__PURE__*/_interopDefault(picomatch);
37
41
  var crypto__namespace = /*#__PURE__*/_interopNamespace(crypto);
38
42
  var matter__default = /*#__PURE__*/_interopDefault(matter);
39
43
  var childProcess__namespace = /*#__PURE__*/_interopNamespace(childProcess);
@@ -48,6 +52,12 @@ var WorkspaceError = class extends Error {
48
52
  this.name = "WorkspaceError";
49
53
  }
50
54
  };
55
+ var WorkspaceNotAvailableError = class extends WorkspaceError {
56
+ constructor() {
57
+ super("Workspace not available. Ensure the agent has a workspace configured.", "NO_WORKSPACE");
58
+ this.name = "WorkspaceNotAvailableError";
59
+ }
60
+ };
51
61
  var FilesystemNotAvailableError = class extends WorkspaceError {
52
62
  constructor() {
53
63
  super("Workspace does not have a filesystem configured", "NO_FILESYSTEM");
@@ -167,6 +177,7 @@ var CompositeFilesystem = class {
167
177
  id;
168
178
  name = "CompositeFilesystem";
169
179
  provider = "composite";
180
+ readOnly;
170
181
  status = "ready";
171
182
  _mounts;
172
183
  constructor(config) {
@@ -179,6 +190,7 @@ var CompositeFilesystem = class {
179
190
  if (this._mounts.size === 0) {
180
191
  throw new Error("CompositeFilesystem requires at least one mount");
181
192
  }
193
+ this.readOnly = [...this._mounts.values()].every((fs5) => fs5.readOnly) || void 0;
182
194
  const mountPaths = [...this._mounts.keys()];
183
195
  for (const a of mountPaths) {
184
196
  for (const b of mountPaths) {
@@ -196,10 +208,29 @@ var CompositeFilesystem = class {
196
208
  }
197
209
  /**
198
210
  * Get the mounts map.
211
+ * Returns a typed map where `get()` preserves the concrete filesystem type per mount path.
199
212
  */
200
213
  get mounts() {
201
214
  return this._mounts;
202
215
  }
216
+ /**
217
+ * Get status and metadata for this composite filesystem.
218
+ * Includes info from each mounted filesystem in `metadata.mounts`.
219
+ */
220
+ async getInfo() {
221
+ const mounts = {};
222
+ for (const [mountPath, fs5] of this._mounts) {
223
+ mounts[mountPath] = await fs5.getInfo?.() ?? null;
224
+ }
225
+ return {
226
+ id: this.id,
227
+ name: this.name,
228
+ provider: this.provider,
229
+ status: this.status,
230
+ readOnly: this.readOnly,
231
+ metadata: { mounts }
232
+ };
233
+ }
203
234
  /**
204
235
  * Get the underlying filesystem for a given path.
205
236
  * Returns undefined if the path doesn't resolve to any mount.
@@ -218,7 +249,8 @@ var CompositeFilesystem = class {
218
249
  }
219
250
  normalizePath(path4) {
220
251
  if (!path4 || path4 === "/") return "/";
221
- let n = path4.startsWith("/") ? path4 : `/${path4}`;
252
+ let n = posixPath__default.default.normalize(path4);
253
+ if (!n.startsWith("/")) n = `/${n}`;
222
254
  if (n.length > 1 && n.endsWith("/")) n = n.slice(0, -1);
223
255
  return n;
224
256
  }
@@ -452,8 +484,7 @@ var CompositeFilesystem = class {
452
484
  return `- ${mountPath}: ${name} ${access2}`;
453
485
  }).join("\n");
454
486
  return `Mounted filesystems:
455
- ${mountDescriptions}
456
- Files written via workspace tools are accessible at the same paths in sandbox commands.`;
487
+ ${mountDescriptions}`;
457
488
  }
458
489
  };
459
490
 
@@ -763,6 +794,7 @@ var LocalFilesystem = class extends MastraFilesystem {
763
794
  status = "pending";
764
795
  _basePath;
765
796
  _contained;
797
+ _allowedPaths;
766
798
  /**
767
799
  * The absolute base path on disk where files are stored.
768
800
  * Useful for understanding how workspace paths map to disk paths.
@@ -770,16 +802,51 @@ var LocalFilesystem = class extends MastraFilesystem {
770
802
  get basePath() {
771
803
  return this._basePath;
772
804
  }
805
+ /**
806
+ * Current set of additional allowed paths (absolute, resolved).
807
+ * These paths are permitted beyond basePath when containment is enabled.
808
+ */
809
+ get allowedPaths() {
810
+ return this._allowedPaths;
811
+ }
812
+ /**
813
+ * Update allowed paths. Accepts a direct array or an updater callback
814
+ * receiving the current paths (React setState pattern).
815
+ *
816
+ * @example
817
+ * ```typescript
818
+ * // Set directly
819
+ * fs.setAllowedPaths(['/home/user/.config']);
820
+ *
821
+ * // Update with callback
822
+ * fs.setAllowedPaths(prev => [...prev, '/home/user/.ssh']);
823
+ * ```
824
+ */
825
+ setAllowedPaths(pathsOrUpdater) {
826
+ const newPaths = typeof pathsOrUpdater === "function" ? pathsOrUpdater(this._allowedPaths) : pathsOrUpdater;
827
+ this._allowedPaths = newPaths.map((p) => nodePath__namespace.resolve(p));
828
+ }
773
829
  constructor(options) {
774
830
  super({ ...options, name: "LocalFilesystem" });
775
831
  this.id = options.id ?? this.generateId();
776
832
  this._basePath = nodePath__namespace.resolve(options.basePath);
777
833
  this._contained = options.contained ?? true;
778
834
  this.readOnly = options.readOnly;
835
+ this._allowedPaths = (options.allowedPaths ?? []).map((p) => nodePath__namespace.resolve(p));
779
836
  }
780
837
  generateId() {
781
838
  return `local-fs-${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 8)}`;
782
839
  }
840
+ /**
841
+ * Check if an absolute path falls within basePath or any allowed path.
842
+ */
843
+ _isWithinAnyRoot(absolutePath) {
844
+ const roots = [this._basePath, ...this._allowedPaths];
845
+ return roots.some((root) => {
846
+ const relative2 = nodePath__namespace.relative(root, absolutePath);
847
+ return !relative2.startsWith("..") && !nodePath__namespace.isAbsolute(relative2);
848
+ });
849
+ }
783
850
  toBuffer(content) {
784
851
  if (Buffer.isBuffer(content)) return content;
785
852
  if (content instanceof Uint8Array) return Buffer.from(content);
@@ -791,8 +858,7 @@ var LocalFilesystem = class extends MastraFilesystem {
791
858
  absolutePath = nodePath__namespace.normalize(inputPath);
792
859
  } else if (this._contained && nodePath__namespace.isAbsolute(inputPath)) {
793
860
  const normalized = nodePath__namespace.normalize(inputPath);
794
- const relative2 = nodePath__namespace.relative(this._basePath, normalized);
795
- if (!relative2.startsWith("..") && !nodePath__namespace.isAbsolute(relative2)) {
861
+ if (this._isWithinAnyRoot(normalized)) {
796
862
  absolutePath = normalized;
797
863
  } else {
798
864
  const cleanedPath = inputPath.replace(/^\/+/, "");
@@ -803,8 +869,7 @@ var LocalFilesystem = class extends MastraFilesystem {
803
869
  absolutePath = nodePath__namespace.resolve(this._basePath, nodePath__namespace.normalize(cleanedPath));
804
870
  }
805
871
  if (this._contained) {
806
- const relative2 = nodePath__namespace.relative(this._basePath, absolutePath);
807
- if (relative2.startsWith("..") || nodePath__namespace.isAbsolute(relative2)) {
872
+ if (!this._isWithinAnyRoot(absolutePath)) {
808
873
  throw new PermissionError(inputPath, "access");
809
874
  }
810
875
  }
@@ -824,14 +889,19 @@ var LocalFilesystem = class extends MastraFilesystem {
824
889
  */
825
890
  async assertPathContained(absolutePath) {
826
891
  if (!this._contained) return;
827
- let baseReal;
828
- try {
829
- baseReal = await fs2__namespace.realpath(this._basePath);
830
- } catch (error) {
831
- if (isEnoentError(error)) {
832
- throw new DirectoryNotFoundError(this._basePath);
892
+ const rootReals = [];
893
+ for (const root of [this._basePath, ...this._allowedPaths]) {
894
+ try {
895
+ rootReals.push(await fs2__namespace.realpath(root));
896
+ } catch (error) {
897
+ if (isEnoentError(error)) {
898
+ continue;
899
+ }
900
+ throw error;
833
901
  }
834
- throw error;
902
+ }
903
+ if (rootReals.length === 0) {
904
+ throw new DirectoryNotFoundError(this._basePath);
835
905
  }
836
906
  let targetReal;
837
907
  try {
@@ -858,7 +928,10 @@ var LocalFilesystem = class extends MastraFilesystem {
858
928
  throw error;
859
929
  }
860
930
  }
861
- if (targetReal !== baseReal && !targetReal.startsWith(baseReal + nodePath__namespace.sep)) {
931
+ const isWithinRoot = rootReals.some(
932
+ (rootReal) => targetReal === rootReal || targetReal.startsWith(rootReal + nodePath__namespace.sep)
933
+ );
934
+ if (!isWithinRoot) {
862
935
  throw new PermissionError(absolutePath, "access");
863
936
  }
864
937
  }
@@ -1217,15 +1290,17 @@ var LocalFilesystem = class extends MastraFilesystem {
1217
1290
  error: this.error,
1218
1291
  metadata: {
1219
1292
  basePath: this.basePath,
1220
- contained: this._contained
1293
+ contained: this._contained,
1294
+ ...this._allowedPaths.length > 0 && { allowedPaths: [...this._allowedPaths] }
1221
1295
  }
1222
1296
  };
1223
1297
  }
1224
1298
  getInstructions() {
1299
+ const allowedNote = this._allowedPaths.length > 0 ? ` Additionally, the following paths outside basePath are accessible: ${this._allowedPaths.join(", ")}.` : "";
1225
1300
  if (this._contained) {
1226
- return `Local filesystem at "${this.basePath}". Files at workspace path "/foo" are stored at "${this.basePath}/foo" on disk.`;
1301
+ return `Local filesystem at "${this.basePath}". Files at workspace path "/foo" are stored at "${this.basePath}/foo" on disk.${allowedNote}`;
1227
1302
  }
1228
- 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.`;
1303
+ 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}`;
1229
1304
  }
1230
1305
  };
1231
1306
  var InMemoryFileReadTracker = class {
@@ -1268,6 +1343,38 @@ var InMemoryFileReadTracker = class {
1268
1343
  return normalized.replace(/\/$/, "") || "/";
1269
1344
  }
1270
1345
  };
1346
+ var GLOB_CHARS = /[*?{}[\]]/;
1347
+ function isGlobPattern(input) {
1348
+ return GLOB_CHARS.test(input);
1349
+ }
1350
+ function extractGlobBase(pattern) {
1351
+ const firstMeta = pattern.search(GLOB_CHARS);
1352
+ if (firstMeta === -1) {
1353
+ return pattern;
1354
+ }
1355
+ const prefix = pattern.slice(0, firstMeta);
1356
+ const lastSlash = prefix.lastIndexOf("/");
1357
+ if (lastSlash <= 0) {
1358
+ return "/";
1359
+ }
1360
+ return prefix.slice(0, lastSlash);
1361
+ }
1362
+ function normalizeForMatch(input) {
1363
+ if (input.startsWith("./")) return input.slice(2);
1364
+ if (input.startsWith("/")) return input.slice(1);
1365
+ return input;
1366
+ }
1367
+ function createGlobMatcher(patterns, options) {
1368
+ const patternArray = (Array.isArray(patterns) ? patterns : [patterns]).map(normalizeForMatch);
1369
+ const matcher = picomatch__default.default(patternArray, {
1370
+ posix: true,
1371
+ dot: options?.dot ?? false
1372
+ });
1373
+ return (path4) => matcher(normalizeForMatch(path4));
1374
+ }
1375
+ function matchGlob(path4, pattern, options) {
1376
+ return createGlobMatcher(pattern, options)(path4);
1377
+ }
1271
1378
 
1272
1379
  // src/workspace/sandbox/errors.ts
1273
1380
  var SandboxError = class extends Error {
@@ -2721,11 +2828,211 @@ var LocalSkillSource = class {
2721
2828
  const entries = await fs2__namespace.readdir(resolved, { withFileTypes: true });
2722
2829
  return entries.map((entry) => ({
2723
2830
  name: entry.name,
2724
- type: entry.isDirectory() ? "directory" : "file"
2831
+ type: entry.isDirectory() ? "directory" : "file",
2832
+ isSymlink: entry.isSymbolicLink() || void 0
2725
2833
  }));
2726
2834
  }
2727
2835
  };
2728
- var WorkspaceSkillsImpl = class {
2836
+
2837
+ // src/workspace/skills/versioned-skill-source.ts
2838
+ var VersionedSkillSource = class {
2839
+ #tree;
2840
+ #blobStore;
2841
+ #versionCreatedAt;
2842
+ /** Computed set of directory paths from the tree entries */
2843
+ #directories;
2844
+ constructor(tree, blobStore, versionCreatedAt) {
2845
+ this.#tree = tree;
2846
+ this.#blobStore = blobStore;
2847
+ this.#versionCreatedAt = versionCreatedAt;
2848
+ this.#directories = this.#computeDirectories();
2849
+ }
2850
+ /**
2851
+ * Compute all directory paths implied by the file tree.
2852
+ * For a file at "references/api.md", this adds "" (root), "references".
2853
+ */
2854
+ #computeDirectories() {
2855
+ const dirs = /* @__PURE__ */ new Set();
2856
+ dirs.add("");
2857
+ dirs.add(".");
2858
+ for (const filePath of Object.keys(this.#tree.entries)) {
2859
+ const parts = filePath.split("/");
2860
+ for (let i = 1; i < parts.length; i++) {
2861
+ dirs.add(parts.slice(0, i).join("/"));
2862
+ }
2863
+ }
2864
+ return dirs;
2865
+ }
2866
+ /**
2867
+ * Normalize a path by stripping leading/trailing slashes and dots.
2868
+ */
2869
+ #normalizePath(path4) {
2870
+ let normalized = path4.replace(/^[./\\]+|[/\\]+$/g, "");
2871
+ if (normalized === "") return "";
2872
+ return normalized;
2873
+ }
2874
+ async exists(path4) {
2875
+ const normalized = this.#normalizePath(path4);
2876
+ if (this.#tree.entries[normalized]) return true;
2877
+ return this.#directories.has(normalized);
2878
+ }
2879
+ async stat(path4) {
2880
+ const normalized = this.#normalizePath(path4);
2881
+ const name = normalized.split("/").pop() || normalized || ".";
2882
+ const entry = this.#tree.entries[normalized];
2883
+ if (entry) {
2884
+ return {
2885
+ name,
2886
+ type: "file",
2887
+ size: entry.size,
2888
+ createdAt: this.#versionCreatedAt,
2889
+ modifiedAt: this.#versionCreatedAt,
2890
+ mimeType: entry.mimeType
2891
+ };
2892
+ }
2893
+ if (this.#directories.has(normalized)) {
2894
+ return {
2895
+ name,
2896
+ type: "directory",
2897
+ size: 0,
2898
+ createdAt: this.#versionCreatedAt,
2899
+ modifiedAt: this.#versionCreatedAt
2900
+ };
2901
+ }
2902
+ throw new Error(`Path not found in skill version tree: ${path4}`);
2903
+ }
2904
+ async readFile(path4) {
2905
+ const normalized = this.#normalizePath(path4);
2906
+ const entry = this.#tree.entries[normalized];
2907
+ if (!entry) {
2908
+ throw new Error(`File not found in skill version tree: ${path4}`);
2909
+ }
2910
+ const blob = await this.#blobStore.get(entry.blobHash);
2911
+ if (!blob) {
2912
+ throw new Error(`Blob not found for hash ${entry.blobHash} (file: ${path4})`);
2913
+ }
2914
+ if (entry.encoding === "base64") {
2915
+ return Buffer.from(blob.content, "base64");
2916
+ }
2917
+ return blob.content;
2918
+ }
2919
+ async readdir(path4) {
2920
+ const normalized = this.#normalizePath(path4);
2921
+ if (!this.#directories.has(normalized)) {
2922
+ throw new Error(`Directory not found in skill version tree: ${path4}`);
2923
+ }
2924
+ const prefix = normalized === "" ? "" : normalized + "/";
2925
+ const seen = /* @__PURE__ */ new Set();
2926
+ const entries = [];
2927
+ for (const filePath of Object.keys(this.#tree.entries)) {
2928
+ if (!filePath.startsWith(prefix)) continue;
2929
+ const remaining = filePath.slice(prefix.length);
2930
+ const nextSegment = remaining.split("/")[0];
2931
+ if (!nextSegment || seen.has(nextSegment)) continue;
2932
+ seen.add(nextSegment);
2933
+ const isDirectory = remaining.includes("/");
2934
+ entries.push({
2935
+ name: nextSegment,
2936
+ type: isDirectory ? "directory" : "file"
2937
+ });
2938
+ }
2939
+ return entries;
2940
+ }
2941
+ };
2942
+
2943
+ // src/workspace/skills/composite-versioned-skill-source.ts
2944
+ var CompositeVersionedSkillSource = class {
2945
+ #sources = /* @__PURE__ */ new Map();
2946
+ #fallback;
2947
+ #fallbackSkills;
2948
+ constructor(entries, blobStore, options) {
2949
+ for (const entry of entries) {
2950
+ this.#sources.set(entry.dirName, new VersionedSkillSource(entry.tree, blobStore, entry.versionCreatedAt));
2951
+ }
2952
+ this.#fallback = options?.fallback;
2953
+ this.#fallbackSkills = new Set(options?.fallbackSkills ?? []);
2954
+ }
2955
+ #normalizePath(path4) {
2956
+ return path4.replace(/^[./\\]+|[/\\]+$/g, "");
2957
+ }
2958
+ /**
2959
+ * Route a path to the correct source.
2960
+ * Returns the source and the remaining path within that source.
2961
+ */
2962
+ #routePath(path4) {
2963
+ const normalized = this.#normalizePath(path4);
2964
+ if (normalized === "") return null;
2965
+ const segments = normalized.split("/");
2966
+ const skillDir = segments[0];
2967
+ const subPath = segments.slice(1).join("/");
2968
+ if (this.#fallbackSkills.has(skillDir) && this.#fallback) {
2969
+ return { source: this.#fallback, subPath: normalized };
2970
+ }
2971
+ const versionedSource = this.#sources.get(skillDir);
2972
+ if (versionedSource) {
2973
+ return { source: versionedSource, subPath };
2974
+ }
2975
+ if (this.#fallback) {
2976
+ return { source: this.#fallback, subPath: normalized };
2977
+ }
2978
+ return null;
2979
+ }
2980
+ async exists(path4) {
2981
+ const normalized = this.#normalizePath(path4);
2982
+ if (normalized === "") return true;
2983
+ const route = this.#routePath(path4);
2984
+ if (!route) return false;
2985
+ return route.source.exists(route.subPath);
2986
+ }
2987
+ async stat(path4) {
2988
+ const normalized = this.#normalizePath(path4);
2989
+ if (normalized === "") {
2990
+ return {
2991
+ name: ".",
2992
+ type: "directory",
2993
+ size: 0,
2994
+ createdAt: /* @__PURE__ */ new Date(),
2995
+ modifiedAt: /* @__PURE__ */ new Date()
2996
+ };
2997
+ }
2998
+ const route = this.#routePath(path4);
2999
+ if (!route) {
3000
+ throw new Error(`Path not found in composite skill source: ${path4}`);
3001
+ }
3002
+ return route.source.stat(route.subPath);
3003
+ }
3004
+ async readFile(path4) {
3005
+ const route = this.#routePath(path4);
3006
+ if (!route) {
3007
+ throw new Error(`File not found in composite skill source: ${path4}`);
3008
+ }
3009
+ return route.source.readFile(route.subPath);
3010
+ }
3011
+ async readdir(path4) {
3012
+ const normalized = this.#normalizePath(path4);
3013
+ if (normalized === "") {
3014
+ const entries = [];
3015
+ const seen = /* @__PURE__ */ new Set();
3016
+ for (const dirName of this.#sources.keys()) {
3017
+ entries.push({ name: dirName, type: "directory" });
3018
+ seen.add(dirName);
3019
+ }
3020
+ for (const dirName of this.#fallbackSkills) {
3021
+ if (!seen.has(dirName)) {
3022
+ entries.push({ name: dirName, type: "directory" });
3023
+ seen.add(dirName);
3024
+ }
3025
+ }
3026
+ return entries;
3027
+ }
3028
+ const route = this.#routePath(path4);
3029
+ if (!route) {
3030
+ throw new Error(`Directory not found in composite skill source: ${path4}`);
3031
+ }
3032
+ return route.source.readdir(route.subPath);
3033
+ }
3034
+ };
3035
+ var WorkspaceSkillsImpl = class _WorkspaceSkillsImpl {
2729
3036
  #source;
2730
3037
  #skillsResolver;
2731
3038
  #searchEngine;
@@ -2740,6 +3047,13 @@ var WorkspaceSkillsImpl = class {
2740
3047
  #lastDiscoveryTime = 0;
2741
3048
  /** Currently resolved skills paths (used to detect changes) */
2742
3049
  #resolvedPaths = [];
3050
+ /** Cached glob-resolved directories and per-pattern resolve timestamps */
3051
+ #globDirCache = /* @__PURE__ */ new Map();
3052
+ #globResolveTimes = /* @__PURE__ */ new Map();
3053
+ static GLOB_RESOLVE_INTERVAL = 5e3;
3054
+ // Re-walk glob dirs every 5s
3055
+ static STALENESS_CHECK_COOLDOWN = 2e3;
3056
+ // Skip staleness check for 2s after discovery
2743
3057
  constructor(config) {
2744
3058
  this.#source = config.source;
2745
3059
  this.#skillsResolver = config.skills;
@@ -2792,6 +3106,35 @@ var WorkspaceSkillsImpl = class {
2792
3106
  await this.refresh();
2793
3107
  }
2794
3108
  }
3109
+ async addSkill(skillPath) {
3110
+ await this.#ensureInitialized();
3111
+ let skillFilePath;
3112
+ let dirName;
3113
+ if (skillPath.endsWith("/SKILL.md") || skillPath === "SKILL.md") {
3114
+ skillFilePath = skillPath;
3115
+ dirName = this.#getParentPath(skillPath).split("/").pop() || "unknown";
3116
+ } else {
3117
+ skillFilePath = this.#joinPath(skillPath, "SKILL.md");
3118
+ dirName = skillPath.split("/").pop() || "unknown";
3119
+ }
3120
+ const source = this.#inferSource(skillPath);
3121
+ const skill = await this.#parseSkillFile(skillFilePath, dirName, source);
3122
+ const existing = this.#skills.get(skill.name);
3123
+ if (existing) {
3124
+ await this.#removeSkillFromIndex(existing);
3125
+ }
3126
+ this.#skills.set(skill.name, skill);
3127
+ await this.#indexSkill(skill);
3128
+ this.#lastDiscoveryTime = Date.now();
3129
+ }
3130
+ async removeSkill(skillName) {
3131
+ await this.#ensureInitialized();
3132
+ const skill = this.#skills.get(skillName);
3133
+ if (!skill) return;
3134
+ await this.#removeSkillFromIndex(skill);
3135
+ this.#skills.delete(skillName);
3136
+ this.#lastDiscoveryTime = Date.now();
3137
+ }
2795
3138
  /**
2796
3139
  * Resolve skills paths from the resolver (static array or function).
2797
3140
  */
@@ -2948,19 +3291,82 @@ var WorkspaceSkillsImpl = class {
2948
3291
  /**
2949
3292
  * Discover skills from all skills paths.
2950
3293
  * Uses currently resolved paths (must be set before calling).
3294
+ *
3295
+ * Paths can be plain directories (e.g., '/skills') or glob patterns
3296
+ * (e.g., '**\/skills'). Glob patterns resolve to directories that match
3297
+ * the pattern, each of which is then scanned for skills.
2951
3298
  */
2952
3299
  async #discoverSkills() {
3300
+ this.#globDirCache.clear();
3301
+ this.#globResolveTimes.clear();
2953
3302
  for (const skillsPath of this.#resolvedPaths) {
2954
3303
  const source = this.#determineSource(skillsPath);
2955
- await this.#discoverSkillsInPath(skillsPath, source);
3304
+ if (isGlobPattern(skillsPath)) {
3305
+ const matchingDirs = await this.#resolveGlobToDirectories(skillsPath);
3306
+ this.#globDirCache.set(skillsPath, matchingDirs);
3307
+ this.#globResolveTimes.set(skillsPath, Date.now());
3308
+ for (const dir of matchingDirs) {
3309
+ await this.#discoverSkillsInPath(dir, source);
3310
+ }
3311
+ } else {
3312
+ const isDirect = await this.#discoverDirectSkill(skillsPath, source);
3313
+ if (!isDirect) {
3314
+ await this.#discoverSkillsInPath(skillsPath, source);
3315
+ }
3316
+ }
2956
3317
  }
2957
3318
  this.#lastDiscoveryTime = Date.now();
2958
3319
  }
3320
+ /**
3321
+ * Resolve a glob pattern to a list of matching directories.
3322
+ * Walks from extractGlobBase() and tests each directory against the pattern.
3323
+ *
3324
+ * Note: Broad patterns like `/** /skills` resolve to a walk root of `/`,
3325
+ * scanning the entire workspace tree. This is cached per-pattern with a
3326
+ * TTL (GLOB_RESOLVE_INTERVAL) to limit I/O. For large workspaces, prefer
3327
+ * more specific patterns like `/src/** /skills` to narrow the walk root.
3328
+ */
3329
+ async #resolveGlobToDirectories(pattern) {
3330
+ const walkRoot = extractGlobBase(pattern);
3331
+ const matcher = createGlobMatcher(pattern, { dot: true });
3332
+ const matchingDirs = [];
3333
+ await this.#walkForDirectories(walkRoot, (dirPath) => {
3334
+ if (matcher(dirPath)) {
3335
+ matchingDirs.push(dirPath);
3336
+ }
3337
+ });
3338
+ return matchingDirs;
3339
+ }
3340
+ /**
3341
+ * Walk a directory tree and call callback for each directory found.
3342
+ */
3343
+ async #walkForDirectories(basePath, callback, depth = 0, maxDepth = 4) {
3344
+ if (depth >= maxDepth) return;
3345
+ try {
3346
+ const entries = await this.#source.readdir(basePath);
3347
+ for (const entry of entries) {
3348
+ if (entry.type !== "directory" || entry.isSymlink) continue;
3349
+ const entryPath = basePath === "/" ? `/${entry.name}` : `${basePath}/${entry.name}`;
3350
+ callback(entryPath);
3351
+ await this.#walkForDirectories(entryPath, callback, depth + 1, maxDepth);
3352
+ }
3353
+ } catch {
3354
+ }
3355
+ }
2959
3356
  /**
2960
3357
  * Discover skills in a single path
2961
3358
  */
2962
3359
  async #discoverSkillsInPath(skillsPath, source) {
2963
- if (!await this.#source.exists(skillsPath)) {
3360
+ try {
3361
+ if (!await this.#source.exists(skillsPath)) {
3362
+ return;
3363
+ }
3364
+ } catch (error) {
3365
+ if (error instanceof Error) {
3366
+ console.warn(`[WorkspaceSkills] Cannot access skills path "${skillsPath}": ${error.message}`);
3367
+ } else {
3368
+ console.warn(`[WorkspaceSkills] Cannot access skills path "${skillsPath}": ${String(error)}`);
3369
+ }
2964
3370
  return;
2965
3371
  }
2966
3372
  try {
@@ -2987,35 +3393,107 @@ var WorkspaceSkillsImpl = class {
2987
3393
  }
2988
3394
  }
2989
3395
  }
3396
+ /**
3397
+ * Attempt to discover a skill from a direct path reference.
3398
+ *
3399
+ * Handles two cases:
3400
+ * - Path ends with `/SKILL.md` → parse directly, extract dirName from parent
3401
+ * - Path is a directory containing `SKILL.md` → parse it as a single skill
3402
+ *
3403
+ * Returns `true` if the path was a direct skill reference (skip subdirectory scan),
3404
+ * `false` to fall through to the normal subdirectory scan.
3405
+ */
3406
+ async #discoverDirectSkill(skillsPath, source) {
3407
+ try {
3408
+ if (skillsPath.endsWith("/SKILL.md") || skillsPath === "SKILL.md") {
3409
+ if (!await this.#source.exists(skillsPath)) {
3410
+ return true;
3411
+ }
3412
+ const skillDir = this.#getParentPath(skillsPath);
3413
+ const dirName = skillDir.split("/").pop() || skillDir;
3414
+ try {
3415
+ const skill = await this.#parseSkillFile(skillsPath, dirName, source);
3416
+ this.#skills.set(skill.name, skill);
3417
+ await this.#indexSkill(skill);
3418
+ } catch (error) {
3419
+ if (error instanceof Error) {
3420
+ console.error(`[WorkspaceSkills] Failed to load skill from ${skillsPath}:`, error.message);
3421
+ }
3422
+ }
3423
+ return true;
3424
+ }
3425
+ if (await this.#source.exists(skillsPath)) {
3426
+ const skillFilePath = this.#joinPath(skillsPath, "SKILL.md");
3427
+ if (await this.#source.exists(skillFilePath)) {
3428
+ const dirName = skillsPath.split("/").pop() || skillsPath;
3429
+ try {
3430
+ const skill = await this.#parseSkillFile(skillFilePath, dirName, source);
3431
+ this.#skills.set(skill.name, skill);
3432
+ await this.#indexSkill(skill);
3433
+ } catch (error) {
3434
+ if (error instanceof Error) {
3435
+ console.error(`[WorkspaceSkills] Failed to load skill from ${skillFilePath}:`, error.message);
3436
+ }
3437
+ }
3438
+ return true;
3439
+ }
3440
+ }
3441
+ return false;
3442
+ } catch {
3443
+ return false;
3444
+ }
3445
+ }
2990
3446
  /**
2991
3447
  * Check if any skills path directory has been modified since last discovery.
2992
3448
  * Compares directory mtime to lastDiscoveryTime.
3449
+ * For glob patterns, checks the walk root and expanded directories.
2993
3450
  */
2994
3451
  async #isSkillsPathStale() {
2995
3452
  if (this.#lastDiscoveryTime === 0) {
2996
3453
  return true;
2997
3454
  }
3455
+ if (Date.now() - this.#lastDiscoveryTime < _WorkspaceSkillsImpl.STALENESS_CHECK_COOLDOWN) {
3456
+ return false;
3457
+ }
2998
3458
  for (const skillsPath of this.#resolvedPaths) {
2999
- try {
3000
- const stat3 = await this.#source.stat(skillsPath);
3001
- const mtime = stat3.modifiedAt.getTime();
3002
- if (mtime > this.#lastDiscoveryTime) {
3003
- return true;
3459
+ let pathsToCheck;
3460
+ if (isGlobPattern(skillsPath)) {
3461
+ const now = Date.now();
3462
+ const lastResolved = this.#globResolveTimes.get(skillsPath) ?? 0;
3463
+ if (now - lastResolved > _WorkspaceSkillsImpl.GLOB_RESOLVE_INTERVAL || !this.#globDirCache.has(skillsPath)) {
3464
+ const dirs = await this.#resolveGlobToDirectories(skillsPath);
3465
+ this.#globDirCache.set(skillsPath, dirs);
3466
+ this.#globResolveTimes.set(skillsPath, now);
3004
3467
  }
3005
- const entries = await this.#source.readdir(skillsPath);
3006
- for (const entry of entries) {
3007
- if (entry.type !== "directory") continue;
3008
- const entryPath = this.#joinPath(skillsPath, entry.name);
3009
- try {
3010
- const entryStat = await this.#source.stat(entryPath);
3011
- if (entryStat.modifiedAt.getTime() > this.#lastDiscoveryTime) {
3012
- return true;
3468
+ pathsToCheck = this.#globDirCache.get(skillsPath) ?? [];
3469
+ } else {
3470
+ pathsToCheck = [skillsPath];
3471
+ }
3472
+ for (const pathToCheck of pathsToCheck) {
3473
+ try {
3474
+ const stat3 = await this.#source.stat(pathToCheck);
3475
+ const mtime = stat3.modifiedAt.getTime();
3476
+ if (mtime > this.#lastDiscoveryTime) {
3477
+ return true;
3478
+ }
3479
+ if (stat3.type !== "directory") {
3480
+ continue;
3481
+ }
3482
+ const entries = await this.#source.readdir(pathToCheck);
3483
+ for (const entry of entries) {
3484
+ if (entry.type !== "directory") continue;
3485
+ const entryPath = this.#joinPath(pathToCheck, entry.name);
3486
+ try {
3487
+ const entryStat = await this.#source.stat(entryPath);
3488
+ if (entryStat.modifiedAt.getTime() > this.#lastDiscoveryTime) {
3489
+ return true;
3490
+ }
3491
+ } catch {
3013
3492
  }
3014
- } catch {
3015
3493
  }
3494
+ } catch {
3495
+ continue;
3016
3496
  }
3017
- } catch {
3018
- continue;
3019
3497
  }
3020
3498
  }
3021
3499
  return false;
@@ -3099,7 +3577,7 @@ ${validation.errors.join("\n")}`);
3099
3577
  const entries = await this.#source.readdir(dirPath);
3100
3578
  for (const entry of entries) {
3101
3579
  const entryPath = this.#joinPath(dirPath, entry.name);
3102
- if (entry.type === "directory") {
3580
+ if (entry.type === "directory" && !entry.isSymlink) {
3103
3581
  await this.#walkDirectory(basePath, entryPath, callback, depth + 1, maxDepth);
3104
3582
  } else {
3105
3583
  const relativePath = entryPath.substring(basePath.length + 1);
@@ -3123,6 +3601,30 @@ ${validation.errors.join("\n")}`);
3123
3601
  }
3124
3602
  return parts.join("\n\n");
3125
3603
  }
3604
+ /**
3605
+ * Remove a skill's entries from the search index.
3606
+ */
3607
+ async #removeSkillFromIndex(skill) {
3608
+ if (!this.#searchEngine?.remove) return;
3609
+ const ids = [`skill:${skill.name}:SKILL.md`, ...skill.references.map((r) => `skill:${skill.name}:${r}`)];
3610
+ for (const id of ids) {
3611
+ try {
3612
+ await this.#searchEngine.remove(id);
3613
+ } catch {
3614
+ }
3615
+ }
3616
+ }
3617
+ /**
3618
+ * Infer the ContentSource for a skill path by matching against resolved paths.
3619
+ */
3620
+ #inferSource(skillPath) {
3621
+ for (const rp of this.#resolvedPaths) {
3622
+ if (skillPath === rp || skillPath.startsWith(rp + "/")) {
3623
+ return this.#determineSource(rp);
3624
+ }
3625
+ }
3626
+ return this.#determineSource(skillPath);
3627
+ }
3126
3628
  /**
3127
3629
  * Index a skill for search
3128
3630
  */
@@ -3232,6 +3734,151 @@ ${validation.errors.join("\n")}`);
3232
3734
  return lastSlash > 0 ? path4.substring(0, lastSlash) : "/";
3233
3735
  }
3234
3736
  };
3737
+ function hashContent(content) {
3738
+ if (Buffer.isBuffer(content)) {
3739
+ return crypto.createHash("sha256").update(content).digest("hex");
3740
+ }
3741
+ return crypto.createHash("sha256").update(content, "utf-8").digest("hex");
3742
+ }
3743
+ function detectMimeType(filename) {
3744
+ const ext = filename.slice(filename.lastIndexOf(".")).toLowerCase();
3745
+ const mimeTypes = {
3746
+ ".md": "text/markdown",
3747
+ ".txt": "text/plain",
3748
+ ".json": "application/json",
3749
+ ".yaml": "text/yaml",
3750
+ ".yml": "text/yaml",
3751
+ ".sh": "text/x-shellscript",
3752
+ ".py": "text/x-python",
3753
+ ".js": "text/javascript",
3754
+ ".ts": "text/typescript",
3755
+ ".html": "text/html",
3756
+ ".css": "text/css",
3757
+ ".png": "image/png",
3758
+ ".jpg": "image/jpeg",
3759
+ ".jpeg": "image/jpeg",
3760
+ ".svg": "image/svg+xml"
3761
+ };
3762
+ return mimeTypes[ext];
3763
+ }
3764
+ function isBinaryMimeType(mimeType) {
3765
+ if (!mimeType) return false;
3766
+ if (mimeType.startsWith("text/")) return false;
3767
+ if (mimeType === "application/json") return false;
3768
+ if (mimeType === "image/svg+xml") return false;
3769
+ return true;
3770
+ }
3771
+ async function walkSkillDirectory(source, basePath, currentPath = basePath) {
3772
+ const entries = await source.readdir(currentPath);
3773
+ const files = [];
3774
+ for (const entry of entries) {
3775
+ const entryPath = joinPath(currentPath, entry.name);
3776
+ if (entry.type === "directory") {
3777
+ const subFiles = await walkSkillDirectory(source, basePath, entryPath);
3778
+ files.push(...subFiles);
3779
+ } else {
3780
+ const rawContent = await source.readFile(entryPath);
3781
+ const relativePath = entryPath.substring(basePath.length + 1);
3782
+ const mimeType = detectMimeType(entry.name);
3783
+ const isBinary = isBinaryMimeType(mimeType);
3784
+ if (isBinary) {
3785
+ const buf = Buffer.isBuffer(rawContent) ? rawContent : Buffer.from(rawContent, "utf-8");
3786
+ files.push({ path: relativePath, content: buf, isBinary: true });
3787
+ } else {
3788
+ const content = typeof rawContent === "string" ? rawContent : rawContent.toString("utf-8");
3789
+ files.push({ path: relativePath, content, isBinary: false });
3790
+ }
3791
+ }
3792
+ }
3793
+ return files;
3794
+ }
3795
+ function joinPath(...segments) {
3796
+ return segments.map((seg, i) => {
3797
+ if (i === 0) return seg.replace(/\/+$/, "");
3798
+ return seg.replace(/^\/+|\/+$/g, "");
3799
+ }).filter(Boolean).join("/");
3800
+ }
3801
+ function collectSubdirPaths(allPaths, subdir) {
3802
+ const prefix = subdir + "/";
3803
+ return allPaths.filter((p) => p.startsWith(prefix)).map((p) => p.substring(prefix.length));
3804
+ }
3805
+ async function collectSkillForPublish(source, skillPath) {
3806
+ const files = await walkSkillDirectory(source, skillPath);
3807
+ const treeEntries = {};
3808
+ const blobMap = /* @__PURE__ */ new Map();
3809
+ const now = /* @__PURE__ */ new Date();
3810
+ for (const file of files) {
3811
+ const hash = hashContent(file.content);
3812
+ const mimeType = detectMimeType(file.path);
3813
+ if (file.isBinary) {
3814
+ const buf = Buffer.isBuffer(file.content) ? file.content : Buffer.from(file.content);
3815
+ const size = buf.length;
3816
+ const base64Content = buf.toString("base64");
3817
+ treeEntries[file.path] = {
3818
+ blobHash: hash,
3819
+ size,
3820
+ mimeType,
3821
+ encoding: "base64"
3822
+ };
3823
+ if (!blobMap.has(hash)) {
3824
+ blobMap.set(hash, {
3825
+ hash,
3826
+ content: base64Content,
3827
+ size,
3828
+ mimeType,
3829
+ createdAt: now
3830
+ });
3831
+ }
3832
+ } else {
3833
+ const content = file.content;
3834
+ const size = Buffer.byteLength(content, "utf-8");
3835
+ treeEntries[file.path] = {
3836
+ blobHash: hash,
3837
+ size,
3838
+ mimeType
3839
+ };
3840
+ if (!blobMap.has(hash)) {
3841
+ blobMap.set(hash, {
3842
+ hash,
3843
+ content,
3844
+ size,
3845
+ mimeType,
3846
+ createdAt: now
3847
+ });
3848
+ }
3849
+ }
3850
+ }
3851
+ const tree = { entries: treeEntries };
3852
+ const blobs = Array.from(blobMap.values());
3853
+ const skillMdFile = files.find((f) => f.path === "SKILL.md");
3854
+ if (!skillMdFile) {
3855
+ throw new Error(`SKILL.md not found in ${skillPath}`);
3856
+ }
3857
+ const parsed = matter__default.default(skillMdFile.content);
3858
+ const frontmatter = parsed.data;
3859
+ const instructions = parsed.content.trim();
3860
+ const allPaths = files.map((f) => f.path);
3861
+ const references = collectSubdirPaths(allPaths, "references");
3862
+ const scripts = collectSubdirPaths(allPaths, "scripts");
3863
+ const assets = collectSubdirPaths(allPaths, "assets");
3864
+ const snapshot = {
3865
+ name: frontmatter.name,
3866
+ description: frontmatter.description,
3867
+ instructions,
3868
+ license: frontmatter.license,
3869
+ compatibility: frontmatter.compatibility,
3870
+ metadata: frontmatter.metadata,
3871
+ ...references.length > 0 ? { references } : {},
3872
+ ...scripts.length > 0 ? { scripts } : {},
3873
+ ...assets.length > 0 ? { assets } : {}
3874
+ };
3875
+ return { snapshot, tree, blobs };
3876
+ }
3877
+ async function publishSkillFromSource(source, skillPath, blobStore) {
3878
+ const result = await collectSkillForPublish(source, skillPath);
3879
+ await blobStore.putMany(result.blobs);
3880
+ return result;
3881
+ }
3235
3882
 
3236
3883
  // src/workspace/workspace.ts
3237
3884
  var Workspace = class {
@@ -3316,12 +3963,18 @@ var Workspace = class {
3316
3963
  }
3317
3964
  /**
3318
3965
  * The filesystem provider (if configured).
3966
+ *
3967
+ * Returns the concrete type you passed to the constructor.
3968
+ * When `mounts` is used instead of `filesystem`, returns `CompositeFilesystem`
3969
+ * parameterized with the concrete mount types.
3319
3970
  */
3320
3971
  get filesystem() {
3321
3972
  return this._fs;
3322
3973
  }
3323
3974
  /**
3324
3975
  * The sandbox provider (if configured).
3976
+ *
3977
+ * Returns the concrete type you passed to the constructor.
3325
3978
  */
3326
3979
  get sandbox() {
3327
3980
  return this._sandbox;
@@ -3351,7 +4004,7 @@ var Workspace = class {
3351
4004
  return void 0;
3352
4005
  }
3353
4006
  if (!this._skills) {
3354
- const source = this._fs ?? new LocalSkillSource();
4007
+ const source = this._config.skillSource ?? this._fs ?? new LocalSkillSource();
3355
4008
  this._skills = new WorkspaceSkillsImpl({
3356
4009
  source,
3357
4010
  skills: this._config.skills,
@@ -3429,31 +4082,51 @@ var Workspace = class {
3429
4082
  /**
3430
4083
  * Rebuild the search index from filesystem paths.
3431
4084
  * Used internally for auto-indexing on init.
4085
+ *
4086
+ * Paths can be plain directories (e.g., '/docs') or glob patterns
4087
+ * (e.g., '/docs/**\/*.md'). Glob patterns are resolved to a walk root
4088
+ * via extractGlobBase, then files are filtered by the pattern.
3432
4089
  */
3433
4090
  async rebuildSearchIndex(paths) {
3434
4091
  if (!this._searchEngine || !this._fs || paths.length === 0) {
3435
4092
  return;
3436
4093
  }
3437
4094
  this._searchEngine.clear();
3438
- for (const basePath of paths) {
4095
+ for (const pathOrGlob of paths) {
3439
4096
  try {
3440
- const files = await this.getAllFiles(basePath);
3441
- for (const filePath of files) {
3442
- try {
3443
- const content = await this._fs.readFile(filePath, { encoding: "utf-8" });
3444
- await this._searchEngine.index({
3445
- id: filePath,
3446
- content
3447
- });
3448
- } catch {
4097
+ if (isGlobPattern(pathOrGlob)) {
4098
+ const walkRoot = extractGlobBase(pathOrGlob);
4099
+ const matcher = createGlobMatcher(pathOrGlob);
4100
+ const files = await this.getAllFiles(walkRoot);
4101
+ for (const filePath of files) {
4102
+ if (!matcher(filePath)) continue;
4103
+ await this.indexFileForSearch(filePath);
4104
+ }
4105
+ } else {
4106
+ const files = await this.getAllFiles(pathOrGlob);
4107
+ for (const filePath of files) {
4108
+ await this.indexFileForSearch(filePath);
3449
4109
  }
3450
4110
  }
3451
4111
  } catch {
3452
4112
  }
3453
4113
  }
3454
4114
  }
3455
- async getAllFiles(dir) {
3456
- if (!this._fs) return [];
4115
+ /**
4116
+ * Index a single file for search. Skips files that can't be read as text.
4117
+ */
4118
+ async indexFileForSearch(filePath) {
4119
+ try {
4120
+ const content = await this._fs.readFile(filePath, { encoding: "utf-8" });
4121
+ await this._searchEngine.index({
4122
+ id: filePath,
4123
+ content
4124
+ });
4125
+ } catch {
4126
+ }
4127
+ }
4128
+ async getAllFiles(dir, depth = 0, maxDepth = 10) {
4129
+ if (!this._fs || depth >= maxDepth) return [];
3457
4130
  const files = [];
3458
4131
  const entries = await this._fs.readdir(dir);
3459
4132
  for (const entry of entries) {
@@ -3461,7 +4134,7 @@ var Workspace = class {
3461
4134
  if (entry.type === "file") {
3462
4135
  files.push(fullPath);
3463
4136
  } else if (entry.type === "directory" && !entry.isSymlink) {
3464
- files.push(...await this.getAllFiles(fullPath));
4137
+ files.push(...await this.getAllFiles(fullPath, depth + 1, maxDepth));
3465
4138
  }
3466
4139
  }
3467
4140
  return files;
@@ -3941,7 +4614,10 @@ var LocalSandbox = class extends MastraSandbox {
3941
4614
  * Status management is handled by the base class.
3942
4615
  */
3943
4616
  async start() {
3944
- this.logger.debug("Starting sandbox", { workingDirectory: this._workingDirectory, isolation: this._isolation });
4617
+ this.logger.debug("[LocalSandbox] Starting sandbox", {
4618
+ workingDirectory: this._workingDirectory,
4619
+ isolation: this._isolation
4620
+ });
3945
4621
  await fs2__namespace.mkdir(this.workingDirectory, { recursive: true });
3946
4622
  if (this._isolation === "seatbelt") {
3947
4623
  const userProvidedPath = this._nativeSandboxConfig.seatbeltProfilePath;
@@ -3967,14 +4643,14 @@ var LocalSandbox = class extends MastraSandbox {
3967
4643
  await fs2__namespace.writeFile(this._seatbeltProfilePath, this._seatbeltProfile, "utf-8");
3968
4644
  }
3969
4645
  }
3970
- this.logger.debug("Sandbox started", { workingDirectory: this._workingDirectory });
4646
+ this.logger.debug("[LocalSandbox] Sandbox started", { workingDirectory: this._workingDirectory });
3971
4647
  }
3972
4648
  /**
3973
4649
  * Stop the local sandbox.
3974
4650
  * Status management is handled by the base class.
3975
4651
  */
3976
4652
  async stop() {
3977
- this.logger.debug("Stopping sandbox", { workingDirectory: this._workingDirectory });
4653
+ this.logger.debug("[LocalSandbox] Stopping sandbox", { workingDirectory: this._workingDirectory });
3978
4654
  }
3979
4655
  /**
3980
4656
  * Destroy the local sandbox and clean up resources.
@@ -3982,7 +4658,7 @@ var LocalSandbox = class extends MastraSandbox {
3982
4658
  * Status management is handled by the base class.
3983
4659
  */
3984
4660
  async destroy() {
3985
- this.logger.debug("Destroying sandbox", { workingDirectory: this._workingDirectory });
4661
+ this.logger.debug("[LocalSandbox] Destroying sandbox", { workingDirectory: this._workingDirectory });
3986
4662
  if (this._seatbeltProfilePath && !this._userProvidedProfilePath) {
3987
4663
  try {
3988
4664
  await fs2__namespace.unlink(this._seatbeltProfilePath);
@@ -4048,7 +4724,7 @@ var LocalSandbox = class extends MastraSandbox {
4048
4724
  });
4049
4725
  }
4050
4726
  async executeCommand(command, args = [], options = {}) {
4051
- this.logger.debug("Executing command", { command, args, cwd: options.cwd ?? this.workingDirectory });
4727
+ this.logger.debug("[LocalSandbox] Executing command", { command, args, cwd: options.cwd ?? this.workingDirectory });
4052
4728
  await this.ensureRunning();
4053
4729
  const startTime = Date.now();
4054
4730
  const wrapped = this.wrapCommandForIsolation(command, args);
@@ -4067,7 +4743,7 @@ var LocalSandbox = class extends MastraSandbox {
4067
4743
  exitCode: result.exitCode,
4068
4744
  executionTimeMs: Date.now() - startTime
4069
4745
  };
4070
- this.logger.info("Command completed", {
4746
+ this.logger.debug("[LocalSandbox] Command completed", {
4071
4747
  command,
4072
4748
  exitCode: commandResult.exitCode,
4073
4749
  executionTimeMs: commandResult.executionTimeMs
@@ -4075,7 +4751,7 @@ var LocalSandbox = class extends MastraSandbox {
4075
4751
  return commandResult;
4076
4752
  } catch (error) {
4077
4753
  const executionTimeMs = Date.now() - startTime;
4078
- this.logger.error("Command failed", { command, error, executionTimeMs });
4754
+ this.logger.error("[LocalSandbox] Command failed", { command, error, executionTimeMs });
4079
4755
  return {
4080
4756
  success: false,
4081
4757
  stdout: "",
@@ -4097,7 +4773,8 @@ var WORKSPACE_TOOLS = {
4097
4773
  LIST_FILES: `${WORKSPACE_TOOLS_PREFIX}_list_files`,
4098
4774
  DELETE: `${WORKSPACE_TOOLS_PREFIX}_delete`,
4099
4775
  FILE_STAT: `${WORKSPACE_TOOLS_PREFIX}_file_stat`,
4100
- MKDIR: `${WORKSPACE_TOOLS_PREFIX}_mkdir`
4776
+ MKDIR: `${WORKSPACE_TOOLS_PREFIX}_mkdir`,
4777
+ GREP: `${WORKSPACE_TOOLS_PREFIX}_grep`
4101
4778
  },
4102
4779
  SANDBOX: {
4103
4780
  EXECUTE_COMMAND: `${WORKSPACE_TOOLS_PREFIX}_execute_command`
@@ -4108,6 +4785,357 @@ var WORKSPACE_TOOLS = {
4108
4785
  }
4109
4786
  };
4110
4787
 
4788
+ // src/workspace/tools/helpers.ts
4789
+ function requireWorkspace(context) {
4790
+ if (!context?.workspace) {
4791
+ throw new WorkspaceNotAvailableError();
4792
+ }
4793
+ return context.workspace;
4794
+ }
4795
+ function requireFilesystem(context) {
4796
+ const workspace = requireWorkspace(context);
4797
+ if (!workspace.filesystem) {
4798
+ throw new FilesystemNotAvailableError();
4799
+ }
4800
+ return { workspace, filesystem: workspace.filesystem };
4801
+ }
4802
+ function requireSandbox(context) {
4803
+ const workspace = requireWorkspace(context);
4804
+ if (!workspace.sandbox) {
4805
+ throw new SandboxNotAvailableError();
4806
+ }
4807
+ return { workspace, sandbox: workspace.sandbox };
4808
+ }
4809
+ async function emitWorkspaceMetadata(context, toolName) {
4810
+ const workspace = requireWorkspace(context);
4811
+ const info = await workspace.getInfo();
4812
+ const toolCallId = context?.agent?.toolCallId;
4813
+ await context?.writer?.custom({
4814
+ type: "data-workspace-metadata",
4815
+ data: { toolName, toolCallId, ...info }
4816
+ });
4817
+ }
4818
+
4819
+ // src/workspace/tools/delete-file.ts
4820
+ var deleteFileTool = chunkEAZ6YDCQ_cjs.createTool({
4821
+ id: WORKSPACE_TOOLS.FILESYSTEM.DELETE,
4822
+ description: "Delete a file or directory from the workspace filesystem",
4823
+ inputSchema: zod.z.object({
4824
+ path: zod.z.string().describe("The path to the file or directory to delete"),
4825
+ recursive: zod.z.boolean().optional().default(false).describe("If true, delete directories and their contents recursively. Required for non-empty directories.")
4826
+ }),
4827
+ execute: async ({ path: path4, recursive }, context) => {
4828
+ const { filesystem } = requireFilesystem(context);
4829
+ await emitWorkspaceMetadata(context, WORKSPACE_TOOLS.FILESYSTEM.DELETE);
4830
+ if (filesystem.readOnly) {
4831
+ throw new WorkspaceReadOnlyError("delete");
4832
+ }
4833
+ const stat3 = await filesystem.stat(path4);
4834
+ if (stat3.type === "directory") {
4835
+ await filesystem.rmdir(path4, { recursive, force: recursive });
4836
+ } else {
4837
+ await filesystem.deleteFile(path4);
4838
+ }
4839
+ return `Deleted ${path4}`;
4840
+ }
4841
+ });
4842
+ var editFileTool = chunkEAZ6YDCQ_cjs.createTool({
4843
+ id: WORKSPACE_TOOLS.FILESYSTEM.EDIT_FILE,
4844
+ description: `Edit a file by replacing specific text. The old_string must match exactly and be unique in the file.
4845
+
4846
+ Usage:
4847
+ - Read the file first to get the exact text to replace.
4848
+ - 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.
4849
+ - Include enough surrounding context (multiple lines) to make old_string unique. If it still isn't unique, include more lines.
4850
+ - Use replace_all only when intentionally replacing all occurrences.`,
4851
+ inputSchema: zod.z.object({
4852
+ path: zod.z.string().describe("The path to the file to edit"),
4853
+ old_string: zod.z.string().describe("The exact text to find and replace. Must be unique in the file."),
4854
+ new_string: zod.z.string().describe("The text to replace old_string with"),
4855
+ replace_all: zod.z.boolean().optional().default(false).describe("If true, replace all occurrences. If false (default), old_string must be unique.")
4856
+ }),
4857
+ execute: async ({ path: path4, old_string, new_string, replace_all }, context) => {
4858
+ const { filesystem } = requireFilesystem(context);
4859
+ await emitWorkspaceMetadata(context, WORKSPACE_TOOLS.FILESYSTEM.EDIT_FILE);
4860
+ if (filesystem.readOnly) {
4861
+ throw new WorkspaceReadOnlyError("edit_file");
4862
+ }
4863
+ try {
4864
+ const content = await filesystem.readFile(path4, { encoding: "utf-8" });
4865
+ if (typeof content !== "string") {
4866
+ return `Cannot edit binary files. Use ${WORKSPACE_TOOLS.FILESYSTEM.WRITE_FILE} instead.`;
4867
+ }
4868
+ const result = replaceString(content, old_string, new_string, replace_all);
4869
+ await filesystem.writeFile(path4, result.content, { overwrite: true });
4870
+ return `Replaced ${result.replacements} occurrence${result.replacements !== 1 ? "s" : ""} in ${path4}`;
4871
+ } catch (error) {
4872
+ if (error instanceof StringNotFoundError) {
4873
+ return error.message;
4874
+ }
4875
+ if (error instanceof StringNotUniqueError) {
4876
+ return error.message;
4877
+ }
4878
+ throw error;
4879
+ }
4880
+ }
4881
+ });
4882
+ var executeCommandTool = chunkEAZ6YDCQ_cjs.createTool({
4883
+ id: WORKSPACE_TOOLS.SANDBOX.EXECUTE_COMMAND,
4884
+ description: `Execute a shell command in the workspace sandbox.
4885
+
4886
+ Usage:
4887
+ - Verify parent directories exist before running commands that create files or directories.
4888
+ - Always quote file paths that contain spaces (e.g., cd "/path/with spaces").
4889
+ - Use the timeout parameter to limit execution time. Behavior when omitted depends on the sandbox provider.
4890
+ - Optionally use cwd to override the working directory. Commands run from the sandbox default if omitted.`,
4891
+ inputSchema: zod.z.object({
4892
+ command: zod.z.string().describe('The command to execute (e.g., "ls", "npm", "python")'),
4893
+ args: zod.z.array(zod.z.string()).nullish().default([]).describe("Arguments to pass to the command"),
4894
+ timeout: zod.z.number().nullish().describe("Maximum execution time in milliseconds. Example: 60000 for 1 minute."),
4895
+ cwd: zod.z.string().nullish().describe("Working directory for the command")
4896
+ }),
4897
+ execute: async ({ command, args, timeout, cwd }, context) => {
4898
+ const { sandbox } = requireSandbox(context);
4899
+ if (!sandbox.executeCommand) {
4900
+ throw new SandboxFeatureNotSupportedError("executeCommand");
4901
+ }
4902
+ await emitWorkspaceMetadata(context, WORKSPACE_TOOLS.SANDBOX.EXECUTE_COMMAND);
4903
+ const toolCallId = context?.agent?.toolCallId;
4904
+ const startedAt = Date.now();
4905
+ let stdout = "";
4906
+ let stderr = "";
4907
+ try {
4908
+ const result = await sandbox.executeCommand(command, args ?? [], {
4909
+ timeout: timeout ?? void 0,
4910
+ cwd: cwd ?? void 0,
4911
+ onStdout: async (data) => {
4912
+ stdout += data;
4913
+ await context?.writer?.custom({
4914
+ type: "data-sandbox-stdout",
4915
+ data: { output: data, timestamp: Date.now(), toolCallId }
4916
+ });
4917
+ },
4918
+ onStderr: async (data) => {
4919
+ stderr += data;
4920
+ await context?.writer?.custom({
4921
+ type: "data-sandbox-stderr",
4922
+ data: { output: data, timestamp: Date.now(), toolCallId }
4923
+ });
4924
+ }
4925
+ });
4926
+ await context?.writer?.custom({
4927
+ type: "data-sandbox-exit",
4928
+ data: {
4929
+ exitCode: result.exitCode,
4930
+ success: result.success,
4931
+ executionTimeMs: result.executionTimeMs,
4932
+ toolCallId
4933
+ }
4934
+ });
4935
+ if (!result.success) {
4936
+ const parts = [result.stdout, result.stderr].filter(Boolean);
4937
+ parts.push(`Exit code: ${result.exitCode}`);
4938
+ return parts.join("\n");
4939
+ }
4940
+ return result.stdout || "(no output)";
4941
+ } catch (error) {
4942
+ await context?.writer?.custom({
4943
+ type: "data-sandbox-exit",
4944
+ data: {
4945
+ exitCode: -1,
4946
+ success: false,
4947
+ executionTimeMs: Date.now() - startedAt,
4948
+ toolCallId
4949
+ }
4950
+ });
4951
+ const parts = [stdout, stderr].filter(Boolean);
4952
+ const errorMessage = error instanceof Error ? error.message : String(error);
4953
+ parts.push(`Error: ${errorMessage}`);
4954
+ return parts.join("\n");
4955
+ }
4956
+ }
4957
+ });
4958
+ var fileStatTool = chunkEAZ6YDCQ_cjs.createTool({
4959
+ id: WORKSPACE_TOOLS.FILESYSTEM.FILE_STAT,
4960
+ description: "Get file or directory metadata from the workspace. Returns existence, type, size, and modification time.",
4961
+ inputSchema: zod.z.object({
4962
+ path: zod.z.string().describe("The path to check")
4963
+ }),
4964
+ execute: async ({ path: path4 }, context) => {
4965
+ const { filesystem } = requireFilesystem(context);
4966
+ await emitWorkspaceMetadata(context, WORKSPACE_TOOLS.FILESYSTEM.FILE_STAT);
4967
+ try {
4968
+ const stat3 = await filesystem.stat(path4);
4969
+ const modifiedAt = stat3.modifiedAt.toISOString();
4970
+ const parts = [`${path4}`, `Type: ${stat3.type}`];
4971
+ if (stat3.size !== void 0) parts.push(`Size: ${stat3.size} bytes`);
4972
+ parts.push(`Modified: ${modifiedAt}`);
4973
+ return parts.join(" ");
4974
+ } catch (error) {
4975
+ if (error instanceof FileNotFoundError) {
4976
+ return `${path4}: not found`;
4977
+ }
4978
+ throw error;
4979
+ }
4980
+ }
4981
+ });
4982
+ var grepTool = chunkEAZ6YDCQ_cjs.createTool({
4983
+ id: WORKSPACE_TOOLS.FILESYSTEM.GREP,
4984
+ description: `Search file contents using a regex pattern. Walks the filesystem and returns matching lines with file paths and line numbers.
4985
+
4986
+ Usage:
4987
+ - Basic search: { pattern: "TODO" }
4988
+ - Regex: { pattern: "function\\s+\\w+\\(" }
4989
+ - Multiple terms: { pattern: "TODO|FIXME|HACK" }
4990
+ - Case-insensitive: { pattern: "error", caseSensitive: false }
4991
+ - Search in directory: { pattern: "import", path: "./src" }
4992
+ - Filter by glob: { pattern: "import", path: "**/*.ts" }
4993
+ - Combined path + glob: { pattern: "import", path: "src/**/*.ts" }
4994
+ - Multiple file types: { pattern: "import", path: "**/*.{ts,tsx,js}" }
4995
+ - Multiple directories: { pattern: "TODO", path: "{src,lib}/**/*.ts" }
4996
+ - With context: { pattern: "function", contextLines: 2 }`,
4997
+ inputSchema: zod.z.object({
4998
+ pattern: zod.z.string().describe("Regex pattern to search for"),
4999
+ path: zod.z.string().optional().default("./").describe(
5000
+ '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.'
5001
+ ),
5002
+ contextLines: zod.z.number().optional().default(0).describe("Number of lines of context to include before and after each match (default: 0)"),
5003
+ maxCount: zod.z.number().optional().describe(
5004
+ "Maximum matches per file. Moves on to the next file after this many matches. Similar to grep -m flag."
5005
+ ),
5006
+ caseSensitive: zod.z.boolean().optional().default(true).describe("Whether the search is case-sensitive (default: true)"),
5007
+ includeHidden: zod.z.boolean().optional().default(false).describe('Include hidden files and directories (names starting with ".") in the search (default: false)')
5008
+ }),
5009
+ execute: async ({ pattern, path: inputPath = "./", contextLines = 0, maxCount, caseSensitive = true, includeHidden = false }, context) => {
5010
+ const { filesystem } = requireFilesystem(context);
5011
+ await emitWorkspaceMetadata(context, WORKSPACE_TOOLS.FILESYSTEM.GREP);
5012
+ const MAX_PATTERN_LENGTH = 1e3;
5013
+ if (pattern.length > MAX_PATTERN_LENGTH) {
5014
+ return `Error: Pattern too long (${pattern.length} chars, max ${MAX_PATTERN_LENGTH}). Use a shorter pattern.`;
5015
+ }
5016
+ let regex;
5017
+ try {
5018
+ regex = new RegExp(pattern, caseSensitive ? "g" : "gi");
5019
+ } catch (e) {
5020
+ return `Error: Invalid regex pattern: ${e.message}`;
5021
+ }
5022
+ let searchPath;
5023
+ let globMatcher;
5024
+ if (isGlobPattern(inputPath)) {
5025
+ searchPath = extractGlobBase(inputPath);
5026
+ globMatcher = createGlobMatcher(inputPath, { dot: includeHidden });
5027
+ } else {
5028
+ searchPath = inputPath;
5029
+ }
5030
+ let filePaths;
5031
+ try {
5032
+ const stat3 = await filesystem.stat(searchPath);
5033
+ if (stat3.type === "file") {
5034
+ filePaths = isTextFile(searchPath) ? [searchPath] : [];
5035
+ } else {
5036
+ const collectFiles = async (dir) => {
5037
+ const files = [];
5038
+ let entries;
5039
+ try {
5040
+ entries = await filesystem.readdir(dir);
5041
+ } catch {
5042
+ return files;
5043
+ }
5044
+ for (const entry of entries) {
5045
+ if (!includeHidden && entry.name.startsWith(".")) continue;
5046
+ const fullPath = dir.endsWith("/") ? `${dir}${entry.name}` : `${dir}/${entry.name}`;
5047
+ if (entry.type === "file") {
5048
+ if (!isTextFile(entry.name)) continue;
5049
+ if (globMatcher && !globMatcher(fullPath)) continue;
5050
+ files.push(fullPath);
5051
+ } else if (entry.type === "directory" && !entry.isSymlink) {
5052
+ files.push(...await collectFiles(fullPath));
5053
+ }
5054
+ }
5055
+ return files;
5056
+ };
5057
+ filePaths = await collectFiles(searchPath);
5058
+ }
5059
+ } catch {
5060
+ filePaths = [];
5061
+ }
5062
+ const outputLines = [];
5063
+ const filesWithMatches = /* @__PURE__ */ new Set();
5064
+ let totalMatchCount = 0;
5065
+ let truncated = false;
5066
+ const MAX_LINE_LENGTH = 500;
5067
+ const GLOBAL_CAP = 1e3;
5068
+ for (const filePath of filePaths) {
5069
+ if (truncated) break;
5070
+ let content;
5071
+ try {
5072
+ const raw = await filesystem.readFile(filePath, { encoding: "utf-8" });
5073
+ if (typeof raw !== "string") continue;
5074
+ content = raw;
5075
+ } catch {
5076
+ continue;
5077
+ }
5078
+ const lines = content.split("\n");
5079
+ let fileMatchCount = 0;
5080
+ for (let i = 0; i < lines.length; i++) {
5081
+ const currentLine = lines[i];
5082
+ regex.lastIndex = 0;
5083
+ const lineMatch = regex.exec(currentLine);
5084
+ if (!lineMatch) continue;
5085
+ filesWithMatches.add(filePath);
5086
+ let lineContent = currentLine;
5087
+ if (lineContent.length > MAX_LINE_LENGTH) {
5088
+ lineContent = lineContent.slice(0, MAX_LINE_LENGTH) + "...";
5089
+ }
5090
+ if (contextLines > 0) {
5091
+ const beforeStart = Math.max(0, i - contextLines);
5092
+ for (let b = beforeStart; b < i; b++) {
5093
+ outputLines.push(`${filePath}:${b + 1}- ${lines[b]}`);
5094
+ }
5095
+ }
5096
+ outputLines.push(`${filePath}:${i + 1}:${lineMatch.index + 1}: ${lineContent}`);
5097
+ if (contextLines > 0) {
5098
+ const afterEnd = Math.min(lines.length - 1, i + contextLines);
5099
+ for (let a = i + 1; a <= afterEnd; a++) {
5100
+ outputLines.push(`${filePath}:${a + 1}- ${lines[a]}`);
5101
+ }
5102
+ outputLines.push("--");
5103
+ }
5104
+ totalMatchCount++;
5105
+ fileMatchCount++;
5106
+ if (maxCount !== void 0 && fileMatchCount >= maxCount) break;
5107
+ if (totalMatchCount >= GLOBAL_CAP) {
5108
+ truncated = true;
5109
+ break;
5110
+ }
5111
+ }
5112
+ }
5113
+ outputLines.push("---");
5114
+ const parts = [`${totalMatchCount} match${totalMatchCount !== 1 ? "es" : ""}`];
5115
+ parts.push(`across ${filesWithMatches.size} file${filesWithMatches.size !== 1 ? "s" : ""}`);
5116
+ if (truncated) {
5117
+ parts.push(`(truncated at ${GLOBAL_CAP})`);
5118
+ }
5119
+ outputLines.push(parts.join(" "));
5120
+ return outputLines.join("\n");
5121
+ }
5122
+ });
5123
+ var indexContentTool = chunkEAZ6YDCQ_cjs.createTool({
5124
+ id: WORKSPACE_TOOLS.SEARCH.INDEX,
5125
+ description: "Index content for search. The path becomes the document ID in search results.",
5126
+ inputSchema: zod.z.object({
5127
+ path: zod.z.string().describe("The document ID/path for search results"),
5128
+ content: zod.z.string().describe("The text content to index"),
5129
+ metadata: zod.z.record(zod.z.unknown()).optional().describe("Optional metadata to store with the document")
5130
+ }),
5131
+ execute: async ({ path: path4, content, metadata }, context) => {
5132
+ const workspace = requireWorkspace(context);
5133
+ await emitWorkspaceMetadata(context, WORKSPACE_TOOLS.SEARCH.INDEX);
5134
+ await workspace.index(path4, content, { metadata });
5135
+ return `Indexed ${path4}`;
5136
+ }
5137
+ });
5138
+
4111
5139
  // src/workspace/tools/tree-formatter.ts
4112
5140
  var BRANCH = "\u251C\u2500\u2500 ";
4113
5141
  var LAST_BRANCH = "\u2514\u2500\u2500 ";
@@ -4119,6 +5147,12 @@ async function formatAsTree(fs5, path4, options) {
4119
5147
  const dirsOnly = options?.dirsOnly ?? false;
4120
5148
  const exclude = options?.exclude;
4121
5149
  const extension = options?.extension;
5150
+ const pattern = options?.pattern;
5151
+ let globMatcher;
5152
+ if (pattern) {
5153
+ const patterns = Array.isArray(pattern) ? pattern : [pattern];
5154
+ globMatcher = createGlobMatcher(patterns, { dot: showHidden });
5155
+ }
4122
5156
  const lines = ["."];
4123
5157
  let dirCount = 0;
4124
5158
  let fileCount = 0;
@@ -4144,7 +5178,7 @@ async function formatAsTree(fs5, path4, options) {
4144
5178
  if (exclude) {
4145
5179
  const patterns = Array.isArray(exclude) ? exclude : [exclude];
4146
5180
  filtered = filtered.filter((e) => {
4147
- return !patterns.some((pattern) => e.name.includes(pattern));
5181
+ return !patterns.some((pattern2) => e.name.includes(pattern2));
4148
5182
  });
4149
5183
  }
4150
5184
  if (dirsOnly) {
@@ -4160,6 +5194,20 @@ async function formatAsTree(fs5, path4, options) {
4160
5194
  });
4161
5195
  });
4162
5196
  }
5197
+ if (globMatcher && !dirsOnly) {
5198
+ filtered = filtered.filter((e) => {
5199
+ if (e.type === "directory") return true;
5200
+ const entryPath = currentPath === path4 ? e.name : `${currentPath === "/" ? "" : currentPath}/${e.name}`;
5201
+ let relativePath;
5202
+ if (path4 === "/" || path4 === "") {
5203
+ relativePath = entryPath.startsWith("/") ? entryPath.slice(1) : entryPath;
5204
+ } else {
5205
+ relativePath = entryPath.startsWith(path4 + "/") ? entryPath.slice(path4.length + 1) : entryPath;
5206
+ if (!relativePath) relativePath = entryPath;
5207
+ }
5208
+ return globMatcher(relativePath);
5209
+ });
5210
+ }
4163
5211
  filtered.sort((a, b) => {
4164
5212
  if (a.type === "directory" && b.type !== "directory") return -1;
4165
5213
  if (a.type !== "directory" && b.type === "directory") return 1;
@@ -4175,7 +5223,7 @@ async function formatAsTree(fs5, path4, options) {
4175
5223
  if (entry.type === "directory") {
4176
5224
  dirCount++;
4177
5225
  if (!entry.isSymlink) {
4178
- const childPath = joinPath(currentPath, entry.name);
5226
+ const childPath = joinPath2(currentPath, entry.name);
4179
5227
  await buildTree(childPath, childPrefix, depth + 1);
4180
5228
  }
4181
5229
  } else {
@@ -4198,13 +5246,159 @@ async function formatAsTree(fs5, path4, options) {
4198
5246
  truncated
4199
5247
  };
4200
5248
  }
4201
- function joinPath(base, name) {
5249
+ function joinPath2(base, name) {
4202
5250
  if (base === "/" || base === "") {
4203
5251
  return `/${name}`;
4204
5252
  }
4205
5253
  return `${base}/${name}`;
4206
5254
  }
4207
5255
 
5256
+ // src/workspace/tools/list-files.ts
5257
+ var listFilesTool = chunkEAZ6YDCQ_cjs.createTool({
5258
+ id: WORKSPACE_TOOLS.FILESYSTEM.LIST_FILES,
5259
+ description: `List files and directories in the workspace filesystem.
5260
+ Returns a tree-style view (like the Unix "tree" command) for easy visualization.
5261
+ The output is displayed to the user as a tree-like structure in the tool result.
5262
+ Options mirror common tree command flags for familiarity.
5263
+
5264
+ Examples:
5265
+ - List root: { path: "./" }
5266
+ - Deep listing: { path: "./src", maxDepth: 5 }
5267
+ - Directories only: { path: "./", dirsOnly: true }
5268
+ - Exclude node_modules: { path: "./", exclude: "node_modules" }
5269
+ - Find TypeScript files: { path: "./src", pattern: "**/*.ts" }
5270
+ - Find config files: { path: "./", pattern: "*.config.{js,ts}" }
5271
+ - Multiple patterns: { path: "./", pattern: ["**/*.ts", "**/*.tsx"] }`,
5272
+ inputSchema: zod.z.object({
5273
+ path: zod.z.string().default("./").describe("Directory path to list"),
5274
+ maxDepth: zod.z.number().optional().default(3).describe("Maximum depth to descend (default: 3). Similar to tree -L flag."),
5275
+ showHidden: zod.z.boolean().optional().default(false).describe('Show hidden files starting with "." (default: false). Similar to tree -a flag.'),
5276
+ dirsOnly: zod.z.boolean().optional().default(false).describe("List directories only, no files (default: false). Similar to tree -d flag."),
5277
+ exclude: zod.z.string().optional().describe('Pattern to exclude (e.g., "node_modules"). Similar to tree -I flag.'),
5278
+ extension: zod.z.string().optional().describe('Filter by file extension (e.g., ".ts"). Similar to tree -P flag.'),
5279
+ pattern: zod.z.union([zod.z.string(), zod.z.array(zod.z.string())]).optional().describe(
5280
+ 'Glob pattern(s) to filter files. Examples: "**/*.ts", "src/**/*.test.ts", "*.config.{js,ts}". Directories always pass through.'
5281
+ )
5282
+ }),
5283
+ execute: async ({ path: path4 = "./", maxDepth = 3, showHidden, dirsOnly, exclude, extension, pattern }, context) => {
5284
+ const { filesystem } = requireFilesystem(context);
5285
+ await emitWorkspaceMetadata(context, WORKSPACE_TOOLS.FILESYSTEM.LIST_FILES);
5286
+ const result = await formatAsTree(filesystem, path4, {
5287
+ maxDepth,
5288
+ showHidden,
5289
+ dirsOnly,
5290
+ exclude: exclude || void 0,
5291
+ extension: extension || void 0,
5292
+ pattern: pattern || void 0
5293
+ });
5294
+ return `${result.tree}
5295
+
5296
+ ${result.summary}`;
5297
+ }
5298
+ });
5299
+ var mkdirTool = chunkEAZ6YDCQ_cjs.createTool({
5300
+ id: WORKSPACE_TOOLS.FILESYSTEM.MKDIR,
5301
+ description: "Create a directory in the workspace filesystem",
5302
+ inputSchema: zod.z.object({
5303
+ path: zod.z.string().describe("The path of the directory to create"),
5304
+ recursive: zod.z.boolean().optional().default(true).describe("Whether to create parent directories if they do not exist")
5305
+ }),
5306
+ execute: async ({ path: path4, recursive }, context) => {
5307
+ const { filesystem } = requireFilesystem(context);
5308
+ await emitWorkspaceMetadata(context, WORKSPACE_TOOLS.FILESYSTEM.MKDIR);
5309
+ if (filesystem.readOnly) {
5310
+ throw new WorkspaceReadOnlyError("mkdir");
5311
+ }
5312
+ await filesystem.mkdir(path4, { recursive });
5313
+ return `Created directory ${path4}`;
5314
+ }
5315
+ });
5316
+ var readFileTool = chunkEAZ6YDCQ_cjs.createTool({
5317
+ id: WORKSPACE_TOOLS.FILESYSTEM.READ_FILE,
5318
+ description: "Read the contents of a file from the workspace filesystem. Use offset/limit parameters to read specific line ranges for large files.",
5319
+ inputSchema: zod.z.object({
5320
+ path: zod.z.string().describe('The path to the file to read (e.g., "/data/config.json")'),
5321
+ encoding: zod.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."),
5322
+ offset: zod.z.number().optional().describe("Line number to start reading from (1-indexed). If omitted, starts from line 1."),
5323
+ limit: zod.z.number().optional().describe("Maximum number of lines to read. If omitted, reads to the end of the file."),
5324
+ showLineNumbers: zod.z.boolean().optional().default(true).describe("Whether to prefix each line with its line number (default: true)")
5325
+ }),
5326
+ execute: async ({ path: path4, encoding, offset, limit, showLineNumbers }, context) => {
5327
+ const { filesystem } = requireFilesystem(context);
5328
+ await emitWorkspaceMetadata(context, WORKSPACE_TOOLS.FILESYSTEM.READ_FILE);
5329
+ const effectiveEncoding = encoding ?? "utf-8";
5330
+ const fullContent = await filesystem.readFile(path4, { encoding: effectiveEncoding });
5331
+ const stat3 = await filesystem.stat(path4);
5332
+ const isTextEncoding = !encoding || encoding === "utf-8" || encoding === "utf8";
5333
+ if (!isTextEncoding) {
5334
+ return `${stat3.path} (${stat3.size} bytes, ${effectiveEncoding})
5335
+ ${fullContent}`;
5336
+ }
5337
+ if (typeof fullContent !== "string") {
5338
+ return `${stat3.path} (${stat3.size} bytes, base64)
5339
+ ${fullContent.toString("base64")}`;
5340
+ }
5341
+ const hasLineRange = offset !== void 0 || limit !== void 0;
5342
+ const result = extractLinesWithLimit(fullContent, offset, limit);
5343
+ const shouldShowLineNumbers = showLineNumbers !== false;
5344
+ const formattedContent = shouldShowLineNumbers ? formatWithLineNumbers(result.content, result.lines.start) : result.content;
5345
+ let header;
5346
+ if (hasLineRange) {
5347
+ header = `${stat3.path} (lines ${result.lines.start}-${result.lines.end} of ${result.totalLines}, ${stat3.size} bytes)`;
5348
+ } else {
5349
+ header = `${stat3.path} (${stat3.size} bytes)`;
5350
+ }
5351
+ return `${header}
5352
+ ${formattedContent}`;
5353
+ }
5354
+ });
5355
+ var searchTool = chunkEAZ6YDCQ_cjs.createTool({
5356
+ id: WORKSPACE_TOOLS.SEARCH.SEARCH,
5357
+ description: "Search indexed content in the workspace. Supports keyword (BM25), semantic (vector), and hybrid search modes.",
5358
+ inputSchema: zod.z.object({
5359
+ query: zod.z.string().describe("The search query string"),
5360
+ topK: zod.z.number().optional().default(5).describe("Maximum number of results to return"),
5361
+ mode: zod.z.enum(["bm25", "vector", "hybrid"]).optional().describe("Search mode: bm25 for keyword search, vector for semantic search, hybrid for both combined"),
5362
+ minScore: zod.z.number().optional().describe("Minimum score threshold (0-1 for normalized scores)")
5363
+ }),
5364
+ execute: async ({ query, topK, mode, minScore }, context) => {
5365
+ const workspace = requireWorkspace(context);
5366
+ await emitWorkspaceMetadata(context, WORKSPACE_TOOLS.SEARCH.SEARCH);
5367
+ const results = await workspace.search(query, {
5368
+ topK,
5369
+ mode,
5370
+ minScore
5371
+ });
5372
+ const effectiveMode = mode ?? (workspace.canHybrid ? "hybrid" : workspace.canVector ? "vector" : "bm25");
5373
+ const lines = results.map((r) => {
5374
+ const lineInfo = r.lineRange ? `:${r.lineRange.start}-${r.lineRange.end}` : "";
5375
+ return `${r.id}${lineInfo}: ${r.content}`;
5376
+ });
5377
+ lines.push("---");
5378
+ lines.push(`${results.length} result${results.length !== 1 ? "s" : ""} (${effectiveMode} search)`);
5379
+ return lines.join("\n");
5380
+ }
5381
+ });
5382
+ var writeFileTool = chunkEAZ6YDCQ_cjs.createTool({
5383
+ id: WORKSPACE_TOOLS.FILESYSTEM.WRITE_FILE,
5384
+ description: "Write content to a file in the workspace filesystem. Creates parent directories if needed.",
5385
+ inputSchema: zod.z.object({
5386
+ path: zod.z.string().describe('The path where to write the file (e.g., "/data/output.txt")'),
5387
+ content: zod.z.string().describe("The content to write to the file"),
5388
+ overwrite: zod.z.boolean().optional().default(true).describe("Whether to overwrite the file if it already exists")
5389
+ }),
5390
+ execute: async ({ path: path4, content, overwrite }, context) => {
5391
+ const { filesystem } = requireFilesystem(context);
5392
+ await emitWorkspaceMetadata(context, WORKSPACE_TOOLS.FILESYSTEM.WRITE_FILE);
5393
+ if (filesystem.readOnly) {
5394
+ throw new WorkspaceReadOnlyError("write_file");
5395
+ }
5396
+ await filesystem.writeFile(path4, content, { overwrite });
5397
+ const size = Buffer.byteLength(content, "utf-8");
5398
+ return `Wrote ${size} bytes to ${path4}`;
5399
+ }
5400
+ });
5401
+
4208
5402
  // src/workspace/tools/tools.ts
4209
5403
  function resolveToolConfig(toolsConfig, toolName) {
4210
5404
  let enabled = true;
@@ -4232,6 +5426,49 @@ function resolveToolConfig(toolsConfig, toolName) {
4232
5426
  }
4233
5427
  return { enabled, requireApproval, requireReadBeforeWrite };
4234
5428
  }
5429
+ function wrapTool(tool, workspace, config) {
5430
+ return {
5431
+ ...tool,
5432
+ requireApproval: config.requireApproval,
5433
+ execute: async (input, context = {}) => {
5434
+ const enrichedContext = { ...context, workspace: context?.workspace ?? workspace };
5435
+ return tool.execute(input, enrichedContext);
5436
+ }
5437
+ };
5438
+ }
5439
+ function wrapWithReadTracker(tool, workspace, readTracker, config, mode) {
5440
+ return {
5441
+ ...tool,
5442
+ requireApproval: config.requireApproval,
5443
+ execute: async (input, context = {}) => {
5444
+ const enrichedContext = { ...context, workspace: context?.workspace ?? workspace };
5445
+ if (mode === "write" && config.requireReadBeforeWrite) {
5446
+ try {
5447
+ const stat3 = await workspace.filesystem.stat(input.path);
5448
+ const check = readTracker.needsReRead(input.path, stat3.modifiedAt);
5449
+ if (check.needsReRead) {
5450
+ throw new FileReadRequiredError(input.path, check.reason);
5451
+ }
5452
+ } catch (error) {
5453
+ if (!(error instanceof FileNotFoundError)) {
5454
+ throw error;
5455
+ }
5456
+ }
5457
+ }
5458
+ const result = await tool.execute(input, enrichedContext);
5459
+ if (mode === "read") {
5460
+ try {
5461
+ const stat3 = await workspace.filesystem.stat(input.path);
5462
+ readTracker.recordRead(input.path, stat3.modifiedAt);
5463
+ } catch {
5464
+ }
5465
+ } else if (mode === "write") {
5466
+ readTracker.clearReadRecord(input.path);
5467
+ }
5468
+ return result;
5469
+ }
5470
+ };
5471
+ }
4235
5472
  function createWorkspaceTools(workspace) {
4236
5473
  const tools = {};
4237
5474
  const toolsConfig = workspace.getToolsConfig();
@@ -4242,493 +5479,48 @@ function createWorkspaceTools(workspace) {
4242
5479
  if (writeFileConfig.requireReadBeforeWrite || editFileConfig.requireReadBeforeWrite) {
4243
5480
  readTracker = new InMemoryFileReadTracker();
4244
5481
  }
4245
- if (workspace.filesystem) {
4246
- const readFileConfig = resolveToolConfig(toolsConfig, WORKSPACE_TOOLS.FILESYSTEM.READ_FILE);
4247
- if (readFileConfig.enabled) {
4248
- tools[WORKSPACE_TOOLS.FILESYSTEM.READ_FILE] = chunk7UWHFWST_cjs.createTool({
4249
- id: WORKSPACE_TOOLS.FILESYSTEM.READ_FILE,
4250
- description: "Read the contents of a file from the workspace filesystem. Use offset/limit parameters to read specific line ranges for large files.",
4251
- requireApproval: readFileConfig.requireApproval,
4252
- inputSchema: zod.z.object({
4253
- path: zod.z.string().describe('The path to the file to read (e.g., "/data/config.json")'),
4254
- encoding: zod.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."),
4255
- offset: zod.z.number().optional().describe("Line number to start reading from (1-indexed). If omitted, starts from line 1."),
4256
- limit: zod.z.number().optional().describe("Maximum number of lines to read. If omitted, reads to the end of the file."),
4257
- showLineNumbers: zod.z.boolean().optional().default(true).describe("Whether to prefix each line with its line number (default: true)")
4258
- }),
4259
- outputSchema: zod.z.object({
4260
- content: zod.z.string().describe("The file contents (with optional line number prefixes)"),
4261
- size: zod.z.number().describe("The file size in bytes"),
4262
- path: zod.z.string().describe("The full path to the file"),
4263
- lines: zod.z.object({
4264
- start: zod.z.number().describe("First line number returned"),
4265
- end: zod.z.number().describe("Last line number returned")
4266
- }).optional().describe("Line range information (when offset/limit used)"),
4267
- totalLines: zod.z.number().optional().describe("Total number of lines in the file")
4268
- }),
4269
- execute: async ({ path: path4, encoding, offset, limit, showLineNumbers }) => {
4270
- const effectiveEncoding = encoding ?? "utf-8";
4271
- const fullContent = await workspace.filesystem.readFile(path4, {
4272
- encoding: effectiveEncoding
4273
- });
4274
- const stat3 = await workspace.filesystem.stat(path4);
4275
- if (readTracker) {
4276
- readTracker.recordRead(path4, stat3.modifiedAt);
4277
- }
4278
- const isTextEncoding = !encoding || encoding === "utf-8" || encoding === "utf8";
4279
- if (!isTextEncoding) {
4280
- return {
4281
- content: fullContent,
4282
- size: stat3.size,
4283
- path: stat3.path
4284
- };
4285
- }
4286
- if (typeof fullContent !== "string") {
4287
- return {
4288
- content: fullContent.toString("base64"),
4289
- size: stat3.size,
4290
- path: stat3.path
4291
- };
4292
- }
4293
- const hasLineRange = offset !== void 0 || limit !== void 0;
4294
- const result = extractLinesWithLimit(fullContent, offset, limit);
4295
- const shouldShowLineNumbers = showLineNumbers !== false;
4296
- const formattedContent = shouldShowLineNumbers ? formatWithLineNumbers(result.content, result.lines.start) : result.content;
4297
- return {
4298
- content: formattedContent,
4299
- size: stat3.size,
4300
- path: stat3.path,
4301
- ...hasLineRange && {
4302
- lines: result.lines,
4303
- totalLines: result.totalLines
4304
- }
4305
- };
4306
- }
4307
- });
4308
- }
4309
- if (!isReadOnly && writeFileConfig.enabled) {
4310
- tools[WORKSPACE_TOOLS.FILESYSTEM.WRITE_FILE] = chunk7UWHFWST_cjs.createTool({
4311
- id: WORKSPACE_TOOLS.FILESYSTEM.WRITE_FILE,
4312
- description: "Write content to a file in the workspace filesystem. Creates parent directories if needed.",
4313
- requireApproval: writeFileConfig.requireApproval,
4314
- inputSchema: zod.z.object({
4315
- path: zod.z.string().describe('The path where to write the file (e.g., "/data/output.txt")'),
4316
- content: zod.z.string().describe("The content to write to the file"),
4317
- overwrite: zod.z.boolean().optional().default(true).describe("Whether to overwrite the file if it already exists")
4318
- }),
4319
- outputSchema: zod.z.object({
4320
- success: zod.z.boolean(),
4321
- path: zod.z.string().describe("The path where the file was written"),
4322
- size: zod.z.number().describe("The size of the written content in bytes")
4323
- }),
4324
- execute: async ({ path: path4, content, overwrite }) => {
4325
- if (readTracker && writeFileConfig.requireReadBeforeWrite) {
4326
- try {
4327
- const stat3 = await workspace.filesystem.stat(path4);
4328
- const check = readTracker.needsReRead(path4, stat3.modifiedAt);
4329
- if (check.needsReRead) {
4330
- throw new FileReadRequiredError(path4, check.reason);
4331
- }
4332
- } catch (error) {
4333
- if (!(error instanceof FileNotFoundError)) {
4334
- throw error;
4335
- }
4336
- }
4337
- }
4338
- await workspace.filesystem.writeFile(path4, content, { overwrite });
4339
- if (readTracker) {
4340
- readTracker.clearReadRecord(path4);
4341
- }
4342
- return {
4343
- success: true,
4344
- path: path4,
4345
- size: Buffer.byteLength(content, "utf-8")
4346
- };
4347
- }
4348
- });
4349
- }
4350
- if (!isReadOnly && editFileConfig.enabled) {
4351
- tools[WORKSPACE_TOOLS.FILESYSTEM.EDIT_FILE] = chunk7UWHFWST_cjs.createTool({
4352
- id: WORKSPACE_TOOLS.FILESYSTEM.EDIT_FILE,
4353
- description: `Edit a file by replacing specific text. The old_string must match exactly and be unique in the file.
4354
-
4355
- Usage:
4356
- - Read the file first to get the exact text to replace.
4357
- - 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.
4358
- - Include enough surrounding context (multiple lines) to make old_string unique. If it still isn't unique, include more lines.
4359
- - Use replace_all only when intentionally replacing all occurrences.`,
4360
- requireApproval: editFileConfig.requireApproval,
4361
- inputSchema: zod.z.object({
4362
- path: zod.z.string().describe("The path to the file to edit"),
4363
- old_string: zod.z.string().describe("The exact text to find and replace. Must be unique in the file."),
4364
- new_string: zod.z.string().describe("The text to replace old_string with"),
4365
- replace_all: zod.z.boolean().optional().default(false).describe("If true, replace all occurrences. If false (default), old_string must be unique.")
4366
- }),
4367
- outputSchema: zod.z.object({
4368
- success: zod.z.boolean(),
4369
- path: zod.z.string().describe("The path to the edited file"),
4370
- replacements: zod.z.number().describe("Number of replacements made"),
4371
- error: zod.z.string().optional().describe("Error message if the edit failed")
4372
- }),
4373
- execute: async ({ path: path4, old_string, new_string, replace_all }) => {
4374
- try {
4375
- if (readTracker && editFileConfig.requireReadBeforeWrite) {
4376
- const stat3 = await workspace.filesystem.stat(path4);
4377
- const check = readTracker.needsReRead(path4, stat3.modifiedAt);
4378
- if (check.needsReRead) {
4379
- throw new FileReadRequiredError(path4, check.reason);
4380
- }
4381
- }
4382
- const content = await workspace.filesystem.readFile(path4, { encoding: "utf-8" });
4383
- if (typeof content !== "string") {
4384
- return {
4385
- success: false,
4386
- path: path4,
4387
- replacements: 0,
4388
- error: "Cannot edit binary files. Use workspace_write_file instead."
4389
- };
4390
- }
4391
- const result = replaceString(content, old_string, new_string, replace_all);
4392
- await workspace.filesystem.writeFile(path4, result.content, { overwrite: true });
4393
- if (readTracker) {
4394
- readTracker.clearReadRecord(path4);
4395
- }
4396
- return {
4397
- success: true,
4398
- path: path4,
4399
- replacements: result.replacements
4400
- };
4401
- } catch (error) {
4402
- if (error instanceof FileReadRequiredError) {
4403
- throw error;
4404
- }
4405
- if (error instanceof StringNotFoundError) {
4406
- return {
4407
- success: false,
4408
- path: path4,
4409
- replacements: 0,
4410
- error: error.message
4411
- };
4412
- }
4413
- if (error instanceof StringNotUniqueError) {
4414
- return {
4415
- success: false,
4416
- path: path4,
4417
- replacements: 0,
4418
- error: error.message
4419
- };
4420
- }
4421
- throw error;
4422
- }
4423
- }
4424
- });
4425
- }
4426
- const listFilesConfig = resolveToolConfig(toolsConfig, WORKSPACE_TOOLS.FILESYSTEM.LIST_FILES);
4427
- if (listFilesConfig.enabled) {
4428
- tools[WORKSPACE_TOOLS.FILESYSTEM.LIST_FILES] = chunk7UWHFWST_cjs.createTool({
4429
- id: WORKSPACE_TOOLS.FILESYSTEM.LIST_FILES,
4430
- description: `List files and directories in the workspace filesystem.
4431
- Returns a tree-style view (like the Unix "tree" command) for easy visualization.
4432
- The output is displayed to the user as a tree-like structure in the tool result.
4433
- Options mirror common tree command flags for familiarity.
4434
-
4435
- Examples:
4436
- - List root: { path: "/" }
4437
- - Deep listing: { path: "/src", maxDepth: 5 }
4438
- - Directories only: { path: "/", dirsOnly: true }
4439
- - Exclude node_modules: { path: "/", exclude: "node_modules" }`,
4440
- requireApproval: listFilesConfig.requireApproval,
4441
- inputSchema: zod.z.object({
4442
- path: zod.z.string().default("/").describe("Directory path to list"),
4443
- maxDepth: zod.z.number().optional().default(3).describe("Maximum depth to descend (default: 3). Similar to tree -L flag."),
4444
- showHidden: zod.z.boolean().optional().default(false).describe('Show hidden files starting with "." (default: false). Similar to tree -a flag.'),
4445
- dirsOnly: zod.z.boolean().optional().default(false).describe("List directories only, no files (default: false). Similar to tree -d flag."),
4446
- exclude: zod.z.string().optional().describe('Pattern to exclude (e.g., "node_modules"). Similar to tree -I flag.'),
4447
- extension: zod.z.string().optional().describe('Filter by file extension (e.g., ".ts"). Similar to tree -P flag.')
4448
- }),
4449
- outputSchema: zod.z.object({
4450
- tree: zod.z.string().describe("Tree-style directory listing"),
4451
- summary: zod.z.string().describe('Summary of directories and files (e.g., "3 directories, 12 files")'),
4452
- metadata: zod.z.object({
4453
- workspace: zod.z.object({
4454
- id: zod.z.string().optional(),
4455
- name: zod.z.string().optional()
4456
- }).optional(),
4457
- filesystem: zod.z.object({
4458
- id: zod.z.string().optional(),
4459
- name: zod.z.string().optional(),
4460
- provider: zod.z.string().optional()
4461
- }).optional()
4462
- }).optional().describe("Metadata about the workspace and filesystem")
4463
- }),
4464
- execute: async ({ path: path4 = "/", maxDepth = 3, showHidden, dirsOnly, exclude, extension }) => {
4465
- const result = await formatAsTree(workspace.filesystem, path4, {
4466
- maxDepth,
4467
- showHidden,
4468
- dirsOnly,
4469
- exclude: exclude || void 0,
4470
- extension: extension || void 0
4471
- });
4472
- const fs5 = workspace.filesystem;
4473
- const metadata = {
4474
- workspace: {
4475
- id: workspace.id,
4476
- name: workspace.name
4477
- },
4478
- filesystem: {
4479
- id: fs5.id,
4480
- name: fs5.name,
4481
- provider: fs5.provider
4482
- }
4483
- };
4484
- return {
4485
- tree: result.tree,
4486
- summary: result.summary,
4487
- metadata
4488
- };
4489
- }
4490
- });
4491
- }
4492
- const deleteConfig = resolveToolConfig(toolsConfig, WORKSPACE_TOOLS.FILESYSTEM.DELETE);
4493
- if (!isReadOnly && deleteConfig.enabled) {
4494
- tools[WORKSPACE_TOOLS.FILESYSTEM.DELETE] = chunk7UWHFWST_cjs.createTool({
4495
- id: WORKSPACE_TOOLS.FILESYSTEM.DELETE,
4496
- description: "Delete a file or directory from the workspace filesystem",
4497
- requireApproval: deleteConfig.requireApproval,
4498
- inputSchema: zod.z.object({
4499
- path: zod.z.string().describe("The path to the file or directory to delete"),
4500
- recursive: zod.z.boolean().optional().default(false).describe(
4501
- "If true, delete directories and their contents recursively. Required for non-empty directories."
4502
- )
4503
- }),
4504
- outputSchema: zod.z.object({
4505
- success: zod.z.boolean(),
4506
- path: zod.z.string()
4507
- }),
4508
- execute: async ({ path: path4, recursive }) => {
4509
- const stat3 = await workspace.filesystem.stat(path4);
4510
- if (stat3.type === "directory") {
4511
- await workspace.filesystem.rmdir(path4, { recursive, force: recursive });
4512
- } else {
4513
- await workspace.filesystem.deleteFile(path4);
4514
- }
4515
- return { success: true, path: path4 };
4516
- }
4517
- });
4518
- }
4519
- const fileStatConfig = resolveToolConfig(toolsConfig, WORKSPACE_TOOLS.FILESYSTEM.FILE_STAT);
4520
- if (fileStatConfig.enabled) {
4521
- tools[WORKSPACE_TOOLS.FILESYSTEM.FILE_STAT] = chunk7UWHFWST_cjs.createTool({
4522
- id: WORKSPACE_TOOLS.FILESYSTEM.FILE_STAT,
4523
- description: "Get file or directory metadata from the workspace. Returns existence, type, size, and modification time.",
4524
- requireApproval: fileStatConfig.requireApproval,
4525
- inputSchema: zod.z.object({
4526
- path: zod.z.string().describe("The path to check")
4527
- }),
4528
- outputSchema: zod.z.object({
4529
- exists: zod.z.boolean().describe("Whether the path exists"),
4530
- type: zod.z.enum(["file", "directory", "none"]).describe("The type of the path if it exists"),
4531
- size: zod.z.number().optional().describe("Size in bytes (for files)"),
4532
- modifiedAt: zod.z.string().optional().describe("Last modification time (ISO string)")
4533
- }),
4534
- execute: async ({ path: path4 }) => {
4535
- try {
4536
- const stat3 = await workspace.filesystem.stat(path4);
4537
- return {
4538
- exists: true,
4539
- type: stat3.type,
4540
- size: stat3.size,
4541
- modifiedAt: stat3.modifiedAt.toISOString()
4542
- };
4543
- } catch (error) {
4544
- if (error instanceof FileNotFoundError) {
4545
- return { exists: false, type: "none" };
4546
- }
4547
- throw error;
4548
- }
4549
- }
4550
- });
4551
- }
4552
- const mkdirConfig = resolveToolConfig(toolsConfig, WORKSPACE_TOOLS.FILESYSTEM.MKDIR);
4553
- if (!isReadOnly && mkdirConfig.enabled) {
4554
- tools[WORKSPACE_TOOLS.FILESYSTEM.MKDIR] = chunk7UWHFWST_cjs.createTool({
4555
- id: WORKSPACE_TOOLS.FILESYSTEM.MKDIR,
4556
- description: "Create a directory in the workspace filesystem",
4557
- requireApproval: mkdirConfig.requireApproval,
4558
- inputSchema: zod.z.object({
4559
- path: zod.z.string().describe("The path of the directory to create"),
4560
- recursive: zod.z.boolean().optional().default(true).describe("Whether to create parent directories if they do not exist")
4561
- }),
4562
- outputSchema: zod.z.object({
4563
- success: zod.z.boolean(),
4564
- path: zod.z.string()
4565
- }),
4566
- execute: async ({ path: path4, recursive }) => {
4567
- await workspace.filesystem.mkdir(path4, { recursive });
4568
- return { success: true, path: path4 };
4569
- }
4570
- });
5482
+ const addTool = (name, tool, opts) => {
5483
+ const config = resolveToolConfig(toolsConfig, name);
5484
+ if (!config.enabled) return;
5485
+ if (opts?.requireWrite && isReadOnly) return;
5486
+ if (readTracker && opts?.readTrackerMode) {
5487
+ tools[name] = wrapWithReadTracker(tool, workspace, readTracker, config, opts.readTrackerMode);
5488
+ } else {
5489
+ tools[name] = wrapTool(tool, workspace, config);
4571
5490
  }
5491
+ };
5492
+ if (workspace.filesystem) {
5493
+ addTool(WORKSPACE_TOOLS.FILESYSTEM.READ_FILE, readFileTool, { readTrackerMode: "read" });
5494
+ addTool(WORKSPACE_TOOLS.FILESYSTEM.WRITE_FILE, writeFileTool, {
5495
+ requireWrite: true,
5496
+ readTrackerMode: "write"
5497
+ });
5498
+ addTool(WORKSPACE_TOOLS.FILESYSTEM.EDIT_FILE, editFileTool, {
5499
+ requireWrite: true,
5500
+ readTrackerMode: "write"
5501
+ });
5502
+ addTool(WORKSPACE_TOOLS.FILESYSTEM.LIST_FILES, listFilesTool);
5503
+ addTool(WORKSPACE_TOOLS.FILESYSTEM.DELETE, deleteFileTool, { requireWrite: true });
5504
+ addTool(WORKSPACE_TOOLS.FILESYSTEM.FILE_STAT, fileStatTool);
5505
+ addTool(WORKSPACE_TOOLS.FILESYSTEM.MKDIR, mkdirTool, { requireWrite: true });
5506
+ addTool(WORKSPACE_TOOLS.FILESYSTEM.GREP, grepTool);
4572
5507
  }
4573
5508
  if (workspace.canBM25 || workspace.canVector) {
4574
- const searchConfig = resolveToolConfig(toolsConfig, WORKSPACE_TOOLS.SEARCH.SEARCH);
4575
- if (searchConfig.enabled) {
4576
- tools[WORKSPACE_TOOLS.SEARCH.SEARCH] = chunk7UWHFWST_cjs.createTool({
4577
- id: WORKSPACE_TOOLS.SEARCH.SEARCH,
4578
- description: "Search indexed content in the workspace. Supports keyword (BM25), semantic (vector), and hybrid search modes.",
4579
- requireApproval: searchConfig.requireApproval,
4580
- inputSchema: zod.z.object({
4581
- query: zod.z.string().describe("The search query string"),
4582
- topK: zod.z.number().optional().default(5).describe("Maximum number of results to return"),
4583
- mode: zod.z.enum(["bm25", "vector", "hybrid"]).optional().describe("Search mode: bm25 for keyword search, vector for semantic search, hybrid for both combined"),
4584
- minScore: zod.z.number().optional().describe("Minimum score threshold (0-1 for normalized scores)")
4585
- }),
4586
- outputSchema: zod.z.object({
4587
- results: zod.z.array(
4588
- zod.z.object({
4589
- id: zod.z.string().describe("Document/file path"),
4590
- content: zod.z.string().describe("The matching content"),
4591
- score: zod.z.number().describe("Relevance score"),
4592
- lineRange: zod.z.object({
4593
- start: zod.z.number(),
4594
- end: zod.z.number()
4595
- }).optional().describe("Line range where query terms were found")
4596
- })
4597
- ),
4598
- count: zod.z.number().describe("Number of results returned"),
4599
- mode: zod.z.string().describe("The search mode that was used")
4600
- }),
4601
- execute: async ({ query, topK, mode, minScore }) => {
4602
- const results = await workspace.search(query, {
4603
- topK,
4604
- mode,
4605
- minScore
4606
- });
4607
- return {
4608
- results: results.map((r) => ({
4609
- id: r.id,
4610
- content: r.content,
4611
- score: r.score,
4612
- lineRange: r.lineRange
4613
- })),
4614
- count: results.length,
4615
- mode: mode ?? (workspace.canHybrid ? "hybrid" : workspace.canVector ? "vector" : "bm25")
4616
- };
4617
- }
4618
- });
4619
- }
4620
- const indexConfig = resolveToolConfig(toolsConfig, WORKSPACE_TOOLS.SEARCH.INDEX);
4621
- if (!isReadOnly && indexConfig.enabled) {
4622
- tools[WORKSPACE_TOOLS.SEARCH.INDEX] = chunk7UWHFWST_cjs.createTool({
4623
- id: WORKSPACE_TOOLS.SEARCH.INDEX,
4624
- description: "Index content for search. The path becomes the document ID in search results.",
4625
- requireApproval: indexConfig.requireApproval,
4626
- inputSchema: zod.z.object({
4627
- path: zod.z.string().describe("The document ID/path for search results"),
4628
- content: zod.z.string().describe("The text content to index"),
4629
- metadata: zod.z.record(zod.z.unknown()).optional().describe("Optional metadata to store with the document")
4630
- }),
4631
- outputSchema: zod.z.object({
4632
- success: zod.z.boolean(),
4633
- path: zod.z.string().describe("The indexed document ID")
4634
- }),
4635
- execute: async ({ path: path4, content, metadata }) => {
4636
- await workspace.index(path4, content, { metadata });
4637
- return { success: true, path: path4 };
4638
- }
4639
- });
4640
- }
5509
+ addTool(WORKSPACE_TOOLS.SEARCH.SEARCH, searchTool);
5510
+ addTool(WORKSPACE_TOOLS.SEARCH.INDEX, indexContentTool, { requireWrite: true });
4641
5511
  }
4642
5512
  if (workspace.sandbox) {
4643
- const pathContext = workspace.getPathContext();
4644
- const pathInfo = pathContext.instructions ? ` ${pathContext.instructions}` : "";
4645
5513
  const executeCommandConfig = resolveToolConfig(toolsConfig, WORKSPACE_TOOLS.SANDBOX.EXECUTE_COMMAND);
4646
5514
  if (workspace.sandbox.executeCommand && executeCommandConfig.enabled) {
4647
- tools[WORKSPACE_TOOLS.SANDBOX.EXECUTE_COMMAND] = chunk7UWHFWST_cjs.createTool({
4648
- id: WORKSPACE_TOOLS.SANDBOX.EXECUTE_COMMAND,
4649
- description: `Execute a shell command in the workspace sandbox.${pathInfo}
5515
+ const pathContext = workspace.getPathContext();
5516
+ const pathInfo = pathContext.instructions ? `
4650
5517
 
4651
- Usage:
4652
- - Verify parent directories exist before running commands that create files or directories.
4653
- - Always quote file paths that contain spaces (e.g., cd "/path/with spaces").
4654
- - Use the timeout parameter to limit execution time. Behavior when omitted depends on the sandbox provider.
4655
- - Use cwd to set the working directory, or commands run from the sandbox default.`,
4656
- requireApproval: executeCommandConfig.requireApproval,
4657
- inputSchema: zod.z.object({
4658
- command: zod.z.string().describe('The command to execute (e.g., "ls", "npm", "python")'),
4659
- args: zod.z.array(zod.z.string()).nullish().default([]).describe("Arguments to pass to the command"),
4660
- timeout: zod.z.number().nullish().describe("Maximum execution time in milliseconds. Example: 60000 for 1 minute."),
4661
- cwd: zod.z.string().nullish().describe("Working directory for the command")
4662
- }),
4663
- outputSchema: zod.z.object({
4664
- success: zod.z.boolean().describe("Whether the command executed successfully (exit code 0)"),
4665
- stdout: zod.z.string().describe("Standard output from the command"),
4666
- stderr: zod.z.string().describe("Standard error output"),
4667
- exitCode: zod.z.number().describe("Exit code (0 = success)"),
4668
- executionTimeMs: zod.z.number().describe("How long the execution took in milliseconds")
4669
- }),
4670
- execute: async ({ command, args, timeout, cwd }, context) => {
4671
- const getExecutionMetadata = () => ({
4672
- workspace: {
4673
- id: workspace.id,
4674
- name: workspace.name
4675
- },
4676
- sandbox: {
4677
- id: workspace.sandbox?.id,
4678
- name: workspace.sandbox?.name,
4679
- provider: workspace.sandbox?.provider,
4680
- status: workspace.sandbox?.status
4681
- }
4682
- });
4683
- const startedAt = Date.now();
4684
- try {
4685
- const result = await workspace.sandbox.executeCommand(command, args ?? [], {
4686
- timeout: timeout ?? void 0,
4687
- cwd: cwd ?? void 0,
4688
- // Stream stdout/stderr as tool-output chunks for proper UI integration
4689
- onStdout: async (data) => {
4690
- await context?.writer?.write({
4691
- type: "sandbox-stdout",
4692
- data,
4693
- timestamp: Date.now(),
4694
- metadata: getExecutionMetadata()
4695
- });
4696
- },
4697
- onStderr: async (data) => {
4698
- await context?.writer?.write({
4699
- type: "sandbox-stderr",
4700
- data,
4701
- timestamp: Date.now(),
4702
- metadata: getExecutionMetadata()
4703
- });
4704
- }
4705
- });
4706
- await context?.writer?.write({
4707
- type: "sandbox-exit",
4708
- exitCode: result.exitCode,
4709
- success: result.success,
4710
- executionTimeMs: result.executionTimeMs,
4711
- metadata: getExecutionMetadata()
4712
- });
4713
- return {
4714
- success: result.success,
4715
- stdout: result.stdout,
4716
- stderr: result.stderr,
4717
- exitCode: result.exitCode,
4718
- executionTimeMs: result.executionTimeMs
4719
- };
4720
- } catch (error) {
4721
- await context?.writer?.write({
4722
- type: "sandbox-exit",
4723
- exitCode: -1,
4724
- success: false,
4725
- executionTimeMs: Date.now() - startedAt,
4726
- metadata: getExecutionMetadata()
4727
- });
4728
- throw error;
4729
- }
4730
- }
4731
- });
5518
+ ${pathContext.instructions}` : "";
5519
+ const description = pathInfo ? `${executeCommandTool.description}${pathInfo}` : executeCommandTool.description;
5520
+ tools[WORKSPACE_TOOLS.SANDBOX.EXECUTE_COMMAND] = {
5521
+ ...wrapTool(executeCommandTool, workspace, executeCommandConfig),
5522
+ description
5523
+ };
4732
5524
  }
4733
5525
  }
4734
5526
  return tools;
@@ -4736,6 +5528,7 @@ Usage:
4736
5528
 
4737
5529
  exports.BM25Index = BM25Index;
4738
5530
  exports.CompositeFilesystem = CompositeFilesystem;
5531
+ exports.CompositeVersionedSkillSource = CompositeVersionedSkillSource;
4739
5532
  exports.DirectoryNotEmptyError = DirectoryNotEmptyError;
4740
5533
  exports.DirectoryNotFoundError = DirectoryNotFoundError;
4741
5534
  exports.FileExistsError = FileExistsError;
@@ -4749,6 +5542,7 @@ exports.IsDirectoryError = IsDirectoryError;
4749
5542
  exports.IsolationUnavailableError = IsolationUnavailableError;
4750
5543
  exports.LocalFilesystem = LocalFilesystem;
4751
5544
  exports.LocalSandbox = LocalSandbox;
5545
+ exports.LocalSkillSource = LocalSkillSource;
4752
5546
  exports.MastraFilesystem = MastraFilesystem;
4753
5547
  exports.MastraSandbox = MastraSandbox;
4754
5548
  exports.MountError = MountError;
@@ -4763,18 +5557,39 @@ exports.SandboxNotAvailableError = SandboxNotAvailableError;
4763
5557
  exports.SandboxNotReadyError = SandboxNotReadyError;
4764
5558
  exports.SandboxTimeoutError = SandboxTimeoutError;
4765
5559
  exports.SearchNotAvailableError = SearchNotAvailableError;
5560
+ exports.VersionedSkillSource = VersionedSkillSource;
4766
5561
  exports.WORKSPACE_TOOLS = WORKSPACE_TOOLS;
4767
5562
  exports.WORKSPACE_TOOLS_PREFIX = WORKSPACE_TOOLS_PREFIX;
4768
5563
  exports.Workspace = Workspace;
4769
5564
  exports.WorkspaceError = WorkspaceError;
5565
+ exports.WorkspaceNotAvailableError = WorkspaceNotAvailableError;
4770
5566
  exports.WorkspaceNotReadyError = WorkspaceNotReadyError;
4771
5567
  exports.WorkspaceReadOnlyError = WorkspaceReadOnlyError;
4772
5568
  exports.callLifecycle = callLifecycle;
5569
+ exports.collectSkillForPublish = collectSkillForPublish;
5570
+ exports.createGlobMatcher = createGlobMatcher;
4773
5571
  exports.createWorkspaceTools = createWorkspaceTools;
5572
+ exports.deleteFileTool = deleteFileTool;
4774
5573
  exports.detectIsolation = detectIsolation;
5574
+ exports.editFileTool = editFileTool;
5575
+ exports.executeCommandTool = executeCommandTool;
5576
+ exports.extractGlobBase = extractGlobBase;
4775
5577
  exports.extractLines = extractLines;
5578
+ exports.fileStatTool = fileStatTool;
4776
5579
  exports.getRecommendedIsolation = getRecommendedIsolation;
5580
+ exports.indexContentTool = indexContentTool;
5581
+ exports.isGlobPattern = isGlobPattern;
4777
5582
  exports.isIsolationAvailable = isIsolationAvailable;
5583
+ exports.listFilesTool = listFilesTool;
5584
+ exports.matchGlob = matchGlob;
5585
+ exports.mkdirTool = mkdirTool;
5586
+ exports.publishSkillFromSource = publishSkillFromSource;
5587
+ exports.readFileTool = readFileTool;
5588
+ exports.requireFilesystem = requireFilesystem;
5589
+ exports.requireSandbox = requireSandbox;
5590
+ exports.requireWorkspace = requireWorkspace;
4778
5591
  exports.resolveToolConfig = resolveToolConfig;
4779
- //# sourceMappingURL=chunk-4KFEMXTV.cjs.map
4780
- //# sourceMappingURL=chunk-4KFEMXTV.cjs.map
5592
+ exports.searchTool = searchTool;
5593
+ exports.writeFileTool = writeFileTool;
5594
+ //# sourceMappingURL=chunk-S4VVZI4E.cjs.map
5595
+ //# sourceMappingURL=chunk-S4VVZI4E.cjs.map