
























@@ -197,6 +197,21 @@ type ChatPayload = {
197197message?: unknown;
198198};
199199200+type AgentWaitResult = {
201+status?: string;
202+error?: string;
203+stopReason?: string;
204+endedAt?: number;
205+pendingError?: boolean;
206+timeoutPhase?: string;
207+providerStarted?: boolean;
208+aborted?: boolean;
209+livenessState?: string;
210+yielded?: boolean;
211+};
212+213+const EMPTY_FINAL_FALLBACK_GRACE_MS = 500;
214+200215function extractTextFromMessage(message: unknown): string {
201216if (!message || typeof message !== "object") {
202217return "";
@@ -218,6 +233,37 @@ function extractTextFromMessage(message: unknown): string {
218233return parts.join("\n\n").trim();
219234}
220235236+function getTerminalAgentWaitError(result: AgentWaitResult | undefined): Error | undefined {
237+if (!result) {
238+return undefined;
239+}
240+const message = result.error?.trim();
241+if (result.status === "error") {
242+return new Error(message || "OpenClaw tool call failed");
243+}
244+if (result.status !== "timeout" || result.pendingError) {
245+return undefined;
246+}
247+const stopReason = result.stopReason?.trim();
248+const timeoutPhase = result.timeoutPhase?.trim();
249+const livenessState = result.livenessState?.trim();
250+const hasTerminalTimeoutMetadata =
251+result.endedAt !== undefined ||
252+message !== undefined ||
253+result.aborted === true ||
254+(livenessState !== undefined && livenessState.length > 0) ||
255+result.yielded === true ||
256+(stopReason !== undefined && stopReason.length > 0) ||
257+timeoutPhase === "preflight" ||
258+timeoutPhase === "provider" ||
259+timeoutPhase === "post_turn" ||
260+result.providerStarted === true;
261+if (hasTerminalTimeoutMetadata) {
262+return new Error(message || "OpenClaw tool call timed out");
263+}
264+return undefined;
265+}
266+221267function waitForChatResult(params: {
222268client: GatewayBrowserClient;
223269runId: string;
@@ -231,15 +277,62 @@ function waitForChatResult(params: {
231277return;
232278}
233279const timer = window.setTimeout(() => {
234-cleanup();
235-reject(new Error("OpenClaw tool call timed out"));
280+settleReject(new Error("OpenClaw tool call timed out"));
236281}, params.timeoutMs);
282+let settled = false;
283+let emptyFinalWaitStarted = false;
284+let emptyFinalFallbackTimer: number | undefined;
237285const onAbort = () => {
238-cleanup();
239-reject(new DOMException("OpenClaw tool call aborted", "AbortError"));
286+settleReject(new DOMException("OpenClaw tool call aborted", "AbortError"));
240287};
241288params.signal?.addEventListener("abort", onAbort, { once: true });
242289let unsubscribe: () => void = () => undefined;
290+const settleResolve = (value: string) => {
291+if (settled) {
292+return;
293+}
294+settled = true;
295+cleanup();
296+resolve(value);
297+};
298+const settleReject = (error: Error | DOMException) => {
299+if (settled) {
300+return;
301+}
302+settled = true;
303+cleanup();
304+reject(error);
305+};
306+const waitForEmptyFinalFallback = () => {
307+if (emptyFinalWaitStarted) {
308+return;
309+}
310+emptyFinalWaitStarted = true;
311+void params.client
312+.request<AgentWaitResult>("agent.wait", {
313+runId: params.runId,
314+timeoutMs: params.timeoutMs,
315+})
316+.then((result) => {
317+if (settled) {
318+return;
319+}
320+const waitError = getTerminalAgentWaitError(result);
321+if (waitError) {
322+settleReject(waitError);
323+return;
324+}
325+if (result?.status === "timeout") {
326+return;
327+}
328+emptyFinalFallbackTimer = window.setTimeout(() => {
329+settleResolve("OpenClaw finished with no text.");
330+}, EMPTY_FINAL_FALLBACK_GRACE_MS);
331+})
332+.catch((error) => {
333+settleReject(error instanceof Error ? error : new Error(String(error)));
334+});
335+};
243336unsubscribe = params.client.addEventListener((evt: GatewayEventFrame) => {
244337if (evt.event !== "chat") {
245338return;
@@ -250,20 +343,25 @@ function waitForChatResult(params: {
250343}
251344emitRealtimeTalkAgentProgress(params.emitTalkEvent, payload);
252345if (payload.state === "final") {
253-cleanup();
254-resolve(extractTextFromMessage(payload.message) || "OpenClaw finished with no text.");
346+const finalText = extractTextFromMessage(payload.message);
347+if (finalText) {
348+settleResolve(finalText);
349+return;
350+}
351+waitForEmptyFinalFallback();
255352} else if (payload.state === "aborted") {
256-cleanup();
257-reject(
353+settleReject(
258354new DOMException(payload.errorMessage ?? "OpenClaw tool call aborted", "AbortError"),
259355);
260356} else if (payload.state === "error") {
261-cleanup();
262-reject(new Error(payload.errorMessage ?? "OpenClaw tool call failed"));
357+settleReject(new Error(payload.errorMessage ?? "OpenClaw tool call failed"));
263358}
264359});
265360function cleanup() {
266361window.clearTimeout(timer);
362+if (emptyFinalFallbackTimer !== undefined) {
363+window.clearTimeout(emptyFinalFallbackTimer);
364+}
267365params.signal?.removeEventListener("abort", onAbort);
268366unsubscribe();
269367}
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。