@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
|
@@ -1,10 +1,8 @@
|
|
|
1
1
|
import { trace } from "@opentelemetry/api";
|
|
2
2
|
import { EventEmitter, type DefaultEventMap } from "tseep";
|
|
3
|
-
import { getPubkeyService } from "@/services/PubkeyService";
|
|
4
3
|
import { INJECTION_ABORT_REASON, llmOpsRegistry } from "@/services/LLMOperationsRegistry";
|
|
5
4
|
import { shortenConversationId } from "@/utils/conversation-id";
|
|
6
5
|
import { logger } from "@/utils/logger";
|
|
7
|
-
import { PREFIX_LENGTH } from "@/utils/nostr-entity-parser";
|
|
8
6
|
import { ConversationStore } from "@/conversations/ConversationStore";
|
|
9
7
|
// Note: FullEventId type is available via @/types/event-ids for future typed method signatures
|
|
10
8
|
import type {
|
|
@@ -1499,181 +1497,6 @@ export class RALRegistry extends EventEmitter<RALRegistryEvents> {
|
|
|
1499
1497
|
return rals.find(ral => ral.queuedInjections.length > 0);
|
|
1500
1498
|
}
|
|
1501
1499
|
|
|
1502
|
-
/**
|
|
1503
|
-
* Build a message containing delegation results for injection into the RAL.
|
|
1504
|
-
* Shows complete conversation transcript for each delegation.
|
|
1505
|
-
* Uses shortened delegation IDs (PREFIX_LENGTH chars) for display; agents can use
|
|
1506
|
-
* these prefixes directly with delegate_followup which will resolve them.
|
|
1507
|
-
* Format: [@sender -> @recipient]: message content
|
|
1508
|
-
*/
|
|
1509
|
-
/**
|
|
1510
|
-
* Helper to format transcript with error handling for name resolution.
|
|
1511
|
-
*/
|
|
1512
|
-
private async formatTranscript(transcript: DelegationMessage[]): Promise<string[]> {
|
|
1513
|
-
const pubkeyService = getPubkeyService();
|
|
1514
|
-
const lines: string[] = [];
|
|
1515
|
-
|
|
1516
|
-
for (const msg of transcript) {
|
|
1517
|
-
try {
|
|
1518
|
-
const senderName = await pubkeyService.getName(msg.senderPubkey);
|
|
1519
|
-
const recipientName = await pubkeyService.getName(msg.recipientPubkey);
|
|
1520
|
-
lines.push(`[@${senderName} -> @${recipientName}]: ${msg.content}`);
|
|
1521
|
-
} catch (error) {
|
|
1522
|
-
// Fallback to shortened pubkeys on error
|
|
1523
|
-
const senderFallback = msg.senderPubkey.substring(0, 12);
|
|
1524
|
-
const recipientFallback = msg.recipientPubkey.substring(0, 12);
|
|
1525
|
-
lines.push(`[@${senderFallback} -> @${recipientFallback}]: ${msg.content}`);
|
|
1526
|
-
}
|
|
1527
|
-
}
|
|
1528
|
-
|
|
1529
|
-
return lines;
|
|
1530
|
-
}
|
|
1531
|
-
|
|
1532
|
-
/**
|
|
1533
|
-
* Helper to render a list of pending delegations with error handling.
|
|
1534
|
-
*/
|
|
1535
|
-
private async renderPendingList(pending: PendingDelegation[]): Promise<string[]> {
|
|
1536
|
-
if (pending.length === 0) {
|
|
1537
|
-
return [];
|
|
1538
|
-
}
|
|
1539
|
-
|
|
1540
|
-
const pubkeyService = getPubkeyService();
|
|
1541
|
-
const lines: string[] = [];
|
|
1542
|
-
|
|
1543
|
-
lines.push("## Still Pending");
|
|
1544
|
-
for (const p of pending) {
|
|
1545
|
-
try {
|
|
1546
|
-
const recipientName = await pubkeyService.getName(p.recipientPubkey);
|
|
1547
|
-
lines.push(`- @${recipientName} (${p.delegationConversationId.substring(0, PREFIX_LENGTH)})`);
|
|
1548
|
-
} catch (error) {
|
|
1549
|
-
const fallbackName = p.recipientPubkey.substring(0, 12);
|
|
1550
|
-
lines.push(`- @${fallbackName} (${p.delegationConversationId.substring(0, PREFIX_LENGTH)})`);
|
|
1551
|
-
}
|
|
1552
|
-
}
|
|
1553
|
-
lines.push("");
|
|
1554
|
-
|
|
1555
|
-
return lines;
|
|
1556
|
-
}
|
|
1557
|
-
|
|
1558
|
-
/**
|
|
1559
|
-
* Helper to render delegation header (agent name + ID) with error handling.
|
|
1560
|
-
*/
|
|
1561
|
-
private async renderDelegationHeader(
|
|
1562
|
-
recipientPubkey: string,
|
|
1563
|
-
conversationId: string,
|
|
1564
|
-
statusText: string
|
|
1565
|
-
): Promise<string[]> {
|
|
1566
|
-
const pubkeyService = getPubkeyService();
|
|
1567
|
-
const lines: string[] = [];
|
|
1568
|
-
|
|
1569
|
-
try {
|
|
1570
|
-
const recipientName = await pubkeyService.getName(recipientPubkey);
|
|
1571
|
-
lines.push(`**@${recipientName} ${statusText}**`);
|
|
1572
|
-
} catch (error) {
|
|
1573
|
-
const fallbackName = recipientPubkey.substring(0, 12);
|
|
1574
|
-
lines.push(`**@${fallbackName} ${statusText}**`);
|
|
1575
|
-
}
|
|
1576
|
-
|
|
1577
|
-
lines.push("");
|
|
1578
|
-
lines.push(`## Delegation ID: ${conversationId.substring(0, PREFIX_LENGTH)}`);
|
|
1579
|
-
|
|
1580
|
-
return lines;
|
|
1581
|
-
}
|
|
1582
|
-
|
|
1583
|
-
/**
|
|
1584
|
-
* Build a message for completed delegations.
|
|
1585
|
-
* Shows the agent name, conversation ID, and full transcript.
|
|
1586
|
-
*/
|
|
1587
|
-
async buildDelegationResultsMessage(
|
|
1588
|
-
completions: CompletedDelegation[],
|
|
1589
|
-
pending: PendingDelegation[] = []
|
|
1590
|
-
): Promise<string> {
|
|
1591
|
-
if (completions.length === 0) {
|
|
1592
|
-
return "";
|
|
1593
|
-
}
|
|
1594
|
-
|
|
1595
|
-
const lines: string[] = [];
|
|
1596
|
-
|
|
1597
|
-
lines.push("# DELEGATION COMPLETED");
|
|
1598
|
-
lines.push("");
|
|
1599
|
-
|
|
1600
|
-
for (const c of completions) {
|
|
1601
|
-
lines.push(...await this.renderDelegationHeader(
|
|
1602
|
-
c.recipientPubkey,
|
|
1603
|
-
c.delegationConversationId,
|
|
1604
|
-
"has finished and returned their final response."
|
|
1605
|
-
));
|
|
1606
|
-
lines.push("");
|
|
1607
|
-
lines.push("### Transcript:");
|
|
1608
|
-
lines.push(...await this.formatTranscript(c.transcript));
|
|
1609
|
-
lines.push("");
|
|
1610
|
-
}
|
|
1611
|
-
|
|
1612
|
-
// Show pending delegations if any remain
|
|
1613
|
-
if (pending.length > 0) {
|
|
1614
|
-
lines.push(...await this.renderPendingList(pending));
|
|
1615
|
-
}
|
|
1616
|
-
|
|
1617
|
-
return lines.join("\n");
|
|
1618
|
-
}
|
|
1619
|
-
|
|
1620
|
-
/**
|
|
1621
|
-
* Build a message for aborted delegations.
|
|
1622
|
-
* Shows the agent name, conversation ID, abort details, and partial transcript.
|
|
1623
|
-
* Format matches buildDelegationResultsMessage for consistency.
|
|
1624
|
-
*/
|
|
1625
|
-
async buildDelegationAbortMessage(
|
|
1626
|
-
abortedDelegations: CompletedDelegation[],
|
|
1627
|
-
pending: PendingDelegation[] = []
|
|
1628
|
-
): Promise<string> {
|
|
1629
|
-
if (abortedDelegations.length === 0) {
|
|
1630
|
-
return "";
|
|
1631
|
-
}
|
|
1632
|
-
|
|
1633
|
-
const lines: string[] = [];
|
|
1634
|
-
|
|
1635
|
-
// Header indicating abort event
|
|
1636
|
-
lines.push("# DELEGATION ABORTED");
|
|
1637
|
-
lines.push("");
|
|
1638
|
-
|
|
1639
|
-
// Format each aborted delegation
|
|
1640
|
-
for (const c of abortedDelegations) {
|
|
1641
|
-
if (c.status !== "aborted") continue; // Type guard for discriminated union
|
|
1642
|
-
|
|
1643
|
-
lines.push(...await this.renderDelegationHeader(
|
|
1644
|
-
c.recipientPubkey,
|
|
1645
|
-
c.delegationConversationId,
|
|
1646
|
-
"was aborted and did not complete their task."
|
|
1647
|
-
));
|
|
1648
|
-
|
|
1649
|
-
// Add abort-specific metadata with error handling for timestamp
|
|
1650
|
-
try {
|
|
1651
|
-
lines.push(`**Aborted at:** ${new Date(c.completedAt).toISOString()}`);
|
|
1652
|
-
} catch {
|
|
1653
|
-
lines.push(`**Aborted at:** (invalid timestamp)`);
|
|
1654
|
-
}
|
|
1655
|
-
lines.push(`**Reason:** ${c.abortReason}`);
|
|
1656
|
-
lines.push("");
|
|
1657
|
-
|
|
1658
|
-
// Show partial transcript if available
|
|
1659
|
-
if (c.transcript && c.transcript.length > 0) {
|
|
1660
|
-
lines.push("### Partial Progress:");
|
|
1661
|
-
lines.push(...await this.formatTranscript(c.transcript));
|
|
1662
|
-
} else {
|
|
1663
|
-
lines.push("### Partial Progress:");
|
|
1664
|
-
lines.push("(No messages exchanged before abort)");
|
|
1665
|
-
}
|
|
1666
|
-
lines.push("");
|
|
1667
|
-
}
|
|
1668
|
-
|
|
1669
|
-
// Show remaining pending delegations if any
|
|
1670
|
-
if (pending.length > 0) {
|
|
1671
|
-
lines.push(...await this.renderPendingList(pending));
|
|
1672
|
-
}
|
|
1673
|
-
|
|
1674
|
-
return lines.join("\n");
|
|
1675
|
-
}
|
|
1676
|
-
|
|
1677
1500
|
/**
|
|
1678
1501
|
* Determine if a new message should wake up an execution.
|
|
1679
1502
|
*
|
|
@@ -2635,13 +2458,17 @@ export class RALRegistry extends EventEmitter<RALRegistryEvents> {
|
|
|
2635
2458
|
/**
|
|
2636
2459
|
* Check if there's any outstanding work for a conversation that would prevent finalization.
|
|
2637
2460
|
*
|
|
2638
|
-
* This method consolidates checking for
|
|
2461
|
+
* This method consolidates checking for:
|
|
2639
2462
|
* 1. Queued injections (messages waiting to be processed in the next LLM step)
|
|
2640
2463
|
* 2. Pending delegations (delegations that haven't completed yet)
|
|
2464
|
+
* 3. Completed delegations (delegations that completed but whose results haven't
|
|
2465
|
+
* been incorporated into the agent's messages via resolveRAL yet)
|
|
2641
2466
|
*
|
|
2642
|
-
*
|
|
2643
|
-
* (
|
|
2644
|
-
*
|
|
2467
|
+
* Checking completed delegations is critical for fast-completing delegations:
|
|
2468
|
+
* recordCompletion() moves delegations from pending→completed immediately (no debounce),
|
|
2469
|
+
* but the executor only processes them via resolveRAL() after the debounce fires.
|
|
2470
|
+
* Without this check, the executor sees pendingDelegations=0 and finalizes prematurely,
|
|
2471
|
+
* clearing the RAL before the completed delegation can be processed.
|
|
2645
2472
|
*
|
|
2646
2473
|
* @param agentPubkey - The agent's pubkey
|
|
2647
2474
|
* @param conversationId - The conversation ID
|
|
@@ -2657,6 +2484,7 @@ export class RALRegistry extends EventEmitter<RALRegistryEvents> {
|
|
|
2657
2484
|
details: {
|
|
2658
2485
|
queuedInjections: number;
|
|
2659
2486
|
pendingDelegations: number;
|
|
2487
|
+
completedDelegations: number;
|
|
2660
2488
|
};
|
|
2661
2489
|
} {
|
|
2662
2490
|
const ral = this.getRAL(agentPubkey, conversationId, ralNumber);
|
|
@@ -2669,13 +2497,23 @@ export class RALRegistry extends EventEmitter<RALRegistryEvents> {
|
|
|
2669
2497
|
ralNumber
|
|
2670
2498
|
).length;
|
|
2671
2499
|
|
|
2672
|
-
//
|
|
2500
|
+
// Count completed delegations that haven't been consumed by resolveRAL yet.
|
|
2501
|
+
// These are delegations where recordCompletion() has run but the executor hasn't
|
|
2502
|
+
// processed them into conversation markers yet.
|
|
2503
|
+
const completedDelegations = this.getConversationCompletedDelegations(
|
|
2504
|
+
agentPubkey,
|
|
2505
|
+
conversationId,
|
|
2506
|
+
ralNumber
|
|
2507
|
+
).length;
|
|
2508
|
+
|
|
2509
|
+
// If RAL doesn't exist, we can't have queued injections but may still have delegations
|
|
2673
2510
|
if (!ral) {
|
|
2674
|
-
const hasWork = pendingDelegations > 0;
|
|
2511
|
+
const hasWork = pendingDelegations > 0 || completedDelegations > 0;
|
|
2675
2512
|
if (hasWork) {
|
|
2676
2513
|
trace.getActiveSpan()?.addEvent("ral.outstanding_work_no_ral", {
|
|
2677
2514
|
"ral.number": ralNumber,
|
|
2678
2515
|
"outstanding.pending_delegations": pendingDelegations,
|
|
2516
|
+
"outstanding.completed_delegations": completedDelegations,
|
|
2679
2517
|
"agent.pubkey": agentPubkey.substring(0, 12),
|
|
2680
2518
|
"conversation.id": shortenConversationId(conversationId),
|
|
2681
2519
|
});
|
|
@@ -2685,6 +2523,7 @@ export class RALRegistry extends EventEmitter<RALRegistryEvents> {
|
|
|
2685
2523
|
details: {
|
|
2686
2524
|
queuedInjections: 0,
|
|
2687
2525
|
pendingDelegations,
|
|
2526
|
+
completedDelegations,
|
|
2688
2527
|
},
|
|
2689
2528
|
};
|
|
2690
2529
|
}
|
|
@@ -2692,7 +2531,7 @@ export class RALRegistry extends EventEmitter<RALRegistryEvents> {
|
|
|
2692
2531
|
// Count queued injections from the RAL entry
|
|
2693
2532
|
const queuedInjections = ral.queuedInjections.length;
|
|
2694
2533
|
|
|
2695
|
-
const hasWork = queuedInjections > 0 || pendingDelegations > 0;
|
|
2534
|
+
const hasWork = queuedInjections > 0 || pendingDelegations > 0 || completedDelegations > 0;
|
|
2696
2535
|
|
|
2697
2536
|
// Add telemetry for debugging race conditions
|
|
2698
2537
|
if (hasWork) {
|
|
@@ -2700,6 +2539,7 @@ export class RALRegistry extends EventEmitter<RALRegistryEvents> {
|
|
|
2700
2539
|
"ral.number": ralNumber,
|
|
2701
2540
|
"outstanding.queued_injections": queuedInjections,
|
|
2702
2541
|
"outstanding.pending_delegations": pendingDelegations,
|
|
2542
|
+
"outstanding.completed_delegations": completedDelegations,
|
|
2703
2543
|
"agent.pubkey": agentPubkey.substring(0, 12),
|
|
2704
2544
|
"conversation.id": shortenConversationId(conversationId),
|
|
2705
2545
|
});
|
|
@@ -2710,6 +2550,7 @@ export class RALRegistry extends EventEmitter<RALRegistryEvents> {
|
|
|
2710
2550
|
details: {
|
|
2711
2551
|
queuedInjections,
|
|
2712
2552
|
pendingDelegations,
|
|
2553
|
+
completedDelegations,
|
|
2713
2554
|
},
|
|
2714
2555
|
};
|
|
2715
2556
|
}
|
|
@@ -7,13 +7,13 @@
|
|
|
7
7
|
* Key features:
|
|
8
8
|
* - Project-scoped: reports are tagged with projectId for isolation
|
|
9
9
|
* - Index on write: called from report_write tool after successful publish
|
|
10
|
-
* - Upsert semantics: re-indexing updates existing documents
|
|
11
|
-
* - Graceful degradation: RAG failures don't break report writes
|
|
10
|
+
* - Upsert semantics: re-indexing updates existing documents via bulkUpsert
|
|
12
11
|
* - Nostr remains authoritative source; RAG is just a search layer
|
|
13
12
|
*/
|
|
14
13
|
|
|
15
14
|
import { logger } from "@/utils/logger";
|
|
16
15
|
import { RAGService, type RAGDocument, type RAGQueryResult } from "@/services/rag/RAGService";
|
|
16
|
+
import { buildProjectFilter } from "@/services/search/projectFilter";
|
|
17
17
|
import type { ReportInfo } from "./ReportService";
|
|
18
18
|
|
|
19
19
|
/** Collection name for report embeddings */
|
|
@@ -187,59 +187,43 @@ export class ReportEmbeddingService {
|
|
|
187
187
|
): Promise<boolean> {
|
|
188
188
|
await this.ensureInitialized();
|
|
189
189
|
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
const embeddingContent = this.buildEmbeddingContent(report);
|
|
193
|
-
|
|
194
|
-
if (!embeddingContent.trim()) {
|
|
195
|
-
logger.debug("No content to embed for report", { slug: report.slug });
|
|
196
|
-
return false;
|
|
197
|
-
}
|
|
198
|
-
|
|
199
|
-
// Delete existing document before inserting (upsert semantics)
|
|
200
|
-
try {
|
|
201
|
-
await this.ragService.deleteDocumentById(REPORT_COLLECTION, documentId);
|
|
202
|
-
} catch {
|
|
203
|
-
// Document might not exist - that's fine
|
|
204
|
-
}
|
|
205
|
-
|
|
206
|
-
const document: RAGDocument = {
|
|
207
|
-
id: documentId,
|
|
208
|
-
content: embeddingContent,
|
|
209
|
-
metadata: {
|
|
210
|
-
slug: report.slug,
|
|
211
|
-
projectId,
|
|
212
|
-
title: report.title || "",
|
|
213
|
-
summary: report.summary || "",
|
|
214
|
-
hashtags: report.hashtags,
|
|
215
|
-
agentPubkey,
|
|
216
|
-
agentName: agentName || "",
|
|
217
|
-
type: "report",
|
|
218
|
-
publishedAt: report.publishedAt ?? Math.floor(Date.now() / 1000),
|
|
219
|
-
},
|
|
220
|
-
timestamp: Date.now(),
|
|
221
|
-
source: "report",
|
|
222
|
-
};
|
|
190
|
+
const documentId = this.buildDocumentId(projectId, report.slug);
|
|
191
|
+
const embeddingContent = this.buildEmbeddingContent(report);
|
|
223
192
|
|
|
224
|
-
|
|
193
|
+
if (!embeddingContent.trim()) {
|
|
194
|
+
logger.debug("No content to embed for report", { slug: report.slug });
|
|
195
|
+
return false;
|
|
196
|
+
}
|
|
225
197
|
|
|
226
|
-
|
|
198
|
+
const document: RAGDocument = {
|
|
199
|
+
id: documentId,
|
|
200
|
+
content: embeddingContent,
|
|
201
|
+
metadata: {
|
|
227
202
|
slug: report.slug,
|
|
228
203
|
projectId,
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
204
|
+
title: report.title || "",
|
|
205
|
+
summary: report.summary || "",
|
|
206
|
+
hashtags: report.hashtags,
|
|
207
|
+
agentPubkey,
|
|
208
|
+
agentName: agentName || "",
|
|
209
|
+
type: "report",
|
|
210
|
+
publishedAt: report.publishedAt ?? Math.floor(Date.now() / 1000),
|
|
211
|
+
},
|
|
212
|
+
timestamp: Date.now(),
|
|
213
|
+
source: "report",
|
|
214
|
+
};
|
|
232
215
|
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
216
|
+
// Atomic upsert via mergeInsert — one LanceDB version per chunk
|
|
217
|
+
await this.ragService.bulkUpsert(REPORT_COLLECTION, [document]);
|
|
218
|
+
|
|
219
|
+
logger.info("📝 Report indexed in RAG", {
|
|
220
|
+
slug: report.slug,
|
|
221
|
+
projectId,
|
|
222
|
+
documentId,
|
|
223
|
+
agentName,
|
|
224
|
+
});
|
|
225
|
+
|
|
226
|
+
return true;
|
|
243
227
|
}
|
|
244
228
|
|
|
245
229
|
/**
|
|
@@ -247,40 +231,16 @@ export class ReportEmbeddingService {
|
|
|
247
231
|
* Called when a report is deleted.
|
|
248
232
|
*/
|
|
249
233
|
public async removeReport(slug: string, projectId: string): Promise<void> {
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
logger.debug("Report collection does not exist, nothing to remove", {
|
|
256
|
-
slug,
|
|
257
|
-
projectId,
|
|
258
|
-
});
|
|
259
|
-
return;
|
|
260
|
-
}
|
|
261
|
-
|
|
262
|
-
const documentId = this.buildDocumentId(projectId, slug);
|
|
263
|
-
await this.ragService.deleteDocumentById(REPORT_COLLECTION, documentId);
|
|
264
|
-
logger.info("🗑️ Report removed from RAG", { slug, projectId, documentId });
|
|
265
|
-
} catch (error) {
|
|
266
|
-
logger.debug("Could not remove report from RAG (may not exist)", {
|
|
267
|
-
slug,
|
|
268
|
-
projectId,
|
|
269
|
-
error,
|
|
270
|
-
});
|
|
234
|
+
// Check if the collection exists before attempting deletion.
|
|
235
|
+
// Avoids ensureInitialized() which would create the collection as a side-effect.
|
|
236
|
+
const collections = await this.ragService.listCollections();
|
|
237
|
+
if (!collections.includes(REPORT_COLLECTION)) {
|
|
238
|
+
return;
|
|
271
239
|
}
|
|
272
|
-
}
|
|
273
240
|
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
*/
|
|
278
|
-
private buildProjectFilter(projectId?: string): string | undefined {
|
|
279
|
-
if (!projectId || projectId.toLowerCase() === "all") {
|
|
280
|
-
return undefined;
|
|
281
|
-
}
|
|
282
|
-
const escapedProjectId = projectId.replace(/'/g, "''");
|
|
283
|
-
return `metadata LIKE '%"projectId":"${escapedProjectId}"%'`;
|
|
241
|
+
const documentId = this.buildDocumentId(projectId, slug);
|
|
242
|
+
await this.ragService.deleteDocumentById(REPORT_COLLECTION, documentId);
|
|
243
|
+
logger.info("🗑️ Report removed from RAG", { slug, projectId, documentId });
|
|
284
244
|
}
|
|
285
245
|
|
|
286
246
|
/**
|
|
@@ -298,36 +258,30 @@ export class ReportEmbeddingService {
|
|
|
298
258
|
|
|
299
259
|
const { limit = 10, minScore = 0.3, projectId } = options;
|
|
300
260
|
|
|
301
|
-
|
|
302
|
-
logger.info("🔍 Report semantic search", { query, limit, minScore, projectId });
|
|
261
|
+
logger.info("🔍 Report semantic search", { query, limit, minScore, projectId });
|
|
303
262
|
|
|
304
|
-
|
|
263
|
+
const filter = buildProjectFilter(projectId);
|
|
305
264
|
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
265
|
+
const results = await this.ragService.queryWithFilter(
|
|
266
|
+
REPORT_COLLECTION,
|
|
267
|
+
query,
|
|
268
|
+
limit * 2,
|
|
269
|
+
filter
|
|
270
|
+
);
|
|
312
271
|
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
272
|
+
const searchResults: ReportSearchResult[] = results
|
|
273
|
+
.filter((result: RAGQueryResult) => result.score >= minScore)
|
|
274
|
+
.slice(0, limit)
|
|
275
|
+
.map((result: RAGQueryResult) => this.transformResult(result));
|
|
317
276
|
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
277
|
+
logger.info("✅ Report semantic search complete", {
|
|
278
|
+
query,
|
|
279
|
+
found: searchResults.length,
|
|
280
|
+
limit,
|
|
281
|
+
projectFilter: filter || "none",
|
|
282
|
+
});
|
|
324
283
|
|
|
325
|
-
|
|
326
|
-
} catch (error) {
|
|
327
|
-
const message = error instanceof Error ? error.message : String(error);
|
|
328
|
-
logger.error("Report semantic search failed", { query, error: message });
|
|
329
|
-
return [];
|
|
330
|
-
}
|
|
284
|
+
return searchResults;
|
|
331
285
|
}
|
|
332
286
|
|
|
333
287
|
/**
|
|
@@ -403,12 +357,8 @@ export class ReportEmbeddingService {
|
|
|
403
357
|
* Clear all report embeddings
|
|
404
358
|
*/
|
|
405
359
|
public async clearIndex(): Promise<void> {
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
logger.info("Cleared report embeddings index");
|
|
409
|
-
} catch (error) {
|
|
410
|
-
logger.debug("No report index to clear or error clearing", { error });
|
|
411
|
-
}
|
|
360
|
+
await this.ragService.deleteCollection(REPORT_COLLECTION);
|
|
361
|
+
logger.info("Cleared report embeddings index");
|
|
412
362
|
|
|
413
363
|
this.initialized = false;
|
|
414
364
|
this.initializationPromise = null;
|
|
@@ -9,13 +9,17 @@
|
|
|
9
9
|
* - Parallel queries across all providers
|
|
10
10
|
* - Graceful degradation: one collection failure doesn't block others
|
|
11
11
|
* - Project-scoped isolation via projectId
|
|
12
|
+
* - Scope-aware collection filtering (global/project/personal)
|
|
12
13
|
* - Optional LLM extraction with configurable prompt
|
|
13
14
|
*/
|
|
14
15
|
|
|
15
16
|
import { logger } from "@/utils/logger";
|
|
16
17
|
import { config as configService } from "@/services/ConfigService";
|
|
18
|
+
import { RAGService } from "@/services/rag/RAGService";
|
|
19
|
+
import { RAGCollectionRegistry } from "@/services/rag/RAGCollectionRegistry";
|
|
17
20
|
import { SearchProviderRegistry } from "./SearchProviderRegistry";
|
|
18
|
-
import
|
|
21
|
+
import { GenericCollectionSearchProvider } from "./providers/GenericCollectionSearchProvider";
|
|
22
|
+
import type { SearchOptions, SearchProvider, SearchResult, UnifiedSearchOutput } from "./types";
|
|
19
23
|
|
|
20
24
|
/** Default search parameters */
|
|
21
25
|
const DEFAULT_LIMIT = 10;
|
|
@@ -47,6 +51,7 @@ export class UnifiedSearchService {
|
|
|
47
51
|
minScore = DEFAULT_MIN_SCORE,
|
|
48
52
|
prompt,
|
|
49
53
|
collections,
|
|
54
|
+
agentPubkey,
|
|
50
55
|
} = options;
|
|
51
56
|
|
|
52
57
|
logger.info("🔍 [UnifiedSearch] Starting search", {
|
|
@@ -55,11 +60,12 @@ export class UnifiedSearchService {
|
|
|
55
60
|
limit,
|
|
56
61
|
minScore,
|
|
57
62
|
prompt: prompt ? `${prompt.substring(0, 50)}...` : undefined,
|
|
58
|
-
collections: collections || "all",
|
|
63
|
+
collections: collections || "all (scope-aware)",
|
|
64
|
+
agentPubkey: agentPubkey ? `${agentPubkey.substring(0, 12)}...` : undefined,
|
|
59
65
|
});
|
|
60
66
|
|
|
61
|
-
//
|
|
62
|
-
const providers = this.
|
|
67
|
+
// Resolve providers: static (specialized) + dynamic (generic for discovered RAG collections)
|
|
68
|
+
const providers = await this.resolveProviders(collections, projectId, agentPubkey);
|
|
63
69
|
if (providers.length === 0) {
|
|
64
70
|
logger.warn("[UnifiedSearch] No search providers available");
|
|
65
71
|
return {
|
|
@@ -136,6 +142,111 @@ export class UnifiedSearchService {
|
|
|
136
142
|
};
|
|
137
143
|
}
|
|
138
144
|
|
|
145
|
+
/**
|
|
146
|
+
* Resolve all providers to query: specialized (from registry) + generic
|
|
147
|
+
* (dynamically created for any RAG collections not covered by a specialized provider).
|
|
148
|
+
*
|
|
149
|
+
* When `requestedCollections` is provided, only those are returned (explicit choice,
|
|
150
|
+
* no scope filtering). When absent, scope-aware filtering is applied.
|
|
151
|
+
*
|
|
152
|
+
* @param requestedCollections - Optional explicit collection filter (bypasses scoping)
|
|
153
|
+
* @param projectId - Current project ID for scope filtering
|
|
154
|
+
* @param agentPubkey - Current agent pubkey for personal scope filtering
|
|
155
|
+
*/
|
|
156
|
+
private async resolveProviders(
|
|
157
|
+
requestedCollections: string[] | undefined,
|
|
158
|
+
projectId: string,
|
|
159
|
+
agentPubkey?: string
|
|
160
|
+
): Promise<SearchProvider[]> {
|
|
161
|
+
const specializedProviders = this.registry.getAll();
|
|
162
|
+
|
|
163
|
+
// Derive covered RAG collections from the providers themselves.
|
|
164
|
+
// A collection is "covered" if a specialized provider declares it via
|
|
165
|
+
// `collectionName` or if its `name` matches a RAG collection name.
|
|
166
|
+
const coveredCollections = new Set<string>();
|
|
167
|
+
for (const provider of specializedProviders) {
|
|
168
|
+
if (provider.collectionName) {
|
|
169
|
+
coveredCollections.add(provider.collectionName);
|
|
170
|
+
}
|
|
171
|
+
coveredCollections.add(provider.name);
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
// Discover all RAG collections
|
|
175
|
+
let allCollections: string[];
|
|
176
|
+
try {
|
|
177
|
+
const ragService = RAGService.getInstance();
|
|
178
|
+
allCollections = await ragService.listCollections();
|
|
179
|
+
} catch (error) {
|
|
180
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
181
|
+
logger.warn("[UnifiedSearch] Failed to discover RAG collections", { error: message });
|
|
182
|
+
allCollections = [];
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
// Apply scope filtering to discovered collections (only when no explicit filter)
|
|
186
|
+
let scopedCollections = allCollections;
|
|
187
|
+
if (!requestedCollections) {
|
|
188
|
+
try {
|
|
189
|
+
const collectionRegistry = RAGCollectionRegistry.getInstance();
|
|
190
|
+
scopedCollections = collectionRegistry.getMatchingCollections(
|
|
191
|
+
allCollections,
|
|
192
|
+
projectId,
|
|
193
|
+
agentPubkey
|
|
194
|
+
);
|
|
195
|
+
|
|
196
|
+
if (scopedCollections.length < allCollections.length) {
|
|
197
|
+
logger.debug("[UnifiedSearch] Scope filtering applied", {
|
|
198
|
+
total: allCollections.length,
|
|
199
|
+
matched: scopedCollections.length,
|
|
200
|
+
excluded: allCollections.length - scopedCollections.length,
|
|
201
|
+
});
|
|
202
|
+
}
|
|
203
|
+
} catch (error) {
|
|
204
|
+
// Registry not available — fall through with all collections
|
|
205
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
206
|
+
logger.debug("[UnifiedSearch] Collection registry unavailable, using all collections", {
|
|
207
|
+
error: message,
|
|
208
|
+
});
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
// Create generic providers for collections not covered by specialized providers
|
|
213
|
+
const genericProviders = scopedCollections
|
|
214
|
+
.filter((name) => !coveredCollections.has(name))
|
|
215
|
+
.map((name) => new GenericCollectionSearchProvider(name));
|
|
216
|
+
|
|
217
|
+
const allProviders = [...specializedProviders, ...genericProviders];
|
|
218
|
+
|
|
219
|
+
if (genericProviders.length > 0) {
|
|
220
|
+
logger.debug("[UnifiedSearch] Dynamic collections discovered", {
|
|
221
|
+
specialized: specializedProviders.map((p) => p.name),
|
|
222
|
+
generic: genericProviders.map((p) => p.name),
|
|
223
|
+
});
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
// Apply explicit collection filter by provider name
|
|
227
|
+
if (requestedCollections && requestedCollections.length > 0) {
|
|
228
|
+
const requestedSet = new Set(requestedCollections);
|
|
229
|
+
|
|
230
|
+
// For explicitly requested collections that aren't in scopedCollections
|
|
231
|
+
// (because scope would have excluded them), add generic providers anyway.
|
|
232
|
+
// The agent explicitly asked — no scope filtering.
|
|
233
|
+
const missingGenericNames = requestedCollections.filter(
|
|
234
|
+
(name) =>
|
|
235
|
+
!specializedProviders.some((p) => p.name === name) &&
|
|
236
|
+
!genericProviders.some((p) => p.name === name) &&
|
|
237
|
+
allCollections.includes(name)
|
|
238
|
+
);
|
|
239
|
+
const extraProviders = missingGenericNames.map(
|
|
240
|
+
(name) => new GenericCollectionSearchProvider(name)
|
|
241
|
+
);
|
|
242
|
+
|
|
243
|
+
const expandedProviders = [...allProviders, ...extraProviders];
|
|
244
|
+
return expandedProviders.filter((p) => requestedSet.has(p.name));
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
return allProviders;
|
|
248
|
+
}
|
|
249
|
+
|
|
139
250
|
/**
|
|
140
251
|
* Use a fast/cheap LLM to extract focused information from search results.
|
|
141
252
|
*
|
|
@@ -19,6 +19,7 @@ export type {
|
|
|
19
19
|
export { ReportSearchProvider } from "./providers/ReportSearchProvider";
|
|
20
20
|
export { ConversationSearchProvider } from "./providers/ConversationSearchProvider";
|
|
21
21
|
export { LessonSearchProvider } from "./providers/LessonSearchProvider";
|
|
22
|
+
export { GenericCollectionSearchProvider } from "./providers/GenericCollectionSearchProvider";
|
|
22
23
|
|
|
23
24
|
import { SearchProviderRegistry } from "./SearchProviderRegistry";
|
|
24
25
|
import { ReportSearchProvider } from "./providers/ReportSearchProvider";
|