@indexnetwork/protocol 4.3.4-rc.309.1 → 4.3.4-rc.311.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 (130) hide show
  1. package/IMPLEMENTATION.md +2 -2
  2. package/dist/chat/chat.agent.d.ts +1 -1
  3. package/dist/chat/chat.agent.js +1 -1
  4. package/dist/chat/chat.agent.js.map +1 -1
  5. package/dist/chat/chat.graph.d.ts +8 -8
  6. package/dist/chat/chat.prompt.js +30 -30
  7. package/dist/chat/chat.prompt.js.map +1 -1
  8. package/dist/chat/chat.prompt.modules.js +8 -8
  9. package/dist/chat/chat.prompt.modules.js.map +1 -1
  10. package/dist/chat/chat.state.d.ts +5 -4
  11. package/dist/chat/chat.state.d.ts.map +1 -1
  12. package/dist/chat/chat.state.js +2 -2
  13. package/dist/chat/chat.state.js.map +1 -1
  14. package/dist/chat/chat.streamer.d.ts +3 -2
  15. package/dist/chat/chat.streamer.d.ts.map +1 -1
  16. package/dist/chat/chat.streamer.js +2 -2
  17. package/dist/chat/chat.streamer.js.map +1 -1
  18. package/dist/chat/tests/chat.graph.mocks.d.ts +1 -1
  19. package/dist/chat/tests/chat.graph.mocks.js +1 -1
  20. package/dist/chat/tests/chat.graph.mocks.js.map +1 -1
  21. package/dist/contact/contact.tools.js +6 -6
  22. package/dist/contact/contact.tools.js.map +1 -1
  23. package/dist/enrichment/enrichment.tools.js +7 -7
  24. package/dist/enrichment/enrichment.tools.js.map +1 -1
  25. package/dist/integration/integration.tools.js +1 -1
  26. package/dist/integration/integration.tools.js.map +1 -1
  27. package/dist/intent/intent.graph.d.ts +5 -5
  28. package/dist/intent/intent.graph.d.ts.map +1 -1
  29. package/dist/intent/intent.graph.js +9 -9
  30. package/dist/intent/intent.graph.js.map +1 -1
  31. package/dist/intent/intent.indexer.d.ts +1 -1
  32. package/dist/intent/intent.indexer.js +4 -4
  33. package/dist/intent/intent.indexer.js.map +1 -1
  34. package/dist/intent/intent.state.d.ts +9 -8
  35. package/dist/intent/intent.state.d.ts.map +1 -1
  36. package/dist/intent/intent.state.js +7 -7
  37. package/dist/intent/intent.state.js.map +1 -1
  38. package/dist/intent/intent.tools.js +26 -26
  39. package/dist/intent/intent.tools.js.map +1 -1
  40. package/dist/maintenance/maintenance.graph.d.ts +2 -2
  41. package/dist/maintenance/maintenance.graph.d.ts.map +1 -1
  42. package/dist/maintenance/maintenance.graph.js.map +1 -1
  43. package/dist/mcp/mcp.server.d.ts +2 -2
  44. package/dist/mcp/mcp.server.js +4 -4
  45. package/dist/mcp/mcp.server.js.map +1 -1
  46. package/dist/network/indexer/indexer.graph.d.ts +2 -2
  47. package/dist/network/indexer/indexer.graph.d.ts.map +1 -1
  48. package/dist/network/indexer/indexer.graph.js +13 -13
  49. package/dist/network/indexer/indexer.graph.js.map +1 -1
  50. package/dist/network/indexer/indexer.state.d.ts +3 -3
  51. package/dist/network/indexer/indexer.state.d.ts.map +1 -1
  52. package/dist/network/indexer/indexer.state.js +4 -4
  53. package/dist/network/indexer/indexer.state.js.map +1 -1
  54. package/dist/network/membership/membership.graph.d.ts +1 -1
  55. package/dist/network/membership/membership.graph.js +21 -21
  56. package/dist/network/membership/membership.graph.js.map +1 -1
  57. package/dist/network/membership/membership.state.d.ts +2 -2
  58. package/dist/network/membership/membership.state.js +2 -2
  59. package/dist/network/membership/membership.state.js.map +1 -1
  60. package/dist/network/network.graph.d.ts +1 -1
  61. package/dist/network/network.graph.d.ts.map +1 -1
  62. package/dist/network/network.graph.js +13 -13
  63. package/dist/network/network.graph.js.map +1 -1
  64. package/dist/network/network.state.d.ts +3 -3
  65. package/dist/network/network.state.d.ts.map +1 -1
  66. package/dist/network/network.state.js +3 -3
  67. package/dist/network/network.state.js.map +1 -1
  68. package/dist/network/network.tools.d.ts.map +1 -1
  69. package/dist/network/network.tools.js +95 -107
  70. package/dist/network/network.tools.js.map +1 -1
  71. package/dist/opportunity/feed/feed.graph.d.ts +10 -0
  72. package/dist/opportunity/feed/feed.graph.d.ts.map +1 -1
  73. package/dist/opportunity/feed/feed.graph.js +4 -0
  74. package/dist/opportunity/feed/feed.graph.js.map +1 -1
  75. package/dist/opportunity/feed/feed.state.d.ts +2 -0
  76. package/dist/opportunity/feed/feed.state.d.ts.map +1 -1
  77. package/dist/opportunity/feed/feed.state.js +8 -0
  78. package/dist/opportunity/feed/feed.state.js.map +1 -1
  79. package/dist/opportunity/opportunity.discover.d.ts +1 -1
  80. package/dist/opportunity/opportunity.discover.d.ts.map +1 -1
  81. package/dist/opportunity/opportunity.discover.js +1 -1
  82. package/dist/opportunity/opportunity.discover.js.map +1 -1
  83. package/dist/opportunity/opportunity.graph.d.ts +6 -6
  84. package/dist/opportunity/opportunity.graph.js +10 -10
  85. package/dist/opportunity/opportunity.graph.js.map +1 -1
  86. package/dist/opportunity/opportunity.introducer.d.ts +3 -3
  87. package/dist/opportunity/opportunity.introducer.d.ts.map +1 -1
  88. package/dist/opportunity/opportunity.introducer.js +2 -2
  89. package/dist/opportunity/opportunity.introducer.js.map +1 -1
  90. package/dist/opportunity/opportunity.state.d.ts +2 -2
  91. package/dist/opportunity/opportunity.state.js +1 -1
  92. package/dist/opportunity/opportunity.state.js.map +1 -1
  93. package/dist/opportunity/opportunity.tools.d.ts.map +1 -1
  94. package/dist/opportunity/opportunity.tools.js +89 -22
  95. package/dist/opportunity/opportunity.tools.js.map +1 -1
  96. package/dist/premise/premise.graph.d.ts +4 -4
  97. package/dist/premise/premise.indexer.d.ts +1 -1
  98. package/dist/premise/premise.indexer.js +5 -5
  99. package/dist/premise/premise.indexer.js.map +1 -1
  100. package/dist/premise/premise.state.d.ts +2 -1
  101. package/dist/premise/premise.state.d.ts.map +1 -1
  102. package/dist/questioner/questioner.tools.d.ts.map +1 -1
  103. package/dist/questioner/questioner.tools.js +22 -9
  104. package/dist/questioner/questioner.tools.js.map +1 -1
  105. package/dist/shared/agent/tool.factory.d.ts +1 -1
  106. package/dist/shared/agent/tool.factory.js +1 -1
  107. package/dist/shared/agent/tool.factory.js.map +1 -1
  108. package/dist/shared/agent/tool.helpers.d.ts +14 -11
  109. package/dist/shared/agent/tool.helpers.d.ts.map +1 -1
  110. package/dist/shared/agent/tool.helpers.js +2 -2
  111. package/dist/shared/agent/tool.helpers.js.map +1 -1
  112. package/dist/shared/agent/tool.scope.d.ts +7 -1
  113. package/dist/shared/agent/tool.scope.d.ts.map +1 -1
  114. package/dist/shared/agent/tool.scope.js +11 -1
  115. package/dist/shared/agent/tool.scope.js.map +1 -1
  116. package/dist/shared/agent/utility.tools.js +20 -20
  117. package/dist/shared/agent/utility.tools.js.map +1 -1
  118. package/dist/shared/hyde/hyde.graph.d.ts +6 -6
  119. package/dist/shared/hyde/hyde.state.d.ts +2 -2
  120. package/dist/shared/interfaces/database.interface.d.ts +62 -59
  121. package/dist/shared/interfaces/database.interface.d.ts.map +1 -1
  122. package/dist/shared/interfaces/database.interface.js.map +1 -1
  123. package/dist/shared/interfaces/embedder.interface.d.ts +2 -2
  124. package/dist/shared/interfaces/embedder.interface.d.ts.map +1 -1
  125. package/dist/shared/interfaces/embedder.interface.js.map +1 -1
  126. package/dist/shared/interfaces/questioner.interface.d.ts +3 -0
  127. package/dist/shared/interfaces/questioner.interface.d.ts.map +1 -1
  128. package/dist/shared/interfaces/questioner.interface.js.map +1 -1
  129. package/dist/shared/schemas/network-assignment.schema.d.ts +2 -2
  130. package/package.json +1 -1
@@ -2,7 +2,7 @@
2
2
  * Introducer Discovery: proactive discovery of connector-flow opportunities
3
3
  * between a user's contacts.
4
4
  *
5
- * Selects top-N contacts from the user's personal index and runs scoped
5
+ * Selects top-N contacts from the user's personal network and runs scoped
6
6
  * HyDE discovery for each, creating latent introducer opportunities that
7
7
  * the user (as introducer) must approve before parties see them.
8
8
  */
@@ -22,9 +22,9 @@ export interface ContactWithIntents {
22
22
  }
23
23
  /** Database methods needed for introducer discovery contact selection. */
24
24
  export interface IntroducerDiscoveryDatabase {
25
- /** Get the user's personal index ID. */
25
+ /** Get the user's personal network ID. */
26
26
  getPersonalIndexId(userId: string): Promise<string | null>;
27
- /** Get contacts from a personal index with their intent freshness data. */
27
+ /** Get contacts from a personal network with their intent freshness data. */
28
28
  getContactsWithIntentFreshness(personalIndexId: string, ownerId: string, limit: number): Promise<ContactWithIntents[]>;
29
29
  }
30
30
  /** Queue interface for enqueuing introducer discovery jobs. */
@@ -1 +1 @@
1
- {"version":3,"file":"opportunity.introducer.d.ts","sourceRoot":"/","sources":["opportunity/opportunity.introducer.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAMH,0DAA0D;AAC1D,eAAO,MAAM,sBAAsB,IAAI,CAAC;AAExC,mDAAmD;AACnD,eAAO,MAAM,0BAA0B,IAAI,CAAC;AAE5C,sEAAsE;AACtE,eAAO,MAAM,2BAA2B,EAAG,sBAA+B,CAAC;AAE3E,oFAAoF;AACpF,MAAM,WAAW,kBAAkB;IACjC,MAAM,EAAE,MAAM,CAAC;IACf,mEAAmE;IACnE,cAAc,EAAE,MAAM,GAAG,IAAI,CAAC;IAC9B,iDAAiD;IACjD,WAAW,EAAE,MAAM,CAAC;CACrB;AAED,0EAA0E;AAC1E,MAAM,WAAW,2BAA2B;IAC1C,wCAAwC;IACxC,kBAAkB,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAAC;IAC3D,2EAA2E;IAC3E,8BAA8B,CAC5B,eAAe,EAAE,MAAM,EACvB,OAAO,EAAE,MAAM,EACf,KAAK,EAAE,MAAM,GACZ,OAAO,CAAC,kBAAkB,EAAE,CAAC,CAAC;CAClC;AAED,+DAA+D;AAC/D,MAAM,WAAW,wBAAwB;IACvC,MAAM,CACJ,IAAI,EAAE;QAAE,QAAQ,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAC;QAAC,QAAQ,CAAC,EAAE,MAAM,EAAE,CAAC;QAAC,aAAa,CAAC,EAAE,MAAM,CAAA;KAAE,EACvF,OAAO,CAAC,EAAE;QAAE,QAAQ,CAAC,EAAE,MAAM,CAAC;QAAC,KAAK,CAAC,EAAE,MAAM,CAAA;KAAE,GAC9C,OAAO,CAAC,OAAO,CAAC,CAAC;CACrB;AAED,qDAAqD;AACrD,MAAM,WAAW,yBAAyB;IACxC,iBAAiB,EAAE,MAAM,CAAC;IAC1B,YAAY,EAAE,MAAM,CAAC;IACrB,aAAa,CAAC,EAAE,MAAM,CAAC;CACxB;AAED;;;;;;;;GAQG;AACH,wBAAsB,0BAA0B,CAC9C,QAAQ,EAAE,2BAA2B,EACrC,MAAM,EAAE,MAAM,EACd,KAAK,GAAE,MAA+B,GACrC,OAAO,CAAC,kBAAkB,EAAE,CAAC,CAgB/B;AAED;;;;;;;;GAQG;AACH,wBAAgB,4BAA4B,CAC1C,kBAAkB,EAAE,MAAM,EAC1B,mBAAmB,GAAE,MAAU,GAC9B,OAAO,CAET;AAED;;;;;;;;GAQG;AACH,wBAAsB,sBAAsB,CAC1C,QAAQ,EAAE,2BAA2B,EACrC,KAAK,EAAE,wBAAwB,EAC/B,MAAM,EAAE,MAAM,GACb,OAAO,CAAC,yBAAyB,CAAC,CAoDpC"}
1
+ {"version":3,"file":"opportunity.introducer.d.ts","sourceRoot":"/","sources":["opportunity/opportunity.introducer.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAMH,0DAA0D;AAC1D,eAAO,MAAM,sBAAsB,IAAI,CAAC;AAExC,mDAAmD;AACnD,eAAO,MAAM,0BAA0B,IAAI,CAAC;AAE5C,sEAAsE;AACtE,eAAO,MAAM,2BAA2B,EAAG,sBAA+B,CAAC;AAE3E,oFAAoF;AACpF,MAAM,WAAW,kBAAkB;IACjC,MAAM,EAAE,MAAM,CAAC;IACf,mEAAmE;IACnE,cAAc,EAAE,MAAM,GAAG,IAAI,CAAC;IAC9B,iDAAiD;IACjD,WAAW,EAAE,MAAM,CAAC;CACrB;AAED,0EAA0E;AAC1E,MAAM,WAAW,2BAA2B;IAC1C,0CAA0C;IAC1C,kBAAkB,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAAC;IAC3D,6EAA6E;IAC7E,8BAA8B,CAC5B,eAAe,EAAE,MAAM,EACvB,OAAO,EAAE,MAAM,EACf,KAAK,EAAE,MAAM,GACZ,OAAO,CAAC,kBAAkB,EAAE,CAAC,CAAC;CAClC;AAED,+DAA+D;AAC/D,MAAM,WAAW,wBAAwB;IACvC,MAAM,CACJ,IAAI,EAAE;QAAE,QAAQ,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAC;QAAC,QAAQ,CAAC,EAAE,MAAM,EAAE,CAAC;QAAC,aAAa,CAAC,EAAE,MAAM,CAAA;KAAE,EACvF,OAAO,CAAC,EAAE;QAAE,QAAQ,CAAC,EAAE,MAAM,CAAC;QAAC,KAAK,CAAC,EAAE,MAAM,CAAA;KAAE,GAC9C,OAAO,CAAC,OAAO,CAAC,CAAC;CACrB;AAED,qDAAqD;AACrD,MAAM,WAAW,yBAAyB;IACxC,iBAAiB,EAAE,MAAM,CAAC;IAC1B,YAAY,EAAE,MAAM,CAAC;IACrB,aAAa,CAAC,EAAE,MAAM,CAAC;CACxB;AAED;;;;;;;;GAQG;AACH,wBAAsB,0BAA0B,CAC9C,QAAQ,EAAE,2BAA2B,EACrC,MAAM,EAAE,MAAM,EACd,KAAK,GAAE,MAA+B,GACrC,OAAO,CAAC,kBAAkB,EAAE,CAAC,CAgB/B;AAED;;;;;;;;GAQG;AACH,wBAAgB,4BAA4B,CAC1C,kBAAkB,EAAE,MAAM,EAC1B,mBAAmB,GAAE,MAAU,GAC9B,OAAO,CAET;AAED;;;;;;;;GAQG;AACH,wBAAsB,sBAAsB,CAC1C,QAAQ,EAAE,2BAA2B,EACrC,KAAK,EAAE,wBAAwB,EAC/B,MAAM,EAAE,MAAM,GACb,OAAO,CAAC,yBAAyB,CAAC,CAoDpC"}
@@ -2,7 +2,7 @@
2
2
  * Introducer Discovery: proactive discovery of connector-flow opportunities
3
3
  * between a user's contacts.
4
4
  *
5
- * Selects top-N contacts from the user's personal index and runs scoped
5
+ * Selects top-N contacts from the user's personal network and runs scoped
6
6
  * HyDE discovery for each, creating latent introducer opportunities that
7
7
  * the user (as introducer) must approve before parties see them.
8
8
  */
@@ -26,7 +26,7 @@ export const INTRODUCER_DISCOVERY_SOURCE = 'introducer_discovery';
26
26
  export async function selectContactsForDiscovery(database, userId, limit = MAX_CONTACTS_PER_CYCLE) {
27
27
  const personalIndexId = await database.getPersonalIndexId(userId);
28
28
  if (!personalIndexId) {
29
- logger.verbose(`[IntroducerDiscovery] No personal index found — userId=${userId}`);
29
+ logger.verbose(`[IntroducerDiscovery] No personal network found — userId=${userId}`);
30
30
  return [];
31
31
  }
32
32
  const contacts = await database.getContactsWithIntentFreshness(personalIndexId, userId, limit);
@@ -1 +1 @@
1
- {"version":3,"file":"opportunity.introducer.js","sourceRoot":"/","sources":["opportunity/opportunity.introducer.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Introducer Discovery: proactive discovery of connector-flow opportunities\n * between a user's contacts.\n *\n * Selects top-N contacts from the user's personal index and runs scoped\n * HyDE discovery for each, creating latent introducer opportunities that\n * the user (as introducer) must approve before parties see them.\n */\n\nimport { protocolLogger } from '../shared/observability/protocol.logger.js';\n\nconst logger = protocolLogger('IntroducerDiscovery');\n\n/** Maximum contacts to evaluate per maintenance cycle. */\nexport const MAX_CONTACTS_PER_CYCLE = 5;\n\n/** Maximum candidate opportunities per contact. */\nexport const MAX_CANDIDATES_PER_CONTACT = 3;\n\n/** Detection source value for introducer-discovered opportunities. */\nexport const INTRODUCER_DISCOVERY_SOURCE = 'introducer_discovery' as const;\n\n/** A contact with their active intents, used for introducer discovery selection. */\nexport interface ContactWithIntents {\n userId: string;\n /** Most recent intent updatedAt timestamp (ISO string or null). */\n latestIntentAt: string | null;\n /** Number of active intents this contact has. */\n intentCount: number;\n}\n\n/** Database methods needed for introducer discovery contact selection. */\nexport interface IntroducerDiscoveryDatabase {\n /** Get the user's personal index ID. */\n getPersonalIndexId(userId: string): Promise<string | null>;\n /** Get contacts from a personal index with their intent freshness data. */\n getContactsWithIntentFreshness(\n personalIndexId: string,\n ownerId: string,\n limit: number,\n ): Promise<ContactWithIntents[]>;\n}\n\n/** Queue interface for enqueuing introducer discovery jobs. */\nexport interface IntroducerDiscoveryQueue {\n addJob(\n data: { intentId: string; userId: string; indexIds?: string[]; contactUserId?: string },\n options?: { priority?: number; jobId?: string },\n ): Promise<unknown>;\n}\n\n/** Result of a single introducer discovery cycle. */\nexport interface IntroducerDiscoveryResult {\n contactsEvaluated: number;\n jobsEnqueued: number;\n skippedReason?: string;\n}\n\n/**\n * Select top-N contacts for introducer discovery, sorted by intent freshness.\n * Contacts with no active intents are excluded.\n *\n * @param database - Database adapter with contact/intent queries\n * @param userId - The introducer user\n * @param limit - Max contacts to return (default MAX_CONTACTS_PER_CYCLE)\n * @returns Sorted contacts with intent data\n */\nexport async function selectContactsForDiscovery(\n database: IntroducerDiscoveryDatabase,\n userId: string,\n limit: number = MAX_CONTACTS_PER_CYCLE,\n): Promise<ContactWithIntents[]> {\n const personalIndexId = await database.getPersonalIndexId(userId);\n if (!personalIndexId) {\n logger.verbose(`[IntroducerDiscovery] No personal index found — userId=${userId}`);\n return [];\n }\n\n const contacts = await database.getContactsWithIntentFreshness(\n personalIndexId,\n userId,\n limit,\n );\n\n logger.verbose(`[IntroducerDiscovery] Selected contacts for discovery — userId=${userId} totalContacts=${contacts.length} limit=${limit}`);\n\n return contacts;\n}\n\n/**\n * Determine whether introducer discovery should run based on the current\n * connector-flow composition. Triggers when connector-flow count is below\n * the soft target.\n *\n * @param connectorFlowCount - Current number of connector-flow opportunities\n * @param connectorFlowTarget - Soft target (default 2)\n * @returns Whether introducer discovery should run\n */\nexport function shouldRunIntroducerDiscovery(\n connectorFlowCount: number,\n connectorFlowTarget: number = 2,\n): boolean {\n return connectorFlowCount < connectorFlowTarget;\n}\n\n/**\n * Run introducer discovery for a user: select contacts, enqueue discovery jobs.\n * Each job uses onBehalfOfUserId so the opportunity graph treats the user as introducer.\n *\n * @param database - Database adapter for contact queries\n * @param queue - Queue for enqueuing discovery jobs\n * @param userId - The introducer user\n * @returns Summary of the discovery cycle\n */\nexport async function runIntroducerDiscovery(\n database: IntroducerDiscoveryDatabase,\n queue: IntroducerDiscoveryQueue,\n userId: string,\n): Promise<IntroducerDiscoveryResult> {\n const personalIndexId = await database.getPersonalIndexId(userId);\n if (!personalIndexId) {\n return { contactsEvaluated: 0, jobsEnqueued: 0, skippedReason: 'no_personal_index' };\n }\n\n const contacts = await selectContactsForDiscovery(database, userId);\n if (contacts.length === 0) {\n return { contactsEvaluated: 0, jobsEnqueued: 0, skippedReason: 'no_contacts' };\n }\n\n const bucket = Math.floor(Date.now() / (12 * 60 * 60 * 1000)); // 12h dedup bucket\n\n const results = await Promise.allSettled(\n contacts.map(async (contact) => {\n // For each contact, we enqueue a discovery job using one of their intents\n // The opportunity graph will use onBehalfOfUserId to discover on behalf of the contact\n // while the userId (introducer) gets the introducer role\n const jobId = `introducer-discovery-${userId}-${contact.userId}-${bucket}`;\n try {\n await queue.addJob(\n {\n intentId: `introducer:${contact.userId}`,\n userId,\n indexIds: [personalIndexId],\n contactUserId: contact.userId,\n },\n { priority: 15, jobId }, // Lower priority than regular rediscovery (10)\n );\n return true;\n } catch (err) {\n const message = err instanceof Error ? err.message : String(err);\n if (/duplicate|already exists|job.*id/i.test(message)) {\n logger.verbose(`[IntroducerDiscovery] Job skipped (duplicate) — userId=${userId} contactUserId=${contact.userId} error=${message}`);\n return false;\n }\n throw err;\n }\n }),\n );\n\n for (const r of results) {\n if (r.status === 'rejected') {\n const errMsg = r.reason instanceof Error ? r.reason.message : String(r.reason);\n logger.error(`[IntroducerDiscovery] Job enqueue failed: ${errMsg}`);\n }\n }\n const jobsEnqueued = results.filter((r) => r.status === 'fulfilled' && r.value).length;\n\n logger.info(`[IntroducerDiscovery] Discovery cycle complete — userId=${userId} contactsEvaluated=${contacts.length} jobsEnqueued=${jobsEnqueued}`);\n\n return { contactsEvaluated: contacts.length, jobsEnqueued };\n}\n"]}
1
+ {"version":3,"file":"opportunity.introducer.js","sourceRoot":"/","sources":["opportunity/opportunity.introducer.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Introducer Discovery: proactive discovery of connector-flow opportunities\n * between a user's contacts.\n *\n * Selects top-N contacts from the user's personal network and runs scoped\n * HyDE discovery for each, creating latent introducer opportunities that\n * the user (as introducer) must approve before parties see them.\n */\n\nimport { protocolLogger } from '../shared/observability/protocol.logger.js';\n\nconst logger = protocolLogger('IntroducerDiscovery');\n\n/** Maximum contacts to evaluate per maintenance cycle. */\nexport const MAX_CONTACTS_PER_CYCLE = 5;\n\n/** Maximum candidate opportunities per contact. */\nexport const MAX_CANDIDATES_PER_CONTACT = 3;\n\n/** Detection source value for introducer-discovered opportunities. */\nexport const INTRODUCER_DISCOVERY_SOURCE = 'introducer_discovery' as const;\n\n/** A contact with their active intents, used for introducer discovery selection. */\nexport interface ContactWithIntents {\n userId: string;\n /** Most recent intent updatedAt timestamp (ISO string or null). */\n latestIntentAt: string | null;\n /** Number of active intents this contact has. */\n intentCount: number;\n}\n\n/** Database methods needed for introducer discovery contact selection. */\nexport interface IntroducerDiscoveryDatabase {\n /** Get the user's personal network ID. */\n getPersonalIndexId(userId: string): Promise<string | null>;\n /** Get contacts from a personal network with their intent freshness data. */\n getContactsWithIntentFreshness(\n personalIndexId: string,\n ownerId: string,\n limit: number,\n ): Promise<ContactWithIntents[]>;\n}\n\n/** Queue interface for enqueuing introducer discovery jobs. */\nexport interface IntroducerDiscoveryQueue {\n addJob(\n data: { intentId: string; userId: string; indexIds?: string[]; contactUserId?: string },\n options?: { priority?: number; jobId?: string },\n ): Promise<unknown>;\n}\n\n/** Result of a single introducer discovery cycle. */\nexport interface IntroducerDiscoveryResult {\n contactsEvaluated: number;\n jobsEnqueued: number;\n skippedReason?: string;\n}\n\n/**\n * Select top-N contacts for introducer discovery, sorted by intent freshness.\n * Contacts with no active intents are excluded.\n *\n * @param database - Database adapter with contact/intent queries\n * @param userId - The introducer user\n * @param limit - Max contacts to return (default MAX_CONTACTS_PER_CYCLE)\n * @returns Sorted contacts with intent data\n */\nexport async function selectContactsForDiscovery(\n database: IntroducerDiscoveryDatabase,\n userId: string,\n limit: number = MAX_CONTACTS_PER_CYCLE,\n): Promise<ContactWithIntents[]> {\n const personalIndexId = await database.getPersonalIndexId(userId);\n if (!personalIndexId) {\n logger.verbose(`[IntroducerDiscovery] No personal network found — userId=${userId}`);\n return [];\n }\n\n const contacts = await database.getContactsWithIntentFreshness(\n personalIndexId,\n userId,\n limit,\n );\n\n logger.verbose(`[IntroducerDiscovery] Selected contacts for discovery — userId=${userId} totalContacts=${contacts.length} limit=${limit}`);\n\n return contacts;\n}\n\n/**\n * Determine whether introducer discovery should run based on the current\n * connector-flow composition. Triggers when connector-flow count is below\n * the soft target.\n *\n * @param connectorFlowCount - Current number of connector-flow opportunities\n * @param connectorFlowTarget - Soft target (default 2)\n * @returns Whether introducer discovery should run\n */\nexport function shouldRunIntroducerDiscovery(\n connectorFlowCount: number,\n connectorFlowTarget: number = 2,\n): boolean {\n return connectorFlowCount < connectorFlowTarget;\n}\n\n/**\n * Run introducer discovery for a user: select contacts, enqueue discovery jobs.\n * Each job uses onBehalfOfUserId so the opportunity graph treats the user as introducer.\n *\n * @param database - Database adapter for contact queries\n * @param queue - Queue for enqueuing discovery jobs\n * @param userId - The introducer user\n * @returns Summary of the discovery cycle\n */\nexport async function runIntroducerDiscovery(\n database: IntroducerDiscoveryDatabase,\n queue: IntroducerDiscoveryQueue,\n userId: string,\n): Promise<IntroducerDiscoveryResult> {\n const personalIndexId = await database.getPersonalIndexId(userId);\n if (!personalIndexId) {\n return { contactsEvaluated: 0, jobsEnqueued: 0, skippedReason: 'no_personal_index' };\n }\n\n const contacts = await selectContactsForDiscovery(database, userId);\n if (contacts.length === 0) {\n return { contactsEvaluated: 0, jobsEnqueued: 0, skippedReason: 'no_contacts' };\n }\n\n const bucket = Math.floor(Date.now() / (12 * 60 * 60 * 1000)); // 12h dedup bucket\n\n const results = await Promise.allSettled(\n contacts.map(async (contact) => {\n // For each contact, we enqueue a discovery job using one of their intents\n // The opportunity graph will use onBehalfOfUserId to discover on behalf of the contact\n // while the userId (introducer) gets the introducer role\n const jobId = `introducer-discovery-${userId}-${contact.userId}-${bucket}`;\n try {\n await queue.addJob(\n {\n intentId: `introducer:${contact.userId}`,\n userId,\n indexIds: [personalIndexId],\n contactUserId: contact.userId,\n },\n { priority: 15, jobId }, // Lower priority than regular rediscovery (10)\n );\n return true;\n } catch (err) {\n const message = err instanceof Error ? err.message : String(err);\n if (/duplicate|already exists|job.*id/i.test(message)) {\n logger.verbose(`[IntroducerDiscovery] Job skipped (duplicate) — userId=${userId} contactUserId=${contact.userId} error=${message}`);\n return false;\n }\n throw err;\n }\n }),\n );\n\n for (const r of results) {\n if (r.status === 'rejected') {\n const errMsg = r.reason instanceof Error ? r.reason.message : String(r.reason);\n logger.error(`[IntroducerDiscovery] Job enqueue failed: ${errMsg}`);\n }\n }\n const jobsEnqueued = results.filter((r) => r.status === 'fulfilled' && r.value).length;\n\n logger.info(`[IntroducerDiscovery] Discovery cycle complete — userId=${userId} contactsEvaluated=${contacts.length} jobsEnqueued=${jobsEnqueued}`);\n\n return { contactsEvaluated: contacts.length, jobsEnqueued };\n}\n"]}
@@ -171,7 +171,7 @@ export declare const OpportunityGraphState: import("@langchain/langgraph").Annot
171
171
  networkId: import("@langchain/langgraph").BaseChannel<Id<"networks"> | undefined, Id<"networks"> | import("@langchain/langgraph").OverwriteValue<Id<"networks"> | undefined> | undefined, unknown>;
172
172
  /**
173
173
  * Optional set of indexes discovery may search within (e.g. a network-scoped
174
- * agent's reachable indexes: the bound network plus the user's personal index).
174
+ * agent's reachable networks: the bound network plus the user's personal network).
175
175
  * The scope node intersects this with the user's actual memberships. Ignored
176
176
  * when `networkId` is set (single-network override). When unset, discovery
177
177
  * spans all of the user's networks.
@@ -259,7 +259,7 @@ export declare const OpportunityGraphState: import("@langchain/langgraph").Annot
259
259
  /** Per-index relevancy scores for dedup tie-breaking. Background path: from intent_indexes. Chat path: transient from IntentIndexer. */
260
260
  indexRelevancyScores: import("@langchain/langgraph").BaseChannel<Record<string, number>, Record<string, number> | import("@langchain/langgraph").OverwriteValue<Record<string, number>>, unknown>;
261
261
  /** Whether discovery used intent (path A) or user context (path B/C). Used by persist for triggeredBy. In-memory routing state only; never persisted. */
262
- discoverySource: import("@langchain/langgraph").BaseChannel<"context" | "intent", "context" | "intent" | import("@langchain/langgraph").OverwriteValue<"context" | "intent">, unknown>;
262
+ discoverySource: import("@langchain/langgraph").BaseChannel<"intent" | "context", "intent" | "context" | import("@langchain/langgraph").OverwriteValue<"intent" | "context">, unknown>;
263
263
  /** Resolved intent ID used for this discovery run (when discoverySource is 'intent'). Set by intent-resolution. */
264
264
  resolvedTriggerIntentId: import("@langchain/langgraph").BaseChannel<Id<"intents"> | undefined, Id<"intents"> | import("@langchain/langgraph").OverwriteValue<Id<"intents"> | undefined> | undefined, unknown>;
265
265
  /** Asker's profile (from prep). Used for profile-as-source discovery and evaluation. */
@@ -40,7 +40,7 @@ export const OpportunityGraphState = Annotation.Root({
40
40
  }),
41
41
  /**
42
42
  * Optional set of indexes discovery may search within (e.g. a network-scoped
43
- * agent's reachable indexes: the bound network plus the user's personal index).
43
+ * agent's reachable networks: the bound network plus the user's personal network).
44
44
  * The scope node intersects this with the user's actual memberships. Ignored
45
45
  * when `networkId` is set (single-network override). When unset, discovery
46
46
  * spans all of the user's networks.
@@ -1 +1 @@
1
- {"version":3,"file":"opportunity.state.js","sourceRoot":"/","sources":["opportunity/opportunity.state.ts"],"names":[],"mappings":"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","sourcesContent":["import { Annotation } from \"@langchain/langgraph\";\nimport type { Id } from '../shared/interfaces/database.interface.js';\nimport type { OpportunityStatus, Opportunity } from '../shared/interfaces/database.interface.js';\nimport type { Lens } from '../shared/interfaces/embedder.interface.js';\nimport type { EvaluatorEntity } from './opportunity.evaluator.js';\nimport type { DebugMetaAgent } from '../chat/chat-streaming.types.js';\nimport type { OpportunityEvidence } from '../shared/schemas/network-assignment.schema.js';\nimport type { DiscoveryNegotiation, DiscoverySummary } from \"./question.prompt.js\";\n\n/**\n * Opportunity Graph State (Linear Multi-Step Workflow)\n * \n * Flow: Prep → Scope → Discovery → Evaluation → Ranking → Persist → END\n * \n * Following the intent graph pattern with Annotation-based state management.\n */\n\n/** Asker's profile shape (identity + context). Used by sourceProfile annotation. */\nexport interface SourceProfileData {\n identity?: { name?: string; bio?: string; location?: string };\n context?: string;\n}\n\n/**\n * Indexed intent with hyde document (from prep node)\n */\nexport interface IndexedIntent {\n intentId: Id<'intents'>;\n payload: string;\n summary?: string;\n hydeDocumentId?: string;\n hydeEmbedding?: number[];\n indexes: Id<'networks'>[];\n}\n\n/**\n * Target index for search (from scope node)\n */\nexport interface TargetNetwork {\n networkId: Id<'networks'>;\n title: string;\n memberCount: number;\n}\n\n/**\n * Candidate match from discovery (semantic search).\n */\nexport interface CandidateMatch {\n candidateUserId: Id<'users'>;\n candidateIntentId?: Id<'intents'>;\n /** Source premise that produced this candidate (set when discoverySource is 'premise-similarity'). */\n sourcePremiseId?: Id<'premises'>;\n /** Candidate premise that matched this candidate (set for premise-based matches). */\n candidatePremiseId?: Id<'premises'>;\n /** Source context that produced this candidate (set when discoverySource is 'context-to-intent'). */\n sourceContextId?: string;\n networkId: Id<'networks'>;\n similarity: number;\n /** Free-text lens label that produced this match. */\n lens: string;\n candidatePayload: string;\n candidateSummary?: string;\n /** How this candidate was found: 'query' (HyDE from search text), 'premise-similarity', or 'context-to-intent'. */\n discoverySource?: 'query' | 'premise-similarity' | 'context-to-intent';\n /** Which discovery strategies found this candidate (set by mergeStrategyCandidates). */\n matchedStrategies?: string[];\n /** Typed evidence that explains why this candidate entered evaluation. */\n evidence?: OpportunityEvidence[];\n}\n\n/**\n * Evaluated candidate with LLM scoring (legacy; used when evaluator returns source/candidate pair).\n * candidateIntentId is set for intent matches; omitted for profile-only matches.\n */\nexport interface EvaluatedCandidate {\n sourceUserId: Id<'users'>;\n candidateUserId: Id<'users'>;\n sourceIntentId?: Id<'intents'>;\n candidateIntentId?: Id<'intents'>;\n networkId: Id<'networks'>;\n score: number; // 0-100\n reasoning: string; // Third-party analytical explanation of the match (for LLM agents)\n valencyRole: 'Agent' | 'Patient' | 'Peer';\n /** Free-text lens label that produced this match. */\n lens: string;\n}\n\n/**\n * Actor in an evaluated opportunity (from entity-bundle evaluator).\n * networkId is filled from the entity bundle in the graph, not by the evaluator.\n */\nexport interface EvaluatedOpportunityActor {\n userId: Id<'users'>;\n role: 'agent' | 'patient' | 'peer';\n intentId?: Id<'intents'>;\n networkId: Id<'networks'>;\n}\n\n/**\n * Evaluated opportunity with multi-actor output (entity-bundle evaluator).\n */\nexport interface EvaluatedOpportunity {\n actors: EvaluatedOpportunityActor[];\n score: number;\n reasoning: string;\n evidence?: OpportunityEvidence[];\n}\n\n/**\n * Which flow triggered this graph invocation. Determines initial persist status,\n * park-window timeout, streaming behavior, and whether AbortSignal is honored.\n *\n * - 'ambient' (default): queue-driven. Persists at the trigger default of\n * `pending` unless `options.initialStatus` overrides (the queue worker\n * passes `'latent'`, chat-bound ambient discovery passes `'draft'`). 5-min\n * park window, no streaming, ignores abort.\n * - 'orchestrator': chat-driven. Persists at the trigger default of\n * `negotiating` unless `options.initialStatus` overrides. 60s park window,\n * streams `opportunity_draft_ready` events, honors abort.\n *\n * See {@link resolveInitialStatus} for the exact fallback used when\n * `options.initialStatus` is undefined.\n */\nexport type OpportunityTrigger = 'ambient' | 'orchestrator';\n\n/**\n * Resolves the initial status for opportunities created in the persist node.\n *\n * Explicit `options.initialStatus` always wins (callers like the chat tool or\n * maintenance scripts override per-call). When the caller leaves it\n * undefined, the trigger drives the default:\n * - 'orchestrator' → 'negotiating' (chat-driven; negotiations run before the\n * user sees a draft card).\n * - 'ambient' (or any other trigger) → 'pending' (long-standing default for\n * queue- and intent-driven discovery).\n *\n * Lives here rather than in opportunity.graph.ts so unit tests can exercise\n * it without pulling in the full graph (and the evaluator's LLM requirements).\n *\n * @param trigger - The graph invocation's trigger\n * @param explicit - Caller-supplied initial status from options.initialStatus\n */\nexport function resolveInitialStatus(\n trigger: OpportunityTrigger,\n explicit: OpportunityStatus | undefined,\n): OpportunityStatus {\n if (explicit !== undefined) return explicit;\n return trigger === 'orchestrator' ? 'negotiating' : 'pending';\n}\n\n/**\n * Options passed to the graph\n */\nexport interface OpportunityGraphOptions {\n /** Initial status for created opportunities (default: 'pending') */\n initialStatus?: OpportunityStatus;\n /** Minimum score threshold (default: 50) */\n minScore?: number;\n /** Maximum opportunities to return (default: 20) */\n limit?: number;\n /** Pre-inferred lenses (if not provided, lens inference runs automatically in HyDE graph) */\n lenses?: Lens[];\n /** User's search query for HyDE generation */\n hydeDescription?: string;\n /** Existing opportunities summary for evaluator deduplication */\n existingOpportunities?: string;\n /** Chat session ID for draft opportunities; stored as context.conversationId for visibility filtering. */\n conversationId?: string;\n /**\n * MCP-only: cap the negotiate-phase wall-clock at this many milliseconds.\n * When set, `negotiateNode` races `negotiateCandidates(...)` against a timer;\n * if the timer wins, the node returns early with a `timed_out` trace and the\n * unawaited negotiation chains finalize each opp's DB status in the\n * background. Set to 20_000 by the MCP `discover_opportunities` handler.\n * Chat, ambient queue, and all other callers omit this — existing behavior.\n */\n negotiateTimeoutMs?: number;\n}\n\n/**\n * Opportunity Graph State Annotation\n */\nexport const OpportunityGraphState = Annotation.Root({\n // ─── Input Fields (Required) ───\n userId: Annotation<Id<'users'>>({\n reducer: (curr, next) => next ?? curr,\n default: () => '' as Id<'users'>,\n }),\n \n searchQuery: Annotation<string | undefined>({\n reducer: (curr, next) => next ?? curr,\n default: () => undefined,\n }),\n \n networkId: Annotation<Id<'networks'> | undefined>({\n reducer: (curr, next) => next ?? curr,\n default: () => undefined,\n }),\n\n /**\n * Optional set of indexes discovery may search within (e.g. a network-scoped\n * agent's reachable indexes: the bound network plus the user's personal index).\n * The scope node intersects this with the user's actual memberships. Ignored\n * when `networkId` is set (single-network override). When unset, discovery\n * spans all of the user's networks.\n */\n indexScope: Annotation<Id<'networks'>[] | undefined>({\n reducer: (curr, next) => next ?? curr,\n default: () => undefined,\n }),\n\n /** Optional intent to use as discovery source and for triggeredBy. When set, used for search text (if query empty) and persist. */\n triggerIntentId: Annotation<Id<'intents'> | undefined>({\n reducer: (curr, next) => next ?? curr,\n default: () => undefined,\n }),\n\n /** Optional: restrict discovery to this specific user ID only (direct connection). */\n targetUserId: Annotation<Id<'users'> | undefined>({\n reducer: (curr, next) => next ?? curr,\n default: () => undefined,\n }),\n\n /** Optional: discover on behalf of this user (introducer flow). When set, prep/eval use this user's profile/intents; userId becomes the introducer. */\n onBehalfOfUserId: Annotation<Id<'users'> | undefined>({\n reducer: (curr, next) => next ?? curr,\n default: () => undefined,\n }),\n\n options: Annotation<OpportunityGraphOptions>({\n reducer: (curr, next) => next ?? curr,\n default: () => ({}),\n }),\n\n /**\n * Which flow triggered this graph invocation. See {@link OpportunityTrigger}\n * for the exact branch behavior and {@link resolveInitialStatus} for the\n * persist default when `options.initialStatus` is unset.\n *\n * - 'ambient' (default): queue-driven, persist default `pending`, 5-min\n * park window, no streaming, ignores abort.\n * - 'orchestrator': chat-driven, persist default `negotiating`, 60s park\n * window, streams `opportunity_draft_ready` events, honors abort.\n */\n trigger: Annotation<OpportunityTrigger>({\n reducer: (curr, next) => next ?? curr,\n default: () => 'ambient',\n }),\n\n /**\n * Accepted opportunities the persist node discovered between the discoverer\n * and a candidate actor (same pair, status='accepted'). The orchestrator\n * branch populates this so the discover_opportunities tool (Task 7) can tell\n * the LLM \"these pairs are already connected, surface the existing chat\n * rather than creating a new draft\". Always empty for the ambient trigger.\n *\n * Left intentionally minimal — conversationId/URL resolution happens at\n * Start Chat time (Task 8), not here.\n */\n dedupAlreadyAccepted: Annotation<Array<{\n opportunityId: string;\n counterpartyUserId: string;\n }>>({\n reducer: (curr, next) => next ?? curr,\n default: () => [],\n }),\n\n /**\n * Operation mode controls graph flow:\n * - 'create': Existing discover pipeline (Prep → Scope → Discovery → Evaluation → Ranking → Persist)\n * - 'create_introduction': Introduction path (validation → evaluation → persist) for chat-driven intros\n * - 'continue_discovery': Pagination path (Prep → Evaluation → Ranking → Persist) using pre-loaded candidates\n * - 'read': List opportunities filtered by userId and optionally networkId (fast path)\n * - 'update': Change opportunity status (accept, reject, etc.)\n * - 'delete': Expire/archive an opportunity\n * - 'send': Promote latent opportunity to pending + queue notification\n * - 'negotiate_existing': Load an existing opportunity by opportunityId and run bilateral negotiation.\n * Used after introducer approval to trigger the normal negotiation flow.\n * - 'approve_introduction': Mark the caller as having approved a latent introducer opportunity,\n * then enqueue a negotiate_existing job for that opportunity.\n *\n * Defaults to 'create' for backward compatibility.\n */\n operationMode: Annotation<'create' | 'create_introduction' | 'continue_discovery' | 'read' | 'update' | 'delete' | 'send' | 'negotiate_existing' | 'approve_introduction'>({\n reducer: (curr, next) => next ?? curr,\n default: () => 'create' as const,\n }),\n\n /** Introduction mode: pre-gathered entities (profiles + intents per party). */\n introductionEntities: Annotation<EvaluatorEntity[]>({\n reducer: (curr, next) => next ?? curr,\n default: () => [],\n }),\n\n /** Introduction mode: optional hint from the introducer. */\n introductionHint: Annotation<string | undefined>({\n reducer: (curr, next) => next ?? curr,\n default: () => undefined,\n }),\n\n /** When set (e.g. chat scope), networkId must match this. */\n requiredNetworkId: Annotation<Id<'networks'> | undefined>({\n reducer: (curr, next) => next ?? curr,\n default: () => undefined,\n }),\n\n /** Set by intro_evaluation; used by persist to build manual detection and introducer actor. */\n introductionContext: Annotation<{ createdByName?: string } | undefined>({\n reducer: (curr, next) => next ?? curr,\n default: () => undefined,\n }),\n\n /** Target opportunity ID for update/delete/send modes. */\n opportunityId: Annotation<string | undefined>({\n reducer: (curr, next) => next ?? curr,\n default: () => undefined,\n }),\n\n /** New status for update mode (e.g. 'accepted', 'rejected'). */\n newStatus: Annotation<string | undefined>({\n reducer: (curr, next) => next ?? curr,\n default: () => undefined,\n }),\n \n // ─── Intermediate Fields (Accumulated) ───\n \n /** User's indexed intents with hyde documents (from prep) */\n indexedIntents: Annotation<IndexedIntent[]>({\n reducer: (curr, next) => next ?? curr,\n default: () => [],\n }),\n \n /** User's network memberships (from prep) */\n userNetworks: Annotation<Id<'networks'>[]>({\n reducer: (curr, next) => next ?? curr,\n default: () => [],\n }),\n \n /** Target indexes to search within (from scope) */\n targetNetworks: Annotation<TargetNetwork[]>({\n reducer: (curr, next) => next ?? curr,\n default: () => [],\n }),\n\n /** Per-index relevancy scores for dedup tie-breaking. Background path: from intent_indexes. Chat path: transient from IntentIndexer. */\n indexRelevancyScores: Annotation<Record<string, number>>({\n reducer: (curr, next) => next ?? curr,\n default: () => ({}),\n }),\n\n /** Whether discovery used intent (path A) or user context (path B/C). Used by persist for triggeredBy. In-memory routing state only; never persisted. */\n discoverySource: Annotation<'intent' | 'context'>({\n reducer: (curr, next) => next ?? curr,\n default: () => 'intent',\n }),\n\n /** Resolved intent ID used for this discovery run (when discoverySource is 'intent'). Set by intent-resolution. */\n resolvedTriggerIntentId: Annotation<Id<'intents'> | undefined>({\n reducer: (curr, next) => next ?? curr,\n default: () => undefined,\n }),\n\n /** Asker's profile (from prep). Used for profile-as-source discovery and evaluation. */\n sourceProfile: Annotation<SourceProfileData | null>({\n reducer: (curr, next) => next ?? curr,\n default: () => null,\n }),\n\n /** User's active premises with embeddings (from prep). Used for premise-to-premise discovery path D. */\n sourcePremises: Annotation<Array<{ premiseId: Id<'premises'>; embedding: number[] }>>({\n reducer: (curr, next) => next ?? curr,\n default: () => [],\n }),\n\n /** User context embeddings per network (from prep). Used for context-to-intent discovery. */\n sourceContexts: Annotation<Array<{ contextId: string; networkId: Id<'networks'>; embedding: number[] }>>({\n reducer: (curr, next) => next ?? curr,\n default: () => [],\n }),\n\n /** Resolved intent is in at least one target index (path A vs C). */\n resolvedIntentInIndex: Annotation<boolean>({\n reducer: (curr, next) => next ?? curr,\n default: () => false,\n }),\n\n /** Create-intent signal: when true, tool should return createIntentSuggested so agent can auto-call create_intent. */\n createIntentSuggested: Annotation<boolean>({\n reducer: (curr, next) => next ?? curr,\n default: () => false,\n }),\n\n /** Suggested description for create_intent when createIntentSuggested is true. */\n suggestedIntentDescription: Annotation<string | undefined>({\n reducer: (curr, next) => next ?? curr,\n default: () => undefined,\n }),\n\n /** HyDE embeddings per lens label (from discovery) */\n hydeEmbeddings: Annotation<Record<string, number[]>>({\n reducer: (curr, next) => next ?? curr,\n default: () => ({}),\n }),\n \n /** Candidate matches from semantic search (from discovery) */\n candidates: Annotation<CandidateMatch[]>({\n reducer: (curr, next) => next ?? curr,\n default: () => [],\n }),\n\n /** Candidates not yet evaluated (for pagination -- cached in Redis by caller). */\n remainingCandidates: Annotation<CandidateMatch[]>({\n reducer: (curr, next) => next ?? curr,\n default: () => [],\n }),\n\n /** Discovery session ID for pagination (maps to Redis cache key). */\n discoveryId: Annotation<string | null>({\n reducer: (curr, next) => next ?? curr,\n default: () => null,\n }),\n\n /** Evaluated candidates with scores (from evaluation; legacy) */\n evaluatedCandidates: Annotation<EvaluatedCandidate[]>({\n reducer: (curr, next) => next ?? curr,\n default: () => [],\n }),\n\n /** Evaluated opportunities with actors (from entity-bundle evaluator) */\n evaluatedOpportunities: Annotation<EvaluatedOpportunity[]>({\n reducer: (curr, next) => next ?? curr,\n default: () => [],\n }),\n \n // ─── Output Fields (Overwrite per turn) ───\n \n /** Final ranked and persisted opportunities */\n opportunities: Annotation<Opportunity[]>({\n reducer: (curr, next) => next,\n default: () => [],\n }),\n\n /** Discovery path: pairs skipped because an opportunity already exists between viewer and candidate (no duplicate created). */\n existingBetweenActors: Annotation<Array<{\n candidateUserId: Id<'users'>;\n networkId: Id<'networks'>;\n existingOpportunityId?: Id<'opportunities'>;\n existingStatus?: OpportunityStatus;\n }>>({\n reducer: (curr, next) => next ?? curr,\n default: () => [],\n }),\n \n /** Error message if any step fails */\n error: Annotation<string | undefined>({\n reducer: (curr, next) => next,\n default: () => undefined,\n }),\n\n /** Output for read mode: enriched list of opportunities. */\n readResult: Annotation<{\n count: number;\n message?: string;\n opportunities: Array<{\n id: string;\n indexName: string;\n connectedWith: string[];\n suggestedBy: string | null;\n reasoning: string;\n status: string;\n category: string;\n confidence: number | null;\n source: string | null;\n }>;\n } | undefined>({\n reducer: (curr, next) => next,\n default: () => undefined,\n }),\n\n /** Output for update/delete/send modes. */\n mutationResult: Annotation<{\n success: boolean;\n message?: string;\n opportunityId?: string;\n notified?: string[];\n conversationId?: string;\n error?: string;\n } | undefined>({\n reducer: (curr, next) => next,\n default: () => undefined,\n }),\n\n // ─── Trace Output ───\n\n /**\n * Accumulated trace entries from each graph node.\n * Used for observability: surfaces internal processing steps (search query, HyDE strategies,\n * candidates found, evaluation results) to the frontend.\n */\n trace: Annotation<Array<{ node: string; detail?: string; data?: Record<string, unknown> }>>({\n reducer: (curr, next) => [...curr, ...(next || [])],\n default: () => [],\n }),\n\n /** Timing records for each agent invocation within this graph run. */\n agentTimings: Annotation<DebugMetaAgent[]>({\n reducer: (acc, val) => [...acc, ...val],\n default: () => [],\n }),\n\n /**\n * Per-candidate negotiation records captured by `negotiateNode`. Populated\n * regardless of accept/reject so the question generator sees a complete\n * picture. Populated for ALL triggers (ambient + orchestrator) since the\n * negotiate node's `onCandidateResolved` hook is unconditional; only the\n * orchestrator streaming side-effects (opportunity_draft_ready emission)\n * are trigger-gated. Empty when the negotiate node was skipped (no\n * opportunities to negotiate).\n */\n discoveryNegotiations: Annotation<DiscoveryNegotiation[]>({\n reducer: (curr, next) => [...curr, ...(next || [])],\n default: () => [],\n }),\n\n /** Aggregate counters across `discoveryNegotiations`. Built in the negotiate node. */\n discoverySummary: Annotation<DiscoverySummary | null>({\n reducer: (_curr, next) => next ?? null,\n default: () => null,\n }),\n});\n"]}
1
+ {"version":3,"file":"opportunity.state.js","sourceRoot":"/","sources":["opportunity/opportunity.state.ts"],"names":[],"mappings":"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","sourcesContent":["import { Annotation } from \"@langchain/langgraph\";\nimport type { Id } from '../shared/interfaces/database.interface.js';\nimport type { OpportunityStatus, Opportunity } from '../shared/interfaces/database.interface.js';\nimport type { Lens } from '../shared/interfaces/embedder.interface.js';\nimport type { EvaluatorEntity } from './opportunity.evaluator.js';\nimport type { DebugMetaAgent } from '../chat/chat-streaming.types.js';\nimport type { OpportunityEvidence } from '../shared/schemas/network-assignment.schema.js';\nimport type { DiscoveryNegotiation, DiscoverySummary } from \"./question.prompt.js\";\n\n/**\n * Opportunity Graph State (Linear Multi-Step Workflow)\n *\n * Flow: Prep → Scope → Discovery → Evaluation → Ranking → Persist → END\n *\n * Following the intent graph pattern with Annotation-based state management.\n */\n\n/** Asker's profile shape (identity + context). Used by sourceProfile annotation. */\nexport interface SourceProfileData {\n identity?: { name?: string; bio?: string; location?: string };\n context?: string;\n}\n\n/**\n * Indexed intent with hyde document (from prep node)\n */\nexport interface IndexedIntent {\n intentId: Id<'intents'>;\n payload: string;\n summary?: string;\n hydeDocumentId?: string;\n hydeEmbedding?: number[];\n indexes: Id<'networks'>[];\n}\n\n/**\n * Target index for search (from scope node)\n */\nexport interface TargetNetwork {\n networkId: Id<'networks'>;\n title: string;\n memberCount: number;\n}\n\n/**\n * Candidate match from discovery (semantic search).\n */\nexport interface CandidateMatch {\n candidateUserId: Id<'users'>;\n candidateIntentId?: Id<'intents'>;\n /** Source premise that produced this candidate (set when discoverySource is 'premise-similarity'). */\n sourcePremiseId?: Id<'premises'>;\n /** Candidate premise that matched this candidate (set for premise-based matches). */\n candidatePremiseId?: Id<'premises'>;\n /** Source context that produced this candidate (set when discoverySource is 'context-to-intent'). */\n sourceContextId?: string;\n networkId: Id<'networks'>;\n similarity: number;\n /** Free-text lens label that produced this match. */\n lens: string;\n candidatePayload: string;\n candidateSummary?: string;\n /** How this candidate was found: 'query' (HyDE from search text), 'premise-similarity', or 'context-to-intent'. */\n discoverySource?: 'query' | 'premise-similarity' | 'context-to-intent';\n /** Which discovery strategies found this candidate (set by mergeStrategyCandidates). */\n matchedStrategies?: string[];\n /** Typed evidence that explains why this candidate entered evaluation. */\n evidence?: OpportunityEvidence[];\n}\n\n/**\n * Evaluated candidate with LLM scoring (legacy; used when evaluator returns source/candidate pair).\n * candidateIntentId is set for intent matches; omitted for profile-only matches.\n */\nexport interface EvaluatedCandidate {\n sourceUserId: Id<'users'>;\n candidateUserId: Id<'users'>;\n sourceIntentId?: Id<'intents'>;\n candidateIntentId?: Id<'intents'>;\n networkId: Id<'networks'>;\n score: number; // 0-100\n reasoning: string; // Third-party analytical explanation of the match (for LLM agents)\n valencyRole: 'Agent' | 'Patient' | 'Peer';\n /** Free-text lens label that produced this match. */\n lens: string;\n}\n\n/**\n * Actor in an evaluated opportunity (from entity-bundle evaluator).\n * networkId is filled from the entity bundle in the graph, not by the evaluator.\n */\nexport interface EvaluatedOpportunityActor {\n userId: Id<'users'>;\n role: 'agent' | 'patient' | 'peer';\n intentId?: Id<'intents'>;\n networkId: Id<'networks'>;\n}\n\n/**\n * Evaluated opportunity with multi-actor output (entity-bundle evaluator).\n */\nexport interface EvaluatedOpportunity {\n actors: EvaluatedOpportunityActor[];\n score: number;\n reasoning: string;\n evidence?: OpportunityEvidence[];\n}\n\n/**\n * Which flow triggered this graph invocation. Determines initial persist status,\n * park-window timeout, streaming behavior, and whether AbortSignal is honored.\n *\n * - 'ambient' (default): queue-driven. Persists at the trigger default of\n * `pending` unless `options.initialStatus` overrides (the queue worker\n * passes `'latent'`, chat-bound ambient discovery passes `'draft'`). 5-min\n * park window, no streaming, ignores abort.\n * - 'orchestrator': chat-driven. Persists at the trigger default of\n * `negotiating` unless `options.initialStatus` overrides. 60s park window,\n * streams `opportunity_draft_ready` events, honors abort.\n *\n * See {@link resolveInitialStatus} for the exact fallback used when\n * `options.initialStatus` is undefined.\n */\nexport type OpportunityTrigger = 'ambient' | 'orchestrator';\n\n/**\n * Resolves the initial status for opportunities created in the persist node.\n *\n * Explicit `options.initialStatus` always wins (callers like the chat tool or\n * maintenance scripts override per-call). When the caller leaves it\n * undefined, the trigger drives the default:\n * - 'orchestrator' → 'negotiating' (chat-driven; negotiations run before the\n * user sees a draft card).\n * - 'ambient' (or any other trigger) → 'pending' (long-standing default for\n * queue- and intent-driven discovery).\n *\n * Lives here rather than in opportunity.graph.ts so unit tests can exercise\n * it without pulling in the full graph (and the evaluator's LLM requirements).\n *\n * @param trigger - The graph invocation's trigger\n * @param explicit - Caller-supplied initial status from options.initialStatus\n */\nexport function resolveInitialStatus(\n trigger: OpportunityTrigger,\n explicit: OpportunityStatus | undefined,\n): OpportunityStatus {\n if (explicit !== undefined) return explicit;\n return trigger === 'orchestrator' ? 'negotiating' : 'pending';\n}\n\n/**\n * Options passed to the graph\n */\nexport interface OpportunityGraphOptions {\n /** Initial status for created opportunities (default: 'pending') */\n initialStatus?: OpportunityStatus;\n /** Minimum score threshold (default: 50) */\n minScore?: number;\n /** Maximum opportunities to return (default: 20) */\n limit?: number;\n /** Pre-inferred lenses (if not provided, lens inference runs automatically in HyDE graph) */\n lenses?: Lens[];\n /** User's search query for HyDE generation */\n hydeDescription?: string;\n /** Existing opportunities summary for evaluator deduplication */\n existingOpportunities?: string;\n /** Chat session ID for draft opportunities; stored as context.conversationId for visibility filtering. */\n conversationId?: string;\n /**\n * MCP-only: cap the negotiate-phase wall-clock at this many milliseconds.\n * When set, `negotiateNode` races `negotiateCandidates(...)` against a timer;\n * if the timer wins, the node returns early with a `timed_out` trace and the\n * unawaited negotiation chains finalize each opp's DB status in the\n * background. Set to 20_000 by the MCP `discover_opportunities` handler.\n * Chat, ambient queue, and all other callers omit this — existing behavior.\n */\n negotiateTimeoutMs?: number;\n}\n\n/**\n * Opportunity Graph State Annotation\n */\nexport const OpportunityGraphState = Annotation.Root({\n // ─── Input Fields (Required) ───\n userId: Annotation<Id<'users'>>({\n reducer: (curr, next) => next ?? curr,\n default: () => '' as Id<'users'>,\n }),\n\n searchQuery: Annotation<string | undefined>({\n reducer: (curr, next) => next ?? curr,\n default: () => undefined,\n }),\n\n networkId: Annotation<Id<'networks'> | undefined>({\n reducer: (curr, next) => next ?? curr,\n default: () => undefined,\n }),\n\n /**\n * Optional set of indexes discovery may search within (e.g. a network-scoped\n * agent's reachable networks: the bound network plus the user's personal network).\n * The scope node intersects this with the user's actual memberships. Ignored\n * when `networkId` is set (single-network override). When unset, discovery\n * spans all of the user's networks.\n */\n indexScope: Annotation<Id<'networks'>[] | undefined>({\n reducer: (curr, next) => next ?? curr,\n default: () => undefined,\n }),\n\n /** Optional intent to use as discovery source and for triggeredBy. When set, used for search text (if query empty) and persist. */\n triggerIntentId: Annotation<Id<'intents'> | undefined>({\n reducer: (curr, next) => next ?? curr,\n default: () => undefined,\n }),\n\n /** Optional: restrict discovery to this specific user ID only (direct connection). */\n targetUserId: Annotation<Id<'users'> | undefined>({\n reducer: (curr, next) => next ?? curr,\n default: () => undefined,\n }),\n\n /** Optional: discover on behalf of this user (introducer flow). When set, prep/eval use this user's profile/intents; userId becomes the introducer. */\n onBehalfOfUserId: Annotation<Id<'users'> | undefined>({\n reducer: (curr, next) => next ?? curr,\n default: () => undefined,\n }),\n\n options: Annotation<OpportunityGraphOptions>({\n reducer: (curr, next) => next ?? curr,\n default: () => ({}),\n }),\n\n /**\n * Which flow triggered this graph invocation. See {@link OpportunityTrigger}\n * for the exact branch behavior and {@link resolveInitialStatus} for the\n * persist default when `options.initialStatus` is unset.\n *\n * - 'ambient' (default): queue-driven, persist default `pending`, 5-min\n * park window, no streaming, ignores abort.\n * - 'orchestrator': chat-driven, persist default `negotiating`, 60s park\n * window, streams `opportunity_draft_ready` events, honors abort.\n */\n trigger: Annotation<OpportunityTrigger>({\n reducer: (curr, next) => next ?? curr,\n default: () => 'ambient',\n }),\n\n /**\n * Accepted opportunities the persist node discovered between the discoverer\n * and a candidate actor (same pair, status='accepted'). The orchestrator\n * branch populates this so the discover_opportunities tool (Task 7) can tell\n * the LLM \"these pairs are already connected, surface the existing chat\n * rather than creating a new draft\". Always empty for the ambient trigger.\n *\n * Left intentionally minimal — conversationId/URL resolution happens at\n * Start Chat time (Task 8), not here.\n */\n dedupAlreadyAccepted: Annotation<Array<{\n opportunityId: string;\n counterpartyUserId: string;\n }>>({\n reducer: (curr, next) => next ?? curr,\n default: () => [],\n }),\n\n /**\n * Operation mode controls graph flow:\n * - 'create': Existing discover pipeline (Prep → Scope → Discovery → Evaluation → Ranking → Persist)\n * - 'create_introduction': Introduction path (validation → evaluation → persist) for chat-driven intros\n * - 'continue_discovery': Pagination path (Prep → Evaluation → Ranking → Persist) using pre-loaded candidates\n * - 'read': List opportunities filtered by userId and optionally networkId (fast path)\n * - 'update': Change opportunity status (accept, reject, etc.)\n * - 'delete': Expire/archive an opportunity\n * - 'send': Promote latent opportunity to pending + queue notification\n * - 'negotiate_existing': Load an existing opportunity by opportunityId and run bilateral negotiation.\n * Used after introducer approval to trigger the normal negotiation flow.\n * - 'approve_introduction': Mark the caller as having approved a latent introducer opportunity,\n * then enqueue a negotiate_existing job for that opportunity.\n *\n * Defaults to 'create' for backward compatibility.\n */\n operationMode: Annotation<'create' | 'create_introduction' | 'continue_discovery' | 'read' | 'update' | 'delete' | 'send' | 'negotiate_existing' | 'approve_introduction'>({\n reducer: (curr, next) => next ?? curr,\n default: () => 'create' as const,\n }),\n\n /** Introduction mode: pre-gathered entities (profiles + intents per party). */\n introductionEntities: Annotation<EvaluatorEntity[]>({\n reducer: (curr, next) => next ?? curr,\n default: () => [],\n }),\n\n /** Introduction mode: optional hint from the introducer. */\n introductionHint: Annotation<string | undefined>({\n reducer: (curr, next) => next ?? curr,\n default: () => undefined,\n }),\n\n /** When set (e.g. chat scope), networkId must match this. */\n requiredNetworkId: Annotation<Id<'networks'> | undefined>({\n reducer: (curr, next) => next ?? curr,\n default: () => undefined,\n }),\n\n /** Set by intro_evaluation; used by persist to build manual detection and introducer actor. */\n introductionContext: Annotation<{ createdByName?: string } | undefined>({\n reducer: (curr, next) => next ?? curr,\n default: () => undefined,\n }),\n\n /** Target opportunity ID for update/delete/send modes. */\n opportunityId: Annotation<string | undefined>({\n reducer: (curr, next) => next ?? curr,\n default: () => undefined,\n }),\n\n /** New status for update mode (e.g. 'accepted', 'rejected'). */\n newStatus: Annotation<string | undefined>({\n reducer: (curr, next) => next ?? curr,\n default: () => undefined,\n }),\n\n // ─── Intermediate Fields (Accumulated) ───\n\n /** User's indexed intents with hyde documents (from prep) */\n indexedIntents: Annotation<IndexedIntent[]>({\n reducer: (curr, next) => next ?? curr,\n default: () => [],\n }),\n\n /** User's network memberships (from prep) */\n userNetworks: Annotation<Id<'networks'>[]>({\n reducer: (curr, next) => next ?? curr,\n default: () => [],\n }),\n\n /** Target indexes to search within (from scope) */\n targetNetworks: Annotation<TargetNetwork[]>({\n reducer: (curr, next) => next ?? curr,\n default: () => [],\n }),\n\n /** Per-index relevancy scores for dedup tie-breaking. Background path: from intent_indexes. Chat path: transient from IntentIndexer. */\n indexRelevancyScores: Annotation<Record<string, number>>({\n reducer: (curr, next) => next ?? curr,\n default: () => ({}),\n }),\n\n /** Whether discovery used intent (path A) or user context (path B/C). Used by persist for triggeredBy. In-memory routing state only; never persisted. */\n discoverySource: Annotation<'intent' | 'context'>({\n reducer: (curr, next) => next ?? curr,\n default: () => 'intent',\n }),\n\n /** Resolved intent ID used for this discovery run (when discoverySource is 'intent'). Set by intent-resolution. */\n resolvedTriggerIntentId: Annotation<Id<'intents'> | undefined>({\n reducer: (curr, next) => next ?? curr,\n default: () => undefined,\n }),\n\n /** Asker's profile (from prep). Used for profile-as-source discovery and evaluation. */\n sourceProfile: Annotation<SourceProfileData | null>({\n reducer: (curr, next) => next ?? curr,\n default: () => null,\n }),\n\n /** User's active premises with embeddings (from prep). Used for premise-to-premise discovery path D. */\n sourcePremises: Annotation<Array<{ premiseId: Id<'premises'>; embedding: number[] }>>({\n reducer: (curr, next) => next ?? curr,\n default: () => [],\n }),\n\n /** User context embeddings per network (from prep). Used for context-to-intent discovery. */\n sourceContexts: Annotation<Array<{ contextId: string; networkId: Id<'networks'>; embedding: number[] }>>({\n reducer: (curr, next) => next ?? curr,\n default: () => [],\n }),\n\n /** Resolved intent is in at least one target index (path A vs C). */\n resolvedIntentInIndex: Annotation<boolean>({\n reducer: (curr, next) => next ?? curr,\n default: () => false,\n }),\n\n /** Create-intent signal: when true, tool should return createIntentSuggested so agent can auto-call create_intent. */\n createIntentSuggested: Annotation<boolean>({\n reducer: (curr, next) => next ?? curr,\n default: () => false,\n }),\n\n /** Suggested description for create_intent when createIntentSuggested is true. */\n suggestedIntentDescription: Annotation<string | undefined>({\n reducer: (curr, next) => next ?? curr,\n default: () => undefined,\n }),\n\n /** HyDE embeddings per lens label (from discovery) */\n hydeEmbeddings: Annotation<Record<string, number[]>>({\n reducer: (curr, next) => next ?? curr,\n default: () => ({}),\n }),\n\n /** Candidate matches from semantic search (from discovery) */\n candidates: Annotation<CandidateMatch[]>({\n reducer: (curr, next) => next ?? curr,\n default: () => [],\n }),\n\n /** Candidates not yet evaluated (for pagination -- cached in Redis by caller). */\n remainingCandidates: Annotation<CandidateMatch[]>({\n reducer: (curr, next) => next ?? curr,\n default: () => [],\n }),\n\n /** Discovery session ID for pagination (maps to Redis cache key). */\n discoveryId: Annotation<string | null>({\n reducer: (curr, next) => next ?? curr,\n default: () => null,\n }),\n\n /** Evaluated candidates with scores (from evaluation; legacy) */\n evaluatedCandidates: Annotation<EvaluatedCandidate[]>({\n reducer: (curr, next) => next ?? curr,\n default: () => [],\n }),\n\n /** Evaluated opportunities with actors (from entity-bundle evaluator) */\n evaluatedOpportunities: Annotation<EvaluatedOpportunity[]>({\n reducer: (curr, next) => next ?? curr,\n default: () => [],\n }),\n\n // ─── Output Fields (Overwrite per turn) ───\n\n /** Final ranked and persisted opportunities */\n opportunities: Annotation<Opportunity[]>({\n reducer: (curr, next) => next,\n default: () => [],\n }),\n\n /** Discovery path: pairs skipped because an opportunity already exists between viewer and candidate (no duplicate created). */\n existingBetweenActors: Annotation<Array<{\n candidateUserId: Id<'users'>;\n networkId: Id<'networks'>;\n existingOpportunityId?: Id<'opportunities'>;\n existingStatus?: OpportunityStatus;\n }>>({\n reducer: (curr, next) => next ?? curr,\n default: () => [],\n }),\n\n /** Error message if any step fails */\n error: Annotation<string | undefined>({\n reducer: (curr, next) => next,\n default: () => undefined,\n }),\n\n /** Output for read mode: enriched list of opportunities. */\n readResult: Annotation<{\n count: number;\n message?: string;\n opportunities: Array<{\n id: string;\n indexName: string;\n connectedWith: string[];\n suggestedBy: string | null;\n reasoning: string;\n status: string;\n category: string;\n confidence: number | null;\n source: string | null;\n }>;\n } | undefined>({\n reducer: (curr, next) => next,\n default: () => undefined,\n }),\n\n /** Output for update/delete/send modes. */\n mutationResult: Annotation<{\n success: boolean;\n message?: string;\n opportunityId?: string;\n notified?: string[];\n conversationId?: string;\n error?: string;\n } | undefined>({\n reducer: (curr, next) => next,\n default: () => undefined,\n }),\n\n // ─── Trace Output ───\n\n /**\n * Accumulated trace entries from each graph node.\n * Used for observability: surfaces internal processing steps (search query, HyDE strategies,\n * candidates found, evaluation results) to the frontend.\n */\n trace: Annotation<Array<{ node: string; detail?: string; data?: Record<string, unknown> }>>({\n reducer: (curr, next) => [...curr, ...(next || [])],\n default: () => [],\n }),\n\n /** Timing records for each agent invocation within this graph run. */\n agentTimings: Annotation<DebugMetaAgent[]>({\n reducer: (acc, val) => [...acc, ...val],\n default: () => [],\n }),\n\n /**\n * Per-candidate negotiation records captured by `negotiateNode`. Populated\n * regardless of accept/reject so the question generator sees a complete\n * picture. Populated for ALL triggers (ambient + orchestrator) since the\n * negotiate node's `onCandidateResolved` hook is unconditional; only the\n * orchestrator streaming side-effects (opportunity_draft_ready emission)\n * are trigger-gated. Empty when the negotiate node was skipped (no\n * opportunities to negotiate).\n */\n discoveryNegotiations: Annotation<DiscoveryNegotiation[]>({\n reducer: (curr, next) => [...curr, ...(next || [])],\n default: () => [],\n }),\n\n /** Aggregate counters across `discoveryNegotiations`. Built in the negotiate node. */\n discoverySummary: Annotation<DiscoverySummary | null>({\n reducer: (_curr, next) => next ?? null,\n default: () => null,\n }),\n});\n"]}
@@ -1 +1 @@
1
- {"version":3,"file":"opportunity.tools.d.ts","sourceRoot":"/","sources":["opportunity/opportunity.tools.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"opportunity.tools.d.ts","sourceRoot":"/","sources":["opportunity/opportunity.tools.ts"],"names":[],"mappings":"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"}
@@ -1,7 +1,7 @@
1
1
  import { z } from "zod";
2
2
  import { requestContext } from "../shared/observability/request-context.js";
3
3
  import { success, error, UUID_REGEX } from "../shared/agent/tool.helpers.js";
4
- import { deriveDiscoveryNetworkIds, focusedNetworkId, focusedNetworkLabel } from "../shared/agent/tool.scope.js";
4
+ import { deriveDiscoveryNetworkIds, focusedIntentId, focusedNetworkId, focusedNetworkLabel } from "../shared/agent/tool.scope.js";
5
5
  import { MINIMAL_MAIN_TEXT_MAX_CHARS, getPrimaryActionLabel, SECONDARY_ACTION_LABEL } from "./opportunity.labels.js";
6
6
  import { viewerCentricCardSummary, narratorRemarkFromReasoning, stripUuids } from "./opportunity.presentation.js";
7
7
  import { runDiscoverFromQuery, continueDiscovery } from "./opportunity.discover.js";
@@ -177,6 +177,13 @@ const UPDATE_OPPORTUNITY_BLOCKED_STATUSES = new Set([
177
177
  "expired",
178
178
  "negotiating",
179
179
  ]);
180
+ function matchesSelectedIntentScope(opportunity, viewerId, scope) {
181
+ if (scope?.scopeType !== 'intent' || !scope.scopeId)
182
+ return true;
183
+ if (opportunity.detection?.triggeredBy === scope.scopeId)
184
+ return true;
185
+ return opportunity.actors.some((actor) => actor.userId === viewerId && actor.intent === scope.scopeId);
186
+ }
180
187
  /**
181
188
  * Maximum number of opportunity cards to show per chat response.
182
189
  * Sized for `selectByComposition` to fill both feed buckets — up to 3
@@ -354,7 +361,7 @@ export function buildOpportunityPresentation(cards, opts) {
354
361
  * Encoded as JSON of a normalized object — NOT a delimiter join — so a
355
362
  * user-supplied string containing the delimiter can never make two distinct
356
363
  * requests collide. `hint` and each entity's `networkId` are included because
357
- * they change the discovery result (different reason / different shared index),
364
+ * they change the discovery result (different reason / different shared network),
358
365
  * so requests that differ only in those must NOT coalesce. Natural-language
359
366
  * fields (`searchQuery`, `hint`) are lowercased; identifiers and the opaque
360
367
  * `continueFrom` pagination token are only trimmed (case-sensitive) so distinct
@@ -414,9 +421,9 @@ export function createOpportunityTools(defineTool, deps) {
414
421
  "Opportunities are the core output of the discovery engine, representing potential valuable connections between people.\n\n" +
415
422
  "**NOT for person lookup** — use read_user_contexts(query=name) to find people by name.\n\n" +
416
423
  "**Four modes:**\n" +
417
- "1. **Discovery** (most common): pass `searchQuery` and/or `networkId`. The system finds other users in shared indexes " +
424
+ "1. **Discovery** (most common): pass `searchQuery` and/or `networkId`. The system finds other users in shared networkes " +
418
425
  "whose intents semantically complement the query. Uses HyDE embeddings and LLM evaluation for scoring.\n" +
419
- "2. **Introduction**: pass `partyUserIds` (2+ user IDs) + `entities` (pre-gathered profiles and intents from shared indexes). " +
426
+ "2. **Introduction**: pass `partyUserIds` (2+ user IDs) + `entities` (pre-gathered profiles and intents from shared networkes). " +
420
427
  "You MUST call read_user_contexts and read_intents for each party BEFORE calling this. " +
421
428
  "Optionally pass `hint` with the user's reason for the introduction.\n" +
422
429
  "3. **Direct connection**: pass `targetUserId` + `searchQuery`. Creates an opportunity between the current user and one specific person.\n" +
@@ -434,13 +441,13 @@ export function createOpportunityTools(defineTool, deps) {
434
441
  "Do NOT call create_intent for these phrasings — create_intent is only for when the user explicitly " +
435
442
  "asks to \"create\", \"save\", \"add\", or \"remember\" a signal.\n\n" +
436
443
  "**Personal-index scoping.** When the user says \"in my network\", \"from my contacts\", \"people I know\", " +
437
- "or similar scoping language, pass the user's personal index ID (from memberships where `isPersonal: true`) " +
438
- "as `networkId`. The personal index contains the user's contacts — scoping discovery to it restricts " +
444
+ "or similar scoping language, pass the user's personal network ID (from memberships where `isPersonal: true`) " +
445
+ "as `networkId`. The personal network contains the user's contacts — scoping discovery to it restricts " +
439
446
  "results to people the user already knows. Without this scoping language, omit networkId to let discovery " +
440
- "run across all indexes.\n\n" +
447
+ "run across all networks.\n\n" +
441
448
  "**Introduction mode prerequisites.** When using `partyUserIds` + `entities`, YOU must pre-fetch each party's " +
442
449
  "profile and intents before calling this tool. The entities array must include each party's userId, profile, " +
443
- "intents from shared indexes, and the shared networkId. Call read_user_contexts, read_network_memberships, " +
450
+ "intents from shared networkes, and the shared networkId. Call read_user_contexts, read_network_memberships, " +
444
451
  "and read_intents for both parties first. The introducer (current user) must NOT appear in entities.\n\n" +
445
452
  "**Signal-visibility follow-up.** If the response includes `suggestIntentCreationForVisibility: true` and " +
446
453
  "`suggestedIntentDescription`, after presenting opportunity cards ask the user ONCE whether they'd also like " +
@@ -459,7 +466,7 @@ export function createOpportunityTools(defineTool, deps) {
459
466
  networkId: z
460
467
  .string()
461
468
  .optional()
462
- .describe("Index UUID to scope discovery to a specific community. Get from read_networks. In an index-scoped chat, omitting this runs discovery only in the scoped community; pass the personal index ID (from read_networks, isPersonal=true) only when the user explicitly asks to discover among contacts."),
469
+ .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."),
463
470
  intentId: z
464
471
  .string()
465
472
  .optional()
@@ -501,13 +508,13 @@ export function createOpportunityTools(defineTool, deps) {
501
508
  .optional(),
502
509
  networkId: z
503
510
  .string()
504
- .describe("Shared index this entity's data comes from (required for intro mode)"),
511
+ .describe("Shared network this entity's data comes from (required for intro mode)"),
505
512
  }))
506
513
  .optional()
507
514
  .describe("Introduction mode: pre-gathered profile and intent data for each party being introduced. " +
508
- "Each entry needs userId, networkId (the shared index), and optionally profile (name, bio, skills, interests) and intents (intentId, payload). " +
515
+ "Each entry needs userId, networkId (the shared network), and optionally profile (name, bio, skills, interests) and intents (intentId, payload). " +
509
516
  "Gather this data by calling read_user_contexts and read_intents for each party BEFORE calling discover_opportunities. " +
510
- "All entities must share the same networkId (the shared index where both parties are members)."),
517
+ "All entities must share the same networkId (the shared network where both parties are members)."),
511
518
  hint: z
512
519
  .string()
513
520
  .optional()
@@ -515,9 +522,9 @@ export function createOpportunityTools(defineTool, deps) {
515
522
  "'complementary skills for a startup'). Helps the evaluator produce better match reasoning."),
516
523
  }),
517
524
  handler: async ({ context, query }) => {
518
- const scopedNetworkId = focusedNetworkId(context);
525
+ const scopedNetworkId = focusedNetworkId(context) ?? context.networkId?.trim();
519
526
  const scopedIndexLabel = focusedNetworkLabel(context);
520
- // Strict scope enforcement: when chat is index-scoped, only allow that index
527
+ // Strict scope enforcement: when chat is network-scoped, only allow that index
521
528
  if (scopedNetworkId &&
522
529
  query.networkId?.trim() &&
523
530
  query.networkId.trim() !== scopedNetworkId) {
@@ -865,7 +872,7 @@ export function createOpportunityTools(defineTool, deps) {
865
872
  }
866
873
  else if (context.scopeType === 'network' && context.scopeId) {
867
874
  // Scoped chat: discovery is focused-network only. Self-owned writes may
868
- // include personal indexes, but opportunity visibility must not.
875
+ // include personal networkes, but opportunity visibility must not.
869
876
  const scopedDiscoveryIds = deriveDiscoveryNetworkIds({
870
877
  memberships: context.userNetworks,
871
878
  scopeType: context.scopeType,
@@ -878,7 +885,7 @@ export function createOpportunityTools(defineTool, deps) {
878
885
  indexScope = [scopedNetworkId];
879
886
  }
880
887
  else {
881
- // No scope - use all indexes (only in unscoped chat)
888
+ // No scope - use all networks (only in unscoped chat)
882
889
  const _scopeGraphStart = Date.now();
883
890
  const _scopeIndexTraceEmitter = requestContext.getStore()?.traceEmitter;
884
891
  _scopeIndexTraceEmitter?.({ type: "graph_start", name: "index" });
@@ -1289,7 +1296,7 @@ export function createOpportunityTools(defineTool, deps) {
1289
1296
  const listOpportunities = defineTool({
1290
1297
  name: "list_opportunities",
1291
1298
  description: "Lists the authenticated user's actionable opportunities (discovered connections). Returns opportunity cards ready for display.\n\n" +
1292
- "**What are opportunities?** Matches between users whose intents complement each other within shared indexes. " +
1299
+ "**What are opportunities?** Matches between users whose intents complement each other within shared networkes. " +
1293
1300
  "Each opportunity has a status: draft (not yet sent), pending (sent, awaiting response), accepted, rejected, or expired.\n\n" +
1294
1301
  "**What this returns:** Only draft and pending opportunities — the ones the user can still act on. " +
1295
1302
  "Accepted, rejected, and expired ones are not surfaced through this tool.\n\n" +
@@ -1300,16 +1307,24 @@ export function createOpportunityTools(defineTool, deps) {
1300
1307
  networkId: z
1301
1308
  .string()
1302
1309
  .optional()
1303
- .describe("Index UUID to filter opportunities to a specific community. Get from read_networks. Defaults to the scoped index in index-scoped chats. Omit to see opportunities across all indexes."),
1310
+ .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."),
1311
+ scopeType: z
1312
+ .enum(['intent'])
1313
+ .optional()
1314
+ .describe("Optional selected scope type. Use 'intent' to narrow listed opportunities to a selected intent."),
1315
+ scopeId: z
1316
+ .string()
1317
+ .optional()
1318
+ .describe("Selected intent UUID when scopeType is 'intent'. Ignored only when absent."),
1304
1319
  includeDigestMarkers: z
1305
1320
  .boolean()
1306
1321
  .optional()
1307
1322
  .describe("Internal scheduled-digest mode only. When true, includes hidden delivery markers so the digest send pass can confirm only edited-in opportunities."),
1308
1323
  }),
1309
1324
  handler: async ({ context, query }) => {
1310
- const scopedNetworkId = focusedNetworkId(context);
1325
+ const scopedNetworkId = focusedNetworkId(context) ?? context.networkId?.trim();
1311
1326
  const scopedIndexLabel = focusedNetworkLabel(context);
1312
- // Strict scope enforcement: when chat is index-scoped, only allow that index
1327
+ // Strict scope enforcement: when chat is network-scoped, only allow that index
1313
1328
  if (scopedNetworkId &&
1314
1329
  query.networkId?.trim() &&
1315
1330
  query.networkId.trim() !== scopedNetworkId) {
@@ -1319,6 +1334,25 @@ export function createOpportunityTools(defineTool, deps) {
1319
1334
  if (effectiveIndexId && !UUID_REGEX.test(effectiveIndexId)) {
1320
1335
  return error("Invalid network ID format.");
1321
1336
  }
1337
+ const contextIntentId = focusedIntentId(context);
1338
+ const rawScopeId = query.scopeId?.trim() || undefined;
1339
+ if (query.scopeType === 'intent' && !rawScopeId) {
1340
+ return error("scopeId required when scopeType is intent.");
1341
+ }
1342
+ if (!query.scopeType && rawScopeId) {
1343
+ return error("scopeType=intent required when scopeId is provided.");
1344
+ }
1345
+ if (rawScopeId && !UUID_REGEX.test(rawScopeId)) {
1346
+ return error("Invalid scope ID format.");
1347
+ }
1348
+ if (contextIntentId && rawScopeId && contextIntentId !== rawScopeId) {
1349
+ return error("This chat is scoped to a different intent.");
1350
+ }
1351
+ const effectiveIntentScope = contextIntentId
1352
+ ? { scopeType: 'intent', scopeId: contextIntentId }
1353
+ : query.scopeType === 'intent' && rawScopeId
1354
+ ? { scopeType: 'intent', scopeId: rawScopeId }
1355
+ : {};
1322
1356
  // The MCP/chat surface exposes actionable opportunities.
1323
1357
  // `latent` is included so the introducer-as-viewer can see their unapproved
1324
1358
  // connector-flow cards ("do you know someone for X?"). Other latent visibility
@@ -1331,6 +1365,7 @@ export function createOpportunityTools(defineTool, deps) {
1331
1365
  // sort order can fill the whole window and starve the other section.
1332
1366
  const fetched = await database.getOpportunitiesForUser(context.userId, {
1333
1367
  networkId: effectiveIndexId,
1368
+ ...effectiveIntentScope,
1334
1369
  statuses,
1335
1370
  limit: CHAT_FETCH_LIMIT,
1336
1371
  });
@@ -1399,8 +1434,10 @@ export function createOpportunityTools(defineTool, deps) {
1399
1434
  if (isDigestMode && deduped.length > 0) {
1400
1435
  const acceptedCounterpartIds = new Set();
1401
1436
  try {
1437
+ // effectiveIntentScope scopes the statuses: ["accepted"] suppression fetch.
1402
1438
  const acceptedOpps = await database.getOpportunitiesForUser(context.userId, {
1403
1439
  ...(effectiveIndexId ? { networkId: effectiveIndexId } : {}),
1440
+ ...effectiveIntentScope,
1404
1441
  statuses: ["accepted"],
1405
1442
  limit: ACCEPTED_SUPPRESSION_FETCH_LIMIT,
1406
1443
  });
@@ -1789,12 +1826,39 @@ export function createOpportunityTools(defineTool, deps) {
1789
1826
  .enum(["pending", "accepted", "rejected", "expired"])
1790
1827
  .describe("New status: 'pending' = send the draft to the other party, 'accepted' = accept the connection, " +
1791
1828
  "'rejected' = decline, 'expired' = mark as timed out."),
1829
+ scopeType: z
1830
+ .enum(['intent'])
1831
+ .optional()
1832
+ .describe("Optional selected scope type. Use 'intent' to require this opportunity to belong to a selected intent."),
1833
+ scopeId: z
1834
+ .string()
1835
+ .optional()
1836
+ .describe("Selected intent UUID when scopeType is 'intent'. Must match the chat's focused intent when one exists."),
1792
1837
  }),
1793
1838
  handler: async ({ context, query }) => {
1794
1839
  const opportunityId = query.opportunityId?.trim();
1795
1840
  if (!opportunityId || !UUID_REGEX.test(opportunityId)) {
1796
1841
  return error("Valid opportunityId required.");
1797
1842
  }
1843
+ const contextIntentId = focusedIntentId(context);
1844
+ const rawScopeId = query.scopeId?.trim() || undefined;
1845
+ if (query.scopeType === 'intent' && !rawScopeId) {
1846
+ return error("scopeId required when scopeType is intent.");
1847
+ }
1848
+ if (!query.scopeType && rawScopeId) {
1849
+ return error("scopeType=intent required when scopeId is provided.");
1850
+ }
1851
+ if (rawScopeId && !UUID_REGEX.test(rawScopeId)) {
1852
+ return error("Invalid scope ID format.");
1853
+ }
1854
+ if (contextIntentId && rawScopeId && contextIntentId !== rawScopeId) {
1855
+ return error("This chat is scoped to a different intent.");
1856
+ }
1857
+ const effectiveIntentScope = contextIntentId
1858
+ ? { scopeType: 'intent', scopeId: contextIntentId }
1859
+ : query.scopeType === 'intent' && rawScopeId
1860
+ ? { scopeType: 'intent', scopeId: rawScopeId }
1861
+ : {};
1798
1862
  // Always fetch the opportunity — needed for actor guard and state machine
1799
1863
  const opportunity = await systemDb.getOpportunity(opportunityId);
1800
1864
  if (!opportunity) {
@@ -1811,18 +1875,21 @@ export function createOpportunityTools(defineTool, deps) {
1811
1875
  if (UPDATE_OPPORTUNITY_BLOCKED_STATUSES.has(opportunity.status)) {
1812
1876
  return error(`This opportunity is already ${opportunity.status} and cannot be updated.`);
1813
1877
  }
1814
- // Strict scope enforcement: when chat is index-scoped, the caller's own
1878
+ // Strict scope enforcement: when chat is network-scoped, the caller's own
1815
1879
  // actor entry on this opportunity must be anchored on the bound network.
1816
1880
  // Mirrors the per-actor filter in getOpportunitiesForUser — relying on
1817
1881
  // a focus-scope id or any-actor matches would let a counterpart's network
1818
1882
  // presence shadow a viewer whose own actor is elsewhere.
1819
- const scopedNetworkId = focusedNetworkId(context);
1883
+ const scopedNetworkId = focusedNetworkId(context) ?? context.networkId?.trim();
1820
1884
  if (scopedNetworkId) {
1821
1885
  const callerOnBoundNetwork = opportunity.actors?.some((a) => a.userId === context.userId && a.networkId === scopedNetworkId);
1822
1886
  if (!callerOnBoundNetwork) {
1823
1887
  return error("Opportunity not found.");
1824
1888
  }
1825
1889
  }
1890
+ if (!matchesSelectedIntentScope(opportunity, context.userId, effectiveIntentScope)) {
1891
+ return error("Opportunity not found.");
1892
+ }
1826
1893
  const isSend = query.status === "pending";
1827
1894
  const _updateGraphStart = Date.now();
1828
1895
  const _updateTraceEmitter = requestContext.getStore()?.traceEmitter;