@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.
- package/README.md +194 -0
- package/dist/backend-wrapper.cjs +3 -0
- package/dist/src/index.js +331928 -0
- package/package.json +103 -0
- package/src/agents/AgentRegistry.ts +418 -0
- package/src/agents/AgentStorage.ts +1133 -0
- package/src/agents/ConfigResolver.ts +229 -0
- package/src/agents/agent-installer.ts +236 -0
- package/src/agents/agent-loader.ts +241 -0
- package/src/agents/constants.ts +82 -0
- package/src/agents/errors.ts +48 -0
- package/src/agents/execution/AgentExecutor.ts +561 -0
- package/src/agents/execution/ExecutionContextFactory.ts +112 -0
- package/src/agents/execution/MessageCompiler.ts +597 -0
- package/src/agents/execution/MessageSyncer.ts +100 -0
- package/src/agents/execution/PostCompletionChecker.ts +278 -0
- package/src/agents/execution/ProgressMonitor.ts +50 -0
- package/src/agents/execution/RALResolver.ts +177 -0
- package/src/agents/execution/SessionManager.ts +181 -0
- package/src/agents/execution/StreamCallbacks.ts +312 -0
- package/src/agents/execution/StreamExecutionHandler.ts +579 -0
- package/src/agents/execution/StreamSetup.ts +313 -0
- package/src/agents/execution/ToolEventHandlers.ts +239 -0
- package/src/agents/execution/ToolExecutionTracker.ts +498 -0
- package/src/agents/execution/ToolResultUtils.ts +97 -0
- package/src/agents/execution/ToolSupervisionWrapper.ts +174 -0
- package/src/agents/execution/constants.ts +16 -0
- package/src/agents/execution/index.ts +3 -0
- package/src/agents/execution/types.ts +96 -0
- package/src/agents/execution/utils.ts +26 -0
- package/src/agents/index.ts +4 -0
- package/src/agents/script-installer.ts +266 -0
- package/src/agents/supervision/SupervisorLLMService.ts +253 -0
- package/src/agents/supervision/SupervisorOrchestrator.ts +471 -0
- package/src/agents/supervision/heuristics/ConsecutiveToolsWithoutTodoHeuristic.ts +73 -0
- package/src/agents/supervision/heuristics/DelegationClaimHeuristic.ts +80 -0
- package/src/agents/supervision/heuristics/HeuristicRegistry.ts +114 -0
- package/src/agents/supervision/heuristics/PendingTodosHeuristic.ts +93 -0
- package/src/agents/supervision/heuristics/SilentAgentHeuristic.ts +54 -0
- package/src/agents/supervision/heuristics/index.ts +5 -0
- package/src/agents/supervision/index.ts +28 -0
- package/src/agents/supervision/registerHeuristics.ts +110 -0
- package/src/agents/supervision/supervisionHealthCheck.ts +123 -0
- package/src/agents/supervision/types.ts +171 -0
- package/src/agents/tool-names.ts +46 -0
- package/src/agents/tool-normalization.ts +184 -0
- package/src/agents/types/index.ts +2 -0
- package/src/agents/types/runtime.ts +74 -0
- package/src/agents/types/storage.ts +145 -0
- package/src/commands/agent/import/index.ts +6 -0
- package/src/commands/agent/import/openclaw-distiller.ts +57 -0
- package/src/commands/agent/import/openclaw-reader.ts +141 -0
- package/src/commands/agent/import/openclaw.ts +154 -0
- package/src/commands/agent/index.ts +6 -0
- package/src/commands/agent.ts +215 -0
- package/src/commands/daemon.ts +198 -0
- package/src/commands/doctor.ts +134 -0
- package/src/commands/setup/embed.ts +228 -0
- package/src/commands/setup/global-system-prompt.ts +223 -0
- package/src/commands/setup/image.ts +179 -0
- package/src/commands/setup/index.ts +16 -0
- package/src/commands/setup/interactive.ts +95 -0
- package/src/commands/setup/llm.ts +38 -0
- package/src/commands/setup/onboarding.ts +294 -0
- package/src/commands/setup/providers.ts +27 -0
- package/src/constants.ts +34 -0
- package/src/conversations/ConversationDiskReader.ts +148 -0
- package/src/conversations/ConversationRegistry.ts +728 -0
- package/src/conversations/ConversationStore.ts +868 -0
- package/src/conversations/MessageBuilder.ts +866 -0
- package/src/conversations/executionTime.ts +62 -0
- package/src/conversations/formatters/DelegationXmlFormatter.ts +64 -0
- package/src/conversations/formatters/ThreadedConversationFormatter.ts +303 -0
- package/src/conversations/formatters/index.ts +9 -0
- package/src/conversations/formatters/utils/MessageFormatter.ts +46 -0
- package/src/conversations/formatters/utils/TimestampFormatter.ts +56 -0
- package/src/conversations/formatters/utils/TreeBuilder.ts +131 -0
- package/src/conversations/formatters/utils/TreeRenderer.ts +49 -0
- package/src/conversations/index.ts +2 -0
- package/src/conversations/persistence/ToolMessageStorage.ts +143 -0
- package/src/conversations/search/ConversationIndexManager.ts +393 -0
- package/src/conversations/search/QueryParser.ts +114 -0
- package/src/conversations/search/SearchEngine.ts +175 -0
- package/src/conversations/search/SnippetExtractor.ts +345 -0
- package/src/conversations/search/embeddings/ConversationEmbeddingService.ts +484 -0
- package/src/conversations/search/embeddings/ConversationIndexingJob.ts +320 -0
- package/src/conversations/search/embeddings/IndexingStateManager.ts +338 -0
- package/src/conversations/search/embeddings/index.ts +18 -0
- package/src/conversations/search/index.ts +49 -0
- package/src/conversations/search/types.ts +124 -0
- package/src/conversations/services/CategoryManager.ts +160 -0
- package/src/conversations/services/ConversationResolver.ts +296 -0
- package/src/conversations/services/ConversationSummarizer.ts +234 -0
- package/src/conversations/services/MetadataDebounceManager.ts +188 -0
- package/src/conversations/services/index.ts +2 -0
- package/src/conversations/types.ts +148 -0
- package/src/conversations/utils/content-utils.ts +69 -0
- package/src/conversations/utils/image-placeholder.ts +281 -0
- package/src/conversations/utils/image-url-utils.ts +171 -0
- package/src/conversations/utils/multimodal-content.ts +90 -0
- package/src/conversations/utils/tool-result-truncator.ts +159 -0
- package/src/daemon/Daemon.ts +1883 -0
- package/src/daemon/ProjectRuntime.ts +657 -0
- package/src/daemon/RestartState.ts +152 -0
- package/src/daemon/RuntimeLifecycle.ts +268 -0
- package/src/daemon/SubscriptionManager.ts +305 -0
- package/src/daemon/UnixSocketTransport.ts +318 -0
- package/src/daemon/filters/SubscriptionFilterBuilder.ts +119 -0
- package/src/daemon/index.ts +9 -0
- package/src/daemon/routing/DaemonRouter.ts +491 -0
- package/src/daemon/types.ts +150 -0
- package/src/daemon/utils/routing-log.ts +76 -0
- package/src/daemon/utils/telemetry.ts +173 -0
- package/src/event-handler/agentDeletion.ts +383 -0
- package/src/event-handler/index.ts +749 -0
- package/src/event-handler/newConversation.ts +165 -0
- package/src/event-handler/project.ts +166 -0
- package/src/event-handler/reply.ts +18 -0
- package/src/events/NDKAgentDefinition.ts +292 -0
- package/src/events/NDKAgentLesson.ts +106 -0
- package/src/events/NDKEventMetadata.ts +34 -0
- package/src/events/NDKMCPTool.ts +60 -0
- package/src/events/NDKProjectStatus.ts +384 -0
- package/src/events/index.ts +4 -0
- package/src/index.ts +126 -0
- package/src/lib/agent-home.ts +334 -0
- package/src/lib/error-formatter.ts +200 -0
- package/src/lib/fs/filesystem.ts +128 -0
- package/src/lib/fs/index.ts +1 -0
- package/src/lib/json-parser.ts +30 -0
- package/src/lib/string.ts +15 -0
- package/src/lib/time.ts +74 -0
- package/src/llm/ChunkHandler.ts +277 -0
- package/src/llm/FinishHandler.ts +250 -0
- package/src/llm/LLMConfigEditor.ts +154 -0
- package/src/llm/LLMServiceFactory.ts +230 -0
- package/src/llm/MessageProcessor.ts +90 -0
- package/src/llm/RecordingState.ts +37 -0
- package/src/llm/StreamPublisher.ts +40 -0
- package/src/llm/TracingUtils.ts +77 -0
- package/src/llm/chunk-validators.ts +57 -0
- package/src/llm/constants.ts +6 -0
- package/src/llm/index.ts +12 -0
- package/src/llm/meta/MetaModelResolver.ts +352 -0
- package/src/llm/meta/index.ts +11 -0
- package/src/llm/middleware/flight-recorder.ts +188 -0
- package/src/llm/providers/MockProvider.ts +332 -0
- package/src/llm/providers/agent/ClaudeCodeProvider.ts +343 -0
- package/src/llm/providers/agent/ClaudeCodeToolsAdapter.ts +203 -0
- package/src/llm/providers/agent/CodexAppServerProvider.ts +214 -0
- package/src/llm/providers/agent/CodexAppServerToolsAdapter.ts +91 -0
- package/src/llm/providers/agent/index.ts +10 -0
- package/src/llm/providers/base/AgentProvider.ts +107 -0
- package/src/llm/providers/base/BaseProvider.ts +114 -0
- package/src/llm/providers/base/StandardProvider.ts +38 -0
- package/src/llm/providers/base/index.ts +9 -0
- package/src/llm/providers/index.ts +106 -0
- package/src/llm/providers/key-manager.ts +238 -0
- package/src/llm/providers/ollama-models.ts +105 -0
- package/src/llm/providers/openrouter-models.ts +102 -0
- package/src/llm/providers/provider-ids.ts +18 -0
- package/src/llm/providers/registry/ProviderRegistry.ts +414 -0
- package/src/llm/providers/registry/index.ts +7 -0
- package/src/llm/providers/standard/AnthropicProvider.ts +71 -0
- package/src/llm/providers/standard/OllamaProvider.ts +59 -0
- package/src/llm/providers/standard/OpenAIProvider.ts +44 -0
- package/src/llm/providers/standard/OpenRouterProvider.ts +103 -0
- package/src/llm/providers/standard/index.ts +10 -0
- package/src/llm/providers/types.ts +194 -0
- package/src/llm/providers/usage-metadata.ts +78 -0
- package/src/llm/service.ts +713 -0
- package/src/llm/types.ts +167 -0
- package/src/llm/utils/ConfigurationManager.ts +650 -0
- package/src/llm/utils/ConfigurationTester.ts +229 -0
- package/src/llm/utils/ModelSelector.ts +212 -0
- package/src/llm/utils/ProviderConfigUI.ts +177 -0
- package/src/llm/utils/claudeCodePromptCompiler.ts +141 -0
- package/src/llm/utils/codex-models.ts +53 -0
- package/src/llm/utils/context-window-cache.ts +30 -0
- package/src/llm/utils/models-dev-cache.ts +267 -0
- package/src/llm/utils/provider-setup.ts +50 -0
- package/src/llm/utils/tool-errors.ts +78 -0
- package/src/llm/utils/usage.ts +74 -0
- package/src/logging/EventRoutingLogger.ts +205 -0
- package/src/nostr/AgentEventDecoder.ts +357 -0
- package/src/nostr/AgentEventEncoder.ts +677 -0
- package/src/nostr/AgentProfilePublisher.ts +657 -0
- package/src/nostr/AgentPublisher.ts +437 -0
- package/src/nostr/BlossomService.ts +226 -0
- package/src/nostr/InterventionPublisher.ts +132 -0
- package/src/nostr/TagExtractor.ts +228 -0
- package/src/nostr/collectEvents.ts +83 -0
- package/src/nostr/constants.ts +38 -0
- package/src/nostr/encryption.ts +26 -0
- package/src/nostr/index.ts +31 -0
- package/src/nostr/keys.ts +17 -0
- package/src/nostr/kinds.ts +37 -0
- package/src/nostr/ndkClient.ts +72 -0
- package/src/nostr/relays.ts +43 -0
- package/src/nostr/trace-context.ts +39 -0
- package/src/nostr/types.ts +227 -0
- package/src/nostr/utils.ts +84 -0
- package/src/prompts/core/FragmentRegistry.ts +30 -0
- package/src/prompts/core/PromptBuilder.ts +98 -0
- package/src/prompts/core/index.ts +3 -0
- package/src/prompts/core/types.ts +13 -0
- package/src/prompts/fragments/00-global-system-prompt.ts +44 -0
- package/src/prompts/fragments/01-agent-identity.ts +69 -0
- package/src/prompts/fragments/02-agent-home-directory.ts +114 -0
- package/src/prompts/fragments/03-system-reminders-explanation.ts +14 -0
- package/src/prompts/fragments/04-relay-configuration.ts +38 -0
- package/src/prompts/fragments/05-delegation-chain.ts +45 -0
- package/src/prompts/fragments/06-agent-todos.ts +74 -0
- package/src/prompts/fragments/06-todo-usage-guidance.ts +34 -0
- package/src/prompts/fragments/07-meta-project-context.ts +234 -0
- package/src/prompts/fragments/08-active-conversations.ts +382 -0
- package/src/prompts/fragments/09-recent-conversations.ts +153 -0
- package/src/prompts/fragments/10-referenced-article.ts +21 -0
- package/src/prompts/fragments/11-nudges.ts +134 -0
- package/src/prompts/fragments/12-skills.ts +127 -0
- package/src/prompts/fragments/13-available-nudges.ts +122 -0
- package/src/prompts/fragments/15-available-agents.ts +53 -0
- package/src/prompts/fragments/16-stay-in-your-lane.ts +41 -0
- package/src/prompts/fragments/17-todo-before-delegation.ts +39 -0
- package/src/prompts/fragments/20-voice-mode.ts +62 -0
- package/src/prompts/fragments/22-scheduled-tasks.ts +175 -0
- package/src/prompts/fragments/24-retrieved-lessons.ts +26 -0
- package/src/prompts/fragments/25-rag-instructions.ts +333 -0
- package/src/prompts/fragments/26-mcp-resources.ts +237 -0
- package/src/prompts/fragments/27-memorized-reports.ts +77 -0
- package/src/prompts/fragments/28-agent-directed-monitoring.ts +32 -0
- package/src/prompts/fragments/29-rag-collections.ts +50 -0
- package/src/prompts/fragments/30-worktree-context.ts +98 -0
- package/src/prompts/fragments/31-agents-md-guidance.ts +96 -0
- package/src/prompts/fragments/32-process-metrics.ts +72 -0
- package/src/prompts/fragments/debug-mode.ts +48 -0
- package/src/prompts/fragments/delegation-completion.ts +44 -0
- package/src/prompts/fragments/index.ts +91 -0
- package/src/prompts/index.ts +21 -0
- package/src/prompts/utils/systemPromptBuilder.ts +777 -0
- package/src/scripts/migrate-prefix-index.ts +157 -0
- package/src/services/AgentDefinitionMonitor.ts +701 -0
- package/src/services/ConfigService.ts +723 -0
- package/src/services/CooldownRegistry.ts +199 -0
- package/src/services/LLMOperationsRegistry.ts +424 -0
- package/src/services/OwnerAgentListService.ts +354 -0
- package/src/services/PubkeyService.ts +308 -0
- package/src/services/agents/AgentMetadataStore.ts +72 -0
- package/src/services/agents/AgentResolution.ts +59 -0
- package/src/services/agents/EscalationService.ts +281 -0
- package/src/services/agents/NDKAgentDiscovery.ts +95 -0
- package/src/services/agents/index.ts +7 -0
- package/src/services/agents-md/AgentsMdService.ts +184 -0
- package/src/services/agents-md/SystemReminderInjector.ts +238 -0
- package/src/services/agents-md/index.ts +11 -0
- package/src/services/apns/APNsClient.ts +203 -0
- package/src/services/apns/APNsService.ts +358 -0
- package/src/services/apns/index.ts +11 -0
- package/src/services/apns/types.ts +80 -0
- package/src/services/compression/CompressionService.ts +445 -0
- package/src/services/compression/compression-schema.ts +28 -0
- package/src/services/compression/compression-types.ts +74 -0
- package/src/services/compression/compression-utils.ts +587 -0
- package/src/services/config/types.ts +394 -0
- package/src/services/dispatch/AgentDispatchService.ts +937 -0
- package/src/services/dispatch/AgentRouter.ts +181 -0
- package/src/services/dispatch/DelegationCompletionHandler.ts +232 -0
- package/src/services/embedding/EmbeddingProvider.ts +188 -0
- package/src/services/embedding/index.ts +5 -0
- package/src/services/event-context/EventContextService.ts +108 -0
- package/src/services/event-context/index.ts +2 -0
- package/src/services/heuristics/ContextBuilder.ts +106 -0
- package/src/services/heuristics/HeuristicEngine.ts +200 -0
- package/src/services/heuristics/formatters.ts +58 -0
- package/src/services/heuristics/index.ts +12 -0
- package/src/services/heuristics/rules/index.ts +25 -0
- package/src/services/heuristics/rules/todoBeforeDelegation.ts +69 -0
- package/src/services/heuristics/rules/todoReminderOnToolUse.ts +63 -0
- package/src/services/heuristics/types.ts +144 -0
- package/src/services/image/ImageGenerationService.ts +389 -0
- package/src/services/image/index.ts +12 -0
- package/src/services/intervention/InterventionService.ts +1352 -0
- package/src/services/intervention/index.ts +7 -0
- package/src/services/mcp/MCPManager.ts +683 -0
- package/src/services/mcp/McpNotificationDelivery.ts +139 -0
- package/src/services/mcp/McpSubscriptionService.ts +653 -0
- package/src/services/mcp/mcpInstaller.ts +130 -0
- package/src/services/nip46/Nip46SigningLog.ts +81 -0
- package/src/services/nip46/Nip46SigningService.ts +467 -0
- package/src/services/nip46/index.ts +4 -0
- package/src/services/nudge/NudgeService.ts +224 -0
- package/src/services/nudge/NudgeWhitelistService.ts +382 -0
- package/src/services/nudge/index.ts +5 -0
- package/src/services/nudge/types.ts +83 -0
- package/src/services/projects/ProjectContext.ts +672 -0
- package/src/services/projects/ProjectContextStore.ts +102 -0
- package/src/services/projects/index.ts +6 -0
- package/src/services/prompt-compiler/index.ts +15 -0
- package/src/services/prompt-compiler/prompt-compiler-service.ts +1143 -0
- package/src/services/pubkey-gate/PubkeyGateService.ts +93 -0
- package/src/services/pubkey-gate/index.ts +1 -0
- package/src/services/rag/EmbeddingProviderFactory.ts +292 -0
- package/src/services/rag/LanceDBMaintenanceService.ts +211 -0
- package/src/services/rag/RAGDatabaseService.ts +173 -0
- package/src/services/rag/RAGOperations.ts +682 -0
- package/src/services/rag/RAGService.ts +240 -0
- package/src/services/rag/RagSubscriptionService.ts +618 -0
- package/src/services/rag/rag-utils.ts +174 -0
- package/src/services/ral/PendingDelegationsRegistry.ts +168 -0
- package/src/services/ral/RALRegistry.ts +2782 -0
- package/src/services/ral/index.ts +4 -0
- package/src/services/ral/types.ts +292 -0
- package/src/services/reports/LocalReportStore.ts +380 -0
- package/src/services/reports/ReportEmbeddingService.ts +430 -0
- package/src/services/reports/ReportService.ts +440 -0
- package/src/services/reports/articleUtils.ts +52 -0
- package/src/services/reports/index.ts +7 -0
- package/src/services/scheduling/SchedulerService.ts +1057 -0
- package/src/services/scheduling/errors.ts +14 -0
- package/src/services/scheduling/index.ts +7 -0
- package/src/services/scheduling/utils.ts +77 -0
- package/src/services/search/SearchProviderRegistry.ts +78 -0
- package/src/services/search/UnifiedSearchService.ts +218 -0
- package/src/services/search/index.ts +47 -0
- package/src/services/search/projectFilter.ts +22 -0
- package/src/services/search/providers/ConversationSearchProvider.ts +48 -0
- package/src/services/search/providers/LessonSearchProvider.ts +75 -0
- package/src/services/search/providers/ReportSearchProvider.ts +49 -0
- package/src/services/search/types.ts +144 -0
- package/src/services/skill/SkillService.ts +482 -0
- package/src/services/skill/index.ts +2 -0
- package/src/services/skill/types.ts +70 -0
- package/src/services/status/OperationsStatusService.ts +276 -0
- package/src/services/status/ProjectStatusService.ts +522 -0
- package/src/services/status/index.ts +11 -0
- package/src/services/storage/PrefixKVStore.ts +242 -0
- package/src/services/storage/index.ts +1 -0
- package/src/services/system-reminder/SystemReminderUtils.ts +96 -0
- package/src/services/system-reminder/index.ts +7 -0
- package/src/services/trust-pubkeys/TrustPubkeyService.ts +325 -0
- package/src/services/trust-pubkeys/index.ts +2 -0
- package/src/telemetry/ConversationSpanManager.ts +111 -0
- package/src/telemetry/EventLoopMonitor.ts +206 -0
- package/src/telemetry/LLMSpanRegistry.ts +20 -0
- package/src/telemetry/NostrSpanProcessor.ts +89 -0
- package/src/telemetry/ToolCallSpanProcessor.ts +66 -0
- package/src/telemetry/diagnostics.ts +27 -0
- package/src/telemetry/setup.ts +120 -0
- package/src/tools/implementations/agents_discover.ts +121 -0
- package/src/tools/implementations/agents_hire.ts +127 -0
- package/src/tools/implementations/agents_list.ts +96 -0
- package/src/tools/implementations/agents_publish.ts +611 -0
- package/src/tools/implementations/agents_read.ts +173 -0
- package/src/tools/implementations/agents_write.ts +200 -0
- package/src/tools/implementations/ask.ts +411 -0
- package/src/tools/implementations/change_model.ts +141 -0
- package/src/tools/implementations/conversation_get.ts +661 -0
- package/src/tools/implementations/conversation_list.ts +377 -0
- package/src/tools/implementations/conversation_search.ts +370 -0
- package/src/tools/implementations/delegate.ts +327 -0
- package/src/tools/implementations/delegate_crossproject.ts +209 -0
- package/src/tools/implementations/delegate_followup.ts +300 -0
- package/src/tools/implementations/fs_edit.ts +162 -0
- package/src/tools/implementations/fs_glob.ts +182 -0
- package/src/tools/implementations/fs_grep.ts +513 -0
- package/src/tools/implementations/fs_read.ts +332 -0
- package/src/tools/implementations/fs_write.ts +113 -0
- package/src/tools/implementations/generate_image.ts +259 -0
- package/src/tools/implementations/home_fs.ts +515 -0
- package/src/tools/implementations/kill.ts +651 -0
- package/src/tools/implementations/learn.ts +166 -0
- package/src/tools/implementations/lesson-formatter.ts +38 -0
- package/src/tools/implementations/lesson_delete.ts +164 -0
- package/src/tools/implementations/lesson_get.ts +105 -0
- package/src/tools/implementations/lessons_list.ts +153 -0
- package/src/tools/implementations/mcp_resource_read.ts +161 -0
- package/src/tools/implementations/mcp_subscribe.ts +158 -0
- package/src/tools/implementations/mcp_subscription_stop.ts +85 -0
- package/src/tools/implementations/nostr_fetch.ts +149 -0
- package/src/tools/implementations/nostr_publish_as_user.ts +353 -0
- package/src/tools/implementations/project_list.ts +146 -0
- package/src/tools/implementations/rag_add_documents.ts +573 -0
- package/src/tools/implementations/rag_create_collection.ts +65 -0
- package/src/tools/implementations/rag_delete_collection.ts +68 -0
- package/src/tools/implementations/rag_list_collections.ts +77 -0
- package/src/tools/implementations/rag_query.ts +107 -0
- package/src/tools/implementations/rag_subscription_create.ts +105 -0
- package/src/tools/implementations/rag_subscription_delete.ts +80 -0
- package/src/tools/implementations/rag_subscription_get.ts +123 -0
- package/src/tools/implementations/rag_subscription_list.ts +128 -0
- package/src/tools/implementations/report_delete.ts +79 -0
- package/src/tools/implementations/report_read.ts +160 -0
- package/src/tools/implementations/report_write.ts +278 -0
- package/src/tools/implementations/reports_list.ts +77 -0
- package/src/tools/implementations/schedule_task.ts +104 -0
- package/src/tools/implementations/schedule_task_cancel.ts +62 -0
- package/src/tools/implementations/schedule_task_once.ts +128 -0
- package/src/tools/implementations/schedule_tasks_list.ts +79 -0
- package/src/tools/implementations/search.ts +160 -0
- package/src/tools/implementations/shell.ts +553 -0
- package/src/tools/implementations/todo.ts +260 -0
- package/src/tools/implementations/upload_blob.ts +381 -0
- package/src/tools/implementations/web_fetch.ts +153 -0
- package/src/tools/implementations/web_search.ts +250 -0
- package/src/tools/registry.ts +670 -0
- package/src/tools/types.ts +177 -0
- package/src/tools/utils.ts +256 -0
- package/src/types/event-ids.ts +320 -0
- package/src/types/index.ts +46 -0
- package/src/utils/agentFetcher.ts +107 -0
- package/src/utils/cli-error.ts +29 -0
- package/src/utils/conversation-id.ts +27 -0
- package/src/utils/conversation-utils.ts +1 -0
- package/src/utils/delegation-chain.ts +357 -0
- package/src/utils/error-handler.ts +42 -0
- package/src/utils/git/gitignore.ts +69 -0
- package/src/utils/git/index.ts +2 -0
- package/src/utils/git/initializeGitRepo.ts +204 -0
- package/src/utils/git/worktree.ts +260 -0
- package/src/utils/lessonFormatter.ts +70 -0
- package/src/utils/lessonTrust.ts +24 -0
- package/src/utils/lockfile.ts +123 -0
- package/src/utils/logger.ts +149 -0
- package/src/utils/nostr-entity-parser.ts +365 -0
- package/src/utils/process.ts +49 -0
- package/src/wrapper.ts +262 -0
- 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, "&")
|
|
20
|
+
.replace(/"/g, """)
|
|
21
|
+
.replace(/</g, "<")
|
|
22
|
+
.replace(/>/g, ">");
|
|
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
|
+
};
|