@tenex-chat/backend 0.9.5 → 0.9.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +5 -1
- package/dist/daemon-wrapper.cjs +34 -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 -46790
- 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 -235
- 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,318 +0,0 @@
|
|
|
1
|
-
import * as net from "net";
|
|
2
|
-
import * as fs from "fs";
|
|
3
|
-
import * as path from "path";
|
|
4
|
-
import type { StreamTransport } from "@/llm";
|
|
5
|
-
import type { LocalStreamChunk } from "@/llm";
|
|
6
|
-
import { logger } from "@/utils/logger";
|
|
7
|
-
|
|
8
|
-
const LOG_PREFIX = "[UnixSocketTransport]";
|
|
9
|
-
|
|
10
|
-
/** Extract a brief caller context from stack trace for debugging (only computed when debug logging enabled) */
|
|
11
|
-
function getCallerContext(): string | undefined {
|
|
12
|
-
// Only compute stack trace if debug logging is actually enabled
|
|
13
|
-
// This avoids the expensive stack capture in production
|
|
14
|
-
if (!logger.isLevelEnabled?.("debug")) {
|
|
15
|
-
return undefined;
|
|
16
|
-
}
|
|
17
|
-
const stack = new Error().stack;
|
|
18
|
-
if (!stack) return undefined;
|
|
19
|
-
// Skip first 3 lines: Error, getCallerContext, cleanup/caller
|
|
20
|
-
const lines = stack.split("\n").slice(3, 6);
|
|
21
|
-
return lines.map((l) => l.trim()).join(" <- ");
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
/** Result of checking socket file info */
|
|
25
|
-
type SocketFileInfoResult =
|
|
26
|
-
| { status: "ok"; exists: true; isSocket: boolean; isFile: boolean; mode: string; mtime: string; age: string }
|
|
27
|
-
| { status: "ok"; exists: false }
|
|
28
|
-
| { status: "error"; error: string };
|
|
29
|
-
|
|
30
|
-
/**
|
|
31
|
-
* Unix domain socket transport for local streaming
|
|
32
|
-
* Single client connection model
|
|
33
|
-
*/
|
|
34
|
-
export class UnixSocketTransport implements StreamTransport {
|
|
35
|
-
private server: net.Server | null = null;
|
|
36
|
-
private client: net.Socket | null = null;
|
|
37
|
-
private socketPath: string;
|
|
38
|
-
|
|
39
|
-
constructor(socketPath?: string) {
|
|
40
|
-
this.socketPath = socketPath ?? this.defaultSocketPath();
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
private defaultSocketPath(): string {
|
|
44
|
-
const runtimeDir = process.env.XDG_RUNTIME_DIR;
|
|
45
|
-
if (runtimeDir) {
|
|
46
|
-
return path.join(runtimeDir, "tenex-stream.sock");
|
|
47
|
-
}
|
|
48
|
-
return "/tmp/tenex-stream.sock";
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
async start(): Promise<void> {
|
|
52
|
-
logger.info(`${LOG_PREFIX} start() called`, {
|
|
53
|
-
socketPath: this.socketPath,
|
|
54
|
-
});
|
|
55
|
-
|
|
56
|
-
// Check for existing socket and log details
|
|
57
|
-
const existingSocketInfo = this.getSocketFileInfo();
|
|
58
|
-
if (existingSocketInfo.status === "ok" && existingSocketInfo.exists) {
|
|
59
|
-
logger.warn(`${LOG_PREFIX} Stale socket found before start`, {
|
|
60
|
-
socketPath: this.socketPath,
|
|
61
|
-
isSocket: existingSocketInfo.isSocket,
|
|
62
|
-
age: existingSocketInfo.age,
|
|
63
|
-
});
|
|
64
|
-
} else if (existingSocketInfo.status === "error") {
|
|
65
|
-
logger.error(`${LOG_PREFIX} Failed to check socket file before start`, {
|
|
66
|
-
socketPath: this.socketPath,
|
|
67
|
-
error: existingSocketInfo.error,
|
|
68
|
-
});
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
// Clean up stale socket, passing the file info we already have
|
|
72
|
-
this.cleanup("start() - cleaning stale socket before creating new one", existingSocketInfo);
|
|
73
|
-
|
|
74
|
-
return new Promise((resolve, reject) => {
|
|
75
|
-
this.server = net.createServer((socket) => {
|
|
76
|
-
logger.info(`${LOG_PREFIX} Client connected to streaming socket`, {
|
|
77
|
-
socketPath: this.socketPath,
|
|
78
|
-
});
|
|
79
|
-
|
|
80
|
-
// Only one client at a time
|
|
81
|
-
if (this.client) {
|
|
82
|
-
logger.warn(`${LOG_PREFIX} Replacing existing client connection`, {
|
|
83
|
-
socketPath: this.socketPath,
|
|
84
|
-
});
|
|
85
|
-
this.client.destroy();
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
this.client = socket;
|
|
89
|
-
|
|
90
|
-
socket.on("close", () => {
|
|
91
|
-
logger.info(`${LOG_PREFIX} Client disconnected from streaming socket`, {
|
|
92
|
-
socketPath: this.socketPath,
|
|
93
|
-
});
|
|
94
|
-
if (this.client === socket) {
|
|
95
|
-
this.client = null;
|
|
96
|
-
}
|
|
97
|
-
});
|
|
98
|
-
|
|
99
|
-
socket.on("error", (err) => {
|
|
100
|
-
logger.error(`${LOG_PREFIX} Socket client error`, {
|
|
101
|
-
error: err.message,
|
|
102
|
-
socketPath: this.socketPath,
|
|
103
|
-
});
|
|
104
|
-
if (this.client === socket) {
|
|
105
|
-
this.client = null;
|
|
106
|
-
}
|
|
107
|
-
});
|
|
108
|
-
});
|
|
109
|
-
|
|
110
|
-
this.server.on("error", (err) => {
|
|
111
|
-
logger.error(`${LOG_PREFIX} Socket server error`, {
|
|
112
|
-
error: err.message,
|
|
113
|
-
socketPath: this.socketPath,
|
|
114
|
-
});
|
|
115
|
-
reject(err);
|
|
116
|
-
});
|
|
117
|
-
|
|
118
|
-
this.server.listen(this.socketPath, () => {
|
|
119
|
-
logger.info(`${LOG_PREFIX} Streaming socket started and listening`, {
|
|
120
|
-
socketPath: this.socketPath,
|
|
121
|
-
});
|
|
122
|
-
resolve();
|
|
123
|
-
});
|
|
124
|
-
});
|
|
125
|
-
}
|
|
126
|
-
|
|
127
|
-
write(chunk: LocalStreamChunk): void {
|
|
128
|
-
if (!this.client) return;
|
|
129
|
-
|
|
130
|
-
try {
|
|
131
|
-
const line = JSON.stringify(chunk) + "\n";
|
|
132
|
-
this.client.write(line);
|
|
133
|
-
} catch (err) {
|
|
134
|
-
logger.error(`${LOG_PREFIX} Failed to write chunk`, {
|
|
135
|
-
error: err instanceof Error ? err.message : String(err),
|
|
136
|
-
socketPath: this.socketPath,
|
|
137
|
-
});
|
|
138
|
-
}
|
|
139
|
-
}
|
|
140
|
-
|
|
141
|
-
isConnected(): boolean {
|
|
142
|
-
return this.client !== null && !this.client.destroyed;
|
|
143
|
-
}
|
|
144
|
-
|
|
145
|
-
async stop(): Promise<void> {
|
|
146
|
-
const callerContext = getCallerContext();
|
|
147
|
-
logger.info(`${LOG_PREFIX} stop() called`, {
|
|
148
|
-
socketPath: this.socketPath,
|
|
149
|
-
hasClient: !!this.client,
|
|
150
|
-
hasServer: !!this.server,
|
|
151
|
-
...(callerContext && { callerContext }),
|
|
152
|
-
});
|
|
153
|
-
|
|
154
|
-
if (this.client) {
|
|
155
|
-
logger.info(`${LOG_PREFIX} Destroying client connection in stop()`, {
|
|
156
|
-
socketPath: this.socketPath,
|
|
157
|
-
});
|
|
158
|
-
this.client.destroy();
|
|
159
|
-
this.client = null;
|
|
160
|
-
}
|
|
161
|
-
|
|
162
|
-
return new Promise((resolve) => {
|
|
163
|
-
if (this.server) {
|
|
164
|
-
logger.info(`${LOG_PREFIX} Closing server in stop()`, {
|
|
165
|
-
socketPath: this.socketPath,
|
|
166
|
-
});
|
|
167
|
-
this.server.close(() => {
|
|
168
|
-
logger.info(`${LOG_PREFIX} Server closed, now calling cleanup()`, {
|
|
169
|
-
socketPath: this.socketPath,
|
|
170
|
-
});
|
|
171
|
-
this.cleanup("stop() - server closed callback");
|
|
172
|
-
logger.info(`${LOG_PREFIX} stop() completed`, {
|
|
173
|
-
socketPath: this.socketPath,
|
|
174
|
-
});
|
|
175
|
-
resolve();
|
|
176
|
-
});
|
|
177
|
-
} else {
|
|
178
|
-
logger.info(`${LOG_PREFIX} stop() called but no server to close`, {
|
|
179
|
-
socketPath: this.socketPath,
|
|
180
|
-
});
|
|
181
|
-
resolve();
|
|
182
|
-
}
|
|
183
|
-
});
|
|
184
|
-
}
|
|
185
|
-
|
|
186
|
-
/**
|
|
187
|
-
* Get file info for the socket path (for debugging)
|
|
188
|
-
* Returns discriminated result: ok (exists/not), or error
|
|
189
|
-
*/
|
|
190
|
-
private getSocketFileInfo(): SocketFileInfoResult {
|
|
191
|
-
try {
|
|
192
|
-
const stats = fs.lstatSync(this.socketPath);
|
|
193
|
-
const ageMs = Date.now() - stats.mtime.getTime();
|
|
194
|
-
return {
|
|
195
|
-
status: "ok",
|
|
196
|
-
exists: true,
|
|
197
|
-
isSocket: stats.isSocket(),
|
|
198
|
-
isFile: stats.isFile(),
|
|
199
|
-
mode: stats.mode.toString(8),
|
|
200
|
-
mtime: stats.mtime.toISOString(),
|
|
201
|
-
age: `${Math.floor(ageMs / 1000)}s`,
|
|
202
|
-
};
|
|
203
|
-
} catch (err) {
|
|
204
|
-
// ENOENT means file doesn't exist - that's a valid "missing" result
|
|
205
|
-
if (err instanceof Error && "code" in err && (err as NodeJS.ErrnoException).code === "ENOENT") {
|
|
206
|
-
return { status: "ok", exists: false };
|
|
207
|
-
}
|
|
208
|
-
// Any other error (permission, IO, etc.) is an actual error
|
|
209
|
-
return {
|
|
210
|
-
status: "error",
|
|
211
|
-
error: err instanceof Error ? err.message : String(err),
|
|
212
|
-
};
|
|
213
|
-
}
|
|
214
|
-
}
|
|
215
|
-
|
|
216
|
-
/**
|
|
217
|
-
* Check if socket exists and is healthy
|
|
218
|
-
*/
|
|
219
|
-
checkSocketHealth(): {
|
|
220
|
-
exists: boolean;
|
|
221
|
-
isSocket: boolean;
|
|
222
|
-
serverRunning: boolean;
|
|
223
|
-
clientConnected: boolean;
|
|
224
|
-
} {
|
|
225
|
-
const info = this.getSocketFileInfo();
|
|
226
|
-
const exists = info.status === "ok" && info.exists;
|
|
227
|
-
const isSocket = info.status === "ok" && info.exists && info.isSocket;
|
|
228
|
-
const result = {
|
|
229
|
-
exists,
|
|
230
|
-
isSocket,
|
|
231
|
-
serverRunning: this.server !== null && this.server.listening,
|
|
232
|
-
clientConnected: this.isConnected(),
|
|
233
|
-
};
|
|
234
|
-
logger.debug(`${LOG_PREFIX} Health check`, {
|
|
235
|
-
socketPath: this.socketPath,
|
|
236
|
-
...result,
|
|
237
|
-
});
|
|
238
|
-
return result;
|
|
239
|
-
}
|
|
240
|
-
|
|
241
|
-
/**
|
|
242
|
-
* Clean up socket file if it exists and is safe to remove
|
|
243
|
-
* @param reason - Why cleanup is being called (for logging)
|
|
244
|
-
* @param fileInfo - Optional pre-fetched file info to avoid redundant FS calls
|
|
245
|
-
*/
|
|
246
|
-
private cleanup(reason: string, fileInfo?: SocketFileInfoResult): void {
|
|
247
|
-
const callerContext = getCallerContext();
|
|
248
|
-
|
|
249
|
-
logger.info(`${LOG_PREFIX} cleanup() called`, {
|
|
250
|
-
socketPath: this.socketPath,
|
|
251
|
-
reason,
|
|
252
|
-
...(callerContext && { callerContext }),
|
|
253
|
-
});
|
|
254
|
-
|
|
255
|
-
// Use provided file info or fetch it
|
|
256
|
-
const info = fileInfo ?? this.getSocketFileInfo();
|
|
257
|
-
|
|
258
|
-
// Handle error case
|
|
259
|
-
if (info.status === "error") {
|
|
260
|
-
logger.error(`${LOG_PREFIX} cleanup() cannot proceed - failed to check socket file`, {
|
|
261
|
-
socketPath: this.socketPath,
|
|
262
|
-
reason,
|
|
263
|
-
error: info.error,
|
|
264
|
-
});
|
|
265
|
-
return;
|
|
266
|
-
}
|
|
267
|
-
|
|
268
|
-
// Nothing to clean up
|
|
269
|
-
if (!info.exists) {
|
|
270
|
-
logger.debug(`${LOG_PREFIX} cleanup() - socket does not exist, nothing to unlink`, {
|
|
271
|
-
socketPath: this.socketPath,
|
|
272
|
-
reason,
|
|
273
|
-
});
|
|
274
|
-
return;
|
|
275
|
-
}
|
|
276
|
-
|
|
277
|
-
// CRITICAL SAFETY CHECK: Only unlink if it's actually a socket
|
|
278
|
-
if (!info.isSocket) {
|
|
279
|
-
logger.error(`${LOG_PREFIX} cleanup() REFUSED to unlink - path is not a socket`, {
|
|
280
|
-
socketPath: this.socketPath,
|
|
281
|
-
reason,
|
|
282
|
-
isFile: info.isFile,
|
|
283
|
-
mode: info.mode,
|
|
284
|
-
});
|
|
285
|
-
return;
|
|
286
|
-
}
|
|
287
|
-
|
|
288
|
-
// Safe to unlink - it's a socket
|
|
289
|
-
try {
|
|
290
|
-
logger.info(`${LOG_PREFIX} About to unlink socket`, {
|
|
291
|
-
socketPath: this.socketPath,
|
|
292
|
-
reason,
|
|
293
|
-
age: info.age,
|
|
294
|
-
});
|
|
295
|
-
|
|
296
|
-
fs.unlinkSync(this.socketPath);
|
|
297
|
-
|
|
298
|
-
logger.info(`${LOG_PREFIX} Socket unlinked successfully`, {
|
|
299
|
-
socketPath: this.socketPath,
|
|
300
|
-
reason,
|
|
301
|
-
});
|
|
302
|
-
} catch (err) {
|
|
303
|
-
const errorMessage = err instanceof Error ? err.message : String(err);
|
|
304
|
-
const errorCode = err instanceof Error && "code" in err ? (err as NodeJS.ErrnoException).code : undefined;
|
|
305
|
-
|
|
306
|
-
logger.error(`${LOG_PREFIX} cleanup() failed to unlink socket`, {
|
|
307
|
-
socketPath: this.socketPath,
|
|
308
|
-
reason,
|
|
309
|
-
error: errorMessage,
|
|
310
|
-
errorCode,
|
|
311
|
-
});
|
|
312
|
-
}
|
|
313
|
-
}
|
|
314
|
-
|
|
315
|
-
getSocketPath(): string {
|
|
316
|
-
return this.socketPath;
|
|
317
|
-
}
|
|
318
|
-
}
|
|
@@ -1,165 +0,0 @@
|
|
|
1
|
-
import type { NDKEvent } from "@nostr-dev-kit/ndk";
|
|
2
|
-
import { NDKArticle } from "@nostr-dev-kit/ndk";
|
|
3
|
-
import { trace } from "@opentelemetry/api";
|
|
4
|
-
import chalk from "chalk";
|
|
5
|
-
import type { AgentExecutor } from "../agents/execution/AgentExecutor";
|
|
6
|
-
import { createExecutionContext } from "../agents/execution/ExecutionContextFactory";
|
|
7
|
-
import { ConversationStore } from "../conversations/ConversationStore";
|
|
8
|
-
import type { ConversationMetadata } from "../conversations/types";
|
|
9
|
-
import { AgentEventDecoder } from "../nostr/AgentEventDecoder";
|
|
10
|
-
import { getNDK } from "../nostr/ndkClient";
|
|
11
|
-
import { TagExtractor } from "../nostr/TagExtractor";
|
|
12
|
-
import { getProjectContext } from "@/services/projects";
|
|
13
|
-
import { formatAnyError } from "@/lib/error-formatter";
|
|
14
|
-
import { logger } from "../utils/logger";
|
|
15
|
-
import { AgentRouter } from "@/services/dispatch/AgentRouter";
|
|
16
|
-
|
|
17
|
-
/**
|
|
18
|
-
* Fetch a kind 30023 (NDKArticle) from an a-tag reference.
|
|
19
|
-
* @param aTagValue - The a-tag value in format "30023:pubkey:d-tag"
|
|
20
|
-
* @returns The article metadata or null if not found
|
|
21
|
-
*/
|
|
22
|
-
async function fetchReferencedArticle(
|
|
23
|
-
aTagValue: string
|
|
24
|
-
): Promise<ConversationMetadata["referencedArticle"] | null> {
|
|
25
|
-
try {
|
|
26
|
-
const parts = aTagValue.split(":");
|
|
27
|
-
if (parts.length < 3 || parts[0] !== "30023") {
|
|
28
|
-
return null;
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
const [, pubkey, ...dTagParts] = parts;
|
|
32
|
-
const dTag = dTagParts.join(":"); // Handle d-tags that contain colons
|
|
33
|
-
|
|
34
|
-
const ndk = getNDK();
|
|
35
|
-
const filter = {
|
|
36
|
-
kinds: [30023],
|
|
37
|
-
authors: [pubkey],
|
|
38
|
-
"#d": [dTag],
|
|
39
|
-
};
|
|
40
|
-
|
|
41
|
-
const events = await ndk.fetchEvents(filter);
|
|
42
|
-
if (events.size === 0) {
|
|
43
|
-
logger.debug(chalk.yellow(`Referenced article not found: ${aTagValue}`));
|
|
44
|
-
return null;
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
const event = Array.from(events)[0];
|
|
48
|
-
const article = NDKArticle.from(event);
|
|
49
|
-
|
|
50
|
-
logger.info(chalk.cyan(`📄 Fetched referenced article: "${article.title || dTag}"`));
|
|
51
|
-
|
|
52
|
-
return {
|
|
53
|
-
title: article.title || dTag,
|
|
54
|
-
content: article.content || "",
|
|
55
|
-
dTag,
|
|
56
|
-
};
|
|
57
|
-
} catch (error) {
|
|
58
|
-
logger.debug(chalk.yellow(`Failed to fetch referenced article: ${formatAnyError(error)}`));
|
|
59
|
-
return null;
|
|
60
|
-
}
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
/**
|
|
64
|
-
* Extract and fetch the first kind 30023 article reference from an event's a-tags.
|
|
65
|
-
* @param event - The event to extract article references from
|
|
66
|
-
* @returns The article metadata or null if none found
|
|
67
|
-
*/
|
|
68
|
-
async function extractReferencedArticle(
|
|
69
|
-
event: NDKEvent
|
|
70
|
-
): Promise<ConversationMetadata["referencedArticle"] | null> {
|
|
71
|
-
const aTags = TagExtractor.getATags(event);
|
|
72
|
-
|
|
73
|
-
// Find the first a-tag referencing a kind 30023 (article)
|
|
74
|
-
const articleATag = aTags.find((tag) => tag.startsWith("30023:"));
|
|
75
|
-
if (!articleATag) {
|
|
76
|
-
return null;
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
return fetchReferencedArticle(articleATag);
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
interface EventHandlerContext {
|
|
83
|
-
agentExecutor: AgentExecutor;
|
|
84
|
-
/**
|
|
85
|
-
* Project directory (normal git repository root).
|
|
86
|
-
* Worktrees are in .worktrees/ subdirectory.
|
|
87
|
-
*/
|
|
88
|
-
projectBasePath: string;
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
export const handleNewConversation = async (
|
|
92
|
-
event: NDKEvent,
|
|
93
|
-
context: EventHandlerContext
|
|
94
|
-
): Promise<void> => {
|
|
95
|
-
try {
|
|
96
|
-
// Create conversation
|
|
97
|
-
const conversation = await ConversationStore.create(event);
|
|
98
|
-
|
|
99
|
-
// Check for referenced kind 30023 articles and populate metadata
|
|
100
|
-
const referencedArticle = await extractReferencedArticle(event);
|
|
101
|
-
if (referencedArticle) {
|
|
102
|
-
conversation.updateMetadata({ referencedArticle });
|
|
103
|
-
await conversation.save();
|
|
104
|
-
|
|
105
|
-
const activeSpan = trace.getActiveSpan();
|
|
106
|
-
if (activeSpan) {
|
|
107
|
-
activeSpan.addEvent("referenced_article_loaded", {
|
|
108
|
-
"article.title": referencedArticle.title,
|
|
109
|
-
"article.dTag": referencedArticle.dTag,
|
|
110
|
-
"article.content_length": referencedArticle.content.length,
|
|
111
|
-
});
|
|
112
|
-
}
|
|
113
|
-
}
|
|
114
|
-
|
|
115
|
-
// Get project context
|
|
116
|
-
const projectCtx = getProjectContext();
|
|
117
|
-
|
|
118
|
-
// Use AgentRouter to resolve target agents (includes project validation for global agents)
|
|
119
|
-
const targetAgents = AgentRouter.resolveTargetAgents(event, projectCtx);
|
|
120
|
-
|
|
121
|
-
// Add telemetry for routing decision
|
|
122
|
-
const activeSpan = trace.getActiveSpan();
|
|
123
|
-
if (activeSpan) {
|
|
124
|
-
const mentionedPubkeys = AgentEventDecoder.getMentionedPubkeys(event);
|
|
125
|
-
activeSpan.addEvent("agent_routing", {
|
|
126
|
-
"routing.mentioned_pubkeys_count": mentionedPubkeys.length,
|
|
127
|
-
"routing.resolved_agent_count": targetAgents.length,
|
|
128
|
-
"routing.agent_names": targetAgents.map((a) => a.name).join(", "),
|
|
129
|
-
"routing.agent_roles": targetAgents.map((a) => a.role).join(", "),
|
|
130
|
-
});
|
|
131
|
-
}
|
|
132
|
-
|
|
133
|
-
// If no valid agents found (filtered by project context), return
|
|
134
|
-
if (targetAgents.length === 0) {
|
|
135
|
-
logger.info(
|
|
136
|
-
chalk.gray(
|
|
137
|
-
"New conversation - no valid agents to route to (may have been filtered by project context)"
|
|
138
|
-
)
|
|
139
|
-
);
|
|
140
|
-
if (activeSpan) {
|
|
141
|
-
activeSpan.addEvent("agent_routing_failed", { reason: "no_agents_resolved" });
|
|
142
|
-
}
|
|
143
|
-
return;
|
|
144
|
-
}
|
|
145
|
-
|
|
146
|
-
// Use first agent for new conversation
|
|
147
|
-
const targetAgent = targetAgents[0];
|
|
148
|
-
|
|
149
|
-
// Create execution context (new conversations don't have branch tags)
|
|
150
|
-
const executionContext = await createExecutionContext({
|
|
151
|
-
agent: targetAgent,
|
|
152
|
-
conversationId: conversation.id,
|
|
153
|
-
projectBasePath: context.projectBasePath,
|
|
154
|
-
triggeringEvent: event,
|
|
155
|
-
mcpManager: projectCtx.mcpManager,
|
|
156
|
-
});
|
|
157
|
-
|
|
158
|
-
// Execute with the appropriate agent
|
|
159
|
-
await context.agentExecutor.execute(executionContext);
|
|
160
|
-
|
|
161
|
-
logger.info(chalk.green("✅ Conversation routed successfully"));
|
|
162
|
-
} catch (error) {
|
|
163
|
-
logger.info(chalk.red(`❌ Failed to route conversation: ${formatAnyError(error)}`));
|
|
164
|
-
}
|
|
165
|
-
};
|