@atlaskit/editor-plugin-autocomplete 9.0.0 → 9.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -25,14 +25,17 @@ function _arrayLikeToArray(r, a) { (null == a || a > r.length) && (a = r.length)
25
25
  * Falls back to cold mode (freq-only) when vectors not yet loaded.
26
26
  *
27
27
  * Session personalization (L1): words the user types are incrementally boosted
28
- * via incrementSessionFreq(), called on word boundaries from the plugin.
28
+ * via incrementSessionFreq(), called on word boundaries from the plugin, and
29
+ * words in ingested context text via ingestDocumentPage(). What the session has
30
+ * boosted is visible at any time from the console: `__atlCtcDebug__.session()`,
31
+ * or `__atlCtcDebug__.session('poll')` for one family — see inspectSessionBoosts.
29
32
  */
30
33
 
31
34
  import { EXPERIENCE_NAME, failExp, startExp, succeedExp } from '../analytics/ufo';
32
35
  import { fetchAutocompleteArtifactBinary, fetchAutocompleteArtifactJson } from './artifact-loader';
33
36
  import { ARTIFACT_NAME } from './artifacts-manifest';
34
37
  import { createCanonicalContextPositionCache, deriveCanonicalCandidateContext, deriveWhitespaceBoundaryContext, logSoftmaxAt, logSumExp, selectBoundaryPrimeRequests } from './canonical-lm-scoring';
35
- import { CTC_STYLES, ctcSection, ctcTag, isAutocompleteDebugEnabled, isAutocompleteDebugVerbose } from './debug-mode';
38
+ import { CTC_STYLES, ctcSection, ctcTag, isAutocompleteDebugEnabled, isAutocompleteDebugVerbose, registerCtcSessionInspector } from './debug-mode';
36
39
  import { loadGrammarDataAsync, rankCandidates, STAGE1_WEIGHT, STAGE2_WEIGHT, MIN_STAGE1_SCORE, MIN_WINNER_MARGIN } from './scoring-pipeline';
37
40
  import { getBoundaryLmState, getCanonicalSurfaceCount, getCanonicalSurfaceTokenIds, getDefaultSlowLaneClientStatus, getProgressiveSurfaceEvidence, getStoredContextInput, getStoredContextVector, getStoredLmLogits, getSurfaceScore, isCanonicalSurfaceScoringSupported, primeBoundaryLm, requestProgressiveSurfaceScores } from './slow-lane-client';
38
41
 
@@ -184,6 +187,18 @@ var DEBUG_TEXT_TAIL_CHARS = 120;
184
187
  * is vocabulary coverage.
185
188
  */
186
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
+
187
202
  /**
188
203
  * A candidate paired with the length of the already-typed prefix it completes.
189
204
  * For a single word this is the current partial token length; for a phrase it
@@ -211,6 +226,17 @@ var WeightedWordTrie = /*#__PURE__*/function () {
211
226
  function WeightedWordTrie() {
212
227
  _classCallCheck(this, WeightedWordTrie);
213
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());
214
240
  /** Highest tenantFreq seen — used to normalize freq scores at query time */
215
241
  _defineProperty(this, "maxTenantFreq", 1);
216
242
  }
@@ -327,6 +353,13 @@ var WeightedWordTrie = /*#__PURE__*/function () {
327
353
  return node.word !== null ? node : null;
328
354
  }
329
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
+
330
363
  /**
331
364
  * Set the session frequency for a word.
332
365
  * Returns true if the word exists in the trie.
@@ -339,6 +372,7 @@ var WeightedWordTrie = /*#__PURE__*/function () {
339
372
  return false;
340
373
  }
341
374
  node.sessionFreq = count;
375
+ this.boostedNodes.add(node);
342
376
  return true;
343
377
  }
344
378
 
@@ -354,8 +388,90 @@ var WeightedWordTrie = /*#__PURE__*/function () {
354
388
  return false;
355
389
  }
356
390
  node.sessionFreq += 1;
391
+ this.boostedNodes.add(node);
357
392
  return true;
358
393
  }
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
+
417
+ /**
418
+ * Every word carrying a session boost, optionally limited to one prefix's
419
+ * subtree. Unlike `getCandidates` a word equal to the prefix is included,
420
+ * since the question here is what the session holds rather than what could
421
+ * still be typed.
422
+ *
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.
425
+ */
426
+ }, {
427
+ key: "collectSessionBoosted",
428
+ value: function collectSessionBoosted() {
429
+ var prefix = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : '';
430
+ var node = this.root;
431
+ var _iterator6 = _createForOfIteratorHelper(prefix.toLowerCase()),
432
+ _step6;
433
+ try {
434
+ for (_iterator6.s(); !(_step6 = _iterator6.n()).done;) {
435
+ var char = _step6.value;
436
+ var next = node.children.get(char);
437
+ if (!next) {
438
+ return [];
439
+ }
440
+ node = next;
441
+ }
442
+ } catch (err) {
443
+ _iterator6.e(err);
444
+ } finally {
445
+ _iterator6.f();
446
+ }
447
+ var boosted = [];
448
+ var stack = [node];
449
+ while (stack.length > 0) {
450
+ var current = stack.pop();
451
+ if (!current) {
452
+ continue;
453
+ }
454
+ if (current.word !== null && current.sessionFreq > 0) {
455
+ boosted.push({
456
+ word: current.word,
457
+ node: current
458
+ });
459
+ }
460
+ var _iterator7 = _createForOfIteratorHelper(current.children.values()),
461
+ _step7;
462
+ try {
463
+ for (_iterator7.s(); !(_step7 = _iterator7.n()).done;) {
464
+ var child = _step7.value;
465
+ stack.push(child);
466
+ }
467
+ } catch (err) {
468
+ _iterator7.e(err);
469
+ } finally {
470
+ _iterator7.f();
471
+ }
472
+ }
473
+ return boosted;
474
+ }
359
475
  }]);
360
476
  }(); // L1/L2 Trie (Session + Atlassian Domain)
361
477
  var wordTrie = new WeightedWordTrie();
@@ -377,19 +493,19 @@ var phraseTrie = new WeightedWordTrie();
377
493
  * expects a simple array of strings: ["about", "above", "actually", ...]
378
494
  */
379
495
  export var initL3Vocabulary = function initL3Vocabulary(l3Words) {
380
- var _iterator5 = _createForOfIteratorHelper(l3Words),
381
- _step5;
496
+ var _iterator8 = _createForOfIteratorHelper(l3Words),
497
+ _step8;
382
498
  try {
383
- for (_iterator5.s(); !(_step5 = _iterator5.n()).done;) {
384
- var word = _step5.value;
499
+ for (_iterator8.s(); !(_step8 = _iterator8.n()).done;) {
500
+ var word = _step8.value;
385
501
  // Insert with a tiny baseline frequency so it mathematically
386
502
  // loses to any domain word in Stage 1, but still scores above 0.
387
503
  l3Trie.insert(word, L3_BASELINE_FREQ, 0, 0);
388
504
  }
389
505
  } catch (err) {
390
- _iterator5.e(err);
506
+ _iterator8.e(err);
391
507
  } finally {
392
- _iterator5.f();
508
+ _iterator8.f();
393
509
  }
394
510
  recallGeneration++;
395
511
  ctcTag('init', "L3 general English loaded: ".concat(l3Words.length, " words"));
@@ -415,6 +531,15 @@ var phraseTermCount = 0;
415
531
  var maxBigramFreq = 1;
416
532
  var maxPhraseFreq = 1;
417
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
+ };
418
543
 
419
544
  // ── Stabilization (QI-2): post-accept cooldown + whole-surface repetition ────
420
545
  // Two guards that stop the accept→echo (`end to end` → `end to end to end`) and
@@ -446,6 +571,22 @@ export var noteSuggestionAccepted = function noteSuggestionAccepted(surface) {
446
571
  }
447
572
  };
448
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
+
449
590
  /** Get vector for a word from the store. */
450
591
  var getWordVector = function getWordVector(word) {
451
592
  if (!vectorStore) {
@@ -470,20 +611,20 @@ var computeContextVectorLocal = function computeContextVectorLocal(textBefore) {
470
611
  var tokens = tokenize(textBefore);
471
612
  var words = tokens.slice(-CONTEXT_WORDS);
472
613
  var vectors = [];
473
- var _iterator6 = _createForOfIteratorHelper(words),
474
- _step6;
614
+ var _iterator9 = _createForOfIteratorHelper(words),
615
+ _step9;
475
616
  try {
476
- for (_iterator6.s(); !(_step6 = _iterator6.n()).done;) {
477
- var word = _step6.value;
617
+ for (_iterator9.s(); !(_step9 = _iterator9.n()).done;) {
618
+ var word = _step9.value;
478
619
  var _v = getWordVector(word);
479
620
  if (_v) {
480
621
  vectors.push(_v);
481
622
  }
482
623
  }
483
624
  } catch (err) {
484
- _iterator6.e(err);
625
+ _iterator9.e(err);
485
626
  } finally {
486
- _iterator6.f();
627
+ _iterator9.f();
487
628
  }
488
629
  if (vectors.length === 0) {
489
630
  return null;
@@ -516,11 +657,11 @@ var getContextVectorForScoring = function getContextVectorForScoring(textBefore)
516
657
  var tokenize = function tokenize(text) {
517
658
  var tokens = [];
518
659
  // eslint-disable-next-line @atlassian/perf-linting/no-expensive-split-replace
519
- var _iterator7 = _createForOfIteratorHelper(text.toLowerCase().split(WHITESPACE_SPLIT_REGEX)),
520
- _step7;
660
+ var _iterator0 = _createForOfIteratorHelper(text.toLowerCase().split(WHITESPACE_SPLIT_REGEX)),
661
+ _step0;
521
662
  try {
522
- for (_iterator7.s(); !(_step7 = _iterator7.n()).done;) {
523
- var raw = _step7.value;
663
+ for (_iterator0.s(); !(_step0 = _iterator0.n()).done;) {
664
+ var raw = _step0.value;
524
665
  // eslint-disable-next-line @atlassian/perf-linting/no-expensive-split-replace
525
666
  var clean = raw.replace(PUNCTUATION_BOUNDARY_REGEX, '');
526
667
  if (clean.length >= 2) {
@@ -528,9 +669,9 @@ var tokenize = function tokenize(text) {
528
669
  }
529
670
  }
530
671
  } catch (err) {
531
- _iterator7.e(err);
672
+ _iterator0.e(err);
532
673
  } finally {
533
- _iterator7.f();
674
+ _iterator0.f();
534
675
  }
535
676
  return tokens;
536
677
  };
@@ -634,11 +775,11 @@ var getPhraseCandidates = function getPhraseCandidates(trimmed) {
634
775
  window: windowPrefix,
635
776
  matches: matches.length
636
777
  });
637
- var _iterator8 = _createForOfIteratorHelper(matches),
638
- _step8;
778
+ var _iterator1 = _createForOfIteratorHelper(matches),
779
+ _step1;
639
780
  try {
640
- for (_iterator8.s(); !(_step8 = _iterator8.n()).done;) {
641
- var _match = _step8.value;
781
+ for (_iterator1.s(); !(_step1 = _iterator1.n()).done;) {
782
+ var _match = _step1.value;
642
783
  if (seen.has(_match.word)) {
643
784
  continue;
644
785
  }
@@ -651,9 +792,9 @@ var getPhraseCandidates = function getPhraseCandidates(trimmed) {
651
792
  });
652
793
  }
653
794
  } catch (err) {
654
- _iterator8.e(err);
795
+ _iterator1.e(err);
655
796
  } finally {
656
- _iterator8.f();
797
+ _iterator1.f();
657
798
  }
658
799
  }
659
800
  logPhrasePath();
@@ -698,17 +839,17 @@ export var getLastPredictionDebug = function getLastPredictionDebug() {
698
839
  return lastPredictionDebug;
699
840
  };
700
841
  export var initVocabulary = function initVocabulary(vocabulary) {
701
- var _iterator9 = _createForOfIteratorHelper(vocabulary.terms),
702
- _step9;
842
+ var _iterator10 = _createForOfIteratorHelper(vocabulary.terms),
843
+ _step10;
703
844
  try {
704
- for (_iterator9.s(); !(_step9 = _iterator9.n()).done;) {
705
- var term = _step9.value;
845
+ for (_iterator10.s(); !(_step10 = _iterator10.n()).done;) {
846
+ var term = _step10.value;
706
847
  wordTrie.insert(term.word, term.freq, term.docFreq, term.authorFreq);
707
848
  }
708
849
  } catch (err) {
709
- _iterator9.e(err);
850
+ _iterator10.e(err);
710
851
  } finally {
711
- _iterator9.f();
852
+ _iterator10.f();
712
853
  }
713
854
  isInitialized = true;
714
855
  recallGeneration++;
@@ -759,6 +900,36 @@ export var incrementSessionFreq = function incrementSessionFreq(word) {
759
900
  wordTrie.incrementSessionFreq(word);
760
901
  };
761
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
+
762
933
  /**
763
934
  * Prime session frequencies from a document page string.
764
935
  *
@@ -775,23 +946,23 @@ export var ingestDocumentPage = function ingestDocumentPage(pageContent) {
775
946
  }
776
947
  var words = tokenize(pageContent);
777
948
  var validBoostedWords = new Set();
778
- var _iterator0 = _createForOfIteratorHelper(words),
779
- _step0;
949
+ var _iterator11 = _createForOfIteratorHelper(words),
950
+ _step11;
780
951
  try {
781
- for (_iterator0.s(); !(_step0 = _iterator0.n()).done;) {
782
- var word = _step0.value;
952
+ for (_iterator11.s(); !(_step11 = _iterator11.n()).done;) {
953
+ var word = _step11.value;
783
954
  var didBoost = wordTrie.incrementSessionFreq(word);
784
955
  if (didBoost) {
785
956
  validBoostedWords.add(word);
786
957
  }
787
958
  }
788
959
  } catch (err) {
789
- _iterator0.e(err);
960
+ _iterator11.e(err);
790
961
  } finally {
791
- _iterator0.f();
962
+ _iterator11.f();
792
963
  }
793
964
  if (isAutocompleteDebugEnabled() && validBoostedWords.size > 0) {
794
- ctcTag('init', "L1 session primed ".concat(validBoostedWords.size, " words from page"), CTC_STYLES.brand);
965
+ ctcTag('init', "L1 session primed ".concat(validBoostedWords.size, " words from page \xB7 __atlCtcDebug__.session() to inspect"), CTC_STYLES.brand);
795
966
  if (isAutocompleteDebugVerbose()) {
796
967
  // eslint-disable-next-line no-console
797
968
  console.dir(Array.from(validBoostedWords).sort());
@@ -799,6 +970,57 @@ export var ingestDocumentPage = function ingestDocumentPage(pageContent) {
799
970
  }
800
971
  };
801
972
 
973
+ /**
974
+ * How many boosted words `inspectSessionBoosts` lists.
975
+ *
976
+ * A page ingest can boost thousands, and a list that long is not read. The
977
+ * strongest boosts are the ones that change an ordering, and `boosted` still
978
+ * reports the full size, so the cap loses nothing but volume.
979
+ */
980
+ var MAX_LISTED_SESSION_WORDS = 100;
981
+ /**
982
+ * Read the session's L1 boosts, optionally narrowed to a prefix.
983
+ *
984
+ * Installed as `__atlCtcDebug__.session()`, with `__atlCtcDebug__.session('poll')`
985
+ * to ask about one family. Returned rather than logged, so the console renders it
986
+ * as an inspectable object and a caller can assert on it.
987
+ *
988
+ * Only words the vocabulary already holds can carry a boost, because both writers
989
+ * go through `incrementSessionFreq` and it only finds existing nodes. An ingested
990
+ * word absent from the vocabulary is therefore missing from here and always will
991
+ * be — that gap is what the inline-code harvester covers, and those surfaces show
992
+ * up under `__atlCtcDebug__.harvest()` instead.
993
+ */
994
+ export var inspectSessionBoosts = function inspectSessionBoosts(prefix) {
995
+ var boosted = wordTrie.collectSessionBoosted(prefix !== null && prefix !== void 0 ? prefix : '');
996
+ return _objectSpread(_objectSpread({
997
+ boosted: boosted.length,
998
+ limit: MAX_LISTED_SESSION_WORDS
999
+ }, prefix === undefined ? {} : {
1000
+ prefix: prefix
1001
+ }), {}, {
1002
+ words: boosted.sort(function (a, b) {
1003
+ return b.node.sessionFreq - a.node.sessionFreq || a.word.localeCompare(b.word, 'en', {
1004
+ numeric: true,
1005
+ sensitivity: 'base'
1006
+ });
1007
+ }).slice(0, MAX_LISTED_SESSION_WORDS).map(function (_ref) {
1008
+ var node = _ref.node,
1009
+ word = _ref.word;
1010
+ return {
1011
+ sessionFreq: node.sessionFreq,
1012
+ sessionOnly: node.tenantFreq === 0,
1013
+ surface: word,
1014
+ tenantFreq: node.tenantFreq
1015
+ };
1016
+ })
1017
+ });
1018
+ };
1019
+
1020
+ // At module scope so the console answers before the first keystroke, which is
1021
+ // when someone reaching for it usually asks.
1022
+ registerCtcSessionInspector(inspectSessionBoosts);
1023
+
802
1024
  /**
803
1025
  * Result of a prediction: the ghost tail to insert plus an immutable record of
804
1026
  * the evidence that authorized the UI commitment.
@@ -830,20 +1052,20 @@ var computeCanonicalRecall = function computeCanonicalRecall(trimmed, currentWor
830
1052
  var existingWords = new Set(wordCandidates.map(function (c) {
831
1053
  return c.word;
832
1054
  }));
833
- var _iterator1 = _createForOfIteratorHelper(l3Candidates),
834
- _step1;
1055
+ var _iterator12 = _createForOfIteratorHelper(l3Candidates),
1056
+ _step12;
835
1057
  try {
836
- for (_iterator1.s(); !(_step1 = _iterator1.n()).done;) {
837
- var l3c = _step1.value;
1058
+ for (_iterator12.s(); !(_step12 = _iterator12.n()).done;) {
1059
+ var l3c = _step12.value;
838
1060
  if (wordCandidates.length >= MAX_CANDIDATES) break;
839
1061
  if (!existingWords.has(l3c.word)) {
840
1062
  wordCandidates.push(l3c);
841
1063
  }
842
1064
  }
843
1065
  } catch (err) {
844
- _iterator1.e(err);
1066
+ _iterator12.e(err);
845
1067
  } finally {
846
- _iterator1.f();
1068
+ _iterator12.f();
847
1069
  }
848
1070
  }
849
1071
 
@@ -855,9 +1077,9 @@ var computeCanonicalRecall = function computeCanonicalRecall(trimmed, currentWor
855
1077
  // Unify: a word completes the current partial token; a phrase completes its
856
1078
  // matched multi-word window. Track the prefix length per term so the ghost
857
1079
  // tail is sliced correctly regardless of term type.
858
- var matched = [].concat(_toConsumableArray(wordCandidates.map(function (_ref) {
859
- var word = _ref.word,
860
- node = _ref.node;
1080
+ var matched = [].concat(_toConsumableArray(wordCandidates.map(function (_ref2) {
1081
+ var word = _ref2.word,
1082
+ node = _ref2.node;
861
1083
  return {
862
1084
  word: word,
863
1085
  node: node,
@@ -870,19 +1092,19 @@ var computeCanonicalRecall = function computeCanonicalRecall(trimmed, currentWor
870
1092
  return _objectSpread(_objectSpread({}, candidate), deriveCanonicalCandidateContext(trimmed, candidate.matchedPrefixLen, candidate.word, getCanonicalSurfaceTokenIds, candidate.surfaceStart, positionCache));
871
1093
  });
872
1094
  var prefixLenByWord = new Map();
873
- var _iterator10 = _createForOfIteratorHelper(canonicalMatched),
874
- _step10;
1095
+ var _iterator13 = _createForOfIteratorHelper(canonicalMatched),
1096
+ _step13;
875
1097
  try {
876
- for (_iterator10.s(); !(_step10 = _iterator10.n()).done;) {
877
- var m = _step10.value;
1098
+ for (_iterator13.s(); !(_step13 = _iterator13.n()).done;) {
1099
+ var m = _step13.value;
878
1100
  if (!prefixLenByWord.has(m.word)) {
879
1101
  prefixLenByWord.set(m.word, m.matchedPrefixLen);
880
1102
  }
881
1103
  }
882
1104
  } catch (err) {
883
- _iterator10.e(err);
1105
+ _iterator13.e(err);
884
1106
  } finally {
885
- _iterator10.f();
1107
+ _iterator13.f();
886
1108
  }
887
1109
  return {
888
1110
  canonicalMatched: canonicalMatched,
@@ -921,6 +1143,14 @@ export var predict = function predict(textBefore) {
921
1143
  void loadDefaultVocabulary({
922
1144
  source: 'predict'
923
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
+ });
924
1154
  return null;
925
1155
  }
926
1156
  var t0 = performance.now();
@@ -949,11 +1179,23 @@ export var predict = function predict(textBefore) {
949
1179
  priority: 0
950
1180
  }));
951
1181
  }
1182
+ recordPredictionOutcome({
1183
+ abstainReason: 'prefetch',
1184
+ awaitingAsyncEvidence: true,
1185
+ scoredCandidateCount: 0,
1186
+ textBefore: textBefore
1187
+ });
952
1188
  return null;
953
1189
  }
954
1190
  var trimmed = textBefore.trimEnd();
955
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$ : '';
956
1192
  if (trailingSurfaceToken.length === 0) {
1193
+ recordPredictionOutcome({
1194
+ abstainReason: 'no-surface-token',
1195
+ awaitingAsyncEvidence: false,
1196
+ scoredCandidateCount: 0,
1197
+ textBefore: textBefore
1198
+ });
957
1199
  return null;
958
1200
  }
959
1201
  var currentWord = trailingSurfaceToken;
@@ -964,6 +1206,14 @@ export var predict = function predict(textBefore) {
964
1206
 
965
1207
  // If every trie was empty for this prefix
966
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
+ });
967
1217
  if (isAutocompleteDebugEnabled()) {
968
1218
  // eslint-disable-next-line no-console
969
1219
  console.log("%c[CTC]%c \u2014 abstain: no matches for \"".concat(currentWord, "\""), CTC_STYLES.brand, CTC_STYLES.body);
@@ -976,9 +1226,9 @@ export var predict = function predict(textBefore) {
976
1226
  var mode = contextVector ? 'warm' : 'cold';
977
1227
 
978
1228
  // Build ScoringCandidate array from matched terms (words + phrases)
979
- var scoringCandidates = canonicalMatched.map(function (_ref2) {
980
- var word = _ref2.word,
981
- node = _ref2.node;
1229
+ var scoringCandidates = canonicalMatched.map(function (_ref3) {
1230
+ var word = _ref3.word,
1231
+ node = _ref3.node;
982
1232
  return {
983
1233
  word: word,
984
1234
  tenantFreq: node.tenantFreq,
@@ -998,9 +1248,9 @@ export var predict = function predict(textBefore) {
998
1248
  var currentWordSeparator = (_canonicalMatched$fin = canonicalMatched.find(function (candidate) {
999
1249
  return candidate.node.termType === 'word';
1000
1250
  })) === null || _canonicalMatched$fin === void 0 ? void 0 : _canonicalMatched$fin.separatorKind;
1001
- var prefixLmLogits = lmLogits && currentWordSeparator === 'whitespace' ? Object.fromEntries(Object.entries(lmLogits).filter(function (_ref3) {
1002
- var _ref4 = _slicedToArray(_ref3, 1),
1003
- word = _ref4[0];
1251
+ var prefixLmLogits = lmLogits && currentWordSeparator === 'whitespace' ? Object.fromEntries(Object.entries(lmLogits).filter(function (_ref4) {
1252
+ var _ref5 = _slicedToArray(_ref4, 1),
1253
+ word = _ref5[0];
1004
1254
  return word.startsWith(prefix);
1005
1255
  })) : null;
1006
1256
  var canonicalScoringSupported = isCanonicalSurfaceScoringSupported();
@@ -1011,29 +1261,29 @@ export var predict = function predict(textBefore) {
1011
1261
  }))).sort();
1012
1262
  var familyKey = eligibleContextKeys.join("\x01");
1013
1263
  var primeRequests = selectBoundaryPrimeRequests(familyKey, canonicalMatched, PHRASE_MAX_WORDS);
1014
- var _iterator11 = _createForOfIteratorHelper(primeRequests),
1015
- _step11;
1264
+ var _iterator14 = _createForOfIteratorHelper(primeRequests),
1265
+ _step14;
1016
1266
  try {
1017
- for (_iterator11.s(); !(_step11 = _iterator11.n()).done;) {
1018
- var request = _step11.value;
1267
+ for (_iterator14.s(); !(_step14 = _iterator14.n()).done;) {
1268
+ var request = _step14.value;
1019
1269
  primeBoundaryLm(request);
1020
1270
  }
1021
1271
  } catch (err) {
1022
- _iterator11.e(err);
1272
+ _iterator14.e(err);
1023
1273
  } finally {
1024
- _iterator11.f();
1274
+ _iterator14.f();
1025
1275
  }
1026
1276
  var runtimeBySurface = new Map(canonicalMatched.map(function (candidate) {
1027
1277
  return [candidate.word, candidate];
1028
1278
  }));
1029
1279
  var canonicalEvidence = new Map();
1030
1280
  var firstTokenGroups = new Map();
1031
- var _iterator12 = _createForOfIteratorHelper(canonicalMatched),
1032
- _step12;
1281
+ var _iterator15 = _createForOfIteratorHelper(canonicalMatched),
1282
+ _step15;
1033
1283
  try {
1034
- for (_iterator12.s(); !(_step12 = _iterator12.n()).done;) {
1284
+ for (_iterator15.s(); !(_step15 = _iterator15.n()).done;) {
1035
1285
  var _firstTokenGroups$get;
1036
- var _candidate = _step12.value;
1286
+ var _candidate = _step15.value;
1037
1287
  if (_candidate.canonicalTokenIds === null) {
1038
1288
  continue;
1039
1289
  }
@@ -1061,28 +1311,28 @@ export var predict = function predict(textBefore) {
1061
1311
  firstTokenGroups.set(_candidate.contextKey, _group2);
1062
1312
  }
1063
1313
  } catch (err) {
1064
- _iterator12.e(err);
1314
+ _iterator15.e(err);
1065
1315
  } finally {
1066
- _iterator12.f();
1316
+ _iterator15.f();
1067
1317
  }
1068
- var _iterator13 = _createForOfIteratorHelper(firstTokenGroups),
1069
- _step13;
1318
+ var _iterator16 = _createForOfIteratorHelper(firstTokenGroups),
1319
+ _step16;
1070
1320
  try {
1071
- for (_iterator13.s(); !(_step13 = _iterator13.n()).done;) {
1072
- var _step13$value = _slicedToArray(_step13.value, 2),
1073
- _contextKey = _step13$value[0],
1074
- _group3 = _step13$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];
1075
1325
  var boundary = getBoundaryLmState(_contextKey);
1076
1326
  if (!boundary) {
1077
1327
  continue;
1078
1328
  }
1079
1329
  var maxLogit = -Infinity;
1080
- var _iterator20 = _createForOfIteratorHelper(_group3),
1081
- _step20;
1330
+ var _iterator23 = _createForOfIteratorHelper(_group3),
1331
+ _step23;
1082
1332
  try {
1083
- for (_iterator20.s(); !(_step20 = _iterator20.n()).done;) {
1333
+ for (_iterator23.s(); !(_step23 = _iterator23.n()).done;) {
1084
1334
  var _candidate2$canonical;
1085
- var _candidate2 = _step20.value;
1335
+ var _candidate2 = _step23.value;
1086
1336
  var tokenId = (_candidate2$canonical = _candidate2.canonicalTokenIds) === null || _candidate2$canonical === void 0 ? void 0 : _candidate2$canonical[0];
1087
1337
  var rawLogit = tokenId === undefined ? undefined : boundary.rawLogits[tokenId];
1088
1338
  if (rawLogit !== undefined && Number.isFinite(rawLogit) && rawLogit > maxLogit) {
@@ -1090,19 +1340,19 @@ export var predict = function predict(textBefore) {
1090
1340
  }
1091
1341
  }
1092
1342
  } catch (err) {
1093
- _iterator20.e(err);
1343
+ _iterator23.e(err);
1094
1344
  } finally {
1095
- _iterator20.f();
1345
+ _iterator23.f();
1096
1346
  }
1097
1347
  if (!Number.isFinite(maxLogit)) {
1098
1348
  continue;
1099
1349
  }
1100
- var _iterator21 = _createForOfIteratorHelper(_group3),
1101
- _step21;
1350
+ var _iterator24 = _createForOfIteratorHelper(_group3),
1351
+ _step24;
1102
1352
  try {
1103
- for (_iterator21.s(); !(_step21 = _iterator21.n()).done;) {
1353
+ for (_iterator24.s(); !(_step24 = _iterator24.n()).done;) {
1104
1354
  var _candidate3$canonical, _candidate3$canonical2, _candidate3$canonical3;
1105
- var _candidate3 = _step21.value;
1355
+ var _candidate3 = _step24.value;
1106
1356
  var _tokenId = (_candidate3$canonical = _candidate3.canonicalTokenIds) === null || _candidate3$canonical === void 0 ? void 0 : _candidate3$canonical[0];
1107
1357
  var _rawLogit = _tokenId === undefined ? undefined : boundary.rawLogits[_tokenId];
1108
1358
  if (_rawLogit === undefined || !Number.isFinite(_rawLogit)) {
@@ -1141,15 +1391,15 @@ export var predict = function predict(textBefore) {
1141
1391
  });
1142
1392
  }
1143
1393
  } catch (err) {
1144
- _iterator21.e(err);
1394
+ _iterator24.e(err);
1145
1395
  } finally {
1146
- _iterator21.f();
1396
+ _iterator24.f();
1147
1397
  }
1148
1398
  }
1149
1399
  } catch (err) {
1150
- _iterator13.e(err);
1400
+ _iterator16.e(err);
1151
1401
  } finally {
1152
- _iterator13.f();
1402
+ _iterator16.f();
1153
1403
  }
1154
1404
  var _rankCandidates = rankCandidates(scoringCandidates, contextVector, function (w) {
1155
1405
  return getWordVector(w);
@@ -1170,12 +1420,12 @@ export var predict = function predict(textBefore) {
1170
1420
  });
1171
1421
  if (canonicalScoringSupported && progressiveEligible.length > 0) {
1172
1422
  var byContext = new Map();
1173
- var _iterator14 = _createForOfIteratorHelper(progressiveEligible),
1174
- _step14;
1423
+ var _iterator17 = _createForOfIteratorHelper(progressiveEligible),
1424
+ _step17;
1175
1425
  try {
1176
- for (_iterator14.s(); !(_step14 = _iterator14.n()).done;) {
1426
+ for (_iterator17.s(); !(_step17 = _iterator17.n()).done;) {
1177
1427
  var _byContext$get;
1178
- var candidate = _step14.value;
1428
+ var candidate = _step17.value;
1179
1429
  var runtime = runtimeBySurface.get(candidate.word);
1180
1430
  if (!runtime || runtime.canonicalTokenIds === null) {
1181
1431
  continue;
@@ -1188,25 +1438,25 @@ export var predict = function predict(textBefore) {
1188
1438
  byContext.set(runtime.contextKey, group);
1189
1439
  }
1190
1440
  } catch (err) {
1191
- _iterator14.e(err);
1441
+ _iterator17.e(err);
1192
1442
  } finally {
1193
- _iterator14.f();
1443
+ _iterator17.f();
1194
1444
  }
1195
- var _iterator15 = _createForOfIteratorHelper(byContext),
1196
- _step15;
1445
+ var _iterator18 = _createForOfIteratorHelper(byContext),
1446
+ _step18;
1197
1447
  try {
1198
- for (_iterator15.s(); !(_step15 = _iterator15.n()).done;) {
1199
- var _step15$value = _slicedToArray(_step15.value, 2),
1200
- contextKey = _step15$value[0],
1201
- _group = _step15$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];
1202
1452
  requestProgressiveSurfaceScores({
1203
1453
  familyKey: familyKey,
1204
1454
  contextKey: contextKey,
1205
1455
  prompt: _group[0].runtime.contextBeforeSurface,
1206
- candidates: _group.map(function (_ref5) {
1456
+ candidates: _group.map(function (_ref6) {
1207
1457
  var _runtime$canonicalTok;
1208
- var runtime = _ref5.runtime,
1209
- rankHint = _ref5.rankHint;
1458
+ var runtime = _ref6.runtime,
1459
+ rankHint = _ref6.rankHint;
1210
1460
  return {
1211
1461
  surface: runtime.word,
1212
1462
  tokenIds: (_runtime$canonicalTok = runtime.canonicalTokenIds) !== null && _runtime$canonicalTok !== void 0 ? _runtime$canonicalTok : [],
@@ -1216,9 +1466,9 @@ export var predict = function predict(textBefore) {
1216
1466
  });
1217
1467
  }
1218
1468
  } catch (err) {
1219
- _iterator15.e(err);
1469
+ _iterator18.e(err);
1220
1470
  } finally {
1221
- _iterator15.f();
1471
+ _iterator18.f();
1222
1472
  }
1223
1473
  }
1224
1474
 
@@ -1307,12 +1557,12 @@ export var predict = function predict(textBefore) {
1307
1557
  var contextRequestedCount = new Map();
1308
1558
  var bestTotalByContext = new Map();
1309
1559
  var bestCandidateByContext = new Map();
1310
- var _iterator16 = _createForOfIteratorHelper(ranked),
1311
- _step16;
1560
+ var _iterator19 = _createForOfIteratorHelper(ranked),
1561
+ _step19;
1312
1562
  try {
1313
- for (_iterator16.s(); !(_step16 = _iterator16.n()).done;) {
1563
+ for (_iterator19.s(); !(_step19 = _iterator19.n()).done;) {
1314
1564
  var _contextRequestedCoun2, _judged$total;
1315
- var _candidate4 = _step16.value;
1565
+ var _candidate4 = _step19.value;
1316
1566
  var _runtime = runtimeBySurface.get(_candidate4.word);
1317
1567
  if (!_runtime || _runtime.canonicalTokenIds === null) {
1318
1568
  continue;
@@ -1351,9 +1601,9 @@ export var predict = function predict(textBefore) {
1351
1601
  * costs one lookup per space rather than a comparison against every member.
1352
1602
  */
1353
1603
  } catch (err) {
1354
- _iterator16.e(err);
1604
+ _iterator19.e(err);
1355
1605
  } finally {
1356
- _iterator16.f();
1606
+ _iterator19.f();
1357
1607
  }
1358
1608
  var extendsAPoolMember = function extendsAPoolMember(surface, pool) {
1359
1609
  for (var space = surface.indexOf(' '); space !== -1; space = surface.indexOf(' ', space + 1)) {
@@ -1388,22 +1638,22 @@ export var predict = function predict(textBefore) {
1388
1638
  // it continues, because the cap alone would leave it looking exactly as
1389
1639
  // certain as its prefix while saying nothing about its own tail.
1390
1640
  var underReadExtensions = new Set();
1391
- var _iterator17 = _createForOfIteratorHelper(contextSurfaces),
1392
- _step17;
1641
+ var _iterator20 = _createForOfIteratorHelper(contextSurfaces),
1642
+ _step20;
1393
1643
  try {
1394
- for (_iterator17.s(); !(_step17 = _iterator17.n()).done;) {
1395
- var _step17$value = _slicedToArray(_step17.value, 2),
1396
- _contextKey2 = _step17$value[0],
1397
- _surfaces = _step17$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];
1398
1648
  var pool = new Set(_surfaces);
1399
- var _iterator22 = _createForOfIteratorHelper(_toConsumableArray(_surfaces).sort(function (a, b) {
1649
+ var _iterator25 = _createForOfIteratorHelper(_toConsumableArray(_surfaces).sort(function (a, b) {
1400
1650
  return a.length - b.length;
1401
1651
  })),
1402
- _step22;
1652
+ _step25;
1403
1653
  try {
1404
- for (_iterator22.s(); !(_step22 = _iterator22.n()).done;) {
1654
+ for (_iterator25.s(); !(_step25 = _iterator25.n()).done;) {
1405
1655
  var _getProgressiveSurfac, _getProgressiveSurfac2, _runtimeBySurface$get8, _runtimeBySurface$get9;
1406
- var surface = _step22.value;
1656
+ var surface = _step25.value;
1407
1657
  var own = optimisticTotalByWord.get(surface);
1408
1658
  if (own === undefined) {
1409
1659
  continue;
@@ -1435,9 +1685,9 @@ export var predict = function predict(textBefore) {
1435
1685
  }
1436
1686
  }
1437
1687
  } catch (err) {
1438
- _iterator22.e(err);
1688
+ _iterator25.e(err);
1439
1689
  } finally {
1440
- _iterator22.f();
1690
+ _iterator25.f();
1441
1691
  }
1442
1692
  }
1443
1693
 
@@ -1450,9 +1700,9 @@ export var predict = function predict(textBefore) {
1450
1700
  * depths.
1451
1701
  */
1452
1702
  } catch (err) {
1453
- _iterator17.e(err);
1703
+ _iterator20.e(err);
1454
1704
  } finally {
1455
- _iterator17.f();
1705
+ _iterator20.f();
1456
1706
  }
1457
1707
  var judgedEvidenceFor = function judgedEvidenceFor(candidate) {
1458
1708
  return underReadExtensions.has(candidate.word) ? null : readJudgedEvidence(candidate);
@@ -1479,20 +1729,20 @@ export var predict = function predict(textBefore) {
1479
1729
  // `contextTotals`, so how contested a context is still counts every scored
1480
1730
  // candidate.
1481
1731
  var logSumExpByContext = new Map();
1482
- var _iterator18 = _createForOfIteratorHelper(contextSurfaces),
1483
- _step18;
1732
+ var _iterator21 = _createForOfIteratorHelper(contextSurfaces),
1733
+ _step21;
1484
1734
  try {
1485
- for (_iterator18.s(); !(_step18 = _iterator18.n()).done;) {
1486
- var _step18$value = _slicedToArray(_step18.value, 2),
1487
- _contextKey3 = _step18$value[0],
1488
- _surfaces2 = _step18$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];
1489
1739
  var _pool = new Set(_surfaces2);
1490
1740
  var minimalTotals = [];
1491
- var _iterator23 = _createForOfIteratorHelper(_surfaces2),
1492
- _step23;
1741
+ var _iterator26 = _createForOfIteratorHelper(_surfaces2),
1742
+ _step26;
1493
1743
  try {
1494
- for (_iterator23.s(); !(_step23 = _iterator23.n()).done;) {
1495
- var _surface = _step23.value;
1744
+ for (_iterator26.s(); !(_step26 = _iterator26.n()).done;) {
1745
+ var _surface = _step26.value;
1496
1746
  var total = optimisticTotalByWord.get(_surface);
1497
1747
  if (total !== undefined && !extendsAPoolMember(_surface, _pool)) {
1498
1748
  minimalTotals.push(total);
@@ -1501,9 +1751,9 @@ export var predict = function predict(textBefore) {
1501
1751
  // An extension is strictly longer than what it extends, so the shortest
1502
1752
  // member of any non-empty pool is always minimal and this is never empty.
1503
1753
  } catch (err) {
1504
- _iterator23.e(err);
1754
+ _iterator26.e(err);
1505
1755
  } finally {
1506
- _iterator23.f();
1756
+ _iterator26.f();
1507
1757
  }
1508
1758
  logSumExpByContext.set(_contextKey3, logSumExp(minimalTotals));
1509
1759
  }
@@ -1515,9 +1765,9 @@ export var predict = function predict(textBefore) {
1515
1765
  * eventual posterior, or a verified total for the posterior itself.
1516
1766
  */
1517
1767
  } catch (err) {
1518
- _iterator18.e(err);
1768
+ _iterator21.e(err);
1519
1769
  } finally {
1520
- _iterator18.f();
1770
+ _iterator21.f();
1521
1771
  }
1522
1772
  var posteriorFor = function posteriorFor(candidate, total) {
1523
1773
  var runtime = runtimeBySurface.get(candidate.word);
@@ -1647,9 +1897,9 @@ export var predict = function predict(textBefore) {
1647
1897
  // The gate is the model's own confidence in the surface; the blended score
1648
1898
  // only orders what has already cleared it, so a strong corpus prior can no
1649
1899
  // longer carry a surface the model is unsure of onto the screen.
1650
- .filter(function (_ref6) {
1651
- var candidate = _ref6.candidate,
1652
- posterior = _ref6.posterior;
1900
+ .filter(function (_ref7) {
1901
+ var candidate = _ref7.candidate,
1902
+ posterior = _ref7.posterior;
1653
1903
  return posterior >= MIN_LM_POSTERIOR[candidate.termType];
1654
1904
  });
1655
1905
  /**
@@ -1677,15 +1927,15 @@ export var predict = function predict(textBefore) {
1677
1927
  // cleared both. A candidate refused here stays in `ranked` and so still
1678
1928
  // counts as competition below — promoting the runner-up in place of an
1679
1929
  // implausible leader would show something worse, not something better.
1680
- var plausible = gateCleared.filter(function (_ref7) {
1681
- var candidate = _ref7.candidate;
1930
+ var plausible = gateCleared.filter(function (_ref8) {
1931
+ var candidate = _ref8.candidate;
1682
1932
  return isPlausibleSurface(candidate);
1683
1933
  });
1684
1934
  // Applied after the gate rather than folded into it, so the two populations
1685
1935
  // stay separable: a surface refused here cleared its threshold and was
1686
1936
  // refused for having had nothing to clear it against.
1687
- var eligible = plausible.filter(function (_ref8) {
1688
- var candidate = _ref8.candidate;
1937
+ var eligible = plausible.filter(function (_ref9) {
1938
+ var candidate = _ref9.candidate;
1689
1939
  return !canonicalLmSupported || scoredPoolSize(candidate) >= MIN_SCORED_POOL_SIZE || requestedPoolSize(candidate) <= 1;
1690
1940
  }).sort(function (a, b) {
1691
1941
  return b.score - a.score;
@@ -1807,6 +2057,12 @@ export var predict = function predict(textBefore) {
1807
2057
  return ranked.some(hasJudgeableEvidence) ? 'below-posterior-gate' : 'no-evidence';
1808
2058
  };
1809
2059
  var abstainReason = resolveAbstainReason();
2060
+ recordPredictionOutcome({
2061
+ abstainReason: abstainReason,
2062
+ awaitingAsyncEvidence: hasUnresolvedPotential && !clearsWinnerMargin,
2063
+ scoredCandidateCount: ranked.length,
2064
+ textBefore: textBefore
2065
+ });
1810
2066
 
1811
2067
  // The leader is the best-supported candidate, not the selected one: an
1812
2068
  // evaluation that showed nothing is exactly the one whose posterior needs
@@ -1851,9 +2107,9 @@ export var predict = function predict(textBefore) {
1851
2107
  'below-posterior-gate': "posterior below ".concat(MIN_LM_POSTERIOR[((_posteriorLeader$cand = posteriorLeader === null || posteriorLeader === void 0 ? void 0 : posteriorLeader.candidate) !== null && _posteriorLeader$cand !== void 0 ? _posteriorLeader$cand : ranked[0]).termType], " (best ").concat(((_posteriorLeader$post = posteriorLeader === null || posteriorLeader === void 0 ? void 0 : posteriorLeader.posterior) !== null && _posteriorLeader$post !== void 0 ? _posteriorLeader$post : 0).toFixed(2), ")"),
1852
2108
  'cold-competitor': 'competitor has no LM evidence yet',
1853
2109
  'empty-completion': 'empty completion',
1854
- 'implausible-surface': "mean per-token log-probability below ".concat(MIN_MEAN_TOKEN_LOG_PROBABILITY, " (best ").concat(gateCleared.map(function (_ref9) {
2110
+ 'implausible-surface': "mean per-token log-probability below ".concat(MIN_MEAN_TOKEN_LOG_PROBABILITY, " (best ").concat(gateCleared.map(function (_ref0) {
1855
2111
  var _judgedEvidenceFor$me, _judgedEvidenceFor4;
1856
- var candidate = _ref9.candidate;
2112
+ var candidate = _ref0.candidate;
1857
2113
  return (_judgedEvidenceFor$me = (_judgedEvidenceFor4 = judgedEvidenceFor(candidate)) === null || _judgedEvidenceFor4 === void 0 ? void 0 : _judgedEvidenceFor4.mean) !== null && _judgedEvidenceFor$me !== void 0 ? _judgedEvidenceFor$me : -Infinity;
1858
2114
  }).reduce(function (best, mean) {
1859
2115
  return Math.max(best, mean);
@@ -1862,6 +2118,9 @@ export var predict = function predict(textBefore) {
1862
2118
  'missing-artifact': 'canonical artifact coverage missing',
1863
2119
  'no-candidate': 'nothing cleared scoring',
1864
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',
1865
2124
  prefetch: "prefetch: ".concat(currentWord.length, "/").concat(DISPLAY_MIN_PREFIX_LENGTH, " chars"),
1866
2125
  'short-completion': "completion shorter than ".concat(MIN_SUGGESTION_LENGTH, " chars"),
1867
2126
  'unresolved-rival': 'expanding plausible token-prefix groups',
@@ -1941,10 +2200,10 @@ export var predict = function predict(textBefore) {
1941
2200
  if (verbose && logitCount > 0 && lmLogits) {
1942
2201
  var rawLmTop = Object.entries(lmLogits).sort(function (a, b) {
1943
2202
  return b[1] - a[1];
1944
- }).slice(0, 5).map(function (_ref0) {
1945
- var _ref1 = _slicedToArray(_ref0, 2),
1946
- word = _ref1[0],
1947
- score = _ref1[1];
2203
+ }).slice(0, 5).map(function (_ref1) {
2204
+ var _ref10 = _slicedToArray(_ref1, 2),
2205
+ word = _ref10[0],
2206
+ score = _ref10[1];
1948
2207
  return "".concat(word, ":").concat(score.toFixed(3));
1949
2208
  }).join(', ');
1950
2209
  ctcSection(' rawLM', "\uD83E\uDDE0 ".concat(rawLmTop));
@@ -1956,17 +2215,17 @@ export var predict = function predict(textBefore) {
1956
2215
  bigram: 0,
1957
2216
  phrase: 0
1958
2217
  };
1959
- var _iterator19 = _createForOfIteratorHelper(canonicalMatched),
1960
- _step19;
2218
+ var _iterator22 = _createForOfIteratorHelper(canonicalMatched),
2219
+ _step22;
1961
2220
  try {
1962
- for (_iterator19.s(); !(_step19 = _iterator19.n()).done;) {
1963
- var m = _step19.value;
2221
+ for (_iterator22.s(); !(_step22 = _iterator22.n()).done;) {
2222
+ var m = _step22.value;
1964
2223
  genByType[m.node.termType] += 1;
1965
2224
  }
1966
2225
  } catch (err) {
1967
- _iterator19.e(err);
2226
+ _iterator22.e(err);
1968
2227
  } finally {
1969
- _iterator19.f();
2228
+ _iterator22.f();
1970
2229
  }
1971
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' : ''));
1972
2231
 
@@ -2025,8 +2284,8 @@ export var predict = function predict(textBefore) {
2025
2284
  return judgedPosterior(candidate) >= MIN_LM_POSTERIOR[termType];
2026
2285
  });
2027
2286
  var plausiblePassed = floorPassed.filter(isPlausibleSurface);
2028
- return "".concat(termType, " m:").concat(genByType[termType], " r:").concat(typeRanked.length, " exact:").concat(exact.length, " abs:").concat(absolute.length, " stable:").concat(stabilized.length, " suffix:").concat(longEnough.length, " floor:").concat(floorPassed.length, " plausible:").concat(plausiblePassed.length, " eligible:").concat(eligible.filter(function (_ref10) {
2029
- var candidate = _ref10.candidate;
2287
+ return "".concat(termType, " m:").concat(genByType[termType], " r:").concat(typeRanked.length, " exact:").concat(exact.length, " abs:").concat(absolute.length, " stable:").concat(stabilized.length, " suffix:").concat(longEnough.length, " floor:").concat(floorPassed.length, " plausible:").concat(plausiblePassed.length, " eligible:").concat(eligible.filter(function (_ref11) {
2288
+ var candidate = _ref11.candidate;
2030
2289
  return candidate.termType === termType;
2031
2290
  }).length);
2032
2291
  };
@@ -2035,11 +2294,11 @@ export var predict = function predict(textBefore) {
2035
2294
  // One line per context: how many candidates share the normaliser, and how
2036
2295
  // much of the mass the leader holds. A leader well under its threshold
2037
2296
  // means the context is contested, which is the abstention we want.
2038
- var contextLeaders = Array.from(bestCandidateByContext.entries()).slice(0, PHRASE_MAX_WORDS).map(function (_ref11) {
2297
+ var contextLeaders = Array.from(bestCandidateByContext.entries()).slice(0, PHRASE_MAX_WORDS).map(function (_ref12) {
2039
2298
  var _contextTotals$get$le2, _contextTotals$get2;
2040
- var _ref12 = _slicedToArray(_ref11, 2),
2041
- contextKey = _ref12[0],
2042
- leader = _ref12[1];
2299
+ var _ref13 = _slicedToArray(_ref12, 2),
2300
+ contextKey = _ref13[0],
2301
+ leader = _ref13[1];
2043
2302
  var shortlistSize = (_contextTotals$get$le2 = (_contextTotals$get2 = contextTotals.get(contextKey)) === null || _contextTotals$get2 === void 0 ? void 0 : _contextTotals$get2.length) !== null && _contextTotals$get$le2 !== void 0 ? _contextTotals$get$le2 : 0;
2044
2303
  var evidenceKind = hasExactEvidence(leader) ? 'exact' : 'upper';
2045
2304
  return "".concat(contextKey.slice(0, 32), " \u2192 n=").concat(shortlistSize, " ").concat(leader.termType, ":\"").concat(leader.word, "\" ").concat(evidenceKind, " p=").concat(optimisticPosterior(leader).toFixed(3), "/").concat(MIN_LM_POSTERIOR[leader.termType]);
@@ -2269,7 +2528,7 @@ var normalizePhraseArtifact = function normalizePhraseArtifact(payload) {
2269
2528
  return null;
2270
2529
  };
2271
2530
  export var loadVectorsAsync = /*#__PURE__*/function () {
2272
- var _ref13 = _asyncToGenerator( /*#__PURE__*/_regeneratorRuntime.mark(function _callee(options) {
2531
+ var _ref14 = _asyncToGenerator( /*#__PURE__*/_regeneratorRuntime.mark(function _callee(options) {
2273
2532
  var _options$isLocalLLM;
2274
2533
  var isLocalLLM, surface, buffer, float32, wordIndexPayload, wordIndex, nWords, dim, _t;
2275
2534
  return _regeneratorRuntime.wrap(function (_context) {
@@ -2344,7 +2603,7 @@ export var loadVectorsAsync = /*#__PURE__*/function () {
2344
2603
  }, _callee, null, [[2, 5]]);
2345
2604
  }));
2346
2605
  return function loadVectorsAsync(_x) {
2347
- return _ref13.apply(this, arguments);
2606
+ return _ref14.apply(this, arguments);
2348
2607
  };
2349
2608
  }();
2350
2609
  export var initVectors = function initVectors(store) {
@@ -2368,7 +2627,7 @@ export var initVectors = function initVectors(store) {
2368
2627
  * A promise that resolves once both fetches have settled
2369
2628
  */
2370
2629
  export var loadPhraseArtifacts = /*#__PURE__*/function () {
2371
- var _ref14 = _asyncToGenerator( /*#__PURE__*/_regeneratorRuntime.mark(function _callee3(options) {
2630
+ var _ref15 = _asyncToGenerator( /*#__PURE__*/_regeneratorRuntime.mark(function _callee3(options) {
2372
2631
  var _options$isLocalLLM2;
2373
2632
  var isLocalLLM, loadOne, _yield$Promise$allSet, _yield$Promise$allSet2, bigramsResult, phrasesResult, bigramCount, phraseCount;
2374
2633
  return _regeneratorRuntime.wrap(function (_context3) {
@@ -2386,7 +2645,7 @@ export var loadPhraseArtifacts = /*#__PURE__*/function () {
2386
2645
  isLocalLLM: isLocalLLM
2387
2646
  });
2388
2647
  loadOne = /*#__PURE__*/function () {
2389
- var _ref15 = _asyncToGenerator( /*#__PURE__*/_regeneratorRuntime.mark(function _callee2(artifactName, termType, label) {
2648
+ var _ref16 = _asyncToGenerator( /*#__PURE__*/_regeneratorRuntime.mark(function _callee2(artifactName, termType, label) {
2390
2649
  var payload, normalized;
2391
2650
  return _regeneratorRuntime.wrap(function (_context2) {
2392
2651
  while (1) switch (_context2.prev = _context2.next) {
@@ -2415,7 +2674,7 @@ export var loadPhraseArtifacts = /*#__PURE__*/function () {
2415
2674
  }, _callee2);
2416
2675
  }));
2417
2676
  return function loadOne(_x3, _x4, _x5) {
2418
- return _ref15.apply(this, arguments);
2677
+ return _ref16.apply(this, arguments);
2419
2678
  };
2420
2679
  }();
2421
2680
  _context3.next = 2;
@@ -2457,7 +2716,7 @@ export var loadPhraseArtifacts = /*#__PURE__*/function () {
2457
2716
  }, _callee3);
2458
2717
  }));
2459
2718
  return function loadPhraseArtifacts(_x2) {
2460
- return _ref14.apply(this, arguments);
2719
+ return _ref15.apply(this, arguments);
2461
2720
  };
2462
2721
  }();
2463
2722
  var vocabularyLoadPromise;
@@ -2523,10 +2782,10 @@ export var loadDefaultVocabulary = function loadDefaultVocabulary(options) {
2523
2782
  _yield$Promise$all2 = _slicedToArray(_yield$Promise$all, 2);
2524
2783
  vocabularyData = _yield$Promise$all2[0];
2525
2784
  l3VocabularyData = _yield$Promise$all2[1];
2526
- terms = Object.entries(vocabularyData.words).map(function (_ref17) {
2527
- var _ref18 = _slicedToArray(_ref17, 2),
2528
- word = _ref18[0],
2529
- stats = _ref18[1];
2785
+ terms = Object.entries(vocabularyData.words).map(function (_ref18) {
2786
+ var _ref19 = _slicedToArray(_ref18, 2),
2787
+ word = _ref19[0],
2788
+ stats = _ref19[1];
2530
2789
  return {
2531
2790
  word: word,
2532
2791
  freq: stats.freq,