pi-ghost-autocomplete 0.3.1 → 0.3.2
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.
- package/README.md +101 -6
- package/dist/bench-cli.js +81 -5
- package/dist/bench-cli.js.map +1 -1
- package/dist/bench.d.ts +130 -2
- package/dist/bench.d.ts.map +1 -1
- package/dist/bench.js +285 -4
- package/dist/bench.js.map +1 -1
- package/dist/cache.d.ts +124 -0
- package/dist/cache.d.ts.map +1 -0
- package/dist/cache.js +173 -0
- package/dist/cache.js.map +1 -0
- package/dist/candidate-rerank.d.ts +57 -0
- package/dist/candidate-rerank.d.ts.map +1 -0
- package/dist/candidate-rerank.js +78 -0
- package/dist/candidate-rerank.js.map +1 -0
- package/dist/config.d.ts +43 -0
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +148 -18
- package/dist/config.js.map +1 -1
- package/dist/context-aggregator.d.ts +14 -5
- package/dist/context-aggregator.d.ts.map +1 -1
- package/dist/context-aggregator.js +38 -9
- package/dist/context-aggregator.js.map +1 -1
- package/dist/context-selector.d.ts +128 -0
- package/dist/context-selector.d.ts.map +1 -0
- package/dist/context-selector.js +300 -0
- package/dist/context-selector.js.map +1 -0
- package/dist/cycle-manager.d.ts +102 -0
- package/dist/cycle-manager.d.ts.map +1 -0
- package/dist/cycle-manager.js +168 -0
- package/dist/cycle-manager.js.map +1 -0
- package/dist/edit-history-ring.d.ts +148 -0
- package/dist/edit-history-ring.d.ts.map +1 -0
- package/dist/edit-history-ring.js +262 -0
- package/dist/edit-history-ring.js.map +1 -0
- package/dist/entropy-stop.d.ts +102 -0
- package/dist/entropy-stop.d.ts.map +1 -0
- package/dist/entropy-stop.js +141 -0
- package/dist/entropy-stop.js.map +1 -0
- package/dist/eval-dataset.d.ts +66 -0
- package/dist/eval-dataset.d.ts.map +1 -0
- package/dist/eval-dataset.js +127 -0
- package/dist/eval-dataset.js.map +1 -0
- package/dist/fast-path-engine.d.ts +286 -0
- package/dist/fast-path-engine.d.ts.map +1 -0
- package/dist/fast-path-engine.js +341 -0
- package/dist/fast-path-engine.js.map +1 -0
- package/dist/fuzzy-matcher.d.ts +8 -0
- package/dist/fuzzy-matcher.d.ts.map +1 -1
- package/dist/fuzzy-matcher.js +5 -3
- package/dist/fuzzy-matcher.js.map +1 -1
- package/dist/ghost-editor.d.ts +321 -4
- package/dist/ghost-editor.d.ts.map +1 -1
- package/dist/ghost-editor.js +1190 -144
- package/dist/ghost-editor.js.map +1 -1
- package/dist/index.d.ts +17 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +81 -2
- package/dist/index.js.map +1 -1
- package/dist/keystroke-timer.d.ts +59 -0
- package/dist/keystroke-timer.d.ts.map +1 -0
- package/dist/keystroke-timer.js +94 -0
- package/dist/keystroke-timer.js.map +1 -0
- package/dist/metrics-builder.d.ts +71 -0
- package/dist/metrics-builder.d.ts.map +1 -0
- package/dist/metrics-builder.js +71 -0
- package/dist/metrics-builder.js.map +1 -0
- package/dist/metrics.d.ts +99 -2
- package/dist/metrics.d.ts.map +1 -1
- package/dist/metrics.js +131 -17
- package/dist/metrics.js.map +1 -1
- package/dist/path-matcher.d.ts +9 -0
- package/dist/path-matcher.d.ts.map +1 -1
- package/dist/path-matcher.js +5 -2
- package/dist/path-matcher.js.map +1 -1
- package/dist/profile-store.d.ts +82 -0
- package/dist/profile-store.d.ts.map +1 -0
- package/dist/profile-store.js +199 -0
- package/dist/profile-store.js.map +1 -0
- package/dist/profile.d.ts +185 -0
- package/dist/profile.d.ts.map +1 -0
- package/dist/profile.js +434 -0
- package/dist/profile.js.map +1 -0
- package/dist/providers/cloud-provider.d.ts +10 -1
- package/dist/providers/cloud-provider.d.ts.map +1 -1
- package/dist/providers/cloud-provider.js +71 -14
- package/dist/providers/cloud-provider.js.map +1 -1
- package/dist/providers/local-provider.d.ts +8 -1
- package/dist/providers/local-provider.d.ts.map +1 -1
- package/dist/providers/local-provider.js +15 -3
- package/dist/providers/local-provider.js.map +1 -1
- package/dist/providers/openai-compat-provider.d.ts +11 -1
- package/dist/providers/openai-compat-provider.d.ts.map +1 -1
- package/dist/providers/openai-compat-provider.js +93 -6
- package/dist/providers/openai-compat-provider.js.map +1 -1
- package/dist/providers/openai-completions-runner.d.ts +25 -1
- package/dist/providers/openai-completions-runner.d.ts.map +1 -1
- package/dist/providers/openai-completions-runner.js +109 -14
- package/dist/providers/openai-completions-runner.js.map +1 -1
- package/dist/providers/race.d.ts +58 -1
- package/dist/providers/race.d.ts.map +1 -1
- package/dist/providers/race.js +317 -4
- package/dist/providers/race.js.map +1 -1
- package/dist/providers/truncate.d.ts.map +1 -1
- package/dist/providers/truncate.js +20 -2
- package/dist/providers/truncate.js.map +1 -1
- package/dist/providers/types.d.ts +96 -1
- package/dist/providers/types.d.ts.map +1 -1
- package/dist/providers/types.js +59 -1
- package/dist/providers/types.js.map +1 -1
- package/dist/provisional-manager.d.ts +93 -0
- package/dist/provisional-manager.d.ts.map +1 -0
- package/dist/provisional-manager.js +98 -0
- package/dist/provisional-manager.js.map +1 -0
- package/dist/provisional.d.ts +150 -0
- package/dist/provisional.d.ts.map +1 -0
- package/dist/provisional.js +238 -0
- package/dist/provisional.js.map +1 -0
- package/dist/regret-tracker.d.ts +84 -0
- package/dist/regret-tracker.d.ts.map +1 -0
- package/dist/regret-tracker.js +145 -0
- package/dist/regret-tracker.js.map +1 -0
- package/dist/replay-cli.d.ts +13 -0
- package/dist/replay-cli.d.ts.map +1 -0
- package/dist/replay-cli.js +167 -0
- package/dist/replay-cli.js.map +1 -0
- package/dist/replay.d.ts +107 -0
- package/dist/replay.d.ts.map +1 -0
- package/dist/replay.js +225 -0
- package/dist/replay.js.map +1 -0
- package/dist/reranker.d.ts +181 -0
- package/dist/reranker.d.ts.map +1 -0
- package/dist/reranker.js +324 -0
- package/dist/reranker.js.map +1 -0
- package/dist/session-trie.d.ts +103 -0
- package/dist/session-trie.d.ts.map +1 -0
- package/dist/session-trie.js +280 -0
- package/dist/session-trie.js.map +1 -0
- package/dist/speculative.d.ts +74 -0
- package/dist/speculative.d.ts.map +1 -0
- package/dist/speculative.js +107 -0
- package/dist/speculative.js.map +1 -0
- package/dist/tool-summarize.d.ts +40 -0
- package/dist/tool-summarize.d.ts.map +1 -0
- package/dist/tool-summarize.js +245 -0
- package/dist/tool-summarize.js.map +1 -0
- package/dist/trie-sanitize.d.ts +22 -0
- package/dist/trie-sanitize.d.ts.map +1 -0
- package/dist/trie-sanitize.js +142 -0
- package/dist/trie-sanitize.js.map +1 -0
- package/dist/versions.d.ts +23 -0
- package/dist/versions.d.ts.map +1 -0
- package/dist/versions.js +31 -0
- package/dist/versions.js.map +1 -0
- package/package.json +3 -2
|
@@ -0,0 +1,280 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Session trie — fast-path completion source backed by the user's own past
|
|
3
|
+
* accepts and committed messages. See PRD §M2.
|
|
4
|
+
*
|
|
5
|
+
* Structurally this is `Map<prefix, Map<suffix, weight>>` rather than a true
|
|
6
|
+
* trie. At the PRD's 5000-entry cap, hash-lookup-by-exact-prefix is faster
|
|
7
|
+
* than walking a character trie, and the simpler shape is easier to reason
|
|
8
|
+
* about for decay and eviction.
|
|
9
|
+
*
|
|
10
|
+
* Insertion sources and weights (PRD §M2 "Insertion signals"):
|
|
11
|
+
* - full accept: +3
|
|
12
|
+
* - partial accept: +2 on the partial word, +1 on the full candidate
|
|
13
|
+
* - committed user message: +1, prefix length 3..12 chars, suffix capped at
|
|
14
|
+
* 4 word-boundary shingles
|
|
15
|
+
* - divergent dismiss: -0.5 (caller decides what counts as divergent;
|
|
16
|
+
* per-keystroke dismisses must NOT call here, per PRD)
|
|
17
|
+
*
|
|
18
|
+
* Lookup gates (PRD §M2 "Lookup"):
|
|
19
|
+
* - prefix length ≥ 3
|
|
20
|
+
* - winner weight ≥ 1.5
|
|
21
|
+
* - winner margin over runner-up ≥ 1.0
|
|
22
|
+
*/
|
|
23
|
+
import { containsSensitive } from "./trie-sanitize.js";
|
|
24
|
+
export const MIN_PREFIX_LEN = 3;
|
|
25
|
+
export const MIN_WEIGHT = 1.5;
|
|
26
|
+
export const MIN_MARGIN = 1.0;
|
|
27
|
+
export const DEFAULT_CAPACITY = 5000;
|
|
28
|
+
export const DEFAULT_DECAY_EVERY = 200;
|
|
29
|
+
export const DEFAULT_DECAY_FACTOR = 0.5;
|
|
30
|
+
export const MAX_COMMITTED_PREFIX_LEN = 12;
|
|
31
|
+
export const MAX_COMMITTED_SUFFIX_WORDS = 4;
|
|
32
|
+
export class SessionTrie {
|
|
33
|
+
capacity;
|
|
34
|
+
decayEvery;
|
|
35
|
+
decayFactor;
|
|
36
|
+
/**
|
|
37
|
+
* `Map` insertion order is the LRU stamp — we re-insert a prefix key after
|
|
38
|
+
* touching it. Beyond capacity, the oldest key (first in iteration order)
|
|
39
|
+
* is evicted.
|
|
40
|
+
*/
|
|
41
|
+
map = new Map();
|
|
42
|
+
insertsSinceDecay = 0;
|
|
43
|
+
constructor(capacity = DEFAULT_CAPACITY, decayEvery = DEFAULT_DECAY_EVERY, decayFactor = DEFAULT_DECAY_FACTOR) {
|
|
44
|
+
this.capacity = capacity;
|
|
45
|
+
this.decayEvery = decayEvery;
|
|
46
|
+
this.decayFactor = decayFactor;
|
|
47
|
+
if (capacity <= 0) {
|
|
48
|
+
this.capacity = DEFAULT_CAPACITY;
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
get size() {
|
|
52
|
+
return this.map.size;
|
|
53
|
+
}
|
|
54
|
+
get stats() {
|
|
55
|
+
let candidates = 0;
|
|
56
|
+
for (const m of this.map.values())
|
|
57
|
+
candidates += m.size;
|
|
58
|
+
return {
|
|
59
|
+
prefixes: this.map.size,
|
|
60
|
+
candidates,
|
|
61
|
+
insertsSinceDecay: this.insertsSinceDecay,
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
insert(prefix, suffix, weight) {
|
|
65
|
+
if (prefix.length === 0 || suffix.length === 0)
|
|
66
|
+
return;
|
|
67
|
+
if (containsSensitive(prefix) || containsSensitive(suffix))
|
|
68
|
+
return;
|
|
69
|
+
this.bumpCandidate(prefix, suffix, weight);
|
|
70
|
+
this.tickDecayCounter();
|
|
71
|
+
this.enforceCapacity();
|
|
72
|
+
}
|
|
73
|
+
/**
|
|
74
|
+
* Insert prefix→remainder pairs from an accepted ghost: prefix is the
|
|
75
|
+
* editor text immediately before the ghost; the full accepted suffix is
|
|
76
|
+
* the value. Weight is the caller's choice (+3 full, +2 partial-word, +1
|
|
77
|
+
* full-candidate-on-partial). Caller handles signal selection.
|
|
78
|
+
*/
|
|
79
|
+
insertFromAccept(prefix, suffix, weight) {
|
|
80
|
+
this.insert(prefix, suffix, weight);
|
|
81
|
+
}
|
|
82
|
+
/**
|
|
83
|
+
* Insert sliding-window prefix→remainder pairs from a committed user
|
|
84
|
+
* message at weight +1 each (PRD §M2). Prefix lengths 3..12 chars,
|
|
85
|
+
* remainder capped at 4 word-boundary shingles to limit memory and avoid
|
|
86
|
+
* memorizing entire messages.
|
|
87
|
+
*
|
|
88
|
+
* Skips the whole message if any token matches a sensitive pattern.
|
|
89
|
+
*/
|
|
90
|
+
insertFromCommittedMessage(text, weightPerShingle = 1) {
|
|
91
|
+
const trimmed = text.trim();
|
|
92
|
+
if (!trimmed)
|
|
93
|
+
return;
|
|
94
|
+
if (containsSensitive(trimmed))
|
|
95
|
+
return;
|
|
96
|
+
const wordBoundaries = collectWordBoundaries(trimmed);
|
|
97
|
+
if (wordBoundaries.length === 0)
|
|
98
|
+
return;
|
|
99
|
+
for (let prefixLen = MIN_PREFIX_LEN; prefixLen <= MAX_COMMITTED_PREFIX_LEN && prefixLen < trimmed.length; prefixLen++) {
|
|
100
|
+
const prefix = trimmed.slice(0, prefixLen);
|
|
101
|
+
const suffix = sliceWordsAfter(trimmed, prefixLen, wordBoundaries, MAX_COMMITTED_SUFFIX_WORDS);
|
|
102
|
+
if (!suffix)
|
|
103
|
+
continue;
|
|
104
|
+
this.bumpCandidate(prefix, suffix, weightPerShingle);
|
|
105
|
+
}
|
|
106
|
+
this.tickDecayCounter();
|
|
107
|
+
this.enforceCapacity();
|
|
108
|
+
}
|
|
109
|
+
/**
|
|
110
|
+
* Penalize an entry that diverged from the user's eventual commit.
|
|
111
|
+
* Per-keystroke dismisses must NOT call this (would penalize correct
|
|
112
|
+
* ghosts mid-typing). See PRD §M2.
|
|
113
|
+
*/
|
|
114
|
+
penalize(prefix, suffix, magnitude = 0.5) {
|
|
115
|
+
magnitude = Math.max(0, magnitude);
|
|
116
|
+
const bucket = this.map.get(prefix);
|
|
117
|
+
if (!bucket)
|
|
118
|
+
return;
|
|
119
|
+
const current = bucket.get(suffix);
|
|
120
|
+
if (current === undefined)
|
|
121
|
+
return;
|
|
122
|
+
const next = current - magnitude;
|
|
123
|
+
if (next <= 0) {
|
|
124
|
+
bucket.delete(suffix);
|
|
125
|
+
if (bucket.size === 0)
|
|
126
|
+
this.map.delete(prefix);
|
|
127
|
+
}
|
|
128
|
+
else {
|
|
129
|
+
bucket.set(suffix, next);
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
lookup(prefix) {
|
|
133
|
+
if (prefix.length < MIN_PREFIX_LEN)
|
|
134
|
+
return null;
|
|
135
|
+
// Walk from longest to shortest prefix. The longest match that satisfies
|
|
136
|
+
// weight + margin wins. For a stored entry whose key is shorter than the
|
|
137
|
+
// user's current prefix, the user has already typed part of the
|
|
138
|
+
// suggested suffix → shift the ghost accordingly (typeahead-delta logic,
|
|
139
|
+
// same idea as `tryWarm`'s typeahead branch). If the user's typing
|
|
140
|
+
// diverges from the stored suffix, that candidate is skipped.
|
|
141
|
+
const maxLen = prefix.length;
|
|
142
|
+
for (let keyLen = maxLen; keyLen >= MIN_PREFIX_LEN; keyLen--) {
|
|
143
|
+
const key = prefix.slice(0, keyLen);
|
|
144
|
+
const bucket = this.map.get(key);
|
|
145
|
+
if (!bucket || bucket.size === 0)
|
|
146
|
+
continue;
|
|
147
|
+
const typedDelta = prefix.slice(keyLen);
|
|
148
|
+
let bestSuffix = null;
|
|
149
|
+
let bestWeight = -Infinity;
|
|
150
|
+
let runnerUp = 0;
|
|
151
|
+
for (const [suffix, weight] of bucket) {
|
|
152
|
+
// Stored suffix must agree with what the user typed since `key`.
|
|
153
|
+
if (typedDelta.length > 0 && !suffix.startsWith(typedDelta))
|
|
154
|
+
continue;
|
|
155
|
+
if (weight > bestWeight) {
|
|
156
|
+
runnerUp = bestWeight === -Infinity ? 0 : bestWeight;
|
|
157
|
+
bestWeight = weight;
|
|
158
|
+
bestSuffix = suffix;
|
|
159
|
+
}
|
|
160
|
+
else if (weight > runnerUp) {
|
|
161
|
+
runnerUp = weight;
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
if (bestSuffix === null)
|
|
165
|
+
continue;
|
|
166
|
+
if (bestWeight < MIN_WEIGHT)
|
|
167
|
+
continue;
|
|
168
|
+
const margin = bestWeight - runnerUp;
|
|
169
|
+
if (margin < MIN_MARGIN)
|
|
170
|
+
continue;
|
|
171
|
+
const shifted = bestSuffix.slice(typedDelta.length);
|
|
172
|
+
if (shifted.length === 0)
|
|
173
|
+
continue;
|
|
174
|
+
// LRU touch on hit.
|
|
175
|
+
this.map.delete(key);
|
|
176
|
+
this.map.set(key, bucket);
|
|
177
|
+
return { ghost: shifted, weight: bestWeight, margin };
|
|
178
|
+
}
|
|
179
|
+
return null;
|
|
180
|
+
}
|
|
181
|
+
clear() {
|
|
182
|
+
this.map.clear();
|
|
183
|
+
this.insertsSinceDecay = 0;
|
|
184
|
+
}
|
|
185
|
+
/**
|
|
186
|
+
* Halve all weights. Exposed for testing; callers normally rely on the
|
|
187
|
+
* automatic decay every `decayEvery` inserts.
|
|
188
|
+
*/
|
|
189
|
+
decayNow() {
|
|
190
|
+
for (const bucket of this.map.values()) {
|
|
191
|
+
for (const [suffix, weight] of bucket) {
|
|
192
|
+
const next = weight * this.decayFactor;
|
|
193
|
+
if (next < 0.05)
|
|
194
|
+
bucket.delete(suffix);
|
|
195
|
+
else
|
|
196
|
+
bucket.set(suffix, next);
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
// Drop empty buckets.
|
|
200
|
+
for (const [prefix, bucket] of this.map) {
|
|
201
|
+
if (bucket.size === 0)
|
|
202
|
+
this.map.delete(prefix);
|
|
203
|
+
}
|
|
204
|
+
this.insertsSinceDecay = 0;
|
|
205
|
+
}
|
|
206
|
+
bumpCandidate(prefix, suffix, weight) {
|
|
207
|
+
let bucket = this.map.get(prefix);
|
|
208
|
+
if (bucket) {
|
|
209
|
+
// LRU touch.
|
|
210
|
+
this.map.delete(prefix);
|
|
211
|
+
}
|
|
212
|
+
else {
|
|
213
|
+
bucket = new Map();
|
|
214
|
+
}
|
|
215
|
+
bucket.set(suffix, (bucket.get(suffix) ?? 0) + weight);
|
|
216
|
+
this.map.set(prefix, bucket);
|
|
217
|
+
}
|
|
218
|
+
tickDecayCounter() {
|
|
219
|
+
this.insertsSinceDecay++;
|
|
220
|
+
if (this.insertsSinceDecay >= this.decayEvery) {
|
|
221
|
+
this.decayNow();
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
enforceCapacity() {
|
|
225
|
+
while (this.map.size > this.capacity) {
|
|
226
|
+
const oldest = this.map.keys().next().value;
|
|
227
|
+
if (oldest === undefined)
|
|
228
|
+
break;
|
|
229
|
+
this.map.delete(oldest);
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
/**
|
|
234
|
+
* Return the end-of-word offsets in `text` (exclusive). For "ab cd ef", returns
|
|
235
|
+
* [2, 5, 8]. Used to align committed-message shingles to word boundaries.
|
|
236
|
+
*/
|
|
237
|
+
/**
|
|
238
|
+
* Pure helper used by `GhostEditor.onPromptCommitted`. Seeds `trie` from the
|
|
239
|
+
* committed text. If `accepted` is non-null AND the committed text contains
|
|
240
|
+
* the accepted ghost, the per-shingle weight drops from +1 to +0.25 to avoid
|
|
241
|
+
* double-counting (PRD §M2 "Insertion signals").
|
|
242
|
+
*/
|
|
243
|
+
export function seedTrieFromCommit(trie, text, accepted) {
|
|
244
|
+
if (!text)
|
|
245
|
+
return;
|
|
246
|
+
const overlaps = accepted ? text.includes(accepted.ghost) : false;
|
|
247
|
+
trie.insertFromCommittedMessage(text, overlaps ? 0.25 : 1);
|
|
248
|
+
}
|
|
249
|
+
function collectWordBoundaries(text) {
|
|
250
|
+
const out = [];
|
|
251
|
+
let inWord = false;
|
|
252
|
+
for (let i = 0; i < text.length; i++) {
|
|
253
|
+
const ch = text[i];
|
|
254
|
+
const isWS = /\s/.test(ch);
|
|
255
|
+
if (!isWS && !inWord)
|
|
256
|
+
inWord = true;
|
|
257
|
+
if (isWS && inWord) {
|
|
258
|
+
out.push(i);
|
|
259
|
+
inWord = false;
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
if (inWord)
|
|
263
|
+
out.push(text.length);
|
|
264
|
+
return out;
|
|
265
|
+
}
|
|
266
|
+
function sliceWordsAfter(text, start, boundaries, maxWords) {
|
|
267
|
+
let crossed = 0;
|
|
268
|
+
let end = text.length;
|
|
269
|
+
for (const b of boundaries) {
|
|
270
|
+
if (b <= start)
|
|
271
|
+
continue;
|
|
272
|
+
crossed++;
|
|
273
|
+
if (crossed >= maxWords) {
|
|
274
|
+
end = b;
|
|
275
|
+
break;
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
return text.slice(start, end);
|
|
279
|
+
}
|
|
280
|
+
//# sourceMappingURL=session-trie.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"session-trie.js","sourceRoot":"","sources":["../src/session-trie.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;GAqBG;AAEH,OAAO,EAAE,iBAAiB,EAAE,MAAM,oBAAoB,CAAC;AAEvD,MAAM,CAAC,MAAM,cAAc,GAAG,CAAC,CAAC;AAChC,MAAM,CAAC,MAAM,UAAU,GAAG,GAAG,CAAC;AAC9B,MAAM,CAAC,MAAM,UAAU,GAAG,GAAG,CAAC;AAC9B,MAAM,CAAC,MAAM,gBAAgB,GAAG,IAAI,CAAC;AACrC,MAAM,CAAC,MAAM,mBAAmB,GAAG,GAAG,CAAC;AACvC,MAAM,CAAC,MAAM,oBAAoB,GAAG,GAAG,CAAC;AACxC,MAAM,CAAC,MAAM,wBAAwB,GAAG,EAAE,CAAC;AAC3C,MAAM,CAAC,MAAM,0BAA0B,GAAG,CAAC,CAAC;AAc5C,MAAM,OAAO,WAAW;IAUL;IACA;IACA;IAXlB;;;;OAIG;IACc,GAAG,GAAG,IAAI,GAAG,EAA+B,CAAC;IACtD,iBAAiB,GAAG,CAAC,CAAC;IAE9B,YACkB,WAAmB,gBAAgB,EACnC,aAAqB,mBAAmB,EACxC,cAAsB,oBAAoB;QAF1C,aAAQ,GAAR,QAAQ,CAA2B;QACnC,eAAU,GAAV,UAAU,CAA8B;QACxC,gBAAW,GAAX,WAAW,CAA+B;QAE3D,IAAI,QAAQ,IAAI,CAAC,EAAE,CAAC;YACnB,IAAI,CAAC,QAAQ,GAAG,gBAAgB,CAAC;QAClC,CAAC;IACF,CAAC;IAED,IAAI,IAAI;QACP,OAAO,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC;IACtB,CAAC;IAED,IAAI,KAAK;QACR,IAAI,UAAU,GAAG,CAAC,CAAC;QACnB,KAAK,MAAM,CAAC,IAAI,IAAI,CAAC,GAAG,CAAC,MAAM,EAAE;YAAE,UAAU,IAAI,CAAC,CAAC,IAAI,CAAC;QACxD,OAAO;YACN,QAAQ,EAAE,IAAI,CAAC,GAAG,CAAC,IAAI;YACvB,UAAU;YACV,iBAAiB,EAAE,IAAI,CAAC,iBAAiB;SACzC,CAAC;IACH,CAAC;IAED,MAAM,CAAC,MAAc,EAAE,MAAc,EAAE,MAAc;QACpD,IAAI,MAAM,CAAC,MAAM,KAAK,CAAC,IAAI,MAAM,CAAC,MAAM,KAAK,CAAC;YAAE,OAAO;QACvD,IAAI,iBAAiB,CAAC,MAAM,CAAC,IAAI,iBAAiB,CAAC,MAAM,CAAC;YAAE,OAAO;QACnE,IAAI,CAAC,aAAa,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,CAAC,CAAC;QAC3C,IAAI,CAAC,gBAAgB,EAAE,CAAC;QACxB,IAAI,CAAC,eAAe,EAAE,CAAC;IACxB,CAAC;IAED;;;;;OAKG;IACH,gBAAgB,CAAC,MAAc,EAAE,MAAc,EAAE,MAAc;QAC9D,IAAI,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,CAAC,CAAC;IACrC,CAAC;IAED;;;;;;;OAOG;IACH,0BAA0B,CAAC,IAAY,EAAE,gBAAgB,GAAG,CAAC;QAC5D,MAAM,OAAO,GAAG,IAAI,CAAC,IAAI,EAAE,CAAC;QAC5B,IAAI,CAAC,OAAO;YAAE,OAAO;QACrB,IAAI,iBAAiB,CAAC,OAAO,CAAC;YAAE,OAAO;QACvC,MAAM,cAAc,GAAG,qBAAqB,CAAC,OAAO,CAAC,CAAC;QACtD,IAAI,cAAc,CAAC,MAAM,KAAK,CAAC;YAAE,OAAO;QACxC,KACC,IAAI,SAAS,GAAG,cAAc,EAC9B,SAAS,IAAI,wBAAwB,IAAI,SAAS,GAAG,OAAO,CAAC,MAAM,EACnE,SAAS,EAAE,EACV,CAAC;YACF,MAAM,MAAM,GAAG,OAAO,CAAC,KAAK,CAAC,CAAC,EAAE,SAAS,CAAC,CAAC;YAC3C,MAAM,MAAM,GAAG,eAAe,CAC7B,OAAO,EACP,SAAS,EACT,cAAc,EACd,0BAA0B,CAC1B,CAAC;YACF,IAAI,CAAC,MAAM;gBAAE,SAAS;YACtB,IAAI,CAAC,aAAa,CAAC,MAAM,EAAE,MAAM,EAAE,gBAAgB,CAAC,CAAC;QACtD,CAAC;QACD,IAAI,CAAC,gBAAgB,EAAE,CAAC;QACxB,IAAI,CAAC,eAAe,EAAE,CAAC;IACxB,CAAC;IAED;;;;OAIG;IACH,QAAQ,CAAC,MAAc,EAAE,MAAc,EAAE,SAAS,GAAG,GAAG;QACvD,SAAS,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,SAAS,CAAC,CAAC;QACnC,MAAM,MAAM,GAAG,IAAI,CAAC,GAAG,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC;QACpC,IAAI,CAAC,MAAM;YAAE,OAAO;QACpB,MAAM,OAAO,GAAG,MAAM,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC;QACnC,IAAI,OAAO,KAAK,SAAS;YAAE,OAAO;QAClC,MAAM,IAAI,GAAG,OAAO,GAAG,SAAS,CAAC;QACjC,IAAI,IAAI,IAAI,CAAC,EAAE,CAAC;YACf,MAAM,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC;YACtB,IAAI,MAAM,CAAC,IAAI,KAAK,CAAC;gBAAE,IAAI,CAAC,GAAG,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC;QAChD,CAAC;aAAM,CAAC;YACP,MAAM,CAAC,GAAG,CAAC,MAAM,EAAE,IAAI,CAAC,CAAC;QAC1B,CAAC;IACF,CAAC;IAED,MAAM,CAAC,MAAc;QACpB,IAAI,MAAM,CAAC,MAAM,GAAG,cAAc;YAAE,OAAO,IAAI,CAAC;QAEhD,yEAAyE;QACzE,yEAAyE;QACzE,gEAAgE;QAChE,yEAAyE;QACzE,mEAAmE;QACnE,8DAA8D;QAC9D,MAAM,MAAM,GAAG,MAAM,CAAC,MAAM,CAAC;QAC7B,KAAK,IAAI,MAAM,GAAG,MAAM,EAAE,MAAM,IAAI,cAAc,EAAE,MAAM,EAAE,EAAE,CAAC;YAC9D,MAAM,GAAG,GAAG,MAAM,CAAC,KAAK,CAAC,CAAC,EAAE,MAAM,CAAC,CAAC;YACpC,MAAM,MAAM,GAAG,IAAI,CAAC,GAAG,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;YACjC,IAAI,CAAC,MAAM,IAAI,MAAM,CAAC,IAAI,KAAK,CAAC;gBAAE,SAAS;YAC3C,MAAM,UAAU,GAAG,MAAM,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC;YAExC,IAAI,UAAU,GAAkB,IAAI,CAAC;YACrC,IAAI,UAAU,GAAG,CAAC,QAAQ,CAAC;YAC3B,IAAI,QAAQ,GAAG,CAAC,CAAC;YACjB,KAAK,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,IAAI,MAAM,EAAE,CAAC;gBACvC,iEAAiE;gBACjE,IAAI,UAAU,CAAC,MAAM,GAAG,CAAC,IAAI,CAAC,MAAM,CAAC,UAAU,CAAC,UAAU,CAAC;oBAAE,SAAS;gBACtE,IAAI,MAAM,GAAG,UAAU,EAAE,CAAC;oBACzB,QAAQ,GAAG,UAAU,KAAK,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,UAAU,CAAC;oBACrD,UAAU,GAAG,MAAM,CAAC;oBACpB,UAAU,GAAG,MAAM,CAAC;gBACrB,CAAC;qBAAM,IAAI,MAAM,GAAG,QAAQ,EAAE,CAAC;oBAC9B,QAAQ,GAAG,MAAM,CAAC;gBACnB,CAAC;YACF,CAAC;YACD,IAAI,UAAU,KAAK,IAAI;gBAAE,SAAS;YAClC,IAAI,UAAU,GAAG,UAAU;gBAAE,SAAS;YACtC,MAAM,MAAM,GAAG,UAAU,GAAG,QAAQ,CAAC;YACrC,IAAI,MAAM,GAAG,UAAU;gBAAE,SAAS;YAElC,MAAM,OAAO,GAAG,UAAU,CAAC,KAAK,CAAC,UAAU,CAAC,MAAM,CAAC,CAAC;YACpD,IAAI,OAAO,CAAC,MAAM,KAAK,CAAC;gBAAE,SAAS;YAEnC,oBAAoB;YACpB,IAAI,CAAC,GAAG,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;YACrB,IAAI,CAAC,GAAG,CAAC,GAAG,CAAC,GAAG,EAAE,MAAM,CAAC,CAAC;YAC1B,OAAO,EAAE,KAAK,EAAE,OAAO,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,EAAE,CAAC;QACvD,CAAC;QACD,OAAO,IAAI,CAAC;IACb,CAAC;IAED,KAAK;QACJ,IAAI,CAAC,GAAG,CAAC,KAAK,EAAE,CAAC;QACjB,IAAI,CAAC,iBAAiB,GAAG,CAAC,CAAC;IAC5B,CAAC;IAED;;;OAGG;IACH,QAAQ;QACP,KAAK,MAAM,MAAM,IAAI,IAAI,CAAC,GAAG,CAAC,MAAM,EAAE,EAAE,CAAC;YACxC,KAAK,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,IAAI,MAAM,EAAE,CAAC;gBACvC,MAAM,IAAI,GAAG,MAAM,GAAG,IAAI,CAAC,WAAW,CAAC;gBACvC,IAAI,IAAI,GAAG,IAAI;oBAAE,MAAM,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC;;oBAClC,MAAM,CAAC,GAAG,CAAC,MAAM,EAAE,IAAI,CAAC,CAAC;YAC/B,CAAC;QACF,CAAC;QACD,sBAAsB;QACtB,KAAK,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,IAAI,IAAI,CAAC,GAAG,EAAE,CAAC;YACzC,IAAI,MAAM,CAAC,IAAI,KAAK,CAAC;gBAAE,IAAI,CAAC,GAAG,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC;QAChD,CAAC;QACD,IAAI,CAAC,iBAAiB,GAAG,CAAC,CAAC;IAC5B,CAAC;IAEO,aAAa,CAAC,MAAc,EAAE,MAAc,EAAE,MAAc;QACnE,IAAI,MAAM,GAAG,IAAI,CAAC,GAAG,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC;QAClC,IAAI,MAAM,EAAE,CAAC;YACZ,aAAa;YACb,IAAI,CAAC,GAAG,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC;QACzB,CAAC;aAAM,CAAC;YACP,MAAM,GAAG,IAAI,GAAG,EAAkB,CAAC;QACpC,CAAC;QACD,MAAM,CAAC,GAAG,CAAC,MAAM,EAAE,CAAC,MAAM,CAAC,GAAG,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC,GAAG,MAAM,CAAC,CAAC;QACvD,IAAI,CAAC,GAAG,CAAC,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAC9B,CAAC;IAEO,gBAAgB;QACvB,IAAI,CAAC,iBAAiB,EAAE,CAAC;QACzB,IAAI,IAAI,CAAC,iBAAiB,IAAI,IAAI,CAAC,UAAU,EAAE,CAAC;YAC/C,IAAI,CAAC,QAAQ,EAAE,CAAC;QACjB,CAAC;IACF,CAAC;IAEO,eAAe;QACtB,OAAO,IAAI,CAAC,GAAG,CAAC,IAAI,GAAG,IAAI,CAAC,QAAQ,EAAE,CAAC;YACtC,MAAM,MAAM,GAAG,IAAI,CAAC,GAAG,CAAC,IAAI,EAAE,CAAC,IAAI,EAAE,CAAC,KAAK,CAAC;YAC5C,IAAI,MAAM,KAAK,SAAS;gBAAE,MAAM;YAChC,IAAI,CAAC,GAAG,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC;QACzB,CAAC;IACF,CAAC;CACD;AAED;;;GAGG;AACH;;;;;GAKG;AACH,MAAM,UAAU,kBAAkB,CACjC,IAAiB,EACjB,IAAY,EACZ,QAAkD;IAElD,IAAI,CAAC,IAAI;QAAE,OAAO;IAClB,MAAM,QAAQ,GAAG,QAAQ,CAAC,CAAC,CAAC,IAAI,CAAC,QAAQ,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC;IAClE,IAAI,CAAC,0BAA0B,CAAC,IAAI,EAAE,QAAQ,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;AAC5D,CAAC;AAED,SAAS,qBAAqB,CAAC,IAAY;IAC1C,MAAM,GAAG,GAAa,EAAE,CAAC;IACzB,IAAI,MAAM,GAAG,KAAK,CAAC;IACnB,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,IAAI,CAAC,MAAM,EAAE,CAAC,EAAE,EAAE,CAAC;QACtC,MAAM,EAAE,GAAG,IAAI,CAAC,CAAC,CAAE,CAAC;QACpB,MAAM,IAAI,GAAG,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;QAC3B,IAAI,CAAC,IAAI,IAAI,CAAC,MAAM;YAAE,MAAM,GAAG,IAAI,CAAC;QACpC,IAAI,IAAI,IAAI,MAAM,EAAE,CAAC;YACpB,GAAG,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;YACZ,MAAM,GAAG,KAAK,CAAC;QAChB,CAAC;IACF,CAAC;IACD,IAAI,MAAM;QAAE,GAAG,CAAC,IAAI,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;IAClC,OAAO,GAAG,CAAC;AACZ,CAAC;AAED,SAAS,eAAe,CACvB,IAAY,EACZ,KAAa,EACb,UAAoB,EACpB,QAAgB;IAEhB,IAAI,OAAO,GAAG,CAAC,CAAC;IAChB,IAAI,GAAG,GAAG,IAAI,CAAC,MAAM,CAAC;IACtB,KAAK,MAAM,CAAC,IAAI,UAAU,EAAE,CAAC;QAC5B,IAAI,CAAC,IAAI,KAAK;YAAE,SAAS;QACzB,OAAO,EAAE,CAAC;QACV,IAAI,OAAO,IAAI,QAAQ,EAAE,CAAC;YACzB,GAAG,GAAG,CAAC,CAAC;YACR,MAAM;QACP,CAAC;IACF,CAAC;IACD,OAAO,IAAI,CAAC,KAAK,CAAC,KAAK,EAAE,GAAG,CAAC,CAAC;AAC/B,CAAC"}
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Speculative cache warm — covers the gap between an exact cache hit and a
|
|
3
|
+
* full LLM call. See PRD §6 M1b.
|
|
4
|
+
*
|
|
5
|
+
* Three match branches:
|
|
6
|
+
* - exact: prefix matches a recent ghost's prefix → reuse the ghost.
|
|
7
|
+
* - typeahead-delta: user typed forward into the ghost text → return the
|
|
8
|
+
* remaining ghost suffix.
|
|
9
|
+
* - backspace-of-N: user backspaced ≤3 chars within an old prefix → return
|
|
10
|
+
* droppedTail + oldGhost. Only fires while the race is in cooldown,
|
|
11
|
+
* because a fresh LLM call is preferable when budget allows.
|
|
12
|
+
*
|
|
13
|
+
* All branches require context/provider/version fingerprints to match the
|
|
14
|
+
* shown ghost to avoid leaking stale results across context shifts.
|
|
15
|
+
*/
|
|
16
|
+
export interface LastGhost {
|
|
17
|
+
prefix: string;
|
|
18
|
+
ghost: string;
|
|
19
|
+
ts: number;
|
|
20
|
+
contextFingerprint: string;
|
|
21
|
+
providerFingerprint: string;
|
|
22
|
+
versionFingerprint: string;
|
|
23
|
+
/** Engine config fingerprint at the time this entry was recorded
|
|
24
|
+
* (refactor #57 phase 2). Optional — entries without it match any
|
|
25
|
+
* fingerprint (legacy + test-fixture compatibility). */
|
|
26
|
+
configFingerprint?: string;
|
|
27
|
+
/**
|
|
28
|
+
* Originating provider name (e.g. "mercury-edit", "ghost-race:..."). Used
|
|
29
|
+
* by the editor to attribute speculative-warm metrics back to the provider
|
|
30
|
+
* that produced the source ghost, instead of the synthetic "speculative-warm"
|
|
31
|
+
* label. See PRD §5b.
|
|
32
|
+
*/
|
|
33
|
+
providerName: string;
|
|
34
|
+
}
|
|
35
|
+
export interface SpeculativeMatchInput {
|
|
36
|
+
prefix: string;
|
|
37
|
+
contextFingerprint: string;
|
|
38
|
+
providerFingerprint: string;
|
|
39
|
+
versionFingerprint: string;
|
|
40
|
+
/** Engine config fingerprint to match against. Optional — when omitted,
|
|
41
|
+
* the warm path skips the config check (legacy / test compatibility). */
|
|
42
|
+
configFingerprint?: string;
|
|
43
|
+
cooldownActive: boolean;
|
|
44
|
+
}
|
|
45
|
+
export type SpeculativeBranch = "exact" | "typeahead-delta" | "backspace-of-N";
|
|
46
|
+
export interface SpeculativeMatch {
|
|
47
|
+
ghost: string;
|
|
48
|
+
branch: SpeculativeBranch;
|
|
49
|
+
source: LastGhost;
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* Fixed-capacity ring of recent ghosts. The newest entry sits at the end.
|
|
53
|
+
*/
|
|
54
|
+
export declare class LastGhostsRing {
|
|
55
|
+
private readonly capacity;
|
|
56
|
+
private readonly buf;
|
|
57
|
+
constructor(capacity?: number);
|
|
58
|
+
get size(): number;
|
|
59
|
+
push(entry: LastGhost): void;
|
|
60
|
+
entries(): readonly LastGhost[];
|
|
61
|
+
clear(): void;
|
|
62
|
+
}
|
|
63
|
+
/**
|
|
64
|
+
* Try to satisfy a prefix from `ring` without firing the LLM. Returns the
|
|
65
|
+
* first match across exact / typeahead-delta / backspace-N. Newest entries
|
|
66
|
+
* are checked first.
|
|
67
|
+
*
|
|
68
|
+
* Grapheme-aware slicing is NOT applied here — the editor passes string
|
|
69
|
+
* prefixes/ghosts that were already produced grapheme-aware in
|
|
70
|
+
* `ghost-editor.ts`. The PRD's grapheme-awareness requirement is for the
|
|
71
|
+
* editor's slicing; this helper operates on the resulting strings.
|
|
72
|
+
*/
|
|
73
|
+
export declare function tryWarm(ring: LastGhostsRing, input: SpeculativeMatchInput): SpeculativeMatch | null;
|
|
74
|
+
//# sourceMappingURL=speculative.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"speculative.d.ts","sourceRoot":"","sources":["../src/speculative.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAIH,MAAM,WAAW,SAAS;IACzB,MAAM,EAAE,MAAM,CAAC;IACf,KAAK,EAAE,MAAM,CAAC;IACd,EAAE,EAAE,MAAM,CAAC;IACX,kBAAkB,EAAE,MAAM,CAAC;IAC3B,mBAAmB,EAAE,MAAM,CAAC;IAC5B,kBAAkB,EAAE,MAAM,CAAC;IAC3B;;6DAEyD;IACzD,iBAAiB,CAAC,EAAE,MAAM,CAAC;IAC3B;;;;;OAKG;IACH,YAAY,EAAE,MAAM,CAAC;CACrB;AAED,MAAM,WAAW,qBAAqB;IACrC,MAAM,EAAE,MAAM,CAAC;IACf,kBAAkB,EAAE,MAAM,CAAC;IAC3B,mBAAmB,EAAE,MAAM,CAAC;IAC5B,kBAAkB,EAAE,MAAM,CAAC;IAC3B;8EAC0E;IAC1E,iBAAiB,CAAC,EAAE,MAAM,CAAC;IAC3B,cAAc,EAAE,OAAO,CAAC;CACxB;AAED,MAAM,MAAM,iBAAiB,GAAG,OAAO,GAAG,iBAAiB,GAAG,gBAAgB,CAAC;AAE/E,MAAM,WAAW,gBAAgB;IAChC,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,iBAAiB,CAAC;IAC1B,MAAM,EAAE,SAAS,CAAC;CAClB;AAED;;GAEG;AACH,qBAAa,cAAc;IAGd,OAAO,CAAC,QAAQ,CAAC,QAAQ;IAFrC,OAAO,CAAC,QAAQ,CAAC,GAAG,CAAmB;gBAEV,QAAQ,GAAE,MAAU;IAEjD,IAAI,IAAI,IAAI,MAAM,CAEjB;IAED,IAAI,CAAC,KAAK,EAAE,SAAS,GAAG,IAAI;IAoB5B,OAAO,IAAI,SAAS,SAAS,EAAE;IAI/B,KAAK,IAAI,IAAI;CAGb;AAED;;;;;;;;;GASG;AACH,wBAAgB,OAAO,CACtB,IAAI,EAAE,cAAc,EACpB,KAAK,EAAE,qBAAqB,GAC1B,gBAAgB,GAAG,IAAI,CAmDzB"}
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Speculative cache warm — covers the gap between an exact cache hit and a
|
|
3
|
+
* full LLM call. See PRD §6 M1b.
|
|
4
|
+
*
|
|
5
|
+
* Three match branches:
|
|
6
|
+
* - exact: prefix matches a recent ghost's prefix → reuse the ghost.
|
|
7
|
+
* - typeahead-delta: user typed forward into the ghost text → return the
|
|
8
|
+
* remaining ghost suffix.
|
|
9
|
+
* - backspace-of-N: user backspaced ≤3 chars within an old prefix → return
|
|
10
|
+
* droppedTail + oldGhost. Only fires while the race is in cooldown,
|
|
11
|
+
* because a fresh LLM call is preferable when budget allows.
|
|
12
|
+
*
|
|
13
|
+
* All branches require context/provider/version fingerprints to match the
|
|
14
|
+
* shown ghost to avoid leaking stale results across context shifts.
|
|
15
|
+
*/
|
|
16
|
+
const BACKSPACE_MAX_DELTA = 3;
|
|
17
|
+
/**
|
|
18
|
+
* Fixed-capacity ring of recent ghosts. The newest entry sits at the end.
|
|
19
|
+
*/
|
|
20
|
+
export class LastGhostsRing {
|
|
21
|
+
capacity;
|
|
22
|
+
buf = [];
|
|
23
|
+
constructor(capacity = 8) {
|
|
24
|
+
this.capacity = capacity;
|
|
25
|
+
}
|
|
26
|
+
get size() {
|
|
27
|
+
return this.buf.length;
|
|
28
|
+
}
|
|
29
|
+
push(entry) {
|
|
30
|
+
// Treat (prefix, providerFingerprint, contextFingerprint, versionFingerprint)
|
|
31
|
+
// as the identity. A new completion for the same prefix in the same context
|
|
32
|
+
// supersedes the older one.
|
|
33
|
+
for (let i = 0; i < this.buf.length; i++) {
|
|
34
|
+
const e = this.buf[i];
|
|
35
|
+
if (e.prefix === entry.prefix &&
|
|
36
|
+
e.contextFingerprint === entry.contextFingerprint &&
|
|
37
|
+
e.providerFingerprint === entry.providerFingerprint &&
|
|
38
|
+
e.versionFingerprint === entry.versionFingerprint) {
|
|
39
|
+
this.buf.splice(i, 1);
|
|
40
|
+
break;
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
this.buf.push(entry);
|
|
44
|
+
while (this.buf.length > this.capacity)
|
|
45
|
+
this.buf.shift();
|
|
46
|
+
}
|
|
47
|
+
entries() {
|
|
48
|
+
return this.buf;
|
|
49
|
+
}
|
|
50
|
+
clear() {
|
|
51
|
+
this.buf.length = 0;
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* Try to satisfy a prefix from `ring` without firing the LLM. Returns the
|
|
56
|
+
* first match across exact / typeahead-delta / backspace-N. Newest entries
|
|
57
|
+
* are checked first.
|
|
58
|
+
*
|
|
59
|
+
* Grapheme-aware slicing is NOT applied here — the editor passes string
|
|
60
|
+
* prefixes/ghosts that were already produced grapheme-aware in
|
|
61
|
+
* `ghost-editor.ts`. The PRD's grapheme-awareness requirement is for the
|
|
62
|
+
* editor's slicing; this helper operates on the resulting strings.
|
|
63
|
+
*/
|
|
64
|
+
export function tryWarm(ring, input) {
|
|
65
|
+
const entries = ring.entries();
|
|
66
|
+
// Newest first.
|
|
67
|
+
for (let i = entries.length - 1; i >= 0; i--) {
|
|
68
|
+
const lg = entries[i];
|
|
69
|
+
if (lg.contextFingerprint !== input.contextFingerprint ||
|
|
70
|
+
lg.providerFingerprint !== input.providerFingerprint ||
|
|
71
|
+
lg.versionFingerprint !== input.versionFingerprint) {
|
|
72
|
+
continue;
|
|
73
|
+
}
|
|
74
|
+
// Config fingerprint folded in #57 phase 2 — skip the check when
|
|
75
|
+
// either side is undefined (legacy entries / test fixtures); reject
|
|
76
|
+
// only when both are defined and differ.
|
|
77
|
+
if (lg.configFingerprint !== undefined &&
|
|
78
|
+
input.configFingerprint !== undefined &&
|
|
79
|
+
lg.configFingerprint !== input.configFingerprint) {
|
|
80
|
+
continue;
|
|
81
|
+
}
|
|
82
|
+
// Branch 1: exact prefix match (always short-circuit).
|
|
83
|
+
if (lg.prefix === input.prefix) {
|
|
84
|
+
return { ghost: lg.ghost, branch: "exact", source: lg };
|
|
85
|
+
}
|
|
86
|
+
// Branch 2: typeahead-delta — user typed forward into the ghost.
|
|
87
|
+
if (input.prefix.startsWith(lg.prefix)) {
|
|
88
|
+
const delta = input.prefix.slice(lg.prefix.length);
|
|
89
|
+
if (delta.length > 0 && lg.ghost.startsWith(delta)) {
|
|
90
|
+
const remainder = lg.ghost.slice(delta.length);
|
|
91
|
+
if (remainder.length > 0) {
|
|
92
|
+
return { ghost: remainder, branch: "typeahead-delta", source: lg };
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
// Branch 3: backspace-of-N — only when cooldown is active.
|
|
97
|
+
if (input.cooldownActive &&
|
|
98
|
+
lg.prefix.startsWith(input.prefix) &&
|
|
99
|
+
lg.prefix.length > input.prefix.length &&
|
|
100
|
+
lg.prefix.length - input.prefix.length <= BACKSPACE_MAX_DELTA) {
|
|
101
|
+
const droppedTail = lg.prefix.slice(input.prefix.length);
|
|
102
|
+
return { ghost: droppedTail + lg.ghost, branch: "backspace-of-N", source: lg };
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
return null;
|
|
106
|
+
}
|
|
107
|
+
//# sourceMappingURL=speculative.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"speculative.js","sourceRoot":"","sources":["../src/speculative.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAEH,MAAM,mBAAmB,GAAG,CAAC,CAAC;AAyC9B;;GAEG;AACH,MAAM,OAAO,cAAc;IAGG;IAFZ,GAAG,GAAgB,EAAE,CAAC;IAEvC,YAA6B,WAAmB,CAAC;QAApB,aAAQ,GAAR,QAAQ,CAAY;IAAG,CAAC;IAErD,IAAI,IAAI;QACP,OAAO,IAAI,CAAC,GAAG,CAAC,MAAM,CAAC;IACxB,CAAC;IAED,IAAI,CAAC,KAAgB;QACpB,8EAA8E;QAC9E,4EAA4E;QAC5E,4BAA4B;QAC5B,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,IAAI,CAAC,GAAG,CAAC,MAAM,EAAE,CAAC,EAAE,EAAE,CAAC;YAC1C,MAAM,CAAC,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,CAAE,CAAC;YACvB,IACC,CAAC,CAAC,MAAM,KAAK,KAAK,CAAC,MAAM;gBACzB,CAAC,CAAC,kBAAkB,KAAK,KAAK,CAAC,kBAAkB;gBACjD,CAAC,CAAC,mBAAmB,KAAK,KAAK,CAAC,mBAAmB;gBACnD,CAAC,CAAC,kBAAkB,KAAK,KAAK,CAAC,kBAAkB,EAChD,CAAC;gBACF,IAAI,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC;gBACtB,MAAM;YACP,CAAC;QACF,CAAC;QACD,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;QACrB,OAAO,IAAI,CAAC,GAAG,CAAC,MAAM,GAAG,IAAI,CAAC,QAAQ;YAAE,IAAI,CAAC,GAAG,CAAC,KAAK,EAAE,CAAC;IAC1D,CAAC;IAED,OAAO;QACN,OAAO,IAAI,CAAC,GAAG,CAAC;IACjB,CAAC;IAED,KAAK;QACJ,IAAI,CAAC,GAAG,CAAC,MAAM,GAAG,CAAC,CAAC;IACrB,CAAC;CACD;AAED;;;;;;;;;GASG;AACH,MAAM,UAAU,OAAO,CACtB,IAAoB,EACpB,KAA4B;IAE5B,MAAM,OAAO,GAAG,IAAI,CAAC,OAAO,EAAE,CAAC;IAC/B,gBAAgB;IAChB,KAAK,IAAI,CAAC,GAAG,OAAO,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC,IAAI,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC;QAC9C,MAAM,EAAE,GAAG,OAAO,CAAC,CAAC,CAAE,CAAC;QACvB,IACC,EAAE,CAAC,kBAAkB,KAAK,KAAK,CAAC,kBAAkB;YAClD,EAAE,CAAC,mBAAmB,KAAK,KAAK,CAAC,mBAAmB;YACpD,EAAE,CAAC,kBAAkB,KAAK,KAAK,CAAC,kBAAkB,EACjD,CAAC;YACF,SAAS;QACV,CAAC;QACD,iEAAiE;QACjE,oEAAoE;QACpE,yCAAyC;QACzC,IACC,EAAE,CAAC,iBAAiB,KAAK,SAAS;YAClC,KAAK,CAAC,iBAAiB,KAAK,SAAS;YACrC,EAAE,CAAC,iBAAiB,KAAK,KAAK,CAAC,iBAAiB,EAC/C,CAAC;YACF,SAAS;QACV,CAAC;QAED,uDAAuD;QACvD,IAAI,EAAE,CAAC,MAAM,KAAK,KAAK,CAAC,MAAM,EAAE,CAAC;YAChC,OAAO,EAAE,KAAK,EAAE,EAAE,CAAC,KAAK,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,EAAE,EAAE,CAAC;QACzD,CAAC;QAED,iEAAiE;QACjE,IAAI,KAAK,CAAC,MAAM,CAAC,UAAU,CAAC,EAAE,CAAC,MAAM,CAAC,EAAE,CAAC;YACxC,MAAM,KAAK,GAAG,KAAK,CAAC,MAAM,CAAC,KAAK,CAAC,EAAE,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC;YACnD,IAAI,KAAK,CAAC,MAAM,GAAG,CAAC,IAAI,EAAE,CAAC,KAAK,CAAC,UAAU,CAAC,KAAK,CAAC,EAAE,CAAC;gBACpD,MAAM,SAAS,GAAG,EAAE,CAAC,KAAK,CAAC,KAAK,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC;gBAC/C,IAAI,SAAS,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;oBAC1B,OAAO,EAAE,KAAK,EAAE,SAAS,EAAE,MAAM,EAAE,iBAAiB,EAAE,MAAM,EAAE,EAAE,EAAE,CAAC;gBACpE,CAAC;YACF,CAAC;QACF,CAAC;QAED,2DAA2D;QAC3D,IACC,KAAK,CAAC,cAAc;YACpB,EAAE,CAAC,MAAM,CAAC,UAAU,CAAC,KAAK,CAAC,MAAM,CAAC;YAClC,EAAE,CAAC,MAAM,CAAC,MAAM,GAAG,KAAK,CAAC,MAAM,CAAC,MAAM;YACtC,EAAE,CAAC,MAAM,CAAC,MAAM,GAAG,KAAK,CAAC,MAAM,CAAC,MAAM,IAAI,mBAAmB,EAC5D,CAAC;YACF,MAAM,WAAW,GAAG,EAAE,CAAC,MAAM,CAAC,KAAK,CAAC,KAAK,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC;YACzD,OAAO,EAAE,KAAK,EAAE,WAAW,GAAG,EAAE,CAAC,KAAK,EAAE,MAAM,EAAE,gBAAgB,EAAE,MAAM,EAAE,EAAE,EAAE,CAAC;QAChF,CAAC;IACF,CAAC;IACD,OAAO,IAAI,CAAC;AACb,CAAC"}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Type-aware compact summaries of tool outputs for inclusion in ghost
|
|
3
|
+
* context. See PRD §M6c.
|
|
4
|
+
*
|
|
5
|
+
* Each summarizer takes the flattened tool message (`toolName` + raw
|
|
6
|
+
* `text`) and produces a short, typed description. The goal is to
|
|
7
|
+
* preserve the *signal* of the tool result (file path, command, exit
|
|
8
|
+
* code, top symbol) without dragging the full output into the prompt.
|
|
9
|
+
*
|
|
10
|
+
* Pure module: no I/O. Dispatch by `toolName` (case-insensitive); falls
|
|
11
|
+
* back to a generic short-line summary for unknown tools.
|
|
12
|
+
*/
|
|
13
|
+
import type { RecentMessage } from "./context-aggregator.js";
|
|
14
|
+
export declare const DEFAULT_TOOL_MAX = 3;
|
|
15
|
+
export interface ToolSummary {
|
|
16
|
+
text: string;
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* Summarize a single tool entry. Returns a new `RecentMessage` with the
|
|
20
|
+
* compact summary as `text`; preserves `role` and `toolName`.
|
|
21
|
+
*
|
|
22
|
+
* Note: PRD §M6c specs this as `summarizeToolResult(entry): ToolSummary`
|
|
23
|
+
* — we deviate by returning a `RecentMessage` so callers can preserve
|
|
24
|
+
* `role`/`toolName` for downstream prompt formatting in `truncate.ts` and
|
|
25
|
+
* `openai-compat-provider.ts`. The internal `ToolSummary` type is the
|
|
26
|
+
* dispatcher return; it carries the rewritten `text` only.
|
|
27
|
+
*/
|
|
28
|
+
export declare function summarizeToolEntry(entry: RecentMessage): RecentMessage;
|
|
29
|
+
/**
|
|
30
|
+
* Apply summarization across an array, optionally capping the number of
|
|
31
|
+
* tool entries retained (PRD §M6c: cap 3 by default). Non-tool entries
|
|
32
|
+
* pass through unchanged.
|
|
33
|
+
*/
|
|
34
|
+
export declare function summarizeAndCapTools(entries: RecentMessage[], maxTools?: number): RecentMessage[];
|
|
35
|
+
/**
|
|
36
|
+
* Parse `PI_GHOST_CONTEXT_TOOL_MAX`. Returns fallback on bad input or
|
|
37
|
+
* negative values.
|
|
38
|
+
*/
|
|
39
|
+
export declare function parseToolMaxEnv(raw: string | undefined): number;
|
|
40
|
+
//# sourceMappingURL=tool-summarize.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"tool-summarize.d.ts","sourceRoot":"","sources":["../src/tool-summarize.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAEH,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,yBAAyB,CAAC;AAE7D,eAAO,MAAM,gBAAgB,IAAI,CAAC;AAKlC,MAAM,WAAW,WAAW;IAC3B,IAAI,EAAE,MAAM,CAAC;CACb;AAED;;;;;;;;;GASG;AACH,wBAAgB,kBAAkB,CAAC,KAAK,EAAE,aAAa,GAAG,aAAa,CAKtE;AAED;;;;GAIG;AACH,wBAAgB,oBAAoB,CACnC,OAAO,EAAE,aAAa,EAAE,EACxB,QAAQ,GAAE,MAAyB,GACjC,aAAa,EAAE,CAgBjB;AAoLD;;;GAGG;AACH,wBAAgB,eAAe,CAAC,GAAG,EAAE,MAAM,GAAG,SAAS,GAAG,MAAM,CAK/D"}
|