@indexnetwork/protocol 6.6.2-rc.388.1 → 6.6.4-rc.390.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 (29) hide show
  1. package/CHANGELOG.md +2 -0
  2. package/dist/negotiation/negotiation.graph.d.ts +8 -2
  3. package/dist/negotiation/negotiation.graph.d.ts.map +1 -1
  4. package/dist/negotiation/negotiation.graph.js +19 -4
  5. package/dist/negotiation/negotiation.graph.js.map +1 -1
  6. package/dist/negotiation/negotiation.state.d.ts +5 -3
  7. package/dist/negotiation/negotiation.state.d.ts.map +1 -1
  8. package/dist/negotiation/negotiation.state.js +5 -1
  9. package/dist/negotiation/negotiation.state.js.map +1 -1
  10. package/dist/opportunity/opportunity.enricher.d.ts +5 -0
  11. package/dist/opportunity/opportunity.enricher.d.ts.map +1 -1
  12. package/dist/opportunity/opportunity.enricher.js +17 -1
  13. package/dist/opportunity/opportunity.enricher.js.map +1 -1
  14. package/dist/opportunity/opportunity.graph.d.ts +109 -247
  15. package/dist/opportunity/opportunity.graph.d.ts.map +1 -1
  16. package/dist/opportunity/opportunity.graph.js +199 -4
  17. package/dist/opportunity/opportunity.graph.js.map +1 -1
  18. package/dist/opportunity/opportunity.persist.d.ts +14 -1
  19. package/dist/opportunity/opportunity.persist.d.ts.map +1 -1
  20. package/dist/opportunity/opportunity.persist.js +33 -4
  21. package/dist/opportunity/opportunity.persist.js.map +1 -1
  22. package/dist/opportunity/opportunity.state.d.ts +17 -0
  23. package/dist/opportunity/opportunity.state.d.ts.map +1 -1
  24. package/dist/opportunity/opportunity.state.js +5 -0
  25. package/dist/opportunity/opportunity.state.js.map +1 -1
  26. package/dist/shared/interfaces/database.interface.d.ts +29 -5
  27. package/dist/shared/interfaces/database.interface.d.ts.map +1 -1
  28. package/dist/shared/interfaces/database.interface.js.map +1 -1
  29. package/package.json +1 -1
@@ -71,6 +71,14 @@ function isActiveNegotiationTaskFresh(task) {
71
71
  : 5 * 60 * 1000;
72
72
  return Date.now() - new Date(task.updatedAt).getTime() < freshnessMs;
73
73
  }
74
+ function triggerForOwner(opportunity, ownerUserId) {
75
+ return opportunity.detection.triggeredBy
76
+ ?? opportunity.actors.find((actor) => actor.userId === ownerUserId)?.intent;
77
+ }
78
+ function belongsToOwnedIntent(opportunity, ownerUserId, triggerIntentId) {
79
+ return opportunity.detection.triggeredBy === triggerIntentId
80
+ || opportunity.actors.some((actor) => actor.userId === ownerUserId && actor.intent === triggerIntentId);
81
+ }
74
82
  /** Put an opportunity actor's exact intent first, then fill the bounded context without duplicates. */
75
83
  export function buildPrioritizedNegotiationIntents(activeIntents, exactIntentId, fallbackIntent) {
76
84
  const exactId = typeof exactIntentId === 'string' && exactIntentId.trim().length > 0
@@ -1946,6 +1954,7 @@ export class OpportunityGraphFactory {
1946
1954
  return {
1947
1955
  userId,
1948
1956
  opportunityId: opp.id,
1957
+ opportunityStatus: opp.status,
1949
1958
  opportunityUpdatedAt: opp.updatedAt,
1950
1959
  reasoning: opp.interpretation?.reasoning ?? '',
1951
1960
  valencyRole: candidateActor.role ?? 'peer',
@@ -2503,8 +2512,22 @@ export class OpportunityGraphFactory {
2503
2512
  initialStatus,
2504
2513
  });
2505
2514
  if (state.evaluatedOpportunities.length === 0) {
2506
- persistLog.verbose('No opportunities to persist');
2507
- return { opportunities: [] };
2515
+ persistLog.verbose('No opportunities to persist', {
2516
+ triggerIntentId: state.triggerIntentId,
2517
+ reason: state.candidates.length === 0 ? 'no_search_candidates' : 'evaluator_rejected_all',
2518
+ });
2519
+ return {
2520
+ opportunities: [],
2521
+ persistenceOutcome: {
2522
+ evaluatedCount: 0,
2523
+ createdCount: 0,
2524
+ reactivatedCount: 0,
2525
+ sameTriggerDuplicateSuppressions: 0,
2526
+ pairActiveNegotiationSuppressions: 0,
2527
+ crossTriggerAllowedCount: 0,
2528
+ finalAtomicConflictCount: 0,
2529
+ },
2530
+ };
2508
2531
  }
2509
2532
  try {
2510
2533
  // Recompute the authoritative owner-side scope at the final boundary.
@@ -2580,6 +2603,7 @@ export class OpportunityGraphFactory {
2580
2603
  };
2581
2604
  const itemsToPersist = [];
2582
2605
  const reactivatedOpportunities = [];
2606
+ let crossTriggerAllowedCount = 0;
2583
2607
  const existingBetweenActors = [];
2584
2608
  const now = new Date().toISOString();
2585
2609
  // Only skip 'draft' (chat-only) opportunities during dedup.
@@ -2883,7 +2907,137 @@ export class OpportunityGraphFactory {
2883
2907
  count: overlapping.length,
2884
2908
  results: overlapping.map(o => ({ id: o.id, status: o.status, actors: o.actors?.map((a) => ({ userId: a.userId, role: a.role })) })),
2885
2909
  });
2886
- if (overlapping.length > 0) {
2910
+ const ownedIntentTriggerId = state.discoverySource === 'intent'
2911
+ && state.triggerIntentId
2912
+ && state.resolvedTriggerIntentId === state.triggerIntentId
2913
+ ? state.triggerIntentId
2914
+ : undefined;
2915
+ if (ownedIntentTriggerId && candidateUserId) {
2916
+ let activeNegotiation;
2917
+ for (const opportunity of overlapping) {
2918
+ if (opportunity.status !== 'negotiating')
2919
+ continue;
2920
+ const task = await this.database.getNegotiationTaskForOpportunity(opportunity.id);
2921
+ if (task && isActiveNegotiationTaskFresh(task)) {
2922
+ activeNegotiation = { opportunity, taskState: task.state };
2923
+ break;
2924
+ }
2925
+ }
2926
+ if (activeNegotiation) {
2927
+ const existingTriggerIntentId = triggerForOwner(activeNegotiation.opportunity, state.userId);
2928
+ existingBetweenActors.push({
2929
+ candidateUserId: candidateUserId,
2930
+ networkId: (activeNegotiation.opportunity.context?.networkId ?? state.networkId ?? state.userNetworks?.[0] ?? ''),
2931
+ existingOpportunityId: activeNegotiation.opportunity.id,
2932
+ existingStatus: activeNegotiation.opportunity.status,
2933
+ reason: 'pair_active_negotiation',
2934
+ ...(existingTriggerIntentId ? { existingTriggerIntentId } : {}),
2935
+ });
2936
+ persistDedupLog.info('Suppressing owned-intent match for pair-global active negotiation', {
2937
+ triggerIntentId: ownedIntentTriggerId,
2938
+ candidateUserId,
2939
+ existingOpportunityId: activeNegotiation.opportunity.id,
2940
+ existingTriggerIntentId,
2941
+ existingStatus: activeNegotiation.opportunity.status,
2942
+ existingAgeMs: Date.now() - new Date(activeNegotiation.opportunity.createdAt).getTime(),
2943
+ taskState: activeNegotiation.taskState,
2944
+ reason: 'pair_active_negotiation',
2945
+ });
2946
+ continue;
2947
+ }
2948
+ const sameTrigger = overlapping
2949
+ .filter((opportunity) => belongsToOwnedIntent(opportunity, state.userId, ownedIntentTriggerId))
2950
+ .sort((a, b) => new Date(b.createdAt).getTime() - new Date(a.createdAt).getTime());
2951
+ const otherTrigger = overlapping.filter((opportunity) => !belongsToOwnedIntent(opportunity, state.userId, ownedIntentTriggerId));
2952
+ const existing = sameTrigger[0];
2953
+ if (!existing) {
2954
+ if (otherTrigger.length > 0) {
2955
+ crossTriggerAllowedCount += 1;
2956
+ persistDedupLog.info('Allowing cross-trigger match for owned intent', {
2957
+ triggerIntentId: ownedIntentTriggerId,
2958
+ candidateUserId,
2959
+ reason: 'cross_trigger_match_allowed',
2960
+ otherTriggers: otherTrigger.map((opportunity) => ({
2961
+ opportunityId: opportunity.id,
2962
+ triggerIntentId: triggerForOwner(opportunity, state.userId),
2963
+ status: opportunity.status,
2964
+ ageMs: Date.now() - new Date(opportunity.createdAt).getTime(),
2965
+ })),
2966
+ });
2967
+ }
2968
+ }
2969
+ else {
2970
+ const existingIndexId = (existing.context?.networkId ?? state.networkId ?? state.userNetworks?.[0] ?? '');
2971
+ const isRecent = new Date(existing.createdAt).getTime() > Date.now() - DEDUP_WINDOW_MS;
2972
+ if (existing.status === 'expired' || existing.status === 'stalled') {
2973
+ const reactivated = await updateStatusIfStillEligible(existing.id, initialStatus, existing.actors, existing.status);
2974
+ if (reactivated) {
2975
+ persistLog.info('Reactivated same-trigger opportunity', {
2976
+ triggerIntentId: ownedIntentTriggerId,
2977
+ opportunityId: existing.id,
2978
+ candidateUserId,
2979
+ previousStatus: existing.status,
2980
+ newStatus: initialStatus,
2981
+ });
2982
+ reactivatedOpportunities.push(reactivated);
2983
+ }
2984
+ continue;
2985
+ }
2986
+ if (existing.status === 'negotiating') {
2987
+ const reactivated = await updateStatusIfStillEligible(existing.id, initialStatus, existing.actors, existing.status);
2988
+ if (reactivated) {
2989
+ persistLog.info('Resuming same-trigger orphaned negotiating opportunity', {
2990
+ triggerIntentId: ownedIntentTriggerId,
2991
+ opportunityId: existing.id,
2992
+ candidateUserId,
2993
+ });
2994
+ reactivatedOpportunities.push(reactivated);
2995
+ }
2996
+ continue;
2997
+ }
2998
+ if (existing.status === 'latent' && initialStatus !== 'latent') {
2999
+ const upgraded = await updateStatusIfStillEligible(existing.id, initialStatus, existing.actors, existing.status);
3000
+ if (upgraded) {
3001
+ persistLog.info('Upgraded same-trigger latent opportunity', {
3002
+ triggerIntentId: ownedIntentTriggerId,
3003
+ opportunityId: existing.id,
3004
+ candidateUserId,
3005
+ newStatus: initialStatus,
3006
+ });
3007
+ reactivatedOpportunities.push(upgraded);
3008
+ }
3009
+ continue;
3010
+ }
3011
+ if (isRecent) {
3012
+ existingBetweenActors.push({
3013
+ candidateUserId: candidateUserId,
3014
+ networkId: existingIndexId,
3015
+ existingOpportunityId: existing.id,
3016
+ existingStatus: existing.status,
3017
+ reason: 'same_trigger_recent_duplicate',
3018
+ existingTriggerIntentId: ownedIntentTriggerId,
3019
+ });
3020
+ persistDedupLog.info('Suppressing recent same-trigger duplicate', {
3021
+ triggerIntentId: ownedIntentTriggerId,
3022
+ candidateUserId,
3023
+ existingOpportunityId: existing.id,
3024
+ existingTriggerIntentId: ownedIntentTriggerId,
3025
+ existingStatus: existing.status,
3026
+ existingAgeMs: Date.now() - new Date(existing.createdAt).getTime(),
3027
+ reason: 'same_trigger_recent_duplicate',
3028
+ });
3029
+ continue;
3030
+ }
3031
+ persistDedupLog.info('Allowing same-trigger opportunity outside dedup window', {
3032
+ triggerIntentId: ownedIntentTriggerId,
3033
+ candidateUserId,
3034
+ existingOpportunityId: existing.id,
3035
+ existingStatus: existing.status,
3036
+ existingAgeMs: Date.now() - new Date(existing.createdAt).getTime(),
3037
+ });
3038
+ }
3039
+ }
3040
+ else if (overlapping.length > 0) {
2887
3041
  const existing = overlapping[0];
2888
3042
  const existingIndexId = (existing.context?.networkId ?? state.networkId ?? state.userNetworks?.[0] ?? '');
2889
3043
  const isRecent = new Date(existing.createdAt).getTime() > Date.now() - DEDUP_WINDOW_MS;
@@ -3076,12 +3230,42 @@ export class OpportunityGraphFactory {
3076
3230
  }
3077
3231
  }
3078
3232
  }
3079
- const { created: createdList } = await persistOpportunities({
3233
+ const intentDedupScope = finalTriggerIntentId && state.discoverySource === 'intent'
3234
+ ? { triggerIntentId: finalTriggerIntentId, dedupWindowMs: DEDUP_WINDOW_MS }
3235
+ : undefined;
3236
+ const { created: createdList, conflicts } = await persistOpportunities({
3080
3237
  database: this.database,
3081
3238
  embedder: this.embedder,
3082
3239
  items: itemsForPersistence,
3083
3240
  networkEligibility,
3241
+ intentDedupScope,
3084
3242
  });
3243
+ for (const conflict of conflicts) {
3244
+ const item = itemsForPersistence[conflict.itemIndex];
3245
+ const candidateActor = item?.actors.find((actor) => actor.userId !== state.userId);
3246
+ if (!candidateActor)
3247
+ continue;
3248
+ existingBetweenActors.push({
3249
+ candidateUserId: candidateActor.userId,
3250
+ networkId: candidateActor.networkId,
3251
+ existingOpportunityId: conflict.existingOpportunityId,
3252
+ existingStatus: conflict.existingStatus,
3253
+ reason: conflict.reason,
3254
+ ...(conflict.existingTriggerIntentId
3255
+ ? { existingTriggerIntentId: conflict.existingTriggerIntentId }
3256
+ : {}),
3257
+ });
3258
+ persistDedupLog.info('Final atomic persistence conflict', {
3259
+ triggerIntentId: finalTriggerIntentId,
3260
+ candidateUserId: candidateActor.userId,
3261
+ existingOpportunityId: conflict.existingOpportunityId,
3262
+ existingTriggerIntentId: conflict.existingTriggerIntentId,
3263
+ existingStatus: conflict.existingStatus,
3264
+ existingAgeMs: Date.now() - new Date(conflict.existingCreatedAt).getTime(),
3265
+ reason: conflict.reason,
3266
+ finalAtomic: true,
3267
+ });
3268
+ }
3085
3269
  const allOpportunities = [...reactivatedOpportunities, ...createdList];
3086
3270
  persistLog.verbose('Persistence complete', {
3087
3271
  created: createdList.length,
@@ -3089,10 +3273,20 @@ export class OpportunityGraphFactory {
3089
3273
  existingBetweenActorsCount: existingBetweenActors.length,
3090
3274
  status: initialStatus,
3091
3275
  });
3276
+ const persistenceOutcome = {
3277
+ evaluatedCount: state.evaluatedOpportunities.length,
3278
+ createdCount: createdList.length,
3279
+ reactivatedCount: reactivatedOpportunities.length,
3280
+ sameTriggerDuplicateSuppressions: existingBetweenActors.filter((entry) => entry.reason === 'same_trigger_recent_duplicate').length,
3281
+ pairActiveNegotiationSuppressions: existingBetweenActors.filter((entry) => entry.reason === 'pair_active_negotiation').length,
3282
+ crossTriggerAllowedCount,
3283
+ finalAtomicConflictCount: conflicts.length,
3284
+ };
3092
3285
  return {
3093
3286
  opportunities: allOpportunities,
3094
3287
  existingBetweenActors,
3095
3288
  dedupAlreadyAccepted,
3289
+ persistenceOutcome,
3096
3290
  trace: [{
3097
3291
  node: "persist",
3098
3292
  detail: `Created ${createdList.length}, reactivated ${reactivatedOpportunities.length}, ${existingBetweenActors.length} existing skipped, ${dedupAlreadyAccepted.length} already-accepted pair(s)`,
@@ -3102,6 +3296,7 @@ export class OpportunityGraphFactory {
3102
3296
  existingSkipped: existingBetweenActors.length,
3103
3297
  alreadyAccepted: dedupAlreadyAccepted.length,
3104
3298
  totalOutput: allOpportunities.length,
3299
+ persistenceOutcome,
3105
3300
  durationMs: Date.now() - startTime,
3106
3301
  },
3107
3302
  }],