@indexnetwork/protocol 14.3.2-rc.477.1 → 16.1.1-rc.478.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 +107 -0
- package/IMPLEMENTATION.md +50 -52
- package/STABILITY.md +3 -3
- package/dist/chat/chat.agent.js +2 -2
- package/dist/chat/chat.graph.d.ts +41 -1
- package/dist/chat/chat.graph.js +108 -127
- package/dist/discovery/hyde.frame.d.ts +4 -4
- package/dist/discovery/hyde.graph.d.ts +71 -12
- package/dist/discovery/hyde.graph.js +337 -335
- package/dist/discovery/lens.inferrer.d.ts +6 -6
- package/dist/enrichment/enrichment.graph.d.ts +127 -9
- package/dist/enrichment/enrichment.graph.js +633 -655
- package/dist/enrichment/enrichment.state.d.ts +1 -1
- package/dist/enrichment/enrichment.tools.context-read.d.ts +10 -0
- package/dist/enrichment/enrichment.tools.context-read.js +353 -0
- package/dist/enrichment/enrichment.tools.context-write.d.ts +9 -0
- package/dist/enrichment/enrichment.tools.context-write.js +417 -0
- package/dist/enrichment/enrichment.tools.d.ts +11 -1
- package/dist/enrichment/enrichment.tools.helpers.d.ts +139 -0
- package/dist/enrichment/enrichment.tools.helpers.js +234 -0
- package/dist/enrichment/enrichment.tools.js +16 -975
- package/dist/index.d.ts +27 -48
- package/dist/index.js +17 -27
- package/dist/intents/application/intent.graph.d.ts +71 -67
- package/dist/intents/application/intent.graph.execute.d.ts +59 -0
- package/dist/intents/application/intent.graph.execute.js +301 -0
- package/dist/intents/application/intent.graph.infer.d.ts +44 -0
- package/dist/intents/application/intent.graph.infer.js +97 -0
- package/dist/intents/application/intent.graph.js +96 -888
- package/dist/intents/application/intent.graph.reconcile.d.ts +71 -0
- package/dist/intents/application/intent.graph.reconcile.js +274 -0
- package/dist/intents/application/intent.graph.shared.d.ts +70 -0
- package/dist/intents/application/intent.graph.shared.js +153 -0
- package/dist/intents/domain/intent.state.d.ts +1 -1
- package/dist/maintenance/maintenance.graph.d.ts +66 -3
- package/dist/maintenance/maintenance.graph.js +155 -156
- package/dist/mcp/mcp.authorization-policy.d.ts +24 -28
- package/dist/mcp/mcp.authorization-policy.js +14 -34
- package/dist/mcp/mcp.server.d.ts +3 -3
- package/dist/mcp/mcp.server.js +12 -16
- package/dist/negotiations/application/negotiation.candidates.d.ts +83 -0
- package/dist/negotiations/application/negotiation.candidates.js +162 -0
- package/dist/negotiations/application/negotiation.graph.d.ts +91 -157
- package/dist/negotiations/application/negotiation.graph.finalize.d.ts +5 -0
- package/dist/negotiations/application/negotiation.graph.finalize.js +280 -0
- package/dist/negotiations/application/negotiation.graph.init.d.ts +54 -0
- package/dist/negotiations/application/negotiation.graph.init.js +227 -0
- package/dist/negotiations/application/negotiation.graph.js +69 -1388
- package/dist/negotiations/application/negotiation.graph.screen.d.ts +23 -0
- package/dist/negotiations/application/negotiation.graph.screen.js +108 -0
- package/dist/negotiations/application/negotiation.graph.shared.d.ts +75 -0
- package/dist/negotiations/application/negotiation.graph.shared.js +125 -0
- package/dist/negotiations/application/negotiation.graph.turn.d.ts +105 -0
- package/dist/negotiations/application/negotiation.graph.turn.js +484 -0
- package/dist/negotiations/domain/negotiation.state.d.ts +1 -1
- package/dist/negotiations/domain/negotiation.state.js +0 -1
- package/dist/networks/application/indexer.graph.d.ts +165 -7
- package/dist/networks/application/indexer.graph.js +339 -388
- package/dist/networks/application/indexer.state.d.ts +1 -1
- package/dist/networks/application/membership.graph.d.ts +83 -5
- package/dist/networks/application/membership.graph.js +177 -207
- package/dist/networks/application/network.graph.d.ts +148 -5
- package/dist/networks/application/network.graph.js +249 -278
- package/dist/networks/domain/membership.state.d.ts +1 -1
- package/dist/networks/domain/network.state.d.ts +1 -1
- package/dist/opportunities/application/delivery-card.cache.d.ts +1 -1
- package/dist/opportunities/application/delivery-card.cache.js +2 -2
- package/dist/opportunities/application/index.d.ts +2 -2
- package/dist/opportunities/application/index.js +2 -2
- package/dist/opportunities/application/opportunity.evaluator.js +1 -1
- package/dist/opportunities/application/opportunity.graph.d.ts +686 -473
- package/dist/opportunities/application/opportunity.graph.discovery-strategies.d.ts +109 -0
- package/dist/opportunities/application/opportunity.graph.discovery-strategies.js +451 -0
- package/dist/opportunities/application/opportunity.graph.discovery.d.ts +69 -0
- package/dist/opportunities/application/opportunity.graph.discovery.js +397 -0
- package/dist/opportunities/application/opportunity.graph.evaluation.d.ts +82 -0
- package/dist/opportunities/application/opportunity.graph.evaluation.js +608 -0
- package/dist/opportunities/application/opportunity.graph.js +120 -3690
- package/dist/opportunities/application/opportunity.graph.modes.d.ts +538 -0
- package/dist/opportunities/application/opportunity.graph.modes.js +538 -0
- package/dist/opportunities/application/opportunity.graph.negotiate.d.ts +109 -0
- package/dist/opportunities/application/opportunity.graph.negotiate.js +392 -0
- package/dist/opportunities/application/opportunity.graph.persist-node.d.ts +93 -0
- package/dist/opportunities/application/opportunity.graph.persist-node.js +765 -0
- package/dist/opportunities/application/opportunity.graph.prep.d.ts +150 -0
- package/dist/opportunities/application/opportunity.graph.prep.js +381 -0
- package/dist/opportunities/application/opportunity.graph.shared.d.ts +173 -0
- package/dist/opportunities/application/opportunity.graph.shared.js +199 -0
- package/dist/opportunities/application/opportunity.presentation.d.ts +399 -0
- package/dist/opportunities/application/{opportunity.presenter.js → opportunity.presentation.js} +735 -9
- package/dist/opportunities/application/opportunity.tools.cards.d.ts +152 -0
- package/dist/opportunities/application/opportunity.tools.cards.js +235 -0
- package/dist/opportunities/application/opportunity.tools.d.ts +8 -114
- package/dist/opportunities/application/opportunity.tools.js +28 -711
- package/dist/opportunities/application/opportunity.tools.list.d.ts +10 -0
- package/dist/opportunities/application/opportunity.tools.list.js +493 -0
- package/dist/opportunities/domain/index.d.ts +3 -3
- package/dist/opportunities/domain/index.js +3 -3
- package/dist/opportunities/domain/opportunity.state.d.ts +19 -19
- package/dist/opportunities/index.d.ts +6 -6
- package/dist/opportunities/index.js +4 -4
- package/dist/opportunities/ports/opportunity.tools.port.d.ts +1 -1
- package/dist/opportunities/radar/radar.graph.d.ts +57 -13
- package/dist/opportunities/radar/radar.graph.js +470 -471
- package/dist/premises/premise.graph.d.ts +88 -20
- package/dist/premises/premise.graph.js +207 -218
- package/dist/questions/domain/question.schema.d.ts +40 -40
- package/dist/shared/agent/tool.helpers.d.ts +21 -0
- package/dist/shared/agent/tool.registry.d.ts +4 -3
- package/dist/shared/agent/tool.registry.js +1 -1
- package/dist/shared/interfaces/database.capabilities.d.ts +100 -0
- package/dist/shared/interfaces/database.capabilities.js +7 -0
- package/dist/shared/interfaces/database.entities.d.ts +533 -0
- package/dist/shared/interfaces/database.entities.js +10 -0
- package/dist/shared/interfaces/database.identity-queries.d.ts +294 -0
- package/dist/shared/interfaces/database.identity-queries.js +4 -0
- package/dist/shared/interfaces/database.interface.d.ts +15 -2277
- package/dist/shared/interfaces/database.interface.js +8 -0
- package/dist/shared/interfaces/database.member-queries.d.ts +228 -0
- package/dist/shared/interfaces/database.member-queries.js +4 -0
- package/dist/shared/interfaces/database.negotiation.d.ts +305 -0
- package/dist/shared/interfaces/database.negotiation.js +26 -0
- package/dist/shared/interfaces/database.network-queries.d.ts +277 -0
- package/dist/shared/interfaces/database.network-queries.js +4 -0
- package/dist/shared/interfaces/database.opportunity-queries.d.ts +285 -0
- package/dist/shared/interfaces/database.opportunity-queries.js +4 -0
- package/dist/shared/interfaces/database.port.d.ts +322 -0
- package/dist/shared/interfaces/database.port.js +29 -0
- package/dist/shared/schemas/discovery-question.schema.d.ts +14 -14
- package/dist/shared/schemas/negotiation-digest.schema.d.ts +2 -2
- package/package.json +2 -15
- package/dist/opportunities/application/opportunity.card-presentation.d.ts +0 -44
- package/dist/opportunities/application/opportunity.card-presentation.js +0 -93
- package/dist/opportunities/application/opportunity.presenter.d.ts +0 -156
- package/dist/opportunities/domain/opportunity.presentation-cache.d.ts +0 -5
- package/dist/opportunities/domain/opportunity.presentation-cache.js +0 -11
- package/dist/opportunities/domain/opportunity.presentation.d.ts +0 -76
- package/dist/opportunities/domain/opportunity.presentation.js +0 -516
- package/dist/opportunities/domain/opportunity.safe-presentation.d.ts +0 -104
- package/dist/opportunities/domain/opportunity.safe-presentation.js +0 -102
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Discovery pipeline, stages 0–2: prep, scope, resolve.
|
|
3
|
+
*
|
|
4
|
+
* Each stage is a top-level function taking the graph state and an explicit
|
|
5
|
+
* dependency bag. `opportunity.graph.ts` wires them into the StateGraph.
|
|
6
|
+
*/
|
|
7
|
+
import type { Id } from '../../shared/interfaces/database.interface.js';
|
|
8
|
+
import type { DebugMetaAgent } from '../../agents/index.js';
|
|
9
|
+
import type { IndexedIntent, TargetNetwork } from '../domain/opportunity.state.js';
|
|
10
|
+
import { type OpportunityGraphDeps, type OpportunityState } from './opportunity.graph.shared.js';
|
|
11
|
+
/**
|
|
12
|
+
* Node 0: Prep
|
|
13
|
+
* Fetches user's network memberships and validates requirements.
|
|
14
|
+
* Returns empty if user has no network memberships (requirement).
|
|
15
|
+
*/
|
|
16
|
+
export declare function prepNode(state: OpportunityState, deps: OpportunityGraphDeps): Promise<{
|
|
17
|
+
userNetworks: Id<"networks">[];
|
|
18
|
+
sourceProfile: null;
|
|
19
|
+
error: string;
|
|
20
|
+
indexedIntents?: undefined;
|
|
21
|
+
sourcePremises?: undefined;
|
|
22
|
+
sourceContexts?: undefined;
|
|
23
|
+
trace?: undefined;
|
|
24
|
+
} | {
|
|
25
|
+
userNetworks: Id<"networks">[];
|
|
26
|
+
indexedIntents: IndexedIntent[];
|
|
27
|
+
sourceProfile: {
|
|
28
|
+
identity: {
|
|
29
|
+
name: string;
|
|
30
|
+
bio: string;
|
|
31
|
+
location: string;
|
|
32
|
+
};
|
|
33
|
+
context: string;
|
|
34
|
+
} | null;
|
|
35
|
+
sourcePremises: {
|
|
36
|
+
premiseId: Id<"premises">;
|
|
37
|
+
embedding: number[];
|
|
38
|
+
}[];
|
|
39
|
+
sourceContexts: {
|
|
40
|
+
contextId: string;
|
|
41
|
+
networkId: Id<"networks">;
|
|
42
|
+
text: string;
|
|
43
|
+
embedding: number[];
|
|
44
|
+
}[];
|
|
45
|
+
trace: {
|
|
46
|
+
node: string;
|
|
47
|
+
detail: string;
|
|
48
|
+
}[];
|
|
49
|
+
error?: undefined;
|
|
50
|
+
} | {
|
|
51
|
+
error: string;
|
|
52
|
+
trace: {
|
|
53
|
+
node: string;
|
|
54
|
+
detail: string;
|
|
55
|
+
data: {
|
|
56
|
+
error: string;
|
|
57
|
+
};
|
|
58
|
+
}[];
|
|
59
|
+
}>;
|
|
60
|
+
/** Trace summary for {@link prepNode}. */
|
|
61
|
+
export declare function prepTraceSummary(result: unknown): string | undefined;
|
|
62
|
+
/**
|
|
63
|
+
* Node 1: Scope
|
|
64
|
+
* Determines which indexes to search within.
|
|
65
|
+
* If networkId provided: searches only that index.
|
|
66
|
+
* Otherwise: searches all user's indexes.
|
|
67
|
+
*/
|
|
68
|
+
export declare function scopeNode(state: OpportunityState, deps: OpportunityGraphDeps): Promise<{
|
|
69
|
+
targetNetworks: never[];
|
|
70
|
+
error: string;
|
|
71
|
+
indexRelevancyScores?: undefined;
|
|
72
|
+
agentTimings?: undefined;
|
|
73
|
+
trace?: undefined;
|
|
74
|
+
} | {
|
|
75
|
+
targetNetworks: TargetNetwork[];
|
|
76
|
+
indexRelevancyScores: Record<string, number>;
|
|
77
|
+
agentTimings: DebugMetaAgent[];
|
|
78
|
+
trace: {
|
|
79
|
+
node: string;
|
|
80
|
+
detail: string;
|
|
81
|
+
data: {
|
|
82
|
+
totalMembers: number;
|
|
83
|
+
};
|
|
84
|
+
}[];
|
|
85
|
+
error?: undefined;
|
|
86
|
+
} | {
|
|
87
|
+
targetNetworks: TargetNetwork[];
|
|
88
|
+
indexRelevancyScores: Record<string, number>;
|
|
89
|
+
trace: {
|
|
90
|
+
node: string;
|
|
91
|
+
detail: string;
|
|
92
|
+
data: {
|
|
93
|
+
totalMembers: number;
|
|
94
|
+
};
|
|
95
|
+
}[];
|
|
96
|
+
error?: undefined;
|
|
97
|
+
agentTimings?: undefined;
|
|
98
|
+
} | {
|
|
99
|
+
targetNetworks: never[];
|
|
100
|
+
error: string;
|
|
101
|
+
trace: {
|
|
102
|
+
node: string;
|
|
103
|
+
detail: string;
|
|
104
|
+
data: {
|
|
105
|
+
error: string;
|
|
106
|
+
};
|
|
107
|
+
}[];
|
|
108
|
+
indexRelevancyScores?: undefined;
|
|
109
|
+
agentTimings?: undefined;
|
|
110
|
+
}>;
|
|
111
|
+
/** Trace summary for {@link scopeNode}. */
|
|
112
|
+
export declare function scopeTraceSummary(result: unknown): string | undefined;
|
|
113
|
+
/**
|
|
114
|
+
* Node 2: Resolve
|
|
115
|
+
* Resolves trigger intent from triggerIntentId or searchQuery vs indexedIntents;
|
|
116
|
+
* sets discoverySource, resolvedTriggerIntentId, resolvedIntentInIndex for routing (path A/B/C).
|
|
117
|
+
*/
|
|
118
|
+
export declare function resolveNode(state: OpportunityState, deps: OpportunityGraphDeps): Promise<{
|
|
119
|
+
resolvedTriggerIntentId: undefined;
|
|
120
|
+
resolvedIntentInIndex: boolean;
|
|
121
|
+
discoverySource: "context";
|
|
122
|
+
error: string;
|
|
123
|
+
trace?: undefined;
|
|
124
|
+
} | {
|
|
125
|
+
resolvedTriggerIntentId: Id<"intents">;
|
|
126
|
+
resolvedIntentInIndex: boolean;
|
|
127
|
+
discoverySource: "intent" | "context";
|
|
128
|
+
error?: undefined;
|
|
129
|
+
trace?: undefined;
|
|
130
|
+
} | {
|
|
131
|
+
resolvedTriggerIntentId: undefined;
|
|
132
|
+
resolvedIntentInIndex: boolean;
|
|
133
|
+
discoverySource: "context";
|
|
134
|
+
error?: undefined;
|
|
135
|
+
trace?: undefined;
|
|
136
|
+
} | {
|
|
137
|
+
resolvedTriggerIntentId: undefined;
|
|
138
|
+
resolvedIntentInIndex: boolean;
|
|
139
|
+
discoverySource: "context";
|
|
140
|
+
error: string;
|
|
141
|
+
trace: {
|
|
142
|
+
node: string;
|
|
143
|
+
detail: string;
|
|
144
|
+
data: {
|
|
145
|
+
error: string;
|
|
146
|
+
};
|
|
147
|
+
}[];
|
|
148
|
+
}>;
|
|
149
|
+
/** Trace summary for {@link resolveNode}. */
|
|
150
|
+
export declare function resolveTraceSummary(result: unknown): string | undefined;
|
|
@@ -0,0 +1,381 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Discovery pipeline, stages 0–2: prep, scope, resolve.
|
|
3
|
+
*
|
|
4
|
+
* Each stage is a top-level function taking the graph state and an explicit
|
|
5
|
+
* dependency bag. `opportunity.graph.ts` wires them into the StateGraph.
|
|
6
|
+
*/
|
|
7
|
+
import { IntentIndexer } from '../../intents/index.js';
|
|
8
|
+
import { withCallLogging } from '../../shared/observability/protocol.logger.js';
|
|
9
|
+
import { timed } from '../../shared/observability/performance.js';
|
|
10
|
+
import { requestContext } from '../../shared/observability/request-context.js';
|
|
11
|
+
import { discoveryProfileMatchingEnabled, discoveryProfileSource } from '../discovery.env.js';
|
|
12
|
+
import { prepLog, scopeLog, resolveLog } from './opportunity.graph.shared.js';
|
|
13
|
+
/**
|
|
14
|
+
* Node 0: Prep
|
|
15
|
+
* Fetches user's network memberships and validates requirements.
|
|
16
|
+
* Returns empty if user has no network memberships (requirement).
|
|
17
|
+
*/
|
|
18
|
+
export async function prepNode(state, deps) {
|
|
19
|
+
return timed("OpportunityGraph.prep", async () => withCallLogging(prepLog, 'prepNode', {
|
|
20
|
+
userId: state.userId,
|
|
21
|
+
hasSearchQuery: !!state.searchQuery,
|
|
22
|
+
requestedIndexId: state.networkId ?? undefined,
|
|
23
|
+
}, async () => {
|
|
24
|
+
// Use getNetworkMemberships (all memberships) for search scope — NOT getUserIndexIds
|
|
25
|
+
// (which filters by autoAssign=true and is intended only for intent assignment).
|
|
26
|
+
const memberships = await deps.database.getNetworkMemberships(state.userId);
|
|
27
|
+
const userNetworkIds = memberships.map(m => m.networkId);
|
|
28
|
+
if (userNetworkIds.length === 0) {
|
|
29
|
+
prepLog.verbose('User has no network memberships - cannot find opportunities');
|
|
30
|
+
return {
|
|
31
|
+
userNetworks: [],
|
|
32
|
+
sourceProfile: null,
|
|
33
|
+
error: 'You need to join at least one network to find opportunities.',
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
const discoveryUserId = state.onBehalfOfUserId ?? state.userId;
|
|
37
|
+
const [intents, profile] = await Promise.all([
|
|
38
|
+
deps.database.getActiveIntents(discoveryUserId),
|
|
39
|
+
deps.database.getProfile(discoveryUserId),
|
|
40
|
+
]);
|
|
41
|
+
const indexedIntents = intents.map((intent) => ({
|
|
42
|
+
intentId: intent.id,
|
|
43
|
+
payload: intent.payload,
|
|
44
|
+
summary: intent.summary ?? undefined,
|
|
45
|
+
indexes: [],
|
|
46
|
+
}));
|
|
47
|
+
const sourceProfile = profile
|
|
48
|
+
? {
|
|
49
|
+
identity: profile.identity ?? undefined,
|
|
50
|
+
context: profile.context ?? undefined,
|
|
51
|
+
}
|
|
52
|
+
: null;
|
|
53
|
+
// Source premises are loaded after scope is resolved so premise discovery
|
|
54
|
+
// only uses premises assigned to the target network(s), and only up to
|
|
55
|
+
// DISCOVERY_SOURCE_PREMISE_LIMIT. Loading all premises here caused
|
|
56
|
+
// BACKEND-5: thousands of parallel vector searches for premise-rich users.
|
|
57
|
+
const sourcePremises = [];
|
|
58
|
+
const profileEnabled = discoveryProfileMatchingEnabled();
|
|
59
|
+
// Context-backed discovery is exclusively the user_context profile-source
|
|
60
|
+
// strategy. Premise mode must not load contexts or emit context-to-intent
|
|
61
|
+
// evidence merely because intent matching is also enabled.
|
|
62
|
+
const userContextProfileEnabled = profileEnabled && discoveryProfileSource() === 'user_context';
|
|
63
|
+
const contextToIntentEnabled = userContextProfileEnabled && process.env.DISCOVERY_CONTEXT_TO_INTENT !== '0';
|
|
64
|
+
const contextToContextEnabled = userContextProfileEnabled;
|
|
65
|
+
const rawContexts = (contextToIntentEnabled || contextToContextEnabled) && typeof deps.database.getUserContexts === 'function'
|
|
66
|
+
? await deps.database.getUserContexts(discoveryUserId)
|
|
67
|
+
: [];
|
|
68
|
+
const sourceContexts = rawContexts
|
|
69
|
+
// The global row (networkId: null) is excluded here — it is not in
|
|
70
|
+
// userNetworkIds — so context-to-intent discovery stays network-scoped.
|
|
71
|
+
.filter((c) => c.embedding && c.embedding.length > 0 && c.networkId !== null && userNetworkIds.includes(c.networkId))
|
|
72
|
+
.map((c) => ({
|
|
73
|
+
contextId: c.id,
|
|
74
|
+
networkId: c.networkId,
|
|
75
|
+
text: c.text,
|
|
76
|
+
embedding: c.embedding,
|
|
77
|
+
}));
|
|
78
|
+
return {
|
|
79
|
+
userNetworks: userNetworkIds,
|
|
80
|
+
indexedIntents,
|
|
81
|
+
sourceProfile,
|
|
82
|
+
sourcePremises,
|
|
83
|
+
sourceContexts,
|
|
84
|
+
trace: [{
|
|
85
|
+
node: "prep",
|
|
86
|
+
detail: `${userNetworkIds.length} network(s), ${intents.length} intent(s), premise discovery deferred, ${sourceContexts.length} context(s), ${profile ? 'profile loaded' : 'no profile'}`,
|
|
87
|
+
}],
|
|
88
|
+
};
|
|
89
|
+
}, { context: { userId: state.userId }, logOutput: true }).catch((error) => {
|
|
90
|
+
const errMsg = error instanceof Error ? error.message : String(error);
|
|
91
|
+
prepLog.error('Failed', { error });
|
|
92
|
+
return {
|
|
93
|
+
error: 'Failed to prepare opportunity search. Please try again.',
|
|
94
|
+
trace: [{
|
|
95
|
+
node: "prep_fatal",
|
|
96
|
+
detail: `Prep failed: ${errMsg}`,
|
|
97
|
+
data: { error: errMsg },
|
|
98
|
+
}],
|
|
99
|
+
};
|
|
100
|
+
}));
|
|
101
|
+
}
|
|
102
|
+
/** Trace summary for {@link prepNode}. */
|
|
103
|
+
export function prepTraceSummary(result) {
|
|
104
|
+
const r = result;
|
|
105
|
+
if (r?.error)
|
|
106
|
+
return `error: ${r.error}`;
|
|
107
|
+
const indexes = r?.userNetworks;
|
|
108
|
+
const intents = r?.indexedIntents;
|
|
109
|
+
return indexes && intents ? `${indexes.length} index(es), ${intents.length} intent(s)` : undefined;
|
|
110
|
+
}
|
|
111
|
+
/**
|
|
112
|
+
* Node 1: Scope
|
|
113
|
+
* Determines which indexes to search within.
|
|
114
|
+
* If networkId provided: searches only that index.
|
|
115
|
+
* Otherwise: searches all user's indexes.
|
|
116
|
+
*/
|
|
117
|
+
export async function scopeNode(state, deps) {
|
|
118
|
+
return timed("OpportunityGraph.scope", async () => {
|
|
119
|
+
scopeLog.verbose('Determining search scope', {
|
|
120
|
+
requestedIndexId: state.networkId,
|
|
121
|
+
userNetworksCount: state.userNetworks.length,
|
|
122
|
+
});
|
|
123
|
+
try {
|
|
124
|
+
let targetIndexIds;
|
|
125
|
+
if (state.networkId) {
|
|
126
|
+
// Validate user is member or owner of requested network
|
|
127
|
+
const isInScope = state.userNetworks.includes(state.networkId);
|
|
128
|
+
const isOwner = !isInScope && await deps.database.isIndexOwner(state.networkId, state.userId);
|
|
129
|
+
if (!isInScope && !isOwner) {
|
|
130
|
+
scopeLog.warn('User not member of requested network', {
|
|
131
|
+
networkId: state.networkId,
|
|
132
|
+
});
|
|
133
|
+
return {
|
|
134
|
+
targetNetworks: [],
|
|
135
|
+
error: 'You are not a member of that network.',
|
|
136
|
+
};
|
|
137
|
+
}
|
|
138
|
+
targetIndexIds = [state.networkId];
|
|
139
|
+
}
|
|
140
|
+
else if (state.indexScope !== undefined) {
|
|
141
|
+
// Bounded scope (e.g. a network-scoped agent's reachable networks):
|
|
142
|
+
// intersect with the user's actual memberships so discovery never
|
|
143
|
+
// reaches networks outside the agent's bound scope. An explicit
|
|
144
|
+
// empty scope is authoritative and must fail closed.
|
|
145
|
+
const allowed = new Set(state.indexScope);
|
|
146
|
+
targetIndexIds = state.userNetworks.filter((n) => allowed.has(n));
|
|
147
|
+
scopeLog.verbose('Applied indexScope intersection', {
|
|
148
|
+
indexScopeCount: state.indexScope.length,
|
|
149
|
+
userNetworksCount: state.userNetworks.length,
|
|
150
|
+
targetCount: targetIndexIds.length,
|
|
151
|
+
});
|
|
152
|
+
}
|
|
153
|
+
else {
|
|
154
|
+
// Search all user's indexes
|
|
155
|
+
targetIndexIds = state.userNetworks;
|
|
156
|
+
}
|
|
157
|
+
if (state.triggerIntentId) {
|
|
158
|
+
// A trigger intent is an authoritative discovery boundary, not just
|
|
159
|
+
// ranking context. Recompute the intersection at the graph edge so
|
|
160
|
+
// direct chat/MCP callers cannot bypass FromIntentQueue admission.
|
|
161
|
+
const assignedNetworkIds = new Set(await deps.database.getNetworkIdsForIntent(state.triggerIntentId));
|
|
162
|
+
const activeOwnerNetworkIds = new Set(state.userNetworks);
|
|
163
|
+
targetIndexIds = targetIndexIds.filter((networkId) => assignedNetworkIds.has(networkId) && activeOwnerNetworkIds.has(networkId));
|
|
164
|
+
scopeLog.verbose('Applied trigger-intent network intersection', {
|
|
165
|
+
triggerIntentId: state.triggerIntentId,
|
|
166
|
+
assignedCount: assignedNetworkIds.size,
|
|
167
|
+
targetCount: targetIndexIds.length,
|
|
168
|
+
});
|
|
169
|
+
}
|
|
170
|
+
// Fetch index details
|
|
171
|
+
const targetNetworks = await Promise.all(targetIndexIds.map(async (networkId) => {
|
|
172
|
+
const index = await deps.database.getNetwork(networkId);
|
|
173
|
+
const memberCount = await deps.database.getNetworkMemberCount(networkId);
|
|
174
|
+
return {
|
|
175
|
+
networkId,
|
|
176
|
+
title: index?.title ?? 'Unknown',
|
|
177
|
+
memberCount,
|
|
178
|
+
};
|
|
179
|
+
}));
|
|
180
|
+
scopeLog.verbose('Scope determined', {
|
|
181
|
+
targetIndexesCount: targetNetworks.length,
|
|
182
|
+
indexes: targetNetworks.map(i => i.title),
|
|
183
|
+
});
|
|
184
|
+
// ── Populate index relevancy scores for dedup tie-breaking ──
|
|
185
|
+
const indexRelevancyScores = {};
|
|
186
|
+
if (state.triggerIntentId) {
|
|
187
|
+
// Background path: look up persisted scores from intent_indexes
|
|
188
|
+
try {
|
|
189
|
+
const scores = await deps.database.getIntentIndexScores(state.triggerIntentId);
|
|
190
|
+
for (const { networkId, relevancyScore } of scores) {
|
|
191
|
+
if (relevancyScore != null) {
|
|
192
|
+
indexRelevancyScores[networkId] = relevancyScore;
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
catch (err) {
|
|
197
|
+
scopeLog.warn('Failed to load intent index scores', { triggerIntentId: state.triggerIntentId, error: err });
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
else if (state.searchQuery?.trim()) {
|
|
201
|
+
// Chat path: score query against target indexes in parallel
|
|
202
|
+
try {
|
|
203
|
+
const indexer = new IntentIndexer();
|
|
204
|
+
const scopeAgentTimings = [];
|
|
205
|
+
const scorableIndexes = targetNetworks.filter(ti => ti.title !== 'Unknown');
|
|
206
|
+
const scoringPromises = scorableIndexes.map(async (ti) => {
|
|
207
|
+
const ctx = await deps.database.getNetworkMemberContext(ti.networkId, state.userId);
|
|
208
|
+
if (!ctx?.indexPrompt?.trim() && !ctx?.memberPrompt?.trim()) {
|
|
209
|
+
return { networkId: ti.networkId, score: 1.0 };
|
|
210
|
+
}
|
|
211
|
+
const _indexerStart = Date.now();
|
|
212
|
+
const traceEmitter = requestContext.getStore()?.traceEmitter;
|
|
213
|
+
traceEmitter?.({ type: "agent_start", name: "intent-networker" });
|
|
214
|
+
let result = null;
|
|
215
|
+
try {
|
|
216
|
+
result = await indexer.invoke(state.searchQuery, ctx?.indexPrompt ?? null, ctx?.memberPrompt ?? null);
|
|
217
|
+
}
|
|
218
|
+
catch {
|
|
219
|
+
return { networkId: ti.networkId, score: 1.0 };
|
|
220
|
+
}
|
|
221
|
+
finally {
|
|
222
|
+
const _indexerDuration = Date.now() - _indexerStart;
|
|
223
|
+
traceEmitter?.({ type: "agent_end", name: "intent-networker", durationMs: _indexerDuration, summary: `Scored index ${ti.networkId}` });
|
|
224
|
+
scopeAgentTimings.push({ name: 'intent.indexer', durationMs: _indexerDuration });
|
|
225
|
+
}
|
|
226
|
+
if (!result)
|
|
227
|
+
return { networkId: ti.networkId, score: 1.0 };
|
|
228
|
+
const score = ctx?.indexPrompt && ctx?.memberPrompt
|
|
229
|
+
? result.indexScore * 0.6 + result.memberScore * 0.4
|
|
230
|
+
: ctx?.indexPrompt ? result.indexScore : result.memberScore;
|
|
231
|
+
return { networkId: ti.networkId, score };
|
|
232
|
+
});
|
|
233
|
+
const results = await Promise.all(scoringPromises);
|
|
234
|
+
for (const { networkId, score } of results) {
|
|
235
|
+
indexRelevancyScores[networkId] = score;
|
|
236
|
+
}
|
|
237
|
+
// Accumulate indexer timings into graph state
|
|
238
|
+
if (scopeAgentTimings.length > 0) {
|
|
239
|
+
return {
|
|
240
|
+
targetNetworks,
|
|
241
|
+
indexRelevancyScores,
|
|
242
|
+
agentTimings: scopeAgentTimings,
|
|
243
|
+
trace: [{
|
|
244
|
+
node: "scope",
|
|
245
|
+
detail: `Searching ${targetNetworks.length} index(es): ${targetNetworks.map(i => `${i.title} (${i.memberCount})`).join(', ')}`,
|
|
246
|
+
data: { totalMembers: targetNetworks.reduce((sum, i) => sum + i.memberCount, 0) },
|
|
247
|
+
}],
|
|
248
|
+
};
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
catch (err) {
|
|
252
|
+
scopeLog.warn('Failed to score query against indexes', { error: err });
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
const totalMembers = targetNetworks.reduce((sum, i) => sum + i.memberCount, 0);
|
|
256
|
+
return {
|
|
257
|
+
targetNetworks,
|
|
258
|
+
indexRelevancyScores,
|
|
259
|
+
trace: [{
|
|
260
|
+
node: "scope",
|
|
261
|
+
detail: `Searching ${targetNetworks.length} index(es): ${targetNetworks.map(i => `${i.title} (${i.memberCount})`).join(', ')}`,
|
|
262
|
+
data: { totalMembers },
|
|
263
|
+
}],
|
|
264
|
+
};
|
|
265
|
+
}
|
|
266
|
+
catch (error) {
|
|
267
|
+
const errMsg = error instanceof Error ? error.message : String(error);
|
|
268
|
+
scopeLog.error('Failed', { error });
|
|
269
|
+
return {
|
|
270
|
+
targetNetworks: [],
|
|
271
|
+
error: 'Failed to determine search scope.',
|
|
272
|
+
trace: [{
|
|
273
|
+
node: "scope_fatal",
|
|
274
|
+
detail: `Scope failed: ${errMsg}`,
|
|
275
|
+
data: { error: errMsg },
|
|
276
|
+
}],
|
|
277
|
+
};
|
|
278
|
+
}
|
|
279
|
+
});
|
|
280
|
+
}
|
|
281
|
+
/** Trace summary for {@link scopeNode}. */
|
|
282
|
+
export function scopeTraceSummary(result) {
|
|
283
|
+
const r = result;
|
|
284
|
+
if (r?.error)
|
|
285
|
+
return `error: ${r.error}`;
|
|
286
|
+
const indexes = r?.targetNetworks;
|
|
287
|
+
return indexes ? `${indexes.length} index(es) in scope` : undefined;
|
|
288
|
+
}
|
|
289
|
+
/**
|
|
290
|
+
* Node 2: Resolve
|
|
291
|
+
* Resolves trigger intent from triggerIntentId or searchQuery vs indexedIntents;
|
|
292
|
+
* sets discoverySource, resolvedTriggerIntentId, resolvedIntentInIndex for routing (path A/B/C).
|
|
293
|
+
*/
|
|
294
|
+
export async function resolveNode(state, deps) {
|
|
295
|
+
return timed("OpportunityGraph.resolve", async () => {
|
|
296
|
+
resolveLog.verbose('Resolving intent and network membership', {
|
|
297
|
+
triggerIntentId: state.triggerIntentId,
|
|
298
|
+
hasSearchQuery: !!state.searchQuery,
|
|
299
|
+
indexedIntentsCount: state.indexedIntents.length,
|
|
300
|
+
});
|
|
301
|
+
const targetIndexIds = state.targetNetworks.map((t) => t.networkId);
|
|
302
|
+
try {
|
|
303
|
+
let resolvedIntentId;
|
|
304
|
+
if (state.triggerIntentId) {
|
|
305
|
+
const isOwnedActiveIntent = state.indexedIntents.some((intent) => intent.intentId === state.triggerIntentId);
|
|
306
|
+
if (!isOwnedActiveIntent) {
|
|
307
|
+
resolveLog.warn('Trigger intent is not an active intent owned by the discovery user', {
|
|
308
|
+
triggerIntentId: state.triggerIntentId,
|
|
309
|
+
userId: state.userId,
|
|
310
|
+
});
|
|
311
|
+
return {
|
|
312
|
+
resolvedTriggerIntentId: undefined,
|
|
313
|
+
resolvedIntentInIndex: false,
|
|
314
|
+
discoverySource: 'context',
|
|
315
|
+
error: 'Trigger intent is not available for discovery.',
|
|
316
|
+
};
|
|
317
|
+
}
|
|
318
|
+
const inNetwork = await deps.database.getNetworkIdsForIntent(state.triggerIntentId);
|
|
319
|
+
const inTarget = inNetwork.some((id) => targetIndexIds.includes(id));
|
|
320
|
+
resolvedIntentId = state.triggerIntentId;
|
|
321
|
+
const resolvedIntentInIndex = inTarget;
|
|
322
|
+
const discoverySource = resolvedIntentInIndex ? 'intent' : 'context';
|
|
323
|
+
return {
|
|
324
|
+
resolvedTriggerIntentId: resolvedIntentId,
|
|
325
|
+
resolvedIntentInIndex,
|
|
326
|
+
discoverySource,
|
|
327
|
+
};
|
|
328
|
+
}
|
|
329
|
+
if (state.searchQuery?.trim() && state.indexedIntents.length > 0) {
|
|
330
|
+
const q = state.searchQuery.trim().toLowerCase();
|
|
331
|
+
const matched = state.indexedIntents.find((i) => i.payload?.toLowerCase().includes(q));
|
|
332
|
+
if (matched) {
|
|
333
|
+
resolvedIntentId = matched.intentId;
|
|
334
|
+
const inNetwork = await deps.database.getNetworkIdsForIntent(matched.intentId);
|
|
335
|
+
const resolvedIntentInIndex = inNetwork.some((id) => targetIndexIds.includes(id));
|
|
336
|
+
const discoverySource = resolvedIntentInIndex ? 'intent' : 'context';
|
|
337
|
+
return {
|
|
338
|
+
resolvedTriggerIntentId: resolvedIntentId,
|
|
339
|
+
resolvedIntentInIndex,
|
|
340
|
+
discoverySource,
|
|
341
|
+
};
|
|
342
|
+
}
|
|
343
|
+
resolveLog.warn('No intent matched search query; leaving resolvedIntentId unset', {
|
|
344
|
+
searchQuery: state.searchQuery,
|
|
345
|
+
indexedIntentsCount: state.indexedIntents.length,
|
|
346
|
+
});
|
|
347
|
+
}
|
|
348
|
+
return {
|
|
349
|
+
resolvedTriggerIntentId: undefined,
|
|
350
|
+
resolvedIntentInIndex: false,
|
|
351
|
+
discoverySource: 'context',
|
|
352
|
+
};
|
|
353
|
+
}
|
|
354
|
+
catch (err) {
|
|
355
|
+
const errMsg = err instanceof Error ? err.message : String(err);
|
|
356
|
+
resolveLog.error('Failed', {
|
|
357
|
+
triggerIntentId: state.triggerIntentId,
|
|
358
|
+
searchQuery: state.searchQuery,
|
|
359
|
+
error: err,
|
|
360
|
+
});
|
|
361
|
+
return {
|
|
362
|
+
resolvedTriggerIntentId: undefined,
|
|
363
|
+
resolvedIntentInIndex: false,
|
|
364
|
+
discoverySource: 'context',
|
|
365
|
+
error: errMsg || 'Resolve failed',
|
|
366
|
+
trace: [{
|
|
367
|
+
node: "resolve_fatal",
|
|
368
|
+
detail: `Resolve failed: ${errMsg}`,
|
|
369
|
+
data: { error: errMsg },
|
|
370
|
+
}],
|
|
371
|
+
};
|
|
372
|
+
}
|
|
373
|
+
});
|
|
374
|
+
}
|
|
375
|
+
/** Trace summary for {@link resolveNode}. */
|
|
376
|
+
export function resolveTraceSummary(result) {
|
|
377
|
+
const r = result;
|
|
378
|
+
if (r?.error)
|
|
379
|
+
return `error: ${r.error}`;
|
|
380
|
+
return r?.discoverySource ? `source: ${r.discoverySource}` : undefined;
|
|
381
|
+
}
|