@tenex-chat/backend 0.9.1

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 (427) hide show
  1. package/README.md +194 -0
  2. package/dist/backend-wrapper.cjs +3 -0
  3. package/dist/src/index.js +331928 -0
  4. package/package.json +103 -0
  5. package/src/agents/AgentRegistry.ts +418 -0
  6. package/src/agents/AgentStorage.ts +1133 -0
  7. package/src/agents/ConfigResolver.ts +229 -0
  8. package/src/agents/agent-installer.ts +236 -0
  9. package/src/agents/agent-loader.ts +241 -0
  10. package/src/agents/constants.ts +82 -0
  11. package/src/agents/errors.ts +48 -0
  12. package/src/agents/execution/AgentExecutor.ts +561 -0
  13. package/src/agents/execution/ExecutionContextFactory.ts +112 -0
  14. package/src/agents/execution/MessageCompiler.ts +597 -0
  15. package/src/agents/execution/MessageSyncer.ts +100 -0
  16. package/src/agents/execution/PostCompletionChecker.ts +278 -0
  17. package/src/agents/execution/ProgressMonitor.ts +50 -0
  18. package/src/agents/execution/RALResolver.ts +177 -0
  19. package/src/agents/execution/SessionManager.ts +181 -0
  20. package/src/agents/execution/StreamCallbacks.ts +312 -0
  21. package/src/agents/execution/StreamExecutionHandler.ts +579 -0
  22. package/src/agents/execution/StreamSetup.ts +313 -0
  23. package/src/agents/execution/ToolEventHandlers.ts +239 -0
  24. package/src/agents/execution/ToolExecutionTracker.ts +498 -0
  25. package/src/agents/execution/ToolResultUtils.ts +97 -0
  26. package/src/agents/execution/ToolSupervisionWrapper.ts +174 -0
  27. package/src/agents/execution/constants.ts +16 -0
  28. package/src/agents/execution/index.ts +3 -0
  29. package/src/agents/execution/types.ts +96 -0
  30. package/src/agents/execution/utils.ts +26 -0
  31. package/src/agents/index.ts +4 -0
  32. package/src/agents/script-installer.ts +266 -0
  33. package/src/agents/supervision/SupervisorLLMService.ts +253 -0
  34. package/src/agents/supervision/SupervisorOrchestrator.ts +471 -0
  35. package/src/agents/supervision/heuristics/ConsecutiveToolsWithoutTodoHeuristic.ts +73 -0
  36. package/src/agents/supervision/heuristics/DelegationClaimHeuristic.ts +80 -0
  37. package/src/agents/supervision/heuristics/HeuristicRegistry.ts +114 -0
  38. package/src/agents/supervision/heuristics/PendingTodosHeuristic.ts +93 -0
  39. package/src/agents/supervision/heuristics/SilentAgentHeuristic.ts +54 -0
  40. package/src/agents/supervision/heuristics/index.ts +5 -0
  41. package/src/agents/supervision/index.ts +28 -0
  42. package/src/agents/supervision/registerHeuristics.ts +110 -0
  43. package/src/agents/supervision/supervisionHealthCheck.ts +123 -0
  44. package/src/agents/supervision/types.ts +171 -0
  45. package/src/agents/tool-names.ts +46 -0
  46. package/src/agents/tool-normalization.ts +184 -0
  47. package/src/agents/types/index.ts +2 -0
  48. package/src/agents/types/runtime.ts +74 -0
  49. package/src/agents/types/storage.ts +145 -0
  50. package/src/commands/agent/import/index.ts +6 -0
  51. package/src/commands/agent/import/openclaw-distiller.ts +57 -0
  52. package/src/commands/agent/import/openclaw-reader.ts +141 -0
  53. package/src/commands/agent/import/openclaw.ts +154 -0
  54. package/src/commands/agent/index.ts +6 -0
  55. package/src/commands/agent.ts +215 -0
  56. package/src/commands/daemon.ts +198 -0
  57. package/src/commands/doctor.ts +134 -0
  58. package/src/commands/setup/embed.ts +228 -0
  59. package/src/commands/setup/global-system-prompt.ts +223 -0
  60. package/src/commands/setup/image.ts +179 -0
  61. package/src/commands/setup/index.ts +16 -0
  62. package/src/commands/setup/interactive.ts +95 -0
  63. package/src/commands/setup/llm.ts +38 -0
  64. package/src/commands/setup/onboarding.ts +294 -0
  65. package/src/commands/setup/providers.ts +27 -0
  66. package/src/constants.ts +34 -0
  67. package/src/conversations/ConversationDiskReader.ts +148 -0
  68. package/src/conversations/ConversationRegistry.ts +728 -0
  69. package/src/conversations/ConversationStore.ts +868 -0
  70. package/src/conversations/MessageBuilder.ts +866 -0
  71. package/src/conversations/executionTime.ts +62 -0
  72. package/src/conversations/formatters/DelegationXmlFormatter.ts +64 -0
  73. package/src/conversations/formatters/ThreadedConversationFormatter.ts +303 -0
  74. package/src/conversations/formatters/index.ts +9 -0
  75. package/src/conversations/formatters/utils/MessageFormatter.ts +46 -0
  76. package/src/conversations/formatters/utils/TimestampFormatter.ts +56 -0
  77. package/src/conversations/formatters/utils/TreeBuilder.ts +131 -0
  78. package/src/conversations/formatters/utils/TreeRenderer.ts +49 -0
  79. package/src/conversations/index.ts +2 -0
  80. package/src/conversations/persistence/ToolMessageStorage.ts +143 -0
  81. package/src/conversations/search/ConversationIndexManager.ts +393 -0
  82. package/src/conversations/search/QueryParser.ts +114 -0
  83. package/src/conversations/search/SearchEngine.ts +175 -0
  84. package/src/conversations/search/SnippetExtractor.ts +345 -0
  85. package/src/conversations/search/embeddings/ConversationEmbeddingService.ts +484 -0
  86. package/src/conversations/search/embeddings/ConversationIndexingJob.ts +320 -0
  87. package/src/conversations/search/embeddings/IndexingStateManager.ts +338 -0
  88. package/src/conversations/search/embeddings/index.ts +18 -0
  89. package/src/conversations/search/index.ts +49 -0
  90. package/src/conversations/search/types.ts +124 -0
  91. package/src/conversations/services/CategoryManager.ts +160 -0
  92. package/src/conversations/services/ConversationResolver.ts +296 -0
  93. package/src/conversations/services/ConversationSummarizer.ts +234 -0
  94. package/src/conversations/services/MetadataDebounceManager.ts +188 -0
  95. package/src/conversations/services/index.ts +2 -0
  96. package/src/conversations/types.ts +148 -0
  97. package/src/conversations/utils/content-utils.ts +69 -0
  98. package/src/conversations/utils/image-placeholder.ts +281 -0
  99. package/src/conversations/utils/image-url-utils.ts +171 -0
  100. package/src/conversations/utils/multimodal-content.ts +90 -0
  101. package/src/conversations/utils/tool-result-truncator.ts +159 -0
  102. package/src/daemon/Daemon.ts +1883 -0
  103. package/src/daemon/ProjectRuntime.ts +657 -0
  104. package/src/daemon/RestartState.ts +152 -0
  105. package/src/daemon/RuntimeLifecycle.ts +268 -0
  106. package/src/daemon/SubscriptionManager.ts +305 -0
  107. package/src/daemon/UnixSocketTransport.ts +318 -0
  108. package/src/daemon/filters/SubscriptionFilterBuilder.ts +119 -0
  109. package/src/daemon/index.ts +9 -0
  110. package/src/daemon/routing/DaemonRouter.ts +491 -0
  111. package/src/daemon/types.ts +150 -0
  112. package/src/daemon/utils/routing-log.ts +76 -0
  113. package/src/daemon/utils/telemetry.ts +173 -0
  114. package/src/event-handler/agentDeletion.ts +383 -0
  115. package/src/event-handler/index.ts +749 -0
  116. package/src/event-handler/newConversation.ts +165 -0
  117. package/src/event-handler/project.ts +166 -0
  118. package/src/event-handler/reply.ts +18 -0
  119. package/src/events/NDKAgentDefinition.ts +292 -0
  120. package/src/events/NDKAgentLesson.ts +106 -0
  121. package/src/events/NDKEventMetadata.ts +34 -0
  122. package/src/events/NDKMCPTool.ts +60 -0
  123. package/src/events/NDKProjectStatus.ts +384 -0
  124. package/src/events/index.ts +4 -0
  125. package/src/index.ts +126 -0
  126. package/src/lib/agent-home.ts +334 -0
  127. package/src/lib/error-formatter.ts +200 -0
  128. package/src/lib/fs/filesystem.ts +128 -0
  129. package/src/lib/fs/index.ts +1 -0
  130. package/src/lib/json-parser.ts +30 -0
  131. package/src/lib/string.ts +15 -0
  132. package/src/lib/time.ts +74 -0
  133. package/src/llm/ChunkHandler.ts +277 -0
  134. package/src/llm/FinishHandler.ts +250 -0
  135. package/src/llm/LLMConfigEditor.ts +154 -0
  136. package/src/llm/LLMServiceFactory.ts +230 -0
  137. package/src/llm/MessageProcessor.ts +90 -0
  138. package/src/llm/RecordingState.ts +37 -0
  139. package/src/llm/StreamPublisher.ts +40 -0
  140. package/src/llm/TracingUtils.ts +77 -0
  141. package/src/llm/chunk-validators.ts +57 -0
  142. package/src/llm/constants.ts +6 -0
  143. package/src/llm/index.ts +12 -0
  144. package/src/llm/meta/MetaModelResolver.ts +352 -0
  145. package/src/llm/meta/index.ts +11 -0
  146. package/src/llm/middleware/flight-recorder.ts +188 -0
  147. package/src/llm/providers/MockProvider.ts +332 -0
  148. package/src/llm/providers/agent/ClaudeCodeProvider.ts +343 -0
  149. package/src/llm/providers/agent/ClaudeCodeToolsAdapter.ts +203 -0
  150. package/src/llm/providers/agent/CodexAppServerProvider.ts +214 -0
  151. package/src/llm/providers/agent/CodexAppServerToolsAdapter.ts +91 -0
  152. package/src/llm/providers/agent/index.ts +10 -0
  153. package/src/llm/providers/base/AgentProvider.ts +107 -0
  154. package/src/llm/providers/base/BaseProvider.ts +114 -0
  155. package/src/llm/providers/base/StandardProvider.ts +38 -0
  156. package/src/llm/providers/base/index.ts +9 -0
  157. package/src/llm/providers/index.ts +106 -0
  158. package/src/llm/providers/key-manager.ts +238 -0
  159. package/src/llm/providers/ollama-models.ts +105 -0
  160. package/src/llm/providers/openrouter-models.ts +102 -0
  161. package/src/llm/providers/provider-ids.ts +18 -0
  162. package/src/llm/providers/registry/ProviderRegistry.ts +414 -0
  163. package/src/llm/providers/registry/index.ts +7 -0
  164. package/src/llm/providers/standard/AnthropicProvider.ts +71 -0
  165. package/src/llm/providers/standard/OllamaProvider.ts +59 -0
  166. package/src/llm/providers/standard/OpenAIProvider.ts +44 -0
  167. package/src/llm/providers/standard/OpenRouterProvider.ts +103 -0
  168. package/src/llm/providers/standard/index.ts +10 -0
  169. package/src/llm/providers/types.ts +194 -0
  170. package/src/llm/providers/usage-metadata.ts +78 -0
  171. package/src/llm/service.ts +713 -0
  172. package/src/llm/types.ts +167 -0
  173. package/src/llm/utils/ConfigurationManager.ts +650 -0
  174. package/src/llm/utils/ConfigurationTester.ts +229 -0
  175. package/src/llm/utils/ModelSelector.ts +212 -0
  176. package/src/llm/utils/ProviderConfigUI.ts +177 -0
  177. package/src/llm/utils/claudeCodePromptCompiler.ts +141 -0
  178. package/src/llm/utils/codex-models.ts +53 -0
  179. package/src/llm/utils/context-window-cache.ts +30 -0
  180. package/src/llm/utils/models-dev-cache.ts +267 -0
  181. package/src/llm/utils/provider-setup.ts +50 -0
  182. package/src/llm/utils/tool-errors.ts +78 -0
  183. package/src/llm/utils/usage.ts +74 -0
  184. package/src/logging/EventRoutingLogger.ts +205 -0
  185. package/src/nostr/AgentEventDecoder.ts +357 -0
  186. package/src/nostr/AgentEventEncoder.ts +677 -0
  187. package/src/nostr/AgentProfilePublisher.ts +657 -0
  188. package/src/nostr/AgentPublisher.ts +437 -0
  189. package/src/nostr/BlossomService.ts +226 -0
  190. package/src/nostr/InterventionPublisher.ts +132 -0
  191. package/src/nostr/TagExtractor.ts +228 -0
  192. package/src/nostr/collectEvents.ts +83 -0
  193. package/src/nostr/constants.ts +38 -0
  194. package/src/nostr/encryption.ts +26 -0
  195. package/src/nostr/index.ts +31 -0
  196. package/src/nostr/keys.ts +17 -0
  197. package/src/nostr/kinds.ts +37 -0
  198. package/src/nostr/ndkClient.ts +72 -0
  199. package/src/nostr/relays.ts +43 -0
  200. package/src/nostr/trace-context.ts +39 -0
  201. package/src/nostr/types.ts +227 -0
  202. package/src/nostr/utils.ts +84 -0
  203. package/src/prompts/core/FragmentRegistry.ts +30 -0
  204. package/src/prompts/core/PromptBuilder.ts +98 -0
  205. package/src/prompts/core/index.ts +3 -0
  206. package/src/prompts/core/types.ts +13 -0
  207. package/src/prompts/fragments/00-global-system-prompt.ts +44 -0
  208. package/src/prompts/fragments/01-agent-identity.ts +69 -0
  209. package/src/prompts/fragments/02-agent-home-directory.ts +114 -0
  210. package/src/prompts/fragments/03-system-reminders-explanation.ts +14 -0
  211. package/src/prompts/fragments/04-relay-configuration.ts +38 -0
  212. package/src/prompts/fragments/05-delegation-chain.ts +45 -0
  213. package/src/prompts/fragments/06-agent-todos.ts +74 -0
  214. package/src/prompts/fragments/06-todo-usage-guidance.ts +34 -0
  215. package/src/prompts/fragments/07-meta-project-context.ts +234 -0
  216. package/src/prompts/fragments/08-active-conversations.ts +382 -0
  217. package/src/prompts/fragments/09-recent-conversations.ts +153 -0
  218. package/src/prompts/fragments/10-referenced-article.ts +21 -0
  219. package/src/prompts/fragments/11-nudges.ts +134 -0
  220. package/src/prompts/fragments/12-skills.ts +127 -0
  221. package/src/prompts/fragments/13-available-nudges.ts +122 -0
  222. package/src/prompts/fragments/15-available-agents.ts +53 -0
  223. package/src/prompts/fragments/16-stay-in-your-lane.ts +41 -0
  224. package/src/prompts/fragments/17-todo-before-delegation.ts +39 -0
  225. package/src/prompts/fragments/20-voice-mode.ts +62 -0
  226. package/src/prompts/fragments/22-scheduled-tasks.ts +175 -0
  227. package/src/prompts/fragments/24-retrieved-lessons.ts +26 -0
  228. package/src/prompts/fragments/25-rag-instructions.ts +333 -0
  229. package/src/prompts/fragments/26-mcp-resources.ts +237 -0
  230. package/src/prompts/fragments/27-memorized-reports.ts +77 -0
  231. package/src/prompts/fragments/28-agent-directed-monitoring.ts +32 -0
  232. package/src/prompts/fragments/29-rag-collections.ts +50 -0
  233. package/src/prompts/fragments/30-worktree-context.ts +98 -0
  234. package/src/prompts/fragments/31-agents-md-guidance.ts +96 -0
  235. package/src/prompts/fragments/32-process-metrics.ts +72 -0
  236. package/src/prompts/fragments/debug-mode.ts +48 -0
  237. package/src/prompts/fragments/delegation-completion.ts +44 -0
  238. package/src/prompts/fragments/index.ts +91 -0
  239. package/src/prompts/index.ts +21 -0
  240. package/src/prompts/utils/systemPromptBuilder.ts +777 -0
  241. package/src/scripts/migrate-prefix-index.ts +157 -0
  242. package/src/services/AgentDefinitionMonitor.ts +701 -0
  243. package/src/services/ConfigService.ts +723 -0
  244. package/src/services/CooldownRegistry.ts +199 -0
  245. package/src/services/LLMOperationsRegistry.ts +424 -0
  246. package/src/services/OwnerAgentListService.ts +354 -0
  247. package/src/services/PubkeyService.ts +308 -0
  248. package/src/services/agents/AgentMetadataStore.ts +72 -0
  249. package/src/services/agents/AgentResolution.ts +59 -0
  250. package/src/services/agents/EscalationService.ts +281 -0
  251. package/src/services/agents/NDKAgentDiscovery.ts +95 -0
  252. package/src/services/agents/index.ts +7 -0
  253. package/src/services/agents-md/AgentsMdService.ts +184 -0
  254. package/src/services/agents-md/SystemReminderInjector.ts +238 -0
  255. package/src/services/agents-md/index.ts +11 -0
  256. package/src/services/apns/APNsClient.ts +203 -0
  257. package/src/services/apns/APNsService.ts +358 -0
  258. package/src/services/apns/index.ts +11 -0
  259. package/src/services/apns/types.ts +80 -0
  260. package/src/services/compression/CompressionService.ts +445 -0
  261. package/src/services/compression/compression-schema.ts +28 -0
  262. package/src/services/compression/compression-types.ts +74 -0
  263. package/src/services/compression/compression-utils.ts +587 -0
  264. package/src/services/config/types.ts +394 -0
  265. package/src/services/dispatch/AgentDispatchService.ts +937 -0
  266. package/src/services/dispatch/AgentRouter.ts +181 -0
  267. package/src/services/dispatch/DelegationCompletionHandler.ts +232 -0
  268. package/src/services/embedding/EmbeddingProvider.ts +188 -0
  269. package/src/services/embedding/index.ts +5 -0
  270. package/src/services/event-context/EventContextService.ts +108 -0
  271. package/src/services/event-context/index.ts +2 -0
  272. package/src/services/heuristics/ContextBuilder.ts +106 -0
  273. package/src/services/heuristics/HeuristicEngine.ts +200 -0
  274. package/src/services/heuristics/formatters.ts +58 -0
  275. package/src/services/heuristics/index.ts +12 -0
  276. package/src/services/heuristics/rules/index.ts +25 -0
  277. package/src/services/heuristics/rules/todoBeforeDelegation.ts +69 -0
  278. package/src/services/heuristics/rules/todoReminderOnToolUse.ts +63 -0
  279. package/src/services/heuristics/types.ts +144 -0
  280. package/src/services/image/ImageGenerationService.ts +389 -0
  281. package/src/services/image/index.ts +12 -0
  282. package/src/services/intervention/InterventionService.ts +1352 -0
  283. package/src/services/intervention/index.ts +7 -0
  284. package/src/services/mcp/MCPManager.ts +683 -0
  285. package/src/services/mcp/McpNotificationDelivery.ts +139 -0
  286. package/src/services/mcp/McpSubscriptionService.ts +653 -0
  287. package/src/services/mcp/mcpInstaller.ts +130 -0
  288. package/src/services/nip46/Nip46SigningLog.ts +81 -0
  289. package/src/services/nip46/Nip46SigningService.ts +467 -0
  290. package/src/services/nip46/index.ts +4 -0
  291. package/src/services/nudge/NudgeService.ts +224 -0
  292. package/src/services/nudge/NudgeWhitelistService.ts +382 -0
  293. package/src/services/nudge/index.ts +5 -0
  294. package/src/services/nudge/types.ts +83 -0
  295. package/src/services/projects/ProjectContext.ts +672 -0
  296. package/src/services/projects/ProjectContextStore.ts +102 -0
  297. package/src/services/projects/index.ts +6 -0
  298. package/src/services/prompt-compiler/index.ts +15 -0
  299. package/src/services/prompt-compiler/prompt-compiler-service.ts +1143 -0
  300. package/src/services/pubkey-gate/PubkeyGateService.ts +93 -0
  301. package/src/services/pubkey-gate/index.ts +1 -0
  302. package/src/services/rag/EmbeddingProviderFactory.ts +292 -0
  303. package/src/services/rag/LanceDBMaintenanceService.ts +211 -0
  304. package/src/services/rag/RAGDatabaseService.ts +173 -0
  305. package/src/services/rag/RAGOperations.ts +682 -0
  306. package/src/services/rag/RAGService.ts +240 -0
  307. package/src/services/rag/RagSubscriptionService.ts +618 -0
  308. package/src/services/rag/rag-utils.ts +174 -0
  309. package/src/services/ral/PendingDelegationsRegistry.ts +168 -0
  310. package/src/services/ral/RALRegistry.ts +2782 -0
  311. package/src/services/ral/index.ts +4 -0
  312. package/src/services/ral/types.ts +292 -0
  313. package/src/services/reports/LocalReportStore.ts +380 -0
  314. package/src/services/reports/ReportEmbeddingService.ts +430 -0
  315. package/src/services/reports/ReportService.ts +440 -0
  316. package/src/services/reports/articleUtils.ts +52 -0
  317. package/src/services/reports/index.ts +7 -0
  318. package/src/services/scheduling/SchedulerService.ts +1057 -0
  319. package/src/services/scheduling/errors.ts +14 -0
  320. package/src/services/scheduling/index.ts +7 -0
  321. package/src/services/scheduling/utils.ts +77 -0
  322. package/src/services/search/SearchProviderRegistry.ts +78 -0
  323. package/src/services/search/UnifiedSearchService.ts +218 -0
  324. package/src/services/search/index.ts +47 -0
  325. package/src/services/search/projectFilter.ts +22 -0
  326. package/src/services/search/providers/ConversationSearchProvider.ts +48 -0
  327. package/src/services/search/providers/LessonSearchProvider.ts +75 -0
  328. package/src/services/search/providers/ReportSearchProvider.ts +49 -0
  329. package/src/services/search/types.ts +144 -0
  330. package/src/services/skill/SkillService.ts +482 -0
  331. package/src/services/skill/index.ts +2 -0
  332. package/src/services/skill/types.ts +70 -0
  333. package/src/services/status/OperationsStatusService.ts +276 -0
  334. package/src/services/status/ProjectStatusService.ts +522 -0
  335. package/src/services/status/index.ts +11 -0
  336. package/src/services/storage/PrefixKVStore.ts +242 -0
  337. package/src/services/storage/index.ts +1 -0
  338. package/src/services/system-reminder/SystemReminderUtils.ts +96 -0
  339. package/src/services/system-reminder/index.ts +7 -0
  340. package/src/services/trust-pubkeys/TrustPubkeyService.ts +325 -0
  341. package/src/services/trust-pubkeys/index.ts +2 -0
  342. package/src/telemetry/ConversationSpanManager.ts +111 -0
  343. package/src/telemetry/EventLoopMonitor.ts +206 -0
  344. package/src/telemetry/LLMSpanRegistry.ts +20 -0
  345. package/src/telemetry/NostrSpanProcessor.ts +89 -0
  346. package/src/telemetry/ToolCallSpanProcessor.ts +66 -0
  347. package/src/telemetry/diagnostics.ts +27 -0
  348. package/src/telemetry/setup.ts +120 -0
  349. package/src/tools/implementations/agents_discover.ts +121 -0
  350. package/src/tools/implementations/agents_hire.ts +127 -0
  351. package/src/tools/implementations/agents_list.ts +96 -0
  352. package/src/tools/implementations/agents_publish.ts +611 -0
  353. package/src/tools/implementations/agents_read.ts +173 -0
  354. package/src/tools/implementations/agents_write.ts +200 -0
  355. package/src/tools/implementations/ask.ts +411 -0
  356. package/src/tools/implementations/change_model.ts +141 -0
  357. package/src/tools/implementations/conversation_get.ts +661 -0
  358. package/src/tools/implementations/conversation_list.ts +377 -0
  359. package/src/tools/implementations/conversation_search.ts +370 -0
  360. package/src/tools/implementations/delegate.ts +327 -0
  361. package/src/tools/implementations/delegate_crossproject.ts +209 -0
  362. package/src/tools/implementations/delegate_followup.ts +300 -0
  363. package/src/tools/implementations/fs_edit.ts +162 -0
  364. package/src/tools/implementations/fs_glob.ts +182 -0
  365. package/src/tools/implementations/fs_grep.ts +513 -0
  366. package/src/tools/implementations/fs_read.ts +332 -0
  367. package/src/tools/implementations/fs_write.ts +113 -0
  368. package/src/tools/implementations/generate_image.ts +259 -0
  369. package/src/tools/implementations/home_fs.ts +515 -0
  370. package/src/tools/implementations/kill.ts +651 -0
  371. package/src/tools/implementations/learn.ts +166 -0
  372. package/src/tools/implementations/lesson-formatter.ts +38 -0
  373. package/src/tools/implementations/lesson_delete.ts +164 -0
  374. package/src/tools/implementations/lesson_get.ts +105 -0
  375. package/src/tools/implementations/lessons_list.ts +153 -0
  376. package/src/tools/implementations/mcp_resource_read.ts +161 -0
  377. package/src/tools/implementations/mcp_subscribe.ts +158 -0
  378. package/src/tools/implementations/mcp_subscription_stop.ts +85 -0
  379. package/src/tools/implementations/nostr_fetch.ts +149 -0
  380. package/src/tools/implementations/nostr_publish_as_user.ts +353 -0
  381. package/src/tools/implementations/project_list.ts +146 -0
  382. package/src/tools/implementations/rag_add_documents.ts +573 -0
  383. package/src/tools/implementations/rag_create_collection.ts +65 -0
  384. package/src/tools/implementations/rag_delete_collection.ts +68 -0
  385. package/src/tools/implementations/rag_list_collections.ts +77 -0
  386. package/src/tools/implementations/rag_query.ts +107 -0
  387. package/src/tools/implementations/rag_subscription_create.ts +105 -0
  388. package/src/tools/implementations/rag_subscription_delete.ts +80 -0
  389. package/src/tools/implementations/rag_subscription_get.ts +123 -0
  390. package/src/tools/implementations/rag_subscription_list.ts +128 -0
  391. package/src/tools/implementations/report_delete.ts +79 -0
  392. package/src/tools/implementations/report_read.ts +160 -0
  393. package/src/tools/implementations/report_write.ts +278 -0
  394. package/src/tools/implementations/reports_list.ts +77 -0
  395. package/src/tools/implementations/schedule_task.ts +104 -0
  396. package/src/tools/implementations/schedule_task_cancel.ts +62 -0
  397. package/src/tools/implementations/schedule_task_once.ts +128 -0
  398. package/src/tools/implementations/schedule_tasks_list.ts +79 -0
  399. package/src/tools/implementations/search.ts +160 -0
  400. package/src/tools/implementations/shell.ts +553 -0
  401. package/src/tools/implementations/todo.ts +260 -0
  402. package/src/tools/implementations/upload_blob.ts +381 -0
  403. package/src/tools/implementations/web_fetch.ts +153 -0
  404. package/src/tools/implementations/web_search.ts +250 -0
  405. package/src/tools/registry.ts +670 -0
  406. package/src/tools/types.ts +177 -0
  407. package/src/tools/utils.ts +256 -0
  408. package/src/types/event-ids.ts +320 -0
  409. package/src/types/index.ts +46 -0
  410. package/src/utils/agentFetcher.ts +107 -0
  411. package/src/utils/cli-error.ts +29 -0
  412. package/src/utils/conversation-id.ts +27 -0
  413. package/src/utils/conversation-utils.ts +1 -0
  414. package/src/utils/delegation-chain.ts +357 -0
  415. package/src/utils/error-handler.ts +42 -0
  416. package/src/utils/git/gitignore.ts +69 -0
  417. package/src/utils/git/index.ts +2 -0
  418. package/src/utils/git/initializeGitRepo.ts +204 -0
  419. package/src/utils/git/worktree.ts +260 -0
  420. package/src/utils/lessonFormatter.ts +70 -0
  421. package/src/utils/lessonTrust.ts +24 -0
  422. package/src/utils/lockfile.ts +123 -0
  423. package/src/utils/logger.ts +149 -0
  424. package/src/utils/nostr-entity-parser.ts +365 -0
  425. package/src/utils/process.ts +49 -0
  426. package/src/wrapper.ts +262 -0
  427. package/tsconfig.json +41 -0
@@ -0,0 +1,382 @@
1
+ import type { AgentInstance } from "@/agents/types";
2
+ import { conversationRegistry } from "@/conversations/ConversationRegistry";
3
+ import type { DelegationChainEntry } from "@/conversations/types";
4
+ import { formatRelativeTimeShort } from "@/lib/time";
5
+ import { RALRegistry } from "@/services/ral/RALRegistry";
6
+ import { getPubkeyService } from "@/services/PubkeyService";
7
+ import { shortenConversationId } from "@/utils/conversation-id";
8
+ import { logger } from "@/utils/logger";
9
+ import type { PromptFragment } from "../core/types";
10
+
11
+ /**
12
+ * Active conversations fragment - provides context about currently active
13
+ * conversations (agents actively streaming/working) in the project.
14
+ *
15
+ * Displays conversations as a hierarchical delegation tree with compact formatting.
16
+ */
17
+
18
+ interface ActiveConversationsArgs {
19
+ agent: AgentInstance;
20
+ currentConversationId?: string;
21
+ projectId?: string;
22
+ }
23
+
24
+ interface ActiveConversationEntry {
25
+ conversationId: string;
26
+ title?: string;
27
+ summary?: string;
28
+ agentName: string;
29
+ agentPubkey: string;
30
+ isStreaming: boolean;
31
+ currentTool?: string;
32
+ startedAt: number;
33
+ lastActivityAt: number;
34
+ messageCount: number;
35
+ parentConversationId?: string;
36
+ }
37
+
38
+ interface ConversationTreeNode {
39
+ entry: ActiveConversationEntry;
40
+ children: ConversationTreeNode[];
41
+ }
42
+
43
+ const MAX_CONVERSATIONS = 10;
44
+ const MAX_SUMMARY_LENGTH = 200;
45
+ const ELLIPSIS = "...";
46
+ const STALE_THRESHOLD_MS = 30 * 60 * 1000; // 30 minutes
47
+
48
+ /**
49
+ * Sanitize text for safe inclusion in system prompt.
50
+ * Prevents prompt injection by:
51
+ * - Stripping/normalizing newlines
52
+ * - Trimming excessive whitespace
53
+ * - Clamping length (result is at most maxLength chars, including ellipsis if truncated)
54
+ */
55
+ function sanitizeForPrompt(text: string, maxLength: number = MAX_SUMMARY_LENGTH): string {
56
+ // Strip newlines and normalize whitespace
57
+ let sanitized = text
58
+ .replace(/[\r\n]+/g, " ") // Replace newlines with spaces
59
+ .replace(/\s+/g, " ") // Collapse multiple spaces
60
+ .trim();
61
+
62
+ // Clamp length - ensure total output (including ellipsis) doesn't exceed maxLength
63
+ if (sanitized.length > maxLength) {
64
+ sanitized = sanitized.substring(0, maxLength - ELLIPSIS.length) + ELLIPSIS;
65
+ }
66
+
67
+ return sanitized;
68
+ }
69
+
70
+ /**
71
+ * Format duration since a timestamp into a human-readable string.
72
+ * Example: "2m", "1h 30m", "5s"
73
+ */
74
+ function formatDuration(startTimestampMs: number): string {
75
+ const now = Date.now();
76
+ const durationMs = now - startTimestampMs;
77
+ const seconds = Math.floor(durationMs / 1000);
78
+ const minutes = Math.floor(seconds / 60);
79
+ const hours = Math.floor(minutes / 60);
80
+
81
+ if (hours > 0) {
82
+ const remainingMinutes = minutes % 60;
83
+ return remainingMinutes > 0 ? `${hours}h ${remainingMinutes}m` : `${hours}h`;
84
+ }
85
+ if (minutes > 0) {
86
+ return `${minutes}m`;
87
+ }
88
+ return `${seconds}s`;
89
+ }
90
+
91
+ /**
92
+ * Select the primary entry from multiple entries in the same conversation.
93
+ * Priority order:
94
+ * 1. Streaming entries (highest priority - actively generating)
95
+ * 2. Entries with currentTool set (running a tool)
96
+ * 3. Entries with active tools (has tools in progress)
97
+ * 4. Most recent activity (fallback)
98
+ */
99
+ function selectPrimaryEntry(entries: ReturnType<RALRegistry["getActiveEntriesForProject"]>): (typeof entries)[0] {
100
+ // 1. Prefer streaming
101
+ const streaming = entries.find(e => e.isStreaming);
102
+ if (streaming) return streaming;
103
+
104
+ // 2. Prefer entries with currentTool
105
+ const withCurrentTool = entries.find(e => e.currentTool);
106
+ if (withCurrentTool) return withCurrentTool;
107
+
108
+ // 3. Prefer entries with active tools
109
+ const withActiveTools = entries.find(e => e.activeTools.size > 0);
110
+ if (withActiveTools) return withActiveTools;
111
+
112
+ // 4. Fall back to most recent activity
113
+ return entries.reduce((mostRecent, entry) =>
114
+ entry.lastActivityAt > mostRecent.lastActivityAt ? entry : mostRecent
115
+ );
116
+ }
117
+
118
+ /**
119
+ * Extract the parent conversation ID from a delegation chain.
120
+ * The second-to-last entry in the chain is the parent conversation.
121
+ */
122
+ export function extractParentFromDelegationChain(chain?: DelegationChainEntry[]): string | undefined {
123
+ if (!chain || chain.length < 2) return undefined;
124
+ return chain[chain.length - 2].conversationId;
125
+ }
126
+
127
+ /**
128
+ * Build a tree structure from a flat list of conversation entries.
129
+ * Children are linked to their parent; if a parent is not in the active set,
130
+ * the child is promoted to a root node.
131
+ *
132
+ * Self-referential cycles (parentConversationId === conversationId) are
133
+ * prevented by skipping self-referential parent assignments.
134
+ * Downstream, the visited set in `getSubtreeMaxActivity` and `renderChildren`
135
+ * prevents duplicate processing of shared nodes (i.e. a node reachable from
136
+ * multiple parents in malformed data), but does not perform full transitive-
137
+ * cycle detection.
138
+ */
139
+ export function buildConversationTree(entries: ActiveConversationEntry[]): ConversationTreeNode[] {
140
+ const nodeMap = new Map<string, ConversationTreeNode>();
141
+ const roots: ConversationTreeNode[] = [];
142
+
143
+ // Create nodes for all entries
144
+ for (const entry of entries) {
145
+ nodeMap.set(entry.conversationId, { entry, children: [] });
146
+ }
147
+
148
+ // Link children to parents (skip self-referential links)
149
+ for (const entry of entries) {
150
+ const node = nodeMap.get(entry.conversationId)!;
151
+ const parentId = entry.parentConversationId;
152
+ if (parentId && parentId !== entry.conversationId) {
153
+ const parentNode = nodeMap.get(parentId);
154
+ if (parentNode) {
155
+ parentNode.children.push(node);
156
+ continue;
157
+ }
158
+ }
159
+ // No parent, self-referential, or parent not in active set → promote to root
160
+ roots.push(node);
161
+ }
162
+
163
+ return roots;
164
+ }
165
+
166
+ /**
167
+ * Get the maximum lastActivityAt across an entire subtree (node + all descendants).
168
+ * Uses a visited set to guard against cycles in malformed data.
169
+ */
170
+ function getSubtreeMaxActivity(node: ConversationTreeNode, visited: Set<string> = new Set()): number {
171
+ if (visited.has(node.entry.conversationId)) return node.entry.lastActivityAt;
172
+ visited.add(node.entry.conversationId);
173
+
174
+ let max = node.entry.lastActivityAt;
175
+ for (const child of node.children) {
176
+ const childMax = getSubtreeMaxActivity(child, visited);
177
+ if (childMax > max) max = childMax;
178
+ }
179
+ return max;
180
+ }
181
+
182
+ /**
183
+ * Sort tree roots by max subtree activity (most recent first).
184
+ * Returns a new array with recursively sorted children (does not mutate the input).
185
+ */
186
+ export function sortTree(roots: ConversationTreeNode[]): ConversationTreeNode[] {
187
+ return [...roots]
188
+ .map(root => sortNodeChildren(root))
189
+ .sort((a, b) => getSubtreeMaxActivity(b) - getSubtreeMaxActivity(a));
190
+ }
191
+
192
+ function sortNodeChildren(node: ConversationTreeNode): ConversationTreeNode {
193
+ const sortedChildren = [...node.children]
194
+ .map(child => sortNodeChildren(child))
195
+ .sort((a, b) => getSubtreeMaxActivity(b) - getSubtreeMaxActivity(a));
196
+ return { ...node, children: sortedChildren };
197
+ }
198
+
199
+ /**
200
+ * Render a single conversation line in compact format.
201
+ */
202
+ function renderConversationLine(entry: ActiveConversationEntry): string {
203
+ const title = entry.title || `Conversation ${shortenConversationId(entry.conversationId)}...`;
204
+ const duration = formatDuration(entry.startedAt);
205
+ const lastMsg = formatRelativeTimeShort(Math.floor(entry.lastActivityAt / 1000));
206
+ const staleMarker = isStale(entry.lastActivityAt) ? " [stale]" : "";
207
+ return `**${title}** (${entry.agentName}) - ${duration}, last msg ${lastMsg}${staleMarker}`;
208
+ }
209
+
210
+ /**
211
+ * Check if a conversation is stale (no activity for >30 minutes).
212
+ */
213
+ function isStale(lastActivityAtMs: number): boolean {
214
+ return (Date.now() - lastActivityAtMs) > STALE_THRESHOLD_MS;
215
+ }
216
+
217
+ /**
218
+ * Render a tree of conversations with tree connectors.
219
+ * Returns an array of lines.
220
+ */
221
+ export function renderTree(roots: ConversationTreeNode[]): string[] {
222
+ const lines: string[] = [];
223
+
224
+ for (let i = 0; i < roots.length; i++) {
225
+ const root = roots[i];
226
+ // Root line: numbered
227
+ lines.push(`${i + 1}. ${renderConversationLine(root.entry)}`);
228
+
229
+ // Optional summary for root
230
+ if (root.entry.summary) {
231
+ lines.push(` ${root.entry.summary}`);
232
+ }
233
+
234
+ // Render children with tree connectors
235
+ renderChildren(root.children, " ", lines);
236
+
237
+ // Blank line between root groups (except last)
238
+ if (i < roots.length - 1) {
239
+ lines.push("");
240
+ }
241
+ }
242
+
243
+ return lines;
244
+ }
245
+
246
+ function renderChildren(children: ConversationTreeNode[], indent: string, lines: string[], visited: Set<string> = new Set()): void {
247
+ for (let i = 0; i < children.length; i++) {
248
+ const child = children[i];
249
+ if (visited.has(child.entry.conversationId)) continue; // cycle guard
250
+ visited.add(child.entry.conversationId);
251
+
252
+ const isLast = i === children.length - 1;
253
+ const connector = isLast ? "└─" : "├─";
254
+ const childIndent = isLast ? `${indent} ` : `${indent}│ `;
255
+
256
+ lines.push(`${indent}${connector} ${renderConversationLine(child.entry)}`);
257
+
258
+ // Optional summary for child
259
+ if (child.entry.summary) {
260
+ lines.push(`${childIndent}${child.entry.summary}`);
261
+ }
262
+
263
+ // Recursively render grandchildren
264
+ renderChildren(child.children, childIndent, lines, visited);
265
+ }
266
+ }
267
+
268
+ /**
269
+ * Load active conversations from RALRegistry.
270
+ * Returns conversations where agents are actively streaming or working.
271
+ * Excludes the current conversation to avoid redundancy.
272
+ */
273
+ function loadActiveConversations(
274
+ _agentPubkey: string, // Not used - we show all active conversations, not filtered by agent
275
+ currentConversationId?: string,
276
+ projectId?: string
277
+ ): ActiveConversationEntry[] {
278
+ if (!projectId) {
279
+ return []; // Project ID required to scope active conversations
280
+ }
281
+
282
+ const ralRegistry = RALRegistry.getInstance();
283
+ const pubkeyService = getPubkeyService();
284
+
285
+ // Get all active RAL entries for this project
286
+ const activeEntries = ralRegistry.getActiveEntriesForProject(projectId);
287
+ const candidateEntries: ActiveConversationEntry[] = [];
288
+
289
+ // Group entries by conversation to deduplicate (multiple agents may be in same conversation)
290
+ const conversationMap = new Map<string, typeof activeEntries>();
291
+ for (const entry of activeEntries) {
292
+ // Skip the current conversation
293
+ if (entry.conversationId === currentConversationId) {
294
+ continue;
295
+ }
296
+
297
+ const existing = conversationMap.get(entry.conversationId);
298
+ if (existing) {
299
+ existing.push(entry);
300
+ } else {
301
+ conversationMap.set(entry.conversationId, [entry]);
302
+ }
303
+ }
304
+
305
+ for (const [conversationId, entries] of conversationMap) {
306
+ try {
307
+ // Get the most active/interesting entry for this conversation
308
+ // Priority: streaming > running tool > most recent activity
309
+ const primaryEntry = selectPrimaryEntry(entries);
310
+
311
+ // Get conversation metadata
312
+ const store = conversationRegistry.get(conversationId);
313
+ let title: string | undefined;
314
+ let summary: string | undefined;
315
+ let messageCount = 0;
316
+ let parentConversationId: string | undefined;
317
+
318
+ if (store) {
319
+ const metadata = store.getMetadata();
320
+ title = metadata.title;
321
+ summary = metadata.summary ? sanitizeForPrompt(metadata.summary) : undefined;
322
+ messageCount = store.getAllMessages().length;
323
+ parentConversationId = extractParentFromDelegationChain(metadata.delegationChain);
324
+ }
325
+
326
+ // Get agent name
327
+ const agentName = pubkeyService.getNameSync(primaryEntry.agentPubkey);
328
+
329
+ candidateEntries.push({
330
+ conversationId,
331
+ title,
332
+ summary,
333
+ agentName,
334
+ agentPubkey: primaryEntry.agentPubkey,
335
+ isStreaming: primaryEntry.isStreaming,
336
+ currentTool: primaryEntry.currentTool,
337
+ startedAt: primaryEntry.createdAt,
338
+ lastActivityAt: primaryEntry.lastActivityAt,
339
+ messageCount,
340
+ parentConversationId,
341
+ });
342
+ } catch (err) {
343
+ logger.debug("Failed to get conversation metadata for active-conversations fragment", {
344
+ conversationId,
345
+ error: err,
346
+ });
347
+ }
348
+ }
349
+
350
+ // Sort by most recent activity first and limit to MAX_CONVERSATIONS
351
+ candidateEntries.sort((a, b) => b.lastActivityAt - a.lastActivityAt);
352
+ return candidateEntries.slice(0, MAX_CONVERSATIONS);
353
+ }
354
+
355
+ export const activeConversationsFragment: PromptFragment<ActiveConversationsArgs> = {
356
+ id: "active-conversations",
357
+ priority: 8, // Before recent conversations (9)
358
+ template: ({ agent, currentConversationId, projectId }) => {
359
+ const activeConversations = loadActiveConversations(agent.pubkey, currentConversationId, projectId);
360
+
361
+ if (activeConversations.length === 0) {
362
+ return ""; // No active conversations to show
363
+ }
364
+
365
+ // Build hierarchical tree from flat list
366
+ const roots = buildConversationTree(activeConversations);
367
+
368
+ // Sort tree by most recent subtree activity
369
+ const sortedRoots = sortTree(roots);
370
+
371
+ // Render tree with connectors
372
+ const lines = renderTree(sortedRoots);
373
+
374
+ return `## Active Conversations
375
+
376
+ The following conversations are currently active in this project (agents working):
377
+
378
+ ${lines.join("\n")}
379
+
380
+ ---`;
381
+ },
382
+ };
@@ -0,0 +1,153 @@
1
+ import type { AgentInstance } from "@/agents/types";
2
+ import { ConversationStore } from "@/conversations/ConversationStore";
3
+ import { formatRelativeTimeShort } from "@/lib/time";
4
+ import { shortenConversationId } from "@/utils/conversation-id";
5
+ import { logger } from "@/utils/logger";
6
+ import type { PromptFragment } from "../core/types";
7
+
8
+ /**
9
+ * Recent conversations fragment - provides context about conversations
10
+ * the agent participated in during the last 24 hours.
11
+ *
12
+ * This gives agents "short-term memory" by surfacing recent activity summaries.
13
+ */
14
+
15
+ interface RecentConversationsArgs {
16
+ agent: AgentInstance;
17
+ currentConversationId?: string;
18
+ projectId?: string;
19
+ }
20
+
21
+ interface RecentConversationEntry {
22
+ id: string;
23
+ title?: string;
24
+ summary?: string;
25
+ lastActivity: number;
26
+ }
27
+
28
+ const TWENTY_FOUR_HOURS_IN_SECONDS = 24 * 60 * 60;
29
+ const MAX_CONVERSATIONS = 10;
30
+ const MAX_SUMMARY_LENGTH = 200;
31
+
32
+ const ELLIPSIS = "...";
33
+
34
+ /**
35
+ * Sanitize text for safe inclusion in system prompt.
36
+ * Prevents prompt injection by:
37
+ * - Stripping/normalizing newlines
38
+ * - Trimming excessive whitespace
39
+ * - Clamping length (result is at most maxLength chars, including ellipsis if truncated)
40
+ */
41
+ function sanitizeForPrompt(text: string, maxLength: number = MAX_SUMMARY_LENGTH): string {
42
+ // Strip newlines and normalize whitespace
43
+ let sanitized = text
44
+ .replace(/[\r\n]+/g, " ") // Replace newlines with spaces
45
+ .replace(/\s+/g, " ") // Collapse multiple spaces
46
+ .trim();
47
+
48
+ // Clamp length - ensure total output (including ellipsis) doesn't exceed maxLength
49
+ if (sanitized.length > maxLength) {
50
+ sanitized = sanitized.substring(0, maxLength - ELLIPSIS.length) + ELLIPSIS;
51
+ }
52
+
53
+ return sanitized;
54
+ }
55
+
56
+ /**
57
+ * Load recent conversations where the agent participated in the last 24 hours.
58
+ * Excludes the current conversation to avoid redundancy.
59
+ *
60
+ * Performance: Uses readConversationPreview for single disk read per conversation
61
+ * (metadata + participation check combined). Does not grow global cache.
62
+ */
63
+ function loadRecentConversations(
64
+ agentPubkey: string,
65
+ currentConversationId?: string,
66
+ projectId?: string
67
+ ): RecentConversationEntry[] {
68
+ const now = Math.floor(Date.now() / 1000);
69
+ const cutoffTime = now - TWENTY_FOUR_HOURS_IN_SECONDS;
70
+
71
+ // Use project-specific listing if projectId is provided, otherwise fall back to current project
72
+ const conversationIds = projectId
73
+ ? ConversationStore.listConversationIdsFromDiskForProject(projectId)
74
+ : ConversationStore.listConversationIdsFromDisk();
75
+ const candidateEntries: RecentConversationEntry[] = [];
76
+
77
+ for (const conversationId of conversationIds) {
78
+ // Skip the current conversation
79
+ if (conversationId === currentConversationId) {
80
+ continue;
81
+ }
82
+
83
+ try {
84
+ // Single disk read: gets metadata + participation check together
85
+ // Use project-aware method when projectId is provided for full scoping
86
+ const preview = projectId
87
+ ? ConversationStore.readConversationPreviewForProject(conversationId, agentPubkey, projectId)
88
+ : ConversationStore.readConversationPreview(conversationId, agentPubkey);
89
+ if (!preview) continue;
90
+
91
+ // Skip conversations older than 24 hours
92
+ if (preview.lastActivity < cutoffTime) {
93
+ continue;
94
+ }
95
+
96
+ // Skip if agent didn't participate
97
+ if (!preview.agentParticipated) {
98
+ continue;
99
+ }
100
+
101
+ // Build summary - ONLY use generated summaries to prevent prompt injection
102
+ // Raw user text is NOT included in the system prompt
103
+ const summary = preview.summary
104
+ ? sanitizeForPrompt(preview.summary)
105
+ : "[No summary available]";
106
+
107
+ candidateEntries.push({
108
+ id: preview.id,
109
+ title: preview.title,
110
+ summary,
111
+ lastActivity: preview.lastActivity,
112
+ });
113
+ } catch (err) {
114
+ logger.debug("Failed to read conversation preview for recent-conversations fragment", {
115
+ conversationId,
116
+ error: err,
117
+ });
118
+ }
119
+ }
120
+
121
+ // Sort by most recent activity first and limit to MAX_CONVERSATIONS
122
+ candidateEntries.sort((a, b) => b.lastActivity - a.lastActivity);
123
+ return candidateEntries.slice(0, MAX_CONVERSATIONS);
124
+ }
125
+
126
+ export const recentConversationsFragment: PromptFragment<RecentConversationsArgs> = {
127
+ id: "recent-conversations",
128
+ priority: 9, // Early in the prompt, after identity but before other context
129
+ template: ({ agent, currentConversationId, projectId }) => {
130
+ const recentConversations = loadRecentConversations(agent.pubkey, currentConversationId, projectId);
131
+
132
+ if (recentConversations.length === 0) {
133
+ return ""; // No recent conversations to show
134
+ }
135
+
136
+ const conversationLines = recentConversations.map((conv, index) => {
137
+ const title = conv.title || `Conversation ${shortenConversationId(conv.id)}...`;
138
+ const relativeTime = formatRelativeTimeShort(conv.lastActivity);
139
+ // Summary is already sanitized (no newlines), safe to include inline
140
+ const summaryLine = conv.summary ? `\n Summary: ${conv.summary}` : "";
141
+
142
+ return `${index + 1}. **${title}** (${relativeTime})${summaryLine}`;
143
+ });
144
+
145
+ return `## Recent Conversations (Past 24h)
146
+
147
+ You participated in the following conversations recently. This context may help you understand ongoing work:
148
+
149
+ ${conversationLines.join("\n\n")}
150
+
151
+ ---`;
152
+ },
153
+ };
@@ -0,0 +1,21 @@
1
+ import { fragmentRegistry } from "../core/FragmentRegistry";
2
+
3
+ interface ReferencedArticleArgs {
4
+ title: string;
5
+ content: string;
6
+ dTag: string;
7
+ }
8
+
9
+ fragmentRegistry.register<ReferencedArticleArgs>({
10
+ id: "referenced-article",
11
+ priority: 10, // High priority to appear early in the prompt
12
+ template: ({ title, content, dTag }) => {
13
+ return `This conversation is about this spec file:
14
+ <spec dTag="${dTag}">
15
+ # ${title}
16
+
17
+ ${content}
18
+ </spec>
19
+ `;
20
+ },
21
+ });
@@ -0,0 +1,134 @@
1
+ import type { NudgeToolPermissions, NudgeData } from "@/services/nudge";
2
+ import { isOnlyToolMode, hasToolPermissions } from "@/services/nudge";
3
+ import type { PromptFragment } from "../core/types";
4
+
5
+ interface NudgesArgs {
6
+ /** Legacy: concatenated nudge content (for backward compatibility) */
7
+ nudgeContent?: string;
8
+ /** Individual nudge data with title and content */
9
+ nudges?: NudgeData[];
10
+ /** Tool permissions extracted from nudge events */
11
+ nudgeToolPermissions?: NudgeToolPermissions;
12
+ }
13
+
14
+ /**
15
+ * Escape XML attribute value to prevent injection
16
+ */
17
+ function escapeAttrValue(value: string): string {
18
+ return value
19
+ .replace(/&/g, "&amp;")
20
+ .replace(/"/g, "&quot;")
21
+ .replace(/</g, "&lt;")
22
+ .replace(/>/g, "&gt;");
23
+ }
24
+
25
+ /**
26
+ * Render aggregated tool permissions as a separate header block.
27
+ * These permissions are collected from ALL active nudges and apply globally.
28
+ */
29
+ function renderToolPermissionsHeader(permissions: NudgeToolPermissions): string {
30
+ if (!hasToolPermissions(permissions)) {
31
+ return "";
32
+ }
33
+
34
+ const lines: string[] = [];
35
+
36
+ if (isOnlyToolMode(permissions)) {
37
+ // only-tool mode: restricted to specific tools only
38
+ lines.push(
39
+ `Your available tools are restricted to: ${permissions.onlyTools!.join(", ")}`
40
+ );
41
+ } else {
42
+ // allow/deny mode
43
+ if (permissions.allowTools && permissions.allowTools.length > 0) {
44
+ lines.push(`Additional tools enabled: ${permissions.allowTools.join(", ")}`);
45
+ }
46
+ if (permissions.denyTools && permissions.denyTools.length > 0) {
47
+ lines.push(`Tools disabled: ${permissions.denyTools.join(", ")}`);
48
+ }
49
+ }
50
+
51
+ if (lines.length === 0) {
52
+ return "";
53
+ }
54
+
55
+ return `<nudge-tool-permissions>
56
+ <!-- Aggregated across all active nudges -->
57
+ ${lines.join("\n")}
58
+ </nudge-tool-permissions>`;
59
+ }
60
+
61
+ /**
62
+ * Render a single nudge with its title and content.
63
+ * Title is escaped to prevent XML injection.
64
+ */
65
+ function renderNudge(nudge: NudgeData): string {
66
+ const titleAttr = nudge.title ? ` title="${escapeAttrValue(nudge.title)}"` : "";
67
+
68
+ return `<nudge${titleAttr}>
69
+ ${nudge.content}
70
+ </nudge>`;
71
+ }
72
+
73
+ /**
74
+ * Fragment for injecting nudge content into the system prompt.
75
+ * Nudges are kind:4201 events referenced via nudge tags on the triggering event.
76
+ * Their content is fetched and injected to provide additional context/instructions.
77
+ *
78
+ * Now supports tool permissions rendering:
79
+ * - For only-tool mode: Shows "This nudge restricts your normal tooling available to: ..."
80
+ * - For allow/deny mode: Shows enabled/disabled tools separately
81
+ */
82
+ export const nudgesFragment: PromptFragment<NudgesArgs> = {
83
+ id: "nudges",
84
+ priority: 11, // After referenced-article (10), before available-agents (15)
85
+ template: ({ nudgeContent, nudges, nudgeToolPermissions }) => {
86
+ // New rendering path: individual nudges with their data
87
+ if (nudges && nudges.length > 0) {
88
+ const parts: string[] = [];
89
+
90
+ // Render tool permissions as a separate header block (aggregated across ALL nudges)
91
+ if (nudgeToolPermissions) {
92
+ const permissionsHeader = renderToolPermissionsHeader(nudgeToolPermissions);
93
+ if (permissionsHeader) {
94
+ parts.push(permissionsHeader);
95
+ }
96
+ }
97
+
98
+ // Render each nudge individually (without embedding permissions in each)
99
+ const renderedNudges = nudges.map((nudge) => renderNudge(nudge));
100
+ parts.push(...renderedNudges);
101
+
102
+ return parts.join("\n\n");
103
+ }
104
+
105
+ // Legacy fallback: just nudgeContent string
106
+ if (!nudgeContent || nudgeContent.trim().length === 0) {
107
+ return "";
108
+ }
109
+
110
+ return `<nudges>
111
+ ${nudgeContent}
112
+ </nudges>`;
113
+ },
114
+ validateArgs: (args: unknown): args is NudgesArgs => {
115
+ if (typeof args !== "object" || args === null) return false;
116
+ const a = args as Record<string, unknown>;
117
+ // Optional: all fields are optional (empty nudge is valid)
118
+ // When nudges array is provided, validate each element has required shape
119
+ if (a.nudges !== undefined) {
120
+ if (!Array.isArray(a.nudges)) return false;
121
+ for (const nudge of a.nudges) {
122
+ if (typeof nudge !== "object" || nudge === null) return false;
123
+ const n = nudge as Record<string, unknown>;
124
+ // content is required, title is optional
125
+ if (typeof n.content !== "string") return false;
126
+ if (n.title !== undefined && typeof n.title !== "string") return false;
127
+ }
128
+ }
129
+ // nudgeContent must be string if provided
130
+ if (a.nudgeContent !== undefined && typeof a.nudgeContent !== "string") return false;
131
+ return true;
132
+ },
133
+ expectedArgs: "{ nudgeContent?: string; nudges?: NudgeData[]; nudgeToolPermissions?: NudgeToolPermissions }",
134
+ };