feat(watch): --ignore-from — Notifier nicht vom Watchdog zumüllen lassen

Der Architekten-Message-Notifier (`watch --await-message claude`) feuerte bei
JEDER Nachricht — auch bei den Watchdog-Erinnerungen, die alle paar Minuten
eintreffen ("review waiting 5m/10m/15m…"). Real erlebt: dreimal hintereinander
geweckt worden, ohne dass etwas Neues passiert war. Ein Signal, das im Takt
seiner eigenen Erinnerungen feuert, entwertet sich selbst.

`--ignore-from <agents>` blendet Absender aus; message-Events tragen dafür
jetzt `from` (bisher nur im title). Armung des Architekten künftig mit
`--ignore-from agenthub` — echte Agenten-Post weckt weiterhin sofort, die
Watchdog-Erinnerungen sieht man beim Pollen ohnehin.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
chahinebrini 2026-07-29 19:47:11 +02:00
parent 7a4ccaeb61
commit c2e19b3371
4 changed files with 15 additions and 4 deletions

View File

@ -168,10 +168,16 @@ async function fetchUnreadMessages(serverUrl: string, agent: string): Promise<Ag
}
}
function isMessageFor(event: AgentHubEvent, agent: string): boolean {
function isMessageFor(event: AgentHubEvent, agent: string, ignoreFrom: string[] = []): boolean {
if (event.type !== 'message' || event.action !== 'created') return false;
const to = event.assignedTo;
return !!to && messageRecipientAliases(agent).has(String(to).toLowerCase());
if (!to || !messageRecipientAliases(agent).has(String(to).toLowerCase())) return false;
// Absender ausblenden, die den Notifier nur zumüllen. Konkreter Fall: der
// Watchdog schreibt dem Architekten alle paar Minuten Erinnerungen — ohne
// Filter weckt der Notifier ihn im Takt dieser Erinnerungen, obwohl nichts
// Neues passiert ist, und das Signal entwertet sich selbst.
const sender = String(event.from ?? (event.title ?? '').split('→')[0] ?? '').trim().toLowerCase();
return !ignoreFrom.some((ignored) => ignored.trim().toLowerCase() === sender);
}
/**
@ -188,7 +194,7 @@ function isMessageFor(event: AgentHubEvent, agent: string): boolean {
*/
export async function watchEvents(
serverUrl: string,
options: { once?: boolean; role?: string; awaitReview?: boolean; awaitMessage?: string; newOnly?: boolean } = {},
options: { once?: boolean; role?: string; awaitReview?: boolean; awaitMessage?: string; newOnly?: boolean; ignoreFrom?: string[] } = {},
): Promise<void> {
const url = new URL('/events', serverUrl);
// Pass role to the server for an additional server-side filter (saves
@ -280,7 +286,7 @@ export async function watchEvents(
await reader.cancel();
return;
}
if (options.awaitMessage && isMessageFor(event, options.awaitMessage)) {
if (options.awaitMessage && isMessageFor(event, options.awaitMessage, options.ignoreFrom)) {
await reader.cancel();
return;
}

View File

@ -927,6 +927,7 @@ export function createProgram(cwd: string): Command {
.option('--await-review', 'Exit when an implementer submits (task → review); architect review-queue notifier')
.option('--await-message <agent>', 'Exit when an unread message arrives for agent/role; architect message notifier')
.option('--new-only', 'With --await-review/--await-message: ignore existing backlog on connect (re-armable without spinning)')
.option('--ignore-from <agents>', 'With --await-message: comma-separated senders to ignore (e.g. "agenthub" to mute watchdog reminders)')
.action(async (options) => {
const { serverUrl } = await resolveContext(program, cwd);
if (!serverUrl) {
@ -939,6 +940,7 @@ export function createProgram(cwd: string): Command {
role: options.role as string | undefined,
awaitReview: options.awaitReview as boolean | undefined,
awaitMessage: options.awaitMessage as string | undefined,
ignoreFrom: typeof options.ignoreFrom === 'string' ? options.ignoreFrom.split(',') : undefined,
newOnly: options.newOnly as boolean | undefined,
});
});

View File

@ -15,6 +15,8 @@ export interface AgentHubEvent {
status?: string;
role?: string;
assignedTo?: string;
/** Absender bei message-Events — erlaubt Empfängern, System-Post zu filtern. */
from?: string;
claimedBy?: string;
reviewer?: string;
}

View File

@ -675,6 +675,7 @@ export async function registerRoutes(app: FastifyInstance, cwd: string): Promise
id: message.id,
title: `${message.from}${message.to}`,
assignedTo: message.to,
from: message.from,
},
message.updatedAt,
);