trellis
Version:
Agentic State Engine — event-sourced causal graph with branching, decision traces, and realtime sync for AI-native applications
629 lines (620 loc) • 17.1 kB
JavaScript
import {
IrohSyncTransport
} from "../chunk-UAQCJ2CF.js";
import {
PartyKitRoomTransport,
TrellisVcsSyncPeer
} from "../chunk-44NB2G5E.js";
import {
DEFAULT_SNAPSHOT_MAX_OPS,
MAX_SUPPORTED_VERSION,
MIN_SUPPORTED_VERSION,
PROTOCOL_VERSION,
SyncEngine,
SyncRoomCore,
findForkPoint,
init_reconciler,
reconcile
} from "../chunk-TIRVQJDD.js";
import {
PROVENANCE,
init_canonical_op
} from "../chunk-RUMOVKR4.js";
import "../chunk-GRWQPKYK.js";
import "../chunk-2ESYSVXG.js";
// src/sync/index.ts
init_reconciler();
// src/sync/memory-transport.ts
var MemoryTransport = class {
peerId;
peerName;
handlers = [];
connectedPeers = /* @__PURE__ */ new Map();
constructor(peerId, peerName = peerId) {
this.peerId = peerId;
this.peerName = peerName;
}
/**
* Connect two transports so they can exchange messages.
*/
static connect(a, b) {
a.connectedPeers.set(b.peerId, b);
b.connectedPeers.set(a.peerId, a);
}
/**
* Disconnect two transports.
*/
static disconnect(a, b) {
a.connectedPeers.delete(b.peerId);
b.connectedPeers.delete(a.peerId);
}
async send(peerId, message) {
const peer = this.connectedPeers.get(peerId);
if (!peer) {
throw new Error(`Peer not connected: ${peerId}`);
}
for (const handler of peer.handlers) {
await handler(message);
}
}
onMessage(handler) {
this.handlers.push(handler);
}
peers() {
return [...this.connectedPeers.entries()].map(([id, transport]) => ({
id,
name: transport.peerName
}));
}
getPeerId() {
return this.peerId;
}
};
// src/sync/memory-room.ts
var MemorySyncRoom = class {
core;
transports = /* @__PURE__ */ new Map();
constructor(roomId = "room", roomName = roomId) {
this.core = new SyncRoomCore(roomId, roomName);
}
connectPeer(peerId, peerName = peerId) {
this.core.connectPeer(peerId, peerName);
const transport = new MemoryRoomTransport(this, peerId, peerName);
this.transports.set(peerId, transport);
return transport;
}
disconnectPeer(peerId) {
this.core.disconnectPeer(peerId);
this.transports.delete(peerId);
}
getRoomPeer() {
return this.core.getRoomPeer();
}
peersFor(peerId) {
return this.core.peersFor(peerId);
}
getOps() {
return this.core.getOps();
}
getOpCount() {
return this.core.getOpCount();
}
async receive(fromPeerId, message) {
const deliveries = await this.core.receive(fromPeerId, message);
for (const delivery of deliveries) {
await this.deliver(delivery.peerId, delivery.message);
}
}
async deliver(peerId, message) {
const transport = this.transports.get(peerId);
if (!transport) {
throw new Error(`Peer not connected to room: ${peerId}`);
}
await transport.deliver(message);
}
};
var MemoryRoomTransport = class {
room;
peerId;
peerName;
handlers = [];
constructor(room, peerId, peerName = peerId) {
this.room = room;
this.peerId = peerId;
this.peerName = peerName;
}
async send(peerId, message) {
if (peerId !== this.room.getRoomPeer().id) {
throw new Error(`Peer not connected through room: ${peerId}`);
}
await this.room.receive(this.peerId, message);
}
onMessage(handler) {
this.handlers.push(handler);
}
peers() {
return this.room.peersFor(this.peerId);
}
getPeerId() {
return this.peerId;
}
getPeerName() {
return this.peerName;
}
async deliver(message) {
for (const handler of this.handlers) {
await handler(message);
}
}
};
// src/sync/sync-room-server.ts
var SyncRoomServer = class {
core;
connections = /* @__PURE__ */ new Map();
welcomeSnapshot;
welcomeSnapshotMaxOps;
constructor(opts = {}) {
this.core = new SyncRoomCore(opts.roomId, opts.roomName);
this.welcomeSnapshot = opts.welcomeSnapshot !== false;
this.welcomeSnapshotMaxOps = opts.welcomeSnapshotMaxOps ?? DEFAULT_SNAPSHOT_MAX_OPS;
}
getRoomId() {
return this.core.getRoomPeer().id;
}
getOpCount() {
return this.core.getOpCount();
}
getOps() {
return this.core.getOps();
}
buildSnapshot(maxOps) {
return this.core.buildSnapshot(maxOps);
}
/** Register a connected peer and optionally send a welcome snapshot. */
async connect(conn) {
this.core.connectPeer(conn.peerId);
this.connections.set(conn.peerId, conn);
if (this.welcomeSnapshot) {
await this.sendSnapshot(conn.peerId, this.welcomeSnapshotMaxOps);
}
}
disconnect(peerId) {
this.core.disconnectPeer(peerId);
this.connections.delete(peerId);
}
/** Route an inbound sync message from a peer. */
async handleMessage(fromPeerId, message) {
const deliveries = await this.core.receive(fromPeerId, message);
await this.deliverAll(deliveries);
}
/** Push a tail snapshot to a peer without an inbound message. */
async sendSnapshot(peerId, maxOps = DEFAULT_SNAPSHOT_MAX_OPS) {
const deliveries = this.core.snapshotDeliveries(peerId, maxOps);
await this.deliverAll(deliveries);
}
async deliverAll(deliveries) {
for (const delivery of deliveries) {
const conn = this.connections.get(delivery.peerId);
if (!conn) continue;
await conn.send(delivery.message);
}
}
};
// src/sync/partykit-room.ts
function createPartyKitRoomHandler(opts = {}) {
function getState(room) {
const bag = room;
if (!bag.trellis) {
bag.trellis = {
server: new SyncRoomServer({
roomId: "room",
roomName: room.id,
welcomeSnapshot: opts.welcomeSnapshot,
welcomeSnapshotMaxOps: opts.welcomeSnapshotMaxOps
})
};
}
return bag.trellis;
}
function peerIdFromConnection(connection, requestUrl) {
if (requestUrl) {
try {
const peer = new URL(requestUrl).searchParams.get("peerId");
if (peer) return peer;
} catch {
}
}
return connection.id;
}
return {
async onConnect(connection, room, request) {
const { server } = getState(room);
const peerId = peerIdFromConnection(connection, request?.url);
const conn = {
peerId,
send(message) {
connection.send(JSON.stringify(message));
}
};
await server.connect(conn);
},
async onMessage(message, connection, room, request) {
const { server } = getState(room);
const peerId = peerIdFromConnection(connection, request?.url);
const raw = typeof message === "string" ? message : new TextDecoder().decode(message);
let parsed;
try {
parsed = JSON.parse(raw);
} catch {
return;
}
if (!parsed || typeof parsed.type !== "string") return;
await server.handleMessage(peerId, parsed);
},
onClose(connection, room, request) {
const { server } = getState(room);
const peerId = peerIdFromConnection(connection, request?.url);
server.disconnect(peerId);
}
};
}
// src/sync/http-transport.ts
var HttpSyncTransport = class {
localPeerId;
peerUrls = /* @__PURE__ */ new Map();
messageHandler = null;
knownPeers = /* @__PURE__ */ new Map();
constructor(localPeerId) {
this.localPeerId = localPeerId;
}
/**
* Add a remote peer by URL (e.g. "http://192.168.1.10:4200").
*/
addPeer(peerId, url, name) {
this.peerUrls.set(peerId, url);
this.knownPeers.set(peerId, {
id: peerId,
name: name ?? peerId,
lastSeen: (/* @__PURE__ */ new Date()).toISOString()
});
}
/**
* Remove a remote peer.
*/
removePeer(peerId) {
this.peerUrls.delete(peerId);
this.knownPeers.delete(peerId);
}
async send(peerId, message) {
const url = this.peerUrls.get(peerId);
if (!url) throw new Error(`Unknown peer "${peerId}". Add it with addPeer() first.`);
const resp = await fetch(`${url}/sync/message`, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify(message)
});
if (!resp.ok) {
throw new Error(`Sync message to ${peerId} failed: ${resp.status} ${resp.statusText}`);
}
const contentType = resp.headers.get("content-type");
if (contentType?.includes("application/json")) {
const reply = await resp.json();
if (reply && reply.type && this.messageHandler) {
await this.messageHandler(reply);
}
}
}
onMessage(handler) {
this.messageHandler = handler;
}
/**
* Receive a message (called by the HTTP server handler).
*/
async receiveMessage(message) {
this.knownPeers.set(message.peerId, {
id: message.peerId,
name: message.peerId,
lastSeen: (/* @__PURE__ */ new Date()).toISOString()
});
if (this.messageHandler) {
await this.messageHandler(message);
}
}
peers() {
return [...this.knownPeers.values()];
}
getLocalPeerId() {
return this.localPeerId;
}
};
function createSyncHandler(transport) {
return (req) => {
const url = new URL(req.url);
if (url.pathname === "/sync/message" && req.method === "POST") {
return new Response(
req.json().then(async (body) => {
await transport.receiveMessage(body);
return JSON.stringify({ ok: true });
}),
{ headers: { "Content-Type": "application/json" } }
);
}
if (url.pathname === "/sync/peers" && req.method === "GET") {
return new Response(
JSON.stringify({
localPeerId: transport.getLocalPeerId(),
peers: transport.peers()
}),
{ headers: { "Content-Type": "application/json" } }
);
}
return null;
};
}
// src/sync/ws-transport.ts
var WebSocketSyncTransport = class {
localPeerId;
connections = /* @__PURE__ */ new Map();
knownPeers = /* @__PURE__ */ new Map();
messageHandler = null;
constructor(localPeerId) {
this.localPeerId = localPeerId;
}
/**
* Connect to a remote peer's WebSocket endpoint.
*/
async connect(peerId, url, name) {
return new Promise((resolve, reject) => {
const ws = new WebSocket(url);
ws.onopen = () => {
this.connections.set(peerId, ws);
this.knownPeers.set(peerId, {
id: peerId,
name: name ?? peerId,
lastSeen: (/* @__PURE__ */ new Date()).toISOString()
});
resolve();
};
ws.onmessage = async (event) => {
try {
const msg = JSON.parse(String(event.data));
this.knownPeers.set(msg.peerId, {
id: msg.peerId,
name: msg.peerId,
lastSeen: (/* @__PURE__ */ new Date()).toISOString()
});
if (this.messageHandler) {
await this.messageHandler(msg);
}
} catch {
}
};
ws.onclose = () => {
this.connections.delete(peerId);
};
ws.onerror = (err) => {
reject(new Error(`WebSocket connection to ${peerId} failed`));
};
});
}
/**
* Accept an incoming WebSocket connection (server side).
*/
acceptConnection(peerId, ws) {
this.connections.set(peerId, ws);
this.knownPeers.set(peerId, {
id: peerId,
name: peerId,
lastSeen: (/* @__PURE__ */ new Date()).toISOString()
});
ws.onmessage = async (event) => {
try {
const msg = JSON.parse(String(event.data));
this.knownPeers.set(msg.peerId, {
id: msg.peerId,
name: msg.peerId,
lastSeen: (/* @__PURE__ */ new Date()).toISOString()
});
if (this.messageHandler) {
await this.messageHandler(msg);
}
} catch {
}
};
ws.onclose = () => {
this.connections.delete(peerId);
};
}
async send(peerId, message) {
const ws = this.connections.get(peerId);
if (!ws || ws.readyState !== WebSocket.OPEN) {
throw new Error(`No open connection to peer "${peerId}".`);
}
ws.send(JSON.stringify(message));
}
onMessage(handler) {
this.messageHandler = handler;
}
peers() {
return [...this.knownPeers.values()];
}
/**
* Disconnect from a peer.
*/
disconnect(peerId) {
const ws = this.connections.get(peerId);
if (ws) {
ws.close();
this.connections.delete(peerId);
}
return Promise.resolve();
}
/**
* Disconnect from all peers.
*/
disconnectAll() {
for (const [id, ws] of this.connections) {
ws.close();
}
this.connections.clear();
}
/**
* Check if connected to a specific peer.
*/
isConnected(peerId) {
const ws = this.connections.get(peerId);
return ws !== void 0 && ws.readyState === WebSocket.OPEN;
}
getLocalPeerId() {
return this.localPeerId;
}
};
// src/sync/multi-repo.ts
init_canonical_op();
var SYNC_CTX = { provenance: PROVENANCE.sync };
var MultiRepoManager = class {
kernel;
constructor(kernel) {
this.kernel = kernel;
}
/**
* Link a remote repository.
*/
async linkRepo(alias, location, description) {
const id = `repo:${alias}`;
const existing = this.kernel.getEntity(id);
if (existing) {
throw new Error(`Repo alias "${alias}" is already linked to "${existing.facts.find((f) => f.a === "location")?.v}".`);
}
await this.kernel.createEntity(id, "LinkedRepo", {
alias,
location,
...description ? { description } : {},
linkedAt: (/* @__PURE__ */ new Date()).toISOString()
}, void 0, SYNC_CTX);
}
/**
* Unlink a remote repository.
*/
async unlinkRepo(alias) {
const id = `repo:${alias}`;
await this.kernel.deleteEntity(id, SYNC_CTX);
}
/**
* List all linked repos.
*/
listLinkedRepos() {
const entities = this.kernel.listEntities("LinkedRepo");
return entities.map((e) => {
const get = (a) => e.facts.find((f) => f.a === a)?.v;
return {
alias: String(get("alias") ?? ""),
location: String(get("location") ?? ""),
description: get("description"),
linkedAt: String(get("linkedAt") ?? ""),
lastSyncedAt: get("lastSyncedAt")
};
});
}
/**
* Get a linked repo by alias.
*/
getLinkedRepo(alias) {
const entity = this.kernel.getEntity(`repo:${alias}`);
if (!entity) return null;
const get = (a) => entity.facts.find((f) => f.a === a)?.v;
return {
alias: String(get("alias") ?? ""),
location: String(get("location") ?? ""),
description: get("description"),
linkedAt: String(get("linkedAt") ?? ""),
lastSyncedAt: get("lastSyncedAt")
};
}
/**
* Create a cross-repo link: entity in this repo → entity in remote repo.
*/
async addCrossRepoLink(sourceEntityId, attribute, targetRepoAlias, targetEntityId) {
const repo = this.getLinkedRepo(targetRepoAlias);
if (!repo) {
throw new Error(`Repo alias "${targetRepoAlias}" is not linked. Use linkRepo() first.`);
}
const crossRef = `@${targetRepoAlias}:${targetEntityId}`;
await this.kernel.addLink(sourceEntityId, attribute, crossRef, SYNC_CTX);
}
/**
* Remove a cross-repo link.
*/
async removeCrossRepoLink(sourceEntityId, attribute, targetRepoAlias, targetEntityId) {
const crossRef = `@${targetRepoAlias}:${targetEntityId}`;
await this.kernel.removeLink(sourceEntityId, attribute, crossRef, SYNC_CTX);
}
/**
* Find all cross-repo references from a given entity.
*/
getCrossRepoLinks(entityId) {
const store = this.kernel.getStore();
const links = store.getLinksByEntity(entityId);
const results = [];
for (const link of links) {
if (link.e1 !== entityId) continue;
const parsed = parseCrossRepoRef(link.e2);
if (parsed) {
results.push({ attribute: link.a, ref: parsed });
}
}
return results;
}
/**
* Find all cross-repo references pointing to a specific remote entity.
*/
findReferencesTo(repoAlias, entityId) {
const crossRef = `@${repoAlias}:${entityId}`;
const store = this.kernel.getStore();
return store.getAllLinks().filter((l) => l.e2 === crossRef);
}
/**
* Update the lastSyncedAt timestamp for a linked repo.
*/
async markSynced(alias) {
await this.kernel.updateEntity(`repo:${alias}`, {
lastSyncedAt: (/* @__PURE__ */ new Date()).toISOString()
}, SYNC_CTX);
}
};
function parseCrossRepoRef(ref) {
if (!ref.startsWith("@")) return null;
const colonIdx = ref.indexOf(":", 1);
if (colonIdx === -1) return null;
return {
repoAlias: ref.slice(1, colonIdx),
entityId: ref.slice(colonIdx + 1)
};
}
function formatCrossRepoRef(repoAlias, entityId) {
return `@${repoAlias}:${entityId}`;
}
export {
DEFAULT_SNAPSHOT_MAX_OPS,
HttpSyncTransport,
IrohSyncTransport,
MAX_SUPPORTED_VERSION,
MIN_SUPPORTED_VERSION,
MemoryRoomTransport,
MemorySyncRoom,
MemoryTransport,
MultiRepoManager,
PROTOCOL_VERSION,
PartyKitRoomTransport,
SyncEngine,
SyncRoomCore,
SyncRoomServer,
TrellisVcsSyncPeer,
WebSocketSyncTransport,
createPartyKitRoomHandler,
createSyncHandler,
findForkPoint,
formatCrossRepoRef,
parseCrossRepoRef,
reconcile
};