fix(background-agent): defer active parent wakes

This commit is contained in:
YeonGyu-Kim
2026-05-13 16:44:34 +09:00
parent 0b99168b7b
commit a337635e3b
4 changed files with 270 additions and 29 deletions
@@ -0,0 +1,55 @@
import { describe, expect, test } from "bun:test"
import {
isSessionActive,
shouldPromptAfterSessionIdle,
} from "./session-idle-settle"
describe("session idle prompt guard", () => {
test("#given session.status reports busy #when checking active session #then it returns true", async () => {
// given
const client = {
session: {
status: async () => ({
data: {
"ses-active": { type: "busy" },
},
}),
},
}
// when
const active = await isSessionActive(client, "ses-active")
// then
expect(active).toBe(true)
})
test("#given a stale idle event but session became busy #when settling before prompt #then it blocks the wake", async () => {
// given
const client = {
session: {
status: async () => ({
"ses-active": { type: "busy" },
}),
},
}
// when
const shouldPrompt = await shouldPromptAfterSessionIdle(client, "ses-active", 0)
// then
expect(shouldPrompt).toBe(false)
})
test("#given session.status is unavailable #when settling before prompt #then it preserves legacy prompt behavior", async () => {
// given
const client = { session: {} }
// when
const shouldPrompt = await shouldPromptAfterSessionIdle(client, "ses-legacy", 0)
// then
expect(shouldPrompt).toBe(true)
})
})
+56
View File
@@ -3,3 +3,59 @@ export const DEFAULT_SESSION_IDLE_SETTLE_MS = 150
export function settleAfterSessionIdle(ms = DEFAULT_SESSION_IDLE_SETTLE_MS): Promise<void> {
return ms > 0 ? new Promise((resolve) => setTimeout(resolve, ms)) : Promise.resolve()
}
type SessionStatusClient = {
session?: {
status?: () => Promise<unknown>
}
}
const ACTIVE_SESSION_STATUSES = new Set(["busy", "retry", "running"])
function isRecord(value: unknown): value is Record<string, unknown> {
return typeof value === "object" && value !== null
}
function getSessionStatusPayload(response: unknown): Record<string, unknown> {
if (isRecord(response) && isRecord(response.data)) {
return response.data
}
if (isRecord(response)) {
return response
}
return {}
}
export function isActiveSessionStatusType(statusType: string): boolean {
return ACTIVE_SESSION_STATUSES.has(statusType)
}
export async function isSessionActive(client: SessionStatusClient, sessionID: string): Promise<boolean> {
if (typeof client.session?.status !== "function") {
return false
}
try {
const statusResult = await client.session.status()
const status = getSessionStatusPayload(statusResult)[sessionID]
if (!isRecord(status)) {
return false
}
const statusType = status.type
return typeof statusType === "string" && isActiveSessionStatusType(statusType)
} catch {
return false
}
}
export async function shouldPromptAfterSessionIdle(
client: SessionStatusClient,
sessionID: string,
settleMs = DEFAULT_SESSION_IDLE_SETTLE_MS,
): Promise<boolean> {
await settleAfterSessionIdle(settleMs)
return !(await isSessionActive(client, sessionID))
}