Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 9 additions & 2 deletions desktop/main.js
Original file line number Diff line number Diff line change
Expand Up @@ -681,6 +681,13 @@ function bindResearchUiProcess(processHandle, pythonCommand, candidates, candida
if (researchServerProcess !== processHandle) return;
researchServerProcess = null;
researchServerOwned = false;
const shouldRetry = code !== 0 && workspaceState.status === "starting" && candidateIndex < candidates.length - 1;
if (shouldRetry) {
const nextCommand = candidates[candidateIndex + 1];
appendLog(`[startup-exit] ${pythonCommand} exited (${code ?? "null"}${signal ? `, ${signal}` : ""}). Retrying with ${nextCommand}.`);
launchResearchUiProcess(candidates, candidateIndex + 1);
return;
}
clearResearchStartupTimer();
updateWorkspaceState({
status: "stopped",
Expand All @@ -696,8 +703,8 @@ function bindResearchUiProcess(processHandle, pythonCommand, candidates, candida
researchServerOwned = false;
}
const shouldRetry =
["EACCES", "EPERM", "ENOENT"].includes(error?.code || "")
&& candidateIndex < candidates.length - 1;
["EACCES", "EPERM", "ENOENT"].includes(error?.code || "") &&
candidateIndex < candidates.length - 1;
if (shouldRetry) {
const nextCommand = candidates[candidateIndex + 1];
appendLog(`[spawn-error] ${pythonCommand} failed (${error.code}). Retrying with ${nextCommand}.`);
Expand Down
110 changes: 108 additions & 2 deletions desktop/renderer/app.js
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ const CONFIG = {
launchControlPath: "/api/launch-control",
paperHealthPath: "/api/paper-sessions-health",
brokerHealthPath: "/api/broker-submissions-health",
hyperliquidSurfacePath: "/api/hyperliquid-surface",
stepbitWorkspacePath: "/api/stepbit-workspace",
detailArtifacts: ["report.json", "run_report.json"],
experimentsConfigDir: "configs/experiments",
Expand Down Expand Up @@ -103,6 +104,10 @@ const elements = {
runtimeAlert: document.getElementById("runtime-alert"),
runtimeRetry: document.getElementById("runtime-retry"),
runtimeChips: document.getElementById("runtime-chips"),
frontierMeta: document.getElementById("frontier-meta"),
frontierSummary: document.getElementById("frontier-summary"),
frontierGrid: document.getElementById("frontier-grid"),
frontierCallout: document.getElementById("frontier-callout"),
chatLog: document.getElementById("chat-log"),
chatForm: document.getElementById("chat-form"),
chatInput: document.getElementById("chat-input"),
Expand Down Expand Up @@ -311,7 +316,7 @@ function bindEvents() {
}

function ensureRefreshLoop() {
if (!state.workspace.serverUrl) return;
if (!state.workspace.serverUrl || state.snapshotStatus.refreshPaused) return;
refreshSnapshot();
if (!state.refreshTimer) state.refreshTimer = window.setInterval(refreshSnapshot, CONFIG.refreshIntervalMs);
}
Expand All @@ -335,14 +340,16 @@ async function refreshSnapshot() {
window.quantlabDesktop.requestJson(CONFIG.launchControlPath),
window.quantlabDesktop.requestJson(CONFIG.paperHealthPath),
window.quantlabDesktop.requestJson(CONFIG.brokerHealthPath),
window.quantlabDesktop.requestJson(CONFIG.hyperliquidSurfacePath),
window.quantlabDesktop.requestJson(CONFIG.stepbitWorkspacePath),
]);
state.snapshot = {
runsRegistry,
launchControl: extra[0].status === "fulfilled" ? extra[0].value : state.snapshot?.launchControl || null,
paperHealth: extra[1].status === "fulfilled" ? extra[1].value : state.snapshot?.paperHealth || null,
brokerHealth: extra[2].status === "fulfilled" ? extra[2].value : state.snapshot?.brokerHealth || null,
stepbitWorkspace: extra[3].status === "fulfilled" ? extra[3].value : state.snapshot?.stepbitWorkspace || null,
hyperliquidSurface: extra[3].status === "fulfilled" ? extra[3].value : state.snapshot?.hyperliquidSurface || null,
stepbitWorkspace: extra[4].status === "fulfilled" ? extra[4].value : state.snapshot?.stepbitWorkspace || null,
};
state.snapshotStatus = {
status: "ok",
Expand Down Expand Up @@ -680,6 +687,7 @@ function renderMarkupInto(container, markup) {
function renderWorkspaceState() {
const { status, serverUrl, error, source } = state.workspace;
const runs = getRuns();
const frontier = getFrontierSnapshot();
const stepbit = state.snapshot?.stepbitWorkspace?.live_urls || {};
const paperCount = state.snapshot?.paperHealth?.total_sessions || 0;
const brokerCount = state.snapshot?.brokerHealth?.total_sessions || 0;
Expand Down Expand Up @@ -713,9 +721,107 @@ function renderWorkspaceState() {
createRuntimeChipNode("Stepbit app", stepbit.frontend_reachable ? "up" : "down", stepbit.frontend_reachable ? "up" : "down"),
createRuntimeChipNode("Stepbit core", stepbit.core_ready ? "ready" : stepbit.core_reachable ? "up" : "down", stepbit.core_ready ? "up" : stepbit.core_reachable ? "warn" : "down"),
);
renderFrontierDashboard(frontier);
renderChatAdapterStatus();
}

function getFrontierSnapshot() {
const paper = state.snapshot?.paperHealth || null;
const broker = state.snapshot?.brokerHealth || null;
const hyperliquid = state.snapshot?.hyperliquidSurface || null;
const stepbit = state.snapshot?.stepbitWorkspace?.live_urls || {};
return { paper, broker, hyperliquid, stepbit };
}

function renderFrontierDashboard(frontier) {
if (!elements.frontierMeta || !elements.frontierSummary || !elements.frontierGrid || !elements.frontierCallout) return;
const { paper, broker, hyperliquid, stepbit } = frontier;
const paperReady = Boolean(paper?.available && paper?.total_sessions);
const brokerReady = Boolean(broker?.available);
const brokerAlerts = Boolean(broker?.has_alerts);
const hyperliquidReady = Boolean(hyperliquid?.available);
const hyperliquidAlerts = Boolean(hyperliquid?.submit_has_alerts);
const stepbitReady = Boolean(stepbit?.core_ready || stepbit?.backend_reachable || stepbit?.frontend_reachable);

clearElement(elements.frontierSummary);
appendChildren(
elements.frontierSummary,
createSummaryCardNode("Paper sessions", String(paper?.total_sessions ?? 0), paperReady ? "tone-positive" : "tone-warning"),
createSummaryCardNode("Broker boundary", brokerReady ? "Visible" : "Missing", brokerAlerts ? "tone-negative" : brokerReady ? "tone-positive" : "tone-warning"),
createSummaryCardNode("Hyperliquid surface", hyperliquidReady ? titleCase(hyperliquid?.latest_ready_artifact_type || "Ready") : "Missing", hyperliquidAlerts ? "tone-negative" : hyperliquidReady ? "tone-positive" : "tone-warning"),
createSummaryCardNode("Stepbit boundary", stepbitReady ? "Reachable" : "Down", stepbitReady ? "tone-positive" : "tone-warning"),
);

clearElement(elements.frontierGrid);
appendChildren(
elements.frontierGrid,
createFrontierCard("Paper bridge", paperReady ? "ready for promotion review" : "still building local session evidence", [
["Latest session", paper?.latest_session_id || "-"],
["Latest status", titleCase(paper?.latest_session_status || "none")],
["Issue watch", paper?.latest_issue_session_id || "none"],
], paperReady ? "positive" : "warning"),
createFrontierCard("Broker boundary", brokerReady ? "validation and alerts visible" : "no broker validation surface yet", [
["Latest submit", broker?.latest_submit_session_id || "-"],
["Latest state", broker?.latest_submit_state || "-"],
["Alerts", broker?.has_alerts ? String((broker?.alerts || []).length) : "none"],
], brokerAlerts ? "negative" : brokerReady ? "positive" : "warning"),
createFrontierCard("Hyperliquid", hyperliquidReady ? "signer and submit surfaces are tracked" : "surface not indexed yet", [
["Latest artifact", hyperliquid?.latest_ready_artifact_type || "-"],
["Signature state", titleCase(hyperliquid?.signature_state || "unknown")],
["Alert status", titleCase(hyperliquid?.submit_alert_status || "unknown")],
], hyperliquidAlerts ? "negative" : hyperliquidReady ? "positive" : "warning"),
createFrontierCard("Stepbit", stepbitReady ? "optional runtime boundary is reachable" : "external boundary is offline", [
["Frontend", stepbit?.frontend_reachable ? "up" : "down"],
["Backend", stepbit?.backend_reachable ? "up" : "down"],
["Core", stepbit?.core_ready ? "ready" : stepbit?.core_reachable ? "up" : "down"],
], stepbitReady ? "positive" : "warning"),
);

elements.frontierMeta.textContent = [
paperReady ? `${paper.total_sessions} paper sessions` : "paper not yet visible",
brokerReady ? `${formatNumericCount(broker?.total_sessions || 0)} broker sessions` : "broker not yet visible",
hyperliquidReady ? `${formatNumericCount(hyperliquid?.submit_health?.total_sessions || 0)} Hyperliquid submit sessions` : "Hyperliquid surface not yet visible",
].join(" · ");

elements.frontierCallout.textContent = buildFrontierCallout({ paper, broker, hyperliquid, stepbit });
}

function buildFrontierCallout({ paper, broker, hyperliquid, stepbit }) {
if (broker?.has_alerts) {
return "Broker alerts are present. Inspect the broker boundary before trusting any submit-oriented flow.";
}
if (hyperliquid?.submit_has_alerts) {
return "Hyperliquid submit alerts are present. Inspect submit sessions and reconciliation before moving further.";
}
if (paper?.available && paper?.total_sessions) {
return `Paper sessions are visible with ${paper.total_sessions} tracked sessions. Use the ops view to decide which sessions are ready to bridge.`;
}
if (stepbit?.backend_reachable || stepbit?.core_reachable) {
return "The optional Stepbit boundary is partially reachable. Keep it as an external helper, not as QuantLab's control plane.";
}
return "Launch a run or wait for local session artifacts so the frontier cards can surface real operator state.";
}

function createSummaryCardNode(label, value, tone = "") {
return createElementNode("article", { className: "summary-card frontier-summary-card" }, [
createElementNode("div", { className: "label", text: label }),
createElementNode("div", { className: `value ${tone}`, text: value }),
]);
}

function createFrontierCard(title, subtitle, rows, tone = "neutral") {
return createElementNode("article", { className: `frontier-card tone-${tone}` }, [
createElementNode("div", { className: "frontier-card-head" }, [
createElementNode("div", { className: "section-label", text: title }),
createElementNode("p", { className: "frontier-card-subtitle", text: subtitle }),
]),
createElementNode("dl", { className: "frontier-metric-list metric-list compact" }, rows.flatMap(([label, value]) => [
createElementNode("dt", { text: label }),
createElementNode("dd", { text: value }),
])),
]);
}

function buildRuntimeAlert() {
if (state.workspace.status === "error" || state.workspace.status === "stopped") {
const recentLogs = (state.workspace.logs || []).slice(-4).join("\n");
Expand Down
18 changes: 18 additions & 0 deletions desktop/renderer/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -131,6 +131,24 @@ <h2>Service state</h2>
<div id="runtime-chips" class="runtime-chips"></div>
</section>

<section class="card frontier-panel">
<div class="section-head">
<div>
<div class="section-label">Execution frontier</div>
<h2>Paper, broker, and Hyperliquid</h2>
</div>
<div id="frontier-meta" class="workflow-meta">Waiting for local readiness surfaces...</div>
</div>
<div id="frontier-summary" class="tab-summary-grid frontier-summary"></div>
<div id="frontier-grid" class="frontier-grid"></div>
<div id="frontier-callout" class="frontier-callout"></div>
<div class="workflow-actions frontier-actions">
<button class="ghost-btn" type="button" data-action="open-ops">Open Paper Ops</button>
<button class="ghost-btn" type="button" data-action="open-experiments">Open Experiments</button>
<button class="ghost-btn" type="button" data-prompt="Show runtime status">Show runtime status</button>
</div>
</section>

<section class="workflow-panel card">
<div class="section-head">
<div>
Expand Down
72 changes: 72 additions & 0 deletions desktop/renderer/styles.css
Original file line number Diff line number Diff line change
Expand Up @@ -406,6 +406,78 @@ button:disabled {
.runtime-chip.warn { color: var(--warning); border-color: rgba(240, 179, 93, 0.32); }
.runtime-chip.down { color: var(--danger); border-color: rgba(242, 125, 136, 0.32); }

.frontier-panel {
display: grid;
gap: 16px;
}

.frontier-summary {
margin-top: 2px;
}

.frontier-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(220px, 1fr));
gap: 14px;
}

.frontier-card {
border: 1px solid var(--border);
border-radius: 16px;
padding: 16px;
background: linear-gradient(180deg, rgba(18, 28, 40, 0.94), rgba(8, 14, 22, 0.82));
display: grid;
gap: 12px;
}

.frontier-card.tone-positive {
border-color: rgba(118, 211, 156, 0.28);
}

.frontier-card.tone-warning {
border-color: rgba(240, 179, 93, 0.28);
}

.frontier-card.tone-negative {
border-color: rgba(242, 125, 136, 0.28);
}

.frontier-card-subtitle {
margin: 6px 0 0;
color: var(--muted);
line-height: 1.5;
}

.frontier-metric-list {
margin: 0;
}

.frontier-metric-list dt {
color: var(--muted);
font-size: 0.76rem;
text-transform: uppercase;
letter-spacing: 0.08em;
}

.frontier-metric-list dd {
margin: 4px 0 0;
font-weight: 600;
line-height: 1.45;
}

.frontier-callout {
border: 1px solid var(--border);
border-radius: 16px;
padding: 14px 16px;
background: rgba(8, 14, 22, 0.82);
color: var(--muted);
line-height: 1.55;
}

.frontier-actions {
margin-top: 2px;
}

.workflow-panel {
display: grid;
gap: 16px;
Expand Down
Loading