@syntrologie/adapt-chatbot 2.44.0 → 2.46.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.
package/dist/runtime.js CHANGED
@@ -9,16 +9,16 @@ import {
9
9
  decodeMutationEnvelope,
10
10
  fetchMountedElements,
11
11
  renderFallbackHtml
12
- } from "./chunk-3JCJDTHS.js";
12
+ } from "./chunk-3ZF4VCPX.js";
13
13
  import {
14
14
  pickPersona
15
- } from "./chunk-FXHYE6FY.js";
15
+ } from "./chunk-MRHETSTW.js";
16
16
  import {
17
17
  getElementInstanceStore,
18
18
  setElementInstanceStore
19
19
  } from "./chunk-EWPPVPJ4.js";
20
- import "./chunk-EKGGGUI2.js";
21
- import "./chunk-VZIUXXAM.js";
20
+ import "./chunk-7TT7YYML.js";
21
+ import "./chunk-TZA7572J.js";
22
22
  import "./chunk-NWVTKK75.js";
23
23
  import {
24
24
  DIVE_DEEPER_EVENT,
@@ -1308,6 +1308,27 @@ var ChatTransport = class {
1308
1308
  this._status = "error";
1309
1309
  return;
1310
1310
  }
1311
+ // ── Deliberate no-ops, made EXPLICIT so the exhaustiveness check below
1312
+ // can do its job. All four were already falling through to nothing; the
1313
+ // `never` guard is what surfaced that they were never written down.
1314
+ //
1315
+ // The three tool-call events belong to a deferred-tool approval flow
1316
+ // that exists on <syntro-chat> (the editor / assistant surface), not
1317
+ // here: the ambient visitor trail renders no approve/deny affordance, so
1318
+ // there is nothing for an interrupt, a cross-run result, or a
1319
+ // cancellation to update. `run-finished` (BUG-1787292410) settles
1320
+ // messages the run left streaming in <syntro-chat>; this surface tracks
1321
+ // run liveness through `typing`, which the transport emits alongside it.
1322
+ case "tool-call-deferred":
1323
+ case "tool-call-result":
1324
+ case "tool-call-cancelled":
1325
+ case "run-finished":
1326
+ return;
1327
+ default: {
1328
+ const _exhaustive = event;
1329
+ void _exhaustive;
1330
+ return;
1331
+ }
1311
1332
  }
1312
1333
  }
1313
1334
  };
@@ -2699,6 +2720,21 @@ var LeaveAMessageLit = class extends LitElement {
2699
2720
  this._uid = `lam-${++lamUidCounter}`;
2700
2721
  this._email = "";
2701
2722
  this._reply = "";
2723
+ /**
2724
+ * Has the VISITOR typed something not yet consumed? Set only by the fields'
2725
+ * own `@input` handlers, cleared on every state transition (the input has
2726
+ * either been sent or explicitly declined). Drives the Escape guard
2727
+ * (BUG-1787284155).
2728
+ *
2729
+ * Deliberately a flag and NOT "current value vs. a baseline captured at
2730
+ * mount". The card mounts PREFILLED, and `LeaveAMessageMountable.update()`
2731
+ * patches `message` in place mid-conversation whenever the agent rewrites
2732
+ * the prefill — which is the case that method exists for. A captured
2733
+ * baseline goes stale on exactly that patch, and every later Escape gets
2734
+ * swallowed on a card the visitor never touched (adversarial review F1).
2735
+ * An `@input` event is the only thing that means a person typed.
2736
+ */
2737
+ this._touched = false;
2702
2738
  /** Visitor↔operator conversation, in order — rendered as a chat
2703
2739
  * transcript once the visitor taps into the mail state (see render()'s
2704
2740
  * 'flipped' branch). Seeded with the visitor's own compose-step message
@@ -2748,6 +2784,7 @@ var LeaveAMessageLit = class extends LitElement {
2748
2784
  this.bridge.sendMessage(`${context}${this.message}`);
2749
2785
  this._transcript = [...this._transcript, { role: "visitor", text: this.message.trim() }];
2750
2786
  this.state = "email";
2787
+ this._touched = false;
2751
2788
  this._persist();
2752
2789
  };
2753
2790
  /** Email step → sent. Blank email is a valid submission (the field is
@@ -2759,6 +2796,7 @@ var LeaveAMessageLit = class extends LitElement {
2759
2796
  if (trimmed && !EMAIL_RE.test(trimmed)) return;
2760
2797
  if (trimmed) this.bridge.setVisitorEmail(trimmed);
2761
2798
  this.state = "sent";
2799
+ this._touched = false;
2762
2800
  this._persist();
2763
2801
  };
2764
2802
  /** Explicit decline — advances regardless of whatever is typed, without
@@ -2767,10 +2805,12 @@ var LeaveAMessageLit = class extends LitElement {
2767
2805
  * have meant to send. */
2768
2806
  this._skipEmail = () => {
2769
2807
  this.state = "sent";
2808
+ this._touched = false;
2770
2809
  this._persist();
2771
2810
  };
2772
2811
  this._flip = () => {
2773
2812
  this.state = "flipped";
2813
+ this._touched = false;
2774
2814
  this._persist();
2775
2815
  };
2776
2816
  this._sendReply = () => {
@@ -2779,9 +2819,38 @@ var LeaveAMessageLit = class extends LitElement {
2779
2819
  this.bridge.sendMessage(text);
2780
2820
  this._transcript = [...this._transcript, { role: "visitor", text }];
2781
2821
  this._reply = "";
2822
+ this._touched = false;
2782
2823
  this._persist();
2783
2824
  this.requestUpdate();
2784
2825
  };
2826
+ /**
2827
+ * Escape must not silently discard a draft (BUG-1787284155).
2828
+ *
2829
+ * The runtime's canvas Escape handler (`CanvasDialogA11y`,
2830
+ * `packages/runtime-sdk/src/a11y/canvasDialog.ts`) closes the surface from a
2831
+ * document-level listener — but stands down on `e.defaultPrevented`,
2832
+ * explicitly so an inner component that owns unsaved state can claim the
2833
+ * key. That seam is what this uses; the widget owns the decision, not the
2834
+ * runtime, because only the widget knows whether anything is at stake.
2835
+ *
2836
+ * A CLEAN card lets Escape through and the canvas closes — normal dialog
2837
+ * behaviour, and the reflex the visitor expects. A DIRTY one swallows the
2838
+ * first Escape and blurs the field: visible feedback that the key did
2839
+ * something, the text still on screen, and a second Escape (focus now
2840
+ * outside the field) closes as usual. Defended once, not held hostage.
2841
+ * WCAG 3.3.4 in spirit — losing typed input to a reflex keystroke, with no
2842
+ * prompt and no undo, is the failure this prevents.
2843
+ *
2844
+ * Scope is the card's own subtree (see the host binding in
2845
+ * `connectedCallback`). A visitor whose focus has moved on to the host page
2846
+ * gets the normal dismiss — the widget claims only keys that reach it.
2847
+ */
2848
+ this._onKeyDown = (e) => {
2849
+ if (e.key !== "Escape") return;
2850
+ if (!this._hasUnsavedInput()) return;
2851
+ e.preventDefault();
2852
+ e.target?.blur?.();
2853
+ };
2785
2854
  }
2786
2855
  createRenderRoot() {
2787
2856
  return this;
@@ -2789,6 +2858,7 @@ var LeaveAMessageLit = class extends LitElement {
2789
2858
  connectedCallback() {
2790
2859
  super.connectedCallback();
2791
2860
  this._loadPersisted();
2861
+ this.addEventListener("keydown", this._onKeyDown);
2792
2862
  }
2793
2863
  updated() {
2794
2864
  if (this.state !== "form" && this.bridge && this.bridge.detect() && !this._unsubscribe) {
@@ -2796,6 +2866,7 @@ var LeaveAMessageLit = class extends LitElement {
2796
2866
  }
2797
2867
  }
2798
2868
  disconnectedCallback() {
2869
+ this.removeEventListener("keydown", this._onKeyDown);
2799
2870
  this._unsubscribe?.();
2800
2871
  this._unsubscribe = void 0;
2801
2872
  super.disconnectedCallback();
@@ -2869,6 +2940,10 @@ var LeaveAMessageLit = class extends LitElement {
2869
2940
  status: "complete"
2870
2941
  }));
2871
2942
  }
2943
+ /** Does the visitor have text here that closing the card would destroy? */
2944
+ _hasUnsavedInput() {
2945
+ return this._touched;
2946
+ }
2872
2947
  render() {
2873
2948
  if (!this.bridge || !this.bridge.detect()) return nothing;
2874
2949
  const copy = this.copy;
@@ -2890,6 +2965,7 @@ var LeaveAMessageLit = class extends LitElement {
2890
2965
  .value=${this.message}
2891
2966
  @input=${(e) => {
2892
2967
  this.message = e.target.value;
2968
+ this._touched = true;
2893
2969
  }}
2894
2970
  ></textarea>
2895
2971
  ${copy?.sendLabel ? html`<button
@@ -2930,6 +3006,7 @@ var LeaveAMessageLit = class extends LitElement {
2930
3006
  @blur=${this._focusOff}
2931
3007
  @input=${(e) => {
2932
3008
  this._email = e.target.value;
3009
+ this._touched = true;
2933
3010
  this.requestUpdate();
2934
3011
  }}
2935
3012
  />
@@ -2996,6 +3073,7 @@ var LeaveAMessageLit = class extends LitElement {
2996
3073
  .value=${this._reply}
2997
3074
  @input=${(e) => {
2998
3075
  this._reply = e.target.value;
3076
+ this._touched = true;
2999
3077
  this.requestUpdate();
3000
3078
  }}
3001
3079
  />