@tenex-chat/backend 0.9.4 → 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.
Files changed (148) hide show
  1. package/README.md +5 -1
  2. package/dist/daemon-wrapper.cjs +47 -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 -46778
  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 -215
  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
@@ -0,0 +1,153 @@
1
+ import type { LanguageModelMiddleware } from "ai";
2
+ import type {
3
+ LanguageModelV3CallOptions,
4
+ LanguageModelV3Message,
5
+ } from "@ai-sdk/provider";
6
+ import { appendFileSync, mkdirSync } from "fs";
7
+ import { join } from "path";
8
+ import { getTenexBasePath } from "@/constants";
9
+ import { trace } from "@opentelemetry/api";
10
+
11
+ interface SanitizationWarning {
12
+ fix: string;
13
+ removed: Array<{ index: number; role: string }>;
14
+ }
15
+
16
+ /**
17
+ * Check if a user or assistant message has empty content (content: []).
18
+ * System messages use string content (always valid).
19
+ * Tool messages may legitimately have minimal content for adjacency.
20
+ */
21
+ function hasEmptyContent(msg: LanguageModelV3Message): boolean {
22
+ if (msg.role === "system" || msg.role === "tool") return false;
23
+ return Array.isArray(msg.content) && msg.content.length === 0;
24
+ }
25
+
26
+ /**
27
+ * Run all sanitization passes on the original prompt, collecting warnings
28
+ * with indices in the original coordinate space.
29
+ *
30
+ * Pass 1: Mark empty-content user/assistant messages for removal.
31
+ * Pass 2: Mark trailing assistant messages for removal (from the end,
32
+ * skipping already-marked indices).
33
+ *
34
+ * Returns the filtered array and any warnings.
35
+ */
36
+ function sanitize(
37
+ prompt: LanguageModelV3Message[]
38
+ ): { result: LanguageModelV3Message[]; warnings: SanitizationWarning[] } {
39
+ const warnings: SanitizationWarning[] = [];
40
+ const removeSet = new Set<number>();
41
+
42
+ // Pass 1: empty content
43
+ const emptyRemoved: Array<{ index: number; role: string }> = [];
44
+ for (let i = 0; i < prompt.length; i++) {
45
+ if (hasEmptyContent(prompt[i])) {
46
+ emptyRemoved.push({ index: i, role: prompt[i].role });
47
+ removeSet.add(i);
48
+ }
49
+ }
50
+ if (emptyRemoved.length > 0) {
51
+ warnings.push({ fix: "empty-content-stripped", removed: emptyRemoved });
52
+ }
53
+
54
+ // Pass 2: trailing assistants (walk backwards, skipping already-removed)
55
+ const trailingRemoved: Array<{ index: number; role: string }> = [];
56
+ for (let i = prompt.length - 1; i >= 0; i--) {
57
+ if (removeSet.has(i)) continue;
58
+ if (prompt[i].role !== "assistant") break;
59
+ trailingRemoved.push({ index: i, role: "assistant" });
60
+ removeSet.add(i);
61
+ }
62
+ if (trailingRemoved.length > 0) {
63
+ warnings.push({ fix: "trailing-assistant-stripped", removed: trailingRemoved });
64
+ }
65
+
66
+ // Build filtered result
67
+ const result = prompt.filter((_, i) => !removeSet.has(i));
68
+ return { result, warnings };
69
+ }
70
+
71
+ /**
72
+ * Append a structured warning to $TENEX_BASE_DIR/daemon/warn.log.
73
+ * Uses appendFileSync — this path is rare (only on detected problems)
74
+ * so the sync I/O cost is acceptable vs. the complexity of async.
75
+ */
76
+ function logWarning(entry: Record<string, unknown>): void {
77
+ try {
78
+ const dir = join(getTenexBasePath(), "daemon");
79
+ mkdirSync(dir, { recursive: true });
80
+ const logPath = join(dir, "warn.log");
81
+ appendFileSync(logPath, JSON.stringify(entry) + "\n", "utf-8");
82
+ } catch {
83
+ // Best-effort logging — never let warn logging crash the LLM call
84
+ }
85
+ }
86
+
87
+ /**
88
+ * Creates a message sanitizer middleware that runs before every LLM API call.
89
+ *
90
+ * This middleware fixes message array problems that would cause API rejection:
91
+ * - Trailing assistant messages (Anthropic rejects these)
92
+ * - Empty-content user/assistant messages
93
+ *
94
+ * It intercepts `params.prompt` via `transformParams`, covering all call paths:
95
+ * initial streamText, prepareStep-rebuilt messages, generateText, and generateObject.
96
+ *
97
+ * When fixes are applied, it logs a structured warning to $TENEX_BASE_DIR/daemon/warn.log
98
+ * and adds an OTel span event for telemetry correlation.
99
+ */
100
+ export function createMessageSanitizerMiddleware(): LanguageModelMiddleware {
101
+ return {
102
+ specificationVersion: "v3" as const,
103
+
104
+ transformParams: async ({ params, type, model }) => {
105
+ const originalPrompt = params.prompt as LanguageModelV3Message[];
106
+ const originalCount = originalPrompt.length;
107
+
108
+ const { result: sanitized, warnings } = sanitize(originalPrompt);
109
+
110
+ // No fixes needed — return params unchanged
111
+ if (warnings.length === 0) {
112
+ return params;
113
+ }
114
+
115
+ // Build log entry
116
+ const modelId = `${model.provider}:${model.modelId}`;
117
+ const allRemoved = warnings.flatMap((w) => w.removed);
118
+
119
+ for (const warning of warnings) {
120
+ const entry = {
121
+ ts: new Date().toISOString(),
122
+ type: "message-sanitizer",
123
+ fix: warning.fix,
124
+ model: modelId,
125
+ callType: type,
126
+ original_count: originalCount,
127
+ fixed_count: sanitized.length,
128
+ removed: warning.removed,
129
+ };
130
+ logWarning(entry);
131
+ }
132
+
133
+ // OTel span event — skip gracefully if no active span
134
+ const span = trace.getActiveSpan();
135
+ if (span) {
136
+ span.addEvent("message-sanitizer.fix-applied", {
137
+ "sanitizer.fixes": warnings.map((w) => w.fix).join(","),
138
+ "sanitizer.original_count": originalCount,
139
+ "sanitizer.fixed_count": sanitized.length,
140
+ "sanitizer.removed_indices": allRemoved.map((r) => r.index).join(","),
141
+ "sanitizer.removed_roles": allRemoved.map((r) => r.role).join(","),
142
+ "sanitizer.model": modelId,
143
+ "sanitizer.call_type": type,
144
+ });
145
+ }
146
+
147
+ return {
148
+ ...params,
149
+ prompt: sanitized as LanguageModelV3CallOptions["prompt"],
150
+ };
151
+ },
152
+ };
153
+ }
@@ -65,41 +65,3 @@ function formatSize(bytes: number): string {
65
65
  const mb = bytes / (1024 * 1024);
66
66
  return `${mb.toFixed(0)}MB`;
67
67
  }
68
-
69
- /**
70
- * Get popular Ollama models for quick selection
71
- */
72
- export function getPopularOllamaModels(): Record<string, string[]> {
73
- return {
74
- "Small Models (< 4GB)": [
75
- "llama3.2:1b",
76
- "llama3.2:3b",
77
- "phi3:mini",
78
- "gemma2:2b",
79
- "qwen2.5:1.5b",
80
- "qwen2.5:3b",
81
- ],
82
- "Medium Models (4-8GB)": [
83
- "llama3.1:8b",
84
- "mistral:7b",
85
- "gemma2:9b",
86
- "qwen2.5:7b",
87
- "deepseek-coder-v2:16b",
88
- ],
89
- "Large Models (> 8GB)": [
90
- "llama3.1:70b",
91
- "mixtral:8x7b",
92
- "qwen2.5:14b",
93
- "qwen2.5:32b",
94
- "qwen2.5:72b",
95
- "deepseek-coder-v2:236b",
96
- ],
97
- "Specialized Models": [
98
- "codellama:7b",
99
- "codellama:13b",
100
- "starcoder2:3b",
101
- "starcoder2:7b",
102
- "sqlcoder:7b",
103
- ],
104
- };
105
- }
@@ -22,7 +22,7 @@ import type { z } from "zod";
22
22
  import { ChunkHandler, type ChunkHandlerState } from "./ChunkHandler";
23
23
  import { createFinishHandler, type FinishHandlerConfig, type FinishHandlerState } from "./FinishHandler";
24
24
  import { extractLastUserMessage, extractSystemContent, prepareMessagesForRequest } from "./MessageProcessor";
25
- import { createFlightRecorderMiddleware } from "./middleware/flight-recorder";
25
+ import { createMessageSanitizerMiddleware } from "./middleware/message-sanitizer";
26
26
  import { PROVIDER_IDS } from "./providers/provider-ids";
27
27
  import { getFullTelemetryConfig, getOpenRouterMetadata, getTraceCorrelationId } from "./TracingUtils";
28
28
  import type { ProviderCapabilities } from "./providers/types";
@@ -196,11 +196,20 @@ export class LLMService extends EventEmitter<LLMServiceEventMap> {
196
196
  throw new Error("No provider available for model creation");
197
197
  }
198
198
 
199
- // Build middleware chain
199
+ return this.wrapWithMiddleware(baseModel);
200
+ }
201
+
202
+ /**
203
+ * Wrap a base model with the standard middleware chain.
204
+ * Used by both getLanguageModel() and createLanguageModelFromRegistry()
205
+ * so all call paths get sanitization and reasoning extraction.
206
+ */
207
+ private wrapWithMiddleware(baseModel: LanguageModel): LanguageModel {
200
208
  const middlewares: LanguageModelMiddleware[] = [];
201
209
 
202
- // Flight recorder - records LLM interactions when enabled via 'r' key
203
- middlewares.push(createFlightRecorderMiddleware());
210
+ // Message sanitizer fix message array issues before every API call
211
+ // Must be first so it sanitizes before anything else processes messages
212
+ middlewares.push(createMessageSanitizerMiddleware());
204
213
 
205
214
  // Extract reasoning from thinking tags
206
215
  middlewares.push(
@@ -211,15 +220,10 @@ export class LLMService extends EventEmitter<LLMServiceEventMap> {
211
220
  })
212
221
  );
213
222
 
214
- // Wrap with middlewares
215
- // Note: Type assertion needed because AI SDK v6 beta uses LanguageModelV3 internally
216
- // but stable providers export LanguageModel (union type). This is a beta compatibility issue.
217
- const wrappedModel = wrapLanguageModel({
223
+ return wrapLanguageModel({
218
224
  model: baseModel as Parameters<typeof wrapLanguageModel>[0]["model"],
219
225
  middleware: middlewares,
220
226
  });
221
-
222
- return wrappedModel;
223
227
  }
224
228
 
225
229
  async stream(
@@ -434,7 +438,7 @@ export class LLMService extends EventEmitter<LLMServiceEventMap> {
434
438
  // tool-call, tool-input-start, tool-input-delta, tool-result, raw.
435
439
  // Events like "finish" and "tool-error" must be forwarded explicitly.
436
440
  if (part.type === "finish") {
437
- // Emit raw-chunk directly to reach StreamPublisher (for HTTP wrapper SSE)
441
+ // Emit raw-chunk directly for low-level listeners.
438
442
  // WITHOUT going through ChunkHandler which would trigger chunk-type-change
439
443
  // and cause AgentExecutor to publish a duplicate kind:1 event.
440
444
  this.emit("raw-chunk", { chunk: part as TextStreamPart<Record<string, AISdkTool>> });
@@ -516,6 +520,22 @@ export class LLMService extends EventEmitter<LLMServiceEventMap> {
516
520
  "stream.finish_part_seen": finishPartSeen,
517
521
  "stream.abort_signal_aborted": options?.abortSignal?.aborted ?? false,
518
522
  });
523
+ logger.writeToWarnLog({
524
+ timestamp: new Date().toISOString(),
525
+ level: "error",
526
+ component: "LLMService",
527
+ message: "LLM stream loop error",
528
+ context: {
529
+ provider: this.provider,
530
+ model: this.model,
531
+ chunkCount,
532
+ lastChunkType: lastChunkType ?? "none",
533
+ finishPartSeen,
534
+ abortSignalAborted: options?.abortSignal?.aborted ?? false,
535
+ },
536
+ error: error instanceof Error ? error.message : String(error),
537
+ stack: error instanceof Error ? error.stack : undefined,
538
+ });
519
539
  await this.handleStreamError(error, startTime);
520
540
  throw error;
521
541
  }
@@ -549,15 +569,17 @@ export class LLMService extends EventEmitter<LLMServiceEventMap> {
549
569
  }
550
570
 
551
571
  /**
552
- * Create a language model instance for dynamic model switching.
553
- * Used by AgentExecutor when the change_model tool switches variants mid-run.
572
+ * Create a wrapped language model for dynamic model switching.
573
+ * Wraps the registry model with the same middleware chain
574
+ * (sanitizer, reasoning extraction) used by all other call paths.
554
575
  */
555
- static createLanguageModelFromRegistry(
576
+ createLanguageModelFromRegistry(
556
577
  provider: string,
557
578
  model: string,
558
579
  registry: ProviderRegistryProvider
559
580
  ): LanguageModel {
560
- return registry.languageModel(`${provider}:${model}`);
581
+ const baseModel = registry.languageModel(`${provider}:${model}`);
582
+ return this.wrapWithMiddleware(baseModel);
561
583
  }
562
584
 
563
585
  /**
@@ -707,6 +729,19 @@ export class LLMService extends EventEmitter<LLMServiceEventMap> {
707
729
  duration,
708
730
  error: error instanceof Error ? error.message : String(error),
709
731
  });
732
+ logger.writeToWarnLog({
733
+ timestamp: new Date().toISOString(),
734
+ level: "error",
735
+ component: "LLMService",
736
+ message: `${operationName} failed`,
737
+ context: {
738
+ provider: this.provider,
739
+ model: this.model,
740
+ duration,
741
+ },
742
+ error: error instanceof Error ? error.message : String(error),
743
+ stack: error instanceof Error ? error.stack : undefined,
744
+ });
710
745
  throw error;
711
746
  }
712
747
  }
package/src/llm/types.ts CHANGED
@@ -47,18 +47,6 @@ export type LanguageModelUsageWithCostUsd = LanguageModelUsage & {
47
47
  contextWindow?: number;
48
48
  };
49
49
 
50
- /**
51
- * Chunk sent over local streaming socket
52
- */
53
- export interface LocalStreamChunk {
54
- /** Hex pubkey of the agent generating this response */
55
- agent_pubkey: string;
56
- /** Root event ID of the conversation (hex) */
57
- conversation_id: string;
58
- /** Raw AI SDK chunk - passthrough without transformation */
59
- data: unknown;
60
- }
61
-
62
50
  /**
63
51
  * Callback invoked when message injection completes
64
52
  * @param delivered - true if the message was successfully delivered to the stream