feat: add heartbeat monitor and vector memory search (Tier 2)
Heartbeat: - HeartbeatMonitor with 5 checks: gateway, model, channels, memory, disk - Configurable interval, failure threshold, notification channel - Recovery notifications when health restores - 25 new tests Vector Memory Search: - EmbeddingProvider interface with OpenAI, Gemini, Ollama, LlamaCpp backends - SQLite-backed VectorStore with cosine similarity search - Text chunker with paragraph-aware splitting and overlap - HybridSearch merging keyword + vector results with configurable weight - Background indexer with dirty-namespace tracking - Graceful fallback to keyword search when embeddings unavailable - 51 new tests Config: automation.heartbeat + memory.embedding schema sections Total: 950 tests passing, all types clean
This commit is contained in:
@@ -22,6 +22,7 @@ export { createCronTools } from './cron.js';
|
||||
|
||||
import type { Tool } from '../types.js';
|
||||
import type { MemoryStore } from '../../memory/store.js';
|
||||
import type { HybridSearch } from '../../memory/hybrid-search.js';
|
||||
import type { WebSearchConfig } from './web-search.js';
|
||||
import { shellExecTool } from './shell.js';
|
||||
import { fileReadTool } from './file-read.js';
|
||||
@@ -47,11 +48,11 @@ export const allBuiltinTools: Tool[] = [
|
||||
];
|
||||
|
||||
/** Create memory tools that require a MemoryStore instance. */
|
||||
export function createMemoryTools(store: MemoryStore): Tool[] {
|
||||
export function createMemoryTools(store: MemoryStore, hybridSearch?: HybridSearch): Tool[] {
|
||||
return [
|
||||
createMemoryReadTool(store),
|
||||
createMemoryWriteTool(store),
|
||||
createMemorySearchTool(store),
|
||||
createMemorySearchTool(store, hybridSearch),
|
||||
];
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import type { Tool, ToolResult } from '../types.js';
|
||||
import type { MemoryStore } from '../../memory/store.js';
|
||||
import type { HybridSearch } from '../../memory/hybrid-search.js';
|
||||
|
||||
interface MemorySearchArgs {
|
||||
query: string;
|
||||
@@ -7,13 +8,15 @@ interface MemorySearchArgs {
|
||||
|
||||
/**
|
||||
* Creates a memory.search tool bound to the given MemoryStore instance.
|
||||
* Searches across all memory namespaces for matching content.
|
||||
* When a HybridSearch instance is provided, uses vector + keyword search;
|
||||
* otherwise falls back to keyword-only search.
|
||||
*/
|
||||
export function createMemorySearchTool(store: MemoryStore): Tool {
|
||||
export function createMemorySearchTool(store: MemoryStore, hybridSearch?: HybridSearch): Tool {
|
||||
return {
|
||||
name: 'memory.search',
|
||||
description:
|
||||
'Search across all memory files for a keyword or phrase. Returns matching lines with surrounding context from every namespace.',
|
||||
'Search across all memory files for a keyword or phrase. Returns matching lines with surrounding context from every namespace.' +
|
||||
(hybridSearch ? ' Uses semantic vector search combined with keyword matching for better results.' : ''),
|
||||
inputSchema: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
@@ -28,6 +31,33 @@ export function createMemorySearchTool(store: MemoryStore): Tool {
|
||||
const args = rawArgs as MemorySearchArgs;
|
||||
|
||||
try {
|
||||
// Try hybrid search first if available
|
||||
if (hybridSearch) {
|
||||
try {
|
||||
const results = await hybridSearch.search(args.query);
|
||||
|
||||
if (results.length === 0) {
|
||||
return { success: true, output: `No matches found for "${args.query}".` };
|
||||
}
|
||||
|
||||
const formatted = results.map((result) => {
|
||||
const sourceLabel = result.source === 'both' ? 'keyword+vector'
|
||||
: result.source === 'vector' ? 'vector'
|
||||
: 'keyword';
|
||||
return `[${result.namespace}:${result.line}] (${sourceLabel}, score: ${result.score.toFixed(3)}) ${result.content}\n context: ${result.context}`;
|
||||
}).join('\n\n');
|
||||
|
||||
return {
|
||||
success: true,
|
||||
output: `Found ${results.length} match${results.length === 1 ? '' : 'es'} for "${args.query}":\n\n${formatted}`,
|
||||
};
|
||||
} catch (hybridError) {
|
||||
// Fall back to keyword search on hybrid failure
|
||||
console.error('Hybrid search failed, falling back to keyword search:', hybridError);
|
||||
}
|
||||
}
|
||||
|
||||
// Keyword-only fallback
|
||||
const results = store.search(args.query);
|
||||
|
||||
if (results.length === 0) {
|
||||
|
||||
Reference in New Issue
Block a user