feat(background-agent): implement process cleanup for BackgroundManager

Add functionality to manage process cleanup by registering and unregistering signal listeners. This ensures that BackgroundManager instances properly shut down and remove their listeners on process exit. Introduce tests to verify listener removal after shutdown.
This commit is contained in:
Jeremy Gollehon
2026-01-14 23:11:38 -08:00
parent 02c907e128
commit ee601d845c
2 changed files with 95 additions and 21 deletions
+38 -1
View File
@@ -1,5 +1,6 @@
import { describe, test, expect, beforeEach } from "bun:test"
import { afterEach } from "bun:test"
import { tmpdir } from "node:os"
import type { PluginInput } from "@opencode-ai/plugin"
import type { BackgroundTask, ResumeInput } from "./types"
import { BackgroundManager } from "./manager"
@@ -167,7 +168,7 @@ function createBackgroundManager(): BackgroundManager {
prompt: async () => ({}),
},
}
return new BackgroundManager({ client, directory: "C:\\tmp" } as unknown as PluginInput)
return new BackgroundManager({ client, directory: tmpdir() } as unknown as PluginInput)
}
function getConcurrencyManager(manager: BackgroundManager): ConcurrencyManager {
@@ -186,6 +187,18 @@ async function tryCompleteTaskForTest(manager: BackgroundManager, task: Backgrou
return (manager as unknown as { tryCompleteTask: (task: BackgroundTask, source: string) => Promise<boolean> }).tryCompleteTask(task, "test")
}
function getCleanupSignals(): Array<NodeJS.Signals | "beforeExit" | "exit"> {
const signals: Array<NodeJS.Signals | "beforeExit" | "exit"> = ["SIGINT", "SIGTERM", "beforeExit", "exit"]
if (process.platform === "win32") {
signals.push("SIGBREAK")
}
return signals
}
function getListenerCounts(signals: Array<NodeJS.Signals | "beforeExit" | "exit">): Record<string, number> {
return Object.fromEntries(signals.map((signal) => [signal, process.listenerCount(signal)]))
}
describe("BackgroundManager.getAllDescendantTasks", () => {
let manager: MockBackgroundManager
@@ -1023,3 +1036,27 @@ describe("BackgroundManager.resume concurrency key", () => {
})
})
describe("BackgroundManager process cleanup", () => {
test("should remove listeners after last shutdown", () => {
// #given
const signals = getCleanupSignals()
const baseline = getListenerCounts(signals)
const managerA = createBackgroundManager()
const managerB = createBackgroundManager()
// #when
const afterCreate = getListenerCounts(signals)
managerA.shutdown()
const afterFirstShutdown = getListenerCounts(signals)
managerB.shutdown()
const afterSecondShutdown = getListenerCounts(signals)
// #then
for (const signal of signals) {
expect(afterCreate[signal]).toBe(baseline[signal] + 1)
expect(afterFirstShutdown[signal]).toBe(baseline[signal] + 1)
expect(afterSecondShutdown[signal]).toBe(baseline[signal])
}
})
})