@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.
Files changed (148) hide show
  1. package/README.md +5 -1
  2. package/dist/daemon-wrapper.cjs +47 -0
  3. package/dist/index.js +59268 -0
  4. package/dist/wrapper.js +171 -0
  5. package/package.json +19 -27
  6. package/src/agents/AgentRegistry.ts +9 -7
  7. package/src/agents/AgentStorage.ts +24 -1
  8. package/src/agents/agent-installer.ts +6 -0
  9. package/src/agents/agent-loader.ts +7 -2
  10. package/src/agents/constants.ts +10 -2
  11. package/src/agents/execution/AgentExecutor.ts +35 -6
  12. package/src/agents/execution/StreamCallbacks.ts +53 -13
  13. package/src/agents/execution/StreamExecutionHandler.ts +110 -16
  14. package/src/agents/execution/StreamSetup.ts +19 -9
  15. package/src/agents/execution/ToolEventHandlers.ts +112 -0
  16. package/src/agents/role-categories.ts +53 -0
  17. package/src/agents/types/runtime.ts +7 -0
  18. package/src/agents/types/storage.ts +7 -0
  19. package/src/commands/agent/import/openclaw-distiller.ts +63 -7
  20. package/src/commands/agent/import/openclaw-reader.ts +54 -0
  21. package/src/commands/agent/import/openclaw.ts +120 -29
  22. package/src/commands/agent/index.ts +83 -2
  23. package/src/commands/setup/display.ts +123 -0
  24. package/src/commands/setup/embed.ts +13 -13
  25. package/src/commands/setup/global-system-prompt.ts +15 -17
  26. package/src/commands/setup/image.ts +17 -20
  27. package/src/commands/setup/interactive.ts +37 -20
  28. package/src/commands/setup/llm.ts +12 -7
  29. package/src/commands/setup/onboarding.ts +1580 -248
  30. package/src/commands/setup/providers.ts +3 -3
  31. package/src/conversations/ConversationStore.ts +23 -2
  32. package/src/conversations/MessageBuilder.ts +51 -73
  33. package/src/conversations/formatters/utils/conversation-transcript-formatter.ts +425 -0
  34. package/src/conversations/search/embeddings/ConversationEmbeddingService.ts +40 -98
  35. package/src/conversations/search/embeddings/ConversationIndexingJob.ts +40 -52
  36. package/src/conversations/services/ConversationSummarizer.ts +1 -2
  37. package/src/conversations/types.ts +11 -0
  38. package/src/daemon/Daemon.ts +78 -57
  39. package/src/daemon/ProjectRuntime.ts +6 -12
  40. package/src/daemon/SubscriptionManager.ts +13 -0
  41. package/src/daemon/index.ts +0 -1
  42. package/src/event-handler/index.ts +1 -0
  43. package/src/index.ts +20 -1
  44. package/src/llm/ChunkHandler.ts +1 -1
  45. package/src/llm/FinishHandler.ts +28 -4
  46. package/src/llm/LLMConfigEditor.ts +218 -106
  47. package/src/llm/index.ts +0 -4
  48. package/src/llm/meta/MetaModelResolver.ts +3 -18
  49. package/src/llm/middleware/message-sanitizer.ts +153 -0
  50. package/src/llm/providers/ollama-models.ts +0 -38
  51. package/src/llm/service.ts +50 -15
  52. package/src/llm/types.ts +0 -12
  53. package/src/llm/utils/ConfigurationManager.ts +88 -465
  54. package/src/llm/utils/ConfigurationTester.ts +42 -185
  55. package/src/llm/utils/ModelSelector.ts +156 -92
  56. package/src/llm/utils/ProviderConfigUI.ts +10 -141
  57. package/src/llm/utils/models-dev-cache.ts +102 -23
  58. package/src/llm/utils/provider-select-prompt.ts +284 -0
  59. package/src/llm/utils/provider-setup.ts +81 -34
  60. package/src/llm/utils/variant-list-prompt.ts +361 -0
  61. package/src/nostr/AgentEventDecoder.ts +1 -0
  62. package/src/nostr/AgentEventEncoder.ts +37 -0
  63. package/src/nostr/AgentProfilePublisher.ts +13 -0
  64. package/src/nostr/AgentPublisher.ts +26 -0
  65. package/src/nostr/kinds.ts +1 -0
  66. package/src/nostr/ndkClient.ts +4 -1
  67. package/src/nostr/types.ts +12 -0
  68. package/src/prompts/fragments/25-rag-instructions.ts +22 -21
  69. package/src/prompts/fragments/31-agents-md-guidance.ts +7 -21
  70. package/src/prompts/fragments/index.ts +2 -0
  71. package/src/prompts/utils/systemPromptBuilder.ts +18 -28
  72. package/src/services/AgentDefinitionMonitor.ts +8 -0
  73. package/src/services/ConfigService.ts +34 -0
  74. package/src/services/PubkeyService.ts +7 -1
  75. package/src/services/compression/CompressionService.ts +133 -74
  76. package/src/services/compression/compression-utils.ts +110 -19
  77. package/src/services/config/types.ts +0 -6
  78. package/src/services/dispatch/AgentDispatchService.ts +79 -0
  79. package/src/services/intervention/InterventionService.ts +78 -5
  80. package/src/services/nip46/Nip46SigningService.ts +30 -1
  81. package/src/services/projects/ProjectContext.ts +8 -6
  82. package/src/services/rag/RAGCollectionRegistry.ts +199 -0
  83. package/src/services/rag/RAGDatabaseService.ts +2 -7
  84. package/src/services/rag/RAGOperations.ts +25 -45
  85. package/src/services/rag/RAGService.ts +0 -31
  86. package/src/services/rag/RagSubscriptionService.ts +71 -122
  87. package/src/services/rag/rag-utils.ts +13 -0
  88. package/src/services/ral/RALRegistry.ts +25 -184
  89. package/src/services/reports/ReportEmbeddingService.ts +63 -113
  90. package/src/services/search/UnifiedSearchService.ts +115 -4
  91. package/src/services/search/index.ts +1 -0
  92. package/src/services/search/projectFilter.ts +20 -4
  93. package/src/services/search/providers/ConversationSearchProvider.ts +1 -0
  94. package/src/services/search/providers/GenericCollectionSearchProvider.ts +81 -0
  95. package/src/services/search/providers/LessonSearchProvider.ts +1 -8
  96. package/src/services/search/providers/ReportSearchProvider.ts +1 -0
  97. package/src/services/search/types.ts +24 -3
  98. package/src/services/trust-pubkeys/SystemPubkeyListService.ts +148 -0
  99. package/src/services/trust-pubkeys/TrustPubkeyService.ts +70 -9
  100. package/src/telemetry/setup.ts +2 -13
  101. package/src/tools/implementations/ask.ts +3 -3
  102. package/src/tools/implementations/conversation_get.ts +28 -268
  103. package/src/tools/implementations/fs_grep.ts +6 -6
  104. package/src/tools/implementations/fs_read.ts +2 -0
  105. package/src/tools/implementations/fs_write.ts +2 -0
  106. package/src/tools/implementations/learn.ts +38 -50
  107. package/src/tools/implementations/rag_add_documents.ts +6 -4
  108. package/src/tools/implementations/rag_create_collection.ts +37 -4
  109. package/src/tools/implementations/rag_delete_collection.ts +9 -0
  110. package/src/tools/implementations/{search.ts → rag_search.ts} +31 -25
  111. package/src/tools/registry.ts +7 -8
  112. package/src/tools/types.ts +11 -2
  113. package/src/tools/utils/transcript-args.ts +13 -0
  114. package/src/utils/cli-theme.ts +13 -0
  115. package/src/utils/logger.ts +55 -0
  116. package/src/utils/metadataKeys.ts +17 -0
  117. package/src/utils/sqlEscaping.ts +39 -0
  118. package/src/wrapper.ts +7 -3
  119. package/dist/src/index.js +0 -46778
  120. package/dist/tenex-backend-wrapper.cjs +0 -3
  121. package/src/agents/execution/constants.ts +0 -16
  122. package/src/agents/execution/index.ts +0 -3
  123. package/src/agents/index.ts +0 -4
  124. package/src/commands/agent.ts +0 -215
  125. package/src/conversations/formatters/DelegationXmlFormatter.ts +0 -64
  126. package/src/conversations/formatters/index.ts +0 -9
  127. package/src/conversations/index.ts +0 -2
  128. package/src/conversations/utils/content-utils.ts +0 -69
  129. package/src/daemon/UnixSocketTransport.ts +0 -318
  130. package/src/event-handler/newConversation.ts +0 -165
  131. package/src/events/NDKProjectStatus.ts +0 -384
  132. package/src/events/index.ts +0 -4
  133. package/src/lib/json-parser.ts +0 -30
  134. package/src/llm/RecordingState.ts +0 -37
  135. package/src/llm/StreamPublisher.ts +0 -40
  136. package/src/llm/middleware/flight-recorder.ts +0 -188
  137. package/src/llm/utils/claudeCodePromptCompiler.ts +0 -141
  138. package/src/nostr/constants.ts +0 -38
  139. package/src/prompts/core/index.ts +0 -3
  140. package/src/prompts/index.ts +0 -21
  141. package/src/services/image/index.ts +0 -12
  142. package/src/services/status/index.ts +0 -11
  143. package/src/telemetry/diagnostics.ts +0 -27
  144. package/src/tools/implementations/rag_query.ts +0 -107
  145. package/src/types/index.ts +0 -46
  146. package/src/utils/agentFetcher.ts +0 -107
  147. package/src/utils/conversation-utils.ts +0 -1
  148. package/src/utils/process.ts +0 -49
@@ -1,3 +0,0 @@
1
- #!/usr/bin/env node
2
- "use strict";
3
- import("./src/index.js").catch(e => { console.error(e); process.exit(1); });
@@ -1,16 +0,0 @@
1
- /**
2
- * Configuration constants for agent execution
3
- */
4
- export const ExecutionConfig = {
5
- /** Delay in milliseconds after publishing typing indicator */
6
- TOOL_INDICATOR_DELAY_MS: 100,
7
-
8
- /** Default duration for tool execution when not tracked */
9
- DEFAULT_TOOL_DURATION_MS: 1000,
10
-
11
- /** Default timeout for shell commands in milliseconds (30 seconds) */
12
- DEFAULT_COMMAND_TIMEOUT_MS: 30000,
13
-
14
- /** Threshold for considering a phase transition as recent in milliseconds (30 seconds) */
15
- RECENT_TRANSITION_THRESHOLD_MS: 30000,
16
- } as const;
@@ -1,3 +0,0 @@
1
- export * from "./AgentExecutor";
2
- export * from "./ExecutionContextFactory";
3
- export * from "./types";
@@ -1,4 +0,0 @@
1
- export { AgentRegistry } from "./AgentRegistry";
2
- export * from "./execution";
3
- export type { AgentInstance, AgentSummary } from "./types/runtime";
4
- export type { AgentConfig, AgentConfigOptionalNsec, StoredAgentData } from "./types/storage";
@@ -1,215 +0,0 @@
1
- import * as fs from "node:fs/promises";
2
- import { existsSync } from "node:fs";
3
- import * as path from "node:path";
4
- import { homedir } from "node:os";
5
- import { Command } from "commander";
6
- import chalk from "chalk";
7
- import { NDKPrivateKeySigner, NDKEvent } from "@nostr-dev-kit/ndk";
8
- import { agentStorage, createStoredAgent } from "@/agents/AgentStorage";
9
- import { installAgentFromNostr, installAgentFromNostrEvent } from "@/agents/agent-installer";
10
- import { initNDK } from "@/nostr/ndkClient";
11
-
12
- // ─── OpenClaw discovery ──────────────────────────────────────────────────────
13
-
14
- const OPENCLAW_STATE_DIR_NAMES = [".openclaw", ".clawdbot", ".moldbot", ".moltbot"];
15
- const OPENCLAW_CONFIG_NAMES = ["openclaw.json", "clawdbot.json", "moldbot.json", "moltbot.json"];
16
-
17
- function findOpenClawStateDir(): string | undefined {
18
- // 1. Environment variable
19
- const envPath = process.env.OPENCLAW_STATE_DIR;
20
- if (envPath && hasOpenClawConfig(envPath)) {
21
- return envPath;
22
- }
23
-
24
- // 2. Home directory candidates
25
- const home = homedir();
26
- for (const name of OPENCLAW_STATE_DIR_NAMES) {
27
- const candidate = path.join(home, name);
28
- if (hasOpenClawConfig(candidate)) {
29
- return candidate;
30
- }
31
- }
32
-
33
- return undefined;
34
- }
35
-
36
- function hasOpenClawConfig(dir: string): boolean {
37
- return OPENCLAW_CONFIG_NAMES.some((name) => existsSync(path.join(dir, name)));
38
- }
39
-
40
- /** Prettify a slug for display: "main" → "Main", "my-agent" → "My Agent" */
41
- function prettifySlug(slug: string): string {
42
- return slug
43
- .replace(/-/g, " ")
44
- .replace(/\b\w/g, (c) => c.toUpperCase());
45
- }
46
-
47
- interface OpenClawAgent {
48
- name: string;
49
- slug: string;
50
- role: string;
51
- }
52
-
53
- async function discoverOpenClawAgents(stateDir: string): Promise<OpenClawAgent[]> {
54
- const agentsDir = path.join(stateDir, "agents");
55
- try {
56
- const entries = await fs.readdir(agentsDir, { withFileTypes: true });
57
- return entries
58
- .filter((e) => e.isDirectory())
59
- .map((e) => ({
60
- name: prettifySlug(e.name),
61
- slug: e.name,
62
- role: "",
63
- }));
64
- } catch {
65
- return [];
66
- }
67
- }
68
-
69
- // ─── tenex agent import openclaw ────────────────────────────────────────────
70
-
71
- async function importOpenClaw(options: {
72
- dryRun: boolean;
73
- json: boolean;
74
- slugs?: string;
75
- }): Promise<void> {
76
- const stateDir = findOpenClawStateDir();
77
-
78
- if (!stateDir) {
79
- if (options.json) {
80
- console.log("[]");
81
- } else {
82
- console.error(chalk.yellow("OpenClaw installation not found."));
83
- }
84
- return;
85
- }
86
-
87
- const agents = await discoverOpenClawAgents(stateDir);
88
-
89
- // Apply slug filter if provided
90
- const filtered =
91
- options.slugs
92
- ? agents.filter((a) => options.slugs!.split(",").map((s) => s.trim()).includes(a.slug))
93
- : agents;
94
-
95
- if (options.json) {
96
- console.log(JSON.stringify(filtered, null, 2));
97
- return;
98
- }
99
-
100
- if (filtered.length === 0) {
101
- console.log(chalk.yellow("No OpenClaw agents found."));
102
- return;
103
- }
104
-
105
- if (options.dryRun) {
106
- console.log(chalk.blue(`Would import ${filtered.length} OpenClaw agent(s):`));
107
- for (const agent of filtered) {
108
- console.log(chalk.gray(` ${agent.slug} → "${agent.name}"`));
109
- }
110
- return;
111
- }
112
-
113
- await agentStorage.initialize();
114
-
115
- let imported = 0;
116
- let skipped = 0;
117
-
118
- for (const agent of filtered) {
119
- // Check if already exists by slug
120
- const existing = await agentStorage.getAgentBySlug(agent.slug);
121
- if (existing) {
122
- console.log(chalk.gray(` ${agent.slug}: already exists, skipping`));
123
- skipped++;
124
- continue;
125
- }
126
-
127
- const signer = NDKPrivateKeySigner.generate();
128
- const stored = createStoredAgent({
129
- nsec: signer.nsec,
130
- slug: agent.slug,
131
- name: agent.name,
132
- role: agent.role,
133
- });
134
-
135
- await agentStorage.saveAgent(stored);
136
- console.log(
137
- chalk.green(` ✓ ${agent.slug}`) +
138
- chalk.gray(` → pubkey ${signer.pubkey.substring(0, 8)}...`)
139
- );
140
- imported++;
141
- }
142
-
143
- console.log(chalk.blue(`\nDone: ${imported} imported, ${skipped} skipped`));
144
- }
145
-
146
- // ─── tenex agent add ─────────────────────────────────────────────────────────
147
-
148
- async function readStdin(): Promise<string> {
149
- return new Promise((resolve, reject) => {
150
- let data = "";
151
- process.stdin.setEncoding("utf-8");
152
- process.stdin.on("data", (chunk) => {
153
- data += chunk;
154
- });
155
- process.stdin.on("end", () => resolve(data.trim()));
156
- process.stdin.on("error", reject);
157
- });
158
- }
159
-
160
- async function addAgent(eventId: string | undefined): Promise<void> {
161
- await agentStorage.initialize();
162
-
163
- if (!process.stdin.isTTY) {
164
- const raw = await readStdin();
165
- const rawEvent = JSON.parse(raw);
166
- const event = new NDKEvent(undefined, rawEvent);
167
- const stored = await installAgentFromNostrEvent(event);
168
- const pubkey = new NDKPrivateKeySigner(stored.nsec).pubkey;
169
- console.log(chalk.green(`✓ Installed agent "${stored.name}" (${stored.slug})`));
170
- console.log(chalk.gray(` pubkey: ${pubkey}`));
171
- return;
172
- }
173
-
174
- if (!eventId) {
175
- console.error(chalk.red("Error: provide an event ID or pipe event JSON via stdin"));
176
- process.exit(1);
177
- }
178
-
179
- await initNDK();
180
- const stored = await installAgentFromNostr(eventId);
181
- const pubkey = new NDKPrivateKeySigner(stored.nsec).pubkey;
182
- console.log(chalk.green(`✓ Installed agent "${stored.name}" (${stored.slug})`));
183
- console.log(chalk.gray(` pubkey: ${pubkey}`));
184
- }
185
-
186
- // ─── Command registration ────────────────────────────────────────────────────
187
-
188
- const importOpenClawCommand = new Command("openclaw")
189
- .description("Import agents from a local OpenClaw installation")
190
- .option("--dry-run", "Print what would be imported without making changes")
191
- .option("--json", "Output as JSON array")
192
- .option("--slugs <slugs>", "Comma-separated list of slugs to import (default: all)")
193
- .action(async (options) => {
194
- await importOpenClaw({
195
- dryRun: !!options.dryRun,
196
- json: !!options.json,
197
- slugs: options.slugs,
198
- });
199
- });
200
-
201
- const importCommand = new Command("import")
202
- .description("Import agents from external sources")
203
- .addCommand(importOpenClawCommand);
204
-
205
- const addCommand = new Command("add")
206
- .description("Install an agent from a Nostr event ID or stdin JSON")
207
- .argument("[event-id]", "Nostr event ID of the agent definition")
208
- .action(async (eventId: string | undefined) => {
209
- await addAgent(eventId);
210
- });
211
-
212
- export const agentCommand = new Command("agent")
213
- .description("Manage TENEX agents")
214
- .addCommand(importCommand)
215
- .addCommand(addCommand);
@@ -1,64 +0,0 @@
1
- interface DelegationResponse {
2
- from: string;
3
- content: string;
4
- eventId: string;
5
- status: "completed" | "error";
6
- }
7
-
8
- interface DelegationInfo {
9
- id: string;
10
- from: string;
11
- recipients: string[];
12
- phase?: string;
13
- message: string;
14
- requestEventId: string;
15
- responses: DelegationResponse[];
16
- }
17
-
18
- /**
19
- * Formats delegation information as structured XML for LLM comprehension
20
- */
21
- export class DelegationXmlFormatter {
22
- /**
23
- * Render a delegation with its responses as XML
24
- */
25
- static render(delegation: DelegationInfo, debug = false): string {
26
- const eventPrefix = debug ? `[Event ${delegation.requestEventId.substring(0, 8)}] ` : "";
27
- const recipientsAttr = delegation.recipients.join(",");
28
- const phaseAttr = delegation.phase ? ` phase="${delegation.phase}"` : "";
29
-
30
- let xml = `${eventPrefix}<delegation from="${delegation.from}" recipients="${recipientsAttr}" id="${delegation.id}"${phaseAttr}>`;
31
- xml += `\n <delegation-request>${DelegationXmlFormatter.escapeXml(delegation.message)}</delegation-request>`;
32
-
33
- // Add responses
34
- for (const response of delegation.responses) {
35
- const statusAttr = response.status === "error" ? ' error="true"' : "";
36
- const eventIdAttr = debug ? ` event-id="${response.eventId.substring(0, 8)}"` : "";
37
- xml += `\n <response from="${response.from}"${statusAttr}${eventIdAttr}>${DelegationXmlFormatter.escapeXml(response.content)}</response>`;
38
- }
39
-
40
- // Add pending placeholders for recipients without responses
41
- const respondedFrom = new Set(delegation.responses.map((r) => r.from));
42
- for (const recipient of delegation.recipients) {
43
- if (!respondedFrom.has(recipient)) {
44
- xml += `\n <response from="${recipient}" status="pending" />`;
45
- }
46
- }
47
-
48
- xml += "\n</delegation>";
49
-
50
- return xml;
51
- }
52
-
53
- /**
54
- * Escape XML special characters
55
- */
56
- private static escapeXml(text: string): string {
57
- return text
58
- .replace(/&/g, "&amp;")
59
- .replace(/</g, "&lt;")
60
- .replace(/>/g, "&gt;")
61
- .replace(/"/g, "&quot;")
62
- .replace(/'/g, "&apos;");
63
- }
64
- }
@@ -1,9 +0,0 @@
1
- export {
2
- ThreadedConversationFormatter,
3
- type ThreadNode,
4
- type FormatterOptions,
5
- } from "./ThreadedConversationFormatter";
6
- export { TreeBuilder } from "./utils/TreeBuilder";
7
- export { MessageFormatter } from "./utils/MessageFormatter";
8
- export { TimestampFormatter } from "./utils/TimestampFormatter";
9
- export { TreeRenderer } from "./utils/TreeRenderer";
@@ -1,2 +0,0 @@
1
- export { ConversationStore } from "./ConversationStore";
2
- export type { ConversationMetadata, ConversationEntry } from "./types";
@@ -1,69 +0,0 @@
1
- /**
2
- * Content utilities for processing conversation messages
3
- * Purpose: Strip <thinking>...</thinking> blocks from conversation history; skip messages that are purely thinking blocks.
4
- * Also filter out events with reasoning tags.
5
- */
6
-
7
- import type { NDKEvent } from "@nostr-dev-kit/ndk";
8
-
9
- /**
10
- * Regex pattern to match thinking blocks (case-insensitive, multi-line)
11
- * Matches: <thinking>, <Thinking>, <THINKING> with any attributes and their closing tags
12
- */
13
- const THINKING_BLOCK_REGEX = /<thinking\b[^>]*>[\s\S]*?<\/thinking>/gi;
14
-
15
- /**
16
- * Remove all thinking blocks from content
17
- * @param content - The content to process
18
- * @returns The content with all thinking blocks removed and normalized whitespace (multiple blank lines collapsed to single newline)
19
- */
20
- export function stripThinkingBlocks(content: string): string {
21
- if (!content) return "";
22
-
23
- // Remove all thinking blocks
24
- let stripped = content.replace(THINKING_BLOCK_REGEX, "");
25
-
26
- // Normalize whitespace more carefully:
27
- // 1. Only collapse multiple spaces that aren't at the beginning of a line (preserve indentation)
28
- // 2. Collapse multiple blank lines to a single newline
29
- stripped = stripped
30
- .split("\n")
31
- .map((line) => {
32
- // Only collapse spaces in the middle of lines, not at the start (preserve indentation)
33
- if (line.trimStart() !== line) {
34
- // Line has leading whitespace - preserve it
35
- const leadingWhitespace = line.match(/^\s*/)?.[0] || "";
36
- const rest = line.slice(leadingWhitespace.length);
37
- return leadingWhitespace + rest.replace(/ {2,}/g, " ");
38
- }
39
- // No leading whitespace - collapse all multiple spaces
40
- return line.replace(/ {2,}/g, " ");
41
- })
42
- .join("\n")
43
- .replace(/\n\s*\n+/g, "\n") // Collapse 2+ newlines to single newline
44
- .trim(); // Trim leading/trailing whitespace
45
-
46
- return stripped;
47
- }
48
-
49
- /**
50
- * Check if content contains only thinking blocks (no other content)
51
- * @param content - The content to check
52
- * @returns True if the content is empty after removing thinking blocks
53
- */
54
- export function isOnlyThinkingBlocks(content: string): boolean {
55
- if (!content || content.trim().length === 0) return false; // Empty/whitespace content is not "only thinking blocks"
56
-
57
- const stripped = stripThinkingBlocks(content);
58
- return stripped.length === 0;
59
- }
60
-
61
- /**
62
- * Check if an event has a reasoning tag
63
- * @param event - The NDK event to check
64
- * @returns True if the event has a ["reasoning"] tag
65
- */
66
- export function hasReasoningTag(event: NDKEvent): boolean {
67
- if (!event.tags) return false;
68
- return event.tags.some((tag) => tag[0] === "reasoning" && tag.length === 1);
69
- }