@tenex-chat/backend 0.9.5 → 0.9.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +5 -1
- package/dist/daemon-wrapper.cjs +47 -0
- package/dist/index.js +59268 -0
- package/dist/wrapper.js +171 -0
- package/package.json +19 -27
- package/src/agents/AgentRegistry.ts +9 -7
- package/src/agents/AgentStorage.ts +24 -1
- package/src/agents/agent-installer.ts +6 -0
- package/src/agents/agent-loader.ts +7 -2
- package/src/agents/constants.ts +10 -2
- package/src/agents/execution/AgentExecutor.ts +35 -6
- package/src/agents/execution/StreamCallbacks.ts +53 -13
- package/src/agents/execution/StreamExecutionHandler.ts +110 -16
- package/src/agents/execution/StreamSetup.ts +19 -9
- package/src/agents/execution/ToolEventHandlers.ts +112 -0
- package/src/agents/role-categories.ts +53 -0
- package/src/agents/types/runtime.ts +7 -0
- package/src/agents/types/storage.ts +7 -0
- package/src/commands/agent/import/openclaw-distiller.ts +63 -7
- package/src/commands/agent/import/openclaw-reader.ts +54 -0
- package/src/commands/agent/import/openclaw.ts +120 -29
- package/src/commands/agent/index.ts +83 -2
- package/src/commands/setup/display.ts +123 -0
- package/src/commands/setup/embed.ts +13 -13
- package/src/commands/setup/global-system-prompt.ts +15 -17
- package/src/commands/setup/image.ts +17 -20
- package/src/commands/setup/interactive.ts +37 -20
- package/src/commands/setup/llm.ts +12 -7
- package/src/commands/setup/onboarding.ts +1580 -248
- package/src/commands/setup/providers.ts +3 -3
- package/src/conversations/ConversationStore.ts +23 -2
- package/src/conversations/MessageBuilder.ts +51 -73
- package/src/conversations/formatters/utils/conversation-transcript-formatter.ts +425 -0
- package/src/conversations/search/embeddings/ConversationEmbeddingService.ts +40 -98
- package/src/conversations/search/embeddings/ConversationIndexingJob.ts +40 -52
- package/src/conversations/services/ConversationSummarizer.ts +1 -2
- package/src/conversations/types.ts +11 -0
- package/src/daemon/Daemon.ts +78 -57
- package/src/daemon/ProjectRuntime.ts +6 -12
- package/src/daemon/SubscriptionManager.ts +13 -0
- package/src/daemon/index.ts +0 -1
- package/src/event-handler/index.ts +1 -0
- package/src/index.ts +20 -1
- package/src/llm/ChunkHandler.ts +1 -1
- package/src/llm/FinishHandler.ts +28 -4
- package/src/llm/LLMConfigEditor.ts +218 -106
- package/src/llm/index.ts +0 -4
- package/src/llm/meta/MetaModelResolver.ts +3 -18
- package/src/llm/middleware/message-sanitizer.ts +153 -0
- package/src/llm/providers/ollama-models.ts +0 -38
- package/src/llm/service.ts +50 -15
- package/src/llm/types.ts +0 -12
- package/src/llm/utils/ConfigurationManager.ts +88 -465
- package/src/llm/utils/ConfigurationTester.ts +42 -185
- package/src/llm/utils/ModelSelector.ts +156 -92
- package/src/llm/utils/ProviderConfigUI.ts +10 -141
- package/src/llm/utils/models-dev-cache.ts +102 -23
- package/src/llm/utils/provider-select-prompt.ts +284 -0
- package/src/llm/utils/provider-setup.ts +81 -34
- package/src/llm/utils/variant-list-prompt.ts +361 -0
- package/src/nostr/AgentEventDecoder.ts +1 -0
- package/src/nostr/AgentEventEncoder.ts +37 -0
- package/src/nostr/AgentProfilePublisher.ts +13 -0
- package/src/nostr/AgentPublisher.ts +26 -0
- package/src/nostr/kinds.ts +1 -0
- package/src/nostr/ndkClient.ts +4 -1
- package/src/nostr/types.ts +12 -0
- package/src/prompts/fragments/25-rag-instructions.ts +22 -21
- package/src/prompts/fragments/31-agents-md-guidance.ts +7 -21
- package/src/prompts/fragments/index.ts +2 -0
- package/src/prompts/utils/systemPromptBuilder.ts +18 -28
- package/src/services/AgentDefinitionMonitor.ts +8 -0
- package/src/services/ConfigService.ts +34 -0
- package/src/services/PubkeyService.ts +7 -1
- package/src/services/compression/CompressionService.ts +133 -74
- package/src/services/compression/compression-utils.ts +110 -19
- package/src/services/config/types.ts +0 -6
- package/src/services/dispatch/AgentDispatchService.ts +79 -0
- package/src/services/intervention/InterventionService.ts +78 -5
- package/src/services/nip46/Nip46SigningService.ts +30 -1
- package/src/services/projects/ProjectContext.ts +8 -6
- package/src/services/rag/RAGCollectionRegistry.ts +199 -0
- package/src/services/rag/RAGDatabaseService.ts +2 -7
- package/src/services/rag/RAGOperations.ts +25 -45
- package/src/services/rag/RAGService.ts +0 -31
- package/src/services/rag/RagSubscriptionService.ts +71 -122
- package/src/services/rag/rag-utils.ts +13 -0
- package/src/services/ral/RALRegistry.ts +25 -184
- package/src/services/reports/ReportEmbeddingService.ts +63 -113
- package/src/services/search/UnifiedSearchService.ts +115 -4
- package/src/services/search/index.ts +1 -0
- package/src/services/search/projectFilter.ts +20 -4
- package/src/services/search/providers/ConversationSearchProvider.ts +1 -0
- package/src/services/search/providers/GenericCollectionSearchProvider.ts +81 -0
- package/src/services/search/providers/LessonSearchProvider.ts +1 -8
- package/src/services/search/providers/ReportSearchProvider.ts +1 -0
- package/src/services/search/types.ts +24 -3
- package/src/services/trust-pubkeys/SystemPubkeyListService.ts +148 -0
- package/src/services/trust-pubkeys/TrustPubkeyService.ts +70 -9
- package/src/telemetry/setup.ts +2 -13
- package/src/tools/implementations/ask.ts +3 -3
- package/src/tools/implementations/conversation_get.ts +28 -268
- package/src/tools/implementations/fs_grep.ts +6 -6
- package/src/tools/implementations/fs_read.ts +2 -0
- package/src/tools/implementations/fs_write.ts +2 -0
- package/src/tools/implementations/learn.ts +38 -50
- package/src/tools/implementations/rag_add_documents.ts +6 -4
- package/src/tools/implementations/rag_create_collection.ts +37 -4
- package/src/tools/implementations/rag_delete_collection.ts +9 -0
- package/src/tools/implementations/{search.ts → rag_search.ts} +31 -25
- package/src/tools/registry.ts +7 -8
- package/src/tools/types.ts +11 -2
- package/src/tools/utils/transcript-args.ts +13 -0
- package/src/utils/cli-theme.ts +13 -0
- package/src/utils/logger.ts +55 -0
- package/src/utils/metadataKeys.ts +17 -0
- package/src/utils/sqlEscaping.ts +39 -0
- package/src/wrapper.ts +7 -3
- package/dist/src/index.js +0 -46790
- package/dist/tenex-backend-wrapper.cjs +0 -3
- package/src/agents/execution/constants.ts +0 -16
- package/src/agents/execution/index.ts +0 -3
- package/src/agents/index.ts +0 -4
- package/src/commands/agent.ts +0 -235
- package/src/conversations/formatters/DelegationXmlFormatter.ts +0 -64
- package/src/conversations/formatters/index.ts +0 -9
- package/src/conversations/index.ts +0 -2
- package/src/conversations/utils/content-utils.ts +0 -69
- package/src/daemon/UnixSocketTransport.ts +0 -318
- package/src/event-handler/newConversation.ts +0 -165
- package/src/events/NDKProjectStatus.ts +0 -384
- package/src/events/index.ts +0 -4
- package/src/lib/json-parser.ts +0 -30
- package/src/llm/RecordingState.ts +0 -37
- package/src/llm/StreamPublisher.ts +0 -40
- package/src/llm/middleware/flight-recorder.ts +0 -188
- package/src/llm/utils/claudeCodePromptCompiler.ts +0 -141
- package/src/nostr/constants.ts +0 -38
- package/src/prompts/core/index.ts +0 -3
- package/src/prompts/index.ts +0 -21
- package/src/services/image/index.ts +0 -12
- package/src/services/status/index.ts +0 -11
- package/src/telemetry/diagnostics.ts +0 -27
- package/src/tools/implementations/rag_query.ts +0 -107
- package/src/types/index.ts +0 -46
- package/src/utils/agentFetcher.ts +0 -107
- package/src/utils/conversation-utils.ts +0 -1
- package/src/utils/process.ts +0 -49
|
@@ -0,0 +1,199 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* RAGCollectionRegistry - Tracks collection metadata for scope-aware search.
|
|
3
|
+
*
|
|
4
|
+
* Stores scope, project, and agent metadata for each RAG collection as a
|
|
5
|
+
* JSON sidecar file alongside the LanceDB data directory. This enables
|
|
6
|
+
* rag_search() to automatically include only relevant collections based on
|
|
7
|
+
* the agent's context (global, project, personal).
|
|
8
|
+
*
|
|
9
|
+
* IMPORTANT: This is NOT access control. No requests are ever denied.
|
|
10
|
+
* Scoping only determines default visibility when rag_search() runs
|
|
11
|
+
* without an explicit `collections` parameter.
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
import { logger } from "@/utils/logger";
|
|
15
|
+
import { getLanceDBDataDir } from "./rag-utils";
|
|
16
|
+
import * as fs from "node:fs";
|
|
17
|
+
import * as path from "node:path";
|
|
18
|
+
|
|
19
|
+
/** Scope types for RAG collections */
|
|
20
|
+
export type CollectionScope = "global" | "project" | "personal";
|
|
21
|
+
|
|
22
|
+
/** Metadata stored for each registered collection */
|
|
23
|
+
export interface CollectionMetadata {
|
|
24
|
+
/** Scope determines default visibility in rag_search() */
|
|
25
|
+
scope: CollectionScope;
|
|
26
|
+
|
|
27
|
+
/** Project ID (NIP-33 address) that created the collection */
|
|
28
|
+
projectId?: string;
|
|
29
|
+
|
|
30
|
+
/** Agent pubkey that created the collection */
|
|
31
|
+
agentPubkey?: string;
|
|
32
|
+
|
|
33
|
+
/** When the collection was first registered */
|
|
34
|
+
createdAt: number;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/** Shape of the sidecar JSON file */
|
|
38
|
+
interface RegistryData {
|
|
39
|
+
/** Schema version for future migrations */
|
|
40
|
+
version: 1;
|
|
41
|
+
|
|
42
|
+
/** Map of collection name → metadata */
|
|
43
|
+
collections: Record<string, CollectionMetadata>;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
const REGISTRY_FILENAME = "collection-registry.json";
|
|
47
|
+
|
|
48
|
+
export class RAGCollectionRegistry {
|
|
49
|
+
private static instance: RAGCollectionRegistry | null = null;
|
|
50
|
+
private data: RegistryData;
|
|
51
|
+
private readonly filePath: string;
|
|
52
|
+
|
|
53
|
+
private constructor() {
|
|
54
|
+
this.filePath = path.join(getLanceDBDataDir(), REGISTRY_FILENAME);
|
|
55
|
+
this.data = this.load();
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
public static getInstance(): RAGCollectionRegistry {
|
|
59
|
+
if (!RAGCollectionRegistry.instance) {
|
|
60
|
+
RAGCollectionRegistry.instance = new RAGCollectionRegistry();
|
|
61
|
+
}
|
|
62
|
+
return RAGCollectionRegistry.instance;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* Register a collection with scope metadata.
|
|
67
|
+
* Overwrites existing metadata for the same collection name.
|
|
68
|
+
*/
|
|
69
|
+
public register(
|
|
70
|
+
name: string,
|
|
71
|
+
metadata: Omit<CollectionMetadata, "createdAt">
|
|
72
|
+
): void {
|
|
73
|
+
const existing = this.data.collections[name];
|
|
74
|
+
this.data.collections[name] = {
|
|
75
|
+
...metadata,
|
|
76
|
+
createdAt: existing?.createdAt ?? Date.now(),
|
|
77
|
+
};
|
|
78
|
+
this.save();
|
|
79
|
+
logger.debug(`[RAGCollectionRegistry] Registered collection '${name}'`, {
|
|
80
|
+
scope: metadata.scope,
|
|
81
|
+
projectId: metadata.projectId,
|
|
82
|
+
});
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* Get metadata for a specific collection.
|
|
87
|
+
* Returns undefined for unregistered (legacy) collections.
|
|
88
|
+
*/
|
|
89
|
+
public get(name: string): CollectionMetadata | undefined {
|
|
90
|
+
return this.data.collections[name];
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* Remove a collection from the registry.
|
|
95
|
+
*/
|
|
96
|
+
public unregister(name: string): void {
|
|
97
|
+
if (this.data.collections[name]) {
|
|
98
|
+
delete this.data.collections[name];
|
|
99
|
+
this.save();
|
|
100
|
+
logger.debug(`[RAGCollectionRegistry] Unregistered collection '${name}'`);
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/**
|
|
105
|
+
* Get all collections that match the given context.
|
|
106
|
+
*
|
|
107
|
+
* Matching rules:
|
|
108
|
+
* - `global` collections: always included
|
|
109
|
+
* - `project` collections: included when projectId matches
|
|
110
|
+
* - `personal` collections: included when agentPubkey matches
|
|
111
|
+
* - Unregistered (legacy) collections: treated as global
|
|
112
|
+
*
|
|
113
|
+
* @param allCollections - All known collection names (from RAGService.listCollections)
|
|
114
|
+
* @param projectId - Current project ID
|
|
115
|
+
* @param agentPubkey - Current agent's pubkey
|
|
116
|
+
* @returns Collection names that match the context
|
|
117
|
+
*/
|
|
118
|
+
public getMatchingCollections(
|
|
119
|
+
allCollections: string[],
|
|
120
|
+
projectId: string,
|
|
121
|
+
agentPubkey?: string
|
|
122
|
+
): string[] {
|
|
123
|
+
return allCollections.filter((name) => {
|
|
124
|
+
const metadata = this.data.collections[name];
|
|
125
|
+
|
|
126
|
+
// Unregistered (legacy) collections are treated as global
|
|
127
|
+
if (!metadata) return true;
|
|
128
|
+
|
|
129
|
+
switch (metadata.scope) {
|
|
130
|
+
case "global":
|
|
131
|
+
return true;
|
|
132
|
+
case "project":
|
|
133
|
+
return metadata.projectId === projectId;
|
|
134
|
+
case "personal":
|
|
135
|
+
return metadata.agentPubkey === agentPubkey;
|
|
136
|
+
default:
|
|
137
|
+
// Unknown scope — treat as global for safety
|
|
138
|
+
return true;
|
|
139
|
+
}
|
|
140
|
+
});
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
/**
|
|
144
|
+
* Get all registered collection metadata.
|
|
145
|
+
* Returns a deep copy to prevent callers from mutating internal state.
|
|
146
|
+
*/
|
|
147
|
+
public getAll(): Record<string, CollectionMetadata> {
|
|
148
|
+
return JSON.parse(JSON.stringify(this.data.collections));
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
/**
|
|
152
|
+
* Load registry from the sidecar JSON file.
|
|
153
|
+
* Returns empty registry if file doesn't exist or is corrupted.
|
|
154
|
+
*/
|
|
155
|
+
private load(): RegistryData {
|
|
156
|
+
try {
|
|
157
|
+
if (fs.existsSync(this.filePath)) {
|
|
158
|
+
const raw = fs.readFileSync(this.filePath, "utf-8");
|
|
159
|
+
const parsed = JSON.parse(raw) as RegistryData;
|
|
160
|
+
if (parsed.version === 1 && parsed.collections) {
|
|
161
|
+
return parsed;
|
|
162
|
+
}
|
|
163
|
+
logger.warn("[RAGCollectionRegistry] Unknown registry version, starting fresh");
|
|
164
|
+
}
|
|
165
|
+
} catch (error) {
|
|
166
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
167
|
+
logger.warn("[RAGCollectionRegistry] Failed to load registry, starting fresh", {
|
|
168
|
+
error: message,
|
|
169
|
+
});
|
|
170
|
+
}
|
|
171
|
+
return { version: 1, collections: {} };
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
/**
|
|
175
|
+
* Persist registry to the sidecar JSON file.
|
|
176
|
+
*/
|
|
177
|
+
private save(): void {
|
|
178
|
+
try {
|
|
179
|
+
// Ensure directory exists
|
|
180
|
+
const dir = path.dirname(this.filePath);
|
|
181
|
+
if (!fs.existsSync(dir)) {
|
|
182
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
183
|
+
}
|
|
184
|
+
fs.writeFileSync(this.filePath, JSON.stringify(this.data, null, 2), "utf-8");
|
|
185
|
+
} catch (error) {
|
|
186
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
187
|
+
logger.error("[RAGCollectionRegistry] Failed to save registry", {
|
|
188
|
+
error: message,
|
|
189
|
+
});
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
/**
|
|
194
|
+
* Reset singleton (for testing).
|
|
195
|
+
*/
|
|
196
|
+
public static resetInstance(): void {
|
|
197
|
+
RAGCollectionRegistry.instance = null;
|
|
198
|
+
}
|
|
199
|
+
}
|
|
@@ -1,7 +1,6 @@
|
|
|
1
|
-
import { config } from "@/services/ConfigService";
|
|
2
|
-
import * as path from "node:path";
|
|
3
1
|
import { handleError } from "@/utils/error-handler";
|
|
4
2
|
import { logger } from "@/utils/logger";
|
|
3
|
+
import { getLanceDBDataDir } from "./rag-utils";
|
|
5
4
|
import { type Connection, type Table, connect } from "@lancedb/lancedb";
|
|
6
5
|
|
|
7
6
|
/**
|
|
@@ -27,11 +26,7 @@ export class RAGDatabaseService {
|
|
|
27
26
|
private tableCache: Map<string, Table> = new Map();
|
|
28
27
|
|
|
29
28
|
constructor(dataDir?: string) {
|
|
30
|
-
|
|
31
|
-
this.dataDir =
|
|
32
|
-
dataDir ||
|
|
33
|
-
process.env.LANCEDB_DATA_DIR ||
|
|
34
|
-
path.join(config.getConfigPath("data"), "lancedb");
|
|
29
|
+
this.dataDir = dataDir || getLanceDBDataDir();
|
|
35
30
|
|
|
36
31
|
logger.debug(`RAGDatabaseService initialized with data directory: ${this.dataDir}`);
|
|
37
32
|
}
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import type { DocumentMetadata, LanceDBResult, LanceDBStoredDocument } from "@/services/rag/rag-utils";
|
|
2
2
|
import { calculateRelevanceScore, mapLanceResultToDocument } from "@/services/rag/rag-utils";
|
|
3
|
+
import { AGENT_PUBKEY_KEYS } from "@/utils/metadataKeys";
|
|
4
|
+
import { SQL_LIKE_ESCAPE_CLAUSE, escapeSqlLikeValue } from "@/utils/sqlEscaping";
|
|
3
5
|
import { handleError } from "@/utils/error-handler";
|
|
4
6
|
import { logger } from "@/utils/logger";
|
|
5
7
|
import type { Table, VectorQuery } from "@lancedb/lancedb";
|
|
@@ -469,33 +471,13 @@ export class RAGOperations {
|
|
|
469
471
|
* @param agentPubkey Optional agent pubkey to count their contributions
|
|
470
472
|
* @returns Object with total count and optional agent-specific count
|
|
471
473
|
*/
|
|
472
|
-
/**
|
|
473
|
-
* Escape a string for use in SQL LIKE pattern.
|
|
474
|
-
* Escapes: single quotes ('), double quotes ("), backslashes (\), and LIKE wildcards (%, _).
|
|
475
|
-
*
|
|
476
|
-
* IMPORTANT: DataFusion (used by LanceDB) has NO default escape character.
|
|
477
|
-
* The backslash escapes here only work when paired with ESCAPE '\\' clause.
|
|
478
|
-
* See: https://github.com/apache/datafusion/issues/13291
|
|
479
|
-
*
|
|
480
|
-
* Note: Agent pubkeys are hex strings (0-9, a-f) so most escaping isn't strictly needed,
|
|
481
|
-
* but we escape properly for defense-in-depth and to handle any future metadata fields.
|
|
482
|
-
*/
|
|
483
|
-
private escapeSqlLikeValue(value: string): string {
|
|
484
|
-
return value
|
|
485
|
-
.replace(/\\/g, "\\\\") // Escape backslashes first
|
|
486
|
-
.replace(/'/g, "''") // SQL standard: escape single quote by doubling
|
|
487
|
-
.replace(/"/g, '\\"') // Escape double quotes
|
|
488
|
-
.replace(/%/g, "\\%") // Escape LIKE wildcard %
|
|
489
|
-
.replace(/_/g, "\\_"); // Escape LIKE wildcard _
|
|
490
|
-
}
|
|
491
|
-
|
|
492
474
|
async getCollectionStats(
|
|
493
475
|
collectionName: string,
|
|
494
476
|
agentPubkey?: string
|
|
495
477
|
): Promise<{ totalCount: number; agentCount?: number }> {
|
|
496
|
-
const table = await this.dbManager.getTable(collectionName);
|
|
497
|
-
|
|
498
478
|
try {
|
|
479
|
+
const table = await this.dbManager.getTable(collectionName);
|
|
480
|
+
|
|
499
481
|
// Get total document count
|
|
500
482
|
const totalCount = await table.countRows();
|
|
501
483
|
|
|
@@ -503,11 +485,11 @@ export class RAGOperations {
|
|
|
503
485
|
// The metadata field is stored as JSON string, so we use LIKE for matching
|
|
504
486
|
let agentCount: number | undefined;
|
|
505
487
|
if (agentPubkey) {
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
const filter = `
|
|
488
|
+
const escaped = escapeSqlLikeValue(agentPubkey);
|
|
489
|
+
const clauses = AGENT_PUBKEY_KEYS
|
|
490
|
+
.map((key) => `metadata LIKE '%"${key}":"${escaped}"%' ${SQL_LIKE_ESCAPE_CLAUSE}`)
|
|
491
|
+
.join(" OR ");
|
|
492
|
+
const filter = `(${clauses})`;
|
|
511
493
|
agentCount = await table.countRows(filter);
|
|
512
494
|
}
|
|
513
495
|
|
|
@@ -532,28 +514,26 @@ export class RAGOperations {
|
|
|
532
514
|
): Promise<Array<{ name: string; agentDocCount: number; totalDocCount: number }>> {
|
|
533
515
|
const collections = await this.listCollections();
|
|
534
516
|
|
|
535
|
-
const
|
|
517
|
+
const results = await Promise.allSettled(
|
|
536
518
|
collections.map(async (name) => {
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
};
|
|
544
|
-
} catch (error) {
|
|
545
|
-
// Log but don't fail - return zero counts for problematic collections
|
|
546
|
-
logger.warn(`Failed to get stats for collection '${name}':`, error);
|
|
547
|
-
return {
|
|
548
|
-
name,
|
|
549
|
-
agentDocCount: 0,
|
|
550
|
-
totalDocCount: 0,
|
|
551
|
-
};
|
|
552
|
-
}
|
|
519
|
+
const { totalCount, agentCount } = await this.getCollectionStats(name, agentPubkey);
|
|
520
|
+
return {
|
|
521
|
+
name,
|
|
522
|
+
agentDocCount: agentCount ?? 0,
|
|
523
|
+
totalDocCount: totalCount,
|
|
524
|
+
};
|
|
553
525
|
})
|
|
554
526
|
);
|
|
555
527
|
|
|
556
|
-
return
|
|
528
|
+
return results
|
|
529
|
+
.filter((r): r is PromiseFulfilledResult<{ name: string; agentDocCount: number; totalDocCount: number }> => {
|
|
530
|
+
if (r.status === "rejected") {
|
|
531
|
+
logger.warn(`Failed to get stats for collection: ${r.reason}`);
|
|
532
|
+
return false;
|
|
533
|
+
}
|
|
534
|
+
return true;
|
|
535
|
+
})
|
|
536
|
+
.map(r => r.value);
|
|
557
537
|
}
|
|
558
538
|
|
|
559
539
|
/**
|
|
@@ -5,36 +5,6 @@ import { RAGDatabaseService } from "./RAGDatabaseService";
|
|
|
5
5
|
import { RAGOperations } from "./RAGOperations";
|
|
6
6
|
import type { BulkUpsertResult, LanceDBSchema, RAGCollection, RAGDocument, RAGQueryResult } from "./RAGOperations";
|
|
7
7
|
|
|
8
|
-
/**
|
|
9
|
-
* Lightweight check for RAG collections without full service initialization.
|
|
10
|
-
* Returns true if any RAG collections exist in the database.
|
|
11
|
-
*
|
|
12
|
-
* This is designed for use in system prompt building where we want to avoid
|
|
13
|
-
* initializing the embedding provider (which can be expensive) if RAG isn't used.
|
|
14
|
-
*/
|
|
15
|
-
export async function hasRagCollections(): Promise<boolean> {
|
|
16
|
-
let tempDbService: RAGDatabaseService | null = null;
|
|
17
|
-
try {
|
|
18
|
-
// Use a temporary DB service just for listing - avoids embedding provider init
|
|
19
|
-
tempDbService = new RAGDatabaseService();
|
|
20
|
-
const tables = await tempDbService.listTables();
|
|
21
|
-
return tables.length > 0;
|
|
22
|
-
} catch (error) {
|
|
23
|
-
// Database doesn't exist or can't connect - no collections
|
|
24
|
-
logger.debug("RAG database not available for collection check:", error);
|
|
25
|
-
return false;
|
|
26
|
-
} finally {
|
|
27
|
-
// Ensure cleanup even on listTables() errors; log but don't override result
|
|
28
|
-
if (tempDbService) {
|
|
29
|
-
try {
|
|
30
|
-
await tempDbService.close();
|
|
31
|
-
} catch (closeError) {
|
|
32
|
-
logger.debug("Failed to close temporary RAG database service:", closeError);
|
|
33
|
-
}
|
|
34
|
-
}
|
|
35
|
-
}
|
|
36
|
-
}
|
|
37
|
-
|
|
38
8
|
/**
|
|
39
9
|
* Facade for RAG functionality
|
|
40
10
|
* Coordinates between database management and operations
|
|
@@ -237,4 +207,3 @@ export class RAGService {
|
|
|
237
207
|
export type { BulkUpsertResult, RAGDocument, RAGCollection, RAGQueryResult } from "./RAGOperations";
|
|
238
208
|
export { RAGValidationError, RAGOperationError } from "./RAGOperations";
|
|
239
209
|
export { RAGDatabaseError } from "./RAGDatabaseService";
|
|
240
|
-
// hasRagCollections is exported at module level (above class)
|
|
@@ -197,72 +197,42 @@ export class RagSubscriptionService {
|
|
|
197
197
|
});
|
|
198
198
|
};
|
|
199
199
|
|
|
200
|
+
// Validate that resourceUri is a proper URI format
|
|
200
201
|
try {
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
);
|
|
208
|
-
}
|
|
202
|
+
new URL(subscription.resourceUri);
|
|
203
|
+
} catch {
|
|
204
|
+
throw new Error(
|
|
205
|
+
`Invalid resourceUri: "${subscription.resourceUri}". Resource URI must be a valid URI format (e.g., "nostr://feed/pubkey/kinds", "file:///path/to/file"). This appears to be a tool name or invalid format. If you're using a resource template, you must first expand it with parameters to get the actual URI.`
|
|
206
|
+
);
|
|
207
|
+
}
|
|
209
208
|
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
209
|
+
// Get mcpManager from project context
|
|
210
|
+
const projectCtx = getProjectContext();
|
|
211
|
+
const mcpManager = projectCtx.mcpManager;
|
|
212
|
+
if (!mcpManager) {
|
|
213
|
+
throw new Error("MCPManager not available in project context");
|
|
214
|
+
}
|
|
216
215
|
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
try {
|
|
220
|
-
// CRITICAL: Register handler FIRST, then subscribe
|
|
221
|
-
const removeHandler = mcpManager.addResourceNotificationHandler(subscription.mcpServerId, listener);
|
|
216
|
+
// CRITICAL: Register handler FIRST, then subscribe
|
|
217
|
+
const removeHandler = mcpManager.addResourceNotificationHandler(subscription.mcpServerId, listener);
|
|
222
218
|
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
// Store listener reference and removal function for cleanup
|
|
230
|
-
this.resourceListeners.set(
|
|
231
|
-
listenerKey,
|
|
232
|
-
listener as unknown as (notification: Notification) => void
|
|
233
|
-
);
|
|
234
|
-
this.handlerRemovers.set(listenerKey, removeHandler);
|
|
219
|
+
// Subscribe to resource updates — if server doesn't support subscriptions, let it throw
|
|
220
|
+
await mcpManager.subscribeToResource(
|
|
221
|
+
subscription.mcpServerId,
|
|
222
|
+
subscription.resourceUri
|
|
223
|
+
);
|
|
235
224
|
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
error instanceof Error &&
|
|
243
|
-
error.message.includes("does not support resource subscriptions")
|
|
244
|
-
) {
|
|
245
|
-
// Server doesn't support subscriptions, gracefully degrade to polling
|
|
246
|
-
subscriptionSupported = false;
|
|
247
|
-
logger.warn(
|
|
248
|
-
`Server '${subscription.mcpServerId}' does not support resource subscriptions. ` +
|
|
249
|
-
`Subscription '${subscription.subscriptionId}' will use polling mode. ` +
|
|
250
|
-
`Call pollResource() manually or set up a polling interval.`
|
|
251
|
-
);
|
|
252
|
-
// Don't exit - gracefully degrade to polling mode
|
|
253
|
-
} else {
|
|
254
|
-
throw error;
|
|
255
|
-
}
|
|
256
|
-
}
|
|
225
|
+
// Store listener reference and removal function for cleanup
|
|
226
|
+
this.resourceListeners.set(
|
|
227
|
+
listenerKey,
|
|
228
|
+
listener as unknown as (notification: Notification) => void
|
|
229
|
+
);
|
|
230
|
+
this.handlerRemovers.set(listenerKey, removeHandler);
|
|
257
231
|
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
subscription.status = SubscriptionStatus.ERROR;
|
|
263
|
-
subscription.lastError = error instanceof Error ? error.message : "Unknown error";
|
|
264
|
-
throw error;
|
|
265
|
-
}
|
|
232
|
+
logger.info(
|
|
233
|
+
`RAG subscription '${subscription.subscriptionId}' active with push notifications. ` +
|
|
234
|
+
`Listening for updates from ${subscription.mcpServerId}:${subscription.resourceUri}`
|
|
235
|
+
);
|
|
266
236
|
}
|
|
267
237
|
|
|
268
238
|
/**
|
|
@@ -272,75 +242,54 @@ export class RagSubscriptionService {
|
|
|
272
242
|
subscription: RagSubscription,
|
|
273
243
|
notification: Notification
|
|
274
244
|
): Promise<void> {
|
|
275
|
-
|
|
276
|
-
// Extract content from notification
|
|
277
|
-
const content = this.extractContentFromNotification(notification);
|
|
245
|
+
const content = this.extractContentFromNotification(notification);
|
|
278
246
|
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
}
|
|
247
|
+
if (!content) {
|
|
248
|
+
throw new Error(
|
|
249
|
+
`Received empty update for subscription '${subscription.subscriptionId}'`
|
|
250
|
+
);
|
|
251
|
+
}
|
|
285
252
|
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
const projectId = projectCtx.project.tagId();
|
|
302
|
-
if (projectId) {
|
|
303
|
-
metadata.project_id = projectId;
|
|
304
|
-
}
|
|
305
|
-
} catch (error) {
|
|
306
|
-
logger.debug("Project context error during RAG subscription update", { error });
|
|
307
|
-
}
|
|
253
|
+
// Build metadata with provenance fields for filtering at query time
|
|
254
|
+
const metadata: DocumentMetadata = {
|
|
255
|
+
agent_pubkey: subscription.agentPubkey,
|
|
256
|
+
subscriptionId: subscription.subscriptionId,
|
|
257
|
+
mcpServerId: subscription.mcpServerId,
|
|
258
|
+
resourceUri: subscription.resourceUri,
|
|
259
|
+
timestamp: Date.now(),
|
|
260
|
+
};
|
|
261
|
+
|
|
262
|
+
// Add project_id if available from project context (uses NIP-33 address format)
|
|
263
|
+
if (isProjectContextInitialized()) {
|
|
264
|
+
const projectCtx = getProjectContext();
|
|
265
|
+
const projectId = projectCtx.project.tagId();
|
|
266
|
+
if (projectId) {
|
|
267
|
+
metadata.project_id = projectId;
|
|
308
268
|
}
|
|
269
|
+
}
|
|
309
270
|
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
// Update subscription metrics
|
|
321
|
-
subscription.documentsProcessed++;
|
|
322
|
-
subscription.lastDocumentIngested = content.substring(0, 200); // Store snippet
|
|
323
|
-
subscription.updatedAt = Date.now();
|
|
324
|
-
subscription.status = SubscriptionStatus.RUNNING;
|
|
325
|
-
subscription.lastError = undefined;
|
|
271
|
+
// Add document to RAG collection
|
|
272
|
+
await this.ragService.addDocuments(subscription.ragCollection, [
|
|
273
|
+
{
|
|
274
|
+
content,
|
|
275
|
+
metadata,
|
|
276
|
+
source: `${subscription.mcpServerId}:${subscription.resourceUri}`,
|
|
277
|
+
timestamp: Date.now(),
|
|
278
|
+
},
|
|
279
|
+
]);
|
|
326
280
|
|
|
327
|
-
|
|
281
|
+
// Update subscription metrics
|
|
282
|
+
subscription.documentsProcessed++;
|
|
283
|
+
subscription.lastDocumentIngested = content.substring(0, 200);
|
|
284
|
+
subscription.updatedAt = Date.now();
|
|
285
|
+
subscription.status = SubscriptionStatus.RUNNING;
|
|
286
|
+
subscription.lastError = undefined;
|
|
328
287
|
|
|
329
|
-
|
|
330
|
-
`Processed update for subscription '${subscription.subscriptionId}', total documents: ${subscription.documentsProcessed}`
|
|
331
|
-
);
|
|
332
|
-
} catch (error) {
|
|
333
|
-
subscription.status = SubscriptionStatus.ERROR;
|
|
334
|
-
subscription.lastError = error instanceof Error ? error.message : "Unknown error";
|
|
335
|
-
subscription.updatedAt = Date.now();
|
|
336
|
-
await this.saveSubscriptions();
|
|
288
|
+
await this.saveSubscriptions();
|
|
337
289
|
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
{ logLevel: "error" }
|
|
342
|
-
);
|
|
343
|
-
}
|
|
290
|
+
logger.debug(
|
|
291
|
+
`Processed update for subscription '${subscription.subscriptionId}', total documents: ${subscription.documentsProcessed}`
|
|
292
|
+
);
|
|
344
293
|
}
|
|
345
294
|
|
|
346
295
|
/**
|
|
@@ -1,4 +1,17 @@
|
|
|
1
|
+
import { config } from "@/services/ConfigService";
|
|
1
2
|
import { logger } from "@/utils/logger";
|
|
3
|
+
import * as path from "node:path";
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Resolve the LanceDB data directory path.
|
|
7
|
+
* Centralizes env/config fallback logic used by multiple RAG services.
|
|
8
|
+
*/
|
|
9
|
+
export function getLanceDBDataDir(): string {
|
|
10
|
+
return (
|
|
11
|
+
process.env.LANCEDB_DATA_DIR ||
|
|
12
|
+
path.join(config.getConfigPath("data"), "lancedb")
|
|
13
|
+
);
|
|
14
|
+
}
|
|
2
15
|
|
|
3
16
|
/**
|
|
4
17
|
* JSON-serializable primitive value.
|