This commit is contained in:
legop3
2026-09-28 17:43:23 -04:00
parent 9de0bfb1e7
commit cd4aee4e74
5 changed files with 40 additions and 5 deletions
+13
View File
@@ -786,3 +786,16 @@ no new training reset or configuration migration is required.
- Focused Node syntax and Python worker entry-point checks passed. Targeted card
lint is run for the UI change. End-to-end training/promotion, snapshot copying,
active-session pinning and browser behavior remain unverified without live data.
### UI cleanup and obsolete models
User explicitly requested removal of UI narration and deletion of obsolete models.
Removed snapshot/threshold explanatory prose and the old commented manual-control
note; idle status now says Stopped. Kept operational status/errors and diagnostics.
Enabled-service startup now deletes permanent model directories whose validated
manifest explicitly identifies policy format 1, even when training is disabled.
Format 2 snapshots, Latest/Previous, recordings and current training state remain.
This supersedes earlier promises to preserve incompatible format 1 model files.
Deletion occurs on the actual server when the updated service starts; no production
files were accessed from this development machine.
@@ -98,6 +98,27 @@ function saveSnapshot(root) {
return model;
});
}
// Explicitly retire format 1 models after the wheel-policy replacement. Only
// recognized old-format manifests qualify; malformed or future models are kept.
function removeObsoleteModels(root) {
return serial(async () => {
const directory = path.join(root, 'models');
let entries;
try { entries = await fs.readdir(directory, { withFileTypes: true }); }
catch (error) { if (error.code === 'ENOENT') return; throw error; }
for (const entry of entries) {
if (!entry.isDirectory() || !UUID.test(entry.name)) continue;
const target = path.join(directory, entry.name);
let model;
try { model = await metadata(target); }
catch { continue; }
if (model.specification?.version === 1 && !leases.has(target)) {
await fs.rm(target, { recursive: true });
}
}
});
}
function listModels(root) {
return serial(async () => {
let entries;
@@ -116,4 +137,4 @@ function listModels(root) {
return models;
});
}
module.exports = { listModels, acquireModel, adoptCheckpoint, saveSnapshot };
module.exports = { removeObsoleteModels, listModels, acquireModel, adoptCheckpoint, saveSnapshot };
@@ -13,6 +13,7 @@ const { createRecording } = require('./recording');
const { recoverRecordings, enforceRetention } = require('./storage');
const { createTrainer } = require('./training');
const { createDriving } = require('./driving');
const { removeObsoleteModels } = require('./models');
const { createGateway } = require('./socketGateway');
function createRuntime({ config, logger }) {
@@ -221,6 +222,7 @@ function createRuntime({ config, logger }) {
async function start() {
await fs.mkdir(recordingsRoot, { recursive: true });
await recoverRecordings(recordingsRoot);
await removeObsoleteModels(root);
await trainer.start();
running = true;
driving.attach();
+2
View File
@@ -397,6 +397,8 @@ function advanceTurn(roverId) {
return;
}
if (queue.queue.length === 0) {
// Empty queues must not expose the departed driver through turn.currentId.
queue.current = null;
stopRover(roverId);
clearTurnTimer(roverId, queue);
setActiveDriver(roverId, null);
@@ -130,7 +130,7 @@ function LearningCard({ roverId }) {
return <CardFrame title="Rover learning" className="w-full" bodyClassName="text-sm text-slate-300">
<div className="space-y-2">
<p className="text-center text-xs text-slate-400">
{session ? `${session.modelName} · ${session.status}` : state ? 'Choose a model to control your rover.' : 'Waiting for rover learning status…'}
{session ? `${session.modelName} · ${session.status}` : state ? 'Stopped' : 'Waiting for rover learning status…'}
</p>
<div className="flex flex-wrap items-center justify-center gap-1">
<select aria-label="Rover model" className={fieldClass} value={chosen?.id || ''} onChange={(event) => setSelectedId(event.target.value)} disabled={working || !models.length}>
@@ -147,7 +147,6 @@ function LearningCard({ roverId }) {
</div>
{state?.control?.canSnapshot && <div className="text-center">
<button type="button" className="button-dark text-sm disabled:opacity-50" disabled={working} onClick={() => act('snapshot')}>Save learner snapshot</button>
<p className="mt-1 text-xs text-slate-400">Preserves the latest completed training checkpoint, including experimental results.</p>
</div>}
{session && <section className="space-y-2" aria-label="Live model actions">
<p className="text-center text-xs text-sky-300">{session.predictionStatus || 'Waiting for fresh video and sensors'}</p>
@@ -161,7 +160,6 @@ function LearningCard({ roverId }) {
value={thresholdDraft ?? session.threshold ?? .7} disabled={working || !state?.control?.canAdjust}
onChange={(event) => setThresholdDraft(Number(event.target.value))}
onPointerUp={commitThreshold} onKeyUp={commitThreshold} onBlur={commitThreshold} />
<span className="block text-slate-400">Lower allows more accessory actions. Wheel speeds are independent.</span>
</label>
<div className="grid max-h-52 grid-cols-1 gap-1 overflow-y-auto @[28rem]:grid-cols-2">
{session.proposals?.map((proposal) => {
@@ -178,7 +176,6 @@ function LearningCard({ roverId }) {
</div>
</section>}
{state?.control?.reason && <p className="text-center text-xs text-slate-400">{state.control.reason}</p>}
{/* <p className="text-center text-xs text-slate-500">Manual controls stay available and do not stop the model.</p> */}
<p className="text-center text-xs text-slate-400">Recording: {recording ? recording.paused ? 'paused for disk space' : `${recording.sessions.filter((item) => item.video === 'Recording human driving').length} driving · ${recording.sessions.filter((item) => item.video === 'Dock lead-in buffer').length} dock buffer(s)` : 'unavailable'} · Training: {training?.status || 'unavailable'}</p>
{(error || state?.catalogError) && <p role="alert" className="break-words text-xs text-amber-300">{error || state.catalogError}</p>}
<details className="surface-muted text-xs" onToggle={(event) => setDetailsOpen(event.currentTarget.open)}>