Merge pull request #4074 from code-yeongyu/fix/delegate-task-spawn

fix(delegate-task): start child prompts reliably
This commit is contained in:
YeonGyu-Kim
2026-05-17 01:00:03 +09:00
committed by GitHub
22 changed files with 1385 additions and 216 deletions
@@ -0,0 +1,85 @@
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
system?: string
tools?: Record<string, boolean>
}
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],
}))
}
function cloneTools(tools: Record<string, boolean> | undefined): Record<string, boolean> | undefined {
return tools ? { ...tools } : undefined
}
export function registerDelegatedChildSessionBootstrap(_args: {
sessionID: string
promptText: string
fallbackChain?: FallbackEntry[]
category?: string
system?: string
tools?: Record<string, boolean>
modelFallbackControllerAccessor?: ModelFallbackControllerAccessor
}): void {
const retryParts = [createInternalAgentTextPart(_args.promptText)]
const fallbackChain = cloneFallbackChain(_args.fallbackChain)
const tools = cloneTools(_args.tools)
delegatedChildSessionBootstraps.set(_args.sessionID, {
retryParts,
...(fallbackChain ? { fallbackChain } : {}),
...(_args.category ? { category: _args.category } : {}),
...(_args.system ? { system: _args.system } : {}),
...(tools ? { tools } : {}),
})
_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)
const tools = cloneTools(bootstrap.tools)
return {
retryParts: cloneRetryParts(bootstrap.retryParts),
...(fallbackChain ? { fallbackChain } : {}),
...(bootstrap.category ? { category: bootstrap.category } : {}),
...(bootstrap.system ? { system: bootstrap.system } : {}),
...(tools ? { tools } : {}),
}
}
export function clearDelegatedChildSessionBootstrap(_sessionID: string): void {
delegatedChildSessionBootstraps.delete(_sessionID)
}
export function clearAllDelegatedChildSessionBootstrap(): void {
delegatedChildSessionBootstraps.clear()
}
+8 -6
View File
@@ -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),
})
}