diff --git a/frontend/console.html b/frontend/console.html
index 78684cd..49e93b4 100644
--- a/frontend/console.html
+++ b/frontend/console.html
@@ -980,6 +980,277 @@
gap: 12px;
}
+ .top-priority-grid {
+ display: grid;
+ grid-template-columns: minmax(0, 1.08fr) minmax(280px, 0.92fr);
+ gap: 14px;
+ }
+
+ .top-priority-card {
+ padding: 18px;
+ border-radius: 18px;
+ border: 1px solid rgba(255,255,255,0.08);
+ background:
+ linear-gradient(180deg, rgba(255,255,255,0.04), rgba(255,255,255,0.02)),
+ rgba(255,255,255,0.02);
+ }
+
+ .top-priority-card.danger {
+ border-color: rgba(255, 111, 97, 0.24);
+ background:
+ linear-gradient(180deg, rgba(255, 111, 97, 0.16), rgba(255, 111, 97, 0.06)),
+ rgba(255, 111, 97, 0.05);
+ }
+
+ .top-priority-card.warn {
+ border-color: rgba(255, 184, 77, 0.24);
+ background:
+ linear-gradient(180deg, rgba(255, 184, 77, 0.14), rgba(255, 184, 77, 0.05)),
+ rgba(255, 184, 77, 0.04);
+ }
+
+ .top-priority-card.good {
+ border-color: rgba(48, 209, 88, 0.22);
+ background:
+ linear-gradient(180deg, rgba(48, 209, 88, 0.12), rgba(48, 209, 88, 0.04)),
+ rgba(48, 209, 88, 0.04);
+ }
+
+ .top-priority-kicker {
+ color: var(--muted);
+ font-size: 11px;
+ margin-bottom: 8px;
+ font-family: "IBM Plex Mono", monospace;
+ text-transform: uppercase;
+ letter-spacing: 0.08em;
+ }
+
+ .top-priority-title {
+ font-size: 20px;
+ font-weight: 700;
+ letter-spacing: -0.03em;
+ color: var(--text);
+ margin-bottom: 10px;
+ }
+
+ .top-priority-detail,
+ .top-priority-action {
+ font-size: 13px;
+ line-height: 1.65;
+ color: var(--muted);
+ }
+
+ .top-priority-action {
+ margin-top: 12px;
+ padding-top: 12px;
+ border-top: 1px solid rgba(255,255,255,0.08);
+ }
+
+ .top-priority-meta {
+ display: flex;
+ flex-wrap: wrap;
+ gap: 8px;
+ margin-top: 14px;
+ }
+
+ .priority-mini-list {
+ display: grid;
+ gap: 10px;
+ }
+
+ .priority-mini-item {
+ padding: 14px;
+ border-radius: 14px;
+ background: rgba(255,255,255,0.03);
+ border: 1px solid rgba(255,255,255,0.06);
+ }
+
+ .priority-mini-item.danger {
+ border-color: rgba(255, 111, 97, 0.18);
+ background: rgba(255, 111, 97, 0.08);
+ }
+
+ .priority-mini-item.warn {
+ border-color: rgba(255, 184, 77, 0.18);
+ background: rgba(255, 184, 77, 0.08);
+ }
+
+ .priority-mini-item.good {
+ border-color: rgba(48, 209, 88, 0.16);
+ background: rgba(48, 209, 88, 0.07);
+ }
+
+ .priority-mini-head {
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+ gap: 10px;
+ margin-bottom: 6px;
+ }
+
+ .priority-mini-title {
+ font-size: 13px;
+ font-weight: 600;
+ color: var(--text);
+ }
+
+ .priority-mini-detail {
+ color: var(--muted);
+ font-size: 12px;
+ line-height: 1.55;
+ }
+
+ .ops-kpi-grid {
+ display: grid;
+ grid-template-columns: repeat(4, minmax(0, 1fr));
+ gap: 12px;
+ }
+
+ .ops-kpi-card {
+ padding: 14px 16px;
+ border-radius: 16px;
+ background: rgba(255,255,255,0.03);
+ border: 1px solid rgba(255,255,255,0.06);
+ }
+
+ .ops-kpi-card.warn {
+ border-color: rgba(255, 184, 77, 0.2);
+ background: rgba(255, 184, 77, 0.08);
+ }
+
+ .ops-kpi-card.danger {
+ border-color: rgba(255, 111, 97, 0.2);
+ background: rgba(255, 111, 97, 0.08);
+ }
+
+ .ops-kpi-label {
+ color: var(--muted);
+ font-size: 11px;
+ margin-bottom: 8px;
+ font-family: "IBM Plex Mono", monospace;
+ text-transform: uppercase;
+ letter-spacing: 0.08em;
+ }
+
+ .ops-kpi-value {
+ font-family: "IBM Plex Mono", monospace;
+ font-size: 20px;
+ color: var(--text);
+ margin-bottom: 6px;
+ }
+
+ .ops-kpi-detail {
+ color: var(--muted);
+ font-size: 12px;
+ line-height: 1.55;
+ }
+
+ .lifecycle-list {
+ display: grid;
+ gap: 12px;
+ }
+
+ .lifecycle-card {
+ padding: 16px;
+ border-radius: 16px;
+ background: rgba(255,255,255,0.03);
+ border: 1px solid rgba(255,255,255,0.06);
+ }
+
+ .lifecycle-card.buy {
+ border-color: rgba(48, 209, 88, 0.18);
+ }
+
+ .lifecycle-card.sell {
+ border-color: rgba(255, 111, 97, 0.18);
+ }
+
+ .lifecycle-head {
+ display: flex;
+ justify-content: space-between;
+ gap: 12px;
+ align-items: flex-start;
+ margin-bottom: 12px;
+ }
+
+ .lifecycle-title {
+ font-size: 16px;
+ font-weight: 700;
+ color: var(--text);
+ margin-bottom: 4px;
+ }
+
+ .lifecycle-meta {
+ color: var(--muted);
+ font-size: 11px;
+ line-height: 1.55;
+ font-family: "IBM Plex Mono", monospace;
+ }
+
+ .lifecycle-summary {
+ color: var(--muted);
+ font-size: 12px;
+ line-height: 1.6;
+ margin-bottom: 12px;
+ }
+
+ .lifecycle-lanes {
+ display: grid;
+ grid-template-columns: repeat(2, minmax(0, 1fr));
+ gap: 10px;
+ }
+
+ .lifecycle-lane {
+ padding: 12px 14px;
+ border-radius: 14px;
+ background: rgba(255,255,255,0.03);
+ border: 1px solid rgba(255,255,255,0.06);
+ }
+
+ .lifecycle-lane.good {
+ border-color: rgba(48, 209, 88, 0.18);
+ background: rgba(48, 209, 88, 0.08);
+ }
+
+ .lifecycle-lane.warn {
+ border-color: rgba(255, 184, 77, 0.18);
+ background: rgba(255, 184, 77, 0.08);
+ }
+
+ .lifecycle-lane.danger {
+ border-color: rgba(255, 111, 97, 0.18);
+ background: rgba(255, 111, 97, 0.08);
+ }
+
+ .lifecycle-lane-head {
+ display: flex;
+ justify-content: space-between;
+ gap: 10px;
+ margin-bottom: 8px;
+ align-items: center;
+ }
+
+ .lifecycle-lane-title {
+ font-size: 13px;
+ font-weight: 600;
+ color: var(--text);
+ }
+
+ .lifecycle-lane-state {
+ font-size: 11px;
+ color: var(--muted);
+ font-family: "IBM Plex Mono", monospace;
+ text-transform: uppercase;
+ }
+
+ .lifecycle-lane-detail {
+ display: grid;
+ gap: 6px;
+ color: var(--muted);
+ font-size: 12px;
+ line-height: 1.55;
+ }
+
.signal-feed-card {
padding: 14px 16px;
border-radius: 16px;
@@ -1959,7 +2230,9 @@
.platform-overview,
.platform-stats,
.signal-feed-stats,
- .position-card-gridline {
+ .position-card-gridline,
+ .ops-kpi-grid,
+ .lifecycle-lanes {
grid-template-columns: repeat(2, minmax(0, 1fr));
}
@@ -1996,7 +2269,10 @@
.heartbeat-grid,
.health-ribbon,
.ops-summary,
- .focus-summary {
+ .focus-summary,
+ .top-priority-grid,
+ .ops-kpi-grid,
+ .lifecycle-lanes {
grid-template-columns: 1fr;
}
@@ -2096,6 +2372,36 @@
+
+
+
Priority Layer
+
当前最重要的问题
+
先回答现在最需要你看的是什么,以及你该做什么
+
+
+
+
+
+
Ops Layer
+
系统运营指标
+
看系统过去一段时间是否真的有分析、信号和执行产出
+
+
+
+
+
+
Lifecycle Layer
+
信号生命周期
+
把信号、执行结果、挂单和持仓串起来,不再分散查看
+
+
+
Decision Layer
@@ -2121,8 +2427,8 @@
Risk Layer
-
管理待处理事项
-
把需要你判断和干预的事情放到最前面
+
待处理清单
+
首屏的辅助核对区,完整优先级已提升到左侧顶部
正在汇总待处理事项...
@@ -2425,6 +2731,247 @@
`;
}
+ function normalizeSeverity(severity) {
+ const value = String(severity || '').toLowerCase();
+ if (['danger', 'error', 'critical'].includes(value)) return 'danger';
+ if (['warning', 'warn', 'hold'].includes(value)) return 'warn';
+ return 'good';
+ }
+
+ function countRecentEvents(events, hours = 24, matcher = null) {
+ const cutoff = Date.now() - hours * 3600 * 1000;
+ return (events || []).filter((event) => {
+ const rawTime = event?.timestamp || event?.created_at || event?.opened_at;
+ const time = rawTime ? new Date(rawTime).getTime() : 0;
+ if (!time || Number.isNaN(time) || time < cutoff) return false;
+ return typeof matcher === 'function' ? matcher(event) : true;
+ }).length;
+ }
+
+ function inferOpsSnapshot(data) {
+ const signals = data.signals?.latest || [];
+ const events = data.execution_events || [];
+ const positions = data.management?.positions || [];
+ const orders = data.management?.orders || [];
+ const blocked24h = countRecentEvents(events, 24, (event) => event.event_type === 'execution_blocked_summary');
+ const success24h = countRecentEvents(events, 24, (event) => event.status === 'success');
+ const warn24h = countRecentEvents(events, 24, (event) => ['warning', 'error'].includes(event.status));
+ const recentSignal24h = countRecentEvents(signals, 24, () => true);
+ const entryOrders = orders.filter((order) => order.category === 'entry');
+ const protectionOrders = orders.filter((order) => order.category === 'tp_sl');
+ const completeProtectionCount = positions.filter((position) => position.take_profit && position.stop_loss).length;
+ const protectionCoverage = positions.length > 0 ? (completeProtectionCount / positions.length) * 100 : 100;
+ const conversionRate = recentSignal24h > 0 ? (success24h / recentSignal24h) * 100 : 0;
+ const staleEntryOrders = entryOrders.filter((order) => {
+ const created = order.created_at ? new Date(order.created_at).getTime() : 0;
+ return created && !Number.isNaN(created) && (Date.now() - created) > (30 * 60 * 1000);
+ }).length;
+
+ return {
+ recentSignal24h,
+ success24h,
+ warn24h,
+ blocked24h,
+ entryOrders,
+ protectionOrders,
+ protectionCoverage,
+ conversionRate,
+ staleEntryOrders,
+ positions,
+ };
+ }
+
+ function deriveTopPriorities(data) {
+ const cryptoAgent = data.crypto_agent || {};
+ const monitor = cryptoAgent.analysis_monitor || {};
+ const attentionItems = data.management?.attention_items || [];
+ const events = data.execution_events || [];
+ const positions = data.management?.positions || [];
+ const executionControls = cryptoAgent.target_execution_controls || {};
+ const haltedEntries = Object.entries(cryptoAgent.platform_halts || {}).filter(([, item]) => item?.halted);
+ const disabledTargets = Object.entries(executionControls).filter(([, item]) => item?.enabled === false);
+ const top = [];
+
+ if (!cryptoAgent.running || !monitor.last_heartbeat_at) {
+ top.push({
+ tone: 'danger',
+ title: !cryptoAgent.running ? 'Crypto Agent 已停止' : '分析心跳缺失',
+ detail: !cryptoAgent.running
+ ? '信号层当前未运行,后续不会继续产出分析、信号或执行决策。'
+ : '当前没有最近分析心跳,说明调度、循环或主任务可能卡住。',
+ action: '优先核对进程状态、定时调度和主循环日志,确认系统是否仍在正常扫盘。',
+ badges: [
+ !cryptoAgent.running ? 'STOPPED' : 'NO HEARTBEAT',
+ monitor.last_heartbeat_at ? `上次 ${relativeTime(monitor.last_heartbeat_at)}` : '无时间戳',
+ ],
+ });
+ }
+
+ if (haltedEntries.length > 0) {
+ const [targetKey, halt] = haltedEntries[0];
+ top.push({
+ tone: 'danger',
+ title: `${targetKey} 已停机`,
+ detail: halt?.reason || '平台已触发回撤熔断或风险停机。',
+ action: '检查触发原因、核对持仓是否已处理,然后决定是恢复自动执行还是保持停机。',
+ badges: [
+ `${haltedEntries.length} 个停机目标`,
+ halt?.halted_at ? formatTime(halt.halted_at) : '等待处理',
+ ],
+ });
+ }
+
+ const latestBlocked = events.find((event) => event.event_type === 'execution_blocked_summary');
+ if (latestBlocked) {
+ top.push({
+ tone: latestBlocked.status === 'error' ? 'danger' : 'warn',
+ title: `${latestBlocked.symbol || '-'} 执行被阻塞`,
+ detail: latestBlocked.reason || '最近有信号未能落到执行层。',
+ action: '切到风险标签核对平台级归因,确认是价格未到、风控拦截、余额问题还是执行关闭。',
+ badges: [
+ latestBlocked.platform || 'execution',
+ latestBlocked.signal_timeframe_text || latestBlocked.setup_type || 'blocked',
+ ],
+ });
+ }
+
+ const unprotected = positions.filter((item) => !item.take_profit || !item.stop_loss);
+ if (unprotected.length > 0) {
+ const sample = unprotected[0];
+ top.push({
+ tone: 'warn',
+ title: `${sample.symbol || '-'} 风控保护不完整`,
+ detail: `当前有 ${unprotected.length} 个持仓缺少止盈或止损,保护单链路可能存在异常。`,
+ action: '优先检查执行监管器和交易所保护单状态,确认 TP/SL 是否漏挂、失败或已失效。',
+ badges: [
+ `${unprotected.length} 个异常持仓`,
+ `${sample.platform || '-'} / ${sample.account_id || '-'}`,
+ ],
+ });
+ }
+
+ if (disabledTargets.length > 0) {
+ const [targetKey] = disabledTargets[0];
+ top.push({
+ tone: 'warn',
+ title: `${targetKey} 自动交易已关闭`,
+ detail: `当前共有 ${disabledTargets.length} 个执行目标处于手动关闭状态。`,
+ action: '确认这是有意停用还是遗留配置。如果应继续执行,需要在控制台重新开启。',
+ badges: [`${disabledTargets.length} OFF`, 'manual control'],
+ });
+ }
+
+ attentionItems.slice(0, 2).forEach((item) => {
+ top.push({
+ tone: normalizeSeverity(item.severity),
+ title: item.title || '待处理事项',
+ detail: item.detail || '请检查对应模块状态。',
+ action: '根据事项详情进入风险、运行或平台区域继续下钻。',
+ badges: [item.timestamp ? relativeTime(item.timestamp) : 'now'],
+ });
+ });
+
+ if (!top.length) {
+ top.push({
+ tone: 'good',
+ title: '当前没有紧急人工接管项',
+ detail: '系统没有明显的停机、熔断、执行阻塞或保护单异常。',
+ action: '重点关注新的信号生命周期和运营指标,确认系统仍有稳定分析与执行产出。',
+ badges: ['CLEAR'],
+ });
+ }
+
+ return top.slice(0, 4);
+ }
+
+ function eventPlatformMatches(event, platform) {
+ const eventPlatform = String(event?.platform || '').toLowerCase();
+ if (platform === 'paper') {
+ return eventPlatform.includes('paper');
+ }
+ if (platform === 'bitget') {
+ return eventPlatform.includes('bitget');
+ }
+ return true;
+ }
+
+ function findMatchingEvent(signal, laneKeyword, events, platform) {
+ const signalTime = signal?.created_at ? new Date(signal.created_at).getTime() : 0;
+ const symbol = signal?.symbol;
+ const candidates = (events || []).filter((event) => {
+ if (event.symbol !== symbol) return false;
+ if (!eventPlatformMatches(event, platform)) return false;
+ if (laneKeyword && event.signal_timeframe_text && !String(event.signal_timeframe_text).includes(laneKeyword)) return false;
+ const eventTime = event?.timestamp ? new Date(event.timestamp).getTime() : 0;
+ if (!signalTime || !eventTime || Number.isNaN(signalTime) || Number.isNaN(eventTime)) return true;
+ return Math.abs(eventTime - signalTime) <= 12 * 3600 * 1000;
+ });
+ return candidates.sort((a, b) => new Date(b.timestamp || 0) - new Date(a.timestamp || 0))[0] || null;
+ }
+
+ function findMatchingPosition(signal, platform, positions) {
+ return (positions || []).find((position) => {
+ if (position.symbol !== signal.symbol) return false;
+ return String(position.platform || '').toLowerCase() === String(platform || '').toLowerCase();
+ }) || null;
+ }
+
+ function findMatchingOrder(signal, platform, orders) {
+ return (orders || []).find((order) => {
+ if (order.symbol !== signal.symbol) return false;
+ if (String(order.platform || '').toLowerCase() !== String(platform || '').toLowerCase()) return false;
+ return order.category === 'entry';
+ }) || null;
+ }
+
+ function buildLifecycleLane(signal, laneName, platform, previewDecision, events, positions, orders) {
+ const position = findMatchingPosition(signal, platform, positions);
+ const order = findMatchingOrder(signal, platform, orders);
+ const event = findMatchingEvent(signal, signal.timeframe || signal.type || '', events, platform);
+ let tone = 'warn';
+ let state = 'pending';
+ const detailRows = [];
+
+ if (previewDecision?.decision) {
+ detailRows.push(`决策: ${previewDecision.decision}`);
+ }
+
+ if (position) {
+ tone = (!position.take_profit || !position.stop_loss) ? 'warn' : 'good';
+ state = 'position';
+ detailRows.push(`已持仓 ${position.side || '-'} / ${formatNumber(position.size || 0, 4)} / ${formatNumber(position.leverage || 0, 1)}x`);
+ detailRows.push(`入场 ${formatMoney(position.entry_price)} / 现价 ${formatMoney(position.mark_price)}`);
+ detailRows.push(`TP ${position.take_profit ? formatMoney(position.take_profit) : '-'} / SL ${position.stop_loss ? formatMoney(position.stop_loss) : '-'}`);
+ } else if (order) {
+ tone = 'warn';
+ state = 'entry order';
+ detailRows.push(`挂单 ${order.order_type || '-'} / ${formatMoney(order.price)} / ${formatNumber(order.size || 0, 4)}`);
+ detailRows.push(`创建 ${order.created_at ? `${relativeTime(order.created_at)} / ${formatTime(order.created_at)}` : '-'}`);
+ } else if (event) {
+ tone = event.status === 'success' ? 'good' : (event.status === 'error' ? 'danger' : 'warn');
+ state = event.status || event.event_type || 'event';
+ detailRows.push(event.reason || '已有执行事件,但未找到持仓或挂单。');
+ if (event.action || event.event_type) {
+ detailRows.push(`动作 ${event.action || '-'} / 事件 ${event.event_type || '-'}`);
+ }
+ } else if (previewDecision?.decision) {
+ tone = ['OPEN', 'ADD', 'CLOSE', 'CANCEL_PENDING'].includes(previewDecision.decision) ? 'warn' : 'good';
+ state = previewDecision.decision.toLowerCase();
+ detailRows.push(previewDecision.reason || previewDecision.reasoning || '已有决策预览,但尚未找到执行落地迹象。');
+ } else {
+ tone = 'good';
+ state = 'idle';
+ detailRows.push('当前没有匹配到执行结果、挂单或持仓。');
+ }
+
+ return {
+ laneName,
+ tone,
+ state,
+ detailRows,
+ };
+ }
+
function loadSensitivePreference() {
try {
revealSensitiveData = window.localStorage.getItem(SENSITIVE_VISIBILITY_KEY) === '1';
@@ -2455,6 +3002,8 @@
cachedConsoleData.crypto_agent?.platform_halts,
cachedConsoleData.crypto_agent?.target_execution_controls || {}
);
+ renderOpsKpis(cachedConsoleData);
+ renderSignalLifecycle(cachedConsoleData);
}
}
@@ -2995,6 +3544,129 @@
});
}
+ function renderTopPriority(data) {
+ const container = document.getElementById('topPriority');
+ if (!container) return;
+
+ const priorities = deriveTopPriorities(data);
+ const primary = priorities[0];
+ const rest = priorities.slice(1, 4);
+
+ container.innerHTML = `
+
+ top priority
+ ${primary.title}
+ ${primary.detail}
+ 建议动作: ${primary.action}
+
+ ${(primary.badges || []).map((badge) => `${badge}`).join('')}
+
+
+
+ ${rest.length ? rest.map((item) => `
+
+
+
${item.title}
+
${item.tone}
+
+
${item.detail}
+
+ `).join('') : compactEmpty('没有其他更高优先级事项', '当前首要问题已经覆盖了最需要处理的点。')}
+
+ `;
+ }
+
+ function renderOpsKpis(data) {
+ const container = document.getElementById('opsKpis');
+ if (!container) return;
+
+ const snapshot = inferOpsSnapshot(data);
+ const halts = countHalted(data.crypto_agent?.platform_halts || {});
+ const disabled = Object.values(data.crypto_agent?.target_execution_controls || {}).filter((item) => item?.enabled === false).length;
+
+ container.innerHTML = `
+
+ 24h 分析产出
+ ${snapshot.recentSignal24h}
+ 最近 24 小时写入的信号数。若长期为 0,需要先看运行心跳。
+
+
+ 24h 执行成功率
+ ${formatPercent(snapshot.conversionRate, 1)}
+ 成功 ${snapshot.success24h} / 信号 ${snapshot.recentSignal24h || 0},阻塞 ${snapshot.blocked24h}。
+
+
+ 持仓保护完整率
+ ${formatPercent(snapshot.protectionCoverage, 0)}
+ 持仓 ${snapshot.positions.length} / 已完整 TP+SL ${snapshot.positions.filter((item) => item.take_profit && item.stop_loss).length}。
+
+
+ 停机与陈旧挂单
+ ${halts + disabled + snapshot.staleEntryOrders}
+ 停机 ${halts} / 关闭 ${disabled} / 超 30m 未成交入场单 ${snapshot.staleEntryOrders}。
+
+ `;
+ }
+
+ function renderSignalLifecycle(data) {
+ const container = document.getElementById('signalLifecycle');
+ if (!container) return;
+
+ const signals = data.signals?.latest || [];
+ const events = data.execution_events || [];
+ const positions = data.management?.positions || [];
+ const orders = data.management?.orders || [];
+ const previewMap = data.crypto_agent?.last_execution_preview || {};
+
+ if (!signals.length) {
+ container.innerHTML = compactEmpty('暂无可追踪的信号生命周期', '当前没有最近信号,先关注运行心跳和分析状态。');
+ return;
+ }
+
+ const cards = signals.slice(0, 5).map((signal) => {
+ const preview = previewMap?.[signal.symbol] || {};
+ const paperLane = buildLifecycleLane(signal, '模拟盘', 'paper', preview.paper, events, positions, orders);
+ const bitgetPreview = preview.bitget_accounts?.default || preview.bitget;
+ const bitgetLane = buildLifecycleLane(signal, 'Bitget', 'bitget', bitgetPreview, events, positions, orders);
+ const tone = statusClassFromAction(signal.action);
+ const summary = signal.reasoning || signal.reason || signal.summary || '暂无额外说明';
+
+ return `
+
+
+
+
${signal.symbol || '-'}
+
+ ${relativeTime(signal.created_at)} / ${formatTime(signal.created_at)}
+ ${signal.signal_type || '-'} / ${signal.timeframe || signal.type || '-'} / ${signal.grade || '-'} / ${formatPercent(signal.confidence || 0, 1)}
+
+
+
${signal.action === 'buy' ? '做多' : signal.action === 'sell' ? '做空' : '观望'}
+
+
+ 入场 ${formatMoney(signal.entry_price)} / 现价 ${formatMoney(signal.current_price)} / TP ${formatMoney(signal.take_profit)} / SL ${formatMoney(signal.stop_loss)}
+ ${summary}
+
+
+ ${[paperLane, bitgetLane].map((lane) => `
+
+
+
${lane.laneName}
+
${lane.state}
+
+
+ ${lane.detailRows.map((row) => `
${row}
`).join('')}
+
+
+ `).join('')}
+
+
+ `;
+ });
+
+ container.innerHTML = cards.join('');
+ }
+
function renderSignalStream(signals) {
const container = document.getElementById('signalStream');
if (!signals || signals.length === 0) {
@@ -3669,6 +4341,9 @@
renderFocusSummary(data);
renderAlertStrip(data);
renderOpsSummary(data);
+ renderTopPriority(data);
+ renderOpsKpis(data);
+ renderSignalLifecycle(data);
renderPlatforms(data.platforms, data.crypto_agent?.platform_halts, data.crypto_agent?.target_execution_controls || {});
renderSignalStream(data.signals?.latest || []);
renderAgentSignals(data.crypto_agent, data.signals);