fix(prompt-gate): pin duplicate prompt dispatches

Keep prompt reservations briefly after successful dispatch so rapid idle/message/error transitions cannot inject the same follow-up twice.

Route all production session prompt calls through the shared gate, restore skipped background resume state, release holds after abort/recovery paths, and preserve Ralph/ULW loop state when a dispatch is deferred.

Add regression coverage for session routing, static prompt route auditing, team-mode live messaging, model suggestion retries, call-omo-agent reuse, background parent wakes, runtime fallback, compaction recovery, Atlas, and Ralph/ULW loops.
This commit is contained in:
YeonGyu-Kim
2026-05-15 13:19:10 +09:00
parent 05189700fb
commit c2aa180e7e
26 changed files with 893 additions and 48 deletions
+50
View File
@@ -266,6 +266,31 @@ describe("promptWithModelSuggestionRetry", () => {
expect(results[1]?.status).toBe("rejected")
})
it("#given promptAsync retry just dispatched #when the same session is prompted again immediately #then the second caller is rejected by the gate", async () => {
// given
const promptMock = mock(async () => undefined)
const client = {
session: {
promptAsync: promptMock,
},
}
const args = {
path: { id: "session-post-dispatch-hold" },
body: {
parts: [{ type: "text", text: "hello" }],
model: { providerID: "anthropic", modelID: "claude-sonnet-4" },
},
}
// when
await promptWithModelSuggestionRetry(unsafeTestValue(client), args)
const second = promptWithModelSuggestionRetry(unsafeTestValue(client), args)
// then
await expect(second).rejects.toThrow("promptAsync skipped by gate: reserved")
expect(promptMock).toHaveBeenCalledTimes(1)
})
it("should throw error from promptAsync directly on model-not-found error", async () => {
// given a client that fails with model-not-found error
const promptMock = mock().mockRejectedValueOnce({
@@ -436,6 +461,31 @@ describe("promptSyncWithModelSuggestionRetry", () => {
expect(promptAsyncMock).toHaveBeenCalledTimes(0)
})
it("#given sync prompt retry just dispatched #when the same session is prompted again immediately #then the second caller is rejected by the gate", async () => {
// given
const promptMock = mock(async () => undefined)
const client = {
session: {
prompt: promptMock,
},
}
const args = {
path: { id: "session-sync-post-dispatch-hold" },
body: {
parts: [{ type: "text", text: "hello" }],
model: { providerID: "anthropic", modelID: "claude-sonnet-4" },
},
}
// when
await promptSyncWithModelSuggestionRetry(unsafeTestValue(client), args)
const second = promptSyncWithModelSuggestionRetry(unsafeTestValue(client), args)
// then
await expect(second).rejects.toThrow("prompt skipped by gate: reserved")
expect(promptMock).toHaveBeenCalledTimes(1)
})
it("should abort and throw timeout error when sync prompt hangs", async () => {
// given a client where sync prompt never resolves unless aborted
let receivedSignal: AbortSignal | undefined