#!/usr/bin/env node import { randomUUID } from "node:crypto"; import { createServer } from "node:http"; import { createRequire } from "node:module"; import { networkInterfaces } from "node:os"; import { dirname, extname, join, resolve } from "node:path"; import { fileURLToPath, pathToFileURL } from "node:url"; import { existsSync } from "node:fs"; import { mkdir, readFile, stat, writeFile } from "node:fs/promises"; const repoRoot = resolve(dirname(fileURLToPath(import.meta.url)), ".."); const defaultStorageRoot = resolve(repoRoot, ".voidbot"); const defaultStatusDir = resolve(defaultStorageRoot, "status"); const defaultSnapshotPath = resolve(defaultStatusDir, "swarm-state.json"); const defaultDashboardPath = resolve(defaultStatusDir, "swarm-dashboard.html"); const defaultCultMeshStorePath = resolve(defaultStatusDir, "cultmesh", "voidbot-swarm-state.cc"); const snapshotDocumentType = "voidbot.swarm_state_snapshot"; const snapshotSchemaId = "voidbot.swarm_state_snapshot.v1"; const args = parseArgs(process.argv.slice(2)); const env = await readDotEnv(resolve(repoRoot, ".env")); const storageRoot = resolveConfigPath(env.STORAGE_ROOT, defaultStorageRoot); const statusDir = resolve(storageRoot, "status"); const heartbeatStatePath = resolveConfigPath( env.REPO_FACE_HEARTBEAT_STATE_PATH, resolve(statusDir, "repo-face-heartbeats.json"), ); const orchestratorPath = resolve(statusDir, "gamecult-orchestrator.json"); const pausePath = resolve(repoRoot, "state", "agent-swarm-paused.json"); const snapshotPath = resolveConfigPath(args.snapshot, defaultSnapshotPath.replace(defaultStatusDir, statusDir)); const dashboardPath = resolveConfigPath(args.out, defaultDashboardPath.replace(defaultStatusDir, statusDir)); const cultMeshStorePath = resolveConfigPath( args.cultmeshStore, defaultCultMeshStorePath.replace(defaultStatusDir, statusDir), ); const renderResult = await render(); if (args.open) { console.log(pathToFileURL(renderResult.dashboardPath).href); } if (args.serve) { await serveDashboard({ host: args.host ?? "0.0.0.0", port: Number.parseInt(args.port ?? "8787", 10), rootDir: dirname(renderResult.dashboardPath), refreshSeconds: Number.parseInt(args.refreshSeconds ?? "10", 10), }); } else { console.log(`Swarm dashboard HTML: ${renderResult.dashboardPath}`); console.log(`Swarm snapshot JSON: ${renderResult.snapshotPath}`); console.log(`CultMesh snapshot store: ${renderResult.cultMeshStorePath}`); } async function render() { let snapshot = await buildSnapshot(); if (args.public) { snapshot = redactSnapshot(snapshot); } await mkdir(dirname(snapshotPath), { recursive: true }); await writeFile(snapshotPath, `${JSON.stringify(snapshot, null, 2)}\n`, "utf8"); await mkdir(dirname(dashboardPath), { recursive: true }); await writeFile(dashboardPath, renderHtml(snapshot), "utf8"); const cultMeshWrite = await writeCultMeshSnapshot(snapshot, cultMeshStorePath); const finalSnapshot = { ...snapshot, cultMesh: { ...snapshot.cultMesh, ...cultMeshWrite, }, }; await writeFile(snapshotPath, `${JSON.stringify(finalSnapshot, null, 2)}\n`, "utf8"); await writeFile(dashboardPath, renderHtml(finalSnapshot), "utf8"); return { dashboardPath, snapshotPath, cultMeshStorePath, }; } async function buildSnapshot() { const now = new Date(); const heartbeat = await readJsonFile(heartbeatStatePath); const orchestrator = await readJsonFile(orchestratorPath); const pause = await readJsonFile(pausePath); const identityMetadata = await readIdentityMetadata(env.REPO_DISCORD_IDENTITIES_PATH); const heartbeatMtime = await getMtime(heartbeatStatePath); const orchestratorMtime = await getMtime(orchestratorPath); const paused = pause.ok ? pause.value?.paused !== false : false; const pauseReason = pause.ok && typeof pause.value?.reason === "string" ? pause.value.reason : undefined; const participants = Array.isArray(heartbeat.value?.participants) ? heartbeat.value.participants : []; const pendingMentions = Array.isArray(heartbeat.value?.pendingMentions) ? heartbeat.value.pendingMentions : []; const initiativeClock = numberOrNull(heartbeat.value?.initiativeClock); const participantSnapshots = participants.map((participant) => projectParticipant(participant, pendingMentions, initiativeClock, identityMetadata), ); const faceStates = await readFaceStates(identityMetadata); for (const participant of participantSnapshots) { participant.faceState = faceStates.get(participant.identityId.toLowerCase()) ?? { readable: false, path: participant.faceStatePath, error: participant.faceStatePath ? "State file was not readable." : "No Face state path registered.", tree: [], }; participant.restState = participant.faceState.restState ?? null; } const activeTurns = participantSnapshots.filter((participant) => participant.activeJobId); const readyNow = participantSnapshots.filter((participant) => participant.status === "active" && !participant.activeJobId && participant.restState?.isNapping !== true && typeof participant.nextTurnInMinutes === "number" && participant.nextTurnInMinutes <= 0, ); const nextParticipant = [...participantSnapshots] .filter((participant) => typeof participant.nextTurnInMinutes === "number") .sort((left, right) => left.nextTurnInMinutes - right.nextTurnInMinutes)[0]; const organs = projectOrgans(orchestrator.value); const recentEvents = Array.isArray(heartbeat.value?.history) ? heartbeat.value.history.slice(-24).reverse().map(projectHistoryEvent) : []; const controls = projectControls(heartbeat.value?.controls); const upcomingTurns = buildUpcomingTurns(participantSnapshots); const lastShuffle = findLastShuffleEvent(recentEvents); return { schemaVersion: snapshotSchemaId, generatedAt: now.toISOString(), title: "VoidBot Swarm State", sources: { heartbeatStatePath, heartbeatStateUpdatedAt: heartbeatMtime, heartbeatStateReadable: heartbeat.ok, heartbeatStateError: heartbeat.ok ? undefined : heartbeat.error, orchestratorPath, orchestratorUpdatedAt: orchestratorMtime, orchestratorReadable: orchestrator.ok, orchestratorError: orchestrator.ok ? undefined : orchestrator.error, pausePath, pauseReadable: pause.ok, pauseError: pause.ok ? undefined : pause.error, }, cultMesh: { documentType: snapshotDocumentType, schemaId: snapshotSchemaId, documentKey: "voidbot-swarm", storePath: cultMeshStorePath, writeStatus: "pending", note: "CultMesh should distribute this typed CultCache snapshot; the web page remains read-only.", }, summary: { state: deriveSwarmState({ heartbeat, orchestrator, paused, activeTurns }), paused, pauseReason, participantCount: participantSnapshots.length, activeTurnCount: activeTurns.length, pendingMentionCount: pendingMentions.length, readyNowCount: readyNow.length, nextIdentityId: nextParticipant?.identityId ?? null, nextDisplayName: nextParticipant?.displayName ?? null, nextTurnInMinutes: nextParticipant?.nextTurnInMinutes ?? null, initiativeClock, baseRecoveryMinutes: numberOrNull(heartbeat.value?.baseRecoveryMinutes), globalHeat: numberOrNull(heartbeat.value?.globalHeat), cadenceMultiplier: controls.cadenceMultiplier, lastTickAt: stringOrNull(heartbeat.value?.lastTickAt), lastShuffle, }, controls, upcomingTurns, participants: participantSnapshots, activeTurns, pendingMentions: pendingMentions.map(projectPendingMention), orchestrator: { state: deriveOrchestratorState(organs), organs, }, recentEvents, }; } function projectControls(value) { const cadenceMultiplier = numberOrNull(value?.cadenceMultiplier) ?? 1; const manualTurnRequests = Array.isArray(value?.manualTurnRequests) ? value.manualTurnRequests.slice(-12).reverse().map((request) => ({ id: stringOrNull(request?.id), identityId: stringOrNull(request?.identityId), requestedAt: stringOrNull(request?.requestedAt), status: stringOrNull(request?.status) ?? "pending", note: stringOrNull(request?.note), })) : []; return { cadenceMultiplier, updatedAt: stringOrNull(value?.updatedAt), manualTurnRequests, }; } function buildUpcomingTurns(participants) { const ordered = [...participants] .filter((participant) => participant.status === "active") .sort((left, right) => { const activeDelta = Number(Boolean(right.activeJobId)) - Number(Boolean(left.activeJobId)); if (activeDelta) return activeDelta; return (left.nextTurnInMinutes ?? 999999) - (right.nextTurnInMinutes ?? 999999); }) .slice(0, 16); const maxMinutes = Math.max(1, ...ordered.map((participant) => Math.max(0, participant.nextTurnInMinutes ?? 0))); return ordered.map((participant, index) => ({ identityId: participant.identityId, displayName: participant.displayName, repoName: participant.repoName, avatarUrl: participant.avatarUrl, participantKind: participant.participantKind, activeJobId: participant.activeJobId, restState: participant.restState, pendingMentionCount: participant.pendingMentionCount, shuffleReason: participant.pendingMentionCount > 0 ? "mention" : participant.activeJobId ? "active" : participant.restState?.isNapping === true ? "nap" : null, nextTurnInMinutes: participant.nextTurnInMinutes, effectiveSpeed: participant.effectiveSpeed, heat: participant.heat, lane: index % 3, timelinePosition: participant.activeJobId ? 0 : Math.min(100, Math.max(0, ((Math.max(0, participant.nextTurnInMinutes ?? 0)) / maxMinutes) * 100)), })); } function findLastShuffleEvent(events) { const directMention = events.find((event) => event.type === "queued" && typeof event.pendingMentionCount === "number" && event.pendingMentionCount > 0 ); if (directMention) { return { kind: "direct_mention", identityId: directMention.identityId, observedAt: directMention.observedAt, }; } const manual = events.find((event) => event.type === "manual_turn_override_applied"); if (manual) { return { kind: "manual_override", identityId: manual.identityId, observedAt: manual.observedAt, }; } return null; } function projectParticipant(participant, pendingMentions, initiativeClock, identityMetadata) { const identityId = String(participant.identityId ?? "unknown"); const metadata = identityMetadata.get(identityId.toLowerCase()) ?? {}; const nextTurnAt = numberOrNull(participant.nextTurnAt); const lastTurnAt = numberOrNull(participant.lastTurnAt); const activeTurnStartedAt = numberOrNull(participant.activeTurnStartedAt); const channelCount = Array.isArray(participant.groups) ? participant.groups.filter((entry) => typeof entry === "string" && entry.startsWith("channel:")).length : 0; const mentionCount = pendingMentions.filter((mention) => mention?.identityId === identityId).length; const nextTurnInMinutes = typeof nextTurnAt === "number" && typeof initiativeClock === "number" ? round(nextTurnAt - initiativeClock, 3) : null; return { identityId, displayName: String(participant.displayName ?? metadata.displayName ?? identityId), repoName: String(participant.repoName ?? metadata.repoName ?? "unknown"), repoPath: stringOrNull(metadata.repoPath), faceStatePath: stringOrNull(metadata.faceStatePath), description: stringOrNull(metadata.description), avatarUrl: metadata.avatarUrl ?? null, channelPermissions: Array.isArray(metadata.channelPermissions) ? metadata.channelPermissions.slice(0, 12).map((entry) => ({ label: stringOrNull(entry?.label), topic: stringOrNull(entry?.topic), speechThreshold: stringOrNull(entry?.speechThreshold), speedMultiplier: numberOrNull(entry?.speedMultiplier), })) : [], participantKind: String(participant.participantKind ?? "repo_face"), turnKind: String(participant.turnKind ?? "repo_face_rumination"), status: String(participant.status ?? "unknown"), currentLoad: numberOrNull(participant.currentLoad), heat: numberOrNull(participant.heat), effectiveSpeed: numberOrNull(participant.effectiveSpeed), initiativeSpeed: numberOrNull(participant.initiativeSpeed), nextTurnAt, nextTurnInMinutes, lastTurnAt, activeTurnStartedAt, activeJobId: stringOrNull(participant.activeJobId), lastQueuedAt: stringOrNull(participant.lastQueuedAt), queuedCount: numberOrNull(participant.queuedCount), pendingMentionCount: mentionCount, channelCount, constraintCount: Array.isArray(participant.constraints) ? participant.constraints.length : 0, constraints: Array.isArray(participant.constraints) ? participant.constraints.slice(0, 4).map(String) : [], }; } function projectOrgans(orchestrator) { const organs = orchestrator?.organs && typeof orchestrator.organs === "object" ? orchestrator.organs : {}; return Object.entries(organs).map(([id, organ]) => ({ id, label: String(organ?.label ?? id), intervalMinutes: numberOrNull(organ?.intervalMinutes), lastStartedAt: stringOrNull(organ?.lastStartedAt), lastFinishedAt: stringOrNull(organ?.lastFinishedAt), lastExitCode: numberOrNull(organ?.lastExitCode), lastStatus: String(organ?.lastStatus ?? "unknown"), lastLogPath: stringOrNull(organ?.lastLogPath), })); } function projectHistoryEvent(event) { return { type: String(event?.type ?? "event"), identityId: stringOrNull(event?.identityId), observedAt: stringOrNull(event?.observedAt ?? event?.queuedAt ?? event?.appliedAt), activeJobId: stringOrNull(event?.activeJobId), statusPath: stringOrNull(event?.statusPath), reason: stringOrNull(event?.reason), pendingMentionCount: numberOrNull(event?.pendingMentionCount), nextTurnAt: numberOrNull(event?.nextTurnAt), recoveryMinutes: numberOrNull(event?.recoveryMinutes), }; } async function readIdentityMetadata(registryPathValue) { const metadata = new Map(); const registryPath = resolveConfigPath(registryPathValue, resolve(repoRoot, ".voidbot", "private", "repo-discord-identities.json")); const registry = await readJsonFile(registryPath); const identities = Array.isArray(registry.value?.identities) ? registry.value.identities : Array.isArray(registry.value) ? registry.value : []; for (const identity of identities) { const id = typeof identity?.id === "string" ? identity.id.toLowerCase() : ""; if (id) { metadata.set(id, { id, displayName: stringOrNull(identity?.displayName), repoName: stringOrNull(identity?.repoName), repoPath: stringOrNull(identity?.repoPath), faceStatePath: stringOrNull(identity?.faceStatePath), description: stringOrNull(identity?.description), avatarUrl: stringOrNull(identity?.avatarUrl), channelPermissions: Array.isArray(identity?.channelPermissions) ? identity.channelPermissions : [], }); } } return metadata; } async function readFaceStates(identityMetadata) { const states = new Map(); let core = null; try { const requireCore = createRequire(resolve(repoRoot, "packages", "core", "package.json")); core = requireCore(resolve(repoRoot, "packages", "core", "dist", "index.js")); } catch (error) { for (const [id, identity] of identityMetadata.entries()) { states.set(id, { readable: false, path: identity.faceStatePath ?? null, error: `Core typed-state loader unavailable: ${error instanceof Error ? error.message : String(error)}`, tree: [], }); } return states; } for (const [id, identity] of identityMetadata.entries()) { if (!identity.faceStatePath) { states.set(id, { readable: false, path: null, error: "No Face state path registered.", tree: [], }); continue; } try { const typedState = await core.loadVoidSelfStateTypedDocuments({ canonicalPath: identity.faceStatePath, identity: { agentId: id, publicName: identity.displayName ?? id, publicDescription: identity.description ?? undefined, }, }); const rendered = core.buildVoidSelfStateContext ? core.buildVoidSelfStateContext(typedState, { sourcePath: identity.faceStatePath, identity: { agentId: id, publicName: identity.displayName ?? id, publicDescription: identity.description ?? undefined, }, }) : null; states.set(id, { readable: true, path: identity.faceStatePath, summary: truncate(rendered?.summary ?? "", 1200), counts: countFaceState(typedState), restState: projectRestState(typedState), tree: buildFaceStateTree(typedState), }); } catch (error) { states.set(id, { readable: false, path: identity.faceStatePath, error: error instanceof Error ? error.message : String(error), tree: [], }); } } return states; } function projectRestState(typedState) { const sleepCycle = typedState?.scheduledRuntime?.sleepCycle; if (!sleepCycle || typeof sleepCycle !== "object") { return null; } return { isNapping: sleepCycle.isNapping === true, napEndsAt: stringOrNull(sleepCycle.currentNapEndsAt), nextNapStartsAt: stringOrNull(sleepCycle.nextNapStartsAt), activeDreamThemes: Array.isArray(sleepCycle.activeDreamThemes) ? sleepCycle.activeDreamThemes.slice(0, 8).map(String) : [], }; } function countFaceState(typedState) { return { shortTerm: typedState?.thoughtMemory?.shortTerm?.length ?? 0, memories: typedState?.thoughtMemory?.memories?.length ?? 0, incubation: typedState?.thoughtMemory?.incubation?.length ?? 0, bonds: typedState?.faceAffect?.socialBonds?.length ?? 0, pressures: typedState?.agencyPressure?.pressures?.length ?? 0, candidates: typedState?.candidateInterventions?.interventions?.length ?? 0, }; } function buildFaceStateTree(typedState) { return [ node("Self Profile", "selfProfile", [ leaf("Public Name", typedState?.selfProfile?.publicName), leaf("Description", typedState?.selfProfile?.publicDescription), collectionNode("Values", "selfProfile.values", typedState?.selfProfile?.values, valueNode), objectNode("Activation", "selfProfile.activationProfile", typedState?.selfProfile?.activationProfile), collectionNode("Private Notes", "selfProfile.privateNotes", typedState?.selfProfile?.privateNotes, valueNode), ]), node("Thought Memory", "thoughtMemory", [ collectionNode("Short Term", "thoughtMemory.shortTerm", typedState?.thoughtMemory?.shortTerm, memoryNode), collectionNode("Durable Memories", "thoughtMemory.memories", typedState?.thoughtMemory?.memories, memoryNode), collectionNode("Incubation", "thoughtMemory.incubation", typedState?.thoughtMemory?.incubation, memoryNode), ]), node("Affect", "faceAffect", [ collectionNode("Needs", "faceAffect.needs", typedState?.faceAffect?.needs, valueNode), collectionNode("Social Bonds", "faceAffect.socialBonds", typedState?.faceAffect?.socialBonds, memoryNode), collectionNode("Status Reads", "faceAffect.statusReads", typedState?.faceAffect?.statusReads, memoryNode), objectNode("Mood", "faceAffect.moodDimensions", typedState?.faceAffect?.moodDimensions), collectionNode("Social Biases", "faceAffect.socialBiases", typedState?.faceAffect?.socialBiases, valueNode), ]), node("Agency", "agencyPressure", [ collectionNode("Pressures", "agencyPressure.pressures", typedState?.agencyPressure?.pressures, memoryNode), collectionNode("Candidates", "candidateInterventions.interventions", typedState?.candidateInterventions?.interventions, memoryNode), ]), node("Runtime", "scheduledRuntime", [ objectNode("Sleep Cycle", "scheduledRuntime.sleepCycle", typedState?.scheduledRuntime?.sleepCycle), objectNode("Speaking Pressure", "scheduledRuntime.speakingPressure", typedState?.scheduledRuntime?.speakingPressure), objectNode("Last Runs", "scheduledRuntime.lastRuns", typedState?.scheduledRuntime?.lastRuns), ]), ].filter(Boolean); } function node(label, path, children = []) { const compactChildren = children.filter(Boolean); return { kind: "branch", label, path, children: compactChildren, count: compactChildren.length }; } function leaf(label, value, path = label) { if (value === null || value === undefined || value === "") return null; return { kind: "leaf", label, path, title: label, preview: truncate(renderValuePreview(value), 120), detail: renderValueDetail(value), }; } function collectionNode(label, path, values, projector) { if (!Array.isArray(values) || values.length === 0) return node(label, path, [leaf("empty", "No entries.", `${path}.empty`)]); return node(label, path, values.slice(0, 80).map((value, index) => projector(value, `${path}.${index}`, index))); } function objectNode(label, path, value) { if (!value || typeof value !== "object") return node(label, path, [leaf("empty", "No entries.", `${path}.empty`)]); const children = Object.entries(value).slice(0, 80).map(([key, entry]) => { if (entry && typeof entry === "object") return objectNode(key, `${path}.${key}`, entry); return leaf(key, entry, `${path}.${key}`); }); return node(label, path, children); } function valueNode(value, path, index) { if (value && typeof value === "object") { const title = value.label ?? value.name ?? value.kind ?? value.id ?? `entry ${index + 1}`; return leaf(String(title), value, path); } return leaf(`entry ${index + 1}`, value, path); } function memoryNode(value, path, index) { if (!value || typeof value !== "object") return leaf(`entry ${index + 1}`, value, path); const target = value.target?.label ?? value.target?.id ?? value.targetKind ?? value.target ?? value.kind; const title = value.summary ?? value.claim ?? value.question ?? value.title ?? value.id ?? value.memoryId ?? `entry ${index + 1}`; const label = target ? `${target}: ${title}` : title; return { kind: "leaf", label: truncate(String(label), 120), path, title: String(title), preview: truncate(value.claim ?? value.question ?? value.tension ?? value.actionImplication ?? value.summary ?? renderValuePreview(value), 180), detail: renderMemoryDetail(value), }; } function renderMemoryDetail(value) { const lines = []; for (const key of ["kind", "summary", "claim", "question", "tension", "actionImplication", "status", "intensity", "createdAt", "updatedAt"]) { if (value?.[key] !== null && value?.[key] !== undefined && value?.[key] !== "") { lines.push(`${key}: ${renderValuePreview(value[key])}`); } } if (value?.target) lines.push(`target: ${renderValuePreview(value.target)}`); if (Array.isArray(value?.anchorRefs) && value.anchorRefs.length > 0) { lines.push(`anchors:\n${value.anchorRefs.map((anchor) => `- ${renderValuePreview(anchor)}`).join("\n")}`); } if (Array.isArray(value?.tags) && value.tags.length > 0) lines.push(`tags: ${value.tags.join(", ")}`); return lines.join("\n\n") || renderValueDetail(value); } function renderValuePreview(value) { if (typeof value === "string") return value; if (typeof value === "number" || typeof value === "boolean") return String(value); return JSON.stringify(value); } function renderValueDetail(value) { return typeof value === "string" ? value : JSON.stringify(value, null, 2); } function projectPendingMention(mention) { return { identityId: stringOrNull(mention?.identityId), sourceChannelId: stringOrNull(mention?.sourceChannelId), sourceMessageId: stringOrNull(mention?.sourceMessageId), createdAt: stringOrNull(mention?.createdAt), prompt: truncate(String(mention?.prompt ?? mention?.content ?? ""), 220), }; } function deriveSwarmState({ heartbeat, orchestrator, paused, activeTurns }) { if (!heartbeat.ok) return "missing"; if (paused) return "paused"; const organ = projectOrgans(orchestrator.value).find((entry) => entry.id === "repo-face-heartbeats"); if (organ && organ.lastStatus && !["ok", "skipped_disabled"].includes(organ.lastStatus)) { return "warning"; } if (activeTurns.length > 0) return "running"; return "ready"; } function deriveOrchestratorState(organs) { if (organs.length === 0) return "missing"; if (organs.some((organ) => ["failed", "error", "stalled"].includes(organ.lastStatus))) return "warning"; if (organs.some((organ) => organ.lastStatus === "running")) return "running"; return "ok"; } async function writeCultMeshSnapshot(snapshot, storePath) { try { const cultCacheRoot = resolve(repoRoot, "..", "CultCacheTS"); const cultCachePackage = resolve(cultCacheRoot, "package.json"); if (!existsSync(cultCachePackage)) { return { writeStatus: "skipped", writeError: `CultCacheTS package was not found at ${cultCachePackage}.`, }; } const requireCultCache = createRequire(cultCachePackage); const { CultCache, SingleFileMessagePackBackingStore, defineDocumentType } = requireCultCache( resolve(cultCacheRoot, "dist", "index.js"), ); const snapshotDefinition = defineDocumentType({ type: snapshotDocumentType, schemaName: snapshotDocumentType, schemaId: snapshotSchemaId, schemaVersion: snapshotSchemaId, contentHash: snapshotSchemaId, canonicalSchemaJson: JSON.stringify({ schemaName: snapshotDocumentType, schemaVersion: snapshotSchemaId, description: "Redacted read-only VoidBot swarm state snapshot for CultMesh distribution.", }), global: true, schema: { parse(input) { if (!input || typeof input !== "object") { throw new Error("VoidBot swarm snapshot must be an object."); } return input; }, }, }); const store = new SingleFileMessagePackBackingStore(storePath); const cache = CultCache.builder() .withDocumentType(snapshotDefinition) .withGenericStore(store) .build(); await cache.pullAllBackingStores(); await cache.put(snapshotDefinition, "voidbot-swarm", snapshot); return { writeStatus: "ok", storePath, writtenAt: new Date().toISOString(), }; } catch (error) { return { writeStatus: "failed", storePath, writeError: error instanceof Error ? error.message : String(error), }; } } function renderHtml(snapshot) { return ` VoidBot Swarm Control
`; } function redactSnapshot(snapshot) { const redacted = JSON.parse(JSON.stringify(snapshot)); redacted.visibility = "public_redacted"; redacted.sources = { heartbeatStateReadable: snapshot.sources?.heartbeatStateReadable ?? false, heartbeatStateUpdatedAt: snapshot.sources?.heartbeatStateUpdatedAt ?? null, orchestratorReadable: snapshot.sources?.orchestratorReadable ?? false, orchestratorUpdatedAt: snapshot.sources?.orchestratorUpdatedAt ?? null, pauseReadable: snapshot.sources?.pauseReadable ?? false, }; redacted.cultMesh = { documentType: snapshot.cultMesh?.documentType, schemaId: snapshot.cultMesh?.schemaId, documentKey: snapshot.cultMesh?.documentKey, writeStatus: snapshot.cultMesh?.writeStatus, writtenAt: snapshot.cultMesh?.writtenAt, note: "Public redacted snapshot. Local paths and job ids are withheld.", }; redacted.participants = redacted.participants.map((participant) => ({ ...participant, activeJobId: participant.activeJobId ? "active" : null, constraints: [], constraintCount: participant.constraintCount, description: participant.description ? truncate(participant.description, 220) : null, faceStatePath: null, faceState: participant.faceState ? { readable: participant.faceState.readable, counts: participant.faceState.counts, path: null, tree: [], } : undefined, })); redacted.activeTurns = redacted.activeTurns.map((participant) => ({ ...participant, activeJobId: participant.activeJobId ? "active" : null, constraints: [], })); redacted.pendingMentions = redacted.pendingMentions.map((mention) => ({ identityId: mention.identityId, createdAt: mention.createdAt, })); redacted.orchestrator.organs = redacted.orchestrator.organs.map((organ) => ({ ...organ, lastLogPath: null, })); redacted.recentEvents = redacted.recentEvents.map((event) => ({ ...event, activeJobId: event.activeJobId ? "active" : null, statusPath: null, })); return redacted; } async function serveDashboard({ host, port, rootDir, refreshSeconds }) { const interval = setInterval(async () => { try { await render(); } catch (error) { console.error(`Swarm dashboard refresh failed: ${error instanceof Error ? error.message : String(error)}`); } }, Math.max(refreshSeconds, 2) * 1000); interval.unref(); const server = createServer(async (request, response) => { try { const requestUrl = new URL(request.url ?? "/", `http://${request.headers.host ?? "localhost"}`); if (request.method === "POST" && requestUrl.pathname === "/api/controls") { await handleControlUpdate(request, response); return; } if (request.method === "POST" && requestUrl.pathname === "/api/turns/force") { await handleForceTurn(request, response); return; } const pathname = requestUrl.pathname === "/" ? "/swarm-dashboard.html" : decodeURIComponent(requestUrl.pathname); const target = resolve(rootDir, `.${pathname}`); if (!target.startsWith(rootDir)) { response.writeHead(403); response.end("Forbidden"); return; } const fileStat = await stat(target); if (!fileStat.isFile()) { response.writeHead(404); response.end("Not found"); return; } response.writeHead(200, { "content-type": contentType(target), "cache-control": "no-store", }); response.end(await readFile(target)); } catch { response.writeHead(404); response.end("Not found"); } }); await new Promise((resolveServer) => server.listen(port, host, resolveServer)); console.log(`Swarm dashboard serving ${rootDir}`); for (const url of localUrls(port, host)) { console.log(url); } } async function handleControlUpdate(request, response) { if (args.public) { writeJsonResponse(response, 403, { ok: false, error: "Controls are disabled in public mode." }); return; } const body = await readRequestJson(request); const multiplier = Number(body.cadenceMultiplier); if (!Number.isFinite(multiplier) || multiplier < 0.01 || multiplier > 12) { writeJsonResponse(response, 400, { ok: false, error: "cadenceMultiplier must be between 0.01 and 12." }); return; } const state = await readMutableHeartbeatState(); state.controls = normalizeDashboardControls(state.controls); state.controls.cadenceMultiplier = Math.round(multiplier * 100) / 100; state.controls.updatedAt = new Date().toISOString(); await writeMutableHeartbeatState(state); await render(); writeJsonResponse(response, 200, { ok: true, controls: state.controls }); } async function handleForceTurn(request, response) { if (args.public) { writeJsonResponse(response, 403, { ok: false, error: "Controls are disabled in public mode." }); return; } const body = await readRequestJson(request); const identityId = typeof body.identityId === "string" ? body.identityId.trim() : ""; if (!identityId) { writeJsonResponse(response, 400, { ok: false, error: "identityId is required." }); return; } const state = await readMutableHeartbeatState(); state.controls = normalizeDashboardControls(state.controls); state.controls.manualTurnRequests.push({ id: randomUUID(), identityId, requestedAt: new Date().toISOString(), status: "pending", note: "Requested from swarm dashboard.", }); state.controls.manualTurnRequests = state.controls.manualTurnRequests.slice(-20); state.controls.updatedAt = new Date().toISOString(); await writeMutableHeartbeatState(state); await render(); writeJsonResponse(response, 200, { ok: true, controls: state.controls }); } async function readMutableHeartbeatState() { const parsed = await readJsonFile(heartbeatStatePath); if (!parsed.ok || !parsed.value || typeof parsed.value !== "object") { throw new Error(`Cannot read heartbeat state at ${heartbeatStatePath}`); } return parsed.value; } async function writeMutableHeartbeatState(state) { await mkdir(dirname(heartbeatStatePath), { recursive: true }); await writeFile(heartbeatStatePath, `${JSON.stringify(state, null, 2)}\n`, "utf8"); } function normalizeDashboardControls(value) { const controls = value && typeof value === "object" ? value : {}; return { cadenceMultiplier: Math.min(12, Math.max(0.01, Number(controls.cadenceMultiplier) || 1)), manualTurnRequests: Array.isArray(controls.manualTurnRequests) ? controls.manualTurnRequests : [], updatedAt: typeof controls.updatedAt === "string" ? controls.updatedAt : undefined, }; } async function readRequestJson(request) { const chunks = []; for await (const chunk of request) { chunks.push(chunk); if (Buffer.concat(chunks).length > 64 * 1024) { throw new Error("Request body is too large."); } } const raw = Buffer.concat(chunks).toString("utf8"); return raw ? JSON.parse(raw) : {}; } function writeJsonResponse(response, statusCode, payload) { response.writeHead(statusCode, { "content-type": "application/json; charset=utf-8", "cache-control": "no-store", }); response.end(`${JSON.stringify(payload)}\n`); } function contentType(path) { switch (extname(path).toLowerCase()) { case ".html": return "text/html; charset=utf-8"; case ".json": return "application/json; charset=utf-8"; case ".cc": return "application/octet-stream"; default: return "text/plain; charset=utf-8"; } } function localUrls(port, host) { const urls = []; if (host === "127.0.0.1" || host === "localhost") { urls.push(`http://127.0.0.1:${port}/`); return urls; } urls.push(`http://127.0.0.1:${port}/`); for (const entries of Object.values(networkInterfaces())) { for (const entry of entries ?? []) { if (entry.family === "IPv4" && !entry.internal) { urls.push(`http://${entry.address}:${port}/`); } } } return [...new Set(urls)]; } async function readJsonFile(path) { try { const raw = await readFile(path, "utf8"); return { ok: true, value: JSON.parse(stripBom(raw)) }; } catch (error) { return { ok: false, value: null, error: error instanceof Error ? error.message : String(error), }; } } async function getMtime(path) { try { return (await stat(path)).mtime.toISOString(); } catch { return null; } } async function readDotEnv(path) { const values = {}; try { const raw = await readFile(path, "utf8"); for (const line of raw.split(/\r?\n/u)) { const trimmed = line.trim(); if (!trimmed || trimmed.startsWith("#")) continue; const separator = line.indexOf("="); if (separator < 1) continue; values[line.slice(0, separator).trim()] = line.slice(separator + 1); } } catch {} return values; } function resolveConfigPath(value, fallback) { const raw = typeof value === "string" && value.trim() ? value.trim() : fallback; return resolve(repoRoot, raw); } function parseArgs(argv) { const parsed = {}; for (let index = 0; index < argv.length; index += 1) { const arg = argv[index]; if (arg === "--serve") parsed.serve = true; else if (arg === "--open") parsed.open = true; else if (arg.startsWith("--")) { const key = arg.slice(2); const next = argv[index + 1]; if (next && !next.startsWith("--")) { parsed[key] = next; index += 1; } else { parsed[key] = "true"; } } } return parsed; } function stripBom(value) { return value.charCodeAt(0) === 0xfeff ? value.slice(1) : value; } function numberOrNull(value) { return typeof value === "number" && Number.isFinite(value) ? value : null; } function stringOrNull(value) { return typeof value === "string" && value.length > 0 ? value : null; } function round(value, places) { const factor = 10 ** places; return Math.round(value * factor) / factor; } function truncate(value, maxLength) { return value.length > maxLength ? `${value.slice(0, maxLength - 3)}...` : value; } function escapeScriptJson(value) { return value.replace(//g, "\\u003e").replace(/&/g, "\\u0026"); }