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
+57
View File
@@ -7,6 +7,7 @@ import { createMemoryV2PluginBackends } from './memory-v2-plugin-backends.mjs';
import { createMem0HttpClient, createMem0MemoryBackend } from './memory-v2-mem0.mjs';
import { createNeo4jHttpClient, createNeo4jMemoryBackend } from './memory-v2-neo4j.mjs';
import { createPgvectorMemoryBackend } from './memory-v2-pgvector.mjs';
import { backfillLegacyMemoriesToPgvector } from './memory-v2-pgvector-backfill.mjs';
import { createQdrantHttpClient, createQdrantMemoryBackend } from './memory-v2-qdrant.mjs';
import { createRedisStreamsClient, createRedisStreamsMemoryBackend } from './memory-v2-redis-streams.mjs';
import { createWeaviateHttpClient, createWeaviateMemoryBackend } from './memory-v2-weaviate.mjs';
@@ -55,6 +56,7 @@ async function createPgPool(connectionString, { importPg, max }) {
export async function createMemoryV2Runtime({
legacyMemoryService = null,
mysqlPool = null,
env = process.env,
logger = console,
importPg = (specifier) => import(specifier),
@@ -75,6 +77,8 @@ export async function createMemoryV2Runtime({
const closers = [];
let pgvectorBackend = null;
let pgPoolRef = null;
let pgEmbedQuery = null;
if (pgvectorEnabled && connectionString) {
try {
const embedQuery = await loadEmbeddingModule(embeddingModule, importModule);
@@ -90,6 +94,8 @@ export async function createMemoryV2Runtime({
max: Math.max(1, Number(env?.MEMORY_PGVECTOR_POOL_MAX ?? 5) || 5),
});
closers.push(() => pool.end?.());
pgPoolRef = pool;
pgEmbedQuery = embedQuery;
pgvectorBackend = createPgvectorMemoryBackend({
pool,
enabled: pgvectorEnabled,
@@ -401,6 +407,55 @@ export async function createMemoryV2Runtime({
logger,
});
const pgBackfillEnabled = readFlag(env, 'MEMORY_PGVECTOR_BACKFILL_ENABLED', pgvectorEnabled);
let pgBackfillBusy = false;
let pgBackfillCursor = { updatedAt: 0, id: '' };
async function syncPgvectorFromLegacy() {
if (!pgBackfillEnabled || !pgPoolRef || !pgEmbedQuery || !mysqlPool?.query) return;
if (pgBackfillBusy) return;
pgBackfillBusy = true;
try {
const maxBatches = Math.max(1, Number(env?.MEMORY_PGVECTOR_BACKFILL_MAX_BATCHES ?? 3) || 3);
const batchLimit = Math.max(1, Number(env?.MEMORY_PGVECTOR_BACKFILL_LIMIT ?? 50) || 50);
for (let batch = 0; batch < maxBatches; batch += 1) {
const result = await backfillLegacyMemoriesToPgvector({
mysqlPool,
pgPool: pgPoolRef,
embedMemory: (item) => pgEmbedQuery(item.text, item),
tableName: env?.MEMORY_PGVECTOR_TABLE,
cursor: pgBackfillCursor,
limit: batchLimit,
dryRun: false,
});
pgBackfillCursor = result.nextCursor;
if (!result.hasMore || result.scanned === 0) break;
}
} catch (err) {
logger?.warn?.(
`[memory-v2] pgvector backfill skipped: ${err instanceof Error ? err.message : err}`,
);
} finally {
pgBackfillBusy = false;
}
}
function wrapMemorySideEffect(methodName) {
const original = memory[methodName].bind(memory);
memory[methodName] = async (input = {}) => {
const result = await original(input);
if ((result?.memories ?? 0) > 0 || (result?.analyzed ?? 0) > 0) {
void syncPgvectorFromLegacy();
}
return result;
};
}
if (pgBackfillEnabled && pgPoolRef && pgEmbedQuery && mysqlPool) {
wrapMemorySideEffect('write');
wrapMemorySideEffect('compact');
}
memory.close = async () => {
const results = await Promise.allSettled(closers.map((close) => close()));
for (const result of results) {
@@ -417,6 +472,7 @@ export async function createMemoryV2Runtime({
export async function createManagedMemoryV2Runtime({
legacyMemoryService = null,
mysqlPool = null,
configService = null,
env = process.env,
logger = console,
@@ -478,6 +534,7 @@ export async function createManagedMemoryV2Runtime({
}
const nextRuntime = await createMemoryV2Runtime({
legacyMemoryService,
mysqlPool,
env: state.effectiveEnv,
logger,
importPg,