dreaming: preserve unknown phase state on partial status

This commit is contained in:
Vignesh Natarajan
2026-04-10 01:38:48 -07:00
committed by Vignesh
parent c519f5abe1
commit f479ab1498
3 changed files with 63 additions and 27 deletions

View File

@@ -173,9 +173,9 @@ function formatDreamNextCycle(nextRunAtMs: number | undefined): string | null {
}
function resolveDreamingNextCycle(
status: { phases: Record<string, { enabled: boolean; nextRunAtMs?: number }> } | null,
status: { phases?: Record<string, { enabled: boolean; nextRunAtMs?: number }> } | null,
): string | null {
if (!status) {
if (!status?.phases) {
return null;
}
const nextRunAtMs = Object.values(status.phases)

View File

@@ -180,6 +180,38 @@ describe("dreaming controller", () => {
expect(state.dreamingStatusError).toBeNull();
});
it("preserves unknown phase state when status omits phase metadata", async () => {
const { state, request } = createState();
request.mockResolvedValue({
dreaming: {
enabled: true,
shortTermCount: 1,
recallSignalCount: 0,
dailySignalCount: 0,
groundedSignalCount: 0,
totalSignalCount: 1,
phaseSignalCount: 0,
lightPhaseHitCount: 0,
remPhaseHitCount: 0,
promotedTotal: 0,
promotedToday: 0,
shortTermEntries: [],
signalEntries: [],
promotedEntries: [],
},
});
await loadDreamingStatus(state);
expect(state.dreamingStatus).toEqual(
expect.objectContaining({
enabled: true,
}),
);
expect(state.dreamingStatus?.phases).toBeUndefined();
expect(state.dreamingStatusError).toBeNull();
});
it("patches config to update global dreaming enablement", async () => {
const { state, request } = createState();
state.configSnapshot = {

View File

@@ -72,7 +72,7 @@ export type DreamingStatus = {
shortTermEntries: DreamingEntry[];
signalEntries: DreamingEntry[];
promotedEntries: DreamingEntry[];
phases: {
phases?: {
light: LightDreamingStatus;
deep: DeepDreamingStatus;
rem: RemDreamingStatus;
@@ -241,6 +241,33 @@ function normalizeDreamingStatus(raw: unknown): DreamingStatus | null {
const lightRecord = asRecord(phasesRecord?.light);
const deepRecord = asRecord(phasesRecord?.deep);
const remRecord = asRecord(phasesRecord?.rem);
const phases =
lightRecord && deepRecord && remRecord
? {
light: {
...normalizePhaseStatusBase(lightRecord),
lookbackDays: normalizeFiniteInt(lightRecord.lookbackDays, 0),
limit: normalizeFiniteInt(lightRecord.limit, 0),
},
deep: {
...normalizePhaseStatusBase(deepRecord),
limit: normalizeFiniteInt(deepRecord.limit, 0),
minScore: normalizeFiniteScore(deepRecord.minScore, 0),
minRecallCount: normalizeFiniteInt(deepRecord.minRecallCount, 0),
minUniqueQueries: normalizeFiniteInt(deepRecord.minUniqueQueries, 0),
recencyHalfLifeDays: normalizeFiniteInt(deepRecord.recencyHalfLifeDays, 0),
...(typeof deepRecord.maxAgeDays === "number" && Number.isFinite(deepRecord.maxAgeDays)
? { maxAgeDays: normalizeFiniteInt(deepRecord.maxAgeDays, 0) }
: {}),
},
rem: {
...normalizePhaseStatusBase(remRecord),
lookbackDays: normalizeFiniteInt(remRecord.lookbackDays, 0),
limit: normalizeFiniteInt(remRecord.limit, 0),
minPatternStrength: normalizeFiniteScore(remRecord.minPatternStrength, 0),
},
}
: undefined;
const timezone = normalizeTrimmedString(record.timezone);
const storePath = normalizeTrimmedString(record.storePath);
const phaseSignalPath = normalizeTrimmedString(record.phaseSignalPath);
@@ -270,30 +297,7 @@ function normalizeDreamingStatus(raw: unknown): DreamingStatus | null {
shortTermEntries: normalizeDreamingEntries(record.shortTermEntries),
signalEntries: normalizeDreamingEntries(record.signalEntries),
promotedEntries: normalizeDreamingEntries(record.promotedEntries),
phases: {
light: {
...normalizePhaseStatusBase(lightRecord),
lookbackDays: normalizeFiniteInt(lightRecord?.lookbackDays, 0),
limit: normalizeFiniteInt(lightRecord?.limit, 0),
},
deep: {
...normalizePhaseStatusBase(deepRecord),
limit: normalizeFiniteInt(deepRecord?.limit, 0),
minScore: normalizeFiniteScore(deepRecord?.minScore, 0),
minRecallCount: normalizeFiniteInt(deepRecord?.minRecallCount, 0),
minUniqueQueries: normalizeFiniteInt(deepRecord?.minUniqueQueries, 0),
recencyHalfLifeDays: normalizeFiniteInt(deepRecord?.recencyHalfLifeDays, 0),
...(typeof deepRecord?.maxAgeDays === "number" && Number.isFinite(deepRecord.maxAgeDays)
? { maxAgeDays: normalizeFiniteInt(deepRecord.maxAgeDays, 0) }
: {}),
},
rem: {
...normalizePhaseStatusBase(remRecord),
lookbackDays: normalizeFiniteInt(remRecord?.lookbackDays, 0),
limit: normalizeFiniteInt(remRecord?.limit, 0),
minPatternStrength: normalizeFiniteScore(remRecord?.minPatternStrength, 0),
},
},
...(phases ? { phases } : {}),
};
}