UNPKG

trellis

Version:

Agentic State Engine — event-sourced causal graph with branching, decision traces, and realtime sync for AI-native applications

996 lines (988 loc) 27.7 kB
import { init_ops, isVcsOpKind, verifyVcsOpHash } from "./chunk-GRWQPKYK.js"; import { __esm, __export } from "./chunk-2ESYSVXG.js"; // src/sync/reconciler.ts var reconciler_exports = {}; __export(reconciler_exports, { findForkPoint: () => findForkPoint, reconcile: () => reconcile }); function findForkPoint(opsA, opsB) { const hashesB = new Set(opsB.map((o) => o.hash)); let forkPoint = null; for (const op of opsA) { if (hashesB.has(op.hash)) { forkPoint = op.hash; } } return forkPoint; } function reconcile(opsA, opsB) { const forkPoint = findForkPoint(opsA, opsB); const hashesA = new Set(opsA.map((o) => o.hash)); const hashesB = new Set(opsB.map((o) => o.hash)); const shared = []; const uniqueToA = []; const uniqueToB = []; for (const op of opsA) { if (hashesB.has(op.hash)) { shared.push(op); } else { uniqueToA.push(op); } } for (const op of opsB) { if (!hashesA.has(op.hash)) { uniqueToB.push(op); } } if (uniqueToA.length === 0) { return { merged: [...shared, ...uniqueToB], uniqueToA: [], uniqueToB, forkPoint, clean: true, conflicts: [] }; } if (uniqueToB.length === 0) { return { merged: [...shared, ...uniqueToA], uniqueToA, uniqueToB: [], forkPoint, clean: true, conflicts: [] }; } const conflicts = detectConflicts(uniqueToA, uniqueToB); const interleaved = interleaveByTimestamp(uniqueToA, uniqueToB); return { merged: [...shared, ...interleaved], uniqueToA, uniqueToB, forkPoint, clean: conflicts.length === 0, conflicts }; } function detectConflicts(uniqueA, uniqueB) { const conflicts = []; const aMutations = /* @__PURE__ */ new Map(); for (const op of uniqueA) { if (!FILE_MUTATION_KINDS.has(op.kind) || !op.vcs?.filePath) continue; const path = op.vcs.filePath; if (!aMutations.has(path)) aMutations.set(path, []); aMutations.get(path).push(op); } for (const op of uniqueB) { if (!FILE_MUTATION_KINDS.has(op.kind) || !op.vcs?.filePath) continue; const path = op.vcs.filePath; const aOps = aMutations.get(path); if (!aOps) continue; for (const aOp of aOps) { if (aOp.kind === "vcs:fileModify" && op.kind === "vcs:fileModify") { conflicts.push({ opA: aOp, opB: op, filePath: path, reason: `Both sides modified ${path}` }); } else if (aOp.kind === "vcs:fileDelete" && op.kind === "vcs:fileModify" || aOp.kind === "vcs:fileModify" && op.kind === "vcs:fileDelete") { conflicts.push({ opA: aOp, opB: op, filePath: path, reason: `Delete/modify conflict on ${path}` }); } else if (aOp.kind === "vcs:fileAdd" && op.kind === "vcs:fileAdd") { if (aOp.vcs?.contentHash !== op.vcs?.contentHash) { conflicts.push({ opA: aOp, opB: op, filePath: path, reason: `Both sides added ${path} with different content` }); } } } } return conflicts; } function interleaveByTimestamp(a, b) { const result = []; let ai = 0; let bi = 0; while (ai < a.length && bi < b.length) { const tA = new Date(a[ai].timestamp).getTime(); const tB = new Date(b[bi].timestamp).getTime(); if (tA <= tB) { result.push(a[ai++]); } else { result.push(b[bi++]); } } while (ai < a.length) result.push(a[ai++]); while (bi < b.length) result.push(b[bi++]); return result; } var FILE_MUTATION_KINDS; var init_reconciler = __esm({ "src/sync/reconciler.ts"() { "use strict"; FILE_MUTATION_KINDS = /* @__PURE__ */ new Set([ "vcs:fileAdd", "vcs:fileModify", "vcs:fileDelete", "vcs:fileRename" ]); } }); // src/sync/types.ts var PROTOCOL_VERSION = 1; var MIN_SUPPORTED_VERSION = 1; var MAX_SUPPORTED_VERSION = 1; // src/sync/room-core.ts init_ops(); // src/vcs/sync-policy.ts import { createHash, randomUUID, createCipheriv, createDecipheriv, createHmac, randomBytes } from "node:crypto"; import { readFileSync, writeFileSync } from "node:fs"; var LOCAL_ONLY_OP_KINDS = ["vcs:chatMessage"]; function isLocalOnlyOpKind(kind) { return LOCAL_ONLY_OP_KINDS.includes(kind); } var DEFAULT_POLICIES = { production: { blockRemoteDestructive: true, bulkDeleteThreshold: 10, requireCleanWorkingTree: true, quarantineSuspicious: true }, sandbox: { blockRemoteDestructive: false, bulkDeleteThreshold: 100, requireCleanWorkingTree: false, quarantineSuspicious: false }, development: { blockRemoteDestructive: false, bulkDeleteThreshold: 50, requireCleanWorkingTree: false, quarantineSuspicious: true } }; function getSyncPolicy() { const env = process.env.TRELLIS_SYNC_ENV ?? "development"; return DEFAULT_POLICIES[env] ?? DEFAULT_POLICIES.development; } function classifyChangeRisk(message) { const type = message.type; switch (type) { case "graph-snapshot": return { risk: "elevated", reason: "Full graph snapshot" }; case "lane-journal": return { risk: "safe", reason: "Lane journal sync" }; case "decision-trace": return { risk: "safe", reason: "Decision trace sync" }; case "entity-delta": return classifyEntityDeltaRisk(message); case "ops": return classifyOpsRisk(message); default: return { risk: "safe", reason: "Unknown message type" }; } } function classifyEntityDeltaRisk(message) { const { entityCount, changeTypes } = message; if (changeTypes.includes("delete")) { if (entityCount > 10) { return { risk: "destructive", reason: `Bulk delete of ${entityCount} entities` }; } return { risk: "elevated", reason: "Entity deletion" }; } if (entityCount > 100) { return { risk: "elevated", reason: `Bulk modification of ${entityCount} entities` }; } return { risk: "safe", reason: "Entity delta sync" }; } function classifyOpsRisk(message) { const ops = message.ops ?? []; const deleteOps = ops.filter( (op) => op.kind === "delete" || op.kind === "repair" ); const systemOps = ops.filter( (op) => op.kind === "config" || op.kind === "agent-rule" ); if (systemOps.length > 0) { return { risk: "critical", reason: "System configuration or agent rule modification" }; } if (deleteOps.length > 5) { return { risk: "destructive", reason: `Bulk destructive operations (${deleteOps.length})` }; } if (deleteOps.length > 0) { return { risk: "elevated", reason: "Destructive operations present" }; } return { risk: "safe", reason: "Normal operations" }; } function shouldBlockMessage(message, policy) { const risk = classifyChangeRisk(message); if (policy.blockRemoteDestructive && risk.risk === "destructive") { return { blocked: true, reason: "destructive-op", details: risk.reason }; } if (policy.blockRemoteDestructive && risk.risk === "critical") { return { blocked: true, reason: "system-modification", details: risk.reason }; } if (risk.risk === "destructive" && message.entityCount > policy.bulkDeleteThreshold) { return { blocked: true, reason: "bulk-delete", details: risk.reason }; } if (policy.quarantineSuspicious && (risk.risk === "elevated" || risk.risk === "destructive")) { return { blocked: true, reason: "quarantine-required", details: risk.reason }; } return { blocked: false }; } var QuarantineStore = class { entries = /* @__PURE__ */ new Map(); storagePath; encryptionKey; hmacKey; constructor(storagePath = ".trellis/quarantine.json") { this.storagePath = storagePath; const keySeed = process.env.TRELLIS_QUARANTINE_KEY || randomUUID(); this.encryptionKey = createHash("sha256").update(keySeed + "-enc").digest(); this.hmacKey = createHash("sha256").update(keySeed + "-hmac").digest(); this.load(); } /** * Add an entry to quarantine. */ add(message, sourcePeerId, reason) { const risk = classifyChangeRisk(message); const id = crypto.randomUUID(); const entry = { id, timestamp: (/* @__PURE__ */ new Date()).toISOString(), sourcePeerId, message, risk, reason, reviewed: false }; this.entries.set(id, entry); this.save(); return id; } /** * Get all quarantine entries. */ getAll() { return Array.from(this.entries.values()).sort( (a, b) => new Date(b.timestamp).getTime() - new Date(a.timestamp).getTime() ); } /** * Get a specific quarantine entry. */ get(id) { return this.entries.get(id); } /** * Mark an entry as reviewed. */ markReviewed(id) { const entry = this.entries.get(id); if (entry) { entry.reviewed = true; this.save(); } } /** * Remove an entry from quarantine. */ remove(id) { this.entries.delete(id); this.save(); } /** * Load quarantine from disk. */ load() { try { const data = readFileSync(this.storagePath, "utf-8"); const encrypted = JSON.parse(data); const hmac = createHmac("sha256", this.hmacKey).update(encrypted.data).digest("hex"); if (hmac !== encrypted.hmac) { console.error( "Quarantine store HMAC verification failed - data may be corrupted" ); return; } const decipher = createDecipheriv( "aes-256-gcm", this.encryptionKey, Buffer.from(encrypted.iv, "hex") ); decipher.setAuthTag(Buffer.from(encrypted.authTag, "hex")); const decrypted = Buffer.concat([ decipher.update(Buffer.from(encrypted.data, "base64")), decipher.final() ]); const entries = JSON.parse( decrypted.toString("utf-8") ); this.entries.clear(); for (const entry of entries) { this.entries.set(entry.id, entry); } } catch { } } /** * Save quarantine to disk. */ save() { const data = JSON.stringify(Array.from(this.entries.values()), null, 2); const iv = randomBytes(16); const cipher = createCipheriv("aes-256-gcm", this.encryptionKey, iv); const encrypted = Buffer.concat([ cipher.update(data, "utf-8"), cipher.final() ]); const authTag = cipher.getAuthTag(); const hmac = createHmac("sha256", this.hmacKey).update(encrypted).digest("hex"); const payload = { data: encrypted.toString("base64"), iv: iv.toString("hex"), authTag: authTag.toString("hex"), hmac }; writeFileSync(this.storagePath, JSON.stringify(payload, null, 2)); } }; // src/sync/room-core.ts var DEFAULT_SNAPSHOT_MAX_OPS = 500; var SyncRoomCore = class { roomPeer; peers = /* @__PURE__ */ new Map(); ops = []; constructor(roomId = "room", roomName = roomId) { this.roomPeer = { id: roomId, name: roomName }; } connectPeer(peerId, peerName = peerId) { this.peers.set(peerId, { id: peerId, name: peerName, lastSeen: (/* @__PURE__ */ new Date()).toISOString() }); } disconnectPeer(peerId) { this.peers.delete(peerId); } getRoomPeer() { return { ...this.roomPeer }; } peersFor(peerId) { return [ this.getRoomPeer(), ...[...this.peers.values()].filter((peer) => peer.id !== peerId).map((peer) => ({ ...peer })) ]; } getOps() { return [...this.ops]; } getOpCount() { return this.ops.length; } /** Build a tail snapshot for late-joiner catch-up. */ buildSnapshot(maxOps = DEFAULT_SNAPSHOT_MAX_OPS) { const opCount = this.ops.length; const headHash = this.ops.at(-1)?.hash; if (opCount <= maxOps) { return { headHash, opCount, truncated: false, ops: [...this.ops] }; } return { headHash, opCount, truncated: true, ops: this.ops.slice(-maxOps) }; } /** Deliver a snapshot message to a connected peer. */ snapshotDeliveries(peerId, maxOps = DEFAULT_SNAPSHOT_MAX_OPS) { const snap = this.buildSnapshot(maxOps); return [ { peerId, message: { version: PROTOCOL_VERSION, type: "snapshot", peerId: this.roomPeer.id, headHash: snap.headHash, opCount: snap.opCount, truncated: snap.truncated, ops: snap.ops } } ]; } async receive(fromPeerId, message) { const peer = this.peers.get(fromPeerId); if (peer) { peer.lastSeen = (/* @__PURE__ */ new Date()).toISOString(); } if (typeof message.version !== "number" || message.version < MIN_SUPPORTED_VERSION || message.version > MAX_SUPPORTED_VERSION) { return [ { peerId: fromPeerId, message: { version: PROTOCOL_VERSION, type: "nack", peerId: this.roomPeer.id, refs: [], reason: "protocol-version", details: `Unsupported protocol version ${message.version}; supported range is ${MIN_SUPPORTED_VERSION}-${MAX_SUPPORTED_VERSION}.` } } ]; } switch (message.type) { case "have": return this.handleHave(fromPeerId, message); case "want": return this.handleWant(fromPeerId, message); case "sync-snapshot": return this.snapshotDeliveries( fromPeerId, message.maxOps ?? DEFAULT_SNAPSHOT_MAX_OPS ); case "ops": return this.handleOps(fromPeerId, message.ops); case "ack": case "nack": case "snapshot": return []; case "graph-snapshot": case "lane-journal": case "decision-trace": case "entity-delta": case "device-revoked": return []; } } async handleHave(peerId, message) { const deliveries = this.roomOpsDeliveries(peerId, message.heads.main); const roomHashes = new Set(this.ops.map((op) => op.hash)); const peerHead = message.heads.main; if (message.opCount > this.ops.length || peerHead && !roomHashes.has(peerHead)) { deliveries.push({ peerId, message: { version: PROTOCOL_VERSION, type: "want", peerId: this.roomPeer.id, wantHashes: [] } }); } else if (!deliveries.some( (d) => d.message.type === "want" || d.message.type === "ops" )) { deliveries.push({ peerId, message: { version: PROTOCOL_VERSION, type: "want", peerId: this.roomPeer.id, wantHashes: [] } }); } return deliveries; } async handleWant(peerId, message) { if (message.wantHashes.length > 0) { const wanted = new Set(message.wantHashes); return this.opsDelivery( peerId, this.ops.filter((op) => wanted.has(op.hash)) ); } if (message.maxOps !== void 0 && message.maxOps > 0 && !message.afterHash) { return this.snapshotDeliveries(peerId, message.maxOps); } return this.roomOpsDeliveries(peerId, message.afterHash); } async handleOps(peerId, ops) { const result = await this.appendOps(ops); const accepted = ops.filter( (op) => this.ops.some((roomOp) => roomOp.hash === op.hash) ); const deliveries = []; if (accepted.length > 0) { deliveries.push({ peerId, message: { version: PROTOCOL_VERSION, type: "ack", peerId: this.roomPeer.id, integrated: accepted.map((op) => op.hash) } }); } if (result.acceptedOps.length > 0) { deliveries.push(...this.broadcastOps(peerId, result.acceptedOps)); } return deliveries; } async appendOps(incomingOps) { const known = new Set(this.ops.map((op) => op.hash)); const pendingByHash = /* @__PURE__ */ new Map(); const rejected = []; const acceptedOps = []; let skipped = 0; let applied = 0; for (const op of incomingOps) { if (known.has(op.hash) || pendingByHash.has(op.hash)) { skipped++; continue; } if (!isVcsOpKind(op.kind)) { rejected.push({ op, reason: "invalid-kind", message: `Rejected non-VCS op kind '${op.kind}'.` }); continue; } if (!await verifyVcsOpHash(op)) { rejected.push({ op, reason: "hash-mismatch", message: `Rejected op with mismatched hash '${op.hash}'.` }); continue; } pendingByHash.set(op.hash, op); } let pending = [...pendingByHash.values()]; while (pending.length > 0) { const nextPending = []; let progressed = false; for (const op of pending) { if (op.previousHash && !known.has(op.previousHash)) { nextPending.push(op); continue; } this.ops.push(op); known.add(op.hash); acceptedOps.push(op); applied++; progressed = true; } if (!progressed) { for (const op of nextPending) { rejected.push({ op, reason: "missing-dependency", message: `Missing previousHash '${op.previousHash}' for op '${op.hash}'.` }); } break; } pending = nextPending; } return { applied, skipped, rejected, acceptedOps }; } roomOpsDeliveries(peerId, afterHash) { let ops = this.ops; if (afterHash) { const index = this.ops.findIndex((op) => op.hash === afterHash); ops = index >= 0 ? this.ops.slice(index + 1) : this.ops; } return this.opsDelivery(peerId, ops); } opsDelivery(peerId, ops) { if (ops.length === 0) return []; const deliverable = ops.filter((op) => !isLocalOnlyOpKind(op.kind)); if (deliverable.length === 0) return []; return [ { peerId, message: { version: PROTOCOL_VERSION, type: "ops", peerId: this.roomPeer.id, ops: deliverable } } ]; } broadcastOps(fromPeerId, ops) { const deliveries = []; for (const peerId of this.peers.keys()) { if (peerId === fromPeerId) continue; deliveries.push(...this.opsDelivery(peerId, ops)); } return deliveries; } }; // src/sync/sync-engine.ts init_reconciler(); var SyncEngine = class { localPeerId; state; transport; getLocalOps; onOpsReceived; onNackReceived; onDeviceRevoked; branchPolicy; constructor(opts) { this.localPeerId = opts.localPeerId; this.transport = opts.transport; this.getLocalOps = opts.getLocalOps; this.onOpsReceived = opts.onOpsReceived; this.onNackReceived = opts.onNackReceived; this.onDeviceRevoked = opts.onDeviceRevoked; this.branchPolicy = opts.branchPolicy ?? { linear: true }; this.state = { localPeerId: opts.localPeerId, peerHeads: /* @__PURE__ */ new Map(), pendingAcks: /* @__PURE__ */ new Set(), lastSync: /* @__PURE__ */ new Map() }; this.transport.onMessage((msg) => this.handleMessage(msg)); } // ------------------------------------------------------------------------- // Public API // ------------------------------------------------------------------------- /** * Initiate a sync with a specific peer. * Sends a 'have' message advertising our heads. */ async pushTo(peerId) { const ops = this.getLocalOps(); const heads = {}; if (ops.length > 0) { heads["main"] = ops[ops.length - 1].hash; } await this.transport.send(peerId, { version: PROTOCOL_VERSION, type: "have", peerId: this.localPeerId, heads, opCount: ops.length }); } /** * Request ops from a peer. */ async pullFrom(peerId) { const ops = this.getLocalOps(); const lastHash = ops.length > 0 ? ops[ops.length - 1].hash : void 0; await this.transport.send(peerId, { version: PROTOCOL_VERSION, type: "want", peerId: this.localPeerId, wantHashes: [], afterHash: lastHash }); } /** * Request the peer's complete op set and rely on hash dedupe during ingest. */ async pullAllFrom(peerId) { await this.transport.send(peerId, { version: PROTOCOL_VERSION, type: "want", peerId: this.localPeerId, wantHashes: [] }); } /** * Request a truncated tail snapshot from a room peer (late-joiner catch-up). * The room replies with a `snapshot` message handled like `ops`. */ async requestSnapshot(peerId, maxOps = DEFAULT_SNAPSHOT_MAX_OPS) { await this.transport.send(peerId, { version: PROTOCOL_VERSION, type: "sync-snapshot", peerId: this.localPeerId, maxOps }); } /** * Send all our ops to a peer (full push). */ async sendOps(peerId, ops) { const opsToSend = ops ?? this.getLocalOps(); await this.sendOpsMessage(peerId, opsToSend); } /** * Internal: send an `ops` message and track outbound hashes in * `pendingAcks` until the receiver acks or nacks them. All three sites * that emit `ops` (sendOps, handleHave, handleWant) route through here so * pendingAcks reflects every outbound op uniformly. */ async sendOpsMessage(peerId, ops) { if (ops.length === 0) return; for (const op of ops) { this.state.pendingAcks.add(op.hash); } await this.transport.send(peerId, { version: PROTOCOL_VERSION, type: "ops", peerId: this.localPeerId, ops }); } /** * Reconcile our ops with a remote peer's ops. */ reconcileWith(remoteOps) { const localOps = this.getLocalOps(); return reconcile(localOps, remoteOps); } /** * Get current sync state. */ getState() { return this.state; } /** * Get branch policy. */ getBranchPolicy() { return this.branchPolicy; } /** * Set branch policy. */ setBranchPolicy(policy) { this.branchPolicy = policy; } /** * List known peers. */ listPeers() { return this.transport.peers(); } // ------------------------------------------------------------------------- // Message handling // ------------------------------------------------------------------------- async handleMessage(msg) { if (typeof msg.version !== "number" || msg.version < MIN_SUPPORTED_VERSION || msg.version > MAX_SUPPORTED_VERSION) { await this.transport.send(msg.peerId, { version: PROTOCOL_VERSION, type: "nack", peerId: this.localPeerId, refs: [], reason: "protocol-version", details: `Unsupported protocol version ${msg.version}; supported range is ${MIN_SUPPORTED_VERSION}-${MAX_SUPPORTED_VERSION}.` }); return; } switch (msg.type) { case "have": await this.handleHave(msg); break; case "want": await this.handleWant(msg); break; case "ops": await this.handleOps(msg); break; case "ack": this.handleAck(msg); break; case "nack": await this.handleNack(msg); break; case "snapshot": await this.handleSnapshot(msg); break; case "sync-snapshot": break; case "device-revoked": await this.handleDeviceRevoked(msg); break; } } async handleDeviceRevoked(msg) { await this.onDeviceRevoked?.(msg); } async handleSnapshot(msg) { if (msg.ops.length === 0) return; await this.handleOps({ version: msg.version, type: "ops", peerId: msg.peerId, ops: msg.ops }); } async handleHave(msg) { this.state.peerHeads.set(msg.peerId, msg.heads); const localOps = this.getLocalOps(); const localHashes = new Set(localOps.map((o) => o.hash)); for (const [, hash] of Object.entries(msg.heads)) { if (!localHashes.has(hash)) { const afterHash = msg.opCount > localOps.length ? void 0 : localOps.length > 0 ? localOps[localOps.length - 1].hash : void 0; await this.transport.send(msg.peerId, { version: PROTOCOL_VERSION, type: "want", peerId: this.localPeerId, wantHashes: [], afterHash }); return; } } if (msg.opCount > localOps.length) { await this.transport.send(msg.peerId, { version: PROTOCOL_VERSION, type: "want", peerId: this.localPeerId, wantHashes: [] }); return; } const peerOpCount = msg.opCount; if (localOps.length > peerOpCount) { await this.sendOpsMessage(msg.peerId, localOps.slice(peerOpCount)); } } async handleWant(msg) { const localOps = this.getLocalOps(); let opsToSend; if (msg.afterHash) { const idx = localOps.findIndex((o) => o.hash === msg.afterHash); opsToSend = idx >= 0 ? localOps.slice(idx + 1) : localOps; } else if (msg.wantHashes.length > 0) { const wanted = new Set(msg.wantHashes); opsToSend = localOps.filter((o) => wanted.has(o.hash)); } else { opsToSend = localOps; } await this.sendOpsMessage(msg.peerId, opsToSend); } async handleOps(msg) { if (msg.ops.length === 0) return; let result; if (this.branchPolicy.linear) { const localOps = this.getLocalOps(); const localHashes = new Set(localOps.map((o) => o.hash)); const newOps = msg.ops.filter((o) => !localHashes.has(o.hash)); if (newOps.length > 0) { result = await this.onOpsReceived(newOps) ?? void 0; } } else { const reconciled = this.reconcileWith(msg.ops); if (reconciled.uniqueToB.length > 0) { result = await this.onOpsReceived(reconciled.uniqueToB) ?? void 0; } } const rejections = result?.rejections ?? []; if (rejections.length > 0) { const byReason = /* @__PURE__ */ new Map(); for (const r of rejections) { const entry = byReason.get(r.reason) ?? { refs: [], details: r.details }; entry.refs.push(r.hash); byReason.set(r.reason, entry); } for (const [reason, entry] of byReason) { await this.transport.send(msg.peerId, { version: PROTOCOL_VERSION, type: "nack", peerId: this.localPeerId, refs: entry.refs, reason, details: entry.details }); } } const rejectedSet = new Set(rejections.map((r) => r.hash)); const ackHashes = msg.ops.map((o) => o.hash).filter((h) => !rejectedSet.has(h)); if (ackHashes.length > 0) { await this.transport.send(msg.peerId, { version: PROTOCOL_VERSION, type: "ack", peerId: this.localPeerId, integrated: ackHashes }); } this.state.lastSync.set(msg.peerId, (/* @__PURE__ */ new Date()).toISOString()); } handleAck(msg) { for (const hash of msg.integrated) { this.state.pendingAcks.delete(hash); } this.state.lastSync.set(msg.peerId, (/* @__PURE__ */ new Date()).toISOString()); } async handleNack(msg) { for (const ref of msg.refs) { this.state.pendingAcks.delete(ref); } this.state.lastSync.set(msg.peerId, (/* @__PURE__ */ new Date()).toISOString()); if (this.onNackReceived) { await this.onNackReceived(msg); } } }; export { PROTOCOL_VERSION, MIN_SUPPORTED_VERSION, MAX_SUPPORTED_VERSION, findForkPoint, reconcile, reconciler_exports, init_reconciler, getSyncPolicy, shouldBlockMessage, QuarantineStore, DEFAULT_SNAPSHOT_MAX_OPS, SyncRoomCore, SyncEngine };