@resolveio/server-lib 22.0.9 → 22.0.11

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.
@@ -139,7 +139,8 @@ var DEFAULT_MAX_FILE_MB = 50;
139
139
  var DEFAULT_MAX_TOTAL_MB = 100;
140
140
  var DEFAULT_MAX_ATTACHMENT_CHARS = 12000;
141
141
  var DEFAULT_MAX_TOTAL_ATTACHMENT_CHARS = 40000;
142
- var DEFAULT_CODEX_MODEL = 'gpt-5.1-codex-mini';
142
+ var DEFAULT_CODEX_MODEL = 'gpt-5.3-codex';
143
+ var DEFAULT_CODEX_FALLBACK_MODEL = 'gpt-5.2-codex';
143
144
  var DEFAULT_CODEX_TIMEOUT_MS = 180000;
144
145
  var AI_ASSISTANT_MONGO_DEFAULT_LIMIT = 20;
145
146
  var AI_ASSISTANT_MONGO_MAX_LIMIT = 200;
@@ -166,6 +167,9 @@ var AI_ASSISTANT_PLANNER_MAX_ROUTES = 200;
166
167
  var AI_ASSISTANT_PLANNER_DEBUG_MAX_CHARS = 2000;
167
168
  var AI_ASSISTANT_LOCALE = 'en-US';
168
169
  var AI_ASSISTANT_CURRENCY_CODE = 'USD';
170
+ var AI_ASSISTANT_USD_CURRENCY_TEXT_PATTERN = /\b(?:USD|US\$)(?:\s| | |[\u00A0\u202F\u2007])*\$?\s*([-+]?[0-9][0-9,]*(?:\.[0-9]+)?)/gi;
171
+ var AI_ASSISTANT_REVENUE_VERIFY_DIFF_WARN_THRESHOLD = 0.15;
172
+ var AI_ASSISTANT_REVENUE_VERIFY_PAID_GRAND_GAP_WARN_THRESHOLD = 0.10;
169
173
  var AI_ASSISTANT_PROGRESS_TICKS = [
170
174
  'Grabbing Data',
171
175
  'Drafting response'
@@ -378,12 +382,13 @@ var AI_ASSISTANT_REPORT_BUILDER_EXPERT_PLAYBOOK = [
378
382
  '',
379
383
  '2) Resolve the target dataset safely.',
380
384
  '- Map user wording to internal collection names using routes, collection hints, field hints, and synonym expansion.',
381
- '- Prefer report-* collections when permissionView is under /report.',
385
+ '- Prefer report-* collections when permissionView is under /report-builder.',
382
386
  '- Never use *.versions unless user explicitly requests bug-history/version investigation.',
383
387
  '- Never invent collection names or fields.',
384
388
  '',
385
389
  '3) Enforce permissions and scope in the directive.',
386
390
  '- Always include permissionView.',
391
+ '- Use /report-builder as the default permissionView for data directives; avoid /report/* routes.',
387
392
  '- Assume non-super-admin unless explicitly told otherwise.',
388
393
  '- Invoice-like collections require invoice view access.',
389
394
  '- Customer portal users must stay in their own customer scope.',
@@ -432,7 +437,7 @@ var AI_ASSISTANT_SYSTEM_PROMPT = [
432
437
  '- Step 1 (always): determine the target collections/models/modules/workflows using context, routes, and collection hints. Assume the user is non-technical and will not provide internal names.',
433
438
  '- Never use *.versions collections for normal requests. Only use a .versions collection when explicitly investigating a bug by checking the last ~5 updates.',
434
439
  '- Planning stage: regex/keyword scan the codebase for collectionName/model definitions, methods, publications, and Angular routes/modules to map user wording to internal names.',
435
- '- If permissionView starts with /report/, prefer the report-* collection when both report and base collections exist.',
440
+ '- If permissionView starts with /report-builder, prefer the report-* collection when both report and base collections exist.',
436
441
  '- Map user wording to internal collections/fields yourself. Do not ask for property names unless required to run a query.',
437
442
  '- Use term hints from context (synonym expansions) when mapping user language to collections.',
438
443
  '- Do not guess or invent collections/fields. If unsure, verify in the codebase or run a small REPORT_BUILDER_READ probe (limit 1-5) to learn the shape.',
@@ -487,8 +492,10 @@ var AI_ASSISTANT_SYSTEM_PROMPT = [
487
492
  '- REPORT_BUILDER_READ: {"collection":"<name>","query":{...},"options":{"projection":{...},"sort":{...},"limit":20},"permissionView":"</route>"}',
488
493
  '- If you need grouped/aggregated data (totals by user, rankings, trends), end your response with a single line exactly in this format:',
489
494
  '- REPORT_BUILDER_AGG: {"collection":"<name>","pipeline":[...],"options":{"allowDiskUse":true,"limit":20},"permissionView":"</route>"}',
490
- '- For invoice data, set permissionView to an invoice route (ex: /invoice/list or /report/invoice).',
495
+ '- For invoice data, set permissionView to an invoice-capable route (ex: /invoice/list or /report-builder/list).',
496
+ '- Do not use /report/* routes as permissionView for data directives; use /report-builder or a module route.',
491
497
  '- For revenue/sales/billing questions, use invoices and sum paid_total (fallback to grand_total) with date_paid and Paid/Closed status when available.',
498
+ '- For revenue answers, always state the metric/date basis used (paid_total/date_paid vs grand_total/date_invoice). If tool verification warns about ambiguity or partial months, call that out before totals.',
492
499
  '- For relative date ranges (last/past/recent), include an upper bound <= $$NOW unless the user specifies a future end date.',
493
500
  '- Keep queries minimal, read-only, and avoid user/credential data unless the user is a super admin.',
494
501
  '- Assume you are not a super admin unless explicitly told otherwise.',
@@ -528,7 +535,7 @@ var AI_ASSISTANT_PLANNER_SYSTEM_PROMPT = [
528
535
  ' - Never propose querying blocked/sensitive collections if the user lacks permission.',
529
536
  '',
530
537
  '4) PERMISSION MATCHING:',
531
- ' - Do NOT hardcode invoice access to "/report/invoice".',
538
+ ' - Do NOT hardcode invoice access to "/report-builder/*".',
532
539
  ' - For invoice-related data or navigation, permission is satisfied if ANY user view contains "invoice" case-insensitive.',
533
540
  ' (General rule: for an entity token X, permission is satisfied if any view contains X case-insensitive.)',
534
541
  ' - If permission checks are ambiguous, choose the safest restriction and explain.',
@@ -663,7 +670,7 @@ var AI_FORM_PATCH_SYSTEM_PROMPT = [
663
670
  '- Use ISO 8601 for dates and true/false for booleans.',
664
671
  '- Use medium reasoning effort.'
665
672
  ].join('\n');
666
- var assistantCodexClient = null;
673
+ var assistantCodexClientByConfig = new Map();
667
674
  var assistantCodexRunQueue = [];
668
675
  var assistantCodexRunDraining = false;
669
676
  /* eslint-enable no-unused-vars */
@@ -1316,7 +1323,7 @@ function executeAiFormPatch(payload, context) {
1316
1323
  }
1317
1324
  function executeAiAssistantCodexRun(payload, context) {
1318
1325
  return __awaiter(this, void 0, void 0, function () {
1319
- var input, message, aiWorkerDebug, requestId, guardrail, conversation_2, now_2, userMsg, assistantMsg, user, isSuperAdmin, hasInvoiceAccess, customerId, conversation, now, attachments, attachmentData, historyLimit, history, _a, historyLines, userDoc, initialProgress, assistantDoc, insertResult, assistantMessageId;
1326
+ var input, message, aiWorkerDebug, requestId, codexModel, codexFallbackModels, guardrail, conversation_2, now_2, userMsg, assistantMsg, user, isSuperAdmin, hasInvoiceAccess, customerId, conversation, now, attachments, attachmentData, historyLimit, history, _a, historyLines, recentToolError, userDoc, initialProgress, assistantDoc, insertResult, assistantMessageId;
1320
1327
  var _this = this;
1321
1328
  var _b, _c;
1322
1329
  return __generator(this, function (_d) {
@@ -1332,6 +1339,8 @@ function executeAiAssistantCodexRun(payload, context) {
1332
1339
  }
1333
1340
  aiWorkerDebug = isAiWorkerDebugEnabled();
1334
1341
  requestId = normalizeOptionalString(input.request_id);
1342
+ codexModel = resolveCodexModel(input.config);
1343
+ codexFallbackModels = resolveCodexFallbackModels(input.config, codexModel);
1335
1344
  guardrail = evaluateAssistantGuardrails(message);
1336
1345
  if (!(guardrail === null || guardrail === void 0 ? void 0 : guardrail.blocked)) return [3 /*break*/, 5];
1337
1346
  return [4 /*yield*/, ensureConversation(input, 'codex')];
@@ -1411,6 +1420,9 @@ function executeAiAssistantCodexRun(payload, context) {
1411
1420
  historyLines.push("".concat(role, ": ").concat(content));
1412
1421
  }
1413
1422
  });
1423
+ recentToolError = isAssistantWhyFollowupMessage(message)
1424
+ ? resolveRecentAssistantToolError(history)
1425
+ : '';
1414
1426
  userDoc = {
1415
1427
  id_conversation: conversation._id,
1416
1428
  role: 'user',
@@ -1425,7 +1437,7 @@ function executeAiAssistantCodexRun(payload, context) {
1425
1437
  id_conversation: conversation._id,
1426
1438
  role: 'assistant',
1427
1439
  content: AI_ASSISTANT_PROGRESS_PLACEHOLDER,
1428
- metadata: __assign(__assign({ model: resolveCodexModel() }, (requestId ? { request_id: requestId } : {})), { pending: true, progress: initialProgress }),
1440
+ metadata: __assign(__assign(__assign({ model: codexModel }, (codexFallbackModels.length ? { model_fallbacks: codexFallbackModels } : {})), (requestId ? { request_id: requestId } : {})), { pending: true, progress: initialProgress }),
1429
1441
  createdAt: now,
1430
1442
  updatedAt: now
1431
1443
  };
@@ -1560,16 +1572,22 @@ function executeAiAssistantCodexRun(payload, context) {
1560
1572
  customerId: customerId,
1561
1573
  collectionHints: collectionHints,
1562
1574
  termHints: termHints,
1563
- fieldHints: fieldHints
1575
+ fieldHints: fieldHints,
1576
+ recentToolError: recentToolError
1564
1577
  });
1565
1578
  prompt_1 = buildAssistantCodexPrompt(message, attachmentData.promptText, historyLines.join('\n'), assistantContext);
1566
1579
  return [4 /*yield*/, resolveAssistantWorkspaceRoot()];
1567
1580
  case 6:
1568
1581
  workspaceRoot = _x.sent();
1569
- codexConfig = resolveCodexSettings();
1582
+ codexConfig = resolveCodexSettings({
1583
+ model: codexModel,
1584
+ fallbackModels: codexFallbackModels
1585
+ });
1570
1586
  runOptions = {
1571
1587
  timeoutMs: resolveCodexTimeoutMs(),
1588
+ fallbackModels: codexFallbackModels,
1572
1589
  threadOptions: {
1590
+ model: codexModel,
1573
1591
  workingDirectory: workspaceRoot,
1574
1592
  sandboxMode: 'read-only',
1575
1593
  skipGitRepoCheck: true,
@@ -1799,6 +1817,8 @@ function executeAiAssistantCodexRun(payload, context) {
1799
1817
  if (!assistantContent) {
1800
1818
  assistantContent = buildAssistantCodexErrorMessage(null);
1801
1819
  }
1820
+ assistantContent = applyAssistantVerificationNotes(assistantContent, toolResult);
1821
+ assistantContent = normalizeAssistantCurrencyText(assistantContent);
1802
1822
  if (aiWorkerDebug) {
1803
1823
  finishedAt = Date.now();
1804
1824
  console.log(new Date(), '[AI Worker Debug] codex run complete', {
@@ -1868,7 +1888,7 @@ function executeAiAssistantCodexRun(payload, context) {
1868
1888
  }
1869
1889
  });
1870
1890
  }
1871
- finalMetadata = __assign(__assign(__assign({ model: resolveCodexModel() }, (requestId ? { request_id: requestId } : {})), (toolResult ? { tool_result: toolResult } : {})), (assistantDebug ? { debug: assistantDebug } : {}));
1891
+ finalMetadata = __assign(__assign(__assign(__assign({ model: codexModel }, (codexFallbackModels.length ? { model_fallbacks: codexFallbackModels } : {})), (requestId ? { request_id: requestId } : {})), (toolResult ? { tool_result: toolResult } : {})), (assistantDebug ? { debug: assistantDebug } : {}));
1872
1892
  finalAssistantDoc = __assign(__assign({}, assistantDoc), { _id: assistantMessageId, content: assistantContent, metadata: finalMetadata, updatedAt: finalNow });
1873
1893
  if (!assistantMessageId) return [3 /*break*/, 39];
1874
1894
  return [4 /*yield*/, ai_terminal_message_collection_1.AiTerminalMessages.updateOne({ _id: assistantMessageId }, {
@@ -2398,7 +2418,7 @@ function executeAiAssistantMongoRead(payload, context) {
2398
2418
  }
2399
2419
  function executeAiAssistantMongoAggregate(payload, context) {
2400
2420
  return __awaiter(this, void 0, void 0, function () {
2401
- var input, rawCollection, dbName, db, collectionResolution, collection, bridgeCollection, schemaFields, _a, user, isSuperAdmin, customerId, fallbackMeta, baseQuery, stripped, userId, normalizedClient, shouldScopeByClient, _b, clientScopedQuery, scopedQuery, normalizedPipeline, sanitizedPipeline, strippedPipeline, pipelineWithScope, normalizedOptions, limitedPipeline, dateField, aggregateOptions, documents, executedPipeline, probeDocs, fallback, fallbackPipeline, fallbackDocs, createdFallback, createdPipeline, createdDocs, expanded, expandedDocs, completionFallback, fallbackPipeline, fallbackDocs, completionExprFallback, fallbackPipeline, fallbackDocs, unwindFallback, shouldUnwind, _c, _d, fallbackPipeline, fallbackDocs, nameFallback, fallbackPipeline, fallbackDocs, _e, _loop_1, i, state_1, matchFields_1, _f, aliases, rewrittenPipeline, fallbackDocs, _loop_2, i, state_2, baseCollection, fallbackPayload, fallbackResult, existingFallbacks, matchStages, diagnostics, combinedMatch, nameFields, dateFields, queryNoName, _g, queryNoDate, _h, _j, _k, _l, allCollections, base, alt, altCount, _m, sanitizedDocuments, includeIds, displayDocs, idLookupDisplay, display;
2421
+ var input, rawCollection, dbName, db, collectionResolution, collection, bridgeCollection, schemaFields, _a, user, isSuperAdmin, customerId, fallbackMeta, baseQuery, stripped, userId, normalizedClient, shouldScopeByClient, _b, clientScopedQuery, scopedQuery, normalizedPipeline, sanitizedPipeline, strippedPipeline, pipelineWithScope, normalizedOptions, limitedPipeline, dateField, aggregateOptions, documents, executedPipeline, probeDocs, fallback, fallbackPipeline, fallbackDocs, createdFallback, createdPipeline, createdDocs, expanded, expandedDocs, completionFallback, fallbackPipeline, fallbackDocs, completionExprFallback, fallbackPipeline, fallbackDocs, unwindFallback, shouldUnwind, _c, _d, fallbackPipeline, fallbackDocs, nameFallback, fallbackPipeline, fallbackDocs, _e, _loop_1, i, state_1, matchFields_1, _f, aliases, rewrittenPipeline, fallbackDocs, _loop_2, i, state_2, baseCollection, fallbackPayload, fallbackResult, existingFallbacks, matchStages, diagnostics, combinedMatch, nameFields, dateFields, queryNoName, _g, queryNoDate, _h, _j, _k, _l, allCollections, base, alt, altCount, _m, verification, sanitizedDocuments, includeIds, displayDocs, idLookupDisplay, display;
2402
2422
  var _o, _p;
2403
2423
  return __generator(this, function (_q) {
2404
2424
  switch (_q.label) {
@@ -2975,7 +2995,15 @@ function executeAiAssistantMongoAggregate(payload, context) {
2975
2995
  case 60:
2976
2996
  fallbackMeta.zeroDiagnostics = diagnostics;
2977
2997
  _q.label = 61;
2978
- case 61:
2998
+ case 61: return [4 /*yield*/, verifyAssistantAggregateReliability({
2999
+ db: db,
3000
+ collection: collection,
3001
+ pipeline: executedPipeline,
3002
+ documents: documents,
3003
+ aggregateOptions: aggregateOptions
3004
+ })];
3005
+ case 62:
3006
+ verification = _q.sent();
2979
3007
  sanitizedDocuments = isSuperAdmin
2980
3008
  ? documents
2981
3009
  : documents.map(function (doc) { return redactSensitiveFields((0, common_1.deepCopy)(doc)); });
@@ -2990,7 +3018,7 @@ function executeAiAssistantMongoAggregate(payload, context) {
2990
3018
  idCustomer: customerId,
2991
3019
  isSuperAdmin: isSuperAdmin
2992
3020
  })];
2993
- case 62:
3021
+ case 63:
2994
3022
  idLookupDisplay = _q.sent();
2995
3023
  if (idLookupDisplay === null || idLookupDisplay === void 0 ? void 0 : idLookupDisplay.docs) {
2996
3024
  displayDocs = idLookupDisplay.docs;
@@ -3004,27 +3032,482 @@ function executeAiAssistantMongoAggregate(payload, context) {
3004
3032
  maxRows: normalizedOptions.limit || sanitizedDocuments.length,
3005
3033
  includeGroupFromId: true
3006
3034
  });
3007
- return [2 /*return*/, __assign({ documents: sanitizedDocuments, display: display }, (isSuperAdmin ? {
3008
- debug: {
3009
- collection: collection,
3010
- collectionRequested: rawCollection,
3011
- collectionResolved: collection,
3012
- collectionMatched: collectionResolution.matched,
3013
- collectionCandidates: collectionResolution.candidates,
3014
- collectionScore: collectionResolution.score,
3015
- bridge: 'report-builder',
3016
- database: dbName,
3017
- query: scopedQuery,
3018
- options: normalizedOptions.aggregateOptions,
3019
- originalPipeline: limitedPipeline,
3020
- executedPipeline: executedPipeline,
3021
- fallbacks: fallbackMeta
3022
- }
3035
+ return [2 /*return*/, __assign(__assign({ documents: sanitizedDocuments, display: display }, (verification ? { verification: verification } : {})), (isSuperAdmin ? {
3036
+ debug: __assign(__assign({ collection: collection, collectionRequested: rawCollection, collectionResolved: collection, collectionMatched: collectionResolution.matched, collectionCandidates: collectionResolution.candidates, collectionScore: collectionResolution.score, bridge: 'report-builder', database: dbName, query: scopedQuery, options: normalizedOptions.aggregateOptions, originalPipeline: limitedPipeline, executedPipeline: executedPipeline }, (verification ? { verification: verification } : {})), { fallbacks: fallbackMeta })
3023
3037
  } : {}))];
3024
3038
  }
3025
3039
  });
3026
3040
  });
3027
3041
  }
3042
+ function verifyAssistantAggregateReliability(params) {
3043
+ return __awaiter(this, void 0, void 0, function () {
3044
+ var collection, collectionBase, monthKey, amountKey, baseMap, groupIndex, groupPaths, dateField, mergedMatch, dateFieldsInMatch, baseMatchNoDate, primaryDateCondition, verifyPipeline, verifyDocs, _a, verifyMap, comparedMonths, paidCloserCount, grandCloserCount, sumPaidDiffPct, sumGrandDiffPct, maxPaidDiffPct, maxGrandDiffPct, sumPaidGrandGapPct, highVarianceMonths, checks, warnings, avgPaidDiffPct, avgGrandDiffPct, avgPaidGrandGapPct, baselineMetric, rollingWindow, normalizedMetric, bounds, fullMonthMatch, fullMonthDocs, fullMonthMap, fullMonthDiff, _b, alternateDateField, alternateDateCondition, bounds, alternateMatch, alternateDocs, alternateMap, alternateDiff, _c;
3045
+ var _d, _e;
3046
+ var _f;
3047
+ return __generator(this, function (_g) {
3048
+ switch (_g.label) {
3049
+ case 0:
3050
+ collection = normalizeOptionalString(params.collection).toLowerCase();
3051
+ collectionBase = collection.startsWith('report-') ? collection.slice('report-'.length) : collection;
3052
+ if (collectionBase !== 'invoices') {
3053
+ return [2 /*return*/, null];
3054
+ }
3055
+ monthKey = resolveAssistantMonthlyKey(params.documents);
3056
+ if (!monthKey) {
3057
+ return [2 /*return*/, null];
3058
+ }
3059
+ amountKey = resolveAssistantNumericAmountKey(params.documents, monthKey);
3060
+ if (!amountKey) {
3061
+ return [2 /*return*/, null];
3062
+ }
3063
+ baseMap = mapAssistantRevenueByMonth(params.documents, monthKey, amountKey);
3064
+ if (!baseMap.size) {
3065
+ return [2 /*return*/, null];
3066
+ }
3067
+ groupIndex = findAggregateGroupIndex(params.pipeline || []);
3068
+ if (groupIndex === -1) {
3069
+ return [2 /*return*/, null];
3070
+ }
3071
+ groupPaths = extractGroupFieldPaths(((_f = params.pipeline[groupIndex]) === null || _f === void 0 ? void 0 : _f.$group) || {});
3072
+ dateField = groupPaths.find(function (field) { return /(date|paid|invoice|created|updated|_at)$/i.test(field); })
3073
+ || groupPaths.find(function (field) { return /(date|paid|invoice|created|updated)/i.test(field); })
3074
+ || 'date_paid';
3075
+ mergedMatch = mergeAssistantPreGroupMatchStages(params.pipeline || [], groupIndex);
3076
+ dateFieldsInMatch = collectMatchFieldsByCondition(mergedMatch, function (_field, condition) { return isDateCondition(condition); });
3077
+ baseMatchNoDate = dateFieldsInMatch.length ? stripMatchFields(mergedMatch, dateFieldsInMatch) : mergedMatch;
3078
+ primaryDateCondition = findMatchConditionForField(mergedMatch, dateField);
3079
+ verifyPipeline = buildAssistantMonthlyRevenueVerificationPipeline(mergedMatch, dateField);
3080
+ verifyDocs = [];
3081
+ _g.label = 1;
3082
+ case 1:
3083
+ _g.trys.push([1, 3, , 4]);
3084
+ return [4 /*yield*/, params.db.collection(params.collection)
3085
+ .aggregate(verifyPipeline, params.aggregateOptions || {})
3086
+ .toArray()];
3087
+ case 2:
3088
+ verifyDocs = _g.sent();
3089
+ return [3 /*break*/, 4];
3090
+ case 3:
3091
+ _a = _g.sent();
3092
+ return [2 /*return*/, {
3093
+ type: 'invoice_revenue_monthly',
3094
+ checks: [
3095
+ { name: 'Cross-check query', status: 'warn', details: 'Verification query failed; result not cross-checked.' }
3096
+ ],
3097
+ warnings: ['Verification query failed, so this total was not independently cross-checked.'],
3098
+ metrics: {
3099
+ baselineField: amountKey,
3100
+ dateField: dateField
3101
+ }
3102
+ }];
3103
+ case 4:
3104
+ verifyMap = mapAssistantVerificationDocsByMonth(verifyDocs);
3105
+ comparedMonths = 0;
3106
+ paidCloserCount = 0;
3107
+ grandCloserCount = 0;
3108
+ sumPaidDiffPct = 0;
3109
+ sumGrandDiffPct = 0;
3110
+ maxPaidDiffPct = 0;
3111
+ maxGrandDiffPct = 0;
3112
+ sumPaidGrandGapPct = 0;
3113
+ highVarianceMonths = [];
3114
+ baseMap.forEach(function (value, month) {
3115
+ var match = verifyMap.get(month);
3116
+ if (!match) {
3117
+ return;
3118
+ }
3119
+ comparedMonths += 1;
3120
+ var paidDiff = Math.abs(value - match.paid);
3121
+ var grandDiff = Math.abs(value - match.grand);
3122
+ var denom = Math.max(Math.abs(value), 1);
3123
+ var paidPct = paidDiff / denom;
3124
+ var grandPct = grandDiff / denom;
3125
+ sumPaidDiffPct += paidPct;
3126
+ sumGrandDiffPct += grandPct;
3127
+ maxPaidDiffPct = Math.max(maxPaidDiffPct, paidPct);
3128
+ maxGrandDiffPct = Math.max(maxGrandDiffPct, grandPct);
3129
+ if (paidDiff < grandDiff) {
3130
+ paidCloserCount += 1;
3131
+ }
3132
+ else if (grandDiff < paidDiff) {
3133
+ grandCloserCount += 1;
3134
+ }
3135
+ var gapPct = Math.abs(match.paid - match.grand) / Math.max(Math.abs(match.grand), 1);
3136
+ sumPaidGrandGapPct += gapPct;
3137
+ if (Math.min(paidPct, grandPct) >= AI_ASSISTANT_REVENUE_VERIFY_DIFF_WARN_THRESHOLD) {
3138
+ highVarianceMonths.push(month);
3139
+ }
3140
+ });
3141
+ checks = [];
3142
+ warnings = [];
3143
+ avgPaidDiffPct = comparedMonths ? (sumPaidDiffPct / comparedMonths) : 0;
3144
+ avgGrandDiffPct = comparedMonths ? (sumGrandDiffPct / comparedMonths) : 0;
3145
+ avgPaidGrandGapPct = comparedMonths ? (sumPaidGrandGapPct / comparedMonths) : 0;
3146
+ baselineMetric = 'unknown';
3147
+ if (comparedMonths) {
3148
+ if (paidCloserCount > grandCloserCount) {
3149
+ baselineMetric = 'paid_total (date_paid)';
3150
+ }
3151
+ else if (grandCloserCount > paidCloserCount) {
3152
+ baselineMetric = 'grand_total (date_paid)';
3153
+ }
3154
+ else {
3155
+ baselineMetric = 'mixed';
3156
+ }
3157
+ }
3158
+ if (!comparedMonths) {
3159
+ checks.push({
3160
+ name: 'Cross-check coverage',
3161
+ status: 'warn',
3162
+ details: 'No overlapping month rows were available for verification.'
3163
+ });
3164
+ warnings.push('Could not cross-check this result against alternate invoice total fields for the same months.');
3165
+ }
3166
+ else {
3167
+ checks.push({
3168
+ name: 'Metric alignment',
3169
+ status: baselineMetric === 'mixed' ? 'warn' : 'pass',
3170
+ details: "Result aligns most with ".concat(baselineMetric, ".")
3171
+ });
3172
+ if (baselineMetric === 'mixed') {
3173
+ warnings.push('Revenue appears to mix definitions across months (paid_total vs grand_total).');
3174
+ }
3175
+ checks.push({
3176
+ name: 'Field variance',
3177
+ status: avgPaidGrandGapPct >= AI_ASSISTANT_REVENUE_VERIFY_PAID_GRAND_GAP_WARN_THRESHOLD ? 'warn' : 'pass',
3178
+ details: "Average paid-vs-grand gap: ".concat((0, common_1.round)(avgPaidGrandGapPct * 100, 2), "%.")
3179
+ });
3180
+ if (avgPaidGrandGapPct >= AI_ASSISTANT_REVENUE_VERIFY_PAID_GRAND_GAP_WARN_THRESHOLD) {
3181
+ warnings.push("paid_total and grand_total differ materially for this period (".concat((0, common_1.round)(avgPaidGrandGapPct * 100, 2), "% average gap)."));
3182
+ }
3183
+ }
3184
+ rollingWindow = detectAssistantRollingMonthWindow(params.pipeline || []);
3185
+ if ((rollingWindow === null || rollingWindow === void 0 ? void 0 : rollingWindow.months) && (rollingWindow === null || rollingWindow === void 0 ? void 0 : rollingWindow.upperNow)) {
3186
+ checks.push({
3187
+ name: 'Date window shape',
3188
+ status: 'warn',
3189
+ details: "Query uses rolling $$NOW bounds (last ".concat(rollingWindow.months, " months), which can include partial months.")
3190
+ });
3191
+ warnings.push("Date window is rolling (last ".concat(rollingWindow.months, " months to now), so first/current months may be partial."));
3192
+ }
3193
+ else {
3194
+ checks.push({
3195
+ name: 'Date window shape',
3196
+ status: 'pass',
3197
+ details: 'No rolling $$NOW month window pattern detected.'
3198
+ });
3199
+ }
3200
+ normalizedMetric = normalizeAssistantVerificationMetric(baselineMetric);
3201
+ if (!((rollingWindow === null || rollingWindow === void 0 ? void 0 : rollingWindow.months) && (rollingWindow === null || rollingWindow === void 0 ? void 0 : rollingWindow.upperNow))) return [3 /*break*/, 8];
3202
+ bounds = resolveAssistantRollingWindowBounds(rollingWindow.months);
3203
+ fullMonthMatch = mergeAssistantMatch(baseMatchNoDate, (_d = {},
3204
+ _d[dateField] = {
3205
+ $gte: bounds.startOfWindow,
3206
+ $lt: bounds.startOfCurrentMonth
3207
+ },
3208
+ _d));
3209
+ _g.label = 5;
3210
+ case 5:
3211
+ _g.trys.push([5, 7, , 8]);
3212
+ return [4 /*yield*/, params.db.collection(params.collection)
3213
+ .aggregate(buildAssistantMonthlyRevenueVerificationPipeline(fullMonthMatch, dateField), params.aggregateOptions || {})
3214
+ .toArray()];
3215
+ case 6:
3216
+ fullMonthDocs = _g.sent();
3217
+ fullMonthMap = mapAssistantMetricByMonth(mapAssistantVerificationDocsByMonth(fullMonthDocs), normalizedMetric);
3218
+ fullMonthDiff = compareAssistantMonthlyValueMaps(baseMap, fullMonthMap);
3219
+ checks.push({
3220
+ name: 'Full-month cross-check',
3221
+ status: fullMonthDiff.comparedMonths && fullMonthDiff.avgDiffPct < AI_ASSISTANT_REVENUE_VERIFY_DIFF_WARN_THRESHOLD ? 'pass' : 'warn',
3222
+ details: fullMonthDiff.comparedMonths
3223
+ ? "Compared ".concat(fullMonthDiff.comparedMonths, " month(s); avg diff ").concat((0, common_1.round)(fullMonthDiff.avgDiffPct * 100, 2), "%.")
3224
+ : 'No overlapping months for full-month cross-check.'
3225
+ });
3226
+ if (fullMonthDiff.comparedMonths && fullMonthDiff.avgDiffPct >= AI_ASSISTANT_REVENUE_VERIFY_DIFF_WARN_THRESHOLD) {
3227
+ warnings.push("Rolling-window totals differ from full-month totals by ".concat((0, common_1.round)(fullMonthDiff.avgDiffPct * 100, 2), "% on average."));
3228
+ }
3229
+ if (!fullMonthDiff.comparedMonths) {
3230
+ warnings.push('Could not compare rolling-window results against full-month buckets.');
3231
+ }
3232
+ return [3 /*break*/, 8];
3233
+ case 7:
3234
+ _b = _g.sent();
3235
+ checks.push({
3236
+ name: 'Full-month cross-check',
3237
+ status: 'warn',
3238
+ details: 'Full-month verification query failed.'
3239
+ });
3240
+ warnings.push('Full-month verification query failed.');
3241
+ return [3 /*break*/, 8];
3242
+ case 8:
3243
+ alternateDateField = dateField === 'date_paid'
3244
+ ? 'date_invoice'
3245
+ : dateField === 'date_invoice'
3246
+ ? 'date_paid'
3247
+ : '';
3248
+ if (!alternateDateField) return [3 /*break*/, 12];
3249
+ alternateDateCondition = null;
3250
+ if ((rollingWindow === null || rollingWindow === void 0 ? void 0 : rollingWindow.months) && (rollingWindow === null || rollingWindow === void 0 ? void 0 : rollingWindow.upperNow)) {
3251
+ bounds = resolveAssistantRollingWindowBounds(rollingWindow.months);
3252
+ alternateDateCondition = {
3253
+ $gte: bounds.rollingStart,
3254
+ $lte: bounds.now
3255
+ };
3256
+ }
3257
+ else if (primaryDateCondition && typeof primaryDateCondition === 'object') {
3258
+ alternateDateCondition = primaryDateCondition;
3259
+ }
3260
+ if (!alternateDateCondition) return [3 /*break*/, 12];
3261
+ alternateMatch = mergeAssistantMatch(baseMatchNoDate, (_e = {}, _e[alternateDateField] = alternateDateCondition, _e));
3262
+ _g.label = 9;
3263
+ case 9:
3264
+ _g.trys.push([9, 11, , 12]);
3265
+ return [4 /*yield*/, params.db.collection(params.collection)
3266
+ .aggregate(buildAssistantMonthlyRevenueVerificationPipeline(alternateMatch, alternateDateField), params.aggregateOptions || {})
3267
+ .toArray()];
3268
+ case 10:
3269
+ alternateDocs = _g.sent();
3270
+ alternateMap = mapAssistantMetricByMonth(mapAssistantVerificationDocsByMonth(alternateDocs), normalizedMetric);
3271
+ alternateDiff = compareAssistantMonthlyValueMaps(baseMap, alternateMap);
3272
+ checks.push({
3273
+ name: "Alternate date field (".concat(alternateDateField, ")"),
3274
+ status: alternateDiff.comparedMonths && alternateDiff.avgDiffPct < AI_ASSISTANT_REVENUE_VERIFY_DIFF_WARN_THRESHOLD ? 'pass' : 'warn',
3275
+ details: alternateDiff.comparedMonths
3276
+ ? "Compared ".concat(alternateDiff.comparedMonths, " month(s); avg diff ").concat((0, common_1.round)(alternateDiff.avgDiffPct * 100, 2), "%.")
3277
+ : 'No overlapping months for alternate date-field cross-check.'
3278
+ });
3279
+ if (alternateDiff.comparedMonths && alternateDiff.avgDiffPct >= AI_ASSISTANT_REVENUE_VERIFY_DIFF_WARN_THRESHOLD) {
3280
+ warnings.push("Switching from ".concat(dateField, " to ").concat(alternateDateField, " changes monthly totals by ").concat((0, common_1.round)(alternateDiff.avgDiffPct * 100, 2), "% on average."));
3281
+ }
3282
+ return [3 /*break*/, 12];
3283
+ case 11:
3284
+ _c = _g.sent();
3285
+ checks.push({
3286
+ name: "Alternate date field (".concat(alternateDateField, ")"),
3287
+ status: 'warn',
3288
+ details: 'Alternate date-field verification query failed.'
3289
+ });
3290
+ return [3 /*break*/, 12];
3291
+ case 12:
3292
+ if (highVarianceMonths.length) {
3293
+ warnings.push("High variance months vs both paid/grand checks: ".concat(highVarianceMonths.join(', '), "."));
3294
+ }
3295
+ return [2 /*return*/, {
3296
+ type: 'invoice_revenue_monthly',
3297
+ checks: checks,
3298
+ warnings: warnings,
3299
+ metrics: {
3300
+ baselineField: amountKey,
3301
+ dateField: dateField,
3302
+ comparedMonths: comparedMonths,
3303
+ avgDiffToPaidPct: (0, common_1.round)(avgPaidDiffPct * 100, 2),
3304
+ avgDiffToGrandPct: (0, common_1.round)(avgGrandDiffPct * 100, 2),
3305
+ maxDiffToPaidPct: (0, common_1.round)(maxPaidDiffPct * 100, 2),
3306
+ maxDiffToGrandPct: (0, common_1.round)(maxGrandDiffPct * 100, 2),
3307
+ avgPaidGrandGapPct: (0, common_1.round)(avgPaidGrandGapPct * 100, 2)
3308
+ }
3309
+ }];
3310
+ }
3311
+ });
3312
+ });
3313
+ }
3314
+ function resolveAssistantMonthlyKey(documents) {
3315
+ var first = Array.isArray(documents) ? documents.find(function (doc) { return doc && typeof doc === 'object'; }) : null;
3316
+ if (!first) {
3317
+ return '';
3318
+ }
3319
+ return Object.keys(first).find(function (key) {
3320
+ var value = first[key];
3321
+ return typeof value === 'string' && /^\d{4}-\d{2}$/.test(value);
3322
+ }) || '';
3323
+ }
3324
+ function resolveAssistantNumericAmountKey(documents, monthKey) {
3325
+ var first = Array.isArray(documents) ? documents.find(function (doc) { return doc && typeof doc === 'object'; }) : null;
3326
+ if (!first) {
3327
+ return '';
3328
+ }
3329
+ var currencyLike = Object.keys(first).filter(function (key) {
3330
+ if (key === monthKey) {
3331
+ return false;
3332
+ }
3333
+ var value = Number(first[key]);
3334
+ if (!Number.isFinite(value)) {
3335
+ return false;
3336
+ }
3337
+ return /(revenue|amount|total|sales|billing|paid|grand)/i.test(key);
3338
+ });
3339
+ if (currencyLike.length) {
3340
+ return currencyLike[0];
3341
+ }
3342
+ return Object.keys(first).find(function (key) {
3343
+ if (key === monthKey) {
3344
+ return false;
3345
+ }
3346
+ return Number.isFinite(Number(first[key]));
3347
+ }) || '';
3348
+ }
3349
+ function mapAssistantRevenueByMonth(documents, monthKey, valueKey) {
3350
+ var mapped = new Map();
3351
+ (Array.isArray(documents) ? documents : []).forEach(function (doc) {
3352
+ var month = normalizeOptionalString(doc === null || doc === void 0 ? void 0 : doc[monthKey]);
3353
+ if (!month || !/^\d{4}-\d{2}$/.test(month)) {
3354
+ return;
3355
+ }
3356
+ var value = Number(doc === null || doc === void 0 ? void 0 : doc[valueKey]);
3357
+ if (!Number.isFinite(value)) {
3358
+ return;
3359
+ }
3360
+ mapped.set(month, value);
3361
+ });
3362
+ return mapped;
3363
+ }
3364
+ function mergeAssistantPreGroupMatchStages(pipeline, groupIndex) {
3365
+ var matches = [];
3366
+ for (var i = 0; i < groupIndex; i += 1) {
3367
+ var stage = pipeline[i];
3368
+ if (stage && typeof stage === 'object' && stage.$match && typeof stage.$match === 'object') {
3369
+ matches.push(stage.$match);
3370
+ }
3371
+ }
3372
+ if (!matches.length) {
3373
+ return {};
3374
+ }
3375
+ if (matches.length === 1) {
3376
+ return matches[0];
3377
+ }
3378
+ return { $and: matches };
3379
+ }
3380
+ function detectAssistantRollingMonthWindow(pipeline) {
3381
+ var raw = JSON.stringify(Array.isArray(pipeline) ? pipeline : []);
3382
+ if (!raw) {
3383
+ return null;
3384
+ }
3385
+ var amountMatch = raw.match(/"\$dateSubtract":\{"startDate":"\$\$NOW","unit":"month","amount":([0-9]+)\}/);
3386
+ if (!amountMatch) {
3387
+ return null;
3388
+ }
3389
+ var months = Number(amountMatch[1]);
3390
+ if (!Number.isFinite(months) || months < 1) {
3391
+ return null;
3392
+ }
3393
+ var nowCount = (raw.match(/\$\$NOW/g) || []).length;
3394
+ return {
3395
+ months: months,
3396
+ upperNow: nowCount >= 2
3397
+ };
3398
+ }
3399
+ function buildAssistantMonthlyRevenueVerificationPipeline(match, dateField) {
3400
+ var pipeline = [];
3401
+ if (match && typeof match === 'object' && Object.keys(match).length) {
3402
+ pipeline.push({ $match: match });
3403
+ }
3404
+ pipeline.push({
3405
+ $group: {
3406
+ _id: {
3407
+ $dateToString: {
3408
+ format: '%Y-%m',
3409
+ date: "$".concat(dateField)
3410
+ }
3411
+ },
3412
+ paid_total_sum: { $sum: { $ifNull: ['$paid_total', 0] } },
3413
+ grand_total_sum: { $sum: { $ifNull: ['$grand_total', 0] } },
3414
+ invoice_count: { $sum: 1 }
3415
+ }
3416
+ }, {
3417
+ $project: {
3418
+ _id: 0,
3419
+ month: '$_id',
3420
+ paid_total_sum: 1,
3421
+ grand_total_sum: 1,
3422
+ invoice_count: 1
3423
+ }
3424
+ }, { $sort: { month: 1 } }, { $limit: 36 });
3425
+ return pipeline;
3426
+ }
3427
+ function mapAssistantVerificationDocsByMonth(docs) {
3428
+ var mapped = new Map();
3429
+ (Array.isArray(docs) ? docs : []).forEach(function (row) {
3430
+ var month = normalizeOptionalString(row === null || row === void 0 ? void 0 : row.month);
3431
+ if (!month || !/^\d{4}-\d{2}$/.test(month)) {
3432
+ return;
3433
+ }
3434
+ var paid = Number(row === null || row === void 0 ? void 0 : row.paid_total_sum);
3435
+ var grand = Number(row === null || row === void 0 ? void 0 : row.grand_total_sum);
3436
+ var count = Number(row === null || row === void 0 ? void 0 : row.invoice_count);
3437
+ mapped.set(month, {
3438
+ paid: Number.isFinite(paid) ? paid : 0,
3439
+ grand: Number.isFinite(grand) ? grand : 0,
3440
+ count: Number.isFinite(count) ? count : 0
3441
+ });
3442
+ });
3443
+ return mapped;
3444
+ }
3445
+ function normalizeAssistantVerificationMetric(value) {
3446
+ var normalized = normalizeOptionalString(value).toLowerCase();
3447
+ if (normalized.startsWith('paid_total')) {
3448
+ return 'paid';
3449
+ }
3450
+ if (normalized.startsWith('grand_total')) {
3451
+ return 'grand';
3452
+ }
3453
+ return 'mixed';
3454
+ }
3455
+ function mapAssistantMetricByMonth(source, metric) {
3456
+ var mapped = new Map();
3457
+ source.forEach(function (value, month) {
3458
+ var next = metric === 'paid'
3459
+ ? value.paid
3460
+ : metric === 'grand'
3461
+ ? value.grand
3462
+ : value.paid || value.grand;
3463
+ mapped.set(month, Number.isFinite(next) ? next : 0);
3464
+ });
3465
+ return mapped;
3466
+ }
3467
+ function compareAssistantMonthlyValueMaps(base, compare) {
3468
+ var comparedMonths = 0;
3469
+ var sumDiffPct = 0;
3470
+ var maxDiffPct = 0;
3471
+ base.forEach(function (baseValue, month) {
3472
+ var compareValue = compare.get(month);
3473
+ if (typeof compareValue !== 'number') {
3474
+ return;
3475
+ }
3476
+ var diffPct = Math.abs(baseValue - compareValue) / Math.max(Math.abs(baseValue), 1);
3477
+ comparedMonths += 1;
3478
+ sumDiffPct += diffPct;
3479
+ maxDiffPct = Math.max(maxDiffPct, diffPct);
3480
+ });
3481
+ return {
3482
+ comparedMonths: comparedMonths,
3483
+ avgDiffPct: comparedMonths ? (sumDiffPct / comparedMonths) : 0,
3484
+ maxDiffPct: maxDiffPct
3485
+ };
3486
+ }
3487
+ function mergeAssistantMatch(base, extra) {
3488
+ var baseObj = base && typeof base === 'object' ? base : {};
3489
+ var extraObj = extra && typeof extra === 'object' ? extra : {};
3490
+ if (!Object.keys(baseObj).length) {
3491
+ return extraObj;
3492
+ }
3493
+ if (!Object.keys(extraObj).length) {
3494
+ return baseObj;
3495
+ }
3496
+ return { $and: [baseObj, extraObj] };
3497
+ }
3498
+ function resolveAssistantRollingWindowBounds(months) {
3499
+ var now = new Date();
3500
+ var rollingStart = new Date(now);
3501
+ rollingStart.setUTCMonth(rollingStart.getUTCMonth() - months);
3502
+ var startOfCurrentMonth = new Date(Date.UTC(now.getUTCFullYear(), now.getUTCMonth(), 1));
3503
+ var startOfWindow = new Date(Date.UTC(startOfCurrentMonth.getUTCFullYear(), startOfCurrentMonth.getUTCMonth() - months, 1));
3504
+ return {
3505
+ now: now,
3506
+ rollingStart: rollingStart,
3507
+ startOfCurrentMonth: startOfCurrentMonth,
3508
+ startOfWindow: startOfWindow
3509
+ };
3510
+ }
3028
3511
  function extractAssistantMongoDirective(content) {
3029
3512
  var lines = String(content || '').split('\n');
3030
3513
  var directiveIndex = -1;
@@ -3079,13 +3562,12 @@ function extractAssistantMongoDirective(content) {
3079
3562
  };
3080
3563
  }
3081
3564
  function buildAssistantToolRequest(directive, payload) {
3082
- var _a;
3083
3565
  var base = directive.payload && typeof directive.payload === 'object' ? directive.payload : {};
3084
3566
  var request = __assign({}, base);
3085
- var route = normalizeOptionalString((_a = payload === null || payload === void 0 ? void 0 : payload.context) === null || _a === void 0 ? void 0 : _a.route);
3086
- if (!request.permissionView && route) {
3087
- request.permissionView = route;
3567
+ if (!request.permissionView) {
3568
+ request.permissionView = '/report-builder';
3088
3569
  }
3570
+ request.permissionView = normalizeAssistantPermissionView(request.permissionView, request.collection);
3089
3571
  if (!request.id_client) {
3090
3572
  var idClient = normalizeOptionalString(payload === null || payload === void 0 ? void 0 : payload.id_client);
3091
3573
  if (idClient) {
@@ -3097,6 +3579,24 @@ function buildAssistantToolRequest(directive, payload) {
3097
3579
  }
3098
3580
  return request;
3099
3581
  }
3582
+ function normalizeAssistantPermissionView(permissionView, collection) {
3583
+ var normalizedPermission = normalizeOptionalString(permissionView);
3584
+ var normalizedCollection = normalizeOptionalString(collection);
3585
+ var loweredPermission = normalizedPermission.toLowerCase();
3586
+ if (!normalizedPermission) {
3587
+ return '/report-builder';
3588
+ }
3589
+ if (loweredPermission === '/report-builder' || loweredPermission.startsWith('/report-builder/')) {
3590
+ return '/report-builder';
3591
+ }
3592
+ if (loweredPermission === '/report' || loweredPermission.startsWith('/report/')) {
3593
+ if (requiresInvoicePermission(normalizedCollection)) {
3594
+ return '/invoice/list';
3595
+ }
3596
+ return '/report-builder';
3597
+ }
3598
+ return normalizedPermission;
3599
+ }
3100
3600
  function buildAssistantToolResultPayload(directive, toolResponse) {
3101
3601
  var _a, _b, _c;
3102
3602
  var directivePayload = directive.payload || {};
@@ -3121,6 +3621,9 @@ function buildAssistantToolResultPayload(directive, toolResponse) {
3121
3621
  || normalizeOptionalString((_c = toolResponse === null || toolResponse === void 0 ? void 0 : toolResponse.debug) === null || _c === void 0 ? void 0 : _c.collection)
3122
3622
  || requestedCollection
3123
3623
  || '';
3624
+ var verification = (toolResponse === null || toolResponse === void 0 ? void 0 : toolResponse.verification) && typeof toolResponse.verification === 'object'
3625
+ ? toolResponse.verification
3626
+ : undefined;
3124
3627
  var result = {
3125
3628
  type: directive.type === 'aggregate' ? 'mongo_agg' : 'mongo_read',
3126
3629
  input: directivePayload,
@@ -3131,6 +3634,7 @@ function buildAssistantToolResultPayload(directive, toolResponse) {
3131
3634
  rowCount: rowCount,
3132
3635
  columns: trimmedDisplay.columns,
3133
3636
  truncated: trimmedDisplay.truncated,
3637
+ verification: verification,
3134
3638
  debug: (toolResponse === null || toolResponse === void 0 ? void 0 : toolResponse.debug) && typeof toolResponse.debug === 'object' ? toolResponse.debug : undefined
3135
3639
  }
3136
3640
  };
@@ -3160,6 +3664,26 @@ function buildAssistantToolResultPrompt(result) {
3160
3664
  else {
3161
3665
  lines.push('Preview: (no rows)');
3162
3666
  }
3667
+ var verification = result.output.verification;
3668
+ if (verification && typeof verification === 'object') {
3669
+ var checks = Array.isArray(verification.checks) ? verification.checks : [];
3670
+ var warnings = Array.isArray(verification.warnings) ? verification.warnings : [];
3671
+ if (checks.length || warnings.length) {
3672
+ lines.push('Verification:');
3673
+ }
3674
+ checks.slice(0, 4).forEach(function (entry) {
3675
+ var name = normalizeOptionalString(entry === null || entry === void 0 ? void 0 : entry.name) || 'check';
3676
+ var status = normalizeOptionalString(entry === null || entry === void 0 ? void 0 : entry.status).toLowerCase() === 'warn' ? 'warn' : 'pass';
3677
+ var details = normalizeOptionalString(entry === null || entry === void 0 ? void 0 : entry.details);
3678
+ lines.push("- ".concat(name, ": ").concat(status).concat(details ? " (".concat(details, ")") : ''));
3679
+ });
3680
+ warnings.slice(0, 4).forEach(function (warning) {
3681
+ var text = normalizeOptionalString(warning);
3682
+ if (text) {
3683
+ lines.push("- warning: ".concat(text));
3684
+ }
3685
+ });
3686
+ }
3163
3687
  return lines.join('\n');
3164
3688
  }
3165
3689
  function buildAssistantCodexToolFollowupPrompt(message, attachmentText, historyText, contextText, toolResultText) {
@@ -3168,7 +3692,7 @@ function buildAssistantCodexToolFollowupPrompt(message, attachmentText, historyT
3168
3692
  var trimmedHistory = normalizeOptionalString(historyText);
3169
3693
  var historyBlock = trimmedHistory ? "\n\nConversation so far:\n".concat(trimmedHistory) : '';
3170
3694
  var toolBlock = toolResultText ? "\n\nTool Result:\n".concat(toolResultText) : '';
3171
- var instruction = '\n\nInstruction:\nNow answer the user. Do NOT output another REPORT_BUILDER_* directive. Output plain Markdown. Summarize first, then include a Markdown table.';
3695
+ var instruction = '\n\nInstruction:\nNow answer the user. Do NOT output another REPORT_BUILDER_* directive. Output plain Markdown. Summarize first, then include a Markdown table. If the Tool Result includes Verification warnings, explicitly include them and call out the metric/date basis used.';
3172
3696
  return "System:\n".concat(AI_ASSISTANT_SYSTEM_PROMPT).concat(contextBlock).concat(historyBlock, "\n\nUser:\n").concat(message).concat(attachmentText || '').concat(toolBlock).concat(instruction).trim();
3173
3697
  }
3174
3698
  function buildAssistantToolFallbackResponse(result) {
@@ -3187,6 +3711,27 @@ function buildAssistantToolFallbackResponse(result) {
3187
3711
  }
3188
3712
  return lines.join('\n').trim();
3189
3713
  }
3714
+ function applyAssistantVerificationNotes(value, toolResult) {
3715
+ var _a, _b, _c, _d;
3716
+ var content = normalizeOptionalString(value);
3717
+ var warnings = Array.isArray((_b = (_a = toolResult === null || toolResult === void 0 ? void 0 : toolResult.output) === null || _a === void 0 ? void 0 : _a.verification) === null || _b === void 0 ? void 0 : _b.warnings)
3718
+ ? (_d = (_c = toolResult === null || toolResult === void 0 ? void 0 : toolResult.output) === null || _c === void 0 ? void 0 : _c.verification) === null || _d === void 0 ? void 0 : _d.warnings.map(function (entry) { return normalizeOptionalString(entry); }).filter(Boolean)
3719
+ : [];
3720
+ if (!warnings.length) {
3721
+ return content || value || '';
3722
+ }
3723
+ var normalizedLower = String(content || '').toLowerCase();
3724
+ var missing = warnings.filter(function (warning) { return !normalizedLower.includes(String(warning).toLowerCase()); });
3725
+ if (!missing.length) {
3726
+ return content || value || '';
3727
+ }
3728
+ var noteLines = ['Verification notes:'];
3729
+ missing.slice(0, 4).forEach(function (warning) { return noteLines.push("- ".concat(warning)); });
3730
+ if (!content) {
3731
+ return noteLines.join('\n');
3732
+ }
3733
+ return "".concat(content, "\n\n").concat(noteLines.join('\n')).trim();
3734
+ }
3190
3735
  function buildAssistantDebugPayload(params) {
3191
3736
  var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m, _o, _p, _q, _r, _s, _t, _u, _v, _w, _x, _y, _z, _0, _1, _2, _3, _4;
3192
3737
  var notes = [];
@@ -3363,7 +3908,7 @@ function buildAssistantToolErrorMessage(error, directive, request) {
3363
3908
  ? "Open ".concat(routeHint, " in the app to view this data or request access.")
3364
3909
  : 'Open the related screen in the app to view this data or request access.';
3365
3910
  if (!routeHint && collection && requiresInvoicePermission(collection)) {
3366
- routeLine = 'Open /invoice/list or /report/invoice to view this data or request access.';
3911
+ routeLine = 'Open /invoice/list or /report-builder/list to view this data or request access.';
3367
3912
  }
3368
3913
  if (normalized.includes('permission scope required')) {
3369
3914
  return "I need a permission scope to access that data. ".concat(routeLine);
@@ -3377,6 +3922,9 @@ function buildAssistantToolErrorMessage(error, directive, request) {
3377
3922
  if (normalized.includes('database access denied')) {
3378
3923
  return "Database access is restricted for that request. ".concat(routeLine);
3379
3924
  }
3925
+ if (normalized.includes('undefined variable') && normalized.includes('now_minus')) {
3926
+ return "The query used an unsupported relative date token. Please retry; the assistant now normalizes relative dates automatically. ".concat(routeLine);
3927
+ }
3380
3928
  if (normalized.includes('report builder bridge') && normalized.includes('not configured')) {
3381
3929
  return "That dataset is not configured for report builder access yet. ".concat(routeLine);
3382
3930
  }
@@ -4876,6 +5424,7 @@ function getAssistantCurrencyFormatter() {
4876
5424
  assistantCurrencyFormatter = new Intl.NumberFormat(AI_ASSISTANT_LOCALE, {
4877
5425
  style: 'currency',
4878
5426
  currency: AI_ASSISTANT_CURRENCY_CODE,
5427
+ currencyDisplay: 'narrowSymbol',
4879
5428
  minimumFractionDigits: 2,
4880
5429
  maximumFractionDigits: 2
4881
5430
  });
@@ -4963,6 +5512,12 @@ function formatAssistantDisplayCell(value, column) {
4963
5512
  if (Array.isArray(value)) {
4964
5513
  return value.map(function (item) { return formatAssistantDisplayCell(item, column); }).filter(Boolean).join(', ');
4965
5514
  }
5515
+ if (typeof value === 'string') {
5516
+ var normalizedCurrency = normalizeAssistantCurrencyText(value);
5517
+ if (normalizedCurrency !== value) {
5518
+ return normalizedCurrency;
5519
+ }
5520
+ }
4966
5521
  var columnKey = String(column || '').toLowerCase();
4967
5522
  if (isAssistantDateColumn(columnKey) && isAssistantLikelyDateValue(value)) {
4968
5523
  return formatAssistantDateValue(value);
@@ -4998,6 +5553,13 @@ function formatAssistantDisplayCell(value, column) {
4998
5553
  }
4999
5554
  return String(value);
5000
5555
  }
5556
+ function normalizeAssistantCurrencyText(value) {
5557
+ var raw = String(value || '');
5558
+ if (!raw) {
5559
+ return '';
5560
+ }
5561
+ return raw.replace(AI_ASSISTANT_USD_CURRENCY_TEXT_PATTERN, function (_match, amount) { return "$".concat(amount); });
5562
+ }
5001
5563
  function formatDisplayTableMarkdown(display) {
5002
5564
  if (!display || !Array.isArray(display.columns) || !display.columns.length) {
5003
5565
  return '';
@@ -5019,7 +5581,7 @@ function stripAssistantMarkdownTables(value) {
5019
5581
  if (!raw) {
5020
5582
  return '';
5021
5583
  }
5022
- var tablePattern = /(^|\n)\|[^\n]*\|\n\|[ \t:-|]+\|\n(?:\|[^\n]*\|\n?)*/g;
5584
+ var tablePattern = /(^|\n)\s*\|[^\n]*\|\s*\n\s*\|[ \t:-|]+\|\s*\n(?:\s*\|[^\n]*\|\s*\n?)*/g;
5023
5585
  var cleaned = raw.replace(tablePattern, '\n').trim();
5024
5586
  return cleaned;
5025
5587
  }
@@ -5067,7 +5629,7 @@ function isDisplayObjectLike(value) {
5067
5629
  }
5068
5630
  function ensureAssistantReadAccess(context, permissionView, collection) {
5069
5631
  return __awaiter(this, void 0, void 0, function () {
5070
- var idUser, user, isSuperAdmin, normalizedPermission, normalizedCollection;
5632
+ var idUser, user, isSuperAdmin, normalizedCollection, normalizedPermission, requiresInvoiceAccess, hasInvoiceAccess, hasViewAccess;
5071
5633
  var _a;
5072
5634
  return __generator(this, function (_b) {
5073
5635
  switch (_b.label) {
@@ -5083,20 +5645,21 @@ function ensureAssistantReadAccess(context, permissionView, collection) {
5083
5645
  throw new Error('AI assistant report builder bridge: Unauthorized.');
5084
5646
  }
5085
5647
  isSuperAdmin = !!((_a = user === null || user === void 0 ? void 0 : user.roles) === null || _a === void 0 ? void 0 : _a.super_admin);
5086
- normalizedPermission = normalizeOptionalString(permissionView);
5087
- if (!isSuperAdmin) {
5088
- if (!normalizedPermission) {
5089
- throw new Error('AI assistant report builder bridge: Permission scope required.');
5090
- }
5091
- if (!userHasViewPermission(user, normalizedPermission)) {
5092
- throw new Error('AI assistant report builder bridge: Access denied.');
5093
- }
5094
- normalizedCollection = normalizeOptionalString(collection);
5095
- if (normalizedCollection && requiresInvoicePermission(normalizedCollection) && !userHasInvoiceAccess(user)) {
5096
- throw new Error('AI assistant report builder bridge: Access denied.');
5097
- }
5648
+ if (isSuperAdmin) {
5649
+ return [2 /*return*/, { user: user, isSuperAdmin: isSuperAdmin }];
5650
+ }
5651
+ normalizedCollection = normalizeOptionalString(collection);
5652
+ normalizedPermission = normalizeAssistantPermissionView(permissionView, normalizedCollection);
5653
+ if (!normalizedPermission) {
5654
+ throw new Error('AI assistant report builder bridge: Permission scope required.');
5655
+ }
5656
+ requiresInvoiceAccess = normalizedCollection ? requiresInvoicePermission(normalizedCollection) : false;
5657
+ hasInvoiceAccess = requiresInvoiceAccess && userHasInvoiceAccess(user);
5658
+ hasViewAccess = userHasViewPermission(user, normalizedPermission);
5659
+ if (!hasViewAccess && !hasInvoiceAccess) {
5660
+ throw new Error('AI assistant report builder bridge: Access denied.');
5098
5661
  }
5099
- else if (normalizedPermission && !userHasViewPermission(user, normalizedPermission)) {
5662
+ if (requiresInvoiceAccess && !hasInvoiceAccess) {
5100
5663
  throw new Error('AI assistant report builder bridge: Access denied.');
5101
5664
  }
5102
5665
  return [2 /*return*/, { user: user, isSuperAdmin: isSuperAdmin }];
@@ -5172,6 +5735,90 @@ function sanitizeAssistantProjection(projection) {
5172
5735
  return projection;
5173
5736
  }
5174
5737
  var AGG_MATCH_EXPR_OPERATORS = new Set(['$eq', '$ne', '$gt', '$gte', '$lt', '$lte']);
5738
+ var ASSISTANT_NOW_RELATIVE_PATTERN = /^\$\$NOW_(MINUS|PLUS)_([0-9]+)_(MINUTES?|HOURS?|DAYS?|WEEKS?|MONTHS?|YEARS?)$/i;
5739
+ function normalizeAssistantTimeUnit(raw) {
5740
+ var normalized = String(raw || '').toLowerCase();
5741
+ if (normalized.startsWith('minute')) {
5742
+ return 'minute';
5743
+ }
5744
+ if (normalized.startsWith('hour')) {
5745
+ return 'hour';
5746
+ }
5747
+ if (normalized.startsWith('day')) {
5748
+ return 'day';
5749
+ }
5750
+ if (normalized.startsWith('week')) {
5751
+ return 'week';
5752
+ }
5753
+ if (normalized.startsWith('month')) {
5754
+ return 'month';
5755
+ }
5756
+ return 'year';
5757
+ }
5758
+ function parseAssistantNowRelativeToken(value) {
5759
+ var trimmed = normalizeOptionalString(value);
5760
+ if (!trimmed) {
5761
+ return null;
5762
+ }
5763
+ var match = trimmed.match(ASSISTANT_NOW_RELATIVE_PATTERN);
5764
+ if (!match) {
5765
+ return null;
5766
+ }
5767
+ var direction = String(match[1] || '').toUpperCase() === 'PLUS' ? 'PLUS' : 'MINUS';
5768
+ var amount = Number(match[2]);
5769
+ if (!Number.isFinite(amount) || amount < 0) {
5770
+ return null;
5771
+ }
5772
+ var unit = normalizeAssistantTimeUnit(match[3] || '');
5773
+ return {
5774
+ operator: direction === 'PLUS' ? '$dateAdd' : '$dateSubtract',
5775
+ amount: amount,
5776
+ unit: unit
5777
+ };
5778
+ }
5779
+ function normalizeAssistantNowExprOperand(value) {
5780
+ var _a;
5781
+ if (typeof value !== 'string') {
5782
+ return value;
5783
+ }
5784
+ var trimmed = normalizeOptionalString(value);
5785
+ if (!trimmed) {
5786
+ return value;
5787
+ }
5788
+ if (trimmed === '$$NOW') {
5789
+ return '$$NOW';
5790
+ }
5791
+ var parsed = parseAssistantNowRelativeToken(trimmed);
5792
+ if (!parsed) {
5793
+ return value;
5794
+ }
5795
+ return _a = {},
5796
+ _a[parsed.operator] = {
5797
+ startDate: '$$NOW',
5798
+ unit: parsed.unit,
5799
+ amount: parsed.amount
5800
+ },
5801
+ _a;
5802
+ }
5803
+ function normalizeAssistantNowExprPlaceholdersDeep(value) {
5804
+ if (Array.isArray(value)) {
5805
+ return value.map(function (entry) { return normalizeAssistantNowExprPlaceholdersDeep(entry); });
5806
+ }
5807
+ if (value instanceof Date || value instanceof RegExp || isMongoObjectId(value)) {
5808
+ return value;
5809
+ }
5810
+ if (typeof value === 'string') {
5811
+ return normalizeAssistantNowExprOperand(value);
5812
+ }
5813
+ if (!value || typeof value !== 'object') {
5814
+ return value;
5815
+ }
5816
+ var result = {};
5817
+ Object.keys(value).forEach(function (key) {
5818
+ result[key] = normalizeAssistantNowExprPlaceholdersDeep(value[key]);
5819
+ });
5820
+ return result;
5821
+ }
5175
5822
  function isMatchExpressionOperand(value) {
5176
5823
  if (typeof value === 'string') {
5177
5824
  return value.startsWith('$$');
@@ -5229,7 +5876,7 @@ function rewriteMatchExpressionsToExpr(match) {
5229
5876
  }
5230
5877
  var operand = nextEntry_1[op];
5231
5878
  if (isMatchExpressionOperand(operand)) {
5232
- exprClauses.push((_a = {}, _a[op] = ["$".concat(key), operand], _a));
5879
+ exprClauses.push((_a = {}, _a[op] = ["$".concat(key), normalizeAssistantNowExprOperand(operand)], _a));
5233
5880
  delete nextEntry_1[op];
5234
5881
  moved_1 = true;
5235
5882
  }
@@ -5243,7 +5890,7 @@ function rewriteMatchExpressionsToExpr(match) {
5243
5890
  return;
5244
5891
  }
5245
5892
  if (typeof entry === 'string' && entry.startsWith('$$')) {
5246
- exprClauses.push({ $eq: ["$".concat(key), entry] });
5893
+ exprClauses.push({ $eq: ["$".concat(key), normalizeAssistantNowExprOperand(entry)] });
5247
5894
  return;
5248
5895
  }
5249
5896
  result[key] = entry;
@@ -5320,7 +5967,7 @@ function normalizeAssistantAggregatePipeline(pipeline, collection) {
5320
5967
  var statusNormalized = isInvoiceCollection ? normalizeInvoiceStatusMatch(exprRewritten) : exprRewritten;
5321
5968
  next.$geoNear = __assign(__assign({}, next.$geoNear), { query: applyAssistantNameRegexToQuery(statusNormalized) });
5322
5969
  }
5323
- return next;
5970
+ return normalizeAssistantNowExprPlaceholdersDeep(next);
5324
5971
  });
5325
5972
  }
5326
5973
  function buildAssistantAggregatePipeline(query, pipeline) {
@@ -6967,7 +7614,9 @@ function normalizeMongoQuery(query) {
6967
7614
  throw new Error('AI assistant report builder bridge: Query contains restricted operators.');
6968
7615
  }
6969
7616
  var rewritten = rewriteEmbeddedMatchObjects(normalized);
6970
- return applyAssistantNameRegexToQuery(rewritten);
7617
+ var exprRewritten = rewriteMatchExpressionsToExpr(rewritten);
7618
+ var nowNormalized = normalizeAssistantNowExprPlaceholdersDeep(exprRewritten);
7619
+ return applyAssistantNameRegexToQuery(nowNormalized);
6971
7620
  }
6972
7621
  function shouldApplyAssistantNameRegex(field) {
6973
7622
  var normalized = String(field || '').toLowerCase().trim();
@@ -7491,7 +8140,7 @@ function shouldAllowVersionCollections(message) {
7491
8140
  }
7492
8141
  function resolveReportCollectionName(permissionView, collectionNames, currentCollection) {
7493
8142
  var normalizedView = normalizeOptionalString(permissionView).toLowerCase();
7494
- if (!normalizedView.startsWith('/report/')) {
8143
+ if (!normalizedView.startsWith('/report-builder')) {
7495
8144
  return null;
7496
8145
  }
7497
8146
  var current = stripVersionSuffix(normalizeOptionalString(currentCollection));
@@ -7499,11 +8148,6 @@ function resolveReportCollectionName(permissionView, collectionNames, currentCol
7499
8148
  if (collectionNames.includes(reportCollection)) {
7500
8149
  return reportCollection;
7501
8150
  }
7502
- var routeTail = normalizedView.replace('/report/', '').replace(/\//g, '-');
7503
- var routeCandidate = routeTail ? "report-".concat(routeTail) : '';
7504
- if (routeCandidate && collectionNames.includes(routeCandidate)) {
7505
- return routeCandidate;
7506
- }
7507
8151
  return null;
7508
8152
  }
7509
8153
  function resolveBaseCollectionFromReport(db, dbName, collection) {
@@ -7541,7 +8185,7 @@ function resolveCollectionOverrideWithContext(params) {
7541
8185
  to: reportPreferred,
7542
8186
  fromScore: 0,
7543
8187
  toScore: 0,
7544
- reason: 'report route preference'
8188
+ reason: 'report builder route preference'
7545
8189
  };
7546
8190
  }
7547
8191
  }
@@ -8149,14 +8793,43 @@ function userHasViewPermission(user, view) {
8149
8793
  }
8150
8794
  return false;
8151
8795
  }
8152
- function userHasAnyViewPermission(user, views) {
8153
- if (!user || !Array.isArray(views)) {
8796
+ function collectUserViewPermissions(user) {
8797
+ var _a, _b;
8798
+ if (!user) {
8799
+ return [];
8800
+ }
8801
+ var groups = Array.isArray((_a = user.roles) === null || _a === void 0 ? void 0 : _a.groups) ? user.roles.groups : [];
8802
+ var miscs = Array.isArray((_b = user.roles) === null || _b === void 0 ? void 0 : _b.miscs) ? user.roles.miscs : [];
8803
+ var collected = [];
8804
+ var seen = new Set();
8805
+ var push = function (value) {
8806
+ var normalized = normalizeOptionalString(value);
8807
+ if (!normalized || seen.has(normalized)) {
8808
+ return;
8809
+ }
8810
+ seen.add(normalized);
8811
+ collected.push(normalized);
8812
+ };
8813
+ groups.forEach(function (group) {
8814
+ var views = Array.isArray(group === null || group === void 0 ? void 0 : group.views) ? group.views : [];
8815
+ views.forEach(push);
8816
+ });
8817
+ miscs.forEach(push);
8818
+ return collected;
8819
+ }
8820
+ function userHasViewTokenPermission(user, tokenRegex) {
8821
+ var _a;
8822
+ if (!user || !tokenRegex) {
8154
8823
  return false;
8155
8824
  }
8156
- return views.some(function (view) { return view && userHasViewPermission(user, view); });
8825
+ if ((_a = user.roles) === null || _a === void 0 ? void 0 : _a.super_admin) {
8826
+ return true;
8827
+ }
8828
+ var permissions = collectUserViewPermissions(user);
8829
+ return permissions.some(function (view) { return tokenRegex.test(view); });
8157
8830
  }
8158
8831
  function userHasInvoiceAccess(user) {
8159
- return userHasAnyViewPermission(user, ['/invoice', '/report/invoice']);
8832
+ return userHasViewTokenPermission(user, /invoice/i);
8160
8833
  }
8161
8834
  function requiresInvoicePermission(collection) {
8162
8835
  var normalized = normalizeOptionalString(collection).toLowerCase();
@@ -8208,16 +8881,59 @@ function resolveCodexWorkerThreadEnabled() {
8208
8881
  }
8209
8882
  return process.env.IS_WORKER_INSTANCE !== 'true';
8210
8883
  }
8211
- function resolveCodexModel() {
8212
- var config = resolveio_server_app_1.ResolveIOServer.getServerConfig() || {};
8213
- var raw = normalizeOptionalString(config['AI_ASSISTANT_CODEX_MODEL']
8884
+ function normalizeCodexModelList(value) {
8885
+ if (Array.isArray(value)) {
8886
+ return value
8887
+ .map(function (entry) { return normalizeOptionalString(entry); })
8888
+ .filter(Boolean);
8889
+ }
8890
+ var raw = normalizeOptionalString(value);
8891
+ if (!raw) {
8892
+ return [];
8893
+ }
8894
+ return raw
8895
+ .split(',')
8896
+ .map(function (entry) { return normalizeOptionalString(entry); })
8897
+ .filter(Boolean);
8898
+ }
8899
+ function resolveCodexModel(config) {
8900
+ var serverConfig = resolveio_server_app_1.ResolveIOServer.getServerConfig() || {};
8901
+ var raw = normalizeOptionalString((config === null || config === void 0 ? void 0 : config.model)
8902
+ || (config === null || config === void 0 ? void 0 : config.codex_model)
8903
+ || serverConfig['AI_ASSISTANT_CODEX_MODEL']
8214
8904
  || process.env.AI_ASSISTANT_CODEX_MODEL
8215
- || config['AI_TERMINAL_CODEX_MODEL']
8905
+ || serverConfig['AI_TERMINAL_CODEX_MODEL']
8216
8906
  || process.env.AI_TERMINAL_CODEX_MODEL
8217
- || config['AI_DASHBOARD_CODEX_MODEL']
8907
+ || serverConfig['AI_DASHBOARD_CODEX_MODEL']
8218
8908
  || process.env.AI_DASHBOARD_CODEX_MODEL);
8219
8909
  return raw || DEFAULT_CODEX_MODEL;
8220
8910
  }
8911
+ function resolveCodexFallbackModels(config, primaryModel) {
8912
+ var serverConfig = resolveio_server_app_1.ResolveIOServer.getServerConfig() || {};
8913
+ var primary = normalizeOptionalString(primaryModel || resolveCodexModel(config));
8914
+ var models = [];
8915
+ var push = function (value) {
8916
+ var normalized = normalizeOptionalString(value);
8917
+ if (!normalized || normalized === primary || models.includes(normalized)) {
8918
+ return;
8919
+ }
8920
+ models.push(normalized);
8921
+ };
8922
+ normalizeCodexModelList(config === null || config === void 0 ? void 0 : config.fallback_models).forEach(push);
8923
+ normalizeCodexModelList(config === null || config === void 0 ? void 0 : config.fallbackModels).forEach(push);
8924
+ push(config === null || config === void 0 ? void 0 : config.fallback_model);
8925
+ push(config === null || config === void 0 ? void 0 : config.fallbackModel);
8926
+ normalizeCodexModelList(serverConfig['AI_ASSISTANT_CODEX_FALLBACK_MODELS'] || process.env.AI_ASSISTANT_CODEX_FALLBACK_MODELS).forEach(push);
8927
+ push(serverConfig['AI_ASSISTANT_CODEX_FALLBACK_MODEL'] || process.env.AI_ASSISTANT_CODEX_FALLBACK_MODEL);
8928
+ normalizeCodexModelList(serverConfig['AI_TERMINAL_CODEX_FALLBACK_MODELS'] || process.env.AI_TERMINAL_CODEX_FALLBACK_MODELS).forEach(push);
8929
+ push(serverConfig['AI_TERMINAL_CODEX_FALLBACK_MODEL'] || process.env.AI_TERMINAL_CODEX_FALLBACK_MODEL);
8930
+ normalizeCodexModelList(serverConfig['AI_DASHBOARD_CODEX_FALLBACK_MODELS'] || process.env.AI_DASHBOARD_CODEX_FALLBACK_MODELS).forEach(push);
8931
+ push(serverConfig['AI_DASHBOARD_CODEX_FALLBACK_MODEL'] || process.env.AI_DASHBOARD_CODEX_FALLBACK_MODEL);
8932
+ if (!models.length) {
8933
+ push(DEFAULT_CODEX_FALLBACK_MODEL);
8934
+ }
8935
+ return models;
8936
+ }
8221
8937
  function resolveCodexThoughtLevel() {
8222
8938
  var config = resolveio_server_app_1.ResolveIOServer.getServerConfig() || {};
8223
8939
  var raw = normalizeOptionalString(config['AI_ASSISTANT_CODEX_THOUGHT_LEVEL']
@@ -8232,25 +8948,38 @@ function resolveCodexThoughtLevel() {
8232
8948
  }
8233
8949
  return 'low';
8234
8950
  }
8235
- function resolveCodexSettings() {
8951
+ function resolveCodexSettings(options) {
8236
8952
  var serverConfig = resolveio_server_app_1.ResolveIOServer.getServerConfig() || {};
8237
8953
  var apiKey = (serverConfig['OPENAI_API_KEY'] || process.env.OPENAI_API_KEY || '').trim();
8238
8954
  if (!apiKey) {
8239
8955
  throw new Error('OpenAI API key missing. Add OPENAI_API_KEY to server config.');
8240
8956
  }
8241
- return {
8242
- apiKey: apiKey,
8243
- baseUrl: (serverConfig['OPENAI_BASE_URL'] || process.env.OPENAI_BASE_URL || '').trim() || undefined,
8244
- model: resolveCodexModel(),
8245
- maxRetries: normalizeOptionalNumber(serverConfig['OPENAI_MAX_RETRIES'] || process.env.OPENAI_MAX_RETRIES),
8246
- retryDelayMs: normalizeOptionalNumber(serverConfig['OPENAI_RETRY_DELAY_MS'] || process.env.OPENAI_RETRY_DELAY_MS)
8247
- };
8957
+ var model = normalizeOptionalString(options === null || options === void 0 ? void 0 : options.model) || resolveCodexModel();
8958
+ var fallbackModels = resolveCodexFallbackModels({ fallbackModels: options === null || options === void 0 ? void 0 : options.fallbackModels }, model);
8959
+ return __assign(__assign({ apiKey: apiKey, baseUrl: (serverConfig['OPENAI_BASE_URL'] || process.env.OPENAI_BASE_URL || '').trim() || undefined, model: model }, (fallbackModels.length ? { fallbackModel: fallbackModels[0], fallbackModels: fallbackModels } : {})), { maxRetries: normalizeOptionalNumber(serverConfig['OPENAI_MAX_RETRIES'] || process.env.OPENAI_MAX_RETRIES), retryDelayMs: normalizeOptionalNumber(serverConfig['OPENAI_RETRY_DELAY_MS'] || process.env.OPENAI_RETRY_DELAY_MS) });
8960
+ }
8961
+ function buildAssistantCodexClientCacheKey(config) {
8962
+ var _a, _b;
8963
+ return JSON.stringify({
8964
+ apiKey: normalizeOptionalString(config === null || config === void 0 ? void 0 : config.apiKey),
8965
+ baseUrl: normalizeOptionalString(config === null || config === void 0 ? void 0 : config.baseUrl),
8966
+ model: normalizeOptionalString(config === null || config === void 0 ? void 0 : config.model),
8967
+ fallbackModel: normalizeOptionalString(config === null || config === void 0 ? void 0 : config.fallbackModel),
8968
+ fallbackModels: normalizeCodexModelList(config === null || config === void 0 ? void 0 : config.fallbackModels),
8969
+ maxRetries: (_a = config === null || config === void 0 ? void 0 : config.maxRetries) !== null && _a !== void 0 ? _a : null,
8970
+ retryDelayMs: (_b = config === null || config === void 0 ? void 0 : config.retryDelayMs) !== null && _b !== void 0 ? _b : null
8971
+ });
8248
8972
  }
8249
- function getAssistantCodexClient() {
8250
- if (!assistantCodexClient) {
8251
- assistantCodexClient = new codex_client_1.CodexClient(resolveCodexSettings());
8973
+ function getAssistantCodexClient(config) {
8974
+ var resolved = config || resolveCodexSettings();
8975
+ var key = buildAssistantCodexClientCacheKey(resolved);
8976
+ var existing = assistantCodexClientByConfig.get(key);
8977
+ if (existing) {
8978
+ return existing;
8252
8979
  }
8253
- return assistantCodexClient;
8980
+ var client = new codex_client_1.CodexClient(resolved);
8981
+ assistantCodexClientByConfig.set(key, client);
8982
+ return client;
8254
8983
  }
8255
8984
  var CodexWorkerBootstrapError = /** @class */ (function (_super) {
8256
8985
  __extends(CodexWorkerBootstrapError, _super);
@@ -8345,14 +9074,14 @@ function runCodexInWorkerThread(prompt, runOptions, config, streamStatusHandler)
8345
9074
  case 0:
8346
9075
  streamedOptions = applyCodexStreamStatusHandler(runOptions, streamStatusHandler);
8347
9076
  if (!!resolveCodexWorkerThreadEnabled()) return [3 /*break*/, 2];
8348
- codexClient = getAssistantCodexClient();
9077
+ codexClient = getAssistantCodexClient(config);
8349
9078
  return [4 /*yield*/, codexClient.run(prompt, streamedOptions)];
8350
9079
  case 1: return [2 /*return*/, _a.sent()];
8351
9080
  case 2: return [4 /*yield*/, resolveCodexWorkerPath()];
8352
9081
  case 3:
8353
9082
  workerPath = _a.sent();
8354
9083
  if (!!workerPath) return [3 /*break*/, 5];
8355
- codexClient = getAssistantCodexClient();
9084
+ codexClient = getAssistantCodexClient(config);
8356
9085
  return [4 /*yield*/, codexClient.run(prompt, streamedOptions)];
8357
9086
  case 4: return [2 /*return*/, _a.sent()];
8358
9087
  case 5:
@@ -8365,7 +9094,7 @@ function runCodexInWorkerThread(prompt, runOptions, config, streamStatusHandler)
8365
9094
  throw error_4;
8366
9095
  }
8367
9096
  console.error('Codex worker bootstrap failed, falling back to in-process run.', error_4);
8368
- codexClient = getAssistantCodexClient();
9097
+ codexClient = getAssistantCodexClient(config);
8369
9098
  return [4 /*yield*/, codexClient.run(prompt, streamedOptions)];
8370
9099
  case 8: return [2 /*return*/, _a.sent()];
8371
9100
  case 9: return [2 /*return*/];
@@ -8527,7 +9256,8 @@ function sanitizeCodexRunOptions(options) {
8527
9256
  timeoutMs: options.timeoutMs,
8528
9257
  threadOptions: options.threadOptions,
8529
9258
  threadKey: options.threadKey,
8530
- reuseThread: options.reuseThread
9259
+ reuseThread: options.reuseThread,
9260
+ fallbackModels: options.fallbackModels
8531
9261
  };
8532
9262
  }
8533
9263
  function resolveCodexWorkerPath() {
@@ -8910,6 +9640,10 @@ function buildAssistantContext(input, userContext) {
8910
9640
  lines.push(hint);
8911
9641
  });
8912
9642
  }
9643
+ var recentToolError = normalizeOptionalString(userContext === null || userContext === void 0 ? void 0 : userContext.recentToolError);
9644
+ if (recentToolError) {
9645
+ lines.push("Most recent data-query error: ".concat(recentToolError));
9646
+ }
8913
9647
  var mongoDb = normalizeOptionalString((_c = input === null || input === void 0 ? void 0 : input.mongo) === null || _c === void 0 ? void 0 : _c.database);
8914
9648
  var mongoDbs = Array.isArray((_d = input === null || input === void 0 ? void 0 : input.mongo) === null || _d === void 0 ? void 0 : _d.databases)
8915
9649
  ? input.mongo.databases.map(function (value) { return normalizeOptionalString(value); }).filter(Boolean)
@@ -9006,6 +9740,34 @@ function buildAssistantFieldHints(message, collectionNames, options) {
9006
9740
  });
9007
9741
  return hints;
9008
9742
  }
9743
+ function isAssistantWhyFollowupMessage(message) {
9744
+ var normalized = normalizeOptionalString(message).toLowerCase();
9745
+ if (!normalized) {
9746
+ return false;
9747
+ }
9748
+ return /^(so\s+)?why(?:\s+not)?[.!?]*$/.test(normalized)
9749
+ || /^(so\s+)?why\b/.test(normalized);
9750
+ }
9751
+ function resolveRecentAssistantToolError(history) {
9752
+ var _a, _b;
9753
+ var entries = Array.isArray(history) ? history : [];
9754
+ for (var i = entries.length - 1; i >= 0; i -= 1) {
9755
+ var entry = entries[i];
9756
+ if ((entry === null || entry === void 0 ? void 0 : entry.role) !== 'assistant') {
9757
+ continue;
9758
+ }
9759
+ var notes = Array.isArray((_b = (_a = entry === null || entry === void 0 ? void 0 : entry.metadata) === null || _a === void 0 ? void 0 : _a.debug) === null || _b === void 0 ? void 0 : _b.notes)
9760
+ ? entry.metadata.debug.notes
9761
+ : [];
9762
+ var matched = notes
9763
+ .map(function (note) { return normalizeOptionalString(note); })
9764
+ .find(function (note) { return note.toLowerCase().startsWith('tool error:'); });
9765
+ if (matched) {
9766
+ return matched.replace(/^tool error:\s*/i, '').trim();
9767
+ }
9768
+ }
9769
+ return '';
9770
+ }
9009
9771
  var cachedClientRouteIndex = null;
9010
9772
  function normalizeRouteKey(value) {
9011
9773
  var trimmed = normalizeOptionalString(value);
@@ -9180,13 +9942,7 @@ function sanitizeAssistantResponse(value) {
9180
9942
  if (!cleaned) {
9181
9943
  return 'I can’t share code, but I can point you to files or explain behavior at a high level.';
9182
9944
  }
9183
- var normalizedCurrency = cleaned
9184
- .replace(/\bUSD(?:\s|&nbsp;|&#160;|[\u00A0\u202F\u2007])*\$?\s*([-+]?[0-9][0-9,]*(?:\.[0-9]+)?)/g, function (_match, amount) {
9185
- return "$".concat(amount);
9186
- })
9187
- .replace(/\bUS\$\s*([-+]?[0-9][0-9,]*(?:\.[0-9]+)?)/g, function (_match, amount) {
9188
- return "$".concat(amount);
9189
- });
9945
+ var normalizedCurrency = normalizeAssistantCurrencyText(cleaned);
9190
9946
  return normalizeAssistantRoutes(normalizedCurrency);
9191
9947
  }
9192
9948
  function evaluateAssistantGuardrails(message) {