@mastra/code-sdk 0.0.0-fix-workflow-step-timing-and-parallel-status-20260715180716
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 +285 -0
- package/LICENSE.md +30 -0
- package/README.md +50 -0
- package/dist/acp/agent.d.ts +35 -0
- package/dist/acp/agent.d.ts.map +1 -0
- package/dist/acp/agent.js +173 -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 +166 -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 +54 -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 +34 -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 +5 -0
- package/dist/acp.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 +41 -0
- package/dist/agents/instructions.js.map +1 -0
- package/dist/agents/mastracode-gateway.d.ts +67 -0
- package/dist/agents/mastracode-gateway.d.ts.map +1 -0
- package/dist/agents/mastracode-gateway.js +446 -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 +107 -0
- package/dist/agents/memory.js.map +1 -0
- package/dist/agents/model.d.ts +55 -0
- package/dist/agents/model.d.ts.map +1 -0
- package/dist/agents/model.js +115 -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 +45 -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 +38 -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 +38 -0
- package/dist/agents/modes/plan.js.map +1 -0
- package/dist/agents/prompts/agent-instructions.d.ts +20 -0
- package/dist/agents/prompts/agent-instructions.d.ts.map +1 -0
- package/dist/agents/prompts/agent-instructions.js +82 -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 +73 -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 +22 -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 +57 -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 +22 -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 +107 -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 +181 -0
- package/dist/agents/prompts/tool-guidance.js.map +1 -0
- package/dist/agents/sandbox-filesystem.d.ts +89 -0
- package/dist/agents/sandbox-filesystem.d.ts.map +1 -0
- package/dist/agents/sandbox-filesystem.js +268 -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 +17 -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 +96 -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 +105 -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 +145 -0
- package/dist/agents/tools.js.map +1 -0
- package/dist/agents/workspace.d.ts +22 -0
- package/dist/agents/workspace.d.ts.map +1 -0
- package/dist/agents/workspace.js +226 -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 +152 -0
- package/dist/analytics.js.map +1 -0
- package/dist/auth/index.d.ts +9 -0
- package/dist/auth/index.d.ts.map +1 -0
- package/dist/auth/index.js +11 -0
- package/dist/auth/index.js.map +1 -0
- package/dist/auth/pkce.d.ts +13 -0
- package/dist/auth/pkce.d.ts.map +1 -0
- package/dist/auth/pkce.js +21 -0
- package/dist/auth/pkce.js.map +1 -0
- package/dist/auth/providers/anthropic.d.ts +17 -0
- package/dist/auth/providers/anthropic.d.ts.map +1 -0
- package/dist/auth/providers/anthropic.js +94 -0
- package/dist/auth/providers/anthropic.js.map +1 -0
- package/dist/auth/providers/github-copilot.d.ts +100 -0
- package/dist/auth/providers/github-copilot.d.ts.map +1 -0
- package/dist/auth/providers/github-copilot.js +302 -0
- package/dist/auth/providers/github-copilot.js.map +1 -0
- package/dist/auth/providers/openai-codex.d.ts +95 -0
- package/dist/auth/providers/openai-codex.d.ts.map +1 -0
- package/dist/auth/providers/openai-codex.js +527 -0
- package/dist/auth/providers/openai-codex.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 +187 -0
- package/dist/auth/storage.js.map +1 -0
- package/dist/auth/types.d.ts +68 -0
- package/dist/auth/types.d.ts.map +1 -0
- package/dist/auth/types.js +1 -0
- package/dist/auth/types.js.map +1 -0
- package/dist/clipboard/index.d.ts +26 -0
- package/dist/clipboard/index.d.ts.map +1 -0
- package/dist/clipboard/index.js +192 -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 +23 -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 +14 -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 +120 -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 +8 -0
- package/dist/evals/index.js.map +1 -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 +52 -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 +201 -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 +56 -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 +7 -0
- package/dist/evals/scorers/index.js.map +1 -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 +238 -0
- package/dist/evals/scorers/outcome.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 +196 -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 +188 -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 +85 -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 +32 -0
- package/dist/headless/index.js.map +1 -0
- package/dist/headless/policy.d.ts +17 -0
- package/dist/headless/policy.d.ts.map +1 -0
- package/dist/headless/policy.js +38 -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 +296 -0
- package/dist/headless/run-mc.js.map +1 -0
- package/dist/headless/types.d.ts +134 -0
- package/dist/headless/types.d.ts.map +1 -0
- package/dist/headless/types.js +9 -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 +78 -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 +125 -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 +16 -0
- package/dist/hooks/index.js.map +1 -0
- package/dist/hooks/manager.d.ts +47 -0
- package/dist/hooks/manager.d.ts.map +1 -0
- package/dist/hooks/manager.js +284 -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 +7 -0
- package/dist/hooks/types.js.map +1 -0
- package/dist/index.d.ts +243 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +730 -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 +24 -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 +309 -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 +9 -0
- package/dist/lsp/index.js.map +1 -0
- package/dist/lsp/language.d.ts +9 -0
- package/dist/lsp/language.d.ts.map +1 -0
- package/dist/lsp/language.js +48 -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 +97 -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 +128 -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 +52 -0
- package/dist/lsp/workspace.js.map +1 -0
- package/dist/mcp/config.d.ts +34 -0
- package/dist/mcp/config.d.ts.map +1 -0
- package/dist/mcp/config.js +205 -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 +10 -0
- package/dist/mcp/index.js.map +1 -0
- package/dist/mcp/manager.d.ts +49 -0
- package/dist/mcp/manager.d.ts.map +1 -0
- package/dist/mcp/manager.js +374 -0
- package/dist/mcp/manager.js.map +1 -0
- package/dist/mcp/types.d.ts +85 -0
- package/dist/mcp/types.d.ts.map +1 -0
- package/dist/mcp/types.js +1 -0
- package/dist/mcp/types.js.map +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 +20 -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 +28 -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 +21 -0
- package/dist/onboarding/index.js.map +1 -0
- package/dist/onboarding/om-settings.d.ts +20 -0
- package/dist/onboarding/om-settings.d.ts.map +1 -0
- package/dist/onboarding/om-settings.js +26 -0
- package/dist/onboarding/om-settings.js.map +1 -0
- package/dist/onboarding/packs.d.ts +48 -0
- package/dist/onboarding/packs.d.ts.map +1 -0
- package/dist/onboarding/packs.js +111 -0
- package/dist/onboarding/packs.js.map +1 -0
- package/dist/onboarding/settings.d.ts +327 -0
- package/dist/onboarding/settings.d.ts.map +1 -0
- package/dist/onboarding/settings.js +598 -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 +119 -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 +30 -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 +111 -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 +200 -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 +221 -0
- package/dist/plugins/loader.js.map +1 -0
- package/dist/plugins/manager.d.ts +60 -0
- package/dist/plugins/manager.d.ts.map +1 -0
- package/dist/plugins/manager.js +378 -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 +75 -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 +77 -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 +26 -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 +96 -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 +152 -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/plugins/types.js.map +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 +53 -0
- package/dist/processors/plan-rejection-abort.js.map +1 -0
- package/dist/providers/amazon-bedrock-gateway.d.ts +40 -0
- package/dist/providers/amazon-bedrock-gateway.d.ts.map +1 -0
- package/dist/providers/amazon-bedrock-gateway.js +79 -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 +62 -0
- package/dist/providers/amazon-bedrock.js.map +1 -0
- package/dist/providers/claude-max.d.ts +50 -0
- package/dist/providers/claude-max.d.ts.map +1 -0
- package/dist/providers/claude-max.js +151 -0
- package/dist/providers/claude-max.js.map +1 -0
- package/dist/providers/github-copilot.d.ts +58 -0
- package/dist/providers/github-copilot.d.ts.map +1 -0
- package/dist/providers/github-copilot.js +263 -0
- package/dist/providers/github-copilot.js.map +1 -0
- package/dist/providers/openai-codex.d.ts +70 -0
- package/dist/providers/openai-codex.d.ts.map +1 -0
- package/dist/providers/openai-codex.js +291 -0
- package/dist/providers/openai-codex.js.map +1 -0
- package/dist/schema.d.ts +167 -0
- package/dist/schema.d.ts.map +1 -0
- package/dist/schema.js +92 -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 +17 -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 +43 -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 +9 -0
- package/dist/tools/index.js.map +1 -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 +88 -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 +22 -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 +65 -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 +74 -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 +55 -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 +207 -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 +49 -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 +9 -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 +43 -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 +99 -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 +302 -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 +104 -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 +177 -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 +96 -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 +72 -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 +101 -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 +236 -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 +108 -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 +52 -0
- package/dist/utils/update-check.d.ts.map +1 -0
- package/dist/utils/update-check.js +177 -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 +126 -0
- package/dist/voice/stt-registry.js.map +1 -0
- package/package.json +94 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"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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","names":[]}
|
|
@@ -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,108 @@
|
|
|
1
|
+
import * as fs from "node:fs";
|
|
2
|
+
import * as path from "node:path";
|
|
3
|
+
import { getAppDataDir } from "./project.js";
|
|
4
|
+
class ThreadLockError extends Error {
|
|
5
|
+
constructor(threadId, ownerPid) {
|
|
6
|
+
super(`Thread ${threadId} is locked by another process (PID ${ownerPid})`);
|
|
7
|
+
this.threadId = threadId;
|
|
8
|
+
this.ownerPid = ownerPid;
|
|
9
|
+
this.name = "ThreadLockError";
|
|
10
|
+
}
|
|
11
|
+
threadId;
|
|
12
|
+
ownerPid;
|
|
13
|
+
}
|
|
14
|
+
function getLocksDir() {
|
|
15
|
+
const dir = path.join(getAppDataDir(), "locks");
|
|
16
|
+
if (!fs.existsSync(dir)) {
|
|
17
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
18
|
+
}
|
|
19
|
+
return dir;
|
|
20
|
+
}
|
|
21
|
+
function getLockPath(threadId) {
|
|
22
|
+
const safeId = threadId.replace(/[^a-zA-Z0-9_-]/g, "_");
|
|
23
|
+
return path.join(getLocksDir(), `${safeId}.lock`);
|
|
24
|
+
}
|
|
25
|
+
function isProcessAlive(pid) {
|
|
26
|
+
try {
|
|
27
|
+
process.kill(pid, 0);
|
|
28
|
+
return true;
|
|
29
|
+
} catch {
|
|
30
|
+
return false;
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
function acquireThreadLock(threadId) {
|
|
34
|
+
const lockPath = getLockPath(threadId);
|
|
35
|
+
const myPid = process.pid;
|
|
36
|
+
if (fs.existsSync(lockPath)) {
|
|
37
|
+
try {
|
|
38
|
+
const content = fs.readFileSync(lockPath, "utf-8").trim();
|
|
39
|
+
const ownerPid = parseInt(content, 10);
|
|
40
|
+
if (!isNaN(ownerPid) && ownerPid !== myPid && isProcessAlive(ownerPid)) {
|
|
41
|
+
throw new ThreadLockError(threadId, ownerPid);
|
|
42
|
+
}
|
|
43
|
+
} catch (error) {
|
|
44
|
+
if (error instanceof ThreadLockError) throw error;
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
fs.writeFileSync(lockPath, String(myPid), { mode: 420 });
|
|
48
|
+
}
|
|
49
|
+
function releaseThreadLock(threadId) {
|
|
50
|
+
const lockPath = getLockPath(threadId);
|
|
51
|
+
const myPid = process.pid;
|
|
52
|
+
try {
|
|
53
|
+
if (!fs.existsSync(lockPath)) return;
|
|
54
|
+
const content = fs.readFileSync(lockPath, "utf-8").trim();
|
|
55
|
+
const ownerPid = parseInt(content, 10);
|
|
56
|
+
if (ownerPid === myPid) {
|
|
57
|
+
fs.unlinkSync(lockPath);
|
|
58
|
+
}
|
|
59
|
+
} catch {
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
function getThreadLockOwner(threadId) {
|
|
63
|
+
const lockPath = getLockPath(threadId);
|
|
64
|
+
try {
|
|
65
|
+
if (!fs.existsSync(lockPath)) return null;
|
|
66
|
+
const content = fs.readFileSync(lockPath, "utf-8").trim();
|
|
67
|
+
const ownerPid = parseInt(content, 10);
|
|
68
|
+
if (isNaN(ownerPid)) return null;
|
|
69
|
+
if (ownerPid === process.pid) return null;
|
|
70
|
+
if (!isProcessAlive(ownerPid)) {
|
|
71
|
+
try {
|
|
72
|
+
fs.unlinkSync(lockPath);
|
|
73
|
+
} catch {
|
|
74
|
+
}
|
|
75
|
+
return null;
|
|
76
|
+
}
|
|
77
|
+
return ownerPid;
|
|
78
|
+
} catch {
|
|
79
|
+
return null;
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
function releaseAllThreadLocks() {
|
|
83
|
+
try {
|
|
84
|
+
const locksDir = getLocksDir();
|
|
85
|
+
const files = fs.readdirSync(locksDir);
|
|
86
|
+
const myPid = String(process.pid);
|
|
87
|
+
for (const file of files) {
|
|
88
|
+
if (!file.endsWith(".lock")) continue;
|
|
89
|
+
const lockPath = path.join(locksDir, file);
|
|
90
|
+
try {
|
|
91
|
+
const content = fs.readFileSync(lockPath, "utf-8").trim();
|
|
92
|
+
if (content === myPid) {
|
|
93
|
+
fs.unlinkSync(lockPath);
|
|
94
|
+
}
|
|
95
|
+
} catch {
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
} catch {
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
export {
|
|
102
|
+
ThreadLockError,
|
|
103
|
+
acquireThreadLock,
|
|
104
|
+
getThreadLockOwner,
|
|
105
|
+
releaseAllThreadLocks,
|
|
106
|
+
releaseThreadLock
|
|
107
|
+
};
|
|
108
|
+
//# sourceMappingURL=thread-lock.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"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":"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","names":[]}
|
|
@@ -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
|
+
function sanitizeInput(text) {
|
|
3
|
+
if (!text) return "";
|
|
4
|
+
return (typeof text === `string` ? text : JSON.stringify(text)).replaceAll(`<|endoftext|>`, ``).replaceAll(`<|endofprompt|>`, ``);
|
|
5
|
+
}
|
|
6
|
+
function tokenEstimate(text) {
|
|
7
|
+
return estimateTokenCount(sanitizeInput(text));
|
|
8
|
+
}
|
|
9
|
+
function truncateStringForTokenEstimate(text, desiredTokenCount, fromEnd = true) {
|
|
10
|
+
const sanitized = sanitizeInput(text);
|
|
11
|
+
const totalTokens = estimateTokenCount(sanitized);
|
|
12
|
+
if (totalTokens <= desiredTokenCount) return sanitized;
|
|
13
|
+
const kept = fromEnd ? sliceByTokens(sanitized, -desiredTokenCount) : sliceByTokens(sanitized, 0, desiredTokenCount);
|
|
14
|
+
return `[Truncated ~${totalTokens - desiredTokenCount} tokens]
|
|
15
|
+
${kept}`;
|
|
16
|
+
}
|
|
17
|
+
export {
|
|
18
|
+
tokenEstimate,
|
|
19
|
+
truncateStringForTokenEstimate
|
|
20
|
+
};
|
|
21
|
+
//# sourceMappingURL=token-estimator.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"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":"AAAA,SAAS,oBAAoB,qBAAqB;AAElD,SAAS,cAAc,MAAuB;AAC5C,MAAI,CAAC,KAAM,QAAO;AAClB,UAAQ,OAAO,SAAS,WAAW,OAAO,KAAK,UAAU,IAAI,GAC1D,WAAW,iBAAiB,EAAE,EAC9B,WAAW,mBAAmB,EAAE;AACrC;AAEO,SAAS,cAAc,MAA+B;AAC3D,SAAO,mBAAmB,cAAc,IAAI,CAAC;AAC/C;AAEO,SAAS,+BAA+B,MAAc,mBAA2B,UAAU,MAAM;AACtG,QAAM,YAAY,cAAc,IAAI;AACpC,QAAM,cAAc,mBAAmB,SAAS;AAEhD,MAAI,eAAe,kBAAmB,QAAO;AAE7C,QAAM,OAAO,UAAU,cAAc,WAAW,CAAC,iBAAiB,IAAI,cAAc,WAAW,GAAG,iBAAiB;AAEnH,SAAO,eAAe,cAAc,iBAAiB;AAAA,EACrD,IAAI;AACN;","names":[]}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Check for newer versions of mastracode on npm.
|
|
3
|
+
*/
|
|
4
|
+
export type PackageManager = 'npm' | 'pnpm' | 'yarn' | 'bun';
|
|
5
|
+
/**
|
|
6
|
+
* Detect which package manager was used to install mastracode globally.
|
|
7
|
+
*
|
|
8
|
+
* Uses a multi-tier approach:
|
|
9
|
+
* 1. npm_config_user_agent env var (set when run via `pnpm run`, `npm run`, etc.)
|
|
10
|
+
* 2. npm_execpath env var (fallback for script-based invocation)
|
|
11
|
+
* 3. NODE_PATH env var (set by pnpm global bin stubs)
|
|
12
|
+
* 4. Resolved path of the running script (process.argv[1])
|
|
13
|
+
* 5. Shell-out to `pnpm list -g` to check if pnpm manages the package
|
|
14
|
+
* 6. Falls back to 'npm'
|
|
15
|
+
*/
|
|
16
|
+
export declare function detectPackageManager(): Promise<PackageManager>;
|
|
17
|
+
/**
|
|
18
|
+
* Build the shell command string a user would run to install/update globally.
|
|
19
|
+
*/
|
|
20
|
+
export declare function getInstallCommand(pm: PackageManager, version?: string): string;
|
|
21
|
+
/**
|
|
22
|
+
* Read the current version, injected at build time by tsup's `define` option.
|
|
23
|
+
* Falls back to reading package.json at runtime (e.g. when running from source with tsx).
|
|
24
|
+
*/
|
|
25
|
+
export declare function getCurrentVersion(): string;
|
|
26
|
+
/**
|
|
27
|
+
* Fetch the latest published version from npm.
|
|
28
|
+
* Returns null if the fetch fails (network error, timeout, etc.).
|
|
29
|
+
*/
|
|
30
|
+
export declare function fetchLatestVersion(): Promise<string | null>;
|
|
31
|
+
/**
|
|
32
|
+
* Simple semver comparison: returns true if `latest` is newer than `current`.
|
|
33
|
+
* Handles standard x.y.z versions. Ignores pre-release tags.
|
|
34
|
+
*/
|
|
35
|
+
export declare function isNewerVersion(current: string, latest: string): boolean;
|
|
36
|
+
/**
|
|
37
|
+
* Fetch the CHANGELOG.md for a specific published version from the npm CDN
|
|
38
|
+
* and extract a human-readable summary of changes.
|
|
39
|
+
* Returns null if the fetch fails or the changelog can't be parsed.
|
|
40
|
+
*/
|
|
41
|
+
export declare function fetchChangelog(version: string): Promise<string | null>;
|
|
42
|
+
/**
|
|
43
|
+
* Extract the changelog section for a specific version and format it
|
|
44
|
+
* as a concise bullet list suitable for terminal display.
|
|
45
|
+
*/
|
|
46
|
+
export declare function parseChangelog(markdown: string, version: string): string | null;
|
|
47
|
+
/**
|
|
48
|
+
* Run the appropriate global install command to update mastracode.
|
|
49
|
+
* Returns true on success, false on failure.
|
|
50
|
+
*/
|
|
51
|
+
export declare function runUpdate(pm: PackageManager, targetVersion: string): Promise<boolean>;
|
|
52
|
+
//# sourceMappingURL=update-check.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"update-check.d.ts","sourceRoot":"","sources":["../../src/utils/update-check.ts"],"names":[],"mappings":"AAAA;;GAEG;AAeH,MAAM,MAAM,cAAc,GAAG,KAAK,GAAG,MAAM,GAAG,MAAM,GAAG,KAAK,CAAC;AAW7D;;;;;;;;;;GAUG;AACH,wBAAsB,oBAAoB,IAAI,OAAO,CAAC,cAAc,CAAC,CA0CpE;AAED;;GAEG;AACH,wBAAgB,iBAAiB,CAAC,EAAE,EAAE,cAAc,EAAE,OAAO,CAAC,EAAE,MAAM,GAAG,MAAM,CAY9E;AAED;;;GAGG;AACH,wBAAgB,iBAAiB,IAAI,MAAM,CAS1C;AAED;;;GAGG;AACH,wBAAsB,kBAAkB,IAAI,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAcjE;AAED;;;GAGG;AACH,wBAAgB,cAAc,CAAC,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,OAAO,CAevE;AAKD;;;;GAIG;AACH,wBAAsB,cAAc,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAe5E;AAED;;;GAGG;AACH,wBAAgB,cAAc,CAAC,QAAQ,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAiD/E;AAED;;;GAGG;AACH,wBAAgB,SAAS,CAAC,EAAE,EAAE,cAAc,EAAE,aAAa,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAOrF"}
|
|
@@ -0,0 +1,177 @@
|
|
|
1
|
+
import { execFile } from "node:child_process";
|
|
2
|
+
import { readFileSync, realpathSync } from "node:fs";
|
|
3
|
+
import { dirname, resolve } from "node:path";
|
|
4
|
+
import { fileURLToPath } from "node:url";
|
|
5
|
+
const PACKAGE_NAME = "mastracode";
|
|
6
|
+
const NPM_REGISTRY_URL = `https://registry.npmjs.org/${PACKAGE_NAME}/latest`;
|
|
7
|
+
const FETCH_TIMEOUT_MS = 5e3;
|
|
8
|
+
function matchPM(str) {
|
|
9
|
+
if (/pnpm/i.test(str)) return "pnpm";
|
|
10
|
+
if (/\byarn\b/i.test(str)) return "yarn";
|
|
11
|
+
if (/\bbun\b/i.test(str)) return "bun";
|
|
12
|
+
if (/\bnpm\b/i.test(str)) return "npm";
|
|
13
|
+
return null;
|
|
14
|
+
}
|
|
15
|
+
async function detectPackageManager() {
|
|
16
|
+
const userAgent = process.env.npm_config_user_agent;
|
|
17
|
+
if (userAgent) {
|
|
18
|
+
const pm = matchPM(userAgent);
|
|
19
|
+
if (pm) return pm;
|
|
20
|
+
}
|
|
21
|
+
const execPath = process.env.npm_execpath;
|
|
22
|
+
if (execPath) {
|
|
23
|
+
const pm = matchPM(execPath);
|
|
24
|
+
if (pm) return pm;
|
|
25
|
+
}
|
|
26
|
+
const nodePath = process.env.NODE_PATH;
|
|
27
|
+
if (nodePath) {
|
|
28
|
+
if (/[/\\]\.pnpm[/\\]/.test(nodePath) || /[/\\]pnpm[/\\]/.test(nodePath)) return "pnpm";
|
|
29
|
+
if (/[/\\]\.yarn[/\\]/.test(nodePath)) return "yarn";
|
|
30
|
+
if (/[/\\]\.bun[/\\]/.test(nodePath)) return "bun";
|
|
31
|
+
}
|
|
32
|
+
try {
|
|
33
|
+
const scriptPath = realpathSync(process.argv[1] ?? "");
|
|
34
|
+
if (/[/\\]\.?pnpm[/\\]/.test(scriptPath)) return "pnpm";
|
|
35
|
+
if (/[/\\]\.?yarn[/\\]/.test(scriptPath)) return "yarn";
|
|
36
|
+
if (/[/\\]\.?bun[/\\]/.test(scriptPath)) return "bun";
|
|
37
|
+
} catch {
|
|
38
|
+
}
|
|
39
|
+
const pnpmResult = await new Promise((resolve2) => {
|
|
40
|
+
execFile("pnpm", ["list", "-g", "--depth=0", PACKAGE_NAME], { timeout: 3e3 }, (error, stdout) => {
|
|
41
|
+
resolve2(!error && stdout.includes(PACKAGE_NAME));
|
|
42
|
+
});
|
|
43
|
+
});
|
|
44
|
+
if (pnpmResult) return "pnpm";
|
|
45
|
+
return "npm";
|
|
46
|
+
}
|
|
47
|
+
function getInstallCommand(pm, version) {
|
|
48
|
+
const pkg = version ? `${PACKAGE_NAME}@${version}` : `${PACKAGE_NAME}@latest`;
|
|
49
|
+
switch (pm) {
|
|
50
|
+
case "pnpm":
|
|
51
|
+
return `pnpm add -g ${pkg}`;
|
|
52
|
+
case "yarn":
|
|
53
|
+
return `yarn global add ${pkg}`;
|
|
54
|
+
case "bun":
|
|
55
|
+
return `bun add -g ${pkg}`;
|
|
56
|
+
default:
|
|
57
|
+
return `npm install -g ${pkg}`;
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
function getCurrentVersion() {
|
|
61
|
+
if (typeof MASTRACODE_VERSION !== "undefined") {
|
|
62
|
+
return MASTRACODE_VERSION;
|
|
63
|
+
}
|
|
64
|
+
const dir = dirname(fileURLToPath(import.meta.url));
|
|
65
|
+
const pkgPath = resolve(dir, "../../package.json");
|
|
66
|
+
const pkg = JSON.parse(readFileSync(pkgPath, "utf-8"));
|
|
67
|
+
return pkg.version;
|
|
68
|
+
}
|
|
69
|
+
async function fetchLatestVersion() {
|
|
70
|
+
if (process.env.MASTRACODE_UPDATE_LATEST_VERSION) return process.env.MASTRACODE_UPDATE_LATEST_VERSION;
|
|
71
|
+
try {
|
|
72
|
+
const controller = new AbortController();
|
|
73
|
+
const timeout = setTimeout(() => controller.abort(), FETCH_TIMEOUT_MS);
|
|
74
|
+
const res = await fetch(NPM_REGISTRY_URL, { signal: controller.signal });
|
|
75
|
+
clearTimeout(timeout);
|
|
76
|
+
if (!res.ok) return null;
|
|
77
|
+
const data = await res.json();
|
|
78
|
+
return data.version ?? null;
|
|
79
|
+
} catch {
|
|
80
|
+
return null;
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
function isNewerVersion(current, latest) {
|
|
84
|
+
const parse = (v) => v.replace(/^v/, "").split("-")[0].split(".").map((s) => {
|
|
85
|
+
const n = Number(s);
|
|
86
|
+
return Number.isFinite(n) ? n : 0;
|
|
87
|
+
});
|
|
88
|
+
const [cMajor = 0, cMinor = 0, cPatch = 0] = parse(current);
|
|
89
|
+
const [lMajor = 0, lMinor = 0, lPatch = 0] = parse(latest);
|
|
90
|
+
if (lMajor !== cMajor) return lMajor > cMajor;
|
|
91
|
+
if (lMinor !== cMinor) return lMinor > cMinor;
|
|
92
|
+
return lPatch > cPatch;
|
|
93
|
+
}
|
|
94
|
+
const MAX_CHANGELOG_ENTRIES = 20;
|
|
95
|
+
async function fetchChangelog(version) {
|
|
96
|
+
if (process.env.MASTRACODE_UPDATE_CHANGELOG) return process.env.MASTRACODE_UPDATE_CHANGELOG;
|
|
97
|
+
try {
|
|
98
|
+
const url = `https://unpkg.com/${PACKAGE_NAME}@${version}/CHANGELOG.md`;
|
|
99
|
+
const controller = new AbortController();
|
|
100
|
+
const timeout = setTimeout(() => controller.abort(), FETCH_TIMEOUT_MS);
|
|
101
|
+
const res = await fetch(url, { signal: controller.signal });
|
|
102
|
+
clearTimeout(timeout);
|
|
103
|
+
if (!res.ok) return null;
|
|
104
|
+
const text = await res.text();
|
|
105
|
+
return parseChangelog(text, version);
|
|
106
|
+
} catch {
|
|
107
|
+
return null;
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
function parseChangelog(markdown, version) {
|
|
111
|
+
const versionHeader = `## ${version}`;
|
|
112
|
+
const startIdx = markdown.indexOf(versionHeader);
|
|
113
|
+
if (startIdx === -1) return null;
|
|
114
|
+
const afterHeader = startIdx + versionHeader.length;
|
|
115
|
+
const nextHeaderIdx = markdown.indexOf("\n## ", afterHeader);
|
|
116
|
+
const section = nextHeaderIdx === -1 ? markdown.slice(afterHeader) : markdown.slice(afterHeader, nextHeaderIdx);
|
|
117
|
+
const lines = section.split("\n");
|
|
118
|
+
const entries = [];
|
|
119
|
+
let skipIndented = false;
|
|
120
|
+
for (let i = 0; i < lines.length; i++) {
|
|
121
|
+
const raw = lines[i];
|
|
122
|
+
const trimmed = raw.trim();
|
|
123
|
+
const isTopLevel = raw === trimmed && trimmed.startsWith("- ");
|
|
124
|
+
if (!isTopLevel) {
|
|
125
|
+
if (skipIndented && /^\s+-\s/.test(raw)) continue;
|
|
126
|
+
skipIndented = false;
|
|
127
|
+
continue;
|
|
128
|
+
}
|
|
129
|
+
if (/^- Updated dependenc/i.test(trimmed)) {
|
|
130
|
+
skipIndented = true;
|
|
131
|
+
continue;
|
|
132
|
+
}
|
|
133
|
+
skipIndented = false;
|
|
134
|
+
let entry = trimmed.slice(2);
|
|
135
|
+
entry = entry.replace(/\[([^\]]+)\]\([^)]+\)/g, "$1");
|
|
136
|
+
entry = entry.replace(/\s*\(#\d+\)\s*/g, " ");
|
|
137
|
+
entry = entry.replace(/\s*\(`[a-f0-9]+`\)\s*/g, " ");
|
|
138
|
+
entry = entry.replace(/\.\s*$/, "");
|
|
139
|
+
entry = entry.trim();
|
|
140
|
+
if (entry) entries.push(entry);
|
|
141
|
+
if (entries.length >= MAX_CHANGELOG_ENTRIES) break;
|
|
142
|
+
}
|
|
143
|
+
if (entries.length === 0) return null;
|
|
144
|
+
return entries.map((e) => ` \u2022 ${e}`).join("\n");
|
|
145
|
+
}
|
|
146
|
+
function runUpdate(pm, targetVersion) {
|
|
147
|
+
const args = buildInstallArgs(pm, targetVersion);
|
|
148
|
+
return new Promise((resolve2) => {
|
|
149
|
+
execFile(pm, args, { timeout: 6e4 }, (error) => {
|
|
150
|
+
resolve2(!error);
|
|
151
|
+
});
|
|
152
|
+
});
|
|
153
|
+
}
|
|
154
|
+
function buildInstallArgs(pm, version) {
|
|
155
|
+
const pkg = `${PACKAGE_NAME}@${version}`;
|
|
156
|
+
switch (pm) {
|
|
157
|
+
case "pnpm":
|
|
158
|
+
return ["add", "-g", pkg];
|
|
159
|
+
case "yarn":
|
|
160
|
+
return ["global", "add", pkg];
|
|
161
|
+
case "bun":
|
|
162
|
+
return ["add", "-g", pkg];
|
|
163
|
+
default:
|
|
164
|
+
return ["install", "-g", pkg];
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
export {
|
|
168
|
+
detectPackageManager,
|
|
169
|
+
fetchChangelog,
|
|
170
|
+
fetchLatestVersion,
|
|
171
|
+
getCurrentVersion,
|
|
172
|
+
getInstallCommand,
|
|
173
|
+
isNewerVersion,
|
|
174
|
+
parseChangelog,
|
|
175
|
+
runUpdate
|
|
176
|
+
};
|
|
177
|
+
//# sourceMappingURL=update-check.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/utils/update-check.ts"],"sourcesContent":["/**\n * Check for newer versions of mastracode on npm.\n */\n\nimport { execFile } from 'node:child_process';\nimport { readFileSync, realpathSync } from 'node:fs';\nimport { dirname, resolve } from 'node:path';\nimport { fileURLToPath } from 'node:url';\n\ndeclare const MASTRACODE_VERSION: string | undefined;\n\nconst PACKAGE_NAME = 'mastracode';\nconst NPM_REGISTRY_URL = `https://registry.npmjs.org/${PACKAGE_NAME}/latest`;\n\n/** Timeout for the npm registry fetch (ms). */\nconst FETCH_TIMEOUT_MS = 5_000;\n\nexport type PackageManager = 'npm' | 'pnpm' | 'yarn' | 'bun';\n\nfunction matchPM(str: string): PackageManager | null {\n // Order matters: check pnpm before npm since \"npm\" is a substring of some paths\n if (/pnpm/i.test(str)) return 'pnpm';\n if (/\\byarn\\b/i.test(str)) return 'yarn';\n if (/\\bbun\\b/i.test(str)) return 'bun';\n if (/\\bnpm\\b/i.test(str)) return 'npm';\n return null;\n}\n\n/**\n * Detect which package manager was used to install mastracode globally.\n *\n * Uses a multi-tier approach:\n * 1. npm_config_user_agent env var (set when run via `pnpm run`, `npm run`, etc.)\n * 2. npm_execpath env var (fallback for script-based invocation)\n * 3. NODE_PATH env var (set by pnpm global bin stubs)\n * 4. Resolved path of the running script (process.argv[1])\n * 5. Shell-out to `pnpm list -g` to check if pnpm manages the package\n * 6. Falls back to 'npm'\n */\nexport async function detectPackageManager(): Promise<PackageManager> {\n // Tier 1: npm_config_user_agent (most reliable when available)\n const userAgent = process.env.npm_config_user_agent;\n if (userAgent) {\n const pm = matchPM(userAgent);\n if (pm) return pm;\n }\n\n // Tier 2: npm_execpath\n const execPath = process.env.npm_execpath;\n if (execPath) {\n const pm = matchPM(execPath);\n if (pm) return pm;\n }\n\n // Tier 3: NODE_PATH (pnpm global stubs set this to paths containing .pnpm)\n const nodePath = process.env.NODE_PATH;\n if (nodePath) {\n if (/[/\\\\]\\.pnpm[/\\\\]/.test(nodePath) || /[/\\\\]pnpm[/\\\\]/.test(nodePath)) return 'pnpm';\n if (/[/\\\\]\\.yarn[/\\\\]/.test(nodePath)) return 'yarn';\n if (/[/\\\\]\\.bun[/\\\\]/.test(nodePath)) return 'bun';\n }\n\n // Tier 4: Resolved script path\n try {\n const scriptPath = realpathSync(process.argv[1] ?? '');\n if (/[/\\\\]\\.?pnpm[/\\\\]/.test(scriptPath)) return 'pnpm';\n if (/[/\\\\]\\.?yarn[/\\\\]/.test(scriptPath)) return 'yarn';\n if (/[/\\\\]\\.?bun[/\\\\]/.test(scriptPath)) return 'bun';\n } catch {\n // realpathSync can fail if argv[1] is missing/broken — fall through\n }\n\n // Tier 5: Check if the package is in pnpm's global store (non-blocking)\n const pnpmResult = await new Promise<boolean>(resolve => {\n execFile('pnpm', ['list', '-g', '--depth=0', PACKAGE_NAME], { timeout: 3_000 }, (error, stdout) => {\n resolve(!error && stdout.includes(PACKAGE_NAME));\n });\n });\n if (pnpmResult) return 'pnpm';\n\n return 'npm';\n}\n\n/**\n * Build the shell command string a user would run to install/update globally.\n */\nexport function getInstallCommand(pm: PackageManager, version?: string): string {\n const pkg = version ? `${PACKAGE_NAME}@${version}` : `${PACKAGE_NAME}@latest`;\n switch (pm) {\n case 'pnpm':\n return `pnpm add -g ${pkg}`;\n case 'yarn':\n return `yarn global add ${pkg}`;\n case 'bun':\n return `bun add -g ${pkg}`;\n default:\n return `npm install -g ${pkg}`;\n }\n}\n\n/**\n * Read the current version, injected at build time by tsup's `define` option.\n * Falls back to reading package.json at runtime (e.g. when running from source with tsx).\n */\nexport function getCurrentVersion(): string {\n if (typeof MASTRACODE_VERSION !== 'undefined') {\n return MASTRACODE_VERSION;\n }\n // Fallback for running from source (e.g. pnpx tsx mastracode/src/main.ts)\n const dir = dirname(fileURLToPath(import.meta.url));\n const pkgPath = resolve(dir, '../../package.json');\n const pkg = JSON.parse(readFileSync(pkgPath, 'utf-8'));\n return pkg.version;\n}\n\n/**\n * Fetch the latest published version from npm.\n * Returns null if the fetch fails (network error, timeout, etc.).\n */\nexport async function fetchLatestVersion(): Promise<string | null> {\n if (process.env.MASTRACODE_UPDATE_LATEST_VERSION) return process.env.MASTRACODE_UPDATE_LATEST_VERSION;\n\n try {\n const controller = new AbortController();\n const timeout = setTimeout(() => controller.abort(), FETCH_TIMEOUT_MS);\n const res = await fetch(NPM_REGISTRY_URL, { signal: controller.signal });\n clearTimeout(timeout);\n if (!res.ok) return null;\n const data = (await res.json()) as { version?: string };\n return data.version ?? null;\n } catch {\n return null;\n }\n}\n\n/**\n * Simple semver comparison: returns true if `latest` is newer than `current`.\n * Handles standard x.y.z versions. Ignores pre-release tags.\n */\nexport function isNewerVersion(current: string, latest: string): boolean {\n const parse = (v: string) =>\n v\n .replace(/^v/, '')\n .split('-')[0]!\n .split('.')\n .map(s => {\n const n = Number(s);\n return Number.isFinite(n) ? n : 0;\n });\n const [cMajor = 0, cMinor = 0, cPatch = 0] = parse(current);\n const [lMajor = 0, lMinor = 0, lPatch = 0] = parse(latest);\n if (lMajor !== cMajor) return lMajor > cMajor;\n if (lMinor !== cMinor) return lMinor > cMinor;\n return lPatch > cPatch;\n}\n\n/** Max entries to show in the changelog summary. */\nconst MAX_CHANGELOG_ENTRIES = 20;\n\n/**\n * Fetch the CHANGELOG.md for a specific published version from the npm CDN\n * and extract a human-readable summary of changes.\n * Returns null if the fetch fails or the changelog can't be parsed.\n */\nexport async function fetchChangelog(version: string): Promise<string | null> {\n if (process.env.MASTRACODE_UPDATE_CHANGELOG) return process.env.MASTRACODE_UPDATE_CHANGELOG;\n\n try {\n const url = `https://unpkg.com/${PACKAGE_NAME}@${version}/CHANGELOG.md`;\n const controller = new AbortController();\n const timeout = setTimeout(() => controller.abort(), FETCH_TIMEOUT_MS);\n const res = await fetch(url, { signal: controller.signal });\n clearTimeout(timeout);\n if (!res.ok) return null;\n const text = await res.text();\n return parseChangelog(text, version);\n } catch {\n return null;\n }\n}\n\n/**\n * Extract the changelog section for a specific version and format it\n * as a concise bullet list suitable for terminal display.\n */\nexport function parseChangelog(markdown: string, version: string): string | null {\n const versionHeader = `## ${version}`;\n const startIdx = markdown.indexOf(versionHeader);\n if (startIdx === -1) return null;\n\n const afterHeader = startIdx + versionHeader.length;\n const nextHeaderIdx = markdown.indexOf('\\n## ', afterHeader);\n const section = nextHeaderIdx === -1 ? markdown.slice(afterHeader) : markdown.slice(afterHeader, nextHeaderIdx);\n\n const lines = section.split('\\n');\n const entries: string[] = [];\n let skipIndented = false;\n\n for (let i = 0; i < lines.length; i++) {\n const raw = lines[i]!;\n const trimmed = raw.trim();\n\n // Only match top-level entries (no leading whitespace before \"- \")\n const isTopLevel = raw === trimmed && trimmed.startsWith('- ');\n if (!isTopLevel) {\n // Skip indented sub-items (e.g. dependency list entries)\n if (skipIndented && /^\\s+-\\s/.test(raw)) continue;\n skipIndented = false;\n continue;\n }\n\n // Skip dependency update entries and their sub-items\n if (/^- Updated dependenc/i.test(trimmed)) {\n skipIndented = true;\n continue;\n }\n skipIndented = false;\n\n let entry = trimmed.slice(2);\n // Strip markdown links: [text](url) → text\n entry = entry.replace(/\\[([^\\]]+)\\]\\([^)]+\\)/g, '$1');\n // Strip PR references like (#15760)\n entry = entry.replace(/\\s*\\(#\\d+\\)\\s*/g, ' ');\n // Strip commit SHA references like (`abc123`)\n entry = entry.replace(/\\s*\\(`[a-f0-9]+`\\)\\s*/g, ' ');\n // Trim trailing periods for cleaner bullet display\n entry = entry.replace(/\\.\\s*$/, '');\n entry = entry.trim();\n if (entry) entries.push(entry);\n if (entries.length >= MAX_CHANGELOG_ENTRIES) break;\n }\n\n if (entries.length === 0) return null;\n return entries.map(e => ` • ${e}`).join('\\n');\n}\n\n/**\n * Run the appropriate global install command to update mastracode.\n * Returns true on success, false on failure.\n */\nexport function runUpdate(pm: PackageManager, targetVersion: string): Promise<boolean> {\n const args = buildInstallArgs(pm, targetVersion);\n return new Promise(resolve => {\n execFile(pm, args, { timeout: 60_000 }, error => {\n resolve(!error);\n });\n });\n}\n\nfunction buildInstallArgs(pm: PackageManager, version: string): string[] {\n const pkg = `${PACKAGE_NAME}@${version}`;\n switch (pm) {\n case 'pnpm':\n return ['add', '-g', pkg];\n case 'yarn':\n return ['global', 'add', pkg];\n case 'bun':\n return ['add', '-g', pkg];\n default:\n return ['install', '-g', pkg];\n }\n}\n"],"mappings":"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","names":["resolve"]}
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Speech-to-text (STT) model registry for push-to-talk voice input.
|
|
3
|
+
*
|
|
4
|
+
* This is the single source of truth shared by the `/voice` settings picker,
|
|
5
|
+
* settings validation, and the cloud transcription resolver. Entries are
|
|
6
|
+
* derived from models.dev (https://models.dev/api.json): a model is treated as
|
|
7
|
+
* STT when its modalities are audio-in / text-out with no text input.
|
|
8
|
+
*
|
|
9
|
+
* Most cloud providers here speak the OpenAI `/audio/transcriptions` shape, so
|
|
10
|
+
* the cloud engine resolves them through Mastra's `OpenAIVoice` (`MastraVoice`)
|
|
11
|
+
* built on the OpenAI client — `openai` uses the default endpoint, every other
|
|
12
|
+
* OpenAI-compatible host supplies its `baseURL`. Deepgram is the exception: it
|
|
13
|
+
* is not OpenAI-compatible, so it resolves through `@mastra/voice-deepgram`
|
|
14
|
+
* (`DeepgramVoice`). Deepgram is not in the models.dev STT snapshot but is a
|
|
15
|
+
* first-class hosted STT provider, so it is added deliberately.
|
|
16
|
+
*
|
|
17
|
+
* The accompanying snapshot test (`__tests__/stt-registry.test.ts`) re-derives
|
|
18
|
+
* the models.dev-backed entries from a checked-in snapshot so this list stays
|
|
19
|
+
* honest and can be refreshed deliberately rather than drifting by hand.
|
|
20
|
+
*/
|
|
21
|
+
/**
|
|
22
|
+
* How a provider's transcription model is constructed.
|
|
23
|
+
* - `openai`: `OpenAIVoice` with the default OpenAI endpoint.
|
|
24
|
+
* - `openai-compatible`: `OpenAIVoice` pointed at the host's `baseURL`
|
|
25
|
+
* (its `/audio/transcriptions` endpoint follows the OpenAI shape).
|
|
26
|
+
* - `deepgram`: `DeepgramVoice` from `@mastra/voice-deepgram` (not
|
|
27
|
+
* OpenAI-compatible; uses the Deepgram SDK).
|
|
28
|
+
*/
|
|
29
|
+
export type STTResolver = 'openai' | 'openai-compatible' | 'deepgram';
|
|
30
|
+
export interface STTModel {
|
|
31
|
+
/** models.dev provider id (also the AuthStorage key for the API key). */
|
|
32
|
+
provider: string;
|
|
33
|
+
/** Bare model id passed to the provider's transcription factory. */
|
|
34
|
+
model: string;
|
|
35
|
+
/** Human-friendly label for the picker. */
|
|
36
|
+
label: string;
|
|
37
|
+
/** How to build the transcription model. */
|
|
38
|
+
resolver: STTResolver;
|
|
39
|
+
/** Base URL for `openai-compatible` providers (from models.dev `api`). */
|
|
40
|
+
baseURL?: string;
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* The curated STT catalog. Order matters: the first entry for a provider is its
|
|
44
|
+
* default model, and the first overall entry (`openai`/`whisper-1`) is the
|
|
45
|
+
* global default.
|
|
46
|
+
*/
|
|
47
|
+
export declare const STT_MODELS: readonly STTModel[];
|
|
48
|
+
/** The global default STT model (first registry entry). */
|
|
49
|
+
export declare const DEFAULT_STT_MODEL: STTModel;
|
|
50
|
+
/** The default STT provider when none is configured. */
|
|
51
|
+
export declare const DEFAULT_STT_PROVIDER: string;
|
|
52
|
+
/** Unique provider ids in registry order. */
|
|
53
|
+
export declare function sttProviders(): string[];
|
|
54
|
+
/** All models offered for a provider, in registry order. */
|
|
55
|
+
export declare function sttModelsForProvider(provider: string): STTModel[];
|
|
56
|
+
/** The default (first) model for a provider, or undefined if unknown. */
|
|
57
|
+
export declare function defaultModelForProvider(provider: string): STTModel | undefined;
|
|
58
|
+
/**
|
|
59
|
+
* Resolve a provider/model pair to a concrete registry entry.
|
|
60
|
+
* Falls back to the provider default (then the global default) so callers
|
|
61
|
+
* always get a usable entry even if settings name an unknown model.
|
|
62
|
+
*/
|
|
63
|
+
export declare function resolveSTTModel(provider?: string, model?: string): STTModel;
|
|
64
|
+
//# sourceMappingURL=stt-registry.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"stt-registry.d.ts","sourceRoot":"","sources":["../../src/voice/stt-registry.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;GAmBG;AAEH;;;;;;;GAOG;AACH,MAAM,MAAM,WAAW,GAAG,QAAQ,GAAG,mBAAmB,GAAG,UAAU,CAAC;AAEtE,MAAM,WAAW,QAAQ;IACvB,yEAAyE;IACzE,QAAQ,EAAE,MAAM,CAAC;IACjB,oEAAoE;IACpE,KAAK,EAAE,MAAM,CAAC;IACd,2CAA2C;IAC3C,KAAK,EAAE,MAAM,CAAC;IACd,4CAA4C;IAC5C,QAAQ,EAAE,WAAW,CAAC;IACtB,0EAA0E;IAC1E,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AAED;;;;GAIG;AACH,eAAO,MAAM,UAAU,EAAE,SAAS,QAAQ,EAqFhC,CAAC;AAEX,2DAA2D;AAC3D,eAAO,MAAM,iBAAiB,EAAE,QAAyB,CAAC;AAE1D,wDAAwD;AACxD,eAAO,MAAM,oBAAoB,QAA6B,CAAC;AAE/D,6CAA6C;AAC7C,wBAAgB,YAAY,IAAI,MAAM,EAAE,CAUvC;AAED,4DAA4D;AAC5D,wBAAgB,oBAAoB,CAAC,QAAQ,EAAE,MAAM,GAAG,QAAQ,EAAE,CAEjE;AAED,yEAAyE;AACzE,wBAAgB,uBAAuB,CAAC,QAAQ,EAAE,MAAM,GAAG,QAAQ,GAAG,SAAS,CAE9E;AAED;;;;GAIG;AACH,wBAAgB,eAAe,CAAC,QAAQ,CAAC,EAAE,MAAM,EAAE,KAAK,CAAC,EAAE,MAAM,GAAG,QAAQ,CAU3E"}
|