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