feat: LLM intent router, direct chat execution, and Memory V2 light intervention

Wire chat intent routing with direct_chat on regular sessions, skill-selected
short-circuit to Agent, memory light/heavy intervention tiers, and fix direct
chat UI stuck streaming after completion.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
john
2026-07-04 22:32:57 +08:00
parent bfb6356f7d
commit e45c9300bf
33 changed files with 3412 additions and 105 deletions
+9 -2
View File
@@ -172,6 +172,7 @@ export function ChatPanel({
const [uploadingImage, setUploadingImage] = useState(false);
const [imageError, setImageError] = useState<string | null>(null);
const [voiceStopSignal, setVoiceStopSignal] = useState(0);
const pendingSkillRef = useRef<string | null>(null);
const [randomPrompt] = useState(
() => CHAT_PLACEHOLDER_PROMPTS[Math.floor(Math.random() * CHAT_PLACEHOLDER_PROMPTS.length)],
);
@@ -432,9 +433,11 @@ export function ChatPanel({
return { accepted, message };
}, [pendingImageBytes, pendingImages.length]);
const submitText = useCallback(async (textOverride?: string) => {
const submitText = useCallback(async (textOverride?: string, skillIdOverride?: string) => {
const baseText = typeof textOverride === 'string' ? textOverride : input;
const trimmed = baseText.trim();
const selectedChatSkill = skillIdOverride ?? pendingSkillRef.current ?? undefined;
pendingSkillRef.current = null;
if ((!trimmed && pendingImages.length === 0) || voiceDisabled) return;
if (pendingImages.length > 0 && !onUploadImage) {
setImageError('当前会话暂不支持图片发送');
@@ -518,6 +521,7 @@ export function ChatPanel({
await onSubmit(trimmed, imagesToSend, previewImagesToSend, {
messageId: outgoingMessageId,
forceDeepReasoning,
selectedChatSkill,
});
uploadedImages.forEach(revokePendingImage);
setPendingImages([]);
@@ -823,7 +827,10 @@ export function ChatPanel({
skills={chatSkills}
disabled={voiceDisabled}
onSelect={submitText}
onPrefill={setInput}
onPrefill={(prompt, skillId) => {
pendingSkillRef.current = skillId ?? null;
setInput(prompt);
}}
/>
)}
{!showHomeWelcome && (
+4 -4
View File
@@ -10,8 +10,8 @@ export function ChatSkillPicker({
}: {
skills: ChatSkillOption[];
disabled?: boolean;
onSelect: (prompt: string) => void | Promise<void>;
onPrefill?: (prompt: string) => void;
onSelect: (prompt: string, skillId?: string) => void | Promise<void>;
onPrefill?: (prompt: string, skillId?: string) => void;
}) {
const [open, setOpen] = useState(false);
const wrapRef = useRef<HTMLDivElement>(null);
@@ -40,10 +40,10 @@ export function ChatSkillPicker({
setOpen(false);
const prompt = skill.buildPrompt(skill.skillName);
if (skill.prefillOnly) {
onPrefill?.(prompt);
onPrefill?.(prompt, skill.id);
return;
}
void onSelect(prompt);
void onSelect(prompt, skill.id);
};
return (