fix(prompt-gate): scope reservation releases

This commit is contained in:
YeonGyu-Kim
2026-05-15 21:52:29 +09:00
parent 2eec0d96d9
commit 2bd4944bad
8 changed files with 156 additions and 8 deletions
@@ -3,6 +3,7 @@ import { afterEach, describe, expect, test } from "bun:test"
import {
promptAfterSessionIdle,
promptAsyncAfterSessionIdle,
releasePromptAsyncReservation,
releaseAllPromptAsyncReservationsForTesting,
} from "./prompt-async-gate"
@@ -154,6 +155,94 @@ describe("promptAsyncAfterSessionIdle", () => {
expect(promptCalls).toBe(2)
})
test("#given a peer-message promptAsync hold #when an unrelated route releases the session #then the peer-message hold remains reserved", async () => {
// given
let promptCalls = 0
const client = {
session: {
promptAsync: async () => {
promptCalls += 1
},
},
}
// when
const first = await promptAsyncAfterSessionIdle({
client,
sessionID: "ses_release_scope",
input: {
path: { id: "ses_release_scope" },
body: {
parts: [{ type: "text", text: '<peer_message from="teammate">hello</peer_message>' }],
},
},
source: "team-live-delivery",
settleMs: 0,
})
releasePromptAsyncReservation("ses_release_scope", "ralph-loop:activity")
const second = await promptAsyncAfterSessionIdle({
client,
sessionID: "ses_release_scope",
input: {
path: { id: "ses_release_scope" },
body: { parts: [{ type: "text", text: "continue" }] },
},
source: "todo-continuation-enforcer",
settleMs: 0,
postDispatchHoldMs: 0,
})
// then
expect(first.status).toBe("dispatched")
expect(second).toEqual({ status: "reserved", reservedBy: "team-live-delivery" })
expect(promptCalls).toBe(1)
})
test("#given a route family promptAsync hold #when the same family aborts another source #then the reservation is released", async () => {
// given
let promptCalls = 0
const client = {
session: {
promptAsync: async () => {
promptCalls += 1
},
},
}
// when
const first = await promptAsyncAfterSessionIdle({
client,
sessionID: "ses_release_family_scope",
input: {
path: { id: "ses_release_family_scope" },
body: { parts: [{ type: "text", text: "continue" }] },
},
source: "model-fallback:message.updated",
settleMs: 0,
})
const released = releasePromptAsyncReservation(
"ses_release_family_scope",
"model-fallback-abort:session.error",
{ reservedByPrefix: "model-fallback:" },
)
const second = await promptAsyncAfterSessionIdle({
client,
sessionID: "ses_release_family_scope",
input: {
path: { id: "ses_release_family_scope" },
body: { parts: [{ type: "text", text: "continue again" }] },
},
source: "model-fallback:session.error",
settleMs: 0,
})
// then
expect(first.status).toBe("dispatched")
expect(released).toBe(true)
expect(second.status).toBe("dispatched")
expect(promptCalls).toBe(2)
})
test("#given two internal prompt calls race for one idle session #when they dispatch concurrently #then only one prompt is accepted", async () => {
// given
let promptCalls = 0