@syntrologie/adapt-chatbot 2.37.2 → 2.38.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.
@@ -1 +1 @@
1
- {"version":3,"file":"AdaptiveChatBarMountable.d.ts","sourceRoot":"","sources":["../src/AdaptiveChatBarMountable.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4BG;AAGH,OAAO,mBAAmB,CAAC;AAC3B,OAAO,EAIL,KAAK,wBAAwB,EAI9B,MAAM,4BAA4B,CAAC;AAkBpC,OAAO,KAAK,EAAE,gBAAgB,EAAE,QAAQ,EAAE,MAAM,sBAAsB,CAAC;AAEvE,OAAO,KAAK,EAAmB,oBAAoB,EAAE,MAAM,YAAY,CAAC;AA8HxE;;;;;;GAMG;AACH,wBAAgB,0BAA0B,IAAI,IAAI,CAIjD;AA6BD,wBAAgB,iBAAiB,CAAC,UAAU,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAExE;AAED,wBAAgB,wBAAwB,CAAC,WAAW,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,SAAS,GAAG,IAAI,CAgB/F;AA+DD;;;;;;;;;;;;GAYG;AACH,wBAAgB,mBAAmB,CAAC,UAAU,EAAE;IAC9C,kBAAkB,CAAC,EAAE,wBAAwB,CAAC;CAC/C,GAAG,CAAC,EAAE,EAAE,gBAAgB,EAAE,GAAG,EAAE,QAAQ,KAAK,gBAAgB,CAkC5D;AAOD;;;;;;;;;;;GAWG;AACH,wBAAgB,gBAAgB,CAAC,OAAO,EAAE,oBAAoB,EAAE,UAAU,EAAE,MAAM,GAAG,IAAI,CAoExF;AAED,6EAA6E;AAC7E,wBAAgB,0BAA0B,IAAI,IAAI,CAMjD;AAsGD,0BAA0B;AAC1B,wBAAgB,6BAA6B,IAAI,IAAI,CAEpD;AA0ED,wBAAgB,+BAA+B,IAAI,IAAI,CAUtD;AAyID,eAAO,MAAM,wBAAwB;qBAClB,WAAW,gBAAgB,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,MAAM,IAAI;sBA8F9D,WAAW,gBAAgB,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,IAAI;CAQ5E,CAAC"}
1
+ {"version":3,"file":"AdaptiveChatBarMountable.d.ts","sourceRoot":"","sources":["../src/AdaptiveChatBarMountable.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4BG;AAGH,OAAO,mBAAmB,CAAC;AAC3B,OAAO,EAIL,KAAK,wBAAwB,EAI9B,MAAM,4BAA4B,CAAC;AAkBpC,OAAO,KAAK,EAAE,gBAAgB,EAAE,QAAQ,EAAE,MAAM,sBAAsB,CAAC;AAGvE,OAAO,KAAK,EAAmB,oBAAoB,EAAE,MAAM,YAAY,CAAC;AAsIxE;;;;;;GAMG;AACH,wBAAgB,0BAA0B,IAAI,IAAI,CAIjD;AA6BD,wBAAgB,iBAAiB,CAAC,UAAU,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAExE;AAED,wBAAgB,wBAAwB,CAAC,WAAW,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,SAAS,GAAG,IAAI,CAgB/F;AA+DD;;;;;;;;;;;;GAYG;AACH,wBAAgB,mBAAmB,CAAC,UAAU,EAAE;IAC9C,kBAAkB,CAAC,EAAE,wBAAwB,CAAC;CAC/C,GAAG,CAAC,EAAE,EAAE,gBAAgB,EAAE,GAAG,EAAE,QAAQ,KAAK,gBAAgB,CAkC5D;AAOD;;;;;;;;;;;GAWG;AACH,wBAAgB,gBAAgB,CAAC,OAAO,EAAE,oBAAoB,EAAE,UAAU,EAAE,MAAM,GAAG,IAAI,CAoExF;AAED,6EAA6E;AAC7E,wBAAgB,0BAA0B,IAAI,IAAI,CAMjD;AAsGD,0BAA0B;AAC1B,wBAAgB,6BAA6B,IAAI,IAAI,CAEpD;AA0ED,wBAAgB,+BAA+B,IAAI,IAAI,CAUtD;AAyID,eAAO,MAAM,wBAAwB;qBAClB,WAAW,gBAAgB,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,MAAM,IAAI;sBAkG9D,WAAW,gBAAgB,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,IAAI;CAQ5E,CAAC"}
@@ -0,0 +1,109 @@
1
+ /**
2
+ * <syntro-leave-a-message> — escalation-only async human handoff
3
+ * (spec: docs/superpowers/specs/2026-08-13-async-handoff-crisp-design.md).
4
+ * Four states: form → sent → mail ("You got mail!") → flipped (reply +
5
+ * inline follow-up). All delivery through an injected SupportChatBridge;
6
+ * fail closed when the bridge is absent or the vendor SDK undetected.
7
+ */
8
+ import { LitElement, nothing } from 'lit';
9
+ import type { SupportChatBridge } from './support-chat/types';
10
+ export type LeaveAMessageState = 'form' | 'sent' | 'mail' | 'flipped';
11
+ /**
12
+ * Minimal runtime slice the widget needs to raise the "You got mail!"
13
+ * toast — mirrors adaptive-nav's `NavWidgetRuntime.events` shape (see
14
+ * `NavWidgetLit#publishEvent`) so tile `notifications` rules (which
15
+ * subscribe to the runtime EventBus, not DOM CustomEvents) can match
16
+ * on `supportchat:reply_received`.
17
+ */
18
+ export interface LeaveAMessageRuntime {
19
+ events: {
20
+ publish: (name: string, props?: Record<string, unknown>) => void;
21
+ };
22
+ /**
23
+ * Optional persistence seam (I2, final-review fixes 2026-08-13) — mirrors
24
+ * adaptive-overlays' `WorkflowWidgetLit.runtimeRef?.state?.user?.ns?.(...)`
25
+ * pattern exactly. On an MPA the tile unmounts and remounts fresh per
26
+ * page; without this, a visitor who already sent a message and is
27
+ * waiting on a reply loses that thread on their very next click (state
28
+ * resets to 'form', the operator's reply becomes invisible). Optional
29
+ * because older host bundles / the playground don't inject it — the
30
+ * widget still works, it just can't survive navigation.
31
+ */
32
+ state?: {
33
+ user?: {
34
+ ns?: (namespace: string) => {
35
+ get: (key: string) => unknown;
36
+ set: (key: string, value: unknown) => void;
37
+ };
38
+ };
39
+ };
40
+ }
41
+ export declare class LeaveAMessageLit extends LitElement {
42
+ static properties: {
43
+ bridge: {
44
+ attribute: boolean;
45
+ };
46
+ message: {
47
+ type: StringConstructor;
48
+ };
49
+ contextSummary: {
50
+ attribute: boolean;
51
+ };
52
+ state: {
53
+ type: StringConstructor;
54
+ };
55
+ runtime: {
56
+ attribute: boolean;
57
+ };
58
+ replyToast: {
59
+ attribute: boolean;
60
+ };
61
+ };
62
+ bridge: SupportChatBridge | undefined;
63
+ message: string;
64
+ contextSummary: string;
65
+ state: LeaveAMessageState;
66
+ /** Runtime plumbing injected by the mountable — undefined outside a live SDK mount. */
67
+ runtime: LeaveAMessageRuntime | undefined;
68
+ /**
69
+ * Visitor-facing toast copy, authored by the WORKSPACE CONFIG
70
+ * (`default_widget_props.replyToast`) — presentation belongs to
71
+ * configuration, not to this bundle: every workspace has its own voice
72
+ * and language. The built-in fallback below exists only so an
73
+ * unconfigured mount still says something truthful.
74
+ */
75
+ replyToast: {
76
+ title?: string;
77
+ body?: string;
78
+ } | undefined;
79
+ private _email;
80
+ private _reply;
81
+ private _operatorMessages;
82
+ private _unsubscribe;
83
+ createRenderRoot(): HTMLElement;
84
+ connectedCallback(): void;
85
+ updated(): void;
86
+ disconnectedCallback(): void;
87
+ /** Read the injected persistence store, if any (I2). */
88
+ private _persistStore;
89
+ /** Hydrate {state, operatorMessages, email} from a prior page's mount. */
90
+ private _loadPersisted;
91
+ /** Snapshot {state, operatorMessages, email} so the NEXT mount (a fresh
92
+ * page in the same session) can pick the thread back up. */
93
+ private _persist;
94
+ /**
95
+ * Raise the reply toast through the canvas element. Pierces shadow roots to
96
+ * find `<smart-canvas>` (same resolution AdaptiveChatBarMountable uses), and
97
+ * no-ops when no canvas is present — the tile's own "You got mail!" face is
98
+ * always the durable signal.
99
+ */
100
+ private _toastReplyArrived;
101
+ private _onReply;
102
+ private _submit;
103
+ private _flip;
104
+ private _sendReply;
105
+ render(): typeof nothing | import("lit-html").TemplateResult<1>;
106
+ }
107
+ /** Idempotent registration — called by the mountable on first mount. */
108
+ export declare function registerLeaveAMessageLit(): void;
109
+ //# sourceMappingURL=LeaveAMessageLit.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"LeaveAMessageLit.d.ts","sourceRoot":"","sources":["../src/LeaveAMessageLit.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AACH,OAAO,EAAQ,UAAU,EAAE,OAAO,EAAE,MAAM,KAAK,CAAC;AAGhD,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,sBAAsB,CAAC;AAsD9D,MAAM,MAAM,kBAAkB,GAAG,MAAM,GAAG,MAAM,GAAG,MAAM,GAAG,SAAS,CAAC;AAEtE;;;;;;GAMG;AACH,MAAM,WAAW,oBAAoB;IACnC,MAAM,EAAE;QACN,OAAO,EAAE,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,KAAK,IAAI,CAAC;KAClE,CAAC;IACF;;;;;;;;;OASG;IACH,KAAK,CAAC,EAAE;QACN,IAAI,CAAC,EAAE;YACL,EAAE,CAAC,EAAE,CAAC,SAAS,EAAE,MAAM,KAAK;gBAC1B,GAAG,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,OAAO,CAAC;gBAC9B,GAAG,EAAE,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,OAAO,KAAK,IAAI,CAAC;aAC5C,CAAC;SACH,CAAC;KACH,CAAC;CACH;AASD,qBAAa,gBAAiB,SAAQ,UAAU;IAC9C,OAAgB,UAAU;;;;;;;;;;;;;;;;;;;MAOxB;IAEF,MAAM,EAAE,iBAAiB,GAAG,SAAS,CAAC;IACtC,OAAO,SAAM;IACb,cAAc,SAAM;IACpB,KAAK,EAAE,kBAAkB,CAAU;IACnC,uFAAuF;IACvF,OAAO,EAAE,oBAAoB,GAAG,SAAS,CAAC;IAE1C;;;;;;OAMG;IACH,UAAU,EAAE;QAAE,KAAK,CAAC,EAAE,MAAM,CAAC;QAAC,IAAI,CAAC,EAAE,MAAM,CAAA;KAAE,GAAG,SAAS,CAAC;IAE1D,OAAO,CAAC,MAAM,CAAM;IACpB,OAAO,CAAC,MAAM,CAAM;IACpB,OAAO,CAAC,iBAAiB,CAAgB;IACzC,OAAO,CAAC,YAAY,CAA2B;IAEtC,gBAAgB,IAAI,WAAW;IAI/B,iBAAiB,IAAI,IAAI;IAYzB,OAAO,IAAI,IAAI;IAcf,oBAAoB,IAAI,IAAI;IAMrC,wDAAwD;IACxD,OAAO,CAAC,aAAa;IAIrB,0EAA0E;IAC1E,OAAO,CAAC,cAAc;IAUtB;iEAC6D;IAC7D,OAAO,CAAC,QAAQ;IAahB;;;;;OAKG;IACH,OAAO,CAAC,kBAAkB;IAiC1B,OAAO,CAAC,QAAQ,CAgCd;IAEF,OAAO,CAAC,OAAO,CAOb;IAEF,OAAO,CAAC,KAAK,CAGX;IAEF,OAAO,CAAC,UAAU,CAKhB;IAEO,MAAM;CA6DhB;AAED,wEAAwE;AACxE,wBAAgB,wBAAwB,IAAI,IAAI,CAI/C"}
@@ -0,0 +1,2 @@
1
+ import './LeaveAMessageLit';
2
+ //# sourceMappingURL=LeaveAMessageLit.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"LeaveAMessageLit.test.d.ts","sourceRoot":"","sources":["../src/LeaveAMessageLit.test.ts"],"names":[],"mappings":"AAEA,OAAO,oBAAoB,CAAC"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=LeaveAMessageMountable.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"LeaveAMessageMountable.test.d.ts","sourceRoot":"","sources":["../src/LeaveAMessageMountable.test.ts"],"names":[],"mappings":""}
package/dist/cdn.d.ts CHANGED
@@ -12,7 +12,7 @@ export declare const manifest: {
12
12
  description: string;
13
13
  runtime: {
14
14
  actions: never[];
15
- widgets: {
15
+ widgets: ({
16
16
  id: string;
17
17
  component: {
18
18
  mount(container: HTMLElement, mountConfig?: Record<string, unknown>): () => void;
@@ -22,7 +22,18 @@ export declare const manifest: {
22
22
  description: string;
23
23
  icon: string;
24
24
  };
25
- }[];
25
+ } | {
26
+ id: string;
27
+ component: {
28
+ mount(container: HTMLElement, config?: (Record<string, unknown> & import("packages/sdk-contracts/dist").MountPlumbing) | null): () => void;
29
+ update(container: HTMLElement, config?: (Record<string, unknown> & import("packages/sdk-contracts/dist").MountPlumbing) | null): void;
30
+ };
31
+ metadata: {
32
+ name: string;
33
+ description: string;
34
+ icon: string;
35
+ };
36
+ })[];
26
37
  };
27
38
  editor: {
28
39
  component: typeof ChatbotEditorLit;
package/dist/cdn.d.ts.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"cdn.d.ts","sourceRoot":"","sources":["../src/cdn.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,EAAE,gBAAgB,EAAE,MAAM,cAAc,CAAC;AAGhD,eAAO,MAAM,QAAQ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAoBpB,CAAC;AAaF,eAAe,QAAQ,CAAC"}
1
+ {"version":3,"file":"cdn.d.ts","sourceRoot":"","sources":["../src/cdn.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,EAAE,gBAAgB,EAAE,MAAM,cAAc,CAAC;AAGhD,eAAO,MAAM,QAAQ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAoBpB,CAAC;AAaF,eAAe,QAAQ,CAAC"}
package/dist/runtime.d.ts CHANGED
@@ -9,8 +9,34 @@
9
9
  * replace the launcher FAB) or any other slot's `lid` / tile slot. The
10
10
  * adaptive no longer auto-registers a canvas lid renderer.
11
11
  */
12
+ import { type MountPlumbing } from '@syntrologie/sdk-contracts';
12
13
  import './AdaptiveChatBar';
13
14
  import './AdaptiveChatTrail';
15
+ /**
16
+ * LeaveAMessageMountable — mounts `<syntro-leave-a-message>`
17
+ * (adaptive-chatbot:leave-a-message). Escalation-only async human handoff:
18
+ * resolves the vendor bridge named in `supportChat.vendor` (per FEAT
19
+ * docs/superpowers/specs/2026-08-13-async-handoff-crisp-design.md) and
20
+ * passes the mount plumbing's `runtime` through exactly as
21
+ * `NavWidgetLitMountable` does — the element needs it to publish
22
+ * `supportchat:reply_received` on the runtime EventBus so tile
23
+ * `notifications` rules can badge the "You got mail!" toast.
24
+ */
25
+ export declare const LeaveAMessageMountable: {
26
+ mount(container: HTMLElement, config?: (Record<string, unknown> & MountPlumbing) | null): () => void;
27
+ /**
28
+ * I1 (final-review fixes 2026-08-13): without this, `WidgetRegistry`
29
+ * falls back to unmount+remount on every config patch (e.g. the agent
30
+ * editing the prefilled `message` mid-conversation), which destroys the
31
+ * live element and with it the visitor's in-flight escalation state
32
+ * (email already captured, an operator reply already sitting in
33
+ * `mail`/`flipped`). Mirrors `AdaptiveChatBarMountable.update`'s
34
+ * pattern: patch the presentational props in place, touch nothing
35
+ * else. `state`, `bridge`, and the operator-message thread are NEVER
36
+ * reset here.
37
+ */
38
+ update(container: HTMLElement, config?: (Record<string, unknown> & MountPlumbing) | null): void;
39
+ };
14
40
  export { ElementInstanceStore, TileHandler } from './elements';
15
41
  export { getElementInstanceStore, setElementInstanceStore } from './elements/store-singleton';
16
42
  export declare const runtime: {
@@ -19,7 +45,7 @@ export declare const runtime: {
19
45
  name: string;
20
46
  description: string;
21
47
  executors: never[];
22
- widgets: {
48
+ widgets: ({
23
49
  id: string;
24
50
  component: {
25
51
  mount(container: HTMLElement, mountConfig?: Record<string, unknown>): () => void;
@@ -29,7 +55,29 @@ export declare const runtime: {
29
55
  description: string;
30
56
  icon: string;
31
57
  };
32
- }[];
58
+ } | {
59
+ id: string;
60
+ component: {
61
+ mount(container: HTMLElement, config?: (Record<string, unknown> & MountPlumbing) | null): () => void;
62
+ /**
63
+ * I1 (final-review fixes 2026-08-13): without this, `WidgetRegistry`
64
+ * falls back to unmount+remount on every config patch (e.g. the agent
65
+ * editing the prefilled `message` mid-conversation), which destroys the
66
+ * live element and with it the visitor's in-flight escalation state
67
+ * (email already captured, an operator reply already sitting in
68
+ * `mail`/`flipped`). Mirrors `AdaptiveChatBarMountable.update`'s
69
+ * pattern: patch the presentational props in place, touch nothing
70
+ * else. `state`, `bridge`, and the operator-message thread are NEVER
71
+ * reset here.
72
+ */
73
+ update(container: HTMLElement, config?: (Record<string, unknown> & MountPlumbing) | null): void;
74
+ };
75
+ metadata: {
76
+ name: string;
77
+ description: string;
78
+ icon: string;
79
+ };
80
+ })[];
33
81
  };
34
82
  export default runtime;
35
83
  //# sourceMappingURL=runtime.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"runtime.d.ts","sourceRoot":"","sources":["../src/runtime.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAIH,OAAO,mBAAmB,CAAC;AAC3B,OAAO,qBAAqB,CAAC;AAW7B,OAAO,EAAE,oBAAoB,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AAC/D,OAAO,EAAE,uBAAuB,EAAE,uBAAuB,EAAE,MAAM,4BAA4B,CAAC;AAE9F,eAAO,MAAM,OAAO;;;;;;;;;;;;;;;;;CA4EnB,CAAC;AAEF,eAAe,OAAO,CAAC"}
1
+ {"version":3,"file":"runtime.d.ts","sourceRoot":"","sources":["../src/runtime.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAEH,OAAO,EAAE,KAAK,aAAa,EAAsB,MAAM,4BAA4B,CAAC;AAGpF,OAAO,mBAAmB,CAAC;AAC3B,OAAO,qBAAqB,CAAC;AAuC7B;;;;;;;;;GASG;AACH,eAAO,MAAM,sBAAsB;qBAChB,WAAW,WAAW,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,aAAa,CAAC,GAAG,IAAI;IAwBvF;;;;;;;;;;OAUG;sBACe,WAAW,WAAW,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,aAAa,CAAC,GAAG,IAAI;CASzF,CAAC;AAMF,OAAO,EAAE,oBAAoB,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AAC/D,OAAO,EAAE,uBAAuB,EAAE,uBAAuB,EAAE,MAAM,4BAA4B,CAAC;AAE9F,eAAO,MAAM,OAAO;;;;;;;;;;;;;;;;;;;6BArDD,WAAW,WAAW,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,aAAa,CAAC,GAAG,IAAI;YAwBvF;;;;;;;;;;eAUG;8BACe,WAAW,WAAW,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,aAAa,CAAC,GAAG,IAAI;;;;;;;;CA2GzF,CAAC;AAEF,eAAe,OAAO,CAAC"}
package/dist/runtime.js CHANGED
@@ -23,7 +23,8 @@ import "./chunk-OIGVZGSX.js";
23
23
  import {
24
24
  DIVE_DEEPER_EVENT,
25
25
  TILE_MOUNTED_EVENT,
26
- TILE_UNMOUNTED_EVENT
26
+ TILE_UNMOUNTED_EVENT,
27
+ stripMountPlumbing
27
28
  } from "./chunk-EACGDSJI.js";
28
29
  import "./chunk-UVKRO5ER.js";
29
30
 
@@ -1770,6 +1771,69 @@ function busEventToRawEvent(e) {
1770
1771
  return { event: e.name, timestamp: ts, properties: { ...e.props ?? {} } };
1771
1772
  }
1772
1773
 
1774
+ // src/support-chat/registry.ts
1775
+ var bridges = /* @__PURE__ */ new Map();
1776
+ function registerSupportChatBridge(bridge) {
1777
+ bridges.set(bridge.vendor, bridge);
1778
+ }
1779
+ function getSupportChatBridge(vendor) {
1780
+ return bridges.get(vendor);
1781
+ }
1782
+
1783
+ // src/support-chat/crisp.ts
1784
+ function crisp() {
1785
+ if (typeof window === "undefined") return void 0;
1786
+ return window.$crisp;
1787
+ }
1788
+ function push(cmd) {
1789
+ try {
1790
+ crisp()?.push(cmd);
1791
+ } catch (err) {
1792
+ console.warn("supportChat.bridge: crisp push failed", cmd[0], cmd[1], err);
1793
+ }
1794
+ }
1795
+ var crispBridge = {
1796
+ vendor: "crisp",
1797
+ detect: () => crisp() !== void 0,
1798
+ hideNative: () => push(["do", "chat:hide"]),
1799
+ setVisitorEmail: (email) => push(["set", "user:email", [email]]),
1800
+ sendMessage: (text) => push(["do", "message:send", ["text", text]]),
1801
+ onOperatorReply: (cb) => {
1802
+ let active = true;
1803
+ push([
1804
+ "on",
1805
+ "message:received",
1806
+ (msg) => {
1807
+ if (!active) return;
1808
+ push(["do", "chat:hide"]);
1809
+ if (typeof msg?.content === "string") cb(msg.content);
1810
+ }
1811
+ ]);
1812
+ return () => {
1813
+ active = false;
1814
+ };
1815
+ }
1816
+ };
1817
+ function registerCrispBridge() {
1818
+ registerSupportChatBridge(crispBridge);
1819
+ }
1820
+ registerCrispBridge();
1821
+
1822
+ // src/support-chat/boot.ts
1823
+ function bootSupportChat(cfg) {
1824
+ if (!cfg?.vendor) return void 0;
1825
+ registerCrispBridge();
1826
+ const bridge = getSupportChatBridge(cfg.vendor);
1827
+ if (!bridge || !bridge.detect()) {
1828
+ console.warn(
1829
+ `supportChat.bridge: vendor '${cfg.vendor}' not detected on this page \u2014 handoff disabled`
1830
+ );
1831
+ return void 0;
1832
+ }
1833
+ if (cfg.suppressNative) bridge.hideNative();
1834
+ return bridge;
1835
+ }
1836
+
1773
1837
  // src/AdaptiveChatBarMountable.ts
1774
1838
  var STATE_KEY = "__syntroChatBarMount";
1775
1839
  function getState(container) {
@@ -2060,6 +2124,7 @@ var AdaptiveChatBarMountable = {
2060
2124
  mount(container, mountConfig) {
2061
2125
  const cfg = mountConfig ?? {};
2062
2126
  configureTransportIfPossible(cfg);
2127
+ bootSupportChat(cfg.supportChat);
2063
2128
  wireDiveDeeperListener();
2064
2129
  const bar = document.createElement("adaptive-chat-bar");
2065
2130
  applyPlaceholder(bar, cfg);
@@ -2196,6 +2261,241 @@ var AdaptiveChipsStripMountable = {
2196
2261
  }
2197
2262
  };
2198
2263
 
2264
+ // src/LeaveAMessageLit.ts
2265
+ import { html, LitElement, nothing } from "lit";
2266
+ import { styleMap } from "lit/directives/style-map.js";
2267
+ var EMAIL_RE = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
2268
+ var box = {
2269
+ display: "flex",
2270
+ flexDirection: "column",
2271
+ gap: "var(--sc-content-item-gap, 10px)",
2272
+ fontFamily: "var(--sc-font-family, system-ui)",
2273
+ fontSize: "var(--sc-content-body-font-size, 13px)"
2274
+ };
2275
+ var fieldStyles = {
2276
+ padding: "var(--sc-content-item-padding, 8px 10px)",
2277
+ borderRadius: "var(--sc-content-border-radius, 8px)",
2278
+ border: "var(--sc-content-bubble-border, 1px solid currentColor)",
2279
+ background: "var(--sc-content-bubble-background, transparent)",
2280
+ color: "inherit",
2281
+ font: "inherit"
2282
+ };
2283
+ var buttonStyles = {
2284
+ // No chip padding token exists (ChipElementConfigZ only has
2285
+ // background/foreground/border/shadow) — plain constant, no fake var().
2286
+ padding: "10px 12px",
2287
+ borderRadius: "var(--sc-chip-border-radius, 9999px)",
2288
+ border: "var(--sc-chip-border, 1px solid currentColor)",
2289
+ background: "var(--sc-chip-background, transparent)",
2290
+ color: "var(--sc-tile-title-color, inherit)",
2291
+ fontWeight: "600",
2292
+ cursor: "pointer",
2293
+ font: "inherit"
2294
+ };
2295
+ var textStyles = {
2296
+ margin: "0"
2297
+ };
2298
+ var mutedTextStyles = {
2299
+ margin: "0",
2300
+ // No opacity token exists in any publishable section — plain constant.
2301
+ opacity: "0.8"
2302
+ };
2303
+ var PERSIST_NAMESPACE = "supportchat";
2304
+ var PERSIST_KEY = "leave-a-message";
2305
+ var LeaveAMessageLit = class extends LitElement {
2306
+ constructor() {
2307
+ super(...arguments);
2308
+ this.message = "";
2309
+ this.contextSummary = "";
2310
+ this.state = "form";
2311
+ this._email = "";
2312
+ this._reply = "";
2313
+ this._operatorMessages = [];
2314
+ this._onReply = (text) => {
2315
+ this._operatorMessages = [...this._operatorMessages, text];
2316
+ if (this.state !== "flipped") {
2317
+ this.state = "mail";
2318
+ this.runtime?.events.publish("supportchat:reply_received", { text });
2319
+ this._toastReplyArrived();
2320
+ this.dispatchEvent(
2321
+ new CustomEvent("syntro:supportchat-reply", {
2322
+ bubbles: true,
2323
+ composed: true,
2324
+ detail: { text }
2325
+ })
2326
+ );
2327
+ }
2328
+ this._persist();
2329
+ this.requestUpdate();
2330
+ };
2331
+ this._submit = () => {
2332
+ if (!this.bridge || !EMAIL_RE.test(this._email)) return;
2333
+ this.bridge.setVisitorEmail(this._email);
2334
+ const context = this.contextSummary ? `[Site assistant context: ${this.contextSummary}]
2335
+ ` : "";
2336
+ this.bridge.sendMessage(`${context}${this.message}`);
2337
+ this.state = "sent";
2338
+ this._persist();
2339
+ };
2340
+ this._flip = () => {
2341
+ this.state = "flipped";
2342
+ this._persist();
2343
+ };
2344
+ this._sendReply = () => {
2345
+ if (!this.bridge || !this._reply.trim()) return;
2346
+ this.bridge.sendMessage(this._reply.trim());
2347
+ this._reply = "";
2348
+ this.requestUpdate();
2349
+ };
2350
+ }
2351
+ createRenderRoot() {
2352
+ return this;
2353
+ }
2354
+ connectedCallback() {
2355
+ super.connectedCallback();
2356
+ this._loadPersisted();
2357
+ }
2358
+ updated() {
2359
+ if (this.state !== "form" && this.bridge && this.bridge.detect() && !this._unsubscribe) {
2360
+ this._unsubscribe = this.bridge.onOperatorReply(this._onReply);
2361
+ }
2362
+ }
2363
+ disconnectedCallback() {
2364
+ this._unsubscribe?.();
2365
+ this._unsubscribe = void 0;
2366
+ super.disconnectedCallback();
2367
+ }
2368
+ /** Read the injected persistence store, if any (I2). */
2369
+ _persistStore() {
2370
+ return this.runtime?.state?.user?.ns?.(PERSIST_NAMESPACE);
2371
+ }
2372
+ /** Hydrate {state, operatorMessages, email} from a prior page's mount. */
2373
+ _loadPersisted() {
2374
+ const store = this._persistStore();
2375
+ if (!store) return;
2376
+ const saved = store.get(PERSIST_KEY);
2377
+ if (!saved) return;
2378
+ if (saved.state) this.state = saved.state;
2379
+ if (Array.isArray(saved.operatorMessages)) this._operatorMessages = saved.operatorMessages;
2380
+ if (typeof saved.email === "string") this._email = saved.email;
2381
+ }
2382
+ /** Snapshot {state, operatorMessages, email} so the NEXT mount (a fresh
2383
+ * page in the same session) can pick the thread back up. */
2384
+ _persist() {
2385
+ const store = this._persistStore();
2386
+ if (!store) return;
2387
+ const state = this.state;
2388
+ const operatorMessages = this._operatorMessages;
2389
+ const email = this._email;
2390
+ const snapshot = { state, operatorMessages, email };
2391
+ store.set(PERSIST_KEY, snapshot);
2392
+ }
2393
+ /**
2394
+ * Raise the reply toast through the canvas element. Pierces shadow roots to
2395
+ * find `<smart-canvas>` (same resolution AdaptiveChatBarMountable uses), and
2396
+ * no-ops when no canvas is present — the tile's own "You got mail!" face is
2397
+ * always the durable signal.
2398
+ */
2399
+ _toastReplyArrived() {
2400
+ if (typeof document === "undefined") return;
2401
+ const title = this.replyToast?.title?.trim();
2402
+ if (!title) return;
2403
+ const find = (root) => {
2404
+ const direct = root.querySelector("smart-canvas");
2405
+ if (direct) return direct;
2406
+ for (const el of root.querySelectorAll("*")) {
2407
+ const sr = el.shadowRoot;
2408
+ if (sr) {
2409
+ const found = find(sr);
2410
+ if (found) return found;
2411
+ }
2412
+ }
2413
+ return null;
2414
+ };
2415
+ try {
2416
+ const canvas = find(document);
2417
+ canvas?.previewNotification?.({ title, body: this.replyToast?.body });
2418
+ } catch (err) {
2419
+ console.warn("supportChat.widget: reply toast failed", err);
2420
+ }
2421
+ }
2422
+ render() {
2423
+ if (!this.bridge || !this.bridge.detect()) return nothing;
2424
+ if (this.state === "form") {
2425
+ return html`<div data-lam style=${styleMap(box)}>
2426
+ <textarea
2427
+ data-lam-message
2428
+ rows="3"
2429
+ style=${styleMap(fieldStyles)}
2430
+ .value=${this.message}
2431
+ @input=${(e) => {
2432
+ this.message = e.target.value;
2433
+ }}
2434
+ ></textarea>
2435
+ <input
2436
+ data-lam-email
2437
+ type="email"
2438
+ placeholder="Your email — so the team can reach you"
2439
+ style=${styleMap(fieldStyles)}
2440
+ @input=${(e) => {
2441
+ this._email = e.target.value;
2442
+ this.requestUpdate();
2443
+ }}
2444
+ />
2445
+ <button type="button" data-lam-send style=${styleMap(buttonStyles)} @click=${this._submit}>
2446
+ Send to the team
2447
+ </button>
2448
+ </div>`;
2449
+ }
2450
+ if (this.state === "sent") {
2451
+ return html`<div data-lam style=${styleMap(box)}>
2452
+ <p style=${styleMap(mutedTextStyles)}>
2453
+ Sent. The team will reply here — or by email if you step away.
2454
+ </p>
2455
+ </div>`;
2456
+ }
2457
+ if (this.state === "mail") {
2458
+ return html`<div data-lam style=${styleMap(box)}>
2459
+ <button type="button" data-lam-mail style=${styleMap(buttonStyles)} @click=${this._flip}>
2460
+ 📬 You got mail! — tap to read
2461
+ </button>
2462
+ </div>`;
2463
+ }
2464
+ return html`<div data-lam style=${styleMap(box)}>
2465
+ ${this._operatorMessages.map(
2466
+ (m) => html`<p data-lam-operator style=${styleMap(textStyles)}>${m}</p>`
2467
+ )}
2468
+ <input
2469
+ data-lam-reply
2470
+ type="text"
2471
+ placeholder="Reply to the team…"
2472
+ style=${styleMap(fieldStyles)}
2473
+ .value=${this._reply}
2474
+ @input=${(e) => {
2475
+ this._reply = e.target.value;
2476
+ }}
2477
+ />
2478
+ <button type="button" data-lam-reply-send style=${styleMap(buttonStyles)} @click=${this._sendReply}>
2479
+ Send
2480
+ </button>
2481
+ </div>`;
2482
+ }
2483
+ };
2484
+ LeaveAMessageLit.properties = {
2485
+ bridge: { attribute: false },
2486
+ message: { type: String },
2487
+ contextSummary: { attribute: false },
2488
+ state: { type: String },
2489
+ runtime: { attribute: false },
2490
+ replyToast: { attribute: false }
2491
+ };
2492
+ function registerLeaveAMessageLit() {
2493
+ if (typeof customElements !== "undefined" && !customElements.get("syntro-leave-a-message")) {
2494
+ customElements.define("syntro-leave-a-message", LeaveAMessageLit);
2495
+ }
2496
+ }
2497
+ registerLeaveAMessageLit();
2498
+
2199
2499
  // src/NavLinkMountable.ts
2200
2500
  var STATE_KEY3 = "__syntroNavLinkMount";
2201
2501
  var SAFE_NAVIGATION_PROTOCOLS = /* @__PURE__ */ new Set(["http:", "https:"]);
@@ -2317,6 +2617,58 @@ var TextAnswerMountable = {
2317
2617
  };
2318
2618
 
2319
2619
  // src/runtime.ts
2620
+ var LEAVE_A_MESSAGE_STATE_KEY = "__syntroLeaveAMessageMount";
2621
+ function getLeaveAMessageState(container) {
2622
+ return container[LEAVE_A_MESSAGE_STATE_KEY] ?? null;
2623
+ }
2624
+ function setLeaveAMessageState(container, state) {
2625
+ container[LEAVE_A_MESSAGE_STATE_KEY] = state;
2626
+ }
2627
+ var LeaveAMessageMountable = {
2628
+ mount(container, config) {
2629
+ registerLeaveAMessageLit();
2630
+ const el = document.createElement("syntro-leave-a-message");
2631
+ const incoming = config ?? null;
2632
+ const stripped = stripMountPlumbing(incoming) ?? {};
2633
+ const supportChat = stripped.supportChat;
2634
+ const runtime2 = incoming?.runtime;
2635
+ Object.assign(el, {
2636
+ bridge: bootSupportChat(supportChat),
2637
+ message: stripped.message ?? "",
2638
+ contextSummary: stripped.context_summary ?? "",
2639
+ // Visitor-facing toast copy from the workspace config — see
2640
+ // LeaveAMessageLit#replyToast (presentation is configuration).
2641
+ replyToast: stripped.replyToast,
2642
+ runtime: runtime2
2643
+ });
2644
+ container.appendChild(el);
2645
+ setLeaveAMessageState(container, { el });
2646
+ return () => {
2647
+ setLeaveAMessageState(container, null);
2648
+ el.remove();
2649
+ };
2650
+ },
2651
+ /**
2652
+ * I1 (final-review fixes 2026-08-13): without this, `WidgetRegistry`
2653
+ * falls back to unmount+remount on every config patch (e.g. the agent
2654
+ * editing the prefilled `message` mid-conversation), which destroys the
2655
+ * live element and with it the visitor's in-flight escalation state
2656
+ * (email already captured, an operator reply already sitting in
2657
+ * `mail`/`flipped`). Mirrors `AdaptiveChatBarMountable.update`'s
2658
+ * pattern: patch the presentational props in place, touch nothing
2659
+ * else. `state`, `bridge`, and the operator-message thread are NEVER
2660
+ * reset here.
2661
+ */
2662
+ update(container, config) {
2663
+ const mounted = getLeaveAMessageState(container);
2664
+ if (!mounted) return;
2665
+ const stripped = stripMountPlumbing(config ?? null) ?? {};
2666
+ if (typeof stripped.message === "string") mounted.el.message = stripped.message;
2667
+ if (typeof stripped.context_summary === "string") {
2668
+ mounted.el.contextSummary = stripped.context_summary;
2669
+ }
2670
+ }
2671
+ };
2320
2672
  var runtime = {
2321
2673
  id: "adaptive-chatbot",
2322
2674
  version: "2.0.0",
@@ -2387,12 +2739,25 @@ var runtime = {
2387
2739
  description: "Same-origin navigation button \u2014 used as a chip payload for LLM-authored nav suggestions.",
2388
2740
  icon: "\u2192"
2389
2741
  }
2742
+ },
2743
+ // Escalation-only async human handoff: email + message into the site's
2744
+ // own support-chat inbox (Crisp first); operator replies flip the tile
2745
+ // ("You got mail!"). See docs/superpowers/specs/2026-08-13-async-handoff-crisp-design.md.
2746
+ {
2747
+ id: "adaptive-chatbot:leave-a-message",
2748
+ component: LeaveAMessageMountable,
2749
+ metadata: {
2750
+ name: "Leave a Message",
2751
+ description: `Escalation-only async human handoff: email + message into the site's own support-chat inbox; operator replies flip the tile ("You got mail!")`,
2752
+ icon: "\u{1F4EC}"
2753
+ }
2390
2754
  }
2391
2755
  ]
2392
2756
  };
2393
2757
  var runtime_default = runtime;
2394
2758
  export {
2395
2759
  ElementInstanceStore,
2760
+ LeaveAMessageMountable,
2396
2761
  TileHandler,
2397
2762
  runtime_default as default,
2398
2763
  getElementInstanceStore,