fix(delegate-task): start child prompts reliably
Preserve delegated child prompt/bootstrap metadata for early runtime fallback before OpenCode has persisted the first user turn. Bind prompt gate calls to the SDK session receiver and keep completed background task lookup visible across plugin manager instances.
This commit is contained in:
@@ -0,0 +1,71 @@
|
||||
import type { ModelFallbackControllerAccessor } from "../hooks/model-fallback"
|
||||
import { createInternalAgentTextPart } from "./internal-initiator-marker"
|
||||
import type { FallbackEntry } from "./model-requirements"
|
||||
import { SessionCategoryRegistry } from "./session-category-registry"
|
||||
|
||||
export type DelegatedChildSessionRetryPart = {
|
||||
type: "text"
|
||||
text: string
|
||||
}
|
||||
|
||||
export type DelegatedChildSessionBootstrap = {
|
||||
retryParts: DelegatedChildSessionRetryPart[]
|
||||
fallbackChain?: FallbackEntry[]
|
||||
category?: string
|
||||
}
|
||||
|
||||
const delegatedChildSessionBootstraps = new Map<string, DelegatedChildSessionBootstrap>()
|
||||
|
||||
function cloneRetryParts(parts: DelegatedChildSessionRetryPart[]): DelegatedChildSessionRetryPart[] {
|
||||
return parts.map((part) => ({ type: part.type, text: part.text }))
|
||||
}
|
||||
|
||||
function cloneFallbackChain(fallbackChain: FallbackEntry[] | undefined): FallbackEntry[] | undefined {
|
||||
return fallbackChain?.map((entry) => ({
|
||||
...entry,
|
||||
providers: [...entry.providers],
|
||||
}))
|
||||
}
|
||||
|
||||
export function registerDelegatedChildSessionBootstrap(_args: {
|
||||
sessionID: string
|
||||
promptText: string
|
||||
fallbackChain?: FallbackEntry[]
|
||||
category?: string
|
||||
modelFallbackControllerAccessor?: ModelFallbackControllerAccessor
|
||||
}): void {
|
||||
const retryParts = [createInternalAgentTextPart(_args.promptText)]
|
||||
const fallbackChain = cloneFallbackChain(_args.fallbackChain)
|
||||
delegatedChildSessionBootstraps.set(_args.sessionID, {
|
||||
retryParts,
|
||||
...(fallbackChain ? { fallbackChain } : {}),
|
||||
...(_args.category ? { category: _args.category } : {}),
|
||||
})
|
||||
|
||||
_args.modelFallbackControllerAccessor?.setSessionFallbackChain(_args.sessionID, fallbackChain)
|
||||
if (_args.category) {
|
||||
SessionCategoryRegistry.register(_args.sessionID, _args.category)
|
||||
}
|
||||
}
|
||||
|
||||
export function getDelegatedChildSessionBootstrap(_sessionID: string): DelegatedChildSessionBootstrap | undefined {
|
||||
const bootstrap = delegatedChildSessionBootstraps.get(_sessionID)
|
||||
if (!bootstrap) {
|
||||
return undefined
|
||||
}
|
||||
|
||||
const fallbackChain = cloneFallbackChain(bootstrap.fallbackChain)
|
||||
return {
|
||||
retryParts: cloneRetryParts(bootstrap.retryParts),
|
||||
...(fallbackChain ? { fallbackChain } : {}),
|
||||
...(bootstrap.category ? { category: bootstrap.category } : {}),
|
||||
}
|
||||
}
|
||||
|
||||
export function clearDelegatedChildSessionBootstrap(_sessionID: string): void {
|
||||
delegatedChildSessionBootstraps.delete(_sessionID)
|
||||
}
|
||||
|
||||
export function clearAllDelegatedChildSessionBootstrap(): void {
|
||||
delegatedChildSessionBootstraps.clear()
|
||||
}
|
||||
@@ -217,12 +217,13 @@ export async function promptAsyncAfterSessionIdle<TInput = PromptAsyncInput>(arg
|
||||
} = args
|
||||
const postDispatchHoldMs = args.postDispatchHoldMs ?? DEFAULT_PROMPT_ASYNC_POST_DISPATCH_HOLD_MS
|
||||
const dispatchTimeoutMs = args.dispatchTimeoutMs ?? DEFAULT_PROMPT_DISPATCH_TIMEOUT_MS
|
||||
const promptAsync = client.session?.promptAsync
|
||||
const session = client.session
|
||||
|
||||
if (typeof promptAsync !== "function") {
|
||||
if (typeof session?.promptAsync !== "function") {
|
||||
log("[prompt-async-gate] promptAsync unavailable", { sessionID, source })
|
||||
return { status: "unavailable" }
|
||||
}
|
||||
const dispatchPromptAsync = session.promptAsync.bind(session)
|
||||
|
||||
return dispatchAfterSessionIdle({
|
||||
sessionName: "promptAsync",
|
||||
@@ -234,7 +235,7 @@ export async function promptAsyncAfterSessionIdle<TInput = PromptAsyncInput>(arg
|
||||
postDispatchHoldMs,
|
||||
dispatchTimeoutMs,
|
||||
checkStatus: args.checkStatus !== false,
|
||||
dispatch: (dispatchInput) => promptAsync(dispatchInput),
|
||||
dispatch: (dispatchInput) => dispatchPromptAsync(dispatchInput),
|
||||
})
|
||||
}
|
||||
|
||||
@@ -257,12 +258,13 @@ export async function promptAfterSessionIdle<TInput = PromptAsyncInput>(args: {
|
||||
} = args
|
||||
const postDispatchHoldMs = args.postDispatchHoldMs ?? DEFAULT_PROMPT_ASYNC_POST_DISPATCH_HOLD_MS
|
||||
const dispatchTimeoutMs = args.dispatchTimeoutMs ?? DEFAULT_PROMPT_DISPATCH_TIMEOUT_MS
|
||||
const prompt = client.session?.prompt
|
||||
const session = client.session
|
||||
|
||||
if (typeof prompt !== "function") {
|
||||
if (typeof session?.prompt !== "function") {
|
||||
log("[prompt-async-gate] prompt unavailable", { sessionID, source })
|
||||
return { status: "unavailable" }
|
||||
}
|
||||
const dispatchPrompt = session.prompt.bind(session)
|
||||
|
||||
return dispatchAfterSessionIdle({
|
||||
sessionName: "prompt",
|
||||
@@ -274,7 +276,7 @@ export async function promptAfterSessionIdle<TInput = PromptAsyncInput>(args: {
|
||||
postDispatchHoldMs,
|
||||
dispatchTimeoutMs,
|
||||
checkStatus: args.checkStatus !== false,
|
||||
dispatch: (dispatchInput) => prompt(dispatchInput),
|
||||
dispatch: (dispatchInput) => dispatchPrompt(dispatchInput),
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user