



















@@ -31,6 +31,7 @@ const mockState = vi.hoisted(() => ({
3131replyToId?: string;
3232replyToCurrent?: boolean;
3333isReasoning?: boolean;
34+isError?: boolean;
3435} | null,
3536dispatchedReplies: [] as Array<{
3637kind: "tool" | "block" | "final";
@@ -45,6 +46,7 @@ const mockState = vi.hoisted(() => ({
4546replyToId?: string;
4647replyToCurrent?: boolean;
4748isReasoning?: boolean;
49+isError?: boolean;
4850};
4951}>,
5052dispatchError: null as Error | null,
@@ -151,6 +153,7 @@ vi.mock("../../auto-reply/dispatch.js", () => ({
151153replyToId?: string;
152154replyToCurrent?: boolean;
153155isReasoning?: boolean;
156+isError?: boolean;
154157}) => boolean;
155158sendBlockReply: (payload: {
156159text?: string;
@@ -162,6 +165,7 @@ vi.mock("../../auto-reply/dispatch.js", () => ({
162165replyToId?: string;
163166replyToCurrent?: boolean;
164167isReasoning?: boolean;
168+isError?: boolean;
165169}) => boolean;
166170sendToolResult: (payload: {
167171text?: string;
@@ -173,6 +177,7 @@ vi.mock("../../auto-reply/dispatch.js", () => ({
173177replyToId?: string;
174178replyToCurrent?: boolean;
175179isReasoning?: boolean;
180+isError?: boolean;
176181}) => boolean;
177182markComplete: () => void;
178183waitForIdle: () => Promise<void>;
@@ -965,6 +970,52 @@ describe("chat directive tag stripping for non-streaming final payloads", () =>
965970expect(assistantEntries).toStrictEqual([]);
966971});
967972973+it("broadcasts returned agent-run error payloads after an agent starts", async () => {
974+createTranscriptFixture("openclaw-chat-send-agent-returned-error-");
975+const errorMessage = "LLM idle timeout (120s): no response from model";
976+mockState.triggerAgentRunStart = true;
977+mockState.dispatchedReplies = [
978+{
979+kind: "final",
980+payload: {
981+text: errorMessage,
982+isError: true,
983+},
984+},
985+];
986+const respond = vi.fn();
987+const context = createChatContext();
988+989+const broadcast = await runNonStreamingChatSend({
990+ context,
991+ respond,
992+idempotencyKey: "idem-agent-returned-error",
993+message: "please keep working",
994+});
995+996+expect(broadcast).toMatchObject({
997+runId: "idem-agent-returned-error",
998+sessionKey: "main",
999+state: "error",
1000+ errorMessage,
1001+});
1002+const dedupe = context.dedupe.get("chat:idem-agent-returned-error");
1003+expect(dedupe?.ok).toBe(false);
1004+expect(dedupe?.payload).toMatchObject({
1005+runId: "idem-agent-returned-error",
1006+status: "error",
1007+summary: errorMessage,
1008+});
1009+expect(findUserUpdate()).toBeDefined();
1010+const assistantUpdates = mockState.emittedTranscriptUpdates.filter(
1011+(update) =>
1012+typeof update.message === "object" &&
1013+update.message !== null &&
1014+(update.message as { role?: unknown }).role === "assistant",
1015+);
1016+expect(assistantUpdates).toStrictEqual([]);
1017+});
1018+9681019it("keeps visible text on non-agent TTS final media because no model transcript exists", async () => {
9691020const transcriptDir = createTranscriptFixture("openclaw-chat-send-command-tts-final-");
9701021const audioPath = path.join(transcriptDir, "tts.mp3");
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。