feat(middleware): introduce TodoMiddleware for context-loss detection in todo management (#1041)

* feat(middleware): introduce TodoMiddleware for context-loss detection in todo management

* Address PR #1041 review suggestions: todo reminder dedup, thread switching, artifact deselect, debug log (#8)

* Initial plan

* Handle all suggestions from PR #1041 review

Co-authored-by: foreleven <4785594+foreleven@users.noreply.github.com>

---------

Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: foreleven <4785594+foreleven@users.noreply.github.com>

* fix(chat-box): prevent automatic deselection of artifacts when switching threads
fix(hooks): reset thread state on new thread creation

---------

Co-authored-by: Copilot <198982749+Copilot@users.noreply.github.com>
Co-authored-by: foreleven <4785594+foreleven@users.noreply.github.com>
This commit is contained in:
JeffJiang
2026-03-10 11:24:53 +08:00
committed by GitHub
parent cf1c4a68ea
commit f5bd691172
8 changed files with 125 additions and 19 deletions

View File

@@ -50,13 +50,13 @@ const ChatBox: React.FC<{ children: React.ReactNode; threadId: string }> = ({
// Update artifacts from the current thread
setArtifacts(thread.values.artifacts);
// Deselect if the currently selected artifact no longer exists
if (
selectedArtifact &&
!thread.values.artifacts?.includes(selectedArtifact)
) {
deselect();
}
// DO NOT automatically deselect the artifact when switching threads, because the artifacts auto discovering is not work now.
// if (
// selectedArtifact &&
// !thread.values.artifacts?.includes(selectedArtifact)
// ) {
// deselect();
// }
if (
env.NEXT_PUBLIC_STATIC_WEBSITE_ONLY === "true" &&

View File

@@ -52,6 +52,10 @@ export function groupMessages<T>(
}
for (const message of messages) {
if (message.name === "todo_reminder") {
continue;
}
if (message.type === "human") {
groups.push({ id: message.id, type: "human", messages: [message] });
continue;

View File

@@ -60,11 +60,12 @@ export function useThreadStream({
useEffect(() => {
const normalizedThreadId = threadId ?? null;
if (threadIdRef.current !== normalizedThreadId) {
threadIdRef.current = normalizedThreadId;
startedRef.current = false; // Reset for new thread
if (!normalizedThreadId) {
// Just reset for new thread creation when threadId becomes null/undefined
startedRef.current = false;
setOnStreamThreadId(normalizedThreadId);
}
threadIdRef.current = normalizedThreadId;
}, [threadId]);
const _handleOnStart = useCallback((id: string) => {
@@ -77,7 +78,6 @@ export function useThreadStream({
const handleStreamStart = useCallback(
(_threadId: string) => {
threadIdRef.current = _threadId;
setOnStreamThreadId(_threadId);
_handleOnStart(_threadId);
},
[_handleOnStart],
@@ -85,6 +85,7 @@ export function useThreadStream({
const queryClient = useQueryClient();
const updateSubtask = useUpdateSubtask();
const thread = useStream<AgentThreadState>({
client: getAPIClient(isMock),
assistantId: "lead_agent",
@@ -93,6 +94,7 @@ export function useThreadStream({
fetchStateHistory: { limit: 1 },
onCreated(meta) {
handleStreamStart(meta.thread_id);
setOnStreamThreadId(meta.thread_id);
},
onLangChainEvent(event) {
if (event.event === "on_tool_end") {