fix(sisyphus-task): add proper error handling for sync mode and implement BackgroundManager.resume()
- Add try-catch for session.prompt() in sync mode with detailed error messages - Sort assistant messages by time to get the most recent response - Add 'No assistant response found' error handling - Implement BackgroundManager.resume() method for task resumption - Fix ConcurrencyManager type mismatch (model → concurrencyKey)
This commit is contained in:
parent
b442b1c857
commit
4b2bf9ccb5
@ -4,6 +4,7 @@ import type { PluginInput } from "@opencode-ai/plugin"
|
|||||||
import type {
|
import type {
|
||||||
BackgroundTask,
|
BackgroundTask,
|
||||||
LaunchInput,
|
LaunchInput,
|
||||||
|
ResumeInput,
|
||||||
} from "./types"
|
} from "./types"
|
||||||
import { log } from "../../shared/logger"
|
import { log } from "../../shared/logger"
|
||||||
import { ConcurrencyManager } from "./concurrency"
|
import { ConcurrencyManager } from "./concurrency"
|
||||||
@ -78,9 +79,9 @@ export class BackgroundManager {
|
|||||||
throw new Error("Agent parameter is required")
|
throw new Error("Agent parameter is required")
|
||||||
}
|
}
|
||||||
|
|
||||||
const model = input.agent
|
const concurrencyKey = input.agent
|
||||||
|
|
||||||
await this.concurrencyManager.acquire(model)
|
await this.concurrencyManager.acquire(concurrencyKey)
|
||||||
|
|
||||||
const createResult = await this.client.session.create({
|
const createResult = await this.client.session.create({
|
||||||
body: {
|
body: {
|
||||||
@ -88,12 +89,12 @@ export class BackgroundManager {
|
|||||||
title: `Background: ${input.description}`,
|
title: `Background: ${input.description}`,
|
||||||
},
|
},
|
||||||
}).catch((error) => {
|
}).catch((error) => {
|
||||||
this.concurrencyManager.release(model)
|
this.concurrencyManager.release(concurrencyKey)
|
||||||
throw error
|
throw error
|
||||||
})
|
})
|
||||||
|
|
||||||
if (createResult.error) {
|
if (createResult.error) {
|
||||||
this.concurrencyManager.release(model)
|
this.concurrencyManager.release(concurrencyKey)
|
||||||
throw new Error(`Failed to create background session: ${createResult.error}`)
|
throw new Error(`Failed to create background session: ${createResult.error}`)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -115,7 +116,8 @@ export class BackgroundManager {
|
|||||||
lastUpdate: new Date(),
|
lastUpdate: new Date(),
|
||||||
},
|
},
|
||||||
parentModel: input.parentModel,
|
parentModel: input.parentModel,
|
||||||
model,
|
model: input.model,
|
||||||
|
concurrencyKey,
|
||||||
}
|
}
|
||||||
|
|
||||||
this.tasks.set(task.id, task)
|
this.tasks.set(task.id, task)
|
||||||
@ -155,8 +157,8 @@ export class BackgroundManager {
|
|||||||
existingTask.error = errorMessage
|
existingTask.error = errorMessage
|
||||||
}
|
}
|
||||||
existingTask.completedAt = new Date()
|
existingTask.completedAt = new Date()
|
||||||
if (existingTask.model) {
|
if (existingTask.concurrencyKey) {
|
||||||
this.concurrencyManager.release(existingTask.model)
|
this.concurrencyManager.release(existingTask.concurrencyKey)
|
||||||
}
|
}
|
||||||
this.markForNotification(existingTask)
|
this.markForNotification(existingTask)
|
||||||
this.notifyParentSession(existingTask)
|
this.notifyParentSession(existingTask)
|
||||||
@ -238,6 +240,62 @@ export class BackgroundManager {
|
|||||||
return task
|
return task
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async resume(input: ResumeInput): Promise<BackgroundTask> {
|
||||||
|
const existingTask = this.findBySession(input.sessionId)
|
||||||
|
if (!existingTask) {
|
||||||
|
throw new Error(`Task not found for session: ${input.sessionId}`)
|
||||||
|
}
|
||||||
|
|
||||||
|
existingTask.status = "running"
|
||||||
|
existingTask.completedAt = undefined
|
||||||
|
existingTask.error = undefined
|
||||||
|
existingTask.parentSessionID = input.parentSessionID
|
||||||
|
existingTask.parentMessageID = input.parentMessageID
|
||||||
|
existingTask.parentModel = input.parentModel
|
||||||
|
|
||||||
|
existingTask.progress = {
|
||||||
|
toolCalls: existingTask.progress?.toolCalls ?? 0,
|
||||||
|
lastUpdate: new Date(),
|
||||||
|
}
|
||||||
|
|
||||||
|
this.startPolling()
|
||||||
|
subagentSessions.add(existingTask.sessionID)
|
||||||
|
|
||||||
|
const toastManager = getTaskToastManager()
|
||||||
|
if (toastManager) {
|
||||||
|
toastManager.addTask({
|
||||||
|
id: existingTask.id,
|
||||||
|
description: existingTask.description,
|
||||||
|
agent: existingTask.agent,
|
||||||
|
isBackground: true,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
log("[background-agent] Resuming task:", { taskId: existingTask.id, sessionID: existingTask.sessionID })
|
||||||
|
|
||||||
|
this.client.session.promptAsync({
|
||||||
|
path: { id: existingTask.sessionID },
|
||||||
|
body: {
|
||||||
|
agent: existingTask.agent,
|
||||||
|
tools: {
|
||||||
|
task: false,
|
||||||
|
call_omo_agent: false,
|
||||||
|
},
|
||||||
|
parts: [{ type: "text", text: input.prompt }],
|
||||||
|
},
|
||||||
|
}).catch((error) => {
|
||||||
|
log("[background-agent] resume promptAsync error:", error)
|
||||||
|
existingTask.status = "error"
|
||||||
|
const errorMessage = error instanceof Error ? error.message : String(error)
|
||||||
|
existingTask.error = errorMessage
|
||||||
|
existingTask.completedAt = new Date()
|
||||||
|
this.markForNotification(existingTask)
|
||||||
|
this.notifyParentSession(existingTask)
|
||||||
|
})
|
||||||
|
|
||||||
|
return existingTask
|
||||||
|
}
|
||||||
|
|
||||||
private async checkSessionTodos(sessionID: string): Promise<boolean> {
|
private async checkSessionTodos(sessionID: string): Promise<boolean> {
|
||||||
try {
|
try {
|
||||||
const response = await this.client.session.todo({
|
const response = await this.client.session.todo({
|
||||||
@ -315,8 +373,8 @@ export class BackgroundManager {
|
|||||||
task.error = "Session deleted"
|
task.error = "Session deleted"
|
||||||
}
|
}
|
||||||
|
|
||||||
if (task.model) {
|
if (task.concurrencyKey) {
|
||||||
this.concurrencyManager.release(task.model)
|
this.concurrencyManager.release(task.concurrencyKey)
|
||||||
}
|
}
|
||||||
this.tasks.delete(task.id)
|
this.tasks.delete(task.id)
|
||||||
this.clearNotificationsForTask(task.id)
|
this.clearNotificationsForTask(task.id)
|
||||||
@ -391,8 +449,8 @@ export class BackgroundManager {
|
|||||||
|
|
||||||
const taskId = task.id
|
const taskId = task.id
|
||||||
setTimeout(async () => {
|
setTimeout(async () => {
|
||||||
if (task.model) {
|
if (task.concurrencyKey) {
|
||||||
this.concurrencyManager.release(task.model)
|
this.concurrencyManager.release(task.concurrencyKey)
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
@ -455,8 +513,8 @@ export class BackgroundManager {
|
|||||||
task.status = "error"
|
task.status = "error"
|
||||||
task.error = "Task timed out after 30 minutes"
|
task.error = "Task timed out after 30 minutes"
|
||||||
task.completedAt = new Date()
|
task.completedAt = new Date()
|
||||||
if (task.model) {
|
if (task.concurrencyKey) {
|
||||||
this.concurrencyManager.release(task.model)
|
this.concurrencyManager.release(task.concurrencyKey)
|
||||||
}
|
}
|
||||||
this.clearNotificationsForTask(taskId)
|
this.clearNotificationsForTask(taskId)
|
||||||
this.tasks.delete(taskId)
|
this.tasks.delete(taskId)
|
||||||
|
|||||||
@ -276,6 +276,7 @@ System notifies on completion. Use \`background_output\` with task_id="${task.id
|
|||||||
metadata: { sessionId: sessionID, category: args.category, sync: true },
|
metadata: { sessionId: sessionID, category: args.category, sync: true },
|
||||||
})
|
})
|
||||||
|
|
||||||
|
try {
|
||||||
await client.session.prompt({
|
await client.session.prompt({
|
||||||
path: { id: sessionID },
|
path: { id: sessionID },
|
||||||
body: {
|
body: {
|
||||||
@ -288,22 +289,39 @@ System notifies on completion. Use \`background_output\` with task_id="${task.id
|
|||||||
parts: [{ type: "text", text: args.prompt }],
|
parts: [{ type: "text", text: args.prompt }],
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
} catch (promptError) {
|
||||||
|
if (toastManager && taskId !== undefined) {
|
||||||
|
toastManager.removeTask(taskId)
|
||||||
|
}
|
||||||
|
const errorMessage = promptError instanceof Error ? promptError.message : String(promptError)
|
||||||
|
if (errorMessage.includes("agent.name") || errorMessage.includes("undefined")) {
|
||||||
|
return `❌ Agent "${agentToUse}" not found. Make sure the agent is registered in your opencode.json or provided by a plugin.\n\nSession ID: ${sessionID}`
|
||||||
|
}
|
||||||
|
return `❌ Failed to send prompt: ${errorMessage}\n\nSession ID: ${sessionID}`
|
||||||
|
}
|
||||||
|
|
||||||
const messagesResult = await client.session.messages({
|
const messagesResult = await client.session.messages({
|
||||||
path: { id: sessionID },
|
path: { id: sessionID },
|
||||||
})
|
})
|
||||||
|
|
||||||
if (messagesResult.error) {
|
if (messagesResult.error) {
|
||||||
return `❌ Error fetching result: ${messagesResult.error}`
|
return `❌ Error fetching result: ${messagesResult.error}\n\nSession ID: ${sessionID}`
|
||||||
}
|
}
|
||||||
|
|
||||||
const messages = ((messagesResult as { data?: unknown }).data ?? messagesResult) as Array<{
|
const messages = ((messagesResult as { data?: unknown }).data ?? messagesResult) as Array<{
|
||||||
info?: { role?: string }
|
info?: { role?: string; time?: { created?: number } }
|
||||||
parts?: Array<{ type?: string; text?: string }>
|
parts?: Array<{ type?: string; text?: string }>
|
||||||
}>
|
}>
|
||||||
|
|
||||||
const assistantMessages = messages.filter((m) => m.info?.role === "assistant")
|
const assistantMessages = messages
|
||||||
const lastMessage = assistantMessages[assistantMessages.length - 1]
|
.filter((m) => m.info?.role === "assistant")
|
||||||
|
.sort((a, b) => (b.info?.time?.created ?? 0) - (a.info?.time?.created ?? 0))
|
||||||
|
const lastMessage = assistantMessages[0]
|
||||||
|
|
||||||
|
if (!lastMessage) {
|
||||||
|
return `❌ No assistant response found.\n\nSession ID: ${sessionID}`
|
||||||
|
}
|
||||||
|
|
||||||
const textParts = lastMessage?.parts?.filter((p) => p.type === "text") ?? []
|
const textParts = lastMessage?.parts?.filter((p) => p.type === "text") ?? []
|
||||||
const textContent = textParts.map((p) => p.text ?? "").filter(Boolean).join("\n")
|
const textContent = textParts.map((p) => p.text ?? "").filter(Boolean).join("\n")
|
||||||
|
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user