UNPKG

trellis

Version:

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

236 lines (234 loc) 7.06 kB
import { TrellisVcsEngine, init_engine } from "./chunk-O56VT7VP.js"; import "./chunk-QC5OHKIJ.js"; import "./chunk-DIHRG6LA.js"; import "./chunk-2DFWNMEW.js"; import "./chunk-G3XIHPSQ.js"; import "./chunk-Q4FKTPX4.js"; import "./chunk-KFJMKL4Y.js"; import "./chunk-LNCBUJNO.js"; import "./chunk-PBH357QR.js"; import "./chunk-E2CFJKLU.js"; import "./chunk-MFZ22U6M.js"; import { PROVENANCE, init_canonical_op } from "./chunk-RUMOVKR4.js"; import "./chunk-GRWQPKYK.js"; import "./chunk-2ESYSVXG.js"; // src/federation/remote-manager.ts init_engine(); init_canonical_op(); import { readFileSync, writeFileSync, existsSync, mkdirSync } from "fs"; import { join, resolve } from "path"; var RemoteManager = class { trellisPath; remotesPath; constructor(trellisPath) { this.trellisPath = trellisPath; this.remotesPath = join(trellisPath, "remotes.json"); } /** Load remotes configuration */ loadRemotes() { if (!existsSync(this.remotesPath)) { return { remotes: {} }; } try { const content = readFileSync(this.remotesPath, "utf8"); return JSON.parse(content); } catch (error) { throw new Error(`Failed to load remotes config: ${error}`); } } /** Save remotes configuration */ saveRemotes(config) { try { if (!existsSync(this.trellisPath)) { mkdirSync(this.trellisPath, { recursive: true }); } writeFileSync(this.remotesPath, JSON.stringify(config, null, 2), "utf8"); } catch (error) { throw new Error(`Failed to save remotes config: ${error}`); } } /** Add a new remote */ addRemote(name, path) { const config = this.loadRemotes(); if (config.remotes[name]) { throw new Error(`Remote '${name}' already exists`); } const resolvedPath = resolve(path); if (!TrellisVcsEngine.isRepo(resolvedPath)) { throw new Error(`Not a TrellisVCS repository: ${resolvedPath}`); } config.remotes[name] = { name, path: resolvedPath }; this.saveRemotes(config); } /** Remove a remote */ removeRemote(name) { const config = this.loadRemotes(); if (!config.remotes[name]) { throw new Error(`Remote '${name}' not found`); } delete config.remotes[name]; this.saveRemotes(config); } /** List all remotes */ listRemotes() { const config = this.loadRemotes(); return Object.values(config.remotes); } /** Pull new ops from a specific remote */ async pullRemote(remoteName, localEngine) { const startTime = Date.now(); const config = this.loadRemotes(); const remote = config.remotes[remoteName]; if (!remote) { throw new Error(`Remote '${remoteName}' not found`); } const errors = []; let newOps = 0; let latestOpId = remote.lastOpId || ""; try { const remoteEngine = new TrellisVcsEngine({ rootPath: remote.path, provenance: PROVENANCE.sync }); remoteEngine.open(); const remoteOps = remoteEngine.getOps(); if (remoteOps.length === 0) { return { remote: remoteName, newOps: 0, latestOpId: "", durationMs: Date.now() - startTime, errors: [] }; } const lastOpHash = remote.lastOpId; let startIndex = 0; if (lastOpHash) { startIndex = remoteOps.findIndex((op) => op.hash === lastOpHash); if (startIndex === -1) { startIndex = 0; errors.push( `Last op hash ${lastOpHash} not found in remote, pulling all ops` ); } else { startIndex++; } } const opsToPull = remoteOps.slice(startIndex); if (opsToPull.length > 0) { const prefixedOps = opsToPull.map( (op) => this.prefixOpEntities(op, remoteName) ); for (const op of prefixedOps) { localEngine.opLog.append(op); newOps++; } latestOpId = remoteOps[remoteOps.length - 1].hash; } remote.lastOpId = latestOpId; remote.pulledAt = (/* @__PURE__ */ new Date()).toISOString(); this.saveRemotes(config); } catch (error) { errors.push(`Failed to pull from remote: ${error}`); } return { remote: remoteName, newOps, latestOpId, durationMs: Date.now() - startTime, errors }; } /** Pull from all configured remotes */ async pullAll(localEngine) { const config = this.loadRemotes(); const remoteNames = Object.keys(config.remotes); if (remoteNames.length === 0) { return { results: [], totalNewOps: 0, totalDurationMs: 0 }; } const startTime = Date.now(); const results = []; let totalNewOps = 0; for (const remoteName of remoteNames) { try { const result = await this.pullRemote(remoteName, localEngine); results.push(result); totalNewOps += result.newOps; } catch (error) { results.push({ remote: remoteName, newOps: 0, latestOpId: "", durationMs: 0, errors: [`Failed to pull: ${error}`] }); } } return { results, totalNewOps, totalDurationMs: Date.now() - startTime }; } /** Prefix entity IDs in operations with remote name */ prefixOpEntities(op, remoteName) { const prefixed = { ...op }; if (op.vcs) { const vcs = { ...op.vcs }; if (vcs.issueId && !vcs.issueId.includes(":")) { vcs.issueId = `${remoteName}:${vcs.issueId}`; } if (vcs.parentIssueId && !vcs.parentIssueId.includes(":")) { vcs.parentIssueId = `${remoteName}:${vcs.parentIssueId}`; } if (vcs.blockedByIssueId && !vcs.blockedByIssueId.includes(":")) { vcs.blockedByIssueId = `${remoteName}:${vcs.blockedByIssueId}`; } if (vcs.decisionId && !vcs.decisionId.includes(":")) { vcs.decisionId = `${remoteName}:${vcs.decisionId}`; } prefixed.vcs = vcs; } if (op.facts) { prefixed.facts = op.facts.map((fact) => { if (fact.e === "entity" && fact.a === "id" && typeof fact.v === "string" && !fact.v.includes(":")) { return { ...fact, v: `${remoteName}:${fact.v}` }; } if (fact.e === "entity" && fact.a === "from" && typeof fact.v === "string" && !fact.v.includes(":")) { return { ...fact, v: `${remoteName}:${fact.v}` }; } if (fact.e === "entity" && fact.a === "to" && typeof fact.v === "string" && !fact.v.includes(":")) { return { ...fact, v: `${remoteName}:${fact.v}` }; } return fact; }); } if (op.links) { prefixed.links = op.links.map((link) => ({ ...link, e1: link.e1.includes(":") ? link.e1 : `${remoteName}:${link.e1}`, e2: link.e2.includes(":") ? link.e2 : `${remoteName}:${link.e2}` })); } if (!prefixed.facts) prefixed.facts = []; prefixed.facts.push({ e: "op", a: "remote", v: remoteName }); return prefixed; } }; export { RemoteManager };