@indexnetwork/protocol 4.5.0-rc.322.1 → 4.5.0-rc.324.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/agent/agent.tools.js +1 -1
- package/dist/agent/agent.tools.js.map +1 -1
- package/dist/chat/chat.interrupt.classifier.js +1 -1
- package/dist/chat/chat.interrupt.classifier.js.map +1 -1
- package/dist/chat/chat.suggester.js +3 -3
- package/dist/chat/chat.suggester.js.map +1 -1
- package/dist/chat/chat.title.generator.js +2 -2
- package/dist/chat/chat.title.generator.js.map +1 -1
- package/dist/contact/contact.tools.js +1 -1
- package/dist/contact/contact.tools.js.map +1 -1
- package/dist/enrichment/enrichment.graph.d.ts.map +1 -1
- package/dist/enrichment/enrichment.graph.js +9 -3
- package/dist/enrichment/enrichment.graph.js.map +1 -1
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -0
- package/dist/index.js.map +1 -1
- package/dist/integration/integration.tools.js +1 -1
- package/dist/integration/integration.tools.js.map +1 -1
- package/dist/intent/intent.graph.d.ts.map +1 -1
- package/dist/intent/intent.graph.js +12 -10
- package/dist/intent/intent.graph.js.map +1 -1
- package/dist/intent/intent.indexer.d.ts.map +1 -1
- package/dist/intent/intent.indexer.js +4 -3
- package/dist/intent/intent.indexer.js.map +1 -1
- package/dist/intent/intent.inferrer.d.ts.map +1 -1
- package/dist/intent/intent.inferrer.js +2 -1
- package/dist/intent/intent.inferrer.js.map +1 -1
- package/dist/intent/intent.reconciler.d.ts.map +1 -1
- package/dist/intent/intent.reconciler.js +5 -4
- package/dist/intent/intent.reconciler.js.map +1 -1
- package/dist/intent/intent.verifier.d.ts.map +1 -1
- package/dist/intent/intent.verifier.js +4 -3
- package/dist/intent/intent.verifier.js.map +1 -1
- package/dist/maintenance/maintenance.graph.d.ts.map +1 -1
- package/dist/maintenance/maintenance.graph.js +27 -5
- package/dist/maintenance/maintenance.graph.js.map +1 -1
- package/dist/mcp/mcp.server.d.ts.map +1 -1
- package/dist/mcp/mcp.server.js +5 -4
- package/dist/mcp/mcp.server.js.map +1 -1
- package/dist/negotiation/insight.generator.js +2 -2
- package/dist/negotiation/insight.generator.js.map +1 -1
- package/dist/negotiation/negotiation.graph.d.ts.map +1 -1
- package/dist/negotiation/negotiation.graph.js +15 -11
- package/dist/negotiation/negotiation.graph.js.map +1 -1
- package/dist/negotiation/negotiation.tools.js +1 -1
- package/dist/negotiation/negotiation.tools.js.map +1 -1
- package/dist/network/network.recommender.d.ts.map +1 -1
- package/dist/network/network.recommender.js +5 -4
- package/dist/network/network.recommender.js.map +1 -1
- package/dist/network/network.tools.d.ts.map +1 -1
- package/dist/network/network.tools.js +4 -2
- package/dist/network/network.tools.js.map +1 -1
- package/dist/opportunity/feed/feed.graph.d.ts.map +1 -1
- package/dist/opportunity/feed/feed.graph.js +30 -23
- package/dist/opportunity/feed/feed.graph.js.map +1 -1
- package/dist/opportunity/opportunity.discover.d.ts.map +1 -1
- package/dist/opportunity/opportunity.discover.js +6 -4
- package/dist/opportunity/opportunity.discover.js.map +1 -1
- package/dist/opportunity/opportunity.enricher.js +2 -2
- package/dist/opportunity/opportunity.enricher.js.map +1 -1
- package/dist/opportunity/opportunity.evaluator.d.ts.map +1 -1
- package/dist/opportunity/opportunity.evaluator.js +9 -7
- package/dist/opportunity/opportunity.evaluator.js.map +1 -1
- package/dist/opportunity/opportunity.graph.d.ts.map +1 -1
- package/dist/opportunity/opportunity.graph.js +123 -105
- package/dist/opportunity/opportunity.graph.js.map +1 -1
- package/dist/opportunity/opportunity.introducer.js +5 -5
- package/dist/opportunity/opportunity.introducer.js.map +1 -1
- package/dist/opportunity/opportunity.persist.js +2 -2
- package/dist/opportunity/opportunity.persist.js.map +1 -1
- package/dist/opportunity/opportunity.presenter.d.ts.map +1 -1
- package/dist/opportunity/opportunity.presenter.js +4 -2
- package/dist/opportunity/opportunity.presenter.js.map +1 -1
- package/dist/opportunity/opportunity.tools.d.ts.map +1 -1
- package/dist/opportunity/opportunity.tools.js +2 -1
- package/dist/opportunity/opportunity.tools.js.map +1 -1
- package/dist/opportunity/opportunity.utils.d.ts.map +1 -1
- package/dist/opportunity/opportunity.utils.js +29 -5
- package/dist/opportunity/opportunity.utils.js.map +1 -1
- package/dist/premise/premise.analyzer.d.ts.map +1 -1
- package/dist/premise/premise.analyzer.js +3 -2
- package/dist/premise/premise.analyzer.js.map +1 -1
- package/dist/premise/premise.decomposer.d.ts.map +1 -1
- package/dist/premise/premise.decomposer.js +12 -3
- package/dist/premise/premise.decomposer.js.map +1 -1
- package/dist/premise/premise.graph.d.ts.map +1 -1
- package/dist/premise/premise.graph.js +19 -9
- package/dist/premise/premise.graph.js.map +1 -1
- package/dist/premise/premise.indexer.d.ts.map +1 -1
- package/dist/premise/premise.indexer.js +2 -1
- package/dist/premise/premise.indexer.js.map +1 -1
- package/dist/premise/premise.tools.d.ts.map +1 -1
- package/dist/premise/premise.tools.js +8 -4
- package/dist/premise/premise.tools.js.map +1 -1
- package/dist/shared/agent/tool.factory.d.ts.map +1 -1
- package/dist/shared/agent/tool.factory.js +4 -2
- package/dist/shared/agent/tool.factory.js.map +1 -1
- package/dist/shared/agent/tool.registry.d.ts.map +1 -1
- package/dist/shared/agent/tool.registry.js +6 -4
- package/dist/shared/agent/tool.registry.js.map +1 -1
- package/dist/shared/interfaces/database.interface.d.ts +1 -1
- package/dist/shared/interfaces/database.interface.js.map +1 -1
- package/dist/shared/observability/log.d.ts +3 -2
- package/dist/shared/observability/log.d.ts.map +1 -1
- package/dist/shared/observability/log.js +72 -9
- package/dist/shared/observability/log.js.map +1 -1
- package/dist/shared/observability/protocol.logger.d.ts.map +1 -1
- package/dist/shared/observability/protocol.logger.js +5 -3
- package/dist/shared/observability/protocol.logger.js.map +1 -1
- package/package.json +5 -5
|
@@ -30,6 +30,24 @@ import { renderNetworkContext } from '../shared/network/metadata.renderer.js';
|
|
|
30
30
|
import { requestContext } from "../shared/observability/request-context.js";
|
|
31
31
|
import { mergeOpportunityEvidence, withCandidateEvidence, withMatchedStrategies } from './opportunity.evidence.js';
|
|
32
32
|
const logger = protocolLogger('OpportunityGraph');
|
|
33
|
+
const prepLog = protocolLogger('OpportunityGraph:Prep');
|
|
34
|
+
const scopeLog = protocolLogger('OpportunityGraph:Scope');
|
|
35
|
+
const resolveLog = protocolLogger('OpportunityGraph:Resolve');
|
|
36
|
+
const discoveryLog = protocolLogger('OpportunityGraph:Discovery');
|
|
37
|
+
const evaluationLog = protocolLogger('OpportunityGraph:Evaluation');
|
|
38
|
+
const negotiateLog = protocolLogger('OpportunityGraph:Negotiate');
|
|
39
|
+
const rankingLog = protocolLogger('OpportunityGraph:Ranking');
|
|
40
|
+
const introValidationLog = protocolLogger('OpportunityGraph:IntroValidation');
|
|
41
|
+
const introEvaluationLog = protocolLogger('OpportunityGraph:IntroEvaluation');
|
|
42
|
+
const persistLog = protocolLogger('OpportunityGraph:Persist');
|
|
43
|
+
const persistPathLog = protocolLogger('OpportunityGraph:Persist:PathSelect');
|
|
44
|
+
const persistDedupLog = protocolLogger('OpportunityGraph:Persist:Dedup');
|
|
45
|
+
const readLog = protocolLogger('OpportunityGraph:Read');
|
|
46
|
+
const updateLog = protocolLogger('OpportunityGraph:Update');
|
|
47
|
+
const deleteLog = protocolLogger('OpportunityGraph:Delete');
|
|
48
|
+
const sendLog = protocolLogger('OpportunityGraph:Send');
|
|
49
|
+
const negotiateExistingLog = protocolLogger('OpportunityGraph:NegotiateExisting');
|
|
50
|
+
const routingLog = protocolLogger('OpportunityGraph:Routing');
|
|
33
51
|
/** Time window for persist-node dedup. Suppresses a second opportunity with the same person while a recent one (within 30 days) is still in flight, so a person is not re-surfaced multiple times within a month (EDG-23). */
|
|
34
52
|
const DEDUP_WINDOW_MS = 30 * 24 * 60 * 60 * 1000;
|
|
35
53
|
/** Default cap for source premises used by premise-to-premise discovery. Prevents BACKEND-5-style fan-out. */
|
|
@@ -173,7 +191,7 @@ export class OpportunityGraphFactory {
|
|
|
173
191
|
* Fetches user's network memberships and validates requirements.
|
|
174
192
|
* Returns empty if user has no network memberships (requirement).
|
|
175
193
|
*/
|
|
176
|
-
const prepNode = withNodeTrace("opportunity-prep", async (state) => timed("OpportunityGraph.prep", async () => withCallLogging(
|
|
194
|
+
const prepNode = withNodeTrace("opportunity-prep", async (state) => timed("OpportunityGraph.prep", async () => withCallLogging(prepLog, 'prepNode', {
|
|
177
195
|
userId: state.userId,
|
|
178
196
|
hasSearchQuery: !!state.searchQuery,
|
|
179
197
|
requestedIndexId: state.networkId ?? undefined,
|
|
@@ -183,7 +201,7 @@ export class OpportunityGraphFactory {
|
|
|
183
201
|
const memberships = await this.database.getNetworkMemberships(state.userId);
|
|
184
202
|
const userNetworkIds = memberships.map(m => m.networkId);
|
|
185
203
|
if (userNetworkIds.length === 0) {
|
|
186
|
-
|
|
204
|
+
prepLog.verbose('User has no network memberships - cannot find opportunities');
|
|
187
205
|
return {
|
|
188
206
|
userNetworks: [],
|
|
189
207
|
sourceProfile: null,
|
|
@@ -238,7 +256,7 @@ export class OpportunityGraphFactory {
|
|
|
238
256
|
};
|
|
239
257
|
}, { context: { userId: state.userId }, logOutput: true }).catch((error) => {
|
|
240
258
|
const errMsg = error instanceof Error ? error.message : String(error);
|
|
241
|
-
|
|
259
|
+
prepLog.error('Failed', { error });
|
|
242
260
|
return {
|
|
243
261
|
error: 'Failed to prepare opportunity search. Please try again.',
|
|
244
262
|
trace: [{
|
|
@@ -263,7 +281,7 @@ export class OpportunityGraphFactory {
|
|
|
263
281
|
*/
|
|
264
282
|
const scopeNode = withNodeTrace("opportunity-scope", async (state) => {
|
|
265
283
|
return timed("OpportunityGraph.scope", async () => {
|
|
266
|
-
|
|
284
|
+
scopeLog.verbose('Determining search scope', {
|
|
267
285
|
requestedIndexId: state.networkId,
|
|
268
286
|
userNetworksCount: state.userNetworks.length,
|
|
269
287
|
});
|
|
@@ -274,7 +292,7 @@ export class OpportunityGraphFactory {
|
|
|
274
292
|
const isInScope = state.userNetworks.includes(state.networkId);
|
|
275
293
|
const isOwner = !isInScope && await this.database.isIndexOwner(state.networkId, state.userId);
|
|
276
294
|
if (!isInScope && !isOwner) {
|
|
277
|
-
|
|
295
|
+
scopeLog.warn('User not member of requested network', {
|
|
278
296
|
networkId: state.networkId,
|
|
279
297
|
});
|
|
280
298
|
return {
|
|
@@ -290,7 +308,7 @@ export class OpportunityGraphFactory {
|
|
|
290
308
|
// reaches networks outside the agent's bound scope.
|
|
291
309
|
const allowed = new Set(state.indexScope);
|
|
292
310
|
targetIndexIds = state.userNetworks.filter((n) => allowed.has(n));
|
|
293
|
-
|
|
311
|
+
scopeLog.verbose('Applied indexScope intersection', {
|
|
294
312
|
indexScopeCount: state.indexScope.length,
|
|
295
313
|
userNetworksCount: state.userNetworks.length,
|
|
296
314
|
targetCount: targetIndexIds.length,
|
|
@@ -310,7 +328,7 @@ export class OpportunityGraphFactory {
|
|
|
310
328
|
memberCount,
|
|
311
329
|
};
|
|
312
330
|
}));
|
|
313
|
-
|
|
331
|
+
scopeLog.verbose('Scope determined', {
|
|
314
332
|
targetIndexesCount: targetNetworks.length,
|
|
315
333
|
indexes: targetNetworks.map(i => i.title),
|
|
316
334
|
});
|
|
@@ -327,7 +345,7 @@ export class OpportunityGraphFactory {
|
|
|
327
345
|
}
|
|
328
346
|
}
|
|
329
347
|
catch (err) {
|
|
330
|
-
|
|
348
|
+
scopeLog.warn('Failed to load intent index scores', { triggerIntentId: state.triggerIntentId, error: err });
|
|
331
349
|
}
|
|
332
350
|
}
|
|
333
351
|
else if (state.searchQuery?.trim()) {
|
|
@@ -382,7 +400,7 @@ export class OpportunityGraphFactory {
|
|
|
382
400
|
}
|
|
383
401
|
}
|
|
384
402
|
catch (err) {
|
|
385
|
-
|
|
403
|
+
scopeLog.warn('Failed to score query against indexes', { error: err });
|
|
386
404
|
}
|
|
387
405
|
}
|
|
388
406
|
const totalMembers = targetNetworks.reduce((sum, i) => sum + i.memberCount, 0);
|
|
@@ -398,7 +416,7 @@ export class OpportunityGraphFactory {
|
|
|
398
416
|
}
|
|
399
417
|
catch (error) {
|
|
400
418
|
const errMsg = error instanceof Error ? error.message : String(error);
|
|
401
|
-
|
|
419
|
+
scopeLog.error('Failed', { error });
|
|
402
420
|
return {
|
|
403
421
|
targetNetworks: [],
|
|
404
422
|
error: 'Failed to determine search scope.',
|
|
@@ -424,7 +442,7 @@ export class OpportunityGraphFactory {
|
|
|
424
442
|
*/
|
|
425
443
|
const resolveNode = withNodeTrace("opportunity-resolve", async (state) => {
|
|
426
444
|
return timed("OpportunityGraph.resolve", async () => {
|
|
427
|
-
|
|
445
|
+
resolveLog.verbose('Resolving intent and network membership', {
|
|
428
446
|
triggerIntentId: state.triggerIntentId,
|
|
429
447
|
hasSearchQuery: !!state.searchQuery,
|
|
430
448
|
indexedIntentsCount: state.indexedIntents.length,
|
|
@@ -458,7 +476,7 @@ export class OpportunityGraphFactory {
|
|
|
458
476
|
discoverySource,
|
|
459
477
|
};
|
|
460
478
|
}
|
|
461
|
-
|
|
479
|
+
resolveLog.warn('No intent matched search query; leaving resolvedIntentId unset', {
|
|
462
480
|
searchQuery: state.searchQuery,
|
|
463
481
|
indexedIntentsCount: state.indexedIntents.length,
|
|
464
482
|
});
|
|
@@ -471,7 +489,7 @@ export class OpportunityGraphFactory {
|
|
|
471
489
|
}
|
|
472
490
|
catch (err) {
|
|
473
491
|
const errMsg = err instanceof Error ? err.message : String(err);
|
|
474
|
-
|
|
492
|
+
resolveLog.error('Failed', {
|
|
475
493
|
triggerIntentId: state.triggerIntentId,
|
|
476
494
|
searchQuery: state.searchQuery,
|
|
477
495
|
error: err,
|
|
@@ -509,7 +527,7 @@ export class OpportunityGraphFactory {
|
|
|
509
527
|
if (!state.targetUserId)
|
|
510
528
|
return candidates;
|
|
511
529
|
const filtered = candidates.filter(c => c.candidateUserId === state.targetUserId);
|
|
512
|
-
|
|
530
|
+
discoveryLog.verbose('targetUserId filter applied', {
|
|
513
531
|
targetUserId: state.targetUserId,
|
|
514
532
|
before: candidates.length,
|
|
515
533
|
after: filtered.length,
|
|
@@ -520,14 +538,14 @@ export class OpportunityGraphFactory {
|
|
|
520
538
|
let discoveryLensInput;
|
|
521
539
|
// Shared variable to capture HyDE output (lenses + documents) for trace entries
|
|
522
540
|
let discoveryHydeOutput;
|
|
523
|
-
|
|
541
|
+
discoveryLog.verbose('Starting semantic search', {
|
|
524
542
|
targetIndexesCount: state.targetNetworks.length,
|
|
525
543
|
discoverySource: state.discoverySource,
|
|
526
544
|
searchQueryPreview: state.searchQuery?.trim().slice(0, 60) ?? '(none)',
|
|
527
545
|
});
|
|
528
546
|
try {
|
|
529
547
|
if (state.targetNetworks.length === 0) {
|
|
530
|
-
|
|
548
|
+
discoveryLog.warn('No target indexes for search');
|
|
531
549
|
return { candidates: [] };
|
|
532
550
|
}
|
|
533
551
|
// ── Direct-connection fast path ──
|
|
@@ -535,7 +553,7 @@ export class OpportunityGraphFactory {
|
|
|
535
553
|
// and construct candidates directly from shared networks.
|
|
536
554
|
if (state.targetUserId) {
|
|
537
555
|
if (state.targetUserId === discoveryUserId) {
|
|
538
|
-
|
|
556
|
+
discoveryLog.warn('Direct-connection target matches discoverer; skipping self-match', {
|
|
539
557
|
targetUserId: state.targetUserId,
|
|
540
558
|
});
|
|
541
559
|
return {
|
|
@@ -547,7 +565,7 @@ export class OpportunityGraphFactory {
|
|
|
547
565
|
}],
|
|
548
566
|
};
|
|
549
567
|
}
|
|
550
|
-
|
|
568
|
+
discoveryLog.verbose('Direct-connection mode — bypassing vector search', {
|
|
551
569
|
targetUserId: state.targetUserId,
|
|
552
570
|
});
|
|
553
571
|
const targetMemberships = await this.database.getNetworkMemberships(state.targetUserId);
|
|
@@ -556,7 +574,7 @@ export class OpportunityGraphFactory {
|
|
|
556
574
|
.filter(ti => targetUserIndexIds.includes(ti.networkId))
|
|
557
575
|
.map(ti => ti.networkId);
|
|
558
576
|
if (sharedIndexIds.length === 0) {
|
|
559
|
-
|
|
577
|
+
discoveryLog.warn('Target user shares no indexes with discoverer', {
|
|
560
578
|
targetUserId: state.targetUserId,
|
|
561
579
|
discovererIndexes: state.targetNetworks.map(ti => ti.networkId),
|
|
562
580
|
});
|
|
@@ -603,7 +621,7 @@ export class OpportunityGraphFactory {
|
|
|
603
621
|
discoverySource: 'query',
|
|
604
622
|
}));
|
|
605
623
|
}
|
|
606
|
-
|
|
624
|
+
discoveryLog.verbose('Direct candidates constructed', {
|
|
607
625
|
count: directCandidates.length,
|
|
608
626
|
sharedIndexes: sharedIndexIds.length,
|
|
609
627
|
targetIntents: targetIntents.length,
|
|
@@ -631,11 +649,11 @@ export class OpportunityGraphFactory {
|
|
|
631
649
|
if (state.discoverySource === 'context') {
|
|
632
650
|
// Context discovery: HyDE (when search query exists) + premise-to-premise.
|
|
633
651
|
if (state.searchQuery?.trim()) {
|
|
634
|
-
|
|
652
|
+
discoveryLog.verbose('Context source with searchQuery → running query HyDE + premise paths', {
|
|
635
653
|
searchQuery: state.searchQuery.trim().substring(0, 80),
|
|
636
654
|
});
|
|
637
655
|
const queryCandidates = await runQueryHydeDiscovery();
|
|
638
|
-
|
|
656
|
+
discoveryLog.verbose('Query HyDE path complete', { candidatesFound: queryCandidates.length });
|
|
639
657
|
// Build trace entries for this path
|
|
640
658
|
const traceEntries = [];
|
|
641
659
|
// Lens input trace (captured from runQueryHydeDiscovery)
|
|
@@ -727,7 +745,7 @@ export class OpportunityGraphFactory {
|
|
|
727
745
|
const searchText = state.searchQuery?.trim() ?? '';
|
|
728
746
|
if (!searchText)
|
|
729
747
|
return [];
|
|
730
|
-
|
|
748
|
+
discoveryLog.verbose('runQueryHydeDiscovery start', { searchText: searchText.slice(0, 80) });
|
|
731
749
|
const discovererContext = buildDiscovererContext(state.sourceProfile, state.indexedIntents);
|
|
732
750
|
discoveryLensInput = {
|
|
733
751
|
profileContext: discovererContext,
|
|
@@ -746,7 +764,7 @@ export class OpportunityGraphFactory {
|
|
|
746
764
|
hydeDocuments: (hydeResult.hydeDocuments ?? {}),
|
|
747
765
|
};
|
|
748
766
|
const embeddingKeys = hydeEmbeddings ? Object.keys(hydeEmbeddings) : [];
|
|
749
|
-
|
|
767
|
+
discoveryLog.verbose('HyDE generator result', {
|
|
750
768
|
lensCount: embeddingKeys.length,
|
|
751
769
|
lenses: embeddingKeys,
|
|
752
770
|
});
|
|
@@ -796,7 +814,7 @@ export class OpportunityGraphFactory {
|
|
|
796
814
|
}));
|
|
797
815
|
const intentCount = all.filter((c) => c.candidateIntentId).length;
|
|
798
816
|
const premiseCount = all.filter((c) => c.candidatePremiseId).length;
|
|
799
|
-
|
|
817
|
+
discoveryLog.verbose('searchWithHydeEmbeddings raw results', {
|
|
800
818
|
total: all.length,
|
|
801
819
|
fromIntent: intentCount,
|
|
802
820
|
fromPremise: premiseCount,
|
|
@@ -824,7 +842,7 @@ export class OpportunityGraphFactory {
|
|
|
824
842
|
return [];
|
|
825
843
|
const sourceLimit = getSourcePremiseDiscoveryLimit();
|
|
826
844
|
if (sourceLimit === 0) {
|
|
827
|
-
|
|
845
|
+
discoveryLog.verbose('runPremiseDiscovery disabled by DISCOVERY_SOURCE_PREMISE_LIMIT=0');
|
|
828
846
|
return [];
|
|
829
847
|
}
|
|
830
848
|
const sourcePremisesFromDb = self.database.getPremisesForUserInNetworks
|
|
@@ -838,7 +856,7 @@ export class OpportunityGraphFactory {
|
|
|
838
856
|
: (state.sourcePremises ?? []).slice(0, sourceLimit));
|
|
839
857
|
if (sourcePremises.length === 0)
|
|
840
858
|
return [];
|
|
841
|
-
|
|
859
|
+
discoveryLog.verbose('runPremiseDiscovery start', {
|
|
842
860
|
premiseCount: sourcePremises.length,
|
|
843
861
|
sourceLimit,
|
|
844
862
|
targetNetworks: targetNetworkIds.length,
|
|
@@ -882,7 +900,7 @@ export class OpportunityGraphFactory {
|
|
|
882
900
|
}
|
|
883
901
|
}
|
|
884
902
|
const deduped = Array.from(byKey.values());
|
|
885
|
-
|
|
903
|
+
discoveryLog.verbose('runPremiseDiscovery complete', {
|
|
886
904
|
sourcePremiseCount: sourcePremises.length,
|
|
887
905
|
rawCount: premiseCandidates.length,
|
|
888
906
|
dedupedCount: deduped.length,
|
|
@@ -904,7 +922,7 @@ export class OpportunityGraphFactory {
|
|
|
904
922
|
const targetNetworkIds = state.targetNetworks.map(t => t.networkId);
|
|
905
923
|
if (targetNetworkIds.length === 0)
|
|
906
924
|
return [];
|
|
907
|
-
|
|
925
|
+
discoveryLog.verbose('runContextToIntentDiscovery start', {
|
|
908
926
|
contextCount: state.sourceContexts.length,
|
|
909
927
|
targetNetworks: targetNetworkIds.length,
|
|
910
928
|
});
|
|
@@ -974,7 +992,7 @@ export class OpportunityGraphFactory {
|
|
|
974
992
|
}
|
|
975
993
|
}
|
|
976
994
|
const deduped = Array.from(byKey.values());
|
|
977
|
-
|
|
995
|
+
discoveryLog.verbose('runContextToIntentDiscovery complete', {
|
|
978
996
|
rawCount: contextCandidates.length,
|
|
979
997
|
dedupedCount: deduped.length,
|
|
980
998
|
});
|
|
@@ -1024,7 +1042,7 @@ export class OpportunityGraphFactory {
|
|
|
1024
1042
|
: state.indexedIntents[0];
|
|
1025
1043
|
const searchText = state.searchQuery ?? resolvedIntent?.payload ?? '';
|
|
1026
1044
|
if (!searchText) {
|
|
1027
|
-
|
|
1045
|
+
discoveryLog.warn('No search text available for intent path');
|
|
1028
1046
|
const [premiseCands, contextCands] = await Promise.all([
|
|
1029
1047
|
runPremiseDiscovery(),
|
|
1030
1048
|
runContextToIntentDiscovery(),
|
|
@@ -1117,7 +1135,7 @@ export class OpportunityGraphFactory {
|
|
|
1117
1135
|
}
|
|
1118
1136
|
}
|
|
1119
1137
|
const candidates = Array.from(byUserAndIndex.values());
|
|
1120
|
-
|
|
1138
|
+
discoveryLog.verbose('Intent-path discovery complete', { candidatesFound: candidates.length });
|
|
1121
1139
|
const usedLenses = Object.keys(hydeEmbeddings);
|
|
1122
1140
|
// Build trace with individual candidate similarity scores
|
|
1123
1141
|
const traceEntries = [];
|
|
@@ -1210,7 +1228,7 @@ export class OpportunityGraphFactory {
|
|
|
1210
1228
|
}
|
|
1211
1229
|
catch (error) {
|
|
1212
1230
|
const errMsg = error instanceof Error ? error.message : String(error);
|
|
1213
|
-
|
|
1231
|
+
discoveryLog.error('Failed', { error });
|
|
1214
1232
|
return {
|
|
1215
1233
|
candidates: [],
|
|
1216
1234
|
error: 'Failed to search for candidates.',
|
|
@@ -1236,11 +1254,11 @@ export class OpportunityGraphFactory {
|
|
|
1236
1254
|
const evaluationNode = async (state) => {
|
|
1237
1255
|
return timed("OpportunityGraph.evaluation", async () => {
|
|
1238
1256
|
const startTime = Date.now();
|
|
1239
|
-
|
|
1257
|
+
evaluationLog.verbose('Starting evaluation', {
|
|
1240
1258
|
candidatesCount: state.candidates.length,
|
|
1241
1259
|
});
|
|
1242
1260
|
if (state.candidates.length === 0) {
|
|
1243
|
-
|
|
1261
|
+
evaluationLog.verbose('No candidates to evaluate');
|
|
1244
1262
|
return { evaluatedOpportunities: [], agentTimings: [] };
|
|
1245
1263
|
}
|
|
1246
1264
|
// Batch candidates to avoid timeout - evaluate top 25 per batch, store remaining
|
|
@@ -1270,7 +1288,7 @@ export class OpportunityGraphFactory {
|
|
|
1270
1288
|
// Re-sort by similarity descending (Map iteration order doesn't guarantee sort)
|
|
1271
1289
|
dedupedCandidates.sort((a, b) => b.similarity - a.similarity);
|
|
1272
1290
|
if (dedupedCandidates.length < sortedCandidates.length) {
|
|
1273
|
-
|
|
1291
|
+
evaluationLog.info("Deduped candidates by userId", {
|
|
1274
1292
|
before: sortedCandidates.length,
|
|
1275
1293
|
after: dedupedCandidates.length,
|
|
1276
1294
|
removed: sortedCandidates.length - dedupedCandidates.length,
|
|
@@ -1284,12 +1302,12 @@ export class OpportunityGraphFactory {
|
|
|
1284
1302
|
const queryRemaining = remaining.filter((c) => c.discoverySource === 'query' || c.discoverySource == null);
|
|
1285
1303
|
const effectiveRemaining = isQueryDriven && queryRemaining.length === 0 ? [] : remaining;
|
|
1286
1304
|
if (isQueryDriven && remaining.length > 0 && queryRemaining.length === 0) {
|
|
1287
|
-
|
|
1305
|
+
evaluationLog.info("Early termination: no query-sourced candidates remain", {
|
|
1288
1306
|
droppedCandidates: remaining.length,
|
|
1289
1307
|
});
|
|
1290
1308
|
}
|
|
1291
1309
|
if (effectiveRemaining.length > 0) {
|
|
1292
|
-
|
|
1310
|
+
evaluationLog.verbose('Batched candidates for evaluation', {
|
|
1293
1311
|
evaluating: batchToEvaluate.length,
|
|
1294
1312
|
remaining: effectiveRemaining.length,
|
|
1295
1313
|
total: sortedCandidates.length,
|
|
@@ -1384,7 +1402,7 @@ export class OpportunityGraphFactory {
|
|
|
1384
1402
|
let pairwiseOpportunities;
|
|
1385
1403
|
if (runParallel) {
|
|
1386
1404
|
// Experimental: one LLM call per candidate, all fired in parallel
|
|
1387
|
-
|
|
1405
|
+
evaluationLog.verbose('Running parallel evaluation', { candidates: candidateEntities.length });
|
|
1388
1406
|
const parallelResults = await Promise.all(candidateEntities.map((candidateEntity) => {
|
|
1389
1407
|
const input = {
|
|
1390
1408
|
discovererId: discoveryUserId,
|
|
@@ -1411,7 +1429,7 @@ export class OpportunityGraphFactory {
|
|
|
1411
1429
|
const _errMsg = err instanceof Error ? err.message : String(err);
|
|
1412
1430
|
agentTimingsAccum.push({ name: 'opportunity.evaluator', durationMs: _evalDuration });
|
|
1413
1431
|
_traceEmitter?.({ type: "agent_end", name: "opportunity-evaluator", durationMs: _evalDuration, summary: `${_candidateName}: error — ${_errMsg}` });
|
|
1414
|
-
|
|
1432
|
+
evaluationLog.warn('Parallel eval failed for candidate', {
|
|
1415
1433
|
candidateUserId: candidateEntity.userId,
|
|
1416
1434
|
error: err,
|
|
1417
1435
|
});
|
|
@@ -1484,7 +1502,7 @@ export class OpportunityGraphFactory {
|
|
|
1484
1502
|
pairwiseOpportunities.push(op);
|
|
1485
1503
|
}
|
|
1486
1504
|
else {
|
|
1487
|
-
|
|
1505
|
+
evaluationLog.warn('Splitting multi-actor opportunity; LLM returned bundled actors instead of one-per-candidate', {
|
|
1488
1506
|
actorCount: nonViewerActors.length,
|
|
1489
1507
|
userIds: nonViewerActors.map(a => a.userId),
|
|
1490
1508
|
});
|
|
@@ -1555,7 +1573,7 @@ export class OpportunityGraphFactory {
|
|
|
1555
1573
|
}),
|
|
1556
1574
|
}));
|
|
1557
1575
|
const passed = evaluatedOpportunities.filter((o) => o.score >= minScore);
|
|
1558
|
-
|
|
1576
|
+
evaluationLog.verbose('Evaluation complete', {
|
|
1559
1577
|
evaluatedCount: evaluatedOpportunities.length,
|
|
1560
1578
|
passed: passed.length,
|
|
1561
1579
|
});
|
|
@@ -1644,7 +1662,7 @@ export class OpportunityGraphFactory {
|
|
|
1644
1662
|
}
|
|
1645
1663
|
catch (error) {
|
|
1646
1664
|
const errMsg = error instanceof Error ? error.message : String(error);
|
|
1647
|
-
|
|
1665
|
+
evaluationLog.error('Failed', { error });
|
|
1648
1666
|
return {
|
|
1649
1667
|
evaluatedOpportunities: [],
|
|
1650
1668
|
error: 'Failed to evaluate candidates.',
|
|
@@ -1700,7 +1718,7 @@ export class OpportunityGraphFactory {
|
|
|
1700
1718
|
};
|
|
1701
1719
|
// Build candidates from persisted opportunities. Each opportunity carries its DB id
|
|
1702
1720
|
// so the negotiation graph's finalize node can update its status from the outcome.
|
|
1703
|
-
|
|
1721
|
+
negotiateLog.verbose('Building candidates from opportunities', {
|
|
1704
1722
|
opportunityCount: state.opportunities.length,
|
|
1705
1723
|
discoveryUserId,
|
|
1706
1724
|
});
|
|
@@ -1710,7 +1728,7 @@ export class OpportunityGraphFactory {
|
|
|
1710
1728
|
const introducerActors = opp.actors
|
|
1711
1729
|
.filter(a => a.role === 'introducer');
|
|
1712
1730
|
if (introducerActors.length > 0 && !introducerActors.every(a => a.approved === true)) {
|
|
1713
|
-
|
|
1731
|
+
negotiateLog.verbose('Skipping opportunity: introducer not approved', {
|
|
1714
1732
|
opportunityId: opp.id,
|
|
1715
1733
|
introducerCount: introducerActors.length,
|
|
1716
1734
|
approvedCount: introducerActors.filter(a => a.approved === true).length,
|
|
@@ -1720,7 +1738,7 @@ export class OpportunityGraphFactory {
|
|
|
1720
1738
|
const candidateActor = opp.actors
|
|
1721
1739
|
.find(a => a.userId !== discoveryUserId);
|
|
1722
1740
|
if (!candidateActor) {
|
|
1723
|
-
|
|
1741
|
+
negotiateLog.verbose('Skipping opportunity: no candidateActor found', {
|
|
1724
1742
|
opportunityId: opp.id,
|
|
1725
1743
|
discoveryUserId,
|
|
1726
1744
|
actors: opp.actors?.map(a => ({ userId: a.userId, role: a.role })) ?? [],
|
|
@@ -1730,7 +1748,7 @@ export class OpportunityGraphFactory {
|
|
|
1730
1748
|
return { opp, candidateActor };
|
|
1731
1749
|
})
|
|
1732
1750
|
.filter((e) => e !== null);
|
|
1733
|
-
|
|
1751
|
+
negotiateLog.verbose('Candidate filtering complete', {
|
|
1734
1752
|
inputOpportunities: state.opportunities.length,
|
|
1735
1753
|
outputCandidates: candidateEntries.length,
|
|
1736
1754
|
});
|
|
@@ -1879,7 +1897,7 @@ export class OpportunityGraphFactory {
|
|
|
1879
1897
|
const updated = await this.database
|
|
1880
1898
|
.updateOpportunityStatus(candidate.opportunityId, 'draft')
|
|
1881
1899
|
.catch((err) => {
|
|
1882
|
-
|
|
1900
|
+
negotiateLog.warn('failed to flip opp to draft; suppressing draft-ready event', {
|
|
1883
1901
|
opportunityId: candidate.opportunityId,
|
|
1884
1902
|
error: err,
|
|
1885
1903
|
});
|
|
@@ -1941,9 +1959,9 @@ export class OpportunityGraphFactory {
|
|
|
1941
1959
|
if (raced === NEGOTIATE_TIMER_SENTINEL) {
|
|
1942
1960
|
// Floating promise is intentional — see comment above.
|
|
1943
1961
|
void negotiationWork.catch((err) => {
|
|
1944
|
-
|
|
1962
|
+
negotiateLog.warn('background negotiation failed after timer fired', { error: err });
|
|
1945
1963
|
});
|
|
1946
|
-
|
|
1964
|
+
negotiateLog.warn('timed out — returning partial results to caller', {
|
|
1947
1965
|
discoveryUserId,
|
|
1948
1966
|
candidateCount: candidates.length,
|
|
1949
1967
|
negotiateTimeoutMs: budgetMs,
|
|
@@ -2023,7 +2041,7 @@ export class OpportunityGraphFactory {
|
|
|
2023
2041
|
};
|
|
2024
2042
|
}
|
|
2025
2043
|
catch (err) {
|
|
2026
|
-
|
|
2044
|
+
negotiateLog.error("Negotiation stage failed", { error: err });
|
|
2027
2045
|
traceEmitter?.({ type: "graph_end", name: "Negotiation graph", durationMs: Date.now() - graphStart });
|
|
2028
2046
|
return {
|
|
2029
2047
|
trace: [{
|
|
@@ -2042,7 +2060,7 @@ export class OpportunityGraphFactory {
|
|
|
2042
2060
|
*/
|
|
2043
2061
|
const rankingNode = withNodeTrace("opportunity-ranking", async (state) => {
|
|
2044
2062
|
return timed("OpportunityGraph.ranking", async () => {
|
|
2045
|
-
|
|
2063
|
+
rankingLog.verbose('Starting ranking', {
|
|
2046
2064
|
evaluatedCount: state.evaluatedOpportunities.length,
|
|
2047
2065
|
});
|
|
2048
2066
|
try {
|
|
@@ -2061,7 +2079,7 @@ export class OpportunityGraphFactory {
|
|
|
2061
2079
|
seen.add(key);
|
|
2062
2080
|
return true;
|
|
2063
2081
|
});
|
|
2064
|
-
|
|
2082
|
+
rankingLog.verbose('Ranking complete', {
|
|
2065
2083
|
sorted: sorted.length,
|
|
2066
2084
|
afterLimit: ranked.length,
|
|
2067
2085
|
afterDedup: deduplicated.length,
|
|
@@ -2070,7 +2088,7 @@ export class OpportunityGraphFactory {
|
|
|
2070
2088
|
}
|
|
2071
2089
|
catch (error) {
|
|
2072
2090
|
const errMsg = error instanceof Error ? error.message : String(error);
|
|
2073
|
-
|
|
2091
|
+
rankingLog.error('Failed', { error });
|
|
2074
2092
|
return {
|
|
2075
2093
|
evaluatedOpportunities: [],
|
|
2076
2094
|
error: 'Failed to rank opportunities.',
|
|
@@ -2095,7 +2113,7 @@ export class OpportunityGraphFactory {
|
|
|
2095
2113
|
*/
|
|
2096
2114
|
const introValidationNode = async (state) => {
|
|
2097
2115
|
return timed("OpportunityGraph.introValidation", async () => {
|
|
2098
|
-
|
|
2116
|
+
introValidationLog.verbose('Starting', {
|
|
2099
2117
|
userId: state.userId,
|
|
2100
2118
|
networkId: state.networkId,
|
|
2101
2119
|
entitiesCount: state.introductionEntities?.length ?? 0,
|
|
@@ -2140,12 +2158,12 @@ export class OpportunityGraphFactory {
|
|
|
2140
2158
|
if (exists) {
|
|
2141
2159
|
return { error: 'An opportunity already exists between these people.' };
|
|
2142
2160
|
}
|
|
2143
|
-
|
|
2161
|
+
introValidationLog.verbose('Validation passed');
|
|
2144
2162
|
return {};
|
|
2145
2163
|
}
|
|
2146
2164
|
catch (err) {
|
|
2147
2165
|
const errMsg = err instanceof Error ? err.message : String(err);
|
|
2148
|
-
|
|
2166
|
+
introValidationLog.error('Failed', {
|
|
2149
2167
|
userId: state.userId,
|
|
2150
2168
|
networkId: state.networkId,
|
|
2151
2169
|
error: err,
|
|
@@ -2182,7 +2200,7 @@ export class OpportunityGraphFactory {
|
|
|
2182
2200
|
*/
|
|
2183
2201
|
const introEvaluationNode = async (state) => {
|
|
2184
2202
|
return timed("OpportunityGraph.introEvaluation", async () => {
|
|
2185
|
-
|
|
2203
|
+
introEvaluationLog.verbose('Starting', { userId: state.userId });
|
|
2186
2204
|
if (state.error) {
|
|
2187
2205
|
return { evaluatedOpportunities: [], agentTimings: [] };
|
|
2188
2206
|
}
|
|
@@ -2244,7 +2262,7 @@ export class OpportunityGraphFactory {
|
|
|
2244
2262
|
_traceEmitterIntro?.({ type: "agent_end", name: "intro-evaluator", durationMs: _introErrDuration, summary: `error — ${errMsg}` });
|
|
2245
2263
|
agentTimingsAccum.push({ name: 'opportunity.evaluator', durationMs: _introErrDuration });
|
|
2246
2264
|
}
|
|
2247
|
-
|
|
2265
|
+
introEvaluationLog.warn('Evaluator or getUser failed, using fallback', { error: evalErr });
|
|
2248
2266
|
const fallback = buildIntroFallback(entities, state, primaryNetworkId, introducerName);
|
|
2249
2267
|
reasoning = fallback.reasoning;
|
|
2250
2268
|
score = fallback.score;
|
|
@@ -2282,13 +2300,13 @@ export class OpportunityGraphFactory {
|
|
|
2282
2300
|
return timed("OpportunityGraph.persist", async () => {
|
|
2283
2301
|
const startTime = Date.now();
|
|
2284
2302
|
const initialStatus = resolveInitialStatus(state.trigger, state.options.initialStatus);
|
|
2285
|
-
|
|
2303
|
+
persistLog.verbose('Starting persistence (dedup-v2)', {
|
|
2286
2304
|
opportunitiesToCreate: state.evaluatedOpportunities.length,
|
|
2287
2305
|
trigger: state.trigger,
|
|
2288
2306
|
initialStatus,
|
|
2289
2307
|
});
|
|
2290
2308
|
if (state.evaluatedOpportunities.length === 0) {
|
|
2291
|
-
|
|
2309
|
+
persistLog.verbose('No opportunities to persist');
|
|
2292
2310
|
return { opportunities: [] };
|
|
2293
2311
|
}
|
|
2294
2312
|
try {
|
|
@@ -2328,7 +2346,7 @@ export class OpportunityGraphFactory {
|
|
|
2328
2346
|
const accepted = await this.database
|
|
2329
2347
|
.findOpportunitiesByActors([dedupUserId, counterpartyUserId], { includeIntroducers: true, statuses: ['accepted'] })
|
|
2330
2348
|
.catch((err) => {
|
|
2331
|
-
|
|
2349
|
+
persistLog.warn('findOpportunitiesByActors (sibling-accept) failed', {
|
|
2332
2350
|
userId: dedupUserId,
|
|
2333
2351
|
counterpartyUserId,
|
|
2334
2352
|
error: err,
|
|
@@ -2343,7 +2361,7 @@ export class OpportunityGraphFactory {
|
|
|
2343
2361
|
const indexIdForActors = state.networkId ?? evaluated.actors[0]?.networkId;
|
|
2344
2362
|
let actors;
|
|
2345
2363
|
let data;
|
|
2346
|
-
|
|
2364
|
+
persistPathLog.verbose('Selecting persistence path', {
|
|
2347
2365
|
isIntroduction: !!state.introductionContext,
|
|
2348
2366
|
stateUserId: state.userId,
|
|
2349
2367
|
stateIndexId: state.networkId,
|
|
@@ -2351,7 +2369,7 @@ export class OpportunityGraphFactory {
|
|
|
2351
2369
|
});
|
|
2352
2370
|
if (state.introductionContext) {
|
|
2353
2371
|
if (indexIdForActors === undefined) {
|
|
2354
|
-
|
|
2372
|
+
persistLog.warn('Introduction path missing networkId; skipping opportunity', {
|
|
2355
2373
|
userId: state.userId,
|
|
2356
2374
|
actorsCount: evaluated.actors.length,
|
|
2357
2375
|
});
|
|
@@ -2401,7 +2419,7 @@ export class OpportunityGraphFactory {
|
|
|
2401
2419
|
}
|
|
2402
2420
|
else if (state.onBehalfOfUserId) {
|
|
2403
2421
|
if (indexIdForActors === undefined) {
|
|
2404
|
-
|
|
2422
|
+
persistLog.warn('Introducer discovery path missing networkId; skipping opportunity', {
|
|
2405
2423
|
userId: state.userId,
|
|
2406
2424
|
actorsCount: evaluated.actors.length,
|
|
2407
2425
|
});
|
|
@@ -2440,7 +2458,7 @@ export class OpportunityGraphFactory {
|
|
|
2440
2458
|
// initialStatus, because introductions are always chat-initiated, not background-discovered.
|
|
2441
2459
|
const reactivated = await this.database.updateOpportunityStatus(existing.id, 'draft');
|
|
2442
2460
|
if (reactivated) {
|
|
2443
|
-
|
|
2461
|
+
persistLog.verbose('Reactivated opportunity (introduction path)', {
|
|
2444
2462
|
opportunityId: existing.id,
|
|
2445
2463
|
candidateUserId,
|
|
2446
2464
|
previousStatus: existing.status,
|
|
@@ -2463,7 +2481,7 @@ export class OpportunityGraphFactory {
|
|
|
2463
2481
|
existingOpportunityId: existing.id,
|
|
2464
2482
|
existingStatus: existing.status,
|
|
2465
2483
|
});
|
|
2466
|
-
|
|
2484
|
+
persistLog.verbose('Skipping negotiating opportunity with active task (introduction path)', {
|
|
2467
2485
|
opportunityId: existing.id,
|
|
2468
2486
|
candidateUserId,
|
|
2469
2487
|
taskState: priorTask.state,
|
|
@@ -2473,7 +2491,7 @@ export class OpportunityGraphFactory {
|
|
|
2473
2491
|
}
|
|
2474
2492
|
const reactivated = await this.database.updateOpportunityStatus(existing.id, 'draft');
|
|
2475
2493
|
if (reactivated) {
|
|
2476
|
-
|
|
2494
|
+
persistLog.info('Resuming orphaned negotiating opportunity (introduction path)', {
|
|
2477
2495
|
opportunityId: existing.id,
|
|
2478
2496
|
candidateUserId,
|
|
2479
2497
|
priorTaskState: priorTask?.state,
|
|
@@ -2486,7 +2504,7 @@ export class OpportunityGraphFactory {
|
|
|
2486
2504
|
// Upgrade latent to draft for introduction path
|
|
2487
2505
|
const upgraded = await this.database.updateOpportunityStatus(existing.id, 'draft');
|
|
2488
2506
|
if (upgraded) {
|
|
2489
|
-
|
|
2507
|
+
persistLog.verbose('Upgraded latent opportunity to draft (introduction path)', {
|
|
2490
2508
|
opportunityId: existing.id,
|
|
2491
2509
|
candidateUserId,
|
|
2492
2510
|
});
|
|
@@ -2502,7 +2520,7 @@ export class OpportunityGraphFactory {
|
|
|
2502
2520
|
existingOpportunityId: existing.id,
|
|
2503
2521
|
existingStatus: existing.status,
|
|
2504
2522
|
});
|
|
2505
|
-
|
|
2523
|
+
persistLog.verbose('Skipping recent duplicate (introduction path)', {
|
|
2506
2524
|
candidateUserId,
|
|
2507
2525
|
existingStatus: existing.status,
|
|
2508
2526
|
existingOpportunityId: existing.id,
|
|
@@ -2510,7 +2528,7 @@ export class OpportunityGraphFactory {
|
|
|
2510
2528
|
continue;
|
|
2511
2529
|
}
|
|
2512
2530
|
// Else: existing opportunity is old enough, allow new opportunity creation
|
|
2513
|
-
|
|
2531
|
+
persistLog.verbose('Allowing new opportunity; existing is outside dedup window (introduction path)', {
|
|
2514
2532
|
candidateUserId,
|
|
2515
2533
|
existingStatus: existing.status,
|
|
2516
2534
|
existingOpportunityId: existing.id,
|
|
@@ -2572,7 +2590,7 @@ export class OpportunityGraphFactory {
|
|
|
2572
2590
|
if (counterpartIdx >= 0) {
|
|
2573
2591
|
actors[counterpartIdx] = { ...actors[counterpartIdx], role: 'agent' };
|
|
2574
2592
|
}
|
|
2575
|
-
|
|
2593
|
+
persistLog.verbose('Swapped discoverer from agent to patient for lifecycle visibility', {
|
|
2576
2594
|
discovererId: state.userId,
|
|
2577
2595
|
});
|
|
2578
2596
|
}
|
|
@@ -2580,7 +2598,7 @@ export class OpportunityGraphFactory {
|
|
|
2580
2598
|
// Index-agnostic dedup: find ANY existing opportunity between these users,
|
|
2581
2599
|
// regardless of which index it was created in or whether a focused network scope is set.
|
|
2582
2600
|
const candidateUserId = evaluated.actors.find((a) => a.userId !== state.userId)?.userId;
|
|
2583
|
-
|
|
2601
|
+
persistDedupLog.verbose('Checking overlapping opportunities', {
|
|
2584
2602
|
stateUserId: state.userId,
|
|
2585
2603
|
candidateUserId: candidateUserId ?? 'NONE',
|
|
2586
2604
|
evaluatedActors: evaluated.actors.map(a => ({ userId: a.userId, role: a.role })),
|
|
@@ -2588,7 +2606,7 @@ export class OpportunityGraphFactory {
|
|
|
2588
2606
|
const overlapping = candidateUserId
|
|
2589
2607
|
? await this.database.findOpportunitiesByActors([state.userId, candidateUserId], { excludeStatuses: DEDUP_SKIP_STATUSES })
|
|
2590
2608
|
: [];
|
|
2591
|
-
|
|
2609
|
+
persistDedupLog.verbose('findOpportunitiesByActors result', {
|
|
2592
2610
|
count: overlapping.length,
|
|
2593
2611
|
results: overlapping.map(o => ({ id: o.id, status: o.status, actors: o.actors?.map((a) => ({ userId: a.userId, role: a.role })) })),
|
|
2594
2612
|
});
|
|
@@ -2602,7 +2620,7 @@ export class OpportunityGraphFactory {
|
|
|
2602
2620
|
// is still in-flight for this pair, so we resume it rather than create a parallel one.
|
|
2603
2621
|
const reactivated = await this.database.updateOpportunityStatus(existing.id, initialStatus);
|
|
2604
2622
|
if (reactivated) {
|
|
2605
|
-
|
|
2623
|
+
persistLog.verbose('Reactivated opportunity', {
|
|
2606
2624
|
opportunityId: existing.id,
|
|
2607
2625
|
candidateUserId,
|
|
2608
2626
|
previousStatus: existing.status,
|
|
@@ -2627,7 +2645,7 @@ export class OpportunityGraphFactory {
|
|
|
2627
2645
|
existingOpportunityId: existing.id,
|
|
2628
2646
|
existingStatus: existing.status,
|
|
2629
2647
|
});
|
|
2630
|
-
|
|
2648
|
+
persistLog.verbose('Skipping negotiating opportunity with active task', {
|
|
2631
2649
|
opportunityId: existing.id,
|
|
2632
2650
|
candidateUserId,
|
|
2633
2651
|
taskState: priorTask.state,
|
|
@@ -2638,7 +2656,7 @@ export class OpportunityGraphFactory {
|
|
|
2638
2656
|
// Task is stale or missing — reactivate the orphaned negotiating opportunity
|
|
2639
2657
|
const reactivated = await this.database.updateOpportunityStatus(existing.id, initialStatus);
|
|
2640
2658
|
if (reactivated) {
|
|
2641
|
-
|
|
2659
|
+
persistLog.info('Resuming orphaned negotiating opportunity', {
|
|
2642
2660
|
opportunityId: existing.id,
|
|
2643
2661
|
candidateUserId,
|
|
2644
2662
|
priorTaskState: priorTask?.state,
|
|
@@ -2651,7 +2669,7 @@ export class OpportunityGraphFactory {
|
|
|
2651
2669
|
// Upgrade latent (background-discovered) to the higher-priority status (e.g. pending)
|
|
2652
2670
|
const upgraded = await this.database.updateOpportunityStatus(existing.id, initialStatus);
|
|
2653
2671
|
if (upgraded) {
|
|
2654
|
-
|
|
2672
|
+
persistLog.verbose('Upgraded latent opportunity to higher-priority status', {
|
|
2655
2673
|
opportunityId: existing.id,
|
|
2656
2674
|
candidateUserId,
|
|
2657
2675
|
previousStatus: 'latent',
|
|
@@ -2670,7 +2688,7 @@ export class OpportunityGraphFactory {
|
|
|
2670
2688
|
existingOpportunityId: existing.id,
|
|
2671
2689
|
existingStatus: existing.status,
|
|
2672
2690
|
});
|
|
2673
|
-
|
|
2691
|
+
persistLog.verbose('Skipping recent duplicate; opportunity created within dedup window', {
|
|
2674
2692
|
candidateUserId,
|
|
2675
2693
|
existingStatus: existing.status,
|
|
2676
2694
|
existingOpportunityId: existing.id,
|
|
@@ -2679,7 +2697,7 @@ export class OpportunityGraphFactory {
|
|
|
2679
2697
|
continue;
|
|
2680
2698
|
}
|
|
2681
2699
|
// Else: existing opportunity is old enough (outside the 30-day dedup window), allow new opportunity creation
|
|
2682
|
-
|
|
2700
|
+
persistLog.verbose('Allowing new opportunity; existing is outside dedup window', {
|
|
2683
2701
|
candidateUserId,
|
|
2684
2702
|
existingStatus: existing.status,
|
|
2685
2703
|
existingOpportunityId: existing.id,
|
|
@@ -2723,7 +2741,7 @@ export class OpportunityGraphFactory {
|
|
|
2723
2741
|
validateOpportunityActors(data.actors);
|
|
2724
2742
|
}
|
|
2725
2743
|
catch (err) {
|
|
2726
|
-
|
|
2744
|
+
persistLog.warn('Skipping opportunity with invalid actors', {
|
|
2727
2745
|
error: err instanceof Error ? err.message : String(err),
|
|
2728
2746
|
opportunityReasoning: evaluated.reasoning?.slice(0, 80),
|
|
2729
2747
|
});
|
|
@@ -2737,7 +2755,7 @@ export class OpportunityGraphFactory {
|
|
|
2737
2755
|
items: itemsToPersist,
|
|
2738
2756
|
});
|
|
2739
2757
|
const allOpportunities = [...reactivatedOpportunities, ...createdList];
|
|
2740
|
-
|
|
2758
|
+
persistLog.verbose('Persistence complete', {
|
|
2741
2759
|
created: createdList.length,
|
|
2742
2760
|
reactivated: reactivatedOpportunities.length,
|
|
2743
2761
|
existingBetweenActorsCount: existingBetweenActors.length,
|
|
@@ -2763,7 +2781,7 @@ export class OpportunityGraphFactory {
|
|
|
2763
2781
|
}
|
|
2764
2782
|
catch (error) {
|
|
2765
2783
|
const errMsg = error instanceof Error ? error.message : String(error);
|
|
2766
|
-
|
|
2784
|
+
persistLog.error('Failed', { error });
|
|
2767
2785
|
return {
|
|
2768
2786
|
opportunities: [],
|
|
2769
2787
|
existingBetweenActors: [],
|
|
@@ -2792,7 +2810,7 @@ export class OpportunityGraphFactory {
|
|
|
2792
2810
|
*/
|
|
2793
2811
|
const readNode = async (state) => {
|
|
2794
2812
|
return timed("OpportunityGraph.read", async () => {
|
|
2795
|
-
|
|
2813
|
+
readLog.verbose('Listing opportunities', {
|
|
2796
2814
|
userId: state.userId,
|
|
2797
2815
|
networkId: state.networkId,
|
|
2798
2816
|
});
|
|
@@ -2900,7 +2918,7 @@ export class OpportunityGraphFactory {
|
|
|
2900
2918
|
};
|
|
2901
2919
|
}
|
|
2902
2920
|
catch (err) {
|
|
2903
|
-
|
|
2921
|
+
readLog.error('Failed', { error: err });
|
|
2904
2922
|
return {
|
|
2905
2923
|
readResult: { count: 0, opportunities: [], message: 'Failed to list opportunities.' },
|
|
2906
2924
|
};
|
|
@@ -2916,7 +2934,7 @@ export class OpportunityGraphFactory {
|
|
|
2916
2934
|
*/
|
|
2917
2935
|
const updateNode = async (state) => {
|
|
2918
2936
|
return timed("OpportunityGraph.update", async () => {
|
|
2919
|
-
|
|
2937
|
+
updateLog.verbose('Updating opportunity status', {
|
|
2920
2938
|
userId: state.userId,
|
|
2921
2939
|
opportunityId: state.opportunityId,
|
|
2922
2940
|
newStatus: state.newStatus,
|
|
@@ -2972,7 +2990,7 @@ export class OpportunityGraphFactory {
|
|
|
2972
2990
|
};
|
|
2973
2991
|
}
|
|
2974
2992
|
catch (err) {
|
|
2975
|
-
|
|
2993
|
+
updateLog.error('Failed', { error: err });
|
|
2976
2994
|
return { mutationResult: { success: false, error: 'Failed to update opportunity.' } };
|
|
2977
2995
|
}
|
|
2978
2996
|
});
|
|
@@ -2982,7 +3000,7 @@ export class OpportunityGraphFactory {
|
|
|
2982
3000
|
*/
|
|
2983
3001
|
const deleteNode = async (state) => {
|
|
2984
3002
|
return timed("OpportunityGraph.delete", async () => {
|
|
2985
|
-
|
|
3003
|
+
deleteLog.verbose('Expiring opportunity', {
|
|
2986
3004
|
userId: state.userId,
|
|
2987
3005
|
opportunityId: state.opportunityId,
|
|
2988
3006
|
});
|
|
@@ -3008,7 +3026,7 @@ export class OpportunityGraphFactory {
|
|
|
3008
3026
|
};
|
|
3009
3027
|
}
|
|
3010
3028
|
catch (err) {
|
|
3011
|
-
|
|
3029
|
+
deleteLog.error('Failed', { error: err });
|
|
3012
3030
|
return { mutationResult: { success: false, error: 'Failed to delete opportunity.' } };
|
|
3013
3031
|
}
|
|
3014
3032
|
});
|
|
@@ -3018,7 +3036,7 @@ export class OpportunityGraphFactory {
|
|
|
3018
3036
|
*/
|
|
3019
3037
|
const sendNode = async (state) => {
|
|
3020
3038
|
return timed("OpportunityGraph.send", async () => {
|
|
3021
|
-
|
|
3039
|
+
sendLog.verbose('Sending opportunity', {
|
|
3022
3040
|
userId: state.userId,
|
|
3023
3041
|
opportunityId: state.opportunityId,
|
|
3024
3042
|
});
|
|
@@ -3081,7 +3099,7 @@ export class OpportunityGraphFactory {
|
|
|
3081
3099
|
};
|
|
3082
3100
|
}
|
|
3083
3101
|
catch (err) {
|
|
3084
|
-
|
|
3102
|
+
sendLog.error('Failed', { error: err });
|
|
3085
3103
|
return { mutationResult: { success: false, error: 'Failed to send opportunity.' } };
|
|
3086
3104
|
}
|
|
3087
3105
|
});
|
|
@@ -3094,7 +3112,7 @@ export class OpportunityGraphFactory {
|
|
|
3094
3112
|
if (!state.opportunityId)
|
|
3095
3113
|
return {};
|
|
3096
3114
|
if (!this.negotiationGraph) {
|
|
3097
|
-
|
|
3115
|
+
negotiateExistingLog.warn('No negotiationGraph wired; skipping', {
|
|
3098
3116
|
opportunityId: state.opportunityId,
|
|
3099
3117
|
});
|
|
3100
3118
|
return {};
|
|
@@ -3102,7 +3120,7 @@ export class OpportunityGraphFactory {
|
|
|
3102
3120
|
try {
|
|
3103
3121
|
const opp = await this.database.getOpportunity(state.opportunityId);
|
|
3104
3122
|
if (!opp) {
|
|
3105
|
-
|
|
3123
|
+
negotiateExistingLog.warn('Opportunity not found', { opportunityId: state.opportunityId });
|
|
3106
3124
|
return {};
|
|
3107
3125
|
}
|
|
3108
3126
|
const actors = opp.actors;
|
|
@@ -3111,13 +3129,13 @@ export class OpportunityGraphFactory {
|
|
|
3111
3129
|
const sourceActor = nonIntroducerActors.find(a => a.role === 'patient' || a.role === 'party')
|
|
3112
3130
|
?? nonIntroducerActors[0];
|
|
3113
3131
|
if (!sourceActor) {
|
|
3114
|
-
|
|
3132
|
+
negotiateExistingLog.warn('No source actor found', { opportunityId: state.opportunityId });
|
|
3115
3133
|
return {};
|
|
3116
3134
|
}
|
|
3117
3135
|
// Find the candidateActor: non-introducer that is NOT the sourceActor
|
|
3118
3136
|
const candidateActor = nonIntroducerActors.find(a => a.userId !== sourceActor.userId);
|
|
3119
3137
|
if (!candidateActor) {
|
|
3120
|
-
|
|
3138
|
+
negotiateExistingLog.warn('No candidate actor found', { opportunityId: state.opportunityId });
|
|
3121
3139
|
return {};
|
|
3122
3140
|
}
|
|
3123
3141
|
// Load user data for both actors in parallel
|
|
@@ -3181,17 +3199,17 @@ export class OpportunityGraphFactory {
|
|
|
3181
3199
|
if (acceptedResults.length > 0 && this.queueNotification) {
|
|
3182
3200
|
for (const actor of nonIntroducerActors) {
|
|
3183
3201
|
await this.queueNotification(opp.id, actor.userId, 'high').catch((err) => {
|
|
3184
|
-
|
|
3202
|
+
negotiateExistingLog.warn('Failed to queue notification', { actorId: actor.userId, error: err });
|
|
3185
3203
|
});
|
|
3186
3204
|
}
|
|
3187
3205
|
}
|
|
3188
|
-
|
|
3206
|
+
negotiateExistingLog.info('Negotiation complete', {
|
|
3189
3207
|
opportunityId: opp.id,
|
|
3190
3208
|
accepted: acceptedResults.length > 0,
|
|
3191
3209
|
});
|
|
3192
3210
|
}
|
|
3193
3211
|
catch (err) {
|
|
3194
|
-
|
|
3212
|
+
negotiateExistingLog.error('Failed', { opportunityId: state.opportunityId, error: err });
|
|
3195
3213
|
return { error: `Failed to load opportunity: ${err instanceof Error ? err.message : String(err)}` };
|
|
3196
3214
|
}
|
|
3197
3215
|
return {};
|
|
@@ -3265,17 +3283,17 @@ export class OpportunityGraphFactory {
|
|
|
3265
3283
|
*/
|
|
3266
3284
|
const shouldContinueAfterPrep = (state) => {
|
|
3267
3285
|
if (state.error) {
|
|
3268
|
-
|
|
3286
|
+
routingLog.verbose('Error in prep - ending early');
|
|
3269
3287
|
return END;
|
|
3270
3288
|
}
|
|
3271
3289
|
// Continuation mode: skip scope/resolve/discovery, go straight to evaluation
|
|
3272
3290
|
if (state.operationMode === 'continue_discovery') {
|
|
3273
|
-
|
|
3291
|
+
routingLog.verbose('Continue discovery → skipping to evaluation', {
|
|
3274
3292
|
candidatesLoaded: state.candidates.length,
|
|
3275
3293
|
});
|
|
3276
3294
|
return 'evaluation';
|
|
3277
3295
|
}
|
|
3278
|
-
|
|
3296
|
+
routingLog.verbose('Continuing to scope');
|
|
3279
3297
|
return 'scope';
|
|
3280
3298
|
};
|
|
3281
3299
|
/**
|
|
@@ -3283,10 +3301,10 @@ export class OpportunityGraphFactory {
|
|
|
3283
3301
|
*/
|
|
3284
3302
|
const shouldContinueAfterScope = (state) => {
|
|
3285
3303
|
if (state.error || state.targetNetworks.length === 0) {
|
|
3286
|
-
|
|
3304
|
+
routingLog.verbose('No target indexes - ending early');
|
|
3287
3305
|
return END;
|
|
3288
3306
|
}
|
|
3289
|
-
|
|
3307
|
+
routingLog.verbose('Continuing to resolve');
|
|
3290
3308
|
return 'resolve';
|
|
3291
3309
|
};
|
|
3292
3310
|
/**
|
|
@@ -3294,7 +3312,7 @@ export class OpportunityGraphFactory {
|
|
|
3294
3312
|
*/
|
|
3295
3313
|
const shouldContinueAfterDiscovery = (state) => {
|
|
3296
3314
|
if (state.createIntentSuggested) {
|
|
3297
|
-
|
|
3315
|
+
routingLog.verbose('Create-intent suggested - ending for tool signal');
|
|
3298
3316
|
return END;
|
|
3299
3317
|
}
|
|
3300
3318
|
return 'evaluation';
|
|
@@ -3304,7 +3322,7 @@ export class OpportunityGraphFactory {
|
|
|
3304
3322
|
*/
|
|
3305
3323
|
const routeAfterIntroValidation = (state) => {
|
|
3306
3324
|
if (state.error) {
|
|
3307
|
-
|
|
3325
|
+
routingLog.verbose('Intro validation error - ending early');
|
|
3308
3326
|
return END;
|
|
3309
3327
|
}
|
|
3310
3328
|
return 'intro_evaluation';
|