@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.
- package/dist/acp/agent.js +2 -1
- package/dist/acp/agent.js.map +1 -1
- package/dist/acp/event-mapper.js +0 -2
- package/dist/acp/event-mapper.js.map +1 -1
- package/dist/acp.js +1 -2
- package/dist/agent-connections/registry.js.map +1 -1
- package/dist/agent-connections/signal-provider.js.map +1 -1
- package/dist/agent-connections/state-processor.js.map +1 -1
- package/dist/agent-connections/tools.js +26 -24
- package/dist/agent-connections/tools.js.map +1 -1
- package/dist/agents/instructions.js +3 -2
- package/dist/agents/instructions.js.map +1 -1
- package/dist/agents/mastracode-gateway.js +35 -27
- package/dist/agents/mastracode-gateway.js.map +1 -1
- package/dist/agents/memory.d.ts.map +1 -1
- package/dist/agents/memory.js +16 -2
- package/dist/agents/memory.js.map +1 -1
- package/dist/agents/prompts/agent-instructions.js +1 -1
- package/dist/agents/prompts/agent-instructions.js.map +1 -1
- package/dist/agents/prompts/index.js +3 -2
- package/dist/agents/prompts/index.js.map +1 -1
- package/dist/agents/prompts/plan.js +2 -1
- package/dist/agents/prompts/plan.js.map +1 -1
- package/dist/agents/sandbox-filesystem.d.ts +28 -0
- package/dist/agents/sandbox-filesystem.d.ts.map +1 -1
- package/dist/agents/sandbox-filesystem.js +81 -2
- package/dist/agents/sandbox-filesystem.js.map +1 -1
- package/dist/agents/tools.d.ts.map +1 -1
- package/dist/agents/tools.js +18 -12
- package/dist/agents/tools.js.map +1 -1
- package/dist/agents/workspace.js +2 -1
- package/dist/agents/workspace.js.map +1 -1
- package/dist/auth/account-rotation-processor.js +2 -1
- package/dist/auth/account-rotation-processor.js.map +1 -1
- package/dist/auth/providers/anthropic.js +2 -2
- package/dist/auth/providers/anthropic.js.map +1 -1
- package/dist/auth/providers/github-copilot.js +1 -1
- package/dist/auth/providers/github-copilot.js.map +1 -1
- package/dist/auth/providers/openai-codex.js +3 -2
- package/dist/auth/providers/openai-codex.js.map +1 -1
- package/dist/auth/providers/xai.js +1 -1
- package/dist/auth/providers/xai.js.map +1 -1
- package/dist/clipboard/index.js +5 -4
- package/dist/clipboard/index.js.map +1 -1
- package/dist/evals/context-builder.js +1 -3
- package/dist/evals/context-builder.js.map +1 -1
- package/dist/evals/scorers/outcome.js +2 -1
- package/dist/evals/scorers/outcome.js.map +1 -1
- package/dist/factory/remote-controller.js.map +1 -1
- package/dist/factory/remote-session.js +1 -3
- package/dist/factory/remote-session.js.map +1 -1
- package/dist/headless/cli.js +2 -1
- package/dist/headless/cli.js.map +1 -1
- package/dist/headless/run-mc.js +9 -9
- package/dist/headless/run-mc.js.map +1 -1
- package/dist/hooks/executor.js +3 -1
- package/dist/hooks/executor.js.map +1 -1
- package/dist/hooks/manager.js +40 -26
- package/dist/hooks/manager.js.map +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +14 -10
- package/dist/index.js.map +1 -1
- package/dist/knowledge-inspector.js +2 -2
- package/dist/knowledge-inspector.js.map +1 -1
- package/dist/lsp/server.js +4 -2
- package/dist/lsp/server.js.map +1 -1
- package/dist/mcp/manager.d.ts.map +1 -1
- package/dist/mcp/manager.js +14 -2
- package/dist/mcp/manager.js.map +1 -1
- package/dist/onboarding/settings.js +5 -2
- package/dist/onboarding/settings.js.map +1 -1
- package/dist/platform/index.js +5 -3
- package/dist/platform/index.js.map +1 -1
- package/dist/plugins/loader.js +3 -1
- package/dist/plugins/loader.js.map +1 -1
- package/dist/plugins/manager.js +2 -1
- package/dist/plugins/manager.js.map +1 -1
- package/dist/plugins/package-link.js.map +1 -1
- package/dist/plugins/scaffold.js +4 -2
- package/dist/plugins/scaffold.js.map +1 -1
- package/dist/plugins/signal-lane.js.map +1 -1
- package/dist/process-memory-diagnostics.js +2 -1
- package/dist/process-memory-diagnostics.js.map +1 -1
- package/dist/providers/amazon-bedrock-gateway.js +2 -1
- package/dist/providers/amazon-bedrock-gateway.js.map +1 -1
- package/dist/providers/amazon-bedrock.js +2 -2
- package/dist/providers/amazon-bedrock.js.map +1 -1
- package/dist/providers/claude-max.js +13 -9
- package/dist/providers/claude-max.js.map +1 -1
- package/dist/providers/github-copilot-gateway.js.map +1 -1
- package/dist/providers/github-copilot.js +13 -12
- package/dist/providers/github-copilot.js.map +1 -1
- package/dist/providers/openai-codex.js +14 -11
- package/dist/providers/openai-codex.js.map +1 -1
- package/dist/providers/provider-thinking.js.map +1 -1
- package/dist/providers/thinking-request.js +1 -1
- package/dist/providers/thinking-request.js.map +1 -1
- package/dist/schedules/run-script.js +1 -1
- package/dist/schedules/run-script.js.map +1 -1
- package/dist/schedules/scheduler.js.map +1 -1
- package/dist/tools/request-sandbox-access.js +7 -5
- package/dist/tools/request-sandbox-access.js.map +1 -1
- package/dist/tools/utils.js +3 -1
- package/dist/tools/utils.js.map +1 -1
- package/dist/tools/web-search.d.ts +13 -11
- package/dist/tools/web-search.d.ts.map +1 -1
- package/dist/tools/web-search.js +21 -15
- package/dist/tools/web-search.js.map +1 -1
- package/dist/utils/debug-log.js +2 -2
- package/dist/utils/debug-log.js.map +1 -1
- package/dist/utils/gateway-sync.js +1 -1
- package/dist/utils/gateway-sync.js.map +1 -1
- package/dist/utils/local-observability.d.ts +1 -1
- package/dist/utils/local-observability.d.ts.map +1 -1
- package/dist/utils/local-observability.js +1 -1
- package/dist/utils/local-observability.js.map +1 -1
- package/dist/utils/prune-cli.js +10 -8
- package/dist/utils/prune-cli.js.map +1 -1
- package/dist/utils/signals-pubsub.js.map +1 -1
- package/dist/utils/slash-command-processor.js +1 -1
- package/dist/utils/slash-command-processor.js.map +1 -1
- package/dist/utils/stdin-pipe.js +1 -1
- package/dist/utils/stdin-pipe.js.map +1 -1
- package/dist/utils/storage-factory.d.ts.map +1 -1
- package/dist/utils/storage-factory.js +1 -1
- package/dist/utils/storage-factory.js.map +1 -1
- package/dist/utils/storage-maintenance.js +1 -1
- package/dist/utils/storage-maintenance.js.map +1 -1
- package/dist/utils/update-check.js +2 -1
- package/dist/utils/update-check.js.map +1 -1
- package/dist/workflows/register-primitives.js +1 -1
- package/dist/workflows/register-primitives.js.map +1 -1
- package/package.json +12 -12
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"storage-factory.js","names":[],"sources":["../../src/utils/storage-factory.ts"],"sourcesContent":["/**\n * Storage factory — creates the appropriate storage backend based on resolved config.\n *\n * If PG is selected but fails to connect, falls back to LibSQL so the TUI\n * can start and the user can fix the connection via /settings.\n */\n\nimport type { MastraCompositeStore, RetentionConfig } from '@mastra/core/storage';\nimport type { MastraVector } from '@mastra/core/vector';\nimport { LibSQLStore, LibSQLVector } from '@mastra/libsql';\
|
|
1
|
+
{"version":3,"file":"storage-factory.js","names":[],"sources":["../../src/utils/storage-factory.ts"],"sourcesContent":["/**\n * Storage factory — creates the appropriate storage backend based on resolved config.\n *\n * If PG is selected but fails to connect, falls back to LibSQL so the TUI\n * can start and the user can fix the connection via /settings.\n */\n\nimport type { MastraCompositeStore, RetentionConfig } from '@mastra/core/storage';\nimport type { MastraVector } from '@mastra/core/vector';\nimport { LibSQLStore, LibSQLVector } from '@mastra/libsql';\n\nimport type { StorageConfig, PgStorageConfig } from './project.js';\nimport { getDatabasePath, getVectorDatabasePath } from './project.js';\nimport { DEFAULT_RETENTION } from './storage-maintenance.js';\n\nexport const MASTRA_CODE_LOCAL_PRAGMAS = {\n cacheSize: -128000,\n mmapSize: 536870912,\n};\n\n/**\n * Construct a LibSQL store for an arbitrary url/authToken, applying the same\n * local pragmas the default factory uses.\n */\nexport function buildLibSQLStore(opts: {\n id?: string;\n url: string;\n authToken?: string;\n retention?: RetentionConfig;\n}): MastraCompositeStore {\n return new LibSQLStore({\n id: opts.id ?? 'mastra-code-storage',\n url: opts.url,\n ...(opts.authToken ? { authToken: opts.authToken } : {}),\n ...(opts.retention ? { retention: opts.retention } : {}),\n localPragmas: MASTRA_CODE_LOCAL_PRAGMAS,\n });\n}\n\n/** Construct a LibSQL vector store for an arbitrary url/authToken. */\nexport function buildLibSQLVector(opts: { id?: string; url: string; authToken?: string }): MastraVector {\n return new LibSQLVector({\n id: opts.id ?? 'mastra-code-vectors',\n url: opts.url,\n ...(opts.authToken ? { authToken: opts.authToken } : {}),\n });\n}\n\nexport interface StorageResult {\n storage: MastraCompositeStore;\n /** The effective backend after any fallback logic has run. */\n backend: 'libsql' | 'pg';\n /** Non-null when PG was requested but failed — contains a user-facing warning. */\n warning?: string;\n}\n\nfunction createFallbackLibSQL(): MastraCompositeStore {\n return buildLibSQLStore({ url: `file:${getDatabasePath()}`, retention: DEFAULT_RETENTION });\n}\n\n/**\n * Create a storage instance from the resolved config.\n *\n * - `libsql` backend → LibSQLStore (always available)\n * - `pg` backend → PostgresStore, falls back to LibSQL on connection failure\n */\nexport async function createStorage(config: StorageConfig): Promise<StorageResult> {\n if (config.backend === 'pg') {\n return createPgStorage(config);\n }\n\n // Default: LibSQL\n return {\n storage: buildLibSQLStore({ url: config.url, authToken: config.authToken, retention: DEFAULT_RETENTION }),\n backend: 'libsql',\n };\n}\n\nasync function createPgStorage(config: PgStorageConfig): Promise<StorageResult> {\n // No connection info → fall back with guidance\n if (!config.connectionString && !config.host) {\n return {\n storage: createFallbackLibSQL(),\n backend: 'libsql',\n warning:\n 'PostgreSQL backend selected but no connection info configured. ' +\n 'Using LibSQL fallback. Set a connection string via /settings.',\n };\n }\n\n // Only Postgres users pay for loading the pg driver.\n const { PostgresStore } = await import('@mastra/pg');\n const base = {\n id: 'mastra-code-storage' as const,\n retention: DEFAULT_RETENTION,\n ...(config.schemaName ? { schemaName: config.schemaName } : {}),\n ...(config.disableInit ? { disableInit: config.disableInit } : {}),\n ...(config.skipDefaultIndexes ? { skipDefaultIndexes: config.skipDefaultIndexes } : {}),\n };\n\n const store = config.connectionString\n ? new PostgresStore({ ...base, connectionString: config.connectionString })\n : new PostgresStore({\n ...base,\n host: config.host!,\n port: config.port,\n database: config.database,\n user: config.user,\n password: config.password,\n });\n\n // Test the connection before committing — if it fails, fall back to LibSQL\n // so the user can fix the config via /settings.\n try {\n await store.init();\n } catch (err: any) {\n const msg = err?.message ?? String(err);\n const target = config.connectionString ?? `${config.host}:${config.port ?? 5432}`;\n try {\n await store.close();\n } catch {\n // ignore cleanup errors\n }\n return {\n storage: createFallbackLibSQL(),\n backend: 'libsql',\n warning:\n `Failed to connect to PostgreSQL at ${target}: ${msg}\\n` +\n 'Using LibSQL fallback. Fix the connection via /settings.',\n };\n }\n\n return { storage: store, backend: 'pg' };\n}\n\n/**\n * Create a vector store for recall search.\n * Uses a separate LibSQL file to avoid bloating the main storage DB with embedding data.\n * For PG backends, reuses the same connection (PG handles the extra tables fine).\n */\nexport async function createVectorStore(\n config: StorageConfig,\n effectiveBackend: 'libsql' | 'pg' = config.backend,\n): Promise<MastraVector | undefined> {\n if (effectiveBackend === 'pg') {\n // PG can handle vector tables in the same database\n const pgConfig = config as PgStorageConfig;\n if (!pgConfig.connectionString && !pgConfig.host) return undefined;\n\n const { PgVector } = await import('@mastra/pg');\n return new PgVector({\n id: 'mastra-code-vectors',\n connectionString:\n pgConfig.connectionString ??\n `postgresql://${pgConfig.user}:${pgConfig.password}@${pgConfig.host}:${pgConfig.port ?? 5432}/${pgConfig.database}`,\n });\n }\n\n // LibSQL: separate file for vectors. Per-tenant configs supply an explicit\n // vectorUrl so each tenant's recall vectors are isolated; otherwise use the\n // shared default file.\n const libsqlConfig = config as { vectorUrl?: string; vectorAuthToken?: string };\n return buildLibSQLVector({\n url: libsqlConfig.vectorUrl ?? `file:${getVectorDatabasePath()}`,\n authToken: libsqlConfig.vectorAuthToken,\n });\n}\n"],"mappings":";;;;AAeA,MAAa,4BAA4B;CACvC,WAAW;CACX,UAAU;AACZ;;;;;AAMA,SAAgB,iBAAiB,MAKR;CACvB,OAAO,IAAI,YAAY;EACrB,IAAI,KAAK,MAAM;EACf,KAAK,KAAK;EACV,GAAI,KAAK,YAAY,EAAE,WAAW,KAAK,UAAU,IAAI,CAAC;EACtD,GAAI,KAAK,YAAY,EAAE,WAAW,KAAK,UAAU,IAAI,CAAC;EACtD,cAAc;CAChB,CAAC;AACH;;AAGA,SAAgB,kBAAkB,MAAsE;CACtG,OAAO,IAAI,aAAa;EACtB,IAAI,KAAK,MAAM;EACf,KAAK,KAAK;EACV,GAAI,KAAK,YAAY,EAAE,WAAW,KAAK,UAAU,IAAI,CAAC;CACxD,CAAC;AACH;AAUA,SAAS,uBAA6C;CACpD,OAAO,iBAAiB;EAAE,KAAK,QAAQ,gBAAgB;EAAK,WAAW;CAAkB,CAAC;AAC5F;;;;;;;AAQA,eAAsB,cAAc,QAA+C;CACjF,IAAI,OAAO,YAAY,MACrB,OAAO,gBAAgB,MAAM;CAI/B,OAAO;EACL,SAAS,iBAAiB;GAAE,KAAK,OAAO;GAAK,WAAW,OAAO;GAAW,WAAW;EAAkB,CAAC;EACxG,SAAS;CACX;AACF;AAEA,eAAe,gBAAgB,QAAiD;CAE9E,IAAI,CAAC,OAAO,oBAAoB,CAAC,OAAO,MACtC,OAAO;EACL,SAAS,qBAAqB;EAC9B,SAAS;EACT,SACE;CAEJ;CAIF,MAAM,EAAE,kBAAkB,MAAM,OAAO;CACvC,MAAM,OAAO;EACX,IAAI;EACJ,WAAW;EACX,GAAI,OAAO,aAAa,EAAE,YAAY,OAAO,WAAW,IAAI,CAAC;EAC7D,GAAI,OAAO,cAAc,EAAE,aAAa,OAAO,YAAY,IAAI,CAAC;EAChE,GAAI,OAAO,qBAAqB,EAAE,oBAAoB,OAAO,mBAAmB,IAAI,CAAC;CACvF;CAEA,MAAM,QAAQ,OAAO,mBACjB,IAAI,cAAc;EAAE,GAAG;EAAM,kBAAkB,OAAO;CAAiB,CAAC,IACxE,IAAI,cAAc;EAChB,GAAG;EACH,MAAM,OAAO;EACb,MAAM,OAAO;EACb,UAAU,OAAO;EACjB,MAAM,OAAO;EACb,UAAU,OAAO;CACnB,CAAC;CAIL,IAAI;EACF,MAAM,MAAM,KAAK;CACnB,SAAS,KAAU;EACjB,MAAM,MAAM,KAAK,WAAW,OAAO,GAAG;EACtC,MAAM,SAAS,OAAO,oBAAoB,GAAG,OAAO,KAAK,GAAG,OAAO,QAAQ;EAC3E,IAAI;GACF,MAAM,MAAM,MAAM;EACpB,QAAQ,CAER;EACA,OAAO;GACL,SAAS,qBAAqB;GAC9B,SAAS;GACT,SACE,sCAAsC,OAAO,IAAI,IAAI;EAEzD;CACF;CAEA,OAAO;EAAE,SAAS;EAAO,SAAS;CAAK;AACzC;;;;;;AAOA,eAAsB,kBACpB,QACA,mBAAoC,OAAO,SACR;CACnC,IAAI,qBAAqB,MAAM;EAE7B,MAAM,WAAW;EACjB,IAAI,CAAC,SAAS,oBAAoB,CAAC,SAAS,MAAM,OAAO,KAAA;EAEzD,MAAM,EAAE,aAAa,MAAM,OAAO;EAClC,OAAO,IAAI,SAAS;GAClB,IAAI;GACJ,kBACE,SAAS,oBACT,gBAAgB,SAAS,KAAK,GAAG,SAAS,SAAS,GAAG,SAAS,KAAK,GAAG,SAAS,QAAQ,KAAK,GAAG,SAAS;EAC7G,CAAC;CACH;CAKA,MAAM,eAAe;CACrB,OAAO,kBAAkB;EACvB,KAAK,aAAa,aAAa,QAAQ,sBAAsB;EAC7D,WAAW,aAAa;CAC1B,CAAC;AACH"}
|
|
@@ -49,7 +49,7 @@ function fileSizeWithWal(dbFile) {
|
|
|
49
49
|
}
|
|
50
50
|
/** Free bytes required before compacting: the compacted copy plus 20% + 256 MB headroom. */
|
|
51
51
|
function requiredFreeBytes(liveBytes) {
|
|
52
|
-
return Math.ceil(liveBytes * 1.2) +
|
|
52
|
+
return Math.ceil(liveBytes * 1.2) + 268435456;
|
|
53
53
|
}
|
|
54
54
|
/** First column of the first row of a PRAGMA result, as a number. */
|
|
55
55
|
function pragmaNumber(db, pragma) {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"storage-maintenance.js","names":[],"sources":["../../src/utils/storage-maintenance.ts"],"sourcesContent":["/**\n * Storage maintenance — retention defaults and disk reclamation for mastracode.\n *\n * Retention (`DEFAULT_RETENTION`) is wired into the inner store by the storage\n * factory so `storage.prune()` can delete aged rows across every domain,\n * including the legacy libsql observability spans table.\n *\n * `prune()` only deletes rows — SQLite keeps the freed pages inside the file.\n * `reclaimLibSQLDisk()` returns that space to the OS by streaming a\n * `VACUUM INTO` copy of each file and swapping it into place. It requires all\n * connections to be closed first, so it only runs from the post-TUI\n * maintenance flow. We deliberately do NOT expose VACUUM as a public API on\n * LibSQLStore.\n */\n\nimport { closeSync, existsSync, openSync, readSync, renameSync, rmSync, statSync, statfsSync } from 'node:fs';\nimport { dirname } from 'node:path';\nimport { setFlagsFromString } from 'node:v8';\nimport { runInNewContext } from 'node:vm';\n\nimport type { MastraCompositeStore, PruneOptions, PruneResult, RetentionConfig } from '@mastra/core/storage';\n// Native `libsql` driver, deliberately alongside `@libsql/client` (used by the\n// stores): maintenance needs deterministic connection close for the\n// exclusivity probe, and the wrapper's close() can leave the file lock held\n// until GC finalizes cached statements. Don't \"clean up\" either dependency.\n// Upstream report: https://github.com/tursodatabase/libsql-js/issues/228\nimport Database from 'libsql';\n\nimport type { StorageConfig } from './project.js';\nimport { getDatabasePath, getVectorDatabasePath } from './project.js';\n\n/**\n * Default retention for mastracode's own storage. Tables not listed are kept\n * forever. Ages chosen for a local dev tool where observability data dwarfs\n * everything else (spans have been observed at 17+ GB):\n * - spans/logs: short-lived debugging telemetry → 14d\n * - scorer results & workflow snapshots: medium-lived run records → 30d\n * - chat history (messages/threads): long-lived, user-visible → 90d\n */\nexport const DEFAULT_RETENTION: RetentionConfig = {\n memory: {\n messages: { maxAge: '90d' },\n threads: { maxAge: '90d' },\n },\n observability: {\n spans: { maxAge: '14d' },\n logs: { maxAge: '14d' },\n },\n scores: {\n scorers: { maxAge: '30d' },\n },\n workflows: {\n workflowSnapshot: { maxAge: '30d' },\n },\n};\n\n/** Result of reclaiming disk for one database file (sizes include the WAL). */\nexport interface ReclaimResult {\n file: string;\n bytesBefore: number;\n bytesAfter: number;\n /**\n * Set when the file was left untouched instead of compacted.\n *\n * `'vector-index'`: the database carries a `libsql_vector_idx` index.\n * Vacuuming such a database corrupts its `libsql_vector_meta_shadow` table\n * (`PRAGMA integrity_check` → `row not in PRIMARY KEY order`), silently at\n * first, so we never compact it. See\n * https://github.com/mastra-ai/mastra/issues/23439.\n *\n * `'detection-failed'`: the schema could not be inspected, so we cannot\n * prove the database is safe to vacuum and skip it as a precaution.\n */\n skipped?: 'vector-index' | 'detection-failed';\n}\n\n/** Handle the TUI uses to run storage maintenance without reaching into store internals. */\nexport interface StorageMaintenance {\n backend: 'libsql' | 'pg';\n /** Retention policies in effect (for display in /prune output). */\n retention: RetentionConfig;\n /** Delete rows older than the retention policies. Safe to re-run; batched and cancellable. `options.retention` replaces the standing policies for this call only. */\n prune: (options?: PruneOptions) => Promise<PruneResult[]>;\n /** Close the long-lived storage connection (checkpoints WAL for local libsql). Nothing can use the store afterwards. */\n closeStorage?: () => Promise<void>;\n /** Compact local libsql files (VACUUM INTO + swap) to return freed pages to the OS. Only set for local libsql backends. */\n reclaimDisk?: (\n onFileStart?: (file: string, bytesBefore: number, liveBytes: number) => void,\n ) => Promise<ReclaimResult[]>;\n}\n\n/** db file size + its WAL sidecar, in bytes. Missing files count as 0. */\nfunction fileSizeWithWal(dbFile: string): number {\n let total = 0;\n for (const candidate of [dbFile, `${dbFile}-wal`]) {\n try {\n total += statSync(candidate).size;\n } catch {\n // file may not exist (e.g. WAL already truncated)\n }\n }\n return total;\n}\n\n/** Free bytes required before compacting: the compacted copy plus 20% + 256 MB headroom. */\nexport function requiredFreeBytes(liveBytes: number): number {\n return Math.ceil(liveBytes * 1.2) + 256 * 1024 * 1024;\n}\n\n/** First column of the first row of a PRAGMA result, as a number. */\nfunction pragmaNumber(db: InstanceType<typeof Database>, pragma: string): number {\n const rows = db.pragma(pragma) as Array<Record<string, unknown>> | undefined;\n const row = rows?.[0];\n return row ? Number(Object.values(row)[0]) : 0;\n}\n\n/**\n * Journal mode straight from the SQLite file header (bytes 18/19:\n * 1 = rollback journal, 2 = WAL) — readable without opening a connection.\n */\nfunction journalModeFromHeader(file: string): 'wal' | 'delete' | 'unknown' {\n const fd = openSync(file, 'r');\n try {\n const buf = Buffer.alloc(2);\n if (readSync(fd, buf, 0, 2, 18) !== 2) return 'unknown';\n if (buf[0] === 2 && buf[1] === 2) return 'wal';\n if (buf[0] === 1 && buf[1] === 1) return 'delete';\n return 'unknown';\n } finally {\n closeSync(fd);\n }\n}\n\n/**\n * Collect our own closed-but-unfinalized libsql connections before probing.\n *\n * libsql-js does not finalize prepared statements on `close()` (they are only\n * released by GC; tursodatabase/libsql-js#228), so a connection we already\n * closed can still hold the WAL lock and make the reclaim probe fail.\n *\n * `gc` is only global under `--expose-gc`, so we set that V8 flag at runtime\n * and read `gc` from a fresh VM context. The flag is process-wide, but it only\n * makes `gc` available to contexts created afterward (the main context's\n * globals are untouched) and changes no GC behavior. A forced\n * collection only frees unreachable objects. This runs only on the explicit\n * vacuum path (`mastracode prune --vacuum` or `/prune vacuum`), and both exit\n * the process right after, so the flag never outlives maintenance in practice.\n *\n * Remove this once libsql-js#228 lands and `close()` finalizes statements.\n */\nasync function releaseClosedConnections(): Promise<void> {\n // Let pending close() callbacks settle before collecting.\n await new Promise(resolve => setImmediate(resolve));\n const gc =\n (globalThis as { gc?: () => void }).gc ??\n (() => {\n setFlagsFromString('--expose-gc');\n return runInNewContext('gc') as () => void;\n })();\n // Native finalizers run after the collection; a second pass collects what they freed.\n gc();\n await new Promise(resolve => setTimeout(resolve, 50));\n gc();\n}\n\n/**\n * Does this database carry a libsql vector index?\n *\n * Takes an already-open connection on purpose: the query needs a prepared\n * statement, and a statement opened before the exclusivity probe pins the\n * connection past close() (libsql-js#228) and makes the probe's\n * `journal_mode = DELETE` fail with SQLITE_BUSY on files nobody else has open.\n * The caller's connection must therefore already be in rollback mode — a\n * pinned statement holds no lock there.\n *\n * Matched by schema, not filename — the main database can carry vector indexes\n * too. SQLite stores `CREATE INDEX` SQL verbatim, so the match is deliberately\n * loose (no trailing `(`): an index written `libsql_vector_idx (embedding)`\n * must not slip through. The two errors are not symmetric — a false positive\n * costs one uncompacted file that we tell the user about, a false negative\n * silently corrupts their vector index.\n */\nfunction hasVectorIndex(db: InstanceType<typeof Database>): boolean {\n const row = db\n .prepare(`SELECT 1 AS hit FROM sqlite_master WHERE type = 'index' AND sql LIKE '%libsql_vector_idx%' LIMIT 1`)\n .get() as Record<string, unknown> | undefined;\n return row !== undefined;\n}\n\n/**\n * Compact each local libsql db file by streaming a `VACUUM INTO` copy next to\n * it, then swapping the copy into place. Reports before/after sizes.\n *\n * Deliberately NOT an in-place `VACUUM`: that copies all live content into a\n * temp store (held in memory by libsql builds with `temp_store=MEMORY`) and\n * then rewrites the entire database back through the WAL — on multi-GB\n * databases that has frozen whole machines via memory/IO exhaustion.\n * `VACUUM INTO` reads the logical database once and writes the compacted copy\n * sequentially with bounded memory, and the WAL never grows.\n *\n * Each file is preflighted: live bytes are estimated from\n * `page_count - freelist_count` and the copy is refused unless the volume has\n * `requiredFreeBytes()` available.\n *\n * MUST run with every connection to these files closed (the swap replaces the\n * inode — a surviving connection would keep writing to the unlinked old file).\n * `runStorageMaintenance()` closes storage before calling this.\n *\n * Databases carrying a `libsql_vector_idx` index are skipped, not compacted:\n * any VACUUM over one corrupts its `libsql_vector_meta_shadow` table (issue\n * #23439). Detection fails closed — a file we cannot inspect is skipped too.\n * Skipped files come back with `skipped` set so the caller can report them.\n */\nexport async function reclaimLibSQLDisk(\n dbFiles: string[],\n onFileStart?: (file: string, bytesBefore: number, liveBytes: number) => void,\n): Promise<ReclaimResult[]> {\n const results: ReclaimResult[] = [];\n for (const file of dbFiles) {\n // Skip missing files and files too small to have a SQLite header — nothing to reclaim\n if (!existsSync(file) || statSync(file).size < 100) continue;\n const bytesBefore = fileSizeWithWal(file);\n const tmp = `${file}.vacuum-tmp`;\n rmSync(tmp, { force: true });\n // Prove we're the only connection before the swap: every open WAL-mode\n // connection (all Mastra Code sessions run WAL) holds a shared-memory\n // lock, so switching WAL -> DELETE only succeeds with exclusive access.\n // A surviving connection would keep writing to the unlinked old inode\n // after the swap and silently lose data.\n //\n // The probe uses exec() only and verifies the outcome from the SQLite\n // file header: prepared statements (including db.pragma()) pin a libsql\n // connection open past close(), so a failed in-connection probe would\n // itself hold the WAL lock and poison every retry in this process.\n // Upstream bug (libsql 0.5.x / @libsql/client 0.17.x): Database.close()\n // does not finalize outstanding statements — they are only finalized by\n // GC, so lock release after close() is nondeterministic. Reported as\n // https://github.com/tursodatabase/libsql-js/issues/228 (fix in flight in\n // PR #214). Once that lands, the header check can become a plain\n // journal_mode query.\n //\n // The same bug means the caller's own storage connection, closed by\n // closeStorage(), still holds the lock until its statements are\n // finalized; collect them first so the probe doesn't see ourselves.\n await releaseClosedConnections();\n const probe = new Database(file);\n try {\n probe.exec('PRAGMA busy_timeout = 2000');\n // Round-trip through WAL so the probe also works when a clean shutdown\n // already left the file in rollback mode.\n probe.exec('PRAGMA journal_mode = WAL');\n probe.exec('PRAGMA journal_mode = DELETE');\n } catch {\n // busy — the header check below reports the failure\n } finally {\n probe.close();\n }\n if (journalModeFromHeader(file) !== 'delete') {\n throw new Error(\n `${file} is in use by another process — is another Mastra Code session running? ` +\n `Close other sessions, then run /prune vacuum (or mastracode prune --vacuum) again.`,\n );\n }\n // Native `libsql` driver, not `@libsql/client`: the wrapper's close() can\n // leave the file lock held in-process (cached statements), which would\n // block other connections afterwards. This connection stays in rollback\n // mode, so even a pinned statement holds no lock once we're done.\n const db = new Database(file);\n try {\n db.exec('PRAGMA busy_timeout = 2000');\n // First, before any check that can throw: a full disk must not abort the\n // whole loop over a file we were going to leave alone anyway.\n let skipped: ReclaimResult['skipped'];\n try {\n if (hasVectorIndex(db)) skipped = 'vector-index';\n } catch {\n // Fail closed: we could not establish that this file is safe to vacuum.\n skipped = 'detection-failed';\n }\n if (skipped) {\n results.push({ file, bytesBefore, bytesAfter: bytesBefore, skipped });\n continue;\n }\n const pageSize = pragmaNumber(db, 'page_size');\n const pageCount = pragmaNumber(db, 'page_count');\n const freelistCount = pragmaNumber(db, 'freelist_count');\n const liveBytes = Math.max(0, pageCount - freelistCount) * pageSize;\n const { bsize, bavail } = statfsSync(dirname(file));\n const freeBytes = bsize * bavail;\n if (freeBytes < requiredFreeBytes(liveBytes)) {\n throw new Error(\n `Not enough free disk space to compact ${file}: need ${formatBytes(requiredFreeBytes(liveBytes))} ` +\n `for a ${formatBytes(liveBytes)} compacted copy, but only ${formatBytes(freeBytes)} is free.`,\n );\n }\n onFileStart?.(file, bytesBefore, liveBytes);\n db.exec(`VACUUM INTO '${tmp.replace(/'/g, \"''\")}'`);\n } catch (err) {\n rmSync(tmp, { force: true });\n throw err;\n } finally {\n db.close();\n }\n // Re-probe right before the swap: `VACUUM INTO` can take tens of seconds\n // on multi-GB files, and a session started in that window reopens the db\n // in WAL mode (flipping the header back). Swapping under it would orphan\n // that session's inode and silently lose its writes.\n if (journalModeFromHeader(file) !== 'delete') {\n rmSync(tmp, { force: true });\n throw new Error(\n `${file} was opened by another process during compaction — is another Mastra Code session running? ` +\n `Close other sessions, then run /prune vacuum (or mastracode prune --vacuum) again.`,\n );\n }\n // Swap the compacted copy into place. The old WAL/SHM sidecars belong to\n // the old inode — they must never be paired with the new file. If any step\n // after the first rename fails, restore the original so the db path is\n // never left empty (a naive restart would otherwise create a fresh db).\n renameSync(file, `${file}.old`);\n try {\n rmSync(`${file}-wal`, { force: true });\n rmSync(`${file}-shm`, { force: true });\n renameSync(tmp, file);\n } catch (err) {\n try {\n renameSync(`${file}.old`, file);\n } catch {\n throw new Error(\n `Failed to swap compacted copy into place AND failed to restore the original — ` +\n `your data is intact at ${file}.old; rename it back to ${file} manually. ` +\n `Original error: ${err instanceof Error ? err.message : String(err)}`,\n );\n }\n rmSync(tmp, { force: true });\n throw err;\n }\n rmSync(`${file}.old`, { force: true });\n results.push({ file, bytesBefore, bytesAfter: fileSizeWithWal(file) });\n }\n return results;\n}\n\n/** `file:/path` or `file:///path` → filesystem path; undefined for non-file urls. */\nfunction fileUrlToPath(url: string): string | undefined {\n if (!url.startsWith('file:')) return undefined;\n return url.replace(/^file:\\/\\//, '').replace(/^file:/, '');\n}\n\n/**\n * Resolve the local libsql db files eligible for checkpoint + VACUUM.\n * Returns [] for remote (turso) urls and PG backends. When PG was requested\n * but the factory fell back to libsql, the fallback uses the default files.\n */\nexport function resolveLocalDbFiles(config: StorageConfig, effectiveBackend: 'libsql' | 'pg'): string[] {\n if (effectiveBackend !== 'libsql') return [];\n if (config.backend === 'pg') {\n // PG requested but fell back to the default local libsql files\n return [getDatabasePath(), getVectorDatabasePath()];\n }\n if (config.isRemote) return [];\n const files: string[] = [];\n const main = fileUrlToPath(config.url);\n if (main) files.push(main);\n const vector = config.vectorUrl ? fileUrlToPath(config.vectorUrl) : getVectorDatabasePath();\n if (vector) files.push(vector);\n return files;\n}\n\n/**\n * Build the maintenance handle for the effective storage backend.\n * `localDbFiles` are the local libsql file paths eligible for VACUUM;\n * pass an empty array for remote (turso) or PG backends.\n */\nexport function createStorageMaintenance(opts: {\n storage: MastraCompositeStore;\n backend: 'libsql' | 'pg';\n retention: RetentionConfig;\n localDbFiles: string[];\n /** Closes the vector store's connection; required for local libsql so the compaction swap isn't blocked by our own handle. */\n closeVector?: () => Promise<void>;\n}): StorageMaintenance {\n const { storage, backend, retention, localDbFiles, closeVector } = opts;\n return {\n backend,\n retention,\n prune: options => storage.prune(options),\n closeStorage: async () => {\n await storage.close?.();\n await closeVector?.();\n },\n ...(backend === 'libsql' && localDbFiles.length > 0\n ? {\n reclaimDisk: (onFileStart?: (file: string, bytesBefore: number, liveBytes: number) => void) =>\n reclaimLibSQLDisk(localDbFiles, onFileStart),\n }\n : {}),\n };\n}\n\nexport function formatBytes(bytes: number): string {\n if (bytes < 1024) return `${bytes} B`;\n const units = ['KB', 'MB', 'GB', 'TB'];\n let value = bytes;\n let unit = 'B';\n for (const next of units) {\n if (value < 1024) break;\n value /= 1024;\n unit = next;\n }\n return `${value.toFixed(1)} ${unit}`;\n}\n\n/** Rows deleted per table per pass: default batchSize (1000) × 20 batches. */\nconst PASS_MAX_BATCHES = 20;\n\n/**\n * Run storage maintenance with exclusive access to the database, reporting\n * progress through `log` (one line per pass so large deletes are visible).\n *\n * Must run AFTER the TUI is stopped and background writers are quiesced —\n * batched deletes and VACUUM contend with live writes (SQLITE_BUSY) and can\n * starve the event loop that renders the TUI. The storage connection is\n * closed at the end (checkpointing the WAL for local libsql), so the process\n * must exit afterwards.\n */\nexport async function runStorageMaintenance(opts: {\n maintenance: StorageMaintenance;\n vacuum: boolean;\n /** Skip the memory domain (messages/threads) so chat history is preserved. */\n keepMemory?: boolean;\n log: (line: string) => void;\n}): Promise<void> {\n const { maintenance, vacuum, keepMemory = false, log } = opts;\n\n // keep-memory: prune with the standing policies minus the memory domain,\n // for this run only. The configured retention is untouched.\n const { memory: _memory, ...withoutMemory } = maintenance.retention;\n const retentionForRun = keepMemory ? { retention: withoutMemory } : {};\n\n log('Pruning rows older than the retention policies:');\n for (const [domain, tables] of Object.entries(maintenance.retention)) {\n for (const [table, policy] of Object.entries(tables ?? {})) {\n const kept = keepMemory && domain === 'memory';\n log(` ${domain}.${table}: ${kept ? 'kept (keep-memory)' : (policy as { maxAge: string | number }).maxAge}`);\n }\n }\n\n const totals = new Map<string, number>();\n for (;;) {\n const results = await maintenance.prune({ maxBatches: PASS_MAX_BATCHES, ...retentionForRun });\n if (results.length === 0) break;\n let remaining = false;\n let deletedThisPass = 0;\n for (const r of results) {\n const key = `${r.domain}.${r.table}`;\n totals.set(key, (totals.get(key) ?? 0) + r.deleted);\n deletedThisPass += r.deleted;\n if (!r.done) remaining = true;\n if (r.deleted > 0) {\n log(` ${key}: ${totals.get(key)} rows deleted${r.done ? '' : ' so far…'}`);\n }\n }\n if (!remaining) break;\n // Safety valve: a table reported more eligible rows but nothing was\n // deleted this pass — bail instead of spinning forever.\n if (deletedThisPass === 0) {\n log(' stopping: no progress in the last pass.');\n break;\n }\n }\n const totalDeleted = [...totals.values()].reduce((sum, n) => sum + n, 0);\n log(\n totalDeleted === 0\n ? 'Nothing to prune — no retention-eligible rows found.'\n : `Prune complete: ${totalDeleted} rows deleted.`,\n );\n\n // Release the long-lived connection: checkpoints the WAL for local libsql\n // and guarantees VACUUM below isn't blocked by our own open handles.\n await maintenance.closeStorage?.();\n\n if (!vacuum) {\n if (maintenance.reclaimDisk) {\n log(\n 'Deleted rows free pages inside the db file but not on disk. Run /prune vacuum (or mastracode prune --vacuum) to reclaim disk space.',\n );\n }\n return;\n }\n\n if (!maintenance.reclaimDisk) {\n log('Disk reclamation (VACUUM) is only available for local libsql storage.');\n return;\n }\n\n log('Reclaiming disk (compacting database files)… this can take several minutes on large databases.');\n const reclaimed = await maintenance.reclaimDisk((file, bytesBefore, liveBytes) => {\n log(` vacuuming ${file} (${formatBytes(bytesBefore)}, ~${formatBytes(liveBytes)} live)…`);\n });\n if (reclaimed.length === 0) {\n log('No local database files found to vacuum.');\n return;\n }\n for (const r of reclaimed) {\n if (r.skipped === 'vector-index') {\n log(` ${r.file}: skipped — it contains a vector index, and compacting it would corrupt that index.`);\n } else if (r.skipped === 'detection-failed') {\n log(` ${r.file}: skipped — it could not be inspected, so it was left untouched as a precaution.`);\n } else {\n log(` ${r.file}: ${formatBytes(r.bytesBefore)} → ${formatBytes(r.bytesAfter)}`);\n }\n }\n const compacted = reclaimed.filter(r => !r.skipped);\n const vectorSkips = reclaimed.filter(r => r.skipped === 'vector-index').length;\n const failedSkips = reclaimed.filter(r => r.skipped === 'detection-failed').length;\n const skipSummary = [\n vectorSkips > 0\n ? `${vectorSkips} skipped because ${vectorSkips === 1 ? 'it contains' : 'they contain'} a vector index`\n : '',\n failedSkips > 0 ? `${failedSkips} skipped because ${failedSkips === 1 ? 'it' : 'they'} could not be inspected` : '',\n ]\n .filter(Boolean)\n .join('; ');\n if (compacted.length === 0) {\n log(`No database files were compacted; ${skipSummary}.`);\n return;\n }\n const saved = compacted.reduce((sum, r) => sum + Math.max(0, r.bytesBefore - r.bytesAfter), 0);\n log(skipSummary ? `Reclaimed ${formatBytes(saved)}; ${skipSummary}.` : `Reclaimed ${formatBytes(saved)}.`);\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAuCA,MAAa,oBAAqC;CAChD,QAAQ;EACN,UAAU,EAAE,QAAQ,MAAM;EAC1B,SAAS,EAAE,QAAQ,MAAM;CAC3B;CACA,eAAe;EACb,OAAO,EAAE,QAAQ,MAAM;EACvB,MAAM,EAAE,QAAQ,MAAM;CACxB;CACA,QAAQ,EACN,SAAS,EAAE,QAAQ,MAAM,EAC3B;CACA,WAAW,EACT,kBAAkB,EAAE,QAAQ,MAAM,EACpC;AACF;;AAsCA,SAAS,gBAAgB,QAAwB;CAC/C,IAAI,QAAQ;CACZ,KAAK,MAAM,aAAa,CAAC,QAAQ,GAAG,OAAO,KAAK,GAC9C,IAAI;EACF,SAAS,SAAS,SAAS,CAAC,CAAC;CAC/B,QAAQ,CAER;CAEF,OAAO;AACT;;AAGA,SAAgB,kBAAkB,WAA2B;CAC3D,OAAO,KAAK,KAAK,YAAY,GAAG,IAAI,MAAM,OAAO;AACnD;;AAGA,SAAS,aAAa,IAAmC,QAAwB;CAE/E,MAAM,MADO,GAAG,OAAO,MACR,CAAC,GAAG;CACnB,OAAO,MAAM,OAAO,OAAO,OAAO,GAAG,CAAC,CAAC,EAAE,IAAI;AAC/C;;;;;AAMA,SAAS,sBAAsB,MAA4C;CACzE,MAAM,KAAK,SAAS,MAAM,GAAG;CAC7B,IAAI;EACF,MAAM,MAAM,OAAO,MAAM,CAAC;EAC1B,IAAI,SAAS,IAAI,KAAK,GAAG,GAAG,EAAE,MAAM,GAAG,OAAO;EAC9C,IAAI,IAAI,OAAO,KAAK,IAAI,OAAO,GAAG,OAAO;EACzC,IAAI,IAAI,OAAO,KAAK,IAAI,OAAO,GAAG,OAAO;EACzC,OAAO;CACT,UAAU;EACR,UAAU,EAAE;CACd;AACF;;;;;;;;;;;;;;;;;;AAmBA,eAAe,2BAA0C;CAEvD,MAAM,IAAI,SAAQ,YAAW,aAAa,OAAO,CAAC;CAClD,MAAM,KACH,WAAmC,aAC7B;EACL,mBAAmB,aAAa;EAChC,OAAO,gBAAgB,IAAI;CAC7B,EAAA,CAAG;CAEL,GAAG;CACH,MAAM,IAAI,SAAQ,YAAW,WAAW,SAAS,EAAE,CAAC;CACpD,GAAG;AACL;;;;;;;;;;;;;;;;;;AAmBA,SAAS,eAAe,IAA4C;CAIlE,OAHY,GACT,QAAQ,oGAAoG,CAAC,CAC7G,IACM,MAAM,KAAA;AACjB;;;;;;;;;;;;;;;;;;;;;;;;;AA0BA,eAAsB,kBACpB,SACA,aAC0B;CAC1B,MAAM,UAA2B,CAAC;CAClC,KAAK,MAAM,QAAQ,SAAS;EAE1B,IAAI,CAAC,WAAW,IAAI,KAAK,SAAS,IAAI,CAAC,CAAC,OAAO,KAAK;EACpD,MAAM,cAAc,gBAAgB,IAAI;EACxC,MAAM,MAAM,GAAG,KAAK;EACpB,OAAO,KAAK,EAAE,OAAO,KAAK,CAAC;EAqB3B,MAAM,yBAAyB;EAC/B,MAAM,QAAQ,IAAI,SAAS,IAAI;EAC/B,IAAI;GACF,MAAM,KAAK,4BAA4B;GAGvC,MAAM,KAAK,2BAA2B;GACtC,MAAM,KAAK,8BAA8B;EAC3C,QAAQ,CAER,UAAU;GACR,MAAM,MAAM;EACd;EACA,IAAI,sBAAsB,IAAI,MAAM,UAClC,MAAM,IAAI,MACR,GAAG,KAAK,2JAEV;EAMF,MAAM,KAAK,IAAI,SAAS,IAAI;EAC5B,IAAI;GACF,GAAG,KAAK,4BAA4B;GAGpC,IAAI;GACJ,IAAI;IACF,IAAI,eAAe,EAAE,GAAG,UAAU;GACpC,QAAQ;IAEN,UAAU;GACZ;GACA,IAAI,SAAS;IACX,QAAQ,KAAK;KAAE;KAAM;KAAa,YAAY;KAAa;IAAQ,CAAC;IACpE;GACF;GACA,MAAM,WAAW,aAAa,IAAI,WAAW;GAC7C,MAAM,YAAY,aAAa,IAAI,YAAY;GAC/C,MAAM,gBAAgB,aAAa,IAAI,gBAAgB;GACvD,MAAM,YAAY,KAAK,IAAI,GAAG,YAAY,aAAa,IAAI;GAC3D,MAAM,EAAE,OAAO,WAAW,WAAW,QAAQ,IAAI,CAAC;GAClD,MAAM,YAAY,QAAQ;GAC1B,IAAI,YAAY,kBAAkB,SAAS,GACzC,MAAM,IAAI,MACR,yCAAyC,KAAK,SAAS,YAAY,kBAAkB,SAAS,CAAC,EAAE,SACtF,YAAY,SAAS,EAAE,4BAA4B,YAAY,SAAS,EAAE,UACvF;GAEF,cAAc,MAAM,aAAa,SAAS;GAC1C,GAAG,KAAK,gBAAgB,IAAI,QAAQ,MAAM,IAAI,EAAE,EAAE;EACpD,SAAS,KAAK;GACZ,OAAO,KAAK,EAAE,OAAO,KAAK,CAAC;GAC3B,MAAM;EACR,UAAU;GACR,GAAG,MAAM;EACX;EAKA,IAAI,sBAAsB,IAAI,MAAM,UAAU;GAC5C,OAAO,KAAK,EAAE,OAAO,KAAK,CAAC;GAC3B,MAAM,IAAI,MACR,GAAG,KAAK,8KAEV;EACF;EAKA,WAAW,MAAM,GAAG,KAAK,KAAK;EAC9B,IAAI;GACF,OAAO,GAAG,KAAK,OAAO,EAAE,OAAO,KAAK,CAAC;GACrC,OAAO,GAAG,KAAK,OAAO,EAAE,OAAO,KAAK,CAAC;GACrC,WAAW,KAAK,IAAI;EACtB,SAAS,KAAK;GACZ,IAAI;IACF,WAAW,GAAG,KAAK,OAAO,IAAI;GAChC,QAAQ;IACN,MAAM,IAAI,MACR,wGAC4B,KAAK,0BAA0B,KAAK,6BAC3C,eAAe,QAAQ,IAAI,UAAU,OAAO,GAAG,GACtE;GACF;GACA,OAAO,KAAK,EAAE,OAAO,KAAK,CAAC;GAC3B,MAAM;EACR;EACA,OAAO,GAAG,KAAK,OAAO,EAAE,OAAO,KAAK,CAAC;EACrC,QAAQ,KAAK;GAAE;GAAM;GAAa,YAAY,gBAAgB,IAAI;EAAE,CAAC;CACvE;CACA,OAAO;AACT;;AAGA,SAAS,cAAc,KAAiC;CACtD,IAAI,CAAC,IAAI,WAAW,OAAO,GAAG,OAAO,KAAA;CACrC,OAAO,IAAI,QAAQ,cAAc,EAAE,CAAC,CAAC,QAAQ,UAAU,EAAE;AAC3D;;;;;;AAOA,SAAgB,oBAAoB,QAAuB,kBAA6C;CACtG,IAAI,qBAAqB,UAAU,OAAO,CAAC;CAC3C,IAAI,OAAO,YAAY,MAErB,OAAO,CAAC,gBAAgB,GAAG,sBAAsB,CAAC;CAEpD,IAAI,OAAO,UAAU,OAAO,CAAC;CAC7B,MAAM,QAAkB,CAAC;CACzB,MAAM,OAAO,cAAc,OAAO,GAAG;CACrC,IAAI,MAAM,MAAM,KAAK,IAAI;CACzB,MAAM,SAAS,OAAO,YAAY,cAAc,OAAO,SAAS,IAAI,sBAAsB;CAC1F,IAAI,QAAQ,MAAM,KAAK,MAAM;CAC7B,OAAO;AACT;;;;;;AAOA,SAAgB,yBAAyB,MAOlB;CACrB,MAAM,EAAE,SAAS,SAAS,WAAW,cAAc,gBAAgB;CACnE,OAAO;EACL;EACA;EACA,QAAO,YAAW,QAAQ,MAAM,OAAO;EACvC,cAAc,YAAY;GACxB,MAAM,QAAQ,QAAQ;GACtB,MAAM,cAAc;EACtB;EACA,GAAI,YAAY,YAAY,aAAa,SAAS,IAC9C,EACE,cAAc,gBACZ,kBAAkB,cAAc,WAAW,EAC/C,IACA,CAAC;CACP;AACF;AAEA,SAAgB,YAAY,OAAuB;CACjD,IAAI,QAAQ,MAAM,OAAO,GAAG,MAAM;CAClC,MAAM,QAAQ;EAAC;EAAM;EAAM;EAAM;CAAI;CACrC,IAAI,QAAQ;CACZ,IAAI,OAAO;CACX,KAAK,MAAM,QAAQ,OAAO;EACxB,IAAI,QAAQ,MAAM;EAClB,SAAS;EACT,OAAO;CACT;CACA,OAAO,GAAG,MAAM,QAAQ,CAAC,EAAE,GAAG;AAChC;;AAGA,MAAM,mBAAmB;;;;;;;;;;;AAYzB,eAAsB,sBAAsB,MAM1B;CAChB,MAAM,EAAE,aAAa,QAAQ,aAAa,OAAO,QAAQ;CAIzD,MAAM,EAAE,QAAQ,SAAS,GAAG,kBAAkB,YAAY;CAC1D,MAAM,kBAAkB,aAAa,EAAE,WAAW,cAAc,IAAI,CAAC;CAErE,IAAI,iDAAiD;CACrD,KAAK,MAAM,CAAC,QAAQ,WAAW,OAAO,QAAQ,YAAY,SAAS,GACjE,KAAK,MAAM,CAAC,OAAO,WAAW,OAAO,QAAQ,UAAU,CAAC,CAAC,GAEvD,IAAI,KAAK,OAAO,GAAG,MAAM,IADZ,cAAc,WAAW,WACF,uBAAwB,OAAuC,QAAQ;CAI/G,MAAM,yBAAS,IAAI,IAAoB;CACvC,SAAS;EACP,MAAM,UAAU,MAAM,YAAY,MAAM;GAAE,YAAY;GAAkB,GAAG;EAAgB,CAAC;EAC5F,IAAI,QAAQ,WAAW,GAAG;EAC1B,IAAI,YAAY;EAChB,IAAI,kBAAkB;EACtB,KAAK,MAAM,KAAK,SAAS;GACvB,MAAM,MAAM,GAAG,EAAE,OAAO,GAAG,EAAE;GAC7B,OAAO,IAAI,MAAM,OAAO,IAAI,GAAG,KAAK,KAAK,EAAE,OAAO;GAClD,mBAAmB,EAAE;GACrB,IAAI,CAAC,EAAE,MAAM,YAAY;GACzB,IAAI,EAAE,UAAU,GACd,IAAI,KAAK,IAAI,IAAI,OAAO,IAAI,GAAG,EAAE,eAAe,EAAE,OAAO,KAAK,YAAY;EAE9E;EACA,IAAI,CAAC,WAAW;EAGhB,IAAI,oBAAoB,GAAG;GACzB,IAAI,2CAA2C;GAC/C;EACF;CACF;CACA,MAAM,eAAe,CAAC,GAAG,OAAO,OAAO,CAAC,CAAC,CAAC,QAAQ,KAAK,MAAM,MAAM,GAAG,CAAC;CACvE,IACE,iBAAiB,IACb,yDACA,mBAAmB,aAAa,eACtC;CAIA,MAAM,YAAY,eAAe;CAEjC,IAAI,CAAC,QAAQ;EACX,IAAI,YAAY,aACd,IACE,qIACF;EAEF;CACF;CAEA,IAAI,CAAC,YAAY,aAAa;EAC5B,IAAI,uEAAuE;EAC3E;CACF;CAEA,IAAI,gGAAgG;CACpG,MAAM,YAAY,MAAM,YAAY,aAAa,MAAM,aAAa,cAAc;EAChF,IAAI,eAAe,KAAK,IAAI,YAAY,WAAW,EAAE,KAAK,YAAY,SAAS,EAAE,QAAQ;CAC3F,CAAC;CACD,IAAI,UAAU,WAAW,GAAG;EAC1B,IAAI,0CAA0C;EAC9C;CACF;CACA,KAAK,MAAM,KAAK,WACd,IAAI,EAAE,YAAY,gBAChB,IAAI,KAAK,EAAE,KAAK,oFAAoF;MAC/F,IAAI,EAAE,YAAY,oBACvB,IAAI,KAAK,EAAE,KAAK,iFAAiF;MAEjG,IAAI,KAAK,EAAE,KAAK,IAAI,YAAY,EAAE,WAAW,EAAE,KAAK,YAAY,EAAE,UAAU,GAAG;CAGnF,MAAM,YAAY,UAAU,QAAO,MAAK,CAAC,EAAE,OAAO;CAClD,MAAM,cAAc,UAAU,QAAO,MAAK,EAAE,YAAY,cAAc,CAAC,CAAC;CACxE,MAAM,cAAc,UAAU,QAAO,MAAK,EAAE,YAAY,kBAAkB,CAAC,CAAC;CAC5E,MAAM,cAAc,CAClB,cAAc,IACV,GAAG,YAAY,mBAAmB,gBAAgB,IAAI,gBAAgB,eAAe,mBACrF,IACJ,cAAc,IAAI,GAAG,YAAY,mBAAmB,gBAAgB,IAAI,OAAO,OAAO,2BAA2B,EACnH,CAAC,CACE,OAAO,OAAO,CAAC,CACf,KAAK,IAAI;CACZ,IAAI,UAAU,WAAW,GAAG;EAC1B,IAAI,qCAAqC,YAAY,EAAE;EACvD;CACF;CACA,MAAM,QAAQ,UAAU,QAAQ,KAAK,MAAM,MAAM,KAAK,IAAI,GAAG,EAAE,cAAc,EAAE,UAAU,GAAG,CAAC;CAC7F,IAAI,cAAc,aAAa,YAAY,KAAK,EAAE,IAAI,YAAY,KAAK,aAAa,YAAY,KAAK,EAAE,EAAE;AAC3G"}
|
|
1
|
+
{"version":3,"file":"storage-maintenance.js","names":[],"sources":["../../src/utils/storage-maintenance.ts"],"sourcesContent":["/**\n * Storage maintenance — retention defaults and disk reclamation for mastracode.\n *\n * Retention (`DEFAULT_RETENTION`) is wired into the inner store by the storage\n * factory so `storage.prune()` can delete aged rows across every domain,\n * including the legacy libsql observability spans table.\n *\n * `prune()` only deletes rows — SQLite keeps the freed pages inside the file.\n * `reclaimLibSQLDisk()` returns that space to the OS by streaming a\n * `VACUUM INTO` copy of each file and swapping it into place. It requires all\n * connections to be closed first, so it only runs from the post-TUI\n * maintenance flow. We deliberately do NOT expose VACUUM as a public API on\n * LibSQLStore.\n */\n\nimport { closeSync, existsSync, openSync, readSync, renameSync, rmSync, statSync, statfsSync } from 'node:fs';\nimport { dirname } from 'node:path';\nimport { setFlagsFromString } from 'node:v8';\nimport { runInNewContext } from 'node:vm';\n\nimport type { MastraCompositeStore, PruneOptions, PruneResult, RetentionConfig } from '@mastra/core/storage';\n// Native `libsql` driver, deliberately alongside `@libsql/client` (used by the\n// stores): maintenance needs deterministic connection close for the\n// exclusivity probe, and the wrapper's close() can leave the file lock held\n// until GC finalizes cached statements. Don't \"clean up\" either dependency.\n// Upstream report: https://github.com/tursodatabase/libsql-js/issues/228\nimport Database from 'libsql';\n\nimport type { StorageConfig } from './project.js';\nimport { getDatabasePath, getVectorDatabasePath } from './project.js';\n\n/**\n * Default retention for mastracode's own storage. Tables not listed are kept\n * forever. Ages chosen for a local dev tool where observability data dwarfs\n * everything else (spans have been observed at 17+ GB):\n * - spans/logs: short-lived debugging telemetry → 14d\n * - scorer results & workflow snapshots: medium-lived run records → 30d\n * - chat history (messages/threads): long-lived, user-visible → 90d\n */\nexport const DEFAULT_RETENTION: RetentionConfig = {\n memory: {\n messages: { maxAge: '90d' },\n threads: { maxAge: '90d' },\n },\n observability: {\n spans: { maxAge: '14d' },\n logs: { maxAge: '14d' },\n },\n scores: {\n scorers: { maxAge: '30d' },\n },\n workflows: {\n workflowSnapshot: { maxAge: '30d' },\n },\n};\n\n/** Result of reclaiming disk for one database file (sizes include the WAL). */\nexport interface ReclaimResult {\n file: string;\n bytesBefore: number;\n bytesAfter: number;\n /**\n * Set when the file was left untouched instead of compacted.\n *\n * `'vector-index'`: the database carries a `libsql_vector_idx` index.\n * Vacuuming such a database corrupts its `libsql_vector_meta_shadow` table\n * (`PRAGMA integrity_check` → `row not in PRIMARY KEY order`), silently at\n * first, so we never compact it. See\n * https://github.com/mastra-ai/mastra/issues/23439.\n *\n * `'detection-failed'`: the schema could not be inspected, so we cannot\n * prove the database is safe to vacuum and skip it as a precaution.\n */\n skipped?: 'vector-index' | 'detection-failed';\n}\n\n/** Handle the TUI uses to run storage maintenance without reaching into store internals. */\nexport interface StorageMaintenance {\n backend: 'libsql' | 'pg';\n /** Retention policies in effect (for display in /prune output). */\n retention: RetentionConfig;\n /** Delete rows older than the retention policies. Safe to re-run; batched and cancellable. `options.retention` replaces the standing policies for this call only. */\n prune: (options?: PruneOptions) => Promise<PruneResult[]>;\n /** Close the long-lived storage connection (checkpoints WAL for local libsql). Nothing can use the store afterwards. */\n closeStorage?: () => Promise<void>;\n /** Compact local libsql files (VACUUM INTO + swap) to return freed pages to the OS. Only set for local libsql backends. */\n reclaimDisk?: (\n onFileStart?: (file: string, bytesBefore: number, liveBytes: number) => void,\n ) => Promise<ReclaimResult[]>;\n}\n\n/** db file size + its WAL sidecar, in bytes. Missing files count as 0. */\nfunction fileSizeWithWal(dbFile: string): number {\n let total = 0;\n for (const candidate of [dbFile, `${dbFile}-wal`]) {\n try {\n total += statSync(candidate).size;\n } catch {\n // file may not exist (e.g. WAL already truncated)\n }\n }\n return total;\n}\n\n/** Free bytes required before compacting: the compacted copy plus 20% + 256 MB headroom. */\nexport function requiredFreeBytes(liveBytes: number): number {\n return Math.ceil(liveBytes * 1.2) + 256 * 1024 * 1024;\n}\n\n/** First column of the first row of a PRAGMA result, as a number. */\nfunction pragmaNumber(db: InstanceType<typeof Database>, pragma: string): number {\n const rows = db.pragma(pragma) as Array<Record<string, unknown>> | undefined;\n const row = rows?.[0];\n return row ? Number(Object.values(row)[0]) : 0;\n}\n\n/**\n * Journal mode straight from the SQLite file header (bytes 18/19:\n * 1 = rollback journal, 2 = WAL) — readable without opening a connection.\n */\nfunction journalModeFromHeader(file: string): 'wal' | 'delete' | 'unknown' {\n const fd = openSync(file, 'r');\n try {\n const buf = Buffer.alloc(2);\n if (readSync(fd, buf, 0, 2, 18) !== 2) return 'unknown';\n if (buf[0] === 2 && buf[1] === 2) return 'wal';\n if (buf[0] === 1 && buf[1] === 1) return 'delete';\n return 'unknown';\n } finally {\n closeSync(fd);\n }\n}\n\n/**\n * Collect our own closed-but-unfinalized libsql connections before probing.\n *\n * libsql-js does not finalize prepared statements on `close()` (they are only\n * released by GC; tursodatabase/libsql-js#228), so a connection we already\n * closed can still hold the WAL lock and make the reclaim probe fail.\n *\n * `gc` is only global under `--expose-gc`, so we set that V8 flag at runtime\n * and read `gc` from a fresh VM context. The flag is process-wide, but it only\n * makes `gc` available to contexts created afterward (the main context's\n * globals are untouched) and changes no GC behavior. A forced\n * collection only frees unreachable objects. This runs only on the explicit\n * vacuum path (`mastracode prune --vacuum` or `/prune vacuum`), and both exit\n * the process right after, so the flag never outlives maintenance in practice.\n *\n * Remove this once libsql-js#228 lands and `close()` finalizes statements.\n */\nasync function releaseClosedConnections(): Promise<void> {\n // Let pending close() callbacks settle before collecting.\n await new Promise(resolve => setImmediate(resolve));\n const gc =\n (globalThis as { gc?: () => void }).gc ??\n (() => {\n setFlagsFromString('--expose-gc');\n return runInNewContext('gc') as () => void;\n })();\n // Native finalizers run after the collection; a second pass collects what they freed.\n gc();\n await new Promise(resolve => setTimeout(resolve, 50));\n gc();\n}\n\n/**\n * Does this database carry a libsql vector index?\n *\n * Takes an already-open connection on purpose: the query needs a prepared\n * statement, and a statement opened before the exclusivity probe pins the\n * connection past close() (libsql-js#228) and makes the probe's\n * `journal_mode = DELETE` fail with SQLITE_BUSY on files nobody else has open.\n * The caller's connection must therefore already be in rollback mode — a\n * pinned statement holds no lock there.\n *\n * Matched by schema, not filename — the main database can carry vector indexes\n * too. SQLite stores `CREATE INDEX` SQL verbatim, so the match is deliberately\n * loose (no trailing `(`): an index written `libsql_vector_idx (embedding)`\n * must not slip through. The two errors are not symmetric — a false positive\n * costs one uncompacted file that we tell the user about, a false negative\n * silently corrupts their vector index.\n */\nfunction hasVectorIndex(db: InstanceType<typeof Database>): boolean {\n const row = db\n .prepare(`SELECT 1 AS hit FROM sqlite_master WHERE type = 'index' AND sql LIKE '%libsql_vector_idx%' LIMIT 1`)\n .get() as Record<string, unknown> | undefined;\n return row !== undefined;\n}\n\n/**\n * Compact each local libsql db file by streaming a `VACUUM INTO` copy next to\n * it, then swapping the copy into place. Reports before/after sizes.\n *\n * Deliberately NOT an in-place `VACUUM`: that copies all live content into a\n * temp store (held in memory by libsql builds with `temp_store=MEMORY`) and\n * then rewrites the entire database back through the WAL — on multi-GB\n * databases that has frozen whole machines via memory/IO exhaustion.\n * `VACUUM INTO` reads the logical database once and writes the compacted copy\n * sequentially with bounded memory, and the WAL never grows.\n *\n * Each file is preflighted: live bytes are estimated from\n * `page_count - freelist_count` and the copy is refused unless the volume has\n * `requiredFreeBytes()` available.\n *\n * MUST run with every connection to these files closed (the swap replaces the\n * inode — a surviving connection would keep writing to the unlinked old file).\n * `runStorageMaintenance()` closes storage before calling this.\n *\n * Databases carrying a `libsql_vector_idx` index are skipped, not compacted:\n * any VACUUM over one corrupts its `libsql_vector_meta_shadow` table (issue\n * #23439). Detection fails closed — a file we cannot inspect is skipped too.\n * Skipped files come back with `skipped` set so the caller can report them.\n */\nexport async function reclaimLibSQLDisk(\n dbFiles: string[],\n onFileStart?: (file: string, bytesBefore: number, liveBytes: number) => void,\n): Promise<ReclaimResult[]> {\n const results: ReclaimResult[] = [];\n for (const file of dbFiles) {\n // Skip missing files and files too small to have a SQLite header — nothing to reclaim\n if (!existsSync(file) || statSync(file).size < 100) continue;\n const bytesBefore = fileSizeWithWal(file);\n const tmp = `${file}.vacuum-tmp`;\n rmSync(tmp, { force: true });\n // Prove we're the only connection before the swap: every open WAL-mode\n // connection (all Mastra Code sessions run WAL) holds a shared-memory\n // lock, so switching WAL -> DELETE only succeeds with exclusive access.\n // A surviving connection would keep writing to the unlinked old inode\n // after the swap and silently lose data.\n //\n // The probe uses exec() only and verifies the outcome from the SQLite\n // file header: prepared statements (including db.pragma()) pin a libsql\n // connection open past close(), so a failed in-connection probe would\n // itself hold the WAL lock and poison every retry in this process.\n // Upstream bug (libsql 0.5.x / @libsql/client 0.17.x): Database.close()\n // does not finalize outstanding statements — they are only finalized by\n // GC, so lock release after close() is nondeterministic. Reported as\n // https://github.com/tursodatabase/libsql-js/issues/228 (fix in flight in\n // PR #214). Once that lands, the header check can become a plain\n // journal_mode query.\n //\n // The same bug means the caller's own storage connection, closed by\n // closeStorage(), still holds the lock until its statements are\n // finalized; collect them first so the probe doesn't see ourselves.\n await releaseClosedConnections();\n const probe = new Database(file);\n try {\n probe.exec('PRAGMA busy_timeout = 2000');\n // Round-trip through WAL so the probe also works when a clean shutdown\n // already left the file in rollback mode.\n probe.exec('PRAGMA journal_mode = WAL');\n probe.exec('PRAGMA journal_mode = DELETE');\n } catch {\n // busy — the header check below reports the failure\n } finally {\n probe.close();\n }\n if (journalModeFromHeader(file) !== 'delete') {\n throw new Error(\n `${file} is in use by another process — is another Mastra Code session running? ` +\n `Close other sessions, then run /prune vacuum (or mastracode prune --vacuum) again.`,\n );\n }\n // Native `libsql` driver, not `@libsql/client`: the wrapper's close() can\n // leave the file lock held in-process (cached statements), which would\n // block other connections afterwards. This connection stays in rollback\n // mode, so even a pinned statement holds no lock once we're done.\n const db = new Database(file);\n try {\n db.exec('PRAGMA busy_timeout = 2000');\n // First, before any check that can throw: a full disk must not abort the\n // whole loop over a file we were going to leave alone anyway.\n let skipped: ReclaimResult['skipped'];\n try {\n if (hasVectorIndex(db)) skipped = 'vector-index';\n } catch {\n // Fail closed: we could not establish that this file is safe to vacuum.\n skipped = 'detection-failed';\n }\n if (skipped) {\n results.push({ file, bytesBefore, bytesAfter: bytesBefore, skipped });\n continue;\n }\n const pageSize = pragmaNumber(db, 'page_size');\n const pageCount = pragmaNumber(db, 'page_count');\n const freelistCount = pragmaNumber(db, 'freelist_count');\n const liveBytes = Math.max(0, pageCount - freelistCount) * pageSize;\n const { bsize, bavail } = statfsSync(dirname(file));\n const freeBytes = bsize * bavail;\n if (freeBytes < requiredFreeBytes(liveBytes)) {\n throw new Error(\n `Not enough free disk space to compact ${file}: need ${formatBytes(requiredFreeBytes(liveBytes))} ` +\n `for a ${formatBytes(liveBytes)} compacted copy, but only ${formatBytes(freeBytes)} is free.`,\n );\n }\n onFileStart?.(file, bytesBefore, liveBytes);\n db.exec(`VACUUM INTO '${tmp.replace(/'/g, \"''\")}'`);\n } catch (err) {\n rmSync(tmp, { force: true });\n throw err;\n } finally {\n db.close();\n }\n // Re-probe right before the swap: `VACUUM INTO` can take tens of seconds\n // on multi-GB files, and a session started in that window reopens the db\n // in WAL mode (flipping the header back). Swapping under it would orphan\n // that session's inode and silently lose its writes.\n if (journalModeFromHeader(file) !== 'delete') {\n rmSync(tmp, { force: true });\n throw new Error(\n `${file} was opened by another process during compaction — is another Mastra Code session running? ` +\n `Close other sessions, then run /prune vacuum (or mastracode prune --vacuum) again.`,\n );\n }\n // Swap the compacted copy into place. The old WAL/SHM sidecars belong to\n // the old inode — they must never be paired with the new file. If any step\n // after the first rename fails, restore the original so the db path is\n // never left empty (a naive restart would otherwise create a fresh db).\n renameSync(file, `${file}.old`);\n try {\n rmSync(`${file}-wal`, { force: true });\n rmSync(`${file}-shm`, { force: true });\n renameSync(tmp, file);\n } catch (err) {\n try {\n renameSync(`${file}.old`, file);\n } catch {\n throw new Error(\n `Failed to swap compacted copy into place AND failed to restore the original — ` +\n `your data is intact at ${file}.old; rename it back to ${file} manually. ` +\n `Original error: ${err instanceof Error ? err.message : String(err)}`,\n );\n }\n rmSync(tmp, { force: true });\n throw err;\n }\n rmSync(`${file}.old`, { force: true });\n results.push({ file, bytesBefore, bytesAfter: fileSizeWithWal(file) });\n }\n return results;\n}\n\n/** `file:/path` or `file:///path` → filesystem path; undefined for non-file urls. */\nfunction fileUrlToPath(url: string): string | undefined {\n if (!url.startsWith('file:')) return undefined;\n return url.replace(/^file:\\/\\//, '').replace(/^file:/, '');\n}\n\n/**\n * Resolve the local libsql db files eligible for checkpoint + VACUUM.\n * Returns [] for remote (turso) urls and PG backends. When PG was requested\n * but the factory fell back to libsql, the fallback uses the default files.\n */\nexport function resolveLocalDbFiles(config: StorageConfig, effectiveBackend: 'libsql' | 'pg'): string[] {\n if (effectiveBackend !== 'libsql') return [];\n if (config.backend === 'pg') {\n // PG requested but fell back to the default local libsql files\n return [getDatabasePath(), getVectorDatabasePath()];\n }\n if (config.isRemote) return [];\n const files: string[] = [];\n const main = fileUrlToPath(config.url);\n if (main) files.push(main);\n const vector = config.vectorUrl ? fileUrlToPath(config.vectorUrl) : getVectorDatabasePath();\n if (vector) files.push(vector);\n return files;\n}\n\n/**\n * Build the maintenance handle for the effective storage backend.\n * `localDbFiles` are the local libsql file paths eligible for VACUUM;\n * pass an empty array for remote (turso) or PG backends.\n */\nexport function createStorageMaintenance(opts: {\n storage: MastraCompositeStore;\n backend: 'libsql' | 'pg';\n retention: RetentionConfig;\n localDbFiles: string[];\n /** Closes the vector store's connection; required for local libsql so the compaction swap isn't blocked by our own handle. */\n closeVector?: () => Promise<void>;\n}): StorageMaintenance {\n const { storage, backend, retention, localDbFiles, closeVector } = opts;\n return {\n backend,\n retention,\n prune: options => storage.prune(options),\n closeStorage: async () => {\n await storage.close?.();\n await closeVector?.();\n },\n ...(backend === 'libsql' && localDbFiles.length > 0\n ? {\n reclaimDisk: (onFileStart?: (file: string, bytesBefore: number, liveBytes: number) => void) =>\n reclaimLibSQLDisk(localDbFiles, onFileStart),\n }\n : {}),\n };\n}\n\nexport function formatBytes(bytes: number): string {\n if (bytes < 1024) return `${bytes} B`;\n const units = ['KB', 'MB', 'GB', 'TB'];\n let value = bytes;\n let unit = 'B';\n for (const next of units) {\n if (value < 1024) break;\n value /= 1024;\n unit = next;\n }\n return `${value.toFixed(1)} ${unit}`;\n}\n\n/** Rows deleted per table per pass: default batchSize (1000) × 20 batches. */\nconst PASS_MAX_BATCHES = 20;\n\n/**\n * Run storage maintenance with exclusive access to the database, reporting\n * progress through `log` (one line per pass so large deletes are visible).\n *\n * Must run AFTER the TUI is stopped and background writers are quiesced —\n * batched deletes and VACUUM contend with live writes (SQLITE_BUSY) and can\n * starve the event loop that renders the TUI. The storage connection is\n * closed at the end (checkpointing the WAL for local libsql), so the process\n * must exit afterwards.\n */\nexport async function runStorageMaintenance(opts: {\n maintenance: StorageMaintenance;\n vacuum: boolean;\n /** Skip the memory domain (messages/threads) so chat history is preserved. */\n keepMemory?: boolean;\n log: (line: string) => void;\n}): Promise<void> {\n const { maintenance, vacuum, keepMemory = false, log } = opts;\n\n // keep-memory: prune with the standing policies minus the memory domain,\n // for this run only. The configured retention is untouched.\n const { memory: _memory, ...withoutMemory } = maintenance.retention;\n const retentionForRun = keepMemory ? { retention: withoutMemory } : {};\n\n log('Pruning rows older than the retention policies:');\n for (const [domain, tables] of Object.entries(maintenance.retention)) {\n for (const [table, policy] of Object.entries(tables ?? {})) {\n const kept = keepMemory && domain === 'memory';\n log(` ${domain}.${table}: ${kept ? 'kept (keep-memory)' : (policy as { maxAge: string | number }).maxAge}`);\n }\n }\n\n const totals = new Map<string, number>();\n for (;;) {\n const results = await maintenance.prune({ maxBatches: PASS_MAX_BATCHES, ...retentionForRun });\n if (results.length === 0) break;\n let remaining = false;\n let deletedThisPass = 0;\n for (const r of results) {\n const key = `${r.domain}.${r.table}`;\n totals.set(key, (totals.get(key) ?? 0) + r.deleted);\n deletedThisPass += r.deleted;\n if (!r.done) remaining = true;\n if (r.deleted > 0) {\n log(` ${key}: ${totals.get(key)} rows deleted${r.done ? '' : ' so far…'}`);\n }\n }\n if (!remaining) break;\n // Safety valve: a table reported more eligible rows but nothing was\n // deleted this pass — bail instead of spinning forever.\n if (deletedThisPass === 0) {\n log(' stopping: no progress in the last pass.');\n break;\n }\n }\n const totalDeleted = [...totals.values()].reduce((sum, n) => sum + n, 0);\n log(\n totalDeleted === 0\n ? 'Nothing to prune — no retention-eligible rows found.'\n : `Prune complete: ${totalDeleted} rows deleted.`,\n );\n\n // Release the long-lived connection: checkpoints the WAL for local libsql\n // and guarantees VACUUM below isn't blocked by our own open handles.\n await maintenance.closeStorage?.();\n\n if (!vacuum) {\n if (maintenance.reclaimDisk) {\n log(\n 'Deleted rows free pages inside the db file but not on disk. Run /prune vacuum (or mastracode prune --vacuum) to reclaim disk space.',\n );\n }\n return;\n }\n\n if (!maintenance.reclaimDisk) {\n log('Disk reclamation (VACUUM) is only available for local libsql storage.');\n return;\n }\n\n log('Reclaiming disk (compacting database files)… this can take several minutes on large databases.');\n const reclaimed = await maintenance.reclaimDisk((file, bytesBefore, liveBytes) => {\n log(` vacuuming ${file} (${formatBytes(bytesBefore)}, ~${formatBytes(liveBytes)} live)…`);\n });\n if (reclaimed.length === 0) {\n log('No local database files found to vacuum.');\n return;\n }\n for (const r of reclaimed) {\n if (r.skipped === 'vector-index') {\n log(` ${r.file}: skipped — it contains a vector index, and compacting it would corrupt that index.`);\n } else if (r.skipped === 'detection-failed') {\n log(` ${r.file}: skipped — it could not be inspected, so it was left untouched as a precaution.`);\n } else {\n log(` ${r.file}: ${formatBytes(r.bytesBefore)} → ${formatBytes(r.bytesAfter)}`);\n }\n }\n const compacted = reclaimed.filter(r => !r.skipped);\n const vectorSkips = reclaimed.filter(r => r.skipped === 'vector-index').length;\n const failedSkips = reclaimed.filter(r => r.skipped === 'detection-failed').length;\n const skipSummary = [\n vectorSkips > 0\n ? `${vectorSkips} skipped because ${vectorSkips === 1 ? 'it contains' : 'they contain'} a vector index`\n : '',\n failedSkips > 0 ? `${failedSkips} skipped because ${failedSkips === 1 ? 'it' : 'they'} could not be inspected` : '',\n ]\n .filter(Boolean)\n .join('; ');\n if (compacted.length === 0) {\n log(`No database files were compacted; ${skipSummary}.`);\n return;\n }\n const saved = compacted.reduce((sum, r) => sum + Math.max(0, r.bytesBefore - r.bytesAfter), 0);\n log(skipSummary ? `Reclaimed ${formatBytes(saved)}; ${skipSummary}.` : `Reclaimed ${formatBytes(saved)}.`);\n}\n"],"mappings":"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"}
|
|
@@ -86,7 +86,8 @@ function getInstallCommand(pm, version) {
|
|
|
86
86
|
*/
|
|
87
87
|
function getCurrentVersion() {
|
|
88
88
|
if (typeof MASTRACODE_VERSION !== "undefined") return MASTRACODE_VERSION;
|
|
89
|
-
const
|
|
89
|
+
const dir = dirname(fileURLToPath(import.meta.url));
|
|
90
|
+
const pkgPath = resolve(dir, "../../package.json");
|
|
90
91
|
return JSON.parse(readFileSync(pkgPath, "utf-8")).version;
|
|
91
92
|
}
|
|
92
93
|
/**
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"update-check.js","names":[],"sources":["../../src/utils/update-check.ts"],"sourcesContent":["/**\n * Check for newer versions of mastracode on npm.\n */\n\nimport { execFile } from 'node:child_process';\nimport { readFileSync, realpathSync } from 'node:fs';\nimport { homedir } from 'node:os';\nimport { dirname, join, resolve, sep } from 'node:path';\nimport { fileURLToPath } from 'node:url';\n\ndeclare const MASTRACODE_VERSION: string | undefined;\n\nconst PACKAGE_NAME = 'mastracode';\nconst NPM_REGISTRY_URL = `https://registry.npmjs.org/${PACKAGE_NAME}/latest`;\n\n/** Timeout for the npm registry fetch (ms). */\nconst FETCH_TIMEOUT_MS = 5_000;\n\n// Package managers and vp are .cmd shims on Windows; execFile can't spawn those without a shell (EINVAL).\nconst USE_SHELL = process.platform === 'win32';\n\nexport type PackageManager = 'npm' | 'pnpm' | 'yarn' | 'bun';\n\nfunction matchPM(str: string): PackageManager | null {\n // Order matters: check pnpm before npm since \"npm\" is a substring of some paths\n if (/pnpm/i.test(str)) return 'pnpm';\n if (/\\byarn\\b/i.test(str)) return 'yarn';\n if (/\\bbun\\b/i.test(str)) return 'bun';\n if (/\\bnpm\\b/i.test(str)) return 'npm';\n return null;\n}\n\n/**\n * Detect which package manager was used to install mastracode globally.\n *\n * Uses a multi-tier approach:\n * 1. npm_config_user_agent env var (set when run via `pnpm run`, `npm run`, etc.)\n * 2. npm_execpath env var (fallback for script-based invocation)\n * 3. NODE_PATH env var (set by pnpm global bin stubs)\n * 4. Resolved path of the running script (process.argv[1])\n * 5. Shell-out to `pnpm list -g` to check if pnpm manages the package\n * 6. Falls back to 'npm'\n */\nexport async function detectPackageManager(): Promise<PackageManager> {\n // Tier 1: npm_config_user_agent (most reliable when available)\n const userAgent = process.env.npm_config_user_agent;\n if (userAgent) {\n const pm = matchPM(userAgent);\n if (pm) return pm;\n }\n\n // Tier 2: npm_execpath\n const execPath = process.env.npm_execpath;\n if (execPath) {\n const pm = matchPM(execPath);\n if (pm) return pm;\n }\n\n // Tier 3: NODE_PATH (pnpm global stubs set this to paths containing .pnpm)\n const nodePath = process.env.NODE_PATH;\n if (nodePath) {\n if (/[/\\\\]\\.pnpm[/\\\\]/.test(nodePath) || /[/\\\\]pnpm[/\\\\]/.test(nodePath)) return 'pnpm';\n if (/[/\\\\]\\.yarn[/\\\\]/.test(nodePath)) return 'yarn';\n if (/[/\\\\]\\.bun[/\\\\]/.test(nodePath)) return 'bun';\n }\n\n // Tier 4: Resolved script path\n try {\n const scriptPath = realpathSync(process.argv[1] ?? '');\n if (/[/\\\\]\\.?pnpm[/\\\\]/.test(scriptPath)) return 'pnpm';\n if (/[/\\\\]\\.?yarn[/\\\\]/.test(scriptPath)) return 'yarn';\n if (/[/\\\\]\\.?bun[/\\\\]/.test(scriptPath)) return 'bun';\n } catch {\n // realpathSync can fail if argv[1] is missing/broken — fall through\n }\n\n // Tier 5: Check if the package is in pnpm's global store (non-blocking)\n const pnpmResult = await new Promise<boolean>(resolve => {\n execFile(\n 'pnpm',\n ['list', '-g', '--depth=0', PACKAGE_NAME],\n { timeout: 3_000, shell: USE_SHELL },\n (error, stdout) => {\n resolve(!error && stdout.includes(PACKAGE_NAME));\n },\n );\n });\n if (pnpmResult) return 'pnpm';\n\n return 'npm';\n}\n\n/**\n * Build the shell command string a user would run to install/update globally.\n */\nexport function getInstallCommand(pm: PackageManager, version?: string): string {\n const pkg = version ? `${PACKAGE_NAME}@${version}` : `${PACKAGE_NAME}@latest`;\n switch (pm) {\n case 'pnpm':\n return `pnpm add -g ${pkg}`;\n case 'yarn':\n return `yarn global add ${pkg}`;\n case 'bun':\n return `bun add -g ${pkg}`;\n default:\n return `npm install -g ${pkg}`;\n }\n}\n\n/**\n * Read the current version, injected at build time by tsdown's `define` option.\n * Falls back to reading package.json at runtime (e.g. when running from source with tsx).\n */\nexport function getCurrentVersion(): string {\n if (typeof MASTRACODE_VERSION !== 'undefined') {\n return MASTRACODE_VERSION;\n }\n // Fallback for running from source (e.g. pnpx tsx mastracode/src/main.ts)\n const dir = dirname(fileURLToPath(import.meta.url));\n const pkgPath = resolve(dir, '../../package.json');\n const pkg = JSON.parse(readFileSync(pkgPath, 'utf-8'));\n return pkg.version;\n}\n\n/**\n * Fetch the latest published version from npm.\n * Returns null if the fetch fails (network error, timeout, etc.).\n */\nexport async function fetchLatestVersion(): Promise<string | null> {\n if (process.env.MASTRACODE_UPDATE_LATEST_VERSION) return process.env.MASTRACODE_UPDATE_LATEST_VERSION;\n\n try {\n const controller = new AbortController();\n const timeout = setTimeout(() => controller.abort(), FETCH_TIMEOUT_MS);\n const res = await fetch(NPM_REGISTRY_URL, { signal: controller.signal });\n clearTimeout(timeout);\n if (!res.ok) return null;\n const data = (await res.json()) as { version?: string };\n return data.version ?? null;\n } catch {\n return null;\n }\n}\n\nconst isPrerelease = (v: string) => v.includes('-');\n\n/**\n * Simple semver comparison: returns true if `latest` is newer than `current`.\n * Handles standard x.y.z versions. Pre-release tags only matter against the\n * matching release; two pre-releases of the same version compare equal.\n */\nexport function isNewerVersion(current: string, latest: string): boolean {\n const parse = (v: string) =>\n v\n .replace(/^v/, '')\n .split('-')[0]!\n .split('.')\n .map(s => {\n const n = Number(s);\n return Number.isFinite(n) ? n : 0;\n });\n const [cMajor = 0, cMinor = 0, cPatch = 0] = parse(current);\n const [lMajor = 0, lMinor = 0, lPatch = 0] = parse(latest);\n if (lMajor !== cMajor) return lMajor > cMajor;\n if (lMinor !== cMinor) return lMinor > cMinor;\n if (lPatch !== cPatch) return lPatch > cPatch;\n // Same x.y.z: a release is newer than its own prerelease (1.2.0 > 1.2.0-rc.1).\n return isPrerelease(current) && !isPrerelease(latest);\n}\n\n/** Max entries to show in the changelog summary. */\nconst MAX_CHANGELOG_ENTRIES = 20;\n\n/**\n * Fetch the CHANGELOG.md for a specific published version from the npm CDN\n * and extract a human-readable summary of changes.\n * Returns null if the fetch fails or the changelog can't be parsed.\n */\nexport async function fetchChangelog(version: string): Promise<string | null> {\n if (process.env.MASTRACODE_UPDATE_CHANGELOG) return process.env.MASTRACODE_UPDATE_CHANGELOG;\n\n try {\n const url = `https://unpkg.com/${PACKAGE_NAME}@${version}/CHANGELOG.md`;\n const controller = new AbortController();\n const timeout = setTimeout(() => controller.abort(), FETCH_TIMEOUT_MS);\n const res = await fetch(url, { signal: controller.signal });\n clearTimeout(timeout);\n if (!res.ok) return null;\n const text = await res.text();\n return parseChangelog(text, version);\n } catch {\n return null;\n }\n}\n\n/**\n * Extract the changelog section for a specific version and format it\n * as a concise bullet list suitable for terminal display.\n */\nexport function parseChangelog(markdown: string, version: string): string | null {\n const versionHeader = `## ${version}`;\n const startIdx = markdown.indexOf(versionHeader);\n if (startIdx === -1) return null;\n\n const afterHeader = startIdx + versionHeader.length;\n const nextHeaderIdx = markdown.indexOf('\\n## ', afterHeader);\n const section = nextHeaderIdx === -1 ? markdown.slice(afterHeader) : markdown.slice(afterHeader, nextHeaderIdx);\n\n const lines = section.split('\\n');\n const entries: string[] = [];\n let skipIndented = false;\n\n for (let i = 0; i < lines.length; i++) {\n const raw = lines[i]!;\n const trimmed = raw.trim();\n\n // Only match top-level entries (no leading whitespace before \"- \")\n const isTopLevel = raw === trimmed && trimmed.startsWith('- ');\n if (!isTopLevel) {\n // Skip indented sub-items (e.g. dependency list entries)\n if (skipIndented && /^\\s+-\\s/.test(raw)) continue;\n skipIndented = false;\n continue;\n }\n\n // Skip dependency update entries and their sub-items\n if (/^- Updated dependenc/i.test(trimmed)) {\n skipIndented = true;\n continue;\n }\n skipIndented = false;\n\n let entry = trimmed.slice(2);\n // Strip markdown links: [text](url) → text\n entry = entry.replace(/\\[([^\\]]+)\\]\\([^)]+\\)/g, '$1');\n // Strip PR references like (#15760)\n entry = entry.replace(/\\s*\\(#\\d+\\)\\s*/g, ' ');\n // Strip commit SHA references like (`abc123`)\n entry = entry.replace(/\\s*\\(`[a-f0-9]+`\\)\\s*/g, ' ');\n // Trim trailing periods for cleaner bullet display\n entry = entry.replace(/\\.\\s*$/, '');\n entry = entry.trim();\n if (entry) entries.push(entry);\n if (entries.length >= MAX_CHANGELOG_ENTRIES) break;\n }\n\n if (entries.length === 0) return null;\n return entries.map(e => ` • ${e}`).join('\\n');\n}\n\n/** Result of attempting an auto-update: exit status plus captured stderr. */\nexport interface UpdateResult {\n ok: boolean;\n stderr?: string;\n}\n\n/**\n * Run the appropriate global install command to update mastracode.\n * Exit code 0 does not prove the running binary changed (the install may land\n * in a location the PATH shim doesn't use) — verify with {@link locateOwnInstall}.\n */\nexport function runUpdate(pm: PackageManager, targetVersion: string): Promise<UpdateResult> {\n return execUpdate(pm, buildInstallArgs(pm, targetVersion));\n}\n\nfunction execUpdate(cmd: string, args: string[]): Promise<UpdateResult> {\n return new Promise(resolve => {\n execFile(cmd, args, { timeout: 60_000, shell: USE_SHELL }, (error, _stdout, stderr) => {\n const captured = (stderr ?? '').trim();\n if (error) {\n resolve({ ok: false, stderr: captured || error.message });\n } else {\n resolve({ ok: true, stderr: captured || undefined });\n }\n });\n });\n}\n\nfunction buildInstallArgs(pm: PackageManager, version: string): string[] {\n const pkg = `${PACKAGE_NAME}@${version}`;\n switch (pm) {\n case 'pnpm':\n return ['add', '-g', pkg];\n case 'yarn':\n return ['global', 'add', pkg];\n case 'bun':\n return ['add', '-g', pkg];\n default:\n return ['install', '-g', pkg];\n }\n}\n\n/** Max stderr lines to surface when an update fails. */\nconst MAX_UPDATE_ERROR_LINES = 5;\n\nfunction formatUpdaterError(stderr: string | undefined): string | null {\n if (!stderr) return null;\n const lines = stderr\n .split('\\n')\n .map(line => line.trimEnd())\n .filter(line => line.trim().length > 0);\n if (lines.length === 0) return null;\n return lines.slice(-MAX_UPDATE_ERROR_LINES).join('\\n');\n}\n\n/**\n * Find the installed mastracode package the running binary belongs to (walking\n * up from `process.argv[1]`) and read its version fresh from disk. Returns\n * null when it can't be determined, e.g. when running from source.\n */\nexport function locateOwnInstall(): { dir: string; version: string | null } | null {\n const entry = process.argv[1];\n if (!entry) return null;\n\n let dir: string;\n try {\n dir = dirname(realpathSync(entry));\n } catch {\n return null;\n }\n\n let previous = '';\n while (dir && dir !== previous) {\n try {\n const pkg = JSON.parse(readFileSync(resolve(dir, 'package.json'), 'utf-8')) as {\n name?: string;\n version?: string;\n };\n if (pkg.name === PACKAGE_NAME) {\n return { dir, version: typeof pkg.version === 'string' ? pkg.version : null };\n }\n } catch {\n // Not a readable manifest — keep walking up.\n }\n previous = dir;\n dir = dirname(dir);\n }\n\n return null;\n}\n\n/**\n * Result of an update attempt. `message` is a ready-made sentence; the other\n * fields let callers lay the result out themselves.\n */\nexport type UpdateOutcome =\n | {\n status: 'updated';\n message: string;\n /** The package manager or tool that installed the update. */\n via: string;\n }\n | {\n status: 'unchanged';\n message: string;\n /** Command the user can run to update the install they're running. */\n command: string;\n /** Where the running install lives, when known. */\n installDir?: string;\n /** Version still on disk after the update ran, when the update ran at all. */\n runningVersion?: string;\n /** Tool that ran the update (when it ran but didn't change the running install). */\n ranWith?: string;\n /** Tool that owns the install when we only suggest its command (e.g. Homebrew). */\n managedBy?: string;\n }\n | {\n status: 'failed';\n message: string;\n /** Command the user can run instead, when there is one. */\n command?: string;\n /** Why it failed: the tail of the package manager's stderr, or our own reason. */\n details?: string;\n };\n\n/**\n * Decide what to tell the user after {@link runUpdate}: `updated` when the\n * on-disk version matches the target (or can't be determined), `unchanged`\n * when the package manager succeeded but the running install didn't change,\n * `failed` on error (with the package manager's stderr).\n */\nexport function resolveUpdateOutcome(opts: {\n pm: PackageManager;\n targetVersion: string;\n result: UpdateResult;\n install: { dir: string; version: string | null } | null;\n /** Overrides the suggested manual command, e.g. when the update was delegated to the owning tool. */\n manualCommand?: string;\n /** Tool that ran the update when it wasn't `pm`, e.g. vite-plus. */\n via?: string;\n}): UpdateOutcome {\n const { pm, targetVersion, result, install } = opts;\n const cmd = opts.manualCommand ?? getInstallCommand(pm, targetVersion);\n const via = opts.via ?? pm;\n\n if (!result.ok) {\n const details = formatUpdaterError(result.stderr) ?? undefined;\n let message = `Auto-update failed. Run \\`${cmd}\\` manually.`;\n if (details) message += `\\n\\n${details}`;\n return { status: 'failed', message, command: cmd, ...(details ? { details } : {}) };\n }\n\n if (install?.version != null && install.version !== targetVersion) {\n const message = opts.manualCommand\n ? `The update ran, but the Mastra Code you are running (at ${install.dir}) is still ` +\n `v${install.version}. Try \\`${cmd}\\` manually.`\n : `The update installed, but the Mastra Code you are running (at ${install.dir}) is still ` +\n `v${install.version} — it looks like it is managed by another tool. Update it with that tool, ` +\n `or try \\`${cmd}\\`.`;\n return {\n status: 'unchanged',\n message,\n command: cmd,\n installDir: install.dir,\n runningVersion: install.version,\n ranWith: via,\n };\n }\n\n return { status: 'updated', message: `Updated to v${targetVersion}. Please restart Mastra Code.`, via };\n}\n\n/** Global node_modules directory the package manager installs into, or null when unknown. */\nfunction getGlobalRoot(pm: PackageManager): Promise<string | null> {\n const args = pm === 'yarn' ? ['global', 'dir'] : ['root', '-g'];\n return new Promise(res => {\n execFile(pm, args, { timeout: 10_000, shell: USE_SHELL }, (error, stdout) => {\n const out = (stdout ?? '').trim().split('\\n').pop()?.trim();\n if (error || !out) return res(null);\n res(pm === 'yarn' ? resolve(out, 'node_modules') : out);\n });\n });\n}\n\n/**\n * Whether updating via `pm` would touch the install the running binary uses.\n * Fail-open: returns true when either side can't be determined, so the update\n * proceeds and the post-install verification has the final word.\n */\nasync function isOwnInstallManagedBy(pm: PackageManager, install: { dir: string } | null): Promise<boolean> {\n if (!install) return true;\n const root = await getGlobalRoot(pm);\n if (!root) return true;\n try {\n return realpathSync(resolve(root, PACKAGE_NAME)) === install.dir;\n } catch {\n // Package absent from the pm's global root: installing there can't affect the running binary.\n return false;\n }\n}\n\n/** A tool other than a package manager that owns the running install, matched by path. */\ninterface InstallOwner {\n name: string;\n /** Command to run to delegate the update; omitted when we only suggest `command` to the user. */\n exec?: { cmd: string; args: string[] };\n command: string;\n}\n\nfunction findInstallOwner(dir: string, version: string): InstallOwner | null {\n if (dir.startsWith(join(homedir(), '.vite-plus') + sep)) {\n return {\n name: 'vite-plus',\n exec: { cmd: 'vp', args: ['install', '-g', `${PACKAGE_NAME}@${version}`] },\n command: `vp install -g ${PACKAGE_NAME}@${version}`,\n };\n }\n // Homebrew keeps formulas under <prefix>/Cellar/ whatever the prefix, so match the path segment.\n if (dir.split(sep).includes('Cellar')) {\n // Suggest-only: brew formulas lag npm, and `brew upgrade` can pull unrelated updates.\n return { name: 'Homebrew', command: `brew upgrade ${PACKAGE_NAME}` };\n }\n return null;\n}\n\n/**\n * What an update will do for the running install: run an install (with the\n * tool and command it will use), or nothing, with the result to show instead.\n */\nexport type UpdatePlan =\n | {\n willInstall: true;\n /** The package manager or tool that will run the install. */\n via: string;\n /** The install command, as the user would type it. */\n command: string;\n exec: { cmd: string; args: string[] };\n }\n | { willInstall: false; outcome: Extract<UpdateOutcome, { status: 'unchanged' | 'failed' }> };\n\n/**\n * Decide how to update the running install, without changing anything:\n * delegate to the tool that owns it when we recognize it, only suggest a\n * command when that tool can't be run for the user (Homebrew) or `pm` doesn't\n * manage it, otherwise run `pm`. {@link performUpdate} carries out this plan,\n * so callers can show what will happen before it starts.\n */\nexport async function planUpdate(pm: PackageManager, targetVersion: string): Promise<UpdatePlan> {\n // The registry-provided version reaches shell commands on Windows — accept only version tokens.\n if (!/^[\\w.+-]+$/.test(targetVersion)) {\n return {\n willInstall: false,\n outcome: {\n status: 'failed',\n message: `Auto-update aborted: unexpected version \"${targetVersion}\".`,\n details: `The npm registry returned an unexpected version: \"${targetVersion}\".`,\n },\n };\n }\n\n const install = locateOwnInstall();\n\n const owner = install ? findInstallOwner(install.dir, targetVersion) : null;\n if (owner) {\n if (!owner.exec) {\n const message =\n `Your Mastra Code install (at ${install!.dir}) is managed by ${owner.name}. ` +\n `Update it with \\`${owner.command}\\`.`;\n return {\n willInstall: false,\n outcome: {\n status: 'unchanged',\n message,\n command: owner.command,\n installDir: install!.dir,\n managedBy: owner.name,\n },\n };\n }\n return { willInstall: true, via: owner.name, command: owner.command, exec: owner.exec };\n }\n\n const command = getInstallCommand(pm, targetVersion);\n if (!(await isOwnInstallManagedBy(pm, install))) {\n const message =\n `Your Mastra Code install (at ${install!.dir}) is not managed by ${pm} — it looks like it was ` +\n `installed by another tool. Update it with that tool, or try \\`${command}\\`.`;\n return { willInstall: false, outcome: { status: 'unchanged', message, command, installDir: install!.dir } };\n }\n\n return { willInstall: true, via: pm, command, exec: { cmd: pm, args: buildInstallArgs(pm, targetVersion) } };\n}\n\n/**\n * Update mastracode following {@link planUpdate} (pass a plan already shown to\n * the user, or let this make one), then verify the result against the version\n * on disk.\n */\nexport async function performUpdate(\n pm: PackageManager,\n targetVersion: string,\n plan?: UpdatePlan,\n): Promise<UpdateOutcome> {\n plan ??= await planUpdate(pm, targetVersion);\n if (!plan.willInstall) return plan.outcome;\n\n const result = await execUpdate(plan.exec.cmd, plan.exec.args);\n // Re-locate so the version reflects what the install just wrote to disk.\n return resolveUpdateOutcome({\n pm,\n targetVersion,\n result,\n install: locateOwnInstall(),\n // A delegated update keeps the owning tool's command; `pm` updates keep the default wording.\n ...(plan.via === pm ? {} : { manualCommand: plan.command, via: plan.via }),\n });\n}\n"],"mappings":";;;;;;;;;AAYA,MAAM,eAAe;AACrB,MAAM,mBAAmB,8BAA8B,aAAa;;AAGpE,MAAM,mBAAmB;AAGzB,MAAM,YAAY,QAAQ,aAAa;AAIvC,SAAS,QAAQ,KAAoC;CAEnD,IAAI,QAAQ,KAAK,GAAG,GAAG,OAAO;CAC9B,IAAI,YAAY,KAAK,GAAG,GAAG,OAAO;CAClC,IAAI,WAAW,KAAK,GAAG,GAAG,OAAO;CACjC,IAAI,WAAW,KAAK,GAAG,GAAG,OAAO;CACjC,OAAO;AACT;;;;;;;;;;;;AAaA,eAAsB,uBAAgD;CAEpE,MAAM,YAAY,QAAQ,IAAI;CAC9B,IAAI,WAAW;EACb,MAAM,KAAK,QAAQ,SAAS;EAC5B,IAAI,IAAI,OAAO;CACjB;CAGA,MAAM,WAAW,QAAQ,IAAI;CAC7B,IAAI,UAAU;EACZ,MAAM,KAAK,QAAQ,QAAQ;EAC3B,IAAI,IAAI,OAAO;CACjB;CAGA,MAAM,WAAW,QAAQ,IAAI;CAC7B,IAAI,UAAU;EACZ,IAAI,mBAAmB,KAAK,QAAQ,KAAK,iBAAiB,KAAK,QAAQ,GAAG,OAAO;EACjF,IAAI,mBAAmB,KAAK,QAAQ,GAAG,OAAO;EAC9C,IAAI,kBAAkB,KAAK,QAAQ,GAAG,OAAO;CAC/C;CAGA,IAAI;EACF,MAAM,aAAa,aAAa,QAAQ,KAAK,MAAM,EAAE;EACrD,IAAI,oBAAoB,KAAK,UAAU,GAAG,OAAO;EACjD,IAAI,oBAAoB,KAAK,UAAU,GAAG,OAAO;EACjD,IAAI,mBAAmB,KAAK,UAAU,GAAG,OAAO;CAClD,QAAQ,CAER;CAaA,IAAI,MAVqB,IAAI,SAAiB,YAAW;EACvD,SACE,QACA;GAAC;GAAQ;GAAM;GAAa;EAAY,GACxC;GAAE,SAAS;GAAO,OAAO;EAAU,IAClC,OAAO,WAAW;GACjB,QAAQ,CAAC,SAAS,OAAO,SAAS,YAAY,CAAC;EACjD,CACF;CACF,CAAC,GACe,OAAO;CAEvB,OAAO;AACT;;;;AAKA,SAAgB,kBAAkB,IAAoB,SAA0B;CAC9E,MAAM,MAAM,UAAU,GAAG,aAAa,GAAG,YAAY,GAAG,aAAa;CACrE,QAAQ,IAAR;EACE,KAAK,QACH,OAAO,eAAe;EACxB,KAAK,QACH,OAAO,mBAAmB;EAC5B,KAAK,OACH,OAAO,cAAc;EACvB,SACE,OAAO,kBAAkB;CAC7B;AACF;;;;;AAMA,SAAgB,oBAA4B;CAC1C,IAAI,OAAO,uBAAuB,aAChC,OAAO;CAIT,MAAM,UAAU,QADJ,QAAQ,cAAc,OAAO,KAAK,GAAG,CACvB,GAAG,oBAAoB;CAEjD,OADY,KAAK,MAAM,aAAa,SAAS,OAAO,CAC3C,CAAC,CAAC;AACb;;;;;AAMA,eAAsB,qBAA6C;CACjE,IAAI,QAAQ,IAAI,kCAAkC,OAAO,QAAQ,IAAI;CAErE,IAAI;EACF,MAAM,aAAa,IAAI,gBAAgB;EACvC,MAAM,UAAU,iBAAiB,WAAW,MAAM,GAAG,gBAAgB;EACrE,MAAM,MAAM,MAAM,MAAM,kBAAkB,EAAE,QAAQ,WAAW,OAAO,CAAC;EACvE,aAAa,OAAO;EACpB,IAAI,CAAC,IAAI,IAAI,OAAO;EAEpB,QAAO,MADa,IAAI,KAAK,EAAA,CACjB,WAAW;CACzB,QAAQ;EACN,OAAO;CACT;AACF;AAEA,MAAM,gBAAgB,MAAc,EAAE,SAAS,GAAG;;;;;;AAOlD,SAAgB,eAAe,SAAiB,QAAyB;CACvE,MAAM,SAAS,MACb,EACG,QAAQ,MAAM,EAAE,CAAC,CACjB,MAAM,GAAG,CAAC,CAAC,EAAE,CACb,MAAM,GAAG,CAAC,CACV,KAAI,MAAK;EACR,MAAM,IAAI,OAAO,CAAC;EAClB,OAAO,OAAO,SAAS,CAAC,IAAI,IAAI;CAClC,CAAC;CACL,MAAM,CAAC,SAAS,GAAG,SAAS,GAAG,SAAS,KAAK,MAAM,OAAO;CAC1D,MAAM,CAAC,SAAS,GAAG,SAAS,GAAG,SAAS,KAAK,MAAM,MAAM;CACzD,IAAI,WAAW,QAAQ,OAAO,SAAS;CACvC,IAAI,WAAW,QAAQ,OAAO,SAAS;CACvC,IAAI,WAAW,QAAQ,OAAO,SAAS;CAEvC,OAAO,aAAa,OAAO,KAAK,CAAC,aAAa,MAAM;AACtD;;AAGA,MAAM,wBAAwB;;;;;;AAO9B,eAAsB,eAAe,SAAyC;CAC5E,IAAI,QAAQ,IAAI,6BAA6B,OAAO,QAAQ,IAAI;CAEhE,IAAI;EACF,MAAM,MAAM,qBAAqB,aAAa,GAAG,QAAQ;EACzD,MAAM,aAAa,IAAI,gBAAgB;EACvC,MAAM,UAAU,iBAAiB,WAAW,MAAM,GAAG,gBAAgB;EACrE,MAAM,MAAM,MAAM,MAAM,KAAK,EAAE,QAAQ,WAAW,OAAO,CAAC;EAC1D,aAAa,OAAO;EACpB,IAAI,CAAC,IAAI,IAAI,OAAO;EAEpB,OAAO,eAAe,MADH,IAAI,KAAK,GACA,OAAO;CACrC,QAAQ;EACN,OAAO;CACT;AACF;;;;;AAMA,SAAgB,eAAe,UAAkB,SAAgC;CAC/E,MAAM,gBAAgB,MAAM;CAC5B,MAAM,WAAW,SAAS,QAAQ,aAAa;CAC/C,IAAI,aAAa,IAAI,OAAO;CAE5B,MAAM,cAAc,WAAW,cAAc;CAC7C,MAAM,gBAAgB,SAAS,QAAQ,SAAS,WAAW;CAG3D,MAAM,SAFU,kBAAkB,KAAK,SAAS,MAAM,WAAW,IAAI,SAAS,MAAM,aAAa,aAAa,EAAA,CAExF,MAAM,IAAI;CAChC,MAAM,UAAoB,CAAC;CAC3B,IAAI,eAAe;CAEnB,KAAK,IAAI,IAAI,GAAG,IAAI,MAAM,QAAQ,KAAK;EACrC,MAAM,MAAM,MAAM;EAClB,MAAM,UAAU,IAAI,KAAK;EAIzB,IAAI,EADe,QAAQ,WAAW,QAAQ,WAAW,IAAI,IAC5C;GAEf,IAAI,gBAAgB,UAAU,KAAK,GAAG,GAAG;GACzC,eAAe;GACf;EACF;EAGA,IAAI,wBAAwB,KAAK,OAAO,GAAG;GACzC,eAAe;GACf;EACF;EACA,eAAe;EAEf,IAAI,QAAQ,QAAQ,MAAM,CAAC;EAE3B,QAAQ,MAAM,QAAQ,0BAA0B,IAAI;EAEpD,QAAQ,MAAM,QAAQ,mBAAmB,GAAG;EAE5C,QAAQ,MAAM,QAAQ,0BAA0B,GAAG;EAEnD,QAAQ,MAAM,QAAQ,UAAU,EAAE;EAClC,QAAQ,MAAM,KAAK;EACnB,IAAI,OAAO,QAAQ,KAAK,KAAK;EAC7B,IAAI,QAAQ,UAAU,uBAAuB;CAC/C;CAEA,IAAI,QAAQ,WAAW,GAAG,OAAO;CACjC,OAAO,QAAQ,KAAI,MAAK,OAAO,GAAG,CAAC,CAAC,KAAK,IAAI;AAC/C;;;;;;AAaA,SAAgB,UAAU,IAAoB,eAA8C;CAC1F,OAAO,WAAW,IAAI,iBAAiB,IAAI,aAAa,CAAC;AAC3D;AAEA,SAAS,WAAW,KAAa,MAAuC;CACtE,OAAO,IAAI,SAAQ,YAAW;EAC5B,SAAS,KAAK,MAAM;GAAE,SAAS;GAAQ,OAAO;EAAU,IAAI,OAAO,SAAS,WAAW;GACrF,MAAM,YAAY,UAAU,GAAA,CAAI,KAAK;GACrC,IAAI,OACF,QAAQ;IAAE,IAAI;IAAO,QAAQ,YAAY,MAAM;GAAQ,CAAC;QAExD,QAAQ;IAAE,IAAI;IAAM,QAAQ,YAAY,KAAA;GAAU,CAAC;EAEvD,CAAC;CACH,CAAC;AACH;AAEA,SAAS,iBAAiB,IAAoB,SAA2B;CACvE,MAAM,MAAM,GAAG,aAAa,GAAG;CAC/B,QAAQ,IAAR;EACE,KAAK,QACH,OAAO;GAAC;GAAO;GAAM;EAAG;EAC1B,KAAK,QACH,OAAO;GAAC;GAAU;GAAO;EAAG;EAC9B,KAAK,OACH,OAAO;GAAC;GAAO;GAAM;EAAG;EAC1B,SACE,OAAO;GAAC;GAAW;GAAM;EAAG;CAChC;AACF;AAKA,SAAS,mBAAmB,QAA2C;CACrE,IAAI,CAAC,QAAQ,OAAO;CACpB,MAAM,QAAQ,OACX,MAAM,IAAI,CAAC,CACX,KAAI,SAAQ,KAAK,QAAQ,CAAC,CAAC,CAC3B,QAAO,SAAQ,KAAK,KAAK,CAAC,CAAC,SAAS,CAAC;CACxC,IAAI,MAAM,WAAW,GAAG,OAAO;CAC/B,OAAO,MAAM,MAAM,EAAuB,CAAC,CAAC,KAAK,IAAI;AACvD;;;;;;AAOA,SAAgB,mBAAmE;CACjF,MAAM,QAAQ,QAAQ,KAAK;CAC3B,IAAI,CAAC,OAAO,OAAO;CAEnB,IAAI;CACJ,IAAI;EACF,MAAM,QAAQ,aAAa,KAAK,CAAC;CACnC,QAAQ;EACN,OAAO;CACT;CAEA,IAAI,WAAW;CACf,OAAO,OAAO,QAAQ,UAAU;EAC9B,IAAI;GACF,MAAM,MAAM,KAAK,MAAM,aAAa,QAAQ,KAAK,cAAc,GAAG,OAAO,CAAC;GAI1E,IAAI,IAAI,SAAS,cACf,OAAO;IAAE;IAAK,SAAS,OAAO,IAAI,YAAY,WAAW,IAAI,UAAU;GAAK;EAEhF,QAAQ,CAER;EACA,WAAW;EACX,MAAM,QAAQ,GAAG;CACnB;CAEA,OAAO;AACT;;;;;;;AA0CA,SAAgB,qBAAqB,MASnB;CAChB,MAAM,EAAE,IAAI,eAAe,QAAQ,YAAY;CAC/C,MAAM,MAAM,KAAK,iBAAiB,kBAAkB,IAAI,aAAa;CACrE,MAAM,MAAM,KAAK,OAAO;CAExB,IAAI,CAAC,OAAO,IAAI;EACd,MAAM,UAAU,mBAAmB,OAAO,MAAM,KAAK,KAAA;EACrD,IAAI,UAAU,6BAA6B,IAAI;EAC/C,IAAI,SAAS,WAAW,OAAO;EAC/B,OAAO;GAAE,QAAQ;GAAU;GAAS,SAAS;GAAK,GAAI,UAAU,EAAE,QAAQ,IAAI,CAAC;EAAG;CACpF;CAEA,IAAI,SAAS,WAAW,QAAQ,QAAQ,YAAY,eAOlD,OAAO;EACL,QAAQ;EACR,SARc,KAAK,gBACjB,2DAA2D,QAAQ,IAAI,cACnE,QAAQ,QAAQ,UAAU,IAAI,gBAClC,iEAAiE,QAAQ,IAAI,cACzE,QAAQ,QAAQ,qFACR,IAAI;EAIlB,SAAS;EACT,YAAY,QAAQ;EACpB,gBAAgB,QAAQ;EACxB,SAAS;CACX;CAGF,OAAO;EAAE,QAAQ;EAAW,SAAS,eAAe,cAAc;EAAgC;CAAI;AACxG;;AAGA,SAAS,cAAc,IAA4C;CACjE,MAAM,OAAO,OAAO,SAAS,CAAC,UAAU,KAAK,IAAI,CAAC,QAAQ,IAAI;CAC9D,OAAO,IAAI,SAAQ,QAAO;EACxB,SAAS,IAAI,MAAM;GAAE,SAAS;GAAQ,OAAO;EAAU,IAAI,OAAO,WAAW;GAC3E,MAAM,OAAO,UAAU,GAAA,CAAI,KAAK,CAAC,CAAC,MAAM,IAAI,CAAC,CAAC,IAAI,CAAC,EAAE,KAAK;GAC1D,IAAI,SAAS,CAAC,KAAK,OAAO,IAAI,IAAI;GAClC,IAAI,OAAO,SAAS,QAAQ,KAAK,cAAc,IAAI,GAAG;EACxD,CAAC;CACH,CAAC;AACH;;;;;;AAOA,eAAe,sBAAsB,IAAoB,SAAmD;CAC1G,IAAI,CAAC,SAAS,OAAO;CACrB,MAAM,OAAO,MAAM,cAAc,EAAE;CACnC,IAAI,CAAC,MAAM,OAAO;CAClB,IAAI;EACF,OAAO,aAAa,QAAQ,MAAM,YAAY,CAAC,MAAM,QAAQ;CAC/D,QAAQ;EAEN,OAAO;CACT;AACF;AAUA,SAAS,iBAAiB,KAAa,SAAsC;CAC3E,IAAI,IAAI,WAAW,KAAK,QAAQ,GAAG,YAAY,IAAI,GAAG,GACpD,OAAO;EACL,MAAM;EACN,MAAM;GAAE,KAAK;GAAM,MAAM;IAAC;IAAW;IAAM,GAAG,aAAa,GAAG;GAAS;EAAE;EACzE,SAAS,iBAAiB,aAAa,GAAG;CAC5C;CAGF,IAAI,IAAI,MAAM,GAAG,CAAC,CAAC,SAAS,QAAQ,GAElC,OAAO;EAAE,MAAM;EAAY,SAAS,gBAAgB;CAAe;CAErE,OAAO;AACT;;;;;;;;AAwBA,eAAsB,WAAW,IAAoB,eAA4C;CAE/F,IAAI,CAAC,aAAa,KAAK,aAAa,GAClC,OAAO;EACL,aAAa;EACb,SAAS;GACP,QAAQ;GACR,SAAS,4CAA4C,cAAc;GACnE,SAAS,qDAAqD,cAAc;EAC9E;CACF;CAGF,MAAM,UAAU,iBAAiB;CAEjC,MAAM,QAAQ,UAAU,iBAAiB,QAAQ,KAAK,aAAa,IAAI;CACvE,IAAI,OAAO;EACT,IAAI,CAAC,MAAM,MAIT,OAAO;GACL,aAAa;GACb,SAAS;IACP,QAAQ;IACR,SAAA,gCAN8B,QAAS,IAAI,kBAAkB,MAAM,KAAK,qBACtD,MAAM,QAAQ;IAMhC,SAAS,MAAM;IACf,YAAY,QAAS;IACrB,WAAW,MAAM;GACnB;EACF;EAEF,OAAO;GAAE,aAAa;GAAM,KAAK,MAAM;GAAM,SAAS,MAAM;GAAS,MAAM,MAAM;EAAK;CACxF;CAEA,MAAM,UAAU,kBAAkB,IAAI,aAAa;CACnD,IAAI,CAAE,MAAM,sBAAsB,IAAI,OAAO,GAI3C,OAAO;EAAE,aAAa;EAAO,SAAS;GAAE,QAAQ;GAAa,SAAA,gCAF3B,QAAS,IAAI,sBAAsB,GAAG,wFACL,QAAQ;GACL;GAAS,YAAY,QAAS;EAAI;CAAE;CAG5G,OAAO;EAAE,aAAa;EAAM,KAAK;EAAI;EAAS,MAAM;GAAE,KAAK;GAAI,MAAM,iBAAiB,IAAI,aAAa;EAAE;CAAE;AAC7G;;;;;;AAOA,eAAsB,cACpB,IACA,eACA,MACwB;CACxB,SAAS,MAAM,WAAW,IAAI,aAAa;CAC3C,IAAI,CAAC,KAAK,aAAa,OAAO,KAAK;CAInC,OAAO,qBAAqB;EAC1B;EACA;EACA,QAAA,MALmB,WAAW,KAAK,KAAK,KAAK,KAAK,KAAK,IAAI;EAM3D,SAAS,iBAAiB;EAE1B,GAAI,KAAK,QAAQ,KAAK,CAAC,IAAI;GAAE,eAAe,KAAK;GAAS,KAAK,KAAK;EAAI;CAC1E,CAAC;AACH"}
|
|
1
|
+
{"version":3,"file":"update-check.js","names":[],"sources":["../../src/utils/update-check.ts"],"sourcesContent":["/**\n * Check for newer versions of mastracode on npm.\n */\n\nimport { execFile } from 'node:child_process';\nimport { readFileSync, realpathSync } from 'node:fs';\nimport { homedir } from 'node:os';\nimport { dirname, join, resolve, sep } from 'node:path';\nimport { fileURLToPath } from 'node:url';\n\ndeclare const MASTRACODE_VERSION: string | undefined;\n\nconst PACKAGE_NAME = 'mastracode';\nconst NPM_REGISTRY_URL = `https://registry.npmjs.org/${PACKAGE_NAME}/latest`;\n\n/** Timeout for the npm registry fetch (ms). */\nconst FETCH_TIMEOUT_MS = 5_000;\n\n// Package managers and vp are .cmd shims on Windows; execFile can't spawn those without a shell (EINVAL).\nconst USE_SHELL = process.platform === 'win32';\n\nexport type PackageManager = 'npm' | 'pnpm' | 'yarn' | 'bun';\n\nfunction matchPM(str: string): PackageManager | null {\n // Order matters: check pnpm before npm since \"npm\" is a substring of some paths\n if (/pnpm/i.test(str)) return 'pnpm';\n if (/\\byarn\\b/i.test(str)) return 'yarn';\n if (/\\bbun\\b/i.test(str)) return 'bun';\n if (/\\bnpm\\b/i.test(str)) return 'npm';\n return null;\n}\n\n/**\n * Detect which package manager was used to install mastracode globally.\n *\n * Uses a multi-tier approach:\n * 1. npm_config_user_agent env var (set when run via `pnpm run`, `npm run`, etc.)\n * 2. npm_execpath env var (fallback for script-based invocation)\n * 3. NODE_PATH env var (set by pnpm global bin stubs)\n * 4. Resolved path of the running script (process.argv[1])\n * 5. Shell-out to `pnpm list -g` to check if pnpm manages the package\n * 6. Falls back to 'npm'\n */\nexport async function detectPackageManager(): Promise<PackageManager> {\n // Tier 1: npm_config_user_agent (most reliable when available)\n const userAgent = process.env.npm_config_user_agent;\n if (userAgent) {\n const pm = matchPM(userAgent);\n if (pm) return pm;\n }\n\n // Tier 2: npm_execpath\n const execPath = process.env.npm_execpath;\n if (execPath) {\n const pm = matchPM(execPath);\n if (pm) return pm;\n }\n\n // Tier 3: NODE_PATH (pnpm global stubs set this to paths containing .pnpm)\n const nodePath = process.env.NODE_PATH;\n if (nodePath) {\n if (/[/\\\\]\\.pnpm[/\\\\]/.test(nodePath) || /[/\\\\]pnpm[/\\\\]/.test(nodePath)) return 'pnpm';\n if (/[/\\\\]\\.yarn[/\\\\]/.test(nodePath)) return 'yarn';\n if (/[/\\\\]\\.bun[/\\\\]/.test(nodePath)) return 'bun';\n }\n\n // Tier 4: Resolved script path\n try {\n const scriptPath = realpathSync(process.argv[1] ?? '');\n if (/[/\\\\]\\.?pnpm[/\\\\]/.test(scriptPath)) return 'pnpm';\n if (/[/\\\\]\\.?yarn[/\\\\]/.test(scriptPath)) return 'yarn';\n if (/[/\\\\]\\.?bun[/\\\\]/.test(scriptPath)) return 'bun';\n } catch {\n // realpathSync can fail if argv[1] is missing/broken — fall through\n }\n\n // Tier 5: Check if the package is in pnpm's global store (non-blocking)\n const pnpmResult = await new Promise<boolean>(resolve => {\n execFile(\n 'pnpm',\n ['list', '-g', '--depth=0', PACKAGE_NAME],\n { timeout: 3_000, shell: USE_SHELL },\n (error, stdout) => {\n resolve(!error && stdout.includes(PACKAGE_NAME));\n },\n );\n });\n if (pnpmResult) return 'pnpm';\n\n return 'npm';\n}\n\n/**\n * Build the shell command string a user would run to install/update globally.\n */\nexport function getInstallCommand(pm: PackageManager, version?: string): string {\n const pkg = version ? `${PACKAGE_NAME}@${version}` : `${PACKAGE_NAME}@latest`;\n switch (pm) {\n case 'pnpm':\n return `pnpm add -g ${pkg}`;\n case 'yarn':\n return `yarn global add ${pkg}`;\n case 'bun':\n return `bun add -g ${pkg}`;\n default:\n return `npm install -g ${pkg}`;\n }\n}\n\n/**\n * Read the current version, injected at build time by tsdown's `define` option.\n * Falls back to reading package.json at runtime (e.g. when running from source with tsx).\n */\nexport function getCurrentVersion(): string {\n if (typeof MASTRACODE_VERSION !== 'undefined') {\n return MASTRACODE_VERSION;\n }\n // Fallback for running from source (e.g. pnpx tsx mastracode/src/main.ts)\n const dir = dirname(fileURLToPath(import.meta.url));\n const pkgPath = resolve(dir, '../../package.json');\n const pkg = JSON.parse(readFileSync(pkgPath, 'utf-8'));\n return pkg.version;\n}\n\n/**\n * Fetch the latest published version from npm.\n * Returns null if the fetch fails (network error, timeout, etc.).\n */\nexport async function fetchLatestVersion(): Promise<string | null> {\n if (process.env.MASTRACODE_UPDATE_LATEST_VERSION) return process.env.MASTRACODE_UPDATE_LATEST_VERSION;\n\n try {\n const controller = new AbortController();\n const timeout = setTimeout(() => controller.abort(), FETCH_TIMEOUT_MS);\n const res = await fetch(NPM_REGISTRY_URL, { signal: controller.signal });\n clearTimeout(timeout);\n if (!res.ok) return null;\n const data = (await res.json()) as { version?: string };\n return data.version ?? null;\n } catch {\n return null;\n }\n}\n\nconst isPrerelease = (v: string) => v.includes('-');\n\n/**\n * Simple semver comparison: returns true if `latest` is newer than `current`.\n * Handles standard x.y.z versions. Pre-release tags only matter against the\n * matching release; two pre-releases of the same version compare equal.\n */\nexport function isNewerVersion(current: string, latest: string): boolean {\n const parse = (v: string) =>\n v\n .replace(/^v/, '')\n .split('-')[0]!\n .split('.')\n .map(s => {\n const n = Number(s);\n return Number.isFinite(n) ? n : 0;\n });\n const [cMajor = 0, cMinor = 0, cPatch = 0] = parse(current);\n const [lMajor = 0, lMinor = 0, lPatch = 0] = parse(latest);\n if (lMajor !== cMajor) return lMajor > cMajor;\n if (lMinor !== cMinor) return lMinor > cMinor;\n if (lPatch !== cPatch) return lPatch > cPatch;\n // Same x.y.z: a release is newer than its own prerelease (1.2.0 > 1.2.0-rc.1).\n return isPrerelease(current) && !isPrerelease(latest);\n}\n\n/** Max entries to show in the changelog summary. */\nconst MAX_CHANGELOG_ENTRIES = 20;\n\n/**\n * Fetch the CHANGELOG.md for a specific published version from the npm CDN\n * and extract a human-readable summary of changes.\n * Returns null if the fetch fails or the changelog can't be parsed.\n */\nexport async function fetchChangelog(version: string): Promise<string | null> {\n if (process.env.MASTRACODE_UPDATE_CHANGELOG) return process.env.MASTRACODE_UPDATE_CHANGELOG;\n\n try {\n const url = `https://unpkg.com/${PACKAGE_NAME}@${version}/CHANGELOG.md`;\n const controller = new AbortController();\n const timeout = setTimeout(() => controller.abort(), FETCH_TIMEOUT_MS);\n const res = await fetch(url, { signal: controller.signal });\n clearTimeout(timeout);\n if (!res.ok) return null;\n const text = await res.text();\n return parseChangelog(text, version);\n } catch {\n return null;\n }\n}\n\n/**\n * Extract the changelog section for a specific version and format it\n * as a concise bullet list suitable for terminal display.\n */\nexport function parseChangelog(markdown: string, version: string): string | null {\n const versionHeader = `## ${version}`;\n const startIdx = markdown.indexOf(versionHeader);\n if (startIdx === -1) return null;\n\n const afterHeader = startIdx + versionHeader.length;\n const nextHeaderIdx = markdown.indexOf('\\n## ', afterHeader);\n const section = nextHeaderIdx === -1 ? markdown.slice(afterHeader) : markdown.slice(afterHeader, nextHeaderIdx);\n\n const lines = section.split('\\n');\n const entries: string[] = [];\n let skipIndented = false;\n\n for (let i = 0; i < lines.length; i++) {\n const raw = lines[i]!;\n const trimmed = raw.trim();\n\n // Only match top-level entries (no leading whitespace before \"- \")\n const isTopLevel = raw === trimmed && trimmed.startsWith('- ');\n if (!isTopLevel) {\n // Skip indented sub-items (e.g. dependency list entries)\n if (skipIndented && /^\\s+-\\s/.test(raw)) continue;\n skipIndented = false;\n continue;\n }\n\n // Skip dependency update entries and their sub-items\n if (/^- Updated dependenc/i.test(trimmed)) {\n skipIndented = true;\n continue;\n }\n skipIndented = false;\n\n let entry = trimmed.slice(2);\n // Strip markdown links: [text](url) → text\n entry = entry.replace(/\\[([^\\]]+)\\]\\([^)]+\\)/g, '$1');\n // Strip PR references like (#15760)\n entry = entry.replace(/\\s*\\(#\\d+\\)\\s*/g, ' ');\n // Strip commit SHA references like (`abc123`)\n entry = entry.replace(/\\s*\\(`[a-f0-9]+`\\)\\s*/g, ' ');\n // Trim trailing periods for cleaner bullet display\n entry = entry.replace(/\\.\\s*$/, '');\n entry = entry.trim();\n if (entry) entries.push(entry);\n if (entries.length >= MAX_CHANGELOG_ENTRIES) break;\n }\n\n if (entries.length === 0) return null;\n return entries.map(e => ` • ${e}`).join('\\n');\n}\n\n/** Result of attempting an auto-update: exit status plus captured stderr. */\nexport interface UpdateResult {\n ok: boolean;\n stderr?: string;\n}\n\n/**\n * Run the appropriate global install command to update mastracode.\n * Exit code 0 does not prove the running binary changed (the install may land\n * in a location the PATH shim doesn't use) — verify with {@link locateOwnInstall}.\n */\nexport function runUpdate(pm: PackageManager, targetVersion: string): Promise<UpdateResult> {\n return execUpdate(pm, buildInstallArgs(pm, targetVersion));\n}\n\nfunction execUpdate(cmd: string, args: string[]): Promise<UpdateResult> {\n return new Promise(resolve => {\n execFile(cmd, args, { timeout: 60_000, shell: USE_SHELL }, (error, _stdout, stderr) => {\n const captured = (stderr ?? '').trim();\n if (error) {\n resolve({ ok: false, stderr: captured || error.message });\n } else {\n resolve({ ok: true, stderr: captured || undefined });\n }\n });\n });\n}\n\nfunction buildInstallArgs(pm: PackageManager, version: string): string[] {\n const pkg = `${PACKAGE_NAME}@${version}`;\n switch (pm) {\n case 'pnpm':\n return ['add', '-g', pkg];\n case 'yarn':\n return ['global', 'add', pkg];\n case 'bun':\n return ['add', '-g', pkg];\n default:\n return ['install', '-g', pkg];\n }\n}\n\n/** Max stderr lines to surface when an update fails. */\nconst MAX_UPDATE_ERROR_LINES = 5;\n\nfunction formatUpdaterError(stderr: string | undefined): string | null {\n if (!stderr) return null;\n const lines = stderr\n .split('\\n')\n .map(line => line.trimEnd())\n .filter(line => line.trim().length > 0);\n if (lines.length === 0) return null;\n return lines.slice(-MAX_UPDATE_ERROR_LINES).join('\\n');\n}\n\n/**\n * Find the installed mastracode package the running binary belongs to (walking\n * up from `process.argv[1]`) and read its version fresh from disk. Returns\n * null when it can't be determined, e.g. when running from source.\n */\nexport function locateOwnInstall(): { dir: string; version: string | null } | null {\n const entry = process.argv[1];\n if (!entry) return null;\n\n let dir: string;\n try {\n dir = dirname(realpathSync(entry));\n } catch {\n return null;\n }\n\n let previous = '';\n while (dir && dir !== previous) {\n try {\n const pkg = JSON.parse(readFileSync(resolve(dir, 'package.json'), 'utf-8')) as {\n name?: string;\n version?: string;\n };\n if (pkg.name === PACKAGE_NAME) {\n return { dir, version: typeof pkg.version === 'string' ? pkg.version : null };\n }\n } catch {\n // Not a readable manifest — keep walking up.\n }\n previous = dir;\n dir = dirname(dir);\n }\n\n return null;\n}\n\n/**\n * Result of an update attempt. `message` is a ready-made sentence; the other\n * fields let callers lay the result out themselves.\n */\nexport type UpdateOutcome =\n | {\n status: 'updated';\n message: string;\n /** The package manager or tool that installed the update. */\n via: string;\n }\n | {\n status: 'unchanged';\n message: string;\n /** Command the user can run to update the install they're running. */\n command: string;\n /** Where the running install lives, when known. */\n installDir?: string;\n /** Version still on disk after the update ran, when the update ran at all. */\n runningVersion?: string;\n /** Tool that ran the update (when it ran but didn't change the running install). */\n ranWith?: string;\n /** Tool that owns the install when we only suggest its command (e.g. Homebrew). */\n managedBy?: string;\n }\n | {\n status: 'failed';\n message: string;\n /** Command the user can run instead, when there is one. */\n command?: string;\n /** Why it failed: the tail of the package manager's stderr, or our own reason. */\n details?: string;\n };\n\n/**\n * Decide what to tell the user after {@link runUpdate}: `updated` when the\n * on-disk version matches the target (or can't be determined), `unchanged`\n * when the package manager succeeded but the running install didn't change,\n * `failed` on error (with the package manager's stderr).\n */\nexport function resolveUpdateOutcome(opts: {\n pm: PackageManager;\n targetVersion: string;\n result: UpdateResult;\n install: { dir: string; version: string | null } | null;\n /** Overrides the suggested manual command, e.g. when the update was delegated to the owning tool. */\n manualCommand?: string;\n /** Tool that ran the update when it wasn't `pm`, e.g. vite-plus. */\n via?: string;\n}): UpdateOutcome {\n const { pm, targetVersion, result, install } = opts;\n const cmd = opts.manualCommand ?? getInstallCommand(pm, targetVersion);\n const via = opts.via ?? pm;\n\n if (!result.ok) {\n const details = formatUpdaterError(result.stderr) ?? undefined;\n let message = `Auto-update failed. Run \\`${cmd}\\` manually.`;\n if (details) message += `\\n\\n${details}`;\n return { status: 'failed', message, command: cmd, ...(details ? { details } : {}) };\n }\n\n if (install?.version != null && install.version !== targetVersion) {\n const message = opts.manualCommand\n ? `The update ran, but the Mastra Code you are running (at ${install.dir}) is still ` +\n `v${install.version}. Try \\`${cmd}\\` manually.`\n : `The update installed, but the Mastra Code you are running (at ${install.dir}) is still ` +\n `v${install.version} — it looks like it is managed by another tool. Update it with that tool, ` +\n `or try \\`${cmd}\\`.`;\n return {\n status: 'unchanged',\n message,\n command: cmd,\n installDir: install.dir,\n runningVersion: install.version,\n ranWith: via,\n };\n }\n\n return { status: 'updated', message: `Updated to v${targetVersion}. Please restart Mastra Code.`, via };\n}\n\n/** Global node_modules directory the package manager installs into, or null when unknown. */\nfunction getGlobalRoot(pm: PackageManager): Promise<string | null> {\n const args = pm === 'yarn' ? ['global', 'dir'] : ['root', '-g'];\n return new Promise(res => {\n execFile(pm, args, { timeout: 10_000, shell: USE_SHELL }, (error, stdout) => {\n const out = (stdout ?? '').trim().split('\\n').pop()?.trim();\n if (error || !out) return res(null);\n res(pm === 'yarn' ? resolve(out, 'node_modules') : out);\n });\n });\n}\n\n/**\n * Whether updating via `pm` would touch the install the running binary uses.\n * Fail-open: returns true when either side can't be determined, so the update\n * proceeds and the post-install verification has the final word.\n */\nasync function isOwnInstallManagedBy(pm: PackageManager, install: { dir: string } | null): Promise<boolean> {\n if (!install) return true;\n const root = await getGlobalRoot(pm);\n if (!root) return true;\n try {\n return realpathSync(resolve(root, PACKAGE_NAME)) === install.dir;\n } catch {\n // Package absent from the pm's global root: installing there can't affect the running binary.\n return false;\n }\n}\n\n/** A tool other than a package manager that owns the running install, matched by path. */\ninterface InstallOwner {\n name: string;\n /** Command to run to delegate the update; omitted when we only suggest `command` to the user. */\n exec?: { cmd: string; args: string[] };\n command: string;\n}\n\nfunction findInstallOwner(dir: string, version: string): InstallOwner | null {\n if (dir.startsWith(join(homedir(), '.vite-plus') + sep)) {\n return {\n name: 'vite-plus',\n exec: { cmd: 'vp', args: ['install', '-g', `${PACKAGE_NAME}@${version}`] },\n command: `vp install -g ${PACKAGE_NAME}@${version}`,\n };\n }\n // Homebrew keeps formulas under <prefix>/Cellar/ whatever the prefix, so match the path segment.\n if (dir.split(sep).includes('Cellar')) {\n // Suggest-only: brew formulas lag npm, and `brew upgrade` can pull unrelated updates.\n return { name: 'Homebrew', command: `brew upgrade ${PACKAGE_NAME}` };\n }\n return null;\n}\n\n/**\n * What an update will do for the running install: run an install (with the\n * tool and command it will use), or nothing, with the result to show instead.\n */\nexport type UpdatePlan =\n | {\n willInstall: true;\n /** The package manager or tool that will run the install. */\n via: string;\n /** The install command, as the user would type it. */\n command: string;\n exec: { cmd: string; args: string[] };\n }\n | { willInstall: false; outcome: Extract<UpdateOutcome, { status: 'unchanged' | 'failed' }> };\n\n/**\n * Decide how to update the running install, without changing anything:\n * delegate to the tool that owns it when we recognize it, only suggest a\n * command when that tool can't be run for the user (Homebrew) or `pm` doesn't\n * manage it, otherwise run `pm`. {@link performUpdate} carries out this plan,\n * so callers can show what will happen before it starts.\n */\nexport async function planUpdate(pm: PackageManager, targetVersion: string): Promise<UpdatePlan> {\n // The registry-provided version reaches shell commands on Windows — accept only version tokens.\n if (!/^[\\w.+-]+$/.test(targetVersion)) {\n return {\n willInstall: false,\n outcome: {\n status: 'failed',\n message: `Auto-update aborted: unexpected version \"${targetVersion}\".`,\n details: `The npm registry returned an unexpected version: \"${targetVersion}\".`,\n },\n };\n }\n\n const install = locateOwnInstall();\n\n const owner = install ? findInstallOwner(install.dir, targetVersion) : null;\n if (owner) {\n if (!owner.exec) {\n const message =\n `Your Mastra Code install (at ${install!.dir}) is managed by ${owner.name}. ` +\n `Update it with \\`${owner.command}\\`.`;\n return {\n willInstall: false,\n outcome: {\n status: 'unchanged',\n message,\n command: owner.command,\n installDir: install!.dir,\n managedBy: owner.name,\n },\n };\n }\n return { willInstall: true, via: owner.name, command: owner.command, exec: owner.exec };\n }\n\n const command = getInstallCommand(pm, targetVersion);\n if (!(await isOwnInstallManagedBy(pm, install))) {\n const message =\n `Your Mastra Code install (at ${install!.dir}) is not managed by ${pm} — it looks like it was ` +\n `installed by another tool. Update it with that tool, or try \\`${command}\\`.`;\n return { willInstall: false, outcome: { status: 'unchanged', message, command, installDir: install!.dir } };\n }\n\n return { willInstall: true, via: pm, command, exec: { cmd: pm, args: buildInstallArgs(pm, targetVersion) } };\n}\n\n/**\n * Update mastracode following {@link planUpdate} (pass a plan already shown to\n * the user, or let this make one), then verify the result against the version\n * on disk.\n */\nexport async function performUpdate(\n pm: PackageManager,\n targetVersion: string,\n plan?: UpdatePlan,\n): Promise<UpdateOutcome> {\n plan ??= await planUpdate(pm, targetVersion);\n if (!plan.willInstall) return plan.outcome;\n\n const result = await execUpdate(plan.exec.cmd, plan.exec.args);\n // Re-locate so the version reflects what the install just wrote to disk.\n return resolveUpdateOutcome({\n pm,\n targetVersion,\n result,\n install: locateOwnInstall(),\n // A delegated update keeps the owning tool's command; `pm` updates keep the default wording.\n ...(plan.via === pm ? {} : { manualCommand: plan.command, via: plan.via }),\n });\n}\n"],"mappings":"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"}
|
|
@@ -25,7 +25,7 @@ async function registerWorkflowBuilderPrimitives(mastra, options) {
|
|
|
25
25
|
});
|
|
26
26
|
const workspaceTools = await createWorkspaceTools(workspace, { workspace });
|
|
27
27
|
for (const [toolId, tool] of Object.entries(workspaceTools)) mastra.addTool(tool, toolId);
|
|
28
|
-
const configuredWebTools = createConfiguredWebTools();
|
|
28
|
+
const configuredWebTools = await createConfiguredWebTools();
|
|
29
29
|
if (configuredWebTools) {
|
|
30
30
|
mastra.addTool(configuredWebTools.web_search, "web-search");
|
|
31
31
|
mastra.addTool(configuredWebTools.web_extract, "web-extract");
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"register-primitives.js","names":[],"sources":["../../src/workflows/register-primitives.ts"],"sourcesContent":["/**\n * Statically register workflow-related primitives on the mastracode Mastra\n * instance so `mastra.listTools()` + `mastra.listAgents()` — and, therefore,\n * the workflow-builder's discovery + the workflow rehydrator — see everything\n * a workflow might want to compose:\n *\n * - workflow-builder sub-agent (so `create-workflow` can delegate)\n * - code-agent (as a plain agent, callable via agent steps)\n * - workspace tools (view/write/edit/find_files/execute_command/etc.)\n * - configured model-independent web tools so workflows can compose search/extract steps\n * - notification_inbox\n * - snapshot of MCP tools from mcpManager\n *\n * Called after `controller.init()` but before `startWorkers()` in both boot\n * paths, so `loadStoredWorkflows()` (inside startWorkers) sees the full\n * registry when rehydrating previously-saved workflows.\n *\n * Tradeoffs:\n * - MCP tools are snapshotted. Servers added after boot are invisible to\n * the workflow-builder until restart.\n * - Provider-native (Anthropic/OpenAI) web\n * tools are skipped because they'd freeze workflows to one model provider.\n * - Workflow tool steps get the closed-over workspace + auth (web provider\n * key, MCP server credentials), not per-request permissions — deliberate; if\n * the user built + saved the workflow they meant to run it.\n */\nimport type { Agent } from '@mastra/core/agent';\nimport type { Mastra } from '@mastra/core/mastra';\nimport { createNotificationInboxTool } from '@mastra/core/notifications';\nimport { LocalFilesystem, LocalSandbox, Workspace, createWorkspaceTools } from '@mastra/core/workspace';\nimport { createMastraCodeWorkspaceTools } from '../agents/tool-availability.js';\nimport { LazyNotificationsStorage } from '../agents/tools.js';\nimport { workflowBuilderAgent } from '../agents/workflow-builder-agent.js';\nimport type { McpManager } from '../mcp';\nimport { MC_TOOLS } from '../tool-names.js';\nimport { createConfiguredWebTools } from '../tools/web-search.js';\n\nexport interface RegisterWorkflowBuilderPrimitivesOptions {\n projectPath: string;\n /**\n * Extra allowed paths the workspace can read/write. Mastracode normally\n * collects skill paths + sandboxAllowedPaths per-request; for the workflow\n * workspace we accept anything the caller wants up-front and trust the\n * worker process to honour the user's intent.\n */\n allowedPaths?: string[];\n /**\n * The code-agent Agent instance. Registered as `code-agent` so workflows\n * can compose it in agent steps. It already has full dynamic tool access\n * (workspace/MCP/web/etc.) via its own `tools: createDynamicTools(...)`.\n */\n codeAgent: Agent;\n /**\n * McpManager for snapshotting MCP tools at boot. Omit or pass undefined\n * if MCP is disabled — workflow-builder will just see fewer tools.\n */\n mcpManager?: McpManager;\n}\n\nexport async function registerWorkflowBuilderPrimitives(\n mastra: Mastra,\n options: RegisterWorkflowBuilderPrimitivesOptions,\n): Promise<void> {\n const { projectPath, allowedPaths = [], codeAgent, mcpManager } = options;\n\n // 1. Agents workflows can compose.\n mastra.addAgent(workflowBuilderAgent, 'workflow-builder');\n mastra.addAgent(codeAgent, 'code-agent');\n\n // 2. Workspace tools — bound to a project-rooted local workspace.\n const workspace = new Workspace({\n id: 'workflow-builder-workspace',\n name: 'Mastra Code Workspace (workflows)',\n filesystem: new LocalFilesystem({\n basePath: projectPath,\n allowedPaths: [projectPath, ...allowedPaths],\n }),\n sandbox: new LocalSandbox({ workingDirectory: projectPath, timeout: 30_000 }),\n tools: createMastraCodeWorkspaceTools(),\n });\n const workspaceTools = await createWorkspaceTools(workspace, { workspace });\n for (const [toolId, tool] of Object.entries(workspaceTools)) {\n mastra.addTool(tool, toolId);\n }\n\n // 3. Model-independent web tools. Provider-native web tools (Anthropic/OpenAI)\n // are model-locked and would freeze workflows to one provider.\n const configuredWebTools = createConfiguredWebTools();\n if (configuredWebTools) {\n mastra.addTool(configuredWebTools.web_search, 'web-search');\n mastra.addTool(configuredWebTools.web_extract, 'web-extract');\n }\n\n // 4. notification_inbox — LazyNotificationsStorage resolves the notifications\n // domain per-call rather than at boot, so it works even when the domain\n // isn't fully initialised yet.\n const storage = mastra.getStorage();\n if (storage) {\n const notificationInbox = createNotificationInboxTool({\n storage: new LazyNotificationsStorage(storage as never),\n });\n mastra.addTool(notificationInbox, MC_TOOLS.NOTIFICATION_INBOX);\n }\n\n // 5. MCP tools — snapshot the current set. Ids are already server-namespaced\n // (`${serverName}_${toolName}`), so the workflow rehydrator's\n // `mastra.getTool(id)` resolves them at run time.\n if (mcpManager) {\n for (const [toolId, tool] of Object.entries(mcpManager.getTools())) {\n mastra.addTool(tool, toolId);\n }\n }\n}\n"],"mappings":";;;;;;;;AA2DA,eAAsB,kCACpB,QACA,SACe;CACf,MAAM,EAAE,aAAa,eAAe,CAAC,GAAG,WAAW,eAAe;CAGlE,OAAO,SAAS,sBAAsB,kBAAkB;CACxD,OAAO,SAAS,WAAW,YAAY;CAGvC,MAAM,YAAY,IAAI,UAAU;EAC9B,IAAI;EACJ,MAAM;EACN,YAAY,IAAI,gBAAgB;GAC9B,UAAU;GACV,cAAc,CAAC,aAAa,GAAG,YAAY;EAC7C,CAAC;EACD,SAAS,IAAI,aAAa;GAAE,kBAAkB;GAAa,SAAS;EAAO,CAAC;EAC5E,OAAO,+BAA+B;CACxC,CAAC;CACD,MAAM,iBAAiB,MAAM,qBAAqB,WAAW,EAAE,UAAU,CAAC;CAC1E,KAAK,MAAM,CAAC,QAAQ,SAAS,OAAO,QAAQ,cAAc,GACxD,OAAO,QAAQ,MAAM,MAAM;CAK7B,MAAM,qBAAqB,yBAAyB;
|
|
1
|
+
{"version":3,"file":"register-primitives.js","names":[],"sources":["../../src/workflows/register-primitives.ts"],"sourcesContent":["/**\n * Statically register workflow-related primitives on the mastracode Mastra\n * instance so `mastra.listTools()` + `mastra.listAgents()` — and, therefore,\n * the workflow-builder's discovery + the workflow rehydrator — see everything\n * a workflow might want to compose:\n *\n * - workflow-builder sub-agent (so `create-workflow` can delegate)\n * - code-agent (as a plain agent, callable via agent steps)\n * - workspace tools (view/write/edit/find_files/execute_command/etc.)\n * - configured model-independent web tools so workflows can compose search/extract steps\n * - notification_inbox\n * - snapshot of MCP tools from mcpManager\n *\n * Called after `controller.init()` but before `startWorkers()` in both boot\n * paths, so `loadStoredWorkflows()` (inside startWorkers) sees the full\n * registry when rehydrating previously-saved workflows.\n *\n * Tradeoffs:\n * - MCP tools are snapshotted. Servers added after boot are invisible to\n * the workflow-builder until restart.\n * - Provider-native (Anthropic/OpenAI) web\n * tools are skipped because they'd freeze workflows to one model provider.\n * - Workflow tool steps get the closed-over workspace + auth (web provider\n * key, MCP server credentials), not per-request permissions — deliberate; if\n * the user built + saved the workflow they meant to run it.\n */\nimport type { Agent } from '@mastra/core/agent';\nimport type { Mastra } from '@mastra/core/mastra';\nimport { createNotificationInboxTool } from '@mastra/core/notifications';\nimport { LocalFilesystem, LocalSandbox, Workspace, createWorkspaceTools } from '@mastra/core/workspace';\nimport { createMastraCodeWorkspaceTools } from '../agents/tool-availability.js';\nimport { LazyNotificationsStorage } from '../agents/tools.js';\nimport { workflowBuilderAgent } from '../agents/workflow-builder-agent.js';\nimport type { McpManager } from '../mcp';\nimport { MC_TOOLS } from '../tool-names.js';\nimport { createConfiguredWebTools } from '../tools/web-search.js';\n\nexport interface RegisterWorkflowBuilderPrimitivesOptions {\n projectPath: string;\n /**\n * Extra allowed paths the workspace can read/write. Mastracode normally\n * collects skill paths + sandboxAllowedPaths per-request; for the workflow\n * workspace we accept anything the caller wants up-front and trust the\n * worker process to honour the user's intent.\n */\n allowedPaths?: string[];\n /**\n * The code-agent Agent instance. Registered as `code-agent` so workflows\n * can compose it in agent steps. It already has full dynamic tool access\n * (workspace/MCP/web/etc.) via its own `tools: createDynamicTools(...)`.\n */\n codeAgent: Agent;\n /**\n * McpManager for snapshotting MCP tools at boot. Omit or pass undefined\n * if MCP is disabled — workflow-builder will just see fewer tools.\n */\n mcpManager?: McpManager;\n}\n\nexport async function registerWorkflowBuilderPrimitives(\n mastra: Mastra,\n options: RegisterWorkflowBuilderPrimitivesOptions,\n): Promise<void> {\n const { projectPath, allowedPaths = [], codeAgent, mcpManager } = options;\n\n // 1. Agents workflows can compose.\n mastra.addAgent(workflowBuilderAgent, 'workflow-builder');\n mastra.addAgent(codeAgent, 'code-agent');\n\n // 2. Workspace tools — bound to a project-rooted local workspace.\n const workspace = new Workspace({\n id: 'workflow-builder-workspace',\n name: 'Mastra Code Workspace (workflows)',\n filesystem: new LocalFilesystem({\n basePath: projectPath,\n allowedPaths: [projectPath, ...allowedPaths],\n }),\n sandbox: new LocalSandbox({ workingDirectory: projectPath, timeout: 30_000 }),\n tools: createMastraCodeWorkspaceTools(),\n });\n const workspaceTools = await createWorkspaceTools(workspace, { workspace });\n for (const [toolId, tool] of Object.entries(workspaceTools)) {\n mastra.addTool(tool, toolId);\n }\n\n // 3. Model-independent web tools. Provider-native web tools (Anthropic/OpenAI)\n // are model-locked and would freeze workflows to one provider.\n const configuredWebTools = await createConfiguredWebTools();\n if (configuredWebTools) {\n mastra.addTool(configuredWebTools.web_search, 'web-search');\n mastra.addTool(configuredWebTools.web_extract, 'web-extract');\n }\n\n // 4. notification_inbox — LazyNotificationsStorage resolves the notifications\n // domain per-call rather than at boot, so it works even when the domain\n // isn't fully initialised yet.\n const storage = mastra.getStorage();\n if (storage) {\n const notificationInbox = createNotificationInboxTool({\n storage: new LazyNotificationsStorage(storage as never),\n });\n mastra.addTool(notificationInbox, MC_TOOLS.NOTIFICATION_INBOX);\n }\n\n // 5. MCP tools — snapshot the current set. Ids are already server-namespaced\n // (`${serverName}_${toolName}`), so the workflow rehydrator's\n // `mastra.getTool(id)` resolves them at run time.\n if (mcpManager) {\n for (const [toolId, tool] of Object.entries(mcpManager.getTools())) {\n mastra.addTool(tool, toolId);\n }\n }\n}\n"],"mappings":";;;;;;;;AA2DA,eAAsB,kCACpB,QACA,SACe;CACf,MAAM,EAAE,aAAa,eAAe,CAAC,GAAG,WAAW,eAAe;CAGlE,OAAO,SAAS,sBAAsB,kBAAkB;CACxD,OAAO,SAAS,WAAW,YAAY;CAGvC,MAAM,YAAY,IAAI,UAAU;EAC9B,IAAI;EACJ,MAAM;EACN,YAAY,IAAI,gBAAgB;GAC9B,UAAU;GACV,cAAc,CAAC,aAAa,GAAG,YAAY;EAC7C,CAAC;EACD,SAAS,IAAI,aAAa;GAAE,kBAAkB;GAAa,SAAS;EAAO,CAAC;EAC5E,OAAO,+BAA+B;CACxC,CAAC;CACD,MAAM,iBAAiB,MAAM,qBAAqB,WAAW,EAAE,UAAU,CAAC;CAC1E,KAAK,MAAM,CAAC,QAAQ,SAAS,OAAO,QAAQ,cAAc,GACxD,OAAO,QAAQ,MAAM,MAAM;CAK7B,MAAM,qBAAqB,MAAM,yBAAyB;CAC1D,IAAI,oBAAoB;EACtB,OAAO,QAAQ,mBAAmB,YAAY,YAAY;EAC1D,OAAO,QAAQ,mBAAmB,aAAa,aAAa;CAC9D;CAKA,MAAM,UAAU,OAAO,WAAW;CAClC,IAAI,SAAS;EACX,MAAM,oBAAoB,4BAA4B,EACpD,SAAS,IAAI,yBAAyB,OAAgB,EACxD,CAAC;EACD,OAAO,QAAQ,mBAAmB,SAAS,kBAAkB;CAC/D;CAKA,IAAI,YACF,KAAK,MAAM,CAAC,QAAQ,SAAS,OAAO,QAAQ,WAAW,SAAS,CAAC,GAC/D,OAAO,QAAQ,MAAM,MAAM;AAGjC"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@mastra/code-sdk",
|
|
3
|
-
"version": "1.15.0-alpha.
|
|
3
|
+
"version": "1.15.0-alpha.13",
|
|
4
4
|
"description": "Mastra Code SDK: the agent core behind Mastra Code (everything except the TUI) — build your own UIs and surfaces on top of it",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"publishConfig": {
|
|
@@ -63,20 +63,20 @@
|
|
|
63
63
|
"yaml": "^2.7.1",
|
|
64
64
|
"zod": "^4.6.7",
|
|
65
65
|
"@mastra/agent-browser": "0.5.4-alpha.0",
|
|
66
|
-
"@mastra/client-js": "1.56.0-alpha.
|
|
67
|
-
"@mastra/fastembed": "1.3.4-alpha.2",
|
|
66
|
+
"@mastra/client-js": "1.56.0-alpha.12",
|
|
68
67
|
"@mastra/duckdb": "1.15.0-alpha.5",
|
|
69
|
-
"@mastra/
|
|
70
|
-
"@mastra/
|
|
71
|
-
"@mastra/
|
|
72
|
-
"@mastra/
|
|
68
|
+
"@mastra/fastembed": "1.3.4-alpha.2",
|
|
69
|
+
"@mastra/core": "1.79.0-alpha.12",
|
|
70
|
+
"@mastra/mcp": "2.5.0-alpha.3",
|
|
71
|
+
"@mastra/libsql": "1.27.0-alpha.4",
|
|
73
72
|
"@mastra/observability": "1.21.0-alpha.2",
|
|
73
|
+
"@mastra/memory": "1.39.0-alpha.8",
|
|
74
|
+
"@mastra/github-signals": "0.5.2-alpha.0",
|
|
74
75
|
"@mastra/parallel": "0.1.3-alpha.1",
|
|
75
|
-
"@mastra/
|
|
76
|
-
"@mastra/pg": "1.32.0-alpha.5",
|
|
77
|
-
"@mastra/stagehand": "0.3.6-alpha.0",
|
|
76
|
+
"@mastra/pg": "1.32.0-alpha.6",
|
|
78
77
|
"@mastra/schema-compat": "1.3.16-alpha.0",
|
|
79
|
-
"@mastra/tavily": "1.1.3-alpha.1"
|
|
78
|
+
"@mastra/tavily": "1.1.3-alpha.1",
|
|
79
|
+
"@mastra/stagehand": "0.3.6-alpha.0"
|
|
80
80
|
},
|
|
81
81
|
"devDependencies": {
|
|
82
82
|
"@copilotkit/aimock": "^1.29.0",
|
|
@@ -86,8 +86,8 @@
|
|
|
86
86
|
"tsx": "^4.23.1",
|
|
87
87
|
"typescript": "^6.0.3",
|
|
88
88
|
"vitest": "4.1.11",
|
|
89
|
-
"@internal/types-builder": "0.0.119",
|
|
90
89
|
"@internal/lint": "0.0.144",
|
|
90
|
+
"@internal/types-builder": "0.0.119",
|
|
91
91
|
"@internal/workspace-test-utils": "0.0.88"
|
|
92
92
|
},
|
|
93
93
|
"engines": {
|