fix(unstable-agent-babysitter): respect active sessions

Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent)

Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
This commit is contained in:
YeonGyu-Kim
2026-05-18 15:48:43 +09:00
parent af1ad4d04b
commit 0ee45aa6ab
2 changed files with 321 additions and 6 deletions
@@ -2,6 +2,7 @@ import { afterEach, describe, expect, test } from "bun:test"
import { _resetForTesting, setMainSession } from "../../features/claude-code-session-state"
import type { BackgroundTask } from "../../features/background-agent"
import { OMO_INTERNAL_INITIATOR_MARKER } from "../../shared/internal-initiator-marker"
import { releaseAllPromptAsyncReservationsForTesting } from "../shared/prompt-async-gate"
import { createUnstableAgentBabysitterHook } from "./index"
const projectDir = process.cwd()
@@ -49,7 +50,7 @@ function createTask(overrides: Partial<BackgroundTask> = {}): BackgroundTask {
status: "running",
progress: {
toolCalls: 1,
lastUpdate: new Date(),
lastUpdate: new Date(Date.now() - 121000),
lastMessage: "still working",
lastMessageAt: new Date(Date.now() - 121000),
},
@@ -61,6 +62,7 @@ function createTask(overrides: Partial<BackgroundTask> = {}): BackgroundTask {
describe("unstable-agent-babysitter hook", () => {
afterEach(() => {
_resetForTesting()
releaseAllPromptAsyncReservationsForTesting()
})
test("settles idle before injecting a reminder", async () => {
@@ -290,4 +292,281 @@ describe("unstable-agent-babysitter hook", () => {
expect(payload.body?.model).toEqual({ providerID: "openai", modelID: "gpt-4" })
expect(payload.body?.variant).toBe("max")
})
test("#given the main session has a fresh user message #when it becomes idle #then babysitter does not inject a reminder", async () => {
// given
const originalNow = Date.now
Date.now = () => 10 * 60 * 1000
setMainSession("main-1")
const promptCalls: Array<{ input: unknown }> = []
const ctx = createMockPluginInput({
messagesBySession: {
"main-1": [
{ info: { role: "user", time: { created: Date.now() - 1_500 } } },
],
"bg-1": [
{ info: { role: "assistant" }, parts: [{ type: "thinking", thinking: "deep thought" }] },
],
},
promptCalls,
})
const backgroundManager = createBackgroundManager([createTask({
progress: {
toolCalls: 1,
lastUpdate: new Date(0),
lastMessage: "still working",
lastMessageAt: new Date(0),
},
})])
const hook = createUnstableAgentBabysitterHook(ctx, {
backgroundManager,
config: { timeout_ms: 120000 },
})
try {
// when
await hook.event({ event: { type: "session.idle", properties: { sessionID: "main-1" } } })
// then
expect(promptCalls.length).toBe(0)
} finally {
Date.now = originalNow
}
})
test("#given the latest main-session message is assistant output after a fresh user message #when it becomes idle #then babysitter may inject a reminder", async () => {
// given
const originalNow = Date.now
Date.now = () => 10 * 60 * 1000
setMainSession("main-1")
const promptCalls: Array<{ input: unknown }> = []
const ctx = createMockPluginInput({
messagesBySession: {
"main-1": [
{ info: { role: "user", time: { created: Date.now() - 1_500 } } },
{ info: { role: "assistant", time: { created: Date.now() - 500 }, agent: "sisyphus" } },
],
"bg-1": [
{ info: { role: "assistant" }, parts: [{ type: "thinking", thinking: "deep thought" }] },
],
},
promptCalls,
})
const backgroundManager = createBackgroundManager([createTask({
progress: {
toolCalls: 1,
lastUpdate: new Date(0),
lastMessage: "still working",
lastMessageAt: new Date(0),
},
})])
const hook = createUnstableAgentBabysitterHook(ctx, {
backgroundManager,
config: { timeout_ms: 120000 },
})
try {
// when
await hook.event({ event: { type: "session.idle", properties: { sessionID: "main-1" } } })
// then
expect(promptCalls.length).toBe(1)
} finally {
Date.now = originalNow
}
})
test("#given an unstable task has a fresh progress update after its last message #when the main session idles #then babysitter does not treat it as hung", async () => {
// given
const originalNow = Date.now
Date.now = () => 10 * 60 * 1000
setMainSession("main-1")
const promptCalls: Array<{ input: unknown }> = []
const ctx = createMockPluginInput({
messagesBySession: { "main-1": [], "bg-1": [] },
promptCalls,
})
const backgroundManager = createBackgroundManager([createTask({
progress: {
toolCalls: 2,
lastUpdate: new Date(Date.now() - 1_000),
lastMessage: "still working",
lastMessageAt: new Date(Date.now() - 5 * 60 * 1000),
},
})])
const hook = createUnstableAgentBabysitterHook(ctx, {
backgroundManager,
config: { timeout_ms: 120000 },
})
try {
// when
await hook.event({ event: { type: "session.idle", properties: { sessionID: "main-1" } } })
// then
expect(promptCalls.length).toBe(0)
} finally {
Date.now = originalNow
}
})
test("#given an unstable task has stale progress and stale last message #when the main session idles #then babysitter still reminds", async () => {
// given
const originalNow = Date.now
Date.now = () => 10 * 60 * 1000
setMainSession("main-1")
const promptCalls: Array<{ input: unknown }> = []
const ctx = createMockPluginInput({
messagesBySession: { "main-1": [], "bg-1": [] },
promptCalls,
})
const backgroundManager = createBackgroundManager([createTask({
progress: {
toolCalls: 2,
lastUpdate: new Date(Date.now() - 5 * 60 * 1000),
lastMessage: "still working",
lastMessageAt: new Date(Date.now() - 5 * 60 * 1000),
},
})])
const hook = createUnstableAgentBabysitterHook(ctx, {
backgroundManager,
config: { timeout_ms: 120000 },
})
try {
// when
await hook.event({ event: { type: "session.idle", properties: { sessionID: "main-1" } } })
// then
expect(promptCalls.length).toBe(1)
} finally {
Date.now = originalNow
}
})
test("#given a reminder was already sent before an abort #when a user message resumes the session within cooldown #then no duplicate reminder is injected", async () => {
// given
const originalNow = Date.now
let currentNow = 10 * 60 * 1000
Date.now = () => currentNow
setMainSession("main-1")
const promptCalls: Array<{ input: unknown }> = []
const ctx = createMockPluginInput({
messagesBySession: { "main-1": [], "bg-1": [] },
promptCalls,
})
const backgroundManager = createBackgroundManager([createTask({
progress: {
toolCalls: 1,
lastUpdate: new Date(0),
lastMessage: "still working",
lastMessageAt: new Date(0),
},
})])
const hook = createUnstableAgentBabysitterHook(ctx, {
backgroundManager,
config: { timeout_ms: 120000 },
})
try {
// when
await hook.event({ event: { type: "session.idle", properties: { sessionID: "main-1" } } })
await hook.event({ event: { type: "session.error", properties: { sessionID: "main-1", error: { name: "AbortError" } } } })
currentNow += 1_000
await hook.event({ event: { type: "message.updated", properties: { sessionID: "main-1", info: { role: "user" } } } })
await hook.event({ event: { type: "session.idle", properties: { sessionID: "main-1" } } })
// then
expect(promptCalls.length).toBe(1)
} finally {
Date.now = originalNow
}
})
test("#given a reminder was already sent before a stop event #when assistant activity resumes within cooldown #then no duplicate reminder is injected", async () => {
// given
const originalNow = Date.now
let currentNow = 10 * 60 * 1000
Date.now = () => currentNow
setMainSession("main-1")
const promptCalls: Array<{ input: unknown }> = []
const ctx = createMockPluginInput({
messagesBySession: { "main-1": [], "bg-1": [] },
promptCalls,
})
const backgroundManager = createBackgroundManager([createTask({
progress: {
toolCalls: 1,
lastUpdate: new Date(0),
lastMessage: "still working",
lastMessageAt: new Date(0),
},
})])
const hook = createUnstableAgentBabysitterHook(ctx, {
backgroundManager,
config: { timeout_ms: 120000 },
})
try {
// when
await hook.event({ event: { type: "session.idle", properties: { sessionID: "main-1" } } })
await hook.event({ event: { type: "session.stop", properties: { sessionID: "main-1" } } })
currentNow += 1_000
await hook.event({ event: { type: "message.updated", properties: { sessionID: "main-1", info: { role: "assistant" } } } })
await hook.event({ event: { type: "session.idle", properties: { sessionID: "main-1" } } })
// then
expect(promptCalls.length).toBe(1)
} finally {
Date.now = originalNow
}
})
test("#given unstable task agent is a config key #when babysitter builds a reminder #then the reminder uses the canonical display name", async () => {
// given
setMainSession("main-1")
const promptCalls: Array<{ input: unknown }> = []
const ctx = createMockPluginInput({
messagesBySession: { "main-1": [], "bg-1": [] },
promptCalls,
})
const backgroundManager = createBackgroundManager([createTask({ agent: "sisyphus" })])
const hook = createUnstableAgentBabysitterHook(ctx, {
backgroundManager,
config: { timeout_ms: 120000 },
})
// when
await hook.event({ event: { type: "session.idle", properties: { sessionID: "main-1" } } })
// then
const payload = promptCalls[0]?.input as { body?: { parts?: Array<{ text?: string }> } } | undefined
const text = payload?.body?.parts?.[0]?.text ?? ""
expect(text).toContain("Agent: Sisyphus - Ultraworker")
expect(text).not.toContain("Agent: sisyphus")
})
test("#given unstable task agent is a legacy display name #when babysitter builds a reminder #then the reminder uses the current display name", async () => {
// given
setMainSession("main-1")
const promptCalls: Array<{ input: unknown }> = []
const ctx = createMockPluginInput({
messagesBySession: { "main-1": [], "bg-1": [] },
promptCalls,
})
const backgroundManager = createBackgroundManager([createTask({ agent: "Sisyphus (Ultraworker)" })])
const hook = createUnstableAgentBabysitterHook(ctx, {
backgroundManager,
config: { timeout_ms: 120000 },
})
// when
await hook.event({ event: { type: "session.idle", properties: { sessionID: "main-1" } } })
// then
const payload = promptCalls[0]?.input as { body?: { parts?: Array<{ text?: string }> } } | undefined
const text = payload?.body?.parts?.[0]?.text ?? ""
expect(text).toContain("Agent: Sisyphus - Ultraworker")
expect(text).not.toContain("Agent: Sisyphus (Ultraworker)")
})
})