@syntrologie/adapt-chatbot 2.55.1 → 2.57.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 (75) hide show
  1. package/dist/AdaptiveChatBar.d.ts +18 -0
  2. package/dist/AdaptiveChatBar.d.ts.map +1 -1
  3. package/dist/AdaptiveChatBar.interleave.test.d.ts +9 -0
  4. package/dist/AdaptiveChatBar.interleave.test.d.ts.map +1 -0
  5. package/dist/AdaptiveChatBar.js +5 -4
  6. package/dist/AdaptiveChatBarMountable.d.ts +28 -17
  7. package/dist/AdaptiveChatBarMountable.d.ts.map +1 -1
  8. package/dist/AdaptiveChatTrail.d.ts.map +1 -1
  9. package/dist/AdaptiveChatTrail.js +4 -3
  10. package/dist/AdaptiveChipsStrip.d.ts +20 -20
  11. package/dist/AdaptiveChipsStrip.js +3 -3
  12. package/dist/ChatAssistantLit.d.ts.map +1 -1
  13. package/dist/ChatAssistantLit.js +5 -4
  14. package/dist/ChatTransport.d.ts +6 -1
  15. package/dist/ChatTransport.d.ts.map +1 -1
  16. package/dist/cdn.d.ts +1 -0
  17. package/dist/cdn.d.ts.map +1 -1
  18. package/dist/chunk-4V5L7IRV.js +4711 -0
  19. package/dist/chunk-4V5L7IRV.js.map +7 -0
  20. package/dist/{chunk-EXDL3OKE.js → chunk-AIQMQZBV.js} +314 -908
  21. package/dist/{chunk-EXDL3OKE.js.map → chunk-AIQMQZBV.js.map} +4 -4
  22. package/dist/{chunk-TZA7572J.js → chunk-GN7Z5IVP.js} +2 -2
  23. package/dist/{chunk-ETDHHQS3.js → chunk-HV52LYST.js} +12 -12
  24. package/dist/{chunk-ETDHHQS3.js.map → chunk-HV52LYST.js.map} +1 -1
  25. package/dist/{chunk-LJEPZI6Y.js → chunk-LFJ54PIF.js} +27 -8
  26. package/dist/chunk-LFJ54PIF.js.map +7 -0
  27. package/dist/chunk-LY5AGXVD.js +21 -0
  28. package/dist/chunk-VIRIVP7H.js +898 -0
  29. package/dist/chunk-VIRIVP7H.js.map +7 -0
  30. package/dist/{chunk-VFWA5W56.js → chunk-XPUHW2YO.js} +44 -7
  31. package/dist/chunk-XPUHW2YO.js.map +7 -0
  32. package/dist/editor-lit.js +1 -1
  33. package/dist/element-session.d.ts +3 -0
  34. package/dist/element-session.d.ts.map +1 -0
  35. package/dist/element-session.js +9 -0
  36. package/dist/element-session.js.map +7 -0
  37. package/dist/elements/ElementInstanceStore.d.ts +15 -1
  38. package/dist/elements/ElementInstanceStore.d.ts.map +1 -1
  39. package/dist/elements/ElementSessionController.d.ts +37 -0
  40. package/dist/elements/ElementSessionController.d.ts.map +1 -0
  41. package/dist/elements/ElementSessionController.test.d.ts +2 -0
  42. package/dist/elements/ElementSessionController.test.d.ts.map +1 -0
  43. package/dist/elements/TileHandler.d.ts.map +1 -1
  44. package/dist/elements/createElementStore.d.ts +11 -0
  45. package/dist/elements/createElementStore.d.ts.map +1 -0
  46. package/dist/elements/tileLifecycleProps.d.ts +13 -0
  47. package/dist/elements/tileLifecycleProps.d.ts.map +1 -0
  48. package/dist/elements/types.d.ts +7 -0
  49. package/dist/elements/types.d.ts.map +1 -1
  50. package/dist/hostAgentController.d.ts +65 -0
  51. package/dist/hostAgentController.d.ts.map +1 -0
  52. package/dist/hostAgentController.test.d.ts +2 -0
  53. package/dist/hostAgentController.test.d.ts.map +1 -0
  54. package/dist/observer/wire.d.ts +54 -0
  55. package/dist/observer/wire.d.ts.map +1 -0
  56. package/dist/observer/wire.test.d.ts +2 -0
  57. package/dist/observer/wire.test.d.ts.map +1 -0
  58. package/dist/runtime.d.ts +3 -0
  59. package/dist/runtime.d.ts.map +1 -1
  60. package/dist/runtime.js +824 -163
  61. package/dist/runtime.js.map +4 -4
  62. package/dist/schema.d.ts +66 -66
  63. package/dist/schema.d.ts.map +1 -1
  64. package/dist/schema.js +113 -75
  65. package/dist/schema.js.map +3 -3
  66. package/dist/types.d.ts +17 -0
  67. package/dist/types.d.ts.map +1 -1
  68. package/package.json +9 -7
  69. package/dist/chunk-LJEPZI6Y.js.map +0 -7
  70. package/dist/chunk-NGV35N4T.js +0 -474
  71. package/dist/chunk-NGV35N4T.js.map +0 -7
  72. package/dist/chunk-UVKRO5ER.js +0 -13
  73. package/dist/chunk-VFWA5W56.js.map +0 -7
  74. /package/dist/{chunk-TZA7572J.js.map → chunk-GN7Z5IVP.js.map} +0 -0
  75. /package/dist/{chunk-UVKRO5ER.js.map → chunk-LY5AGXVD.js.map} +0 -0
package/dist/runtime.js CHANGED
@@ -1,34 +1,37 @@
1
1
  import {
2
- ActionHandler,
3
2
  AgUiTransport,
4
3
  ChatAssistantLitMountable,
5
- ElementInstanceStore,
6
- ItemHandler,
7
- TileHandler,
8
4
  acquireBotCheckTokenForSend,
9
5
  decodeMutationEnvelope,
10
6
  fetchMountedElements,
11
7
  renderFallbackHtml
12
- } from "./chunk-EXDL3OKE.js";
8
+ } from "./chunk-AIQMQZBV.js";
9
+ import {
10
+ ElementInstanceStore,
11
+ TileHandler,
12
+ createElementSessionController,
13
+ createTemplateWidgetResolver
14
+ } from "./chunk-VIRIVP7H.js";
13
15
  import {
14
16
  pickPersona
15
- } from "./chunk-VFWA5W56.js";
17
+ } from "./chunk-XPUHW2YO.js";
16
18
  import {
17
19
  getElementInstanceStore,
18
20
  setElementInstanceStore
19
21
  } from "./chunk-EWPPVPJ4.js";
20
- import "./chunk-LJEPZI6Y.js";
21
- import "./chunk-TZA7572J.js";
22
- import "./chunk-ETDHHQS3.js";
22
+ import "./chunk-LFJ54PIF.js";
23
+ import "./chunk-GN7Z5IVP.js";
24
+ import "./chunk-HV52LYST.js";
23
25
  import {
24
26
  DIVE_DEEPER_EVENT,
25
27
  TELEMETRY_DOM_EVENT,
26
28
  TILE_MOUNTED_EVENT,
27
29
  TILE_UNMOUNTED_EVENT,
28
30
  isCanonicalBusEvent,
31
+ reportCardStage,
29
32
  stripMountPlumbing
30
- } from "./chunk-NGV35N4T.js";
31
- import "./chunk-UVKRO5ER.js";
33
+ } from "./chunk-4V5L7IRV.js";
34
+ import "./chunk-LY5AGXVD.js";
32
35
 
33
36
  // src/ChatSession.ts
34
37
  var _userIdCounter = 0;
@@ -540,6 +543,9 @@ var ChatTransport = class {
540
543
  this._firstDeltaAt = null;
541
544
  this._runMounts = 0;
542
545
  this._runCards = 0;
546
+ /** The mount_* tool of every mount this run fired, in order — which surfaces
547
+ * the concierge chose (a carousel on one turn, separate cards on another). */
548
+ this._runMountTools = [];
543
549
  /**
544
550
  * Count of RUN_FINISHED events the server still owes us that must be
545
551
  * swallowed, not reported — one per interrupt. NOT a boolean: an
@@ -723,6 +729,7 @@ var ChatTransport = class {
723
729
  this._firstDeltaAt = null;
724
730
  this._runMounts = 0;
725
731
  this._runCards = 0;
732
+ this._runMountTools = [];
726
733
  this._pendingSuppressedFinishes = 0;
727
734
  }
728
735
  /**
@@ -984,6 +991,7 @@ var ChatTransport = class {
984
991
  this._firstDeltaAt = null;
985
992
  this._runMounts = 0;
986
993
  this._runCards = 0;
994
+ this._runMountTools = [];
987
995
  this._reportAtom("syntro_chat_message_sent", { turn, chars: text.length, via });
988
996
  void this._forwardUserMessage(text, id);
989
997
  }
@@ -1090,6 +1098,23 @@ var ChatTransport = class {
1090
1098
  if (!runtime2) return;
1091
1099
  const mutations = decodeMutationEnvelope(payload);
1092
1100
  if (mutations !== null) {
1101
+ const config = this._config;
1102
+ if (config?.onElementEnvelope) {
1103
+ void config.onElementEnvelope({
1104
+ type: "syntro.element.mutation",
1105
+ schema_version: "v0",
1106
+ mutations
1107
+ }).then(() => {
1108
+ if (this._config === config)
1109
+ runtime2.events.publish("chatbot.element_mutation_applied", {
1110
+ source: "chat-transport",
1111
+ count: mutations.length
1112
+ });
1113
+ }).catch(() => {
1114
+ console.error("[chat-transport] element mutation apply failed");
1115
+ });
1116
+ return;
1117
+ }
1093
1118
  const handler = this._config?.onElementMutation;
1094
1119
  if (handler) {
1095
1120
  try {
@@ -1308,6 +1333,7 @@ var ChatTransport = class {
1308
1333
  if (!existing && event.toolCall.name.startsWith("mount_")) {
1309
1334
  this._runMounts += 1;
1310
1335
  if (/card/i.test(event.toolCall.name)) this._runCards += 1;
1336
+ this._runMountTools.push(event.toolCall.name);
1311
1337
  }
1312
1338
  if (!existing) {
1313
1339
  this._startThinkingCycle(toolNarrationVariants(event.toolCall.name));
@@ -1405,7 +1431,8 @@ var ChatTransport = class {
1405
1431
  ttft_ms: (this._firstDeltaAt ?? end) - this._runStartedAt,
1406
1432
  total_ms: end - this._runStartedAt,
1407
1433
  mounts: this._runMounts,
1408
- cards: this._runCards
1434
+ cards: this._runCards,
1435
+ mount_tools: [...this._runMountTools]
1409
1436
  });
1410
1437
  }
1411
1438
  this._runStartedAt = 0;
@@ -1439,6 +1466,38 @@ function findSmartCanvas() {
1439
1466
  return find(document);
1440
1467
  }
1441
1468
 
1469
+ // src/observer/bus-translate.ts
1470
+ function eventTypeFromName(name) {
1471
+ switch (name) {
1472
+ case "ui.click":
1473
+ return "click";
1474
+ case "ui.submit":
1475
+ return "submit";
1476
+ case "ui.change":
1477
+ return "change";
1478
+ default:
1479
+ return void 0;
1480
+ }
1481
+ }
1482
+ function busEventToRawEvent(e) {
1483
+ if (!e?.name) return null;
1484
+ const ts = typeof e.ts === "number" ? e.ts : Date.now();
1485
+ if (e.source === "posthog") {
1486
+ const original = e.props?.originalEvent;
1487
+ if (typeof original !== "string" || original.length === 0) return null;
1488
+ const src = e.props ?? {};
1489
+ const properties = { ...src };
1490
+ if (Array.isArray(src.elements)) properties.$elements = src.elements;
1491
+ if (typeof src.tagName === "string") properties.$element_tag_name = src.tagName;
1492
+ if (typeof src.pathname === "string") properties.$pathname = src.pathname;
1493
+ if (typeof src.url === "string") properties.$current_url = src.url;
1494
+ const eventType = eventTypeFromName(e.name);
1495
+ if (eventType) properties.$event_type = eventType;
1496
+ return { event: original, timestamp: ts, properties };
1497
+ }
1498
+ return { event: e.name, timestamp: ts, properties: { ...e.props ?? {} } };
1499
+ }
1500
+
1442
1501
  // src/observer/allowlist.ts
1443
1502
  var MAX_TEXT_LEN = 200;
1444
1503
  var SIGNIFICANT_CLICK_TAGS = /* @__PURE__ */ new Set(["button", "a"]);
@@ -1941,36 +2000,74 @@ function _makeHandle(owner) {
1941
2000
  };
1942
2001
  }
1943
2002
 
1944
- // src/observer/bus-translate.ts
1945
- function eventTypeFromName(name) {
1946
- switch (name) {
1947
- case "ui.click":
1948
- return "click";
1949
- case "ui.submit":
1950
- return "submit";
1951
- case "ui.change":
1952
- return "change";
1953
- default:
1954
- return void 0;
1955
- }
2003
+ // src/observer/wire.ts
2004
+ var PAGE_LEVEL_KINDS = /* @__PURE__ */ new Set(["view", "scroll", "idle", "hesitation", "hover", "nav"]);
2005
+ function buildObserverEnrich(runtimeRef) {
2006
+ return (ev, raw) => {
2007
+ const extract = runtimeRef.getProductIdentity;
2008
+ if (typeof extract !== "function") return ev;
2009
+ if (ev.kind === "click") {
2010
+ const chain = raw.properties.$elements;
2011
+ if (!Array.isArray(chain)) return ev;
2012
+ const id = extract(chain);
2013
+ if (!id) return ev;
2014
+ return {
2015
+ ...ev,
2016
+ product: {
2017
+ handle: id.productHandle,
2018
+ url: id.productUrl,
2019
+ ...id.productTitle ? { title: id.productTitle } : {}
2020
+ }
2021
+ };
2022
+ }
2023
+ if (!ev.product && PAGE_LEVEL_KINDS.has(ev.kind) && typeof location !== "undefined") {
2024
+ const id = extract([{ href: location.pathname }]);
2025
+ if (!id) return ev;
2026
+ return {
2027
+ ...ev,
2028
+ product: {
2029
+ handle: id.productHandle,
2030
+ url: id.productUrl,
2031
+ ...id.productTitle ? { title: id.productTitle } : {}
2032
+ }
2033
+ };
2034
+ }
2035
+ return ev;
2036
+ };
1956
2037
  }
1957
- function busEventToRawEvent(e) {
1958
- if (!e?.name) return null;
1959
- const ts = typeof e.ts === "number" ? e.ts : Date.now();
1960
- if (e.source === "posthog") {
1961
- const original = e.props?.originalEvent;
1962
- if (typeof original !== "string" || original.length === 0) return null;
1963
- const src = e.props ?? {};
1964
- const properties = { ...src };
1965
- if (Array.isArray(src.elements)) properties.$elements = src.elements;
1966
- if (typeof src.tagName === "string") properties.$element_tag_name = src.tagName;
1967
- if (typeof src.pathname === "string") properties.$pathname = src.pathname;
1968
- if (typeof src.url === "string") properties.$current_url = src.url;
1969
- const eventType = eventTypeFromName(e.name);
1970
- if (eventType) properties.$event_type = eventType;
1971
- return { event: original, timestamp: ts, properties };
1972
- }
1973
- return { event: e.name, timestamp: ts, properties: { ...e.props ?? {} } };
2038
+ function wireObserver(runtime2) {
2039
+ if (typeof window === "undefined" || !runtime2?.events?.subscribe) return () => {
2040
+ };
2041
+ const trimmed = (runtime2.backendUrl ?? "").replace(/\/$/, "");
2042
+ const url = trimmed ? `${trimmed}/api/adaptive/observation` : "/api/adaptive/observation";
2043
+ const runtimeId = (method) => {
2044
+ try {
2045
+ const value = runtime2.telemetry?.[method]?.();
2046
+ return typeof value === "string" && value.length > 0 ? value : null;
2047
+ } catch {
2048
+ return null;
2049
+ }
2050
+ };
2051
+ const token = () => typeof runtime2.token === "string" ? runtime2.token : "";
2052
+ const handle = startObserver({
2053
+ url,
2054
+ token,
2055
+ getDistinctId: () => runtimeId("getDistinctId"),
2056
+ getSessionId: () => runtimeId("getSessionId"),
2057
+ enrich: buildObserverEnrich(runtime2)
2058
+ });
2059
+ window.__syntroObserverStats = () => handle.stats();
2060
+ const unsubscribe = runtime2.events.subscribe({}, (event) => {
2061
+ const raw = busEventToRawEvent(event);
2062
+ if (raw) handle.ingest(raw);
2063
+ });
2064
+ let stopped = false;
2065
+ return () => {
2066
+ if (stopped) return;
2067
+ stopped = true;
2068
+ unsubscribe();
2069
+ handle.stop();
2070
+ };
1974
2071
  }
1975
2072
 
1976
2073
  // src/support-chat/registry.ts
@@ -3066,106 +3163,41 @@ function applyPlaceholder(bar, cfg2) {
3066
3163
  if (typeof cfg2.maximized === "boolean") {
3067
3164
  bar.maximized = cfg2.maximized;
3068
3165
  }
3166
+ if (cfg2.chatControl !== void 0) bar.chatControl = cfg2.chatControl;
3167
+ if (typeof cfg2.interleaved === "boolean") bar.interleaved = cfg2.interleaved;
3069
3168
  }
3070
- var _elementStore = null;
3071
- var _templateWidgetMap = /* @__PURE__ */ new Map();
3072
- var SERVER_MOUNTED_TILE_WIDGETS = ["syntro-product-reco-carousel"];
3073
- function resolveTileWidget(templateId) {
3074
- return _templateWidgetMap.get(templateId);
3075
- }
3076
- function refreshTemplateWidgetMap(uiTemplates) {
3077
- _templateWidgetMap.clear();
3078
- for (const tag of SERVER_MOUNTED_TILE_WIDGETS) {
3079
- _templateWidgetMap.set(tag, tag);
3080
- }
3081
- if (!uiTemplates) return;
3082
- const tiles = uiTemplates.tiles;
3083
- if (!Array.isArray(tiles)) return;
3084
- for (const t of tiles) {
3085
- const id = typeof t?.id === "string" ? t.id : null;
3086
- const widget = typeof t?.widget === "string" ? t.widget : null;
3087
- if (id && widget) _templateWidgetMap.set(id, widget);
3088
- }
3089
- }
3090
- function getOrCreateElementStore(runtime2) {
3091
- if (_elementStore) return _elementStore;
3092
- _elementStore = new ElementInstanceStore({
3093
- actions: runtime2.actions,
3094
- // Pass the runtime's event bus so ItemHandler can broadcast
3095
- // `element.compositional_append` / `_patch` / `_remove` events
3096
- // to container widgets (chips strip, FAQ accordion, nav tips).
3097
- events: {
3098
- publish: runtime2.events.publish.bind(runtime2.events),
3099
- subscribe: runtime2.events.subscribe?.bind(runtime2.events)
3100
- },
3101
- handlers: [new TileHandler(resolveTileWidget), new ActionHandler(), new ItemHandler()],
3102
- resolveTileWidget
3103
- });
3104
- setElementInstanceStore(_elementStore);
3105
- return _elementStore;
3169
+ var _elementSession = null;
3170
+ var _pendingTemplates;
3171
+ function refreshTemplateWidgetMap(templates) {
3172
+ _pendingTemplates = templates;
3173
+ _elementSession?.configureTemplates(templates);
3174
+ }
3175
+ function getOrCreateElementSession(runtime2) {
3176
+ if (_elementSession) return _elementSession;
3177
+ _elementSession = createElementSessionController({ ...runtime2, templates: _pendingTemplates });
3178
+ setElementInstanceStore(_elementSession.store);
3179
+ return _elementSession;
3106
3180
  }
3107
- var _hydrationStarted = false;
3108
3181
  function hydrateOnce(runtime2, backendUrl) {
3109
- if (_hydrationStarted) return;
3110
- _hydrationStarted = true;
3111
- const store = getOrCreateElementStore(runtime2);
3182
+ const session = getOrCreateElementSession(runtime2);
3112
3183
  const trimmed = backendUrl.replace(/\/$/, "");
3113
3184
  const endpoint = trimmed ? `${trimmed}/api/adaptive/mounted_elements` : "/api/adaptive/mounted_elements";
3114
- fetchMountedElements({ endpoint, token: runtime2.token }).then((response) => {
3115
- if (!response) return;
3116
- void store.hydrate(response.mounted_elements);
3117
- });
3118
- }
3119
- var PAGE_LEVEL_KINDS = /* @__PURE__ */ new Set(["view", "scroll", "idle", "hesitation", "hover", "nav"]);
3120
- function buildObserverEnrich(runtimeRef) {
3121
- return (ev, raw) => {
3122
- const extract = runtimeRef.getProductIdentity;
3123
- if (typeof extract !== "function") return ev;
3124
- if (ev.kind === "click") {
3125
- const chain = raw.properties.$elements;
3126
- if (!Array.isArray(chain)) return ev;
3127
- const id = extract(chain);
3128
- if (!id) return ev;
3129
- return {
3130
- ...ev,
3131
- product: {
3132
- handle: id.productHandle,
3133
- url: id.productUrl,
3134
- ...id.productTitle ? { title: id.productTitle } : {}
3135
- }
3136
- };
3137
- }
3138
- if (!ev.product && PAGE_LEVEL_KINDS.has(ev.kind) && typeof location !== "undefined") {
3139
- const id = extract([{ href: location.pathname }]);
3140
- if (!id) return ev;
3141
- return {
3142
- ...ev,
3143
- product: {
3144
- handle: id.productHandle,
3145
- url: id.productUrl,
3146
- ...id.productTitle ? { title: id.productTitle } : {}
3147
- }
3148
- };
3149
- }
3150
- return ev;
3151
- };
3185
+ session.hydrateOnce(() => fetchMountedElements({ endpoint, token: runtime2.token }));
3152
3186
  }
3153
- var _nudgeSurfaceWired = false;
3154
- var _nudgeListener = null;
3187
+ var _removeNudgeListener = null;
3155
3188
  function wireNudgeSurface(runtime2, backendUrl) {
3156
- if (_nudgeSurfaceWired || typeof document === "undefined") return;
3157
- _nudgeSurfaceWired = true;
3158
- _nudgeListener = (e) => {
3189
+ if (_removeNudgeListener || typeof document === "undefined") return;
3190
+ const listener = (e) => {
3159
3191
  try {
3160
3192
  const detail = e.detail;
3161
3193
  const isIntent = detail?.phase === "intent";
3162
3194
  if (!isIntent) {
3163
- const store = getOrCreateElementStore(runtime2);
3195
+ const session = getOrCreateElementSession(runtime2);
3164
3196
  const trimmed = backendUrl.replace(/\/$/, "");
3165
3197
  const endpoint = trimmed ? `${trimmed}/api/adaptive/mounted_elements` : "/api/adaptive/mounted_elements";
3166
3198
  void fetchMountedElements({ endpoint, token: runtime2.token }).then((response) => {
3167
3199
  if (!response) return;
3168
- void store.hydrate(response.mounted_elements);
3200
+ void session.hydrateLegacy(response.mounted_elements, response.session_expires_at);
3169
3201
  });
3170
3202
  }
3171
3203
  if (detail?.suppressToast === true) return;
@@ -3181,7 +3213,11 @@ function wireNudgeSurface(runtime2, backendUrl) {
3181
3213
  console.warn("[adaptive-chatbot] nudge surface reaction failed", err);
3182
3214
  }
3183
3215
  };
3184
- document.addEventListener("syntro:nudge", _nudgeListener);
3216
+ document.addEventListener("syntro:nudge", listener);
3217
+ _removeNudgeListener = () => {
3218
+ document.removeEventListener("syntro:nudge", listener);
3219
+ _removeNudgeListener = null;
3220
+ };
3185
3221
  }
3186
3222
  function configureTransportIfPossible(cfg2) {
3187
3223
  if (!cfg2.runtime) return;
@@ -3192,9 +3228,7 @@ function configureTransportIfPossible(cfg2) {
3192
3228
  const forwardedProps = elementsActive ? { elementsEnabled: true, uiTemplates: cfg2.uiTemplates } : void 0;
3193
3229
  if (elementsActive) publishTemplatesIfPossible(backendUrl, cfg2.uiTemplates, cfg2.runtime?.token);
3194
3230
  const runtime2 = cfg2.runtime;
3195
- const onElementMutation = elementsActive ? (mutations) => {
3196
- void getOrCreateElementStore(runtime2).apply(mutations);
3197
- } : void 0;
3231
+ const onElementEnvelope = elementsActive ? (envelope) => getOrCreateElementSession(runtime2).applyLegacyEnvelope(envelope) : void 0;
3198
3232
  if (elementsActive) {
3199
3233
  hydrateOnce(runtime2, backendUrl);
3200
3234
  }
@@ -3203,7 +3237,7 @@ function configureTransportIfPossible(cfg2) {
3203
3237
  ...cfg2,
3204
3238
  backendUrl,
3205
3239
  forwardedProps,
3206
- onElementMutation,
3240
+ onElementEnvelope,
3207
3241
  activeLidSlot: cfg2._syntroSlotName,
3208
3242
  personaId: persona?.id,
3209
3243
  personaName: persona?.name,
@@ -3250,46 +3284,19 @@ function wireDiveDeeperListener() {
3250
3284
  }
3251
3285
  var _observerStarted = false;
3252
3286
  var _observerRuntime = null;
3253
- var _observerHandle = null;
3254
- var _observerBusUnsubscribe = null;
3287
+ var _observerStop = null;
3255
3288
  function startObserverIfPossible(cfg2) {
3256
3289
  if (typeof window === "undefined") return;
3257
3290
  const runtimeRef = cfg2.runtime;
3258
3291
  if (!runtimeRef?.events?.subscribe) return;
3259
3292
  if (_observerStarted && _observerRuntime === runtimeRef) return;
3260
3293
  if (_observerStarted) {
3261
- _observerBusUnsubscribe?.();
3262
- _observerHandle?.stop();
3263
- _observerBusUnsubscribe = null;
3264
- _observerHandle = null;
3294
+ _observerStop?.();
3295
+ _observerStop = null;
3265
3296
  _observerStarted = false;
3266
3297
  }
3267
- const trimmed = (runtimeRef.backendUrl ?? "").replace(/\/$/, "");
3268
- const url = trimmed ? `${trimmed}/api/adaptive/observation` : "/api/adaptive/observation";
3269
- const runtimeId = (method) => {
3270
- try {
3271
- const v = runtimeRef.telemetry?.[method]?.();
3272
- return typeof v === "string" && v.length > 0 ? v : null;
3273
- } catch {
3274
- return null;
3275
- }
3276
- };
3277
- const getDistinctId = () => runtimeId("getDistinctId");
3278
- const getSessionId = () => runtimeId("getSessionId");
3279
- const token = () => {
3280
- const t = runtimeRef.token;
3281
- return typeof t === "string" ? t : "";
3282
- };
3283
- const enrich = buildObserverEnrich(runtimeRef);
3284
- const handle = startObserver({ url, token, getDistinctId, getSessionId, enrich });
3285
- window.__syntroObserverStats = () => handle.stats();
3286
- const unsubscribe = runtimeRef.events.subscribe({}, (evt) => {
3287
- const raw = busEventToRawEvent(evt);
3288
- if (raw) handle.ingest(raw);
3289
- });
3298
+ _observerStop = wireObserver(runtimeRef);
3290
3299
  _observerRuntime = runtimeRef;
3291
- _observerHandle = handle;
3292
- _observerBusUnsubscribe = unsubscribe;
3293
3300
  _observerStarted = true;
3294
3301
  }
3295
3302
  function wireListeners(bar, state) {
@@ -3358,7 +3365,7 @@ var AdaptiveChatBarMountable = {
3358
3365
  });
3359
3366
  const runtimeEvents = cfg2.runtime ? cfg2.runtime.events : void 0;
3360
3367
  const refreshReceipts = () => {
3361
- const store = _elementStore;
3368
+ const store = _elementSession?.store;
3362
3369
  if (store) {
3363
3370
  bar.tileReceipts = store.listTileReceipts();
3364
3371
  bar.inlineWidgets = store.listInlineWidgets();
@@ -3471,6 +3478,658 @@ var AdaptiveChipsStripMountable = {
3471
3478
  }
3472
3479
  };
3473
3480
 
3481
+ // src/hostAgentController.ts
3482
+ var READ_TOOL = "read_brand_view";
3483
+ var MISSING_RESULT_MS = 8e3;
3484
+ var PENDING_MAX_MS = 12e4;
3485
+ var AGENT_TURN_KEY = "syntro/agent-turn";
3486
+ var CARD_HEARTBEAT_PREFIX = "syntro:mcp-card:";
3487
+ var heartbeatPrefix = (sessionRef) => `${CARD_HEARTBEAT_PREFIX}${sessionRef}:`;
3488
+ var CARD_HEARTBEAT_INTERVAL_MS = 2e3;
3489
+ var CARD_SIBLING_WINDOW_MS = 6e3;
3490
+ var CARD_HEARTBEAT_STALE_MS = 6e4;
3491
+ var CARD_SUPERSEDED = "canvas:superseded";
3492
+ var DOCKED_PREFIX = "syntro:mcp-docked:";
3493
+ var DOCKED_FOR_MS = 24 * 60 * 60 * 1e3;
3494
+ function coarsePointer() {
3495
+ try {
3496
+ return typeof matchMedia === "function" && matchMedia("(pointer: coarse)").matches;
3497
+ } catch {
3498
+ return false;
3499
+ }
3500
+ }
3501
+ function cardStorage() {
3502
+ try {
3503
+ return globalThis.localStorage ?? void 0;
3504
+ } catch {
3505
+ return void 0;
3506
+ }
3507
+ }
3508
+ var STAR_TOOL = "star_card_tile";
3509
+ function decodeStars(value) {
3510
+ if (!record(value)) return void 0;
3511
+ const out = {};
3512
+ for (const [instanceId, star] of Object.entries(value)) {
3513
+ if (nonempty(instanceId) && record(star) && typeof star.starred === "boolean" && typeof star.updatedAt === "string")
3514
+ out[instanceId] = {
3515
+ starred: star.starred,
3516
+ updatedAt: star.updatedAt,
3517
+ ...typeof star.changedAt === "string" ? { changedAt: star.changedAt } : {}
3518
+ };
3519
+ }
3520
+ return out;
3521
+ }
3522
+ function mergeStars(held, incoming) {
3523
+ const out = { ...held ?? {} };
3524
+ for (const [instanceId, next] of Object.entries(incoming)) {
3525
+ const current = out[instanceId];
3526
+ const keepCurrent = current?.changedAt !== void 0 && (next.changedAt === void 0 || current.changedAt > next.changedAt);
3527
+ if (!keepCurrent) out[instanceId] = next;
3528
+ }
3529
+ return out;
3530
+ }
3531
+ var interactionOwners = /* @__PURE__ */ new WeakMap();
3532
+ function hostUiSubscription(value) {
3533
+ return record(value) && typeof value.subscribeHostUi === "function";
3534
+ }
3535
+ function validSession(value) {
3536
+ if (!record(value) || !nonempty(value.session_ref) || !(value.session_expires_at === null || value.session_expires_at === void 0 || typeof value.session_expires_at === "string" && Number.isFinite(Date.parse(value.session_expires_at))) || !(value.verified_templates === null || value.verified_templates === void 0 || record(value.verified_templates)))
3537
+ return false;
3538
+ const tiles = value.verified_templates?.tiles;
3539
+ return tiles === void 0 || tiles === null || Array.isArray(tiles) && tiles.every((tile) => record(tile) && nonempty(tile.id) && nonempty(tile.widget));
3540
+ }
3541
+ function record(value) {
3542
+ return value !== null && typeof value === "object" && !Array.isArray(value);
3543
+ }
3544
+ function nonempty(value) {
3545
+ return typeof value === "string" && value.trim().length > 0;
3546
+ }
3547
+ function expiry(value) {
3548
+ return value === null || typeof value === "string" && Number.isFinite(Date.parse(value));
3549
+ }
3550
+ function decodeAgentTurn(result) {
3551
+ if (!record(result) || !record(result._meta)) return;
3552
+ const payload = result._meta["syntro/agent-turn"];
3553
+ if (!record(payload)) return;
3554
+ if (payload.version !== 1 && payload.version !== 2) return;
3555
+ const sessionRef = payload.sessionRef ?? payload.sessionId;
3556
+ if (!nonempty(sessionRef) || !nonempty(payload.turnId) || !record(payload.uiTemplates) || !record(payload.snapshot))
3557
+ return;
3558
+ const snapshot = payload.snapshot;
3559
+ if (snapshot.schema_version !== "v0" || !Array.isArray(snapshot.mounted_elements) || !expiry(snapshot.session_expires_at))
3560
+ return;
3561
+ const sessionExpiresAt = payload.sessionExpiresAt ?? snapshot.session_expires_at;
3562
+ if (!expiry(sessionExpiresAt)) return;
3563
+ const tiles = payload.uiTemplates.tiles;
3564
+ if (tiles !== void 0 && (!Array.isArray(tiles) || tiles.some(
3565
+ (template) => !record(template) || !nonempty(template.id) || !nonempty(template.widget)
3566
+ )))
3567
+ return;
3568
+ const templates = createTemplateWidgetResolver();
3569
+ templates.refresh(payload.uiTemplates);
3570
+ for (const candidate of snapshot.mounted_elements) {
3571
+ if (record(candidate) && candidate.element_type === "tile" && candidate.state === "active" && (!nonempty(candidate.template_id) || !templates.resolve(candidate.template_id)))
3572
+ return;
3573
+ }
3574
+ const session = {
3575
+ session_ref: sessionRef,
3576
+ session_expires_at: sessionExpiresAt,
3577
+ verified_templates: payload.uiTemplates
3578
+ };
3579
+ const frame = {
3580
+ schema_version: "v1",
3581
+ kind: "snapshot",
3582
+ session_ref: sessionRef,
3583
+ // The turn id IS the dedup id: the runtime commits one snapshot per
3584
+ // accepted request and names it with that request's id.
3585
+ result_id: payload.turnId,
3586
+ session_expires_at: snapshot.session_expires_at,
3587
+ mounted_elements: snapshot.mounted_elements
3588
+ };
3589
+ const issuedAt = typeof payload.issuedAt === "number" && Number.isFinite(payload.issuedAt) ? payload.issuedAt : void 0;
3590
+ const stars = decodeStars(payload.stars);
3591
+ return {
3592
+ session,
3593
+ frame,
3594
+ ...issuedAt !== void 0 ? { issuedAt } : {},
3595
+ ...stars ? { stars } : {}
3596
+ };
3597
+ }
3598
+ function activateHostAgentController(runtime2, bridge, hostUi, options = {}) {
3599
+ let controller = createElementSessionController(runtime2);
3600
+ let disposed = false;
3601
+ let pending = Promise.resolve();
3602
+ let session;
3603
+ let generation = 0;
3604
+ const retiredScopes = /* @__PURE__ */ new Set();
3605
+ let stopObserver;
3606
+ let readRequested = false;
3607
+ let applied = 0;
3608
+ const cardId = `${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 10)}`;
3609
+ let heartbeatKey;
3610
+ let heartbeatTimer;
3611
+ let popOutDecided = false;
3612
+ let born;
3613
+ let showingEarlyRead = false;
3614
+ let answerIssuedAt;
3615
+ let heartbeatSession;
3616
+ let folded = false;
3617
+ function hostSurface() {
3618
+ try {
3619
+ return runtime2.context?.get().surface ?? {};
3620
+ } catch {
3621
+ return {};
3622
+ }
3623
+ }
3624
+ function readHeartbeat(key) {
3625
+ try {
3626
+ const raw = cardStorage()?.getItem(key);
3627
+ if (!raw) return;
3628
+ const parsed = JSON.parse(raw);
3629
+ if (!record(parsed) || !nonempty(parsed.id) || typeof parsed.ts !== "number") return;
3630
+ return {
3631
+ id: parsed.id,
3632
+ ts: parsed.ts,
3633
+ mode: typeof parsed.mode === "string" ? parsed.mode : void 0,
3634
+ born: typeof parsed.born === "number" ? parsed.born : void 0
3635
+ };
3636
+ } catch {
3637
+ return;
3638
+ }
3639
+ }
3640
+ function writeHeartbeat() {
3641
+ if (!heartbeatKey) return;
3642
+ try {
3643
+ cardStorage()?.setItem(
3644
+ heartbeatKey,
3645
+ JSON.stringify({ id: cardId, ts: Date.now(), mode: hostSurface().displayMode, born })
3646
+ );
3647
+ } catch {
3648
+ }
3649
+ considerFold();
3650
+ }
3651
+ function considerFold() {
3652
+ if (folded || disposed || !heartbeatSession || born === void 0) return;
3653
+ const surface = hostSurface();
3654
+ if (surface.type !== "mcp-app" || surface.displayMode !== "inline") return;
3655
+ if (!surface.availableDisplayModes || popsOut(surface, heartbeatSession)) return;
3656
+ const mine = born;
3657
+ if (!freshSiblings(heartbeatSession).some((beat) => (beat.born ?? 0) > mine)) return;
3658
+ folded = true;
3659
+ runtime2.events.publish(CARD_SUPERSEDED, { superseded: true });
3660
+ }
3661
+ function pruneHeartbeats() {
3662
+ try {
3663
+ const store = cardStorage();
3664
+ if (!store) return;
3665
+ const stale = [];
3666
+ for (let index = 0; index < store.length; index++) {
3667
+ const key = store.key(index);
3668
+ if (!key?.startsWith(CARD_HEARTBEAT_PREFIX) || key === heartbeatKey) continue;
3669
+ const beat = readHeartbeat(key);
3670
+ if (!beat || Date.now() - beat.ts > CARD_HEARTBEAT_STALE_MS) stale.push(key);
3671
+ }
3672
+ for (const key of stale) store.removeItem(key);
3673
+ } catch {
3674
+ }
3675
+ }
3676
+ const releaseHeartbeat = () => stopHeartbeat();
3677
+ const onVisibilityChange = () => {
3678
+ if (!heartbeatKey) return;
3679
+ if (document.visibilityState === "hidden") {
3680
+ if (heartbeatTimer !== void 0) {
3681
+ clearInterval(heartbeatTimer);
3682
+ heartbeatTimer = void 0;
3683
+ }
3684
+ return;
3685
+ }
3686
+ if (heartbeatTimer !== void 0) return;
3687
+ writeHeartbeat();
3688
+ heartbeatTimer = setInterval(writeHeartbeat, CARD_HEARTBEAT_INTERVAL_MS);
3689
+ };
3690
+ let unsubscribeModeWatch;
3691
+ function startHeartbeat(sessionRef) {
3692
+ const key = heartbeatPrefix(sessionRef) + cardId;
3693
+ if (heartbeatKey === key) return;
3694
+ if (heartbeatKey) stopHeartbeat();
3695
+ heartbeatKey = key;
3696
+ heartbeatSession = sessionRef;
3697
+ pruneHeartbeats();
3698
+ writeHeartbeat();
3699
+ heartbeatTimer = setInterval(writeHeartbeat, CARD_HEARTBEAT_INTERVAL_MS);
3700
+ let heartbeatMode = hostSurface().displayMode;
3701
+ try {
3702
+ unsubscribeModeWatch = runtime2.context?.subscribe?.(() => {
3703
+ const mode = hostSurface().displayMode;
3704
+ if (mode === heartbeatMode) return;
3705
+ if (heartbeatMode === "pip" && mode === "inline" && heartbeatSession)
3706
+ rememberDocked(heartbeatSession);
3707
+ heartbeatMode = mode;
3708
+ writeHeartbeat();
3709
+ });
3710
+ } catch {
3711
+ }
3712
+ if (typeof window !== "undefined") {
3713
+ window.addEventListener("pagehide", releaseHeartbeat);
3714
+ window.addEventListener("beforeunload", releaseHeartbeat);
3715
+ }
3716
+ if (typeof document !== "undefined")
3717
+ document.addEventListener("visibilitychange", onVisibilityChange);
3718
+ }
3719
+ function stopHeartbeat() {
3720
+ if (heartbeatTimer !== void 0) {
3721
+ clearInterval(heartbeatTimer);
3722
+ heartbeatTimer = void 0;
3723
+ }
3724
+ unsubscribeModeWatch?.();
3725
+ unsubscribeModeWatch = void 0;
3726
+ const key = heartbeatKey;
3727
+ heartbeatKey = void 0;
3728
+ if (typeof window !== "undefined") {
3729
+ window.removeEventListener("pagehide", releaseHeartbeat);
3730
+ window.removeEventListener("beforeunload", releaseHeartbeat);
3731
+ }
3732
+ if (typeof document !== "undefined")
3733
+ document.removeEventListener("visibilitychange", onVisibilityChange);
3734
+ if (!key) return;
3735
+ try {
3736
+ cardStorage()?.removeItem(key);
3737
+ } catch {
3738
+ }
3739
+ }
3740
+ function freshSiblings(sessionRef) {
3741
+ const siblings = [];
3742
+ try {
3743
+ const store = cardStorage();
3744
+ if (!store) return siblings;
3745
+ const prefix = heartbeatPrefix(sessionRef);
3746
+ for (let index = 0; index < store.length; index++) {
3747
+ const key = store.key(index);
3748
+ if (!key?.startsWith(prefix)) continue;
3749
+ const beat = readHeartbeat(key);
3750
+ if (!beat || beat.id === cardId) continue;
3751
+ if (Date.now() - beat.ts > CARD_SIBLING_WINDOW_MS) continue;
3752
+ siblings.push(beat);
3753
+ }
3754
+ } catch {
3755
+ return siblings;
3756
+ }
3757
+ return siblings;
3758
+ }
3759
+ function considerPopOut(sessionRef) {
3760
+ if (popOutDecided) return;
3761
+ popOutDecided = true;
3762
+ const siblings = freshSiblings(sessionRef);
3763
+ if (siblings.some((beat) => beat.mode !== "inline")) return;
3764
+ const surface = hostSurface();
3765
+ if (surface.type !== "mcp-app" || surface.displayMode !== "inline") return;
3766
+ if (!popsOut(surface, sessionRef)) return;
3767
+ runtime2.events.publish("canvas:request-display-mode", { mode: "pip" });
3768
+ }
3769
+ function popsOut(surface, sessionRef) {
3770
+ if (!surface.availableDisplayModes?.includes("pip")) return false;
3771
+ if (coarsePointer()) return false;
3772
+ return !shopperDocked(sessionRef);
3773
+ }
3774
+ function shopperDocked(sessionRef) {
3775
+ try {
3776
+ const raw = cardStorage()?.getItem(DOCKED_PREFIX + sessionRef);
3777
+ const at = raw ? Number(raw) : Number.NaN;
3778
+ return Number.isFinite(at) && Date.now() - at < DOCKED_FOR_MS;
3779
+ } catch {
3780
+ return false;
3781
+ }
3782
+ }
3783
+ function rememberDocked(sessionRef) {
3784
+ try {
3785
+ cardStorage()?.setItem(DOCKED_PREFIX + sessionRef, String(Date.now()));
3786
+ } catch {
3787
+ }
3788
+ }
3789
+ let interactiveState;
3790
+ function interactive(value) {
3791
+ if (disposed || interactiveState === value) return;
3792
+ interactiveState = value;
3793
+ hostUi?.setInteractive?.(value);
3794
+ runtime2.events.publish("host.ui_state", { interactive: value });
3795
+ }
3796
+ function queue(operation) {
3797
+ const expected = generation;
3798
+ pending = pending.then(async () => {
3799
+ if (!disposed && expected === generation) await operation();
3800
+ }).catch(() => {
3801
+ if (!disposed && expected === generation) {
3802
+ interactive(false);
3803
+ runtime2.events.publish("host.ui_error", { reason: "projection_failed" });
3804
+ }
3805
+ });
3806
+ }
3807
+ function scheduleHostRead(sessionRef, why = "reload") {
3808
+ if (disposed || readRequested || typeof bridge.callServerTool !== "function") return;
3809
+ const call = bridge.callServerTool.bind(bridge);
3810
+ readRequested = true;
3811
+ const expected = generation;
3812
+ const readAgain = () => {
3813
+ if (disposed || why !== "turn_started") return;
3814
+ readRequested = false;
3815
+ if (session) scheduleHostRead(session.session_ref);
3816
+ };
3817
+ void Promise.resolve().then(() => {
3818
+ if (disposed) return;
3819
+ if (expected !== generation) return readAgain();
3820
+ queue(async () => {
3821
+ const appliedAtIssue = applied;
3822
+ reportCardStage("read_brand_view_called", { why });
3823
+ void call({ name: READ_TOOL, arguments: { sessionRef } }).then((result) => {
3824
+ reportReadAnswer(why, result);
3825
+ if (disposed) return;
3826
+ if (expected !== generation) return readAgain();
3827
+ if (applied !== appliedAtIssue) {
3828
+ reportCardStage("tool_result_rejected", { reason: "stale" });
3829
+ return;
3830
+ }
3831
+ return consumeToolResult(result, { earlyRead: why === "turn_started" });
3832
+ }).catch((error) => {
3833
+ reportCardStage("read_brand_view_answered", {
3834
+ why,
3835
+ ok: false,
3836
+ withAgentTurn: false,
3837
+ // The host's own words for the refusal: a read that never
3838
+ // reaches the connector leaves no other record of why.
3839
+ reason: readFailureReason(error instanceof Error ? error.message : String(error))
3840
+ });
3841
+ if (why === "turn_started") return readAgain();
3842
+ if (!disposed && expected === generation)
3843
+ runtime2.events.publish("host.ui_error", { reason: "refresh_required" });
3844
+ });
3845
+ });
3846
+ });
3847
+ }
3848
+ function reportReadAnswer(why, result) {
3849
+ const failed = record(result) && result.isError === true;
3850
+ const text = failed ? firstText(result) : void 0;
3851
+ reportCardStage("read_brand_view_answered", {
3852
+ why,
3853
+ ok: !failed,
3854
+ withAgentTurn: decodeAgentTurn(result) !== void 0,
3855
+ ...text ? { reason: readFailureReason(text) } : {}
3856
+ });
3857
+ }
3858
+ const missingAfterMs = options.missingResultMs ?? MISSING_RESULT_MS;
3859
+ let missingTimer = setTimeout(
3860
+ reportMissingResult,
3861
+ missingAfterMs
3862
+ );
3863
+ function standDown() {
3864
+ if (missingTimer !== void 0) clearTimeout(missingTimer);
3865
+ missingTimer = void 0;
3866
+ }
3867
+ function reportMissingResult() {
3868
+ missingTimer = void 0;
3869
+ if (disposed) return;
3870
+ reportCardStage("tool_result_missing", { afterMs: missingAfterMs });
3871
+ }
3872
+ function acceptHostUi2(payload) {
3873
+ if (disposed || !record(payload)) return;
3874
+ if (payload.kind === "session") {
3875
+ if (!validSession(payload.session)) {
3876
+ interactive(false);
3877
+ runtime2.events.publish("host.ui_error", { reason: "invalid_session" });
3878
+ return;
3879
+ }
3880
+ const next = payload.session;
3881
+ standDown();
3882
+ if (retiredScopes.has(next.session_ref)) return;
3883
+ if (session && next.session_ref === session.session_ref) {
3884
+ session = next;
3885
+ controller.configureTemplates(next.verified_templates ?? void 0);
3886
+ interactive(true);
3887
+ return;
3888
+ }
3889
+ if (session) retiredScopes.add(session.session_ref);
3890
+ generation++;
3891
+ const shutdown = controller.dispose();
3892
+ controller = createElementSessionController({
3893
+ ...runtime2,
3894
+ templates: next.verified_templates ?? void 0,
3895
+ hostScope: { sessionRef: next.session_ref }
3896
+ });
3897
+ pending = shutdown;
3898
+ session = next;
3899
+ stopObserver ?? (stopObserver = wireObserver(runtime2));
3900
+ interactive(true);
3901
+ reportCardStage("session_bound", { sessionRef: next.session_ref });
3902
+ scheduleHostRead(next.session_ref);
3903
+ return;
3904
+ }
3905
+ if (payload.kind !== "frame") return;
3906
+ const active = controller;
3907
+ const frame = payload.frame;
3908
+ queue(async () => {
3909
+ const outcome = await active.acceptHostFrame(payload.frame);
3910
+ if (active !== controller || disposed) return;
3911
+ if (outcome.status === "rejected") {
3912
+ reportCardStage("tool_result_rejected", { reason: outcome.reason });
3913
+ if (outcome.reason === "projection_failed") interactive(false);
3914
+ runtime2.events.publish("host.ui_error", { reason: outcome.reason });
3915
+ } else if (outcome.status === "duplicate") {
3916
+ reportCardStage("tool_result_rejected", {
3917
+ reason: "duplicate",
3918
+ turnId: outcome.resultId
3919
+ });
3920
+ } else if (outcome.status === "applied") {
3921
+ applied++;
3922
+ const tiles = (Array.isArray(frame?.mounted_elements) ? frame.mounted_elements : []).filter(
3923
+ (element) => element.element_type === "tile" && element.state === "active"
3924
+ );
3925
+ reportCardStage("frame_applied", {
3926
+ turnId: outcome.resultId,
3927
+ tiles: tiles.length,
3928
+ templateIds: [...new Set(tiles.map((element) => element.template_id))].filter((id) => typeof id === "string" && id.length > 0 && id.length <= 80).slice(0, 12)
3929
+ });
3930
+ runtime2.events.publish("host.frame_applied", {
3931
+ result_id: outcome.resultId,
3932
+ tiles: tiles.length
3933
+ });
3934
+ if (born === void 0 && tiles.length > 0 && !showingEarlyRead) {
3935
+ born = answerIssuedAt ?? Date.now();
3936
+ writeHeartbeat();
3937
+ }
3938
+ interactive(true);
3939
+ }
3940
+ });
3941
+ }
3942
+ async function consumeToolResult(result, { earlyRead = false } = {}) {
3943
+ if (disposed) return;
3944
+ const turn = decodeAgentTurn(result);
3945
+ if (!turn) {
3946
+ if (record(result) && record(result._meta) && AGENT_TURN_KEY in result._meta)
3947
+ reportCardStage("tool_result_rejected", { reason: "decode_failed" });
3948
+ return;
3949
+ }
3950
+ showingEarlyRead = earlyRead;
3951
+ if (!earlyRead) {
3952
+ answerIssuedAt ?? (answerIssuedAt = turn.issuedAt ?? Date.now());
3953
+ considerPopOut(turn.session.session_ref);
3954
+ }
3955
+ acceptHostUi2({ kind: "session", session: turn.session });
3956
+ acceptHostUi2({ kind: "frame", frame: turn.frame });
3957
+ startHeartbeat(turn.session.session_ref);
3958
+ boundSessionRef = turn.session.session_ref;
3959
+ if (turn.stars) {
3960
+ knownStars = mergeStars(knownStars, turn.stars);
3961
+ publishStars();
3962
+ }
3963
+ }
3964
+ let boundSessionRef;
3965
+ let knownStars;
3966
+ function publishStars() {
3967
+ if (disposed || !knownStars) return;
3968
+ runtime2.events.publish("host.stars", { stars: { ...knownStars } });
3969
+ }
3970
+ async function requestStar(props) {
3971
+ const instanceId = props?.instanceId;
3972
+ const starred = props?.starred;
3973
+ if (!nonempty(instanceId) || typeof starred !== "boolean") return;
3974
+ const failed = () => {
3975
+ if (!disposed) runtime2.events.publish("host.star_failed", { instanceId });
3976
+ };
3977
+ if (!boundSessionRef || typeof bridge.callServerTool !== "function") return failed();
3978
+ let result;
3979
+ try {
3980
+ result = await bridge.callServerTool({
3981
+ name: STAR_TOOL,
3982
+ arguments: { sessionRef: boundSessionRef, instanceId, starred }
3983
+ });
3984
+ } catch {
3985
+ return failed();
3986
+ }
3987
+ const stored = record(result) && !result.isError ? result.structuredContent : void 0;
3988
+ if (!record(stored) || stored.instanceId !== instanceId || typeof stored.starred !== "boolean" || typeof stored.updatedAt !== "string")
3989
+ return failed();
3990
+ knownStars = mergeStars(knownStars, {
3991
+ [instanceId]: {
3992
+ starred: stored.starred,
3993
+ updatedAt: stored.updatedAt,
3994
+ ...typeof stored.changedAt === "string" ? { changedAt: stored.changedAt } : {}
3995
+ }
3996
+ });
3997
+ publishStars();
3998
+ }
3999
+ const stopStarRequests = runtime2.events.subscribe?.(
4000
+ { names: ["canvas:star-request", "canvas:stars-request"] },
4001
+ (event) => {
4002
+ if (disposed) return;
4003
+ if (event.name === "canvas:stars-request") publishStars();
4004
+ else void requestStar(event.props);
4005
+ }
4006
+ );
4007
+ const deliver = (result) => {
4008
+ standDown();
4009
+ void consumeToolResult(result);
4010
+ };
4011
+ const stopActivity = bridge.subscribeToolActivity?.(standDown);
4012
+ let turnPending;
4013
+ let pendingTimer;
4014
+ function publishTurnPending(value) {
4015
+ if (disposed || turnPending === value) return;
4016
+ turnPending = value;
4017
+ if (pendingTimer !== void 0) clearTimeout(pendingTimer);
4018
+ pendingTimer = value ? setTimeout(() => publishTurnPending(false), PENDING_MAX_MS) : void 0;
4019
+ runtime2.events.publish("host.turn_pending", { pending: value });
4020
+ }
4021
+ const stopPendingRequests = runtime2.events.subscribe?.(
4022
+ { names: ["canvas:turn-pending-request"] },
4023
+ () => {
4024
+ if (!disposed && turnPending !== void 0)
4025
+ runtime2.events.publish("host.turn_pending", { pending: turnPending });
4026
+ }
4027
+ );
4028
+ const stopToolCall = bridge.subscribeToolCall?.((call) => {
4029
+ if (disposed) return;
4030
+ if (call.phase === "input") {
4031
+ publishTurnPending(true);
4032
+ const sessionRef = call.arguments.sessionRef;
4033
+ if (nonempty(sessionRef) && !session) scheduleHostRead(sessionRef, "turn_started");
4034
+ return;
4035
+ }
4036
+ void Promise.resolve().then(() => queue(async () => publishTurnPending(false)));
4037
+ });
4038
+ const unsubscribe = bridge.subscribeToolResults(deliver, {
4039
+ metadataKey: AGENT_TURN_KEY
4040
+ });
4041
+ return {
4042
+ acceptHostUi: acceptHostUi2,
4043
+ consumeToolResult,
4044
+ whenIdle: async () => {
4045
+ await pending;
4046
+ await controller.whenIdle();
4047
+ },
4048
+ dispose() {
4049
+ if (disposed) return;
4050
+ disposed = true;
4051
+ generation++;
4052
+ if (missingTimer !== void 0) clearTimeout(missingTimer);
4053
+ missingTimer = void 0;
4054
+ stopActivity?.();
4055
+ stopToolCall?.();
4056
+ stopPendingRequests?.();
4057
+ if (pendingTimer !== void 0) clearTimeout(pendingTimer);
4058
+ stopStarRequests?.();
4059
+ stopHeartbeat();
4060
+ stopObserver?.();
4061
+ stopObserver = void 0;
4062
+ unsubscribe();
4063
+ void controller.dispose();
4064
+ }
4065
+ };
4066
+ }
4067
+ var READ_REASON_MAX = 120;
4068
+ function readFailureReason(text) {
4069
+ const flat = text.replace(/\s+/g, " ").replace(/[A-Za-z0-9_\-+/=]{24,}/g, "[id]").trim();
4070
+ return flat.length > READ_REASON_MAX ? `${flat.slice(0, READ_REASON_MAX - 1)}~` : flat || "unknown";
4071
+ }
4072
+ function firstText(result) {
4073
+ if (!Array.isArray(result.content)) return void 0;
4074
+ for (const block of result.content) {
4075
+ if (record(block) && block.type === "text" && typeof block.text === "string") return block.text;
4076
+ }
4077
+ return void 0;
4078
+ }
4079
+ function activateHostAgent(runtime2) {
4080
+ if (!record(runtime2) || !record(runtime2.context) || typeof runtime2.context.get !== "function")
4081
+ return;
4082
+ const context = runtime2.context.get();
4083
+ if (!record(context) || !record(context.surface) || context.surface.type !== "mcp-app") return;
4084
+ if (!record(runtime2.actions) || typeof runtime2.actions.apply !== "function" || typeof runtime2.actions.applyBatch !== "function" || !record(runtime2.events) || typeof runtime2.events.publish !== "function" || typeof window === "undefined")
4085
+ return;
4086
+ const host = window.MCPApp?.bridge;
4087
+ if (typeof host?.subscribeToolResults !== "function") return;
4088
+ const receiver = hostUiSubscription(runtime2.hostUi) ? runtime2.hostUi : void 0;
4089
+ const interactionOwner = /* @__PURE__ */ Symbol("host-view");
4090
+ let interactionCanvas = null;
4091
+ let previousInert = false;
4092
+ let previousDisabled = null;
4093
+ const controller = activateHostAgentController(
4094
+ runtime2,
4095
+ host,
4096
+ receiver ? {
4097
+ setInteractive: (value) => {
4098
+ const canvas = findSmartCanvas();
4099
+ if (!canvas) return;
4100
+ if (interactionCanvas !== canvas) {
4101
+ interactionCanvas = canvas;
4102
+ const previous = interactionOwners.get(canvas);
4103
+ previousInert = previous ? previous.inert : canvas.inert;
4104
+ previousDisabled = previous ? previous.disabled : canvas.getAttribute("aria-disabled");
4105
+ interactionOwners.set(canvas, {
4106
+ owner: interactionOwner,
4107
+ inert: previousInert,
4108
+ disabled: previousDisabled
4109
+ });
4110
+ }
4111
+ if (interactionOwners.get(canvas)?.owner !== interactionOwner) return;
4112
+ canvas.inert = !value || previousInert;
4113
+ canvas.setAttribute("aria-disabled", value ? previousDisabled ?? "false" : "true");
4114
+ }
4115
+ } : void 0
4116
+ );
4117
+ const unsubscribe = receiver?.subscribeHostUi(controller.acceptHostUi);
4118
+ return () => {
4119
+ unsubscribe?.();
4120
+ controller.dispose();
4121
+ if (interactionCanvas && interactionOwners.get(interactionCanvas)?.owner === interactionOwner) {
4122
+ interactionCanvas.inert = previousInert;
4123
+ if (previousDisabled === null) interactionCanvas.removeAttribute("aria-disabled");
4124
+ else interactionCanvas.setAttribute("aria-disabled", previousDisabled);
4125
+ interactionOwners.delete(interactionCanvas);
4126
+ }
4127
+ };
4128
+ }
4129
+ function acceptHostUi(runtime2, payload) {
4130
+ runtime2.acceptHostUi?.(payload);
4131
+ }
4132
+
3474
4133
  // src/NavLinkMountable.ts
3475
4134
  var STATE_KEY3 = "__syntroNavLinkMount";
3476
4135
  var SAFE_NAVIGATION_PROTOCOLS = /* @__PURE__ */ new Set(["http:", "https:"]);
@@ -3655,6 +4314,8 @@ var runtime = {
3655
4314
  name: "Chat Assistant",
3656
4315
  description: "AI chat assistant powered by @syntrologie/chat with SSE transport",
3657
4316
  executors: [],
4317
+ onActivate: activateHostAgent,
4318
+ acceptHostUi,
3658
4319
  widgets: [
3659
4320
  {
3660
4321
  id: "adaptive-chatbot:assistant",