Final sweep: Crucible fixes, Path Tracer polish, forge progress, tests green.
Some checks failed
CI Docker Mining Proof / Linux agent hashrate proof (push) Has been cancelled
Some checks failed
CI Docker Mining Proof / Linux agent hashrate proof (push) Has been cancelled
Align dashboard subtitle default and UpsertAgent tests with fleet label behavior; WebSocket coalesce and PathForge hardening; Crucible expanded ops and visual DV fixes; Vitest 610/610 and full test-suite pass; trim PROBLEMS.md to open items only.
This commit is contained in:
@@ -11,6 +11,7 @@ import { primaryGroupForAgent } from '../help/fleetGroups';
|
||||
import { useFleetGroups } from '../hooks/useFleetGroups';
|
||||
import { useMatrixRain } from '../context/MatrixRainContext';
|
||||
import { parseFullSysCheckMessage, type FullSysCheckReport } from '../types/syscheck';
|
||||
import type { WSCommandResult } from '../types/ws';
|
||||
import { sanitizeScreenshotBase64 } from '../help/screenshotDownload';
|
||||
import FullSysCheckPanel from '../components/Fleet/FullSysCheckPanel';
|
||||
import CrucibleExpandedOps from '../components/Fleet/CrucibleExpandedOps';
|
||||
@@ -306,7 +307,7 @@ const TERM_RENDER_CAP = 400;
|
||||
// ── Component ──────────────────────────────────────────────────────────────
|
||||
|
||||
export default function CruciblePage() {
|
||||
const { agents, commandResults } = useWebSocket();
|
||||
const { agents, commandResults, latestMessage } = useWebSocket();
|
||||
const { setCrucibleFocus } = useMatrixRain();
|
||||
|
||||
// Selection
|
||||
@@ -322,6 +323,7 @@ export default function CruciblePage() {
|
||||
const termEndRef = useRef<HTMLDivElement>(null);
|
||||
const cmdRef = useRef<HTMLInputElement>(null);
|
||||
const lastSeqRef = useRef(0);
|
||||
const lastLatestCmdRef = useRef<typeof latestMessage>(null);
|
||||
|
||||
// Command history
|
||||
const [cmdHistory, setCmdHistory] = useState<string[]>([]);
|
||||
@@ -435,12 +437,18 @@ export default function CruciblePage() {
|
||||
|
||||
useEffect(() => {
|
||||
if (!commandResults || commandResults.length === 0) return;
|
||||
const newEntries = commandResults.filter((r) => r._seq > lastSeqRef.current);
|
||||
const newEntries = commandResults.filter(
|
||||
(r) => typeof r._seq === 'number' && r._seq > lastSeqRef.current,
|
||||
);
|
||||
if (newEntries.length === 0) return;
|
||||
lastSeqRef.current = newEntries[newEntries.length - 1]._seq;
|
||||
|
||||
const lines: TermLine[] = [];
|
||||
let maxSeq = lastSeqRef.current;
|
||||
|
||||
for (const r of newEntries) {
|
||||
if (typeof r._seq === 'number') {
|
||||
maxSeq = Math.max(maxSeq, r._seq);
|
||||
}
|
||||
const aid = r.agent_id;
|
||||
if (!aid) continue;
|
||||
|
||||
@@ -517,6 +525,10 @@ export default function CruciblePage() {
|
||||
});
|
||||
} else {
|
||||
const msgLines = msg.split('\n').filter(Boolean);
|
||||
if (msgLines.length === 0) {
|
||||
const label = r.action ? `[${r.action}]` : '[result]';
|
||||
msgLines.push(r.success ? `${label} OK` : `${label} FAILED`);
|
||||
}
|
||||
for (const line of msgLines) {
|
||||
lines.push({
|
||||
id: mkId(), agentId: aid, agentName: name,
|
||||
@@ -525,12 +537,64 @@ export default function CruciblePage() {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
lastSeqRef.current = maxSeq;
|
||||
if (lines.length > 0) {
|
||||
setTermLines((prev) => [...prev, ...lines].slice(-2000));
|
||||
}
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [commandResults]);
|
||||
|
||||
// Backup path: if commandResults batching ever misses an entry, latestMessage
|
||||
// still carries command_result (WS_LATEST_MESSAGE_TYPES includes it).
|
||||
useEffect(() => {
|
||||
if (!latestMessage || latestMessage.type !== 'command_result') return;
|
||||
if (latestMessage === lastLatestCmdRef.current) return;
|
||||
lastLatestCmdRef.current = latestMessage;
|
||||
|
||||
let r = latestMessage.payload as WSCommandResult | string;
|
||||
if (typeof r === 'string') {
|
||||
try {
|
||||
r = JSON.parse(r) as WSCommandResult;
|
||||
} catch {
|
||||
return;
|
||||
}
|
||||
}
|
||||
const aid = r.agent_id;
|
||||
if (!aid) return;
|
||||
|
||||
// commandResults effect owns entries already queued in the provider buffer
|
||||
if (
|
||||
commandResults?.some(
|
||||
(c) => c.agent_id === aid && c.action === r.action && c.message === r.message,
|
||||
)
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
const msg = r.message ?? '';
|
||||
const agent = agents.find((a) => a.id === aid);
|
||||
const name = agent?.name ?? aid.slice(0, 8);
|
||||
const targeted = selectedIds.size === 0 || selectedIds.has(aid);
|
||||
const msgLines = msg.split('\n').filter(Boolean);
|
||||
if (msgLines.length === 0) {
|
||||
const label = r.action ? `[${r.action}]` : '[result]';
|
||||
msgLines.push(r.success ? `${label} OK` : `${label} FAILED`);
|
||||
}
|
||||
const lines: TermLine[] = msgLines.map((line) => ({
|
||||
id: mkId(),
|
||||
agentId: aid,
|
||||
agentName: name,
|
||||
isCmd: false,
|
||||
text: line,
|
||||
ts: new Date(),
|
||||
success: r.success,
|
||||
targeted,
|
||||
}));
|
||||
setTermLines((prev) => [...prev, ...lines].slice(-2000));
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [latestMessage, commandResults]);
|
||||
|
||||
// ── Selection helpers ──────────────────────────────────────────────────
|
||||
|
||||
const toggle = (id: string) =>
|
||||
|
||||
Reference in New Issue
Block a user