@resolveio/server-lib 20.14.4 → 20.14.5

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.
@@ -48,6 +48,7 @@ var __values = (this && this.__values) || function(o) {
48
48
  };
49
49
  Object.defineProperty(exports, "__esModule", { value: true });
50
50
  exports.loadAiTerminalMethods = loadAiTerminalMethods;
51
+ exports.executeAiAssistantMongoRead = executeAiAssistantMongoRead;
51
52
  var fs_1 = require("fs");
52
53
  var os = require("os");
53
54
  var path = require("path");
@@ -67,21 +68,79 @@ var DEFAULT_MAX_ATTACHMENT_CHARS = 12000;
67
68
  var DEFAULT_MAX_TOTAL_ATTACHMENT_CHARS = 40000;
68
69
  var DEFAULT_CODEX_MODEL = 'gpt-5.2-codex';
69
70
  var DEFAULT_CODEX_TIMEOUT_MS = 180000;
71
+ var AI_ASSISTANT_MONGO_DEFAULT_LIMIT = 20;
72
+ var AI_ASSISTANT_MONGO_MAX_LIMIT = 200;
73
+ var AI_ASSISTANT_BLOCKED_COLLECTIONS = new Set([
74
+ 'user-groups',
75
+ 'logged-in-users',
76
+ 'ai-terminal-messages',
77
+ 'ai-terminal-conversations',
78
+ 'openai-usage-ledger',
79
+ 'logs',
80
+ 'notifications',
81
+ 'email-history'
82
+ ]);
83
+ var AI_ASSISTANT_SENSITIVE_FIELDS = [
84
+ 'password',
85
+ 'salt',
86
+ 'hash',
87
+ 'secret',
88
+ 'token',
89
+ 'api_key',
90
+ 'email',
91
+ 'phonenumber',
92
+ 'phone',
93
+ 'ssn',
94
+ 'address',
95
+ 'services',
96
+ 'roles'
97
+ ];
70
98
  var AI_ASSISTANT_SYSTEM_PROMPT = [
71
99
  'You are the ResolveIO in-app AI assistant running with read-only access to the codebase.',
72
- '- Do not provide code snippets, full code, or file contents.',
73
- '- Do not modify files, run destructive commands, or access databases.',
100
+ '- Never share code or file contents. All code is proprietary.',
101
+ '- Do not modify files, run destructive commands, or access databases directly.',
102
+ '- Read-only Mongo access is allowed only via the MONGO_READ directive (see below).',
74
103
  '- Do not access secrets, credentials, or user data.',
75
104
  '- Do not assist with hacking, bypassing security, or abuse.',
76
105
  '- Prefer high-level explanations and point to routes instead of code. Only mention file paths if explicitly requested.',
77
106
  '- 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.',
107
+ '- When asked "why is this happening," respond with: cause, trigger, data source(s), and expected vs actual outcome.',
108
+ '- For troubleshooting, ask 2-3 targeted questions first, then give a short decision tree (If X, do Y; If not, do Z).',
109
+ '- Provide checklists for common tasks, highlighting required fields and common pitfalls.',
110
+ '- If asked "where is this set," give the screen/workflow name and navigation steps to reach it.',
111
+ '- If asked "what changed," summarize release notes if known; if not available, say so and suggest where to check or offer a support ticket.',
112
+ '- Suggest 1-2 related screens or next steps when it helps.',
113
+ '- If access is blocked, name the permission/role needed and how to request it.',
78
114
  '- Avoid vague labels like "Operations app"; use the specific screen/workflow name.',
79
115
  '- Do not mention other client projects or ask which client; stay within the current project context.',
80
116
  '- Respond with a single concise message. Do not add labels like "Customer-facing summary" or "Work ticket summary" and do not include estimated hours.',
117
+ '- 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.',
118
+ '- When giving steps, include navigation guidance like "Go to <screen>" and "Click/Select/Enter <field>" so the user can follow it exactly.',
81
119
  '- If context scope is provided (ex: current page), prioritize that screen in your answer.',
82
120
  '- If a request is out of scope, offer to create a support ticket with a short summary.',
121
+ '- If the user explicitly asks to create/open/file a support ticket, end your response with a single line exactly in this format:',
122
+ '- SUPPORT_TICKET_CREATE: <one-line summary>',
123
+ '- Only include that line when the user clearly wants a ticket created. Do not claim a ticket is created unless you include that line.',
124
+ '- If you need database data to answer, end your response with a single line exactly in this format:',
125
+ '- MONGO_READ: {"collection":"<name>","query":{...},"options":{"projection":{...},"sort":{...},"limit":20},"permissionView":"</route>"}',
126
+ '- For invoice data, set permissionView to an invoice route (ex: /invoice/list or /report/invoice).',
127
+ '- Keep queries minimal, read-only, and avoid user/credential data unless the user is a super admin.',
128
+ '- Assume you are not a super admin unless explicitly told otherwise.',
129
+ '- Only request data when the user has permission for that module; invoice data requires invoice view access.',
130
+ '- If the user lacks permission, answer without data and explain how to view it in the app or request access.',
131
+ '- Use MONGO_READ only to produce summaries/snapshots/health checks (not raw dumps) when permitted.',
132
+ '- When referencing data, summarize it in bullets and avoid raw JSON or dumps.',
83
133
  '- Keep responses concise and use low reasoning effort.'
84
134
  ].join('\n');
135
+ var AI_FORM_PATCH_SYSTEM_PROMPT = [
136
+ 'You are the ResolveIO form patch assistant.',
137
+ '- Your job is to map the user request to the provided form fields.',
138
+ '- Only use the allowed fields and their types.',
139
+ '- Return ONLY valid JSON and nothing else.',
140
+ '- If you cannot map the request, return an empty patch with a short note.',
141
+ '- Use ISO 8601 for dates and true/false for booleans.',
142
+ '- Use medium reasoning effort.'
143
+ ].join('\n');
85
144
  var assistantCodexClient = null;
86
145
  function loadAiTerminalMethods(methodManager) {
87
146
  methodManager.methods({
@@ -267,6 +326,24 @@ function loadAiTerminalMethods(methodManager) {
267
326
  });
268
327
  }
269
328
  },
329
+ aiFormPatch: {
330
+ check: new simpl_schema_1.default({
331
+ payload: {
332
+ type: Object,
333
+ blackbox: true
334
+ }
335
+ }),
336
+ function: function (payload) {
337
+ return __awaiter(this, void 0, void 0, function () {
338
+ return __generator(this, function (_a) {
339
+ switch (_a.label) {
340
+ case 0: return [4 /*yield*/, executeAiFormPatch(payload, this)];
341
+ case 1: return [2 /*return*/, _a.sent()];
342
+ }
343
+ });
344
+ });
345
+ }
346
+ },
270
347
  aiCoderTerminalUploadFile: {
271
348
  check: new simpl_schema_1.default({
272
349
  id_conversation: {
@@ -315,6 +392,24 @@ function loadAiTerminalMethods(methodManager) {
315
392
  });
316
393
  }
317
394
  },
395
+ aiAssistantMongoRead: {
396
+ check: new simpl_schema_1.default({
397
+ payload: {
398
+ type: Object,
399
+ blackbox: true
400
+ }
401
+ }),
402
+ function: function (payload) {
403
+ return __awaiter(this, void 0, void 0, function () {
404
+ return __generator(this, function (_a) {
405
+ switch (_a.label) {
406
+ case 0: return [4 /*yield*/, executeAiAssistantMongoRead(payload, this)];
407
+ case 1: return [2 /*return*/, _a.sent()];
408
+ }
409
+ });
410
+ });
411
+ }
412
+ },
318
413
  aiCoderTerminalDeployTest: {
319
414
  check: new simpl_schema_1.default({
320
415
  id_conversation: {
@@ -483,11 +578,79 @@ function executeAiTerminalRun(payload, context) {
483
578
  });
484
579
  });
485
580
  }
486
- function executeAiAssistantCodexRun(payload, context) {
581
+ function executeAiFormPatch(payload, context) {
487
582
  return __awaiter(this, void 0, void 0, function () {
488
- var input, message, guardrail, conversation_2, now_2, userMsg, assistantMsg, conversation, now, attachments, attachmentData, historyLimit, history, _a, historyLines, prompt, workspaceRoot, codexClient, runOptions, responseText, assistantContent, userDoc, assistantDoc, insertResult;
583
+ var input, message, allowedFields, guardrail, systemPrompt, userPrompt, messages, openaiSettings, client, response, usage, idClient, parsed;
584
+ var _a;
489
585
  return __generator(this, function (_b) {
490
586
  switch (_b.label) {
587
+ case 0:
588
+ input = payload || {};
589
+ message = normalizeOptionalString(input.message);
590
+ if (!message) {
591
+ throw new Error('Message is required.');
592
+ }
593
+ if (!(context === null || context === void 0 ? void 0 : context.id_user)) {
594
+ throw new Error('Unauthorized.');
595
+ }
596
+ allowedFields = normalizeAiFormFields(input.allowed_fields || input.fields);
597
+ if (!allowedFields.length) {
598
+ throw new Error('Allowed fields are required.');
599
+ }
600
+ guardrail = evaluateGuardrails(message);
601
+ if (guardrail === null || guardrail === void 0 ? void 0 : guardrail.blocked) {
602
+ return [2 /*return*/, { error: guardrail.response, blocked: true }];
603
+ }
604
+ systemPrompt = buildAiFormSystemPrompt();
605
+ userPrompt = buildAiFormUserPrompt(message, allowedFields, input.patch_format, input.route);
606
+ messages = [
607
+ { role: 'system', content: systemPrompt },
608
+ { role: 'user', content: userPrompt }
609
+ ];
610
+ openaiSettings = resolveOpenAISettings(input.config || {});
611
+ client = new openai_client_1.OpenAIClient(openaiSettings);
612
+ return [4 /*yield*/, client.chat(messages, { timeoutMs: 60000, responseFormat: 'json_object' })];
613
+ case 1:
614
+ response = _b.sent();
615
+ usage = response.usage || estimateUsage(messages, response.content, response.model || openaiSettings.model);
616
+ idClient = normalizeOptionalString(input.id_client);
617
+ if (!idClient) return [3 /*break*/, 3];
618
+ return [4 /*yield*/, (0, openai_usage_ledger_manager_1.recordOpenAIUsage)({
619
+ id_client: idClient,
620
+ model: response.model || openaiSettings.model || 'unknown',
621
+ input_tokens: usage.inputTokens,
622
+ output_tokens: usage.outputTokens,
623
+ total_tokens: usage.totalTokens,
624
+ category: 'ai-form'
625
+ })];
626
+ case 2:
627
+ _b.sent();
628
+ _b.label = 3;
629
+ case 3:
630
+ parsed = parseJsonObject(response.content);
631
+ if (!parsed || typeof parsed !== 'object') {
632
+ throw new Error('AI form patch response was not valid JSON.');
633
+ }
634
+ return [2 /*return*/, {
635
+ patch: (_a = parsed.patch) !== null && _a !== void 0 ? _a : parsed,
636
+ notes: normalizeOptionalString(parsed.notes) || undefined,
637
+ usage: {
638
+ model: response.model || openaiSettings.model,
639
+ input_tokens: usage.inputTokens,
640
+ output_tokens: usage.outputTokens,
641
+ total_tokens: usage.totalTokens
642
+ }
643
+ }];
644
+ }
645
+ });
646
+ });
647
+ }
648
+ function executeAiAssistantCodexRun(payload, context) {
649
+ return __awaiter(this, void 0, void 0, function () {
650
+ var input, message, guardrail, conversation_2, now_2, userMsg, assistantMsg, user, isSuperAdmin, hasInvoiceAccess, conversation, now, attachments, attachmentData, historyLimit, history, _a, historyLines, prompt, workspaceRoot, codexClient, runOptions, responseText, assistantContent, userDoc, assistantDoc, insertResult;
651
+ var _b;
652
+ return __generator(this, function (_c) {
653
+ switch (_c.label) {
491
654
  case 0:
492
655
  input = payload || {};
493
656
  message = normalizeOptionalString(input.message);
@@ -501,7 +664,7 @@ function executeAiAssistantCodexRun(payload, context) {
501
664
  if (!(guardrail === null || guardrail === void 0 ? void 0 : guardrail.blocked)) return [3 /*break*/, 5];
502
665
  return [4 /*yield*/, ensureConversation(input, 'codex')];
503
666
  case 1:
504
- conversation_2 = _b.sent();
667
+ conversation_2 = _c.sent();
505
668
  now_2 = new Date();
506
669
  userMsg = {
507
670
  id_conversation: conversation_2._id,
@@ -523,36 +686,41 @@ function executeAiAssistantCodexRun(payload, context) {
523
686
  };
524
687
  return [4 /*yield*/, ai_terminal_message_collection_1.AiTerminalMessages.insertOne(userMsg)];
525
688
  case 2:
526
- _b.sent();
689
+ _c.sent();
527
690
  return [4 /*yield*/, ai_terminal_message_collection_1.AiTerminalMessages.insertOne(assistantMsg)];
528
691
  case 3:
529
- _b.sent();
692
+ _c.sent();
530
693
  return [4 /*yield*/, touchConversation(conversation_2._id, now_2)];
531
694
  case 4:
532
- _b.sent();
695
+ _c.sent();
533
696
  return [2 /*return*/, {
534
697
  conversation: conversation_2,
535
698
  message: assistantMsg,
536
699
  guardrails: { blocked: true, reason: guardrail.reason }
537
700
  }];
538
- case 5: return [4 /*yield*/, ensureConversation(input, 'codex')];
701
+ case 5: return [4 /*yield*/, user_collection_1.Users.findById(context === null || context === void 0 ? void 0 : context.id_user)];
539
702
  case 6:
540
- conversation = _b.sent();
703
+ user = _c.sent();
704
+ isSuperAdmin = !!((_b = user === null || user === void 0 ? void 0 : user.roles) === null || _b === void 0 ? void 0 : _b.super_admin);
705
+ hasInvoiceAccess = userHasInvoiceAccess(user);
706
+ return [4 /*yield*/, ensureConversation(input, 'codex')];
707
+ case 7:
708
+ conversation = _c.sent();
541
709
  now = new Date();
542
710
  attachments = Array.isArray(input.attachments) ? input.attachments : [];
543
711
  return [4 /*yield*/, readAttachmentContents(attachments)];
544
- case 7:
545
- attachmentData = _b.sent();
712
+ case 8:
713
+ attachmentData = _c.sent();
546
714
  historyLimit = normalizeHistoryLimit(input.max_history);
547
- if (!(historyLimit > 0)) return [3 /*break*/, 9];
715
+ if (!(historyLimit > 0)) return [3 /*break*/, 10];
548
716
  return [4 /*yield*/, ai_terminal_message_collection_1.AiTerminalMessages.find({ id_conversation: conversation._id, role: { $in: ['user', 'assistant'] } }, { sort: { createdAt: 1 }, limit: historyLimit * 2 })];
549
- case 8:
550
- _a = _b.sent();
551
- return [3 /*break*/, 10];
552
717
  case 9:
553
- _a = [];
554
- _b.label = 10;
718
+ _a = _c.sent();
719
+ return [3 /*break*/, 11];
555
720
  case 10:
721
+ _a = [];
722
+ _c.label = 11;
723
+ case 11:
556
724
  history = _a;
557
725
  historyLines = [];
558
726
  history.forEach(function (entry) {
@@ -562,10 +730,10 @@ function executeAiAssistantCodexRun(payload, context) {
562
730
  historyLines.push("".concat(role, ": ").concat(content));
563
731
  }
564
732
  });
565
- prompt = buildAssistantCodexPrompt(message, attachmentData.promptText, historyLines.join('\n'), buildAssistantContext(input));
733
+ prompt = buildAssistantCodexPrompt(message, attachmentData.promptText, historyLines.join('\n'), buildAssistantContext(input, { isSuperAdmin: isSuperAdmin, hasInvoiceAccess: hasInvoiceAccess }));
566
734
  return [4 /*yield*/, resolveAssistantWorkspaceRoot()];
567
- case 11:
568
- workspaceRoot = _b.sent();
735
+ case 12:
736
+ workspaceRoot = _c.sent();
569
737
  codexClient = getAssistantCodexClient();
570
738
  runOptions = {
571
739
  timeoutMs: resolveCodexTimeoutMs(),
@@ -581,8 +749,8 @@ function executeAiAssistantCodexRun(payload, context) {
581
749
  }
582
750
  };
583
751
  return [4 /*yield*/, codexClient.run(prompt, runOptions)];
584
- case 12:
585
- responseText = _b.sent();
752
+ case 13:
753
+ responseText = _c.sent();
586
754
  assistantContent = sanitizeAssistantResponse(responseText);
587
755
  userDoc = {
588
756
  id_conversation: conversation._id,
@@ -603,20 +771,20 @@ function executeAiAssistantCodexRun(payload, context) {
603
771
  updatedAt: now
604
772
  };
605
773
  return [4 /*yield*/, ai_terminal_message_collection_1.AiTerminalMessages.insertOne(userDoc)];
606
- case 13:
607
- _b.sent();
608
- return [4 /*yield*/, ai_terminal_message_collection_1.AiTerminalMessages.insertOne(assistantDoc)];
609
774
  case 14:
610
- insertResult = _b.sent();
611
- return [4 /*yield*/, touchConversation(conversation._id, now, insertResult._id)];
775
+ _c.sent();
776
+ return [4 /*yield*/, ai_terminal_message_collection_1.AiTerminalMessages.insertOne(assistantDoc)];
612
777
  case 15:
613
- _b.sent();
614
- if (!(input.delete_files_after_run !== false)) return [3 /*break*/, 17];
615
- return [4 /*yield*/, cleanupAttachments(attachmentData.attachments)];
778
+ insertResult = _c.sent();
779
+ return [4 /*yield*/, touchConversation(conversation._id, now, insertResult._id)];
616
780
  case 16:
617
- _b.sent();
618
- _b.label = 17;
619
- case 17: return [2 /*return*/, {
781
+ _c.sent();
782
+ if (!(input.delete_files_after_run !== false)) return [3 /*break*/, 18];
783
+ return [4 /*yield*/, cleanupAttachments(attachmentData.attachments)];
784
+ case 17:
785
+ _c.sent();
786
+ _c.label = 18;
787
+ case 18: return [2 /*return*/, {
620
788
  conversation: conversation,
621
789
  message: assistantDoc
622
790
  }];
@@ -624,6 +792,243 @@ function executeAiAssistantCodexRun(payload, context) {
624
792
  });
625
793
  });
626
794
  }
795
+ function executeAiAssistantMongoRead(payload, context) {
796
+ return __awaiter(this, void 0, void 0, function () {
797
+ var input, collection, _a, user, isSuperAdmin, dbName, db, baseQuery, userId, scopedQuery, normalized, documents, total, sanitizedDocuments;
798
+ return __generator(this, function (_b) {
799
+ switch (_b.label) {
800
+ case 0:
801
+ input = payload || {};
802
+ collection = normalizeOptionalString(input.collection);
803
+ if (!collection) {
804
+ throw new Error('AI assistant mongo read: Collection is required.');
805
+ }
806
+ return [4 /*yield*/, ensureAssistantReadAccess(context, input.permissionView, collection)];
807
+ case 1:
808
+ _a = _b.sent(), user = _a.user, isSuperAdmin = _a.isSuperAdmin;
809
+ if (!isSuperAdmin && AI_ASSISTANT_BLOCKED_COLLECTIONS.has(collection)) {
810
+ throw new Error('AI assistant mongo read: Access denied.');
811
+ }
812
+ dbName = resolveAssistantDatabaseName(input.database, input.mongo);
813
+ db = resolveio_server_app_1.ResolveIOServer.getMongoConnection().db(dbName);
814
+ baseQuery = normalizeMongoQuery(input.query);
815
+ if (!isSuperAdmin && (collection === 'users' || collection === 'user-versions')) {
816
+ userId = normalizeOptionalString(user === null || user === void 0 ? void 0 : user._id);
817
+ if (!userId) {
818
+ throw new Error('AI assistant mongo read: Access denied.');
819
+ }
820
+ baseQuery = {
821
+ $and: [baseQuery, { _id: userId }]
822
+ };
823
+ }
824
+ scopedQuery = applyClientScopeFilter(baseQuery, input.id_client, isSuperAdmin);
825
+ normalized = normalizeAssistantFindOptions(input.options);
826
+ return [4 /*yield*/, db.collection(collection).find(scopedQuery, normalized.findOptions).toArray()];
827
+ case 2:
828
+ documents = _b.sent();
829
+ total = null;
830
+ if (!normalized.includeTotal) return [3 /*break*/, 4];
831
+ return [4 /*yield*/, db.collection(collection).countDocuments(scopedQuery)];
832
+ case 3:
833
+ total = _b.sent();
834
+ _b.label = 4;
835
+ case 4:
836
+ sanitizedDocuments = isSuperAdmin
837
+ ? documents
838
+ : documents.map(function (doc) { return redactSensitiveFields((0, common_1.deepCopy)(doc)); });
839
+ return [2 /*return*/, {
840
+ documents: sanitizedDocuments,
841
+ total: total
842
+ }];
843
+ }
844
+ });
845
+ });
846
+ }
847
+ function ensureAssistantReadAccess(context, permissionView, collection) {
848
+ return __awaiter(this, void 0, void 0, function () {
849
+ var idUser, user, isSuperAdmin, normalizedPermission, normalizedCollection;
850
+ var _a;
851
+ return __generator(this, function (_b) {
852
+ switch (_b.label) {
853
+ case 0:
854
+ idUser = context === null || context === void 0 ? void 0 : context.id_user;
855
+ if (!idUser) {
856
+ throw new Error('AI assistant mongo read: Unauthorized.');
857
+ }
858
+ return [4 /*yield*/, user_collection_1.Users.findById(idUser)];
859
+ case 1:
860
+ user = _b.sent();
861
+ if (!user) {
862
+ throw new Error('AI assistant mongo read: Unauthorized.');
863
+ }
864
+ isSuperAdmin = !!((_a = user === null || user === void 0 ? void 0 : user.roles) === null || _a === void 0 ? void 0 : _a.super_admin);
865
+ normalizedPermission = normalizeOptionalString(permissionView);
866
+ if (!isSuperAdmin) {
867
+ if (!normalizedPermission) {
868
+ throw new Error('AI assistant mongo read: Permission scope required.');
869
+ }
870
+ if (!userHasViewPermission(user, normalizedPermission)) {
871
+ throw new Error('AI assistant mongo read: Access denied.');
872
+ }
873
+ normalizedCollection = normalizeOptionalString(collection);
874
+ if (normalizedCollection && requiresInvoicePermission(normalizedCollection) && !userHasInvoiceAccess(user)) {
875
+ throw new Error('AI assistant mongo read: Access denied.');
876
+ }
877
+ }
878
+ else if (normalizedPermission && !userHasViewPermission(user, normalizedPermission)) {
879
+ throw new Error('AI assistant mongo read: Access denied.');
880
+ }
881
+ return [2 /*return*/, { user: user, isSuperAdmin: isSuperAdmin }];
882
+ }
883
+ });
884
+ });
885
+ }
886
+ function resolveAssistantDatabaseName(database, mongoConfig) {
887
+ var _a, _b;
888
+ var defaultDb = normalizeOptionalString(mongoConfig === null || mongoConfig === void 0 ? void 0 : mongoConfig.database) || ((_a = resolveio_server_app_1.ResolveIOServer.getServerConfig()) === null || _a === void 0 ? void 0 : _a.DATABASE) || '';
889
+ var dbName = normalizeOptionalString(database) || defaultDb;
890
+ if (!dbName) {
891
+ throw new Error('AI assistant mongo read: Database is required.');
892
+ }
893
+ var allowedFromConfig = Array.isArray(mongoConfig === null || mongoConfig === void 0 ? void 0 : mongoConfig.databases)
894
+ ? mongoConfig === null || mongoConfig === void 0 ? void 0 : mongoConfig.databases.map(function (value) { return normalizeOptionalString(value); }).filter(Boolean)
895
+ : [];
896
+ if (allowedFromConfig.length && !allowedFromConfig.includes(dbName)) {
897
+ throw new Error('AI assistant mongo read: Database access denied.');
898
+ }
899
+ var allowedDatabases = ((_b = resolveio_server_app_1.ResolveIOServer.getMongoManager()) === null || _b === void 0 ? void 0 : _b.getWatchedDatabases()) || [];
900
+ if (allowedDatabases.length && !allowedDatabases.includes(dbName)) {
901
+ throw new Error('AI assistant mongo read: Database access denied.');
902
+ }
903
+ return dbName;
904
+ }
905
+ function normalizeAssistantFindOptions(options) {
906
+ var normalized = options || {};
907
+ var projection = normalized.projection && Object.keys(normalized.projection).length ? normalized.projection : undefined;
908
+ var sort = normalized.sort && Object.keys(normalized.sort).length ? normalized.sort : undefined;
909
+ var limit = typeof normalized.limit === 'number'
910
+ ? Math.min(Math.max(normalized.limit, 0), AI_ASSISTANT_MONGO_MAX_LIMIT)
911
+ : AI_ASSISTANT_MONGO_DEFAULT_LIMIT;
912
+ var skip = typeof normalized.skip === 'number' ? Math.max(normalized.skip, 0) : 0;
913
+ return {
914
+ findOptions: {
915
+ projection: projection,
916
+ sort: sort,
917
+ limit: limit,
918
+ skip: skip
919
+ },
920
+ includeTotal: normalized.includeTotal === true
921
+ };
922
+ }
923
+ function normalizeMongoQuery(query) {
924
+ var normalized = query && typeof query === 'object' ? query : {};
925
+ if (containsForbiddenMongoOperators(normalized)) {
926
+ throw new Error('AI assistant mongo read: Query contains restricted operators.');
927
+ }
928
+ return normalized;
929
+ }
930
+ function containsForbiddenMongoOperators(value) {
931
+ var e_1, _a;
932
+ if (!value || typeof value !== 'object') {
933
+ return false;
934
+ }
935
+ if (Array.isArray(value)) {
936
+ return value.some(function (entry) { return containsForbiddenMongoOperators(entry); });
937
+ }
938
+ try {
939
+ for (var _b = __values(Object.keys(value)), _c = _b.next(); !_c.done; _c = _b.next()) {
940
+ var key = _c.value;
941
+ var normalized = key.toLowerCase();
942
+ if (normalized === '$where' || normalized === '$function' || normalized === '$accumulator') {
943
+ return true;
944
+ }
945
+ if (containsForbiddenMongoOperators(value[key])) {
946
+ return true;
947
+ }
948
+ }
949
+ }
950
+ catch (e_1_1) { e_1 = { error: e_1_1 }; }
951
+ finally {
952
+ try {
953
+ if (_c && !_c.done && (_a = _b.return)) _a.call(_b);
954
+ }
955
+ finally { if (e_1) throw e_1.error; }
956
+ }
957
+ return false;
958
+ }
959
+ function applyClientScopeFilter(query, idClient, isSuperAdmin) {
960
+ if (isSuperAdmin === void 0) { isSuperAdmin = false; }
961
+ if (isSuperAdmin) {
962
+ return query;
963
+ }
964
+ var normalizedClient = normalizeOptionalString(idClient);
965
+ if (!normalizedClient) {
966
+ return query;
967
+ }
968
+ return {
969
+ $and: [query, { id_client: normalizedClient }]
970
+ };
971
+ }
972
+ function userHasViewPermission(user, view) {
973
+ var _a, _b, _c;
974
+ if (!user || !view) {
975
+ return false;
976
+ }
977
+ if ((_a = user.roles) === null || _a === void 0 ? void 0 : _a.super_admin) {
978
+ return true;
979
+ }
980
+ var groups = Array.isArray((_b = user.roles) === null || _b === void 0 ? void 0 : _b.groups) ? user.roles.groups : [];
981
+ var miscs = Array.isArray((_c = user.roles) === null || _c === void 0 ? void 0 : _c.miscs) ? user.roles.miscs : [];
982
+ if (groups.some(function (group) { return Array.isArray(group.views) && group.views.some(function (v) { return v.startsWith(view); }); })) {
983
+ return true;
984
+ }
985
+ if (miscs.some(function (v) { return v.startsWith(view); })) {
986
+ return true;
987
+ }
988
+ if (groups.some(function (group) { return group.name === view; })) {
989
+ return true;
990
+ }
991
+ return false;
992
+ }
993
+ function userHasAnyViewPermission(user, views) {
994
+ if (!user || !Array.isArray(views)) {
995
+ return false;
996
+ }
997
+ return views.some(function (view) { return view && userHasViewPermission(user, view); });
998
+ }
999
+ function userHasInvoiceAccess(user) {
1000
+ return userHasAnyViewPermission(user, ['/invoice', '/report/invoice']);
1001
+ }
1002
+ function requiresInvoicePermission(collection) {
1003
+ var normalized = normalizeOptionalString(collection).toLowerCase();
1004
+ if (!normalized) {
1005
+ return false;
1006
+ }
1007
+ return normalized.includes('invoice');
1008
+ }
1009
+ function redactSensitiveFields(value) {
1010
+ if (Array.isArray(value)) {
1011
+ return value.map(function (entry) { return redactSensitiveFields(entry); });
1012
+ }
1013
+ if (!value || typeof value !== 'object') {
1014
+ return value;
1015
+ }
1016
+ var result = {};
1017
+ Object.keys(value).forEach(function (key) {
1018
+ if (shouldRedactField(key)) {
1019
+ return;
1020
+ }
1021
+ result[key] = redactSensitiveFields(value[key]);
1022
+ });
1023
+ return result;
1024
+ }
1025
+ function shouldRedactField(key) {
1026
+ var normalized = String(key || '').trim().toLowerCase();
1027
+ if (!normalized) {
1028
+ return false;
1029
+ }
1030
+ return AI_ASSISTANT_SENSITIVE_FIELDS.some(function (field) { return normalized === field || normalized.includes(field); });
1031
+ }
627
1032
  function resolveCodexTimeoutMs() {
628
1033
  var config = resolveio_server_app_1.ResolveIOServer.getServerConfig() || {};
629
1034
  var raw = normalizeOptionalNumber(config['AI_ASSISTANT_CODEX_TIMEOUT_MS'] || process.env.AI_ASSISTANT_CODEX_TIMEOUT_MS);
@@ -702,13 +1107,19 @@ function buildAssistantCodexPrompt(message, attachmentText, historyText, context
702
1107
  var historyBlock = trimmedHistory ? "\n\nConversation so far:\n".concat(trimmedHistory) : '';
703
1108
  return "System:\n".concat(AI_ASSISTANT_SYSTEM_PROMPT).concat(contextBlock).concat(historyBlock, "\n\nUser:\n").concat(message).concat(attachmentText || '').trim();
704
1109
  }
705
- function buildAssistantContext(input) {
706
- var _a, _b;
1110
+ function buildAssistantContext(input, userContext) {
1111
+ var _a, _b, _c, _d, _e, _f;
707
1112
  var lines = [];
708
1113
  var idApp = normalizeOptionalString(input === null || input === void 0 ? void 0 : input.id_app);
709
1114
  if (idApp) {
710
1115
  lines.push("Current app id: ".concat(idApp));
711
1116
  }
1117
+ if (typeof (userContext === null || userContext === void 0 ? void 0 : userContext.isSuperAdmin) === 'boolean') {
1118
+ lines.push("User is super admin: ".concat(userContext.isSuperAdmin ? 'yes' : 'no'));
1119
+ }
1120
+ if (typeof (userContext === null || userContext === void 0 ? void 0 : userContext.hasInvoiceAccess) === 'boolean') {
1121
+ lines.push("User has invoice access: ".concat(userContext.hasInvoiceAccess ? 'yes' : 'no'));
1122
+ }
712
1123
  var contextMode = normalizeOptionalString((_a = input === null || input === void 0 ? void 0 : input.context) === null || _a === void 0 ? void 0 : _a.mode);
713
1124
  var contextRoute = normalizeOptionalString((_b = input === null || input === void 0 ? void 0 : input.context) === null || _b === void 0 ? void 0 : _b.route);
714
1125
  if (contextMode) {
@@ -717,6 +1128,21 @@ function buildAssistantContext(input) {
717
1128
  if (contextRoute) {
718
1129
  lines.push("Current page route: ".concat(contextRoute));
719
1130
  }
1131
+ var mongoDb = normalizeOptionalString((_c = input === null || input === void 0 ? void 0 : input.mongo) === null || _c === void 0 ? void 0 : _c.database);
1132
+ var mongoDbs = Array.isArray((_d = input === null || input === void 0 ? void 0 : input.mongo) === null || _d === void 0 ? void 0 : _d.databases)
1133
+ ? input.mongo.databases.map(function (value) { return normalizeOptionalString(value); }).filter(Boolean)
1134
+ : [];
1135
+ var mongoAccess = normalizeOptionalString((_e = input === null || input === void 0 ? void 0 : input.mongo) === null || _e === void 0 ? void 0 : _e.access)
1136
+ || (((_f = input === null || input === void 0 ? void 0 : input.mongo) === null || _f === void 0 ? void 0 : _f.readonly) === true ? 'read' : '');
1137
+ if (mongoDb) {
1138
+ lines.push("Mongo database: ".concat(mongoDb));
1139
+ }
1140
+ if (mongoDbs.length) {
1141
+ lines.push("Mongo databases allowed: ".concat(mongoDbs.join(', ')));
1142
+ }
1143
+ if (mongoAccess) {
1144
+ lines.push("Mongo access: ".concat(mongoAccess));
1145
+ }
720
1146
  return lines.join('\n');
721
1147
  }
722
1148
  function sanitizeAssistantResponse(value) {
@@ -756,18 +1182,18 @@ function sanitizeAssistantResponse(value) {
756
1182
  return cleaned;
757
1183
  }
758
1184
  function evaluateAssistantGuardrails(message) {
759
- var e_1, _a;
1185
+ var e_2, _a;
760
1186
  var normalized = String(message || '').toLowerCase();
761
1187
  var patterns = [
762
1188
  {
763
1189
  pattern: /\b(show|share|paste|provide|dump|output)\b.*\b(code|snippet|file|function|class|script|sql)\b/i,
764
1190
  reason: 'Code sharing is restricted.',
765
- response: 'I can’t share code. I can explain behavior and point you to file paths or routes.'
1191
+ response: 'I can’t share code or file contents. All code is proprietary. I can explain behavior and point you to screens or routes.'
766
1192
  },
767
1193
  {
768
1194
  pattern: /\b(write|generate|create|implement|fix)\b.*\b(code|script|function|class|endpoint|sql)\b/i,
769
1195
  reason: 'Code generation is restricted.',
770
- response: 'I can’t generate code. I can explain how the feature works and where to look in the project.'
1196
+ response: 'I can’t generate code. All code is proprietary. I can explain how the feature works and where to look in the product.'
771
1197
  },
772
1198
  {
773
1199
  pattern: /\b(credentials?|passwords?|secrets?|tokens?)\b/i,
@@ -802,12 +1228,12 @@ function evaluateAssistantGuardrails(message) {
802
1228
  }
803
1229
  }
804
1230
  }
805
- catch (e_1_1) { e_1 = { error: e_1_1 }; }
1231
+ catch (e_2_1) { e_2 = { error: e_2_1 }; }
806
1232
  finally {
807
1233
  try {
808
1234
  if (patterns_1_1 && !patterns_1_1.done && (_a = patterns_1.return)) _a.call(patterns_1);
809
1235
  }
810
- finally { if (e_1) throw e_1.error; }
1236
+ finally { if (e_2) throw e_2.error; }
811
1237
  }
812
1238
  return null;
813
1239
  }
@@ -827,6 +1253,93 @@ function resolveOpenAISettings(config) {
827
1253
  retryDelayMs: normalizeOptionalNumber(serverConfig['OPENAI_RETRY_DELAY_MS'] || process.env.OPENAI_RETRY_DELAY_MS)
828
1254
  };
829
1255
  }
1256
+ function buildAiFormSystemPrompt() {
1257
+ return AI_FORM_PATCH_SYSTEM_PROMPT;
1258
+ }
1259
+ function buildAiFormUserPrompt(message, fields, patchFormat, route) {
1260
+ var lines = [];
1261
+ lines.push("User request: ".concat(message));
1262
+ var normalizedRoute = normalizeOptionalString(route);
1263
+ if (normalizedRoute) {
1264
+ lines.push("Current route: ".concat(normalizedRoute));
1265
+ }
1266
+ if (patchFormat) {
1267
+ lines.push('Patch format:');
1268
+ lines.push(patchFormat);
1269
+ }
1270
+ lines.push('Allowed fields:');
1271
+ fields.forEach(function (field) {
1272
+ var name = field.name || '';
1273
+ if (!name) {
1274
+ return;
1275
+ }
1276
+ var parts = [name];
1277
+ if (field.type) {
1278
+ parts.push("type=".concat(field.type));
1279
+ }
1280
+ if (field.label) {
1281
+ parts.push("label=\"".concat(field.label, "\""));
1282
+ }
1283
+ if (Array.isArray(field.enums) && field.enums.length) {
1284
+ parts.push("enums=".concat(field.enums.join('|')));
1285
+ }
1286
+ lines.push("- ".concat(parts.join(' ')));
1287
+ });
1288
+ lines.push('Return JSON only.');
1289
+ return lines.join('\n');
1290
+ }
1291
+ function normalizeAiFormFields(fields) {
1292
+ if (!Array.isArray(fields)) {
1293
+ return [];
1294
+ }
1295
+ return fields
1296
+ .map(function (field) {
1297
+ if (!field || typeof field !== 'object') {
1298
+ return null;
1299
+ }
1300
+ var name = normalizeOptionalString(field.name) || normalizeOptionalString(field.path);
1301
+ if (!name) {
1302
+ return null;
1303
+ }
1304
+ var type = normalizeOptionalString(field.type);
1305
+ var label = normalizeOptionalString(field.label);
1306
+ var enums = Array.isArray(field.enums)
1307
+ ? field.enums.map(function (value) { return normalizeOptionalString(value); }).filter(Boolean)
1308
+ : undefined;
1309
+ var normalizedField = { name: name };
1310
+ if (type) {
1311
+ normalizedField.type = type;
1312
+ }
1313
+ if (label) {
1314
+ normalizedField.label = label;
1315
+ }
1316
+ if (enums && enums.length) {
1317
+ normalizedField.enums = enums;
1318
+ }
1319
+ return normalizedField;
1320
+ })
1321
+ .filter(function (field) { return !!field; });
1322
+ }
1323
+ function parseJsonObject(content) {
1324
+ if (!content || typeof content !== 'string') {
1325
+ return null;
1326
+ }
1327
+ try {
1328
+ return JSON.parse(content);
1329
+ }
1330
+ catch (_a) {
1331
+ var match = content.match(/\{[\s\S]*\}/);
1332
+ if (!match) {
1333
+ return null;
1334
+ }
1335
+ try {
1336
+ return JSON.parse(match[0]);
1337
+ }
1338
+ catch (_b) {
1339
+ return null;
1340
+ }
1341
+ }
1342
+ }
830
1343
  function resolveUploadLimits() {
831
1344
  var config = resolveio_server_app_1.ResolveIOServer.getServerConfig() || {};
832
1345
  var maxFileMb = normalizeOptionalNumber(config['AI_TERMINAL_MAX_FILE_MB'] || process.env.AI_TERMINAL_MAX_FILE_MB) || DEFAULT_MAX_FILE_MB;
@@ -884,8 +1397,8 @@ function handleCodexUpload(id_conversation, file_name, content_base64, size, con
884
1397
  }
885
1398
  function readAttachmentContents(attachments) {
886
1399
  return __awaiter(this, void 0, void 0, function () {
887
- var limits, totalBytes, totalChars, chunks, cleaned, attachments_1, attachments_1_1, attachment, localPath, safe, stat, ext, name_1, type, readable, content, _a, e_2_1;
888
- var e_2, _b;
1400
+ var limits, totalBytes, totalChars, chunks, cleaned, attachments_1, attachments_1_1, attachment, localPath, safe, stat, ext, name_1, type, readable, content, _a, e_3_1;
1401
+ var e_3, _b;
889
1402
  return __generator(this, function (_c) {
890
1403
  switch (_c.label) {
891
1404
  case 0:
@@ -964,14 +1477,14 @@ function readAttachmentContents(attachments) {
964
1477
  return [3 /*break*/, 2];
965
1478
  case 10: return [3 /*break*/, 13];
966
1479
  case 11:
967
- e_2_1 = _c.sent();
968
- e_2 = { error: e_2_1 };
1480
+ e_3_1 = _c.sent();
1481
+ e_3 = { error: e_3_1 };
969
1482
  return [3 /*break*/, 13];
970
1483
  case 12:
971
1484
  try {
972
1485
  if (attachments_1_1 && !attachments_1_1.done && (_b = attachments_1.return)) _b.call(attachments_1);
973
1486
  }
974
- finally { if (e_2) throw e_2.error; }
1487
+ finally { if (e_3) throw e_3.error; }
975
1488
  return [7 /*endfinally*/];
976
1489
  case 13: return [2 /*return*/, {
977
1490
  promptText: chunks.length ? "\n\nAttachments:\n".concat(chunks.join('\n\n')) : '',
@@ -1124,10 +1637,11 @@ function estimateUsage(messages, responseText, model) {
1124
1637
  };
1125
1638
  }
1126
1639
  function evaluateGuardrails(message) {
1127
- var e_3, _a;
1640
+ var e_4, _a;
1128
1641
  var normalized = String(message || '').toLowerCase();
1129
1642
  var patterns = [
1130
1643
  { pattern: /\b(source\s*code|full\s*code|entire\s*code|repo\s*dump|repository|git\s*clone)\b/i, reason: 'Code access is restricted.' },
1644
+ { pattern: /\b(show|share|paste|provide|dump|output)\b.*\b(code|file|contents|snippet)\b/i, reason: 'Code access is restricted.' },
1131
1645
  { pattern: /\b(credentials?|passwords?|secrets?|tokens?)\b/i, reason: 'Credentials and secrets are restricted.' },
1132
1646
  { pattern: /\b(delete|drop)\s+(database|db|schema)\b/i, reason: 'Database operations are restricted.' },
1133
1647
  { pattern: /\b(shell|terminal|ssh|sudo|rm\s+-rf|chmod|chown)\b/i, reason: 'Server operations are restricted.' },
@@ -1140,17 +1654,17 @@ function evaluateGuardrails(message) {
1140
1654
  return {
1141
1655
  blocked: true,
1142
1656
  reason: entry.reason,
1143
- response: 'I can’t help with that request because it could impact system security or expose protected information. If you need a change, describe the outcome and I can help within approved workflows.'
1657
+ response: 'I can’t help with that request because it could expose proprietary code or protected information. If you need a change, describe the outcome and I can help within approved workflows.'
1144
1658
  };
1145
1659
  }
1146
1660
  }
1147
1661
  }
1148
- catch (e_3_1) { e_3 = { error: e_3_1 }; }
1662
+ catch (e_4_1) { e_4 = { error: e_4_1 }; }
1149
1663
  finally {
1150
1664
  try {
1151
1665
  if (patterns_2_1 && !patterns_2_1.done && (_a = patterns_2.return)) _a.call(patterns_2);
1152
1666
  }
1153
- finally { if (e_3) throw e_3.error; }
1667
+ finally { if (e_4) throw e_4.error; }
1154
1668
  }
1155
1669
  return null;
1156
1670
  }