@resolveio/server-lib 22.0.10 → 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;
@@ -381,12 +382,13 @@ var AI_ASSISTANT_REPORT_BUILDER_EXPERT_PLAYBOOK = [
381
382
  '',
382
383
  '2) Resolve the target dataset safely.',
383
384
  '- Map user wording to internal collection names using routes, collection hints, field hints, and synonym expansion.',
384
- '- Prefer report-* collections when permissionView is under /report.',
385
+ '- Prefer report-* collections when permissionView is under /report-builder.',
385
386
  '- Never use *.versions unless user explicitly requests bug-history/version investigation.',
386
387
  '- Never invent collection names or fields.',
387
388
  '',
388
389
  '3) Enforce permissions and scope in the directive.',
389
390
  '- Always include permissionView.',
391
+ '- Use /report-builder as the default permissionView for data directives; avoid /report/* routes.',
390
392
  '- Assume non-super-admin unless explicitly told otherwise.',
391
393
  '- Invoice-like collections require invoice view access.',
392
394
  '- Customer portal users must stay in their own customer scope.',
@@ -435,7 +437,7 @@ var AI_ASSISTANT_SYSTEM_PROMPT = [
435
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.',
436
438
  '- Never use *.versions collections for normal requests. Only use a .versions collection when explicitly investigating a bug by checking the last ~5 updates.',
437
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.',
438
- '- 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.',
439
441
  '- Map user wording to internal collections/fields yourself. Do not ask for property names unless required to run a query.',
440
442
  '- Use term hints from context (synonym expansions) when mapping user language to collections.',
441
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.',
@@ -490,7 +492,8 @@ var AI_ASSISTANT_SYSTEM_PROMPT = [
490
492
  '- REPORT_BUILDER_READ: {"collection":"<name>","query":{...},"options":{"projection":{...},"sort":{...},"limit":20},"permissionView":"</route>"}',
491
493
  '- If you need grouped/aggregated data (totals by user, rankings, trends), end your response with a single line exactly in this format:',
492
494
  '- REPORT_BUILDER_AGG: {"collection":"<name>","pipeline":[...],"options":{"allowDiskUse":true,"limit":20},"permissionView":"</route>"}',
493
- '- 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.',
494
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.',
495
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.',
496
499
  '- For relative date ranges (last/past/recent), include an upper bound <= $$NOW unless the user specifies a future end date.',
@@ -532,7 +535,7 @@ var AI_ASSISTANT_PLANNER_SYSTEM_PROMPT = [
532
535
  ' - Never propose querying blocked/sensitive collections if the user lacks permission.',
533
536
  '',
534
537
  '4) PERMISSION MATCHING:',
535
- ' - Do NOT hardcode invoice access to "/report/invoice".',
538
+ ' - Do NOT hardcode invoice access to "/report-builder/*".',
536
539
  ' - For invoice-related data or navigation, permission is satisfied if ANY user view contains "invoice" case-insensitive.',
537
540
  ' (General rule: for an entity token X, permission is satisfied if any view contains X case-insensitive.)',
538
541
  ' - If permission checks are ambiguous, choose the safest restriction and explain.',
@@ -667,7 +670,7 @@ var AI_FORM_PATCH_SYSTEM_PROMPT = [
667
670
  '- Use ISO 8601 for dates and true/false for booleans.',
668
671
  '- Use medium reasoning effort.'
669
672
  ].join('\n');
670
- var assistantCodexClient = null;
673
+ var assistantCodexClientByConfig = new Map();
671
674
  var assistantCodexRunQueue = [];
672
675
  var assistantCodexRunDraining = false;
673
676
  /* eslint-enable no-unused-vars */
@@ -1320,7 +1323,7 @@ function executeAiFormPatch(payload, context) {
1320
1323
  }
1321
1324
  function executeAiAssistantCodexRun(payload, context) {
1322
1325
  return __awaiter(this, void 0, void 0, function () {
1323
- 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;
1324
1327
  var _this = this;
1325
1328
  var _b, _c;
1326
1329
  return __generator(this, function (_d) {
@@ -1336,6 +1339,8 @@ function executeAiAssistantCodexRun(payload, context) {
1336
1339
  }
1337
1340
  aiWorkerDebug = isAiWorkerDebugEnabled();
1338
1341
  requestId = normalizeOptionalString(input.request_id);
1342
+ codexModel = resolveCodexModel(input.config);
1343
+ codexFallbackModels = resolveCodexFallbackModels(input.config, codexModel);
1339
1344
  guardrail = evaluateAssistantGuardrails(message);
1340
1345
  if (!(guardrail === null || guardrail === void 0 ? void 0 : guardrail.blocked)) return [3 /*break*/, 5];
1341
1346
  return [4 /*yield*/, ensureConversation(input, 'codex')];
@@ -1415,6 +1420,9 @@ function executeAiAssistantCodexRun(payload, context) {
1415
1420
  historyLines.push("".concat(role, ": ").concat(content));
1416
1421
  }
1417
1422
  });
1423
+ recentToolError = isAssistantWhyFollowupMessage(message)
1424
+ ? resolveRecentAssistantToolError(history)
1425
+ : '';
1418
1426
  userDoc = {
1419
1427
  id_conversation: conversation._id,
1420
1428
  role: 'user',
@@ -1429,7 +1437,7 @@ function executeAiAssistantCodexRun(payload, context) {
1429
1437
  id_conversation: conversation._id,
1430
1438
  role: 'assistant',
1431
1439
  content: AI_ASSISTANT_PROGRESS_PLACEHOLDER,
1432
- 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 }),
1433
1441
  createdAt: now,
1434
1442
  updatedAt: now
1435
1443
  };
@@ -1564,16 +1572,22 @@ function executeAiAssistantCodexRun(payload, context) {
1564
1572
  customerId: customerId,
1565
1573
  collectionHints: collectionHints,
1566
1574
  termHints: termHints,
1567
- fieldHints: fieldHints
1575
+ fieldHints: fieldHints,
1576
+ recentToolError: recentToolError
1568
1577
  });
1569
1578
  prompt_1 = buildAssistantCodexPrompt(message, attachmentData.promptText, historyLines.join('\n'), assistantContext);
1570
1579
  return [4 /*yield*/, resolveAssistantWorkspaceRoot()];
1571
1580
  case 6:
1572
1581
  workspaceRoot = _x.sent();
1573
- codexConfig = resolveCodexSettings();
1582
+ codexConfig = resolveCodexSettings({
1583
+ model: codexModel,
1584
+ fallbackModels: codexFallbackModels
1585
+ });
1574
1586
  runOptions = {
1575
1587
  timeoutMs: resolveCodexTimeoutMs(),
1588
+ fallbackModels: codexFallbackModels,
1576
1589
  threadOptions: {
1590
+ model: codexModel,
1577
1591
  workingDirectory: workspaceRoot,
1578
1592
  sandboxMode: 'read-only',
1579
1593
  skipGitRepoCheck: true,
@@ -1874,7 +1888,7 @@ function executeAiAssistantCodexRun(payload, context) {
1874
1888
  }
1875
1889
  });
1876
1890
  }
1877
- 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 } : {}));
1878
1892
  finalAssistantDoc = __assign(__assign({}, assistantDoc), { _id: assistantMessageId, content: assistantContent, metadata: finalMetadata, updatedAt: finalNow });
1879
1893
  if (!assistantMessageId) return [3 /*break*/, 39];
1880
1894
  return [4 /*yield*/, ai_terminal_message_collection_1.AiTerminalMessages.updateOne({ _id: assistantMessageId }, {
@@ -3548,13 +3562,12 @@ function extractAssistantMongoDirective(content) {
3548
3562
  };
3549
3563
  }
3550
3564
  function buildAssistantToolRequest(directive, payload) {
3551
- var _a;
3552
3565
  var base = directive.payload && typeof directive.payload === 'object' ? directive.payload : {};
3553
3566
  var request = __assign({}, base);
3554
- var route = normalizeOptionalString((_a = payload === null || payload === void 0 ? void 0 : payload.context) === null || _a === void 0 ? void 0 : _a.route);
3555
- if (!request.permissionView && route) {
3556
- request.permissionView = route;
3567
+ if (!request.permissionView) {
3568
+ request.permissionView = '/report-builder';
3557
3569
  }
3570
+ request.permissionView = normalizeAssistantPermissionView(request.permissionView, request.collection);
3558
3571
  if (!request.id_client) {
3559
3572
  var idClient = normalizeOptionalString(payload === null || payload === void 0 ? void 0 : payload.id_client);
3560
3573
  if (idClient) {
@@ -3566,6 +3579,24 @@ function buildAssistantToolRequest(directive, payload) {
3566
3579
  }
3567
3580
  return request;
3568
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
+ }
3569
3600
  function buildAssistantToolResultPayload(directive, toolResponse) {
3570
3601
  var _a, _b, _c;
3571
3602
  var directivePayload = directive.payload || {};
@@ -3877,7 +3908,7 @@ function buildAssistantToolErrorMessage(error, directive, request) {
3877
3908
  ? "Open ".concat(routeHint, " in the app to view this data or request access.")
3878
3909
  : 'Open the related screen in the app to view this data or request access.';
3879
3910
  if (!routeHint && collection && requiresInvoicePermission(collection)) {
3880
- 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.';
3881
3912
  }
3882
3913
  if (normalized.includes('permission scope required')) {
3883
3914
  return "I need a permission scope to access that data. ".concat(routeLine);
@@ -3891,6 +3922,9 @@ function buildAssistantToolErrorMessage(error, directive, request) {
3891
3922
  if (normalized.includes('database access denied')) {
3892
3923
  return "Database access is restricted for that request. ".concat(routeLine);
3893
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
+ }
3894
3928
  if (normalized.includes('report builder bridge') && normalized.includes('not configured')) {
3895
3929
  return "That dataset is not configured for report builder access yet. ".concat(routeLine);
3896
3930
  }
@@ -5595,7 +5629,7 @@ function isDisplayObjectLike(value) {
5595
5629
  }
5596
5630
  function ensureAssistantReadAccess(context, permissionView, collection) {
5597
5631
  return __awaiter(this, void 0, void 0, function () {
5598
- var idUser, user, isSuperAdmin, normalizedPermission, normalizedCollection;
5632
+ var idUser, user, isSuperAdmin, normalizedCollection, normalizedPermission, requiresInvoiceAccess, hasInvoiceAccess, hasViewAccess;
5599
5633
  var _a;
5600
5634
  return __generator(this, function (_b) {
5601
5635
  switch (_b.label) {
@@ -5611,20 +5645,21 @@ function ensureAssistantReadAccess(context, permissionView, collection) {
5611
5645
  throw new Error('AI assistant report builder bridge: Unauthorized.');
5612
5646
  }
5613
5647
  isSuperAdmin = !!((_a = user === null || user === void 0 ? void 0 : user.roles) === null || _a === void 0 ? void 0 : _a.super_admin);
5614
- normalizedPermission = normalizeOptionalString(permissionView);
5615
- if (!isSuperAdmin) {
5616
- if (!normalizedPermission) {
5617
- throw new Error('AI assistant report builder bridge: Permission scope required.');
5618
- }
5619
- if (!userHasViewPermission(user, normalizedPermission)) {
5620
- throw new Error('AI assistant report builder bridge: Access denied.');
5621
- }
5622
- normalizedCollection = normalizeOptionalString(collection);
5623
- if (normalizedCollection && requiresInvoicePermission(normalizedCollection) && !userHasInvoiceAccess(user)) {
5624
- throw new Error('AI assistant report builder bridge: Access denied.');
5625
- }
5648
+ if (isSuperAdmin) {
5649
+ return [2 /*return*/, { user: user, isSuperAdmin: isSuperAdmin }];
5626
5650
  }
5627
- else if (normalizedPermission && !userHasViewPermission(user, normalizedPermission)) {
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.');
5661
+ }
5662
+ if (requiresInvoiceAccess && !hasInvoiceAccess) {
5628
5663
  throw new Error('AI assistant report builder bridge: Access denied.');
5629
5664
  }
5630
5665
  return [2 /*return*/, { user: user, isSuperAdmin: isSuperAdmin }];
@@ -5700,6 +5735,90 @@ function sanitizeAssistantProjection(projection) {
5700
5735
  return projection;
5701
5736
  }
5702
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
+ }
5703
5822
  function isMatchExpressionOperand(value) {
5704
5823
  if (typeof value === 'string') {
5705
5824
  return value.startsWith('$$');
@@ -5757,7 +5876,7 @@ function rewriteMatchExpressionsToExpr(match) {
5757
5876
  }
5758
5877
  var operand = nextEntry_1[op];
5759
5878
  if (isMatchExpressionOperand(operand)) {
5760
- exprClauses.push((_a = {}, _a[op] = ["$".concat(key), operand], _a));
5879
+ exprClauses.push((_a = {}, _a[op] = ["$".concat(key), normalizeAssistantNowExprOperand(operand)], _a));
5761
5880
  delete nextEntry_1[op];
5762
5881
  moved_1 = true;
5763
5882
  }
@@ -5771,7 +5890,7 @@ function rewriteMatchExpressionsToExpr(match) {
5771
5890
  return;
5772
5891
  }
5773
5892
  if (typeof entry === 'string' && entry.startsWith('$$')) {
5774
- exprClauses.push({ $eq: ["$".concat(key), entry] });
5893
+ exprClauses.push({ $eq: ["$".concat(key), normalizeAssistantNowExprOperand(entry)] });
5775
5894
  return;
5776
5895
  }
5777
5896
  result[key] = entry;
@@ -5848,7 +5967,7 @@ function normalizeAssistantAggregatePipeline(pipeline, collection) {
5848
5967
  var statusNormalized = isInvoiceCollection ? normalizeInvoiceStatusMatch(exprRewritten) : exprRewritten;
5849
5968
  next.$geoNear = __assign(__assign({}, next.$geoNear), { query: applyAssistantNameRegexToQuery(statusNormalized) });
5850
5969
  }
5851
- return next;
5970
+ return normalizeAssistantNowExprPlaceholdersDeep(next);
5852
5971
  });
5853
5972
  }
5854
5973
  function buildAssistantAggregatePipeline(query, pipeline) {
@@ -7495,7 +7614,9 @@ function normalizeMongoQuery(query) {
7495
7614
  throw new Error('AI assistant report builder bridge: Query contains restricted operators.');
7496
7615
  }
7497
7616
  var rewritten = rewriteEmbeddedMatchObjects(normalized);
7498
- return applyAssistantNameRegexToQuery(rewritten);
7617
+ var exprRewritten = rewriteMatchExpressionsToExpr(rewritten);
7618
+ var nowNormalized = normalizeAssistantNowExprPlaceholdersDeep(exprRewritten);
7619
+ return applyAssistantNameRegexToQuery(nowNormalized);
7499
7620
  }
7500
7621
  function shouldApplyAssistantNameRegex(field) {
7501
7622
  var normalized = String(field || '').toLowerCase().trim();
@@ -8019,7 +8140,7 @@ function shouldAllowVersionCollections(message) {
8019
8140
  }
8020
8141
  function resolveReportCollectionName(permissionView, collectionNames, currentCollection) {
8021
8142
  var normalizedView = normalizeOptionalString(permissionView).toLowerCase();
8022
- if (!normalizedView.startsWith('/report/')) {
8143
+ if (!normalizedView.startsWith('/report-builder')) {
8023
8144
  return null;
8024
8145
  }
8025
8146
  var current = stripVersionSuffix(normalizeOptionalString(currentCollection));
@@ -8027,11 +8148,6 @@ function resolveReportCollectionName(permissionView, collectionNames, currentCol
8027
8148
  if (collectionNames.includes(reportCollection)) {
8028
8149
  return reportCollection;
8029
8150
  }
8030
- var routeTail = normalizedView.replace('/report/', '').replace(/\//g, '-');
8031
- var routeCandidate = routeTail ? "report-".concat(routeTail) : '';
8032
- if (routeCandidate && collectionNames.includes(routeCandidate)) {
8033
- return routeCandidate;
8034
- }
8035
8151
  return null;
8036
8152
  }
8037
8153
  function resolveBaseCollectionFromReport(db, dbName, collection) {
@@ -8069,7 +8185,7 @@ function resolveCollectionOverrideWithContext(params) {
8069
8185
  to: reportPreferred,
8070
8186
  fromScore: 0,
8071
8187
  toScore: 0,
8072
- reason: 'report route preference'
8188
+ reason: 'report builder route preference'
8073
8189
  };
8074
8190
  }
8075
8191
  }
@@ -8677,14 +8793,43 @@ function userHasViewPermission(user, view) {
8677
8793
  }
8678
8794
  return false;
8679
8795
  }
8680
- function userHasAnyViewPermission(user, views) {
8681
- 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) {
8682
8823
  return false;
8683
8824
  }
8684
- 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); });
8685
8830
  }
8686
8831
  function userHasInvoiceAccess(user) {
8687
- return userHasAnyViewPermission(user, ['/invoice', '/report/invoice']);
8832
+ return userHasViewTokenPermission(user, /invoice/i);
8688
8833
  }
8689
8834
  function requiresInvoicePermission(collection) {
8690
8835
  var normalized = normalizeOptionalString(collection).toLowerCase();
@@ -8736,16 +8881,59 @@ function resolveCodexWorkerThreadEnabled() {
8736
8881
  }
8737
8882
  return process.env.IS_WORKER_INSTANCE !== 'true';
8738
8883
  }
8739
- function resolveCodexModel() {
8740
- var config = resolveio_server_app_1.ResolveIOServer.getServerConfig() || {};
8741
- 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']
8742
8904
  || process.env.AI_ASSISTANT_CODEX_MODEL
8743
- || config['AI_TERMINAL_CODEX_MODEL']
8905
+ || serverConfig['AI_TERMINAL_CODEX_MODEL']
8744
8906
  || process.env.AI_TERMINAL_CODEX_MODEL
8745
- || config['AI_DASHBOARD_CODEX_MODEL']
8907
+ || serverConfig['AI_DASHBOARD_CODEX_MODEL']
8746
8908
  || process.env.AI_DASHBOARD_CODEX_MODEL);
8747
8909
  return raw || DEFAULT_CODEX_MODEL;
8748
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
+ }
8749
8937
  function resolveCodexThoughtLevel() {
8750
8938
  var config = resolveio_server_app_1.ResolveIOServer.getServerConfig() || {};
8751
8939
  var raw = normalizeOptionalString(config['AI_ASSISTANT_CODEX_THOUGHT_LEVEL']
@@ -8760,25 +8948,38 @@ function resolveCodexThoughtLevel() {
8760
8948
  }
8761
8949
  return 'low';
8762
8950
  }
8763
- function resolveCodexSettings() {
8951
+ function resolveCodexSettings(options) {
8764
8952
  var serverConfig = resolveio_server_app_1.ResolveIOServer.getServerConfig() || {};
8765
8953
  var apiKey = (serverConfig['OPENAI_API_KEY'] || process.env.OPENAI_API_KEY || '').trim();
8766
8954
  if (!apiKey) {
8767
8955
  throw new Error('OpenAI API key missing. Add OPENAI_API_KEY to server config.');
8768
8956
  }
8769
- return {
8770
- apiKey: apiKey,
8771
- baseUrl: (serverConfig['OPENAI_BASE_URL'] || process.env.OPENAI_BASE_URL || '').trim() || undefined,
8772
- model: resolveCodexModel(),
8773
- maxRetries: normalizeOptionalNumber(serverConfig['OPENAI_MAX_RETRIES'] || process.env.OPENAI_MAX_RETRIES),
8774
- retryDelayMs: normalizeOptionalNumber(serverConfig['OPENAI_RETRY_DELAY_MS'] || process.env.OPENAI_RETRY_DELAY_MS)
8775
- };
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
+ });
8776
8972
  }
8777
- function getAssistantCodexClient() {
8778
- if (!assistantCodexClient) {
8779
- 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;
8780
8979
  }
8781
- return assistantCodexClient;
8980
+ var client = new codex_client_1.CodexClient(resolved);
8981
+ assistantCodexClientByConfig.set(key, client);
8982
+ return client;
8782
8983
  }
8783
8984
  var CodexWorkerBootstrapError = /** @class */ (function (_super) {
8784
8985
  __extends(CodexWorkerBootstrapError, _super);
@@ -8873,14 +9074,14 @@ function runCodexInWorkerThread(prompt, runOptions, config, streamStatusHandler)
8873
9074
  case 0:
8874
9075
  streamedOptions = applyCodexStreamStatusHandler(runOptions, streamStatusHandler);
8875
9076
  if (!!resolveCodexWorkerThreadEnabled()) return [3 /*break*/, 2];
8876
- codexClient = getAssistantCodexClient();
9077
+ codexClient = getAssistantCodexClient(config);
8877
9078
  return [4 /*yield*/, codexClient.run(prompt, streamedOptions)];
8878
9079
  case 1: return [2 /*return*/, _a.sent()];
8879
9080
  case 2: return [4 /*yield*/, resolveCodexWorkerPath()];
8880
9081
  case 3:
8881
9082
  workerPath = _a.sent();
8882
9083
  if (!!workerPath) return [3 /*break*/, 5];
8883
- codexClient = getAssistantCodexClient();
9084
+ codexClient = getAssistantCodexClient(config);
8884
9085
  return [4 /*yield*/, codexClient.run(prompt, streamedOptions)];
8885
9086
  case 4: return [2 /*return*/, _a.sent()];
8886
9087
  case 5:
@@ -8893,7 +9094,7 @@ function runCodexInWorkerThread(prompt, runOptions, config, streamStatusHandler)
8893
9094
  throw error_4;
8894
9095
  }
8895
9096
  console.error('Codex worker bootstrap failed, falling back to in-process run.', error_4);
8896
- codexClient = getAssistantCodexClient();
9097
+ codexClient = getAssistantCodexClient(config);
8897
9098
  return [4 /*yield*/, codexClient.run(prompt, streamedOptions)];
8898
9099
  case 8: return [2 /*return*/, _a.sent()];
8899
9100
  case 9: return [2 /*return*/];
@@ -9055,7 +9256,8 @@ function sanitizeCodexRunOptions(options) {
9055
9256
  timeoutMs: options.timeoutMs,
9056
9257
  threadOptions: options.threadOptions,
9057
9258
  threadKey: options.threadKey,
9058
- reuseThread: options.reuseThread
9259
+ reuseThread: options.reuseThread,
9260
+ fallbackModels: options.fallbackModels
9059
9261
  };
9060
9262
  }
9061
9263
  function resolveCodexWorkerPath() {
@@ -9438,6 +9640,10 @@ function buildAssistantContext(input, userContext) {
9438
9640
  lines.push(hint);
9439
9641
  });
9440
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
+ }
9441
9647
  var mongoDb = normalizeOptionalString((_c = input === null || input === void 0 ? void 0 : input.mongo) === null || _c === void 0 ? void 0 : _c.database);
9442
9648
  var mongoDbs = Array.isArray((_d = input === null || input === void 0 ? void 0 : input.mongo) === null || _d === void 0 ? void 0 : _d.databases)
9443
9649
  ? input.mongo.databases.map(function (value) { return normalizeOptionalString(value); }).filter(Boolean)
@@ -9534,6 +9740,34 @@ function buildAssistantFieldHints(message, collectionNames, options) {
9534
9740
  });
9535
9741
  return hints;
9536
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
+ }
9537
9771
  var cachedClientRouteIndex = null;
9538
9772
  function normalizeRouteKey(value) {
9539
9773
  var trimmed = normalizeOptionalString(value);