@pitcher/js-api 1.29.0 → 1.30.0

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 (73) hide show
  1. package/js-api.esm.js +408 -39
  2. package/js-api.esm.js.map +1 -1
  3. package/js-api.umd.min.js +32 -11
  4. package/js-api.umd.min.js.map +1 -1
  5. package/lib/ai-tasks/__tests__/weekly-focus.spec.d.ts +1 -0
  6. package/lib/ai-tasks/weekly-focus.d.ts +241 -0
  7. package/lib/apps/browser/appTypes.d.ts +28 -0
  8. package/lib/apps/browser/stores/api.d.ts +6 -1
  9. package/lib/apps/browser/stores/app.d.ts +5 -0
  10. package/lib/apps/browser/stores/app.selectionPruneScope.test.d.ts +1 -0
  11. package/lib/apps/browser/stores/upload.d.ts +26 -1
  12. package/lib/apps/browser/stores/upload.types.d.ts +9 -0
  13. package/lib/apps/browser/util/lifecycle.d.ts +57 -0
  14. package/lib/apps/browser/util/lifecycle.test.d.ts +1 -0
  15. package/lib/apps/canvas-builder/components/ui/DynamicContent/useDynamicContent.d.ts +2 -0
  16. package/lib/apps/canvas-builder/components/ui/Multimedia/Multimedia.ldAsset.spec.d.ts +1 -0
  17. package/lib/apps/canvas-builder/composables/useCanvas.d.ts +203 -1
  18. package/lib/apps/canvas-builder/composables/useCanvas.saveEcho.spec.d.ts +1 -0
  19. package/lib/apps/canvas-builder/composables/useCanvasBlocks.d.ts +36 -0
  20. package/lib/apps/canvas-builder/composables/useCanvasExit.d.ts +1 -0
  21. package/lib/apps/canvas-builder/composables/useCanvasHistory.d.ts +36 -0
  22. package/lib/apps/canvas-builder/composables/useCanvasTheme.d.ts +2 -0
  23. package/lib/apps/canvas-builder/composables/useContentSelector.d.ts +8 -1
  24. package/lib/apps/canvas-builder/composables/useInstalledEmbeddables.d.ts +1 -1
  25. package/lib/apps/canvas-builder/composables/useNavigatorDrawer.d.ts +2 -0
  26. package/lib/apps/canvas-builder/composables/usePopupApps.d.ts +2 -0
  27. package/lib/apps/canvas-builder/types/canvas.d.ts +53 -3
  28. package/lib/apps/canvas-builder/util/completionWizard.util.d.ts +107 -0
  29. package/lib/apps/canvas-builder/util/completionWizard.util.test.d.ts +1 -0
  30. package/lib/apps/canvas-builder/util/fullscreenMode.util.d.ts +7 -0
  31. package/lib/apps/canvas-builder/util/fullscreenMode.util.test.d.ts +1 -0
  32. package/lib/apps/canvas-builder/util/lazySections.util.d.ts +35 -0
  33. package/lib/apps/canvas-builder/util/lazySections.util.test.d.ts +1 -0
  34. package/lib/apps/collection-player/composables/collection-navigation.util.d.ts +27 -0
  35. package/lib/apps/collection-player/composables/collection-navigation.util.spec.d.ts +1 -0
  36. package/lib/apps/collection-player/stores/app.store.d.ts +3 -0
  37. package/lib/apps/content-selector/components/Assets.uploaderRefresh.spec.d.ts +1 -0
  38. package/lib/apps/content-selector/stores/app.d.ts +1 -1
  39. package/lib/components/CFileViewer/pdf/CFileViewer.pdf.annotations.use.d.ts +5 -1
  40. package/lib/components/CFileViewer/pdf/CFileViewer.pdf.formFields.use.d.ts +8 -0
  41. package/lib/components/CFileViewer/pdf/CFileViewer.pdf.link.use.d.ts +1 -1
  42. package/lib/components/CFileViewer/pdf/CFileViewer.pdf.overlayItems.use.d.ts +1 -1
  43. package/lib/components/CFileViewer/pdf/CFileViewer.pdf.use.d.ts +4 -9
  44. package/lib/components/CFileViewer/pdf/CFileViewer.pdf.util.d.ts +1 -1
  45. package/lib/components/CFileViewer/pdf/CFileViewerFullscreen.pdf.use.d.ts +1 -1
  46. package/lib/components/CFileViewer/pdf/__tests__/CFileViewer.pdf.annotations.text.test.d.ts +1 -0
  47. package/lib/components/savedCanvases/CSavedCanvasesManagement.use.d.ts +1 -0
  48. package/lib/composables/useCourseOnlyApps.d.ts +1 -1
  49. package/lib/composables/usePdfInterpolation.d.ts +2 -2
  50. package/lib/composables/useWindowEvents.d.ts +1 -1
  51. package/lib/constants/cdp.const.d.ts +1 -0
  52. package/lib/constants/translationLanguages.const.d.ts +72 -0
  53. package/lib/main.lib.d.ts +2 -0
  54. package/lib/sdk/api/HighLevelApi.d.ts +17 -6
  55. package/lib/sdk/api/modules/ai.d.ts +43 -1
  56. package/lib/sdk/api/modules/ai.spec.d.ts +1 -0
  57. package/lib/sdk/api/modules/canvas.d.ts +8 -0
  58. package/lib/sdk/api/modules/ui/types.ui.d.ts +7 -0
  59. package/lib/sdk/main.d.ts +8 -0
  60. package/lib/sdk/payload.types.d.ts +104 -0
  61. package/lib/types/ai-tasks.const.d.ts +39 -1
  62. package/lib/types/call.d.ts +14 -0
  63. package/lib/types/launchDarkly.types.d.ts +1 -3
  64. package/lib/types/organizationSettings.types.d.ts +10 -0
  65. package/lib/util/user.d.ts +18 -0
  66. package/lib/util/user.test.d.ts +1 -0
  67. package/package.json +1 -1
  68. package/types/openapi/models/DeletedEnvironment.d.ts +0 -1
  69. package/types/openapi/models/Environment.d.ts +0 -1
  70. package/types/openapi/models/EnvironmentRequest.d.ts +0 -1
  71. package/types/openapi/models/File.d.ts +2 -0
  72. package/types/openapi/models/FileRetrieve.d.ts +2 -0
  73. package/types/openapi/models/PatchedEnvironmentRequest.d.ts +0 -1
package/js-api.esm.js CHANGED
@@ -2385,6 +2385,261 @@ function resolvePiaSearchMode(config) {
2385
2385
  return "off";
2386
2386
  }
2387
2387
 
2388
+ const WINDOW_DAYS = 6;
2389
+ const MAX_RANK_MEETINGS = 12;
2390
+ const HERO_MAX_CHARS = 400;
2391
+ const REASON_MAX_CHARS = 140;
2392
+ const DEFAULT_MAX_PICKS = 5;
2393
+ const TITLE_MAX_CHARS = 70;
2394
+ const ACCOUNT_NAME_MAX_CHARS = 60;
2395
+ const SECTION_LABEL_MAX_CHARS = 40;
2396
+ const FIELD_LABEL_MAX_CHARS = 30;
2397
+ const FIELD_VALUE_MAX_CHARS = 60;
2398
+ const WEEKLY_FOCUS_RANKING_RULES = `You rank a sales rep's upcoming meetings by how much they need preparation.
2399
+
2400
+ Below are {{meeting_count}} numbered meetings, each with its account's CRM context (deals, tasks, recent calls).
2401
+
2402
+ Pick the {{max_picks}} most important. Pick fewer only if the list has fewer meetings.
2403
+
2404
+ Rules:
2405
+ - Highest priority first; ties go to the earlier meeting.
2406
+ - Strongest signals: an overdue or due-soon high-priority task, a poorly rated recent interaction, a deal closing within days. Routine meetings on healthy accounts rank last.
2407
+ - {{account_rule}}
2408
+ - Each "reason": ONE sentence, max {{reason_max_chars}} characters, only facts from the data — name the deal, task, rating, or date that makes it matter.
2409
+ - "hero": 1-2 sentences, max {{hero_max_chars}} characters, summarizing the week in plain business language.
2410
+ - Never invent facts. Never repeat field names, codes, or IDs.`;
2411
+ const ONE_PER_ACCOUNT_RULE = "Pick at most ONE meeting per account: when an account has several meetings, pick only its most important one.";
2412
+ const MULTI_PER_ACCOUNT_RULE = "Prefer spreading picks across different accounts, but you may pick two meetings on the same account when each is genuinely high priority on its own.";
2413
+ function effectiveMaxPicks(maxPicks) {
2414
+ if (typeof maxPicks !== "number" || !Number.isFinite(maxPicks)) return DEFAULT_MAX_PICKS;
2415
+ return Math.min(8, Math.max(1, Math.floor(maxPicks)));
2416
+ }
2417
+ function clamp(value, max) {
2418
+ const trimmed = value.trim();
2419
+ return trimmed.length > max ? trimmed.slice(0, max - 1).trimEnd() + "…" : trimmed;
2420
+ }
2421
+ function fill(template2, tokens) {
2422
+ return template2.replace(
2423
+ /\{\{(\w+)\}\}/g,
2424
+ (match, key) => Object.prototype.hasOwnProperty.call(tokens, key) ? tokens[key] : match
2425
+ );
2426
+ }
2427
+ const DATE_ONLY = /^(\d{4})-(\d{2})-(\d{2})$/;
2428
+ function startOfDayUtcMs(date, timeZone) {
2429
+ if (!timeZone) return Date.UTC(date.getFullYear(), date.getMonth(), date.getDate());
2430
+ const parts = new Intl.DateTimeFormat("en-US", {
2431
+ timeZone,
2432
+ year: "numeric",
2433
+ month: "2-digit",
2434
+ day: "2-digit"
2435
+ }).formatToParts(date);
2436
+ const get = (type) => Number(parts.find((p) => p.type === type)?.value);
2437
+ return Date.UTC(get("year"), get("month") - 1, get("day"));
2438
+ }
2439
+ function daysAgo(iso, options) {
2440
+ const now = options?.now ?? /* @__PURE__ */ new Date();
2441
+ const todayMs = startOfDayUtcMs(now, options?.timeZone);
2442
+ const dateOnly = DATE_ONLY.exec(iso);
2443
+ if (dateOnly) {
2444
+ const [y, m, d] = [+dateOnly[1], +dateOnly[2] - 1, +dateOnly[3]];
2445
+ const thenMs = Date.UTC(y, m, d);
2446
+ const back = new Date(thenMs);
2447
+ if (back.getUTCFullYear() !== y || back.getUTCMonth() !== m || back.getUTCDate() !== d) return null;
2448
+ return Math.round((todayMs - thenMs) / 864e5);
2449
+ }
2450
+ const then = new Date(iso);
2451
+ if (Number.isNaN(then.getTime())) return null;
2452
+ return Math.round((todayMs - startOfDayUtcMs(then, options?.timeZone)) / 864e5);
2453
+ }
2454
+ function weeklyWindowMeetings(meetings, options) {
2455
+ const now = options?.now ?? /* @__PURE__ */ new Date();
2456
+ const nowMs = now.getTime();
2457
+ return meetings.filter((meeting) => {
2458
+ const days = daysAgo(meeting.meeting_time, { now, timeZone: options?.timeZone });
2459
+ if (days === null) return false;
2460
+ if (!DATE_ONLY.test(meeting.meeting_time)) {
2461
+ const t = new Date(meeting.meeting_time).getTime();
2462
+ if (Number.isNaN(t) || t <= nowMs) return false;
2463
+ }
2464
+ const offset = -days;
2465
+ return offset >= 0 && offset <= WINDOW_DAYS;
2466
+ }).sort((a, b) => sortableTime(a.meeting_time) - sortableTime(b.meeting_time));
2467
+ }
2468
+ function sortableTime(iso) {
2469
+ const t = Date.parse(iso);
2470
+ return Number.isNaN(t) ? Number.POSITIVE_INFINITY : t;
2471
+ }
2472
+ function relativeDays(days) {
2473
+ if (days === 0) return "today";
2474
+ if (days === 1) return "yesterday";
2475
+ if (days < 0) return days === -1 ? "tomorrow" : `in ${-days} days`;
2476
+ return `${days} days ago`;
2477
+ }
2478
+ function formatWhen(iso, timeZone) {
2479
+ if (DATE_ONLY.test(iso)) return iso;
2480
+ const date = new Date(iso);
2481
+ if (Number.isNaN(date.getTime())) return iso;
2482
+ const day = date.toLocaleDateString("en-US", {
2483
+ weekday: "short",
2484
+ month: "short",
2485
+ day: "numeric",
2486
+ ...timeZone && { timeZone }
2487
+ });
2488
+ const time = date.toLocaleTimeString("en-US", {
2489
+ hour: "numeric",
2490
+ minute: "2-digit",
2491
+ ...timeZone && { timeZone }
2492
+ });
2493
+ return `${day} ${time}`.trim();
2494
+ }
2495
+ function meetingBlock(index, meeting, context, options) {
2496
+ const when = formatWhen(meeting.meeting_time, options.timeZone);
2497
+ const offset = -(daysAgo(meeting.meeting_time, options) ?? 0);
2498
+ const day = offset === 0 ? "today" : offset === 1 ? "tomorrow" : `in ${offset} days`;
2499
+ const attendees = meeting.attendee_count ?? 0;
2500
+ const head = `${index}. "${clamp(meeting.meeting_subject, TITLE_MAX_CHARS)}" — ${clamp(meeting.account_name, ACCOUNT_NAME_MAX_CHARS)} — ${when} (${day})` + (attendees > 0 ? ` — ${attendees} attendee${attendees === 1 ? "" : "s"}` : "");
2501
+ const lines = [head];
2502
+ for (const section of context?.sections ?? []) {
2503
+ if (section.items.length === 0) continue;
2504
+ const first = section.items[0];
2505
+ const fieldList = first.fields ?? [];
2506
+ const fields = section.fields_curated && fieldList.length > 0 ? " · " + fieldList.slice(0, 3).map((f) => `${clamp(f.label, FIELD_LABEL_MAX_CHARS)}: ${clamp(f.value, FIELD_VALUE_MAX_CHARS)}`).join(" · ") : "";
2507
+ const age = first.days_ago != null ? ` (${relativeDays(first.days_ago)})` : "";
2508
+ const count = section.items.length > 1 ? `${section.items.length} records; key: ` : "";
2509
+ lines.push(
2510
+ ` ${clamp(section.label, SECTION_LABEL_MAX_CHARS)}: ${count}${clamp(first.title, TITLE_MAX_CHARS)}${fields}${age}`
2511
+ );
2512
+ }
2513
+ return lines.join("\n");
2514
+ }
2515
+ function template(variables) {
2516
+ const meetings = variables.meetings;
2517
+ const context = variables.context ?? {};
2518
+ const maxPicks = effectiveMaxPicks(variables.max_picks);
2519
+ const onePerAccount = variables.one_per_account !== false;
2520
+ const options = { now: variables.now, timeZone: variables.time_zone };
2521
+ const blocks = meetings.map((m, i) => meetingBlock(i + 1, m, context[m.account_id], options)).join("\n");
2522
+ const rules = fill(variables.ranking_rules || WEEKLY_FOCUS_RANKING_RULES, {
2523
+ meeting_count: String(meetings.length),
2524
+ max_picks: String(maxPicks),
2525
+ reason_max_chars: String(REASON_MAX_CHARS),
2526
+ hero_max_chars: String(HERO_MAX_CHARS),
2527
+ account_rule: onePerAccount ? ONE_PER_ACCOUNT_RULE : MULTI_PER_ACCOUNT_RULE
2528
+ });
2529
+ return `${rules}
2530
+
2531
+ Answer with JSON ONLY — no other text. The object has exactly two keys:
2532
+ "hero": the summary as one string.
2533
+ "top": an array with one entry per picked meeting, highest priority first. Each entry is an object with "m" (the picked meeting's number from the list below, as an integer) and "reason" (one sentence as a string).
2534
+
2535
+ MEETINGS:
2536
+ ${blocks}`;
2537
+ }
2538
+ function tryParse(slice) {
2539
+ try {
2540
+ return JSON.parse(slice);
2541
+ } catch {
2542
+ const repaired = slice.replace(/[“”]/g, '"').replace(/[‘’]/g, "'").replace(/,\s*([}\]])/g, "$1");
2543
+ try {
2544
+ return JSON.parse(repaired);
2545
+ } catch {
2546
+ return null;
2547
+ }
2548
+ }
2549
+ }
2550
+ function looksLikeResponse(o) {
2551
+ return Array.isArray(o.top);
2552
+ }
2553
+ function extractJson(raw) {
2554
+ let fallback = null;
2555
+ for (let start = raw.indexOf("{"); start !== -1; start = raw.indexOf("{", start + 1)) {
2556
+ let depth = 0;
2557
+ let inString = false;
2558
+ let escaped = false;
2559
+ for (let i = start; i < raw.length; i++) {
2560
+ const ch = raw[i];
2561
+ if (inString) {
2562
+ if (escaped) escaped = false;
2563
+ else if (ch === "\\") escaped = true;
2564
+ else if (ch === '"') inString = false;
2565
+ } else if (ch === '"') {
2566
+ inString = true;
2567
+ } else if (ch === "{") {
2568
+ depth += 1;
2569
+ } else if (ch === "}") {
2570
+ depth -= 1;
2571
+ if (depth === 0) {
2572
+ const parsed = tryParse(raw.slice(start, i + 1));
2573
+ if (parsed) {
2574
+ if (looksLikeResponse(parsed)) return parsed;
2575
+ if (!fallback) fallback = parsed;
2576
+ }
2577
+ break;
2578
+ }
2579
+ }
2580
+ }
2581
+ }
2582
+ return fallback;
2583
+ }
2584
+ function normalizeWeeklyFocus(parsed) {
2585
+ const raw = parsed ?? {};
2586
+ const top = [];
2587
+ if (Array.isArray(raw.top)) {
2588
+ for (const pick of raw.top) {
2589
+ const m = typeof pick?.m === "number" ? pick.m : Number(pick?.m);
2590
+ const reason = typeof pick?.reason === "string" ? pick.reason.trim() : "";
2591
+ if (!Number.isInteger(m) || !reason) continue;
2592
+ top.push({ m, reason: clamp(reason, REASON_MAX_CHARS) });
2593
+ }
2594
+ }
2595
+ const hero = typeof raw.hero === "string" ? clamp(raw.hero, HERO_MAX_CHARS) : "";
2596
+ return { hero, top };
2597
+ }
2598
+ function extractWeeklyFocus(rawText) {
2599
+ const parsed = extractJson(rawText ?? "");
2600
+ if (!parsed) return { hero: "", top: [] };
2601
+ return normalizeWeeklyFocus(parsed);
2602
+ }
2603
+ function resolveWeeklyFocusPicks(output, rankedMeetings, options = {}) {
2604
+ const maxPicks = effectiveMaxPicks(options.maxPicks);
2605
+ const onePerAccount = options.onePerAccount !== false;
2606
+ const windowCount = options.windowCount ?? rankedMeetings.length;
2607
+ const seen = /* @__PURE__ */ new Set();
2608
+ const pickedAccounts = /* @__PURE__ */ new Set();
2609
+ const top = [];
2610
+ let dropped = 0;
2611
+ for (const pick of output.top ?? []) {
2612
+ const meeting = Number.isInteger(pick.m) ? rankedMeetings[pick.m - 1] : void 0;
2613
+ const reason = typeof pick.reason === "string" ? pick.reason.trim() : "";
2614
+ if (!meeting || !reason || seen.has(meeting.event_id)) {
2615
+ dropped += 1;
2616
+ continue;
2617
+ }
2618
+ if (onePerAccount && pickedAccounts.has(meeting.account_id)) {
2619
+ dropped += 1;
2620
+ continue;
2621
+ }
2622
+ seen.add(meeting.event_id);
2623
+ pickedAccounts.add(meeting.account_id);
2624
+ top.push({ event_id: meeting.event_id, rank: top.length + 1, reason: clamp(reason, REASON_MAX_CHARS) });
2625
+ if (top.length >= maxPicks) break;
2626
+ }
2627
+ return { top, collapsed_count: Math.max(0, windowCount - top.length), dropped };
2628
+ }
2629
+ function weeklyFocusTarget(rankedMeetings, options = {}) {
2630
+ const onePerAccount = options.onePerAccount !== false;
2631
+ const ceiling = onePerAccount ? new Set(rankedMeetings.map((m) => m.account_id)).size : rankedMeetings.length;
2632
+ return Math.min(effectiveMaxPicks(options.maxPicks), ceiling);
2633
+ }
2634
+ function prepareWeeklyFocusMeetings(meetings, options) {
2635
+ const windowMeetings = weeklyWindowMeetings(meetings, options);
2636
+ return {
2637
+ windowMeetings,
2638
+ rankedMeetings: windowMeetings.slice(0, MAX_RANK_MEETINGS),
2639
+ windowCount: windowMeetings.length
2640
+ };
2641
+ }
2642
+
2388
2643
  const PIA_SEARCH_ROUTE = "/core/api/protected/pia-prepare/pia-search";
2389
2644
  const CAPABILITIES_TIMEOUT_MS = 3e3;
2390
2645
  const ON_DEVICE_MAX_TOKENS = 512;
@@ -2419,6 +2674,32 @@ function apiOriginFromEnv(env) {
2419
2674
  if (typeof orgDomain === "string" && orgDomain.length > 0) return normalizeOrigin(orgDomain);
2420
2675
  throw new Error("piaSearchAnswer: no customer domain found in env (token_claims / organization metadata)");
2421
2676
  }
2677
+ async function postJsonWithTimeout(url, body, accessToken, timeoutMs, label) {
2678
+ const controller = new AbortController();
2679
+ const timer = setTimeout(() => controller.abort(), timeoutMs);
2680
+ try {
2681
+ const response = await fetch(url, {
2682
+ method: "POST",
2683
+ signal: controller.signal,
2684
+ headers: {
2685
+ "Content-Type": "application/json",
2686
+ Authorization: `Bearer ${accessToken}`
2687
+ },
2688
+ body: JSON.stringify(body)
2689
+ });
2690
+ if (!response.ok) {
2691
+ throw new Error(`${label} ${response.status}`);
2692
+ }
2693
+ return await response.json();
2694
+ } catch (error) {
2695
+ if (error?.name === "AbortError") {
2696
+ throw new Error(`${label} online request timed out after ${timeoutMs}ms`);
2697
+ }
2698
+ throw error;
2699
+ } finally {
2700
+ clearTimeout(timer);
2701
+ }
2702
+ }
2422
2703
  function buildOnDevicePrompt(query, contextText) {
2423
2704
  const context = typeof contextText === "string" && contextText.trim().length > 0 ? contextText.trim() : "No content context was provided.";
2424
2705
  return `You are PIA, a sales content assistant. Answer the rep's question using ONLY the CONTEXT below — do not invent content that is not in it.
@@ -2428,7 +2709,7 @@ ${context}
2428
2709
 
2429
2710
  QUESTION: ${query}
2430
2711
 
2431
- Answer concisely in plain text. If the context does not contain the answer, say you could not find it in the available content.`;
2712
+ Answer concisely in plain text. If the context does not contain the answer, say you could not find it in the available content. Refer to content by its name only — do not include any file ids or codes in your answer.`;
2432
2713
  }
2433
2714
  async function aiGetCapabilities() {
2434
2715
  const env = await highLevelApi.API.request("get_env");
@@ -2474,41 +2755,126 @@ async function piaSearchAnswer(payload) {
2474
2755
  if (!instanceId) {
2475
2756
  throw new Error("piaSearchAnswer: instance_id could not be resolved from the payload or env");
2476
2757
  }
2477
- const controller = new AbortController();
2478
- const timer = setTimeout(() => controller.abort(), ONLINE_TIMEOUT_MS);
2479
- try {
2480
- const response = await fetch(`${apiOriginFromEnv(env)}${PIA_SEARCH_ROUTE}`, {
2481
- method: "POST",
2482
- signal: controller.signal,
2483
- headers: {
2484
- "Content-Type": "application/json",
2485
- Authorization: `Bearer ${env.pitcher.access_token}`
2486
- },
2487
- body: JSON.stringify({
2488
- query: payload.query,
2489
- instance_id: instanceId,
2490
- ...Array.isArray(payload.file_ids) && payload.file_ids.length > 0 && { file_ids: payload.file_ids }
2491
- })
2492
- });
2493
- if (!response.ok) {
2494
- throw new Error(`pia_search ${response.status}`);
2495
- }
2496
- const data = await response.json();
2497
- const output = data?.pia_search ?? {};
2498
- return {
2499
- answer: typeof output.answer === "string" ? output.answer : "",
2500
- cited_file_ids: Array.isArray(output.cited_file_ids) ? output.cited_file_ids : [],
2501
- ...output.confidence && { confidence: output.confidence },
2502
- source: "online"
2503
- };
2504
- } catch (error) {
2505
- if (error?.name === "AbortError") {
2506
- throw new Error(`pia_search online request timed out after ${ONLINE_TIMEOUT_MS}ms`);
2758
+ const data = await postJsonWithTimeout(
2759
+ `${apiOriginFromEnv(env)}${PIA_SEARCH_ROUTE}`,
2760
+ {
2761
+ query: payload.query,
2762
+ instance_id: instanceId,
2763
+ ...Array.isArray(payload.file_ids) && payload.file_ids.length > 0 && { file_ids: payload.file_ids },
2764
+ ...payload.include_document_text === true && { include_document_text: true },
2765
+ ...Array.isArray(payload.expansion_terms) && payload.expansion_terms.length > 0 && { expansion_terms: payload.expansion_terms }
2766
+ },
2767
+ env.pitcher.access_token,
2768
+ ONLINE_TIMEOUT_MS,
2769
+ "pia_search"
2770
+ );
2771
+ const output = data?.pia_search ?? {};
2772
+ return {
2773
+ answer: typeof output.answer === "string" ? output.answer : "",
2774
+ cited_file_ids: Array.isArray(output.cited_file_ids) ? output.cited_file_ids : [],
2775
+ ...output.confidence && { confidence: output.confidence },
2776
+ source: "online"
2777
+ };
2778
+ }
2779
+ const WEEKLY_FOCUS_ROUTE = "/core/api/protected/pia-prepare/weekly-focus";
2780
+ const weeklyFocusOutputReserve = (maxPicks) => 220 + 60 * maxPicks;
2781
+ const ON_DEVICE_COMPLETE_TIMEOUT_MS = 12e4;
2782
+ async function weeklyFocusRank(payload) {
2783
+ if (!payload || !Array.isArray(payload.meetings)) {
2784
+ return Promise.reject(new Error("meetings is required and must be an array"));
2785
+ }
2786
+ const env = await highLevelApi.API.request("get_env");
2787
+ const timeZone = payload.time_zone || Intl.DateTimeFormat().resolvedOptions().timeZone;
2788
+ const { windowMeetings, rankedMeetings, windowCount } = prepareWeeklyFocusMeetings(payload.meetings, { timeZone });
2789
+ if (rankedMeetings.length === 0) {
2790
+ return { hero: "", top: [], collapsed_count: 0, source: env?.mode === "IOS" ? "on_device" : "online" };
2791
+ }
2792
+ const maxPicks = effectiveMaxPicks(payload.max_picks);
2793
+ const resolveOptions = {
2794
+ maxPicks,
2795
+ onePerAccount: payload.one_per_account,
2796
+ windowCount
2797
+ };
2798
+ if (env?.mode === "IOS") {
2799
+ try {
2800
+ const capabilities = await withTimeout(
2801
+ highLevelApi.API.request("ai.get_capabilities"),
2802
+ CAPABILITIES_TIMEOUT_MS,
2803
+ "ai.get_capabilities"
2804
+ );
2805
+ if (capabilities?.available) {
2806
+ const prompt = template({
2807
+ meetings: rankedMeetings,
2808
+ context: payload.context,
2809
+ max_picks: maxPicks,
2810
+ one_per_account: payload.one_per_account,
2811
+ time_zone: timeZone
2812
+ });
2813
+ const runOnce = async () => {
2814
+ const completion = await withTimeout(
2815
+ aiComplete({ prompt, max_tokens: weeklyFocusOutputReserve(maxPicks) }),
2816
+ ON_DEVICE_COMPLETE_TIMEOUT_MS,
2817
+ "ai.complete"
2818
+ );
2819
+ const parsed2 = extractWeeklyFocus(completion.text);
2820
+ return { parsed: parsed2, resolved: resolveWeeklyFocusPicks(parsed2, rankedMeetings, resolveOptions) };
2821
+ };
2822
+ let attempt = await runOnce();
2823
+ if (attempt.resolved.top.length < weeklyFocusTarget(rankedMeetings, resolveOptions)) {
2824
+ try {
2825
+ const second = await runOnce();
2826
+ if (second.resolved.top.length > attempt.resolved.top.length) attempt = second;
2827
+ } catch (error) {
2828
+ console.warn("[weeklyFocusRank] on-device retry failed, keeping the first attempt:", error);
2829
+ }
2830
+ }
2831
+ const { parsed, resolved } = attempt;
2832
+ if (resolved.top.length > 0 || parsed.hero) {
2833
+ return {
2834
+ hero: parsed.hero,
2835
+ top: resolved.top,
2836
+ collapsed_count: resolved.collapsed_count,
2837
+ source: "on_device"
2838
+ };
2839
+ }
2840
+ }
2841
+ } catch (error) {
2842
+ console.warn("[weeklyFocusRank] on-device path unavailable, falling back to online:", error);
2507
2843
  }
2508
- throw error;
2509
- } finally {
2510
- clearTimeout(timer);
2511
2844
  }
2845
+ const instanceId = payload.instance_id || env?.pitcher?.instance?.id;
2846
+ if (!instanceId) {
2847
+ throw new Error("weeklyFocusRank: instance_id could not be resolved from the payload or env");
2848
+ }
2849
+ const data = await postJsonWithTimeout(
2850
+ `${apiOriginFromEnv(env)}${WEEKLY_FOCUS_ROUTE}`,
2851
+ {
2852
+ instance_id: instanceId,
2853
+ // Send the WINDOW, not the caller's whole calendar. The route re-windows
2854
+ // anyway (idempotent — same rule, same zone), so this only drops meetings
2855
+ // it would have dropped, while keeping `collapsed_count` correct: the route
2856
+ // derives the window size from what it receives, so sending the
2857
+ // already-capped 12 would understate it. It also keeps a caller with a long
2858
+ // history from tripping the route's 500-meeting body cap over meetings
2859
+ // outside the week entirely.
2860
+ meetings: windowMeetings,
2861
+ time_zone: timeZone,
2862
+ ...payload.context && { context: payload.context },
2863
+ // Clamped, not raw: the route 400s on out-of-range values.
2864
+ ...payload.max_picks !== void 0 && { max_picks: maxPicks },
2865
+ ...payload.one_per_account !== void 0 && { one_per_account: payload.one_per_account }
2866
+ },
2867
+ env.pitcher.access_token,
2868
+ ONLINE_TIMEOUT_MS,
2869
+ "weekly_focus"
2870
+ );
2871
+ const output = data?.weekly_focus ?? {};
2872
+ return {
2873
+ hero: typeof output.hero === "string" ? output.hero : "",
2874
+ top: Array.isArray(output.top) ? output.top : [],
2875
+ collapsed_count: typeof output.collapsed_count === "number" ? output.collapsed_count : 0,
2876
+ source: "online"
2877
+ };
2512
2878
  }
2513
2879
 
2514
2880
  const APPS_DB_BASE_PATH = "/core/api/protected/appsdb";
@@ -3871,6 +4237,7 @@ const modules = /*#__PURE__*/Object.freeze(/*#__PURE__*/Object.defineProperty({
3871
4237
  openWebViewAlwaysOnTop,
3872
4238
  patchCoreFolder,
3873
4239
  piaSearchAnswer,
4240
+ postJsonWithTimeout,
3874
4241
  query,
3875
4242
  refreshAccessToken,
3876
4243
  refreshServiceToken,
@@ -3898,7 +4265,8 @@ const modules = /*#__PURE__*/Object.freeze(/*#__PURE__*/Object.defineProperty({
3898
4265
  updateCanvasIndicators,
3899
4266
  updateFile,
3900
4267
  updateFolder,
3901
- updateMyUser
4268
+ updateMyUser,
4269
+ weeklyFocusRank
3902
4270
  }, Symbol.toStringTag, { value: 'Module' }));
3903
4271
 
3904
4272
  class EventEmitter {
@@ -4380,16 +4748,17 @@ const onlineRestApiHandlers = {
4380
4748
  },
4381
4749
  update_canvas: async (params) => {
4382
4750
  check();
4383
- const { id, fields, ...canvasData } = params;
4751
+ const { id, fields, lazy_sections, ...canvasData } = params;
4752
+ const lazyQs = lazy_sections === true ? "lazy_sections=true" : "";
4384
4753
  const useCoreEndpoint = env?.launch_darkly?.enable_canvas_core_patch === true;
4385
4754
  if (useCoreEndpoint) {
4386
4755
  const origin = env.pitcher.token_claims["https://pitcher.com/claims/urls"].custom_domain;
4387
- const fieldsQs = fields ? `?fields=${encodeURIComponent(fields)}` : "";
4388
- const url = `${origin}/core/api/protected/v1/canvases/${id}${fieldsQs}`;
4756
+ const qs = [fields ? `fields=${encodeURIComponent(fields)}` : "", lazyQs].filter(Boolean).join("&");
4757
+ const url = `${origin}/core/api/protected/v1/canvases/${id}${qs ? `?${qs}` : ""}`;
4389
4758
  const res2 = await http.patchUrl(url, canvasData);
4390
4759
  return res2;
4391
4760
  }
4392
- const res = await http.patch(`/canvases/${id}`, { fields, ...canvasData });
4761
+ const res = await http.patch(`/canvases/${id}${lazyQs ? `?${lazyQs}` : ""}`, { fields, ...canvasData });
4393
4762
  return res;
4394
4763
  },
4395
4764
  delete_canvas: async (params) => {