feat(cleanup): hide and prune stale mailboxes
Mailbox listings grew unbounded as old sessions ended without unregistering. This adds two layers of cleanup, configurable via mailbox.json or `serve` flags: - Lazy filter: list responses (REST /v1/list, MCP list_mailboxes) drop mailboxes idle longer than hideAfterMinutes (default 24h), while always keeping the caller and any sender with messages pending for them. - Background sweep: startServer runs an initial prune on boot and schedules an unref'd interval timer that hard-deletes mailboxes idle longer than deleteAfterMinutes (default 7d) which have no pending messages, and wipes their delivered history.
This commit is contained in:
@@ -4,17 +4,26 @@ import { join, resolve } from "node:path";
|
||||
|
||||
export const DEFAULT_PORT = 37849;
|
||||
export const DEFAULT_BIND = "127.0.0.1";
|
||||
export const DEFAULT_HIDE_AFTER_MINUTES = 60 * 24;
|
||||
export const DEFAULT_DELETE_AFTER_MINUTES = 60 * 24 * 7;
|
||||
export const DEFAULT_SWEEP_INTERVAL_MINUTES = 60;
|
||||
|
||||
export interface FileConfig {
|
||||
port?: number;
|
||||
bind?: string;
|
||||
dbPath?: string;
|
||||
hideAfterMinutes?: number;
|
||||
deleteAfterMinutes?: number;
|
||||
sweepIntervalMinutes?: number;
|
||||
}
|
||||
|
||||
export interface DaemonConfig {
|
||||
port: number;
|
||||
bind: string;
|
||||
dbPath: string;
|
||||
hideAfterMinutes: number;
|
||||
deleteAfterMinutes: number;
|
||||
sweepIntervalMinutes: number;
|
||||
}
|
||||
|
||||
export function defaultDbPath(): string {
|
||||
@@ -65,6 +74,12 @@ export function loadFileConfig(explicitPath?: string): FileConfig {
|
||||
port: typeof parsed.port === "number" ? parsed.port : undefined,
|
||||
bind: typeof parsed.bind === "string" ? parsed.bind : undefined,
|
||||
dbPath: typeof parsed.dbPath === "string" ? parsed.dbPath : undefined,
|
||||
hideAfterMinutes:
|
||||
typeof parsed.hideAfterMinutes === "number" ? parsed.hideAfterMinutes : undefined,
|
||||
deleteAfterMinutes:
|
||||
typeof parsed.deleteAfterMinutes === "number" ? parsed.deleteAfterMinutes : undefined,
|
||||
sweepIntervalMinutes:
|
||||
typeof parsed.sweepIntervalMinutes === "number" ? parsed.sweepIntervalMinutes : undefined,
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -76,6 +91,9 @@ export interface ServeOverrides {
|
||||
bind?: string;
|
||||
dbPath?: string;
|
||||
config?: string;
|
||||
hideAfterMinutes?: number;
|
||||
deleteAfterMinutes?: number;
|
||||
sweepIntervalMinutes?: number;
|
||||
}
|
||||
|
||||
export function resolveConfig(overrides: ServeOverrides): DaemonConfig {
|
||||
@@ -83,7 +101,20 @@ export function resolveConfig(overrides: ServeOverrides): DaemonConfig {
|
||||
const port = overrides.port ?? file.port ?? DEFAULT_PORT;
|
||||
const bind = overrides.bind ?? file.bind ?? DEFAULT_BIND;
|
||||
const dbPathRaw = overrides.dbPath ?? file.dbPath ?? defaultDbPath();
|
||||
return { port, bind, dbPath: expandPath(dbPathRaw) };
|
||||
const hideAfterMinutes =
|
||||
overrides.hideAfterMinutes ?? file.hideAfterMinutes ?? DEFAULT_HIDE_AFTER_MINUTES;
|
||||
const deleteAfterMinutes =
|
||||
overrides.deleteAfterMinutes ?? file.deleteAfterMinutes ?? DEFAULT_DELETE_AFTER_MINUTES;
|
||||
const sweepIntervalMinutes =
|
||||
overrides.sweepIntervalMinutes ?? file.sweepIntervalMinutes ?? DEFAULT_SWEEP_INTERVAL_MINUTES;
|
||||
return {
|
||||
port,
|
||||
bind,
|
||||
dbPath: expandPath(dbPathRaw),
|
||||
hideAfterMinutes,
|
||||
deleteAfterMinutes,
|
||||
sweepIntervalMinutes,
|
||||
};
|
||||
}
|
||||
|
||||
export function baseUrl(cfg: { port: number; bind: string }): string {
|
||||
|
||||
Reference in New Issue
Block a user