@cortexkit/pi-magic-context 0.34.0 → 0.34.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -200,6 +200,7 @@ import {
200
200
  hasMeaningfulUserText,
201
201
  hasMemoryClassifiedAtColumn,
202
202
  hasMemoryShareableColumn,
203
+ hasMuralCueColumns,
203
204
  hasPiFallbackMessageTags,
204
205
  hasPiFallbackToolOwnerTags,
205
206
  incrementHistorianFailure,
@@ -269,6 +270,7 @@ import {
269
270
  recordHistorianDrainFailure,
270
271
  recordMemoryMapping,
271
272
  recordMemoryVerifications,
273
+ recordMuralCueRejection,
272
274
  recordOverflowDetected,
273
275
  recordProtectedTailNoEligibleHead,
274
276
  recordProtectedTailPublicationFloor,
@@ -361,7 +363,7 @@ import {
361
363
  withMigrationLanguageDirective,
362
364
  withRawMessageProvider,
363
365
  withRawSessionMessageCache
364
- } from "./index-0k3j1tc3.js";
366
+ } from "./index-fw42dv6t.js";
365
367
  import {
366
368
  pushNotification
367
369
  } from "./index-hw34xmzk.js";
@@ -443,6 +445,16 @@ var CANONICAL_DREAM_TASKS = [
443
445
  "promote-primers",
444
446
  "refresh-primers"
445
447
  ];
448
+ function formatDreamTaskBacklogs(backlogs, tasks = CANONICAL_DREAM_TASKS) {
449
+ return tasks.filter((task) => backlogs[task] !== undefined).map((task) => {
450
+ const backlog = backlogs[task];
451
+ return `- ${task}: ${backlog?.pending ?? 0} pending / ${backlog?.total ?? 0} total`;
452
+ }).join(`
453
+ `);
454
+ }
455
+ function processedDreamTaskItems(startPending, endPending) {
456
+ return Math.max(0, startPending - endPending);
457
+ }
446
458
  var MEMORY_DOMAIN_TASKS = [
447
459
  "map-memories",
448
460
  "verify",
@@ -572,6 +584,15 @@ function formatFailClosedBlockingProcesses(processes) {
572
584
  }
573
585
  function formatFailClosedBlockingMessage(reason) {
574
586
  if (reason.kind === "migration_guard") {
587
+ if (reason.unreadableFile) {
588
+ const arm = reason.unreadableArm ?? "io";
589
+ const recovery = arm === "io" ? `If no OpenCode server is running, it is safe to delete ${reason.unreadableFile} and retry.` : `The file may be a recent incomplete write; retry after the file is older than the ten-minute grace window, or stop OpenCode before deleting it.`;
590
+ return [
591
+ `Magic Context cannot migrate the shared database because RPC discovery file ${reason.unreadableFile} is uncertain (${arm} arm), so the absence of a live OpenCode server cannot be proven.`,
592
+ recovery,
593
+ `Recovery: ${FAIL_CLOSED_DOCTOR_COMMAND}`
594
+ ].join(" ");
595
+ }
575
596
  return [
576
597
  `Magic Context cannot migrate the shared database because ${formatFailClosedBlockingProcesses(reason.blockingProcesses)} may be running an older Magic Context build that would fail against the migrated database.`,
577
598
  "Restart the blocking process (it will pick up the new build and migrate on start), or shut it down and retry.",
@@ -580,7 +601,7 @@ function formatFailClosedBlockingMessage(reason) {
580
601
  }
581
602
  if (reason.kind === "schema_fence") {
582
603
  return [
583
- `Magic Context cannot operate: this Magic Context build is older than the database; upgrade/restart this harness (database schema v${reason.persistedVersion}, build supports through v${reason.supportedVersion}).`,
604
+ `Magic Context cannot operate: this Magic Context build is older than the database; upgrade/restart this harness (upstream migration lane v${reason.persistedVersion}, build supports through v${reason.supportedVersion}).`,
584
605
  `Recovery: ${FAIL_CLOSED_DOCTOR_COMMAND}`
585
606
  ].join(" ");
586
607
  }
@@ -3069,7 +3090,474 @@ ${sidekickText}
3069
3090
  </sidekick-augmentation>`;
3070
3091
  pi.sendUserMessage(augmentedPrompt);
3071
3092
  }
3072
- });
3093
+ });
3094
+ }
3095
+
3096
+ // ../plugin/src/features/magic-context/smart-notes/types.ts
3097
+ var SMART_NOTE_CHECK_POLICY_VERSION = 1;
3098
+ var SMART_NOTE_CHECK_FLOOR_MS = 5 * 60 * 1000;
3099
+ var SMART_NOTE_CHECK_CEILING_MS = 24 * 60 * 60 * 1000;
3100
+ var SMART_NOTE_CHECK_DEFAULT_INTERVAL_MS = 60 * 60 * 1000;
3101
+ var SMART_NOTE_CHECK_MAX_STALENESS_MS = 7 * 24 * 60 * 60 * 1000;
3102
+ var SMART_NOTE_CHECK_LIVENESS_RECHECK_MS = 24 * 60 * 60 * 1000;
3103
+
3104
+ class SmartNoteNetworkError extends Error {
3105
+ isSmartNoteNetworkError = true;
3106
+ terminal;
3107
+ constructor(message, options = {}) {
3108
+ super(message);
3109
+ this.name = "SmartNoteNetworkError";
3110
+ this.terminal = options.terminal ?? false;
3111
+ }
3112
+ }
3113
+
3114
+ class SmartNoteSecurityError extends Error {
3115
+ isSmartNoteSecurityError = true;
3116
+ constructor(message) {
3117
+ super(message);
3118
+ this.name = "SmartNoteSecurityError";
3119
+ }
3120
+ }
3121
+ function isSmartNoteNetworkError(error) {
3122
+ return error instanceof SmartNoteNetworkError || error instanceof Error && (error.name === "SmartNoteNetworkError" || error.message.includes("SmartNoteNetworkError") || error.message.includes("SMART_NOTE_NETWORK"));
3123
+ }
3124
+ function isTerminalSmartNoteNetworkError(error) {
3125
+ return error instanceof SmartNoteNetworkError && error.terminal;
3126
+ }
3127
+ function parseSmartNoteManifest(json) {
3128
+ if (!json)
3129
+ return { capabilities: [] };
3130
+ try {
3131
+ const parsed = JSON.parse(json);
3132
+ const capabilities = Array.isArray(parsed.capabilities) ? parsed.capabilities.filter((c) => ["readFile", "gitHeadSha", "gitTag", "gitLog", "httpGet"].includes(String(c))) : [];
3133
+ return {
3134
+ capabilities,
3135
+ readFiles: stringArray(parsed.readFiles),
3136
+ hosts: stringArray(parsed.hosts),
3137
+ urls: stringArray(parsed.urls),
3138
+ signals: stringArray(parsed.signals),
3139
+ summary: typeof parsed.summary === "string" ? parsed.summary : undefined
3140
+ };
3141
+ } catch {
3142
+ return { capabilities: [] };
3143
+ }
3144
+ }
3145
+ function stringArray(value) {
3146
+ if (!Array.isArray(value))
3147
+ return;
3148
+ const arr = value.filter((item) => typeof item === "string");
3149
+ return arr.length > 0 ? arr : undefined;
3150
+ }
3151
+
3152
+ // ../plugin/src/features/magic-context/smart-notes/storage.ts
3153
+ function toSmartNote(note) {
3154
+ return {
3155
+ ...note,
3156
+ checkStatus: note.checkStatus ?? "uncompiled",
3157
+ checkFailureCount: note.checkFailureCount ?? 0,
3158
+ checkNetworkFailureCount: note.checkNetworkFailureCount ?? 0,
3159
+ policyVersion: note.policyVersion ?? 0
3160
+ };
3161
+ }
3162
+ function commitSmartNoteState(db, args) {
3163
+ db.exec("BEGIN IMMEDIATE");
3164
+ let leaseLost = false;
3165
+ let committed = false;
3166
+ try {
3167
+ if (args.leaseHeld && !args.leaseHeld()) {
3168
+ leaseLost = true;
3169
+ } else if (claimExpectedState(db, args.expected)) {
3170
+ args.write();
3171
+ committed = true;
3172
+ }
3173
+ db.exec("COMMIT");
3174
+ } catch (error) {
3175
+ try {
3176
+ db.exec("ROLLBACK");
3177
+ } catch {}
3178
+ throw error;
3179
+ }
3180
+ if (leaseLost) {
3181
+ throw new Error(`Dream lease lost during smart-note ${args.phase} commit`);
3182
+ }
3183
+ if (!committed) {
3184
+ log(`[debug] smart note #${args.expected.noteId}: discarded stale ${args.phase} result`);
3185
+ }
3186
+ return committed;
3187
+ }
3188
+ function claimExpectedState(db, expected) {
3189
+ if (expected.kind === "compiled-check") {
3190
+ return db.prepare(`UPDATE notes SET id = id
3191
+ WHERE id = ? AND type = 'smart' AND status = 'pending'
3192
+ AND check_status = 'compiled' AND compiled_check = ?
3193
+ AND check_hash IS ? AND check_compiled_at IS ?`).run(expected.noteId, expected.compiledCheck, expected.checkHash, expected.checkCompiledAt).changes > 0;
3194
+ }
3195
+ const statusClause = expected.checkStatus ? " AND check_status = ?" : "";
3196
+ const params = [
3197
+ expected.noteId,
3198
+ expected.content,
3199
+ expected.surfaceCondition,
3200
+ expected.updatedAt
3201
+ ];
3202
+ if (expected.checkStatus)
3203
+ params.push(expected.checkStatus);
3204
+ return db.prepare(`UPDATE notes SET id = id
3205
+ WHERE id = ? AND type = 'smart' AND status = 'pending'
3206
+ AND content = ? AND surface_condition IS ? AND updated_at = ?${statusClause}`).run(...params).changes > 0;
3207
+ }
3208
+ function getDueCompiledSmartNoteChecks(db, projectPath, now, limit) {
3209
+ return getPendingSmartNotes(db, projectPath).map(toSmartNote).filter((note) => note.checkStatus === "compiled" && note.compiledCheck !== null && note.policyVersion === SMART_NOTE_CHECK_POLICY_VERSION && (note.checkQuarantinedUntil === null || note.checkQuarantinedUntil <= now) && (note.checkNextDueAt === null || note.checkNextDueAt <= now)).sort((a, b) => (a.checkNextDueAt ?? 0) - (b.checkNextDueAt ?? 0) || a.id - b.id).slice(0, Math.max(1, limit));
3210
+ }
3211
+ function getSmartNotesNeedingCompilation(db, projectPath, now, limit) {
3212
+ return getPendingSmartNotes(db, projectPath).map(toSmartNote).filter((note) => (note.checkNextDueAt === null || note.checkNextDueAt <= now) && (note.checkStatus === "uncompiled" || note.checkStatus === "failing" || note.compiledCheck === null || note.policyVersion !== SMART_NOTE_CHECK_POLICY_VERSION)).sort((a, b) => a.createdAt - b.createdAt || a.id - b.id).slice(0, Math.max(1, limit));
3213
+ }
3214
+ function getStaleCompiledSmartNotes(db, projectPath, now, limit) {
3215
+ const staleBefore = now - SMART_NOTE_CHECK_MAX_STALENESS_MS;
3216
+ const livenessBefore = now - SMART_NOTE_CHECK_LIVENESS_RECHECK_MS;
3217
+ return getPendingSmartNotes(db, projectPath).map(toSmartNote).filter((note) => note.checkStatus === "compiled" && note.compiledCheck !== null && note.policyVersion === SMART_NOTE_CHECK_POLICY_VERSION && note.checkFalseSinceAt !== null && note.checkFalseSinceAt <= staleBefore && (note.checkLastLivenessAt === null || note.checkLastLivenessAt <= livenessBefore)).sort((a, b) => (a.checkFalseSinceAt ?? 0) - (b.checkFalseSinceAt ?? 0) || a.id - b.id).slice(0, Math.max(1, limit));
3218
+ }
3219
+ function storeCompiledSmartNoteCheck(db, args) {
3220
+ db.prepare(`UPDATE notes
3221
+ SET compiled_check = ?,
3222
+ manifest_json = ?,
3223
+ check_hash = ?,
3224
+ check_cron = ?,
3225
+ check_version = 1,
3226
+ check_status = 'compiled',
3227
+ check_failure_count = 0,
3228
+ check_network_failure_count = 0,
3229
+ check_quarantined_until = NULL,
3230
+ check_next_due_at = ?,
3231
+ check_compiled_at = ?,
3232
+ check_false_since_at = COALESCE(check_false_since_at, ?),
3233
+ check_last_liveness_at = NULL,
3234
+ policy_version = ?,
3235
+ updated_at = ?
3236
+ WHERE id = ? AND type = 'smart'`).run(args.compiledCheck, JSON.stringify(args.manifest), args.checkHash, args.checkCron, args.nextDueAt, args.now, args.now, SMART_NOTE_CHECK_POLICY_VERSION, args.now, args.noteId);
3237
+ }
3238
+ function markCompiledCheckFalse(db, noteId, nextDueAt, now) {
3239
+ db.prepare(`UPDATE notes
3240
+ SET last_checked_at = ?,
3241
+ updated_at = ?,
3242
+ check_next_due_at = ?,
3243
+ check_failure_count = 0,
3244
+ check_network_failure_count = 0,
3245
+ check_false_since_at = COALESCE(check_false_since_at, ?)
3246
+ WHERE id = ? AND type = 'smart'`).run(now, now, nextDueAt, now, noteId);
3247
+ }
3248
+ function markCompiledCheckLogicFailure(db, noteId, now, maxFailures) {
3249
+ const failureCount = readFailureCount(db, noteId, "check_failure_count") + 1;
3250
+ const status = failureCount >= maxFailures ? "failing" : "compiled";
3251
+ db.prepare(`UPDATE notes
3252
+ SET check_failure_count = ?,
3253
+ check_status = ?,
3254
+ check_next_due_at = ?,
3255
+ updated_at = ?
3256
+ WHERE id = ? AND type = 'smart'`).run(failureCount, status, now + backoffMs(failureCount), now, noteId);
3257
+ }
3258
+ function markCompiledCheckNetworkFailure(db, noteId, now, maxFailures) {
3259
+ const failureCount = readFailureCount(db, noteId, "check_network_failure_count") + 1;
3260
+ const quarantinedUntil = now + backoffMs(failureCount);
3261
+ const status = failureCount >= maxFailures ? "failing" : "compiled";
3262
+ db.prepare(`UPDATE notes
3263
+ SET check_network_failure_count = ?,
3264
+ check_status = ?,
3265
+ check_next_due_at = ?,
3266
+ check_quarantined_until = ?,
3267
+ updated_at = ?
3268
+ WHERE id = ? AND type = 'smart'`).run(failureCount, status, quarantinedUntil, quarantinedUntil, now, noteId);
3269
+ }
3270
+ function markSmartNoteLivenessChecked(db, noteId, now) {
3271
+ db.prepare(`UPDATE notes
3272
+ SET check_last_liveness_at = ?, updated_at = ?
3273
+ WHERE id = ? AND type = 'smart'`).run(now, now, noteId);
3274
+ }
3275
+ function markSmartNoteCheckStatus(db, noteId, status, now) {
3276
+ db.prepare(`UPDATE notes SET check_status = ?, updated_at = ? WHERE id = ? AND type = 'smart'`).run(status, now, noteId);
3277
+ }
3278
+ function markSmartNoteCompilationFailure(db, noteId, now, maxFailures) {
3279
+ const failureCount = readFailureCount(db, noteId, "check_failure_count") + 1;
3280
+ const status = failureCount >= maxFailures ? "fallback" : "uncompiled";
3281
+ db.prepare(`UPDATE notes
3282
+ SET check_failure_count = ?,
3283
+ check_status = ?,
3284
+ check_next_due_at = ?,
3285
+ updated_at = ?
3286
+ WHERE id = ? AND type = 'smart'`).run(failureCount, status, now + backoffMs(failureCount), now, noteId);
3287
+ }
3288
+ function readFailureCount(db, noteId, column) {
3289
+ if (column !== "check_failure_count" && column !== "check_network_failure_count")
3290
+ return 0;
3291
+ const row = db.prepare(`SELECT ${column} AS count FROM notes WHERE id = ?`).get(noteId);
3292
+ return row?.count ?? 0;
3293
+ }
3294
+ function backoffMs(failureCount) {
3295
+ const minutes = Math.min(24 * 60, 5 * 2 ** Math.max(0, failureCount - 1));
3296
+ return minutes * 60 * 1000;
3297
+ }
3298
+
3299
+ // ../plugin/src/features/magic-context/dreamer/storage-task-schedule.ts
3300
+ function toRow(r) {
3301
+ return {
3302
+ projectPath: r.project_path,
3303
+ task: r.task,
3304
+ lastRunAt: r.last_run_at,
3305
+ nextDueAt: r.next_due_at,
3306
+ schedule: r.schedule ?? null,
3307
+ lastStatus: r.last_status ?? null,
3308
+ lastError: r.last_error,
3309
+ retryCount: r.retry_count ?? 0,
3310
+ lastCheckedCommit: r.last_checked_commit ?? null,
3311
+ lastBroadRunAt: r.last_broad_run_at ?? null,
3312
+ retrospectiveWatermarkMs: r.retrospective_watermark_ms ?? null
3313
+ };
3314
+ }
3315
+ var SELECT_COLUMNS = "project_path, task, last_run_at, next_due_at, schedule, last_status, last_error, retry_count, last_checked_commit, last_broad_run_at, retrospective_watermark_ms";
3316
+ function getTaskScheduleState(db, projectPath, task) {
3317
+ const row = db.prepare(`SELECT ${SELECT_COLUMNS} FROM task_schedule_state WHERE project_path = ? AND task = ?`).get(projectPath, task);
3318
+ return row ? toRow(row) : null;
3319
+ }
3320
+ function getMostRecentTaskRunAt(db, projectPath) {
3321
+ const row = db.prepare("SELECT MAX(last_run_at) AS max_at FROM task_schedule_state WHERE project_path = ?").get(projectPath);
3322
+ const value = row?.max_at ?? null;
3323
+ return typeof value === "number" && value > 0 ? value : null;
3324
+ }
3325
+ function pruneNonCanonicalTaskRows(db, projectPath, canonicalTasks) {
3326
+ if (canonicalTasks.length === 0)
3327
+ return 0;
3328
+ const placeholders = canonicalTasks.map(() => "?").join(", ");
3329
+ const result = db.prepare(`DELETE FROM task_schedule_state WHERE project_path = ? AND task NOT IN (${placeholders})`).run(projectPath, ...canonicalTasks);
3330
+ return Number(result.changes ?? 0);
3331
+ }
3332
+ function deleteTaskScheduleRowsForProject(db, projectPath) {
3333
+ const result = db.prepare("DELETE FROM task_schedule_state WHERE project_path = ?").run(projectPath);
3334
+ return Number(result.changes ?? 0);
3335
+ }
3336
+ function seedTaskScheduleState(db, projectPath, task, nextDueAt, lastRunAt, schedule) {
3337
+ db.prepare("INSERT INTO task_schedule_state (project_path, task, last_run_at, next_due_at, schedule, last_status, last_error, retry_count) VALUES (?, ?, ?, ?, ?, NULL, NULL, 0) ON CONFLICT(project_path, task) DO NOTHING").run(projectPath, task, lastRunAt, nextDueAt, schedule);
3338
+ }
3339
+ function writeTaskScheduleState(db, row) {
3340
+ const broadCycleUpdate = row.lastBroadRunAt === undefined ? "last_broad_run_at = task_schedule_state.last_broad_run_at" : "last_broad_run_at = excluded.last_broad_run_at";
3341
+ db.prepare(`INSERT INTO task_schedule_state
3342
+ (project_path, task, last_run_at, next_due_at, schedule, last_status, last_error, retry_count, last_checked_commit, last_broad_run_at, retrospective_watermark_ms)
3343
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
3344
+ ON CONFLICT(project_path, task) DO UPDATE SET
3345
+ last_run_at = excluded.last_run_at,
3346
+ next_due_at = excluded.next_due_at,
3347
+ schedule = excluded.schedule,
3348
+ last_status = excluded.last_status,
3349
+ last_error = excluded.last_error,
3350
+ retry_count = excluded.retry_count,
3351
+ last_checked_commit = COALESCE(excluded.last_checked_commit, task_schedule_state.last_checked_commit),
3352
+ ${broadCycleUpdate},
3353
+ retrospective_watermark_ms = COALESCE(excluded.retrospective_watermark_ms, task_schedule_state.retrospective_watermark_ms)`).run(row.projectPath, row.task, row.lastRunAt, row.nextDueAt, row.schedule, row.lastStatus, row.lastError, row.retryCount, row.lastCheckedCommit ?? null, row.lastBroadRunAt ?? null, row.retrospectiveWatermarkMs ?? null);
3354
+ }
3355
+ function isRetrospectiveWindowProcessed(db, projectPath, windowKey) {
3356
+ const row = db.prepare("SELECT 1 AS one FROM retrospective_processed_windows WHERE project_path = ? AND window_key = ?").get(projectPath, windowKey);
3357
+ return row != null;
3358
+ }
3359
+ function recordRetrospectiveWindowProcessed(db, projectPath, windowKey) {
3360
+ db.prepare("INSERT INTO retrospective_processed_windows (project_path, window_key, processed_at) VALUES (?, ?, ?) ON CONFLICT(project_path, window_key) DO NOTHING").run(projectPath, windowKey, Date.now());
3361
+ }
3362
+
3363
+ // ../plugin/src/features/magic-context/dreamer/task-gates.ts
3364
+ function countActiveMemories(db, projectPath) {
3365
+ const row = db.prepare("SELECT COUNT(*) AS cnt FROM memories WHERE project_path = ? AND status IN ('active','permanent')").get(projectPath);
3366
+ return row?.cnt ?? 0;
3367
+ }
3368
+ function countUnmappedActiveMemories(db, projectPath) {
3369
+ const row = db.prepare(`SELECT COUNT(*) AS cnt
3370
+ FROM memories m
3371
+ WHERE m.project_path = ?
3372
+ AND m.status IN ('active','permanent')
3373
+ AND NOT EXISTS (
3374
+ SELECT 1 FROM memory_verifications v WHERE v.memory_id = m.id
3375
+ )`).get(projectPath);
3376
+ return row?.cnt ?? 0;
3377
+ }
3378
+ function countCompartmentsSince(db, projectPath, since) {
3379
+ const row = db.prepare(`SELECT COUNT(*) AS cnt
3380
+ FROM compartments c
3381
+ JOIN session_projects sp ON sp.session_id = c.session_id
3382
+ WHERE sp.project_path = ? AND c.created_at > ?`).get(projectPath, since);
3383
+ return row?.cnt ?? 0;
3384
+ }
3385
+ function countProjectSessionsSince(db, projectPath, since) {
3386
+ const row = since === null ? db.prepare("SELECT COUNT(*) AS cnt FROM session_projects WHERE project_path = ?").get(projectPath) : db.prepare("SELECT COUNT(*) AS cnt FROM session_projects WHERE project_path = ? AND updated_at > ?").get(projectPath, since);
3387
+ return row?.cnt ?? 0;
3388
+ }
3389
+ function countMappedMemories(db, projectPath) {
3390
+ const row = db.prepare(`SELECT COUNT(DISTINCT m.id) AS cnt
3391
+ FROM memories m
3392
+ JOIN memory_verifications v ON v.memory_id = m.id
3393
+ WHERE m.project_path = ?
3394
+ AND m.status IN ('active','permanent')
3395
+ AND v.file_path <> ''`).get(projectPath);
3396
+ return row?.cnt ?? 0;
3397
+ }
3398
+ function countUnverifiedMappedMemories(db, projectPath) {
3399
+ const row = db.prepare(`SELECT COUNT(DISTINCT m.id) AS cnt
3400
+ FROM memories m
3401
+ JOIN memory_verifications v ON v.memory_id = m.id
3402
+ WHERE m.project_path = ?
3403
+ AND m.status IN ('active','permanent')
3404
+ AND v.file_path <> ''
3405
+ AND v.verified_at = 0`).get(projectPath);
3406
+ return row?.cnt ?? 0;
3407
+ }
3408
+ function countBroadCycleCandidates(db, projectPath, cycleStartAt) {
3409
+ const row = db.prepare(`SELECT COUNT(*) AS cnt
3410
+ FROM memories m
3411
+ WHERE m.project_path = ?
3412
+ AND m.status IN ('active','permanent')
3413
+ AND (
3414
+ SELECT MAX(v.verified_at)
3415
+ FROM memory_verifications v
3416
+ WHERE v.memory_id = m.id
3417
+ AND v.file_path <> ''
3418
+ ) < ?`).get(projectPath, cycleStartAt);
3419
+ return row?.cnt ?? 0;
3420
+ }
3421
+ function countCueCandidates(db, projectPath) {
3422
+ if (!hasMuralCueColumns(db))
3423
+ return countActiveMemories(db, projectPath);
3424
+ const row = db.prepare(`SELECT COUNT(*) AS cnt
3425
+ FROM memories
3426
+ WHERE project_path = ?
3427
+ AND status IN ('active','permanent')
3428
+ AND (mural_cue IS NULL OR mural_cue_hash IS NULL OR updated_at > mural_cue_at)`).get(projectPath);
3429
+ return row?.cnt ?? 0;
3430
+ }
3431
+ function countStalePrimers(db, projectPath) {
3432
+ const row = db.prepare(`SELECT COUNT(*) AS cnt
3433
+ FROM primers
3434
+ WHERE project_path = ?
3435
+ AND status = 'active'
3436
+ AND (answer IS NULL OR TRIM(answer) = '' OR answer_refreshed_at IS NULL
3437
+ OR last_observed_at > answer_refreshed_at)`).get(projectPath);
3438
+ return row?.cnt ?? 0;
3439
+ }
3440
+ function countUnclassifiedActiveMemories(db, projectPath) {
3441
+ if (!hasMemoryClassifiedAtColumn(db))
3442
+ return countActiveMemories(db, projectPath);
3443
+ const row = db.prepare(`SELECT COUNT(*) AS cnt
3444
+ FROM memories
3445
+ WHERE project_path = ?
3446
+ AND status IN ('active','permanent')
3447
+ AND classified_at IS NULL`).get(projectPath);
3448
+ return row?.cnt ?? 0;
3449
+ }
3450
+ function countPendingSmartNotes(db, projectPath) {
3451
+ const row = db.prepare("SELECT COUNT(*) AS cnt FROM notes WHERE project_path = ? AND type = 'smart' AND status = 'pending'").get(projectPath);
3452
+ return row?.cnt ?? 0;
3453
+ }
3454
+ function countUserMemoryCandidates(db) {
3455
+ const row = db.prepare("SELECT COUNT(*) AS cnt FROM user_memory_candidates").get();
3456
+ return row?.cnt ?? 0;
3457
+ }
3458
+ function countActivePrimers(db, projectPath) {
3459
+ const row = db.prepare("SELECT COUNT(*) AS cnt FROM primers WHERE project_path = ? AND status = 'active'").get(projectPath);
3460
+ return row?.cnt ?? 0;
3461
+ }
3462
+ function getDreamTaskBacklog(db, projectPath, task, options = {}) {
3463
+ switch (task) {
3464
+ case "map-memories": {
3465
+ const total = countActiveMemories(db, projectPath);
3466
+ return { pending: countUnmappedActiveMemories(db, projectPath), total };
3467
+ }
3468
+ case "verify": {
3469
+ return {
3470
+ pending: countUnverifiedMappedMemories(db, projectPath),
3471
+ total: countMappedMemories(db, projectPath)
3472
+ };
3473
+ }
3474
+ case "verify-broad": {
3475
+ const total = countMappedMemories(db, projectPath);
3476
+ const cycleStartAt = getTaskScheduleState(db, projectPath, "verify-broad")?.lastBroadRunAt;
3477
+ const pending = cycleStartAt == null ? total : countBroadCycleCandidates(db, projectPath, cycleStartAt);
3478
+ return { pending, total };
3479
+ }
3480
+ case "curate": {
3481
+ const total = countActiveMemories(db, projectPath);
3482
+ return { pending: total, total };
3483
+ }
3484
+ case "compress-cues": {
3485
+ const total = countActiveMemories(db, projectPath);
3486
+ return { pending: countCueCandidates(db, projectPath), total };
3487
+ }
3488
+ case "classify-memories": {
3489
+ const total = countActiveMemories(db, projectPath);
3490
+ return { pending: countUnclassifiedActiveMemories(db, projectPath), total };
3491
+ }
3492
+ case "retrospective": {
3493
+ const pending = countProjectSessionsSince(db, projectPath, options.retrospectiveWatermarkMs ?? null);
3494
+ return { pending, total: pending };
3495
+ }
3496
+ case "maintain-docs": {
3497
+ const total = countCompartmentsSince(db, projectPath, 0);
3498
+ const pending = countCompartmentsSince(db, projectPath, options.lastRunAt ?? 0);
3499
+ return { pending, total };
3500
+ }
3501
+ case "evaluate-smart-notes": {
3502
+ const pending = countPendingSmartNotes(db, projectPath);
3503
+ return { pending, total: pending };
3504
+ }
3505
+ case "review-user-memories": {
3506
+ const pending = countUserMemoryCandidates(db);
3507
+ return { pending, total: pending };
3508
+ }
3509
+ case "promote-primers": {
3510
+ const pending = countPrimerCandidatesForProject(db, projectPath);
3511
+ return { pending, total: pending };
3512
+ }
3513
+ case "refresh-primers": {
3514
+ const total = countActivePrimers(db, projectPath);
3515
+ return { pending: countStalePrimers(db, projectPath), total };
3516
+ }
3517
+ default: {
3518
+ const _exhaustive = task;
3519
+ return _exhaustive;
3520
+ }
3521
+ }
3522
+ }
3523
+ function getDreamTaskBacklogs(db, projectPath, tasks = CANONICAL_DREAM_TASKS, options = {}) {
3524
+ const result = {};
3525
+ for (const task of tasks)
3526
+ result[task] = getDreamTaskBacklog(db, projectPath, task, options);
3527
+ return result;
3528
+ }
3529
+ function evaluateTaskGate(task, ctx) {
3530
+ const { db, projectIdentity: project, lastRunAt } = ctx;
3531
+ switch (task) {
3532
+ case "map-memories":
3533
+ return countUnmappedActiveMemories(db, project) > 0;
3534
+ case "verify":
3535
+ return countActiveMemories(db, project) > 0;
3536
+ case "verify-broad":
3537
+ return getTaskScheduleState(db, project, "verify-broad")?.lastBroadRunAt != null || countActiveMemories(db, project) > 0;
3538
+ case "curate":
3539
+ return countActiveMemories(db, project) > 0;
3540
+ case "compress-cues":
3541
+ return countActiveMemories(db, project) > 0;
3542
+ case "classify-memories":
3543
+ return countActiveMemories(db, project) > 0;
3544
+ case "retrospective":
3545
+ return countProjectSessionsSince(db, project, ctx.retrospectiveWatermarkMs ?? null) > 0;
3546
+ case "maintain-docs":
3547
+ return countCompartmentsSince(db, project, lastRunAt ?? 0) > 0;
3548
+ case "evaluate-smart-notes":
3549
+ return getSmartNotesNeedingCompilation(db, project, Date.now(), 1).length > 0 || getStaleCompiledSmartNotes(db, project, Date.now(), 1).length > 0 || getPendingSmartNotes(db, project).some((note) => note.checkStatus === "fallback");
3550
+ case "review-user-memories":
3551
+ return getUserMemoryCandidates(db).length >= ctx.promotionThreshold;
3552
+ case "promote-primers":
3553
+ return countPrimerCandidatesForProject(db, project) >= (ctx.promotionThreshold ?? 2);
3554
+ case "refresh-primers":
3555
+ return getActivePrimers(db, project).some((primer) => !primer.answer.trim() || primer.answerRefreshedAt == null || (primer.lastObservedAt ?? 0) > primer.answerRefreshedAt);
3556
+ default: {
3557
+ const _exhaustive = task;
3558
+ return Boolean(_exhaustive);
3559
+ }
3560
+ }
3073
3561
  }
3074
3562
 
3075
3563
  // ../plugin/src/features/magic-context/dreamer/open-opencode-db.ts
@@ -3971,7 +4459,7 @@ function hasBalancedParentheses(cue) {
3971
4459
  }
3972
4460
  return depth === 0;
3973
4461
  }
3974
- function validateCue(cue, importance) {
4462
+ function validateCue(cue, importance, ownId) {
3975
4463
  const trimmed = cue.trim();
3976
4464
  if (trimmed.length === 0)
3977
4465
  return { reason: "empty" };
@@ -3979,8 +4467,9 @@ function validateCue(cue, importance) {
3979
4467
  const length = [...trimmed].length;
3980
4468
  if (length > budget)
3981
4469
  return { reason: `over-budget ${length}>${budget}` };
3982
- if (/#\d+/.test(trimmed))
4470
+ if (ownId !== undefined && new RegExp(`#${ownId}\\b`).test(trimmed)) {
3983
4471
  return { reason: "leaked-id" };
4472
+ }
3984
4473
  if (!hasBalancedParentheses(trimmed))
3985
4474
  return { reason: "unbalanced-parens" };
3986
4475
  const markers = trimmed.split("⊘").length - 1;
@@ -3996,6 +4485,7 @@ function validateCue(cue, importance) {
3996
4485
  // ../plugin/src/features/magic-context/mural/compress-cues.ts
3997
4486
  var COMPRESS_CUES_CHUNK_SIZE = 40;
3998
4487
  var CHUNK_TIMEOUT_FLOOR_MS = 240000;
4488
+ var CUE_REJECTION_LATCH_THRESHOLD = 3;
3999
4489
  var CONSECUTIVE_TIMEOUT_LIMIT = 2;
4000
4490
  function toPromptMemory(candidate) {
4001
4491
  const memory = candidate.memory;
@@ -4006,6 +4496,38 @@ function toPromptMemory(candidate) {
4006
4496
  content: memory.content
4007
4497
  };
4008
4498
  }
4499
+ function stripOwnIdToken(value, ownId) {
4500
+ return value.replace(new RegExp(`#${ownId}\\b`, "g"), "");
4501
+ }
4502
+ function truncateCue(value, budget) {
4503
+ const trimmed = value.trim();
4504
+ const codepoints = [...trimmed];
4505
+ if (codepoints.length <= budget)
4506
+ return trimmed;
4507
+ const prefix = codepoints.slice(0, budget).join("");
4508
+ const boundary = prefix.search(/\s+\S*$/);
4509
+ return (boundary > 0 ? prefix.slice(0, boundary) : prefix).trim();
4510
+ }
4511
+ function sanitizeCue(value, candidate) {
4512
+ return truncateCue(stripOwnIdToken(value, candidate.memory.id), cueBudgetFor(candidate.memory.importance ?? 50));
4513
+ }
4514
+ function deterministicFallbackCue(candidate, lastCandidate) {
4515
+ const importance = candidate.memory.importance ?? 50;
4516
+ const budget = cueBudgetFor(importance);
4517
+ const sanitizedCandidate = sanitizeCue(lastCandidate, candidate);
4518
+ if (validateCue(sanitizedCandidate, importance, candidate.memory.id) === null) {
4519
+ return sanitizedCandidate;
4520
+ }
4521
+ const sourceSlice = sanitizeCue(candidate.memory.content, candidate);
4522
+ if (validateCue(sourceSlice, importance, candidate.memory.id) === null) {
4523
+ return sourceSlice;
4524
+ }
4525
+ const grammarSafe = truncateCue(sourceSlice.replaceAll("⊘", "").replace(/[()]/g, "").replace(/\b(?:must not|never|without|instead of|exclude|excludes)\b/gi, "").replace(/\s+/g, " "), budget);
4526
+ if (validateCue(grammarSafe, importance, candidate.memory.id) === null) {
4527
+ return grammarSafe;
4528
+ }
4529
+ return "memory";
4530
+ }
4009
4531
  function selectCandidates(db, projectIdentity) {
4010
4532
  const memories = getMemoriesByProject(db, projectIdentity, ["active", "permanent"]);
4011
4533
  const cueState = getMuralCueState(db, memories.map((memory) => memory.id));
@@ -4059,6 +4581,7 @@ async function runCompressCues(args) {
4059
4581
  result.skipped += outcome.skipped;
4060
4582
  result.remaining -= outcome.compressed;
4061
4583
  result.chunks += 1;
4584
+ args.onProgress?.(result.compressed + result.skipped);
4062
4585
  if (outcome.failure?.class === "timeout") {
4063
4586
  consecutiveTimeouts += 1;
4064
4587
  timeoutStreakElapsedMs.push(outcome.failure.elapsedMs);
@@ -4173,10 +4696,19 @@ function applyCues(args, chunk, manifestText) {
4173
4696
  const candidate = byId.get(entry.id);
4174
4697
  if (!candidate)
4175
4698
  throw new Error(`cues manifest contains unknown id ${entry.id}`);
4176
- const failure = validateCue(entry.cue, candidate.memory.importance ?? 50);
4699
+ const importance = candidate.memory.importance ?? 50;
4700
+ const failure = validateCue(entry.cue, importance, candidate.memory.id);
4177
4701
  if (failure) {
4702
+ const rejectionCount = recordMuralCueRejection(args.db, args.projectIdentity, entry.id, candidate.contentHash);
4703
+ if (rejectionCount >= CUE_REJECTION_LATCH_THRESHOLD) {
4704
+ const fallback = deterministicFallbackCue(candidate, entry.cue);
4705
+ setMuralCue(args.db, args.projectIdentity, entry.id, fallback, candidate.contentHash);
4706
+ compressed += 1;
4707
+ log(`[dreamer] compress-cues: fallback cue for memory ${entry.id} (${failure.reason}; ${rejectionCount} rejections; fallback)`);
4708
+ continue;
4709
+ }
4178
4710
  skipped += 1;
4179
- log(`[dreamer] compress-cues: skipped cue for memory ${entry.id} (${failure.reason})`);
4711
+ log(`[dreamer] compress-cues: skipped cue for memory ${entry.id} (${failure.reason}; rejection ${rejectionCount}/${CUE_REJECTION_LATCH_THRESHOLD})`);
4180
4712
  continue;
4181
4713
  }
4182
4714
  setMuralCue(args.db, args.projectIdentity, entry.id, entry.cue.trim(), candidate.contentHash);
@@ -5154,6 +5686,7 @@ async function runClassify(args) {
5154
5686
  result.changed += counts.changed;
5155
5687
  result.remaining -= counts.classified;
5156
5688
  result.chunks += 1;
5689
+ args.onProgress?.(result.classified);
5157
5690
  }
5158
5691
  result.complete = result.remaining === 0;
5159
5692
  log(`[dreamer] classify: stage=${stage} classified=${result.classified} changed=${result.changed} chunks=${result.chunks} remaining=${result.remaining} complete=${result.complete}`);
@@ -5414,64 +5947,6 @@ import { lookup as dnsLookup } from "node:dns/promises";
5414
5947
  import * as https from "node:https";
5415
5948
  import { isIP } from "node:net";
5416
5949
  import { domainToASCII } from "node:url";
5417
-
5418
- // ../plugin/src/features/magic-context/smart-notes/types.ts
5419
- var SMART_NOTE_CHECK_POLICY_VERSION = 1;
5420
- var SMART_NOTE_CHECK_FLOOR_MS = 5 * 60 * 1000;
5421
- var SMART_NOTE_CHECK_CEILING_MS = 24 * 60 * 60 * 1000;
5422
- var SMART_NOTE_CHECK_DEFAULT_INTERVAL_MS = 60 * 60 * 1000;
5423
- var SMART_NOTE_CHECK_MAX_STALENESS_MS = 7 * 24 * 60 * 60 * 1000;
5424
- var SMART_NOTE_CHECK_LIVENESS_RECHECK_MS = 24 * 60 * 60 * 1000;
5425
-
5426
- class SmartNoteNetworkError extends Error {
5427
- isSmartNoteNetworkError = true;
5428
- terminal;
5429
- constructor(message, options = {}) {
5430
- super(message);
5431
- this.name = "SmartNoteNetworkError";
5432
- this.terminal = options.terminal ?? false;
5433
- }
5434
- }
5435
-
5436
- class SmartNoteSecurityError extends Error {
5437
- isSmartNoteSecurityError = true;
5438
- constructor(message) {
5439
- super(message);
5440
- this.name = "SmartNoteSecurityError";
5441
- }
5442
- }
5443
- function isSmartNoteNetworkError(error) {
5444
- return error instanceof SmartNoteNetworkError || error instanceof Error && (error.name === "SmartNoteNetworkError" || error.message.includes("SmartNoteNetworkError") || error.message.includes("SMART_NOTE_NETWORK"));
5445
- }
5446
- function isTerminalSmartNoteNetworkError(error) {
5447
- return error instanceof SmartNoteNetworkError && error.terminal;
5448
- }
5449
- function parseSmartNoteManifest(json) {
5450
- if (!json)
5451
- return { capabilities: [] };
5452
- try {
5453
- const parsed = JSON.parse(json);
5454
- const capabilities = Array.isArray(parsed.capabilities) ? parsed.capabilities.filter((c) => ["readFile", "gitHeadSha", "gitTag", "gitLog", "httpGet"].includes(String(c))) : [];
5455
- return {
5456
- capabilities,
5457
- readFiles: stringArray(parsed.readFiles),
5458
- hosts: stringArray(parsed.hosts),
5459
- urls: stringArray(parsed.urls),
5460
- signals: stringArray(parsed.signals),
5461
- summary: typeof parsed.summary === "string" ? parsed.summary : undefined
5462
- };
5463
- } catch {
5464
- return { capabilities: [] };
5465
- }
5466
- }
5467
- function stringArray(value) {
5468
- if (!Array.isArray(value))
5469
- return;
5470
- const arr = value.filter((item) => typeof item === "string");
5471
- return arr.length > 0 ? arr : undefined;
5472
- }
5473
-
5474
- // ../plugin/src/features/magic-context/smart-notes/ssrf-guard.ts
5475
5950
  var DNS_TIMEOUT_MS = 3000;
5476
5951
  var DEFAULT_HTTP_TIMEOUT_MS = 5000;
5477
5952
  var DEFAULT_HTTP_BODY_LIMIT_BYTES = 64 * 1024;
@@ -6403,205 +6878,58 @@ function literalCalls(code, method) {
6403
6878
  const values = [];
6404
6879
  for (const match of code.matchAll(regex)) {
6405
6880
  values.push(match[2].replace(/\\([\\"'])/g, "$1"));
6406
- }
6407
- return values;
6408
- }
6409
- function normalizeCron(cron) {
6410
- if (cron.length > MAX_CRON_CHARS)
6411
- throw new Error("check_cron exceeds 256 characters");
6412
- const normalized = cron.trim() || "0 * * * *";
6413
- const parsed = parseCron(normalized);
6414
- if (!parsed.ok)
6415
- throw new Error(`invalid check_cron: ${parsed.error}`);
6416
- const next = nextOccurrence(parsed.cron, new Date, undefined, SMART_NOTE_CHECK_CEILING_MS);
6417
- if (!next)
6418
- throw new Error("check_cron has no occurrence within the scheduling ceiling");
6419
- return normalized;
6420
- }
6421
- function unique(items) {
6422
- return [...new Set(items)];
6423
- }
6424
- function uniqueStrings(items, normalize = (value) => value) {
6425
- if (!Array.isArray(items))
6426
- return;
6427
- const values = unique(items.slice(0, MAX_MANIFEST_ENTRIES).filter((item) => typeof item === "string" && item.length > 0).map(normalize));
6428
- return values.length > 0 ? values : undefined;
6429
- }
6430
- function boundedError(error) {
6431
- return error.slice(0, MAX_COMPILER_ERROR_CHARS);
6432
- }
6433
-
6434
- // ../plugin/src/features/magic-context/smart-notes/schedule.ts
6435
- function nextSmartNoteCheckDueAt(cron, options = {}) {
6436
- const now = options.now ?? Date.now();
6437
- const floorMs = options.floorMs ?? SMART_NOTE_CHECK_FLOOR_MS;
6438
- const ceilingMs = options.ceilingMs ?? SMART_NOTE_CHECK_CEILING_MS;
6439
- const rawNext = cron?.trim() ? nextDueAtMs(cron, now, undefined, ceilingMs) : null;
6440
- const rawDelta = rawNext ? rawNext - now : SMART_NOTE_CHECK_DEFAULT_INTERVAL_MS;
6441
- const clamped = Math.min(ceilingMs, Math.max(floorMs, rawDelta));
6442
- const jittered = clamped + deterministicJitterMs(clamped, options.noteId, options.hash);
6443
- const bounded = Math.min(ceilingMs, Math.max(floorMs, jittered));
6444
- return now + bounded;
6445
- }
6446
- function deterministicJitterMs(intervalMs, noteId, hash) {
6447
- const max = Math.min(60000, Math.floor(intervalMs * 0.1));
6448
- if (max <= 0)
6449
- return 0;
6450
- const seed = `${noteId ?? 0}:${hash ?? ""}`;
6451
- let h = 2166136261;
6452
- for (let i = 0;i < seed.length; i++) {
6453
- h ^= seed.charCodeAt(i);
6454
- h = Math.imul(h, 16777619);
6455
- }
6456
- const unsigned = h >>> 0;
6457
- return unsigned % (max * 2 + 1) - max;
6458
- }
6459
-
6460
- // ../plugin/src/features/magic-context/smart-notes/storage.ts
6461
- function toSmartNote(note) {
6462
- return {
6463
- ...note,
6464
- checkStatus: note.checkStatus ?? "uncompiled",
6465
- checkFailureCount: note.checkFailureCount ?? 0,
6466
- checkNetworkFailureCount: note.checkNetworkFailureCount ?? 0,
6467
- policyVersion: note.policyVersion ?? 0
6468
- };
6469
- }
6470
- function commitSmartNoteState(db, args) {
6471
- db.exec("BEGIN IMMEDIATE");
6472
- let leaseLost = false;
6473
- let committed = false;
6474
- try {
6475
- if (args.leaseHeld && !args.leaseHeld()) {
6476
- leaseLost = true;
6477
- } else if (claimExpectedState(db, args.expected)) {
6478
- args.write();
6479
- committed = true;
6480
- }
6481
- db.exec("COMMIT");
6482
- } catch (error) {
6483
- try {
6484
- db.exec("ROLLBACK");
6485
- } catch {}
6486
- throw error;
6487
- }
6488
- if (leaseLost) {
6489
- throw new Error(`Dream lease lost during smart-note ${args.phase} commit`);
6490
- }
6491
- if (!committed) {
6492
- log(`[debug] smart note #${args.expected.noteId}: discarded stale ${args.phase} result`);
6493
- }
6494
- return committed;
6495
- }
6496
- function claimExpectedState(db, expected) {
6497
- if (expected.kind === "compiled-check") {
6498
- return db.prepare(`UPDATE notes SET id = id
6499
- WHERE id = ? AND type = 'smart' AND status = 'pending'
6500
- AND check_status = 'compiled' AND compiled_check = ?
6501
- AND check_hash IS ? AND check_compiled_at IS ?`).run(expected.noteId, expected.compiledCheck, expected.checkHash, expected.checkCompiledAt).changes > 0;
6502
- }
6503
- const statusClause = expected.checkStatus ? " AND check_status = ?" : "";
6504
- const params = [
6505
- expected.noteId,
6506
- expected.content,
6507
- expected.surfaceCondition,
6508
- expected.updatedAt
6509
- ];
6510
- if (expected.checkStatus)
6511
- params.push(expected.checkStatus);
6512
- return db.prepare(`UPDATE notes SET id = id
6513
- WHERE id = ? AND type = 'smart' AND status = 'pending'
6514
- AND content = ? AND surface_condition IS ? AND updated_at = ?${statusClause}`).run(...params).changes > 0;
6515
- }
6516
- function getDueCompiledSmartNoteChecks(db, projectPath, now, limit) {
6517
- return getPendingSmartNotes(db, projectPath).map(toSmartNote).filter((note) => note.checkStatus === "compiled" && note.compiledCheck !== null && note.policyVersion === SMART_NOTE_CHECK_POLICY_VERSION && (note.checkQuarantinedUntil === null || note.checkQuarantinedUntil <= now) && (note.checkNextDueAt === null || note.checkNextDueAt <= now)).sort((a, b) => (a.checkNextDueAt ?? 0) - (b.checkNextDueAt ?? 0) || a.id - b.id).slice(0, Math.max(1, limit));
6518
- }
6519
- function getSmartNotesNeedingCompilation(db, projectPath, now, limit) {
6520
- return getPendingSmartNotes(db, projectPath).map(toSmartNote).filter((note) => (note.checkNextDueAt === null || note.checkNextDueAt <= now) && (note.checkStatus === "uncompiled" || note.checkStatus === "failing" || note.compiledCheck === null || note.policyVersion !== SMART_NOTE_CHECK_POLICY_VERSION)).sort((a, b) => a.createdAt - b.createdAt || a.id - b.id).slice(0, Math.max(1, limit));
6521
- }
6522
- function getStaleCompiledSmartNotes(db, projectPath, now, limit) {
6523
- const staleBefore = now - SMART_NOTE_CHECK_MAX_STALENESS_MS;
6524
- const livenessBefore = now - SMART_NOTE_CHECK_LIVENESS_RECHECK_MS;
6525
- return getPendingSmartNotes(db, projectPath).map(toSmartNote).filter((note) => note.checkStatus === "compiled" && note.compiledCheck !== null && note.policyVersion === SMART_NOTE_CHECK_POLICY_VERSION && note.checkFalseSinceAt !== null && note.checkFalseSinceAt <= staleBefore && (note.checkLastLivenessAt === null || note.checkLastLivenessAt <= livenessBefore)).sort((a, b) => (a.checkFalseSinceAt ?? 0) - (b.checkFalseSinceAt ?? 0) || a.id - b.id).slice(0, Math.max(1, limit));
6526
- }
6527
- function storeCompiledSmartNoteCheck(db, args) {
6528
- db.prepare(`UPDATE notes
6529
- SET compiled_check = ?,
6530
- manifest_json = ?,
6531
- check_hash = ?,
6532
- check_cron = ?,
6533
- check_version = 1,
6534
- check_status = 'compiled',
6535
- check_failure_count = 0,
6536
- check_network_failure_count = 0,
6537
- check_quarantined_until = NULL,
6538
- check_next_due_at = ?,
6539
- check_compiled_at = ?,
6540
- check_false_since_at = COALESCE(check_false_since_at, ?),
6541
- check_last_liveness_at = NULL,
6542
- policy_version = ?,
6543
- updated_at = ?
6544
- WHERE id = ? AND type = 'smart'`).run(args.compiledCheck, JSON.stringify(args.manifest), args.checkHash, args.checkCron, args.nextDueAt, args.now, args.now, SMART_NOTE_CHECK_POLICY_VERSION, args.now, args.noteId);
6545
- }
6546
- function markCompiledCheckFalse(db, noteId, nextDueAt, now) {
6547
- db.prepare(`UPDATE notes
6548
- SET last_checked_at = ?,
6549
- updated_at = ?,
6550
- check_next_due_at = ?,
6551
- check_failure_count = 0,
6552
- check_network_failure_count = 0,
6553
- check_false_since_at = COALESCE(check_false_since_at, ?)
6554
- WHERE id = ? AND type = 'smart'`).run(now, now, nextDueAt, now, noteId);
6555
- }
6556
- function markCompiledCheckLogicFailure(db, noteId, now, maxFailures) {
6557
- const failureCount = readFailureCount(db, noteId, "check_failure_count") + 1;
6558
- const status = failureCount >= maxFailures ? "failing" : "compiled";
6559
- db.prepare(`UPDATE notes
6560
- SET check_failure_count = ?,
6561
- check_status = ?,
6562
- check_next_due_at = ?,
6563
- updated_at = ?
6564
- WHERE id = ? AND type = 'smart'`).run(failureCount, status, now + backoffMs(failureCount), now, noteId);
6881
+ }
6882
+ return values;
6565
6883
  }
6566
- function markCompiledCheckNetworkFailure(db, noteId, now, maxFailures) {
6567
- const failureCount = readFailureCount(db, noteId, "check_network_failure_count") + 1;
6568
- const quarantinedUntil = now + backoffMs(failureCount);
6569
- const status = failureCount >= maxFailures ? "failing" : "compiled";
6570
- db.prepare(`UPDATE notes
6571
- SET check_network_failure_count = ?,
6572
- check_status = ?,
6573
- check_next_due_at = ?,
6574
- check_quarantined_until = ?,
6575
- updated_at = ?
6576
- WHERE id = ? AND type = 'smart'`).run(failureCount, status, quarantinedUntil, quarantinedUntil, now, noteId);
6884
+ function normalizeCron(cron) {
6885
+ if (cron.length > MAX_CRON_CHARS)
6886
+ throw new Error("check_cron exceeds 256 characters");
6887
+ const normalized = cron.trim() || "0 * * * *";
6888
+ const parsed = parseCron(normalized);
6889
+ if (!parsed.ok)
6890
+ throw new Error(`invalid check_cron: ${parsed.error}`);
6891
+ const next = nextOccurrence(parsed.cron, new Date, undefined, SMART_NOTE_CHECK_CEILING_MS);
6892
+ if (!next)
6893
+ throw new Error("check_cron has no occurrence within the scheduling ceiling");
6894
+ return normalized;
6577
6895
  }
6578
- function markSmartNoteLivenessChecked(db, noteId, now) {
6579
- db.prepare(`UPDATE notes
6580
- SET check_last_liveness_at = ?, updated_at = ?
6581
- WHERE id = ? AND type = 'smart'`).run(now, now, noteId);
6896
+ function unique(items) {
6897
+ return [...new Set(items)];
6582
6898
  }
6583
- function markSmartNoteCheckStatus(db, noteId, status, now) {
6584
- db.prepare(`UPDATE notes SET check_status = ?, updated_at = ? WHERE id = ? AND type = 'smart'`).run(status, now, noteId);
6899
+ function uniqueStrings(items, normalize = (value) => value) {
6900
+ if (!Array.isArray(items))
6901
+ return;
6902
+ const values = unique(items.slice(0, MAX_MANIFEST_ENTRIES).filter((item) => typeof item === "string" && item.length > 0).map(normalize));
6903
+ return values.length > 0 ? values : undefined;
6585
6904
  }
6586
- function markSmartNoteCompilationFailure(db, noteId, now, maxFailures) {
6587
- const failureCount = readFailureCount(db, noteId, "check_failure_count") + 1;
6588
- const status = failureCount >= maxFailures ? "fallback" : "uncompiled";
6589
- db.prepare(`UPDATE notes
6590
- SET check_failure_count = ?,
6591
- check_status = ?,
6592
- check_next_due_at = ?,
6593
- updated_at = ?
6594
- WHERE id = ? AND type = 'smart'`).run(failureCount, status, now + backoffMs(failureCount), now, noteId);
6905
+ function boundedError(error) {
6906
+ return error.slice(0, MAX_COMPILER_ERROR_CHARS);
6595
6907
  }
6596
- function readFailureCount(db, noteId, column) {
6597
- if (column !== "check_failure_count" && column !== "check_network_failure_count")
6598
- return 0;
6599
- const row = db.prepare(`SELECT ${column} AS count FROM notes WHERE id = ?`).get(noteId);
6600
- return row?.count ?? 0;
6908
+
6909
+ // ../plugin/src/features/magic-context/smart-notes/schedule.ts
6910
+ function nextSmartNoteCheckDueAt(cron, options = {}) {
6911
+ const now = options.now ?? Date.now();
6912
+ const floorMs = options.floorMs ?? SMART_NOTE_CHECK_FLOOR_MS;
6913
+ const ceilingMs = options.ceilingMs ?? SMART_NOTE_CHECK_CEILING_MS;
6914
+ const rawNext = cron?.trim() ? nextDueAtMs(cron, now, undefined, ceilingMs) : null;
6915
+ const rawDelta = rawNext ? rawNext - now : SMART_NOTE_CHECK_DEFAULT_INTERVAL_MS;
6916
+ const clamped = Math.min(ceilingMs, Math.max(floorMs, rawDelta));
6917
+ const jittered = clamped + deterministicJitterMs(clamped, options.noteId, options.hash);
6918
+ const bounded = Math.min(ceilingMs, Math.max(floorMs, jittered));
6919
+ return now + bounded;
6601
6920
  }
6602
- function backoffMs(failureCount) {
6603
- const minutes = Math.min(24 * 60, 5 * 2 ** Math.max(0, failureCount - 1));
6604
- return minutes * 60 * 1000;
6921
+ function deterministicJitterMs(intervalMs, noteId, hash) {
6922
+ const max = Math.min(60000, Math.floor(intervalMs * 0.1));
6923
+ if (max <= 0)
6924
+ return 0;
6925
+ const seed = `${noteId ?? 0}:${hash ?? ""}`;
6926
+ let h = 2166136261;
6927
+ for (let i = 0;i < seed.length; i++) {
6928
+ h ^= seed.charCodeAt(i);
6929
+ h = Math.imul(h, 16777619);
6930
+ }
6931
+ const unsigned = h >>> 0;
6932
+ return unsigned % (max * 2 + 1) - max;
6605
6933
  }
6606
6934
 
6607
6935
  // ../plugin/src/features/magic-context/smart-notes/runner.ts
@@ -7337,6 +7665,7 @@ async function mapMemories(args) {
7337
7665
  result.independent += counts.independent;
7338
7666
  result.remaining -= counts.mapped + counts.independent;
7339
7667
  result.batches += 1;
7668
+ args.onProgress?.(result.mapped + result.independent);
7340
7669
  }
7341
7670
  result.complete = result.remaining === 0;
7342
7671
  log(`[dreamer] map-memories: mapped=${result.mapped} independent=${result.independent} batches=${result.batches} remaining=${result.remaining} complete=${result.complete}`);
@@ -7938,6 +8267,7 @@ async function refreshPrimers(args) {
7938
8267
  result.refreshed += 1;
7939
8268
  else
7940
8269
  result.skipped += 1;
8270
+ args.onProgress?.(result.refreshed + result.skipped);
7941
8271
  }
7942
8272
  log(`[dreamer] refresh-primers: refreshed=${result.refreshed} skipped=${result.skipped}`);
7943
8273
  return result;
@@ -8523,69 +8853,6 @@ function insertDreamRun(db, run) {
8523
8853
  getInsertDreamRunStatement(db).run(run.projectPath, run.startedAt, run.finishedAt, run.holderId, JSON.stringify(run.tasks), run.tasksSucceeded, run.tasksFailed, run.smartNotesSurfaced, run.smartNotesPending, run.memoryChanges ? JSON.stringify(run.memoryChanges) : null, run.parentSessionId ?? null);
8524
8854
  }
8525
8855
 
8526
- // ../plugin/src/features/magic-context/dreamer/storage-task-schedule.ts
8527
- function toRow(r) {
8528
- return {
8529
- projectPath: r.project_path,
8530
- task: r.task,
8531
- lastRunAt: r.last_run_at,
8532
- nextDueAt: r.next_due_at,
8533
- schedule: r.schedule ?? null,
8534
- lastStatus: r.last_status ?? null,
8535
- lastError: r.last_error,
8536
- retryCount: r.retry_count ?? 0,
8537
- lastCheckedCommit: r.last_checked_commit ?? null,
8538
- lastBroadRunAt: r.last_broad_run_at ?? null,
8539
- retrospectiveWatermarkMs: r.retrospective_watermark_ms ?? null
8540
- };
8541
- }
8542
- var SELECT_COLUMNS = "project_path, task, last_run_at, next_due_at, schedule, last_status, last_error, retry_count, last_checked_commit, last_broad_run_at, retrospective_watermark_ms";
8543
- function getTaskScheduleState(db, projectPath, task) {
8544
- const row = db.prepare(`SELECT ${SELECT_COLUMNS} FROM task_schedule_state WHERE project_path = ? AND task = ?`).get(projectPath, task);
8545
- return row ? toRow(row) : null;
8546
- }
8547
- function getMostRecentTaskRunAt(db, projectPath) {
8548
- const row = db.prepare("SELECT MAX(last_run_at) AS max_at FROM task_schedule_state WHERE project_path = ?").get(projectPath);
8549
- const value = row?.max_at ?? null;
8550
- return typeof value === "number" && value > 0 ? value : null;
8551
- }
8552
- function pruneNonCanonicalTaskRows(db, projectPath, canonicalTasks) {
8553
- if (canonicalTasks.length === 0)
8554
- return 0;
8555
- const placeholders = canonicalTasks.map(() => "?").join(", ");
8556
- const result = db.prepare(`DELETE FROM task_schedule_state WHERE project_path = ? AND task NOT IN (${placeholders})`).run(projectPath, ...canonicalTasks);
8557
- return Number(result.changes ?? 0);
8558
- }
8559
- function deleteTaskScheduleRowsForProject(db, projectPath) {
8560
- const result = db.prepare("DELETE FROM task_schedule_state WHERE project_path = ?").run(projectPath);
8561
- return Number(result.changes ?? 0);
8562
- }
8563
- function seedTaskScheduleState(db, projectPath, task, nextDueAt, lastRunAt, schedule) {
8564
- db.prepare("INSERT INTO task_schedule_state (project_path, task, last_run_at, next_due_at, schedule, last_status, last_error, retry_count) VALUES (?, ?, ?, ?, ?, NULL, NULL, 0) ON CONFLICT(project_path, task) DO NOTHING").run(projectPath, task, lastRunAt, nextDueAt, schedule);
8565
- }
8566
- function writeTaskScheduleState(db, row) {
8567
- db.prepare(`INSERT INTO task_schedule_state
8568
- (project_path, task, last_run_at, next_due_at, schedule, last_status, last_error, retry_count, last_checked_commit, last_broad_run_at, retrospective_watermark_ms)
8569
- VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
8570
- ON CONFLICT(project_path, task) DO UPDATE SET
8571
- last_run_at = excluded.last_run_at,
8572
- next_due_at = excluded.next_due_at,
8573
- schedule = excluded.schedule,
8574
- last_status = excluded.last_status,
8575
- last_error = excluded.last_error,
8576
- retry_count = excluded.retry_count,
8577
- last_checked_commit = COALESCE(excluded.last_checked_commit, task_schedule_state.last_checked_commit),
8578
- last_broad_run_at = COALESCE(excluded.last_broad_run_at, task_schedule_state.last_broad_run_at),
8579
- retrospective_watermark_ms = COALESCE(excluded.retrospective_watermark_ms, task_schedule_state.retrospective_watermark_ms)`).run(row.projectPath, row.task, row.lastRunAt, row.nextDueAt, row.schedule, row.lastStatus, row.lastError, row.retryCount, row.lastCheckedCommit ?? null, row.lastBroadRunAt ?? null, row.retrospectiveWatermarkMs ?? null);
8580
- }
8581
- function isRetrospectiveWindowProcessed(db, projectPath, windowKey) {
8582
- const row = db.prepare("SELECT 1 AS one FROM retrospective_processed_windows WHERE project_path = ? AND window_key = ?").get(projectPath, windowKey);
8583
- return row != null;
8584
- }
8585
- function recordRetrospectiveWindowProcessed(db, projectPath, windowKey) {
8586
- db.prepare("INSERT INTO retrospective_processed_windows (project_path, window_key, processed_at) VALUES (?, ?, ?) ON CONFLICT(project_path, window_key) DO NOTHING").run(projectPath, windowKey, Date.now());
8587
- }
8588
-
8589
8856
  // ../plugin/src/features/magic-context/dreamer/verify.ts
8590
8857
  import { createHash as createHash5 } from "node:crypto";
8591
8858
 
@@ -8594,6 +8861,30 @@ import path5 from "node:path";
8594
8861
  function minOf(values) {
8595
8862
  return values.reduce((acc, v) => v < acc ? v : acc, Number.POSITIVE_INFINITY);
8596
8863
  }
8864
+ function ensureBroadCycleStart(args) {
8865
+ const current = getTaskScheduleState(args.db, args.projectIdentity, "verify-broad");
8866
+ if (current?.lastBroadRunAt != null && current.lastBroadRunAt > 0) {
8867
+ return current.lastBroadRunAt;
8868
+ }
8869
+ if (!current)
8870
+ return args.runStartedAt;
8871
+ if (!args.holderId || !args.leaseKey) {
8872
+ throw new Error("verify-broad cycle opening requires the task lease");
8873
+ }
8874
+ return runLeaseGuardedWrite(args.db, args.holderId, args.leaseKey, () => {
8875
+ const latest = getTaskScheduleState(args.db, args.projectIdentity, "verify-broad");
8876
+ if (!latest)
8877
+ return args.runStartedAt;
8878
+ if (latest.lastBroadRunAt != null && latest.lastBroadRunAt > 0) {
8879
+ return latest.lastBroadRunAt;
8880
+ }
8881
+ writeTaskScheduleState(args.db, {
8882
+ ...latest,
8883
+ lastBroadRunAt: args.runStartedAt
8884
+ });
8885
+ return args.runStartedAt;
8886
+ });
8887
+ }
8597
8888
  async function partitionVerifyScope(args) {
8598
8889
  const runStartedAt = args.now ?? Date.now();
8599
8890
  const active = getMemoriesByProject(args.db, args.projectIdentity);
@@ -8605,6 +8896,29 @@ async function partitionVerifyScope(args) {
8605
8896
  content: m.content,
8606
8897
  mappedFiles: verById.get(m.id)?.files ?? []
8607
8898
  });
8899
+ if (args.forceBroad) {
8900
+ const broadCycleStartAt = ensureBroadCycleStart({
8901
+ db: args.db,
8902
+ projectIdentity: args.projectIdentity,
8903
+ holderId: args.holderId,
8904
+ leaseKey: args.leaseKey,
8905
+ runStartedAt
8906
+ });
8907
+ const broadCandidates = candidates.filter((m) => (verById.get(m.id)?.verifiedAt ?? 0) < broadCycleStartAt).sort((a, b) => {
8908
+ const verifiedAtA = verById.get(a.id)?.verifiedAt ?? 0;
8909
+ const verifiedAtB = verById.get(b.id)?.verifiedAt ?? 0;
8910
+ return verifiedAtA - verifiedAtB || a.id - b.id;
8911
+ });
8912
+ return {
8913
+ runStartedAt,
8914
+ mode: "broad",
8915
+ inScope: broadCandidates.map(toPrompt),
8916
+ inScopeIds: broadCandidates.map((m) => m.id),
8917
+ skippedIds: candidates.filter((m) => !broadCandidates.some((candidate) => candidate.id === m.id)).map((m) => m.id),
8918
+ broadCycleStartAt,
8919
+ reason: `broad cycle (${broadCandidates.length} remain; started ${broadCycleStartAt})`
8920
+ };
8921
+ }
8608
8922
  if (candidates.length === 0) {
8609
8923
  return {
8610
8924
  runStartedAt,
@@ -8615,16 +8929,6 @@ async function partitionVerifyScope(args) {
8615
8929
  reason: "no file-mapped memories in scope"
8616
8930
  };
8617
8931
  }
8618
- if (args.forceBroad) {
8619
- return {
8620
- runStartedAt,
8621
- mode: "broad",
8622
- inScope: candidates.map(toPrompt),
8623
- inScopeIds: candidates.map((m) => m.id),
8624
- skippedIds: [],
8625
- reason: "broad full-pool verification of file-mapped memories"
8626
- };
8627
- }
8628
8932
  const allInScope = (mode, reason) => ({
8629
8933
  runStartedAt,
8630
8934
  mode,
@@ -8743,7 +9047,25 @@ function parseVerifyManifest(text) {
8743
9047
 
8744
9048
  // ../plugin/src/features/magic-context/dreamer/verify.ts
8745
9049
  var VERIFY_BATCH_SIZE = 50;
9050
+ function closeBroadCycle(args, cycleStartAt) {
9051
+ if (!args.forceBroad || cycleStartAt === undefined)
9052
+ return;
9053
+ if (!getTaskScheduleState(args.db, args.projectIdentity, "verify-broad"))
9054
+ return;
9055
+ runLeaseGuardedWrite(args.db, args.holderId, args.leaseKey, () => {
9056
+ const current = getTaskScheduleState(args.db, args.projectIdentity, "verify-broad");
9057
+ if (current?.lastBroadRunAt !== cycleStartAt)
9058
+ return;
9059
+ if (!current)
9060
+ return;
9061
+ writeTaskScheduleState(args.db, {
9062
+ ...current,
9063
+ lastBroadRunAt: null
9064
+ });
9065
+ });
9066
+ }
8746
9067
  async function runVerify(args) {
9068
+ const runStartedAt = Date.now();
8747
9069
  const result = {
8748
9070
  verified: 0,
8749
9071
  updated: 0,
@@ -8758,14 +9080,20 @@ async function runVerify(args) {
8758
9080
  db: args.db,
8759
9081
  projectIdentity: args.projectIdentity,
8760
9082
  projectDirectory: args.sessionDirectory,
8761
- forceBroad: args.forceBroad
9083
+ forceBroad: args.forceBroad,
9084
+ now: runStartedAt,
9085
+ holderId: args.holderId,
9086
+ leaseKey: args.leaseKey
8762
9087
  });
8763
9088
  result.mode = gate.mode;
9089
+ result.broadCycleStartAt = gate.broadCycleStartAt;
8764
9090
  result.inScope = gate.inScope.length;
8765
9091
  result.remaining = gate.inScope.length;
8766
9092
  log(`[dreamer] ${args.forceBroad ? "verify-broad" : "verify"} gate: mode=${gate.mode} in_scope=${gate.inScope.length} skipped=${gate.skippedIds.length} reason=${gate.reason}`);
8767
- if (gate.inScope.length === 0)
9093
+ if (gate.inScope.length === 0) {
9094
+ closeBroadCycle(args, gate.broadCycleStartAt);
8768
9095
  return result;
9096
+ }
8769
9097
  const batches = [];
8770
9098
  for (let i = 0;i < gate.inScope.length; i += VERIFY_BATCH_SIZE) {
8771
9099
  batches.push(gate.inScope.slice(i, i + VERIFY_BATCH_SIZE));
@@ -8785,8 +9113,11 @@ async function runVerify(args) {
8785
9113
  result.archived += counts.archived;
8786
9114
  result.remaining -= counts.verified + counts.updated + counts.archived;
8787
9115
  result.batches += 1;
9116
+ args.onProgress?.(result.verified + result.updated + result.archived);
8788
9117
  }
8789
9118
  result.complete = result.remaining === 0;
9119
+ if (result.complete)
9120
+ closeBroadCycle(args, gate.broadCycleStartAt);
8790
9121
  log(`[dreamer] ${args.forceBroad ? "verify-broad" : "verify"}: verified=${result.verified} updated=${result.updated} archived=${result.archived} batches=${result.batches} remaining=${result.remaining} complete=${result.complete}`);
8791
9122
  return result;
8792
9123
  } finally {
@@ -9076,6 +9407,20 @@ function createDreamTaskExecutor(deps) {
9076
9407
  const { db, projectIdentity, holderId, leaseKey } = ctx;
9077
9408
  const startedAt = Date.now();
9078
9409
  const deadline = startedAt + config.timeoutMinutes * 60 * 1000;
9410
+ const backlogAtStart = getDreamTaskBacklog(db, projectIdentity, config.task);
9411
+ const reportProgress = (processed) => {
9412
+ deps.onProgress?.({
9413
+ task: config.task,
9414
+ processed: Math.max(0, processed),
9415
+ total: backlogAtStart.pending,
9416
+ startedAt
9417
+ });
9418
+ };
9419
+ reportProgress(0);
9420
+ const incompleteMessage = (remaining) => {
9421
+ const processed = processedDreamTaskItems(backlogAtStart.pending, remaining);
9422
+ return `${config.task} incomplete: ${remaining} remain (was ${backlogAtStart.pending} at run start; processed ${processed} this run)`;
9423
+ };
9079
9424
  const parent = await resolveParentSessionId();
9080
9425
  let moduleRoute;
9081
9426
  if (config.task === "map-memories" || config.task === "compress-cues" || config.task === "verify" || config.task === "verify-broad" || config.task === "retrospective") {
@@ -9104,7 +9449,19 @@ function createDreamTaskExecutor(deps) {
9104
9449
  name: config.task,
9105
9450
  durationMs: Date.now() - startedAt,
9106
9451
  resultChars: 0,
9107
- ...error ? { error } : {}
9452
+ ...error ? { error } : {},
9453
+ backlog: (() => {
9454
+ const end = extra?.backlogAfter ?? getDreamTaskBacklog(db, projectIdentity, config.task);
9455
+ const processed = processedDreamTaskItems(backlogAtStart.pending, end.pending);
9456
+ const value = {
9457
+ pendingAtStart: backlogAtStart.pending,
9458
+ totalAtStart: backlogAtStart.total,
9459
+ pendingAtEnd: end.pending,
9460
+ totalAtEnd: end.total,
9461
+ processed
9462
+ };
9463
+ return value;
9464
+ })()
9108
9465
  }
9109
9466
  ],
9110
9467
  tasksSucceeded: status === "completed" ? 1 : 0,
@@ -9138,8 +9495,8 @@ function createDreamTaskExecutor(deps) {
9138
9495
  }
9139
9496
  try {
9140
9497
  if (config.task === "compress-cues") {
9141
- if (deps.experimentalMural?.enabled !== true) {
9142
- log("[dreamer] compress-cues: skipped (experimental.mural is not enabled)");
9498
+ if (deps.mural?.enabled !== true) {
9499
+ log("[dreamer] compress-cues: skipped (mural is not enabled)");
9143
9500
  recordRun("completed", null);
9144
9501
  return { status: "completed" };
9145
9502
  }
@@ -9158,12 +9515,13 @@ function createDreamTaskExecutor(deps) {
9158
9515
  holderId,
9159
9516
  leaseKey,
9160
9517
  deadline,
9161
- model: config.model ?? deps.experimentalMural.model ?? deps.dreamerModel,
9162
- fallbackModels: config.fallbackModels
9518
+ model: config.model ?? deps.mural.model ?? deps.dreamerModel,
9519
+ fallbackModels: config.fallbackModels,
9520
+ onProgress: (processed) => reportProgress(processed)
9163
9521
  });
9164
9522
  log(`[dreamer] compress-cues: compressed=${result.compressed} skipped=${result.skipped} chunks=${result.chunks} remaining=${result.remaining}`);
9165
9523
  if (!result.complete) {
9166
- const error = `compress-cues incomplete: ${result.remaining} selected memories remain`;
9524
+ const error = incompleteMessage(result.remaining);
9167
9525
  recordRun("failed", error);
9168
9526
  return { status: "failed", transient: true, error };
9169
9527
  }
@@ -9200,11 +9558,12 @@ function createDreamTaskExecutor(deps) {
9200
9558
  deadline,
9201
9559
  model: config.model,
9202
9560
  fallbackModels: config.fallbackModels,
9203
- moduleRoute
9561
+ moduleRoute,
9562
+ onProgress: (processed) => reportProgress(processed)
9204
9563
  });
9205
9564
  log(`[dreamer] map-memories: mapped=${result.mapped} independent=${result.independent} batches=${result.batches} remaining=${result.remaining}`);
9206
9565
  if (!result.complete) {
9207
- const error = `map-memories incomplete: ${result.remaining} selected memories remain`;
9566
+ const error = incompleteMessage(result.remaining);
9208
9567
  recordRun("failed", error);
9209
9568
  return { status: "failed", transient: true, error };
9210
9569
  }
@@ -9226,19 +9585,32 @@ function createDreamTaskExecutor(deps) {
9226
9585
  model: config.model,
9227
9586
  fallbackModels: config.fallbackModels,
9228
9587
  language: config.language ?? deps.language,
9229
- moduleRoute
9588
+ moduleRoute,
9589
+ onProgress: (processed2) => reportProgress(processed2)
9230
9590
  });
9591
+ const processed = result.verified + result.updated + result.archived;
9592
+ const broadProgress = config.task === "verify-broad" ? `verify-broad cycle ${result.broadCycleStartAt ?? "open"}: verified ${processed}, ${result.remaining} remain` : null;
9593
+ const backlogAfter = config.task === "verify-broad" ? { pending: result.remaining, total: backlogAtStart.total } : undefined;
9231
9594
  if (!result.complete) {
9232
- const error = `${config.task} incomplete: ${result.remaining} selected memories remain`;
9595
+ if (broadProgress && processed > 0) {
9596
+ recordRun("completed", broadProgress, {
9597
+ memoryChanges: computeMemoryDelta(memoryBefore),
9598
+ backlogAfter
9599
+ });
9600
+ return { status: "completed", error: broadProgress };
9601
+ }
9602
+ const error = incompleteMessage(result.remaining);
9233
9603
  recordRun("failed", error, {
9234
- memoryChanges: computeMemoryDelta(memoryBefore)
9604
+ memoryChanges: computeMemoryDelta(memoryBefore),
9605
+ backlogAfter
9235
9606
  });
9236
9607
  return { status: "failed", transient: true, error };
9237
9608
  }
9238
- recordRun("completed", null, {
9239
- memoryChanges: computeMemoryDelta(memoryBefore)
9609
+ recordRun("completed", broadProgress, {
9610
+ memoryChanges: computeMemoryDelta(memoryBefore),
9611
+ backlogAfter
9240
9612
  });
9241
- return { status: "completed" };
9613
+ return broadProgress ? { status: "completed", error: broadProgress } : { status: "completed" };
9242
9614
  }
9243
9615
  if (config.task === "classify-memories") {
9244
9616
  let moduleArgs;
@@ -9288,11 +9660,12 @@ function createDreamTaskExecutor(deps) {
9288
9660
  deadline,
9289
9661
  model: config.model,
9290
9662
  fallbackModels: config.fallbackModels,
9291
- ...moduleArgs
9663
+ ...moduleArgs,
9664
+ onProgress: (processed) => reportProgress(processed)
9292
9665
  });
9293
9666
  log(`[dreamer] classify-memories: stage=${result.stage} classified=${result.classified} changed=${result.changed} chunks=${result.chunks} remaining=${result.remaining}`);
9294
9667
  if (!result.complete) {
9295
- const error = `classify-memories incomplete: ${result.remaining} selected memories remain`;
9668
+ const error = incompleteMessage(result.remaining);
9296
9669
  recordRun("failed", error);
9297
9670
  return { status: "failed", transient: true, error };
9298
9671
  }
@@ -9328,7 +9701,8 @@ function createDreamTaskExecutor(deps) {
9328
9701
  model: config.model,
9329
9702
  fallbackModels: config.fallbackModels,
9330
9703
  language: config.language ?? deps.language,
9331
- rawProviderFactory: deps.primerRawProviderFactory
9704
+ rawProviderFactory: deps.primerRawProviderFactory,
9705
+ onProgress: (processed) => reportProgress(processed)
9332
9706
  });
9333
9707
  recordRun("completed", null);
9334
9708
  log(`[dreamer] refresh-primers: refreshed=${result.refreshed} skipped=${result.skipped}`);
@@ -9382,6 +9756,8 @@ function createDreamTaskExecutor(deps) {
9382
9756
  recordRun("failed", brief);
9383
9757
  log(`[dreamer] task ${config.task} failed (transient=${transient}): ${brief}`);
9384
9758
  return { status: "failed", transient, error: brief };
9759
+ } finally {
9760
+ deps.onProgress?.(null, config.task);
9385
9761
  }
9386
9762
  };
9387
9763
  }
@@ -9757,66 +10133,6 @@ async function runAgenticTask(config, ctx, helpers) {
9757
10133
  }
9758
10134
  }
9759
10135
 
9760
- // ../plugin/src/features/magic-context/dreamer/task-gates.ts
9761
- function countActiveMemories(db, projectPath) {
9762
- const row = db.prepare("SELECT COUNT(*) AS cnt FROM memories WHERE project_path = ? AND status IN ('active','permanent')").get(projectPath);
9763
- return row?.cnt ?? 0;
9764
- }
9765
- function countUnmappedActiveMemories(db, projectPath) {
9766
- const row = db.prepare(`SELECT COUNT(*) AS cnt
9767
- FROM memories m
9768
- WHERE m.project_path = ?
9769
- AND m.status IN ('active','permanent')
9770
- AND NOT EXISTS (
9771
- SELECT 1 FROM memory_verifications v WHERE v.memory_id = m.id
9772
- )`).get(projectPath);
9773
- return row?.cnt ?? 0;
9774
- }
9775
- function countCompartmentsSince(db, projectPath, since) {
9776
- const row = db.prepare(`SELECT COUNT(*) AS cnt
9777
- FROM compartments c
9778
- JOIN session_projects sp ON sp.session_id = c.session_id
9779
- WHERE sp.project_path = ? AND c.created_at > ?`).get(projectPath, since);
9780
- return row?.cnt ?? 0;
9781
- }
9782
- function countProjectSessionsSince(db, projectPath, since) {
9783
- const row = since === null ? db.prepare("SELECT COUNT(*) AS cnt FROM session_projects WHERE project_path = ?").get(projectPath) : db.prepare("SELECT COUNT(*) AS cnt FROM session_projects WHERE project_path = ? AND updated_at > ?").get(projectPath, since);
9784
- return row?.cnt ?? 0;
9785
- }
9786
- function evaluateTaskGate(task, ctx) {
9787
- const { db, projectIdentity: project, lastRunAt } = ctx;
9788
- switch (task) {
9789
- case "map-memories":
9790
- return countUnmappedActiveMemories(db, project) > 0;
9791
- case "verify":
9792
- return countActiveMemories(db, project) > 0;
9793
- case "verify-broad":
9794
- return countActiveMemories(db, project) > 0;
9795
- case "curate":
9796
- return countActiveMemories(db, project) > 0;
9797
- case "compress-cues":
9798
- return countActiveMemories(db, project) > 0;
9799
- case "classify-memories":
9800
- return countActiveMemories(db, project) > 0;
9801
- case "retrospective":
9802
- return countProjectSessionsSince(db, project, ctx.retrospectiveWatermarkMs ?? null) > 0;
9803
- case "maintain-docs":
9804
- return countCompartmentsSince(db, project, lastRunAt ?? 0) > 0;
9805
- case "evaluate-smart-notes":
9806
- return getSmartNotesNeedingCompilation(db, project, Date.now(), 1).length > 0 || getStaleCompiledSmartNotes(db, project, Date.now(), 1).length > 0 || getPendingSmartNotes(db, project).some((note) => note.checkStatus === "fallback");
9807
- case "review-user-memories":
9808
- return getUserMemoryCandidates(db).length >= ctx.promotionThreshold;
9809
- case "promote-primers":
9810
- return countPrimerCandidatesForProject(db, project) >= (ctx.promotionThreshold ?? 2);
9811
- case "refresh-primers":
9812
- return getActivePrimers(db, project).some((primer) => !primer.answer.trim() || primer.answerRefreshedAt == null || (primer.lastObservedAt ?? 0) > primer.answerRefreshedAt);
9813
- default: {
9814
- const _exhaustive = task;
9815
- return Boolean(_exhaustive);
9816
- }
9817
- }
9818
- }
9819
-
9820
10136
  // ../plugin/src/features/magic-context/dreamer/task-scheduler.ts
9821
10137
  var MAX_TASK_RETRIES = 3;
9822
10138
  function ensureSeeded(db, projectIdentity, config, now) {
@@ -9964,10 +10280,10 @@ async function runDomainGroup(deps, group, cb) {
9964
10280
  cb?.onRan?.(due.config.task);
9965
10281
  } else if (outcome.transient) {
9966
10282
  recordTransientFailure(db, projectIdentity, due, finishedAt, outcome.error ?? null);
9967
- cb?.onFailed?.(due.config.task);
10283
+ cb?.onFailed?.(due.config.task, outcome.error);
9968
10284
  } else {
9969
10285
  advanceAfterRun(db, projectIdentity, due, finishedAt, "failed", outcome.error ?? null);
9970
- cb?.onFailed?.(due.config.task);
10286
+ cb?.onFailed?.(due.config.task, outcome.error);
9971
10287
  }
9972
10288
  }
9973
10289
  } finally {
@@ -9976,7 +10292,15 @@ async function runDomainGroup(deps, group, cb) {
9976
10292
  }
9977
10293
  async function runManualDream(deps) {
9978
10294
  const now = Date.now();
9979
- const result = { ran: [], skippedNoWork: [], deferredBusy: [], failed: [] };
10295
+ const result = {
10296
+ ran: [],
10297
+ skippedNoWork: [],
10298
+ deferredBusy: [],
10299
+ failed: [],
10300
+ failureDetails: [],
10301
+ backlogBefore: {},
10302
+ backlogAfter: {}
10303
+ };
9980
10304
  let selected;
9981
10305
  let forceGate = false;
9982
10306
  if (deps.task) {
@@ -9990,6 +10314,9 @@ async function runManualDream(deps) {
9990
10314
  }
9991
10315
  if (selected.length === 0)
9992
10316
  return result;
10317
+ const selectedTaskNames = selected.map((config) => config.task);
10318
+ result.backlogBefore = getDreamTaskBacklogs(deps.db, deps.projectIdentity, selectedTaskNames);
10319
+ result.backlogAfter = { ...result.backlogBefore };
9993
10320
  for (const cfg of selected)
9994
10321
  ensureSeeded(deps.db, deps.projectIdentity, cfg, now);
9995
10322
  const dueAll = selected.map((config) => ({ config, scheduledAt: now }));
@@ -10011,8 +10338,10 @@ async function runManualDream(deps) {
10011
10338
  else
10012
10339
  result.skippedNoWork.push(d.config.task);
10013
10340
  }
10014
- if (gated.length === 0)
10341
+ if (gated.length === 0) {
10342
+ result.backlogAfter = getDreamTaskBacklogs(deps.db, deps.projectIdentity, selectedTaskNames);
10015
10343
  return result;
10344
+ }
10016
10345
  const groups = new Map;
10017
10346
  for (const d of gated) {
10018
10347
  const kind = leaseKindFor(d.config.task);
@@ -10024,9 +10353,14 @@ async function runManualDream(deps) {
10024
10353
  forceGate,
10025
10354
  leaseWaitMs: MANUAL_RUN_LEASE_WAIT_MS,
10026
10355
  onRan: (t) => result.ran.push(t),
10027
- onFailed: (t) => result.failed.push(t),
10356
+ onFailed: (task, error) => {
10357
+ result.failed.push(task);
10358
+ if (error)
10359
+ result.failureDetails?.push(`${task}: ${error}`);
10360
+ },
10028
10361
  onBusy: (t) => result.deferredBusy.push(t)
10029
10362
  })));
10363
+ result.backlogAfter = getDreamTaskBacklogs(deps.db, deps.projectIdentity, selectedTaskNames);
10030
10364
  return result;
10031
10365
  }
10032
10366
  async function runDueTasksForProject(deps) {
@@ -10341,10 +10675,11 @@ async function sweepProject(reg, origin, db, gitCommitEnabled) {
10341
10675
  ensureProjectRegistered: reg.ensureRegistered,
10342
10676
  language: reg.language,
10343
10677
  dreamerModel: dreamerConfig.model,
10344
- experimentalMural: reg.experimentalMural,
10678
+ mural: reg.mural,
10345
10679
  memoryInjectionBudgetTokens: reg.memoryInjectionBudgetTokens,
10346
10680
  transformMode: reg.transformMode,
10347
- moduleClient: reg.moduleClient
10681
+ moduleClient: reg.moduleClient,
10682
+ onProgress: (progress, completedTask) => reg.onDreamerProgress?.(progress, completedTask)
10348
10683
  });
10349
10684
  const ran = await runDueTasksForProject({
10350
10685
  db,
@@ -11014,13 +11349,18 @@ Unknown task "${requested}".`,
11014
11349
  });
11015
11350
  return;
11016
11351
  }
11352
+ const backlogTasks = task ? [task] : CANONICAL_DREAM_TASKS;
11353
+ const backlogBefore = getDreamTaskBacklogs(deps.db, project.projectIdentity, backlogTasks);
11017
11354
  sendCtxStatusMessage(pi, {
11018
11355
  title: "/ctx-dream",
11019
11356
  text: [
11020
11357
  "## /ctx-dream",
11021
11358
  "",
11022
11359
  task ? `Running dream task "${task}" for ${project.projectIdentity}…` : `Starting dream run for ${project.projectIdentity}…`,
11023
- `Project directory: ${project.projectDir}`
11360
+ `Project directory: ${project.projectDir}`,
11361
+ "",
11362
+ "Backlog before starting:",
11363
+ formatDreamTaskBacklogs(backlogBefore, backlogTasks)
11024
11364
  ].join(`
11025
11365
  `),
11026
11366
  level: "info"
@@ -11035,10 +11375,16 @@ Unknown task "${requested}".`,
11035
11375
  lines.push(`Ran: ${result.ran.join(", ")}`);
11036
11376
  if (result.failed.length > 0)
11037
11377
  lines.push(`Failed: ${result.failed.join(", ")}`);
11378
+ if ((result.failureDetails?.length ?? 0) > 0) {
11379
+ lines.push("Failure details:", ...(result.failureDetails ?? []).map((detail) => `- ${detail}`));
11380
+ }
11038
11381
  if (result.skippedNoWork.length > 0)
11039
11382
  lines.push(`Skipped (no work): ${result.skippedNoWork.join(", ")}`);
11040
11383
  if (result.deferredBusy.length > 0)
11041
11384
  lines.push(`Busy: ${result.deferredBusy.join(", ")} — another dream task holds this domain's lease; retry in a minute`);
11385
+ if (Object.keys(result.backlogAfter ?? {}).length > 0) {
11386
+ lines.push("", "Backlog at run end:", formatDreamTaskBacklogs(result.backlogAfter));
11387
+ }
11042
11388
  if (lines.length === 0)
11043
11389
  lines.push("No enabled dream tasks to run.");
11044
11390
  sendCtxStatusMessage(pi, {
@@ -27175,7 +27521,7 @@ function formatExecuteThreshold(detail, contextLimit) {
27175
27521
  }
27176
27522
  return `${percentage}%${clampNote}`;
27177
27523
  }
27178
- function executeStatus(db, sessionId, protectedTags, executeThresholdPercentageConfig = DEFAULT_EXECUTE_THRESHOLD_PERCENTAGE, liveModelKey, historyBudgetPercentage, commitClusterTrigger, executeThresholdTokens, contextLimit) {
27524
+ function executeStatus(db, sessionId, protectedTags, executeThresholdPercentageConfig = DEFAULT_EXECUTE_THRESHOLD_PERCENTAGE, liveModelKey, historyBudgetPercentage, commitClusterTrigger, executeThresholdTokens, contextLimit, dreamer) {
27179
27525
  const thresholdDetail = resolveExecuteThresholdDetail(executeThresholdPercentageConfig, liveModelKey, DEFAULT_EXECUTE_THRESHOLD_PERCENTAGE, {
27180
27526
  tokensConfig: executeThresholdTokens,
27181
27527
  contextLimit,
@@ -27230,6 +27576,11 @@ function executeStatus(db, sessionId, protectedTags, executeThresholdPercentageC
27230
27576
  `**Protected tags:** ${protectedTags}`,
27231
27577
  `**Subagent session:** ${meta.isSubagent}`
27232
27578
  ];
27579
+ if (dreamer?.backlog && Object.keys(dreamer.backlog).length > 0) {
27580
+ lines.push("", "### Dreamer", ...dreamer.progress ? [
27581
+ `- Running: ${dreamer.progress.task} — ${dreamer.progress.processed}/${dreamer.progress.total} processed`
27582
+ ] : [], ...formatDreamTaskBacklogs(dreamer.backlog).split("\\n"));
27583
+ }
27233
27584
  if (meta.lastContextPercentage > 0 || meta.lastInputTokens > 0) {
27234
27585
  lines.push("", "### Context Usage", `- Last percentage: ${meta.lastContextPercentage.toFixed(1)}%`, `- Last input tokens: ${meta.lastInputTokens.toLocaleString()}`, `- Resolved context limit: ${displayContextLimit > 0 ? displayContextLimit.toLocaleString() : "unknown"}`, `- Proactive compartment evaluation: ${proactiveCompartmentTrigger}%`, `- Post-drop target for historian: ${(executeThresholdPercentage * POST_DROP_TARGET_RATIO).toFixed(0)}% (${executeThresholdPercentage}% * ${POST_DROP_TARGET_RATIO})`, `- Commit cluster trigger: ${commitClusterTrigger?.enabled !== false ? `enabled (min ${commitClusterTrigger?.min_clusters ?? 3} clusters)` : "disabled"}, tail-size trigger: > 3x compartment budget`);
27235
27586
  }
@@ -27252,6 +27603,12 @@ ${c.content}
27252
27603
  lines.push(`- §${op.tagId}§ → ${op.operation}`);
27253
27604
  }
27254
27605
  }
27606
+ if (dreamer?.backlog && Object.keys(dreamer.backlog).length > 0) {
27607
+ lines.push("", "### Dreamer Backlog", formatDreamTaskBacklogs(dreamer.backlog));
27608
+ }
27609
+ if (dreamer?.progress) {
27610
+ lines.push("", "### Dreamer Progress", `- ${dreamer.progress.task}: ${dreamer.progress.processed}/${dreamer.progress.total} processed this run`);
27611
+ }
27255
27612
  return lines.join(`
27256
27613
  `);
27257
27614
  } catch (error) {
@@ -27326,7 +27683,7 @@ ${c.content}
27326
27683
  // package.json
27327
27684
  var package_default = {
27328
27685
  name: "@cortexkit/pi-magic-context",
27329
- version: "0.34.0",
27686
+ version: "0.34.2",
27330
27687
  type: "module",
27331
27688
  description: "Pi coding agent extension for Magic Context — cross-session memory and context management",
27332
27689
  main: "dist/index.js",
@@ -27834,7 +28191,9 @@ No active Pi session is available.`,
27834
28191
  model: ctx.model,
27835
28192
  detectedContextLimit
27836
28193
  });
27837
- const statusText = executeStatus(currentDeps.db, sessionId, currentDeps.protectedTags ?? 20, currentDeps.executeThresholdPercentage, modelKey, currentDeps.historyBudgetPercentage, currentDeps.commitClusterTrigger, currentDeps.executeThresholdTokens, usableContextLimit);
28194
+ const statusText = executeStatus(currentDeps.db, sessionId, currentDeps.protectedTags ?? 20, currentDeps.executeThresholdPercentage, modelKey, currentDeps.historyBudgetPercentage, currentDeps.commitClusterTrigger, currentDeps.executeThresholdTokens, usableContextLimit, {
28195
+ backlog: getDreamTaskBacklogs(currentDeps.db, currentDeps.projectIdentity, CANONICAL_DREAM_TASKS)
28196
+ });
27838
28197
  const details = buildStatusDetails(currentDeps, sessionId);
27839
28198
  sendCtxStatusMessage(pi, { title: "/ctx-status", text: statusText, level: "info" }, details);
27840
28199
  } catch (error) {
@@ -27876,6 +28235,7 @@ function buildStatusDetails(deps, sessionId) {
27876
28235
  dreamer: {
27877
28236
  enabled: deps.dreamer?.runnable === true,
27878
28237
  scheduleSummary: deps.dreamer?.scheduleSummary ?? null,
28238
+ backlog: getDreamTaskBacklogs(deps.db, deps.projectIdentity, CANONICAL_DREAM_TASKS),
27879
28239
  lastRunAt: getMostRecentTaskRunAt(deps.db, deps.projectIdentity)
27880
28240
  },
27881
28241
  historian: readHistorianState(deps.db, sessionId, meta)
@@ -28838,7 +29198,7 @@ async function startPiMagicContextRuntime(pi, database, dbPath) {
28838
29198
  injectDocs: cfg.dreamer?.inject_docs !== false,
28839
29199
  injectionBudgetTokens: cfg.memory.injection_budget_tokens,
28840
29200
  temporalAwareness: cfg.temporal_awareness === true,
28841
- muralEnabled: cfg.experimental?.mural?.enabled === true
29201
+ muralEnabled: cfg.mural.enabled === true
28842
29202
  },
28843
29203
  scheduler: {
28844
29204
  executeThresholdPercentage: cfg.execute_threshold_percentage,