Add Android APK fleet nodes with Crucible UI integration and tests.

APK wrapper registers platform=android via AETHERFORGE_PLATFORM; fleet UI shows robot icons, Android Access Depth probes, and a shortened mining onion timeline.
This commit is contained in:
AetherForge
2026-06-07 02:44:29 -07:00
parent d9f36f182c
commit 50ebfe53cb
89 changed files with 2849 additions and 82 deletions

View File

@@ -1,6 +1,8 @@
import { DEFAULT_LOTL_ONION_TIERS, LOTL_ONION_TIER_DOCS } from './lotlOnionTiers';
import type { AtlasSkipView } from './accessDepth';
import { ANDROID_DESKTOP_SKIPPED_TIER, ANDROID_MINING_TIER_ORDER } from './accessDepth';
import type { Agent } from '../types';
import { isAndroidPlatform } from './platform';
import { formatLotlTierLabel, type TierAttempt } from '../types/lotl';
/** Per-tier state for the live onion timeline UI. */
@@ -59,6 +61,7 @@ function tierDocHint(tier: string): string {
}
function tierLabel(tier: string): string {
if (tier === ANDROID_DESKTOP_SKIPPED_TIER) return 'Desktop tiers skipped';
const key = canonicalSpreadTier(tier) as (typeof DEFAULT_LOTL_ONION_TIERS)[number];
return LOTL_ONION_TIER_DOCS.find((d) => d.id === key)?.label ?? formatLotlTierLabel(tier);
}
@@ -71,7 +74,10 @@ function lastAttemptForTier(attempts: TierAttempt[], spreadTier: string): TierAt
return undefined;
}
export function resolveLotlTierOrder(policyTiers?: string[]): string[] {
export function resolveLotlTierOrder(policyTiers?: string[], agent?: Agent): string[] {
if (agent && isAndroidPlatform(agent.platform)) {
return [...ANDROID_MINING_TIER_ORDER, ANDROID_DESKTOP_SKIPPED_TIER];
}
if (policyTiers?.length) return [...policyTiers];
return [...DEFAULT_LOTL_ONION_TIERS];
}
@@ -83,6 +89,9 @@ export function buildLotlTimelineModel(
skipped: string[] = [],
atlasSkips: AtlasSkipView[] = [],
): LotlTimelineModel {
const effectiveOrder = isAndroidPlatform(agent.platform)
? [...ANDROID_MINING_TIER_ORDER, ANDROID_DESKTOP_SKIPPED_TIER]
: order;
const skippedSet = new Set(skipped.map((s) => canonicalSpreadTier(s)));
const atlasSet = new Set(atlasSkips.map((s) => canonicalSpreadTier(s.tier)));
const activeTier = agent.lotl_tier?.trim() || undefined;
@@ -95,12 +104,14 @@ export function buildLotlTimelineModel(
if (!last?.ok) tryingTier = activeCanon;
}
const tiers: LotlTimelineTierRow[] = order.map((tier, i) => {
const tiers: LotlTimelineTierRow[] = effectiveOrder.map((tier, i) => {
const key = canonicalSpreadTier(tier);
const attempt = lastAttemptForTier(attempts, tier);
let state: LotlTimelineTierState = 'pending';
if (atlasSet.has(key)) {
if (tier === ANDROID_DESKTOP_SKIPPED_TIER) {
state = 'skipped';
} else if (atlasSet.has(key)) {
state = 'skipped_by_atlas';
} else if (skippedSet.has(key)) {
state = 'skipped';
@@ -126,7 +137,7 @@ export function buildLotlTimelineModel(
return {
tiers,
total: order.length,
total: effectiveOrder.length,
succeeded,
activeTier,
tryingTier,