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:
@@ -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,
|
||||
|
||||
Reference in New Issue
Block a user