feat(FN-1399): add background memory summarization after task completion

- Add MemoryInsights class in @fusion/core for AI-powered memory audit generation
- Add post-run hook to CronRunner for triggering memory summarization after scheduled tasks
- Wire memory background processing in both dashboard and serve commands
- Add memoryAuditEnabled and memoryAuditSchedule settings for configurable automation
- Fix startup ordering: sync automation before cronRunner.start() to prevent race conditions
- Add comprehensive tests for memory-insights and dashboard/serve integration
- Update contributing.md and settings-reference.md with documentation
This commit is contained in:
gsxdsm
2026-04-09 16:14:44 -07:00
parent 8ba375549c
commit daabb51e71
13 changed files with 1803 additions and 4 deletions

View File

@@ -1,8 +1,8 @@
import { execSync } from "node:child_process";
import type { AddressInfo } from "node:net";
import { createInterface } from "node:readline";
import { TaskStore, AutomationStore, CentralCore, AgentStore, getTaskMergeBlocker } from "@fusion/core";
import type { Settings, TaskDetail, PrInfo } from "@fusion/core";
import { TaskStore, AutomationStore, CentralCore, AgentStore, getTaskMergeBlocker, syncInsightExtractionAutomation, INSIGHT_EXTRACTION_SCHEDULE_NAME, processAndAuditInsightExtraction } from "@fusion/core";
import type { Settings, TaskDetail, PrInfo, ScheduledTask, AutomationRunResult } from "@fusion/core";
import { createServer, GitHubClient } from "@fusion/dashboard";
import { TriageProcessor, TaskExecutor, Scheduler, AgentSemaphore, WorktreePool, aiMergeTask, UsageLimitPauser, PRIORITY_MERGE, scanIdleWorktrees, cleanupOrphanedWorktrees, NtfyNotifier, PrMonitor, PrCommentHandler, CronRunner, StuckTaskDetector, SelfHealingManager, MissionAutopilot, createAiPromptExecutor, HeartbeatMonitor, HeartbeatTriggerScheduler, type WakeContext } from "@fusion/engine";
import { AuthStorage, DefaultPackageManager, ModelRegistry, SettingsManager, discoverAndLoadExtensions, getAgentDir, createExtensionRuntime } from "@mariozechner/pi-coding-agent";
@@ -864,8 +864,65 @@ export async function runDashboard(port: number, opts: { paused?: boolean; dev?:
missionAutopilot.setScheduler(scheduler);
// ── CronRunner: scheduled task execution ──────────────────────────
// Post-run callback for memory insight extraction processing
const onMemoryInsightRunProcessed = async (
schedule: ScheduledTask,
result: AutomationRunResult,
): Promise<void> => {
// Only process the memory insight extraction schedule
if (schedule.name !== INSIGHT_EXTRACTION_SCHEDULE_NAME) {
return;
}
// Extract the AI step output from the result
const stepResults = result.stepResults ?? [];
const aiStep = stepResults.find((sr) => sr.stepName === "Extract Memory Insights");
if (!aiStep) {
console.log(`[memory-audit] No insight extraction step found in ${schedule.name} result`);
return;
}
console.log(`[memory-audit] Processing memory insight extraction run...`);
try {
const auditReport = await processAndAuditInsightExtraction(cwd, {
rawResponse: aiStep.output ?? "",
stepSuccess: aiStep.success,
runAt: result.startedAt,
error: aiStep.error,
});
console.log(
`[memory-audit] ✓ Audit complete — Health: ${auditReport.health}, ` +
`Insights: ${auditReport.insightsMemory.insightCount}`,
);
} catch (err) {
console.error(
`[memory-audit] ✗ Failed to process insight extraction: ${err instanceof Error ? err.message : String(err)}`,
);
}
};
const aiPromptExecutor = await createAiPromptExecutor(cwd);
const cronRunner = new CronRunner(store, automationStore, { aiPromptExecutor });
const cronRunner = new CronRunner(store, automationStore, {
aiPromptExecutor,
onScheduleRunProcessed: onMemoryInsightRunProcessed,
});
// ── Sync insight extraction automation on startup ─────────────────
// Run sync BEFORE starting the cron runner to avoid stale config races.
// This ensures the insight extraction schedule is created/updated/deleted
// before the first tick can execute it.
try {
await syncInsightExtractionAutomation(automationStore, settings);
} catch (err) {
console.error(
`[memory-audit] Failed to sync insight extraction automation: ${err instanceof Error ? err.message : String(err)}`,
);
}
cronRunner.start();
triage.start();
@@ -965,6 +1022,29 @@ export async function runDashboard(port: number, opts: { paused?: boolean; dev?:
}
});
// ── Insight extraction automation sync on settings change ─────────
// When insight extraction settings change (enable/disable/schedule/min interval),
// resync the automation schedule without requiring a restart.
registerHandler(store, "settings:updated", async ({ settings: s, previous: prev }) => {
const insightKeys = [
"insightExtractionEnabled",
"insightExtractionSchedule",
"insightExtractionMinIntervalMs",
] as const;
const relevantKeyChanged = insightKeys.some((key) => s[key] !== prev[key]);
if (relevantKeyChanged) {
try {
await syncInsightExtractionAutomation(automationStore, s);
console.log("[memory-audit] Insight extraction automation synced with settings");
} catch (err) {
console.error(
`[memory-audit] Failed to sync insight extraction automation: ${err instanceof Error ? err.message : String(err)}`,
);
}
}
});
// ── Periodic retry: catch failed merges on each poll cycle ────────
// Uses a setTimeout chain so the interval dynamically follows
// settings.pollIntervalMs without requiring an engine restart.