@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,866 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Message builder for converting ConversationStore entries to LLM messages.
|
|
3
|
+
*
|
|
4
|
+
* This module handles the complex logic of building ModelMessages from
|
|
5
|
+
* ConversationEntry records, including:
|
|
6
|
+
* - Tool call/result ordering for AI SDK validation
|
|
7
|
+
* - Orphaned tool call reconciliation
|
|
8
|
+
* - Message deference during pending tool execution
|
|
9
|
+
* - Delegation completion pruning
|
|
10
|
+
* - Message attribution for unexpected senders
|
|
11
|
+
* - AGENTS.md system reminder injection for file-read operations
|
|
12
|
+
*/
|
|
13
|
+
import type { ModelMessage, ToolCallPart, ToolResultPart } from "ai";
|
|
14
|
+
import { trace } from "@opentelemetry/api";
|
|
15
|
+
import type { ConversationEntry, DelegationMarker } from "./types";
|
|
16
|
+
import { getPubkeyService } from "@/services/PubkeyService";
|
|
17
|
+
import { convertToMultimodalContent, hasImageUrls } from "./utils/multimodal-content";
|
|
18
|
+
import { processToolResult, shouldTruncateToolResult, type TruncationContext } from "./utils/tool-result-truncator";
|
|
19
|
+
import {
|
|
20
|
+
createImageTracker,
|
|
21
|
+
processToolResultWithImageTracking,
|
|
22
|
+
type ImageTracker,
|
|
23
|
+
} from "./utils/image-placeholder";
|
|
24
|
+
import { extractImageUrls } from "./utils/image-url-utils";
|
|
25
|
+
import {
|
|
26
|
+
createAgentsMdVisibilityTracker,
|
|
27
|
+
getSystemRemindersForPath,
|
|
28
|
+
shouldInjectForTool,
|
|
29
|
+
extractPathFromToolInput,
|
|
30
|
+
appendSystemReminderToOutput,
|
|
31
|
+
type AgentsMdVisibilityTracker,
|
|
32
|
+
} from "@/services/agents-md";
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Extract tool input from a ToolCallPart.
|
|
36
|
+
* AI SDK v6 uses 'input' property, but some storage formats use 'args'.
|
|
37
|
+
* This utility handles both cases in a type-safe manner.
|
|
38
|
+
*/
|
|
39
|
+
function getToolInput(part: ToolCallPart): unknown {
|
|
40
|
+
// ToolCallPart type may not include 'args', but storage format might
|
|
41
|
+
const partWithArgs = part as ToolCallPart & { args?: unknown };
|
|
42
|
+
return partWithArgs.input ?? partWithArgs.args;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export interface MessageBuilderContext {
|
|
46
|
+
/**
|
|
47
|
+
* The pubkey of the agent viewing/building messages
|
|
48
|
+
*/
|
|
49
|
+
viewingAgentPubkey: string;
|
|
50
|
+
/**
|
|
51
|
+
* The current RAL number for the execution
|
|
52
|
+
*/
|
|
53
|
+
ralNumber: number;
|
|
54
|
+
/**
|
|
55
|
+
* Set of currently active RAL numbers for the agent
|
|
56
|
+
*/
|
|
57
|
+
activeRals: Set<number>;
|
|
58
|
+
/**
|
|
59
|
+
* Index offset when processing a slice of messages (default 0)
|
|
60
|
+
*/
|
|
61
|
+
indexOffset?: number;
|
|
62
|
+
/**
|
|
63
|
+
* Total message count for truncation context
|
|
64
|
+
*/
|
|
65
|
+
totalMessages: number;
|
|
66
|
+
/**
|
|
67
|
+
* Project root directory for AGENTS.md discovery.
|
|
68
|
+
* If provided, enables system reminder injection after file-read tool results.
|
|
69
|
+
*/
|
|
70
|
+
projectRoot?: string;
|
|
71
|
+
/**
|
|
72
|
+
* Set of pubkeys that belong to agents (non-whitelisted).
|
|
73
|
+
* Used by computeAttributionPrefix to distinguish agent messages from user messages.
|
|
74
|
+
* If not provided, all non-self pubkeys are treated as users (no agent attribution).
|
|
75
|
+
*/
|
|
76
|
+
agentPubkeys?: Set<string>;
|
|
77
|
+
/**
|
|
78
|
+
* The conversation ID being built.
|
|
79
|
+
* Required for delegation marker expansion - markers are only expanded
|
|
80
|
+
* if their parentConversationId matches this conversationId (direct children only).
|
|
81
|
+
*/
|
|
82
|
+
conversationId?: string;
|
|
83
|
+
/**
|
|
84
|
+
* Callback to get messages from a delegation conversation.
|
|
85
|
+
* Used for lazy expansion of delegation markers.
|
|
86
|
+
* Returns the messages array or undefined if conversation not found.
|
|
87
|
+
*/
|
|
88
|
+
getDelegationMessages?: (delegationConversationId: string) => ConversationEntry[] | undefined;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* Context for AGENTS.md system reminder injection
|
|
93
|
+
*/
|
|
94
|
+
interface AgentsMdContext {
|
|
95
|
+
/** Project root for AGENTS.md discovery */
|
|
96
|
+
projectRoot: string;
|
|
97
|
+
/** Visibility tracker for deduplication */
|
|
98
|
+
tracker: AgentsMdVisibilityTracker;
|
|
99
|
+
/** Map of toolCallId -> path for file-read tools */
|
|
100
|
+
toolCallPaths: Map<string, string>;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
/**
|
|
104
|
+
* Derive the appropriate role for a message based on viewer perspective.
|
|
105
|
+
*
|
|
106
|
+
* Rules:
|
|
107
|
+
* - Explicit role override: If entry.role is set (for synthetic entries like compressed summaries), use it
|
|
108
|
+
* - assistant: Only for the viewing agent's own messages
|
|
109
|
+
* - user: All other messages (regardless of targeting)
|
|
110
|
+
* - tool: Tool results (fixed)
|
|
111
|
+
* - system: Synthetic system messages (compressed summaries, etc.)
|
|
112
|
+
*
|
|
113
|
+
* Note: Attribution context is not added to LLM input. Role simply distinguishes
|
|
114
|
+
* between the agent's own messages and messages from others.
|
|
115
|
+
*/
|
|
116
|
+
function deriveRole(
|
|
117
|
+
entry: ConversationEntry,
|
|
118
|
+
viewingAgentPubkey: string
|
|
119
|
+
): "user" | "assistant" | "tool" | "system" {
|
|
120
|
+
// Explicit role override for synthetic entries (e.g., compressed summaries)
|
|
121
|
+
// CRITICAL: Without this, compressed summaries with pubkey="system" would become "user" role,
|
|
122
|
+
// turning compressed history into user instructions and causing catastrophic LLM behavior.
|
|
123
|
+
if (entry.role) {
|
|
124
|
+
return entry.role;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
// Tool messages have fixed roles
|
|
128
|
+
if (entry.messageType === "tool-call") return "assistant";
|
|
129
|
+
if (entry.messageType === "tool-result") return "tool";
|
|
130
|
+
|
|
131
|
+
// Text messages - assistant for own, user for everything else
|
|
132
|
+
if (entry.pubkey === viewingAgentPubkey) {
|
|
133
|
+
return "assistant"; // Own messages
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
return "user"; // All non-self messages
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
/**
|
|
140
|
+
* Compute an attribution prefix for a conversation entry.
|
|
141
|
+
*
|
|
142
|
+
* Returns a string prefix (or empty string) to prepend to the message content,
|
|
143
|
+
* enabling the LLM to distinguish who said what in multi-agent shared conversations.
|
|
144
|
+
*
|
|
145
|
+
* Priority rules (ordered, first match wins):
|
|
146
|
+
* 1. Self message → "" (no prefix)
|
|
147
|
+
* 2. Non-text entry (tool-call, tool-result, delegation-marker, role-override) → "" (no prefix)
|
|
148
|
+
* 3. Has targetedPubkeys NOT including viewing agent → "[@sender -> @recipient] " (routing)
|
|
149
|
+
* 4. Sender is agent (in agentPubkeys set) → "[@sender] " (attribution)
|
|
150
|
+
* 5. Otherwise (user message targeted to me, or no targeting) → "" (no prefix)
|
|
151
|
+
*
|
|
152
|
+
* **Purity note:** This function is pure when a custom `resolveDisplayName` is provided
|
|
153
|
+
* (as done in unit tests). The default resolver calls `PubkeyService.getNameSync()`, which
|
|
154
|
+
* reads from a global service singleton—this is intentional for production use but means
|
|
155
|
+
* the default invocation is not referentially transparent.
|
|
156
|
+
*
|
|
157
|
+
* @param entry - The conversation entry to compute prefix for
|
|
158
|
+
* @param viewingAgentPubkey - The pubkey of the agent viewing/building messages
|
|
159
|
+
* @param agentPubkeys - Set of pubkeys that belong to agents (used to distinguish agents from users)
|
|
160
|
+
* @param resolveDisplayName - Optional injectable name resolver (defaults to PubkeyService.getNameSync)
|
|
161
|
+
* @returns The prefix string to prepend, or empty string for no prefix
|
|
162
|
+
*/
|
|
163
|
+
export function computeAttributionPrefix(
|
|
164
|
+
entry: ConversationEntry,
|
|
165
|
+
viewingAgentPubkey: string,
|
|
166
|
+
agentPubkeys: Set<string>,
|
|
167
|
+
resolveDisplayName?: (pubkey: string) => string
|
|
168
|
+
): string {
|
|
169
|
+
const resolve = resolveDisplayName ?? ((pk: string) => {
|
|
170
|
+
try {
|
|
171
|
+
const name = getPubkeyService().getNameSync(pk);
|
|
172
|
+
return name || pk.substring(0, 8);
|
|
173
|
+
} catch {
|
|
174
|
+
return pk.substring(0, 8);
|
|
175
|
+
}
|
|
176
|
+
});
|
|
177
|
+
|
|
178
|
+
// Determine the actual sender (injected messages track original sender via senderPubkey)
|
|
179
|
+
const senderPubkey = entry.senderPubkey ?? entry.pubkey;
|
|
180
|
+
|
|
181
|
+
// Rule 1: Self message → no prefix
|
|
182
|
+
if (senderPubkey === viewingAgentPubkey) return "";
|
|
183
|
+
|
|
184
|
+
// Rule 2: Non-text entry → no prefix
|
|
185
|
+
if (entry.messageType !== "text") return "";
|
|
186
|
+
if (entry.role) return ""; // Explicit role override (synthetic entries like compressed summaries)
|
|
187
|
+
|
|
188
|
+
// Rule 3: Has targetedPubkeys NOT including viewing agent → routing prefix
|
|
189
|
+
const targetedPubkeys = entry.targetedPubkeys ?? [];
|
|
190
|
+
if (targetedPubkeys.length > 0 && !targetedPubkeys.includes(viewingAgentPubkey)) {
|
|
191
|
+
const senderName = resolve(senderPubkey);
|
|
192
|
+
const recipientName = resolve(targetedPubkeys[0]);
|
|
193
|
+
return `[@${senderName} -> @${recipientName}] `;
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
// Rule 4: Sender is agent → attribution prefix
|
|
197
|
+
if (agentPubkeys.has(senderPubkey)) {
|
|
198
|
+
const senderName = resolve(senderPubkey);
|
|
199
|
+
return `[@${senderName}] `;
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
// Rule 5: Otherwise (user message targeted to me, or no targeting) → no prefix
|
|
203
|
+
return "";
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
/**
|
|
207
|
+
* Convert a ConversationEntry to a ModelMessage for the viewing agent.
|
|
208
|
+
*
|
|
209
|
+
* Attribution prefixes are added for multi-agent shared conversations using
|
|
210
|
+
* computeAttributionPrefix() to help the LLM distinguish who said what.
|
|
211
|
+
*
|
|
212
|
+
* For text messages containing image URLs, the content is converted to
|
|
213
|
+
* multimodal format (TextPart + ImagePart array) for AI SDK compatibility.
|
|
214
|
+
*
|
|
215
|
+
* Image Placeholder Strategy:
|
|
216
|
+
* - First appearance of an image: shown in full
|
|
217
|
+
* - Subsequent appearances: replaced with placeholder referencing eventId
|
|
218
|
+
* - The imageTracker tracks which images have been seen
|
|
219
|
+
*
|
|
220
|
+
* AGENTS.md System Reminders:
|
|
221
|
+
* - When a file-read tool result is processed, check for AGENTS.md files
|
|
222
|
+
* - Inject system reminders after the tool output for newly visible AGENTS.md files
|
|
223
|
+
* - Track visibility to avoid duplication in subsequent tool results
|
|
224
|
+
*/
|
|
225
|
+
async function entryToMessage(
|
|
226
|
+
entry: ConversationEntry,
|
|
227
|
+
viewingAgentPubkey: string,
|
|
228
|
+
truncationContext: TruncationContext | undefined,
|
|
229
|
+
agentPubkeys: Set<string>,
|
|
230
|
+
imageTracker: ImageTracker,
|
|
231
|
+
agentsMdContext?: AgentsMdContext,
|
|
232
|
+
enableMultimodal: boolean = true
|
|
233
|
+
): Promise<ModelMessage> {
|
|
234
|
+
const role = deriveRole(entry, viewingAgentPubkey);
|
|
235
|
+
|
|
236
|
+
if (entry.messageType === "tool-call" && entry.toolData) {
|
|
237
|
+
// Track paths from file-read tool calls for AGENTS.md injection
|
|
238
|
+
if (agentsMdContext) {
|
|
239
|
+
for (const part of entry.toolData as ToolCallPart[]) {
|
|
240
|
+
if (shouldInjectForTool(part.toolName)) {
|
|
241
|
+
const toolInput = getToolInput(part);
|
|
242
|
+
const path = extractPathFromToolInput(toolInput);
|
|
243
|
+
if (path) {
|
|
244
|
+
agentsMdContext.toolCallPaths.set(part.toolCallId, path);
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
return { role: "assistant", content: entry.toolData as ToolCallPart[] };
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
if (entry.messageType === "tool-result" && entry.toolData) {
|
|
253
|
+
// First: Apply image placeholder strategy (tracks & replaces seen images)
|
|
254
|
+
const imageProcessingResult = processToolResultWithImageTracking(
|
|
255
|
+
entry.toolData as ToolResultPart[],
|
|
256
|
+
imageTracker,
|
|
257
|
+
entry.eventId
|
|
258
|
+
);
|
|
259
|
+
let toolData = imageProcessingResult.processedParts;
|
|
260
|
+
|
|
261
|
+
// Check if result will be truncated (for AGENTS.md visibility tracking)
|
|
262
|
+
const willBeTruncated = truncationContext
|
|
263
|
+
? shouldTruncateToolResult(toolData, truncationContext)
|
|
264
|
+
: false;
|
|
265
|
+
|
|
266
|
+
// Then: Apply truncation for buried tool results to save context
|
|
267
|
+
toolData = truncationContext
|
|
268
|
+
? processToolResult(toolData, truncationContext)
|
|
269
|
+
: toolData;
|
|
270
|
+
|
|
271
|
+
// Inject AGENTS.md system reminders after file-read tool results
|
|
272
|
+
// Only inject if:
|
|
273
|
+
// 1. AGENTS.md context is available
|
|
274
|
+
// 2. Tool result is NOT truncated (reminders would be lost)
|
|
275
|
+
// 3. Tool is a file-read operation with a tracked path
|
|
276
|
+
if (agentsMdContext && !willBeTruncated) {
|
|
277
|
+
for (const part of toolData) {
|
|
278
|
+
const path = agentsMdContext.toolCallPaths.get(part.toolCallId);
|
|
279
|
+
if (path) {
|
|
280
|
+
const reminders = await getSystemRemindersForPath(
|
|
281
|
+
path,
|
|
282
|
+
agentsMdContext.projectRoot,
|
|
283
|
+
agentsMdContext.tracker,
|
|
284
|
+
willBeTruncated
|
|
285
|
+
);
|
|
286
|
+
|
|
287
|
+
if (reminders.hasReminders) {
|
|
288
|
+
// Cast is needed because appendSystemReminderToOutput returns unknown
|
|
289
|
+
(part as { output: unknown }).output = appendSystemReminderToOutput(part.output, reminders.content);
|
|
290
|
+
|
|
291
|
+
// Telemetry for AGENTS.md injection
|
|
292
|
+
trace.getActiveSpan?.()?.addEvent("conversation.agents_md_injected", {
|
|
293
|
+
"agents_md.file_count": reminders.includedFiles.length,
|
|
294
|
+
"agents_md.paths": reminders.includedFiles.map(f => f.directory).join(","),
|
|
295
|
+
"agents_md.target_path": path,
|
|
296
|
+
});
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
// Clean up the tracked path
|
|
300
|
+
agentsMdContext.toolCallPaths.delete(part.toolCallId);
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
return {
|
|
306
|
+
role: "tool",
|
|
307
|
+
content: toolData,
|
|
308
|
+
_imageReplacementStats: imageProcessingResult.replacedCount > 0
|
|
309
|
+
? { replacedCount: imageProcessingResult.replacedCount, uniqueReplacedCount: imageProcessingResult.uniqueReplacedCount }
|
|
310
|
+
: undefined,
|
|
311
|
+
} as unknown as ModelMessage;
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
// Text message - compute attribution prefix for multi-agent conversations
|
|
315
|
+
const prefix = computeAttributionPrefix(entry, viewingAgentPubkey, agentPubkeys);
|
|
316
|
+
let messageContent = prefix ? `${prefix}${entry.content}` : entry.content;
|
|
317
|
+
|
|
318
|
+
// Track any images in text messages (but don't replace them - user content)
|
|
319
|
+
// This ensures that if the same image appears later in a tool result,
|
|
320
|
+
// it will be replaced with a placeholder
|
|
321
|
+
const imageUrls = extractImageUrls(messageContent);
|
|
322
|
+
for (const url of imageUrls) {
|
|
323
|
+
imageTracker.markAsSeen(url);
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
// Convert to multimodal format if content contains image URLs, but ONLY for user messages
|
|
327
|
+
// AND only when enableMultimodal is true (i.e., the most recent user message with images).
|
|
328
|
+
//
|
|
329
|
+
// The AI SDK ModelMessage[] schema only allows ImagePart in user role messages (UserModelMessage).
|
|
330
|
+
// AssistantModelMessage content only supports TextPart, ReasoningPart, ToolCallPart, etc. — no ImagePart.
|
|
331
|
+
// Applying multimodal conversion to assistant messages causes:
|
|
332
|
+
// AI_InvalidPromptError: Invalid prompt: The messages do not match the ModelMessage[] schema.
|
|
333
|
+
//
|
|
334
|
+
// For older user messages that contained images, we keep the URL as plain text in the string —
|
|
335
|
+
// the LLM has already seen them, no need to re-fetch and waste context window on image tokens.
|
|
336
|
+
const content = (role === "user" && enableMultimodal) ? convertToMultimodalContent(messageContent) : messageContent;
|
|
337
|
+
|
|
338
|
+
return { role, content } as ModelMessage;
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
/**
|
|
342
|
+
* Expand a delegation marker into a formatted transcript message.
|
|
343
|
+
*
|
|
344
|
+
* This function formats messages from a delegation conversation into a flat
|
|
345
|
+
* transcript. The transcript includes:
|
|
346
|
+
* - Text messages only (not tool calls/results)
|
|
347
|
+
* - Messages with p-tags (targeted to specific recipients)
|
|
348
|
+
* - No nested delegation markers (flat expansion only)
|
|
349
|
+
*
|
|
350
|
+
* Format: [@sender -> @recipient]: message content
|
|
351
|
+
*
|
|
352
|
+
* @param marker - The delegation marker to expand
|
|
353
|
+
* @param delegationMessages - Messages from the delegation conversation, or undefined if not found
|
|
354
|
+
* @returns Formatted transcript as a ModelMessage
|
|
355
|
+
*/
|
|
356
|
+
async function expandDelegationMarker(
|
|
357
|
+
marker: DelegationMarker,
|
|
358
|
+
delegationMessages: ConversationEntry[] | undefined
|
|
359
|
+
): Promise<ModelMessage> {
|
|
360
|
+
const pubkeyService = getPubkeyService();
|
|
361
|
+
|
|
362
|
+
// Handle pending delegations - show that work is in progress
|
|
363
|
+
if (marker.status === "pending") {
|
|
364
|
+
try {
|
|
365
|
+
const recipientName = await pubkeyService.getName(marker.recipientPubkey);
|
|
366
|
+
return {
|
|
367
|
+
role: "user",
|
|
368
|
+
content: `# DELEGATION IN PROGRESS\n\n@${recipientName} is currently working on this task.`,
|
|
369
|
+
};
|
|
370
|
+
} catch {
|
|
371
|
+
return {
|
|
372
|
+
role: "user",
|
|
373
|
+
content: `# DELEGATION IN PROGRESS\n\nAgent ${marker.recipientPubkey.substring(0, 12)} is currently working on this task.`,
|
|
374
|
+
};
|
|
375
|
+
}
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
if (!delegationMessages) {
|
|
379
|
+
// Delegation conversation not found - return placeholder
|
|
380
|
+
try {
|
|
381
|
+
const recipientName = await pubkeyService.getName(marker.recipientPubkey);
|
|
382
|
+
const statusText = marker.status === "aborted"
|
|
383
|
+
? `was aborted: ${marker.abortReason || "unknown reason"}`
|
|
384
|
+
: "completed (transcript unavailable)";
|
|
385
|
+
return {
|
|
386
|
+
role: "user",
|
|
387
|
+
content: `# DELEGATION ${marker.status.toUpperCase()}\n\n@${recipientName} ${statusText}`,
|
|
388
|
+
};
|
|
389
|
+
} catch {
|
|
390
|
+
return {
|
|
391
|
+
role: "user",
|
|
392
|
+
content: `# DELEGATION ${marker.status.toUpperCase()}\n\nAgent ${marker.recipientPubkey.substring(0, 12)} ${marker.status === "aborted" ? "was aborted" : "completed"} (transcript unavailable)`,
|
|
393
|
+
};
|
|
394
|
+
}
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
// Build flat transcript from delegation conversation
|
|
398
|
+
const lines: string[] = [];
|
|
399
|
+
|
|
400
|
+
// Header based on status
|
|
401
|
+
if (marker.status === "aborted") {
|
|
402
|
+
lines.push("# DELEGATION ABORTED");
|
|
403
|
+
lines.push("");
|
|
404
|
+
if (marker.abortReason) {
|
|
405
|
+
lines.push(`**Reason:** ${marker.abortReason}`);
|
|
406
|
+
lines.push("");
|
|
407
|
+
}
|
|
408
|
+
} else {
|
|
409
|
+
lines.push("# DELEGATION COMPLETED");
|
|
410
|
+
lines.push("");
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
// Filter for targeted text messages only (no tool calls, no nested markers)
|
|
414
|
+
const transcriptMessages = delegationMessages.filter(msg =>
|
|
415
|
+
msg.messageType === "text" &&
|
|
416
|
+
msg.targetedPubkeys &&
|
|
417
|
+
msg.targetedPubkeys.length > 0
|
|
418
|
+
);
|
|
419
|
+
|
|
420
|
+
if (transcriptMessages.length === 0) {
|
|
421
|
+
lines.push("(No messages in delegation transcript)");
|
|
422
|
+
} else {
|
|
423
|
+
lines.push("### Transcript:");
|
|
424
|
+
for (const msg of transcriptMessages) {
|
|
425
|
+
try {
|
|
426
|
+
const senderName = await pubkeyService.getName(msg.pubkey);
|
|
427
|
+
const recipientName = msg.targetedPubkeys?.[0]
|
|
428
|
+
? await pubkeyService.getName(msg.targetedPubkeys[0])
|
|
429
|
+
: "unknown";
|
|
430
|
+
lines.push(`[@${senderName} -> @${recipientName}]: ${msg.content}`);
|
|
431
|
+
} catch {
|
|
432
|
+
// Fallback to shortened pubkeys
|
|
433
|
+
const senderFallback = msg.pubkey.substring(0, 12);
|
|
434
|
+
const recipientFallback = msg.targetedPubkeys?.[0]?.substring(0, 12) || "unknown";
|
|
435
|
+
lines.push(`[@${senderFallback} -> @${recipientFallback}]: ${msg.content}`);
|
|
436
|
+
}
|
|
437
|
+
}
|
|
438
|
+
}
|
|
439
|
+
|
|
440
|
+
return {
|
|
441
|
+
role: "user",
|
|
442
|
+
content: lines.join("\n"),
|
|
443
|
+
};
|
|
444
|
+
}
|
|
445
|
+
|
|
446
|
+
/**
|
|
447
|
+
* Format a nested delegation marker as a minimal reference.
|
|
448
|
+
*
|
|
449
|
+
* For nested delegations (delegations that occurred within another delegation),
|
|
450
|
+
* we don't expand the full transcript to avoid exponential bloat. Instead, we
|
|
451
|
+
* display a minimal marker showing:
|
|
452
|
+
* - The recipient agent
|
|
453
|
+
* - The delegation conversation ID (shortened)
|
|
454
|
+
* - The status (completed/aborted)
|
|
455
|
+
*
|
|
456
|
+
* This provides visibility that a delegation happened without including
|
|
457
|
+
* potentially large transcripts in the parent conversation context.
|
|
458
|
+
*
|
|
459
|
+
* @param marker - The delegation marker to format
|
|
460
|
+
* @returns Minimal reference message as a ModelMessage
|
|
461
|
+
*/
|
|
462
|
+
async function formatNestedDelegationMarker(
|
|
463
|
+
marker: DelegationMarker
|
|
464
|
+
): Promise<ModelMessage> {
|
|
465
|
+
const pubkeyService = getPubkeyService();
|
|
466
|
+
|
|
467
|
+
let recipientName: string;
|
|
468
|
+
try {
|
|
469
|
+
recipientName = await pubkeyService.getName(marker.recipientPubkey);
|
|
470
|
+
} catch {
|
|
471
|
+
recipientName = marker.recipientPubkey.substring(0, 12);
|
|
472
|
+
}
|
|
473
|
+
|
|
474
|
+
const shortConversationId = marker.delegationConversationId.substring(0, 12);
|
|
475
|
+
|
|
476
|
+
// Simple one-line format: [Delegation to @recipient (conv: abc123...) - status]
|
|
477
|
+
let statusSuffix: string;
|
|
478
|
+
if (marker.status === "aborted") {
|
|
479
|
+
statusSuffix = ` - aborted${marker.abortReason ? `: ${marker.abortReason}` : ""}`;
|
|
480
|
+
} else if (marker.status === "pending") {
|
|
481
|
+
statusSuffix = " - pending";
|
|
482
|
+
} else {
|
|
483
|
+
statusSuffix = " - completed";
|
|
484
|
+
}
|
|
485
|
+
|
|
486
|
+
return {
|
|
487
|
+
role: "user",
|
|
488
|
+
content: `[Delegation to @${recipientName} (conv: ${shortConversationId}...)${statusSuffix}]`,
|
|
489
|
+
};
|
|
490
|
+
}
|
|
491
|
+
|
|
492
|
+
/**
|
|
493
|
+
* Build ModelMessages from conversation entries.
|
|
494
|
+
*
|
|
495
|
+
* This function handles the complex logic of:
|
|
496
|
+
* 1. Filtering messages by RAL visibility rules
|
|
497
|
+
* 2. Ensuring tool-call/tool-result ordering for AI SDK validation
|
|
498
|
+
* 3. Deferring non-tool messages while tool-calls are pending
|
|
499
|
+
* 4. Injecting synthetic results for orphaned tool-calls
|
|
500
|
+
* 5. Pruning superseded delegation completion messages
|
|
501
|
+
*
|
|
502
|
+
* The AI SDK (Vercel AI) validates that every tool-call message is immediately
|
|
503
|
+
* followed by its corresponding tool-result message. This function ensures
|
|
504
|
+
* that validation passes even when messages arrive out of order.
|
|
505
|
+
*/
|
|
506
|
+
export async function buildMessagesFromEntries(
|
|
507
|
+
entries: ConversationEntry[],
|
|
508
|
+
ctx: MessageBuilderContext
|
|
509
|
+
): Promise<ModelMessage[]> {
|
|
510
|
+
const {
|
|
511
|
+
viewingAgentPubkey,
|
|
512
|
+
ralNumber,
|
|
513
|
+
activeRals,
|
|
514
|
+
indexOffset = 0,
|
|
515
|
+
totalMessages,
|
|
516
|
+
agentPubkeys = new Set<string>(),
|
|
517
|
+
projectRoot,
|
|
518
|
+
} = ctx;
|
|
519
|
+
|
|
520
|
+
const result: ModelMessage[] = [];
|
|
521
|
+
const delegationCompletionPrefix = "# DELEGATION COMPLETED";
|
|
522
|
+
|
|
523
|
+
// Image placeholder strategy: Track seen images across all messages
|
|
524
|
+
// First appearance = full image, subsequent = placeholder
|
|
525
|
+
const imageTracker = createImageTracker();
|
|
526
|
+
|
|
527
|
+
// AGENTS.md system reminder context (only if projectRoot is provided)
|
|
528
|
+
const agentsMdContext: AgentsMdContext | undefined = projectRoot
|
|
529
|
+
? {
|
|
530
|
+
projectRoot,
|
|
531
|
+
tracker: createAgentsMdVisibilityTracker(),
|
|
532
|
+
toolCallPaths: new Map(),
|
|
533
|
+
}
|
|
534
|
+
: undefined;
|
|
535
|
+
|
|
536
|
+
// Track latest delegation completion for each RAL (to prune superseded ones)
|
|
537
|
+
const latestDelegationCompletionIndexByRal = new Map<number, number>();
|
|
538
|
+
const getDelegationCompletionRal = (entry: ConversationEntry): number | undefined => {
|
|
539
|
+
if (entry.messageType !== "text") return undefined;
|
|
540
|
+
if (typeof entry.ral !== "number") return undefined;
|
|
541
|
+
if (!entry.content.trimStart().startsWith(delegationCompletionPrefix)) return undefined;
|
|
542
|
+
if (!(entry.targetedPubkeys?.includes(viewingAgentPubkey) ?? false)) return undefined;
|
|
543
|
+
return entry.ral;
|
|
544
|
+
};
|
|
545
|
+
|
|
546
|
+
// First pass: identify latest delegation completion for each RAL
|
|
547
|
+
for (let i = 0; i < entries.length; i++) {
|
|
548
|
+
const entry = entries[i];
|
|
549
|
+
const ral = getDelegationCompletionRal(entry);
|
|
550
|
+
if (ral !== undefined) {
|
|
551
|
+
latestDelegationCompletionIndexByRal.set(ral, i);
|
|
552
|
+
}
|
|
553
|
+
}
|
|
554
|
+
|
|
555
|
+
// Pre-scan: find the last user text message that contains image URLs.
|
|
556
|
+
// Only that message gets multimodal conversion (ImagePart objects that trigger image fetching).
|
|
557
|
+
// Older user messages with images keep URLs as plain text — the LLM already saw them,
|
|
558
|
+
// no need to re-fetch and consume context window with image tokens.
|
|
559
|
+
let lastUserImageEntryIndex = -1;
|
|
560
|
+
for (let i = 0; i < entries.length; i++) {
|
|
561
|
+
const entry = entries[i];
|
|
562
|
+
if (entry.messageType !== "text") continue;
|
|
563
|
+
if (deriveRole(entry, viewingAgentPubkey) !== "user") continue;
|
|
564
|
+
if (hasImageUrls(entry.content)) {
|
|
565
|
+
lastUserImageEntryIndex = i;
|
|
566
|
+
}
|
|
567
|
+
}
|
|
568
|
+
|
|
569
|
+
let prunedDelegationCompletions = 0;
|
|
570
|
+
|
|
571
|
+
// ============================================================================
|
|
572
|
+
// TOOL-CALL / TOOL-RESULT ORDERING FIX
|
|
573
|
+
// ============================================================================
|
|
574
|
+
//
|
|
575
|
+
// The AI SDK (Vercel AI) validates that every tool-call message is immediately
|
|
576
|
+
// followed by its corresponding tool-result message. If any other message type
|
|
577
|
+
// (user, assistant text, system) appears between a tool-call and its result,
|
|
578
|
+
// the SDK throws: "Tool result is missing for tool call <id>"
|
|
579
|
+
//
|
|
580
|
+
// PROBLEM 1: User messages arriving mid-tool-execution
|
|
581
|
+
// ----------------------------------------------------
|
|
582
|
+
// Messages are stored in ConversationStore in chronological order:
|
|
583
|
+
// 1. Agent issues tool-call (stored immediately)
|
|
584
|
+
// 2. User sends a new message (stored while tool executes)
|
|
585
|
+
// 3. Tool completes, result stored
|
|
586
|
+
//
|
|
587
|
+
// This results in: [tool-call, user-message, tool-result]
|
|
588
|
+
// But AI SDK requires: [tool-call, tool-result, user-message]
|
|
589
|
+
//
|
|
590
|
+
// PROBLEM 2: Orphaned tool-calls from RAL interruption
|
|
591
|
+
// ----------------------------------------------------
|
|
592
|
+
// When a RAL is aborted mid-execution (e.g., due to a delegation completion
|
|
593
|
+
// triggering an injection), tool-calls may be stored but their results never
|
|
594
|
+
// recorded. The tool continues executing in the background, but the stream
|
|
595
|
+
// handler that would record the result has been torn down.
|
|
596
|
+
//
|
|
597
|
+
// When the RAL resumes, it has orphaned tool-calls with no matching results.
|
|
598
|
+
//
|
|
599
|
+
// SOLUTION:
|
|
600
|
+
// ---------
|
|
601
|
+
// 1. Track pending tool-calls (those without results yet)
|
|
602
|
+
// 2. Defer non-tool messages while tool-calls are pending
|
|
603
|
+
// 3. Flush deferred messages only after all pending results arrive
|
|
604
|
+
// 4. For orphaned tool-calls (no result ever stored), inject synthetic
|
|
605
|
+
// error results to satisfy the AI SDK validation
|
|
606
|
+
//
|
|
607
|
+
// ============================================================================
|
|
608
|
+
|
|
609
|
+
// Map of toolCallId -> {toolName, resultIndex} for pending tool-calls
|
|
610
|
+
// resultIndex tracks where the synthetic result should be inserted if needed
|
|
611
|
+
const pendingToolCalls = new Map<string, { toolName: string; resultIndex: number }>();
|
|
612
|
+
|
|
613
|
+
// Messages deferred because they arrived while tool-calls were pending
|
|
614
|
+
const deferredMessages: Array<{ entry: ConversationEntry; truncationContext: TruncationContext; enableMultimodal: boolean }> = [];
|
|
615
|
+
|
|
616
|
+
for (let i = 0; i < entries.length; i++) {
|
|
617
|
+
const entry = entries[i];
|
|
618
|
+
|
|
619
|
+
// Skip superseded delegation completions
|
|
620
|
+
const ral = getDelegationCompletionRal(entry);
|
|
621
|
+
if (ral !== undefined) {
|
|
622
|
+
const latestIndex = latestDelegationCompletionIndexByRal.get(ral);
|
|
623
|
+
if (latestIndex !== undefined && latestIndex !== i) {
|
|
624
|
+
prunedDelegationCompletions += 1;
|
|
625
|
+
continue;
|
|
626
|
+
}
|
|
627
|
+
}
|
|
628
|
+
|
|
629
|
+
// Create truncation context for tool result processing
|
|
630
|
+
const truncationContext: TruncationContext = {
|
|
631
|
+
currentIndex: indexOffset + i,
|
|
632
|
+
totalMessages,
|
|
633
|
+
eventId: entry.eventId,
|
|
634
|
+
};
|
|
635
|
+
|
|
636
|
+
// Helper to check if entry should be included based on RAL visibility
|
|
637
|
+
const shouldInclude = (): boolean => {
|
|
638
|
+
// User messages (no RAL) - always include
|
|
639
|
+
if (!entry.ral) return true;
|
|
640
|
+
|
|
641
|
+
// Same agent
|
|
642
|
+
if (entry.pubkey === viewingAgentPubkey) {
|
|
643
|
+
if (entry.ral === ralNumber) return true; // Current RAL
|
|
644
|
+
if (activeRals.has(entry.ral)) return false; // Other active RAL - skip
|
|
645
|
+
return true; // Completed RAL
|
|
646
|
+
}
|
|
647
|
+
|
|
648
|
+
// Other agent's message - only include text content
|
|
649
|
+
return entry.messageType === "text" && !!entry.content;
|
|
650
|
+
};
|
|
651
|
+
|
|
652
|
+
if (!shouldInclude()) continue;
|
|
653
|
+
|
|
654
|
+
// TOOL-CALL: Add to result and register as pending
|
|
655
|
+
if (entry.messageType === "tool-call" && entry.toolData) {
|
|
656
|
+
const resultIndex = result.length;
|
|
657
|
+
result.push(await entryToMessage(entry, viewingAgentPubkey, truncationContext, agentPubkeys, imageTracker, agentsMdContext));
|
|
658
|
+
|
|
659
|
+
for (const part of entry.toolData as ToolCallPart[]) {
|
|
660
|
+
pendingToolCalls.set(part.toolCallId, {
|
|
661
|
+
toolName: part.toolName,
|
|
662
|
+
// Result should be inserted right after the tool-call message
|
|
663
|
+
resultIndex: resultIndex + 1,
|
|
664
|
+
});
|
|
665
|
+
}
|
|
666
|
+
continue;
|
|
667
|
+
}
|
|
668
|
+
|
|
669
|
+
// TOOL-RESULT: Add to result and mark tool-call as resolved
|
|
670
|
+
if (entry.messageType === "tool-result" && entry.toolData) {
|
|
671
|
+
result.push(await entryToMessage(entry, viewingAgentPubkey, truncationContext, agentPubkeys, imageTracker, agentsMdContext));
|
|
672
|
+
|
|
673
|
+
for (const part of entry.toolData as ToolResultPart[]) {
|
|
674
|
+
pendingToolCalls.delete(part.toolCallId);
|
|
675
|
+
}
|
|
676
|
+
|
|
677
|
+
// All tool-calls resolved - flush deferred messages now
|
|
678
|
+
if (pendingToolCalls.size === 0 && deferredMessages.length > 0) {
|
|
679
|
+
for (const deferred of deferredMessages) {
|
|
680
|
+
result.push(await entryToMessage(deferred.entry, viewingAgentPubkey, deferred.truncationContext, agentPubkeys, imageTracker, agentsMdContext, deferred.enableMultimodal));
|
|
681
|
+
}
|
|
682
|
+
deferredMessages.length = 0;
|
|
683
|
+
}
|
|
684
|
+
continue;
|
|
685
|
+
}
|
|
686
|
+
|
|
687
|
+
// DELEGATION-MARKER: Expand only if direct child of current conversation
|
|
688
|
+
if (entry.messageType === "delegation-marker" && entry.delegationMarker) {
|
|
689
|
+
const marker = entry.delegationMarker;
|
|
690
|
+
|
|
691
|
+
// Guard: conversationId must be present for marker expansion
|
|
692
|
+
// If missing, log a warning and skip - this indicates a bug in the caller
|
|
693
|
+
if (!ctx.conversationId) {
|
|
694
|
+
trace.getActiveSpan?.()?.addEvent("conversation.delegation_marker_skipped", {
|
|
695
|
+
"delegation.conversation_id": marker.delegationConversationId.substring(0, 12),
|
|
696
|
+
"delegation.parent_conversation_id": marker.parentConversationId.substring(0, 12),
|
|
697
|
+
"skip.reason": "missing_conversation_id",
|
|
698
|
+
"skip.severity": "warning",
|
|
699
|
+
});
|
|
700
|
+
continue;
|
|
701
|
+
}
|
|
702
|
+
|
|
703
|
+
// Only expand direct children - skip nested delegations
|
|
704
|
+
// This prevents exponential transcript bloat
|
|
705
|
+
if (marker.parentConversationId === ctx.conversationId) {
|
|
706
|
+
// Get delegation messages using the provided callback
|
|
707
|
+
const delegationMessages = ctx.getDelegationMessages?.(marker.delegationConversationId);
|
|
708
|
+
const expandedMessage = await expandDelegationMarker(marker, delegationMessages);
|
|
709
|
+
|
|
710
|
+
// CRITICAL: Delegation markers can arrive mid-tool-execution when a delegation
|
|
711
|
+
// completes while tools are running. We must defer them just like regular
|
|
712
|
+
// user messages to maintain tool-call/tool-result adjacency for AI SDK validation.
|
|
713
|
+
if (pendingToolCalls.size > 0) {
|
|
714
|
+
// Create a synthetic entry to defer the already-expanded ModelMessage
|
|
715
|
+
// We store the expanded content as a text entry for the deferred queue
|
|
716
|
+
// CRITICAL: Explicit role: "user" ensures consistency with expandDelegationMarker()
|
|
717
|
+
// which always returns user role. Without this, if recipientPubkey === viewingAgentPubkey
|
|
718
|
+
// (self-delegation), deriveRole() would incorrectly produce "assistant" role.
|
|
719
|
+
deferredMessages.push({
|
|
720
|
+
entry: {
|
|
721
|
+
pubkey: marker.recipientPubkey,
|
|
722
|
+
role: "user",
|
|
723
|
+
content: expandedMessage.content as string,
|
|
724
|
+
messageType: "text",
|
|
725
|
+
// Use the delegation marker's entry properties for context
|
|
726
|
+
eventId: entry.eventId,
|
|
727
|
+
ral: entry.ral,
|
|
728
|
+
},
|
|
729
|
+
truncationContext,
|
|
730
|
+
enableMultimodal: false,
|
|
731
|
+
});
|
|
732
|
+
} else {
|
|
733
|
+
result.push(expandedMessage);
|
|
734
|
+
}
|
|
735
|
+
|
|
736
|
+
trace.getActiveSpan?.()?.addEvent("conversation.delegation_marker_expanded", {
|
|
737
|
+
"delegation.conversation_id": marker.delegationConversationId.substring(0, 12),
|
|
738
|
+
"delegation.status": marker.status,
|
|
739
|
+
"delegation.transcript_found": !!delegationMessages,
|
|
740
|
+
"delegation.deferred": pendingToolCalls.size > 0,
|
|
741
|
+
});
|
|
742
|
+
} else {
|
|
743
|
+
// Nested delegation marker - show minimal reference only
|
|
744
|
+
// Don't expand transcript to avoid exponential bloat
|
|
745
|
+
const nestedMarkerMessage = await formatNestedDelegationMarker(marker);
|
|
746
|
+
|
|
747
|
+
// Same deferral logic for nested markers
|
|
748
|
+
// CRITICAL: Explicit role: "user" ensures consistency with formatNestedDelegationMarker()
|
|
749
|
+
// which always returns user role.
|
|
750
|
+
if (pendingToolCalls.size > 0) {
|
|
751
|
+
deferredMessages.push({
|
|
752
|
+
entry: {
|
|
753
|
+
pubkey: marker.recipientPubkey,
|
|
754
|
+
role: "user",
|
|
755
|
+
content: nestedMarkerMessage.content as string,
|
|
756
|
+
messageType: "text",
|
|
757
|
+
eventId: entry.eventId,
|
|
758
|
+
ral: entry.ral,
|
|
759
|
+
},
|
|
760
|
+
truncationContext,
|
|
761
|
+
enableMultimodal: false,
|
|
762
|
+
});
|
|
763
|
+
} else {
|
|
764
|
+
result.push(nestedMarkerMessage);
|
|
765
|
+
}
|
|
766
|
+
|
|
767
|
+
trace.getActiveSpan?.()?.addEvent("conversation.nested_delegation_marker_displayed", {
|
|
768
|
+
"delegation.conversation_id": marker.delegationConversationId.substring(0, 12),
|
|
769
|
+
"delegation.parent_conversation_id": marker.parentConversationId.substring(0, 12),
|
|
770
|
+
"current.conversation_id": ctx.conversationId.substring(0, 12),
|
|
771
|
+
"delegation.status": marker.status,
|
|
772
|
+
"delegation.recipient_pubkey": marker.recipientPubkey.substring(0, 12),
|
|
773
|
+
"delegation.deferred": pendingToolCalls.size > 0,
|
|
774
|
+
});
|
|
775
|
+
}
|
|
776
|
+
continue;
|
|
777
|
+
}
|
|
778
|
+
|
|
779
|
+
// NON-TOOL MESSAGE (user/assistant text, system, etc.)
|
|
780
|
+
// Only the most recent user message with images gets multimodal conversion
|
|
781
|
+
const enableMultimodal = i === lastUserImageEntryIndex;
|
|
782
|
+
// If tool-calls are pending, defer this message
|
|
783
|
+
if (pendingToolCalls.size > 0) {
|
|
784
|
+
deferredMessages.push({ entry, truncationContext, enableMultimodal });
|
|
785
|
+
} else {
|
|
786
|
+
result.push(await entryToMessage(entry, viewingAgentPubkey, truncationContext, agentPubkeys, imageTracker, agentsMdContext, enableMultimodal));
|
|
787
|
+
}
|
|
788
|
+
}
|
|
789
|
+
|
|
790
|
+
// ============================================================================
|
|
791
|
+
// ORPHANED TOOL-CALL RECONCILIATION
|
|
792
|
+
// ============================================================================
|
|
793
|
+
// If we reach the end of all entries and still have pending tool-calls,
|
|
794
|
+
// those are orphans - tool-calls whose results were never stored.
|
|
795
|
+
//
|
|
796
|
+
// We insert synthetic error results at the correct positions to maintain
|
|
797
|
+
// valid message structure for the AI SDK.
|
|
798
|
+
// ============================================================================
|
|
799
|
+
if (pendingToolCalls.size > 0) {
|
|
800
|
+
// Sort by resultIndex descending so splice operations don't shift indices
|
|
801
|
+
const orphans = Array.from(pendingToolCalls.entries())
|
|
802
|
+
.sort((a, b) => b[1].resultIndex - a[1].resultIndex);
|
|
803
|
+
|
|
804
|
+
for (const [toolCallId, info] of orphans) {
|
|
805
|
+
const syntheticResult: ModelMessage = {
|
|
806
|
+
role: "tool",
|
|
807
|
+
content: [{
|
|
808
|
+
type: "tool-result" as const,
|
|
809
|
+
toolCallId,
|
|
810
|
+
toolName: info.toolName,
|
|
811
|
+
output: {
|
|
812
|
+
type: "text" as const,
|
|
813
|
+
value: "[Error: Tool execution was interrupted - result unavailable]",
|
|
814
|
+
},
|
|
815
|
+
}] as ToolResultPart[],
|
|
816
|
+
};
|
|
817
|
+
// Insert synthetic result right after the tool-call
|
|
818
|
+
result.splice(info.resultIndex, 0, syntheticResult);
|
|
819
|
+
}
|
|
820
|
+
|
|
821
|
+
// Log for debugging/monitoring orphaned tool-calls in production
|
|
822
|
+
trace.getActiveSpan?.()?.addEvent("conversation.orphaned_tool_calls_reconciled", {
|
|
823
|
+
"orphan.count": pendingToolCalls.size,
|
|
824
|
+
"orphan.tool_call_ids": Array.from(pendingToolCalls.keys()).join(","),
|
|
825
|
+
});
|
|
826
|
+
}
|
|
827
|
+
|
|
828
|
+
// Flush any remaining deferred messages
|
|
829
|
+
for (const deferred of deferredMessages) {
|
|
830
|
+
result.push(await entryToMessage(deferred.entry, viewingAgentPubkey, deferred.truncationContext, agentPubkeys, imageTracker, agentsMdContext, deferred.enableMultimodal));
|
|
831
|
+
}
|
|
832
|
+
|
|
833
|
+
if (prunedDelegationCompletions > 0) {
|
|
834
|
+
trace.getActiveSpan?.()?.addEvent("conversation.delegation_completion_pruned", {
|
|
835
|
+
"delegation.pruned_count": prunedDelegationCompletions,
|
|
836
|
+
"delegation.kept_count": latestDelegationCompletionIndexByRal.size,
|
|
837
|
+
});
|
|
838
|
+
}
|
|
839
|
+
|
|
840
|
+
// Image placeholder telemetry: Aggregate replacement statistics
|
|
841
|
+
// The _imageReplacementStats field is set during entryToMessage for tool-result messages
|
|
842
|
+
let totalReplacedCount = 0;
|
|
843
|
+
let totalUniqueReplacedCount = 0;
|
|
844
|
+
for (const msg of result) {
|
|
845
|
+
const stats = (msg as unknown as { _imageReplacementStats?: { replacedCount: number; uniqueReplacedCount: number } })._imageReplacementStats;
|
|
846
|
+
if (stats) {
|
|
847
|
+
totalReplacedCount += stats.replacedCount;
|
|
848
|
+
totalUniqueReplacedCount += stats.uniqueReplacedCount;
|
|
849
|
+
// Clean up the internal field (don't send to API)
|
|
850
|
+
delete (msg as unknown as { _imageReplacementStats?: unknown })._imageReplacementStats;
|
|
851
|
+
}
|
|
852
|
+
}
|
|
853
|
+
|
|
854
|
+
// Only emit telemetry when actual replacements occurred
|
|
855
|
+
if (totalReplacedCount > 0) {
|
|
856
|
+
trace.getActiveSpan?.()?.addEvent("conversation.image_placeholder_applied", {
|
|
857
|
+
"image.replaced_count": totalReplacedCount, // Total occurrences replaced
|
|
858
|
+
"image.unique_replaced_count": totalUniqueReplacedCount, // Unique URLs that were replaced
|
|
859
|
+
"image.unique_urls_tracked": imageTracker.getSeenUrls().size, // All unique URLs seen (including first appearances)
|
|
860
|
+
// Estimated token savings: ~1,600 tokens per replacement
|
|
861
|
+
"image.estimated_tokens_saved": totalReplacedCount * 1600,
|
|
862
|
+
});
|
|
863
|
+
}
|
|
864
|
+
|
|
865
|
+
return result;
|
|
866
|
+
}
|