@mastra/code-sdk 0.0.0-esbuild-bundle-worker-20260807173433
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 +1349 -0
- package/LICENSE.md +30 -0
- package/README.md +70 -0
- package/dist/acp/agent.d.ts +35 -0
- package/dist/acp/agent.d.ts.map +1 -0
- package/dist/acp/agent.js +161 -0
- package/dist/acp/agent.js.map +1 -0
- package/dist/acp/event-mapper.d.ts +22 -0
- package/dist/acp/event-mapper.d.ts.map +1 -0
- package/dist/acp/event-mapper.js +214 -0
- package/dist/acp/event-mapper.js.map +1 -0
- package/dist/acp/index.d.ts +8 -0
- package/dist/acp/index.d.ts.map +1 -0
- package/dist/acp/index.js +65 -0
- package/dist/acp/index.js.map +1 -0
- package/dist/acp/server.d.ts +7 -0
- package/dist/acp/server.d.ts.map +1 -0
- package/dist/acp/server.js +35 -0
- package/dist/acp/server.js.map +1 -0
- package/dist/acp.d.ts +2 -0
- package/dist/acp.d.ts.map +1 -0
- package/dist/acp.js +2 -0
- package/dist/agents/credential-resolver.d.ts +51 -0
- package/dist/agents/credential-resolver.d.ts.map +1 -0
- package/dist/agents/credential-resolver.js +52 -0
- package/dist/agents/credential-resolver.js.map +1 -0
- package/dist/agents/custom-provider-source.d.ts +35 -0
- package/dist/agents/custom-provider-source.d.ts.map +1 -0
- package/dist/agents/custom-provider-source.js +25 -0
- package/dist/agents/custom-provider-source.js.map +1 -0
- package/dist/agents/instructions.d.ts +6 -0
- package/dist/agents/instructions.d.ts.map +1 -0
- package/dist/agents/instructions.js +32 -0
- package/dist/agents/instructions.js.map +1 -0
- package/dist/agents/mastracode-gateway.d.ts +74 -0
- package/dist/agents/mastracode-gateway.d.ts.map +1 -0
- package/dist/agents/mastracode-gateway.js +400 -0
- package/dist/agents/mastracode-gateway.js.map +1 -0
- package/dist/agents/memory.d.ts +13 -0
- package/dist/agents/memory.d.ts.map +1 -0
- package/dist/agents/memory.js +116 -0
- package/dist/agents/memory.js.map +1 -0
- package/dist/agents/model.d.ts +59 -0
- package/dist/agents/model.d.ts.map +1 -0
- package/dist/agents/model.js +132 -0
- package/dist/agents/model.js.map +1 -0
- package/dist/agents/modes/build.d.ts +10 -0
- package/dist/agents/modes/build.d.ts.map +1 -0
- package/dist/agents/modes/build.js +44 -0
- package/dist/agents/modes/build.js.map +1 -0
- package/dist/agents/modes/explore.d.ts +10 -0
- package/dist/agents/modes/explore.d.ts.map +1 -0
- package/dist/agents/modes/explore.js +39 -0
- package/dist/agents/modes/explore.js.map +1 -0
- package/dist/agents/modes/plan.d.ts +6 -0
- package/dist/agents/modes/plan.d.ts.map +1 -0
- package/dist/agents/modes/plan.js +37 -0
- package/dist/agents/modes/plan.js.map +1 -0
- package/dist/agents/prompts/agent-instructions.d.ts +61 -0
- package/dist/agents/prompts/agent-instructions.d.ts.map +1 -0
- package/dist/agents/prompts/agent-instructions.js +201 -0
- package/dist/agents/prompts/agent-instructions.js.map +1 -0
- package/dist/agents/prompts/build.d.ts +12 -0
- package/dist/agents/prompts/build.d.ts.map +1 -0
- package/dist/agents/prompts/build.js +76 -0
- package/dist/agents/prompts/build.js.map +1 -0
- package/dist/agents/prompts/fast.d.ts +5 -0
- package/dist/agents/prompts/fast.d.ts.map +1 -0
- package/dist/agents/prompts/fast.js +26 -0
- package/dist/agents/prompts/fast.js.map +1 -0
- package/dist/agents/prompts/index.d.ts +19 -0
- package/dist/agents/prompts/index.d.ts.map +1 -0
- package/dist/agents/prompts/index.js +65 -0
- package/dist/agents/prompts/index.js.map +1 -0
- package/dist/agents/prompts/model.d.ts +5 -0
- package/dist/agents/prompts/model.d.ts.map +1 -0
- package/dist/agents/prompts/model.js +23 -0
- package/dist/agents/prompts/model.js.map +1 -0
- package/dist/agents/prompts/plan.d.ts +6 -0
- package/dist/agents/prompts/plan.d.ts.map +1 -0
- package/dist/agents/prompts/plan.js +111 -0
- package/dist/agents/prompts/plan.js.map +1 -0
- package/dist/agents/prompts/tool-guidance.d.ts +13 -0
- package/dist/agents/prompts/tool-guidance.d.ts.map +1 -0
- package/dist/agents/prompts/tool-guidance.js +149 -0
- package/dist/agents/prompts/tool-guidance.js.map +1 -0
- package/dist/agents/sandbox-filesystem.d.ts +99 -0
- package/dist/agents/sandbox-filesystem.d.ts.map +1 -0
- package/dist/agents/sandbox-filesystem.js +328 -0
- package/dist/agents/sandbox-filesystem.js.map +1 -0
- package/dist/agents/sandbox-reattach.d.ts +19 -0
- package/dist/agents/sandbox-reattach.d.ts.map +1 -0
- package/dist/agents/sandbox-reattach.js +15 -0
- package/dist/agents/sandbox-reattach.js.map +1 -0
- package/dist/agents/thread-caveman-state.d.ts +17 -0
- package/dist/agents/thread-caveman-state.d.ts.map +1 -0
- package/dist/agents/thread-caveman-state.js +107 -0
- package/dist/agents/thread-caveman-state.js.map +1 -0
- package/dist/agents/tool-availability.d.ts +10 -0
- package/dist/agents/tool-availability.d.ts.map +1 -0
- package/dist/agents/tool-availability.js +87 -0
- package/dist/agents/tool-availability.js.map +1 -0
- package/dist/agents/tools.d.ts +19 -0
- package/dist/agents/tools.d.ts.map +1 -0
- package/dist/agents/tools.js +105 -0
- package/dist/agents/tools.js.map +1 -0
- package/dist/agents/workspace.d.ts +32 -0
- package/dist/agents/workspace.d.ts.map +1 -0
- package/dist/agents/workspace.js +251 -0
- package/dist/agents/workspace.js.map +1 -0
- package/dist/analytics.d.ts +18 -0
- package/dist/analytics.d.ts.map +1 -0
- package/dist/analytics.js +163 -0
- package/dist/analytics.js.map +1 -0
- package/dist/auth/authorization-input.d.ts +16 -0
- package/dist/auth/authorization-input.d.ts.map +1 -0
- package/dist/auth/authorization-input.js +42 -0
- package/dist/auth/authorization-input.js.map +1 -0
- package/dist/auth/device-code.d.ts +93 -0
- package/dist/auth/device-code.d.ts.map +1 -0
- package/dist/auth/device-code.js +129 -0
- package/dist/auth/device-code.js.map +1 -0
- package/dist/auth/index.d.ts +10 -0
- package/dist/auth/index.d.ts.map +1 -0
- package/dist/auth/index.js +6 -0
- package/dist/auth/pkce.d.ts +13 -0
- package/dist/auth/pkce.d.ts.map +1 -0
- package/dist/auth/pkce.js +32 -0
- package/dist/auth/pkce.js.map +1 -0
- package/dist/auth/providers/anthropic.d.ts +40 -0
- package/dist/auth/providers/anthropic.d.ts.map +1 -0
- package/dist/auth/providers/anthropic.js +126 -0
- package/dist/auth/providers/anthropic.js.map +1 -0
- package/dist/auth/providers/github-copilot.d.ts +154 -0
- package/dist/auth/providers/github-copilot.d.ts.map +1 -0
- package/dist/auth/providers/github-copilot.js +340 -0
- package/dist/auth/providers/github-copilot.js.map +1 -0
- package/dist/auth/providers/openai-codex.d.ts +139 -0
- package/dist/auth/providers/openai-codex.d.ts.map +1 -0
- package/dist/auth/providers/openai-codex.js +519 -0
- package/dist/auth/providers/openai-codex.js.map +1 -0
- package/dist/auth/providers/xai.d.ts +64 -0
- package/dist/auth/providers/xai.d.ts.map +1 -0
- package/dist/auth/providers/xai.js +205 -0
- package/dist/auth/providers/xai.js.map +1 -0
- package/dist/auth/storage.d.ts +91 -0
- package/dist/auth/storage.d.ts.map +1 -0
- package/dist/auth/storage.js +197 -0
- package/dist/auth/storage.js.map +1 -0
- package/dist/auth/types.d.ts +89 -0
- package/dist/auth/types.d.ts.map +1 -0
- package/dist/auth/types.js +1 -0
- package/dist/clipboard/index.d.ts +26 -0
- package/dist/clipboard/index.d.ts.map +1 -0
- package/dist/clipboard/index.js +226 -0
- package/dist/clipboard/index.js.map +1 -0
- package/dist/constants.d.ts +10 -0
- package/dist/constants.d.ts.map +1 -0
- package/dist/constants.js +18 -0
- package/dist/constants.js.map +1 -0
- package/dist/error-classification.d.ts +10 -0
- package/dist/error-classification.d.ts.map +1 -0
- package/dist/error-classification.js +22 -0
- package/dist/error-classification.js.map +1 -0
- package/dist/evals/context-builder.d.ts +41 -0
- package/dist/evals/context-builder.d.ts.map +1 -0
- package/dist/evals/context-builder.js +161 -0
- package/dist/evals/context-builder.js.map +1 -0
- package/dist/evals/index.d.ts +4 -0
- package/dist/evals/index.d.ts.map +1 -0
- package/dist/evals/index.js +5 -0
- package/dist/evals/scorers/classify-command.d.ts +31 -0
- package/dist/evals/scorers/classify-command.d.ts.map +1 -0
- package/dist/evals/scorers/classify-command.js +73 -0
- package/dist/evals/scorers/classify-command.js.map +1 -0
- package/dist/evals/scorers/efficiency.d.ts +44 -0
- package/dist/evals/scorers/efficiency.d.ts.map +1 -0
- package/dist/evals/scorers/efficiency.js +259 -0
- package/dist/evals/scorers/efficiency.js.map +1 -0
- package/dist/evals/scorers/extract-tools.d.ts +25 -0
- package/dist/evals/scorers/extract-tools.d.ts.map +1 -0
- package/dist/evals/scorers/extract-tools.js +60 -0
- package/dist/evals/scorers/extract-tools.js.map +1 -0
- package/dist/evals/scorers/index.d.ts +3 -0
- package/dist/evals/scorers/index.d.ts.map +1 -0
- package/dist/evals/scorers/index.js +3 -0
- package/dist/evals/scorers/outcome.d.ts +49 -0
- package/dist/evals/scorers/outcome.d.ts.map +1 -0
- package/dist/evals/scorers/outcome.js +347 -0
- package/dist/evals/scorers/outcome.js.map +1 -0
- package/dist/goal-manager.d.ts +78 -0
- package/dist/goal-manager.d.ts.map +1 -0
- package/dist/goal-manager.js +273 -0
- package/dist/goal-manager.js.map +1 -0
- package/dist/goal-signal.d.ts +14 -0
- package/dist/goal-signal.d.ts.map +1 -0
- package/dist/goal-signal.js +17 -0
- package/dist/goal-signal.js.map +1 -0
- package/dist/headless/cli.d.ts +42 -0
- package/dist/headless/cli.d.ts.map +1 -0
- package/dist/headless/cli.js +195 -0
- package/dist/headless/cli.js.map +1 -0
- package/dist/headless/flags.d.ts +33 -0
- package/dist/headless/flags.d.ts.map +1 -0
- package/dist/headless/flags.js +190 -0
- package/dist/headless/flags.js.map +1 -0
- package/dist/headless/format.d.ts +38 -0
- package/dist/headless/format.d.ts.map +1 -0
- package/dist/headless/format.js +89 -0
- package/dist/headless/format.js.map +1 -0
- package/dist/headless/index.d.ts +25 -0
- package/dist/headless/index.d.ts.map +1 -0
- package/dist/headless/index.js +6 -0
- package/dist/headless/policy.d.ts +17 -0
- package/dist/headless/policy.d.ts.map +1 -0
- package/dist/headless/policy.js +42 -0
- package/dist/headless/policy.js.map +1 -0
- package/dist/headless/run-mc.d.ts +7 -0
- package/dist/headless/run-mc.d.ts.map +1 -0
- package/dist/headless/run-mc.js +346 -0
- package/dist/headless/run-mc.js.map +1 -0
- package/dist/headless/types.d.ts +158 -0
- package/dist/headless/types.d.ts.map +1 -0
- package/dist/headless/types.js +18 -0
- package/dist/headless/types.js.map +1 -0
- package/dist/hooks/config.d.ts +11 -0
- package/dist/hooks/config.d.ts.map +1 -0
- package/dist/hooks/config.js +73 -0
- package/dist/hooks/config.js.map +1 -0
- package/dist/hooks/executor.d.ts +13 -0
- package/dist/hooks/executor.d.ts.map +1 -0
- package/dist/hooks/executor.js +128 -0
- package/dist/hooks/executor.js.map +1 -0
- package/dist/hooks/index.d.ts +6 -0
- package/dist/hooks/index.d.ts.map +1 -0
- package/dist/hooks/index.js +5 -0
- package/dist/hooks/manager.d.ts +47 -0
- package/dist/hooks/manager.d.ts.map +1 -0
- package/dist/hooks/manager.js +225 -0
- package/dist/hooks/manager.js.map +1 -0
- package/dist/hooks/types.d.ts +152 -0
- package/dist/hooks/types.d.ts.map +1 -0
- package/dist/hooks/types.js +8 -0
- package/dist/hooks/types.js.map +1 -0
- package/dist/index.d.ts +258 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +740 -0
- package/dist/index.js.map +1 -0
- package/dist/ipc/ipc-reporter.d.ts +14 -0
- package/dist/ipc/ipc-reporter.d.ts.map +1 -0
- package/dist/ipc/ipc-reporter.js +21 -0
- package/dist/ipc/ipc-reporter.js.map +1 -0
- package/dist/lsp/client.d.ts +56 -0
- package/dist/lsp/client.d.ts.map +1 -0
- package/dist/lsp/client.js +251 -0
- package/dist/lsp/client.js.map +1 -0
- package/dist/lsp/index.d.ts +4 -0
- package/dist/lsp/index.d.ts.map +1 -0
- package/dist/lsp/index.js +4 -0
- package/dist/lsp/language.d.ts +9 -0
- package/dist/lsp/language.d.ts.map +1 -0
- package/dist/lsp/language.js +44 -0
- package/dist/lsp/language.js.map +1 -0
- package/dist/lsp/manager.d.ts +34 -0
- package/dist/lsp/manager.d.ts.map +1 -0
- package/dist/lsp/manager.js +82 -0
- package/dist/lsp/manager.js.map +1 -0
- package/dist/lsp/server.d.ts +27 -0
- package/dist/lsp/server.d.ts.map +1 -0
- package/dist/lsp/server.js +159 -0
- package/dist/lsp/server.js.map +1 -0
- package/dist/lsp/workspace.d.ts +9 -0
- package/dist/lsp/workspace.d.ts.map +1 -0
- package/dist/lsp/workspace.js +55 -0
- package/dist/lsp/workspace.js.map +1 -0
- package/dist/mcp/config.d.ts +54 -0
- package/dist/mcp/config.d.ts.map +1 -0
- package/dist/mcp/config.js +240 -0
- package/dist/mcp/config.js.map +1 -0
- package/dist/mcp/index.d.ts +5 -0
- package/dist/mcp/index.d.ts.map +1 -0
- package/dist/mcp/index.js +3 -0
- package/dist/mcp/manager.d.ts +98 -0
- package/dist/mcp/manager.d.ts.map +1 -0
- package/dist/mcp/manager.js +612 -0
- package/dist/mcp/manager.js.map +1 -0
- package/dist/mcp/state.d.ts +33 -0
- package/dist/mcp/state.d.ts.map +1 -0
- package/dist/mcp/state.js +88 -0
- package/dist/mcp/state.js.map +1 -0
- package/dist/mcp/types.d.ts +120 -0
- package/dist/mcp/types.d.ts.map +1 -0
- package/dist/mcp/types.js +1 -0
- package/dist/onboarding/custom-packs.d.ts +7 -0
- package/dist/onboarding/custom-packs.d.ts.map +1 -0
- package/dist/onboarding/custom-packs.js +17 -0
- package/dist/onboarding/custom-packs.js.map +1 -0
- package/dist/onboarding/custom-providers.d.ts +4 -0
- package/dist/onboarding/custom-providers.d.ts.map +1 -0
- package/dist/onboarding/custom-providers.js +30 -0
- package/dist/onboarding/custom-providers.js.map +1 -0
- package/dist/onboarding/index.d.ts +5 -0
- package/dist/onboarding/index.d.ts.map +1 -0
- package/dist/onboarding/index.js +3 -0
- package/dist/onboarding/om-settings.d.ts +25 -0
- package/dist/onboarding/om-settings.d.ts.map +1 -0
- package/dist/onboarding/om-settings.js +47 -0
- package/dist/onboarding/om-settings.js.map +1 -0
- package/dist/onboarding/packs.d.ts +46 -0
- package/dist/onboarding/packs.d.ts.map +1 -0
- package/dist/onboarding/packs.js +160 -0
- package/dist/onboarding/packs.js.map +1 -0
- package/dist/onboarding/settings.d.ts +338 -0
- package/dist/onboarding/settings.d.ts.map +1 -0
- package/dist/onboarding/settings.js +673 -0
- package/dist/onboarding/settings.js.map +1 -0
- package/dist/permissions.d.ts +55 -0
- package/dist/permissions.d.ts.map +1 -0
- package/dist/permissions.js +132 -0
- package/dist/permissions.js.map +1 -0
- package/dist/plugin.d.ts +81 -0
- package/dist/plugin.d.ts.map +1 -0
- package/dist/plugin.js +28 -0
- package/dist/plugin.js.map +1 -0
- package/dist/plugins/dependencies.d.ts +9 -0
- package/dist/plugins/dependencies.d.ts.map +1 -0
- package/dist/plugins/dependencies.js +88 -0
- package/dist/plugins/dependencies.js.map +1 -0
- package/dist/plugins/install.d.ts +21 -0
- package/dist/plugins/install.d.ts.map +1 -0
- package/dist/plugins/install.js +190 -0
- package/dist/plugins/install.js.map +1 -0
- package/dist/plugins/loader.d.ts +15 -0
- package/dist/plugins/loader.d.ts.map +1 -0
- package/dist/plugins/loader.js +206 -0
- package/dist/plugins/loader.js.map +1 -0
- package/dist/plugins/manager.d.ts +64 -0
- package/dist/plugins/manager.d.ts.map +1 -0
- package/dist/plugins/manager.js +384 -0
- package/dist/plugins/manager.js.map +1 -0
- package/dist/plugins/manifest.d.ts +14 -0
- package/dist/plugins/manifest.d.ts.map +1 -0
- package/dist/plugins/manifest.js +50 -0
- package/dist/plugins/manifest.js.map +1 -0
- package/dist/plugins/package-link.d.ts +3 -0
- package/dist/plugins/package-link.d.ts.map +1 -0
- package/dist/plugins/package-link.js +61 -0
- package/dist/plugins/package-link.js.map +1 -0
- package/dist/plugins/paths.d.ts +10 -0
- package/dist/plugins/paths.d.ts.map +1 -0
- package/dist/plugins/paths.js +25 -0
- package/dist/plugins/paths.js.map +1 -0
- package/dist/plugins/registry.d.ts +8 -0
- package/dist/plugins/registry.d.ts.map +1 -0
- package/dist/plugins/registry.js +112 -0
- package/dist/plugins/registry.js.map +1 -0
- package/dist/plugins/scaffold.d.ts +10 -0
- package/dist/plugins/scaffold.d.ts.map +1 -0
- package/dist/plugins/scaffold.js +116 -0
- package/dist/plugins/scaffold.js.map +1 -0
- package/dist/plugins/types.d.ts +45 -0
- package/dist/plugins/types.d.ts.map +1 -0
- package/dist/plugins/types.js +1 -0
- package/dist/processors/plan-rejection-abort.d.ts +12 -0
- package/dist/processors/plan-rejection-abort.d.ts.map +1 -0
- package/dist/processors/plan-rejection-abort.js +54 -0
- package/dist/processors/plan-rejection-abort.js.map +1 -0
- package/dist/providers/amazon-bedrock-gateway.d.ts +47 -0
- package/dist/providers/amazon-bedrock-gateway.d.ts.map +1 -0
- package/dist/providers/amazon-bedrock-gateway.js +152 -0
- package/dist/providers/amazon-bedrock-gateway.js.map +1 -0
- package/dist/providers/amazon-bedrock.d.ts +28 -0
- package/dist/providers/amazon-bedrock.d.ts.map +1 -0
- package/dist/providers/amazon-bedrock.js +83 -0
- package/dist/providers/amazon-bedrock.js.map +1 -0
- package/dist/providers/claude-max.d.ts +52 -0
- package/dist/providers/claude-max.d.ts.map +1 -0
- package/dist/providers/claude-max.js +171 -0
- package/dist/providers/claude-max.js.map +1 -0
- package/dist/providers/github-copilot.d.ts +60 -0
- package/dist/providers/github-copilot.d.ts.map +1 -0
- package/dist/providers/github-copilot.js +265 -0
- package/dist/providers/github-copilot.js.map +1 -0
- package/dist/providers/openai-codex.d.ts +72 -0
- package/dist/providers/openai-codex.d.ts.map +1 -0
- package/dist/providers/openai-codex.js +302 -0
- package/dist/providers/openai-codex.js.map +1 -0
- package/dist/providers/xai.d.ts +33 -0
- package/dist/providers/xai.d.ts.map +1 -0
- package/dist/providers/xai.js +70 -0
- package/dist/providers/xai.js.map +1 -0
- package/dist/schema.d.ts +192 -0
- package/dist/schema.d.ts.map +1 -0
- package/dist/schema.js +98 -0
- package/dist/schema.js.map +1 -0
- package/dist/theme-palette.d.ts +17 -0
- package/dist/theme-palette.d.ts.map +1 -0
- package/dist/theme-palette.js +21 -0
- package/dist/theme-palette.js.map +1 -0
- package/dist/tool-names.d.ts +73 -0
- package/dist/tool-names.d.ts.map +1 -0
- package/dist/tool-names.js +52 -0
- package/dist/tool-names.js.map +1 -0
- package/dist/tools/index.d.ts +6 -0
- package/dist/tools/index.d.ts.map +1 -0
- package/dist/tools/index.js +3 -0
- package/dist/tools/request-sandbox-access.d.ts +6 -0
- package/dist/tools/request-sandbox-access.d.ts.map +1 -0
- package/dist/tools/request-sandbox-access.js +79 -0
- package/dist/tools/request-sandbox-access.js.map +1 -0
- package/dist/tools/utils.d.ts +20 -0
- package/dist/tools/utils.d.ts.map +1 -0
- package/dist/tools/utils.js +32 -0
- package/dist/tools/utils.js.map +1 -0
- package/dist/tools/web-search.d.ts +35 -0
- package/dist/tools/web-search.d.ts.map +1 -0
- package/dist/tools/web-search.js +61 -0
- package/dist/tools/web-search.js.map +1 -0
- package/dist/utils/binaries.d.ts +7 -0
- package/dist/utils/binaries.d.ts.map +1 -0
- package/dist/utils/binaries.js +78 -0
- package/dist/utils/binaries.js.map +1 -0
- package/dist/utils/debug-log.d.ts +12 -0
- package/dist/utils/debug-log.d.ts.map +1 -0
- package/dist/utils/debug-log.js +59 -0
- package/dist/utils/debug-log.js.map +1 -0
- package/dist/utils/errors.d.ts +36 -0
- package/dist/utils/errors.d.ts.map +1 -0
- package/dist/utils/errors.js +172 -0
- package/dist/utils/errors.js.map +1 -0
- package/dist/utils/gateway-sync.d.ts +26 -0
- package/dist/utils/gateway-sync.d.ts.map +1 -0
- package/dist/utils/gateway-sync.js +61 -0
- package/dist/utils/gateway-sync.js.map +1 -0
- package/dist/utils/path-security.d.ts +2 -0
- package/dist/utils/path-security.d.ts.map +1 -0
- package/dist/utils/path-security.js +10 -0
- package/dist/utils/path-security.js.map +1 -0
- package/dist/utils/plan-diff.d.ts +25 -0
- package/dist/utils/plan-diff.d.ts.map +1 -0
- package/dist/utils/plan-diff.js +62 -0
- package/dist/utils/plan-diff.js.map +1 -0
- package/dist/utils/plans.d.ts +56 -0
- package/dist/utils/plans.d.ts.map +1 -0
- package/dist/utils/plans.js +117 -0
- package/dist/utils/plans.js.map +1 -0
- package/dist/utils/project.d.ts +160 -0
- package/dist/utils/project.d.ts.map +1 -0
- package/dist/utils/project.js +364 -0
- package/dist/utils/project.js.map +1 -0
- package/dist/utils/signals-pubsub.d.ts +41 -0
- package/dist/utils/signals-pubsub.d.ts.map +1 -0
- package/dist/utils/signals-pubsub.js +122 -0
- package/dist/utils/signals-pubsub.js.map +1 -0
- package/dist/utils/slash-command-loader.d.ts +52 -0
- package/dist/utils/slash-command-loader.d.ts.map +1 -0
- package/dist/utils/slash-command-loader.js +141 -0
- package/dist/utils/slash-command-loader.js.map +1 -0
- package/dist/utils/slash-command-processor.d.ts +14 -0
- package/dist/utils/slash-command-processor.d.ts.map +1 -0
- package/dist/utils/slash-command-processor.js +105 -0
- package/dist/utils/slash-command-processor.js.map +1 -0
- package/dist/utils/stdin-pipe.d.ts +33 -0
- package/dist/utils/stdin-pipe.d.ts.map +1 -0
- package/dist/utils/stdin-pipe.js +96 -0
- package/dist/utils/stdin-pipe.js.map +1 -0
- package/dist/utils/storage-factory.d.ts +50 -0
- package/dist/utils/storage-factory.d.ts.map +1 -0
- package/dist/utils/storage-factory.js +121 -0
- package/dist/utils/storage-factory.js.map +1 -0
- package/dist/utils/storage-maintenance.d.ts +103 -0
- package/dist/utils/storage-maintenance.d.ts.map +1 -0
- package/dist/utils/storage-maintenance.js +274 -0
- package/dist/utils/storage-maintenance.js.map +1 -0
- package/dist/utils/thread-lock.d.ts +26 -0
- package/dist/utils/thread-lock.d.ts.map +1 -0
- package/dist/utils/thread-lock.js +112 -0
- package/dist/utils/thread-lock.js.map +1 -0
- package/dist/utils/token-estimator.d.ts +3 -0
- package/dist/utils/token-estimator.d.ts.map +1 -0
- package/dist/utils/token-estimator.js +21 -0
- package/dist/utils/token-estimator.js.map +1 -0
- package/dist/utils/update-check.d.ts +101 -0
- package/dist/utils/update-check.d.ts.map +1 -0
- package/dist/utils/update-check.js +383 -0
- package/dist/utils/update-check.js.map +1 -0
- package/dist/voice/stt-registry.d.ts +64 -0
- package/dist/voice/stt-registry.d.ts.map +1 -0
- package/dist/voice/stt-registry.js +144 -0
- package/dist/voice/stt-registry.js.map +1 -0
- package/package.json +96 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"storage-factory.js","names":[],"sources":["../../src/utils/storage-factory.ts"],"sourcesContent":["/**\n * Storage factory — creates the appropriate storage backend based on resolved config.\n *\n * If PG is selected but fails to connect, falls back to LibSQL so the TUI\n * can start and the user can fix the connection via /settings.\n */\n\nimport type { MastraCompositeStore, RetentionConfig } from '@mastra/core/storage';\nimport type { MastraVector } from '@mastra/core/vector';\nimport { LibSQLStore, LibSQLVector } from '@mastra/libsql';\nimport { PostgresStore } from '@mastra/pg';\n\nimport type { StorageConfig, PgStorageConfig } from './project.js';\nimport { getDatabasePath, getVectorDatabasePath } from './project.js';\nimport { DEFAULT_RETENTION } from './storage-maintenance.js';\n\nexport const MASTRA_CODE_LOCAL_PRAGMAS = {\n cacheSize: -128000,\n mmapSize: 536870912,\n};\n\n/**\n * Construct a LibSQL store for an arbitrary url/authToken, applying the same\n * local pragmas the default factory uses.\n */\nexport function buildLibSQLStore(opts: {\n id?: string;\n url: string;\n authToken?: string;\n retention?: RetentionConfig;\n}): MastraCompositeStore {\n return new LibSQLStore({\n id: opts.id ?? 'mastra-code-storage',\n url: opts.url,\n ...(opts.authToken ? { authToken: opts.authToken } : {}),\n ...(opts.retention ? { retention: opts.retention } : {}),\n localPragmas: MASTRA_CODE_LOCAL_PRAGMAS,\n });\n}\n\n/** Construct a LibSQL vector store for an arbitrary url/authToken. */\nexport function buildLibSQLVector(opts: { id?: string; url: string; authToken?: string }): MastraVector {\n return new LibSQLVector({\n id: opts.id ?? 'mastra-code-vectors',\n url: opts.url,\n ...(opts.authToken ? { authToken: opts.authToken } : {}),\n });\n}\n\nexport interface StorageResult {\n storage: MastraCompositeStore;\n /** The effective backend after any fallback logic has run. */\n backend: 'libsql' | 'pg';\n /** Non-null when PG was requested but failed — contains a user-facing warning. */\n warning?: string;\n}\n\nfunction createFallbackLibSQL(): MastraCompositeStore {\n return buildLibSQLStore({ url: `file:${getDatabasePath()}`, retention: DEFAULT_RETENTION });\n}\n\n/**\n * Create a storage instance from the resolved config.\n *\n * - `libsql` backend → LibSQLStore (always available)\n * - `pg` backend → PostgresStore, falls back to LibSQL on connection failure\n */\nexport async function createStorage(config: StorageConfig): Promise<StorageResult> {\n if (config.backend === 'pg') {\n return createPgStorage(config);\n }\n\n // Default: LibSQL\n return {\n storage: buildLibSQLStore({ url: config.url, authToken: config.authToken, retention: DEFAULT_RETENTION }),\n backend: 'libsql',\n };\n}\n\nasync function createPgStorage(config: PgStorageConfig): Promise<StorageResult> {\n // No connection info → fall back with guidance\n if (!config.connectionString && !config.host) {\n return {\n storage: createFallbackLibSQL(),\n backend: 'libsql',\n warning:\n 'PostgreSQL backend selected but no connection info configured. ' +\n 'Using LibSQL fallback. Set a connection string via /settings.',\n };\n }\n\n const base = {\n id: 'mastra-code-storage' as const,\n retention: DEFAULT_RETENTION,\n ...(config.schemaName ? { schemaName: config.schemaName } : {}),\n ...(config.disableInit ? { disableInit: config.disableInit } : {}),\n ...(config.skipDefaultIndexes ? { skipDefaultIndexes: config.skipDefaultIndexes } : {}),\n };\n\n const store = config.connectionString\n ? new PostgresStore({ ...base, connectionString: config.connectionString })\n : new PostgresStore({\n ...base,\n host: config.host!,\n port: config.port,\n database: config.database,\n user: config.user,\n password: config.password,\n });\n\n // Test the connection before committing — if it fails, fall back to LibSQL\n // so the user can fix the config via /settings.\n try {\n await store.init();\n } catch (err: any) {\n const msg = err?.message ?? String(err);\n const target = config.connectionString ?? `${config.host}:${config.port ?? 5432}`;\n try {\n await store.close();\n } catch {\n // ignore cleanup errors\n }\n return {\n storage: createFallbackLibSQL(),\n backend: 'libsql',\n warning:\n `Failed to connect to PostgreSQL at ${target}: ${msg}\\n` +\n 'Using LibSQL fallback. Fix the connection via /settings.',\n };\n }\n\n return { storage: store, backend: 'pg' };\n}\n\n/**\n * Create a vector store for recall search.\n * Uses a separate LibSQL file to avoid bloating the main storage DB with embedding data.\n * For PG backends, reuses the same connection (PG handles the extra tables fine).\n */\nexport async function createVectorStore(\n config: StorageConfig,\n effectiveBackend: 'libsql' | 'pg' = config.backend,\n): Promise<MastraVector | undefined> {\n if (effectiveBackend === 'pg') {\n // PG can handle vector tables in the same database\n const pgConfig = config as PgStorageConfig;\n if (!pgConfig.connectionString && !pgConfig.host) return undefined;\n\n const { PgVector } = await import('@mastra/pg');\n return new PgVector({\n id: 'mastra-code-vectors',\n connectionString:\n pgConfig.connectionString ??\n `postgresql://${pgConfig.user}:${pgConfig.password}@${pgConfig.host}:${pgConfig.port ?? 5432}/${pgConfig.database}`,\n });\n }\n\n // LibSQL: separate file for vectors. Per-tenant configs supply an explicit\n // vectorUrl so each tenant's recall vectors are isolated; otherwise use the\n // shared default file.\n const libsqlConfig = config as { vectorUrl?: string; vectorAuthToken?: string };\n return buildLibSQLVector({\n url: libsqlConfig.vectorUrl ?? `file:${getVectorDatabasePath()}`,\n authToken: libsqlConfig.vectorAuthToken,\n });\n}\n"],"mappings":";;;;;AAgBA,MAAa,4BAA4B;CACvC,WAAW;CACX,UAAU;AACZ;;;;;AAMA,SAAgB,iBAAiB,MAKR;CACvB,OAAO,IAAI,YAAY;EACrB,IAAI,KAAK,MAAM;EACf,KAAK,KAAK;EACV,GAAI,KAAK,YAAY,EAAE,WAAW,KAAK,UAAU,IAAI,CAAC;EACtD,GAAI,KAAK,YAAY,EAAE,WAAW,KAAK,UAAU,IAAI,CAAC;EACtD,cAAc;CAChB,CAAC;AACH;;AAGA,SAAgB,kBAAkB,MAAsE;CACtG,OAAO,IAAI,aAAa;EACtB,IAAI,KAAK,MAAM;EACf,KAAK,KAAK;EACV,GAAI,KAAK,YAAY,EAAE,WAAW,KAAK,UAAU,IAAI,CAAC;CACxD,CAAC;AACH;AAUA,SAAS,uBAA6C;CACpD,OAAO,iBAAiB;EAAE,KAAK,QAAQ,gBAAgB;EAAK,WAAW;CAAkB,CAAC;AAC5F;;;;;;;AAQA,eAAsB,cAAc,QAA+C;CACjF,IAAI,OAAO,YAAY,MACrB,OAAO,gBAAgB,MAAM;CAI/B,OAAO;EACL,SAAS,iBAAiB;GAAE,KAAK,OAAO;GAAK,WAAW,OAAO;GAAW,WAAW;EAAkB,CAAC;EACxG,SAAS;CACX;AACF;AAEA,eAAe,gBAAgB,QAAiD;CAE9E,IAAI,CAAC,OAAO,oBAAoB,CAAC,OAAO,MACtC,OAAO;EACL,SAAS,qBAAqB;EAC9B,SAAS;EACT,SACE;CAEJ;CAGF,MAAM,OAAO;EACX,IAAI;EACJ,WAAW;EACX,GAAI,OAAO,aAAa,EAAE,YAAY,OAAO,WAAW,IAAI,CAAC;EAC7D,GAAI,OAAO,cAAc,EAAE,aAAa,OAAO,YAAY,IAAI,CAAC;EAChE,GAAI,OAAO,qBAAqB,EAAE,oBAAoB,OAAO,mBAAmB,IAAI,CAAC;CACvF;CAEA,MAAM,QAAQ,OAAO,mBACjB,IAAI,cAAc;EAAE,GAAG;EAAM,kBAAkB,OAAO;CAAiB,CAAC,IACxE,IAAI,cAAc;EAChB,GAAG;EACH,MAAM,OAAO;EACb,MAAM,OAAO;EACb,UAAU,OAAO;EACjB,MAAM,OAAO;EACb,UAAU,OAAO;CACnB,CAAC;CAIL,IAAI;EACF,MAAM,MAAM,KAAK;CACnB,SAAS,KAAU;EACjB,MAAM,MAAM,KAAK,WAAW,OAAO,GAAG;EACtC,MAAM,SAAS,OAAO,oBAAoB,GAAG,OAAO,KAAK,GAAG,OAAO,QAAQ;EAC3E,IAAI;GACF,MAAM,MAAM,MAAM;EACpB,QAAQ,CAER;EACA,OAAO;GACL,SAAS,qBAAqB;GAC9B,SAAS;GACT,SACE,sCAAsC,OAAO,IAAI,IAAI;EAEzD;CACF;CAEA,OAAO;EAAE,SAAS;EAAO,SAAS;CAAK;AACzC;;;;;;AAOA,eAAsB,kBACpB,QACA,mBAAoC,OAAO,SACR;CACnC,IAAI,qBAAqB,MAAM;EAE7B,MAAM,WAAW;EACjB,IAAI,CAAC,SAAS,oBAAoB,CAAC,SAAS,MAAM,OAAO,KAAA;EAEzD,MAAM,EAAE,aAAa,MAAM,OAAO;EAClC,OAAO,IAAI,SAAS;GAClB,IAAI;GACJ,kBACE,SAAS,oBACT,gBAAgB,SAAS,KAAK,GAAG,SAAS,SAAS,GAAG,SAAS,KAAK,GAAG,SAAS,QAAQ,KAAK,GAAG,SAAS;EAC7G,CAAC;CACH;CAKA,MAAM,eAAe;CACrB,OAAO,kBAAkB;EACvB,KAAK,aAAa,aAAa,QAAQ,sBAAsB;EAC7D,WAAW,aAAa;CAC1B,CAAC;AACH"}
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Storage maintenance — retention defaults and disk reclamation for mastracode.
|
|
3
|
+
*
|
|
4
|
+
* Retention (`DEFAULT_RETENTION`) is wired into the inner store by the storage
|
|
5
|
+
* factory so `storage.prune()` can delete aged rows across every domain,
|
|
6
|
+
* including the legacy libsql observability spans table.
|
|
7
|
+
*
|
|
8
|
+
* `prune()` only deletes rows — SQLite keeps the freed pages inside the file.
|
|
9
|
+
* `reclaimLibSQLDisk()` returns that space to the OS by streaming a
|
|
10
|
+
* `VACUUM INTO` copy of each file and swapping it into place. It requires all
|
|
11
|
+
* connections to be closed first, so it only runs from the post-TUI
|
|
12
|
+
* maintenance flow. We deliberately do NOT expose VACUUM as a public API on
|
|
13
|
+
* LibSQLStore.
|
|
14
|
+
*/
|
|
15
|
+
import type { MastraCompositeStore, PruneOptions, PruneResult, RetentionConfig } from '@mastra/core/storage';
|
|
16
|
+
import type { StorageConfig } from './project.js';
|
|
17
|
+
/**
|
|
18
|
+
* Default retention for mastracode's own storage. Tables not listed are kept
|
|
19
|
+
* forever. Ages chosen for a local dev tool where observability data dwarfs
|
|
20
|
+
* everything else (spans have been observed at 17+ GB):
|
|
21
|
+
* - spans/logs: short-lived debugging telemetry → 14d
|
|
22
|
+
* - scorer results & workflow snapshots: medium-lived run records → 30d
|
|
23
|
+
* - chat history (messages/threads): long-lived, user-visible → 90d
|
|
24
|
+
*/
|
|
25
|
+
export declare const DEFAULT_RETENTION: RetentionConfig;
|
|
26
|
+
/** Result of reclaiming disk for one database file (sizes include the WAL). */
|
|
27
|
+
export interface ReclaimResult {
|
|
28
|
+
file: string;
|
|
29
|
+
bytesBefore: number;
|
|
30
|
+
bytesAfter: number;
|
|
31
|
+
}
|
|
32
|
+
/** Handle the TUI uses to run storage maintenance without reaching into store internals. */
|
|
33
|
+
export interface StorageMaintenance {
|
|
34
|
+
backend: 'libsql' | 'pg';
|
|
35
|
+
/** Retention policies in effect (for display in /prune output). */
|
|
36
|
+
retention: RetentionConfig;
|
|
37
|
+
/** Delete rows older than the retention policies. Safe to re-run; batched and cancellable. `options.retention` replaces the standing policies for this call only. */
|
|
38
|
+
prune: (options?: PruneOptions) => Promise<PruneResult[]>;
|
|
39
|
+
/** Close the long-lived storage connection (checkpoints WAL for local libsql). Nothing can use the store afterwards. */
|
|
40
|
+
closeStorage?: () => Promise<void>;
|
|
41
|
+
/** Compact local libsql files (VACUUM INTO + swap) to return freed pages to the OS. Only set for local libsql backends. */
|
|
42
|
+
reclaimDisk?: (onFileStart?: (file: string, bytesBefore: number, liveBytes: number) => void) => Promise<ReclaimResult[]>;
|
|
43
|
+
}
|
|
44
|
+
/** Free bytes required before compacting: the compacted copy plus 20% + 256 MB headroom. */
|
|
45
|
+
export declare function requiredFreeBytes(liveBytes: number): number;
|
|
46
|
+
/**
|
|
47
|
+
* Compact each local libsql db file by streaming a `VACUUM INTO` copy next to
|
|
48
|
+
* it, then swapping the copy into place. Reports before/after sizes.
|
|
49
|
+
*
|
|
50
|
+
* Deliberately NOT an in-place `VACUUM`: that copies all live content into a
|
|
51
|
+
* temp store (held in memory by libsql builds with `temp_store=MEMORY`) and
|
|
52
|
+
* then rewrites the entire database back through the WAL — on multi-GB
|
|
53
|
+
* databases that has frozen whole machines via memory/IO exhaustion.
|
|
54
|
+
* `VACUUM INTO` reads the logical database once and writes the compacted copy
|
|
55
|
+
* sequentially with bounded memory, and the WAL never grows.
|
|
56
|
+
*
|
|
57
|
+
* Each file is preflighted: live bytes are estimated from
|
|
58
|
+
* `page_count - freelist_count` and the copy is refused unless the volume has
|
|
59
|
+
* `requiredFreeBytes()` available.
|
|
60
|
+
*
|
|
61
|
+
* MUST run with every connection to these files closed (the swap replaces the
|
|
62
|
+
* inode — a surviving connection would keep writing to the unlinked old file).
|
|
63
|
+
* `runStorageMaintenance()` closes storage before calling this.
|
|
64
|
+
*/
|
|
65
|
+
export declare function reclaimLibSQLDisk(dbFiles: string[], onFileStart?: (file: string, bytesBefore: number, liveBytes: number) => void): Promise<ReclaimResult[]>;
|
|
66
|
+
/**
|
|
67
|
+
* Resolve the local libsql db files eligible for checkpoint + VACUUM.
|
|
68
|
+
* Returns [] for remote (turso) urls and PG backends. When PG was requested
|
|
69
|
+
* but the factory fell back to libsql, the fallback uses the default files.
|
|
70
|
+
*/
|
|
71
|
+
export declare function resolveLocalDbFiles(config: StorageConfig, effectiveBackend: 'libsql' | 'pg'): string[];
|
|
72
|
+
/**
|
|
73
|
+
* Build the maintenance handle for the effective storage backend.
|
|
74
|
+
* `localDbFiles` are the local libsql file paths eligible for VACUUM;
|
|
75
|
+
* pass an empty array for remote (turso) or PG backends.
|
|
76
|
+
*/
|
|
77
|
+
export declare function createStorageMaintenance(opts: {
|
|
78
|
+
storage: MastraCompositeStore;
|
|
79
|
+
backend: 'libsql' | 'pg';
|
|
80
|
+
retention: RetentionConfig;
|
|
81
|
+
localDbFiles: string[];
|
|
82
|
+
/** Closes the vector store's connection; required for local libsql so the compaction swap isn't blocked by our own handle. */
|
|
83
|
+
closeVector?: () => Promise<void>;
|
|
84
|
+
}): StorageMaintenance;
|
|
85
|
+
export declare function formatBytes(bytes: number): string;
|
|
86
|
+
/**
|
|
87
|
+
* Run storage maintenance with exclusive access to the database, reporting
|
|
88
|
+
* progress through `log` (one line per pass so large deletes are visible).
|
|
89
|
+
*
|
|
90
|
+
* Must run AFTER the TUI is stopped and background writers are quiesced —
|
|
91
|
+
* batched deletes and VACUUM contend with live writes (SQLITE_BUSY) and can
|
|
92
|
+
* starve the event loop that renders the TUI. The storage connection is
|
|
93
|
+
* closed at the end (checkpointing the WAL for local libsql), so the process
|
|
94
|
+
* must exit afterwards.
|
|
95
|
+
*/
|
|
96
|
+
export declare function runStorageMaintenance(opts: {
|
|
97
|
+
maintenance: StorageMaintenance;
|
|
98
|
+
vacuum: boolean;
|
|
99
|
+
/** Skip the memory domain (messages/threads) so chat history is preserved. */
|
|
100
|
+
keepMemory?: boolean;
|
|
101
|
+
log: (line: string) => void;
|
|
102
|
+
}): Promise<void>;
|
|
103
|
+
//# sourceMappingURL=storage-maintenance.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"storage-maintenance.d.ts","sourceRoot":"","sources":["../../src/utils/storage-maintenance.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAKH,OAAO,KAAK,EAAE,oBAAoB,EAAE,YAAY,EAAE,WAAW,EAAE,eAAe,EAAE,MAAM,sBAAsB,CAAC;AAQ7G,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,cAAc,CAAC;AAGlD;;;;;;;GAOG;AACH,eAAO,MAAM,iBAAiB,EAAE,eAe/B,CAAC;AAEF,+EAA+E;AAC/E,MAAM,WAAW,aAAa;IAC5B,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,EAAE,MAAM,CAAC;IACpB,UAAU,EAAE,MAAM,CAAC;CACpB;AAED,4FAA4F;AAC5F,MAAM,WAAW,kBAAkB;IACjC,OAAO,EAAE,QAAQ,GAAG,IAAI,CAAC;IACzB,mEAAmE;IACnE,SAAS,EAAE,eAAe,CAAC;IAC3B,qKAAqK;IACrK,KAAK,EAAE,CAAC,OAAO,CAAC,EAAE,YAAY,KAAK,OAAO,CAAC,WAAW,EAAE,CAAC,CAAC;IAC1D,wHAAwH;IACxH,YAAY,CAAC,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;IACnC,2HAA2H;IAC3H,WAAW,CAAC,EAAE,CACZ,WAAW,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,KAAK,IAAI,KACzE,OAAO,CAAC,aAAa,EAAE,CAAC,CAAC;CAC/B;AAeD,4FAA4F;AAC5F,wBAAgB,iBAAiB,CAAC,SAAS,EAAE,MAAM,GAAG,MAAM,CAE3D;AA0BD;;;;;;;;;;;;;;;;;;GAkBG;AACH,wBAAsB,iBAAiB,CACrC,OAAO,EAAE,MAAM,EAAE,EACjB,WAAW,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,KAAK,IAAI,GAC3E,OAAO,CAAC,aAAa,EAAE,CAAC,CA0G1B;AAQD;;;;GAIG;AACH,wBAAgB,mBAAmB,CAAC,MAAM,EAAE,aAAa,EAAE,gBAAgB,EAAE,QAAQ,GAAG,IAAI,GAAG,MAAM,EAAE,CAatG;AAED;;;;GAIG;AACH,wBAAgB,wBAAwB,CAAC,IAAI,EAAE;IAC7C,OAAO,EAAE,oBAAoB,CAAC;IAC9B,OAAO,EAAE,QAAQ,GAAG,IAAI,CAAC;IACzB,SAAS,EAAE,eAAe,CAAC;IAC3B,YAAY,EAAE,MAAM,EAAE,CAAC;IACvB,8HAA8H;IAC9H,WAAW,CAAC,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;CACnC,GAAG,kBAAkB,CAiBrB;AAED,wBAAgB,WAAW,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAWjD;AAKD;;;;;;;;;GASG;AACH,wBAAsB,qBAAqB,CAAC,IAAI,EAAE;IAChD,WAAW,EAAE,kBAAkB,CAAC;IAChC,MAAM,EAAE,OAAO,CAAC;IAChB,8EAA8E;IAC9E,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,GAAG,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAC;CAC7B,GAAG,OAAO,CAAC,IAAI,CAAC,CA2EhB"}
|
|
@@ -0,0 +1,274 @@
|
|
|
1
|
+
import { getDatabasePath, getVectorDatabasePath } from "./project.js";
|
|
2
|
+
import { closeSync, existsSync, openSync, readSync, renameSync, rmSync, statSync, statfsSync } from "fs";
|
|
3
|
+
import { dirname } from "path";
|
|
4
|
+
import Database from "libsql";
|
|
5
|
+
//#region src/utils/storage-maintenance.ts
|
|
6
|
+
/**
|
|
7
|
+
* Storage maintenance — retention defaults and disk reclamation for mastracode.
|
|
8
|
+
*
|
|
9
|
+
* Retention (`DEFAULT_RETENTION`) is wired into the inner store by the storage
|
|
10
|
+
* factory so `storage.prune()` can delete aged rows across every domain,
|
|
11
|
+
* including the legacy libsql observability spans table.
|
|
12
|
+
*
|
|
13
|
+
* `prune()` only deletes rows — SQLite keeps the freed pages inside the file.
|
|
14
|
+
* `reclaimLibSQLDisk()` returns that space to the OS by streaming a
|
|
15
|
+
* `VACUUM INTO` copy of each file and swapping it into place. It requires all
|
|
16
|
+
* connections to be closed first, so it only runs from the post-TUI
|
|
17
|
+
* maintenance flow. We deliberately do NOT expose VACUUM as a public API on
|
|
18
|
+
* LibSQLStore.
|
|
19
|
+
*/
|
|
20
|
+
/**
|
|
21
|
+
* Default retention for mastracode's own storage. Tables not listed are kept
|
|
22
|
+
* forever. Ages chosen for a local dev tool where observability data dwarfs
|
|
23
|
+
* everything else (spans have been observed at 17+ GB):
|
|
24
|
+
* - spans/logs: short-lived debugging telemetry → 14d
|
|
25
|
+
* - scorer results & workflow snapshots: medium-lived run records → 30d
|
|
26
|
+
* - chat history (messages/threads): long-lived, user-visible → 90d
|
|
27
|
+
*/
|
|
28
|
+
const DEFAULT_RETENTION = {
|
|
29
|
+
memory: {
|
|
30
|
+
messages: { maxAge: "90d" },
|
|
31
|
+
threads: { maxAge: "90d" }
|
|
32
|
+
},
|
|
33
|
+
observability: {
|
|
34
|
+
spans: { maxAge: "14d" },
|
|
35
|
+
logs: { maxAge: "14d" }
|
|
36
|
+
},
|
|
37
|
+
scores: { scorers: { maxAge: "30d" } },
|
|
38
|
+
workflows: { workflowSnapshot: { maxAge: "30d" } }
|
|
39
|
+
};
|
|
40
|
+
/** db file size + its WAL sidecar, in bytes. Missing files count as 0. */
|
|
41
|
+
function fileSizeWithWal(dbFile) {
|
|
42
|
+
let total = 0;
|
|
43
|
+
for (const candidate of [dbFile, `${dbFile}-wal`]) try {
|
|
44
|
+
total += statSync(candidate).size;
|
|
45
|
+
} catch {}
|
|
46
|
+
return total;
|
|
47
|
+
}
|
|
48
|
+
/** Free bytes required before compacting: the compacted copy plus 20% + 256 MB headroom. */
|
|
49
|
+
function requiredFreeBytes(liveBytes) {
|
|
50
|
+
return Math.ceil(liveBytes * 1.2) + 256 * 1024 * 1024;
|
|
51
|
+
}
|
|
52
|
+
/** First column of the first row of a PRAGMA result, as a number. */
|
|
53
|
+
function pragmaNumber(db, pragma) {
|
|
54
|
+
const row = db.pragma(pragma)?.[0];
|
|
55
|
+
return row ? Number(Object.values(row)[0]) : 0;
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* Journal mode straight from the SQLite file header (bytes 18/19:
|
|
59
|
+
* 1 = rollback journal, 2 = WAL) — readable without opening a connection.
|
|
60
|
+
*/
|
|
61
|
+
function journalModeFromHeader(file) {
|
|
62
|
+
const fd = openSync(file, "r");
|
|
63
|
+
try {
|
|
64
|
+
const buf = Buffer.alloc(2);
|
|
65
|
+
if (readSync(fd, buf, 0, 2, 18) !== 2) return "unknown";
|
|
66
|
+
if (buf[0] === 2 && buf[1] === 2) return "wal";
|
|
67
|
+
if (buf[0] === 1 && buf[1] === 1) return "delete";
|
|
68
|
+
return "unknown";
|
|
69
|
+
} finally {
|
|
70
|
+
closeSync(fd);
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
/**
|
|
74
|
+
* Compact each local libsql db file by streaming a `VACUUM INTO` copy next to
|
|
75
|
+
* it, then swapping the copy into place. Reports before/after sizes.
|
|
76
|
+
*
|
|
77
|
+
* Deliberately NOT an in-place `VACUUM`: that copies all live content into a
|
|
78
|
+
* temp store (held in memory by libsql builds with `temp_store=MEMORY`) and
|
|
79
|
+
* then rewrites the entire database back through the WAL — on multi-GB
|
|
80
|
+
* databases that has frozen whole machines via memory/IO exhaustion.
|
|
81
|
+
* `VACUUM INTO` reads the logical database once and writes the compacted copy
|
|
82
|
+
* sequentially with bounded memory, and the WAL never grows.
|
|
83
|
+
*
|
|
84
|
+
* Each file is preflighted: live bytes are estimated from
|
|
85
|
+
* `page_count - freelist_count` and the copy is refused unless the volume has
|
|
86
|
+
* `requiredFreeBytes()` available.
|
|
87
|
+
*
|
|
88
|
+
* MUST run with every connection to these files closed (the swap replaces the
|
|
89
|
+
* inode — a surviving connection would keep writing to the unlinked old file).
|
|
90
|
+
* `runStorageMaintenance()` closes storage before calling this.
|
|
91
|
+
*/
|
|
92
|
+
async function reclaimLibSQLDisk(dbFiles, onFileStart) {
|
|
93
|
+
const results = [];
|
|
94
|
+
for (const file of dbFiles) {
|
|
95
|
+
if (!existsSync(file) || statSync(file).size < 100) continue;
|
|
96
|
+
const bytesBefore = fileSizeWithWal(file);
|
|
97
|
+
const tmp = `${file}.vacuum-tmp`;
|
|
98
|
+
rmSync(tmp, { force: true });
|
|
99
|
+
const probe = new Database(file);
|
|
100
|
+
try {
|
|
101
|
+
probe.exec("PRAGMA busy_timeout = 2000");
|
|
102
|
+
probe.exec("PRAGMA journal_mode = WAL");
|
|
103
|
+
probe.exec("PRAGMA journal_mode = DELETE");
|
|
104
|
+
} catch {} finally {
|
|
105
|
+
probe.close();
|
|
106
|
+
}
|
|
107
|
+
if (journalModeFromHeader(file) !== "delete") throw new Error(`${file} is in use by another process — is another Mastra Code session running? Close other sessions and run /prune vacuum again.`);
|
|
108
|
+
const db = new Database(file);
|
|
109
|
+
try {
|
|
110
|
+
db.exec("PRAGMA busy_timeout = 2000");
|
|
111
|
+
const pageSize = pragmaNumber(db, "page_size");
|
|
112
|
+
const pageCount = pragmaNumber(db, "page_count");
|
|
113
|
+
const freelistCount = pragmaNumber(db, "freelist_count");
|
|
114
|
+
const liveBytes = Math.max(0, pageCount - freelistCount) * pageSize;
|
|
115
|
+
const { bsize, bavail } = statfsSync(dirname(file));
|
|
116
|
+
const freeBytes = bsize * bavail;
|
|
117
|
+
if (freeBytes < requiredFreeBytes(liveBytes)) throw new Error(`Not enough free disk space to compact ${file}: need ${formatBytes(requiredFreeBytes(liveBytes))} for a ${formatBytes(liveBytes)} compacted copy, but only ${formatBytes(freeBytes)} is free.`);
|
|
118
|
+
onFileStart?.(file, bytesBefore, liveBytes);
|
|
119
|
+
db.exec(`VACUUM INTO '${tmp.replace(/'/g, "''")}'`);
|
|
120
|
+
} catch (err) {
|
|
121
|
+
rmSync(tmp, { force: true });
|
|
122
|
+
throw err;
|
|
123
|
+
} finally {
|
|
124
|
+
db.close();
|
|
125
|
+
}
|
|
126
|
+
if (journalModeFromHeader(file) !== "delete") {
|
|
127
|
+
rmSync(tmp, { force: true });
|
|
128
|
+
throw new Error(`${file} was opened by another process during compaction — is another Mastra Code session running? Close other sessions and run /prune vacuum again.`);
|
|
129
|
+
}
|
|
130
|
+
renameSync(file, `${file}.old`);
|
|
131
|
+
try {
|
|
132
|
+
rmSync(`${file}-wal`, { force: true });
|
|
133
|
+
rmSync(`${file}-shm`, { force: true });
|
|
134
|
+
renameSync(tmp, file);
|
|
135
|
+
} catch (err) {
|
|
136
|
+
try {
|
|
137
|
+
renameSync(`${file}.old`, file);
|
|
138
|
+
} catch {
|
|
139
|
+
throw new Error(`Failed to swap compacted copy into place AND failed to restore the original — your data is intact at ${file}.old; rename it back to ${file} manually. Original error: ${err instanceof Error ? err.message : String(err)}`);
|
|
140
|
+
}
|
|
141
|
+
rmSync(tmp, { force: true });
|
|
142
|
+
throw err;
|
|
143
|
+
}
|
|
144
|
+
rmSync(`${file}.old`, { force: true });
|
|
145
|
+
results.push({
|
|
146
|
+
file,
|
|
147
|
+
bytesBefore,
|
|
148
|
+
bytesAfter: fileSizeWithWal(file)
|
|
149
|
+
});
|
|
150
|
+
}
|
|
151
|
+
return results;
|
|
152
|
+
}
|
|
153
|
+
/** `file:/path` or `file:///path` → filesystem path; undefined for non-file urls. */
|
|
154
|
+
function fileUrlToPath(url) {
|
|
155
|
+
if (!url.startsWith("file:")) return void 0;
|
|
156
|
+
return url.replace(/^file:\/\//, "").replace(/^file:/, "");
|
|
157
|
+
}
|
|
158
|
+
/**
|
|
159
|
+
* Resolve the local libsql db files eligible for checkpoint + VACUUM.
|
|
160
|
+
* Returns [] for remote (turso) urls and PG backends. When PG was requested
|
|
161
|
+
* but the factory fell back to libsql, the fallback uses the default files.
|
|
162
|
+
*/
|
|
163
|
+
function resolveLocalDbFiles(config, effectiveBackend) {
|
|
164
|
+
if (effectiveBackend !== "libsql") return [];
|
|
165
|
+
if (config.backend === "pg") return [getDatabasePath(), getVectorDatabasePath()];
|
|
166
|
+
if (config.isRemote) return [];
|
|
167
|
+
const files = [];
|
|
168
|
+
const main = fileUrlToPath(config.url);
|
|
169
|
+
if (main) files.push(main);
|
|
170
|
+
const vector = config.vectorUrl ? fileUrlToPath(config.vectorUrl) : getVectorDatabasePath();
|
|
171
|
+
if (vector) files.push(vector);
|
|
172
|
+
return files;
|
|
173
|
+
}
|
|
174
|
+
/**
|
|
175
|
+
* Build the maintenance handle for the effective storage backend.
|
|
176
|
+
* `localDbFiles` are the local libsql file paths eligible for VACUUM;
|
|
177
|
+
* pass an empty array for remote (turso) or PG backends.
|
|
178
|
+
*/
|
|
179
|
+
function createStorageMaintenance(opts) {
|
|
180
|
+
const { storage, backend, retention, localDbFiles, closeVector } = opts;
|
|
181
|
+
return {
|
|
182
|
+
backend,
|
|
183
|
+
retention,
|
|
184
|
+
prune: (options) => storage.prune(options),
|
|
185
|
+
closeStorage: async () => {
|
|
186
|
+
await storage.close?.();
|
|
187
|
+
await closeVector?.();
|
|
188
|
+
},
|
|
189
|
+
...backend === "libsql" && localDbFiles.length > 0 ? { reclaimDisk: (onFileStart) => reclaimLibSQLDisk(localDbFiles, onFileStart) } : {}
|
|
190
|
+
};
|
|
191
|
+
}
|
|
192
|
+
function formatBytes(bytes) {
|
|
193
|
+
if (bytes < 1024) return `${bytes} B`;
|
|
194
|
+
const units = [
|
|
195
|
+
"KB",
|
|
196
|
+
"MB",
|
|
197
|
+
"GB",
|
|
198
|
+
"TB"
|
|
199
|
+
];
|
|
200
|
+
let value = bytes;
|
|
201
|
+
let unit = "B";
|
|
202
|
+
for (const next of units) {
|
|
203
|
+
if (value < 1024) break;
|
|
204
|
+
value /= 1024;
|
|
205
|
+
unit = next;
|
|
206
|
+
}
|
|
207
|
+
return `${value.toFixed(1)} ${unit}`;
|
|
208
|
+
}
|
|
209
|
+
/** Rows deleted per table per pass: default batchSize (1000) × 20 batches. */
|
|
210
|
+
const PASS_MAX_BATCHES = 20;
|
|
211
|
+
/**
|
|
212
|
+
* Run storage maintenance with exclusive access to the database, reporting
|
|
213
|
+
* progress through `log` (one line per pass so large deletes are visible).
|
|
214
|
+
*
|
|
215
|
+
* Must run AFTER the TUI is stopped and background writers are quiesced —
|
|
216
|
+
* batched deletes and VACUUM contend with live writes (SQLITE_BUSY) and can
|
|
217
|
+
* starve the event loop that renders the TUI. The storage connection is
|
|
218
|
+
* closed at the end (checkpointing the WAL for local libsql), so the process
|
|
219
|
+
* must exit afterwards.
|
|
220
|
+
*/
|
|
221
|
+
async function runStorageMaintenance(opts) {
|
|
222
|
+
const { maintenance, vacuum, keepMemory = false, log } = opts;
|
|
223
|
+
const { memory: _memory, ...withoutMemory } = maintenance.retention;
|
|
224
|
+
const retentionForRun = keepMemory ? { retention: withoutMemory } : {};
|
|
225
|
+
log("Pruning rows older than the retention policies:");
|
|
226
|
+
for (const [domain, tables] of Object.entries(maintenance.retention)) for (const [table, policy] of Object.entries(tables ?? {})) log(` ${domain}.${table}: ${keepMemory && domain === "memory" ? "kept (keep-memory)" : policy.maxAge}`);
|
|
227
|
+
const totals = /* @__PURE__ */ new Map();
|
|
228
|
+
for (;;) {
|
|
229
|
+
const results = await maintenance.prune({
|
|
230
|
+
maxBatches: PASS_MAX_BATCHES,
|
|
231
|
+
...retentionForRun
|
|
232
|
+
});
|
|
233
|
+
if (results.length === 0) break;
|
|
234
|
+
let remaining = false;
|
|
235
|
+
let deletedThisPass = 0;
|
|
236
|
+
for (const r of results) {
|
|
237
|
+
const key = `${r.domain}.${r.table}`;
|
|
238
|
+
totals.set(key, (totals.get(key) ?? 0) + r.deleted);
|
|
239
|
+
deletedThisPass += r.deleted;
|
|
240
|
+
if (!r.done) remaining = true;
|
|
241
|
+
if (r.deleted > 0) log(` ${key}: ${totals.get(key)} rows deleted${r.done ? "" : " so far…"}`);
|
|
242
|
+
}
|
|
243
|
+
if (!remaining) break;
|
|
244
|
+
if (deletedThisPass === 0) {
|
|
245
|
+
log(" stopping: no progress in the last pass.");
|
|
246
|
+
break;
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
const totalDeleted = [...totals.values()].reduce((sum, n) => sum + n, 0);
|
|
250
|
+
log(totalDeleted === 0 ? "Nothing to prune — no retention-eligible rows found." : `Prune complete: ${totalDeleted} rows deleted.`);
|
|
251
|
+
await maintenance.closeStorage?.();
|
|
252
|
+
if (!vacuum) {
|
|
253
|
+
if (maintenance.reclaimDisk) log("Deleted rows free pages inside the db file but not on disk. Run /prune vacuum to reclaim disk space.");
|
|
254
|
+
return;
|
|
255
|
+
}
|
|
256
|
+
if (!maintenance.reclaimDisk) {
|
|
257
|
+
log("Disk reclamation (VACUUM) is only available for local libsql storage.");
|
|
258
|
+
return;
|
|
259
|
+
}
|
|
260
|
+
log("Reclaiming disk (compacting database files)… this can take several minutes on large databases.");
|
|
261
|
+
const reclaimed = await maintenance.reclaimDisk((file, bytesBefore, liveBytes) => {
|
|
262
|
+
log(` vacuuming ${file} (${formatBytes(bytesBefore)}, ~${formatBytes(liveBytes)} live)…`);
|
|
263
|
+
});
|
|
264
|
+
if (reclaimed.length === 0) {
|
|
265
|
+
log("No local database files found to vacuum.");
|
|
266
|
+
return;
|
|
267
|
+
}
|
|
268
|
+
for (const r of reclaimed) log(` ${r.file}: ${formatBytes(r.bytesBefore)} → ${formatBytes(r.bytesAfter)}`);
|
|
269
|
+
log(`Reclaimed ${formatBytes(reclaimed.reduce((sum, r) => sum + Math.max(0, r.bytesBefore - r.bytesAfter), 0))}.`);
|
|
270
|
+
}
|
|
271
|
+
//#endregion
|
|
272
|
+
export { DEFAULT_RETENTION, createStorageMaintenance, formatBytes, reclaimLibSQLDisk, requiredFreeBytes, resolveLocalDbFiles, runStorageMaintenance };
|
|
273
|
+
|
|
274
|
+
//# sourceMappingURL=storage-maintenance.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"storage-maintenance.js","names":[],"sources":["../../src/utils/storage-maintenance.ts"],"sourcesContent":["/**\n * Storage maintenance — retention defaults and disk reclamation for mastracode.\n *\n * Retention (`DEFAULT_RETENTION`) is wired into the inner store by the storage\n * factory so `storage.prune()` can delete aged rows across every domain,\n * including the legacy libsql observability spans table.\n *\n * `prune()` only deletes rows — SQLite keeps the freed pages inside the file.\n * `reclaimLibSQLDisk()` returns that space to the OS by streaming a\n * `VACUUM INTO` copy of each file and swapping it into place. It requires all\n * connections to be closed first, so it only runs from the post-TUI\n * maintenance flow. We deliberately do NOT expose VACUUM as a public API on\n * LibSQLStore.\n */\n\nimport { closeSync, existsSync, openSync, readSync, renameSync, rmSync, statSync, statfsSync } from 'node:fs';\nimport { dirname } from 'node:path';\n\nimport type { MastraCompositeStore, PruneOptions, PruneResult, RetentionConfig } from '@mastra/core/storage';\n// Native `libsql` driver, deliberately alongside `@libsql/client` (used by the\n// stores): maintenance needs deterministic connection close for the\n// exclusivity probe, and the wrapper's close() can leave the file lock held\n// until GC finalizes cached statements. Don't \"clean up\" either dependency.\n// Upstream report: https://github.com/tursodatabase/libsql-js/issues/228\nimport Database from 'libsql';\n\nimport type { StorageConfig } from './project.js';\nimport { getDatabasePath, getVectorDatabasePath } from './project.js';\n\n/**\n * Default retention for mastracode's own storage. Tables not listed are kept\n * forever. Ages chosen for a local dev tool where observability data dwarfs\n * everything else (spans have been observed at 17+ GB):\n * - spans/logs: short-lived debugging telemetry → 14d\n * - scorer results & workflow snapshots: medium-lived run records → 30d\n * - chat history (messages/threads): long-lived, user-visible → 90d\n */\nexport const DEFAULT_RETENTION: RetentionConfig = {\n memory: {\n messages: { maxAge: '90d' },\n threads: { maxAge: '90d' },\n },\n observability: {\n spans: { maxAge: '14d' },\n logs: { maxAge: '14d' },\n },\n scores: {\n scorers: { maxAge: '30d' },\n },\n workflows: {\n workflowSnapshot: { maxAge: '30d' },\n },\n};\n\n/** Result of reclaiming disk for one database file (sizes include the WAL). */\nexport interface ReclaimResult {\n file: string;\n bytesBefore: number;\n bytesAfter: number;\n}\n\n/** Handle the TUI uses to run storage maintenance without reaching into store internals. */\nexport interface StorageMaintenance {\n backend: 'libsql' | 'pg';\n /** Retention policies in effect (for display in /prune output). */\n retention: RetentionConfig;\n /** Delete rows older than the retention policies. Safe to re-run; batched and cancellable. `options.retention` replaces the standing policies for this call only. */\n prune: (options?: PruneOptions) => Promise<PruneResult[]>;\n /** Close the long-lived storage connection (checkpoints WAL for local libsql). Nothing can use the store afterwards. */\n closeStorage?: () => Promise<void>;\n /** Compact local libsql files (VACUUM INTO + swap) to return freed pages to the OS. Only set for local libsql backends. */\n reclaimDisk?: (\n onFileStart?: (file: string, bytesBefore: number, liveBytes: number) => void,\n ) => Promise<ReclaimResult[]>;\n}\n\n/** db file size + its WAL sidecar, in bytes. Missing files count as 0. */\nfunction fileSizeWithWal(dbFile: string): number {\n let total = 0;\n for (const candidate of [dbFile, `${dbFile}-wal`]) {\n try {\n total += statSync(candidate).size;\n } catch {\n // file may not exist (e.g. WAL already truncated)\n }\n }\n return total;\n}\n\n/** Free bytes required before compacting: the compacted copy plus 20% + 256 MB headroom. */\nexport function requiredFreeBytes(liveBytes: number): number {\n return Math.ceil(liveBytes * 1.2) + 256 * 1024 * 1024;\n}\n\n/** First column of the first row of a PRAGMA result, as a number. */\nfunction pragmaNumber(db: InstanceType<typeof Database>, pragma: string): number {\n const rows = db.pragma(pragma) as Array<Record<string, unknown>> | undefined;\n const row = rows?.[0];\n return row ? Number(Object.values(row)[0]) : 0;\n}\n\n/**\n * Journal mode straight from the SQLite file header (bytes 18/19:\n * 1 = rollback journal, 2 = WAL) — readable without opening a connection.\n */\nfunction journalModeFromHeader(file: string): 'wal' | 'delete' | 'unknown' {\n const fd = openSync(file, 'r');\n try {\n const buf = Buffer.alloc(2);\n if (readSync(fd, buf, 0, 2, 18) !== 2) return 'unknown';\n if (buf[0] === 2 && buf[1] === 2) return 'wal';\n if (buf[0] === 1 && buf[1] === 1) return 'delete';\n return 'unknown';\n } finally {\n closeSync(fd);\n }\n}\n\n/**\n * Compact each local libsql db file by streaming a `VACUUM INTO` copy next to\n * it, then swapping the copy into place. Reports before/after sizes.\n *\n * Deliberately NOT an in-place `VACUUM`: that copies all live content into a\n * temp store (held in memory by libsql builds with `temp_store=MEMORY`) and\n * then rewrites the entire database back through the WAL — on multi-GB\n * databases that has frozen whole machines via memory/IO exhaustion.\n * `VACUUM INTO` reads the logical database once and writes the compacted copy\n * sequentially with bounded memory, and the WAL never grows.\n *\n * Each file is preflighted: live bytes are estimated from\n * `page_count - freelist_count` and the copy is refused unless the volume has\n * `requiredFreeBytes()` available.\n *\n * MUST run with every connection to these files closed (the swap replaces the\n * inode — a surviving connection would keep writing to the unlinked old file).\n * `runStorageMaintenance()` closes storage before calling this.\n */\nexport async function reclaimLibSQLDisk(\n dbFiles: string[],\n onFileStart?: (file: string, bytesBefore: number, liveBytes: number) => void,\n): Promise<ReclaimResult[]> {\n const results: ReclaimResult[] = [];\n for (const file of dbFiles) {\n // Skip missing files and files too small to have a SQLite header — nothing to reclaim\n if (!existsSync(file) || statSync(file).size < 100) continue;\n const bytesBefore = fileSizeWithWal(file);\n const tmp = `${file}.vacuum-tmp`;\n rmSync(tmp, { force: true });\n // Prove we're the only connection before the swap: every open WAL-mode\n // connection (all Mastra Code sessions run WAL) holds a shared-memory\n // lock, so switching WAL -> DELETE only succeeds with exclusive access.\n // A surviving connection would keep writing to the unlinked old inode\n // after the swap and silently lose data.\n //\n // The probe uses exec() only and verifies the outcome from the SQLite\n // file header: prepared statements (including db.pragma()) pin a libsql\n // connection open past close(), so a failed in-connection probe would\n // itself hold the WAL lock and poison every retry in this process.\n // Upstream bug (libsql 0.5.x / @libsql/client 0.17.x): Database.close()\n // does not finalize outstanding statements — they are only finalized by\n // GC, so lock release after close() is nondeterministic. Reported as\n // https://github.com/tursodatabase/libsql-js/issues/228 (fix in flight in\n // PR #214). Once that lands, the header check can become a plain\n // journal_mode query.\n const probe = new Database(file);\n try {\n probe.exec('PRAGMA busy_timeout = 2000');\n // Round-trip through WAL so the probe also works when a clean shutdown\n // already left the file in rollback mode.\n probe.exec('PRAGMA journal_mode = WAL');\n probe.exec('PRAGMA journal_mode = DELETE');\n } catch {\n // busy — the header check below reports the failure\n } finally {\n probe.close();\n }\n if (journalModeFromHeader(file) !== 'delete') {\n throw new Error(\n `${file} is in use by another process — is another Mastra Code session running? ` +\n `Close other sessions and run /prune vacuum again.`,\n );\n }\n // Native `libsql` driver, not `@libsql/client`: the wrapper's close() can\n // leave the file lock held in-process (cached statements), which would\n // block other connections afterwards. This connection stays in rollback\n // mode, so even a pinned statement holds no lock once we're done.\n const db = new Database(file);\n try {\n db.exec('PRAGMA busy_timeout = 2000');\n const pageSize = pragmaNumber(db, 'page_size');\n const pageCount = pragmaNumber(db, 'page_count');\n const freelistCount = pragmaNumber(db, 'freelist_count');\n const liveBytes = Math.max(0, pageCount - freelistCount) * pageSize;\n const { bsize, bavail } = statfsSync(dirname(file));\n const freeBytes = bsize * bavail;\n if (freeBytes < requiredFreeBytes(liveBytes)) {\n throw new Error(\n `Not enough free disk space to compact ${file}: need ${formatBytes(requiredFreeBytes(liveBytes))} ` +\n `for a ${formatBytes(liveBytes)} compacted copy, but only ${formatBytes(freeBytes)} is free.`,\n );\n }\n onFileStart?.(file, bytesBefore, liveBytes);\n db.exec(`VACUUM INTO '${tmp.replace(/'/g, \"''\")}'`);\n } catch (err) {\n rmSync(tmp, { force: true });\n throw err;\n } finally {\n db.close();\n }\n // Re-probe right before the swap: `VACUUM INTO` can take tens of seconds\n // on multi-GB files, and a session started in that window reopens the db\n // in WAL mode (flipping the header back). Swapping under it would orphan\n // that session's inode and silently lose its writes.\n if (journalModeFromHeader(file) !== 'delete') {\n rmSync(tmp, { force: true });\n throw new Error(\n `${file} was opened by another process during compaction — is another Mastra Code session running? ` +\n `Close other sessions and run /prune vacuum again.`,\n );\n }\n // Swap the compacted copy into place. The old WAL/SHM sidecars belong to\n // the old inode — they must never be paired with the new file. If any step\n // after the first rename fails, restore the original so the db path is\n // never left empty (a naive restart would otherwise create a fresh db).\n renameSync(file, `${file}.old`);\n try {\n rmSync(`${file}-wal`, { force: true });\n rmSync(`${file}-shm`, { force: true });\n renameSync(tmp, file);\n } catch (err) {\n try {\n renameSync(`${file}.old`, file);\n } catch {\n throw new Error(\n `Failed to swap compacted copy into place AND failed to restore the original — ` +\n `your data is intact at ${file}.old; rename it back to ${file} manually. ` +\n `Original error: ${err instanceof Error ? err.message : String(err)}`,\n );\n }\n rmSync(tmp, { force: true });\n throw err;\n }\n rmSync(`${file}.old`, { force: true });\n results.push({ file, bytesBefore, bytesAfter: fileSizeWithWal(file) });\n }\n return results;\n}\n\n/** `file:/path` or `file:///path` → filesystem path; undefined for non-file urls. */\nfunction fileUrlToPath(url: string): string | undefined {\n if (!url.startsWith('file:')) return undefined;\n return url.replace(/^file:\\/\\//, '').replace(/^file:/, '');\n}\n\n/**\n * Resolve the local libsql db files eligible for checkpoint + VACUUM.\n * Returns [] for remote (turso) urls and PG backends. When PG was requested\n * but the factory fell back to libsql, the fallback uses the default files.\n */\nexport function resolveLocalDbFiles(config: StorageConfig, effectiveBackend: 'libsql' | 'pg'): string[] {\n if (effectiveBackend !== 'libsql') return [];\n if (config.backend === 'pg') {\n // PG requested but fell back to the default local libsql files\n return [getDatabasePath(), getVectorDatabasePath()];\n }\n if (config.isRemote) return [];\n const files: string[] = [];\n const main = fileUrlToPath(config.url);\n if (main) files.push(main);\n const vector = config.vectorUrl ? fileUrlToPath(config.vectorUrl) : getVectorDatabasePath();\n if (vector) files.push(vector);\n return files;\n}\n\n/**\n * Build the maintenance handle for the effective storage backend.\n * `localDbFiles` are the local libsql file paths eligible for VACUUM;\n * pass an empty array for remote (turso) or PG backends.\n */\nexport function createStorageMaintenance(opts: {\n storage: MastraCompositeStore;\n backend: 'libsql' | 'pg';\n retention: RetentionConfig;\n localDbFiles: string[];\n /** Closes the vector store's connection; required for local libsql so the compaction swap isn't blocked by our own handle. */\n closeVector?: () => Promise<void>;\n}): StorageMaintenance {\n const { storage, backend, retention, localDbFiles, closeVector } = opts;\n return {\n backend,\n retention,\n prune: options => storage.prune(options),\n closeStorage: async () => {\n await storage.close?.();\n await closeVector?.();\n },\n ...(backend === 'libsql' && localDbFiles.length > 0\n ? {\n reclaimDisk: (onFileStart?: (file: string, bytesBefore: number, liveBytes: number) => void) =>\n reclaimLibSQLDisk(localDbFiles, onFileStart),\n }\n : {}),\n };\n}\n\nexport function formatBytes(bytes: number): string {\n if (bytes < 1024) return `${bytes} B`;\n const units = ['KB', 'MB', 'GB', 'TB'];\n let value = bytes;\n let unit = 'B';\n for (const next of units) {\n if (value < 1024) break;\n value /= 1024;\n unit = next;\n }\n return `${value.toFixed(1)} ${unit}`;\n}\n\n/** Rows deleted per table per pass: default batchSize (1000) × 20 batches. */\nconst PASS_MAX_BATCHES = 20;\n\n/**\n * Run storage maintenance with exclusive access to the database, reporting\n * progress through `log` (one line per pass so large deletes are visible).\n *\n * Must run AFTER the TUI is stopped and background writers are quiesced —\n * batched deletes and VACUUM contend with live writes (SQLITE_BUSY) and can\n * starve the event loop that renders the TUI. The storage connection is\n * closed at the end (checkpointing the WAL for local libsql), so the process\n * must exit afterwards.\n */\nexport async function runStorageMaintenance(opts: {\n maintenance: StorageMaintenance;\n vacuum: boolean;\n /** Skip the memory domain (messages/threads) so chat history is preserved. */\n keepMemory?: boolean;\n log: (line: string) => void;\n}): Promise<void> {\n const { maintenance, vacuum, keepMemory = false, log } = opts;\n\n // keep-memory: prune with the standing policies minus the memory domain,\n // for this run only. The configured retention is untouched.\n const { memory: _memory, ...withoutMemory } = maintenance.retention;\n const retentionForRun = keepMemory ? { retention: withoutMemory } : {};\n\n log('Pruning rows older than the retention policies:');\n for (const [domain, tables] of Object.entries(maintenance.retention)) {\n for (const [table, policy] of Object.entries(tables ?? {})) {\n const kept = keepMemory && domain === 'memory';\n log(` ${domain}.${table}: ${kept ? 'kept (keep-memory)' : (policy as { maxAge: string | number }).maxAge}`);\n }\n }\n\n const totals = new Map<string, number>();\n for (;;) {\n const results = await maintenance.prune({ maxBatches: PASS_MAX_BATCHES, ...retentionForRun });\n if (results.length === 0) break;\n let remaining = false;\n let deletedThisPass = 0;\n for (const r of results) {\n const key = `${r.domain}.${r.table}`;\n totals.set(key, (totals.get(key) ?? 0) + r.deleted);\n deletedThisPass += r.deleted;\n if (!r.done) remaining = true;\n if (r.deleted > 0) {\n log(` ${key}: ${totals.get(key)} rows deleted${r.done ? '' : ' so far…'}`);\n }\n }\n if (!remaining) break;\n // Safety valve: a table reported more eligible rows but nothing was\n // deleted this pass — bail instead of spinning forever.\n if (deletedThisPass === 0) {\n log(' stopping: no progress in the last pass.');\n break;\n }\n }\n const totalDeleted = [...totals.values()].reduce((sum, n) => sum + n, 0);\n log(\n totalDeleted === 0\n ? 'Nothing to prune — no retention-eligible rows found.'\n : `Prune complete: ${totalDeleted} rows deleted.`,\n );\n\n // Release the long-lived connection: checkpoints the WAL for local libsql\n // and guarantees VACUUM below isn't blocked by our own open handles.\n await maintenance.closeStorage?.();\n\n if (!vacuum) {\n if (maintenance.reclaimDisk) {\n log('Deleted rows free pages inside the db file but not on disk. Run /prune vacuum to reclaim disk space.');\n }\n return;\n }\n\n if (!maintenance.reclaimDisk) {\n log('Disk reclamation (VACUUM) is only available for local libsql storage.');\n return;\n }\n\n log('Reclaiming disk (compacting database files)… this can take several minutes on large databases.');\n const reclaimed = await maintenance.reclaimDisk((file, bytesBefore, liveBytes) => {\n log(` vacuuming ${file} (${formatBytes(bytesBefore)}, ~${formatBytes(liveBytes)} live)…`);\n });\n if (reclaimed.length === 0) {\n log('No local database files found to vacuum.');\n return;\n }\n for (const r of reclaimed) {\n log(` ${r.file}: ${formatBytes(r.bytesBefore)} → ${formatBytes(r.bytesAfter)}`);\n }\n const saved = reclaimed.reduce((sum, r) => sum + Math.max(0, r.bytesBefore - r.bytesAfter), 0);\n log(`Reclaimed ${formatBytes(saved)}.`);\n}\n"],"mappings":"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"}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
export declare class ThreadLockError extends Error {
|
|
2
|
+
readonly threadId: string;
|
|
3
|
+
readonly ownerPid: number;
|
|
4
|
+
constructor(threadId: string, ownerPid: number);
|
|
5
|
+
}
|
|
6
|
+
/**
|
|
7
|
+
* Attempt to acquire a lock for the given thread.
|
|
8
|
+
* Throws ThreadLockError if another live process holds the lock.
|
|
9
|
+
* Reclaims stale locks from dead processes.
|
|
10
|
+
*/
|
|
11
|
+
export declare function acquireThreadLock(threadId: string): void;
|
|
12
|
+
/**
|
|
13
|
+
* Release the lock for the given thread (only if we own it).
|
|
14
|
+
*/
|
|
15
|
+
export declare function releaseThreadLock(threadId: string): void;
|
|
16
|
+
/**
|
|
17
|
+
* Check if a thread is locked by another process.
|
|
18
|
+
* Returns the PID of the owner if locked, null otherwise.
|
|
19
|
+
*/
|
|
20
|
+
export declare function getThreadLockOwner(threadId: string): number | null;
|
|
21
|
+
/**
|
|
22
|
+
* Release all thread locks owned by this process.
|
|
23
|
+
* Call this on process exit.
|
|
24
|
+
*/
|
|
25
|
+
export declare function releaseAllThreadLocks(): void;
|
|
26
|
+
//# sourceMappingURL=thread-lock.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"thread-lock.d.ts","sourceRoot":"","sources":["../../src/utils/thread-lock.ts"],"names":[],"mappings":"AAWA,qBAAa,eAAgB,SAAQ,KAAK;aAEtB,QAAQ,EAAE,MAAM;aAChB,QAAQ,EAAE,MAAM;gBADhB,QAAQ,EAAE,MAAM,EAChB,QAAQ,EAAE,MAAM;CAKnC;AAyBD;;;;GAIG;AACH,wBAAgB,iBAAiB,CAAC,QAAQ,EAAE,MAAM,GAAG,IAAI,CAsBxD;AAED;;GAEG;AACH,wBAAgB,iBAAiB,CAAC,QAAQ,EAAE,MAAM,GAAG,IAAI,CAiBxD;AAED;;;GAGG;AACH,wBAAgB,kBAAkB,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAuBlE;AAED;;;GAGG;AACH,wBAAgB,qBAAqB,IAAI,IAAI,CAqB5C"}
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
import { getAppDataDir } from "./project.js";
|
|
2
|
+
import * as fs$1 from "fs";
|
|
3
|
+
import * as path$1 from "path";
|
|
4
|
+
//#region src/utils/thread-lock.ts
|
|
5
|
+
/**
|
|
6
|
+
* Thread lock — ensures only one process writes to a thread at a time.
|
|
7
|
+
*
|
|
8
|
+
* Uses filesystem lock files: <appDataDir>/locks/<threadId>.lock
|
|
9
|
+
* Each lock file contains the PID of the owning process.
|
|
10
|
+
* Stale locks (from crashed processes) are detected and reclaimed.
|
|
11
|
+
*/
|
|
12
|
+
var ThreadLockError = class extends Error {
|
|
13
|
+
threadId;
|
|
14
|
+
ownerPid;
|
|
15
|
+
constructor(threadId, ownerPid) {
|
|
16
|
+
super(`Thread ${threadId} is locked by another process (PID ${ownerPid})`);
|
|
17
|
+
this.threadId = threadId;
|
|
18
|
+
this.ownerPid = ownerPid;
|
|
19
|
+
this.name = "ThreadLockError";
|
|
20
|
+
}
|
|
21
|
+
};
|
|
22
|
+
function getLocksDir() {
|
|
23
|
+
const dir = path$1.join(getAppDataDir(), "locks");
|
|
24
|
+
if (!fs$1.existsSync(dir)) fs$1.mkdirSync(dir, { recursive: true });
|
|
25
|
+
return dir;
|
|
26
|
+
}
|
|
27
|
+
function getLockPath(threadId) {
|
|
28
|
+
const safeId = threadId.replace(/[^a-zA-Z0-9_-]/g, "_");
|
|
29
|
+
return path$1.join(getLocksDir(), `${safeId}.lock`);
|
|
30
|
+
}
|
|
31
|
+
function isProcessAlive(pid) {
|
|
32
|
+
try {
|
|
33
|
+
process.kill(pid, 0);
|
|
34
|
+
return true;
|
|
35
|
+
} catch {
|
|
36
|
+
return false;
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* Attempt to acquire a lock for the given thread.
|
|
41
|
+
* Throws ThreadLockError if another live process holds the lock.
|
|
42
|
+
* Reclaims stale locks from dead processes.
|
|
43
|
+
*/
|
|
44
|
+
function acquireThreadLock(threadId) {
|
|
45
|
+
const lockPath = getLockPath(threadId);
|
|
46
|
+
const myPid = process.pid;
|
|
47
|
+
if (fs$1.existsSync(lockPath)) try {
|
|
48
|
+
const content = fs$1.readFileSync(lockPath, "utf-8").trim();
|
|
49
|
+
const ownerPid = parseInt(content, 10);
|
|
50
|
+
if (!isNaN(ownerPid) && ownerPid !== myPid && isProcessAlive(ownerPid)) throw new ThreadLockError(threadId, ownerPid);
|
|
51
|
+
} catch (error) {
|
|
52
|
+
if (error instanceof ThreadLockError) throw error;
|
|
53
|
+
}
|
|
54
|
+
fs$1.writeFileSync(lockPath, String(myPid), { mode: 420 });
|
|
55
|
+
}
|
|
56
|
+
/**
|
|
57
|
+
* Release the lock for the given thread (only if we own it).
|
|
58
|
+
*/
|
|
59
|
+
function releaseThreadLock(threadId) {
|
|
60
|
+
const lockPath = getLockPath(threadId);
|
|
61
|
+
const myPid = process.pid;
|
|
62
|
+
try {
|
|
63
|
+
if (!fs$1.existsSync(lockPath)) return;
|
|
64
|
+
const content = fs$1.readFileSync(lockPath, "utf-8").trim();
|
|
65
|
+
if (parseInt(content, 10) === myPid) fs$1.unlinkSync(lockPath);
|
|
66
|
+
} catch {}
|
|
67
|
+
}
|
|
68
|
+
/**
|
|
69
|
+
* Check if a thread is locked by another process.
|
|
70
|
+
* Returns the PID of the owner if locked, null otherwise.
|
|
71
|
+
*/
|
|
72
|
+
function getThreadLockOwner(threadId) {
|
|
73
|
+
const lockPath = getLockPath(threadId);
|
|
74
|
+
try {
|
|
75
|
+
if (!fs$1.existsSync(lockPath)) return null;
|
|
76
|
+
const content = fs$1.readFileSync(lockPath, "utf-8").trim();
|
|
77
|
+
const ownerPid = parseInt(content, 10);
|
|
78
|
+
if (isNaN(ownerPid)) return null;
|
|
79
|
+
if (ownerPid === process.pid) return null;
|
|
80
|
+
if (!isProcessAlive(ownerPid)) {
|
|
81
|
+
try {
|
|
82
|
+
fs$1.unlinkSync(lockPath);
|
|
83
|
+
} catch {}
|
|
84
|
+
return null;
|
|
85
|
+
}
|
|
86
|
+
return ownerPid;
|
|
87
|
+
} catch {
|
|
88
|
+
return null;
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
/**
|
|
92
|
+
* Release all thread locks owned by this process.
|
|
93
|
+
* Call this on process exit.
|
|
94
|
+
*/
|
|
95
|
+
function releaseAllThreadLocks() {
|
|
96
|
+
try {
|
|
97
|
+
const locksDir = getLocksDir();
|
|
98
|
+
const files = fs$1.readdirSync(locksDir);
|
|
99
|
+
const myPid = String(process.pid);
|
|
100
|
+
for (const file of files) {
|
|
101
|
+
if (!file.endsWith(".lock")) continue;
|
|
102
|
+
const lockPath = path$1.join(locksDir, file);
|
|
103
|
+
try {
|
|
104
|
+
if (fs$1.readFileSync(lockPath, "utf-8").trim() === myPid) fs$1.unlinkSync(lockPath);
|
|
105
|
+
} catch {}
|
|
106
|
+
}
|
|
107
|
+
} catch {}
|
|
108
|
+
}
|
|
109
|
+
//#endregion
|
|
110
|
+
export { ThreadLockError, acquireThreadLock, getThreadLockOwner, releaseAllThreadLocks, releaseThreadLock };
|
|
111
|
+
|
|
112
|
+
//# sourceMappingURL=thread-lock.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"thread-lock.js","names":["path","fs"],"sources":["../../src/utils/thread-lock.ts"],"sourcesContent":["/**\n * Thread lock — ensures only one process writes to a thread at a time.\n *\n * Uses filesystem lock files: <appDataDir>/locks/<threadId>.lock\n * Each lock file contains the PID of the owning process.\n * Stale locks (from crashed processes) are detected and reclaimed.\n */\nimport * as fs from 'node:fs';\nimport * as path from 'node:path';\nimport { getAppDataDir } from './project.js';\n\nexport class ThreadLockError extends Error {\n constructor(\n public readonly threadId: string,\n public readonly ownerPid: number,\n ) {\n super(`Thread ${threadId} is locked by another process (PID ${ownerPid})`);\n this.name = 'ThreadLockError';\n }\n}\n\nfunction getLocksDir(): string {\n const dir = path.join(getAppDataDir(), 'locks');\n if (!fs.existsSync(dir)) {\n fs.mkdirSync(dir, { recursive: true });\n }\n return dir;\n}\n\nfunction getLockPath(threadId: string): string {\n // Sanitize thread ID for filesystem safety\n const safeId = threadId.replace(/[^a-zA-Z0-9_-]/g, '_');\n return path.join(getLocksDir(), `${safeId}.lock`);\n}\n\nfunction isProcessAlive(pid: number): boolean {\n try {\n process.kill(pid, 0);\n return true;\n } catch {\n return false;\n }\n}\n\n/**\n * Attempt to acquire a lock for the given thread.\n * Throws ThreadLockError if another live process holds the lock.\n * Reclaims stale locks from dead processes.\n */\nexport function acquireThreadLock(threadId: string): void {\n const lockPath = getLockPath(threadId);\n const myPid = process.pid;\n\n // Check for existing lock\n if (fs.existsSync(lockPath)) {\n try {\n const content = fs.readFileSync(lockPath, 'utf-8').trim();\n const ownerPid = parseInt(content, 10);\n\n if (!isNaN(ownerPid) && ownerPid !== myPid && isProcessAlive(ownerPid)) {\n throw new ThreadLockError(threadId, ownerPid);\n }\n // Stale lock (dead process) or our own lock — reclaim it\n } catch (error) {\n if (error instanceof ThreadLockError) throw error;\n // File read error — try to overwrite\n }\n }\n\n // Write our PID to the lock file\n fs.writeFileSync(lockPath, String(myPid), { mode: 0o644 });\n}\n\n/**\n * Release the lock for the given thread (only if we own it).\n */\nexport function releaseThreadLock(threadId: string): void {\n const lockPath = getLockPath(threadId);\n const myPid = process.pid;\n\n try {\n if (!fs.existsSync(lockPath)) return;\n\n const content = fs.readFileSync(lockPath, 'utf-8').trim();\n const ownerPid = parseInt(content, 10);\n\n // Only remove if we own it\n if (ownerPid === myPid) {\n fs.unlinkSync(lockPath);\n }\n } catch {\n // Best-effort cleanup — ignore errors\n }\n}\n\n/**\n * Check if a thread is locked by another process.\n * Returns the PID of the owner if locked, null otherwise.\n */\nexport function getThreadLockOwner(threadId: string): number | null {\n const lockPath = getLockPath(threadId);\n\n try {\n if (!fs.existsSync(lockPath)) return null;\n\n const content = fs.readFileSync(lockPath, 'utf-8').trim();\n const ownerPid = parseInt(content, 10);\n\n if (isNaN(ownerPid)) return null;\n if (ownerPid === process.pid) return null; // Our own lock\n if (!isProcessAlive(ownerPid)) {\n // Stale lock — clean it up\n try {\n fs.unlinkSync(lockPath);\n } catch {}\n return null;\n }\n\n return ownerPid;\n } catch {\n return null;\n }\n}\n\n/**\n * Release all thread locks owned by this process.\n * Call this on process exit.\n */\nexport function releaseAllThreadLocks(): void {\n try {\n const locksDir = getLocksDir();\n const files = fs.readdirSync(locksDir);\n const myPid = String(process.pid);\n\n for (const file of files) {\n if (!file.endsWith('.lock')) continue;\n const lockPath = path.join(locksDir, file);\n try {\n const content = fs.readFileSync(lockPath, 'utf-8').trim();\n if (content === myPid) {\n fs.unlinkSync(lockPath);\n }\n } catch {\n // Best-effort\n }\n }\n } catch {\n // Best-effort\n }\n}\n"],"mappings":";;;;;;;;;;;AAWA,IAAa,kBAAb,cAAqC,MAAM;CAEvB;CACA;CAFlB,YACE,UACA,UACA;EACA,MAAM,UAAU,SAAS,qCAAqC,SAAS,EAAE;EAHzD,KAAA,WAAA;EACA,KAAA,WAAA;EAGhB,KAAK,OAAO;CACd;AACF;AAEA,SAAS,cAAsB;CAC7B,MAAM,MAAMA,OAAK,KAAK,cAAc,GAAG,OAAO;CAC9C,IAAI,CAACC,KAAG,WAAW,GAAG,GACpB,KAAG,UAAU,KAAK,EAAE,WAAW,KAAK,CAAC;CAEvC,OAAO;AACT;AAEA,SAAS,YAAY,UAA0B;CAE7C,MAAM,SAAS,SAAS,QAAQ,mBAAmB,GAAG;CACtD,OAAOD,OAAK,KAAK,YAAY,GAAG,GAAG,OAAO,MAAM;AAClD;AAEA,SAAS,eAAe,KAAsB;CAC5C,IAAI;EACF,QAAQ,KAAK,KAAK,CAAC;EACnB,OAAO;CACT,QAAQ;EACN,OAAO;CACT;AACF;;;;;;AAOA,SAAgB,kBAAkB,UAAwB;CACxD,MAAM,WAAW,YAAY,QAAQ;CACrC,MAAM,QAAQ,QAAQ;CAGtB,IAAIC,KAAG,WAAW,QAAQ,GACxB,IAAI;EACF,MAAM,UAAUA,KAAG,aAAa,UAAU,OAAO,CAAC,CAAC,KAAK;EACxD,MAAM,WAAW,SAAS,SAAS,EAAE;EAErC,IAAI,CAAC,MAAM,QAAQ,KAAK,aAAa,SAAS,eAAe,QAAQ,GACnE,MAAM,IAAI,gBAAgB,UAAU,QAAQ;CAGhD,SAAS,OAAO;EACd,IAAI,iBAAiB,iBAAiB,MAAM;CAE9C;CAIF,KAAG,cAAc,UAAU,OAAO,KAAK,GAAG,EAAE,MAAM,IAAM,CAAC;AAC3D;;;;AAKA,SAAgB,kBAAkB,UAAwB;CACxD,MAAM,WAAW,YAAY,QAAQ;CACrC,MAAM,QAAQ,QAAQ;CAEtB,IAAI;EACF,IAAI,CAACA,KAAG,WAAW,QAAQ,GAAG;EAE9B,MAAM,UAAUA,KAAG,aAAa,UAAU,OAAO,CAAC,CAAC,KAAK;EAIxD,IAHiB,SAAS,SAAS,EAGxB,MAAM,OACf,KAAG,WAAW,QAAQ;CAE1B,QAAQ,CAER;AACF;;;;;AAMA,SAAgB,mBAAmB,UAAiC;CAClE,MAAM,WAAW,YAAY,QAAQ;CAErC,IAAI;EACF,IAAI,CAACA,KAAG,WAAW,QAAQ,GAAG,OAAO;EAErC,MAAM,UAAUA,KAAG,aAAa,UAAU,OAAO,CAAC,CAAC,KAAK;EACxD,MAAM,WAAW,SAAS,SAAS,EAAE;EAErC,IAAI,MAAM,QAAQ,GAAG,OAAO;EAC5B,IAAI,aAAa,QAAQ,KAAK,OAAO;EACrC,IAAI,CAAC,eAAe,QAAQ,GAAG;GAE7B,IAAI;IACF,KAAG,WAAW,QAAQ;GACxB,QAAQ,CAAC;GACT,OAAO;EACT;EAEA,OAAO;CACT,QAAQ;EACN,OAAO;CACT;AACF;;;;;AAMA,SAAgB,wBAA8B;CAC5C,IAAI;EACF,MAAM,WAAW,YAAY;EAC7B,MAAM,QAAQA,KAAG,YAAY,QAAQ;EACrC,MAAM,QAAQ,OAAO,QAAQ,GAAG;EAEhC,KAAK,MAAM,QAAQ,OAAO;GACxB,IAAI,CAAC,KAAK,SAAS,OAAO,GAAG;GAC7B,MAAM,WAAWD,OAAK,KAAK,UAAU,IAAI;GACzC,IAAI;IAEF,IADgBC,KAAG,aAAa,UAAU,OAAO,CAAC,CAAC,KACzC,MAAM,OACd,KAAG,WAAW,QAAQ;GAE1B,QAAQ,CAER;EACF;CACF,QAAQ,CAER;AACF"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"token-estimator.d.ts","sourceRoot":"","sources":["../../src/utils/token-estimator.ts"],"names":[],"mappings":"AASA,wBAAgB,aAAa,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,GAAG,MAAM,CAE3D;AAED,wBAAgB,8BAA8B,CAAC,IAAI,EAAE,MAAM,EAAE,iBAAiB,EAAE,MAAM,EAAE,OAAO,UAAO,UAUrG"}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { estimateTokenCount, sliceByTokens } from "tokenx";
|
|
2
|
+
//#region src/utils/token-estimator.ts
|
|
3
|
+
function sanitizeInput(text) {
|
|
4
|
+
if (!text) return "";
|
|
5
|
+
return (typeof text === `string` ? text : JSON.stringify(text)).replaceAll(`<|endoftext|>`, ``).replaceAll(`<|endofprompt|>`, ``);
|
|
6
|
+
}
|
|
7
|
+
function tokenEstimate(text) {
|
|
8
|
+
return estimateTokenCount(sanitizeInput(text));
|
|
9
|
+
}
|
|
10
|
+
function truncateStringForTokenEstimate(text, desiredTokenCount, fromEnd = true) {
|
|
11
|
+
const sanitized = sanitizeInput(text);
|
|
12
|
+
const totalTokens = estimateTokenCount(sanitized);
|
|
13
|
+
if (totalTokens <= desiredTokenCount) return sanitized;
|
|
14
|
+
const kept = fromEnd ? sliceByTokens(sanitized, -desiredTokenCount) : sliceByTokens(sanitized, 0, desiredTokenCount);
|
|
15
|
+
return `[Truncated ~${totalTokens - desiredTokenCount} tokens]
|
|
16
|
+
${kept}`;
|
|
17
|
+
}
|
|
18
|
+
//#endregion
|
|
19
|
+
export { tokenEstimate, truncateStringForTokenEstimate };
|
|
20
|
+
|
|
21
|
+
//# sourceMappingURL=token-estimator.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"token-estimator.js","names":[],"sources":["../../src/utils/token-estimator.ts"],"sourcesContent":["import { estimateTokenCount, sliceByTokens } from 'tokenx';\n\nfunction sanitizeInput(text: string | object) {\n if (!text) return '';\n return (typeof text === `string` ? text : JSON.stringify(text))\n .replaceAll(`<|endoftext|>`, ``)\n .replaceAll(`<|endofprompt|>`, ``);\n}\n\nexport function tokenEstimate(text: string | object): number {\n return estimateTokenCount(sanitizeInput(text));\n}\n\nexport function truncateStringForTokenEstimate(text: string, desiredTokenCount: number, fromEnd = true) {\n const sanitized = sanitizeInput(text);\n const totalTokens = estimateTokenCount(sanitized);\n\n if (totalTokens <= desiredTokenCount) return sanitized;\n\n const kept = fromEnd ? sliceByTokens(sanitized, -desiredTokenCount) : sliceByTokens(sanitized, 0, desiredTokenCount);\n\n return `[Truncated ~${totalTokens - desiredTokenCount} tokens]\n${kept}`;\n}\n"],"mappings":";;AAEA,SAAS,cAAc,MAAuB;CAC5C,IAAI,CAAC,MAAM,OAAO;CAClB,QAAQ,OAAO,SAAS,WAAW,OAAO,KAAK,UAAU,IAAI,EAAA,CAC1D,WAAW,iBAAiB,EAAE,CAAC,CAC/B,WAAW,mBAAmB,EAAE;AACrC;AAEA,SAAgB,cAAc,MAA+B;CAC3D,OAAO,mBAAmB,cAAc,IAAI,CAAC;AAC/C;AAEA,SAAgB,+BAA+B,MAAc,mBAA2B,UAAU,MAAM;CACtG,MAAM,YAAY,cAAc,IAAI;CACpC,MAAM,cAAc,mBAAmB,SAAS;CAEhD,IAAI,eAAe,mBAAmB,OAAO;CAE7C,MAAM,OAAO,UAAU,cAAc,WAAW,CAAC,iBAAiB,IAAI,cAAc,WAAW,GAAG,iBAAiB;CAEnH,OAAO,eAAe,cAAc,kBAAkB;EACtD;AACF"}
|