fix: 协助卡停轮与提交防 409,隐藏配置向导会话,并限制岗位会商产物重复写入

澄清中间件改为 ToolMessage + after_model 停轮,前端提交前取消残留 run;系统/向导 thread_type 从对话列表排除;岗位总结席位加 write_file 上限。

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
董宏凯andCursor committed 2026-08-23 20:29:59 +08:00
1 parent 51afebb394
commit b81d200eca
31 files changed
+1459 -592

No files matched your search

+8 -2
View File
@@ -99,8 +99,14 @@ export function useNotebookChat(
},
});
const send = async (text: string, selectedSourceIds: number[]) => {
if (thread.isLoading || !token) return;
const send = async (
text: string,
selectedSourceIds: number[],
options?: { fromClarification?: boolean },
) => {
if (!token) return;
// 协助卡提交前会先停轮;此时 isLoading 可能仍为 true,不能直接丢弃。
if (thread.isLoading && !options?.fromClarification) return;
// API 需要 document.id,而不是 source.id
const toDocIds = (ids: number[]) =>
ids
+44 -13
View File
@@ -28,6 +28,7 @@ import {
isAbortLikeError,
isTransientStreamTransportError,
} from "./thread-access-error";
import { isHiddenFromRecentChats, shouldHideSystemThreadsFromList } from "./utils";
export type ToolEndEvent = {
name: string;
@@ -210,6 +211,39 @@ export async function stopThreadRun(
}
}
/**
* 协助卡提交前:若上一轮还在流式(弱模型出卡后偶发未停干净),先取消服务端 run
* 并断开本地 SSE,避免 409「已有活跃 run」或用户点了选项却发不出去。
*/
export async function stopActiveThreadBeforeClarificationReply(
threadId: string | null | undefined,
thread: { isLoading?: boolean; stop: () => void | Promise<void> },
): Promise<void> {
let storedRunId: string | null = null;
if (threadId && typeof window !== "undefined") {
try {
storedRunId = normalizeStoredRunId(
window.sessionStorage.getItem(`lg:stream:${threadId}`),
);
} catch {
storedRunId = null;
}
}
if (!thread.isLoading && !storedRunId) return;
try {
await stopThreadRun(threadId);
} catch {
/* best-effort */
}
try {
await Promise.resolve(thread.stop());
} catch {
/* best-effort */
}
// cancel 落地需要一拍,立刻 submit 仍可能 409。
await new Promise((resolve) => setTimeout(resolve, 120));
}
function getStreamErrorMessage(error: unknown): string {
if (typeof error === "string" && error.trim()) {
return error;
@@ -1451,18 +1485,10 @@ export function useThreads(
}
// Safety net on top of the backend's system-thread exclusion: drop
// machine-created threads (scheduled tasks, roundtable workers,
// notebooks) in case an older backend still returns them.
return threads.filter((t) => {
const metadata = t.metadata as Record<string, unknown> | null;
if (metadata?.system) return false;
const threadType = metadata?.thread_type;
return (
threadType !== "notebook" &&
threadType !== "scheduler" &&
threadType !== "roundtable"
);
});
// machine-created / wizard threads (scheduled tasks, report-structure
// setup, writing setup, notebooks, …) in case an older backend still
// returns them.
return threads.filter((t) => !isHiddenFromRecentChats(t));
},
refetchOnWindowFocus: false,
});
@@ -1545,8 +1571,13 @@ export function useThreadsPage(options: {
throw new Error("Failed to load conversations");
}
const rows = (await response.json()) as AgentThread[];
// 与后端 exclude_system 对齐:普通列表再兜一层。调用方显式按
// thread_type / system 取数(笔记本等)时不滤,避免把目标会话藏掉。
const visible = shouldHideSystemThreadsFromList(metadata)
? rows.filter((thread) => !isHiddenFromRecentChats(thread))
: rows;
return {
threads: rows.slice(0, pageSize),
threads: visible.slice(0, pageSize),
hasMore: rows.length > pageSize,
};
},
+38
View File
@@ -91,6 +91,44 @@ export function isBootstrapThread(thread: {
return thread.metadata?.thread_type === "bootstrap";
}
/**
* 不应出现在普通对话列表里的 thread_type。
* 与后端 ``EXCLUDED_SYSTEM_THREAD_TYPES`` 保持同步;bootstrap 不在此列(侧栏仍展示)。
*/
export const HIDDEN_FROM_RECENT_CHAT_TYPES = new Set([
"scheduler",
"roundtable",
"notebook",
"deep_research",
"writing_setup",
"report_structure_setup",
]);
export function isSetupWizardThread(thread: {
metadata?: Record<string, unknown> | null;
}): boolean {
const threadType = thread.metadata?.thread_type;
return threadType === "writing_setup" || threadType === "report_structure_setup";
}
/** 侧栏 / 全部对话应隐藏的系统 / 向导会话(bootstrap 除外,仍会展示)。 */
export function isHiddenFromRecentChats(thread: {
metadata?: Record<string, unknown> | null;
}): boolean {
const metadata = thread.metadata;
if (metadata?.system === true) return true;
const threadType = metadata?.thread_type;
return typeof threadType === "string" && HIDDEN_FROM_RECENT_CHAT_TYPES.has(threadType);
}
/** 调用方显式按 thread_type / system 过滤时,不要再套一层「最近对话」隐藏。 */
export function shouldHideSystemThreadsFromList(
metadata?: Record<string, unknown> | null,
): boolean {
if (!metadata) return true;
return !("thread_type" in metadata || "system" in metadata);
}
/**
* bootstrap 会话点击后回到「创建智能体」页并恢复该会话。该页只挂在
* /page/workspace 下(见 WorkspaceRoutes 的 agents/new/:thread_id),故路径固定,