agenthub/src/server/statusRefresh.ts
chahinebrini 961675ac40 fix(server): auto-regenerate status snapshot on every change
The status snapshot (.agenthub/status/latest.md) was only refreshed on an
explicit `status --update`, so it drifted stale and misled agents that
follow golden rule #2 ("read status/latest.md") — e.g. an implementer
seeing an old task list and picking the wrong task.

- src/server/statusRefresh.ts: subscribe to the event bus and regenerate
  the snapshot on every task/handoff/decision/memory change (REST and
  filesystem-watch paths), debounced 300 ms. Presence ('agent') events
  are skipped; the snapshot lives outside the watched dirs so it can't
  feed back into the watcher.
- wired into startServer lifecycle (start + stop on close).
- test: status/latest.md reflects a new task shortly after POST /tasks.
  117/117 green.

Bump 0.3.0 -> 0.3.1.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-27 17:27:19 +02:00

41 lines
1.4 KiB
TypeScript

import { eventBus } from './events.js';
import type { AgentHubEvent } from './events.js';
import { updateStatus } from '../core/services/statusService.js';
/**
* Keep `.agenthub/status/latest.md` fresh. Agents are told (golden rule #2) to
* read the status snapshot, but it was only regenerated on an explicit
* `status --update` — so it drifted stale and misled agents (e.g. showing an
* old task list). This subscribes to the in-process event bus and regenerates
* the snapshot on every entity change, from both the REST and filesystem-watch
* paths, debounced so a burst collapses to one write.
*
* Presence ('agent') events are ephemeral and don't affect status. Writing the
* snapshot lives in `.agenthub/status/` — outside the watched entity dirs — so
* it never feeds back into the watcher.
*/
const DEBOUNCE_MS = 300;
export function startStatusAutoRefresh(cwd: string): () => void {
let timer: NodeJS.Timeout | undefined;
const onChange = (event: AgentHubEvent) => {
if (event.type === 'agent') return;
if (timer) clearTimeout(timer);
timer = setTimeout(() => {
timer = undefined;
try {
updateStatus(cwd);
} catch {
// best-effort — a failed status refresh must never break the server
}
}, DEBOUNCE_MS);
};
eventBus.on('change', onChange);
return () => {
if (timer) clearTimeout(timer);
eventBus.off('change', onChange);
};
}