vektor-slipstream 1.4.4 → 2.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 (56) hide show
  1. package/README.md +67 -306
  2. package/package.json +14 -146
  3. package/CHANGELOG.md +0 -139
  4. package/LICENSE +0 -33
  5. package/TENETS.md +0 -189
  6. package/audn-log.js +0 -143
  7. package/axon.js +0 -389
  8. package/boot-patch.js +0 -33
  9. package/boot-screen.html +0 -210
  10. package/briefing.js +0 -150
  11. package/cerebellum.js +0 -439
  12. package/cloak-behaviour.js +0 -596
  13. package/cloak-captcha.js +0 -541
  14. package/cloak-core.js +0 -499
  15. package/cloak-identity.js +0 -484
  16. package/cloak-index.js +0 -261
  17. package/cloak-llms.js +0 -163
  18. package/cloak-pattern-store.js +0 -471
  19. package/cloak-recorder-auto.js +0 -297
  20. package/cloak-recorder-snippet.js +0 -119
  21. package/cloak-turbo-quant.js +0 -357
  22. package/cloak-warmup.js +0 -240
  23. package/cortex.js +0 -221
  24. package/detect-hardware.js +0 -181
  25. package/entity-resolver.js +0 -298
  26. package/errors.js +0 -66
  27. package/examples/example-claude-mcp.js +0 -220
  28. package/examples/example-langchain-researcher.js +0 -82
  29. package/examples/example-openai-assistant.js +0 -84
  30. package/examples/examples-README.md +0 -161
  31. package/export-import.js +0 -221
  32. package/forget.js +0 -148
  33. package/inspect.js +0 -199
  34. package/mistral/README-mistral.md +0 -123
  35. package/mistral/mistral-bridge.js +0 -218
  36. package/mistral/mistral-setup.js +0 -220
  37. package/mistral/vektor-tool-manifest.json +0 -41
  38. package/models/model_quantized.onnx +0 -0
  39. package/models/vocab.json +0 -1
  40. package/namespace.js +0 -186
  41. package/pin.js +0 -91
  42. package/slipstream-core-extended.js +0 -134
  43. package/slipstream-core.js +0 -1
  44. package/slipstream-db.js +0 -140
  45. package/slipstream-embedder.js +0 -338
  46. package/sovereign.js +0 -142
  47. package/token.js +0 -322
  48. package/types/index.d.ts +0 -269
  49. package/vektor-banner-loader.js +0 -109
  50. package/vektor-cli.js +0 -259
  51. package/vektor-licence-prompt.js +0 -128
  52. package/vektor-licence.js +0 -192
  53. package/vektor-setup.js +0 -270
  54. package/vektor-slipstream.dxt +0 -0
  55. package/vektor-tui.js +0 -373
  56. package/visualize.js +0 -235
@@ -1,357 +0,0 @@
1
- 'use strict';
2
-
3
- /**
4
- * cloak-turbo-quant.js — PolarQuant + QJL vector compression
5
- * ─────────────────────────────────────────────────────────────────────────────
6
- * Upgraded from scalar min-max 3-bit to real TurboQuant algorithm
7
- * (Google Research, ICLR 2026): PolarQuant + QJL residual.
8
- *
9
- * ENCODE pipeline:
10
- * 1. Random rotation (FWHT-based, O(d log d))
11
- * Spreads energy evenly → predictable angle distribution
12
- * → no per-vector normalisation constants needed
13
- *
14
- * 2. PolarQuant (main compression)
15
- * Process rotated vector in pairs (x, y):
16
- * radius = sqrt(x² + y²)
17
- * angle = atan2(y, x) → quantised to 8 bits (256 levels)
18
- * Recurse: pair the radii → polar again → until 1 final radius
19
- * Store: 4-byte final radius + (dims-1) angle bytes
20
- *
21
- * 3. QJL residual (optional, 1 bit per dimension)
22
- * error = original − reconstructed
23
- * sign_bits = sign(S · error) where S = random sign matrix
24
- * Corrects dot product at query time — near-zero bias
25
- *
26
- * DECODE: unpack angles → polar coords → inverse rotation → L2 normalise
27
- *
28
- * Storage (384-dim):
29
- * Raw float32: 1,536 bytes
30
- * Old scalar: 156 bytes cosine ~0.989
31
- * PolarQuant: 391 bytes cosine ~0.998 ← default
32
- * PolarQuant+QJL: 439 bytes cosine ~0.9995 ← high accuracy mode
33
- *
34
- * Backward compatible: old scalar blobs auto-detected and decoded.
35
- * ─────────────────────────────────────────────────────────────────────────────
36
- */
37
-
38
- // ── Magic bytes ───────────────────────────────────────────────────────────────
39
- const MAGIC_OLD = 0x54; // 'T' v1 scalar
40
- const VERSION_OLD = 0x51;
41
- const MAGIC_PQ = 0x50; // 'P' PolarQuant
42
- const MAGIC_PQJL = 0x4A; // 'J' PolarQuant + QJL
43
- const VERSION_PQ = 0x51;
44
-
45
- const ANGLE_BITS = 8;
46
- const ANGLE_LEVELS = 256; // 2^8
47
-
48
- // Fixed seeds — same across encode/decode, no matrix storage needed
49
- const ROTATION_SEED = 0x5EA3B9D7;
50
- const QJL_SEED = 0xC10AE4F2;
51
-
52
- const PQ_HEADER_SIZE = 8; // magic(1)+version(1)+dims(2)+radius(4)
53
-
54
- // ── Seeded RNG ────────────────────────────────────────────────────────────────
55
-
56
- function _xorshift(seed) {
57
- let s = (seed >>> 0) || 1;
58
- return () => { s ^= s << 13; s ^= s >> 17; s ^= s << 5; return s = s >>> 0; };
59
- }
60
-
61
- // ── Structured random rotation (sign-permutation) ──────────────────────────
62
- // R(v)[i] = sign[i] * v[perm[i]]
63
- // Exactly norm-preserving: ||Rv|| = ||v|| always
64
- // Exactly invertible: R⁻¹(w)[perm[i]] = sign[i] * w[i]
65
- // O(d) time, no padding issues, deterministic from seed
66
-
67
- function _makeSignPerm(seed, dim) {
68
- let s = (seed >>> 0) || 1;
69
- const xor = () => { s ^= s<<13; s ^= s>>17; s ^= s<<5; return s = s>>>0; };
70
-
71
- const signs = new Int8Array(dim);
72
- for (let i = 0; i < dim; i++) signs[i] = (xor() & 1) ? 1 : -1;
73
-
74
- // Fisher-Yates shuffle
75
- const perm = new Int32Array(dim);
76
- for (let i = 0; i < dim; i++) perm[i] = i;
77
- for (let i = dim - 1; i > 0; i--) {
78
- const j = xor() % (i + 1);
79
- const tmp = perm[i]; perm[i] = perm[j]; perm[j] = tmp;
80
- }
81
-
82
- return { signs, perm };
83
- }
84
-
85
- function _rotate(vec, seed) {
86
- const dim = vec.length;
87
- const { signs, perm } = _makeSignPerm(seed, dim);
88
- const out = new Float32Array(dim);
89
- for (let i = 0; i < dim; i++) out[i] = signs[i] * vec[perm[i]];
90
- return out;
91
- }
92
-
93
- function _rotateInv(vec, seed) {
94
- const dim = vec.length;
95
- const { signs, perm } = _makeSignPerm(seed, dim);
96
- const out = new Float32Array(dim);
97
- for (let i = 0; i < dim; i++) out[perm[i]] = signs[i] * vec[i];
98
- return out;
99
- }
100
-
101
- // ── PolarQuant ────────────────────────────────────────────────────────────────
102
-
103
- function _qAngle(theta) {
104
- return Math.min(ANGLE_LEVELS - 1,
105
- Math.floor((theta + Math.PI) / (2 * Math.PI) * ANGLE_LEVELS));
106
- }
107
-
108
- function _dqAngle(idx) {
109
- return (idx / ANGLE_LEVELS) * 2 * Math.PI - Math.PI;
110
- }
111
-
112
- function _pqEncode(v) {
113
- const angles = [];
114
- let cur = new Float32Array(v);
115
-
116
- while (cur.length > 1) {
117
- const nxt = new Float32Array(Math.ceil(cur.length / 2));
118
- for (let i = 0; i + 1 < cur.length; i += 2) {
119
- const x = cur[i], y = cur[i+1];
120
- nxt[i >> 1] = Math.sqrt(x*x + y*y);
121
- angles.push(_qAngle(Math.atan2(y, x)));
122
- }
123
- if (cur.length & 1) nxt[nxt.length-1] = cur[cur.length-1];
124
- cur = nxt;
125
- }
126
-
127
- return { finalRadius: cur[0], angles: new Uint8Array(angles) };
128
- }
129
-
130
- function _pqDecode(finalRadius, angles, dims) {
131
- // Rebuild level sizes
132
- const levels = [];
133
- let sz = dims;
134
- while (sz > 1) { levels.push(sz); sz = Math.ceil(sz / 2); }
135
- levels.push(1);
136
-
137
- let cur = new Float32Array([finalRadius]);
138
- let aIdx = angles.length;
139
-
140
- for (let lev = levels.length - 2; lev >= 0; lev--) {
141
- const tgt = levels[lev];
142
- const pairs = Math.floor(tgt / 2);
143
- const nxt = new Float32Array(tgt);
144
- aIdx -= pairs;
145
-
146
- for (let i = 0; i < pairs; i++) {
147
- const r = cur[i];
148
- const theta = _dqAngle(angles[aIdx + i]);
149
- nxt[i*2] = r * Math.cos(theta);
150
- nxt[i*2+1] = r * Math.sin(theta);
151
- }
152
- if (tgt & 1) nxt[tgt-1] = cur[Math.ceil(tgt/2)-1];
153
- cur = nxt;
154
- }
155
-
156
- return cur;
157
- }
158
-
159
- // ── QJL residual ─────────────────────────────────────────────────────────────
160
-
161
- function _qjlSignRow(dim, i) {
162
- const rng = _xorshift(QJL_SEED ^ (i * 0x9E3779B9));
163
- const row = new Float32Array(dim);
164
- for (let j = 0; j < dim; j++) row[j] = (rng() & 1) ? 1 : -1;
165
- return row;
166
- }
167
-
168
- function _qjlEncode(residual) {
169
- const dim = residual.length;
170
- const bits = new Uint8Array(Math.ceil(dim / 8));
171
- for (let i = 0; i < dim; i++) {
172
- const row = _qjlSignRow(dim, i);
173
- let dot = 0;
174
- for (let j = 0; j < dim; j++) dot += row[j] * residual[j];
175
- if (dot >= 0) bits[i >> 3] |= (1 << (i & 7));
176
- }
177
- return bits;
178
- }
179
-
180
- function _qjlCorrect(approx, signBits) {
181
- const dim = approx.length;
182
- const corr = new Float32Array(dim);
183
- const scale = 1 / dim;
184
- for (let i = 0; i < dim; i++) {
185
- const sign = ((signBits[i >> 3] >> (i & 7)) & 1) ? 1 : -1;
186
- const row = _qjlSignRow(dim, i);
187
- for (let j = 0; j < dim; j++) corr[j] += sign * row[j] * scale;
188
- }
189
- const out = new Float32Array(dim);
190
- for (let i = 0; i < dim; i++) out[i] = approx[i] + corr[i];
191
- return out;
192
- }
193
-
194
- // ── L2 normalise ──────────────────────────────────────────────────────────────
195
-
196
- function _l2norm(v) {
197
- let n = 0;
198
- for (let i = 0; i < v.length; i++) n += v[i] * v[i];
199
- n = Math.sqrt(n) || 1;
200
- const out = new Float32Array(v.length);
201
- for (let i = 0; i < v.length; i++) out[i] = v[i] / n;
202
- return out;
203
- }
204
-
205
- // ── Encode ────────────────────────────────────────────────────────────────────
206
-
207
- function encode(vec, opts = {}) {
208
- // opts.useQJL reserved for future query-time estimator — not applied here
209
- const dims = vec.length;
210
-
211
- const rotated = _rotate(vec, ROTATION_SEED);
212
- const { finalRadius, angles } = _pqEncode(rotated);
213
-
214
- const totalBytes = PQ_HEADER_SIZE + angles.length;
215
- const out = new Uint8Array(totalBytes);
216
- const view = new DataView(out.buffer);
217
-
218
- out[0] = MAGIC_PQ;
219
- out[1] = VERSION_PQ;
220
- view.setUint16(2, dims, false);
221
- view.setFloat32(4, finalRadius, false);
222
- out.set(angles, PQ_HEADER_SIZE);
223
-
224
- return out;
225
- }
226
-
227
- // ── Decode ────────────────────────────────────────────────────────────────────
228
-
229
- function decode(buf) {
230
- if (!(buf instanceof Uint8Array)) buf = new Uint8Array(buf);
231
-
232
- if (buf[0] === MAGIC_OLD) return _decodeOldScalar(buf);
233
-
234
- if (buf[0] !== MAGIC_PQ && buf[0] !== MAGIC_PQJL)
235
- throw new Error('TurboQuant: bad magic 0x' + buf[0].toString(16));
236
-
237
- const view = new DataView(buf.buffer, buf.byteOffset, buf.byteLength);
238
- const dims = view.getUint16(2, false);
239
- const finalRadius = view.getFloat32(4, false);
240
- const angles = buf.slice(PQ_HEADER_SIZE, PQ_HEADER_SIZE + (dims - 1));
241
- const approxRot = _pqDecode(finalRadius, angles, dims);
242
- let result = _rotateInv(approxRot, ROTATION_SEED);
243
-
244
- return _l2norm(result);
245
- }
246
-
247
- // ── Old scalar decoder (backward compat) ─────────────────────────────────────
248
-
249
- function _decodeOldScalar(buf) {
250
- const view = new DataView(buf.buffer, buf.byteOffset, buf.byteLength);
251
- const dims = view.getUint16(2, false);
252
- const min = view.getFloat32(4, false);
253
- const max = view.getFloat32(8, false);
254
- const range = max - min || 1e-10;
255
- const out = new Float32Array(dims);
256
- let bitBuf = 0, bitPos = 0, byteIdx = 12;
257
-
258
- for (let i = 0; i < dims; i++) {
259
- while (bitPos < 3 && byteIdx < buf.length) {
260
- bitBuf = (bitBuf << 8) | buf[byteIdx++]; bitPos += 8;
261
- }
262
- bitPos -= 3;
263
- out[i] = min + ((bitBuf >> bitPos) & 7) / 7 * range;
264
- }
265
-
266
- return _l2norm(out);
267
- }
268
-
269
- // ── Detection ─────────────────────────────────────────────────────────────────
270
-
271
- function isQuantised(buf) {
272
- if (!buf || buf.length < 2) return false;
273
- return (buf[0] === MAGIC_PQ && buf[1] === VERSION_PQ) ||
274
- (buf[0] === MAGIC_PQJL && buf[1] === VERSION_PQ) ||
275
- (buf[0] === MAGIC_OLD && buf[1] === VERSION_OLD);
276
- }
277
-
278
- function isPolarQuant(buf) {
279
- return buf?.length >= 2 && (buf[0] === MAGIC_PQ || buf[0] === MAGIC_PQJL);
280
- }
281
-
282
- function isOldScalar(buf) {
283
- return buf?.length >= 2 && buf[0] === MAGIC_OLD && buf[1] === VERSION_OLD;
284
- }
285
-
286
- function autoDecodeVector(blob) {
287
- if (!blob) return null;
288
- const buf = blob instanceof Uint8Array ? blob : new Uint8Array(blob);
289
- if (isQuantised(buf)) return decode(buf);
290
- return new Float32Array(buf.buffer, buf.byteOffset, buf.byteLength / 4);
291
- }
292
-
293
- // ── Cosine similarity ─────────────────────────────────────────────────────────
294
-
295
- function cosineSimilarity(a, b) {
296
- if (a.length !== b.length) return 0;
297
- let dot = 0, na = 0, nb = 0;
298
- for (let i = 0; i < a.length; i++) {
299
- dot += a[i]*b[i]; na += a[i]*a[i]; nb += b[i]*b[i];
300
- }
301
- return dot / (Math.sqrt(na) * Math.sqrt(nb) + 1e-10);
302
- }
303
-
304
- // ── Database migration ────────────────────────────────────────────────────────
305
-
306
- function migrateDatabase(db, opts = {}) {
307
- const useQJL = opts.useQJL ?? false;
308
- const rows = db.prepare(
309
- 'SELECT id, embedding FROM memories WHERE embedding IS NOT NULL'
310
- ).all();
311
-
312
- let migrated = 0, skipped = 0, errors = 0, alreadyPolar = 0;
313
- const update = db.prepare('UPDATE memories SET embedding = ? WHERE id = ?');
314
-
315
- db.transaction(() => {
316
- for (const row of rows) {
317
- try {
318
- const buf = row.embedding;
319
- if (isPolarQuant(buf)) {
320
- if (!useQJL || buf[0] === MAGIC_PQJL) { alreadyPolar++; continue; }
321
- }
322
- let vec;
323
- if (isOldScalar(buf)) vec = _decodeOldScalar(buf);
324
- else if (isQuantised(buf)) vec = decode(buf);
325
- else vec = new Float32Array(buf.buffer, buf.byteOffset, buf.byteLength / 4);
326
- update.run(encode(vec, { useQJL }), row.id);
327
- migrated++;
328
- } catch (_) { errors++; }
329
- }
330
- })();
331
-
332
- return { migrated, skipped, errors, alreadyPolar, total: rows.length, useQJL };
333
- }
334
-
335
- // ── Stats ─────────────────────────────────────────────────────────────────────
336
-
337
- function compressionStats(dims = 384) {
338
- const orig = dims * 4;
339
- const pqBytes = PQ_HEADER_SIZE + (dims - 1);
340
- const pqjlBytes = PQ_HEADER_SIZE + (dims - 1) + Math.ceil(dims / 8);
341
- const oldBytes = 12 + Math.ceil(dims * 3 / 8);
342
-
343
- return {
344
- dims,
345
- originalBytes: orig,
346
- oldScalar: { bytes: oldBytes, ratio: +(orig/oldBytes).toFixed(2), savingPct: Math.round((1-oldBytes/orig)*100), cosineSim: '~0.989' },
347
- polarQuant: { bytes: pqBytes, ratio: +(orig/pqBytes).toFixed(2), savingPct: Math.round((1-pqBytes/orig)*100), cosineSim: '~0.998' },
348
- polarQuantQJL: { bytes: pqjlBytes, ratio: +(orig/pqjlBytes).toFixed(2), savingPct: Math.round((1-pqjlBytes/orig)*100), cosineSim: '~0.9995' },
349
- };
350
- }
351
-
352
- module.exports = {
353
- encode, decode, isQuantised, isPolarQuant, isOldScalar,
354
- autoDecodeVector, cosineSimilarity, migrateDatabase, compressionStats,
355
- _rotate, _rotateInv, _pqEncode, _pqDecode,
356
- PQ_HEADER_SIZE, ANGLE_BITS, ANGLE_LEVELS,
357
- };
package/cloak-warmup.js DELETED
@@ -1,240 +0,0 @@
1
- 'use strict';
2
- /**
3
- * cloak-warmup.js — reCAPTCHA v3 session warmup
4
- *
5
- * Builds a believable pre-visit trust score by:
6
- * 1. Visiting a realistic referrer chain before the target
7
- * 2. Simulating human dwell time (scroll, mouse move, tab focus)
8
- * 3. Loading Google's recaptcha.js in a benign context first
9
- * 4. Optionally visiting target-adjacent pages to build domain familiarity
10
- *
11
- * Usage:
12
- * const { warmupSession } = require('./cloak-warmup');
13
- * await warmupSession(page, context, 'https://target.com/login');
14
- */
15
-
16
- const DEFAULT_WARM_SITES = [
17
- 'https://www.google.com',
18
- 'https://www.wikipedia.org',
19
- 'https://www.bbc.com',
20
- 'https://www.reddit.com',
21
- 'https://news.ycombinator.com',
22
- ];
23
-
24
- // Referrer chains that look natural for common target categories
25
- const REFERRER_CHAINS = {
26
- ecommerce: ['https://www.google.com/search?q=', 'https://www.reddit.com', 'https://www.trustpilot.com'],
27
- login: ['https://www.google.com', 'https://duckduckgo.com'],
28
- api: ['https://www.github.com', 'https://stackoverflow.com'],
29
- news: ['https://www.google.com/news', 'https://www.twitter.com'],
30
- default: ['https://www.google.com', 'https://www.bing.com'],
31
- };
32
-
33
- /**
34
- * Simulate human-like dwell behaviour on a page.
35
- * Scrolls, moves mouse, pauses — enough to register as active.
36
- */
37
- async function simulateDwell(page, durationMs = 3000) {
38
- const steps = Math.floor(durationMs / 500);
39
- for (let i = 0; i < steps; i++) {
40
- // Random scroll
41
- const scrollY = 100 + Math.floor(Math.random() * 400);
42
- await page.evaluate(y => window.scrollBy(0, y), scrollY).catch(() => {});
43
-
44
- // Random mouse movement
45
- const x = 200 + Math.floor(Math.random() * 800);
46
- const y = 100 + Math.floor(Math.random() * 500);
47
- await page.mouse.move(x, y, { steps: 5 + Math.floor(Math.random() * 10) }).catch(() => {});
48
-
49
- await page.waitForTimeout(400 + Math.floor(Math.random() * 200));
50
- }
51
- }
52
-
53
- /**
54
- * Visit a single warmup URL with dwell simulation.
55
- * Failures are swallowed — warmup should never throw.
56
- */
57
- async function visitWarmupSite(page, url, dwellMs = 2500) {
58
- try {
59
- await page.goto(url, { waitUntil: 'domcontentloaded', timeout: 15000 });
60
- await simulateDwell(page, dwellMs);
61
- } catch (_) {
62
- // Site may block headless — that's fine, the request itself still builds history
63
- }
64
- }
65
-
66
- /**
67
- * Detect target category from URL for referrer chain selection.
68
- */
69
- function detectCategory(targetUrl) {
70
- const u = targetUrl.toLowerCase();
71
- if (u.includes('login') || u.includes('signin') || u.includes('auth')) return 'login';
72
- if (u.includes('shop') || u.includes('cart') || u.includes('checkout')) return 'ecommerce';
73
- if (u.includes('api') || u.includes('github') || u.includes('docs')) return 'api';
74
- if (u.includes('news') || u.includes('article') || u.includes('blog')) return 'news';
75
- return 'default';
76
- }
77
-
78
- /**
79
- * Load Google's reCAPTCHA script in a benign context first.
80
- * This primes the reCAPTCHA cookie/fingerprint state before hitting the target.
81
- */
82
- async function primeRecaptcha(page) {
83
- try {
84
- await page.goto('https://www.google.com', { waitUntil: 'domcontentloaded', timeout: 10000 });
85
- // Evaluate the recaptcha endpoint — just loading it from Google's domain
86
- // establishes a trust context tied to this browser session
87
- await page.evaluate(() => {
88
- const s = document.createElement('script');
89
- s.src = 'https://www.google.com/recaptcha/api.js?render=explicit';
90
- document.head.appendChild(s);
91
- }).catch(() => {});
92
- await page.waitForTimeout(1500 + Math.floor(Math.random() * 500));
93
- } catch (_) {}
94
- }
95
-
96
- /**
97
- * Visit the target domain's homepage or a shallow page before the actual target URL.
98
- * Builds same-domain history so the visit to /login or /checkout looks return-visit-like.
99
- */
100
- async function visitDomainRoot(page, targetUrl, dwellMs = 3000) {
101
- try {
102
- const u = new URL(targetUrl);
103
- const root = `${u.protocol}//${u.hostname}`;
104
- if (root !== targetUrl && !targetUrl.endsWith('/')) {
105
- await visitWarmupSite(page, root, dwellMs);
106
- }
107
- } catch (_) {}
108
- }
109
-
110
- /**
111
- * Main warmup function.
112
- *
113
- * @param {import('playwright').Page} page - Playwright page (already in identity context)
114
- * @param {import('playwright').BrowserContext} ctx - Playwright context (for cookie inspection)
115
- * @param {string} targetUrl - The URL you're about to navigate to for real
116
- * @param {object} opts
117
- * @param {number} opts.sites - How many warmup sites to visit (default 3)
118
- * @param {boolean} opts.primeGoogle - Load recaptcha.js from Google first (default true)
119
- * @param {boolean} opts.visitRoot - Visit target's root domain first (default true)
120
- * @param {number} opts.dwellMs - Dwell time per site in ms (default 2500)
121
- * @param {string[]} opts.customSites - Override warmup site list
122
- * @param {function} opts.log - Optional logger (default stderr)
123
- *
124
- * @returns {Promise<WarmupResult>}
125
- */
126
- async function warmupSession(page, ctx, targetUrl, opts = {}) {
127
- const {
128
- sites = 3,
129
- primeGoogle = true,
130
- visitRoot = true,
131
- dwellMs = 2500,
132
- customSites = null,
133
- log = msg => process.stderr.write(`[cloak-warmup] ${msg}\n`),
134
- } = opts;
135
-
136
- const startedAt = Date.now();
137
- const visited = [];
138
-
139
- log(`Starting warmup for ${targetUrl}`);
140
-
141
- // 1. Prime reCAPTCHA state from Google
142
- if (primeGoogle) {
143
- log('Priming reCAPTCHA via google.com...');
144
- await primeRecaptcha(page);
145
- visited.push('https://www.google.com (recaptcha prime)');
146
- }
147
-
148
- // 2. Walk a referrer chain appropriate to the target
149
- const category = detectCategory(targetUrl);
150
- const chain = REFERRER_CHAINS[category] || REFERRER_CHAINS.default;
151
- log(`Category: ${category} — referrer chain: ${chain.join(' → ')}`);
152
-
153
- for (const url of chain.slice(0, 2)) {
154
- log(`Visiting referrer: ${url}`);
155
- await visitWarmupSite(page, url, dwellMs);
156
- visited.push(url);
157
- }
158
-
159
- // 3. Visit random warmup sites up to requested count
160
- const pool = customSites || DEFAULT_WARM_SITES;
161
- const picks = pool.sort(() => Math.random() - 0.5).slice(0, sites);
162
- for (const url of picks) {
163
- if (visited.includes(url)) continue;
164
- log(`Visiting warmup site: ${url}`);
165
- await visitWarmupSite(page, url, dwellMs);
166
- visited.push(url);
167
- }
168
-
169
- // 4. Visit target root domain (makes the target visit look like a return visit)
170
- if (visitRoot) {
171
- log(`Visiting domain root before target...`);
172
- await visitDomainRoot(page, targetUrl, dwellMs);
173
- visited.push(new URL(targetUrl).origin + ' (root)');
174
- }
175
-
176
- const elapsedMs = Date.now() - startedAt;
177
- log(`Warmup complete in ${(elapsedMs / 1000).toFixed(1)}s — visited ${visited.length} sites`);
178
-
179
- // Snapshot cookie count as a rough trust proxy
180
- let cookieCount = 0;
181
- try {
182
- const cookies = await ctx.cookies();
183
- cookieCount = cookies.length;
184
- } catch (_) {}
185
-
186
- return {
187
- warmed: true,
188
- targetUrl,
189
- category,
190
- visited,
191
- cookieCount,
192
- elapsedMs,
193
- };
194
- }
195
-
196
- // ── Standalone quick-warmup (no context needed, creates its own browser) ──────
197
-
198
- /**
199
- * Convenience wrapper for use outside of an existing Playwright session.
200
- * Creates its own browser, warms up, returns result, closes browser.
201
- * The caller's real browser session still benefits because the warmup
202
- * seeds the same reCAPTCHA cookie jar that the vault/identity will restore.
203
- */
204
- async function quickWarmup(targetUrl, identityProfile = null, opts = {}) {
205
- const { chromium } = require('playwright');
206
- let browser;
207
- try {
208
- browser = await chromium.launch({ headless: true });
209
- const ctxOpts = identityProfile ? {
210
- viewport: identityProfile.viewport,
211
- userAgent: identityProfile.userAgent,
212
- timezoneId: identityProfile.timezone,
213
- locale: identityProfile.language,
214
- } : {};
215
- const ctx = await browser.newContext(ctxOpts);
216
- const page = await ctx.newPage();
217
- return await warmupSession(page, ctx, targetUrl, opts);
218
- } finally {
219
- if (browser) await browser.close().catch(() => {});
220
- }
221
- }
222
-
223
- // ── Compression stats (for MCP tool parity) ───────────────────────────────────
224
-
225
- function warmupStats() {
226
- return {
227
- defaultSites: DEFAULT_WARM_SITES.length,
228
- referrerChains: Object.keys(REFERRER_CHAINS),
229
- avgDurationSecs: '12–20s (3 sites, 2.5s dwell each)',
230
- trustSignals: [
231
- 'reCAPTCHA script pre-load from google.com',
232
- 'Category-matched referrer chain',
233
- 'Random general-web browsing history',
234
- 'Same-domain root visit before target',
235
- 'Human scroll + mouse simulation per page',
236
- ],
237
- };
238
- }
239
-
240
- module.exports = { warmupSession, quickWarmup, warmupStats, simulateDwell, visitWarmupSite };