mirror of
https://github.com/bytedance/deer-flow.git
synced 2026-05-22 16:06:50 +00:00
fix(frontend): parse Task cancelled and polling timed out as terminal
Address @ShenAC-SAC's BUG-007 review on #3131. `task_tool.py` actually emits five terminal strings: - `Task Succeeded. Result: …` - `Task failed. …` - `Task timed out. …` - `Task cancelled by user.` ← previously matched none - `Task polling timed out after N minutes …` ← previously matched none The previous cut handled three; the last two fell through to the "unknown content" branch and pushed the subtask card back to `in_progress` even though the backend had already reached a terminal state. Add explicit matches plus regression tests for both. The `in_progress` fallback is now reserved for genuinely unrecognised output (i.e. contract drift), as documented. Refs: bytedance/deer-flow#3107 (BUG-007), bytedance/deer-flow#3131 review
This commit is contained in:
@@ -23,6 +23,8 @@ export interface SubtaskResultUpdate {
|
|||||||
export const SUCCESS_PREFIX = "Task Succeeded. Result:";
|
export const SUCCESS_PREFIX = "Task Succeeded. Result:";
|
||||||
export const FAILURE_PREFIX = "Task failed.";
|
export const FAILURE_PREFIX = "Task failed.";
|
||||||
export const TIMEOUT_PREFIX = "Task timed out";
|
export const TIMEOUT_PREFIX = "Task timed out";
|
||||||
|
export const CANCELLED_PREFIX = "Task cancelled by user.";
|
||||||
|
export const POLLING_TIMEOUT_PREFIX = "Task polling timed out";
|
||||||
export const ERROR_WRAPPER_PATTERN = /^Error\b/i;
|
export const ERROR_WRAPPER_PATTERN = /^Error\b/i;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -62,6 +64,14 @@ export function parseSubtaskResult(text: string): SubtaskResultUpdate {
|
|||||||
return { status: "failed", error: trimmed };
|
return { status: "failed", error: trimmed };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (trimmed.startsWith(CANCELLED_PREFIX)) {
|
||||||
|
return { status: "failed", error: trimmed };
|
||||||
|
}
|
||||||
|
|
||||||
|
if (trimmed.startsWith(POLLING_TIMEOUT_PREFIX)) {
|
||||||
|
return { status: "failed", error: trimmed };
|
||||||
|
}
|
||||||
|
|
||||||
// ToolErrorHandlingMiddleware-style wrapper, or any other terminal error
|
// ToolErrorHandlingMiddleware-style wrapper, or any other terminal error
|
||||||
// signal the backend forwards to the lead agent.
|
// signal the backend forwards to the lead agent.
|
||||||
if (ERROR_WRAPPER_PATTERN.test(trimmed)) {
|
if (ERROR_WRAPPER_PATTERN.test(trimmed)) {
|
||||||
|
|||||||
@@ -25,6 +25,25 @@ describe("parseSubtaskResult", () => {
|
|||||||
expect(parsed.error).toBe("Task timed out after 900s");
|
expect(parsed.error).toBe("Task timed out after 900s");
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("recognises the cancelled-by-user prefix", () => {
|
||||||
|
// bytedance/deer-flow#3131 review: this is one of the five terminal
|
||||||
|
// strings task_tool.py actually emits — the previous cut treated it as
|
||||||
|
// unrecognised content and pushed the card back to in_progress.
|
||||||
|
const parsed = parseSubtaskResult("Task cancelled by user.");
|
||||||
|
expect(parsed.status).toBe("failed");
|
||||||
|
expect(parsed.error).toBe("Task cancelled by user.");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("recognises the polling-timed-out prefix", () => {
|
||||||
|
// Emitted by task_tool when the background polling loop runs out of
|
||||||
|
// budget waiting for the subagent to reach a terminal state.
|
||||||
|
const parsed = parseSubtaskResult(
|
||||||
|
"Task polling timed out after 15 minutes. This may indicate the background task is stuck. Status: RUNNING",
|
||||||
|
);
|
||||||
|
expect(parsed.status).toBe("failed");
|
||||||
|
expect(parsed.error).toContain("polling timed out");
|
||||||
|
});
|
||||||
|
|
||||||
it("treats middleware-wrapped tool errors as terminal failures", () => {
|
it("treats middleware-wrapped tool errors as terminal failures", () => {
|
||||||
// bytedance/deer-flow issue #3107 BUG-007: the parent-visible ToolMessage
|
// bytedance/deer-flow issue #3107 BUG-007: the parent-visible ToolMessage
|
||||||
// produced by ToolErrorHandlingMiddleware never matches the three legacy
|
// produced by ToolErrorHandlingMiddleware never matches the three legacy
|
||||||
|
|||||||
Reference in New Issue
Block a user