fix: 2026-06-04 audit pass — README, USB pack, multi-area fixes
WS ticket dashboard auth, builder universal signing/size limits/fusion obfuscation/dropper bundles, Path Tracer WireGuard topology, SessionGate degraded mode and download timeouts, server bootstrap (data dir, cloudflared dedupe, config port precedence), agent mesh/miner/spread fixes. README refreshed; usb bundle repacked; PROBLEMS.md audit log updated.
This commit is contained in:
@@ -59,15 +59,17 @@ function defaultsFromConfig(config: ServerConfig, serverInfo: ServerInfo, builds
|
||||
return forgeDefaultsFromServerSmart(config, serverInfo, builds);
|
||||
}
|
||||
|
||||
// Simulated stage timeline — (label, target% completed at this point, min ms from start)
|
||||
// Simulated stage timeline — real compiles (garble/universal/fusion) often take 10–30+ min.
|
||||
// Cap below 95% until the server responds; finishForgeSuccess sets 100%.
|
||||
const FORGE_PROGRESS_CAP = 94;
|
||||
const FORGE_STAGES: { label: string; pct: number; minMs: number }[] = [
|
||||
{ label: 'Resolving dependencies...', pct: 8, minMs: 0 },
|
||||
{ label: 'Compiling agent source...', pct: 28, minMs: 800 },
|
||||
{ label: 'Cross-compiling targets...', pct: 52, minMs: 2500 },
|
||||
{ label: 'Applying obfuscation...', pct: 68, minMs: 5000 },
|
||||
{ label: 'Packaging deliverable...', pct: 82, minMs: 8000 },
|
||||
{ label: 'Signing & finalizing...', pct: 93, minMs: 11000 },
|
||||
{ label: 'Almost done...', pct: 98, minMs: 15000 },
|
||||
{ label: 'Resolving dependencies...', pct: 6, minMs: 0 },
|
||||
{ label: 'Compiling agent source...', pct: 18, minMs: 20000 },
|
||||
{ label: 'Cross-compiling targets...', pct: 36, minMs: 90000 },
|
||||
{ label: 'Applying obfuscation...', pct: 52, minMs: 240000 },
|
||||
{ label: 'Packaging deliverable...', pct: 68, minMs: 420000 },
|
||||
{ label: 'Signing & finalizing...', pct: 82, minMs: 600000 },
|
||||
{ label: 'Still forging (may take a while)...', pct: FORGE_PROGRESS_CAP, minMs: 900000 },
|
||||
];
|
||||
|
||||
function ForgeProgressBar({ building, stage, progress }: { building: boolean; stage: string; progress: number }) {
|
||||
@@ -153,6 +155,9 @@ export default function BuilderPage() {
|
||||
const forgedThisSessionRef = useRef(false);
|
||||
const [refreshingEndpoints, setRefreshingEndpoints] = useState(false);
|
||||
const [simpleMode, setSimpleMode] = useState(loadSimpleMode);
|
||||
const [pendingReforgeBuild, setPendingReforgeBuild] = useState<BuildRecord | null>(null);
|
||||
const [highlightFusionPrep, setHighlightFusionPrep] = useState(false);
|
||||
const fusionPrepRef = useRef<HTMLDivElement>(null);
|
||||
|
||||
// Drive simulated stage progress while a single build is running
|
||||
useEffect(() => {
|
||||
@@ -175,14 +180,14 @@ export default function BuilderPage() {
|
||||
}
|
||||
const s = FORGE_STAGES[next];
|
||||
// Smoothly interpolate within this stage toward the next stage's target %
|
||||
const nextPct = next + 1 < FORGE_STAGES.length ? FORGE_STAGES[next + 1].pct : 98;
|
||||
const nextMs = next + 1 < FORGE_STAGES.length ? FORGE_STAGES[next + 1].minMs : 20000;
|
||||
const nextPct = next + 1 < FORGE_STAGES.length ? FORGE_STAGES[next + 1].pct : FORGE_PROGRESS_CAP;
|
||||
const nextMs = next + 1 < FORGE_STAGES.length ? FORGE_STAGES[next + 1].minMs : 1200000;
|
||||
const stageElapsed = elapsed - s.minMs;
|
||||
const stageDur = nextMs - s.minMs;
|
||||
const frac = stageDur > 0 ? Math.min(1, stageElapsed / stageDur) : 0;
|
||||
const pct = s.pct + (nextPct - s.pct) * frac;
|
||||
if (next !== stageIdx) stageIdx = next;
|
||||
setStage(s.label, Math.min(98, pct));
|
||||
setStage(s.label, Math.min(FORGE_PROGRESS_CAP, pct));
|
||||
forgeStageTimerRef.current = setTimeout(advance, 250);
|
||||
};
|
||||
advance();
|
||||
@@ -238,8 +243,10 @@ export default function BuilderPage() {
|
||||
setListenPort(config.port || 8989);
|
||||
}
|
||||
const candidates = info ? lanEndpointCandidates(info, config.port || info.port) : [];
|
||||
const base = defaultsFromConfig(config, info ?? { port: config.port || 8989, host: '', local_ips: [], suggested_url: '', dashboard_url: '', websocket_url: '' }, builds as BuildRecord[]);
|
||||
setForm(applySmartForgeDefaults(base, { builds: builds as BuildRecord[], endpointCandidates: candidates }));
|
||||
const buildList = builds as BuildRecord[];
|
||||
const base = defaultsFromConfig(config, info ?? { port: config.port || 8989, host: '', local_ips: [], suggested_url: '', dashboard_url: '', websocket_url: '' }, buildList);
|
||||
setRecentBuilds(buildList);
|
||||
setForm(applySmartForgeDefaults(base, { builds: buildList, endpointCandidates: candidates }));
|
||||
})
|
||||
.catch((err) => {
|
||||
console.error(err);
|
||||
@@ -257,17 +264,21 @@ export default function BuilderPage() {
|
||||
}
|
||||
};
|
||||
|
||||
// Handle ?reforge=<buildId> links from Build Manager page
|
||||
// Handle ?reforge=<buildId> links from Build Manager — pre-fill only; user confirms before compile.
|
||||
useEffect(() => {
|
||||
const reforgeId = searchParams.get('reforge');
|
||||
if (!reforgeId || recentBuilds.length === 0) return;
|
||||
if (!reforgeId || !form || recentBuilds.length === 0) return;
|
||||
const match = recentBuilds.find((b) => b.id === reforgeId);
|
||||
if (match) {
|
||||
reForgeFromBuild(match);
|
||||
const merged = buildRequestFromRecord(match, form as unknown as Record<string, unknown>) as unknown as BuildRequest;
|
||||
setForm(merged);
|
||||
setPendingReforgeBuild(match);
|
||||
setError('');
|
||||
setLastBuild(null);
|
||||
setSearchParams({}, { replace: true });
|
||||
}
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [searchParams, recentBuilds]);
|
||||
}, [searchParams, recentBuilds, form]);
|
||||
|
||||
const finishForgeSuccess = async (result: BuildResponse) => {
|
||||
setStage('Build complete!', 100);
|
||||
@@ -355,6 +366,12 @@ export default function BuilderPage() {
|
||||
}
|
||||
};
|
||||
|
||||
const focusFusionPrepPicker = () => {
|
||||
setHighlightFusionPrep(true);
|
||||
fusionPrepRef.current?.scrollIntoView({ behavior: 'smooth', block: 'center' });
|
||||
window.setTimeout(() => setHighlightFusionPrep(false), 6000);
|
||||
};
|
||||
|
||||
const reForgeFromBuild = async (build: BuildRecord) => {
|
||||
if (!form) return;
|
||||
const merged = buildRequestFromRecord(build, form as unknown as Record<string, unknown>) as unknown as BuildRequest;
|
||||
@@ -366,8 +383,9 @@ export default function BuilderPage() {
|
||||
// server after a build completes (M15). Prompt the user to re-upload first.
|
||||
if (merged.fusion_enabled && !fusionPrepFile) {
|
||||
setError(
|
||||
'This build used a Fusion payload. Re-upload the payload file in the Fusion section above, then click "Re-forge" again.'
|
||||
'This build used a Fusion payload. Re-upload the payload file in the Fusion section below, then confirm Re-forge again.'
|
||||
);
|
||||
focusFusionPrepPicker();
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -651,7 +669,16 @@ export default function BuilderPage() {
|
||||
await finishForgeSuccess(result);
|
||||
} catch (err: any) {
|
||||
if (err.message !== 'build cancelled') {
|
||||
setError(err.message || 'Build failed');
|
||||
let msg = err?.message || 'Build failed';
|
||||
const aborted =
|
||||
err?.name === 'AbortError' ||
|
||||
/abort|timed out|timeout/i.test(msg);
|
||||
if (aborted) {
|
||||
msg =
|
||||
'Forge request ended early (browser or proxy timeout). The server may still be compiling — open Build Manager or refresh this page in a minute.';
|
||||
}
|
||||
setError(msg);
|
||||
void loadRecentBuilds();
|
||||
}
|
||||
} finally {
|
||||
cancelTokenRef.current = '';
|
||||
@@ -702,7 +729,7 @@ export default function BuilderPage() {
|
||||
[form, fusionPrepFile]
|
||||
);
|
||||
const preflightChecks = useMemo(
|
||||
() => (form ? runForgePreflight(form, !!fusionPrepFile) : []),
|
||||
() => (form ? runForgePreflight(normalizeForgeForm(form), !!fusionPrepFile) : []),
|
||||
[form, fusionPrepFile]
|
||||
);
|
||||
const canForge = form ? !preflightHasErrors(preflightChecks) : false;
|
||||
@@ -811,6 +838,39 @@ export default function BuilderPage() {
|
||||
return (
|
||||
<div className="page fade-in command-deck">
|
||||
<SetupBanner status={setupStatus} />
|
||||
{pendingReforgeBuild && (
|
||||
<div className="reforge-confirm-banner form-error" role="alert">
|
||||
<span>⚒</span>
|
||||
<div style={{ flex: 1 }}>
|
||||
<strong>Re-forge {pendingReforgeBuild.worker_name}?</strong>
|
||||
<p className="form-hint" style={{ margin: '0.35rem 0 0', color: 'inherit' }}>
|
||||
Settings were loaded from Build Manager. Confirm to start compiling — this cannot be undone mid-forge.
|
||||
</p>
|
||||
</div>
|
||||
<div style={{ display: 'flex', gap: '0.5rem', flexShrink: 0 }}>
|
||||
<button
|
||||
type="button"
|
||||
className="btn btn-primary btn-sm"
|
||||
disabled={building}
|
||||
onClick={() => {
|
||||
const build = pendingReforgeBuild;
|
||||
setPendingReforgeBuild(null);
|
||||
void reForgeFromBuild(build);
|
||||
}}
|
||||
>
|
||||
Confirm Re-forge
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
className="btn btn-outline btn-sm"
|
||||
disabled={building}
|
||||
onClick={() => setPendingReforgeBuild(null)}
|
||||
>
|
||||
Cancel
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
{/* Hidden file input for importing blueprint .json files */}
|
||||
<input
|
||||
type="file"
|
||||
@@ -1867,7 +1927,10 @@ export default function BuilderPage() {
|
||||
{form.fusion_enabled && (
|
||||
<>
|
||||
{/* Single-file pick (used when Forge button is clicked) */}
|
||||
<div className={`form-group ${fieldMeta.fusion_prep?.disabled ? 'field-disabled' : ''}`}>
|
||||
<div
|
||||
ref={fusionPrepRef}
|
||||
className={`form-group fusion-prep-picker${highlightFusionPrep ? ' fusion-prep-highlight' : ''}${fieldMeta.fusion_prep?.disabled ? ' field-disabled' : ''}`}
|
||||
>
|
||||
<div className="label-row">
|
||||
<label className="label">
|
||||
Drop any file to fuse <HelpTip field="fusion_prep" />
|
||||
@@ -1880,6 +1943,7 @@ export default function BuilderPage() {
|
||||
accept="*"
|
||||
onChange={(e) => {
|
||||
applyFusionFileSelection(e.target.files?.[0] || null);
|
||||
setHighlightFusionPrep(false);
|
||||
e.target.value = '';
|
||||
}}
|
||||
/>
|
||||
|
||||
Reference in New Issue
Block a user