basant307/AI_Governance_Project
048
1/**2 * @license3 * Copyright 2025 Qwen4 * SPDX-License-Identifier: Apache-2.05 */6 7/**8 * Warnings we know about and want to keep out of the user-facing terminal.9 * Listener accumulation on long-lived AbortSignals during multi-round agent10 * sessions is structural, not a real memory leak — the listeners are removed11 * (via {once:true} + reverse-cleanup in utils/abortController.ts) but a few12 * extreme cases (e.g. OpenAI retry storms layered with multiple wrappers) can13 * still graze the per-signal cap. Match any MaxListenersExceededWarning that14 * mentions AbortSignal so we cover every shape Node ≥20 emits — `[AbortSignal]`,15 * `[AbortSignal{...}]`, `[AbortSignal { ... }]`. We deliberately don't match16 * the generic `EventTarget` token so unrelated EventTarget leaks stay visible.17 */18const SUPPRESSED_WARNINGS: RegExp[] = [19 /MaxListenersExceededWarning.*AbortSignal/,20];21 22function isSuppressed(warning: Error): boolean {23 const text = `${warning.name}: ${warning.message}`;24 return SUPPRESSED_WARNINGS.some((re) => re.test(text));25}26 27function isDebugMode(): boolean {28 if (process.env['NODE_ENV'] === 'development') return true;29 const truthy = (v: string | undefined) =>30 !!v && v !== '0' && v.toLowerCase() !== 'false';31 return truthy(process.env['DEBUG']) || truthy(process.env['QWEN_DEBUG']);32}33 34let installedHandler: ((warning: Error) => void) | null = null;35 36/**37 * For tests only — uninstall the handler and reset internal state.38 */39export function resetWarningHandlerForTests(): void {40 if (installedHandler) {41 process.removeListener('warning', installedHandler);42 installedHandler = null;43 }44}45 46/**47 * Install a process-level `warning` handler that swallows the well-known48 * `MaxListenersExceededWarning` for AbortSignal while letting every other49 * warning through — including generic EventTarget leak warnings, which we50 * leave visible because they likely indicate a real leak elsewhere. In51 * debug mode (NODE_ENV=development, or DEBUG / QWEN_DEBUG set), all52 * warnings are forwarded so developers can still see them.53 *54 * Implementation note: simply adding a `warning` listener does NOT prevent55 * Node's default printer from writing to stderr — the default handler is56 * registered as an ordinary listener (`lib/internal/process/warning.js`).57 * To actually suppress targeted warnings, we capture the existing listeners58 * (which include the default printer and any third-party telemetry hooks),59 * remove them, then install ours as the sole listener. Non-suppressed60 * warnings get fanned out to the captured listeners so the default printer61 * still fires for them; suppressed warnings stop here.62 *63 * Idempotent — repeated calls are a no-op.64 */65export function initializeWarningHandler(): void {66 if (installedHandler) return;67 68 // Snapshot everything currently listening on 'warning' (Node's default69 // onWarning printer + any third-party telemetry subscribers). We will fan70 // out non-suppressed warnings back to them.71 //72 // Trade-offs to be aware of (documented for future readers):73 // - Listeners ADDED via `process.on('warning', ...)` after this init are74 // independent of our snapshot. They receive `process.emit('warning')`75 // directly and bypass the suppression filter. Node's default printer76 // is in our snapshot (not added later), so stderr stays clean; late77 // telemetry will see the full warning stream including AbortSignal78 // leaks. This is intentional — telemetry should see what's happening.79 // - Listeners REMOVED via `process.removeListener('warning', fn)` after80 // this init have no effect: we hold our own strong reference in the81 // snapshot. Re-snapshotting per warning doesn't fix this because the82 // listeners are already removed from Node's list (we called83 // `process.removeAllListeners` to disable Node's default printing of84 // suppressed warnings). Callers who need conditional fan-out should85 // install BEFORE initializeWarningHandler.86 const priorListeners = [...process.listeners('warning')] as Array<87 (warning: Error) => void88 >;89 90 installedHandler = (warning: Error) => {91 // Evaluate isDebugMode() per warning so DEBUG / QWEN_DEBUG can be92 // toggled at runtime (e.g. via a `/debug` slash command) without93 // re-running initializeWarningHandler.94 if (!isDebugMode() && isSuppressed(warning)) return;95 for (const fn of priorListeners) {96 try {97 fn(warning);98 } catch {99 // Don't let a misbehaving prior listener (e.g. a buggy telemetry100 // hook) take down warning delivery for the rest of the chain.101 }102 }103 };104 105 process.removeAllListeners('warning');106 process.on('warning', installedHandler);107}108 