adaptive-bitmask 1.0.0-rc.1

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.
@@ -0,0 +1,1141 @@
1
+ "use strict";
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __export = (target, all) => {
7
+ for (var name in all)
8
+ __defProp(target, name, { get: all[name], enumerable: true });
9
+ };
10
+ var __copyProps = (to, from, except, desc) => {
11
+ if (from && typeof from === "object" || typeof from === "function") {
12
+ for (let key of __getOwnPropNames(from))
13
+ if (!__hasOwnProp.call(to, key) && key !== except)
14
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
15
+ }
16
+ return to;
17
+ };
18
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
+
20
+ // src/ai/index.ts
21
+ var ai_exports = {};
22
+ __export(ai_exports, {
23
+ CoordinationSession: () => CoordinationSession,
24
+ createCoordinationMiddleware: () => createCoordinationMiddleware,
25
+ createCoordinationTools: () => createCoordinationTools
26
+ });
27
+ module.exports = __toCommonJS(ai_exports);
28
+
29
+ // src/transports/websocket.ts
30
+ var import_ws = require("ws");
31
+
32
+ // src/message.ts
33
+ var MESSAGE_SIZE_BYTES = 24;
34
+ var UINT32_MAX = 4294967295;
35
+ var UINT64_MAX = (1n << 64n) - 1n;
36
+ var BitmaskMessage = class _BitmaskMessage {
37
+ mask;
38
+ agentId;
39
+ timestampMs;
40
+ schemaVersion;
41
+ constructor(data) {
42
+ this._assertValid(data);
43
+ this.mask = data.mask;
44
+ this.agentId = data.agentId;
45
+ this.timestampMs = data.timestampMs;
46
+ this.schemaVersion = data.schemaVersion;
47
+ }
48
+ /** Create a message with current timestamp. */
49
+ static now(mask, agentId, schemaVersion) {
50
+ return new _BitmaskMessage({
51
+ mask,
52
+ agentId,
53
+ timestampMs: Date.now(),
54
+ schemaVersion
55
+ });
56
+ }
57
+ /** Wire size in bytes. */
58
+ get sizeBytes() {
59
+ return MESSAGE_SIZE_BYTES;
60
+ }
61
+ /**
62
+ * Serialize to 24-byte ArrayBuffer (little-endian).
63
+ *
64
+ * This is the canonical wire format. gRPC/WebSocket transports
65
+ * should send these bytes directly.
66
+ */
67
+ serialize() {
68
+ const buf = new ArrayBuffer(MESSAGE_SIZE_BYTES);
69
+ const view = new DataView(buf);
70
+ view.setBigUint64(0, this.mask, true);
71
+ view.setUint32(8, this.agentId, true);
72
+ view.setBigInt64(12, BigInt(this.timestampMs), true);
73
+ view.setUint32(20, this.schemaVersion, true);
74
+ return buf;
75
+ }
76
+ /** Serialize to Uint8Array. */
77
+ toBytes() {
78
+ return new Uint8Array(this.serialize());
79
+ }
80
+ /**
81
+ * Deserialize from ArrayBuffer or Uint8Array.
82
+ * Validates length before parsing.
83
+ */
84
+ static deserialize(data) {
85
+ const buf = data instanceof Uint8Array ? data.buffer.slice(data.byteOffset, data.byteOffset + data.byteLength) : data;
86
+ if (buf.byteLength !== MESSAGE_SIZE_BYTES) {
87
+ throw new Error(
88
+ `Invalid message: expected exactly ${MESSAGE_SIZE_BYTES} bytes, got ${buf.byteLength}`
89
+ );
90
+ }
91
+ const view = new DataView(buf);
92
+ return new _BitmaskMessage({
93
+ mask: view.getBigUint64(0, true),
94
+ agentId: view.getUint32(8, true),
95
+ timestampMs: Number(view.getBigInt64(12, true)),
96
+ schemaVersion: view.getUint32(20, true)
97
+ });
98
+ }
99
+ /**
100
+ * JSON-equivalent size for comparison.
101
+ * Useful for demonstrating compression ratio.
102
+ */
103
+ get jsonSize() {
104
+ return JSON.stringify({
105
+ mask: this.mask.toString(),
106
+ agentId: this.agentId,
107
+ timestampMs: this.timestampMs,
108
+ schemaVersion: this.schemaVersion
109
+ }).length;
110
+ }
111
+ /** Compression ratio vs JSON encoding. */
112
+ get compressionVsJson() {
113
+ return this.jsonSize / MESSAGE_SIZE_BYTES;
114
+ }
115
+ /** Human-readable string for debugging. */
116
+ toString() {
117
+ return `BitmaskMessage(agent=${this.agentId}, v=${this.schemaVersion}, bits=${this.mask.toString(2).padStart(64, "0")}, t=${this.timestampMs})`;
118
+ }
119
+ _assertValid(data) {
120
+ if (typeof data.mask !== "bigint") {
121
+ throw new TypeError(`mask must be bigint, got ${typeof data.mask}`);
122
+ }
123
+ if (data.mask < 0n || data.mask > UINT64_MAX) {
124
+ throw new RangeError(`mask out of uint64 range: ${data.mask.toString()}`);
125
+ }
126
+ this._assertUint32("agentId", data.agentId);
127
+ this._assertUint32("schemaVersion", data.schemaVersion);
128
+ if (!Number.isSafeInteger(data.timestampMs)) {
129
+ throw new RangeError(
130
+ `timestampMs must be a safe integer, got ${data.timestampMs}`
131
+ );
132
+ }
133
+ }
134
+ _assertUint32(field, value) {
135
+ if (!Number.isInteger(value) || value < 0 || value > UINT32_MAX) {
136
+ throw new RangeError(
137
+ `${field} must be an integer in [0, ${UINT32_MAX}], got ${value}`
138
+ );
139
+ }
140
+ }
141
+ };
142
+
143
+ // src/bitmask.ts
144
+ var BITMASK_WIDTH = 64;
145
+ var EMERGENCY_RANGE = [56, 63];
146
+ var HIGH_FREQ_RANGE = [0, 47];
147
+ var MED_FREQ_RANGE = [48, 55];
148
+ var SINGLE_BIT_TO_POSITION = new Map(
149
+ Array.from({ length: BITMASK_WIDTH }, (_, i) => [1n << BigInt(i), i])
150
+ );
151
+ function activeBits(mask) {
152
+ const bits = [];
153
+ forEachSetBit(mask, (bit) => bits.push(bit));
154
+ return bits;
155
+ }
156
+ function forEachSetBit(mask, fn) {
157
+ if (mask < 0n) {
158
+ throw new RangeError("Bitmask must be non-negative");
159
+ }
160
+ let m = mask;
161
+ while (m !== 0n) {
162
+ const leastSignificantBit = m & -m;
163
+ const position = SINGLE_BIT_TO_POSITION.get(leastSignificantBit);
164
+ if (position === void 0) {
165
+ throw new Error(`Invalid 64-bit mask: ${mask.toString()}`);
166
+ }
167
+ fn(position);
168
+ m ^= leastSignificantBit;
169
+ }
170
+ }
171
+ function hasEmergency(mask) {
172
+ const emergencyMask = 0xFFn << 56n;
173
+ return (mask & emergencyMask) !== 0n;
174
+ }
175
+ function encode(features, schema, options = {}) {
176
+ let mask = 0n;
177
+ let mapped = 0;
178
+ let unmapped = 0;
179
+ const unknownFeatures = [];
180
+ for (const feature of features) {
181
+ const bit = schema.get(feature);
182
+ if (bit !== void 0) {
183
+ mask |= 1n << BigInt(bit);
184
+ mapped++;
185
+ } else {
186
+ unmapped++;
187
+ unknownFeatures.push(feature);
188
+ }
189
+ }
190
+ if (options.throwOnUnknownFeatures && unknownFeatures.length > 0) {
191
+ const uniqueUnknown = [...new Set(unknownFeatures)];
192
+ throw new Error(
193
+ `Unknown features (${uniqueUnknown.length}): ${uniqueUnknown.join(", ")}`
194
+ );
195
+ }
196
+ return { mask, mapped, unmapped };
197
+ }
198
+ function decode(mask, reverseSchema) {
199
+ const features = [];
200
+ for (let i = 0; i < BITMASK_WIDTH; i++) {
201
+ if (mask & 1n << BigInt(i)) {
202
+ const feats = reverseSchema.get(i);
203
+ if (feats) {
204
+ features.push(...feats);
205
+ }
206
+ }
207
+ }
208
+ return features;
209
+ }
210
+
211
+ // src/schema.ts
212
+ var MAX_EMERGENCY_FEATURES = EMERGENCY_RANGE[1] - EMERGENCY_RANGE[0] + 1;
213
+ var MAX_REGULAR_FEATURES = HIGH_FREQ_RANGE[1] - HIGH_FREQ_RANGE[0] + 1 + (MED_FREQ_RANGE[1] - MED_FREQ_RANGE[0] + 1);
214
+ var FNV_OFFSET_64 = 0xcbf29ce484222325n;
215
+ var FNV_PRIME_64 = 0x100000001b3n;
216
+ var MASK_64 = (1n << 64n) - 1n;
217
+ var SchemaManager = class {
218
+ _version = 0;
219
+ _featureToBit = /* @__PURE__ */ new Map();
220
+ _bitToFeatures = /* @__PURE__ */ new Map();
221
+ _activationCounts = /* @__PURE__ */ new Map();
222
+ _totalActivations = 0;
223
+ _emergencyFeatures;
224
+ _emergencyPrefix;
225
+ _maxFeatures;
226
+ constructor(config = {}) {
227
+ this._maxFeatures = config.maxFeatures ?? BITMASK_WIDTH;
228
+ this._emergencyPrefix = config.emergencyPrefix ?? "EMERGENCY_";
229
+ this._emergencyFeatures = new Set(config.emergencyFeatures ?? []);
230
+ }
231
+ /** Current schema version. */
232
+ get version() {
233
+ return this._version;
234
+ }
235
+ /** Read-only view of feature → bit mapping. */
236
+ get featureToBit() {
237
+ return this._featureToBit;
238
+ }
239
+ /** Read-only view of bit → features mapping. */
240
+ get bitToFeatures() {
241
+ return this._bitToFeatures;
242
+ }
243
+ /** Number of actively mapped features. */
244
+ get activeFeatureCount() {
245
+ return this._featureToBit.size;
246
+ }
247
+ /** Deterministic fingerprint of current schema mapping and version. */
248
+ get fingerprint() {
249
+ return this._computeFingerprint(
250
+ this._version,
251
+ this._featureToBit,
252
+ this._emergencyPrefix,
253
+ this._emergencyFeatures
254
+ );
255
+ }
256
+ /** Check if a feature is an emergency feature. */
257
+ isEmergency(feature) {
258
+ return this._emergencyFeatures.has(feature) || feature.startsWith(this._emergencyPrefix);
259
+ }
260
+ /**
261
+ * Register a feature in the schema.
262
+ * Emergency features are pinned to bits 56-63.
263
+ * Regular features fill bits 0-55 in order.
264
+ *
265
+ * Returns the assigned bit position, or -1 if schema is full.
266
+ */
267
+ register(feature) {
268
+ const existing = this._featureToBit.get(feature);
269
+ if (existing !== void 0) return existing;
270
+ if (this._featureToBit.size >= this._maxFeatures) return -1;
271
+ if (this.isEmergency(feature)) {
272
+ return this._registerEmergency(feature);
273
+ }
274
+ return this._registerRegular(feature);
275
+ }
276
+ /**
277
+ * Register multiple features at once.
278
+ * Returns a map of feature → assigned bit position.
279
+ */
280
+ registerAll(features) {
281
+ const result = /* @__PURE__ */ new Map();
282
+ for (const feature of features) {
283
+ result.set(feature, this.register(feature));
284
+ }
285
+ return result;
286
+ }
287
+ /**
288
+ * Record feature activations for frequency tracking.
289
+ * Call this every coordination round with the observed features.
290
+ */
291
+ recordActivations(features) {
292
+ for (const feature of features) {
293
+ const count = this._activationCounts.get(feature) ?? 0;
294
+ this._activationCounts.set(feature, count + 1);
295
+ this._totalActivations++;
296
+ }
297
+ }
298
+ /**
299
+ * Frequency-based pruning (Section 3.3).
300
+ *
301
+ * Sorts features by activation frequency, retains:
302
+ * - All emergency features in bits 56-63 (never pruned)
303
+ * - Top 48 regular features in bits 0-47 (high-frequency)
304
+ * - Next 8 regular features in bits 48-55 (medium-frequency)
305
+ *
306
+ * Increments schema version.
307
+ */
308
+ prune() {
309
+ const knownFeatures = this._collectKnownFeatures();
310
+ const emergencyList = knownFeatures.filter((feature) => this.isEmergency(feature)).sort((a, b) => this._compareByFrequencyThenName(a, b));
311
+ const regularList = knownFeatures.filter((feature) => !this.isEmergency(feature)).sort((a, b) => this._compareByFrequencyThenName(a, b));
312
+ const newFeatureToBit = /* @__PURE__ */ new Map();
313
+ const newBitToFeatures = /* @__PURE__ */ new Map();
314
+ const [emergStart] = EMERGENCY_RANGE;
315
+ for (let i = 0; i < Math.min(emergencyList.length, MAX_EMERGENCY_FEATURES); i++) {
316
+ const bit = emergStart + i;
317
+ const feature = emergencyList[i];
318
+ newFeatureToBit.set(feature, bit);
319
+ newBitToFeatures.set(bit, [feature]);
320
+ }
321
+ const [highStart] = HIGH_FREQ_RANGE;
322
+ const highCount = HIGH_FREQ_RANGE[1] - HIGH_FREQ_RANGE[0] + 1;
323
+ for (let i = 0; i < Math.min(regularList.length, highCount); i++) {
324
+ const bit = highStart + i;
325
+ const feature = regularList[i];
326
+ newFeatureToBit.set(feature, bit);
327
+ newBitToFeatures.set(bit, [feature]);
328
+ }
329
+ const [medStart] = MED_FREQ_RANGE;
330
+ for (let i = highCount; i < Math.min(regularList.length, MAX_REGULAR_FEATURES); i++) {
331
+ const bit = medStart + (i - highCount);
332
+ const feature = regularList[i];
333
+ newFeatureToBit.set(feature, bit);
334
+ newBitToFeatures.set(bit, [feature]);
335
+ }
336
+ const excludedFeatures = [
337
+ ...regularList.slice(MAX_REGULAR_FEATURES),
338
+ ...emergencyList.slice(MAX_EMERGENCY_FEATURES)
339
+ ];
340
+ const mappingChanged = !this._mapsEqual(this._featureToBit, newFeatureToBit);
341
+ if (mappingChanged) {
342
+ this._featureToBit = newFeatureToBit;
343
+ this._bitToFeatures = newBitToFeatures;
344
+ this._version++;
345
+ }
346
+ return {
347
+ pruned: excludedFeatures.length,
348
+ retained: this._featureToBit.size,
349
+ version: this._version,
350
+ excludedFeatures
351
+ };
352
+ }
353
+ /** Get a serializable snapshot of the current schema state. */
354
+ snapshot() {
355
+ let emergencyCount = 0;
356
+ for (const [feat] of this._featureToBit) {
357
+ if (this.isEmergency(feat)) emergencyCount++;
358
+ }
359
+ return {
360
+ version: this._version,
361
+ featureToBit: new Map(this._featureToBit),
362
+ bitToFeatures: new Map(
363
+ [...this._bitToFeatures].map(([k, v]) => [k, [...v]])
364
+ ),
365
+ totalTracked: this._activationCounts.size,
366
+ activeFeatures: this._featureToBit.size,
367
+ emergencyCount
368
+ };
369
+ }
370
+ /**
371
+ * Export canonical schema state for distribution.
372
+ * Entries are sorted by bit (then feature) for deterministic serialization.
373
+ */
374
+ exportSchema() {
375
+ const entries = [...this._featureToBit.entries()].sort((a, b) => {
376
+ if (a[1] !== b[1]) return a[1] - b[1];
377
+ return a[0] < b[0] ? -1 : a[0] > b[0] ? 1 : 0;
378
+ }).map(([feature, bit]) => [feature, bit]);
379
+ return {
380
+ version: this._version,
381
+ entries,
382
+ emergencyPrefix: this._emergencyPrefix,
383
+ emergencyFeatures: [...this._emergencyFeatures].sort(
384
+ (a, b) => a < b ? -1 : a > b ? 1 : 0
385
+ ),
386
+ fingerprint: this.fingerprint
387
+ };
388
+ }
389
+ /**
390
+ * Import an exported schema state.
391
+ * Replaces active mappings and resets activation counters.
392
+ */
393
+ importSchema(schema) {
394
+ this._assertExportedSchema(schema);
395
+ const newFeatureToBit = /* @__PURE__ */ new Map();
396
+ const newBitToFeatures = /* @__PURE__ */ new Map();
397
+ for (const [feature, bit] of schema.entries) {
398
+ newFeatureToBit.set(feature, bit);
399
+ newBitToFeatures.set(bit, [feature]);
400
+ }
401
+ const expectedFingerprint = this._computeFingerprint(
402
+ schema.version,
403
+ newFeatureToBit,
404
+ schema.emergencyPrefix,
405
+ new Set(schema.emergencyFeatures)
406
+ );
407
+ if (schema.fingerprint !== expectedFingerprint) {
408
+ throw new Error(
409
+ `Schema fingerprint mismatch: expected ${expectedFingerprint}, got ${schema.fingerprint}`
410
+ );
411
+ }
412
+ this._featureToBit = newFeatureToBit;
413
+ this._bitToFeatures = newBitToFeatures;
414
+ this._emergencyPrefix = schema.emergencyPrefix;
415
+ this._emergencyFeatures = new Set(schema.emergencyFeatures);
416
+ this._activationCounts.clear();
417
+ this._totalActivations = 0;
418
+ this._version = schema.version;
419
+ }
420
+ /** Get activation frequency for a feature. */
421
+ getFrequency(feature) {
422
+ return this._activationCounts.get(feature) ?? 0;
423
+ }
424
+ /** Get all features ranked by activation frequency. */
425
+ getRankedFeatures() {
426
+ const ranked = [];
427
+ for (const [feature, count] of this._activationCounts) {
428
+ ranked.push({
429
+ feature,
430
+ count,
431
+ bit: this._featureToBit.get(feature)
432
+ });
433
+ }
434
+ ranked.sort((a, b) => b.count - a.count);
435
+ return ranked;
436
+ }
437
+ /** Reset all activation counts (but keep schema mappings). */
438
+ resetCounts() {
439
+ this._activationCounts.clear();
440
+ this._totalActivations = 0;
441
+ }
442
+ /**
443
+ * Collision probability for a specific feature:
444
+ * P(collision) = 1 - (1 - 1/64)^(m - 1)
445
+ * where m is active feature count.
446
+ */
447
+ get theoreticalCollisionRate() {
448
+ const m = this._featureToBit.size;
449
+ if (m <= 1) return 0;
450
+ return 1 - Math.pow(1 - 1 / BITMASK_WIDTH, m - 1);
451
+ }
452
+ /**
453
+ * Expected excluded feature count under uniform assignment:
454
+ * E[excluded] = m - 64 * (1 - (1 - 1/64)^m)
455
+ */
456
+ expectedExcludedFeatures(featureCount = this._featureToBit.size) {
457
+ if (!Number.isInteger(featureCount) || featureCount < 0) {
458
+ throw new RangeError(
459
+ `featureCount must be a non-negative integer, got ${featureCount}`
460
+ );
461
+ }
462
+ if (featureCount === 0) return 0;
463
+ return featureCount - BITMASK_WIDTH * (1 - Math.pow(1 - 1 / BITMASK_WIDTH, featureCount));
464
+ }
465
+ // ── Private ──
466
+ _registerEmergency(feature) {
467
+ const [start, end] = EMERGENCY_RANGE;
468
+ for (let bit = start; bit <= end; bit++) {
469
+ if (!this._bitToFeatures.has(bit)) {
470
+ this._featureToBit.set(feature, bit);
471
+ this._bitToFeatures.set(bit, [feature]);
472
+ this._version++;
473
+ return bit;
474
+ }
475
+ }
476
+ return -1;
477
+ }
478
+ _registerRegular(feature) {
479
+ const [highStart, highEnd] = HIGH_FREQ_RANGE;
480
+ for (let bit = highStart; bit <= highEnd; bit++) {
481
+ if (!this._bitToFeatures.has(bit)) {
482
+ this._featureToBit.set(feature, bit);
483
+ this._bitToFeatures.set(bit, [feature]);
484
+ this._version++;
485
+ return bit;
486
+ }
487
+ }
488
+ const [medStart, medEnd] = MED_FREQ_RANGE;
489
+ for (let bit = medStart; bit <= medEnd; bit++) {
490
+ if (!this._bitToFeatures.has(bit)) {
491
+ this._featureToBit.set(feature, bit);
492
+ this._bitToFeatures.set(bit, [feature]);
493
+ this._version++;
494
+ return bit;
495
+ }
496
+ }
497
+ return -1;
498
+ }
499
+ _collectKnownFeatures() {
500
+ const all = /* @__PURE__ */ new Set();
501
+ for (const feature of this._activationCounts.keys()) all.add(feature);
502
+ for (const feature of this._featureToBit.keys()) all.add(feature);
503
+ for (const feature of this._emergencyFeatures) all.add(feature);
504
+ return [...all];
505
+ }
506
+ _compareByFrequencyThenName(a, b) {
507
+ const countA = this._activationCounts.get(a) ?? 0;
508
+ const countB = this._activationCounts.get(b) ?? 0;
509
+ if (countA !== countB) return countB - countA;
510
+ if (a < b) return -1;
511
+ if (a > b) return 1;
512
+ return 0;
513
+ }
514
+ _mapsEqual(a, b) {
515
+ if (a.size !== b.size) return false;
516
+ for (const [feature, bit] of a) {
517
+ if (b.get(feature) !== bit) return false;
518
+ }
519
+ return true;
520
+ }
521
+ _assertExportedSchema(schema) {
522
+ if (!Number.isInteger(schema.version) || schema.version < 0) {
523
+ throw new RangeError(`Schema version must be a non-negative integer, got ${schema.version}`);
524
+ }
525
+ if (typeof schema.fingerprint !== "string" || schema.fingerprint.length === 0) {
526
+ throw new TypeError("Schema fingerprint must be a non-empty string");
527
+ }
528
+ const seenFeatures = /* @__PURE__ */ new Set();
529
+ const seenBits = /* @__PURE__ */ new Set();
530
+ for (const [feature, bit] of schema.entries) {
531
+ if (!feature || typeof feature !== "string") {
532
+ throw new TypeError(`Invalid feature name in schema export: ${String(feature)}`);
533
+ }
534
+ if (!Number.isInteger(bit) || bit < 0 || bit >= BITMASK_WIDTH) {
535
+ throw new RangeError(`Invalid bit position in schema export: ${bit}`);
536
+ }
537
+ if (seenFeatures.has(feature)) {
538
+ throw new Error(`Duplicate feature in schema export: ${feature}`);
539
+ }
540
+ if (seenBits.has(bit)) {
541
+ throw new Error(`Duplicate bit in schema export: ${bit}`);
542
+ }
543
+ seenFeatures.add(feature);
544
+ seenBits.add(bit);
545
+ }
546
+ }
547
+ _computeFingerprint(version, mapping, emergencyPrefix, emergencyFeatures) {
548
+ const canonicalEntries = [...mapping.entries()].sort((a, b) => {
549
+ if (a[1] !== b[1]) return a[1] - b[1];
550
+ if (a[0] < b[0]) return -1;
551
+ if (a[0] > b[0]) return 1;
552
+ return 0;
553
+ }).map(([feature, bit]) => `${bit}:${feature}`).join("|");
554
+ const canonicalEmergency = [...emergencyFeatures].sort((a, b) => a < b ? -1 : a > b ? 1 : 0).join("|");
555
+ const canonical = `v=${version};ep=${emergencyPrefix};ef=${canonicalEmergency};m=${canonicalEntries}`;
556
+ let hash = FNV_OFFSET_64;
557
+ for (const char of canonical) {
558
+ hash ^= BigInt(char.codePointAt(0) ?? 0);
559
+ hash = hash * FNV_PRIME_64 & MASK_64;
560
+ }
561
+ return hash.toString(16).padStart(16, "0");
562
+ }
563
+ };
564
+
565
+ // src/coordinator.ts
566
+ var Coordinator = class {
567
+ _buffer = [];
568
+ _seenAgents = /* @__PURE__ */ new Set();
569
+ _schemaVersion;
570
+ _deadlineMs;
571
+ _staleMessagePolicy;
572
+ _onTelemetry;
573
+ _roundStartTime = 0;
574
+ _aggregationCount = 0;
575
+ _droppedStaleMessages = 0;
576
+ constructor(config = {}) {
577
+ this._deadlineMs = config.deadlineMs ?? 15;
578
+ this._schemaVersion = config.schemaVersion;
579
+ this._staleMessagePolicy = config.staleMessagePolicy ?? "accept";
580
+ this._onTelemetry = config.onTelemetry;
581
+ }
582
+ /** Number of messages in current buffer. */
583
+ get bufferedCount() {
584
+ return this._buffer.length;
585
+ }
586
+ /** Total aggregation rounds performed. */
587
+ get aggregationCount() {
588
+ return this._aggregationCount;
589
+ }
590
+ /** Update expected schema version. */
591
+ set schemaVersion(version) {
592
+ this._schemaVersion = version;
593
+ }
594
+ /** Start a new coordination round. Clears the buffer. */
595
+ startRound() {
596
+ this._buffer = [];
597
+ this._seenAgents.clear();
598
+ this._roundStartTime = performance.now();
599
+ this._droppedStaleMessages = 0;
600
+ }
601
+ /**
602
+ * Receive a message from an agent.
603
+ * Returns false if the message was dropped (deadline exceeded or duplicate agent).
604
+ */
605
+ receive(message) {
606
+ if (this._roundStartTime > 0) {
607
+ const elapsed = performance.now() - this._roundStartTime;
608
+ if (elapsed > this._deadlineMs) {
609
+ this._emitTelemetry({
610
+ type: "message_dropped",
611
+ agentId: message.agentId,
612
+ reason: "deadline"
613
+ });
614
+ return false;
615
+ }
616
+ }
617
+ const isStale = this._schemaVersion !== void 0 && message.schemaVersion !== this._schemaVersion;
618
+ if (isStale) {
619
+ if (this._staleMessagePolicy === "drop") {
620
+ this._droppedStaleMessages++;
621
+ this._emitTelemetry({
622
+ type: "message_dropped",
623
+ agentId: message.agentId,
624
+ reason: "stale"
625
+ });
626
+ return false;
627
+ }
628
+ if (this._staleMessagePolicy === "warn") {
629
+ console.warn(
630
+ `adaptive-bitmask: stale schema version from agent ${message.agentId} (expected ${this._schemaVersion}, got ${message.schemaVersion})`
631
+ );
632
+ }
633
+ }
634
+ if (this._seenAgents.has(message.agentId)) {
635
+ const idx = this._buffer.findIndex((m) => m.agentId === message.agentId);
636
+ if (idx !== -1) {
637
+ this._buffer[idx] = message;
638
+ }
639
+ this._emitTelemetry({
640
+ type: "message_accepted",
641
+ agentId: message.agentId,
642
+ stale: isStale,
643
+ replaced: true
644
+ });
645
+ return true;
646
+ }
647
+ this._seenAgents.add(message.agentId);
648
+ this._buffer.push(message);
649
+ this._emitTelemetry({
650
+ type: "message_accepted",
651
+ agentId: message.agentId,
652
+ stale: isStale,
653
+ replaced: false
654
+ });
655
+ return true;
656
+ }
657
+ /**
658
+ * Receive multiple messages at once.
659
+ * Returns number of messages accepted.
660
+ */
661
+ receiveAll(messages) {
662
+ let accepted = 0;
663
+ for (const msg of messages) {
664
+ if (this.receive(msg)) accepted++;
665
+ }
666
+ return accepted;
667
+ }
668
+ /**
669
+ * Aggregate all buffered messages into a consensus result.
670
+ * Clears the buffer after aggregation.
671
+ */
672
+ aggregate() {
673
+ const t0 = performance.now();
674
+ let aggregated = 0n;
675
+ const bitVotes = /* @__PURE__ */ new Map();
676
+ let staleCount = 0;
677
+ const uniqueAgents = /* @__PURE__ */ new Set();
678
+ for (const msg of this._buffer) {
679
+ uniqueAgents.add(msg.agentId);
680
+ if (this._schemaVersion !== void 0 && msg.schemaVersion !== this._schemaVersion) {
681
+ staleCount++;
682
+ }
683
+ aggregated |= msg.mask;
684
+ forEachSetBit(msg.mask, (bit) => {
685
+ bitVotes.set(bit, (bitVotes.get(bit) ?? 0) + 1);
686
+ });
687
+ }
688
+ const messageCount = this._buffer.length;
689
+ const confidence = /* @__PURE__ */ new Map();
690
+ for (const [bit, votes] of bitVotes) {
691
+ confidence.set(bit, messageCount > 0 ? votes / messageCount : 0);
692
+ }
693
+ const elapsed = (performance.now() - t0) * 1e3;
694
+ this._aggregationCount++;
695
+ this._buffer = [];
696
+ this._seenAgents.clear();
697
+ const result = {
698
+ aggregatedMask: aggregated,
699
+ confidence,
700
+ messageCount,
701
+ uniqueAgents: uniqueAgents.size,
702
+ staleMessages: staleCount,
703
+ droppedStaleMessages: this._droppedStaleMessages,
704
+ aggregationTimeUs: elapsed
705
+ };
706
+ this._emitTelemetry({ type: "round_aggregated", result });
707
+ return result;
708
+ }
709
+ _emitTelemetry(event) {
710
+ this._onTelemetry?.(event);
711
+ }
712
+ };
713
+
714
+ // src/arbiter.ts
715
+ var Arbiter = class {
716
+ _weights;
717
+ _weightSum;
718
+ _executeThreshold;
719
+ _synthesizeThreshold;
720
+ _emergencyOverride;
721
+ _onTelemetry;
722
+ _decisionCount = 0;
723
+ constructor(config = {}) {
724
+ this._executeThreshold = config.executeThreshold ?? 0.55;
725
+ this._synthesizeThreshold = config.synthesizeThreshold ?? 0.4;
726
+ this._emergencyOverride = config.emergencyOverride ?? true;
727
+ this._onTelemetry = config.onTelemetry;
728
+ this._weights = new Float64Array(BITMASK_WIDTH);
729
+ if (config.weights) {
730
+ if (config.weights.length !== BITMASK_WIDTH) {
731
+ throw new Error(`Weight vector must have ${BITMASK_WIDTH} elements, got ${config.weights.length}`);
732
+ }
733
+ this._weights.set(config.weights);
734
+ } else {
735
+ this._weights.fill(1);
736
+ }
737
+ this._weightSum = this._weights.reduce((sum, w) => sum + w, 0);
738
+ }
739
+ /** Number of decisions made since creation. */
740
+ get decisionCount() {
741
+ return this._decisionCount;
742
+ }
743
+ /** Set the weight for a specific bit position. */
744
+ setWeight(position, weight) {
745
+ if (position < 0 || position >= BITMASK_WIDTH) {
746
+ throw new RangeError(`Position ${position} out of range`);
747
+ }
748
+ this._weightSum -= this._weights[position];
749
+ this._weights[position] = weight;
750
+ this._weightSum += weight;
751
+ }
752
+ /** Get the current weight vector (copy). */
753
+ get weights() {
754
+ return Array.from(this._weights);
755
+ }
756
+ /**
757
+ * Score an aggregated bitmask and produce a decision.
758
+ *
759
+ * @param aggregatedMask — OR-aggregated mask from all agents
760
+ * @param confidence — Optional per-bit confidence (fraction of agents that set each bit)
761
+ */
762
+ score(aggregatedMask, confidence) {
763
+ const t0 = performance.now();
764
+ const active = activeBits(aggregatedMask);
765
+ const emergency = hasEmergency(aggregatedMask);
766
+ if (emergency && this._emergencyOverride) {
767
+ const elapsed2 = (performance.now() - t0) * 1e3;
768
+ this._decisionCount++;
769
+ const result2 = {
770
+ decision: "REJECT",
771
+ rawScore: 0,
772
+ confidenceScore: 0,
773
+ finalScore: 0,
774
+ activeBitCount: active.length,
775
+ hasEmergency: true,
776
+ scoringTimeUs: elapsed2
777
+ };
778
+ this._emitTelemetry({ type: "decision", result: result2 });
779
+ return result2;
780
+ }
781
+ let numerator = 0;
782
+ for (const bit of active) {
783
+ numerator += this._weights[bit];
784
+ }
785
+ const rawScore = this._weightSum > 0 ? numerator / this._weightSum : 0;
786
+ let confidenceScore = rawScore;
787
+ if (confidence && confidence.size > 0) {
788
+ let confNumerator = 0;
789
+ let confDenominator = 0;
790
+ for (const bit of active) {
791
+ const conf = confidence.get(bit) ?? 0;
792
+ confNumerator += conf * this._weights[bit];
793
+ confDenominator += this._weights[bit];
794
+ }
795
+ confidenceScore = confDenominator > 0 ? confNumerator / confDenominator : rawScore;
796
+ }
797
+ const finalScore = Math.min(1, rawScore * 0.6 + confidenceScore * 0.4);
798
+ let decision;
799
+ if (finalScore >= this._executeThreshold) {
800
+ decision = "EXECUTE";
801
+ } else if (finalScore >= this._synthesizeThreshold) {
802
+ decision = "SYNTHESIZE";
803
+ } else {
804
+ decision = "REJECT";
805
+ }
806
+ const elapsed = (performance.now() - t0) * 1e3;
807
+ this._decisionCount++;
808
+ const result = {
809
+ decision,
810
+ rawScore,
811
+ confidenceScore,
812
+ finalScore,
813
+ activeBitCount: active.length,
814
+ hasEmergency: emergency,
815
+ scoringTimeUs: elapsed
816
+ };
817
+ this._emitTelemetry({ type: "decision", result });
818
+ return result;
819
+ }
820
+ /**
821
+ * Convenience: aggregate messages then score.
822
+ * OR-merges all message masks, computes per-bit confidence,
823
+ * validates schema versions, then runs scoring.
824
+ */
825
+ scoreMessages(messages, expectedSchemaVersion) {
826
+ if (messages.length === 0) {
827
+ return {
828
+ decision: "REJECT",
829
+ rawScore: 0,
830
+ confidenceScore: 0,
831
+ finalScore: 0,
832
+ activeBitCount: 0,
833
+ hasEmergency: false,
834
+ scoringTimeUs: 0,
835
+ staleCount: 0
836
+ };
837
+ }
838
+ let aggregated = 0n;
839
+ const bitVotes = /* @__PURE__ */ new Map();
840
+ let staleCount = 0;
841
+ for (const msg of messages) {
842
+ if (expectedSchemaVersion !== void 0 && msg.schemaVersion !== expectedSchemaVersion) {
843
+ staleCount++;
844
+ }
845
+ aggregated |= msg.mask;
846
+ forEachSetBit(msg.mask, (bit) => {
847
+ bitVotes.set(bit, (bitVotes.get(bit) ?? 0) + 1);
848
+ });
849
+ }
850
+ const confidence = /* @__PURE__ */ new Map();
851
+ for (const [bit, votes] of bitVotes) {
852
+ confidence.set(bit, votes / messages.length);
853
+ }
854
+ const result = this.score(aggregated, confidence);
855
+ this._emitTelemetry({
856
+ type: "score_messages",
857
+ messageCount: messages.length,
858
+ staleCount,
859
+ result
860
+ });
861
+ return { ...result, staleCount };
862
+ }
863
+ /**
864
+ * Paper-canonical strategy ranking and decision logic (Section 6).
865
+ * Uses ŝ_final = 0.6*ŝ_raw + 0.4*c, then applies:
866
+ * - EXECUTE if lead > 15 points
867
+ * - SYNTHESIZE if top strategies are within threshold
868
+ * - REJECT if top score < 40%
869
+ */
870
+ scoreStrategies(candidates, options = {}) {
871
+ if (candidates.length === 0) {
872
+ const emptyResult = {
873
+ decision: "REJECT",
874
+ leadScore: 0,
875
+ rankings: []
876
+ };
877
+ this._emitTelemetry({ type: "strategy_decision", result: emptyResult });
878
+ return emptyResult;
879
+ }
880
+ const leadThreshold = options.leadThreshold ?? 0.15;
881
+ const rejectThreshold = options.rejectThreshold ?? 0.4;
882
+ const rankings = candidates.map((candidate) => {
883
+ const confidence = candidate.confidence ?? options.globalConfidence;
884
+ const scored = this._scoreMaskComponents(candidate.mask, confidence);
885
+ return {
886
+ id: candidate.id,
887
+ mask: candidate.mask,
888
+ rawScore: scored.rawScore,
889
+ confidenceScore: scored.confidenceScore,
890
+ finalScore: scored.finalScore
891
+ };
892
+ }).sort((a, b) => {
893
+ if (b.finalScore !== a.finalScore) return b.finalScore - a.finalScore;
894
+ if (a.id < b.id) return -1;
895
+ if (a.id > b.id) return 1;
896
+ return 0;
897
+ });
898
+ const top1 = rankings[0];
899
+ const top2 = rankings[1];
900
+ const leadScore = top2 ? top1.finalScore - top2.finalScore : top1.finalScore;
901
+ let result;
902
+ if (top1.finalScore < rejectThreshold) {
903
+ result = {
904
+ decision: "REJECT",
905
+ leadScore,
906
+ rankings
907
+ };
908
+ } else if (leadScore > leadThreshold) {
909
+ result = {
910
+ decision: "EXECUTE",
911
+ selectedStrategyId: top1.id,
912
+ leadScore,
913
+ rankings
914
+ };
915
+ } else {
916
+ const contenders = rankings.slice(0, Math.min(3, rankings.length)).filter((candidate) => top1.finalScore - candidate.finalScore <= leadThreshold);
917
+ const contenderMasks = contenders.length >= 2 ? contenders.map((candidate) => candidate.mask) : rankings.slice(0, Math.min(2, rankings.length)).map((candidate) => candidate.mask);
918
+ result = {
919
+ decision: "SYNTHESIZE",
920
+ synthesizedMask: this._synthesizeMask(contenderMasks),
921
+ leadScore,
922
+ rankings
923
+ };
924
+ }
925
+ this._decisionCount++;
926
+ this._emitTelemetry({ type: "strategy_decision", result });
927
+ return result;
928
+ }
929
+ _scoreMaskComponents(mask, confidence) {
930
+ if (hasEmergency(mask) && this._emergencyOverride) {
931
+ return { rawScore: 0, confidenceScore: 0, finalScore: 0 };
932
+ }
933
+ const active = activeBits(mask);
934
+ let numerator = 0;
935
+ for (const bit of active) {
936
+ numerator += this._weights[bit];
937
+ }
938
+ const rawScore = this._weightSum > 0 ? numerator / this._weightSum : 0;
939
+ let confidenceScore = rawScore;
940
+ if (confidence && confidence.size > 0) {
941
+ let confNumerator = 0;
942
+ let confDenominator = 0;
943
+ for (const bit of active) {
944
+ const conf = confidence.get(bit) ?? 0;
945
+ confNumerator += conf * this._weights[bit];
946
+ confDenominator += this._weights[bit];
947
+ }
948
+ confidenceScore = confDenominator > 0 ? confNumerator / confDenominator : rawScore;
949
+ }
950
+ return {
951
+ rawScore,
952
+ confidenceScore,
953
+ finalScore: Math.min(1, rawScore * 0.6 + confidenceScore * 0.4)
954
+ };
955
+ }
956
+ _synthesizeMask(masks) {
957
+ if (masks.length === 0) return 0n;
958
+ const votes = /* @__PURE__ */ new Map();
959
+ for (const mask of masks) {
960
+ forEachSetBit(mask, (bit) => {
961
+ votes.set(bit, (votes.get(bit) ?? 0) + 1);
962
+ });
963
+ }
964
+ const requiredVotes = Math.floor(masks.length / 2) + 1;
965
+ let synthesized = 0n;
966
+ for (const [bit, count] of votes) {
967
+ if (count >= requiredVotes) {
968
+ synthesized |= 1n << BigInt(bit);
969
+ }
970
+ }
971
+ return synthesized;
972
+ }
973
+ _emitTelemetry(event) {
974
+ this._onTelemetry?.(event);
975
+ }
976
+ };
977
+
978
+ // src/ai/session.ts
979
+ var CoordinationSession = class {
980
+ schema;
981
+ coordinator;
982
+ arbiter;
983
+ _agentIds = /* @__PURE__ */ new Map();
984
+ constructor(config) {
985
+ this.schema = new SchemaManager({
986
+ emergencyPrefix: config.emergencyPrefix ?? "EMERGENCY_",
987
+ emergencyFeatures: config.emergencyFeatures
988
+ });
989
+ this.schema.registerAll(config.features);
990
+ this.coordinator = new Coordinator({
991
+ ...config.coordinatorConfig,
992
+ schemaVersion: this.schema.version
993
+ });
994
+ this.arbiter = new Arbiter(config.arbiterConfig);
995
+ }
996
+ /** Deterministic agent ID: FNV-1a hash of name → uint32. */
997
+ agentId(name) {
998
+ const cached = this._agentIds.get(name);
999
+ if (cached !== void 0) return cached;
1000
+ let hash = 2166136261;
1001
+ for (let i = 0; i < name.length; i++) {
1002
+ hash ^= name.charCodeAt(i);
1003
+ hash = Math.imul(hash, 16777619);
1004
+ }
1005
+ const id = hash >>> 0;
1006
+ this._agentIds.set(name, id);
1007
+ return id;
1008
+ }
1009
+ /** Start a new coordination round. Clears the coordinator buffer. */
1010
+ startRound() {
1011
+ this.coordinator.startRound();
1012
+ }
1013
+ /** Encode features + create message + receive in one call. */
1014
+ report(agentName, features) {
1015
+ const { mask, mapped, unmapped } = encode(
1016
+ features,
1017
+ this.schema.featureToBit
1018
+ );
1019
+ const msg = BitmaskMessage.now(
1020
+ mask,
1021
+ this.agentId(agentName),
1022
+ this.schema.version
1023
+ );
1024
+ const accepted = this.coordinator.receive(msg);
1025
+ return { accepted, mapped, unmapped };
1026
+ }
1027
+ /** Aggregate current buffer + score via arbiter. */
1028
+ decide() {
1029
+ const { aggregatedMask, confidence } = this.coordinator.aggregate();
1030
+ const aggregatedFeatures = decode(aggregatedMask, this.schema.bitToFeatures);
1031
+ const result = this.arbiter.score(aggregatedMask, confidence);
1032
+ return {
1033
+ decision: result.decision,
1034
+ aggregatedFeatures,
1035
+ confidence,
1036
+ result
1037
+ };
1038
+ }
1039
+ };
1040
+
1041
+ // src/ai/tools.ts
1042
+ var import_ai = require("ai");
1043
+ var import_zod = require("zod");
1044
+ function createCoordinationTools(session) {
1045
+ const reportObservation = (0, import_ai.tool)({
1046
+ description: "Report observed features to the coordination layer. Each feature string is encoded into the shared bitmask.",
1047
+ parameters: import_zod.z.object({
1048
+ agentName: import_zod.z.string().describe("Name identifying this agent"),
1049
+ features: import_zod.z.array(import_zod.z.string()).describe("Feature names observed by this agent")
1050
+ }),
1051
+ execute: async ({ agentName, features }) => {
1052
+ const result = session.report(agentName, features);
1053
+ return {
1054
+ accepted: result.accepted,
1055
+ mapped: result.mapped,
1056
+ unmapped: result.unmapped
1057
+ };
1058
+ }
1059
+ });
1060
+ const getConsensus = (0, import_ai.tool)({
1061
+ description: "Query the current aggregated consensus state across all agents.",
1062
+ parameters: import_zod.z.object({}),
1063
+ execute: async () => {
1064
+ const { aggregatedMask, confidence, uniqueAgents } = session.coordinator.aggregate();
1065
+ const features = decode(aggregatedMask, session.schema.bitToFeatures);
1066
+ const confidenceObj = {};
1067
+ for (const [bit, conf] of confidence) {
1068
+ confidenceObj[String(bit)] = conf;
1069
+ }
1070
+ return {
1071
+ features,
1072
+ confidence: confidenceObj,
1073
+ agentCount: uniqueAgents
1074
+ };
1075
+ }
1076
+ });
1077
+ const requestDecision = (0, import_ai.tool)({
1078
+ description: "Trigger the arbiter to score the current aggregated state and return a decision.",
1079
+ parameters: import_zod.z.object({}),
1080
+ execute: async () => {
1081
+ const { decision, aggregatedFeatures, result } = session.decide();
1082
+ return {
1083
+ decision,
1084
+ score: result.finalScore,
1085
+ features: aggregatedFeatures,
1086
+ hasEmergency: result.hasEmergency
1087
+ };
1088
+ }
1089
+ });
1090
+ return { reportObservation, getConsensus, requestDecision };
1091
+ }
1092
+
1093
+ // src/ai/middleware.ts
1094
+ function createCoordinationMiddleware(session, options = {}) {
1095
+ const { injectConsensus = false, autoEncodeToolCalls = false, agentName } = options;
1096
+ if (autoEncodeToolCalls && !agentName) {
1097
+ throw new Error("agentName is required when autoEncodeToolCalls is enabled");
1098
+ }
1099
+ const middleware = {};
1100
+ if (injectConsensus) {
1101
+ middleware.transformParams = async ({ params }) => {
1102
+ const { aggregatedMask, confidence } = session.coordinator.aggregate();
1103
+ const features = decode(aggregatedMask, session.schema.bitToFeatures);
1104
+ const emergency = hasEmergency(aggregatedMask);
1105
+ const confidenceEntries = Array.from(confidence.entries()).map(([bit, conf]) => ` bit ${bit}: ${(conf * 100).toFixed(0)}%`).join("\n");
1106
+ const consensusText = [
1107
+ "[Coordination Consensus]",
1108
+ `Features: ${features.length > 0 ? features.join(", ") : "none"}`,
1109
+ `Emergency: ${emergency}`,
1110
+ confidenceEntries ? `Confidence:
1111
+ ${confidenceEntries}` : ""
1112
+ ].filter(Boolean).join("\n");
1113
+ return {
1114
+ ...params,
1115
+ prompt: [
1116
+ { role: "system", content: consensusText },
1117
+ ...params.prompt
1118
+ ]
1119
+ };
1120
+ };
1121
+ }
1122
+ if (autoEncodeToolCalls) {
1123
+ middleware.wrapGenerate = async ({ doGenerate }) => {
1124
+ const result = await doGenerate();
1125
+ if (result.toolCalls && result.toolCalls.length > 0) {
1126
+ const matchingFeatures = result.toolCalls.map((tc) => tc.toolName).filter((name) => session.schema.featureToBit.has(name));
1127
+ if (matchingFeatures.length > 0) {
1128
+ session.report(agentName, matchingFeatures);
1129
+ }
1130
+ }
1131
+ return result;
1132
+ };
1133
+ }
1134
+ return middleware;
1135
+ }
1136
+ // Annotate the CommonJS export names for ESM import in node:
1137
+ 0 && (module.exports = {
1138
+ CoordinationSession,
1139
+ createCoordinationMiddleware,
1140
+ createCoordinationTools
1141
+ });