@atlaskit/editor-plugin-autocomplete 8.0.0 → 9.0.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.
Files changed (79) hide show
  1. package/CHANGELOG.md +80 -0
  2. package/dist/cjs/analytics/ufo.js +2 -1
  3. package/dist/cjs/pm-plugins/artifact-loader.js +135 -0
  4. package/dist/cjs/pm-plugins/artifacts-manifest.js +3 -0
  5. package/dist/cjs/pm-plugins/autocomplete-plugin.js +416 -129
  6. package/dist/cjs/pm-plugins/canonical-lm-scoring.js +371 -0
  7. package/dist/cjs/pm-plugins/debug-mode.js +90 -7
  8. package/dist/cjs/pm-plugins/ghost-text-decoration.js +12 -3
  9. package/dist/cjs/pm-plugins/local-slow-lane-client.js +1202 -453
  10. package/dist/cjs/pm-plugins/scoring-pipeline.js +164 -19
  11. package/dist/cjs/pm-plugins/slow-lane-client.js +100 -37
  12. package/dist/cjs/pm-plugins/text-predictor.js +1951 -273
  13. package/dist/es2019/analytics/ufo.js +2 -0
  14. package/dist/es2019/pm-plugins/artifact-loader.js +70 -0
  15. package/dist/es2019/pm-plugins/artifacts-manifest.js +3 -0
  16. package/dist/es2019/pm-plugins/autocomplete-plugin.js +425 -130
  17. package/dist/es2019/pm-plugins/canonical-lm-scoring.js +270 -0
  18. package/dist/es2019/pm-plugins/debug-mode.js +87 -7
  19. package/dist/es2019/pm-plugins/ghost-text-decoration.js +12 -3
  20. package/dist/es2019/pm-plugins/local-slow-lane-client.js +940 -326
  21. package/dist/es2019/pm-plugins/scoring-pipeline.js +137 -13
  22. package/dist/es2019/pm-plugins/slow-lane-client.js +94 -37
  23. package/dist/es2019/pm-plugins/text-predictor.js +1603 -199
  24. package/dist/esm/analytics/ufo.js +2 -1
  25. package/dist/esm/pm-plugins/artifact-loader.js +128 -0
  26. package/dist/esm/pm-plugins/artifacts-manifest.js +3 -0
  27. package/dist/esm/pm-plugins/autocomplete-plugin.js +419 -132
  28. package/dist/esm/pm-plugins/canonical-lm-scoring.js +364 -0
  29. package/dist/esm/pm-plugins/debug-mode.js +89 -6
  30. package/dist/esm/pm-plugins/ghost-text-decoration.js +12 -3
  31. package/dist/esm/pm-plugins/local-slow-lane-client.js +1200 -451
  32. package/dist/esm/pm-plugins/scoring-pipeline.js +163 -18
  33. package/dist/esm/pm-plugins/slow-lane-client.js +100 -37
  34. package/dist/esm/pm-plugins/text-predictor.js +1953 -277
  35. package/dist/types/analytics/ufo.d.ts +1 -0
  36. package/dist/types/pm-plugins/artifact-loader.d.ts +44 -0
  37. package/dist/types/pm-plugins/artifacts-manifest.d.ts +3 -0
  38. package/dist/types/pm-plugins/autocomplete-plugin.d.ts +39 -0
  39. package/dist/types/pm-plugins/canonical-lm-scoring.d.ts +158 -0
  40. package/dist/types/pm-plugins/debug-mode.d.ts +36 -1
  41. package/dist/types/pm-plugins/ghost-text-decoration.d.ts +5 -2
  42. package/dist/types/pm-plugins/local-slow-lane-client.d.ts +87 -54
  43. package/dist/types/pm-plugins/scoring-pipeline.d.ts +33 -1
  44. package/dist/types/pm-plugins/slow-lane-client.d.ts +51 -3
  45. package/dist/types/pm-plugins/text-predictor.d.ts +120 -2
  46. package/package.json +2 -2
  47. package/src/analytics/ufo.ts +5 -0
  48. package/src/pm-plugins/artifact-loader.ts +92 -0
  49. package/src/pm-plugins/artifacts-manifest.ts +3 -0
  50. package/src/pm-plugins/autocomplete-plugin.ts +517 -133
  51. package/src/pm-plugins/canonical-lm-scoring.ts +412 -0
  52. package/src/pm-plugins/debug-mode.ts +109 -7
  53. package/src/pm-plugins/ghost-text-decoration.ts +13 -4
  54. package/src/pm-plugins/local-slow-lane-client.ts +1302 -386
  55. package/src/pm-plugins/scoring-pipeline.ts +167 -17
  56. package/src/pm-plugins/slow-lane-client.ts +147 -62
  57. package/src/pm-plugins/text-predictor.ts +2006 -255
  58. package/CANONICAL_FIX__DO_NOT_USE_ME_A/package.json +0 -8
  59. package/CANONICAL_FIX__DO_NOT_USE_ME_B/package.json +0 -8
  60. package/CANONICAL_FIX__DO_NOT_USE_ME_C/package.json +0 -8
  61. package/dist/cjs/entry-points/src-pm-plugins-autocomplete-plugin.js +0 -18
  62. package/dist/cjs/entry-points/src-pm-plugins-slow-lane-client.js +0 -36
  63. package/dist/cjs/entry-points/src-pm-plugins-text-predictor.js +0 -66
  64. package/dist/es2019/entry-points/src-pm-plugins-autocomplete-plugin.js +0 -2
  65. package/dist/es2019/entry-points/src-pm-plugins-slow-lane-client.js +0 -2
  66. package/dist/es2019/entry-points/src-pm-plugins-text-predictor.js +0 -2
  67. package/dist/esm/entry-points/src-pm-plugins-autocomplete-plugin.js +0 -2
  68. package/dist/esm/entry-points/src-pm-plugins-slow-lane-client.js +0 -2
  69. package/dist/esm/entry-points/src-pm-plugins-text-predictor.js +0 -2
  70. package/dist/types/entry-points/src-pm-plugins-autocomplete-plugin.d.ts +0 -2
  71. package/dist/types/entry-points/src-pm-plugins-slow-lane-client.d.ts +0 -2
  72. package/dist/types/entry-points/src-pm-plugins-text-predictor.d.ts +0 -2
  73. package/scripts/gen_first_token_to_words.py +0 -170
  74. package/src/entry-points/src-pm-plugins-autocomplete-plugin.ts +0 -7
  75. package/src/entry-points/src-pm-plugins-slow-lane-client.ts +0 -13
  76. package/src/entry-points/src-pm-plugins-text-predictor.ts +0 -14
  77. package/src/pm-plugins/autocomplete-plugin/package.json +0 -8
  78. package/src/pm-plugins/slow-lane-client/package.json +0 -8
  79. package/src/pm-plugins/text-predictor/package.json +0 -8
@@ -1,14 +1,13 @@
1
1
  import _toConsumableArray from "@babel/runtime/helpers/toConsumableArray";
2
+ import _defineProperty from "@babel/runtime/helpers/defineProperty";
2
3
  import _slicedToArray from "@babel/runtime/helpers/slicedToArray";
3
4
  import _asyncToGenerator from "@babel/runtime/helpers/asyncToGenerator";
4
- import _createClass from "@babel/runtime/helpers/createClass";
5
- import _classCallCheck from "@babel/runtime/helpers/classCallCheck";
6
- import _defineProperty from "@babel/runtime/helpers/defineProperty";
7
- function ownKeys(e, r) { var t = Object.keys(e); if (Object.getOwnPropertySymbols) { var o = Object.getOwnPropertySymbols(e); r && (o = o.filter(function (r) { return Object.getOwnPropertyDescriptor(e, r).enumerable; })), t.push.apply(t, o); } return t; }
8
- function _objectSpread(e) { for (var r = 1; r < arguments.length; r++) { var t = null != arguments[r] ? arguments[r] : {}; r % 2 ? ownKeys(Object(t), !0).forEach(function (r) { _defineProperty(e, r, t[r]); }) : Object.getOwnPropertyDescriptors ? Object.defineProperties(e, Object.getOwnPropertyDescriptors(t)) : ownKeys(Object(t)).forEach(function (r) { Object.defineProperty(e, r, Object.getOwnPropertyDescriptor(t, r)); }); } return e; }
5
+ import _typeof from "@babel/runtime/helpers/typeof";
9
6
  function _createForOfIteratorHelper(r, e) { var t = "undefined" != typeof Symbol && r[Symbol.iterator] || r["@@iterator"]; if (!t) { if (Array.isArray(r) || (t = _unsupportedIterableToArray(r)) || e && r && "number" == typeof r.length) { t && (r = t); var _n = 0, F = function F() {}; return { s: F, n: function n() { return _n >= r.length ? { done: !0 } : { done: !1, value: r[_n++] }; }, e: function e(r) { throw r; }, f: F }; } throw new TypeError("Invalid attempt to iterate non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method."); } var o, a = !0, u = !1; return { s: function s() { t = t.call(r); }, n: function n() { var r = t.next(); return a = r.done, r; }, e: function e(r) { u = !0, o = r; }, f: function f() { try { a || null == t.return || t.return(); } finally { if (u) throw o; } } }; }
10
7
  function _unsupportedIterableToArray(r, a) { if (r) { if ("string" == typeof r) return _arrayLikeToArray(r, a); var t = {}.toString.call(r).slice(8, -1); return "Object" === t && r.constructor && (t = r.constructor.name), "Map" === t || "Set" === t ? Array.from(r) : "Arguments" === t || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(t) ? _arrayLikeToArray(r, a) : void 0; } }
11
8
  function _arrayLikeToArray(r, a) { (null == a || a > r.length) && (a = r.length); for (var e = 0, n = Array(a); e < a; e++) n[e] = r[e]; return n; }
9
+ function ownKeys(e, r) { var t = Object.keys(e); if (Object.getOwnPropertySymbols) { var o = Object.getOwnPropertySymbols(e); r && (o = o.filter(function (r) { return Object.getOwnPropertyDescriptor(e, r).enumerable; })), t.push.apply(t, o); } return t; }
10
+ function _objectSpread(e) { for (var r = 1; r < arguments.length; r++) { var t = null != arguments[r] ? arguments[r] : {}; r % 2 ? ownKeys(Object(t), !0).forEach(function (r) { _defineProperty(e, r, t[r]); }) : Object.getOwnPropertyDescriptors ? Object.defineProperties(e, Object.getOwnPropertyDescriptors(t)) : ownKeys(Object(t)).forEach(function (r) { Object.defineProperty(e, r, Object.getOwnPropertyDescriptor(t, r)); }); } return e; }
12
11
  import _regeneratorRuntime from "@babel/runtime/regenerator";
13
12
  /**
14
13
  * Local Slow Lane Client: On-device inference via @mlc-ai/web-llm.
@@ -17,11 +16,9 @@ import _regeneratorRuntime from "@babel/runtime/regenerator";
17
16
  * a backend API, this client runs two models in the browser via WebGPU, in a
18
17
  * single MLCEngine, to reproduce the BE encoder's outputs on-device:
19
18
  *
20
- * - Causal LM (SmolLM2-135M-Instruct): one decode step per word boundary. A
21
- * registered LogitProcessor captures the raw next-token logits, which
22
- * `computeBePayload` turns into a whole-word `lm_logits` payload — a faithful
23
- * port of the BE `CausalLMEncoder._get_top_k_probs` (masked softmax over the
24
- * vocab's first-tokens, prefix expansion, L2 reservation, log-space pooling).
19
+ * - Causal LM (SmolLM2-135M-Instruct): context-keyed one-step boundary primes
20
+ * provide canonical first-token logits. A persistent token-prefix scheduler
21
+ * expands shared paths and exact-scores only plausible finalists.
25
22
  * - Semantic embedder (Snowflake Arctic Embed S): produces the real 384-d
26
23
  * `semantic_vector`. Inputs are wrapped as passages (see `wrapForArctic`) so
27
24
  * the runtime vector lands in the same space as the precomputed word bin.
@@ -31,7 +28,7 @@ import _regeneratorRuntime from "@babel/runtime/regenerator";
31
28
  * the main thread is viable:
32
29
  *
33
30
  * - WebGPU GPU compute is inherently async (doesn't block the main thread)
34
- * - CPU overhead (BE-parity post-processing) is a few ms
31
+ * - CPU overhead for named-token reads and cache bookkeeping is small
35
32
  * - Per-inference latency is well within autocomplete expectations
36
33
  * (~250 ms between word boundaries)
37
34
  *
@@ -42,16 +39,21 @@ import _regeneratorRuntime from "@babel/runtime/regenerator";
42
39
  * - Standard npm import — just works
43
40
  *
44
41
  * ── Interface ────────────────────────────────────────────────────────────
45
- * Same shape as createSlowLaneClient so text-predictor.ts needs zero changes.
46
- * The client exposes getContextVector() and getLmLogits() which are populated
47
- * asynchronously after each updateContext() call.
42
+ * Same base shape as createSlowLaneClient, plus on-device canonical-surface
43
+ * scoring. Semantic updates remain word-boundary timed; causal work is requested
44
+ * independently for exact pre-surface contexts.
48
45
  */
49
46
 
50
47
  import { abortExp, EXPERIENCE_NAME, failExp, startExp, succeedExp } from '../analytics/ufo';
51
- import { ARTIFACT_NAME, fetchArtifactJson } from './artifacts-manifest';
52
- import { isAutocompleteDebugEnabled } from './debug-mode';
48
+ import { fetchAutocompleteArtifactJson } from './artifact-loader';
49
+ import { ARTIFACT_NAME } from './artifacts-manifest';
50
+ import { CanonicalLogitProcessor, CanonicalSurfaceTokenTrie, logSoftmaxAt, logSumExp } from './canonical-lm-scoring';
51
+ import { CTC_STYLES, isAutocompleteDebugEnabled, isAutocompleteDebugVerbose } from './debug-mode';
52
+ import { MIN_WINNER_MARGIN, STAGE1_WEIGHT, STAGE2_WEIGHT } from './scoring-pipeline';
53
53
  import { isWordBoundary } from './slow-lane-client';
54
54
 
55
+ /** Which of the two causal-LM call shapes a measurement or log line describes. */
56
+
55
57
  // ─── Types ───────────────────────────────────────────────────────────────────
56
58
 
57
59
  // Same return type as createSlowLaneClient for drop-in compatibility
@@ -103,22 +105,7 @@ export var LOCAL_MLC_EMBEDDING_MODEL_ID = 'snowflake-arctic-embed-s-q0f32-MLC-b4
103
105
  export var wrapForArctic = function wrapForArctic(text) {
104
106
  return "[CLS] ".concat(text, " [SEP]");
105
107
  };
106
-
107
- /**
108
- * BE-parity constants — must match `CausalLMEncoder` defaults in the Python
109
- * sidecar (`cc-smarts/python-sidecar/src/causal_lm_encoder.py`) and
110
- * `SlowLaneEngine` (`typeahead_context_encoding.py`) so local payloads behave
111
- * identically to the server-client setup.
112
- */
113
- export var BE_PARITY = {
114
- /** Final payload size cap (BE: `top_k_words`). */
115
- TOP_K_WORDS: 2000,
116
- /** L2 (domain) words admitted unconditionally before pooling (BE: `reserved_l2_slots`). */
117
- RESERVED_L2_SLOTS: 500,
118
- /** Log-space additive bias favouring L2 over L3 in the pool (BE: `l2_bias`). */
119
- L2_BIAS: 1.0,
120
- /** Drop words below this probability from the final payload (BE: `> 0.00001`). */
121
- MIN_PROB: 0.00001,
108
+ export var LOCAL_INFERENCE = {
122
109
  /**
123
110
  * Word-level approximation of the BE causal LM token limit.
124
111
  *
@@ -139,6 +126,72 @@ export var BE_PARITY = {
139
126
  */
140
127
  MAX_CONTEXT_WORDS: 100
141
128
  };
129
+ export var CANONICAL_SCORING = {
130
+ /**
131
+ * How many contexts keep their prefilled state before the oldest is dropped.
132
+ *
133
+ * Sizes every context-keyed cache together on purpose: an expansion needs
134
+ * both the boundary logits and the candidate list to still be resident, so
135
+ * bounding them separately would evict half of a context and strand the
136
+ * other half.
137
+ *
138
+ * The context of a keystroke is the text *before* the word being typed,
139
+ * which does not change while that word is typed, so one word should cost
140
+ * one prefill and then hit. Measured hit rate was 45% over the first 84s of
141
+ * a session and 24% over the following four minutes, well short of that, and
142
+ * prompt prefills per decision rose 0.80 → 1.25 across the same split while
143
+ * cost per prefill stayed flat. Contexts were being dropped while still live.
144
+ *
145
+ * A boundary entry holds a `Float32Array` over the 49,152-token vocabulary,
146
+ * so each one is ~192KB and this bound is the dominant term in the scorer's
147
+ * footprint: ~24MB resident here, against ~6MB at the 32 this replaced. That
148
+ * cost is what kept the bound low, not a hit rate anyone had measured.
149
+ */
150
+ BOUNDARY_CACHE_MAX: 128,
151
+ EXACT_MAX_TARGET_TOKENS: 8,
152
+ PREFIX_CACHE_MAX: 256,
153
+ /**
154
+ * How many candidates compete for expansion in one context.
155
+ *
156
+ * Each distinct token prefix among them is a separate branch, and a branch
157
+ * the engine is not already standing on costs a prompt prefill before its
158
+ * first decode. A wide field therefore spreads a decision's round trips
159
+ * across candidates and finishes none of them inside the budget.
160
+ */
161
+ PROGRESSIVE_CANDIDATES_MAX: 8,
162
+ /**
163
+ * Ceiling on distinct token prefixes explored per context.
164
+ *
165
+ * Raising this to 8 to give long surfaces more room did the opposite: model
166
+ * calls per decision went 2.35 → 4.32, the warm-KV extend share fell 40% →
167
+ * 31% as the extra branches displaced the live path, the prefix queue backed
168
+ * up to 29 deep, and acceptances per thousand decisions fell 9.3 → 6.7. No
169
+ * phrase was shown either way, so breadth was never the binding constraint.
170
+ *
171
+ * What blocked them then was normalisation against a short word's per-token
172
+ * mean, which has since been replaced by a posterior over sequence
173
+ * log-likelihoods. That removes the bias towards short surfaces but does not
174
+ * by itself make phrases reachable: a longer surface is strictly less likely
175
+ * than a shorter one, so a phrase sharing a shortlist with a unigram still
176
+ * holds little of its mass. Whether the remaining gap is the threshold or the
177
+ * comparison is still open.
178
+ */
179
+ PROGRESSIVE_EXPANSIONS_PER_CONTEXT_MAX: 4,
180
+ PROGRESSIVE_INPUT_MAX: 400,
181
+ SURFACE_CACHE_MAX: 128,
182
+ TIER_A_PRIMES_MAX: 3
183
+ };
184
+ /**
185
+ * How much optimistic score we give up to stay on the warm KV path.
186
+ *
187
+ * Expanding a prefix the cache already holds costs one decode step, while
188
+ * branching to any other prefix costs a full prompt prefill first. Measured,
189
+ * that is about 30ms against about 66ms, so continuing the live path is worth
190
+ * roughly half a round trip and the margin has to be wide enough to reflect
191
+ * that. At the previous 0.05 almost any ranking difference was enough to
192
+ * abandon the sequence, and 70% of expansions ended up re-prefilling.
193
+ */
194
+ var PROGRESSIVE_WARM_PATH_MARGIN = 0.25;
142
195
  var splitOnWhitespace = function splitOnWhitespace(text) {
143
196
  var trimmed = text.trim();
144
197
  if (trimmed === '') {
@@ -173,114 +226,60 @@ var truncateToLastNWords = function truncateToLastNWords(text, n) {
173
226
  return words.length <= n ? text : words.slice(-n).join(' ');
174
227
  };
175
228
 
176
- // ─── Logit capture ─────────────────────────────────────────────────────────
177
-
178
- /**
179
- * A LogitProcessor that captures the raw next-token logits and passes them
180
- * through unmodified.
181
- *
182
- * web-llm invokes `processLogits` on the CPU after the model's forward pass and
183
- * before sampling, handing us the full `Float32Array(vocab_size)` at the current
184
- * decode position. We copy it off web-llm's shared buffer (which it may reuse
185
- * across calls) and return the original untouched so sampling is unaffected.
186
- *
187
- * This is the raw-logit access the BE-parity algorithm needs (masked softmax +
188
- * prefix expansion, consumed in a later step). Registered for the causal LM
189
- * only — the embedder never decodes tokens, so it produces no logits.
190
- */
191
- var CapturingLogitProcessor = /*#__PURE__*/_createClass(function CapturingLogitProcessor() {
192
- var _this = this;
193
- _classCallCheck(this, CapturingLogitProcessor);
194
- _defineProperty(this, "captured", null);
195
- _defineProperty(this, "processLogits", function (logits) {
196
- // Copy off web-llm's shared buffer — it may reuse `logits` across calls.
197
- _this.captured = new Float32Array(logits);
198
- return logits;
199
- });
200
- _defineProperty(this, "processSampledToken", function () {
201
- // No-op — we don't track sampled tokens.
202
- });
203
- _defineProperty(this, "resetState", function () {
204
- _this.captured = null;
205
- });
206
- }); // ─── BE-parity data + algorithm ──────────────────────────────────────────────
207
229
  /**
208
- * Prefix-expansion map: first-token id → words whose space-prefixed SmolLM2
209
- * encoding starts with that token. Generated offline by
210
- * `scripts/gen_first_token_to_words.py`, which mirrors the BE's in-memory map
211
- * (`CausalLMEncoder._ensure_loaded`).
212
- *
213
- * Populated lazily by `loadBePayloadData()` from a dynamically-imported JSON so
214
- * the (large) payload is only fetched when the local client is actually
215
- * initialised — keeping it out of the editor's main chunk for the vast majority
216
- * of users (who run with `useLocalModel` off).
230
+ * Full canonical leading-space token sequence for every served surface. The
231
+ * producer keeps the existing `phrase-continuation-tokens.json` wire name while
232
+ * expanding its key set to the complete word/bigram/phrase union.
217
233
  */
218
- var firstTokenToWords = new Map();
219
-
220
- /**
221
- * L2 (Atlassian-domain) word set, derived from the keys of `vocabulary_10k.json`.
222
- * Used by `computeBePayload` for tier-aware ranking: any word in the prefix map
223
- * that is not in this set is treated as L3 (general English), matching the BE.
224
- * Populated lazily alongside `firstTokenToWords` — see `loadBePayloadData()`.
225
- */
226
- var l2Words = new Set();
227
-
228
- /**
229
- * Array of token IDs that appear as a first token for at least one vocabulary
230
- * word. Derived from `firstTokenToWords` when the data loads so `computeBePayload`
231
- * does not re-allocate this array on every word-boundary call.
232
- */
233
- var prefixMapTokenIds = [];
234
+ var surfaceTokenIds = new Map();
235
+ var surfaceTokenTrie = new CanonicalSurfaceTokenTrie();
234
236
 
235
237
  /** De-dupes concurrent loads and lets repeated calls await the same payload. */
236
- var bePayloadDataPromise;
238
+ var surfaceTokenIdsPromise;
239
+ var isPhraseContinuationTokens = function isPhraseContinuationTokens(payload) {
240
+ if (payload == null || _typeof(payload) !== 'object') {
241
+ return false;
242
+ }
243
+ return Object.values(payload).every(function (value) {
244
+ return Array.isArray(value) && value.every(function (entry) {
245
+ return typeof entry === 'number' && entry >= 0;
246
+ });
247
+ });
248
+ };
237
249
 
238
- /**
239
- * Lazily load and build the BE-parity lookup tables from their CDN payloads.
240
- * Both artifacts are fetched on demand so neither is downloaded unless local
241
- * inference is initialised.
242
- *
243
- * :returns:
244
- * A promise that resolves once `firstTokenToWords`, `l2Words` and
245
- * `prefixMapTokenIds` are populated.
246
- */
247
- var loadBePayloadData = function loadBePayloadData() {
248
- if (!bePayloadDataPromise) {
249
- bePayloadDataPromise = _asyncToGenerator( /*#__PURE__*/_regeneratorRuntime.mark(function _callee() {
250
- var _yield$Promise$all, _yield$Promise$all2, firstTokenToWordsData, vocabularyData;
250
+ /** Lazily load the producer's full-union canonical surface token map. */
251
+ var loadCanonicalSurfaceTokens = function loadCanonicalSurfaceTokens() {
252
+ if (!surfaceTokenIdsPromise) {
253
+ surfaceTokenIdsPromise = _asyncToGenerator( /*#__PURE__*/_regeneratorRuntime.mark(function _callee() {
254
+ var continuationTokensData;
251
255
  return _regeneratorRuntime.wrap(function (_context) {
252
256
  while (1) switch (_context.prev = _context.next) {
253
257
  case 0:
254
258
  _context.next = 1;
255
- return Promise.all([fetchArtifactJson(ARTIFACT_NAME.FIRST_TOKEN_TO_WORDS), fetchArtifactJson(ARTIFACT_NAME.VOCABULARY)]);
259
+ return fetchAutocompleteArtifactJson(ARTIFACT_NAME.PHRASE_CONTINUATION_TOKENS, {
260
+ summarize: function summarize(payload) {
261
+ return "".concat(Object.keys(payload).length, " surfaces");
262
+ },
263
+ validate: isPhraseContinuationTokens
264
+ }).catch(function () {
265
+ return null;
266
+ });
256
267
  case 1:
257
- _yield$Promise$all = _context.sent;
258
- _yield$Promise$all2 = _slicedToArray(_yield$Promise$all, 2);
259
- firstTokenToWordsData = _yield$Promise$all2[0];
260
- vocabularyData = _yield$Promise$all2[1];
261
- if (!(firstTokenToWordsData == null || (vocabularyData === null || vocabularyData === void 0 ? void 0 : vocabularyData.words) == null)) {
262
- _context.next = 2;
263
- break;
264
- }
265
- throw new Error("[LocalSlowLane] unexpected artifact payload \u2014 " + "firstTokenToWordsData=".concat(firstTokenToWordsData == null ? 'null/undefined' : 'defined', ", ") + "vocabularyData=".concat(vocabularyData == null ? 'null/undefined' : vocabularyData.words == null ? 'defined but missing .words' : 'defined'));
266
- case 2:
267
- firstTokenToWords = new Map(Object.entries(firstTokenToWordsData).map(function (_ref2) {
268
+ continuationTokensData = _context.sent;
269
+ surfaceTokenIds = new Map(Object.entries(continuationTokensData !== null && continuationTokensData !== void 0 ? continuationTokensData : {}).map(function (_ref2) {
268
270
  var _ref3 = _slicedToArray(_ref2, 2),
269
- tokenId = _ref3[0],
270
- words = _ref3[1];
271
- return [Number(tokenId), words];
271
+ surface = _ref3[0],
272
+ tokenIds = _ref3[1];
273
+ return [surface.toLowerCase(), tokenIds];
272
274
  }));
273
- l2Words = new Set(Object.keys(vocabularyData.words));
274
- prefixMapTokenIds = Array.from(firstTokenToWords.keys());
275
+ surfaceTokenTrie = new CanonicalSurfaceTokenTrie(surfaceTokenIds.entries());
275
276
  if (isAutocompleteDebugEnabled()) {
276
277
  // eslint-disable-next-line no-console
277
- console.log('%c[LocalSlowLane] %c✅ BE-parity payload data loaded:', 'color: #9c27b0; font-weight: bold;', 'color: #4caf50; font-weight: bold;', {
278
- firstTokenToWordsEntries: firstTokenToWords.size,
279
- l2WordsCount: l2Words.size,
280
- prefixMapTokenIdsLength: prefixMapTokenIds.length
278
+ console.log("%c[CTC:model] %c".concat(surfaceTokenIds.size > 0 ? '✅ canonical surface tokens loaded:' : '⚠️ canonical surface tokens unavailable — LM evidence will remain absent:'), 'color: #9c27b0; font-weight: bold;', surfaceTokenIds.size > 0 ? 'color: #4caf50; font-weight: bold;' : 'color: #ff9800; font-weight: bold;', {
279
+ surfaces: surfaceTokenIds.size
281
280
  });
282
281
  }
283
- case 3:
282
+ case 2:
284
283
  case "end":
285
284
  return _context.stop();
286
285
  }
@@ -289,225 +288,11 @@ var loadBePayloadData = function loadBePayloadData() {
289
288
  // Don't cache a rejected promise — a transient import failure would
290
289
  // otherwise prevent the local model from ever initialising again this
291
290
  // session. Reset so the next init attempt retries.
292
- bePayloadDataPromise = undefined;
291
+ surfaceTokenIdsPromise = undefined;
293
292
  throw e;
294
293
  });
295
294
  }
296
- return bePayloadDataPromise;
297
- };
298
-
299
- /**
300
- * Convert a raw next-token logit vector into a whole-word probability payload,
301
- * faithfully porting the BE `CausalLMEncoder._get_top_k_probs`
302
- * (`cc-smarts/python-sidecar/src/causal_lm_encoder.py`).
303
- *
304
- * Steps: (1) numerically-stable masked softmax over only the token ids present
305
- * in the prefix-expansion map; (2) spread each token's probability to every
306
- * whole word sharing that first token, taking the max; (3) reserve the top L2
307
- * words unconditionally; (4) rank the remainder in a log-space pool with an
308
- * additive L2 bias; (5) emit raw probabilities for the survivors, lowercased
309
- * and trimmed at `MIN_PROB`.
310
- *
311
- * :params:
312
- * rawLogits: Full-vocabulary logits from the LM's single decode step
313
- * prefixMap: Map of first-token id to the words starting with that token
314
- * domainWords: Set of L2 (domain) words, for tier-aware ranking
315
- * :returns:
316
- * A record of lowercase word to probability — the BE `lm_logits` payload
317
- */
318
- export var computeBePayload = function computeBePayload(rawLogits, prefixMap, domainWords) {
319
- var validTokenIds = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : prefixMapTokenIds;
320
- // 1. Numerically-stable masked softmax over validTokenIds only.
321
- var maxLogit = -Infinity;
322
- var _iterator = _createForOfIteratorHelper(validTokenIds),
323
- _step;
324
- try {
325
- for (_iterator.s(); !(_step = _iterator.n()).done;) {
326
- var id = _step.value;
327
- var v = rawLogits[id];
328
- if (v > maxLogit) {
329
- maxLogit = v;
330
- }
331
- }
332
- } catch (err) {
333
- _iterator.e(err);
334
- } finally {
335
- _iterator.f();
336
- }
337
- var sumExp = 0;
338
- var expByToken = new Map();
339
- var _iterator2 = _createForOfIteratorHelper(validTokenIds),
340
- _step2;
341
- try {
342
- for (_iterator2.s(); !(_step2 = _iterator2.n()).done;) {
343
- var _id = _step2.value;
344
- var e = Math.exp(rawLogits[_id] - maxLogit);
345
- expByToken.set(_id, e);
346
- sumExp += e;
347
- }
348
-
349
- // 2. Prefix expansion with max aggregation (probabilities sum to 1 over the
350
- // masked subset, so divide each token's exp by sumExp on the fly).
351
- } catch (err) {
352
- _iterator2.e(err);
353
- } finally {
354
- _iterator2.f();
355
- }
356
- var wordProbs = new Map();
357
- var _iterator3 = _createForOfIteratorHelper(prefixMap),
358
- _step3;
359
- try {
360
- for (_iterator3.s(); !(_step3 = _iterator3.n()).done;) {
361
- var _expByToken$get;
362
- var _step3$value = _slicedToArray(_step3.value, 2),
363
- _id2 = _step3$value[0],
364
- words = _step3$value[1];
365
- var _p = sumExp > 0 ? ((_expByToken$get = expByToken.get(_id2)) !== null && _expByToken$get !== void 0 ? _expByToken$get : 0) / sumExp : 0;
366
- var _iterator8 = _createForOfIteratorHelper(words),
367
- _step8;
368
- try {
369
- for (_iterator8.s(); !(_step8 = _iterator8.n()).done;) {
370
- var _wordProbs$get2;
371
- var _w = _step8.value;
372
- var prev = (_wordProbs$get2 = wordProbs.get(_w)) !== null && _wordProbs$get2 !== void 0 ? _wordProbs$get2 : 0;
373
- if (_p > prev) {
374
- wordProbs.set(_w, _p);
375
- }
376
- }
377
- } catch (err) {
378
- _iterator8.e(err);
379
- } finally {
380
- _iterator8.f();
381
- }
382
- }
383
-
384
- // 3. Split into L2 / L3 and reserve the top L2 slots unconditionally.
385
- } catch (err) {
386
- _iterator3.e(err);
387
- } finally {
388
- _iterator3.f();
389
- }
390
- var l2Matches = [];
391
- var l3Matches = [];
392
- var _iterator4 = _createForOfIteratorHelper(wordProbs),
393
- _step4;
394
- try {
395
- for (_iterator4.s(); !(_step4 = _iterator4.n()).done;) {
396
- var _step4$value = _slicedToArray(_step4.value, 2),
397
- _w2 = _step4$value[0],
398
- _p2 = _step4$value[1];
399
- if (domainWords.has(_w2)) {
400
- l2Matches.push([_w2, _p2]);
401
- } else {
402
- l3Matches.push([_w2, _p2]);
403
- }
404
- }
405
- } catch (err) {
406
- _iterator4.e(err);
407
- } finally {
408
- _iterator4.f();
409
- }
410
- l2Matches.sort(function (a, b) {
411
- return b[1] - a[1];
412
- });
413
- var reserved = l2Matches.slice(0, BE_PARITY.RESERVED_L2_SLOTS);
414
-
415
- // 4. Pool the leftovers in log space; the L2 bias only affects ranking here.
416
- // Words in l2Matches are unique and the array is sorted descending, so the
417
- // non-reserved entries are exactly the tail after the reserved prefix — slice
418
- // it directly rather than allocating a Set and scanning every entry on this
419
- // hot path (runs ~every word boundary while typing).
420
- var pool = [];
421
- var _iterator5 = _createForOfIteratorHelper(l2Matches.slice(BE_PARITY.RESERVED_L2_SLOTS)),
422
- _step5;
423
- try {
424
- for (_iterator5.s(); !(_step5 = _iterator5.n()).done;) {
425
- var _step5$value = _slicedToArray(_step5.value, 2),
426
- _w3 = _step5$value[0],
427
- _p3 = _step5$value[1];
428
- pool.push([_w3, Math.log(Math.max(_p3, 1e-10)) + BE_PARITY.L2_BIAS]);
429
- }
430
- } catch (err) {
431
- _iterator5.e(err);
432
- } finally {
433
- _iterator5.f();
434
- }
435
- for (var _i = 0, _l3Matches = l3Matches; _i < _l3Matches.length; _i++) {
436
- var _l3Matches$_i = _slicedToArray(_l3Matches[_i], 2),
437
- w = _l3Matches$_i[0],
438
- p = _l3Matches$_i[1];
439
- pool.push([w, Math.log(Math.max(p, 1e-10))]);
440
- }
441
- pool.sort(function (a, b) {
442
- return b[1] - a[1];
443
- });
444
- var remainingSlots = Math.max(0, BE_PARITY.TOP_K_WORDS - reserved.length);
445
- var poolWinners = pool.slice(0, remainingSlots);
446
-
447
- // 5. Assemble payload: store RAW probabilities (the bias was ranking-only),
448
- // lowercase keys, trimmed at MIN_PROB. Reserved first, then pool winners.
449
- // Reserved entries are written first; pool-winner writes must NOT clobber a
450
- // reserved entry whose normalised key collides (two source words can
451
- // `.trim().toLowerCase()` to the same key — e.g. "Function" vs "function ").
452
- // Without the existence guard, a low-probability pool winner would silently
453
- // overwrite the (higher-probability) reserved entry, degrading top-K
454
- // quality in a way that's invisible from the debug summary.
455
- var result = {};
456
- var addEntry = function addEntry(word, prob, allowOverwrite) {
457
- if (prob <= BE_PARITY.MIN_PROB) {
458
- return;
459
- }
460
- var key = word.trim().toLowerCase();
461
- if (!allowOverwrite && key in result) {
462
- return;
463
- }
464
- result[key] = prob;
465
- };
466
- var _iterator6 = _createForOfIteratorHelper(reserved),
467
- _step6;
468
- try {
469
- for (_iterator6.s(); !(_step6 = _iterator6.n()).done;) {
470
- var _step6$value = _slicedToArray(_step6.value, 2),
471
- _w4 = _step6$value[0],
472
- _p4 = _step6$value[1];
473
- addEntry(_w4, _p4, true);
474
- }
475
- } catch (err) {
476
- _iterator6.e(err);
477
- } finally {
478
- _iterator6.f();
479
- }
480
- var _iterator7 = _createForOfIteratorHelper(poolWinners),
481
- _step7;
482
- try {
483
- for (_iterator7.s(); !(_step7 = _iterator7.n()).done;) {
484
- var _wordProbs$get3;
485
- var _step7$value = _slicedToArray(_step7.value, 1),
486
- _w5 = _step7$value[0];
487
- addEntry(_w5, (_wordProbs$get3 = wordProbs.get(_w5)) !== null && _wordProbs$get3 !== void 0 ? _wordProbs$get3 : 0, false);
488
- }
489
- } catch (err) {
490
- _iterator7.e(err);
491
- } finally {
492
- _iterator7.f();
493
- }
494
- if (isAutocompleteDebugEnabled()) {
495
- var topReserved = reserved.slice(0, 5).map(function (_ref4) {
496
- var _ref5 = _slicedToArray(_ref4, 2),
497
- w = _ref5[0],
498
- p = _ref5[1];
499
- return "".concat(w, ":").concat((p * 100).toFixed(2), "%");
500
- }).join(', ');
501
- var topPool = poolWinners.slice(0, 5).map(function (_ref6) {
502
- var _wordProbs$get;
503
- var _ref7 = _slicedToArray(_ref6, 1),
504
- w = _ref7[0];
505
- return "".concat(w, ":").concat((((_wordProbs$get = wordProbs.get(w)) !== null && _wordProbs$get !== void 0 ? _wordProbs$get : 0) * 100).toFixed(2), "%");
506
- }).join(', ');
507
- // eslint-disable-next-line no-console
508
- console.log('%c[computeBePayload] %c%d valid tokens → %d words expanded | L2: %d / L3: %d | reserved: %d | pool winners: %d | final: %d words\n maxLogit(masked): %s | sumExp: %s\n top reserved L2: %s\n top pool: %s', 'color: #9c27b0; font-weight: bold;', 'color: inherit;', validTokenIds.length, wordProbs.size, l2Matches.length, l3Matches.length, reserved.length, poolWinners.length, Object.keys(result).length, maxLogit.toFixed(3), sumExp.toFixed(1), topReserved || '(none)', topPool || '(none)');
509
- }
510
- return result;
295
+ return surfaceTokenIdsPromise;
511
296
  };
512
297
 
513
298
  // ─── Factory ─────────────────────────────────────────────────────────────────
@@ -523,9 +308,8 @@ export var computeBePayload = function computeBePayload(rawLogits, prefixMap, do
523
308
  * const client = createLocalSlowLaneClient({ debounceMs: 300 });
524
309
  * // On word boundaries:
525
310
  * client.updateContext(docText);
526
- * // In scoring pipeline:
527
- * const vec = client.getContextVector();
528
- * const logits = client.getLmLogits();
311
+ * // Candidate scoring independently calls
312
+ * // primeBoundaryLm/requestProgressiveSurfaceScores.
529
313
  * // On plugin teardown:
530
314
  * client.destroy();
531
315
  * ```
@@ -535,6 +319,8 @@ export var createLocalSlowLaneClient = function createLocalSlowLaneClient() {
535
319
  var _config$debounceMs = config.debounceMs,
536
320
  debounceMs = _config$debounceMs === void 0 ? DEFAULT_DEBOUNCE_MS : _config$debounceMs,
537
321
  onUpdate = config.onUpdate,
322
+ onBoundaryLmUpdate = config.onBoundaryLmUpdate,
323
+ onSurfaceScoreUpdate = config.onSurfaceScoreUpdate,
538
324
  onStatus = config.onStatus,
539
325
  onLoadError = config.onLoadError,
540
326
  onLoadSuccess = config.onLoadSuccess,
@@ -544,8 +330,54 @@ export var createLocalSlowLaneClient = function createLocalSlowLaneClient() {
544
330
  surface = config.surface;
545
331
 
546
332
  // ── State ──────────────────────────────────────────────────────────────
333
+ var storedContextInput = null;
547
334
  var storedContextVector = null;
548
335
  var storedLmLogits = null;
336
+ var boundaryCache = new Map();
337
+ var prefixExpansionCache = new Map();
338
+ var surfaceScoreCache = new Map();
339
+ var causalInFlight = false;
340
+ var inFlightBoundaryContextKey = null;
341
+ var latestCausalFamilyKey = '';
342
+ var causalRequestsThisFamily = 0;
343
+ var causalGeneratedTokensThisFamily = 0;
344
+ var tierAPrimeCacheHitsThisFamily = 0;
345
+ var tierAPrimeCacheMissesThisFamily = 0;
346
+ var causalInferenceOrdinal = 0;
347
+ var causalInferenceByContext = new Map();
348
+ var causalInferenceByFamily = new Map();
349
+ var exactEvidenceCountByContext = new Map();
350
+ var pendingBoundaryPrimes = new Map();
351
+ var pendingPrefixExpansions = new Map();
352
+ var inFlightPrefixExpansions = new Set();
353
+ // The engine holds exactly one linear KV sequence. WebLLM lets us extend it
354
+ // (`forwardTokensAndSample`) or drop it (a text completion always resets
355
+ // first), but never fork or rewind it. Tracking what is currently
356
+ // materialised is what makes decoding continuous: expanding a token prefix
357
+ // that extends `kvPath` costs a single decode step, while any other prefix
358
+ // costs a fresh prompt prefill.
359
+ var kvContextKey = null;
360
+ var kvPath = [];
361
+ var isPrefixOf = function isPrefixOf(prefix, path) {
362
+ return prefix.length <= path.length && prefix.every(function (token, index) {
363
+ return token === path[index];
364
+ });
365
+ };
366
+ /**
367
+ * Whether running this expansion would extend the sequence the engine is
368
+ * already holding rather than discarding it for a fresh prompt prefill.
369
+ *
370
+ * `planProgressiveExpansion` also prefers a warm group, but it decides when
371
+ * the work is queued and the queue is drained later. A boundary prime or
372
+ * another context's expansion running in between moves the path out from
373
+ * under that choice, so the preference has to be re-checked at the moment
374
+ * something is picked up.
375
+ */
376
+ var extendsLiveKvPath = function extendsLiveKvPath(request) {
377
+ return kvContextKey === request.contextKey && isPrefixOf(kvPath, request.tokenPrefix);
378
+ };
379
+ var progressiveRequests = new Map();
380
+ var progressivePrefixesByContext = new Map();
549
381
  var debounceTimer = null;
550
382
  var lastRequestedText = '';
551
383
  var requestCounter = 0;
@@ -563,14 +395,12 @@ export var createLocalSlowLaneClient = function createLocalSlowLaneClient() {
563
395
  var initFailed = false;
564
396
  var engine = null;
565
397
  var engineInitPromise = null;
566
- // Captures raw next-token logits from the LM's single decode step. Registered
567
- // with the engine below; `lmLogitsCapture.captured` is consumed in a later step.
568
- var lmLogitsCapture = new CapturingLogitProcessor();
398
+ var causalLogitProcessor = new CanonicalLogitProcessor();
569
399
  var unloadEngine = function unloadEngine(engineToUnload) {
570
400
  engineToUnload.unload().catch(function (error) {
571
401
  if (isAutocompleteDebugEnabled()) {
572
402
  // eslint-disable-next-line no-console
573
- console.log('%c[LocalSlowLane] %cFailed to unload engine', 'color: #9c27b0; font-weight: bold;', 'color: inherit;', error);
403
+ console.log('%c[CTC:model] %cFailed to unload engine', 'color: #9c27b0; font-weight: bold;', 'color: inherit;', error);
574
404
  }
575
405
  });
576
406
  };
@@ -581,7 +411,7 @@ export var createLocalSlowLaneClient = function createLocalSlowLaneClient() {
581
411
  var message = "[".concat((progress.progress * 100).toFixed(0), "%] ").concat(progress.text);
582
412
  if (isAutocompleteDebugEnabled()) {
583
413
  // eslint-disable-next-line no-console
584
- console.log("%c[LocalSlowLane] %c\uD83D\uDD04 ".concat(message), 'color: #9c27b0; font-weight: bold;', 'color: inherit;');
414
+ console.log("%c[CTC:model] %c\uD83D\uDD04 ".concat(message), 'color: #9c27b0; font-weight: bold;', 'color: inherit;');
585
415
  }
586
416
  onStatus === null || onStatus === void 0 || onStatus(message);
587
417
  };
@@ -594,8 +424,8 @@ export var createLocalSlowLaneClient = function createLocalSlowLaneClient() {
594
424
  * to a concrete hardware/browser limitation rather than a generic error.
595
425
  */
596
426
  var probeWebGpuCapabilities = /*#__PURE__*/function () {
597
- var _ref8 = _asyncToGenerator( /*#__PURE__*/_regeneratorRuntime.mark(function _callee2() {
598
- var gpu, _adapter$limits, _adapter$limits2, adapter, vendor, architecture, _info, _requestAdapterInfo, _ref9, info, _t, _t2, _t3;
427
+ var _ref4 = _asyncToGenerator( /*#__PURE__*/_regeneratorRuntime.mark(function _callee2() {
428
+ var gpu, _adapter$limits, _adapter$limits2, adapter, vendor, architecture, _info, _requestAdapterInfo, _ref5, info, _t, _t2, _t3;
599
429
  return _regeneratorRuntime.wrap(function (_context2) {
600
430
  while (1) switch (_context2.prev = _context2.next) {
601
431
  case 0:
@@ -632,7 +462,7 @@ export var createLocalSlowLaneClient = function createLocalSlowLaneClient() {
632
462
  break;
633
463
  case 4:
634
464
  _context2.next = 5;
635
- return (_requestAdapterInfo = (_ref9 = adapter).requestAdapterInfo) === null || _requestAdapterInfo === void 0 ? void 0 : _requestAdapterInfo.call(_ref9);
465
+ return (_requestAdapterInfo = (_ref5 = adapter).requestAdapterInfo) === null || _requestAdapterInfo === void 0 ? void 0 : _requestAdapterInfo.call(_ref5);
636
466
  case 5:
637
467
  _t = _context2.sent;
638
468
  case 6:
@@ -668,7 +498,7 @@ export var createLocalSlowLaneClient = function createLocalSlowLaneClient() {
668
498
  }, _callee2, null, [[1, 9], [3, 7]]);
669
499
  }));
670
500
  return function probeWebGpuCapabilities() {
671
- return _ref8.apply(this, arguments);
501
+ return _ref4.apply(this, arguments);
672
502
  };
673
503
  }();
674
504
 
@@ -710,7 +540,7 @@ export var createLocalSlowLaneClient = function createLocalSlowLaneClient() {
710
540
  var message = LOAD_FAILURE_MESSAGE[reason];
711
541
  if (isAutocompleteDebugEnabled()) {
712
542
  // eslint-disable-next-line no-console
713
- console.log("[LocalSlowLane] Engine initialisation failed (".concat(reason, "): ").concat(debugDetail !== null && debugDetail !== void 0 ? debugDetail : message));
543
+ console.log("[CTC:model] Engine initialisation failed (".concat(reason, "): ").concat(debugDetail !== null && debugDetail !== void 0 ? debugDetail : message));
714
544
  }
715
545
  onStatus === null || onStatus === void 0 || onStatus("Engine initialisation failed: ".concat(message));
716
546
  onLoadError === null || onLoadError === void 0 || onLoadError({
@@ -724,8 +554,8 @@ export var createLocalSlowLaneClient = function createLocalSlowLaneClient() {
724
554
  initFailed = true;
725
555
  };
726
556
  var initEngine = /*#__PURE__*/function () {
727
- var _ref0 = _asyncToGenerator( /*#__PURE__*/_regeneratorRuntime.mark(function _callee3() {
728
- var capabilities, startTime, _yield$Promise$all3, _yield$Promise$all4, _yield$Promise$all4$, MLCEngineCtor, prebuiltAppConfig, customModelRecord, appConfig, newEngine, loadDurationMs, errorMsg, _t4;
557
+ var _ref6 = _asyncToGenerator( /*#__PURE__*/_regeneratorRuntime.mark(function _callee3() {
558
+ var capabilities, startTime, _yield$Promise$all, _yield$Promise$all2, _yield$Promise$all2$, MLCEngineCtor, prebuiltAppConfig, customModelRecord, appConfig, newEngine, loadDurationMs, errorMsg, _t4;
729
559
  return _regeneratorRuntime.wrap(function (_context3) {
730
560
  while (1) switch (_context3.prev = _context3.next) {
731
561
  case 0:
@@ -758,20 +588,20 @@ export var createLocalSlowLaneClient = function createLocalSlowLaneClient() {
758
588
  _context3.prev = 5;
759
589
  if (isAutocompleteDebugEnabled()) {
760
590
  // eslint-disable-next-line no-console
761
- console.log("%c[LocalSlowLane] %c\uD83D\uDE80 Initialising MLC engine with models: ".concat(modelId, " (LM) + ").concat(LOCAL_MLC_EMBEDDING_MODEL_ID, " (embedder)"), 'color: #9c27b0; font-weight: bold;', 'color: inherit;');
591
+ console.log("%c[CTC:model] %c\uD83D\uDE80 Initialising MLC engine with models: ".concat(modelId, " (LM) + ").concat(LOCAL_MLC_EMBEDDING_MODEL_ID, " (embedder)"), 'color: #9c27b0; font-weight: bold;', 'color: inherit;');
762
592
  }
763
593
  onStatus === null || onStatus === void 0 || onStatus("Initialising models: ".concat(modelId, " + ").concat(LOCAL_MLC_EMBEDDING_MODEL_ID, "\u2026"));
764
594
 
765
- // Fetch the web-llm runtime and the BE-parity lookup tables in parallel;
595
+ // Fetch the web-llm runtime and canonical token artifact in parallel;
766
596
  // both are dynamically imported so they stay out of the main editor chunk.
767
597
  _context3.next = 6;
768
- return Promise.all([import( /* webpackChunkName: "@atlaskit-internal_editor-plugin-autocomplete-mlc-web-llm" */'@mlc-ai/web-llm'), loadBePayloadData()]);
598
+ return Promise.all([import( /* webpackChunkName: "@atlaskit-internal_editor-plugin-autocomplete-mlc-web-llm" */'@mlc-ai/web-llm'), loadCanonicalSurfaceTokens()]);
769
599
  case 6:
770
- _yield$Promise$all3 = _context3.sent;
771
- _yield$Promise$all4 = _slicedToArray(_yield$Promise$all3, 1);
772
- _yield$Promise$all4$ = _yield$Promise$all4[0];
773
- MLCEngineCtor = _yield$Promise$all4$.MLCEngine;
774
- prebuiltAppConfig = _yield$Promise$all4$.prebuiltAppConfig;
600
+ _yield$Promise$all = _context3.sent;
601
+ _yield$Promise$all2 = _slicedToArray(_yield$Promise$all, 1);
602
+ _yield$Promise$all2$ = _yield$Promise$all2[0];
603
+ MLCEngineCtor = _yield$Promise$all2$.MLCEngine;
604
+ prebuiltAppConfig = _yield$Promise$all2$.prebuiltAppConfig;
775
605
  customModelRecord = customModelConfig ? _objectSpread(_objectSpread({
776
606
  model: customModelConfig.model,
777
607
  model_id: modelId,
@@ -793,7 +623,7 @@ export var createLocalSlowLaneClient = function createLocalSlowLaneClient() {
793
623
  newEngine = new MLCEngineCtor({
794
624
  appConfig: appConfig,
795
625
  initProgressCallback: initProgressCallback,
796
- logitProcessorRegistry: new Map([[modelId, lmLogitsCapture]])
626
+ logitProcessorRegistry: new Map([[modelId, causalLogitProcessor]])
797
627
  });
798
628
  _context3.next = 7;
799
629
  return newEngine.reload([modelId, LOCAL_MLC_EMBEDDING_MODEL_ID]);
@@ -811,13 +641,13 @@ export var createLocalSlowLaneClient = function createLocalSlowLaneClient() {
811
641
  loadDurationMs = Math.round(performance.now() - startTime);
812
642
  if (isAutocompleteDebugEnabled()) {
813
643
  // eslint-disable-next-line no-console
814
- console.log('%c[LocalSlowLane] %c✅ Both models loaded and ready', 'color: #9c27b0; font-weight: bold;', 'color: #4caf50;');
644
+ console.log('%c[CTC:model] %c✅ Both models loaded and ready', 'color: #9c27b0; font-weight: bold;', 'color: #4caf50;');
815
645
  // One-time identity summary so you can confirm which models are active
816
646
  // without digging through the init-progress scroll.
817
647
  // eslint-disable-next-line no-console
818
- console.log('%c[LocalSlowLane] %c🧠 Causal LM →', 'color: #9c27b0; font-weight: bold;', 'color: #2196f3; font-weight: bold;', modelId);
648
+ console.log('%c[CTC:model] %c🧠 Causal LM →', 'color: #9c27b0; font-weight: bold;', 'color: #2196f3; font-weight: bold;', modelId);
819
649
  // eslint-disable-next-line no-console
820
- console.log('%c[LocalSlowLane] %c🔢 Embedder →', 'color: #9c27b0; font-weight: bold;', 'color: #009688; font-weight: bold;', LOCAL_MLC_EMBEDDING_MODEL_ID);
650
+ console.log('%c[CTC:model] %c🔢 Embedder →', 'color: #9c27b0; font-weight: bold;', 'color: #009688; font-weight: bold;', LOCAL_MLC_EMBEDDING_MODEL_ID);
821
651
  }
822
652
  onStatus === null || onStatus === void 0 || onStatus('Model loaded and ready.');
823
653
  onLoadSuccess === null || onLoadSuccess === void 0 || onLoadSuccess({
@@ -840,7 +670,7 @@ export var createLocalSlowLaneClient = function createLocalSlowLaneClient() {
840
670
  }, _callee3, null, [[5, 9]]);
841
671
  }));
842
672
  return function initEngine() {
843
- return _ref0.apply(this, arguments);
673
+ return _ref6.apply(this, arguments);
844
674
  };
845
675
  }();
846
676
  var ensureEngineInitialized = function ensureEngineInitialized() {
@@ -855,53 +685,25 @@ export var createLocalSlowLaneClient = function createLocalSlowLaneClient() {
855
685
 
856
686
  // ── Inference ──────────────────────────────────────────────────────────
857
687
 
858
- /**
859
- * Run a single forward pass to produce the BE-parity slow-lane outputs.
860
- *
861
- * Two calls run in parallel on the shared engine:
862
- * - `completions.create({ max_tokens: 1 })` runs the causal LM for exactly
863
- * one decode step. We ignore the generated text; the LogitProcessor
864
- * captures the raw next-token logits during that step, which we turn into
865
- * a whole-word payload via `computeBePayload`.
866
- * - `embeddings.create(...)` runs the Arctic embedder to produce the real
867
- * 384-d semantic vector (passage-encoded; see `wrapForArctic`).
868
- */
688
+ /** Run the boundary-timed Arctic semantic inference independently of the LM. */
869
689
  var runInference = /*#__PURE__*/function () {
870
- var _ref1 = _asyncToGenerator( /*#__PURE__*/_regeneratorRuntime.mark(function _callee4(text, requestId) {
871
- var experienceId, lmText, semanticText, arcticInput, captureCompletionTime, _data, tStart, tLmDone, tEmbDone, _yield$Promise$all5, _yield$Promise$all6, embeddingResponse, rawLogits, payload, embedding, sumSq, i, topTokens, errorMsg, _t5;
690
+ var _ref7 = _asyncToGenerator( /*#__PURE__*/_regeneratorRuntime.mark(function _callee4(text, requestId) {
691
+ var experienceId, semanticText, arcticInput, _data, tStart, embeddingResponse, tEmbDone, embedding, sumSq, i, errorMsg, _t5;
872
692
  return _regeneratorRuntime.wrap(function (_context4) {
873
693
  while (1) switch (_context4.prev = _context4.next) {
874
694
  case 0:
875
- captureCompletionTime = function _captureCompletionTim(promise, onResolved) {
876
- return promise.then(function (value) {
877
- onResolved(performance.now());
878
- return value;
879
- });
880
- };
881
695
  if (!(!engine || destroyed)) {
882
696
  _context4.next = 1;
883
697
  break;
884
698
  }
885
699
  return _context4.abrupt("return");
886
700
  case 1:
887
- experienceId = String(requestId); // Clear the capture buffer so we read only this pass's logits. The engine
888
- // serialises per-model requests and updateContext is debounced, so the
889
- // latest request's decode step is the last to populate `captured` before
890
- // we read it below; stale requests bail on the latestRequestId guard.
891
- lmLogitsCapture.resetState();
892
-
893
- // Apply BE-parity rolling-window truncation before both encoders.
894
- // BE semantic: last max_context_words words (typeahead_context_encoding.py:36)
895
- // BE causal LM: last max_context_tokens BPE tokens (causal_lm_encoder.py:194–198),
896
- // approximated here with word count (no tokenizer available on FE).
897
- lmText = truncateToLastNWords(text, BE_PARITY.MAX_CONTEXT_TOKENS);
898
- semanticText = truncateToLastNWords(text, BE_PARITY.MAX_CONTEXT_WORDS);
701
+ experienceId = String(requestId);
702
+ semanticText = truncateToLastNWords(text, LOCAL_INFERENCE.MAX_CONTEXT_WORDS);
899
703
  arcticInput = wrapForArctic(semanticText);
900
- if (isAutocompleteDebugEnabled()) {
901
- // eslint-disable-next-line no-console
902
- console.log("%c[LocalSlowLane] %c\uD83D\uDD22 Arctic input (".concat(arcticInput.length, " chars, ").concat(splitOnWhitespace(semanticText).length, " words): \"").concat(arcticInput.length > 100 ? "".concat(arcticInput.slice(0, 100), "\u2026") : arcticInput, "\""), 'color: #9c27b0; font-weight: bold;', 'color: #009688;');
704
+ if (isAutocompleteDebugVerbose()) {
903
705
  // eslint-disable-next-line no-console
904
- console.log("%c[LocalSlowLane] %c\uD83E\uDDE0 LM input (".concat(lmText.length, " chars, ").concat(splitOnWhitespace(lmText).length, " words): \"").concat(lmText.length > 100 ? "".concat(lmText.slice(0, 100), "\u2026") : lmText, "\""), 'color: #9c27b0; font-weight: bold;', 'color: #2196f3;');
706
+ console.log("%c[CTC:model] %c\uD83D\uDD22 Arctic input (".concat(arcticInput.length, " chars, ").concat(splitOnWhitespace(semanticText).length, " words): \"").concat(arcticInput.length > 100 ? "".concat(arcticInput.slice(0, 100), "\u2026") : arcticInput, "\""), 'color: #9c27b0; font-weight: bold;', 'color: #009688;');
905
707
  }
906
708
  _context4.prev = 2;
907
709
  // Reuse the network slow-lane-fetch UFO experience (tagged isLocalLLM:true,
@@ -917,30 +719,17 @@ export var createLocalSlowLaneClient = function createLocalSlowLaneClient() {
917
719
  surface: surface
918
720
  } : {}));
919
721
  tStart = performance.now();
920
- tLmDone = 0;
921
- tEmbDone = 0;
922
722
  _context4.next = 3;
923
- return Promise.all([captureCompletionTime(engine.completions.create({
924
- model: modelId,
925
- prompt: lmText,
926
- max_tokens: 1,
927
- temperature: 0,
928
- logprobs: false
929
- }), function (resolvedAt) {
930
- tLmDone = resolvedAt;
931
- }), captureCompletionTime(engine.embeddings.create({
723
+ return engine.embeddings.create({
932
724
  model: LOCAL_MLC_EMBEDDING_MODEL_ID,
933
725
  input: arcticInput
934
- }), function (resolvedAt) {
935
- tEmbDone = resolvedAt;
936
- })]);
726
+ });
937
727
  case 3:
938
- _yield$Promise$all5 = _context4.sent;
939
- _yield$Promise$all6 = _slicedToArray(_yield$Promise$all5, 2);
940
- embeddingResponse = _yield$Promise$all6[1];
728
+ embeddingResponse = _context4.sent;
729
+ tEmbDone = performance.now();
941
730
  if (isAutocompleteDebugEnabled()) {
942
731
  // eslint-disable-next-line no-console
943
- console.log("%c[LocalSlowLane] %c\u23F1 LM: ".concat((tLmDone - tStart).toFixed(0), "ms | Embedder: ").concat((tEmbDone - tStart).toFixed(0), "ms | Total: ").concat((Math.max(tLmDone, tEmbDone) - tStart).toFixed(0), "ms"), 'color: #9c27b0; font-weight: bold;', 'color: #ff9800;');
732
+ console.log("%c[CTC:model] %c\u23F1 Embedder: ".concat((tEmbDone - tStart).toFixed(0), "ms"), 'color: #9c27b0; font-weight: bold;', 'color: #ff9800;');
944
733
  }
945
734
 
946
735
  // Discard stale results
@@ -955,24 +744,16 @@ export var createLocalSlowLaneClient = function createLocalSlowLaneClient() {
955
744
  } : {}));
956
745
  return _context4.abrupt("return");
957
746
  case 4:
958
- // ── LM logits: whole-word BE-parity payload ──────────────────
959
- rawLogits = lmLogitsCapture.captured;
960
- if (rawLogits) {
961
- payload = computeBePayload(rawLogits, firstTokenToWords, l2Words);
962
- storedLmLogits = Object.keys(payload).length > 0 ? payload : null;
963
- } else {
964
- storedLmLogits = null;
965
- }
966
-
967
747
  // ── Semantic vector: real 384-d Arctic embedding ─────────────
968
748
  // Guard against base64-encoded responses (encoding_format: 'base64' would
969
749
  // yield a string, and new Float32Array(string) silently produces an empty
970
750
  // array, corrupting downstream cosine-similarity scoring).
971
751
  embedding = (_data = embeddingResponse.data) === null || _data === void 0 || (_data = _data[0]) === null || _data === void 0 ? void 0 : _data.embedding;
972
752
  storedContextVector = Array.isArray(embedding) && embedding.length > 0 ? new Float32Array(embedding) : null;
753
+ storedContextInput = storedContextVector ? semanticText : null;
973
754
  if (isAutocompleteDebugEnabled()) {
974
755
  // eslint-disable-next-line no-console
975
- console.groupCollapsed("%c[LocalSlowLane] %c\uD83D\uDCE5 Inference result (request #".concat(requestId, ")"), 'color: #9c27b0; font-weight: bold;', 'color: inherit;');
756
+ console.groupCollapsed("%c[CTC:model] %c\uD83D\uDCE5 Inference result (request #".concat(requestId, ")"), 'color: #9c27b0; font-weight: bold;', 'color: inherit;');
976
757
  if (storedContextVector) {
977
758
  sumSq = 0;
978
759
  for (i = 0; i < storedContextVector.length; i++) {
@@ -985,29 +766,14 @@ export var createLocalSlowLaneClient = function createLocalSlowLaneClient() {
985
766
  console.log('❌ No vector');
986
767
  }
987
768
  // eslint-disable-next-line no-console
988
- console.log(storedLmLogits ? "\u2705 lm_logits: ".concat(Object.keys(storedLmLogits).length, " words") : '❌ No lm_logits');
989
- if (storedLmLogits) {
990
- topTokens = Object.entries(storedLmLogits).sort(function (_ref10, _ref11) {
991
- var _ref12 = _slicedToArray(_ref10, 2),
992
- a = _ref12[1];
993
- var _ref13 = _slicedToArray(_ref11, 2),
994
- b = _ref13[1];
995
- return b - a;
996
- }).slice(0, 10); // eslint-disable-next-line no-console
997
- console.log('Top 10 predictions:', topTokens.map(function (_ref14) {
998
- var _ref15 = _slicedToArray(_ref14, 2),
999
- t = _ref15[0],
1000
- p = _ref15[1];
1001
- return "".concat(t, ": ").concat((p * 100).toFixed(1), "%");
1002
- }).join(', '));
1003
- }
769
+ console.log('🧠 causal LM: independently primed by exact candidate contexts');
1004
770
  // eslint-disable-next-line no-console
1005
771
  console.groupEnd();
1006
772
  }
1007
773
  succeedExp(EXPERIENCE_NAME.SLOW_LANE_FETCH, experienceId, _objectSpread({
1008
774
  textLength: text.length,
1009
775
  hasVector: storedContextVector !== null,
1010
- hasLmLogits: storedLmLogits !== null,
776
+ hasLmLogits: false,
1011
777
  isLocalLLM: true
1012
778
  }, surface ? {
1013
779
  surface: surface
@@ -1015,7 +781,7 @@ export var createLocalSlowLaneClient = function createLocalSlowLaneClient() {
1015
781
  onUpdate === null || onUpdate === void 0 || onUpdate({
1016
782
  textLength: text.length,
1017
783
  hasVector: storedContextVector !== null,
1018
- hasLmLogits: storedLmLogits !== null
784
+ hasLmLogits: false
1019
785
  });
1020
786
  _context4.next = 7;
1021
787
  break;
@@ -1033,6 +799,7 @@ export var createLocalSlowLaneClient = function createLocalSlowLaneClient() {
1033
799
  } : {}));
1034
800
  return _context4.abrupt("return");
1035
801
  case 6:
802
+ storedContextInput = null;
1036
803
  storedContextVector = null;
1037
804
  storedLmLogits = null;
1038
805
  failExp(EXPERIENCE_NAME.SLOW_LANE_FETCH, experienceId, _objectSpread({
@@ -1049,7 +816,7 @@ export var createLocalSlowLaneClient = function createLocalSlowLaneClient() {
1049
816
  errorMsg = _t5 instanceof Error ? _t5.message : String(_t5);
1050
817
  if (isAutocompleteDebugEnabled()) {
1051
818
  // eslint-disable-next-line no-console
1052
- console.log("%c[LocalSlowLane] %c\u274C Inference error (request #".concat(requestId, "): ").concat(errorMsg), 'color: #9c27b0; font-weight: bold;', 'color: #f44336;');
819
+ console.log("%c[CTC:model] %c\u274C Inference error (request #".concat(requestId, "): ").concat(errorMsg), 'color: #9c27b0; font-weight: bold;', 'color: #f44336;');
1053
820
  }
1054
821
  case 7:
1055
822
  case "end":
@@ -1058,9 +825,859 @@ export var createLocalSlowLaneClient = function createLocalSlowLaneClient() {
1058
825
  }, _callee4, null, [[2, 5]]);
1059
826
  }));
1060
827
  return function runInference(_x, _x2) {
828
+ return _ref7.apply(this, arguments);
829
+ };
830
+ }();
831
+
832
+ // ── Canonical causal scorer ─────────────────────────────────────────────
833
+
834
+ var surfaceCacheKey = function surfaceCacheKey(contextKey, surface) {
835
+ var _surfaceTokenIds$get;
836
+ var tokenIds = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : (_surfaceTokenIds$get = surfaceTokenIds.get(surface.toLowerCase())) !== null && _surfaceTokenIds$get !== void 0 ? _surfaceTokenIds$get : [];
837
+ return "".concat(contextKey, "\0").concat(surface.toLowerCase(), "\0").concat(tokenIds.join(','));
838
+ };
839
+
840
+ // Declared as functions rather than generic arrows: this file carries a `webpackChunkName`
841
+ // comment, which opts it into a build-time parse that reads `<T>(` as a JSX tag.
842
+ function getLru(cache, key) {
843
+ var value = cache.get(key);
844
+ if (value === undefined) {
845
+ return null;
846
+ }
847
+ cache.delete(key);
848
+ cache.set(key, value);
849
+ return value;
850
+ }
851
+ function setLru(cache, key, value, maxSize) {
852
+ cache.delete(key);
853
+ cache.set(key, value);
854
+ while (cache.size > maxSize) {
855
+ var oldest = cache.keys().next().value;
856
+ if (oldest === undefined) {
857
+ break;
858
+ }
859
+ cache.delete(oldest);
860
+ }
861
+ }
862
+ var prefixCacheKey = function prefixCacheKey(contextKey, tokenPrefix) {
863
+ return "".concat(contextKey, "\0").concat(tokenPrefix.join(','));
864
+ };
865
+ var logProgressiveState = function logProgressiveState(state, detail) {
866
+ if (!isAutocompleteDebugEnabled()) {
867
+ return;
868
+ }
869
+ // eslint-disable-next-line no-console
870
+ console.log("%c[CTC:model] %c\uD83D\uDD00 grouped ".concat(state, " \xB7 ").concat(detail), 'color: #9c27b0; font-weight: bold;', state === 'failed' ? 'color: #f44336;' : state === 'completed' || state === 'cached' ? 'color: #4caf50;' : 'color: #2196f3;');
871
+ };
872
+ var createCausalInferenceAggregate = function createCausalInferenceAggregate(startedAt) {
873
+ return {
874
+ e2eLatencyMs: 0,
875
+ failedRequests: 0,
876
+ firstStartedAt: startedAt,
877
+ promptTokens: 0,
878
+ promptUsageSamples: 0,
879
+ requestedCompletionTokens: 0,
880
+ requests: 0,
881
+ sampledOutputTokens: 0,
882
+ timeToFirstTokenMs: 0,
883
+ wallClockMs: 0,
884
+ webLlmDecodeSteps: 0,
885
+ webLlmUsageSamples: 0
886
+ };
887
+ };
888
+ var updateCausalInferenceAggregate = function updateCausalInferenceAggregate(cache, key, measurement) {
889
+ var _getLru;
890
+ var aggregate = (_getLru = getLru(cache, key)) !== null && _getLru !== void 0 ? _getLru : createCausalInferenceAggregate(performance.now() - measurement.wallClockMs);
891
+ aggregate.requests++;
892
+ aggregate.requestedCompletionTokens += measurement.requestedCompletionTokens;
893
+ aggregate.wallClockMs += measurement.wallClockMs;
894
+ if (measurement.outcome === 'failed') {
895
+ aggregate.failedRequests++;
896
+ }
897
+ if (measurement.promptTokens !== null) {
898
+ aggregate.promptTokens += measurement.promptTokens;
899
+ aggregate.promptUsageSamples++;
900
+ }
901
+ if (measurement.sampledOutputTokens !== null && measurement.webLlmDecodeSteps !== null) {
902
+ aggregate.sampledOutputTokens += measurement.sampledOutputTokens;
903
+ aggregate.webLlmDecodeSteps += measurement.webLlmDecodeSteps;
904
+ aggregate.webLlmUsageSamples++;
905
+ }
906
+ if (measurement.timeToFirstTokenMs !== null) {
907
+ aggregate.timeToFirstTokenMs += measurement.timeToFirstTokenMs;
908
+ }
909
+ if (measurement.e2eLatencyMs !== null) {
910
+ aggregate.e2eLatencyMs += measurement.e2eLatencyMs;
911
+ }
912
+ setLru(cache, key, aggregate, CANONICAL_SCORING.BOUNDARY_CACHE_MAX);
913
+ return aggregate;
914
+ };
915
+ var causalAggregateForLog = function causalAggregateForLog(aggregate) {
916
+ return {
917
+ calls: aggregate.requests,
918
+ elapsedMs: Number((performance.now() - aggregate.firstStartedAt).toFixed(1)),
919
+ failedCalls: aggregate.failedRequests,
920
+ inferenceWallMs: Number(aggregate.wallClockMs.toFixed(1)),
921
+ promptTokens: aggregate.promptUsageSamples > 0 ? aggregate.promptTokens : 'unreported',
922
+ requestedOutputTokens: aggregate.requestedCompletionTokens,
923
+ sampledOutputTokens: aggregate.webLlmUsageSamples > 0 ? aggregate.sampledOutputTokens : 'unreported',
924
+ timeToFirstTokenMs: aggregate.timeToFirstTokenMs > 0 ? Number(aggregate.timeToFirstTokenMs.toFixed(1)) : 'unreported',
925
+ webLlmE2eMs: aggregate.e2eLatencyMs > 0 ? Number(aggregate.e2eLatencyMs.toFixed(1)) : 'unreported',
926
+ webLlmDecodeSteps: aggregate.webLlmUsageSamples > 0 ? aggregate.webLlmDecodeSteps : 'unreported'
927
+ };
928
+ };
929
+ var recordCausalInference = function recordCausalInference(measurement) {
930
+ var _measurement$promptTo, _measurement$sampledO, _measurement$webLlmDe, _measurement$timeToFi, _measurement$timeToFi2;
931
+ if (!isAutocompleteDebugEnabled()) {
932
+ return;
933
+ }
934
+ var familyAggregate = updateCausalInferenceAggregate(causalInferenceByFamily, measurement.familyKey, measurement);
935
+ var contextAggregate = updateCausalInferenceAggregate(causalInferenceByContext, measurement.contextKey, measurement);
936
+ // One line plus two nested objects per LM call is heavy enough to distort
937
+ // the latencies it reports, so keep the per-call breakdown behind verbose.
938
+ if (!isAutocompleteDebugVerbose()) {
939
+ return;
940
+ }
941
+ // eslint-disable-next-line no-console
942
+ console.log("%c[CTC:model-cost]%c ".concat(measurement.kind, " ").concat(measurement.outcome, " \xB7 ").concat(measurement.wallClockMs.toFixed(1), "ms \xB7 prompt ").concat((_measurement$promptTo = measurement.promptTokens) !== null && _measurement$promptTo !== void 0 ? _measurement$promptTo : '?', " tok/").concat(measurement.promptWords, " words \xB7 output ").concat((_measurement$sampledO = measurement.sampledOutputTokens) !== null && _measurement$sampledO !== void 0 ? _measurement$sampledO : '?', " sampled/").concat(measurement.requestedCompletionTokens, " requested \xB7 decode ").concat((_measurement$webLlmDe = measurement.webLlmDecodeSteps) !== null && _measurement$webLlmDe !== void 0 ? _measurement$webLlmDe : '?', " step").concat(measurement.webLlmDecodeSteps === 1 ? '' : 's', " \xB7 TTFT ").concat((_measurement$timeToFi = (_measurement$timeToFi2 = measurement.timeToFirstTokenMs) === null || _measurement$timeToFi2 === void 0 ? void 0 : _measurement$timeToFi2.toFixed(1)) !== null && _measurement$timeToFi !== void 0 ? _measurement$timeToFi : '?', "ms \xB7 ").concat(measurement.warmState), CTC_STYLES.section, CTC_STYLES.body, {
943
+ request: {
944
+ contextKey: measurement.contextKey,
945
+ familyKey: measurement.familyKey,
946
+ kind: measurement.kind,
947
+ outcome: measurement.outcome,
948
+ warmState: measurement.warmState
949
+ },
950
+ actual: {
951
+ decodeTokensPerSecond: measurement.decodeTokensPerSecond,
952
+ e2eLatencyMs: measurement.e2eLatencyMs,
953
+ prefillTokensPerSecond: measurement.prefillTokensPerSecond,
954
+ promptTokens: measurement.promptTokens,
955
+ promptWords: measurement.promptWords,
956
+ sampledOutputTokens: measurement.sampledOutputTokens,
957
+ timePerDecodeTokenMs: measurement.timePerDecodeTokenMs,
958
+ timeToFirstTokenMs: measurement.timeToFirstTokenMs,
959
+ wallClockMs: measurement.wallClockMs,
960
+ webLlmDecodeSteps: measurement.webLlmDecodeSteps
961
+ },
962
+ context: causalAggregateForLog(contextAggregate),
963
+ family: causalAggregateForLog(familyAggregate)
964
+ });
965
+ };
966
+
967
+ /**
968
+ * Pull the captured distribution out of the processor, timing the handover.
969
+ *
970
+ * Every call yields one array the width of the vocabulary. If that width is
971
+ * large and the handover is slow, thousands of calls per session turn into
972
+ * allocation churn that shows up as latency without any model work behind it.
973
+ */
974
+ var captureLogits = function captureLogits() {
975
+ return causalLogitProcessor.getCapturedLogits();
976
+ };
977
+ var createMeasuredCausalCompletion = /*#__PURE__*/function () {
978
+ var _ref8 = _asyncToGenerator( /*#__PURE__*/_regeneratorRuntime.mark(function _callee5(activeEngine, input) {
979
+ var prompt, promptWords, startedAt, warmState, _usage$completion_tok, _usage$extra$decode_t, _usage$extra, _usage$extra2, _usage$extra$prefill_, _usage$extra3, _usage$prompt_tokens, _usage$extra4, _usage$extra5, completion, latencyMs, usage, webLlmDecodeSteps, sampledOutputTokens, _latencyMs, _t6;
980
+ return _regeneratorRuntime.wrap(function (_context5) {
981
+ while (1) switch (_context5.prev = _context5.next) {
982
+ case 0:
983
+ prompt = truncateToLastNWords(input.prompt, LOCAL_INFERENCE.MAX_CONTEXT_TOKENS);
984
+ promptWords = splitOnWhitespace(prompt).length;
985
+ startedAt = performance.now();
986
+ warmState = ++causalInferenceOrdinal === 1 ? 'cold-first-call' : 'warm';
987
+ _context5.prev = 1;
988
+ _context5.next = 2;
989
+ return activeEngine.completions.create({
990
+ model: modelId,
991
+ prompt: prompt,
992
+ max_tokens: input.requestedCompletionTokens,
993
+ temperature: 0,
994
+ logprobs: false,
995
+ ignore_eos: true
996
+ });
997
+ case 2:
998
+ completion = _context5.sent;
999
+ latencyMs = performance.now() - startedAt;
1000
+ usage = completion.usage;
1001
+ webLlmDecodeSteps = (_usage$completion_tok = usage === null || usage === void 0 ? void 0 : usage.completion_tokens) !== null && _usage$completion_tok !== void 0 ? _usage$completion_tok : null; // WebLLM samples the first output token during prefill, but its
1002
+ // completion_tokens usage counter increments only in decodeStep().
1003
+ // Add that prefill-sampled token back without exceeding max_tokens.
1004
+ sampledOutputTokens = webLlmDecodeSteps === null ? null : Math.min(input.requestedCompletionTokens, webLlmDecodeSteps + 1);
1005
+ recordCausalInference({
1006
+ contextKey: input.contextKey,
1007
+ decodeTokensPerSecond: (_usage$extra$decode_t = usage === null || usage === void 0 || (_usage$extra = usage.extra) === null || _usage$extra === void 0 ? void 0 : _usage$extra.decode_tokens_per_s) !== null && _usage$extra$decode_t !== void 0 ? _usage$extra$decode_t : null,
1008
+ e2eLatencyMs: (usage === null || usage === void 0 || (_usage$extra2 = usage.extra) === null || _usage$extra2 === void 0 ? void 0 : _usage$extra2.e2e_latency_s) !== undefined ? usage.extra.e2e_latency_s * 1000 : null,
1009
+ familyKey: input.familyKey,
1010
+ kind: input.kind,
1011
+ outcome: 'completed',
1012
+ prefillTokensPerSecond: (_usage$extra$prefill_ = usage === null || usage === void 0 || (_usage$extra3 = usage.extra) === null || _usage$extra3 === void 0 ? void 0 : _usage$extra3.prefill_tokens_per_s) !== null && _usage$extra$prefill_ !== void 0 ? _usage$extra$prefill_ : null,
1013
+ promptTokens: (_usage$prompt_tokens = usage === null || usage === void 0 ? void 0 : usage.prompt_tokens) !== null && _usage$prompt_tokens !== void 0 ? _usage$prompt_tokens : null,
1014
+ promptWords: promptWords,
1015
+ requestedCompletionTokens: input.requestedCompletionTokens,
1016
+ sampledOutputTokens: sampledOutputTokens,
1017
+ timePerDecodeTokenMs: (usage === null || usage === void 0 || (_usage$extra4 = usage.extra) === null || _usage$extra4 === void 0 ? void 0 : _usage$extra4.time_per_output_token_s) !== undefined ? usage.extra.time_per_output_token_s * 1000 : null,
1018
+ timeToFirstTokenMs: (usage === null || usage === void 0 || (_usage$extra5 = usage.extra) === null || _usage$extra5 === void 0 ? void 0 : _usage$extra5.time_to_first_token_s) !== undefined ? usage.extra.time_to_first_token_s * 1000 : null,
1019
+ wallClockMs: latencyMs,
1020
+ warmState: warmState,
1021
+ webLlmDecodeSteps: webLlmDecodeSteps
1022
+ });
1023
+ return _context5.abrupt("return", {
1024
+ latencyMs: latencyMs
1025
+ });
1026
+ case 3:
1027
+ _context5.prev = 3;
1028
+ _t6 = _context5["catch"](1);
1029
+ _latencyMs = performance.now() - startedAt;
1030
+ recordCausalInference({
1031
+ contextKey: input.contextKey,
1032
+ decodeTokensPerSecond: null,
1033
+ e2eLatencyMs: null,
1034
+ familyKey: input.familyKey,
1035
+ kind: input.kind,
1036
+ outcome: 'failed',
1037
+ prefillTokensPerSecond: null,
1038
+ promptTokens: null,
1039
+ promptWords: promptWords,
1040
+ requestedCompletionTokens: input.requestedCompletionTokens,
1041
+ sampledOutputTokens: null,
1042
+ timePerDecodeTokenMs: null,
1043
+ timeToFirstTokenMs: null,
1044
+ wallClockMs: _latencyMs,
1045
+ warmState: warmState,
1046
+ webLlmDecodeSteps: null
1047
+ });
1048
+ throw _t6;
1049
+ case 4:
1050
+ case "end":
1051
+ return _context5.stop();
1052
+ }
1053
+ }, _callee5, null, [[1, 3]]);
1054
+ }));
1055
+ return function createMeasuredCausalCompletion(_x3, _x4) {
1056
+ return _ref8.apply(this, arguments);
1057
+ };
1058
+ }();
1059
+ var logExactEvidenceReadiness = function logExactEvidenceReadiness(contextKey, familyKey, exactSurfaceCount, source) {
1060
+ var _exactEvidenceCountBy;
1061
+ if (!isAutocompleteDebugEnabled()) {
1062
+ return;
1063
+ }
1064
+ var previousCount = (_exactEvidenceCountBy = exactEvidenceCountByContext.get(contextKey)) !== null && _exactEvidenceCountBy !== void 0 ? _exactEvidenceCountBy : 0;
1065
+ if (exactSurfaceCount <= previousCount) {
1066
+ return;
1067
+ }
1068
+ setLru(exactEvidenceCountByContext, contextKey, exactSurfaceCount, CANONICAL_SCORING.BOUNDARY_CACHE_MAX);
1069
+ var contextAggregate = getLru(causalInferenceByContext, contextKey);
1070
+ var familyAggregate = getLru(causalInferenceByFamily, familyKey);
1071
+ // eslint-disable-next-line no-console
1072
+ console.log("%c[CTC:readiness]%c exact evidence \xB7 ".concat(exactSurfaceCount, " surface").concat(exactSurfaceCount === 1 ? '' : 's', " \xB7 source=").concat(source, " \xB7 ctx=").concat(contextKey.slice(0, 64)), CTC_STYLES.good, CTC_STYLES.body, {
1073
+ context: contextAggregate ? causalAggregateForLog(contextAggregate) : null,
1074
+ contextKey: contextKey,
1075
+ exactSurfaceCount: exactSurfaceCount,
1076
+ family: familyAggregate ? causalAggregateForLog(familyAggregate) : null,
1077
+ familyKey: familyKey,
1078
+ source: source
1079
+ });
1080
+ };
1081
+ var getProgressiveEvidence = function getProgressiveEvidence(contextKey, candidateSurface) {
1082
+ var _surfaceTokenTrie$get;
1083
+ var tokenIds = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : (_surfaceTokenTrie$get = surfaceTokenTrie.getTokenIds(candidateSurface)) !== null && _surfaceTokenTrie$get !== void 0 ? _surfaceTokenTrie$get : [];
1084
+ if (tokenIds.length === 0) {
1085
+ return null;
1086
+ }
1087
+ var exactKey = surfaceCacheKey(contextKey, candidateSurface, tokenIds);
1088
+ var exact = getLru(surfaceScoreCache, exactKey);
1089
+ if (exact) {
1090
+ return {
1091
+ meanTokenLogProbabilityUpperBound: exact.meanTokenLogProbability,
1092
+ scoredTokenCount: exact.tokenCount,
1093
+ totalLogProbability: exact.totalLogProbability,
1094
+ totalTokenCount: exact.tokenCount
1095
+ };
1096
+ }
1097
+ var boundary = getLru(boundaryCache, contextKey);
1098
+ var firstToken = tokenIds[0];
1099
+ if (!boundary || firstToken === undefined) {
1100
+ return null;
1101
+ }
1102
+ var totalLogProbability = logSoftmaxAt(boundary.rawLogits, firstToken);
1103
+ if (!Number.isFinite(totalLogProbability)) {
1104
+ return null;
1105
+ }
1106
+ var scoredTokenCount = 1;
1107
+ while (scoredTokenCount < tokenIds.length) {
1108
+ var prefix = tokenIds.slice(0, scoredTokenCount);
1109
+ var expansion = getLru(prefixExpansionCache, prefixCacheKey(contextKey, prefix));
1110
+ if (!expansion) {
1111
+ break;
1112
+ }
1113
+ var nextTokenLogProbability = logSoftmaxAt(expansion.rawNextTokenLogits, tokenIds[scoredTokenCount]);
1114
+ if (!Number.isFinite(nextTokenLogProbability)) {
1115
+ break;
1116
+ }
1117
+ totalLogProbability = expansion.totalLogProbability + nextTokenLogProbability;
1118
+ scoredTokenCount++;
1119
+ }
1120
+ if (scoredTokenCount === tokenIds.length) {
1121
+ var exactScore = {
1122
+ contextKey: contextKey,
1123
+ surface: candidateSurface,
1124
+ totalLogProbability: totalLogProbability,
1125
+ meanTokenLogProbability: totalLogProbability / tokenIds.length,
1126
+ tokenCount: tokenIds.length
1127
+ };
1128
+ setLru(surfaceScoreCache, exactKey, exactScore, CANONICAL_SCORING.SURFACE_CACHE_MAX);
1129
+ }
1130
+ return {
1131
+ // Every unscored future token has log probability <= 0. Dividing the
1132
+ // scored total by the final token count is therefore a safe optimistic
1133
+ // bound on the eventual mean.
1134
+ meanTokenLogProbabilityUpperBound: totalLogProbability / tokenIds.length,
1135
+ scoredTokenCount: scoredTokenCount,
1136
+ totalLogProbability: totalLogProbability,
1137
+ totalTokenCount: tokenIds.length
1138
+ };
1139
+ };
1140
+ var _planProgressiveExpansion = function planProgressiveExpansion(contextKey) {
1141
+ var _progressivePrefixesB;
1142
+ var request = progressiveRequests.get(contextKey);
1143
+ if (!request || destroyed || !boundaryCache.has(contextKey)) {
1144
+ return;
1145
+ }
1146
+ var eligible = request.candidates.filter(function (candidate) {
1147
+ return candidate.tokenIds.length > 0 && candidate.tokenIds.length <= CANONICAL_SCORING.EXACT_MAX_TARGET_TOKENS;
1148
+ }).map(function (candidate) {
1149
+ return _objectSpread(_objectSpread({}, candidate), {}, {
1150
+ evidence: getProgressiveEvidence(contextKey, candidate.surface, candidate.tokenIds)
1151
+ });
1152
+ }).filter(function (candidate) {
1153
+ return candidate.evidence !== null;
1154
+ });
1155
+
1156
+ // Score candidates the way arbitration will: a posterior over sequence
1157
+ // log-likelihoods, blended with the corpus prior at the shipped weights.
1158
+ //
1159
+ // Sharing the rule is the point. Ranking on `exp(per-token mean)` instead
1160
+ // answers a different question — it favours short surfaces, because
1161
+ // dividing by fewer tokens flatters them — so this scheduler used to stop
1162
+ // reading on a margin the decision layer did not recognise, and then
1163
+ // abstain for want of the very tokens it declined to read. Nothing about
1164
+ // that was visible from either side.
1165
+ //
1166
+ // The normaliser spans every eligible candidate rather than only the
1167
+ // scheduled ones. That can only make each posterior smaller and each margin
1168
+ // narrower, so the error is always towards reading another token instead of
1169
+ // stopping early — the safe direction ahead of a precision-first gate.
1170
+ var normalizer = logSumExp(eligible.map(function (candidate) {
1171
+ return candidate.evidence.totalLogProbability;
1172
+ }));
1173
+ var confidenceScore = function confidenceScore(candidate) {
1174
+ var _candidate$rankHint;
1175
+ return STAGE1_WEIGHT * ((_candidate$rankHint = candidate.rankHint) !== null && _candidate$rankHint !== void 0 ? _candidate$rankHint : 0) + STAGE2_WEIGHT * (Number.isFinite(normalizer) ? Math.exp(candidate.evidence.totalLogProbability - normalizer) : 0);
1176
+ };
1177
+ var candidates = eligible.sort(function (a, b) {
1178
+ return confidenceScore(b) - confidenceScore(a);
1179
+ }).slice(0, CANONICAL_SCORING.PROGRESSIVE_CANDIDATES_MAX);
1180
+ var unresolved = candidates.filter(function (candidate) {
1181
+ return candidate.evidence.scoredTokenCount < candidate.tokenIds.length;
1182
+ });
1183
+ if (unresolved.length === 0) {
1184
+ logProgressiveState('completed', "ctx=".concat(contextKey.slice(0, 48), " \xB7 exact=").concat(candidates.length, "/").concat(candidates.length));
1185
+ return;
1186
+ }
1187
+ var bestExactScore = Math.max.apply(Math, _toConsumableArray(candidates.filter(function (candidate) {
1188
+ return candidate.evidence.scoredTokenCount === candidate.tokenIds.length;
1189
+ }).map(confidenceScore)).concat([-Infinity]));
1190
+ var bestUnresolvedScore = Math.max.apply(Math, _toConsumableArray(unresolved.map(confidenceScore)).concat([-Infinity]));
1191
+ if (bestExactScore - bestUnresolvedScore >= MIN_WINNER_MARGIN) {
1192
+ logProgressiveState('completed', "safe bound \xB7 ctx=".concat(contextKey.slice(0, 40), " \xB7 margin=").concat((bestExactScore - bestUnresolvedScore).toFixed(2), " \xB7 unresolved=").concat(unresolved.length));
1193
+ return;
1194
+ }
1195
+ var groups = surfaceTokenTrie.groupByScoredPrefix(unresolved.map(function (candidate) {
1196
+ return {
1197
+ surface: candidate.surface,
1198
+ tokenIds: candidate.tokenIds,
1199
+ scoredTokenCount: candidate.evidence.scoredTokenCount
1200
+ };
1201
+ }));
1202
+ var bySurface = new Map(candidates.map(function (candidate) {
1203
+ return [candidate.surface, candidate];
1204
+ }));
1205
+ var rankedGroups = groups.map(function (group) {
1206
+ var members = group.surfaces.map(function (candidateSurface) {
1207
+ return bySurface.get(candidateSurface);
1208
+ }).filter(function (candidate) {
1209
+ return Boolean(candidate);
1210
+ });
1211
+ var optimisticScore = Math.max.apply(Math, _toConsumableArray(members.map(confidenceScore)));
1212
+ return _objectSpread(_objectSpread({}, group), {}, {
1213
+ optimisticScore: optimisticScore
1214
+ });
1215
+ }).sort(function (a, b) {
1216
+ return b.optimisticScore - a.optimisticScore;
1217
+ });
1218
+ var bestGroup = rankedGroups[0];
1219
+ if (!bestGroup) {
1220
+ return;
1221
+ }
1222
+ var warmGroup = kvContextKey === contextKey ? rankedGroups.find(function (group) {
1223
+ return isPrefixOf(kvPath, group.tokenPrefix);
1224
+ }) : undefined;
1225
+ var nextGroup = warmGroup && warmGroup.optimisticScore >= bestGroup.optimisticScore - PROGRESSIVE_WARM_PATH_MARGIN ? warmGroup : bestGroup;
1226
+ var contextPrefixes = (_progressivePrefixesB = progressivePrefixesByContext.get(contextKey)) !== null && _progressivePrefixesB !== void 0 ? _progressivePrefixesB : new Set();
1227
+ progressivePrefixesByContext.set(contextKey, contextPrefixes);
1228
+ var key = prefixCacheKey(contextKey, nextGroup.tokenPrefix);
1229
+ if (getLru(prefixExpansionCache, key)) {
1230
+ logProgressiveState('cached', "ctx=".concat(contextKey.slice(0, 40), " \xB7 prefix=[").concat(nextGroup.tokenPrefix.join(','), "]"));
1231
+ _planProgressiveExpansion(contextKey);
1232
+ return;
1233
+ }
1234
+ if (pendingPrefixExpansions.has(key) || inFlightPrefixExpansions.has(key)) {
1235
+ logProgressiveState('deduplicated', "ctx=".concat(contextKey.slice(0, 40), " \xB7 prefix=[").concat(nextGroup.tokenPrefix.join(','), "]"));
1236
+ return;
1237
+ }
1238
+ if (contextPrefixes.size >= CANONICAL_SCORING.PROGRESSIVE_EXPANSIONS_PER_CONTEXT_MAX) {
1239
+ logProgressiveState('completed', "ctx=".concat(contextKey.slice(0, 48), " \xB7 expansion cap=").concat(contextPrefixes.size, " \xB7 unresolved=").concat(unresolved.length));
1240
+ return;
1241
+ }
1242
+ contextPrefixes.add(key);
1243
+ pendingPrefixExpansions.set(key, {
1244
+ contextKey: contextKey,
1245
+ familyKey: request.familyKey,
1246
+ prompt: request.prompt,
1247
+ tokenPrefix: nextGroup.tokenPrefix,
1248
+ surfaces: nextGroup.surfaces
1249
+ });
1250
+ logProgressiveState('queued', "ctx=".concat(contextKey.slice(0, 40), " \xB7 prefix=[").concat(nextGroup.tokenPrefix.join(','), "] \xB7 surfaces=").concat(nextGroup.surfaces.length));
1251
+ _drainCausalQueue();
1252
+ };
1253
+
1254
+ /**
1255
+ * Prefill `prompt` and cache the boundary distribution that follows it.
1256
+ *
1257
+ * A text completion resets the KV cache before prefilling, so asking for a
1258
+ * single token leaves the cache holding exactly the prompt. That is the
1259
+ * anchor every later decode step extends.
1260
+ */
1261
+ var prefillBoundary = /*#__PURE__*/function () {
1262
+ var _ref9 = _asyncToGenerator( /*#__PURE__*/_regeneratorRuntime.mark(function _callee6(activeEngine, request) {
1263
+ var _yield$createMeasured, latencyMs, rawLogits;
1264
+ return _regeneratorRuntime.wrap(function (_context6) {
1265
+ while (1) switch (_context6.prev = _context6.next) {
1266
+ case 0:
1267
+ kvContextKey = null;
1268
+ kvPath = [];
1269
+ causalRequestsThisFamily++;
1270
+ causalGeneratedTokensThisFamily++;
1271
+ causalLogitProcessor.startCapture();
1272
+ _context6.next = 1;
1273
+ return createMeasuredCausalCompletion(activeEngine, {
1274
+ contextKey: request.contextKey,
1275
+ familyKey: request.familyKey,
1276
+ kind: 'boundary',
1277
+ prompt: request.prompt,
1278
+ requestedCompletionTokens: 1
1279
+ });
1280
+ case 1:
1281
+ _yield$createMeasured = _context6.sent;
1282
+ latencyMs = _yield$createMeasured.latencyMs;
1283
+ rawLogits = captureLogits();
1284
+ if (rawLogits) {
1285
+ _context6.next = 2;
1286
+ break;
1287
+ }
1288
+ return _context6.abrupt("return", null);
1289
+ case 2:
1290
+ setLru(boundaryCache, request.contextKey, {
1291
+ contextKey: request.contextKey,
1292
+ prompt: request.prompt,
1293
+ rawLogits: rawLogits
1294
+ }, CANONICAL_SCORING.BOUNDARY_CACHE_MAX);
1295
+ kvContextKey = request.contextKey;
1296
+ kvPath = [];
1297
+ return _context6.abrupt("return", {
1298
+ latencyMs: latencyMs,
1299
+ rawLogits: rawLogits
1300
+ });
1301
+ case 3:
1302
+ case "end":
1303
+ return _context6.stop();
1304
+ }
1305
+ }, _callee6);
1306
+ }));
1307
+ return function prefillBoundary(_x5, _x6) {
1308
+ return _ref9.apply(this, arguments);
1309
+ };
1310
+ }();
1311
+
1312
+ /**
1313
+ * Append one token to the KV cache and return the distribution that follows.
1314
+ *
1315
+ * This deliberately bypasses the completion API: a completion would reset the
1316
+ * cache and re-prefill the whole prompt, whereas this forwards a single token
1317
+ * on top of the work already done.
1318
+ */
1319
+ var decodeOneToken = /*#__PURE__*/function () {
1320
+ var _ref0 = _asyncToGenerator( /*#__PURE__*/_regeneratorRuntime.mark(function _callee7(activeEngine, token) {
1321
+ var _t7;
1322
+ return _regeneratorRuntime.wrap(function (_context7) {
1323
+ while (1) switch (_context7.prev = _context7.next) {
1324
+ case 0:
1325
+ causalLogitProcessor.startCapture();
1326
+ _context7.prev = 1;
1327
+ _context7.next = 2;
1328
+ return activeEngine.forwardTokensAndSample([token], false, modelId);
1329
+ case 2:
1330
+ _context7.next = 4;
1331
+ break;
1332
+ case 3:
1333
+ _context7.prev = 3;
1334
+ _t7 = _context7["catch"](1);
1335
+ kvContextKey = null;
1336
+ kvPath = [];
1337
+ throw _t7;
1338
+ case 4:
1339
+ causalRequestsThisFamily++;
1340
+ causalGeneratedTokensThisFamily++;
1341
+ return _context7.abrupt("return", captureLogits());
1342
+ case 5:
1343
+ case "end":
1344
+ return _context7.stop();
1345
+ }
1346
+ }, _callee7, null, [[1, 3]]);
1347
+ }));
1348
+ return function decodeOneToken(_x7, _x8) {
1349
+ return _ref0.apply(this, arguments);
1350
+ };
1351
+ }();
1352
+ var runBoundaryPrime = /*#__PURE__*/function () {
1353
+ var _ref1 = _asyncToGenerator( /*#__PURE__*/_regeneratorRuntime.mark(function _callee8(request) {
1354
+ var _progressiveRequest$c, _progressiveRequest$f, _progressiveRequests$, _progressiveRequests$2;
1355
+ var primed, latencyMs, rawLogits, progressiveRequest, exactSurfaceCount, callbackFamilyKey;
1356
+ return _regeneratorRuntime.wrap(function (_context8) {
1357
+ while (1) switch (_context8.prev = _context8.next) {
1358
+ case 0:
1359
+ if (!(!engine || destroyed)) {
1360
+ _context8.next = 1;
1361
+ break;
1362
+ }
1363
+ return _context8.abrupt("return");
1364
+ case 1:
1365
+ _context8.next = 2;
1366
+ return prefillBoundary(engine, request);
1367
+ case 2:
1368
+ primed = _context8.sent;
1369
+ if (primed) {
1370
+ _context8.next = 3;
1371
+ break;
1372
+ }
1373
+ return _context8.abrupt("return");
1374
+ case 3:
1375
+ latencyMs = primed.latencyMs, rawLogits = primed.rawLogits;
1376
+ _planProgressiveExpansion(request.contextKey);
1377
+ progressiveRequest = progressiveRequests.get(request.contextKey);
1378
+ exactSurfaceCount = (_progressiveRequest$c = progressiveRequest === null || progressiveRequest === void 0 ? void 0 : progressiveRequest.candidates.filter(function (candidate) {
1379
+ var evidence = getProgressiveEvidence(request.contextKey, candidate.surface, candidate.tokenIds);
1380
+ return evidence !== null && evidence.scoredTokenCount === evidence.totalTokenCount;
1381
+ }).length) !== null && _progressiveRequest$c !== void 0 ? _progressiveRequest$c : 0;
1382
+ logExactEvidenceReadiness(request.contextKey, (_progressiveRequest$f = progressiveRequest === null || progressiveRequest === void 0 ? void 0 : progressiveRequest.familyKey) !== null && _progressiveRequest$f !== void 0 ? _progressiveRequest$f : request.familyKey, exactSurfaceCount, 'boundary');
1383
+ if (isAutocompleteDebugEnabled()) {
1384
+ // eslint-disable-next-line no-console
1385
+ console.log("%c[CTC:model] %c\uD83E\uDDE0 Tier A prime #".concat(request.priority + 1, " \xB7 ctx=").concat(request.contextKey.slice(0, 48), " \xB7 ").concat(rawLogits.length, " logits \xB7 ").concat(latencyMs.toFixed(0), "ms \xB7 family req:").concat(causalRequestsThisFamily, " tok:").concat(causalGeneratedTokensThisFamily, " A hit/miss:").concat(tierAPrimeCacheHitsThisFamily, "/").concat(tierAPrimeCacheMissesThisFamily), 'color: #9c27b0; font-weight: bold;', 'color: #2196f3;');
1386
+ }
1387
+ callbackFamilyKey = (_progressiveRequests$ = (_progressiveRequests$2 = progressiveRequests.get(request.contextKey)) === null || _progressiveRequests$2 === void 0 ? void 0 : _progressiveRequests$2.familyKey) !== null && _progressiveRequests$ !== void 0 ? _progressiveRequests$ : request.familyKey;
1388
+ if (callbackFamilyKey === latestCausalFamilyKey && !destroyed) {
1389
+ onBoundaryLmUpdate === null || onBoundaryLmUpdate === void 0 || onBoundaryLmUpdate({
1390
+ contextKey: request.contextKey,
1391
+ familyKey: callbackFamilyKey,
1392
+ latencyMs: latencyMs
1393
+ });
1394
+ }
1395
+ case 4:
1396
+ case "end":
1397
+ return _context8.stop();
1398
+ }
1399
+ }, _callee8);
1400
+ }));
1401
+ return function runBoundaryPrime(_x9) {
1061
1402
  return _ref1.apply(this, arguments);
1062
1403
  };
1063
1404
  }();
1405
+ var runPrefixExpansion = /*#__PURE__*/function () {
1406
+ var _ref10 = _asyncToGenerator( /*#__PURE__*/_regeneratorRuntime.mark(function _callee9(request) {
1407
+ var _progressivePrefixesB2, _progressiveRequest$f2, _progressiveRequests$3, _progressiveRequests$4;
1408
+ var activeEngine, startedAt, logitsAtPath, totalAtPath, pathIsLive, _getLru$rawLogits, _getLru2, _resume$rawNextTokenL, _resume$totalLogProba, resume, primed, contextPrefixes, index, token, stepLogProbability, nextLogits, stepKey, expansion, latencyMs, progressiveRequest, newlyExact, exactSurfaceCount, _iterator, _step, candidate, exactKey, wasExact, evidence, callbackFamilyKey;
1409
+ return _regeneratorRuntime.wrap(function (_context9) {
1410
+ while (1) switch (_context9.prev = _context9.next) {
1411
+ case 0:
1412
+ if (!(!engine || destroyed)) {
1413
+ _context9.next = 1;
1414
+ break;
1415
+ }
1416
+ return _context9.abrupt("return");
1417
+ case 1:
1418
+ activeEngine = engine;
1419
+ startedAt = performance.now();
1420
+ logProgressiveState('started', "ctx=".concat(request.contextKey.slice(0, 40), " \xB7 prefix=[").concat(request.tokenPrefix.join(','), "] \xB7 surfaces=").concat(request.surfaces.length));
1421
+
1422
+ // Resume from whatever the KV cache already holds for this context, and
1423
+ // fall back to a prompt prefill only when the requested prefix branches
1424
+ // away from it.
1425
+ logitsAtPath = null;
1426
+ totalAtPath = 0;
1427
+ pathIsLive = extendsLiveKvPath(request);
1428
+ if (pathIsLive) {
1429
+ if (kvPath.length === 0) {
1430
+ logitsAtPath = (_getLru$rawLogits = (_getLru2 = getLru(boundaryCache, request.contextKey)) === null || _getLru2 === void 0 ? void 0 : _getLru2.rawLogits) !== null && _getLru$rawLogits !== void 0 ? _getLru$rawLogits : null;
1431
+ } else {
1432
+ resume = getLru(prefixExpansionCache, prefixCacheKey(request.contextKey, kvPath));
1433
+ logitsAtPath = (_resume$rawNextTokenL = resume === null || resume === void 0 ? void 0 : resume.rawNextTokenLogits) !== null && _resume$rawNextTokenL !== void 0 ? _resume$rawNextTokenL : null;
1434
+ totalAtPath = (_resume$totalLogProba = resume === null || resume === void 0 ? void 0 : resume.totalLogProbability) !== null && _resume$totalLogProba !== void 0 ? _resume$totalLogProba : 0;
1435
+ }
1436
+ }
1437
+ if (logitsAtPath) {
1438
+ _context9.next = 4;
1439
+ break;
1440
+ }
1441
+ _context9.next = 2;
1442
+ return prefillBoundary(activeEngine, request);
1443
+ case 2:
1444
+ primed = _context9.sent;
1445
+ if (primed) {
1446
+ _context9.next = 3;
1447
+ break;
1448
+ }
1449
+ logProgressiveState('failed', "ctx=".concat(request.contextKey.slice(0, 40), " \xB7 prefix=[").concat(request.tokenPrefix.join(','), "] \xB7 missing boundary logits"));
1450
+ return _context9.abrupt("return");
1451
+ case 3:
1452
+ logitsAtPath = primed.rawLogits;
1453
+ totalAtPath = 0;
1454
+ case 4:
1455
+ // Walking the path caches every depth along it, not just the requested
1456
+ // one, so a later expansion that shares this prefix costs nothing.
1457
+ contextPrefixes = (_progressivePrefixesB2 = progressivePrefixesByContext.get(request.contextKey)) !== null && _progressivePrefixesB2 !== void 0 ? _progressivePrefixesB2 : new Set();
1458
+ progressivePrefixesByContext.set(request.contextKey, contextPrefixes);
1459
+ index = kvPath.length;
1460
+ case 5:
1461
+ if (!(index < request.tokenPrefix.length)) {
1462
+ _context9.next = 11;
1463
+ break;
1464
+ }
1465
+ token = request.tokenPrefix[index];
1466
+ if (!(token === undefined)) {
1467
+ _context9.next = 6;
1468
+ break;
1469
+ }
1470
+ return _context9.abrupt("continue", 11);
1471
+ case 6:
1472
+ stepLogProbability = logSoftmaxAt(logitsAtPath, token);
1473
+ if (Number.isFinite(stepLogProbability)) {
1474
+ _context9.next = 7;
1475
+ break;
1476
+ }
1477
+ logProgressiveState('failed', "ctx=".concat(request.contextKey.slice(0, 40), " \xB7 prefix=[").concat(request.tokenPrefix.join(','), "] \xB7 token ").concat(token, " unscoreable"));
1478
+ return _context9.abrupt("return");
1479
+ case 7:
1480
+ _context9.next = 8;
1481
+ return decodeOneToken(activeEngine, token);
1482
+ case 8:
1483
+ nextLogits = _context9.sent;
1484
+ if (!(!nextLogits || destroyed)) {
1485
+ _context9.next = 9;
1486
+ break;
1487
+ }
1488
+ kvContextKey = null;
1489
+ kvPath = [];
1490
+ logProgressiveState('failed', "ctx=".concat(request.contextKey.slice(0, 40), " \xB7 prefix=[").concat(request.tokenPrefix.join(','), "] \xB7 missing logits"));
1491
+ return _context9.abrupt("return");
1492
+ case 9:
1493
+ kvContextKey = request.contextKey;
1494
+ kvPath = [].concat(_toConsumableArray(kvPath), [token]);
1495
+ totalAtPath += stepLogProbability;
1496
+ logitsAtPath = nextLogits;
1497
+ stepKey = prefixCacheKey(request.contextKey, kvPath);
1498
+ expansion = {
1499
+ contextKey: request.contextKey,
1500
+ tokenPrefix: _toConsumableArray(kvPath),
1501
+ totalLogProbability: totalAtPath,
1502
+ rawNextTokenLogits: nextLogits
1503
+ };
1504
+ setLru(prefixExpansionCache, stepKey, expansion, CANONICAL_SCORING.PREFIX_CACHE_MAX);
1505
+ contextPrefixes.add(stepKey);
1506
+ case 10:
1507
+ index++;
1508
+ _context9.next = 5;
1509
+ break;
1510
+ case 11:
1511
+ latencyMs = performance.now() - startedAt;
1512
+ progressiveRequest = progressiveRequests.get(request.contextKey);
1513
+ newlyExact = 0;
1514
+ exactSurfaceCount = 0;
1515
+ if (progressiveRequest) {
1516
+ _iterator = _createForOfIteratorHelper(progressiveRequest.candidates);
1517
+ try {
1518
+ for (_iterator.s(); !(_step = _iterator.n()).done;) {
1519
+ candidate = _step.value;
1520
+ exactKey = surfaceCacheKey(request.contextKey, candidate.surface, candidate.tokenIds);
1521
+ wasExact = surfaceScoreCache.has(exactKey);
1522
+ evidence = getProgressiveEvidence(request.contextKey, candidate.surface, candidate.tokenIds);
1523
+ if (!wasExact && evidence !== null && evidence.scoredTokenCount === evidence.totalTokenCount && surfaceScoreCache.has(exactKey)) {
1524
+ newlyExact++;
1525
+ }
1526
+ if (evidence !== null && evidence.scoredTokenCount === evidence.totalTokenCount) {
1527
+ exactSurfaceCount++;
1528
+ }
1529
+ }
1530
+ } catch (err) {
1531
+ _iterator.e(err);
1532
+ } finally {
1533
+ _iterator.f();
1534
+ }
1535
+ }
1536
+ logExactEvidenceReadiness(request.contextKey, (_progressiveRequest$f2 = progressiveRequest === null || progressiveRequest === void 0 ? void 0 : progressiveRequest.familyKey) !== null && _progressiveRequest$f2 !== void 0 ? _progressiveRequest$f2 : request.familyKey, exactSurfaceCount, 'prefix');
1537
+ logProgressiveState('expanded', "ctx=".concat(request.contextKey.slice(0, 40), " \xB7 prefix=[").concat(request.tokenPrefix.join(','), "] \xB7 exact+").concat(newlyExact, " \xB7 ").concat(latencyMs.toFixed(0), "ms"));
1538
+ _planProgressiveExpansion(request.contextKey);
1539
+ callbackFamilyKey = (_progressiveRequests$3 = (_progressiveRequests$4 = progressiveRequests.get(request.contextKey)) === null || _progressiveRequests$4 === void 0 ? void 0 : _progressiveRequests$4.familyKey) !== null && _progressiveRequests$3 !== void 0 ? _progressiveRequests$3 : request.familyKey;
1540
+ if (callbackFamilyKey === latestCausalFamilyKey && !destroyed) {
1541
+ onSurfaceScoreUpdate === null || onSurfaceScoreUpdate === void 0 || onSurfaceScoreUpdate({
1542
+ contextKey: request.contextKey,
1543
+ count: newlyExact,
1544
+ familyKey: callbackFamilyKey,
1545
+ latencyMs: latencyMs
1546
+ });
1547
+ } else {
1548
+ logProgressiveState('stale', "cached only \xB7 ctx=".concat(request.contextKey.slice(0, 40), " \xB7 family=").concat(request.familyKey.slice(0, 32)));
1549
+ }
1550
+ case 12:
1551
+ case "end":
1552
+ return _context9.stop();
1553
+ }
1554
+ }, _callee9);
1555
+ }));
1556
+ return function runPrefixExpansion(_x0) {
1557
+ return _ref10.apply(this, arguments);
1558
+ };
1559
+ }();
1560
+
1561
+ /**
1562
+ * Whether any live decision would still take this request's result.
1563
+ *
1564
+ * Ranking on the family a request was created under, as this used to, misses
1565
+ * in both directions. Work queued a keystroke ago for a context still under
1566
+ * the cursor sorts as stale even though the callbacks resolve delivery
1567
+ * through `progressiveRequests` and would hand it over. Work for a context
1568
+ * nothing asks about any more sorts as runnable even though the same
1569
+ * resolution drops it on arrival — and that one is expensive, because an
1570
+ * abandoned context is never the one the KV cache holds, so running it pays
1571
+ * a prompt prefill and leaves the live context evicted, charging the next
1572
+ * live request a second prefill. Two prefills for a discarded result.
1573
+ *
1574
+ * Either signal alone is enough to keep the work, which matters because a
1575
+ * boundary prime is queued before its context's surfaces are requested: at
1576
+ * that moment the stored family is still the previous decision's, and only
1577
+ * the request's own family says it is current.
1578
+ *
1579
+ * Agreeing with the layer being fed is the correction the expansion planner
1580
+ * already carries for arbitration's scoring rule — a scheduler deciding on
1581
+ * its own rule stops on margins the consumer does not recognise.
1582
+ */
1583
+ var stillWanted = function stillWanted(request) {
1584
+ var _progressiveRequests$5;
1585
+ return request.familyKey === latestCausalFamilyKey || ((_progressiveRequests$5 = progressiveRequests.get(request.contextKey)) === null || _progressiveRequests$5 === void 0 ? void 0 : _progressiveRequests$5.familyKey) === latestCausalFamilyKey;
1586
+ };
1587
+ var _drainCausalQueue = function drainCausalQueue() {
1588
+ if (causalInFlight || destroyed) {
1589
+ return;
1590
+ }
1591
+ // Drop abandoned work rather than leaving it to be picked up whenever the
1592
+ // live family happens to have nothing queued. Its cached side effects are
1593
+ // speculative — they only pay off if the user deletes back into exactly
1594
+ // this context and prefix — and the prefill pair above is certain.
1595
+ var _iterator2 = _createForOfIteratorHelper(pendingPrefixExpansions),
1596
+ _step2;
1597
+ try {
1598
+ for (_iterator2.s(); !(_step2 = _iterator2.n()).done;) {
1599
+ var _step2$value = _slicedToArray(_step2.value, 2),
1600
+ key = _step2$value[0],
1601
+ request = _step2$value[1];
1602
+ if (!stillWanted(request)) {
1603
+ pendingPrefixExpansions.delete(key);
1604
+ }
1605
+ }
1606
+ } catch (err) {
1607
+ _iterator2.e(err);
1608
+ } finally {
1609
+ _iterator2.f();
1610
+ }
1611
+ var _iterator3 = _createForOfIteratorHelper(pendingBoundaryPrimes),
1612
+ _step3;
1613
+ try {
1614
+ for (_iterator3.s(); !(_step3 = _iterator3.n()).done;) {
1615
+ var _step3$value = _slicedToArray(_step3.value, 2),
1616
+ contextKey = _step3$value[0],
1617
+ _request = _step3$value[1];
1618
+ if (!stillWanted(_request)) {
1619
+ pendingBoundaryPrimes.delete(contextKey);
1620
+ }
1621
+ }
1622
+ } catch (err) {
1623
+ _iterator3.e(err);
1624
+ } finally {
1625
+ _iterator3.f();
1626
+ }
1627
+ var sortedPrimes = Array.from(pendingBoundaryPrimes.values()).sort(function (a, b) {
1628
+ return a.priority - b.priority;
1629
+ });
1630
+ // Everything left is wanted, so relevance no longer needs a sort key.
1631
+ // Take whichever request continues the live sequence: that expansion costs
1632
+ // one decode step where any other costs a full prompt prefill, and nothing
1633
+ // is skipped, only reordered.
1634
+ //
1635
+ // Staying inside the live context when nothing continues it was tried and
1636
+ // reverted. It moved re-primes from `reprime-context` to `reprime-branch`
1637
+ // and left the total flat, because both pay for a prompt prefill: a
1638
+ // context whose remaining prefixes diverge at the first token re-primes as
1639
+ // a branch instead of as a context. Ordering cannot recover that; only
1640
+ // queueing fewer divergent branches can, which is what the prune above
1641
+ // does.
1642
+ var sortedPrefixes = Array.from(pendingPrefixExpansions.values()).sort(function (a, b) {
1643
+ return Number(extendsLiveKvPath(b)) - Number(extendsLiveKvPath(a));
1644
+ });
1645
+ var nextPrefix = sortedPrefixes[0];
1646
+ var nextPrime = nextPrefix ? undefined : sortedPrimes[0];
1647
+ if (!nextPrefix && !nextPrime) {
1648
+ return;
1649
+ }
1650
+ causalInFlight = true;
1651
+ var prefixKey = null;
1652
+ if (nextPrefix) {
1653
+ prefixKey = prefixCacheKey(nextPrefix.contextKey, nextPrefix.tokenPrefix);
1654
+ pendingPrefixExpansions.delete(prefixKey);
1655
+ inFlightPrefixExpansions.add(prefixKey);
1656
+ } else if (nextPrime) {
1657
+ pendingBoundaryPrimes.delete(nextPrime.contextKey);
1658
+ inFlightBoundaryContextKey = nextPrime.contextKey;
1659
+ }
1660
+ void ensureEngineInitialized().then(function () {
1661
+ return nextPrefix ? runPrefixExpansion(nextPrefix) : nextPrime ? runBoundaryPrime(nextPrime) : undefined;
1662
+ }).catch(function (error) {
1663
+ if (nextPrefix) {
1664
+ logProgressiveState('failed', "ctx=".concat(nextPrefix.contextKey.slice(0, 40), " \xB7 prefix=[").concat(nextPrefix.tokenPrefix.join(','), "] \xB7 ").concat(error instanceof Error ? error.message : String(error)));
1665
+ }
1666
+ if (isAutocompleteDebugEnabled()) {
1667
+ // eslint-disable-next-line no-console
1668
+ console.log("%c[CTC:model] %c\u274C canonical causal request failed: ".concat(error instanceof Error ? error.message : String(error)), 'color: #9c27b0; font-weight: bold;', 'color: #f44336;');
1669
+ }
1670
+ }).finally(function () {
1671
+ if (prefixKey) {
1672
+ inFlightPrefixExpansions.delete(prefixKey);
1673
+ }
1674
+ if (nextPrime) {
1675
+ inFlightBoundaryContextKey = null;
1676
+ }
1677
+ causalInFlight = false;
1678
+ _drainCausalQueue();
1679
+ });
1680
+ };
1064
1681
 
1065
1682
  // ── Context update (debounced) ─────────────────────────────────────────
1066
1683
 
@@ -1110,7 +1727,7 @@ export var createLocalSlowLaneClient = function createLocalSlowLaneClient() {
1110
1727
  latestRequestId = requestId;
1111
1728
  if (isAutocompleteDebugEnabled()) {
1112
1729
  // eslint-disable-next-line no-console
1113
- console.groupCollapsed("%c[LocalSlowLane] %c\uD83D\uDCE4 Context update (request #".concat(requestId, ") | ").concat(text.length, " chars"), 'color: #9c27b0; font-weight: bold;', 'color: inherit;');
1730
+ console.groupCollapsed("%c[CTC:model] %c\uD83D\uDCE4 Context update (request #".concat(requestId, ") | ").concat(text.length, " chars"), 'color: #9c27b0; font-weight: bold;', 'color: inherit;');
1114
1731
  var lines = text.split('\n');
1115
1732
  lines.forEach(function (line, i) {
1116
1733
  // eslint-disable-next-line no-console
@@ -1146,22 +1763,136 @@ export var createLocalSlowLaneClient = function createLocalSlowLaneClient() {
1146
1763
  doUpdateContext(lastRequestedText);
1147
1764
  }, debounceMs);
1148
1765
  };
1766
+ var setLatestCausalFamily = function setLatestCausalFamily(familyKey) {
1767
+ if (familyKey === latestCausalFamilyKey) {
1768
+ return;
1769
+ }
1770
+ latestCausalFamilyKey = familyKey;
1771
+ causalRequestsThisFamily = 0;
1772
+ causalGeneratedTokensThisFamily = 0;
1773
+ tierAPrimeCacheHitsThisFamily = 0;
1774
+ tierAPrimeCacheMissesThisFamily = 0;
1775
+ };
1776
+ var primeBoundaryLm = function primeBoundaryLm(input) {
1777
+ if (destroyed) {
1778
+ return;
1779
+ }
1780
+ setLatestCausalFamily(input.familyKey);
1781
+ if (getLru(boundaryCache, input.contextKey)) {
1782
+ tierAPrimeCacheHitsThisFamily++;
1783
+ if (isAutocompleteDebugVerbose()) {
1784
+ // eslint-disable-next-line no-console
1785
+ console.log("%c[CTC:model] %c\u26A1 Tier A cache hit \xB7 ctx=".concat(input.contextKey.slice(0, 48)), 'color: #9c27b0; font-weight: bold;', 'color: #2196f3;');
1786
+ }
1787
+ return;
1788
+ }
1789
+ if (pendingBoundaryPrimes.has(input.contextKey) || inFlightBoundaryContextKey === input.contextKey) {
1790
+ logProgressiveState('deduplicated', "boundary \xB7 ctx=".concat(input.contextKey.slice(0, 48)));
1791
+ return;
1792
+ }
1793
+ if (pendingBoundaryPrimes.size >= CANONICAL_SCORING.TIER_A_PRIMES_MAX) {
1794
+ var stalePending = Array.from(pendingBoundaryPrimes.entries()).find(function (_ref11) {
1795
+ var _ref12 = _slicedToArray(_ref11, 2),
1796
+ request = _ref12[1];
1797
+ return request.familyKey !== latestCausalFamilyKey;
1798
+ });
1799
+ if (stalePending) {
1800
+ pendingBoundaryPrimes.delete(stalePending[0]);
1801
+ logProgressiveState('stale', "dropped unstarted boundary \xB7 ctx=".concat(stalePending[0].slice(0, 48)));
1802
+ }
1803
+ }
1804
+ if (pendingBoundaryPrimes.size < CANONICAL_SCORING.TIER_A_PRIMES_MAX) {
1805
+ tierAPrimeCacheMissesThisFamily++;
1806
+ pendingBoundaryPrimes.set(input.contextKey, input);
1807
+ }
1808
+ _drainCausalQueue();
1809
+ };
1810
+ var requestProgressiveSurfaceScores = function requestProgressiveSurfaceScores(input) {
1811
+ if (destroyed) {
1812
+ return;
1813
+ }
1814
+ setLatestCausalFamily(input.familyKey);
1815
+ var candidates = input.candidates.filter(function (candidate) {
1816
+ return candidate.tokenIds.length > 0 && candidate.tokenIds.length <= CANONICAL_SCORING.EXACT_MAX_TARGET_TOKENS;
1817
+ }).slice(0, CANONICAL_SCORING.PROGRESSIVE_INPUT_MAX);
1818
+ if (candidates.length === 0) {
1819
+ return;
1820
+ }
1821
+ var previous = progressiveRequests.get(input.contextKey);
1822
+ var candidateSignature = candidates.map(function (candidate) {
1823
+ return "".concat(candidate.surface, ":").concat(candidate.tokenIds.join(','));
1824
+ }).join("\x01");
1825
+ var previousSignature = previous === null || previous === void 0 ? void 0 : previous.candidates.map(function (candidate) {
1826
+ return "".concat(candidate.surface, ":").concat(candidate.tokenIds.join(','));
1827
+ }).join("\x01");
1828
+ setLru(progressiveRequests, input.contextKey, _objectSpread(_objectSpread({}, input), {}, {
1829
+ candidates: candidates
1830
+ }), CANONICAL_SCORING.BOUNDARY_CACHE_MAX);
1831
+ var _iterator4 = _createForOfIteratorHelper(progressivePrefixesByContext.keys()),
1832
+ _step4;
1833
+ try {
1834
+ for (_iterator4.s(); !(_step4 = _iterator4.n()).done;) {
1835
+ var cachedContextKey = _step4.value;
1836
+ if (!progressiveRequests.has(cachedContextKey)) {
1837
+ progressivePrefixesByContext.delete(cachedContextKey);
1838
+ }
1839
+ }
1840
+ } catch (err) {
1841
+ _iterator4.e(err);
1842
+ } finally {
1843
+ _iterator4.f();
1844
+ }
1845
+ var cachedProgressCount = candidates.filter(function (candidate) {
1846
+ var evidence = getProgressiveEvidence(input.contextKey, candidate.surface, candidate.tokenIds);
1847
+ return evidence !== null && (evidence.scoredTokenCount > 1 || evidence.scoredTokenCount === evidence.totalTokenCount);
1848
+ }).length;
1849
+ if (cachedProgressCount > 0) {
1850
+ logProgressiveState('cached', "ctx=".concat(input.contextKey.slice(0, 40), " \xB7 surfaces=").concat(cachedProgressCount, "/").concat(candidates.length));
1851
+ }
1852
+ if (candidateSignature === previousSignature) {
1853
+ logProgressiveState('deduplicated', "candidate set \xB7 ctx=".concat(input.contextKey.slice(0, 40), " \xB7 surfaces=").concat(candidates.length));
1854
+ } else {
1855
+ logProgressiveState('queued', "candidate set \xB7 ctx=".concat(input.contextKey.slice(0, 40), " \xB7 surfaces=").concat(candidates.length));
1856
+ }
1857
+ _planProgressiveExpansion(input.contextKey);
1858
+ };
1149
1859
 
1150
1860
  // ── Public API (same shape as createSlowLaneClient) ────────────────────
1151
1861
  return {
1152
1862
  updateContext: updateContextDebounced,
1863
+ getBoundaryLmState: function getBoundaryLmState(contextKey) {
1864
+ return getLru(boundaryCache, contextKey);
1865
+ },
1866
+ getCanonicalSurfaceTokenIds: function getCanonicalSurfaceTokenIds(candidateSurface) {
1867
+ return surfaceTokenTrie.getTokenIds(candidateSurface);
1868
+ },
1869
+ getCanonicalSurfaceCount: function getCanonicalSurfaceCount() {
1870
+ return surfaceTokenIds.size;
1871
+ },
1872
+ getContextInput: function getContextInput() {
1873
+ return storedContextInput;
1874
+ },
1153
1875
  getContextVector: function getContextVector() {
1154
1876
  return storedContextVector;
1155
1877
  },
1156
1878
  getLmLogits: function getLmLogits() {
1157
1879
  return storedLmLogits;
1158
1880
  },
1881
+ getProgressiveSurfaceEvidence: function getProgressiveSurfaceEvidence(contextKey, candidateSurface) {
1882
+ return getProgressiveEvidence(contextKey, candidateSurface);
1883
+ },
1884
+ getSurfaceScore: function getSurfaceScore(contextKey, candidateSurface) {
1885
+ return getLru(surfaceScoreCache, surfaceCacheKey(contextKey, candidateSurface));
1886
+ },
1159
1887
  setContextVector: function setContextVector(vector) {
1888
+ storedContextInput = null;
1160
1889
  storedContextVector = vector;
1161
1890
  },
1162
1891
  setLmLogits: function setLmLogits(logits) {
1163
1892
  storedLmLogits = logits;
1164
1893
  },
1894
+ primeBoundaryLm: primeBoundaryLm,
1895
+ requestProgressiveSurfaceScores: requestProgressiveSurfaceScores,
1165
1896
  isWordBoundary: isWordBoundary,
1166
1897
  isReady: function isReady() {
1167
1898
  return ready;
@@ -1182,8 +1913,26 @@ export var createLocalSlowLaneClient = function createLocalSlowLaneClient() {
1182
1913
  activeInferenceText = null;
1183
1914
  activeInferenceRequestId = -1;
1184
1915
  pendingInference = null;
1916
+ storedContextInput = null;
1185
1917
  storedContextVector = null;
1186
1918
  storedLmLogits = null;
1919
+ causalInFlight = false;
1920
+ inFlightBoundaryContextKey = null;
1921
+ kvContextKey = null;
1922
+ kvPath = [];
1923
+ latestCausalFamilyKey = '';
1924
+ pendingBoundaryPrimes.clear();
1925
+ pendingPrefixExpansions.clear();
1926
+ inFlightPrefixExpansions.clear();
1927
+ progressiveRequests.clear();
1928
+ progressivePrefixesByContext.clear();
1929
+ boundaryCache.clear();
1930
+ causalInferenceByContext.clear();
1931
+ causalInferenceByFamily.clear();
1932
+ exactEvidenceCountByContext.clear();
1933
+ prefixExpansionCache.clear();
1934
+ surfaceScoreCache.clear();
1935
+ causalLogitProcessor.resetState();
1187
1936
  }
1188
1937
  };
1189
1938
  };