@indexnetwork/protocol 6.12.0-rc.403.1 → 6.13.22-rc.405.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/CHANGELOG.md +241 -0
- package/IMPLEMENTATION.md +20 -0
- package/STABILITY.md +4 -0
- package/dist/agent/agent.tools.d.ts +11 -3
- package/dist/agent/agent.tools.js +11 -301
- package/dist/capabilities/communities.facade.d.ts +13 -0
- package/dist/capabilities/communities.facade.js +12 -0
- package/dist/capabilities/communities.tools.port.d.ts +5 -0
- package/dist/capabilities/communities.tools.port.js +1 -0
- package/dist/capabilities/contacts.facade.d.ts +9 -0
- package/dist/capabilities/contacts.facade.js +8 -0
- package/dist/capabilities/contacts.tools.port.d.ts +9 -0
- package/dist/capabilities/contacts.tools.port.js +1 -0
- package/dist/capabilities/integrations.facade.d.ts +8 -0
- package/dist/capabilities/integrations.facade.js +7 -0
- package/dist/capabilities/integrations.tools.port.d.ts +9 -0
- package/dist/capabilities/integrations.tools.port.js +1 -0
- package/dist/capabilities/interaction-composition.facade.d.ts +10 -0
- package/dist/capabilities/interaction-composition.facade.js +9 -0
- package/dist/capabilities/negotiation.discovery.facade.d.ts +9 -0
- package/dist/capabilities/negotiation.discovery.facade.js +7 -0
- package/dist/capabilities/negotiation.facade.d.ts +23 -0
- package/dist/capabilities/negotiation.facade.js +17 -0
- package/dist/capabilities/negotiation.memory.facade.d.ts +6 -0
- package/dist/capabilities/negotiation.memory.facade.js +5 -0
- package/dist/capabilities/negotiation.questions.facade.d.ts +6 -0
- package/dist/capabilities/negotiation.questions.facade.js +5 -0
- package/dist/capabilities/negotiation.state.facade.d.ts +5 -0
- package/dist/capabilities/negotiation.state.facade.js +1 -0
- package/dist/capabilities/negotiation.summary.facade.d.ts +5 -0
- package/dist/capabilities/negotiation.summary.facade.js +5 -0
- package/dist/capabilities/negotiation.tools.port.d.ts +5 -0
- package/dist/capabilities/negotiation.tools.port.js +1 -0
- package/dist/capabilities/opportunities.facade.d.ts +50 -0
- package/dist/capabilities/opportunities.facade.js +50 -0
- package/dist/capabilities/opportunities.safety.facade.d.ts +2 -0
- package/dist/capabilities/opportunities.safety.facade.js +2 -0
- package/dist/capabilities/opportunities.tools.port.d.ts +5 -0
- package/dist/capabilities/opportunities.tools.port.js +1 -0
- package/dist/capabilities/participant-agents.debug.facade.d.ts +2 -0
- package/dist/capabilities/participant-agents.debug.facade.js +1 -0
- package/dist/capabilities/participant-agents.facade.d.ts +35 -0
- package/dist/capabilities/participant-agents.facade.js +35 -0
- package/dist/capabilities/participant-agents.tools.facade.d.ts +8 -0
- package/dist/capabilities/participant-agents.tools.facade.js +8 -0
- package/dist/capabilities/participant-agents.tools.port.d.ts +9 -0
- package/dist/capabilities/participant-agents.tools.port.js +1 -0
- package/dist/capabilities/participant-context.facade.d.ts +13 -0
- package/dist/capabilities/participant-context.facade.js +14 -0
- package/dist/capabilities/participant-context.tools.port.d.ts +9 -0
- package/dist/capabilities/participant-context.tools.port.js +1 -0
- package/dist/capabilities/questions.enqueue.facade.d.ts +5 -0
- package/dist/capabilities/questions.enqueue.facade.js +1 -0
- package/dist/capabilities/questions.facade.d.ts +8 -0
- package/dist/capabilities/questions.facade.js +7 -0
- package/dist/capabilities/questions.runtime.facade.d.ts +5 -0
- package/dist/capabilities/questions.runtime.facade.js +5 -0
- package/dist/capabilities/questions.tools.port.d.ts +5 -0
- package/dist/capabilities/questions.tools.port.js +1 -0
- package/dist/capabilities/signals.facade.d.ts +16 -0
- package/dist/capabilities/signals.facade.js +14 -0
- package/dist/capabilities/signals.indexing.facade.d.ts +12 -0
- package/dist/capabilities/signals.indexing.facade.js +11 -0
- package/dist/capabilities/signals.tools.port.d.ts +5 -0
- package/dist/capabilities/signals.tools.port.js +1 -0
- package/dist/chat/chat-streaming.types.d.ts +0 -1
- package/dist/chat/chat-streaming.types.js +1 -2
- package/dist/chat/chat.agent.d.ts +0 -1
- package/dist/chat/chat.agent.js +0 -1
- package/dist/chat/chat.graph.d.ts +0 -1
- package/dist/chat/chat.graph.js +0 -1
- package/dist/chat/chat.interrupt.classifier.d.ts +0 -1
- package/dist/chat/chat.interrupt.classifier.js +0 -1
- package/dist/chat/chat.persona.d.ts +0 -1
- package/dist/chat/chat.persona.js +0 -1
- package/dist/chat/chat.prompt.d.ts +0 -1
- package/dist/chat/chat.prompt.js +0 -1
- package/dist/chat/chat.prompt.modules.d.ts +0 -1
- package/dist/chat/chat.prompt.modules.js +0 -1
- package/dist/chat/chat.question-dedup.d.ts +0 -1
- package/dist/chat/chat.question-dedup.js +0 -1
- package/dist/chat/chat.state.d.ts +0 -1
- package/dist/chat/chat.state.js +0 -1
- package/dist/chat/chat.streamer.d.ts +0 -1
- package/dist/chat/chat.streamer.js +0 -1
- package/dist/chat/chat.suggester.d.ts +0 -1
- package/dist/chat/chat.suggester.js +0 -1
- package/dist/chat/chat.summarizer.d.ts +0 -1
- package/dist/chat/chat.summarizer.js +0 -1
- package/dist/chat/chat.title.generator.d.ts +0 -1
- package/dist/chat/chat.title.generator.js +0 -1
- package/dist/chat/chat.tools.d.ts +0 -1
- package/dist/chat/chat.tools.js +0 -1
- package/dist/chat/chat.utils.d.ts +0 -1
- package/dist/chat/chat.utils.js +0 -1
- package/dist/chat/negotiator.persona.d.ts +0 -1
- package/dist/chat/negotiator.persona.js +0 -1
- package/dist/chat/negotiator.prompt.d.ts +1 -2
- package/dist/chat/negotiator.prompt.js +1 -2
- package/dist/chat/negotiator.tools.d.ts +0 -1
- package/dist/chat/negotiator.tools.js +0 -1
- package/dist/chat/onboarding.persona.d.ts +0 -1
- package/dist/chat/onboarding.persona.js +0 -1
- package/dist/chat/onboarding.prompt.d.ts +0 -1
- package/dist/chat/onboarding.prompt.js +0 -1
- package/dist/chat/reporter.action.contracts.d.ts +36 -0
- package/dist/chat/reporter.action.contracts.js +1 -0
- package/dist/chat/reporter.action.tools.d.ts +2 -37
- package/dist/chat/reporter.action.tools.js +0 -1
- package/dist/chat/reporter.persona.d.ts +0 -1
- package/dist/chat/reporter.persona.js +0 -1
- package/dist/chat/reporter.prompt.d.ts +0 -1
- package/dist/chat/reporter.prompt.js +0 -1
- package/dist/chat/signal.persona.d.ts +0 -1
- package/dist/chat/signal.persona.js +0 -1
- package/dist/chat/signal.prompt.d.ts +0 -1
- package/dist/chat/signal.prompt.js +0 -1
- package/dist/communities/application/index.d.ts +45 -0
- package/dist/communities/application/index.js +50 -0
- package/dist/communities/application/indexer.graph.d.ts +446 -0
- package/dist/communities/application/indexer.graph.js +420 -0
- package/dist/communities/application/indexer.state.d.ts +172 -0
- package/dist/communities/application/indexer.state.js +116 -0
- package/dist/communities/application/membership.graph.d.ts +270 -0
- package/dist/communities/application/membership.graph.js +229 -0
- package/dist/communities/application/network.graph.d.ts +648 -0
- package/dist/communities/application/network.graph.js +303 -0
- package/dist/communities/application/network.recommender.d.ts +44 -0
- package/dist/communities/application/network.recommender.js +113 -0
- package/dist/communities/application/network.tools.d.ts +35 -0
- package/dist/communities/application/network.tools.js +578 -0
- package/dist/communities/domain/index.d.ts +29 -0
- package/dist/communities/domain/index.js +31 -0
- package/dist/communities/domain/membership.state.d.ts +87 -0
- package/dist/communities/domain/membership.state.js +53 -0
- package/dist/communities/domain/network.state.d.ts +192 -0
- package/dist/communities/domain/network.state.js +66 -0
- package/dist/communities/index.d.ts +14 -0
- package/dist/communities/index.js +14 -0
- package/dist/communities/ports/index.d.ts +25 -0
- package/dist/communities/ports/index.js +27 -0
- package/dist/communities/public/index.d.ts +30 -0
- package/dist/communities/public/index.js +33 -0
- package/dist/contact/contact.inviter.d.ts +7 -44
- package/dist/contact/contact.inviter.js +6 -59
- package/dist/contact/contact.tools.d.ts +7 -5
- package/dist/contact/contact.tools.js +7 -177
- package/dist/contacts/application/contact.inviter.d.ts +49 -0
- package/dist/contacts/application/contact.inviter.js +64 -0
- package/dist/contacts/application/contact.tools.d.ts +20 -0
- package/dist/contacts/application/contact.tools.js +191 -0
- package/dist/contacts/application/index.d.ts +22 -0
- package/dist/contacts/application/index.js +21 -0
- package/dist/contacts/domain/contact.types.d.ts +51 -0
- package/dist/contacts/domain/contact.types.js +10 -0
- package/dist/contacts/domain/index.d.ts +18 -0
- package/dist/contacts/domain/index.js +1 -0
- package/dist/contacts/index.d.ts +24 -0
- package/dist/contacts/index.js +24 -0
- package/dist/contacts/ports/contact.repository.port.d.ts +28 -0
- package/dist/contacts/ports/contact.repository.port.js +14 -0
- package/dist/contacts/ports/contact.tools.port.d.ts +26 -0
- package/dist/contacts/ports/contact.tools.port.js +21 -0
- package/dist/contacts/ports/index.d.ts +22 -0
- package/dist/contacts/ports/index.js +21 -0
- package/dist/contacts/public/index.d.ts +36 -0
- package/dist/contacts/public/index.js +33 -0
- package/dist/context/context.generator.d.ts +0 -1
- package/dist/context/context.generator.js +0 -1
- package/dist/enrichment/enrichment.enricher.d.ts +0 -1
- package/dist/enrichment/enrichment.enricher.js +0 -1
- package/dist/enrichment/enrichment.generator.d.ts +0 -1
- package/dist/enrichment/enrichment.generator.js +0 -1
- package/dist/enrichment/enrichment.graph.d.ts +6 -7
- package/dist/enrichment/enrichment.graph.js +0 -1
- package/dist/enrichment/enrichment.state.d.ts +2 -3
- package/dist/enrichment/enrichment.state.js +0 -1
- package/dist/enrichment/enrichment.tools.d.ts +3 -3
- package/dist/enrichment/enrichment.tools.js +0 -1
- package/dist/index.d.ts +106 -91
- package/dist/index.js +79 -71
- package/dist/integration/integration.tools.d.ts +6 -10
- package/dist/integration/integration.tools.js +6 -82
- package/dist/integrations/application/index.d.ts +14 -0
- package/dist/integrations/application/index.js +14 -0
- package/dist/integrations/application/integration.tools.d.ts +27 -0
- package/dist/integrations/application/integration.tools.js +98 -0
- package/dist/integrations/domain/index.d.ts +18 -0
- package/dist/integrations/domain/index.js +1 -0
- package/dist/integrations/domain/integration.types.d.ts +53 -0
- package/dist/integrations/domain/integration.types.js +11 -0
- package/dist/integrations/index.d.ts +27 -0
- package/dist/integrations/index.js +27 -0
- package/dist/integrations/ports/index.d.ts +28 -0
- package/dist/integrations/ports/index.js +26 -0
- package/dist/integrations/ports/integration.adapter.port.d.ts +62 -0
- package/dist/integrations/ports/integration.adapter.port.js +15 -0
- package/dist/integrations/ports/integration.importer.port.d.ts +31 -0
- package/dist/integrations/ports/integration.importer.port.js +15 -0
- package/dist/integrations/ports/integration.tools.port.d.ts +28 -0
- package/dist/integrations/ports/integration.tools.port.js +21 -0
- package/dist/integrations/public/index.d.ts +37 -0
- package/dist/integrations/public/index.js +34 -0
- package/dist/intent/intent.clarifier.d.ts +3 -52
- package/dist/intent/intent.clarifier.js +2 -202
- package/dist/intent/intent.graph.d.ts +3 -551
- package/dist/intent/intent.graph.js +3 -937
- package/dist/intent/intent.indexer.d.ts +2 -83
- package/dist/intent/intent.indexer.js +2 -171
- package/dist/intent/intent.inferrer.d.ts +2 -95
- package/dist/intent/intent.inferrer.js +2 -234
- package/dist/intent/intent.reconciler.d.ts +2 -106
- package/dist/intent/intent.reconciler.js +2 -187
- package/dist/intent/intent.specificity.d.ts +2 -3
- package/dist/intent/intent.specificity.js +2 -3
- package/dist/intent/intent.state.d.ts +8 -229
- package/dist/intent/intent.state.js +8 -189
- package/dist/intent/intent.tools.d.ts +6 -24
- package/dist/intent/intent.tools.js +4 -788
- package/dist/intent/intent.verifier.d.ts +2 -116
- package/dist/intent/intent.verifier.js +2 -263
- package/dist/maintenance/maintenance.graph.d.ts +0 -1
- package/dist/maintenance/maintenance.graph.js +0 -1
- package/dist/maintenance/maintenance.state.d.ts +0 -1
- package/dist/maintenance/maintenance.state.js +0 -1
- package/dist/mcp/elicitation.builder.d.ts +0 -1
- package/dist/mcp/elicitation.builder.js +0 -1
- package/dist/mcp/elicitation.dispatcher.d.ts +0 -1
- package/dist/mcp/elicitation.dispatcher.js +0 -1
- package/dist/mcp/mcp.server.d.ts +0 -1
- package/dist/mcp/mcp.server.js +1 -2
- package/dist/negotiation/application/index.d.ts +48 -0
- package/dist/negotiation/application/index.js +49 -0
- package/dist/negotiation/application/insight.generator.d.ts +31 -0
- package/dist/negotiation/application/insight.generator.js +105 -0
- package/dist/negotiation/application/negotiation.agent.d.ts +110 -0
- package/dist/negotiation/application/negotiation.agent.js +244 -0
- package/dist/negotiation/application/negotiation.detail-reader.d.ts +94 -0
- package/dist/negotiation/application/negotiation.detail-reader.js +91 -0
- package/dist/negotiation/application/negotiation.graph.d.ts +664 -0
- package/dist/negotiation/application/negotiation.graph.js +1393 -0
- package/dist/negotiation/application/negotiation.reflect.d.ts +194 -0
- package/dist/negotiation/application/negotiation.reflect.js +145 -0
- package/dist/negotiation/application/negotiation.screen.d.ts +78 -0
- package/dist/negotiation/application/negotiation.screen.js +108 -0
- package/dist/negotiation/application/negotiation.summarizer.d.ts +25 -0
- package/dist/negotiation/application/negotiation.summarizer.js +101 -0
- package/dist/negotiation/application/negotiation.tools.d.ts +18 -0
- package/dist/negotiation/application/negotiation.tools.js +782 -0
- package/dist/negotiation/domain/index.d.ts +48 -0
- package/dist/negotiation/domain/index.js +50 -0
- package/dist/negotiation/domain/negotiation.consultation-policy.d.ts +39 -0
- package/dist/negotiation/domain/negotiation.consultation-policy.js +68 -0
- package/dist/negotiation/domain/negotiation.deadlock.contracts.d.ts +10 -0
- package/dist/negotiation/domain/negotiation.deadlock.contracts.js +1 -0
- package/dist/negotiation/domain/negotiation.deadlock.d.ts +81 -0
- package/dist/negotiation/domain/negotiation.deadlock.js +103 -0
- package/dist/negotiation/domain/negotiation.intent-snapshot-provenance.d.ts +21 -0
- package/dist/negotiation/domain/negotiation.intent-snapshot-provenance.js +28 -0
- package/dist/negotiation/domain/negotiation.lifecycle-narration.d.ts +18 -0
- package/dist/negotiation/domain/negotiation.lifecycle-narration.js +87 -0
- package/dist/negotiation/domain/negotiation.memory.d.ts +65 -0
- package/dist/negotiation/domain/negotiation.memory.js +83 -0
- package/dist/negotiation/domain/negotiation.protocol.d.ts +536 -0
- package/dist/negotiation/domain/negotiation.protocol.js +202 -0
- package/dist/negotiation/domain/negotiation.question-safety.d.ts +25 -0
- package/dist/negotiation/domain/negotiation.question-safety.js +55 -0
- package/dist/negotiation/domain/negotiation.screen.contracts.d.ts +88 -0
- package/dist/negotiation/domain/negotiation.screen.contracts.js +58 -0
- package/dist/negotiation/domain/negotiation.state.d.ts +497 -0
- package/dist/negotiation/domain/negotiation.state.js +288 -0
- package/dist/negotiation/domain/negotiation.task-lock-policy.d.ts +10 -0
- package/dist/negotiation/domain/negotiation.task-lock-policy.js +22 -0
- package/dist/negotiation/index.d.ts +49 -0
- package/dist/negotiation/index.js +49 -0
- package/dist/negotiation/insight.generator.d.ts +3 -30
- package/dist/negotiation/insight.generator.js +3 -104
- package/dist/negotiation/negotiation.agent.d.ts +3 -109
- package/dist/negotiation/negotiation.agent.js +3 -248
- package/dist/negotiation/negotiation.attribution.d.ts +0 -1
- package/dist/negotiation/negotiation.attribution.js +0 -1
- package/dist/negotiation/negotiation.consultation-policy.d.ts +3 -38
- package/dist/negotiation/negotiation.consultation-policy.js +3 -67
- package/dist/negotiation/negotiation.deadlock.contracts.d.ts +5 -0
- package/dist/negotiation/negotiation.deadlock.contracts.js +5 -0
- package/dist/negotiation/negotiation.deadlock.d.ts +3 -94
- package/dist/negotiation/negotiation.deadlock.js +3 -101
- package/dist/negotiation/negotiation.detail-reader.d.ts +5 -0
- package/dist/negotiation/negotiation.detail-reader.js +5 -0
- package/dist/negotiation/negotiation.graph.d.ts +3 -663
- package/dist/negotiation/negotiation.graph.js +3 -1459
- package/dist/negotiation/negotiation.intent-snapshot-provenance.d.ts +5 -0
- package/dist/negotiation/negotiation.intent-snapshot-provenance.js +5 -0
- package/dist/negotiation/negotiation.lifecycle-narration.d.ts +5 -0
- package/dist/negotiation/negotiation.lifecycle-narration.js +5 -0
- package/dist/negotiation/negotiation.memory.d.ts +5 -56
- package/dist/negotiation/negotiation.memory.js +5 -69
- package/dist/negotiation/negotiation.protocol.d.ts +3 -535
- package/dist/negotiation/negotiation.protocol.js +3 -201
- package/dist/negotiation/negotiation.question-safety.d.ts +3 -24
- package/dist/negotiation/negotiation.question-safety.js +3 -54
- package/dist/negotiation/negotiation.reflect.d.ts +4 -197
- package/dist/negotiation/negotiation.reflect.js +4 -151
- package/dist/negotiation/negotiation.screen.d.ts +6 -159
- package/dist/negotiation/negotiation.screen.js +6 -152
- package/dist/negotiation/negotiation.state.d.ts +3 -495
- package/dist/negotiation/negotiation.state.js +3 -284
- package/dist/negotiation/negotiation.summarizer.d.ts +3 -24
- package/dist/negotiation/negotiation.summarizer.js +3 -100
- package/dist/negotiation/negotiation.task-lock-policy.d.ts +5 -0
- package/dist/negotiation/negotiation.task-lock-policy.js +5 -0
- package/dist/negotiation/negotiation.tools.d.ts +3 -15
- package/dist/negotiation/negotiation.tools.js +3 -946
- package/dist/negotiation/ports/index.d.ts +18 -0
- package/dist/negotiation/ports/index.js +18 -0
- package/dist/negotiation/ports/negotiation.tools.port.d.ts +18 -0
- package/dist/negotiation/ports/negotiation.tools.port.js +1 -0
- package/dist/negotiation/public/index.d.ts +58 -0
- package/dist/negotiation/public/index.js +60 -0
- package/dist/network/indexer/indexer.graph.d.ts +3 -413
- package/dist/network/indexer/indexer.graph.js +3 -375
- package/dist/network/indexer/indexer.state.d.ts +3 -149
- package/dist/network/indexer/indexer.state.js +3 -98
- package/dist/network/membership/membership.graph.d.ts +3 -248
- package/dist/network/membership/membership.graph.js +3 -202
- package/dist/network/membership/membership.state.d.ts +3 -75
- package/dist/network/membership/membership.state.js +3 -41
- package/dist/network/network.graph.d.ts +3 -629
- package/dist/network/network.graph.js +3 -267
- package/dist/network/network.recommender.d.ts +3 -40
- package/dist/network/network.recommender.js +3 -109
- package/dist/network/network.state.d.ts +3 -180
- package/dist/network/network.state.js +3 -54
- package/dist/network/network.tools.d.ts +5 -3
- package/dist/network/network.tools.js +5 -566
- package/dist/opportunity/application/delivery-card.cache.d.ts +25 -0
- package/dist/opportunity/application/delivery-card.cache.js +43 -0
- package/dist/opportunity/application/index.d.ts +63 -0
- package/dist/opportunity/application/index.js +68 -0
- package/dist/opportunity/application/negotiation-context.loader.d.ts +59 -0
- package/dist/opportunity/application/negotiation-context.loader.js +88 -0
- package/dist/opportunity/application/opportunity.card-presentation.d.ts +44 -0
- package/dist/opportunity/application/opportunity.card-presentation.js +104 -0
- package/dist/opportunity/application/opportunity.discover.d.ts +215 -0
- package/dist/opportunity/application/opportunity.discover.js +793 -0
- package/dist/opportunity/application/opportunity.discovery-continuation-finalization.d.ts +34 -0
- package/dist/opportunity/application/opportunity.discovery-continuation-finalization.js +74 -0
- package/dist/opportunity/application/opportunity.discovery-mcp-lifecycle-finalization.d.ts +44 -0
- package/dist/opportunity/application/opportunity.discovery-mcp-lifecycle-finalization.js +92 -0
- package/dist/opportunity/application/opportunity.discovery-negotiation-summary.d.ts +14 -0
- package/dist/opportunity/application/opportunity.discovery-negotiation-summary.js +46 -0
- package/dist/opportunity/application/opportunity.enricher.d.ts +71 -0
- package/dist/opportunity/application/opportunity.enricher.js +280 -0
- package/dist/opportunity/application/opportunity.evaluator.d.ts +275 -0
- package/dist/opportunity/application/opportunity.evaluator.js +459 -0
- package/dist/opportunity/application/opportunity.existing-negotiation.d.ts +77 -0
- package/dist/opportunity/application/opportunity.existing-negotiation.js +109 -0
- package/dist/opportunity/application/opportunity.feed-selection.d.ts +30 -0
- package/dist/opportunity/application/opportunity.feed-selection.js +96 -0
- package/dist/opportunity/application/opportunity.graph.d.ts +1427 -0
- package/dist/opportunity/application/opportunity.graph.js +3693 -0
- package/dist/opportunity/application/opportunity.introducer.d.ts +77 -0
- package/dist/opportunity/application/opportunity.introducer.js +99 -0
- package/dist/opportunity/application/opportunity.lifecycle.d.ts +63 -0
- package/dist/opportunity/application/opportunity.lifecycle.js +181 -0
- package/dist/opportunity/application/opportunity.newborn-stamping.d.ts +41 -0
- package/dist/opportunity/application/opportunity.newborn-stamping.js +81 -0
- package/dist/opportunity/application/opportunity.pending-questions.d.ts +33 -0
- package/dist/opportunity/application/opportunity.pending-questions.js +42 -0
- package/dist/opportunity/application/opportunity.persist.d.ts +58 -0
- package/dist/opportunity/application/opportunity.persist.js +145 -0
- package/dist/opportunity/application/opportunity.persistence-admission.d.ts +39 -0
- package/dist/opportunity/application/opportunity.persistence-admission.js +59 -0
- package/dist/opportunity/application/opportunity.presenter.d.ts +156 -0
- package/dist/opportunity/application/opportunity.presenter.js +736 -0
- package/dist/opportunity/application/opportunity.tools.d.ts +131 -0
- package/dist/opportunity/application/opportunity.tools.js +1834 -0
- package/dist/opportunity/application/opportunity.update-admission.d.ts +24 -0
- package/dist/opportunity/application/opportunity.update-admission.js +38 -0
- package/dist/opportunity/delivery-card.cache.d.ts +5 -26
- package/dist/opportunity/delivery-card.cache.js +5 -44
- package/dist/opportunity/discovery-question.helper.d.ts +3 -17
- package/dist/opportunity/discovery-question.helper.js +3 -14
- package/dist/opportunity/discriminator/discriminator.adjustments.d.ts +0 -1
- package/dist/opportunity/discriminator/discriminator.adjustments.js +0 -1
- package/dist/opportunity/discriminator/discriminator.assigner.d.ts +4 -5
- package/dist/opportunity/discriminator/discriminator.assigner.js +0 -1
- package/dist/opportunity/discriminator/discriminator.env.d.ts +0 -1
- package/dist/opportunity/discriminator/discriminator.env.js +0 -1
- package/dist/opportunity/discriminator/discriminator.miner.d.ts +0 -1
- package/dist/opportunity/discriminator/discriminator.miner.js +0 -1
- package/dist/opportunity/discriminator/discriminator.push.d.ts +0 -1
- package/dist/opportunity/discriminator/discriminator.push.js +0 -1
- package/dist/opportunity/discriminator/discriminator.question.d.ts +0 -1
- package/dist/opportunity/discriminator/discriminator.question.js +0 -1
- package/dist/opportunity/discriminator/discriminator.scorer.d.ts +0 -1
- package/dist/opportunity/discriminator/discriminator.scorer.js +0 -1
- package/dist/opportunity/discriminator/discriminator.shadow.d.ts +0 -1
- package/dist/opportunity/discriminator/discriminator.shadow.js +0 -1
- package/dist/opportunity/discriminator/discriminator.types.d.ts +0 -1
- package/dist/opportunity/discriminator/discriminator.types.js +0 -1
- package/dist/opportunity/domain/discovery-question.helper.d.ts +18 -0
- package/dist/opportunity/domain/discovery-question.helper.js +15 -0
- package/dist/opportunity/domain/index.d.ts +55 -0
- package/dist/opportunity/domain/index.js +52 -0
- package/dist/opportunity/domain/negotiation-summary.builder.d.ts +37 -0
- package/dist/opportunity/domain/negotiation-summary.builder.js +66 -0
- package/dist/opportunity/domain/opportunity.actor.d.ts +15 -0
- package/dist/opportunity/domain/opportunity.actor.js +39 -0
- package/dist/opportunity/domain/opportunity.claim-safety.d.ts +24 -0
- package/dist/opportunity/domain/opportunity.claim-safety.js +88 -0
- package/dist/opportunity/domain/opportunity.discovery-run-coalescing.d.ts +33 -0
- package/dist/opportunity/domain/opportunity.discovery-run-coalescing.js +33 -0
- package/dist/opportunity/domain/opportunity.discovery.contracts.d.ts +45 -0
- package/dist/opportunity/domain/opportunity.discovery.contracts.js +1 -0
- package/dist/opportunity/domain/opportunity.evidence.d.ts +21 -0
- package/dist/opportunity/domain/opportunity.evidence.js +78 -0
- package/dist/opportunity/domain/opportunity.labels.d.ts +8 -0
- package/dist/opportunity/domain/opportunity.labels.js +10 -0
- package/dist/opportunity/domain/opportunity.presentation-cache.d.ts +6 -0
- package/dist/opportunity/domain/opportunity.presentation-cache.js +14 -0
- package/dist/opportunity/domain/opportunity.presentation.d.ts +76 -0
- package/dist/opportunity/domain/opportunity.presentation.js +516 -0
- package/dist/opportunity/domain/opportunity.safe-presentation.d.ts +104 -0
- package/dist/opportunity/domain/opportunity.safe-presentation.js +102 -0
- package/dist/opportunity/domain/opportunity.state.d.ts +519 -0
- package/dist/opportunity/domain/opportunity.state.js +309 -0
- package/dist/opportunity/domain/opportunity.utils.d.ts +217 -0
- package/dist/opportunity/domain/opportunity.utils.js +395 -0
- package/dist/opportunity/feed/feed.categorizer.d.ts +0 -1
- package/dist/opportunity/feed/feed.categorizer.js +0 -1
- package/dist/opportunity/feed/feed.graph.d.ts +1 -2
- package/dist/opportunity/feed/feed.graph.js +0 -1
- package/dist/opportunity/feed/feed.health.d.ts +0 -1
- package/dist/opportunity/feed/feed.health.js +0 -1
- package/dist/opportunity/feed/feed.state.d.ts +1 -2
- package/dist/opportunity/feed/feed.state.js +0 -1
- package/dist/opportunity/index.d.ts +37 -0
- package/dist/opportunity/index.js +37 -0
- package/dist/opportunity/negotiation-context.loader.d.ts +3 -58
- package/dist/opportunity/negotiation-context.loader.js +3 -87
- package/dist/opportunity/negotiation-evidence/negotiation-evidence.env.d.ts +0 -1
- package/dist/opportunity/negotiation-evidence/negotiation-evidence.env.js +0 -1
- package/dist/opportunity/negotiation-evidence/negotiation-evidence.extractor.d.ts +0 -1
- package/dist/opportunity/negotiation-evidence/negotiation-evidence.extractor.js +0 -1
- package/dist/opportunity/negotiation-evidence/negotiation-evidence.miner.d.ts +0 -1
- package/dist/opportunity/negotiation-evidence/negotiation-evidence.miner.js +0 -1
- package/dist/opportunity/negotiation-evidence/negotiation-evidence.shadow.d.ts +0 -1
- package/dist/opportunity/negotiation-evidence/negotiation-evidence.shadow.js +0 -1
- package/dist/opportunity/negotiation-evidence/negotiation-evidence.types.d.ts +0 -1
- package/dist/opportunity/negotiation-evidence/negotiation-evidence.types.js +0 -1
- package/dist/opportunity/negotiation-evidence/negotiation-evidence.verifier.d.ts +0 -1
- package/dist/opportunity/negotiation-evidence/negotiation-evidence.verifier.js +0 -1
- package/dist/opportunity/negotiation-summary.builder.d.ts +3 -36
- package/dist/opportunity/negotiation-summary.builder.js +3 -65
- package/dist/opportunity/opportunity.actor.d.ts +3 -14
- package/dist/opportunity/opportunity.actor.js +3 -38
- package/dist/opportunity/opportunity.card-presentation.d.ts +5 -0
- package/dist/opportunity/opportunity.card-presentation.js +5 -0
- package/dist/opportunity/opportunity.claim-safety.d.ts +3 -23
- package/dist/opportunity/opportunity.claim-safety.js +3 -87
- package/dist/opportunity/opportunity.discover.d.ts +3 -218
- package/dist/opportunity/opportunity.discover.js +3 -917
- package/dist/opportunity/opportunity.discovery-continuation-finalization.d.ts +5 -0
- package/dist/opportunity/opportunity.discovery-continuation-finalization.js +5 -0
- package/dist/opportunity/opportunity.discovery-mcp-lifecycle-finalization.d.ts +5 -0
- package/dist/opportunity/opportunity.discovery-mcp-lifecycle-finalization.js +5 -0
- package/dist/opportunity/opportunity.discovery-negotiation-summary.d.ts +5 -0
- package/dist/opportunity/opportunity.discovery-negotiation-summary.js +5 -0
- package/dist/opportunity/opportunity.discovery-run-coalescing.d.ts +5 -0
- package/dist/opportunity/opportunity.discovery-run-coalescing.js +5 -0
- package/dist/opportunity/opportunity.discovery.contracts.d.ts +5 -0
- package/dist/opportunity/opportunity.discovery.contracts.js +5 -0
- package/dist/opportunity/opportunity.enricher.d.ts +3 -70
- package/dist/opportunity/opportunity.enricher.js +3 -279
- package/dist/opportunity/opportunity.evaluator.d.ts +5 -276
- package/dist/opportunity/opportunity.evaluator.js +5 -460
- package/dist/opportunity/opportunity.evidence.d.ts +5 -22
- package/dist/opportunity/opportunity.evidence.js +5 -79
- package/dist/opportunity/opportunity.existing-negotiation.d.ts +5 -0
- package/dist/opportunity/opportunity.existing-negotiation.js +5 -0
- package/dist/opportunity/opportunity.feed-selection.d.ts +5 -0
- package/dist/opportunity/opportunity.feed-selection.js +5 -0
- package/dist/opportunity/opportunity.graph.d.ts +3 -1489
- package/dist/opportunity/opportunity.graph.js +3 -4071
- package/dist/opportunity/opportunity.introducer.d.ts +3 -76
- package/dist/opportunity/opportunity.introducer.js +3 -98
- package/dist/opportunity/opportunity.labels.d.ts +5 -9
- package/dist/opportunity/opportunity.labels.js +5 -11
- package/dist/opportunity/opportunity.lifecycle.d.ts +5 -0
- package/dist/opportunity/opportunity.lifecycle.js +5 -0
- package/dist/opportunity/opportunity.newborn-stamping.d.ts +5 -0
- package/dist/opportunity/opportunity.newborn-stamping.js +5 -0
- package/dist/opportunity/opportunity.pending-questions.d.ts +3 -32
- package/dist/opportunity/opportunity.pending-questions.js +3 -41
- package/dist/opportunity/opportunity.persist.d.ts +3 -57
- package/dist/opportunity/opportunity.persist.js +3 -144
- package/dist/opportunity/opportunity.persistence-admission.d.ts +5 -0
- package/dist/opportunity/opportunity.persistence-admission.js +5 -0
- package/dist/opportunity/opportunity.presentation-cache.d.ts +5 -7
- package/dist/opportunity/opportunity.presentation-cache.js +5 -15
- package/dist/opportunity/opportunity.presentation.d.ts +3 -75
- package/dist/opportunity/opportunity.presentation.js +3 -515
- package/dist/opportunity/opportunity.presenter.d.ts +10 -155
- package/dist/opportunity/opportunity.presenter.js +10 -735
- package/dist/opportunity/opportunity.safe-presentation.d.ts +3 -103
- package/dist/opportunity/opportunity.safe-presentation.js +3 -101
- package/dist/opportunity/opportunity.state.d.ts +3 -518
- package/dist/opportunity/opportunity.state.js +3 -308
- package/dist/opportunity/opportunity.tools.d.ts +3 -157
- package/dist/opportunity/opportunity.tools.js +3 -2238
- package/dist/opportunity/opportunity.update-admission.d.ts +5 -0
- package/dist/opportunity/opportunity.update-admission.js +5 -0
- package/dist/opportunity/opportunity.utils.d.ts +3 -216
- package/dist/opportunity/opportunity.utils.js +3 -394
- package/dist/opportunity/outcome/outcome.env.d.ts +0 -1
- package/dist/opportunity/outcome/outcome.env.js +0 -1
- package/dist/opportunity/outcome/outcome.hypotheses.d.ts +0 -1
- package/dist/opportunity/outcome/outcome.hypotheses.js +0 -1
- package/dist/opportunity/outcome/outcome.shadow.d.ts +0 -1
- package/dist/opportunity/outcome/outcome.shadow.js +0 -1
- package/dist/opportunity/outcome/outcome.types.d.ts +0 -1
- package/dist/opportunity/outcome/outcome.types.js +0 -1
- package/dist/opportunity/ports/index.d.ts +5 -0
- package/dist/opportunity/ports/index.js +1 -0
- package/dist/opportunity/ports/opportunity.tools.port.d.ts +5 -0
- package/dist/opportunity/ports/opportunity.tools.port.js +1 -0
- package/dist/opportunity/public/index.d.ts +58 -0
- package/dist/opportunity/public/index.js +51 -0
- package/dist/participant-agents/application/agent.tools.d.ts +16 -0
- package/dist/participant-agents/application/agent.tools.js +313 -0
- package/dist/participant-agents/application/index.d.ts +21 -0
- package/dist/participant-agents/application/index.js +22 -0
- package/dist/participant-agents/domain/agent.types.d.ts +76 -0
- package/dist/participant-agents/domain/agent.types.js +22 -0
- package/dist/participant-agents/domain/index.d.ts +20 -0
- package/dist/participant-agents/domain/index.js +1 -0
- package/dist/participant-agents/index.d.ts +24 -0
- package/dist/participant-agents/index.js +24 -0
- package/dist/participant-agents/ports/agent.dispatcher.port.d.ts +25 -0
- package/dist/participant-agents/ports/agent.dispatcher.port.js +1 -0
- package/dist/participant-agents/ports/agent.repository.port.d.ts +118 -0
- package/dist/participant-agents/ports/agent.repository.port.js +10 -0
- package/dist/participant-agents/ports/agent.tools.port.d.ts +24 -0
- package/dist/participant-agents/ports/agent.tools.port.js +20 -0
- package/dist/participant-agents/ports/index.d.ts +29 -0
- package/dist/participant-agents/ports/index.js +27 -0
- package/dist/participant-agents/public/index.d.ts +39 -0
- package/dist/participant-agents/public/index.js +36 -0
- package/dist/participant-context/application/index.d.ts +73 -0
- package/dist/participant-context/application/index.js +83 -0
- package/dist/participant-context/domain/index.d.ts +67 -0
- package/dist/participant-context/domain/index.js +68 -0
- package/dist/participant-context/index.d.ts +16 -0
- package/dist/participant-context/index.js +16 -0
- package/dist/participant-context/ports/index.d.ts +37 -0
- package/dist/participant-context/ports/index.js +34 -0
- package/dist/participant-context/public/index.d.ts +48 -0
- package/dist/participant-context/public/index.js +55 -0
- package/dist/platform/index.d.ts +19 -0
- package/dist/platform/index.js +23 -0
- package/dist/premise/premise.analyzer.d.ts +0 -1
- package/dist/premise/premise.analyzer.js +0 -2
- package/dist/premise/premise.decomposer.d.ts +0 -1
- package/dist/premise/premise.decomposer.js +0 -2
- package/dist/premise/premise.graph.d.ts +1 -2
- package/dist/premise/premise.graph.js +0 -2
- package/dist/premise/premise.indexer.d.ts +0 -1
- package/dist/premise/premise.indexer.js +0 -2
- package/dist/premise/premise.state.d.ts +1 -2
- package/dist/premise/premise.state.js +0 -1
- package/dist/premise/premise.tools.d.ts +3 -3
- package/dist/premise/premise.tools.js +0 -2
- package/dist/public/index.d.ts +17 -0
- package/dist/public/index.js +17 -0
- package/dist/questioner/questioner.agent.d.ts +4 -27
- package/dist/questioner/questioner.agent.js +3 -133
- package/dist/questioner/questioner.ask.tool.d.ts +3 -12
- package/dist/questioner/questioner.ask.tool.js +3 -299
- package/dist/questioner/questioner.discovery.prompt.d.ts +3 -7
- package/dist/questioner/questioner.discovery.prompt.js +3 -180
- package/dist/questioner/questioner.env.d.ts +3 -57
- package/dist/questioner/questioner.env.js +3 -93
- package/dist/questioner/questioner.presets.d.ts +4 -19
- package/dist/questioner/questioner.presets.js +3 -465
- package/dist/questioner/questioner.qud.d.ts +3 -6
- package/dist/questioner/questioner.qud.js +3 -10
- package/dist/questioner/questioner.tools.d.ts +3 -15
- package/dist/questioner/questioner.tools.js +3 -186
- package/dist/questioner/questioner.types.d.ts +4 -218
- package/dist/questioner/questioner.types.js +5 -62
- package/dist/questions/application/index.d.ts +39 -0
- package/dist/questions/application/index.js +41 -0
- package/dist/questions/application/question.agent.d.ts +28 -0
- package/dist/questions/application/question.agent.js +139 -0
- package/dist/questions/application/question.ask.tool.d.ts +12 -0
- package/dist/questions/application/question.ask.tool.js +300 -0
- package/dist/questions/application/question.discovery.prompt.d.ts +8 -0
- package/dist/questions/application/question.discovery.prompt.js +181 -0
- package/dist/questions/application/question.env.d.ts +61 -0
- package/dist/questions/application/question.env.js +97 -0
- package/dist/questions/application/question.input.d.ts +237 -0
- package/dist/questions/application/question.input.js +61 -0
- package/dist/questions/application/question.presets.d.ts +24 -0
- package/dist/questions/application/question.presets.js +445 -0
- package/dist/questions/application/question.qud.d.ts +12 -0
- package/dist/questions/application/question.qud.js +16 -0
- package/dist/questions/application/question.tools.d.ts +15 -0
- package/dist/questions/application/question.tools.js +190 -0
- package/dist/questions/domain/index.d.ts +25 -0
- package/dist/questions/domain/index.js +25 -0
- package/dist/questions/domain/question.schema.d.ts +1537 -0
- package/dist/questions/domain/question.schema.js +422 -0
- package/dist/questions/index.d.ts +22 -0
- package/dist/questions/index.js +22 -0
- package/dist/questions/ports/index.d.ts +29 -0
- package/dist/questions/ports/index.js +27 -0
- package/dist/questions/ports/question.generator.port.d.ts +34 -0
- package/dist/questions/ports/question.generator.port.js +1 -0
- package/dist/questions/ports/question.persistence.port.d.ts +87 -0
- package/dist/questions/ports/question.persistence.port.js +1 -0
- package/dist/questions/ports/question.tools.port.d.ts +15 -0
- package/dist/questions/ports/question.tools.port.js +1 -0
- package/dist/questions/public/index.d.ts +47 -0
- package/dist/questions/public/index.js +49 -0
- package/dist/runtime/background/index.d.ts +14 -0
- package/dist/runtime/background/index.js +14 -0
- package/dist/runtime/foreground/composition/tool.factory.d.ts +16 -0
- package/dist/runtime/foreground/composition/tool.factory.js +224 -0
- package/dist/runtime/foreground/composition/tool.registry.d.ts +11 -0
- package/dist/runtime/foreground/composition/tool.registry.js +106 -0
- package/dist/runtime/foreground/index.d.ts +13 -0
- package/dist/runtime/foreground/index.js +13 -0
- package/dist/runtime/foreground/signals/intent.tools.d.ts +13 -0
- package/dist/runtime/foreground/signals/intent.tools.js +13 -0
- package/dist/shared/agent/model-signal.d.ts +0 -1
- package/dist/shared/agent/model-signal.js +0 -1
- package/dist/shared/agent/model.config.d.ts +0 -1
- package/dist/shared/agent/model.config.js +0 -1
- package/dist/shared/agent/tool.factory.d.ts +4 -15
- package/dist/shared/agent/tool.factory.js +4 -226
- package/dist/shared/agent/tool.helpers.d.ts +51 -12
- package/dist/shared/agent/tool.helpers.js +0 -1
- package/dist/shared/agent/tool.registry.d.ts +9 -9
- package/dist/shared/agent/tool.registry.js +9 -104
- package/dist/shared/agent/tool.runtime.d.ts +0 -1
- package/dist/shared/agent/tool.runtime.js +0 -1
- package/dist/shared/agent/tool.scope.d.ts +0 -1
- package/dist/shared/agent/tool.scope.js +0 -1
- package/dist/shared/agent/utility.tools.d.ts +5 -3
- package/dist/shared/agent/utility.tools.js +0 -1
- package/dist/shared/assignment/network-assignment.policy.d.ts +0 -1
- package/dist/shared/assignment/network-assignment.policy.js +0 -1
- package/dist/shared/hyde/hyde.documents.d.ts +0 -1
- package/dist/shared/hyde/hyde.documents.js +0 -1
- package/dist/shared/hyde/hyde.env.d.ts +0 -1
- package/dist/shared/hyde/hyde.env.js +0 -1
- package/dist/shared/hyde/hyde.frame.d.ts +4 -5
- package/dist/shared/hyde/hyde.frame.js +0 -1
- package/dist/shared/hyde/hyde.generator.d.ts +0 -1
- package/dist/shared/hyde/hyde.generator.js +0 -1
- package/dist/shared/hyde/hyde.graph.d.ts +0 -1
- package/dist/shared/hyde/hyde.graph.js +0 -1
- package/dist/shared/hyde/hyde.state.d.ts +0 -1
- package/dist/shared/hyde/hyde.state.js +0 -1
- package/dist/shared/hyde/hyde.strategies.d.ts +0 -1
- package/dist/shared/hyde/hyde.strategies.js +0 -1
- package/dist/shared/hyde/hyde.validator.d.ts +0 -1
- package/dist/shared/hyde/hyde.validator.js +0 -1
- package/dist/shared/hyde/lens.inferrer.d.ts +6 -7
- package/dist/shared/hyde/lens.inferrer.js +0 -1
- package/dist/shared/interfaces/agent-dispatcher.interface.d.ts +4 -1
- package/dist/shared/interfaces/agent-dispatcher.interface.js +4 -1
- package/dist/shared/interfaces/agent.interface.d.ts +13 -173
- package/dist/shared/interfaces/agent.interface.js +12 -12
- package/dist/shared/interfaces/auth.interface.d.ts +0 -1
- package/dist/shared/interfaces/auth.interface.js +0 -1
- package/dist/shared/interfaces/cache.interface.d.ts +0 -1
- package/dist/shared/interfaces/cache.interface.js +0 -1
- package/dist/shared/interfaces/chat-message-writer.interface.d.ts +0 -1
- package/dist/shared/interfaces/chat-message-writer.interface.js +0 -1
- package/dist/shared/interfaces/chat-session.interface.d.ts +0 -1
- package/dist/shared/interfaces/chat-session.interface.js +0 -1
- package/dist/shared/interfaces/chat-summary.interface.d.ts +0 -1
- package/dist/shared/interfaces/chat-summary.interface.js +0 -1
- package/dist/shared/interfaces/connect-link.interface.d.ts +0 -1
- package/dist/shared/interfaces/connect-link.interface.js +0 -1
- package/dist/shared/interfaces/contact.interface.d.ts +7 -55
- package/dist/shared/interfaces/contact.interface.js +7 -1
- package/dist/shared/interfaces/database.interface.d.ts +0 -1
- package/dist/shared/interfaces/database.interface.js +0 -1
- package/dist/shared/interfaces/delivery-ledger.interface.d.ts +0 -1
- package/dist/shared/interfaces/delivery-ledger.interface.js +0 -1
- package/dist/shared/interfaces/discovery-run.interface.d.ts +2 -3
- package/dist/shared/interfaces/discovery-run.interface.js +0 -1
- package/dist/shared/interfaces/embedder.interface.d.ts +0 -1
- package/dist/shared/interfaces/embedder.interface.js +0 -1
- package/dist/shared/interfaces/enrichment-run.interface.d.ts +2 -3
- package/dist/shared/interfaces/enrichment-run.interface.js +0 -1
- package/dist/shared/interfaces/enrichment.interface.d.ts +0 -1
- package/dist/shared/interfaces/enrichment.interface.js +0 -1
- package/dist/shared/interfaces/integration.interface.d.ts +6 -88
- package/dist/shared/interfaces/integration.interface.js +7 -1
- package/dist/shared/interfaces/negotiation-events.interface.d.ts +0 -1
- package/dist/shared/interfaces/negotiation-events.interface.js +0 -1
- package/dist/shared/interfaces/negotiation-summary.interface.d.ts +0 -1
- package/dist/shared/interfaces/negotiation-summary.interface.js +0 -1
- package/dist/shared/interfaces/negotiator-memory.interface.d.ts +0 -1
- package/dist/shared/interfaces/negotiator-memory.interface.js +0 -1
- package/dist/shared/interfaces/question-generator.interface.d.ts +3 -28
- package/dist/shared/interfaces/question-generator.interface.js +0 -1
- package/dist/shared/interfaces/questioner.interface.d.ts +3 -77
- package/dist/shared/interfaces/questioner.interface.js +0 -1
- package/dist/shared/interfaces/queue.interface.d.ts +0 -1
- package/dist/shared/interfaces/queue.interface.js +0 -1
- package/dist/shared/interfaces/scraper.interface.d.ts +0 -1
- package/dist/shared/interfaces/scraper.interface.js +0 -1
- package/dist/shared/interfaces/tool-request-context.interface.d.ts +12 -0
- package/dist/shared/interfaces/tool-request-context.interface.js +1 -0
- package/dist/shared/network/metadata.renderer.d.ts +0 -1
- package/dist/shared/network/metadata.renderer.js +0 -1
- package/dist/shared/observability/debug-meta.sanitizer.d.ts +0 -1
- package/dist/shared/observability/debug-meta.sanitizer.js +0 -1
- package/dist/shared/observability/log.d.ts +0 -1
- package/dist/shared/observability/log.js +0 -1
- package/dist/shared/observability/performance.d.ts +0 -1
- package/dist/shared/observability/performance.js +0 -1
- package/dist/shared/observability/protocol.logger.d.ts +0 -1
- package/dist/shared/observability/protocol.logger.js +0 -1
- package/dist/shared/observability/request-context.d.ts +0 -1
- package/dist/shared/observability/request-context.js +0 -1
- package/dist/shared/observability/trace.d.ts +0 -1
- package/dist/shared/observability/trace.js +0 -1
- package/dist/shared/schemas/chat-context.schema.d.ts +0 -1
- package/dist/shared/schemas/chat-context.schema.js +0 -1
- package/dist/shared/schemas/discovery-question.schema.d.ts +17 -18
- package/dist/shared/schemas/discovery-question.schema.js +0 -1
- package/dist/shared/schemas/identity.schema.d.ts +0 -1
- package/dist/shared/schemas/identity.schema.js +0 -1
- package/dist/shared/schemas/mcp-auth.schema.d.ts +0 -1
- package/dist/shared/schemas/mcp-auth.schema.js +0 -1
- package/dist/shared/schemas/negotiation-digest.schema.d.ts +5 -6
- package/dist/shared/schemas/negotiation-digest.schema.js +0 -1
- package/dist/shared/schemas/negotiation-state.schema.d.ts +4 -5
- package/dist/shared/schemas/negotiation-state.schema.js +0 -1
- package/dist/shared/schemas/network-assignment.schema.d.ts +0 -1
- package/dist/shared/schemas/network-assignment.schema.js +0 -1
- package/dist/shared/schemas/pending-question.schema.d.ts +0 -1
- package/dist/shared/schemas/pending-question.schema.js +0 -1
- package/dist/shared/schemas/question.schema.d.ts +3 -1505
- package/dist/shared/schemas/question.schema.js +3 -410
- package/dist/shared/ui/lucide.icon-catalog.d.ts +0 -1
- package/dist/shared/ui/lucide.icon-catalog.js +0 -1
- package/dist/shared/utils/social-label.d.ts +0 -1
- package/dist/shared/utils/social-label.js +0 -1
- package/dist/shared/utils/telegram-handle.d.ts +0 -1
- package/dist/shared/utils/telegram-handle.js +0 -1
- package/dist/signals/application/index.d.ts +19 -0
- package/dist/signals/application/index.js +22 -0
- package/dist/signals/application/intent.clarifier.d.ts +51 -0
- package/dist/signals/application/intent.clarifier.js +201 -0
- package/dist/signals/application/intent.graph.d.ts +552 -0
- package/dist/signals/application/intent.graph.js +938 -0
- package/dist/signals/application/intent.indexer.d.ts +82 -0
- package/dist/signals/application/intent.indexer.js +170 -0
- package/dist/signals/application/intent.inferrer.d.ts +94 -0
- package/dist/signals/application/intent.inferrer.js +233 -0
- package/dist/signals/application/intent.reconciler.d.ts +105 -0
- package/dist/signals/application/intent.reconciler.js +186 -0
- package/dist/signals/application/intent.tools.d.ts +24 -0
- package/dist/signals/application/intent.tools.js +789 -0
- package/dist/signals/application/intent.verifier.d.ts +115 -0
- package/dist/signals/application/intent.verifier.js +262 -0
- package/dist/signals/domain/index.d.ts +9 -0
- package/dist/signals/domain/index.js +11 -0
- package/dist/signals/domain/intent.state.d.ts +230 -0
- package/dist/signals/domain/intent.state.js +190 -0
- package/dist/signals/domain/signal.specificity.d.ts +2 -0
- package/dist/signals/domain/signal.specificity.js +2 -0
- package/dist/signals/index.d.ts +13 -0
- package/dist/signals/index.js +13 -0
- package/dist/signals/ports/index.d.ts +15 -0
- package/dist/signals/ports/index.js +13 -0
- package/dist/signals/public/index.d.ts +15 -0
- package/dist/signals/public/index.js +16 -0
- package/package.json +14 -3
- package/dist/agent/agent.tools.d.ts.map +0 -1
- package/dist/agent/agent.tools.js.map +0 -1
- package/dist/agent/tests/fakes.d.ts +0 -25
- package/dist/agent/tests/fakes.d.ts.map +0 -1
- package/dist/agent/tests/fakes.js +0 -178
- package/dist/agent/tests/fakes.js.map +0 -1
- package/dist/chat/chat-streaming.types.d.ts.map +0 -1
- package/dist/chat/chat-streaming.types.js.map +0 -1
- package/dist/chat/chat.agent.d.ts.map +0 -1
- package/dist/chat/chat.agent.js.map +0 -1
- package/dist/chat/chat.graph.d.ts.map +0 -1
- package/dist/chat/chat.graph.js.map +0 -1
- package/dist/chat/chat.interrupt.classifier.d.ts.map +0 -1
- package/dist/chat/chat.interrupt.classifier.js.map +0 -1
- package/dist/chat/chat.persona.d.ts.map +0 -1
- package/dist/chat/chat.persona.js.map +0 -1
- package/dist/chat/chat.prompt.d.ts.map +0 -1
- package/dist/chat/chat.prompt.js.map +0 -1
- package/dist/chat/chat.prompt.modules.d.ts.map +0 -1
- package/dist/chat/chat.prompt.modules.js.map +0 -1
- package/dist/chat/chat.question-dedup.d.ts.map +0 -1
- package/dist/chat/chat.question-dedup.js.map +0 -1
- package/dist/chat/chat.state.d.ts.map +0 -1
- package/dist/chat/chat.state.js.map +0 -1
- package/dist/chat/chat.streamer.d.ts.map +0 -1
- package/dist/chat/chat.streamer.js.map +0 -1
- package/dist/chat/chat.suggester.d.ts.map +0 -1
- package/dist/chat/chat.suggester.js.map +0 -1
- package/dist/chat/chat.summarizer.d.ts.map +0 -1
- package/dist/chat/chat.summarizer.js.map +0 -1
- package/dist/chat/chat.title.generator.d.ts.map +0 -1
- package/dist/chat/chat.title.generator.js.map +0 -1
- package/dist/chat/chat.tools.d.ts.map +0 -1
- package/dist/chat/chat.tools.js.map +0 -1
- package/dist/chat/chat.utils.d.ts.map +0 -1
- package/dist/chat/chat.utils.js.map +0 -1
- package/dist/chat/negotiator.persona.d.ts.map +0 -1
- package/dist/chat/negotiator.persona.js.map +0 -1
- package/dist/chat/negotiator.prompt.d.ts.map +0 -1
- package/dist/chat/negotiator.prompt.js.map +0 -1
- package/dist/chat/negotiator.tools.d.ts.map +0 -1
- package/dist/chat/negotiator.tools.js.map +0 -1
- package/dist/chat/onboarding.persona.d.ts.map +0 -1
- package/dist/chat/onboarding.persona.js.map +0 -1
- package/dist/chat/onboarding.prompt.d.ts.map +0 -1
- package/dist/chat/onboarding.prompt.js.map +0 -1
- package/dist/chat/reporter.action.tools.d.ts.map +0 -1
- package/dist/chat/reporter.action.tools.js.map +0 -1
- package/dist/chat/reporter.persona.d.ts.map +0 -1
- package/dist/chat/reporter.persona.js.map +0 -1
- package/dist/chat/reporter.prompt.d.ts.map +0 -1
- package/dist/chat/reporter.prompt.js.map +0 -1
- package/dist/chat/signal.persona.d.ts.map +0 -1
- package/dist/chat/signal.persona.js.map +0 -1
- package/dist/chat/signal.prompt.d.ts.map +0 -1
- package/dist/chat/signal.prompt.js.map +0 -1
- package/dist/chat/tests/chat.graph.mocks.d.ts +0 -98
- package/dist/chat/tests/chat.graph.mocks.d.ts.map +0 -1
- package/dist/chat/tests/chat.graph.mocks.js +0 -225
- package/dist/chat/tests/chat.graph.mocks.js.map +0 -1
- package/dist/contact/contact.inviter.d.ts.map +0 -1
- package/dist/contact/contact.inviter.js.map +0 -1
- package/dist/contact/contact.tools.d.ts.map +0 -1
- package/dist/contact/contact.tools.js.map +0 -1
- package/dist/context/context.generator.d.ts.map +0 -1
- package/dist/context/context.generator.js.map +0 -1
- package/dist/enrichment/enrichment.enricher.d.ts.map +0 -1
- package/dist/enrichment/enrichment.enricher.js.map +0 -1
- package/dist/enrichment/enrichment.generator.d.ts.map +0 -1
- package/dist/enrichment/enrichment.generator.js.map +0 -1
- package/dist/enrichment/enrichment.graph.d.ts.map +0 -1
- package/dist/enrichment/enrichment.graph.js.map +0 -1
- package/dist/enrichment/enrichment.state.d.ts.map +0 -1
- package/dist/enrichment/enrichment.state.js.map +0 -1
- package/dist/enrichment/enrichment.tools.d.ts.map +0 -1
- package/dist/enrichment/enrichment.tools.js.map +0 -1
- package/dist/index.d.ts.map +0 -1
- package/dist/index.js.map +0 -1
- package/dist/integration/integration.tools.d.ts.map +0 -1
- package/dist/integration/integration.tools.js.map +0 -1
- package/dist/intent/intent.clarifier.d.ts.map +0 -1
- package/dist/intent/intent.clarifier.js.map +0 -1
- package/dist/intent/intent.graph.d.ts.map +0 -1
- package/dist/intent/intent.graph.js.map +0 -1
- package/dist/intent/intent.indexer.d.ts.map +0 -1
- package/dist/intent/intent.indexer.js.map +0 -1
- package/dist/intent/intent.inferrer.d.ts.map +0 -1
- package/dist/intent/intent.inferrer.js.map +0 -1
- package/dist/intent/intent.reconciler.d.ts.map +0 -1
- package/dist/intent/intent.reconciler.js.map +0 -1
- package/dist/intent/intent.specificity.d.ts.map +0 -1
- package/dist/intent/intent.specificity.js.map +0 -1
- package/dist/intent/intent.state.d.ts.map +0 -1
- package/dist/intent/intent.state.js.map +0 -1
- package/dist/intent/intent.tools.d.ts.map +0 -1
- package/dist/intent/intent.tools.js.map +0 -1
- package/dist/intent/intent.verifier.d.ts.map +0 -1
- package/dist/intent/intent.verifier.js.map +0 -1
- package/dist/maintenance/maintenance.graph.d.ts.map +0 -1
- package/dist/maintenance/maintenance.graph.js.map +0 -1
- package/dist/maintenance/maintenance.state.d.ts.map +0 -1
- package/dist/maintenance/maintenance.state.js.map +0 -1
- package/dist/mcp/elicitation.builder.d.ts.map +0 -1
- package/dist/mcp/elicitation.builder.js.map +0 -1
- package/dist/mcp/elicitation.dispatcher.d.ts.map +0 -1
- package/dist/mcp/elicitation.dispatcher.js.map +0 -1
- package/dist/mcp/mcp.server.d.ts.map +0 -1
- package/dist/mcp/mcp.server.js.map +0 -1
- package/dist/negotiation/insight.generator.d.ts.map +0 -1
- package/dist/negotiation/insight.generator.js.map +0 -1
- package/dist/negotiation/negotiation.agent.d.ts.map +0 -1
- package/dist/negotiation/negotiation.agent.js.map +0 -1
- package/dist/negotiation/negotiation.attribution.d.ts.map +0 -1
- package/dist/negotiation/negotiation.attribution.js.map +0 -1
- package/dist/negotiation/negotiation.consultation-policy.d.ts.map +0 -1
- package/dist/negotiation/negotiation.consultation-policy.js.map +0 -1
- package/dist/negotiation/negotiation.deadlock.d.ts.map +0 -1
- package/dist/negotiation/negotiation.deadlock.js.map +0 -1
- package/dist/negotiation/negotiation.graph.d.ts.map +0 -1
- package/dist/negotiation/negotiation.graph.js.map +0 -1
- package/dist/negotiation/negotiation.memory.d.ts.map +0 -1
- package/dist/negotiation/negotiation.memory.js.map +0 -1
- package/dist/negotiation/negotiation.protocol.d.ts.map +0 -1
- package/dist/negotiation/negotiation.protocol.js.map +0 -1
- package/dist/negotiation/negotiation.question-safety.d.ts.map +0 -1
- package/dist/negotiation/negotiation.question-safety.js.map +0 -1
- package/dist/negotiation/negotiation.reflect.d.ts.map +0 -1
- package/dist/negotiation/negotiation.reflect.js.map +0 -1
- package/dist/negotiation/negotiation.screen.d.ts.map +0 -1
- package/dist/negotiation/negotiation.screen.js.map +0 -1
- package/dist/negotiation/negotiation.state.d.ts.map +0 -1
- package/dist/negotiation/negotiation.state.js.map +0 -1
- package/dist/negotiation/negotiation.summarizer.d.ts.map +0 -1
- package/dist/negotiation/negotiation.summarizer.js.map +0 -1
- package/dist/negotiation/negotiation.tools.d.ts.map +0 -1
- package/dist/negotiation/negotiation.tools.js.map +0 -1
- package/dist/network/indexer/indexer.graph.d.ts.map +0 -1
- package/dist/network/indexer/indexer.graph.js.map +0 -1
- package/dist/network/indexer/indexer.state.d.ts.map +0 -1
- package/dist/network/indexer/indexer.state.js.map +0 -1
- package/dist/network/membership/membership.graph.d.ts.map +0 -1
- package/dist/network/membership/membership.graph.js.map +0 -1
- package/dist/network/membership/membership.state.d.ts.map +0 -1
- package/dist/network/membership/membership.state.js.map +0 -1
- package/dist/network/network.graph.d.ts.map +0 -1
- package/dist/network/network.graph.js.map +0 -1
- package/dist/network/network.recommender.d.ts.map +0 -1
- package/dist/network/network.recommender.js.map +0 -1
- package/dist/network/network.state.d.ts.map +0 -1
- package/dist/network/network.state.js.map +0 -1
- package/dist/network/network.tools.d.ts.map +0 -1
- package/dist/network/network.tools.js.map +0 -1
- package/dist/opportunity/delivery-card.cache.d.ts.map +0 -1
- package/dist/opportunity/delivery-card.cache.js.map +0 -1
- package/dist/opportunity/discovery-question.helper.d.ts.map +0 -1
- package/dist/opportunity/discovery-question.helper.js.map +0 -1
- package/dist/opportunity/discriminator/discriminator.adjustments.d.ts.map +0 -1
- package/dist/opportunity/discriminator/discriminator.adjustments.js.map +0 -1
- package/dist/opportunity/discriminator/discriminator.assigner.d.ts.map +0 -1
- package/dist/opportunity/discriminator/discriminator.assigner.js.map +0 -1
- package/dist/opportunity/discriminator/discriminator.env.d.ts.map +0 -1
- package/dist/opportunity/discriminator/discriminator.env.js.map +0 -1
- package/dist/opportunity/discriminator/discriminator.miner.d.ts.map +0 -1
- package/dist/opportunity/discriminator/discriminator.miner.js.map +0 -1
- package/dist/opportunity/discriminator/discriminator.push.d.ts.map +0 -1
- package/dist/opportunity/discriminator/discriminator.push.js.map +0 -1
- package/dist/opportunity/discriminator/discriminator.question.d.ts.map +0 -1
- package/dist/opportunity/discriminator/discriminator.question.js.map +0 -1
- package/dist/opportunity/discriminator/discriminator.scorer.d.ts.map +0 -1
- package/dist/opportunity/discriminator/discriminator.scorer.js.map +0 -1
- package/dist/opportunity/discriminator/discriminator.shadow.d.ts.map +0 -1
- package/dist/opportunity/discriminator/discriminator.shadow.js.map +0 -1
- package/dist/opportunity/discriminator/discriminator.types.d.ts.map +0 -1
- package/dist/opportunity/discriminator/discriminator.types.js.map +0 -1
- package/dist/opportunity/feed/feed.categorizer.d.ts.map +0 -1
- package/dist/opportunity/feed/feed.categorizer.js.map +0 -1
- package/dist/opportunity/feed/feed.graph.d.ts.map +0 -1
- package/dist/opportunity/feed/feed.graph.js.map +0 -1
- package/dist/opportunity/feed/feed.health.d.ts.map +0 -1
- package/dist/opportunity/feed/feed.health.js.map +0 -1
- package/dist/opportunity/feed/feed.state.d.ts.map +0 -1
- package/dist/opportunity/feed/feed.state.js.map +0 -1
- package/dist/opportunity/negotiation-context.loader.d.ts.map +0 -1
- package/dist/opportunity/negotiation-context.loader.js.map +0 -1
- package/dist/opportunity/negotiation-evidence/negotiation-evidence.env.d.ts.map +0 -1
- package/dist/opportunity/negotiation-evidence/negotiation-evidence.env.js.map +0 -1
- package/dist/opportunity/negotiation-evidence/negotiation-evidence.extractor.d.ts.map +0 -1
- package/dist/opportunity/negotiation-evidence/negotiation-evidence.extractor.js.map +0 -1
- package/dist/opportunity/negotiation-evidence/negotiation-evidence.miner.d.ts.map +0 -1
- package/dist/opportunity/negotiation-evidence/negotiation-evidence.miner.js.map +0 -1
- package/dist/opportunity/negotiation-evidence/negotiation-evidence.shadow.d.ts.map +0 -1
- package/dist/opportunity/negotiation-evidence/negotiation-evidence.shadow.js.map +0 -1
- package/dist/opportunity/negotiation-evidence/negotiation-evidence.types.d.ts.map +0 -1
- package/dist/opportunity/negotiation-evidence/negotiation-evidence.types.js.map +0 -1
- package/dist/opportunity/negotiation-evidence/negotiation-evidence.verifier.d.ts.map +0 -1
- package/dist/opportunity/negotiation-evidence/negotiation-evidence.verifier.js.map +0 -1
- package/dist/opportunity/negotiation-summary.builder.d.ts.map +0 -1
- package/dist/opportunity/negotiation-summary.builder.js.map +0 -1
- package/dist/opportunity/opportunity.actor.d.ts.map +0 -1
- package/dist/opportunity/opportunity.actor.js.map +0 -1
- package/dist/opportunity/opportunity.claim-safety.d.ts.map +0 -1
- package/dist/opportunity/opportunity.claim-safety.js.map +0 -1
- package/dist/opportunity/opportunity.discover.d.ts.map +0 -1
- package/dist/opportunity/opportunity.discover.js.map +0 -1
- package/dist/opportunity/opportunity.enricher.d.ts.map +0 -1
- package/dist/opportunity/opportunity.enricher.js.map +0 -1
- package/dist/opportunity/opportunity.evaluator.d.ts.map +0 -1
- package/dist/opportunity/opportunity.evaluator.js.map +0 -1
- package/dist/opportunity/opportunity.evidence.d.ts.map +0 -1
- package/dist/opportunity/opportunity.evidence.js.map +0 -1
- package/dist/opportunity/opportunity.graph.d.ts.map +0 -1
- package/dist/opportunity/opportunity.graph.js.map +0 -1
- package/dist/opportunity/opportunity.introducer.d.ts.map +0 -1
- package/dist/opportunity/opportunity.introducer.js.map +0 -1
- package/dist/opportunity/opportunity.labels.d.ts.map +0 -1
- package/dist/opportunity/opportunity.labels.js.map +0 -1
- package/dist/opportunity/opportunity.pending-questions.d.ts.map +0 -1
- package/dist/opportunity/opportunity.pending-questions.js.map +0 -1
- package/dist/opportunity/opportunity.persist.d.ts.map +0 -1
- package/dist/opportunity/opportunity.persist.js.map +0 -1
- package/dist/opportunity/opportunity.presentation-cache.d.ts.map +0 -1
- package/dist/opportunity/opportunity.presentation-cache.js.map +0 -1
- package/dist/opportunity/opportunity.presentation.d.ts.map +0 -1
- package/dist/opportunity/opportunity.presentation.js.map +0 -1
- package/dist/opportunity/opportunity.presenter.d.ts.map +0 -1
- package/dist/opportunity/opportunity.presenter.js.map +0 -1
- package/dist/opportunity/opportunity.safe-presentation.d.ts.map +0 -1
- package/dist/opportunity/opportunity.safe-presentation.js.map +0 -1
- package/dist/opportunity/opportunity.state.d.ts.map +0 -1
- package/dist/opportunity/opportunity.state.js.map +0 -1
- package/dist/opportunity/opportunity.tools.d.ts.map +0 -1
- package/dist/opportunity/opportunity.tools.js.map +0 -1
- package/dist/opportunity/opportunity.utils.d.ts.map +0 -1
- package/dist/opportunity/opportunity.utils.js.map +0 -1
- package/dist/opportunity/outcome/outcome.env.d.ts.map +0 -1
- package/dist/opportunity/outcome/outcome.env.js.map +0 -1
- package/dist/opportunity/outcome/outcome.hypotheses.d.ts.map +0 -1
- package/dist/opportunity/outcome/outcome.hypotheses.js.map +0 -1
- package/dist/opportunity/outcome/outcome.shadow.d.ts.map +0 -1
- package/dist/opportunity/outcome/outcome.shadow.js.map +0 -1
- package/dist/opportunity/outcome/outcome.types.d.ts.map +0 -1
- package/dist/opportunity/outcome/outcome.types.js.map +0 -1
- package/dist/premise/premise.analyzer.d.ts.map +0 -1
- package/dist/premise/premise.analyzer.js.map +0 -1
- package/dist/premise/premise.decomposer.d.ts.map +0 -1
- package/dist/premise/premise.decomposer.js.map +0 -1
- package/dist/premise/premise.graph.d.ts.map +0 -1
- package/dist/premise/premise.graph.js.map +0 -1
- package/dist/premise/premise.indexer.d.ts.map +0 -1
- package/dist/premise/premise.indexer.js.map +0 -1
- package/dist/premise/premise.state.d.ts.map +0 -1
- package/dist/premise/premise.state.js.map +0 -1
- package/dist/premise/premise.tools.d.ts.map +0 -1
- package/dist/premise/premise.tools.js.map +0 -1
- package/dist/questioner/questioner.agent.d.ts.map +0 -1
- package/dist/questioner/questioner.agent.js.map +0 -1
- package/dist/questioner/questioner.ask.tool.d.ts.map +0 -1
- package/dist/questioner/questioner.ask.tool.js.map +0 -1
- package/dist/questioner/questioner.discovery.prompt.d.ts.map +0 -1
- package/dist/questioner/questioner.discovery.prompt.js.map +0 -1
- package/dist/questioner/questioner.env.d.ts.map +0 -1
- package/dist/questioner/questioner.env.js.map +0 -1
- package/dist/questioner/questioner.presets.d.ts.map +0 -1
- package/dist/questioner/questioner.presets.js.map +0 -1
- package/dist/questioner/questioner.qud.d.ts.map +0 -1
- package/dist/questioner/questioner.qud.js.map +0 -1
- package/dist/questioner/questioner.tools.d.ts.map +0 -1
- package/dist/questioner/questioner.tools.js.map +0 -1
- package/dist/questioner/questioner.types.d.ts.map +0 -1
- package/dist/questioner/questioner.types.js.map +0 -1
- package/dist/shared/agent/model-signal.d.ts.map +0 -1
- package/dist/shared/agent/model-signal.js.map +0 -1
- package/dist/shared/agent/model.config.d.ts.map +0 -1
- package/dist/shared/agent/model.config.js.map +0 -1
- package/dist/shared/agent/tests/llm-assert.d.ts +0 -11
- package/dist/shared/agent/tests/llm-assert.d.ts.map +0 -1
- package/dist/shared/agent/tests/llm-assert.js +0 -43
- package/dist/shared/agent/tests/llm-assert.js.map +0 -1
- package/dist/shared/agent/tool.factory.d.ts.map +0 -1
- package/dist/shared/agent/tool.factory.js.map +0 -1
- package/dist/shared/agent/tool.helpers.d.ts.map +0 -1
- package/dist/shared/agent/tool.helpers.js.map +0 -1
- package/dist/shared/agent/tool.registry.d.ts.map +0 -1
- package/dist/shared/agent/tool.registry.js.map +0 -1
- package/dist/shared/agent/tool.runtime.d.ts.map +0 -1
- package/dist/shared/agent/tool.runtime.js.map +0 -1
- package/dist/shared/agent/tool.scope.d.ts.map +0 -1
- package/dist/shared/agent/tool.scope.js.map +0 -1
- package/dist/shared/agent/utility.tools.d.ts.map +0 -1
- package/dist/shared/agent/utility.tools.js.map +0 -1
- package/dist/shared/assignment/network-assignment.policy.d.ts.map +0 -1
- package/dist/shared/assignment/network-assignment.policy.js.map +0 -1
- package/dist/shared/hyde/hyde.documents.d.ts.map +0 -1
- package/dist/shared/hyde/hyde.documents.js.map +0 -1
- package/dist/shared/hyde/hyde.env.d.ts.map +0 -1
- package/dist/shared/hyde/hyde.env.js.map +0 -1
- package/dist/shared/hyde/hyde.frame.d.ts.map +0 -1
- package/dist/shared/hyde/hyde.frame.js.map +0 -1
- package/dist/shared/hyde/hyde.generator.d.ts.map +0 -1
- package/dist/shared/hyde/hyde.generator.js.map +0 -1
- package/dist/shared/hyde/hyde.graph.d.ts.map +0 -1
- package/dist/shared/hyde/hyde.graph.js.map +0 -1
- package/dist/shared/hyde/hyde.state.d.ts.map +0 -1
- package/dist/shared/hyde/hyde.state.js.map +0 -1
- package/dist/shared/hyde/hyde.strategies.d.ts.map +0 -1
- package/dist/shared/hyde/hyde.strategies.js.map +0 -1
- package/dist/shared/hyde/hyde.validator.d.ts.map +0 -1
- package/dist/shared/hyde/hyde.validator.js.map +0 -1
- package/dist/shared/hyde/lens.inferrer.d.ts.map +0 -1
- package/dist/shared/hyde/lens.inferrer.js.map +0 -1
- package/dist/shared/interfaces/agent-dispatcher.interface.d.ts.map +0 -1
- package/dist/shared/interfaces/agent-dispatcher.interface.js.map +0 -1
- package/dist/shared/interfaces/agent.interface.d.ts.map +0 -1
- package/dist/shared/interfaces/agent.interface.js.map +0 -1
- package/dist/shared/interfaces/auth.interface.d.ts.map +0 -1
- package/dist/shared/interfaces/auth.interface.js.map +0 -1
- package/dist/shared/interfaces/cache.interface.d.ts.map +0 -1
- package/dist/shared/interfaces/cache.interface.js.map +0 -1
- package/dist/shared/interfaces/chat-message-writer.interface.d.ts.map +0 -1
- package/dist/shared/interfaces/chat-message-writer.interface.js.map +0 -1
- package/dist/shared/interfaces/chat-session.interface.d.ts.map +0 -1
- package/dist/shared/interfaces/chat-session.interface.js.map +0 -1
- package/dist/shared/interfaces/chat-summary.interface.d.ts.map +0 -1
- package/dist/shared/interfaces/chat-summary.interface.js.map +0 -1
- package/dist/shared/interfaces/connect-link.interface.d.ts.map +0 -1
- package/dist/shared/interfaces/connect-link.interface.js.map +0 -1
- package/dist/shared/interfaces/contact.interface.d.ts.map +0 -1
- package/dist/shared/interfaces/contact.interface.js.map +0 -1
- package/dist/shared/interfaces/database.interface.d.ts.map +0 -1
- package/dist/shared/interfaces/database.interface.js.map +0 -1
- package/dist/shared/interfaces/delivery-ledger.interface.d.ts.map +0 -1
- package/dist/shared/interfaces/delivery-ledger.interface.js.map +0 -1
- package/dist/shared/interfaces/discovery-run.interface.d.ts.map +0 -1
- package/dist/shared/interfaces/discovery-run.interface.js.map +0 -1
- package/dist/shared/interfaces/embedder.interface.d.ts.map +0 -1
- package/dist/shared/interfaces/embedder.interface.js.map +0 -1
- package/dist/shared/interfaces/enrichment-run.interface.d.ts.map +0 -1
- package/dist/shared/interfaces/enrichment-run.interface.js.map +0 -1
- package/dist/shared/interfaces/enrichment.interface.d.ts.map +0 -1
- package/dist/shared/interfaces/enrichment.interface.js.map +0 -1
- package/dist/shared/interfaces/integration.interface.d.ts.map +0 -1
- package/dist/shared/interfaces/integration.interface.js.map +0 -1
- package/dist/shared/interfaces/negotiation-events.interface.d.ts.map +0 -1
- package/dist/shared/interfaces/negotiation-events.interface.js.map +0 -1
- package/dist/shared/interfaces/negotiation-summary.interface.d.ts.map +0 -1
- package/dist/shared/interfaces/negotiation-summary.interface.js.map +0 -1
- package/dist/shared/interfaces/negotiator-memory.interface.d.ts.map +0 -1
- package/dist/shared/interfaces/negotiator-memory.interface.js.map +0 -1
- package/dist/shared/interfaces/question-generator.interface.d.ts.map +0 -1
- package/dist/shared/interfaces/question-generator.interface.js.map +0 -1
- package/dist/shared/interfaces/questioner.interface.d.ts.map +0 -1
- package/dist/shared/interfaces/questioner.interface.js.map +0 -1
- package/dist/shared/interfaces/queue.interface.d.ts.map +0 -1
- package/dist/shared/interfaces/queue.interface.js.map +0 -1
- package/dist/shared/interfaces/scraper.interface.d.ts.map +0 -1
- package/dist/shared/interfaces/scraper.interface.js.map +0 -1
- package/dist/shared/network/metadata.renderer.d.ts.map +0 -1
- package/dist/shared/network/metadata.renderer.js.map +0 -1
- package/dist/shared/observability/debug-meta.sanitizer.d.ts.map +0 -1
- package/dist/shared/observability/debug-meta.sanitizer.js.map +0 -1
- package/dist/shared/observability/log.d.ts.map +0 -1
- package/dist/shared/observability/log.js.map +0 -1
- package/dist/shared/observability/performance.d.ts.map +0 -1
- package/dist/shared/observability/performance.js.map +0 -1
- package/dist/shared/observability/protocol.logger.d.ts.map +0 -1
- package/dist/shared/observability/protocol.logger.js.map +0 -1
- package/dist/shared/observability/request-context.d.ts.map +0 -1
- package/dist/shared/observability/request-context.js.map +0 -1
- package/dist/shared/observability/trace.d.ts.map +0 -1
- package/dist/shared/observability/trace.js.map +0 -1
- package/dist/shared/schemas/chat-context.schema.d.ts.map +0 -1
- package/dist/shared/schemas/chat-context.schema.js.map +0 -1
- package/dist/shared/schemas/discovery-question.schema.d.ts.map +0 -1
- package/dist/shared/schemas/discovery-question.schema.js.map +0 -1
- package/dist/shared/schemas/identity.schema.d.ts.map +0 -1
- package/dist/shared/schemas/identity.schema.js.map +0 -1
- package/dist/shared/schemas/mcp-auth.schema.d.ts.map +0 -1
- package/dist/shared/schemas/mcp-auth.schema.js.map +0 -1
- package/dist/shared/schemas/negotiation-digest.schema.d.ts.map +0 -1
- package/dist/shared/schemas/negotiation-digest.schema.js.map +0 -1
- package/dist/shared/schemas/negotiation-state.schema.d.ts.map +0 -1
- package/dist/shared/schemas/negotiation-state.schema.js.map +0 -1
- package/dist/shared/schemas/network-assignment.schema.d.ts.map +0 -1
- package/dist/shared/schemas/network-assignment.schema.js.map +0 -1
- package/dist/shared/schemas/pending-question.schema.d.ts.map +0 -1
- package/dist/shared/schemas/pending-question.schema.js.map +0 -1
- package/dist/shared/schemas/question.schema.d.ts.map +0 -1
- package/dist/shared/schemas/question.schema.js.map +0 -1
- package/dist/shared/ui/lucide.icon-catalog.d.ts.map +0 -1
- package/dist/shared/ui/lucide.icon-catalog.js.map +0 -1
- package/dist/shared/utils/social-label.d.ts.map +0 -1
- package/dist/shared/utils/social-label.js.map +0 -1
- package/dist/shared/utils/telegram-handle.d.ts.map +0 -1
- package/dist/shared/utils/telegram-handle.js.map +0 -1
|
@@ -0,0 +1,1834 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { requestContext } from "../../shared/observability/request-context.js";
|
|
3
|
+
import { success, error, UUID_REGEX } from "../../shared/agent/tool.helpers.js";
|
|
4
|
+
import { deriveDiscoveryNetworkIds, focusedIntentId, focusedNetworkId, focusedNetworkLabel } from "../../shared/agent/tool.scope.js";
|
|
5
|
+
import { MINIMAL_MAIN_TEXT_MAX_CHARS, getPrimaryActionLabel, SECONDARY_ACTION_LABEL } from "../domain/opportunity.labels.js";
|
|
6
|
+
import { narratorRemarkFromReasoning, stripUuids } from "../domain/opportunity.presentation.js";
|
|
7
|
+
import { safeFallbackSummary, getSafePresentationOrSkip } from "../domain/opportunity.safe-presentation.js";
|
|
8
|
+
import { buildOpportunityPresentation } from "./opportunity.card-presentation.js";
|
|
9
|
+
import { findCoalescedDiscoveryRun } from "../domain/opportunity.discovery-run-coalescing.js";
|
|
10
|
+
import { finalizeMcpDiscoveryLifecycle } from './opportunity.discovery-mcp-lifecycle-finalization.js';
|
|
11
|
+
import { runDiscoverFromQuery, continueDiscovery } from "./opportunity.discover.js";
|
|
12
|
+
import { isDiscoveryQuestionsEnabled, isUptakeGuardEnabled } from "../../capabilities/questions.runtime.facade.js";
|
|
13
|
+
import { OpportunityPresenter, gatherPresenterContext } from "./opportunity.presenter.js";
|
|
14
|
+
import { loadNegotiationContext } from "./negotiation-context.loader.js";
|
|
15
|
+
import { admitOpportunityUpdate } from './opportunity.update-admission.js';
|
|
16
|
+
import { selectOpportunityFeed } from './opportunity.feed-selection.js';
|
|
17
|
+
export { buildOpportunityPresentation } from "./opportunity.card-presentation.js";
|
|
18
|
+
function stripLeadingNarratorName(remark, narratorName) {
|
|
19
|
+
let t = remark.trim();
|
|
20
|
+
if (!t || !narratorName.trim())
|
|
21
|
+
return remark;
|
|
22
|
+
const name = narratorName.trim();
|
|
23
|
+
const nameLower = name.toLowerCase();
|
|
24
|
+
for (;;) {
|
|
25
|
+
const lower = t.toLowerCase();
|
|
26
|
+
if (!lower.startsWith(nameLower))
|
|
27
|
+
break;
|
|
28
|
+
// Require a word boundary after the name so a short name like "Al" does not
|
|
29
|
+
// mangle a longer word ("Always …" → "ways …"). The char after the name must
|
|
30
|
+
// be a separator (sentence/clause punctuation or whitespace) or end of string.
|
|
31
|
+
// Keep this set in sync with the separator stripped from `rest` below.
|
|
32
|
+
const boundary = t.charAt(name.length);
|
|
33
|
+
if (boundary && !/[\s.:,\-–—]/.test(boundary))
|
|
34
|
+
break;
|
|
35
|
+
const rest = t.slice(name.length).replace(/^\s*[.:,\-–—]\s*/i, '').trim();
|
|
36
|
+
if (rest.length === 0 || rest === t)
|
|
37
|
+
break;
|
|
38
|
+
t = rest;
|
|
39
|
+
}
|
|
40
|
+
return t;
|
|
41
|
+
}
|
|
42
|
+
import { protocolLogger } from "../../shared/observability/protocol.logger.js";
|
|
43
|
+
import { mergePendingQuestions } from "./opportunity.pending-questions.js";
|
|
44
|
+
import { invokeWithAbortSignal } from "../../shared/agent/model-signal.js";
|
|
45
|
+
const logger = protocolLogger("ChatTools:Opportunity");
|
|
46
|
+
const discoverOpportunitiesLog = protocolLogger("ChatTools:Opportunity:discover_opportunities");
|
|
47
|
+
/**
|
|
48
|
+
* Pure status × role → ConnectLinkKind matrix.
|
|
49
|
+
*
|
|
50
|
+
* Returns the kind of short-link the viewer can act on directly, or `null` if
|
|
51
|
+
* no direct link makes sense for this combination. Non-null kinds map to:
|
|
52
|
+
*
|
|
53
|
+
* - `connect` — pending opp where viewer is a non-introducer party. Clicking
|
|
54
|
+
* flips the opp to accepted and opens the chat with the counterpart.
|
|
55
|
+
* - `approve_introduction` — draft or latent opp where viewer is an unapproved
|
|
56
|
+
* introducer. Clicking flips approved=true and triggers negotiation. The
|
|
57
|
+
* `draft` case comes from `discover_opportunities` intro mode; the `latent`
|
|
58
|
+
* case comes from background-discovered connector-flow cards surfaced in
|
|
59
|
+
* `list_opportunities`. In both, status remains pre-send and the `/c/<code>`
|
|
60
|
+
* link is the only MCP path to approve.
|
|
61
|
+
* - `outreach` — accepted opp where viewer is a non-introducer party.
|
|
62
|
+
* Clicking opens the existing chat (no state change).
|
|
63
|
+
* - `send_direct` — draft or latent opp where viewer is a non-introducer
|
|
64
|
+
* party. Issued by `discover_opportunities` in direct (no-introducer)
|
|
65
|
+
* mode: the match has already passed evaluation, the row exists in
|
|
66
|
+
* draft state, and the sender just needs to release it. Clicking flips
|
|
67
|
+
* the opp straight to accepted and opens the chat with a greeting —
|
|
68
|
+
* same handler path as `connect`. The counterpart's side sees the new
|
|
69
|
+
* accepted opp and can engage or ignore.
|
|
70
|
+
*/
|
|
71
|
+
export function resolveActionableLinkKind(input) {
|
|
72
|
+
const { status, viewerRole, viewerApproved, viewerActedAt } = input;
|
|
73
|
+
const isIntroducer = viewerRole === "introducer";
|
|
74
|
+
const hasViewerActed = !!viewerActedAt;
|
|
75
|
+
if (status === "accepted") {
|
|
76
|
+
return isIntroducer ? null : "outreach";
|
|
77
|
+
}
|
|
78
|
+
if (status === "pending") {
|
|
79
|
+
return isIntroducer || hasViewerActed ? null : "connect";
|
|
80
|
+
}
|
|
81
|
+
if (status === "draft" || status === "latent") {
|
|
82
|
+
if (!isIntroducer)
|
|
83
|
+
return hasViewerActed ? null : "send_direct";
|
|
84
|
+
return viewerApproved === true ? null : "approve_introduction";
|
|
85
|
+
}
|
|
86
|
+
return null;
|
|
87
|
+
}
|
|
88
|
+
/**
|
|
89
|
+
* Build the agent-facing profile link for a counterpart — always the Index web
|
|
90
|
+
* profile URL with `?link_preview=false`. Returns `undefined` only if
|
|
91
|
+
* `frontendUrl` is not configured.
|
|
92
|
+
*
|
|
93
|
+
* The `?link_preview=false` hint is honored by chat-gateway runtimes (e.g.
|
|
94
|
+
* OpenClaw's Telegram delivery) that strip link previews when present in the
|
|
95
|
+
* URL; consistent placement matters more than Telegram's own handling.
|
|
96
|
+
*
|
|
97
|
+
* Trailing slashes on frontendUrl are stripped before concatenation.
|
|
98
|
+
*/
|
|
99
|
+
export function buildProfileUrl(counterpartUserId, frontendUrl) {
|
|
100
|
+
// The profile link is always the Index web profile, regardless of the
|
|
101
|
+
// counterpart's socials or the viewer's surface. Surface-aware Telegram
|
|
102
|
+
// deep-linking lives on the connect URL (`/c/:code`) — the connect-link
|
|
103
|
+
// controller resolves that to a pre-messaged t.me link — never here.
|
|
104
|
+
if (!frontendUrl)
|
|
105
|
+
return undefined;
|
|
106
|
+
const base = frontendUrl.replace(/\/+$/, "");
|
|
107
|
+
return `${base}/u/${counterpartUserId}?link_preview=false`;
|
|
108
|
+
}
|
|
109
|
+
/**
|
|
110
|
+
* Build the deep-link to an opportunity's A2A negotiation trace
|
|
111
|
+
* (`/chat/:conversationId`) so users can see *what negotiation led to* the
|
|
112
|
+
* surfaced opportunity (EDG-50/EDG-51). Returns `undefined` when `frontendUrl`
|
|
113
|
+
* is unset or there is no negotiation conversation to link to.
|
|
114
|
+
*
|
|
115
|
+
* The `?link_preview=false` hint mirrors `buildProfileUrl` — chat-gateway
|
|
116
|
+
* runtimes (e.g. Telegram delivery) strip link previews when it is present.
|
|
117
|
+
* Trailing slashes on `frontendUrl` are stripped before concatenation.
|
|
118
|
+
*/
|
|
119
|
+
export function buildNegotiationUrl(conversationId, frontendUrl) {
|
|
120
|
+
if (!frontendUrl || !conversationId)
|
|
121
|
+
return undefined;
|
|
122
|
+
const base = frontendUrl.replace(/\/+$/, "");
|
|
123
|
+
return `${base}/chat/${conversationId}?link_preview=false`;
|
|
124
|
+
}
|
|
125
|
+
/**
|
|
126
|
+
* Mint a short-link for `card` if the (status, viewerRole, viewerApproved)
|
|
127
|
+
* combination is actionable; mutate the card in place with `acceptUrl`,
|
|
128
|
+
* `profileUrl`, and `feedCategory`. No-op (and no DB call) if not actionable.
|
|
129
|
+
*
|
|
130
|
+
* Swallows mint errors after logging — the card is still returned without an
|
|
131
|
+
* `acceptUrl`, matching the prior `list_opportunities` resilience behavior.
|
|
132
|
+
*/
|
|
133
|
+
export async function attachActionableLinks(card, opts) {
|
|
134
|
+
// profileUrl is independent of whether the (status, viewerRole) combination
|
|
135
|
+
// is actionable — every counterpart has a profile page worth linking to,
|
|
136
|
+
// even on a fresh draft where there is no acceptUrl yet. Setting it before
|
|
137
|
+
// the early-return below means cards from non-actionable combinations
|
|
138
|
+
// (e.g. draft + party in `discover_opportunities` direct mode) still carry
|
|
139
|
+
// the profile link the agent needs to render. Without this, the agent gets
|
|
140
|
+
// a name with no URL attached and tends to fabricate one.
|
|
141
|
+
const profileUrl = buildProfileUrl(opts.counterpartUserId, opts.frontendUrl);
|
|
142
|
+
if (profileUrl)
|
|
143
|
+
card.profileUrl = profileUrl;
|
|
144
|
+
const kind = resolveActionableLinkKind({
|
|
145
|
+
status: card.status,
|
|
146
|
+
viewerRole: card.viewerRole,
|
|
147
|
+
viewerApproved: opts.viewerApproved,
|
|
148
|
+
viewerActedAt: opts.viewerActedAt,
|
|
149
|
+
});
|
|
150
|
+
logger.info("Opportunity actionability decision", {
|
|
151
|
+
opportunityId: card.opportunityId,
|
|
152
|
+
status: card.status,
|
|
153
|
+
viewerRole: card.viewerRole,
|
|
154
|
+
viewerApproved: opts.viewerApproved,
|
|
155
|
+
viewerActedAt: opts.viewerActedAt,
|
|
156
|
+
kind: kind ?? "none",
|
|
157
|
+
});
|
|
158
|
+
if (kind === null)
|
|
159
|
+
return;
|
|
160
|
+
try {
|
|
161
|
+
const { url } = await opts.mintConnectLink({
|
|
162
|
+
userId: opts.viewerId,
|
|
163
|
+
opportunityId: card.opportunityId,
|
|
164
|
+
kind,
|
|
165
|
+
greeting: null,
|
|
166
|
+
preferredSurface: opts.preferredSurface,
|
|
167
|
+
});
|
|
168
|
+
card.acceptUrl = url;
|
|
169
|
+
card.feedCategory = card.viewerRole === "introducer" ? "connector-flow" : "connection";
|
|
170
|
+
}
|
|
171
|
+
catch (err) {
|
|
172
|
+
logger.warn("Failed to mint MCP opportunity link — surfacing card without acceptUrl/feedCategory; profileUrl is still attached", {
|
|
173
|
+
opportunityId: card.opportunityId,
|
|
174
|
+
kind,
|
|
175
|
+
error: err instanceof Error ? err.message : String(err),
|
|
176
|
+
});
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
function publicUptakeQuestion(question) {
|
|
180
|
+
return {
|
|
181
|
+
id: question.id,
|
|
182
|
+
title: question.title,
|
|
183
|
+
prompt: question.prompt,
|
|
184
|
+
options: question.options,
|
|
185
|
+
multiSelect: question.multiSelect,
|
|
186
|
+
};
|
|
187
|
+
}
|
|
188
|
+
function uptakeAdvisory(opportunityId, questions) {
|
|
189
|
+
return JSON.stringify({
|
|
190
|
+
success: false,
|
|
191
|
+
error: "Resolve the pending uptake questions or explicitly continue anyway.",
|
|
192
|
+
advisory: {
|
|
193
|
+
code: "unresolved_uptake_questions",
|
|
194
|
+
advisoryOnly: true,
|
|
195
|
+
opportunityId,
|
|
196
|
+
questions,
|
|
197
|
+
acknowledgedUptakeQuestionIds: questions.map((question) => question.id),
|
|
198
|
+
},
|
|
199
|
+
});
|
|
200
|
+
}
|
|
201
|
+
/**
|
|
202
|
+
* Maximum number of opportunity cards to show per chat response.
|
|
203
|
+
* Sized for `selectByComposition` to fill both feed buckets — up to 3
|
|
204
|
+
* connection + 3 connector-flow per the digest/ambient prompt rules.
|
|
205
|
+
*/
|
|
206
|
+
const CHAT_DISPLAY_LIMIT = 6;
|
|
207
|
+
/**
|
|
208
|
+
* Build minimal opportunity card data for chat without calling the LLM presenter.
|
|
209
|
+
* Uses only required fields from the opportunity record and counterpart name/avatar
|
|
210
|
+
* so list_opportunities and discovery return quickly.
|
|
211
|
+
*
|
|
212
|
+
* Note: narratorChip.text is generated via regex heuristics (narratorRemarkFromReasoning)
|
|
213
|
+
* rather than the OpportunityPresenter LLM. If narrator quality becomes an issue again,
|
|
214
|
+
* consider making this function async and delegating to OpportunityPresenter.presentHomeCard()
|
|
215
|
+
* which already produces a high-quality narratorRemark via LLM (used by the home graph
|
|
216
|
+
* and discovery pipeline). The trade-off is 5-20s latency per card.
|
|
217
|
+
*
|
|
218
|
+
* Exported for use in tests (opportunity.tools.spec.ts).
|
|
219
|
+
*/
|
|
220
|
+
export function buildMinimalOpportunityCard(opp, viewerId, counterpartUserId, counterpartName, counterpartAvatar, introducerName, introducerAvatar, viewerName, secondPartyName, secondPartyAvatar, secondPartyUserId, isCounterpartGhost) {
|
|
221
|
+
const viewerActor = opp.actors.find((a) => a.userId === viewerId);
|
|
222
|
+
const viewerRole = viewerActor?.role ?? "party";
|
|
223
|
+
const introducerActor = opp.actors.find((a) => a.role === "introducer" && a.userId !== viewerId);
|
|
224
|
+
const viewerIsIntroducer = opp.actors.some((a) => a.role === "introducer" && a.userId === viewerId);
|
|
225
|
+
const reasoning = opp.interpretation?.reasoning ?? "";
|
|
226
|
+
// Shared sanitization standard — see opportunity.safe-presentation.ts.
|
|
227
|
+
const mainText = safeFallbackSummary(reasoning, {
|
|
228
|
+
counterpartName,
|
|
229
|
+
viewerName,
|
|
230
|
+
introducerName: introducerName ?? undefined,
|
|
231
|
+
maxChars: MINIMAL_MAIN_TEXT_MAX_CHARS,
|
|
232
|
+
emptyText: "A suggested connection.",
|
|
233
|
+
});
|
|
234
|
+
const score = typeof opp.interpretation?.confidence === "number"
|
|
235
|
+
? opp.interpretation.confidence
|
|
236
|
+
: undefined;
|
|
237
|
+
const narratorName = viewerIsIntroducer
|
|
238
|
+
? "You"
|
|
239
|
+
: introducerName?.trim() || (introducerActor ? "Someone" : "Index");
|
|
240
|
+
const primaryActionLabel = getPrimaryActionLabel(viewerRole);
|
|
241
|
+
return {
|
|
242
|
+
opportunityId: opp.id,
|
|
243
|
+
userId: counterpartUserId,
|
|
244
|
+
name: counterpartName,
|
|
245
|
+
avatar: counterpartAvatar,
|
|
246
|
+
mainText,
|
|
247
|
+
cta: "Start a conversation to connect.",
|
|
248
|
+
headline: viewerIsIntroducer && secondPartyName
|
|
249
|
+
? `${counterpartName} → ${secondPartyName}`
|
|
250
|
+
: `Connection with ${counterpartName}`,
|
|
251
|
+
primaryActionLabel,
|
|
252
|
+
secondaryActionLabel: SECONDARY_ACTION_LABEL,
|
|
253
|
+
mutualIntentsLabel: "Suggested connection",
|
|
254
|
+
narratorChip: {
|
|
255
|
+
name: narratorName,
|
|
256
|
+
text: narratorRemarkFromReasoning(reasoning, counterpartName, viewerName),
|
|
257
|
+
...(viewerIsIntroducer
|
|
258
|
+
? { userId: viewerId, avatar: null }
|
|
259
|
+
: introducerActor
|
|
260
|
+
? { userId: introducerActor.userId, avatar: introducerAvatar ?? null }
|
|
261
|
+
: {}),
|
|
262
|
+
},
|
|
263
|
+
viewerRole,
|
|
264
|
+
score,
|
|
265
|
+
status: opp.status ?? "latent",
|
|
266
|
+
isGhost: isCounterpartGhost ?? false,
|
|
267
|
+
...(viewerIsIntroducer && secondPartyName
|
|
268
|
+
? {
|
|
269
|
+
secondParty: {
|
|
270
|
+
name: secondPartyName,
|
|
271
|
+
...(secondPartyAvatar != null ? { avatar: secondPartyAvatar } : {}),
|
|
272
|
+
...(secondPartyUserId ? { userId: secondPartyUserId } : {}),
|
|
273
|
+
},
|
|
274
|
+
}
|
|
275
|
+
: {}),
|
|
276
|
+
};
|
|
277
|
+
}
|
|
278
|
+
function confirmDeliveryError(code, retryable, message) {
|
|
279
|
+
return JSON.stringify({ success: false, error: message, code, retryable });
|
|
280
|
+
}
|
|
281
|
+
export function createOpportunityTools(defineTool, deps) {
|
|
282
|
+
const { database, userDb, systemDb, graphs, cache } = deps;
|
|
283
|
+
const runDiscoveryFromQuery = deps.opportunityDiscovery?.runDiscoverFromQuery ??
|
|
284
|
+
runDiscoverFromQuery;
|
|
285
|
+
const continueOpportunityDiscovery = deps.opportunityDiscovery?.continueDiscovery ??
|
|
286
|
+
continueDiscovery;
|
|
287
|
+
const createOpportunityPresenter = deps.opportunityPresentation?.createPresenter ??
|
|
288
|
+
(() => new OpportunityPresenter());
|
|
289
|
+
const gatherOpportunityPresenterContext = deps.opportunityPresentation?.gatherPresenterContext ??
|
|
290
|
+
gatherPresenterContext;
|
|
291
|
+
const discoverOpportunities = defineTool({
|
|
292
|
+
name: "discover_opportunities",
|
|
293
|
+
description: "Discovers opportunities — connections between users based on complementary intents — and persists them as drafts. " +
|
|
294
|
+
"Opportunities are the core output of the discovery engine, representing potential valuable connections between people.\n\n" +
|
|
295
|
+
"**NOT for person lookup** — use read_user_contexts(query=name) to find people by name.\n\n" +
|
|
296
|
+
"**Four modes:**\n" +
|
|
297
|
+
"1. **Discovery** (most common): pass `searchQuery` and/or `networkId`. The system finds other users in shared networkes " +
|
|
298
|
+
"whose intents semantically complement the query. Uses HyDE embeddings and LLM evaluation for scoring.\n" +
|
|
299
|
+
"2. **Introduction**: pass `partyUserIds` (2+ user IDs) + `entities` (pre-gathered profiles and intents from shared networkes). " +
|
|
300
|
+
"You MUST call read_user_contexts and read_intents for each party BEFORE calling this. " +
|
|
301
|
+
"Optionally pass `hint` with the user's reason for the introduction.\n" +
|
|
302
|
+
"3. **Direct connection**: pass `targetUserId` + `searchQuery`. Creates an opportunity between the current user and one specific person.\n" +
|
|
303
|
+
"4. **Introducer discovery**: pass `introTargetUserId` (find matches FOR that person; current user becomes the introducer). " +
|
|
304
|
+
"Use when user asks 'who should I introduce to [person]?'\n\n" +
|
|
305
|
+
"**Returns:** In regular chat, opportunity code blocks (render as interactive cards) with opportunityId, match reasoning, confidence score, and status. " +
|
|
306
|
+
"In MCP contexts, starts an async discovery run and returns `discoveryRunId` with status `queued`. " +
|
|
307
|
+
"Then poll get_discovery_run with that id roughly every 5 seconds until status is `succeeded`, `failed`, or `cancelled`, and present its `result`. " +
|
|
308
|
+
"Do NOT call discover_opportunities again for the same request while a run is in progress — a repeat call with the same parameters " +
|
|
309
|
+
"returns the SAME in-progress run (with `coalesced: true`), not a new one. Keep polling the run id instead of starting new runs. " +
|
|
310
|
+
"All results start as drafts. Supports pagination via `continueFrom` for large result sets.\n\n" +
|
|
311
|
+
"**Next steps:** Use update_opportunity(opportunityId, status='pending') to send a draft to the other party.\n\n" +
|
|
312
|
+
"**Discovery-first rule.** For open-ended connection-seeking requests (\"find me a mentor\", " +
|
|
313
|
+
"\"who needs a React dev\", \"looking for investors\"), call this tool with `searchQuery` FIRST. " +
|
|
314
|
+
"Do NOT call create_intent for these phrasings — create_intent is only for when the user explicitly " +
|
|
315
|
+
"asks to \"create\", \"save\", \"add\", or \"remember\" a signal.\n\n" +
|
|
316
|
+
"**Personal-index scoping.** When the user says \"in my network\", \"from my contacts\", \"people I know\", " +
|
|
317
|
+
"or similar scoping language, pass the user's personal network ID (from memberships where `isPersonal: true`) " +
|
|
318
|
+
"as `networkId`. The personal network contains the user's contacts — scoping discovery to it restricts " +
|
|
319
|
+
"results to people the user already knows. Without this scoping language, omit networkId to let discovery " +
|
|
320
|
+
"run across all networks.\n\n" +
|
|
321
|
+
"**Introduction mode prerequisites.** When using `partyUserIds` + `entities`, YOU must pre-fetch each party's " +
|
|
322
|
+
"profile and intents before calling this tool. The entities array must include each party's userId, profile, " +
|
|
323
|
+
"intents from shared networkes, and the shared networkId. Call read_user_contexts, read_network_memberships, " +
|
|
324
|
+
"and read_intents for both parties first. The introducer (current user) must NOT appear in entities.\n\n" +
|
|
325
|
+
"**Signal-visibility follow-up.** If the response includes `suggestIntentCreationForVisibility: true` and " +
|
|
326
|
+
"`suggestedIntentDescription`, after presenting opportunity cards ask the user ONCE whether they'd also like " +
|
|
327
|
+
"to create a signal so others can find them. On yes, call create_intent with the suggested description. " +
|
|
328
|
+
"Never suggest this after introducer-mode (`introTargetUserId`) calls — the query describes the other person's " +
|
|
329
|
+
"needs, not the signed-in user's.",
|
|
330
|
+
querySchema: z.object({
|
|
331
|
+
continueFrom: z
|
|
332
|
+
.string()
|
|
333
|
+
.optional()
|
|
334
|
+
.describe("Pagination token: pass the discoveryId from a previous discover_opportunities result to evaluate the next batch of candidates. Do not combine with searchQuery or other mode parameters — when a fresh searchQuery is also present, the server ignores continueFrom and runs a fresh discovery."),
|
|
335
|
+
searchQuery: z
|
|
336
|
+
.string()
|
|
337
|
+
.optional()
|
|
338
|
+
.describe("Discovery mode: natural language description of what to look for (e.g. 'AI/ML engineers', 'startup advisors in fintech'). Drives semantic matching against other users' intents and profiles."),
|
|
339
|
+
networkId: z
|
|
340
|
+
.string()
|
|
341
|
+
.optional()
|
|
342
|
+
.describe("Network UUID to scope discovery to a specific community. Get from read_networks. In an network-scoped chat, omitting this runs discovery only in the scoped community; pass the personal network ID (from read_networks, isPersonal=true) only when the user explicitly asks to discover among contacts."),
|
|
343
|
+
intentId: z
|
|
344
|
+
.string()
|
|
345
|
+
.optional()
|
|
346
|
+
.describe("Optional intent UUID to use as the discovery source. The intent's description drives matching instead of searchQuery. Get from read_intents. Typically used by background processing, not direct agent calls."),
|
|
347
|
+
targetUserId: z
|
|
348
|
+
.string()
|
|
349
|
+
.optional()
|
|
350
|
+
.describe("Direct connection mode: create an opportunity with this specific user. Get the userId from read_user_contexts(query=name). Combine with searchQuery to explain the connection reason."),
|
|
351
|
+
introTargetUserId: z
|
|
352
|
+
.string()
|
|
353
|
+
.optional()
|
|
354
|
+
.describe("Introducer discovery mode: find matches FOR this user ID (the current user becomes the introducer). " +
|
|
355
|
+
"Get the userId from read_user_contexts(query=name). " +
|
|
356
|
+
"Use when the user asks 'who should I introduce to [person]?'. " +
|
|
357
|
+
"Do NOT combine with partyUserIds (that's full introduction mode)."),
|
|
358
|
+
partyUserIds: z
|
|
359
|
+
.array(z.string())
|
|
360
|
+
.optional()
|
|
361
|
+
.describe("Introduction mode: array of 2+ user IDs to introduce to each other. Get user IDs from read_user_contexts or read_network_memberships. Must also provide entities with pre-gathered profile/intent data."),
|
|
362
|
+
entities: z
|
|
363
|
+
.array(z.object({
|
|
364
|
+
userId: z.string(),
|
|
365
|
+
profile: z
|
|
366
|
+
.object({
|
|
367
|
+
name: z.string().optional(),
|
|
368
|
+
bio: z.string().optional(),
|
|
369
|
+
location: z.string().optional(),
|
|
370
|
+
interests: z.array(z.string()).optional(),
|
|
371
|
+
skills: z.array(z.string()).optional(),
|
|
372
|
+
context: z.string().optional(),
|
|
373
|
+
})
|
|
374
|
+
.optional(),
|
|
375
|
+
intents: z
|
|
376
|
+
.array(z.object({
|
|
377
|
+
intentId: z.string(),
|
|
378
|
+
payload: z.string(),
|
|
379
|
+
summary: z.string().optional(),
|
|
380
|
+
}))
|
|
381
|
+
.optional(),
|
|
382
|
+
networkId: z
|
|
383
|
+
.string()
|
|
384
|
+
.describe("Shared network this entity's data comes from (required for intro mode)"),
|
|
385
|
+
}))
|
|
386
|
+
.optional()
|
|
387
|
+
.describe("Introduction mode: pre-gathered profile and intent data for each party being introduced. " +
|
|
388
|
+
"Each entry needs userId, networkId (the shared network), and optionally profile (name, bio, skills, interests) and intents (intentId, payload). " +
|
|
389
|
+
"Gather this data by calling read_user_contexts and read_intents for each party BEFORE calling discover_opportunities. " +
|
|
390
|
+
"All entities must share the same networkId (the shared network where both parties are members)."),
|
|
391
|
+
hint: z
|
|
392
|
+
.string()
|
|
393
|
+
.optional()
|
|
394
|
+
.describe("Introduction mode: the user's reason for making this introduction (e.g. 'both working on AI in healthcare', " +
|
|
395
|
+
"'complementary skills for a startup'). Helps the evaluator produce better match reasoning."),
|
|
396
|
+
}),
|
|
397
|
+
handler: async ({ context, query }) => {
|
|
398
|
+
const scopedNetworkId = focusedNetworkId(context) ?? context.networkId?.trim();
|
|
399
|
+
const scopedIndexLabel = focusedNetworkLabel(context);
|
|
400
|
+
// Strict scope enforcement: when chat is network-scoped, only allow that index
|
|
401
|
+
if (scopedNetworkId &&
|
|
402
|
+
query.networkId?.trim() &&
|
|
403
|
+
query.networkId.trim() !== scopedNetworkId) {
|
|
404
|
+
return error(`This chat is scoped to ${scopedIndexLabel}. You can only create opportunities in this community.`);
|
|
405
|
+
}
|
|
406
|
+
// Distinguish an explicit `query.networkId` override (caller wants discovery
|
|
407
|
+
// scoped to one specific index) from an implicit scoped-chat context.
|
|
408
|
+
// Scoped-chat discovery stays focused to the scoped community only; the
|
|
409
|
+
// personal-inclusive allowed reach is reserved for self-owned writes.
|
|
410
|
+
const explicitIndexId = query.networkId?.trim() || undefined;
|
|
411
|
+
const effectiveIndexId = explicitIndexId;
|
|
412
|
+
if (effectiveIndexId && !UUID_REGEX.test(effectiveIndexId)) {
|
|
413
|
+
return error("Invalid network ID format.");
|
|
414
|
+
}
|
|
415
|
+
const contextIntentId = focusedIntentId(context);
|
|
416
|
+
const requestedIntentId = query.intentId?.trim() || undefined;
|
|
417
|
+
if (requestedIntentId != null && !UUID_REGEX.test(requestedIntentId)) {
|
|
418
|
+
return error("Invalid intent ID format.");
|
|
419
|
+
}
|
|
420
|
+
if (contextIntentId && requestedIntentId && requestedIntentId !== contextIntentId) {
|
|
421
|
+
return error("This chat is scoped to a different intent.");
|
|
422
|
+
}
|
|
423
|
+
const triggerIntentId = contextIntentId ?? requestedIntentId;
|
|
424
|
+
if (triggerIntentId) {
|
|
425
|
+
const triggerIntent = await systemDb.getIntent(triggerIntentId);
|
|
426
|
+
if (!triggerIntent || triggerIntent.userId !== context.userId) {
|
|
427
|
+
return error("Intent not found or you are not authorized to use it for discovery.");
|
|
428
|
+
}
|
|
429
|
+
if (triggerIntent.archivedAt ||
|
|
430
|
+
(triggerIntent.status != null && triggerIntent.status !== 'ACTIVE')) {
|
|
431
|
+
return error("This intent is not active. Resume it before starting discovery.");
|
|
432
|
+
}
|
|
433
|
+
}
|
|
434
|
+
if (context.isMcp && deps.discoveryRuns && deps.discoveryRunQueue) {
|
|
435
|
+
// Coalesce: if an equivalent discovery is already queued/running for this
|
|
436
|
+
// user, return that run instead of spawning a duplicate. An over-eager
|
|
437
|
+
// MCP client that re-fires discover_opportunities (instead of polling
|
|
438
|
+
// get_discovery_run) would otherwise kick off a fresh, expensive
|
|
439
|
+
// opportunity-graph run on every call — the loop that drives the agent
|
|
440
|
+
// into provider rate limits.
|
|
441
|
+
try {
|
|
442
|
+
const active = await deps.discoveryRuns.listActive(context.userId);
|
|
443
|
+
const existing = findCoalescedDiscoveryRun(query, context, active);
|
|
444
|
+
if (existing) {
|
|
445
|
+
return success({
|
|
446
|
+
status: existing.status === "running" ? "running" : "queued",
|
|
447
|
+
discoveryRunId: existing.id,
|
|
448
|
+
coalesced: true,
|
|
449
|
+
message: `A discovery run for this exact request is already ${existing.status}. ` +
|
|
450
|
+
`Do NOT call discover_opportunities again — keep calling get_discovery_run with ` +
|
|
451
|
+
`discoveryRunId="${existing.id}" (about every 5 seconds) until it succeeds, fails, ` +
|
|
452
|
+
`or is cancelled, then present its result.`,
|
|
453
|
+
});
|
|
454
|
+
}
|
|
455
|
+
}
|
|
456
|
+
catch {
|
|
457
|
+
// listActive is a best-effort optimization; fall through to create on error.
|
|
458
|
+
}
|
|
459
|
+
const run = await deps.discoveryRuns.create({
|
|
460
|
+
userId: context.userId,
|
|
461
|
+
agentId: context.agentId ?? null,
|
|
462
|
+
input: query,
|
|
463
|
+
context: {
|
|
464
|
+
userId: context.userId,
|
|
465
|
+
userName: context.userName,
|
|
466
|
+
userEmail: context.userEmail,
|
|
467
|
+
...(context.scopeType && context.scopeId ? { scopeType: context.scopeType, scopeId: context.scopeId } : scopedNetworkId ? { scopeType: 'network', scopeId: scopedNetworkId } : {}),
|
|
468
|
+
...(context.indexName ? { indexName: context.indexName } : {}),
|
|
469
|
+
...(context.sessionId ? { sessionId: context.sessionId } : {}),
|
|
470
|
+
...(context.agentId ? { agentId: context.agentId } : {}),
|
|
471
|
+
...(context.clientSurface ? { clientSurface: context.clientSurface } : {}),
|
|
472
|
+
},
|
|
473
|
+
});
|
|
474
|
+
try {
|
|
475
|
+
await deps.discoveryRunQueue.enqueue(run.id);
|
|
476
|
+
}
|
|
477
|
+
catch (err) {
|
|
478
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
479
|
+
await deps.discoveryRuns.markFailed(run.id, message);
|
|
480
|
+
return error(`Failed to enqueue discovery run: ${message}`);
|
|
481
|
+
}
|
|
482
|
+
return success({
|
|
483
|
+
status: "queued",
|
|
484
|
+
discoveryRunId: run.id,
|
|
485
|
+
message: `Discovery started. Poll get_discovery_run with discoveryRunId="${run.id}" about every 5 seconds ` +
|
|
486
|
+
`until status is succeeded, failed, or cancelled, then present its result. ` +
|
|
487
|
+
`Do NOT call discover_opportunities again for this request while the run is in progress — ` +
|
|
488
|
+
`a repeat call returns this same run, not a new one.`,
|
|
489
|
+
});
|
|
490
|
+
}
|
|
491
|
+
// ── Continuation mode ──
|
|
492
|
+
// `continueFrom` is a pagination token for resuming a prior discovery's
|
|
493
|
+
// cached candidates. When a caller (typically an MCP client's LLM) sends
|
|
494
|
+
// a fresh `searchQuery` alongside a stale `continueFrom`, treat it as a
|
|
495
|
+
// fresh search — the explicit search intent wins. Resuming against the
|
|
496
|
+
// stale session's exhausted cache silently produced the "No more
|
|
497
|
+
// matching opportunities found in the remaining candidates" response
|
|
498
|
+
// for users who expected fresh results (IND-305).
|
|
499
|
+
if (query.continueFrom && query.searchQuery?.trim()) {
|
|
500
|
+
logger.warn("discover_opportunities: dropping stale continueFrom in favor of fresh searchQuery", {
|
|
501
|
+
userId: context.userId,
|
|
502
|
+
continueFrom: query.continueFrom,
|
|
503
|
+
});
|
|
504
|
+
}
|
|
505
|
+
if (query.continueFrom && !query.searchQuery?.trim()) {
|
|
506
|
+
const _continueTraceEmitter = requestContext.getStore()?.traceEmitter;
|
|
507
|
+
const _graphStart = Date.now();
|
|
508
|
+
_continueTraceEmitter?.({ type: "graph_start", name: "opportunity" });
|
|
509
|
+
const result = await continueOpportunityDiscovery({
|
|
510
|
+
opportunityGraph: graphs.opportunity,
|
|
511
|
+
database,
|
|
512
|
+
cache,
|
|
513
|
+
userId: context.userId,
|
|
514
|
+
discoveryId: query.continueFrom,
|
|
515
|
+
expectedIndexId: scopedNetworkId,
|
|
516
|
+
limit: 20,
|
|
517
|
+
presenter: createOpportunityPresenter(),
|
|
518
|
+
useHomeCardFormat: true,
|
|
519
|
+
...(context.sessionId ? { chatSessionId: context.sessionId } : {}),
|
|
520
|
+
});
|
|
521
|
+
const _graphMs = Date.now() - _graphStart;
|
|
522
|
+
_continueTraceEmitter?.({ type: "graph_end", name: "opportunity", durationMs: _graphMs });
|
|
523
|
+
const allDebugSteps = [...(result.debugSteps ?? [])];
|
|
524
|
+
if (!result.found) {
|
|
525
|
+
return success({
|
|
526
|
+
found: false,
|
|
527
|
+
count: 0,
|
|
528
|
+
message: result.message ?? "No more matching opportunities found in the remaining candidates.",
|
|
529
|
+
summary: "No more matches found",
|
|
530
|
+
...(result.pagination ? { pagination: result.pagination } : {}),
|
|
531
|
+
debugSteps: allDebugSteps,
|
|
532
|
+
_graphTimings: [{ name: 'opportunity', durationMs: _graphMs, agents: [] }],
|
|
533
|
+
});
|
|
534
|
+
}
|
|
535
|
+
// Continuation now negotiates just like fresh discovery. For MCP callers,
|
|
536
|
+
// surface only post-negotiation drafts; keep in-flight attempts count-only
|
|
537
|
+
// and never render rejected/stalled or fallback latent rows as cards.
|
|
538
|
+
let negotiatingCount = 0;
|
|
539
|
+
const continuationCards = context.isMcp
|
|
540
|
+
? (result.opportunities ?? []).filter((opportunity) => {
|
|
541
|
+
if (opportunity.status === 'draft')
|
|
542
|
+
return true;
|
|
543
|
+
if (opportunity.status === 'negotiating') {
|
|
544
|
+
negotiatingCount += 1;
|
|
545
|
+
return false;
|
|
546
|
+
}
|
|
547
|
+
if (opportunity.status === 'rejected' || opportunity.status === 'stalled' || opportunity.status === 'latent') {
|
|
548
|
+
return false;
|
|
549
|
+
}
|
|
550
|
+
discoverOpportunitiesLog.warn('unexpected continuation status — counting as negotiating', {
|
|
551
|
+
opportunityId: opportunity.opportunityId,
|
|
552
|
+
status: opportunity.status,
|
|
553
|
+
});
|
|
554
|
+
negotiatingCount += 1;
|
|
555
|
+
return false;
|
|
556
|
+
})
|
|
557
|
+
: (result.opportunities ?? []);
|
|
558
|
+
// Build card data; cap at CHAT_DISPLAY_LIMIT (remaining feeds into pagination)
|
|
559
|
+
const allCardData = continuationCards.map((opp) => ({
|
|
560
|
+
opportunityId: opp.opportunityId,
|
|
561
|
+
userId: opp.userId,
|
|
562
|
+
name: opp.name,
|
|
563
|
+
avatar: opp.avatar,
|
|
564
|
+
mainText: getSafePresentationOrSkip(opp, { counterpartName: opp.name })?.summary ?? "",
|
|
565
|
+
cta: opp.homeCardPresentation?.suggestedAction,
|
|
566
|
+
headline: opp.homeCardPresentation?.headline,
|
|
567
|
+
primaryActionLabel: opp.homeCardPresentation?.primaryActionLabel,
|
|
568
|
+
secondaryActionLabel: opp.homeCardPresentation?.secondaryActionLabel,
|
|
569
|
+
mutualIntentsLabel: opp.homeCardPresentation?.mutualIntentsLabel,
|
|
570
|
+
narratorChip: opp.narratorChip,
|
|
571
|
+
viewerRole: opp.viewerRole,
|
|
572
|
+
isGhost: opp.isGhost ?? false,
|
|
573
|
+
score: opp.score,
|
|
574
|
+
status: opp.status,
|
|
575
|
+
viewerActedAt: opp.viewerActedAt,
|
|
576
|
+
}));
|
|
577
|
+
const displayedCards = allCardData.slice(0, CHAT_DISPLAY_LIMIT);
|
|
578
|
+
const extraFromCap = allCardData.length - displayedCards.length;
|
|
579
|
+
let message = buildOpportunityPresentation(displayedCards, {
|
|
580
|
+
isMcp: context.isMcp ?? false,
|
|
581
|
+
leadIn: `Found ${displayedCards.length} more potential connection(s).`,
|
|
582
|
+
});
|
|
583
|
+
if (context.isMcp && negotiatingCount > 0) {
|
|
584
|
+
message = displayedCards.length > 0
|
|
585
|
+
? `${message}\n\n${negotiatingCount} more opportunit${negotiatingCount === 1 ? 'y is' : 'ies are'} still being evaluated — check back via \`list_opportunities\` shortly.`
|
|
586
|
+
: `Found candidates, but they're still being evaluated. Try \`list_opportunities\` in a minute — ${negotiatingCount} pending.`;
|
|
587
|
+
}
|
|
588
|
+
else if (context.isMcp && displayedCards.length === 0) {
|
|
589
|
+
message = 'No additional actionable matches were found in this continuation.';
|
|
590
|
+
}
|
|
591
|
+
const isIntroducerContinuation = !!query.introTargetUserId?.trim();
|
|
592
|
+
const totalRemaining = (result.pagination?.remaining ?? 0) + extraFromCap;
|
|
593
|
+
if (totalRemaining > 0 && result.pagination?.discoveryId) {
|
|
594
|
+
message += `\n\nThere are ${totalRemaining} more candidates. Ask if the user wants to see more — they can say "show me more" and you should call discover_opportunities with continueFrom="${result.pagination.discoveryId}".`;
|
|
595
|
+
}
|
|
596
|
+
else if (displayedCards.length > 0 && isIntroducerContinuation) {
|
|
597
|
+
message += `\n\nThese are all the introduction candidates I found for this person.`;
|
|
598
|
+
}
|
|
599
|
+
else if (displayedCards.length > 0) {
|
|
600
|
+
message += `\n\nThese are all the connections I found. If the user wants to attract more connections, suggest they create a signal — e.g. "Would you like to create a signal so others looking for someone like you can find you?" If they agree, call create_intent with a description based on what they were searching for.`;
|
|
601
|
+
}
|
|
602
|
+
return success({
|
|
603
|
+
found: true,
|
|
604
|
+
count: displayedCards.length,
|
|
605
|
+
message,
|
|
606
|
+
summary: `Found ${displayedCards.length} more match(es)`,
|
|
607
|
+
...(result.pagination ? { pagination: result.pagination } : {}),
|
|
608
|
+
debugSteps: allDebugSteps,
|
|
609
|
+
_graphTimings: [{ name: 'opportunity', durationMs: _graphMs, agents: [] }],
|
|
610
|
+
});
|
|
611
|
+
}
|
|
612
|
+
// Normalize entity networkIds before any checks to avoid raw-vs-trimmed mismatches.
|
|
613
|
+
const normalizedEntities = query.entities?.map((e) => ({ ...e, networkId: e.networkId?.trim() }));
|
|
614
|
+
// Derive partyUserIds from entities when agent passes entities but omits partyUserIds (intro mode).
|
|
615
|
+
// Only derive when all entities share the same networkId to prevent cross-network introductions.
|
|
616
|
+
const partyUserIdsFromEntities = normalizedEntities &&
|
|
617
|
+
normalizedEntities.length >= 2 &&
|
|
618
|
+
normalizedEntities.every((e) => e.userId && e.networkId) &&
|
|
619
|
+
new Set(normalizedEntities.map((e) => e.networkId)).size === 1
|
|
620
|
+
? [...new Set(normalizedEntities.map((e) => e.userId))]
|
|
621
|
+
: undefined;
|
|
622
|
+
const effectivePartyUserIds = query.partyUserIds && query.partyUserIds.length >= 2
|
|
623
|
+
? query.partyUserIds
|
|
624
|
+
: (partyUserIdsFromEntities?.length ?? 0) >= 2
|
|
625
|
+
? partyUserIdsFromEntities
|
|
626
|
+
: undefined;
|
|
627
|
+
// ── Introduction mode ── (validation and persistence via opportunity graph)
|
|
628
|
+
if (effectivePartyUserIds && effectivePartyUserIds.length >= 2) {
|
|
629
|
+
if (!normalizedEntities || normalizedEntities.length === 0) {
|
|
630
|
+
return error("Introduction requires pre-gathered entity data. " +
|
|
631
|
+
"First use read_network_memberships to find shared networks, " +
|
|
632
|
+
"then read_user_contexts and read_intents for each party, " +
|
|
633
|
+
"then pass the results as entities.");
|
|
634
|
+
}
|
|
635
|
+
const normalizedEntityNetworkIds = normalizedEntities
|
|
636
|
+
.map((e) => e.networkId)
|
|
637
|
+
.filter((id) => Boolean(id));
|
|
638
|
+
if (normalizedEntityNetworkIds.length !== normalizedEntities.length ||
|
|
639
|
+
new Set(normalizedEntityNetworkIds).size !== 1) {
|
|
640
|
+
return error("All entities must include the same shared networkId.");
|
|
641
|
+
}
|
|
642
|
+
const [primaryNetworkId] = normalizedEntityNetworkIds;
|
|
643
|
+
const introducedPartyUserIds = effectivePartyUserIds.filter((uid) => uid !== context.userId);
|
|
644
|
+
if (introducedPartyUserIds.length === 0) {
|
|
645
|
+
return error("No counterpart to introduce. Provide at least one other user ID in partyUserIds (besides yourself).");
|
|
646
|
+
}
|
|
647
|
+
const evaluatorEntities = normalizedEntities.map((e) => ({
|
|
648
|
+
userId: e.userId,
|
|
649
|
+
profile: e.profile ?? {},
|
|
650
|
+
intents: e.intents,
|
|
651
|
+
networkId: e.networkId,
|
|
652
|
+
}));
|
|
653
|
+
const _introGraphStart = Date.now();
|
|
654
|
+
const _introTraceEmitter = requestContext.getStore()?.traceEmitter;
|
|
655
|
+
_introTraceEmitter?.({ type: "graph_start", name: "opportunity" });
|
|
656
|
+
const result = await invokeWithAbortSignal(graphs.opportunity, {
|
|
657
|
+
operationMode: "create_introduction",
|
|
658
|
+
userId: context.userId,
|
|
659
|
+
networkId: primaryNetworkId,
|
|
660
|
+
introductionEntities: evaluatorEntities,
|
|
661
|
+
introductionHint: query.hint,
|
|
662
|
+
requiredNetworkId: scopedNetworkId,
|
|
663
|
+
options: {
|
|
664
|
+
initialStatus: "draft",
|
|
665
|
+
...(context.sessionId ? { conversationId: context.sessionId } : {}),
|
|
666
|
+
},
|
|
667
|
+
});
|
|
668
|
+
const _introGraphMs = Date.now() - _introGraphStart;
|
|
669
|
+
_introTraceEmitter?.({ type: "graph_end", name: "opportunity", durationMs: _introGraphMs });
|
|
670
|
+
if (result.error || !result.opportunities?.length) {
|
|
671
|
+
return error(result.error ?? "Failed to create introduction.");
|
|
672
|
+
}
|
|
673
|
+
const created = result.opportunities[0];
|
|
674
|
+
const reasoning = created.interpretation?.reasoning ?? "A suggested connection.";
|
|
675
|
+
const confidence = typeof created.interpretation?.confidence === "number"
|
|
676
|
+
? created.interpretation.confidence
|
|
677
|
+
: parseFloat(String(created.confidence ?? 0)) || 0;
|
|
678
|
+
const introducerUser = await userDb.getUser();
|
|
679
|
+
const firstPartyId = introducedPartyUserIds[0];
|
|
680
|
+
const firstEntity = query.entities?.find((e) => e.userId === firstPartyId);
|
|
681
|
+
const counterpartUser = firstPartyId
|
|
682
|
+
? await database.getUser(firstPartyId)
|
|
683
|
+
: null;
|
|
684
|
+
const counterpartName = firstEntity?.profile?.name ?? firstPartyId ?? "Someone";
|
|
685
|
+
// Second party — used in the headline and arrow layout for the introducer view ("A → B")
|
|
686
|
+
const secondPartyId = introducedPartyUserIds[1];
|
|
687
|
+
const secondEntity = query.entities?.find((e) => e.userId === secondPartyId);
|
|
688
|
+
const secondPartyName = secondEntity?.profile?.name;
|
|
689
|
+
const secondPartyAvatar = secondEntity?.profile?.avatar ?? null;
|
|
690
|
+
const secondPartyUser = secondPartyId ? await database.getUser(secondPartyId) : null;
|
|
691
|
+
const viewerIsParty = effectivePartyUserIds.includes(context.userId);
|
|
692
|
+
const viewerRole = viewerIsParty ? "party" : "introducer";
|
|
693
|
+
const isCounterpartGhost = counterpartUser?.isGhost ?? false;
|
|
694
|
+
const primaryActionLabel = getPrimaryActionLabel(viewerRole);
|
|
695
|
+
const publicMatchReason = safeFallbackSummary(reasoning, {
|
|
696
|
+
counterpartName,
|
|
697
|
+
introducerName: introducerUser?.name ?? undefined,
|
|
698
|
+
maxChars: MINIMAL_MAIN_TEXT_MAX_CHARS,
|
|
699
|
+
emptyText: "A suggested connection.",
|
|
700
|
+
});
|
|
701
|
+
const narratorText = narratorRemarkFromReasoning(reasoning, counterpartName, introducerUser?.name ?? undefined);
|
|
702
|
+
const narratorChip = viewerIsParty
|
|
703
|
+
? {
|
|
704
|
+
name: "Index",
|
|
705
|
+
text: narratorText,
|
|
706
|
+
}
|
|
707
|
+
: {
|
|
708
|
+
name: "You",
|
|
709
|
+
text: narratorText,
|
|
710
|
+
userId: context.userId,
|
|
711
|
+
};
|
|
712
|
+
const headline = !viewerIsParty && secondPartyName
|
|
713
|
+
? `${counterpartName} → ${secondPartyName}`
|
|
714
|
+
: `Connection with ${counterpartName}`;
|
|
715
|
+
const cardData = {
|
|
716
|
+
opportunityId: created.id,
|
|
717
|
+
userId: firstPartyId,
|
|
718
|
+
name: counterpartName,
|
|
719
|
+
avatar: counterpartUser?.avatar ??
|
|
720
|
+
firstEntity?.profile
|
|
721
|
+
?.avatar ??
|
|
722
|
+
null,
|
|
723
|
+
mainText: safeFallbackSummary(reasoning, {
|
|
724
|
+
counterpartName,
|
|
725
|
+
// viewerName not available in this context; introducer name passed separately
|
|
726
|
+
introducerName: introducerUser?.name ?? undefined,
|
|
727
|
+
maxChars: MINIMAL_MAIN_TEXT_MAX_CHARS,
|
|
728
|
+
emptyText: "A suggested connection.",
|
|
729
|
+
}),
|
|
730
|
+
cta: "Start a conversation to connect.",
|
|
731
|
+
headline,
|
|
732
|
+
primaryActionLabel,
|
|
733
|
+
secondaryActionLabel: SECONDARY_ACTION_LABEL,
|
|
734
|
+
mutualIntentsLabel: "Suggested connection",
|
|
735
|
+
narratorChip,
|
|
736
|
+
viewerRole,
|
|
737
|
+
isGhost: isCounterpartGhost,
|
|
738
|
+
score: confidence,
|
|
739
|
+
status: created.status ?? "draft",
|
|
740
|
+
...(!viewerIsParty && secondPartyName
|
|
741
|
+
? {
|
|
742
|
+
secondParty: {
|
|
743
|
+
name: secondPartyName,
|
|
744
|
+
avatar: secondPartyUser?.avatar ?? secondPartyAvatar,
|
|
745
|
+
...(secondPartyId ? { userId: secondPartyId } : {}),
|
|
746
|
+
},
|
|
747
|
+
}
|
|
748
|
+
: {}),
|
|
749
|
+
};
|
|
750
|
+
if (context.isMcp && deps.mintConnectLink) {
|
|
751
|
+
await attachActionableLinks(cardData, {
|
|
752
|
+
viewerId: context.userId,
|
|
753
|
+
viewerApproved: false,
|
|
754
|
+
counterpartUserId: firstPartyId,
|
|
755
|
+
mintConnectLink: deps.mintConnectLink,
|
|
756
|
+
frontendUrl: deps.frontendUrl,
|
|
757
|
+
preferredSurface: context.clientSurface,
|
|
758
|
+
});
|
|
759
|
+
}
|
|
760
|
+
return success({
|
|
761
|
+
found: true,
|
|
762
|
+
count: 1,
|
|
763
|
+
summary: "Draft introduction created",
|
|
764
|
+
message: buildOpportunityPresentation([cardData], {
|
|
765
|
+
isMcp: context.isMcp ?? false,
|
|
766
|
+
leadIn: "Draft introduction created.",
|
|
767
|
+
label: "opportunity",
|
|
768
|
+
}),
|
|
769
|
+
opportunities: [
|
|
770
|
+
{
|
|
771
|
+
opportunityId: created.id,
|
|
772
|
+
matchReason: publicMatchReason,
|
|
773
|
+
score: confidence,
|
|
774
|
+
status: created.status ?? "draft",
|
|
775
|
+
},
|
|
776
|
+
],
|
|
777
|
+
_graphTimings: [{ name: 'opportunity', durationMs: _introGraphMs, agents: result.agentTimings ?? [] }],
|
|
778
|
+
});
|
|
779
|
+
}
|
|
780
|
+
// ── Discovery mode ──
|
|
781
|
+
const searchQuery = query.searchQuery?.trim() ?? "";
|
|
782
|
+
if (query.intentId != null && query.intentId !== "" && !UUID_REGEX.test(query.intentId.trim())) {
|
|
783
|
+
return error("Invalid intent ID format.");
|
|
784
|
+
}
|
|
785
|
+
let indexScope;
|
|
786
|
+
const _scopeGraphTimings = [];
|
|
787
|
+
if (effectiveIndexId) {
|
|
788
|
+
if (!UUID_REGEX.test(effectiveIndexId)) {
|
|
789
|
+
return error("Invalid network ID format.");
|
|
790
|
+
}
|
|
791
|
+
const _scopeGraphStart = Date.now();
|
|
792
|
+
const _scopeIndexMembershipTraceEmitter = requestContext.getStore()?.traceEmitter;
|
|
793
|
+
_scopeIndexMembershipTraceEmitter?.({ type: "graph_start", name: "network_membership" });
|
|
794
|
+
const memberResult = await invokeWithAbortSignal(graphs.networkMembership, {
|
|
795
|
+
userId: context.userId,
|
|
796
|
+
networkId: effectiveIndexId,
|
|
797
|
+
operationMode: "read",
|
|
798
|
+
});
|
|
799
|
+
const _scopeIndexMembershipMs = Date.now() - _scopeGraphStart;
|
|
800
|
+
_scopeIndexMembershipTraceEmitter?.({ type: "graph_end", name: "network_membership", durationMs: _scopeIndexMembershipMs });
|
|
801
|
+
_scopeGraphTimings.push({ name: 'network_membership', durationMs: _scopeIndexMembershipMs, agents: [] });
|
|
802
|
+
if (memberResult.error) {
|
|
803
|
+
return error("Network not found or you are not a member.");
|
|
804
|
+
}
|
|
805
|
+
indexScope = [effectiveIndexId];
|
|
806
|
+
}
|
|
807
|
+
else if (context.scopeType === 'network' && context.scopeId) {
|
|
808
|
+
// Scoped chat: discovery is focused-network only. Self-owned writes may
|
|
809
|
+
// include personal networkes, but opportunity visibility must not.
|
|
810
|
+
const scopedDiscoveryIds = deriveDiscoveryNetworkIds({
|
|
811
|
+
memberships: context.userNetworks,
|
|
812
|
+
scopeType: context.scopeType,
|
|
813
|
+
scopeId: context.scopeId,
|
|
814
|
+
});
|
|
815
|
+
indexScope = scopedDiscoveryIds;
|
|
816
|
+
}
|
|
817
|
+
else if (scopedNetworkId) {
|
|
818
|
+
// Scoped context: preserve focused-only discovery using the scope envelope.
|
|
819
|
+
indexScope = [scopedNetworkId];
|
|
820
|
+
}
|
|
821
|
+
else {
|
|
822
|
+
// No scope - use all networks (only in unscoped chat)
|
|
823
|
+
const _scopeGraphStart = Date.now();
|
|
824
|
+
const _scopeIndexTraceEmitter = requestContext.getStore()?.traceEmitter;
|
|
825
|
+
_scopeIndexTraceEmitter?.({ type: "graph_start", name: "index" });
|
|
826
|
+
const indexResult = await invokeWithAbortSignal(graphs.index, {
|
|
827
|
+
userId: context.userId,
|
|
828
|
+
operationMode: "read",
|
|
829
|
+
showAll: true,
|
|
830
|
+
});
|
|
831
|
+
const _scopeIndexMs = Date.now() - _scopeGraphStart;
|
|
832
|
+
_scopeIndexTraceEmitter?.({ type: "graph_end", name: "index", durationMs: _scopeIndexMs });
|
|
833
|
+
_scopeGraphTimings.push({ name: 'index', durationMs: _scopeIndexMs, agents: [] });
|
|
834
|
+
if (indexResult.error) {
|
|
835
|
+
return error(indexResult.error);
|
|
836
|
+
}
|
|
837
|
+
indexScope = (indexResult.readResult?.memberOf || []).map((m) => m.networkId);
|
|
838
|
+
}
|
|
839
|
+
const toolDebugSteps = [
|
|
840
|
+
{ step: "resolve_index_scope", detail: `${indexScope.length} index(es)` },
|
|
841
|
+
];
|
|
842
|
+
if (query.introTargetUserId?.trim() && query.introTargetUserId.trim() === context.userId) {
|
|
843
|
+
return error("You cannot discover introductions for yourself. Try regular discovery instead.");
|
|
844
|
+
}
|
|
845
|
+
const _discoverTraceEmitter = requestContext.getStore()?.traceEmitter;
|
|
846
|
+
const _discoverGraphStart = Date.now();
|
|
847
|
+
_discoverTraceEmitter?.({ type: "graph_start", name: "opportunity" });
|
|
848
|
+
// Chat-driven invocations run under the orchestrator trigger: persist
|
|
849
|
+
// opens at 'negotiating', negotiate fans out with a 60s park window,
|
|
850
|
+
// each accepted draft streams via traceEmitter, and the persist step
|
|
851
|
+
// surfaces already-accepted pairs. Other callers (maintenance, queue
|
|
852
|
+
// workers) still get the 'ambient' default.
|
|
853
|
+
// Orchestrator trigger fires for both web chat (has sessionId) and MCP
|
|
854
|
+
// (isMcp=true, no sessionId). Both are user-initiated discovery that
|
|
855
|
+
// should persist as `negotiating` and flip to `draft` post-finalize via
|
|
856
|
+
// onCandidateResolved. Ambient/cron paths leave both falsy and use the
|
|
857
|
+
// `pending` default.
|
|
858
|
+
const runDiscoveryOrchestrator = !!context.sessionId || !!context.isMcp;
|
|
859
|
+
const result = await runDiscoveryFromQuery({
|
|
860
|
+
opportunityGraph: graphs.opportunity,
|
|
861
|
+
database,
|
|
862
|
+
userId: context.userId,
|
|
863
|
+
query: searchQuery,
|
|
864
|
+
indexScope,
|
|
865
|
+
limit: 20,
|
|
866
|
+
presenter: createOpportunityPresenter(),
|
|
867
|
+
useHomeCardFormat: true,
|
|
868
|
+
triggerIntentId,
|
|
869
|
+
targetUserId: query.targetUserId?.trim() || undefined,
|
|
870
|
+
onBehalfOfUserId: query.introTargetUserId?.trim() || undefined,
|
|
871
|
+
cache,
|
|
872
|
+
// MCP-only: cap the negotiate phase at 20 s so Railway's edge proxy
|
|
873
|
+
// (which 502s the client at ~57 s) never beats the response. The
|
|
874
|
+
// remainder finalizes in the background and is fetched on the
|
|
875
|
+
// user's next list_opportunities call. Removable when IND-274
|
|
876
|
+
// (negotiation conversation continuation) lands.
|
|
877
|
+
...(context.isMcp ? { negotiateTimeoutMs: 20000 } : {}),
|
|
878
|
+
...(context.sessionId ? { chatSessionId: context.sessionId } : {}),
|
|
879
|
+
...(runDiscoveryOrchestrator && { trigger: 'orchestrator' }),
|
|
880
|
+
...(deps.chatSummary && { chatSummary: deps.chatSummary }),
|
|
881
|
+
...(deps.questionGenerator && { questionGenerator: deps.questionGenerator }),
|
|
882
|
+
...(deps.questionerEnqueue && { questionerEnqueue: deps.questionerEnqueue }),
|
|
883
|
+
...(context.scopeType && context.scopeId ? { scopeType: context.scopeType, scopeId: context.scopeId } : {}),
|
|
884
|
+
...(deps.negotiationSummary && { negotiationSummary: deps.negotiationSummary }),
|
|
885
|
+
// Decision questions add an LLM call after the negotiation phase.
|
|
886
|
+
// Capped at QUESTIONER_DISCOVERY_TIMEOUT_MS (12 s default,
|
|
887
|
+
// env-overridable; see opportunity.discover.ts). Aborted calls return
|
|
888
|
+
// no questions but the rest of the discovery payload still ships.
|
|
889
|
+
// For chat sessions, questions are rendered by the frontend via
|
|
890
|
+
// streamed events (Slice 4). For MCP, they drive a sequential
|
|
891
|
+
// elicitation/create flow (Slice 5) — the MCP tool handler awaits the
|
|
892
|
+
// elicitations before returning the tool result. The per-negotiation
|
|
893
|
+
// summarizer is similarly capped at NEGOTIATION_SUMMARY_TIMEOUT_MS
|
|
894
|
+
// (5 s default per negotiation). Gated by QUESTIONER_DISCOVERY_ENABLED
|
|
895
|
+
// (hierarchical: requires QUESTIONER_ENABLED too).
|
|
896
|
+
enableQuestions: isDiscoveryQuestionsEnabled() &&
|
|
897
|
+
(!!context.sessionId || !!context.isMcp),
|
|
898
|
+
});
|
|
899
|
+
// ── Pending question injection ────────────────────────────────────
|
|
900
|
+
// Look up previously-generated questions relevant to this user's
|
|
901
|
+
// discovery context and merge them into the result alongside any
|
|
902
|
+
// inline-generated questions from the current run.
|
|
903
|
+
const pendingQuestionResult = await mergePendingQuestions({
|
|
904
|
+
findPendingQuestions: deps.findPendingQuestions,
|
|
905
|
+
userId: context.userId,
|
|
906
|
+
sourceType: 'discovery',
|
|
907
|
+
...(context.scopeType === 'network' && context.scopeId ? { networkId: context.scopeId } : {}),
|
|
908
|
+
...(contextIntentId ? { scopeType: 'intent', scopeId: contextIntentId } : {}),
|
|
909
|
+
surfacedQuestionIds: new Set(), // Dedup handled at chat.agent level
|
|
910
|
+
});
|
|
911
|
+
const pendingQuestions = pendingQuestionResult.questions;
|
|
912
|
+
const _discoverGraphMs = Date.now() - _discoverGraphStart;
|
|
913
|
+
_discoverTraceEmitter?.({ type: "graph_end", name: "opportunity", durationMs: _discoverGraphMs });
|
|
914
|
+
const _discoverGraphTimings = [
|
|
915
|
+
..._scopeGraphTimings,
|
|
916
|
+
{ name: 'opportunity', durationMs: _discoverGraphMs, agents: [] },
|
|
917
|
+
];
|
|
918
|
+
const allDebugSteps = [
|
|
919
|
+
...toolDebugSteps,
|
|
920
|
+
...(result.debugSteps ?? []),
|
|
921
|
+
];
|
|
922
|
+
// Extract negotiation timing from trace (if negotiation ran)
|
|
923
|
+
const negotiateStep = (result.debugSteps ?? []).find(s => s.step === 'negotiate' && s.data?.durationMs != null);
|
|
924
|
+
const _allGraphTimings = [
|
|
925
|
+
..._discoverGraphTimings,
|
|
926
|
+
...(negotiateStep?.data?.durationMs != null
|
|
927
|
+
? [{ name: 'negotiation', durationMs: negotiateStep.data.durationMs, agents: [] }]
|
|
928
|
+
: []),
|
|
929
|
+
];
|
|
930
|
+
const isIntroducerFlow = !!query.introTargetUserId?.trim();
|
|
931
|
+
if (result.createIntentSuggested && result.suggestedIntentDescription && !isIntroducerFlow) {
|
|
932
|
+
return success({
|
|
933
|
+
found: false,
|
|
934
|
+
count: 0,
|
|
935
|
+
createIntentSuggested: true,
|
|
936
|
+
suggestedIntentDescription: result.suggestedIntentDescription,
|
|
937
|
+
message: "No matching opportunities found. Call create_intent with the suggested description, then discover_opportunities again.",
|
|
938
|
+
summary: "No matches found",
|
|
939
|
+
...(result.pagination ? { pagination: result.pagination } : {}),
|
|
940
|
+
debugSteps: allDebugSteps,
|
|
941
|
+
_graphTimings: _allGraphTimings,
|
|
942
|
+
...(() => {
|
|
943
|
+
const allQ = [...(result.questions ?? []), ...pendingQuestions];
|
|
944
|
+
return allQ.length > 0 ? { questions: allQ } : {};
|
|
945
|
+
})(),
|
|
946
|
+
...(result.discoveryQuestionsDebug ? { _discoveryQuestionsDebug: result.discoveryQuestionsDebug } : {}),
|
|
947
|
+
});
|
|
948
|
+
}
|
|
949
|
+
if (!result.found) {
|
|
950
|
+
return success({
|
|
951
|
+
found: false,
|
|
952
|
+
count: 0,
|
|
953
|
+
message: result.message ?? "No matching opportunities found.",
|
|
954
|
+
summary: "No matches found",
|
|
955
|
+
...(result.pagination ? { pagination: result.pagination } : {}),
|
|
956
|
+
debugSteps: allDebugSteps,
|
|
957
|
+
_graphTimings: _allGraphTimings,
|
|
958
|
+
...(() => {
|
|
959
|
+
const allQ = [...(result.questions ?? []), ...pendingQuestions];
|
|
960
|
+
return allQ.length > 0 ? { questions: allQ } : {};
|
|
961
|
+
})(),
|
|
962
|
+
...(result.discoveryQuestionsDebug ? { _discoveryQuestionsDebug: result.discoveryQuestionsDebug } : {}),
|
|
963
|
+
});
|
|
964
|
+
}
|
|
965
|
+
// Found but only existing connections (no new opportunities created)
|
|
966
|
+
const forMention = result.existingConnectionsForMention ?? result.existingConnections ?? [];
|
|
967
|
+
if ((result.opportunities?.length ?? 0) === 0 && forMention.length > 0) {
|
|
968
|
+
return success({
|
|
969
|
+
found: true,
|
|
970
|
+
count: 0,
|
|
971
|
+
message: result.message ??
|
|
972
|
+
"No new opportunities created; you already have a connection with: " +
|
|
973
|
+
forMention.map((c) => c.name + (c.status ? " (" + c.status + ")" : "")).join(", ") +
|
|
974
|
+
". View on your home page.",
|
|
975
|
+
existingConnections: result.existingConnections,
|
|
976
|
+
summary: "No new matches (existing connections only)",
|
|
977
|
+
debugSteps: allDebugSteps,
|
|
978
|
+
_graphTimings: _allGraphTimings,
|
|
979
|
+
...(() => {
|
|
980
|
+
const allQ = [...(result.questions ?? []), ...pendingQuestions];
|
|
981
|
+
return allQ.length > 0 ? { questions: allQ } : {};
|
|
982
|
+
})(),
|
|
983
|
+
...(result.discoveryQuestionsDebug ? { _discoveryQuestionsDebug: result.discoveryQuestionsDebug } : {}),
|
|
984
|
+
});
|
|
985
|
+
}
|
|
986
|
+
const lifecycleFinalization = await finalizeMcpDiscoveryLifecycle({
|
|
987
|
+
isMcp: context.isMcp === true,
|
|
988
|
+
candidates: result.opportunities ?? [],
|
|
989
|
+
existingConnections: result.existingConnections,
|
|
990
|
+
existingConnectionsForMention: result.existingConnectionsForMention,
|
|
991
|
+
alreadyAcceptedPairs: result.alreadyAcceptedPairs,
|
|
992
|
+
pagination: result.pagination,
|
|
993
|
+
isIntroducerFlow,
|
|
994
|
+
displayLimit: CHAT_DISPLAY_LIMIT,
|
|
995
|
+
readOpportunitiesByIds: (ids) => database.getOpportunitiesByIds(ids),
|
|
996
|
+
warn: (message, data) => discoverOpportunitiesLog.warn(message, data),
|
|
997
|
+
projectSafeCard: (opp) => ({
|
|
998
|
+
opportunityId: opp.opportunityId,
|
|
999
|
+
userId: opp.userId,
|
|
1000
|
+
name: opp.name,
|
|
1001
|
+
avatar: opp.avatar,
|
|
1002
|
+
mainText: getSafePresentationOrSkip(opp, { counterpartName: opp.name })?.summary ?? "",
|
|
1003
|
+
cta: opp.homeCardPresentation?.suggestedAction,
|
|
1004
|
+
headline: opp.homeCardPresentation?.headline,
|
|
1005
|
+
primaryActionLabel: opp.homeCardPresentation?.primaryActionLabel,
|
|
1006
|
+
secondaryActionLabel: opp.homeCardPresentation?.secondaryActionLabel,
|
|
1007
|
+
mutualIntentsLabel: opp.homeCardPresentation?.mutualIntentsLabel,
|
|
1008
|
+
narratorChip: opp.narratorChip,
|
|
1009
|
+
viewerRole: opp.viewerRole,
|
|
1010
|
+
isGhost: opp.isGhost ?? false,
|
|
1011
|
+
score: opp.score,
|
|
1012
|
+
status: opp.status,
|
|
1013
|
+
...(opp.secondParty && { secondParty: opp.secondParty }),
|
|
1014
|
+
}),
|
|
1015
|
+
});
|
|
1016
|
+
const { displayedCards, displayedCandidates } = lifecycleFinalization;
|
|
1017
|
+
if (context.isMcp && deps.mintConnectLink) {
|
|
1018
|
+
const mintConnectLink = deps.mintConnectLink;
|
|
1019
|
+
await Promise.all(displayedCards.map(async (card, idx) => {
|
|
1020
|
+
const source = displayedCandidates[idx];
|
|
1021
|
+
await attachActionableLinks(card, {
|
|
1022
|
+
viewerId: context.userId,
|
|
1023
|
+
viewerApproved: source?.viewerApproved,
|
|
1024
|
+
viewerActedAt: source?.viewerActedAt,
|
|
1025
|
+
counterpartUserId: source?.userId ?? card.userId,
|
|
1026
|
+
mintConnectLink,
|
|
1027
|
+
frontendUrl: deps.frontendUrl,
|
|
1028
|
+
preferredSurface: context.clientSurface,
|
|
1029
|
+
});
|
|
1030
|
+
}));
|
|
1031
|
+
}
|
|
1032
|
+
let message = buildOpportunityPresentation(displayedCards, {
|
|
1033
|
+
isMcp: context.isMcp ?? false,
|
|
1034
|
+
leadIn: `Found ${displayedCards.length} potential connection(s).`,
|
|
1035
|
+
});
|
|
1036
|
+
message = lifecycleFinalization.composeMessage(message);
|
|
1037
|
+
return success({
|
|
1038
|
+
found: true,
|
|
1039
|
+
count: displayedCards.length,
|
|
1040
|
+
message,
|
|
1041
|
+
summary: `Found ${displayedCards.length} match(es)`,
|
|
1042
|
+
...(result.existingConnections?.length ? { existingConnections: result.existingConnections } : {}),
|
|
1043
|
+
...(result.pagination ? { pagination: result.pagination } : {}),
|
|
1044
|
+
debugSteps: allDebugSteps,
|
|
1045
|
+
// Distinct from `createIntentSuggested` (no-results path) intentionally:
|
|
1046
|
+
// `handleCreateIntentCallback` in chat.agent.ts auto-creates for that key.
|
|
1047
|
+
// This flag is for the results-found path where the agent must ask the user first.
|
|
1048
|
+
...(searchQuery && !query.targetUserId && !isIntroducerFlow
|
|
1049
|
+
? {
|
|
1050
|
+
suggestIntentCreationForVisibility: true,
|
|
1051
|
+
suggestedIntentDescription: searchQuery,
|
|
1052
|
+
}
|
|
1053
|
+
: {}),
|
|
1054
|
+
...(() => {
|
|
1055
|
+
const allQ = [...(result.questions ?? []), ...pendingQuestions];
|
|
1056
|
+
return allQ.length > 0 ? { questions: allQ } : {};
|
|
1057
|
+
})(),
|
|
1058
|
+
...(result.discoveryQuestionsDebug ? { _discoveryQuestionsDebug: result.discoveryQuestionsDebug } : {}),
|
|
1059
|
+
_graphTimings: _allGraphTimings,
|
|
1060
|
+
});
|
|
1061
|
+
},
|
|
1062
|
+
});
|
|
1063
|
+
const getDiscoveryRun = defineTool({
|
|
1064
|
+
name: "get_discovery_run",
|
|
1065
|
+
description: "Checks the status of an async discovery run started by discover_opportunities in MCP contexts. " +
|
|
1066
|
+
"Poll this tool with the discoveryRunId roughly every 5 seconds until status is succeeded, failed, or cancelled. " +
|
|
1067
|
+
"While status is queued or running, keep polling THIS tool — do NOT call discover_opportunities again (that does not speed anything up and returns the same run). " +
|
|
1068
|
+
"When succeeded, the result field contains the same discovery payload that discover_opportunities would have returned synchronously.",
|
|
1069
|
+
querySchema: z.object({
|
|
1070
|
+
discoveryRunId: z.string().describe("Discovery run ID returned by discover_opportunities."),
|
|
1071
|
+
}),
|
|
1072
|
+
handler: async ({ context, query }) => {
|
|
1073
|
+
if (!deps.discoveryRuns) {
|
|
1074
|
+
return error("Async discovery runs are not available in this context.");
|
|
1075
|
+
}
|
|
1076
|
+
const run = await deps.discoveryRuns.get(query.discoveryRunId, context.userId);
|
|
1077
|
+
if (!run)
|
|
1078
|
+
return error("Discovery run not found.");
|
|
1079
|
+
return success({
|
|
1080
|
+
discoveryRunId: run.id,
|
|
1081
|
+
status: run.status,
|
|
1082
|
+
progress: run.progress ?? null,
|
|
1083
|
+
result: run.result ?? null,
|
|
1084
|
+
error: run.error ?? null,
|
|
1085
|
+
cancelRequestedAt: run.cancelRequestedAt?.toISOString?.() ?? null,
|
|
1086
|
+
createdAt: run.createdAt.toISOString(),
|
|
1087
|
+
startedAt: run.startedAt?.toISOString?.() ?? null,
|
|
1088
|
+
completedAt: run.completedAt?.toISOString?.() ?? null,
|
|
1089
|
+
});
|
|
1090
|
+
},
|
|
1091
|
+
});
|
|
1092
|
+
const cancelDiscoveryRun = defineTool({
|
|
1093
|
+
name: "cancel_discovery_run",
|
|
1094
|
+
description: "Requests cancellation for an async discovery run. If the queued job has not started, it is removed and marked cancelled. " +
|
|
1095
|
+
"If already running, the worker observes cancellation and stops at the next cancellation check.",
|
|
1096
|
+
querySchema: z.object({
|
|
1097
|
+
discoveryRunId: z.string().describe("Discovery run ID returned by discover_opportunities."),
|
|
1098
|
+
}),
|
|
1099
|
+
handler: async ({ context, query }) => {
|
|
1100
|
+
if (!deps.discoveryRuns || !deps.discoveryRunQueue) {
|
|
1101
|
+
return error("Async discovery runs are not available in this context.");
|
|
1102
|
+
}
|
|
1103
|
+
const existing = await deps.discoveryRuns.get(query.discoveryRunId, context.userId);
|
|
1104
|
+
if (!existing)
|
|
1105
|
+
return error("Discovery run not found.");
|
|
1106
|
+
if (!["queued", "running"].includes(existing.status)) {
|
|
1107
|
+
return success({
|
|
1108
|
+
discoveryRunId: existing.id,
|
|
1109
|
+
status: existing.status,
|
|
1110
|
+
cancelled: existing.status === "cancelled",
|
|
1111
|
+
message: `Discovery run is already ${existing.status}.`,
|
|
1112
|
+
});
|
|
1113
|
+
}
|
|
1114
|
+
const run = await deps.discoveryRuns.requestCancel(query.discoveryRunId, context.userId);
|
|
1115
|
+
if (!run)
|
|
1116
|
+
return error("Discovery run is no longer cancellable.");
|
|
1117
|
+
const removed = await deps.discoveryRunQueue.cancel(run.id);
|
|
1118
|
+
if (removed) {
|
|
1119
|
+
await deps.discoveryRuns.markCancelled(run.id, "cancelled before worker start");
|
|
1120
|
+
}
|
|
1121
|
+
const updated = await deps.discoveryRuns.get(run.id, context.userId);
|
|
1122
|
+
const status = updated?.status ?? (removed ? "cancelled" : run.status);
|
|
1123
|
+
const message = removed
|
|
1124
|
+
? "Discovery run cancelled."
|
|
1125
|
+
: status === "queued"
|
|
1126
|
+
? "Cancellation requested while the discovery run is still queued. It will be skipped or cancelled before work starts."
|
|
1127
|
+
: status === "running"
|
|
1128
|
+
? "Cancellation requested. The running worker will stop at the next cancellation check."
|
|
1129
|
+
: `Cancellation requested. Discovery run is now ${status}.`;
|
|
1130
|
+
return success({
|
|
1131
|
+
discoveryRunId: run.id,
|
|
1132
|
+
status,
|
|
1133
|
+
cancelled: removed || status === "cancelled",
|
|
1134
|
+
message,
|
|
1135
|
+
});
|
|
1136
|
+
},
|
|
1137
|
+
});
|
|
1138
|
+
const listOpportunities = defineTool({
|
|
1139
|
+
name: "list_opportunities",
|
|
1140
|
+
description: "Lists the authenticated user's actionable opportunities (discovered connections). Returns opportunity cards ready for display.\n\n" +
|
|
1141
|
+
"**What are opportunities?** Matches between users whose intents complement each other within shared networkes. " +
|
|
1142
|
+
"Each opportunity has a status: draft (not yet sent), pending (sent, awaiting response), accepted, rejected, or expired.\n\n" +
|
|
1143
|
+
"**What this returns:** Only draft and pending opportunities — the ones the user can still act on. " +
|
|
1144
|
+
"Accepted, rejected, and expired ones are not surfaced through this tool.\n\n" +
|
|
1145
|
+
"**When to use:** When the user wants to see their current matches or review what's waiting for their response.\n\n" +
|
|
1146
|
+
"**Returns:** Up to 3 opportunity code blocks (interactive cards) with counterpart name, match reasoning, confidence score, " +
|
|
1147
|
+
"and current status. Use update_opportunity to act on them (send, accept, reject).",
|
|
1148
|
+
querySchema: z.object({
|
|
1149
|
+
networkId: z
|
|
1150
|
+
.string()
|
|
1151
|
+
.optional()
|
|
1152
|
+
.describe("Network UUID to filter opportunities to a specific community. Get from read_networks. Defaults to the scoped network in network-scoped chats. Omit to see opportunities across all networks."),
|
|
1153
|
+
scopeType: z
|
|
1154
|
+
.enum(['intent'])
|
|
1155
|
+
.optional()
|
|
1156
|
+
.describe("Optional selected scope type. Use 'intent' to narrow listed opportunities to a selected intent."),
|
|
1157
|
+
scopeId: z
|
|
1158
|
+
.string()
|
|
1159
|
+
.optional()
|
|
1160
|
+
.describe("Selected intent UUID when scopeType is 'intent'. Ignored only when absent."),
|
|
1161
|
+
includeDigestMarkers: z
|
|
1162
|
+
.boolean()
|
|
1163
|
+
.optional()
|
|
1164
|
+
.describe("Internal scheduled-digest mode only. When true, includes hidden delivery markers so the digest send pass can confirm only edited-in opportunities."),
|
|
1165
|
+
}),
|
|
1166
|
+
handler: async ({ context, query }) => {
|
|
1167
|
+
const scopedNetworkId = focusedNetworkId(context) ?? context.networkId?.trim();
|
|
1168
|
+
const scopedIndexLabel = focusedNetworkLabel(context);
|
|
1169
|
+
// Strict scope enforcement: when chat is network-scoped, only allow that index
|
|
1170
|
+
if (scopedNetworkId &&
|
|
1171
|
+
query.networkId?.trim() &&
|
|
1172
|
+
query.networkId.trim() !== scopedNetworkId) {
|
|
1173
|
+
return error(`This chat is scoped to ${scopedIndexLabel}. You can only list opportunities from this community.`);
|
|
1174
|
+
}
|
|
1175
|
+
const effectiveIndexId = (scopedNetworkId || query.networkId?.trim()) ?? undefined;
|
|
1176
|
+
if (effectiveIndexId && !UUID_REGEX.test(effectiveIndexId)) {
|
|
1177
|
+
return error("Invalid network ID format.");
|
|
1178
|
+
}
|
|
1179
|
+
const contextIntentId = focusedIntentId(context);
|
|
1180
|
+
const rawScopeId = query.scopeId?.trim() || undefined;
|
|
1181
|
+
if (query.scopeType === 'intent' && !rawScopeId) {
|
|
1182
|
+
return error("scopeId required when scopeType is intent.");
|
|
1183
|
+
}
|
|
1184
|
+
if (!query.scopeType && rawScopeId) {
|
|
1185
|
+
return error("scopeType=intent required when scopeId is provided.");
|
|
1186
|
+
}
|
|
1187
|
+
if (rawScopeId && !UUID_REGEX.test(rawScopeId)) {
|
|
1188
|
+
return error("Invalid scope ID format.");
|
|
1189
|
+
}
|
|
1190
|
+
if (contextIntentId && rawScopeId && contextIntentId !== rawScopeId) {
|
|
1191
|
+
return error("This chat is scoped to a different intent.");
|
|
1192
|
+
}
|
|
1193
|
+
const effectiveIntentScope = contextIntentId
|
|
1194
|
+
? { scopeType: 'intent', scopeId: contextIntentId }
|
|
1195
|
+
: query.scopeType === 'intent' && rawScopeId
|
|
1196
|
+
? { scopeType: 'intent', scopeId: rawScopeId }
|
|
1197
|
+
: {};
|
|
1198
|
+
const selection = await selectOpportunityFeed({
|
|
1199
|
+
reader: database,
|
|
1200
|
+
deliveryLedger: deps.deliveryLedger,
|
|
1201
|
+
viewerId: context.userId,
|
|
1202
|
+
networkId: effectiveIndexId,
|
|
1203
|
+
intentScope: effectiveIntentScope,
|
|
1204
|
+
isMcp: context.isMcp === true,
|
|
1205
|
+
includeDigestMarkers: query.includeDigestMarkers,
|
|
1206
|
+
displayLimit: CHAT_DISPLAY_LIMIT,
|
|
1207
|
+
warn: (message, data) => logger.warn(message, data),
|
|
1208
|
+
});
|
|
1209
|
+
const { opportunities, dedupedCount, skippedIds, redeliveryIds, fetchedCount, isDigestMode } = selection;
|
|
1210
|
+
const buildListDebugSteps = () => {
|
|
1211
|
+
const steps = [];
|
|
1212
|
+
if (skippedIds.length > 0) {
|
|
1213
|
+
steps.push({
|
|
1214
|
+
step: "opportunity_display_skips",
|
|
1215
|
+
detail: `${skippedIds.length} opportunity card(s) couldn't be displayed`,
|
|
1216
|
+
data: {
|
|
1217
|
+
skippedCount: skippedIds.length,
|
|
1218
|
+
totalOpportunities: fetchedCount,
|
|
1219
|
+
skippedOpportunityIds: skippedIds,
|
|
1220
|
+
},
|
|
1221
|
+
});
|
|
1222
|
+
}
|
|
1223
|
+
return steps;
|
|
1224
|
+
};
|
|
1225
|
+
if (!opportunities || opportunities.length === 0) {
|
|
1226
|
+
if (skippedIds.length > 0) {
|
|
1227
|
+
const listDebugSteps = buildListDebugSteps();
|
|
1228
|
+
return success({
|
|
1229
|
+
found: false,
|
|
1230
|
+
count: 0,
|
|
1231
|
+
summary: "Some opportunities couldn't be displayed",
|
|
1232
|
+
message: "I found opportunities, but couldn't render them. Please try again.",
|
|
1233
|
+
...(listDebugSteps.length ? { debugSteps: listDebugSteps } : {}),
|
|
1234
|
+
});
|
|
1235
|
+
}
|
|
1236
|
+
// Digest mode: distinguish "everything was already shown" from "nothing
|
|
1237
|
+
// exists" so the brief omits the people section instead of prompting
|
|
1238
|
+
// the user to run discovery.
|
|
1239
|
+
if (isDigestMode && dedupedCount > 0) {
|
|
1240
|
+
return success({
|
|
1241
|
+
found: false,
|
|
1242
|
+
count: 0,
|
|
1243
|
+
summary: "No new opportunities to show",
|
|
1244
|
+
message: "No new opportunities today — everything actionable has already been shown recently. Omit the people section from the digest.",
|
|
1245
|
+
});
|
|
1246
|
+
}
|
|
1247
|
+
return success({
|
|
1248
|
+
found: false,
|
|
1249
|
+
count: 0,
|
|
1250
|
+
summary: "No opportunities yet",
|
|
1251
|
+
message: "You have no opportunities yet. Use discover_opportunities to find connections.",
|
|
1252
|
+
});
|
|
1253
|
+
}
|
|
1254
|
+
// Batch-fetch profiles and users for all counterpart and introducer userIds to avoid N+1
|
|
1255
|
+
const counterpartUserIds = new Set();
|
|
1256
|
+
const introducerUserIds = new Set();
|
|
1257
|
+
for (const opp of opportunities) {
|
|
1258
|
+
const counterpartActor = opp.actors.find((a) => a.userId !== context.userId && a.role !== "introducer");
|
|
1259
|
+
if (counterpartActor?.userId)
|
|
1260
|
+
counterpartUserIds.add(counterpartActor.userId);
|
|
1261
|
+
const introducerActor = opp.actors.find((a) => a.role === "introducer" && a.userId !== context.userId);
|
|
1262
|
+
if (introducerActor?.userId)
|
|
1263
|
+
introducerUserIds.add(introducerActor.userId);
|
|
1264
|
+
}
|
|
1265
|
+
const allUserIds = [
|
|
1266
|
+
...new Set([...counterpartUserIds, ...introducerUserIds]),
|
|
1267
|
+
];
|
|
1268
|
+
const [profileResults, userResults] = await Promise.all([
|
|
1269
|
+
Promise.all(allUserIds.map((id) => database.getProfile(id))),
|
|
1270
|
+
Promise.all(allUserIds.map((id) => database.getUser(id))),
|
|
1271
|
+
]);
|
|
1272
|
+
const profileMap = new Map();
|
|
1273
|
+
const userMap = new Map();
|
|
1274
|
+
allUserIds.forEach((userId, i) => {
|
|
1275
|
+
const profile = profileResults[i] ?? null;
|
|
1276
|
+
const user = userResults[i] ?? null;
|
|
1277
|
+
if (profile)
|
|
1278
|
+
profileMap.set(userId, profile);
|
|
1279
|
+
if (user)
|
|
1280
|
+
userMap.set(userId, user);
|
|
1281
|
+
});
|
|
1282
|
+
const cardDataList = [];
|
|
1283
|
+
const seenOpportunityIds = new Set();
|
|
1284
|
+
if (isDigestMode) {
|
|
1285
|
+
// ── Digest mode: use LLM presenter for rich, second-person card text ──
|
|
1286
|
+
const presenter = createOpportunityPresenter();
|
|
1287
|
+
const presenterDb = database;
|
|
1288
|
+
const PRESENTER_CONCURRENCY = 6;
|
|
1289
|
+
for (let i = 0; i < opportunities.length; i += PRESENTER_CONCURRENCY) {
|
|
1290
|
+
const chunk = opportunities.slice(i, i + PRESENTER_CONCURRENCY);
|
|
1291
|
+
const chunkCards = await Promise.all(chunk.map(async (opp) => {
|
|
1292
|
+
if (seenOpportunityIds.has(opp.id))
|
|
1293
|
+
return null;
|
|
1294
|
+
seenOpportunityIds.add(opp.id);
|
|
1295
|
+
try {
|
|
1296
|
+
const counterpartActor = opp.actors.find((a) => a.userId !== context.userId && a.role !== "introducer");
|
|
1297
|
+
const counterpartUserId = counterpartActor?.userId;
|
|
1298
|
+
if (!counterpartUserId)
|
|
1299
|
+
return null;
|
|
1300
|
+
const viewerIsIntroducerHere = opp.actors.some((a) => a.role === "introducer" && a.userId === context.userId);
|
|
1301
|
+
const secondPartyActorForHeadline = viewerIsIntroducerHere
|
|
1302
|
+
? opp.actors.find((a) => a.userId !== context.userId &&
|
|
1303
|
+
a.userId !== counterpartUserId &&
|
|
1304
|
+
a.role !== "introducer")
|
|
1305
|
+
: undefined;
|
|
1306
|
+
const introducerActor = opp.actors.find((a) => a.role === "introducer" && a.userId !== context.userId);
|
|
1307
|
+
const createdByName = opp.detection.createdByName;
|
|
1308
|
+
const counterpartUser = userMap.get(counterpartUserId) ?? null;
|
|
1309
|
+
const counterpartName = profileMap.get(counterpartUserId)?.identity?.name ??
|
|
1310
|
+
counterpartUser?.name ??
|
|
1311
|
+
"Someone";
|
|
1312
|
+
const introducerName = createdByName ??
|
|
1313
|
+
(introducerActor
|
|
1314
|
+
? (profileMap.get(introducerActor.userId)?.identity?.name ?? null)
|
|
1315
|
+
: null);
|
|
1316
|
+
const introducerUser = introducerActor
|
|
1317
|
+
? userMap.get(introducerActor.userId) ?? null
|
|
1318
|
+
: null;
|
|
1319
|
+
const secondPartyUser = secondPartyActorForHeadline
|
|
1320
|
+
? userMap.get(secondPartyActorForHeadline.userId) ?? null
|
|
1321
|
+
: null;
|
|
1322
|
+
const secondPartyNameForHeadline = secondPartyActorForHeadline
|
|
1323
|
+
? (profileMap.get(secondPartyActorForHeadline.userId)?.identity?.name ??
|
|
1324
|
+
secondPartyUser?.name ??
|
|
1325
|
+
undefined)
|
|
1326
|
+
: undefined;
|
|
1327
|
+
const viewerActor = opp.actors.find((a) => a.userId === context.userId);
|
|
1328
|
+
const viewerRole = viewerActor?.role ?? "party";
|
|
1329
|
+
const isCounterpartGhost = counterpartUser?.isGhost ?? false;
|
|
1330
|
+
try {
|
|
1331
|
+
// Load the negotiation context alongside presenter context so
|
|
1332
|
+
// the digest copy can explain *why* the opportunity surfaced
|
|
1333
|
+
// (EDG-50) — the presenter grounds `digestSummary` in concrete
|
|
1334
|
+
// negotiation turns when this is present. The same context
|
|
1335
|
+
// yields the conversationId for the negotiation-trace link
|
|
1336
|
+
// (EDG-51).
|
|
1337
|
+
const [ctx, negotiationContext] = await Promise.all([
|
|
1338
|
+
gatherOpportunityPresenterContext(presenterDb, opp, context.userId, counterpartUserId),
|
|
1339
|
+
loadNegotiationContext(deps.negotiationDatabase, opp.id, opp.status),
|
|
1340
|
+
]);
|
|
1341
|
+
const presentation = await presenter.presentHomeCard({
|
|
1342
|
+
...ctx,
|
|
1343
|
+
opportunityStatus: opp.status,
|
|
1344
|
+
...(negotiationContext ? { negotiationContext } : {}),
|
|
1345
|
+
});
|
|
1346
|
+
const negotiationUrl = buildNegotiationUrl(negotiationContext?.conversationId, deps.frontendUrl);
|
|
1347
|
+
// Build narrator chip from presenter output
|
|
1348
|
+
let narratorChip;
|
|
1349
|
+
const introducerIsCounterpart = introducerActor && counterpartActor && introducerActor.userId === counterpartActor.userId;
|
|
1350
|
+
if (introducerActor && introducerActor.userId !== context.userId && !introducerIsCounterpart) {
|
|
1351
|
+
const narratorName = introducerName?.trim() || "Someone";
|
|
1352
|
+
narratorChip = {
|
|
1353
|
+
name: narratorName,
|
|
1354
|
+
text: stripLeadingNarratorName(presentation.narratorRemark, narratorName),
|
|
1355
|
+
avatar: introducerUser?.avatar ?? null,
|
|
1356
|
+
userId: introducerActor.userId,
|
|
1357
|
+
};
|
|
1358
|
+
}
|
|
1359
|
+
else if (introducerActor?.userId === context.userId) {
|
|
1360
|
+
narratorChip = { name: "You", text: presentation.narratorRemark, userId: context.userId };
|
|
1361
|
+
}
|
|
1362
|
+
else {
|
|
1363
|
+
narratorChip = { name: "Index", text: presentation.narratorRemark };
|
|
1364
|
+
}
|
|
1365
|
+
const card = {
|
|
1366
|
+
opportunityId: opp.id,
|
|
1367
|
+
userId: counterpartUserId,
|
|
1368
|
+
name: counterpartName,
|
|
1369
|
+
avatar: counterpartUser?.avatar ?? null,
|
|
1370
|
+
mainText: stripUuids(presentation.personalizedSummary),
|
|
1371
|
+
digestSummary: stripUuids(presentation.digestSummary),
|
|
1372
|
+
// Deep-link to the negotiation trace that produced this card
|
|
1373
|
+
// (EDG-51). Only present when a negotiation conversation exists.
|
|
1374
|
+
...(negotiationUrl ? { negotiationUrl } : {}),
|
|
1375
|
+
cta: presentation.suggestedAction,
|
|
1376
|
+
headline: viewerIsIntroducerHere && secondPartyNameForHeadline
|
|
1377
|
+
? `${counterpartName} → ${secondPartyNameForHeadline}`
|
|
1378
|
+
: presentation.headline,
|
|
1379
|
+
primaryActionLabel: getPrimaryActionLabel(viewerRole),
|
|
1380
|
+
secondaryActionLabel: SECONDARY_ACTION_LABEL,
|
|
1381
|
+
mutualIntentsLabel: presentation.mutualIntentsLabel,
|
|
1382
|
+
narratorChip,
|
|
1383
|
+
viewerRole,
|
|
1384
|
+
score: typeof opp.interpretation?.confidence === "number"
|
|
1385
|
+
? opp.interpretation.confidence
|
|
1386
|
+
: undefined,
|
|
1387
|
+
status: opp.status,
|
|
1388
|
+
isGhost: isCounterpartGhost,
|
|
1389
|
+
...(redeliveryIds.has(opp.id) ? { redelivery: true } : {}),
|
|
1390
|
+
...(viewerIsIntroducerHere && secondPartyNameForHeadline
|
|
1391
|
+
? {
|
|
1392
|
+
secondParty: {
|
|
1393
|
+
name: secondPartyNameForHeadline,
|
|
1394
|
+
...(secondPartyUser?.avatar != null ? { avatar: secondPartyUser.avatar } : {}),
|
|
1395
|
+
...(secondPartyActorForHeadline?.userId ? { userId: secondPartyActorForHeadline.userId } : {}),
|
|
1396
|
+
},
|
|
1397
|
+
}
|
|
1398
|
+
: {}),
|
|
1399
|
+
};
|
|
1400
|
+
// Attach actionable links for MCP callers
|
|
1401
|
+
if (context.isMcp && deps.mintConnectLink) {
|
|
1402
|
+
const viewerApproved = viewerActor?.role === "introducer" ? viewerActor.approved === true : undefined;
|
|
1403
|
+
await attachActionableLinks(card, {
|
|
1404
|
+
viewerId: context.userId,
|
|
1405
|
+
viewerApproved,
|
|
1406
|
+
viewerActedAt: viewerActor?.actedAt ?? null,
|
|
1407
|
+
counterpartUserId,
|
|
1408
|
+
mintConnectLink: deps.mintConnectLink,
|
|
1409
|
+
frontendUrl: deps.frontendUrl,
|
|
1410
|
+
preferredSurface: context.clientSurface,
|
|
1411
|
+
});
|
|
1412
|
+
}
|
|
1413
|
+
return card;
|
|
1414
|
+
}
|
|
1415
|
+
catch (presenterErr) {
|
|
1416
|
+
logger.warn("LLM presenter failed for list_opportunities digest card, skipping raw fallback", {
|
|
1417
|
+
opportunityId: opp.id,
|
|
1418
|
+
err: presenterErr,
|
|
1419
|
+
});
|
|
1420
|
+
// Scheduled digests should only surface OpportunityPresenter-rendered
|
|
1421
|
+
// copy. The minimal fallback reuses evaluator reasoning, which can
|
|
1422
|
+
// contain raw narrator phrasing (for example "The discoverer...")
|
|
1423
|
+
// and is not suitable for AgentVillage morning briefs.
|
|
1424
|
+
skippedIds.push(opp.id);
|
|
1425
|
+
return null;
|
|
1426
|
+
}
|
|
1427
|
+
}
|
|
1428
|
+
catch (err) {
|
|
1429
|
+
logger.warn("Skipping opportunity that failed to build card", {
|
|
1430
|
+
opportunityId: opp.id,
|
|
1431
|
+
err,
|
|
1432
|
+
});
|
|
1433
|
+
skippedIds.push(opp.id);
|
|
1434
|
+
return null;
|
|
1435
|
+
}
|
|
1436
|
+
}));
|
|
1437
|
+
for (const card of chunkCards) {
|
|
1438
|
+
if (card)
|
|
1439
|
+
cardDataList.push(card);
|
|
1440
|
+
}
|
|
1441
|
+
}
|
|
1442
|
+
}
|
|
1443
|
+
else {
|
|
1444
|
+
// ── Chat/list mode: use OpportunityPresenter for user-facing card copy ──
|
|
1445
|
+
const presenter = createOpportunityPresenter();
|
|
1446
|
+
const presenterDb = database;
|
|
1447
|
+
const PRESENTER_CONCURRENCY = 6;
|
|
1448
|
+
for (let i = 0; i < opportunities.length; i += PRESENTER_CONCURRENCY) {
|
|
1449
|
+
const chunk = opportunities.slice(i, i + PRESENTER_CONCURRENCY);
|
|
1450
|
+
const chunkCards = await Promise.all(chunk.map(async (opp) => {
|
|
1451
|
+
if (seenOpportunityIds.has(opp.id))
|
|
1452
|
+
return null;
|
|
1453
|
+
seenOpportunityIds.add(opp.id);
|
|
1454
|
+
try {
|
|
1455
|
+
const counterpartActor = opp.actors.find((a) => a.userId !== context.userId && a.role !== "introducer");
|
|
1456
|
+
const counterpartUserId = counterpartActor?.userId;
|
|
1457
|
+
if (!counterpartUserId)
|
|
1458
|
+
return null;
|
|
1459
|
+
const viewerIsIntroducerHere = opp.actors.some((a) => a.role === "introducer" && a.userId === context.userId);
|
|
1460
|
+
const secondPartyActorForHeadline = viewerIsIntroducerHere
|
|
1461
|
+
? opp.actors.find((a) => a.userId !== context.userId &&
|
|
1462
|
+
a.userId !== counterpartUserId &&
|
|
1463
|
+
a.role !== "introducer")
|
|
1464
|
+
: undefined;
|
|
1465
|
+
const introducerActor = opp.actors.find((a) => a.role === "introducer" && a.userId !== context.userId);
|
|
1466
|
+
const createdByName = opp.detection.createdByName;
|
|
1467
|
+
const counterpartProfile = profileMap.get(counterpartUserId) ?? null;
|
|
1468
|
+
const counterpartUser = userMap.get(counterpartUserId) ?? null;
|
|
1469
|
+
const introducerProfile = introducerActor && !createdByName
|
|
1470
|
+
? profileMap.get(introducerActor.userId) ?? null
|
|
1471
|
+
: null;
|
|
1472
|
+
const counterpartName = counterpartProfile?.identity?.name ??
|
|
1473
|
+
counterpartUser?.name ??
|
|
1474
|
+
"Someone";
|
|
1475
|
+
const introducerName = createdByName ??
|
|
1476
|
+
(introducerActor ? introducerProfile?.identity?.name ?? null : null);
|
|
1477
|
+
const introducerUser = introducerActor
|
|
1478
|
+
? userMap.get(introducerActor.userId) ?? null
|
|
1479
|
+
: null;
|
|
1480
|
+
const secondPartyUser = secondPartyActorForHeadline
|
|
1481
|
+
? userMap.get(secondPartyActorForHeadline.userId) ?? null
|
|
1482
|
+
: null;
|
|
1483
|
+
const secondPartyNameForHeadline = secondPartyActorForHeadline
|
|
1484
|
+
? (profileMap.get(secondPartyActorForHeadline.userId)?.identity?.name ??
|
|
1485
|
+
secondPartyUser?.name ??
|
|
1486
|
+
undefined)
|
|
1487
|
+
: undefined;
|
|
1488
|
+
const viewerActor = opp.actors.find((a) => a.userId === context.userId);
|
|
1489
|
+
const viewerRole = viewerActor?.role ?? "party";
|
|
1490
|
+
const isCounterpartGhost = counterpartUser?.isGhost ?? false;
|
|
1491
|
+
const [ctx, negotiationContext] = await Promise.all([
|
|
1492
|
+
gatherOpportunityPresenterContext(presenterDb, opp, context.userId, counterpartUserId),
|
|
1493
|
+
loadNegotiationContext(deps.negotiationDatabase, opp.id, opp.status),
|
|
1494
|
+
]);
|
|
1495
|
+
const presentation = await presenter.presentHomeCard({
|
|
1496
|
+
...ctx,
|
|
1497
|
+
opportunityStatus: opp.status,
|
|
1498
|
+
...(negotiationContext ? { negotiationContext } : {}),
|
|
1499
|
+
});
|
|
1500
|
+
let narratorChip;
|
|
1501
|
+
const introducerIsCounterpart = introducerActor && counterpartActor && introducerActor.userId === counterpartActor.userId;
|
|
1502
|
+
if (introducerActor && introducerActor.userId !== context.userId && !introducerIsCounterpart) {
|
|
1503
|
+
const narratorName = introducerName?.trim() || "Someone";
|
|
1504
|
+
narratorChip = {
|
|
1505
|
+
name: narratorName,
|
|
1506
|
+
text: stripLeadingNarratorName(presentation.narratorRemark, narratorName),
|
|
1507
|
+
avatar: introducerUser?.avatar ?? null,
|
|
1508
|
+
userId: introducerActor.userId,
|
|
1509
|
+
};
|
|
1510
|
+
}
|
|
1511
|
+
else if (introducerActor?.userId === context.userId) {
|
|
1512
|
+
narratorChip = { name: "You", text: presentation.narratorRemark, userId: context.userId };
|
|
1513
|
+
}
|
|
1514
|
+
else {
|
|
1515
|
+
narratorChip = { name: "Index", text: presentation.narratorRemark };
|
|
1516
|
+
}
|
|
1517
|
+
const cardData = {
|
|
1518
|
+
opportunityId: opp.id,
|
|
1519
|
+
userId: counterpartUserId,
|
|
1520
|
+
name: counterpartName,
|
|
1521
|
+
avatar: counterpartUser?.avatar ?? null,
|
|
1522
|
+
mainText: stripUuids(presentation.personalizedSummary),
|
|
1523
|
+
cta: presentation.suggestedAction,
|
|
1524
|
+
headline: viewerIsIntroducerHere && secondPartyNameForHeadline
|
|
1525
|
+
? `${counterpartName} → ${secondPartyNameForHeadline}`
|
|
1526
|
+
: presentation.headline,
|
|
1527
|
+
primaryActionLabel: getPrimaryActionLabel(viewerRole),
|
|
1528
|
+
secondaryActionLabel: SECONDARY_ACTION_LABEL,
|
|
1529
|
+
mutualIntentsLabel: presentation.mutualIntentsLabel,
|
|
1530
|
+
narratorChip,
|
|
1531
|
+
viewerRole,
|
|
1532
|
+
score: typeof opp.interpretation?.confidence === "number"
|
|
1533
|
+
? opp.interpretation.confidence
|
|
1534
|
+
: undefined,
|
|
1535
|
+
status: opp.status,
|
|
1536
|
+
isGhost: isCounterpartGhost,
|
|
1537
|
+
...(viewerIsIntroducerHere && secondPartyNameForHeadline
|
|
1538
|
+
? {
|
|
1539
|
+
secondParty: {
|
|
1540
|
+
name: secondPartyNameForHeadline,
|
|
1541
|
+
...(secondPartyUser?.avatar != null ? { avatar: secondPartyUser.avatar } : {}),
|
|
1542
|
+
...(secondPartyActorForHeadline?.userId ? { userId: secondPartyActorForHeadline.userId } : {}),
|
|
1543
|
+
},
|
|
1544
|
+
}
|
|
1545
|
+
: {}),
|
|
1546
|
+
};
|
|
1547
|
+
// For MCP callers (e.g. Edge Claw), mint a connect token and attach
|
|
1548
|
+
// acceptUrl + profileUrl when the (status, viewerRole) is actionable
|
|
1549
|
+
// for the viewer. Non-actionable combos (sender-on-draft,
|
|
1550
|
+
// pending-on-introducer-waiting, rejected, etc.) deliberately get
|
|
1551
|
+
// no link — the LLM would otherwise hallucinate `/api/.../connect`
|
|
1552
|
+
// URLs from the exposed opportunityId.
|
|
1553
|
+
if (context.isMcp && deps.mintConnectLink) {
|
|
1554
|
+
const viewerApproved = viewerActor?.role === "introducer" ? viewerActor.approved === true : undefined;
|
|
1555
|
+
await attachActionableLinks(cardData, {
|
|
1556
|
+
viewerId: context.userId,
|
|
1557
|
+
viewerApproved,
|
|
1558
|
+
viewerActedAt: viewerActor?.actedAt ?? null,
|
|
1559
|
+
counterpartUserId,
|
|
1560
|
+
mintConnectLink: deps.mintConnectLink,
|
|
1561
|
+
frontendUrl: deps.frontendUrl,
|
|
1562
|
+
preferredSurface: context.clientSurface,
|
|
1563
|
+
});
|
|
1564
|
+
}
|
|
1565
|
+
return cardData;
|
|
1566
|
+
}
|
|
1567
|
+
catch (err) {
|
|
1568
|
+
logger.warn("Skipping opportunity that failed to build presenter card", {
|
|
1569
|
+
opportunityId: opp.id,
|
|
1570
|
+
err,
|
|
1571
|
+
});
|
|
1572
|
+
skippedIds.push(opp.id);
|
|
1573
|
+
return null;
|
|
1574
|
+
}
|
|
1575
|
+
}));
|
|
1576
|
+
for (const card of chunkCards) {
|
|
1577
|
+
if (card)
|
|
1578
|
+
cardDataList.push(card);
|
|
1579
|
+
}
|
|
1580
|
+
}
|
|
1581
|
+
}
|
|
1582
|
+
const listDebugSteps = buildListDebugSteps();
|
|
1583
|
+
if (cardDataList.length === 0) {
|
|
1584
|
+
if (skippedIds.length > 0) {
|
|
1585
|
+
return success({
|
|
1586
|
+
found: false,
|
|
1587
|
+
count: 0,
|
|
1588
|
+
summary: "Some opportunities couldn't be displayed",
|
|
1589
|
+
message: "I found opportunities, but couldn't render them. Please try again.",
|
|
1590
|
+
...(listDebugSteps.length ? { debugSteps: listDebugSteps } : {}),
|
|
1591
|
+
});
|
|
1592
|
+
}
|
|
1593
|
+
return success({
|
|
1594
|
+
found: false,
|
|
1595
|
+
count: 0,
|
|
1596
|
+
summary: "No opportunities yet",
|
|
1597
|
+
message: "You have no opportunities yet. Use discover_opportunities to find connections.",
|
|
1598
|
+
});
|
|
1599
|
+
}
|
|
1600
|
+
return success({
|
|
1601
|
+
found: true,
|
|
1602
|
+
count: cardDataList.length,
|
|
1603
|
+
summary: `You have ${cardDataList.length} opportunity(ies)`,
|
|
1604
|
+
message: buildOpportunityPresentation(cardDataList, {
|
|
1605
|
+
isMcp: context.isMcp ?? false,
|
|
1606
|
+
leadIn: `You have ${cardDataList.length} opportunity(ies).`,
|
|
1607
|
+
includeDigestMarkers: context.isMcp === true && query.includeDigestMarkers === true,
|
|
1608
|
+
}),
|
|
1609
|
+
...(listDebugSteps.length ? { debugSteps: listDebugSteps } : {}),
|
|
1610
|
+
});
|
|
1611
|
+
},
|
|
1612
|
+
});
|
|
1613
|
+
const updateOpportunity = defineTool({
|
|
1614
|
+
name: "update_opportunity",
|
|
1615
|
+
description: "Updates an opportunity's status, advancing it through the connection lifecycle.\n\n" +
|
|
1616
|
+
"**Status transitions:**\n" +
|
|
1617
|
+
"- `pending`: Sends a draft opportunity to the other party. They'll be notified and can accept or reject. " +
|
|
1618
|
+
"This is the primary action after discover_opportunities returns a draft.\n" +
|
|
1619
|
+
"- `accepted`: Accept a received opportunity — opens a direct conversation between both parties. Returns a conversationId to surface to the user.\n" +
|
|
1620
|
+
"- `rejected`: Decline a received opportunity.\n" +
|
|
1621
|
+
"- `expired`: Mark as expired (typically done by the system after timeout).\n\n" +
|
|
1622
|
+
"**When to use:** After discover_opportunities or list_opportunities returns opportunity cards. " +
|
|
1623
|
+
"The user clicks 'Send' (pending), 'Accept', or 'Reject' on the card, and the agent calls this tool. " +
|
|
1624
|
+
"An accepted transition may first return a non-success uptake advisory with preparatory questions. Surface those questions, then retry with all returned question ids in acknowledgedUptakeQuestionIds; acknowledgement confirms presentation, not an answer.\n\n" +
|
|
1625
|
+
"**Returns:** Confirmation with the new status and notification details (who was notified), or a structured uptake advisory without mutation.",
|
|
1626
|
+
querySchema: z.object({
|
|
1627
|
+
opportunityId: z
|
|
1628
|
+
.string()
|
|
1629
|
+
.describe("The UUID of the opportunity to update. Get from discover_opportunities or list_opportunities results."),
|
|
1630
|
+
status: z
|
|
1631
|
+
.enum(["pending", "accepted", "rejected", "expired"])
|
|
1632
|
+
.describe("New status: 'pending' = send the draft to the other party, 'accepted' = accept the connection, " +
|
|
1633
|
+
"'rejected' = decline, 'expired' = mark as timed out."),
|
|
1634
|
+
acknowledgedUptakeQuestionIds: z
|
|
1635
|
+
.array(z.string().min(1))
|
|
1636
|
+
.optional()
|
|
1637
|
+
.describe("On an acknowledged retry after an uptake advisory, include every question id returned by that advisory."),
|
|
1638
|
+
scopeType: z
|
|
1639
|
+
.enum(['intent'])
|
|
1640
|
+
.optional()
|
|
1641
|
+
.describe("Optional selected scope type. Use 'intent' to require this opportunity to belong to a selected intent."),
|
|
1642
|
+
scopeId: z
|
|
1643
|
+
.string()
|
|
1644
|
+
.optional()
|
|
1645
|
+
.describe("Selected intent UUID when scopeType is 'intent'. Must match the chat's focused intent when one exists."),
|
|
1646
|
+
}),
|
|
1647
|
+
handler: async ({ context, query }) => {
|
|
1648
|
+
const opportunityId = query.opportunityId?.trim();
|
|
1649
|
+
if (!opportunityId || !UUID_REGEX.test(opportunityId)) {
|
|
1650
|
+
return error("Valid opportunityId required.");
|
|
1651
|
+
}
|
|
1652
|
+
const contextIntentId = focusedIntentId(context);
|
|
1653
|
+
const rawScopeId = query.scopeId?.trim() || undefined;
|
|
1654
|
+
if (query.scopeType === 'intent' && !rawScopeId) {
|
|
1655
|
+
return error("scopeId required when scopeType is intent.");
|
|
1656
|
+
}
|
|
1657
|
+
if (!query.scopeType && rawScopeId) {
|
|
1658
|
+
return error("scopeType=intent required when scopeId is provided.");
|
|
1659
|
+
}
|
|
1660
|
+
if (rawScopeId && !UUID_REGEX.test(rawScopeId)) {
|
|
1661
|
+
return error("Invalid scope ID format.");
|
|
1662
|
+
}
|
|
1663
|
+
if (contextIntentId && rawScopeId && contextIntentId !== rawScopeId) {
|
|
1664
|
+
return error("This chat is scoped to a different intent.");
|
|
1665
|
+
}
|
|
1666
|
+
const effectiveIntentScope = contextIntentId
|
|
1667
|
+
? { scopeType: 'intent', scopeId: contextIntentId }
|
|
1668
|
+
: query.scopeType === 'intent' && rawScopeId
|
|
1669
|
+
? { scopeType: 'intent', scopeId: rawScopeId }
|
|
1670
|
+
: {};
|
|
1671
|
+
const scopedNetworkId = focusedNetworkId(context) ?? context.networkId?.trim();
|
|
1672
|
+
const admission = await admitOpportunityUpdate(systemDb, {
|
|
1673
|
+
opportunityId,
|
|
1674
|
+
viewerId: context.userId,
|
|
1675
|
+
scopedNetworkId,
|
|
1676
|
+
selectedIntentScope: effectiveIntentScope,
|
|
1677
|
+
});
|
|
1678
|
+
if (admission.kind === 'denied')
|
|
1679
|
+
return error(admission.message);
|
|
1680
|
+
// The caller actor's own network is the exact question lookup boundary,
|
|
1681
|
+
// even for an otherwise unscoped request. A focused network may only be
|
|
1682
|
+
// equal to this after the guard above.
|
|
1683
|
+
// Unscoped callers query all of their exact opportunity questions; a
|
|
1684
|
+
// network-scoped caller is clamped to the bound network. Selecting the
|
|
1685
|
+
// first duplicate actor row would miss a valid question on another
|
|
1686
|
+
// shared network.
|
|
1687
|
+
const uptakeNetworkId = scopedNetworkId;
|
|
1688
|
+
// Soft uptake interlock: only acceptance is advisory-gated. All existing
|
|
1689
|
+
// actor/scope/privacy guards run first so the question lookup cannot be
|
|
1690
|
+
// used to probe opportunities or networks the caller cannot access.
|
|
1691
|
+
if (query.status === "accepted" && isUptakeGuardEnabled() && deps.findPendingQuestions) {
|
|
1692
|
+
try {
|
|
1693
|
+
const pending = await deps.findPendingQuestions(context.userId, {
|
|
1694
|
+
sourceType: "opportunity",
|
|
1695
|
+
sourceId: opportunityId,
|
|
1696
|
+
modes: ["negotiation"],
|
|
1697
|
+
purpose: "uptake",
|
|
1698
|
+
...(uptakeNetworkId ? { networkId: uptakeNetworkId } : {}),
|
|
1699
|
+
});
|
|
1700
|
+
// Defense in depth if a host overlooks one or more filters. Actor
|
|
1701
|
+
// internals are checked here and never serialized into the advisory.
|
|
1702
|
+
const exactPending = pending.filter((question) => {
|
|
1703
|
+
if (question.sourceType !== "opportunity" ||
|
|
1704
|
+
question.sourceId !== opportunityId ||
|
|
1705
|
+
question.mode !== "negotiation" ||
|
|
1706
|
+
question.purpose !== "uptake") {
|
|
1707
|
+
return false;
|
|
1708
|
+
}
|
|
1709
|
+
if (!question.actors?.some((actor) => actor.userId === context.userId))
|
|
1710
|
+
return false;
|
|
1711
|
+
if (uptakeNetworkId && !question.actors.some((actor) => actor.userId === context.userId && actor.networkId === uptakeNetworkId)) {
|
|
1712
|
+
return false;
|
|
1713
|
+
}
|
|
1714
|
+
return true;
|
|
1715
|
+
});
|
|
1716
|
+
const acknowledged = new Set(query.acknowledgedUptakeQuestionIds ?? []);
|
|
1717
|
+
if (exactPending.some((question) => !acknowledged.has(question.id))) {
|
|
1718
|
+
return uptakeAdvisory(opportunityId, exactPending.map(publicUptakeQuestion));
|
|
1719
|
+
}
|
|
1720
|
+
}
|
|
1721
|
+
catch (err) {
|
|
1722
|
+
logger.warn("update_opportunity: uptake question lookup failed open", {
|
|
1723
|
+
opportunityId,
|
|
1724
|
+
userId: context.userId,
|
|
1725
|
+
error: err instanceof Error ? err.message : String(err),
|
|
1726
|
+
});
|
|
1727
|
+
deps.reportToolError?.(err, {
|
|
1728
|
+
subsystem: "opportunity",
|
|
1729
|
+
operation: "opportunity.uptake_lookup",
|
|
1730
|
+
toolName: "update_opportunity",
|
|
1731
|
+
userId: context.userId,
|
|
1732
|
+
});
|
|
1733
|
+
}
|
|
1734
|
+
}
|
|
1735
|
+
const isSend = query.status === "pending";
|
|
1736
|
+
const _updateGraphStart = Date.now();
|
|
1737
|
+
const _updateTraceEmitter = requestContext.getStore()?.traceEmitter;
|
|
1738
|
+
_updateTraceEmitter?.({ type: "graph_start", name: "opportunity" });
|
|
1739
|
+
const result = await invokeWithAbortSignal(graphs.opportunity, {
|
|
1740
|
+
userId: context.userId,
|
|
1741
|
+
operationMode: isSend ? "send" : "update",
|
|
1742
|
+
opportunityId: query.opportunityId,
|
|
1743
|
+
...(isSend ? {} : { newStatus: query.status }),
|
|
1744
|
+
});
|
|
1745
|
+
const _updateGraphMs = Date.now() - _updateGraphStart;
|
|
1746
|
+
_updateTraceEmitter?.({ type: "graph_end", name: "opportunity", durationMs: _updateGraphMs });
|
|
1747
|
+
if (result.mutationResult) {
|
|
1748
|
+
if (result.mutationResult.success) {
|
|
1749
|
+
return success({
|
|
1750
|
+
opportunityId: result.mutationResult.opportunityId,
|
|
1751
|
+
status: query.status,
|
|
1752
|
+
message: result.mutationResult.message,
|
|
1753
|
+
...(result.mutationResult.notified && { notified: result.mutationResult.notified }),
|
|
1754
|
+
...(result.mutationResult.conversationId && {
|
|
1755
|
+
conversationId: result.mutationResult.conversationId,
|
|
1756
|
+
}),
|
|
1757
|
+
_graphTimings: [{ name: 'opportunity', durationMs: _updateGraphMs, agents: result.agentTimings ?? [] }],
|
|
1758
|
+
});
|
|
1759
|
+
}
|
|
1760
|
+
return error(result.mutationResult.error || "Failed to update opportunity.");
|
|
1761
|
+
}
|
|
1762
|
+
return error("Failed to update opportunity.");
|
|
1763
|
+
},
|
|
1764
|
+
});
|
|
1765
|
+
const confirmOpportunityDelivery = defineTool({
|
|
1766
|
+
name: "confirm_opportunity_delivery",
|
|
1767
|
+
description: "Marks an opportunity as delivered to the user via the OpenClaw channel. " +
|
|
1768
|
+
"Call this for each opportunity you decide to surface, BEFORE including it in your delivery message. " +
|
|
1769
|
+
"The 'trigger' argument records which dispatch path produced this delivery: " +
|
|
1770
|
+
"'ambient' for real-time critical alerts (target ≤3/day), 'digest' for the daily sweep, " +
|
|
1771
|
+
"'accepted' for accepted-opportunity notifications to the counterparty. " +
|
|
1772
|
+
"Idempotent — safe to call even if the opportunity was already confirmed.",
|
|
1773
|
+
querySchema: z.object({
|
|
1774
|
+
opportunityId: z
|
|
1775
|
+
.string()
|
|
1776
|
+
.describe("The UUID of the opportunity to mark as delivered."),
|
|
1777
|
+
trigger: z
|
|
1778
|
+
.enum(['ambient', 'digest', 'accepted'])
|
|
1779
|
+
.describe("Which dispatch path produced this delivery. Use 'ambient' if the dispatch prompt says you are in the ambient pass; use 'digest' if it says you are in the daily digest; use 'accepted' for accepted-opportunity notifications to the counterparty."),
|
|
1780
|
+
}),
|
|
1781
|
+
handler: async ({ context, query }) => {
|
|
1782
|
+
if (!context.isMcp || !context.agentId) {
|
|
1783
|
+
return confirmDeliveryError("unauthenticated", false, "confirm_opportunity_delivery is only available to authenticated agent MCP contexts.");
|
|
1784
|
+
}
|
|
1785
|
+
if (!deps.deliveryLedger) {
|
|
1786
|
+
return confirmDeliveryError("ledger_unavailable", false, "Delivery ledger not available in this context.");
|
|
1787
|
+
}
|
|
1788
|
+
if (!UUID_REGEX.test(query.opportunityId)) {
|
|
1789
|
+
return confirmDeliveryError("invalid_opportunity_id", false, "Invalid opportunity ID format.");
|
|
1790
|
+
}
|
|
1791
|
+
try {
|
|
1792
|
+
const result = await deps.deliveryLedger.confirmOpportunityDelivery({
|
|
1793
|
+
opportunityId: query.opportunityId,
|
|
1794
|
+
userId: context.userId,
|
|
1795
|
+
agentId: context.agentId,
|
|
1796
|
+
trigger: query.trigger,
|
|
1797
|
+
});
|
|
1798
|
+
return success({ status: result });
|
|
1799
|
+
}
|
|
1800
|
+
catch (err) {
|
|
1801
|
+
const reason = err instanceof Error ? err.message : String(err);
|
|
1802
|
+
// Permanent failures — the caller MUST NOT retry. Retrying a deleted
|
|
1803
|
+
// opportunity or an unauthorized actor never succeeds and only spams
|
|
1804
|
+
// the ledger / MCP transport.
|
|
1805
|
+
if (reason === 'opportunity_not_found') {
|
|
1806
|
+
logger.warn('confirm_opportunity_delivery: opportunity not found', {
|
|
1807
|
+
opportunityId: query.opportunityId,
|
|
1808
|
+
});
|
|
1809
|
+
return confirmDeliveryError('opportunity_not_found', false, 'Opportunity not found — it may have been deleted. Do not retry.');
|
|
1810
|
+
}
|
|
1811
|
+
if (reason === 'not_authorized') {
|
|
1812
|
+
logger.warn('confirm_opportunity_delivery: caller is not an actor', {
|
|
1813
|
+
opportunityId: query.opportunityId,
|
|
1814
|
+
userId: context.userId,
|
|
1815
|
+
});
|
|
1816
|
+
return confirmDeliveryError('not_authorized', false, 'You are not an actor on this opportunity. Do not retry.');
|
|
1817
|
+
}
|
|
1818
|
+
// Unknown / transient (e.g. DB connectivity) — safe to retry. The
|
|
1819
|
+
// ledger write is idempotent, so a retry that races a prior success
|
|
1820
|
+
// returns 'already_delivered' rather than a duplicate row.
|
|
1821
|
+
logger.error('Failed to confirm opportunity delivery', { err });
|
|
1822
|
+
return confirmDeliveryError('confirm_failed', true, 'Failed to confirm opportunity delivery — transient error, safe to retry.');
|
|
1823
|
+
}
|
|
1824
|
+
},
|
|
1825
|
+
});
|
|
1826
|
+
return [
|
|
1827
|
+
discoverOpportunities,
|
|
1828
|
+
getDiscoveryRun,
|
|
1829
|
+
cancelDiscoveryRun,
|
|
1830
|
+
listOpportunities,
|
|
1831
|
+
updateOpportunity,
|
|
1832
|
+
confirmOpportunityDelivery,
|
|
1833
|
+
];
|
|
1834
|
+
}
|