#!/usr/bin/env node "use strict"; const fs = require("fs"); const path = require("path"); const { createRequire } = require("module"); const { operatorViewPublication, projectSwarmOperatorView, readSwarmOperatorInputs, watchSwarmOperatorInputs, } = require("./voidbot-swarm-operator-view.cjs"); const { parseEndpoint: parseIdunnEndpoint, publishIdunnRudpHealth } = require("./publish-idunn-rudp-health.cjs"); const repoRoot = path.resolve(__dirname, ".."); const defaultStorePath = path.join(repoRoot, ".voidbot", "status", "cultmesh", "voidbot-swarm-state.cc"); const defaultControlStorePath = path.join(repoRoot, ".voidbot", "private", "swarm-controls.cc"); const defaultBind = "127.0.0.1:17873"; const defaultOdinCultMeshUri = "cultmesh://odin/rendezvous/provider-catalog"; const defaultOdinRudpEndpoint = "rudp://127.0.0.1:17871"; const connectionId = 0x43554c54; const odinConnectionId = 0x0d1d0002; const args = parseArgs(process.argv.slice(2)); const storePath = path.resolve(args.store || process.env.VOIDBOT_SWARM_CULTMESH_STORE || defaultStorePath); const controlStorePath = path.resolve(args.controlStore || process.env.VOIDBOT_SWARM_CONTROL_STORE || defaultControlStorePath); const operatorInputPaths = { heartbeat: path.resolve(process.env.REPO_FACE_HEARTBEAT_STATE_PATH || path.join(repoRoot, ".voidbot", "status", "repo-face-heartbeats.json")), orchestrator: path.resolve(process.env.VOIDBOT_ORCHESTRATOR_STATE_PATH || path.join(repoRoot, ".voidbot", "status", "gamecult-orchestrator.json")), pause: path.resolve(process.env.VOIDBOT_SWARM_PAUSE_STATE_PATH || path.join(repoRoot, "state", "agent-swarm-paused.json")), control: controlStorePath, }; const bind = parseBind(args.bind || process.env.VOIDBOT_SWARM_CULTMESH_BIND || defaultBind); const odinCultMeshUri = args.odinCultMeshUri || args["odin-cultmesh-uri"] || process.env.VOIDBOT_ODIN_CULTMESH_URI || process.env.ODIN_CULTMESH_URI || defaultOdinCultMeshUri; const odinRudpEndpoint = args.odinRudpEndpoint || args["odin-rudp-endpoint"] || process.env.VOIDBOT_ODIN_RUDP || process.env.CULTMESH_URI_ODIN_RUDP || defaultOdinRudpEndpoint; const idunnHealthEndpoint = String(process.env.VOIDBOT_IDUNN_RUDP_HEALTH || "").trim(); const idunnHealthIntervalMs = 30_000; const { CultMesh, CultMeshMemoryProviderReceiptStore, CultMeshProviderRudpTransport, CultMeshProviderSession, CultNetDocumentRegistry, defineCultNetDocumentBinding, defineDocumentType, } = loadCultRuntime(); const documents = defineDocuments(defineDocumentType); const documentDefinitions = Object.values(documents); const bindings = { provider: defineCultNetDocumentBinding({ definition: documents.provider }), surface: defineCultNetDocumentBinding({ definition: documents.surface }), commandReceipt: defineCultNetDocumentBinding({ definition: documents.commandReceipt }), }; const documentRegistry = new CultNetDocumentRegistry( documentDefinitions.map((definition) => defineCultNetDocumentBinding({ definition })), ); let server = null; let announceTimer = null; let liveProviderSession = null; let stopOperatorWatch = null; let operatorPublishChain = Promise.resolve(); let idunnHealthTimer = null; let idunnHealthPublishChain = Promise.resolve(); main().catch((error) => { console.error(error instanceof Error ? error.stack || error.message : String(error)); process.exitCode = 1; }); async function main() { await publishCurrentControlState(); server = CultMesh.createRudpDocumentServer("voidbot-swarm-cultmesh", connectionId, { bindHost: bind.host, bindPort: bind.port, documents: documentRegistry, getCache: async () => { if (!fs.existsSync(storePath)) { throw new Error(`VoidBot swarm CultMesh store is missing at ${storePath}`); } const node = await CultMesh.createNode(storePath, { documents: documentDefinitions, }); return node.cache; }, onError: (error) => { console.error(`VoidBot swarm CultMesh/RUDP server error: ${error instanceof Error ? error.message : String(error)}`); }, onDocumentPutRaw: applySwarmCommand, }); await server.start(); liveProviderSession = await startLiveProviderSession(); await publishCurrentOperatorView(); stopOperatorWatch = watchSwarmOperatorInputs(operatorInputPaths, queueOperatorViewPublication); console.log(`VoidBot swarm CultMesh/RUDP serving ${storePath} at rudp://${bind.host}:${bind.port}`); announceToOdin().catch((error) => { console.error(`VoidBot swarm Odin announcement failed: ${error instanceof Error ? error.message : String(error)}`); }); announceTimer = setInterval(() => { announceToOdin().catch((error) => { console.error(`VoidBot swarm Odin announcement failed: ${error instanceof Error ? error.message : String(error)}`); }); }, 30_000); announceTimer.unref?.(); if (idunnHealthEndpoint) { await queueIdunnHealthPublication(); idunnHealthTimer = setInterval(queueIdunnHealthPublication, idunnHealthIntervalMs); idunnHealthTimer.unref?.(); } else { console.log("VoidBot Idunn health publication is disabled because VOIDBOT_IDUNN_RUDP_HEALTH is not configured."); } const shutdown = () => { try { if (announceTimer) clearInterval(announceTimer); if (idunnHealthTimer) clearInterval(idunnHealthTimer); server?.close?.(); stopOperatorWatch?.(); void liveProviderSession?.stop?.(); } finally { process.exit(0); } }; process.once("SIGINT", shutdown); process.once("SIGTERM", shutdown); } function queueIdunnHealthPublication() { idunnHealthPublishChain = idunnHealthPublishChain .then(() => publishIdunnRudpHealth({ endpoint: parseIdunnEndpoint(idunnHealthEndpoint), daemonId: process.env.VOIDBOT_IDUNN_DAEMON || "voidbot", healthContract: process.env.VOIDBOT_IDUNN_HEALTH_CONTRACT || "voidbot.cultnet-rudp-stack-health", }, { state: "healthy", detail: "VoidBot swarm CultMesh publisher is serving retained runtime state.", observedAt: new Date().toISOString(), })) .catch((error) => console.error(`VoidBot Idunn health publication failed: ${error instanceof Error ? error.message : String(error)}`)); return idunnHealthPublishChain; } async function publishCurrentControlState() { if (!fs.existsSync(controlStorePath)) return; const controlNode = await CultMesh.createNode(controlStorePath, { documents: [documents.swarmControl] }); const control = controlNode.get(documents.swarmControl, "voidbot-swarm"); if (control) await projectAcceptedHeat(control); } async function applySwarmCommand(document) { if (document.schemaId !== "gamecult.eve.command.v1") return; const command = document.payload; if (!command || command.providerId !== "voidbot.swarm" || command.command !== "swarm.set_heat") return; const value = Number(command.payload?.value); if (!Number.isFinite(value) || value < 0.05 || value > 2) { throw new Error(`swarm.set_heat value must be between 0.05 and 2, got ${command.payload?.value}`); } const node = await CultMesh.createNode(controlStorePath, { documents: [documents.swarmControl] }); const control = { schemaVersion: "voidbot.swarm_control_state.v1", globalHeat: Math.round(value * 100) / 100, commandId: command.commandId, updatedAt: new Date().toISOString(), updatedBy: command.clientId || command.publishedBy || "unknown", }; await node.put(documents.swarmControl, "voidbot-swarm", control); await node.flush?.(true); await projectAcceptedHeat(control); console.log(`Applied swarm heat ${control.globalHeat} from ${control.commandId}`); return { binding: bindings.commandReceipt, recordKey: control.commandId, value: { schema: "gamecult.eve.command_receipt.v1", receiptId: `voidbot.swarm:${control.commandId}`, commandId: control.commandId, command: command.command, state: "reconciled", ownerRepo: "VoidBot", authority: "voidbot.swarm_control_state.v1:voidbot-swarm", providerId: command.providerId, surfaceId: command.surfaceId || "voidbot.swarm.surface", sourceVersion: Date.parse(control.updatedAt), issuedAtUtc: control.updatedAt, message: `Swarm heat applied at ${control.globalHeat}.`, diagnostics: [{ code: "state-published", pointerId: "globalHeat" }], }, sourceRuntimeId: "voidbot-swarm-cultmesh", sourceRole: "swarm-control-owner", tags: ["eve", "command-receipt", "swarm-control"], }; } async function projectAcceptedHeat(control) { const node = await CultMesh.createNode(storePath, { documents: documentDefinitions }); await node.put(documents.swarmControl, "voidbot-swarm", control); await node.flush?.(true); await liveProviderSession?.upsertPublication?.(controlPublication(control)); await queueOperatorViewPublication(control); } function queueOperatorViewPublication(control) { operatorPublishChain = operatorPublishChain .then(() => publishCurrentOperatorView(control)) .catch((error) => console.error(`VoidBot swarm operator view publication failed: ${error instanceof Error ? error.message : String(error)}`)); return operatorPublishChain; } async function publishCurrentOperatorView(controlOverride) { const { control: _controlPath, ...jsonInputPaths } = operatorInputPaths; const inputs = await readSwarmOperatorInputs(jsonInputPaths); const controlNode = controlOverride ? null : await CultMesh.createNode(controlStorePath, { documents: [documents.swarmControl] }); const control = controlOverride ?? controlNode?.get(documents.swarmControl, "voidbot-swarm"); const view = projectSwarmOperatorView({ ...inputs, control, route: { providerStatus: liveProviderSession ? "connected" : "local-only", endpoint: `rudp://${bind.host}:${bind.port}`, }, }); const node = await CultMesh.createNode(storePath, { documents: documentDefinitions }); await node.put(documents.swarmOperatorView, "voidbot-swarm", view); await node.flush?.(true); await liveProviderSession?.upsertPublication?.(operatorViewPublication(view)); } async function startLiveProviderSession() { const endpoint = String(process.env.HERMODR_PROVIDER_SESSION_ENDPOINT || "").trim(); const sessionToken = String(process.env.HERMODR_PROVIDER_SESSION_TOKEN || "").trim(); if (!endpoint && !sessionToken) return null; if (!endpoint || !sessionToken) { throw new Error("HERMODR_PROVIDER_SESSION_ENDPOINT and HERMODR_PROVIDER_SESSION_TOKEN must be configured together."); } const controlNode = await CultMesh.createNode(controlStorePath, { documents: [documents.swarmControl] }); const control = controlNode.get(documents.swarmControl, "voidbot-swarm"); const transport = new CultMeshProviderRudpTransport({ endpoint, runtimeId: "voidbot-swarm-live-provider", connectionId, sessionToken, }); const session = new CultMeshProviderSession({ identity: { providerId: "voidbot.swarm", serviceInstanceId: "voidbot-swarm-live-provider", endpointId: "voidbot.swarm.live.rudp", verseId: "voidbot.local", }, transport, receiptStore: new CultMeshMemoryProviderReceiptStore(), publications: control ? [controlPublication(control)] : [], commandHandlers: {}, }); await session.start(); console.log(`VoidBot swarm provider session ${session.state.status} as ${session.identity.providerId}${session.state.lastError ? `: ${session.state.lastError}` : ""}`); return session; } function controlPublication(control) { return { publicationId: "voidbot.swarm.control", documentType: "voidbot.swarm_control_state", schemaId: "voidbot.swarm_control_state.v1", recordKey: "voidbot-swarm", value: control, }; } async function announceToOdin() { const node = await CultMesh.createNode(storePath, { documents: documentDefinitions, }); const provider = node.get(documents.provider, "voidbot.swarm"); const surface = node.get(documents.surface, "voidbot.swarm"); if (!provider || !surface) { throw new Error("VoidBot swarm store must contain provider and surface documents before announcing to Odin."); } const options = { connectTimeoutMs: 2_000, flushTimeoutMs: 150, sourceRuntimeId: "voidbot-swarm-cultmesh", sourceRole: "daemon-provider", tags: ["voidbot", "eve", "cultmesh-rudp"], resolveCultMeshRudpEndpoint: (uri) => String(uri || "").startsWith("cultmesh://odin/") ? odinRudpEndpoint : undefined, }; await CultMesh.publishRudpDocumentOnce( "voidbot-swarm-cultmesh", odinConnectionId, odinRudpEndpoint, bindings.provider, "voidbot.swarm", provider, options, ); await CultMesh.publishRudpDocumentOnce( "voidbot-swarm-cultmesh", odinConnectionId, odinRudpEndpoint, bindings.surface, "voidbot.swarm", surface, options, ); console.log(`VoidBot swarm announced provider and surface documents to ${odinCultMeshUri}`); } function defineDocuments(defineDocumentType) { const objectSchema = (label) => ({ parse(value) { if (!value || typeof value !== "object" || Array.isArray(value)) { throw new Error(`${label} must be an object.`); } return value; }, }); return { snapshot: defineDocumentType({ type: "voidbot.swarm_state_snapshot", schemaName: "voidbot.swarm_state_snapshot", schemaId: "voidbot.swarm_state_snapshot.v1", schemaVersion: "voidbot.swarm_state_snapshot.v1", global: true, schema: objectSchema("VoidBot swarm snapshot"), }), provider: defineDocumentType({ type: "gamecult.eve.provider_advertisement", schemaName: "gamecult.eve.provider_advertisement", schemaId: "gamecult.eve.provider_advertisement.v1", schemaVersion: "gamecult.eve.provider_advertisement.v1", global: false, name: (value) => value?.providerId || value?.provider?.id || "provider", schema: objectSchema("Eve provider advertisement"), }), surface: defineDocumentType({ type: "gamecult.eve.surface_state", schemaName: "gamecult.eve.surface_state", schemaId: "gamecult.eve.surface_state.v1", schemaVersion: "gamecult.eve.surface_state.v1", global: false, name: (value) => value?.providerId || value?.provider_id || "surface", schema: objectSchema("Eve surface state"), }), binding: defineDocumentType({ type: "gamecult.eve.interface_binding", schemaName: "gamecult.eve.interface_binding", schemaId: "gamecult.eve.interface_binding.v1", schemaVersion: "gamecult.eve.interface_binding.v1", global: false, name: (value) => value?.bindingId || value?.providerId || "interface", schema: objectSchema("Eve interface binding"), }), providerCatalog: defineDocumentType({ type: "voidbot.provider_advertisement_catalog", schemaName: "voidbot.provider_advertisement_catalog", schemaId: "voidbot.provider_advertisement_catalog.v0", schemaVersion: "voidbot.provider_advertisement_catalog.v0", global: true, schema: objectSchema("VoidBot provider advertisement catalog"), }), transportProfile: defineDocumentType({ type: "idunn.daemon_transport_profile", schemaName: "idunn.daemon_transport_profile", schemaId: "idunn.daemon_transport_profile.v1", schemaVersion: "idunn.daemon_transport_profile.v1", global: false, name: (value) => value?.profile_id || value?.daemon_id || "voidbot", schema: objectSchema("Idunn daemon transport profile"), }), commandBoundary: defineDocumentType({ type: "idunn.command_boundary", schemaName: "idunn.command_boundary", schemaId: "idunn.command_boundary.v1", schemaVersion: "idunn.command_boundary.v1", global: false, name: (value) => value?.boundary_id || value?.daemon_id || "voidbot", schema: objectSchema("Idunn command boundary"), }), eveCommand: defineDocumentType({ type: "gamecult.eve.command", schemaName: "gamecult.eve.command", schemaId: "gamecult.eve.command.v1", schemaVersion: "gamecult.eve.command.v1", global: false, name: (value) => value?.commandId || value?.command_id || "swarm-command", schema: objectSchema("Eve command"), }), commandReceipt: defineDocumentType({ type: "gamecult.eve.command_receipt", schemaName: "gamecult.eve.command_receipt", schemaId: "gamecult.eve.command_receipt.v1", schemaVersion: "gamecult.eve.command_receipt.v1", global: false, name: (value) => value?.commandId || value?.receiptId || "command-receipt", schema: objectSchema("Eve command receipt"), }), swarmControl: defineDocumentType({ type: "voidbot.swarm_control_state", schemaName: "voidbot.swarm_control_state", schemaId: "voidbot.swarm_control_state.v1", schemaVersion: "voidbot.swarm_control_state.v1", global: false, name: () => "voidbot-swarm", schema: objectSchema("VoidBot swarm control state"), }), swarmOperatorView: defineDocumentType({ type: "voidbot.swarm_operator_view", schemaName: "voidbot.swarm_operator_view", schemaId: "voidbot.swarm_operator_view.v1", schemaVersion: "voidbot.swarm_operator_view.v1", global: false, name: () => "voidbot-swarm", schema: objectSchema("VoidBot swarm operator view"), }), }; } function loadCultRuntime() { const cultLibRoot = process.env.VOIDBOT_CULTLIB_ROOT ? path.resolve(process.env.VOIDBOT_CULTLIB_ROOT) : path.resolve(repoRoot, "..", "CultLib-dev-runtime"); const packageJson = path.resolve(cultLibRoot, "packages", "cultmesh-ts", "package.json"); const requireCult = createRequire(packageJson); const { CultMesh, CultMeshMemoryProviderReceiptStore, CultMeshProviderRudpTransport, CultMeshProviderSession, } = requireCult("./dist/index.js"); const { CultNetDocumentRegistry, defineCultNetDocumentBinding } = createRequire(path.resolve(cultLibRoot, "packages", "cultnet-ts", "package.json"))("./dist/index.js"); const { defineDocumentType } = createRequire(path.resolve(cultLibRoot, "packages", "cultcache-ts", "package.json"))("./dist/index.js"); return { CultMesh, CultMeshMemoryProviderReceiptStore, CultMeshProviderRudpTransport, CultMeshProviderSession, CultNetDocumentRegistry, defineCultNetDocumentBinding, defineDocumentType, }; } function parseArgs(values) { const parsed = {}; for (let index = 0; index < values.length; index += 1) { const arg = values[index]; if (arg === "--store") parsed.store = values[++index]; else if (arg === "--control-store") parsed.controlStore = values[++index]; else if (arg === "--bind") parsed.bind = values[++index]; else if (arg === "--odin-cultmesh-uri") parsed.odinCultMeshUri = values[++index]; else if (arg === "--odin-rudp-endpoint") parsed.odinRudpEndpoint = values[++index]; } return parsed; } function parseBind(value) { const text = String(value || "").trim(); const index = text.lastIndexOf(":"); if (index <= 0) throw new Error(`Bind address must be host:port, got ${value}`); const host = text.slice(0, index); const port = Number(text.slice(index + 1)); if (!Number.isInteger(port) || port <= 0 || port > 65535) { throw new Error(`Invalid port in bind address ${value}`); } return { host, port }; }