fix(call-omo-agent): register bootstrap and session agent before sync prompt dispatch

call_omo_agent sync path created the child OpenCode session and went
straight into promptAsync without registering child session agent,
session tools, or bootstrap state. If first dispatch failed before any
durable user message persisted, runtime fallback could not reconstruct
the original prompt or the agent identity for that child session.

Bind setSessionAgent and setSessionTools to the child session id with
the same tool restrictions that the prompt body sends, register a
delegated child session bootstrap with the prompt text, fallback chain,
and prompt tools, then clean bootstrap + session tools in finally for
sessions this call created.
This commit is contained in:
YeonGyu-Kim
2026-05-17 00:08:56 +09:00
parent 097d7dc547
commit ea5f6ddfd7
2 changed files with 97 additions and 14 deletions
+60 -1
View File
@@ -1,5 +1,5 @@
import { unsafeTestValue } from "../../../test-support/unsafe-test-value"
const { describe, test, expect, mock } = require("bun:test")
import { describe, test, expect, mock } from "bun:test"
type ExecuteSync = typeof import("./sync-executor").executeSync
@@ -13,6 +13,7 @@ type PromptAsyncInput = {
variant?: string
temperature?: number
topP?: number
maxOutputTokens?: number
options?: Record<string, unknown>
}
}
@@ -342,6 +343,64 @@ describe("executeSync", () => {
expect(deps.setSessionFallbackChain).toHaveBeenCalledWith("ses-fallback", fallbackChain)
})
test("registers child-session bootstrap and tracked prompt state before sync prompt dispatch", async () => {
//#given
const executeSync = await importExecuteSync()
const { _resetForTesting, getSessionAgent } = require("../../features/claude-code-session-state")
const { clearAllDelegatedChildSessionBootstrap, getDelegatedChildSessionBootstrap } = require("../../shared/delegated-child-session-bootstrap")
const { clearSessionTools, getSessionTools } = require("../../shared/session-tools-store")
const deps = createDependencies({
createOrGetSession: mock(async () => ({ sessionID: "ses-call-bootstrap", isNew: true })),
})
const toolContext = createToolContext()
const observed: Array<{
agent: string | undefined
tools: Record<string, boolean> | undefined
bootstrap: ReturnType<typeof getDelegatedChildSessionBootstrap>
}> = []
const recorder = createPromptAsyncRecorder(async () => {
observed.push({
agent: getSessionAgent("ses-call-bootstrap"),
tools: getSessionTools("ses-call-bootstrap"),
bootstrap: getDelegatedChildSessionBootstrap("ses-call-bootstrap"),
})
return { data: {} }
})
const args = {
subagent_type: "explore",
description: "bootstrap state",
prompt: "collect bootstrap evidence",
run_in_background: false,
}
const fallbackChain = [
{ providers: ["openai"], model: "gpt-5.4", variant: "high" },
]
try {
//#when
await executeSync(
args,
toolContext,
createContext(recorder.promptAsync) as never,
deps,
fallbackChain
)
//#then
expect(observed[0]?.agent).toBe("explore")
expect(observed[0]?.tools?.question).toBe(false)
expect(observed[0]?.tools?.task).toBe(false)
expect(observed[0]?.bootstrap?.retryParts[0]?.text).toContain("collect bootstrap evidence")
expect(observed[0]?.bootstrap?.tools?.question).toBe(false)
expect(observed[0]?.bootstrap?.fallbackChain?.[0]?.model).toBe("gpt-5.4")
expect(getDelegatedChildSessionBootstrap("ses-call-bootstrap")).toBeUndefined()
} finally {
clearAllDelegatedChildSessionBootstrap()
clearSessionTools()
_resetForTesting()
}
})
test("returns dedicated agent-not-found error with task metadata", async () => {
//#given
const executeSync = await importExecuteSync()
+37 -13
View File
@@ -1,15 +1,20 @@
import type { CallOmoAgentArgs } from "./types"
import type { PluginInput } from "@opencode-ai/plugin"
import { subagentSessions, syncSubagentSessions } from "../../features/claude-code-session-state"
import { getAgentToolRestrictions, log } from "../../shared"
import { applySessionPromptParams } from "../../shared/session-prompt-params-helpers"
import type { DelegatedModelConfig } from "../../shared/model-resolution-types"
import type { FallbackEntry } from "../../shared/model-requirements"
import { getAgentDisplayName, stripAgentListSortPrefix } from "../../shared/agent-display-names"
import { setSessionAgent, subagentSessions, syncSubagentSessions } from "../../features/claude-code-session-state"
import { promptAsyncAfterSessionIdle } from "../../hooks/shared/prompt-async-gate"
import { getAgentToolRestrictions, log } from "../../shared"
import { getAgentDisplayName, stripAgentListSortPrefix } from "../../shared/agent-display-names"
import {
clearDelegatedChildSessionBootstrap,
registerDelegatedChildSessionBootstrap,
} from "../../shared/delegated-child-session-bootstrap"
import type { FallbackEntry } from "../../shared/model-requirements"
import type { DelegatedModelConfig } from "../../shared/model-resolution-types"
import { applySessionPromptParams } from "../../shared/session-prompt-params-helpers"
import { deleteSessionTools, setSessionTools } from "../../shared/session-tools-store"
import { waitForCompletion } from "./completion-poller"
import { processMessages } from "./message-processor"
import { createOrGetSession } from "./session-creator"
import type { CallOmoAgentArgs } from "./types"
type SessionWithPromptAsync = {
promptAsync: (opts: { path: { id: string }; body: Record<string, unknown> }) => Promise<unknown>
@@ -58,6 +63,14 @@ function buildPromptGenerationParams(model: DelegatedModelConfig | undefined): R
}
}
function buildSyncPromptTools(agent: string): Record<string, boolean> {
return {
...getAgentToolRestrictions(agent),
task: false,
question: false,
}
}
export async function executeSync(
args: CallOmoAgentArgs,
toolContext: {
@@ -105,6 +118,16 @@ export async function executeSync(
log(`[call_omo_agent] Sending prompt to session ${sessionID}`)
log(`[call_omo_agent] Prompt text:`, args.prompt.substring(0, 100))
const normalizedSubagentType = stripAgentListSortPrefix(args.subagent_type)
const promptAgent = getAgentDisplayName(normalizedSubagentType)
const promptTools = buildSyncPromptTools(normalizedSubagentType)
setSessionAgent(sessionID, promptAgent)
setSessionTools(sessionID, promptTools)
registerDelegatedChildSessionBootstrap({
sessionID,
promptText: args.prompt,
fallbackChain,
tools: promptTools,
})
try {
if (!hasPromptAsync(ctx.client.session)) {
@@ -119,12 +142,8 @@ export async function executeSync(
input: {
path: { id: sessionID },
body: {
agent: getAgentDisplayName(normalizedSubagentType),
tools: {
...getAgentToolRestrictions(normalizedSubagentType),
task: false,
question: false,
},
agent: promptAgent,
tools: promptTools,
parts: [{ type: "text", text: args.prompt }],
...(model ? { model: { providerID: model.providerID, modelID: model.modelID } } : {}),
...(model?.variant ? { variant: model.variant } : {}),
@@ -160,9 +179,14 @@ export async function executeSync(
deps.clearSessionFallbackChain(sessionID)
}
if (sessionID) {
clearDelegatedChildSessionBootstrap(sessionID)
}
if (sessionID && createdSessionForExecution) {
subagentSessions.delete(sessionID)
syncSubagentSessions.delete(sessionID)
deleteSessionTools(sessionID)
}
}
}