@open-mercato/ai-assistant 0.6.6-develop.6362.1.a3dd164018 → 0.6.6-develop.6363.1.03c5e3429a

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.
@@ -1,6 +1,8 @@
1
1
  import path from "node:path";
2
2
  import fs from "node:fs";
3
+ import { createRequire } from "node:module";
3
4
  import { fileURLToPath, pathToFileURL } from "node:url";
5
+ const requireFromHere = createRequire(import.meta.url);
4
6
  function findGeneratedFile(fileName) {
5
7
  const here = (() => {
6
8
  try {
@@ -26,7 +28,8 @@ function findGeneratedFile(fileName) {
26
28
  return null;
27
29
  }
28
30
  async function compileAndImportGenerated(tsPath) {
29
- const jsPath = tsPath.replace(/\.ts$/, ".mjs");
31
+ const useJestCjsArtifact = isJestRuntime();
32
+ const jsPath = tsPath.replace(/\.ts$/, useJestCjsArtifact ? ".jest.cjs" : ".mjs");
30
33
  const appRoot = path.dirname(path.dirname(path.dirname(tsPath)));
31
34
  if (!fs.existsSync(tsPath)) {
32
35
  throw new Error(`Generated file not found: ${tsPath}`);
@@ -36,18 +39,28 @@ async function compileAndImportGenerated(tsPath) {
36
39
  if (needsCompile) {
37
40
  const esbuild = await import("esbuild");
38
41
  const tsSource = fs.readFileSync(tsPath, "utf-8");
39
- const aliasRewritten = rewriteGeneratedAliasImports(tsSource, appRoot);
42
+ const aliasRewritten = rewriteGeneratedAliasImportsForRuntime(
43
+ tsSource,
44
+ appRoot,
45
+ useJestCjsArtifact ? "cjs" : "esm"
46
+ );
40
47
  const result = await esbuild.transform(aliasRewritten, {
41
48
  loader: "ts",
42
- format: "esm",
49
+ format: useJestCjsArtifact ? "cjs" : "esm",
43
50
  target: "node18",
44
51
  sourcemap: false,
45
52
  sourcefile: tsPath
46
53
  });
47
54
  fs.writeFileSync(jsPath, result.code);
48
55
  }
56
+ if (useJestCjsArtifact) {
57
+ return requireFromHere(jsPath);
58
+ }
49
59
  return await import(pathToFileURL(jsPath).href);
50
60
  }
61
+ function isJestRuntime() {
62
+ return typeof process.env.JEST_WORKER_ID === "string";
63
+ }
51
64
  const UNSAFE_JS_STRING_CHAR_ESCAPES = {
52
65
  60: "\\u003C",
53
66
  // < — HTML/script-tag breakout
@@ -68,10 +81,14 @@ function toSafeJsStringLiteral(value) {
68
81
  return escapeUnsafeJsStringChars(JSON.stringify(value));
69
82
  }
70
83
  function rewriteGeneratedAliasImports(source, appRoot) {
84
+ return rewriteGeneratedAliasImportsForRuntime(source, appRoot, "esm");
85
+ }
86
+ function rewriteGeneratedAliasImportsForRuntime(source, appRoot, runtime) {
71
87
  const generatedDir = path.join(appRoot, ".mercato", "generated");
72
88
  const toResolvedLiteral = (target) => {
73
89
  const candidate = fs.existsSync(target) ? target : fs.existsSync(target + ".ts") ? target + ".ts" : target;
74
- return toSafeJsStringLiteral(pathToFileURL(candidate).href);
90
+ const specifier = runtime === "esm" ? pathToFileURL(candidate).href : candidate;
91
+ return toSafeJsStringLiteral(specifier);
75
92
  };
76
93
  const resolveAlias = (relativePath) => toResolvedLiteral(path.join(appRoot, relativePath));
77
94
  const resolveRelative = (specifier) => toResolvedLiteral(path.resolve(generatedDir, specifier));
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../../src/modules/ai_assistant/lib/generated-registry-loader.ts"],
4
- "sourcesContent": ["/**\n * Runtime loader for `.mercato/generated/*.generated.ts` registry files.\n *\n * The generated registries import their entries through the `@/` path alias\n * (e.g. `@/.mercato/generated/ai-tools.generated`). That alias is only\n * understood by the Next.js bundler \u2014 in a standalone Node process (the\n * `mcp:dev` / `mcp:serve` MCP servers, the CLI tool-test runner) a raw\n * `import('@/.mercato/...')` throws `ERR_MODULE_NOT_FOUND: Cannot find\n * package '@/.mercato'` because Node treats `@/` as a package specifier.\n *\n * These helpers locate the generated `.ts` file on disk and compile-and-import\n * it with esbuild (transpile-only), rewriting `@/` aliases to absolute paths.\n * This mirrors `loadBootstrapData` in\n * `@open-mercato/shared/lib/bootstrap/dynamicLoader` and works in both the\n * monorepo and standalone apps.\n */\nimport path from 'node:path'\nimport fs from 'node:fs'\nimport { fileURLToPath, pathToFileURL } from 'node:url'\n\n/**\n * Locate a generated registry file (e.g. `ai-tools.generated.ts`) without\n * hardcoding the workspace layout. Searches upward from this module's compiled\n * location for a `apps/mercato/.mercato/generated/<fileName>` (monorepo), then\n * falls back to cwd-relative lookups (standalone apps run from the app dir).\n */\nexport function findGeneratedFile(fileName: string): string | null {\n const here = (() => {\n try {\n return fileURLToPath(import.meta.url)\n } catch {\n return null\n }\n })()\n\n if (here) {\n let cursor = path.dirname(here)\n for (let i = 0; i < 12; i++) {\n const candidate = path.join(cursor, 'apps', 'mercato', '.mercato', 'generated', fileName)\n if (fs.existsSync(candidate)) return candidate\n const next = path.dirname(cursor)\n if (next === cursor) break\n cursor = next\n }\n }\n // Fallbacks: cwd-based lookup (CLI invoked from apps/mercato, or a standalone\n // app whose root holds `.mercato/generated`).\n const fromCwd = path.resolve(process.cwd(), 'apps', 'mercato', '.mercato', 'generated', fileName)\n if (fs.existsSync(fromCwd)) return fromCwd\n const fromCwdDirect = path.resolve(process.cwd(), '.mercato', 'generated', fileName)\n if (fs.existsSync(fromCwdDirect)) return fromCwdDirect\n return null\n}\n\n/**\n * Compile-and-import a generated registry file on the fly. Rewrites the entry\n * specifiers Node can't resolve standalone (`@/...` aliases and the\n * `../../src/...` relative imports the generator emits for `@app` local\n * modules) to absolute file URLs, transpiles TS \u2192 ESM, and emits a sibling\n * `.mjs` we can `import()` from Node. Cached on mtime so repeat calls in the\n * same process don't recompile.\n *\n * Transpile-only (no bundling): the generated registries declare an array\n * literal whose entries are static `import(\"\u2026\")` arrow functions \u2014 we want\n * those `import()` strings to stay as runtime imports so Node resolves them\n * lazily through the workspace's normal module resolution. Eagerly bundling\n * pulls Next.js / route-handler internals into the `.mjs` and breaks at runtime\n * (e.g. `next/server` package-exports map).\n */\nexport async function compileAndImportGenerated(tsPath: string): Promise<Record<string, unknown>> {\n const jsPath = tsPath.replace(/\\.ts$/, '.mjs')\n // appRoot is two directories up from `.mercato/generated/<file>.ts`.\n const appRoot = path.dirname(path.dirname(path.dirname(tsPath)))\n\n if (!fs.existsSync(tsPath)) {\n throw new Error(`Generated file not found: ${tsPath}`)\n }\n\n const jsExists = fs.existsSync(jsPath)\n const needsCompile =\n !jsExists || fs.statSync(tsPath).mtimeMs > fs.statSync(jsPath).mtimeMs\n\n if (needsCompile) {\n const esbuild = await import('esbuild')\n const tsSource = fs.readFileSync(tsPath, 'utf-8')\n const aliasRewritten = rewriteGeneratedAliasImports(tsSource, appRoot)\n const result = await esbuild.transform(aliasRewritten, {\n loader: 'ts',\n format: 'esm',\n target: 'node18',\n sourcemap: false,\n sourcefile: tsPath,\n })\n fs.writeFileSync(jsPath, result.code)\n }\n\n return (await import(pathToFileURL(jsPath).href)) as Record<string, unknown>\n}\n\nconst UNSAFE_JS_STRING_CHAR_ESCAPES: Record<number, string> = {\n 0x3c: '\\\\u003C', // < \u2014 HTML/script-tag breakout\n 0x3e: '\\\\u003E', // > \u2014 HTML/script-tag breakout\n 0x2028: '\\\\u2028', // line separator \u2014 string content but a statement terminator pre-ES2019\n 0x2029: '\\\\u2029', // paragraph separator \u2014 same\n}\n\n/**\n * Escape characters that `JSON.stringify` leaves intact but which can still\n * break out of (or alter the meaning of) the JavaScript string literal that the\n * stringified value is embedded into \u2014 notably `<`/`>` (HTML/script-tag\n * breakout) and the U+2028 / U+2029 line separators (valid string content but\n * statement terminators in pre-ES2019 parsers). Apply this on top of\n * `JSON.stringify` so the emitted import source stays well-formed regardless of\n * the resolved path. Exported for unit testing.\n */\nexport function escapeUnsafeJsStringChars(value: string): string {\n return value.replace(\n /[<>\\u2028\\u2029]/g,\n (char) => UNSAFE_JS_STRING_CHAR_ESCAPES[char.charCodeAt(0)],\n )\n}\n\n/**\n * Stringify a resolved path into a JavaScript string literal that is safe to\n * embed in generated source: `JSON.stringify` handles quoting/standard escapes,\n * and `escapeUnsafeJsStringChars` neutralizes the characters it leaves intact.\n */\nfunction toSafeJsStringLiteral(value: string): string {\n return escapeUnsafeJsStringChars(JSON.stringify(value))\n}\n\n/**\n * Rewrite the two specifier shapes the generator emits for module entries in a\n * generated registry to absolute `file://` URLs Node can resolve in a\n * standalone process:\n *\n * 1. `@/...` path-alias imports (both `from \"@/x\"` and dynamic `import(\"@/x\")`).\n * The `@/` alias is a Next.js bundler convention; outside the bundler Node\n * treats `@/...` as a bare package specifier and throws\n * `ERR_MODULE_NOT_FOUND`. Resolved against `appRoot`.\n * 2. `../../src/...` relative imports the generator emits for `@app` local\n * modules (e.g. `from \"../../src/modules/<id>/ai-tools\"`). esbuild's\n * transform (transpile-only) leaves these untouched, so the compiled\n * `.mjs` keeps an extensionless relative specifier that resolves to a\n * `.ts` file with no compiled `.js`/`.mjs` sibling \u2014 Node ESM then throws\n * `ERR_MODULE_NOT_FOUND`. Resolved against the generated file's directory\n * (`<appRoot>/.mercato/generated`), the location the generator wrote them\n * relative to. Package-backed modules (`@open-mercato/*`) are unaffected \u2014\n * their bare specifiers resolve through `node_modules` to compiled `.js`.\n *\n * Both shapes reuse the same `.ts`-suffix probe so a source-only TypeScript\n * target is loaded directly (Node strips types). Other specifiers (bare\n * packages, sibling `./` imports) are left untouched. Exported for unit testing.\n */\nexport function rewriteGeneratedAliasImports(source: string, appRoot: string): string {\n const generatedDir = path.join(appRoot, '.mercato', 'generated')\n const toResolvedLiteral = (target: string): string => {\n const candidate = fs.existsSync(target)\n ? target\n : fs.existsSync(target + '.ts')\n ? target + '.ts'\n : target\n return toSafeJsStringLiteral(pathToFileURL(candidate).href)\n }\n const resolveAlias = (relativePath: string): string =>\n toResolvedLiteral(path.join(appRoot, relativePath))\n const resolveRelative = (specifier: string): string =>\n toResolvedLiteral(path.resolve(generatedDir, specifier))\n return source\n .replace(/from\\s+[\"']@\\/([^\"']+)[\"']/g, (_match, relativePath: string) => {\n return `from ${resolveAlias(relativePath)}`\n })\n .replace(/import\\s*\\(\\s*[\"']@\\/([^\"']+)[\"']\\s*\\)/g, (_match, relativePath: string) => {\n return `import(${resolveAlias(relativePath)})`\n })\n .replace(/from\\s+[\"']((?:\\.\\.\\/)+src\\/[^\"']+)[\"']/g, (_match, specifier: string) => {\n return `from ${resolveRelative(specifier)}`\n })\n .replace(/import\\s*\\(\\s*[\"']((?:\\.\\.\\/)+src\\/[^\"']+)[\"']\\s*\\)/g, (_match, specifier: string) => {\n return `import(${resolveRelative(specifier)})`\n })\n}\n\n/**\n * Compile-and-import `api-routes.generated.ts` and register its manifest with\n * the shared registry. Many module tools are \"API-backed\" \u2014 their handlers\n * delegate to `createAiApiOperationRunner`, which fails closed with\n * \"No API route manifest registered\" unless the manifest is present. In the\n * Next.js app this is wired at bootstrap, but the standalone MCP servers\n * (`mcp:dev` / `mcp:serve`) bootstrap DI without it, so we register it here.\n *\n * Idempotent: `registerApiRouteManifests` replaces the stored manifest, so\n * calling this repeatedly (e.g. per-request HTTP handlers) is safe. Returns the\n * number of registered routes (0 when the generated file is absent).\n */\nexport async function ensureApiRouteManifestsRegistered(): Promise<number> {\n const registry = await import('@open-mercato/shared/modules/registry')\n // Already wired (e.g. the Next.js app bootstrap, or a prior call). Leave the\n // existing manifest untouched so we never interfere with the in-app agents\n // framework, which registers it at bootstrap with its own override pipeline.\n const existing = registry.getApiRouteManifests()\n if (existing.length > 0) return existing.length\n\n const tsPath = findGeneratedFile('api-routes.generated.ts')\n if (!tsPath) return 0\n try {\n const mod = await compileAndImportGenerated(tsPath)\n const apiRoutes = (mod as { apiRoutes?: unknown }).apiRoutes\n if (!Array.isArray(apiRoutes)) return 0\n registry.registerApiRouteManifests(\n apiRoutes as Parameters<typeof registry.registerApiRouteManifests>[0],\n )\n return apiRoutes.length\n } catch (error) {\n console.warn(\n '[MCP Tools] Could not register api-routes manifest:',\n error instanceof Error ? error.message : error,\n )\n return 0\n }\n}\n"],
5
- "mappings": "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",
4
+ "sourcesContent": ["/**\n * Runtime loader for `.mercato/generated/*.generated.ts` registry files.\n *\n * The generated registries import their entries through the `@/` path alias\n * (e.g. `@/.mercato/generated/ai-tools.generated`). That alias is only\n * understood by the Next.js bundler \u2014 in a standalone Node process (the\n * `mcp:dev` / `mcp:serve` MCP servers, the CLI tool-test runner) a raw\n * `import('@/.mercato/...')` throws `ERR_MODULE_NOT_FOUND: Cannot find\n * package '@/.mercato'` because Node treats `@/` as a package specifier.\n *\n * These helpers locate the generated `.ts` file on disk and compile-and-import\n * it with esbuild (transpile-only), rewriting `@/` aliases to absolute paths.\n * This mirrors `loadBootstrapData` in\n * `@open-mercato/shared/lib/bootstrap/dynamicLoader` and works in both the\n * monorepo and standalone apps.\n */\nimport path from 'node:path'\nimport fs from 'node:fs'\nimport { createRequire } from 'node:module'\nimport { fileURLToPath, pathToFileURL } from 'node:url'\n\nconst requireFromHere = createRequire(import.meta.url)\n\n/**\n * Locate a generated registry file (e.g. `ai-tools.generated.ts`) without\n * hardcoding the workspace layout. Searches upward from this module's compiled\n * location for a `apps/mercato/.mercato/generated/<fileName>` (monorepo), then\n * falls back to cwd-relative lookups (standalone apps run from the app dir).\n */\nexport function findGeneratedFile(fileName: string): string | null {\n const here = (() => {\n try {\n return fileURLToPath(import.meta.url)\n } catch {\n return null\n }\n })()\n\n if (here) {\n let cursor = path.dirname(here)\n for (let i = 0; i < 12; i++) {\n const candidate = path.join(cursor, 'apps', 'mercato', '.mercato', 'generated', fileName)\n if (fs.existsSync(candidate)) return candidate\n const next = path.dirname(cursor)\n if (next === cursor) break\n cursor = next\n }\n }\n // Fallbacks: cwd-based lookup (CLI invoked from apps/mercato, or a standalone\n // app whose root holds `.mercato/generated`).\n const fromCwd = path.resolve(process.cwd(), 'apps', 'mercato', '.mercato', 'generated', fileName)\n if (fs.existsSync(fromCwd)) return fromCwd\n const fromCwdDirect = path.resolve(process.cwd(), '.mercato', 'generated', fileName)\n if (fs.existsSync(fromCwdDirect)) return fromCwdDirect\n return null\n}\n\n/**\n * Compile-and-import a generated registry file on the fly. Rewrites the entry\n * specifiers Node can't resolve standalone (`@/...` aliases and the\n * `../../src/...` relative imports the generator emits for `@app` local\n * modules) to absolute file URLs, transpiles TS \u2192 ESM, and emits a sibling\n * `.mjs` we can `import()` from Node. Cached on mtime so repeat calls in the\n * same process don't recompile.\n *\n * Transpile-only (no bundling): the generated registries declare an array\n * literal whose entries are static `import(\"\u2026\")` arrow functions \u2014 we want\n * those `import()` strings to stay as runtime imports so Node resolves them\n * lazily through the workspace's normal module resolution. Eagerly bundling\n * pulls Next.js / route-handler internals into the `.mjs` and breaks at runtime\n * (e.g. `next/server` package-exports map).\n */\nexport async function compileAndImportGenerated(tsPath: string): Promise<Record<string, unknown>> {\n const useJestCjsArtifact = isJestRuntime()\n const jsPath = tsPath.replace(/\\.ts$/, useJestCjsArtifact ? '.jest.cjs' : '.mjs')\n // appRoot is two directories up from `.mercato/generated/<file>.ts`.\n const appRoot = path.dirname(path.dirname(path.dirname(tsPath)))\n\n if (!fs.existsSync(tsPath)) {\n throw new Error(`Generated file not found: ${tsPath}`)\n }\n\n const jsExists = fs.existsSync(jsPath)\n const needsCompile =\n !jsExists || fs.statSync(tsPath).mtimeMs > fs.statSync(jsPath).mtimeMs\n\n if (needsCompile) {\n const esbuild = await import('esbuild')\n const tsSource = fs.readFileSync(tsPath, 'utf-8')\n const aliasRewritten = rewriteGeneratedAliasImportsForRuntime(\n tsSource,\n appRoot,\n useJestCjsArtifact ? 'cjs' : 'esm',\n )\n const result = await esbuild.transform(aliasRewritten, {\n loader: 'ts',\n format: useJestCjsArtifact ? 'cjs' : 'esm',\n target: 'node18',\n sourcemap: false,\n sourcefile: tsPath,\n })\n fs.writeFileSync(jsPath, result.code)\n }\n\n if (useJestCjsArtifact) {\n return requireFromHere(jsPath) as Record<string, unknown>\n }\n return (await import(pathToFileURL(jsPath).href)) as Record<string, unknown>\n}\n\nfunction isJestRuntime(): boolean {\n return typeof process.env.JEST_WORKER_ID === 'string'\n}\n\nconst UNSAFE_JS_STRING_CHAR_ESCAPES: Record<number, string> = {\n 0x3c: '\\\\u003C', // < \u2014 HTML/script-tag breakout\n 0x3e: '\\\\u003E', // > \u2014 HTML/script-tag breakout\n 0x2028: '\\\\u2028', // line separator \u2014 string content but a statement terminator pre-ES2019\n 0x2029: '\\\\u2029', // paragraph separator \u2014 same\n}\n\n/**\n * Escape characters that `JSON.stringify` leaves intact but which can still\n * break out of (or alter the meaning of) the JavaScript string literal that the\n * stringified value is embedded into \u2014 notably `<`/`>` (HTML/script-tag\n * breakout) and the U+2028 / U+2029 line separators (valid string content but\n * statement terminators in pre-ES2019 parsers). Apply this on top of\n * `JSON.stringify` so the emitted import source stays well-formed regardless of\n * the resolved path. Exported for unit testing.\n */\nexport function escapeUnsafeJsStringChars(value: string): string {\n return value.replace(\n /[<>\\u2028\\u2029]/g,\n (char) => UNSAFE_JS_STRING_CHAR_ESCAPES[char.charCodeAt(0)],\n )\n}\n\n/**\n * Stringify a resolved path into a JavaScript string literal that is safe to\n * embed in generated source: `JSON.stringify` handles quoting/standard escapes,\n * and `escapeUnsafeJsStringChars` neutralizes the characters it leaves intact.\n */\nfunction toSafeJsStringLiteral(value: string): string {\n return escapeUnsafeJsStringChars(JSON.stringify(value))\n}\n\n/**\n * Rewrite the two specifier shapes the generator emits for module entries in a\n * generated registry to absolute `file://` URLs Node can resolve in a\n * standalone process:\n *\n * 1. `@/...` path-alias imports (both `from \"@/x\"` and dynamic `import(\"@/x\")`).\n * The `@/` alias is a Next.js bundler convention; outside the bundler Node\n * treats `@/...` as a bare package specifier and throws\n * `ERR_MODULE_NOT_FOUND`. Resolved against `appRoot`.\n * 2. `../../src/...` relative imports the generator emits for `@app` local\n * modules (e.g. `from \"../../src/modules/<id>/ai-tools\"`). esbuild's\n * transform (transpile-only) leaves these untouched, so the compiled\n * `.mjs` keeps an extensionless relative specifier that resolves to a\n * `.ts` file with no compiled `.js`/`.mjs` sibling \u2014 Node ESM then throws\n * `ERR_MODULE_NOT_FOUND`. Resolved against the generated file's directory\n * (`<appRoot>/.mercato/generated`), the location the generator wrote them\n * relative to. Package-backed modules (`@open-mercato/*`) are unaffected \u2014\n * their bare specifiers resolve through `node_modules` to compiled `.js`.\n *\n * Both shapes reuse the same `.ts`-suffix probe so a source-only TypeScript\n * target is loaded directly (Node strips types). Other specifiers (bare\n * packages, sibling `./` imports) are left untouched. Exported for unit testing.\n */\nexport function rewriteGeneratedAliasImports(source: string, appRoot: string): string {\n return rewriteGeneratedAliasImportsForRuntime(source, appRoot, 'esm')\n}\n\nfunction rewriteGeneratedAliasImportsForRuntime(\n source: string,\n appRoot: string,\n runtime: 'esm' | 'cjs',\n): string {\n const generatedDir = path.join(appRoot, '.mercato', 'generated')\n const toResolvedLiteral = (target: string): string => {\n const candidate = fs.existsSync(target)\n ? target\n : fs.existsSync(target + '.ts')\n ? target + '.ts'\n : target\n const specifier = runtime === 'esm' ? pathToFileURL(candidate).href : candidate\n return toSafeJsStringLiteral(specifier)\n }\n const resolveAlias = (relativePath: string): string =>\n toResolvedLiteral(path.join(appRoot, relativePath))\n const resolveRelative = (specifier: string): string =>\n toResolvedLiteral(path.resolve(generatedDir, specifier))\n return source\n .replace(/from\\s+[\"']@\\/([^\"']+)[\"']/g, (_match, relativePath: string) => {\n return `from ${resolveAlias(relativePath)}`\n })\n .replace(/import\\s*\\(\\s*[\"']@\\/([^\"']+)[\"']\\s*\\)/g, (_match, relativePath: string) => {\n return `import(${resolveAlias(relativePath)})`\n })\n .replace(/from\\s+[\"']((?:\\.\\.\\/)+src\\/[^\"']+)[\"']/g, (_match, specifier: string) => {\n return `from ${resolveRelative(specifier)}`\n })\n .replace(/import\\s*\\(\\s*[\"']((?:\\.\\.\\/)+src\\/[^\"']+)[\"']\\s*\\)/g, (_match, specifier: string) => {\n return `import(${resolveRelative(specifier)})`\n })\n}\n\n/**\n * Compile-and-import `api-routes.generated.ts` and register its manifest with\n * the shared registry. Many module tools are \"API-backed\" \u2014 their handlers\n * delegate to `createAiApiOperationRunner`, which fails closed with\n * \"No API route manifest registered\" unless the manifest is present. In the\n * Next.js app this is wired at bootstrap, but the standalone MCP servers\n * (`mcp:dev` / `mcp:serve`) bootstrap DI without it, so we register it here.\n *\n * Idempotent: `registerApiRouteManifests` replaces the stored manifest, so\n * calling this repeatedly (e.g. per-request HTTP handlers) is safe. Returns the\n * number of registered routes (0 when the generated file is absent).\n */\nexport async function ensureApiRouteManifestsRegistered(): Promise<number> {\n const registry = await import('@open-mercato/shared/modules/registry')\n // Already wired (e.g. the Next.js app bootstrap, or a prior call). Leave the\n // existing manifest untouched so we never interfere with the in-app agents\n // framework, which registers it at bootstrap with its own override pipeline.\n const existing = registry.getApiRouteManifests()\n if (existing.length > 0) return existing.length\n\n const tsPath = findGeneratedFile('api-routes.generated.ts')\n if (!tsPath) return 0\n try {\n const mod = await compileAndImportGenerated(tsPath)\n const apiRoutes = (mod as { apiRoutes?: unknown }).apiRoutes\n if (!Array.isArray(apiRoutes)) return 0\n registry.registerApiRouteManifests(\n apiRoutes as Parameters<typeof registry.registerApiRouteManifests>[0],\n )\n return apiRoutes.length\n } catch (error) {\n console.warn(\n '[MCP Tools] Could not register api-routes manifest:',\n error instanceof Error ? error.message : error,\n )\n return 0\n }\n}\n"],
5
+ "mappings": "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",
6
6
  "names": []
7
7
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@open-mercato/ai-assistant",
3
- "version": "0.6.6-develop.6362.1.a3dd164018",
3
+ "version": "0.6.6-develop.6363.1.03c5e3429a",
4
4
  "license": "MIT",
5
5
  "type": "module",
6
6
  "engines": {
@@ -99,16 +99,16 @@
99
99
  "zod-to-json-schema": "^3.25.2"
100
100
  },
101
101
  "peerDependencies": {
102
- "@open-mercato/shared": "0.6.6-develop.6362.1.a3dd164018",
103
- "@open-mercato/ui": "0.6.6-develop.6362.1.a3dd164018",
102
+ "@open-mercato/shared": "0.6.6-develop.6363.1.03c5e3429a",
103
+ "@open-mercato/ui": "0.6.6-develop.6363.1.03c5e3429a",
104
104
  "react": "^19.0.0",
105
105
  "react-dom": "^19.0.0",
106
106
  "zod": ">=3.23.0"
107
107
  },
108
108
  "devDependencies": {
109
- "@open-mercato/cli": "0.6.6-develop.6362.1.a3dd164018",
110
- "@open-mercato/shared": "0.6.6-develop.6362.1.a3dd164018",
111
- "@open-mercato/ui": "0.6.6-develop.6362.1.a3dd164018",
109
+ "@open-mercato/cli": "0.6.6-develop.6363.1.03c5e3429a",
110
+ "@open-mercato/shared": "0.6.6-develop.6363.1.03c5e3429a",
111
+ "@open-mercato/ui": "0.6.6-develop.6363.1.03c5e3429a",
112
112
  "@types/react": "^19.2.17",
113
113
  "@types/react-dom": "^19.2.3",
114
114
  "react": "19.2.7",
@@ -7,6 +7,7 @@ import {
7
7
  rewriteGeneratedAliasImports,
8
8
  escapeUnsafeJsStringChars,
9
9
  findGeneratedFile,
10
+ compileAndImportGenerated,
10
11
  ensureApiRouteManifestsRegistered,
11
12
  } from '../generated-registry-loader'
12
13
 
@@ -189,6 +190,35 @@ describe('findGeneratedFile', () => {
189
190
  })
190
191
  })
191
192
 
193
+ describe('compileAndImportGenerated', () => {
194
+ it('uses a Jest-safe CJS artifact instead of an existing ESM .mjs artifact', async () => {
195
+ const appRoot = makeTempDir()
196
+ const generatedDir = path.join(appRoot, '.mercato', 'generated')
197
+ const moduleDir = path.join(appRoot, 'src', 'modules', 'example')
198
+ fs.mkdirSync(generatedDir, { recursive: true })
199
+ fs.mkdirSync(moduleDir, { recursive: true })
200
+ fs.writeFileSync(path.join(moduleDir, 'ai-agents.ts'), 'export const aiAgents = [{ id: "example.agent" }]\n')
201
+
202
+ const generatedPath = path.join(generatedDir, 'ai-agents.generated.ts')
203
+ fs.writeFileSync(
204
+ generatedPath,
205
+ [
206
+ 'import * as ExampleAgents from "../../src/modules/example/ai-agents"',
207
+ 'export const allAiAgents = ExampleAgents.aiAgents',
208
+ ].join('\n'),
209
+ )
210
+ fs.writeFileSync(
211
+ path.join(generatedDir, 'ai-agents.generated.mjs'),
212
+ 'import broken from "this-existing-esm-artifact-should-not-be-used";\nexport const allAiAgents = [broken]\n',
213
+ )
214
+
215
+ const mod = await compileAndImportGenerated(generatedPath)
216
+
217
+ expect(mod.allAiAgents).toEqual([{ id: 'example.agent' }])
218
+ expect(fs.existsSync(path.join(generatedDir, 'ai-agents.generated.jest.cjs'))).toBe(true)
219
+ })
220
+ })
221
+
192
222
  describe('ensureApiRouteManifestsRegistered', () => {
193
223
  // Regression for "Operation runner manifest unavailable: No API route manifest
194
224
  // registered" when calling API-backed module tools over MCP. The standalone
@@ -16,8 +16,11 @@
16
16
  */
17
17
  import path from 'node:path'
18
18
  import fs from 'node:fs'
19
+ import { createRequire } from 'node:module'
19
20
  import { fileURLToPath, pathToFileURL } from 'node:url'
20
21
 
22
+ const requireFromHere = createRequire(import.meta.url)
23
+
21
24
  /**
22
25
  * Locate a generated registry file (e.g. `ai-tools.generated.ts`) without
23
26
  * hardcoding the workspace layout. Searches upward from this module's compiled
@@ -68,7 +71,8 @@ export function findGeneratedFile(fileName: string): string | null {
68
71
  * (e.g. `next/server` package-exports map).
69
72
  */
70
73
  export async function compileAndImportGenerated(tsPath: string): Promise<Record<string, unknown>> {
71
- const jsPath = tsPath.replace(/\.ts$/, '.mjs')
74
+ const useJestCjsArtifact = isJestRuntime()
75
+ const jsPath = tsPath.replace(/\.ts$/, useJestCjsArtifact ? '.jest.cjs' : '.mjs')
72
76
  // appRoot is two directories up from `.mercato/generated/<file>.ts`.
73
77
  const appRoot = path.dirname(path.dirname(path.dirname(tsPath)))
74
78
 
@@ -83,10 +87,14 @@ export async function compileAndImportGenerated(tsPath: string): Promise<Record<
83
87
  if (needsCompile) {
84
88
  const esbuild = await import('esbuild')
85
89
  const tsSource = fs.readFileSync(tsPath, 'utf-8')
86
- const aliasRewritten = rewriteGeneratedAliasImports(tsSource, appRoot)
90
+ const aliasRewritten = rewriteGeneratedAliasImportsForRuntime(
91
+ tsSource,
92
+ appRoot,
93
+ useJestCjsArtifact ? 'cjs' : 'esm',
94
+ )
87
95
  const result = await esbuild.transform(aliasRewritten, {
88
96
  loader: 'ts',
89
- format: 'esm',
97
+ format: useJestCjsArtifact ? 'cjs' : 'esm',
90
98
  target: 'node18',
91
99
  sourcemap: false,
92
100
  sourcefile: tsPath,
@@ -94,9 +102,16 @@ export async function compileAndImportGenerated(tsPath: string): Promise<Record<
94
102
  fs.writeFileSync(jsPath, result.code)
95
103
  }
96
104
 
105
+ if (useJestCjsArtifact) {
106
+ return requireFromHere(jsPath) as Record<string, unknown>
107
+ }
97
108
  return (await import(pathToFileURL(jsPath).href)) as Record<string, unknown>
98
109
  }
99
110
 
111
+ function isJestRuntime(): boolean {
112
+ return typeof process.env.JEST_WORKER_ID === 'string'
113
+ }
114
+
100
115
  const UNSAFE_JS_STRING_CHAR_ESCAPES: Record<number, string> = {
101
116
  0x3c: '\\u003C', // < — HTML/script-tag breakout
102
117
  0x3e: '\\u003E', // > — HTML/script-tag breakout
@@ -153,6 +168,14 @@ function toSafeJsStringLiteral(value: string): string {
153
168
  * packages, sibling `./` imports) are left untouched. Exported for unit testing.
154
169
  */
155
170
  export function rewriteGeneratedAliasImports(source: string, appRoot: string): string {
171
+ return rewriteGeneratedAliasImportsForRuntime(source, appRoot, 'esm')
172
+ }
173
+
174
+ function rewriteGeneratedAliasImportsForRuntime(
175
+ source: string,
176
+ appRoot: string,
177
+ runtime: 'esm' | 'cjs',
178
+ ): string {
156
179
  const generatedDir = path.join(appRoot, '.mercato', 'generated')
157
180
  const toResolvedLiteral = (target: string): string => {
158
181
  const candidate = fs.existsSync(target)
@@ -160,7 +183,8 @@ export function rewriteGeneratedAliasImports(source: string, appRoot: string): s
160
183
  : fs.existsSync(target + '.ts')
161
184
  ? target + '.ts'
162
185
  : target
163
- return toSafeJsStringLiteral(pathToFileURL(candidate).href)
186
+ const specifier = runtime === 'esm' ? pathToFileURL(candidate).href : candidate
187
+ return toSafeJsStringLiteral(specifier)
164
188
  }
165
189
  const resolveAlias = (relativePath: string): string =>
166
190
  toResolvedLiteral(path.join(appRoot, relativePath))