feat(electrobun): multi-machine relay + OTEL telemetry
Multi-machine relay: - relay-client.ts: WebSocket client for agor-relay with token auth, exponential backoff (1s-30s), TCP probe, heartbeat (15s ping) - machines-store.svelte.ts: remote machine state tracking - RemoteMachinesSettings.svelte: machine list, add/connect/disconnect UI - 7 RPC types (remote.connect/disconnect/list/send/status + events) Telemetry: - telemetry.ts: OTEL spans + OTLP/HTTP export to Tempo, controlled by AGOR_OTLP_ENDPOINT env var - telemetry-bridge.ts: tel.info/warn/error frontend convenience API - telemetry.log RPC for frontend→Bun tracing
This commit is contained in:
parent
ec30c69c3e
commit
88206205fe
11 changed files with 1458 additions and 15 deletions
|
|
@ -10,9 +10,15 @@ import { SidecarManager } from "./sidecar-manager.ts";
|
|||
import type { PtyRPCSchema } from "../shared/pty-rpc-schema.ts";
|
||||
import { randomUUID } from "crypto";
|
||||
import { SearchDb } from "./search-db.ts";
|
||||
import { checkForUpdates, getLastCheckTimestamp } from "./updater.ts";
|
||||
import { RelayClient } from "./relay-client.ts";
|
||||
import { initTelemetry, telemetry } from "./telemetry.ts";
|
||||
import { homedir } from "os";
|
||||
import { join } from "path";
|
||||
|
||||
/** Current app version — sourced from electrobun.config.ts at build time. */
|
||||
const APP_VERSION = "0.0.1";
|
||||
|
||||
const DEV_SERVER_PORT = 9760; // Project convention: 9700+ range
|
||||
const DEV_SERVER_URL = `http://localhost:${DEV_SERVER_PORT}`;
|
||||
|
||||
|
|
@ -21,8 +27,12 @@ const DEV_SERVER_URL = `http://localhost:${DEV_SERVER_PORT}`;
|
|||
const ptyClient = new PtyClient();
|
||||
const sidecarManager = new SidecarManager();
|
||||
const searchDb = new SearchDb();
|
||||
const relayClient = new RelayClient();
|
||||
const PLUGINS_DIR = join(homedir(), ".config", "agor", "plugins");
|
||||
|
||||
// Initialize telemetry (console-only unless AGOR_OTLP_ENDPOINT is set)
|
||||
initTelemetry();
|
||||
|
||||
async function connectToDaemon(retries = 5, delayMs = 500): Promise<boolean> {
|
||||
for (let attempt = 1; attempt <= retries; attempt++) {
|
||||
try {
|
||||
|
|
@ -874,6 +884,103 @@ const rpc = BrowserView.defineRPC<PtyRPCSchema>({
|
|||
return { ok: false, content: "", error };
|
||||
}
|
||||
},
|
||||
|
||||
// ── Updater handlers ──────────────────────────────────────────────────
|
||||
|
||||
"updater.check": async () => {
|
||||
try {
|
||||
const result = await checkForUpdates(APP_VERSION);
|
||||
return { ...result, error: undefined };
|
||||
} catch (err) {
|
||||
const error = err instanceof Error ? err.message : String(err);
|
||||
console.error("[updater.check]", err);
|
||||
return {
|
||||
available: false,
|
||||
version: "",
|
||||
downloadUrl: "",
|
||||
releaseNotes: "",
|
||||
checkedAt: Date.now(),
|
||||
error,
|
||||
};
|
||||
}
|
||||
},
|
||||
|
||||
"updater.getVersion": () => {
|
||||
return {
|
||||
version: APP_VERSION,
|
||||
lastCheck: getLastCheckTimestamp(),
|
||||
};
|
||||
},
|
||||
|
||||
// ── Remote machine (relay) handlers ──────────────────────────────────
|
||||
|
||||
"remote.connect": async ({ url, token, label }) => {
|
||||
try {
|
||||
const machineId = await relayClient.connect(url, token, label);
|
||||
return { ok: true, machineId };
|
||||
} catch (err) {
|
||||
const error = err instanceof Error ? err.message : String(err);
|
||||
console.error("[remote.connect]", err);
|
||||
return { ok: false, error };
|
||||
}
|
||||
},
|
||||
|
||||
"remote.disconnect": ({ machineId }) => {
|
||||
try {
|
||||
relayClient.disconnect(machineId);
|
||||
return { ok: true };
|
||||
} catch (err) {
|
||||
const error = err instanceof Error ? err.message : String(err);
|
||||
console.error("[remote.disconnect]", err);
|
||||
return { ok: false, error };
|
||||
}
|
||||
},
|
||||
|
||||
"remote.list": () => {
|
||||
try {
|
||||
return { machines: relayClient.listMachines() };
|
||||
} catch (err) {
|
||||
console.error("[remote.list]", err);
|
||||
return { machines: [] };
|
||||
}
|
||||
},
|
||||
|
||||
"remote.send": ({ machineId, command, payload }) => {
|
||||
try {
|
||||
relayClient.sendCommand(machineId, command, payload);
|
||||
return { ok: true };
|
||||
} catch (err) {
|
||||
const error = err instanceof Error ? err.message : String(err);
|
||||
console.error("[remote.send]", err);
|
||||
return { ok: false, error };
|
||||
}
|
||||
},
|
||||
|
||||
"remote.status": ({ machineId }) => {
|
||||
try {
|
||||
const info = relayClient.getStatus(machineId);
|
||||
if (!info) {
|
||||
return { status: "disconnected" as const, latencyMs: null, error: "Machine not found" };
|
||||
}
|
||||
return { status: info.status, latencyMs: info.latencyMs };
|
||||
} catch (err) {
|
||||
const error = err instanceof Error ? err.message : String(err);
|
||||
console.error("[remote.status]", err);
|
||||
return { status: "error" as const, latencyMs: null, error };
|
||||
}
|
||||
},
|
||||
|
||||
// ── Telemetry handler ────────────────────────────────────────────────
|
||||
|
||||
"telemetry.log": ({ level, message, attributes }) => {
|
||||
try {
|
||||
telemetry.log(level, `[frontend] ${message}`, attributes ?? {});
|
||||
return { ok: true };
|
||||
} catch (err) {
|
||||
console.error("[telemetry.log]", err);
|
||||
return { ok: false };
|
||||
}
|
||||
},
|
||||
},
|
||||
|
||||
messages: {},
|
||||
|
|
@ -900,6 +1007,29 @@ ptyClient.on("session_closed", (msg) => {
|
|||
}
|
||||
});
|
||||
|
||||
// ── Forward relay events to WebView ─────────────────────────────────────────
|
||||
|
||||
relayClient.onEvent((machineId, event) => {
|
||||
try {
|
||||
rpc.send["remote.event"]({
|
||||
machineId,
|
||||
eventType: event.type,
|
||||
sessionId: event.sessionId,
|
||||
payload: event.payload,
|
||||
});
|
||||
} catch (err) {
|
||||
console.error("[remote.event] forward error:", err);
|
||||
}
|
||||
});
|
||||
|
||||
relayClient.onStatus((machineId, status, error) => {
|
||||
try {
|
||||
rpc.send["remote.statusChange"]({ machineId, status, error });
|
||||
} catch (err) {
|
||||
console.error("[remote.statusChange] forward error:", err);
|
||||
}
|
||||
});
|
||||
|
||||
// ── App window ───────────────────────────────────────────────────────────────
|
||||
|
||||
async function getMainViewUrl(): Promise<string> {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue