diff --git a/README.md b/README.md index 0ee1781cc..133c6bc4c 100644 --- a/README.md +++ b/README.md @@ -926,7 +926,7 @@ Image bytes loaded for a vision-model call are transient: DeerFlow removes the h After each run, DeerFlow records a workspace change summary for the run-owned `workspace` and `outputs` directories. The Web UI shows a compact "files changed" badge on the assistant turn; opening it reveals created, modified, and deleted files with text diffs when safe to display. Uploads are excluded because they are user inputs, not agent-generated changes. Large, binary, or sensitive-looking files are shown as metadata only. -Files presented through `present_files` remain part of the thread's artifact state, and the Web UI restores the artifact panel and selected document after a page refresh. +Files presented through `present_files` remain part of the thread's artifact state, and the Web UI restores the artifact panel and selected document after a page refresh. The currently selected formal artifact is refreshed once when the run finishes so edits become visible without a manual reload. With `AioSandboxProvider`, shell execution runs inside isolated containers. With `LocalSandboxProvider`, file tools still map to per-thread directories on the host, but host `bash` is disabled by default because it is not a secure isolation boundary. Re-enable host bash only for fully trusted local workflows. Host bash commands have a wall-clock timeout, and long-lived processes should be started in the background with output redirected to a workspace log. diff --git a/frontend/AGENTS.md b/frontend/AGENTS.md index 0d0309f06..70885c9e0 100644 --- a/frontend/AGENTS.md +++ b/frontend/AGENTS.md @@ -69,6 +69,7 @@ The frontend is a stateful chat application. Users create **threads** (conversat 2. Stream events update thread state (messages, artifacts, todos, goal). The main thread stream uses the LangGraph SDK's `throttle: true` mode so updates received in the same macrotask coalesce before React is notified; do not replace it with a numeric delay without validating the SDK's trailing-debounce behavior on a continuous stream. File-tool artifact auto-open work must run in an effect with timer cleanup; never schedule timers while rendering streamed `write_file` or `str_replace` updates. `ThreadState.artifacts` remains the authoritative artifact list. The artifacts provider persists only thread-scoped panel UI state (`open`, selected path, and a refresh bootstrap cache) in session storage; an initial empty stream value must not overwrite that restored state before history finishes loading. + Formal artifact content is refreshed once when the run finishes; transient `write-file:` previews remain message-driven. 3. `useThreadHistory` loads persisted conversation pages from `GET /api/threads/{id}/messages/page`, preserving the backend's thread-global event `seq`; rendering overlays checkpoint/live copies at their matching canonical identities (a summarized checkpoint may contain a protected early input plus a recent tail). Context-compaction rescue diffs every retained visible identity rather than slicing at the first anchor, and keeps a run-scoped ledger of committed visible messages so replacement updates and repeated rolling checkpoint windows cannot erase an already displayed step. The resolver suppresses checkpoint/transient prefixes whose canonical position is still behind an unloaded cursor page instead of collapsing that unknown gap before a recent anchor, then adds optimistic messages without timestamp re-sorting. History invalidation preserves already-loaded pages so their established ordering positions are not discarded. 4. Stop actions call the LangGraph SDK stream stop path; `core/threads/hooks.ts` invalidates current-thread, thread-history, token-usage, and sidebar/search caches immediately and schedules one follow-up refetch because SDK stop may finish via abort + fire-and-forget cancel before backend title finalization commits 5. TanStack Query manages server state; localStorage stores user settings diff --git a/frontend/src/core/artifacts/hooks.ts b/frontend/src/core/artifacts/hooks.ts index df6a68a37..e66c3bee1 100644 --- a/frontend/src/core/artifacts/hooks.ts +++ b/frontend/src/core/artifacts/hooks.ts @@ -1,5 +1,5 @@ import { useQuery } from "@tanstack/react-query"; -import { useMemo } from "react"; +import { useEffect, useMemo, useRef } from "react"; import { useThread } from "@/components/workspace/messages/context"; @@ -25,15 +25,27 @@ export function useArtifactContent({ return null; }, [filepath, isWriteFile, thread]); - const { data, isLoading, error } = useQuery({ + const { data, isLoading, error, refetch } = useQuery({ queryKey: ["artifact", filepath, threadId, isMock], queryFn: () => { return loadArtifactContent({ filepath, threadId, isMock }); }, enabled, - // Cache artifact content for 5 minutes to avoid repeated fetches (especially for .skill ZIP extraction) - staleTime: 5 * 60 * 1000, + staleTime: 0, + refetchOnWindowFocus: true, }); + + // Refetch once when the run settles so edits made during the run are + // visible without a manual reload. + const wasLoadingRef = useRef(thread.isLoading); + useEffect(() => { + const wasLoading = wasLoadingRef.current; + wasLoadingRef.current = thread.isLoading; + if (wasLoading && !thread.isLoading && enabled && !isWriteFile) { + void refetch().catch(() => undefined); + } + }, [enabled, isWriteFile, refetch, thread.isLoading]); + return { content: isWriteFile ? content : data?.content, url: isWriteFile ? undefined : data?.url, diff --git a/frontend/src/core/artifacts/loader.ts b/frontend/src/core/artifacts/loader.ts index 9584507a0..d935e1237 100644 --- a/frontend/src/core/artifacts/loader.ts +++ b/frontend/src/core/artifacts/loader.ts @@ -19,7 +19,7 @@ export async function loadArtifactContent({ enhancedFilepath = filepath + "/SKILL.md"; } const url = urlOfArtifact({ filepath: enhancedFilepath, threadId, isMock }); - const response = await fetch(url); + const response = await fetch(url, { cache: "no-store" }); const text = await response.text(); return { content: text, url }; }