@mastra/code-sdk 1.15.0-alpha.11 → 1.15.0-alpha.13

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (133) hide show
  1. package/dist/acp/agent.js +2 -1
  2. package/dist/acp/agent.js.map +1 -1
  3. package/dist/acp/event-mapper.js +0 -2
  4. package/dist/acp/event-mapper.js.map +1 -1
  5. package/dist/acp.js +1 -2
  6. package/dist/agent-connections/registry.js.map +1 -1
  7. package/dist/agent-connections/signal-provider.js.map +1 -1
  8. package/dist/agent-connections/state-processor.js.map +1 -1
  9. package/dist/agent-connections/tools.js +26 -24
  10. package/dist/agent-connections/tools.js.map +1 -1
  11. package/dist/agents/instructions.js +3 -2
  12. package/dist/agents/instructions.js.map +1 -1
  13. package/dist/agents/mastracode-gateway.js +35 -27
  14. package/dist/agents/mastracode-gateway.js.map +1 -1
  15. package/dist/agents/memory.d.ts.map +1 -1
  16. package/dist/agents/memory.js +16 -2
  17. package/dist/agents/memory.js.map +1 -1
  18. package/dist/agents/prompts/agent-instructions.js +1 -1
  19. package/dist/agents/prompts/agent-instructions.js.map +1 -1
  20. package/dist/agents/prompts/index.js +3 -2
  21. package/dist/agents/prompts/index.js.map +1 -1
  22. package/dist/agents/prompts/plan.js +2 -1
  23. package/dist/agents/prompts/plan.js.map +1 -1
  24. package/dist/agents/sandbox-filesystem.d.ts +28 -0
  25. package/dist/agents/sandbox-filesystem.d.ts.map +1 -1
  26. package/dist/agents/sandbox-filesystem.js +81 -2
  27. package/dist/agents/sandbox-filesystem.js.map +1 -1
  28. package/dist/agents/tools.d.ts.map +1 -1
  29. package/dist/agents/tools.js +18 -12
  30. package/dist/agents/tools.js.map +1 -1
  31. package/dist/agents/workspace.js +2 -1
  32. package/dist/agents/workspace.js.map +1 -1
  33. package/dist/auth/account-rotation-processor.js +2 -1
  34. package/dist/auth/account-rotation-processor.js.map +1 -1
  35. package/dist/auth/providers/anthropic.js +2 -2
  36. package/dist/auth/providers/anthropic.js.map +1 -1
  37. package/dist/auth/providers/github-copilot.js +1 -1
  38. package/dist/auth/providers/github-copilot.js.map +1 -1
  39. package/dist/auth/providers/openai-codex.js +3 -2
  40. package/dist/auth/providers/openai-codex.js.map +1 -1
  41. package/dist/auth/providers/xai.js +1 -1
  42. package/dist/auth/providers/xai.js.map +1 -1
  43. package/dist/clipboard/index.js +5 -4
  44. package/dist/clipboard/index.js.map +1 -1
  45. package/dist/evals/context-builder.js +1 -3
  46. package/dist/evals/context-builder.js.map +1 -1
  47. package/dist/evals/scorers/outcome.js +2 -1
  48. package/dist/evals/scorers/outcome.js.map +1 -1
  49. package/dist/factory/remote-controller.js.map +1 -1
  50. package/dist/factory/remote-session.js +1 -3
  51. package/dist/factory/remote-session.js.map +1 -1
  52. package/dist/headless/cli.js +2 -1
  53. package/dist/headless/cli.js.map +1 -1
  54. package/dist/headless/run-mc.js +9 -9
  55. package/dist/headless/run-mc.js.map +1 -1
  56. package/dist/hooks/executor.js +3 -1
  57. package/dist/hooks/executor.js.map +1 -1
  58. package/dist/hooks/manager.js +40 -26
  59. package/dist/hooks/manager.js.map +1 -1
  60. package/dist/index.d.ts.map +1 -1
  61. package/dist/index.js +14 -10
  62. package/dist/index.js.map +1 -1
  63. package/dist/knowledge-inspector.js +2 -2
  64. package/dist/knowledge-inspector.js.map +1 -1
  65. package/dist/lsp/server.js +4 -2
  66. package/dist/lsp/server.js.map +1 -1
  67. package/dist/mcp/manager.d.ts.map +1 -1
  68. package/dist/mcp/manager.js +14 -2
  69. package/dist/mcp/manager.js.map +1 -1
  70. package/dist/onboarding/settings.js +5 -2
  71. package/dist/onboarding/settings.js.map +1 -1
  72. package/dist/platform/index.js +5 -3
  73. package/dist/platform/index.js.map +1 -1
  74. package/dist/plugins/loader.js +3 -1
  75. package/dist/plugins/loader.js.map +1 -1
  76. package/dist/plugins/manager.js +2 -1
  77. package/dist/plugins/manager.js.map +1 -1
  78. package/dist/plugins/package-link.js.map +1 -1
  79. package/dist/plugins/scaffold.js +4 -2
  80. package/dist/plugins/scaffold.js.map +1 -1
  81. package/dist/plugins/signal-lane.js.map +1 -1
  82. package/dist/process-memory-diagnostics.js +2 -1
  83. package/dist/process-memory-diagnostics.js.map +1 -1
  84. package/dist/providers/amazon-bedrock-gateway.js +2 -1
  85. package/dist/providers/amazon-bedrock-gateway.js.map +1 -1
  86. package/dist/providers/amazon-bedrock.js +2 -2
  87. package/dist/providers/amazon-bedrock.js.map +1 -1
  88. package/dist/providers/claude-max.js +13 -9
  89. package/dist/providers/claude-max.js.map +1 -1
  90. package/dist/providers/github-copilot-gateway.js.map +1 -1
  91. package/dist/providers/github-copilot.js +13 -12
  92. package/dist/providers/github-copilot.js.map +1 -1
  93. package/dist/providers/openai-codex.js +14 -11
  94. package/dist/providers/openai-codex.js.map +1 -1
  95. package/dist/providers/provider-thinking.js.map +1 -1
  96. package/dist/providers/thinking-request.js +1 -1
  97. package/dist/providers/thinking-request.js.map +1 -1
  98. package/dist/schedules/run-script.js +1 -1
  99. package/dist/schedules/run-script.js.map +1 -1
  100. package/dist/schedules/scheduler.js.map +1 -1
  101. package/dist/tools/request-sandbox-access.js +7 -5
  102. package/dist/tools/request-sandbox-access.js.map +1 -1
  103. package/dist/tools/utils.js +3 -1
  104. package/dist/tools/utils.js.map +1 -1
  105. package/dist/tools/web-search.d.ts +13 -11
  106. package/dist/tools/web-search.d.ts.map +1 -1
  107. package/dist/tools/web-search.js +21 -15
  108. package/dist/tools/web-search.js.map +1 -1
  109. package/dist/utils/debug-log.js +2 -2
  110. package/dist/utils/debug-log.js.map +1 -1
  111. package/dist/utils/gateway-sync.js +1 -1
  112. package/dist/utils/gateway-sync.js.map +1 -1
  113. package/dist/utils/local-observability.d.ts +1 -1
  114. package/dist/utils/local-observability.d.ts.map +1 -1
  115. package/dist/utils/local-observability.js +1 -1
  116. package/dist/utils/local-observability.js.map +1 -1
  117. package/dist/utils/prune-cli.js +10 -8
  118. package/dist/utils/prune-cli.js.map +1 -1
  119. package/dist/utils/signals-pubsub.js.map +1 -1
  120. package/dist/utils/slash-command-processor.js +1 -1
  121. package/dist/utils/slash-command-processor.js.map +1 -1
  122. package/dist/utils/stdin-pipe.js +1 -1
  123. package/dist/utils/stdin-pipe.js.map +1 -1
  124. package/dist/utils/storage-factory.d.ts.map +1 -1
  125. package/dist/utils/storage-factory.js +1 -1
  126. package/dist/utils/storage-factory.js.map +1 -1
  127. package/dist/utils/storage-maintenance.js +1 -1
  128. package/dist/utils/storage-maintenance.js.map +1 -1
  129. package/dist/utils/update-check.js +2 -1
  130. package/dist/utils/update-check.js.map +1 -1
  131. package/dist/workflows/register-primitives.js +1 -1
  132. package/dist/workflows/register-primitives.js.map +1 -1
  133. package/package.json +12 -12
@@ -2,8 +2,6 @@ import { loadSettings } from "../onboarding/settings.js";
2
2
  import { truncateStringForTokenEstimate } from "../utils/token-estimator.js";
3
3
  import { createTool, isValidationError } from "@mastra/core/tools";
4
4
  import { z } from "zod";
5
- import { createParallelExtractTool, createParallelSearchTool } from "@mastra/parallel";
6
- import { createTavilyExtractTool, createTavilySearchTool } from "@mastra/tavily";
7
5
  //#region src/tools/web-search.ts
8
6
  const MAX_WEB_SEARCH_TOKENS = 2e3;
9
7
  const MAX_WEB_EXTRACT_TOKENS = 2e3;
@@ -31,7 +29,8 @@ function hasParallelKey() {
31
29
  * relevance filtering, markdown string formatting, and token truncation.
32
30
  * The underlying Tavily tool handles client init, input validation, and the API call.
33
31
  */
34
- function createWebSearchTool() {
32
+ async function createWebSearchTool() {
33
+ const { createTavilySearchTool } = await import("@mastra/tavily");
35
34
  const tavilySearchTool = createTavilySearchTool();
36
35
  return createTool({
37
36
  id: "web-search",
@@ -45,7 +44,8 @@ function createWebSearchTool() {
45
44
  for (const r of filtered) parts.push(`## ${r.title}\n${r.url}\n${r.content}`);
46
45
  const images = (output.images || []).map((img) => img.url).filter(Boolean);
47
46
  if (images.length > 0) parts.push(`Images:\n${images.join("\n")}`);
48
- return truncateStringForTokenEstimate(parts.join("\n\n"), MAX_WEB_SEARCH_TOKENS);
47
+ const text = parts.join("\n\n");
48
+ return truncateStringForTokenEstimate(text, MAX_WEB_SEARCH_TOKENS);
49
49
  }
50
50
  });
51
51
  }
@@ -53,7 +53,8 @@ function createWebSearchTool() {
53
53
  * Wraps the @mastra/tavily extract tool with mastracode-specific behavior:
54
54
  * markdown string formatting and token truncation.
55
55
  */
56
- function createWebExtractTool() {
56
+ async function createWebExtractTool() {
57
+ const { createTavilyExtractTool } = await import("@mastra/tavily");
57
58
  const tavilyExtractTool = createTavilyExtractTool();
58
59
  return createTool({
59
60
  id: "web-extract",
@@ -64,7 +65,8 @@ function createWebExtractTool() {
64
65
  const parts = [];
65
66
  for (const r of output.results) parts.push(`## ${r.url}\n${r.rawContent}`);
66
67
  for (const r of output.failedResults) parts.push(`## ${r.url}\nError: ${r.error}`);
67
- return truncateStringForTokenEstimate(parts.join("\n\n"), MAX_WEB_EXTRACT_TOKENS);
68
+ const text = parts.join("\n\n");
69
+ return truncateStringForTokenEstimate(text, MAX_WEB_EXTRACT_TOKENS);
68
70
  }
69
71
  });
70
72
  }
@@ -72,7 +74,8 @@ function createWebExtractTool() {
72
74
  * Wraps the @mastra/parallel search tool with Mastra Code's standard tool id,
73
75
  * markdown string output, and token truncation.
74
76
  */
75
- function createParallelWebSearchTool() {
77
+ async function createParallelWebSearchTool() {
78
+ const { createParallelSearchTool } = await import("@mastra/parallel");
76
79
  const parallelSearchTool = createParallelSearchTool();
77
80
  return createTool({
78
81
  id: "web-search",
@@ -98,7 +101,8 @@ function createParallelWebSearchTool() {
98
101
  * Wraps the @mastra/parallel extract tool with Mastra Code's standard tool id,
99
102
  * markdown string output, and token truncation.
100
103
  */
101
- function createParallelWebExtractTool() {
104
+ async function createParallelWebExtractTool() {
105
+ const { createParallelExtractTool } = await import("@mastra/parallel");
102
106
  const parallelExtractTool = createParallelExtractTool();
103
107
  return createTool({
104
108
  id: "web-extract",
@@ -137,16 +141,18 @@ function resolveWebSearchProvider(preference = "auto") {
137
141
  /**
138
142
  * Create the configured model-independent web tools for the provider selected
139
143
  * via the `webSearchProvider` preference (set in the TUI settings panel).
144
+ * Returns `undefined` synchronously when no provider key is configured, so the
145
+ * provider SDK (Tavily pulls in tiktoken, ~15 MB) only loads when one is.
140
146
  */
141
147
  function createConfiguredWebTools() {
142
148
  const provider = resolveWebSearchProvider(loadSettings().preferences.webSearchProvider);
143
- if (provider === "parallel") return {
144
- web_search: createParallelWebSearchTool(),
145
- web_extract: createParallelWebExtractTool()
146
- };
147
- if (provider === "tavily") return {
148
- web_search: createWebSearchTool(),
149
- web_extract: createWebExtractTool()
149
+ if (provider === "parallel") return loadWebTools(createParallelWebSearchTool, createParallelWebExtractTool);
150
+ if (provider === "tavily") return loadWebTools(createWebSearchTool, createWebExtractTool);
151
+ }
152
+ async function loadWebTools(createSearch, createExtract) {
153
+ return {
154
+ web_search: await createSearch(),
155
+ web_extract: await createExtract()
150
156
  };
151
157
  }
152
158
  //#endregion
@@ -1 +1 @@
1
- {"version":3,"file":"web-search.js","names":[],"sources":["../../src/tools/web-search.ts"],"sourcesContent":["import { createTool, isValidationError } from '@mastra/core/tools';\nimport type { ValidationError } from '@mastra/core/tools';\nimport { createParallelSearchTool, createParallelExtractTool } from '@mastra/parallel';\nimport { createTavilySearchTool, createTavilyExtractTool } from '@mastra/tavily';\nimport { z } from 'zod';\n\nimport { loadSettings } from '../onboarding/settings.js';\nimport type { WebSearchProviderSetting } from '../onboarding/settings.js';\nimport { truncateStringForTokenEstimate } from '../utils/token-estimator.js';\n\nconst MAX_WEB_SEARCH_TOKENS = 2_000;\nconst MAX_WEB_EXTRACT_TOKENS = 2_000;\n\nconst MIN_RELEVANCE_SCORE = 0.25;\n\nconst parallelWebSearchInputSchema = z.object({\n query: z.string().min(1).describe('The search query'),\n});\n\nfunction requireParallelOutput<T>(output: T | ValidationError | void, operation: 'search' | 'extract'): T {\n if (output === undefined) {\n throw new Error(`Parallel ${operation} returned no output`);\n }\n\n if (isValidationError(output)) {\n throw new Error(output.message);\n }\n\n return output;\n}\n\n/**\n * Check whether a Tavily API key is available in the environment.\n * Used to select model-independent web tools before falling back to\n * model-native web search.\n */\nexport function hasTavilyKey(): boolean {\n return !!process.env.TAVILY_API_KEY;\n}\n\n/** Check whether a Parallel API key is available in the environment. */\nexport function hasParallelKey(): boolean {\n return !!process.env.PARALLEL_API_KEY;\n}\n\n/**\n * Wraps the @mastra/tavily search tool with mastracode-specific behavior:\n * relevance filtering, markdown string formatting, and token truncation.\n * The underlying Tavily tool handles client init, input validation, and the API call.\n */\nexport function createWebSearchTool() {\n const tavilySearchTool = createTavilySearchTool();\n\n return createTool({\n id: 'web-search',\n description: tavilySearchTool.description!,\n inputSchema: tavilySearchTool.inputSchema!,\n execute: async (input, context) => {\n const output: any = await tavilySearchTool.execute!(input as any, context as any);\n\n const parts: string[] = [];\n\n if (output.answer) {\n parts.push(`Answer: ${output.answer}`);\n }\n\n const filtered = output.results.filter((r: any) => (r.score ?? 1) >= MIN_RELEVANCE_SCORE);\n for (const r of filtered) {\n parts.push(`## ${r.title}\\n${r.url}\\n${r.content}`);\n }\n\n const images = (output.images || []).map((img: any) => img.url).filter(Boolean);\n if (images.length > 0) {\n parts.push(`Images:\\n${images.join('\\n')}`);\n }\n\n const text = parts.join('\\n\\n');\n return truncateStringForTokenEstimate(text, MAX_WEB_SEARCH_TOKENS);\n },\n });\n}\n\n/**\n * Wraps the @mastra/tavily extract tool with mastracode-specific behavior:\n * markdown string formatting and token truncation.\n */\nexport function createWebExtractTool() {\n const tavilyExtractTool = createTavilyExtractTool();\n\n return createTool({\n id: 'web-extract',\n description: tavilyExtractTool.description!,\n inputSchema: tavilyExtractTool.inputSchema!,\n execute: async (input, context) => {\n const output: any = await tavilyExtractTool.execute!(input as any, context as any);\n\n const parts: string[] = [];\n\n for (const r of output.results) {\n parts.push(`## ${r.url}\\n${r.rawContent}`);\n }\n\n for (const r of output.failedResults) {\n parts.push(`## ${r.url}\\nError: ${r.error}`);\n }\n\n const text = parts.join('\\n\\n');\n return truncateStringForTokenEstimate(text, MAX_WEB_EXTRACT_TOKENS);\n },\n });\n}\n\n/**\n * Wraps the @mastra/parallel search tool with Mastra Code's standard tool id,\n * markdown string output, and token truncation.\n */\nexport function createParallelWebSearchTool() {\n const parallelSearchTool = createParallelSearchTool();\n\n return createTool({\n id: 'web-search',\n description: parallelSearchTool.description!,\n inputSchema: parallelWebSearchInputSchema,\n execute: async (input, context) => {\n const output = requireParallelOutput(\n await parallelSearchTool.execute!({ searchQueries: [input.query] }, context),\n 'search',\n );\n const parts: string[] = [];\n\n for (const result of output.results) {\n const title = result.title || result.url;\n const excerpts = (result.excerpts || []).filter(Boolean).join('\\n');\n parts.push([`## ${title}`, result.url, excerpts].filter(Boolean).join('\\n'));\n }\n\n return truncateStringForTokenEstimate(parts.join('\\n\\n'), MAX_WEB_SEARCH_TOKENS);\n },\n });\n}\n\n/**\n * Wraps the @mastra/parallel extract tool with Mastra Code's standard tool id,\n * markdown string output, and token truncation.\n */\nexport function createParallelWebExtractTool() {\n const parallelExtractTool = createParallelExtractTool();\n\n return createTool({\n id: 'web-extract',\n description: parallelExtractTool.description!,\n inputSchema: parallelExtractTool.inputSchema!,\n execute: async (input, context) => {\n const output = requireParallelOutput(await parallelExtractTool.execute!(input, context), 'extract');\n const parts: string[] = [];\n\n for (const result of output.results) {\n const content = result.fullContent || (result.excerpts || []).filter(Boolean).join('\\n');\n parts.push([`## ${result.url}`, content].filter(Boolean).join('\\n'));\n }\n\n for (const error of output.errors) {\n const status = error.httpStatusCode ? ` (${error.httpStatusCode})` : '';\n parts.push([`## ${error.url}`, `Error: ${error.errorType}${status}`, error.content].filter(Boolean).join('\\n'));\n }\n\n return truncateStringForTokenEstimate(parts.join('\\n\\n'), MAX_WEB_EXTRACT_TOKENS);\n },\n });\n}\n\n/**\n * Resolve which model-independent web provider to use. An explicit user\n * preference wins while its API key is configured; otherwise `auto` picks the\n * first configured provider key (Tavily, then Parallel).\n */\nexport function resolveWebSearchProvider(\n preference: WebSearchProviderSetting = 'auto',\n): 'tavily' | 'parallel' | undefined {\n if (preference === 'tavily' && hasTavilyKey()) return 'tavily';\n if (preference === 'parallel' && hasParallelKey()) return 'parallel';\n\n // `auto`, or an explicit choice whose key is no longer configured.\n if (hasTavilyKey()) return 'tavily';\n if (hasParallelKey()) return 'parallel';\n return undefined;\n}\n\n/**\n * Create the configured model-independent web tools for the provider selected\n * via the `webSearchProvider` preference (set in the TUI settings panel).\n */\nexport function createConfiguredWebTools() {\n const provider = resolveWebSearchProvider(loadSettings().preferences.webSearchProvider);\n\n if (provider === 'parallel') {\n return {\n web_search: createParallelWebSearchTool(),\n web_extract: createParallelWebExtractTool(),\n };\n }\n\n if (provider === 'tavily') {\n return {\n web_search: createWebSearchTool(),\n web_extract: createWebExtractTool(),\n };\n }\n\n return undefined;\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"web-search.js","names":[],"sources":["../../src/tools/web-search.ts"],"sourcesContent":["import { createTool, isValidationError } from '@mastra/core/tools';\nimport type { ValidationError } from '@mastra/core/tools';\nimport { z } from 'zod';\n\nimport { loadSettings } from '../onboarding/settings.js';\nimport type { WebSearchProviderSetting } from '../onboarding/settings.js';\nimport { truncateStringForTokenEstimate } from '../utils/token-estimator.js';\n\nconst MAX_WEB_SEARCH_TOKENS = 2_000;\nconst MAX_WEB_EXTRACT_TOKENS = 2_000;\n\nconst MIN_RELEVANCE_SCORE = 0.25;\n\nconst parallelWebSearchInputSchema = z.object({\n query: z.string().min(1).describe('The search query'),\n});\n\nfunction requireParallelOutput<T>(output: T | ValidationError | void, operation: 'search' | 'extract'): T {\n if (output === undefined) {\n throw new Error(`Parallel ${operation} returned no output`);\n }\n\n if (isValidationError(output)) {\n throw new Error(output.message);\n }\n\n return output;\n}\n\n/**\n * Check whether a Tavily API key is available in the environment.\n * Used to select model-independent web tools before falling back to\n * model-native web search.\n */\nexport function hasTavilyKey(): boolean {\n return !!process.env.TAVILY_API_KEY;\n}\n\n/** Check whether a Parallel API key is available in the environment. */\nexport function hasParallelKey(): boolean {\n return !!process.env.PARALLEL_API_KEY;\n}\n\n/**\n * Wraps the @mastra/tavily search tool with mastracode-specific behavior:\n * relevance filtering, markdown string formatting, and token truncation.\n * The underlying Tavily tool handles client init, input validation, and the API call.\n */\nexport async function createWebSearchTool() {\n const { createTavilySearchTool } = await import('@mastra/tavily');\n const tavilySearchTool = createTavilySearchTool();\n\n return createTool({\n id: 'web-search',\n description: tavilySearchTool.description!,\n inputSchema: tavilySearchTool.inputSchema!,\n execute: async (input, context) => {\n const output: any = await tavilySearchTool.execute!(input as any, context as any);\n\n const parts: string[] = [];\n\n if (output.answer) {\n parts.push(`Answer: ${output.answer}`);\n }\n\n const filtered = output.results.filter((r: any) => (r.score ?? 1) >= MIN_RELEVANCE_SCORE);\n for (const r of filtered) {\n parts.push(`## ${r.title}\\n${r.url}\\n${r.content}`);\n }\n\n const images = (output.images || []).map((img: any) => img.url).filter(Boolean);\n if (images.length > 0) {\n parts.push(`Images:\\n${images.join('\\n')}`);\n }\n\n const text = parts.join('\\n\\n');\n return truncateStringForTokenEstimate(text, MAX_WEB_SEARCH_TOKENS);\n },\n });\n}\n\n/**\n * Wraps the @mastra/tavily extract tool with mastracode-specific behavior:\n * markdown string formatting and token truncation.\n */\nexport async function createWebExtractTool() {\n const { createTavilyExtractTool } = await import('@mastra/tavily');\n const tavilyExtractTool = createTavilyExtractTool();\n\n return createTool({\n id: 'web-extract',\n description: tavilyExtractTool.description!,\n inputSchema: tavilyExtractTool.inputSchema!,\n execute: async (input, context) => {\n const output: any = await tavilyExtractTool.execute!(input as any, context as any);\n\n const parts: string[] = [];\n\n for (const r of output.results) {\n parts.push(`## ${r.url}\\n${r.rawContent}`);\n }\n\n for (const r of output.failedResults) {\n parts.push(`## ${r.url}\\nError: ${r.error}`);\n }\n\n const text = parts.join('\\n\\n');\n return truncateStringForTokenEstimate(text, MAX_WEB_EXTRACT_TOKENS);\n },\n });\n}\n\n/**\n * Wraps the @mastra/parallel search tool with Mastra Code's standard tool id,\n * markdown string output, and token truncation.\n */\nexport async function createParallelWebSearchTool() {\n const { createParallelSearchTool } = await import('@mastra/parallel');\n const parallelSearchTool = createParallelSearchTool();\n\n return createTool({\n id: 'web-search',\n description: parallelSearchTool.description!,\n inputSchema: parallelWebSearchInputSchema,\n execute: async (input, context) => {\n const output = requireParallelOutput(\n await parallelSearchTool.execute!({ searchQueries: [input.query] }, context),\n 'search',\n );\n const parts: string[] = [];\n\n for (const result of output.results) {\n const title = result.title || result.url;\n const excerpts = (result.excerpts || []).filter(Boolean).join('\\n');\n parts.push([`## ${title}`, result.url, excerpts].filter(Boolean).join('\\n'));\n }\n\n return truncateStringForTokenEstimate(parts.join('\\n\\n'), MAX_WEB_SEARCH_TOKENS);\n },\n });\n}\n\n/**\n * Wraps the @mastra/parallel extract tool with Mastra Code's standard tool id,\n * markdown string output, and token truncation.\n */\nexport async function createParallelWebExtractTool() {\n const { createParallelExtractTool } = await import('@mastra/parallel');\n const parallelExtractTool = createParallelExtractTool();\n\n return createTool({\n id: 'web-extract',\n description: parallelExtractTool.description!,\n inputSchema: parallelExtractTool.inputSchema!,\n execute: async (input, context) => {\n const output = requireParallelOutput(await parallelExtractTool.execute!(input, context), 'extract');\n const parts: string[] = [];\n\n for (const result of output.results) {\n const content = result.fullContent || (result.excerpts || []).filter(Boolean).join('\\n');\n parts.push([`## ${result.url}`, content].filter(Boolean).join('\\n'));\n }\n\n for (const error of output.errors) {\n const status = error.httpStatusCode ? ` (${error.httpStatusCode})` : '';\n parts.push([`## ${error.url}`, `Error: ${error.errorType}${status}`, error.content].filter(Boolean).join('\\n'));\n }\n\n return truncateStringForTokenEstimate(parts.join('\\n\\n'), MAX_WEB_EXTRACT_TOKENS);\n },\n });\n}\n\n/**\n * Resolve which model-independent web provider to use. An explicit user\n * preference wins while its API key is configured; otherwise `auto` picks the\n * first configured provider key (Tavily, then Parallel).\n */\nexport function resolveWebSearchProvider(\n preference: WebSearchProviderSetting = 'auto',\n): 'tavily' | 'parallel' | undefined {\n if (preference === 'tavily' && hasTavilyKey()) return 'tavily';\n if (preference === 'parallel' && hasParallelKey()) return 'parallel';\n\n // `auto`, or an explicit choice whose key is no longer configured.\n if (hasTavilyKey()) return 'tavily';\n if (hasParallelKey()) return 'parallel';\n return undefined;\n}\n\n/**\n * Create the configured model-independent web tools for the provider selected\n * via the `webSearchProvider` preference (set in the TUI settings panel).\n * Returns `undefined` synchronously when no provider key is configured, so the\n * provider SDK (Tavily pulls in tiktoken, ~15 MB) only loads when one is.\n */\nexport function createConfiguredWebTools() {\n const provider = resolveWebSearchProvider(loadSettings().preferences.webSearchProvider);\n\n if (provider === 'parallel') {\n return loadWebTools(createParallelWebSearchTool, createParallelWebExtractTool);\n }\n\n if (provider === 'tavily') {\n return loadWebTools(createWebSearchTool, createWebExtractTool);\n }\n\n return undefined;\n}\n\nasync function loadWebTools<S, E>(createSearch: () => Promise<S>, createExtract: () => Promise<E>) {\n return { web_search: await createSearch(), web_extract: await createExtract() };\n}\n"],"mappings":"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"}
@@ -2,8 +2,8 @@ import { getAppDataDir } from "./project.js";
2
2
  import * as path$1 from "path";
3
3
  import * as fs$1 from "fs";
4
4
  //#region src/utils/debug-log.ts
5
- const MAX_LOG_SIZE = 5 * 1024 * 1024;
6
- const KEEP_SIZE = 4 * 1024 * 1024;
5
+ const MAX_LOG_SIZE = 5242880;
6
+ const KEEP_SIZE = 4194304;
7
7
  /**
8
8
  * Truncate a log file to roughly {@link KEEP_SIZE} bytes if it exceeds
9
9
  * {@link MAX_LOG_SIZE}, cutting at the first newline so we don't start mid-line.
@@ -1 +1 @@
1
- {"version":3,"file":"debug-log.js","names":["fs","path"],"sources":["../../src/utils/debug-log.ts"],"sourcesContent":["import * as fs from 'node:fs';\nimport * as path from 'node:path';\n\nimport { getAppDataDir } from './project.js';\n\nconst MAX_LOG_SIZE = 5 * 1024 * 1024; // 5 MB\nconst KEEP_SIZE = 4 * 1024 * 1024; // 4 MB\n\n/**\n * Truncate a log file to roughly {@link KEEP_SIZE} bytes if it exceeds\n * {@link MAX_LOG_SIZE}, cutting at the first newline so we don't start mid-line.\n */\nexport function truncateLogFile(logFile: string): void {\n try {\n const stat = fs.statSync(logFile);\n if (stat.size > MAX_LOG_SIZE) {\n const buf = Buffer.alloc(KEEP_SIZE);\n const fd = fs.openSync(logFile, 'r');\n fs.readSync(fd, buf, 0, KEEP_SIZE, stat.size - KEEP_SIZE);\n fs.closeSync(fd);\n const firstNewline = buf.indexOf(10);\n const trimmed = firstNewline >= 0 ? buf.subarray(firstNewline + 1) : buf;\n fs.writeFileSync(logFile, trimmed);\n }\n } catch {\n // File may not exist yet — that's fine\n }\n}\n\n/**\n * Set up debug logging. When {@link MASTRA_DEBUG} is `\"true\"`, redirects\n * `console.error` and `console.warn` to a log file (truncating it first if\n * oversized). Otherwise silences them to avoid corrupting the TUI.\n */\nexport function setupDebugLogging(): void {\n const debugEnabled = ['true', '1'].includes(process.env.MASTRA_DEBUG ?? '');\n\n if (debugEnabled) {\n const logFile = path.join(getAppDataDir(), 'debug.log');\n truncateLogFile(logFile);\n\n const logStream = fs.createWriteStream(logFile, { flags: 'a' });\n const fmt = (a: unknown): string => {\n if (typeof a === 'string') return a;\n if (a instanceof Error) return a.stack ?? `${a.name}: ${a.message}`;\n try {\n return JSON.stringify(a);\n } catch {\n return String(a);\n }\n };\n console.error = (...args: unknown[]) => {\n logStream.write(`[ERROR] ${new Date().toISOString()} ${args.map(fmt).join(' ')}\\n`);\n };\n console.warn = (...args: unknown[]) => {\n logStream.write(`[WARN] ${new Date().toISOString()} ${args.map(fmt).join(' ')}\\n`);\n };\n } else {\n const noop = () => {};\n console.error = noop;\n console.warn = noop;\n }\n}\n"],"mappings":";;;;AAKA,MAAM,eAAe,IAAI,OAAO;AAChC,MAAM,YAAY,IAAI,OAAO;;;;;AAM7B,SAAgB,gBAAgB,SAAuB;CACrD,IAAI;EACF,MAAM,OAAOA,KAAG,SAAS,OAAO;EAChC,IAAI,KAAK,OAAO,cAAc;GAC5B,MAAM,MAAM,OAAO,MAAM,SAAS;GAClC,MAAM,KAAKA,KAAG,SAAS,SAAS,GAAG;GACnC,KAAG,SAAS,IAAI,KAAK,GAAG,WAAW,KAAK,OAAO,SAAS;GACxD,KAAG,UAAU,EAAE;GACf,MAAM,eAAe,IAAI,QAAQ,EAAE;GACnC,MAAM,UAAU,gBAAgB,IAAI,IAAI,SAAS,eAAe,CAAC,IAAI;GACrE,KAAG,cAAc,SAAS,OAAO;EACnC;CACF,QAAQ,CAER;AACF;;;;;;AAOA,SAAgB,oBAA0B;CAGxC,IAFqB,CAAC,QAAQ,GAAG,CAAC,CAAC,SAAS,QAAQ,IAAI,gBAAgB,EAEzD,GAAG;EAChB,MAAM,UAAUC,OAAK,KAAK,cAAc,GAAG,WAAW;EACtD,gBAAgB,OAAO;EAEvB,MAAM,YAAYD,KAAG,kBAAkB,SAAS,EAAE,OAAO,IAAI,CAAC;EAC9D,MAAM,OAAO,MAAuB;GAClC,IAAI,OAAO,MAAM,UAAU,OAAO;GAClC,IAAI,aAAa,OAAO,OAAO,EAAE,SAAS,GAAG,EAAE,KAAK,IAAI,EAAE;GAC1D,IAAI;IACF,OAAO,KAAK,UAAU,CAAC;GACzB,QAAQ;IACN,OAAO,OAAO,CAAC;GACjB;EACF;EACA,QAAQ,SAAS,GAAG,SAAoB;GACtC,UAAU,MAAM,4BAAW,IAAI,KAAK,EAAA,CAAE,YAAY,EAAE,GAAG,KAAK,IAAI,GAAG,CAAC,CAAC,KAAK,GAAG,EAAE,GAAG;EACpF;EACA,QAAQ,QAAQ,GAAG,SAAoB;GACrC,UAAU,MAAM,2BAAU,IAAI,KAAK,EAAA,CAAE,YAAY,EAAE,GAAG,KAAK,IAAI,GAAG,CAAC,CAAC,KAAK,GAAG,EAAE,GAAG;EACnF;CACF,OAAO;EACL,MAAM,aAAa,CAAC;EACpB,QAAQ,QAAQ;EAChB,QAAQ,OAAO;CACjB;AACF"}
1
+ {"version":3,"file":"debug-log.js","names":["fs","path"],"sources":["../../src/utils/debug-log.ts"],"sourcesContent":["import * as fs from 'node:fs';\nimport * as path from 'node:path';\n\nimport { getAppDataDir } from './project.js';\n\nconst MAX_LOG_SIZE = 5 * 1024 * 1024; // 5 MB\nconst KEEP_SIZE = 4 * 1024 * 1024; // 4 MB\n\n/**\n * Truncate a log file to roughly {@link KEEP_SIZE} bytes if it exceeds\n * {@link MAX_LOG_SIZE}, cutting at the first newline so we don't start mid-line.\n */\nexport function truncateLogFile(logFile: string): void {\n try {\n const stat = fs.statSync(logFile);\n if (stat.size > MAX_LOG_SIZE) {\n const buf = Buffer.alloc(KEEP_SIZE);\n const fd = fs.openSync(logFile, 'r');\n fs.readSync(fd, buf, 0, KEEP_SIZE, stat.size - KEEP_SIZE);\n fs.closeSync(fd);\n const firstNewline = buf.indexOf(10);\n const trimmed = firstNewline >= 0 ? buf.subarray(firstNewline + 1) : buf;\n fs.writeFileSync(logFile, trimmed);\n }\n } catch {\n // File may not exist yet — that's fine\n }\n}\n\n/**\n * Set up debug logging. When {@link MASTRA_DEBUG} is `\"true\"`, redirects\n * `console.error` and `console.warn` to a log file (truncating it first if\n * oversized). Otherwise silences them to avoid corrupting the TUI.\n */\nexport function setupDebugLogging(): void {\n const debugEnabled = ['true', '1'].includes(process.env.MASTRA_DEBUG ?? '');\n\n if (debugEnabled) {\n const logFile = path.join(getAppDataDir(), 'debug.log');\n truncateLogFile(logFile);\n\n const logStream = fs.createWriteStream(logFile, { flags: 'a' });\n const fmt = (a: unknown): string => {\n if (typeof a === 'string') return a;\n if (a instanceof Error) return a.stack ?? `${a.name}: ${a.message}`;\n try {\n return JSON.stringify(a);\n } catch {\n return String(a);\n }\n };\n console.error = (...args: unknown[]) => {\n logStream.write(`[ERROR] ${new Date().toISOString()} ${args.map(fmt).join(' ')}\\n`);\n };\n console.warn = (...args: unknown[]) => {\n logStream.write(`[WARN] ${new Date().toISOString()} ${args.map(fmt).join(' ')}\\n`);\n };\n } else {\n const noop = () => {};\n console.error = noop;\n console.warn = noop;\n }\n}\n"],"mappings":";;;;AAKA,MAAM,eAAe;AACrB,MAAM,YAAY;;;;;AAMlB,SAAgB,gBAAgB,SAAuB;CACrD,IAAI;EACF,MAAM,OAAOA,KAAG,SAAS,OAAO;EAChC,IAAI,KAAK,OAAO,cAAc;GAC5B,MAAM,MAAM,OAAO,MAAM,SAAS;GAClC,MAAM,KAAKA,KAAG,SAAS,SAAS,GAAG;GACnC,KAAG,SAAS,IAAI,KAAK,GAAG,WAAW,KAAK,OAAO,SAAS;GACxD,KAAG,UAAU,EAAE;GACf,MAAM,eAAe,IAAI,QAAQ,EAAE;GACnC,MAAM,UAAU,gBAAgB,IAAI,IAAI,SAAS,eAAe,CAAC,IAAI;GACrE,KAAG,cAAc,SAAS,OAAO;EACnC;CACF,QAAQ,CAER;AACF;;;;;;AAOA,SAAgB,oBAA0B;CAGxC,IAFqB,CAAC,QAAQ,GAAG,CAAC,CAAC,SAAS,QAAQ,IAAI,gBAAgB,EAEzD,GAAG;EAChB,MAAM,UAAUC,OAAK,KAAK,cAAc,GAAG,WAAW;EACtD,gBAAgB,OAAO;EAEvB,MAAM,YAAYD,KAAG,kBAAkB,SAAS,EAAE,OAAO,IAAI,CAAC;EAC9D,MAAM,OAAO,MAAuB;GAClC,IAAI,OAAO,MAAM,UAAU,OAAO;GAClC,IAAI,aAAa,OAAO,OAAO,EAAE,SAAS,GAAG,EAAE,KAAK,IAAI,EAAE;GAC1D,IAAI;IACF,OAAO,KAAK,UAAU,CAAC;GACzB,QAAQ;IACN,OAAO,OAAO,CAAC;GACjB;EACF;EACA,QAAQ,SAAS,GAAG,SAAoB;GACtC,UAAU,MAAM,4BAAW,IAAI,KAAK,EAAA,CAAE,YAAY,EAAE,GAAG,KAAK,IAAI,GAAG,CAAC,CAAC,KAAK,GAAG,EAAE,GAAG;EACpF;EACA,QAAQ,QAAQ,GAAG,SAAoB;GACrC,UAAU,MAAM,2BAAU,IAAI,KAAK,EAAA,CAAE,YAAY,EAAE,GAAG,KAAK,IAAI,GAAG,CAAC,CAAC,KAAK,GAAG,EAAE,GAAG;EACnF;CACF,OAAO;EACL,MAAM,aAAa,CAAC;EACpB,QAAQ,QAAQ;EAChB,QAAQ,OAAO;CACjB;AACF"}
@@ -8,7 +8,7 @@ import { GatewayRegistry } from "@mastra/core/llm";
8
8
  * providers, generating types, and writing to the global cache. Keep this
9
9
  * file small — provider/registry logic belongs in `@mastra/core`.
10
10
  */
11
- const DEFAULT_SYNC_INTERVAL_MS = 300 * 1e3;
11
+ const DEFAULT_SYNC_INTERVAL_MS = 3e5;
12
12
  let syncInterval = null;
13
13
  let isSyncing = false;
14
14
  function getRegistry() {
@@ -1 +1 @@
1
- {"version":3,"file":"gateway-sync.js","names":[],"sources":["../../src/utils/gateway-sync.ts"],"sourcesContent":["/**\n * Gateway sync utility for keeping the model registry up to date.\n *\n * This is a thin wrapper around `@mastra/core/llm`'s `GatewayRegistry` so\n * mastracode and `mastra dev` share a single implementation for fetching\n * providers, generating types, and writing to the global cache. Keep this\n * file small — provider/registry logic belongs in `@mastra/core`.\n */\n\nimport { GatewayRegistry } from '@mastra/core/llm';\n\nconst DEFAULT_SYNC_INTERVAL_MS = 5 * 60 * 1000;\n\nlet syncInterval: NodeJS.Timeout | null = null;\nlet isSyncing = false;\n\nfunction getRegistry(): GatewayRegistry {\n return GatewayRegistry.getInstance({ useDynamicLoading: true });\n}\n\n/**\n * Sync gateways and update the global cache via `@mastra/core`'s registry.\n *\n * Skips the sync if the global cache was refreshed within the last\n * `DEFAULT_SYNC_INTERVAL_MS`, unless `force` is set. Multiple calls in\n * the same process coalesce while a sync is in flight.\n */\nexport async function syncGateways(force = false): Promise<void> {\n if (isSyncing && !force) {\n return;\n }\n\n if (!force) {\n const lastSync = getRegistry().getLastRefreshTime();\n if (lastSync && Date.now() - lastSync.getTime() < DEFAULT_SYNC_INTERVAL_MS) {\n return;\n }\n }\n\n isSyncing = true;\n try {\n await getRegistry().syncGateways(true);\n } catch {\n // Silently ignore — the bundled registry already contains all model\n // data so a failed network fetch is non-critical.\n } finally {\n isSyncing = false;\n }\n}\n\n/**\n * Start periodic gateway sync.\n * @param intervalMs Sync interval in milliseconds (default: 5 minutes)\n */\nexport function startGatewaySync(intervalMs = DEFAULT_SYNC_INTERVAL_MS): void {\n if (syncInterval) {\n return;\n }\n\n // Do an initial sync (will skip if recently synced)\n syncGateways().catch(() => {});\n\n // Set up periodic sync\n syncInterval = setInterval(() => {\n syncGateways().catch(() => {});\n }, intervalMs);\n\n // Don't prevent process exit\n syncInterval.unref();\n}\n\n/**\n * Stop periodic gateway sync.\n */\nexport function stopGatewaySync(): void {\n if (syncInterval) {\n clearInterval(syncInterval);\n syncInterval = null;\n }\n}\n"],"mappings":";;;;;;;;;;AAWA,MAAM,2BAA2B,MAAS;AAE1C,IAAI,eAAsC;AAC1C,IAAI,YAAY;AAEhB,SAAS,cAA+B;CACtC,OAAO,gBAAgB,YAAY,EAAE,mBAAmB,KAAK,CAAC;AAChE;;;;;;;;AASA,eAAsB,aAAa,QAAQ,OAAsB;CAC/D,IAAI,aAAa,CAAC,OAChB;CAGF,IAAI,CAAC,OAAO;EACV,MAAM,WAAW,YAAY,CAAC,CAAC,mBAAmB;EAClD,IAAI,YAAY,KAAK,IAAI,IAAI,SAAS,QAAQ,IAAI,0BAChD;CAEJ;CAEA,YAAY;CACZ,IAAI;EACF,MAAM,YAAY,CAAC,CAAC,aAAa,IAAI;CACvC,QAAQ,CAGR,UAAU;EACR,YAAY;CACd;AACF;;;;;AAMA,SAAgB,iBAAiB,aAAa,0BAAgC;CAC5E,IAAI,cACF;CAIF,aAAa,CAAC,CAAC,YAAY,CAAC,CAAC;CAG7B,eAAe,kBAAkB;EAC/B,aAAa,CAAC,CAAC,YAAY,CAAC,CAAC;CAC/B,GAAG,UAAU;CAGb,aAAa,MAAM;AACrB;;;;AAKA,SAAgB,kBAAwB;CACtC,IAAI,cAAc;EAChB,cAAc,YAAY;EAC1B,eAAe;CACjB;AACF"}
1
+ {"version":3,"file":"gateway-sync.js","names":[],"sources":["../../src/utils/gateway-sync.ts"],"sourcesContent":["/**\n * Gateway sync utility for keeping the model registry up to date.\n *\n * This is a thin wrapper around `@mastra/core/llm`'s `GatewayRegistry` so\n * mastracode and `mastra dev` share a single implementation for fetching\n * providers, generating types, and writing to the global cache. Keep this\n * file small — provider/registry logic belongs in `@mastra/core`.\n */\n\nimport { GatewayRegistry } from '@mastra/core/llm';\n\nconst DEFAULT_SYNC_INTERVAL_MS = 5 * 60 * 1000;\n\nlet syncInterval: NodeJS.Timeout | null = null;\nlet isSyncing = false;\n\nfunction getRegistry(): GatewayRegistry {\n return GatewayRegistry.getInstance({ useDynamicLoading: true });\n}\n\n/**\n * Sync gateways and update the global cache via `@mastra/core`'s registry.\n *\n * Skips the sync if the global cache was refreshed within the last\n * `DEFAULT_SYNC_INTERVAL_MS`, unless `force` is set. Multiple calls in\n * the same process coalesce while a sync is in flight.\n */\nexport async function syncGateways(force = false): Promise<void> {\n if (isSyncing && !force) {\n return;\n }\n\n if (!force) {\n const lastSync = getRegistry().getLastRefreshTime();\n if (lastSync && Date.now() - lastSync.getTime() < DEFAULT_SYNC_INTERVAL_MS) {\n return;\n }\n }\n\n isSyncing = true;\n try {\n await getRegistry().syncGateways(true);\n } catch {\n // Silently ignore — the bundled registry already contains all model\n // data so a failed network fetch is non-critical.\n } finally {\n isSyncing = false;\n }\n}\n\n/**\n * Start periodic gateway sync.\n * @param intervalMs Sync interval in milliseconds (default: 5 minutes)\n */\nexport function startGatewaySync(intervalMs = DEFAULT_SYNC_INTERVAL_MS): void {\n if (syncInterval) {\n return;\n }\n\n // Do an initial sync (will skip if recently synced)\n syncGateways().catch(() => {});\n\n // Set up periodic sync\n syncInterval = setInterval(() => {\n syncGateways().catch(() => {});\n }, intervalMs);\n\n // Don't prevent process exit\n syncInterval.unref();\n}\n\n/**\n * Stop periodic gateway sync.\n */\nexport function stopGatewaySync(): void {\n if (syncInterval) {\n clearInterval(syncInterval);\n syncInterval = null;\n }\n}\n"],"mappings":";;;;;;;;;;AAWA,MAAM,2BAA2B;AAEjC,IAAI,eAAsC;AAC1C,IAAI,YAAY;AAEhB,SAAS,cAA+B;CACtC,OAAO,gBAAgB,YAAY,EAAE,mBAAmB,KAAK,CAAC;AAChE;;;;;;;;AASA,eAAsB,aAAa,QAAQ,OAAsB;CAC/D,IAAI,aAAa,CAAC,OAChB;CAGF,IAAI,CAAC,OAAO;EACV,MAAM,WAAW,YAAY,CAAC,CAAC,mBAAmB;EAClD,IAAI,YAAY,KAAK,IAAI,IAAI,SAAS,QAAQ,IAAI,0BAChD;CAEJ;CAEA,YAAY;CACZ,IAAI;EACF,MAAM,YAAY,CAAC,CAAC,aAAa,IAAI;CACvC,QAAQ,CAGR,UAAU;EACR,YAAY;CACd;AACF;;;;;AAMA,SAAgB,iBAAiB,aAAa,0BAAgC;CAC5E,IAAI,cACF;CAIF,aAAa,CAAC,CAAC,YAAY,CAAC,CAAC;CAG7B,eAAe,kBAAkB;EAC/B,aAAa,CAAC,CAAC,YAAY,CAAC,CAAC;CAC/B,GAAG,UAAU;CAGb,aAAa,MAAM;AACrB;;;;AAKA,SAAgB,kBAAwB;CACtC,IAAI,cAAc;EAChB,cAAc,YAAY;EAC1B,eAAe;CACjB;AACF"}
@@ -1,5 +1,5 @@
1
1
  import type { PruneResult, RetentionConfig } from '@mastra/core/storage';
2
- import { DuckDBStore } from '@mastra/duckdb';
2
+ import type { DuckDBStore } from '@mastra/duckdb';
3
3
  /**
4
4
  * Age limits for the local trace store: 14 days for spans, metrics and logs,
5
5
  * 30 days for scores and feedback. Spans, logs and scores use the same windows
@@ -1 +1 @@
1
- {"version":3,"file":"local-observability.d.ts","sourceRoot":"","sources":["../../src/utils/local-observability.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,eAAe,EAAE,MAAM,sBAAsB,CAAC;AACzE,OAAO,EAAE,WAAW,EAAE,MAAM,gBAAgB,CAAC;AAE7C;;;;GAIG;AACH,eAAO,MAAM,6BAA6B,EAAE,eAQ3C,CAAC;AAqBF;;;;;GAKG;AACH,wBAAsB,2BAA2B,CAC/C,IAAI,EAAE,MAAM,GACX,OAAO,CAAC;IAAE,KAAK,EAAE,WAAW,CAAC;IAAC,MAAM,EAAE,OAAO,CAAC,WAAW,EAAE,CAAC,CAAA;CAAE,CAAC,CAMjE"}
1
+ {"version":3,"file":"local-observability.d.ts","sourceRoot":"","sources":["../../src/utils/local-observability.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,eAAe,EAAE,MAAM,sBAAsB,CAAC;AACzE,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,gBAAgB,CAAC;AAElD;;;;GAIG;AACH,eAAO,MAAM,6BAA6B,EAAE,eAQ3C,CAAC;AAqBF;;;;;GAKG;AACH,wBAAsB,2BAA2B,CAC/C,IAAI,EAAE,MAAM,GACX,OAAO,CAAC;IAAE,KAAK,EAAE,WAAW,CAAC;IAAC,MAAM,EAAE,OAAO,CAAC,WAAW,EAAE,CAAC,CAAA;CAAE,CAAC,CAQjE"}
@@ -1,4 +1,3 @@
1
- import { DuckDBStore } from "@mastra/duckdb";
2
1
  //#region src/utils/local-observability.ts
3
2
  /**
4
3
  * Age limits for the local trace store: 14 days for spans, metrics and logs,
@@ -40,6 +39,7 @@ async function pruneExistingTables(store) {
40
39
  * `pruned` never rejects; anything left over is pruned on the next open.
41
40
  */
42
41
  async function openLocalObservabilityStore(path) {
42
+ const { DuckDBStore } = await import("@mastra/duckdb");
43
43
  const store = new DuckDBStore({
44
44
  id: "mastra-code-observability",
45
45
  path,
@@ -1 +1 @@
1
- {"version":3,"file":"local-observability.js","names":[],"sources":["../../src/utils/local-observability.ts"],"sourcesContent":["import type { PruneResult, RetentionConfig } from '@mastra/core/storage';\nimport { DuckDBStore } from '@mastra/duckdb';\n\n/**\n * Age limits for the local trace store: 14 days for spans, metrics and logs,\n * 30 days for scores and feedback. Spans, logs and scores use the same windows\n * as libsql's `DEFAULT_RETENTION`, which has no metrics or feedback entries.\n */\nexport const LOCAL_OBSERVABILITY_RETENTION: RetentionConfig = {\n observability: {\n spans: { maxAge: '14d' },\n metrics: { maxAge: '14d' },\n logs: { maxAge: '14d' },\n scores: { maxAge: '30d' },\n feedback: { maxAge: '30d' },\n },\n};\n\n/** Upper bound on rows deleted per table per session, so startup pruning never runs long. */\nconst MAX_ROWS_PER_OPEN = 50_000;\n\nconst OBSERVABILITY_TABLES = ['span_events', 'metric_events', 'log_events', 'score_events', 'feedback_events'];\n\n/**\n * Prune only when every observability table already exists. A brand-new file\n * has no tables until the session composite runs init(), and pruning it would\n * log a missing-table error from the storage layer for a file with nothing to delete.\n */\nasync function pruneExistingTables(store: DuckDBStore): Promise<PruneResult[]> {\n const existing = await store.db.query<{ table_name: string }>(\n `SELECT table_name FROM information_schema.tables WHERE table_name IN (${OBSERVABILITY_TABLES.map(() => '?').join(', ')})`,\n OBSERVABILITY_TABLES,\n );\n if (existing.length < OBSERVABILITY_TABLES.length) return [];\n return store.prune({ maxRows: MAX_ROWS_PER_OPEN, pauseMs: 10 });\n}\n\n/**\n * Open the local observability DuckDB file used when `/observability local on`.\n * Connects eagerly so a lock error surfaces now, not mid-session, then prunes\n * aged events in the background so the file cannot grow without bound.\n * `pruned` never rejects; anything left over is pruned on the next open.\n */\nexport async function openLocalObservabilityStore(\n path: string,\n): Promise<{ store: DuckDBStore; pruned: Promise<PruneResult[]> }> {\n const store = new DuckDBStore({ id: 'mastra-code-observability', path, retention: LOCAL_OBSERVABILITY_RETENTION });\n await store.db.getConnection();\n // No init() here: the session composite initializes the domain.\n const pruned = pruneExistingTables(store).catch(() => []);\n return { store, pruned };\n}\n"],"mappings":";;;;;;;AAQA,MAAa,gCAAiD,EAC5D,eAAe;CACb,OAAO,EAAE,QAAQ,MAAM;CACvB,SAAS,EAAE,QAAQ,MAAM;CACzB,MAAM,EAAE,QAAQ,MAAM;CACtB,QAAQ,EAAE,QAAQ,MAAM;CACxB,UAAU,EAAE,QAAQ,MAAM;AAC5B,EACF;;AAGA,MAAM,oBAAoB;AAE1B,MAAM,uBAAuB;CAAC;CAAe;CAAiB;CAAc;CAAgB;AAAiB;;;;;;AAO7G,eAAe,oBAAoB,OAA4C;CAK7E,KAAI,MAJmB,MAAM,GAAG,MAC9B,yEAAyE,qBAAqB,UAAU,GAAG,CAAC,CAAC,KAAK,IAAI,EAAE,IACxH,oBACF,EAAA,CACa,SAAS,qBAAqB,QAAQ,OAAO,CAAC;CAC3D,OAAO,MAAM,MAAM;EAAE,SAAS;EAAmB,SAAS;CAAG,CAAC;AAChE;;;;;;;AAQA,eAAsB,4BACpB,MACiE;CACjE,MAAM,QAAQ,IAAI,YAAY;EAAE,IAAI;EAA6B;EAAM,WAAW;CAA8B,CAAC;CACjH,MAAM,MAAM,GAAG,cAAc;CAG7B,OAAO;EAAE;EAAO,QADD,oBAAoB,KAAK,CAAC,CAAC,YAAY,CAAC,CAClC;CAAE;AACzB"}
1
+ {"version":3,"file":"local-observability.js","names":[],"sources":["../../src/utils/local-observability.ts"],"sourcesContent":["import type { PruneResult, RetentionConfig } from '@mastra/core/storage';\nimport type { DuckDBStore } from '@mastra/duckdb';\n\n/**\n * Age limits for the local trace store: 14 days for spans, metrics and logs,\n * 30 days for scores and feedback. Spans, logs and scores use the same windows\n * as libsql's `DEFAULT_RETENTION`, which has no metrics or feedback entries.\n */\nexport const LOCAL_OBSERVABILITY_RETENTION: RetentionConfig = {\n observability: {\n spans: { maxAge: '14d' },\n metrics: { maxAge: '14d' },\n logs: { maxAge: '14d' },\n scores: { maxAge: '30d' },\n feedback: { maxAge: '30d' },\n },\n};\n\n/** Upper bound on rows deleted per table per session, so startup pruning never runs long. */\nconst MAX_ROWS_PER_OPEN = 50_000;\n\nconst OBSERVABILITY_TABLES = ['span_events', 'metric_events', 'log_events', 'score_events', 'feedback_events'];\n\n/**\n * Prune only when every observability table already exists. A brand-new file\n * has no tables until the session composite runs init(), and pruning it would\n * log a missing-table error from the storage layer for a file with nothing to delete.\n */\nasync function pruneExistingTables(store: DuckDBStore): Promise<PruneResult[]> {\n const existing = await store.db.query<{ table_name: string }>(\n `SELECT table_name FROM information_schema.tables WHERE table_name IN (${OBSERVABILITY_TABLES.map(() => '?').join(', ')})`,\n OBSERVABILITY_TABLES,\n );\n if (existing.length < OBSERVABILITY_TABLES.length) return [];\n return store.prune({ maxRows: MAX_ROWS_PER_OPEN, pauseMs: 10 });\n}\n\n/**\n * Open the local observability DuckDB file used when `/observability local on`.\n * Connects eagerly so a lock error surfaces now, not mid-session, then prunes\n * aged events in the background so the file cannot grow without bound.\n * `pruned` never rejects; anything left over is pruned on the next open.\n */\nexport async function openLocalObservabilityStore(\n path: string,\n): Promise<{ store: DuckDBStore; pruned: Promise<PruneResult[]> }> {\n // Loaded on demand: DuckDB's native binding adds ~25 MB to every session that never turns this on.\n const { DuckDBStore } = await import('@mastra/duckdb');\n const store = new DuckDBStore({ id: 'mastra-code-observability', path, retention: LOCAL_OBSERVABILITY_RETENTION });\n await store.db.getConnection();\n // No init() here: the session composite initializes the domain.\n const pruned = pruneExistingTables(store).catch(() => []);\n return { store, pruned };\n}\n"],"mappings":";;;;;;AAQA,MAAa,gCAAiD,EAC5D,eAAe;CACb,OAAO,EAAE,QAAQ,MAAM;CACvB,SAAS,EAAE,QAAQ,MAAM;CACzB,MAAM,EAAE,QAAQ,MAAM;CACtB,QAAQ,EAAE,QAAQ,MAAM;CACxB,UAAU,EAAE,QAAQ,MAAM;AAC5B,EACF;;AAGA,MAAM,oBAAoB;AAE1B,MAAM,uBAAuB;CAAC;CAAe;CAAiB;CAAc;CAAgB;AAAiB;;;;;;AAO7G,eAAe,oBAAoB,OAA4C;CAK7E,KAAI,MAJmB,MAAM,GAAG,MAC9B,yEAAyE,qBAAqB,UAAU,GAAG,CAAC,CAAC,KAAK,IAAI,EAAE,IACxH,oBACF,EAAA,CACa,SAAS,qBAAqB,QAAQ,OAAO,CAAC;CAC3D,OAAO,MAAM,MAAM;EAAE,SAAS;EAAmB,SAAS;CAAG,CAAC;AAChE;;;;;;;AAQA,eAAsB,4BACpB,MACiE;CAEjE,MAAM,EAAE,gBAAgB,MAAM,OAAO;CACrC,MAAM,QAAQ,IAAI,YAAY;EAAE,IAAI;EAA6B;EAAM,WAAW;CAA8B,CAAC;CACjH,MAAM,MAAM,GAAG,cAAc;CAG7B,OAAO;EAAE;EAAO,QADD,oBAAoB,KAAK,CAAC,CAAC,YAAY,CAAC,CAClC;CAAE;AACzB"}
@@ -68,7 +68,8 @@ async function runPruneCommand(args) {
68
68
  }
69
69
  async function prune({ vacuum, keepMemory }) {
70
70
  const settings = loadSettings();
71
- const storageConfig = getStorageConfig(detectProject(process.cwd()).rootPath, settings.storage, DEFAULT_CONFIG_DIR);
71
+ const project = detectProject(process.cwd());
72
+ const storageConfig = getStorageConfig(project.rootPath, settings.storage, DEFAULT_CONFIG_DIR);
72
73
  console.log(`Storage target: ${describeStorageTarget(storageConfig)}`);
73
74
  const { storage, backend, warning } = await createStorage(storageConfig);
74
75
  if (warning) console.log(warning);
@@ -79,14 +80,15 @@ async function prune({ vacuum, keepMemory }) {
79
80
  if (localDbFiles.length > 0) console.log(`Database: ${localDbFiles.join(", ")}`);
80
81
  if (vacuum && localDbFiles.length > 0) vector = await createVectorStore(storageConfig, backend);
81
82
  const libsqlVector = vector instanceof LibSQLVector ? vector : void 0;
83
+ const maintenance = createStorageMaintenance({
84
+ storage,
85
+ backend,
86
+ retention: DEFAULT_RETENTION,
87
+ localDbFiles,
88
+ ...libsqlVector ? { closeVector: () => libsqlVector.close() } : {}
89
+ });
82
90
  await runStorageMaintenance({
83
- maintenance: createStorageMaintenance({
84
- storage,
85
- backend,
86
- retention: DEFAULT_RETENTION,
87
- localDbFiles,
88
- ...libsqlVector ? { closeVector: () => libsqlVector.close() } : {}
89
- }),
91
+ maintenance,
90
92
  vacuum,
91
93
  keepMemory,
92
94
  log: (line) => console.log(line)
@@ -1 +1 @@
1
- {"version":3,"file":"prune-cli.js","names":[],"sources":["../../src/utils/prune-cli.ts"],"sourcesContent":["/**\n * `mastracode prune` — storage maintenance from a non-interactive shell.\n *\n * The in-TUI `/prune` command has to stop the UI, quiesce background writers\n * (MCP, workers, intervals) and then exit the process, because retention\n * deletes and VACUUM need the database to themselves. A standalone process\n * already satisfies that contract — and, unlike `/prune`, it does not require a\n * rendered prompt first, so maintenance stays reachable when the TUI cannot\n * start. Without it a large mastra.db has no in-app remedy (issue #22056).\n *\n * Usage:\n * mastracode prune delete rows older than the retention policies\n * mastracode prune --vacuum prune, then checkpoint WAL + VACUUM to return disk to the OS\n * (local libsql only; remote libsql and Postgres only prune rows)\n * mastracode prune --keep-memory prune, but keep chat history (messages/threads)\n */\n\nimport path from 'node:path';\n\nimport type { MastraVector } from '@mastra/core/vector';\nimport { LibSQLVector } from '@mastra/libsql';\n\nimport { DEFAULT_CONFIG_DIR } from '../constants.js';\nimport { loadSettings } from '../onboarding/settings.js';\n\nimport { acquireMaintenanceLock } from './maintenance-lock.js';\nimport { detectProject, getStorageConfig } from './project.js';\nimport type { StorageConfig } from './project.js';\nimport { createStorage, createVectorStore } from './storage-factory.js';\nimport {\n DEFAULT_RETENTION,\n createStorageMaintenance,\n resolveLocalDbFiles,\n runStorageMaintenance,\n} from './storage-maintenance.js';\n\nconst USAGE = `Usage: mastracode prune [--vacuum] [--keep-memory]\n\n --vacuum After pruning, compact the local database files so freed\n pages are returned to the OS. Needs free disk for the copy.\n Local libsql only; remote libsql and Postgres only prune rows.\n --keep-memory Prune everything except chat history (messages/threads).\n`;\n\n/**\n * Run storage maintenance for the project in the current directory.\n * Returns the process exit code (0 on success).\n */\nexport async function runPruneCommand(args: string[]): Promise<number> {\n if (args.includes('--help') || args.includes('-h')) {\n console.log(USAGE);\n return 0;\n }\n\n const flags = new Set(args.map(a => a.toLowerCase()));\n const unknown = [...flags].filter(f => f !== '--vacuum' && f !== '--keep-memory');\n if (unknown.length > 0) {\n console.error(`Unknown prune option: ${unknown.join(', ')}\\n${USAGE}`);\n return 1;\n }\n const vacuum = flags.has('--vacuum');\n const keepMemory = flags.has('--keep-memory');\n\n // Startup loads the project .env before registering its session and resolving\n // storage (MASTRA_APP_DATA_DIR, MASTRA_DB_PATH, ...). Do the same before taking\n // the lock, or prune locks/scans a different app data dir or opens another DB.\n try {\n process.loadEnvFile(path.join(process.cwd(), '.env'));\n } catch {\n // No .env file — keys may be in the shell environment\n }\n\n // Maintenance needs the database to itself: hold the lock for the whole run\n // and refuse while any mastracode session is registered.\n let releaseLock: () => void;\n try {\n releaseLock = acquireMaintenanceLock();\n } catch (err) {\n console.error(err instanceof Error ? err.message : String(err));\n return 1;\n }\n\n try {\n return await prune({ vacuum, keepMemory });\n } finally {\n releaseLock();\n }\n}\n\nasync function prune({ vacuum, keepMemory }: { vacuum: boolean; keepMemory: boolean }): Promise<number> {\n // Resolve storage exactly the way startup does, so prune targets the same\n // database the TUI uses — honoring MASTRA_DB_PATH / MASTRA_STORAGE_BACKEND,\n // global settings, and a project's own .mastracode/database.json.\n const settings = loadSettings();\n const project = detectProject(process.cwd());\n const storageConfig = getStorageConfig(project.rootPath, settings.storage, DEFAULT_CONFIG_DIR);\n\n // A project .env can point storage elsewhere (startup honors it too), so say\n // exactly what is about to be pruned before anything is deleted.\n console.log(`Storage target: ${describeStorageTarget(storageConfig)}`);\n\n const { storage, backend, warning } = await createStorage(storageConfig);\n if (warning) console.log(warning);\n\n let vector: MastraVector | undefined;\n try {\n // The libsql factory does not init (the PG path already has); init is\n // coalesced, so tables are guaranteed to exist before we delete from them.\n await storage.init();\n\n const localDbFiles = resolveLocalDbFiles(storageConfig, backend);\n if (localDbFiles.length > 0) {\n console.log(`Database: ${localDbFiles.join(', ')}`);\n }\n\n // Only the local-libsql compaction needs the vector connection (its file\n // swap refuses while any connection is open). Opening it otherwise would\n // create mastra-vectors.db as a side effect.\n if (vacuum && localDbFiles.length > 0) {\n vector = await createVectorStore(storageConfig, backend);\n }\n const libsqlVector = vector instanceof LibSQLVector ? vector : undefined;\n\n const maintenance = createStorageMaintenance({\n storage,\n backend,\n retention: DEFAULT_RETENTION,\n localDbFiles,\n ...(libsqlVector ? { closeVector: () => libsqlVector.close() } : {}),\n });\n\n await runStorageMaintenance({ maintenance, vacuum, keepMemory, log: line => console.log(line) });\n console.log('Storage maintenance complete.');\n return 0;\n } catch (err) {\n console.error(`Storage maintenance failed: ${err instanceof Error ? err.message : String(err)}`);\n // runStorageMaintenance closes storage on its success path. If it threw\n // first, release the connections here so a retry isn't blocked by our own\n // open handles. Best effort — the original failure is what matters.\n try {\n await storage.close?.();\n if (vector instanceof LibSQLVector) await vector.close();\n } catch {\n // ignore cleanup errors\n }\n return 1;\n }\n}\n\n/** Credential-free URL: drops userinfo and the query string (e.g. ?authToken=). */\nfunction redactUrl(raw: string): string {\n try {\n const url = new URL(raw);\n url.username = '';\n url.password = '';\n url.search = '';\n return url.toString();\n } catch {\n return '<unparseable URL>';\n }\n}\n\nexport function describeStorageTarget(config: StorageConfig): string {\n if (config.backend === 'libsql') {\n return config.url.startsWith('file:')\n ? `libsql file ${config.url.slice('file:'.length)}`\n : `libsql ${redactUrl(config.url)}`;\n }\n if (config.connectionString) return `pg ${redactUrl(config.connectionString)}`;\n const host = `${config.host ?? 'localhost'}${config.port ? `:${config.port}` : ''}`;\n return `pg postgres://${host}/${config.database ?? ''}`;\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"prune-cli.js","names":[],"sources":["../../src/utils/prune-cli.ts"],"sourcesContent":["/**\n * `mastracode prune` — storage maintenance from a non-interactive shell.\n *\n * The in-TUI `/prune` command has to stop the UI, quiesce background writers\n * (MCP, workers, intervals) and then exit the process, because retention\n * deletes and VACUUM need the database to themselves. A standalone process\n * already satisfies that contract — and, unlike `/prune`, it does not require a\n * rendered prompt first, so maintenance stays reachable when the TUI cannot\n * start. Without it a large mastra.db has no in-app remedy (issue #22056).\n *\n * Usage:\n * mastracode prune delete rows older than the retention policies\n * mastracode prune --vacuum prune, then checkpoint WAL + VACUUM to return disk to the OS\n * (local libsql only; remote libsql and Postgres only prune rows)\n * mastracode prune --keep-memory prune, but keep chat history (messages/threads)\n */\n\nimport path from 'node:path';\n\nimport type { MastraVector } from '@mastra/core/vector';\nimport { LibSQLVector } from '@mastra/libsql';\n\nimport { DEFAULT_CONFIG_DIR } from '../constants.js';\nimport { loadSettings } from '../onboarding/settings.js';\n\nimport { acquireMaintenanceLock } from './maintenance-lock.js';\nimport { detectProject, getStorageConfig } from './project.js';\nimport type { StorageConfig } from './project.js';\nimport { createStorage, createVectorStore } from './storage-factory.js';\nimport {\n DEFAULT_RETENTION,\n createStorageMaintenance,\n resolveLocalDbFiles,\n runStorageMaintenance,\n} from './storage-maintenance.js';\n\nconst USAGE = `Usage: mastracode prune [--vacuum] [--keep-memory]\n\n --vacuum After pruning, compact the local database files so freed\n pages are returned to the OS. Needs free disk for the copy.\n Local libsql only; remote libsql and Postgres only prune rows.\n --keep-memory Prune everything except chat history (messages/threads).\n`;\n\n/**\n * Run storage maintenance for the project in the current directory.\n * Returns the process exit code (0 on success).\n */\nexport async function runPruneCommand(args: string[]): Promise<number> {\n if (args.includes('--help') || args.includes('-h')) {\n console.log(USAGE);\n return 0;\n }\n\n const flags = new Set(args.map(a => a.toLowerCase()));\n const unknown = [...flags].filter(f => f !== '--vacuum' && f !== '--keep-memory');\n if (unknown.length > 0) {\n console.error(`Unknown prune option: ${unknown.join(', ')}\\n${USAGE}`);\n return 1;\n }\n const vacuum = flags.has('--vacuum');\n const keepMemory = flags.has('--keep-memory');\n\n // Startup loads the project .env before registering its session and resolving\n // storage (MASTRA_APP_DATA_DIR, MASTRA_DB_PATH, ...). Do the same before taking\n // the lock, or prune locks/scans a different app data dir or opens another DB.\n try {\n process.loadEnvFile(path.join(process.cwd(), '.env'));\n } catch {\n // No .env file — keys may be in the shell environment\n }\n\n // Maintenance needs the database to itself: hold the lock for the whole run\n // and refuse while any mastracode session is registered.\n let releaseLock: () => void;\n try {\n releaseLock = acquireMaintenanceLock();\n } catch (err) {\n console.error(err instanceof Error ? err.message : String(err));\n return 1;\n }\n\n try {\n return await prune({ vacuum, keepMemory });\n } finally {\n releaseLock();\n }\n}\n\nasync function prune({ vacuum, keepMemory }: { vacuum: boolean; keepMemory: boolean }): Promise<number> {\n // Resolve storage exactly the way startup does, so prune targets the same\n // database the TUI uses — honoring MASTRA_DB_PATH / MASTRA_STORAGE_BACKEND,\n // global settings, and a project's own .mastracode/database.json.\n const settings = loadSettings();\n const project = detectProject(process.cwd());\n const storageConfig = getStorageConfig(project.rootPath, settings.storage, DEFAULT_CONFIG_DIR);\n\n // A project .env can point storage elsewhere (startup honors it too), so say\n // exactly what is about to be pruned before anything is deleted.\n console.log(`Storage target: ${describeStorageTarget(storageConfig)}`);\n\n const { storage, backend, warning } = await createStorage(storageConfig);\n if (warning) console.log(warning);\n\n let vector: MastraVector | undefined;\n try {\n // The libsql factory does not init (the PG path already has); init is\n // coalesced, so tables are guaranteed to exist before we delete from them.\n await storage.init();\n\n const localDbFiles = resolveLocalDbFiles(storageConfig, backend);\n if (localDbFiles.length > 0) {\n console.log(`Database: ${localDbFiles.join(', ')}`);\n }\n\n // Only the local-libsql compaction needs the vector connection (its file\n // swap refuses while any connection is open). Opening it otherwise would\n // create mastra-vectors.db as a side effect.\n if (vacuum && localDbFiles.length > 0) {\n vector = await createVectorStore(storageConfig, backend);\n }\n const libsqlVector = vector instanceof LibSQLVector ? vector : undefined;\n\n const maintenance = createStorageMaintenance({\n storage,\n backend,\n retention: DEFAULT_RETENTION,\n localDbFiles,\n ...(libsqlVector ? { closeVector: () => libsqlVector.close() } : {}),\n });\n\n await runStorageMaintenance({ maintenance, vacuum, keepMemory, log: line => console.log(line) });\n console.log('Storage maintenance complete.');\n return 0;\n } catch (err) {\n console.error(`Storage maintenance failed: ${err instanceof Error ? err.message : String(err)}`);\n // runStorageMaintenance closes storage on its success path. If it threw\n // first, release the connections here so a retry isn't blocked by our own\n // open handles. Best effort — the original failure is what matters.\n try {\n await storage.close?.();\n if (vector instanceof LibSQLVector) await vector.close();\n } catch {\n // ignore cleanup errors\n }\n return 1;\n }\n}\n\n/** Credential-free URL: drops userinfo and the query string (e.g. ?authToken=). */\nfunction redactUrl(raw: string): string {\n try {\n const url = new URL(raw);\n url.username = '';\n url.password = '';\n url.search = '';\n return url.toString();\n } catch {\n return '<unparseable URL>';\n }\n}\n\nexport function describeStorageTarget(config: StorageConfig): string {\n if (config.backend === 'libsql') {\n return config.url.startsWith('file:')\n ? `libsql file ${config.url.slice('file:'.length)}`\n : `libsql ${redactUrl(config.url)}`;\n }\n if (config.connectionString) return `pg ${redactUrl(config.connectionString)}`;\n const host = `${config.host ?? 'localhost'}${config.port ? `:${config.port}` : ''}`;\n return `pg postgres://${host}/${config.database ?? ''}`;\n}\n"],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"signals-pubsub.js","names":["#resourceId","#rootDir","#leaseSockets","#sockets","#pending","#refs","#liveSubscriptions","#clearGenerations","#closing","#leaseProvider","#sharedPeerDiscovery","#canShareDiscovery","#leasesFor","#closed","#publishTo","#dirFor","#subscribeTo","#unsubscribeFrom","#release","#isRefCounted","#getOrCreate","#retain","#initSocket","#socketPath"],"sources":["../../src/utils/signals-pubsub.ts"],"sourcesContent":["import { mkdir } from 'node:fs/promises';\nimport { isAbsolute, join } from 'node:path';\n\nimport { PubSub, UnixSocketPubSub } from '@mastra/core/events';\nimport type { Event, EventCallback, LeaseProvider, PubSubDeliveryMode, SubscribeOptions } from '@mastra/core/events';\n\nconst DEFAULT_SOCKET_ROOT = '/tmp/mc';\nconst SHARED_SCOPE_DIR = '_shared';\nconst THREAD_STREAM_PREFIX = 'agent.thread-stream.';\nconst THREAD_KEY_SEPARATOR = '\\0';\nconst THREAD_CLAIM_LEASE_PREFIX = 'thread-claim:';\nconst NOTIFICATION_DISPATCH_LEASE_PREFIX = 'notification-dispatch:';\nconst LEASE_SOCKET_NAME = '.leases.sock';\nconst OWNER_DISCOVERY_TOPIC = 'agent.thread-owner-discovery';\nconst PEER_DISCOVERY_TOPIC = 'agent.thread-peer-discovery';\nconst RESERVED_SOCKET_NAME = /^agent_thread-(?:peer|owner)-discovery(?:_|$)/i;\nconst MAX_PATH_SEGMENT_LENGTH = 128;\nconst UUID_PATTERN = '[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}';\nconst DISCOVERY_REPLY_TOPIC = new RegExp(`^agent\\\\.thread-(?:peer|owner)-discovery\\\\.${UUID_PATTERN}$`, 'i');\nconst IDLE_ACCEPTANCE_REPLY_TOPIC = new RegExp(`^agent\\\\.thread-stream\\\\..+\\\\.idle-acceptance\\\\.${UUID_PATTERN}$`, 'i');\n\n/**\n * One-shot reply topics are minted per request (`<topic>.<uuid>`), used for a\n * single round trip, and never published to again once the request settles.\n */\nfunction isEphemeralTopic(topic: string): boolean {\n return DISCOVERY_REPLY_TOPIC.test(topic) || IDLE_ACCEPTANCE_REPLY_TOPIC.test(topic);\n}\n\nexport type SignalsPubSubOptions = {\n /**\n * Also list this process's threads to, and find threads in, other projects on\n * this machine: peer discovery runs in `<root>/_shared/` instead of this\n * resource's directory.\n */\n sharedAgentDiscovery?: boolean;\n /**\n * Directory that holds every resource's socket directory. Defaults to the\n * absolute path in `MASTRACODE_SIGNALS_SOCKET_ROOT`, else `/tmp/mc`.\n */\n rootDir?: string;\n};\n\nfunction socketRootFromEnv(): string {\n const root = process.env.MASTRACODE_SIGNALS_SOCKET_ROOT;\n return root && isAbsolute(root) ? root : DEFAULT_SOCKET_ROOT;\n}\n\n/**\n * Whether an id can name a single directory: no path separators or control\n * characters, not `.`/`..`, and bounded in length.\n */\nfunction isSafePathSegment(value: string): boolean {\n if (!value || value.length > MAX_PATH_SEGMENT_LENGTH || value === '.' || value === '..') return false;\n for (const char of value) {\n const code = char.charCodeAt(0);\n if (char === '/' || char === '\\\\' || code < 0x20 || code === 0x7f) return false;\n }\n return true;\n}\n\n/** The resource a thread key (`<resourceId>\\0<threadId>`) belongs to. */\nfunction resourceOfThreadKey(key: string): string | undefined {\n const separatorIdx = key.indexOf(THREAD_KEY_SEPARATOR);\n if (separatorIdx <= 0) return undefined;\n const resourceId = key.slice(0, separatorIdx);\n return isSafePathSegment(resourceId) ? resourceId : undefined;\n}\n\n/** Decodes `agent.thread-stream.<encoded resourceId\\0threadId>[...]`. */\nfunction decodeThreadTopic(topic: string): string | undefined {\n if (!topic.startsWith(THREAD_STREAM_PREFIX)) return undefined;\n try {\n return decodeURIComponent(topic.slice(THREAD_STREAM_PREFIX.length));\n } catch {\n return undefined;\n }\n}\n\n/**\n * The lease that lets exactly one process dispatch a resource's due\n * notifications: those of the resource's threads that carry `scope` (a\n * `threadScopeKey`, one project's threads), or with no `scope`, those of its\n * threads that carry none. Held in that resource's directory, so every process\n * serving the resource contends for it whichever project it was started in.\n */\nexport function notificationDispatchLeaseKey(resourceId: string, scope?: string): string {\n return `${NOTIFICATION_DISPATCH_LEASE_PREFIX}${resourceId}${scope === undefined ? '' : `${THREAD_KEY_SEPARATOR}${scope}`}`;\n}\n\n/**\n * The resource a lease key belongs to: a thread's run lease and claim lease,\n * and a resource's notification dispatch leases.\n */\nfunction resourceOfLeaseKey(key: string): string | undefined {\n if (key.startsWith(NOTIFICATION_DISPATCH_LEASE_PREFIX)) {\n const [resourceId = ''] = key.slice(NOTIFICATION_DISPATCH_LEASE_PREFIX.length).split(THREAD_KEY_SEPARATOR, 1);\n return isSafePathSegment(resourceId) ? resourceId : undefined;\n }\n return resourceOfThreadKey(\n key.startsWith(THREAD_CLAIM_LEASE_PREFIX) ? key.slice(THREAD_CLAIM_LEASE_PREFIX.length) : key,\n );\n}\n\nfunction isOwnerDiscoveryTopic(topic: string): boolean {\n return topic === OWNER_DISCOVERY_TOPIC || topic.startsWith(`${OWNER_DISCOVERY_TOPIC}.`);\n}\n\nfunction isPeerDiscoveryTopic(topic: string): boolean {\n return topic === PEER_DISCOVERY_TOPIC || topic.startsWith(`${PEER_DISCOVERY_TOPIC}.`);\n}\n\n/**\n * Whether a threadId can be the base name of a thread's socket file: no\n * separators or control characters, not `.`/`..`, and not a name another\n * socket in the same directory already uses (the lease socket, and the\n * sanitized peer/owner discovery topics and their reply topics), compared\n * case-insensitively because default macOS and Windows filesystems are.\n */\nfunction isSafeFileName(value: string): boolean {\n if (!value || value === '.' || value === '..' || `${value}.sock`.toLowerCase() === LEASE_SOCKET_NAME) return false;\n if (RESERVED_SOCKET_NAME.test(value)) return false;\n for (const char of value) {\n const code = char.charCodeAt(0);\n if (char === '/' || char === '\\\\' || code < 0x20 || code === 0x7f) return false;\n }\n return true;\n}\n\n/**\n * Derive a filesystem-safe key for the topic. Thread-stream topics embed\n * a threadId; all other topics use a sanitized version of the topic name.\n */\nfunction topicKey(topic: string): string {\n const decoded = decodeThreadTopic(topic);\n if (decoded !== undefined) {\n const separatorIdx = decoded.indexOf(THREAD_KEY_SEPARATOR);\n if (separatorIdx !== -1) return decoded.slice(separatorIdx + 1);\n }\n return topic.replace(/[^a-zA-Z0-9_-]/g, '_');\n}\n\nfunction socketKey(dir: string, topic: string): string {\n return `${dir}/${topicKey(topic)}`;\n}\n\n/**\n * A PubSub that manages one Unix socket per topic for cross-process signal\n * coordination between mastracode processes on this machine.\n *\n * Socket paths use `<root>/<dir>/<sanitized-topic>.sock` (root `/tmp/mc`)\n * for inspectability and automatic OS cleanup. Each topic gets its own\n * socket, so broker election and message routing are per-topic.\n *\n * Every process shares one database, so any process can act on any project's\n * thread — the notification dispatcher, for one, delivers whatever is due.\n * Whatever coordinates a thread therefore lives with the thread, not with the\n * process touching it:\n *\n * - A thread's stream topic and its leases (the run lease and the claim\n * lease) live in the directory of the resource that owns the thread, so a\n * process from another project sees that thread's active run and claim.\n * - Thread-owner discovery lives in `<root>/_shared/`, so a process that finds\n * a thread claimed can reach the owner and hand it the signal. It only\n * answers for a thread the asker names, so it discloses nothing new.\n * - Peer discovery (which lists threads) runs in `<root>/_shared/` when\n * `sharedAgentDiscovery` is set, so processes in every project can list each\n * other's threads. Without it, it stays in this resource's directory.\n * - Everything else stays in this process's own resource directory.\n *\n * Stale sockets from crashed processes are handled by\n * {@link UnixSocketPubSub}'s built-in election logic: it detects\n * ECONNREFUSED on a dead broker socket, unlinks it, and re-elects.\n * No blanket cleanup is needed here — that would break concurrent\n * mc instances sharing a directory.\n *\n * One-shot reply topics (discovery and idle-acceptance replies) are\n * reference-counted: every in-flight publish/subscribe and every live\n * subscription holds a reference, and the topic's socket is closed as soon as\n * the count drops to zero. Without this each request would keep a socket, a\n * file descriptor and (for the broker) a socket file for the process lifetime.\n * With `sharedAgentDiscovery`, another resource's thread topics are counted the\n * same way, so a process that signals many threads elsewhere does not keep a\n * socket open for each.\n */\nclass SignalsPubSub extends PubSub {\n readonly #resourceId: string;\n readonly #rootDir: string;\n readonly #leaseSockets = new Map<string, UnixSocketPubSub>();\n readonly #sockets = new Map<string, UnixSocketPubSub>();\n readonly #pending = new Map<string, Promise<UnixSocketPubSub>>();\n readonly #refs = new Map<string, number>();\n readonly #liveSubscriptions = new Map<string, Set<EventCallback>>();\n readonly #clearGenerations = new Map<string, number>();\n readonly #closing = new Map<string, Promise<void>>();\n readonly #leaseProvider: LeaseProvider;\n readonly #sharedPeerDiscovery: boolean;\n #closed = false;\n\n constructor(resourceId: string, options: SignalsPubSubOptions = {}) {\n super();\n this.#resourceId = resourceId;\n this.#rootDir = options.rootDir ?? socketRootFromEnv();\n this.#sharedPeerDiscovery = Boolean(options.sharedAgentDiscovery) && this.#canShareDiscovery();\n // Created eagerly, as before, so this resource's lease socket exists from the start.\n this.#leasesFor(resourceId);\n const route = (key: string) => {\n // Lease sockets are created per resource on demand; once closed, never\n // reopen one (a lease renewal timer may outlive close()).\n if (this.#closed) throw new Error('SignalsPubSub is closed');\n return this.#leasesFor(resourceOfLeaseKey(key) ?? this.#resourceId);\n };\n this.#leaseProvider = {\n acquireLease: async (key, owner, ttlMs) => route(key).acquireLease(key, owner, ttlMs),\n getLeaseOwner: async key => route(key).getLeaseOwner(key),\n releaseLease: async (key, owner) => route(key).releaseLease(key, owner),\n renewLease: async (key, owner, ttlMs) => route(key).renewLease(key, owner, ttlMs),\n transferLease: async (key, fromOwner, toOwner, ttlMs) => route(key).transferLease(key, fromOwner, toOwner, ttlMs),\n };\n }\n\n override get supportedModes(): ReadonlyArray<PubSubDeliveryMode> {\n return ['push'];\n }\n\n /**\n * Filesystem leases. A thread's run and claim leases are held in the\n * directory of the thread's resource, so every process contends for the\n * same lease; any other key stays in this resource's directory.\n */\n getLeaseProvider(): LeaseProvider {\n return this.#leaseProvider;\n }\n\n async publish(\n topic: string,\n event: Omit<Event, 'id' | 'createdAt'>,\n options?: { localOnly?: boolean },\n ): Promise<void> {\n await this.#publishTo(topic, this.#dirFor(topic), event, options);\n }\n\n async subscribe(topic: string, cb: EventCallback, options?: SubscribeOptions): Promise<void> {\n await this.#subscribeTo(topic, this.#dirFor(topic), cb, options);\n }\n\n async unsubscribe(topic: string, cb: EventCallback): Promise<void> {\n await this.#unsubscribeFrom(topic, this.#dirFor(topic), cb);\n }\n\n /**\n * Drops a one-shot reply topic: its remaining subscriptions release their\n * references and the socket closes once no operation still uses it. Other\n * topics (workflow events, thread streams) have subscribers in other\n * processes that need the broker, so clearing them is a no-op.\n */\n override async clearTopic(topic: string): Promise<void> {\n if (!isEphemeralTopic(topic)) return;\n const key = socketKey(this.#dirFor(topic), topic);\n if (!this.#refs.has(key)) return;\n this.#clearGenerations.set(key, (this.#clearGenerations.get(key) ?? 0) + 1);\n const subscriptions = this.#liveSubscriptions.get(key);\n if (!subscriptions) return;\n this.#liveSubscriptions.delete(key);\n for (let i = 0; i < subscriptions.size; i++) this.#release(key);\n }\n\n async flush(): Promise<void> {\n await Promise.all([...this.#sockets.values()].map(s => s.flush()));\n }\n\n async close(): Promise<void> {\n this.#closed = true;\n await Promise.allSettled([\n ...[...this.#leaseSockets.values()].map(s => s.close()),\n ...[...this.#sockets.values()].map(s => s.close()),\n ...this.#closing.values(),\n ]);\n this.#sockets.clear();\n this.#leaseSockets.clear();\n this.#refs.clear();\n this.#liveSubscriptions.clear();\n this.#clearGenerations.clear();\n }\n\n /** Get the underlying socket for a topic (for testing/inspection). */\n getSocket(topic: string): UnixSocketPubSub | undefined {\n return this.#sockets.get(socketKey(this.#dirFor(topic), topic));\n }\n\n #canShareDiscovery(): boolean {\n if (isSafePathSegment(this.#resourceId) && this.#resourceId !== SHARED_SCOPE_DIR) return true;\n console.warn(\n `Cross-project agent discovery is disabled: resource id ${JSON.stringify(this.#resourceId)} cannot be used as a directory name, so other projects could not reach this instance.`,\n );\n return false;\n }\n\n /**\n * One-shot reply topics close once unused. With shared discovery, so do thread\n * topics routed to another resource; without it, they behave as before.\n */\n #isRefCounted(topic: string): boolean {\n if (isEphemeralTopic(topic)) return true;\n if (!this.#sharedPeerDiscovery) return false;\n const decoded = decodeThreadTopic(topic);\n if (decoded === undefined) return false;\n const resourceId = resourceOfThreadKey(decoded);\n return resourceId !== undefined && resourceId !== this.#resourceId;\n }\n\n async #publishTo(\n topic: string,\n dir: string,\n event: Omit<Event, 'id' | 'createdAt'>,\n options?: { localOnly?: boolean },\n ): Promise<void> {\n if (!this.#isRefCounted(topic)) {\n const socket = await this.#getOrCreate(topic, dir);\n await socket.publish(topic, event, options);\n return;\n }\n const key = socketKey(dir, topic);\n // Retain synchronously so a concurrent release cannot close the socket\n // this publish is about to use.\n this.#retain(key);\n try {\n const socket = await this.#getOrCreate(topic, dir);\n await socket.publish(topic, event, options);\n } finally {\n this.#release(key);\n }\n }\n\n async #subscribeTo(topic: string, dir: string, cb: EventCallback, options?: SubscribeOptions): Promise<void> {\n if (!this.#isRefCounted(topic)) {\n const socket = await this.#getOrCreate(topic, dir);\n await socket.subscribe(topic, cb, options);\n return;\n }\n const key = socketKey(dir, topic);\n this.#retain(key);\n const generation = this.#clearGenerations.get(key) ?? 0;\n try {\n const socket = await this.#getOrCreate(topic, dir);\n await socket.subscribe(topic, cb, options);\n if ((this.#clearGenerations.get(key) ?? 0) !== generation) {\n // The topic was cleared while this subscribe was in flight.\n await socket.unsubscribe(topic, cb);\n return;\n }\n let subscriptions = this.#liveSubscriptions.get(key);\n if (!subscriptions) {\n subscriptions = new Set();\n this.#liveSubscriptions.set(key, subscriptions);\n }\n if (!subscriptions.has(cb)) {\n subscriptions.add(cb);\n this.#retain(key);\n }\n } finally {\n this.#release(key);\n }\n }\n\n async #unsubscribeFrom(topic: string, dir: string, cb: EventCallback): Promise<void> {\n const key = socketKey(dir, topic);\n const socket = this.#sockets.get(key);\n const subscriptions = this.#isRefCounted(topic) ? this.#liveSubscriptions.get(key) : undefined;\n if (!subscriptions?.delete(cb)) {\n if (!socket) return;\n await socket.unsubscribe(topic, cb);\n return;\n }\n if (subscriptions.size === 0) this.#liveSubscriptions.delete(key);\n try {\n await socket?.unsubscribe(topic, cb);\n } finally {\n this.#release(key);\n }\n }\n\n #leasesFor(resourceId: string): UnixSocketPubSub {\n let socket = this.#leaseSockets.get(resourceId);\n if (!socket) {\n socket = new UnixSocketPubSub(join(this.#rootDir, resourceId, LEASE_SOCKET_NAME));\n this.#leaseSockets.set(resourceId, socket);\n }\n return socket;\n }\n\n /** The directory, under the root, that holds a topic's socket. */\n #dirFor(topic: string): string {\n if (isOwnerDiscoveryTopic(topic)) return SHARED_SCOPE_DIR;\n if (this.#sharedPeerDiscovery && isPeerDiscoveryTopic(topic)) return SHARED_SCOPE_DIR;\n const decoded = decodeThreadTopic(topic);\n if (decoded !== undefined) return resourceOfThreadKey(decoded) ?? this.#resourceId;\n return this.#resourceId;\n }\n\n #retain(key: string): void {\n this.#refs.set(key, (this.#refs.get(key) ?? 0) + 1);\n }\n\n #release(key: string): void {\n const refs = (this.#refs.get(key) ?? 0) - 1;\n if (refs > 0) {\n this.#refs.set(key, refs);\n return;\n }\n this.#refs.delete(key);\n this.#clearGenerations.delete(key);\n this.#liveSubscriptions.delete(key);\n const socket = this.#sockets.get(key);\n if (!socket) return;\n this.#sockets.delete(key);\n const closing: Promise<void> = socket\n .close()\n .catch(() => {})\n .finally(() => {\n if (this.#closing.get(key) === closing) this.#closing.delete(key);\n });\n this.#closing.set(key, closing);\n }\n\n async #getOrCreate(topic: string, dir: string): Promise<UnixSocketPubSub> {\n if (this.#closed) throw new Error('SignalsPubSub is closed');\n const key = socketKey(dir, topic);\n // A socket for this key may still be closing after its last reference\n // was released; never hand out the closing instance.\n const closing = this.#closing.get(key);\n if (closing) {\n await closing;\n if (this.#closed) throw new Error('SignalsPubSub is closed');\n }\n const existing = this.#sockets.get(key);\n if (existing) return existing;\n // Deduplicate concurrent callers so only one socket is created per topic.\n let inflight = this.#pending.get(key);\n if (!inflight) {\n inflight = this.#initSocket(topic, dir, key);\n this.#pending.set(key, inflight);\n }\n const socket = await inflight;\n if (this.#closed) throw new Error('SignalsPubSub is closed');\n return socket;\n }\n\n async #initSocket(topic: string, dir: string, key: string): Promise<UnixSocketPubSub> {\n try {\n const socketPath = await this.#socketPath(topic, dir);\n if (this.#closed) throw new Error('SignalsPubSub is closed');\n const socket = new UnixSocketPubSub(socketPath);\n this.#sockets.set(key, socket);\n return socket;\n } finally {\n this.#pending.delete(key);\n }\n }\n\n async #socketPath(topic: string, scope: string): Promise<string> {\n let key = topicKey(topic);\n // A threadId names a file in its resource's directory, so never let it\n // reach outside that directory, regardless of who owns the thread or\n // whether cross-project discovery is enabled.\n if (decodeThreadTopic(topic) !== undefined && !isSafeFileName(key)) {\n throw new Error('Cannot route an agent thread topic whose threadId is not a safe file name');\n }\n const dir = join(this.#rootDir, scope);\n await mkdir(dir, { recursive: true });\n const candidate = join(dir, `${key}.sock`);\n // macOS sun_path limit is 104 bytes; Linux is 108. Use 104 as the\n // conservative bound. When the path is too long, replace the key with\n // a short hash so the socket can still be created.\n if (Buffer.byteLength(candidate) > 104) {\n key = Buffer.from(await globalThis.crypto.subtle.digest('SHA-256', new TextEncoder().encode(key)))\n .toString('hex')\n .slice(0, 16);\n return join(dir, `${key}.sock`);\n }\n return candidate;\n }\n}\n\n/**\n * Creates a per-topic PubSub backed by Unix sockets for cross-process signal\n * and workflow event coordination between mastracode processes.\n *\n * Topics live under `/tmp/mc/<resourceId>/`, except that a thread's stream and\n * leases live under its own resource's directory and thread-owner discovery\n * under `/tmp/mc/_shared/` (see {@link SignalsPubSub}). With\n * `sharedAgentDiscovery`, peer discovery runs there too. Stale sockets from\n * crashed processes are handled by the underlying {@link UnixSocketPubSub}'s\n * broker election logic.\n */\nexport function createSignalsPubSub(resourceId: string, options?: SignalsPubSubOptions): SignalsPubSub {\n return new SignalsPubSub(resourceId, options);\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"signals-pubsub.js","names":[],"sources":["../../src/utils/signals-pubsub.ts"],"sourcesContent":["import { mkdir } from 'node:fs/promises';\nimport { isAbsolute, join } from 'node:path';\n\nimport { PubSub, UnixSocketPubSub } from '@mastra/core/events';\nimport type { Event, EventCallback, LeaseProvider, PubSubDeliveryMode, SubscribeOptions } from '@mastra/core/events';\n\nconst DEFAULT_SOCKET_ROOT = '/tmp/mc';\nconst SHARED_SCOPE_DIR = '_shared';\nconst THREAD_STREAM_PREFIX = 'agent.thread-stream.';\nconst THREAD_KEY_SEPARATOR = '\\0';\nconst THREAD_CLAIM_LEASE_PREFIX = 'thread-claim:';\nconst NOTIFICATION_DISPATCH_LEASE_PREFIX = 'notification-dispatch:';\nconst LEASE_SOCKET_NAME = '.leases.sock';\nconst OWNER_DISCOVERY_TOPIC = 'agent.thread-owner-discovery';\nconst PEER_DISCOVERY_TOPIC = 'agent.thread-peer-discovery';\nconst RESERVED_SOCKET_NAME = /^agent_thread-(?:peer|owner)-discovery(?:_|$)/i;\nconst MAX_PATH_SEGMENT_LENGTH = 128;\nconst UUID_PATTERN = '[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}';\nconst DISCOVERY_REPLY_TOPIC = new RegExp(`^agent\\\\.thread-(?:peer|owner)-discovery\\\\.${UUID_PATTERN}$`, 'i');\nconst IDLE_ACCEPTANCE_REPLY_TOPIC = new RegExp(`^agent\\\\.thread-stream\\\\..+\\\\.idle-acceptance\\\\.${UUID_PATTERN}$`, 'i');\n\n/**\n * One-shot reply topics are minted per request (`<topic>.<uuid>`), used for a\n * single round trip, and never published to again once the request settles.\n */\nfunction isEphemeralTopic(topic: string): boolean {\n return DISCOVERY_REPLY_TOPIC.test(topic) || IDLE_ACCEPTANCE_REPLY_TOPIC.test(topic);\n}\n\nexport type SignalsPubSubOptions = {\n /**\n * Also list this process's threads to, and find threads in, other projects on\n * this machine: peer discovery runs in `<root>/_shared/` instead of this\n * resource's directory.\n */\n sharedAgentDiscovery?: boolean;\n /**\n * Directory that holds every resource's socket directory. Defaults to the\n * absolute path in `MASTRACODE_SIGNALS_SOCKET_ROOT`, else `/tmp/mc`.\n */\n rootDir?: string;\n};\n\nfunction socketRootFromEnv(): string {\n const root = process.env.MASTRACODE_SIGNALS_SOCKET_ROOT;\n return root && isAbsolute(root) ? root : DEFAULT_SOCKET_ROOT;\n}\n\n/**\n * Whether an id can name a single directory: no path separators or control\n * characters, not `.`/`..`, and bounded in length.\n */\nfunction isSafePathSegment(value: string): boolean {\n if (!value || value.length > MAX_PATH_SEGMENT_LENGTH || value === '.' || value === '..') return false;\n for (const char of value) {\n const code = char.charCodeAt(0);\n if (char === '/' || char === '\\\\' || code < 0x20 || code === 0x7f) return false;\n }\n return true;\n}\n\n/** The resource a thread key (`<resourceId>\\0<threadId>`) belongs to. */\nfunction resourceOfThreadKey(key: string): string | undefined {\n const separatorIdx = key.indexOf(THREAD_KEY_SEPARATOR);\n if (separatorIdx <= 0) return undefined;\n const resourceId = key.slice(0, separatorIdx);\n return isSafePathSegment(resourceId) ? resourceId : undefined;\n}\n\n/** Decodes `agent.thread-stream.<encoded resourceId\\0threadId>[...]`. */\nfunction decodeThreadTopic(topic: string): string | undefined {\n if (!topic.startsWith(THREAD_STREAM_PREFIX)) return undefined;\n try {\n return decodeURIComponent(topic.slice(THREAD_STREAM_PREFIX.length));\n } catch {\n return undefined;\n }\n}\n\n/**\n * The lease that lets exactly one process dispatch a resource's due\n * notifications: those of the resource's threads that carry `scope` (a\n * `threadScopeKey`, one project's threads), or with no `scope`, those of its\n * threads that carry none. Held in that resource's directory, so every process\n * serving the resource contends for it whichever project it was started in.\n */\nexport function notificationDispatchLeaseKey(resourceId: string, scope?: string): string {\n return `${NOTIFICATION_DISPATCH_LEASE_PREFIX}${resourceId}${scope === undefined ? '' : `${THREAD_KEY_SEPARATOR}${scope}`}`;\n}\n\n/**\n * The resource a lease key belongs to: a thread's run lease and claim lease,\n * and a resource's notification dispatch leases.\n */\nfunction resourceOfLeaseKey(key: string): string | undefined {\n if (key.startsWith(NOTIFICATION_DISPATCH_LEASE_PREFIX)) {\n const [resourceId = ''] = key.slice(NOTIFICATION_DISPATCH_LEASE_PREFIX.length).split(THREAD_KEY_SEPARATOR, 1);\n return isSafePathSegment(resourceId) ? resourceId : undefined;\n }\n return resourceOfThreadKey(\n key.startsWith(THREAD_CLAIM_LEASE_PREFIX) ? key.slice(THREAD_CLAIM_LEASE_PREFIX.length) : key,\n );\n}\n\nfunction isOwnerDiscoveryTopic(topic: string): boolean {\n return topic === OWNER_DISCOVERY_TOPIC || topic.startsWith(`${OWNER_DISCOVERY_TOPIC}.`);\n}\n\nfunction isPeerDiscoveryTopic(topic: string): boolean {\n return topic === PEER_DISCOVERY_TOPIC || topic.startsWith(`${PEER_DISCOVERY_TOPIC}.`);\n}\n\n/**\n * Whether a threadId can be the base name of a thread's socket file: no\n * separators or control characters, not `.`/`..`, and not a name another\n * socket in the same directory already uses (the lease socket, and the\n * sanitized peer/owner discovery topics and their reply topics), compared\n * case-insensitively because default macOS and Windows filesystems are.\n */\nfunction isSafeFileName(value: string): boolean {\n if (!value || value === '.' || value === '..' || `${value}.sock`.toLowerCase() === LEASE_SOCKET_NAME) return false;\n if (RESERVED_SOCKET_NAME.test(value)) return false;\n for (const char of value) {\n const code = char.charCodeAt(0);\n if (char === '/' || char === '\\\\' || code < 0x20 || code === 0x7f) return false;\n }\n return true;\n}\n\n/**\n * Derive a filesystem-safe key for the topic. Thread-stream topics embed\n * a threadId; all other topics use a sanitized version of the topic name.\n */\nfunction topicKey(topic: string): string {\n const decoded = decodeThreadTopic(topic);\n if (decoded !== undefined) {\n const separatorIdx = decoded.indexOf(THREAD_KEY_SEPARATOR);\n if (separatorIdx !== -1) return decoded.slice(separatorIdx + 1);\n }\n return topic.replace(/[^a-zA-Z0-9_-]/g, '_');\n}\n\nfunction socketKey(dir: string, topic: string): string {\n return `${dir}/${topicKey(topic)}`;\n}\n\n/**\n * A PubSub that manages one Unix socket per topic for cross-process signal\n * coordination between mastracode processes on this machine.\n *\n * Socket paths use `<root>/<dir>/<sanitized-topic>.sock` (root `/tmp/mc`)\n * for inspectability and automatic OS cleanup. Each topic gets its own\n * socket, so broker election and message routing are per-topic.\n *\n * Every process shares one database, so any process can act on any project's\n * thread — the notification dispatcher, for one, delivers whatever is due.\n * Whatever coordinates a thread therefore lives with the thread, not with the\n * process touching it:\n *\n * - A thread's stream topic and its leases (the run lease and the claim\n * lease) live in the directory of the resource that owns the thread, so a\n * process from another project sees that thread's active run and claim.\n * - Thread-owner discovery lives in `<root>/_shared/`, so a process that finds\n * a thread claimed can reach the owner and hand it the signal. It only\n * answers for a thread the asker names, so it discloses nothing new.\n * - Peer discovery (which lists threads) runs in `<root>/_shared/` when\n * `sharedAgentDiscovery` is set, so processes in every project can list each\n * other's threads. Without it, it stays in this resource's directory.\n * - Everything else stays in this process's own resource directory.\n *\n * Stale sockets from crashed processes are handled by\n * {@link UnixSocketPubSub}'s built-in election logic: it detects\n * ECONNREFUSED on a dead broker socket, unlinks it, and re-elects.\n * No blanket cleanup is needed here — that would break concurrent\n * mc instances sharing a directory.\n *\n * One-shot reply topics (discovery and idle-acceptance replies) are\n * reference-counted: every in-flight publish/subscribe and every live\n * subscription holds a reference, and the topic's socket is closed as soon as\n * the count drops to zero. Without this each request would keep a socket, a\n * file descriptor and (for the broker) a socket file for the process lifetime.\n * With `sharedAgentDiscovery`, another resource's thread topics are counted the\n * same way, so a process that signals many threads elsewhere does not keep a\n * socket open for each.\n */\nclass SignalsPubSub extends PubSub {\n readonly #resourceId: string;\n readonly #rootDir: string;\n readonly #leaseSockets = new Map<string, UnixSocketPubSub>();\n readonly #sockets = new Map<string, UnixSocketPubSub>();\n readonly #pending = new Map<string, Promise<UnixSocketPubSub>>();\n readonly #refs = new Map<string, number>();\n readonly #liveSubscriptions = new Map<string, Set<EventCallback>>();\n readonly #clearGenerations = new Map<string, number>();\n readonly #closing = new Map<string, Promise<void>>();\n readonly #leaseProvider: LeaseProvider;\n readonly #sharedPeerDiscovery: boolean;\n #closed = false;\n\n constructor(resourceId: string, options: SignalsPubSubOptions = {}) {\n super();\n this.#resourceId = resourceId;\n this.#rootDir = options.rootDir ?? socketRootFromEnv();\n this.#sharedPeerDiscovery = Boolean(options.sharedAgentDiscovery) && this.#canShareDiscovery();\n // Created eagerly, as before, so this resource's lease socket exists from the start.\n this.#leasesFor(resourceId);\n const route = (key: string) => {\n // Lease sockets are created per resource on demand; once closed, never\n // reopen one (a lease renewal timer may outlive close()).\n if (this.#closed) throw new Error('SignalsPubSub is closed');\n return this.#leasesFor(resourceOfLeaseKey(key) ?? this.#resourceId);\n };\n this.#leaseProvider = {\n acquireLease: async (key, owner, ttlMs) => route(key).acquireLease(key, owner, ttlMs),\n getLeaseOwner: async key => route(key).getLeaseOwner(key),\n releaseLease: async (key, owner) => route(key).releaseLease(key, owner),\n renewLease: async (key, owner, ttlMs) => route(key).renewLease(key, owner, ttlMs),\n transferLease: async (key, fromOwner, toOwner, ttlMs) => route(key).transferLease(key, fromOwner, toOwner, ttlMs),\n };\n }\n\n override get supportedModes(): ReadonlyArray<PubSubDeliveryMode> {\n return ['push'];\n }\n\n /**\n * Filesystem leases. A thread's run and claim leases are held in the\n * directory of the thread's resource, so every process contends for the\n * same lease; any other key stays in this resource's directory.\n */\n getLeaseProvider(): LeaseProvider {\n return this.#leaseProvider;\n }\n\n async publish(\n topic: string,\n event: Omit<Event, 'id' | 'createdAt'>,\n options?: { localOnly?: boolean },\n ): Promise<void> {\n await this.#publishTo(topic, this.#dirFor(topic), event, options);\n }\n\n async subscribe(topic: string, cb: EventCallback, options?: SubscribeOptions): Promise<void> {\n await this.#subscribeTo(topic, this.#dirFor(topic), cb, options);\n }\n\n async unsubscribe(topic: string, cb: EventCallback): Promise<void> {\n await this.#unsubscribeFrom(topic, this.#dirFor(topic), cb);\n }\n\n /**\n * Drops a one-shot reply topic: its remaining subscriptions release their\n * references and the socket closes once no operation still uses it. Other\n * topics (workflow events, thread streams) have subscribers in other\n * processes that need the broker, so clearing them is a no-op.\n */\n override async clearTopic(topic: string): Promise<void> {\n if (!isEphemeralTopic(topic)) return;\n const key = socketKey(this.#dirFor(topic), topic);\n if (!this.#refs.has(key)) return;\n this.#clearGenerations.set(key, (this.#clearGenerations.get(key) ?? 0) + 1);\n const subscriptions = this.#liveSubscriptions.get(key);\n if (!subscriptions) return;\n this.#liveSubscriptions.delete(key);\n for (let i = 0; i < subscriptions.size; i++) this.#release(key);\n }\n\n async flush(): Promise<void> {\n await Promise.all([...this.#sockets.values()].map(s => s.flush()));\n }\n\n async close(): Promise<void> {\n this.#closed = true;\n await Promise.allSettled([\n ...[...this.#leaseSockets.values()].map(s => s.close()),\n ...[...this.#sockets.values()].map(s => s.close()),\n ...this.#closing.values(),\n ]);\n this.#sockets.clear();\n this.#leaseSockets.clear();\n this.#refs.clear();\n this.#liveSubscriptions.clear();\n this.#clearGenerations.clear();\n }\n\n /** Get the underlying socket for a topic (for testing/inspection). */\n getSocket(topic: string): UnixSocketPubSub | undefined {\n return this.#sockets.get(socketKey(this.#dirFor(topic), topic));\n }\n\n #canShareDiscovery(): boolean {\n if (isSafePathSegment(this.#resourceId) && this.#resourceId !== SHARED_SCOPE_DIR) return true;\n console.warn(\n `Cross-project agent discovery is disabled: resource id ${JSON.stringify(this.#resourceId)} cannot be used as a directory name, so other projects could not reach this instance.`,\n );\n return false;\n }\n\n /**\n * One-shot reply topics close once unused. With shared discovery, so do thread\n * topics routed to another resource; without it, they behave as before.\n */\n #isRefCounted(topic: string): boolean {\n if (isEphemeralTopic(topic)) return true;\n if (!this.#sharedPeerDiscovery) return false;\n const decoded = decodeThreadTopic(topic);\n if (decoded === undefined) return false;\n const resourceId = resourceOfThreadKey(decoded);\n return resourceId !== undefined && resourceId !== this.#resourceId;\n }\n\n async #publishTo(\n topic: string,\n dir: string,\n event: Omit<Event, 'id' | 'createdAt'>,\n options?: { localOnly?: boolean },\n ): Promise<void> {\n if (!this.#isRefCounted(topic)) {\n const socket = await this.#getOrCreate(topic, dir);\n await socket.publish(topic, event, options);\n return;\n }\n const key = socketKey(dir, topic);\n // Retain synchronously so a concurrent release cannot close the socket\n // this publish is about to use.\n this.#retain(key);\n try {\n const socket = await this.#getOrCreate(topic, dir);\n await socket.publish(topic, event, options);\n } finally {\n this.#release(key);\n }\n }\n\n async #subscribeTo(topic: string, dir: string, cb: EventCallback, options?: SubscribeOptions): Promise<void> {\n if (!this.#isRefCounted(topic)) {\n const socket = await this.#getOrCreate(topic, dir);\n await socket.subscribe(topic, cb, options);\n return;\n }\n const key = socketKey(dir, topic);\n this.#retain(key);\n const generation = this.#clearGenerations.get(key) ?? 0;\n try {\n const socket = await this.#getOrCreate(topic, dir);\n await socket.subscribe(topic, cb, options);\n if ((this.#clearGenerations.get(key) ?? 0) !== generation) {\n // The topic was cleared while this subscribe was in flight.\n await socket.unsubscribe(topic, cb);\n return;\n }\n let subscriptions = this.#liveSubscriptions.get(key);\n if (!subscriptions) {\n subscriptions = new Set();\n this.#liveSubscriptions.set(key, subscriptions);\n }\n if (!subscriptions.has(cb)) {\n subscriptions.add(cb);\n this.#retain(key);\n }\n } finally {\n this.#release(key);\n }\n }\n\n async #unsubscribeFrom(topic: string, dir: string, cb: EventCallback): Promise<void> {\n const key = socketKey(dir, topic);\n const socket = this.#sockets.get(key);\n const subscriptions = this.#isRefCounted(topic) ? this.#liveSubscriptions.get(key) : undefined;\n if (!subscriptions?.delete(cb)) {\n if (!socket) return;\n await socket.unsubscribe(topic, cb);\n return;\n }\n if (subscriptions.size === 0) this.#liveSubscriptions.delete(key);\n try {\n await socket?.unsubscribe(topic, cb);\n } finally {\n this.#release(key);\n }\n }\n\n #leasesFor(resourceId: string): UnixSocketPubSub {\n let socket = this.#leaseSockets.get(resourceId);\n if (!socket) {\n socket = new UnixSocketPubSub(join(this.#rootDir, resourceId, LEASE_SOCKET_NAME));\n this.#leaseSockets.set(resourceId, socket);\n }\n return socket;\n }\n\n /** The directory, under the root, that holds a topic's socket. */\n #dirFor(topic: string): string {\n if (isOwnerDiscoveryTopic(topic)) return SHARED_SCOPE_DIR;\n if (this.#sharedPeerDiscovery && isPeerDiscoveryTopic(topic)) return SHARED_SCOPE_DIR;\n const decoded = decodeThreadTopic(topic);\n if (decoded !== undefined) return resourceOfThreadKey(decoded) ?? this.#resourceId;\n return this.#resourceId;\n }\n\n #retain(key: string): void {\n this.#refs.set(key, (this.#refs.get(key) ?? 0) + 1);\n }\n\n #release(key: string): void {\n const refs = (this.#refs.get(key) ?? 0) - 1;\n if (refs > 0) {\n this.#refs.set(key, refs);\n return;\n }\n this.#refs.delete(key);\n this.#clearGenerations.delete(key);\n this.#liveSubscriptions.delete(key);\n const socket = this.#sockets.get(key);\n if (!socket) return;\n this.#sockets.delete(key);\n const closing: Promise<void> = socket\n .close()\n .catch(() => {})\n .finally(() => {\n if (this.#closing.get(key) === closing) this.#closing.delete(key);\n });\n this.#closing.set(key, closing);\n }\n\n async #getOrCreate(topic: string, dir: string): Promise<UnixSocketPubSub> {\n if (this.#closed) throw new Error('SignalsPubSub is closed');\n const key = socketKey(dir, topic);\n // A socket for this key may still be closing after its last reference\n // was released; never hand out the closing instance.\n const closing = this.#closing.get(key);\n if (closing) {\n await closing;\n if (this.#closed) throw new Error('SignalsPubSub is closed');\n }\n const existing = this.#sockets.get(key);\n if (existing) return existing;\n // Deduplicate concurrent callers so only one socket is created per topic.\n let inflight = this.#pending.get(key);\n if (!inflight) {\n inflight = this.#initSocket(topic, dir, key);\n this.#pending.set(key, inflight);\n }\n const socket = await inflight;\n if (this.#closed) throw new Error('SignalsPubSub is closed');\n return socket;\n }\n\n async #initSocket(topic: string, dir: string, key: string): Promise<UnixSocketPubSub> {\n try {\n const socketPath = await this.#socketPath(topic, dir);\n if (this.#closed) throw new Error('SignalsPubSub is closed');\n const socket = new UnixSocketPubSub(socketPath);\n this.#sockets.set(key, socket);\n return socket;\n } finally {\n this.#pending.delete(key);\n }\n }\n\n async #socketPath(topic: string, scope: string): Promise<string> {\n let key = topicKey(topic);\n // A threadId names a file in its resource's directory, so never let it\n // reach outside that directory, regardless of who owns the thread or\n // whether cross-project discovery is enabled.\n if (decodeThreadTopic(topic) !== undefined && !isSafeFileName(key)) {\n throw new Error('Cannot route an agent thread topic whose threadId is not a safe file name');\n }\n const dir = join(this.#rootDir, scope);\n await mkdir(dir, { recursive: true });\n const candidate = join(dir, `${key}.sock`);\n // macOS sun_path limit is 104 bytes; Linux is 108. Use 104 as the\n // conservative bound. When the path is too long, replace the key with\n // a short hash so the socket can still be created.\n if (Buffer.byteLength(candidate) > 104) {\n key = Buffer.from(await globalThis.crypto.subtle.digest('SHA-256', new TextEncoder().encode(key)))\n .toString('hex')\n .slice(0, 16);\n return join(dir, `${key}.sock`);\n }\n return candidate;\n }\n}\n\n/**\n * Creates a per-topic PubSub backed by Unix sockets for cross-process signal\n * and workflow event coordination between mastracode processes.\n *\n * Topics live under `/tmp/mc/<resourceId>/`, except that a thread's stream and\n * leases live under its own resource's directory and thread-owner discovery\n * under `/tmp/mc/_shared/` (see {@link SignalsPubSub}). With\n * `sharedAgentDiscovery`, peer discovery runs there too. Stale sockets from\n * crashed processes are handled by the underlying {@link UnixSocketPubSub}'s\n * broker election logic.\n */\nexport function createSignalsPubSub(resourceId: string, options?: SignalsPubSubOptions): SignalsPubSub {\n return new SignalsPubSub(resourceId, options);\n}\n"],"mappings":"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"}
@@ -52,7 +52,7 @@ async function replaceShellOutput(template, workingDir) {
52
52
  cwd: workingDir,
53
53
  encoding: "utf-8",
54
54
  timeout: 3e4,
55
- maxBuffer: 1024 * 1024
55
+ maxBuffer: 1048576
56
56
  });
57
57
  result = result.replace(fullMatch, output.trim());
58
58
  } catch (error) {
@@ -1 +1 @@
1
- {"version":3,"file":"slash-command-processor.js","names":["fs","path"],"sources":["../../src/utils/slash-command-processor.ts"],"sourcesContent":["import { execSync } from 'node:child_process';\nimport { promises as fs } from 'node:fs';\nimport * as path from 'node:path';\nimport type { SlashCommandMetadata } from './slash-command-loader.js';\n\n/**\n * Process a slash command by replacing variables and executing shell commands\n */\nexport async function processSlashCommand(\n command: SlashCommandMetadata,\n args: string[],\n workingDir: string,\n): Promise<string> {\n const { result: withArgs, shouldAppendRawArgs } = replaceArguments(command.template, args);\n let result = withArgs;\n\n // Replace shell commands\n result = await replaceShellOutput(result, workingDir);\n\n // Replace file references\n result = await replaceFileReferences(result, workingDir);\n\n // Append raw args after shell/file processing to avoid executing user input\n if (shouldAppendRawArgs) {\n result = result.trimEnd() + `\\n\\nARGUMENTS: ${args.join(' ')}`;\n }\n\n return result;\n}\n\n/**\n * Replace argument variables in template\n * $ARGUMENTS - all arguments joined\n * $1, $2, etc. - positional arguments\n */\nfunction replaceArguments(template: string, args: string[]): { result: string; shouldAppendRawArgs: boolean } {\n let result = template;\n\n // Check if template references any argument variables\n const hasArgumentsVar = /\\$ARGUMENTS/.test(template);\n const hasPositionalVar = /\\$[1-9]\\d*/.test(template);\n\n // Replace $ARGUMENTS with all args joined\n result = result.replace(/\\$ARGUMENTS/g, args.join(' '));\n\n // Replace range arguments $1+, $2+, etc. before single positional arguments.\n args.forEach((_, index) => {\n const argNumber = index + 1;\n const pattern = new RegExp(`\\\\\\$${argNumber}\\\\+`, 'g');\n result = result.replace(pattern, args.slice(index).join(' '));\n });\n\n // Replace positional arguments $1, $2, etc.\n args.forEach((arg, index) => {\n const pattern = new RegExp(`\\\\\\$${index + 1}`, 'g');\n result = result.replace(pattern, arg);\n });\n\n // Clear unused positional and range arguments\n result = result.replace(/\\$[1-9]\\d*\\+?/g, '');\n\n return {\n result,\n shouldAppendRawArgs: !hasArgumentsVar && !hasPositionalVar && args.length > 0,\n };\n}\n\n/**\n * Replace shell command references with their output\n * Format: !`command`\n */\nasync function replaceShellOutput(template: string, workingDir: string): Promise<string> {\n const shellPattern = /!`([^`]+)`/g;\n const matches = [...template.matchAll(shellPattern)];\n\n let result = template;\n for (const match of matches) {\n const [fullMatch, command] = match;\n try {\n const output = execSync(command!, {\n cwd: workingDir,\n encoding: 'utf-8',\n timeout: 30000,\n maxBuffer: 1024 * 1024, // 1MB buffer\n });\n result = result.replace(fullMatch, output.trim());\n } catch (error) {\n console.error(`Error executing shell command \"${command}\":`, error);\n result = result.replace(fullMatch, `[Error: Failed to execute \"${command}\"]`);\n }\n }\n\n return result;\n}\n\n/**\n * Replace file references with file content\n * Formats: @filename, @path/to/file, @path\\to\\file, @C:\\path\\to\\file, or @C:/path/to/file\n * Spaces, quoted paths, and glob patterns are not supported.\n */\nasync function replaceFileReferences(template: string, workingDir: string): Promise<string> {\n const filePattern = /@((?:[A-Za-z]:[\\\\/])?[\\w./\\\\-]+)/g;\n const matches = [...template.matchAll(filePattern)];\n\n let result = template;\n for (const match of matches) {\n const [fullMatch, filePath] = match;\n try {\n const fullPath = resolveFileReferencePath(workingDir, filePath!);\n const content = await fs.readFile(fullPath, 'utf-8');\n result = result.replace(fullMatch, content);\n } catch {\n // Leave literal @mentions/search qualifiers such as @me intact when they do not resolve to files.\n }\n }\n\n return result;\n}\n\nfunction resolveFileReferencePath(workingDir: string, filePath: string): string {\n const usesWindowsPaths = [workingDir, filePath].some(value => /^[A-Za-z]:[\\\\/]/.test(value) || value.includes('\\\\'));\n const pathResolver = usesWindowsPaths ? path.win32 : path;\n\n return pathResolver.resolve(workingDir, filePath);\n}\n\n/**\n * Format a command for display in help/autocomplete\n */\nexport function formatCommandForDisplay(command: SlashCommandMetadata): string {\n const parts = [command.name];\n\n if (command.description) {\n parts.push(`- ${command.description}`);\n }\n\n return parts.join(' ');\n}\n\n/**\n * Group commands by namespace for display\n */\nexport function groupCommandsByNamespace(commands: SlashCommandMetadata[]): Map<string, SlashCommandMetadata[]> {\n const groups = new Map<string, SlashCommandMetadata[]>();\n\n for (const command of commands) {\n const namespace = command.namespace || command.name.split(':')[0] || 'general';\n\n if (!groups.has(namespace)) {\n groups.set(namespace, []);\n }\n\n groups.get(namespace)!.push(command);\n }\n\n return groups;\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"slash-command-processor.js","names":["fs","path"],"sources":["../../src/utils/slash-command-processor.ts"],"sourcesContent":["import { execSync } from 'node:child_process';\nimport { promises as fs } from 'node:fs';\nimport * as path from 'node:path';\nimport type { SlashCommandMetadata } from './slash-command-loader.js';\n\n/**\n * Process a slash command by replacing variables and executing shell commands\n */\nexport async function processSlashCommand(\n command: SlashCommandMetadata,\n args: string[],\n workingDir: string,\n): Promise<string> {\n const { result: withArgs, shouldAppendRawArgs } = replaceArguments(command.template, args);\n let result = withArgs;\n\n // Replace shell commands\n result = await replaceShellOutput(result, workingDir);\n\n // Replace file references\n result = await replaceFileReferences(result, workingDir);\n\n // Append raw args after shell/file processing to avoid executing user input\n if (shouldAppendRawArgs) {\n result = result.trimEnd() + `\\n\\nARGUMENTS: ${args.join(' ')}`;\n }\n\n return result;\n}\n\n/**\n * Replace argument variables in template\n * $ARGUMENTS - all arguments joined\n * $1, $2, etc. - positional arguments\n */\nfunction replaceArguments(template: string, args: string[]): { result: string; shouldAppendRawArgs: boolean } {\n let result = template;\n\n // Check if template references any argument variables\n const hasArgumentsVar = /\\$ARGUMENTS/.test(template);\n const hasPositionalVar = /\\$[1-9]\\d*/.test(template);\n\n // Replace $ARGUMENTS with all args joined\n result = result.replace(/\\$ARGUMENTS/g, args.join(' '));\n\n // Replace range arguments $1+, $2+, etc. before single positional arguments.\n args.forEach((_, index) => {\n const argNumber = index + 1;\n const pattern = new RegExp(`\\\\\\$${argNumber}\\\\+`, 'g');\n result = result.replace(pattern, args.slice(index).join(' '));\n });\n\n // Replace positional arguments $1, $2, etc.\n args.forEach((arg, index) => {\n const pattern = new RegExp(`\\\\\\$${index + 1}`, 'g');\n result = result.replace(pattern, arg);\n });\n\n // Clear unused positional and range arguments\n result = result.replace(/\\$[1-9]\\d*\\+?/g, '');\n\n return {\n result,\n shouldAppendRawArgs: !hasArgumentsVar && !hasPositionalVar && args.length > 0,\n };\n}\n\n/**\n * Replace shell command references with their output\n * Format: !`command`\n */\nasync function replaceShellOutput(template: string, workingDir: string): Promise<string> {\n const shellPattern = /!`([^`]+)`/g;\n const matches = [...template.matchAll(shellPattern)];\n\n let result = template;\n for (const match of matches) {\n const [fullMatch, command] = match;\n try {\n const output = execSync(command!, {\n cwd: workingDir,\n encoding: 'utf-8',\n timeout: 30000,\n maxBuffer: 1024 * 1024, // 1MB buffer\n });\n result = result.replace(fullMatch, output.trim());\n } catch (error) {\n console.error(`Error executing shell command \"${command}\":`, error);\n result = result.replace(fullMatch, `[Error: Failed to execute \"${command}\"]`);\n }\n }\n\n return result;\n}\n\n/**\n * Replace file references with file content\n * Formats: @filename, @path/to/file, @path\\to\\file, @C:\\path\\to\\file, or @C:/path/to/file\n * Spaces, quoted paths, and glob patterns are not supported.\n */\nasync function replaceFileReferences(template: string, workingDir: string): Promise<string> {\n const filePattern = /@((?:[A-Za-z]:[\\\\/])?[\\w./\\\\-]+)/g;\n const matches = [...template.matchAll(filePattern)];\n\n let result = template;\n for (const match of matches) {\n const [fullMatch, filePath] = match;\n try {\n const fullPath = resolveFileReferencePath(workingDir, filePath!);\n const content = await fs.readFile(fullPath, 'utf-8');\n result = result.replace(fullMatch, content);\n } catch {\n // Leave literal @mentions/search qualifiers such as @me intact when they do not resolve to files.\n }\n }\n\n return result;\n}\n\nfunction resolveFileReferencePath(workingDir: string, filePath: string): string {\n const usesWindowsPaths = [workingDir, filePath].some(value => /^[A-Za-z]:[\\\\/]/.test(value) || value.includes('\\\\'));\n const pathResolver = usesWindowsPaths ? path.win32 : path;\n\n return pathResolver.resolve(workingDir, filePath);\n}\n\n/**\n * Format a command for display in help/autocomplete\n */\nexport function formatCommandForDisplay(command: SlashCommandMetadata): string {\n const parts = [command.name];\n\n if (command.description) {\n parts.push(`- ${command.description}`);\n }\n\n return parts.join(' ');\n}\n\n/**\n * Group commands by namespace for display\n */\nexport function groupCommandsByNamespace(commands: SlashCommandMetadata[]): Map<string, SlashCommandMetadata[]> {\n const groups = new Map<string, SlashCommandMetadata[]>();\n\n for (const command of commands) {\n const namespace = command.namespace || command.name.split(':')[0] || 'general';\n\n if (!groups.has(namespace)) {\n groups.set(namespace, []);\n }\n\n groups.get(namespace)!.push(command);\n }\n\n return groups;\n}\n"],"mappings":"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"}
@@ -2,7 +2,7 @@ import * as fs$1 from "fs";
2
2
  import * as tty from "tty";
3
3
  //#region src/utils/stdin-pipe.ts
4
4
  /** Maximum piped input size (1 MB). Content beyond this is truncated. */
5
- const MAX_PIPE_BYTES = 1024 * 1024;
5
+ const MAX_PIPE_BYTES = 1048576;
6
6
  /**
7
7
  * Matches all ANSI escape sequences — SGR (colors), cursor movement, line
8
8
  * clears, scrolling, window titles, etc.
@@ -1 +1 @@
1
- {"version":3,"file":"stdin-pipe.js","names":["fs"],"sources":["../../src/utils/stdin-pipe.ts"],"sourcesContent":["import * as fs from 'node:fs';\nimport * as tty from 'node:tty';\n\n/** Maximum piped input size (1 MB). Content beyond this is truncated. */\nconst MAX_PIPE_BYTES = 1024 * 1024;\n\n/**\n * Matches all ANSI escape sequences — SGR (colors), cursor movement, line\n * clears, scrolling, window titles, etc.\n */\nconst ANSI_RE = /\\x1b(?:\\[[0-9;?]*[A-Za-z]|\\][^\\x07\\x1b]*(?:\\x07|\\x1b\\\\)?|\\([A-Z])/g;\n\n/**\n * Clean up raw piped output so the agent sees readable text, not terminal\n * control noise.\n *\n * 1. Strip all ANSI escape sequences (colors, cursor movement, line clears).\n * 2. Simulate carriage-return overwrites: for each line, split on `\\r` and\n * keep only the last segment — this is what the terminal would display\n * after a spinner/progress bar finishes.\n * 3. Collapse runs of blank lines.\n */\nexport function sanitizePipedOutput(raw: string): string {\n // Strip ANSI escapes first\n let text = raw.replace(ANSI_RE, '');\n\n // Strip binary control characters (everything below 0x20 except \\t, \\n, \\r)\n text = text.replace(/[\\x00-\\x08\\x0B\\x0C\\x0E-\\x1F\\x7F]/g, '');\n\n // Simulate \\r overwrites: for each line, the last \\r-segment wins\n text = text\n .split('\\n')\n .map(line => {\n if (!line.includes('\\r')) return line;\n const segments = line.split('\\r');\n // Walk backwards to find the last non-empty segment — a trailing \\r\n // produces an empty final segment that would otherwise discard visible text.\n for (let i = segments.length - 1; i >= 0; i--) {\n if (segments[i]!.length > 0) return segments[i]!;\n }\n return '';\n })\n .join('\\n');\n\n // Collapse 3+ consecutive blank lines into 2\n text = text.replace(/\\n{3,}/g, '\\n\\n');\n\n return text.trim();\n}\n\n/**\n * If stdin is a pipe (not a TTY), read **all** data until EOF and return it as\n * a string. The returned promise only resolves once the write end of the pipe\n * is closed — i.e. the sending process has fully exited — so callers are\n * guaranteed to receive the complete output, not a partial snapshot from a\n * program that writes progressively.\n *\n * Returns `null` when:\n * - stdin is a TTY (interactive terminal)\n * - the pipe was empty or contained only whitespace\n */\nexport async function drainPipedStdin(): Promise<string | null> {\n if (process.stdin.isTTY) return null;\n\n const chunks: Buffer[] = [];\n let totalBytes = 0;\n let truncated = false;\n\n for await (const chunk of process.stdin) {\n const buf = chunk as Buffer;\n\n if (totalBytes + buf.length > MAX_PIPE_BYTES) {\n // Take only what fits under the cap — copy so we don't retain the\n // original backing buffer, then zero the source.\n const remaining = MAX_PIPE_BYTES - totalBytes;\n if (remaining > 0) {\n chunks.push(Buffer.from(buf.subarray(0, remaining)));\n }\n buf.fill(0);\n totalBytes = MAX_PIPE_BYTES;\n truncated = true;\n // Keep draining so we don't leave unread data in the pipe, but don't\n // store it — we still need to wait for EOF.\n continue;\n }\n\n chunks.push(buf);\n totalBytes += buf.length;\n }\n\n if (truncated) {\n process.stderr.write(`Warning: Piped input exceeded ${MAX_PIPE_BYTES / 1024 / 1024}MB and was truncated.\\n`);\n }\n\n const raw = Buffer.concat(chunks).toString('utf-8');\n\n // Zero out raw buffers so piped secrets don't linger in memory\n for (const buf of chunks) {\n buf.fill(0);\n }\n chunks.length = 0;\n\n const content = sanitizePipedOutput(raw);\n return content.length > 0 ? content : null;\n}\n\n/**\n * Reopen `/dev/tty` (or `CON` on Windows) as `process.stdin` so that the\n * interactive TUI can read keyboard input after the original piped stdin has\n * been consumed.\n *\n * @returns `true` if the swap succeeded, `false` if a TTY could not be opened\n * (e.g. running in a headless CI container with no controlling terminal).\n */\nexport function reopenStdinFromTTY(): boolean {\n const ttyPath = process.platform === 'win32' ? 'CON' : '/dev/tty';\n\n let fd: number;\n try {\n fd = fs.openSync(ttyPath, 'r');\n } catch {\n return false;\n }\n\n const ttyStream = new tty.ReadStream(fd);\n\n Object.defineProperty(process, 'stdin', {\n value: ttyStream,\n writable: true,\n configurable: true,\n });\n\n return true;\n}\n"],"mappings":";;;;AAIA,MAAM,iBAAiB,OAAO;;;;;AAM9B,MAAM,UAAU;;;;;;;;;;;AAYhB,SAAgB,oBAAoB,KAAqB;CAEvD,IAAI,OAAO,IAAI,QAAQ,SAAS,EAAE;CAGlC,OAAO,KAAK,QAAQ,qCAAqC,EAAE;CAG3D,OAAO,KACJ,MAAM,IAAI,CAAC,CACX,KAAI,SAAQ;EACX,IAAI,CAAC,KAAK,SAAS,IAAI,GAAG,OAAO;EACjC,MAAM,WAAW,KAAK,MAAM,IAAI;EAGhC,KAAK,IAAI,IAAI,SAAS,SAAS,GAAG,KAAK,GAAG,KACxC,IAAI,SAAS,EAAE,CAAE,SAAS,GAAG,OAAO,SAAS;EAE/C,OAAO;CACT,CAAC,CAAC,CACD,KAAK,IAAI;CAGZ,OAAO,KAAK,QAAQ,WAAW,MAAM;CAErC,OAAO,KAAK,KAAK;AACnB;;;;;;;;;;;;AAaA,eAAsB,kBAA0C;CAC9D,IAAI,QAAQ,MAAM,OAAO,OAAO;CAEhC,MAAM,SAAmB,CAAC;CAC1B,IAAI,aAAa;CACjB,IAAI,YAAY;CAEhB,WAAW,MAAM,SAAS,QAAQ,OAAO;EACvC,MAAM,MAAM;EAEZ,IAAI,aAAa,IAAI,SAAS,gBAAgB;GAG5C,MAAM,YAAY,iBAAiB;GACnC,IAAI,YAAY,GACd,OAAO,KAAK,OAAO,KAAK,IAAI,SAAS,GAAG,SAAS,CAAC,CAAC;GAErD,IAAI,KAAK,CAAC;GACV,aAAa;GACb,YAAY;GAGZ;EACF;EAEA,OAAO,KAAK,GAAG;EACf,cAAc,IAAI;CACpB;CAEA,IAAI,WACF,QAAQ,OAAO,MAAM,iCAAiC,iBAAiB,OAAO,KAAK,wBAAwB;CAG7G,MAAM,MAAM,OAAO,OAAO,MAAM,CAAC,CAAC,SAAS,OAAO;CAGlD,KAAK,MAAM,OAAO,QAChB,IAAI,KAAK,CAAC;CAEZ,OAAO,SAAS;CAEhB,MAAM,UAAU,oBAAoB,GAAG;CACvC,OAAO,QAAQ,SAAS,IAAI,UAAU;AACxC;;;;;;;;;AAUA,SAAgB,qBAA8B;CAC5C,MAAM,UAAU,QAAQ,aAAa,UAAU,QAAQ;CAEvD,IAAI;CACJ,IAAI;EACF,KAAKA,KAAG,SAAS,SAAS,GAAG;CAC/B,QAAQ;EACN,OAAO;CACT;CAEA,MAAM,YAAY,IAAI,IAAI,WAAW,EAAE;CAEvC,OAAO,eAAe,SAAS,SAAS;EACtC,OAAO;EACP,UAAU;EACV,cAAc;CAChB,CAAC;CAED,OAAO;AACT"}
1
+ {"version":3,"file":"stdin-pipe.js","names":["fs"],"sources":["../../src/utils/stdin-pipe.ts"],"sourcesContent":["import * as fs from 'node:fs';\nimport * as tty from 'node:tty';\n\n/** Maximum piped input size (1 MB). Content beyond this is truncated. */\nconst MAX_PIPE_BYTES = 1024 * 1024;\n\n/**\n * Matches all ANSI escape sequences — SGR (colors), cursor movement, line\n * clears, scrolling, window titles, etc.\n */\nconst ANSI_RE = /\\x1b(?:\\[[0-9;?]*[A-Za-z]|\\][^\\x07\\x1b]*(?:\\x07|\\x1b\\\\)?|\\([A-Z])/g;\n\n/**\n * Clean up raw piped output so the agent sees readable text, not terminal\n * control noise.\n *\n * 1. Strip all ANSI escape sequences (colors, cursor movement, line clears).\n * 2. Simulate carriage-return overwrites: for each line, split on `\\r` and\n * keep only the last segment — this is what the terminal would display\n * after a spinner/progress bar finishes.\n * 3. Collapse runs of blank lines.\n */\nexport function sanitizePipedOutput(raw: string): string {\n // Strip ANSI escapes first\n let text = raw.replace(ANSI_RE, '');\n\n // Strip binary control characters (everything below 0x20 except \\t, \\n, \\r)\n text = text.replace(/[\\x00-\\x08\\x0B\\x0C\\x0E-\\x1F\\x7F]/g, '');\n\n // Simulate \\r overwrites: for each line, the last \\r-segment wins\n text = text\n .split('\\n')\n .map(line => {\n if (!line.includes('\\r')) return line;\n const segments = line.split('\\r');\n // Walk backwards to find the last non-empty segment — a trailing \\r\n // produces an empty final segment that would otherwise discard visible text.\n for (let i = segments.length - 1; i >= 0; i--) {\n if (segments[i]!.length > 0) return segments[i]!;\n }\n return '';\n })\n .join('\\n');\n\n // Collapse 3+ consecutive blank lines into 2\n text = text.replace(/\\n{3,}/g, '\\n\\n');\n\n return text.trim();\n}\n\n/**\n * If stdin is a pipe (not a TTY), read **all** data until EOF and return it as\n * a string. The returned promise only resolves once the write end of the pipe\n * is closed — i.e. the sending process has fully exited — so callers are\n * guaranteed to receive the complete output, not a partial snapshot from a\n * program that writes progressively.\n *\n * Returns `null` when:\n * - stdin is a TTY (interactive terminal)\n * - the pipe was empty or contained only whitespace\n */\nexport async function drainPipedStdin(): Promise<string | null> {\n if (process.stdin.isTTY) return null;\n\n const chunks: Buffer[] = [];\n let totalBytes = 0;\n let truncated = false;\n\n for await (const chunk of process.stdin) {\n const buf = chunk as Buffer;\n\n if (totalBytes + buf.length > MAX_PIPE_BYTES) {\n // Take only what fits under the cap — copy so we don't retain the\n // original backing buffer, then zero the source.\n const remaining = MAX_PIPE_BYTES - totalBytes;\n if (remaining > 0) {\n chunks.push(Buffer.from(buf.subarray(0, remaining)));\n }\n buf.fill(0);\n totalBytes = MAX_PIPE_BYTES;\n truncated = true;\n // Keep draining so we don't leave unread data in the pipe, but don't\n // store it — we still need to wait for EOF.\n continue;\n }\n\n chunks.push(buf);\n totalBytes += buf.length;\n }\n\n if (truncated) {\n process.stderr.write(`Warning: Piped input exceeded ${MAX_PIPE_BYTES / 1024 / 1024}MB and was truncated.\\n`);\n }\n\n const raw = Buffer.concat(chunks).toString('utf-8');\n\n // Zero out raw buffers so piped secrets don't linger in memory\n for (const buf of chunks) {\n buf.fill(0);\n }\n chunks.length = 0;\n\n const content = sanitizePipedOutput(raw);\n return content.length > 0 ? content : null;\n}\n\n/**\n * Reopen `/dev/tty` (or `CON` on Windows) as `process.stdin` so that the\n * interactive TUI can read keyboard input after the original piped stdin has\n * been consumed.\n *\n * @returns `true` if the swap succeeded, `false` if a TTY could not be opened\n * (e.g. running in a headless CI container with no controlling terminal).\n */\nexport function reopenStdinFromTTY(): boolean {\n const ttyPath = process.platform === 'win32' ? 'CON' : '/dev/tty';\n\n let fd: number;\n try {\n fd = fs.openSync(ttyPath, 'r');\n } catch {\n return false;\n }\n\n const ttyStream = new tty.ReadStream(fd);\n\n Object.defineProperty(process, 'stdin', {\n value: ttyStream,\n writable: true,\n configurable: true,\n });\n\n return true;\n}\n"],"mappings":";;;;AAIA,MAAM,iBAAiB;;;;;AAMvB,MAAM,UAAU;;;;;;;;;;;AAYhB,SAAgB,oBAAoB,KAAqB;CAEvD,IAAI,OAAO,IAAI,QAAQ,SAAS,EAAE;CAGlC,OAAO,KAAK,QAAQ,qCAAqC,EAAE;CAG3D,OAAO,KACJ,MAAM,IAAI,CAAC,CACX,KAAI,SAAQ;EACX,IAAI,CAAC,KAAK,SAAS,IAAI,GAAG,OAAO;EACjC,MAAM,WAAW,KAAK,MAAM,IAAI;EAGhC,KAAK,IAAI,IAAI,SAAS,SAAS,GAAG,KAAK,GAAG,KACxC,IAAI,SAAS,EAAE,CAAE,SAAS,GAAG,OAAO,SAAS;EAE/C,OAAO;CACT,CAAC,CAAC,CACD,KAAK,IAAI;CAGZ,OAAO,KAAK,QAAQ,WAAW,MAAM;CAErC,OAAO,KAAK,KAAK;AACnB;;;;;;;;;;;;AAaA,eAAsB,kBAA0C;CAC9D,IAAI,QAAQ,MAAM,OAAO,OAAO;CAEhC,MAAM,SAAmB,CAAC;CAC1B,IAAI,aAAa;CACjB,IAAI,YAAY;CAEhB,WAAW,MAAM,SAAS,QAAQ,OAAO;EACvC,MAAM,MAAM;EAEZ,IAAI,aAAa,IAAI,SAAS,gBAAgB;GAG5C,MAAM,YAAY,iBAAiB;GACnC,IAAI,YAAY,GACd,OAAO,KAAK,OAAO,KAAK,IAAI,SAAS,GAAG,SAAS,CAAC,CAAC;GAErD,IAAI,KAAK,CAAC;GACV,aAAa;GACb,YAAY;GAGZ;EACF;EAEA,OAAO,KAAK,GAAG;EACf,cAAc,IAAI;CACpB;CAEA,IAAI,WACF,QAAQ,OAAO,MAAM,iCAAiC,iBAAiB,OAAO,KAAK,wBAAwB;CAG7G,MAAM,MAAM,OAAO,OAAO,MAAM,CAAC,CAAC,SAAS,OAAO;CAGlD,KAAK,MAAM,OAAO,QAChB,IAAI,KAAK,CAAC;CAEZ,OAAO,SAAS;CAEhB,MAAM,UAAU,oBAAoB,GAAG;CACvC,OAAO,QAAQ,SAAS,IAAI,UAAU;AACxC;;;;;;;;;AAUA,SAAgB,qBAA8B;CAC5C,MAAM,UAAU,QAAQ,aAAa,UAAU,QAAQ;CAEvD,IAAI;CACJ,IAAI;EACF,KAAKA,KAAG,SAAS,SAAS,GAAG;CAC/B,QAAQ;EACN,OAAO;CACT;CAEA,MAAM,YAAY,IAAI,IAAI,WAAW,EAAE;CAEvC,OAAO,eAAe,SAAS,SAAS;EACtC,OAAO;EACP,UAAU;EACV,cAAc;CAChB,CAAC;CAED,OAAO;AACT"}
@@ -1 +1 @@
1
- {"version":3,"file":"storage-factory.d.ts","sourceRoot":"","sources":["../../src/utils/storage-factory.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,KAAK,EAAE,oBAAoB,EAAE,eAAe,EAAE,MAAM,sBAAsB,CAAC;AAClF,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,qBAAqB,CAAC;AAIxD,OAAO,KAAK,EAAE,aAAa,EAAmB,MAAM,cAAc,CAAC;AAInE,eAAO,MAAM,yBAAyB;;;CAGrC,CAAC;AAEF;;;GAGG;AACH,wBAAgB,gBAAgB,CAAC,IAAI,EAAE;IACrC,EAAE,CAAC,EAAE,MAAM,CAAC;IACZ,GAAG,EAAE,MAAM,CAAC;IACZ,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,SAAS,CAAC,EAAE,eAAe,CAAC;CAC7B,GAAG,oBAAoB,CAQvB;AAED,sEAAsE;AACtE,wBAAgB,iBAAiB,CAAC,IAAI,EAAE;IAAE,EAAE,CAAC,EAAE,MAAM,CAAC;IAAC,GAAG,EAAE,MAAM,CAAC;IAAC,SAAS,CAAC,EAAE,MAAM,CAAA;CAAE,GAAG,YAAY,CAMtG;AAED,MAAM,WAAW,aAAa;IAC5B,OAAO,EAAE,oBAAoB,CAAC;IAC9B,8DAA8D;IAC9D,OAAO,EAAE,QAAQ,GAAG,IAAI,CAAC;IACzB,kFAAkF;IAClF,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AAMD;;;;;GAKG;AACH,wBAAsB,aAAa,CAAC,MAAM,EAAE,aAAa,GAAG,OAAO,CAAC,aAAa,CAAC,CAUjF;AAyDD;;;;GAIG;AACH,wBAAsB,iBAAiB,CACrC,MAAM,EAAE,aAAa,EACrB,gBAAgB,GAAE,QAAQ,GAAG,IAAqB,GACjD,OAAO,CAAC,YAAY,GAAG,SAAS,CAAC,CAuBnC"}
1
+ {"version":3,"file":"storage-factory.d.ts","sourceRoot":"","sources":["../../src/utils/storage-factory.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,KAAK,EAAE,oBAAoB,EAAE,eAAe,EAAE,MAAM,sBAAsB,CAAC;AAClF,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,qBAAqB,CAAC;AAGxD,OAAO,KAAK,EAAE,aAAa,EAAmB,MAAM,cAAc,CAAC;AAInE,eAAO,MAAM,yBAAyB;;;CAGrC,CAAC;AAEF;;;GAGG;AACH,wBAAgB,gBAAgB,CAAC,IAAI,EAAE;IACrC,EAAE,CAAC,EAAE,MAAM,CAAC;IACZ,GAAG,EAAE,MAAM,CAAC;IACZ,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,SAAS,CAAC,EAAE,eAAe,CAAC;CAC7B,GAAG,oBAAoB,CAQvB;AAED,sEAAsE;AACtE,wBAAgB,iBAAiB,CAAC,IAAI,EAAE;IAAE,EAAE,CAAC,EAAE,MAAM,CAAC;IAAC,GAAG,EAAE,MAAM,CAAC;IAAC,SAAS,CAAC,EAAE,MAAM,CAAA;CAAE,GAAG,YAAY,CAMtG;AAED,MAAM,WAAW,aAAa;IAC5B,OAAO,EAAE,oBAAoB,CAAC;IAC9B,8DAA8D;IAC9D,OAAO,EAAE,QAAQ,GAAG,IAAI,CAAC;IACzB,kFAAkF;IAClF,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AAMD;;;;;GAKG;AACH,wBAAsB,aAAa,CAAC,MAAM,EAAE,aAAa,GAAG,OAAO,CAAC,aAAa,CAAC,CAUjF;AA2DD;;;;GAIG;AACH,wBAAsB,iBAAiB,CACrC,MAAM,EAAE,aAAa,EACrB,gBAAgB,GAAE,QAAQ,GAAG,IAAqB,GACjD,OAAO,CAAC,YAAY,GAAG,SAAS,CAAC,CAuBnC"}
@@ -1,7 +1,6 @@
1
1
  import { getDatabasePath, getVectorDatabasePath } from "./project.js";
2
2
  import { DEFAULT_RETENTION } from "./storage-maintenance.js";
3
3
  import { LibSQLStore, LibSQLVector } from "@mastra/libsql";
4
- import { PostgresStore } from "@mastra/pg";
5
4
  //#region src/utils/storage-factory.ts
6
5
  const MASTRA_CODE_LOCAL_PRAGMAS = {
7
6
  cacheSize: -128e3,
@@ -57,6 +56,7 @@ async function createPgStorage(config) {
57
56
  backend: "libsql",
58
57
  warning: "PostgreSQL backend selected but no connection info configured. Using LibSQL fallback. Set a connection string via /settings."
59
58
  };
59
+ const { PostgresStore } = await import("@mastra/pg");
60
60
  const base = {
61
61
  id: "mastra-code-storage",
62
62
  retention: DEFAULT_RETENTION,