diff --git a/frontend/app/components/assistant-ui/thread.tsx b/frontend/app/components/assistant-ui/thread.tsx
index a26fe77..9cdeeb4 100644
--- a/frontend/app/components/assistant-ui/thread.tsx
+++ b/frontend/app/components/assistant-ui/thread.tsx
@@ -58,6 +58,7 @@ import {
import { MarkdownText } from "@/components/assistant-ui/markdown-text";
import { Reasoning } from "@/components/assistant-ui/reasoning";
import {
+ type ClawRunStatus,
fetchLatestRunStatus,
toReplayRepository,
} from "@/components/assistant-ui/thread-list";
@@ -925,6 +926,8 @@ const ComposerAction: FC = () => {
});
const [runtimeCancelling, setRuntimeCancelling] = useState(false);
const cancelSessionId = replayedRun.sessionId ?? currentSessionId;
+ const backendRunStatus = useBackendActiveRunStatus(cancelSessionId);
+ const cancelRunId = replayedRun.runId ?? backendRunStatus?.run_id ?? null;
useEffect(() => {
if (!runtimeRunning) setRuntimeCancelling(false);
}, [runtimeRunning]);
@@ -935,7 +938,7 @@ const ComposerAction: FC = () => {
- {!runtimeRunning && !replayedRun.active ? (
+ {!runtimeRunning && !replayedRun.active && !backendRunStatus ? (
{
onClick={() => {
if (!cancelSessionId) return;
setRuntimeCancelling(true);
- void cancelLatestRun(cancelSessionId, replayedRun.runId)
+ void cancelLatestRun(cancelSessionId, cancelRunId)
.catch(() => undefined)
.finally(() => {
// 后端可能已经 idle;无论取消接口是否真的命中 run,都清理本地残留运行态。
aui.thread().cancelRun();
+ window.dispatchEvent(new Event("claw-sessions-changed"));
window.setTimeout(() => setRuntimeCancelling(false), 1200);
});
}}
@@ -973,16 +977,48 @@ const ComposerAction: FC = () => {
) : null}
- {!runtimeRunning && replayedRun.active ? (
+ {!runtimeRunning && (replayedRun.active || backendRunStatus) ? (
) : null}
);
};
+function useBackendActiveRunStatus(sessionId: string | null) {
+ const [status, setStatus] = useState(null);
+
+ useEffect(() => {
+ if (!sessionId) {
+ setStatus(null);
+ return;
+ }
+ const safeSessionId = sessionId;
+ let cancelled = false;
+
+ async function refresh() {
+ const nextStatus = await fetchLatestRunStatus(safeSessionId);
+ if (cancelled) return;
+ setStatus(isActiveRunStatus(nextStatus) ? nextStatus : null);
+ }
+
+ void refresh();
+ const interval = window.setInterval(refresh, 1000);
+ return () => {
+ cancelled = true;
+ window.clearInterval(interval);
+ };
+ }, [sessionId]);
+
+ return status;
+}
+
+function isActiveRunStatus(status?: ClawRunStatus | null) {
+ return status?.status === "queued" || status?.status === "running";
+}
+
const ReplayedRunCancelButton: FC<{
sessionId: string | null;
runId: string | null;