{
  "version": 3,
  "sources": ["../../../../src/logic-functions/session-closed.ts", "twenty-sdk-define-stub:__twenty-sdk-define-stub__", "../../../../src/constants/universal-identifiers.ts"],
  "sourcesContent": ["import { CoreApiClient } from 'twenty-client-sdk/core';\nimport { defineLogicFunction, RoutePayload } from 'twenty-sdk/define';\n\nimport { SESSION_CLOSED_LF } from 'src/constants/universal-identifiers';\n\n// Talqui -> Twenty session-closed receiver. POST /s/talqui/session-closed.\n//\n// Called by plugin-crms when a Talqui session is closed, carrying the closing\n// data (duration, CSAT, motive, summary). The wire payload is session-based\n// (`{ session }`); upserts by externalId (= Talqui sessionID): a closed signal\n// arriving before/without its started counterpart still creates the row \u2014\n// signals are never dropped.\n\ntype SessionClosedBody = {\n  session?: {\n    sessionID?: string;\n    endedAt?: string;\n    durationSec?: number | null;\n    csat?: number | null;\n    motive?: string;\n    summary?: string;\n  };\n};\n\ntype HandlerResult = {\n  ok: true;\n  processed: boolean;\n  reason?: string;\n  recordId?: string;\n  created?: boolean;\n};\n\nconst MAX_SUMMARY_LENGTH = 16_000;\n\n// CSAT arrives as 1-5 (or null = not rated); the RATING field stores\n// enum values RATING_1..RATING_5. 0/undefined means \"not rated\" -> null.\nconst toRating = (csat: number | null | undefined): string | null => {\n  if (typeof csat !== 'number' || csat < 1) return null;\n  return `RATING_${Math.min(5, Math.round(csat))}`;\n};\n\nexport const handler = async (\n  payload: RoutePayload<SessionClosedBody>,\n): Promise<HandlerResult> => {\n  const session = payload.body?.session;\n  const sessionID = session?.sessionID?.trim();\n  const endedAt = session?.endedAt;\n\n  if (!sessionID || !endedAt) {\n    console.warn(\n      `session-closed: invalid payload (sessionID=${sessionID ?? 'missing'})`,\n    );\n    return { ok: true, processed: false, reason: 'invalid_payload' };\n  }\n\n  const summary = session.summary;\n  if (summary && summary.length > MAX_SUMMARY_LENGTH) {\n    console.warn(\n      `session-closed: summary truncated sessionID=${sessionID} from=${summary.length}`,\n    );\n  }\n\n  const data: Record<string, unknown> = {\n    endedAt,\n    durationSec:\n      typeof session.durationSec === 'number' ? Math.max(0, session.durationSec) : null,\n    csatScore: toRating(session.csat),\n    serviceMotive: session.motive ?? '',\n    summary: summary ? summary.slice(0, MAX_SUMMARY_LENGTH) : '',\n  };\n\n  const client = new CoreApiClient() as any;\n\n  const lookup = await client\n    .query({\n      session: {\n        __args: { filter: { externalId: { eq: sessionID } } },\n        id: true,\n      },\n    })\n    .catch(() => null);\n  const existingId: string | undefined = lookup?.session?.id;\n\n  try {\n    if (existingId) {\n      await client.mutation({\n        updateSession: {\n          __args: { id: existingId, data },\n          id: true,\n        },\n      });\n      console.log(`session-closed: closed sessionID=${sessionID} recordId=${existingId}`);\n      return { ok: true, processed: true, recordId: existingId, created: false };\n    }\n\n    // Closed arriving before started (out-of-order delivery) \u2014 create the\n    // row with whatever we have instead of dropping the signal.\n    console.warn(`session-closed: closed_without_started sessionID=${sessionID}`);\n    const created = await client.mutation({\n      createSession: {\n        __args: {\n          data: {\n            name: `Conversation ${sessionID}`,\n            externalId: sessionID,\n            ...data,\n          },\n        },\n        id: true,\n      },\n    });\n    const recordId: string = created.createSession.id;\n    return { ok: true, processed: true, recordId, created: true };\n  } catch (err) {\n    console.error(\n      `session-closed: upsert failed sessionID=${sessionID}: ${\n        err instanceof Error ? err.message : String(err)\n      }`,\n    );\n    return { ok: true, processed: false, reason: 'upsert_failed' };\n  }\n};\n\nexport default defineLogicFunction({\n  universalIdentifier: SESSION_CLOSED_LF,\n  name: 'session-closed',\n  description:\n    'Receives session-closed signals from plugin-crms and updates (or creates) the matching Conversation by externalId (= Talqui sessionID).',\n  timeoutSeconds: 30,\n  handler,\n  httpRouteTriggerSettings: {\n    path: '/talqui/session-closed',\n    httpMethod: 'POST',\n    isAuthRequired: true,\n  },\n});\n", "\n// Auto-generated stub for twenty-sdk/define injected by the SDK CLI build.\n// Real implementations would pull in zod, twenty-shared and ~1MB of code; at\n// runtime only `default.config.handler` is consumed, so tiny no-ops suffice.\nconst __defineFactoryStub = (config) => ({\n  success: true,\n  config,\n  errors: [],\n});\n\nconst __anyHandler = {\n  get(_target, prop) {\n    if (prop === '__esModule') return true;\n    if (prop === Symbol.toPrimitive) return () => '';\n    if (typeof prop === 'symbol') return undefined;\n    return new Proxy(() => undefined, __anyHandler);\n  },\n  apply() {\n    return new Proxy(() => undefined, __anyHandler);\n  },\n};\nconst __anyStub = new Proxy(() => undefined, __anyHandler);\n\nexport const createValidationResult = __defineFactoryStub;\nexport const defineAgent = __defineFactoryStub;\nexport const defineApplication = __defineFactoryStub;\nexport const defineApplicationRole = __defineFactoryStub;\nexport const defineCommandMenuItem = __defineFactoryStub;\nexport const defineConnectionProvider = __defineFactoryStub;\nexport const defineField = __defineFactoryStub;\nexport const defineFrontComponent = __defineFactoryStub;\nexport const defineIndex = __defineFactoryStub;\nexport const defineLogicFunction = __defineFactoryStub;\nexport const defineNavigationMenuItem = __defineFactoryStub;\nexport const defineObject = __defineFactoryStub;\nexport const definePageLayout = __defineFactoryStub;\nexport const definePageLayoutTab = __defineFactoryStub;\nexport const definePermissionFlag = __defineFactoryStub;\nexport const definePostInstallLogicFunction = __defineFactoryStub;\nexport const definePreInstallLogicFunction = __defineFactoryStub;\nexport const defineRole = __defineFactoryStub;\nexport const defineSkill = __defineFactoryStub;\nexport const defineView = __defineFactoryStub;\nexport const defineViewField = __defineFactoryStub;\nexport const AggregateOperations = __anyStub;\nexport const DateDisplayFormat = __anyStub;\nexport const FieldMetadataSettingsOnClickAction = __anyStub;\nexport const FieldType = __anyStub;\nexport const HTTPMethod = __anyStub;\nexport const NavigationMenuItemType = __anyStub;\nexport const NumberDataType = __anyStub;\nexport const ObjectRecordGroupByDateGranularity = __anyStub;\nexport const OnDeleteAction = __anyStub;\nexport const PageLayoutTabLayoutMode = __anyStub;\nexport const PageLayoutType = __anyStub;\nexport const RelationType = __anyStub;\nexport const RowLevelPermissionPredicateGroupLogicalOperator = __anyStub;\nexport const RowLevelPermissionPredicateOperand = __anyStub;\nexport const STANDARD_OBJECT = __anyStub;\nexport const STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS = __anyStub;\nexport const STANDARD_PAGE_LAYOUT = __anyStub;\nexport const STANDARD_PAGE_LAYOUT_UNIVERSAL_IDENTIFIERS = __anyStub;\nexport const SystemPermissionFlag = __anyStub;\nexport const ViewCalendarLayout = __anyStub;\nexport const ViewFilterGroupLogicalOperator = __anyStub;\nexport const ViewFilterOperand = __anyStub;\nexport const ViewKey = __anyStub;\nexport const ViewOpenRecordIn = __anyStub;\nexport const ViewSortDirection = __anyStub;\nexport const ViewType = __anyStub;\nexport const ViewVisibility = __anyStub;\nexport const canAccessFullAdminPanel = __anyStub;\nexport const canImpersonate = __anyStub;\nexport const every = __anyStub;\nexport const everyDefined = __anyStub;\nexport const everyEquals = __anyStub;\nexport const favoriteRecordIds = __anyStub;\nexport const featureFlags = __anyStub;\nexport const generateDefaultFieldUniversalIdentifier = __anyStub;\nexport const hasAnySoftDeleteFilterOnView = __anyStub;\nexport const includes = __anyStub;\nexport const includesEvery = __anyStub;\nexport const isDashboardPageLayoutInEditMode = __anyStub;\nexport const isDefined = __anyStub;\nexport const isInSidePanel = __anyStub;\nexport const isLayoutCustomizationModeEnabled = __anyStub;\nexport const isNonEmptyString = __anyStub;\nexport const isSelectAll = __anyStub;\nexport const none = __anyStub;\nexport const noneDefined = __anyStub;\nexport const noneEquals = __anyStub;\nexport const numberOfSelectedRecords = __anyStub;\nexport const objectMetadataItem = __anyStub;\nexport const objectMetadataLabel = __anyStub;\nexport const objectPermissions = __anyStub;\nexport const pageType = __anyStub;\nexport const selectedRecords = __anyStub;\nexport const some = __anyStub;\nexport const someDefined = __anyStub;\nexport const someEquals = __anyStub;\nexport const someNonEmptyString = __anyStub;\nexport const targetObjectReadPermissions = __anyStub;\nexport const targetObjectWritePermissions = __anyStub;\nexport const validateFields = __anyStub;\n", "export const APP_DISPLAY_NAME = 'Talqui Chat';\nexport const APP_DESCRIPTION = 'AI-powered chat for Webchat, WhatsApp and Instagram integrated to your CRM';\nexport const APPLICATION_UNIVERSAL_IDENTIFIER = '7880eb3d-e2cd-4396-a9ed-159b1eecb2fd';\nexport const DEFAULT_ROLE_UNIVERSAL_IDENTIFIER = 'ca41b3b3-bd0d-431c-bfa2-dfc110719b98';\nexport const MAIN_PAGE_FRONT_COMPONENT_UNIVERSAL_IDENTIFIER =\n  'a953a135-ed35-4c4d-81df-bbcc492c93a1';\nexport const MAIN_PAGE_LAYOUT_UNIVERSAL_IDENTIFIER = 'd7d70821-9c3c-43ee-8434-b7d043c4dc13';\nexport const MAIN_PAGE_LAYOUT_TAB_UNIVERSAL_IDENTIFIER = '56cfc3dd-a891-45a8-a65d-296dcd5911aa';\nexport const MAIN_PAGE_WIDGET_UNIVERSAL_IDENTIFIER = '790e051c-7c53-48fc-a87f-425caf118b94';\nexport const MAIN_PAGE_NAVIGATION_MENU_ITEM_UNIVERSAL_IDENTIFIER =\n  '9f21b9d7-93d9-45d5-a58d-ee03e745a3f9';\n\n// Session object + scalar fields (labelled \"Conversation\" in the UI).\n// NOTE: universalIdentifiers are frozen \u2014 only the const names changed.\nexport const SESSION_UNIVERSAL_IDENTIFIER = 'ce892978-2336-4d31-a146-85872d516fce';\nexport const SESSION_NAME_FIELD = '9eb6fdec-ea9a-4351-bd2d-01c38482095d';\nexport const SESSION_CHANNEL_FIELD = 'e1e4fb47-53de-42c3-925c-e5b07dfb2f89';\nexport const SESSION_STARTED_AT_FIELD = '8a33e25f-1dd8-490a-8ace-2fb44d861242';\nexport const SESSION_ENDED_AT_FIELD = '4f506405-2d82-465b-8837-6e4451bba7fa';\nexport const SESSION_DURATION_SEC_FIELD = '002aa75b-b157-4958-9e7f-cb306dfd15fd';\nexport const SESSION_URL_FIELD = '8a33b10d-5fa2-4089-8dc3-8c6a7af6b9fb';\nexport const SESSION_EXTERNAL_ID_FIELD = 'd1e630df-82b5-4098-9f3c-220325d18b94';\nexport const SESSION_SUMMARY_FIELD = '769554b2-6dd6-4150-91c5-2179a506ca11';\nexport const SESSION_CSAT_SCORE_FIELD = 'a887b5b7-755b-4dce-b620-0d3af6e553f6';\nexport const SESSION_MOTIVE_FIELD = '4193c755-ef60-425e-90ef-1c754e26c1d3';\n\n// Relations: session <-> standard objects\nexport const SESSION_PERSON_FIELD = '7951fe9d-9e1e-40b0-8a01-0320050bc2c6';\nexport const PERSON_SESSIONS_FIELD = '2d7a0882-1c70-4563-a70a-ef75f12aec89';\nexport const SESSION_COMPANY_FIELD = '08cba9e4-ca7a-4446-94f2-9d9077c55995';\nexport const COMPANY_SESSIONS_FIELD = 'b4b50b67-9949-4c86-9903-872f6fd22e6e';\n// Opportunity <-> Conversation, INVERTED (2026-07-04): 1 Opportunity -> N\n// Conversations. The FK now lives on the Conversation (session.opportunity,\n// joinColumn `opportunityId`); the opportunity exposes the O2M `sessions`. The\n// previous pair (FK on Opportunity, 1 conversation max) is retired \u2014 its old\n// universalIdentifiers are dropped by the sync, the new ones created.\nexport const OPPORTUNITY_SESSIONS_FIELD = '6655c67b-8fc9-47c8-a304-4d4a23f66591';\nexport const SESSION_OPPORTUNITY_FIELD = 'aadd5275-e156-4a3e-a9fc-673064a8ee0f';\n\n// Scalar field added to the standard Opportunity object: the Talqui lead id\n// (sessionMeta.tlID), the natural key the events bridge upserts opportunities by.\n// Field-level isUnique enforces one opportunity per tlid (a standalone index on a\n// standard object is rejected by the app sync).\nexport const OPPORTUNITY_TALQUI_LEAD_ID_FIELD = 'ed405d4a-9de1-490f-beb6-f3e9550ce404';\n\n// Attribution added to the app-owned Conversation (session) object. Because the\n// object and its fields ship with the app, every installer auto-provisions them\n// \u2014 no manual field creation, and the events bridge writes them via the\n// session-started logic function (no custom-field validation risk). Populated\n// from the CTWA referral (session.sessionMeta.referral*): utm* are the base UTM\n// slots (source=instagram, medium=paid_social; campaign deferred to the CAPI\n// phase), metaAdId is the ad id, ctwaClid is the Meta click id (CAPI key), and\n// talquiLeadId mirrors the opportunity key when the session carries one.\nexport const SESSION_UTM_SOURCE_FIELD = 'b52bd069-bf24-439d-a44a-89e30203961d';\nexport const SESSION_UTM_MEDIUM_FIELD = '5726e06c-bd05-470f-b557-6b0164640e03';\nexport const SESSION_UTM_CONTENT_FIELD = 'bd8cc3e5-8a21-4492-bfc4-67d0ec26d1be';\nexport const SESSION_UTM_CAMPAIGN_FIELD = 'cd05059c-f12e-497d-8104-f8b4073c91b3';\nexport const SESSION_META_AD_ID_FIELD = '3098d356-7374-4f95-bb9b-3c715684a2b5';\nexport const SESSION_CTWA_CLID_FIELD = '1bab2e39-1166-449d-ba6f-99bc48edee29';\nexport const SESSION_TALQUI_LEAD_ID_FIELD = 'e742d1d0-69ba-4303-8c62-994686c4cdf5';\n\n// SessionNote object + fields + relation pair\nexport const SESSION_NOTE_UNIVERSAL_IDENTIFIER = '66434c3c-6581-4777-9c6a-64fb0bbc3feb';\nexport const SESSION_NOTE_NAME_FIELD = 'e9188436-664f-4862-aced-4e747f3aee31';\nexport const SESSION_NOTE_CREATED_AT_FIELD = 'b91e5608-871f-4f52-b3e0-a40afd96bc58';\nexport const SESSION_NOTE_BODY_FIELD = 'c4599c9f-49b5-4438-adc4-8d5463f146ac';\nexport const SESSION_NOTE_SESSION_FIELD = '866ffb7a-dfbf-416e-8423-b0e3479c34d2';\nexport const SESSION_NOTES_FIELD = '7cdd103c-9c5d-4963-b6fe-3100e87c4963';\n\n// Navigation + views\nexport const ALL_SESSIONS_VIEW = 'd730029c-e0b0-4e12-8212-394ebbafad46';\nexport const SESSIONS_NAV_MENU_ITEM = '0cefb558-0031-4d6e-b2d6-1e3e879ae10b';\n\n// Logic functions (session signals bridge)\nexport const SESSION_STARTED_LF = '040ff442-79d4-4c4a-9af5-5f3c64058776';\nexport const SESSION_CLOSED_LF = '7a4d6297-5b70-4fea-ac71-706c247472f2';\nexport const SESSION_SUMMARIZED_LF = '995758ca-84dd-41ca-a284-690acade82c4';\n// DB-event LF: links an ad-hoc (widget/manual) opportunity to the contact's\n// most-recent free conversation on create.\nexport const OPPORTUNITY_CREATED_LF = '7b7be604-8f33-420f-9332-fe0e54fdf611';\n\n// \"Conversations\" tab on Person/Company/Opportunity record pages.\nexport const PERSON_RECORD_PAGE_LAYOUT = '20202020-a102-4002-8002-ae0a1ea11002';\nexport const COMPANY_RECORD_PAGE_LAYOUT = '20202020-a101-4001-8001-c0aba11c0001';\nexport const OPPORTUNITY_RECORD_PAGE_LAYOUT = '20202020-a103-4003-8003-0aa0b1ca1003';\n\n// Each object gets a page-layout-tab + a FIELD widget bound to the relation.\nexport const PERSON_CONVERSATIONS_TAB = 'c746f718-a089-4d74-8568-c7a1d091e322';\nexport const PERSON_CONVERSATIONS_WIDGET = '9c3914ca-5e11-430e-b5e0-7bdfcb61915b';\nexport const COMPANY_CONVERSATIONS_TAB = 'a406030c-abc9-4495-898a-45fc1910f594';\nexport const COMPANY_CONVERSATIONS_WIDGET = 'df424ee9-2c7d-4072-8ba6-20cc1e9cc099';\nexport const OPPORTUNITY_CONVERSATIONS_TAB = '97bacaa2-c8c6-48b4-8223-501ee41bce6c';\nexport const OPPORTUNITY_CONVERSATIONS_WIDGET = '6c35200e-c92b-4a93-8b88-b2c165409a20';\n"],
  "mappings": "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",
  "names": []
}
