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
+27 -3
View File
@@ -164,7 +164,8 @@ import { createScheduleService } from './schedule-service.mjs';
import { createFeedbackService } from './user-feedback.mjs';
import { startScheduleReminderWorker } from './schedule-reminder-worker.mjs';
import { createLlmProviderService, RELAY_BOOTSTRAP } from './llm-providers.mjs';
import { createDirectChatService, isDirectChatSessionId, sendDirectChatSessionEvents } from './direct-chat-service.mjs';
import { createDirectChatService, isDirectChatSessionId, isPortalDirectChatSnapshot, sendDirectChatSessionEvents } from './direct-chat-service.mjs';
import { createManagedChatIntentRouter } from './chat-intent-router.mjs';
import { createSessionSnapshotService } from './session-snapshot.mjs';
import { createConversationMemoryService } from './conversation-memory.mjs';
import { createManagedMemoryV2Runtime } from './memory-v2-runtime.mjs';
@@ -285,6 +286,7 @@ if (ACCESS_PASSWORD && !isDatabaseConfigured()) {
let userAuth = null;
let tkmindProxy = null;
let agentRunGateway = null;
let chatIntentRouter = null;
let toolGateway = null;
let directChatService = null;
let sessionSnapshotService = null;
@@ -523,14 +525,22 @@ async function bootstrapUserAuth() {
void llmProviderService.syncSelectedToGoosed().catch((err) => {
console.warn('LLM provider boot sync skipped:', err instanceof Error ? err.message : err);
});
conversationMemoryService = createConversationMemoryService(pool);
memoryV2ConfigService = createMemoryV2AdminConfigService(pool);
conversationMemoryService = createConversationMemoryService(pool, {
llmProviderService,
getEffectiveEnv: async () => {
const state = await memoryV2ConfigService.getRuntimeState().catch(() => null);
return { ...process.env, ...(state?.overrides ?? {}) };
},
});
memoryV2 = await createManagedMemoryV2Runtime({
legacyMemoryService: conversationMemoryService,
configService: memoryV2ConfigService,
mysqlPool: pool,
});
sessionSnapshotService = createSessionSnapshotService(pool, {
conversationMemoryService,
memoryV2,
});
directChatService = createDirectChatService({
userAuth,
@@ -539,6 +549,11 @@ async function bootstrapUserAuth() {
memoryV2,
conversationMemoryService,
});
chatIntentRouter = createManagedChatIntentRouter({
llmProviderService,
memoryV2,
configService: memoryV2ConfigService,
});
tkmindProxy = createTkmindProxy({
apiTarget: API_TARGET,
apiTargets: API_TARGETS,
@@ -564,6 +579,7 @@ async function bootstrapUserAuth() {
tkmindProxy,
toolGateway,
directChatService,
chatIntentRouter,
autoDispatch: ['1', 'true', 'yes', 'on'].includes(
String(process.env.MEMIND_AGENT_RUN_AUTODISPATCH ?? '1').trim().toLowerCase(),
),
@@ -4460,7 +4476,11 @@ api.get('/sessions/:sessionId', async (req, res, next) => {
const canUseSnapshotCache = hintMc != null && hintUa != null;
const mcMatch = snapshot.meta.synced_msg_count === hintMc;
const uaMatch = snapshot.meta.source_updated_at === hintUa;
if (isDirectChatSessionId(sessionId) || (canUseSnapshotCache && mcMatch && uaMatch)) {
if (
isDirectChatSessionId(sessionId) ||
isPortalDirectChatSnapshot(snapshot) ||
(canUseSnapshotCache && mcMatch && uaMatch)
) {
const sanitizedMessages = sanitizeSessionConversationPublicHtmlLinks(
snapshot.messages,
req.currentUser,
@@ -4560,6 +4580,10 @@ api.get('/sessions/:sessionId/events', async (req, res, next) => {
}
return sendDirectChatSessionEvents(req, res, snapshot);
}
const portalDirectSnapshot = await sessionSnapshotService?.get(sessionId).catch(() => null);
if (isPortalDirectChatSnapshot(portalDirectSnapshot)) {
return sendDirectChatSessionEvents(req, res, portalDirectSnapshot);
}
const publishDir = resolveMindSpaceUserPublishDir(__dirname, { id: req.currentUser.id });
const syncPublicHtmlDuringStream = (event) => {
materializePublicHtmlWritesFromSessionEvent(event, { publishDir });