@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,384 +0,0 @@
|
|
|
1
|
-
import { NDKKind } from "@/nostr/kinds";
|
|
2
|
-
import type NDK from "@nostr-dev-kit/ndk";
|
|
3
|
-
import { NDKEvent, type NDKRawEvent } from "@nostr-dev-kit/ndk";
|
|
4
|
-
|
|
5
|
-
/**
|
|
6
|
-
* NDKProjectStatus represents a TenexProjectStatus event
|
|
7
|
-
* Used to indicate project status including online agents and model configurations
|
|
8
|
-
*/
|
|
9
|
-
export class NDKProjectStatus extends NDKEvent {
|
|
10
|
-
static kind = NDKKind.TenexProjectStatus;
|
|
11
|
-
static kinds = [NDKKind.TenexProjectStatus];
|
|
12
|
-
|
|
13
|
-
constructor(ndk?: NDK, event?: NDKEvent | NDKRawEvent) {
|
|
14
|
-
super(ndk, event);
|
|
15
|
-
this.kind ??= NDKKind.TenexProjectStatus;
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
static from(event: NDKEvent): NDKProjectStatus {
|
|
19
|
-
return new NDKProjectStatus(event.ndk, event);
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
/**
|
|
23
|
-
* Get the project this status refers to
|
|
24
|
-
* Returns the value of the "a" tag (replaceable event reference)
|
|
25
|
-
*/
|
|
26
|
-
get projectReference(): string | undefined {
|
|
27
|
-
return this.tagValue("a");
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
/**
|
|
31
|
-
* Set the project this status refers to
|
|
32
|
-
* @param projectTagId The tag ID of the NDKProject event (format: kind:pubkey:dTag)
|
|
33
|
-
*/
|
|
34
|
-
set projectReference(projectTagId: string | undefined) {
|
|
35
|
-
this.removeTag("a");
|
|
36
|
-
if (projectTagId) {
|
|
37
|
-
this.tags.push(["a", projectTagId]);
|
|
38
|
-
}
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
/**
|
|
42
|
-
* Get all agent entries from this status event
|
|
43
|
-
* Returns an array of {pubkey, slug} objects
|
|
44
|
-
*/
|
|
45
|
-
get agents(): Array<{ pubkey: string; slug: string }> {
|
|
46
|
-
const agentTags = this.tags.filter((tag) => tag[0] === "agent" && tag[1] && tag[2]);
|
|
47
|
-
return agentTags.map((tag) => ({
|
|
48
|
-
pubkey: tag[1],
|
|
49
|
-
slug: tag[2],
|
|
50
|
-
}));
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
/**
|
|
54
|
-
* Add an agent to the status
|
|
55
|
-
* @param pubkey The agent's public key
|
|
56
|
-
* @param slug The agent's slug/identifier
|
|
57
|
-
*/
|
|
58
|
-
addAgent(pubkey: string, slug: string): void {
|
|
59
|
-
this.tags.push(["agent", pubkey, slug]);
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
/**
|
|
63
|
-
* Remove an agent from the status
|
|
64
|
-
* @param pubkey The agent's public key to remove
|
|
65
|
-
*/
|
|
66
|
-
removeAgent(pubkey: string): void {
|
|
67
|
-
this.tags = this.tags.filter((tag) => !(tag[0] === "agent" && tag[1] === pubkey));
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
/**
|
|
71
|
-
* Clear all agents from the status
|
|
72
|
-
*/
|
|
73
|
-
clearAgents(): void {
|
|
74
|
-
this.tags = this.tags.filter((tag) => tag[0] !== "agent");
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
/**
|
|
78
|
-
* Check if a specific agent is in the status
|
|
79
|
-
* @param pubkey The agent's public key
|
|
80
|
-
*/
|
|
81
|
-
hasAgent(pubkey: string): boolean {
|
|
82
|
-
return this.tags.some((tag) => tag[0] === "agent" && tag[1] === pubkey);
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
/**
|
|
86
|
-
* Get all model configurations from this status event
|
|
87
|
-
* Returns an array of {modelSlug, agents} objects where agents is an array of agent slugs
|
|
88
|
-
*/
|
|
89
|
-
get models(): Array<{ modelSlug: string; agents: string[] }> {
|
|
90
|
-
const modelTags = this.tags.filter((tag) => tag[0] === "model" && tag[1]);
|
|
91
|
-
return modelTags.map((tag) => ({
|
|
92
|
-
modelSlug: tag[1],
|
|
93
|
-
agents: tag.slice(2).filter((a) => a), // Get all agent slugs from index 2 onwards
|
|
94
|
-
}));
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
/**
|
|
98
|
-
* Add a model with its agent access list
|
|
99
|
-
* @param modelSlug The model slug identifier (e.g., "gpt-4", "claude-3")
|
|
100
|
-
* @param agentSlugs Array of agent slugs that use this model
|
|
101
|
-
*/
|
|
102
|
-
addModel(modelSlug: string, agentSlugs: string[]): void {
|
|
103
|
-
// Remove existing model tag if it exists
|
|
104
|
-
this.removeModel(modelSlug);
|
|
105
|
-
// Add new model tag with all agent slugs
|
|
106
|
-
this.tags.push(["model", modelSlug, ...agentSlugs]);
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
/**
|
|
110
|
-
* Remove a model from the status
|
|
111
|
-
* @param modelSlug The model slug to remove
|
|
112
|
-
*/
|
|
113
|
-
removeModel(modelSlug: string): void {
|
|
114
|
-
this.tags = this.tags.filter((tag) => !(tag[0] === "model" && tag[1] === modelSlug));
|
|
115
|
-
}
|
|
116
|
-
|
|
117
|
-
/**
|
|
118
|
-
* Clear all model configurations from the status
|
|
119
|
-
*/
|
|
120
|
-
clearModels(): void {
|
|
121
|
-
this.tags = this.tags.filter((tag) => tag[0] !== "model");
|
|
122
|
-
}
|
|
123
|
-
|
|
124
|
-
/**
|
|
125
|
-
* Check if a specific model exists
|
|
126
|
-
* @param modelSlug The model slug
|
|
127
|
-
*/
|
|
128
|
-
hasModel(modelSlug: string): boolean {
|
|
129
|
-
return this.tags.some((tag) => tag[0] === "model" && tag[1] === modelSlug);
|
|
130
|
-
}
|
|
131
|
-
|
|
132
|
-
/**
|
|
133
|
-
* Get agents that use a specific model
|
|
134
|
-
* @param modelSlug The model slug
|
|
135
|
-
* @returns Array of agent slugs that use this model
|
|
136
|
-
*/
|
|
137
|
-
getModelAgents(modelSlug: string): string[] {
|
|
138
|
-
const modelTag = this.tags.find((tag) => tag[0] === "model" && tag[1] === modelSlug);
|
|
139
|
-
return modelTag ? modelTag.slice(2).filter((a) => a) : [];
|
|
140
|
-
}
|
|
141
|
-
|
|
142
|
-
/**
|
|
143
|
-
* Check if a specific agent uses a model
|
|
144
|
-
* @param modelSlug The model slug
|
|
145
|
-
* @param agentSlug The agent slug
|
|
146
|
-
*/
|
|
147
|
-
agentUsesModel(modelSlug: string, agentSlug: string): boolean {
|
|
148
|
-
const agents = this.getModelAgents(modelSlug);
|
|
149
|
-
return agents.includes(agentSlug);
|
|
150
|
-
}
|
|
151
|
-
|
|
152
|
-
/**
|
|
153
|
-
* Get all models used by a specific agent
|
|
154
|
-
* @param agentSlug The agent slug
|
|
155
|
-
* @returns Array of model slugs used by this agent
|
|
156
|
-
*/
|
|
157
|
-
getAgentModels(agentSlug: string): string[] {
|
|
158
|
-
return this.models
|
|
159
|
-
.filter((model) => model.agents.includes(agentSlug))
|
|
160
|
-
.map((model) => model.modelSlug);
|
|
161
|
-
}
|
|
162
|
-
|
|
163
|
-
/**
|
|
164
|
-
* Get the status message/content
|
|
165
|
-
*/
|
|
166
|
-
get status(): string {
|
|
167
|
-
return this.content;
|
|
168
|
-
}
|
|
169
|
-
|
|
170
|
-
/**
|
|
171
|
-
* Set the status message/content
|
|
172
|
-
*/
|
|
173
|
-
set status(value: string) {
|
|
174
|
-
this.content = value;
|
|
175
|
-
}
|
|
176
|
-
|
|
177
|
-
/**
|
|
178
|
-
* Get all tools with their agent access information
|
|
179
|
-
* Returns an array of {toolName, agents} objects where agents is an array of agent slugs
|
|
180
|
-
*/
|
|
181
|
-
get tools(): Array<{ toolName: string; agents: string[] }> {
|
|
182
|
-
const toolTags = this.tags.filter((tag) => tag[0] === "tool" && tag[1]);
|
|
183
|
-
return toolTags.map((tag) => ({
|
|
184
|
-
toolName: tag[1],
|
|
185
|
-
agents: tag.slice(2).filter((a) => a), // Get all agent slugs from index 2 onwards
|
|
186
|
-
}));
|
|
187
|
-
}
|
|
188
|
-
|
|
189
|
-
/**
|
|
190
|
-
* Add a tool with its agent access list
|
|
191
|
-
* @param toolName The name of the tool
|
|
192
|
-
* @param agentSlugs Array of agent slugs that have access to this tool
|
|
193
|
-
*/
|
|
194
|
-
addTool(toolName: string, agentSlugs: string[]): void {
|
|
195
|
-
// Remove existing tool tag if it exists
|
|
196
|
-
this.removeTool(toolName);
|
|
197
|
-
// Add new tool tag with all agent slugs
|
|
198
|
-
this.tags.push(["tool", toolName, ...agentSlugs]);
|
|
199
|
-
}
|
|
200
|
-
|
|
201
|
-
/**
|
|
202
|
-
* Remove a tool from the status
|
|
203
|
-
* @param toolName The tool name to remove
|
|
204
|
-
*/
|
|
205
|
-
removeTool(toolName: string): void {
|
|
206
|
-
this.tags = this.tags.filter((tag) => !(tag[0] === "tool" && tag[1] === toolName));
|
|
207
|
-
}
|
|
208
|
-
|
|
209
|
-
/**
|
|
210
|
-
* Clear all tools from the status
|
|
211
|
-
*/
|
|
212
|
-
clearTools(): void {
|
|
213
|
-
this.tags = this.tags.filter((tag) => tag[0] !== "tool");
|
|
214
|
-
}
|
|
215
|
-
|
|
216
|
-
/**
|
|
217
|
-
* Check if a specific tool exists
|
|
218
|
-
* @param toolName The tool name
|
|
219
|
-
*/
|
|
220
|
-
hasTool(toolName: string): boolean {
|
|
221
|
-
return this.tags.some((tag) => tag[0] === "tool" && tag[1] === toolName);
|
|
222
|
-
}
|
|
223
|
-
|
|
224
|
-
/**
|
|
225
|
-
* Get agents that have access to a specific tool
|
|
226
|
-
* @param toolName The tool name
|
|
227
|
-
* @returns Array of agent slugs that have access to this tool
|
|
228
|
-
*/
|
|
229
|
-
getToolAgents(toolName: string): string[] {
|
|
230
|
-
const toolTag = this.tags.find((tag) => tag[0] === "tool" && tag[1] === toolName);
|
|
231
|
-
return toolTag ? toolTag.slice(2).filter((a) => a) : [];
|
|
232
|
-
}
|
|
233
|
-
|
|
234
|
-
/**
|
|
235
|
-
* Check if a specific agent has access to a tool
|
|
236
|
-
* @param toolName The tool name
|
|
237
|
-
* @param agentSlug The agent slug
|
|
238
|
-
*/
|
|
239
|
-
agentHasTool(toolName: string, agentSlug: string): boolean {
|
|
240
|
-
const agents = this.getToolAgents(toolName);
|
|
241
|
-
return agents.includes(agentSlug);
|
|
242
|
-
}
|
|
243
|
-
|
|
244
|
-
/**
|
|
245
|
-
* Get all tools accessible by a specific agent
|
|
246
|
-
* @param agentSlug The agent slug
|
|
247
|
-
* @returns Array of tool names accessible by this agent
|
|
248
|
-
*/
|
|
249
|
-
getAgentTools(agentSlug: string): string[] {
|
|
250
|
-
return this.tools
|
|
251
|
-
.filter((tool) => tool.agents.includes(agentSlug))
|
|
252
|
-
.map((tool) => tool.toolName);
|
|
253
|
-
}
|
|
254
|
-
|
|
255
|
-
/**
|
|
256
|
-
* Get all worktrees from this status event
|
|
257
|
-
* Returns an array of branch names, with the first being the default branch
|
|
258
|
-
*/
|
|
259
|
-
get worktrees(): string[] {
|
|
260
|
-
const worktreeTags = this.tags.filter((tag) => tag[0] === "branch" && tag[1]);
|
|
261
|
-
return worktreeTags.map((tag) => tag[1]);
|
|
262
|
-
}
|
|
263
|
-
|
|
264
|
-
/**
|
|
265
|
-
* Add a worktree to the status
|
|
266
|
-
* @param branchName The branch name
|
|
267
|
-
*/
|
|
268
|
-
addWorktree(branchName: string): void {
|
|
269
|
-
this.tags.push(["branch", branchName]);
|
|
270
|
-
}
|
|
271
|
-
|
|
272
|
-
/**
|
|
273
|
-
* Get the default worktree (first worktree in the list)
|
|
274
|
-
*/
|
|
275
|
-
get defaultWorktree(): string | undefined {
|
|
276
|
-
return this.worktrees[0];
|
|
277
|
-
}
|
|
278
|
-
|
|
279
|
-
// ========================================================================
|
|
280
|
-
// Scheduled Tasks
|
|
281
|
-
// ========================================================================
|
|
282
|
-
|
|
283
|
-
/**
|
|
284
|
-
* Get all scheduled tasks from this status event
|
|
285
|
-
* Tag format: ["scheduled-task", id, title, schedule, targetAgent, type, lastRunTimestamp]
|
|
286
|
-
*/
|
|
287
|
-
get scheduledTasks(): Array<{
|
|
288
|
-
id: string;
|
|
289
|
-
title: string;
|
|
290
|
-
schedule: string;
|
|
291
|
-
/** Agent slug when resolvable, otherwise a truncated pubkey prefix */
|
|
292
|
-
targetAgent: string;
|
|
293
|
-
type: "cron" | "oneoff";
|
|
294
|
-
lastRun?: number;
|
|
295
|
-
}> {
|
|
296
|
-
const taskTags = this.tags.filter((tag) => tag[0] === "scheduled-task" && tag[1]);
|
|
297
|
-
return taskTags.map((tag) => {
|
|
298
|
-
const rawType = tag[5];
|
|
299
|
-
const type = rawType === "cron" || rawType === "oneoff" ? rawType : "cron";
|
|
300
|
-
const parsedLastRun = tag[6] ? Number(tag[6]) : NaN;
|
|
301
|
-
const lastRun = Number.isFinite(parsedLastRun) ? parsedLastRun : undefined;
|
|
302
|
-
|
|
303
|
-
return {
|
|
304
|
-
id: tag[1],
|
|
305
|
-
title: tag[2] || "",
|
|
306
|
-
schedule: tag[3] || "",
|
|
307
|
-
targetAgent: tag[4] || "",
|
|
308
|
-
type,
|
|
309
|
-
lastRun,
|
|
310
|
-
};
|
|
311
|
-
});
|
|
312
|
-
}
|
|
313
|
-
|
|
314
|
-
/**
|
|
315
|
-
* Add a scheduled task to the status
|
|
316
|
-
* @param id Task identifier
|
|
317
|
-
* @param title Human-readable task title
|
|
318
|
-
* @param schedule Cron expression or ISO timestamp
|
|
319
|
-
* @param targetAgent Agent slug or pubkey-prefix label for the target agent
|
|
320
|
-
* @param type Task type: "cron" or "oneoff"
|
|
321
|
-
* @param lastRun Optional last run Unix timestamp in seconds
|
|
322
|
-
*/
|
|
323
|
-
addScheduledTask(
|
|
324
|
-
id: string,
|
|
325
|
-
title: string,
|
|
326
|
-
schedule: string,
|
|
327
|
-
targetAgent: string,
|
|
328
|
-
type: "cron" | "oneoff",
|
|
329
|
-
lastRun?: number
|
|
330
|
-
): void {
|
|
331
|
-
this.tags.push([
|
|
332
|
-
"scheduled-task",
|
|
333
|
-
id,
|
|
334
|
-
title,
|
|
335
|
-
schedule,
|
|
336
|
-
targetAgent,
|
|
337
|
-
type,
|
|
338
|
-
lastRun !== undefined ? String(lastRun) : "",
|
|
339
|
-
]);
|
|
340
|
-
}
|
|
341
|
-
|
|
342
|
-
/**
|
|
343
|
-
* Remove a scheduled task from the status
|
|
344
|
-
* @param id The task ID to remove
|
|
345
|
-
*/
|
|
346
|
-
removeScheduledTask(id: string): void {
|
|
347
|
-
this.tags = this.tags.filter(
|
|
348
|
-
(tag) => !(tag[0] === "scheduled-task" && tag[1] === id)
|
|
349
|
-
);
|
|
350
|
-
}
|
|
351
|
-
|
|
352
|
-
/**
|
|
353
|
-
* Clear all scheduled tasks from the status
|
|
354
|
-
*/
|
|
355
|
-
clearScheduledTasks(): void {
|
|
356
|
-
this.tags = this.tags.filter((tag) => tag[0] !== "scheduled-task");
|
|
357
|
-
}
|
|
358
|
-
|
|
359
|
-
/**
|
|
360
|
-
* Check if a specific scheduled task exists
|
|
361
|
-
* @param id The task ID
|
|
362
|
-
*/
|
|
363
|
-
hasScheduledTask(id: string): boolean {
|
|
364
|
-
return this.tags.some((tag) => tag[0] === "scheduled-task" && tag[1] === id);
|
|
365
|
-
}
|
|
366
|
-
|
|
367
|
-
/**
|
|
368
|
-
* Get all scheduled tasks targeting a specific agent
|
|
369
|
-
* @param agentIdentifier The agent slug or truncated pubkey prefix
|
|
370
|
-
*/
|
|
371
|
-
getScheduledTasksForAgent(agentIdentifier: string): Array<{
|
|
372
|
-
id: string;
|
|
373
|
-
title: string;
|
|
374
|
-
schedule: string;
|
|
375
|
-
/** Agent slug when resolvable, otherwise a truncated pubkey prefix */
|
|
376
|
-
targetAgent: string;
|
|
377
|
-
type: "cron" | "oneoff";
|
|
378
|
-
lastRun?: number;
|
|
379
|
-
}> {
|
|
380
|
-
return this.scheduledTasks.filter(
|
|
381
|
-
(task) => task.targetAgent === agentIdentifier
|
|
382
|
-
);
|
|
383
|
-
}
|
|
384
|
-
}
|
package/src/events/index.ts
DELETED
package/src/lib/json-parser.ts
DELETED
|
@@ -1,30 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Safely parse JSON with markdown code block cleanup
|
|
3
|
-
* @param text The text to parse, potentially containing markdown code blocks
|
|
4
|
-
* @param context Optional context for error logging
|
|
5
|
-
* @returns Parsed object or null if parsing fails
|
|
6
|
-
*/
|
|
7
|
-
export function safeParseJSON<T = unknown>(text: string, context?: string): T | null {
|
|
8
|
-
try {
|
|
9
|
-
// Clean up response - remove markdown code blocks if present
|
|
10
|
-
let cleanText = text.trim();
|
|
11
|
-
|
|
12
|
-
// Remove ```json or ``` wrapper if present
|
|
13
|
-
if (cleanText.startsWith("```json")) {
|
|
14
|
-
cleanText = cleanText.replace(/^```json\s*/, "").replace(/```\s*$/, "");
|
|
15
|
-
} else if (cleanText.startsWith("```")) {
|
|
16
|
-
cleanText = cleanText.replace(/^```\s*/, "").replace(/```\s*$/, "");
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
return JSON.parse(cleanText);
|
|
20
|
-
} catch (error) {
|
|
21
|
-
if (context) {
|
|
22
|
-
// Use console.error since lib/ should not depend on TENEX logger
|
|
23
|
-
console.error(`[JSON Parser] Failed to parse JSON in ${context}`, {
|
|
24
|
-
error: error instanceof Error ? error.message : String(error),
|
|
25
|
-
text: text.substring(0, 200),
|
|
26
|
-
});
|
|
27
|
-
}
|
|
28
|
-
return null;
|
|
29
|
-
}
|
|
30
|
-
}
|
|
@@ -1,37 +0,0 @@
|
|
|
1
|
-
import { EventEmitter } from "tseep";
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* Global recording state singleton.
|
|
5
|
-
* Controls whether the flight recorder middleware saves LLM interactions.
|
|
6
|
-
*/
|
|
7
|
-
class RecordingStateManager extends EventEmitter<{
|
|
8
|
-
"state-changed": (isRecording: boolean) => void;
|
|
9
|
-
}> {
|
|
10
|
-
private _isRecording = false;
|
|
11
|
-
|
|
12
|
-
get isRecording(): boolean {
|
|
13
|
-
return this._isRecording;
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
toggle(): boolean {
|
|
17
|
-
this._isRecording = !this._isRecording;
|
|
18
|
-
this.emit("state-changed", this._isRecording);
|
|
19
|
-
return this._isRecording;
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
start(): void {
|
|
23
|
-
if (!this._isRecording) {
|
|
24
|
-
this._isRecording = true;
|
|
25
|
-
this.emit("state-changed", true);
|
|
26
|
-
}
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
stop(): void {
|
|
30
|
-
if (this._isRecording) {
|
|
31
|
-
this._isRecording = false;
|
|
32
|
-
this.emit("state-changed", false);
|
|
33
|
-
}
|
|
34
|
-
}
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
export const recordingState = new RecordingStateManager();
|
|
@@ -1,40 +0,0 @@
|
|
|
1
|
-
import { logger } from "@/utils/logger";
|
|
2
|
-
import type { LocalStreamChunk } from "./types";
|
|
3
|
-
|
|
4
|
-
/**
|
|
5
|
-
* Interface for stream transports (Unix socket, future Nostr ephemeral)
|
|
6
|
-
*/
|
|
7
|
-
export interface StreamTransport {
|
|
8
|
-
write(chunk: LocalStreamChunk): void;
|
|
9
|
-
isConnected(): boolean;
|
|
10
|
-
}
|
|
11
|
-
|
|
12
|
-
/**
|
|
13
|
-
* Publishes AI SDK chunks to connected transports
|
|
14
|
-
* Fire-and-forget: silently drops if no transport connected
|
|
15
|
-
*/
|
|
16
|
-
export class StreamPublisher {
|
|
17
|
-
private transport: StreamTransport | null = null;
|
|
18
|
-
|
|
19
|
-
setTransport(transport: StreamTransport | null): void {
|
|
20
|
-
this.transport = transport;
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
write(chunk: LocalStreamChunk): void {
|
|
24
|
-
logger.debug("[StreamPublisher] write called", {
|
|
25
|
-
hasTransport: !!this.transport,
|
|
26
|
-
isConnected: this.transport?.isConnected() ?? false,
|
|
27
|
-
chunkType: (chunk.data as { type?: string })?.type,
|
|
28
|
-
});
|
|
29
|
-
if (this.transport?.isConnected()) {
|
|
30
|
-
this.transport.write(chunk);
|
|
31
|
-
}
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
isConnected(): boolean {
|
|
35
|
-
return this.transport?.isConnected() ?? false;
|
|
36
|
-
}
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
/** Singleton instance */
|
|
40
|
-
export const streamPublisher = new StreamPublisher();
|
|
@@ -1,188 +0,0 @@
|
|
|
1
|
-
import type { LanguageModelMiddleware } from "ai";
|
|
2
|
-
import { mkdir, writeFile } from "fs/promises";
|
|
3
|
-
import { join } from "path";
|
|
4
|
-
import { getTenexBasePath } from "@/constants";
|
|
5
|
-
import { recordingState } from "../RecordingState";
|
|
6
|
-
|
|
7
|
-
type WrapGenerateParams = Parameters<NonNullable<LanguageModelMiddleware["wrapGenerate"]>>[0];
|
|
8
|
-
type WrapStreamParams = Parameters<NonNullable<LanguageModelMiddleware["wrapStream"]>>[0];
|
|
9
|
-
|
|
10
|
-
// Recording types - using unknown for SDK types that may change between versions
|
|
11
|
-
interface RecordingResponse {
|
|
12
|
-
content: unknown;
|
|
13
|
-
finishReason: unknown;
|
|
14
|
-
usage: unknown;
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
interface RecordingError {
|
|
18
|
-
message: string;
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
interface ToolCallRecord {
|
|
22
|
-
toolCallId: string;
|
|
23
|
-
toolName: string;
|
|
24
|
-
args: unknown;
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
/**
|
|
28
|
-
* Flight recorder configuration
|
|
29
|
-
*/
|
|
30
|
-
export interface FlightRecorderConfig {
|
|
31
|
-
/** Base directory for recordings (defaults to ~/.tenex/recordings) */
|
|
32
|
-
baseDir?: string;
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
/**
|
|
36
|
-
* Creates a flight recorder middleware that records LLM interactions when enabled.
|
|
37
|
-
* Recording is controlled by the global recordingState - toggle with Ctrl+R in daemon.
|
|
38
|
-
*
|
|
39
|
-
* Recordings are saved to: {baseDir}/YYYY-MM-DD/{timestamp}-{hash}.json
|
|
40
|
-
* Respects TENEX_BASE_DIR environment variable for instance isolation.
|
|
41
|
-
*/
|
|
42
|
-
export function createFlightRecorderMiddleware(
|
|
43
|
-
config: FlightRecorderConfig = {}
|
|
44
|
-
): LanguageModelMiddleware {
|
|
45
|
-
const baseDir = config.baseDir || join(getTenexBasePath(), "recordings");
|
|
46
|
-
|
|
47
|
-
return {
|
|
48
|
-
specificationVersion: "v3" as const,
|
|
49
|
-
|
|
50
|
-
async wrapGenerate({ doGenerate, params, model }: WrapGenerateParams) {
|
|
51
|
-
if (!recordingState.isRecording) {
|
|
52
|
-
return doGenerate();
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
const startTime = Date.now();
|
|
56
|
-
const hash = createSimpleHash(JSON.stringify(params.prompt));
|
|
57
|
-
|
|
58
|
-
const recording = {
|
|
59
|
-
timestamp: new Date().toISOString(),
|
|
60
|
-
type: "generate",
|
|
61
|
-
model: { provider: model.provider, modelId: model.modelId },
|
|
62
|
-
request: {
|
|
63
|
-
prompt: params.prompt,
|
|
64
|
-
temperature: params.temperature,
|
|
65
|
-
maxOutputTokens: params.maxOutputTokens,
|
|
66
|
-
tools: params.tools,
|
|
67
|
-
toolChoice: params.toolChoice,
|
|
68
|
-
},
|
|
69
|
-
response: null as RecordingResponse | null,
|
|
70
|
-
error: null as RecordingError | null,
|
|
71
|
-
duration: 0,
|
|
72
|
-
};
|
|
73
|
-
|
|
74
|
-
try {
|
|
75
|
-
const result = await doGenerate();
|
|
76
|
-
recording.response = {
|
|
77
|
-
content: result.content,
|
|
78
|
-
finishReason: result.finishReason,
|
|
79
|
-
usage: result.usage,
|
|
80
|
-
};
|
|
81
|
-
recording.duration = Date.now() - startTime;
|
|
82
|
-
await saveRecording(baseDir, hash, recording);
|
|
83
|
-
return result;
|
|
84
|
-
} catch (error) {
|
|
85
|
-
recording.error = {
|
|
86
|
-
message: error instanceof Error ? error.message : String(error),
|
|
87
|
-
};
|
|
88
|
-
recording.duration = Date.now() - startTime;
|
|
89
|
-
await saveRecording(baseDir, hash, recording);
|
|
90
|
-
throw error;
|
|
91
|
-
}
|
|
92
|
-
},
|
|
93
|
-
|
|
94
|
-
wrapStream({ doStream, params, model }: WrapStreamParams) {
|
|
95
|
-
if (!recordingState.isRecording) {
|
|
96
|
-
return doStream();
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
const startTime = Date.now();
|
|
100
|
-
const hash = createSimpleHash(JSON.stringify(params.prompt));
|
|
101
|
-
|
|
102
|
-
// Buffer to collect stream content
|
|
103
|
-
const textParts: string[] = [];
|
|
104
|
-
const toolCalls: ToolCallRecord[] = [];
|
|
105
|
-
let finishReason: string | undefined;
|
|
106
|
-
let usage: unknown;
|
|
107
|
-
|
|
108
|
-
// Wrap the stream with a TransformStream to intercept chunks
|
|
109
|
-
return doStream().then((result) => {
|
|
110
|
-
const transform = new TransformStream({
|
|
111
|
-
transform(chunk, controller) {
|
|
112
|
-
// Pass through the chunk
|
|
113
|
-
controller.enqueue(chunk);
|
|
114
|
-
|
|
115
|
-
// Collect data for recording
|
|
116
|
-
if (chunk.type === "text-delta" && chunk.textDelta) {
|
|
117
|
-
textParts.push(chunk.textDelta);
|
|
118
|
-
}
|
|
119
|
-
if (chunk.type === "tool-call") {
|
|
120
|
-
toolCalls.push({
|
|
121
|
-
toolCallId: chunk.toolCallId,
|
|
122
|
-
toolName: chunk.toolName,
|
|
123
|
-
args: chunk.args,
|
|
124
|
-
});
|
|
125
|
-
}
|
|
126
|
-
if (chunk.type === "finish") {
|
|
127
|
-
finishReason = chunk.finishReason;
|
|
128
|
-
usage = chunk.usage;
|
|
129
|
-
}
|
|
130
|
-
},
|
|
131
|
-
flush() {
|
|
132
|
-
// Stream finished - save the recording
|
|
133
|
-
const recording = {
|
|
134
|
-
timestamp: new Date().toISOString(),
|
|
135
|
-
type: "stream",
|
|
136
|
-
model: { provider: model.provider, modelId: model.modelId },
|
|
137
|
-
request: {
|
|
138
|
-
prompt: params.prompt,
|
|
139
|
-
temperature: params.temperature,
|
|
140
|
-
maxOutputTokens: params.maxOutputTokens,
|
|
141
|
-
tools: params.tools,
|
|
142
|
-
toolChoice: params.toolChoice,
|
|
143
|
-
},
|
|
144
|
-
response: {
|
|
145
|
-
content: [{ type: "text", text: textParts.join("") }],
|
|
146
|
-
toolCalls: toolCalls.length > 0 ? toolCalls : undefined,
|
|
147
|
-
finishReason,
|
|
148
|
-
usage,
|
|
149
|
-
},
|
|
150
|
-
duration: Date.now() - startTime,
|
|
151
|
-
};
|
|
152
|
-
saveRecording(baseDir, hash, recording).catch(() => {});
|
|
153
|
-
},
|
|
154
|
-
});
|
|
155
|
-
|
|
156
|
-
return {
|
|
157
|
-
...result,
|
|
158
|
-
stream: result.stream.pipeThrough(transform),
|
|
159
|
-
};
|
|
160
|
-
});
|
|
161
|
-
},
|
|
162
|
-
};
|
|
163
|
-
}
|
|
164
|
-
|
|
165
|
-
async function saveRecording(baseDir: string, hash: string, recording: Record<string, unknown>): Promise<void> {
|
|
166
|
-
try {
|
|
167
|
-
const now = new Date();
|
|
168
|
-
const dateStr = now.toISOString().split("T")[0];
|
|
169
|
-
const dirPath = join(baseDir, dateStr);
|
|
170
|
-
await mkdir(dirPath, { recursive: true });
|
|
171
|
-
|
|
172
|
-
const timestamp = now.toISOString().replace(/[:.]/g, "-");
|
|
173
|
-
const filename = `${timestamp}-${hash}.json`;
|
|
174
|
-
await writeFile(join(dirPath, filename), JSON.stringify(recording, null, 2), "utf-8");
|
|
175
|
-
} catch (error) {
|
|
176
|
-
console.error("[FlightRecorder] Failed to save recording:", error);
|
|
177
|
-
}
|
|
178
|
-
}
|
|
179
|
-
|
|
180
|
-
function createSimpleHash(input: string): string {
|
|
181
|
-
let hash = 0;
|
|
182
|
-
for (let i = 0; i < input.length; i++) {
|
|
183
|
-
const char = input.charCodeAt(i);
|
|
184
|
-
hash = (hash << 5) - hash + char;
|
|
185
|
-
hash = hash & hash;
|
|
186
|
-
}
|
|
187
|
-
return Math.abs(hash).toString(16).substring(0, 8);
|
|
188
|
-
}
|