@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.
@@ -187,6 +187,18 @@ var DEBUG_TEXT_TAIL_CHARS = 120;
187
187
  * is vocabulary coverage.
188
188
  */
189
189
 
190
+ /**
191
+ * What the scored path concluded, recorded on every evaluation whether or not
192
+ * debug is on.
193
+ *
194
+ * This exists for the inline-code harvester, which may only offer a harvested
195
+ * surface once the scored path has finished and come away empty. The reason is
196
+ * the load-bearing part: `no-candidate` means no vocabulary reaches this prefix
197
+ * at all, while `winner-margin` means two known words the model cannot yet
198
+ * separate — the first is a gap worth filling and the second is a prefix
199
+ * ambiguous enough that filling it would be a guess.
200
+ */
201
+
190
202
  /**
191
203
  * A candidate paired with the length of the already-typed prefix it completes.
192
204
  * For a single word this is the current partial token length; for a phrase it
@@ -214,6 +226,17 @@ var WeightedWordTrie = /*#__PURE__*/function () {
214
226
  function WeightedWordTrie() {
215
227
  _classCallCheck(this, WeightedWordTrie);
216
228
  _defineProperty(this, "root", new TrieNode());
229
+ /**
230
+ * Every node a session boost has been written to.
231
+ *
232
+ * Kept because dropping the boosts is no longer a rare event — it happens
233
+ * each time the reader changes page or conversation — and walking a vocabulary
234
+ * of tens of thousands of words to find the few hundred that were touched
235
+ * costs about 10ms of main thread every time. The two writers below are the
236
+ * only way a `sessionFreq` moves, so keeping this in step costs one Set
237
+ * insertion on a path that is already descending the trie.
238
+ */
239
+ _defineProperty(this, "boostedNodes", new Set());
217
240
  /** Highest tenantFreq seen — used to normalize freq scores at query time */
218
241
  _defineProperty(this, "maxTenantFreq", 1);
219
242
  }
@@ -330,6 +353,13 @@ var WeightedWordTrie = /*#__PURE__*/function () {
330
353
  return node.word !== null ? node : null;
331
354
  }
332
355
 
356
+ /** Whether this exact surface is stored as a terminal word. */
357
+ }, {
358
+ key: "hasWord",
359
+ value: function hasWord(word) {
360
+ return this.findNode(word) !== null;
361
+ }
362
+
333
363
  /**
334
364
  * Set the session frequency for a word.
335
365
  * Returns true if the word exists in the trie.
@@ -342,6 +372,7 @@ var WeightedWordTrie = /*#__PURE__*/function () {
342
372
  return false;
343
373
  }
344
374
  node.sessionFreq = count;
375
+ this.boostedNodes.add(node);
345
376
  return true;
346
377
  }
347
378
 
@@ -357,28 +388,51 @@ var WeightedWordTrie = /*#__PURE__*/function () {
357
388
  return false;
358
389
  }
359
390
  node.sessionFreq += 1;
391
+ this.boostedNodes.add(node);
360
392
  return true;
361
393
  }
362
394
 
395
+ /**
396
+ * Zero every session boost, in the number of words boosted rather than the
397
+ * number of words known.
398
+ */
399
+ }, {
400
+ key: "clearSessionBoosts",
401
+ value: function clearSessionBoosts() {
402
+ var _iterator5 = _createForOfIteratorHelper(this.boostedNodes),
403
+ _step5;
404
+ try {
405
+ for (_iterator5.s(); !(_step5 = _iterator5.n()).done;) {
406
+ var node = _step5.value;
407
+ node.sessionFreq = 0;
408
+ }
409
+ } catch (err) {
410
+ _iterator5.e(err);
411
+ } finally {
412
+ _iterator5.f();
413
+ }
414
+ this.boostedNodes.clear();
415
+ }
416
+
363
417
  /**
364
418
  * Every word carrying a session boost, optionally limited to one prefix's
365
419
  * subtree. Unlike `getCandidates` a word equal to the prefix is included,
366
420
  * since the question here is what the session holds rather than what could
367
421
  * still be typed.
368
422
  *
369
- * Walks the trie instead of reading an index, so nothing has to be kept in
370
- * step on the write path for the sake of being able to ask.
423
+ * Walks the trie rather than reading `boostedNodes`, since a prefix answer is
424
+ * a subtree question and this only runs when a human asks it.
371
425
  */
372
426
  }, {
373
427
  key: "collectSessionBoosted",
374
428
  value: function collectSessionBoosted() {
375
429
  var prefix = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : '';
376
430
  var node = this.root;
377
- var _iterator5 = _createForOfIteratorHelper(prefix.toLowerCase()),
378
- _step5;
431
+ var _iterator6 = _createForOfIteratorHelper(prefix.toLowerCase()),
432
+ _step6;
379
433
  try {
380
- for (_iterator5.s(); !(_step5 = _iterator5.n()).done;) {
381
- var char = _step5.value;
434
+ for (_iterator6.s(); !(_step6 = _iterator6.n()).done;) {
435
+ var char = _step6.value;
382
436
  var next = node.children.get(char);
383
437
  if (!next) {
384
438
  return [];
@@ -386,9 +440,9 @@ var WeightedWordTrie = /*#__PURE__*/function () {
386
440
  node = next;
387
441
  }
388
442
  } catch (err) {
389
- _iterator5.e(err);
443
+ _iterator6.e(err);
390
444
  } finally {
391
- _iterator5.f();
445
+ _iterator6.f();
392
446
  }
393
447
  var boosted = [];
394
448
  var stack = [node];
@@ -403,17 +457,17 @@ var WeightedWordTrie = /*#__PURE__*/function () {
403
457
  node: current
404
458
  });
405
459
  }
406
- var _iterator6 = _createForOfIteratorHelper(current.children.values()),
407
- _step6;
460
+ var _iterator7 = _createForOfIteratorHelper(current.children.values()),
461
+ _step7;
408
462
  try {
409
- for (_iterator6.s(); !(_step6 = _iterator6.n()).done;) {
410
- var child = _step6.value;
463
+ for (_iterator7.s(); !(_step7 = _iterator7.n()).done;) {
464
+ var child = _step7.value;
411
465
  stack.push(child);
412
466
  }
413
467
  } catch (err) {
414
- _iterator6.e(err);
468
+ _iterator7.e(err);
415
469
  } finally {
416
- _iterator6.f();
470
+ _iterator7.f();
417
471
  }
418
472
  }
419
473
  return boosted;
@@ -439,19 +493,19 @@ var phraseTrie = new WeightedWordTrie();
439
493
  * expects a simple array of strings: ["about", "above", "actually", ...]
440
494
  */
441
495
  export var initL3Vocabulary = function initL3Vocabulary(l3Words) {
442
- var _iterator7 = _createForOfIteratorHelper(l3Words),
443
- _step7;
496
+ var _iterator8 = _createForOfIteratorHelper(l3Words),
497
+ _step8;
444
498
  try {
445
- for (_iterator7.s(); !(_step7 = _iterator7.n()).done;) {
446
- var word = _step7.value;
499
+ for (_iterator8.s(); !(_step8 = _iterator8.n()).done;) {
500
+ var word = _step8.value;
447
501
  // Insert with a tiny baseline frequency so it mathematically
448
502
  // loses to any domain word in Stage 1, but still scores above 0.
449
503
  l3Trie.insert(word, L3_BASELINE_FREQ, 0, 0);
450
504
  }
451
505
  } catch (err) {
452
- _iterator7.e(err);
506
+ _iterator8.e(err);
453
507
  } finally {
454
- _iterator7.f();
508
+ _iterator8.f();
455
509
  }
456
510
  recallGeneration++;
457
511
  ctcTag('init', "L3 general English loaded: ".concat(l3Words.length, " words"));
@@ -477,6 +531,15 @@ var phraseTermCount = 0;
477
531
  var maxBigramFreq = 1;
478
532
  var maxPhraseFreq = 1;
479
533
  var lastPredictionDebug = null;
534
+ var lastPredictionOutcome = null;
535
+ var recordPredictionOutcome = function recordPredictionOutcome(outcome) {
536
+ lastPredictionOutcome = outcome;
537
+ };
538
+
539
+ /** The verdict from the most recent `predict` call. Always populated. */
540
+ export var getLastPredictionOutcome = function getLastPredictionOutcome() {
541
+ return lastPredictionOutcome;
542
+ };
480
543
 
481
544
  // ── Stabilization (QI-2): post-accept cooldown + whole-surface repetition ────
482
545
  // Two guards that stop the accept→echo (`end to end` → `end to end to end`) and
@@ -508,6 +571,22 @@ export var noteSuggestionAccepted = function noteSuggestionAccepted(surface) {
508
571
  }
509
572
  };
510
573
 
574
+ /**
575
+ * Whether `surface` is the one the user just accepted and is still inside its
576
+ * cooldown window.
577
+ *
578
+ * Read-only, unlike the advance inside `predict`: the cooldown is measured in
579
+ * predictions, and a caller asking whether it is active must not consume one of
580
+ * them. Exported for the harvest path, which displays without going through
581
+ * arbitration and so would otherwise re-offer what was just accepted.
582
+ */
583
+ export var isSurfaceInAcceptCooldown = function isSurfaceInAcceptCooldown(surface) {
584
+ if (!acceptCooldown || acceptCooldown.surface !== surface.trim().toLowerCase()) {
585
+ return false;
586
+ }
587
+ return acceptCooldown.predictionsSince <= COOLDOWN_KEYSTROKES || performance.now() - acceptCooldown.ts < COOLDOWN_MS;
588
+ };
589
+
511
590
  /** Get vector for a word from the store. */
512
591
  var getWordVector = function getWordVector(word) {
513
592
  if (!vectorStore) {
@@ -532,20 +611,20 @@ var computeContextVectorLocal = function computeContextVectorLocal(textBefore) {
532
611
  var tokens = tokenize(textBefore);
533
612
  var words = tokens.slice(-CONTEXT_WORDS);
534
613
  var vectors = [];
535
- var _iterator8 = _createForOfIteratorHelper(words),
536
- _step8;
614
+ var _iterator9 = _createForOfIteratorHelper(words),
615
+ _step9;
537
616
  try {
538
- for (_iterator8.s(); !(_step8 = _iterator8.n()).done;) {
539
- var word = _step8.value;
617
+ for (_iterator9.s(); !(_step9 = _iterator9.n()).done;) {
618
+ var word = _step9.value;
540
619
  var _v = getWordVector(word);
541
620
  if (_v) {
542
621
  vectors.push(_v);
543
622
  }
544
623
  }
545
624
  } catch (err) {
546
- _iterator8.e(err);
625
+ _iterator9.e(err);
547
626
  } finally {
548
- _iterator8.f();
627
+ _iterator9.f();
549
628
  }
550
629
  if (vectors.length === 0) {
551
630
  return null;
@@ -578,11 +657,11 @@ var getContextVectorForScoring = function getContextVectorForScoring(textBefore)
578
657
  var tokenize = function tokenize(text) {
579
658
  var tokens = [];
580
659
  // eslint-disable-next-line @atlassian/perf-linting/no-expensive-split-replace
581
- var _iterator9 = _createForOfIteratorHelper(text.toLowerCase().split(WHITESPACE_SPLIT_REGEX)),
582
- _step9;
660
+ var _iterator0 = _createForOfIteratorHelper(text.toLowerCase().split(WHITESPACE_SPLIT_REGEX)),
661
+ _step0;
583
662
  try {
584
- for (_iterator9.s(); !(_step9 = _iterator9.n()).done;) {
585
- var raw = _step9.value;
663
+ for (_iterator0.s(); !(_step0 = _iterator0.n()).done;) {
664
+ var raw = _step0.value;
586
665
  // eslint-disable-next-line @atlassian/perf-linting/no-expensive-split-replace
587
666
  var clean = raw.replace(PUNCTUATION_BOUNDARY_REGEX, '');
588
667
  if (clean.length >= 2) {
@@ -590,9 +669,9 @@ var tokenize = function tokenize(text) {
590
669
  }
591
670
  }
592
671
  } catch (err) {
593
- _iterator9.e(err);
672
+ _iterator0.e(err);
594
673
  } finally {
595
- _iterator9.f();
674
+ _iterator0.f();
596
675
  }
597
676
  return tokens;
598
677
  };
@@ -696,11 +775,11 @@ var getPhraseCandidates = function getPhraseCandidates(trimmed) {
696
775
  window: windowPrefix,
697
776
  matches: matches.length
698
777
  });
699
- var _iterator0 = _createForOfIteratorHelper(matches),
700
- _step0;
778
+ var _iterator1 = _createForOfIteratorHelper(matches),
779
+ _step1;
701
780
  try {
702
- for (_iterator0.s(); !(_step0 = _iterator0.n()).done;) {
703
- var _match = _step0.value;
781
+ for (_iterator1.s(); !(_step1 = _iterator1.n()).done;) {
782
+ var _match = _step1.value;
704
783
  if (seen.has(_match.word)) {
705
784
  continue;
706
785
  }
@@ -713,9 +792,9 @@ var getPhraseCandidates = function getPhraseCandidates(trimmed) {
713
792
  });
714
793
  }
715
794
  } catch (err) {
716
- _iterator0.e(err);
795
+ _iterator1.e(err);
717
796
  } finally {
718
- _iterator0.f();
797
+ _iterator1.f();
719
798
  }
720
799
  }
721
800
  logPhrasePath();
@@ -760,17 +839,17 @@ export var getLastPredictionDebug = function getLastPredictionDebug() {
760
839
  return lastPredictionDebug;
761
840
  };
762
841
  export var initVocabulary = function initVocabulary(vocabulary) {
763
- var _iterator1 = _createForOfIteratorHelper(vocabulary.terms),
764
- _step1;
842
+ var _iterator10 = _createForOfIteratorHelper(vocabulary.terms),
843
+ _step10;
765
844
  try {
766
- for (_iterator1.s(); !(_step1 = _iterator1.n()).done;) {
767
- var term = _step1.value;
845
+ for (_iterator10.s(); !(_step10 = _iterator10.n()).done;) {
846
+ var term = _step10.value;
768
847
  wordTrie.insert(term.word, term.freq, term.docFreq, term.authorFreq);
769
848
  }
770
849
  } catch (err) {
771
- _iterator1.e(err);
850
+ _iterator10.e(err);
772
851
  } finally {
773
- _iterator1.f();
852
+ _iterator10.f();
774
853
  }
775
854
  isInitialized = true;
776
855
  recallGeneration++;
@@ -821,6 +900,36 @@ export var incrementSessionFreq = function incrementSessionFreq(word) {
821
900
  wordTrie.incrementSessionFreq(word);
822
901
  };
823
902
 
903
+ /**
904
+ * Drop every L1 boost this session has accumulated.
905
+ *
906
+ * The vocabulary itself is left alone: only `sessionFreq` is cleared, so the
907
+ * tenant and generic frequencies a boost was sitting on top of survive. Called
908
+ * when the plugin decides the session it was learning for has ended — a new
909
+ * conversation, or a different page — since a boost is a claim about what is
910
+ * being discussed and that claim does not carry over.
911
+ */
912
+ export var resetSessionBoosts = function resetSessionBoosts() {
913
+ wordTrie.clearSessionBoosts();
914
+ // Anything memoized against the old boosts is now describing a session that
915
+ // no longer exists.
916
+ recallGeneration++;
917
+ };
918
+
919
+ /**
920
+ * Which vocabulary already holds this surface, if any.
921
+ *
922
+ * Used by the inline-code harvester to drop terms the scored path can already
923
+ * serve, so that harvesting stays limited to words with no route to a
924
+ * suggestion today.
925
+ */
926
+ export var lookupVocabularySource = function lookupVocabularySource(word) {
927
+ if (wordTrie.hasWord(word)) {
928
+ return 'l2';
929
+ }
930
+ return l3Trie.hasWord(word) ? 'l3' : null;
931
+ };
932
+
824
933
  /**
825
934
  * Prime session frequencies from a document page string.
826
935
  *
@@ -837,20 +946,20 @@ export var ingestDocumentPage = function ingestDocumentPage(pageContent) {
837
946
  }
838
947
  var words = tokenize(pageContent);
839
948
  var validBoostedWords = new Set();
840
- var _iterator10 = _createForOfIteratorHelper(words),
841
- _step10;
949
+ var _iterator11 = _createForOfIteratorHelper(words),
950
+ _step11;
842
951
  try {
843
- for (_iterator10.s(); !(_step10 = _iterator10.n()).done;) {
844
- var word = _step10.value;
952
+ for (_iterator11.s(); !(_step11 = _iterator11.n()).done;) {
953
+ var word = _step11.value;
845
954
  var didBoost = wordTrie.incrementSessionFreq(word);
846
955
  if (didBoost) {
847
956
  validBoostedWords.add(word);
848
957
  }
849
958
  }
850
959
  } catch (err) {
851
- _iterator10.e(err);
960
+ _iterator11.e(err);
852
961
  } finally {
853
- _iterator10.f();
962
+ _iterator11.f();
854
963
  }
855
964
  if (isAutocompleteDebugEnabled() && validBoostedWords.size > 0) {
856
965
  ctcTag('init', "L1 session primed ".concat(validBoostedWords.size, " words from page \xB7 __atlCtcDebug__.session() to inspect"), CTC_STYLES.brand);
@@ -868,7 +977,7 @@ export var ingestDocumentPage = function ingestDocumentPage(pageContent) {
868
977
  * strongest boosts are the ones that change an ordering, and `boosted` still
869
978
  * reports the full size, so the cap loses nothing but volume.
870
979
  */
871
- var MAX_LISTED_SESSION_WORDS = 50;
980
+ var MAX_LISTED_SESSION_WORDS = 100;
872
981
  /**
873
982
  * Read the session's L1 boosts, optionally narrowed to a prefix.
874
983
  *
@@ -879,7 +988,8 @@ var MAX_LISTED_SESSION_WORDS = 50;
879
988
  * Only words the vocabulary already holds can carry a boost, because both writers
880
989
  * go through `incrementSessionFreq` and it only finds existing nodes. An ingested
881
990
  * word absent from the vocabulary is therefore missing from here and always will
882
- * be.
991
+ * be — that gap is what the inline-code harvester covers, and those surfaces show
992
+ * up under `__atlCtcDebug__.harvest()` instead.
883
993
  */
884
994
  export var inspectSessionBoosts = function inspectSessionBoosts(prefix) {
885
995
  var boosted = wordTrie.collectSessionBoosted(prefix !== null && prefix !== void 0 ? prefix : '');
@@ -942,20 +1052,20 @@ var computeCanonicalRecall = function computeCanonicalRecall(trimmed, currentWor
942
1052
  var existingWords = new Set(wordCandidates.map(function (c) {
943
1053
  return c.word;
944
1054
  }));
945
- var _iterator11 = _createForOfIteratorHelper(l3Candidates),
946
- _step11;
1055
+ var _iterator12 = _createForOfIteratorHelper(l3Candidates),
1056
+ _step12;
947
1057
  try {
948
- for (_iterator11.s(); !(_step11 = _iterator11.n()).done;) {
949
- var l3c = _step11.value;
1058
+ for (_iterator12.s(); !(_step12 = _iterator12.n()).done;) {
1059
+ var l3c = _step12.value;
950
1060
  if (wordCandidates.length >= MAX_CANDIDATES) break;
951
1061
  if (!existingWords.has(l3c.word)) {
952
1062
  wordCandidates.push(l3c);
953
1063
  }
954
1064
  }
955
1065
  } catch (err) {
956
- _iterator11.e(err);
1066
+ _iterator12.e(err);
957
1067
  } finally {
958
- _iterator11.f();
1068
+ _iterator12.f();
959
1069
  }
960
1070
  }
961
1071
 
@@ -982,19 +1092,19 @@ var computeCanonicalRecall = function computeCanonicalRecall(trimmed, currentWor
982
1092
  return _objectSpread(_objectSpread({}, candidate), deriveCanonicalCandidateContext(trimmed, candidate.matchedPrefixLen, candidate.word, getCanonicalSurfaceTokenIds, candidate.surfaceStart, positionCache));
983
1093
  });
984
1094
  var prefixLenByWord = new Map();
985
- var _iterator12 = _createForOfIteratorHelper(canonicalMatched),
986
- _step12;
1095
+ var _iterator13 = _createForOfIteratorHelper(canonicalMatched),
1096
+ _step13;
987
1097
  try {
988
- for (_iterator12.s(); !(_step12 = _iterator12.n()).done;) {
989
- var m = _step12.value;
1098
+ for (_iterator13.s(); !(_step13 = _iterator13.n()).done;) {
1099
+ var m = _step13.value;
990
1100
  if (!prefixLenByWord.has(m.word)) {
991
1101
  prefixLenByWord.set(m.word, m.matchedPrefixLen);
992
1102
  }
993
1103
  }
994
1104
  } catch (err) {
995
- _iterator12.e(err);
1105
+ _iterator13.e(err);
996
1106
  } finally {
997
- _iterator12.f();
1107
+ _iterator13.f();
998
1108
  }
999
1109
  return {
1000
1110
  canonicalMatched: canonicalMatched,
@@ -1033,6 +1143,14 @@ export var predict = function predict(textBefore) {
1033
1143
  void loadDefaultVocabulary({
1034
1144
  source: 'predict'
1035
1145
  }).catch(function () {});
1146
+ // Awaiting, not empty-handed: with no vocabulary loaded the harvester's own
1147
+ // intake filter has not been applied to anything either.
1148
+ recordPredictionOutcome({
1149
+ abstainReason: 'not-initialized',
1150
+ awaitingAsyncEvidence: true,
1151
+ scoredCandidateCount: 0,
1152
+ textBefore: textBefore
1153
+ });
1036
1154
  return null;
1037
1155
  }
1038
1156
  var t0 = performance.now();
@@ -1061,11 +1179,23 @@ export var predict = function predict(textBefore) {
1061
1179
  priority: 0
1062
1180
  }));
1063
1181
  }
1182
+ recordPredictionOutcome({
1183
+ abstainReason: 'prefetch',
1184
+ awaitingAsyncEvidence: true,
1185
+ scoredCandidateCount: 0,
1186
+ textBefore: textBefore
1187
+ });
1064
1188
  return null;
1065
1189
  }
1066
1190
  var trimmed = textBefore.trimEnd();
1067
1191
  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$ : '';
1068
1192
  if (trailingSurfaceToken.length === 0) {
1193
+ recordPredictionOutcome({
1194
+ abstainReason: 'no-surface-token',
1195
+ awaitingAsyncEvidence: false,
1196
+ scoredCandidateCount: 0,
1197
+ textBefore: textBefore
1198
+ });
1069
1199
  return null;
1070
1200
  }
1071
1201
  var currentWord = trailingSurfaceToken;
@@ -1076,6 +1206,14 @@ export var predict = function predict(textBefore) {
1076
1206
 
1077
1207
  // If every trie was empty for this prefix
1078
1208
  if (canonicalMatched.length === 0) {
1209
+ // Terminal, and the only verdict that says the vocabulary has no claim on
1210
+ // this prefix at all — which is what makes it the harvester's cue.
1211
+ recordPredictionOutcome({
1212
+ abstainReason: 'no-candidate',
1213
+ awaitingAsyncEvidence: false,
1214
+ scoredCandidateCount: 0,
1215
+ textBefore: textBefore
1216
+ });
1079
1217
  if (isAutocompleteDebugEnabled()) {
1080
1218
  // eslint-disable-next-line no-console
1081
1219
  console.log("%c[CTC]%c \u2014 abstain: no matches for \"".concat(currentWord, "\""), CTC_STYLES.brand, CTC_STYLES.body);
@@ -1123,29 +1261,29 @@ export var predict = function predict(textBefore) {
1123
1261
  }))).sort();
1124
1262
  var familyKey = eligibleContextKeys.join("\x01");
1125
1263
  var primeRequests = selectBoundaryPrimeRequests(familyKey, canonicalMatched, PHRASE_MAX_WORDS);
1126
- var _iterator13 = _createForOfIteratorHelper(primeRequests),
1127
- _step13;
1264
+ var _iterator14 = _createForOfIteratorHelper(primeRequests),
1265
+ _step14;
1128
1266
  try {
1129
- for (_iterator13.s(); !(_step13 = _iterator13.n()).done;) {
1130
- var request = _step13.value;
1267
+ for (_iterator14.s(); !(_step14 = _iterator14.n()).done;) {
1268
+ var request = _step14.value;
1131
1269
  primeBoundaryLm(request);
1132
1270
  }
1133
1271
  } catch (err) {
1134
- _iterator13.e(err);
1272
+ _iterator14.e(err);
1135
1273
  } finally {
1136
- _iterator13.f();
1274
+ _iterator14.f();
1137
1275
  }
1138
1276
  var runtimeBySurface = new Map(canonicalMatched.map(function (candidate) {
1139
1277
  return [candidate.word, candidate];
1140
1278
  }));
1141
1279
  var canonicalEvidence = new Map();
1142
1280
  var firstTokenGroups = new Map();
1143
- var _iterator14 = _createForOfIteratorHelper(canonicalMatched),
1144
- _step14;
1281
+ var _iterator15 = _createForOfIteratorHelper(canonicalMatched),
1282
+ _step15;
1145
1283
  try {
1146
- for (_iterator14.s(); !(_step14 = _iterator14.n()).done;) {
1284
+ for (_iterator15.s(); !(_step15 = _iterator15.n()).done;) {
1147
1285
  var _firstTokenGroups$get;
1148
- var _candidate = _step14.value;
1286
+ var _candidate = _step15.value;
1149
1287
  if (_candidate.canonicalTokenIds === null) {
1150
1288
  continue;
1151
1289
  }
@@ -1173,28 +1311,28 @@ export var predict = function predict(textBefore) {
1173
1311
  firstTokenGroups.set(_candidate.contextKey, _group2);
1174
1312
  }
1175
1313
  } catch (err) {
1176
- _iterator14.e(err);
1314
+ _iterator15.e(err);
1177
1315
  } finally {
1178
- _iterator14.f();
1316
+ _iterator15.f();
1179
1317
  }
1180
- var _iterator15 = _createForOfIteratorHelper(firstTokenGroups),
1181
- _step15;
1318
+ var _iterator16 = _createForOfIteratorHelper(firstTokenGroups),
1319
+ _step16;
1182
1320
  try {
1183
- for (_iterator15.s(); !(_step15 = _iterator15.n()).done;) {
1184
- var _step15$value = _slicedToArray(_step15.value, 2),
1185
- _contextKey = _step15$value[0],
1186
- _group3 = _step15$value[1];
1321
+ for (_iterator16.s(); !(_step16 = _iterator16.n()).done;) {
1322
+ var _step16$value = _slicedToArray(_step16.value, 2),
1323
+ _contextKey = _step16$value[0],
1324
+ _group3 = _step16$value[1];
1187
1325
  var boundary = getBoundaryLmState(_contextKey);
1188
1326
  if (!boundary) {
1189
1327
  continue;
1190
1328
  }
1191
1329
  var maxLogit = -Infinity;
1192
- var _iterator22 = _createForOfIteratorHelper(_group3),
1193
- _step22;
1330
+ var _iterator23 = _createForOfIteratorHelper(_group3),
1331
+ _step23;
1194
1332
  try {
1195
- for (_iterator22.s(); !(_step22 = _iterator22.n()).done;) {
1333
+ for (_iterator23.s(); !(_step23 = _iterator23.n()).done;) {
1196
1334
  var _candidate2$canonical;
1197
- var _candidate2 = _step22.value;
1335
+ var _candidate2 = _step23.value;
1198
1336
  var tokenId = (_candidate2$canonical = _candidate2.canonicalTokenIds) === null || _candidate2$canonical === void 0 ? void 0 : _candidate2$canonical[0];
1199
1337
  var rawLogit = tokenId === undefined ? undefined : boundary.rawLogits[tokenId];
1200
1338
  if (rawLogit !== undefined && Number.isFinite(rawLogit) && rawLogit > maxLogit) {
@@ -1202,19 +1340,19 @@ export var predict = function predict(textBefore) {
1202
1340
  }
1203
1341
  }
1204
1342
  } catch (err) {
1205
- _iterator22.e(err);
1343
+ _iterator23.e(err);
1206
1344
  } finally {
1207
- _iterator22.f();
1345
+ _iterator23.f();
1208
1346
  }
1209
1347
  if (!Number.isFinite(maxLogit)) {
1210
1348
  continue;
1211
1349
  }
1212
- var _iterator23 = _createForOfIteratorHelper(_group3),
1213
- _step23;
1350
+ var _iterator24 = _createForOfIteratorHelper(_group3),
1351
+ _step24;
1214
1352
  try {
1215
- for (_iterator23.s(); !(_step23 = _iterator23.n()).done;) {
1353
+ for (_iterator24.s(); !(_step24 = _iterator24.n()).done;) {
1216
1354
  var _candidate3$canonical, _candidate3$canonical2, _candidate3$canonical3;
1217
- var _candidate3 = _step23.value;
1355
+ var _candidate3 = _step24.value;
1218
1356
  var _tokenId = (_candidate3$canonical = _candidate3.canonicalTokenIds) === null || _candidate3$canonical === void 0 ? void 0 : _candidate3$canonical[0];
1219
1357
  var _rawLogit = _tokenId === undefined ? undefined : boundary.rawLogits[_tokenId];
1220
1358
  if (_rawLogit === undefined || !Number.isFinite(_rawLogit)) {
@@ -1253,15 +1391,15 @@ export var predict = function predict(textBefore) {
1253
1391
  });
1254
1392
  }
1255
1393
  } catch (err) {
1256
- _iterator23.e(err);
1394
+ _iterator24.e(err);
1257
1395
  } finally {
1258
- _iterator23.f();
1396
+ _iterator24.f();
1259
1397
  }
1260
1398
  }
1261
1399
  } catch (err) {
1262
- _iterator15.e(err);
1400
+ _iterator16.e(err);
1263
1401
  } finally {
1264
- _iterator15.f();
1402
+ _iterator16.f();
1265
1403
  }
1266
1404
  var _rankCandidates = rankCandidates(scoringCandidates, contextVector, function (w) {
1267
1405
  return getWordVector(w);
@@ -1282,12 +1420,12 @@ export var predict = function predict(textBefore) {
1282
1420
  });
1283
1421
  if (canonicalScoringSupported && progressiveEligible.length > 0) {
1284
1422
  var byContext = new Map();
1285
- var _iterator16 = _createForOfIteratorHelper(progressiveEligible),
1286
- _step16;
1423
+ var _iterator17 = _createForOfIteratorHelper(progressiveEligible),
1424
+ _step17;
1287
1425
  try {
1288
- for (_iterator16.s(); !(_step16 = _iterator16.n()).done;) {
1426
+ for (_iterator17.s(); !(_step17 = _iterator17.n()).done;) {
1289
1427
  var _byContext$get;
1290
- var candidate = _step16.value;
1428
+ var candidate = _step17.value;
1291
1429
  var runtime = runtimeBySurface.get(candidate.word);
1292
1430
  if (!runtime || runtime.canonicalTokenIds === null) {
1293
1431
  continue;
@@ -1300,17 +1438,17 @@ export var predict = function predict(textBefore) {
1300
1438
  byContext.set(runtime.contextKey, group);
1301
1439
  }
1302
1440
  } catch (err) {
1303
- _iterator16.e(err);
1441
+ _iterator17.e(err);
1304
1442
  } finally {
1305
- _iterator16.f();
1443
+ _iterator17.f();
1306
1444
  }
1307
- var _iterator17 = _createForOfIteratorHelper(byContext),
1308
- _step17;
1445
+ var _iterator18 = _createForOfIteratorHelper(byContext),
1446
+ _step18;
1309
1447
  try {
1310
- for (_iterator17.s(); !(_step17 = _iterator17.n()).done;) {
1311
- var _step17$value = _slicedToArray(_step17.value, 2),
1312
- contextKey = _step17$value[0],
1313
- _group = _step17$value[1];
1448
+ for (_iterator18.s(); !(_step18 = _iterator18.n()).done;) {
1449
+ var _step18$value = _slicedToArray(_step18.value, 2),
1450
+ contextKey = _step18$value[0],
1451
+ _group = _step18$value[1];
1314
1452
  requestProgressiveSurfaceScores({
1315
1453
  familyKey: familyKey,
1316
1454
  contextKey: contextKey,
@@ -1328,9 +1466,9 @@ export var predict = function predict(textBefore) {
1328
1466
  });
1329
1467
  }
1330
1468
  } catch (err) {
1331
- _iterator17.e(err);
1469
+ _iterator18.e(err);
1332
1470
  } finally {
1333
- _iterator17.f();
1471
+ _iterator18.f();
1334
1472
  }
1335
1473
  }
1336
1474
 
@@ -1419,12 +1557,12 @@ export var predict = function predict(textBefore) {
1419
1557
  var contextRequestedCount = new Map();
1420
1558
  var bestTotalByContext = new Map();
1421
1559
  var bestCandidateByContext = new Map();
1422
- var _iterator18 = _createForOfIteratorHelper(ranked),
1423
- _step18;
1560
+ var _iterator19 = _createForOfIteratorHelper(ranked),
1561
+ _step19;
1424
1562
  try {
1425
- for (_iterator18.s(); !(_step18 = _iterator18.n()).done;) {
1563
+ for (_iterator19.s(); !(_step19 = _iterator19.n()).done;) {
1426
1564
  var _contextRequestedCoun2, _judged$total;
1427
- var _candidate4 = _step18.value;
1565
+ var _candidate4 = _step19.value;
1428
1566
  var _runtime = runtimeBySurface.get(_candidate4.word);
1429
1567
  if (!_runtime || _runtime.canonicalTokenIds === null) {
1430
1568
  continue;
@@ -1463,9 +1601,9 @@ export var predict = function predict(textBefore) {
1463
1601
  * costs one lookup per space rather than a comparison against every member.
1464
1602
  */
1465
1603
  } catch (err) {
1466
- _iterator18.e(err);
1604
+ _iterator19.e(err);
1467
1605
  } finally {
1468
- _iterator18.f();
1606
+ _iterator19.f();
1469
1607
  }
1470
1608
  var extendsAPoolMember = function extendsAPoolMember(surface, pool) {
1471
1609
  for (var space = surface.indexOf(' '); space !== -1; space = surface.indexOf(' ', space + 1)) {
@@ -1500,22 +1638,22 @@ export var predict = function predict(textBefore) {
1500
1638
  // it continues, because the cap alone would leave it looking exactly as
1501
1639
  // certain as its prefix while saying nothing about its own tail.
1502
1640
  var underReadExtensions = new Set();
1503
- var _iterator19 = _createForOfIteratorHelper(contextSurfaces),
1504
- _step19;
1641
+ var _iterator20 = _createForOfIteratorHelper(contextSurfaces),
1642
+ _step20;
1505
1643
  try {
1506
- for (_iterator19.s(); !(_step19 = _iterator19.n()).done;) {
1507
- var _step19$value = _slicedToArray(_step19.value, 2),
1508
- _contextKey2 = _step19$value[0],
1509
- _surfaces = _step19$value[1];
1644
+ for (_iterator20.s(); !(_step20 = _iterator20.n()).done;) {
1645
+ var _step20$value = _slicedToArray(_step20.value, 2),
1646
+ _contextKey2 = _step20$value[0],
1647
+ _surfaces = _step20$value[1];
1510
1648
  var pool = new Set(_surfaces);
1511
- var _iterator24 = _createForOfIteratorHelper(_toConsumableArray(_surfaces).sort(function (a, b) {
1649
+ var _iterator25 = _createForOfIteratorHelper(_toConsumableArray(_surfaces).sort(function (a, b) {
1512
1650
  return a.length - b.length;
1513
1651
  })),
1514
- _step24;
1652
+ _step25;
1515
1653
  try {
1516
- for (_iterator24.s(); !(_step24 = _iterator24.n()).done;) {
1654
+ for (_iterator25.s(); !(_step25 = _iterator25.n()).done;) {
1517
1655
  var _getProgressiveSurfac, _getProgressiveSurfac2, _runtimeBySurface$get8, _runtimeBySurface$get9;
1518
- var surface = _step24.value;
1656
+ var surface = _step25.value;
1519
1657
  var own = optimisticTotalByWord.get(surface);
1520
1658
  if (own === undefined) {
1521
1659
  continue;
@@ -1547,9 +1685,9 @@ export var predict = function predict(textBefore) {
1547
1685
  }
1548
1686
  }
1549
1687
  } catch (err) {
1550
- _iterator24.e(err);
1688
+ _iterator25.e(err);
1551
1689
  } finally {
1552
- _iterator24.f();
1690
+ _iterator25.f();
1553
1691
  }
1554
1692
  }
1555
1693
 
@@ -1562,9 +1700,9 @@ export var predict = function predict(textBefore) {
1562
1700
  * depths.
1563
1701
  */
1564
1702
  } catch (err) {
1565
- _iterator19.e(err);
1703
+ _iterator20.e(err);
1566
1704
  } finally {
1567
- _iterator19.f();
1705
+ _iterator20.f();
1568
1706
  }
1569
1707
  var judgedEvidenceFor = function judgedEvidenceFor(candidate) {
1570
1708
  return underReadExtensions.has(candidate.word) ? null : readJudgedEvidence(candidate);
@@ -1591,20 +1729,20 @@ export var predict = function predict(textBefore) {
1591
1729
  // `contextTotals`, so how contested a context is still counts every scored
1592
1730
  // candidate.
1593
1731
  var logSumExpByContext = new Map();
1594
- var _iterator20 = _createForOfIteratorHelper(contextSurfaces),
1595
- _step20;
1732
+ var _iterator21 = _createForOfIteratorHelper(contextSurfaces),
1733
+ _step21;
1596
1734
  try {
1597
- for (_iterator20.s(); !(_step20 = _iterator20.n()).done;) {
1598
- var _step20$value = _slicedToArray(_step20.value, 2),
1599
- _contextKey3 = _step20$value[0],
1600
- _surfaces2 = _step20$value[1];
1735
+ for (_iterator21.s(); !(_step21 = _iterator21.n()).done;) {
1736
+ var _step21$value = _slicedToArray(_step21.value, 2),
1737
+ _contextKey3 = _step21$value[0],
1738
+ _surfaces2 = _step21$value[1];
1601
1739
  var _pool = new Set(_surfaces2);
1602
1740
  var minimalTotals = [];
1603
- var _iterator25 = _createForOfIteratorHelper(_surfaces2),
1604
- _step25;
1741
+ var _iterator26 = _createForOfIteratorHelper(_surfaces2),
1742
+ _step26;
1605
1743
  try {
1606
- for (_iterator25.s(); !(_step25 = _iterator25.n()).done;) {
1607
- var _surface = _step25.value;
1744
+ for (_iterator26.s(); !(_step26 = _iterator26.n()).done;) {
1745
+ var _surface = _step26.value;
1608
1746
  var total = optimisticTotalByWord.get(_surface);
1609
1747
  if (total !== undefined && !extendsAPoolMember(_surface, _pool)) {
1610
1748
  minimalTotals.push(total);
@@ -1613,9 +1751,9 @@ export var predict = function predict(textBefore) {
1613
1751
  // An extension is strictly longer than what it extends, so the shortest
1614
1752
  // member of any non-empty pool is always minimal and this is never empty.
1615
1753
  } catch (err) {
1616
- _iterator25.e(err);
1754
+ _iterator26.e(err);
1617
1755
  } finally {
1618
- _iterator25.f();
1756
+ _iterator26.f();
1619
1757
  }
1620
1758
  logSumExpByContext.set(_contextKey3, logSumExp(minimalTotals));
1621
1759
  }
@@ -1627,9 +1765,9 @@ export var predict = function predict(textBefore) {
1627
1765
  * eventual posterior, or a verified total for the posterior itself.
1628
1766
  */
1629
1767
  } catch (err) {
1630
- _iterator20.e(err);
1768
+ _iterator21.e(err);
1631
1769
  } finally {
1632
- _iterator20.f();
1770
+ _iterator21.f();
1633
1771
  }
1634
1772
  var posteriorFor = function posteriorFor(candidate, total) {
1635
1773
  var runtime = runtimeBySurface.get(candidate.word);
@@ -1919,6 +2057,12 @@ export var predict = function predict(textBefore) {
1919
2057
  return ranked.some(hasJudgeableEvidence) ? 'below-posterior-gate' : 'no-evidence';
1920
2058
  };
1921
2059
  var abstainReason = resolveAbstainReason();
2060
+ recordPredictionOutcome({
2061
+ abstainReason: abstainReason,
2062
+ awaitingAsyncEvidence: hasUnresolvedPotential && !clearsWinnerMargin,
2063
+ scoredCandidateCount: ranked.length,
2064
+ textBefore: textBefore
2065
+ });
1922
2066
 
1923
2067
  // The leader is the best-supported candidate, not the selected one: an
1924
2068
  // evaluation that showed nothing is exactly the one whose posterior needs
@@ -1974,6 +2118,9 @@ export var predict = function predict(textBefore) {
1974
2118
  'missing-artifact': 'canonical artifact coverage missing',
1975
2119
  'no-candidate': 'nothing cleared scoring',
1976
2120
  'no-evidence': 'full-surface evidence absent',
2121
+ // Recorded at the two exits above this block, so they never print here.
2122
+ 'no-surface-token': 'no trailing surface token',
2123
+ 'not-initialized': 'vocabulary not loaded',
1977
2124
  prefetch: "prefetch: ".concat(currentWord.length, "/").concat(DISPLAY_MIN_PREFIX_LENGTH, " chars"),
1978
2125
  'short-completion': "completion shorter than ".concat(MIN_SUGGESTION_LENGTH, " chars"),
1979
2126
  'unresolved-rival': 'expanding plausible token-prefix groups',
@@ -2068,17 +2215,17 @@ export var predict = function predict(textBefore) {
2068
2215
  bigram: 0,
2069
2216
  phrase: 0
2070
2217
  };
2071
- var _iterator21 = _createForOfIteratorHelper(canonicalMatched),
2072
- _step21;
2218
+ var _iterator22 = _createForOfIteratorHelper(canonicalMatched),
2219
+ _step22;
2073
2220
  try {
2074
- for (_iterator21.s(); !(_step21 = _iterator21.n()).done;) {
2075
- var m = _step21.value;
2221
+ for (_iterator22.s(); !(_step22 = _iterator22.n()).done;) {
2222
+ var m = _step22.value;
2076
2223
  genByType[m.node.termType] += 1;
2077
2224
  }
2078
2225
  } catch (err) {
2079
- _iterator21.e(err);
2226
+ _iterator22.e(err);
2080
2227
  } finally {
2081
- _iterator21.f();
2228
+ _iterator22.f();
2082
2229
  }
2083
2230
  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' : ''));
2084
2231