Avoid look_at status map wait hang

This commit is contained in:
ilseob lee
2026-05-21 15:48:03 +09:00
parent 53cabfe44d
commit b3a195d662
3 changed files with 37 additions and 8 deletions
+5 -6
View File
@@ -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) {
+30
View File
@@ -90,6 +90,36 @@ describe("waitForLookAtSessionResult", () => {
).rejects.toThrow("timed out")
})
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" } } }],
+2 -2
View File
@@ -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 }
}