@tenex-chat/backend 0.9.5 → 0.9.7

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.
Files changed (148) hide show
  1. package/README.md +5 -1
  2. package/dist/daemon-wrapper.cjs +34 -0
  3. package/dist/index.js +59268 -0
  4. package/dist/wrapper.js +171 -0
  5. package/package.json +19 -27
  6. package/src/agents/AgentRegistry.ts +9 -7
  7. package/src/agents/AgentStorage.ts +24 -1
  8. package/src/agents/agent-installer.ts +6 -0
  9. package/src/agents/agent-loader.ts +7 -2
  10. package/src/agents/constants.ts +10 -2
  11. package/src/agents/execution/AgentExecutor.ts +35 -6
  12. package/src/agents/execution/StreamCallbacks.ts +53 -13
  13. package/src/agents/execution/StreamExecutionHandler.ts +110 -16
  14. package/src/agents/execution/StreamSetup.ts +19 -9
  15. package/src/agents/execution/ToolEventHandlers.ts +112 -0
  16. package/src/agents/role-categories.ts +53 -0
  17. package/src/agents/types/runtime.ts +7 -0
  18. package/src/agents/types/storage.ts +7 -0
  19. package/src/commands/agent/import/openclaw-distiller.ts +63 -7
  20. package/src/commands/agent/import/openclaw-reader.ts +54 -0
  21. package/src/commands/agent/import/openclaw.ts +120 -29
  22. package/src/commands/agent/index.ts +83 -2
  23. package/src/commands/setup/display.ts +123 -0
  24. package/src/commands/setup/embed.ts +13 -13
  25. package/src/commands/setup/global-system-prompt.ts +15 -17
  26. package/src/commands/setup/image.ts +17 -20
  27. package/src/commands/setup/interactive.ts +37 -20
  28. package/src/commands/setup/llm.ts +12 -7
  29. package/src/commands/setup/onboarding.ts +1580 -248
  30. package/src/commands/setup/providers.ts +3 -3
  31. package/src/conversations/ConversationStore.ts +23 -2
  32. package/src/conversations/MessageBuilder.ts +51 -73
  33. package/src/conversations/formatters/utils/conversation-transcript-formatter.ts +425 -0
  34. package/src/conversations/search/embeddings/ConversationEmbeddingService.ts +40 -98
  35. package/src/conversations/search/embeddings/ConversationIndexingJob.ts +40 -52
  36. package/src/conversations/services/ConversationSummarizer.ts +1 -2
  37. package/src/conversations/types.ts +11 -0
  38. package/src/daemon/Daemon.ts +78 -57
  39. package/src/daemon/ProjectRuntime.ts +6 -12
  40. package/src/daemon/SubscriptionManager.ts +13 -0
  41. package/src/daemon/index.ts +0 -1
  42. package/src/event-handler/index.ts +1 -0
  43. package/src/index.ts +20 -1
  44. package/src/llm/ChunkHandler.ts +1 -1
  45. package/src/llm/FinishHandler.ts +28 -4
  46. package/src/llm/LLMConfigEditor.ts +218 -106
  47. package/src/llm/index.ts +0 -4
  48. package/src/llm/meta/MetaModelResolver.ts +3 -18
  49. package/src/llm/middleware/message-sanitizer.ts +153 -0
  50. package/src/llm/providers/ollama-models.ts +0 -38
  51. package/src/llm/service.ts +50 -15
  52. package/src/llm/types.ts +0 -12
  53. package/src/llm/utils/ConfigurationManager.ts +88 -465
  54. package/src/llm/utils/ConfigurationTester.ts +42 -185
  55. package/src/llm/utils/ModelSelector.ts +156 -92
  56. package/src/llm/utils/ProviderConfigUI.ts +10 -141
  57. package/src/llm/utils/models-dev-cache.ts +102 -23
  58. package/src/llm/utils/provider-select-prompt.ts +284 -0
  59. package/src/llm/utils/provider-setup.ts +81 -34
  60. package/src/llm/utils/variant-list-prompt.ts +361 -0
  61. package/src/nostr/AgentEventDecoder.ts +1 -0
  62. package/src/nostr/AgentEventEncoder.ts +37 -0
  63. package/src/nostr/AgentProfilePublisher.ts +13 -0
  64. package/src/nostr/AgentPublisher.ts +26 -0
  65. package/src/nostr/kinds.ts +1 -0
  66. package/src/nostr/ndkClient.ts +4 -1
  67. package/src/nostr/types.ts +12 -0
  68. package/src/prompts/fragments/25-rag-instructions.ts +22 -21
  69. package/src/prompts/fragments/31-agents-md-guidance.ts +7 -21
  70. package/src/prompts/fragments/index.ts +2 -0
  71. package/src/prompts/utils/systemPromptBuilder.ts +18 -28
  72. package/src/services/AgentDefinitionMonitor.ts +8 -0
  73. package/src/services/ConfigService.ts +34 -0
  74. package/src/services/PubkeyService.ts +7 -1
  75. package/src/services/compression/CompressionService.ts +133 -74
  76. package/src/services/compression/compression-utils.ts +110 -19
  77. package/src/services/config/types.ts +0 -6
  78. package/src/services/dispatch/AgentDispatchService.ts +79 -0
  79. package/src/services/intervention/InterventionService.ts +78 -5
  80. package/src/services/nip46/Nip46SigningService.ts +30 -1
  81. package/src/services/projects/ProjectContext.ts +8 -6
  82. package/src/services/rag/RAGCollectionRegistry.ts +199 -0
  83. package/src/services/rag/RAGDatabaseService.ts +2 -7
  84. package/src/services/rag/RAGOperations.ts +25 -45
  85. package/src/services/rag/RAGService.ts +0 -31
  86. package/src/services/rag/RagSubscriptionService.ts +71 -122
  87. package/src/services/rag/rag-utils.ts +13 -0
  88. package/src/services/ral/RALRegistry.ts +25 -184
  89. package/src/services/reports/ReportEmbeddingService.ts +63 -113
  90. package/src/services/search/UnifiedSearchService.ts +115 -4
  91. package/src/services/search/index.ts +1 -0
  92. package/src/services/search/projectFilter.ts +20 -4
  93. package/src/services/search/providers/ConversationSearchProvider.ts +1 -0
  94. package/src/services/search/providers/GenericCollectionSearchProvider.ts +81 -0
  95. package/src/services/search/providers/LessonSearchProvider.ts +1 -8
  96. package/src/services/search/providers/ReportSearchProvider.ts +1 -0
  97. package/src/services/search/types.ts +24 -3
  98. package/src/services/trust-pubkeys/SystemPubkeyListService.ts +148 -0
  99. package/src/services/trust-pubkeys/TrustPubkeyService.ts +70 -9
  100. package/src/telemetry/setup.ts +2 -13
  101. package/src/tools/implementations/ask.ts +3 -3
  102. package/src/tools/implementations/conversation_get.ts +28 -268
  103. package/src/tools/implementations/fs_grep.ts +6 -6
  104. package/src/tools/implementations/fs_read.ts +2 -0
  105. package/src/tools/implementations/fs_write.ts +2 -0
  106. package/src/tools/implementations/learn.ts +38 -50
  107. package/src/tools/implementations/rag_add_documents.ts +6 -4
  108. package/src/tools/implementations/rag_create_collection.ts +37 -4
  109. package/src/tools/implementations/rag_delete_collection.ts +9 -0
  110. package/src/tools/implementations/{search.ts → rag_search.ts} +31 -25
  111. package/src/tools/registry.ts +7 -8
  112. package/src/tools/types.ts +11 -2
  113. package/src/tools/utils/transcript-args.ts +13 -0
  114. package/src/utils/cli-theme.ts +13 -0
  115. package/src/utils/logger.ts +55 -0
  116. package/src/utils/metadataKeys.ts +17 -0
  117. package/src/utils/sqlEscaping.ts +39 -0
  118. package/src/wrapper.ts +7 -3
  119. package/dist/src/index.js +0 -46790
  120. package/dist/tenex-backend-wrapper.cjs +0 -3
  121. package/src/agents/execution/constants.ts +0 -16
  122. package/src/agents/execution/index.ts +0 -3
  123. package/src/agents/index.ts +0 -4
  124. package/src/commands/agent.ts +0 -235
  125. package/src/conversations/formatters/DelegationXmlFormatter.ts +0 -64
  126. package/src/conversations/formatters/index.ts +0 -9
  127. package/src/conversations/index.ts +0 -2
  128. package/src/conversations/utils/content-utils.ts +0 -69
  129. package/src/daemon/UnixSocketTransport.ts +0 -318
  130. package/src/event-handler/newConversation.ts +0 -165
  131. package/src/events/NDKProjectStatus.ts +0 -384
  132. package/src/events/index.ts +0 -4
  133. package/src/lib/json-parser.ts +0 -30
  134. package/src/llm/RecordingState.ts +0 -37
  135. package/src/llm/StreamPublisher.ts +0 -40
  136. package/src/llm/middleware/flight-recorder.ts +0 -188
  137. package/src/llm/utils/claudeCodePromptCompiler.ts +0 -141
  138. package/src/nostr/constants.ts +0 -38
  139. package/src/prompts/core/index.ts +0 -3
  140. package/src/prompts/index.ts +0 -21
  141. package/src/services/image/index.ts +0 -12
  142. package/src/services/status/index.ts +0 -11
  143. package/src/telemetry/diagnostics.ts +0 -27
  144. package/src/tools/implementations/rag_query.ts +0 -107
  145. package/src/types/index.ts +0 -46
  146. package/src/utils/agentFetcher.ts +0 -107
  147. package/src/utils/conversation-utils.ts +0 -1
  148. package/src/utils/process.ts +0 -49
@@ -1,9 +1,10 @@
1
1
  /**
2
- * Unified Search Tool
2
+ * RAG Search Tool
3
3
  *
4
- * Single search tool that queries across ALL project-scoped RAG collections
5
- * (conversations, reports, lessons) and returns unified results with metadata
6
- * that allows agents to dig deeper using other tools.
4
+ * Single search tool that queries across ALL accessible RAG collections.
5
+ * Specialized providers handle well-known collections (reports, conversations,
6
+ * lessons) with smart filtering, while dynamically discovered collections are
7
+ * queried via generic providers with basic project-scoped filtering.
7
8
  *
8
9
  * Supports optional prompt-based LLM extraction for focused information retrieval.
9
10
  */
@@ -15,11 +16,11 @@ import { logger } from "@/utils/logger";
15
16
  import { tool } from "ai";
16
17
  import { z } from "zod";
17
18
 
18
- const searchSchema = z.object({
19
+ const ragSearchSchema = z.object({
19
20
  query: z.string().describe(
20
- "Natural language search query. Searches across all project knowledge: " +
21
- "reports (documentation, architecture), conversations (past discussions), " +
22
- "and lessons (agent insights). Use descriptive natural language for best results."
21
+ "Natural language search query. Searches across all project knowledge " +
22
+ "including reports, conversations, lessons, and any additional RAG collections. " +
23
+ "Use descriptive natural language for best results."
23
24
  ),
24
25
  prompt: z
25
26
  .string()
@@ -36,18 +37,22 @@ const searchSchema = z.object({
36
37
  .default(10)
37
38
  .describe("Maximum number of results to return across all collections. Defaults to 10."),
38
39
  collections: z
39
- .array(z.enum(["reports", "conversations", "lessons"]))
40
+ .array(z.string())
40
41
  .optional()
41
42
  .describe(
42
- "Which collections to search. Defaults to all. " +
43
- "Use to narrow search scope when you know where the information lives."
43
+ "Filter by provider name. When omitted, searches all collections matching the " +
44
+ "agent's scope (global + project + personal). When provided, searches exactly " +
45
+ "those collections (no scope filtering — the agent explicitly chose them). " +
46
+ "Well-known provider names: 'reports', 'conversations', 'lessons'. " +
47
+ "Dynamically discovered RAG collections use their collection name as the " +
48
+ "provider name (e.g., 'custom_knowledge')."
44
49
  ),
45
50
  });
46
51
 
47
- type SearchInput = z.infer<typeof searchSchema>;
52
+ type RAGSearchInput = z.infer<typeof ragSearchSchema>;
48
53
 
49
- async function executeSearch(
50
- input: SearchInput,
54
+ async function executeRAGSearch(
55
+ input: RAGSearchInput,
51
56
  context: ToolExecutionContext
52
57
  ): Promise<Record<string, unknown>> {
53
58
  const { query, prompt, limit = 10, collections } = input;
@@ -70,7 +75,7 @@ async function executeSearch(
70
75
  };
71
76
  }
72
77
 
73
- logger.info("🔍 [SearchTool] Executing unified search", {
78
+ logger.info("🔍 [RAGSearch] Executing unified search", {
74
79
  query,
75
80
  prompt: prompt ? `${prompt.substring(0, 50)}...` : undefined,
76
81
  limit,
@@ -90,6 +95,7 @@ async function executeSearch(
90
95
  limit,
91
96
  prompt,
92
97
  collections,
98
+ agentPubkey: context.agent.pubkey,
93
99
  });
94
100
 
95
101
  // Format results for agent consumption
@@ -118,28 +124,28 @@ async function executeSearch(
118
124
  };
119
125
  }
120
126
 
121
- export function createSearchTool(context: ToolExecutionContext): AISdkTool {
127
+ export function createRAGSearchTool(context: ToolExecutionContext): AISdkTool {
122
128
  const aiTool = tool({
123
129
  description:
124
- "Search across ALL project knowledge — reports, conversations, and lessons using " +
125
- "natural language semantic search. Returns ranked results with metadata and retrieval " +
126
- "instructions. Each result includes a `retrievalTool` and `retrievalArg` that you can " +
127
- "use to fetch the full document (e.g., call report_read with the slug, lesson_get with " +
128
- "the event ID, or conversation_get with the conversation ID).\n\n" +
130
+ "Search across ALL project knowledge — reports, conversations, lessons, and any " +
131
+ "additional RAG collections — using natural language semantic search. Returns ranked " +
132
+ "results with metadata and retrieval instructions. Each result includes a `retrievalTool` " +
133
+ "and `retrievalArg` that you can use to fetch the full document (e.g., call report_read " +
134
+ "with the slug, lesson_get with the event ID, or conversation_get with the conversation ID).\n\n" +
129
135
  "Optionally provide a `prompt` parameter to have an LLM extract focused information " +
130
136
  "from the search results, rather than reviewing them manually.\n\n" +
131
137
  "This is the primary discovery tool for finding information across the project. Use " +
132
138
  "conversation_search for deep exploration of specific conversation content.",
133
139
 
134
- inputSchema: searchSchema,
140
+ inputSchema: ragSearchSchema,
135
141
 
136
- execute: async (input: SearchInput) => {
137
- return await executeSearch(input, context);
142
+ execute: async (input: RAGSearchInput) => {
143
+ return await executeRAGSearch(input, context);
138
144
  },
139
145
  });
140
146
 
141
147
  Object.defineProperty(aiTool, "getHumanReadableContent", {
142
- value: ({ query, prompt, limit, collections }: SearchInput) => {
148
+ value: ({ query, prompt, limit, collections }: RAGSearchInput) => {
143
149
  const parts = [`Searching project knowledge for "${query}"`];
144
150
  if (prompt) parts.push(`prompt: "${prompt.substring(0, 40)}..."`);
145
151
  if (collections?.length) parts.push(`in: ${collections.join(", ")}`);
@@ -52,7 +52,7 @@ import { createRAGAddDocumentsTool } from "./implementations/rag_add_documents";
52
52
  import { createRAGCreateCollectionTool } from "./implementations/rag_create_collection";
53
53
  import { createRAGDeleteCollectionTool } from "./implementations/rag_delete_collection";
54
54
  import { createRAGListCollectionsTool } from "./implementations/rag_list_collections";
55
- import { createRAGQueryTool } from "./implementations/rag_query";
55
+
56
56
  import { createRAGSubscriptionCreateTool } from "./implementations/rag_subscription_create";
57
57
  import { createRAGSubscriptionDeleteTool } from "./implementations/rag_subscription_delete";
58
58
  import { createRAGSubscriptionGetTool } from "./implementations/rag_subscription_get";
@@ -83,8 +83,8 @@ import { createWebSearchTool } from "./implementations/web_search";
83
83
  import { createNostrFetchTool } from "./implementations/nostr_fetch";
84
84
  import { createNostrPublishAsUserTool } from "./implementations/nostr_publish_as_user";
85
85
 
86
- // Unified search tool
87
- import { createSearchTool } from "./implementations/search";
86
+ // Unified RAG search tool
87
+ import { createRAGSearchTool } from "./implementations/rag_search";
88
88
 
89
89
  // Image generation tools
90
90
  import { createGenerateImageTool } from "./implementations/generate_image";
@@ -124,7 +124,7 @@ const toolMetadata: Partial<Record<ToolName, { hasSideEffects: boolean }>> = {
124
124
  report_read: { hasSideEffects: false },
125
125
  schedule_tasks_list: { hasSideEffects: false },
126
126
  rag_list_collections: { hasSideEffects: false },
127
- rag_query: { hasSideEffects: false },
127
+
128
128
  rag_subscription_list: { hasSideEffects: false },
129
129
  rag_subscription_get: { hasSideEffects: false },
130
130
  mcp_resource_read: { hasSideEffects: false },
@@ -132,7 +132,7 @@ const toolMetadata: Partial<Record<ToolName, { hasSideEffects: boolean }>> = {
132
132
  web_fetch: { hasSideEffects: false },
133
133
  web_search: { hasSideEffects: false },
134
134
  nostr_fetch: { hasSideEffects: false },
135
- search: { hasSideEffects: false },
135
+ rag_search: { hasSideEffects: false },
136
136
  };
137
137
 
138
138
  /**
@@ -204,8 +204,8 @@ const toolFactories: Record<ToolName, ToolFactory> = {
204
204
  // Conversation search
205
205
  conversation_search: createConversationSearchTool,
206
206
 
207
- // Unified search across all project knowledge
208
- search: createSearchTool,
207
+ // Unified RAG search across all project knowledge
208
+ rag_search: createRAGSearchTool,
209
209
 
210
210
  shell: createShellTool,
211
211
  kill: createKillTool,
@@ -216,7 +216,6 @@ const toolFactories: Record<ToolName, ToolFactory> = {
216
216
  // RAG tools
217
217
  rag_create_collection: createRAGCreateCollectionTool,
218
218
  rag_add_documents: createRAGAddDocumentsTool,
219
- rag_query: createRAGQueryTool,
220
219
  rag_delete_collection: createRAGDeleteCollectionTool,
221
220
  rag_list_collections: createRAGListCollectionsTool,
222
221
 
@@ -5,6 +5,11 @@ import type { MCPManager } from "@/services/mcp/MCPManager";
5
5
  import type { NDKEvent } from "@nostr-dev-kit/ndk";
6
6
  import type { Tool as CoreTool } from "ai";
7
7
 
8
+ export interface ToolTranscriptArgSpec {
9
+ key: string;
10
+ attribute?: string;
11
+ }
12
+
8
13
  /**
9
14
  * Tool names available in the system.
10
15
  * Keep this list in sync with implementations registered in the tool registry.
@@ -47,7 +52,7 @@ export type ToolName =
47
52
  | "upload_blob"
48
53
  | "rag_create_collection"
49
54
  | "rag_add_documents"
50
- | "rag_query"
55
+ | "rag_search"
51
56
  | "rag_delete_collection"
52
57
  | "rag_list_collections"
53
58
  | "rag_subscription_create"
@@ -63,7 +68,6 @@ export type ToolName =
63
68
  | "web_search"
64
69
  | "nostr_fetch"
65
70
  | "nostr_publish_as_user"
66
- | "search"
67
71
  | "change_model"
68
72
  | "kill"
69
73
  | "generate_image";
@@ -73,6 +77,11 @@ export type ToolName =
73
77
  */
74
78
  export type AISdkTool<TInput = unknown, TOutput = unknown> = CoreTool<TInput, TOutput> & {
75
79
  getHumanReadableContent?: (args: TInput) => string;
80
+ /**
81
+ * Optional list of input argument keys to expose in conversation transcript XML.
82
+ * Keys are read from tool input args at execution time and serialized as XML attributes.
83
+ */
84
+ transcriptArgsToInclude?: ToolTranscriptArgSpec[];
76
85
  /**
77
86
  * Whether this tool has side effects (modifies state, writes files, sends messages, etc.)
78
87
  * Default is true (assume side effects unless explicitly declared false).
@@ -0,0 +1,13 @@
1
+ import type { AISdkTool, ToolTranscriptArgSpec } from "@/tools/types";
2
+
3
+ export function attachTranscriptArgs<TInput = unknown, TOutput = unknown>(
4
+ tool: AISdkTool<TInput, TOutput>,
5
+ args: ToolTranscriptArgSpec[]
6
+ ): AISdkTool<TInput, TOutput> {
7
+ Object.defineProperty(tool, "transcriptArgsToInclude", {
8
+ value: args,
9
+ enumerable: false,
10
+ configurable: true,
11
+ });
12
+ return tool;
13
+ }
@@ -0,0 +1,13 @@
1
+ import chalk from "chalk";
2
+
3
+ export const amber = chalk.hex("#FFC107");
4
+ export const amberBold = chalk.hex("#FFC107").bold;
5
+
6
+ export const inquirerTheme = {
7
+ prefix: { idle: amber("?"), done: chalk.green("✓") },
8
+ icon: { cursor: amber("❯") },
9
+ style: {
10
+ highlight: (text: string) => amber(text),
11
+ answer: (text: string) => amber(text),
12
+ },
13
+ };
@@ -40,6 +40,8 @@ const emojis = {
40
40
 
41
41
  // File logging state
42
42
  let logFilePath: string | null = null;
43
+ let warnLogFilePath: string | null = null;
44
+ const WARN_LOG_MAX_SIZE = 100 * 1024 * 1024; // 100MB
43
45
 
44
46
  // Helper to format timestamp for file output
45
47
  function formatTimestamp(): string {
@@ -64,6 +66,55 @@ function writeToFile(level: string, message: string, args: unknown[]): void {
64
66
  fs.appendFileSync(logFilePath, logLine);
65
67
  }
66
68
 
69
+ /**
70
+ * Structured entry written to warn.log for operator troubleshooting.
71
+ */
72
+ export interface WarnLogEntry {
73
+ timestamp: string;
74
+ level: "warn" | "error";
75
+ component: string;
76
+ message: string;
77
+ context?: Record<string, unknown>;
78
+ error?: string;
79
+ stack?: string;
80
+ }
81
+
82
+ /**
83
+ * Rotate warn.log when it exceeds WARN_LOG_MAX_SIZE.
84
+ * Renames current file to warn.log.1, discarding any previous .1.
85
+ */
86
+ function rotateWarnLogIfNeeded(): void {
87
+ if (!warnLogFilePath) return;
88
+ try {
89
+ const stat = fs.statSync(warnLogFilePath);
90
+ if (stat.size >= WARN_LOG_MAX_SIZE) {
91
+ const rotatedPath = `${warnLogFilePath}.1`;
92
+ try {
93
+ fs.unlinkSync(rotatedPath);
94
+ } catch {
95
+ // .1 doesn't exist, fine
96
+ }
97
+ fs.renameSync(warnLogFilePath, rotatedPath);
98
+ }
99
+ } catch {
100
+ // File doesn't exist yet, no rotation needed
101
+ }
102
+ }
103
+
104
+ /**
105
+ * Write a structured JSON entry to warn.log.
106
+ * Only writes if warn.log transport has been initialized.
107
+ */
108
+ function writeToWarnLog(entry: WarnLogEntry): void {
109
+ if (!warnLogFilePath) return;
110
+ try {
111
+ rotateWarnLogIfNeeded();
112
+ fs.appendFileSync(warnLogFilePath, JSON.stringify(entry) + "\n");
113
+ } catch {
114
+ // Swallow — we can't let warn.log failures crash the system
115
+ }
116
+ }
117
+
67
118
  // Initialize daemon logging
68
119
  async function initDaemonLogging(): Promise<void> {
69
120
  // Lazy-load config to avoid circular dependency
@@ -76,11 +127,15 @@ async function initDaemonLogging(): Promise<void> {
76
127
  // Ensure directory exists
77
128
  const logDir = path.dirname(logFilePath);
78
129
  fs.mkdirSync(logDir, { recursive: true });
130
+
131
+ // Initialize warn.log in the same directory
132
+ warnLogFilePath = path.join(logDir, "warn.log");
79
133
  }
80
134
 
81
135
  // Main logger object
82
136
  export const logger = {
83
137
  initDaemonLogging,
138
+ writeToWarnLog,
84
139
 
85
140
  /**
86
141
  * Check if a specific log level is enabled
@@ -0,0 +1,17 @@
1
+ /**
2
+ * Canonical metadata key variants for RAG document filtering.
3
+ *
4
+ * Documents ingested via different code-paths use different key conventions:
5
+ * - Specialized services (reports, lessons, conversations) write **camelCase**.
6
+ * - The generic `rag_add_documents` tool historically wrote **snake_case**.
7
+ *
8
+ * Every SQL LIKE filter that matches on these keys must check BOTH variants
9
+ * to avoid silently missing documents. Keeping the pairs here as shared
10
+ * constants prevents the two sets from drifting out of sync.
11
+ */
12
+
13
+ /** Tuple of [camelCase, snake_case] key names for project ID metadata. */
14
+ export const PROJECT_ID_KEYS = ["projectId", "project_id"] as const;
15
+
16
+ /** Tuple of [camelCase, snake_case] key names for agent pubkey metadata. */
17
+ export const AGENT_PUBKEY_KEYS = ["agentPubkey", "agent_pubkey"] as const;
@@ -0,0 +1,39 @@
1
+ /**
2
+ * SQL escaping utilities for LanceDB metadata LIKE queries.
3
+ *
4
+ * Shared across all RAG query builders (project filters, agent stats, etc.)
5
+ * to ensure consistent escaping of user-supplied values in SQL LIKE patterns.
6
+ *
7
+ * IMPORTANT: DataFusion (used by LanceDB) has NO default escape character.
8
+ * Backslash-based escapes only work when paired with an ESCAPE '\\' clause.
9
+ * See: https://github.com/apache/datafusion/issues/13291
10
+ */
11
+
12
+ /**
13
+ * The ESCAPE clause literal required by every LIKE expression that uses
14
+ * the backslash escapes produced by {@link escapeSqlLikeValue}.
15
+ *
16
+ * Usage: `metadata LIKE '…${escaped}…' ${SQL_LIKE_ESCAPE_CLAUSE}`
17
+ */
18
+ export const SQL_LIKE_ESCAPE_CLAUSE = "ESCAPE '\\\\'";
19
+
20
+ /**
21
+ * Escape a string for use inside a SQL LIKE pattern.
22
+ *
23
+ * Escapes: backslashes (\), single quotes ('), double quotes ("),
24
+ * and LIKE wildcards (%, _).
25
+ *
26
+ * The returned value MUST be used together with {@link SQL_LIKE_ESCAPE_CLAUSE}
27
+ * so that DataFusion recognises the backslash as the escape character.
28
+ *
29
+ * @param value - Raw string to escape (e.g. a project ID or agent pubkey).
30
+ * @returns Escaped string safe for interpolation into a LIKE pattern.
31
+ */
32
+ export function escapeSqlLikeValue(value: string): string {
33
+ return value
34
+ .replace(/\\/g, "\\\\") // Escape backslashes first
35
+ .replace(/'/g, "''") // SQL standard: escape single quote by doubling
36
+ .replace(/"/g, '\\"') // Escape double quotes
37
+ .replace(/%/g, "\\%") // Escape LIKE wildcard %
38
+ .replace(/_/g, "\\_"); // Escape LIKE wildcard _
39
+ }
package/src/wrapper.ts CHANGED
@@ -193,13 +193,17 @@ class DaemonWrapper {
193
193
  */
194
194
  private spawnDaemon(args: string[]): Promise<number> {
195
195
  return new Promise((resolve) => {
196
- // Build command: bun <entry-point> daemon --supervised [args]
197
196
  const indexPath = resolveEntryPoint();
198
197
  const daemonArgs = [indexPath, "daemon", "--supervised", ...args];
199
198
 
200
- console.log(`[Wrapper] Spawning: bun ${daemonArgs.join(" ")}`);
199
+ // Use Bun when running TypeScript sources; otherwise run compiled JS via Node.
200
+ const runtimeBinary = indexPath.endsWith(".ts")
201
+ ? (process.env.TENEX_BUN_BIN || "bun")
202
+ : process.execPath;
201
203
 
202
- this.child = spawn("bun", daemonArgs, {
204
+ console.log(`[Wrapper] Spawning: ${runtimeBinary} ${daemonArgs.join(" ")}`);
205
+
206
+ this.child = spawn(runtimeBinary, daemonArgs, {
203
207
  stdio: "inherit",
204
208
  env: process.env,
205
209
  });