@indexnetwork/protocol 6.2.1-rc.378.1 → 6.2.3-rc.380.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/CHANGELOG.md +2 -0
- package/dist/negotiation/negotiation.graph.d.ts +7 -0
- package/dist/negotiation/negotiation.graph.d.ts.map +1 -1
- package/dist/negotiation/negotiation.graph.js +18 -3
- package/dist/negotiation/negotiation.graph.js.map +1 -1
- package/dist/negotiation/negotiation.state.d.ts +4 -0
- package/dist/negotiation/negotiation.state.d.ts.map +1 -1
- package/dist/negotiation/negotiation.state.js +5 -0
- package/dist/negotiation/negotiation.state.js.map +1 -1
- package/dist/opportunity/opportunity.actor.d.ts +16 -0
- package/dist/opportunity/opportunity.actor.d.ts.map +1 -0
- package/dist/opportunity/opportunity.actor.js +40 -0
- package/dist/opportunity/opportunity.actor.js.map +1 -0
- package/dist/opportunity/opportunity.discover.d.ts.map +1 -1
- package/dist/opportunity/opportunity.discover.js +15 -5
- package/dist/opportunity/opportunity.discover.js.map +1 -1
- package/dist/opportunity/opportunity.evaluator.d.ts +3 -3
- package/dist/opportunity/opportunity.evaluator.d.ts.map +1 -1
- package/dist/opportunity/opportunity.evaluator.js +4 -1
- 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 +125 -71
- package/dist/opportunity/opportunity.graph.js.map +1 -1
- package/dist/opportunity/opportunity.persist.d.ts.map +1 -1
- package/dist/opportunity/opportunity.persist.js +4 -2
- package/dist/opportunity/opportunity.persist.js.map +1 -1
- package/dist/opportunity/opportunity.tools.d.ts.map +1 -1
- package/dist/opportunity/opportunity.tools.js +34 -3
- package/dist/opportunity/opportunity.tools.js.map +1 -1
- package/dist/shared/interfaces/database.interface.d.ts +39 -5
- package/dist/shared/interfaces/database.interface.d.ts.map +1 -1
- package/dist/shared/interfaces/database.interface.js.map +1 -1
- package/package.json +1 -1
|
@@ -25,6 +25,7 @@ import { hasUnsupportedOpportunityClaim } from './opportunity.claim-safety.js';
|
|
|
25
25
|
import { persistOpportunities } from './opportunity.persist.js';
|
|
26
26
|
import { INTRODUCER_DISCOVERY_SOURCE } from './opportunity.introducer.js';
|
|
27
27
|
import { negotiateCandidates } from "../negotiation/negotiation.graph.js";
|
|
28
|
+
import { ASK_USER_LOCK_SLACK_MS, askUserAnswerWindowMs } from "../negotiation/negotiation.protocol.js";
|
|
28
29
|
import { AMBIENT_PARK_WINDOW_MS } from "../negotiation/negotiation.tools.js";
|
|
29
30
|
import { buildDiscoverySummary, toDiscoveryNegotiation } from "./negotiation-summary.builder.js";
|
|
30
31
|
import { protocolLogger, withCallLogging } from '../shared/observability/protocol.logger.js';
|
|
@@ -32,6 +33,7 @@ import { timed } from '../shared/observability/performance.js';
|
|
|
32
33
|
import { renderNetworkContext } from '../shared/network/metadata.renderer.js';
|
|
33
34
|
import { requestContext } from "../shared/observability/request-context.js";
|
|
34
35
|
import { mergeOpportunityEvidence, withCandidateEvidence, withMatchedStrategies } from './opportunity.evidence.js';
|
|
36
|
+
import { normalizeOpportunityActorIntent, resolveOpportunityActorIntent } from './opportunity.actor.js';
|
|
35
37
|
const logger = protocolLogger('OpportunityGraph');
|
|
36
38
|
const prepLog = protocolLogger('OpportunityGraph:Prep');
|
|
37
39
|
const scopeLog = protocolLogger('OpportunityGraph:Scope');
|
|
@@ -54,6 +56,21 @@ const routingLog = protocolLogger('OpportunityGraph:Routing');
|
|
|
54
56
|
/** 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). */
|
|
55
57
|
const DEDUP_WINDOW_MS = 30 * 24 * 60 * 60 * 1000;
|
|
56
58
|
const NEGOTIATION_INTENT_LIMIT = 5;
|
|
59
|
+
const ACTIVE_NEGOTIATION_TASK_STATES = new Set([
|
|
60
|
+
'submitted',
|
|
61
|
+
'working',
|
|
62
|
+
'input_required',
|
|
63
|
+
'waiting_for_agent',
|
|
64
|
+
'claimed',
|
|
65
|
+
]);
|
|
66
|
+
function isActiveNegotiationTaskFresh(task) {
|
|
67
|
+
if (!ACTIVE_NEGOTIATION_TASK_STATES.has(task.state))
|
|
68
|
+
return false;
|
|
69
|
+
const freshnessMs = task.state === 'input_required'
|
|
70
|
+
? askUserAnswerWindowMs() + ASK_USER_LOCK_SLACK_MS
|
|
71
|
+
: 5 * 60 * 1000;
|
|
72
|
+
return Date.now() - new Date(task.updatedAt).getTime() < freshnessMs;
|
|
73
|
+
}
|
|
57
74
|
/** Put an opportunity actor's exact intent first, then fill the bounded context without duplicates. */
|
|
58
75
|
export function buildPrioritizedNegotiationIntents(activeIntents, exactIntentId, fallbackIntent) {
|
|
59
76
|
const exactId = typeof exactIntentId === 'string' && exactIntentId.trim().length > 0
|
|
@@ -1827,6 +1844,27 @@ export class OpportunityGraphFactory {
|
|
|
1827
1844
|
return {};
|
|
1828
1845
|
const traceEmitter = requestContext.getStore()?.traceEmitter;
|
|
1829
1846
|
const graphStart = Date.now();
|
|
1847
|
+
const persistedById = new Map(state.opportunities.map((opportunity) => [opportunity.id, opportunity]));
|
|
1848
|
+
const attemptBoundaryById = new Map(state.opportunities.map((opportunity) => [opportunity.id, opportunity.updatedAt]));
|
|
1849
|
+
const compensateTasklessNegotiatingOpportunity = async (opportunityId) => {
|
|
1850
|
+
const opportunity = persistedById.get(opportunityId);
|
|
1851
|
+
const expectedUpdatedAt = attemptBoundaryById.get(opportunityId);
|
|
1852
|
+
if (opportunity?.status !== 'negotiating' || !expectedUpdatedAt)
|
|
1853
|
+
return;
|
|
1854
|
+
const fallbackStatus = opportunity.actors.some((actor) => actor.role === 'introducer')
|
|
1855
|
+
? 'latent'
|
|
1856
|
+
: 'draft';
|
|
1857
|
+
await this.database
|
|
1858
|
+
.compensateTasklessNegotiatingOpportunity(opportunityId, expectedUpdatedAt, fallbackStatus)
|
|
1859
|
+
.catch((error) => {
|
|
1860
|
+
negotiateLog.warn('Failed to compensate taskless negotiating opportunity', {
|
|
1861
|
+
opportunityId,
|
|
1862
|
+
expectedUpdatedAt,
|
|
1863
|
+
fallbackStatus,
|
|
1864
|
+
error,
|
|
1865
|
+
});
|
|
1866
|
+
});
|
|
1867
|
+
};
|
|
1830
1868
|
traceEmitter?.({ type: "graph_start", name: "Negotiation graph" });
|
|
1831
1869
|
try {
|
|
1832
1870
|
// Use the same discoveryUserId pattern as evaluationNode
|
|
@@ -1859,6 +1897,7 @@ export class OpportunityGraphFactory {
|
|
|
1859
1897
|
opportunityCount: state.opportunities.length,
|
|
1860
1898
|
discoveryUserId,
|
|
1861
1899
|
});
|
|
1900
|
+
const filteredBeforeInvocation = [];
|
|
1862
1901
|
const candidateEntries = state.opportunities
|
|
1863
1902
|
.map(opp => {
|
|
1864
1903
|
// Skip opportunities where any introducer exists but has not yet approved.
|
|
@@ -1870,6 +1909,7 @@ export class OpportunityGraphFactory {
|
|
|
1870
1909
|
introducerCount: introducerActors.length,
|
|
1871
1910
|
approvedCount: introducerActors.filter(a => a.approved === true).length,
|
|
1872
1911
|
});
|
|
1912
|
+
filteredBeforeInvocation.push(opp.id);
|
|
1873
1913
|
return null;
|
|
1874
1914
|
}
|
|
1875
1915
|
const candidateActor = opp.actors.find(a => a.userId !== discoveryUserId);
|
|
@@ -1879,18 +1919,20 @@ export class OpportunityGraphFactory {
|
|
|
1879
1919
|
discoveryUserId,
|
|
1880
1920
|
actors: opp.actors?.map(a => ({ userId: a.userId, role: a.role })) ?? [],
|
|
1881
1921
|
});
|
|
1922
|
+
filteredBeforeInvocation.push(opp.id);
|
|
1882
1923
|
return null;
|
|
1883
1924
|
}
|
|
1884
1925
|
return { opp, candidateActor };
|
|
1885
1926
|
})
|
|
1886
1927
|
.filter((e) => e !== null);
|
|
1928
|
+
await Promise.all(filteredBeforeInvocation.map(compensateTasklessNegotiatingOpportunity));
|
|
1887
1929
|
negotiateLog.verbose('Candidate filtering complete', {
|
|
1888
1930
|
inputOpportunities: state.opportunities.length,
|
|
1889
1931
|
outputCandidates: candidateEntries.length,
|
|
1890
1932
|
});
|
|
1891
1933
|
const candidates = await Promise.all(candidateEntries.map(async ({ opp, candidateActor }) => {
|
|
1892
1934
|
const userId = candidateActor.userId;
|
|
1893
|
-
const candidateIntentId = candidateActor
|
|
1935
|
+
const candidateIntentId = resolveOpportunityActorIntent(candidateActor);
|
|
1894
1936
|
const [profile, user, activeIntents, intent] = await Promise.all([
|
|
1895
1937
|
this.database.getProfile(userId).catch(() => null),
|
|
1896
1938
|
this.database.getUser(userId).catch(() => null),
|
|
@@ -1904,6 +1946,7 @@ export class OpportunityGraphFactory {
|
|
|
1904
1946
|
return {
|
|
1905
1947
|
userId,
|
|
1906
1948
|
opportunityId: opp.id,
|
|
1949
|
+
opportunityUpdatedAt: opp.updatedAt,
|
|
1907
1950
|
reasoning: opp.interpretation?.reasoning ?? '',
|
|
1908
1951
|
valencyRole: candidateActor.role ?? 'peer',
|
|
1909
1952
|
networkId: candidateActor.networkId,
|
|
@@ -1991,7 +2034,10 @@ export class OpportunityGraphFactory {
|
|
|
1991
2034
|
const candidateOrderById = new Map();
|
|
1992
2035
|
candidates.forEach((c, i) => candidateOrderById.set(c.userId, i));
|
|
1993
2036
|
const resolutions = [];
|
|
2037
|
+
const resolvedOpportunityIds = new Set();
|
|
1994
2038
|
const onCandidateResolved = async ({ candidate, accepted, turns, outcome }) => {
|
|
2039
|
+
if (candidate.opportunityId)
|
|
2040
|
+
resolvedOpportunityIds.add(candidate.opportunityId);
|
|
1995
2041
|
resolutions.push({
|
|
1996
2042
|
__order: candidateOrderById.get(candidate.userId) ?? Number.MAX_SAFE_INTEGER,
|
|
1997
2043
|
candidateUserId: candidate.userId,
|
|
@@ -2007,6 +2053,9 @@ export class OpportunityGraphFactory {
|
|
|
2007
2053
|
turns,
|
|
2008
2054
|
outcome,
|
|
2009
2055
|
});
|
|
2056
|
+
if (candidate.opportunityId) {
|
|
2057
|
+
await compensateTasklessNegotiatingOpportunity(candidate.opportunityId);
|
|
2058
|
+
}
|
|
2010
2059
|
if (state.trigger !== 'orchestrator')
|
|
2011
2060
|
return;
|
|
2012
2061
|
// ─── orchestrator streaming body ───
|
|
@@ -2098,6 +2147,12 @@ export class OpportunityGraphFactory {
|
|
|
2098
2147
|
clearTimeout(timerId);
|
|
2099
2148
|
}
|
|
2100
2149
|
if (raced === NEGOTIATE_TIMER_SENTINEL) {
|
|
2150
|
+
// Restore any attempt that is still before its task boundary. A running or
|
|
2151
|
+
// parked negotiation already has a task and makes the CAS a no-op; a hung
|
|
2152
|
+
// pre-task init becomes owner-actionable while its floating work may retry.
|
|
2153
|
+
await Promise.all(candidates
|
|
2154
|
+
.filter((candidate) => candidate.opportunityId && !resolvedOpportunityIds.has(candidate.opportunityId))
|
|
2155
|
+
.map((candidate) => compensateTasklessNegotiatingOpportunity(candidate.opportunityId)));
|
|
2101
2156
|
// Floating promise is intentional — see comment above.
|
|
2102
2157
|
void negotiationWork.catch((err) => {
|
|
2103
2158
|
negotiateLog.warn('background negotiation failed after timer fired', { error: err });
|
|
@@ -2182,6 +2237,7 @@ export class OpportunityGraphFactory {
|
|
|
2182
2237
|
};
|
|
2183
2238
|
}
|
|
2184
2239
|
catch (err) {
|
|
2240
|
+
await Promise.all(state.opportunities.map((opportunity) => compensateTasklessNegotiatingOpportunity(opportunity.id)));
|
|
2185
2241
|
negotiateLog.error("Negotiation stage failed", { error: err });
|
|
2186
2242
|
traceEmitter?.({ type: "graph_end", name: "Negotiation graph", durationMs: Date.now() - graphStart });
|
|
2187
2243
|
return {
|
|
@@ -2507,7 +2563,7 @@ export class OpportunityGraphFactory {
|
|
|
2507
2563
|
}
|
|
2508
2564
|
if (evaluatedToPersist.length === 0)
|
|
2509
2565
|
return { opportunities: [] };
|
|
2510
|
-
const updateStatusIfStillEligible = async (opportunityId, status, existingActors) => {
|
|
2566
|
+
const updateStatusIfStillEligible = async (opportunityId, status, existingActors, expectedStatus) => {
|
|
2511
2567
|
// Reactivation preserves the existing opportunity row, so lock the
|
|
2512
2568
|
// existing participant anchors rather than the evaluator's current
|
|
2513
2569
|
// (often network-less) actor output. Introducers do not participate
|
|
@@ -2520,7 +2576,7 @@ export class OpportunityGraphFactory {
|
|
|
2520
2576
|
persistLog.error('Network-eligible status update adapter is unavailable; failing closed');
|
|
2521
2577
|
return null;
|
|
2522
2578
|
}
|
|
2523
|
-
return this.database.updateOpportunityStatusIfNetworkEligible(opportunityId, status, anchors, networkEligibility);
|
|
2579
|
+
return this.database.updateOpportunityStatusIfNetworkEligible(opportunityId, status, anchors, networkEligibility, expectedStatus);
|
|
2524
2580
|
};
|
|
2525
2581
|
const itemsToPersist = [];
|
|
2526
2582
|
const reactivatedOpportunities = [];
|
|
@@ -2588,12 +2644,15 @@ export class OpportunityGraphFactory {
|
|
|
2588
2644
|
continue;
|
|
2589
2645
|
}
|
|
2590
2646
|
// Introduction path: manual detection, introducer actor, curator_judgment signal.
|
|
2591
|
-
const evaluatorActors = evaluated.actors.map((a) =>
|
|
2592
|
-
|
|
2593
|
-
|
|
2594
|
-
|
|
2595
|
-
|
|
2596
|
-
|
|
2647
|
+
const evaluatorActors = evaluated.actors.map((a) => {
|
|
2648
|
+
const intent = normalizeOpportunityActorIntent(a.intentId);
|
|
2649
|
+
return {
|
|
2650
|
+
networkId: a.networkId ?? indexIdForActors,
|
|
2651
|
+
userId: a.userId,
|
|
2652
|
+
role: a.role,
|
|
2653
|
+
...(intent ? { intent: intent } : {}),
|
|
2654
|
+
};
|
|
2655
|
+
});
|
|
2597
2656
|
const viewerAlreadyInActors = evaluatorActors.some(a => a.userId === state.userId);
|
|
2598
2657
|
actors = viewerAlreadyInActors
|
|
2599
2658
|
? evaluatorActors
|
|
@@ -2638,12 +2697,15 @@ export class OpportunityGraphFactory {
|
|
|
2638
2697
|
continue;
|
|
2639
2698
|
}
|
|
2640
2699
|
// Introducer discovery path: introducer is state.userId, target is onBehalfOfUserId.
|
|
2641
|
-
const evaluatorActors = evaluated.actors.map((a) =>
|
|
2642
|
-
|
|
2643
|
-
|
|
2644
|
-
|
|
2645
|
-
|
|
2646
|
-
|
|
2700
|
+
const evaluatorActors = evaluated.actors.map((a) => {
|
|
2701
|
+
const intent = normalizeOpportunityActorIntent(a.intentId);
|
|
2702
|
+
return {
|
|
2703
|
+
networkId: a.networkId ?? indexIdForActors,
|
|
2704
|
+
userId: a.userId,
|
|
2705
|
+
role: a.role,
|
|
2706
|
+
...(intent ? { intent: intent } : {}),
|
|
2707
|
+
};
|
|
2708
|
+
});
|
|
2647
2709
|
const viewerAlreadyInActors = evaluatorActors.some(a => a.userId === state.userId);
|
|
2648
2710
|
actors = viewerAlreadyInActors
|
|
2649
2711
|
? evaluatorActors
|
|
@@ -2668,7 +2730,7 @@ export class OpportunityGraphFactory {
|
|
|
2668
2730
|
if (existing.status === 'stalled' || sameIntroducer) {
|
|
2669
2731
|
// Introduction path always targets 'draft' (chat-only surface) rather than using
|
|
2670
2732
|
// initialStatus, because introductions are always chat-initiated, not background-discovered.
|
|
2671
|
-
const reactivated = await updateStatusIfStillEligible(existing.id, 'draft', existing.actors);
|
|
2733
|
+
const reactivated = await updateStatusIfStillEligible(existing.id, 'draft', existing.actors, existing.status);
|
|
2672
2734
|
if (reactivated) {
|
|
2673
2735
|
persistLog.verbose('Reactivated opportunity (introduction path)', {
|
|
2674
2736
|
opportunityId: existing.id,
|
|
@@ -2683,25 +2745,21 @@ export class OpportunityGraphFactory {
|
|
|
2683
2745
|
else if (existing.status === 'negotiating') {
|
|
2684
2746
|
// Orphan heal (introduction path): same logic as discovery path
|
|
2685
2747
|
const priorTask = await this.database.getNegotiationTaskForOpportunity(existing.id);
|
|
2686
|
-
if (priorTask) {
|
|
2687
|
-
|
|
2688
|
-
|
|
2689
|
-
|
|
2690
|
-
|
|
2691
|
-
|
|
2692
|
-
|
|
2693
|
-
|
|
2694
|
-
|
|
2695
|
-
|
|
2696
|
-
|
|
2697
|
-
|
|
2698
|
-
|
|
2699
|
-
taskState: priorTask.state,
|
|
2700
|
-
});
|
|
2701
|
-
continue;
|
|
2702
|
-
}
|
|
2748
|
+
if (priorTask && isActiveNegotiationTaskFresh(priorTask)) {
|
|
2749
|
+
existingBetweenActors.push({
|
|
2750
|
+
candidateUserId: candidateUserId,
|
|
2751
|
+
networkId: (state.networkId ?? indexIdForActors ?? ''),
|
|
2752
|
+
existingOpportunityId: existing.id,
|
|
2753
|
+
existingStatus: existing.status,
|
|
2754
|
+
});
|
|
2755
|
+
persistLog.verbose('Skipping negotiating opportunity with active task (introduction path)', {
|
|
2756
|
+
opportunityId: existing.id,
|
|
2757
|
+
candidateUserId,
|
|
2758
|
+
taskState: priorTask.state,
|
|
2759
|
+
});
|
|
2760
|
+
continue;
|
|
2703
2761
|
}
|
|
2704
|
-
const reactivated = await updateStatusIfStillEligible(existing.id, 'draft', existing.actors);
|
|
2762
|
+
const reactivated = await updateStatusIfStillEligible(existing.id, 'draft', existing.actors, existing.status);
|
|
2705
2763
|
if (reactivated) {
|
|
2706
2764
|
persistLog.info('Resuming orphaned negotiating opportunity (introduction path)', {
|
|
2707
2765
|
opportunityId: existing.id,
|
|
@@ -2714,7 +2772,7 @@ export class OpportunityGraphFactory {
|
|
|
2714
2772
|
}
|
|
2715
2773
|
else if (existing.status === 'latent') {
|
|
2716
2774
|
// Upgrade latent to draft for introduction path
|
|
2717
|
-
const upgraded = await updateStatusIfStillEligible(existing.id, 'draft', existing.actors);
|
|
2775
|
+
const upgraded = await updateStatusIfStillEligible(existing.id, 'draft', existing.actors, existing.status);
|
|
2718
2776
|
if (upgraded) {
|
|
2719
2777
|
persistLog.verbose('Upgraded latent opportunity to draft (introduction path)', {
|
|
2720
2778
|
opportunityId: existing.id,
|
|
@@ -2785,13 +2843,16 @@ export class OpportunityGraphFactory {
|
|
|
2785
2843
|
}
|
|
2786
2844
|
}
|
|
2787
2845
|
}
|
|
2788
|
-
const evaluatorActors = evaluated.actors.map((a) =>
|
|
2789
|
-
|
|
2790
|
-
|
|
2791
|
-
|
|
2792
|
-
|
|
2793
|
-
|
|
2794
|
-
|
|
2846
|
+
const evaluatorActors = evaluated.actors.map((a) => {
|
|
2847
|
+
const intent = normalizeOpportunityActorIntent(a.intentId);
|
|
2848
|
+
return {
|
|
2849
|
+
networkId: a.networkId ?? indexIdForActors,
|
|
2850
|
+
userId: a.userId,
|
|
2851
|
+
role: a.role,
|
|
2852
|
+
...(intent ? { intent: intent } : {}),
|
|
2853
|
+
...(premiseLookup.has(a.userId) ? { premise: premiseLookup.get(a.userId).premiseId } : {}),
|
|
2854
|
+
};
|
|
2855
|
+
});
|
|
2795
2856
|
actors = evaluatorActors;
|
|
2796
2857
|
const hasIntroducerActor = actors.some(a => a.role === 'introducer');
|
|
2797
2858
|
if (!hasIntroducerActor) {
|
|
@@ -2830,7 +2891,7 @@ export class OpportunityGraphFactory {
|
|
|
2830
2891
|
// Reactivate expired or stalled opportunities.
|
|
2831
2892
|
// Stalled opportunities are reactivated regardless of age: a stalled negotiation
|
|
2832
2893
|
// is still in-flight for this pair, so we resume it rather than create a parallel one.
|
|
2833
|
-
const reactivated = await updateStatusIfStillEligible(existing.id, initialStatus, existing.actors);
|
|
2894
|
+
const reactivated = await updateStatusIfStillEligible(existing.id, initialStatus, existing.actors, existing.status);
|
|
2834
2895
|
if (reactivated) {
|
|
2835
2896
|
persistLog.verbose('Reactivated opportunity', {
|
|
2836
2897
|
opportunityId: existing.id,
|
|
@@ -2846,27 +2907,23 @@ export class OpportunityGraphFactory {
|
|
|
2846
2907
|
// Orphan heal: if a prior opportunity is stuck in 'negotiating' with a stale task,
|
|
2847
2908
|
// reactivate it so the new discovery run can reuse it instead of creating a duplicate.
|
|
2848
2909
|
const priorTask = await this.database.getNegotiationTaskForOpportunity(existing.id);
|
|
2849
|
-
if (priorTask) {
|
|
2850
|
-
|
|
2851
|
-
|
|
2852
|
-
|
|
2853
|
-
|
|
2854
|
-
|
|
2855
|
-
|
|
2856
|
-
|
|
2857
|
-
|
|
2858
|
-
|
|
2859
|
-
|
|
2860
|
-
|
|
2861
|
-
|
|
2862
|
-
|
|
2863
|
-
taskState: priorTask.state,
|
|
2864
|
-
});
|
|
2865
|
-
continue;
|
|
2866
|
-
}
|
|
2910
|
+
if (priorTask && isActiveNegotiationTaskFresh(priorTask)) {
|
|
2911
|
+
// Still active — skip (lock gate in init node will handle)
|
|
2912
|
+
existingBetweenActors.push({
|
|
2913
|
+
candidateUserId: candidateUserId,
|
|
2914
|
+
networkId: existingIndexId,
|
|
2915
|
+
existingOpportunityId: existing.id,
|
|
2916
|
+
existingStatus: existing.status,
|
|
2917
|
+
});
|
|
2918
|
+
persistLog.verbose('Skipping negotiating opportunity with active task', {
|
|
2919
|
+
opportunityId: existing.id,
|
|
2920
|
+
candidateUserId,
|
|
2921
|
+
taskState: priorTask.state,
|
|
2922
|
+
});
|
|
2923
|
+
continue;
|
|
2867
2924
|
}
|
|
2868
2925
|
// Task is stale or missing — reactivate the orphaned negotiating opportunity
|
|
2869
|
-
const reactivated = await updateStatusIfStillEligible(existing.id, initialStatus, existing.actors);
|
|
2926
|
+
const reactivated = await updateStatusIfStillEligible(existing.id, initialStatus, existing.actors, existing.status);
|
|
2870
2927
|
if (reactivated) {
|
|
2871
2928
|
persistLog.info('Resuming orphaned negotiating opportunity', {
|
|
2872
2929
|
opportunityId: existing.id,
|
|
@@ -2879,7 +2936,7 @@ export class OpportunityGraphFactory {
|
|
|
2879
2936
|
}
|
|
2880
2937
|
else if (existing.status === 'latent' && initialStatus !== 'latent') {
|
|
2881
2938
|
// Upgrade latent (background-discovered) to the higher-priority status (e.g. pending)
|
|
2882
|
-
const upgraded = await updateStatusIfStillEligible(existing.id, initialStatus, existing.actors);
|
|
2939
|
+
const upgraded = await updateStatusIfStillEligible(existing.id, initialStatus, existing.actors, existing.status);
|
|
2883
2940
|
if (upgraded) {
|
|
2884
2941
|
persistLog.verbose('Upgraded latent opportunity to higher-priority status', {
|
|
2885
2942
|
opportunityId: existing.id,
|
|
@@ -3412,8 +3469,8 @@ export class OpportunityGraphFactory {
|
|
|
3412
3469
|
negotiateExistingLog.warn('No candidate actor found', { opportunityId: state.opportunityId });
|
|
3413
3470
|
return {};
|
|
3414
3471
|
}
|
|
3415
|
-
const sourceIntentId = sourceActor
|
|
3416
|
-
const candidateIntentId = candidateActor
|
|
3472
|
+
const sourceIntentId = resolveOpportunityActorIntent(sourceActor);
|
|
3473
|
+
const candidateIntentId = resolveOpportunityActorIntent(candidateActor);
|
|
3417
3474
|
// Load user data for both actors in parallel
|
|
3418
3475
|
const [sourceUserAccount, sourceProfile, sourceIntents, candidateAccount, candidateProfile, candidateIntents] = await Promise.all([
|
|
3419
3476
|
this.database.getUser(sourceActor.userId).catch(() => null),
|
|
@@ -3673,16 +3730,13 @@ export class OpportunityGraphFactory {
|
|
|
3673
3730
|
[END]: END,
|
|
3674
3731
|
})
|
|
3675
3732
|
// Discovery → Ranking → Persist → Negotiate (post-persist).
|
|
3676
|
-
//
|
|
3677
|
-
//
|
|
3678
|
-
//
|
|
3679
|
-
// negotiation graph or no opportunities were persisted (negotiateNode returns early).
|
|
3733
|
+
// Fresh and continuation discovery both negotiate newly created/reactivated
|
|
3734
|
+
// opportunities. The stage is skipped only when no negotiation graph is wired or
|
|
3735
|
+
// persistence produced no negotiation targets (negotiateNode also guards both cases).
|
|
3680
3736
|
.addNode('negotiate', negotiateNode)
|
|
3681
3737
|
.addEdge('evaluation', 'ranking')
|
|
3682
3738
|
.addEdge('ranking', 'persist')
|
|
3683
3739
|
.addConditionalEdges('persist', (state) => {
|
|
3684
|
-
if (state.operationMode === 'continue_discovery')
|
|
3685
|
-
return END;
|
|
3686
3740
|
if (!this.negotiationGraph)
|
|
3687
3741
|
return END;
|
|
3688
3742
|
if (!state.opportunities || state.opportunities.length === 0)
|