aboutsummaryrefslogtreecommitdiff
path: root/modules/core/core-bundle.js
diff options
context:
space:
mode:
Diffstat (limited to 'modules/core/core-bundle.js')
-rw-r--r--modules/core/core-bundle.js47
1 files changed, 40 insertions, 7 deletions
diff --git a/modules/core/core-bundle.js b/modules/core/core-bundle.js
index cd510b7..84d194d 100644
--- a/modules/core/core-bundle.js
+++ b/modules/core/core-bundle.js
@@ -942,6 +942,17 @@ class VirtualFileSystem {
}
checkpoint(name) {
if (!name) name = new Date().toISOString();
+ name = String(name).trim();
+ if (!name || name.indexOf('/') !== -1) {
+ throw new Error('checkpoint name must be non-empty and must not contain "/"');
+ }
+ // Two unnamed calls can land in the same millisecond; without this the second silently
+ // overwrites the first (measured: 6 calls -> 3 distinct names).
+ if (this.listCheckpoints().indexOf(name) !== -1) {
+ let n = 2;
+ while (this.listCheckpoints().indexOf(name + '-' + n) !== -1) n++;
+ name = name + '-' + n;
+ }
const createdAt = new Date().toISOString();
// Strip system dirs to prevent fractal bloat
const rootClone = JSON.parse(JSON.stringify(this.root));
@@ -961,12 +972,15 @@ class VirtualFileSystem {
}
this.fsWriteJSON('/_snapshots/checkpoint-' + name + '.json', data);
this.saveToStorage();
- // Auto-manage: keep max 3 checkpoints, delete oldest
+ // Auto-manage: keep the 3 NEWEST checkpoints — by createdAt, not by name, so a custom
+ // name ("aaa-oldest") cannot pin itself forever while newer snapshots are deleted.
const all = this.listCheckpoints();
if (all.length > 3) {
- const sorted = all.slice().sort();
- const toDelete = sorted.slice(0, all.length - 3);
- for (const old of toDelete) this.deleteCheckpoint(old);
+ const aged = all.map(n => {
+ const snap = this.fsReadJSON('/_snapshots/checkpoint-' + n + '.json');
+ return { name: n, at: (snap && snap.createdAt) || '' };
+ }).sort((a, b) => (a.at < b.at ? -1 : (a.at > b.at ? 1 : 0)));
+ for (const old of aged.slice(0, aged.length - 3)) this.deleteCheckpoint(old.name);
}
return name;
}
@@ -976,8 +990,18 @@ class VirtualFileSystem {
console.error('Rollback failed: checkpoint not found or invalid');
return false;
}
+ // The snapshot deliberately omits scaffolding (bloat guard at :944-950). Restoring the
+ // clone verbatim deletes every checkpoint, every agent session and the trash, so keep the
+ // live copies and re-attach them after the root swap.
+ const keep = {};
+ for (const dir of ['_snapshots', '_sessions', '.trash']) {
+ const node = this.root && this.root.content ? this.root.content[dir] : null;
+ if (node) keep[dir] = node;
+ }
this.root = raw.root;
this.currentPath = raw.currentPath || [];
+ if (!this.root.content) this.root.content = {};
+ for (const dir of Object.keys(keep)) this.root.content[dir] = keep[dir];
this.saveToStorage();
return true;
}
@@ -18261,6 +18285,7 @@ class PredictorModule {
return this._dataPoints.filter(dp => {
if (!dp.observedDuration) return false; // outcomes only, never predictions
if (dp.usable === false) return false; // aborted runs are not evidence
+ if (!(dp.observedDuration >= 1.5)) return false; // belt and braces: never evidence
if (dp.features) return this._similarity(features, dp.features) > 0.3;
return false;
});
@@ -18412,7 +18437,7 @@ class PredictorModule {
// ─── Commands ───
showStatus() {
- const actualExecutions = this._dataPoints.filter(dp => dp.observedDuration).length;
+ const actualExecutions = this._dataPoints.filter(dp => dp.observedDuration && dp.usable !== false).length;
const paired = this._accuracy.total;
const unpaired = this._dataPoints.filter(dp => dp.paired === false).length;
const accPct = paired > 0 ? ((this._accuracy.correct / paired) * 100).toFixed(0) + '%' : 'n/a';
@@ -18517,8 +18542,16 @@ class PredictorModule {
const data = fs.fsReadJSON('/system/metrics/predictor.json');
if (!data) return;
- // Prediction-only points are noise for similarity; keep outcomes.
- this._dataPoints = (data.dataPoints || []).filter(dp => dp && dp.observedDuration);
+ // Legacy (pre-v2) points carry no `usable`. Backfill it from the measurement — an
+ // aborted run must not enter similarity as evidence (measured: a 0.079 s point
+ // dragged avgDuration to ~0 and floored every prediction at 1 s).
+ this._dataPoints = (data.dataPoints || [])
+ .filter(dp => dp && dp.observedDuration)
+ .map(dp => {
+ if (typeof dp.usable !== 'boolean') dp.usable = dp.observedDuration >= 1.5;
+ return dp;
+ })
+ .filter(dp => dp.usable !== false);
this._predictions = data.predictions || 0;
if (data.v === this._SCHEMA) {