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