fix(delegate-task): Wave 1 - fix polling timeout, resource cleanup, tool restrictions, idle dedup, auth-plugins JSONC, CLI runner hang
- fix(delegate-task): return error on poll timeout instead of silent null - fix(delegate-task): ensure toast and session cleanup on all error paths with try/finally - fix(delegate-task): apply agent tool restrictions in sync-prompt-sender - fix(plugin): add symmetric idle dedup to prevent double hook triggers - fix(cli): replace regex-based JSONC editing with jsonc-parser in auth-plugins - fix(cli): abort event stream after completion and restore no-timeout default All changes verified with tests and typecheck.
This commit is contained in:
@@ -0,0 +1,123 @@
|
||||
const { describe, test, expect, mock } = require("bun:test")
|
||||
|
||||
describe("sendSyncPrompt", () => {
|
||||
test("applies agent tool restrictions for explore agent", async () => {
|
||||
//#given
|
||||
const mockPromptWithModelSuggestionRetry = mock(async () => {})
|
||||
mock.module("../../shared/model-suggestion-retry", () => ({
|
||||
promptWithModelSuggestionRetry: mockPromptWithModelSuggestionRetry,
|
||||
}))
|
||||
|
||||
const { sendSyncPrompt } = require("./sync-prompt-sender")
|
||||
|
||||
const mockClient = {
|
||||
session: {
|
||||
prompt: mock(async () => ({ data: {} })),
|
||||
},
|
||||
}
|
||||
|
||||
const input = {
|
||||
sessionID: "test-session",
|
||||
agentToUse: "explore",
|
||||
args: {
|
||||
description: "test task",
|
||||
prompt: "test prompt",
|
||||
category: "quick",
|
||||
run_in_background: false,
|
||||
load_skills: [],
|
||||
},
|
||||
systemContent: undefined,
|
||||
categoryModel: undefined,
|
||||
toastManager: null,
|
||||
taskId: undefined,
|
||||
}
|
||||
|
||||
//#when
|
||||
await sendSyncPrompt(mockClient as any, input)
|
||||
|
||||
//#then
|
||||
expect(mockPromptWithModelSuggestionRetry).toHaveBeenCalled()
|
||||
const callArgs = mockPromptWithModelSuggestionRetry.mock.calls[0][1]
|
||||
expect(callArgs.body.tools.call_omo_agent).toBe(false)
|
||||
})
|
||||
|
||||
test("applies agent tool restrictions for librarian agent", async () => {
|
||||
//#given
|
||||
const mockPromptWithModelSuggestionRetry = mock(async () => {})
|
||||
mock.module("../../shared/model-suggestion-retry", () => ({
|
||||
promptWithModelSuggestionRetry: mockPromptWithModelSuggestionRetry,
|
||||
}))
|
||||
|
||||
const { sendSyncPrompt } = require("./sync-prompt-sender")
|
||||
|
||||
const mockClient = {
|
||||
session: {
|
||||
prompt: mock(async () => ({ data: {} })),
|
||||
},
|
||||
}
|
||||
|
||||
const input = {
|
||||
sessionID: "test-session",
|
||||
agentToUse: "librarian",
|
||||
args: {
|
||||
description: "test task",
|
||||
prompt: "test prompt",
|
||||
category: "quick",
|
||||
run_in_background: false,
|
||||
load_skills: [],
|
||||
},
|
||||
systemContent: undefined,
|
||||
categoryModel: undefined,
|
||||
toastManager: null,
|
||||
taskId: undefined,
|
||||
}
|
||||
|
||||
//#when
|
||||
await sendSyncPrompt(mockClient as any, input)
|
||||
|
||||
//#then
|
||||
expect(mockPromptWithModelSuggestionRetry).toHaveBeenCalled()
|
||||
const callArgs = mockPromptWithModelSuggestionRetry.mock.calls[0][1]
|
||||
expect(callArgs.body.tools.call_omo_agent).toBe(false)
|
||||
})
|
||||
|
||||
test("does not restrict call_omo_agent for sisyphus agent", async () => {
|
||||
//#given
|
||||
const mockPromptWithModelSuggestionRetry = mock(async () => {})
|
||||
mock.module("../../shared/model-suggestion-retry", () => ({
|
||||
promptWithModelSuggestionRetry: mockPromptWithModelSuggestionRetry,
|
||||
}))
|
||||
|
||||
const { sendSyncPrompt } = require("./sync-prompt-sender")
|
||||
|
||||
const mockClient = {
|
||||
session: {
|
||||
prompt: mock(async () => ({ data: {} })),
|
||||
},
|
||||
}
|
||||
|
||||
const input = {
|
||||
sessionID: "test-session",
|
||||
agentToUse: "sisyphus",
|
||||
args: {
|
||||
description: "test task",
|
||||
prompt: "test prompt",
|
||||
category: "quick",
|
||||
run_in_background: false,
|
||||
load_skills: [],
|
||||
},
|
||||
systemContent: undefined,
|
||||
categoryModel: undefined,
|
||||
toastManager: null,
|
||||
taskId: undefined,
|
||||
}
|
||||
|
||||
//#when
|
||||
await sendSyncPrompt(mockClient as any, input)
|
||||
|
||||
//#then
|
||||
expect(mockPromptWithModelSuggestionRetry).toHaveBeenCalled()
|
||||
const callArgs = mockPromptWithModelSuggestionRetry.mock.calls[0][1]
|
||||
expect(callArgs.body.tools.call_omo_agent).toBe(true)
|
||||
})
|
||||
})
|
||||
@@ -2,6 +2,7 @@ import type { DelegateTaskArgs, OpencodeClient } from "./types"
|
||||
import { isPlanFamily } from "./constants"
|
||||
import { promptWithModelSuggestionRetry } from "../../shared/model-suggestion-retry"
|
||||
import { formatDetailedError } from "./error-formatting"
|
||||
import { getAgentToolRestrictions } from "../../shared/agent-tool-restrictions"
|
||||
|
||||
export async function sendSyncPrompt(
|
||||
client: OpencodeClient,
|
||||
@@ -26,6 +27,7 @@ export async function sendSyncPrompt(
|
||||
task: allowTask,
|
||||
call_omo_agent: true,
|
||||
question: false,
|
||||
...getAgentToolRestrictions(input.agentToUse),
|
||||
},
|
||||
parts: [{ type: "text", text: input.args.prompt }],
|
||||
...(input.categoryModel ? { model: { providerID: input.categoryModel.providerID, modelID: input.categoryModel.modelID } } : {}),
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
declare const require: (name: string) => any
|
||||
const { describe, test, expect, beforeEach, afterEach } = require("bun:test")
|
||||
import { describe, test, expect, beforeEach, afterEach } from "bun:test"
|
||||
import { __setTimingConfig, __resetTimingConfig } from "./timing"
|
||||
|
||||
function createMockCtx(aborted = false) {
|
||||
@@ -8,6 +7,7 @@ function createMockCtx(aborted = false) {
|
||||
return {
|
||||
sessionID: "parent-session",
|
||||
messageID: "parent-message",
|
||||
agent: "test-agent",
|
||||
abort: controller.signal,
|
||||
}
|
||||
}
|
||||
@@ -39,15 +39,12 @@ describe("pollSyncSession", () => {
|
||||
data: [
|
||||
{ info: { id: "msg_001", role: "user", time: { created: 1000 } } },
|
||||
{
|
||||
info: { id: "msg_002", role: "assistant", time: { created: 2000 }, finish: "end_turn" },
|
||||
info: { id: "msg_002", role: "assistant", time: { created: 2000 }, finish: "stop" },
|
||||
parts: [{ type: "text", text: "Done" }],
|
||||
},
|
||||
],
|
||||
}),
|
||||
status: async () => {
|
||||
pollCount++
|
||||
return { data: { "ses_test": { type: "idle" } } }
|
||||
},
|
||||
status: async () => ({ data: { "ses_test": { type: "idle" } } }),
|
||||
},
|
||||
}
|
||||
|
||||
@@ -247,7 +244,7 @@ describe("pollSyncSession", () => {
|
||||
})
|
||||
|
||||
describe("timeout handling", () => {
|
||||
test("returns null on timeout (graceful)", async () => {
|
||||
test("returns error string on timeout", async () => {
|
||||
//#given - never returns a terminal finish, but timeout is very short
|
||||
const { pollSyncSession } = require("./sync-session-poller")
|
||||
|
||||
@@ -255,7 +252,7 @@ describe("pollSyncSession", () => {
|
||||
POLL_INTERVAL_MS: 10,
|
||||
MIN_STABILITY_TIME_MS: 0,
|
||||
STABILITY_POLLS_REQUIRED: 1,
|
||||
MAX_POLL_TIME_MS: 50,
|
||||
MAX_POLL_TIME_MS: 0,
|
||||
})
|
||||
|
||||
const mockClient = {
|
||||
@@ -277,8 +274,8 @@ describe("pollSyncSession", () => {
|
||||
taskId: undefined,
|
||||
})
|
||||
|
||||
//#then - timeout returns null (not an error, result is fetched separately)
|
||||
expect(result).toBeNull()
|
||||
//#then - timeout returns error string
|
||||
expect(result).toBe("Poll timeout reached after 50ms for session ses_timeout")
|
||||
})
|
||||
})
|
||||
|
||||
@@ -327,19 +324,111 @@ describe("pollSyncSession", () => {
|
||||
})
|
||||
})
|
||||
|
||||
describe("isSessionComplete edge cases", () => {
|
||||
const { isSessionComplete } = require("./sync-session-poller")
|
||||
describe("isSessionComplete edge cases", () => {
|
||||
test("returns false when messages array is empty", () => {
|
||||
const { isSessionComplete } = require("./sync-session-poller")
|
||||
|
||||
test("returns false when messages array is empty", () => {
|
||||
//#given - empty messages array
|
||||
const messages: any[] = []
|
||||
//#given - empty messages array
|
||||
const messages: any[] = []
|
||||
|
||||
//#when
|
||||
const result = isSessionComplete(messages)
|
||||
//#when
|
||||
const result = isSessionComplete(messages)
|
||||
|
||||
//#then - should return false
|
||||
expect(result).toBe(false)
|
||||
})
|
||||
//#then - should return false
|
||||
expect(result).toBe(false)
|
||||
})
|
||||
|
||||
test("returns false when no assistant message exists", () => {
|
||||
const { isSessionComplete } = require("./sync-session-poller")
|
||||
|
||||
//#given - only user messages, no assistant
|
||||
const messages = [
|
||||
{ info: { id: "msg_001", role: "user", time: { created: 1000 } } },
|
||||
{ info: { id: "msg_002", role: "user", time: { created: 2000 } } },
|
||||
]
|
||||
|
||||
//#when
|
||||
const result = isSessionComplete(messages)
|
||||
|
||||
//#then - should return false
|
||||
expect(result).toBe(false)
|
||||
})
|
||||
|
||||
test("returns false when only assistant message exists (no user)", () => {
|
||||
const { isSessionComplete } = require("./sync-session-poller")
|
||||
|
||||
//#given - only assistant message, no user message
|
||||
const messages = [
|
||||
{
|
||||
info: { id: "msg_001", role: "assistant", time: { created: 1000 }, finish: "end_turn" },
|
||||
parts: [{ type: "text", text: "Response" }],
|
||||
},
|
||||
]
|
||||
|
||||
//#when
|
||||
const result = isSessionComplete(messages)
|
||||
|
||||
//#then - should return false (no user message to compare IDs)
|
||||
expect(result).toBe(false)
|
||||
})
|
||||
|
||||
test("returns false when assistant message has missing finish field", () => {
|
||||
const { isSessionComplete } = require("./sync-session-poller")
|
||||
|
||||
//#given - assistant message without finish field
|
||||
const messages = [
|
||||
{ info: { id: "msg_001", role: "user", time: { created: 1000 } } },
|
||||
{
|
||||
info: { id: "msg_002", role: "assistant", time: { created: 2000 } },
|
||||
parts: [{ type: "text", text: "Response" }],
|
||||
},
|
||||
]
|
||||
|
||||
//#when
|
||||
const result = isSessionComplete(messages)
|
||||
|
||||
//#then - should return false (missing finish)
|
||||
expect(result).toBe(false)
|
||||
})
|
||||
|
||||
test("returns false when assistant message has missing info.id field", () => {
|
||||
const { isSessionComplete } = require("./sync-session-poller")
|
||||
|
||||
//#given - assistant message without id in info
|
||||
const messages = [
|
||||
{ info: { id: "msg_001", role: "user", time: { created: 1000 } } },
|
||||
{
|
||||
info: { role: "assistant", time: { created: 2000 }, finish: "end_turn" },
|
||||
parts: [{ type: "text", text: "Response" }],
|
||||
},
|
||||
]
|
||||
|
||||
//#when
|
||||
const result = isSessionComplete(messages)
|
||||
|
||||
//#then - should return false (missing assistant id)
|
||||
expect(result).toBe(false)
|
||||
})
|
||||
|
||||
test("returns false when user message has missing info.id field", () => {
|
||||
const { isSessionComplete } = require("./sync-session-poller")
|
||||
|
||||
//#given - user message without id in info
|
||||
const messages = [
|
||||
{ info: { role: "user", time: { created: 1000 } } },
|
||||
{
|
||||
info: { id: "msg_002", role: "assistant", time: { created: 2000 }, finish: "end_turn" },
|
||||
parts: [{ type: "text", text: "Response" }],
|
||||
},
|
||||
]
|
||||
|
||||
//#when
|
||||
const result = isSessionComplete(messages)
|
||||
|
||||
//#then - should return false (missing user id)
|
||||
expect(result).toBe(false)
|
||||
})
|
||||
})
|
||||
|
||||
test("returns false when no assistant message exists", () => {
|
||||
//#given - only user messages, no assistant
|
||||
|
||||
@@ -36,6 +36,7 @@ export async function pollSyncSession(
|
||||
const syncTiming = getTimingConfig()
|
||||
const pollStart = Date.now()
|
||||
let pollCount = 0
|
||||
let timedOut = false
|
||||
|
||||
log("[task] Starting poll loop", { sessionID: input.sessionID, agentToUse: input.agentToUse })
|
||||
|
||||
@@ -93,8 +94,9 @@ export async function pollSyncSession(
|
||||
}
|
||||
|
||||
if (Date.now() - pollStart >= syncTiming.MAX_POLL_TIME_MS) {
|
||||
timedOut = true
|
||||
log("[task] Poll timeout reached", { sessionID: input.sessionID, pollCount })
|
||||
}
|
||||
|
||||
return null
|
||||
return timedOut ? `Poll timeout reached after ${syncTiming.MAX_POLL_TIME_MS}ms for session ${input.sessionID}` : null
|
||||
}
|
||||
|
||||
@@ -0,0 +1,217 @@
|
||||
const { describe, test, expect, beforeEach, afterEach, mock, spyOn } = require("bun:test")
|
||||
|
||||
describe("executeSyncTask - cleanup on error paths", () => {
|
||||
let removeTaskCalls: string[] = []
|
||||
let addTaskCalls: any[] = []
|
||||
let deleteCalls: string[] = []
|
||||
let addCalls: string[] = []
|
||||
let resetToastManager: (() => void) | null = null
|
||||
|
||||
beforeEach(() => {
|
||||
//#given - configure fast timing for all tests
|
||||
const { __setTimingConfig } = require("./timing")
|
||||
__setTimingConfig({
|
||||
POLL_INTERVAL_MS: 10,
|
||||
MIN_STABILITY_TIME_MS: 0,
|
||||
STABILITY_POLLS_REQUIRED: 1,
|
||||
MAX_POLL_TIME_MS: 100,
|
||||
})
|
||||
|
||||
//#given - reset call tracking
|
||||
removeTaskCalls = []
|
||||
addTaskCalls = []
|
||||
deleteCalls = []
|
||||
addCalls = []
|
||||
|
||||
//#given - initialize real task toast manager (avoid global module mocks)
|
||||
const { initTaskToastManager, _resetTaskToastManagerForTesting } = require("../../features/task-toast-manager/manager")
|
||||
_resetTaskToastManagerForTesting()
|
||||
resetToastManager = _resetTaskToastManagerForTesting
|
||||
|
||||
const toastManager = initTaskToastManager({
|
||||
tui: { showToast: mock(() => Promise.resolve()) },
|
||||
})
|
||||
|
||||
spyOn(toastManager, "addTask").mockImplementation((task: any) => {
|
||||
addTaskCalls.push(task)
|
||||
})
|
||||
spyOn(toastManager, "removeTask").mockImplementation((id: string) => {
|
||||
removeTaskCalls.push(id)
|
||||
})
|
||||
|
||||
//#given - mock subagentSessions
|
||||
const { subagentSessions } = require("../../features/claude-code-session-state")
|
||||
spyOn(subagentSessions, "add").mockImplementation((id: string) => {
|
||||
addCalls.push(id)
|
||||
})
|
||||
spyOn(subagentSessions, "delete").mockImplementation((id: string) => {
|
||||
deleteCalls.push(id)
|
||||
})
|
||||
|
||||
//#given - mock other dependencies
|
||||
mock.module("./sync-session-creator.ts", () => ({
|
||||
createSyncSession: async () => ({ ok: true, sessionID: "ses_test_12345678" }),
|
||||
}))
|
||||
|
||||
mock.module("./sync-prompt-sender.ts", () => ({
|
||||
sendSyncPrompt: async () => null,
|
||||
}))
|
||||
|
||||
mock.module("./sync-session-poller.ts", () => ({
|
||||
pollSyncSession: async () => null,
|
||||
}))
|
||||
|
||||
mock.module("./sync-result-fetcher.ts", () => ({
|
||||
fetchSyncResult: async () => ({ ok: true, textContent: "Result" }),
|
||||
}))
|
||||
})
|
||||
|
||||
afterEach(() => {
|
||||
//#given - reset timing after each test
|
||||
const { __resetTimingConfig } = require("./timing")
|
||||
__resetTimingConfig()
|
||||
|
||||
mock.restore()
|
||||
resetToastManager?.()
|
||||
resetToastManager = null
|
||||
})
|
||||
|
||||
test("cleans up toast and subagentSessions when fetchSyncResult returns ok: false", async () => {
|
||||
//#given - mock fetchSyncResult to return error
|
||||
mock.module("./sync-result-fetcher.ts", () => ({
|
||||
fetchSyncResult: async () => ({ ok: false, error: "Fetch failed" }),
|
||||
}))
|
||||
|
||||
const mockClient = {
|
||||
session: {
|
||||
create: async () => ({ data: { id: "ses_test_12345678" } }),
|
||||
},
|
||||
}
|
||||
|
||||
const { executeSyncTask } = require("./sync-task")
|
||||
|
||||
const mockCtx = {
|
||||
sessionID: "parent-session",
|
||||
callID: "call-123",
|
||||
metadata: () => {},
|
||||
}
|
||||
|
||||
const mockExecutorCtx = {
|
||||
client: mockClient,
|
||||
directory: "/tmp",
|
||||
onSyncSessionCreated: null,
|
||||
}
|
||||
|
||||
const args = {
|
||||
prompt: "test prompt",
|
||||
description: "test task",
|
||||
category: "test",
|
||||
load_skills: [],
|
||||
run_in_background: false,
|
||||
command: null,
|
||||
}
|
||||
|
||||
//#when - executeSyncTask with fetchSyncResult failing
|
||||
const result = await executeSyncTask(args, mockCtx, mockExecutorCtx, {
|
||||
sessionID: "parent-session",
|
||||
}, "test-agent", undefined, undefined)
|
||||
|
||||
//#then - should return error and cleanup resources
|
||||
expect(result).toBe("Fetch failed")
|
||||
expect(removeTaskCalls.length).toBe(1)
|
||||
expect(removeTaskCalls[0]).toBe("sync_ses_test")
|
||||
expect(deleteCalls.length).toBe(1)
|
||||
expect(deleteCalls[0]).toBe("ses_test_12345678")
|
||||
})
|
||||
|
||||
test("cleans up toast and subagentSessions when pollSyncSession returns error", async () => {
|
||||
//#given - mock pollSyncSession to return error
|
||||
mock.module("./sync-session-poller.ts", () => ({
|
||||
pollSyncSession: async () => "Poll error",
|
||||
}))
|
||||
|
||||
const mockClient = {
|
||||
session: {
|
||||
create: async () => ({ data: { id: "ses_test_12345678" } }),
|
||||
},
|
||||
}
|
||||
|
||||
const { executeSyncTask } = require("./sync-task")
|
||||
|
||||
const mockCtx = {
|
||||
sessionID: "parent-session",
|
||||
callID: "call-123",
|
||||
metadata: () => {},
|
||||
}
|
||||
|
||||
const mockExecutorCtx = {
|
||||
client: mockClient,
|
||||
directory: "/tmp",
|
||||
onSyncSessionCreated: null,
|
||||
}
|
||||
|
||||
const args = {
|
||||
prompt: "test prompt",
|
||||
description: "test task",
|
||||
category: "test",
|
||||
load_skills: [],
|
||||
run_in_background: false,
|
||||
command: null,
|
||||
}
|
||||
|
||||
//#when - executeSyncTask with pollSyncSession failing
|
||||
const result = await executeSyncTask(args, mockCtx, mockExecutorCtx, {
|
||||
sessionID: "parent-session",
|
||||
}, "test-agent", undefined, undefined)
|
||||
|
||||
//#then - should return error and cleanup resources
|
||||
expect(result).toBe("Poll error")
|
||||
expect(removeTaskCalls.length).toBe(1)
|
||||
expect(removeTaskCalls[0]).toBe("sync_ses_test")
|
||||
expect(deleteCalls.length).toBe(1)
|
||||
expect(deleteCalls[0]).toBe("ses_test_12345678")
|
||||
})
|
||||
|
||||
test("cleans up toast and subagentSessions on successful completion", async () => {
|
||||
const mockClient = {
|
||||
session: {
|
||||
create: async () => ({ data: { id: "ses_test_12345678" } }),
|
||||
},
|
||||
}
|
||||
|
||||
const { executeSyncTask } = require("./sync-task")
|
||||
|
||||
const mockCtx = {
|
||||
sessionID: "parent-session",
|
||||
callID: "call-123",
|
||||
metadata: () => {},
|
||||
}
|
||||
|
||||
const mockExecutorCtx = {
|
||||
client: mockClient,
|
||||
directory: "/tmp",
|
||||
onSyncSessionCreated: null,
|
||||
}
|
||||
|
||||
const args = {
|
||||
prompt: "test prompt",
|
||||
description: "test task",
|
||||
category: "test",
|
||||
load_skills: [],
|
||||
run_in_background: false,
|
||||
command: null,
|
||||
}
|
||||
|
||||
//#when - executeSyncTask completes successfully
|
||||
const result = await executeSyncTask(args, mockCtx, mockExecutorCtx, {
|
||||
sessionID: "parent-session",
|
||||
}, "test-agent", undefined, undefined)
|
||||
|
||||
//#then - should complete and cleanup resources
|
||||
expect(result).toContain("Task completed")
|
||||
expect(removeTaskCalls.length).toBe(1)
|
||||
expect(removeTaskCalls[0]).toBe("sync_ses_test")
|
||||
expect(deleteCalls.length).toBe(1)
|
||||
expect(deleteCalls[0]).toBe("ses_test_12345678")
|
||||
})
|
||||
})
|
||||
@@ -102,30 +102,25 @@ export async function executeSyncTask(
|
||||
return promptError
|
||||
}
|
||||
|
||||
const pollError = await pollSyncSession(ctx, client, {
|
||||
sessionID,
|
||||
agentToUse,
|
||||
toastManager,
|
||||
taskId,
|
||||
})
|
||||
if (pollError) {
|
||||
return pollError
|
||||
}
|
||||
try {
|
||||
const pollError = await pollSyncSession(ctx, client, {
|
||||
sessionID,
|
||||
agentToUse,
|
||||
toastManager,
|
||||
taskId,
|
||||
})
|
||||
if (pollError) {
|
||||
return pollError
|
||||
}
|
||||
|
||||
const result = await fetchSyncResult(client, sessionID)
|
||||
if (!result.ok) {
|
||||
return result.error
|
||||
}
|
||||
const result = await fetchSyncResult(client, sessionID)
|
||||
if (!result.ok) {
|
||||
return result.error
|
||||
}
|
||||
|
||||
const duration = formatDuration(startTime)
|
||||
const duration = formatDuration(startTime)
|
||||
|
||||
if (toastManager) {
|
||||
toastManager.removeTask(taskId)
|
||||
}
|
||||
|
||||
subagentSessions.delete(sessionID)
|
||||
|
||||
return `Task completed in ${duration}.
|
||||
return `Task completed in ${duration}.
|
||||
|
||||
Agent: ${agentToUse}${args.category ? ` (category: ${args.category})` : ""}
|
||||
|
||||
@@ -136,13 +131,15 @@ ${result.textContent || "(No text output)"}
|
||||
<task_metadata>
|
||||
session_id: ${sessionID}
|
||||
</task_metadata>`
|
||||
} finally {
|
||||
if (toastManager && taskId !== undefined) {
|
||||
toastManager.removeTask(taskId)
|
||||
}
|
||||
if (syncSessionID) {
|
||||
subagentSessions.delete(syncSessionID)
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
if (toastManager && taskId !== undefined) {
|
||||
toastManager.removeTask(taskId)
|
||||
}
|
||||
if (syncSessionID) {
|
||||
subagentSessions.delete(syncSessionID)
|
||||
}
|
||||
return formatDetailedError(error, {
|
||||
operation: "Execute task",
|
||||
args,
|
||||
|
||||
Reference in New Issue
Block a user