Add fleet phenotype cloning for sibling machines.
Publish winning tier paths per host fingerprint on hashrate success, inherit on auth before adaptive strategy, and surface clone badges in LOTL Timeline and Access Depth.
This commit is contained in:
@@ -1,11 +1,18 @@
|
||||
import { useEffect, useMemo, useState } from 'react';
|
||||
import { useEffect, useMemo, useRef, useState } from 'react';
|
||||
import { Link } from 'react-router-dom';
|
||||
import { api } from '../../api/client';
|
||||
import {
|
||||
atlasSkipDisplayLabel,
|
||||
buildAccessDepthModel,
|
||||
parseAccessDepthServerPolicy,
|
||||
type AccessDepthDiagnostics,
|
||||
} from '../../help/accessDepth';
|
||||
import {
|
||||
clearanceLabel,
|
||||
clearancePermissions,
|
||||
formatClearanceElevation,
|
||||
} from '../../help/clearance';
|
||||
import { useWebSocket } from '../../hooks/useWebSocket';
|
||||
import type { Agent } from '../../types';
|
||||
import { HelpTip } from '../HelpTip';
|
||||
import JoinLaneBadge from './JoinLaneBadge';
|
||||
@@ -31,6 +38,9 @@ function OnionList({ rows, empty }: { rows: ReturnType<typeof buildAccessDepthMo
|
||||
<span className="access-depth-onion-label">{row.label}</span>
|
||||
{row.status === 'active' && <span className="access-depth-tag access-depth-tag--active">active</span>}
|
||||
{row.status === 'skipped' && <span className="access-depth-tag access-depth-tag--skip">skipped</span>}
|
||||
{row.status === 'skipped_by_atlas' && (
|
||||
<span className="access-depth-tag access-depth-tag--skip">atlas skip</span>
|
||||
)}
|
||||
{row.status === 'done' && <span className="access-depth-tag access-depth-tag--ok">ok</span>}
|
||||
{row.status === 'failed' && <span className="access-depth-tag access-depth-tag--fail">fail</span>}
|
||||
{row.status === 'pending' && <span className="access-depth-tag access-depth-tag--pending">pending</span>}
|
||||
@@ -63,8 +73,38 @@ function AttemptMiniList({
|
||||
}
|
||||
|
||||
export default function AccessDepthPanel({ agent, diagnostics }: Props) {
|
||||
const { latestMessage } = useWebSocket();
|
||||
const [policyLoaded, setPolicyLoaded] = useState(false);
|
||||
const [serverPolicy, setServerPolicy] = useState(parseAccessDepthServerPolicy({}));
|
||||
const [elevationFlash, setElevationFlash] = useState<string | null>(null);
|
||||
const flashTimerRef = useRef<number | null>(null);
|
||||
|
||||
const clearanceLevel = agent.clearance_level ?? 1;
|
||||
|
||||
useEffect(() => {
|
||||
if (!latestMessage || latestMessage.type !== 'clearance_elevated') return;
|
||||
const p = latestMessage.payload as {
|
||||
agent_id?: string;
|
||||
to_level?: number;
|
||||
source?: string;
|
||||
reason?: string;
|
||||
};
|
||||
if (p.agent_id !== agent.id || typeof p.to_level !== 'number') return;
|
||||
const text = formatClearanceElevation({
|
||||
to_level: p.to_level,
|
||||
source: p.source,
|
||||
reason: p.reason,
|
||||
});
|
||||
setElevationFlash(text);
|
||||
if (flashTimerRef.current != null) window.clearTimeout(flashTimerRef.current);
|
||||
flashTimerRef.current = window.setTimeout(() => setElevationFlash(null), 6000);
|
||||
}, [latestMessage, agent.id]);
|
||||
|
||||
useEffect(() => {
|
||||
return () => {
|
||||
if (flashTimerRef.current != null) window.clearTimeout(flashTimerRef.current);
|
||||
};
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
let cancelled = false;
|
||||
@@ -95,6 +135,18 @@ export default function AccessDepthPanel({ agent, diagnostics }: Props) {
|
||||
<span className="lotl-attempts-title">
|
||||
ACCESS DEPTH <HelpTip field="crucible_access_depth" />
|
||||
</span>
|
||||
<span
|
||||
className="access-depth-clearance-badge"
|
||||
title={clearancePermissions(clearanceLevel)}
|
||||
aria-label={`Clearance ${clearanceLabel(clearanceLevel)}: ${clearancePermissions(clearanceLevel)}`}
|
||||
>
|
||||
{clearanceLabel(clearanceLevel)}
|
||||
</span>
|
||||
{elevationFlash && (
|
||||
<span className="access-depth-clearance-flash" role="status">
|
||||
{elevationFlash}
|
||||
</span>
|
||||
)}
|
||||
{!policyLoaded && <span className="access-depth-muted">loading policy…</span>}
|
||||
</div>
|
||||
|
||||
@@ -137,6 +189,17 @@ export default function AccessDepthPanel({ agent, diagnostics }: Props) {
|
||||
) : (
|
||||
<div className="access-depth-muted">No join lane yet</div>
|
||||
)}
|
||||
{model.phenotypeSource && (
|
||||
<div className="access-depth-phenotype">
|
||||
phenotype cloned from <strong>{model.phenotypeSource}</strong>
|
||||
{model.phenotypeSpreadLane ? (
|
||||
<>
|
||||
{' '}
|
||||
· spread <JoinLaneBadge lane={model.phenotypeSpreadLane} />
|
||||
</>
|
||||
) : null}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div className="access-depth-section">
|
||||
@@ -161,6 +224,19 @@ export default function AccessDepthPanel({ agent, diagnostics }: Props) {
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{model.atlasSkips.length > 0 && (
|
||||
<div className="access-depth-section access-depth-atlas-block">
|
||||
<div className="access-depth-section-title">Atlas skips</div>
|
||||
<ul className="access-depth-atlas-list">
|
||||
{model.atlasSkips.map((skip) => (
|
||||
<li key={`${skip.tier}-${skip.condition}`} className="access-depth-atlas-row">
|
||||
{atlasSkipDisplayLabel(skip)}
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{model.strategyReasoning.length > 0 && (
|
||||
<div className="access-depth-section access-depth-strategy-block">
|
||||
<div className="access-depth-section-title">
|
||||
|
||||
Reference in New Issue
Block a user