diff --git a/src/core/services/presenceService.ts b/src/core/services/presenceService.ts index a020d1d..61a8e1b 100644 --- a/src/core/services/presenceService.ts +++ b/src/core/services/presenceService.ts @@ -22,6 +22,15 @@ import { VERSION } from '../../version.js'; export const ACTIVE_WINDOW_MS = 2 * 60_000; /** Longer than this without any action ⇒ the agent is stale/offline. */ export const STALE_WINDOW_MS = 10 * 60_000; +/** + * Ab wann gilt ein Agent als DORMANT (der Architekt muss eingreifen)? + * + * `inLoop` allein taugt dafür NICHT: es ist false, sobald ein Agent einen Task + * ausführt — ein hart arbeitender Agent sähe damit genauso aus wie ein toter. + * Entscheidend ist, wie lange der letzte Check-in her ist. Alles darunter ist + * normale Arbeit und darf keinen Alarm auslösen. + */ +export const DORMANT_AFTER_MS = 5 * 60_000; export type AgentLight = 'active' | 'busy' | 'idle' | 'stale'; diff --git a/src/server/routes.ts b/src/server/routes.ts index 3868c04..00fa18b 100644 --- a/src/server/routes.ts +++ b/src/server/routes.ts @@ -11,7 +11,7 @@ import { getStatus, updateStatus } from '../core/services/statusService.js'; import { autoDelegate, suggestDelegation } from '../core/services/delegateService.js'; import { computeBudget } from '../core/services/budgetService.js'; import { getRoster } from '../core/services/rosterService.js'; -import { computeHealth, enterLoop, leaveLoop, stampSeen } from '../core/services/presenceService.js'; +import { computeHealth, enterLoop, leaveLoop, stampSeen, DORMANT_AFTER_MS } from '../core/services/presenceService.js'; import { resolvePending, hasPending } from '../core/services/checkinService.js'; import { loadConfig, saveConfig } from '../core/config.js'; import { agentIdentity } from '../core/services/identityService.js'; @@ -96,8 +96,23 @@ export async function registerRoutes(app: FastifyInstance, cwd: string): Promise assignedTo: task.assignedTo, updatedAt: task.updatedAt, })), + // Zwei verschiedene Zustände, die nicht gleich behandelt werden dürfen: + // + // a) `loopExitReason` gesetzt → der Agent hat den Loop AUSDRÜCKLICH + // verlassen ("turn ended"). Er hört ab sofort nicht mehr zu; ein + // Reopen erreicht ihn nie. Sofort melden, ohne Wartezeit. + // b) kein Exit-Grund, aber ein Task in Arbeit → der Agent FÜHRT gerade + // aus. `inLoop` ist dabei immer false; ohne Zeitbedingung wäre jeder + // hart arbeitende Agent "dormant" und der Architekt würde Leute + // anstoßen, die einwandfrei laufen. Erst nach DORMANT_AFTER_MS ohne + // Check-in ist das ein Fall für mich. dormantAgents: health.agents - .filter((agent) => !agent.inLoop && (agent.taskId || agent.loopExitReason)) + .filter((agent) => { + if (agent.inLoop) return false; + if (agent.loopExitReason) return true; + if (!agent.taskId) return false; + return agent.lastSeenAgoSec === undefined || agent.lastSeenAgoSec * 1000 >= DORMANT_AFTER_MS; + }) .map((agent) => ({ name: agent.name, taskId: agent.taskId,