@resolveio/server-lib 20.14.45 → 20.14.46

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.
@@ -101,6 +101,11 @@ Object.defineProperty(exports, "__esModule", { value: true });
101
101
  exports.loadAiTerminalMethods = loadAiTerminalMethods;
102
102
  exports.executeAiAssistantMongoRead = executeAiAssistantMongoRead;
103
103
  exports.executeAiAssistantMongoAggregate = executeAiAssistantMongoAggregate;
104
+ exports.extractAssistantMongoDirective = extractAssistantMongoDirective;
105
+ exports.serializeMongoValue = serializeMongoValue;
106
+ exports.flattenForTable = flattenForTable;
107
+ exports.buildDisplayTable = buildDisplayTable;
108
+ exports.formatDisplayTableMarkdown = formatDisplayTableMarkdown;
104
109
  var fs_1 = require("fs");
105
110
  var events_1 = require("events");
106
111
  var os = require("os");
@@ -125,6 +130,28 @@ var DEFAULT_CODEX_MODEL = 'gpt-5.2-codex';
125
130
  var DEFAULT_CODEX_TIMEOUT_MS = 180000;
126
131
  var AI_ASSISTANT_MONGO_DEFAULT_LIMIT = 20;
127
132
  var AI_ASSISTANT_MONGO_MAX_LIMIT = 200;
133
+ var AI_ASSISTANT_TOOL_MAX_STEPS = 1;
134
+ var AI_ASSISTANT_DISPLAY_MAX_COLUMNS = 12;
135
+ var AI_ASSISTANT_DISPLAY_PREVIEW_MAX_ROWS = 20;
136
+ var AI_ASSISTANT_DISPLAY_STRING_LIMIT = 160;
137
+ var AI_ASSISTANT_DISPLAY_PRIORITY_FIELDS = [
138
+ 'name',
139
+ 'title',
140
+ 'status',
141
+ 'type',
142
+ 'stage',
143
+ 'state',
144
+ 'category',
145
+ 'date_created',
146
+ 'createdAt',
147
+ 'updatedAt',
148
+ 'date_updated',
149
+ 'date_completed',
150
+ 'id_customer',
151
+ 'id_client',
152
+ 'id_user',
153
+ 'id_invoice'
154
+ ];
128
155
  var AI_ASSISTANT_DATE_FALLBACKS = {
129
156
  date_created: 'createdAt',
130
157
  createdAt: 'date_created'
@@ -157,12 +184,29 @@ var AI_ASSISTANT_SENSITIVE_FIELDS = [
157
184
  var AI_ASSISTANT_CLIENT_SCOPE_CACHE = new Map();
158
185
  var AI_ASSISTANT_SYSTEM_PROMPT = [
159
186
  'You are the ResolveIO in-app AI assistant running with read-only access to the codebase.',
187
+ 'Core rules:',
160
188
  '- Never share code or file contents. All code is proprietary.',
161
189
  '- Do not modify files, run destructive commands, or access databases directly.',
162
- '- Read-only Mongo access is allowed only via the MONGO_READ directive (see below).',
190
+ '- Read-only Mongo access is allowed only via the MONGO_READ/MONGO_AGG directives (see below).',
163
191
  '- Do not access secrets, credentials, or user data.',
164
192
  '- If the user has a customer portal scope (other.id_customer), only discuss that customer\'s data and what is visible in their customer portal. Never reference other customers or internal/admin-only data. If asked for anything outside the portal, say it isn\'t available.',
165
193
  '- Do not assist with hacking, bypassing security, or abuse.',
194
+ 'Accuracy & tools:',
195
+ '- Do not guess or invent collections/fields. If unsure, verify in the codebase or run a small Mongo read (limit 1-5) to learn the shape.',
196
+ '- Prefer running a small Mongo read over asking multiple questions.',
197
+ '- Ask at most one clarifying question only when required to run a query or resolve missing details.',
198
+ '- Use the codebase context to choose correct collections/fields/workflows and use MONGO_READ/MONGO_AGG to answer with real data when needed.',
199
+ '- For direct questions, answer first. Ask a single follow-up only if required to proceed.',
200
+ 'Data Presentation:',
201
+ '- Output plain Markdown (NO triple backticks).',
202
+ '- When you reference database results, summarize first, then include a Markdown table.',
203
+ '- When presenting record lists or aggregates, produce a Markdown table (pipes).',
204
+ '- Never show raw JSON dumps.',
205
+ '- Do not include `_id` & `__v` & `id_<other collection _id property>` in tables by default.',
206
+ '- If `_id` & `__v` & `id_<other collection _id property>` is needed, show it as `id` and shorten (e.g., first 6-8 chars) unless asked otherwise.',
207
+ '- Rename `createdAt` to `Created At` and `updatedAt` to `Updated At` in tables.',
208
+ 'Response style:',
209
+ '- Keep responses concise: answer first, then 3-8 bullets, then a table when data is involved.',
166
210
  '- Prefer high-level explanations and point to routes instead of code. Only mention file paths if explicitly requested.',
167
211
  '- When asked where to do something, give the exact screen name and the in-app route as a standalone path starting with "/" so it can be clicked.',
168
212
  '- When asked "why is this happening," respond with: cause, trigger, data source(s), and expected vs actual outcome.',
@@ -174,7 +218,7 @@ var AI_ASSISTANT_SYSTEM_PROMPT = [
174
218
  '- If access is blocked, name the permission/role needed and how to request it.',
175
219
  '- Avoid vague labels like "Operations app"; use the specific screen/workflow name.',
176
220
  '- Do not mention other client projects or ask which client; stay within the current project context.',
177
- '- Respond with a single concise message. Do not add labels like "Customer-facing summary" or "Work ticket summary" and do not include estimated hours.',
221
+ '- Do not add labels like "Customer-facing summary" or "Work ticket summary" and do not include estimated hours.',
178
222
  '- Use structured responses by default: short heading, then bullet points. For "how do I" questions, provide a step-by-step list (numbered) with explicit page/screen names and actions.',
179
223
  '- When giving steps, include navigation guidance like "Go to <screen>" and "Click/Select/Enter <field>" so the user can follow it exactly.',
180
224
  '- If context scope is provided (ex: current page), prioritize that screen in your answer.',
@@ -183,8 +227,7 @@ var AI_ASSISTANT_SYSTEM_PROMPT = [
183
227
  '- SUPPORT_TICKET_CREATE: <one-line summary>',
184
228
  '- Only include that line when the user clearly wants a ticket created. Do not claim a ticket is created unless you include that line.',
185
229
  '- Do not end responses with tentative phrasing like "I could" or "I’m going to." Complete the request or state what is required to proceed.',
186
- '- Use the codebase context to choose correct collections/fields/workflows and use MONGO_READ/MONGO_AGG to answer with real data when needed.',
187
- '- For direct questions, answer first. Ask a single follow-up only if required to run a query or resolve missing details.',
230
+ 'Mongo directives:',
188
231
  '- If you need database data to answer, end your response with a single line exactly in this format:',
189
232
  '- MONGO_READ: {"collection":"<name>","query":{...},"options":{"projection":{...},"sort":{...},"limit":20},"permissionView":"</route>"}',
190
233
  '- If you need grouped/aggregated data (totals by user, rankings, trends), end your response with a single line exactly in this format:',
@@ -199,7 +242,7 @@ var AI_ASSISTANT_SYSTEM_PROMPT = [
199
242
  '- For creation-date questions when both date_created and createdAt exist, match both with $or so results are not missed.',
200
243
  '- When grouping by fields that can be arrays (drivers, deliveries, routes, chemicals), $unwind first and group by both id and name when available.',
201
244
  '- Use MONGO_READ/MONGO_AGG only to produce summaries/snapshots/health checks (not raw dumps) when permitted.',
202
- '- When referencing data, summarize it in bullets and avoid raw JSON or dumps.',
245
+ '- If the user explicitly asks for IDs, set options.includeIds: true.',
203
246
  '- Keep responses concise and use low reasoning effort.'
204
247
  ].join('\n');
205
248
  var AI_FORM_PATCH_SYSTEM_PROMPT = [
@@ -738,10 +781,10 @@ function executeAiFormPatch(payload, context) {
738
781
  }
739
782
  function executeAiAssistantCodexRun(payload, context) {
740
783
  return __awaiter(this, void 0, void 0, function () {
741
- var input, message, guardrail, conversation_2, now_2, userMsg, assistantMsg, user, isSuperAdmin, hasInvoiceAccess, customerId, conversation, now, attachments, attachmentData, historyLimit, history, _a, historyLines, prompt, workspaceRoot, codexConfig, runOptions, responseText, assistantContent, userDoc, assistantDoc, insertResult;
742
- var _b, _c;
743
- return __generator(this, function (_d) {
744
- switch (_d.label) {
784
+ var input, message, guardrail, conversation_2, now_2, userMsg, assistantMsg, user, isSuperAdmin, hasInvoiceAccess, customerId, conversation, now, attachments, attachmentData, historyLimit, history, _a, historyLines, assistantContext, prompt, workspaceRoot, codexConfig, runOptions, responseText, directive, cleanedResponseText, assistantContent, toolResult, toolRequest, toolResponse, _b, toolPayload, followupPrompt, followupText, _c, error_1, userDoc, assistantDoc, insertResult;
785
+ var _d, _e;
786
+ return __generator(this, function (_f) {
787
+ switch (_f.label) {
745
788
  case 0:
746
789
  input = payload || {};
747
790
  message = normalizeOptionalString(input.message);
@@ -755,7 +798,7 @@ function executeAiAssistantCodexRun(payload, context) {
755
798
  if (!(guardrail === null || guardrail === void 0 ? void 0 : guardrail.blocked)) return [3 /*break*/, 5];
756
799
  return [4 /*yield*/, ensureConversation(input, 'codex')];
757
800
  case 1:
758
- conversation_2 = _d.sent();
801
+ conversation_2 = _f.sent();
759
802
  now_2 = new Date();
760
803
  userMsg = {
761
804
  id_conversation: conversation_2._id,
@@ -777,13 +820,13 @@ function executeAiAssistantCodexRun(payload, context) {
777
820
  };
778
821
  return [4 /*yield*/, ai_terminal_message_collection_1.AiTerminalMessages.insertOne(userMsg)];
779
822
  case 2:
780
- _d.sent();
823
+ _f.sent();
781
824
  return [4 /*yield*/, ai_terminal_message_collection_1.AiTerminalMessages.insertOne(assistantMsg)];
782
825
  case 3:
783
- _d.sent();
826
+ _f.sent();
784
827
  return [4 /*yield*/, touchConversation(conversation_2._id, now_2)];
785
828
  case 4:
786
- _d.sent();
829
+ _f.sent();
787
830
  return [2 /*return*/, {
788
831
  conversation: conversation_2,
789
832
  message: assistantMsg,
@@ -791,27 +834,27 @@ function executeAiAssistantCodexRun(payload, context) {
791
834
  }];
792
835
  case 5: return [4 /*yield*/, user_collection_1.Users.findById(context === null || context === void 0 ? void 0 : context.id_user)];
793
836
  case 6:
794
- user = _d.sent();
795
- isSuperAdmin = !!((_b = user === null || user === void 0 ? void 0 : user.roles) === null || _b === void 0 ? void 0 : _b.super_admin);
837
+ user = _f.sent();
838
+ isSuperAdmin = !!((_d = user === null || user === void 0 ? void 0 : user.roles) === null || _d === void 0 ? void 0 : _d.super_admin);
796
839
  hasInvoiceAccess = userHasInvoiceAccess(user);
797
- customerId = normalizeOptionalString((_c = user === null || user === void 0 ? void 0 : user.other) === null || _c === void 0 ? void 0 : _c.id_customer);
840
+ customerId = normalizeOptionalString((_e = user === null || user === void 0 ? void 0 : user.other) === null || _e === void 0 ? void 0 : _e.id_customer);
798
841
  return [4 /*yield*/, ensureConversation(input, 'codex')];
799
842
  case 7:
800
- conversation = _d.sent();
843
+ conversation = _f.sent();
801
844
  now = new Date();
802
845
  attachments = Array.isArray(input.attachments) ? input.attachments : [];
803
846
  return [4 /*yield*/, readAttachmentContents(attachments)];
804
847
  case 8:
805
- attachmentData = _d.sent();
848
+ attachmentData = _f.sent();
806
849
  historyLimit = normalizeHistoryLimit(input.max_history);
807
850
  if (!(historyLimit > 0)) return [3 /*break*/, 10];
808
851
  return [4 /*yield*/, ai_terminal_message_collection_1.AiTerminalMessages.find({ id_conversation: conversation._id, role: { $in: ['user', 'assistant'] } }, { sort: { createdAt: 1 }, limit: historyLimit * 2 })];
809
852
  case 9:
810
- _a = _d.sent();
853
+ _a = _f.sent();
811
854
  return [3 /*break*/, 11];
812
855
  case 10:
813
856
  _a = [];
814
- _d.label = 11;
857
+ _f.label = 11;
815
858
  case 11:
816
859
  history = _a;
817
860
  historyLines = [];
@@ -822,10 +865,11 @@ function executeAiAssistantCodexRun(payload, context) {
822
865
  historyLines.push("".concat(role, ": ").concat(content));
823
866
  }
824
867
  });
825
- prompt = buildAssistantCodexPrompt(message, attachmentData.promptText, historyLines.join('\n'), buildAssistantContext(input, { isSuperAdmin: isSuperAdmin, hasInvoiceAccess: hasInvoiceAccess, customerId: customerId }));
868
+ assistantContext = buildAssistantContext(input, { isSuperAdmin: isSuperAdmin, hasInvoiceAccess: hasInvoiceAccess, customerId: customerId });
869
+ prompt = buildAssistantCodexPrompt(message, attachmentData.promptText, historyLines.join('\n'), assistantContext);
826
870
  return [4 /*yield*/, resolveAssistantWorkspaceRoot()];
827
871
  case 12:
828
- workspaceRoot = _d.sent();
872
+ workspaceRoot = _f.sent();
829
873
  codexConfig = resolveCodexSettings();
830
874
  runOptions = {
831
875
  timeoutMs: resolveCodexTimeoutMs(),
@@ -842,8 +886,48 @@ function executeAiAssistantCodexRun(payload, context) {
842
886
  };
843
887
  return [4 /*yield*/, runCodexInWorkerThread(prompt, runOptions, codexConfig)];
844
888
  case 13:
845
- responseText = _d.sent();
846
- assistantContent = sanitizeAssistantResponse(responseText);
889
+ responseText = _f.sent();
890
+ directive = extractAssistantMongoDirective(responseText);
891
+ cleanedResponseText = (directive === null || directive === void 0 ? void 0 : directive.cleaned) || responseText;
892
+ assistantContent = sanitizeAssistantResponse(cleanedResponseText);
893
+ toolResult = null;
894
+ if (!((directive === null || directive === void 0 ? void 0 : directive.payload) && AI_ASSISTANT_TOOL_MAX_STEPS > 0)) return [3 /*break*/, 24];
895
+ toolRequest = buildAssistantToolRequest(directive, input);
896
+ _f.label = 14;
897
+ case 14:
898
+ _f.trys.push([14, 23, , 24]);
899
+ if (!(directive.type === 'aggregate')) return [3 /*break*/, 16];
900
+ return [4 /*yield*/, executeAiAssistantMongoAggregate(toolRequest, context)];
901
+ case 15:
902
+ _b = _f.sent();
903
+ return [3 /*break*/, 18];
904
+ case 16: return [4 /*yield*/, executeAiAssistantMongoRead(toolRequest, context)];
905
+ case 17:
906
+ _b = _f.sent();
907
+ _f.label = 18;
908
+ case 18:
909
+ toolResponse = _b;
910
+ toolPayload = buildAssistantToolResultPayload(directive, toolResponse);
911
+ toolResult = toolPayload.result;
912
+ followupPrompt = buildAssistantCodexToolFollowupPrompt(message, attachmentData.promptText, historyLines.join('\n'), assistantContext, toolPayload.prompt);
913
+ _f.label = 19;
914
+ case 19:
915
+ _f.trys.push([19, 21, , 22]);
916
+ return [4 /*yield*/, runCodexInWorkerThread(followupPrompt, runOptions, codexConfig)];
917
+ case 20:
918
+ followupText = _f.sent();
919
+ assistantContent = sanitizeAssistantResponse(followupText);
920
+ return [3 /*break*/, 22];
921
+ case 21:
922
+ _c = _f.sent();
923
+ assistantContent = buildAssistantToolFallbackResponse(toolPayload.result);
924
+ return [3 /*break*/, 22];
925
+ case 22: return [3 /*break*/, 24];
926
+ case 23:
927
+ error_1 = _f.sent();
928
+ assistantContent = buildAssistantToolErrorMessage(error_1, directive, toolRequest);
929
+ return [3 /*break*/, 24];
930
+ case 24:
847
931
  userDoc = {
848
932
  id_conversation: conversation._id,
849
933
  role: 'user',
@@ -862,34 +946,34 @@ function executeAiAssistantCodexRun(payload, context) {
862
946
  createdAt: now,
863
947
  updatedAt: now
864
948
  };
949
+ if (toolResult) {
950
+ assistantDoc.metadata = __assign(__assign({}, (assistantDoc.metadata || {})), { tool_result: toolResult });
951
+ }
865
952
  return [4 /*yield*/, ai_terminal_message_collection_1.AiTerminalMessages.insertOne(userDoc)];
866
- case 14:
867
- _d.sent();
953
+ case 25:
954
+ _f.sent();
868
955
  return [4 /*yield*/, ai_terminal_message_collection_1.AiTerminalMessages.insertOne(assistantDoc)];
869
- case 15:
870
- insertResult = _d.sent();
956
+ case 26:
957
+ insertResult = _f.sent();
871
958
  return [4 /*yield*/, touchConversation(conversation._id, now, insertResult._id)];
872
- case 16:
873
- _d.sent();
874
- if (!(input.delete_files_after_run !== false)) return [3 /*break*/, 18];
959
+ case 27:
960
+ _f.sent();
961
+ if (!(input.delete_files_after_run !== false)) return [3 /*break*/, 29];
875
962
  return [4 /*yield*/, cleanupAttachments(attachmentData.attachments)];
876
- case 17:
877
- _d.sent();
878
- _d.label = 18;
879
- case 18: return [2 /*return*/, {
880
- conversation: conversation,
881
- message: assistantDoc
882
- }];
963
+ case 28:
964
+ _f.sent();
965
+ _f.label = 29;
966
+ case 29: return [2 /*return*/, __assign({ conversation: conversation, message: assistantDoc }, (toolResult ? { tool_result: toolResult } : {}))];
883
967
  }
884
968
  });
885
969
  });
886
970
  }
887
971
  function executeAiAssistantMongoRead(payload, context) {
888
972
  return __awaiter(this, void 0, void 0, function () {
889
- var input, collection, _a, user, isSuperAdmin, customerId, dbName, db, baseQuery, userId, normalizedClient, shouldScopeByClient, _b, clientScopedQuery, scopedQuery, normalized, documents, total, sanitizedDocuments;
890
- var _c;
891
- return __generator(this, function (_d) {
892
- switch (_d.label) {
973
+ var input, collection, _a, user, isSuperAdmin, customerId, dbName, db, baseQuery, userId, normalizedClient, shouldScopeByClient, _b, clientScopedQuery, scopedQuery, normalized, documents, total, sanitizedDocuments, includeIds, display;
974
+ var _c, _d;
975
+ return __generator(this, function (_e) {
976
+ switch (_e.label) {
893
977
  case 0:
894
978
  input = payload || {};
895
979
  collection = normalizeOptionalString(input.collection);
@@ -898,7 +982,7 @@ function executeAiAssistantMongoRead(payload, context) {
898
982
  }
899
983
  return [4 /*yield*/, ensureAssistantReadAccess(context, input.permissionView, collection)];
900
984
  case 1:
901
- _a = _d.sent(), user = _a.user, isSuperAdmin = _a.isSuperAdmin;
985
+ _a = _e.sent(), user = _a.user, isSuperAdmin = _a.isSuperAdmin;
902
986
  if (!isSuperAdmin && AI_ASSISTANT_BLOCKED_COLLECTIONS.has(collection)) {
903
987
  throw new Error('AI assistant mongo read: Access denied.');
904
988
  }
@@ -919,11 +1003,11 @@ function executeAiAssistantMongoRead(payload, context) {
919
1003
  if (!(!isSuperAdmin && normalizedClient)) return [3 /*break*/, 3];
920
1004
  return [4 /*yield*/, collectionHasClientIndex(db, dbName, collection)];
921
1005
  case 2:
922
- _b = _d.sent();
1006
+ _b = _e.sent();
923
1007
  return [3 /*break*/, 4];
924
1008
  case 3:
925
1009
  _b = false;
926
- _d.label = 4;
1010
+ _e.label = 4;
927
1011
  case 4:
928
1012
  shouldScopeByClient = _b;
929
1013
  clientScopedQuery = shouldScopeByClient
@@ -933,18 +1017,27 @@ function executeAiAssistantMongoRead(payload, context) {
933
1017
  normalized = normalizeAssistantFindOptions(input.options);
934
1018
  return [4 /*yield*/, db.collection(collection).find(scopedQuery, normalized.findOptions).toArray()];
935
1019
  case 5:
936
- documents = _d.sent();
1020
+ documents = _e.sent();
937
1021
  total = null;
938
1022
  if (!normalized.includeTotal) return [3 /*break*/, 7];
939
1023
  return [4 /*yield*/, db.collection(collection).countDocuments(scopedQuery)];
940
1024
  case 6:
941
- total = _d.sent();
942
- _d.label = 7;
1025
+ total = _e.sent();
1026
+ _e.label = 7;
943
1027
  case 7:
944
1028
  sanitizedDocuments = isSuperAdmin
945
1029
  ? documents
946
1030
  : documents.map(function (doc) { return redactSensitiveFields((0, common_1.deepCopy)(doc)); });
947
- return [2 /*return*/, __assign({ documents: sanitizedDocuments, total: total }, (isSuperAdmin ? {
1031
+ includeIds = ((_d = input.options) === null || _d === void 0 ? void 0 : _d.includeIds) === true;
1032
+ display = buildDisplayTable(sanitizedDocuments, {
1033
+ includeIds: includeIds,
1034
+ maxColumns: AI_ASSISTANT_DISPLAY_MAX_COLUMNS,
1035
+ maxRows: normalized.findOptions.limit
1036
+ });
1037
+ if (total !== null) {
1038
+ display.total = total;
1039
+ }
1040
+ return [2 /*return*/, __assign({ documents: sanitizedDocuments, total: total, display: display }, (isSuperAdmin ? {
948
1041
  debug: {
949
1042
  collection: collection,
950
1043
  database: dbName,
@@ -959,10 +1052,10 @@ function executeAiAssistantMongoRead(payload, context) {
959
1052
  }
960
1053
  function executeAiAssistantMongoAggregate(payload, context) {
961
1054
  return __awaiter(this, void 0, void 0, function () {
962
- var input, collection, _a, user, isSuperAdmin, customerId, dbName, db, baseQuery, userId, normalizedClient, shouldScopeByClient, _b, clientScopedQuery, scopedQuery, normalizedPipeline, pipelineWithScope, normalizedOptions, limitedPipeline, documents, executedPipeline, fallbackMeta, fallback, fallbackPipeline, fallbackDocs, unwindFallback, fallbackPipeline, fallbackDocs, sanitizedDocuments;
963
- var _c;
964
- return __generator(this, function (_d) {
965
- switch (_d.label) {
1055
+ var input, collection, _a, user, isSuperAdmin, customerId, dbName, db, baseQuery, userId, normalizedClient, shouldScopeByClient, _b, clientScopedQuery, scopedQuery, normalizedPipeline, pipelineWithScope, normalizedOptions, limitedPipeline, documents, executedPipeline, fallbackMeta, fallback, fallbackPipeline, fallbackDocs, unwindFallback, fallbackPipeline, fallbackDocs, sanitizedDocuments, includeIds, display;
1056
+ var _c, _d;
1057
+ return __generator(this, function (_e) {
1058
+ switch (_e.label) {
966
1059
  case 0:
967
1060
  input = payload || {};
968
1061
  collection = normalizeOptionalString(input.collection);
@@ -971,7 +1064,7 @@ function executeAiAssistantMongoAggregate(payload, context) {
971
1064
  }
972
1065
  return [4 /*yield*/, ensureAssistantReadAccess(context, input.permissionView, collection)];
973
1066
  case 1:
974
- _a = _d.sent(), user = _a.user, isSuperAdmin = _a.isSuperAdmin;
1067
+ _a = _e.sent(), user = _a.user, isSuperAdmin = _a.isSuperAdmin;
975
1068
  if (!isSuperAdmin && AI_ASSISTANT_BLOCKED_COLLECTIONS.has(collection)) {
976
1069
  throw new Error('AI assistant mongo aggregate: Access denied.');
977
1070
  }
@@ -992,11 +1085,11 @@ function executeAiAssistantMongoAggregate(payload, context) {
992
1085
  if (!(!isSuperAdmin && normalizedClient)) return [3 /*break*/, 3];
993
1086
  return [4 /*yield*/, collectionHasClientIndex(db, dbName, collection)];
994
1087
  case 2:
995
- _b = _d.sent();
1088
+ _b = _e.sent();
996
1089
  return [3 /*break*/, 4];
997
1090
  case 3:
998
1091
  _b = false;
999
- _d.label = 4;
1092
+ _e.label = 4;
1000
1093
  case 4:
1001
1094
  shouldScopeByClient = _b;
1002
1095
  clientScopedQuery = shouldScopeByClient
@@ -1014,7 +1107,7 @@ function executeAiAssistantMongoAggregate(payload, context) {
1014
1107
  .aggregate(limitedPipeline, normalizedOptions.aggregateOptions)
1015
1108
  .toArray()];
1016
1109
  case 5:
1017
- documents = _d.sent();
1110
+ documents = _e.sent();
1018
1111
  executedPipeline = limitedPipeline;
1019
1112
  fallbackMeta = {};
1020
1113
  if (!!documents.length) return [3 /*break*/, 7];
@@ -1027,13 +1120,13 @@ function executeAiAssistantMongoAggregate(payload, context) {
1027
1120
  .aggregate(fallbackPipeline, normalizedOptions.aggregateOptions)
1028
1121
  .toArray()];
1029
1122
  case 6:
1030
- fallbackDocs = _d.sent();
1123
+ fallbackDocs = _e.sent();
1031
1124
  if (fallbackDocs.length) {
1032
1125
  documents = fallbackDocs;
1033
1126
  executedPipeline = fallbackPipeline;
1034
1127
  fallbackMeta.dateField.used = true;
1035
1128
  }
1036
- _d.label = 7;
1129
+ _e.label = 7;
1037
1130
  case 7:
1038
1131
  if (!(documents.length <= 1)) return [3 /*break*/, 9];
1039
1132
  unwindFallback = resolveAggregateUnwindFallback(executedPipeline);
@@ -1045,18 +1138,24 @@ function executeAiAssistantMongoAggregate(payload, context) {
1045
1138
  .aggregate(fallbackPipeline, normalizedOptions.aggregateOptions)
1046
1139
  .toArray()];
1047
1140
  case 8:
1048
- fallbackDocs = _d.sent();
1141
+ fallbackDocs = _e.sent();
1049
1142
  if (fallbackDocs.length > documents.length) {
1050
1143
  documents = fallbackDocs;
1051
1144
  executedPipeline = fallbackPipeline;
1052
1145
  fallbackMeta.unwind.used = true;
1053
1146
  }
1054
- _d.label = 9;
1147
+ _e.label = 9;
1055
1148
  case 9:
1056
1149
  sanitizedDocuments = isSuperAdmin
1057
1150
  ? documents
1058
1151
  : documents.map(function (doc) { return redactSensitiveFields((0, common_1.deepCopy)(doc)); });
1059
- return [2 /*return*/, __assign({ documents: sanitizedDocuments }, (isSuperAdmin ? {
1152
+ includeIds = ((_d = input.options) === null || _d === void 0 ? void 0 : _d.includeIds) === true;
1153
+ display = buildDisplayTable(sanitizedDocuments, {
1154
+ includeIds: includeIds,
1155
+ maxColumns: AI_ASSISTANT_DISPLAY_MAX_COLUMNS,
1156
+ maxRows: normalizedOptions.limit || sanitizedDocuments.length
1157
+ });
1158
+ return [2 /*return*/, __assign({ documents: sanitizedDocuments, display: display }, (isSuperAdmin ? {
1060
1159
  debug: {
1061
1160
  collection: collection,
1062
1161
  database: dbName,
@@ -1071,6 +1170,478 @@ function executeAiAssistantMongoAggregate(payload, context) {
1071
1170
  });
1072
1171
  });
1073
1172
  }
1173
+ function extractAssistantMongoDirective(content) {
1174
+ var lines = String(content || '').split('\n');
1175
+ var directiveIndex = -1;
1176
+ var directiveLine = '';
1177
+ var directiveType = null;
1178
+ var directiveIndexes = new Set();
1179
+ lines.forEach(function (line, index) {
1180
+ var normalized = line.trim().replace(/^[-*]+\s*/, '');
1181
+ var upper = normalized.toUpperCase();
1182
+ if (upper.startsWith('MONGO_READ:')) {
1183
+ directiveIndexes.add(index);
1184
+ directiveIndex = index;
1185
+ directiveLine = normalized;
1186
+ directiveType = 'read';
1187
+ return;
1188
+ }
1189
+ if (upper.startsWith('MONGO_AGG:') || upper.startsWith('MONGO_AGGREGATE:')) {
1190
+ directiveIndexes.add(index);
1191
+ directiveIndex = index;
1192
+ directiveLine = normalized;
1193
+ directiveType = 'aggregate';
1194
+ }
1195
+ });
1196
+ if (directiveIndex === -1 || !directiveType) {
1197
+ return null;
1198
+ }
1199
+ var colonIndex = directiveLine.indexOf(':');
1200
+ var after = colonIndex >= 0 ? directiveLine.slice(colonIndex + 1).trim() : '';
1201
+ var parsed = after ? parseJsonObject(after) : null;
1202
+ var payload = parsed && typeof parsed === 'object' ? parsed : null;
1203
+ var cleaned = lines.filter(function (_, index) { return !directiveIndexes.has(index); }).join('\n').trim();
1204
+ return {
1205
+ type: directiveType,
1206
+ payload: payload,
1207
+ cleaned: cleaned,
1208
+ rawLine: directiveLine
1209
+ };
1210
+ }
1211
+ function buildAssistantToolRequest(directive, payload) {
1212
+ var _a;
1213
+ var base = directive.payload && typeof directive.payload === 'object' ? directive.payload : {};
1214
+ var request = __assign({}, base);
1215
+ var route = normalizeOptionalString((_a = payload === null || payload === void 0 ? void 0 : payload.context) === null || _a === void 0 ? void 0 : _a.route);
1216
+ if (!request.permissionView && route) {
1217
+ request.permissionView = route;
1218
+ }
1219
+ if (!request.id_client) {
1220
+ var idClient = normalizeOptionalString(payload === null || payload === void 0 ? void 0 : payload.id_client);
1221
+ if (idClient) {
1222
+ request.id_client = idClient;
1223
+ }
1224
+ }
1225
+ if (!request.mongo && (payload === null || payload === void 0 ? void 0 : payload.mongo)) {
1226
+ request.mongo = payload.mongo;
1227
+ }
1228
+ return request;
1229
+ }
1230
+ function buildAssistantToolResultPayload(directive, toolResponse) {
1231
+ var _a;
1232
+ var directivePayload = directive.payload || {};
1233
+ var documents = Array.isArray(toolResponse === null || toolResponse === void 0 ? void 0 : toolResponse.documents) ? toolResponse.documents : [];
1234
+ var includeIds = ((_a = directivePayload === null || directivePayload === void 0 ? void 0 : directivePayload.options) === null || _a === void 0 ? void 0 : _a.includeIds) === true;
1235
+ var display = (toolResponse === null || toolResponse === void 0 ? void 0 : toolResponse.display) && typeof toolResponse.display === 'object'
1236
+ ? toolResponse.display
1237
+ : buildDisplayTable(documents, {
1238
+ includeIds: includeIds,
1239
+ maxColumns: AI_ASSISTANT_DISPLAY_MAX_COLUMNS,
1240
+ maxRows: AI_ASSISTANT_DISPLAY_PREVIEW_MAX_ROWS
1241
+ });
1242
+ var trimmedDisplay = trimDisplayTable(display, {
1243
+ maxColumns: AI_ASSISTANT_DISPLAY_MAX_COLUMNS,
1244
+ maxRows: AI_ASSISTANT_DISPLAY_PREVIEW_MAX_ROWS
1245
+ });
1246
+ var total = typeof (toolResponse === null || toolResponse === void 0 ? void 0 : toolResponse.total) === 'number' ? toolResponse.total : null;
1247
+ var rowCount = documents.length || trimmedDisplay.rowCount;
1248
+ var collection = normalizeOptionalString(directivePayload === null || directivePayload === void 0 ? void 0 : directivePayload.collection) || '';
1249
+ var result = {
1250
+ type: directive.type === 'aggregate' ? 'mongo_agg' : 'mongo_read',
1251
+ input: directivePayload,
1252
+ output: {
1253
+ display: trimmedDisplay,
1254
+ total: total !== null ? total : undefined,
1255
+ collection: collection || undefined,
1256
+ rowCount: rowCount,
1257
+ columns: trimmedDisplay.columns,
1258
+ truncated: trimmedDisplay.truncated
1259
+ }
1260
+ };
1261
+ return {
1262
+ result: result,
1263
+ prompt: buildAssistantToolResultPrompt(result)
1264
+ };
1265
+ }
1266
+ function buildAssistantToolResultPrompt(result) {
1267
+ var _a, _b;
1268
+ var lines = ['Tool Result:'];
1269
+ lines.push("Type: ".concat(result.type));
1270
+ if (result.output.collection) {
1271
+ lines.push("Collection: ".concat(result.output.collection));
1272
+ }
1273
+ lines.push("Row count: ".concat(result.output.rowCount));
1274
+ if (typeof result.output.total === 'number') {
1275
+ lines.push("Total: ".concat(result.output.total));
1276
+ }
1277
+ if (Array.isArray(result.output.columns) && result.output.columns.length) {
1278
+ lines.push("Columns: ".concat(result.output.columns.join(', ')));
1279
+ }
1280
+ if ((_b = (_a = result.output.display) === null || _a === void 0 ? void 0 : _a.rows) === null || _b === void 0 ? void 0 : _b.length) {
1281
+ lines.push('Preview:');
1282
+ lines.push(formatDisplayTableMarkdown(result.output.display));
1283
+ }
1284
+ else {
1285
+ lines.push('Preview: (no rows)');
1286
+ }
1287
+ return lines.join('\n');
1288
+ }
1289
+ function buildAssistantCodexToolFollowupPrompt(message, attachmentText, historyText, contextText, toolResultText) {
1290
+ var trimmedContext = normalizeOptionalString(contextText);
1291
+ var contextBlock = trimmedContext ? "\n\nContext:\n".concat(trimmedContext) : '';
1292
+ var trimmedHistory = normalizeOptionalString(historyText);
1293
+ var historyBlock = trimmedHistory ? "\n\nConversation so far:\n".concat(trimmedHistory) : '';
1294
+ var toolBlock = toolResultText ? "\n\nTool Result:\n".concat(toolResultText) : '';
1295
+ var instruction = '\n\nInstruction:\nNow answer the user. Do NOT output another MONGO_* directive. Output plain Markdown. Summarize first, then include a Markdown table.';
1296
+ return "System:\n".concat(AI_ASSISTANT_SYSTEM_PROMPT).concat(contextBlock).concat(historyBlock, "\n\nUser:\n").concat(message).concat(attachmentText || '').concat(toolBlock).concat(instruction).trim();
1297
+ }
1298
+ function buildAssistantToolFallbackResponse(result) {
1299
+ var _a, _b;
1300
+ var lines = ['Here is a data snapshot based on the tool result:'];
1301
+ if (result.output.collection) {
1302
+ lines.push("- Collection: ".concat(result.output.collection));
1303
+ }
1304
+ lines.push("- Rows returned: ".concat(result.output.rowCount));
1305
+ if (typeof result.output.total === 'number') {
1306
+ lines.push("- Total: ".concat(result.output.total));
1307
+ }
1308
+ if ((_b = (_a = result.output.display) === null || _a === void 0 ? void 0 : _a.rows) === null || _b === void 0 ? void 0 : _b.length) {
1309
+ lines.push('');
1310
+ lines.push(formatDisplayTableMarkdown(result.output.display));
1311
+ }
1312
+ return lines.join('\n').trim();
1313
+ }
1314
+ function buildAssistantToolErrorMessage(error, directive, request) {
1315
+ var _a, _b;
1316
+ var rawMessage = normalizeOptionalString(error === null || error === void 0 ? void 0 : error.message) || 'Unable to access data.';
1317
+ var normalized = rawMessage.toLowerCase();
1318
+ var routeHint = normalizeOptionalString(request === null || request === void 0 ? void 0 : request.permissionView)
1319
+ || normalizeOptionalString((_a = directive.payload) === null || _a === void 0 ? void 0 : _a.permissionView);
1320
+ var collection = normalizeOptionalString(request === null || request === void 0 ? void 0 : request.collection) || normalizeOptionalString((_b = directive.payload) === null || _b === void 0 ? void 0 : _b.collection);
1321
+ var routeLine = routeHint
1322
+ ? "Open ".concat(routeHint, " in the app to view this data or request access.")
1323
+ : 'Open the related screen in the app to view this data or request access.';
1324
+ if (!routeHint && collection && requiresInvoicePermission(collection)) {
1325
+ routeLine = 'Open /invoice/list or /report/invoice to view this data or request access.';
1326
+ }
1327
+ if (normalized.includes('permission scope required')) {
1328
+ return "I need a permission scope to access that data. ".concat(routeLine);
1329
+ }
1330
+ if (normalized.includes('access denied')) {
1331
+ if (collection && requiresInvoicePermission(collection)) {
1332
+ return "Invoice access is required to view that data. ".concat(routeLine);
1333
+ }
1334
+ return "You don't have permission to view that data. ".concat(routeLine);
1335
+ }
1336
+ if (normalized.includes('database access denied')) {
1337
+ return "Database access is restricted for that request. ".concat(routeLine);
1338
+ }
1339
+ if (normalized.includes('collection is required')) {
1340
+ return 'I need a valid collection to read from. Please specify which screen or dataset you want.';
1341
+ }
1342
+ return "I couldn't access the requested data. ".concat(routeLine);
1343
+ }
1344
+ function isAssistantIdField(key) {
1345
+ var normalized = String(key || '').trim().toLowerCase();
1346
+ if (!normalized) {
1347
+ return false;
1348
+ }
1349
+ if (normalized === '_id' || normalized === '__v') {
1350
+ return true;
1351
+ }
1352
+ if (normalized.startsWith('id_')) {
1353
+ return true;
1354
+ }
1355
+ var parts = normalized.split('.');
1356
+ var last = parts[parts.length - 1] || '';
1357
+ return last.startsWith('id_');
1358
+ }
1359
+ function isMongoObjectId(value) {
1360
+ if (!value || typeof value !== 'object') {
1361
+ return false;
1362
+ }
1363
+ if (typeof value.toHexString === 'function') {
1364
+ return true;
1365
+ }
1366
+ return value._bsontype === 'ObjectId';
1367
+ }
1368
+ function isPlainObject(value) {
1369
+ return !!value && typeof value === 'object' && Object.prototype.toString.call(value) === '[object Object]';
1370
+ }
1371
+ function normalizeColumnPriorityKey(column) {
1372
+ var trimmed = String(column || '').trim();
1373
+ if (!trimmed) {
1374
+ return '';
1375
+ }
1376
+ var base = trimmed.includes('.') ? trimmed.split('.').pop() || trimmed : trimmed;
1377
+ return base.toLowerCase();
1378
+ }
1379
+ function formatDisplayColumnName(column) {
1380
+ var trimmed = String(column || '').trim();
1381
+ if (!trimmed) {
1382
+ return '';
1383
+ }
1384
+ if (trimmed === '_id') {
1385
+ return 'id';
1386
+ }
1387
+ if (trimmed === '_group') {
1388
+ return 'Group';
1389
+ }
1390
+ var normalized = trimmed.replace(/[\s_]+/g, '').toLowerCase();
1391
+ if (normalized === 'createdat') {
1392
+ return 'Created At';
1393
+ }
1394
+ if (normalized === 'updatedat') {
1395
+ return 'Updated At';
1396
+ }
1397
+ return trimmed;
1398
+ }
1399
+ function truncateDisplayText(value, maxLength) {
1400
+ if (maxLength === void 0) { maxLength = AI_ASSISTANT_DISPLAY_STRING_LIMIT; }
1401
+ if (!value) {
1402
+ return value;
1403
+ }
1404
+ if (value.length <= maxLength) {
1405
+ return value;
1406
+ }
1407
+ var sliceLength = Math.max(0, maxLength - 3);
1408
+ return "".concat(value.slice(0, sliceLength), "...");
1409
+ }
1410
+ function serializeMongoValue(value, maxLength) {
1411
+ if (maxLength === void 0) { maxLength = AI_ASSISTANT_DISPLAY_STRING_LIMIT; }
1412
+ if (value === null || value === undefined) {
1413
+ return null;
1414
+ }
1415
+ if (typeof value === 'number' || typeof value === 'boolean') {
1416
+ return value;
1417
+ }
1418
+ if (typeof value === 'string') {
1419
+ return truncateDisplayText(value, maxLength);
1420
+ }
1421
+ if (value instanceof Date) {
1422
+ return value.toISOString();
1423
+ }
1424
+ if (isMongoObjectId(value)) {
1425
+ try {
1426
+ var hex = typeof value.toHexString === 'function' ? value.toHexString() : String(value);
1427
+ var shortened = hex.length > 8 ? hex.slice(0, 8) : hex;
1428
+ return truncateDisplayText(shortened, maxLength);
1429
+ }
1430
+ catch (_a) {
1431
+ return truncateDisplayText(String(value), maxLength);
1432
+ }
1433
+ }
1434
+ if (Array.isArray(value)) {
1435
+ var preview = value.slice(0, 3).map(function (entry) { return serializeMongoValue(entry, maxLength); });
1436
+ var compact = preview.map(function (entry) { return (entry === null || entry === undefined) ? '' : String(entry); }).join(', ');
1437
+ var suffix = value.length > 3 ? ', ...' : '';
1438
+ return truncateDisplayText("[".concat(compact).concat(suffix, "]"), maxLength);
1439
+ }
1440
+ if (typeof value === 'object') {
1441
+ var text = '';
1442
+ try {
1443
+ text = JSON.stringify(value);
1444
+ }
1445
+ catch (_b) {
1446
+ text = String(value);
1447
+ }
1448
+ if (!text || text === '[object Object]') {
1449
+ try {
1450
+ text = JSON.stringify(value, null, 2);
1451
+ }
1452
+ catch (_c) {
1453
+ text = String(value);
1454
+ }
1455
+ }
1456
+ return truncateDisplayText(text, maxLength);
1457
+ }
1458
+ return truncateDisplayText(String(value), maxLength);
1459
+ }
1460
+ function flattenForTable(doc) {
1461
+ var result = {};
1462
+ if (!doc || typeof doc !== 'object') {
1463
+ return result;
1464
+ }
1465
+ if (Object.prototype.hasOwnProperty.call(doc, '_id')) {
1466
+ var idValue_1 = doc._id;
1467
+ if (idValue_1 !== null && idValue_1 !== undefined) {
1468
+ if (isPlainObject(idValue_1) && !isMongoObjectId(idValue_1) && !(idValue_1 instanceof Date)) {
1469
+ Object.keys(idValue_1).forEach(function (key) {
1470
+ if (!key || Object.prototype.hasOwnProperty.call(result, key)) {
1471
+ return;
1472
+ }
1473
+ result[key] = idValue_1[key];
1474
+ });
1475
+ }
1476
+ else if (!Object.prototype.hasOwnProperty.call(result, '_group')) {
1477
+ result._group = idValue_1;
1478
+ }
1479
+ }
1480
+ }
1481
+ Object.keys(doc).forEach(function (key) {
1482
+ if (key === '_id') {
1483
+ return;
1484
+ }
1485
+ var value = doc[key];
1486
+ if (value === undefined) {
1487
+ return;
1488
+ }
1489
+ if (isPlainObject(value) && !isMongoObjectId(value) && !(value instanceof Date)) {
1490
+ var nestedKeys = Object.keys(value);
1491
+ if (nestedKeys.length && nestedKeys.length <= 6) {
1492
+ nestedKeys.forEach(function (nestedKey) {
1493
+ if (!nestedKey) {
1494
+ return;
1495
+ }
1496
+ var nestedValue = value[nestedKey];
1497
+ if (nestedValue === undefined) {
1498
+ return;
1499
+ }
1500
+ result["".concat(key, ".").concat(nestedKey)] = nestedValue;
1501
+ });
1502
+ return;
1503
+ }
1504
+ }
1505
+ result[key] = value;
1506
+ });
1507
+ return result;
1508
+ }
1509
+ function buildDisplayTable(docs, options) {
1510
+ var rowsRaw = Array.isArray(docs) ? docs.map(function (doc) { return flattenForTable(doc); }) : [];
1511
+ var stats = new Map();
1512
+ rowsRaw.forEach(function (row) {
1513
+ Object.keys(row).forEach(function (key) {
1514
+ var value = row[key];
1515
+ var entry = stats.get(key) || { nonEmpty: 0, objectLike: 0 };
1516
+ if (!isEmptyDisplayValue(value)) {
1517
+ entry.nonEmpty += 1;
1518
+ }
1519
+ if (isDisplayObjectLike(value)) {
1520
+ entry.objectLike += 1;
1521
+ }
1522
+ stats.set(key, entry);
1523
+ });
1524
+ });
1525
+ var columns = Array.from(stats.keys()).filter(function (key) {
1526
+ var entry = stats.get(key);
1527
+ if (!entry || entry.nonEmpty === 0) {
1528
+ return false;
1529
+ }
1530
+ if (!(options === null || options === void 0 ? void 0 : options.includeIds) && isAssistantIdField(key)) {
1531
+ return false;
1532
+ }
1533
+ if (entry.objectLike / Math.max(entry.nonEmpty, 1) > 0.5) {
1534
+ return false;
1535
+ }
1536
+ return true;
1537
+ });
1538
+ if (!columns.length && rowsRaw.length) {
1539
+ columns = Object.keys(rowsRaw[0] || {}).filter(function (key) { return (options === null || options === void 0 ? void 0 : options.includeIds) || !isAssistantIdField(key); });
1540
+ }
1541
+ var priorityFields = (options === null || options === void 0 ? void 0 : options.priorityFields) || AI_ASSISTANT_DISPLAY_PRIORITY_FIELDS;
1542
+ var priorityMap = new Map(priorityFields.map(function (field, index) { return [field.toLowerCase(), index]; }));
1543
+ columns.sort(function (a, b) {
1544
+ var _a, _b, _c, _d;
1545
+ var aPriority = (_a = priorityMap.get(normalizeColumnPriorityKey(a))) !== null && _a !== void 0 ? _a : 999;
1546
+ var bPriority = (_b = priorityMap.get(normalizeColumnPriorityKey(b))) !== null && _b !== void 0 ? _b : 999;
1547
+ if (aPriority !== bPriority) {
1548
+ return aPriority - bPriority;
1549
+ }
1550
+ var aCount = ((_c = stats.get(a)) === null || _c === void 0 ? void 0 : _c.nonEmpty) || 0;
1551
+ var bCount = ((_d = stats.get(b)) === null || _d === void 0 ? void 0 : _d.nonEmpty) || 0;
1552
+ if (aCount !== bCount) {
1553
+ return bCount - aCount;
1554
+ }
1555
+ return a.localeCompare(b);
1556
+ });
1557
+ var maxColumns = typeof (options === null || options === void 0 ? void 0 : options.maxColumns) === 'number'
1558
+ ? Math.max(options.maxColumns, 0)
1559
+ : AI_ASSISTANT_DISPLAY_MAX_COLUMNS;
1560
+ if (maxColumns && columns.length > maxColumns) {
1561
+ columns = columns.slice(0, maxColumns);
1562
+ }
1563
+ var columnLabels = columns.map(function (column) { return formatDisplayColumnName(column) || column; });
1564
+ var maxRows = typeof (options === null || options === void 0 ? void 0 : options.maxRows) === 'number'
1565
+ ? Math.max(options.maxRows, 0)
1566
+ : rowsRaw.length;
1567
+ var limitedRows = maxRows ? rowsRaw.slice(0, maxRows) : [];
1568
+ var rows = limitedRows.map(function (row) {
1569
+ var next = {};
1570
+ columns.forEach(function (column, index) {
1571
+ var label = columnLabels[index] || column;
1572
+ next[label] = serializeMongoValue(row[column]);
1573
+ });
1574
+ return next;
1575
+ });
1576
+ return {
1577
+ columns: columnLabels,
1578
+ rows: rows,
1579
+ rowCount: rowsRaw.length,
1580
+ truncated: rowsRaw.length > maxRows || columns.length > maxColumns,
1581
+ includeIds: (options === null || options === void 0 ? void 0 : options.includeIds) === true
1582
+ };
1583
+ }
1584
+ function trimDisplayTable(display, options) {
1585
+ if (!display || !Array.isArray(display.columns)) {
1586
+ return display;
1587
+ }
1588
+ var rowsSource = Array.isArray(display.rows) ? display.rows : [];
1589
+ var maxColumns = typeof (options === null || options === void 0 ? void 0 : options.maxColumns) === 'number'
1590
+ ? Math.max(options.maxColumns, 0)
1591
+ : display.columns.length;
1592
+ var maxRows = typeof (options === null || options === void 0 ? void 0 : options.maxRows) === 'number'
1593
+ ? Math.max(options.maxRows, 0)
1594
+ : rowsSource.length;
1595
+ var columns = maxColumns ? display.columns.slice(0, maxColumns) : [];
1596
+ var rows = maxRows ? rowsSource.slice(0, maxRows).map(function (row) {
1597
+ var next = {};
1598
+ columns.forEach(function (column) {
1599
+ next[column] = row === null || row === void 0 ? void 0 : row[column];
1600
+ });
1601
+ return next;
1602
+ }) : [];
1603
+ return __assign(__assign({}, display), { columns: columns, rows: rows, truncated: display.truncated || display.columns.length > maxColumns || rowsSource.length > maxRows });
1604
+ }
1605
+ function formatDisplayTableMarkdown(display) {
1606
+ if (!display || !Array.isArray(display.columns) || !display.columns.length) {
1607
+ return '';
1608
+ }
1609
+ var header = "| ".concat(display.columns.join(' | '), " |");
1610
+ var separator = "| ".concat(display.columns.map(function () { return '---'; }).join(' | '), " |");
1611
+ var rows = (display.rows || []).map(function (row) {
1612
+ var cells = display.columns.map(function (column) { return escapeMarkdownCell(row === null || row === void 0 ? void 0 : row[column]); });
1613
+ return "| ".concat(cells.join(' | '), " |");
1614
+ });
1615
+ return __spreadArray([header, separator], __read(rows), false).join('\n').trim();
1616
+ }
1617
+ function escapeMarkdownCell(value) {
1618
+ var raw = value === null || value === undefined ? '' : String(value);
1619
+ return raw.replace(/\|/g, '\\|').replace(/\r?\n/g, ' ').trim();
1620
+ }
1621
+ function isEmptyDisplayValue(value) {
1622
+ if (value === null || value === undefined) {
1623
+ return true;
1624
+ }
1625
+ if (typeof value === 'string') {
1626
+ return !value.trim();
1627
+ }
1628
+ if (Array.isArray(value)) {
1629
+ return value.length === 0;
1630
+ }
1631
+ return false;
1632
+ }
1633
+ function isDisplayObjectLike(value) {
1634
+ if (value === null || value === undefined) {
1635
+ return false;
1636
+ }
1637
+ if (value instanceof Date) {
1638
+ return false;
1639
+ }
1640
+ if (isMongoObjectId(value)) {
1641
+ return false;
1642
+ }
1643
+ return typeof value === 'object';
1644
+ }
1074
1645
  function ensureAssistantReadAccess(context, permissionView, collection) {
1075
1646
  return __awaiter(this, void 0, void 0, function () {
1076
1647
  var idUser, user, isSuperAdmin, normalizedPermission, normalizedCollection;
@@ -1131,7 +1702,7 @@ function resolveAssistantDatabaseName(database, mongoConfig) {
1131
1702
  }
1132
1703
  function normalizeAssistantFindOptions(options) {
1133
1704
  var normalized = options || {};
1134
- var projection = normalized.projection && Object.keys(normalized.projection).length ? normalized.projection : undefined;
1705
+ var projection = sanitizeAssistantProjection(normalized.projection);
1135
1706
  var sort = normalized.sort && Object.keys(normalized.sort).length ? normalized.sort : undefined;
1136
1707
  var limit = typeof normalized.limit === 'number'
1137
1708
  ? Math.min(Math.max(normalized.limit, 0), AI_ASSISTANT_MONGO_MAX_LIMIT)
@@ -1147,6 +1718,33 @@ function normalizeAssistantFindOptions(options) {
1147
1718
  includeTotal: normalized.includeTotal === true
1148
1719
  };
1149
1720
  }
1721
+ function sanitizeAssistantProjection(projection) {
1722
+ if (!projection || typeof projection !== 'object') {
1723
+ return undefined;
1724
+ }
1725
+ var keys = Object.keys(projection);
1726
+ if (!keys.length) {
1727
+ return undefined;
1728
+ }
1729
+ var hasInclude = false;
1730
+ var hasExclude = false;
1731
+ keys.forEach(function (key) {
1732
+ var value = projection[key];
1733
+ if (value === 0 || value === false) {
1734
+ hasExclude = true;
1735
+ }
1736
+ else if (value === 1 || value === true) {
1737
+ hasInclude = true;
1738
+ }
1739
+ });
1740
+ if (hasInclude && !hasExclude) {
1741
+ var onlyIds = keys.every(function (key) { return isAssistantIdField(key); });
1742
+ if (onlyIds) {
1743
+ return undefined;
1744
+ }
1745
+ }
1746
+ return projection;
1747
+ }
1150
1748
  function normalizeAssistantAggregatePipeline(pipeline) {
1151
1749
  if (!Array.isArray(pipeline)) {
1152
1750
  return [];
@@ -1635,7 +2233,7 @@ var CodexWorkerBootstrapError = /** @class */ (function (_super) {
1635
2233
  }(Error));
1636
2234
  function runCodexInWorkerThread(prompt, runOptions, config) {
1637
2235
  return __awaiter(this, void 0, void 0, function () {
1638
- var codexClient, workerPath, codexClient, error_1, codexClient;
2236
+ var codexClient, workerPath, codexClient, error_2, codexClient;
1639
2237
  return __generator(this, function (_a) {
1640
2238
  switch (_a.label) {
1641
2239
  case 0:
@@ -1655,11 +2253,11 @@ function runCodexInWorkerThread(prompt, runOptions, config) {
1655
2253
  return [4 /*yield*/, runCodexInWorkerThreadInternal(workerPath, prompt, runOptions, config)];
1656
2254
  case 6: return [2 /*return*/, _a.sent()];
1657
2255
  case 7:
1658
- error_1 = _a.sent();
1659
- if (!(error_1 instanceof CodexWorkerBootstrapError)) {
1660
- throw error_1;
2256
+ error_2 = _a.sent();
2257
+ if (!(error_2 instanceof CodexWorkerBootstrapError)) {
2258
+ throw error_2;
1661
2259
  }
1662
- console.error('Codex worker bootstrap failed, falling back to in-process run.', error_1);
2260
+ console.error('Codex worker bootstrap failed, falling back to in-process run.', error_2);
1663
2261
  codexClient = getAssistantCodexClient();
1664
2262
  return [4 /*yield*/, codexClient.run(prompt, runOptions)];
1665
2263
  case 8: return [2 /*return*/, _a.sent()];
@@ -1696,7 +2294,7 @@ function runCodexInWorkerThreadInternal(workerPath, prompt, runOptions, config)
1696
2294
  timeoutMs = ((sanitizedOptions === null || sanitizedOptions === void 0 ? void 0 : sanitizedOptions.timeoutMs) || resolveCodexTimeoutMs()) + 15000;
1697
2295
  timeoutController = new AbortController();
1698
2296
  timeoutPromise = (function () { return __awaiter(_this, void 0, void 0, function () {
1699
- var error_2;
2297
+ var error_3;
1700
2298
  return __generator(this, function (_a) {
1701
2299
  switch (_a.label) {
1702
2300
  case 0:
@@ -1706,11 +2304,11 @@ function runCodexInWorkerThreadInternal(workerPath, prompt, runOptions, config)
1706
2304
  _a.sent();
1707
2305
  return [2 /*return*/, { type: 'timeout' }];
1708
2306
  case 2:
1709
- error_2 = _a.sent();
1710
- if ((error_2 === null || error_2 === void 0 ? void 0 : error_2.name) === 'AbortError') {
2307
+ error_3 = _a.sent();
2308
+ if ((error_3 === null || error_3 === void 0 ? void 0 : error_3.name) === 'AbortError') {
1711
2309
  return [2 /*return*/, { type: 'aborted' }];
1712
2310
  }
1713
- throw error_2;
2311
+ throw error_3;
1714
2312
  case 3: return [2 /*return*/];
1715
2313
  }
1716
2314
  });
@@ -2068,7 +2666,7 @@ function sanitizeAssistantResponse(value) {
2068
2666
  if (!raw) {
2069
2667
  return 'I could not generate a response. Please try again.';
2070
2668
  }
2071
- var withoutBlocks = raw.replace(/```[\\s\\S]*?```/g, '').replace(/`([^`]+)`/g, '$1').trim();
2669
+ var withoutBlocks = raw.replace(/```[\s\S]*?```/g, '').replace(/`([^`]+)`/g, '$1').trim();
2072
2670
  var stripEstimated = function (line) { return line.replace(/\bEstimated (human )?hours:\s*.*$/i, '').trimEnd(); };
2073
2671
  var isTentativeQueryLine = function (line) {
2074
2672
  var normalized = line.trim().replace(/^[-*•]+\s*/, '');