@aglyn/plugins-ai 1.0.0-beta.250 → 1.0.0-beta.253

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 (126) hide show
  1. package/package.json +17 -17
  2. package/src/lib/components/ai-brief-dialog.component.js +15 -1
  3. package/src/lib/components/ai-brief-dialog.component.js.map +1 -1
  4. package/src/lib/components/ai-job-links.js +1 -1
  5. package/src/lib/components/ai-job-links.js.map +1 -1
  6. package/src/lib/components/ai-job-plan.component.js +20 -2
  7. package/src/lib/components/ai-job-plan.component.js.map +1 -1
  8. package/src/lib/components/ai-jobs-store.d.ts +11 -0
  9. package/src/lib/components/ai-jobs-store.js +22 -0
  10. package/src/lib/components/ai-jobs-store.js.map +1 -1
  11. package/src/lib/jobs/ai-job-crm-step.js +7 -4
  12. package/src/lib/jobs/ai-job-crm-step.js.map +1 -1
  13. package/src/lib/jobs/ai-job-drafts.d.ts +24 -0
  14. package/src/lib/jobs/ai-job-drafts.js +46 -0
  15. package/src/lib/jobs/ai-job-drafts.js.map +1 -1
  16. package/src/lib/jobs/ai-job-experiment-step.js +6 -1
  17. package/src/lib/jobs/ai-job-experiment-step.js.map +1 -1
  18. package/src/lib/jobs/ai-job-form-dataset.d.ts +6 -1
  19. package/src/lib/jobs/ai-job-form-dataset.js +7 -2
  20. package/src/lib/jobs/ai-job-form-dataset.js.map +1 -1
  21. package/src/lib/jobs/ai-job-form-reuse.d.ts +78 -0
  22. package/src/lib/jobs/ai-job-form-reuse.js +136 -0
  23. package/src/lib/jobs/ai-job-form-reuse.js.map +1 -0
  24. package/src/lib/jobs/ai-job-form-step.d.ts +17 -0
  25. package/src/lib/jobs/ai-job-form-step.js +63 -11
  26. package/src/lib/jobs/ai-job-form-step.js.map +1 -1
  27. package/src/lib/jobs/ai-job-insight-step.js +6 -1
  28. package/src/lib/jobs/ai-job-insight-step.js.map +1 -1
  29. package/src/lib/jobs/ai-job-page-language.d.ts +10 -5
  30. package/src/lib/jobs/ai-job-page-language.js +10 -5
  31. package/src/lib/jobs/ai-job-page-language.js.map +1 -1
  32. package/src/lib/jobs/ai-job-plan-step.d.ts +32 -1
  33. package/src/lib/jobs/ai-job-plan-step.js +53 -2
  34. package/src/lib/jobs/ai-job-plan-step.js.map +1 -1
  35. package/src/lib/jobs/ai-job-seo-step.js +5 -3
  36. package/src/lib/jobs/ai-job-seo-step.js.map +1 -1
  37. package/src/lib/jobs/ai-job-site-content.d.ts +7 -5
  38. package/src/lib/jobs/ai-job-site-content.js +7 -5
  39. package/src/lib/jobs/ai-job-site-content.js.map +1 -1
  40. package/src/lib/jobs/ai-job-site-datasets.d.ts +1 -1
  41. package/src/lib/jobs/ai-job-site-datasets.js +2 -2
  42. package/src/lib/jobs/ai-job-site-datasets.js.map +1 -1
  43. package/src/lib/jobs/ai-job-site-step.js +2 -1
  44. package/src/lib/jobs/ai-job-site-step.js.map +1 -1
  45. package/src/lib/jobs/ai-job-text-step.js +6 -3
  46. package/src/lib/jobs/ai-job-text-step.js.map +1 -1
  47. package/src/lib/jobs/ai-layout-stock-photos.d.ts +11 -7
  48. package/src/lib/jobs/ai-layout-stock-photos.js +25 -10
  49. package/src/lib/jobs/ai-layout-stock-photos.js.map +1 -1
  50. package/src/lib/jobs/ai-stock-business-nouns.d.ts +62 -0
  51. package/src/lib/jobs/ai-stock-business-nouns.js +264 -0
  52. package/src/lib/jobs/ai-stock-business-nouns.js.map +1 -0
  53. package/src/lib/layout-language/ai-layout-language.js +5 -1
  54. package/src/lib/layout-language/ai-layout-language.js.map +1 -1
  55. package/src/lib/layout-language/ai-layout-links.js +5 -2
  56. package/src/lib/layout-language/ai-layout-links.js.map +1 -1
  57. package/src/lib/model/ai-build-plan.d.ts +7 -3
  58. package/src/lib/model/ai-build-plan.js +11 -1
  59. package/src/lib/model/ai-build-plan.js.map +1 -1
  60. package/src/lib/model/ai-credit-estimate.d.ts +39 -19
  61. package/src/lib/model/ai-credit-estimate.js +43 -19
  62. package/src/lib/model/ai-credit-estimate.js.map +1 -1
  63. package/src/lib/model/ai-job-activity.d.ts +30 -0
  64. package/src/lib/model/ai-job-activity.js +35 -0
  65. package/src/lib/model/ai-job-activity.js.map +1 -1
  66. package/src/lib/model/ai-media-credits.d.ts +5 -3
  67. package/src/lib/model/ai-media-credits.js +7 -5
  68. package/src/lib/model/ai-media-credits.js.map +1 -1
  69. package/src/lib/model/ai-site-job.js +6 -3
  70. package/src/lib/model/ai-site-job.js.map +1 -1
  71. package/src/lib/model/ai-site-start-seo.d.ts +11 -2
  72. package/src/lib/model/ai-site-start-seo.js +29 -15
  73. package/src/lib/model/ai-site-start-seo.js.map +1 -1
  74. package/src/lib/model/ai-site-store-pages.d.ts +5 -0
  75. package/src/lib/model/ai-site-store-pages.js +2 -0
  76. package/src/lib/model/ai-site-store-pages.js.map +1 -1
  77. package/src/lib/plugin-config.js +2 -2
  78. package/src/lib/plugin-config.js.map +1 -1
  79. package/src/lib/providers/anthropic-crm-wording-superseded.d.ts +29 -0
  80. package/src/lib/providers/anthropic-crm-wording-superseded.js +31 -0
  81. package/src/lib/providers/anthropic-crm-wording-superseded.js.map +1 -0
  82. package/src/lib/providers/anthropic.js +29 -7
  83. package/src/lib/providers/anthropic.js.map +1 -1
  84. package/src/lib/providers/catalog.d.ts +24 -17
  85. package/src/lib/providers/catalog.js +63 -30
  86. package/src/lib/providers/catalog.js.map +1 -1
  87. package/src/lib/providers/contract.d.ts +19 -1
  88. package/src/lib/providers/contract.js +10 -0
  89. package/src/lib/providers/contract.js.map +1 -1
  90. package/src/lib/providers/model-choice.d.ts +1 -1
  91. package/src/lib/providers/model-choice.js +39 -30
  92. package/src/lib/providers/model-choice.js.map +1 -1
  93. package/src/lib/providers/routing.d.ts +20 -1
  94. package/src/lib/providers/routing.js +71 -12
  95. package/src/lib/providers/routing.js.map +1 -1
  96. package/src/lib/runtime/ai-dev-env.d.ts +58 -1
  97. package/src/lib/runtime/ai-dev-env.js +86 -6
  98. package/src/lib/runtime/ai-dev-env.js.map +1 -1
  99. package/src/lib/runtime/ai-dev-replay.d.ts +4 -0
  100. package/src/lib/runtime/ai-dev-replay.js +4 -0
  101. package/src/lib/runtime/ai-dev-replay.js.map +1 -1
  102. package/src/lib/runtime/ai-doctrine-validators.js +13 -3
  103. package/src/lib/runtime/ai-doctrine-validators.js.map +1 -1
  104. package/src/lib/runtime/ai-doctrine.js +8 -4
  105. package/src/lib/runtime/ai-doctrine.js.map +1 -1
  106. package/src/lib/runtime/ai-eval-live.d.ts +43 -1
  107. package/src/lib/runtime/ai-eval-live.js +79 -9
  108. package/src/lib/runtime/ai-eval-live.js.map +1 -1
  109. package/src/lib/runtime/ai-live-batch.d.ts +20 -4
  110. package/src/lib/runtime/ai-live-batch.js +57 -10
  111. package/src/lib/runtime/ai-live-batch.js.map +1 -1
  112. package/src/lib/runtime/ai-palette.generated.js +4 -0
  113. package/src/lib/runtime/ai-palette.generated.js.map +1 -1
  114. package/src/lib/runtime/ai-runtime.js +21 -4
  115. package/src/lib/runtime/ai-runtime.js.map +1 -1
  116. package/src/lib/runtime/seo-fields.js +4 -3
  117. package/src/lib/runtime/seo-fields.js.map +1 -1
  118. package/src/lib/server/ai-assist.js +3 -2
  119. package/src/lib/server/ai-assist.js.map +1 -1
  120. package/src/lib/server/ai-media-image.d.ts +5 -1
  121. package/src/lib/server/ai-media-image.js +6 -2
  122. package/src/lib/server/ai-media-image.js.map +1 -1
  123. package/src/lib/server/ai-media-svg.js +4 -0
  124. package/src/lib/server/ai-media-svg.js.map +1 -1
  125. package/src/lib/usage/ai-allotment-alerts.js +2 -5
  126. package/src/lib/usage/ai-allotment-alerts.js.map +1 -1
@@ -208,6 +208,9 @@ function entryFor(uid, orgId, user) {
208
208
  }, [
209
209
  entry
210
210
  ]);
211
+ return useEntryJobs(entry);
212
+ }
213
+ /** An entry's jobs, kept current as the entry moves. */ function useEntryJobs(entry) {
211
214
  const subscribe = useMemo(()=>(listener)=>{
212
215
  if (!entry) return ()=>undefined;
213
216
  entry.listeners.add(listener);
@@ -223,6 +226,25 @@ function entryFor(uid, orgId, user) {
223
226
  };
224
227
  return useSyncExternalStore(subscribe, snapshot, snapshot);
225
228
  }
229
+ /**
230
+ * The same list, as the surfaces that read it hold it, without reading it
231
+ * here (AGL-3765): for a door that warns from what is already known — the
232
+ * top-bar indicator and the Assist panel read the list wherever a person can
233
+ * start a job — and asks no server of its own. It moves with every read they
234
+ * make and every job a surface hands in through {@link publishAiJob}; empty
235
+ * where nothing has read it, and when `enabled` is false.
236
+ */ export function useAiJobsInFlightKnown(user, orgId, enabled = true) {
237
+ var _ref;
238
+ const uid = (_ref = user == null ? void 0 : user.uid) != null ? _ref : null;
239
+ const userRef = useRef(user);
240
+ userRef.current = user;
241
+ const entry = useMemo(()=>enabled && uid && orgId ? entryFor(uid, orgId, userRef.current) : null, [
242
+ enabled,
243
+ uid,
244
+ orgId
245
+ ]);
246
+ return useEntryJobs(entry);
247
+ }
226
248
  /** What the indicator and the launcher show for the workspace, or `null` for nothing. */ export function useAiJobsActivity(user, orgId, enabled = true) {
227
249
  const jobs = useAiJobsInFlight(user, orgId, enabled);
228
250
  return useMemo(()=>aiJobsActivity(jobs), [
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../../../../../libs/plugins/ai/src/lib/components/ai-jobs-store.ts"],"sourcesContent":["/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n'use client'\n\nimport {\n authorizedFetch,\n type MaybeTokenSource,\n type TokenSource,\n} from '@aglyn/shared-util-http/authorized-token'\nimport { useEffect, useMemo, useRef, useSyncExternalStore } from 'react'\nimport type { AiFreeCreditsLeft } from '../model/ai-site-job'\nimport { aiJobsActivity, type AiJobsActivity } from '../model/ai-job-activity'\nimport { AI_JOB_TERMINAL_STATUSES, type AiJobSummary } from '../model/ai-jobs.types'\n\n/**\n * WHERE A PERSON FINDS THEIR AI JOBS (AGL-3593).\n *\n * Two things every AI surface in the console shares, kept in this module\n * rather than in any one component because the surfaces that need them — a\n * brief dialog on the Screens page, the guided start's full-screen dialog,\n * the top-bar indicator, the Assist launcher and the drawer inside the\n * panel — are mounted in different zones and know nothing of one another.\n * All of them are this plugin's, so no shell, zone or event bus carries it.\n *\n * ── Opening AI jobs ──────────────────────────────────────────────────────\n *\n * {@link openAiJobs} is the one action every \"Open AI jobs\" button, the\n * indicator and a notification's link take: the Assist panel subscribes, opens\n * itself, expands its AI jobs drawer, and scrolls to and highlights the job\n * named. A request is a sequence number as well as a job, so asking twice for\n * the same job opens it twice.\n *\n * ── The workspace's unsettled jobs ───────────────────────────────────────\n *\n * The indicator, the launcher's badge and the drawer's open-by-default rule\n * read the same list: the workspace's jobs that are not yet settled, from\n * the jobs route's `status=active` (a Firestore `in` query under the\n * (status, createdAt) index, never a filter of a loaded window). ONE list per\n * person and workspace however many surfaces read it, re-read on a timer only\n * while something in it is still moving or waiting and the tab is in view —\n * a workspace with nothing running costs one read per page load and no more.\n *\n * Every surface that learns a job's state some other way — the dialog that\n * created it, the drawer's event stream, the in-dialog confirm — hands it in\n * through {@link publishAiJob}, so the indicator moves the moment the job\n * does rather than on the next read.\n */\n\n// ── Opening AI jobs ───────────────────────────────────────────────────────\n\n/** The latest request to open AI jobs; `seq` is 0 until anything asked. */\nexport interface AiJobsOpenRequest {\n seq: number\n /** The job to scroll to and highlight, or `null` for the drawer alone. */\n jobId: string | null\n}\n\nlet openRequest: AiJobsOpenRequest = { seq: 0, jobId: null }\nconst openListeners = new Set<() => void>()\n\n/**\n * Opens the Assist panel on its AI jobs drawer, expanded, scrolled to\n * `jobId` when one is named. A surface that covers the panel — a dialog —\n * closes itself beside the call; this opens, it does not close anything.\n */\nexport function openAiJobs(options: { jobId?: string | null } = {}): void {\n openRequest = { seq: openRequest.seq + 1, jobId: options.jobId ?? null }\n for (const listener of openListeners) listener()\n}\n\nfunction subscribeOpen(listener: () => void): () => void {\n openListeners.add(listener)\n return () => {\n openListeners.delete(listener)\n }\n}\n\nconst readOpen = (): AiJobsOpenRequest => openRequest\n\n/** The latest open request, for the panel; a new `seq` is a new request. */\nexport function useAiJobsOpenRequest(): AiJobsOpenRequest {\n return useSyncExternalStore(subscribeOpen, readOpen, readOpen)\n}\n\n/**\n * The query parameter a notification's link carries: a console page opened\n * with `?aiJob={id}` opens AI jobs on that job, the way pressing the\n * indicator does.\n */\nexport { AI_JOB_LINK_PARAM } from '../model/ai-job-notice'\n\n// ── The workspace's unsettled jobs ────────────────────────────────────────\n\n/** How often a list with something in it is read again, while the tab is in view. */\nexport const AI_JOBS_ACTIVE_POLL_MS = 15_000\n\n/** The most unsettled jobs one read lists; a glance, not an archive. */\nconst ACTIVE_LIMIT = 20\n\nconst NONE: readonly AiJobSummary[] = Object.freeze([])\n\ninterface Entry {\n jobs: readonly AiJobSummary[]\n /** Who reads it, kept current by whichever surface subscribed last. */\n user: MaybeTokenSource\n orgId: string\n uid: string\n subscribers: number\n inflight: boolean\n timer: ReturnType<typeof setTimeout> | null\n /** The route said the feature is not this workspace's: nothing is read again. */\n closed: boolean\n listeners: Set<() => void>\n}\n\nconst entries = new Map<string, Entry>()\n\n/**\n * Who hears of a read as it starts and what a Free workspace has left when it\n * lands (AGL-3722): the usage strip's store follows the jobs list this way\n * rather than by import, so this module stays out of the strip's chunk.\n */\nexport interface AiJobsReadWatcher {\n start: (uid: string, orgId: string) => void\n done: (uid: string, orgId: string, credits: AiFreeCreditsLeft | undefined) => void\n}\nconst readWatchers = new Set<AiJobsReadWatcher>()\n\n/** Listen to the jobs list's reads; returns the way to stop. */\nexport function watchAiJobsReads(watcher: AiJobsReadWatcher): () => void {\n readWatchers.add(watcher)\n return () => {\n readWatchers.delete(watcher)\n }\n}\n\nconst keyOf = (uid: string, orgId: string): string => `${uid}\\n${orgId}`\n\nfunction emit(entry: Entry): void {\n for (const listener of entry.listeners) listener()\n}\n\nfunction isSettled(job: AiJobSummary): boolean {\n return AI_JOB_TERMINAL_STATUSES.includes(job.status)\n}\n\nfunction schedule(entry: Entry): void {\n if (entry.timer) clearTimeout(entry.timer)\n entry.timer = null\n if (entry.closed || entry.subscribers === 0 || entry.jobs.length === 0) return\n entry.timer = setTimeout(() => {\n entry.timer = null\n const hidden = typeof document !== 'undefined' && document.visibilityState === 'hidden'\n // A hidden tab reads nothing; coming back into view reads at once.\n if (hidden) return\n void read(entry)\n }, AI_JOBS_ACTIVE_POLL_MS)\n}\n\nasync function read(entry: Entry): Promise<void> {\n if (entry.inflight || entry.closed) return\n entry.inflight = true\n for (const watcher of readWatchers) watcher.start(entry.uid, entry.orgId)\n try {\n const response = await authorizedFetch(\n entry.user,\n `/api/ai/jobs?orgId=${encodeURIComponent(entry.orgId)}&status=active&limit=${ACTIVE_LIMIT}`,\n )\n if (response.status === 404 || response.status === 403) {\n // The release flag off, or a plan or member without generation: there\n // is nothing to count, now or on the next tick.\n entry.closed = true\n entry.jobs = NONE\n emit(entry)\n return\n }\n if (!response.ok) return\n const payload = (await response.json().catch(() => null)) as {\n jobs?: AiJobSummary[]\n freeCredits?: AiFreeCreditsLeft\n } | null\n // The same read carries what a Free workspace has left, so the usage\n // strip follows jobs that spend between chat messages (AGL-3722).\n for (const watcher of readWatchers) watcher.done(entry.uid, entry.orgId, payload?.freeCredits)\n entry.jobs = Object.freeze((payload?.jobs ?? []).filter((job) => !isSettled(job)))\n emit(entry)\n } catch {\n // An unreachable route keeps what was last read; the next tick asks again.\n } finally {\n entry.inflight = false\n schedule(entry)\n }\n}\n\nfunction entryFor(uid: string, orgId: string, user: MaybeTokenSource): Entry {\n const key = keyOf(uid, orgId)\n let entry = entries.get(key)\n if (!entry) {\n entry = {\n jobs: NONE,\n user,\n orgId,\n uid,\n subscribers: 0,\n inflight: false,\n timer: null,\n closed: false,\n listeners: new Set(),\n }\n entries.set(key, entry)\n }\n return entry\n}\n\n/**\n * Hands a job's latest state to every surface counting this workspace's jobs:\n * a new job joins the list, a settled one leaves it. Keyed on the job's own\n * workspace, for every person whose list holds that workspace in this tab.\n */\nexport function publishAiJob(job: AiJobSummary | null | undefined): void {\n if (!job?.orgId) return\n for (const entry of entries.values()) {\n if (entry.orgId !== job.orgId || entry.closed) continue\n const index = entry.jobs.findIndex((known) => known.id === job.id)\n let next: AiJobSummary[]\n if (isSettled(job)) {\n if (index === -1) continue\n next = entry.jobs.filter((known) => known.id !== job.id)\n // A job that just settled has just spent: read what is left (AGL-3722).\n if (entry.subscribers > 0) void read(entry)\n } else if (index === -1) {\n next = [job, ...entry.jobs]\n } else {\n next = [...entry.jobs]\n next[index] = job\n }\n entry.jobs = Object.freeze(next)\n emit(entry)\n // A list that just gained its first job starts its timer.\n if (!entry.timer && !entry.inflight) schedule(entry)\n }\n}\n\n/** Forget every list and request; specs start each case from nothing. */\nexport function resetAiJobsStoreForTests(): void {\n for (const entry of entries.values()) {\n if (entry.timer) clearTimeout(entry.timer)\n }\n entries.clear()\n openListeners.clear()\n readWatchers.clear()\n openRequest = { seq: 0, jobId: null }\n}\n\n/**\n * The workspace's jobs that are not yet settled, newest first, for the\n * signed-in person — empty while the first read is in flight, where the\n * route refuses, and when `enabled` is false (a surface whose own gates have\n * not passed reads nothing at all).\n */\nexport function useAiJobsInFlight(\n user: (TokenSource & { uid?: string | null }) | null | undefined,\n orgId: string | null | undefined,\n enabled = true,\n): readonly AiJobSummary[] {\n const uid = user?.uid ?? null\n const userRef = useRef<MaybeTokenSource>(user)\n userRef.current = user\n const entry = useMemo(\n () => (enabled && uid && orgId ? entryFor(uid, orgId, userRef.current) : null),\n [enabled, uid, orgId],\n )\n\n useEffect(() => {\n if (!entry) return undefined\n entry.user = userRef.current\n entry.subscribers += 1\n if (entry.subscribers === 1) void read(entry)\n const onVisible = () => {\n if (document.visibilityState === 'visible' && entry.jobs.length > 0) void read(entry)\n }\n document.addEventListener('visibilitychange', onVisible)\n return () => {\n document.removeEventListener('visibilitychange', onVisible)\n entry.subscribers -= 1\n if (entry.subscribers === 0 && entry.timer) {\n clearTimeout(entry.timer)\n entry.timer = null\n }\n }\n }, [entry])\n\n const subscribe = useMemo(\n () => (listener: () => void) => {\n if (!entry) return () => undefined\n entry.listeners.add(listener)\n return () => {\n entry.listeners.delete(listener)\n }\n },\n [entry],\n )\n const snapshot = (): readonly AiJobSummary[] => entry?.jobs ?? NONE\n return useSyncExternalStore(subscribe, snapshot, snapshot)\n}\n\n/** What the indicator and the launcher show for the workspace, or `null` for nothing. */\nexport function useAiJobsActivity(\n user: (TokenSource & { uid?: string | null }) | null | undefined,\n orgId: string | null | undefined,\n enabled = true,\n): AiJobsActivity<AiJobSummary> | null {\n const jobs = useAiJobsInFlight(user, orgId, enabled)\n return useMemo(() => aiJobsActivity(jobs), [jobs])\n}\n"],"names":["authorizedFetch","useEffect","useMemo","useRef","useSyncExternalStore","aiJobsActivity","AI_JOB_TERMINAL_STATUSES","openRequest","seq","jobId","openListeners","Set","openAiJobs","options","listener","subscribeOpen","add","delete","readOpen","useAiJobsOpenRequest","AI_JOB_LINK_PARAM","AI_JOBS_ACTIVE_POLL_MS","ACTIVE_LIMIT","NONE","Object","freeze","entries","Map","readWatchers","watchAiJobsReads","watcher","keyOf","uid","orgId","emit","entry","listeners","isSettled","job","includes","status","schedule","timer","clearTimeout","closed","subscribers","jobs","length","setTimeout","hidden","document","visibilityState","read","inflight","start","response","user","encodeURIComponent","ok","payload","json","catch","done","freeCredits","filter","entryFor","key","get","set","publishAiJob","values","index","findIndex","known","id","next","resetAiJobsStoreForTests","clear","useAiJobsInFlight","enabled","userRef","current","undefined","onVisible","addEventListener","removeEventListener","subscribe","snapshot","useAiJobsActivity"],"mappings":"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"}
1
+ {"version":3,"sources":["../../../../../../../libs/plugins/ai/src/lib/components/ai-jobs-store.ts"],"sourcesContent":["/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n'use client'\n\nimport {\n authorizedFetch,\n type MaybeTokenSource,\n type TokenSource,\n} from '@aglyn/shared-util-http/authorized-token'\nimport { useEffect, useMemo, useRef, useSyncExternalStore } from 'react'\nimport type { AiFreeCreditsLeft } from '../model/ai-site-job'\nimport { aiJobsActivity, type AiJobsActivity } from '../model/ai-job-activity'\nimport { AI_JOB_TERMINAL_STATUSES, type AiJobSummary } from '../model/ai-jobs.types'\n\n/**\n * WHERE A PERSON FINDS THEIR AI JOBS (AGL-3593).\n *\n * Two things every AI surface in the console shares, kept in this module\n * rather than in any one component because the surfaces that need them — a\n * brief dialog on the Screens page, the guided start's full-screen dialog,\n * the top-bar indicator, the Assist launcher and the drawer inside the\n * panel — are mounted in different zones and know nothing of one another.\n * All of them are this plugin's, so no shell, zone or event bus carries it.\n *\n * ── Opening AI jobs ──────────────────────────────────────────────────────\n *\n * {@link openAiJobs} is the one action every \"Open AI jobs\" button, the\n * indicator and a notification's link take: the Assist panel subscribes, opens\n * itself, expands its AI jobs drawer, and scrolls to and highlights the job\n * named. A request is a sequence number as well as a job, so asking twice for\n * the same job opens it twice.\n *\n * ── The workspace's unsettled jobs ───────────────────────────────────────\n *\n * The indicator, the launcher's badge and the drawer's open-by-default rule\n * read the same list: the workspace's jobs that are not yet settled, from\n * the jobs route's `status=active` (a Firestore `in` query under the\n * (status, createdAt) index, never a filter of a loaded window). ONE list per\n * person and workspace however many surfaces read it, re-read on a timer only\n * while something in it is still moving or waiting and the tab is in view —\n * a workspace with nothing running costs one read per page load and no more.\n *\n * Every surface that learns a job's state some other way — the dialog that\n * created it, the drawer's event stream, the in-dialog confirm — hands it in\n * through {@link publishAiJob}, so the indicator moves the moment the job\n * does rather than on the next read.\n */\n\n// ── Opening AI jobs ───────────────────────────────────────────────────────\n\n/** The latest request to open AI jobs; `seq` is 0 until anything asked. */\nexport interface AiJobsOpenRequest {\n seq: number\n /** The job to scroll to and highlight, or `null` for the drawer alone. */\n jobId: string | null\n}\n\nlet openRequest: AiJobsOpenRequest = { seq: 0, jobId: null }\nconst openListeners = new Set<() => void>()\n\n/**\n * Opens the Assist panel on its AI jobs drawer, expanded, scrolled to\n * `jobId` when one is named. A surface that covers the panel — a dialog —\n * closes itself beside the call; this opens, it does not close anything.\n */\nexport function openAiJobs(options: { jobId?: string | null } = {}): void {\n openRequest = { seq: openRequest.seq + 1, jobId: options.jobId ?? null }\n for (const listener of openListeners) listener()\n}\n\nfunction subscribeOpen(listener: () => void): () => void {\n openListeners.add(listener)\n return () => {\n openListeners.delete(listener)\n }\n}\n\nconst readOpen = (): AiJobsOpenRequest => openRequest\n\n/** The latest open request, for the panel; a new `seq` is a new request. */\nexport function useAiJobsOpenRequest(): AiJobsOpenRequest {\n return useSyncExternalStore(subscribeOpen, readOpen, readOpen)\n}\n\n/**\n * The query parameter a notification's link carries: a console page opened\n * with `?aiJob={id}` opens AI jobs on that job, the way pressing the\n * indicator does.\n */\nexport { AI_JOB_LINK_PARAM } from '../model/ai-job-notice'\n\n// ── The workspace's unsettled jobs ────────────────────────────────────────\n\n/** How often a list with something in it is read again, while the tab is in view. */\nexport const AI_JOBS_ACTIVE_POLL_MS = 15_000\n\n/** The most unsettled jobs one read lists; a glance, not an archive. */\nconst ACTIVE_LIMIT = 20\n\nconst NONE: readonly AiJobSummary[] = Object.freeze([])\n\ninterface Entry {\n jobs: readonly AiJobSummary[]\n /** Who reads it, kept current by whichever surface subscribed last. */\n user: MaybeTokenSource\n orgId: string\n uid: string\n subscribers: number\n inflight: boolean\n timer: ReturnType<typeof setTimeout> | null\n /** The route said the feature is not this workspace's: nothing is read again. */\n closed: boolean\n listeners: Set<() => void>\n}\n\nconst entries = new Map<string, Entry>()\n\n/**\n * Who hears of a read as it starts and what a Free workspace has left when it\n * lands (AGL-3722): the usage strip's store follows the jobs list this way\n * rather than by import, so this module stays out of the strip's chunk.\n */\nexport interface AiJobsReadWatcher {\n start: (uid: string, orgId: string) => void\n done: (uid: string, orgId: string, credits: AiFreeCreditsLeft | undefined) => void\n}\nconst readWatchers = new Set<AiJobsReadWatcher>()\n\n/** Listen to the jobs list's reads; returns the way to stop. */\nexport function watchAiJobsReads(watcher: AiJobsReadWatcher): () => void {\n readWatchers.add(watcher)\n return () => {\n readWatchers.delete(watcher)\n }\n}\n\nconst keyOf = (uid: string, orgId: string): string => `${uid}\\n${orgId}`\n\nfunction emit(entry: Entry): void {\n for (const listener of entry.listeners) listener()\n}\n\nfunction isSettled(job: AiJobSummary): boolean {\n return AI_JOB_TERMINAL_STATUSES.includes(job.status)\n}\n\nfunction schedule(entry: Entry): void {\n if (entry.timer) clearTimeout(entry.timer)\n entry.timer = null\n if (entry.closed || entry.subscribers === 0 || entry.jobs.length === 0) return\n entry.timer = setTimeout(() => {\n entry.timer = null\n const hidden = typeof document !== 'undefined' && document.visibilityState === 'hidden'\n // A hidden tab reads nothing; coming back into view reads at once.\n if (hidden) return\n void read(entry)\n }, AI_JOBS_ACTIVE_POLL_MS)\n}\n\nasync function read(entry: Entry): Promise<void> {\n if (entry.inflight || entry.closed) return\n entry.inflight = true\n for (const watcher of readWatchers) watcher.start(entry.uid, entry.orgId)\n try {\n const response = await authorizedFetch(\n entry.user,\n `/api/ai/jobs?orgId=${encodeURIComponent(entry.orgId)}&status=active&limit=${ACTIVE_LIMIT}`,\n )\n if (response.status === 404 || response.status === 403) {\n // The release flag off, or a plan or member without generation: there\n // is nothing to count, now or on the next tick.\n entry.closed = true\n entry.jobs = NONE\n emit(entry)\n return\n }\n if (!response.ok) return\n const payload = (await response.json().catch(() => null)) as {\n jobs?: AiJobSummary[]\n freeCredits?: AiFreeCreditsLeft\n } | null\n // The same read carries what a Free workspace has left, so the usage\n // strip follows jobs that spend between chat messages (AGL-3722).\n for (const watcher of readWatchers) watcher.done(entry.uid, entry.orgId, payload?.freeCredits)\n entry.jobs = Object.freeze((payload?.jobs ?? []).filter((job) => !isSettled(job)))\n emit(entry)\n } catch {\n // An unreachable route keeps what was last read; the next tick asks again.\n } finally {\n entry.inflight = false\n schedule(entry)\n }\n}\n\nfunction entryFor(uid: string, orgId: string, user: MaybeTokenSource): Entry {\n const key = keyOf(uid, orgId)\n let entry = entries.get(key)\n if (!entry) {\n entry = {\n jobs: NONE,\n user,\n orgId,\n uid,\n subscribers: 0,\n inflight: false,\n timer: null,\n closed: false,\n listeners: new Set(),\n }\n entries.set(key, entry)\n }\n return entry\n}\n\n/**\n * Hands a job's latest state to every surface counting this workspace's jobs:\n * a new job joins the list, a settled one leaves it. Keyed on the job's own\n * workspace, for every person whose list holds that workspace in this tab.\n */\nexport function publishAiJob(job: AiJobSummary | null | undefined): void {\n if (!job?.orgId) return\n for (const entry of entries.values()) {\n if (entry.orgId !== job.orgId || entry.closed) continue\n const index = entry.jobs.findIndex((known) => known.id === job.id)\n let next: AiJobSummary[]\n if (isSettled(job)) {\n if (index === -1) continue\n next = entry.jobs.filter((known) => known.id !== job.id)\n // A job that just settled has just spent: read what is left (AGL-3722).\n if (entry.subscribers > 0) void read(entry)\n } else if (index === -1) {\n next = [job, ...entry.jobs]\n } else {\n next = [...entry.jobs]\n next[index] = job\n }\n entry.jobs = Object.freeze(next)\n emit(entry)\n // A list that just gained its first job starts its timer.\n if (!entry.timer && !entry.inflight) schedule(entry)\n }\n}\n\n/** Forget every list and request; specs start each case from nothing. */\nexport function resetAiJobsStoreForTests(): void {\n for (const entry of entries.values()) {\n if (entry.timer) clearTimeout(entry.timer)\n }\n entries.clear()\n openListeners.clear()\n readWatchers.clear()\n openRequest = { seq: 0, jobId: null }\n}\n\n/**\n * The workspace's jobs that are not yet settled, newest first, for the\n * signed-in person — empty while the first read is in flight, where the\n * route refuses, and when `enabled` is false (a surface whose own gates have\n * not passed reads nothing at all).\n */\nexport function useAiJobsInFlight(\n user: (TokenSource & { uid?: string | null }) | null | undefined,\n orgId: string | null | undefined,\n enabled = true,\n): readonly AiJobSummary[] {\n const uid = user?.uid ?? null\n const userRef = useRef<MaybeTokenSource>(user)\n userRef.current = user\n const entry = useMemo(\n () => (enabled && uid && orgId ? entryFor(uid, orgId, userRef.current) : null),\n [enabled, uid, orgId],\n )\n\n useEffect(() => {\n if (!entry) return undefined\n entry.user = userRef.current\n entry.subscribers += 1\n if (entry.subscribers === 1) void read(entry)\n const onVisible = () => {\n if (document.visibilityState === 'visible' && entry.jobs.length > 0) void read(entry)\n }\n document.addEventListener('visibilitychange', onVisible)\n return () => {\n document.removeEventListener('visibilitychange', onVisible)\n entry.subscribers -= 1\n if (entry.subscribers === 0 && entry.timer) {\n clearTimeout(entry.timer)\n entry.timer = null\n }\n }\n }, [entry])\n\n return useEntryJobs(entry)\n}\n\n/** An entry's jobs, kept current as the entry moves. */\nfunction useEntryJobs(entry: Entry | null): readonly AiJobSummary[] {\n const subscribe = useMemo(\n () => (listener: () => void) => {\n if (!entry) return () => undefined\n entry.listeners.add(listener)\n return () => {\n entry.listeners.delete(listener)\n }\n },\n [entry],\n )\n const snapshot = (): readonly AiJobSummary[] => entry?.jobs ?? NONE\n return useSyncExternalStore(subscribe, snapshot, snapshot)\n}\n\n/**\n * The same list, as the surfaces that read it hold it, without reading it\n * here (AGL-3765): for a door that warns from what is already known — the\n * top-bar indicator and the Assist panel read the list wherever a person can\n * start a job — and asks no server of its own. It moves with every read they\n * make and every job a surface hands in through {@link publishAiJob}; empty\n * where nothing has read it, and when `enabled` is false.\n */\nexport function useAiJobsInFlightKnown(\n user: (TokenSource & { uid?: string | null }) | null | undefined,\n orgId: string | null | undefined,\n enabled = true,\n): readonly AiJobSummary[] {\n const uid = user?.uid ?? null\n const userRef = useRef<MaybeTokenSource>(user)\n userRef.current = user\n const entry = useMemo(\n () => (enabled && uid && orgId ? entryFor(uid, orgId, userRef.current) : null),\n [enabled, uid, orgId],\n )\n return useEntryJobs(entry)\n}\n\n/** What the indicator and the launcher show for the workspace, or `null` for nothing. */\nexport function useAiJobsActivity(\n user: (TokenSource & { uid?: string | null }) | null | undefined,\n orgId: string | null | undefined,\n enabled = true,\n): AiJobsActivity<AiJobSummary> | null {\n const jobs = useAiJobsInFlight(user, orgId, enabled)\n return useMemo(() => aiJobsActivity(jobs), [jobs])\n}\n"],"names":["authorizedFetch","useEffect","useMemo","useRef","useSyncExternalStore","aiJobsActivity","AI_JOB_TERMINAL_STATUSES","openRequest","seq","jobId","openListeners","Set","openAiJobs","options","listener","subscribeOpen","add","delete","readOpen","useAiJobsOpenRequest","AI_JOB_LINK_PARAM","AI_JOBS_ACTIVE_POLL_MS","ACTIVE_LIMIT","NONE","Object","freeze","entries","Map","readWatchers","watchAiJobsReads","watcher","keyOf","uid","orgId","emit","entry","listeners","isSettled","job","includes","status","schedule","timer","clearTimeout","closed","subscribers","jobs","length","setTimeout","hidden","document","visibilityState","read","inflight","start","response","user","encodeURIComponent","ok","payload","json","catch","done","freeCredits","filter","entryFor","key","get","set","publishAiJob","values","index","findIndex","known","id","next","resetAiJobsStoreForTests","clear","useAiJobsInFlight","enabled","userRef","current","undefined","onVisible","addEventListener","removeEventListener","useEntryJobs","subscribe","snapshot","useAiJobsInFlightKnown","useAiJobsActivity"],"mappings":"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"}
@@ -18,7 +18,7 @@ import { _ as _extends } from "@swc/helpers/_/_extends";
18
18
  import { pluginRecordFactsReader } from "@aglyn/aglyn/plugin-manager/plugin-record-facts";
19
19
  import { AI_CRM_ANSWER_RETENTION_DAYS, AI_CRM_ANSWERS_COLLECTION, AI_CRM_EMAIL_REQUEST_MAX_CHARS, AI_CRM_OUTPUT_IDS, AI_CRM_PLUGIN_ID, aiCrmAnswerExpiry, parseAiCrmRequest, readAiCrmProposal } from "../model/ai-crm.js";
20
20
  import { AI_STEP_TIERS } from "../providers/catalog.js";
21
- import { AI_ROUTING_TABLE, aiModelForStep } from "../providers/routing.js";
21
+ import { AI_ROUTING_TABLE, aiModelForStep, aiRouteSettings } from "../providers/routing.js";
22
22
  import { aiDoctrineScopeFor, aiDoctrineSystemBlocks, runValidatedGeneration } from "../runtime/ai-doctrine.js";
23
23
  import { AI_CRM_EMAIL_TOOL, AI_CRM_EMAIL_TOOL_NAME, AI_CRM_MAPPING_TOOL, AI_CRM_MAPPING_TOOL_NAME, AI_CRM_RECORD_TOOL_NAME, aiCrmEmailMergeFields, aiCrmImportFields, aiCrmRecordCheckContext, aiCrmRecordTool, checkAiCrmEmail, checkAiCrmMapping, checkAiCrmRecord } from "../tools/ai-crm-tool.js";
24
24
  import { AI_CRM_UNAVAILABLE_COPY, aiCrmAccessRefusal, aiCrmFactsResource } from "./ai-crm-access.js";
@@ -848,7 +848,8 @@ export function createAiJobCrmStep(deps = {}) {
848
848
  }, output);
849
849
  }
850
850
  const generation = await runValidatedGeneration(AI_CRM_GENERATION_KINDS.record, _extends({
851
- step: 'job.crm',
851
+ step: 'job.crm'
852
+ }, aiRouteSettings('job.crm'), {
852
853
  model,
853
854
  instructions,
854
855
  messages: [
@@ -898,7 +899,8 @@ export function createAiJobCrmStep(deps = {}) {
898
899
  });
899
900
  const mergeFields = aiCrmEmailMergeFields(kind);
900
901
  const generation = await runValidatedGeneration(AI_CRM_GENERATION_KINDS.email, _extends({
901
- step: 'job.crm',
902
+ step: 'job.crm'
903
+ }, aiRouteSettings('job.crm'), {
902
904
  model,
903
905
  instructions: AI_CRM_EMAIL_INSTRUCTIONS,
904
906
  messages: [
@@ -961,7 +963,8 @@ export function createAiJobCrmStep(deps = {}) {
961
963
  });
962
964
  }
963
965
  const generation = await runValidatedGeneration(AI_CRM_GENERATION_KINDS.mapping, _extends({
964
- step: 'job.crm',
966
+ step: 'job.crm'
967
+ }, aiRouteSettings('job.crm'), {
965
968
  model,
966
969
  instructions: AI_CRM_MAPPING_INSTRUCTIONS,
967
970
  messages: [
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../../../../../libs/plugins/ai/src/lib/jobs/ai-job-crm-step.ts"],"sourcesContent":["/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { createHash } from 'node:crypto'\nimport { pluginRecordFactsReader } from '@aglyn/aglyn/plugin-manager/plugin-record-facts'\nimport {\n AI_CRM_ANSWER_RETENTION_DAYS,\n AI_CRM_ANSWERS_COLLECTION,\n AI_CRM_EMAIL_REQUEST_MAX_CHARS,\n AI_CRM_OUTPUT_IDS,\n AI_CRM_PLUGIN_ID,\n aiCrmAnswerExpiry,\n parseAiCrmRequest,\n readAiCrmProposal,\n type AiCrmAnswerRecord,\n type AiCrmEmailProposal,\n type AiCrmMappingProposal,\n type AiCrmOutputRef,\n type AiCrmProposal,\n type AiCrmRecordKind,\n type AiCrmRecordProposal,\n} from '../model/ai-crm'\nimport { AI_STEP_TIERS } from '../providers/catalog'\nimport type { AiTool } from '../providers/contract'\nimport { AI_ROUTING_TABLE, aiModelForStep } from '../providers/routing'\nimport { aiDoctrineScopeFor, aiDoctrineSystemBlocks, runValidatedGeneration } from '../runtime/ai-doctrine'\nimport type { AiSystemBlock } from '../runtime/ai-runtime'\nimport {\n AI_CRM_EMAIL_TOOL,\n AI_CRM_EMAIL_TOOL_NAME,\n AI_CRM_MAPPING_TOOL,\n AI_CRM_MAPPING_TOOL_NAME,\n AI_CRM_RECORD_TOOL_NAME,\n aiCrmEmailMergeFields,\n aiCrmImportFields,\n aiCrmRecordCheckContext,\n aiCrmRecordTool,\n checkAiCrmEmail,\n checkAiCrmMapping,\n checkAiCrmRecord,\n type AiCrmImportField,\n} from '../tools/ai-crm-tool'\nimport {\n AI_CRM_UNAVAILABLE_COPY,\n aiCrmAccessRefusal,\n aiCrmFactsResource,\n type AiCrmReaderLookup,\n} from './ai-crm-access'\nimport { registerAiJobAdmission, type AiJobAdmission, type AiJobAdmissionRefusal } from './ai-job-admission'\nimport { aiJobStepBudget } from './ai-job-budget'\nimport { aiGenerationSpent, aiUnspentOutcome } from './ai-job-generation'\nimport type { AiJobStepContext, AiJobStepOutcome, AiJobStepRunner } from './ai-job-text-step'\nimport { registerAiJobStep } from './ai-jobs'\n\n/**\n * The `crm` step (AGL-2917): CRM by AI, as proposals.\n *\n * `inputs.task` says what the job is asked (`model/ai-crm.ts`):\n *\n * - `record` — one contact, company, deal or lead (`record`, `recordId`): a\n * summary, the next step as a task, a deal's stage, a lead's standing;\n * - `email` — a draft for the CRM composer, from what the member asked for\n * (the brief) and the record's facts;\n * - `mapping` — an import's columns (`collection`, `columns`) matched to the\n * fields the CRM's import fills.\n *\n * ## The CRM decides what is read\n *\n * The step never reads a CRM document. It asks the CRM's readers on the core's\n * record-facts seam for the job's creator, and the CRM applies its own rules —\n * the member's reach and `data.manage`, the plan, the record's visibility on\n * the site — and reports the facts it chooses to. The admission below asks the\n * same reader before the job exists, beside what an in-process call skips of\n * the plugin API dispatcher: the CRM switched on and released where the job\n * runs. A mapping sends the file's headers and each column's shape, never a\n * cell.\n *\n * ## It writes no CRM record\n *\n * The CRM's task form, stage route, composer and import drawer are the\n * writes, and a member makes each one. No email is sent by a job.\n *\n * ## The answer is kept apart from the job\n *\n * Every member of a workspace may read its jobs, and not every member may\n * read every record. So a job's output names only the question — which\n * record, which import — and the answer is kept at\n * `orgs/{orgId}/aiCrmAnswers/{jobId}`, which no client may read, for two\n * weeks (`AI_CRM_ANSWER_RETENTION_DAYS`). The answer door\n * (`server/ai-crm-answer.ts`) serves it to a member the CRM still lets read\n * the record, asking the same reader again.\n *\n * ## A summary is asked once per timeline\n *\n * A record answer is keyed on the whole request — the record, the site, the\n * model, the rules, the tool and the facts as the CRM reported them — and a\n * later job whose key matches a kept answer reuses it and spends nothing.\n * The facts change exactly when something the model was told changes, so an\n * unchanged record is never asked twice while its answer is kept.\n */\n\n/**\n * ASSUMED: the step's own round trips — the facts read (the record and its\n * three windowed queries, in parallel), a record's reuse lookup, and the\n * answer's write.\n */\nexport const AI_CRM_FACTS_READS_MS = 1_500\n\n/**\n * The step's time (AGL-3035): one generation and its re-ask on the tier\n * `job.crm` is served from, after the facts read, with no site inventory and\n * so no lookup round. It fits an inline door's budget, so a member who asks on\n * a record page is answered in the request.\n */\nexport const AI_JOB_CRM_STEP_BUDGET = aiJobStepBudget({\n tier: AI_STEP_TIERS['job.crm'],\n maxTokens: AI_ROUTING_TABLE['job.crm'].maxTokens,\n lookups: 0,\n ownReadsMs: AI_CRM_FACTS_READS_MS,\n})\n\n/** The least time one CRM step needs before it starts. */\nexport const AI_JOB_CRM_STEP_MINIMUM_MS = AI_JOB_CRM_STEP_BUDGET.minimumMs\n\n/** Kept answers to one request a reuse lookup reads. */\nexport const AI_CRM_REUSE_CANDIDATES = 20\n\n/** How long a record answer stays reusable, beside its facts being unchanged: as long as it is kept. */\nexport const AI_CRM_REUSE_WINDOW_MS = AI_CRM_ANSWER_RETENTION_DAYS * 24 * 60 * 60_000\n\nexport const AI_CRM_FACTS_FAILURE_COPY = 'The CRM record could not be read. Try again.'\nexport const AI_CRM_KEEP_FAILURE_COPY = 'The answer could not be kept. Try again.'\n\n/** The generation kinds the step asks under, which the doctrine states only its field rules to. */\nexport const AI_CRM_GENERATION_KINDS = {\n record: 'crm-record',\n email: 'crm-email',\n mapping: 'crm-mapping',\n} as const\n\nconst OUTPUT_LABELS = {\n record: 'CRM summary',\n email: 'CRM email draft',\n mapping: 'Import column matches',\n} as const\n\ntype Facts = Readonly<Record<string, unknown>>\n\n/* ------------------------------------------------------------------------ *\n * The rules\n * ------------------------------------------------------------------------ */\n\nconst WRITE_ONLY_FROM_FACTS =\n 'Write only from the facts. Never invent an event, a date, an amount, a name or a promise the facts do not state, and write dates as the facts do.'\n\nconst NEXT_STEP_RULE =\n 'nextStep: the one task that should come next, or null when an open task already covers it or nothing is worth doing. Its title starts with a verb, and its reason names the fact behind it.'\n\nconst LANGUAGE_RULE = 'Write in the language of the facts’ notes and timeline.'\n\nfunction rulesBlock(opening: string, toolName: string, rules: readonly string[]): AiSystemBlock[] {\n return [\n {\n text:\n `${opening}\\n\\n` +\n `Answer by calling ${toolName} exactly once. A reply in prose cannot be used.\\n\\n` +\n `Rules:\\n${rules.map((rule) => `- ${rule}`).join('\\n')}`,\n cacheBreakpoint: true,\n },\n ]\n}\n\n/**\n * The rules for a record of one kind. Each kind is sent the rules its tool can\n * be refused for, and no others: on a model that caches no prompt this short,\n * a rule a request cannot break is paid for on every request.\n */\nexport function aiCrmRecordInstructions(kind: AiCrmRecordKind): AiSystemBlock[] {\n const opening = `You help a small business's team work its customer records. You are given one ${kind} a team member opened, as facts read from their CRM.`\n const rules =\n kind === 'lead'\n ? [\n WRITE_ONLY_FROM_FACTS,\n 'summary: two sentences at most: how the lead came in, what it did, and when it was last seen.',\n 'standing: two sentences at most on why the lead stands where it does: its status, how it came in, and how often and how recently it was active. There is no lead score; never give one.',\n LANGUAGE_RULE,\n ]\n : [\n WRITE_ONLY_FROM_FACTS,\n `summary: two sentences at most: when the ${kind} was last in touch and how, and what is still open.`,\n NEXT_STEP_RULE,\n ...(kind === 'deal'\n ? [\n 'stage: the open stage the deal should move to, by its id in the facts, only when the timeline shows the deal has moved there; otherwise null, and always null for a deal that is won or lost. Never propose winning or losing a deal.',\n ]\n : []),\n LANGUAGE_RULE,\n ]\n return rulesBlock(opening, AI_CRM_RECORD_TOOL_NAME, rules)\n}\n\nexport const AI_CRM_EMAIL_INSTRUCTIONS: AiSystemBlock[] = rulesBlock(\n 'You draft a one-to-one email that a team member reviews, edits and sends themselves from a customer record in their CRM. Nothing is sent until they press Send.',\n AI_CRM_EMAIL_TOOL_NAME,\n [\n 'Write what the team member asks for, from the record’s facts. Never invent a price, a date, an offer, a meeting time or a promise that neither states.',\n 'body: plain text in short paragraphs separated by a blank line, with no markdown, no links and no signature block beyond the sign-off.',\n 'Greet the person and sign off only with the merge fields the request lists, written exactly as listed. Use no other merge field.',\n 'Never write an email address or a phone number.',\n 'Write in the language of the team member’s request.',\n ],\n)\n\nexport const AI_CRM_MAPPING_INSTRUCTIONS: AiSystemBlock[] = rulesBlock(\n 'You match the columns of a spreadsheet a team member is importing into their CRM to the fields the import fills. You are given each column’s header and the shape of its values, never the values.',\n AI_CRM_MAPPING_TOOL_NAME,\n [\n 'Match a column only to a field the request lists, both by their numbers, and only when the column’s header says it holds that field. Leave out a column no field fits.',\n 'A field takes at most one column, and a column one field.',\n 'A column’s shape must suit its field: an email field takes an email column; a phone field a phone or number column; a number, date or yes-no field a column of that shape. Only an email field takes an email column. An empty column takes any field its header names.',\n ],\n)\n\n/* ------------------------------------------------------------------------ *\n * The facts, as the model reads them\n * ------------------------------------------------------------------------ */\n\nconst text = (value: unknown): string => (typeof value === 'string' ? value : '')\nconst list = (value: unknown): Array<Record<string, unknown>> =>\n Array.isArray(value) ? value.filter((entry): entry is Record<string, unknown> => Boolean(entry) && typeof entry === 'object') : []\nconst words = (value: unknown): string[] => (Array.isArray(value) ? value.map(text).filter(Boolean) : [])\n\nfunction line(label: string, value: unknown): string[] {\n const shown = typeof value === 'number' ? String(value) : text(value)\n return shown ? [`${label}: ${shown}`] : []\n}\n\nfunction timelineLines(value: unknown): string[] {\n const entries = list(value)\n if (!entries.length) return ['Timeline: nothing logged or captured yet']\n return [\n 'Timeline, newest first:',\n ...entries.map((entry) => {\n const detail = [\n text(entry['direction']),\n entry['from'] ? `from ${text(entry['from'])}` : '',\n entry['to'] ? `to ${text(entry['to'])}` : '',\n text(entry['delivery']),\n text(entry['outcome']),\n ].filter(Boolean)\n const subject = text(entry['subject'])\n return (\n `- ${text(entry['on'])} ${text(entry['kind'])}` +\n (detail.length ? ` (${detail.join(', ')})` : '') +\n (subject || entry['text'] ? ':' : '') +\n (subject ? ` \"${subject}\"` : '') +\n (text(entry['text']) ? ` ${text(entry['text'])}` : '')\n )\n }),\n ]\n}\n\nfunction taskLines(value: unknown): string[] {\n const tasks = list(value)\n if (!tasks.length) return ['Open tasks: none']\n return [\n 'Open tasks:',\n ...tasks.map(\n (task) =>\n `- ${text(task['title'])} (${[\n text(task['kind']),\n `${text(task['priority']) || 'normal'} priority`,\n ...(task['status'] ? [text(task['status'])] : []),\n task['due'] ? `due ${text(task['due'])}` : 'no due date',\n ...(task['overdue'] === true ? ['overdue'] : []),\n ...(task['assignee'] ? [`assigned to ${text(task['assignee'])}`] : []),\n ].join(', ')})` + (task['notes'] ? `: ${text(task['notes'])}` : ''),\n ),\n ]\n}\n\nfunction dealLines(value: unknown): string[] {\n const deals = list(value)\n if (!deals.length) return ['Deals: none']\n return [\n 'Deals:',\n ...deals.map(\n (deal) =>\n `- ${text(deal['title'])}: ${[\n text(deal['stage']),\n text(deal['status']),\n text(deal['amount']),\n deal['expectedClose'] ? `expected to close ${text(deal['expectedClose'])}` : '',\n ]\n .filter(Boolean)\n .join(', ')}`,\n ),\n ]\n}\n\n/** The record's custom field values under their labels (AGL-3520). */\nfunction customLines(value: unknown): string[] {\n const fields = list(value).filter((field) => text(field['label']) && text(field['value']))\n return fields.length\n ? ['Custom fields:', ...fields.map((field) => `- ${text(field['label'])}: ${text(field['value'])}`)]\n : []\n}\n\nconst yesNo = (label: string, value: unknown): string[] => (value === true ? [label] : [])\n\n/**\n * A record's facts as the CRM's reader marks them when it answered the whole\n * record (AGL-3520): `wholeRecord: true`, set only while the organization has\n * `release_crm_assist_whole_record` on. Absent, the facts are the disclosed\n * ones and are written by {@link aiCrmDisclosedFactsLines}.\n */\nexport const AI_CRM_WHOLE_RECORD_FIELD = 'wholeRecord'\n\n/** Keeps only `keys` of each entry: the disclosed writer's allow-list for nested rows. */\nconst only = (value: unknown, keys: readonly string[]): Array<Record<string, unknown>> =>\n list(value).map((entry) => Object.fromEntries(keys.filter((key) => key in entry).map((key) => [key, entry[key]])))\n\n/** What the disclosed writer reads of a timeline entry: never who an email was from or to. */\nconst DISCLOSED_TIMELINE_KEYS = ['on', 'kind', 'direction', 'subject', 'text', 'outcome', 'delivery'] as const\n/** What the disclosed writer reads of an open task: never its assignee, status or notes. */\nconst DISCLOSED_TASK_KEYS = ['title', 'kind', 'priority', 'due', 'overdue'] as const\n\n/**\n * The facts as lines of text while the published pages describe the narrow\n * set: only the fields named here are written, whatever else a reader\n * reports, so what reaches a prompt is decided in two places that both have\n * to agree — the CRM's disclosed builders and this list. A company's account\n * fields beyond its headcount, and a lead's profile fields, stay out of the\n * prompt as they always have.\n */\nexport function aiCrmDisclosedFactsLines(kind: AiCrmRecordKind, facts: Facts): string[] {\n const timeline = timelineLines(only(facts['timeline'], DISCLOSED_TIMELINE_KEYS))\n const openTasks = taskLines(only(facts['openTasks'], DISCLOSED_TASK_KEYS))\n if (kind === 'contact') {\n const orders = Number(facts['orders']) || 0\n return [\n ...line('Contact', facts['name']),\n ...line('Salutation', facts['salutation']),\n ...line('Job title', facts['jobTitle']),\n ...line('Department', facts['department']),\n // A request the person made, which a drafted next step must respect (AGL-3515).\n ...yesNo('Do not call: they asked not to be phoned', facts['doNotCall']),\n ...line('Company', facts['company']),\n ...line('Lifecycle stage', facts['lifecycleStage']),\n ...line('Tags', words(facts['tags']).join(', ')),\n ...line('Came in through', words(facts['sources']).join(', ')),\n ...(orders ? [`Orders: ${orders}${facts['lastPurchase'] ? `, the last on ${text(facts['lastPurchase'])}` : ''}`] : []),\n ...line('In the CRM since', facts['since']),\n ...line('Last opened or clicked an email', facts['lastEmailEngagement']),\n ...line('Notes', facts['notes']),\n ...timeline,\n ...openTasks,\n ...dealLines(facts['deals']),\n ]\n }\n if (kind === 'company') {\n return [\n ...line('Company', facts['name']),\n ...line('Domain', facts['domain']),\n ...line('Industry', facts['industry']),\n ...line('Employees', facts['employees'] ?? ''),\n ...line('Tags', words(facts['tags']).join(', ')),\n ...line('People in the CRM', facts['people']),\n ...line('In the CRM since', facts['since']),\n ...line('Notes', facts['notes']),\n ...timeline,\n ...openTasks,\n ...dealLines(facts['deals']),\n ]\n }\n if (kind === 'deal') {\n const stages = list(facts['stages'])\n const withWhom = [text(facts['contact']), text(facts['company'])].filter(Boolean).join(' at ')\n return [\n ...line('Deal', facts['title']),\n ...line('Pipeline', facts['pipeline']),\n ...line('Status', facts['status']),\n ...line('Stage', facts['stage'] ? `${text(facts['stage'])} (id ${text(facts['stageId'])})` : ''),\n ...line('Stages in order', stages.map((stage) => `${text(stage['id'])} \"${text(stage['name'])}\" (${text(stage['kind'])})`).join('; ')),\n ...line('In this stage since', facts['inStageSince']),\n ...line('Amount', facts['amount']),\n // Salesforce's Opportunity fields (AGL-3516).\n ...line('Probability', typeof facts['probability'] === 'number' ? `${facts['probability']}%` : ''),\n ...line('Forecast category', facts['forecastCategory']),\n ...line('Type', facts['type']),\n ...line('Lead source', facts['leadSource']),\n ...line('Next step', facts['nextStep']),\n ...line('Expected to close', facts['expectedClose']),\n ...line('Lost reason', facts['lostReason']),\n ...line('With', withWhom),\n ...contactRoleLines(facts['contactRoles']),\n ...line('Products on the deal', typeof facts['products'] === 'number' ? facts['products'] || '' : ''),\n ...line('In the CRM since', facts['since']),\n ...line('Notes', facts['notes']),\n ...timeline,\n ...openTasks,\n ]\n }\n return [\n ...line('Lead', facts['name']),\n ...line('Status', facts['status']),\n ...line('Captured through', words(facts['sources']).join(', ')),\n ...line('Captures', Number(facts['captures']) || ''),\n ...line('First seen', facts['firstSeen']),\n ...line('Last seen', facts['lastSeen']),\n `Assigned to a teammate: ${facts['assigned'] === true ? 'yes' : 'no'}`,\n `Converted to a contact: ${facts['converted'] === true ? 'yes' : 'no'}`,\n ...line('Unqualified reason', facts['unqualifiedReason']),\n ...line('Notes', facts['notes']),\n ...timeline,\n ]\n}\n\n/** Every contact on a deal and the part each plays (AGL-3521), by name. */\nfunction contactRoleLines(value: unknown): string[] {\n return line(\n 'Contact roles',\n list(value)\n .map((row) => {\n const part = [text(row['role']), row['primary'] === true ? 'Primary' : ''].filter(Boolean).join(', ')\n return part ? `${text(row['name'])} (${part})` : text(row['name'])\n })\n .join('; '),\n )\n}\n\n/**\n * The facts as lines of text: the WHOLE record the CRM reports (AGL-3520) —\n * every standard field, its contact details and addresses, its picklist\n * labels, its custom fields under their labels, its consent, notes,\n * timeline, open tasks and deals. A field the record does not hold writes\n * no line. The CRM's builders decide what a record is; nothing here drops a\n * field they report, and no reader reports a token, an account identifier\n * or a record id beyond a stage's.\n */\nexport function aiCrmFactsLines(kind: AiCrmRecordKind, facts: Facts): string[] {\n if (kind === 'contact') {\n const orders = Number(facts['orders']) || 0\n return [\n ...line('Contact', facts['name']),\n ...line('Salutation', facts['salutation']),\n ...line('First name', facts['firstName']),\n ...line('Last name', facts['lastName']),\n ...line('Email', words(facts['emails']).join(', ')),\n ...line('Phone', facts['phone']),\n ...line('Mobile phone', facts['mobilePhone']),\n ...line('Home phone', facts['homePhone']),\n ...line('Other phone', facts['otherPhone']),\n ...line('Fax', facts['fax']),\n // A request the person made, which a drafted next step must respect (AGL-3515).\n ...yesNo('Do not call: they asked not to be phoned', facts['doNotCall']),\n ...line('Job title', facts['jobTitle']),\n ...line('Department', facts['department']),\n ...line('Company', facts['company']),\n ...line('Reports to', facts['reportsTo']),\n ...line('Assistant', facts['assistant']),\n ...line('Assistant phone', facts['assistantPhone']),\n ...line('Birthdate', facts['birthdate']),\n ...line('Mailing address', facts['mailingAddress']),\n ...line('Other address', facts['otherAddress']),\n ...line('Lifecycle stage', facts['lifecycleStage']),\n ...line('Lead source', facts['leadSource']),\n ...line('Owner', facts['owner']),\n ...line('Marketing email consent', facts['marketingConsent']),\n ...line('Tags', words(facts['tags']).join(', ')),\n ...line('Came in through', words(facts['sources']).join(', ')),\n ...(orders ? [`Orders: ${orders}${facts['lastPurchase'] ? `, the last on ${text(facts['lastPurchase'])}` : ''}`] : []),\n ...line('In the CRM since', facts['since']),\n ...line('Last opened or clicked an email', facts['lastEmailEngagement']),\n ...customLines(facts['custom']),\n ...line('Notes', facts['notes']),\n ...timelineLines(facts['timeline']),\n ...taskLines(facts['openTasks']),\n ...dealLines(facts['deals']),\n ]\n }\n if (kind === 'company') {\n return [\n ...line('Company', facts['name']),\n ...line('Domain', facts['domain']),\n ...line('Website', facts['website']),\n ...line('Phone', facts['phone']),\n ...line('Fax', facts['fax']),\n // Salesforce's Account fields (AGL-3514).\n ...line('Type', facts['type']),\n ...line('Industry', facts['industry']),\n ...line('Rating', facts['rating']),\n ...line('Ownership', facts['ownership']),\n ...line('Account source', facts['accountSource']),\n ...line('Employees', facts['employees'] ?? ''),\n ...line('Annual revenue', facts['annualRevenue']),\n ...line('Account number', facts['accountNumber']),\n ...line('Account site', facts['site']),\n ...line('Ticker symbol', facts['tickerSymbol']),\n ...line('SIC code', facts['sicCode']),\n ...line('Billing address', facts['billingAddress']),\n ...line('Shipping address', facts['shippingAddress']),\n ...line('Parent company', facts['parentCompany']),\n ...line('Owner', facts['owner']),\n ...line('Tags', words(facts['tags']).join(', ')),\n ...line('People in the CRM', facts['people']),\n ...line('In the CRM since', facts['since']),\n ...customLines(facts['custom']),\n ...line('Notes', facts['notes']),\n ...timelineLines(facts['timeline']),\n ...taskLines(facts['openTasks']),\n ...dealLines(facts['deals']),\n ]\n }\n if (kind === 'deal') {\n const stages = list(facts['stages'])\n const products = list(facts['products'])\n const withWhom = [text(facts['contact']), text(facts['company'])].filter(Boolean).join(' at ')\n return [\n ...line('Deal', facts['title']),\n ...line('Pipeline', facts['pipeline']),\n ...line('Status', facts['status']),\n ...line('Stage', facts['stage'] ? `${text(facts['stage'])} (id ${text(facts['stageId'])})` : ''),\n ...line('Stages in order', stages.map((stage) => `${text(stage['id'])} \"${text(stage['name'])}\" (${text(stage['kind'])})`).join('; ')),\n ...line('In this stage since', facts['inStageSince']),\n ...line('Amount', facts['amount']),\n // Salesforce's Opportunity fields (AGL-3516).\n ...line('Probability', typeof facts['probability'] === 'number' ? `${facts['probability']}%` : ''),\n ...line('Forecast category', facts['forecastCategory']),\n ...line('Type', facts['type']),\n ...line('Lead source', facts['leadSource']),\n ...line('Campaign', facts['campaign']),\n ...line('Next step', facts['nextStep']),\n ...line('Expected to close', facts['expectedClose']),\n ...line('Lost reason', facts['lostReason']),\n ...line('With', withWhom),\n ...line('Owner', facts['owner']),\n ...contactRoleLines(facts['contactRoles']),\n ...(products.length\n ? [\n 'Products:',\n ...products.map(\n (product) =>\n `- ${text(product['name'])} × ${Number(product['quantity']) || 0}` +\n (product['unitAmount'] ? ` at ${text(product['unitAmount'])}` : ''),\n ),\n ]\n : []),\n ...line('In the CRM since', facts['since']),\n ...customLines(facts['custom']),\n ...line('Notes', facts['notes']),\n ...timelineLines(facts['timeline']),\n ...taskLines(facts['openTasks']),\n ]\n }\n return [\n ...line('Lead', facts['name']),\n ...line('Salutation', facts['salutation']),\n ...line('First name', facts['firstName']),\n ...line('Last name', facts['lastName']),\n ...line('Email', facts['email']),\n ...line('Phone', facts['phone']),\n ...line('Mobile phone', facts['mobilePhone']),\n ...line('Fax', facts['fax']),\n ...yesNo('Do not call: they asked not to be phoned', facts['doNotCall']),\n ...line('Company', facts['company']),\n ...line('Job title', facts['jobTitle']),\n ...line('Website', facts['website']),\n ...line('Address', facts['address']),\n ...line('Industry', facts['industry']),\n ...line('Rating', facts['rating']),\n ...line('Employees', facts['employees'] ?? ''),\n ...line('Annual revenue', facts['annualRevenue']),\n ...line('Lead source', facts['leadSource']),\n ...line('Status', facts['status']),\n ...line('Owner', facts['owner']),\n ...line('Campaigns', words(facts['campaigns']).join(', ')),\n ...line('Marketing email consent', facts['marketingConsent']),\n ...line('Tags', words(facts['tags']).join(', ')),\n ...line('Captured through', words(facts['sources']).join(', ')),\n ...line('Captures', Number(facts['captures']) || ''),\n ...line('First seen', facts['firstSeen']),\n ...line('Last seen', facts['lastSeen']),\n `Assigned to a teammate: ${facts['assigned'] === true ? 'yes' : 'no'}`,\n `Converted to a contact: ${facts['converted'] === true ? 'yes' : 'no'}`,\n ...line('Unqualified reason', facts['unqualifiedReason']),\n ...customLines(facts['custom']),\n ...line('Notes', facts['notes']),\n ...timelineLines(facts['timeline']),\n ...(list(facts['openTasks']).length ? taskLines(facts['openTasks']) : []),\n ]\n}\n\n/**\n * The most characters a record's facts take in a request (AGL-3520) —\n * about six thousand tokens at the three characters a token the routing\n * ceilings are measured with. The CRM's own cuts keep a record well under\n * it; a record that holds everything at once is fitted by\n * {@link aiCrmFitFacts}, which shortens its long texts and drops no field.\n */\nexport const AI_CRM_FACTS_MAX_CHARS = 18_000\n\n/** The cuts {@link aiCrmFitFacts} tries in turn: notes, then each timeline text, then each custom value and task note. */\nconst FIT_STEPS = [\n { notes: 300, text: 160, custom: 160 },\n { notes: 160, text: 80, custom: 80 },\n { notes: 80, text: 40, custom: 40 },\n] as const\n\nconst cut = (value: unknown, max: number): unknown =>\n typeof value === 'string' && value.length > max ? `${value.slice(0, max - 1).trimEnd()}…` : value\n\n/**\n * The facts, fitted to {@link AI_CRM_FACTS_MAX_CHARS} as lines: unchanged\n * when they fit, otherwise with the long texts — the notes, each timeline\n * entry's text, each task's notes and each custom value — cut shorter step\n * by step. Every field stays; only how much of a long text is sent changes.\n */\nexport function aiCrmFitFacts(kind: AiCrmRecordKind, facts: Facts): Facts {\n const size = (candidate: Facts) => aiCrmFactsLines(kind, candidate).join('\\n').length\n if (size(facts) <= AI_CRM_FACTS_MAX_CHARS) return facts\n let fitted: Facts = facts\n for (const step of FIT_STEPS) {\n fitted = {\n ...facts,\n notes: cut(facts['notes'], step.notes),\n timeline: list(facts['timeline']).map((entry) => ({ ...entry, text: cut(entry['text'], step.text) })),\n openTasks: list(facts['openTasks']).map((task) => ({ ...task, notes: cut(task['notes'], step.text) })),\n custom: list(facts['custom']).map((field) => ({ ...field, value: cut(field['value'], step.custom) })),\n }\n if (size(fitted) <= AI_CRM_FACTS_MAX_CHARS) return fitted\n }\n return fitted\n}\n\n/**\n * The lines a prompt carries for a record: the whole record, fitted, when\n * the reader marked it {@link AI_CRM_WHOLE_RECORD_FIELD}; otherwise the\n * disclosed lines alone.\n */\nexport function aiCrmPromptFactsLines(kind: AiCrmRecordKind, facts: Facts): string[] {\n return facts[AI_CRM_WHOLE_RECORD_FIELD] === true\n ? aiCrmFactsLines(kind, aiCrmFitFacts(kind, facts))\n : aiCrmDisclosedFactsLines(kind, facts)\n}\n\n/** The user turn for a record. */\nexport function aiCrmRecordPrompt(kind: AiCrmRecordKind, facts: Facts): string {\n return [`Record: ${kind}`, ...aiCrmPromptFactsLines(kind, facts)].join('\\n')\n}\n\n/** The user turn for an email draft. */\nexport function aiCrmEmailPrompt(input: { kind: AiCrmRecordKind; facts: Facts; request: string }): string {\n return [\n `Record: ${input.kind}`,\n `Merge fields: ${aiCrmEmailMergeFields(input.kind).map((key) => `{{${key}}}`).join(', ')}`,\n `The team member asks: ${input.request.replace(/\\s+/g, ' ').trim().slice(0, AI_CRM_EMAIL_REQUEST_MAX_CHARS)}`,\n '',\n 'Facts:',\n ...aiCrmPromptFactsLines(input.kind, input.facts),\n ].join('\\n')\n}\n\n/** The user turn for an import's columns. */\nexport function aiCrmMappingPrompt(input: {\n collection: string\n fields: readonly AiCrmImportField[]\n columns: ReadonlyArray<{ header: string; shape: string }>\n}): string {\n return [\n `Import: ${input.collection}`,\n 'Fields (number: label, type):',\n ...input.fields.map(\n (field, index) => `- ${index}: ${field.label}, ${field.type}${field.required ? ', required' : ''}`,\n ),\n 'Columns (number: header, shape):',\n ...input.columns.map((column, index) => `- ${index}: ${column.header || '(no header)'}, ${column.shape}`),\n ].join('\\n')\n}\n\n/* ------------------------------------------------------------------------ *\n * A record answer, asked once per timeline\n * ------------------------------------------------------------------------ */\n\n/**\n * A digest of the whole request a record answer is produced for. A hash of the\n * request, never of the answer, and nothing of the facts is recoverable from\n * it. The version tag strands every old key when what a key means changes.\n */\nexport function aiCrmRecordKey(input: {\n kind: AiCrmRecordKind\n id: string\n hostId: string | null\n model: string\n prompt: string\n system: readonly AiSystemBlock[]\n tool: AiTool\n}): string {\n const digest = createHash('sha256')\n for (const part of [\n 'crm-record.v1',\n input.kind,\n input.id,\n input.hostId ?? '',\n input.model,\n input.prompt,\n ...input.system.map((block) => block.text),\n JSON.stringify(input.tool),\n ]) {\n digest.update(`${part.length}:${part}\\u0000`)\n }\n return digest.digest('hex')\n}\n\n/** A kept answer about a record, as the reuse lookup reads it. */\nexport interface AiCrmAnswerCandidate {\n jobId: string\n hostId: string | null\n task: string\n key: string | null\n createdAtMs: number | null\n proposal: AiCrmProposal | null\n}\n\nexport type AiCrmAnswerFinder = (\n orgId: string,\n key: string,\n firestore?: FirebaseFirestore.Firestore,\n) => Promise<AiCrmAnswerCandidate[]>\n\nconst millis = (value: unknown): number | null => {\n if (value instanceof Date) return value.getTime()\n const toMillis = (value as { toMillis?: () => number } | null)?.toMillis\n return typeof toMillis === 'function' ? toMillis.call(value) : null\n}\n\n/**\n * The finder the step uses in production: one equality on the request key\n * inside the org's kept answers. Firestore's automatic single-field index\n * answers it, so no composite index is deployed; the site and the window are\n * applied in memory.\n */\nexport const findAiCrmAnswersByKey: AiCrmAnswerFinder = async (orgId, key, firestore) => {\n if (!firestore) return []\n const snapshot = await firestore\n .collection('orgs')\n .doc(orgId)\n .collection(AI_CRM_ANSWERS_COLLECTION)\n .where('key', '==', key)\n .limit(AI_CRM_REUSE_CANDIDATES)\n .get()\n return snapshot.docs.map((doc) => {\n const data = (doc.data() ?? {}) as Partial<AiCrmAnswerRecord>\n return {\n jobId: doc.id,\n hostId: data.hostId ?? null,\n task: String(data.task ?? ''),\n key: data.key ?? null,\n createdAtMs: millis(data.createdAt),\n proposal: readAiCrmProposal(data.proposal),\n }\n })\n}\n\n/** The newest answer another job kept for the same request, inside the window. */\nexport function aiReusableCrmRecord(\n candidates: readonly AiCrmAnswerCandidate[],\n context: { jobId: string; hostId: string | null; key: string; now: Date },\n): { jobId: string; proposal: AiCrmRecordProposal } | null {\n const floor = context.now.getTime() - AI_CRM_REUSE_WINDOW_MS\n let best: { jobId: string; proposal: AiCrmRecordProposal; at: number } | null = null\n for (const candidate of candidates) {\n if (candidate.jobId === context.jobId || candidate.task !== 'record') continue\n if (candidate.hostId !== context.hostId || candidate.key !== context.key) continue\n if (candidate.proposal?.kind !== 'record') continue\n const at = candidate.createdAtMs ?? 0\n if (at < floor) continue\n if (!best || at > best.at) best = { jobId: candidate.jobId, proposal: candidate.proposal, at }\n }\n return best ? { jobId: best.jobId, proposal: best.proposal } : null\n}\n\n/* ------------------------------------------------------------------------ *\n * Admission\n * ------------------------------------------------------------------------ */\n\nexport interface AiCrmAdmissionDeps {\n readerFor?: AiCrmReaderLookup\n now?: () => Date\n}\n\n/**\n * Whether a `crm` job may start here for this member; `null` admits it: its\n * inputs name a record or an import, and the CRM would let the member read it\n * ({@link aiCrmAccessRefusal}). Asked at the create door before the job\n * exists, so a refusal spends nothing. A door that names no member asks\n * nothing of the reader.\n */\nexport async function aiCrmAdmissionRefusal(\n context: Parameters<AiJobAdmission>[0],\n deps: AiCrmAdmissionDeps = {},\n): Promise<AiJobAdmissionRefusal | null> {\n const request = parseAiCrmRequest(context.inputs)\n if (typeof request === 'string') return { status: 400, error: request }\n return aiCrmAccessRefusal({\n firestore: context.firestore,\n orgId: context.orgId,\n hostId: context.hostId ?? null,\n ...aiCrmFactsResource(request),\n org: context.org,\n uid: context.uid,\n ...(deps.readerFor ? { readerFor: deps.readerFor } : {}),\n now: deps.now?.() ?? new Date(),\n })\n}\n\nexport const aiCrmJobAdmission: AiJobAdmission = (context) => aiCrmAdmissionRefusal(context)\n\n/* ------------------------------------------------------------------------ *\n * The runner\n * ------------------------------------------------------------------------ */\n\nexport interface AiJobCrmStepDeps {\n readerFor?: AiCrmReaderLookup\n /** The reuse lookup; specs hand in a fake, and `null` turns reuse off. */\n findAnswersByKey?: AiCrmAnswerFinder | null\n}\n\nfunction modelOf(context: AiJobStepContext): string {\n return context.modelFor?.('job.crm') ?? aiModelForStep('job.crm')\n}\n\n/** An answer as the step hands it to be kept; the job's ids and the clock are added. */\ntype AiCrmAnswerToKeep = Pick<AiCrmAnswerRecord, 'task' | 'resource' | 'recordId' | 'proposal' | 'key'>\n\n/** What a step spent, whatever outputs it goes on to name. */\ntype AiCrmSpent = Omit<AiJobStepOutcome, 'outputs'>\n\n/**\n * Keeps an answer at `orgs/{orgId}/aiCrmAnswers/{jobId}`, which no client may\n * read, on its own clock.\n */\nasync function keepAiCrmAnswer(context: AiJobStepContext, answer: AiCrmAnswerToKeep): Promise<void> {\n const { job, now, firestore } = context\n const record = JSON.parse(\n JSON.stringify({ jobId: job.$id, orgId: job.orgId, hostId: job.hostId ?? null, createdBy: job.createdBy, ...answer }),\n ) as AiCrmAnswerRecord\n await firestore\n .collection('orgs')\n .doc(job.orgId)\n .collection(AI_CRM_ANSWERS_COLLECTION)\n .doc(job.$id)\n .set({ ...record, createdAt: now, expiresAt: aiCrmAnswerExpiry(now) })\n}\n\nexport function createAiJobCrmStep(deps: AiJobCrmStepDeps = {}): AiJobStepRunner {\n const readerFor = deps.readerFor ?? pluginRecordFactsReader\n const findAnswersByKey = deps.findAnswersByKey === undefined ? findAiCrmAnswersByKey : deps.findAnswersByKey\n return async (context): Promise<AiJobStepOutcome> => {\n const { job, now, signal, firestore } = context\n const model = modelOf(context)\n const request = parseAiCrmRequest(job.inputs)\n if (typeof request === 'string') return { ...aiUnspentOutcome(model), failure: request }\n const { resource, id } = aiCrmFactsResource(request)\n const found = readerFor(resource)\n if (!found || found.pluginId !== AI_CRM_PLUGIN_ID) {\n return { ...aiUnspentOutcome(model), failure: AI_CRM_UNAVAILABLE_COPY }\n }\n let facts: Facts\n try {\n const read = await found.reader.read({\n orgId: job.orgId,\n hostId: job.hostId ?? null,\n id,\n // The job's creator: the CRM reads the record as the member who asked.\n uid: job.createdBy,\n org: (context.org as Record<string, unknown> | null | undefined) ?? null,\n now,\n })\n if (read.ok === false) return { ...aiUnspentOutcome(model), failure: read.error }\n facts = read.facts\n } catch (error) {\n console.error('ai crm facts read failed', { orgId: job.orgId, jobId: job.$id, resource, error })\n return { ...aiUnspentOutcome(model), failure: AI_CRM_FACTS_FAILURE_COPY }\n }\n const maxTokens = AI_JOB_CRM_STEP_BUDGET.maxTokens(model)\n const hostId = job.hostId ?? null\n\n // Keeps the answer, then names it on the job. A keep that fails fails the\n // step as spent: the tokens were used, and there is nothing to show.\n const keep = async (\n spent: AiCrmSpent,\n answer: AiCrmAnswerToKeep,\n output: { id: string; label: string; ref: AiCrmOutputRef },\n ): Promise<AiJobStepOutcome> => {\n try {\n await keepAiCrmAnswer(context, answer)\n } catch (error) {\n console.error('ai crm answer keep failed', { orgId: job.orgId, jobId: job.$id, error })\n return { ...spent, outputs: [], failure: AI_CRM_KEEP_FAILURE_COPY }\n }\n return {\n ...spent,\n outputs: [{ resource: 'crm', id: output.id, hostId, label: output.label, proposal: { ...output.ref } }],\n }\n }\n\n if (request.task === 'record') {\n const { kind } = request.record\n if (facts['record'] !== kind) return { ...aiUnspentOutcome(model), failure: AI_CRM_FACTS_FAILURE_COPY }\n const instructions = aiCrmRecordInstructions(kind)\n const tool = aiCrmRecordTool(kind)\n const prompt = aiCrmRecordPrompt(kind, facts)\n const key = aiCrmRecordKey({\n kind,\n id,\n hostId,\n model,\n prompt,\n system: aiDoctrineSystemBlocks(undefined, {\n instructions,\n scope: aiDoctrineScopeFor(AI_CRM_GENERATION_KINDS.record),\n }),\n tool,\n })\n const output = { id: AI_CRM_OUTPUT_IDS.record(kind, id), label: OUTPUT_LABELS.record, ref: { kind: 'record', record: { kind, id } } } as const\n // Reuse before asking: an answer kept for the same key is the same\n // request, so the member reads it again at no cost.\n const reused = findAnswersByKey\n ? aiReusableCrmRecord(await findAnswersByKey(job.orgId, key, firestore).catch(() => []), {\n jobId: job.$id,\n hostId,\n key,\n now,\n })\n : null\n if (reused) {\n return keep(\n aiUnspentOutcome(model),\n { task: 'record', resource, recordId: id, key, proposal: { ...reused.proposal, reusedFrom: reused.jobId } },\n output,\n )\n }\n const generation = await runValidatedGeneration(AI_CRM_GENERATION_KINDS.record, {\n step: 'job.crm',\n model,\n instructions,\n messages: [{ role: 'user', content: prompt }],\n tool,\n maxTokens,\n ...(signal ? { signal } : {}),\n check: (answer) => checkAiCrmRecord(answer, aiCrmRecordCheckContext(kind, facts)),\n })\n const spent = aiGenerationSpent(generation)\n if (generation.status === 'refused') return { ...spent, refused: true }\n if (generation.status === 'needs_input') return { ...spent, failure: generation.message }\n const proposal: AiCrmRecordProposal = {\n kind: 'record',\n record: { kind, id },\n summary: generation.value.summary,\n nextStep: generation.value.nextStep,\n stage: generation.value.stage,\n standing: generation.value.standing,\n asOf: now.toISOString().slice(0, 10),\n }\n return keep(spent, { task: 'record', resource, recordId: id, key, proposal }, output)\n }\n\n if (request.task === 'email') {\n const { kind } = request.record\n if (facts['record'] !== kind) return { ...aiUnspentOutcome(model), failure: AI_CRM_FACTS_FAILURE_COPY }\n const mergeFields = aiCrmEmailMergeFields(kind)\n const generation = await runValidatedGeneration(AI_CRM_GENERATION_KINDS.email, {\n step: 'job.crm',\n model,\n instructions: AI_CRM_EMAIL_INSTRUCTIONS,\n messages: [{ role: 'user', content: aiCrmEmailPrompt({ kind, facts, request: job.brief }) }],\n tool: AI_CRM_EMAIL_TOOL,\n maxTokens,\n ...(signal ? { signal } : {}),\n check: (answer) => checkAiCrmEmail(answer, { mergeFields }),\n })\n const spent = aiGenerationSpent(generation)\n if (generation.status === 'refused') return { ...spent, refused: true }\n if (generation.status === 'needs_input') return { ...spent, failure: generation.message }\n const proposal: AiCrmEmailProposal = {\n kind: 'email',\n record: { kind, id },\n subject: generation.value.subject,\n body: generation.value.body,\n }\n return keep(\n spent,\n { task: 'email', resource, recordId: id, key: null, proposal },\n { id: AI_CRM_OUTPUT_IDS.email(kind, id), label: OUTPUT_LABELS.email, ref: { kind: 'email', record: { kind, id } } },\n )\n }\n\n const fields = aiCrmImportFields(facts)\n if (facts['record'] !== 'import' || !fields.length) {\n return { ...aiUnspentOutcome(model), failure: AI_CRM_FACTS_FAILURE_COPY }\n }\n const generation = await runValidatedGeneration(AI_CRM_GENERATION_KINDS.mapping, {\n step: 'job.crm',\n model,\n instructions: AI_CRM_MAPPING_INSTRUCTIONS,\n messages: [\n {\n role: 'user',\n content: aiCrmMappingPrompt({ collection: request.collection, fields, columns: request.columns }),\n },\n ],\n tool: AI_CRM_MAPPING_TOOL,\n maxTokens,\n ...(signal ? { signal } : {}),\n check: (answer) => checkAiCrmMapping(answer, { columns: request.columns, fields }),\n })\n const spent = aiGenerationSpent(generation)\n if (generation.status === 'refused') return { ...spent, refused: true }\n if (generation.status === 'needs_input') return { ...spent, failure: generation.message }\n const proposal: AiCrmMappingProposal = {\n kind: 'mapping',\n collection: request.collection,\n matches: generation.value,\n columns: request.columns.length,\n }\n return keep(\n spent,\n { task: 'mapping', resource, recordId: request.collection, key: null, proposal },\n {\n id: AI_CRM_OUTPUT_IDS.mapping(request.collection),\n label: OUTPUT_LABELS.mapping,\n ref: { kind: 'mapping', collection: request.collection },\n },\n )\n }\n}\n\nexport const runAiJobCrmStep = createAiJobCrmStep()\n\n/** Registers the CRM step and the check a `crm` job passes before it is created. */\nexport function registerAiCrmJob(): void {\n registerAiJobStep('crm', runAiJobCrmStep, { minimumMs: AI_JOB_CRM_STEP_MINIMUM_MS })\n registerAiJobAdmission('crm', aiCrmJobAdmission)\n}\n"],"names":["createHash","pluginRecordFactsReader","AI_CRM_ANSWER_RETENTION_DAYS","AI_CRM_ANSWERS_COLLECTION","AI_CRM_EMAIL_REQUEST_MAX_CHARS","AI_CRM_OUTPUT_IDS","AI_CRM_PLUGIN_ID","aiCrmAnswerExpiry","parseAiCrmRequest","readAiCrmProposal","AI_STEP_TIERS","AI_ROUTING_TABLE","aiModelForStep","aiDoctrineScopeFor","aiDoctrineSystemBlocks","runValidatedGeneration","AI_CRM_EMAIL_TOOL","AI_CRM_EMAIL_TOOL_NAME","AI_CRM_MAPPING_TOOL","AI_CRM_MAPPING_TOOL_NAME","AI_CRM_RECORD_TOOL_NAME","aiCrmEmailMergeFields","aiCrmImportFields","aiCrmRecordCheckContext","aiCrmRecordTool","checkAiCrmEmail","checkAiCrmMapping","checkAiCrmRecord","AI_CRM_UNAVAILABLE_COPY","aiCrmAccessRefusal","aiCrmFactsResource","registerAiJobAdmission","aiJobStepBudget","aiGenerationSpent","aiUnspentOutcome","registerAiJobStep","AI_CRM_FACTS_READS_MS","AI_JOB_CRM_STEP_BUDGET","tier","maxTokens","lookups","ownReadsMs","AI_JOB_CRM_STEP_MINIMUM_MS","minimumMs","AI_CRM_REUSE_CANDIDATES","AI_CRM_REUSE_WINDOW_MS","AI_CRM_FACTS_FAILURE_COPY","AI_CRM_KEEP_FAILURE_COPY","AI_CRM_GENERATION_KINDS","record","email","mapping","OUTPUT_LABELS","WRITE_ONLY_FROM_FACTS","NEXT_STEP_RULE","LANGUAGE_RULE","rulesBlock","opening","toolName","rules","text","map","rule","join","cacheBreakpoint","aiCrmRecordInstructions","kind","AI_CRM_EMAIL_INSTRUCTIONS","AI_CRM_MAPPING_INSTRUCTIONS","value","list","Array","isArray","filter","entry","Boolean","words","line","label","shown","String","timelineLines","entries","length","detail","subject","taskLines","tasks","task","dealLines","deals","deal","customLines","fields","field","yesNo","AI_CRM_WHOLE_RECORD_FIELD","only","keys","Object","fromEntries","key","DISCLOSED_TIMELINE_KEYS","DISCLOSED_TASK_KEYS","aiCrmDisclosedFactsLines","facts","timeline","openTasks","orders","Number","stages","withWhom","stage","contactRoleLines","row","part","aiCrmFactsLines","products","product","AI_CRM_FACTS_MAX_CHARS","FIT_STEPS","notes","custom","cut","max","slice","trimEnd","aiCrmFitFacts","size","candidate","fitted","step","aiCrmPromptFactsLines","aiCrmRecordPrompt","aiCrmEmailPrompt","input","request","replace","trim","aiCrmMappingPrompt","collection","index","type","required","columns","column","header","shape","aiCrmRecordKey","digest","id","hostId","model","prompt","system","block","JSON","stringify","tool","update","millis","Date","getTime","toMillis","call","findAiCrmAnswersByKey","orgId","firestore","snapshot","doc","where","limit","get","docs","data","jobId","createdAtMs","createdAt","proposal","aiReusableCrmRecord","candidates","context","floor","now","best","at","aiCrmAdmissionRefusal","deps","inputs","status","error","org","uid","readerFor","aiCrmJobAdmission","modelOf","modelFor","keepAiCrmAnswer","answer","job","parse","$id","createdBy","set","expiresAt","createAiJobCrmStep","findAnswersByKey","undefined","signal","failure","resource","found","pluginId","read","reader","ok","console","keep","spent","output","outputs","ref","instructions","scope","reused","catch","recordId","reusedFrom","generation","messages","role","content","check","refused","message","summary","nextStep","standing","asOf","toISOString","mergeFields","brief","body","matches","runAiJobCrmStep","registerAiCrmJob"],"mappings":";AAAA;;;;;;;;;;;;;;;CAeC,GAED,SAASA,UAAU,QAAQ,cAAa;AACxC,SAASC,uBAAuB,QAAQ,kDAAiD;AACzF,SACEC,4BAA4B,EAC5BC,yBAAyB,EACzBC,8BAA8B,EAC9BC,iBAAiB,EACjBC,gBAAgB,EAChBC,iBAAiB,EACjBC,iBAAiB,EACjBC,iBAAiB,QAQZ,qBAAiB;AACxB,SAASC,aAAa,QAAQ,0BAAsB;AAEpD,SAASC,gBAAgB,EAAEC,cAAc,QAAQ,0BAAsB;AACvE,SAASC,kBAAkB,EAAEC,sBAAsB,EAAEC,sBAAsB,QAAQ,4BAAwB;AAE3G,SACEC,iBAAiB,EACjBC,sBAAsB,EACtBC,mBAAmB,EACnBC,wBAAwB,EACxBC,uBAAuB,EACvBC,qBAAqB,EACrBC,iBAAiB,EACjBC,uBAAuB,EACvBC,eAAe,EACfC,eAAe,EACfC,iBAAiB,EACjBC,gBAAgB,QAEX,0BAAsB;AAC7B,SACEC,uBAAuB,EACvBC,kBAAkB,EAClBC,kBAAkB,QAEb,qBAAiB;AACxB,SAASC,sBAAsB,QAAyD,wBAAoB;AAC5G,SAASC,eAAe,QAAQ,qBAAiB;AACjD,SAASC,iBAAiB,EAAEC,gBAAgB,QAAQ,yBAAqB;AAEzE,SAASC,iBAAiB,QAAQ,eAAW;AAE7C;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CA6CC,GAED;;;;CAIC,GACD,OAAO,MAAMC,wBAAwB,KAAK;AAE1C;;;;;CAKC,GACD,OAAO,MAAMC,yBAAyBL,gBAAgB;IACpDM,MAAM5B,aAAa,CAAC,UAAU;IAC9B6B,WAAW5B,gBAAgB,CAAC,UAAU,CAAC4B,SAAS;IAChDC,SAAS;IACTC,YAAYL;AACd,GAAE;AAEF,wDAAwD,GACxD,OAAO,MAAMM,6BAA6BL,uBAAuBM,SAAS,CAAA;AAE1E,sDAAsD,GACtD,OAAO,MAAMC,0BAA0B,GAAE;AAEzC,sGAAsG,GACtG,OAAO,MAAMC,yBAAyB3C,+BAA+B,KAAK,KAAK,MAAM;AAErF,OAAO,MAAM4C,4BAA4B,+CAA8C;AACvF,OAAO,MAAMC,2BAA2B,2CAA0C;AAElF,iGAAiG,GACjG,OAAO,MAAMC,0BAA0B;IACrCC,QAAQ;IACRC,OAAO;IACPC,SAAS;AACX,EAAU;AAEV,MAAMC,gBAAgB;IACpBH,QAAQ;IACRC,OAAO;IACPC,SAAS;AACX;AAIA;;4EAE4E,GAE5E,MAAME,wBACJ;AAEF,MAAMC,iBACJ;AAEF,MAAMC,gBAAgB;AAEtB,SAASC,WAAWC,OAAe,EAAEC,QAAgB,EAAEC,KAAwB;IAC7E,OAAO;QACL;YACEC,MACE,GAAGH,QAAQ,IAAI,CAAC,GAChB,CAAC,kBAAkB,EAAEC,SAAS,mDAAmD,CAAC,GAClF,CAAC,QAAQ,EAAEC,MAAME,GAAG,CAAC,CAACC,OAAS,CAAC,EAAE,EAAEA,MAAM,EAAEC,IAAI,CAAC,OAAO;YAC1DC,iBAAiB;QACnB;KACD;AACH;AAEA;;;;CAIC,GACD,OAAO,SAASC,wBAAwBC,IAAqB;IAC3D,MAAMT,UAAU,CAAC,8EAA8E,EAAES,KAAK,oDAAoD,CAAC;IAC3J,MAAMP,QACJO,SAAS,SACL;QACEb;QACA;QACA;QACAE;KACD,GACD;QACEF;QACA,CAAC,yCAAyC,EAAEa,KAAK,mDAAmD,CAAC;QACrGZ;WACIY,SAAS,SACT;YACE;SACD,GACD,EAAE;QACNX;KACD;IACP,OAAOC,WAAWC,SAASrC,yBAAyBuC;AACtD;AAEA,OAAO,MAAMQ,4BAA6CX,WACxD,mKACAvC,wBACA;IACE;IACA;IACA;IACA;IACA;CACD,EACF;AAED,OAAO,MAAMmD,8BAA+CZ,WAC1D,sMACArC,0BACA;IACE;IACA;IACA;CACD,EACF;AAED;;4EAE4E,GAE5E,MAAMyC,OAAO,CAACS,QAA4B,OAAOA,UAAU,WAAWA,QAAQ;AAC9E,MAAMC,OAAO,CAACD,QACZE,MAAMC,OAAO,CAACH,SAASA,MAAMI,MAAM,CAAC,CAACC,QAA4CC,QAAQD,UAAU,OAAOA,UAAU,YAAY,EAAE;AACpI,MAAME,QAAQ,CAACP,QAA8BE,MAAMC,OAAO,CAACH,SAASA,MAAMR,GAAG,CAACD,MAAMa,MAAM,CAACE,WAAW,EAAE;AAExG,SAASE,KAAKC,KAAa,EAAET,KAAc;IACzC,MAAMU,QAAQ,OAAOV,UAAU,WAAWW,OAAOX,SAAST,KAAKS;IAC/D,OAAOU,QAAQ;QAAC,GAAGD,MAAM,EAAE,EAAEC,OAAO;KAAC,GAAG,EAAE;AAC5C;AAEA,SAASE,cAAcZ,KAAc;IACnC,MAAMa,UAAUZ,KAAKD;IACrB,IAAI,CAACa,QAAQC,MAAM,EAAE,OAAO;QAAC;KAA2C;IACxE,OAAO;QACL;WACGD,QAAQrB,GAAG,CAAC,CAACa;YACd,MAAMU,SAAS;gBACbxB,KAAKc,KAAK,CAAC,YAAY;gBACvBA,KAAK,CAAC,OAAO,GAAG,CAAC,KAAK,EAAEd,KAAKc,KAAK,CAAC,OAAO,GAAG,GAAG;gBAChDA,KAAK,CAAC,KAAK,GAAG,CAAC,GAAG,EAAEd,KAAKc,KAAK,CAAC,KAAK,GAAG,GAAG;gBAC1Cd,KAAKc,KAAK,CAAC,WAAW;gBACtBd,KAAKc,KAAK,CAAC,UAAU;aACtB,CAACD,MAAM,CAACE;YACT,MAAMU,UAAUzB,KAAKc,KAAK,CAAC,UAAU;YACrC,OACE,CAAC,EAAE,EAAEd,KAAKc,KAAK,CAAC,KAAK,EAAE,CAAC,EAAEd,KAAKc,KAAK,CAAC,OAAO,GAAG,GAC9CU,CAAAA,OAAOD,MAAM,GAAG,CAAC,EAAE,EAAEC,OAAOrB,IAAI,CAAC,MAAM,CAAC,CAAC,GAAG,EAAC,IAC7CsB,CAAAA,WAAWX,KAAK,CAAC,OAAO,GAAG,MAAM,EAAC,IAClCW,CAAAA,UAAU,CAAC,EAAE,EAAEA,QAAQ,CAAC,CAAC,GAAG,EAAC,IAC7BzB,CAAAA,KAAKc,KAAK,CAAC,OAAO,IAAI,CAAC,CAAC,EAAEd,KAAKc,KAAK,CAAC,OAAO,GAAG,GAAG,EAAC;QAExD;KACD;AACH;AAEA,SAASY,UAAUjB,KAAc;IAC/B,MAAMkB,QAAQjB,KAAKD;IACnB,IAAI,CAACkB,MAAMJ,MAAM,EAAE,OAAO;QAAC;KAAmB;IAC9C,OAAO;QACL;WACGI,MAAM1B,GAAG,CACV,CAAC2B,OACC,CAAC,EAAE,EAAE5B,KAAK4B,IAAI,CAAC,QAAQ,EAAE,EAAE,EAAE;gBAC3B5B,KAAK4B,IAAI,CAAC,OAAO;gBACjB,GAAG5B,KAAK4B,IAAI,CAAC,WAAW,KAAK,SAAS,SAAS,CAAC;mBAC5CA,IAAI,CAAC,SAAS,GAAG;oBAAC5B,KAAK4B,IAAI,CAAC,SAAS;iBAAE,GAAG,EAAE;gBAChDA,IAAI,CAAC,MAAM,GAAG,CAAC,IAAI,EAAE5B,KAAK4B,IAAI,CAAC,MAAM,GAAG,GAAG;mBACvCA,IAAI,CAAC,UAAU,KAAK,OAAO;oBAAC;iBAAU,GAAG,EAAE;mBAC3CA,IAAI,CAAC,WAAW,GAAG;oBAAC,CAAC,YAAY,EAAE5B,KAAK4B,IAAI,CAAC,WAAW,GAAG;iBAAC,GAAG,EAAE;aACtE,CAACzB,IAAI,CAAC,MAAM,CAAC,CAAC,GAAIyB,CAAAA,IAAI,CAAC,QAAQ,GAAG,CAAC,EAAE,EAAE5B,KAAK4B,IAAI,CAAC,QAAQ,GAAG,GAAG,EAAC;KAEtE;AACH;AAEA,SAASC,UAAUpB,KAAc;IAC/B,MAAMqB,QAAQpB,KAAKD;IACnB,IAAI,CAACqB,MAAMP,MAAM,EAAE,OAAO;QAAC;KAAc;IACzC,OAAO;QACL;WACGO,MAAM7B,GAAG,CACV,CAAC8B,OACC,CAAC,EAAE,EAAE/B,KAAK+B,IAAI,CAAC,QAAQ,EAAE,EAAE,EAAE;gBAC3B/B,KAAK+B,IAAI,CAAC,QAAQ;gBAClB/B,KAAK+B,IAAI,CAAC,SAAS;gBACnB/B,KAAK+B,IAAI,CAAC,SAAS;gBACnBA,IAAI,CAAC,gBAAgB,GAAG,CAAC,kBAAkB,EAAE/B,KAAK+B,IAAI,CAAC,gBAAgB,GAAG,GAAG;aAC9E,CACElB,MAAM,CAACE,SACPZ,IAAI,CAAC,OAAO;KAEpB;AACH;AAEA,oEAAoE,GACpE,SAAS6B,YAAYvB,KAAc;IACjC,MAAMwB,SAASvB,KAAKD,OAAOI,MAAM,CAAC,CAACqB,QAAUlC,KAAKkC,KAAK,CAAC,QAAQ,KAAKlC,KAAKkC,KAAK,CAAC,QAAQ;IACxF,OAAOD,OAAOV,MAAM,GAChB;QAAC;WAAqBU,OAAOhC,GAAG,CAAC,CAACiC,QAAU,CAAC,EAAE,EAAElC,KAAKkC,KAAK,CAAC,QAAQ,EAAE,EAAE,EAAElC,KAAKkC,KAAK,CAAC,QAAQ,GAAG;KAAE,GAClG,EAAE;AACR;AAEA,MAAMC,QAAQ,CAACjB,OAAeT,QAA8BA,UAAU,OAAO;QAACS;KAAM,GAAG,EAAE;AAEzF;;;;;CAKC,GACD,OAAO,MAAMkB,4BAA4B,cAAa;AAEtD,wFAAwF,GACxF,MAAMC,OAAO,CAAC5B,OAAgB6B,OAC5B5B,KAAKD,OAAOR,GAAG,CAAC,CAACa,QAAUyB,OAAOC,WAAW,CAACF,KAAKzB,MAAM,CAAC,CAAC4B,MAAQA,OAAO3B,OAAOb,GAAG,CAAC,CAACwC,MAAQ;gBAACA;gBAAK3B,KAAK,CAAC2B,IAAI;aAAC;AAEjH,4FAA4F,GAC5F,MAAMC,0BAA0B;IAAC;IAAM;IAAQ;IAAa;IAAW;IAAQ;IAAW;CAAW;AACrG,0FAA0F,GAC1F,MAAMC,sBAAsB;IAAC;IAAS;IAAQ;IAAY;IAAO;CAAU;AAE3E;;;;;;;CAOC,GACD,OAAO,SAASC,yBAAyBtC,IAAqB,EAAEuC,KAAY;IAC1E,MAAMC,WAAWzB,cAAcgB,KAAKQ,KAAK,CAAC,WAAW,EAAEH;IACvD,MAAMK,YAAYrB,UAAUW,KAAKQ,KAAK,CAAC,YAAY,EAAEF;IACrD,IAAIrC,SAAS,WAAW;QACtB,MAAM0C,SAASC,OAAOJ,KAAK,CAAC,SAAS,KAAK;QAC1C,OAAO;eACF5B,KAAK,WAAW4B,KAAK,CAAC,OAAO;eAC7B5B,KAAK,cAAc4B,KAAK,CAAC,aAAa;eACtC5B,KAAK,aAAa4B,KAAK,CAAC,WAAW;eACnC5B,KAAK,cAAc4B,KAAK,CAAC,aAAa;YACzC,gFAAgF;eAC7EV,MAAM,4CAA4CU,KAAK,CAAC,YAAY;eACpE5B,KAAK,WAAW4B,KAAK,CAAC,UAAU;eAChC5B,KAAK,mBAAmB4B,KAAK,CAAC,iBAAiB;eAC/C5B,KAAK,QAAQD,MAAM6B,KAAK,CAAC,OAAO,EAAE1C,IAAI,CAAC;eACvCc,KAAK,mBAAmBD,MAAM6B,KAAK,CAAC,UAAU,EAAE1C,IAAI,CAAC;eACpD6C,SAAS;gBAAC,CAAC,QAAQ,EAAEA,SAASH,KAAK,CAAC,eAAe,GAAG,CAAC,cAAc,EAAE7C,KAAK6C,KAAK,CAAC,eAAe,GAAG,GAAG,IAAI;aAAC,GAAG,EAAE;eAClH5B,KAAK,oBAAoB4B,KAAK,CAAC,QAAQ;eACvC5B,KAAK,mCAAmC4B,KAAK,CAAC,sBAAsB;eACpE5B,KAAK,SAAS4B,KAAK,CAAC,QAAQ;eAC5BC;eACAC;eACAlB,UAAUgB,KAAK,CAAC,QAAQ;SAC5B;IACH;IACA,IAAIvC,SAAS,WAAW;YAKCuC;QAJvB,OAAO;eACF5B,KAAK,WAAW4B,KAAK,CAAC,OAAO;eAC7B5B,KAAK,UAAU4B,KAAK,CAAC,SAAS;eAC9B5B,KAAK,YAAY4B,KAAK,CAAC,WAAW;eAClC5B,KAAK,cAAa4B,mBAAAA,KAAK,CAAC,YAAY,YAAlBA,mBAAsB;eACxC5B,KAAK,QAAQD,MAAM6B,KAAK,CAAC,OAAO,EAAE1C,IAAI,CAAC;eACvCc,KAAK,qBAAqB4B,KAAK,CAAC,SAAS;eACzC5B,KAAK,oBAAoB4B,KAAK,CAAC,QAAQ;eACvC5B,KAAK,SAAS4B,KAAK,CAAC,QAAQ;eAC5BC;eACAC;eACAlB,UAAUgB,KAAK,CAAC,QAAQ;SAC5B;IACH;IACA,IAAIvC,SAAS,QAAQ;QACnB,MAAM4C,SAASxC,KAAKmC,KAAK,CAAC,SAAS;QACnC,MAAMM,WAAW;YAACnD,KAAK6C,KAAK,CAAC,UAAU;YAAG7C,KAAK6C,KAAK,CAAC,UAAU;SAAE,CAAChC,MAAM,CAACE,SAASZ,IAAI,CAAC;QACvF,OAAO;eACFc,KAAK,QAAQ4B,KAAK,CAAC,QAAQ;eAC3B5B,KAAK,YAAY4B,KAAK,CAAC,WAAW;eAClC5B,KAAK,UAAU4B,KAAK,CAAC,SAAS;eAC9B5B,KAAK,SAAS4B,KAAK,CAAC,QAAQ,GAAG,GAAG7C,KAAK6C,KAAK,CAAC,QAAQ,EAAE,KAAK,EAAE7C,KAAK6C,KAAK,CAAC,UAAU,EAAE,CAAC,CAAC,GAAG;eAC1F5B,KAAK,mBAAmBiC,OAAOjD,GAAG,CAAC,CAACmD,QAAU,GAAGpD,KAAKoD,KAAK,CAAC,KAAK,EAAE,EAAE,EAAEpD,KAAKoD,KAAK,CAAC,OAAO,EAAE,GAAG,EAAEpD,KAAKoD,KAAK,CAAC,OAAO,EAAE,CAAC,CAAC,EAAEjD,IAAI,CAAC;eAC7Hc,KAAK,uBAAuB4B,KAAK,CAAC,eAAe;eACjD5B,KAAK,UAAU4B,KAAK,CAAC,SAAS;YACjC,8CAA8C;eAC3C5B,KAAK,eAAe,OAAO4B,KAAK,CAAC,cAAc,KAAK,WAAW,GAAGA,KAAK,CAAC,cAAc,CAAC,CAAC,CAAC,GAAG;eAC5F5B,KAAK,qBAAqB4B,KAAK,CAAC,mBAAmB;eACnD5B,KAAK,QAAQ4B,KAAK,CAAC,OAAO;eAC1B5B,KAAK,eAAe4B,KAAK,CAAC,aAAa;eACvC5B,KAAK,aAAa4B,KAAK,CAAC,WAAW;eACnC5B,KAAK,qBAAqB4B,KAAK,CAAC,gBAAgB;eAChD5B,KAAK,eAAe4B,KAAK,CAAC,aAAa;eACvC5B,KAAK,QAAQkC;eACbE,iBAAiBR,KAAK,CAAC,eAAe;eACtC5B,KAAK,wBAAwB,OAAO4B,KAAK,CAAC,WAAW,KAAK,WAAWA,KAAK,CAAC,WAAW,IAAI,KAAK;eAC/F5B,KAAK,oBAAoB4B,KAAK,CAAC,QAAQ;eACvC5B,KAAK,SAAS4B,KAAK,CAAC,QAAQ;eAC5BC;eACAC;SACJ;IACH;IACA,OAAO;WACF9B,KAAK,QAAQ4B,KAAK,CAAC,OAAO;WAC1B5B,KAAK,UAAU4B,KAAK,CAAC,SAAS;WAC9B5B,KAAK,oBAAoBD,MAAM6B,KAAK,CAAC,UAAU,EAAE1C,IAAI,CAAC;WACtDc,KAAK,YAAYgC,OAAOJ,KAAK,CAAC,WAAW,KAAK;WAC9C5B,KAAK,cAAc4B,KAAK,CAAC,YAAY;WACrC5B,KAAK,aAAa4B,KAAK,CAAC,WAAW;QACtC,CAAC,wBAAwB,EAAEA,KAAK,CAAC,WAAW,KAAK,OAAO,QAAQ,MAAM;QACtE,CAAC,wBAAwB,EAAEA,KAAK,CAAC,YAAY,KAAK,OAAO,QAAQ,MAAM;WACpE5B,KAAK,sBAAsB4B,KAAK,CAAC,oBAAoB;WACrD5B,KAAK,SAAS4B,KAAK,CAAC,QAAQ;WAC5BC;KACJ;AACH;AAEA,yEAAyE,GACzE,SAASO,iBAAiB5C,KAAc;IACtC,OAAOQ,KACL,iBACAP,KAAKD,OACFR,GAAG,CAAC,CAACqD;QACJ,MAAMC,OAAO;YAACvD,KAAKsD,GAAG,CAAC,OAAO;YAAGA,GAAG,CAAC,UAAU,KAAK,OAAO,YAAY;SAAG,CAACzC,MAAM,CAACE,SAASZ,IAAI,CAAC;QAChG,OAAOoD,OAAO,GAAGvD,KAAKsD,GAAG,CAAC,OAAO,EAAE,EAAE,EAAEC,KAAK,CAAC,CAAC,GAAGvD,KAAKsD,GAAG,CAAC,OAAO;IACnE,GACCnD,IAAI,CAAC;AAEZ;AAEA;;;;;;;;CAQC,GACD,OAAO,SAASqD,gBAAgBlD,IAAqB,EAAEuC,KAAY;QAmI1CA;IAlIvB,IAAIvC,SAAS,WAAW;QACtB,MAAM0C,SAASC,OAAOJ,KAAK,CAAC,SAAS,KAAK;QAC1C,OAAO;eACF5B,KAAK,WAAW4B,KAAK,CAAC,OAAO;eAC7B5B,KAAK,cAAc4B,KAAK,CAAC,aAAa;eACtC5B,KAAK,cAAc4B,KAAK,CAAC,YAAY;eACrC5B,KAAK,aAAa4B,KAAK,CAAC,WAAW;eACnC5B,KAAK,SAASD,MAAM6B,KAAK,CAAC,SAAS,EAAE1C,IAAI,CAAC;eAC1Cc,KAAK,SAAS4B,KAAK,CAAC,QAAQ;eAC5B5B,KAAK,gBAAgB4B,KAAK,CAAC,cAAc;eACzC5B,KAAK,cAAc4B,KAAK,CAAC,YAAY;eACrC5B,KAAK,eAAe4B,KAAK,CAAC,aAAa;eACvC5B,KAAK,OAAO4B,KAAK,CAAC,MAAM;YAC3B,gFAAgF;eAC7EV,MAAM,4CAA4CU,KAAK,CAAC,YAAY;eACpE5B,KAAK,aAAa4B,KAAK,CAAC,WAAW;eACnC5B,KAAK,cAAc4B,KAAK,CAAC,aAAa;eACtC5B,KAAK,WAAW4B,KAAK,CAAC,UAAU;eAChC5B,KAAK,cAAc4B,KAAK,CAAC,YAAY;eACrC5B,KAAK,aAAa4B,KAAK,CAAC,YAAY;eACpC5B,KAAK,mBAAmB4B,KAAK,CAAC,iBAAiB;eAC/C5B,KAAK,aAAa4B,KAAK,CAAC,YAAY;eACpC5B,KAAK,mBAAmB4B,KAAK,CAAC,iBAAiB;eAC/C5B,KAAK,iBAAiB4B,KAAK,CAAC,eAAe;eAC3C5B,KAAK,mBAAmB4B,KAAK,CAAC,iBAAiB;eAC/C5B,KAAK,eAAe4B,KAAK,CAAC,aAAa;eACvC5B,KAAK,SAAS4B,KAAK,CAAC,QAAQ;eAC5B5B,KAAK,2BAA2B4B,KAAK,CAAC,mBAAmB;eACzD5B,KAAK,QAAQD,MAAM6B,KAAK,CAAC,OAAO,EAAE1C,IAAI,CAAC;eACvCc,KAAK,mBAAmBD,MAAM6B,KAAK,CAAC,UAAU,EAAE1C,IAAI,CAAC;eACpD6C,SAAS;gBAAC,CAAC,QAAQ,EAAEA,SAASH,KAAK,CAAC,eAAe,GAAG,CAAC,cAAc,EAAE7C,KAAK6C,KAAK,CAAC,eAAe,GAAG,GAAG,IAAI;aAAC,GAAG,EAAE;eAClH5B,KAAK,oBAAoB4B,KAAK,CAAC,QAAQ;eACvC5B,KAAK,mCAAmC4B,KAAK,CAAC,sBAAsB;eACpEb,YAAYa,KAAK,CAAC,SAAS;eAC3B5B,KAAK,SAAS4B,KAAK,CAAC,QAAQ;eAC5BxB,cAAcwB,KAAK,CAAC,WAAW;eAC/BnB,UAAUmB,KAAK,CAAC,YAAY;eAC5BhB,UAAUgB,KAAK,CAAC,QAAQ;SAC5B;IACH;IACA,IAAIvC,SAAS,WAAW;YAaCuC;QAZvB,OAAO;eACF5B,KAAK,WAAW4B,KAAK,CAAC,OAAO;eAC7B5B,KAAK,UAAU4B,KAAK,CAAC,SAAS;eAC9B5B,KAAK,WAAW4B,KAAK,CAAC,UAAU;eAChC5B,KAAK,SAAS4B,KAAK,CAAC,QAAQ;eAC5B5B,KAAK,OAAO4B,KAAK,CAAC,MAAM;YAC3B,0CAA0C;eACvC5B,KAAK,QAAQ4B,KAAK,CAAC,OAAO;eAC1B5B,KAAK,YAAY4B,KAAK,CAAC,WAAW;eAClC5B,KAAK,UAAU4B,KAAK,CAAC,SAAS;eAC9B5B,KAAK,aAAa4B,KAAK,CAAC,YAAY;eACpC5B,KAAK,kBAAkB4B,KAAK,CAAC,gBAAgB;eAC7C5B,KAAK,cAAa4B,oBAAAA,KAAK,CAAC,YAAY,YAAlBA,oBAAsB;eACxC5B,KAAK,kBAAkB4B,KAAK,CAAC,gBAAgB;eAC7C5B,KAAK,kBAAkB4B,KAAK,CAAC,gBAAgB;eAC7C5B,KAAK,gBAAgB4B,KAAK,CAAC,OAAO;eAClC5B,KAAK,iBAAiB4B,KAAK,CAAC,eAAe;eAC3C5B,KAAK,YAAY4B,KAAK,CAAC,UAAU;eACjC5B,KAAK,mBAAmB4B,KAAK,CAAC,iBAAiB;eAC/C5B,KAAK,oBAAoB4B,KAAK,CAAC,kBAAkB;eACjD5B,KAAK,kBAAkB4B,KAAK,CAAC,gBAAgB;eAC7C5B,KAAK,SAAS4B,KAAK,CAAC,QAAQ;eAC5B5B,KAAK,QAAQD,MAAM6B,KAAK,CAAC,OAAO,EAAE1C,IAAI,CAAC;eACvCc,KAAK,qBAAqB4B,KAAK,CAAC,SAAS;eACzC5B,KAAK,oBAAoB4B,KAAK,CAAC,QAAQ;eACvCb,YAAYa,KAAK,CAAC,SAAS;eAC3B5B,KAAK,SAAS4B,KAAK,CAAC,QAAQ;eAC5BxB,cAAcwB,KAAK,CAAC,WAAW;eAC/BnB,UAAUmB,KAAK,CAAC,YAAY;eAC5BhB,UAAUgB,KAAK,CAAC,QAAQ;SAC5B;IACH;IACA,IAAIvC,SAAS,QAAQ;QACnB,MAAM4C,SAASxC,KAAKmC,KAAK,CAAC,SAAS;QACnC,MAAMY,WAAW/C,KAAKmC,KAAK,CAAC,WAAW;QACvC,MAAMM,WAAW;YAACnD,KAAK6C,KAAK,CAAC,UAAU;YAAG7C,KAAK6C,KAAK,CAAC,UAAU;SAAE,CAAChC,MAAM,CAACE,SAASZ,IAAI,CAAC;QACvF,OAAO;eACFc,KAAK,QAAQ4B,KAAK,CAAC,QAAQ;eAC3B5B,KAAK,YAAY4B,KAAK,CAAC,WAAW;eAClC5B,KAAK,UAAU4B,KAAK,CAAC,SAAS;eAC9B5B,KAAK,SAAS4B,KAAK,CAAC,QAAQ,GAAG,GAAG7C,KAAK6C,KAAK,CAAC,QAAQ,EAAE,KAAK,EAAE7C,KAAK6C,KAAK,CAAC,UAAU,EAAE,CAAC,CAAC,GAAG;eAC1F5B,KAAK,mBAAmBiC,OAAOjD,GAAG,CAAC,CAACmD,QAAU,GAAGpD,KAAKoD,KAAK,CAAC,KAAK,EAAE,EAAE,EAAEpD,KAAKoD,KAAK,CAAC,OAAO,EAAE,GAAG,EAAEpD,KAAKoD,KAAK,CAAC,OAAO,EAAE,CAAC,CAAC,EAAEjD,IAAI,CAAC;eAC7Hc,KAAK,uBAAuB4B,KAAK,CAAC,eAAe;eACjD5B,KAAK,UAAU4B,KAAK,CAAC,SAAS;YACjC,8CAA8C;eAC3C5B,KAAK,eAAe,OAAO4B,KAAK,CAAC,cAAc,KAAK,WAAW,GAAGA,KAAK,CAAC,cAAc,CAAC,CAAC,CAAC,GAAG;eAC5F5B,KAAK,qBAAqB4B,KAAK,CAAC,mBAAmB;eACnD5B,KAAK,QAAQ4B,KAAK,CAAC,OAAO;eAC1B5B,KAAK,eAAe4B,KAAK,CAAC,aAAa;eACvC5B,KAAK,YAAY4B,KAAK,CAAC,WAAW;eAClC5B,KAAK,aAAa4B,KAAK,CAAC,WAAW;eACnC5B,KAAK,qBAAqB4B,KAAK,CAAC,gBAAgB;eAChD5B,KAAK,eAAe4B,KAAK,CAAC,aAAa;eACvC5B,KAAK,QAAQkC;eACblC,KAAK,SAAS4B,KAAK,CAAC,QAAQ;eAC5BQ,iBAAiBR,KAAK,CAAC,eAAe;eACrCY,SAASlC,MAAM,GACf;gBACE;mBACGkC,SAASxD,GAAG,CACb,CAACyD,UACC,CAAC,EAAE,EAAE1D,KAAK0D,OAAO,CAAC,OAAO,EAAE,GAAG,EAAET,OAAOS,OAAO,CAAC,WAAW,KAAK,GAAG,GACjEA,CAAAA,OAAO,CAAC,aAAa,GAAG,CAAC,IAAI,EAAE1D,KAAK0D,OAAO,CAAC,aAAa,GAAG,GAAG,EAAC;aAEtE,GACD,EAAE;eACHzC,KAAK,oBAAoB4B,KAAK,CAAC,QAAQ;eACvCb,YAAYa,KAAK,CAAC,SAAS;eAC3B5B,KAAK,SAAS4B,KAAK,CAAC,QAAQ;eAC5BxB,cAAcwB,KAAK,CAAC,WAAW;eAC/BnB,UAAUmB,KAAK,CAAC,YAAY;SAChC;IACH;IACA,OAAO;WACF5B,KAAK,QAAQ4B,KAAK,CAAC,OAAO;WAC1B5B,KAAK,cAAc4B,KAAK,CAAC,aAAa;WACtC5B,KAAK,cAAc4B,KAAK,CAAC,YAAY;WACrC5B,KAAK,aAAa4B,KAAK,CAAC,WAAW;WACnC5B,KAAK,SAAS4B,KAAK,CAAC,QAAQ;WAC5B5B,KAAK,SAAS4B,KAAK,CAAC,QAAQ;WAC5B5B,KAAK,gBAAgB4B,KAAK,CAAC,cAAc;WACzC5B,KAAK,OAAO4B,KAAK,CAAC,MAAM;WACxBV,MAAM,4CAA4CU,KAAK,CAAC,YAAY;WACpE5B,KAAK,WAAW4B,KAAK,CAAC,UAAU;WAChC5B,KAAK,aAAa4B,KAAK,CAAC,WAAW;WACnC5B,KAAK,WAAW4B,KAAK,CAAC,UAAU;WAChC5B,KAAK,WAAW4B,KAAK,CAAC,UAAU;WAChC5B,KAAK,YAAY4B,KAAK,CAAC,WAAW;WAClC5B,KAAK,UAAU4B,KAAK,CAAC,SAAS;WAC9B5B,KAAK,cAAa4B,mBAAAA,KAAK,CAAC,YAAY,YAAlBA,mBAAsB;WACxC5B,KAAK,kBAAkB4B,KAAK,CAAC,gBAAgB;WAC7C5B,KAAK,eAAe4B,KAAK,CAAC,aAAa;WACvC5B,KAAK,UAAU4B,KAAK,CAAC,SAAS;WAC9B5B,KAAK,SAAS4B,KAAK,CAAC,QAAQ;WAC5B5B,KAAK,aAAaD,MAAM6B,KAAK,CAAC,YAAY,EAAE1C,IAAI,CAAC;WACjDc,KAAK,2BAA2B4B,KAAK,CAAC,mBAAmB;WACzD5B,KAAK,QAAQD,MAAM6B,KAAK,CAAC,OAAO,EAAE1C,IAAI,CAAC;WACvCc,KAAK,oBAAoBD,MAAM6B,KAAK,CAAC,UAAU,EAAE1C,IAAI,CAAC;WACtDc,KAAK,YAAYgC,OAAOJ,KAAK,CAAC,WAAW,KAAK;WAC9C5B,KAAK,cAAc4B,KAAK,CAAC,YAAY;WACrC5B,KAAK,aAAa4B,KAAK,CAAC,WAAW;QACtC,CAAC,wBAAwB,EAAEA,KAAK,CAAC,WAAW,KAAK,OAAO,QAAQ,MAAM;QACtE,CAAC,wBAAwB,EAAEA,KAAK,CAAC,YAAY,KAAK,OAAO,QAAQ,MAAM;WACpE5B,KAAK,sBAAsB4B,KAAK,CAAC,oBAAoB;WACrDb,YAAYa,KAAK,CAAC,SAAS;WAC3B5B,KAAK,SAAS4B,KAAK,CAAC,QAAQ;WAC5BxB,cAAcwB,KAAK,CAAC,WAAW;WAC9BnC,KAAKmC,KAAK,CAAC,YAAY,EAAEtB,MAAM,GAAGG,UAAUmB,KAAK,CAAC,YAAY,IAAI,EAAE;KACzE;AACH;AAEA;;;;;;CAMC,GACD,OAAO,MAAMc,yBAAyB,MAAM;AAE5C,wHAAwH,GACxH,MAAMC,YAAY;IAChB;QAAEC,OAAO;QAAK7D,MAAM;QAAK8D,QAAQ;IAAI;IACrC;QAAED,OAAO;QAAK7D,MAAM;QAAI8D,QAAQ;IAAG;IACnC;QAAED,OAAO;QAAI7D,MAAM;QAAI8D,QAAQ;IAAG;CACnC;AAED,MAAMC,MAAM,CAACtD,OAAgBuD,MAC3B,OAAOvD,UAAU,YAAYA,MAAMc,MAAM,GAAGyC,MAAM,GAAGvD,MAAMwD,KAAK,CAAC,GAAGD,MAAM,GAAGE,OAAO,GAAG,CAAC,CAAC,GAAGzD;AAE9F;;;;;CAKC,GACD,OAAO,SAAS0D,cAAc7D,IAAqB,EAAEuC,KAAY;IAC/D,MAAMuB,OAAO,CAACC,YAAqBb,gBAAgBlD,MAAM+D,WAAWlE,IAAI,CAAC,MAAMoB,MAAM;IACrF,IAAI6C,KAAKvB,UAAUc,wBAAwB,OAAOd;IAClD,IAAIyB,SAAgBzB;IACpB,KAAK,MAAM0B,QAAQX,UAAW;QAC5BU,SAAS,aACJzB;YACHgB,OAAOE,IAAIlB,KAAK,CAAC,QAAQ,EAAE0B,KAAKV,KAAK;YACrCf,UAAUpC,KAAKmC,KAAK,CAAC,WAAW,EAAE5C,GAAG,CAAC,CAACa,QAAW,aAAKA;oBAAOd,MAAM+D,IAAIjD,KAAK,CAAC,OAAO,EAAEyD,KAAKvE,IAAI;;YAChG+C,WAAWrC,KAAKmC,KAAK,CAAC,YAAY,EAAE5C,GAAG,CAAC,CAAC2B,OAAU,aAAKA;oBAAMiC,OAAOE,IAAInC,IAAI,CAAC,QAAQ,EAAE2C,KAAKvE,IAAI;;YACjG8D,QAAQpD,KAAKmC,KAAK,CAAC,SAAS,EAAE5C,GAAG,CAAC,CAACiC,QAAW,aAAKA;oBAAOzB,OAAOsD,IAAI7B,KAAK,CAAC,QAAQ,EAAEqC,KAAKT,MAAM;;;QAElG,IAAIM,KAAKE,WAAWX,wBAAwB,OAAOW;IACrD;IACA,OAAOA;AACT;AAEA;;;;CAIC,GACD,OAAO,SAASE,sBAAsBlE,IAAqB,EAAEuC,KAAY;IACvE,OAAOA,KAAK,CAACT,0BAA0B,KAAK,OACxCoB,gBAAgBlD,MAAM6D,cAAc7D,MAAMuC,UAC1CD,yBAAyBtC,MAAMuC;AACrC;AAEA,gCAAgC,GAChC,OAAO,SAAS4B,kBAAkBnE,IAAqB,EAAEuC,KAAY;IACnE,OAAO;QAAC,CAAC,QAAQ,EAAEvC,MAAM;WAAKkE,sBAAsBlE,MAAMuC;KAAO,CAAC1C,IAAI,CAAC;AACzE;AAEA,sCAAsC,GACtC,OAAO,SAASuE,iBAAiBC,KAA+D;IAC9F,OAAO;QACL,CAAC,QAAQ,EAAEA,MAAMrE,IAAI,EAAE;QACvB,CAAC,cAAc,EAAE7C,sBAAsBkH,MAAMrE,IAAI,EAAEL,GAAG,CAAC,CAACwC,MAAQ,CAAC,EAAE,EAAEA,IAAI,EAAE,CAAC,EAAEtC,IAAI,CAAC,OAAO;QAC1F,CAAC,sBAAsB,EAAEwE,MAAMC,OAAO,CAACC,OAAO,CAAC,QAAQ,KAAKC,IAAI,GAAGb,KAAK,CAAC,GAAGzH,iCAAiC;QAC7G;QACA;WACGgI,sBAAsBG,MAAMrE,IAAI,EAAEqE,MAAM9B,KAAK;KACjD,CAAC1C,IAAI,CAAC;AACT;AAEA,2CAA2C,GAC3C,OAAO,SAAS4E,mBAAmBJ,KAIlC;IACC,OAAO;QACL,CAAC,QAAQ,EAAEA,MAAMK,UAAU,EAAE;QAC7B;WACGL,MAAM1C,MAAM,CAAChC,GAAG,CACjB,CAACiC,OAAO+C,QAAU,CAAC,EAAE,EAAEA,MAAM,EAAE,EAAE/C,MAAMhB,KAAK,CAAC,EAAE,EAAEgB,MAAMgD,IAAI,GAAGhD,MAAMiD,QAAQ,GAAG,eAAe,IAAI;QAEpG;WACGR,MAAMS,OAAO,CAACnF,GAAG,CAAC,CAACoF,QAAQJ,QAAU,CAAC,EAAE,EAAEA,MAAM,EAAE,EAAEI,OAAOC,MAAM,IAAI,cAAc,EAAE,EAAED,OAAOE,KAAK,EAAE;KACzG,CAACpF,IAAI,CAAC;AACT;AAEA;;4EAE4E,GAE5E;;;;CAIC,GACD,OAAO,SAASqF,eAAeb,KAQ9B;QAMGA;IALF,MAAMc,SAASrJ,WAAW;IAC1B,KAAK,MAAMmH,QAAQ;QACjB;QACAoB,MAAMrE,IAAI;QACVqE,MAAMe,EAAE;SACRf,gBAAAA,MAAMgB,MAAM,YAAZhB,gBAAgB;QAChBA,MAAMiB,KAAK;QACXjB,MAAMkB,MAAM;WACTlB,MAAMmB,MAAM,CAAC7F,GAAG,CAAC,CAAC8F,QAAUA,MAAM/F,IAAI;QACzCgG,KAAKC,SAAS,CAACtB,MAAMuB,IAAI;KAC1B,CAAE;QACDT,OAAOU,MAAM,CAAC,GAAG5C,KAAKhC,MAAM,CAAC,CAAC,EAAEgC,KAAK,MAAM,CAAC;IAC9C;IACA,OAAOkC,OAAOA,MAAM,CAAC;AACvB;AAkBA,MAAMW,SAAS,CAAC3F;IACd,IAAIA,iBAAiB4F,MAAM,OAAO5F,MAAM6F,OAAO;IAC/C,MAAMC,WAAY9F,yBAAD,AAACA,MAA8C8F,QAAQ;IACxE,OAAO,OAAOA,aAAa,aAAaA,SAASC,IAAI,CAAC/F,SAAS;AACjE;AAEA;;;;;CAKC,GACD,OAAO,MAAMgG,wBAA2C,OAAOC,OAAOjE,KAAKkE;IACzE,IAAI,CAACA,WAAW,OAAO,EAAE;IACzB,MAAMC,WAAW,MAAMD,UACpB3B,UAAU,CAAC,QACX6B,GAAG,CAACH,OACJ1B,UAAU,CAACzI,2BACXuK,KAAK,CAAC,OAAO,MAAMrE,KACnBsE,KAAK,CAAC/H,yBACNgI,GAAG;IACN,OAAOJ,SAASK,IAAI,CAAChH,GAAG,CAAC,CAAC4G;YACVA,WAGJK,cACKA,YACRA;QALP,MAAMA,QAAQL,YAAAA,IAAIK,IAAI,cAARL,YAAc,CAAC;QAC7B,OAAO;YACLM,OAAON,IAAInB,EAAE;YACbC,MAAM,GAAEuB,eAAAA,KAAKvB,MAAM,YAAXuB,eAAe;YACvBtF,MAAMR,QAAO8F,aAAAA,KAAKtF,IAAI,YAATsF,aAAa;YAC1BzE,GAAG,GAAEyE,YAAAA,KAAKzE,GAAG,YAARyE,YAAY;YACjBE,aAAahB,OAAOc,KAAKG,SAAS;YAClCC,UAAUzK,kBAAkBqK,KAAKI,QAAQ;QAC3C;IACF;AACF,EAAC;AAED,gFAAgF,GAChF,OAAO,SAASC,oBACdC,UAA2C,EAC3CC,OAAyE;IAEzE,MAAMC,QAAQD,QAAQE,GAAG,CAACrB,OAAO,KAAKrH;IACtC,IAAI2I,OAA4E;IAChF,KAAK,MAAMvD,aAAamD,WAAY;YAIvBnD;YADPA;QAFJ,IAAIA,UAAU8C,KAAK,KAAKM,QAAQN,KAAK,IAAI9C,UAAUzC,IAAI,KAAK,UAAU;QACtE,IAAIyC,UAAUsB,MAAM,KAAK8B,QAAQ9B,MAAM,IAAItB,UAAU5B,GAAG,KAAKgF,QAAQhF,GAAG,EAAE;QAC1E,IAAI4B,EAAAA,sBAAAA,UAAUiD,QAAQ,qBAAlBjD,oBAAoB/D,IAAI,MAAK,UAAU;QAC3C,MAAMuH,MAAKxD,yBAAAA,UAAU+C,WAAW,YAArB/C,yBAAyB;QACpC,IAAIwD,KAAKH,OAAO;QAChB,IAAI,CAACE,QAAQC,KAAKD,KAAKC,EAAE,EAAED,OAAO;YAAET,OAAO9C,UAAU8C,KAAK;YAAEG,UAAUjD,UAAUiD,QAAQ;YAAEO;QAAG;IAC/F;IACA,OAAOD,OAAO;QAAET,OAAOS,KAAKT,KAAK;QAAEG,UAAUM,KAAKN,QAAQ;IAAC,IAAI;AACjE;AAWA;;;;;;CAMC,GACD,OAAO,eAAeQ,sBACpBL,OAAsC,EACtCM,OAA2B,CAAC,CAAC;QAOnBN;IALV,MAAM7C,UAAUhI,kBAAkB6K,QAAQO,MAAM;IAChD,IAAI,OAAOpD,YAAY,UAAU,OAAO;QAAEqD,QAAQ;QAAKC,OAAOtD;IAAQ;IACtE,OAAO3G,mBAAmB;QACxB0I,WAAWc,QAAQd,SAAS;QAC5BD,OAAOe,QAAQf,KAAK;QACpBf,MAAM,GAAE8B,kBAAAA,QAAQ9B,MAAM,YAAd8B,kBAAkB;OACvBvJ,mBAAmB0G;QACtBuD,KAAKV,QAAQU,GAAG;QAChBC,KAAKX,QAAQW,GAAG;OACZL,KAAKM,SAAS,GAAG;QAAEA,WAAWN,KAAKM,SAAS;IAAC,IAAI,CAAC;QACtDV,GAAG,UAAEI,KAAKJ,GAAG,oBAARI,KAAKJ,GAAG,MAARI,wBAAgB,IAAI1B;;AAE7B;AAEA,OAAO,MAAMiC,oBAAoC,CAACb,UAAYK,sBAAsBL,SAAQ;AAY5F,SAASc,QAAQd,OAAyB;;IACxC,eAAOA,QAAQe,QAAQ,oBAAhBf,QAAQe,QAAQ,MAAhBf,SAAmB,6BAAczK,eAAe;AACzD;AAQA;;;CAGC,GACD,eAAeyL,gBAAgBhB,OAAyB,EAAEiB,MAAyB;QAGpBC;IAF7D,MAAM,EAAEA,GAAG,EAAEhB,GAAG,EAAEhB,SAAS,EAAE,GAAGc;IAChC,MAAMpI,SAAS2G,KAAK4C,KAAK,CACvB5C,KAAKC,SAAS,CAAC;QAAEkB,OAAOwB,IAAIE,GAAG;QAAEnC,OAAOiC,IAAIjC,KAAK;QAAEf,MAAM,GAAEgD,cAAAA,IAAIhD,MAAM,YAAVgD,cAAc;QAAMG,WAAWH,IAAIG,SAAS;OAAKJ;IAE9G,MAAM/B,UACH3B,UAAU,CAAC,QACX6B,GAAG,CAAC8B,IAAIjC,KAAK,EACb1B,UAAU,CAACzI,2BACXsK,GAAG,CAAC8B,IAAIE,GAAG,EACXE,GAAG,CAAC,aAAK1J;QAAQgI,WAAWM;QAAKqB,WAAWrM,kBAAkBgL;;AACnE;AAEA,OAAO,SAASsB,mBAAmBlB,OAAyB,CAAC,CAAC;QAC1CA;IAAlB,MAAMM,aAAYN,kBAAAA,KAAKM,SAAS,YAAdN,kBAAkB1L;IACpC,MAAM6M,mBAAmBnB,KAAKmB,gBAAgB,KAAKC,YAAY1C,wBAAwBsB,KAAKmB,gBAAgB;IAC5G,OAAO,OAAOzB;YA4BGkB;QA3Bf,MAAM,EAAEA,GAAG,EAAEhB,GAAG,EAAEyB,MAAM,EAAEzC,SAAS,EAAE,GAAGc;QACxC,MAAM7B,QAAQ2C,QAAQd;QACtB,MAAM7C,UAAUhI,kBAAkB+L,IAAIX,MAAM;QAC5C,IAAI,OAAOpD,YAAY,UAAU,OAAO,aAAKtG,iBAAiBsH;YAAQyD,SAASzE;;QAC/E,MAAM,EAAE0E,QAAQ,EAAE5D,EAAE,EAAE,GAAGxH,mBAAmB0G;QAC5C,MAAM2E,QAAQlB,UAAUiB;QACxB,IAAI,CAACC,SAASA,MAAMC,QAAQ,KAAK9M,kBAAkB;YACjD,OAAO,aAAK4B,iBAAiBsH;gBAAQyD,SAASrL;;QAChD;QACA,IAAI6E;QACJ,IAAI;gBAGQ8F,cAIFlB;YANR,MAAMgC,OAAO,MAAMF,MAAMG,MAAM,CAACD,IAAI,CAAC;gBACnC/C,OAAOiC,IAAIjC,KAAK;gBAChBf,MAAM,GAAEgD,eAAAA,IAAIhD,MAAM,YAAVgD,eAAc;gBACtBjD;gBACA,uEAAuE;gBACvE0C,KAAKO,IAAIG,SAAS;gBAClBX,GAAG,GAAGV,eAAAA,QAAQU,GAAG,YAAXV,eAA8D;gBACpEE;YACF;YACA,IAAI8B,KAAKE,EAAE,KAAK,OAAO,OAAO,aAAKrL,iBAAiBsH;gBAAQyD,SAASI,KAAKvB,KAAK;;YAC/ErF,QAAQ4G,KAAK5G,KAAK;QACpB,EAAE,OAAOqF,OAAO;YACd0B,QAAQ1B,KAAK,CAAC,4BAA4B;gBAAExB,OAAOiC,IAAIjC,KAAK;gBAAES,OAAOwB,IAAIE,GAAG;gBAAES;gBAAUpB;YAAM;YAC9F,OAAO,aAAK5J,iBAAiBsH;gBAAQyD,SAASnK;;QAChD;QACA,MAAMP,YAAYF,uBAAuBE,SAAS,CAACiH;QACnD,MAAMD,UAASgD,cAAAA,IAAIhD,MAAM,YAAVgD,cAAc;QAE7B,0EAA0E;QAC1E,qEAAqE;QACrE,MAAMkB,OAAO,OACXC,OACApB,QACAqB;YAEA,IAAI;gBACF,MAAMtB,gBAAgBhB,SAASiB;YACjC,EAAE,OAAOR,OAAO;gBACd0B,QAAQ1B,KAAK,CAAC,6BAA6B;oBAAExB,OAAOiC,IAAIjC,KAAK;oBAAES,OAAOwB,IAAIE,GAAG;oBAAEX;gBAAM;gBACrF,OAAO,aAAK4B;oBAAOE,SAAS,EAAE;oBAAEX,SAASlK;;YAC3C;YACA,OAAO,aACF2K;gBACHE,SAAS;oBAAC;wBAAEV,UAAU;wBAAO5D,IAAIqE,OAAOrE,EAAE;wBAAEC;wBAAQzE,OAAO6I,OAAO7I,KAAK;wBAAEoG,UAAU,aAAKyC,OAAOE,GAAG;oBAAG;iBAAE;;QAE3G;QAEA,IAAIrF,QAAQhD,IAAI,KAAK,UAAU;YAC7B,MAAM,EAAEtB,IAAI,EAAE,GAAGsE,QAAQvF,MAAM;YAC/B,IAAIwD,KAAK,CAAC,SAAS,KAAKvC,MAAM,OAAO,aAAKhC,iBAAiBsH;gBAAQyD,SAASnK;;YAC5E,MAAMgL,eAAe7J,wBAAwBC;YAC7C,MAAM4F,OAAOtI,gBAAgB0C;YAC7B,MAAMuF,SAASpB,kBAAkBnE,MAAMuC;YACvC,MAAMJ,MAAM+C,eAAe;gBACzBlF;gBACAoF;gBACAC;gBACAC;gBACAC;gBACAC,QAAQ5I,uBAAuBiM,WAAW;oBACxCe;oBACAC,OAAOlN,mBAAmBmC,wBAAwBC,MAAM;gBAC1D;gBACA6G;YACF;YACA,MAAM6D,SAAS;gBAAErE,IAAIjJ,kBAAkB4C,MAAM,CAACiB,MAAMoF;gBAAKxE,OAAO1B,cAAcH,MAAM;gBAAE4K,KAAK;oBAAE3J,MAAM;oBAAUjB,QAAQ;wBAAEiB;wBAAMoF;oBAAG;gBAAE;YAAE;YACpI,mEAAmE;YACnE,oDAAoD;YACpD,MAAM0E,SAASlB,mBACX3B,oBAAoB,MAAM2B,iBAAiBP,IAAIjC,KAAK,EAAEjE,KAAKkE,WAAW0D,KAAK,CAAC,IAAM,EAAE,GAAG;gBACrFlD,OAAOwB,IAAIE,GAAG;gBACdlD;gBACAlD;gBACAkF;YACF,KACA;YACJ,IAAIyC,QAAQ;gBACV,OAAOP,KACLvL,iBAAiBsH,QACjB;oBAAEhE,MAAM;oBAAU0H;oBAAUgB,UAAU5E;oBAAIjD;oBAAK6E,UAAU,aAAK8C,OAAO9C,QAAQ;wBAAEiD,YAAYH,OAAOjD,KAAK;;gBAAG,GAC1G4C;YAEJ;YACA,MAAMS,aAAa,MAAMrN,uBAAuBiC,wBAAwBC,MAAM,EAAE;gBAC9EkF,MAAM;gBACNqB;gBACAsE;gBACAO,UAAU;oBAAC;wBAAEC,MAAM;wBAAQC,SAAS9E;oBAAO;iBAAE;gBAC7CK;gBACAvH;eACIyK,SAAS;gBAAEA;YAAO,IAAI,CAAC;gBAC3BwB,OAAO,CAAClC,SAAW3K,iBAAiB2K,QAAQ/K,wBAAwB2C,MAAMuC;;YAE5E,MAAMiH,QAAQzL,kBAAkBmM;YAChC,IAAIA,WAAWvC,MAAM,KAAK,WAAW,OAAO,aAAK6B;gBAAOe,SAAS;;YACjE,IAAIL,WAAWvC,MAAM,KAAK,eAAe,OAAO,aAAK6B;gBAAOT,SAASmB,WAAWM,OAAO;;YACvF,MAAMxD,WAAgC;gBACpChH,MAAM;gBACNjB,QAAQ;oBAAEiB;oBAAMoF;gBAAG;gBACnBqF,SAASP,WAAW/J,KAAK,CAACsK,OAAO;gBACjCC,UAAUR,WAAW/J,KAAK,CAACuK,QAAQ;gBACnC5H,OAAOoH,WAAW/J,KAAK,CAAC2C,KAAK;gBAC7B6H,UAAUT,WAAW/J,KAAK,CAACwK,QAAQ;gBACnCC,MAAMvD,IAAIwD,WAAW,GAAGlH,KAAK,CAAC,GAAG;YACnC;YACA,OAAO4F,KAAKC,OAAO;gBAAElI,MAAM;gBAAU0H;gBAAUgB,UAAU5E;gBAAIjD;gBAAK6E;YAAS,GAAGyC;QAChF;QAEA,IAAInF,QAAQhD,IAAI,KAAK,SAAS;YAC5B,MAAM,EAAEtB,IAAI,EAAE,GAAGsE,QAAQvF,MAAM;YAC/B,IAAIwD,KAAK,CAAC,SAAS,KAAKvC,MAAM,OAAO,aAAKhC,iBAAiBsH;gBAAQyD,SAASnK;;YAC5E,MAAMkM,cAAc3N,sBAAsB6C;YAC1C,MAAMkK,aAAa,MAAMrN,uBAAuBiC,wBAAwBE,KAAK,EAAE;gBAC7EiF,MAAM;gBACNqB;gBACAsE,cAAc3J;gBACdkK,UAAU;oBAAC;wBAAEC,MAAM;wBAAQC,SAASjG,iBAAiB;4BAAEpE;4BAAMuC;4BAAO+B,SAAS+D,IAAI0C,KAAK;wBAAC;oBAAG;iBAAE;gBAC5FnF,MAAM9I;gBACNuB;eACIyK,SAAS;gBAAEA;YAAO,IAAI,CAAC;gBAC3BwB,OAAO,CAAClC,SAAW7K,gBAAgB6K,QAAQ;wBAAE0C;oBAAY;;YAE3D,MAAMtB,QAAQzL,kBAAkBmM;YAChC,IAAIA,WAAWvC,MAAM,KAAK,WAAW,OAAO,aAAK6B;gBAAOe,SAAS;;YACjE,IAAIL,WAAWvC,MAAM,KAAK,eAAe,OAAO,aAAK6B;gBAAOT,SAASmB,WAAWM,OAAO;;YACvF,MAAMxD,WAA+B;gBACnChH,MAAM;gBACNjB,QAAQ;oBAAEiB;oBAAMoF;gBAAG;gBACnBjE,SAAS+I,WAAW/J,KAAK,CAACgB,OAAO;gBACjC6J,MAAMd,WAAW/J,KAAK,CAAC6K,IAAI;YAC7B;YACA,OAAOzB,KACLC,OACA;gBAAElI,MAAM;gBAAS0H;gBAAUgB,UAAU5E;gBAAIjD,KAAK;gBAAM6E;YAAS,GAC7D;gBAAE5B,IAAIjJ,kBAAkB6C,KAAK,CAACgB,MAAMoF;gBAAKxE,OAAO1B,cAAcF,KAAK;gBAAE2K,KAAK;oBAAE3J,MAAM;oBAASjB,QAAQ;wBAAEiB;wBAAMoF;oBAAG;gBAAE;YAAE;QAEtH;QAEA,MAAMzD,SAASvE,kBAAkBmF;QACjC,IAAIA,KAAK,CAAC,SAAS,KAAK,YAAY,CAACZ,OAAOV,MAAM,EAAE;YAClD,OAAO,aAAKjD,iBAAiBsH;gBAAQyD,SAASnK;;QAChD;QACA,MAAMsL,aAAa,MAAMrN,uBAAuBiC,wBAAwBG,OAAO,EAAE;YAC/EgF,MAAM;YACNqB;YACAsE,cAAc1J;YACdiK,UAAU;gBACR;oBACEC,MAAM;oBACNC,SAAS5F,mBAAmB;wBAAEC,YAAYJ,QAAQI,UAAU;wBAAE/C;wBAAQmD,SAASR,QAAQQ,OAAO;oBAAC;gBACjG;aACD;YACDc,MAAM5I;YACNqB;WACIyK,SAAS;YAAEA;QAAO,IAAI,CAAC;YAC3BwB,OAAO,CAAClC,SAAW5K,kBAAkB4K,QAAQ;oBAAEtD,SAASR,QAAQQ,OAAO;oBAAEnD;gBAAO;;QAElF,MAAM6H,QAAQzL,kBAAkBmM;QAChC,IAAIA,WAAWvC,MAAM,KAAK,WAAW,OAAO,aAAK6B;YAAOe,SAAS;;QACjE,IAAIL,WAAWvC,MAAM,KAAK,eAAe,OAAO,aAAK6B;YAAOT,SAASmB,WAAWM,OAAO;;QACvF,MAAMxD,WAAiC;YACrChH,MAAM;YACN0E,YAAYJ,QAAQI,UAAU;YAC9BuG,SAASf,WAAW/J,KAAK;YACzB2E,SAASR,QAAQQ,OAAO,CAAC7D,MAAM;QACjC;QACA,OAAOsI,KACLC,OACA;YAAElI,MAAM;YAAW0H;YAAUgB,UAAU1F,QAAQI,UAAU;YAAEvC,KAAK;YAAM6E;QAAS,GAC/E;YACE5B,IAAIjJ,kBAAkB8C,OAAO,CAACqF,QAAQI,UAAU;YAChD9D,OAAO1B,cAAcD,OAAO;YAC5B0K,KAAK;gBAAE3J,MAAM;gBAAW0E,YAAYJ,QAAQI,UAAU;YAAC;QACzD;IAEJ;AACF;AAEA,OAAO,MAAMwG,kBAAkBvC,qBAAoB;AAEnD,kFAAkF,GAClF,OAAO,SAASwC;IACdlN,kBAAkB,OAAOiN,iBAAiB;QAAEzM,WAAWD;IAA2B;IAClFX,uBAAuB,OAAOmK;AAChC"}
1
+ {"version":3,"sources":["../../../../../../../libs/plugins/ai/src/lib/jobs/ai-job-crm-step.ts"],"sourcesContent":["/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { createHash } from 'node:crypto'\nimport { pluginRecordFactsReader } from '@aglyn/aglyn/plugin-manager/plugin-record-facts'\nimport {\n AI_CRM_ANSWER_RETENTION_DAYS,\n AI_CRM_ANSWERS_COLLECTION,\n AI_CRM_EMAIL_REQUEST_MAX_CHARS,\n AI_CRM_OUTPUT_IDS,\n AI_CRM_PLUGIN_ID,\n aiCrmAnswerExpiry,\n parseAiCrmRequest,\n readAiCrmProposal,\n type AiCrmAnswerRecord,\n type AiCrmEmailProposal,\n type AiCrmMappingProposal,\n type AiCrmOutputRef,\n type AiCrmProposal,\n type AiCrmRecordKind,\n type AiCrmRecordProposal,\n} from '../model/ai-crm'\nimport { AI_STEP_TIERS } from '../providers/catalog'\nimport type { AiTool } from '../providers/contract'\nimport { AI_ROUTING_TABLE, aiModelForStep, aiRouteSettings } from '../providers/routing'\nimport { aiDoctrineScopeFor, aiDoctrineSystemBlocks, runValidatedGeneration } from '../runtime/ai-doctrine'\nimport type { AiSystemBlock } from '../runtime/ai-runtime'\nimport {\n AI_CRM_EMAIL_TOOL,\n AI_CRM_EMAIL_TOOL_NAME,\n AI_CRM_MAPPING_TOOL,\n AI_CRM_MAPPING_TOOL_NAME,\n AI_CRM_RECORD_TOOL_NAME,\n aiCrmEmailMergeFields,\n aiCrmImportFields,\n aiCrmRecordCheckContext,\n aiCrmRecordTool,\n checkAiCrmEmail,\n checkAiCrmMapping,\n checkAiCrmRecord,\n type AiCrmImportField,\n} from '../tools/ai-crm-tool'\nimport {\n AI_CRM_UNAVAILABLE_COPY,\n aiCrmAccessRefusal,\n aiCrmFactsResource,\n type AiCrmReaderLookup,\n} from './ai-crm-access'\nimport { registerAiJobAdmission, type AiJobAdmission, type AiJobAdmissionRefusal } from './ai-job-admission'\nimport { aiJobStepBudget } from './ai-job-budget'\nimport { aiGenerationSpent, aiUnspentOutcome } from './ai-job-generation'\nimport type { AiJobStepContext, AiJobStepOutcome, AiJobStepRunner } from './ai-job-text-step'\nimport { registerAiJobStep } from './ai-jobs'\n\n/**\n * The `crm` step (AGL-2917): CRM by AI, as proposals.\n *\n * `inputs.task` says what the job is asked (`model/ai-crm.ts`):\n *\n * - `record` — one contact, company, deal or lead (`record`, `recordId`): a\n * summary, the next step as a task, a deal's stage, a lead's standing;\n * - `email` — a draft for the CRM composer, from what the member asked for\n * (the brief) and the record's facts;\n * - `mapping` — an import's columns (`collection`, `columns`) matched to the\n * fields the CRM's import fills.\n *\n * ## The CRM decides what is read\n *\n * The step never reads a CRM document. It asks the CRM's readers on the core's\n * record-facts seam for the job's creator, and the CRM applies its own rules —\n * the member's reach and `data.manage`, the plan, the record's visibility on\n * the site — and reports the facts it chooses to. The admission below asks the\n * same reader before the job exists, beside what an in-process call skips of\n * the plugin API dispatcher: the CRM switched on and released where the job\n * runs. A mapping sends the file's headers and each column's shape, never a\n * cell.\n *\n * ## It writes no CRM record\n *\n * The CRM's task form, stage route, composer and import drawer are the\n * writes, and a member makes each one. No email is sent by a job.\n *\n * ## The answer is kept apart from the job\n *\n * Every member of a workspace may read its jobs, and not every member may\n * read every record. So a job's output names only the question — which\n * record, which import — and the answer is kept at\n * `orgs/{orgId}/aiCrmAnswers/{jobId}`, which no client may read, for two\n * weeks (`AI_CRM_ANSWER_RETENTION_DAYS`). The answer door\n * (`server/ai-crm-answer.ts`) serves it to a member the CRM still lets read\n * the record, asking the same reader again.\n *\n * ## A summary is asked once per timeline\n *\n * A record answer is keyed on the whole request — the record, the site, the\n * model, the rules, the tool and the facts as the CRM reported them — and a\n * later job whose key matches a kept answer reuses it and spends nothing.\n * The facts change exactly when something the model was told changes, so an\n * unchanged record is never asked twice while its answer is kept.\n */\n\n/**\n * ASSUMED: the step's own round trips — the facts read (the record and its\n * three windowed queries, in parallel), a record's reuse lookup, and the\n * answer's write.\n */\nexport const AI_CRM_FACTS_READS_MS = 1_500\n\n/**\n * The step's time (AGL-3035): one generation and its re-ask on the tier\n * `job.crm` is served from, after the facts read, with no site inventory and\n * so no lookup round. It fits an inline door's budget, so a member who asks on\n * a record page is answered in the request.\n */\nexport const AI_JOB_CRM_STEP_BUDGET = aiJobStepBudget({\n tier: AI_STEP_TIERS['job.crm'],\n maxTokens: AI_ROUTING_TABLE['job.crm'].maxTokens,\n lookups: 0,\n ownReadsMs: AI_CRM_FACTS_READS_MS,\n})\n\n/** The least time one CRM step needs before it starts. */\nexport const AI_JOB_CRM_STEP_MINIMUM_MS = AI_JOB_CRM_STEP_BUDGET.minimumMs\n\n/** Kept answers to one request a reuse lookup reads. */\nexport const AI_CRM_REUSE_CANDIDATES = 20\n\n/** How long a record answer stays reusable, beside its facts being unchanged: as long as it is kept. */\nexport const AI_CRM_REUSE_WINDOW_MS = AI_CRM_ANSWER_RETENTION_DAYS * 24 * 60 * 60_000\n\nexport const AI_CRM_FACTS_FAILURE_COPY = 'The CRM record could not be read. Try again.'\nexport const AI_CRM_KEEP_FAILURE_COPY = 'The answer could not be kept. Try again.'\n\n/** The generation kinds the step asks under, which the doctrine states only its field rules to. */\nexport const AI_CRM_GENERATION_KINDS = {\n record: 'crm-record',\n email: 'crm-email',\n mapping: 'crm-mapping',\n} as const\n\nconst OUTPUT_LABELS = {\n record: 'CRM summary',\n email: 'CRM email draft',\n mapping: 'Import column matches',\n} as const\n\ntype Facts = Readonly<Record<string, unknown>>\n\n/* ------------------------------------------------------------------------ *\n * The rules\n * ------------------------------------------------------------------------ */\n\nconst WRITE_ONLY_FROM_FACTS =\n 'Write only from the facts. Never invent an event, a date, an amount, a name or a promise the facts do not state, and write dates as the facts do.'\n\nconst NEXT_STEP_RULE =\n 'nextStep: the one task that should come next, or null when an open task already covers it or nothing is worth doing. Its title starts with a verb, and its reason names the fact behind it.'\n\nconst LANGUAGE_RULE = 'Write in the language of the facts’ notes and timeline.'\n\nfunction rulesBlock(opening: string, toolName: string, rules: readonly string[]): AiSystemBlock[] {\n return [\n {\n text:\n `${opening}\\n\\n` +\n `Answer by calling ${toolName} exactly once. A reply in prose cannot be used.\\n\\n` +\n `Rules:\\n${rules.map((rule) => `- ${rule}`).join('\\n')}`,\n cacheBreakpoint: true,\n },\n ]\n}\n\n/**\n * The rules for a record of one kind. Each kind is sent the rules its tool can\n * be refused for, and no others: on a model that caches no prompt this short,\n * a rule a request cannot break is paid for on every request.\n */\nexport function aiCrmRecordInstructions(kind: AiCrmRecordKind): AiSystemBlock[] {\n const opening = `You help a small business's team work its customer records. You are given one ${kind} a team member opened, as facts read from their CRM.`\n const rules =\n kind === 'lead'\n ? [\n WRITE_ONLY_FROM_FACTS,\n 'summary: two sentences at most: how the lead came in, what it did, and when it was last seen.',\n 'standing: two sentences at most on why the lead stands where it does: its status, how it came in, and how often and how recently it was active. There is no lead score; never give one.',\n LANGUAGE_RULE,\n ]\n : [\n WRITE_ONLY_FROM_FACTS,\n `summary: two sentences at most: when the ${kind} was last in touch and how, and what is still open.`,\n NEXT_STEP_RULE,\n ...(kind === 'deal'\n ? [\n 'stage: the open stage the deal should move to, by its id in the facts, only when the timeline shows the deal has moved there; otherwise null, and always null for a deal that is won or lost. Never propose winning or losing a deal.',\n ]\n : []),\n LANGUAGE_RULE,\n ]\n return rulesBlock(opening, AI_CRM_RECORD_TOOL_NAME, rules)\n}\n\nexport const AI_CRM_EMAIL_INSTRUCTIONS: AiSystemBlock[] = rulesBlock(\n 'You draft a one-to-one email that a team member reviews, edits and sends themselves from a customer record in their CRM. Nothing is sent until they press Send.',\n AI_CRM_EMAIL_TOOL_NAME,\n [\n 'Write what the team member asks for, from the record’s facts. Never invent a price, a date, an offer, a meeting time or a promise that neither states.',\n 'body: plain text in short paragraphs separated by a blank line, with no markdown, no links and no signature block beyond the sign-off.',\n 'Greet the person and sign off only with the merge fields the request lists, written exactly as listed. Use no other merge field.',\n 'Never write an email address or a phone number.',\n 'Write in the language of the team member’s request.',\n ],\n)\n\nexport const AI_CRM_MAPPING_INSTRUCTIONS: AiSystemBlock[] = rulesBlock(\n 'You match the columns of a spreadsheet a team member is importing into their CRM to the fields the import fills. You are given each column’s header and the shape of its values, never the values.',\n AI_CRM_MAPPING_TOOL_NAME,\n [\n 'Match a column only to a field the request lists, both by their numbers, and only when the column’s header says it holds that field. Leave out a column no field fits.',\n 'A field takes at most one column, and a column one field.',\n 'A column’s shape must suit its field: an email field takes an email column; a phone field a phone or number column; a number, date or yes-no field a column of that shape. Only an email field takes an email column. An empty column takes any field its header names.',\n ],\n)\n\n/* ------------------------------------------------------------------------ *\n * The facts, as the model reads them\n * ------------------------------------------------------------------------ */\n\nconst text = (value: unknown): string => (typeof value === 'string' ? value : '')\nconst list = (value: unknown): Array<Record<string, unknown>> =>\n Array.isArray(value) ? value.filter((entry): entry is Record<string, unknown> => Boolean(entry) && typeof entry === 'object') : []\nconst words = (value: unknown): string[] => (Array.isArray(value) ? value.map(text).filter(Boolean) : [])\n\nfunction line(label: string, value: unknown): string[] {\n const shown = typeof value === 'number' ? String(value) : text(value)\n return shown ? [`${label}: ${shown}`] : []\n}\n\nfunction timelineLines(value: unknown): string[] {\n const entries = list(value)\n if (!entries.length) return ['Timeline: nothing logged or captured yet']\n return [\n 'Timeline, newest first:',\n ...entries.map((entry) => {\n const detail = [\n text(entry['direction']),\n entry['from'] ? `from ${text(entry['from'])}` : '',\n entry['to'] ? `to ${text(entry['to'])}` : '',\n text(entry['delivery']),\n text(entry['outcome']),\n ].filter(Boolean)\n const subject = text(entry['subject'])\n return (\n `- ${text(entry['on'])} ${text(entry['kind'])}` +\n (detail.length ? ` (${detail.join(', ')})` : '') +\n (subject || entry['text'] ? ':' : '') +\n (subject ? ` \"${subject}\"` : '') +\n (text(entry['text']) ? ` ${text(entry['text'])}` : '')\n )\n }),\n ]\n}\n\nfunction taskLines(value: unknown): string[] {\n const tasks = list(value)\n if (!tasks.length) return ['Open tasks: none']\n return [\n 'Open tasks:',\n ...tasks.map(\n (task) =>\n `- ${text(task['title'])} (${[\n text(task['kind']),\n `${text(task['priority']) || 'normal'} priority`,\n ...(task['status'] ? [text(task['status'])] : []),\n task['due'] ? `due ${text(task['due'])}` : 'no due date',\n ...(task['overdue'] === true ? ['overdue'] : []),\n ...(task['assignee'] ? [`assigned to ${text(task['assignee'])}`] : []),\n ].join(', ')})` + (task['notes'] ? `: ${text(task['notes'])}` : ''),\n ),\n ]\n}\n\nfunction dealLines(value: unknown): string[] {\n const deals = list(value)\n if (!deals.length) return ['Deals: none']\n return [\n 'Deals:',\n ...deals.map(\n (deal) =>\n `- ${text(deal['title'])}: ${[\n text(deal['stage']),\n text(deal['status']),\n text(deal['amount']),\n deal['expectedClose'] ? `expected to close ${text(deal['expectedClose'])}` : '',\n ]\n .filter(Boolean)\n .join(', ')}`,\n ),\n ]\n}\n\n/** The record's custom field values under their labels (AGL-3520). */\nfunction customLines(value: unknown): string[] {\n const fields = list(value).filter((field) => text(field['label']) && text(field['value']))\n return fields.length\n ? ['Custom fields:', ...fields.map((field) => `- ${text(field['label'])}: ${text(field['value'])}`)]\n : []\n}\n\nconst yesNo = (label: string, value: unknown): string[] => (value === true ? [label] : [])\n\n/**\n * A record's facts as the CRM's reader marks them when it answered the whole\n * record (AGL-3520): `wholeRecord: true`, set only while the organization has\n * `release_crm_assist_whole_record` on. Absent, the facts are the disclosed\n * ones and are written by {@link aiCrmDisclosedFactsLines}.\n */\nexport const AI_CRM_WHOLE_RECORD_FIELD = 'wholeRecord'\n\n/** Keeps only `keys` of each entry: the disclosed writer's allow-list for nested rows. */\nconst only = (value: unknown, keys: readonly string[]): Array<Record<string, unknown>> =>\n list(value).map((entry) => Object.fromEntries(keys.filter((key) => key in entry).map((key) => [key, entry[key]])))\n\n/** What the disclosed writer reads of a timeline entry: never who an email was from or to. */\nconst DISCLOSED_TIMELINE_KEYS = ['on', 'kind', 'direction', 'subject', 'text', 'outcome', 'delivery'] as const\n/** What the disclosed writer reads of an open task: never its assignee, status or notes. */\nconst DISCLOSED_TASK_KEYS = ['title', 'kind', 'priority', 'due', 'overdue'] as const\n\n/**\n * The facts as lines of text while the published pages describe the narrow\n * set: only the fields named here are written, whatever else a reader\n * reports, so what reaches a prompt is decided in two places that both have\n * to agree — the CRM's disclosed builders and this list. A company's account\n * fields beyond its headcount, and a lead's profile fields, stay out of the\n * prompt as they always have.\n */\nexport function aiCrmDisclosedFactsLines(kind: AiCrmRecordKind, facts: Facts): string[] {\n const timeline = timelineLines(only(facts['timeline'], DISCLOSED_TIMELINE_KEYS))\n const openTasks = taskLines(only(facts['openTasks'], DISCLOSED_TASK_KEYS))\n if (kind === 'contact') {\n const orders = Number(facts['orders']) || 0\n return [\n ...line('Contact', facts['name']),\n ...line('Salutation', facts['salutation']),\n ...line('Job title', facts['jobTitle']),\n ...line('Department', facts['department']),\n // A request the person made, which a drafted next step must respect (AGL-3515).\n ...yesNo('Do not call: they asked not to be phoned', facts['doNotCall']),\n ...line('Company', facts['company']),\n ...line('Lifecycle stage', facts['lifecycleStage']),\n ...line('Tags', words(facts['tags']).join(', ')),\n ...line('Came in through', words(facts['sources']).join(', ')),\n ...(orders ? [`Orders: ${orders}${facts['lastPurchase'] ? `, the last on ${text(facts['lastPurchase'])}` : ''}`] : []),\n ...line('In the CRM since', facts['since']),\n ...line('Last opened or clicked an email', facts['lastEmailEngagement']),\n ...line('Notes', facts['notes']),\n ...timeline,\n ...openTasks,\n ...dealLines(facts['deals']),\n ]\n }\n if (kind === 'company') {\n return [\n ...line('Company', facts['name']),\n ...line('Domain', facts['domain']),\n ...line('Industry', facts['industry']),\n ...line('Employees', facts['employees'] ?? ''),\n ...line('Tags', words(facts['tags']).join(', ')),\n ...line('People in the CRM', facts['people']),\n ...line('In the CRM since', facts['since']),\n ...line('Notes', facts['notes']),\n ...timeline,\n ...openTasks,\n ...dealLines(facts['deals']),\n ]\n }\n if (kind === 'deal') {\n const stages = list(facts['stages'])\n const withWhom = [text(facts['contact']), text(facts['company'])].filter(Boolean).join(' at ')\n return [\n ...line('Deal', facts['title']),\n ...line('Pipeline', facts['pipeline']),\n ...line('Status', facts['status']),\n ...line('Stage', facts['stage'] ? `${text(facts['stage'])} (id ${text(facts['stageId'])})` : ''),\n ...line('Stages in order', stages.map((stage) => `${text(stage['id'])} \"${text(stage['name'])}\" (${text(stage['kind'])})`).join('; ')),\n ...line('In this stage since', facts['inStageSince']),\n ...line('Amount', facts['amount']),\n // Salesforce's Opportunity fields (AGL-3516).\n ...line('Probability', typeof facts['probability'] === 'number' ? `${facts['probability']}%` : ''),\n ...line('Forecast category', facts['forecastCategory']),\n ...line('Type', facts['type']),\n ...line('Lead source', facts['leadSource']),\n ...line('Next step', facts['nextStep']),\n ...line('Expected to close', facts['expectedClose']),\n ...line('Lost reason', facts['lostReason']),\n ...line('With', withWhom),\n ...contactRoleLines(facts['contactRoles']),\n ...line('Products on the deal', typeof facts['products'] === 'number' ? facts['products'] || '' : ''),\n ...line('In the CRM since', facts['since']),\n ...line('Notes', facts['notes']),\n ...timeline,\n ...openTasks,\n ]\n }\n return [\n ...line('Lead', facts['name']),\n ...line('Status', facts['status']),\n ...line('Captured through', words(facts['sources']).join(', ')),\n ...line('Captures', Number(facts['captures']) || ''),\n ...line('First seen', facts['firstSeen']),\n ...line('Last seen', facts['lastSeen']),\n `Assigned to a teammate: ${facts['assigned'] === true ? 'yes' : 'no'}`,\n `Converted to a contact: ${facts['converted'] === true ? 'yes' : 'no'}`,\n ...line('Unqualified reason', facts['unqualifiedReason']),\n ...line('Notes', facts['notes']),\n ...timeline,\n ]\n}\n\n/** Every contact on a deal and the part each plays (AGL-3521), by name. */\nfunction contactRoleLines(value: unknown): string[] {\n return line(\n 'Contact roles',\n list(value)\n .map((row) => {\n const part = [text(row['role']), row['primary'] === true ? 'Primary' : ''].filter(Boolean).join(', ')\n return part ? `${text(row['name'])} (${part})` : text(row['name'])\n })\n .join('; '),\n )\n}\n\n/**\n * The facts as lines of text: the WHOLE record the CRM reports (AGL-3520) —\n * every standard field, its contact details and addresses, its picklist\n * labels, its custom fields under their labels, its consent, notes,\n * timeline, open tasks and deals. A field the record does not hold writes\n * no line. The CRM's builders decide what a record is; nothing here drops a\n * field they report, and no reader reports a token, an account identifier\n * or a record id beyond a stage's.\n */\nexport function aiCrmFactsLines(kind: AiCrmRecordKind, facts: Facts): string[] {\n if (kind === 'contact') {\n const orders = Number(facts['orders']) || 0\n return [\n ...line('Contact', facts['name']),\n ...line('Salutation', facts['salutation']),\n ...line('First name', facts['firstName']),\n ...line('Last name', facts['lastName']),\n ...line('Email', words(facts['emails']).join(', ')),\n ...line('Phone', facts['phone']),\n ...line('Mobile phone', facts['mobilePhone']),\n ...line('Home phone', facts['homePhone']),\n ...line('Other phone', facts['otherPhone']),\n ...line('Fax', facts['fax']),\n // A request the person made, which a drafted next step must respect (AGL-3515).\n ...yesNo('Do not call: they asked not to be phoned', facts['doNotCall']),\n ...line('Job title', facts['jobTitle']),\n ...line('Department', facts['department']),\n ...line('Company', facts['company']),\n ...line('Reports to', facts['reportsTo']),\n ...line('Assistant', facts['assistant']),\n ...line('Assistant phone', facts['assistantPhone']),\n ...line('Birthdate', facts['birthdate']),\n ...line('Mailing address', facts['mailingAddress']),\n ...line('Other address', facts['otherAddress']),\n ...line('Lifecycle stage', facts['lifecycleStage']),\n ...line('Lead source', facts['leadSource']),\n ...line('Owner', facts['owner']),\n ...line('Marketing email consent', facts['marketingConsent']),\n ...line('Tags', words(facts['tags']).join(', ')),\n ...line('Came in through', words(facts['sources']).join(', ')),\n ...(orders ? [`Orders: ${orders}${facts['lastPurchase'] ? `, the last on ${text(facts['lastPurchase'])}` : ''}`] : []),\n ...line('In the CRM since', facts['since']),\n ...line('Last opened or clicked an email', facts['lastEmailEngagement']),\n ...customLines(facts['custom']),\n ...line('Notes', facts['notes']),\n ...timelineLines(facts['timeline']),\n ...taskLines(facts['openTasks']),\n ...dealLines(facts['deals']),\n ]\n }\n if (kind === 'company') {\n return [\n ...line('Company', facts['name']),\n ...line('Domain', facts['domain']),\n ...line('Website', facts['website']),\n ...line('Phone', facts['phone']),\n ...line('Fax', facts['fax']),\n // Salesforce's Account fields (AGL-3514).\n ...line('Type', facts['type']),\n ...line('Industry', facts['industry']),\n ...line('Rating', facts['rating']),\n ...line('Ownership', facts['ownership']),\n ...line('Account source', facts['accountSource']),\n ...line('Employees', facts['employees'] ?? ''),\n ...line('Annual revenue', facts['annualRevenue']),\n ...line('Account number', facts['accountNumber']),\n ...line('Account site', facts['site']),\n ...line('Ticker symbol', facts['tickerSymbol']),\n ...line('SIC code', facts['sicCode']),\n ...line('Billing address', facts['billingAddress']),\n ...line('Shipping address', facts['shippingAddress']),\n ...line('Parent company', facts['parentCompany']),\n ...line('Owner', facts['owner']),\n ...line('Tags', words(facts['tags']).join(', ')),\n ...line('People in the CRM', facts['people']),\n ...line('In the CRM since', facts['since']),\n ...customLines(facts['custom']),\n ...line('Notes', facts['notes']),\n ...timelineLines(facts['timeline']),\n ...taskLines(facts['openTasks']),\n ...dealLines(facts['deals']),\n ]\n }\n if (kind === 'deal') {\n const stages = list(facts['stages'])\n const products = list(facts['products'])\n const withWhom = [text(facts['contact']), text(facts['company'])].filter(Boolean).join(' at ')\n return [\n ...line('Deal', facts['title']),\n ...line('Pipeline', facts['pipeline']),\n ...line('Status', facts['status']),\n ...line('Stage', facts['stage'] ? `${text(facts['stage'])} (id ${text(facts['stageId'])})` : ''),\n ...line('Stages in order', stages.map((stage) => `${text(stage['id'])} \"${text(stage['name'])}\" (${text(stage['kind'])})`).join('; ')),\n ...line('In this stage since', facts['inStageSince']),\n ...line('Amount', facts['amount']),\n // Salesforce's Opportunity fields (AGL-3516).\n ...line('Probability', typeof facts['probability'] === 'number' ? `${facts['probability']}%` : ''),\n ...line('Forecast category', facts['forecastCategory']),\n ...line('Type', facts['type']),\n ...line('Lead source', facts['leadSource']),\n ...line('Campaign', facts['campaign']),\n ...line('Next step', facts['nextStep']),\n ...line('Expected to close', facts['expectedClose']),\n ...line('Lost reason', facts['lostReason']),\n ...line('With', withWhom),\n ...line('Owner', facts['owner']),\n ...contactRoleLines(facts['contactRoles']),\n ...(products.length\n ? [\n 'Products:',\n ...products.map(\n (product) =>\n `- ${text(product['name'])} × ${Number(product['quantity']) || 0}` +\n (product['unitAmount'] ? ` at ${text(product['unitAmount'])}` : ''),\n ),\n ]\n : []),\n ...line('In the CRM since', facts['since']),\n ...customLines(facts['custom']),\n ...line('Notes', facts['notes']),\n ...timelineLines(facts['timeline']),\n ...taskLines(facts['openTasks']),\n ]\n }\n return [\n ...line('Lead', facts['name']),\n ...line('Salutation', facts['salutation']),\n ...line('First name', facts['firstName']),\n ...line('Last name', facts['lastName']),\n ...line('Email', facts['email']),\n ...line('Phone', facts['phone']),\n ...line('Mobile phone', facts['mobilePhone']),\n ...line('Fax', facts['fax']),\n ...yesNo('Do not call: they asked not to be phoned', facts['doNotCall']),\n ...line('Company', facts['company']),\n ...line('Job title', facts['jobTitle']),\n ...line('Website', facts['website']),\n ...line('Address', facts['address']),\n ...line('Industry', facts['industry']),\n ...line('Rating', facts['rating']),\n ...line('Employees', facts['employees'] ?? ''),\n ...line('Annual revenue', facts['annualRevenue']),\n ...line('Lead source', facts['leadSource']),\n ...line('Status', facts['status']),\n ...line('Owner', facts['owner']),\n ...line('Campaigns', words(facts['campaigns']).join(', ')),\n ...line('Marketing email consent', facts['marketingConsent']),\n ...line('Tags', words(facts['tags']).join(', ')),\n ...line('Captured through', words(facts['sources']).join(', ')),\n ...line('Captures', Number(facts['captures']) || ''),\n ...line('First seen', facts['firstSeen']),\n ...line('Last seen', facts['lastSeen']),\n `Assigned to a teammate: ${facts['assigned'] === true ? 'yes' : 'no'}`,\n `Converted to a contact: ${facts['converted'] === true ? 'yes' : 'no'}`,\n ...line('Unqualified reason', facts['unqualifiedReason']),\n ...customLines(facts['custom']),\n ...line('Notes', facts['notes']),\n ...timelineLines(facts['timeline']),\n ...(list(facts['openTasks']).length ? taskLines(facts['openTasks']) : []),\n ]\n}\n\n/**\n * The most characters a record's facts take in a request (AGL-3520) —\n * about six thousand tokens at the three characters a token the routing\n * ceilings are measured with. The CRM's own cuts keep a record well under\n * it; a record that holds everything at once is fitted by\n * {@link aiCrmFitFacts}, which shortens its long texts and drops no field.\n */\nexport const AI_CRM_FACTS_MAX_CHARS = 18_000\n\n/** The cuts {@link aiCrmFitFacts} tries in turn: notes, then each timeline text, then each custom value and task note. */\nconst FIT_STEPS = [\n { notes: 300, text: 160, custom: 160 },\n { notes: 160, text: 80, custom: 80 },\n { notes: 80, text: 40, custom: 40 },\n] as const\n\nconst cut = (value: unknown, max: number): unknown =>\n typeof value === 'string' && value.length > max ? `${value.slice(0, max - 1).trimEnd()}…` : value\n\n/**\n * The facts, fitted to {@link AI_CRM_FACTS_MAX_CHARS} as lines: unchanged\n * when they fit, otherwise with the long texts — the notes, each timeline\n * entry's text, each task's notes and each custom value — cut shorter step\n * by step. Every field stays; only how much of a long text is sent changes.\n */\nexport function aiCrmFitFacts(kind: AiCrmRecordKind, facts: Facts): Facts {\n const size = (candidate: Facts) => aiCrmFactsLines(kind, candidate).join('\\n').length\n if (size(facts) <= AI_CRM_FACTS_MAX_CHARS) return facts\n let fitted: Facts = facts\n for (const step of FIT_STEPS) {\n fitted = {\n ...facts,\n notes: cut(facts['notes'], step.notes),\n timeline: list(facts['timeline']).map((entry) => ({ ...entry, text: cut(entry['text'], step.text) })),\n openTasks: list(facts['openTasks']).map((task) => ({ ...task, notes: cut(task['notes'], step.text) })),\n custom: list(facts['custom']).map((field) => ({ ...field, value: cut(field['value'], step.custom) })),\n }\n if (size(fitted) <= AI_CRM_FACTS_MAX_CHARS) return fitted\n }\n return fitted\n}\n\n/**\n * The lines a prompt carries for a record: the whole record, fitted, when\n * the reader marked it {@link AI_CRM_WHOLE_RECORD_FIELD}; otherwise the\n * disclosed lines alone.\n */\nexport function aiCrmPromptFactsLines(kind: AiCrmRecordKind, facts: Facts): string[] {\n return facts[AI_CRM_WHOLE_RECORD_FIELD] === true\n ? aiCrmFactsLines(kind, aiCrmFitFacts(kind, facts))\n : aiCrmDisclosedFactsLines(kind, facts)\n}\n\n/** The user turn for a record. */\nexport function aiCrmRecordPrompt(kind: AiCrmRecordKind, facts: Facts): string {\n return [`Record: ${kind}`, ...aiCrmPromptFactsLines(kind, facts)].join('\\n')\n}\n\n/** The user turn for an email draft. */\nexport function aiCrmEmailPrompt(input: { kind: AiCrmRecordKind; facts: Facts; request: string }): string {\n return [\n `Record: ${input.kind}`,\n `Merge fields: ${aiCrmEmailMergeFields(input.kind).map((key) => `{{${key}}}`).join(', ')}`,\n `The team member asks: ${input.request.replace(/\\s+/g, ' ').trim().slice(0, AI_CRM_EMAIL_REQUEST_MAX_CHARS)}`,\n '',\n 'Facts:',\n ...aiCrmPromptFactsLines(input.kind, input.facts),\n ].join('\\n')\n}\n\n/** The user turn for an import's columns. */\nexport function aiCrmMappingPrompt(input: {\n collection: string\n fields: readonly AiCrmImportField[]\n columns: ReadonlyArray<{ header: string; shape: string }>\n}): string {\n return [\n `Import: ${input.collection}`,\n 'Fields (number: label, type):',\n ...input.fields.map(\n (field, index) => `- ${index}: ${field.label}, ${field.type}${field.required ? ', required' : ''}`,\n ),\n 'Columns (number: header, shape):',\n ...input.columns.map((column, index) => `- ${index}: ${column.header || '(no header)'}, ${column.shape}`),\n ].join('\\n')\n}\n\n/* ------------------------------------------------------------------------ *\n * A record answer, asked once per timeline\n * ------------------------------------------------------------------------ */\n\n/**\n * A digest of the whole request a record answer is produced for. A hash of the\n * request, never of the answer, and nothing of the facts is recoverable from\n * it. The version tag strands every old key when what a key means changes.\n */\nexport function aiCrmRecordKey(input: {\n kind: AiCrmRecordKind\n id: string\n hostId: string | null\n model: string\n prompt: string\n system: readonly AiSystemBlock[]\n tool: AiTool\n}): string {\n const digest = createHash('sha256')\n for (const part of [\n 'crm-record.v1',\n input.kind,\n input.id,\n input.hostId ?? '',\n input.model,\n input.prompt,\n ...input.system.map((block) => block.text),\n JSON.stringify(input.tool),\n ]) {\n digest.update(`${part.length}:${part}\\u0000`)\n }\n return digest.digest('hex')\n}\n\n/** A kept answer about a record, as the reuse lookup reads it. */\nexport interface AiCrmAnswerCandidate {\n jobId: string\n hostId: string | null\n task: string\n key: string | null\n createdAtMs: number | null\n proposal: AiCrmProposal | null\n}\n\nexport type AiCrmAnswerFinder = (\n orgId: string,\n key: string,\n firestore?: FirebaseFirestore.Firestore,\n) => Promise<AiCrmAnswerCandidate[]>\n\nconst millis = (value: unknown): number | null => {\n if (value instanceof Date) return value.getTime()\n const toMillis = (value as { toMillis?: () => number } | null)?.toMillis\n return typeof toMillis === 'function' ? toMillis.call(value) : null\n}\n\n/**\n * The finder the step uses in production: one equality on the request key\n * inside the org's kept answers. Firestore's automatic single-field index\n * answers it, so no composite index is deployed; the site and the window are\n * applied in memory.\n */\nexport const findAiCrmAnswersByKey: AiCrmAnswerFinder = async (orgId, key, firestore) => {\n if (!firestore) return []\n const snapshot = await firestore\n .collection('orgs')\n .doc(orgId)\n .collection(AI_CRM_ANSWERS_COLLECTION)\n .where('key', '==', key)\n .limit(AI_CRM_REUSE_CANDIDATES)\n .get()\n return snapshot.docs.map((doc) => {\n const data = (doc.data() ?? {}) as Partial<AiCrmAnswerRecord>\n return {\n jobId: doc.id,\n hostId: data.hostId ?? null,\n task: String(data.task ?? ''),\n key: data.key ?? null,\n createdAtMs: millis(data.createdAt),\n proposal: readAiCrmProposal(data.proposal),\n }\n })\n}\n\n/** The newest answer another job kept for the same request, inside the window. */\nexport function aiReusableCrmRecord(\n candidates: readonly AiCrmAnswerCandidate[],\n context: { jobId: string; hostId: string | null; key: string; now: Date },\n): { jobId: string; proposal: AiCrmRecordProposal } | null {\n const floor = context.now.getTime() - AI_CRM_REUSE_WINDOW_MS\n let best: { jobId: string; proposal: AiCrmRecordProposal; at: number } | null = null\n for (const candidate of candidates) {\n if (candidate.jobId === context.jobId || candidate.task !== 'record') continue\n if (candidate.hostId !== context.hostId || candidate.key !== context.key) continue\n if (candidate.proposal?.kind !== 'record') continue\n const at = candidate.createdAtMs ?? 0\n if (at < floor) continue\n if (!best || at > best.at) best = { jobId: candidate.jobId, proposal: candidate.proposal, at }\n }\n return best ? { jobId: best.jobId, proposal: best.proposal } : null\n}\n\n/* ------------------------------------------------------------------------ *\n * Admission\n * ------------------------------------------------------------------------ */\n\nexport interface AiCrmAdmissionDeps {\n readerFor?: AiCrmReaderLookup\n now?: () => Date\n}\n\n/**\n * Whether a `crm` job may start here for this member; `null` admits it: its\n * inputs name a record or an import, and the CRM would let the member read it\n * ({@link aiCrmAccessRefusal}). Asked at the create door before the job\n * exists, so a refusal spends nothing. A door that names no member asks\n * nothing of the reader.\n */\nexport async function aiCrmAdmissionRefusal(\n context: Parameters<AiJobAdmission>[0],\n deps: AiCrmAdmissionDeps = {},\n): Promise<AiJobAdmissionRefusal | null> {\n const request = parseAiCrmRequest(context.inputs)\n if (typeof request === 'string') return { status: 400, error: request }\n return aiCrmAccessRefusal({\n firestore: context.firestore,\n orgId: context.orgId,\n hostId: context.hostId ?? null,\n ...aiCrmFactsResource(request),\n org: context.org,\n uid: context.uid,\n ...(deps.readerFor ? { readerFor: deps.readerFor } : {}),\n now: deps.now?.() ?? new Date(),\n })\n}\n\nexport const aiCrmJobAdmission: AiJobAdmission = (context) => aiCrmAdmissionRefusal(context)\n\n/* ------------------------------------------------------------------------ *\n * The runner\n * ------------------------------------------------------------------------ */\n\nexport interface AiJobCrmStepDeps {\n readerFor?: AiCrmReaderLookup\n /** The reuse lookup; specs hand in a fake, and `null` turns reuse off. */\n findAnswersByKey?: AiCrmAnswerFinder | null\n}\n\nfunction modelOf(context: AiJobStepContext): string {\n return context.modelFor?.('job.crm') ?? aiModelForStep('job.crm')\n}\n\n/** An answer as the step hands it to be kept; the job's ids and the clock are added. */\ntype AiCrmAnswerToKeep = Pick<AiCrmAnswerRecord, 'task' | 'resource' | 'recordId' | 'proposal' | 'key'>\n\n/** What a step spent, whatever outputs it goes on to name. */\ntype AiCrmSpent = Omit<AiJobStepOutcome, 'outputs'>\n\n/**\n * Keeps an answer at `orgs/{orgId}/aiCrmAnswers/{jobId}`, which no client may\n * read, on its own clock.\n */\nasync function keepAiCrmAnswer(context: AiJobStepContext, answer: AiCrmAnswerToKeep): Promise<void> {\n const { job, now, firestore } = context\n const record = JSON.parse(\n JSON.stringify({ jobId: job.$id, orgId: job.orgId, hostId: job.hostId ?? null, createdBy: job.createdBy, ...answer }),\n ) as AiCrmAnswerRecord\n await firestore\n .collection('orgs')\n .doc(job.orgId)\n .collection(AI_CRM_ANSWERS_COLLECTION)\n .doc(job.$id)\n .set({ ...record, createdAt: now, expiresAt: aiCrmAnswerExpiry(now) })\n}\n\nexport function createAiJobCrmStep(deps: AiJobCrmStepDeps = {}): AiJobStepRunner {\n const readerFor = deps.readerFor ?? pluginRecordFactsReader\n const findAnswersByKey = deps.findAnswersByKey === undefined ? findAiCrmAnswersByKey : deps.findAnswersByKey\n return async (context): Promise<AiJobStepOutcome> => {\n const { job, now, signal, firestore } = context\n const model = modelOf(context)\n const request = parseAiCrmRequest(job.inputs)\n if (typeof request === 'string') return { ...aiUnspentOutcome(model), failure: request }\n const { resource, id } = aiCrmFactsResource(request)\n const found = readerFor(resource)\n if (!found || found.pluginId !== AI_CRM_PLUGIN_ID) {\n return { ...aiUnspentOutcome(model), failure: AI_CRM_UNAVAILABLE_COPY }\n }\n let facts: Facts\n try {\n const read = await found.reader.read({\n orgId: job.orgId,\n hostId: job.hostId ?? null,\n id,\n // The job's creator: the CRM reads the record as the member who asked.\n uid: job.createdBy,\n org: (context.org as Record<string, unknown> | null | undefined) ?? null,\n now,\n })\n if (read.ok === false) return { ...aiUnspentOutcome(model), failure: read.error }\n facts = read.facts\n } catch (error) {\n console.error('ai crm facts read failed', { orgId: job.orgId, jobId: job.$id, resource, error })\n return { ...aiUnspentOutcome(model), failure: AI_CRM_FACTS_FAILURE_COPY }\n }\n const maxTokens = AI_JOB_CRM_STEP_BUDGET.maxTokens(model)\n const hostId = job.hostId ?? null\n\n // Keeps the answer, then names it on the job. A keep that fails fails the\n // step as spent: the tokens were used, and there is nothing to show.\n const keep = async (\n spent: AiCrmSpent,\n answer: AiCrmAnswerToKeep,\n output: { id: string; label: string; ref: AiCrmOutputRef },\n ): Promise<AiJobStepOutcome> => {\n try {\n await keepAiCrmAnswer(context, answer)\n } catch (error) {\n console.error('ai crm answer keep failed', { orgId: job.orgId, jobId: job.$id, error })\n return { ...spent, outputs: [], failure: AI_CRM_KEEP_FAILURE_COPY }\n }\n return {\n ...spent,\n outputs: [{ resource: 'crm', id: output.id, hostId, label: output.label, proposal: { ...output.ref } }],\n }\n }\n\n if (request.task === 'record') {\n const { kind } = request.record\n if (facts['record'] !== kind) return { ...aiUnspentOutcome(model), failure: AI_CRM_FACTS_FAILURE_COPY }\n const instructions = aiCrmRecordInstructions(kind)\n const tool = aiCrmRecordTool(kind)\n const prompt = aiCrmRecordPrompt(kind, facts)\n const key = aiCrmRecordKey({\n kind,\n id,\n hostId,\n model,\n prompt,\n system: aiDoctrineSystemBlocks(undefined, {\n instructions,\n scope: aiDoctrineScopeFor(AI_CRM_GENERATION_KINDS.record),\n }),\n tool,\n })\n const output = { id: AI_CRM_OUTPUT_IDS.record(kind, id), label: OUTPUT_LABELS.record, ref: { kind: 'record', record: { kind, id } } } as const\n // Reuse before asking: an answer kept for the same key is the same\n // request, so the member reads it again at no cost.\n const reused = findAnswersByKey\n ? aiReusableCrmRecord(await findAnswersByKey(job.orgId, key, firestore).catch(() => []), {\n jobId: job.$id,\n hostId,\n key,\n now,\n })\n : null\n if (reused) {\n return keep(\n aiUnspentOutcome(model),\n { task: 'record', resource, recordId: id, key, proposal: { ...reused.proposal, reusedFrom: reused.jobId } },\n output,\n )\n }\n const generation = await runValidatedGeneration(AI_CRM_GENERATION_KINDS.record, {\n step: 'job.crm',\n ...aiRouteSettings('job.crm'),\n model,\n instructions,\n messages: [{ role: 'user', content: prompt }],\n tool,\n maxTokens,\n ...(signal ? { signal } : {}),\n check: (answer) => checkAiCrmRecord(answer, aiCrmRecordCheckContext(kind, facts)),\n })\n const spent = aiGenerationSpent(generation)\n if (generation.status === 'refused') return { ...spent, refused: true }\n if (generation.status === 'needs_input') return { ...spent, failure: generation.message }\n const proposal: AiCrmRecordProposal = {\n kind: 'record',\n record: { kind, id },\n summary: generation.value.summary,\n nextStep: generation.value.nextStep,\n stage: generation.value.stage,\n standing: generation.value.standing,\n asOf: now.toISOString().slice(0, 10),\n }\n return keep(spent, { task: 'record', resource, recordId: id, key, proposal }, output)\n }\n\n if (request.task === 'email') {\n const { kind } = request.record\n if (facts['record'] !== kind) return { ...aiUnspentOutcome(model), failure: AI_CRM_FACTS_FAILURE_COPY }\n const mergeFields = aiCrmEmailMergeFields(kind)\n const generation = await runValidatedGeneration(AI_CRM_GENERATION_KINDS.email, {\n step: 'job.crm',\n ...aiRouteSettings('job.crm'),\n model,\n instructions: AI_CRM_EMAIL_INSTRUCTIONS,\n messages: [{ role: 'user', content: aiCrmEmailPrompt({ kind, facts, request: job.brief }) }],\n tool: AI_CRM_EMAIL_TOOL,\n maxTokens,\n ...(signal ? { signal } : {}),\n check: (answer) => checkAiCrmEmail(answer, { mergeFields }),\n })\n const spent = aiGenerationSpent(generation)\n if (generation.status === 'refused') return { ...spent, refused: true }\n if (generation.status === 'needs_input') return { ...spent, failure: generation.message }\n const proposal: AiCrmEmailProposal = {\n kind: 'email',\n record: { kind, id },\n subject: generation.value.subject,\n body: generation.value.body,\n }\n return keep(\n spent,\n { task: 'email', resource, recordId: id, key: null, proposal },\n { id: AI_CRM_OUTPUT_IDS.email(kind, id), label: OUTPUT_LABELS.email, ref: { kind: 'email', record: { kind, id } } },\n )\n }\n\n const fields = aiCrmImportFields(facts)\n if (facts['record'] !== 'import' || !fields.length) {\n return { ...aiUnspentOutcome(model), failure: AI_CRM_FACTS_FAILURE_COPY }\n }\n const generation = await runValidatedGeneration(AI_CRM_GENERATION_KINDS.mapping, {\n step: 'job.crm',\n ...aiRouteSettings('job.crm'),\n model,\n instructions: AI_CRM_MAPPING_INSTRUCTIONS,\n messages: [\n {\n role: 'user',\n content: aiCrmMappingPrompt({ collection: request.collection, fields, columns: request.columns }),\n },\n ],\n tool: AI_CRM_MAPPING_TOOL,\n maxTokens,\n ...(signal ? { signal } : {}),\n check: (answer) => checkAiCrmMapping(answer, { columns: request.columns, fields }),\n })\n const spent = aiGenerationSpent(generation)\n if (generation.status === 'refused') return { ...spent, refused: true }\n if (generation.status === 'needs_input') return { ...spent, failure: generation.message }\n const proposal: AiCrmMappingProposal = {\n kind: 'mapping',\n collection: request.collection,\n matches: generation.value,\n columns: request.columns.length,\n }\n return keep(\n spent,\n { task: 'mapping', resource, recordId: request.collection, key: null, proposal },\n {\n id: AI_CRM_OUTPUT_IDS.mapping(request.collection),\n label: OUTPUT_LABELS.mapping,\n ref: { kind: 'mapping', collection: request.collection },\n },\n )\n }\n}\n\nexport const runAiJobCrmStep = createAiJobCrmStep()\n\n/** Registers the CRM step and the check a `crm` job passes before it is created. */\nexport function registerAiCrmJob(): void {\n registerAiJobStep('crm', runAiJobCrmStep, { minimumMs: AI_JOB_CRM_STEP_MINIMUM_MS })\n registerAiJobAdmission('crm', aiCrmJobAdmission)\n}\n"],"names":["createHash","pluginRecordFactsReader","AI_CRM_ANSWER_RETENTION_DAYS","AI_CRM_ANSWERS_COLLECTION","AI_CRM_EMAIL_REQUEST_MAX_CHARS","AI_CRM_OUTPUT_IDS","AI_CRM_PLUGIN_ID","aiCrmAnswerExpiry","parseAiCrmRequest","readAiCrmProposal","AI_STEP_TIERS","AI_ROUTING_TABLE","aiModelForStep","aiRouteSettings","aiDoctrineScopeFor","aiDoctrineSystemBlocks","runValidatedGeneration","AI_CRM_EMAIL_TOOL","AI_CRM_EMAIL_TOOL_NAME","AI_CRM_MAPPING_TOOL","AI_CRM_MAPPING_TOOL_NAME","AI_CRM_RECORD_TOOL_NAME","aiCrmEmailMergeFields","aiCrmImportFields","aiCrmRecordCheckContext","aiCrmRecordTool","checkAiCrmEmail","checkAiCrmMapping","checkAiCrmRecord","AI_CRM_UNAVAILABLE_COPY","aiCrmAccessRefusal","aiCrmFactsResource","registerAiJobAdmission","aiJobStepBudget","aiGenerationSpent","aiUnspentOutcome","registerAiJobStep","AI_CRM_FACTS_READS_MS","AI_JOB_CRM_STEP_BUDGET","tier","maxTokens","lookups","ownReadsMs","AI_JOB_CRM_STEP_MINIMUM_MS","minimumMs","AI_CRM_REUSE_CANDIDATES","AI_CRM_REUSE_WINDOW_MS","AI_CRM_FACTS_FAILURE_COPY","AI_CRM_KEEP_FAILURE_COPY","AI_CRM_GENERATION_KINDS","record","email","mapping","OUTPUT_LABELS","WRITE_ONLY_FROM_FACTS","NEXT_STEP_RULE","LANGUAGE_RULE","rulesBlock","opening","toolName","rules","text","map","rule","join","cacheBreakpoint","aiCrmRecordInstructions","kind","AI_CRM_EMAIL_INSTRUCTIONS","AI_CRM_MAPPING_INSTRUCTIONS","value","list","Array","isArray","filter","entry","Boolean","words","line","label","shown","String","timelineLines","entries","length","detail","subject","taskLines","tasks","task","dealLines","deals","deal","customLines","fields","field","yesNo","AI_CRM_WHOLE_RECORD_FIELD","only","keys","Object","fromEntries","key","DISCLOSED_TIMELINE_KEYS","DISCLOSED_TASK_KEYS","aiCrmDisclosedFactsLines","facts","timeline","openTasks","orders","Number","stages","withWhom","stage","contactRoleLines","row","part","aiCrmFactsLines","products","product","AI_CRM_FACTS_MAX_CHARS","FIT_STEPS","notes","custom","cut","max","slice","trimEnd","aiCrmFitFacts","size","candidate","fitted","step","aiCrmPromptFactsLines","aiCrmRecordPrompt","aiCrmEmailPrompt","input","request","replace","trim","aiCrmMappingPrompt","collection","index","type","required","columns","column","header","shape","aiCrmRecordKey","digest","id","hostId","model","prompt","system","block","JSON","stringify","tool","update","millis","Date","getTime","toMillis","call","findAiCrmAnswersByKey","orgId","firestore","snapshot","doc","where","limit","get","docs","data","jobId","createdAtMs","createdAt","proposal","aiReusableCrmRecord","candidates","context","floor","now","best","at","aiCrmAdmissionRefusal","deps","inputs","status","error","org","uid","readerFor","aiCrmJobAdmission","modelOf","modelFor","keepAiCrmAnswer","answer","job","parse","$id","createdBy","set","expiresAt","createAiJobCrmStep","findAnswersByKey","undefined","signal","failure","resource","found","pluginId","read","reader","ok","console","keep","spent","output","outputs","ref","instructions","scope","reused","catch","recordId","reusedFrom","generation","messages","role","content","check","refused","message","summary","nextStep","standing","asOf","toISOString","mergeFields","brief","body","matches","runAiJobCrmStep","registerAiCrmJob"],"mappings":"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"}
@@ -176,6 +176,30 @@ export declare function aiDraftAllowanceRefusal(firestore: Firestore, input: {
176
176
  hostId: string;
177
177
  org: Partial<AglynOrgBilling> | null;
178
178
  }): Promise<string | null>;
179
+ /**
180
+ * A stored instant in milliseconds, whatever wrote it: a Firestore
181
+ * `Timestamp`, a `Date`, a number or an ISO string. `null` when unreadable.
182
+ */
183
+ export declare function aiDraftInstantMs(value: unknown): number | null;
184
+ /**
185
+ * Whether the band refuses the kind only because of drafts made after
186
+ * `sinceMs` (AGL-3765): the rows refuse now, and the rows made at or before
187
+ * then would not. A job whose plan was confirmed while the site had room, and
188
+ * that another build or a member filled before the job's own write, stopped
189
+ * on something it could not have known when it was confirmed. A row whose
190
+ * `createdAt` cannot be read counts as made before, so a refusal this cannot
191
+ * date stays the workspace's own limit.
192
+ */
193
+ export declare function aiDraftBandTakenSince(kind: AiDraftKind, rows: ReadonlyArray<Pick<SiblingRow, 'id' | 'kind' | 'sourceType' | 'deletedAt'> & {
194
+ createdAt?: unknown;
195
+ }>, org: Partial<AglynOrgBilling> | null, sinceMs: number | null, routingMap?: RoutingMap): boolean;
196
+ /** {@link aiDraftBandTakenSince}, over the site's rows of the kind as they are now. */
197
+ export declare function readAiDraftBandTakenSince(firestore: Firestore, input: {
198
+ kind: AiDraftKind;
199
+ hostId: string;
200
+ org: Partial<AglynOrgBilling> | null;
201
+ since: unknown;
202
+ }): Promise<boolean>;
179
203
  /**
180
204
  * A draft screen's one-segment address: the one asked for, else one from its
181
205
  * name, never a reserved segment, and never an address a live screen keeps