@atlaskit/editor-plugin-autocomplete 9.1.0 → 9.2.1

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.
@@ -4,7 +4,7 @@ var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefau
4
4
  Object.defineProperty(exports, "__esModule", {
5
5
  value: true
6
6
  });
7
- exports.predict = exports.noteSuggestionAccepted = exports.loadVectorsAsync = exports.loadPhraseArtifacts = exports.loadDefaultVocabulary = exports.inspectSessionBoosts = exports.initVocabulary = exports.initVectors = exports.initPhrases = exports.initL3Vocabulary = exports.ingestDocumentPage = exports.incrementSessionFreq = exports.getPredictorStatus = exports.getLastPredictionDebug = void 0;
7
+ exports.resetSessionBoosts = exports.predict = exports.noteSuggestionAccepted = exports.lookupVocabularySource = exports.loadVectorsAsync = exports.loadPhraseArtifacts = exports.loadDefaultVocabulary = exports.isSurfaceInAcceptCooldown = exports.inspectSessionBoosts = exports.initVocabulary = exports.initVectors = exports.initPhrases = exports.initL3Vocabulary = exports.ingestDocumentPage = exports.incrementSessionFreq = exports.getPredictorStatus = exports.getLastPredictionOutcome = exports.getLastPredictionDebug = void 0;
8
8
  var _regenerator = _interopRequireDefault(require("@babel/runtime/regenerator"));
9
9
  var _asyncToGenerator2 = _interopRequireDefault(require("@babel/runtime/helpers/asyncToGenerator"));
10
10
  var _typeof2 = _interopRequireDefault(require("@babel/runtime/helpers/typeof"));
@@ -191,6 +191,18 @@ var DEBUG_TEXT_TAIL_CHARS = 120;
191
191
  * is vocabulary coverage.
192
192
  */
193
193
 
194
+ /**
195
+ * What the scored path concluded, recorded on every evaluation whether or not
196
+ * debug is on.
197
+ *
198
+ * This exists for the inline-code harvester, which may only offer a harvested
199
+ * surface once the scored path has finished and come away empty. The reason is
200
+ * the load-bearing part: `no-candidate` means no vocabulary reaches this prefix
201
+ * at all, while `winner-margin` means two known words the model cannot yet
202
+ * separate — the first is a gap worth filling and the second is a prefix
203
+ * ambiguous enough that filling it would be a guess.
204
+ */
205
+
194
206
  /**
195
207
  * A candidate paired with the length of the already-typed prefix it completes.
196
208
  * For a single word this is the current partial token length; for a phrase it
@@ -218,6 +230,17 @@ var WeightedWordTrie = /*#__PURE__*/function () {
218
230
  function WeightedWordTrie() {
219
231
  (0, _classCallCheck2.default)(this, WeightedWordTrie);
220
232
  (0, _defineProperty2.default)(this, "root", new TrieNode());
233
+ /**
234
+ * Every node a session boost has been written to.
235
+ *
236
+ * Kept because dropping the boosts is no longer a rare event — it happens
237
+ * each time the reader changes page or conversation — and walking a vocabulary
238
+ * of tens of thousands of words to find the few hundred that were touched
239
+ * costs about 10ms of main thread every time. The two writers below are the
240
+ * only way a `sessionFreq` moves, so keeping this in step costs one Set
241
+ * insertion on a path that is already descending the trie.
242
+ */
243
+ (0, _defineProperty2.default)(this, "boostedNodes", new Set());
221
244
  /** Highest tenantFreq seen — used to normalize freq scores at query time */
222
245
  (0, _defineProperty2.default)(this, "maxTenantFreq", 1);
223
246
  }
@@ -334,6 +357,13 @@ var WeightedWordTrie = /*#__PURE__*/function () {
334
357
  return node.word !== null ? node : null;
335
358
  }
336
359
 
360
+ /** Whether this exact surface is stored as a terminal word. */
361
+ }, {
362
+ key: "hasWord",
363
+ value: function hasWord(word) {
364
+ return this.findNode(word) !== null;
365
+ }
366
+
337
367
  /**
338
368
  * Set the session frequency for a word.
339
369
  * Returns true if the word exists in the trie.
@@ -346,6 +376,7 @@ var WeightedWordTrie = /*#__PURE__*/function () {
346
376
  return false;
347
377
  }
348
378
  node.sessionFreq = count;
379
+ this.boostedNodes.add(node);
349
380
  return true;
350
381
  }
351
382
 
@@ -361,28 +392,51 @@ var WeightedWordTrie = /*#__PURE__*/function () {
361
392
  return false;
362
393
  }
363
394
  node.sessionFreq += 1;
395
+ this.boostedNodes.add(node);
364
396
  return true;
365
397
  }
366
398
 
399
+ /**
400
+ * Zero every session boost, in the number of words boosted rather than the
401
+ * number of words known.
402
+ */
403
+ }, {
404
+ key: "clearSessionBoosts",
405
+ value: function clearSessionBoosts() {
406
+ var _iterator5 = _createForOfIteratorHelper(this.boostedNodes),
407
+ _step5;
408
+ try {
409
+ for (_iterator5.s(); !(_step5 = _iterator5.n()).done;) {
410
+ var node = _step5.value;
411
+ node.sessionFreq = 0;
412
+ }
413
+ } catch (err) {
414
+ _iterator5.e(err);
415
+ } finally {
416
+ _iterator5.f();
417
+ }
418
+ this.boostedNodes.clear();
419
+ }
420
+
367
421
  /**
368
422
  * Every word carrying a session boost, optionally limited to one prefix's
369
423
  * subtree. Unlike `getCandidates` a word equal to the prefix is included,
370
424
  * since the question here is what the session holds rather than what could
371
425
  * still be typed.
372
426
  *
373
- * Walks the trie instead of reading an index, so nothing has to be kept in
374
- * step on the write path for the sake of being able to ask.
427
+ * Walks the trie rather than reading `boostedNodes`, since a prefix answer is
428
+ * a subtree question and this only runs when a human asks it.
375
429
  */
376
430
  }, {
377
431
  key: "collectSessionBoosted",
378
432
  value: function collectSessionBoosted() {
379
433
  var prefix = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : '';
380
434
  var node = this.root;
381
- var _iterator5 = _createForOfIteratorHelper(prefix.toLowerCase()),
382
- _step5;
435
+ var _iterator6 = _createForOfIteratorHelper(prefix.toLowerCase()),
436
+ _step6;
383
437
  try {
384
- for (_iterator5.s(); !(_step5 = _iterator5.n()).done;) {
385
- var char = _step5.value;
438
+ for (_iterator6.s(); !(_step6 = _iterator6.n()).done;) {
439
+ var char = _step6.value;
386
440
  var next = node.children.get(char);
387
441
  if (!next) {
388
442
  return [];
@@ -390,9 +444,9 @@ var WeightedWordTrie = /*#__PURE__*/function () {
390
444
  node = next;
391
445
  }
392
446
  } catch (err) {
393
- _iterator5.e(err);
447
+ _iterator6.e(err);
394
448
  } finally {
395
- _iterator5.f();
449
+ _iterator6.f();
396
450
  }
397
451
  var boosted = [];
398
452
  var stack = [node];
@@ -407,17 +461,17 @@ var WeightedWordTrie = /*#__PURE__*/function () {
407
461
  node: current
408
462
  });
409
463
  }
410
- var _iterator6 = _createForOfIteratorHelper(current.children.values()),
411
- _step6;
464
+ var _iterator7 = _createForOfIteratorHelper(current.children.values()),
465
+ _step7;
412
466
  try {
413
- for (_iterator6.s(); !(_step6 = _iterator6.n()).done;) {
414
- var child = _step6.value;
467
+ for (_iterator7.s(); !(_step7 = _iterator7.n()).done;) {
468
+ var child = _step7.value;
415
469
  stack.push(child);
416
470
  }
417
471
  } catch (err) {
418
- _iterator6.e(err);
472
+ _iterator7.e(err);
419
473
  } finally {
420
- _iterator6.f();
474
+ _iterator7.f();
421
475
  }
422
476
  }
423
477
  return boosted;
@@ -443,19 +497,19 @@ var phraseTrie = new WeightedWordTrie();
443
497
  * expects a simple array of strings: ["about", "above", "actually", ...]
444
498
  */
445
499
  var initL3Vocabulary = exports.initL3Vocabulary = function initL3Vocabulary(l3Words) {
446
- var _iterator7 = _createForOfIteratorHelper(l3Words),
447
- _step7;
500
+ var _iterator8 = _createForOfIteratorHelper(l3Words),
501
+ _step8;
448
502
  try {
449
- for (_iterator7.s(); !(_step7 = _iterator7.n()).done;) {
450
- var word = _step7.value;
503
+ for (_iterator8.s(); !(_step8 = _iterator8.n()).done;) {
504
+ var word = _step8.value;
451
505
  // Insert with a tiny baseline frequency so it mathematically
452
506
  // loses to any domain word in Stage 1, but still scores above 0.
453
507
  l3Trie.insert(word, L3_BASELINE_FREQ, 0, 0);
454
508
  }
455
509
  } catch (err) {
456
- _iterator7.e(err);
510
+ _iterator8.e(err);
457
511
  } finally {
458
- _iterator7.f();
512
+ _iterator8.f();
459
513
  }
460
514
  recallGeneration++;
461
515
  (0, _debugMode.ctcTag)('init', "L3 general English loaded: ".concat(l3Words.length, " words"));
@@ -481,6 +535,15 @@ var phraseTermCount = 0;
481
535
  var maxBigramFreq = 1;
482
536
  var maxPhraseFreq = 1;
483
537
  var lastPredictionDebug = null;
538
+ var lastPredictionOutcome = null;
539
+ var recordPredictionOutcome = function recordPredictionOutcome(outcome) {
540
+ lastPredictionOutcome = outcome;
541
+ };
542
+
543
+ /** The verdict from the most recent `predict` call. Always populated. */
544
+ var getLastPredictionOutcome = exports.getLastPredictionOutcome = function getLastPredictionOutcome() {
545
+ return lastPredictionOutcome;
546
+ };
484
547
 
485
548
  // ── Stabilization (QI-2): post-accept cooldown + whole-surface repetition ────
486
549
  // Two guards that stop the accept→echo (`end to end` → `end to end to end`) and
@@ -512,6 +575,22 @@ var noteSuggestionAccepted = exports.noteSuggestionAccepted = function noteSugge
512
575
  }
513
576
  };
514
577
 
578
+ /**
579
+ * Whether `surface` is the one the user just accepted and is still inside its
580
+ * cooldown window.
581
+ *
582
+ * Read-only, unlike the advance inside `predict`: the cooldown is measured in
583
+ * predictions, and a caller asking whether it is active must not consume one of
584
+ * them. Exported for the harvest path, which displays without going through
585
+ * arbitration and so would otherwise re-offer what was just accepted.
586
+ */
587
+ var isSurfaceInAcceptCooldown = exports.isSurfaceInAcceptCooldown = function isSurfaceInAcceptCooldown(surface) {
588
+ if (!acceptCooldown || acceptCooldown.surface !== surface.trim().toLowerCase()) {
589
+ return false;
590
+ }
591
+ return acceptCooldown.predictionsSince <= COOLDOWN_KEYSTROKES || performance.now() - acceptCooldown.ts < COOLDOWN_MS;
592
+ };
593
+
515
594
  /** Get vector for a word from the store. */
516
595
  var getWordVector = function getWordVector(word) {
517
596
  if (!vectorStore) {
@@ -536,20 +615,20 @@ var computeContextVectorLocal = function computeContextVectorLocal(textBefore) {
536
615
  var tokens = tokenize(textBefore);
537
616
  var words = tokens.slice(-CONTEXT_WORDS);
538
617
  var vectors = [];
539
- var _iterator8 = _createForOfIteratorHelper(words),
540
- _step8;
618
+ var _iterator9 = _createForOfIteratorHelper(words),
619
+ _step9;
541
620
  try {
542
- for (_iterator8.s(); !(_step8 = _iterator8.n()).done;) {
543
- var word = _step8.value;
621
+ for (_iterator9.s(); !(_step9 = _iterator9.n()).done;) {
622
+ var word = _step9.value;
544
623
  var _v = getWordVector(word);
545
624
  if (_v) {
546
625
  vectors.push(_v);
547
626
  }
548
627
  }
549
628
  } catch (err) {
550
- _iterator8.e(err);
629
+ _iterator9.e(err);
551
630
  } finally {
552
- _iterator8.f();
631
+ _iterator9.f();
553
632
  }
554
633
  if (vectors.length === 0) {
555
634
  return null;
@@ -582,11 +661,11 @@ var getContextVectorForScoring = function getContextVectorForScoring(textBefore)
582
661
  var tokenize = function tokenize(text) {
583
662
  var tokens = [];
584
663
  // eslint-disable-next-line @atlassian/perf-linting/no-expensive-split-replace
585
- var _iterator9 = _createForOfIteratorHelper(text.toLowerCase().split(WHITESPACE_SPLIT_REGEX)),
586
- _step9;
664
+ var _iterator0 = _createForOfIteratorHelper(text.toLowerCase().split(WHITESPACE_SPLIT_REGEX)),
665
+ _step0;
587
666
  try {
588
- for (_iterator9.s(); !(_step9 = _iterator9.n()).done;) {
589
- var raw = _step9.value;
667
+ for (_iterator0.s(); !(_step0 = _iterator0.n()).done;) {
668
+ var raw = _step0.value;
590
669
  // eslint-disable-next-line @atlassian/perf-linting/no-expensive-split-replace
591
670
  var clean = raw.replace(PUNCTUATION_BOUNDARY_REGEX, '');
592
671
  if (clean.length >= 2) {
@@ -594,9 +673,9 @@ var tokenize = function tokenize(text) {
594
673
  }
595
674
  }
596
675
  } catch (err) {
597
- _iterator9.e(err);
676
+ _iterator0.e(err);
598
677
  } finally {
599
- _iterator9.f();
678
+ _iterator0.f();
600
679
  }
601
680
  return tokens;
602
681
  };
@@ -700,11 +779,11 @@ var getPhraseCandidates = function getPhraseCandidates(trimmed) {
700
779
  window: windowPrefix,
701
780
  matches: matches.length
702
781
  });
703
- var _iterator0 = _createForOfIteratorHelper(matches),
704
- _step0;
782
+ var _iterator1 = _createForOfIteratorHelper(matches),
783
+ _step1;
705
784
  try {
706
- for (_iterator0.s(); !(_step0 = _iterator0.n()).done;) {
707
- var _match = _step0.value;
785
+ for (_iterator1.s(); !(_step1 = _iterator1.n()).done;) {
786
+ var _match = _step1.value;
708
787
  if (seen.has(_match.word)) {
709
788
  continue;
710
789
  }
@@ -717,9 +796,9 @@ var getPhraseCandidates = function getPhraseCandidates(trimmed) {
717
796
  });
718
797
  }
719
798
  } catch (err) {
720
- _iterator0.e(err);
799
+ _iterator1.e(err);
721
800
  } finally {
722
- _iterator0.f();
801
+ _iterator1.f();
723
802
  }
724
803
  }
725
804
  logPhrasePath();
@@ -764,17 +843,17 @@ var getLastPredictionDebug = exports.getLastPredictionDebug = function getLastPr
764
843
  return lastPredictionDebug;
765
844
  };
766
845
  var initVocabulary = exports.initVocabulary = function initVocabulary(vocabulary) {
767
- var _iterator1 = _createForOfIteratorHelper(vocabulary.terms),
768
- _step1;
846
+ var _iterator10 = _createForOfIteratorHelper(vocabulary.terms),
847
+ _step10;
769
848
  try {
770
- for (_iterator1.s(); !(_step1 = _iterator1.n()).done;) {
771
- var term = _step1.value;
849
+ for (_iterator10.s(); !(_step10 = _iterator10.n()).done;) {
850
+ var term = _step10.value;
772
851
  wordTrie.insert(term.word, term.freq, term.docFreq, term.authorFreq);
773
852
  }
774
853
  } catch (err) {
775
- _iterator1.e(err);
854
+ _iterator10.e(err);
776
855
  } finally {
777
- _iterator1.f();
856
+ _iterator10.f();
778
857
  }
779
858
  isInitialized = true;
780
859
  recallGeneration++;
@@ -825,6 +904,36 @@ var incrementSessionFreq = exports.incrementSessionFreq = function incrementSess
825
904
  wordTrie.incrementSessionFreq(word);
826
905
  };
827
906
 
907
+ /**
908
+ * Drop every L1 boost this session has accumulated.
909
+ *
910
+ * The vocabulary itself is left alone: only `sessionFreq` is cleared, so the
911
+ * tenant and generic frequencies a boost was sitting on top of survive. Called
912
+ * when the plugin decides the session it was learning for has ended — a new
913
+ * conversation, or a different page — since a boost is a claim about what is
914
+ * being discussed and that claim does not carry over.
915
+ */
916
+ var resetSessionBoosts = exports.resetSessionBoosts = function resetSessionBoosts() {
917
+ wordTrie.clearSessionBoosts();
918
+ // Anything memoized against the old boosts is now describing a session that
919
+ // no longer exists.
920
+ recallGeneration++;
921
+ };
922
+
923
+ /**
924
+ * Which vocabulary already holds this surface, if any.
925
+ *
926
+ * Used by the inline-code harvester to drop terms the scored path can already
927
+ * serve, so that harvesting stays limited to words with no route to a
928
+ * suggestion today.
929
+ */
930
+ var lookupVocabularySource = exports.lookupVocabularySource = function lookupVocabularySource(word) {
931
+ if (wordTrie.hasWord(word)) {
932
+ return 'l2';
933
+ }
934
+ return l3Trie.hasWord(word) ? 'l3' : null;
935
+ };
936
+
828
937
  /**
829
938
  * Prime session frequencies from a document page string.
830
939
  *
@@ -841,20 +950,20 @@ var ingestDocumentPage = exports.ingestDocumentPage = function ingestDocumentPag
841
950
  }
842
951
  var words = tokenize(pageContent);
843
952
  var validBoostedWords = new Set();
844
- var _iterator10 = _createForOfIteratorHelper(words),
845
- _step10;
953
+ var _iterator11 = _createForOfIteratorHelper(words),
954
+ _step11;
846
955
  try {
847
- for (_iterator10.s(); !(_step10 = _iterator10.n()).done;) {
848
- var word = _step10.value;
956
+ for (_iterator11.s(); !(_step11 = _iterator11.n()).done;) {
957
+ var word = _step11.value;
849
958
  var didBoost = wordTrie.incrementSessionFreq(word);
850
959
  if (didBoost) {
851
960
  validBoostedWords.add(word);
852
961
  }
853
962
  }
854
963
  } catch (err) {
855
- _iterator10.e(err);
964
+ _iterator11.e(err);
856
965
  } finally {
857
- _iterator10.f();
966
+ _iterator11.f();
858
967
  }
859
968
  if ((0, _debugMode.isAutocompleteDebugEnabled)() && validBoostedWords.size > 0) {
860
969
  (0, _debugMode.ctcTag)('init', "L1 session primed ".concat(validBoostedWords.size, " words from page \xB7 __atlCtcDebug__.session() to inspect"), _debugMode.CTC_STYLES.brand);
@@ -872,7 +981,7 @@ var ingestDocumentPage = exports.ingestDocumentPage = function ingestDocumentPag
872
981
  * strongest boosts are the ones that change an ordering, and `boosted` still
873
982
  * reports the full size, so the cap loses nothing but volume.
874
983
  */
875
- var MAX_LISTED_SESSION_WORDS = 50;
984
+ var MAX_LISTED_SESSION_WORDS = 100;
876
985
  /**
877
986
  * Read the session's L1 boosts, optionally narrowed to a prefix.
878
987
  *
@@ -883,7 +992,8 @@ var MAX_LISTED_SESSION_WORDS = 50;
883
992
  * Only words the vocabulary already holds can carry a boost, because both writers
884
993
  * go through `incrementSessionFreq` and it only finds existing nodes. An ingested
885
994
  * word absent from the vocabulary is therefore missing from here and always will
886
- * be.
995
+ * be — that gap is what the inline-code harvester covers, and those surfaces show
996
+ * up under `__atlCtcDebug__.harvest()` instead.
887
997
  */
888
998
  var inspectSessionBoosts = exports.inspectSessionBoosts = function inspectSessionBoosts(prefix) {
889
999
  var boosted = wordTrie.collectSessionBoosted(prefix !== null && prefix !== void 0 ? prefix : '');
@@ -946,20 +1056,20 @@ var computeCanonicalRecall = function computeCanonicalRecall(trimmed, currentWor
946
1056
  var existingWords = new Set(wordCandidates.map(function (c) {
947
1057
  return c.word;
948
1058
  }));
949
- var _iterator11 = _createForOfIteratorHelper(l3Candidates),
950
- _step11;
1059
+ var _iterator12 = _createForOfIteratorHelper(l3Candidates),
1060
+ _step12;
951
1061
  try {
952
- for (_iterator11.s(); !(_step11 = _iterator11.n()).done;) {
953
- var l3c = _step11.value;
1062
+ for (_iterator12.s(); !(_step12 = _iterator12.n()).done;) {
1063
+ var l3c = _step12.value;
954
1064
  if (wordCandidates.length >= MAX_CANDIDATES) break;
955
1065
  if (!existingWords.has(l3c.word)) {
956
1066
  wordCandidates.push(l3c);
957
1067
  }
958
1068
  }
959
1069
  } catch (err) {
960
- _iterator11.e(err);
1070
+ _iterator12.e(err);
961
1071
  } finally {
962
- _iterator11.f();
1072
+ _iterator12.f();
963
1073
  }
964
1074
  }
965
1075
 
@@ -986,19 +1096,19 @@ var computeCanonicalRecall = function computeCanonicalRecall(trimmed, currentWor
986
1096
  return _objectSpread(_objectSpread({}, candidate), (0, _canonicalLmScoring.deriveCanonicalCandidateContext)(trimmed, candidate.matchedPrefixLen, candidate.word, _slowLaneClient.getCanonicalSurfaceTokenIds, candidate.surfaceStart, positionCache));
987
1097
  });
988
1098
  var prefixLenByWord = new Map();
989
- var _iterator12 = _createForOfIteratorHelper(canonicalMatched),
990
- _step12;
1099
+ var _iterator13 = _createForOfIteratorHelper(canonicalMatched),
1100
+ _step13;
991
1101
  try {
992
- for (_iterator12.s(); !(_step12 = _iterator12.n()).done;) {
993
- var m = _step12.value;
1102
+ for (_iterator13.s(); !(_step13 = _iterator13.n()).done;) {
1103
+ var m = _step13.value;
994
1104
  if (!prefixLenByWord.has(m.word)) {
995
1105
  prefixLenByWord.set(m.word, m.matchedPrefixLen);
996
1106
  }
997
1107
  }
998
1108
  } catch (err) {
999
- _iterator12.e(err);
1109
+ _iterator13.e(err);
1000
1110
  } finally {
1001
- _iterator12.f();
1111
+ _iterator13.f();
1002
1112
  }
1003
1113
  return {
1004
1114
  canonicalMatched: canonicalMatched,
@@ -1037,6 +1147,14 @@ var predict = exports.predict = function predict(textBefore) {
1037
1147
  void loadDefaultVocabulary({
1038
1148
  source: 'predict'
1039
1149
  }).catch(function () {});
1150
+ // Awaiting, not empty-handed: with no vocabulary loaded the harvester's own
1151
+ // intake filter has not been applied to anything either.
1152
+ recordPredictionOutcome({
1153
+ abstainReason: 'not-initialized',
1154
+ awaitingAsyncEvidence: true,
1155
+ scoredCandidateCount: 0,
1156
+ textBefore: textBefore
1157
+ });
1040
1158
  return null;
1041
1159
  }
1042
1160
  var t0 = performance.now();
@@ -1065,11 +1183,23 @@ var predict = exports.predict = function predict(textBefore) {
1065
1183
  priority: 0
1066
1184
  }));
1067
1185
  }
1186
+ recordPredictionOutcome({
1187
+ abstainReason: 'prefetch',
1188
+ awaitingAsyncEvidence: true,
1189
+ scoredCandidateCount: 0,
1190
+ textBefore: textBefore
1191
+ });
1068
1192
  return null;
1069
1193
  }
1070
1194
  var trimmed = textBefore.trimEnd();
1071
1195
  var trailingSurfaceToken = (_trimmed$match$ = (_trimmed$match = trimmed.match(TRAILING_SURFACE_TOKEN_REGEX)) === null || _trimmed$match === void 0 ? void 0 : _trimmed$match[0]) !== null && _trimmed$match$ !== void 0 ? _trimmed$match$ : '';
1072
1196
  if (trailingSurfaceToken.length === 0) {
1197
+ recordPredictionOutcome({
1198
+ abstainReason: 'no-surface-token',
1199
+ awaitingAsyncEvidence: false,
1200
+ scoredCandidateCount: 0,
1201
+ textBefore: textBefore
1202
+ });
1073
1203
  return null;
1074
1204
  }
1075
1205
  var currentWord = trailingSurfaceToken;
@@ -1080,6 +1210,14 @@ var predict = exports.predict = function predict(textBefore) {
1080
1210
 
1081
1211
  // If every trie was empty for this prefix
1082
1212
  if (canonicalMatched.length === 0) {
1213
+ // Terminal, and the only verdict that says the vocabulary has no claim on
1214
+ // this prefix at all — which is what makes it the harvester's cue.
1215
+ recordPredictionOutcome({
1216
+ abstainReason: 'no-candidate',
1217
+ awaitingAsyncEvidence: false,
1218
+ scoredCandidateCount: 0,
1219
+ textBefore: textBefore
1220
+ });
1083
1221
  if ((0, _debugMode.isAutocompleteDebugEnabled)()) {
1084
1222
  // eslint-disable-next-line no-console
1085
1223
  console.log("%c[CTC]%c \u2014 abstain: no matches for \"".concat(currentWord, "\""), _debugMode.CTC_STYLES.brand, _debugMode.CTC_STYLES.body);
@@ -1127,29 +1265,29 @@ var predict = exports.predict = function predict(textBefore) {
1127
1265
  }))).sort();
1128
1266
  var familyKey = eligibleContextKeys.join("\x01");
1129
1267
  var primeRequests = (0, _canonicalLmScoring.selectBoundaryPrimeRequests)(familyKey, canonicalMatched, PHRASE_MAX_WORDS);
1130
- var _iterator13 = _createForOfIteratorHelper(primeRequests),
1131
- _step13;
1268
+ var _iterator14 = _createForOfIteratorHelper(primeRequests),
1269
+ _step14;
1132
1270
  try {
1133
- for (_iterator13.s(); !(_step13 = _iterator13.n()).done;) {
1134
- var request = _step13.value;
1271
+ for (_iterator14.s(); !(_step14 = _iterator14.n()).done;) {
1272
+ var request = _step14.value;
1135
1273
  (0, _slowLaneClient.primeBoundaryLm)(request);
1136
1274
  }
1137
1275
  } catch (err) {
1138
- _iterator13.e(err);
1276
+ _iterator14.e(err);
1139
1277
  } finally {
1140
- _iterator13.f();
1278
+ _iterator14.f();
1141
1279
  }
1142
1280
  var runtimeBySurface = new Map(canonicalMatched.map(function (candidate) {
1143
1281
  return [candidate.word, candidate];
1144
1282
  }));
1145
1283
  var canonicalEvidence = new Map();
1146
1284
  var firstTokenGroups = new Map();
1147
- var _iterator14 = _createForOfIteratorHelper(canonicalMatched),
1148
- _step14;
1285
+ var _iterator15 = _createForOfIteratorHelper(canonicalMatched),
1286
+ _step15;
1149
1287
  try {
1150
- for (_iterator14.s(); !(_step14 = _iterator14.n()).done;) {
1288
+ for (_iterator15.s(); !(_step15 = _iterator15.n()).done;) {
1151
1289
  var _firstTokenGroups$get;
1152
- var _candidate = _step14.value;
1290
+ var _candidate = _step15.value;
1153
1291
  if (_candidate.canonicalTokenIds === null) {
1154
1292
  continue;
1155
1293
  }
@@ -1177,28 +1315,28 @@ var predict = exports.predict = function predict(textBefore) {
1177
1315
  firstTokenGroups.set(_candidate.contextKey, _group2);
1178
1316
  }
1179
1317
  } catch (err) {
1180
- _iterator14.e(err);
1318
+ _iterator15.e(err);
1181
1319
  } finally {
1182
- _iterator14.f();
1320
+ _iterator15.f();
1183
1321
  }
1184
- var _iterator15 = _createForOfIteratorHelper(firstTokenGroups),
1185
- _step15;
1322
+ var _iterator16 = _createForOfIteratorHelper(firstTokenGroups),
1323
+ _step16;
1186
1324
  try {
1187
- for (_iterator15.s(); !(_step15 = _iterator15.n()).done;) {
1188
- var _step15$value = (0, _slicedToArray2.default)(_step15.value, 2),
1189
- _contextKey = _step15$value[0],
1190
- _group3 = _step15$value[1];
1325
+ for (_iterator16.s(); !(_step16 = _iterator16.n()).done;) {
1326
+ var _step16$value = (0, _slicedToArray2.default)(_step16.value, 2),
1327
+ _contextKey = _step16$value[0],
1328
+ _group3 = _step16$value[1];
1191
1329
  var boundary = (0, _slowLaneClient.getBoundaryLmState)(_contextKey);
1192
1330
  if (!boundary) {
1193
1331
  continue;
1194
1332
  }
1195
1333
  var maxLogit = -Infinity;
1196
- var _iterator22 = _createForOfIteratorHelper(_group3),
1197
- _step22;
1334
+ var _iterator23 = _createForOfIteratorHelper(_group3),
1335
+ _step23;
1198
1336
  try {
1199
- for (_iterator22.s(); !(_step22 = _iterator22.n()).done;) {
1337
+ for (_iterator23.s(); !(_step23 = _iterator23.n()).done;) {
1200
1338
  var _candidate2$canonical;
1201
- var _candidate2 = _step22.value;
1339
+ var _candidate2 = _step23.value;
1202
1340
  var tokenId = (_candidate2$canonical = _candidate2.canonicalTokenIds) === null || _candidate2$canonical === void 0 ? void 0 : _candidate2$canonical[0];
1203
1341
  var rawLogit = tokenId === undefined ? undefined : boundary.rawLogits[tokenId];
1204
1342
  if (rawLogit !== undefined && Number.isFinite(rawLogit) && rawLogit > maxLogit) {
@@ -1206,19 +1344,19 @@ var predict = exports.predict = function predict(textBefore) {
1206
1344
  }
1207
1345
  }
1208
1346
  } catch (err) {
1209
- _iterator22.e(err);
1347
+ _iterator23.e(err);
1210
1348
  } finally {
1211
- _iterator22.f();
1349
+ _iterator23.f();
1212
1350
  }
1213
1351
  if (!Number.isFinite(maxLogit)) {
1214
1352
  continue;
1215
1353
  }
1216
- var _iterator23 = _createForOfIteratorHelper(_group3),
1217
- _step23;
1354
+ var _iterator24 = _createForOfIteratorHelper(_group3),
1355
+ _step24;
1218
1356
  try {
1219
- for (_iterator23.s(); !(_step23 = _iterator23.n()).done;) {
1357
+ for (_iterator24.s(); !(_step24 = _iterator24.n()).done;) {
1220
1358
  var _candidate3$canonical, _candidate3$canonical2, _candidate3$canonical3;
1221
- var _candidate3 = _step23.value;
1359
+ var _candidate3 = _step24.value;
1222
1360
  var _tokenId = (_candidate3$canonical = _candidate3.canonicalTokenIds) === null || _candidate3$canonical === void 0 ? void 0 : _candidate3$canonical[0];
1223
1361
  var _rawLogit = _tokenId === undefined ? undefined : boundary.rawLogits[_tokenId];
1224
1362
  if (_rawLogit === undefined || !Number.isFinite(_rawLogit)) {
@@ -1257,15 +1395,15 @@ var predict = exports.predict = function predict(textBefore) {
1257
1395
  });
1258
1396
  }
1259
1397
  } catch (err) {
1260
- _iterator23.e(err);
1398
+ _iterator24.e(err);
1261
1399
  } finally {
1262
- _iterator23.f();
1400
+ _iterator24.f();
1263
1401
  }
1264
1402
  }
1265
1403
  } catch (err) {
1266
- _iterator15.e(err);
1404
+ _iterator16.e(err);
1267
1405
  } finally {
1268
- _iterator15.f();
1406
+ _iterator16.f();
1269
1407
  }
1270
1408
  var _rankCandidates = (0, _scoringPipeline.rankCandidates)(scoringCandidates, contextVector, function (w) {
1271
1409
  return getWordVector(w);
@@ -1286,12 +1424,12 @@ var predict = exports.predict = function predict(textBefore) {
1286
1424
  });
1287
1425
  if (canonicalScoringSupported && progressiveEligible.length > 0) {
1288
1426
  var byContext = new Map();
1289
- var _iterator16 = _createForOfIteratorHelper(progressiveEligible),
1290
- _step16;
1427
+ var _iterator17 = _createForOfIteratorHelper(progressiveEligible),
1428
+ _step17;
1291
1429
  try {
1292
- for (_iterator16.s(); !(_step16 = _iterator16.n()).done;) {
1430
+ for (_iterator17.s(); !(_step17 = _iterator17.n()).done;) {
1293
1431
  var _byContext$get;
1294
- var candidate = _step16.value;
1432
+ var candidate = _step17.value;
1295
1433
  var runtime = runtimeBySurface.get(candidate.word);
1296
1434
  if (!runtime || runtime.canonicalTokenIds === null) {
1297
1435
  continue;
@@ -1304,17 +1442,17 @@ var predict = exports.predict = function predict(textBefore) {
1304
1442
  byContext.set(runtime.contextKey, group);
1305
1443
  }
1306
1444
  } catch (err) {
1307
- _iterator16.e(err);
1445
+ _iterator17.e(err);
1308
1446
  } finally {
1309
- _iterator16.f();
1447
+ _iterator17.f();
1310
1448
  }
1311
- var _iterator17 = _createForOfIteratorHelper(byContext),
1312
- _step17;
1449
+ var _iterator18 = _createForOfIteratorHelper(byContext),
1450
+ _step18;
1313
1451
  try {
1314
- for (_iterator17.s(); !(_step17 = _iterator17.n()).done;) {
1315
- var _step17$value = (0, _slicedToArray2.default)(_step17.value, 2),
1316
- contextKey = _step17$value[0],
1317
- _group = _step17$value[1];
1452
+ for (_iterator18.s(); !(_step18 = _iterator18.n()).done;) {
1453
+ var _step18$value = (0, _slicedToArray2.default)(_step18.value, 2),
1454
+ contextKey = _step18$value[0],
1455
+ _group = _step18$value[1];
1318
1456
  (0, _slowLaneClient.requestProgressiveSurfaceScores)({
1319
1457
  familyKey: familyKey,
1320
1458
  contextKey: contextKey,
@@ -1332,9 +1470,9 @@ var predict = exports.predict = function predict(textBefore) {
1332
1470
  });
1333
1471
  }
1334
1472
  } catch (err) {
1335
- _iterator17.e(err);
1473
+ _iterator18.e(err);
1336
1474
  } finally {
1337
- _iterator17.f();
1475
+ _iterator18.f();
1338
1476
  }
1339
1477
  }
1340
1478
 
@@ -1423,12 +1561,12 @@ var predict = exports.predict = function predict(textBefore) {
1423
1561
  var contextRequestedCount = new Map();
1424
1562
  var bestTotalByContext = new Map();
1425
1563
  var bestCandidateByContext = new Map();
1426
- var _iterator18 = _createForOfIteratorHelper(ranked),
1427
- _step18;
1564
+ var _iterator19 = _createForOfIteratorHelper(ranked),
1565
+ _step19;
1428
1566
  try {
1429
- for (_iterator18.s(); !(_step18 = _iterator18.n()).done;) {
1567
+ for (_iterator19.s(); !(_step19 = _iterator19.n()).done;) {
1430
1568
  var _contextRequestedCoun2, _judged$total;
1431
- var _candidate4 = _step18.value;
1569
+ var _candidate4 = _step19.value;
1432
1570
  var _runtime = runtimeBySurface.get(_candidate4.word);
1433
1571
  if (!_runtime || _runtime.canonicalTokenIds === null) {
1434
1572
  continue;
@@ -1467,9 +1605,9 @@ var predict = exports.predict = function predict(textBefore) {
1467
1605
  * costs one lookup per space rather than a comparison against every member.
1468
1606
  */
1469
1607
  } catch (err) {
1470
- _iterator18.e(err);
1608
+ _iterator19.e(err);
1471
1609
  } finally {
1472
- _iterator18.f();
1610
+ _iterator19.f();
1473
1611
  }
1474
1612
  var extendsAPoolMember = function extendsAPoolMember(surface, pool) {
1475
1613
  for (var space = surface.indexOf(' '); space !== -1; space = surface.indexOf(' ', space + 1)) {
@@ -1504,22 +1642,22 @@ var predict = exports.predict = function predict(textBefore) {
1504
1642
  // it continues, because the cap alone would leave it looking exactly as
1505
1643
  // certain as its prefix while saying nothing about its own tail.
1506
1644
  var underReadExtensions = new Set();
1507
- var _iterator19 = _createForOfIteratorHelper(contextSurfaces),
1508
- _step19;
1645
+ var _iterator20 = _createForOfIteratorHelper(contextSurfaces),
1646
+ _step20;
1509
1647
  try {
1510
- for (_iterator19.s(); !(_step19 = _iterator19.n()).done;) {
1511
- var _step19$value = (0, _slicedToArray2.default)(_step19.value, 2),
1512
- _contextKey2 = _step19$value[0],
1513
- _surfaces = _step19$value[1];
1648
+ for (_iterator20.s(); !(_step20 = _iterator20.n()).done;) {
1649
+ var _step20$value = (0, _slicedToArray2.default)(_step20.value, 2),
1650
+ _contextKey2 = _step20$value[0],
1651
+ _surfaces = _step20$value[1];
1514
1652
  var pool = new Set(_surfaces);
1515
- var _iterator24 = _createForOfIteratorHelper((0, _toConsumableArray2.default)(_surfaces).sort(function (a, b) {
1653
+ var _iterator25 = _createForOfIteratorHelper((0, _toConsumableArray2.default)(_surfaces).sort(function (a, b) {
1516
1654
  return a.length - b.length;
1517
1655
  })),
1518
- _step24;
1656
+ _step25;
1519
1657
  try {
1520
- for (_iterator24.s(); !(_step24 = _iterator24.n()).done;) {
1658
+ for (_iterator25.s(); !(_step25 = _iterator25.n()).done;) {
1521
1659
  var _getProgressiveSurfac, _getProgressiveSurfac2, _runtimeBySurface$get8, _runtimeBySurface$get9;
1522
- var surface = _step24.value;
1660
+ var surface = _step25.value;
1523
1661
  var own = optimisticTotalByWord.get(surface);
1524
1662
  if (own === undefined) {
1525
1663
  continue;
@@ -1551,9 +1689,9 @@ var predict = exports.predict = function predict(textBefore) {
1551
1689
  }
1552
1690
  }
1553
1691
  } catch (err) {
1554
- _iterator24.e(err);
1692
+ _iterator25.e(err);
1555
1693
  } finally {
1556
- _iterator24.f();
1694
+ _iterator25.f();
1557
1695
  }
1558
1696
  }
1559
1697
 
@@ -1566,9 +1704,9 @@ var predict = exports.predict = function predict(textBefore) {
1566
1704
  * depths.
1567
1705
  */
1568
1706
  } catch (err) {
1569
- _iterator19.e(err);
1707
+ _iterator20.e(err);
1570
1708
  } finally {
1571
- _iterator19.f();
1709
+ _iterator20.f();
1572
1710
  }
1573
1711
  var judgedEvidenceFor = function judgedEvidenceFor(candidate) {
1574
1712
  return underReadExtensions.has(candidate.word) ? null : readJudgedEvidence(candidate);
@@ -1595,20 +1733,20 @@ var predict = exports.predict = function predict(textBefore) {
1595
1733
  // `contextTotals`, so how contested a context is still counts every scored
1596
1734
  // candidate.
1597
1735
  var logSumExpByContext = new Map();
1598
- var _iterator20 = _createForOfIteratorHelper(contextSurfaces),
1599
- _step20;
1736
+ var _iterator21 = _createForOfIteratorHelper(contextSurfaces),
1737
+ _step21;
1600
1738
  try {
1601
- for (_iterator20.s(); !(_step20 = _iterator20.n()).done;) {
1602
- var _step20$value = (0, _slicedToArray2.default)(_step20.value, 2),
1603
- _contextKey3 = _step20$value[0],
1604
- _surfaces2 = _step20$value[1];
1739
+ for (_iterator21.s(); !(_step21 = _iterator21.n()).done;) {
1740
+ var _step21$value = (0, _slicedToArray2.default)(_step21.value, 2),
1741
+ _contextKey3 = _step21$value[0],
1742
+ _surfaces2 = _step21$value[1];
1605
1743
  var _pool = new Set(_surfaces2);
1606
1744
  var minimalTotals = [];
1607
- var _iterator25 = _createForOfIteratorHelper(_surfaces2),
1608
- _step25;
1745
+ var _iterator26 = _createForOfIteratorHelper(_surfaces2),
1746
+ _step26;
1609
1747
  try {
1610
- for (_iterator25.s(); !(_step25 = _iterator25.n()).done;) {
1611
- var _surface = _step25.value;
1748
+ for (_iterator26.s(); !(_step26 = _iterator26.n()).done;) {
1749
+ var _surface = _step26.value;
1612
1750
  var total = optimisticTotalByWord.get(_surface);
1613
1751
  if (total !== undefined && !extendsAPoolMember(_surface, _pool)) {
1614
1752
  minimalTotals.push(total);
@@ -1617,9 +1755,9 @@ var predict = exports.predict = function predict(textBefore) {
1617
1755
  // An extension is strictly longer than what it extends, so the shortest
1618
1756
  // member of any non-empty pool is always minimal and this is never empty.
1619
1757
  } catch (err) {
1620
- _iterator25.e(err);
1758
+ _iterator26.e(err);
1621
1759
  } finally {
1622
- _iterator25.f();
1760
+ _iterator26.f();
1623
1761
  }
1624
1762
  logSumExpByContext.set(_contextKey3, (0, _canonicalLmScoring.logSumExp)(minimalTotals));
1625
1763
  }
@@ -1631,9 +1769,9 @@ var predict = exports.predict = function predict(textBefore) {
1631
1769
  * eventual posterior, or a verified total for the posterior itself.
1632
1770
  */
1633
1771
  } catch (err) {
1634
- _iterator20.e(err);
1772
+ _iterator21.e(err);
1635
1773
  } finally {
1636
- _iterator20.f();
1774
+ _iterator21.f();
1637
1775
  }
1638
1776
  var posteriorFor = function posteriorFor(candidate, total) {
1639
1777
  var runtime = runtimeBySurface.get(candidate.word);
@@ -1923,6 +2061,12 @@ var predict = exports.predict = function predict(textBefore) {
1923
2061
  return ranked.some(hasJudgeableEvidence) ? 'below-posterior-gate' : 'no-evidence';
1924
2062
  };
1925
2063
  var abstainReason = resolveAbstainReason();
2064
+ recordPredictionOutcome({
2065
+ abstainReason: abstainReason,
2066
+ awaitingAsyncEvidence: hasUnresolvedPotential && !clearsWinnerMargin,
2067
+ scoredCandidateCount: ranked.length,
2068
+ textBefore: textBefore
2069
+ });
1926
2070
 
1927
2071
  // The leader is the best-supported candidate, not the selected one: an
1928
2072
  // evaluation that showed nothing is exactly the one whose posterior needs
@@ -1978,6 +2122,9 @@ var predict = exports.predict = function predict(textBefore) {
1978
2122
  'missing-artifact': 'canonical artifact coverage missing',
1979
2123
  'no-candidate': 'nothing cleared scoring',
1980
2124
  'no-evidence': 'full-surface evidence absent',
2125
+ // Recorded at the two exits above this block, so they never print here.
2126
+ 'no-surface-token': 'no trailing surface token',
2127
+ 'not-initialized': 'vocabulary not loaded',
1981
2128
  prefetch: "prefetch: ".concat(currentWord.length, "/").concat(DISPLAY_MIN_PREFIX_LENGTH, " chars"),
1982
2129
  'short-completion': "completion shorter than ".concat(MIN_SUGGESTION_LENGTH, " chars"),
1983
2130
  'unresolved-rival': 'expanding plausible token-prefix groups',
@@ -2072,17 +2219,17 @@ var predict = exports.predict = function predict(textBefore) {
2072
2219
  bigram: 0,
2073
2220
  phrase: 0
2074
2221
  };
2075
- var _iterator21 = _createForOfIteratorHelper(canonicalMatched),
2076
- _step21;
2222
+ var _iterator22 = _createForOfIteratorHelper(canonicalMatched),
2223
+ _step22;
2077
2224
  try {
2078
- for (_iterator21.s(); !(_step21 = _iterator21.n()).done;) {
2079
- var m = _step21.value;
2225
+ for (_iterator22.s(); !(_step22 = _iterator22.n()).done;) {
2226
+ var m = _step22.value;
2080
2227
  genByType[m.node.termType] += 1;
2081
2228
  }
2082
2229
  } catch (err) {
2083
- _iterator21.e(err);
2230
+ _iterator22.e(err);
2084
2231
  } finally {
2085
- _iterator21.f();
2232
+ _iterator22.f();
2086
2233
  }
2087
2234
  (0, _debugMode.ctcSection)('GENERATE', "matched ".concat(canonicalMatched.length, " \u2192 word:").concat(genByType.word, " bigram:").concat(genByType.bigram, " phrase:").concat(genByType.phrase).concat(canonicalLmSupported ? ' · display needs exact surface evidence' : ''));
2088
2235