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:
@@ -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()
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user