Merge pull request #4238 from islee23520/fix/look-at-status-map-hang
Avoid look_at status map wait hang
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
import type { PluginInput } from "@opencode-ai/plugin"
|
||||
import type { ToolContext } from "@opencode-ai/plugin/tool"
|
||||
import { log, promptSyncWithModelSuggestionRetry } from "../../shared"
|
||||
import { isAmbiguousPromptDispatchFailure, log, promptSyncWithModelSuggestionRetry } from "../../shared"
|
||||
import { extractLatestAssistantText } from "./assistant-message-extractor"
|
||||
import { MULTIMODAL_LOOKER_AGENT } from "./constants"
|
||||
import { READ_ENABLED, buildLookAtPrompt } from "./look-at-prompt"
|
||||
@@ -61,7 +61,7 @@ Original error: ${createResult.error}`
|
||||
log(`[look_at] Created session: ${sessionID}`)
|
||||
|
||||
log(`[look_at] Sending prompt with ${isBase64Input ? "base64 image" : "file"} to session ${sessionID}`)
|
||||
let promptFailed = false
|
||||
let shouldWaitForStatus = true
|
||||
try {
|
||||
await promptSyncWithModelSuggestionRetry(ctx.client, {
|
||||
path: { id: sessionID },
|
||||
@@ -84,16 +84,15 @@ Original error: ${createResult.error}`
|
||||
queueBehavior: "defer",
|
||||
})
|
||||
} catch (promptError) {
|
||||
promptFailed = true
|
||||
log("[look_at] Prompt error (ignored, will still fetch messages):", promptError)
|
||||
log("[look_at] Prompt dispatch failed; checking child session evidence:", promptError)
|
||||
shouldWaitForStatus = isAmbiguousPromptDispatchFailure(promptError)
|
||||
}
|
||||
|
||||
let observedMessages: unknown[] | undefined
|
||||
let observedText: string | undefined
|
||||
if (typeof ctx.client.session.status === "function") {
|
||||
if (shouldWaitForStatus && typeof ctx.client.session.status === "function") {
|
||||
const waitResult = await waitForLookAtSessionResult(ctx.client, sessionID, {
|
||||
allowStableIdleWithoutActivity: true,
|
||||
allowEmptyStableIdleWithoutActivity: promptFailed,
|
||||
})
|
||||
observedText = waitResult.outcome.text ?? undefined
|
||||
if (observedText) {
|
||||
|
||||
@@ -90,6 +90,52 @@ describe("waitForLookAtSessionResult", () => {
|
||||
).rejects.toThrow("timed out")
|
||||
})
|
||||
|
||||
test("#given supported status never lists the session but assistant output exists #when polling #then resolves with observed output", async () => {
|
||||
const assistantMessages: RawMessage[] = [
|
||||
{ info: { role: "user" }, parts: [{ type: "text", text: "inspect this" }] },
|
||||
{ info: { role: "assistant" }, parts: [{ type: "text", text: "observed result" }] },
|
||||
]
|
||||
const client = createMockClient([{ data: {} }], assistantMessages)
|
||||
|
||||
const result = await waitForLookAtSessionResult(unsafeTestValue(client), "ses_test", {
|
||||
pollIntervalMs: 10,
|
||||
timeoutMs: 5000,
|
||||
})
|
||||
|
||||
expect(result.outcome.text).toBe("observed result")
|
||||
expect(client.session.status).toHaveBeenCalledTimes(1)
|
||||
})
|
||||
|
||||
test("#given status omits session before it starts #when later idle has response #then waits instead of treating empty status as done", async () => {
|
||||
const assistantMessages: RawMessage[] = [
|
||||
{ info: { role: "user" }, parts: [{ type: "text", text: "analyze this" }] },
|
||||
{ info: { role: "assistant" }, parts: [{ type: "text", text: "late result" }] },
|
||||
]
|
||||
let statusCalls = 0
|
||||
const client = {
|
||||
session: {
|
||||
status: mock(async () => {
|
||||
statusCalls += 1
|
||||
if (statusCalls <= 3) return { data: {} }
|
||||
if (statusCalls === 4) return { data: { ses_test: { type: "busy" } } }
|
||||
return { data: { ses_test: { type: "idle" } } }
|
||||
}),
|
||||
messages: mock(async () => ({
|
||||
data: statusCalls >= 5 ? assistantMessages : [],
|
||||
error: null,
|
||||
})),
|
||||
},
|
||||
}
|
||||
|
||||
const result = await waitForLookAtSessionResult(unsafeTestValue(client), "ses_test", {
|
||||
pollIntervalMs: 10,
|
||||
timeoutMs: 5000,
|
||||
})
|
||||
|
||||
expect(result.outcome.text).toBe("late result")
|
||||
expect(statusCalls).toBe(5)
|
||||
})
|
||||
|
||||
test("#given session never becomes idle #when polling exceeds timeout #then rejects", async () => {
|
||||
const client = createMockClient(
|
||||
[{ data: { ses_test: { type: "busy" } } }],
|
||||
|
||||
@@ -109,11 +109,11 @@ export async function waitForLookAtSessionResult(
|
||||
const { messages, error: messagesError } = await getSessionMessages(client, sessionID)
|
||||
const outcome = extractLatestAssistantOutcome(messages)
|
||||
|
||||
if (outcome.text && !isActive) {
|
||||
if (outcome.text && (!isActive || supportedButNeverSeen)) {
|
||||
return { messages, outcome, statusType }
|
||||
}
|
||||
|
||||
if (outcome.errorName && !isActive) {
|
||||
if (outcome.errorName && (!isActive || supportedButNeverSeen)) {
|
||||
return { messages, outcome, statusType }
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user