Fix Odin health actuation and share discovery pass
This commit is contained in:
parent
505c55311f
commit
e1eb4b45a7
7 changed files with 415 additions and 251 deletions
|
|
@ -1768,6 +1768,12 @@ fn daemon_surgery_plan(target: &DaemonTarget, updated_at: &str) -> IdunnDaemonSu
|
|||
fn swarm_targets(options: &SwarmOptions) -> Result<Vec<DaemonTarget>> {
|
||||
let repo_root = options.repo_root.display().to_string();
|
||||
let script = |name: &str| format!(r"{}\scripts\{}", repo_root, name);
|
||||
let powershell_script = |name: &str| {
|
||||
format!(
|
||||
r#"C:\WINDOWS\System32\WindowsPowerShell\v1.0\powershell.exe -NoProfile -ExecutionPolicy Bypass -File "{}\scripts\{}""#,
|
||||
repo_root, name
|
||||
)
|
||||
};
|
||||
let project = |name: &str| PathBuf::from(format!(r"E:\Projects\{name}"));
|
||||
|
||||
match options.profile.as_str() {
|
||||
|
|
@ -1777,9 +1783,9 @@ fn swarm_targets(options: &SwarmOptions) -> Result<Vec<DaemonTarget>> {
|
|||
verse_id: "starfire.local".to_string(),
|
||||
name: "Odin all-seer".to_string(),
|
||||
health_contract: health_contract("odin.cultnet-rudp-provider-health", "failed"),
|
||||
health_command: Some(script("health-odin.cmd")),
|
||||
health_command: Some(powershell_script("health-odin.ps1")),
|
||||
deploy_command: None,
|
||||
restart_command: Some(script("restart-odin.cmd")),
|
||||
restart_command: Some(powershell_script("restart-odin.ps1")),
|
||||
release: None,
|
||||
enabled: true,
|
||||
interval_seconds: 30,
|
||||
|
|
@ -2701,9 +2707,15 @@ mod tests {
|
|||
verse_id: "starfire.local".to_string(),
|
||||
name: "Odin".to_string(),
|
||||
health_contract: health_contract("odin.cultnet-rudp-provider-health", "failed"),
|
||||
health_command: Some("health-odin.cmd".to_string()),
|
||||
health_command: Some(
|
||||
r#"C:\WINDOWS\System32\WindowsPowerShell\v1.0\powershell.exe -NoProfile -ExecutionPolicy Bypass -File "E:\Projects\Odin\scripts\health-odin.ps1""#
|
||||
.to_string(),
|
||||
),
|
||||
deploy_command: None,
|
||||
restart_command: Some("restart-odin.cmd".to_string()),
|
||||
restart_command: Some(
|
||||
r#"C:\WINDOWS\System32\WindowsPowerShell\v1.0\powershell.exe -NoProfile -ExecutionPolicy Bypass -File "E:\Projects\Odin\scripts\restart-odin.ps1""#
|
||||
.to_string(),
|
||||
),
|
||||
release: None,
|
||||
enabled: true,
|
||||
interval_seconds: 30,
|
||||
|
|
|
|||
|
|
@ -1,2 +1,2 @@
|
|||
@echo off
|
||||
curl.exe -fsS http://127.0.0.1:8797/health >NUL
|
||||
"%SystemRoot%\System32\WindowsPowerShell\v1.0\powershell.exe" -NoProfile -ExecutionPolicy Bypass -File E:\Projects\Odin\scripts\health-odin.ps1
|
||||
|
|
|
|||
80
scripts/health-odin.ps1
Normal file
80
scripts/health-odin.ps1
Normal file
|
|
@ -0,0 +1,80 @@
|
|||
param(
|
||||
[int] $Port = 8797,
|
||||
[string] $StateDir = "E:\Projects\Odin\scratch\odin",
|
||||
[int] $MaxSilenceSeconds = 120,
|
||||
[string] $CultNetRudpBind = "0.0.0.0:17871"
|
||||
)
|
||||
|
||||
$ErrorActionPreference = "Stop"
|
||||
|
||||
$pidPath = Join-Path $StateDir "odin.pid"
|
||||
$outLog = Join-Path $StateDir "odin.out.log"
|
||||
$errLog = Join-Path $StateDir "odin.err.log"
|
||||
|
||||
if (-not (Test-Path -LiteralPath $pidPath)) {
|
||||
throw "Odin PID file is missing at $pidPath"
|
||||
}
|
||||
|
||||
$pidText = (Get-Content -LiteralPath $pidPath -Raw).Trim()
|
||||
if ($pidText -notmatch '^\d+$') {
|
||||
throw "Odin PID file is invalid at ${pidPath}: '$pidText'"
|
||||
}
|
||||
|
||||
$process = Get-CimInstance Win32_Process -Filter "ProcessId = $pidText" -ErrorAction SilentlyContinue
|
||||
if ($null -eq $process) {
|
||||
throw "Odin process $pidText is not running"
|
||||
}
|
||||
|
||||
foreach ($pattern in @(
|
||||
'node.exe',
|
||||
'src\odin-coordinator.cjs',
|
||||
"--port $Port",
|
||||
"--stateDir $StateDir",
|
||||
"--cultnet-rudp-bind $CultNetRudpBind",
|
||||
'--idunn-daemon odin',
|
||||
'--idunn-health-contract odin.cultnet-rudp-provider-health'
|
||||
)) {
|
||||
if ($process.CommandLine -notlike "*$pattern*") {
|
||||
throw "Odin process command line is missing expected segment ${pattern}: $($process.CommandLine)"
|
||||
}
|
||||
}
|
||||
|
||||
foreach ($logPath in @($outLog, $errLog)) {
|
||||
if (-not (Test-Path -LiteralPath $logPath)) {
|
||||
throw "Odin log is missing at $logPath"
|
||||
}
|
||||
}
|
||||
|
||||
$newestLogWriteUtc = @($outLog, $errLog) |
|
||||
ForEach-Object { (Get-Item -LiteralPath $_).LastWriteTimeUtc } |
|
||||
Sort-Object -Descending |
|
||||
Select-Object -First 1
|
||||
$logAgeSeconds = ([DateTime]::UtcNow - $newestLogWriteUtc).TotalSeconds
|
||||
|
||||
$healthUri = "http://127.0.0.1:$Port/health"
|
||||
try {
|
||||
$health = Invoke-RestMethod -Uri $healthUri -TimeoutSec 10
|
||||
} catch {
|
||||
throw "Odin health endpoint is unavailable at ${healthUri}: $($_.Exception.Message)"
|
||||
}
|
||||
|
||||
if (-not $health.ok) {
|
||||
throw "Odin health endpoint reports ok=false"
|
||||
}
|
||||
|
||||
$tcpListener = Get-NetTCPConnection -State Listen -LocalPort $Port -ErrorAction SilentlyContinue |
|
||||
Where-Object { $_.OwningProcess -eq [int]$pidText } |
|
||||
Select-Object -First 1
|
||||
if ($null -eq $tcpListener) {
|
||||
throw "Odin is not listening on TCP port $Port as process $pidText"
|
||||
}
|
||||
|
||||
$rudpPort = [int](($CultNetRudpBind -split ':')[-1])
|
||||
$udpListener = Get-NetUDPEndpoint -LocalPort $rudpPort -ErrorAction SilentlyContinue |
|
||||
Where-Object { $_.OwningProcess -eq [int]$pidText } |
|
||||
Select-Object -First 1
|
||||
if ($null -eq $udpListener) {
|
||||
throw "Odin is not listening on UDP port $rudpPort as process $pidText"
|
||||
}
|
||||
|
||||
Write-Host "Odin healthy: pid=$pidText tcp=$Port udp=$rudpPort log_age=$([math]::Round($logAgeSeconds))s"
|
||||
|
|
@ -1,2 +1,2 @@
|
|||
@echo off
|
||||
powershell.exe -NoProfile -ExecutionPolicy Bypass -File E:\Projects\Odin\scripts\start-odin.ps1
|
||||
"%SystemRoot%\System32\WindowsPowerShell\v1.0\powershell.exe" -NoProfile -ExecutionPolicy Bypass -File E:\Projects\Odin\scripts\restart-odin.ps1
|
||||
|
|
|
|||
35
scripts/restart-odin.ps1
Normal file
35
scripts/restart-odin.ps1
Normal file
|
|
@ -0,0 +1,35 @@
|
|||
param(
|
||||
[int] $Port = 8797,
|
||||
[string] $StateDir = "E:\Projects\Odin\scratch\odin",
|
||||
[string] $CultNetRudpBind = "0.0.0.0:17871",
|
||||
[string] $IdunnRudpHealth = "127.0.0.1:17870",
|
||||
[string] $IdunnDaemon = "odin",
|
||||
[string] $IdunnHealthContract = "odin.cultnet-rudp-provider-health"
|
||||
)
|
||||
|
||||
$ErrorActionPreference = "Stop"
|
||||
|
||||
$repoRoot = Split-Path -Parent $PSScriptRoot
|
||||
$stopScript = Join-Path $PSScriptRoot "stop-odin.ps1"
|
||||
$startScript = Join-Path $PSScriptRoot "start-odin.ps1"
|
||||
$healthScript = Join-Path $PSScriptRoot "health-odin.ps1"
|
||||
$powerShellExe = Join-Path $env:WINDIR "System32\WindowsPowerShell\v1.0\powershell.exe"
|
||||
|
||||
if (-not (Test-Path -LiteralPath $powerShellExe)) {
|
||||
throw "PowerShell executable is missing at $powerShellExe"
|
||||
}
|
||||
|
||||
& $powerShellExe -NoProfile -ExecutionPolicy Bypass -File $stopScript -StateDir $StateDir
|
||||
|
||||
& $powerShellExe -NoProfile -ExecutionPolicy Bypass -File $startScript `
|
||||
-Port $Port `
|
||||
-StateDir $StateDir `
|
||||
-CultNetRudpBind $CultNetRudpBind `
|
||||
-IdunnRudpHealth $IdunnRudpHealth `
|
||||
-IdunnDaemon $IdunnDaemon `
|
||||
-IdunnHealthContract $IdunnHealthContract
|
||||
|
||||
& $powerShellExe -NoProfile -ExecutionPolicy Bypass -File $healthScript `
|
||||
-Port $Port `
|
||||
-StateDir $StateDir `
|
||||
-CultNetRudpBind $CultNetRudpBind
|
||||
|
|
@ -24,9 +24,102 @@ function createInterfaceDiscovery({
|
|||
let lastLanScanAt = 0;
|
||||
|
||||
async function discoverInterfaces() {
|
||||
const { interfaces } = await discoverAll();
|
||||
return interfaces;
|
||||
}
|
||||
|
||||
async function discoverProviderAdvertisements() {
|
||||
const { providerAdvertisements } = await discoverAll();
|
||||
return providerAdvertisements;
|
||||
}
|
||||
|
||||
async function discoverAll() {
|
||||
await refreshLanDeckDiscovery();
|
||||
const manifestsByProvider = await discoverDeckProviderManifests();
|
||||
const eveInterfaces = await Promise.all(
|
||||
[...manifestsByProvider.values()].map(({ provider, deckUrl }) => fetchEveProvider(deckUrl, provider.id, provider)),
|
||||
);
|
||||
const { interfaces: cultMeshInterfaces, providerAdvertisements } = await discoverCultMeshEntries();
|
||||
|
||||
const interfaces = [...eveInterfaces];
|
||||
for (const entry of cultMeshInterfaces) {
|
||||
const existingIndex = interfaces.findIndex((candidate) => candidate.providerId === entry.providerId);
|
||||
if (existingIndex >= 0) {
|
||||
interfaces[existingIndex] = entry;
|
||||
} else {
|
||||
interfaces.push(entry);
|
||||
}
|
||||
}
|
||||
interfaces.sort((left, right) => left.providerId.localeCompare(right.providerId));
|
||||
providerAdvertisements.sort((left, right) => String(left.id).localeCompare(String(right.id)));
|
||||
return { interfaces, providerAdvertisements };
|
||||
}
|
||||
|
||||
function cultMeshDocumentsAvailable() {
|
||||
const {
|
||||
interfaceBindingDefinition,
|
||||
idunnDaemonHealthDefinition,
|
||||
muninnCaptureStreamDefinition,
|
||||
muninnCommandBoundaryDefinition,
|
||||
muninnMoveControllerStateDefinition,
|
||||
muninnMoveIdentityDefinition,
|
||||
muninnMoveLightCommandDefinition,
|
||||
muninnMoveMarkerCandidateDefinition,
|
||||
muninnObsStreamCatalogDefinition,
|
||||
muninnQuestAccessDefinition,
|
||||
muninnTelemetrySurfaceDefinition,
|
||||
muninnTransportProfileDefinition,
|
||||
operatorStateDefinition,
|
||||
providerAdvertisementDefinition,
|
||||
stonksCommandBoundaryDefinition,
|
||||
stonksMarketSnapshotDefinition,
|
||||
stonksRequestEventDefinition,
|
||||
stonksTransportProfileDefinition,
|
||||
streamPixelsCommandBoundaryDefinition,
|
||||
streamPixelsTransportProfileDefinition,
|
||||
surfaceDefinition,
|
||||
viliCommandBoundaryDefinition,
|
||||
viliTransportProfileDefinition,
|
||||
weksaCommandBoundaryDefinition,
|
||||
weksaOperatorStateDefinition,
|
||||
weksaTransportProfileDefinition,
|
||||
voidbotSwarmSnapshotDefinition,
|
||||
} = documents;
|
||||
return Boolean(
|
||||
CultMesh &&
|
||||
interfaceBindingDefinition &&
|
||||
idunnDaemonHealthDefinition &&
|
||||
muninnCaptureStreamDefinition &&
|
||||
muninnCommandBoundaryDefinition &&
|
||||
muninnMoveControllerStateDefinition &&
|
||||
muninnMoveIdentityDefinition &&
|
||||
muninnMoveLightCommandDefinition &&
|
||||
muninnMoveMarkerCandidateDefinition &&
|
||||
muninnObsStreamCatalogDefinition &&
|
||||
muninnQuestAccessDefinition &&
|
||||
muninnTelemetrySurfaceDefinition &&
|
||||
muninnTransportProfileDefinition &&
|
||||
operatorStateDefinition &&
|
||||
providerAdvertisementDefinition &&
|
||||
stonksCommandBoundaryDefinition &&
|
||||
stonksMarketSnapshotDefinition &&
|
||||
stonksRequestEventDefinition &&
|
||||
stonksTransportProfileDefinition &&
|
||||
streamPixelsCommandBoundaryDefinition &&
|
||||
streamPixelsTransportProfileDefinition &&
|
||||
surfaceDefinition &&
|
||||
viliCommandBoundaryDefinition &&
|
||||
viliTransportProfileDefinition &&
|
||||
weksaCommandBoundaryDefinition &&
|
||||
weksaOperatorStateDefinition &&
|
||||
weksaTransportProfileDefinition &&
|
||||
voidbotSwarmSnapshotDefinition
|
||||
);
|
||||
}
|
||||
|
||||
async function discoverDeckProviderManifests() {
|
||||
const manifestsByProvider = new Map();
|
||||
for (const deckUrl of discoveredDeckUrls) {
|
||||
await Promise.all(discoveredDeckUrls.map(async (deckUrl) => {
|
||||
const manifestUrl = deckUrl.replace(/^ws:/, "http:").replace(/\/eve\/deck.*$/, "/eve/deck/providers");
|
||||
try {
|
||||
const catalog = JSON.parse(await httpGet(manifestUrl, 2500));
|
||||
|
|
@ -40,25 +133,11 @@ function createInterfaceDiscovery({
|
|||
} catch {
|
||||
// Discovery is opportunistic. Failed endpoints fall out of the next pass.
|
||||
}
|
||||
}
|
||||
|
||||
const interfaces = [];
|
||||
for (const { provider, deckUrl } of manifestsByProvider.values()) {
|
||||
interfaces.push(await fetchEveProvider(deckUrl, provider.id, provider));
|
||||
}
|
||||
for (const entry of await discoverCultMeshInterfaceBindings()) {
|
||||
const existingIndex = interfaces.findIndex((candidate) => candidate.providerId === entry.providerId);
|
||||
if (existingIndex >= 0) {
|
||||
interfaces[existingIndex] = entry;
|
||||
} else {
|
||||
interfaces.push(entry);
|
||||
}
|
||||
}
|
||||
interfaces.sort((left, right) => left.providerId.localeCompare(right.providerId));
|
||||
return interfaces;
|
||||
}));
|
||||
return manifestsByProvider;
|
||||
}
|
||||
|
||||
async function discoverProviderAdvertisements() {
|
||||
async function discoverCultMeshEntries() {
|
||||
const {
|
||||
interfaceBindingDefinition,
|
||||
idunnDaemonHealthDefinition,
|
||||
|
|
@ -88,280 +167,87 @@ function createInterfaceDiscovery({
|
|||
weksaTransportProfileDefinition,
|
||||
voidbotSwarmSnapshotDefinition,
|
||||
} = documents;
|
||||
if (!CultMesh || !interfaceBindingDefinition || !idunnDaemonHealthDefinition || !muninnCaptureStreamDefinition || !muninnCommandBoundaryDefinition || !muninnMoveControllerStateDefinition || !muninnMoveIdentityDefinition || !muninnMoveLightCommandDefinition || !muninnMoveMarkerCandidateDefinition || !muninnObsStreamCatalogDefinition || !muninnQuestAccessDefinition || !muninnTelemetrySurfaceDefinition || !muninnTransportProfileDefinition || !operatorStateDefinition || !stonksCommandBoundaryDefinition || !stonksMarketSnapshotDefinition || !stonksRequestEventDefinition || !stonksTransportProfileDefinition || !streamPixelsCommandBoundaryDefinition || !streamPixelsTransportProfileDefinition || !surfaceDefinition || !viliCommandBoundaryDefinition || !viliTransportProfileDefinition || !weksaCommandBoundaryDefinition || !weksaOperatorStateDefinition || !weksaTransportProfileDefinition || !voidbotSwarmSnapshotDefinition || !providerAdvertisementDefinition) {
|
||||
return [];
|
||||
if (!cultMeshDocumentsAvailable()) {
|
||||
return { interfaces: [], providerAdvertisements: [] };
|
||||
}
|
||||
|
||||
const providers = [];
|
||||
for (const storeSpec of interfaceBindingStores) {
|
||||
const nodeDocuments = [
|
||||
voidbotSwarmSnapshotDefinition,
|
||||
stonksRequestEventDefinition,
|
||||
stonksMarketSnapshotDefinition,
|
||||
providerAdvertisementDefinition,
|
||||
interfaceBindingDefinition,
|
||||
surfaceDefinition,
|
||||
stonksCommandBoundaryDefinition,
|
||||
stonksTransportProfileDefinition,
|
||||
streamPixelsCommandBoundaryDefinition,
|
||||
streamPixelsTransportProfileDefinition,
|
||||
idunnDaemonHealthDefinition,
|
||||
muninnCaptureStreamDefinition,
|
||||
muninnCommandBoundaryDefinition,
|
||||
muninnMoveControllerStateDefinition,
|
||||
muninnMoveIdentityDefinition,
|
||||
muninnMoveLightCommandDefinition,
|
||||
muninnMoveMarkerCandidateDefinition,
|
||||
muninnObsStreamCatalogDefinition,
|
||||
muninnQuestAccessDefinition,
|
||||
muninnTelemetrySurfaceDefinition,
|
||||
muninnTransportProfileDefinition,
|
||||
operatorStateDefinition,
|
||||
viliCommandBoundaryDefinition,
|
||||
viliTransportProfileDefinition,
|
||||
weksaOperatorStateDefinition,
|
||||
weksaCommandBoundaryDefinition,
|
||||
weksaTransportProfileDefinition,
|
||||
];
|
||||
|
||||
const storeEntries = await Promise.all(interfaceBindingStores.map(async (storeSpec) => {
|
||||
let resolvedStore = null;
|
||||
try {
|
||||
resolvedStore = resolveInterfaceBindingStore(storeSpec);
|
||||
if (!resolvedStore) {
|
||||
continue;
|
||||
return { interfaces: [], providerAdvertisements: [] };
|
||||
}
|
||||
const { localPath: storePath, sourceId } = resolvedStore;
|
||||
if (!fs.existsSync(storePath)) {
|
||||
continue;
|
||||
return { interfaces: [], providerAdvertisements: [] };
|
||||
}
|
||||
const node = await CultMesh.createNode(storePath, {
|
||||
documents: [
|
||||
voidbotSwarmSnapshotDefinition,
|
||||
stonksRequestEventDefinition,
|
||||
stonksMarketSnapshotDefinition,
|
||||
providerAdvertisementDefinition,
|
||||
interfaceBindingDefinition,
|
||||
surfaceDefinition,
|
||||
stonksCommandBoundaryDefinition,
|
||||
stonksTransportProfileDefinition,
|
||||
streamPixelsCommandBoundaryDefinition,
|
||||
streamPixelsTransportProfileDefinition,
|
||||
idunnDaemonHealthDefinition,
|
||||
muninnCaptureStreamDefinition,
|
||||
muninnCommandBoundaryDefinition,
|
||||
muninnMoveControllerStateDefinition,
|
||||
muninnMoveIdentityDefinition,
|
||||
muninnMoveLightCommandDefinition,
|
||||
muninnMoveMarkerCandidateDefinition,
|
||||
muninnObsStreamCatalogDefinition,
|
||||
muninnQuestAccessDefinition,
|
||||
muninnTelemetrySurfaceDefinition,
|
||||
muninnTransportProfileDefinition,
|
||||
operatorStateDefinition,
|
||||
viliCommandBoundaryDefinition,
|
||||
viliTransportProfileDefinition,
|
||||
weksaOperatorStateDefinition,
|
||||
weksaCommandBoundaryDefinition,
|
||||
weksaTransportProfileDefinition,
|
||||
],
|
||||
const node = await CultMesh.createNode(storePath, { documents: nodeDocuments });
|
||||
return inspectCultMeshStore({
|
||||
documents,
|
||||
interfaceBindingDefinition,
|
||||
muninnTelemetrySurfaceDefinition,
|
||||
node,
|
||||
providerAdvertisementDefinition,
|
||||
sourceId,
|
||||
storePath,
|
||||
});
|
||||
const advertisements = typeof node.cache?.getAll === "function"
|
||||
? node.cache.getAll(providerAdvertisementDefinition).map(unwrapDocumentRecord)
|
||||
: [];
|
||||
for (const advertisement of advertisements) {
|
||||
const providerId = advertisement?.providerId || advertisement?.provider?.id || advertisement?.id;
|
||||
if (!providerId) {
|
||||
continue;
|
||||
}
|
||||
providers.push({
|
||||
id: providerId,
|
||||
title: advertisement.title || advertisement.provider?.title || providerId,
|
||||
description: advertisement.description || "Provider-owned CultMesh advertisement.",
|
||||
version: String(advertisement.version || 0),
|
||||
endpoint: advertisement.cultMeshAddress || advertisement.endpoints?.[0]?.address || advertisement.provider?.endpoint || `cultmesh:${sourceId}`,
|
||||
canonicalService: advertisement.canonicalService || null,
|
||||
locatedService: advertisement.locatedService || null,
|
||||
cultMeshAddress: advertisement.cultMeshAddress || null,
|
||||
endpoints: advertisement.endpoints || [],
|
||||
routes: advertisement.routes || [],
|
||||
transportEndpoint: advertisement.routes?.find((route) => route.transport === "cultnet")?.address
|
||||
|| advertisement.routes?.find((route) => route.transport === "compatibility-eve-deck")?.address
|
||||
|| advertisement.provider?.endpoint
|
||||
|| `cultmesh:${sourceId}`,
|
||||
capabilities: advertisement.provider?.capabilities || advertisement.capabilities || [],
|
||||
operatorState: providerRecord(node, [
|
||||
operatorStateDefinition,
|
||||
weksaOperatorStateDefinition,
|
||||
], advertisement) || null,
|
||||
commandBoundary: providerRecord(node, [
|
||||
muninnCommandBoundaryDefinition,
|
||||
viliCommandBoundaryDefinition,
|
||||
weksaCommandBoundaryDefinition,
|
||||
stonksCommandBoundaryDefinition,
|
||||
streamPixelsCommandBoundaryDefinition,
|
||||
], advertisement) || null,
|
||||
transportProfile: providerRecord(node, [
|
||||
muninnTransportProfileDefinition,
|
||||
viliTransportProfileDefinition,
|
||||
weksaTransportProfileDefinition,
|
||||
stonksTransportProfileDefinition,
|
||||
streamPixelsTransportProfileDefinition,
|
||||
], advertisement) || null,
|
||||
usesCultMesh: true,
|
||||
transport: advertisement.provider?.transport || "CultMesh provider advertisement",
|
||||
status: advertisement.status || "unknown",
|
||||
updatedAt: advertisement.updatedAt || new Date().toISOString(),
|
||||
source: `cultmesh:${sourceId}`,
|
||||
commandSurface: advertisement.commandSurface || null,
|
||||
});
|
||||
}
|
||||
} catch {
|
||||
// Provider advertisements are optional discovery hints; broken stores
|
||||
// are still surfaced through interface discovery when possible.
|
||||
} catch (error) {
|
||||
const interfaces = [];
|
||||
const providerAdvertisements = [];
|
||||
if (resolvedStore?.localPath && fs.existsSync(resolvedStore.localPath)) {
|
||||
providers.push(...inspectProviderAdvertisementsFromStore(
|
||||
interfaces.push(...inspectCultMeshInterfacesFromStore(
|
||||
resolvedStore.localPath,
|
||||
resolvedStore.sourceId,
|
||||
));
|
||||
providerAdvertisements.push(...inspectProviderAdvertisementsFromStore(
|
||||
resolvedStore.localPath,
|
||||
resolvedStore.sourceId,
|
||||
));
|
||||
}
|
||||
if (interfaces.length === 0) {
|
||||
interfaces.push(dashboardUnavailable(`cultmesh:${storeSpec}`, `cultmesh:${storeSpec}`, error.message));
|
||||
}
|
||||
return { interfaces, providerAdvertisements };
|
||||
} finally {
|
||||
resolvedStore?.cleanup?.();
|
||||
}
|
||||
}
|
||||
}));
|
||||
|
||||
providers.sort((left, right) => String(left.id).localeCompare(String(right.id)));
|
||||
return providers;
|
||||
}
|
||||
|
||||
async function discoverCultMeshInterfaceBindings() {
|
||||
const {
|
||||
interfaceBindingDefinition,
|
||||
idunnDaemonHealthDefinition,
|
||||
muninnCaptureStreamDefinition,
|
||||
muninnCommandBoundaryDefinition,
|
||||
muninnMoveControllerStateDefinition,
|
||||
muninnMoveIdentityDefinition,
|
||||
muninnMoveLightCommandDefinition,
|
||||
muninnMoveMarkerCandidateDefinition,
|
||||
muninnObsStreamCatalogDefinition,
|
||||
muninnQuestAccessDefinition,
|
||||
muninnTelemetrySurfaceDefinition,
|
||||
muninnTransportProfileDefinition,
|
||||
operatorStateDefinition,
|
||||
providerAdvertisementDefinition,
|
||||
stonksCommandBoundaryDefinition,
|
||||
stonksMarketSnapshotDefinition,
|
||||
stonksRequestEventDefinition,
|
||||
stonksTransportProfileDefinition,
|
||||
streamPixelsCommandBoundaryDefinition,
|
||||
streamPixelsTransportProfileDefinition,
|
||||
surfaceDefinition,
|
||||
viliCommandBoundaryDefinition,
|
||||
viliTransportProfileDefinition,
|
||||
weksaCommandBoundaryDefinition,
|
||||
weksaOperatorStateDefinition,
|
||||
weksaTransportProfileDefinition,
|
||||
voidbotSwarmSnapshotDefinition,
|
||||
} = documents;
|
||||
if (!CultMesh || !interfaceBindingDefinition || !idunnDaemonHealthDefinition || !muninnCaptureStreamDefinition || !muninnCommandBoundaryDefinition || !muninnMoveControllerStateDefinition || !muninnMoveIdentityDefinition || !muninnMoveLightCommandDefinition || !muninnMoveMarkerCandidateDefinition || !muninnObsStreamCatalogDefinition || !muninnQuestAccessDefinition || !muninnTelemetrySurfaceDefinition || !muninnTransportProfileDefinition || !operatorStateDefinition || !stonksCommandBoundaryDefinition || !stonksMarketSnapshotDefinition || !stonksRequestEventDefinition || !stonksTransportProfileDefinition || !streamPixelsCommandBoundaryDefinition || !streamPixelsTransportProfileDefinition || !surfaceDefinition || !viliCommandBoundaryDefinition || !viliTransportProfileDefinition || !weksaCommandBoundaryDefinition || !weksaOperatorStateDefinition || !weksaTransportProfileDefinition || !voidbotSwarmSnapshotDefinition || !providerAdvertisementDefinition) {
|
||||
return [];
|
||||
}
|
||||
const interfaces = [];
|
||||
for (const storeSpec of interfaceBindingStores) {
|
||||
let resolvedStore = null;
|
||||
try {
|
||||
resolvedStore = resolveInterfaceBindingStore(storeSpec);
|
||||
if (!resolvedStore) {
|
||||
continue;
|
||||
}
|
||||
const { localPath: storePath, sourceId } = resolvedStore;
|
||||
if (!fs.existsSync(storePath)) {
|
||||
continue;
|
||||
}
|
||||
const node = await CultMesh.createNode(storePath, {
|
||||
documents: [
|
||||
voidbotSwarmSnapshotDefinition,
|
||||
stonksRequestEventDefinition,
|
||||
stonksMarketSnapshotDefinition,
|
||||
providerAdvertisementDefinition,
|
||||
interfaceBindingDefinition,
|
||||
surfaceDefinition,
|
||||
stonksCommandBoundaryDefinition,
|
||||
stonksTransportProfileDefinition,
|
||||
streamPixelsCommandBoundaryDefinition,
|
||||
streamPixelsTransportProfileDefinition,
|
||||
idunnDaemonHealthDefinition,
|
||||
muninnCaptureStreamDefinition,
|
||||
muninnCommandBoundaryDefinition,
|
||||
muninnMoveControllerStateDefinition,
|
||||
muninnMoveIdentityDefinition,
|
||||
muninnMoveLightCommandDefinition,
|
||||
muninnMoveMarkerCandidateDefinition,
|
||||
muninnObsStreamCatalogDefinition,
|
||||
muninnQuestAccessDefinition,
|
||||
muninnTelemetrySurfaceDefinition,
|
||||
muninnTransportProfileDefinition,
|
||||
operatorStateDefinition,
|
||||
viliCommandBoundaryDefinition,
|
||||
viliTransportProfileDefinition,
|
||||
weksaOperatorStateDefinition,
|
||||
weksaCommandBoundaryDefinition,
|
||||
weksaTransportProfileDefinition,
|
||||
],
|
||||
});
|
||||
const advertisements = typeof node.cache?.getAll === "function"
|
||||
? node.cache.getAll(providerAdvertisementDefinition).map(unwrapDocumentRecord)
|
||||
: [];
|
||||
for (const advertisement of advertisements) {
|
||||
if (!advertisement?.providerId) {
|
||||
continue;
|
||||
}
|
||||
const state = providerSurfaceState(
|
||||
node,
|
||||
advertisement,
|
||||
surfaceDefinition,
|
||||
muninnTelemetrySurfaceDefinition,
|
||||
);
|
||||
if (!state?.surface?.root) {
|
||||
continue;
|
||||
}
|
||||
interfaces.push(cultMeshProviderInterface({
|
||||
advertisement,
|
||||
state,
|
||||
storePath: sourceId,
|
||||
operatorState: providerRecord(node, [
|
||||
operatorStateDefinition,
|
||||
weksaOperatorStateDefinition,
|
||||
], advertisement) || null,
|
||||
commandBoundary: providerRecord(node, [
|
||||
muninnCommandBoundaryDefinition,
|
||||
viliCommandBoundaryDefinition,
|
||||
weksaCommandBoundaryDefinition,
|
||||
stonksCommandBoundaryDefinition,
|
||||
streamPixelsCommandBoundaryDefinition,
|
||||
], advertisement) || null,
|
||||
transportProfile: providerRecord(node, [
|
||||
muninnTransportProfileDefinition,
|
||||
viliTransportProfileDefinition,
|
||||
weksaTransportProfileDefinition,
|
||||
stonksTransportProfileDefinition,
|
||||
streamPixelsTransportProfileDefinition,
|
||||
], advertisement) || null,
|
||||
}));
|
||||
}
|
||||
const bindings = typeof node.cache?.getAll === "function"
|
||||
? node.cache.getAll(interfaceBindingDefinition).map(unwrapDocumentRecord)
|
||||
: [unwrapDocumentRecord(node.get(interfaceBindingDefinition, "voidbot.swarm"))].filter(Boolean);
|
||||
for (const binding of bindings) {
|
||||
if (!binding?.providerId) {
|
||||
continue;
|
||||
}
|
||||
const state = unwrapDocumentRecord(node.get(surfaceDefinition, binding.providerId));
|
||||
const surface = state?.surface || binding.surface || null;
|
||||
interfaces.push({
|
||||
providerId: binding.providerId,
|
||||
title: binding.title || state?.title || binding.providerId,
|
||||
state: "active",
|
||||
detail: `${surface?.root?.kind || binding.kind || "surface"} ${countSurfaceNodes(surface, state)} nodes via CultMesh`,
|
||||
version: state?.version || 0,
|
||||
updatedAt: state?.updatedAt || binding.updatedAt || new Date().toISOString(),
|
||||
source: `cultmesh:${sourceId}`,
|
||||
manifest: binding.provider || null,
|
||||
canonicalService: binding.provider?.canonicalService || null,
|
||||
locatedService: binding.provider?.locatedService || null,
|
||||
cultMeshAddress: binding.provider?.cultMeshAddress || binding.provider?.endpoint || null,
|
||||
endpoints: binding.provider?.endpoints || [],
|
||||
routes: binding.provider?.routes || [],
|
||||
surface,
|
||||
});
|
||||
}
|
||||
} catch (error) {
|
||||
if (resolvedStore?.localPath && fs.existsSync(resolvedStore.localPath)) {
|
||||
const fallbackInterfaces = inspectCultMeshInterfacesFromStore(
|
||||
resolvedStore.localPath,
|
||||
resolvedStore.sourceId,
|
||||
);
|
||||
if (fallbackInterfaces.length > 0) {
|
||||
interfaces.push(...fallbackInterfaces);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
interfaces.push(dashboardUnavailable(`cultmesh:${storeSpec}`, `cultmesh:${storeSpec}`, error.message));
|
||||
} finally {
|
||||
resolvedStore?.cleanup?.();
|
||||
}
|
||||
}
|
||||
return interfaces;
|
||||
return {
|
||||
interfaces: storeEntries.flatMap((entry) => entry.interfaces),
|
||||
providerAdvertisements: storeEntries.flatMap((entry) => entry.providerAdvertisements),
|
||||
};
|
||||
}
|
||||
|
||||
async function refreshLanDeckDiscovery() {
|
||||
|
|
@ -383,6 +269,7 @@ function createInterfaceDiscovery({
|
|||
}
|
||||
|
||||
return {
|
||||
discoverAll,
|
||||
discoverProviderAdvertisements,
|
||||
discoverInterfaces,
|
||||
getDiscoveredDeckUrls: () => [...discoveredDeckUrls],
|
||||
|
|
@ -421,6 +308,146 @@ function cultMeshProviderInterface({
|
|||
};
|
||||
}
|
||||
|
||||
function inspectCultMeshStore({
|
||||
documents,
|
||||
interfaceBindingDefinition,
|
||||
muninnTelemetrySurfaceDefinition,
|
||||
node,
|
||||
providerAdvertisementDefinition,
|
||||
sourceId,
|
||||
}) {
|
||||
const {
|
||||
operatorStateDefinition,
|
||||
stonksCommandBoundaryDefinition,
|
||||
stonksTransportProfileDefinition,
|
||||
streamPixelsCommandBoundaryDefinition,
|
||||
streamPixelsTransportProfileDefinition,
|
||||
surfaceDefinition,
|
||||
viliCommandBoundaryDefinition,
|
||||
viliTransportProfileDefinition,
|
||||
weksaCommandBoundaryDefinition,
|
||||
weksaOperatorStateDefinition,
|
||||
weksaTransportProfileDefinition,
|
||||
muninnCommandBoundaryDefinition,
|
||||
muninnTransportProfileDefinition,
|
||||
} = documents;
|
||||
|
||||
const providerAdvertisements = [];
|
||||
const interfaces = [];
|
||||
const advertisements = typeof node.cache?.getAll === "function"
|
||||
? node.cache.getAll(providerAdvertisementDefinition).map(unwrapDocumentRecord)
|
||||
: [];
|
||||
|
||||
for (const advertisement of advertisements) {
|
||||
const providerId = advertisement?.providerId || advertisement?.provider?.id || advertisement?.id;
|
||||
if (!providerId) {
|
||||
continue;
|
||||
}
|
||||
providerAdvertisements.push({
|
||||
id: providerId,
|
||||
title: advertisement.title || advertisement.provider?.title || providerId,
|
||||
description: advertisement.description || "Provider-owned CultMesh advertisement.",
|
||||
version: String(advertisement.version || 0),
|
||||
endpoint: advertisement.cultMeshAddress || advertisement.endpoints?.[0]?.address || advertisement.provider?.endpoint || `cultmesh:${sourceId}`,
|
||||
canonicalService: advertisement.canonicalService || null,
|
||||
locatedService: advertisement.locatedService || null,
|
||||
cultMeshAddress: advertisement.cultMeshAddress || null,
|
||||
endpoints: advertisement.endpoints || [],
|
||||
routes: advertisement.routes || [],
|
||||
transportEndpoint: advertisement.routes?.find((route) => route.transport === "cultnet")?.address
|
||||
|| advertisement.routes?.find((route) => route.transport === "compatibility-eve-deck")?.address
|
||||
|| advertisement.provider?.endpoint
|
||||
|| `cultmesh:${sourceId}`,
|
||||
capabilities: advertisement.provider?.capabilities || advertisement.capabilities || [],
|
||||
operatorState: providerRecord(node, [
|
||||
operatorStateDefinition,
|
||||
weksaOperatorStateDefinition,
|
||||
], advertisement) || null,
|
||||
commandBoundary: providerRecord(node, [
|
||||
muninnCommandBoundaryDefinition,
|
||||
viliCommandBoundaryDefinition,
|
||||
weksaCommandBoundaryDefinition,
|
||||
stonksCommandBoundaryDefinition,
|
||||
streamPixelsCommandBoundaryDefinition,
|
||||
], advertisement) || null,
|
||||
transportProfile: providerRecord(node, [
|
||||
muninnTransportProfileDefinition,
|
||||
viliTransportProfileDefinition,
|
||||
weksaTransportProfileDefinition,
|
||||
stonksTransportProfileDefinition,
|
||||
streamPixelsTransportProfileDefinition,
|
||||
], advertisement) || null,
|
||||
usesCultMesh: true,
|
||||
transport: advertisement.provider?.transport || "CultMesh provider advertisement",
|
||||
status: advertisement.status || "unknown",
|
||||
updatedAt: advertisement.updatedAt || new Date().toISOString(),
|
||||
source: `cultmesh:${sourceId}`,
|
||||
commandSurface: advertisement.commandSurface || null,
|
||||
});
|
||||
|
||||
const state = providerSurfaceState(
|
||||
node,
|
||||
advertisement,
|
||||
surfaceDefinition,
|
||||
muninnTelemetrySurfaceDefinition,
|
||||
);
|
||||
if (state?.surface?.root) {
|
||||
interfaces.push(cultMeshProviderInterface({
|
||||
advertisement,
|
||||
state,
|
||||
storePath: sourceId,
|
||||
operatorState: providerRecord(node, [
|
||||
operatorStateDefinition,
|
||||
weksaOperatorStateDefinition,
|
||||
], advertisement) || null,
|
||||
commandBoundary: providerRecord(node, [
|
||||
muninnCommandBoundaryDefinition,
|
||||
viliCommandBoundaryDefinition,
|
||||
weksaCommandBoundaryDefinition,
|
||||
stonksCommandBoundaryDefinition,
|
||||
streamPixelsCommandBoundaryDefinition,
|
||||
], advertisement) || null,
|
||||
transportProfile: providerRecord(node, [
|
||||
muninnTransportProfileDefinition,
|
||||
viliTransportProfileDefinition,
|
||||
weksaTransportProfileDefinition,
|
||||
stonksTransportProfileDefinition,
|
||||
streamPixelsTransportProfileDefinition,
|
||||
], advertisement) || null,
|
||||
}));
|
||||
}
|
||||
}
|
||||
|
||||
const bindings = typeof node.cache?.getAll === "function"
|
||||
? node.cache.getAll(interfaceBindingDefinition).map(unwrapDocumentRecord)
|
||||
: [unwrapDocumentRecord(node.get(interfaceBindingDefinition, "voidbot.swarm"))].filter(Boolean);
|
||||
for (const binding of bindings) {
|
||||
if (!binding?.providerId) {
|
||||
continue;
|
||||
}
|
||||
const state = unwrapDocumentRecord(node.get(surfaceDefinition, binding.providerId));
|
||||
const surface = state?.surface || binding.surface || null;
|
||||
interfaces.push({
|
||||
providerId: binding.providerId,
|
||||
title: binding.title || state?.title || binding.providerId,
|
||||
state: "active",
|
||||
detail: `${surface?.root?.kind || binding.kind || "surface"} ${countSurfaceNodes(surface, state)} nodes via CultMesh`,
|
||||
version: state?.version || 0,
|
||||
updatedAt: state?.updatedAt || binding.updatedAt || new Date().toISOString(),
|
||||
source: `cultmesh:${sourceId}`,
|
||||
manifest: binding.provider || null,
|
||||
canonicalService: binding.provider?.canonicalService || null,
|
||||
locatedService: binding.provider?.locatedService || null,
|
||||
cultMeshAddress: binding.provider?.cultMeshAddress || binding.provider?.endpoint || null,
|
||||
endpoints: binding.provider?.endpoints || [],
|
||||
routes: binding.provider?.routes || [],
|
||||
surface,
|
||||
});
|
||||
}
|
||||
|
||||
return { interfaces, providerAdvertisements };
|
||||
}
|
||||
|
||||
function providerSurfaceState(node, advertisement, surfaceDefinition, muninnTelemetrySurfaceDefinition) {
|
||||
const surfaceKey = advertisement.surfaceId || advertisement.surface_id || advertisement.providerId;
|
||||
const surfaceState = unwrapDocumentRecord(node.get?.(surfaceDefinition, surfaceKey));
|
||||
|
|
|
|||
|
|
@ -39,17 +39,27 @@ function createStateBuilder({ cachePath, gamecultTextDocumentStorePath, interfac
|
|||
async function buildState() {
|
||||
version += 1;
|
||||
const observedAt = new Date().toISOString();
|
||||
const [docker, adb, hosts, yggdrasilServices, nightwingServices, nightwingGpu, discoveredInterfaces, providerAdvertisements, observations] = await Promise.all([
|
||||
const [
|
||||
docker,
|
||||
adb,
|
||||
hosts,
|
||||
yggdrasilServices,
|
||||
nightwingServices,
|
||||
nightwingGpu,
|
||||
discovery,
|
||||
observations,
|
||||
] = await Promise.all([
|
||||
dockerSnapshot(),
|
||||
adbSnapshot(),
|
||||
hostChecks(),
|
||||
remoteServices("ygg", ["nginx", "streampixels-web", "streampixels-service", "heimdall", "repixelizer-gui", "bifrost"]),
|
||||
remoteServices("nightwing", ["ssh", "nightwing-eve-dashboard", "nightwing-eve-browser-reference", "gjallar", "docker"]),
|
||||
remoteGpu("nightwing"),
|
||||
interfaceDiscovery.discoverInterfaces(),
|
||||
interfaceDiscovery.discoverProviderAdvertisements(),
|
||||
interfaceDiscovery.discoverAll(),
|
||||
observationSnapshot(observationLogPath, observationFreshSeconds),
|
||||
]);
|
||||
const discoveredInterfaces = discovery.interfaces;
|
||||
const providerAdvertisements = discovery.providerAdvertisements;
|
||||
const interfaceById = new Map(discoveredInterfaces.map((entry) => [entry.providerId, entry]));
|
||||
const interfaces = [...interfaceById.values()];
|
||||
const marqueeText = await buildMarqueeText({ interfaces, textDocumentStorePath: gamecultTextDocumentStorePath, stonksBurstSize, stonksStateUrl });
|
||||
|
|
|
|||
Loading…
Reference in a new issue