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
JavaScript
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
};