@xuda.io/ai_module 1.1.5640 → 1.1.5642

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.
Files changed (2) hide show
  1. package/index.mjs +347 -21
  2. package/package.json +1 -1
package/index.mjs CHANGED
@@ -308,11 +308,37 @@ const { init, check, check_structure, get_zod_schema, get_fields_z_schema } = aw
308
308
  const { get_xuda_couch } = _common;
309
309
 
310
310
  const db_module = await import(`${module_path}/db_module/index.mjs`);
311
+
312
+ // Sort keys for the dashboard list tabs, per docType. Each entry is a fallback
313
+ // chain (first defined value wins) because the doc shapes differ: a chat has
314
+ // its own title + date_created_ts, a studio doc carries the name under
315
+ // properties.menuName and may only have ts. See
316
+ // db_module.find_app_couch_sorted_page.
317
+ // `shared` points at the owner's copy: an app/agent shared with you, or bought
318
+ // from the marketplace, is stored locally as a stub with no menuName, so a name
319
+ // sort has to read the source doc. Same two pointers the enrichment follows
320
+ // (get_ai_agents/get_apps: shared_from_app_id + share_item_id, then
321
+ // installed_from_app_id keyed by the doc's own _id).
322
+ const LIST_SORT_PATHS = {
323
+ chat: { created: ['date_created_ts', 'date_created', 'ts'], updated: ['ts', 'date_created_ts'], name: ['title'] },
324
+ studio: {
325
+ created: ['date_created_ts', 'date_created', 'ts'],
326
+ updated: ['ts', 'date_created_ts'],
327
+ name: ['properties.menuName', 'studio_meta.name'],
328
+ shared: [
329
+ { app_id: 'studio_meta.shared_from_app_id', doc_id: 'studio_meta.share_item_id' },
330
+ { app_id: 'studio_meta.installed_from_app_id', doc_id: '_id' },
331
+ ],
332
+ },
333
+ };
334
+
311
335
  const account_ms = await import(`${module_path}/account_module/index_ms.mjs`);
312
336
  const drive_ms = await import(`${module_path}/drive_module/index_ms.mjs`);
313
337
  const jobs_ms = await import(`${module_path}/jobs_module/index_ms.mjs`);
314
338
  const team_ms = await import(`${module_path}/team_module/index_ms.mjs`);
315
339
  const email_ms = await import(`${module_path}/email_module/index_ms.mjs`);
340
+ // Profile broadcasts send SMS through the same primitive Phone Manager uses.
341
+ const voice_ms = await import(`${module_path}/voice_module/index_ms.mjs`);
316
342
  const api_ms = await import(`${module_path}/api_module/index_ms.mjs`);
317
343
  const stripe_ms = await import(`${module_path}/stripe_module/index_ms.mjs`);
318
344
 
@@ -2073,6 +2099,14 @@ export const get_ai_chats = async function (req, job_id, headers) {
2073
2099
  };
2074
2100
  }
2075
2101
 
2102
+ // User-picked order from the dashboard list controls (sort_by date |
2103
+ // updated | name, sort_dir asc | desc). Null when none was asked for,
2104
+ // which leaves the default newest-first path below untouched. Only the
2105
+ // top-level chat list is sortable: with a reference_id these are the
2106
+ // items INSIDE one conversation, which must stay in thread order.
2107
+ const sorted = reference_id || conversation_id ? null : await db_module.find_app_couch_sorted_page(account_profile_info.app_id, opt, req, LIST_SORT_PATHS.chat);
2108
+ if (sorted) return sorted;
2109
+
2076
2110
  let ai_chats = await db_module.find_app_couch_query(account_profile_info.app_id, opt);
2077
2111
  if (!limit || conversation_id) {
2078
2112
  ai_chats.total_docs = ai_chats.docs.length;
@@ -2243,9 +2277,19 @@ export const get_ai_chats = async function (req, job_id, headers) {
2243
2277
  doc.notifications = contact_chat_conversation_count_ret[doc._id] - contact_chat_conversation_read_ret[doc._id];
2244
2278
  doc.chats = doc.interactions;
2245
2279
 
2246
- doc.user_contact = await get_user_card({ uid, uid_query: doc.uid });
2280
+ // Who WROTE this row. A contact/profile timeline comes back from
2281
+ // get_data_view_conversation_items as a composite { conversation, conversation_item }
2282
+ // that carries no uid of its own, so `doc.uid` was undefined here. get_user_card then
2283
+ // built a selector with contact_uid dropped (JSON.stringify strips undefined) and
2284
+ // matched ANY contact, which is why every note on a contact page was bylined with the
2285
+ // first contact in the book, the person the note is ABOUT, instead of its author.
2286
+ const author_uid = doc.uid || doc.conversation_item?.uid || doc.conversation?.initiator_uid || doc.conversation?.uid;
2287
+
2288
+ if (author_uid) {
2289
+ doc.user_contact = await get_user_card({ uid, uid_query: author_uid });
2290
+ }
2247
2291
 
2248
- doc.privilege = doc.uid === uid || doc?.account_profile_info?.uid === uid;
2292
+ doc.privilege = author_uid === uid || doc?.account_profile_info?.uid === uid;
2249
2293
 
2250
2294
  if (reference_id && doc.reference_type === 'contact') {
2251
2295
  // only valid for contacts conversation
@@ -2254,6 +2298,17 @@ export const get_ai_chats = async function (req, job_id, headers) {
2254
2298
  docs.push(doc);
2255
2299
  }
2256
2300
 
2301
+ // Calls and SMS belong on the contact's timeline next to the notes rather than behind
2302
+ // their own tabs, so merge them in and order the whole thing by time. Only for a real
2303
+ // contact timeline: the account-wide lists have their own screens.
2304
+ if (reference_id && reference_type === 'contacts') {
2305
+ const voice_rows = await _tl_voice_rows(uid, account_profile_info, reference_id, docs[0]?.user_contact || null);
2306
+ if (voice_rows.length) {
2307
+ const row_ts = (r) => Number(r?.conversation_item?.date_created_ts || r?.conversation?.date_created_ts || r?.date_created_ts || 0);
2308
+ docs = [...docs, ...voice_rows].sort((a, b) => row_ts(a) - row_ts(b));
2309
+ }
2310
+ }
2311
+
2257
2312
  return { code: 2, data: { docs: [...requests_from.docs, ...docs], total_docs: ai_chats.total_docs + requests_from.total_docs } };
2258
2313
  } catch (err) {
2259
2314
  return { code: -2, data: err.message };
@@ -2974,17 +3029,24 @@ export const get_ai_agents = async function (req, job_id, headers) {
2974
3029
 
2975
3030
  let user_agents = { docs: [], total_docs: 0 };
2976
3031
  if (filter_type !== 'pending') {
2977
- user_agents = await db_module.find_app_couch_query(account_profile_info.app_id, opt);
2978
- if (!limit || agent_id) {
2979
- user_agents.total_docs = user_agents.docs.length;
3032
+ // User-picked order from the dashboard list controls, see
3033
+ // db_module.find_app_couch_sorted_page. Null when none was asked for.
3034
+ const sorted = agent_id ? null : await db_module.find_app_couch_sorted_page(account_profile_info.app_id, opt, req, LIST_SORT_PATHS.studio);
3035
+ if (sorted) {
3036
+ user_agents = sorted;
2980
3037
  } else {
2981
- delete opt.sort;
2982
- delete opt.skip;
2983
- opt.limit = 9999;
2984
- opt.fields = ['_id'];
3038
+ user_agents = await db_module.find_app_couch_query(account_profile_info.app_id, opt);
3039
+ if (!limit || agent_id) {
3040
+ user_agents.total_docs = user_agents.docs.length;
3041
+ } else {
3042
+ delete opt.sort;
3043
+ delete opt.skip;
3044
+ opt.limit = 9999;
3045
+ opt.fields = ['_id'];
2985
3046
 
2986
- const counter = await db_module.find_app_couch_query(account_profile_info.app_id, opt);
2987
- user_agents.total_docs = counter.docs.length;
3047
+ const counter = await db_module.find_app_couch_query(account_profile_info.app_id, opt);
3048
+ user_agents.total_docs = counter.docs.length;
3049
+ }
2988
3050
  }
2989
3051
  }
2990
3052
 
@@ -3460,17 +3522,25 @@ export const get_apps = async function (req, job_id, headers) {
3460
3522
 
3461
3523
  let user_apps = { docs: [], total_docs: 0 };
3462
3524
  if (filter_type !== 'pending') {
3463
- user_apps = await db_module.find_app_couch_query(account_profile_info.app_id, opt);
3464
- if (!limit || mini_app_id) {
3465
- user_apps.total_docs = user_apps.docs.length;
3525
+ // User-picked order from the dashboard list controls, see
3526
+ // db_module.find_app_couch_sorted_page. Null when none was asked for,
3527
+ // which leaves the default newest-first path below untouched.
3528
+ const sorted = mini_app_id ? null : await db_module.find_app_couch_sorted_page(account_profile_info.app_id, opt, req, LIST_SORT_PATHS.studio);
3529
+ if (sorted) {
3530
+ user_apps = sorted;
3466
3531
  } else {
3467
- delete opt.sort;
3468
- delete opt.skip;
3469
- opt.limit = 9999;
3470
- opt.fields = ['_id'];
3532
+ user_apps = await db_module.find_app_couch_query(account_profile_info.app_id, opt);
3533
+ if (!limit || mini_app_id) {
3534
+ user_apps.total_docs = user_apps.docs.length;
3535
+ } else {
3536
+ delete opt.sort;
3537
+ delete opt.skip;
3538
+ opt.limit = 9999;
3539
+ opt.fields = ['_id'];
3471
3540
 
3472
- const counter = await db_module.find_app_couch_query(account_profile_info.app_id, opt);
3473
- user_apps.total_docs = counter.docs.length;
3541
+ const counter = await db_module.find_app_couch_query(account_profile_info.app_id, opt);
3542
+ user_apps.total_docs = counter.docs.length;
3543
+ }
3474
3544
  }
3475
3545
  }
3476
3546
 
@@ -5143,6 +5213,103 @@ const process_conversation = async function (uid, conversation_id, account_profi
5143
5213
  // }
5144
5214
  };
5145
5215
 
5216
+ // --- contact timeline: calls and SMS -----------------------------------------------------
5217
+ // Phone activity lives in xuda_master as voice_call / voice_message docs keyed by
5218
+ // owner_uid + phone number, NOT by contact, so it can only be tied to a contact by matching
5219
+ // numbers. Digits only, comparing the last 10, so a number written +1 561 235 1675 on an
5220
+ // account matches +15612351675 on a call. Same rule as voice_module's own caller matching.
5221
+ const _tl_digits = (value) => String(value ?? '').replace(/\D+/g, '');
5222
+ const _tl_same_phone = (a, b) => {
5223
+ const x = _tl_digits(a);
5224
+ const y = _tl_digits(b);
5225
+ if (!x || !y) return false;
5226
+ if (x === y) return true;
5227
+ const n = Math.min(10, x.length, y.length);
5228
+ return n >= 7 && x.slice(-n) === y.slice(-n);
5229
+ };
5230
+
5231
+ // Every number this contact can be reached on. A contact linked to a Xuda user carries no
5232
+ // phone on its own doc at all: it lives on their account, which is why matching only the
5233
+ // contact doc finds nothing for exactly the contacts that matter most.
5234
+ const _tl_contact_phones = async function (app_id, contact_id) {
5235
+ const out = [];
5236
+ let contact = null;
5237
+ try {
5238
+ contact = await db_module.get_app_couch_doc_native(app_id, contact_id);
5239
+ for (const f of ['phone_e164', 'phone', 'telephone', 'phone_number', 'mobile', 'tel']) {
5240
+ if (contact?.[f]) out.push(contact[f]);
5241
+ }
5242
+ if (contact?.contact_uid) {
5243
+ const ret = await db_module.get_couch_doc('xuda_accounts', contact.contact_uid);
5244
+ const info = ret?.code >= 0 ? ret.data?.account_info || {} : {};
5245
+ if (info.phone_number) out.push(info.phone_number);
5246
+ if (info.tel) out.push(info.tel);
5247
+ }
5248
+ } catch (_) { /* no phone is a normal contact, not an error */ }
5249
+ return { phones: out.filter(Boolean), contact };
5250
+ };
5251
+
5252
+ // Calls and SMS as timeline rows, in the same composite shape the note and email rows use
5253
+ // ({ conversation, conversation_item, user_contact }) so the timeline renders them by
5254
+ // conversation_type without a second code path.
5255
+ const _tl_voice_rows = async function (uid, account_profile_info, contact_id, user_contact) {
5256
+ try {
5257
+ const { phones, contact } = await _tl_contact_phones(account_profile_info.app_id, contact_id);
5258
+ if (!phones.length) return [];
5259
+ const peer = (d) => (d.direction === 'inbound' ? d.from : d.to);
5260
+ // Byline the party who did the thing: an inbound call or SMS came FROM the contact, an
5261
+ // outbound one went out under this account. Bylining every call with the account name
5262
+ // read as if the business had called itself.
5263
+ const card_for = (d) => (d.direction === 'inbound' ? contact || user_contact : user_contact);
5264
+ const [calls, messages] = await Promise.all([
5265
+ db_module.find_couch_query('xuda_master', { selector: { docType: 'voice_call', owner_uid: uid }, limit: 500 }, false, true),
5266
+ db_module.find_couch_query('xuda_master', { selector: { docType: 'voice_message', owner_uid: uid }, limit: 500 }, false, true),
5267
+ ]);
5268
+ const mine = (d) => phones.some((p) => _tl_same_phone(p, peer(d)));
5269
+ const row = (type, doc, text, extra) => ({
5270
+ conversation: { _id: doc._id, docType: 'chat_conversation', conversation_type: type, reference_type: 'contacts', reference_id: contact_id, uid, prompt: text, title: text, date_created_ts: doc.created_ts, ts: doc.created_ts, stat: 3 },
5271
+ conversation_item: { _id: doc._id, docType: 'chat_conversation_item', conversation_type: type, type, text, direction: doc.direction, date_created_ts: doc.created_ts, ts: doc.created_ts, stat: 3, uid },
5272
+ user_contact: card_for(doc),
5273
+ voice: { kind: type, ...extra },
5274
+ privilege: true,
5275
+ });
5276
+
5277
+ // What the call was actually about, so the row says something beyond its duration.
5278
+ // The caller's LONGEST turn, because the opening turns are "Hello?" and whatever noise
5279
+ // the speech recogniser picked up before the greeting, while the request itself is the
5280
+ // one sentence they actually spoke at length.
5281
+ const call_gist = (d) => {
5282
+ const turns = Array.isArray(d.transcript) ? d.transcript : [];
5283
+ const said = turns
5284
+ .filter((t) => t.role === 'caller' || t.role === 'user')
5285
+ .map((t) => String(t.text || '').replace(/\s+/g, ' ').trim())
5286
+ .filter(Boolean);
5287
+ const text = said.sort((a, b) => b.length - a.length)[0] || '';
5288
+ return text.length > 140 ? `${text.slice(0, 139)}…` : text;
5289
+ };
5290
+ // Stored as an object ({ type, id }) by the realtime path but sent as a plain string on
5291
+ // the socket event, and the UI compares it to 'ai'. Normalize to the string form here so
5292
+ // a row reads the same however the call was recorded.
5293
+ const handled_kind = (d) => (typeof d.handled_by === 'object' && d.handled_by ? d.handled_by.type || '' : d.handled_by || '');
5294
+ const call_rows = (calls?.docs || []).filter((d) => d.status !== 'ringing' && mine(d)).map((d) =>
5295
+ row('call', d, d.direction === 'inbound' ? 'Incoming call' : 'Outgoing call', {
5296
+ status: d.status,
5297
+ duration_sec: d.duration_sec || 0,
5298
+ has_recording: !!d.recording,
5299
+ transcript_turns: (d.transcript || []).length,
5300
+ transcript_preview: call_gist(d),
5301
+ handled_by: handled_kind(d),
5302
+ }),
5303
+ );
5304
+ const sms_rows = (messages?.docs || []).filter(mine).map((d) => row('sms', d, d.body || '', { status: d.status, segments: d.segments || 1 }));
5305
+ return [...call_rows, ...sms_rows];
5306
+ } catch (err) {
5307
+ // A phone lookup must never take the whole timeline down with it.
5308
+ console.error('[ai_module] contact voice timeline rows failed:', err.message);
5309
+ return [];
5310
+ }
5311
+ };
5312
+
5146
5313
  const contactGuardrailAgent = new Agent({
5147
5314
  name: 'Person Guardrail check',
5148
5315
  instructions: 'Check if the prompt about person.',
@@ -5256,7 +5423,23 @@ export const submit_chat_conversation = async function (req, job_id, headers) {
5256
5423
  }
5257
5424
 
5258
5425
  case 'account_profiles': {
5259
- ret = await ai_chat_conversation(req, job_id, headers);
5426
+ // This used to call ai_chat_conversation unconditionally, so Note, Chat, SMS and
5427
+ // Email on a profile all silently became AI chats. A profile thread is a group
5428
+ // thread: each mode records one item here, and the broadcast modes also deliver
5429
+ // to every member. See profile_thread_post.
5430
+ switch (conversation_doc.conversation_type) {
5431
+ case 'note':
5432
+ case 'chat':
5433
+ case 'sms':
5434
+ case 'email': {
5435
+ ret = await profile_thread_post(req, job_id, headers, conversation_doc.conversation_type);
5436
+ break;
5437
+ }
5438
+ default: {
5439
+ ret = await ai_chat_conversation(req, job_id, headers);
5440
+ break;
5441
+ }
5442
+ }
5260
5443
  break;
5261
5444
  }
5262
5445
 
@@ -5428,6 +5611,149 @@ const chat_note = async function (req, job_id, headers) {
5428
5611
  }
5429
5612
  };
5430
5613
 
5614
+ // ── Account-profile group thread ────────────────────────────────────────────────────────
5615
+ // A profile conversation is ONE group thread, not a set of per-member threads (Boaz,
5616
+ // 2026-07-31: "only on the profile like group chat"). Every mode records a single item on
5617
+ // the profile conversation. SMS and email additionally deliver that text to each member,
5618
+ // but those sends are a delivery detail: nothing is written to a member's own contact
5619
+ // timeline, and inbound replies belong back on this same profile conversation.
5620
+
5621
+ // Resolve the profile's members to one delivery address each, then send per member.
5622
+ // Returns a per-recipient outcome rather than a bare boolean, because a broadcast that
5623
+ // only half-succeeded must not be recorded as if it had reached everyone.
5624
+ const profile_broadcast_recipients = async function (owner_uid, profile_id, kind) {
5625
+ const member_uids = await account_ms.get_account_profile_group_member_uids(owner_uid, profile_id);
5626
+ // The owner is not a member of their own profile and must not receive their own broadcast.
5627
+ const uids = [...new Set((member_uids || []).filter((u) => u && u !== owner_uid))];
5628
+ const out = [];
5629
+ for (const member_uid of uids) {
5630
+ let doc = null;
5631
+ try {
5632
+ doc = await db_module.get_couch_doc_native('xuda_accounts', member_uid);
5633
+ } catch (_) {
5634
+ doc = null;
5635
+ }
5636
+ const info = doc?.account_info || {};
5637
+ const name = [info.name, info.last_name].filter(Boolean).join(' ') || info.email || member_uid;
5638
+ const address = kind === 'sms' ? info.phone_number : info.email;
5639
+ out.push({ uid: member_uid, name, address: address || null });
5640
+ }
5641
+ return out;
5642
+ };
5643
+
5644
+ const profile_broadcast = async function (uid, account_profile_info, profile_id, kind, body) {
5645
+ const recipients = await profile_broadcast_recipients(uid, profile_id, kind);
5646
+ const results = [];
5647
+
5648
+ // One SMS-capable number for the whole broadcast, resolved once. Without a number there
5649
+ // is nothing to send from, and that is a configuration error worth surfacing rather than
5650
+ // reporting every member as individually unreachable.
5651
+ let voice_number_id = null;
5652
+ if (kind === 'sms') {
5653
+ const numbers = await voice_ms.list_voice_numbers({ uid, profile_id });
5654
+ const usable = (numbers?.data || []).find((n) => n.capabilities?.SMS === true || n.capabilities?.sms === true);
5655
+ if (!usable) throw new Error('This profile has no phone number that can send SMS');
5656
+ voice_number_id = usable.id;
5657
+ }
5658
+
5659
+ const profile_name = account_profile_info.account_profile_obj?.profile_name || 'your team';
5660
+ for (const recipient of recipients) {
5661
+ if (!recipient.address) {
5662
+ results.push({ ...recipient, ok: false, error: kind === 'sms' ? 'no phone number on file' : 'no email address on file' });
5663
+ continue;
5664
+ }
5665
+ try {
5666
+ if (kind === 'sms') {
5667
+ const ret = await voice_ms.send_voice_message({ uid, profile_id, voice_number_id, to: recipient.address, body });
5668
+ if (ret?.code < 0) throw new Error(ret.data || 'send failed');
5669
+ } else {
5670
+ await email_ms.send_email({
5671
+ email: recipient.address,
5672
+ subject: `Message from ${profile_name}`,
5673
+ body,
5674
+ display_type: 'info',
5675
+ app_id: account_profile_info.app_id,
5676
+ });
5677
+ }
5678
+ results.push({ ...recipient, ok: true });
5679
+ } catch (err) {
5680
+ results.push({ ...recipient, ok: false, error: err.message });
5681
+ }
5682
+ }
5683
+
5684
+ return { results, sent: results.filter((r) => r.ok).length, failed: results.filter((r) => !r.ok).length };
5685
+ };
5686
+
5687
+ // `kind` is the composer mode: note | chat | sms | email. AI chat does not come through
5688
+ // here, it keeps going to ai_chat_conversation.
5689
+ const profile_thread_post = async function (req, job_id, headers, kind) {
5690
+ const { uid, profile_id } = req;
5691
+ const account_profile_info = await get_active_account_profile_info(uid, profile_id);
5692
+ const { prompt: body, conversation_doc, attachments = [] } = req;
5693
+ try {
5694
+ const conversation_id = conversation_doc._id;
5695
+ const app_id = account_profile_info.app_id;
5696
+ const text = String(body || '').trim();
5697
+ if (!text) throw new Error('A message body is required');
5698
+
5699
+ await attachment_handler(uid, app_id, conversation_id, attachments, account_profile_info);
5700
+
5701
+ // Deliver BEFORE recording. A thread item is a claim that the message went out, so a
5702
+ // broadcast that reached nobody has to fail loudly instead of leaving a row that reads
5703
+ // as sent. A partial success is recorded, with the per-recipient detail on the item.
5704
+ let delivery = null;
5705
+ if (kind === 'sms' || kind === 'email') {
5706
+ delivery = await profile_broadcast(uid, account_profile_info, conversation_doc.reference_id, kind, text);
5707
+ if (!delivery.sent) {
5708
+ const why = delivery.results.find((r) => r.error)?.error;
5709
+ throw new Error(delivery.failed ? `Could not deliver to any member (${why})` : 'This profile has no members to send to');
5710
+ }
5711
+ }
5712
+
5713
+ let conversation_item_reference_id;
5714
+ try {
5715
+ conversation_item_reference_id = await add_conversation_item(uid, profile_id, conversation_id, text, kind, conversation_doc.reference_id, {});
5716
+ report_ai_status('conversations');
5717
+ } catch (err) {
5718
+ report_ai_status('conversations', err);
5719
+ throw err;
5720
+ }
5721
+
5722
+ const out_conversation_item_obj = {
5723
+ _id: await _common.xuda_get_uuid('chat_conversation_item'),
5724
+ stat: 3,
5725
+ docType: 'chat_conversation_item',
5726
+ uid,
5727
+ conversation_type: kind,
5728
+ type: kind,
5729
+ date_created_ts: Date.now(),
5730
+ ts: Date.now(),
5731
+ conversation_id,
5732
+ text,
5733
+ reference_id: conversation_doc.reference_id,
5734
+ conversation_item_reference_id,
5735
+ direction: 'out',
5736
+ role: 'user',
5737
+ read: { [uid]: Date.now() },
5738
+ rtl: _common.detectRTL(text),
5739
+ ...(delivery
5740
+ ? {
5741
+ broadcast: {
5742
+ channel: kind,
5743
+ sent: delivery.sent,
5744
+ failed: delivery.failed,
5745
+ recipients: delivery.results.map((r) => ({ uid: r.uid, name: r.name, ok: r.ok, ...(r.error ? { error: r.error } : {}) })),
5746
+ },
5747
+ }
5748
+ : {}),
5749
+ };
5750
+
5751
+ return await db_module.save_app_couch_doc(app_id, out_conversation_item_obj);
5752
+ } catch (err) {
5753
+ return { code: -15, data: err.message };
5754
+ }
5755
+ };
5756
+
5431
5757
  const chat_email = async function (req, job_id, headers) {
5432
5758
  const { profile_id, uid, email_id, perform_ai_execution = true, from_mailbox, _thread_reentry, direction } = req;
5433
5759
  const account_profile_info = await get_active_account_profile_info(uid, profile_id);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@xuda.io/ai_module",
3
- "version": "1.1.5640",
3
+ "version": "1.1.5642",
4
4
  "description": "Xuda AI Module",
5
5
  "main": "index.mjs",
6
6
  "type": "module",