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.
- package/README.md +67 -306
- package/package.json +14 -146
- package/CHANGELOG.md +0 -139
- package/LICENSE +0 -33
- package/TENETS.md +0 -189
- package/audn-log.js +0 -143
- package/axon.js +0 -389
- package/boot-patch.js +0 -33
- package/boot-screen.html +0 -210
- package/briefing.js +0 -150
- package/cerebellum.js +0 -439
- package/cloak-behaviour.js +0 -596
- package/cloak-captcha.js +0 -541
- package/cloak-core.js +0 -499
- package/cloak-identity.js +0 -484
- package/cloak-index.js +0 -261
- package/cloak-llms.js +0 -163
- package/cloak-pattern-store.js +0 -471
- package/cloak-recorder-auto.js +0 -297
- package/cloak-recorder-snippet.js +0 -119
- package/cloak-turbo-quant.js +0 -357
- package/cloak-warmup.js +0 -240
- package/cortex.js +0 -221
- package/detect-hardware.js +0 -181
- package/entity-resolver.js +0 -298
- package/errors.js +0 -66
- package/examples/example-claude-mcp.js +0 -220
- package/examples/example-langchain-researcher.js +0 -82
- package/examples/example-openai-assistant.js +0 -84
- package/examples/examples-README.md +0 -161
- package/export-import.js +0 -221
- package/forget.js +0 -148
- package/inspect.js +0 -199
- package/mistral/README-mistral.md +0 -123
- package/mistral/mistral-bridge.js +0 -218
- package/mistral/mistral-setup.js +0 -220
- package/mistral/vektor-tool-manifest.json +0 -41
- package/models/model_quantized.onnx +0 -0
- package/models/vocab.json +0 -1
- package/namespace.js +0 -186
- package/pin.js +0 -91
- package/slipstream-core-extended.js +0 -134
- package/slipstream-core.js +0 -1
- package/slipstream-db.js +0 -140
- package/slipstream-embedder.js +0 -338
- package/sovereign.js +0 -142
- package/token.js +0 -322
- package/types/index.d.ts +0 -269
- package/vektor-banner-loader.js +0 -109
- package/vektor-cli.js +0 -259
- package/vektor-licence-prompt.js +0 -128
- package/vektor-licence.js +0 -192
- package/vektor-setup.js +0 -270
- package/vektor-slipstream.dxt +0 -0
- package/vektor-tui.js +0 -373
- package/visualize.js +0 -235
package/cloak-turbo-quant.js
DELETED
|
@@ -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 };
|