diff --git a/src/tools/call-omo-agent/sync-executor.test.ts b/src/tools/call-omo-agent/sync-executor.test.ts index 9b8392b93..c0bb0a8d3 100644 --- a/src/tools/call-omo-agent/sync-executor.test.ts +++ b/src/tools/call-omo-agent/sync-executor.test.ts @@ -79,11 +79,15 @@ function createToolContext(): ToolContext { } } -function createContext(promptAsync: ReturnType) { +function createContext( + promptAsync: ReturnType, + status?: () => Promise, +) { return { client: { session: { promptAsync, + ...(status ? { status } : {}), }, }, } @@ -350,6 +354,41 @@ describe("executeSync", () => { expect(deps.processMessages).not.toHaveBeenCalled() }) + test("does not send a duplicate sync prompt when a reused session is active", async () => { + //#given + const executeSync = await importExecuteSync() + const deps = createDependencies({ + createOrGetSession: mock(async () => ({ sessionID: "ses-active-reuse", isNew: false })), + }) + const toolContext = createToolContext() + const recorder = createPromptAsyncRecorder() + const args = { + subagent_type: "explore", + description: "active reuse", + prompt: "find something", + run_in_background: false, + session_id: "ses-active-reuse", + } + + //#when + const result = await executeSync( + args, + toolContext, + createContext( + recorder.promptAsync, + async () => ({ data: { "ses-active-reuse": { type: "busy" } } }), + ) as never, + deps, + ) + + //#then + expect(recorder.promptAsync).toHaveBeenCalledTimes(0) + expect(result).toContain("Error: Failed to send prompt") + expect(result).toContain("session_id: ses-active-reuse") + expect(deps.waitForCompletion).not.toHaveBeenCalled() + expect(deps.processMessages).not.toHaveBeenCalled() + }) + test("commits reserved descendant quota after creating a new sync session", async () => { //#given const { executeSync } = require("./sync-executor") diff --git a/src/tools/call-omo-agent/sync-executor.ts b/src/tools/call-omo-agent/sync-executor.ts index 31ae8beb8..85c0c6213 100644 --- a/src/tools/call-omo-agent/sync-executor.ts +++ b/src/tools/call-omo-agent/sync-executor.ts @@ -6,6 +6,7 @@ import { applySessionPromptParams } from "../../shared/session-prompt-params-hel import type { DelegatedModelConfig } from "../../shared/model-resolution-types" import type { FallbackEntry } from "../../shared/model-requirements" import { stripAgentListSortPrefix } from "../../shared/agent-display-names" +import { promptAsyncAfterSessionIdle } from "../../hooks/shared/prompt-async-gate" import { waitForCompletion } from "./completion-poller" import { processMessages } from "./message-processor" import { createOrGetSession } from "./session-creator" @@ -110,21 +111,34 @@ export async function executeSync( return `Error: Failed to send prompt: promptAsync is not available on this OpenCode client.\n\n\nsession_id: ${sessionID}\n` } - await ctx.client.session.promptAsync({ - path: { id: sessionID }, - body: { - agent: normalizedSubagentType, - tools: { - ...getAgentToolRestrictions(normalizedSubagentType), - task: false, - question: false, + const promptResult = await promptAsyncAfterSessionIdle({ + client: ctx.client, + sessionID, + source: "call-omo-agent:sync", + settleMs: 0, + postDispatchHoldMs: 0, + input: { + path: { id: sessionID }, + body: { + agent: normalizedSubagentType, + tools: { + ...getAgentToolRestrictions(normalizedSubagentType), + task: false, + question: false, + }, + parts: [{ type: "text", text: args.prompt }], + ...(model ? { model: { providerID: model.providerID, modelID: model.modelID } } : {}), + ...(model?.variant ? { variant: model.variant } : {}), + ...buildPromptGenerationParams(model), }, - parts: [{ type: "text", text: args.prompt }], - ...(model ? { model: { providerID: model.providerID, modelID: model.modelID } } : {}), - ...(model?.variant ? { variant: model.variant } : {}), - ...buildPromptGenerationParams(model), }, }) + if (promptResult.status === "failed") { + throw promptResult.error + } + if (promptResult.status !== "dispatched") { + throw new Error(`promptAsync skipped by gate: ${promptResult.status}`) + } } catch (error) { const errorMessage = error instanceof Error ? error.message : String(error) log(`[call_omo_agent] Prompt error:`, errorMessage)