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