test(background-agent): add stale detection unit tests

This commit is contained in:
justsisyphus
2026-01-17 17:43:16 +09:00
parent 1b6037bbdf
commit 638842966f

View File

@@ -1060,3 +1060,253 @@ describe("BackgroundManager process cleanup", () => {
})
})
describe("BackgroundManager.checkAndInterruptStaleTasks", () => {
test("should NOT interrupt task running less than 30 seconds (min runtime guard)", async () => {
const client = {
session: {
prompt: async () => ({}),
abort: async () => ({}),
},
}
const manager = new BackgroundManager({ client, directory: tmpdir() } as unknown as PluginInput, { staleTimeoutMs: 180_000 })
const task: BackgroundTask = {
id: "task-1",
sessionID: "session-1",
parentSessionID: "parent-1",
parentMessageID: "msg-1",
description: "Test task",
prompt: "Test",
agent: "test-agent",
status: "running",
startedAt: new Date(Date.now() - 20_000),
progress: {
toolCalls: 0,
lastUpdate: new Date(Date.now() - 200_000),
},
}
manager["tasks"].set(task.id, task)
await manager["checkAndInterruptStaleTasks"]()
expect(task.status).toBe("running")
})
test("should NOT interrupt task with recent lastUpdate", async () => {
const client = {
session: {
prompt: async () => ({}),
abort: async () => ({}),
},
}
const manager = new BackgroundManager({ client, directory: tmpdir() } as unknown as PluginInput, { staleTimeoutMs: 180_000 })
const task: BackgroundTask = {
id: "task-2",
sessionID: "session-2",
parentSessionID: "parent-2",
parentMessageID: "msg-2",
description: "Test task",
prompt: "Test",
agent: "test-agent",
status: "running",
startedAt: new Date(Date.now() - 60_000),
progress: {
toolCalls: 5,
lastUpdate: new Date(Date.now() - 30_000),
},
}
manager["tasks"].set(task.id, task)
await manager["checkAndInterruptStaleTasks"]()
expect(task.status).toBe("running")
})
test("should interrupt task with stale lastUpdate (> 3min)", async () => {
const client = {
session: {
prompt: async () => ({}),
abort: async () => ({}),
},
}
const manager = new BackgroundManager({ client, directory: tmpdir() } as unknown as PluginInput, { staleTimeoutMs: 180_000 })
const task: BackgroundTask = {
id: "task-3",
sessionID: "session-3",
parentSessionID: "parent-3",
parentMessageID: "msg-3",
description: "Stale task",
prompt: "Test",
agent: "test-agent",
status: "running",
startedAt: new Date(Date.now() - 300_000),
progress: {
toolCalls: 2,
lastUpdate: new Date(Date.now() - 200_000),
},
}
manager["tasks"].set(task.id, task)
await manager["checkAndInterruptStaleTasks"]()
expect(task.status).toBe("cancelled")
expect(task.error).toContain("Stale timeout")
expect(task.error).toContain("3min")
expect(task.completedAt).toBeDefined()
})
test("should respect custom staleTimeoutMs config", async () => {
const client = {
session: {
prompt: async () => ({}),
abort: async () => ({}),
},
}
const manager = new BackgroundManager({ client, directory: tmpdir() } as unknown as PluginInput, { staleTimeoutMs: 60_000 })
const task: BackgroundTask = {
id: "task-4",
sessionID: "session-4",
parentSessionID: "parent-4",
parentMessageID: "msg-4",
description: "Custom timeout task",
prompt: "Test",
agent: "test-agent",
status: "running",
startedAt: new Date(Date.now() - 120_000),
progress: {
toolCalls: 1,
lastUpdate: new Date(Date.now() - 90_000),
},
}
manager["tasks"].set(task.id, task)
await manager["checkAndInterruptStaleTasks"]()
expect(task.status).toBe("cancelled")
expect(task.error).toContain("Stale timeout")
})
test("should release concurrency before abort", async () => {
const client = {
session: {
prompt: async () => ({}),
abort: async () => ({}),
},
}
const manager = new BackgroundManager({ client, directory: tmpdir() } as unknown as PluginInput, { staleTimeoutMs: 180_000 })
const task: BackgroundTask = {
id: "task-5",
sessionID: "session-5",
parentSessionID: "parent-5",
parentMessageID: "msg-5",
description: "Concurrency test",
prompt: "Test",
agent: "test-agent",
status: "running",
startedAt: new Date(Date.now() - 300_000),
progress: {
toolCalls: 1,
lastUpdate: new Date(Date.now() - 200_000),
},
concurrencyKey: "test-agent",
}
manager["tasks"].set(task.id, task)
await manager["checkAndInterruptStaleTasks"]()
expect(task.concurrencyKey).toBeUndefined()
expect(task.status).toBe("cancelled")
})
test("should handle multiple stale tasks in same poll cycle", async () => {
const client = {
session: {
prompt: async () => ({}),
abort: async () => ({}),
},
}
const manager = new BackgroundManager({ client, directory: tmpdir() } as unknown as PluginInput, { staleTimeoutMs: 180_000 })
const task1: BackgroundTask = {
id: "task-6",
sessionID: "session-6",
parentSessionID: "parent-6",
parentMessageID: "msg-6",
description: "Stale 1",
prompt: "Test",
agent: "test-agent",
status: "running",
startedAt: new Date(Date.now() - 300_000),
progress: {
toolCalls: 1,
lastUpdate: new Date(Date.now() - 200_000),
},
}
const task2: BackgroundTask = {
id: "task-7",
sessionID: "session-7",
parentSessionID: "parent-7",
parentMessageID: "msg-7",
description: "Stale 2",
prompt: "Test",
agent: "test-agent",
status: "running",
startedAt: new Date(Date.now() - 400_000),
progress: {
toolCalls: 2,
lastUpdate: new Date(Date.now() - 250_000),
},
}
manager["tasks"].set(task1.id, task1)
manager["tasks"].set(task2.id, task2)
await manager["checkAndInterruptStaleTasks"]()
expect(task1.status).toBe("cancelled")
expect(task2.status).toBe("cancelled")
})
test("should use default timeout when config not provided", async () => {
const client = {
session: {
prompt: async () => ({}),
abort: async () => ({}),
},
}
const manager = new BackgroundManager({ client, directory: tmpdir() } as unknown as PluginInput)
const task: BackgroundTask = {
id: "task-8",
sessionID: "session-8",
parentSessionID: "parent-8",
parentMessageID: "msg-8",
description: "Default timeout",
prompt: "Test",
agent: "test-agent",
status: "running",
startedAt: new Date(Date.now() - 300_000),
progress: {
toolCalls: 1,
lastUpdate: new Date(Date.now() - 200_000),
},
}
manager["tasks"].set(task.id, task)
await manager["checkAndInterruptStaleTasks"]()
expect(task.status).toBe("cancelled")
})
})