Sync source failure Agent Activity messages

This commit is contained in:
Admin 2026-09-29 06:46:04 +00:00
parent 679c8c198a
commit 00b95667c9
2 changed files with 67 additions and 3 deletions

View File

@ -99,6 +99,7 @@ export async function migrate() {
body TEXT NOT NULL,
related_type TEXT,
related_id TEXT,
dedupe_key TEXT UNIQUE,
acknowledged_at TIMESTAMPTZ,
acknowledged_by TEXT,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
@ -127,6 +128,8 @@ export async function migrate() {
await pool.query("ALTER TABLE sources ADD COLUMN IF NOT EXISTS baseline_complete BOOLEAN NOT NULL DEFAULT FALSE");
await pool.query("ALTER TABLE signals ADD COLUMN IF NOT EXISTS change_kind TEXT NOT NULL DEFAULT 'new'");
await pool.query("ALTER TABLE agent_messages ADD COLUMN IF NOT EXISTS dedupe_key TEXT");
await pool.query("CREATE UNIQUE INDEX IF NOT EXISTS idx_agent_messages_dedupe_key ON agent_messages(dedupe_key)");
const marker = await pool.query(
`INSERT INTO app_meta(key,value)
@ -525,3 +528,37 @@ export async function acknowledgeAgentMessage(id: string, acknowledgedBy: string
);
return result.rows[0];
}
export async function postSystemMessageOnce(args: {
dedupeKey: string;
title: string;
body: string;
severity?: string;
kind?: string;
relatedType?: string | null;
relatedId?: string | null;
}) {
const id = randomUUID();
const result = await pool.query(
`INSERT INTO agent_messages(
id,actor_sub,actor_name,actor_client_id,severity,kind,title,body,
related_type,related_id,dedupe_key
)
VALUES($1,'system:source-monitor','Source Monitor','media-intelligence-worker',
$2,$3,$4,$5,$6,$7,$8)
ON CONFLICT(dedupe_key) DO NOTHING
RETURNING *`,
[
id,
args.severity ?? "warning",
args.kind ?? "warning",
args.title,
args.body,
args.relatedType ?? null,
args.relatedId ?? null,
args.dedupeKey
]
);
return result.rows[0] ?? null;
}

View File

@ -1,5 +1,5 @@
import { randomUUID } from "node:crypto";
import { pool, storeItem } from "./db.js";
import { createHash, randomUUID } from "node:crypto";
import { pool, postSystemMessageOnce, storeItem } from "./db.js";
import { fetchSource, SOURCES } from "./sources.js";
import { scoreItem } from "./scoring.js";
import type { SourceId } from "./types.js";
@ -14,12 +14,14 @@ export async function syncSource(sourceId: SourceId) {
"INSERT INTO source_runs(id,source_id,status) VALUES($1,$2,'running')",
[runId, sourceId]
);
let previousError: string | null = null;
try {
const sourceState = await pool.query(
"SELECT baseline_complete FROM sources WHERE id=$1",
"SELECT baseline_complete,last_error FROM sources WHERE id=$1",
[sourceId]
);
const baseline = !Boolean(sourceState.rows[0]?.baseline_complete);
previousError = sourceState.rows[0]?.last_error ?? null;
const items = await fetchSource(sourceId);
let created = 0;
@ -43,6 +45,19 @@ export async function syncSource(sourceId: SourceId) {
[sourceId, baseline && items.length > 0]
);
if (previousError) {
const errorHash = createHash("sha256").update(previousError).digest("hex").slice(0, 16);
await postSystemMessageOnce({
dedupeKey: `source-recovered:${sourceId}:${errorHash}`,
title: `${source.name} recovered`,
body: `The source sync is healthy again after the previous error: ${previousError}`,
severity: "notice",
kind: "update",
relatedType: "source",
relatedId: sourceId
});
}
return {
sourceId,
mode: baseline ? "baseline" : "live",
@ -60,6 +75,18 @@ export async function syncSource(sourceId: SourceId) {
"UPDATE sources SET last_synced_at=NOW(),last_error=$2 WHERE id=$1",
[sourceId, message]
);
const errorHash = createHash("sha256").update(message).digest("hex").slice(0, 16);
await postSystemMessageOnce({
dedupeKey: `source-error:${sourceId}:${errorHash}`,
title: `${source.name} sync failed`,
body: message,
severity: "warning",
kind: "warning",
relatedType: "source",
relatedId: sourceId
});
throw error;
}
}