UNPKG

@memberjunction/a2aserver

Version:

MemberJunction Agent-To-Agent (A2A) Server Implementation

95 lines 3.06 kB
import { LogStatus } from "@memberjunction/core"; /** * Terminal task statuses — tasks in these states are eligible for cleanup * after the retention window elapses. */ export const TERMINAL_STATUSES = new Set(['completed', 'cancelled', 'failed']); /** * Bounded in-memory task store with periodic terminal-state cleanup. * * Replaces the unbounded module-level `Map<string, Task>` that was the single * largest leak found in the audit (R2-C11): tasks accumulated forever with * their `messages[]` and `artifacts[]` arrays, growing memory roughly as * `tasks × messages × artifact-bytes`. The store keeps the same `Map`-like * surface (`set`/`get`/`delete`/`has`/`size`) so callers don't change, and * adds a sweeper that drops tasks in terminal state older than `retentionMs`. * Implements `IShutdownable` so the sweeper timer is cleared during graceful * shutdown. */ export class TaskStore { constructor(options = {}) { this._tasks = new Map(); this._timer = null; this._sweepIntervalMs = options.sweepIntervalMs ?? 5 * 60 * 1000; this._retentionMs = options.retentionMs ?? 60 * 60 * 1000; } get ShutdownName() { return 'A2AServer.TaskStore'; } /** * Begin the periodic sweep. Idempotent; calling twice is safe. Uses `unref()` * so the sweep timer doesn't hold the process alive on its own. */ Start() { if (this._timer) return; this._timer = setInterval(() => this.Sweep(), this._sweepIntervalMs); if (this._timer && typeof this._timer.unref === 'function') { this._timer.unref(); } } /** * Stop the sweep timer and drop all tasks. Idempotent. */ Shutdown() { if (this._timer) { clearInterval(this._timer); this._timer = null; } this._tasks.clear(); } /** * Configured retention window in milliseconds. */ get RetentionMs() { return this._retentionMs; } /** * Drop terminal-state tasks that haven't been updated in `retentionMs`. * Returns the count evicted (useful for tests and diagnostics). */ Sweep(now = Date.now()) { const cutoff = now - this._retentionMs; let evicted = 0; for (const [id, task] of this._tasks) { if (TERMINAL_STATUSES.has(task.status) && task.updated.getTime() <= cutoff) { this._tasks.delete(id); evicted++; } } if (evicted > 0) { LogStatus(`A2AServer.TaskStore swept ${evicted} terminal task(s)`); } return evicted; } set(id, task) { this._tasks.set(id, task); return this; } get(id) { return this._tasks.get(id); } has(id) { return this._tasks.has(id); } delete(id) { return this._tasks.delete(id); } get size() { return this._tasks.size; } values() { return this._tasks.values(); } } //# sourceMappingURL=TaskStore.js.map