fix(delegation): use blocking prompt for sync tasks instead of polling
Replace promptAsync + manual polling loop with promptSyncWithModelSuggestionRetry (session.prompt) which blocks until the LLM response completes. This matches OpenCode's native task tool behavior and fixes empty/broken responses that occurred when polling declared stability prematurely. Applied to both executeSyncTask and executeSyncContinuation paths.
This commit is contained in:
@@ -12,7 +12,7 @@ import { resolveMultipleSkillsAsync } from "../../features/opencode-skill-loader
|
|||||||
import { discoverSkills } from "../../features/opencode-skill-loader"
|
import { discoverSkills } from "../../features/opencode-skill-loader"
|
||||||
import { getTaskToastManager } from "../../features/task-toast-manager"
|
import { getTaskToastManager } from "../../features/task-toast-manager"
|
||||||
import { subagentSessions, getSessionAgent } from "../../features/claude-code-session-state"
|
import { subagentSessions, getSessionAgent } from "../../features/claude-code-session-state"
|
||||||
import { log, getAgentToolRestrictions, resolveModelPipeline, promptWithModelSuggestionRetry } from "../../shared"
|
import { log, getAgentToolRestrictions, resolveModelPipeline, promptWithModelSuggestionRetry, promptSyncWithModelSuggestionRetry } from "../../shared"
|
||||||
import { fetchAvailableModels, isModelAvailable } from "../../shared/model-availability"
|
import { fetchAvailableModels, isModelAvailable } from "../../shared/model-availability"
|
||||||
import { readConnectedProvidersCache } from "../../shared/connected-providers-cache"
|
import { readConnectedProvidersCache } from "../../shared/connected-providers-cache"
|
||||||
import { CATEGORY_MODEL_REQUIREMENTS } from "../../shared/model-requirements"
|
import { CATEGORY_MODEL_REQUIREMENTS } from "../../shared/model-requirements"
|
||||||
@@ -211,7 +211,7 @@ export async function executeSyncContinuation(
|
|||||||
: undefined
|
: undefined
|
||||||
}
|
}
|
||||||
|
|
||||||
await (client.session as any).promptAsync({
|
await promptSyncWithModelSuggestionRetry(client, {
|
||||||
path: { id: args.session_id! },
|
path: { id: args.session_id! },
|
||||||
body: {
|
body: {
|
||||||
...(resumeAgent !== undefined ? { agent: resumeAgent } : {}),
|
...(resumeAgent !== undefined ? { agent: resumeAgent } : {}),
|
||||||
@@ -233,30 +233,6 @@ export async function executeSyncContinuation(
|
|||||||
return `Failed to send continuation prompt: ${errorMessage}\n\nSession ID: ${args.session_id}`
|
return `Failed to send continuation prompt: ${errorMessage}\n\nSession ID: ${args.session_id}`
|
||||||
}
|
}
|
||||||
|
|
||||||
const timing = getTimingConfig()
|
|
||||||
const pollStart = Date.now()
|
|
||||||
let lastMsgCount = 0
|
|
||||||
let stablePolls = 0
|
|
||||||
|
|
||||||
while (Date.now() - pollStart < 60000) {
|
|
||||||
await new Promise(resolve => setTimeout(resolve, timing.POLL_INTERVAL_MS))
|
|
||||||
|
|
||||||
const elapsed = Date.now() - pollStart
|
|
||||||
if (elapsed < timing.SESSION_CONTINUATION_STABILITY_MS) continue
|
|
||||||
|
|
||||||
const messagesCheck = await client.session.messages({ path: { id: args.session_id! } })
|
|
||||||
const msgs = ((messagesCheck as { data?: unknown }).data ?? messagesCheck) as Array<unknown>
|
|
||||||
const currentMsgCount = msgs.length
|
|
||||||
|
|
||||||
if (currentMsgCount > 0 && currentMsgCount === lastMsgCount) {
|
|
||||||
stablePolls++
|
|
||||||
if (stablePolls >= timing.STABILITY_POLLS_REQUIRED) break
|
|
||||||
} else {
|
|
||||||
stablePolls = 0
|
|
||||||
lastMsgCount = currentMsgCount
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const messagesResult = await client.session.messages({
|
const messagesResult = await client.session.messages({
|
||||||
path: { id: args.session_id! },
|
path: { id: args.session_id! },
|
||||||
})
|
})
|
||||||
@@ -621,7 +597,7 @@ export async function executeSyncTask(
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
const allowTask = isPlanAgent(agentToUse)
|
const allowTask = isPlanAgent(agentToUse)
|
||||||
await promptWithModelSuggestionRetry(client, {
|
await promptSyncWithModelSuggestionRetry(client, {
|
||||||
path: { id: sessionID },
|
path: { id: sessionID },
|
||||||
body: {
|
body: {
|
||||||
agent: agentToUse,
|
agent: agentToUse,
|
||||||
@@ -659,70 +635,6 @@ export async function executeSyncTask(
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
const syncTiming = getTimingConfig()
|
|
||||||
const pollStart = Date.now()
|
|
||||||
let lastMsgCount = 0
|
|
||||||
let stablePolls = 0
|
|
||||||
let pollCount = 0
|
|
||||||
|
|
||||||
log("[task] Starting poll loop", { sessionID, agentToUse })
|
|
||||||
|
|
||||||
while (Date.now() - pollStart < syncTiming.MAX_POLL_TIME_MS) {
|
|
||||||
if (ctx.abort?.aborted) {
|
|
||||||
log("[task] Aborted by user", { sessionID })
|
|
||||||
if (toastManager && taskId) toastManager.removeTask(taskId)
|
|
||||||
return `Task aborted.\n\nSession ID: ${sessionID}`
|
|
||||||
}
|
|
||||||
|
|
||||||
await new Promise(resolve => setTimeout(resolve, syncTiming.POLL_INTERVAL_MS))
|
|
||||||
pollCount++
|
|
||||||
|
|
||||||
const statusResult = await client.session.status()
|
|
||||||
const allStatuses = (statusResult.data ?? {}) as Record<string, { type: string }>
|
|
||||||
const sessionStatus = allStatuses[sessionID]
|
|
||||||
|
|
||||||
if (pollCount % 10 === 0) {
|
|
||||||
log("[task] Poll status", {
|
|
||||||
sessionID,
|
|
||||||
pollCount,
|
|
||||||
elapsed: Math.floor((Date.now() - pollStart) / 1000) + "s",
|
|
||||||
sessionStatus: sessionStatus?.type ?? "not_in_status",
|
|
||||||
stablePolls,
|
|
||||||
lastMsgCount,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
if (sessionStatus && sessionStatus.type !== "idle") {
|
|
||||||
stablePolls = 0
|
|
||||||
lastMsgCount = 0
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
const elapsed = Date.now() - pollStart
|
|
||||||
if (elapsed < syncTiming.MIN_STABILITY_TIME_MS) {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
const messagesCheck = await client.session.messages({ path: { id: sessionID } })
|
|
||||||
const msgs = ((messagesCheck as { data?: unknown }).data ?? messagesCheck) as Array<unknown>
|
|
||||||
const currentMsgCount = msgs.length
|
|
||||||
|
|
||||||
if (currentMsgCount === lastMsgCount) {
|
|
||||||
stablePolls++
|
|
||||||
if (stablePolls >= syncTiming.STABILITY_POLLS_REQUIRED) {
|
|
||||||
log("[task] Poll complete - messages stable", { sessionID, pollCount, currentMsgCount })
|
|
||||||
break
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
stablePolls = 0
|
|
||||||
lastMsgCount = currentMsgCount
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (Date.now() - pollStart >= syncTiming.MAX_POLL_TIME_MS) {
|
|
||||||
log("[task] Poll timeout reached", { sessionID, pollCount, lastMsgCount, stablePolls })
|
|
||||||
}
|
|
||||||
|
|
||||||
const messagesResult = await client.session.messages({
|
const messagesResult = await client.session.messages({
|
||||||
path: { id: sessionID },
|
path: { id: sessionID },
|
||||||
})
|
})
|
||||||
@@ -963,7 +875,7 @@ Sisyphus-Junior is spawned automatically when you specify a category. Pick the a
|
|||||||
return {
|
return {
|
||||||
agentToUse: "",
|
agentToUse: "",
|
||||||
categoryModel: undefined,
|
categoryModel: undefined,
|
||||||
error: `You are prometheus. You cannot delegate to prometheus via task.
|
error: `You are the plan agent. You cannot delegate to plan via task.
|
||||||
|
|
||||||
Create the work plan directly - that's your job as the planning agent.`,
|
Create the work plan directly - that's your job as the planning agent.`,
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user