@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
|
@@ -77,15 +77,16 @@ rag_add_documents({
|
|
|
77
77
|
})
|
|
78
78
|
\`\`\`
|
|
79
79
|
|
|
80
|
-
### 3.
|
|
81
|
-
|
|
80
|
+
### 3. rag_search
|
|
81
|
+
Search across ALL project knowledge — reports, conversations, lessons, and any
|
|
82
|
+
additional RAG collections — using natural language semantic search.
|
|
82
83
|
|
|
83
84
|
\`\`\`typescript
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
85
|
+
rag_search({
|
|
86
|
+
query: "How does authentication work?",
|
|
87
|
+
limit: 10, // Maximum results (default: 10)
|
|
88
|
+
collections: ["reports", "lessons"], // Optional: filter to specific collections
|
|
89
|
+
prompt: "Summarize the auth approach" // Optional: LLM extraction
|
|
89
90
|
})
|
|
90
91
|
\`\`\`
|
|
91
92
|
|
|
@@ -143,7 +144,7 @@ All tools use standardized error responses:
|
|
|
143
144
|
{
|
|
144
145
|
"success": false,
|
|
145
146
|
"error": "Descriptive error message",
|
|
146
|
-
"toolName": "
|
|
147
|
+
"toolName": "rag_search"
|
|
147
148
|
}
|
|
148
149
|
\`\`\`
|
|
149
150
|
|
|
@@ -169,9 +170,9 @@ rag_add_documents({
|
|
|
169
170
|
})
|
|
170
171
|
|
|
171
172
|
// Later, retrieve relevant context
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
173
|
+
rag_search({
|
|
174
|
+
query: "What are the user's programming language preferences?",
|
|
175
|
+
collections: ["agent_insights"]
|
|
175
176
|
})
|
|
176
177
|
\`\`\`
|
|
177
178
|
|
|
@@ -192,9 +193,9 @@ rag_add_documents({
|
|
|
192
193
|
})
|
|
193
194
|
|
|
194
195
|
// Query for specific information
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
196
|
+
rag_search({
|
|
197
|
+
query: "API authentication methods",
|
|
198
|
+
collections: ["project_docs"]
|
|
198
199
|
})
|
|
199
200
|
\`\`\`
|
|
200
201
|
|
|
@@ -222,9 +223,9 @@ rag_add_documents({
|
|
|
222
223
|
})
|
|
223
224
|
|
|
224
225
|
// Find related lessons semantically
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
226
|
+
rag_search({
|
|
227
|
+
query: "How to handle promise rejections",
|
|
228
|
+
collections: ["lessons"]
|
|
228
229
|
})
|
|
229
230
|
\`\`\`
|
|
230
231
|
|
|
@@ -247,9 +248,9 @@ rag_add_documents({
|
|
|
247
248
|
}]
|
|
248
249
|
})
|
|
249
250
|
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
251
|
+
rag_search({
|
|
252
|
+
query: "authentication hook implementation",
|
|
253
|
+
collections: ["code_patterns"]
|
|
253
254
|
})
|
|
254
255
|
\`\`\`
|
|
255
256
|
|
|
@@ -273,7 +274,7 @@ Share collections between agents:
|
|
|
273
274
|
\`\`\`typescript
|
|
274
275
|
delegate({
|
|
275
276
|
task: "Analyze the project documentation",
|
|
276
|
-
tools: ["
|
|
277
|
+
tools: ["rag_search"],
|
|
277
278
|
context: "Use collection 'project_docs' for analysis"
|
|
278
279
|
})
|
|
279
280
|
\`\`\`
|
|
@@ -57,27 +57,13 @@ export const agentsMdGuidanceFragment: PromptFragment<AgentsMdGuidanceArgs> = {
|
|
|
57
57
|
- Deeper, more specific AGENTS.md files override general root instructions.
|
|
58
58
|
|
|
59
59
|
### Writing AGENTS.md Files
|
|
60
|
-
When
|
|
61
|
-
1.
|
|
62
|
-
2.
|
|
63
|
-
3.
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
# Directory Context
|
|
68
|
-
Specific architectural details or business logic for this directory.
|
|
69
|
-
|
|
70
|
-
## Commands
|
|
71
|
-
- Test: \`npm test path/to/dir\`
|
|
72
|
-
- Lint: \`npm run lint:specific\`
|
|
73
|
-
|
|
74
|
-
## Conventions
|
|
75
|
-
- Code Style: Functional patterns preferred
|
|
76
|
-
- Naming: CamelCase for files, PascalCase for classes
|
|
77
|
-
|
|
78
|
-
## Related
|
|
79
|
-
- [API Docs](./docs/api.md)
|
|
80
|
-
\`\`\``);
|
|
60
|
+
When creating or updating an AGENTS.md file:
|
|
61
|
+
1. **Maximum 50 lines.** If it's longer, you're including too much. Cut aggressively.
|
|
62
|
+
2. **No code examples.** Don't include correct/incorrect patterns, usage snippets, or testing templates. Just state the rule in plain English.
|
|
63
|
+
3. **No boilerplate sections.** Don't add Anti-Patterns, Testing, Dependencies, or Related sections. Only include what's genuinely unique to this directory.
|
|
64
|
+
4. **Don't repeat parent info.** Import patterns, naming conventions, layer rules — if it's in a parent directory's AGENTS.md, don't restate it.
|
|
65
|
+
5. **List actual files.** Name the real files that exist, not idealized directory trees. Keep it to key files only, not every file.
|
|
66
|
+
6. **State rules, not ideology.** "Tools delegate to services" is a rule. A 10-line example showing correct vs incorrect delegation is ideology.`);
|
|
81
67
|
|
|
82
68
|
// If root AGENTS.md content is available and short, include it
|
|
83
69
|
if (rootAgentsMdContent && rootAgentsMdContent.length < MAX_ROOT_CONTENT_LENGTH_FOR_SYSTEM_PROMPT) {
|
|
@@ -37,6 +37,7 @@ import { agentDirectedMonitoringFragment } from "./28-agent-directed-monitoring"
|
|
|
37
37
|
import { ragCollectionsFragment } from "./29-rag-collections";
|
|
38
38
|
import { worktreeContextFragment } from "./30-worktree-context";
|
|
39
39
|
import { agentsMdGuidanceFragment } from "./31-agents-md-guidance";
|
|
40
|
+
import { processMetricsFragment } from "./32-process-metrics";
|
|
40
41
|
import { debugModeFragment } from "./debug-mode";
|
|
41
42
|
import { delegationCompletionFragment } from "./delegation-completion";
|
|
42
43
|
|
|
@@ -85,6 +86,7 @@ export function registerAllFragments(): void {
|
|
|
85
86
|
fragmentRegistry.register(ragCollectionsFragment);
|
|
86
87
|
fragmentRegistry.register(worktreeContextFragment);
|
|
87
88
|
fragmentRegistry.register(agentsMdGuidanceFragment);
|
|
89
|
+
fragmentRegistry.register(processMetricsFragment);
|
|
88
90
|
}
|
|
89
91
|
|
|
90
92
|
// Auto-register all fragments on import
|
|
@@ -250,37 +250,24 @@ async function addCoreAgentFragments(
|
|
|
250
250
|
|
|
251
251
|
// Add RAG collection attribution - shows agents their contributions to RAG collections
|
|
252
252
|
// This uses the provenance tracking metadata (agent_pubkey) from document ingestion
|
|
253
|
-
//
|
|
254
|
-
// OPTIMIZATION: First check if any collections exist using lightweight check
|
|
255
|
-
// to avoid initializing embedding provider when RAG isn't used.
|
|
256
253
|
try {
|
|
257
|
-
const {
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
if (
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
agentPubkey: agent.pubkey,
|
|
272
|
-
collections,
|
|
273
|
-
});
|
|
274
|
-
logger.debug("📊 Added RAG collection stats to system prompt", {
|
|
275
|
-
agent: agent.name,
|
|
276
|
-
collectionsWithContributions: collections.filter(c => c.agentDocCount > 0).length,
|
|
277
|
-
totalCollections: collections.length,
|
|
278
|
-
});
|
|
279
|
-
}
|
|
254
|
+
const { RAGService } = await import("@/services/rag/RAGService");
|
|
255
|
+
const ragService = RAGService.getInstance();
|
|
256
|
+
const collections = await ragService.getAllCollectionStats(agent.pubkey);
|
|
257
|
+
|
|
258
|
+
if (collections.length > 0) {
|
|
259
|
+
builder.add("rag-collections", {
|
|
260
|
+
agentPubkey: agent.pubkey,
|
|
261
|
+
collections,
|
|
262
|
+
});
|
|
263
|
+
logger.debug("📊 Added RAG collection stats to system prompt", {
|
|
264
|
+
agent: agent.name,
|
|
265
|
+
collectionsWithContributions: collections.filter(c => c.agentDocCount > 0).length,
|
|
266
|
+
totalCollections: collections.length,
|
|
267
|
+
});
|
|
280
268
|
}
|
|
281
269
|
} catch (error) {
|
|
282
|
-
|
|
283
|
-
logger.debug("Could not fetch RAG collection stats for prompt:", error);
|
|
270
|
+
logger.debug("Could not get RAG collection stats:", error);
|
|
284
271
|
}
|
|
285
272
|
}
|
|
286
273
|
|
|
@@ -654,6 +641,9 @@ async function buildMainSystemPrompt(options: BuildSystemPromptOptions): Promise
|
|
|
654
641
|
// Add relay configuration context
|
|
655
642
|
systemPromptBuilder.add("relay-configuration", {});
|
|
656
643
|
|
|
644
|
+
// Add process metrics (PID, uptime, CPU/memory usage)
|
|
645
|
+
systemPromptBuilder.add("process-metrics", {});
|
|
646
|
+
|
|
657
647
|
// Add meta-project context (other projects this agent belongs to)
|
|
658
648
|
// This gives agents cross-project awareness without overwhelming them
|
|
659
649
|
systemPromptBuilder.add("meta-project-context", {
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import type { StoredAgent } from "@/agents/AgentStorage";
|
|
2
2
|
import { agentStorage } from "@/agents/AgentStorage";
|
|
3
3
|
import { NDKAgentDefinition } from "@/events/NDKAgentDefinition";
|
|
4
|
+
import { isValidCategory } from "@/agents/role-categories";
|
|
4
5
|
import { logger } from "@/utils/logger";
|
|
5
6
|
import type NDK from "@nostr-dev-kit/ndk";
|
|
6
7
|
import type { NDKEvent, NDKFilter, NDKSubscription } from "@nostr-dev-kit/ndk";
|
|
@@ -545,6 +546,13 @@ export class AgentDefinitionMonitor {
|
|
|
545
546
|
changedFields.push("role");
|
|
546
547
|
}
|
|
547
548
|
|
|
549
|
+
const rawCategory = agentDef.category || undefined;
|
|
550
|
+
const newCategory = rawCategory && isValidCategory(rawCategory) ? rawCategory : undefined;
|
|
551
|
+
if (newCategory !== storedAgent.category) {
|
|
552
|
+
storedAgent.category = newCategory;
|
|
553
|
+
changedFields.push("category");
|
|
554
|
+
}
|
|
555
|
+
|
|
548
556
|
const newDescription = agentDef.description || undefined;
|
|
549
557
|
if (newDescription !== storedAgent.description) {
|
|
550
558
|
storedAgent.description = newDescription;
|
|
@@ -367,6 +367,40 @@ export class ConfigService {
|
|
|
367
367
|
return loadedConfig.llms.configurations[name];
|
|
368
368
|
}
|
|
369
369
|
|
|
370
|
+
/**
|
|
371
|
+
* Get all resolved LLM configurations, skipping meta models.
|
|
372
|
+
* Returns the default config first, followed by the rest in declaration order.
|
|
373
|
+
*/
|
|
374
|
+
getAllLLMConfigs(): LLMConfiguration[] {
|
|
375
|
+
if (!this.loadedConfig) {
|
|
376
|
+
throw new Error("Config not loaded. Call loadConfig() first.");
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
const llms = this.loadedConfig.llms;
|
|
380
|
+
const configs: LLMConfiguration[] = [];
|
|
381
|
+
const seen = new Set<string>();
|
|
382
|
+
|
|
383
|
+
// Default first
|
|
384
|
+
if (llms.default && llms.configurations[llms.default]) {
|
|
385
|
+
const config = llms.configurations[llms.default];
|
|
386
|
+
if (!isMetaModelConfiguration(config)) {
|
|
387
|
+
configs.push(config);
|
|
388
|
+
seen.add(`${config.provider}:${config.model}`);
|
|
389
|
+
}
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
// Then the rest, deduplicating by provider:model
|
|
393
|
+
for (const config of Object.values(llms.configurations)) {
|
|
394
|
+
if (isMetaModelConfiguration(config)) continue;
|
|
395
|
+
const key = `${config.provider}:${config.model}`;
|
|
396
|
+
if (seen.has(key)) continue;
|
|
397
|
+
seen.add(key);
|
|
398
|
+
configs.push(config);
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
return configs;
|
|
402
|
+
}
|
|
403
|
+
|
|
370
404
|
/**
|
|
371
405
|
* Check if a configuration name refers to a meta model.
|
|
372
406
|
*/
|
|
@@ -85,7 +85,13 @@ export class PubkeyService {
|
|
|
85
85
|
* Uses AgentRegistry's getAgentByPubkey for efficient O(1) lookup.
|
|
86
86
|
*/
|
|
87
87
|
private getAgentSlug(pubkey: Hexpubkey): string | undefined {
|
|
88
|
-
|
|
88
|
+
let projectCtx;
|
|
89
|
+
try {
|
|
90
|
+
projectCtx = getProjectContext();
|
|
91
|
+
} catch {
|
|
92
|
+
// Name resolution must remain available even outside a project-scoped context
|
|
93
|
+
return undefined;
|
|
94
|
+
}
|
|
89
95
|
|
|
90
96
|
// Use direct pubkey lookup from AgentRegistry (O(1) instead of O(n))
|
|
91
97
|
const agent = projectCtx.getAgentByPubkey(pubkey);
|
|
@@ -2,6 +2,7 @@ import type { LLMService } from "@/llm/service";
|
|
|
2
2
|
import { shortenConversationId } from "@/utils/conversation-id";
|
|
3
3
|
import type { ConversationStore } from "@/conversations/ConversationStore";
|
|
4
4
|
import type { ConversationEntry } from "@/conversations/types";
|
|
5
|
+
import { renderConversationXml } from "@/conversations/formatters/utils/conversation-transcript-formatter";
|
|
5
6
|
import { trace, SpanStatusCode, type Span } from "@opentelemetry/api";
|
|
6
7
|
import { logger } from "@/utils/logger";
|
|
7
8
|
import { config } from "@/services/ConfigService";
|
|
@@ -18,9 +19,52 @@ import {
|
|
|
18
19
|
validateSegmentsForEntries,
|
|
19
20
|
applySegmentsToEntries,
|
|
20
21
|
createFallbackSegmentForEntries,
|
|
22
|
+
computeTokenAwareWindowSize,
|
|
21
23
|
} from "./compression-utils.js";
|
|
22
24
|
|
|
23
25
|
const tracer = trace.getTracer("tenex.compression");
|
|
26
|
+
const CONTEXT_SEGMENT_LIMIT = 3;
|
|
27
|
+
|
|
28
|
+
function buildContextPreamble(existingSegments: CompressionSegment[]): string {
|
|
29
|
+
if (existingSegments.length === 0) {
|
|
30
|
+
return "";
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
const recentSegments = existingSegments.slice(-CONTEXT_SEGMENT_LIMIT);
|
|
34
|
+
const contextLines = recentSegments.map(
|
|
35
|
+
(segment, index) => `[Previous context ${index + 1}]: ${segment.compressed}`
|
|
36
|
+
);
|
|
37
|
+
|
|
38
|
+
return `Previous conversation context (already compressed):\n${contextLines.join("\n")}\n\n`;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
function buildCompressionPrompt(
|
|
42
|
+
transcriptXml: string,
|
|
43
|
+
firstShortId: string,
|
|
44
|
+
lastShortId: string,
|
|
45
|
+
existingSegments: CompressionSegment[]
|
|
46
|
+
): string {
|
|
47
|
+
const contextPreamble = buildContextPreamble(existingSegments);
|
|
48
|
+
|
|
49
|
+
return `You are compressing conversation history represented as XML. Analyze the conversation and create 1-3 compressed segments that preserve key information while being concise.
|
|
50
|
+
|
|
51
|
+
For each segment, provide:
|
|
52
|
+
- fromEventId: starting message id from XML (the id attribute)
|
|
53
|
+
- toEventId: ending message id from XML (the id attribute)
|
|
54
|
+
- compressed: a concise summary (2-4 sentences) of the key points
|
|
55
|
+
|
|
56
|
+
Rules:
|
|
57
|
+
- Preserve attribution: who said or did what.
|
|
58
|
+
- Preserve recipient targeting when present.
|
|
59
|
+
- Preserve temporal flow using the time="+seconds" indicators and conversation t0.
|
|
60
|
+
- Use IDs exactly as shown in XML id attributes (do not invent IDs).
|
|
61
|
+
- The first segment must start at id "${firstShortId}" and the last segment must end at id "${lastShortId}".
|
|
62
|
+
|
|
63
|
+
${contextPreamble}Messages to compress:
|
|
64
|
+
${transcriptXml}
|
|
65
|
+
|
|
66
|
+
Create segments that group related topics together. Preserve important decisions, errors, and outcomes.`;
|
|
67
|
+
}
|
|
24
68
|
|
|
25
69
|
/**
|
|
26
70
|
* CompressionService - Orchestrates conversation history compression.
|
|
@@ -153,17 +197,57 @@ export class CompressionService {
|
|
|
153
197
|
conversationId,
|
|
154
198
|
entries,
|
|
155
199
|
compressionConfig.slidingWindowSize,
|
|
156
|
-
span
|
|
200
|
+
span,
|
|
201
|
+
effectiveBudget
|
|
157
202
|
);
|
|
158
203
|
}
|
|
159
204
|
span.setStatus({ code: SpanStatusCode.OK });
|
|
160
205
|
return;
|
|
161
206
|
}
|
|
162
207
|
|
|
208
|
+
// Compute range impact before attempting LLM call
|
|
209
|
+
const rangeEntries = entries.slice(range.startIndex, range.endIndex);
|
|
210
|
+
const rangeTokens = estimateTokensFromEntries(rangeEntries);
|
|
211
|
+
|
|
212
|
+
// Skip LLM when range can't meaningfully help
|
|
213
|
+
if (blocking) {
|
|
214
|
+
const tokenOverage = currentTokens - effectiveBudget;
|
|
215
|
+
if (rangeTokens < tokenOverage) {
|
|
216
|
+
// Even compressing the entire range to zero can't close the gap
|
|
217
|
+
span.addEvent("compression.skip_to_fallback", {
|
|
218
|
+
"reason": "range_too_small_for_overage",
|
|
219
|
+
"range.tokens": rangeTokens,
|
|
220
|
+
"token.overage": tokenOverage,
|
|
221
|
+
});
|
|
222
|
+
await this.useFallback(
|
|
223
|
+
conversationId,
|
|
224
|
+
entries,
|
|
225
|
+
compressionConfig.slidingWindowSize,
|
|
226
|
+
span,
|
|
227
|
+
effectiveBudget
|
|
228
|
+
);
|
|
229
|
+
span.setStatus({ code: SpanStatusCode.OK });
|
|
230
|
+
return;
|
|
231
|
+
}
|
|
232
|
+
} else {
|
|
233
|
+
// Proactive mode: skip if range is tiny (not worth an LLM call)
|
|
234
|
+
if (rangeTokens < 500) {
|
|
235
|
+
span.addEvent("compression.skip_proactive", {
|
|
236
|
+
"reason": "range_too_small",
|
|
237
|
+
"range.tokens": rangeTokens,
|
|
238
|
+
});
|
|
239
|
+
span.setStatus({ code: SpanStatusCode.OK });
|
|
240
|
+
return;
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
|
|
163
244
|
// Attempt LLM compression
|
|
164
245
|
try {
|
|
165
|
-
const
|
|
166
|
-
|
|
246
|
+
const newSegments = await this.compressEntries(
|
|
247
|
+
conversationId,
|
|
248
|
+
rangeEntries,
|
|
249
|
+
existingSegments
|
|
250
|
+
);
|
|
167
251
|
|
|
168
252
|
// Emit telemetry for successful summary generation
|
|
169
253
|
span.addEvent("compression.summary_generated", {
|
|
@@ -195,7 +279,8 @@ export class CompressionService {
|
|
|
195
279
|
conversationId,
|
|
196
280
|
entries,
|
|
197
281
|
compressionConfig.slidingWindowSize,
|
|
198
|
-
span
|
|
282
|
+
span,
|
|
283
|
+
effectiveBudget
|
|
199
284
|
);
|
|
200
285
|
}
|
|
201
286
|
return;
|
|
@@ -239,7 +324,8 @@ export class CompressionService {
|
|
|
239
324
|
conversationId,
|
|
240
325
|
entries,
|
|
241
326
|
compressionConfig.slidingWindowSize,
|
|
242
|
-
span
|
|
327
|
+
span,
|
|
328
|
+
effectiveBudget
|
|
243
329
|
);
|
|
244
330
|
} else {
|
|
245
331
|
// Proactive mode: fail silently, don't throw
|
|
@@ -263,7 +349,9 @@ export class CompressionService {
|
|
|
263
349
|
* Compress a range of entries using LLM.
|
|
264
350
|
*/
|
|
265
351
|
private async compressEntries(
|
|
266
|
-
|
|
352
|
+
conversationId: string,
|
|
353
|
+
entries: ConversationEntry[],
|
|
354
|
+
existingSegments: CompressionSegment[]
|
|
267
355
|
): Promise<CompressionSegment[]> {
|
|
268
356
|
return tracer.startActiveSpan(
|
|
269
357
|
"compression.llm_compress",
|
|
@@ -271,71 +359,30 @@ export class CompressionService {
|
|
|
271
359
|
try {
|
|
272
360
|
span.setAttribute("entries.count", entries.length);
|
|
273
361
|
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
}
|
|
283
|
-
|
|
284
|
-
// Add tool payload summary for tool-call/tool-result entries
|
|
285
|
-
if (e.toolData && e.toolData.length > 0) {
|
|
286
|
-
const toolSummary = e.toolData
|
|
287
|
-
.map((tool) => {
|
|
288
|
-
if ('toolName' in tool) {
|
|
289
|
-
// ToolCallPart
|
|
290
|
-
return `Tool: ${tool.toolName}`;
|
|
291
|
-
} else if ('toolCallId' in tool) {
|
|
292
|
-
// ToolResultPart - cast to any to avoid type narrowing issues
|
|
293
|
-
const toolResult = tool as any;
|
|
294
|
-
const resultPreview = typeof toolResult.result === 'string'
|
|
295
|
-
? toolResult.result.substring(0, 100)
|
|
296
|
-
: JSON.stringify(toolResult.result).substring(0, 100);
|
|
297
|
-
return `Result: ${resultPreview}${resultPreview.length >= 100 ? '...' : ''}`;
|
|
298
|
-
}
|
|
299
|
-
return '';
|
|
300
|
-
})
|
|
301
|
-
.filter(Boolean)
|
|
302
|
-
.join(', ');
|
|
303
|
-
|
|
304
|
-
if (toolSummary) {
|
|
305
|
-
formatted += ` [${toolSummary}]`;
|
|
306
|
-
}
|
|
307
|
-
}
|
|
308
|
-
|
|
309
|
-
return formatted;
|
|
310
|
-
})
|
|
311
|
-
.join("\n\n");
|
|
312
|
-
|
|
313
|
-
const eventIds = entries
|
|
314
|
-
.filter((e) => e.eventId)
|
|
315
|
-
.map((e) => e.eventId!);
|
|
316
|
-
|
|
317
|
-
if (eventIds.length === 0) {
|
|
362
|
+
const {
|
|
363
|
+
xml: transcriptXml,
|
|
364
|
+
shortIdToEventId,
|
|
365
|
+
firstShortId,
|
|
366
|
+
lastShortId,
|
|
367
|
+
} = renderConversationXml(entries, { conversationId });
|
|
368
|
+
|
|
369
|
+
if (shortIdToEventId.size === 0 || !firstShortId || !lastShortId) {
|
|
318
370
|
throw new Error("No eventIds found in entries to compress");
|
|
319
371
|
}
|
|
320
372
|
|
|
373
|
+
const prompt = buildCompressionPrompt(
|
|
374
|
+
transcriptXml,
|
|
375
|
+
firstShortId,
|
|
376
|
+
lastShortId,
|
|
377
|
+
existingSegments
|
|
378
|
+
);
|
|
379
|
+
|
|
321
380
|
// Call LLM to compress
|
|
322
381
|
const result = await this.effectiveLlmService.generateObject(
|
|
323
382
|
[
|
|
324
383
|
{
|
|
325
384
|
role: "user",
|
|
326
|
-
content:
|
|
327
|
-
|
|
328
|
-
For each segment, provide:
|
|
329
|
-
- fromEventId: starting message event ID
|
|
330
|
-
- toEventId: ending message event ID
|
|
331
|
-
- compressed: a concise summary (2-4 sentences) of the key points
|
|
332
|
-
|
|
333
|
-
Messages to compress:
|
|
334
|
-
${formattedEntries}
|
|
335
|
-
|
|
336
|
-
Event IDs in order: ${eventIds.join(", ")}
|
|
337
|
-
|
|
338
|
-
Create segments that group related topics together. Preserve important decisions, errors, and outcomes.`,
|
|
385
|
+
content: prompt,
|
|
339
386
|
},
|
|
340
387
|
],
|
|
341
388
|
CompressionSegmentsSchema
|
|
@@ -343,13 +390,17 @@ Create segments that group related topics together. Preserve important decisions
|
|
|
343
390
|
|
|
344
391
|
// Convert LLM output to CompressionSegment format
|
|
345
392
|
const segments: CompressionSegment[] = result.object.map(
|
|
346
|
-
(seg: CompressionSegmentInput) =>
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
393
|
+
(seg: CompressionSegmentInput) => {
|
|
394
|
+
const fromId = seg.fromEventId.trim();
|
|
395
|
+
const toId = seg.toEventId.trim();
|
|
396
|
+
return {
|
|
397
|
+
fromEventId: shortIdToEventId.get(fromId) ?? fromId,
|
|
398
|
+
toEventId: shortIdToEventId.get(toId) ?? toId,
|
|
399
|
+
compressed: seg.compressed,
|
|
400
|
+
createdAt: Date.now(),
|
|
401
|
+
model: this.effectiveLlmService.model,
|
|
402
|
+
};
|
|
403
|
+
}
|
|
353
404
|
);
|
|
354
405
|
|
|
355
406
|
span.setAttribute("segments.count", segments.length);
|
|
@@ -375,19 +426,27 @@ Create segments that group related topics together. Preserve important decisions
|
|
|
375
426
|
conversationId: string,
|
|
376
427
|
entries: ConversationEntry[],
|
|
377
428
|
windowSize: number,
|
|
378
|
-
span: Span
|
|
429
|
+
span: Span,
|
|
430
|
+
tokenBudget: number
|
|
379
431
|
): Promise<void> {
|
|
432
|
+
const tokenAwareWindowSize = computeTokenAwareWindowSize(entries, tokenBudget);
|
|
433
|
+
const effectiveWindowSize = Math.min(windowSize, tokenAwareWindowSize);
|
|
434
|
+
|
|
380
435
|
span.setAttribute("fallback.used", true);
|
|
381
|
-
span.setAttribute("fallback.
|
|
436
|
+
span.setAttribute("fallback.configured_window", windowSize);
|
|
437
|
+
span.setAttribute("fallback.token_aware_window", tokenAwareWindowSize);
|
|
438
|
+
span.setAttribute("fallback.effective_window", effectiveWindowSize);
|
|
382
439
|
|
|
383
440
|
logger.warn("Compression fallback triggered - using sliding window truncation", {
|
|
384
441
|
conversationId,
|
|
385
442
|
entriesCount: entries.length,
|
|
386
|
-
windowSize,
|
|
443
|
+
configuredWindow: windowSize,
|
|
444
|
+
tokenAwareWindow: tokenAwareWindowSize,
|
|
445
|
+
effectiveWindow: effectiveWindowSize,
|
|
387
446
|
});
|
|
388
447
|
|
|
389
448
|
// Delegate to pure utility function
|
|
390
|
-
const fallbackSegment = createFallbackSegmentForEntries(entries,
|
|
449
|
+
const fallbackSegment = createFallbackSegmentForEntries(entries, effectiveWindowSize);
|
|
391
450
|
|
|
392
451
|
if (!fallbackSegment) {
|
|
393
452
|
// Can't create a valid segment (too few entries or insufficient event IDs)
|