@open-mercato/ai-assistant 0.6.5-develop.4882.1.901c3aa813 → 0.6.5-develop.5033.1.c970204a3f

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,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../../src/modules/ai_assistant/lib/codemode-tools.ts"],
4
- "sourcesContent": ["/**\n * Code Mode Tools\n *\n * Two meta-tools that replace all individual API/schema/module tools:\n * - search: Query the OpenAPI spec + entity graph programmatically\n * - execute: Make API calls via a sandboxed api.request() wrapper\n *\n * The AI writes JavaScript that runs in a node:vm sandbox with injected globals.\n */\n\nimport { z } from 'zod'\nimport type { RbacService } from '@open-mercato/core/modules/auth/services/rbacService'\nimport { registerMcpTool } from './tool-registry'\nimport type { McpToolContext } from './types'\nimport { createSandbox } from './sandbox'\nimport { truncateResult } from './truncate'\nimport { hasRequiredFeatures } from './auth'\nimport { getApiEndpoints, getRawOpenApiSpec, type ApiEndpoint } from './api-endpoint-index'\nimport {\n getCachedEntityGraph,\n inferModuleFromEntity,\n type EntityGraph,\n} from './entity-graph'\nimport {\n lookupSearchCache,\n storeSearchResult,\n buildMemoryContext,\n buildSearchLabel,\n incrementToolCallCount,\n} from './session-memory'\nimport { fetchWithTimeout, resolveTimeoutMs } from '@open-mercato/shared/lib/http/fetchWithTimeout'\n\nconst DEFAULT_AI_API_REQUEST_TIMEOUT_MS = 30_000\n\nfunction resolveAiApiRequestTimeoutMs(): number {\n const raw = process.env.AI_API_REQUEST_TIMEOUT_MS\n const parsed = raw ? Number.parseInt(raw, 10) : undefined\n return resolveTimeoutMs(parsed, DEFAULT_AI_API_REQUEST_TIMEOUT_MS)\n}\n\n/**\n * Cached spec object combining OpenAPI paths + entity schemas.\n */\nlet cachedCodeModeSpec: Record<string, unknown> | null = null\n\n/**\n * Cached TypeScript type stubs for common CRUD endpoints.\n * Generated once at startup from the OpenAPI spec.\n */\nlet cachedCommonTypes: string | null = null\n\nexport const CODE_MODE_REQUIRED_FEATURES = ['ai_assistant.view'] as const\n\n/**\n * Build the merged spec object for the search tool.\n */\nasync function getCodeModeSpec(): Promise<Record<string, unknown>> {\n if (cachedCodeModeSpec) return cachedCodeModeSpec\n\n const rawSpec = await getRawOpenApiSpec()\n const graph = getCachedEntityGraph()\n\n const paths = (rawSpec?.paths ?? {}) as Record<string, Record<string, unknown>>\n const entitySchemas = graph ? buildEntitySchemas(graph) : []\n\n const spec: Record<string, unknown> = {\n paths,\n info: rawSpec?.info,\n components: rawSpec?.components,\n entitySchemas,\n }\n\n // --- Helper functions injected into sandbox ---\n\n /**\n * spec.findEndpoints(keyword) \u2014 find all endpoints matching a keyword.\n * Returns compact list: [{ path, methods }]\n */\n spec.findEndpoints = (keyword: string) => {\n const kw = keyword.toLowerCase()\n return Object.entries(paths)\n .filter(([path]) => path.toLowerCase().includes(kw))\n .map(([path, methods]) => ({\n path,\n methods: Object.keys(methods).filter((m) => m !== 'parameters'),\n }))\n }\n\n /**\n * spec.describeEndpoint(path, method) \u2014 compact endpoint profile with working example.\n * Returns: { path, method, summary, requiredFields, optionalFields, nestedCollections, example, relatedEndpoints, relatedEntity }\n * For full schema access, use: spec.paths[path][method].requestBody\n */\n spec.describeEndpoint = (path: string, method: string) => {\n const pathObj = paths[path] as Record<string, unknown> | undefined\n if (!pathObj) return null\n\n const endpoint = pathObj[method.toLowerCase()] as Record<string, unknown> | undefined\n if (!endpoint) return null\n\n // Extract requestBody JSON Schema\n const bodySchema = extractRequestBodySchema(endpoint)\n const bodyProps = (bodySchema?.properties ?? {}) as Record<string, Record<string, unknown>>\n const bodyRequired = (bodySchema?.required ?? []) as string[]\n\n // Split fields into required (with types) vs optional (names only)\n const requiredFields: Array<{ name: string; type: string; format?: string }> = []\n const optionalFields: string[] = []\n const nestedCollections: Array<{\n field: string\n type: string\n requiredFields: Array<{ name: string; type: string }>\n commonFields: string[]\n }> = []\n\n for (const [name, prop] of Object.entries(bodyProps)) {\n const propType = (prop.type as string) || 'string'\n\n // Detect nested array collections (e.g. lines, items, addresses)\n if (propType === 'array' && prop.items && (prop.items as Record<string, unknown>).type === 'object') {\n const itemSchema = prop.items as Record<string, unknown>\n const itemProps = (itemSchema.properties ?? {}) as Record<string, Record<string, unknown>>\n const itemRequired = (itemSchema.required ?? []) as string[]\n\n const nestedRequired = itemRequired.map((n) => ({\n name: n,\n type: ((itemProps[n]?.type as string) || 'string'),\n }))\n\n // Common fields: first few non-required fields that are likely user-provided\n const nestedOptional = Object.keys(itemProps).filter((n) => !itemRequired.includes(n))\n const commonFields = nestedOptional.slice(0, 6)\n\n nestedCollections.push({\n field: name,\n type: 'array',\n requiredFields: nestedRequired,\n commonFields,\n })\n continue\n }\n\n if (bodyRequired.includes(name)) {\n const field: { name: string; type: string; format?: string } = { name, type: propType }\n if (prop.format) field.format = prop.format as string\n requiredFields.push(field)\n } else {\n optionalFields.push(name)\n }\n }\n\n // Generate minimal working example from required fields + nested collections\n const example: Record<string, unknown> = {}\n for (const field of requiredFields) {\n example[field.name] = generatePlaceholder(field.type, field.format)\n }\n for (const collection of nestedCollections) {\n const itemExample: Record<string, unknown> = {}\n for (const field of collection.requiredFields) {\n itemExample[field.name] = generatePlaceholder(field.type)\n }\n // Add first 2 common fields to the example\n for (const name of collection.commonFields.slice(0, 2)) {\n itemExample[name] = '<value>'\n }\n example[collection.field] = [itemExample]\n }\n\n // Find related endpoints sharing the same module prefix\n const segments = path.replace('/api/', '').split('/')\n const moduleSegment = segments[0]\n const resourceName = segments[1] || segments[0]\n const modulePrefix = `/api/${moduleSegment}/`\n const relatedEndpoints = Object.entries(paths)\n .filter(([p]) => p.startsWith(modulePrefix) && p !== path && !p.includes('{'))\n .map(([p, methods]) => ({\n path: p,\n methods: Object.keys(methods as Record<string, unknown>).filter((m) => m !== 'parameters'),\n }))\n .slice(0, 8)\n\n // Compact entity: className + relationship summary\n const resourceNorm = resourceName.replace(/-/g, '_')\n const resourceSingular = resourceNorm.endsWith('s') ? resourceNorm.slice(0, -1) : resourceNorm\n const moduleSingular = moduleSegment.endsWith('s') ? moduleSegment.slice(0, -1) : moduleSegment\n const prefixedTable = `${moduleSingular}_${resourceNorm}`\n\n const entity = entitySchemas.find((e: Record<string, unknown>) => {\n const table = ((e.tableName as string) || '').toLowerCase()\n const cls = ((e.className as string) || '').toLowerCase()\n const mod = ((e.module as string) || '').toLowerCase()\n if (table === resourceNorm || table === prefixedTable) return true\n if (cls.includes(moduleSingular) && cls.includes(resourceSingular)) return true\n if (mod === moduleSegment && cls.includes(resourceSingular)) return true\n if (cls === resourceSingular || cls.includes(resourceSingular)) return true\n return false\n }) || null\n\n let relatedEntity: string | null = null\n if (entity) {\n const ent = entity as Record<string, unknown>\n const rels = (ent.relationships as Array<{ relationship: string; target: string }>) || []\n const relSummary = rels.map((r) => `${r.relationship}: ${r.target}`).join(', ')\n relatedEntity = `${ent.className}${relSummary ? ` (${relSummary})` : ''}`\n }\n\n // GET endpoints: include query parameters compactly\n const parameters = method.toLowerCase() === 'get'\n ? (endpoint.parameters as Array<Record<string, unknown>> || [])\n .filter((p) => p.in === 'query')\n .map((p) => p.name as string)\n : undefined\n\n return {\n path,\n method: method.toUpperCase(),\n summary: endpoint.summary || endpoint.description,\n ...(parameters && parameters.length > 0 ? { queryParams: parameters } : {}),\n ...(requiredFields.length > 0 ? { requiredFields } : {}),\n ...(optionalFields.length > 0 ? { optionalFields } : {}),\n ...(nestedCollections.length > 0 ? { nestedCollections } : {}),\n ...(Object.keys(example).length > 0 ? { example } : {}),\n ...(relatedEndpoints.length > 0 ? { relatedEndpoints } : {}),\n relatedEntity,\n }\n }\n\n /**\n * spec.describeEntity(keyword) \u2014 find entity by keyword and return its full schema.\n * Returns: { className, tableName, module, fields, relationships }\n */\n spec.describeEntity = (keyword: string) => {\n const kw = keyword.toLowerCase()\n return entitySchemas.find((e: Record<string, unknown>) => {\n const cls = (e.className as string || '').toLowerCase()\n const table = (e.tableName as string || '').toLowerCase()\n return cls.includes(kw) || table.includes(kw)\n }) || null\n }\n\n cachedCodeModeSpec = spec\n return spec\n}\n\n/**\n * Extract the JSON Schema from an OpenAPI endpoint's requestBody.\n * Handles the common `content['application/json'].schema` path.\n */\nfunction extractRequestBodySchema(\n endpoint: Record<string, unknown>\n): Record<string, unknown> | null {\n const requestBody = endpoint.requestBody as Record<string, unknown> | undefined\n if (!requestBody) return null\n\n const content = requestBody.content as Record<string, Record<string, unknown>> | undefined\n if (!content) return null\n\n const jsonContent = content['application/json']\n if (!jsonContent) return null\n\n return (jsonContent.schema as Record<string, unknown>) || null\n}\n\n/**\n * Generate a placeholder value for a given JSON Schema type.\n */\nfunction generatePlaceholder(type: string, format?: string): unknown {\n if (format === 'uuid' || format === 'objectId') return '<uuid>'\n if (format === 'date-time' || format === 'date') return '<date>'\n if (format === 'email') return '<email>'\n switch (type) {\n case 'string': return '<string>'\n case 'number':\n case 'integer': return 0\n case 'boolean': return false\n case 'array': return []\n default: return '<value>'\n }\n}\n\n/**\n * Common CRUD endpoints to pre-generate types for.\n * These are the endpoints the agent uses most and where debug spirals happen.\n */\nconst COMMON_ENDPOINTS: Array<{ path: string; method: string; typeName: string }> = [\n { path: '/api/sales/quotes', method: 'post', typeName: 'CreateQuote' },\n { path: '/api/sales/orders', method: 'post', typeName: 'CreateOrder' },\n { path: '/api/sales/invoices', method: 'post', typeName: 'CreateInvoice' },\n { path: '/api/customers/companies', method: 'post', typeName: 'CreateCompany' },\n { path: '/api/customers/people', method: 'post', typeName: 'CreatePerson' },\n { path: '/api/customers/deals', method: 'post', typeName: 'CreateDeal' },\n { path: '/api/catalog/products', method: 'post', typeName: 'CreateProduct' },\n { path: '/api/customers/companies', method: 'put', typeName: 'UpdateCompany' },\n { path: '/api/customers/people', method: 'put', typeName: 'UpdatePerson' },\n { path: '/api/sales/quotes', method: 'put', typeName: 'UpdateQuote' },\n]\n\n/**\n * Generate TypeScript-like type stubs from the OpenAPI spec for common endpoints.\n * This runs once at startup and injects types into the execute tool description\n * so the LLM sees the correct payload shape without needing to call describeEndpoint.\n */\nasync function generateCommonTypes(): Promise<string> {\n if (cachedCommonTypes) return cachedCommonTypes\n\n const rawSpec = await getRawOpenApiSpec()\n if (!rawSpec?.paths) {\n cachedCommonTypes = ''\n return ''\n }\n\n const paths = rawSpec.paths as Record<string, Record<string, unknown>>\n const typeLines: string[] = ['Available types for api.request() body:\\n']\n\n for (const { path, method, typeName } of COMMON_ENDPOINTS) {\n const pathObj = paths[path] as Record<string, unknown> | undefined\n if (!pathObj) continue\n\n const endpoint = pathObj[method] as Record<string, unknown> | undefined\n if (!endpoint) continue\n\n const bodySchema = extractRequestBodySchema(endpoint)\n if (!bodySchema?.properties) continue\n\n const typeStr = schemaToTypeString(\n typeName,\n bodySchema,\n `${method.toUpperCase()} ${path}`,\n )\n if (typeStr) typeLines.push(typeStr)\n }\n\n if (typeLines.length <= 1) {\n cachedCommonTypes = ''\n return ''\n }\n\n cachedCommonTypes = typeLines.join('\\n')\n console.error(`[Code Mode] Generated ${typeLines.length - 1} common type stubs`)\n return cachedCommonTypes\n}\n\n/**\n * Convert a JSON Schema object to a compact TypeScript-like type string.\n * Produces a single-line or multi-line type declaration the LLM can use directly.\n */\nfunction schemaToTypeString(\n typeName: string,\n schema: Record<string, unknown>,\n comment: string,\n): string | null {\n const props = schema.properties as Record<string, Record<string, unknown>> | undefined\n if (!props) return null\n\n const required = new Set((schema.required as string[]) || [])\n\n // Skip internal fields that the sandbox injects automatically\n const skipFields = new Set(['tenantId', 'organizationId'])\n\n const fields: string[] = []\n const nestedTypes: string[] = []\n\n for (const [name, prop] of Object.entries(props)) {\n if (skipFields.has(name)) continue\n if (!prop || typeof prop !== 'object') continue\n\n const isRequired = required.has(name)\n const optMark = isRequired ? '' : '?'\n\n // Detect nested array of objects \u2192 extract as separate type\n if (\n prop.type === 'array' &&\n prop.items &&\n (prop.items as Record<string, unknown>).type === 'object'\n ) {\n const itemTypeName = `${typeName}${capitalize(singularize(name))}`\n const itemSchema = prop.items as Record<string, unknown>\n const nestedType = schemaToTypeString(itemTypeName, itemSchema, '')\n if (nestedType) nestedTypes.push(nestedType)\n fields.push(`${name}${optMark}: ${itemTypeName}[]`)\n continue\n }\n\n const propType = resolvePropertyType(prop)\n fields.push(`${name}${optMark}: ${propType}`)\n }\n\n if (fields.length === 0) return null\n\n const commentLine = comment ? `// ${comment}\\n` : ''\n const nested = nestedTypes.length > 0 ? nestedTypes.join('\\n') + '\\n' : ''\n return `${nested}${commentLine}type ${typeName} = { ${fields.join('; ')} }`\n}\n\n/**\n * Resolve a JSON Schema property to a compact TypeScript type string.\n */\nfunction resolvePropertyType(prop: Record<string, unknown>): string {\n // Handle anyOf (nullable types)\n if (prop.anyOf && Array.isArray(prop.anyOf)) {\n const variants = (prop.anyOf as Array<Record<string, unknown> | null>).filter(\n (s): s is Record<string, unknown> => s != null,\n )\n const nonNull = variants.filter((s) => s.type !== 'null')\n if (nonNull.length === 1) {\n return resolvePropertyType(nonNull[0]) + ' | null'\n }\n if (nonNull.length > 1) {\n return nonNull.map((s) => resolvePropertyType(s)).join(' | ')\n }\n }\n\n // Handle enum\n if (prop.enum && Array.isArray(prop.enum)) {\n return (prop.enum as string[]).map((v) => `'${v}'`).join(' | ')\n }\n\n const type = prop.type as string\n const format = prop.format as string | undefined\n\n if (type === 'array') {\n const items = prop.items as Record<string, unknown> | undefined\n if (items) return `${resolvePropertyType(items)}[]`\n return 'unknown[]'\n }\n\n if (type === 'object') return 'object'\n\n if (format === 'uuid') return 'string /*uuid*/'\n if (format === 'date-time') return 'string /*ISO date*/'\n if (format === 'date') return 'string /*date*/'\n if (format === 'email') return 'string /*email*/'\n\n switch (type) {\n case 'string': return 'string'\n case 'number':\n case 'integer': return 'number'\n case 'boolean': return 'boolean'\n default: return 'unknown'\n }\n}\n\nfunction capitalize(s: string): string {\n return s.charAt(0).toUpperCase() + s.slice(1)\n}\n\nfunction singularize(s: string): string {\n if (s.endsWith('ies')) return s.slice(0, -3) + 'y'\n if (s.endsWith('ses')) return s.slice(0, -2)\n if (s.endsWith('s') && !s.endsWith('ss')) return s.slice(0, -1)\n return s\n}\n\n/**\n * Format a 400 API error response into a human-readable fix instruction.\n * Parses Zod-style validation errors and produces a concise message the LLM can act on.\n */\nfunction formatValidationError(data: unknown): string {\n if (!data || typeof data !== 'object') {\n return `Validation error: ${JSON.stringify(data)}`\n }\n\n // Raw Zod v4 array format: [{ expected, code, path, message }]\n if (Array.isArray(data)) {\n const issues = data as Array<Record<string, unknown>>\n const parts = issues.slice(0, 5).map((issue) => {\n const path = Array.isArray(issue.path) ? issue.path.join('.') : ''\n const msg = issue.message as string || `expected ${issue.expected}` || issue.code as string || 'invalid'\n return path ? `${path}: ${msg}` : msg\n })\n if (parts.length > 0) {\n return `Validation failed \u2014 ${parts.join('; ')}. Fix the listed fields and retry.`\n }\n }\n\n const obj = data as Record<string, unknown>\n\n // Zod v4 flat format: { fieldErrors: { field: [messages] }, formErrors: [messages] }\n if (obj.fieldErrors && typeof obj.fieldErrors === 'object') {\n const fieldErrors = obj.fieldErrors as Record<string, string[]>\n const parts: string[] = []\n for (const [field, messages] of Object.entries(fieldErrors)) {\n if (Array.isArray(messages) && messages.length > 0) {\n parts.push(`${field}: ${messages[0]}`)\n }\n }\n const formErrors = obj.formErrors as string[] | undefined\n if (Array.isArray(formErrors) && formErrors.length > 0) {\n parts.push(formErrors[0])\n }\n if (parts.length > 0) {\n return `Validation failed \u2014 ${parts.join('; ')}. Fix the listed fields and retry.`\n }\n }\n\n // Zod v3 format: { issues: [{ path: [...], message, code }] }\n if (obj.issues && Array.isArray(obj.issues)) {\n const issues = obj.issues as Array<Record<string, unknown>>\n const parts = issues.slice(0, 5).map((issue) => {\n const path = Array.isArray(issue.path) ? issue.path.join('.') : ''\n const msg = issue.message as string || issue.code as string || 'invalid'\n return path ? `${path}: ${msg}` : msg\n })\n return `Validation failed \u2014 ${parts.join('; ')}. Fix the listed fields and retry.`\n }\n\n // Our API error format: { error: string, details: ... }\n if (obj.error && typeof obj.error === 'string') {\n const details = obj.details\n if (details && typeof details === 'object') {\n return formatValidationError(details)\n }\n return obj.error\n }\n\n // Generic: { message: string }\n if (obj.message && typeof obj.message === 'string') {\n return obj.message\n }\n\n // Fallback: compact JSON\n const json = JSON.stringify(data)\n if (json.length > 500) {\n return `Validation error (truncated): ${json.slice(0, 500)}...`\n }\n return `Validation error: ${json}`\n}\n\n/**\n * Build entity schema array from the entity graph.\n */\nfunction buildEntitySchemas(graph: EntityGraph) {\n return graph.nodes.map((node) => {\n const relationships = graph.edges\n .filter((edge) => edge.source === node.className)\n .map((edge) => ({\n relationship: edge.relationship,\n target: edge.target,\n property: edge.property,\n nullable: edge.nullable,\n }))\n\n return {\n className: node.className,\n tableName: node.tableName,\n module: inferModuleFromEntity(node.className, node.tableName),\n fields: node.properties,\n relationships,\n }\n })\n}\n\n/**\n * Detect mutation HTTP methods in code via static analysis.\n * Returns which methods were found (POST, PUT, PATCH, DELETE).\n */\nfunction detectMutationInCode(code: string): { hasMutation: boolean; methods: string[] } {\n const methods: string[] = []\n const pattern = /method:\\s*['\"](\\w+)['\"]/gi\n let match\n while ((match = pattern.exec(code)) !== null) {\n const method = match[1].toUpperCase()\n if (['POST', 'PUT', 'PATCH', 'DELETE'].includes(method)) {\n methods.push(method)\n }\n }\n return { hasMutation: methods.length > 0, methods }\n}\n\n/**\n * Load and register the two Code Mode tools.\n * Generates TypeScript type stubs for common endpoints at startup.\n * @returns Number of tools registered (always 2)\n */\nexport async function loadCodeModeTools(): Promise<number> {\n const commonTypes = await generateCommonTypes()\n registerSearchTool()\n registerExecuteTool(commonTypes)\n return 2\n}\n\n/**\n * search \u2014 Query the OpenAPI spec and entity graph programmatically.\n */\nfunction registerSearchTool(): void {\n registerMcpTool(\n {\n name: 'search',\n description: `Query the OpenAPI spec and entity schemas. READ-ONLY, no side effects.\nGlobals: spec.findEndpoints(keyword), spec.describeEndpoint(path, method), spec.describeEntity(keyword), spec.paths, spec.entitySchemas.\nUse BEFORE execute to learn endpoint schemas for CREATE/UPDATE. Skip for common paths (companies, people, orders, quotes, products).`,\n inputSchema: z.object({\n code: z\n .string()\n .describe(\n 'An async arrow function that queries spec, e.g. async () => spec.paths[\"/api/customers/companies\"]'\n ),\n }),\n requiredFeatures: [...CODE_MODE_REQUIRED_FEATURES],\n handler: async (input: { code: string }, ctx: McpToolContext) => {\n const codePreview = input.code.slice(0, 120).replace(/\\n/g, ' ')\n console.error(`[AI Usage] search: code=\"${codePreview}${input.code.length > 120 ? '...' : ''}\"`)\n\n // Check session memory for cached result\n if (ctx.sessionId) {\n const cached = lookupSearchCache(ctx.sessionId, input.code)\n if (cached) {\n console.error(`[AI Usage] search: CACHE HIT (label=\"${cached.label}\")`)\n const memoryContext = buildMemoryContext(ctx.sessionId)\n return {\n success: true,\n result: cached.result,\n fromCache: true,\n _memoryContext: memoryContext,\n }\n }\n\n // Enforce tool call limit\n const { count, exceeded } = incrementToolCallCount(ctx.sessionId)\n if (exceeded) {\n console.error(`[AI Usage] search: TOOL CALL LIMIT EXCEEDED (count=${count})`)\n return {\n success: false,\n error: 'Tool call limit exceeded. Summarize what you know and respond to the user.',\n }\n }\n }\n\n const spec = await getCodeModeSpec()\n const sandbox = createSandbox({ spec })\n const result = await sandbox.execute(input.code)\n\n if (result.error) {\n console.error(`[AI Usage] search: ERROR in ${result.durationMs}ms \u2014 ${result.error}`)\n return {\n success: false,\n error: result.error,\n logs: result.logs,\n durationMs: result.durationMs,\n }\n }\n\n const truncated = truncateResult(result.result)\n console.error(`[AI Usage] search: OK in ${result.durationMs}ms \u2014 ${truncated.length} chars`)\n\n // Store in session memory\n if (ctx.sessionId) {\n const label = buildSearchLabel(input.code)\n storeSearchResult(ctx.sessionId, input.code, truncated, label)\n }\n\n const memoryContext = ctx.sessionId ? buildMemoryContext(ctx.sessionId) : undefined\n return {\n success: true,\n result: truncated,\n logs: result.logs,\n durationMs: result.durationMs,\n _memoryContext: memoryContext,\n }\n },\n },\n { moduleId: 'codemode' }\n )\n}\n\n/**\n * execute \u2014 Run JavaScript that can make API calls via api.request().\n */\nfunction registerExecuteTool(commonTypes: string): void {\n const typesBlock = commonTypes\n ? `\\n\\n${commonTypes}`\n : ''\n\n registerMcpTool(\n {\n name: 'execute',\n description: `Make API calls. Returns JSON.\nGlobals: api.request({ method, path, query?, body? }) \u2192 { success, statusCode, data }, context { tenantId, organizationId, userId }.\nRULES: For FIND/LIST \u2192 GET only (1 call). For UPDATE \u2192 PUT to collection path with id in BODY. NEVER PUT/POST/DELETE unless user explicitly asked to change data. Before ANY write operation (POST/PUT/DELETE), you MUST use the AskUserQuestion tool to get explicit user confirmation. Do NOT just ask in text \u2014 use the tool so execution pauses until the user responds.${typesBlock}`,\n inputSchema: z.object({\n code: z\n .string()\n .describe(\n 'Async arrow function. For reads: async () => api.request({ method: \"GET\", path: \"/api/customers/companies\" }). For updates: async () => api.request({ method: \"PUT\", path: \"/api/customers/companies\", body: { id: \"<uuid>\", name: \"New Name\" } }). id goes in BODY not URL.'\n ),\n }),\n requiredFeatures: [...CODE_MODE_REQUIRED_FEATURES],\n handler: async (input: { code: string }, ctx: McpToolContext) => {\n const codePreview = input.code.slice(0, 120).replace(/\\n/g, ' ')\n console.error(`[AI Usage] execute: code=\"${codePreview}${input.code.length > 120 ? '...' : ''}\" user=${ctx.userId || 'unknown'}`)\n\n // Enforce tool call limit\n if (ctx.sessionId) {\n const { count, exceeded } = incrementToolCallCount(ctx.sessionId)\n if (exceeded) {\n console.error(`[AI Usage] execute: TOOL CALL LIMIT EXCEEDED (count=${count})`)\n return {\n success: false,\n error: 'Tool call limit exceeded. Summarize what you know and respond to the user.',\n }\n }\n }\n\n // Detect mutations via static analysis \u2014 cap API calls for safety\n const mutationInfo = detectMutationInCode(input.code)\n const maxApiCalls = mutationInfo.hasMutation ? 20 : 50\n if (mutationInfo.hasMutation) {\n console.error(`[AI Usage] execute: MUTATION DETECTED (${mutationInfo.methods.join(',')}) \u2014 capping API calls to ${maxApiCalls}`)\n }\n let apiCallCount = 0\n\n const apiRequestFn = createApiRequestFn(ctx, () => {\n apiCallCount++\n if (apiCallCount > maxApiCalls) {\n throw new Error(`API call limit exceeded (max ${maxApiCalls})`)\n }\n })\n\n const context = {\n tenantId: ctx.tenantId,\n organizationId: ctx.organizationId,\n userId: ctx.userId,\n }\n\n const sandbox = createSandbox(\n { api: { request: apiRequestFn }, context },\n { maxApiCalls }\n )\n\n const result = await sandbox.execute(input.code)\n\n if (result.error) {\n console.error(`[AI Usage] execute: ERROR in ${result.durationMs}ms \u2014 apiCalls=${apiCallCount} \u2014 ${result.error}`)\n return {\n success: false,\n error: result.error,\n logs: result.logs,\n durationMs: result.durationMs,\n apiCallCount,\n }\n }\n\n const truncated = truncateResult(result.result)\n console.error(`[AI Usage] execute: OK in ${result.durationMs}ms \u2014 apiCalls=${apiCallCount} \u2014 ${truncated.length} chars`)\n\n const memoryContext = ctx.sessionId ? buildMemoryContext(ctx.sessionId) : undefined\n return {\n success: true,\n result: truncated,\n logs: result.logs,\n durationMs: result.durationMs,\n apiCallCount,\n _memoryContext: memoryContext,\n }\n },\n },\n { moduleId: 'codemode' }\n )\n}\n\n/**\n * Create the api.request() function for the execute sandbox.\n */\nfunction createApiRequestFn(\n ctx: McpToolContext,\n onCall: () => void\n): (params: {\n method: string\n path: string\n query?: Record<string, string>\n body?: Record<string, unknown>\n}) => Promise<unknown> {\n const baseUrl =\n process.env.NEXT_PUBLIC_API_BASE_URL ||\n process.env.NEXT_PUBLIC_APP_URL ||\n process.env.APP_URL ||\n 'http://localhost:3000'\n\n return async (params) => {\n onCall()\n\n const { method, path, query, body } = params\n const callStart = Date.now()\n const normalizedMethod = method.toUpperCase()\n const apiPath = normalizeApiRequestPath(path)\n const authorization = await authorizeCodeModeApiRequest(ctx, normalizedMethod, apiPath)\n\n if (!authorization.allowed) {\n const callDuration = Date.now() - callStart\n console.error(\n `[AI Usage] api.request: ${normalizedMethod} ${apiPath} \u2192 ${authorization.statusCode} in ${callDuration}ms (blocked by Code Mode RBAC)`\n )\n return {\n success: false,\n statusCode: authorization.statusCode,\n error: authorization.error,\n details: authorization.details,\n }\n }\n\n let url = `${baseUrl}${apiPath}`\n\n // Build query parameters\n const queryParams: Record<string, string> = { ...query }\n\n if (normalizedMethod === 'GET') {\n if (ctx.tenantId) queryParams.tenantId = ctx.tenantId\n if (ctx.organizationId) queryParams.organizationId = ctx.organizationId\n }\n\n if (Object.keys(queryParams).length > 0) {\n const separator = url.includes('?') ? '&' : '?'\n url += separator + new URLSearchParams(queryParams).toString()\n }\n\n // Build request body with context injection\n let requestBody: Record<string, unknown> | undefined\n if (['POST', 'PUT', 'PATCH'].includes(normalizedMethod)) {\n requestBody = { ...body }\n if (ctx.tenantId) requestBody.tenantId = ctx.tenantId\n if (ctx.organizationId) requestBody.organizationId = ctx.organizationId\n }\n\n // Build headers\n const headers: Record<string, string> = {\n 'Content-Type': 'application/json',\n }\n if (ctx.apiKeySecret) headers['X-API-Key'] = ctx.apiKeySecret\n if (ctx.tenantId) headers['X-Tenant-Id'] = ctx.tenantId\n if (ctx.organizationId) headers['X-Organization-Id'] = ctx.organizationId\n\n // Execute request using host fetch (not sandbox)\n const response = await fetchWithTimeout(url, {\n method: normalizedMethod,\n headers,\n body: requestBody ? JSON.stringify(requestBody) : undefined,\n timeoutMs: resolveAiApiRequestTimeoutMs(),\n })\n\n const responseText = await response.text()\n const data = tryParseJson(responseText)\n const callDuration = Date.now() - callStart\n\n if (!response.ok) {\n console.error(`[AI Usage] api.request: ${normalizedMethod} ${apiPath} \u2192 ${response.status} in ${callDuration}ms`)\n\n // Format 400 validation errors into a clear fix instruction for the LLM\n if (response.status === 400) {\n return {\n success: false,\n statusCode: 400,\n error: formatValidationError(data),\n }\n }\n\n return {\n success: false,\n statusCode: response.status,\n error: `API error ${response.status}`,\n details: data,\n }\n }\n\n console.error(`[AI Usage] api.request: ${normalizedMethod} ${apiPath} \u2192 ${response.status} in ${callDuration}ms (${responseText.length} bytes)`)\n\n // Add mutation warning for non-GET calls\n if (!['GET', 'HEAD', 'OPTIONS'].includes(normalizedMethod)) {\n return {\n success: true,\n statusCode: response.status,\n data,\n _note: 'WRITE operation performed. Only do writes when user explicitly requested data modification.',\n }\n }\n\n return {\n success: true,\n statusCode: response.status,\n data,\n }\n }\n}\n\ntype CodeModeApiAuthorization =\n | { allowed: true; endpoint: ApiEndpoint }\n | { allowed: false; statusCode: number; error: string; details?: Record<string, unknown> }\n\nexport async function authorizeCodeModeApiRequest(\n ctx: McpToolContext,\n method: string,\n path: string\n): Promise<CodeModeApiAuthorization> {\n const normalizedMethod = method.toUpperCase()\n const normalizedPath = normalizeApiRequestPath(path)\n const endpoint = await findCodeModeApiEndpoint(normalizedMethod, normalizedPath)\n\n if (!endpoint) {\n return {\n allowed: false,\n statusCode: 403,\n error: `Code Mode cannot call undocumented API endpoint ${normalizedMethod} ${normalizedPath}`,\n }\n }\n\n const rbacService = resolveRbacService(ctx)\n const requiredFeatures = endpoint.requiredFeatures ?? []\n\n if (requiredFeatures.length > 0) {\n if (hasRequiredFeatures(requiredFeatures, ctx.userFeatures, ctx.isSuperAdmin, rbacService)) {\n return { allowed: true, endpoint }\n }\n\n return {\n allowed: false,\n statusCode: 403,\n error: `Insufficient permissions for ${normalizedMethod} ${normalizedPath}`,\n details: { requiredFeatures, operationId: endpoint.operationId },\n }\n }\n\n if (isUnsafeHttpMethod(normalizedMethod)) {\n return {\n allowed: false,\n statusCode: 403,\n error: `Code Mode cannot call mutation endpoint without declared required features: ${normalizedMethod} ${normalizedPath}`,\n details: { operationId: endpoint.operationId },\n }\n }\n\n return { allowed: true, endpoint }\n}\n\nfunction resolveRbacService(ctx: McpToolContext): RbacService | undefined {\n try {\n return ctx.container.resolve('rbacService') as RbacService\n } catch {\n return undefined\n }\n}\n\nasync function findCodeModeApiEndpoint(\n method: string,\n path: string\n): Promise<ApiEndpoint | null> {\n const endpoints = await getApiEndpoints()\n const exactMatch = endpoints.find((endpoint) => endpoint.method === method && endpoint.path === path)\n if (exactMatch) {\n return exactMatch\n }\n\n return endpoints.find((endpoint) => endpoint.method === method && matchApiEndpointPath(endpoint.path, path)) ?? null\n}\n\nexport function matchApiEndpointPath(endpointPath: string, requestPath: string): boolean {\n const normalizedEndpointPath = normalizeApiRequestPath(endpointPath)\n const normalizedRequestPath = normalizeApiRequestPath(requestPath)\n\n if (normalizedEndpointPath === normalizedRequestPath) {\n return true\n }\n\n const endpointSegments = normalizedEndpointPath.split('/').filter(Boolean)\n const requestSegments = normalizedRequestPath.split('/').filter(Boolean)\n\n if (endpointSegments.length !== requestSegments.length) {\n return false\n }\n\n return endpointSegments.every((segment, index) => {\n if (isPathParameterSegment(segment)) {\n return requestSegments[index].length > 0\n }\n return segment === requestSegments[index]\n })\n}\n\nfunction normalizeApiRequestPath(path: string): string {\n const [rawPath] = path.split('?')\n const normalizedPath = rawPath.startsWith('/api')\n ? rawPath\n : `/api${rawPath.startsWith('/') ? rawPath : `/${rawPath}`}`\n\n if (normalizedPath.length > 1 && normalizedPath.endsWith('/')) {\n return normalizedPath.slice(0, -1)\n }\n\n return normalizedPath\n}\n\nfunction isPathParameterSegment(segment: string): boolean {\n return (\n (segment.startsWith('{') && segment.endsWith('}')) ||\n (segment.startsWith('[') && segment.endsWith(']')) ||\n segment.startsWith(':')\n )\n}\n\nfunction isUnsafeHttpMethod(method: string): boolean {\n return !['GET', 'HEAD', 'OPTIONS'].includes(method.toUpperCase())\n}\n\nfunction tryParseJson(text: string): unknown {\n try {\n return JSON.parse(text)\n } catch {\n return text\n }\n}\n"],
5
- "mappings": "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",
4
+ "sourcesContent": ["/**\n * Code Mode Tools\n *\n * Two meta-tools that replace all individual API/schema/module tools:\n * - search: Query the OpenAPI spec + entity graph programmatically\n * - execute: Make API calls via a sandboxed api.request() wrapper\n *\n * The AI writes JavaScript that runs in a node:vm sandbox with injected globals.\n */\n\nimport { z } from 'zod'\nimport type { RbacService } from '@open-mercato/core/modules/auth/services/rbacService'\nimport { registerMcpTool } from './tool-registry'\nimport type { McpToolContext } from './types'\nimport { createSandbox } from './sandbox'\nimport { truncateResult } from './truncate'\nimport { hasRequiredFeatures } from './auth'\nimport { getApiEndpoints, getRawOpenApiSpec, type ApiEndpoint } from './api-endpoint-index'\nimport {\n getCachedEntityGraph,\n inferModuleFromEntity,\n type EntityGraph,\n} from './entity-graph'\nimport {\n lookupSearchCache,\n storeSearchResult,\n buildMemoryContext,\n buildSearchLabel,\n incrementToolCallCount,\n} from './session-memory'\nimport { fetchWithTimeout, resolveTimeoutMs } from '@open-mercato/shared/lib/http/fetchWithTimeout'\n\nconst DEFAULT_AI_API_REQUEST_TIMEOUT_MS = 30_000\n\nfunction resolveAiApiRequestTimeoutMs(): number {\n const raw = process.env.AI_API_REQUEST_TIMEOUT_MS\n const parsed = raw ? Number.parseInt(raw, 10) : undefined\n return resolveTimeoutMs(parsed, DEFAULT_AI_API_REQUEST_TIMEOUT_MS)\n}\n\n/**\n * Cached spec object combining OpenAPI paths + entity schemas.\n */\nlet cachedCodeModeSpec: Record<string, unknown> | null = null\n\n/**\n * Cached TypeScript type stubs for common CRUD endpoints.\n * Generated once at startup from the OpenAPI spec.\n */\nlet cachedCommonTypes: string | null = null\n\nexport const CODE_MODE_REQUIRED_FEATURES = ['ai_assistant.view'] as const\n\n/**\n * Build the merged spec object for the search tool.\n */\nasync function getCodeModeSpec(): Promise<Record<string, unknown>> {\n if (cachedCodeModeSpec) return cachedCodeModeSpec\n\n const rawSpec = await getRawOpenApiSpec()\n const graph = getCachedEntityGraph()\n\n const paths = (rawSpec?.paths ?? {}) as Record<string, Record<string, unknown>>\n const entitySchemas = graph ? buildEntitySchemas(graph) : []\n\n const spec: Record<string, unknown> = {\n paths,\n info: rawSpec?.info,\n components: rawSpec?.components,\n entitySchemas,\n }\n\n // --- Helper functions injected into sandbox ---\n\n /**\n * spec.findEndpoints(keyword) \u2014 find all endpoints matching a keyword.\n * Returns compact list: [{ path, methods }]\n */\n spec.findEndpoints = (keyword: string) => {\n const kw = keyword.toLowerCase()\n return Object.entries(paths)\n .filter(([path]) => path.toLowerCase().includes(kw))\n .map(([path, methods]) => ({\n path,\n methods: Object.keys(methods).filter((m) => m !== 'parameters'),\n }))\n }\n\n /**\n * spec.describeEndpoint(path, method) \u2014 compact endpoint profile with working example.\n * Returns: { path, method, summary, requiredFields, optionalFields, nestedCollections, example, relatedEndpoints, relatedEntity }\n * For full schema access, use: spec.paths[path][method].requestBody\n */\n spec.describeEndpoint = (path: string, method: string) => {\n const pathObj = paths[path] as Record<string, unknown> | undefined\n if (!pathObj) return null\n\n const endpoint = pathObj[method.toLowerCase()] as Record<string, unknown> | undefined\n if (!endpoint) return null\n\n // Extract requestBody JSON Schema\n const bodySchema = extractRequestBodySchema(endpoint)\n const bodyProps = (bodySchema?.properties ?? {}) as Record<string, Record<string, unknown>>\n const bodyRequired = (bodySchema?.required ?? []) as string[]\n\n // Split fields into required (with types) vs optional (names only)\n const requiredFields: Array<{ name: string; type: string; format?: string }> = []\n const optionalFields: string[] = []\n const nestedCollections: Array<{\n field: string\n type: string\n requiredFields: Array<{ name: string; type: string }>\n commonFields: string[]\n }> = []\n\n for (const [name, prop] of Object.entries(bodyProps)) {\n const propType = (prop.type as string) || 'string'\n\n // Detect nested array collections (e.g. lines, items, addresses)\n if (propType === 'array' && prop.items && (prop.items as Record<string, unknown>).type === 'object') {\n const itemSchema = prop.items as Record<string, unknown>\n const itemProps = (itemSchema.properties ?? {}) as Record<string, Record<string, unknown>>\n const itemRequired = (itemSchema.required ?? []) as string[]\n\n const nestedRequired = itemRequired.map((n) => ({\n name: n,\n type: ((itemProps[n]?.type as string) || 'string'),\n }))\n\n // Common fields: first few non-required fields that are likely user-provided\n const nestedOptional = Object.keys(itemProps).filter((n) => !itemRequired.includes(n))\n const commonFields = nestedOptional.slice(0, 6)\n\n nestedCollections.push({\n field: name,\n type: 'array',\n requiredFields: nestedRequired,\n commonFields,\n })\n continue\n }\n\n if (bodyRequired.includes(name)) {\n const field: { name: string; type: string; format?: string } = { name, type: propType }\n if (prop.format) field.format = prop.format as string\n requiredFields.push(field)\n } else {\n optionalFields.push(name)\n }\n }\n\n // Generate minimal working example from required fields + nested collections\n const example: Record<string, unknown> = {}\n for (const field of requiredFields) {\n example[field.name] = generatePlaceholder(field.type, field.format)\n }\n for (const collection of nestedCollections) {\n const itemExample: Record<string, unknown> = {}\n for (const field of collection.requiredFields) {\n itemExample[field.name] = generatePlaceholder(field.type)\n }\n // Add first 2 common fields to the example\n for (const name of collection.commonFields.slice(0, 2)) {\n itemExample[name] = '<value>'\n }\n example[collection.field] = [itemExample]\n }\n\n // Find related endpoints sharing the same module prefix\n const segments = path.replace('/api/', '').split('/')\n const moduleSegment = segments[0]\n const resourceName = segments[1] || segments[0]\n const modulePrefix = `/api/${moduleSegment}/`\n const relatedEndpoints = Object.entries(paths)\n .filter(([p]) => p.startsWith(modulePrefix) && p !== path && !p.includes('{'))\n .map(([p, methods]) => ({\n path: p,\n methods: Object.keys(methods as Record<string, unknown>).filter((m) => m !== 'parameters'),\n }))\n .slice(0, 8)\n\n // Compact entity: className + relationship summary\n const resourceNorm = resourceName.replace(/-/g, '_')\n const resourceSingular = resourceNorm.endsWith('s') ? resourceNorm.slice(0, -1) : resourceNorm\n const moduleSingular = moduleSegment.endsWith('s') ? moduleSegment.slice(0, -1) : moduleSegment\n const prefixedTable = `${moduleSingular}_${resourceNorm}`\n\n const entity = entitySchemas.find((e: Record<string, unknown>) => {\n const table = ((e.tableName as string) || '').toLowerCase()\n const cls = ((e.className as string) || '').toLowerCase()\n const mod = ((e.module as string) || '').toLowerCase()\n if (table === resourceNorm || table === prefixedTable) return true\n if (cls.includes(moduleSingular) && cls.includes(resourceSingular)) return true\n if (mod === moduleSegment && cls.includes(resourceSingular)) return true\n if (cls === resourceSingular || cls.includes(resourceSingular)) return true\n return false\n }) || null\n\n let relatedEntity: string | null = null\n if (entity) {\n const ent = entity as Record<string, unknown>\n const rels = (ent.relationships as Array<{ relationship: string; target: string }>) || []\n const relSummary = rels.map((r) => `${r.relationship}: ${r.target}`).join(', ')\n relatedEntity = `${ent.className}${relSummary ? ` (${relSummary})` : ''}`\n }\n\n // GET endpoints: include query parameters compactly\n const parameters = method.toLowerCase() === 'get'\n ? (endpoint.parameters as Array<Record<string, unknown>> || [])\n .filter((p) => p.in === 'query')\n .map((p) => p.name as string)\n : undefined\n\n return {\n path,\n method: method.toUpperCase(),\n summary: endpoint.summary || endpoint.description,\n ...(parameters && parameters.length > 0 ? { queryParams: parameters } : {}),\n ...(requiredFields.length > 0 ? { requiredFields } : {}),\n ...(optionalFields.length > 0 ? { optionalFields } : {}),\n ...(nestedCollections.length > 0 ? { nestedCollections } : {}),\n ...(Object.keys(example).length > 0 ? { example } : {}),\n ...(relatedEndpoints.length > 0 ? { relatedEndpoints } : {}),\n relatedEntity,\n }\n }\n\n /**\n * spec.describeEntity(keyword) \u2014 find entity by keyword and return its full schema.\n * Returns: { className, tableName, module, fields, relationships }\n */\n spec.describeEntity = (keyword: string) => {\n const kw = keyword.toLowerCase()\n return entitySchemas.find((e: Record<string, unknown>) => {\n const cls = (e.className as string || '').toLowerCase()\n const table = (e.tableName as string || '').toLowerCase()\n return cls.includes(kw) || table.includes(kw)\n }) || null\n }\n\n cachedCodeModeSpec = spec\n return spec\n}\n\n/**\n * Extract the JSON Schema from an OpenAPI endpoint's requestBody.\n * Handles the common `content['application/json'].schema` path.\n */\nfunction extractRequestBodySchema(\n endpoint: Record<string, unknown>\n): Record<string, unknown> | null {\n const requestBody = endpoint.requestBody as Record<string, unknown> | undefined\n if (!requestBody) return null\n\n const content = requestBody.content as Record<string, Record<string, unknown>> | undefined\n if (!content) return null\n\n const jsonContent = content['application/json']\n if (!jsonContent) return null\n\n return (jsonContent.schema as Record<string, unknown>) || null\n}\n\n/**\n * Generate a placeholder value for a given JSON Schema type.\n */\nfunction generatePlaceholder(type: string, format?: string): unknown {\n if (format === 'uuid' || format === 'objectId') return '<uuid>'\n if (format === 'date-time' || format === 'date') return '<date>'\n if (format === 'email') return '<email>'\n switch (type) {\n case 'string': return '<string>'\n case 'number':\n case 'integer': return 0\n case 'boolean': return false\n case 'array': return []\n default: return '<value>'\n }\n}\n\n/**\n * Common CRUD endpoints to pre-generate types for.\n * These are the endpoints the agent uses most and where debug spirals happen.\n */\nconst COMMON_ENDPOINTS: Array<{ path: string; method: string; typeName: string }> = [\n { path: '/api/sales/quotes', method: 'post', typeName: 'CreateQuote' },\n { path: '/api/sales/orders', method: 'post', typeName: 'CreateOrder' },\n { path: '/api/sales/invoices', method: 'post', typeName: 'CreateInvoice' },\n { path: '/api/customers/companies', method: 'post', typeName: 'CreateCompany' },\n { path: '/api/customers/people', method: 'post', typeName: 'CreatePerson' },\n { path: '/api/customers/deals', method: 'post', typeName: 'CreateDeal' },\n { path: '/api/catalog/products', method: 'post', typeName: 'CreateProduct' },\n { path: '/api/customers/companies', method: 'put', typeName: 'UpdateCompany' },\n { path: '/api/customers/people', method: 'put', typeName: 'UpdatePerson' },\n { path: '/api/sales/quotes', method: 'put', typeName: 'UpdateQuote' },\n]\n\n/**\n * Generate TypeScript-like type stubs from the OpenAPI spec for common endpoints.\n * This runs once at startup and injects types into the execute tool description\n * so the LLM sees the correct payload shape without needing to call describeEndpoint.\n */\nasync function generateCommonTypes(): Promise<string> {\n if (cachedCommonTypes) return cachedCommonTypes\n\n const rawSpec = await getRawOpenApiSpec()\n if (!rawSpec?.paths) {\n cachedCommonTypes = ''\n return ''\n }\n\n const paths = rawSpec.paths as Record<string, Record<string, unknown>>\n const typeLines: string[] = ['Available types for api.request() body:\\n']\n\n for (const { path, method, typeName } of COMMON_ENDPOINTS) {\n const pathObj = paths[path] as Record<string, unknown> | undefined\n if (!pathObj) continue\n\n const endpoint = pathObj[method] as Record<string, unknown> | undefined\n if (!endpoint) continue\n\n const bodySchema = extractRequestBodySchema(endpoint)\n if (!bodySchema?.properties) continue\n\n const typeStr = schemaToTypeString(\n typeName,\n bodySchema,\n `${method.toUpperCase()} ${path}`,\n )\n if (typeStr) typeLines.push(typeStr)\n }\n\n if (typeLines.length <= 1) {\n cachedCommonTypes = ''\n return ''\n }\n\n cachedCommonTypes = typeLines.join('\\n')\n console.error(`[Code Mode] Generated ${typeLines.length - 1} common type stubs`)\n return cachedCommonTypes\n}\n\n/**\n * Convert a JSON Schema object to a compact TypeScript-like type string.\n * Produces a single-line or multi-line type declaration the LLM can use directly.\n */\nfunction schemaToTypeString(\n typeName: string,\n schema: Record<string, unknown>,\n comment: string,\n): string | null {\n const props = schema.properties as Record<string, Record<string, unknown>> | undefined\n if (!props) return null\n\n const required = new Set((schema.required as string[]) || [])\n\n // Skip internal fields that the sandbox injects automatically\n const skipFields = new Set(['tenantId', 'organizationId'])\n\n const fields: string[] = []\n const nestedTypes: string[] = []\n\n for (const [name, prop] of Object.entries(props)) {\n if (skipFields.has(name)) continue\n if (!prop || typeof prop !== 'object') continue\n\n const isRequired = required.has(name)\n const optMark = isRequired ? '' : '?'\n\n // Detect nested array of objects \u2192 extract as separate type\n if (\n prop.type === 'array' &&\n prop.items &&\n (prop.items as Record<string, unknown>).type === 'object'\n ) {\n const itemTypeName = `${typeName}${capitalize(singularize(name))}`\n const itemSchema = prop.items as Record<string, unknown>\n const nestedType = schemaToTypeString(itemTypeName, itemSchema, '')\n if (nestedType) nestedTypes.push(nestedType)\n fields.push(`${name}${optMark}: ${itemTypeName}[]`)\n continue\n }\n\n const propType = resolvePropertyType(prop)\n fields.push(`${name}${optMark}: ${propType}`)\n }\n\n if (fields.length === 0) return null\n\n const commentLine = comment ? `// ${comment}\\n` : ''\n const nested = nestedTypes.length > 0 ? nestedTypes.join('\\n') + '\\n' : ''\n return `${nested}${commentLine}type ${typeName} = { ${fields.join('; ')} }`\n}\n\n/**\n * Resolve a JSON Schema property to a compact TypeScript type string.\n */\nfunction resolvePropertyType(prop: Record<string, unknown>): string {\n // Handle anyOf (nullable types)\n if (prop.anyOf && Array.isArray(prop.anyOf)) {\n const variants = (prop.anyOf as Array<Record<string, unknown> | null>).filter(\n (s): s is Record<string, unknown> => s != null,\n )\n const nonNull = variants.filter((s) => s.type !== 'null')\n if (nonNull.length === 1) {\n return resolvePropertyType(nonNull[0]) + ' | null'\n }\n if (nonNull.length > 1) {\n return nonNull.map((s) => resolvePropertyType(s)).join(' | ')\n }\n }\n\n // Handle enum\n if (prop.enum && Array.isArray(prop.enum)) {\n return (prop.enum as string[]).map((v) => `'${v}'`).join(' | ')\n }\n\n const type = prop.type as string\n const format = prop.format as string | undefined\n\n if (type === 'array') {\n const items = prop.items as Record<string, unknown> | undefined\n if (items) return `${resolvePropertyType(items)}[]`\n return 'unknown[]'\n }\n\n if (type === 'object') return 'object'\n\n if (format === 'uuid') return 'string /*uuid*/'\n if (format === 'date-time') return 'string /*ISO date*/'\n if (format === 'date') return 'string /*date*/'\n if (format === 'email') return 'string /*email*/'\n\n switch (type) {\n case 'string': return 'string'\n case 'number':\n case 'integer': return 'number'\n case 'boolean': return 'boolean'\n default: return 'unknown'\n }\n}\n\nfunction capitalize(s: string): string {\n return s.charAt(0).toUpperCase() + s.slice(1)\n}\n\nfunction singularize(s: string): string {\n if (s.endsWith('ies')) return s.slice(0, -3) + 'y'\n if (s.endsWith('ses')) return s.slice(0, -2)\n if (s.endsWith('s') && !s.endsWith('ss')) return s.slice(0, -1)\n return s\n}\n\n/**\n * Format a 400 API error response into a human-readable fix instruction.\n * Parses Zod-style validation errors and produces a concise message the LLM can act on.\n */\nfunction formatValidationError(data: unknown): string {\n if (!data || typeof data !== 'object') {\n return `Validation error: ${JSON.stringify(data)}`\n }\n\n // Raw Zod v4 array format: [{ expected, code, path, message }]\n if (Array.isArray(data)) {\n const issues = data as Array<Record<string, unknown>>\n const parts = issues.slice(0, 5).map((issue) => {\n const path = Array.isArray(issue.path) ? issue.path.join('.') : ''\n const msg = issue.message as string || `expected ${issue.expected}` || issue.code as string || 'invalid'\n return path ? `${path}: ${msg}` : msg\n })\n if (parts.length > 0) {\n return `Validation failed \u2014 ${parts.join('; ')}. Fix the listed fields and retry.`\n }\n }\n\n const obj = data as Record<string, unknown>\n\n // Zod v4 flat format: { fieldErrors: { field: [messages] }, formErrors: [messages] }\n if (obj.fieldErrors && typeof obj.fieldErrors === 'object') {\n const fieldErrors = obj.fieldErrors as Record<string, string[]>\n const parts: string[] = []\n for (const [field, messages] of Object.entries(fieldErrors)) {\n if (Array.isArray(messages) && messages.length > 0) {\n parts.push(`${field}: ${messages[0]}`)\n }\n }\n const formErrors = obj.formErrors as string[] | undefined\n if (Array.isArray(formErrors) && formErrors.length > 0) {\n parts.push(formErrors[0])\n }\n if (parts.length > 0) {\n return `Validation failed \u2014 ${parts.join('; ')}. Fix the listed fields and retry.`\n }\n }\n\n // Zod v3 format: { issues: [{ path: [...], message, code }] }\n if (obj.issues && Array.isArray(obj.issues)) {\n const issues = obj.issues as Array<Record<string, unknown>>\n const parts = issues.slice(0, 5).map((issue) => {\n const path = Array.isArray(issue.path) ? issue.path.join('.') : ''\n const msg = issue.message as string || issue.code as string || 'invalid'\n return path ? `${path}: ${msg}` : msg\n })\n return `Validation failed \u2014 ${parts.join('; ')}. Fix the listed fields and retry.`\n }\n\n // Our API error format: { error: string, details: ... }\n if (obj.error && typeof obj.error === 'string') {\n const details = obj.details\n if (details && typeof details === 'object') {\n return formatValidationError(details)\n }\n return obj.error\n }\n\n // Generic: { message: string }\n if (obj.message && typeof obj.message === 'string') {\n return obj.message\n }\n\n // Fallback: compact JSON\n const json = JSON.stringify(data)\n if (json.length > 500) {\n return `Validation error (truncated): ${json.slice(0, 500)}...`\n }\n return `Validation error: ${json}`\n}\n\n/**\n * Build entity schema array from the entity graph.\n */\nfunction buildEntitySchemas(graph: EntityGraph) {\n return graph.nodes.map((node) => {\n const relationships = graph.edges\n .filter((edge) => edge.source === node.className)\n .map((edge) => ({\n relationship: edge.relationship,\n target: edge.target,\n property: edge.property,\n nullable: edge.nullable,\n }))\n\n return {\n className: node.className,\n tableName: node.tableName,\n module: inferModuleFromEntity(node.className, node.tableName),\n fields: node.properties,\n relationships,\n }\n })\n}\n\n/** Maximum api.request() calls allowed per execute() run, regardless of method. */\nexport const CODE_MODE_MAX_API_CALLS = 50\n/** Maximum mutation (non-GET/HEAD/OPTIONS) api.request() calls allowed per execute() run. */\nexport const CODE_MODE_MAX_MUTATION_CALLS = 20\n\n/**\n * Load and register the two Code Mode tools.\n * Generates TypeScript type stubs for common endpoints at startup.\n * @returns Number of tools registered (always 2)\n */\nexport async function loadCodeModeTools(): Promise<number> {\n const commonTypes = await generateCommonTypes()\n registerSearchTool()\n registerExecuteTool(commonTypes)\n return 2\n}\n\n/**\n * search \u2014 Query the OpenAPI spec and entity graph programmatically.\n */\nfunction registerSearchTool(): void {\n registerMcpTool(\n {\n name: 'search',\n description: `Query the OpenAPI spec and entity schemas. READ-ONLY, no side effects.\nGlobals: spec.findEndpoints(keyword), spec.describeEndpoint(path, method), spec.describeEntity(keyword), spec.paths, spec.entitySchemas.\nUse BEFORE execute to learn endpoint schemas for CREATE/UPDATE. Skip for common paths (companies, people, orders, quotes, products).`,\n inputSchema: z.object({\n code: z\n .string()\n .describe(\n 'An async arrow function that queries spec, e.g. async () => spec.paths[\"/api/customers/companies\"]'\n ),\n }),\n requiredFeatures: [...CODE_MODE_REQUIRED_FEATURES],\n handler: async (input: { code: string }, ctx: McpToolContext) => {\n const codePreview = input.code.slice(0, 120).replace(/\\n/g, ' ')\n console.error(`[AI Usage] search: code=\"${codePreview}${input.code.length > 120 ? '...' : ''}\"`)\n\n // Check session memory for cached result\n if (ctx.sessionId) {\n const cached = lookupSearchCache(ctx.sessionId, input.code)\n if (cached) {\n console.error(`[AI Usage] search: CACHE HIT (label=\"${cached.label}\")`)\n const memoryContext = buildMemoryContext(ctx.sessionId)\n return {\n success: true,\n result: cached.result,\n fromCache: true,\n _memoryContext: memoryContext,\n }\n }\n\n // Enforce tool call limit\n const { count, exceeded } = incrementToolCallCount(ctx.sessionId)\n if (exceeded) {\n console.error(`[AI Usage] search: TOOL CALL LIMIT EXCEEDED (count=${count})`)\n return {\n success: false,\n error: 'Tool call limit exceeded. Summarize what you know and respond to the user.',\n }\n }\n }\n\n const spec = await getCodeModeSpec()\n const sandbox = createSandbox({ spec })\n const result = await sandbox.execute(input.code)\n\n if (result.error) {\n console.error(`[AI Usage] search: ERROR in ${result.durationMs}ms \u2014 ${result.error}`)\n return {\n success: false,\n error: result.error,\n logs: result.logs,\n durationMs: result.durationMs,\n }\n }\n\n const truncated = truncateResult(result.result)\n console.error(`[AI Usage] search: OK in ${result.durationMs}ms \u2014 ${truncated.length} chars`)\n\n // Store in session memory\n if (ctx.sessionId) {\n const label = buildSearchLabel(input.code)\n storeSearchResult(ctx.sessionId, input.code, truncated, label)\n }\n\n const memoryContext = ctx.sessionId ? buildMemoryContext(ctx.sessionId) : undefined\n return {\n success: true,\n result: truncated,\n logs: result.logs,\n durationMs: result.durationMs,\n _memoryContext: memoryContext,\n }\n },\n },\n { moduleId: 'codemode' }\n )\n}\n\n/**\n * execute \u2014 Run JavaScript that can make API calls via api.request().\n */\nfunction registerExecuteTool(commonTypes: string): void {\n const typesBlock = commonTypes\n ? `\\n\\n${commonTypes}`\n : ''\n\n registerMcpTool(\n {\n name: 'execute',\n description: `Make API calls. Returns JSON.\nGlobals: api.request({ method, path, query?, body? }) \u2192 { success, statusCode, data }, context { tenantId, organizationId, userId }.\nRULES: For FIND/LIST \u2192 GET only (1 call). For UPDATE \u2192 PUT to collection path with id in BODY. NEVER PUT/POST/DELETE unless user explicitly asked to change data. Before ANY write operation (POST/PUT/DELETE), you MUST use the AskUserQuestion tool to get explicit user confirmation. Do NOT just ask in text \u2014 use the tool so execution pauses until the user responds.${typesBlock}`,\n inputSchema: z.object({\n code: z\n .string()\n .describe(\n 'Async arrow function. For reads: async () => api.request({ method: \"GET\", path: \"/api/customers/companies\" }). For updates: async () => api.request({ method: \"PUT\", path: \"/api/customers/companies\", body: { id: \"<uuid>\", name: \"New Name\" } }). id goes in BODY not URL.'\n ),\n }),\n requiredFeatures: [...CODE_MODE_REQUIRED_FEATURES],\n handler: async (input: { code: string }, ctx: McpToolContext) => {\n const codePreview = input.code.slice(0, 120).replace(/\\n/g, ' ')\n console.error(`[AI Usage] execute: code=\"${codePreview}${input.code.length > 120 ? '...' : ''}\" user=${ctx.userId || 'unknown'}`)\n\n // Enforce tool call limit\n if (ctx.sessionId) {\n const { count, exceeded } = incrementToolCallCount(ctx.sessionId)\n if (exceeded) {\n console.error(`[AI Usage] execute: TOOL CALL LIMIT EXCEEDED (count=${count})`)\n return {\n success: false,\n error: 'Tool call limit exceeded. Summarize what you know and respond to the user.',\n }\n }\n }\n\n // Cap API calls for safety. The mutation cap is enforced against the\n // actually-observed HTTP method, not a static scan of the source \u2014 so a\n // dynamically-built method (e.g. 'PO' + 'ST') can never escape it.\n const maxApiCalls = CODE_MODE_MAX_API_CALLS\n let apiCallCount = 0\n let mutationCallCount = 0\n\n const apiRequestFn = createApiRequestFn(ctx, (normalizedMethod) => {\n apiCallCount++\n if (apiCallCount > maxApiCalls) {\n throw new Error(`API call limit exceeded (max ${maxApiCalls})`)\n }\n if (isUnsafeHttpMethod(normalizedMethod)) {\n mutationCallCount++\n if (mutationCallCount > CODE_MODE_MAX_MUTATION_CALLS) {\n throw new Error(`Mutation API call limit exceeded (max ${CODE_MODE_MAX_MUTATION_CALLS})`)\n }\n }\n })\n\n const context = {\n tenantId: ctx.tenantId,\n organizationId: ctx.organizationId,\n userId: ctx.userId,\n }\n\n const sandbox = createSandbox(\n { api: { request: apiRequestFn }, context },\n { maxApiCalls }\n )\n\n const result = await sandbox.execute(input.code)\n\n if (result.error) {\n console.error(`[AI Usage] execute: ERROR in ${result.durationMs}ms \u2014 apiCalls=${apiCallCount} \u2014 ${result.error}`)\n return {\n success: false,\n error: result.error,\n logs: result.logs,\n durationMs: result.durationMs,\n apiCallCount,\n }\n }\n\n const truncated = truncateResult(result.result)\n console.error(`[AI Usage] execute: OK in ${result.durationMs}ms \u2014 apiCalls=${apiCallCount} \u2014 ${truncated.length} chars`)\n\n const memoryContext = ctx.sessionId ? buildMemoryContext(ctx.sessionId) : undefined\n return {\n success: true,\n result: truncated,\n logs: result.logs,\n durationMs: result.durationMs,\n apiCallCount,\n _memoryContext: memoryContext,\n }\n },\n },\n { moduleId: 'codemode' }\n )\n}\n\n/**\n * Create the api.request() function for the execute sandbox.\n */\nexport function createApiRequestFn(\n ctx: McpToolContext,\n onCall: (normalizedMethod: string) => void\n): (params: {\n method: string\n path: string\n query?: Record<string, string>\n body?: Record<string, unknown>\n}) => Promise<unknown> {\n const baseUrl =\n process.env.NEXT_PUBLIC_API_BASE_URL ||\n process.env.NEXT_PUBLIC_APP_URL ||\n process.env.APP_URL ||\n 'http://localhost:3000'\n\n return async (params) => {\n const { method, path, query, body } = params\n const callStart = Date.now()\n const normalizedMethod = String(method ?? '').toUpperCase()\n onCall(normalizedMethod)\n const apiPath = normalizeApiRequestPath(path)\n const authorization = await authorizeCodeModeApiRequest(ctx, normalizedMethod, apiPath)\n\n if (!authorization.allowed) {\n const callDuration = Date.now() - callStart\n console.error(\n `[AI Usage] api.request: ${normalizedMethod} ${apiPath} \u2192 ${authorization.statusCode} in ${callDuration}ms (blocked by Code Mode RBAC)`\n )\n return {\n success: false,\n statusCode: authorization.statusCode,\n error: authorization.error,\n details: authorization.details,\n }\n }\n\n let url = `${baseUrl}${apiPath}`\n\n // Build query parameters\n const queryParams: Record<string, string> = { ...query }\n\n if (normalizedMethod === 'GET') {\n if (ctx.tenantId) queryParams.tenantId = ctx.tenantId\n if (ctx.organizationId) queryParams.organizationId = ctx.organizationId\n }\n\n if (Object.keys(queryParams).length > 0) {\n const separator = url.includes('?') ? '&' : '?'\n url += separator + new URLSearchParams(queryParams).toString()\n }\n\n // Build request body with context injection\n let requestBody: Record<string, unknown> | undefined\n if (['POST', 'PUT', 'PATCH'].includes(normalizedMethod)) {\n requestBody = { ...body }\n if (ctx.tenantId) requestBody.tenantId = ctx.tenantId\n if (ctx.organizationId) requestBody.organizationId = ctx.organizationId\n }\n\n // Build headers\n const headers: Record<string, string> = {\n 'Content-Type': 'application/json',\n }\n if (ctx.apiKeySecret) headers['X-API-Key'] = ctx.apiKeySecret\n if (ctx.tenantId) headers['X-Tenant-Id'] = ctx.tenantId\n if (ctx.organizationId) headers['X-Organization-Id'] = ctx.organizationId\n\n // Execute request using host fetch (not sandbox)\n const response = await fetchWithTimeout(url, {\n method: normalizedMethod,\n headers,\n body: requestBody ? JSON.stringify(requestBody) : undefined,\n timeoutMs: resolveAiApiRequestTimeoutMs(),\n })\n\n const responseText = await response.text()\n const data = tryParseJson(responseText)\n const callDuration = Date.now() - callStart\n\n if (!response.ok) {\n console.error(`[AI Usage] api.request: ${normalizedMethod} ${apiPath} \u2192 ${response.status} in ${callDuration}ms`)\n\n // Format 400 validation errors into a clear fix instruction for the LLM\n if (response.status === 400) {\n return {\n success: false,\n statusCode: 400,\n error: formatValidationError(data),\n }\n }\n\n return {\n success: false,\n statusCode: response.status,\n error: `API error ${response.status}`,\n details: data,\n }\n }\n\n console.error(`[AI Usage] api.request: ${normalizedMethod} ${apiPath} \u2192 ${response.status} in ${callDuration}ms (${responseText.length} bytes)`)\n\n // Add mutation warning for non-GET calls\n if (!['GET', 'HEAD', 'OPTIONS'].includes(normalizedMethod)) {\n return {\n success: true,\n statusCode: response.status,\n data,\n _note: 'WRITE operation performed. Only do writes when user explicitly requested data modification.',\n }\n }\n\n return {\n success: true,\n statusCode: response.status,\n data,\n }\n }\n}\n\ntype CodeModeApiAuthorization =\n | { allowed: true; endpoint: ApiEndpoint }\n | { allowed: false; statusCode: number; error: string; details?: Record<string, unknown> }\n\nexport async function authorizeCodeModeApiRequest(\n ctx: McpToolContext,\n method: string,\n path: string\n): Promise<CodeModeApiAuthorization> {\n const normalizedMethod = method.toUpperCase()\n const normalizedPath = normalizeApiRequestPath(path)\n const endpoint = await findCodeModeApiEndpoint(normalizedMethod, normalizedPath)\n\n if (!endpoint) {\n return {\n allowed: false,\n statusCode: 403,\n error: `Code Mode cannot call undocumented API endpoint ${normalizedMethod} ${normalizedPath}`,\n }\n }\n\n const rbacService = resolveRbacService(ctx)\n const requiredFeatures = endpoint.requiredFeatures ?? []\n\n if (requiredFeatures.length > 0) {\n if (hasRequiredFeatures(requiredFeatures, ctx.userFeatures, ctx.isSuperAdmin, rbacService)) {\n return { allowed: true, endpoint }\n }\n\n return {\n allowed: false,\n statusCode: 403,\n error: `Insufficient permissions for ${normalizedMethod} ${normalizedPath}`,\n details: { requiredFeatures, operationId: endpoint.operationId },\n }\n }\n\n if (isUnsafeHttpMethod(normalizedMethod)) {\n return {\n allowed: false,\n statusCode: 403,\n error: `Code Mode cannot call mutation endpoint without declared required features: ${normalizedMethod} ${normalizedPath}`,\n details: { operationId: endpoint.operationId },\n }\n }\n\n return { allowed: true, endpoint }\n}\n\nfunction resolveRbacService(ctx: McpToolContext): RbacService | undefined {\n try {\n return ctx.container.resolve('rbacService') as RbacService\n } catch {\n return undefined\n }\n}\n\nasync function findCodeModeApiEndpoint(\n method: string,\n path: string\n): Promise<ApiEndpoint | null> {\n const endpoints = await getApiEndpoints()\n const exactMatch = endpoints.find((endpoint) => endpoint.method === method && endpoint.path === path)\n if (exactMatch) {\n return exactMatch\n }\n\n return endpoints.find((endpoint) => endpoint.method === method && matchApiEndpointPath(endpoint.path, path)) ?? null\n}\n\nexport function matchApiEndpointPath(endpointPath: string, requestPath: string): boolean {\n const normalizedEndpointPath = normalizeApiRequestPath(endpointPath)\n const normalizedRequestPath = normalizeApiRequestPath(requestPath)\n\n if (normalizedEndpointPath === normalizedRequestPath) {\n return true\n }\n\n const endpointSegments = normalizedEndpointPath.split('/').filter(Boolean)\n const requestSegments = normalizedRequestPath.split('/').filter(Boolean)\n\n if (endpointSegments.length !== requestSegments.length) {\n return false\n }\n\n return endpointSegments.every((segment, index) => {\n if (isPathParameterSegment(segment)) {\n return requestSegments[index].length > 0\n }\n return segment === requestSegments[index]\n })\n}\n\nfunction normalizeApiRequestPath(path: string): string {\n const [rawPath] = path.split('?')\n const normalizedPath = rawPath.startsWith('/api')\n ? rawPath\n : `/api${rawPath.startsWith('/') ? rawPath : `/${rawPath}`}`\n\n if (normalizedPath.length > 1 && normalizedPath.endsWith('/')) {\n return normalizedPath.slice(0, -1)\n }\n\n return normalizedPath\n}\n\nfunction isPathParameterSegment(segment: string): boolean {\n return (\n (segment.startsWith('{') && segment.endsWith('}')) ||\n (segment.startsWith('[') && segment.endsWith(']')) ||\n segment.startsWith(':')\n )\n}\n\nexport function isUnsafeHttpMethod(method: string): boolean {\n return !['GET', 'HEAD', 'OPTIONS'].includes(method.toUpperCase())\n}\n\nfunction tryParseJson(text: string): unknown {\n try {\n return JSON.parse(text)\n } catch {\n return text\n }\n}\n"],
5
+ "mappings": "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",
6
6
  "names": ["memoryContext", "callDuration"]
7
7
  }
@@ -124,13 +124,14 @@ async function pickDefaultTenant(container) {
124
124
  );
125
125
  const tenantId = Array.isArray(tenantRows) && tenantRows[0] ? String(tenantRows[0].id) : null;
126
126
  if (!tenantId) return null;
127
- const escaped = tenantId.replace(/'/g, "''");
128
127
  const orgRows = await conn.execute(
129
- `SELECT id FROM organizations WHERE tenant_id = '${escaped}' AND deleted_at IS NULL ORDER BY created_at ASC LIMIT 1`
128
+ `SELECT id FROM organizations WHERE tenant_id = ? AND deleted_at IS NULL ORDER BY created_at ASC LIMIT 1`,
129
+ [tenantId]
130
130
  );
131
131
  const organizationId = Array.isArray(orgRows) && orgRows[0] ? String(orgRows[0].id) : null;
132
132
  const userRows = await conn.execute(
133
- `SELECT id FROM users WHERE tenant_id = '${escaped}' AND deleted_at IS NULL ORDER BY created_at ASC LIMIT 1`
133
+ `SELECT id FROM users WHERE tenant_id = ? AND deleted_at IS NULL ORDER BY created_at ASC LIMIT 1`,
134
+ [tenantId]
134
135
  );
135
136
  const userId = Array.isArray(userRows) && userRows[0] ? String(userRows[0].id) : null;
136
137
  return { tenantId, organizationId, userId };
@@ -413,6 +414,7 @@ async function runToolTests(options = {}) {
413
414
  };
414
415
  }
415
416
  export {
417
+ pickDefaultTenant,
416
418
  runToolTests
417
419
  };
418
420
  //# sourceMappingURL=tool-test-runner.js.map
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../../src/modules/ai_assistant/lib/tool-test-runner.ts"],
4
- "sourcesContent": ["/**\n * In-process AI tool test runner.\n *\n * Iterates every tool registered in `ai-tools.generated.ts`, invokes the\n * handler against a super-admin tenant context, and returns a structured\n * report. Used by the `mercato ai_assistant test-tools` CLI subcommand and\n * by `.ai/qa/tests/integration/TC-INT-AI-TOOLS.spec.ts`.\n *\n * Safety posture:\n * - No HTTP exposure \u2014 runs only inside the Node process driving the CLI.\n * - Mutation tools are exercised through `prepareMutation` and we assert a\n * pending-action envelope is returned. The pending-action row is created\n * in `ai_pending_actions` but never confirmed, so no real write happens.\n * - Tools without an explicit fixture entry are skipped with reason\n * `'no fixture'` rather than failing.\n */\nimport path from 'node:path'\nimport fs from 'node:fs'\nimport { fileURLToPath, pathToFileURL } from 'node:url'\nimport type { AwilixContainer } from 'awilix'\nimport { createRequestContainer } from '@open-mercato/shared/lib/di/container'\nimport type { McpToolContext, AiToolDefinition } from './types'\nimport type { AiAgentDefinition, AiAgentMutationPolicy } from './ai-agent-definition'\nimport { getFixture, type ToolFixture } from './tool-test-fixtures'\nimport { prepareMutation } from './prepare-mutation'\nimport { executePendingActionConfirm } from './pending-action-executor'\n\nexport type ToolTestStatus = 'pass' | 'fail' | 'skip'\n\nexport interface ToolTestRecord {\n module: string\n tool: string\n isMutation: boolean\n status: ToolTestStatus\n durationMs: number\n reason?: string\n resultPreview?: unknown\n}\n\nexport interface ToolTestReport {\n tenantId: string | null\n organizationId: string | null\n total: number\n passed: number\n failed: number\n skipped: number\n records: ToolTestRecord[]\n}\n\ninterface RawAiToolsModule {\n aiToolConfigEntriesRaw?: { moduleId: string; tools: unknown[] }[]\n aiToolConfigEntries?: { moduleId: string; tools: unknown[] }[]\n}\n\nfunction isToolDefinition(value: unknown): value is AiToolDefinition {\n if (!value || typeof value !== 'object') return false\n const candidate = value as Record<string, unknown>\n return (\n typeof candidate.name === 'string' &&\n typeof candidate.description === 'string' &&\n candidate.inputSchema !== undefined &&\n typeof candidate.handler === 'function'\n )\n}\n\n/**\n * Locate `apps/mercato/.mercato/generated/ai-tools.generated.ts` without\n * hardcoding the workspace layout. Searches upward from this file's\n * compiled location; in the monorepo dist this is\n * `packages/ai-assistant/dist/...` so we walk up until we hit a directory\n * containing `apps/mercato/.mercato/generated`.\n */\nfunction findGeneratedAiToolsPath(): string | null {\n const here = (() => {\n try {\n return fileURLToPath(import.meta.url)\n } catch {\n return null\n }\n })()\n if (!here) return null\n let cursor = path.dirname(here)\n for (let i = 0; i < 12; i++) {\n const candidate = path.join(cursor, 'apps', 'mercato', '.mercato', 'generated', 'ai-tools.generated.ts')\n if (fs.existsSync(candidate)) return candidate\n const next = path.dirname(cursor)\n if (next === cursor) break\n cursor = next\n }\n // Fallback: cwd-based lookup (when CLI is invoked from apps/mercato).\n const fromCwd = path.resolve(process.cwd(), 'apps', 'mercato', '.mercato', 'generated', 'ai-tools.generated.ts')\n if (fs.existsSync(fromCwd)) return fromCwd\n const fromCwdDirect = path.resolve(process.cwd(), '.mercato', 'generated', 'ai-tools.generated.ts')\n if (fs.existsSync(fromCwdDirect)) return fromCwdDirect\n return null\n}\n\n/**\n * Compile-and-import `ai-tools.generated.ts` on the fly. Mirrors the approach\n * used by `loadBootstrapData` in `@open-mercato/shared/lib/bootstrap/dynamicLoader`:\n * resolves the `@/` alias to the app root, marks every other package import as\n * external, and emits a sibling `.mjs` we can `import()` from Node. Cached on\n * mtime so repeat runs in the same process don't recompile.\n */\nasync function compileAndImportGenerated(tsPath: string): Promise<RawAiToolsModule> {\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 const tsExists = fs.existsSync(tsPath)\n if (!tsExists) {\n throw new Error(`Generated file not found: ${tsPath}`)\n }\n const jsExists = fs.existsSync(jsPath)\n const needsCompile =\n !jsExists || fs.statSync(tsPath).mtimeMs > fs.statSync(jsPath).mtimeMs\n if (needsCompile) {\n const esbuild = await import('esbuild')\n // Transpile-only: don't bundle. Generated registry files only declare an\n // array literal whose entries are static `import(\"\u2026\")` arrow functions \u2014\n // we want those `import()` strings to stay as runtime imports so Node\n // resolves them lazily through the workspace's normal module resolution.\n // Eagerly bundling them pulls Next.js / route handler internals into the\n // .mjs and breaks at runtime (e.g. `next/server` package-exports map).\n const tsSource = fs.readFileSync(tsPath, 'utf-8')\n // Rewrite `@/...` aliases to absolute paths so Node can resolve them.\n const aliasRewritten = tsSource.replace(\n /from\\s+[\"']@\\/([^\"']+)[\"']/g,\n (_match, p1: string) => {\n const target = path.join(appRoot, p1)\n const candidate = fs.existsSync(target)\n ? target\n : fs.existsSync(target + '.ts')\n ? target + '.ts'\n : target\n return `from ${JSON.stringify(pathToFileURL(candidate).href)}`\n },\n ).replace(\n /import\\s*\\(\\s*[\"']@\\/([^\"']+)[\"']\\s*\\)/g,\n (_match, p1: string) => {\n const target = path.join(appRoot, p1)\n const candidate = fs.existsSync(target)\n ? target\n : fs.existsSync(target + '.ts')\n ? target + '.ts'\n : target\n return `import(${JSON.stringify(pathToFileURL(candidate).href)})`\n },\n )\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 return (await import(pathToFileURL(jsPath).href)) as RawAiToolsModule\n}\n\n/**\n * Compile-and-import `api-routes.generated.ts` and register its manifest with\n * the shared registry. Many tool handlers delegate to `aiApiOperationRunner`\n * which fails closed when no manifest is registered. Idempotent: registering\n * the same array twice is safe.\n */\nasync function ensureApiRouteManifestsRegistered(generatedDir: string): Promise<void> {\n const tsPath = path.join(generatedDir, 'api-routes.generated.ts')\n if (!fs.existsSync(tsPath)) return\n try {\n const mod = (await compileAndImportGenerated(tsPath)) as Record<string, unknown>\n const apiRoutes = (mod as { apiRoutes?: unknown }).apiRoutes\n if (!Array.isArray(apiRoutes)) {\n console.warn('[tool-test-runner] api-routes.generated.mjs returned no apiRoutes array')\n return\n }\n const registry = await import('@open-mercato/shared/modules/registry')\n registry.registerApiRouteManifests(\n apiRoutes as Parameters<typeof registry.registerApiRouteManifests>[0],\n )\n if (process.env.OM_TOOL_TEST_DEBUG === '1') {\n console.log(\n `[tool-test-runner] Registered ${apiRoutes.length} api-route manifests; getApiRouteManifests().length=${registry.getApiRouteManifests().length}`,\n )\n }\n } catch (error) {\n console.warn(\n '[tool-test-runner] Could not register api-routes manifest:',\n error instanceof Error ? error.message : error,\n )\n }\n}\n\nasync function loadGeneratedTools(): Promise<{ moduleId: string; tools: AiToolDefinition[] }[]> {\n const tsPath = findGeneratedAiToolsPath()\n if (!tsPath) {\n throw new Error(\n 'Could not locate apps/<app>/.mercato/generated/ai-tools.generated.ts. Run `yarn generate` first.',\n )\n }\n await ensureApiRouteManifestsRegistered(path.dirname(tsPath))\n const mod = await compileAndImportGenerated(tsPath)\n const entries = mod.aiToolConfigEntriesRaw ?? mod.aiToolConfigEntries ?? []\n const result: { moduleId: string; tools: AiToolDefinition[] }[] = []\n for (const entry of entries) {\n if (!entry || typeof entry.moduleId !== 'string') continue\n const tools = Array.isArray(entry.tools)\n ? entry.tools.filter(isToolDefinition)\n : []\n result.push({ moduleId: entry.moduleId, tools })\n }\n return result\n}\n\nasync function pickDefaultTenant(\n container: AwilixContainer,\n): Promise<{ tenantId: string; organizationId: string | null; userId: string | null } | null> {\n try {\n const em = container.resolve<{\n getConnection: () => { execute: (sql: string) => Promise<unknown[]> }\n }>('em') as unknown as {\n getConnection: () => { execute: (sql: string) => Promise<Record<string, unknown>[]> }\n }\n const conn = em.getConnection()\n const tenantRows = await conn.execute(\n `SELECT id FROM tenants WHERE deleted_at IS NULL ORDER BY created_at ASC LIMIT 1`,\n )\n const tenantId =\n Array.isArray(tenantRows) && tenantRows[0]\n ? String((tenantRows[0] as Record<string, unknown>).id)\n : null\n if (!tenantId) return null\n const escaped = tenantId.replace(/'/g, \"''\")\n const orgRows = await conn.execute(\n `SELECT id FROM organizations WHERE tenant_id = '${escaped}' AND deleted_at IS NULL ORDER BY created_at ASC LIMIT 1`,\n )\n const organizationId =\n Array.isArray(orgRows) && orgRows[0]\n ? String((orgRows[0] as Record<string, unknown>).id)\n : null\n const userRows = await conn.execute(\n `SELECT id FROM users WHERE tenant_id = '${escaped}' AND deleted_at IS NULL ORDER BY created_at ASC LIMIT 1`,\n )\n const userId =\n Array.isArray(userRows) && userRows[0]\n ? String((userRows[0] as Record<string, unknown>).id)\n : null\n return { tenantId, organizationId, userId }\n } catch (error) {\n if (process.env.OM_TOOL_TEST_DEBUG === '1') {\n console.warn('[tool-test-runner] pickDefaultTenant failed:', error)\n }\n return null\n }\n}\n\nfunction buildSuperAdminContext(\n container: AwilixContainer,\n tenantId: string | null,\n organizationId: string | null,\n userId: string | null,\n): McpToolContext {\n return {\n tenantId,\n organizationId,\n userId,\n container,\n userFeatures: ['*'],\n isSuperAdmin: true,\n }\n}\n\nfunction clipPreview(value: unknown): unknown {\n try {\n const json = JSON.stringify(value)\n if (json.length <= 400) return value\n return `${json.slice(0, 400)}\u2026(truncated, ${json.length} bytes)`\n } catch {\n return '[unserializable]'\n }\n}\n\nfunction dummyAgentForTool(tool: AiToolDefinition): AiAgentDefinition {\n // The runner never registers this agent; it's only used as input to\n // `prepareMutation` for shape compatibility. The id namespace is namespaced\n // under `__test__` so it cannot collide with real agents.\n const policy: AiAgentMutationPolicy = 'destructive-confirm-required'\n return {\n id: `__test__.${tool.name}`,\n moduleId: tool.name.split('.')[0] ?? '__test__',\n label: 'Tool Test Runner',\n description: 'Synthetic agent used by the tool test runner',\n systemPrompt: '',\n allowedTools: [tool.name],\n readOnly: false,\n mutationPolicy: policy,\n } as AiAgentDefinition\n}\n\nasync function executeReadTool(\n tool: AiToolDefinition,\n args: Record<string, unknown>,\n container: AwilixContainer,\n ctx: McpToolContext,\n): Promise<unknown> {\n // Mirror the dispatcher: resolve a fresh container per call so EM identity\n // map state is clean between tools.\n const fresh = await createRequestContainer()\n const freshCtx: McpToolContext = { ...ctx, container: fresh, tool }\n void container // keep arg for future use\n return tool.handler(args as never, freshCtx)\n}\n\nasync function executeMutationTool(\n tool: AiToolDefinition,\n args: Record<string, unknown>,\n container: AwilixContainer,\n ctx: McpToolContext,\n): Promise<unknown> {\n // Route through prepareMutation so we assert the approval contract still\n // holds \u2014 the handler must NEVER write directly. We pass a destructive\n // policy so the runtime treats the call as a confirmation candidate and\n // creates a pending-action row.\n const fresh = await createRequestContainer()\n const agent = dummyAgentForTool(tool)\n const userId = ctx.userId ?? '00000000-0000-0000-0000-000000000000'\n const { uiPart, pendingAction } = await prepareMutation(\n {\n agent,\n tool,\n toolCallArgs: args,\n conversationId: null,\n mutationPolicyOverride: null,\n },\n {\n tenantId: ctx.tenantId,\n organizationId: ctx.organizationId,\n userId,\n features: ctx.userFeatures,\n isSuperAdmin: ctx.isSuperAdmin,\n container: fresh,\n },\n )\n // Exercise the actual handler via the same path the production confirm\n // route uses. Without this, mutation tools whose handler crashes (e.g.\n // missing `ctx.tool` for the API operation runner) would be reported as\n // passing because `prepareMutation` never invokes the handler.\n // `prepareMutation` already enforced `tenantId` is non-null above; cast\n // here for the stricter `PendingActionExecuteContext` shape.\n const confirmTenantId = ctx.tenantId as string\n const confirmContainer = await createRequestContainer()\n const confirmation = await executePendingActionConfirm({\n action: pendingAction,\n agent,\n tool,\n ctx: {\n tenantId: confirmTenantId,\n organizationId: ctx.organizationId,\n userId,\n container: confirmContainer,\n userFeatures: ctx.userFeatures,\n isSuperAdmin: ctx.isSuperAdmin,\n },\n emitEvent: async () => {},\n })\n if (!confirmation.ok) {\n const error = (confirmation.executionResult as { error?: { message?: string } } | undefined)?.error\n const cause = confirmation.cause\n const causeMessage =\n cause instanceof Error\n ? cause.message\n : typeof cause === 'string'\n ? cause\n : null\n const message = error?.message ?? causeMessage ?? 'handler invocation failed'\n throw new Error(message)\n }\n return {\n status: 'pending-confirmation',\n pendingActionId: pendingAction.id,\n expiresAt: pendingAction.expiresAt.toISOString(),\n uiPartType: (uiPart as { type?: string } | undefined)?.type ?? null,\n handlerExecuted: true,\n }\n}\n\nasync function resolveFixtureInput(\n fixture: ToolFixture,\n resolved: Map<string, AiToolDefinition>,\n container: AwilixContainer,\n ctx: McpToolContext,\n): Promise<{ ok: true; input: Record<string, unknown> } | { ok: false; reason: string }> {\n if (fixture.input) return { ok: true, input: { ...fixture.input } }\n if (!fixture.idFrom) {\n return { ok: false, reason: 'fixture has neither input nor idFrom' }\n }\n const sourceTool = resolved.get(fixture.idFrom)\n if (!sourceTool) {\n return { ok: false, reason: `idFrom tool not found: ${fixture.idFrom}` }\n }\n const sourceFixture = getFixture(fixture.idFrom)\n if (!sourceFixture?.input) {\n return {\n ok: false,\n reason: `idFrom source ${fixture.idFrom} has no static input fixture`,\n }\n }\n let listResult: unknown\n try {\n listResult = await executeReadTool(\n sourceTool,\n { ...sourceFixture.input },\n container,\n ctx,\n )\n } catch (error) {\n return {\n ok: false,\n reason: `idFrom source ${fixture.idFrom} threw: ${\n error instanceof Error ? error.message : String(error)\n }`,\n }\n }\n const records = extractRecords(listResult)\n if (!records.length) {\n return {\n ok: false,\n reason: `idFrom source ${fixture.idFrom} returned no records`,\n }\n }\n const idField = 'id'\n const id = (records[0] as Record<string, unknown>)[idField]\n if (typeof id !== 'string' || !id) {\n return {\n ok: false,\n reason: `idFrom source ${fixture.idFrom} first record has no string id`,\n }\n }\n const bindAs = fixture.bindAs ?? 'id'\n return {\n ok: true,\n input: { [bindAs]: id, ...(fixture.extra ?? {}) },\n }\n}\n\nfunction extractRecords(value: unknown): unknown[] {\n if (!value || typeof value !== 'object') return []\n const candidate = value as Record<string, unknown>\n for (const key of ['records', 'items', 'people', 'companies', 'deals', 'results', 'data']) {\n const arr = candidate[key]\n if (Array.isArray(arr)) return arr\n }\n return []\n}\n\nexport interface RunToolTestsOptions {\n tenantId?: string | null\n organizationId?: string | null\n userId?: string | null\n moduleFilter?: string | null\n includeMutations?: boolean\n}\n\nexport async function runToolTests(\n options: RunToolTestsOptions = {},\n): Promise<ToolTestReport> {\n const includeMutations = options.includeMutations ?? true\n const container = await createRequestContainer()\n let tenantId: string | null = options.tenantId ?? null\n let organizationId: string | null = options.organizationId ?? null\n let userId: string | null = options.userId ?? null\n if (!tenantId) {\n const picked = await pickDefaultTenant(container)\n if (picked) {\n tenantId = picked.tenantId\n organizationId = picked.organizationId\n userId = userId ?? picked.userId\n }\n }\n const ctx = buildSuperAdminContext(container, tenantId, organizationId, userId)\n const grouped = await loadGeneratedTools()\n\n const flat: { moduleId: string; tool: AiToolDefinition }[] = []\n for (const group of grouped) {\n if (options.moduleFilter && group.moduleId !== options.moduleFilter) continue\n for (const tool of group.tools) flat.push({ moduleId: group.moduleId, tool })\n }\n const resolved = new Map<string, AiToolDefinition>()\n for (const { tool } of flat) resolved.set(tool.name, tool)\n\n const records: ToolTestRecord[] = []\n for (const { moduleId, tool } of flat) {\n const start = Date.now()\n const isMutation = tool.isMutation === true\n const fixture = getFixture(tool.name)\n if (!fixture) {\n records.push({\n module: moduleId,\n tool: tool.name,\n isMutation,\n status: 'skip',\n durationMs: Date.now() - start,\n reason: 'no fixture',\n })\n continue\n }\n if (fixture.skip) {\n records.push({\n module: moduleId,\n tool: tool.name,\n isMutation,\n status: 'skip',\n durationMs: Date.now() - start,\n reason: fixture.note ?? 'skip-by-fixture',\n })\n continue\n }\n if (isMutation && !includeMutations) {\n records.push({\n module: moduleId,\n tool: tool.name,\n isMutation,\n status: 'skip',\n durationMs: Date.now() - start,\n reason: 'mutations excluded',\n })\n continue\n }\n const inputResult = await resolveFixtureInput(fixture, resolved, container, ctx)\n if (!inputResult.ok) {\n records.push({\n module: moduleId,\n tool: tool.name,\n isMutation,\n status: 'skip',\n durationMs: Date.now() - start,\n reason: inputResult.reason,\n })\n continue\n }\n try {\n const result = isMutation\n ? await executeMutationTool(tool, inputResult.input, container, ctx)\n : await executeReadTool(tool, inputResult.input, container, ctx)\n // Result must be JSON-serializable.\n JSON.stringify(result)\n // Mutation tools must return a pending-confirmation envelope.\n if (\n isMutation &&\n (typeof result !== 'object' ||\n result === null ||\n (result as Record<string, unknown>).status !== 'pending-confirmation')\n ) {\n records.push({\n module: moduleId,\n tool: tool.name,\n isMutation,\n status: 'fail',\n durationMs: Date.now() - start,\n reason: 'mutation tool did not route through prepareMutation (no pending-confirmation envelope)',\n resultPreview: clipPreview(result),\n })\n continue\n }\n records.push({\n module: moduleId,\n tool: tool.name,\n isMutation,\n status: 'pass',\n durationMs: Date.now() - start,\n resultPreview: clipPreview(result),\n })\n } catch (error) {\n records.push({\n module: moduleId,\n tool: tool.name,\n isMutation,\n status: 'fail',\n durationMs: Date.now() - start,\n reason: error instanceof Error ? error.message : String(error),\n })\n }\n }\n const passed = records.filter((r) => r.status === 'pass').length\n const failed = records.filter((r) => r.status === 'fail').length\n const skipped = records.filter((r) => r.status === 'skip').length\n return {\n tenantId,\n organizationId,\n total: records.length,\n passed,\n failed,\n skipped,\n records,\n }\n}\n"],
5
- "mappings": "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",
4
+ "sourcesContent": ["/**\n * In-process AI tool test runner.\n *\n * Iterates every tool registered in `ai-tools.generated.ts`, invokes the\n * handler against a super-admin tenant context, and returns a structured\n * report. Used by the `mercato ai_assistant test-tools` CLI subcommand and\n * by `.ai/qa/tests/integration/TC-INT-AI-TOOLS.spec.ts`.\n *\n * Safety posture:\n * - No HTTP exposure \u2014 runs only inside the Node process driving the CLI.\n * - Mutation tools are exercised through `prepareMutation` and we assert a\n * pending-action envelope is returned. The pending-action row is created\n * in `ai_pending_actions` but never confirmed, so no real write happens.\n * - Tools without an explicit fixture entry are skipped with reason\n * `'no fixture'` rather than failing.\n */\nimport path from 'node:path'\nimport fs from 'node:fs'\nimport { fileURLToPath, pathToFileURL } from 'node:url'\nimport type { AwilixContainer } from 'awilix'\nimport { createRequestContainer } from '@open-mercato/shared/lib/di/container'\nimport type { McpToolContext, AiToolDefinition } from './types'\nimport type { AiAgentDefinition, AiAgentMutationPolicy } from './ai-agent-definition'\nimport { getFixture, type ToolFixture } from './tool-test-fixtures'\nimport { prepareMutation } from './prepare-mutation'\nimport { executePendingActionConfirm } from './pending-action-executor'\n\nexport type ToolTestStatus = 'pass' | 'fail' | 'skip'\n\nexport interface ToolTestRecord {\n module: string\n tool: string\n isMutation: boolean\n status: ToolTestStatus\n durationMs: number\n reason?: string\n resultPreview?: unknown\n}\n\nexport interface ToolTestReport {\n tenantId: string | null\n organizationId: string | null\n total: number\n passed: number\n failed: number\n skipped: number\n records: ToolTestRecord[]\n}\n\ninterface RawAiToolsModule {\n aiToolConfigEntriesRaw?: { moduleId: string; tools: unknown[] }[]\n aiToolConfigEntries?: { moduleId: string; tools: unknown[] }[]\n}\n\nfunction isToolDefinition(value: unknown): value is AiToolDefinition {\n if (!value || typeof value !== 'object') return false\n const candidate = value as Record<string, unknown>\n return (\n typeof candidate.name === 'string' &&\n typeof candidate.description === 'string' &&\n candidate.inputSchema !== undefined &&\n typeof candidate.handler === 'function'\n )\n}\n\n/**\n * Locate `apps/mercato/.mercato/generated/ai-tools.generated.ts` without\n * hardcoding the workspace layout. Searches upward from this file's\n * compiled location; in the monorepo dist this is\n * `packages/ai-assistant/dist/...` so we walk up until we hit a directory\n * containing `apps/mercato/.mercato/generated`.\n */\nfunction findGeneratedAiToolsPath(): string | null {\n const here = (() => {\n try {\n return fileURLToPath(import.meta.url)\n } catch {\n return null\n }\n })()\n if (!here) return null\n let cursor = path.dirname(here)\n for (let i = 0; i < 12; i++) {\n const candidate = path.join(cursor, 'apps', 'mercato', '.mercato', 'generated', 'ai-tools.generated.ts')\n if (fs.existsSync(candidate)) return candidate\n const next = path.dirname(cursor)\n if (next === cursor) break\n cursor = next\n }\n // Fallback: cwd-based lookup (when CLI is invoked from apps/mercato).\n const fromCwd = path.resolve(process.cwd(), 'apps', 'mercato', '.mercato', 'generated', 'ai-tools.generated.ts')\n if (fs.existsSync(fromCwd)) return fromCwd\n const fromCwdDirect = path.resolve(process.cwd(), '.mercato', 'generated', 'ai-tools.generated.ts')\n if (fs.existsSync(fromCwdDirect)) return fromCwdDirect\n return null\n}\n\n/**\n * Compile-and-import `ai-tools.generated.ts` on the fly. Mirrors the approach\n * used by `loadBootstrapData` in `@open-mercato/shared/lib/bootstrap/dynamicLoader`:\n * resolves the `@/` alias to the app root, marks every other package import as\n * external, and emits a sibling `.mjs` we can `import()` from Node. Cached on\n * mtime so repeat runs in the same process don't recompile.\n */\nasync function compileAndImportGenerated(tsPath: string): Promise<RawAiToolsModule> {\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 const tsExists = fs.existsSync(tsPath)\n if (!tsExists) {\n throw new Error(`Generated file not found: ${tsPath}`)\n }\n const jsExists = fs.existsSync(jsPath)\n const needsCompile =\n !jsExists || fs.statSync(tsPath).mtimeMs > fs.statSync(jsPath).mtimeMs\n if (needsCompile) {\n const esbuild = await import('esbuild')\n // Transpile-only: don't bundle. Generated registry files only declare an\n // array literal whose entries are static `import(\"\u2026\")` arrow functions \u2014\n // we want those `import()` strings to stay as runtime imports so Node\n // resolves them lazily through the workspace's normal module resolution.\n // Eagerly bundling them pulls Next.js / route handler internals into the\n // .mjs and breaks at runtime (e.g. `next/server` package-exports map).\n const tsSource = fs.readFileSync(tsPath, 'utf-8')\n // Rewrite `@/...` aliases to absolute paths so Node can resolve them.\n const aliasRewritten = tsSource.replace(\n /from\\s+[\"']@\\/([^\"']+)[\"']/g,\n (_match, p1: string) => {\n const target = path.join(appRoot, p1)\n const candidate = fs.existsSync(target)\n ? target\n : fs.existsSync(target + '.ts')\n ? target + '.ts'\n : target\n return `from ${JSON.stringify(pathToFileURL(candidate).href)}`\n },\n ).replace(\n /import\\s*\\(\\s*[\"']@\\/([^\"']+)[\"']\\s*\\)/g,\n (_match, p1: string) => {\n const target = path.join(appRoot, p1)\n const candidate = fs.existsSync(target)\n ? target\n : fs.existsSync(target + '.ts')\n ? target + '.ts'\n : target\n return `import(${JSON.stringify(pathToFileURL(candidate).href)})`\n },\n )\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 return (await import(pathToFileURL(jsPath).href)) as RawAiToolsModule\n}\n\n/**\n * Compile-and-import `api-routes.generated.ts` and register its manifest with\n * the shared registry. Many tool handlers delegate to `aiApiOperationRunner`\n * which fails closed when no manifest is registered. Idempotent: registering\n * the same array twice is safe.\n */\nasync function ensureApiRouteManifestsRegistered(generatedDir: string): Promise<void> {\n const tsPath = path.join(generatedDir, 'api-routes.generated.ts')\n if (!fs.existsSync(tsPath)) return\n try {\n const mod = (await compileAndImportGenerated(tsPath)) as Record<string, unknown>\n const apiRoutes = (mod as { apiRoutes?: unknown }).apiRoutes\n if (!Array.isArray(apiRoutes)) {\n console.warn('[tool-test-runner] api-routes.generated.mjs returned no apiRoutes array')\n return\n }\n const registry = await import('@open-mercato/shared/modules/registry')\n registry.registerApiRouteManifests(\n apiRoutes as Parameters<typeof registry.registerApiRouteManifests>[0],\n )\n if (process.env.OM_TOOL_TEST_DEBUG === '1') {\n console.log(\n `[tool-test-runner] Registered ${apiRoutes.length} api-route manifests; getApiRouteManifests().length=${registry.getApiRouteManifests().length}`,\n )\n }\n } catch (error) {\n console.warn(\n '[tool-test-runner] Could not register api-routes manifest:',\n error instanceof Error ? error.message : error,\n )\n }\n}\n\nasync function loadGeneratedTools(): Promise<{ moduleId: string; tools: AiToolDefinition[] }[]> {\n const tsPath = findGeneratedAiToolsPath()\n if (!tsPath) {\n throw new Error(\n 'Could not locate apps/<app>/.mercato/generated/ai-tools.generated.ts. Run `yarn generate` first.',\n )\n }\n await ensureApiRouteManifestsRegistered(path.dirname(tsPath))\n const mod = await compileAndImportGenerated(tsPath)\n const entries = mod.aiToolConfigEntriesRaw ?? mod.aiToolConfigEntries ?? []\n const result: { moduleId: string; tools: AiToolDefinition[] }[] = []\n for (const entry of entries) {\n if (!entry || typeof entry.moduleId !== 'string') continue\n const tools = Array.isArray(entry.tools)\n ? entry.tools.filter(isToolDefinition)\n : []\n result.push({ moduleId: entry.moduleId, tools })\n }\n return result\n}\n\nexport async function pickDefaultTenant(\n container: AwilixContainer,\n): Promise<{ tenantId: string; organizationId: string | null; userId: string | null } | null> {\n try {\n const em = container.resolve<{\n getConnection: () => {\n execute: (sql: string, params?: unknown[]) => Promise<Record<string, unknown>[]>\n }\n }>('em') as unknown as {\n getConnection: () => {\n execute: (sql: string, params?: unknown[]) => Promise<Record<string, unknown>[]>\n }\n }\n const conn = em.getConnection()\n const tenantRows = await conn.execute(\n `SELECT id FROM tenants WHERE deleted_at IS NULL ORDER BY created_at ASC LIMIT 1`,\n )\n const tenantId =\n Array.isArray(tenantRows) && tenantRows[0]\n ? String((tenantRows[0] as Record<string, unknown>).id)\n : null\n if (!tenantId) return null\n const orgRows = await conn.execute(\n `SELECT id FROM organizations WHERE tenant_id = ? AND deleted_at IS NULL ORDER BY created_at ASC LIMIT 1`,\n [tenantId],\n )\n const organizationId =\n Array.isArray(orgRows) && orgRows[0]\n ? String((orgRows[0] as Record<string, unknown>).id)\n : null\n const userRows = await conn.execute(\n `SELECT id FROM users WHERE tenant_id = ? AND deleted_at IS NULL ORDER BY created_at ASC LIMIT 1`,\n [tenantId],\n )\n const userId =\n Array.isArray(userRows) && userRows[0]\n ? String((userRows[0] as Record<string, unknown>).id)\n : null\n return { tenantId, organizationId, userId }\n } catch (error) {\n if (process.env.OM_TOOL_TEST_DEBUG === '1') {\n console.warn('[tool-test-runner] pickDefaultTenant failed:', error)\n }\n return null\n }\n}\n\nfunction buildSuperAdminContext(\n container: AwilixContainer,\n tenantId: string | null,\n organizationId: string | null,\n userId: string | null,\n): McpToolContext {\n return {\n tenantId,\n organizationId,\n userId,\n container,\n userFeatures: ['*'],\n isSuperAdmin: true,\n }\n}\n\nfunction clipPreview(value: unknown): unknown {\n try {\n const json = JSON.stringify(value)\n if (json.length <= 400) return value\n return `${json.slice(0, 400)}\u2026(truncated, ${json.length} bytes)`\n } catch {\n return '[unserializable]'\n }\n}\n\nfunction dummyAgentForTool(tool: AiToolDefinition): AiAgentDefinition {\n // The runner never registers this agent; it's only used as input to\n // `prepareMutation` for shape compatibility. The id namespace is namespaced\n // under `__test__` so it cannot collide with real agents.\n const policy: AiAgentMutationPolicy = 'destructive-confirm-required'\n return {\n id: `__test__.${tool.name}`,\n moduleId: tool.name.split('.')[0] ?? '__test__',\n label: 'Tool Test Runner',\n description: 'Synthetic agent used by the tool test runner',\n systemPrompt: '',\n allowedTools: [tool.name],\n readOnly: false,\n mutationPolicy: policy,\n } as AiAgentDefinition\n}\n\nasync function executeReadTool(\n tool: AiToolDefinition,\n args: Record<string, unknown>,\n container: AwilixContainer,\n ctx: McpToolContext,\n): Promise<unknown> {\n // Mirror the dispatcher: resolve a fresh container per call so EM identity\n // map state is clean between tools.\n const fresh = await createRequestContainer()\n const freshCtx: McpToolContext = { ...ctx, container: fresh, tool }\n void container // keep arg for future use\n return tool.handler(args as never, freshCtx)\n}\n\nasync function executeMutationTool(\n tool: AiToolDefinition,\n args: Record<string, unknown>,\n container: AwilixContainer,\n ctx: McpToolContext,\n): Promise<unknown> {\n // Route through prepareMutation so we assert the approval contract still\n // holds \u2014 the handler must NEVER write directly. We pass a destructive\n // policy so the runtime treats the call as a confirmation candidate and\n // creates a pending-action row.\n const fresh = await createRequestContainer()\n const agent = dummyAgentForTool(tool)\n const userId = ctx.userId ?? '00000000-0000-0000-0000-000000000000'\n const { uiPart, pendingAction } = await prepareMutation(\n {\n agent,\n tool,\n toolCallArgs: args,\n conversationId: null,\n mutationPolicyOverride: null,\n },\n {\n tenantId: ctx.tenantId,\n organizationId: ctx.organizationId,\n userId,\n features: ctx.userFeatures,\n isSuperAdmin: ctx.isSuperAdmin,\n container: fresh,\n },\n )\n // Exercise the actual handler via the same path the production confirm\n // route uses. Without this, mutation tools whose handler crashes (e.g.\n // missing `ctx.tool` for the API operation runner) would be reported as\n // passing because `prepareMutation` never invokes the handler.\n // `prepareMutation` already enforced `tenantId` is non-null above; cast\n // here for the stricter `PendingActionExecuteContext` shape.\n const confirmTenantId = ctx.tenantId as string\n const confirmContainer = await createRequestContainer()\n const confirmation = await executePendingActionConfirm({\n action: pendingAction,\n agent,\n tool,\n ctx: {\n tenantId: confirmTenantId,\n organizationId: ctx.organizationId,\n userId,\n container: confirmContainer,\n userFeatures: ctx.userFeatures,\n isSuperAdmin: ctx.isSuperAdmin,\n },\n emitEvent: async () => {},\n })\n if (!confirmation.ok) {\n const error = (confirmation.executionResult as { error?: { message?: string } } | undefined)?.error\n const cause = confirmation.cause\n const causeMessage =\n cause instanceof Error\n ? cause.message\n : typeof cause === 'string'\n ? cause\n : null\n const message = error?.message ?? causeMessage ?? 'handler invocation failed'\n throw new Error(message)\n }\n return {\n status: 'pending-confirmation',\n pendingActionId: pendingAction.id,\n expiresAt: pendingAction.expiresAt.toISOString(),\n uiPartType: (uiPart as { type?: string } | undefined)?.type ?? null,\n handlerExecuted: true,\n }\n}\n\nasync function resolveFixtureInput(\n fixture: ToolFixture,\n resolved: Map<string, AiToolDefinition>,\n container: AwilixContainer,\n ctx: McpToolContext,\n): Promise<{ ok: true; input: Record<string, unknown> } | { ok: false; reason: string }> {\n if (fixture.input) return { ok: true, input: { ...fixture.input } }\n if (!fixture.idFrom) {\n return { ok: false, reason: 'fixture has neither input nor idFrom' }\n }\n const sourceTool = resolved.get(fixture.idFrom)\n if (!sourceTool) {\n return { ok: false, reason: `idFrom tool not found: ${fixture.idFrom}` }\n }\n const sourceFixture = getFixture(fixture.idFrom)\n if (!sourceFixture?.input) {\n return {\n ok: false,\n reason: `idFrom source ${fixture.idFrom} has no static input fixture`,\n }\n }\n let listResult: unknown\n try {\n listResult = await executeReadTool(\n sourceTool,\n { ...sourceFixture.input },\n container,\n ctx,\n )\n } catch (error) {\n return {\n ok: false,\n reason: `idFrom source ${fixture.idFrom} threw: ${\n error instanceof Error ? error.message : String(error)\n }`,\n }\n }\n const records = extractRecords(listResult)\n if (!records.length) {\n return {\n ok: false,\n reason: `idFrom source ${fixture.idFrom} returned no records`,\n }\n }\n const idField = 'id'\n const id = (records[0] as Record<string, unknown>)[idField]\n if (typeof id !== 'string' || !id) {\n return {\n ok: false,\n reason: `idFrom source ${fixture.idFrom} first record has no string id`,\n }\n }\n const bindAs = fixture.bindAs ?? 'id'\n return {\n ok: true,\n input: { [bindAs]: id, ...(fixture.extra ?? {}) },\n }\n}\n\nfunction extractRecords(value: unknown): unknown[] {\n if (!value || typeof value !== 'object') return []\n const candidate = value as Record<string, unknown>\n for (const key of ['records', 'items', 'people', 'companies', 'deals', 'results', 'data']) {\n const arr = candidate[key]\n if (Array.isArray(arr)) return arr\n }\n return []\n}\n\nexport interface RunToolTestsOptions {\n tenantId?: string | null\n organizationId?: string | null\n userId?: string | null\n moduleFilter?: string | null\n includeMutations?: boolean\n}\n\nexport async function runToolTests(\n options: RunToolTestsOptions = {},\n): Promise<ToolTestReport> {\n const includeMutations = options.includeMutations ?? true\n const container = await createRequestContainer()\n let tenantId: string | null = options.tenantId ?? null\n let organizationId: string | null = options.organizationId ?? null\n let userId: string | null = options.userId ?? null\n if (!tenantId) {\n const picked = await pickDefaultTenant(container)\n if (picked) {\n tenantId = picked.tenantId\n organizationId = picked.organizationId\n userId = userId ?? picked.userId\n }\n }\n const ctx = buildSuperAdminContext(container, tenantId, organizationId, userId)\n const grouped = await loadGeneratedTools()\n\n const flat: { moduleId: string; tool: AiToolDefinition }[] = []\n for (const group of grouped) {\n if (options.moduleFilter && group.moduleId !== options.moduleFilter) continue\n for (const tool of group.tools) flat.push({ moduleId: group.moduleId, tool })\n }\n const resolved = new Map<string, AiToolDefinition>()\n for (const { tool } of flat) resolved.set(tool.name, tool)\n\n const records: ToolTestRecord[] = []\n for (const { moduleId, tool } of flat) {\n const start = Date.now()\n const isMutation = tool.isMutation === true\n const fixture = getFixture(tool.name)\n if (!fixture) {\n records.push({\n module: moduleId,\n tool: tool.name,\n isMutation,\n status: 'skip',\n durationMs: Date.now() - start,\n reason: 'no fixture',\n })\n continue\n }\n if (fixture.skip) {\n records.push({\n module: moduleId,\n tool: tool.name,\n isMutation,\n status: 'skip',\n durationMs: Date.now() - start,\n reason: fixture.note ?? 'skip-by-fixture',\n })\n continue\n }\n if (isMutation && !includeMutations) {\n records.push({\n module: moduleId,\n tool: tool.name,\n isMutation,\n status: 'skip',\n durationMs: Date.now() - start,\n reason: 'mutations excluded',\n })\n continue\n }\n const inputResult = await resolveFixtureInput(fixture, resolved, container, ctx)\n if (!inputResult.ok) {\n records.push({\n module: moduleId,\n tool: tool.name,\n isMutation,\n status: 'skip',\n durationMs: Date.now() - start,\n reason: inputResult.reason,\n })\n continue\n }\n try {\n const result = isMutation\n ? await executeMutationTool(tool, inputResult.input, container, ctx)\n : await executeReadTool(tool, inputResult.input, container, ctx)\n // Result must be JSON-serializable.\n JSON.stringify(result)\n // Mutation tools must return a pending-confirmation envelope.\n if (\n isMutation &&\n (typeof result !== 'object' ||\n result === null ||\n (result as Record<string, unknown>).status !== 'pending-confirmation')\n ) {\n records.push({\n module: moduleId,\n tool: tool.name,\n isMutation,\n status: 'fail',\n durationMs: Date.now() - start,\n reason: 'mutation tool did not route through prepareMutation (no pending-confirmation envelope)',\n resultPreview: clipPreview(result),\n })\n continue\n }\n records.push({\n module: moduleId,\n tool: tool.name,\n isMutation,\n status: 'pass',\n durationMs: Date.now() - start,\n resultPreview: clipPreview(result),\n })\n } catch (error) {\n records.push({\n module: moduleId,\n tool: tool.name,\n isMutation,\n status: 'fail',\n durationMs: Date.now() - start,\n reason: error instanceof Error ? error.message : String(error),\n })\n }\n }\n const passed = records.filter((r) => r.status === 'pass').length\n const failed = records.filter((r) => r.status === 'fail').length\n const skipped = records.filter((r) => r.status === 'skip').length\n return {\n tenantId,\n organizationId,\n total: records.length,\n passed,\n failed,\n skipped,\n records,\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.5-develop.4882.1.901c3aa813",
3
+ "version": "0.6.5-develop.5033.1.c970204a3f",
4
4
  "type": "module",
5
5
  "engines": {
6
6
  "node": ">=22.0.0"
@@ -98,16 +98,16 @@
98
98
  "zod-to-json-schema": "^3.25.2"
99
99
  },
100
100
  "peerDependencies": {
101
- "@open-mercato/shared": "0.6.5-develop.4882.1.901c3aa813",
102
- "@open-mercato/ui": "0.6.5-develop.4882.1.901c3aa813",
101
+ "@open-mercato/shared": "0.6.5-develop.5033.1.c970204a3f",
102
+ "@open-mercato/ui": "0.6.5-develop.5033.1.c970204a3f",
103
103
  "react": "^19.0.0",
104
104
  "react-dom": "^19.0.0",
105
105
  "zod": ">=3.23.0"
106
106
  },
107
107
  "devDependencies": {
108
- "@open-mercato/cli": "0.6.5-develop.4882.1.901c3aa813",
109
- "@open-mercato/shared": "0.6.5-develop.4882.1.901c3aa813",
110
- "@open-mercato/ui": "0.6.5-develop.4882.1.901c3aa813",
108
+ "@open-mercato/cli": "0.6.5-develop.5033.1.c970204a3f",
109
+ "@open-mercato/shared": "0.6.5-develop.5033.1.c970204a3f",
110
+ "@open-mercato/ui": "0.6.5-develop.5033.1.c970204a3f",
111
111
  "@types/react": "^19.2.16",
112
112
  "@types/react-dom": "^19.2.3",
113
113
  "react": "19.2.7",
@@ -372,10 +372,7 @@ test.describe('TC-AI-AGENT-LOOP-001–006: agentic loop controls', () => {
372
372
  test.setTimeout(60_000);
373
373
  await login(page, 'superadmin');
374
374
 
375
- let capturedAgentsPayload: typeof agentsPayload | null = null;
376
-
377
375
  await page.route('**/api/ai_assistant/ai/agents', async (route) => {
378
- capturedAgentsPayload = agentsPayload;
379
376
  await route.fulfill({
380
377
  status: 200,
381
378
  contentType: 'application/json',
@@ -383,31 +380,22 @@ test.describe('TC-AI-AGENT-LOOP-001–006: agentic loop controls', () => {
383
380
  });
384
381
  });
385
382
 
386
- // The agents request fires from a post-hydration `useQuery`, not from
387
- // navigation, so the route handler that sets `capturedAgentsPayload` runs
388
- // after `goto` resolves. Await the response deterministically instead of
389
- // asserting the captured payload immediately (which races hydration).
390
- const agentsResponsePromise = page.waitForResponse(
391
- (response) =>
392
- response.url().includes('/api/ai_assistant/ai/agents') &&
393
- response.request().method() === 'GET',
394
- );
395
-
396
- await page.goto(playgroundPath, { waitUntil: 'domcontentloaded' });
397
- await agentsResponsePromise;
383
+ const capturedAgentsPayload = await page.evaluate(async () => {
384
+ const response = await fetch('/api/ai_assistant/ai/agents');
385
+ return (await response.json()) as typeof agentsPayload;
386
+ });
398
387
 
399
388
  // Verify that the mocked payload carrying executionEngine was served.
400
389
  // This asserts the agents API contract for Phase 5:
401
390
  // - tool-loop-agent entries include `executionEngine: 'tool-loop-agent'`
402
391
  // - stream-text entries either omit it or set `executionEngine: 'stream-text'`
403
- expect(capturedAgentsPayload).not.toBeNull();
404
- const toolLoopEntry = capturedAgentsPayload!.agents.find(
392
+ const toolLoopEntry = capturedAgentsPayload.agents.find(
405
393
  (a: (typeof agentsPayload)['agents'][number]) => a.id === 'catalog.tool_loop_assistant',
406
394
  );
407
395
  expect(toolLoopEntry).toBeDefined();
408
396
  expect(toolLoopEntry?.executionEngine).toBe('tool-loop-agent');
409
397
 
410
- const streamTextEntry = capturedAgentsPayload!.agents.find(
398
+ const streamTextEntry = capturedAgentsPayload.agents.find(
411
399
  (a: (typeof agentsPayload)['agents'][number]) => a.id === 'customers.account_assistant',
412
400
  );
413
401
  expect(streamTextEntry).toBeDefined();
@@ -78,6 +78,8 @@ const STEP_EVENT = {
78
78
  };
79
79
 
80
80
  test.describe('TC-AI-TOKEN-USAGE-001–005: token usage stats page', () => {
81
+ test.describe.configure({ timeout: 120_000 });
82
+
81
83
  test('TC-AI-TOKEN-USAGE-001: usage page renders summary tiles for superadmin', async ({ page }) => {
82
84
  await login(page, 'superadmin');
83
85
 
@@ -97,7 +99,7 @@ test.describe('TC-AI-TOKEN-USAGE-001–005: token usage stats page', () => {
97
99
  });
98
100
  });
99
101
 
100
- await page.goto(USAGE_PAGE, { waitUntil: 'domcontentloaded' });
102
+ await page.goto(USAGE_PAGE, { waitUntil: 'commit' });
101
103
 
102
104
  const summaryTile = page.locator('p.font-semibold.text-xl', { hasText: /^(1,000|500)$/ }).first();
103
105
  await expect(summaryTile).toBeVisible({ timeout: 15_000 });
@@ -130,7 +132,7 @@ test.describe('TC-AI-TOKEN-USAGE-001–005: token usage stats page', () => {
130
132
  (response) => response.url().includes('/api/ai_assistant/usage/sessions') && response.status() === 200,
131
133
  { timeout: 15_000 },
132
134
  );
133
- await page.goto(USAGE_PAGE, { waitUntil: 'domcontentloaded' });
135
+ await page.goto(USAGE_PAGE, { waitUntil: 'commit' });
134
136
  await Promise.all([initialDailyRequest, initialSessionsRequest]);
135
137
 
136
138
  const fromInput = page.locator('#usage-from');
@@ -178,7 +180,7 @@ test.describe('TC-AI-TOKEN-USAGE-001–005: token usage stats page', () => {
178
180
  });
179
181
  });
180
182
 
181
- await page.goto(USAGE_PAGE, { waitUntil: 'domcontentloaded' });
183
+ await page.goto(USAGE_PAGE, { waitUntil: 'commit' });
182
184
 
183
185
  const sessionCell = page.getByText('00000000').first();
184
186
  await expect(sessionCell).toBeVisible({ timeout: 15_000 });
@@ -216,7 +218,7 @@ test.describe('TC-AI-TOKEN-USAGE-001–005: token usage stats page', () => {
216
218
  });
217
219
  });
218
220
 
219
- await page.goto(USAGE_PAGE, { waitUntil: 'domcontentloaded' });
221
+ await page.goto(USAGE_PAGE, { waitUntil: 'commit' });
220
222
 
221
223
  const sessionCell = page.getByText('00000000').first();
222
224
  await expect(sessionCell).toBeVisible({ timeout: 15_000 });
@@ -233,7 +235,7 @@ test.describe('TC-AI-TOKEN-USAGE-001–005: token usage stats page', () => {
233
235
  const context = await browser.newContext();
234
236
  const page = await context.newPage();
235
237
  try {
236
- await page.goto(USAGE_PAGE, { waitUntil: 'domcontentloaded' });
238
+ await page.goto(USAGE_PAGE, { waitUntil: 'commit' });
237
239
  await page.waitForURL(/\/login/, { timeout: 15_000 });
238
240
  expect(page.url()).toMatch(/\/login/);
239
241
  } finally {