@indexnetwork/protocol 6.2.2-rc.379.1 → 6.2.3
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 +1 -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.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.graph.d.ts.map +1 -1
- package/dist/opportunity/opportunity.graph.js +93 -49
- package/dist/opportunity/opportunity.graph.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
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"opportunity.graph.d.ts","sourceRoot":"/","sources":["opportunity/opportunity.graph.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAGH,OAAO,KAAK,EAAE,EAAE,EAAE,MAAM,4CAA4C,CAAC;AACrE,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,iCAAiC,CAAC;AACtE,OAAO,EAAyB,KAAK,aAAa,EAAE,KAAK,iBAAiB,EAAE,KAAK,aAAa,EAAE,KAAK,cAAc,EAAE,KAAK,kBAAkB,EAAE,KAAK,oBAAoB,EAAE,KAAK,yBAAyB,EAAE,MAAM,wBAAwB,CAAC;AAExO,OAAO,EAAwB,KAAK,gBAAgB,EAAuC,KAAK,eAAe,EAAE,KAAK,cAAc,EAAE,MAAM,4BAA4B,CAAC;AACzK,OAAO,KAAK,EAAE,wBAAwB,EAAE,MAAM,4CAA4C,CAAC;AAS3F,yDAAyD;AACzD,MAAM,MAAM,wBAAwB,GAAG;IACrC,MAAM,CAAC,EAAE,CACP,oBAAoB,EAAE,MAAM,EAC5B,UAAU,EAAE,gBAAgB,EAAE,EAC9B,OAAO,EAAE;QAAE,QAAQ,CAAC,EAAE,MAAM,CAAA;KAAE,KAC3B,OAAO,CAAC,KAAK,CAAC;QACjB,QAAQ,EAAE,MAAM,CAAC;QACjB,WAAW,EAAE,MAAM,CAAC;QACpB,KAAK,EAAE,MAAM,CAAC;QACd,SAAS,EAAE,MAAM,CAAC;QAClB,WAAW,EAAE,OAAO,GAAG,SAAS,GAAG,MAAM,CAAC;KAC3C,CAAC,CAAC,CAAC;IACJ,kBAAkB,CAAC,EAAE,CAAC,KAAK,EAAE,cAAc,EAAE,OAAO,EAAE;QAAE,QAAQ,CAAC,EAAE,MAAM,CAAA;KAAE,KAAK,OAAO,CAAC,KAAK,CAAC;QAC5F,SAAS,EAAE,MAAM,CAAC;QAClB,KAAK,EAAE,MAAM,CAAC;QACd,MAAM,EAAE,KAAK,CAAC;YAAE,MAAM,EAAE,MAAM,CAAC;YAAC,IAAI,EAAE,OAAO,GAAG,SAAS,GAAG,MAAM,CAAC;YAAC,QAAQ,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;YAAC,WAAW,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;SAAE,CAAC,CAAC;KAC9H,CAAC,CAAC,CAAC;CACL,CAAC;AACF,OAAO,KAAK,EAAE,QAAQ,EAAiB,MAAM,4CAA4C,CAAC;AAC1F,OAAO,KAAK,EAAE,qBAAqB,EAAE,WAAW,EAAiE,MAAM,4CAA4C,CAAC;
|
|
1
|
+
{"version":3,"file":"opportunity.graph.d.ts","sourceRoot":"/","sources":["opportunity/opportunity.graph.ts"],"names":[],"mappings":"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"}
|
|
@@ -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';
|
|
@@ -55,6 +56,21 @@ const routingLog = protocolLogger('OpportunityGraph:Routing');
|
|
|
55
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). */
|
|
56
57
|
const DEDUP_WINDOW_MS = 30 * 24 * 60 * 60 * 1000;
|
|
57
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
|
+
}
|
|
58
74
|
/** Put an opportunity actor's exact intent first, then fill the bounded context without duplicates. */
|
|
59
75
|
export function buildPrioritizedNegotiationIntents(activeIntents, exactIntentId, fallbackIntent) {
|
|
60
76
|
const exactId = typeof exactIntentId === 'string' && exactIntentId.trim().length > 0
|
|
@@ -1828,6 +1844,27 @@ export class OpportunityGraphFactory {
|
|
|
1828
1844
|
return {};
|
|
1829
1845
|
const traceEmitter = requestContext.getStore()?.traceEmitter;
|
|
1830
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
|
+
};
|
|
1831
1868
|
traceEmitter?.({ type: "graph_start", name: "Negotiation graph" });
|
|
1832
1869
|
try {
|
|
1833
1870
|
// Use the same discoveryUserId pattern as evaluationNode
|
|
@@ -1860,6 +1897,7 @@ export class OpportunityGraphFactory {
|
|
|
1860
1897
|
opportunityCount: state.opportunities.length,
|
|
1861
1898
|
discoveryUserId,
|
|
1862
1899
|
});
|
|
1900
|
+
const filteredBeforeInvocation = [];
|
|
1863
1901
|
const candidateEntries = state.opportunities
|
|
1864
1902
|
.map(opp => {
|
|
1865
1903
|
// Skip opportunities where any introducer exists but has not yet approved.
|
|
@@ -1871,6 +1909,7 @@ export class OpportunityGraphFactory {
|
|
|
1871
1909
|
introducerCount: introducerActors.length,
|
|
1872
1910
|
approvedCount: introducerActors.filter(a => a.approved === true).length,
|
|
1873
1911
|
});
|
|
1912
|
+
filteredBeforeInvocation.push(opp.id);
|
|
1874
1913
|
return null;
|
|
1875
1914
|
}
|
|
1876
1915
|
const candidateActor = opp.actors.find(a => a.userId !== discoveryUserId);
|
|
@@ -1880,11 +1919,13 @@ export class OpportunityGraphFactory {
|
|
|
1880
1919
|
discoveryUserId,
|
|
1881
1920
|
actors: opp.actors?.map(a => ({ userId: a.userId, role: a.role })) ?? [],
|
|
1882
1921
|
});
|
|
1922
|
+
filteredBeforeInvocation.push(opp.id);
|
|
1883
1923
|
return null;
|
|
1884
1924
|
}
|
|
1885
1925
|
return { opp, candidateActor };
|
|
1886
1926
|
})
|
|
1887
1927
|
.filter((e) => e !== null);
|
|
1928
|
+
await Promise.all(filteredBeforeInvocation.map(compensateTasklessNegotiatingOpportunity));
|
|
1888
1929
|
negotiateLog.verbose('Candidate filtering complete', {
|
|
1889
1930
|
inputOpportunities: state.opportunities.length,
|
|
1890
1931
|
outputCandidates: candidateEntries.length,
|
|
@@ -1905,6 +1946,7 @@ export class OpportunityGraphFactory {
|
|
|
1905
1946
|
return {
|
|
1906
1947
|
userId,
|
|
1907
1948
|
opportunityId: opp.id,
|
|
1949
|
+
opportunityUpdatedAt: opp.updatedAt,
|
|
1908
1950
|
reasoning: opp.interpretation?.reasoning ?? '',
|
|
1909
1951
|
valencyRole: candidateActor.role ?? 'peer',
|
|
1910
1952
|
networkId: candidateActor.networkId,
|
|
@@ -1992,7 +2034,10 @@ export class OpportunityGraphFactory {
|
|
|
1992
2034
|
const candidateOrderById = new Map();
|
|
1993
2035
|
candidates.forEach((c, i) => candidateOrderById.set(c.userId, i));
|
|
1994
2036
|
const resolutions = [];
|
|
2037
|
+
const resolvedOpportunityIds = new Set();
|
|
1995
2038
|
const onCandidateResolved = async ({ candidate, accepted, turns, outcome }) => {
|
|
2039
|
+
if (candidate.opportunityId)
|
|
2040
|
+
resolvedOpportunityIds.add(candidate.opportunityId);
|
|
1996
2041
|
resolutions.push({
|
|
1997
2042
|
__order: candidateOrderById.get(candidate.userId) ?? Number.MAX_SAFE_INTEGER,
|
|
1998
2043
|
candidateUserId: candidate.userId,
|
|
@@ -2008,6 +2053,9 @@ export class OpportunityGraphFactory {
|
|
|
2008
2053
|
turns,
|
|
2009
2054
|
outcome,
|
|
2010
2055
|
});
|
|
2056
|
+
if (candidate.opportunityId) {
|
|
2057
|
+
await compensateTasklessNegotiatingOpportunity(candidate.opportunityId);
|
|
2058
|
+
}
|
|
2011
2059
|
if (state.trigger !== 'orchestrator')
|
|
2012
2060
|
return;
|
|
2013
2061
|
// ─── orchestrator streaming body ───
|
|
@@ -2099,6 +2147,12 @@ export class OpportunityGraphFactory {
|
|
|
2099
2147
|
clearTimeout(timerId);
|
|
2100
2148
|
}
|
|
2101
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)));
|
|
2102
2156
|
// Floating promise is intentional — see comment above.
|
|
2103
2157
|
void negotiationWork.catch((err) => {
|
|
2104
2158
|
negotiateLog.warn('background negotiation failed after timer fired', { error: err });
|
|
@@ -2183,6 +2237,7 @@ export class OpportunityGraphFactory {
|
|
|
2183
2237
|
};
|
|
2184
2238
|
}
|
|
2185
2239
|
catch (err) {
|
|
2240
|
+
await Promise.all(state.opportunities.map((opportunity) => compensateTasklessNegotiatingOpportunity(opportunity.id)));
|
|
2186
2241
|
negotiateLog.error("Negotiation stage failed", { error: err });
|
|
2187
2242
|
traceEmitter?.({ type: "graph_end", name: "Negotiation graph", durationMs: Date.now() - graphStart });
|
|
2188
2243
|
return {
|
|
@@ -2508,7 +2563,7 @@ export class OpportunityGraphFactory {
|
|
|
2508
2563
|
}
|
|
2509
2564
|
if (evaluatedToPersist.length === 0)
|
|
2510
2565
|
return { opportunities: [] };
|
|
2511
|
-
const updateStatusIfStillEligible = async (opportunityId, status, existingActors) => {
|
|
2566
|
+
const updateStatusIfStillEligible = async (opportunityId, status, existingActors, expectedStatus) => {
|
|
2512
2567
|
// Reactivation preserves the existing opportunity row, so lock the
|
|
2513
2568
|
// existing participant anchors rather than the evaluator's current
|
|
2514
2569
|
// (often network-less) actor output. Introducers do not participate
|
|
@@ -2521,7 +2576,7 @@ export class OpportunityGraphFactory {
|
|
|
2521
2576
|
persistLog.error('Network-eligible status update adapter is unavailable; failing closed');
|
|
2522
2577
|
return null;
|
|
2523
2578
|
}
|
|
2524
|
-
return this.database.updateOpportunityStatusIfNetworkEligible(opportunityId, status, anchors, networkEligibility);
|
|
2579
|
+
return this.database.updateOpportunityStatusIfNetworkEligible(opportunityId, status, anchors, networkEligibility, expectedStatus);
|
|
2525
2580
|
};
|
|
2526
2581
|
const itemsToPersist = [];
|
|
2527
2582
|
const reactivatedOpportunities = [];
|
|
@@ -2675,7 +2730,7 @@ export class OpportunityGraphFactory {
|
|
|
2675
2730
|
if (existing.status === 'stalled' || sameIntroducer) {
|
|
2676
2731
|
// Introduction path always targets 'draft' (chat-only surface) rather than using
|
|
2677
2732
|
// initialStatus, because introductions are always chat-initiated, not background-discovered.
|
|
2678
|
-
const reactivated = await updateStatusIfStillEligible(existing.id, 'draft', existing.actors);
|
|
2733
|
+
const reactivated = await updateStatusIfStillEligible(existing.id, 'draft', existing.actors, existing.status);
|
|
2679
2734
|
if (reactivated) {
|
|
2680
2735
|
persistLog.verbose('Reactivated opportunity (introduction path)', {
|
|
2681
2736
|
opportunityId: existing.id,
|
|
@@ -2690,25 +2745,21 @@ export class OpportunityGraphFactory {
|
|
|
2690
2745
|
else if (existing.status === 'negotiating') {
|
|
2691
2746
|
// Orphan heal (introduction path): same logic as discovery path
|
|
2692
2747
|
const priorTask = await this.database.getNegotiationTaskForOpportunity(existing.id);
|
|
2693
|
-
if (priorTask) {
|
|
2694
|
-
|
|
2695
|
-
|
|
2696
|
-
|
|
2697
|
-
|
|
2698
|
-
|
|
2699
|
-
|
|
2700
|
-
|
|
2701
|
-
|
|
2702
|
-
|
|
2703
|
-
|
|
2704
|
-
|
|
2705
|
-
|
|
2706
|
-
taskState: priorTask.state,
|
|
2707
|
-
});
|
|
2708
|
-
continue;
|
|
2709
|
-
}
|
|
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;
|
|
2710
2761
|
}
|
|
2711
|
-
const reactivated = await updateStatusIfStillEligible(existing.id, 'draft', existing.actors);
|
|
2762
|
+
const reactivated = await updateStatusIfStillEligible(existing.id, 'draft', existing.actors, existing.status);
|
|
2712
2763
|
if (reactivated) {
|
|
2713
2764
|
persistLog.info('Resuming orphaned negotiating opportunity (introduction path)', {
|
|
2714
2765
|
opportunityId: existing.id,
|
|
@@ -2721,7 +2772,7 @@ export class OpportunityGraphFactory {
|
|
|
2721
2772
|
}
|
|
2722
2773
|
else if (existing.status === 'latent') {
|
|
2723
2774
|
// Upgrade latent to draft for introduction path
|
|
2724
|
-
const upgraded = await updateStatusIfStillEligible(existing.id, 'draft', existing.actors);
|
|
2775
|
+
const upgraded = await updateStatusIfStillEligible(existing.id, 'draft', existing.actors, existing.status);
|
|
2725
2776
|
if (upgraded) {
|
|
2726
2777
|
persistLog.verbose('Upgraded latent opportunity to draft (introduction path)', {
|
|
2727
2778
|
opportunityId: existing.id,
|
|
@@ -2840,7 +2891,7 @@ export class OpportunityGraphFactory {
|
|
|
2840
2891
|
// Reactivate expired or stalled opportunities.
|
|
2841
2892
|
// Stalled opportunities are reactivated regardless of age: a stalled negotiation
|
|
2842
2893
|
// is still in-flight for this pair, so we resume it rather than create a parallel one.
|
|
2843
|
-
const reactivated = await updateStatusIfStillEligible(existing.id, initialStatus, existing.actors);
|
|
2894
|
+
const reactivated = await updateStatusIfStillEligible(existing.id, initialStatus, existing.actors, existing.status);
|
|
2844
2895
|
if (reactivated) {
|
|
2845
2896
|
persistLog.verbose('Reactivated opportunity', {
|
|
2846
2897
|
opportunityId: existing.id,
|
|
@@ -2856,27 +2907,23 @@ export class OpportunityGraphFactory {
|
|
|
2856
2907
|
// Orphan heal: if a prior opportunity is stuck in 'negotiating' with a stale task,
|
|
2857
2908
|
// reactivate it so the new discovery run can reuse it instead of creating a duplicate.
|
|
2858
2909
|
const priorTask = await this.database.getNegotiationTaskForOpportunity(existing.id);
|
|
2859
|
-
if (priorTask) {
|
|
2860
|
-
|
|
2861
|
-
|
|
2862
|
-
|
|
2863
|
-
|
|
2864
|
-
|
|
2865
|
-
|
|
2866
|
-
|
|
2867
|
-
|
|
2868
|
-
|
|
2869
|
-
|
|
2870
|
-
|
|
2871
|
-
|
|
2872
|
-
|
|
2873
|
-
taskState: priorTask.state,
|
|
2874
|
-
});
|
|
2875
|
-
continue;
|
|
2876
|
-
}
|
|
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;
|
|
2877
2924
|
}
|
|
2878
2925
|
// Task is stale or missing — reactivate the orphaned negotiating opportunity
|
|
2879
|
-
const reactivated = await updateStatusIfStillEligible(existing.id, initialStatus, existing.actors);
|
|
2926
|
+
const reactivated = await updateStatusIfStillEligible(existing.id, initialStatus, existing.actors, existing.status);
|
|
2880
2927
|
if (reactivated) {
|
|
2881
2928
|
persistLog.info('Resuming orphaned negotiating opportunity', {
|
|
2882
2929
|
opportunityId: existing.id,
|
|
@@ -2889,7 +2936,7 @@ export class OpportunityGraphFactory {
|
|
|
2889
2936
|
}
|
|
2890
2937
|
else if (existing.status === 'latent' && initialStatus !== 'latent') {
|
|
2891
2938
|
// Upgrade latent (background-discovered) to the higher-priority status (e.g. pending)
|
|
2892
|
-
const upgraded = await updateStatusIfStillEligible(existing.id, initialStatus, existing.actors);
|
|
2939
|
+
const upgraded = await updateStatusIfStillEligible(existing.id, initialStatus, existing.actors, existing.status);
|
|
2893
2940
|
if (upgraded) {
|
|
2894
2941
|
persistLog.verbose('Upgraded latent opportunity to higher-priority status', {
|
|
2895
2942
|
opportunityId: existing.id,
|
|
@@ -3683,16 +3730,13 @@ export class OpportunityGraphFactory {
|
|
|
3683
3730
|
[END]: END,
|
|
3684
3731
|
})
|
|
3685
3732
|
// Discovery → Ranking → Persist → Negotiate (post-persist).
|
|
3686
|
-
//
|
|
3687
|
-
//
|
|
3688
|
-
//
|
|
3689
|
-
// 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).
|
|
3690
3736
|
.addNode('negotiate', negotiateNode)
|
|
3691
3737
|
.addEdge('evaluation', 'ranking')
|
|
3692
3738
|
.addEdge('ranking', 'persist')
|
|
3693
3739
|
.addConditionalEdges('persist', (state) => {
|
|
3694
|
-
if (state.operationMode === 'continue_discovery')
|
|
3695
|
-
return END;
|
|
3696
3740
|
if (!this.negotiationGraph)
|
|
3697
3741
|
return END;
|
|
3698
3742
|
if (!state.opportunities || state.opportunities.length === 0)
|