@fayz-ai/core 0.14.0 → 0.16.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 (100) hide show
  1. package/dist/access/engine.d.ts +31 -1
  2. package/dist/access/engine.d.ts.map +1 -1
  3. package/dist/access/index.d.ts +1 -1
  4. package/dist/access/index.d.ts.map +1 -1
  5. package/dist/access/index.js +24 -3
  6. package/dist/access/index.js.map +1 -1
  7. package/dist/app/app-config.d.ts +101 -0
  8. package/dist/app/app-config.d.ts.map +1 -0
  9. package/dist/app/service-client.d.ts +69 -0
  10. package/dist/app/service-client.d.ts.map +1 -0
  11. package/dist/{chunk-S5FOFKR4.js → chunk-2ZAUJVYP.js} +103 -24
  12. package/dist/chunk-2ZAUJVYP.js.map +1 -0
  13. package/dist/{chunk-KFBEKFII.js → chunk-BCI743RH.js} +37 -4
  14. package/dist/chunk-BCI743RH.js.map +1 -0
  15. package/dist/{chunk-IKZR3WSK.js → chunk-KJUD6JA5.js} +3 -3
  16. package/dist/{chunk-IKZR3WSK.js.map → chunk-KJUD6JA5.js.map} +1 -1
  17. package/dist/{chunk-7GJTLBG3.js → chunk-OHH26S4N.js} +3 -3
  18. package/dist/{chunk-7GJTLBG3.js.map → chunk-OHH26S4N.js.map} +1 -1
  19. package/dist/chunk-RCMFU3RZ.js +13 -0
  20. package/dist/chunk-RCMFU3RZ.js.map +1 -0
  21. package/dist/{chunk-RRYFXUQX.js → chunk-UMIZDILP.js} +7 -3
  22. package/dist/chunk-UMIZDILP.js.map +1 -0
  23. package/dist/{chunk-X4FRQR2R.js → chunk-ZIB6Y7TE.js} +153 -8
  24. package/dist/chunk-ZIB6Y7TE.js.map +1 -0
  25. package/dist/data/archetype.d.ts +5 -0
  26. package/dist/data/archetype.d.ts.map +1 -1
  27. package/dist/data/cached.d.ts +17 -2
  28. package/dist/data/cached.d.ts.map +1 -1
  29. package/dist/data/index.d.ts +1 -0
  30. package/dist/data/index.d.ts.map +1 -1
  31. package/dist/data/index.js +2 -2
  32. package/dist/data/missing-objects.d.ts +14 -0
  33. package/dist/data/missing-objects.d.ts.map +1 -0
  34. package/dist/data/offline.d.ts +10 -1
  35. package/dist/data/offline.d.ts.map +1 -1
  36. package/dist/data/resolve.d.ts.map +1 -1
  37. package/dist/data/supabase.d.ts +5 -0
  38. package/dist/data/supabase.d.ts.map +1 -1
  39. package/dist/data/unit-scope.d.ts +48 -0
  40. package/dist/data/unit-scope.d.ts.map +1 -0
  41. package/dist/events/envelope.d.ts.map +1 -1
  42. package/dist/events/index.d.ts +2 -0
  43. package/dist/events/index.d.ts.map +1 -1
  44. package/dist/events/log.d.ts.map +1 -1
  45. package/dist/events/useDataVersion.d.ts +28 -0
  46. package/dist/events/useDataVersion.d.ts.map +1 -0
  47. package/dist/i18n/index.d.ts.map +1 -1
  48. package/dist/i18n/index.js +1 -1
  49. package/dist/i18n/shell-translations.en.d.ts.map +1 -1
  50. package/dist/i18n/shell-translations.pt-br.d.ts.map +1 -1
  51. package/dist/index.d.ts +15 -2
  52. package/dist/index.d.ts.map +1 -1
  53. package/dist/index.js +653 -45
  54. package/dist/index.js.map +1 -1
  55. package/dist/integrations/index.js +2 -2
  56. package/dist/lib/debug.d.ts +12 -0
  57. package/dist/lib/debug.d.ts.map +1 -0
  58. package/dist/lib/unit.d.ts +20 -0
  59. package/dist/lib/unit.d.ts.map +1 -0
  60. package/dist/manifest/index.d.ts +45 -0
  61. package/dist/manifest/index.d.ts.map +1 -1
  62. package/dist/manifest/integration-surface.d.ts +16 -0
  63. package/dist/manifest/integration-surface.d.ts.map +1 -0
  64. package/dist/messaging/index.d.ts +5 -2
  65. package/dist/messaging/index.d.ts.map +1 -1
  66. package/dist/messaging/phone.d.ts +32 -0
  67. package/dist/messaging/phone.d.ts.map +1 -0
  68. package/dist/messaging/provider-template.d.ts +122 -0
  69. package/dist/messaging/provider-template.d.ts.map +1 -0
  70. package/dist/messaging/registry.d.ts +12 -1
  71. package/dist/messaging/registry.d.ts.map +1 -1
  72. package/dist/messaging/types.d.ts +32 -0
  73. package/dist/messaging/types.d.ts.map +1 -1
  74. package/dist/plugin/index.js +1 -1
  75. package/dist/plugin/runtime.d.ts.map +1 -1
  76. package/dist/search/engine.d.ts.map +1 -1
  77. package/dist/{shell-translations.pt-br-PTOWYXNA.js → shell-translations.pt-br-HD4F3QKU.js} +118 -7
  78. package/dist/shell-translations.pt-br-HD4F3QKU.js.map +1 -0
  79. package/dist/testing/index.js +3 -3
  80. package/dist/types/agents.d.ts +97 -0
  81. package/dist/types/agents.d.ts.map +1 -0
  82. package/dist/types/analytics.d.ts +15 -0
  83. package/dist/types/analytics.d.ts.map +1 -1
  84. package/dist/types/auth.d.ts +9 -0
  85. package/dist/types/auth.d.ts.map +1 -1
  86. package/dist/types/crud.d.ts +38 -0
  87. package/dist/types/crud.d.ts.map +1 -1
  88. package/dist/types/index.d.ts +2 -0
  89. package/dist/types/index.d.ts.map +1 -1
  90. package/dist/types/index.js +1 -1
  91. package/dist/types/org.d.ts +10 -0
  92. package/dist/types/org.d.ts.map +1 -1
  93. package/dist/types/plugins.d.ts +97 -0
  94. package/dist/types/plugins.d.ts.map +1 -1
  95. package/package.json +2 -2
  96. package/dist/chunk-KFBEKFII.js.map +0 -1
  97. package/dist/chunk-RRYFXUQX.js.map +0 -1
  98. package/dist/chunk-S5FOFKR4.js.map +0 -1
  99. package/dist/chunk-X4FRQR2R.js.map +0 -1
  100. package/dist/shell-translations.pt-br-PTOWYXNA.js.map +0 -1
package/dist/index.js CHANGED
@@ -1,22 +1,72 @@
1
- export { PluginContractError, assertConnectorContract, assertPluginManifestContract, bespokeConnectorTables } from './chunk-IKZR3WSK.js';
2
- export { CONNECTIONS_TABLE, CONNECTOR_CATEGORIES, ConnectionSettingsError, ConnectorRuntimeTokenUnavailableError, RequestBlockedError, RequestPlaneError, SYNC_RUNS_TABLE, assertSettingsAllowed, buyerIdempotencyKey, connectionKey, connectionRuns, connectionSettings, connectionStatus, connectorDirection, connectorEntities, connectorGrantScopes, connectorPermissions, connectorPlanes, connectorRuntimeToken, connectorSettings, connectorSubscribes, connectorSubscribesTo, createConnectionStore, createRpcEffectLedger, eventKeyMatches, getConnectorRuntimeTokenSource, grants, groupedPermissions, hasRequestPlane, hasSensitivePermission, invocationPlane, isConnectorCategory, isDataPermission, isEventPermission, isSettingSet, listConnections, missingRequiredSettings, permissionActions, permissionsWrite, preferredPlane, requestPlaneSpec, runEffect, runRequest, setConnectorRuntimeTokenSource, settingsStatus, setupStatusDetail, sortedPermissions, withAfterHooks } from './chunk-7GJTLBG3.js';
1
+ export { PluginContractError, assertConnectorContract, assertPluginManifestContract, bespokeConnectorTables } from './chunk-KJUD6JA5.js';
2
+ export { CONNECTIONS_TABLE, CONNECTOR_CATEGORIES, ConnectionSettingsError, ConnectorRuntimeTokenUnavailableError, RequestBlockedError, RequestPlaneError, SYNC_RUNS_TABLE, assertSettingsAllowed, buyerIdempotencyKey, connectionKey, connectionRuns, connectionSettings, connectionStatus, connectorDirection, connectorEntities, connectorGrantScopes, connectorPermissions, connectorPlanes, connectorRuntimeToken, connectorSettings, connectorSubscribes, connectorSubscribesTo, createConnectionStore, createRpcEffectLedger, eventKeyMatches, getConnectorRuntimeTokenSource, grants, groupedPermissions, hasRequestPlane, hasSensitivePermission, invocationPlane, isConnectorCategory, isDataPermission, isEventPermission, isSettingSet, listConnections, missingRequiredSettings, permissionActions, permissionsWrite, preferredPlane, requestPlaneSpec, runEffect, runRequest, setConnectorRuntimeTokenSource, settingsStatus, setupStatusDetail, sortedPermissions, withAfterHooks } from './chunk-OHH26S4N.js';
3
3
  export { CONNECTOR_RUNTIME_TOKEN_HEADER, ConnectorHttpError, RateLimitExceededError, advanceWindow, backoffDelay, classify, createRateLimiter, cursorCompleteness, fetchWithRetry, isExpiring, paginate, parseRetryAfter, reconcileMirror, serializeCursor, sliceWindow, statusNeedsBody } from './chunk-ED6NBH6U.js';
4
- import { eventBus, DATA_CHANGED_EVENT, resolveDataProvider } from './chunk-S5FOFKR4.js';
5
- export { CustomFieldsError, DATA_CHANGED_EVENT, DataError, FIELDS_TABLE, attachPersistentCache, bulkCreate, bulkRemove, cacheKeyFor, cachePrefixFor, clearCustomFieldsCache, countByTenant, createArchetypeProvider, createEventBus, createFayzApiProvider, createMockProvider, dataCall, dataQuery, emitDataChanged, eventBus, getPersistentCache, invalidateCount, localList, matchesDataChange, mergeCustomFields, registerDataTelemetry, replicaKeyFor, resolveCustomFields, resolveDataProvider, syncReplica, toDataError, useDataChanged, validateCustomFields, withCache, withOffline } from './chunk-S5FOFKR4.js';
6
- import { getActiveTenantId, getSupabaseClientOptional } from './chunk-KFBEKFII.js';
7
- export { DEFAULT_CACHE_TTL, applyRangeToQuery, clearGlobalCache, createCacheStore, createSupabaseProvider, getActiveTenantId, getSupabaseClientOptional, globalCache, isFilterObject, matchesRange, parseRangeFilter, setActiveTenantId, setGlobalSupabaseClient, stableKey, subscribeActiveTenant, useActiveTenantId } from './chunk-KFBEKFII.js';
8
- import { getBlock, checkNodePlacement, getScaffold } from './chunk-RRYFXUQX.js';
9
- export { BLOCK_SETTING_TYPES, PLUGIN_API_VERSION, PluginRuntimeProvider, auditBlocks, blockRegistry, buildEntityDomainEvents, checkAgainstSchema, checkBlockConstraints, checkBlockContext, checkNodePlacement, clearExtensionPoints, clearHandles, clearLoaders, componentRegistry, dataNeedKey, defaultPropsFromSettings, defineBlock, defineExtensionPoint, defineLoader, definePlugin, describeBlock, describeBlocks, describeExtensionPoints, describeLoaders, entityEventKey, extensionAfterHooks, extensionPointRegistry, formatSchemaIssues, getAnalyticsCards, getBlock, getBlockMeta, getComponent, getDashboardWidgets, getHandle, getMetric, getPage, getPlugin, getPluginFactory, getScaffold, getWidgetsForZone, handleNameFor, handleRegistry, hasExtensionPoint, hasHandle, hasLoader, isBlockEnabledOn, isLoaderParamRef, isLoaderPropRef, listBlocks, listExtensionHandlers, listExtensionPoints, listHandles, listLoaders, listMetrics, listPluginFactories, listPlugins, loaderRegistry, metricRegistry, pageRegistry, pluginFactoryRegistry, pluginRegistry, registerBlock, registerComponent, registerExtensionHandler, registerHandle, registerMetric, registerPage, registerPlugin, registerPluginFactory, registerScaffold, repairBlockTree, resolveHandle, resolveLoader, resolvePluginComponent, resolvePluginRuntime, runEscapeValve, runExtensionPoint, scaffoldRegistry, settingsToJsonSchema, summarizeBlockAudit, unregisterExtensionHandler, useConnectorsForPlugin, usePluginRuntime, usePluginRuntimeOptional } from './chunk-RRYFXUQX.js';
4
+ import { eventBus, DATA_CHANGED_EVENT, resolveDataProvider } from './chunk-2ZAUJVYP.js';
5
+ export { CustomFieldsError, DATA_CHANGED_EVENT, DataError, FIELDS_TABLE, attachPersistentCache, bulkCreate, bulkRemove, cacheKeyFor, cachePrefixFor, clearCustomFieldsCache, countByTenant, createArchetypeProvider, createEventBus, createFayzApiProvider, createMockProvider, dataCall, dataQuery, emitDataChanged, eventBus, getActiveUnitId, getPersistentCache, invalidateCount, isKnownMissing, isUnitMode, localList, matchesDataChange, mergeCustomFields, registerDataTelemetry, rememberIfMissing, replicaKeyFor, resetActiveUnit, resetMissingObjects, resolveCustomFields, resolveDataProvider, setActiveUnitId, setUnitMode, subscribeActiveUnit, syncReplica, toDataError, useActiveUnitId, useDataChanged, useUnitMode, validateCustomFields, withCache, withOffline } from './chunk-2ZAUJVYP.js';
6
+ import { getActiveTenantId, getSupabaseClientOptional } from './chunk-BCI743RH.js';
7
+ export { DEFAULT_CACHE_TTL, applyRangeToQuery, clearGlobalCache, createCacheStore, createSupabaseProvider, getActiveTenantId, getScopedResource, getSupabaseClientOptional, globalCache, isFilterObject, matchesRange, parseRangeFilter, setActiveTenantId, setGlobalSupabaseClient, setScopedResources, stableKey, subscribeActiveTenant, useActiveTenantId } from './chunk-BCI743RH.js';
8
+ import { getBlock, checkNodePlacement, getScaffold } from './chunk-UMIZDILP.js';
9
+ export { BLOCK_SETTING_TYPES, PLUGIN_API_VERSION, PluginRuntimeProvider, auditBlocks, blockRegistry, buildEntityDomainEvents, checkAgainstSchema, checkBlockConstraints, checkBlockContext, checkNodePlacement, clearExtensionPoints, clearHandles, clearLoaders, componentRegistry, dataNeedKey, defaultPropsFromSettings, defineBlock, defineExtensionPoint, defineLoader, definePlugin, describeBlock, describeBlocks, describeExtensionPoints, describeLoaders, entityEventKey, extensionAfterHooks, extensionPointRegistry, formatSchemaIssues, getAnalyticsCards, getBlock, getBlockMeta, getComponent, getDashboardWidgets, getHandle, getMetric, getPage, getPlugin, getPluginFactory, getScaffold, getWidgetsForZone, handleNameFor, handleRegistry, hasExtensionPoint, hasHandle, hasLoader, isBlockEnabledOn, isLoaderParamRef, isLoaderPropRef, listBlocks, listExtensionHandlers, listExtensionPoints, listHandles, listLoaders, listMetrics, listPluginFactories, listPlugins, loaderRegistry, metricRegistry, pageRegistry, pluginFactoryRegistry, pluginRegistry, registerBlock, registerComponent, registerExtensionHandler, registerHandle, registerMetric, registerPage, registerPlugin, registerPluginFactory, registerScaffold, repairBlockTree, resolveHandle, resolveLoader, resolvePluginComponent, resolvePluginRuntime, runEscapeValve, runExtensionPoint, scaffoldRegistry, settingsToJsonSchema, summarizeBlockAudit, unregisterExtensionHandler, useConnectorsForPlugin, usePluginRuntime, usePluginRuntimeOptional } from './chunk-UMIZDILP.js';
10
10
  export { ENTITY_FIELD_TYPES, GENERIC_ENTITY_TABLE, RESERVED_ENTITY_FIELDS, Registry, clearCustomFieldsDeclarations, clearEntityDeclarations, customFieldsRegistry, declaredCustomFields, defineCustomFields, defineEntity, describeEntities, entityAdminForm, entityDeclarationRegistry, entityRecordSchema, hasCustomFieldsDeclaration, hasEntityDeclaration, isCustomId, listEntityDeclarations, resolveEntityDeclaration, validateEntityRecordData } from './chunk-EWKX4VUM.js';
11
11
  export { clearEntityRegistry, deriveEntityKey, getAllEntities, getEntityByKey, registerEntity } from './chunk-CU64QI4A.js';
12
- import { getCurrentLocale } from './chunk-X4FRQR2R.js';
13
- export { I18nProvider, coreTranslations, getCurrentLocale, mergeTranslations, preloadLocale, registerTranslations, setCurrentLocale, setLocaleStore, tl, useI18nConfig, useTranslation } from './chunk-X4FRQR2R.js';
12
+ import { getCurrentLocale } from './chunk-ZIB6Y7TE.js';
13
+ export { I18nProvider, coreTranslations, getCurrentLocale, mergeTranslations, preloadLocale, registerTranslations, setCurrentLocale, setLocaleStore, tl, useI18nConfig, useTranslation } from './chunk-ZIB6Y7TE.js';
14
14
  export { defaultRouterAdapter, hashRouterAdapter, historyRouterAdapter, hostRouterAdapter, staticRouterAdapter, windowRouterAdapter } from './chunk-OXQGRJZT.js';
15
15
  export { FayzRuntimeError, createFayzRuntimeClient } from './chunk-7RTUHSKY.js';
16
- import * as React2 from 'react';
16
+ export { AGENT_ROLES } from './chunk-RCMFU3RZ.js';
17
+ import * as React3 from 'react';
17
18
  import { useEffect } from 'react';
18
19
  import { create } from 'zustand';
19
20
 
21
+ // src/lib/debug.ts
22
+ function isDevBuild() {
23
+ try {
24
+ return import.meta.env?.DEV === true;
25
+ } catch {
26
+ return false;
27
+ }
28
+ }
29
+ function flagValue() {
30
+ try {
31
+ return typeof localStorage !== "undefined" ? localStorage.getItem("debug") : null;
32
+ } catch {
33
+ return null;
34
+ }
35
+ }
36
+ function scopeEnabled(scope) {
37
+ const flag = flagValue();
38
+ if (flag === "off") return false;
39
+ if (flag) {
40
+ return flag.split(",").some((raw) => {
41
+ const pattern = raw.trim();
42
+ if (!pattern) return false;
43
+ if (pattern === "*" || pattern === scope) return true;
44
+ if (pattern.endsWith(".*")) return scope.startsWith(pattern.slice(0, -1));
45
+ return false;
46
+ });
47
+ }
48
+ return isDevBuild();
49
+ }
50
+ function scopeColor(scope) {
51
+ let hash = 0;
52
+ for (let i = 0; i < scope.length; i++) hash = hash * 31 + scope.charCodeAt(i) | 0;
53
+ return `hsl(${Math.abs(hash) % 360} 60% 45%)`;
54
+ }
55
+ var LEVEL_STYLE = { log: "", warn: "color:#b45309", error: "color:#dc2626" };
56
+ function createDebug(scope) {
57
+ const badge = `%c${scope}%c`;
58
+ const badgeStyle = `color:${scopeColor(scope)};font-weight:600`;
59
+ const emit = (level) => (message, ...data) => {
60
+ if (!scopeEnabled(scope)) return;
61
+ console[level](`${badge} ${message}`, badgeStyle, LEVEL_STYLE[level], ...data);
62
+ };
63
+ const logger = emit("log");
64
+ logger.warn = emit("warn");
65
+ logger.error = emit("error");
66
+ Object.defineProperty(logger, "enabled", { get: () => scopeEnabled(scope) });
67
+ return logger;
68
+ }
69
+
20
70
  // src/types/plugins.ts
21
71
  var WidgetZone = {
22
72
  SIDEBAR_BEFORE_NAV: "shell.sidebar.before-nav",
@@ -378,6 +428,7 @@ function exportCSV(columns, rows, filename) {
378
428
  }
379
429
 
380
430
  // src/events/envelope.ts
431
+ var debug = createDebug("events");
381
432
  function emitDomainEvent(key, payload, meta) {
382
433
  const event = {
383
434
  key,
@@ -387,6 +438,7 @@ function emitDomainEvent(key, payload, meta) {
387
438
  subject: meta?.subject ?? null,
388
439
  occurredAt: meta?.occurredAt ?? (/* @__PURE__ */ new Date()).toISOString()
389
440
  };
441
+ debug(`emit ${key}`, { tenantId: event.tenantId, subject: event.subject, sinks: sinks.size, payload });
390
442
  eventBus.emit(key, event);
391
443
  for (const sink of sinks) {
392
444
  try {
@@ -424,6 +476,29 @@ function sampleEventPayload(fields) {
424
476
  for (const f of fields ?? []) out[f.name] = f.example ?? `{{${f.name}}}`;
425
477
  return out;
426
478
  }
479
+ function matchesDomainEvent(event, match) {
480
+ if (typeof match === "function") return match(event);
481
+ const patterns = Array.isArray(match) ? match : [match];
482
+ return patterns.some((p) => p === "*" || (p.endsWith("*") ? event.key.startsWith(p.slice(0, -1)) : p === event.key));
483
+ }
484
+ function useDataVersion(match) {
485
+ const [version, setVersion] = React3.useState(0);
486
+ const matchRef = React3.useRef(match);
487
+ matchRef.current = match;
488
+ React3.useEffect(() => registerEventSink((event) => {
489
+ if (matchesDomainEvent(event, matchRef.current)) setVersion((v) => v + 1);
490
+ }), []);
491
+ return version;
492
+ }
493
+ function useOnDataChange(match, handler) {
494
+ const matchRef = React3.useRef(match);
495
+ matchRef.current = match;
496
+ const handlerRef = React3.useRef(handler);
497
+ handlerRef.current = handler;
498
+ React3.useEffect(() => registerEventSink((event) => {
499
+ if (matchesDomainEvent(event, matchRef.current)) handlerRef.current(event);
500
+ }), []);
501
+ }
427
502
 
428
503
  // src/events/log.ts
429
504
  var EVENT_LOG_TABLE = "plg_event_log";
@@ -446,6 +521,7 @@ function toRun(row) {
446
521
  }
447
522
  var pending = [];
448
523
  var flushing = false;
524
+ var sinkDisabled = false;
449
525
  function flushSoon() {
450
526
  if (flushing) return;
451
527
  flushing = true;
@@ -453,12 +529,19 @@ function flushSoon() {
453
529
  const rows = pending;
454
530
  pending = [];
455
531
  flushing = false;
456
- if (!rows.length) return;
532
+ if (!rows.length || sinkDisabled) return;
457
533
  const db = client();
458
534
  if (!db) return;
459
535
  void (async () => {
460
536
  try {
461
- await db.from(EVENT_LOG_TABLE).insert(rows);
537
+ const { error } = await db.from(EVENT_LOG_TABLE).insert(rows);
538
+ if (!error) return;
539
+ if (error.code === "PGRST205" || String(error.message ?? "").includes("Could not find the table")) {
540
+ sinkDisabled = true;
541
+ console.warn(`[events] ${EVENT_LOG_TABLE} n\xE3o existe neste pool \u2014 hist\xF3rico desligado (migra\xE7\xE3o 027).`);
542
+ return;
543
+ }
544
+ console.warn("[events] could not record run", error);
462
545
  } catch (err) {
463
546
  console.warn("[events] could not record run", err);
464
547
  }
@@ -510,9 +593,9 @@ async function loadEventRuns(eventKey, options) {
510
593
 
511
594
  // src/events/index.tsx
512
595
  function useOnEvent(event, handler, deps = []) {
513
- const ref = React2.useRef(handler);
596
+ const ref = React3.useRef(handler);
514
597
  ref.current = handler;
515
- React2.useEffect(
598
+ React3.useEffect(
516
599
  () => eventBus.on(event, (payload) => ref.current(payload)),
517
600
  // eslint-disable-next-line react-hooks/exhaustive-deps
518
601
  [event, ...deps]
@@ -679,7 +762,8 @@ var SCAN_CONCURRENCY = 16;
679
762
  var TARGET_TIMEOUT_MS = 3e3;
680
763
  var CACHE_TTL_MS = 3e4;
681
764
  var CACHE_MAX_ENTRIES = 40;
682
- var RPC_NAME = "fayz_global_search";
765
+ var RPC_NAMES = ["global_search", "fayz_global_search"];
766
+ var rpcName = RPC_NAMES[0];
683
767
  var indexAvailable = null;
684
768
  function setSearchIndexAvailable(value) {
685
769
  indexAvailable = value;
@@ -871,20 +955,34 @@ function isMissingFunction(error) {
871
955
  if (!error) return false;
872
956
  if (error.code === "PGRST202") return true;
873
957
  const message = (error.message ?? "").toLowerCase();
874
- return message.includes(RPC_NAME) && (message.includes("does not exist") || message.includes("not find"));
958
+ return RPC_NAMES.some((n) => message.includes(n)) && (message.includes("does not exist") || message.includes("not find"));
875
959
  }
876
960
  async function searchViaIndex(query, targets, perTarget, limit) {
877
961
  const client2 = getSupabaseClientOptional();
878
962
  const tenantId = getActiveTenantId();
879
963
  if (!client2 || !tenantId) return { hits: [], ok: false };
880
964
  const byKey = new Map(targets.map((t) => [t.key, t]));
881
- const { data, error } = await client2.rpc(RPC_NAME, {
965
+ const args = {
882
966
  p_query: query.raw,
883
967
  p_tenant_id: tenantId,
884
968
  p_entity_keys: targets.map((t) => t.key),
885
969
  p_per_source: perTarget * 3,
886
970
  p_limit: limit * 3
887
- });
971
+ };
972
+ let { data, error } = await client2.rpc(rpcName, args);
973
+ if (error && isMissingFunction(error)) {
974
+ const other = RPC_NAMES.find((n) => n !== rpcName);
975
+ if (other) {
976
+ const retry = await client2.rpc(other, args);
977
+ if (!retry.error) {
978
+ rpcName = other;
979
+ data = retry.data;
980
+ error = null;
981
+ } else if (isMissingFunction(retry.error)) {
982
+ error = retry.error;
983
+ } else throw retry.error;
984
+ }
985
+ }
888
986
  if (error) {
889
987
  if (isMissingFunction(error)) {
890
988
  indexAvailable = false;
@@ -1099,15 +1197,15 @@ function blockEditorMode() {
1099
1197
  }
1100
1198
  var BLOCK_PATH_ATTRIBUTE = "data-fayz-block";
1101
1199
  var BLOCK_TYPE_ATTRIBUTE = "data-fayz-block-type";
1102
- var BlockPathContext = React2.createContext("");
1200
+ var BlockPathContext = React3.createContext("");
1103
1201
  function useBlockPath() {
1104
- return React2.useContext(BlockPathContext);
1202
+ return React3.useContext(BlockPathContext);
1105
1203
  }
1106
1204
  function getBlockErrorVisibility() {
1107
1205
  return errorVisibility ?? defaultVisibility();
1108
1206
  }
1109
1207
  function BlockErrorCard({ title, detail }) {
1110
- return React2.createElement(
1208
+ return React3.createElement(
1111
1209
  "div",
1112
1210
  {
1113
1211
  "data-block-error": title,
@@ -1120,14 +1218,14 @@ function BlockErrorCard({ title, detail }) {
1120
1218
  }
1121
1219
  },
1122
1220
  title,
1123
- detail ? React2.createElement("div", { style: { opacity: 0.8, marginTop: 4 } }, detail) : null
1221
+ detail ? React3.createElement("div", { style: { opacity: 0.8, marginTop: 4 } }, detail) : null
1124
1222
  );
1125
1223
  }
1126
1224
  function UnknownBlock({ type }) {
1127
1225
  if (getBlockErrorVisibility() === "silent") return null;
1128
- return React2.createElement(BlockErrorCard, { title: `Unknown block: "${type}"` });
1226
+ return React3.createElement(BlockErrorCard, { title: `Unknown block: "${type}"` });
1129
1227
  }
1130
- var BlockErrorBoundary = class extends React2.Component {
1228
+ var BlockErrorBoundary = class extends React3.Component {
1131
1229
  constructor() {
1132
1230
  super(...arguments);
1133
1231
  this.state = { error: null };
@@ -1141,22 +1239,22 @@ var BlockErrorBoundary = class extends React2.Component {
1141
1239
  render() {
1142
1240
  if (!this.state.error) return this.props.children;
1143
1241
  if (getBlockErrorVisibility() === "silent") return null;
1144
- return React2.createElement(BlockErrorCard, {
1242
+ return React3.createElement(BlockErrorCard, {
1145
1243
  title: `Block "${this.props.blockType}" crashed`,
1146
1244
  detail: this.state.error.message
1147
1245
  });
1148
1246
  }
1149
1247
  };
1150
- var BlockAncestryContext = React2.createContext([]);
1248
+ var BlockAncestryContext = React3.createContext([]);
1151
1249
  function ConstraintViolation({ detail }) {
1152
1250
  if (getBlockErrorVisibility() === "silent") return null;
1153
- return React2.createElement(BlockErrorCard, { title: "Misplaced block", detail });
1251
+ return React3.createElement(BlockErrorCard, { title: "Misplaced block", detail });
1154
1252
  }
1155
1253
  var warnedViolations = /* @__PURE__ */ new Set();
1156
1254
  function BlockRenderer({ node, path }) {
1157
- const ancestry = React2.useContext(BlockAncestryContext);
1255
+ const ancestry = React3.useContext(BlockAncestryContext);
1158
1256
  const Comp = getBlock(node.type);
1159
- if (!Comp) return React2.createElement(UnknownBlock, { type: node.type });
1257
+ if (!Comp) return React3.createElement(UnknownBlock, { type: node.type });
1160
1258
  const violations = checkNodePlacement(node.type, ancestry);
1161
1259
  if (violations.length > 0) {
1162
1260
  const message = violations.map((v) => v.detail).join("; ");
@@ -1164,23 +1262,23 @@ function BlockRenderer({ node, path }) {
1164
1262
  warnedViolations.add(message);
1165
1263
  console.warn(`[blocks] ${message}`);
1166
1264
  }
1167
- return React2.createElement(ConstraintViolation, { detail: violations[0].detail });
1265
+ return React3.createElement(ConstraintViolation, { detail: violations[0].detail });
1168
1266
  }
1169
1267
  const childEls = node.children?.length ? renderBlocks(node.children, path ? `${path}.children` : void 0) : null;
1170
- const rendered = React2.createElement(
1268
+ const rendered = React3.createElement(
1171
1269
  BlockAncestryContext.Provider,
1172
1270
  { value: ancestry.length ? [...ancestry, node.type] : [node.type] },
1173
- React2.createElement(
1271
+ React3.createElement(
1174
1272
  BlockErrorBoundary,
1175
1273
  { blockType: node.type },
1176
- React2.createElement(Comp, { ...node.props ?? {}, node }, childEls)
1274
+ React3.createElement(Comp, { ...node.props ?? {}, node }, childEls)
1177
1275
  )
1178
1276
  );
1179
1277
  if (!editorMode || !path) return rendered;
1180
- return React2.createElement(
1278
+ return React3.createElement(
1181
1279
  BlockPathContext.Provider,
1182
1280
  { value: path },
1183
- React2.createElement(
1281
+ React3.createElement(
1184
1282
  "div",
1185
1283
  {
1186
1284
  [BLOCK_PATH_ATTRIBUTE]: path,
@@ -1193,7 +1291,7 @@ function BlockRenderer({ node, path }) {
1193
1291
  }
1194
1292
  function renderBlocks(nodes, basePath) {
1195
1293
  return nodes.map(
1196
- (node, i) => React2.createElement(BlockRenderer, {
1294
+ (node, i) => React3.createElement(BlockRenderer, {
1197
1295
  key: node.id ?? `${node.type}-${i}`,
1198
1296
  node,
1199
1297
  // POSITIONAL, deliberately. `[#id]` is the reorder-stable form
@@ -1235,6 +1333,10 @@ var allowedManifestKeys = /* @__PURE__ */ new Set([
1235
1333
  "surfaces",
1236
1334
  "limitDeclarations",
1237
1335
  "agent",
1336
+ // Declared on AppManifest and in app-manifest.schema.json since the connector
1337
+ // surface landed, but never added here — so every app that publishes one
1338
+ // failed validation at boot.
1339
+ "integrationSurface",
1238
1340
  "contractHash"
1239
1341
  ]);
1240
1342
  var allowedBackendKeys = /* @__PURE__ */ new Set(["provider", "projectRef", "url", "adapterId", "options"]);
@@ -1481,14 +1583,62 @@ function isNonEmptyString(value) {
1481
1583
  return typeof value === "string" && value.trim().length > 0;
1482
1584
  }
1483
1585
 
1586
+ // src/manifest/integration-surface.ts
1587
+ function resolveIntegrationSurface(declarations, entities) {
1588
+ const byKey = new Map(entities.map((e) => [e.key, e]));
1589
+ const seen = /* @__PURE__ */ new Set();
1590
+ const resolved = declarations.map((declaration) => {
1591
+ const entity = byKey.get(declaration.entity);
1592
+ if (!entity) {
1593
+ throw new Error(
1594
+ `integrationSurface: no entity "${declaration.entity}" in this app. Known: ${[...byKey.keys()].sort().join(", ") || "(none)"}`
1595
+ );
1596
+ }
1597
+ if (seen.has(declaration.entity)) {
1598
+ throw new Error(`integrationSurface: "${declaration.entity}" is declared twice`);
1599
+ }
1600
+ seen.add(declaration.entity);
1601
+ if (entity.data.tenantScoped === false) {
1602
+ throw new Error(
1603
+ `integrationSurface: "${declaration.entity}" is not tenant-scoped, so sharing it would reach every organization in the pool, not the one that consented`
1604
+ );
1605
+ }
1606
+ if (declaration.operations.length === 0) {
1607
+ throw new Error(`integrationSurface: "${declaration.entity}" declares no operations`);
1608
+ }
1609
+ if (!declaration.reason.trim()) {
1610
+ throw new Error(
1611
+ `integrationSurface: "${declaration.entity}" has no reason \u2014 it is what the consent screen shows`
1612
+ );
1613
+ }
1614
+ return {
1615
+ ...declaration,
1616
+ label: entity.label,
1617
+ table: entity.data.table,
1618
+ ...entity.data.schema ? { schema: entity.data.schema } : {},
1619
+ ...entity.data.tenantIdColumn ? { tenantIdColumn: entity.data.tenantIdColumn } : {}
1620
+ };
1621
+ });
1622
+ const columns = new Set(resolved.map((entity) => entity.tenantIdColumn ?? "tenant_id"));
1623
+ if (columns.size > 1) {
1624
+ throw new Error(
1625
+ `integrationSurface: shared entities disagree on the tenant column (${[...columns].sort().join(", ")}). A connection carries one, so they cannot be shared together.`
1626
+ );
1627
+ }
1628
+ return { entities: resolved };
1629
+ }
1630
+ function surfaceAllowsWrites(surface) {
1631
+ return surface.entities.some((entity) => entity.operations.some((operation) => operation !== "read"));
1632
+ }
1633
+
1484
1634
  // src/manifest/serialize.ts
1485
1635
  function sortValue(value) {
1486
1636
  if (Array.isArray(value)) return value.map(sortValue);
1487
1637
  if (value !== null && typeof value === "object") {
1488
- const record = value;
1638
+ const record2 = value;
1489
1639
  const out = {};
1490
- for (const key of Object.keys(record).sort()) {
1491
- const v = record[key];
1640
+ for (const key of Object.keys(record2).sort()) {
1641
+ const v = record2[key];
1492
1642
  if (v !== void 0) out[key] = sortValue(v);
1493
1643
  }
1494
1644
  return out;
@@ -1636,6 +1786,9 @@ var app_manifest_schema_default = {
1636
1786
  agent: {
1637
1787
  $ref: "#/$defs/agentContract"
1638
1788
  },
1789
+ integrationSurface: {
1790
+ $ref: "#/$defs/integrationSurface"
1791
+ },
1639
1792
  contractHash: {
1640
1793
  type: "string",
1641
1794
  minLength: 1,
@@ -1643,6 +1796,71 @@ var app_manifest_schema_default = {
1643
1796
  }
1644
1797
  },
1645
1798
  $defs: {
1799
+ integrationSurface: {
1800
+ type: "object",
1801
+ description: "What another app may reach when a member of this organization consents. An entity absent from this list is unreachable, not merely undocumented.",
1802
+ required: [
1803
+ "entities"
1804
+ ],
1805
+ additionalProperties: false,
1806
+ properties: {
1807
+ entities: {
1808
+ type: "array",
1809
+ items: {
1810
+ type: "object",
1811
+ required: [
1812
+ "entity",
1813
+ "operations",
1814
+ "reason",
1815
+ "label",
1816
+ "table"
1817
+ ],
1818
+ additionalProperties: false,
1819
+ properties: {
1820
+ entity: {
1821
+ type: "string",
1822
+ minLength: 1,
1823
+ description: "Key of an entity in agent.entities."
1824
+ },
1825
+ operations: {
1826
+ type: "array",
1827
+ minItems: 1,
1828
+ items: {
1829
+ type: "string",
1830
+ enum: [
1831
+ "read",
1832
+ "create",
1833
+ "update"
1834
+ ]
1835
+ },
1836
+ description: "Deleting another product's records is deliberately not expressible."
1837
+ },
1838
+ reason: {
1839
+ type: "string",
1840
+ minLength: 1,
1841
+ description: "Shown on the consent screen, so it is written for the person approving."
1842
+ },
1843
+ label: {
1844
+ type: "string",
1845
+ minLength: 1
1846
+ },
1847
+ table: {
1848
+ type: "string",
1849
+ minLength: 1
1850
+ },
1851
+ schema: {
1852
+ type: "string",
1853
+ minLength: 1
1854
+ },
1855
+ tenantIdColumn: {
1856
+ type: "string",
1857
+ minLength: 1
1858
+ }
1859
+ }
1860
+ }
1861
+ }
1862
+ }
1863
+ },
1646
1864
  surface: {
1647
1865
  type: "object",
1648
1866
  required: [
@@ -2189,9 +2407,9 @@ var app_manifest_schema_default = {
2189
2407
  }
2190
2408
  }
2191
2409
  };
2192
- var ManifestContext = React2.createContext(null);
2410
+ var ManifestContext = React3.createContext(null);
2193
2411
  function useManifest() {
2194
- return React2.useContext(ManifestContext);
2412
+ return React3.useContext(ManifestContext);
2195
2413
  }
2196
2414
  function defineApp(manifest) {
2197
2415
  const full = {
@@ -2223,12 +2441,201 @@ function renderApp(input, opts = {}) {
2223
2441
  `Scaffold "${surface.scaffold}" is not registered. Call renderApp from the package that provides it (e.g. @fayz-ai/admin, @fayz-ai/portal, @fayz-ai/storefront) or call its register*Scaffold() first.`
2224
2442
  );
2225
2443
  }
2226
- return React2.createElement(
2444
+ return React3.createElement(
2227
2445
  ManifestContext.Provider,
2228
2446
  { value: manifest },
2229
- React2.createElement(Scaffold, { manifest, surface: surfaceName })
2447
+ React3.createElement(Scaffold, { manifest, surface: surfaceName })
2230
2448
  );
2231
2449
  }
2450
+
2451
+ // src/app/app-config.ts
2452
+ var EMPTY_APP_CONFIG = Object.freeze({
2453
+ tenant_id: null,
2454
+ plugins: [],
2455
+ nav: [],
2456
+ permissions: []
2457
+ });
2458
+ function isRpcCapable(client2) {
2459
+ return typeof client2?.rpc === "function";
2460
+ }
2461
+ var cache2 = /* @__PURE__ */ new Map();
2462
+ async function fetchAppConfig(client2, options = {}) {
2463
+ const key = options.tenantId ?? "current";
2464
+ if (!options.force) {
2465
+ const hit = cache2.get(key);
2466
+ if (hit) return hit;
2467
+ }
2468
+ if (!isRpcCapable(client2)) return EMPTY_APP_CONFIG;
2469
+ const { data, error } = await client2.rpc("app_config");
2470
+ if (error || !data || typeof data !== "object") return EMPTY_APP_CONFIG;
2471
+ const config = normalizeAppConfig(data);
2472
+ cache2.set(key, config);
2473
+ return config;
2474
+ }
2475
+ function invalidateAppConfig(tenantId) {
2476
+ if (tenantId) cache2.delete(tenantId);
2477
+ else cache2.clear();
2478
+ }
2479
+ function normalizeAppConfig(raw) {
2480
+ return {
2481
+ tenant_id: raw.tenant_id ?? null,
2482
+ branding: raw.branding ?? {},
2483
+ settings: raw.settings ?? {},
2484
+ plugins: Array.isArray(raw.plugins) ? raw.plugins : [],
2485
+ nav: Array.isArray(raw.nav) ? raw.nav : [],
2486
+ permissions: Array.isArray(raw.permissions) ? raw.permissions : [],
2487
+ generated_at: raw.generated_at
2488
+ };
2489
+ }
2490
+ function toTenantPluginBindings(config) {
2491
+ const byPlugin = /* @__PURE__ */ new Map();
2492
+ for (const row of config.plugins) {
2493
+ if (row.facet) continue;
2494
+ byPlugin.set(row.plugin, {
2495
+ pluginId: row.plugin,
2496
+ status: row.status,
2497
+ tenantId: config.tenant_id ?? void 0,
2498
+ config: { ...row.config }
2499
+ });
2500
+ }
2501
+ for (const row of config.plugins) {
2502
+ if (!row.facet) continue;
2503
+ const parent = byPlugin.get(row.plugin);
2504
+ if (!parent) continue;
2505
+ const facets = parent.config?.facets ?? {};
2506
+ parent.config = {
2507
+ ...parent.config,
2508
+ facets: { ...facets, [row.facet]: row.status === "active" }
2509
+ };
2510
+ }
2511
+ return [...byPlugin.values()];
2512
+ }
2513
+ function applyNavComposition(entries, nav) {
2514
+ const overrides = new Map(nav.map((n) => [n.key, n]));
2515
+ return entries.filter((entry) => !overrides.get(entry.key)?.hidden).map((entry) => {
2516
+ const override = overrides.get(entry.key);
2517
+ if (!override) return entry;
2518
+ return {
2519
+ ...entry,
2520
+ label: override.label ?? entry.label,
2521
+ section: override.section ?? entry.section,
2522
+ position: override.position ?? entry.position
2523
+ };
2524
+ }).sort((a, b) => (a.position ?? 999) - (b.position ?? 999));
2525
+ }
2526
+ var TENANT_OWNED_MANIFEST_KEYS = ["theme", "branding", "locale"];
2527
+ function applyAppConfigToManifest(manifest, config) {
2528
+ const branding = config.branding ?? {};
2529
+ const settings = config.settings ?? {};
2530
+ const next = { ...manifest };
2531
+ if (Object.keys(branding).length > 0) {
2532
+ next.theme = { ...manifest.theme ?? {}, ...branding.theme ?? {} };
2533
+ next.branding = { ...manifest.branding ?? {}, ...branding };
2534
+ }
2535
+ if (settings.locale && typeof settings.locale === "object") {
2536
+ next.locale = { ...manifest.locale ?? {}, ...settings.locale };
2537
+ }
2538
+ if (manifest.surfaces && config.plugins.length > 0) {
2539
+ const active = new Map(
2540
+ config.plugins.filter((p) => p.status === "active" && !p.facet).map((p) => [p.plugin, p])
2541
+ );
2542
+ const surfaces = {};
2543
+ for (const [name, surface] of Object.entries(manifest.surfaces)) {
2544
+ const plugins = surface.plugins;
2545
+ surfaces[name] = plugins ? {
2546
+ ...surface,
2547
+ // A plugin the bundle carries but this tenant has not turned on is
2548
+ // dropped here rather than rendered and hidden — a screen that
2549
+ // exists and refuses is worse than one that was never offered.
2550
+ plugins: plugins.filter((p) => active.has(p.id)).map((p) => ({ ...p, config: { ...p.config ?? {}, ...active.get(p.id)?.config ?? {} } }))
2551
+ } : surface;
2552
+ }
2553
+ next.surfaces = surfaces;
2554
+ }
2555
+ return next;
2556
+ }
2557
+
2558
+ // src/app/service-client.ts
2559
+ function isRpcCapable2(client2) {
2560
+ return typeof client2?.rpc === "function";
2561
+ }
2562
+ async function callService(client2, name, payload = {}, options = {}) {
2563
+ if (!isRpcCapable2(client2)) {
2564
+ return { ok: false, reason: "unavailable", message: "no client to call through" };
2565
+ }
2566
+ const started2 = Date.now();
2567
+ const forTenant = options.tenantId;
2568
+ const { data: authData, error: authError } = forTenant ? await client2.rpc("service_authorize_for", { p_name: name, p_tenant: forTenant }) : await client2.rpc("service_authorize", { p_name: name });
2569
+ if (authError || !authData || typeof authData !== "object") {
2570
+ return { ok: false, reason: "unavailable", message: "the authorization call itself failed" };
2571
+ }
2572
+ const auth = authData;
2573
+ if (auth.allowed !== true) {
2574
+ return {
2575
+ ok: false,
2576
+ reason: auth.reason ?? "failed",
2577
+ manualFallback: auth.manual_fallback,
2578
+ entitlement: auth.entitlement,
2579
+ plugin: auth.plugin,
2580
+ permission: auth.permission
2581
+ };
2582
+ }
2583
+ const manualFallback = auth.manual_fallback;
2584
+ const timeoutMs = options.timeoutMs ?? auth.timeout_ms ?? 8e3;
2585
+ if (!client2.functions) {
2586
+ return { ok: false, reason: "unavailable", manualFallback, message: "no function transport" };
2587
+ }
2588
+ try {
2589
+ const result = await withTimeout2(
2590
+ client2.functions.invoke(options.router ?? "service-router", {
2591
+ body: forTenant ? { service: name, payload, tenant_id: forTenant } : { service: name, payload }
2592
+ }),
2593
+ timeoutMs
2594
+ );
2595
+ const durationMs = Date.now() - started2;
2596
+ if (result === TIMED_OUT) {
2597
+ void record(client2, name, "timeout", "client timeout", durationMs, forTenant);
2598
+ return { ok: false, reason: "timeout", manualFallback };
2599
+ }
2600
+ if (result.error) {
2601
+ void record(client2, name, "failed", String(result.error), durationMs, forTenant);
2602
+ return { ok: false, reason: "failed", manualFallback, message: String(result.error) };
2603
+ }
2604
+ void record(client2, name, "ok", null, durationMs, forTenant);
2605
+ return { ok: true, data: result.data, durationMs };
2606
+ } catch (err) {
2607
+ const durationMs = Date.now() - started2;
2608
+ void record(client2, name, "failed", String(err), durationMs, forTenant);
2609
+ return { ok: false, reason: "failed", manualFallback, message: String(err) };
2610
+ }
2611
+ }
2612
+ var TIMED_OUT = /* @__PURE__ */ Symbol("timed-out");
2613
+ function withTimeout2(promise, ms) {
2614
+ return Promise.race([
2615
+ promise,
2616
+ new Promise((resolve) => setTimeout(() => resolve(TIMED_OUT), ms))
2617
+ ]);
2618
+ }
2619
+ async function record(client2, service, status, reason, durationMs, tenantId) {
2620
+ try {
2621
+ await (tenantId ? client2.rpc("service_call_record_for", {
2622
+ p_service: service,
2623
+ p_tenant: tenantId,
2624
+ p_status: status,
2625
+ p_reason: reason,
2626
+ p_duration_ms: durationMs
2627
+ }) : client2.rpc("service_call_record", {
2628
+ p_service: service,
2629
+ p_status: status,
2630
+ p_reason: reason,
2631
+ p_duration_ms: durationMs
2632
+ }));
2633
+ } catch {
2634
+ }
2635
+ }
2636
+ function callServiceForTenant(client2, tenantId, name, payload = {}, options = {}) {
2637
+ return callService(client2, name, payload, { ...options, tenantId });
2638
+ }
2232
2639
  var MAX_ENTRIES = 20;
2233
2640
  var useNavHistoryStore = create((set) => ({
2234
2641
  stack: [],
@@ -2280,6 +2687,23 @@ function getMessageDefaults() {
2280
2687
  function clearMessageDefaults() {
2281
2688
  defaultsRegistry.clear();
2282
2689
  }
2690
+ var ACTION_IDS = ["confirm", "cancel", "pay"];
2691
+ var ACTION_KINDS = ["reply", "payment"];
2692
+ function normalizeMessageActions(value) {
2693
+ if (value == null) return null;
2694
+ if (!Array.isArray(value)) return [];
2695
+ const out = [];
2696
+ for (const raw of value) {
2697
+ const item = raw;
2698
+ const id = item?.id;
2699
+ const kind = item?.kind;
2700
+ const label = String(item?.label ?? "").trim();
2701
+ if (!id || !ACTION_IDS.includes(id) || !kind || !ACTION_KINDS.includes(kind) || !label) continue;
2702
+ if (out.some((a) => a.id === id)) continue;
2703
+ out.push({ id, label, kind });
2704
+ }
2705
+ return out;
2706
+ }
2283
2707
  function renderMessageTemplate(text, values) {
2284
2708
  return text.replace(/\{\{\s*(\w+)\s*\}\}/g, (whole, key) => values[key] ?? whole);
2285
2709
  }
@@ -2303,6 +2727,7 @@ function resolveMessages(overrides, events) {
2303
2727
  enabled: override?.enabled ?? def.defaultEnabled?.[channel] ?? false,
2304
2728
  subject: override?.subject ?? fallback.subject ?? "",
2305
2729
  body: override?.body ?? fallback.body,
2730
+ actions: override?.actions ?? fallback.actions ?? [],
2306
2731
  customized: Boolean(override?.subject || override?.body),
2307
2732
  placeholders
2308
2733
  });
@@ -2334,6 +2759,7 @@ async function loadMessageOverrides(tenantId) {
2334
2759
  subject: row.subject || null,
2335
2760
  body: row.body || null,
2336
2761
  name: row.name || null,
2762
+ actions: normalizeMessageActions(row.actions),
2337
2763
  updatedAt: row.updated_at ?? row.created_at
2338
2764
  }));
2339
2765
  } catch (err) {
@@ -2342,6 +2768,188 @@ async function loadMessageOverrides(tenantId) {
2342
2768
  }
2343
2769
  }
2344
2770
 
2771
+ // src/messaging/provider-template.ts
2772
+ var PROVIDER_TEMPLATE_LANGUAGE = "pt_BR";
2773
+ var MAX_QUICK_REPLY_BUTTONS = 3;
2774
+ var DEFAULT_LEAD_PADDING = "Ol\xE1! ";
2775
+ var DEFAULT_TRAIL_PADDING = " At\xE9 breve!";
2776
+ var MAX_TEMPLATE_VARIABLES = 10;
2777
+ var PLACEHOLDER = /\{\{\s*([\w]+)\s*\}\}/g;
2778
+ function providerTemplateName(eventKey, tenantId) {
2779
+ const slug = String(eventKey ?? "").toLowerCase().replace(/[^a-z0-9]+/g, "_").replace(/^_+|_+$/g, "");
2780
+ const base = `t_${slug || "message"}`;
2781
+ const owner = String(tenantId ?? "").trim();
2782
+ return owner ? `${base}_${shortHash(owner)}` : base;
2783
+ }
2784
+ function shortHash(value) {
2785
+ let hash = 2166136261;
2786
+ for (let i = 0; i < value.length; i += 1) {
2787
+ hash ^= value.charCodeAt(i);
2788
+ hash = Math.imul(hash, 16777619) >>> 0;
2789
+ }
2790
+ return hash.toString(36).padStart(7, "0");
2791
+ }
2792
+ function placementRuleApplies(category) {
2793
+ return category !== "authentication";
2794
+ }
2795
+ function compileProviderTemplate(input) {
2796
+ const source = String(input.body ?? "");
2797
+ if (!source.trim()) {
2798
+ return { ok: false, code: "empty_body", message: "A mensagem est\xE1 vazia" };
2799
+ }
2800
+ const fields = input.fields ?? [];
2801
+ const known = new Set(fields.map((f) => f.name));
2802
+ const sample = sampleEventPayload(fields);
2803
+ const examples = new Map(
2804
+ fields.map((f) => {
2805
+ const value = String(sample[f.name] ?? "");
2806
+ return [f.name, value && !value.startsWith("{{") ? value : f.label || f.name];
2807
+ })
2808
+ );
2809
+ const variableMap = [];
2810
+ let unknown = null;
2811
+ const positional = source.replace(PLACEHOLDER, (whole, name) => {
2812
+ if (!known.has(name)) {
2813
+ unknown ?? (unknown = name);
2814
+ return whole;
2815
+ }
2816
+ let index = variableMap.indexOf(name);
2817
+ if (index < 0) {
2818
+ variableMap.push(name);
2819
+ index = variableMap.length - 1;
2820
+ }
2821
+ return `{{${index + 1}}}`;
2822
+ });
2823
+ if (unknown) {
2824
+ return {
2825
+ ok: false,
2826
+ code: "unknown_variable",
2827
+ // Loud, because the alternative is a template Meta approves and a customer
2828
+ // reads with `{{cupom}}` printed in it.
2829
+ message: `A mensagem usa {{${unknown}}}, que este evento n\xE3o envia`,
2830
+ detail: unknown
2831
+ };
2832
+ }
2833
+ if (variableMap.length > MAX_TEMPLATE_VARIABLES) {
2834
+ return {
2835
+ ok: false,
2836
+ code: "too_many_variables",
2837
+ message: `A mensagem tem ${variableMap.length} campos e o WhatsApp aceita ${MAX_TEMPLATE_VARIABLES}`
2838
+ };
2839
+ }
2840
+ const category = input.kind === "marketing" ? "marketing" : "utility";
2841
+ let text = positional;
2842
+ const padded = { lead: false, trail: false };
2843
+ if (placementRuleApplies(category)) {
2844
+ if (/^[^\p{L}\p{N}]*\{\{\d+\}\}/u.test(text.trim())) {
2845
+ text = `${input.leadPadding ?? DEFAULT_LEAD_PADDING}${text.trimStart()}`;
2846
+ padded.lead = true;
2847
+ }
2848
+ if (/\{\{\d+\}\}[^\p{L}\p{N}]*$/u.test(text.trim())) {
2849
+ text = `${text.trimEnd()}${input.trailPadding ?? DEFAULT_TRAIL_PADDING}`;
2850
+ padded.trail = true;
2851
+ }
2852
+ }
2853
+ const leftover = /\{\{\s*(?!\d+\s*\}\})[^}]*\}\}/.exec(text);
2854
+ if (leftover) {
2855
+ return {
2856
+ ok: false,
2857
+ code: "named_placeholder_left",
2858
+ message: "A mensagem ainda tem um campo com nome, e o WhatsApp s\xF3 aceita numerados",
2859
+ detail: leftover[0]
2860
+ };
2861
+ }
2862
+ const components = [{
2863
+ type: "BODY",
2864
+ text,
2865
+ // One row of examples, one cell per variable, in the same order as the map.
2866
+ // Meta requires an example for each; Tyxter refuses the create without them.
2867
+ ...variableMap.length > 0 ? { example: { body_text: [variableMap.map((name) => examples.get(name) ?? name)] } } : {}
2868
+ }];
2869
+ const replies = (input.actions ?? []).filter((action) => action.kind === "reply").slice(0, MAX_QUICK_REPLY_BUTTONS);
2870
+ const actionsMap = replies.map((action, index) => ({
2871
+ index,
2872
+ id: action.id,
2873
+ kind: action.kind
2874
+ }));
2875
+ if (replies.length > 0) {
2876
+ components.push({
2877
+ type: "BUTTONS",
2878
+ buttons: replies.map((action) => ({
2879
+ type: "QUICK_REPLY",
2880
+ // 20 is Meta's button-text ceiling. Truncating beats a rejection that
2881
+ // arrives a day later naming a component index.
2882
+ text: action.label.slice(0, 20)
2883
+ }))
2884
+ });
2885
+ }
2886
+ return {
2887
+ ok: true,
2888
+ template: {
2889
+ name: providerTemplateName(input.eventKey, input.tenantId),
2890
+ language: PROVIDER_TEMPLATE_LANGUAGE,
2891
+ category,
2892
+ components,
2893
+ variableMap,
2894
+ actionsMap,
2895
+ bodySnapshot: text,
2896
+ padded
2897
+ }
2898
+ };
2899
+ }
2900
+ function positionalVariables(variableMap, values) {
2901
+ const out = {};
2902
+ variableMap.forEach((name, index) => {
2903
+ const value = values[name];
2904
+ out[String(index + 1)] = value == null ? "" : String(value);
2905
+ });
2906
+ return out;
2907
+ }
2908
+
2909
+ // src/messaging/phone.ts
2910
+ var BR_COUNTRY_CALLING_CODE = "55";
2911
+ var BR_MATCH_SUFFIX_LENGTH = 8;
2912
+ function isPlausibleDdd(ddd) {
2913
+ const n = Number(ddd);
2914
+ return n >= 11 && n <= 99 && !ddd.endsWith("0");
2915
+ }
2916
+ function isLegacyMobile(national) {
2917
+ return national.length === 10 && /^[6-9]/.test(national.slice(2));
2918
+ }
2919
+ function normalizeBrPhoneToE164(input) {
2920
+ const raw = String(input ?? "").trim();
2921
+ if (!raw) return null;
2922
+ const hadPlus = raw.startsWith("+");
2923
+ const digits = raw.replace(/\D/g, "");
2924
+ if (!digits) return null;
2925
+ if (hadPlus && !digits.startsWith(BR_COUNTRY_CALLING_CODE)) {
2926
+ return digits.length >= 8 && digits.length <= 15 ? `+${digits}` : null;
2927
+ }
2928
+ let national = digits;
2929
+ if (national.startsWith(BR_COUNTRY_CALLING_CODE) && national.length > 11) {
2930
+ national = national.slice(2);
2931
+ }
2932
+ if (national.length === 10 || national.length === 11) {
2933
+ if (!isPlausibleDdd(national.slice(0, 2))) return null;
2934
+ if (national.length === 11 && !/^9/.test(national.slice(2))) {
2935
+ return null;
2936
+ }
2937
+ const repaired = isLegacyMobile(national) ? `${national.slice(0, 2)}9${national.slice(2)}` : national;
2938
+ return `+${BR_COUNTRY_CALLING_CODE}${repaired}`;
2939
+ }
2940
+ return null;
2941
+ }
2942
+ function brNationalNumber(e164) {
2943
+ const value = String(e164 ?? "").trim();
2944
+ if (!value.startsWith(`+${BR_COUNTRY_CALLING_CODE}`)) return null;
2945
+ const national = value.slice(3);
2946
+ return /^\d{10,11}$/.test(national) ? national : null;
2947
+ }
2948
+ function brPhoneSuffixKey(input) {
2949
+ const digits = String(input ?? "").replace(/\D/g, "");
2950
+ return digits.length >= BR_MATCH_SUFFIX_LENGTH ? digits.slice(-BR_MATCH_SUFFIX_LENGTH) : null;
2951
+ }
2952
+
2345
2953
  // src/telemetry/errors.ts
2346
2954
  var APP_ERROR_EVENT = "app.error";
2347
2955
  var reporters = /* @__PURE__ */ new Set();
@@ -2473,6 +3081,6 @@ function createVendorReporter(client2) {
2473
3081
  };
2474
3082
  }
2475
3083
 
2476
- export { APP_ERROR_EVENT, BLOCK_PATH_ATTRIBUTE, BLOCK_TYPE_ATTRIBUTE, BlockChildren, BlockErrorBoundary, BlockRenderer, COUNTRIES, CURRENT_MANIFEST_VERSION, DEFAULT_COUNTRY, EVENT_LOG_TABLE, MESSAGE_TEMPLATES_TABLE, MIN_QUERY_LENGTH, WidgetZone, absoluteUrl, app_manifest_schema_default as appManifestSchema, applySeo, blockEditorMode, breadcrumbJsonLd, buildCSV, buildSeoTags, clearErrorReporters, clearEventSinks, clearMessageDefaults, clearPostalCache, clearSearchCache, computeContractHash, createSafeDataProvider, createVendorReporter, createViaCepProvider, defineApp, digitsOf, downloadCSV, emitDomainEvent, exportCSV, foldText, foldWithMap, formatCurrency, formatDate, formatDateTime, formatPostalCode, getActiveLocale, getBlockErrorVisibility, getCountry, getDefaultCurrency, getEventsWithoutMessages, getMessageDefaults, getPostalLookupProvider, highlightRanges, installGlobalErrorHandlers, isSearchIndexAvailable, isValidPostalCode, loadEventRuns, loadMessageOverrides, lookupPostalCode, maskDigitCount, maskPhone, migrateManifest, navHistoryBack, normalizePostalCode, normalizeQuery, onDomainEvent, onDomainEvents, organizationJsonLd, recordEventRun, recordNavigation, registerErrorReporter, registerEventSink, registerManifestMigration, registerMessageDefaults, renderApp, renderBlocks, renderMessageTemplate, reportAppError, resetErrorDedupe, resolveBackendEnv, resolveMessage, resolveMessages, routeModule, sampleEventPayload, scoreCandidate, searchEverything, seoOrigin, serializeManifest, serializeManifestWithHash, setBlockEditorMode, setBlockErrorVisibility, setDefaultCurrency, setPostalLookupProvider, setSearchIndexAvailable, similarity, startEventLog, unmaskPhone, useBlockPath, useManifest, useNavHistoryStore, useNavReferrer, useOnEvent, useSeo, validateManifest };
3084
+ export { APP_ERROR_EVENT, BLOCK_PATH_ATTRIBUTE, BLOCK_TYPE_ATTRIBUTE, BR_COUNTRY_CALLING_CODE, BR_MATCH_SUFFIX_LENGTH, BlockChildren, BlockErrorBoundary, BlockRenderer, COUNTRIES, CURRENT_MANIFEST_VERSION, DEFAULT_COUNTRY, DEFAULT_LEAD_PADDING, DEFAULT_TRAIL_PADDING, EMPTY_APP_CONFIG, EVENT_LOG_TABLE, MAX_QUICK_REPLY_BUTTONS, MAX_TEMPLATE_VARIABLES, MESSAGE_TEMPLATES_TABLE, MIN_QUERY_LENGTH, PROVIDER_TEMPLATE_LANGUAGE, TENANT_OWNED_MANIFEST_KEYS, WidgetZone, absoluteUrl, app_manifest_schema_default as appManifestSchema, applyAppConfigToManifest, applyNavComposition, applySeo, blockEditorMode, brNationalNumber, brPhoneSuffixKey, breadcrumbJsonLd, buildCSV, buildSeoTags, callService, callServiceForTenant, clearErrorReporters, clearEventSinks, clearMessageDefaults, clearPostalCache, clearSearchCache, compileProviderTemplate, computeContractHash, createDebug, createSafeDataProvider, createVendorReporter, createViaCepProvider, defineApp, digitsOf, downloadCSV, emitDomainEvent, exportCSV, fetchAppConfig, foldText, foldWithMap, formatCurrency, formatDate, formatDateTime, formatPostalCode, getActiveLocale, getBlockErrorVisibility, getCountry, getDefaultCurrency, getEventsWithoutMessages, getMessageDefaults, getPostalLookupProvider, highlightRanges, installGlobalErrorHandlers, invalidateAppConfig, isSearchIndexAvailable, isValidPostalCode, loadEventRuns, loadMessageOverrides, lookupPostalCode, maskDigitCount, maskPhone, migrateManifest, navHistoryBack, normalizeAppConfig, normalizeBrPhoneToE164, normalizeMessageActions, normalizePostalCode, normalizeQuery, onDomainEvent, onDomainEvents, organizationJsonLd, placementRuleApplies, positionalVariables, providerTemplateName, recordEventRun, recordNavigation, registerErrorReporter, registerEventSink, registerManifestMigration, registerMessageDefaults, renderApp, renderBlocks, renderMessageTemplate, reportAppError, resetErrorDedupe, resolveBackendEnv, resolveIntegrationSurface, resolveMessage, resolveMessages, routeModule, sampleEventPayload, scoreCandidate, searchEverything, seoOrigin, serializeManifest, serializeManifestWithHash, setBlockEditorMode, setBlockErrorVisibility, setDefaultCurrency, setPostalLookupProvider, setSearchIndexAvailable, shortHash, similarity, startEventLog, surfaceAllowsWrites, toTenantPluginBindings, unmaskPhone, useBlockPath, useDataVersion, useManifest, useNavHistoryStore, useNavReferrer, useOnDataChange, useOnEvent, useSeo, validateManifest };
2477
3085
  //# sourceMappingURL=index.js.map
2478
3086
  //# sourceMappingURL=index.js.map