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
@@ -389,6 +389,35 @@ describe("executeSync", () => {
expect(deps.processMessages).not.toHaveBeenCalled()
})
test("#given a reused sync session was just prompted #when executeSync is called again immediately #then the second prompt is rejected by the shared gate", async () => {
//#given
const executeSync = await importExecuteSync()
const deps = createDependencies({
createOrGetSession: mock(async () => ({ sessionID: "ses-reused-hold", isNew: false })),
})
const toolContext = createToolContext()
const recorder = createPromptAsyncRecorder()
const args = {
subagent_type: "explore",
description: "reused hold",
prompt: "find something",
run_in_background: false,
session_id: "ses-reused-hold",
}
const context = createContext(recorder.promptAsync) as never
//#when
const first = await executeSync(args, toolContext, context, deps)
const second = await executeSync(args, toolContext, context, deps)
//#then
expect(first).toContain("agent response")
expect(second).toContain("promptAsync skipped by gate: reserved")
expect(recorder.promptAsync).toHaveBeenCalledTimes(1)
expect(deps.waitForCompletion).toHaveBeenCalledTimes(1)
expect(deps.processMessages).toHaveBeenCalledTimes(1)
})
test("commits reserved descendant quota after creating a new sync session", async () => {
//#given
const { executeSync } = require("./sync-executor")
@@ -116,7 +116,6 @@ export async function executeSync(
sessionID,
source: "call-omo-agent:sync",
settleMs: 0,
postDispatchHoldMs: 0,
input: {
path: { id: sessionID },
body: {