Skip to content

Commit ff1185b

Browse files
committed
fix(plugin): close 4 reported correctness issues
1. Stale dream queue entries no longer block /ctx-dream — enqueueDream() now cleans started entries older than 10 minutes before rejecting. 2. Smart-note evaluation success no longer masks failed dream tasks — hasSuccessfulTask excludes the supplementary smart-notes task. 3. Note-nudge cooldown now checks unconditionally instead of only when sticky fields are present, preventing rapid trigger bypass. 4. FTS search failures are now logged instead of silently returning [].
1 parent 2d79bb9 commit ff1185b

File tree

4 files changed

+25
-13
lines changed

4 files changed

+25
-13
lines changed

packages/plugin/src/features/magic-context/dreamer/queue.ts

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,12 +34,19 @@ export function enqueueDream(
3434
): DreamQueueEntry | null {
3535
const now = Date.now();
3636
return db.transaction(() => {
37+
// Clean stale started entries before checking — prevents post-crash permanent "already queued"
38+
const staleThresholdMs = 10 * 60 * 1000; // 10 minutes
39+
db.run(
40+
"DELETE FROM dream_queue WHERE project_path = ? AND started_at IS NOT NULL AND started_at < ?",
41+
[projectIdentity, now - staleThresholdMs],
42+
);
43+
3744
const existing = db
3845
.query<{ id: number }, [string]>("SELECT id FROM dream_queue WHERE project_path = ?")
3946
.get(projectIdentity);
4047

4148
if (existing) {
42-
return null; // already queued
49+
return null; // already queued (fresh entry)
4350
}
4451

4552
const result = db

packages/plugin/src/features/magic-context/dreamer/runner.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -271,7 +271,9 @@ export async function runDream(args: {
271271
result.finishedAt = Date.now();
272272
// Only update dream timestamps when at least one task succeeded — failed runs
273273
// should not block re-scheduling for the project.
274-
const hasSuccessfulTask = result.tasks.some((t) => !t.error);
274+
// Only count configured dream tasks for success — smart-note evaluation is supplementary
275+
// and should not mask failures of real tasks like consolidate/verify/archive-stale
276+
const hasSuccessfulTask = result.tasks.some((t) => !t.error && t.name !== "smart-notes");
275277
if (hasSuccessfulTask) {
276278
setDreamState(args.db, `last_dream_at:${args.projectIdentity}`, String(result.finishedAt));
277279
setDreamState(args.db, "last_dream_at", String(result.finishedAt));

packages/plugin/src/features/magic-context/search.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ import { embedText, isEmbeddingEnabled } from "./memory/embedding";
1515
import { sanitizeFtsQuery } from "./memory/storage-memory-fts";
1616
import { ensureMessagesIndexed } from "./message-index";
1717
import { getSessionFacts, type SessionFact } from "./storage";
18+
import { log } from "../../shared/logger";
1819

1920
type PreparedStatement = ReturnType<Database["prepare"]>;
2021

@@ -212,7 +213,8 @@ function getFtsMatches(args: {
212213
}): Memory[] {
213214
try {
214215
return searchMemoriesFTS(args.db, args.projectPath, args.query, args.limit);
215-
} catch {
216+
} catch (error) {
217+
log(`[search] FTS query failed for "${args.query}": ${error instanceof Error ? error.message : String(error)}`);
216218
return [];
217219
}
218220
}

packages/plugin/src/hooks/magic-context/note-nudger.ts

Lines changed: 11 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -94,16 +94,16 @@ export function peekNoteNudgeText(
9494
// Suppress if we delivered a nudge recently (within 15 minutes).
9595
// Prevents the same notes from being re-surfaced on every commit/todo boundary
9696
// in quick succession during active work.
97-
if (state.stickyText && state.stickyMessageId) {
98-
const deliveredAt = getPersistedNoteNudgeDeliveredAt(db, sessionId);
99-
if (deliveredAt > 0 && Date.now() - deliveredAt < NOTE_NUDGE_COOLDOWN_MS) {
100-
sessionLog(
101-
sessionId,
102-
`note-nudge: suppressing — last delivered ${Math.round((Date.now() - deliveredAt) / 1000)}s ago (cooldown ${NOTE_NUDGE_COOLDOWN_MS / 60000}m)`,
103-
);
104-
clearPersistedNoteNudge(db, sessionId);
105-
return null;
106-
}
97+
// Check unconditionally — a new trigger clears sticky fields, so gating on
98+
// stickyText presence would let triggers bypass the cooldown window.
99+
const deliveredAt = getPersistedNoteNudgeDeliveredAt(db, sessionId);
100+
if (deliveredAt > 0 && Date.now() - deliveredAt < NOTE_NUDGE_COOLDOWN_MS) {
101+
sessionLog(
102+
sessionId,
103+
`note-nudge: suppressing — last delivered ${Math.round((Date.now() - deliveredAt) / 1000)}s ago (cooldown ${NOTE_NUDGE_COOLDOWN_MS / 60000}m)`,
104+
);
105+
clearPersistedNoteNudge(db, sessionId);
106+
return null;
107107
}
108108

109109
// Check if there are actually notes to remind about
@@ -177,4 +177,5 @@ export function getNoteNudgeText(db: Database, sessionId: string): string | null
177177
*/
178178
export function clearNoteNudgeState(db: Database, sessionId: string): void {
179179
clearPersistedNoteNudge(db, sessionId);
180+
lastDeliveredAt.delete(sessionId); // also reset in-memory cooldown
180181
}

0 commit comments

Comments
 (0)