@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.
- 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 -46778
- 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 -215
- 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
|
@@ -301,26 +301,23 @@ export class ProjectRuntime {
|
|
|
301
301
|
|
|
302
302
|
// Stop status publisher
|
|
303
303
|
if (this.statusPublisher) {
|
|
304
|
-
|
|
304
|
+
logger.info(`[ProjectRuntime] Stopping status publisher: ${this.projectId}`);
|
|
305
305
|
await this.statusPublisher.stopPublishing();
|
|
306
306
|
this.statusPublisher = null;
|
|
307
|
-
console.log(chalk.gray(" done"));
|
|
308
307
|
}
|
|
309
308
|
|
|
310
309
|
// Stop operations status publisher
|
|
311
310
|
if (this.operationsStatusPublisher) {
|
|
312
|
-
|
|
311
|
+
logger.info(`[ProjectRuntime] Stopping operations status: ${this.projectId}`);
|
|
313
312
|
this.operationsStatusPublisher.stop();
|
|
314
313
|
this.operationsStatusPublisher = null;
|
|
315
|
-
console.log(chalk.gray(" done"));
|
|
316
314
|
}
|
|
317
315
|
|
|
318
316
|
// Cleanup event handler
|
|
319
317
|
if (this.eventHandler) {
|
|
320
|
-
|
|
318
|
+
logger.info(`[ProjectRuntime] Cleaning up event handler: ${this.projectId}`);
|
|
321
319
|
await this.eventHandler.cleanup();
|
|
322
320
|
this.eventHandler = null;
|
|
323
|
-
console.log(chalk.gray(" done"));
|
|
324
321
|
}
|
|
325
322
|
|
|
326
323
|
// Shutdown MCP subscription service
|
|
@@ -332,20 +329,17 @@ export class ProjectRuntime {
|
|
|
332
329
|
}
|
|
333
330
|
|
|
334
331
|
// Save conversation state
|
|
335
|
-
|
|
332
|
+
logger.info(`[ProjectRuntime] Saving conversations: ${this.projectId}`);
|
|
336
333
|
await ConversationStore.cleanup();
|
|
337
|
-
console.log(chalk.gray(" done"));
|
|
338
334
|
|
|
339
335
|
// Reset local report store
|
|
340
|
-
|
|
336
|
+
logger.info(`[ProjectRuntime] Resetting report store: ${this.projectId}`);
|
|
341
337
|
this.localReportStore.reset();
|
|
342
|
-
console.log(chalk.gray(" done"));
|
|
343
338
|
|
|
344
339
|
// Release our reference to the prefix KV store (but don't close it -
|
|
345
340
|
// it's a daemon-global resource that outlives individual project runtimes)
|
|
346
|
-
|
|
341
|
+
logger.info(`[ProjectRuntime] Releasing storage: ${this.projectId}`);
|
|
347
342
|
await prefixKVStore.close();
|
|
348
|
-
console.log(chalk.gray(" done"));
|
|
349
343
|
|
|
350
344
|
// Clear context
|
|
351
345
|
this.context = null;
|
|
@@ -288,6 +288,19 @@ export class SubscriptionManager {
|
|
|
288
288
|
eventId: event.id,
|
|
289
289
|
eventKind: event.kind,
|
|
290
290
|
});
|
|
291
|
+
logger.writeToWarnLog({
|
|
292
|
+
timestamp: new Date().toISOString(),
|
|
293
|
+
level: "error",
|
|
294
|
+
component: "SubscriptionManager",
|
|
295
|
+
message: "Error handling incoming Nostr event",
|
|
296
|
+
context: {
|
|
297
|
+
eventId: event.id,
|
|
298
|
+
eventKind: event.kind,
|
|
299
|
+
pubkey: event.pubkey,
|
|
300
|
+
},
|
|
301
|
+
error: error instanceof Error ? error.message : String(error),
|
|
302
|
+
stack: error instanceof Error ? error.stack : undefined,
|
|
303
|
+
});
|
|
291
304
|
}
|
|
292
305
|
}
|
|
293
306
|
|
package/src/daemon/index.ts
CHANGED
package/src/index.ts
CHANGED
|
@@ -8,6 +8,7 @@
|
|
|
8
8
|
import { existsSync, readFileSync } from "node:fs";
|
|
9
9
|
import { homedir } from "node:os";
|
|
10
10
|
import { join } from "node:path";
|
|
11
|
+
import { fileURLToPath } from "node:url";
|
|
11
12
|
import { initializeTelemetry } from "@/telemetry/setup";
|
|
12
13
|
|
|
13
14
|
/**
|
|
@@ -25,6 +26,24 @@ interface TelemetryConfig {
|
|
|
25
26
|
endpoint: string;
|
|
26
27
|
}
|
|
27
28
|
|
|
29
|
+
function getCliVersion(): string {
|
|
30
|
+
if (process.env.npm_package_version) {
|
|
31
|
+
return process.env.npm_package_version;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
try {
|
|
35
|
+
const packageJsonPath = fileURLToPath(new URL("../package.json", import.meta.url));
|
|
36
|
+
const packageJson = JSON.parse(readFileSync(packageJsonPath, "utf8"));
|
|
37
|
+
if (typeof packageJson.version === "string" && packageJson.version.length > 0) {
|
|
38
|
+
return packageJson.version;
|
|
39
|
+
}
|
|
40
|
+
} catch {
|
|
41
|
+
// Fall through to default when package metadata is unavailable.
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
return "0.0.0";
|
|
45
|
+
}
|
|
46
|
+
|
|
28
47
|
function getTelemetryConfig(): TelemetryConfig {
|
|
29
48
|
const configPath = join(getBasePath(), "config.json");
|
|
30
49
|
const defaults: TelemetryConfig = {
|
|
@@ -89,7 +108,7 @@ async function main(): Promise<void> {
|
|
|
89
108
|
program
|
|
90
109
|
.name("tenex")
|
|
91
110
|
.description("TENEX Command Line Interface")
|
|
92
|
-
.version(
|
|
111
|
+
.version(getCliVersion());
|
|
93
112
|
|
|
94
113
|
// Register subcommands
|
|
95
114
|
program.addCommand(daemonCommand);
|
package/src/llm/ChunkHandler.ts
CHANGED
|
@@ -40,7 +40,7 @@ export class ChunkHandler {
|
|
|
40
40
|
return;
|
|
41
41
|
}
|
|
42
42
|
|
|
43
|
-
// Emit raw-chunk event for
|
|
43
|
+
// Emit raw-chunk event for low-level stream observers.
|
|
44
44
|
logger.debug("[LLMService] emitting raw-chunk", { chunkType: chunk.type });
|
|
45
45
|
this.emitter.emit("raw-chunk", { chunk: event.chunk });
|
|
46
46
|
|
package/src/llm/FinishHandler.ts
CHANGED
|
@@ -56,24 +56,32 @@ export function createFinishHandler(
|
|
|
56
56
|
// This creates intentional duplication (conversation + completion both have same text)
|
|
57
57
|
// but ensures delegated agents receive the full response via p-tag on completion.
|
|
58
58
|
//
|
|
59
|
-
//
|
|
60
|
-
// 1. Use cachedContent if available (normal case)
|
|
61
|
-
// 2. Use e.text if
|
|
62
|
-
//
|
|
59
|
+
// Multi-level fallback for finalMessage:
|
|
60
|
+
// 1. Use cachedContent if available (normal case: text is still buffered)
|
|
61
|
+
// 2. Use e.text if non-empty (text was already published via chunk-type-change,
|
|
62
|
+
// but we still re-publish for delegations - see comment above)
|
|
63
|
+
// 3. Use accumulated steps text if non-empty (handles multi-step flows where
|
|
64
|
+
// the final step has no text - e.g., last step processed a tool result and
|
|
65
|
+
// stopped immediately; e.text = finalStep.text which is "" in that case,
|
|
66
|
+
// but the real response text is in an earlier step)
|
|
67
|
+
// 4. Use error message if all sources are empty
|
|
63
68
|
const ERROR_FALLBACK_MESSAGE =
|
|
64
69
|
"There was an error capturing the work done, please review the conversation for the results";
|
|
65
70
|
|
|
66
71
|
const cachedContent = state.getCachedContent();
|
|
67
72
|
const text = e.text ?? "";
|
|
73
|
+
const stepsText = e.steps.reduce((acc, step) => acc + step.text, "");
|
|
68
74
|
|
|
69
75
|
const fallbackLevel =
|
|
70
76
|
cachedContent.length > 0 ? "cached" :
|
|
71
77
|
text.length > 0 ? "text" :
|
|
78
|
+
stepsText.length > 0 ? "steps" :
|
|
72
79
|
"error";
|
|
73
80
|
|
|
74
81
|
const finalMessage =
|
|
75
82
|
fallbackLevel === "cached" ? cachedContent :
|
|
76
83
|
fallbackLevel === "text" ? text :
|
|
84
|
+
fallbackLevel === "steps" ? stepsText :
|
|
77
85
|
ERROR_FALLBACK_MESSAGE;
|
|
78
86
|
|
|
79
87
|
const usedFallbackToText = fallbackLevel === "text";
|
|
@@ -108,6 +116,8 @@ export function createFinishHandler(
|
|
|
108
116
|
"complete.message_length": finalMessage.length,
|
|
109
117
|
"complete.cached_content_length": cachedContent.length,
|
|
110
118
|
"complete.e_text_length": text.length,
|
|
119
|
+
"complete.steps_text_length": stepsText.length,
|
|
120
|
+
"complete.fallback_level": fallbackLevel,
|
|
111
121
|
"complete.used_fallback_to_e_text": usedFallbackToText,
|
|
112
122
|
"complete.used_error_fallback": usedErrorFallback,
|
|
113
123
|
"complete.usage_input_tokens": usage.inputTokens,
|
|
@@ -173,6 +183,20 @@ export function createFinishHandler(
|
|
|
173
183
|
logger.error("[LLMService] Error in onFinish handler", {
|
|
174
184
|
error: error instanceof Error ? error.message : String(error),
|
|
175
185
|
});
|
|
186
|
+
logger.writeToWarnLog({
|
|
187
|
+
timestamp: new Date().toISOString(),
|
|
188
|
+
level: "error",
|
|
189
|
+
component: "FinishHandler",
|
|
190
|
+
message: "Error in LLM onFinish handler",
|
|
191
|
+
context: {
|
|
192
|
+
provider: config.provider,
|
|
193
|
+
model: config.model,
|
|
194
|
+
finishReason: e.finishReason,
|
|
195
|
+
stepsCount: e.steps.length,
|
|
196
|
+
},
|
|
197
|
+
error: error instanceof Error ? error.message : String(error),
|
|
198
|
+
stack: error instanceof Error ? error.stack : undefined,
|
|
199
|
+
});
|
|
176
200
|
throw error;
|
|
177
201
|
}
|
|
178
202
|
};
|
|
@@ -1,137 +1,255 @@
|
|
|
1
1
|
import { config } from "@/services/ConfigService";
|
|
2
2
|
import type { TenexLLMs } from "@/services/config/types";
|
|
3
|
+
import {
|
|
4
|
+
createPrompt,
|
|
5
|
+
useState,
|
|
6
|
+
useEffect,
|
|
7
|
+
useRef,
|
|
8
|
+
useKeypress,
|
|
9
|
+
usePrefix,
|
|
10
|
+
isEnterKey,
|
|
11
|
+
isUpKey,
|
|
12
|
+
isDownKey,
|
|
13
|
+
makeTheme,
|
|
14
|
+
} from "@inquirer/core";
|
|
15
|
+
import { cursorHide } from "@inquirer/ansi";
|
|
3
16
|
import chalk from "chalk";
|
|
4
|
-
import
|
|
17
|
+
import { inquirerTheme } from "@/utils/cli-theme";
|
|
18
|
+
import * as display from "@/commands/setup/display";
|
|
5
19
|
import { llmServiceFactory } from "./LLMServiceFactory";
|
|
6
20
|
import { ConfigurationManager } from "./utils/ConfigurationManager";
|
|
7
21
|
import { ConfigurationTester } from "./utils/ConfigurationTester";
|
|
22
|
+
import type { TestResult } from "./utils/ConfigurationTester";
|
|
8
23
|
import { ProviderConfigUI } from "./utils/ProviderConfigUI";
|
|
9
|
-
import { runProviderSetup } from "./utils/provider-setup";
|
|
10
24
|
|
|
11
|
-
/**
|
|
12
|
-
* Internal type used by editor to work with providers
|
|
13
|
-
* Merges providers with llms for internal convenience
|
|
14
|
-
*/
|
|
15
25
|
type LLMConfigWithProviders = TenexLLMs & {
|
|
16
26
|
providers: Record<string, { apiKey: string | string[] }>;
|
|
17
27
|
};
|
|
18
28
|
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
* Note: LLM configs are now global only (no project-level llms.json)
|
|
22
|
-
*/
|
|
23
|
-
export class LLMConfigEditor {
|
|
24
|
-
constructor() {}
|
|
29
|
+
type ListItem = { name: string; value: string; configName?: string };
|
|
30
|
+
type ActionItem = { name: string; value: string; key: string };
|
|
25
31
|
|
|
26
|
-
|
|
27
|
-
|
|
32
|
+
type MenuConfig = {
|
|
33
|
+
message: string;
|
|
34
|
+
items: ListItem[];
|
|
35
|
+
actions: ActionItem[];
|
|
36
|
+
onTest?: (configName: string) => Promise<TestResult>;
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
const SPINNER_FRAMES = ["⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇", "⠏"];
|
|
40
|
+
|
|
41
|
+
const menuTheme = {
|
|
42
|
+
icon: { cursor: inquirerTheme.icon.cursor },
|
|
43
|
+
style: {
|
|
44
|
+
highlight: inquirerTheme.style.highlight,
|
|
45
|
+
},
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
const selectWithFooter = createPrompt<string, MenuConfig>((config, done) => {
|
|
49
|
+
const { items, actions } = config;
|
|
50
|
+
const theme = makeTheme(menuTheme);
|
|
51
|
+
// items + actions + Done
|
|
52
|
+
const doneIndex = items.length + actions.length;
|
|
53
|
+
const totalNavigable = doneIndex + 1;
|
|
54
|
+
|
|
55
|
+
const [active, setActive] = useState(0);
|
|
56
|
+
const resultsRef = useRef<Record<string, TestResult>>({});
|
|
57
|
+
const [testing, setTesting] = useState<string | null>(null);
|
|
58
|
+
const [spinnerFrame, setSpinnerFrame] = useState(0);
|
|
59
|
+
const prefix = usePrefix({ status: "idle", theme });
|
|
60
|
+
|
|
61
|
+
const timerRef = useRef<ReturnType<typeof setInterval> | null>(null);
|
|
62
|
+
|
|
63
|
+
useEffect(() => {
|
|
64
|
+
if (testing && !timerRef.current) {
|
|
65
|
+
timerRef.current = setInterval(() => {
|
|
66
|
+
setSpinnerFrame(spinnerFrame + 1);
|
|
67
|
+
}, 80);
|
|
68
|
+
}
|
|
69
|
+
if (!testing && timerRef.current) {
|
|
70
|
+
clearInterval(timerRef.current);
|
|
71
|
+
timerRef.current = null;
|
|
72
|
+
}
|
|
73
|
+
return () => {
|
|
74
|
+
if (timerRef.current) {
|
|
75
|
+
clearInterval(timerRef.current);
|
|
76
|
+
timerRef.current = null;
|
|
77
|
+
}
|
|
78
|
+
};
|
|
79
|
+
}, [testing != null, spinnerFrame]);
|
|
80
|
+
|
|
81
|
+
useKeypress((key, rl) => {
|
|
82
|
+
if (testing) return;
|
|
28
83
|
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
{
|
|
58
|
-
name: `Prompt compilation model: ${llmsConfig.promptCompilation || "none"}`,
|
|
59
|
-
value: "promptCompilation",
|
|
60
|
-
},
|
|
61
|
-
{
|
|
62
|
-
name: `Compression model: ${llmsConfig.compression || "none"}`,
|
|
63
|
-
value: "compression",
|
|
64
|
-
},
|
|
65
|
-
{ name: "Test configuration", value: "test" },
|
|
66
|
-
{ name: "Exit", value: "exit" },
|
|
67
|
-
],
|
|
68
|
-
},
|
|
69
|
-
]);
|
|
70
|
-
|
|
71
|
-
if (action === "exit") process.exit(0);
|
|
72
|
-
|
|
73
|
-
if (action === "test") {
|
|
74
|
-
await ConfigurationTester.test(llmsConfig);
|
|
84
|
+
if (isEnterKey(key)) {
|
|
85
|
+
if (active < items.length) {
|
|
86
|
+
done(items[active]!.value);
|
|
87
|
+
} else if (active < doneIndex) {
|
|
88
|
+
done(actions[active - items.length]!.value);
|
|
89
|
+
} else {
|
|
90
|
+
done("done");
|
|
91
|
+
}
|
|
92
|
+
} else if (isUpKey(key) || isDownKey(key)) {
|
|
93
|
+
rl.clearLine(0);
|
|
94
|
+
const offset = isUpKey(key) ? -1 : 1;
|
|
95
|
+
setActive((active + offset + totalNavigable) % totalNavigable);
|
|
96
|
+
} else if (key.name === "t" && active < items.length) {
|
|
97
|
+
const item = items[active];
|
|
98
|
+
if (item?.configName && config.onTest) {
|
|
99
|
+
if (resultsRef.current[item.configName]) return;
|
|
100
|
+
setTesting(item.configName);
|
|
101
|
+
config.onTest(item.configName).then((result) => {
|
|
102
|
+
resultsRef.current[item.configName!] = result;
|
|
103
|
+
setTesting(null);
|
|
104
|
+
});
|
|
105
|
+
}
|
|
106
|
+
} else if (key.name === "d" && active < items.length) {
|
|
107
|
+
const configValue = items[active]?.value;
|
|
108
|
+
if (configValue?.startsWith("config:")) {
|
|
109
|
+
const configName = configValue.slice("config:".length);
|
|
110
|
+
done(`delete:${configName}`);
|
|
111
|
+
}
|
|
75
112
|
} else {
|
|
76
|
-
|
|
77
|
-
if (
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
if (action === "default") await ConfigurationManager.setDefault(llmsConfig);
|
|
81
|
-
if (action === "summarization") await ConfigurationManager.setSummarizationModel(llmsConfig);
|
|
82
|
-
if (action === "supervision") await ConfigurationManager.setSupervisionModel(llmsConfig);
|
|
83
|
-
if (action === "search") await ConfigurationManager.setSearchModel(llmsConfig);
|
|
84
|
-
if (action === "promptCompilation") await ConfigurationManager.setPromptCompilationModel(llmsConfig);
|
|
85
|
-
if (action === "compression") await ConfigurationManager.setCompressionModel(llmsConfig);
|
|
86
|
-
await this.saveConfig(llmsConfig);
|
|
113
|
+
const match = actions.find((a) => a.key === key.name);
|
|
114
|
+
if (match) {
|
|
115
|
+
done(match.value);
|
|
116
|
+
}
|
|
87
117
|
}
|
|
118
|
+
});
|
|
88
119
|
|
|
89
|
-
|
|
120
|
+
const message = theme.style.message(config.message, "idle");
|
|
121
|
+
const cursor = theme.icon.cursor;
|
|
122
|
+
const lines: string[] = [];
|
|
123
|
+
|
|
124
|
+
lines.push(`${prefix} ${message}`);
|
|
125
|
+
|
|
126
|
+
if (items.length === 0) {
|
|
127
|
+
lines.push(chalk.dim(" No configurations yet"));
|
|
128
|
+
} else {
|
|
129
|
+
for (let i = 0; i < items.length; i++) {
|
|
130
|
+
const item = items[i]!;
|
|
131
|
+
const isActive = i === active;
|
|
132
|
+
const pfx = isActive ? `${cursor} ` : " ";
|
|
133
|
+
const color = isActive ? theme.style.highlight : (x: string) => x;
|
|
134
|
+
const name = item.configName;
|
|
135
|
+
|
|
136
|
+
if (name && testing === name) {
|
|
137
|
+
const frame = SPINNER_FRAMES[spinnerFrame % SPINNER_FRAMES.length];
|
|
138
|
+
lines.push(`${pfx}${chalk.yellow(frame)} ${color(item.name)}`);
|
|
139
|
+
} else {
|
|
140
|
+
const result = name ? resultsRef.current[name] : undefined;
|
|
141
|
+
if (result) {
|
|
142
|
+
const icon = result.success ? chalk.green("✓") : chalk.red("✗");
|
|
143
|
+
const errorHint = !result.success ? ` ${chalk.dim(result.error)}` : "";
|
|
144
|
+
lines.push(`${pfx}${icon} ${color(item.name)}${errorHint}`);
|
|
145
|
+
} else {
|
|
146
|
+
lines.push(`${pfx} ${color(item.name)}`);
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
lines.push(` ${"─".repeat(40)}`);
|
|
153
|
+
|
|
154
|
+
for (let i = 0; i < actions.length; i++) {
|
|
155
|
+
const action = actions[i]!;
|
|
156
|
+
const idx = items.length + i;
|
|
157
|
+
const isActive = active === idx;
|
|
158
|
+
const pfx = isActive ? `${cursor} ` : " ";
|
|
159
|
+
lines.push(`${pfx}${chalk.cyan(action.name)}`);
|
|
90
160
|
}
|
|
91
161
|
|
|
92
|
-
|
|
93
|
-
|
|
162
|
+
const donePfx = active === doneIndex ? `${cursor} ` : " ";
|
|
163
|
+
lines.push(`${donePfx}${display.doneLabel()}`);
|
|
94
164
|
|
|
165
|
+
const helpParts = [
|
|
166
|
+
`${chalk.bold("↑↓")} ${chalk.dim("navigate")}`,
|
|
167
|
+
`${chalk.bold("⏎")} ${chalk.dim("select")}`,
|
|
168
|
+
`${chalk.bold("t")} ${chalk.dim("test")}`,
|
|
169
|
+
`${chalk.bold("d")} ${chalk.dim("delete")}`,
|
|
170
|
+
];
|
|
171
|
+
lines.push(chalk.dim(` ${helpParts.join(chalk.dim(" • "))}`));
|
|
172
|
+
|
|
173
|
+
return `${lines.join("\n")}${cursorHide}`;
|
|
174
|
+
});
|
|
175
|
+
|
|
176
|
+
export class LLMConfigEditor {
|
|
177
|
+
private advanced: boolean;
|
|
178
|
+
|
|
179
|
+
constructor(options: { advanced?: boolean } = {}) {
|
|
180
|
+
this.advanced = options.advanced ?? false;
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
async showMainMenu(): Promise<void> {
|
|
95
184
|
const llmsConfig = await this.loadConfig();
|
|
96
|
-
const globalPath = config.getGlobalPath();
|
|
97
185
|
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
const updatedProviders = await runProviderSetup(existingProviders);
|
|
102
|
-
llmsConfig.providers = updatedProviders.providers;
|
|
103
|
-
await this.saveConfig(llmsConfig);
|
|
186
|
+
display.blank();
|
|
187
|
+
display.step(0, 0, "LLM Configuration");
|
|
188
|
+
ProviderConfigUI.displayProviders(llmsConfig);
|
|
104
189
|
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
190
|
+
const configNames = Object.keys(llmsConfig.configurations);
|
|
191
|
+
const items: ListItem[] = configNames.map((name) => {
|
|
192
|
+
const cfg = llmsConfig.configurations[name];
|
|
193
|
+
const detail =
|
|
194
|
+
cfg.provider === "meta"
|
|
195
|
+
? `multi-modal, ${Object.keys((cfg as { variants: Record<string, unknown> }).variants).length} variants`
|
|
196
|
+
: `${"model" in cfg ? cfg.model : "unknown"}`;
|
|
197
|
+
return {
|
|
198
|
+
name: `${name} ${chalk.dim(detail)}`,
|
|
199
|
+
value: `config:${name}`,
|
|
200
|
+
configName: name,
|
|
201
|
+
};
|
|
202
|
+
});
|
|
203
|
+
|
|
204
|
+
const actions: ActionItem[] = [
|
|
205
|
+
{ name: `Add new configuration ${chalk.dim("(a)")}`, value: "add", key: "a" },
|
|
206
|
+
{ name: `Add multi-modal configuration ${chalk.dim("(m)")}`, value: "addMultiModal", key: "m" },
|
|
207
|
+
];
|
|
208
|
+
|
|
209
|
+
const action = await selectWithFooter({
|
|
210
|
+
message: "Configurations",
|
|
211
|
+
items,
|
|
212
|
+
actions,
|
|
213
|
+
onTest: (configName) => ConfigurationTester.runTest(llmsConfig, configName),
|
|
214
|
+
});
|
|
109
215
|
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
await ConfigurationTester.test(llmsConfig);
|
|
216
|
+
if (action.startsWith("delete:")) {
|
|
217
|
+
const configName = action.slice("delete:".length);
|
|
218
|
+
await this.deleteConfig(llmsConfig, configName);
|
|
219
|
+
} else if (action === "add") {
|
|
220
|
+
await ConfigurationManager.add(llmsConfig, this.advanced);
|
|
221
|
+
await this.saveConfig(llmsConfig);
|
|
222
|
+
} else if (action === "addMultiModal") {
|
|
223
|
+
await ConfigurationManager.addMultiModal(llmsConfig);
|
|
224
|
+
await this.saveConfig(llmsConfig);
|
|
225
|
+
} else if (action === "done") {
|
|
226
|
+
return;
|
|
122
227
|
}
|
|
123
228
|
|
|
124
|
-
|
|
229
|
+
await this.showMainMenu();
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
private async deleteConfig(llmsConfig: LLMConfigWithProviders, configName: string): Promise<void> {
|
|
233
|
+
delete llmsConfig.configurations[configName];
|
|
234
|
+
|
|
235
|
+
if (llmsConfig.default === configName) {
|
|
236
|
+
const remaining = Object.keys(llmsConfig.configurations);
|
|
237
|
+
llmsConfig.default = remaining.length > 0 ? remaining[0] : undefined;
|
|
238
|
+
if (llmsConfig.default) {
|
|
239
|
+
display.hint(`Default changed to "${llmsConfig.default}"`);
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
display.success(`Configuration "${configName}" deleted`);
|
|
244
|
+
await this.saveConfig(llmsConfig);
|
|
125
245
|
}
|
|
126
246
|
|
|
127
247
|
private async loadConfig(): Promise<LLMConfigWithProviders> {
|
|
128
248
|
const globalPath = config.getGlobalPath();
|
|
129
249
|
|
|
130
|
-
// Load providers and llms separately
|
|
131
250
|
const providersConfig = await config.loadTenexProviders(globalPath);
|
|
132
251
|
const llmsConfig = await config.loadTenexLLMs(globalPath);
|
|
133
252
|
|
|
134
|
-
// Merge for internal editor use
|
|
135
253
|
return {
|
|
136
254
|
...llmsConfig,
|
|
137
255
|
providers: providersConfig.providers,
|
|
@@ -139,16 +257,10 @@ export class LLMConfigEditor {
|
|
|
139
257
|
}
|
|
140
258
|
|
|
141
259
|
private async saveConfig(llmsConfig: LLMConfigWithProviders): Promise<void> {
|
|
142
|
-
// Split providers and llms for separate storage
|
|
143
260
|
const { providers, ...llmsWithoutProviders } = llmsConfig;
|
|
144
261
|
|
|
145
|
-
// Save providers to providers.json
|
|
146
262
|
await config.saveGlobalProviders({ providers });
|
|
147
|
-
|
|
148
|
-
// Save llms to llms.json
|
|
149
263
|
await config.saveGlobalLLMs(llmsWithoutProviders as TenexLLMs);
|
|
150
|
-
|
|
151
|
-
// Re-initialize factory with updated providers
|
|
152
264
|
await llmServiceFactory.initializeProviders(providers);
|
|
153
265
|
}
|
|
154
266
|
}
|
package/src/llm/index.ts
CHANGED
|
@@ -4,9 +4,5 @@ export { LLMService } from "./service";
|
|
|
4
4
|
// Export factory
|
|
5
5
|
export { LLMServiceFactory, llmServiceFactory } from "./LLMServiceFactory";
|
|
6
6
|
|
|
7
|
-
// Export stream publisher
|
|
8
|
-
export { StreamPublisher, streamPublisher, type StreamTransport } from "./StreamPublisher";
|
|
9
|
-
export type { LocalStreamChunk } from "./types";
|
|
10
|
-
|
|
11
7
|
// Export types
|
|
12
8
|
export * from "./types";
|
|
@@ -134,8 +134,8 @@ export class MetaModelResolver {
|
|
|
134
134
|
}
|
|
135
135
|
|
|
136
136
|
/**
|
|
137
|
-
* Select the winning variant from a set of matches using
|
|
138
|
-
*
|
|
137
|
+
* Select the winning variant from a set of matches using position-based resolution.
|
|
138
|
+
* First keyword match wins (earliest position in message).
|
|
139
139
|
*/
|
|
140
140
|
private static selectWinningVariant(
|
|
141
141
|
matches: Array<{ keyword: string; variantName: string; variant: MetaModelVariant; position: number }>
|
|
@@ -144,16 +144,7 @@ export class MetaModelResolver {
|
|
|
144
144
|
return null;
|
|
145
145
|
}
|
|
146
146
|
|
|
147
|
-
|
|
148
|
-
const sorted = [...matches].sort((a, b) => {
|
|
149
|
-
const tierA = a.variant.tier ?? 0;
|
|
150
|
-
const tierB = b.variant.tier ?? 0;
|
|
151
|
-
if (tierB !== tierA) {
|
|
152
|
-
return tierB - tierA; // Higher tier wins
|
|
153
|
-
}
|
|
154
|
-
return a.position - b.position; // Earlier position wins if tiers equal
|
|
155
|
-
});
|
|
156
|
-
|
|
147
|
+
const sorted = [...matches].sort((a, b) => a.position - b.position);
|
|
157
148
|
return sorted[0];
|
|
158
149
|
}
|
|
159
150
|
|
|
@@ -256,7 +247,6 @@ export class MetaModelResolver {
|
|
|
256
247
|
logger.debug("[MetaModelResolver] Resolved variant", {
|
|
257
248
|
variantName: winner.variantName,
|
|
258
249
|
matchedKeywords,
|
|
259
|
-
tier: winner.variant.tier ?? 0,
|
|
260
250
|
configName: winner.variant.model,
|
|
261
251
|
});
|
|
262
252
|
|
|
@@ -314,11 +304,6 @@ export class MetaModelResolver {
|
|
|
314
304
|
static generateSystemPromptFragment(config: MetaModelConfiguration): string {
|
|
315
305
|
const lines: string[] = [];
|
|
316
306
|
|
|
317
|
-
if (config.description) {
|
|
318
|
-
lines.push(config.description);
|
|
319
|
-
lines.push("");
|
|
320
|
-
}
|
|
321
|
-
|
|
322
307
|
lines.push("You have access to the following models via change_model() tool:");
|
|
323
308
|
|
|
324
309
|
for (const [variantName, variant] of Object.entries(config.variants)) {
|