feat: make heartbeat trigger incremental reviews and suggest from graph changes

Heartbeat no longer writes its own report; it triggers the shared incremental review and, per plan intervention, derives evidence-backed suggestions from the review's graph changes. Manual review is incremental by default with explicit reanalysis. The Supervisor tab is renamed Smart heartbeat with heartbeat plans, and the redundant inner plan frame is removed.
This commit is contained in:
mesalogo
2026-09-30 10:45:22 +08:00
parent d30f794184
commit 6293ef105d
51 changed files with 2056 additions and 2150 deletions
@@ -1,6 +1,6 @@
import { createHash, randomUUID } from 'node:crypto'
import type { DatabaseSync } from 'node:sqlite'
import type { SupervisionEvidence, SupervisionRunRequest, SupervisionSummaryOutput } from '../../shared/supervision-contracts'
import { isIncrementalReview, type SupervisionEvidence, type SupervisionRunRequest, type SupervisionSummaryOutput } from '../../shared/supervision-contracts'
import type { SupervisionReviewBatch, SupervisionReviewProgress, SupervisionReviewSettings } from '../../shared/supervision-review-contracts'
import { reviewScope } from './review-checkpoint'
@@ -84,7 +84,7 @@ export class SupervisionReviewStore {
length(s.body), CASE WHEN c.revision = review_revision(s.context) THEN c.processed_offset ELSE 0 END,
CASE WHEN c.revision = review_revision(s.context) THEN c.processed_offset ELSE 0 END
FROM supervision_review_current s LEFT JOIN review_checkpoints c
ON ? = 'heartbeat' AND c.stage = 'supervisor' AND c.scope = ? AND c.source = s.source
ON ? = 1 AND c.stage = 'supervisor' AND c.scope = ? AND c.source = s.source
WHERE ((s.occurred >= ? AND s.occurred <= ?) OR (s.alternate_time >= ? AND s.alternate_time <= ?))
AND (? = 'global' OR s.project_id IN (SELECT value FROM json_each(?)))
AND length(s.body) > 0
@@ -93,7 +93,7 @@ export class SupervisionReviewStore {
ORDER BY s.project_id, s.conversation_id, s.sequence, s.source LIMIT ?`)
let cursor: [string, string, number, string] = ['', '', -1, '']
for (;;) {
const page = insertPage.run(runId, request.trigger, reviewScope(request.scope), request.timeRange.from, request.timeRange.to,
const page = insertPage.run(runId, isIncrementalReview(request) ? 1 : 0, reviewScope(request.scope), request.timeRange.from, request.timeRange.to,
request.timeRange.from, request.timeRange.to, request.scope.kind,
JSON.stringify(request.scope.kind === 'projects' ? request.scope.projectIds : []), ...cursor, state.config.pageSize)
if (!page.changes) break
@@ -115,7 +115,7 @@ export class SupervisionReviewStore {
unfinished(request: SupervisionRunRequest): string | undefined {
return this.db.prepare(`SELECT s.id FROM supervision_runs s JOIN supervision_review_runs r ON r.run_id = s.id
WHERE s.trigger = 'heartbeat' AND s.scope_json = ? AND s.status IN ('paused', 'failed', 'running')
WHERE s.trigger = 'heartbeat' AND s.scope_json = ? AND s.status IN ('failed', 'running')
AND COALESCE(json_extract(r.state_json, '$.restartRequired'), 0) = 0
ORDER BY s.created_at LIMIT 1`).get(JSON.stringify(request.scope))?.id as string | undefined
}
@@ -280,7 +280,7 @@ export class SupervisionReviewStore {
commitCheckpoints(runId: string, request: SupervisionRunRequest): void {
this.assertComplete(runId)
if (!this.db.isTransaction) throw new Error('Review publication must be transactional')
if (request.trigger !== 'heartbeat') return
if (!isIncrementalReview(request)) return
this.db.prepare(`INSERT INTO review_checkpoints (stage, scope, source, revision, processed_offset, source_length)
SELECT 'supervisor', ?, source, revision, processed_offset, length FROM supervision_review_sources WHERE run_id = ?
ON CONFLICT(stage, scope, source) DO UPDATE SET revision = excluded.revision,