@syntrologie/adapt-chatbot 2.55.0 → 2.56.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 (76) 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 +4 -4
  6. package/dist/AdaptiveChatBarMountable.d.ts +28 -17
  7. package/dist/AdaptiveChatBarMountable.d.ts.map +1 -1
  8. package/dist/AdaptiveChatTrail.js +3 -3
  9. package/dist/AdaptiveChipsStrip.d.ts +20 -20
  10. package/dist/AdaptiveChipsStrip.js +3 -3
  11. package/dist/ChatAssistantLit.d.ts +5 -3
  12. package/dist/ChatAssistantLit.d.ts.map +1 -1
  13. package/dist/ChatAssistantLit.js +5 -4
  14. package/dist/ChatTransport.d.ts +10 -5
  15. package/dist/ChatTransport.d.ts.map +1 -1
  16. package/dist/Turnstile.d.ts +18 -0
  17. package/dist/Turnstile.d.ts.map +1 -1
  18. package/dist/cdn.d.ts +1 -0
  19. package/dist/cdn.d.ts.map +1 -1
  20. package/dist/chunk-23E6WZOO.js +882 -0
  21. package/dist/chunk-23E6WZOO.js.map +7 -0
  22. package/dist/{chunk-57MPLQOL.js → chunk-7QXC475Y.js} +400 -935
  23. package/dist/{chunk-57MPLQOL.js.map → chunk-7QXC475Y.js.map} +4 -4
  24. package/dist/{chunk-TZA7572J.js → chunk-GN7Z5IVP.js} +2 -2
  25. package/dist/chunk-LY5AGXVD.js +21 -0
  26. package/dist/{chunk-ETDHHQS3.js → chunk-NMYFYMJI.js} +12 -12
  27. package/dist/{chunk-ETDHHQS3.js.map → chunk-NMYFYMJI.js.map} +1 -1
  28. package/dist/{chunk-LJEPZI6Y.js → chunk-WQIDXGGS.js} +2 -2
  29. package/dist/{chunk-VFWA5W56.js → chunk-XPUHW2YO.js} +44 -7
  30. package/dist/chunk-XPUHW2YO.js.map +7 -0
  31. package/dist/chunk-Z3YR4JFE.js +4702 -0
  32. package/dist/chunk-Z3YR4JFE.js.map +7 -0
  33. package/dist/editor-lit.js +1 -1
  34. package/dist/element-session.d.ts +3 -0
  35. package/dist/element-session.d.ts.map +1 -0
  36. package/dist/element-session.js +9 -0
  37. package/dist/element-session.js.map +7 -0
  38. package/dist/elements/ElementInstanceStore.d.ts +15 -1
  39. package/dist/elements/ElementInstanceStore.d.ts.map +1 -1
  40. package/dist/elements/ElementSessionController.d.ts +37 -0
  41. package/dist/elements/ElementSessionController.d.ts.map +1 -0
  42. package/dist/elements/ElementSessionController.test.d.ts +2 -0
  43. package/dist/elements/ElementSessionController.test.d.ts.map +1 -0
  44. package/dist/elements/TileHandler.d.ts.map +1 -1
  45. package/dist/elements/createElementStore.d.ts +11 -0
  46. package/dist/elements/createElementStore.d.ts.map +1 -0
  47. package/dist/elements/tileLifecycleProps.d.ts +13 -0
  48. package/dist/elements/tileLifecycleProps.d.ts.map +1 -0
  49. package/dist/elements/types.d.ts +7 -0
  50. package/dist/elements/types.d.ts.map +1 -1
  51. package/dist/hostAgentController.d.ts +65 -0
  52. package/dist/hostAgentController.d.ts.map +1 -0
  53. package/dist/hostAgentController.test.d.ts +2 -0
  54. package/dist/hostAgentController.test.d.ts.map +1 -0
  55. package/dist/observer/wire.d.ts +54 -0
  56. package/dist/observer/wire.d.ts.map +1 -0
  57. package/dist/observer/wire.test.d.ts +2 -0
  58. package/dist/observer/wire.test.d.ts.map +1 -0
  59. package/dist/runtime.d.ts +3 -0
  60. package/dist/runtime.d.ts.map +1 -1
  61. package/dist/runtime.js +832 -172
  62. package/dist/runtime.js.map +4 -4
  63. package/dist/schema.d.ts +66 -66
  64. package/dist/schema.d.ts.map +1 -1
  65. package/dist/schema.js +75 -76
  66. package/dist/schema.js.map +3 -3
  67. package/dist/types.d.ts +17 -0
  68. package/dist/types.d.ts.map +1 -1
  69. package/package.json +9 -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
  76. /package/dist/{chunk-LJEPZI6Y.js.map → chunk-WQIDXGGS.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
- acquireTokenWithChallenge,
4
+ acquireBotCheckTokenForSend,
9
5
  decodeMutationEnvelope,
10
6
  fetchMountedElements,
11
7
  renderFallbackHtml
12
- } from "./chunk-57MPLQOL.js";
8
+ } from "./chunk-7QXC475Y.js";
9
+ import {
10
+ ElementInstanceStore,
11
+ TileHandler,
12
+ createElementSessionController,
13
+ createTemplateWidgetResolver
14
+ } from "./chunk-23E6WZOO.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-WQIDXGGS.js";
23
+ import "./chunk-GN7Z5IVP.js";
24
+ import "./chunk-NMYFYMJI.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-Z3YR4JFE.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
  /**
@@ -745,9 +752,6 @@ var ChatTransport = class {
745
752
  const headers2 = {};
746
753
  const token = readRuntimeToken(this._config?.runtime);
747
754
  if (token) headers2.Authorization = `Bearer ${token}`;
748
- const cft = await this._acquireTurnstileWithChallenge();
749
- if (cft) headers2["CF-Turnstile-Token"] = cft;
750
- if (abort.signal.aborted) return;
751
755
  const readIds = () => {
752
756
  try {
753
757
  const rt = this._config?.runtime;
@@ -987,6 +991,7 @@ var ChatTransport = class {
987
991
  this._firstDeltaAt = null;
988
992
  this._runMounts = 0;
989
993
  this._runCards = 0;
994
+ this._runMountTools = [];
990
995
  this._reportAtom("syntro_chat_message_sent", { turn, chars: text.length, via });
991
996
  void this._forwardUserMessage(text, id);
992
997
  }
@@ -1093,6 +1098,23 @@ var ChatTransport = class {
1093
1098
  if (!runtime2) return;
1094
1099
  const mutations = decodeMutationEnvelope(payload);
1095
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
+ }
1096
1118
  const handler = this._config?.onElementMutation;
1097
1119
  if (handler) {
1098
1120
  try {
@@ -1129,13 +1151,15 @@ var ChatTransport = class {
1129
1151
  }
1130
1152
  }
1131
1153
  /**
1132
- * Acquire a Turnstile token via the managed-challenge flow.
1133
- * Delegates to the shared Turnstile helper that owns the verify
1134
- * panel lifecycle. Returns null when Turnstile is disabled at
1135
- * build time or acquisition fails.
1154
+ * Acquire a Turnstile token for a visitor send. Only reached from
1155
+ * `_ensureConnected`, i.e. on interaction. Returns null without touching
1156
+ * Cloudflare when the build has no sitekey or the backend does not enforce
1157
+ * the check; null also when acquisition fails.
1136
1158
  */
1137
1159
  async _acquireTurnstileWithChallenge() {
1138
- const { token } = await acquireTokenWithChallenge();
1160
+ if (!this._config) return null;
1161
+ const backendUrl = this._config.backendUrl.replace(/\/$/, "");
1162
+ const { token } = await acquireBotCheckTokenForSend(backendUrl);
1139
1163
  return token;
1140
1164
  }
1141
1165
  // -------------------------------------------------------------------------
@@ -1309,6 +1333,7 @@ var ChatTransport = class {
1309
1333
  if (!existing && event.toolCall.name.startsWith("mount_")) {
1310
1334
  this._runMounts += 1;
1311
1335
  if (/card/i.test(event.toolCall.name)) this._runCards += 1;
1336
+ this._runMountTools.push(event.toolCall.name);
1312
1337
  }
1313
1338
  if (!existing) {
1314
1339
  this._startThinkingCycle(toolNarrationVariants(event.toolCall.name));
@@ -1406,7 +1431,8 @@ var ChatTransport = class {
1406
1431
  ttft_ms: (this._firstDeltaAt ?? end) - this._runStartedAt,
1407
1432
  total_ms: end - this._runStartedAt,
1408
1433
  mounts: this._runMounts,
1409
- cards: this._runCards
1434
+ cards: this._runCards,
1435
+ mount_tools: [...this._runMountTools]
1410
1436
  });
1411
1437
  }
1412
1438
  this._runStartedAt = 0;
@@ -1440,6 +1466,38 @@ function findSmartCanvas() {
1440
1466
  return find(document);
1441
1467
  }
1442
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
+
1443
1501
  // src/observer/allowlist.ts
1444
1502
  var MAX_TEXT_LEN = 200;
1445
1503
  var SIGNIFICANT_CLICK_TAGS = /* @__PURE__ */ new Set(["button", "a"]);
@@ -1942,36 +2000,74 @@ function _makeHandle(owner) {
1942
2000
  };
1943
2001
  }
1944
2002
 
1945
- // src/observer/bus-translate.ts
1946
- function eventTypeFromName(name) {
1947
- switch (name) {
1948
- case "ui.click":
1949
- return "click";
1950
- case "ui.submit":
1951
- return "submit";
1952
- case "ui.change":
1953
- return "change";
1954
- default:
1955
- return void 0;
1956
- }
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
+ };
1957
2037
  }
1958
- function busEventToRawEvent(e) {
1959
- if (!e?.name) return null;
1960
- const ts = typeof e.ts === "number" ? e.ts : Date.now();
1961
- if (e.source === "posthog") {
1962
- const original = e.props?.originalEvent;
1963
- if (typeof original !== "string" || original.length === 0) return null;
1964
- const src = e.props ?? {};
1965
- const properties = { ...src };
1966
- if (Array.isArray(src.elements)) properties.$elements = src.elements;
1967
- if (typeof src.tagName === "string") properties.$element_tag_name = src.tagName;
1968
- if (typeof src.pathname === "string") properties.$pathname = src.pathname;
1969
- if (typeof src.url === "string") properties.$current_url = src.url;
1970
- const eventType = eventTypeFromName(e.name);
1971
- if (eventType) properties.$event_type = eventType;
1972
- return { event: original, timestamp: ts, properties };
1973
- }
1974
- 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
+ };
1975
2071
  }
1976
2072
 
1977
2073
  // src/support-chat/registry.ts
@@ -3067,106 +3163,41 @@ function applyPlaceholder(bar, cfg2) {
3067
3163
  if (typeof cfg2.maximized === "boolean") {
3068
3164
  bar.maximized = cfg2.maximized;
3069
3165
  }
3166
+ if (cfg2.chatControl !== void 0) bar.chatControl = cfg2.chatControl;
3167
+ if (typeof cfg2.interleaved === "boolean") bar.interleaved = cfg2.interleaved;
3070
3168
  }
3071
- var _elementStore = null;
3072
- var _templateWidgetMap = /* @__PURE__ */ new Map();
3073
- var SERVER_MOUNTED_TILE_WIDGETS = ["syntro-product-reco-carousel"];
3074
- function resolveTileWidget(templateId) {
3075
- return _templateWidgetMap.get(templateId);
3076
- }
3077
- function refreshTemplateWidgetMap(uiTemplates) {
3078
- _templateWidgetMap.clear();
3079
- for (const tag of SERVER_MOUNTED_TILE_WIDGETS) {
3080
- _templateWidgetMap.set(tag, tag);
3081
- }
3082
- if (!uiTemplates) return;
3083
- const tiles = uiTemplates.tiles;
3084
- if (!Array.isArray(tiles)) return;
3085
- for (const t of tiles) {
3086
- const id = typeof t?.id === "string" ? t.id : null;
3087
- const widget = typeof t?.widget === "string" ? t.widget : null;
3088
- if (id && widget) _templateWidgetMap.set(id, widget);
3089
- }
3090
- }
3091
- function getOrCreateElementStore(runtime2) {
3092
- if (_elementStore) return _elementStore;
3093
- _elementStore = new ElementInstanceStore({
3094
- actions: runtime2.actions,
3095
- // Pass the runtime's event bus so ItemHandler can broadcast
3096
- // `element.compositional_append` / `_patch` / `_remove` events
3097
- // to container widgets (chips strip, FAQ accordion, nav tips).
3098
- events: {
3099
- publish: runtime2.events.publish.bind(runtime2.events),
3100
- subscribe: runtime2.events.subscribe?.bind(runtime2.events)
3101
- },
3102
- handlers: [new TileHandler(resolveTileWidget), new ActionHandler(), new ItemHandler()],
3103
- resolveTileWidget
3104
- });
3105
- setElementInstanceStore(_elementStore);
3106
- 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;
3107
3180
  }
3108
- var _hydrationStarted = false;
3109
3181
  function hydrateOnce(runtime2, backendUrl) {
3110
- if (_hydrationStarted) return;
3111
- _hydrationStarted = true;
3112
- const store = getOrCreateElementStore(runtime2);
3182
+ const session = getOrCreateElementSession(runtime2);
3113
3183
  const trimmed = backendUrl.replace(/\/$/, "");
3114
3184
  const endpoint = trimmed ? `${trimmed}/api/adaptive/mounted_elements` : "/api/adaptive/mounted_elements";
3115
- fetchMountedElements({ endpoint, token: runtime2.token }).then((response) => {
3116
- if (!response) return;
3117
- void store.hydrate(response.mounted_elements);
3118
- });
3119
- }
3120
- var PAGE_LEVEL_KINDS = /* @__PURE__ */ new Set(["view", "scroll", "idle", "hesitation", "hover", "nav"]);
3121
- function buildObserverEnrich(runtimeRef) {
3122
- return (ev, raw) => {
3123
- const extract = runtimeRef.getProductIdentity;
3124
- if (typeof extract !== "function") return ev;
3125
- if (ev.kind === "click") {
3126
- const chain = raw.properties.$elements;
3127
- if (!Array.isArray(chain)) return ev;
3128
- const id = extract(chain);
3129
- if (!id) return ev;
3130
- return {
3131
- ...ev,
3132
- product: {
3133
- handle: id.productHandle,
3134
- url: id.productUrl,
3135
- ...id.productTitle ? { title: id.productTitle } : {}
3136
- }
3137
- };
3138
- }
3139
- if (!ev.product && PAGE_LEVEL_KINDS.has(ev.kind) && typeof location !== "undefined") {
3140
- const id = extract([{ href: location.pathname }]);
3141
- if (!id) return ev;
3142
- return {
3143
- ...ev,
3144
- product: {
3145
- handle: id.productHandle,
3146
- url: id.productUrl,
3147
- ...id.productTitle ? { title: id.productTitle } : {}
3148
- }
3149
- };
3150
- }
3151
- return ev;
3152
- };
3185
+ session.hydrateOnce(() => fetchMountedElements({ endpoint, token: runtime2.token }));
3153
3186
  }
3154
- var _nudgeSurfaceWired = false;
3155
- var _nudgeListener = null;
3187
+ var _removeNudgeListener = null;
3156
3188
  function wireNudgeSurface(runtime2, backendUrl) {
3157
- if (_nudgeSurfaceWired || typeof document === "undefined") return;
3158
- _nudgeSurfaceWired = true;
3159
- _nudgeListener = (e) => {
3189
+ if (_removeNudgeListener || typeof document === "undefined") return;
3190
+ const listener = (e) => {
3160
3191
  try {
3161
3192
  const detail = e.detail;
3162
3193
  const isIntent = detail?.phase === "intent";
3163
3194
  if (!isIntent) {
3164
- const store = getOrCreateElementStore(runtime2);
3195
+ const session = getOrCreateElementSession(runtime2);
3165
3196
  const trimmed = backendUrl.replace(/\/$/, "");
3166
3197
  const endpoint = trimmed ? `${trimmed}/api/adaptive/mounted_elements` : "/api/adaptive/mounted_elements";
3167
3198
  void fetchMountedElements({ endpoint, token: runtime2.token }).then((response) => {
3168
3199
  if (!response) return;
3169
- void store.hydrate(response.mounted_elements);
3200
+ void session.hydrateLegacy(response.mounted_elements, response.session_expires_at);
3170
3201
  });
3171
3202
  }
3172
3203
  if (detail?.suppressToast === true) return;
@@ -3182,7 +3213,11 @@ function wireNudgeSurface(runtime2, backendUrl) {
3182
3213
  console.warn("[adaptive-chatbot] nudge surface reaction failed", err);
3183
3214
  }
3184
3215
  };
3185
- document.addEventListener("syntro:nudge", _nudgeListener);
3216
+ document.addEventListener("syntro:nudge", listener);
3217
+ _removeNudgeListener = () => {
3218
+ document.removeEventListener("syntro:nudge", listener);
3219
+ _removeNudgeListener = null;
3220
+ };
3186
3221
  }
3187
3222
  function configureTransportIfPossible(cfg2) {
3188
3223
  if (!cfg2.runtime) return;
@@ -3193,9 +3228,7 @@ function configureTransportIfPossible(cfg2) {
3193
3228
  const forwardedProps = elementsActive ? { elementsEnabled: true, uiTemplates: cfg2.uiTemplates } : void 0;
3194
3229
  if (elementsActive) publishTemplatesIfPossible(backendUrl, cfg2.uiTemplates, cfg2.runtime?.token);
3195
3230
  const runtime2 = cfg2.runtime;
3196
- const onElementMutation = elementsActive ? (mutations) => {
3197
- void getOrCreateElementStore(runtime2).apply(mutations);
3198
- } : void 0;
3231
+ const onElementEnvelope = elementsActive ? (envelope) => getOrCreateElementSession(runtime2).applyLegacyEnvelope(envelope) : void 0;
3199
3232
  if (elementsActive) {
3200
3233
  hydrateOnce(runtime2, backendUrl);
3201
3234
  }
@@ -3204,7 +3237,7 @@ function configureTransportIfPossible(cfg2) {
3204
3237
  ...cfg2,
3205
3238
  backendUrl,
3206
3239
  forwardedProps,
3207
- onElementMutation,
3240
+ onElementEnvelope,
3208
3241
  activeLidSlot: cfg2._syntroSlotName,
3209
3242
  personaId: persona?.id,
3210
3243
  personaName: persona?.name,
@@ -3251,46 +3284,19 @@ function wireDiveDeeperListener() {
3251
3284
  }
3252
3285
  var _observerStarted = false;
3253
3286
  var _observerRuntime = null;
3254
- var _observerHandle = null;
3255
- var _observerBusUnsubscribe = null;
3287
+ var _observerStop = null;
3256
3288
  function startObserverIfPossible(cfg2) {
3257
3289
  if (typeof window === "undefined") return;
3258
3290
  const runtimeRef = cfg2.runtime;
3259
3291
  if (!runtimeRef?.events?.subscribe) return;
3260
3292
  if (_observerStarted && _observerRuntime === runtimeRef) return;
3261
3293
  if (_observerStarted) {
3262
- _observerBusUnsubscribe?.();
3263
- _observerHandle?.stop();
3264
- _observerBusUnsubscribe = null;
3265
- _observerHandle = null;
3294
+ _observerStop?.();
3295
+ _observerStop = null;
3266
3296
  _observerStarted = false;
3267
3297
  }
3268
- const trimmed = (runtimeRef.backendUrl ?? "").replace(/\/$/, "");
3269
- const url = trimmed ? `${trimmed}/api/adaptive/observation` : "/api/adaptive/observation";
3270
- const runtimeId = (method) => {
3271
- try {
3272
- const v = runtimeRef.telemetry?.[method]?.();
3273
- return typeof v === "string" && v.length > 0 ? v : null;
3274
- } catch {
3275
- return null;
3276
- }
3277
- };
3278
- const getDistinctId = () => runtimeId("getDistinctId");
3279
- const getSessionId = () => runtimeId("getSessionId");
3280
- const token = () => {
3281
- const t = runtimeRef.token;
3282
- return typeof t === "string" ? t : "";
3283
- };
3284
- const enrich = buildObserverEnrich(runtimeRef);
3285
- const handle = startObserver({ url, token, getDistinctId, getSessionId, enrich });
3286
- window.__syntroObserverStats = () => handle.stats();
3287
- const unsubscribe = runtimeRef.events.subscribe({}, (evt) => {
3288
- const raw = busEventToRawEvent(evt);
3289
- if (raw) handle.ingest(raw);
3290
- });
3298
+ _observerStop = wireObserver(runtimeRef);
3291
3299
  _observerRuntime = runtimeRef;
3292
- _observerHandle = handle;
3293
- _observerBusUnsubscribe = unsubscribe;
3294
3300
  _observerStarted = true;
3295
3301
  }
3296
3302
  function wireListeners(bar, state) {
@@ -3359,7 +3365,7 @@ var AdaptiveChatBarMountable = {
3359
3365
  });
3360
3366
  const runtimeEvents = cfg2.runtime ? cfg2.runtime.events : void 0;
3361
3367
  const refreshReceipts = () => {
3362
- const store = _elementStore;
3368
+ const store = _elementSession?.store;
3363
3369
  if (store) {
3364
3370
  bar.tileReceipts = store.listTileReceipts();
3365
3371
  bar.inlineWidgets = store.listInlineWidgets();
@@ -3472,6 +3478,658 @@ var AdaptiveChipsStripMountable = {
3472
3478
  }
3473
3479
  };
3474
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
+
3475
4133
  // src/NavLinkMountable.ts
3476
4134
  var STATE_KEY3 = "__syntroNavLinkMount";
3477
4135
  var SAFE_NAVIGATION_PROTOCOLS = /* @__PURE__ */ new Set(["http:", "https:"]);
@@ -3656,6 +4314,8 @@ var runtime = {
3656
4314
  name: "Chat Assistant",
3657
4315
  description: "AI chat assistant powered by @syntrologie/chat with SSE transport",
3658
4316
  executors: [],
4317
+ onActivate: activateHostAgent,
4318
+ acceptHostUi,
3659
4319
  widgets: [
3660
4320
  {
3661
4321
  id: "adaptive-chatbot:assistant",