@resolveio/server-lib 20.14.4 → 20.14.6

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,8 +1128,136 @@ 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
  }
1148
+ var cachedClientRouteIndex = null;
1149
+ function normalizeRouteKey(value) {
1150
+ var trimmed = normalizeOptionalString(value);
1151
+ if (!trimmed) {
1152
+ return '';
1153
+ }
1154
+ var withSlash = trimmed.startsWith('/') ? trimmed : "/".concat(trimmed);
1155
+ return withSlash.replace(/\/+$/, '');
1156
+ }
1157
+ function normalizeRouteMatchKey(value) {
1158
+ return normalizeRouteKey(value).toLowerCase();
1159
+ }
1160
+ function buildClientRouteIndex() {
1161
+ var e_2, _a;
1162
+ var _b;
1163
+ var routes = ((_b = resolveio_server_app_1.ResolveIOServer.getClientRoutes) === null || _b === void 0 ? void 0 : _b.call(resolveio_server_app_1.ResolveIOServer)) || [];
1164
+ var set = new Set();
1165
+ var map = new Map();
1166
+ try {
1167
+ for (var routes_1 = __values(routes), routes_1_1 = routes_1.next(); !routes_1_1.done; routes_1_1 = routes_1.next()) {
1168
+ var route = routes_1_1.value;
1169
+ var normalized = normalizeRouteKey(route);
1170
+ if (!normalized) {
1171
+ continue;
1172
+ }
1173
+ var matchKey = normalized.toLowerCase();
1174
+ set.add(matchKey);
1175
+ if (!map.has(matchKey)) {
1176
+ map.set(matchKey, normalized);
1177
+ }
1178
+ }
1179
+ }
1180
+ catch (e_2_1) { e_2 = { error: e_2_1 }; }
1181
+ finally {
1182
+ try {
1183
+ if (routes_1_1 && !routes_1_1.done && (_a = routes_1.return)) _a.call(routes_1);
1184
+ }
1185
+ finally { if (e_2) throw e_2.error; }
1186
+ }
1187
+ return { set: set, map: map, size: routes.length };
1188
+ }
1189
+ function getClientRouteIndex() {
1190
+ var _a;
1191
+ var routes = ((_a = resolveio_server_app_1.ResolveIOServer.getClientRoutes) === null || _a === void 0 ? void 0 : _a.call(resolveio_server_app_1.ResolveIOServer)) || [];
1192
+ if (cachedClientRouteIndex && cachedClientRouteIndex.size === routes.length) {
1193
+ return cachedClientRouteIndex;
1194
+ }
1195
+ cachedClientRouteIndex = buildClientRouteIndex();
1196
+ return cachedClientRouteIndex;
1197
+ }
1198
+ function splitRouteSuffix(value) {
1199
+ var matchIndex = value.search(/[?#]/);
1200
+ if (matchIndex === -1) {
1201
+ return { base: value, suffix: '' };
1202
+ }
1203
+ return { base: value.slice(0, matchIndex), suffix: value.slice(matchIndex) };
1204
+ }
1205
+ function isClientRoute(value, index) {
1206
+ var normalized = normalizeRouteMatchKey(value);
1207
+ if (!normalized) {
1208
+ return false;
1209
+ }
1210
+ if (index.set.has(normalized)) {
1211
+ return true;
1212
+ }
1213
+ var parts = normalized.split('/').filter(Boolean);
1214
+ while (parts.length > 1) {
1215
+ parts.pop();
1216
+ var prefix = "/".concat(parts.join('/'));
1217
+ if (index.set.has(prefix)) {
1218
+ return true;
1219
+ }
1220
+ }
1221
+ return false;
1222
+ }
1223
+ function getCanonicalRoute(value, index) {
1224
+ var normalized = normalizeRouteMatchKey(value);
1225
+ if (!normalized) {
1226
+ return null;
1227
+ }
1228
+ return index.map.get(normalized) || null;
1229
+ }
1230
+ function swapTwoSegmentRoute(value) {
1231
+ var normalized = normalizeRouteKey(value);
1232
+ if (!normalized) {
1233
+ return null;
1234
+ }
1235
+ var parts = normalized.split('/').filter(Boolean);
1236
+ if (parts.length !== 2) {
1237
+ return null;
1238
+ }
1239
+ return "/".concat(parts[1], "/").concat(parts[0]);
1240
+ }
1241
+ function normalizeAssistantRoutes(value) {
1242
+ var index = getClientRouteIndex();
1243
+ if (!index.set.size) {
1244
+ return value;
1245
+ }
1246
+ var routePattern = /\/[a-z0-9][a-z0-9/_-]*(?:\?[a-z0-9=&%._-]+)?(?:#[a-z0-9._-]+)?/gi;
1247
+ return value.replace(routePattern, function (match) {
1248
+ var _a = splitRouteSuffix(match), base = _a.base, suffix = _a.suffix;
1249
+ if (isClientRoute(base, index)) {
1250
+ var canonical_1 = getCanonicalRoute(base, index);
1251
+ return canonical_1 ? "".concat(canonical_1).concat(suffix) : match;
1252
+ }
1253
+ var swapped = swapTwoSegmentRoute(base);
1254
+ if (!swapped || !isClientRoute(swapped, index)) {
1255
+ return match;
1256
+ }
1257
+ var canonical = getCanonicalRoute(swapped, index) || swapped;
1258
+ return "".concat(canonical).concat(suffix);
1259
+ });
1260
+ }
722
1261
  function sanitizeAssistantResponse(value) {
723
1262
  var raw = normalizeOptionalString(value);
724
1263
  if (!raw) {
@@ -753,21 +1292,21 @@ function sanitizeAssistantResponse(value) {
753
1292
  if (!cleaned) {
754
1293
  return 'I can’t share code, but I can point you to files or explain behavior at a high level.';
755
1294
  }
756
- return cleaned;
1295
+ return normalizeAssistantRoutes(cleaned);
757
1296
  }
758
1297
  function evaluateAssistantGuardrails(message) {
759
- var e_1, _a;
1298
+ var e_3, _a;
760
1299
  var normalized = String(message || '').toLowerCase();
761
1300
  var patterns = [
762
1301
  {
763
1302
  pattern: /\b(show|share|paste|provide|dump|output)\b.*\b(code|snippet|file|function|class|script|sql)\b/i,
764
1303
  reason: 'Code sharing is restricted.',
765
- response: 'I can’t share code. I can explain behavior and point you to file paths or routes.'
1304
+ 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
1305
  },
767
1306
  {
768
1307
  pattern: /\b(write|generate|create|implement|fix)\b.*\b(code|script|function|class|endpoint|sql)\b/i,
769
1308
  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.'
1309
+ 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
1310
  },
772
1311
  {
773
1312
  pattern: /\b(credentials?|passwords?|secrets?|tokens?)\b/i,
@@ -802,12 +1341,12 @@ function evaluateAssistantGuardrails(message) {
802
1341
  }
803
1342
  }
804
1343
  }
805
- catch (e_1_1) { e_1 = { error: e_1_1 }; }
1344
+ catch (e_3_1) { e_3 = { error: e_3_1 }; }
806
1345
  finally {
807
1346
  try {
808
1347
  if (patterns_1_1 && !patterns_1_1.done && (_a = patterns_1.return)) _a.call(patterns_1);
809
1348
  }
810
- finally { if (e_1) throw e_1.error; }
1349
+ finally { if (e_3) throw e_3.error; }
811
1350
  }
812
1351
  return null;
813
1352
  }
@@ -827,6 +1366,93 @@ function resolveOpenAISettings(config) {
827
1366
  retryDelayMs: normalizeOptionalNumber(serverConfig['OPENAI_RETRY_DELAY_MS'] || process.env.OPENAI_RETRY_DELAY_MS)
828
1367
  };
829
1368
  }
1369
+ function buildAiFormSystemPrompt() {
1370
+ return AI_FORM_PATCH_SYSTEM_PROMPT;
1371
+ }
1372
+ function buildAiFormUserPrompt(message, fields, patchFormat, route) {
1373
+ var lines = [];
1374
+ lines.push("User request: ".concat(message));
1375
+ var normalizedRoute = normalizeOptionalString(route);
1376
+ if (normalizedRoute) {
1377
+ lines.push("Current route: ".concat(normalizedRoute));
1378
+ }
1379
+ if (patchFormat) {
1380
+ lines.push('Patch format:');
1381
+ lines.push(patchFormat);
1382
+ }
1383
+ lines.push('Allowed fields:');
1384
+ fields.forEach(function (field) {
1385
+ var name = field.name || '';
1386
+ if (!name) {
1387
+ return;
1388
+ }
1389
+ var parts = [name];
1390
+ if (field.type) {
1391
+ parts.push("type=".concat(field.type));
1392
+ }
1393
+ if (field.label) {
1394
+ parts.push("label=\"".concat(field.label, "\""));
1395
+ }
1396
+ if (Array.isArray(field.enums) && field.enums.length) {
1397
+ parts.push("enums=".concat(field.enums.join('|')));
1398
+ }
1399
+ lines.push("- ".concat(parts.join(' ')));
1400
+ });
1401
+ lines.push('Return JSON only.');
1402
+ return lines.join('\n');
1403
+ }
1404
+ function normalizeAiFormFields(fields) {
1405
+ if (!Array.isArray(fields)) {
1406
+ return [];
1407
+ }
1408
+ return fields
1409
+ .map(function (field) {
1410
+ if (!field || typeof field !== 'object') {
1411
+ return null;
1412
+ }
1413
+ var name = normalizeOptionalString(field.name) || normalizeOptionalString(field.path);
1414
+ if (!name) {
1415
+ return null;
1416
+ }
1417
+ var type = normalizeOptionalString(field.type);
1418
+ var label = normalizeOptionalString(field.label);
1419
+ var enums = Array.isArray(field.enums)
1420
+ ? field.enums.map(function (value) { return normalizeOptionalString(value); }).filter(Boolean)
1421
+ : undefined;
1422
+ var normalizedField = { name: name };
1423
+ if (type) {
1424
+ normalizedField.type = type;
1425
+ }
1426
+ if (label) {
1427
+ normalizedField.label = label;
1428
+ }
1429
+ if (enums && enums.length) {
1430
+ normalizedField.enums = enums;
1431
+ }
1432
+ return normalizedField;
1433
+ })
1434
+ .filter(function (field) { return !!field; });
1435
+ }
1436
+ function parseJsonObject(content) {
1437
+ if (!content || typeof content !== 'string') {
1438
+ return null;
1439
+ }
1440
+ try {
1441
+ return JSON.parse(content);
1442
+ }
1443
+ catch (_a) {
1444
+ var match = content.match(/\{[\s\S]*\}/);
1445
+ if (!match) {
1446
+ return null;
1447
+ }
1448
+ try {
1449
+ return JSON.parse(match[0]);
1450
+ }
1451
+ catch (_b) {
1452
+ return null;
1453
+ }
1454
+ }
1455
+ }
830
1456
  function resolveUploadLimits() {
831
1457
  var config = resolveio_server_app_1.ResolveIOServer.getServerConfig() || {};
832
1458
  var maxFileMb = normalizeOptionalNumber(config['AI_TERMINAL_MAX_FILE_MB'] || process.env.AI_TERMINAL_MAX_FILE_MB) || DEFAULT_MAX_FILE_MB;
@@ -884,8 +1510,8 @@ function handleCodexUpload(id_conversation, file_name, content_base64, size, con
884
1510
  }
885
1511
  function readAttachmentContents(attachments) {
886
1512
  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;
1513
+ var limits, totalBytes, totalChars, chunks, cleaned, attachments_1, attachments_1_1, attachment, localPath, safe, stat, ext, name_1, type, readable, content, _a, e_4_1;
1514
+ var e_4, _b;
889
1515
  return __generator(this, function (_c) {
890
1516
  switch (_c.label) {
891
1517
  case 0:
@@ -964,14 +1590,14 @@ function readAttachmentContents(attachments) {
964
1590
  return [3 /*break*/, 2];
965
1591
  case 10: return [3 /*break*/, 13];
966
1592
  case 11:
967
- e_2_1 = _c.sent();
968
- e_2 = { error: e_2_1 };
1593
+ e_4_1 = _c.sent();
1594
+ e_4 = { error: e_4_1 };
969
1595
  return [3 /*break*/, 13];
970
1596
  case 12:
971
1597
  try {
972
1598
  if (attachments_1_1 && !attachments_1_1.done && (_b = attachments_1.return)) _b.call(attachments_1);
973
1599
  }
974
- finally { if (e_2) throw e_2.error; }
1600
+ finally { if (e_4) throw e_4.error; }
975
1601
  return [7 /*endfinally*/];
976
1602
  case 13: return [2 /*return*/, {
977
1603
  promptText: chunks.length ? "\n\nAttachments:\n".concat(chunks.join('\n\n')) : '',
@@ -1124,10 +1750,11 @@ function estimateUsage(messages, responseText, model) {
1124
1750
  };
1125
1751
  }
1126
1752
  function evaluateGuardrails(message) {
1127
- var e_3, _a;
1753
+ var e_5, _a;
1128
1754
  var normalized = String(message || '').toLowerCase();
1129
1755
  var patterns = [
1130
1756
  { 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.' },
1757
+ { pattern: /\b(show|share|paste|provide|dump|output)\b.*\b(code|file|contents|snippet)\b/i, reason: 'Code access is restricted.' },
1131
1758
  { pattern: /\b(credentials?|passwords?|secrets?|tokens?)\b/i, reason: 'Credentials and secrets are restricted.' },
1132
1759
  { pattern: /\b(delete|drop)\s+(database|db|schema)\b/i, reason: 'Database operations are restricted.' },
1133
1760
  { pattern: /\b(shell|terminal|ssh|sudo|rm\s+-rf|chmod|chown)\b/i, reason: 'Server operations are restricted.' },
@@ -1140,17 +1767,17 @@ function evaluateGuardrails(message) {
1140
1767
  return {
1141
1768
  blocked: true,
1142
1769
  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.'
1770
+ 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
1771
  };
1145
1772
  }
1146
1773
  }
1147
1774
  }
1148
- catch (e_3_1) { e_3 = { error: e_3_1 }; }
1775
+ catch (e_5_1) { e_5 = { error: e_5_1 }; }
1149
1776
  finally {
1150
1777
  try {
1151
1778
  if (patterns_2_1 && !patterns_2_1.done && (_a = patterns_2.return)) _a.call(patterns_2);
1152
1779
  }
1153
- finally { if (e_3) throw e_3.error; }
1780
+ finally { if (e_5) throw e_5.error; }
1154
1781
  }
1155
1782
  return null;
1156
1783
  }