@vinaverse/client 0.1.0-alpha.11 → 0.1.0-alpha.13

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 (48) hide show
  1. package/README.md +3 -1
  2. package/dist/agent/index.cjs +187 -24
  3. package/dist/agent/index.cjs.map +1 -1
  4. package/dist/agent/index.d.cts +3 -3
  5. package/dist/agent/index.d.ts +3 -3
  6. package/dist/agent/index.js +3 -3
  7. package/dist/backend/index.cjs +3 -0
  8. package/dist/backend/index.cjs.map +1 -1
  9. package/dist/backend/index.d.cts +3 -3
  10. package/dist/backend/index.d.ts +3 -3
  11. package/dist/backend/index.js +6 -3
  12. package/dist/backend/index.js.map +1 -1
  13. package/dist/{chunk-46D3FSLU.js → chunk-5HKZB2CJ.js} +14 -6
  14. package/dist/chunk-5HKZB2CJ.js.map +1 -0
  15. package/dist/{chunk-GWLLUOFY.js → chunk-LAB2FPXF.js} +31 -8
  16. package/dist/chunk-LAB2FPXF.js.map +1 -0
  17. package/dist/{chunk-JI6HUXH3.js → chunk-WOMAGTI5.js} +149 -20
  18. package/dist/chunk-WOMAGTI5.js.map +1 -0
  19. package/dist/conversation/index.cjs +40 -9
  20. package/dist/conversation/index.cjs.map +1 -1
  21. package/dist/conversation/index.d.cts +4 -4
  22. package/dist/conversation/index.d.ts +4 -4
  23. package/dist/conversation/index.js +3 -3
  24. package/dist/history/index.cjs +7 -1
  25. package/dist/history/index.cjs.map +1 -1
  26. package/dist/history/index.d.cts +10 -4
  27. package/dist/history/index.d.ts +10 -4
  28. package/dist/history/index.js +10 -5
  29. package/dist/history/index.js.map +1 -1
  30. package/dist/index.cjs +29 -6
  31. package/dist/index.cjs.map +1 -1
  32. package/dist/index.d.cts +1 -1
  33. package/dist/index.d.ts +1 -1
  34. package/dist/index.js +1 -1
  35. package/dist/{runtime-CNgz2_8G.d.ts → runtime-44JlV32O.d.ts} +92 -14
  36. package/dist/{runtime-6O0qIPmo.d.cts → runtime-DWiB-XHX.d.cts} +92 -14
  37. package/dist/{session-D0ijOQX3.d.ts → session-lPGrKdhf.d.cts} +5 -0
  38. package/dist/{session-D0ijOQX3.d.cts → session-lPGrKdhf.d.ts} +5 -0
  39. package/dist/{store-CGRSxVcL.d.ts → store-Cjw9YmXA.d.ts} +6 -1
  40. package/dist/{store-CxoHwd64.d.cts → store-D-an5vch.d.cts} +6 -1
  41. package/dist/testing/index.cjs.map +1 -1
  42. package/dist/testing/index.d.cts +3 -3
  43. package/dist/testing/index.d.ts +3 -3
  44. package/dist/testing/index.js +3 -3
  45. package/package.json +2 -2
  46. package/dist/chunk-46D3FSLU.js.map +0 -1
  47. package/dist/chunk-GWLLUOFY.js.map +0 -1
  48. package/dist/chunk-JI6HUXH3.js.map +0 -1
package/README.md CHANGED
@@ -183,7 +183,9 @@ against the session it was given in; a reply restored from the device or a resum
183
183
  `{ screen: 'other' }`, no entity); revisions only grow across a conversation's sessions (`placeRevision` on the
184
184
  session request), a `context_conflict` (409, with `current_revision`) sends the newest place once more above it, only while the session
185
185
  and the person are the same, then says `moved`,
186
- and a telling lands only on the session it was told to. `onEvent` also says `message_sent`, `reply_completed` and
186
+ and a telling lands only on the session it was told to. One update is in flight per conversation: a place told meanwhile
187
+ (`show` and `tell` in one move, rapid moves) waits, only the newest, and goes next; a refusal that is no conflict is said
188
+ through `onWarning` (`context_refused`) in development builds. `onEvent` also says `message_sent`, `reply_completed` and
187
189
  `card_shown` (never the words), its `key` is a salted SHA-256 hash of the conversation's key (`hashKey`), never the person's id: stable per install
188
190
  (the salt is kept at `<persistenceKey>:salt` in your storage, a new one after `erase()`), and
189
191
  `session_opened` says where the chat was opened from (`entryPoint`: `launcher`, `ask`, `deep_link`, or your own).
@@ -19,7 +19,8 @@ var CARD_KINDS = {
19
19
  question: true,
20
20
  proposal: true,
21
21
  next_step: true,
22
- links: true
22
+ links: true,
23
+ anchor: true
23
24
  };
24
25
  function isKnownCard(card) {
25
26
  return Object.hasOwn(CARD_KINDS, card.kind);
@@ -129,6 +130,11 @@ var RECONNECTABLE = /* @__PURE__ */ new Set(["server_restart"]);
129
130
  var NEW_SESSION = /* @__PURE__ */ new Set(["idle", "expired", "limit_reached"]);
130
131
  var str = (v) => typeof v === "string" ? v : void 0;
131
132
  var ConversationEngine = class {
133
+ /** `now`: the clock a message is stamped by when the server gives no time. */
134
+ constructor(now = Date.now) {
135
+ this.now = now;
136
+ }
137
+ now;
132
138
  sessionId = null;
133
139
  conversationId = null;
134
140
  connection = null;
@@ -317,7 +323,14 @@ var ConversationEngine = class {
317
323
  case "message.staff": {
318
324
  const id = str(event.message_id);
319
325
  if (id !== void 0)
320
- this.say("staff", str(event.text) ?? "", `s:${id}`);
326
+ this.say(
327
+ "staff",
328
+ str(event.text) ?? "",
329
+ `s:${id}`,
330
+ {},
331
+ void 0,
332
+ str(event.at)
333
+ );
321
334
  return;
322
335
  }
323
336
  case "input.received": {
@@ -399,7 +412,9 @@ var ConversationEngine = class {
399
412
  role,
400
413
  str(item.text) ?? "",
401
414
  transcriptKey(role, str(item.id), str(item.at)),
402
- item.interrupted === true ? { partial: "interrupted" } : {}
415
+ item.interrupted === true ? { partial: "interrupted" } : {},
416
+ void 0,
417
+ str(item.at)
403
418
  );
404
419
  }
405
420
  }
@@ -444,7 +459,8 @@ var ConversationEngine = class {
444
459
  text2,
445
460
  key,
446
461
  partial ? { partial } : {},
447
- at
462
+ at,
463
+ str(item.at)
448
464
  );
449
465
  else
450
466
  this.messages = this.messages.map((m) => {
@@ -572,9 +588,16 @@ var ConversationEngine = class {
572
588
  this.unqueue(id);
573
589
  }
574
590
  /** Adds a message once: a key already held (a frame seen twice) changes nothing. */
575
- say(role, text2, key, extra = {}, seq) {
591
+ say(role, text2, key, extra = {}, seq, at) {
576
592
  if (this.messages.some((m) => m.key === key)) return;
577
- this.messages.push({ role, text: text2, key, seq: seq ?? this.seq++, ...extra });
593
+ this.messages.push({
594
+ role,
595
+ text: text2,
596
+ key,
597
+ seq: seq ?? this.seq++,
598
+ ...extra,
599
+ at: at || new Date(this.now()).toISOString()
600
+ });
578
601
  }
579
602
  /** The turn's streamed text, kept as the agent's message when the turn ends without its `message.completed`. */
580
603
  cutOff(partial) {
@@ -1547,6 +1570,7 @@ function buildTimeline(input, previous) {
1547
1570
  role: "person",
1548
1571
  text: sent.text,
1549
1572
  state: pending,
1573
+ at: sent.at,
1550
1574
  ...pending === "not_sent" ? {
1551
1575
  rejection: rejection ?? {
1552
1576
  code: state.refused[id] ?? "",
@@ -1598,7 +1622,8 @@ function messageItem(m) {
1598
1622
  text: m.text,
1599
1623
  state: m.received ? "delivered" : "sent",
1600
1624
  ...m.partial ? { partial: m.partial } : {},
1601
- ...m.refusal ? { refusal: true } : {}
1625
+ ...m.refusal ? { refusal: true } : {},
1626
+ at: m.at
1602
1627
  };
1603
1628
  }
1604
1629
  function equal(a, b) {
@@ -1632,7 +1657,9 @@ var OUTCOMES2 = /* @__PURE__ */ new Set([
1632
1657
  ]);
1633
1658
  var ConversationStore = class {
1634
1659
  options;
1635
- engine = new ConversationEngine();
1660
+ engine = new ConversationEngine(
1661
+ () => this.options.clock?.now() ?? Date.now()
1662
+ );
1636
1663
  session = null;
1637
1664
  info = null;
1638
1665
  getting = null;
@@ -1815,7 +1842,11 @@ var ConversationStore = class {
1815
1842
  if ([...text2].length > MAX_TEXT)
1816
1843
  throw new RangeError(`A message is at most ${MAX_TEXT} characters.`);
1817
1844
  const id = (this.options.createId ?? randomId)();
1818
- this.sent.set(id, { text: text2, order: lastOrder(this.engine.state()) + 0.5 });
1845
+ this.sent.set(id, {
1846
+ text: text2,
1847
+ order: lastOrder(this.engine.state()) + 0.5,
1848
+ at: new Date(this.options.clock?.now() ?? Date.now()).toISOString()
1849
+ });
1819
1850
  this.tellActivity({ type: "message_sent", id });
1820
1851
  if (!this.session) {
1821
1852
  this.early.set(id, text2);
@@ -2456,6 +2487,65 @@ function shallowEqual(a, b) {
2456
2487
  }
2457
2488
 
2458
2489
  // src/agent/runtime.ts
2490
+ var ANCHOR_TYPES = /* @__PURE__ */ new Set([
2491
+ "booking",
2492
+ "rental",
2493
+ "professional",
2494
+ "workspace",
2495
+ "service",
2496
+ "class",
2497
+ "policy"
2498
+ ]);
2499
+ function anchorRef(type, id) {
2500
+ return ANCHOR_TYPES.has(type) && /^[A-Za-z0-9][A-Za-z0-9_.:-]{0,127}$/.test(id) ? { type, id } : null;
2501
+ }
2502
+ var JOINS = [
2503
+ 768,
2504
+ 879,
2505
+ 6832,
2506
+ 6911,
2507
+ 7616,
2508
+ 7679,
2509
+ 8400,
2510
+ 8447,
2511
+ 65056,
2512
+ 65071,
2513
+ 8205,
2514
+ 8205,
2515
+ 65038,
2516
+ 65039,
2517
+ 127995,
2518
+ 127999
2519
+ ];
2520
+ var joins = (point) => {
2521
+ const c = point?.codePointAt(0) ?? -1;
2522
+ for (let i = 0; i < JOINS.length; i += 2)
2523
+ if (c >= JOINS[i] && c <= JOINS[i + 1]) return true;
2524
+ return false;
2525
+ };
2526
+ function entryLabel(label) {
2527
+ let out = "";
2528
+ let size = 0;
2529
+ const points = Array.from(label.trim());
2530
+ for (let i = 0; i < points.length; ) {
2531
+ let end = i + 1;
2532
+ while (end < points.length && (joins(points[end]) || points[end - 1] === "\u200D"))
2533
+ end++;
2534
+ if (size + end - i > 60) break;
2535
+ out += points.slice(i, end).join("");
2536
+ size += end - i;
2537
+ i = end;
2538
+ }
2539
+ return out || void 0;
2540
+ }
2541
+ function askAboutKey(key, place) {
2542
+ return { ...key, thread: `ask:${place.entity.type}:${place.entity.id}` };
2543
+ }
2544
+ function entryOf(entry) {
2545
+ if (!/^[a-z][a-z0-9_]{0,31}$/.test(entry.point)) return null;
2546
+ const label = entry.label === void 0 ? void 0 : entryLabel(entry.label);
2547
+ return label ? { point: entry.point, label } : { point: entry.point };
2548
+ }
2459
2549
  var AgentRefusal = class extends Error {
2460
2550
  constructor(kind, options = {}) {
2461
2551
  super(
@@ -2590,6 +2680,7 @@ var samePlace = (a, b) => a?.screen === b?.screen && (a?.entity?.type ?? null) =
2590
2680
  function usable(session, insecure) {
2591
2681
  return typeof session.id === "string" && session.id !== "" && (insecure.realtime === true || /^wss:\/\//i.test(session.realtime_url ?? "")) && (insecure.turns === true || /^https:\/\//i.test(session.turns_url ?? "")) && Boolean(session.client_secret);
2592
2682
  }
2683
+ var devBuild = () => globalThis.__DEV__ === true;
2593
2684
  function warnInsecure(insecure, warn) {
2594
2685
  if (!(insecure.realtime || insecure.turns)) return;
2595
2686
  if (globalThis.__DEV__ === true) return;
@@ -2608,6 +2699,7 @@ function createAgentRuntime(options) {
2608
2699
  };
2609
2700
  const clock = options.clock ?? SYSTEM_CLOCK;
2610
2701
  const keepAlive = options.keepAlive ?? "session";
2702
+ const graceMs = options.graceMs ?? 6e4;
2611
2703
  const staleMs = options.statusStaleMs ?? 5 * 6e4;
2612
2704
  const insecure = options.insecureAddresses ?? {};
2613
2705
  warnInsecure(insecure, options.onWarning ?? ((m) => console.warn(m)));
@@ -2627,6 +2719,7 @@ function createAgentRuntime(options) {
2627
2719
  changed();
2628
2720
  };
2629
2721
  let sessionName = null;
2722
+ const names = /* @__PURE__ */ new Map();
2630
2723
  let generation = 0;
2631
2724
  const keyOf = (key) => agentKey(key, options.agent ?? null);
2632
2725
  const prefix = options.conversation?.persistenceKey ?? "vinaverse.agent";
@@ -2709,6 +2802,7 @@ function createAgentRuntime(options) {
2709
2802
  if (!entry) return;
2710
2803
  live.delete(k);
2711
2804
  clearTimeout(entry.timer);
2805
+ clearTimeout(entry.grace);
2712
2806
  entry.store.dispose();
2713
2807
  emit({ type: "conversation_ended", key: k });
2714
2808
  changed();
@@ -2734,7 +2828,9 @@ function createAgentRuntime(options) {
2734
2828
  agent: options.agent ?? null,
2735
2829
  place: entry.place,
2736
2830
  placeRevision: ++entry.revision,
2737
- entryPoint: entry.entryPoint
2831
+ entryPoint: entry.entryPoint,
2832
+ ...options.sendAnchor && entry.anchor ? { anchor: entry.anchor } : {},
2833
+ ...options.sendAnchor && entry.via ? { entry: entry.via } : {}
2738
2834
  };
2739
2835
  const { conversationId, openId, place: told } = asked;
2740
2836
  let session;
@@ -2761,6 +2857,7 @@ function createAgentRuntime(options) {
2761
2857
  const unsettled = reason === "unknown_open" ? String(field(error, "openId") ?? openId) : null;
2762
2858
  update(entry, { failure, reason, code, openId: unsettled });
2763
2859
  emit({ type: "session_failed", key: entry.key, failure, reason });
2860
+ if (entry.shown === 0) end(entry.key);
2764
2861
  }
2765
2862
  throw error;
2766
2863
  }
@@ -2770,8 +2867,9 @@ function createAgentRuntime(options) {
2770
2867
  if (session.conversation_id)
2771
2868
  current.set(entry.key, session.conversation_id);
2772
2869
  const name = session.agent?.display_name?.trim();
2773
- if (name && name !== sessionName) {
2870
+ if (name && (name !== sessionName || names.get(key.mode) !== name)) {
2774
2871
  sessionName = name;
2872
+ names.set(key.mode, name);
2775
2873
  changed();
2776
2874
  }
2777
2875
  update(entry, {
@@ -2789,6 +2887,14 @@ function createAgentRuntime(options) {
2789
2887
  sessionId: session.id,
2790
2888
  ...entry.entryPoint ? { from: entry.entryPoint } : {}
2791
2889
  });
2890
+ if (entry.shown === 0) {
2891
+ if (entry.expiresAt === null || over(entry)) {
2892
+ end(entry.key);
2893
+ return session;
2894
+ }
2895
+ expireLater(entry);
2896
+ }
2897
+ if (!samePlace(told, entry.place)) tell(entry, {});
2792
2898
  return session;
2793
2899
  }
2794
2900
  const kept = (k) => `${prefix}.${k}`;
@@ -2803,6 +2909,7 @@ function createAgentRuntime(options) {
2803
2909
  live.clear();
2804
2910
  current.clear();
2805
2911
  sessionName = null;
2912
+ names.clear();
2806
2913
  status = null;
2807
2914
  reading = null;
2808
2915
  statusState = NO_STATUS;
@@ -2908,18 +3015,31 @@ function createAgentRuntime(options) {
2908
3015
  place: null,
2909
3016
  told: null,
2910
3017
  revision: 0,
3018
+ telling: false,
3019
+ queued: false,
2911
3020
  entryPoint: null,
3021
+ anchor: null,
3022
+ via: null,
2912
3023
  expiresAt: null,
2913
3024
  shown: 0,
2914
3025
  started: false,
2915
3026
  suspended: false,
2916
3027
  sentOnce: /* @__PURE__ */ new Set(),
2917
3028
  timer: void 0,
3029
+ grace: void 0,
2918
3030
  turns: /* @__PURE__ */ new Map(),
2919
3031
  failedOpen: void 0
2920
3032
  };
2921
3033
  return entry;
2922
3034
  }
3035
+ const suspend = (entry) => {
3036
+ clearTimeout(entry.grace);
3037
+ entry.grace = void 0;
3038
+ if (entry.shown > 0 || entry.suspended || live.get(entry.key) !== entry)
3039
+ return;
3040
+ entry.suspended = true;
3041
+ entry.store.setForeground(false);
3042
+ };
2923
3043
  const over = (entry) => entry.expiresAt !== null && entry.expiresAt <= clock.now();
2924
3044
  const expireLater = (entry) => {
2925
3045
  clearTimeout(entry.timer);
@@ -2937,31 +3057,63 @@ function createAgentRuntime(options) {
2937
3057
  const tell = (entry, at) => {
2938
3058
  if (at.place !== void 0) entry.place = at.place;
2939
3059
  if (at.entryPoint !== void 0) entry.entryPoint = at.entryPoint;
3060
+ if (at.anchor !== void 0)
3061
+ entry.anchor = at.anchor && anchorRef(at.anchor.type, at.anchor.id);
3062
+ if (at.entry !== void 0) entry.via = at.entry && entryOf(at.entry);
2940
3063
  const sessionId = entry.state.sessionId;
2941
3064
  const moved = sessionId !== null && !samePlace(entry.told, entry.place);
2942
3065
  update(entry, { moved });
2943
3066
  const updateContext = backend.updateContext?.bind(backend);
2944
3067
  if (!moved || !updateContext || sessionId === null) return;
3068
+ if (entry.telling) {
3069
+ entry.queued = true;
3070
+ return;
3071
+ }
3072
+ entry.telling = true;
2945
3073
  const asked = generation;
2946
3074
  const current2 = () => asked === generation && live.get(entry.key) === entry && entry.state.sessionId === sessionId;
3075
+ const settle = () => {
3076
+ entry.telling = false;
3077
+ if (!entry.queued) return;
3078
+ entry.queued = false;
3079
+ if (asked === generation && live.get(entry.key) === entry)
3080
+ tell(entry, {});
3081
+ };
2947
3082
  const send = (again) => {
2948
3083
  const place = entry.place;
2949
3084
  const revision = ++entry.revision;
2950
- updateContext(sessionId, place ?? CLEARED, revision).then(
3085
+ let told;
3086
+ try {
3087
+ told = updateContext(sessionId, place ?? CLEARED, revision);
3088
+ } catch (error) {
3089
+ told = Promise.reject(error);
3090
+ }
3091
+ told.then(
2951
3092
  () => {
2952
- if (!current2() || entry.revision !== revision) return;
2953
- entry.told = place;
2954
- update(entry, { moved: !samePlace(entry.told, entry.place) });
2955
- emit({ type: "context_updated", key: entry.key, sessionId });
3093
+ if (current2() && entry.revision === revision) {
3094
+ entry.told = place;
3095
+ update(entry, { moved: !samePlace(entry.told, entry.place) });
3096
+ emit({ type: "context_updated", key: entry.key, sessionId });
3097
+ }
3098
+ settle();
2956
3099
  },
2957
3100
  (error) => {
2958
3101
  const theirs = field(error, "currentRevision");
2959
3102
  const conflict = field(error, "outcome") === "context_conflict" || field(error, "reason") === "conflict";
2960
- if (!conflict || again || !current2() || entry.revision !== revision)
3103
+ if (conflict && !again && current2() && entry.revision === revision) {
3104
+ if (typeof theirs === "number" && theirs >= entry.revision)
3105
+ entry.revision = theirs;
3106
+ send(true);
2961
3107
  return;
2962
- if (typeof theirs === "number" && theirs >= entry.revision)
2963
- entry.revision = theirs;
2964
- send(true);
3108
+ }
3109
+ if (!conflict && devBuild()) {
3110
+ const status2 = field(error, "status");
3111
+ warn(
3112
+ `@vinaverse/client/agent: updateContext was refused${typeof status2 === "number" ? ` (${status2})` : ""}: ${error instanceof Error ? error.message : String(error)}. The new place is offered as a question instead.`,
3113
+ { code: "context_refused" }
3114
+ );
3115
+ }
3116
+ settle();
2965
3117
  }
2966
3118
  );
2967
3119
  };
@@ -2980,6 +3132,8 @@ function createAgentRuntime(options) {
2980
3132
  entry.shown++;
2981
3133
  clearTimeout(entry.timer);
2982
3134
  entry.timer = void 0;
3135
+ clearTimeout(entry.grace);
3136
+ entry.grace = void 0;
2983
3137
  if (entry.suspended) {
2984
3138
  entry.suspended = false;
2985
3139
  entry.store.setForeground(true);
@@ -2995,12 +3149,15 @@ function createAgentRuntime(options) {
2995
3149
  released = true;
2996
3150
  entry.shown--;
2997
3151
  if (entry.shown > 0 || live.get(entry.key) !== entry) return;
2998
- if (keepAlive === "view" || NOT_KEPT.has(entry.store.getSnapshot().status) || entry.expiresAt === null || over(entry)) {
3152
+ const status2 = entry.store.getSnapshot().status;
3153
+ const opening = status2 === "starting" && keepAlive !== "view";
3154
+ if (!opening && (keepAlive === "view" || NOT_KEPT.has(status2) || entry.expiresAt === null || over(entry))) {
2999
3155
  end(entry.key);
3000
3156
  return;
3001
3157
  }
3002
- entry.suspended = true;
3003
- entry.store.setForeground(false);
3158
+ if (graceMs > 0)
3159
+ entry.grace = setTimeout(() => suspend(entry), graceMs);
3160
+ else suspend(entry);
3004
3161
  expireLater(entry);
3005
3162
  };
3006
3163
  },
@@ -3070,6 +3227,9 @@ function createAgentRuntime(options) {
3070
3227
  forget(key) {
3071
3228
  forget(keyOf(key));
3072
3229
  },
3230
+ background() {
3231
+ for (const entry of live.values()) if (entry.grace) suspend(entry);
3232
+ },
3073
3233
  sweep() {
3074
3234
  for (const entry of [...live.values()])
3075
3235
  if (entry.shown === 0 && over(entry)) end(entry.key);
@@ -3128,7 +3288,7 @@ function createAgentRuntime(options) {
3128
3288
  getStatusState: () => statusState,
3129
3289
  backend,
3130
3290
  epoch: () => generation,
3131
- getName: () => sessionName ?? status?.value.displayName ?? null,
3291
+ getName: (mode) => (mode === void 0 ? sessionName : names.get(mode)) ?? status?.value.displayName ?? null,
3132
3292
  subscribe(listener) {
3133
3293
  listeners.add(listener);
3134
3294
  return () => listeners.delete(listener);
@@ -3312,7 +3472,10 @@ async function retrying(run, policy, signal, onRetry) {
3312
3472
 
3313
3473
  exports.AgentRefusal = AgentRefusal;
3314
3474
  exports.agentKey = agentKey;
3475
+ exports.anchorRef = anchorRef;
3476
+ exports.askAboutKey = askAboutKey;
3315
3477
  exports.createAgentRuntime = createAgentRuntime;
3478
+ exports.entryLabel = entryLabel;
3316
3479
  exports.greetedKey = greetedKey;
3317
3480
  exports.hashKey = hashKey;
3318
3481
  exports.isRefusal = isRefusal;