@indexnetwork/protocol 0.17.0-rc.43.1 → 0.18.0-rc.45.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 (40) hide show
  1. package/dist/chat/chat.prompt.js +3 -3
  2. package/dist/chat/chat.prompt.modules.js +1 -1
  3. package/dist/contact/contact.tools.js +2 -2
  4. package/dist/contact/contact.tools.js.map +1 -1
  5. package/dist/intent/intent.tools.js +5 -5
  6. package/dist/intent/intent.tools.js.map +1 -1
  7. package/dist/mcp/mcp.server.d.ts +9 -0
  8. package/dist/mcp/mcp.server.d.ts.map +1 -1
  9. package/dist/mcp/mcp.server.js +32 -1
  10. package/dist/mcp/mcp.server.js.map +1 -1
  11. package/dist/network/network.graph.d.ts +33 -22
  12. package/dist/network/network.graph.d.ts.map +1 -1
  13. package/dist/network/network.graph.js +20 -6
  14. package/dist/network/network.graph.js.map +1 -1
  15. package/dist/network/network.state.d.ts +9 -6
  16. package/dist/network/network.state.d.ts.map +1 -1
  17. package/dist/network/network.state.js.map +1 -1
  18. package/dist/network/network.tools.d.ts.map +1 -1
  19. package/dist/network/network.tools.js +12 -13
  20. package/dist/network/network.tools.js.map +1 -1
  21. package/dist/opportunity/opportunity.graph.d.ts +38 -8
  22. package/dist/opportunity/opportunity.graph.d.ts.map +1 -1
  23. package/dist/opportunity/opportunity.graph.js +177 -3
  24. package/dist/opportunity/opportunity.graph.js.map +1 -1
  25. package/dist/opportunity/opportunity.state.d.ts +5 -1
  26. package/dist/opportunity/opportunity.state.d.ts.map +1 -1
  27. package/dist/opportunity/opportunity.state.js +4 -0
  28. package/dist/opportunity/opportunity.state.js.map +1 -1
  29. package/dist/profile/profile.tools.d.ts.map +1 -1
  30. package/dist/profile/profile.tools.js +9 -4
  31. package/dist/profile/profile.tools.js.map +1 -1
  32. package/dist/shared/agent/tool.factory.d.ts.map +1 -1
  33. package/dist/shared/agent/tool.factory.js +1 -1
  34. package/dist/shared/agent/tool.factory.js.map +1 -1
  35. package/dist/shared/agent/tool.helpers.d.ts +2 -0
  36. package/dist/shared/agent/tool.helpers.d.ts.map +1 -1
  37. package/dist/shared/agent/tool.helpers.js.map +1 -1
  38. package/dist/shared/interfaces/database.interface.d.ts +10 -2
  39. package/dist/shared/interfaces/database.interface.d.ts.map +1 -1
  40. package/package.json +1 -1
@@ -73,7 +73,13 @@ export class OpportunityGraphFactory {
73
73
  * agent (long timeout) or fall back to the system agent immediately
74
74
  * (short timeout). Without it, the chat path always uses a short timeout.
75
75
  */
76
- agentDispatcher) {
76
+ agentDispatcher,
77
+ /**
78
+ * Callback to enqueue a negotiate_existing job for an opportunity.
79
+ * When provided, negotiate_existing mode uses this to queue follow-up
80
+ * negotiations after introducer approval.
81
+ */
82
+ queueNegotiateExisting) {
77
83
  this.database = database;
78
84
  this.embedder = embedder;
79
85
  this.hydeGenerator = hydeGenerator;
@@ -81,6 +87,7 @@ export class OpportunityGraphFactory {
81
87
  this.queueNotification = queueNotification;
82
88
  this.negotiationGraph = negotiationGraph;
83
89
  this.agentDispatcher = agentDispatcher;
90
+ this.queueNegotiateExisting = queueNegotiateExisting;
84
91
  }
85
92
  createGraph() {
86
93
  const evaluatorAgent = this.optionalEvaluator ?? new OpportunityEvaluator();
@@ -1480,6 +1487,11 @@ export class OpportunityGraphFactory {
1480
1487
  // so the negotiation graph's finalize node can update its status from the outcome.
1481
1488
  const candidateEntries = state.opportunities
1482
1489
  .map(opp => {
1490
+ // Skip opportunities where any introducer exists but has not yet approved.
1491
+ const introducerActors = opp.actors
1492
+ .filter(a => a.role === 'introducer');
1493
+ if (introducerActors.length > 0 && !introducerActors.every(a => a.approved === true))
1494
+ return null;
1483
1495
  const candidateActor = opp.actors
1484
1496
  .find(a => a.userId !== discoveryUserId);
1485
1497
  if (!candidateActor)
@@ -2018,7 +2030,7 @@ export class OpportunityGraphFactory {
2018
2030
  ? evaluatorActors
2019
2031
  : [
2020
2032
  ...evaluatorActors,
2021
- { networkId: indexIdForActors, userId: state.userId, role: 'introducer' },
2033
+ { networkId: indexIdForActors, userId: state.userId, role: 'introducer', approved: false },
2022
2034
  ];
2023
2035
  data = {
2024
2036
  detection: {
@@ -2068,7 +2080,7 @@ export class OpportunityGraphFactory {
2068
2080
  ? evaluatorActors
2069
2081
  : [
2070
2082
  ...evaluatorActors,
2071
- { networkId: indexIdForActors, userId: state.userId, role: 'introducer' },
2083
+ { networkId: indexIdForActors, userId: state.userId, role: 'introducer', approved: false },
2072
2084
  ];
2073
2085
  const candidateUserId = evaluated.actors.find((a) => a.userId !== state.onBehalfOfUserId)?.userId;
2074
2086
  const overlapping = candidateUserId
@@ -2577,6 +2589,158 @@ export class OpportunityGraphFactory {
2577
2589
  }
2578
2590
  });
2579
2591
  };
2592
+ /**
2593
+ * Negotiate Existing Node: Load an existing opportunity by ID and run bilateral negotiation.
2594
+ * Used after introducer approval to trigger the normal negotiation flow for a latent opportunity.
2595
+ */
2596
+ const negotiateExistingNode = async (state) => {
2597
+ if (!state.opportunityId)
2598
+ return {};
2599
+ if (!this.negotiationGraph) {
2600
+ logger.warn('[Graph:NegotiateExisting] No negotiationGraph wired; skipping', {
2601
+ opportunityId: state.opportunityId,
2602
+ });
2603
+ return {};
2604
+ }
2605
+ try {
2606
+ const opp = await this.database.getOpportunity(state.opportunityId);
2607
+ if (!opp) {
2608
+ logger.warn('[Graph:NegotiateExisting] Opportunity not found', { opportunityId: state.opportunityId });
2609
+ return {};
2610
+ }
2611
+ const actors = opp.actors;
2612
+ const nonIntroducerActors = actors.filter(a => a.role !== 'introducer');
2613
+ // Find the sourceActor: non-introducer with role patient or party, fallback to first non-introducer
2614
+ const sourceActor = nonIntroducerActors.find(a => a.role === 'patient' || a.role === 'party')
2615
+ ?? nonIntroducerActors[0];
2616
+ if (!sourceActor) {
2617
+ logger.warn('[Graph:NegotiateExisting] No source actor found', { opportunityId: state.opportunityId });
2618
+ return {};
2619
+ }
2620
+ // Find the candidateActor: non-introducer that is NOT the sourceActor
2621
+ const candidateActor = nonIntroducerActors.find(a => a.userId !== sourceActor.userId);
2622
+ if (!candidateActor) {
2623
+ logger.warn('[Graph:NegotiateExisting] No candidate actor found', { opportunityId: state.opportunityId });
2624
+ return {};
2625
+ }
2626
+ // Load user data for both actors in parallel
2627
+ const [sourceUserAccount, sourceProfile, sourceIntents, candidateAccount, candidateProfile, candidateIntents] = await Promise.all([
2628
+ this.database.getUser(sourceActor.userId).catch(() => null),
2629
+ this.database.getProfile(sourceActor.userId).catch(() => null),
2630
+ this.database.getActiveIntents(sourceActor.userId).catch(() => []),
2631
+ this.database.getUser(candidateActor.userId).catch(() => null),
2632
+ this.database.getProfile(candidateActor.userId).catch(() => null),
2633
+ this.database.getActiveIntents(candidateActor.userId).catch(() => []),
2634
+ ]);
2635
+ const toNegIntent = (ai) => ({
2636
+ id: ai.id,
2637
+ title: ai.summary ?? '',
2638
+ description: ai.payload ?? '',
2639
+ confidence: 1,
2640
+ });
2641
+ const sourceUser = {
2642
+ id: sourceActor.userId,
2643
+ intents: sourceIntents.slice(0, 5).map(toNegIntent),
2644
+ profile: {
2645
+ name: sourceProfile?.identity?.name ?? sourceUserAccount?.name,
2646
+ bio: sourceProfile?.identity?.bio ?? sourceUserAccount?.intro ?? undefined,
2647
+ location: sourceProfile?.identity?.location ?? sourceUserAccount?.location ?? undefined,
2648
+ skills: sourceProfile?.attributes?.skills,
2649
+ interests: sourceProfile?.attributes?.interests,
2650
+ },
2651
+ };
2652
+ const candidateIntentsForNeg = candidateIntents.slice(0, 5).map(toNegIntent);
2653
+ const candidate = {
2654
+ userId: candidateActor.userId,
2655
+ opportunityId: opp.id,
2656
+ reasoning: opp.interpretation?.reasoning ?? '',
2657
+ valencyRole: candidateActor.role ?? 'peer',
2658
+ networkId: candidateActor.networkId,
2659
+ candidateUser: {
2660
+ id: candidateActor.userId,
2661
+ intents: candidateIntentsForNeg,
2662
+ profile: {
2663
+ name: candidateProfile?.identity?.name ?? candidateAccount?.name,
2664
+ bio: candidateProfile?.identity?.bio ?? candidateAccount?.intro ?? undefined,
2665
+ location: candidateProfile?.identity?.location ?? candidateAccount?.location ?? undefined,
2666
+ skills: candidateProfile?.attributes?.skills,
2667
+ interests: candidateProfile?.attributes?.interests,
2668
+ },
2669
+ },
2670
+ };
2671
+ // Load index context for the candidate's network
2672
+ const indexContextMap = new Map();
2673
+ if (candidate.networkId) {
2674
+ const ctx = await this.database.getNetworkMemberContext(candidate.networkId, sourceActor.userId).catch(() => null);
2675
+ const prompt = [ctx?.indexPrompt, ctx?.memberPrompt]
2676
+ .filter((v) => !!v?.trim())
2677
+ .join('\n\n');
2678
+ if (prompt)
2679
+ indexContextMap.set(candidate.networkId, prompt);
2680
+ }
2681
+ const acceptedResults = await negotiateCandidates(this.negotiationGraph, sourceUser, [candidate], { networkId: '', prompt: '' }, {
2682
+ maxTurns: 6,
2683
+ indexContextOverrides: indexContextMap,
2684
+ timeoutMs: AMBIENT_PARK_WINDOW_MS,
2685
+ trigger: 'ambient',
2686
+ });
2687
+ // Send notifications to non-introducer actors if negotiation was accepted
2688
+ if (acceptedResults.length > 0 && this.queueNotification) {
2689
+ for (const actor of nonIntroducerActors) {
2690
+ await this.queueNotification(opp.id, actor.userId, 'high').catch((err) => {
2691
+ logger.warn('[Graph:NegotiateExisting] Failed to queue notification', { actorId: actor.userId, error: err });
2692
+ });
2693
+ }
2694
+ }
2695
+ logger.info('[Graph:NegotiateExisting] Negotiation complete', {
2696
+ opportunityId: opp.id,
2697
+ accepted: acceptedResults.length > 0,
2698
+ });
2699
+ }
2700
+ catch (err) {
2701
+ logger.error('[Graph:NegotiateExisting] Failed', { opportunityId: state.opportunityId, error: err });
2702
+ return { error: `Failed to load opportunity: ${err instanceof Error ? err.message : String(err)}` };
2703
+ }
2704
+ return {};
2705
+ };
2706
+ /**
2707
+ * Node: Approve Introduction
2708
+ * Called by the introducer to approve a latent introducer-pattern opportunity.
2709
+ * Sets approved=true on the introducer actor (status stays latent), then
2710
+ * enqueues a negotiate_existing job so the parties negotiate normally.
2711
+ */
2712
+ const approveIntroductionNode = async (state) => {
2713
+ const { opportunityId, userId } = state;
2714
+ if (!opportunityId) {
2715
+ return { mutationResult: { success: false, error: 'opportunityId required for approve_introduction' } };
2716
+ }
2717
+ let opp;
2718
+ try {
2719
+ opp = await this.database.getOpportunity(opportunityId);
2720
+ }
2721
+ catch (err) {
2722
+ return { mutationResult: { success: false, error: `Failed to load opportunity: ${err instanceof Error ? err.message : String(err)}` } };
2723
+ }
2724
+ if (!opp) {
2725
+ return { mutationResult: { success: false, error: 'Opportunity not found' } };
2726
+ }
2727
+ const introducerActor = opp.actors
2728
+ .find(a => a.role === 'introducer' && a.userId === userId);
2729
+ if (!introducerActor) {
2730
+ return { mutationResult: { success: false, error: 'You are not the introducer for this opportunity' } };
2731
+ }
2732
+ if (introducerActor.approved === true) {
2733
+ return { mutationResult: { success: false, error: 'Introduction already approved' } };
2734
+ }
2735
+ const updated = await this.database.updateOpportunityActorApproval(opportunityId, userId, true);
2736
+ if (!updated) {
2737
+ return { mutationResult: { success: false, error: 'Failed to update approval' } };
2738
+ }
2739
+ if (this.queueNegotiateExisting) {
2740
+ await this.queueNegotiateExisting(opportunityId, userId);
2741
+ }
2742
+ return { mutationResult: { success: true, opportunityId } };
2743
+ };
2580
2744
  // ═══════════════════════════════════════════════════════════════
2581
2745
  // CONDITIONAL ROUTING FUNCTIONS
2582
2746
  // ═══════════════════════════════════════════════════════════════
@@ -2595,6 +2759,10 @@ export class OpportunityGraphFactory {
2595
2759
  return 'send';
2596
2760
  if (mode === 'create_introduction')
2597
2761
  return 'intro_validation';
2762
+ if (mode === 'negotiate_existing')
2763
+ return 'negotiate_existing';
2764
+ if (mode === 'approve_introduction')
2765
+ return 'approve_introduction';
2598
2766
  // 'create' is the default discovery pipeline
2599
2767
  return 'prep';
2600
2768
  };
@@ -2667,6 +2835,8 @@ export class OpportunityGraphFactory {
2667
2835
  .addNode('update', updateNode)
2668
2836
  .addNode('delete_opp', deleteNode)
2669
2837
  .addNode('send', sendNode)
2838
+ .addNode('negotiate_existing', negotiateExistingNode)
2839
+ .addNode('approve_introduction', approveIntroductionNode)
2670
2840
  // Route by operation mode from START
2671
2841
  .addConditionalEdges(START, routeByMode, {
2672
2842
  prep: 'prep',
@@ -2675,6 +2845,8 @@ export class OpportunityGraphFactory {
2675
2845
  update: 'update',
2676
2846
  delete_opp: 'delete_opp',
2677
2847
  send: 'send',
2848
+ negotiate_existing: 'negotiate_existing',
2849
+ approve_introduction: 'approve_introduction',
2678
2850
  })
2679
2851
  // Introduction path: validation -> evaluation -> persist (or END on validation error)
2680
2852
  .addConditionalEdges('intro_validation', routeAfterIntroValidation, {
@@ -2687,6 +2859,8 @@ export class OpportunityGraphFactory {
2687
2859
  .addEdge('update', END)
2688
2860
  .addEdge('delete_opp', END)
2689
2861
  .addEdge('send', END)
2862
+ .addEdge('negotiate_existing', END)
2863
+ .addEdge('approve_introduction', END)
2690
2864
  // Conditional routing: early exit if no indexed intents
2691
2865
  .addConditionalEdges('prep', shouldContinueAfterPrep, {
2692
2866
  scope: 'scope',