@indexnetwork/protocol 4.3.4-rc.309.1 → 4.3.4-rc.311.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/IMPLEMENTATION.md +2 -2
- package/dist/chat/chat.agent.d.ts +1 -1
- package/dist/chat/chat.agent.js +1 -1
- package/dist/chat/chat.agent.js.map +1 -1
- package/dist/chat/chat.graph.d.ts +8 -8
- package/dist/chat/chat.prompt.js +30 -30
- package/dist/chat/chat.prompt.js.map +1 -1
- package/dist/chat/chat.prompt.modules.js +8 -8
- package/dist/chat/chat.prompt.modules.js.map +1 -1
- package/dist/chat/chat.state.d.ts +5 -4
- package/dist/chat/chat.state.d.ts.map +1 -1
- package/dist/chat/chat.state.js +2 -2
- package/dist/chat/chat.state.js.map +1 -1
- package/dist/chat/chat.streamer.d.ts +3 -2
- package/dist/chat/chat.streamer.d.ts.map +1 -1
- package/dist/chat/chat.streamer.js +2 -2
- package/dist/chat/chat.streamer.js.map +1 -1
- package/dist/chat/tests/chat.graph.mocks.d.ts +1 -1
- package/dist/chat/tests/chat.graph.mocks.js +1 -1
- package/dist/chat/tests/chat.graph.mocks.js.map +1 -1
- package/dist/contact/contact.tools.js +6 -6
- package/dist/contact/contact.tools.js.map +1 -1
- package/dist/enrichment/enrichment.tools.js +7 -7
- package/dist/enrichment/enrichment.tools.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 +5 -5
- package/dist/intent/intent.graph.d.ts.map +1 -1
- package/dist/intent/intent.graph.js +9 -9
- package/dist/intent/intent.graph.js.map +1 -1
- package/dist/intent/intent.indexer.d.ts +1 -1
- package/dist/intent/intent.indexer.js +4 -4
- package/dist/intent/intent.indexer.js.map +1 -1
- package/dist/intent/intent.state.d.ts +9 -8
- package/dist/intent/intent.state.d.ts.map +1 -1
- package/dist/intent/intent.state.js +7 -7
- package/dist/intent/intent.state.js.map +1 -1
- package/dist/intent/intent.tools.js +26 -26
- package/dist/intent/intent.tools.js.map +1 -1
- package/dist/maintenance/maintenance.graph.d.ts +2 -2
- package/dist/maintenance/maintenance.graph.d.ts.map +1 -1
- package/dist/maintenance/maintenance.graph.js.map +1 -1
- package/dist/mcp/mcp.server.d.ts +2 -2
- package/dist/mcp/mcp.server.js +4 -4
- package/dist/mcp/mcp.server.js.map +1 -1
- package/dist/network/indexer/indexer.graph.d.ts +2 -2
- package/dist/network/indexer/indexer.graph.d.ts.map +1 -1
- package/dist/network/indexer/indexer.graph.js +13 -13
- package/dist/network/indexer/indexer.graph.js.map +1 -1
- package/dist/network/indexer/indexer.state.d.ts +3 -3
- package/dist/network/indexer/indexer.state.d.ts.map +1 -1
- package/dist/network/indexer/indexer.state.js +4 -4
- package/dist/network/indexer/indexer.state.js.map +1 -1
- package/dist/network/membership/membership.graph.d.ts +1 -1
- package/dist/network/membership/membership.graph.js +21 -21
- package/dist/network/membership/membership.graph.js.map +1 -1
- package/dist/network/membership/membership.state.d.ts +2 -2
- package/dist/network/membership/membership.state.js +2 -2
- package/dist/network/membership/membership.state.js.map +1 -1
- package/dist/network/network.graph.d.ts +1 -1
- package/dist/network/network.graph.d.ts.map +1 -1
- package/dist/network/network.graph.js +13 -13
- package/dist/network/network.graph.js.map +1 -1
- package/dist/network/network.state.d.ts +3 -3
- package/dist/network/network.state.d.ts.map +1 -1
- package/dist/network/network.state.js +3 -3
- package/dist/network/network.state.js.map +1 -1
- package/dist/network/network.tools.d.ts.map +1 -1
- package/dist/network/network.tools.js +95 -107
- package/dist/network/network.tools.js.map +1 -1
- package/dist/opportunity/feed/feed.graph.d.ts +10 -0
- package/dist/opportunity/feed/feed.graph.d.ts.map +1 -1
- package/dist/opportunity/feed/feed.graph.js +4 -0
- package/dist/opportunity/feed/feed.graph.js.map +1 -1
- package/dist/opportunity/feed/feed.state.d.ts +2 -0
- package/dist/opportunity/feed/feed.state.d.ts.map +1 -1
- package/dist/opportunity/feed/feed.state.js +8 -0
- package/dist/opportunity/feed/feed.state.js.map +1 -1
- package/dist/opportunity/opportunity.discover.d.ts +1 -1
- package/dist/opportunity/opportunity.discover.d.ts.map +1 -1
- package/dist/opportunity/opportunity.discover.js +1 -1
- package/dist/opportunity/opportunity.discover.js.map +1 -1
- package/dist/opportunity/opportunity.graph.d.ts +6 -6
- package/dist/opportunity/opportunity.graph.js +10 -10
- package/dist/opportunity/opportunity.graph.js.map +1 -1
- package/dist/opportunity/opportunity.introducer.d.ts +3 -3
- package/dist/opportunity/opportunity.introducer.d.ts.map +1 -1
- package/dist/opportunity/opportunity.introducer.js +2 -2
- package/dist/opportunity/opportunity.introducer.js.map +1 -1
- package/dist/opportunity/opportunity.state.d.ts +2 -2
- package/dist/opportunity/opportunity.state.js +1 -1
- package/dist/opportunity/opportunity.state.js.map +1 -1
- package/dist/opportunity/opportunity.tools.d.ts.map +1 -1
- package/dist/opportunity/opportunity.tools.js +89 -22
- package/dist/opportunity/opportunity.tools.js.map +1 -1
- package/dist/premise/premise.graph.d.ts +4 -4
- package/dist/premise/premise.indexer.d.ts +1 -1
- package/dist/premise/premise.indexer.js +5 -5
- package/dist/premise/premise.indexer.js.map +1 -1
- package/dist/premise/premise.state.d.ts +2 -1
- package/dist/premise/premise.state.d.ts.map +1 -1
- package/dist/questioner/questioner.tools.d.ts.map +1 -1
- package/dist/questioner/questioner.tools.js +22 -9
- package/dist/questioner/questioner.tools.js.map +1 -1
- package/dist/shared/agent/tool.factory.d.ts +1 -1
- package/dist/shared/agent/tool.factory.js +1 -1
- package/dist/shared/agent/tool.factory.js.map +1 -1
- package/dist/shared/agent/tool.helpers.d.ts +14 -11
- package/dist/shared/agent/tool.helpers.d.ts.map +1 -1
- package/dist/shared/agent/tool.helpers.js +2 -2
- package/dist/shared/agent/tool.helpers.js.map +1 -1
- package/dist/shared/agent/tool.scope.d.ts +7 -1
- package/dist/shared/agent/tool.scope.d.ts.map +1 -1
- package/dist/shared/agent/tool.scope.js +11 -1
- package/dist/shared/agent/tool.scope.js.map +1 -1
- package/dist/shared/agent/utility.tools.js +20 -20
- package/dist/shared/agent/utility.tools.js.map +1 -1
- package/dist/shared/hyde/hyde.graph.d.ts +6 -6
- package/dist/shared/hyde/hyde.state.d.ts +2 -2
- package/dist/shared/interfaces/database.interface.d.ts +62 -59
- package/dist/shared/interfaces/database.interface.d.ts.map +1 -1
- package/dist/shared/interfaces/database.interface.js.map +1 -1
- package/dist/shared/interfaces/embedder.interface.d.ts +2 -2
- package/dist/shared/interfaces/embedder.interface.d.ts.map +1 -1
- package/dist/shared/interfaces/embedder.interface.js.map +1 -1
- package/dist/shared/interfaces/questioner.interface.d.ts +3 -0
- package/dist/shared/interfaces/questioner.interface.d.ts.map +1 -1
- package/dist/shared/interfaces/questioner.interface.js.map +1 -1
- package/dist/shared/schemas/network-assignment.schema.d.ts +2 -2
- package/package.json +1 -1
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
* Introducer Discovery: proactive discovery of connector-flow opportunities
|
|
3
3
|
* between a user's contacts.
|
|
4
4
|
*
|
|
5
|
-
* Selects top-N contacts from the user's personal
|
|
5
|
+
* Selects top-N contacts from the user's personal network and runs scoped
|
|
6
6
|
* HyDE discovery for each, creating latent introducer opportunities that
|
|
7
7
|
* the user (as introducer) must approve before parties see them.
|
|
8
8
|
*/
|
|
@@ -22,9 +22,9 @@ export interface ContactWithIntents {
|
|
|
22
22
|
}
|
|
23
23
|
/** Database methods needed for introducer discovery contact selection. */
|
|
24
24
|
export interface IntroducerDiscoveryDatabase {
|
|
25
|
-
/** Get the user's personal
|
|
25
|
+
/** Get the user's personal network ID. */
|
|
26
26
|
getPersonalIndexId(userId: string): Promise<string | null>;
|
|
27
|
-
/** Get contacts from a personal
|
|
27
|
+
/** Get contacts from a personal network with their intent freshness data. */
|
|
28
28
|
getContactsWithIntentFreshness(personalIndexId: string, ownerId: string, limit: number): Promise<ContactWithIntents[]>;
|
|
29
29
|
}
|
|
30
30
|
/** Queue interface for enqueuing introducer discovery jobs. */
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"opportunity.introducer.d.ts","sourceRoot":"/","sources":["opportunity/opportunity.introducer.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAMH,0DAA0D;AAC1D,eAAO,MAAM,sBAAsB,IAAI,CAAC;AAExC,mDAAmD;AACnD,eAAO,MAAM,0BAA0B,IAAI,CAAC;AAE5C,sEAAsE;AACtE,eAAO,MAAM,2BAA2B,EAAG,sBAA+B,CAAC;AAE3E,oFAAoF;AACpF,MAAM,WAAW,kBAAkB;IACjC,MAAM,EAAE,MAAM,CAAC;IACf,mEAAmE;IACnE,cAAc,EAAE,MAAM,GAAG,IAAI,CAAC;IAC9B,iDAAiD;IACjD,WAAW,EAAE,MAAM,CAAC;CACrB;AAED,0EAA0E;AAC1E,MAAM,WAAW,2BAA2B;IAC1C,
|
|
1
|
+
{"version":3,"file":"opportunity.introducer.d.ts","sourceRoot":"/","sources":["opportunity/opportunity.introducer.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAMH,0DAA0D;AAC1D,eAAO,MAAM,sBAAsB,IAAI,CAAC;AAExC,mDAAmD;AACnD,eAAO,MAAM,0BAA0B,IAAI,CAAC;AAE5C,sEAAsE;AACtE,eAAO,MAAM,2BAA2B,EAAG,sBAA+B,CAAC;AAE3E,oFAAoF;AACpF,MAAM,WAAW,kBAAkB;IACjC,MAAM,EAAE,MAAM,CAAC;IACf,mEAAmE;IACnE,cAAc,EAAE,MAAM,GAAG,IAAI,CAAC;IAC9B,iDAAiD;IACjD,WAAW,EAAE,MAAM,CAAC;CACrB;AAED,0EAA0E;AAC1E,MAAM,WAAW,2BAA2B;IAC1C,0CAA0C;IAC1C,kBAAkB,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAAC;IAC3D,6EAA6E;IAC7E,8BAA8B,CAC5B,eAAe,EAAE,MAAM,EACvB,OAAO,EAAE,MAAM,EACf,KAAK,EAAE,MAAM,GACZ,OAAO,CAAC,kBAAkB,EAAE,CAAC,CAAC;CAClC;AAED,+DAA+D;AAC/D,MAAM,WAAW,wBAAwB;IACvC,MAAM,CACJ,IAAI,EAAE;QAAE,QAAQ,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAC;QAAC,QAAQ,CAAC,EAAE,MAAM,EAAE,CAAC;QAAC,aAAa,CAAC,EAAE,MAAM,CAAA;KAAE,EACvF,OAAO,CAAC,EAAE;QAAE,QAAQ,CAAC,EAAE,MAAM,CAAC;QAAC,KAAK,CAAC,EAAE,MAAM,CAAA;KAAE,GAC9C,OAAO,CAAC,OAAO,CAAC,CAAC;CACrB;AAED,qDAAqD;AACrD,MAAM,WAAW,yBAAyB;IACxC,iBAAiB,EAAE,MAAM,CAAC;IAC1B,YAAY,EAAE,MAAM,CAAC;IACrB,aAAa,CAAC,EAAE,MAAM,CAAC;CACxB;AAED;;;;;;;;GAQG;AACH,wBAAsB,0BAA0B,CAC9C,QAAQ,EAAE,2BAA2B,EACrC,MAAM,EAAE,MAAM,EACd,KAAK,GAAE,MAA+B,GACrC,OAAO,CAAC,kBAAkB,EAAE,CAAC,CAgB/B;AAED;;;;;;;;GAQG;AACH,wBAAgB,4BAA4B,CAC1C,kBAAkB,EAAE,MAAM,EAC1B,mBAAmB,GAAE,MAAU,GAC9B,OAAO,CAET;AAED;;;;;;;;GAQG;AACH,wBAAsB,sBAAsB,CAC1C,QAAQ,EAAE,2BAA2B,EACrC,KAAK,EAAE,wBAAwB,EAC/B,MAAM,EAAE,MAAM,GACb,OAAO,CAAC,yBAAyB,CAAC,CAoDpC"}
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
* Introducer Discovery: proactive discovery of connector-flow opportunities
|
|
3
3
|
* between a user's contacts.
|
|
4
4
|
*
|
|
5
|
-
* Selects top-N contacts from the user's personal
|
|
5
|
+
* Selects top-N contacts from the user's personal network and runs scoped
|
|
6
6
|
* HyDE discovery for each, creating latent introducer opportunities that
|
|
7
7
|
* the user (as introducer) must approve before parties see them.
|
|
8
8
|
*/
|
|
@@ -26,7 +26,7 @@ export const INTRODUCER_DISCOVERY_SOURCE = 'introducer_discovery';
|
|
|
26
26
|
export async function selectContactsForDiscovery(database, userId, limit = MAX_CONTACTS_PER_CYCLE) {
|
|
27
27
|
const personalIndexId = await database.getPersonalIndexId(userId);
|
|
28
28
|
if (!personalIndexId) {
|
|
29
|
-
logger.verbose(`[IntroducerDiscovery] No personal
|
|
29
|
+
logger.verbose(`[IntroducerDiscovery] No personal network found — userId=${userId}`);
|
|
30
30
|
return [];
|
|
31
31
|
}
|
|
32
32
|
const contacts = await database.getContactsWithIntentFreshness(personalIndexId, userId, limit);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"opportunity.introducer.js","sourceRoot":"/","sources":["opportunity/opportunity.introducer.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,EAAE,cAAc,EAAE,MAAM,4CAA4C,CAAC;AAE5E,MAAM,MAAM,GAAG,cAAc,CAAC,qBAAqB,CAAC,CAAC;AAErD,0DAA0D;AAC1D,MAAM,CAAC,MAAM,sBAAsB,GAAG,CAAC,CAAC;AAExC,mDAAmD;AACnD,MAAM,CAAC,MAAM,0BAA0B,GAAG,CAAC,CAAC;AAE5C,sEAAsE;AACtE,MAAM,CAAC,MAAM,2BAA2B,GAAG,sBAA+B,CAAC;AAsC3E;;;;;;;;GAQG;AACH,MAAM,CAAC,KAAK,UAAU,0BAA0B,CAC9C,QAAqC,EACrC,MAAc,EACd,QAAgB,sBAAsB;IAEtC,MAAM,eAAe,GAAG,MAAM,QAAQ,CAAC,kBAAkB,CAAC,MAAM,CAAC,CAAC;IAClE,IAAI,CAAC,eAAe,EAAE,CAAC;QACrB,MAAM,CAAC,OAAO,CAAC,
|
|
1
|
+
{"version":3,"file":"opportunity.introducer.js","sourceRoot":"/","sources":["opportunity/opportunity.introducer.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Introducer Discovery: proactive discovery of connector-flow opportunities\n * between a user's contacts.\n *\n * Selects top-N contacts from the user's personal network and runs scoped\n * HyDE discovery for each, creating latent introducer opportunities that\n * the user (as introducer) must approve before parties see them.\n */\n\nimport { protocolLogger } from '../shared/observability/protocol.logger.js';\n\nconst logger = protocolLogger('IntroducerDiscovery');\n\n/** Maximum contacts to evaluate per maintenance cycle. */\nexport const MAX_CONTACTS_PER_CYCLE = 5;\n\n/** Maximum candidate opportunities per contact. */\nexport const MAX_CANDIDATES_PER_CONTACT = 3;\n\n/** Detection source value for introducer-discovered opportunities. */\nexport const INTRODUCER_DISCOVERY_SOURCE = 'introducer_discovery' as const;\n\n/** A contact with their active intents, used for introducer discovery selection. */\nexport interface ContactWithIntents {\n userId: string;\n /** Most recent intent updatedAt timestamp (ISO string or null). */\n latestIntentAt: string | null;\n /** Number of active intents this contact has. */\n intentCount: number;\n}\n\n/** Database methods needed for introducer discovery contact selection. */\nexport interface IntroducerDiscoveryDatabase {\n /** Get the user's personal network ID. */\n getPersonalIndexId(userId: string): Promise<string | null>;\n /** Get contacts from a personal network with their intent freshness data. */\n getContactsWithIntentFreshness(\n personalIndexId: string,\n ownerId: string,\n limit: number,\n ): Promise<ContactWithIntents[]>;\n}\n\n/** Queue interface for enqueuing introducer discovery jobs. */\nexport interface IntroducerDiscoveryQueue {\n addJob(\n data: { intentId: string; userId: string; indexIds?: string[]; contactUserId?: string },\n options?: { priority?: number; jobId?: string },\n ): Promise<unknown>;\n}\n\n/** Result of a single introducer discovery cycle. */\nexport interface IntroducerDiscoveryResult {\n contactsEvaluated: number;\n jobsEnqueued: number;\n skippedReason?: string;\n}\n\n/**\n * Select top-N contacts for introducer discovery, sorted by intent freshness.\n * Contacts with no active intents are excluded.\n *\n * @param database - Database adapter with contact/intent queries\n * @param userId - The introducer user\n * @param limit - Max contacts to return (default MAX_CONTACTS_PER_CYCLE)\n * @returns Sorted contacts with intent data\n */\nexport async function selectContactsForDiscovery(\n database: IntroducerDiscoveryDatabase,\n userId: string,\n limit: number = MAX_CONTACTS_PER_CYCLE,\n): Promise<ContactWithIntents[]> {\n const personalIndexId = await database.getPersonalIndexId(userId);\n if (!personalIndexId) {\n logger.verbose(`[IntroducerDiscovery] No personal network found — userId=${userId}`);\n return [];\n }\n\n const contacts = await database.getContactsWithIntentFreshness(\n personalIndexId,\n userId,\n limit,\n );\n\n logger.verbose(`[IntroducerDiscovery] Selected contacts for discovery — userId=${userId} totalContacts=${contacts.length} limit=${limit}`);\n\n return contacts;\n}\n\n/**\n * Determine whether introducer discovery should run based on the current\n * connector-flow composition. Triggers when connector-flow count is below\n * the soft target.\n *\n * @param connectorFlowCount - Current number of connector-flow opportunities\n * @param connectorFlowTarget - Soft target (default 2)\n * @returns Whether introducer discovery should run\n */\nexport function shouldRunIntroducerDiscovery(\n connectorFlowCount: number,\n connectorFlowTarget: number = 2,\n): boolean {\n return connectorFlowCount < connectorFlowTarget;\n}\n\n/**\n * Run introducer discovery for a user: select contacts, enqueue discovery jobs.\n * Each job uses onBehalfOfUserId so the opportunity graph treats the user as introducer.\n *\n * @param database - Database adapter for contact queries\n * @param queue - Queue for enqueuing discovery jobs\n * @param userId - The introducer user\n * @returns Summary of the discovery cycle\n */\nexport async function runIntroducerDiscovery(\n database: IntroducerDiscoveryDatabase,\n queue: IntroducerDiscoveryQueue,\n userId: string,\n): Promise<IntroducerDiscoveryResult> {\n const personalIndexId = await database.getPersonalIndexId(userId);\n if (!personalIndexId) {\n return { contactsEvaluated: 0, jobsEnqueued: 0, skippedReason: 'no_personal_index' };\n }\n\n const contacts = await selectContactsForDiscovery(database, userId);\n if (contacts.length === 0) {\n return { contactsEvaluated: 0, jobsEnqueued: 0, skippedReason: 'no_contacts' };\n }\n\n const bucket = Math.floor(Date.now() / (12 * 60 * 60 * 1000)); // 12h dedup bucket\n\n const results = await Promise.allSettled(\n contacts.map(async (contact) => {\n // For each contact, we enqueue a discovery job using one of their intents\n // The opportunity graph will use onBehalfOfUserId to discover on behalf of the contact\n // while the userId (introducer) gets the introducer role\n const jobId = `introducer-discovery-${userId}-${contact.userId}-${bucket}`;\n try {\n await queue.addJob(\n {\n intentId: `introducer:${contact.userId}`,\n userId,\n indexIds: [personalIndexId],\n contactUserId: contact.userId,\n },\n { priority: 15, jobId }, // Lower priority than regular rediscovery (10)\n );\n return true;\n } catch (err) {\n const message = err instanceof Error ? err.message : String(err);\n if (/duplicate|already exists|job.*id/i.test(message)) {\n logger.verbose(`[IntroducerDiscovery] Job skipped (duplicate) — userId=${userId} contactUserId=${contact.userId} error=${message}`);\n return false;\n }\n throw err;\n }\n }),\n );\n\n for (const r of results) {\n if (r.status === 'rejected') {\n const errMsg = r.reason instanceof Error ? r.reason.message : String(r.reason);\n logger.error(`[IntroducerDiscovery] Job enqueue failed: ${errMsg}`);\n }\n }\n const jobsEnqueued = results.filter((r) => r.status === 'fulfilled' && r.value).length;\n\n logger.info(`[IntroducerDiscovery] Discovery cycle complete — userId=${userId} contactsEvaluated=${contacts.length} jobsEnqueued=${jobsEnqueued}`);\n\n return { contactsEvaluated: contacts.length, jobsEnqueued };\n}\n"]}
|
|
@@ -171,7 +171,7 @@ export declare const OpportunityGraphState: import("@langchain/langgraph").Annot
|
|
|
171
171
|
networkId: import("@langchain/langgraph").BaseChannel<Id<"networks"> | undefined, Id<"networks"> | import("@langchain/langgraph").OverwriteValue<Id<"networks"> | undefined> | undefined, unknown>;
|
|
172
172
|
/**
|
|
173
173
|
* Optional set of indexes discovery may search within (e.g. a network-scoped
|
|
174
|
-
* agent's reachable
|
|
174
|
+
* agent's reachable networks: the bound network plus the user's personal network).
|
|
175
175
|
* The scope node intersects this with the user's actual memberships. Ignored
|
|
176
176
|
* when `networkId` is set (single-network override). When unset, discovery
|
|
177
177
|
* spans all of the user's networks.
|
|
@@ -259,7 +259,7 @@ export declare const OpportunityGraphState: import("@langchain/langgraph").Annot
|
|
|
259
259
|
/** Per-index relevancy scores for dedup tie-breaking. Background path: from intent_indexes. Chat path: transient from IntentIndexer. */
|
|
260
260
|
indexRelevancyScores: import("@langchain/langgraph").BaseChannel<Record<string, number>, Record<string, number> | import("@langchain/langgraph").OverwriteValue<Record<string, number>>, unknown>;
|
|
261
261
|
/** Whether discovery used intent (path A) or user context (path B/C). Used by persist for triggeredBy. In-memory routing state only; never persisted. */
|
|
262
|
-
discoverySource: import("@langchain/langgraph").BaseChannel<"
|
|
262
|
+
discoverySource: import("@langchain/langgraph").BaseChannel<"intent" | "context", "intent" | "context" | import("@langchain/langgraph").OverwriteValue<"intent" | "context">, unknown>;
|
|
263
263
|
/** Resolved intent ID used for this discovery run (when discoverySource is 'intent'). Set by intent-resolution. */
|
|
264
264
|
resolvedTriggerIntentId: import("@langchain/langgraph").BaseChannel<Id<"intents"> | undefined, Id<"intents"> | import("@langchain/langgraph").OverwriteValue<Id<"intents"> | undefined> | undefined, unknown>;
|
|
265
265
|
/** Asker's profile (from prep). Used for profile-as-source discovery and evaluation. */
|
|
@@ -40,7 +40,7 @@ export const OpportunityGraphState = Annotation.Root({
|
|
|
40
40
|
}),
|
|
41
41
|
/**
|
|
42
42
|
* Optional set of indexes discovery may search within (e.g. a network-scoped
|
|
43
|
-
* agent's reachable
|
|
43
|
+
* agent's reachable networks: the bound network plus the user's personal network).
|
|
44
44
|
* The scope node intersects this with the user's actual memberships. Ignored
|
|
45
45
|
* when `networkId` is set (single-network override). When unset, discovery
|
|
46
46
|
* spans all of the user's networks.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"opportunity.state.js","sourceRoot":"/","sources":["opportunity/opportunity.state.ts"],"names":[],"mappings":"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","sourcesContent":["import { Annotation } from \"@langchain/langgraph\";\nimport type { Id } from '../shared/interfaces/database.interface.js';\nimport type { OpportunityStatus, Opportunity } from '../shared/interfaces/database.interface.js';\nimport type { Lens } from '../shared/interfaces/embedder.interface.js';\nimport type { EvaluatorEntity } from './opportunity.evaluator.js';\nimport type { DebugMetaAgent } from '../chat/chat-streaming.types.js';\nimport type { OpportunityEvidence } from '../shared/schemas/network-assignment.schema.js';\nimport type { DiscoveryNegotiation, DiscoverySummary } from \"./question.prompt.js\";\n\n/**\n * Opportunity Graph State (Linear Multi-Step Workflow)\n * \n * Flow: Prep → Scope → Discovery → Evaluation → Ranking → Persist → END\n * \n * Following the intent graph pattern with Annotation-based state management.\n */\n\n/** Asker's profile shape (identity + context). Used by sourceProfile annotation. */\nexport interface SourceProfileData {\n identity?: { name?: string; bio?: string; location?: string };\n context?: string;\n}\n\n/**\n * Indexed intent with hyde document (from prep node)\n */\nexport interface IndexedIntent {\n intentId: Id<'intents'>;\n payload: string;\n summary?: string;\n hydeDocumentId?: string;\n hydeEmbedding?: number[];\n indexes: Id<'networks'>[];\n}\n\n/**\n * Target index for search (from scope node)\n */\nexport interface TargetNetwork {\n networkId: Id<'networks'>;\n title: string;\n memberCount: number;\n}\n\n/**\n * Candidate match from discovery (semantic search).\n */\nexport interface CandidateMatch {\n candidateUserId: Id<'users'>;\n candidateIntentId?: Id<'intents'>;\n /** Source premise that produced this candidate (set when discoverySource is 'premise-similarity'). */\n sourcePremiseId?: Id<'premises'>;\n /** Candidate premise that matched this candidate (set for premise-based matches). */\n candidatePremiseId?: Id<'premises'>;\n /** Source context that produced this candidate (set when discoverySource is 'context-to-intent'). */\n sourceContextId?: string;\n networkId: Id<'networks'>;\n similarity: number;\n /** Free-text lens label that produced this match. */\n lens: string;\n candidatePayload: string;\n candidateSummary?: string;\n /** How this candidate was found: 'query' (HyDE from search text), 'premise-similarity', or 'context-to-intent'. */\n discoverySource?: 'query' | 'premise-similarity' | 'context-to-intent';\n /** Which discovery strategies found this candidate (set by mergeStrategyCandidates). */\n matchedStrategies?: string[];\n /** Typed evidence that explains why this candidate entered evaluation. */\n evidence?: OpportunityEvidence[];\n}\n\n/**\n * Evaluated candidate with LLM scoring (legacy; used when evaluator returns source/candidate pair).\n * candidateIntentId is set for intent matches; omitted for profile-only matches.\n */\nexport interface EvaluatedCandidate {\n sourceUserId: Id<'users'>;\n candidateUserId: Id<'users'>;\n sourceIntentId?: Id<'intents'>;\n candidateIntentId?: Id<'intents'>;\n networkId: Id<'networks'>;\n score: number; // 0-100\n reasoning: string; // Third-party analytical explanation of the match (for LLM agents)\n valencyRole: 'Agent' | 'Patient' | 'Peer';\n /** Free-text lens label that produced this match. */\n lens: string;\n}\n\n/**\n * Actor in an evaluated opportunity (from entity-bundle evaluator).\n * networkId is filled from the entity bundle in the graph, not by the evaluator.\n */\nexport interface EvaluatedOpportunityActor {\n userId: Id<'users'>;\n role: 'agent' | 'patient' | 'peer';\n intentId?: Id<'intents'>;\n networkId: Id<'networks'>;\n}\n\n/**\n * Evaluated opportunity with multi-actor output (entity-bundle evaluator).\n */\nexport interface EvaluatedOpportunity {\n actors: EvaluatedOpportunityActor[];\n score: number;\n reasoning: string;\n evidence?: OpportunityEvidence[];\n}\n\n/**\n * Which flow triggered this graph invocation. Determines initial persist status,\n * park-window timeout, streaming behavior, and whether AbortSignal is honored.\n *\n * - 'ambient' (default): queue-driven. Persists at the trigger default of\n * `pending` unless `options.initialStatus` overrides (the queue worker\n * passes `'latent'`, chat-bound ambient discovery passes `'draft'`). 5-min\n * park window, no streaming, ignores abort.\n * - 'orchestrator': chat-driven. Persists at the trigger default of\n * `negotiating` unless `options.initialStatus` overrides. 60s park window,\n * streams `opportunity_draft_ready` events, honors abort.\n *\n * See {@link resolveInitialStatus} for the exact fallback used when\n * `options.initialStatus` is undefined.\n */\nexport type OpportunityTrigger = 'ambient' | 'orchestrator';\n\n/**\n * Resolves the initial status for opportunities created in the persist node.\n *\n * Explicit `options.initialStatus` always wins (callers like the chat tool or\n * maintenance scripts override per-call). When the caller leaves it\n * undefined, the trigger drives the default:\n * - 'orchestrator' → 'negotiating' (chat-driven; negotiations run before the\n * user sees a draft card).\n * - 'ambient' (or any other trigger) → 'pending' (long-standing default for\n * queue- and intent-driven discovery).\n *\n * Lives here rather than in opportunity.graph.ts so unit tests can exercise\n * it without pulling in the full graph (and the evaluator's LLM requirements).\n *\n * @param trigger - The graph invocation's trigger\n * @param explicit - Caller-supplied initial status from options.initialStatus\n */\nexport function resolveInitialStatus(\n trigger: OpportunityTrigger,\n explicit: OpportunityStatus | undefined,\n): OpportunityStatus {\n if (explicit !== undefined) return explicit;\n return trigger === 'orchestrator' ? 'negotiating' : 'pending';\n}\n\n/**\n * Options passed to the graph\n */\nexport interface OpportunityGraphOptions {\n /** Initial status for created opportunities (default: 'pending') */\n initialStatus?: OpportunityStatus;\n /** Minimum score threshold (default: 50) */\n minScore?: number;\n /** Maximum opportunities to return (default: 20) */\n limit?: number;\n /** Pre-inferred lenses (if not provided, lens inference runs automatically in HyDE graph) */\n lenses?: Lens[];\n /** User's search query for HyDE generation */\n hydeDescription?: string;\n /** Existing opportunities summary for evaluator deduplication */\n existingOpportunities?: string;\n /** Chat session ID for draft opportunities; stored as context.conversationId for visibility filtering. */\n conversationId?: string;\n /**\n * MCP-only: cap the negotiate-phase wall-clock at this many milliseconds.\n * When set, `negotiateNode` races `negotiateCandidates(...)` against a timer;\n * if the timer wins, the node returns early with a `timed_out` trace and the\n * unawaited negotiation chains finalize each opp's DB status in the\n * background. Set to 20_000 by the MCP `discover_opportunities` handler.\n * Chat, ambient queue, and all other callers omit this — existing behavior.\n */\n negotiateTimeoutMs?: number;\n}\n\n/**\n * Opportunity Graph State Annotation\n */\nexport const OpportunityGraphState = Annotation.Root({\n // ─── Input Fields (Required) ───\n userId: Annotation<Id<'users'>>({\n reducer: (curr, next) => next ?? curr,\n default: () => '' as Id<'users'>,\n }),\n \n searchQuery: Annotation<string | undefined>({\n reducer: (curr, next) => next ?? curr,\n default: () => undefined,\n }),\n \n networkId: Annotation<Id<'networks'> | undefined>({\n reducer: (curr, next) => next ?? curr,\n default: () => undefined,\n }),\n\n /**\n * Optional set of indexes discovery may search within (e.g. a network-scoped\n * agent's reachable indexes: the bound network plus the user's personal index).\n * The scope node intersects this with the user's actual memberships. Ignored\n * when `networkId` is set (single-network override). When unset, discovery\n * spans all of the user's networks.\n */\n indexScope: Annotation<Id<'networks'>[] | undefined>({\n reducer: (curr, next) => next ?? curr,\n default: () => undefined,\n }),\n\n /** Optional intent to use as discovery source and for triggeredBy. When set, used for search text (if query empty) and persist. */\n triggerIntentId: Annotation<Id<'intents'> | undefined>({\n reducer: (curr, next) => next ?? curr,\n default: () => undefined,\n }),\n\n /** Optional: restrict discovery to this specific user ID only (direct connection). */\n targetUserId: Annotation<Id<'users'> | undefined>({\n reducer: (curr, next) => next ?? curr,\n default: () => undefined,\n }),\n\n /** Optional: discover on behalf of this user (introducer flow). When set, prep/eval use this user's profile/intents; userId becomes the introducer. */\n onBehalfOfUserId: Annotation<Id<'users'> | undefined>({\n reducer: (curr, next) => next ?? curr,\n default: () => undefined,\n }),\n\n options: Annotation<OpportunityGraphOptions>({\n reducer: (curr, next) => next ?? curr,\n default: () => ({}),\n }),\n\n /**\n * Which flow triggered this graph invocation. See {@link OpportunityTrigger}\n * for the exact branch behavior and {@link resolveInitialStatus} for the\n * persist default when `options.initialStatus` is unset.\n *\n * - 'ambient' (default): queue-driven, persist default `pending`, 5-min\n * park window, no streaming, ignores abort.\n * - 'orchestrator': chat-driven, persist default `negotiating`, 60s park\n * window, streams `opportunity_draft_ready` events, honors abort.\n */\n trigger: Annotation<OpportunityTrigger>({\n reducer: (curr, next) => next ?? curr,\n default: () => 'ambient',\n }),\n\n /**\n * Accepted opportunities the persist node discovered between the discoverer\n * and a candidate actor (same pair, status='accepted'). The orchestrator\n * branch populates this so the discover_opportunities tool (Task 7) can tell\n * the LLM \"these pairs are already connected, surface the existing chat\n * rather than creating a new draft\". Always empty for the ambient trigger.\n *\n * Left intentionally minimal — conversationId/URL resolution happens at\n * Start Chat time (Task 8), not here.\n */\n dedupAlreadyAccepted: Annotation<Array<{\n opportunityId: string;\n counterpartyUserId: string;\n }>>({\n reducer: (curr, next) => next ?? curr,\n default: () => [],\n }),\n\n /**\n * Operation mode controls graph flow:\n * - 'create': Existing discover pipeline (Prep → Scope → Discovery → Evaluation → Ranking → Persist)\n * - 'create_introduction': Introduction path (validation → evaluation → persist) for chat-driven intros\n * - 'continue_discovery': Pagination path (Prep → Evaluation → Ranking → Persist) using pre-loaded candidates\n * - 'read': List opportunities filtered by userId and optionally networkId (fast path)\n * - 'update': Change opportunity status (accept, reject, etc.)\n * - 'delete': Expire/archive an opportunity\n * - 'send': Promote latent opportunity to pending + queue notification\n * - 'negotiate_existing': Load an existing opportunity by opportunityId and run bilateral negotiation.\n * Used after introducer approval to trigger the normal negotiation flow.\n * - 'approve_introduction': Mark the caller as having approved a latent introducer opportunity,\n * then enqueue a negotiate_existing job for that opportunity.\n *\n * Defaults to 'create' for backward compatibility.\n */\n operationMode: Annotation<'create' | 'create_introduction' | 'continue_discovery' | 'read' | 'update' | 'delete' | 'send' | 'negotiate_existing' | 'approve_introduction'>({\n reducer: (curr, next) => next ?? curr,\n default: () => 'create' as const,\n }),\n\n /** Introduction mode: pre-gathered entities (profiles + intents per party). */\n introductionEntities: Annotation<EvaluatorEntity[]>({\n reducer: (curr, next) => next ?? curr,\n default: () => [],\n }),\n\n /** Introduction mode: optional hint from the introducer. */\n introductionHint: Annotation<string | undefined>({\n reducer: (curr, next) => next ?? curr,\n default: () => undefined,\n }),\n\n /** When set (e.g. chat scope), networkId must match this. */\n requiredNetworkId: Annotation<Id<'networks'> | undefined>({\n reducer: (curr, next) => next ?? curr,\n default: () => undefined,\n }),\n\n /** Set by intro_evaluation; used by persist to build manual detection and introducer actor. */\n introductionContext: Annotation<{ createdByName?: string } | undefined>({\n reducer: (curr, next) => next ?? curr,\n default: () => undefined,\n }),\n\n /** Target opportunity ID for update/delete/send modes. */\n opportunityId: Annotation<string | undefined>({\n reducer: (curr, next) => next ?? curr,\n default: () => undefined,\n }),\n\n /** New status for update mode (e.g. 'accepted', 'rejected'). */\n newStatus: Annotation<string | undefined>({\n reducer: (curr, next) => next ?? curr,\n default: () => undefined,\n }),\n \n // ─── Intermediate Fields (Accumulated) ───\n \n /** User's indexed intents with hyde documents (from prep) */\n indexedIntents: Annotation<IndexedIntent[]>({\n reducer: (curr, next) => next ?? curr,\n default: () => [],\n }),\n \n /** User's network memberships (from prep) */\n userNetworks: Annotation<Id<'networks'>[]>({\n reducer: (curr, next) => next ?? curr,\n default: () => [],\n }),\n \n /** Target indexes to search within (from scope) */\n targetNetworks: Annotation<TargetNetwork[]>({\n reducer: (curr, next) => next ?? curr,\n default: () => [],\n }),\n\n /** Per-index relevancy scores for dedup tie-breaking. Background path: from intent_indexes. Chat path: transient from IntentIndexer. */\n indexRelevancyScores: Annotation<Record<string, number>>({\n reducer: (curr, next) => next ?? curr,\n default: () => ({}),\n }),\n\n /** Whether discovery used intent (path A) or user context (path B/C). Used by persist for triggeredBy. In-memory routing state only; never persisted. */\n discoverySource: Annotation<'intent' | 'context'>({\n reducer: (curr, next) => next ?? curr,\n default: () => 'intent',\n }),\n\n /** Resolved intent ID used for this discovery run (when discoverySource is 'intent'). Set by intent-resolution. */\n resolvedTriggerIntentId: Annotation<Id<'intents'> | undefined>({\n reducer: (curr, next) => next ?? curr,\n default: () => undefined,\n }),\n\n /** Asker's profile (from prep). Used for profile-as-source discovery and evaluation. */\n sourceProfile: Annotation<SourceProfileData | null>({\n reducer: (curr, next) => next ?? curr,\n default: () => null,\n }),\n\n /** User's active premises with embeddings (from prep). Used for premise-to-premise discovery path D. */\n sourcePremises: Annotation<Array<{ premiseId: Id<'premises'>; embedding: number[] }>>({\n reducer: (curr, next) => next ?? curr,\n default: () => [],\n }),\n\n /** User context embeddings per network (from prep). Used for context-to-intent discovery. */\n sourceContexts: Annotation<Array<{ contextId: string; networkId: Id<'networks'>; embedding: number[] }>>({\n reducer: (curr, next) => next ?? curr,\n default: () => [],\n }),\n\n /** Resolved intent is in at least one target index (path A vs C). */\n resolvedIntentInIndex: Annotation<boolean>({\n reducer: (curr, next) => next ?? curr,\n default: () => false,\n }),\n\n /** Create-intent signal: when true, tool should return createIntentSuggested so agent can auto-call create_intent. */\n createIntentSuggested: Annotation<boolean>({\n reducer: (curr, next) => next ?? curr,\n default: () => false,\n }),\n\n /** Suggested description for create_intent when createIntentSuggested is true. */\n suggestedIntentDescription: Annotation<string | undefined>({\n reducer: (curr, next) => next ?? curr,\n default: () => undefined,\n }),\n\n /** HyDE embeddings per lens label (from discovery) */\n hydeEmbeddings: Annotation<Record<string, number[]>>({\n reducer: (curr, next) => next ?? curr,\n default: () => ({}),\n }),\n \n /** Candidate matches from semantic search (from discovery) */\n candidates: Annotation<CandidateMatch[]>({\n reducer: (curr, next) => next ?? curr,\n default: () => [],\n }),\n\n /** Candidates not yet evaluated (for pagination -- cached in Redis by caller). */\n remainingCandidates: Annotation<CandidateMatch[]>({\n reducer: (curr, next) => next ?? curr,\n default: () => [],\n }),\n\n /** Discovery session ID for pagination (maps to Redis cache key). */\n discoveryId: Annotation<string | null>({\n reducer: (curr, next) => next ?? curr,\n default: () => null,\n }),\n\n /** Evaluated candidates with scores (from evaluation; legacy) */\n evaluatedCandidates: Annotation<EvaluatedCandidate[]>({\n reducer: (curr, next) => next ?? curr,\n default: () => [],\n }),\n\n /** Evaluated opportunities with actors (from entity-bundle evaluator) */\n evaluatedOpportunities: Annotation<EvaluatedOpportunity[]>({\n reducer: (curr, next) => next ?? curr,\n default: () => [],\n }),\n \n // ─── Output Fields (Overwrite per turn) ───\n \n /** Final ranked and persisted opportunities */\n opportunities: Annotation<Opportunity[]>({\n reducer: (curr, next) => next,\n default: () => [],\n }),\n\n /** Discovery path: pairs skipped because an opportunity already exists between viewer and candidate (no duplicate created). */\n existingBetweenActors: Annotation<Array<{\n candidateUserId: Id<'users'>;\n networkId: Id<'networks'>;\n existingOpportunityId?: Id<'opportunities'>;\n existingStatus?: OpportunityStatus;\n }>>({\n reducer: (curr, next) => next ?? curr,\n default: () => [],\n }),\n \n /** Error message if any step fails */\n error: Annotation<string | undefined>({\n reducer: (curr, next) => next,\n default: () => undefined,\n }),\n\n /** Output for read mode: enriched list of opportunities. */\n readResult: Annotation<{\n count: number;\n message?: string;\n opportunities: Array<{\n id: string;\n indexName: string;\n connectedWith: string[];\n suggestedBy: string | null;\n reasoning: string;\n status: string;\n category: string;\n confidence: number | null;\n source: string | null;\n }>;\n } | undefined>({\n reducer: (curr, next) => next,\n default: () => undefined,\n }),\n\n /** Output for update/delete/send modes. */\n mutationResult: Annotation<{\n success: boolean;\n message?: string;\n opportunityId?: string;\n notified?: string[];\n conversationId?: string;\n error?: string;\n } | undefined>({\n reducer: (curr, next) => next,\n default: () => undefined,\n }),\n\n // ─── Trace Output ───\n\n /**\n * Accumulated trace entries from each graph node.\n * Used for observability: surfaces internal processing steps (search query, HyDE strategies,\n * candidates found, evaluation results) to the frontend.\n */\n trace: Annotation<Array<{ node: string; detail?: string; data?: Record<string, unknown> }>>({\n reducer: (curr, next) => [...curr, ...(next || [])],\n default: () => [],\n }),\n\n /** Timing records for each agent invocation within this graph run. */\n agentTimings: Annotation<DebugMetaAgent[]>({\n reducer: (acc, val) => [...acc, ...val],\n default: () => [],\n }),\n\n /**\n * Per-candidate negotiation records captured by `negotiateNode`. Populated\n * regardless of accept/reject so the question generator sees a complete\n * picture. Populated for ALL triggers (ambient + orchestrator) since the\n * negotiate node's `onCandidateResolved` hook is unconditional; only the\n * orchestrator streaming side-effects (opportunity_draft_ready emission)\n * are trigger-gated. Empty when the negotiate node was skipped (no\n * opportunities to negotiate).\n */\n discoveryNegotiations: Annotation<DiscoveryNegotiation[]>({\n reducer: (curr, next) => [...curr, ...(next || [])],\n default: () => [],\n }),\n\n /** Aggregate counters across `discoveryNegotiations`. Built in the negotiate node. */\n discoverySummary: Annotation<DiscoverySummary | null>({\n reducer: (_curr, next) => next ?? null,\n default: () => null,\n }),\n});\n"]}
|
|
1
|
+
{"version":3,"file":"opportunity.state.js","sourceRoot":"/","sources":["opportunity/opportunity.state.ts"],"names":[],"mappings":"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","sourcesContent":["import { Annotation } from \"@langchain/langgraph\";\nimport type { Id } from '../shared/interfaces/database.interface.js';\nimport type { OpportunityStatus, Opportunity } from '../shared/interfaces/database.interface.js';\nimport type { Lens } from '../shared/interfaces/embedder.interface.js';\nimport type { EvaluatorEntity } from './opportunity.evaluator.js';\nimport type { DebugMetaAgent } from '../chat/chat-streaming.types.js';\nimport type { OpportunityEvidence } from '../shared/schemas/network-assignment.schema.js';\nimport type { DiscoveryNegotiation, DiscoverySummary } from \"./question.prompt.js\";\n\n/**\n * Opportunity Graph State (Linear Multi-Step Workflow)\n *\n * Flow: Prep → Scope → Discovery → Evaluation → Ranking → Persist → END\n *\n * Following the intent graph pattern with Annotation-based state management.\n */\n\n/** Asker's profile shape (identity + context). Used by sourceProfile annotation. */\nexport interface SourceProfileData {\n identity?: { name?: string; bio?: string; location?: string };\n context?: string;\n}\n\n/**\n * Indexed intent with hyde document (from prep node)\n */\nexport interface IndexedIntent {\n intentId: Id<'intents'>;\n payload: string;\n summary?: string;\n hydeDocumentId?: string;\n hydeEmbedding?: number[];\n indexes: Id<'networks'>[];\n}\n\n/**\n * Target index for search (from scope node)\n */\nexport interface TargetNetwork {\n networkId: Id<'networks'>;\n title: string;\n memberCount: number;\n}\n\n/**\n * Candidate match from discovery (semantic search).\n */\nexport interface CandidateMatch {\n candidateUserId: Id<'users'>;\n candidateIntentId?: Id<'intents'>;\n /** Source premise that produced this candidate (set when discoverySource is 'premise-similarity'). */\n sourcePremiseId?: Id<'premises'>;\n /** Candidate premise that matched this candidate (set for premise-based matches). */\n candidatePremiseId?: Id<'premises'>;\n /** Source context that produced this candidate (set when discoverySource is 'context-to-intent'). */\n sourceContextId?: string;\n networkId: Id<'networks'>;\n similarity: number;\n /** Free-text lens label that produced this match. */\n lens: string;\n candidatePayload: string;\n candidateSummary?: string;\n /** How this candidate was found: 'query' (HyDE from search text), 'premise-similarity', or 'context-to-intent'. */\n discoverySource?: 'query' | 'premise-similarity' | 'context-to-intent';\n /** Which discovery strategies found this candidate (set by mergeStrategyCandidates). */\n matchedStrategies?: string[];\n /** Typed evidence that explains why this candidate entered evaluation. */\n evidence?: OpportunityEvidence[];\n}\n\n/**\n * Evaluated candidate with LLM scoring (legacy; used when evaluator returns source/candidate pair).\n * candidateIntentId is set for intent matches; omitted for profile-only matches.\n */\nexport interface EvaluatedCandidate {\n sourceUserId: Id<'users'>;\n candidateUserId: Id<'users'>;\n sourceIntentId?: Id<'intents'>;\n candidateIntentId?: Id<'intents'>;\n networkId: Id<'networks'>;\n score: number; // 0-100\n reasoning: string; // Third-party analytical explanation of the match (for LLM agents)\n valencyRole: 'Agent' | 'Patient' | 'Peer';\n /** Free-text lens label that produced this match. */\n lens: string;\n}\n\n/**\n * Actor in an evaluated opportunity (from entity-bundle evaluator).\n * networkId is filled from the entity bundle in the graph, not by the evaluator.\n */\nexport interface EvaluatedOpportunityActor {\n userId: Id<'users'>;\n role: 'agent' | 'patient' | 'peer';\n intentId?: Id<'intents'>;\n networkId: Id<'networks'>;\n}\n\n/**\n * Evaluated opportunity with multi-actor output (entity-bundle evaluator).\n */\nexport interface EvaluatedOpportunity {\n actors: EvaluatedOpportunityActor[];\n score: number;\n reasoning: string;\n evidence?: OpportunityEvidence[];\n}\n\n/**\n * Which flow triggered this graph invocation. Determines initial persist status,\n * park-window timeout, streaming behavior, and whether AbortSignal is honored.\n *\n * - 'ambient' (default): queue-driven. Persists at the trigger default of\n * `pending` unless `options.initialStatus` overrides (the queue worker\n * passes `'latent'`, chat-bound ambient discovery passes `'draft'`). 5-min\n * park window, no streaming, ignores abort.\n * - 'orchestrator': chat-driven. Persists at the trigger default of\n * `negotiating` unless `options.initialStatus` overrides. 60s park window,\n * streams `opportunity_draft_ready` events, honors abort.\n *\n * See {@link resolveInitialStatus} for the exact fallback used when\n * `options.initialStatus` is undefined.\n */\nexport type OpportunityTrigger = 'ambient' | 'orchestrator';\n\n/**\n * Resolves the initial status for opportunities created in the persist node.\n *\n * Explicit `options.initialStatus` always wins (callers like the chat tool or\n * maintenance scripts override per-call). When the caller leaves it\n * undefined, the trigger drives the default:\n * - 'orchestrator' → 'negotiating' (chat-driven; negotiations run before the\n * user sees a draft card).\n * - 'ambient' (or any other trigger) → 'pending' (long-standing default for\n * queue- and intent-driven discovery).\n *\n * Lives here rather than in opportunity.graph.ts so unit tests can exercise\n * it without pulling in the full graph (and the evaluator's LLM requirements).\n *\n * @param trigger - The graph invocation's trigger\n * @param explicit - Caller-supplied initial status from options.initialStatus\n */\nexport function resolveInitialStatus(\n trigger: OpportunityTrigger,\n explicit: OpportunityStatus | undefined,\n): OpportunityStatus {\n if (explicit !== undefined) return explicit;\n return trigger === 'orchestrator' ? 'negotiating' : 'pending';\n}\n\n/**\n * Options passed to the graph\n */\nexport interface OpportunityGraphOptions {\n /** Initial status for created opportunities (default: 'pending') */\n initialStatus?: OpportunityStatus;\n /** Minimum score threshold (default: 50) */\n minScore?: number;\n /** Maximum opportunities to return (default: 20) */\n limit?: number;\n /** Pre-inferred lenses (if not provided, lens inference runs automatically in HyDE graph) */\n lenses?: Lens[];\n /** User's search query for HyDE generation */\n hydeDescription?: string;\n /** Existing opportunities summary for evaluator deduplication */\n existingOpportunities?: string;\n /** Chat session ID for draft opportunities; stored as context.conversationId for visibility filtering. */\n conversationId?: string;\n /**\n * MCP-only: cap the negotiate-phase wall-clock at this many milliseconds.\n * When set, `negotiateNode` races `negotiateCandidates(...)` against a timer;\n * if the timer wins, the node returns early with a `timed_out` trace and the\n * unawaited negotiation chains finalize each opp's DB status in the\n * background. Set to 20_000 by the MCP `discover_opportunities` handler.\n * Chat, ambient queue, and all other callers omit this — existing behavior.\n */\n negotiateTimeoutMs?: number;\n}\n\n/**\n * Opportunity Graph State Annotation\n */\nexport const OpportunityGraphState = Annotation.Root({\n // ─── Input Fields (Required) ───\n userId: Annotation<Id<'users'>>({\n reducer: (curr, next) => next ?? curr,\n default: () => '' as Id<'users'>,\n }),\n\n searchQuery: Annotation<string | undefined>({\n reducer: (curr, next) => next ?? curr,\n default: () => undefined,\n }),\n\n networkId: Annotation<Id<'networks'> | undefined>({\n reducer: (curr, next) => next ?? curr,\n default: () => undefined,\n }),\n\n /**\n * Optional set of indexes discovery may search within (e.g. a network-scoped\n * agent's reachable networks: the bound network plus the user's personal network).\n * The scope node intersects this with the user's actual memberships. Ignored\n * when `networkId` is set (single-network override). When unset, discovery\n * spans all of the user's networks.\n */\n indexScope: Annotation<Id<'networks'>[] | undefined>({\n reducer: (curr, next) => next ?? curr,\n default: () => undefined,\n }),\n\n /** Optional intent to use as discovery source and for triggeredBy. When set, used for search text (if query empty) and persist. */\n triggerIntentId: Annotation<Id<'intents'> | undefined>({\n reducer: (curr, next) => next ?? curr,\n default: () => undefined,\n }),\n\n /** Optional: restrict discovery to this specific user ID only (direct connection). */\n targetUserId: Annotation<Id<'users'> | undefined>({\n reducer: (curr, next) => next ?? curr,\n default: () => undefined,\n }),\n\n /** Optional: discover on behalf of this user (introducer flow). When set, prep/eval use this user's profile/intents; userId becomes the introducer. */\n onBehalfOfUserId: Annotation<Id<'users'> | undefined>({\n reducer: (curr, next) => next ?? curr,\n default: () => undefined,\n }),\n\n options: Annotation<OpportunityGraphOptions>({\n reducer: (curr, next) => next ?? curr,\n default: () => ({}),\n }),\n\n /**\n * Which flow triggered this graph invocation. See {@link OpportunityTrigger}\n * for the exact branch behavior and {@link resolveInitialStatus} for the\n * persist default when `options.initialStatus` is unset.\n *\n * - 'ambient' (default): queue-driven, persist default `pending`, 5-min\n * park window, no streaming, ignores abort.\n * - 'orchestrator': chat-driven, persist default `negotiating`, 60s park\n * window, streams `opportunity_draft_ready` events, honors abort.\n */\n trigger: Annotation<OpportunityTrigger>({\n reducer: (curr, next) => next ?? curr,\n default: () => 'ambient',\n }),\n\n /**\n * Accepted opportunities the persist node discovered between the discoverer\n * and a candidate actor (same pair, status='accepted'). The orchestrator\n * branch populates this so the discover_opportunities tool (Task 7) can tell\n * the LLM \"these pairs are already connected, surface the existing chat\n * rather than creating a new draft\". Always empty for the ambient trigger.\n *\n * Left intentionally minimal — conversationId/URL resolution happens at\n * Start Chat time (Task 8), not here.\n */\n dedupAlreadyAccepted: Annotation<Array<{\n opportunityId: string;\n counterpartyUserId: string;\n }>>({\n reducer: (curr, next) => next ?? curr,\n default: () => [],\n }),\n\n /**\n * Operation mode controls graph flow:\n * - 'create': Existing discover pipeline (Prep → Scope → Discovery → Evaluation → Ranking → Persist)\n * - 'create_introduction': Introduction path (validation → evaluation → persist) for chat-driven intros\n * - 'continue_discovery': Pagination path (Prep → Evaluation → Ranking → Persist) using pre-loaded candidates\n * - 'read': List opportunities filtered by userId and optionally networkId (fast path)\n * - 'update': Change opportunity status (accept, reject, etc.)\n * - 'delete': Expire/archive an opportunity\n * - 'send': Promote latent opportunity to pending + queue notification\n * - 'negotiate_existing': Load an existing opportunity by opportunityId and run bilateral negotiation.\n * Used after introducer approval to trigger the normal negotiation flow.\n * - 'approve_introduction': Mark the caller as having approved a latent introducer opportunity,\n * then enqueue a negotiate_existing job for that opportunity.\n *\n * Defaults to 'create' for backward compatibility.\n */\n operationMode: Annotation<'create' | 'create_introduction' | 'continue_discovery' | 'read' | 'update' | 'delete' | 'send' | 'negotiate_existing' | 'approve_introduction'>({\n reducer: (curr, next) => next ?? curr,\n default: () => 'create' as const,\n }),\n\n /** Introduction mode: pre-gathered entities (profiles + intents per party). */\n introductionEntities: Annotation<EvaluatorEntity[]>({\n reducer: (curr, next) => next ?? curr,\n default: () => [],\n }),\n\n /** Introduction mode: optional hint from the introducer. */\n introductionHint: Annotation<string | undefined>({\n reducer: (curr, next) => next ?? curr,\n default: () => undefined,\n }),\n\n /** When set (e.g. chat scope), networkId must match this. */\n requiredNetworkId: Annotation<Id<'networks'> | undefined>({\n reducer: (curr, next) => next ?? curr,\n default: () => undefined,\n }),\n\n /** Set by intro_evaluation; used by persist to build manual detection and introducer actor. */\n introductionContext: Annotation<{ createdByName?: string } | undefined>({\n reducer: (curr, next) => next ?? curr,\n default: () => undefined,\n }),\n\n /** Target opportunity ID for update/delete/send modes. */\n opportunityId: Annotation<string | undefined>({\n reducer: (curr, next) => next ?? curr,\n default: () => undefined,\n }),\n\n /** New status for update mode (e.g. 'accepted', 'rejected'). */\n newStatus: Annotation<string | undefined>({\n reducer: (curr, next) => next ?? curr,\n default: () => undefined,\n }),\n\n // ─── Intermediate Fields (Accumulated) ───\n\n /** User's indexed intents with hyde documents (from prep) */\n indexedIntents: Annotation<IndexedIntent[]>({\n reducer: (curr, next) => next ?? curr,\n default: () => [],\n }),\n\n /** User's network memberships (from prep) */\n userNetworks: Annotation<Id<'networks'>[]>({\n reducer: (curr, next) => next ?? curr,\n default: () => [],\n }),\n\n /** Target indexes to search within (from scope) */\n targetNetworks: Annotation<TargetNetwork[]>({\n reducer: (curr, next) => next ?? curr,\n default: () => [],\n }),\n\n /** Per-index relevancy scores for dedup tie-breaking. Background path: from intent_indexes. Chat path: transient from IntentIndexer. */\n indexRelevancyScores: Annotation<Record<string, number>>({\n reducer: (curr, next) => next ?? curr,\n default: () => ({}),\n }),\n\n /** Whether discovery used intent (path A) or user context (path B/C). Used by persist for triggeredBy. In-memory routing state only; never persisted. */\n discoverySource: Annotation<'intent' | 'context'>({\n reducer: (curr, next) => next ?? curr,\n default: () => 'intent',\n }),\n\n /** Resolved intent ID used for this discovery run (when discoverySource is 'intent'). Set by intent-resolution. */\n resolvedTriggerIntentId: Annotation<Id<'intents'> | undefined>({\n reducer: (curr, next) => next ?? curr,\n default: () => undefined,\n }),\n\n /** Asker's profile (from prep). Used for profile-as-source discovery and evaluation. */\n sourceProfile: Annotation<SourceProfileData | null>({\n reducer: (curr, next) => next ?? curr,\n default: () => null,\n }),\n\n /** User's active premises with embeddings (from prep). Used for premise-to-premise discovery path D. */\n sourcePremises: Annotation<Array<{ premiseId: Id<'premises'>; embedding: number[] }>>({\n reducer: (curr, next) => next ?? curr,\n default: () => [],\n }),\n\n /** User context embeddings per network (from prep). Used for context-to-intent discovery. */\n sourceContexts: Annotation<Array<{ contextId: string; networkId: Id<'networks'>; embedding: number[] }>>({\n reducer: (curr, next) => next ?? curr,\n default: () => [],\n }),\n\n /** Resolved intent is in at least one target index (path A vs C). */\n resolvedIntentInIndex: Annotation<boolean>({\n reducer: (curr, next) => next ?? curr,\n default: () => false,\n }),\n\n /** Create-intent signal: when true, tool should return createIntentSuggested so agent can auto-call create_intent. */\n createIntentSuggested: Annotation<boolean>({\n reducer: (curr, next) => next ?? curr,\n default: () => false,\n }),\n\n /** Suggested description for create_intent when createIntentSuggested is true. */\n suggestedIntentDescription: Annotation<string | undefined>({\n reducer: (curr, next) => next ?? curr,\n default: () => undefined,\n }),\n\n /** HyDE embeddings per lens label (from discovery) */\n hydeEmbeddings: Annotation<Record<string, number[]>>({\n reducer: (curr, next) => next ?? curr,\n default: () => ({}),\n }),\n\n /** Candidate matches from semantic search (from discovery) */\n candidates: Annotation<CandidateMatch[]>({\n reducer: (curr, next) => next ?? curr,\n default: () => [],\n }),\n\n /** Candidates not yet evaluated (for pagination -- cached in Redis by caller). */\n remainingCandidates: Annotation<CandidateMatch[]>({\n reducer: (curr, next) => next ?? curr,\n default: () => [],\n }),\n\n /** Discovery session ID for pagination (maps to Redis cache key). */\n discoveryId: Annotation<string | null>({\n reducer: (curr, next) => next ?? curr,\n default: () => null,\n }),\n\n /** Evaluated candidates with scores (from evaluation; legacy) */\n evaluatedCandidates: Annotation<EvaluatedCandidate[]>({\n reducer: (curr, next) => next ?? curr,\n default: () => [],\n }),\n\n /** Evaluated opportunities with actors (from entity-bundle evaluator) */\n evaluatedOpportunities: Annotation<EvaluatedOpportunity[]>({\n reducer: (curr, next) => next ?? curr,\n default: () => [],\n }),\n\n // ─── Output Fields (Overwrite per turn) ───\n\n /** Final ranked and persisted opportunities */\n opportunities: Annotation<Opportunity[]>({\n reducer: (curr, next) => next,\n default: () => [],\n }),\n\n /** Discovery path: pairs skipped because an opportunity already exists between viewer and candidate (no duplicate created). */\n existingBetweenActors: Annotation<Array<{\n candidateUserId: Id<'users'>;\n networkId: Id<'networks'>;\n existingOpportunityId?: Id<'opportunities'>;\n existingStatus?: OpportunityStatus;\n }>>({\n reducer: (curr, next) => next ?? curr,\n default: () => [],\n }),\n\n /** Error message if any step fails */\n error: Annotation<string | undefined>({\n reducer: (curr, next) => next,\n default: () => undefined,\n }),\n\n /** Output for read mode: enriched list of opportunities. */\n readResult: Annotation<{\n count: number;\n message?: string;\n opportunities: Array<{\n id: string;\n indexName: string;\n connectedWith: string[];\n suggestedBy: string | null;\n reasoning: string;\n status: string;\n category: string;\n confidence: number | null;\n source: string | null;\n }>;\n } | undefined>({\n reducer: (curr, next) => next,\n default: () => undefined,\n }),\n\n /** Output for update/delete/send modes. */\n mutationResult: Annotation<{\n success: boolean;\n message?: string;\n opportunityId?: string;\n notified?: string[];\n conversationId?: string;\n error?: string;\n } | undefined>({\n reducer: (curr, next) => next,\n default: () => undefined,\n }),\n\n // ─── Trace Output ───\n\n /**\n * Accumulated trace entries from each graph node.\n * Used for observability: surfaces internal processing steps (search query, HyDE strategies,\n * candidates found, evaluation results) to the frontend.\n */\n trace: Annotation<Array<{ node: string; detail?: string; data?: Record<string, unknown> }>>({\n reducer: (curr, next) => [...curr, ...(next || [])],\n default: () => [],\n }),\n\n /** Timing records for each agent invocation within this graph run. */\n agentTimings: Annotation<DebugMetaAgent[]>({\n reducer: (acc, val) => [...acc, ...val],\n default: () => [],\n }),\n\n /**\n * Per-candidate negotiation records captured by `negotiateNode`. Populated\n * regardless of accept/reject so the question generator sees a complete\n * picture. Populated for ALL triggers (ambient + orchestrator) since the\n * negotiate node's `onCandidateResolved` hook is unconditional; only the\n * orchestrator streaming side-effects (opportunity_draft_ready emission)\n * are trigger-gated. Empty when the negotiate node was skipped (no\n * opportunities to negotiate).\n */\n discoveryNegotiations: Annotation<DiscoveryNegotiation[]>({\n reducer: (curr, next) => [...curr, ...(next || [])],\n default: () => [],\n }),\n\n /** Aggregate counters across `discoveryNegotiations`. Built in the negotiate node. */\n discoverySummary: Annotation<DiscoverySummary | null>({\n reducer: (_curr, next) => next ?? null,\n default: () => null,\n }),\n});\n"]}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"opportunity.tools.d.ts","sourceRoot":"/","sources":["opportunity/opportunity.tools.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,UAAU,EAAE,QAAQ,EAAE,MAAM,iCAAiC,CAAC;AA+B5E,OAAO,KAAK,EAAE,WAAW,EAAqB,MAAM,4CAA4C,CAAC;AAEjG,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,gDAAgD,CAAC;AAOtF;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AACH,wBAAgB,yBAAyB,CAAC,KAAK,EAAE;IAC/C,MAAM,EAAE,MAAM,CAAC;IACf,UAAU,EAAE,MAAM,CAAC;IACnB,cAAc,CAAC,EAAE,OAAO,CAAC;CAC1B,GAAG,eAAe,GAAG,IAAI,CAczB;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,eAAe,CAC7B,iBAAiB,EAAE,MAAM,EACzB,WAAW,EAAE,MAAM,GAAG,SAAS,GAC9B,MAAM,GAAG,SAAS,CAQpB;AAED;;;;;;;;;GASG;AACH,wBAAgB,mBAAmB,CACjC,cAAc,EAAE,MAAM,GAAG,SAAS,EAClC,WAAW,EAAE,MAAM,GAAG,SAAS,GAC9B,MAAM,GAAG,SAAS,CAIpB;AAED;;;;;;;GAOG;AACH,wBAAsB,qBAAqB,CACzC,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG;IAC9B,aAAa,EAAE,MAAM,CAAC;IACtB,UAAU,EAAE,MAAM,CAAC;IACnB,MAAM,EAAE,MAAM,CAAC;CAChB,EACD,IAAI,EAAE;IACJ,QAAQ,EAAE,MAAM,CAAC;IACjB,cAAc,CAAC,EAAE,OAAO,CAAC;IACzB,iBAAiB,EAAE,MAAM,CAAC;IAC1B,eAAe,EAAE,WAAW,CAAC,QAAQ,CAAC,iBAAiB,CAAC,CAAC,CAAC;IAC1D,WAAW,EAAE,MAAM,GAAG,SAAS,CAAC;IAChC,gBAAgB,CAAC,EAAE,UAAU,GAAG,KAAK,CAAC;CACvC,GACA,OAAO,CAAC,IAAI,CAAC,CA6Cf;
|
|
1
|
+
{"version":3,"file":"opportunity.tools.d.ts","sourceRoot":"/","sources":["opportunity/opportunity.tools.ts"],"names":[],"mappings":"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"}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
2
|
import { requestContext } from "../shared/observability/request-context.js";
|
|
3
3
|
import { success, error, UUID_REGEX } from "../shared/agent/tool.helpers.js";
|
|
4
|
-
import { deriveDiscoveryNetworkIds, focusedNetworkId, focusedNetworkLabel } from "../shared/agent/tool.scope.js";
|
|
4
|
+
import { deriveDiscoveryNetworkIds, focusedIntentId, focusedNetworkId, focusedNetworkLabel } from "../shared/agent/tool.scope.js";
|
|
5
5
|
import { MINIMAL_MAIN_TEXT_MAX_CHARS, getPrimaryActionLabel, SECONDARY_ACTION_LABEL } from "./opportunity.labels.js";
|
|
6
6
|
import { viewerCentricCardSummary, narratorRemarkFromReasoning, stripUuids } from "./opportunity.presentation.js";
|
|
7
7
|
import { runDiscoverFromQuery, continueDiscovery } from "./opportunity.discover.js";
|
|
@@ -177,6 +177,13 @@ const UPDATE_OPPORTUNITY_BLOCKED_STATUSES = new Set([
|
|
|
177
177
|
"expired",
|
|
178
178
|
"negotiating",
|
|
179
179
|
]);
|
|
180
|
+
function matchesSelectedIntentScope(opportunity, viewerId, scope) {
|
|
181
|
+
if (scope?.scopeType !== 'intent' || !scope.scopeId)
|
|
182
|
+
return true;
|
|
183
|
+
if (opportunity.detection?.triggeredBy === scope.scopeId)
|
|
184
|
+
return true;
|
|
185
|
+
return opportunity.actors.some((actor) => actor.userId === viewerId && actor.intent === scope.scopeId);
|
|
186
|
+
}
|
|
180
187
|
/**
|
|
181
188
|
* Maximum number of opportunity cards to show per chat response.
|
|
182
189
|
* Sized for `selectByComposition` to fill both feed buckets — up to 3
|
|
@@ -354,7 +361,7 @@ export function buildOpportunityPresentation(cards, opts) {
|
|
|
354
361
|
* Encoded as JSON of a normalized object — NOT a delimiter join — so a
|
|
355
362
|
* user-supplied string containing the delimiter can never make two distinct
|
|
356
363
|
* requests collide. `hint` and each entity's `networkId` are included because
|
|
357
|
-
* they change the discovery result (different reason / different shared
|
|
364
|
+
* they change the discovery result (different reason / different shared network),
|
|
358
365
|
* so requests that differ only in those must NOT coalesce. Natural-language
|
|
359
366
|
* fields (`searchQuery`, `hint`) are lowercased; identifiers and the opaque
|
|
360
367
|
* `continueFrom` pagination token are only trimmed (case-sensitive) so distinct
|
|
@@ -414,9 +421,9 @@ export function createOpportunityTools(defineTool, deps) {
|
|
|
414
421
|
"Opportunities are the core output of the discovery engine, representing potential valuable connections between people.\n\n" +
|
|
415
422
|
"**NOT for person lookup** — use read_user_contexts(query=name) to find people by name.\n\n" +
|
|
416
423
|
"**Four modes:**\n" +
|
|
417
|
-
"1. **Discovery** (most common): pass `searchQuery` and/or `networkId`. The system finds other users in shared
|
|
424
|
+
"1. **Discovery** (most common): pass `searchQuery` and/or `networkId`. The system finds other users in shared networkes " +
|
|
418
425
|
"whose intents semantically complement the query. Uses HyDE embeddings and LLM evaluation for scoring.\n" +
|
|
419
|
-
"2. **Introduction**: pass `partyUserIds` (2+ user IDs) + `entities` (pre-gathered profiles and intents from shared
|
|
426
|
+
"2. **Introduction**: pass `partyUserIds` (2+ user IDs) + `entities` (pre-gathered profiles and intents from shared networkes). " +
|
|
420
427
|
"You MUST call read_user_contexts and read_intents for each party BEFORE calling this. " +
|
|
421
428
|
"Optionally pass `hint` with the user's reason for the introduction.\n" +
|
|
422
429
|
"3. **Direct connection**: pass `targetUserId` + `searchQuery`. Creates an opportunity between the current user and one specific person.\n" +
|
|
@@ -434,13 +441,13 @@ export function createOpportunityTools(defineTool, deps) {
|
|
|
434
441
|
"Do NOT call create_intent for these phrasings — create_intent is only for when the user explicitly " +
|
|
435
442
|
"asks to \"create\", \"save\", \"add\", or \"remember\" a signal.\n\n" +
|
|
436
443
|
"**Personal-index scoping.** When the user says \"in my network\", \"from my contacts\", \"people I know\", " +
|
|
437
|
-
"or similar scoping language, pass the user's personal
|
|
438
|
-
"as `networkId`. The personal
|
|
444
|
+
"or similar scoping language, pass the user's personal network ID (from memberships where `isPersonal: true`) " +
|
|
445
|
+
"as `networkId`. The personal network contains the user's contacts — scoping discovery to it restricts " +
|
|
439
446
|
"results to people the user already knows. Without this scoping language, omit networkId to let discovery " +
|
|
440
|
-
"run across all
|
|
447
|
+
"run across all networks.\n\n" +
|
|
441
448
|
"**Introduction mode prerequisites.** When using `partyUserIds` + `entities`, YOU must pre-fetch each party's " +
|
|
442
449
|
"profile and intents before calling this tool. The entities array must include each party's userId, profile, " +
|
|
443
|
-
"intents from shared
|
|
450
|
+
"intents from shared networkes, and the shared networkId. Call read_user_contexts, read_network_memberships, " +
|
|
444
451
|
"and read_intents for both parties first. The introducer (current user) must NOT appear in entities.\n\n" +
|
|
445
452
|
"**Signal-visibility follow-up.** If the response includes `suggestIntentCreationForVisibility: true` and " +
|
|
446
453
|
"`suggestedIntentDescription`, after presenting opportunity cards ask the user ONCE whether they'd also like " +
|
|
@@ -459,7 +466,7 @@ export function createOpportunityTools(defineTool, deps) {
|
|
|
459
466
|
networkId: z
|
|
460
467
|
.string()
|
|
461
468
|
.optional()
|
|
462
|
-
.describe("
|
|
469
|
+
.describe("Network UUID to scope discovery to a specific community. Get from read_networks. In an network-scoped chat, omitting this runs discovery only in the scoped community; pass the personal network ID (from read_networks, isPersonal=true) only when the user explicitly asks to discover among contacts."),
|
|
463
470
|
intentId: z
|
|
464
471
|
.string()
|
|
465
472
|
.optional()
|
|
@@ -501,13 +508,13 @@ export function createOpportunityTools(defineTool, deps) {
|
|
|
501
508
|
.optional(),
|
|
502
509
|
networkId: z
|
|
503
510
|
.string()
|
|
504
|
-
.describe("Shared
|
|
511
|
+
.describe("Shared network this entity's data comes from (required for intro mode)"),
|
|
505
512
|
}))
|
|
506
513
|
.optional()
|
|
507
514
|
.describe("Introduction mode: pre-gathered profile and intent data for each party being introduced. " +
|
|
508
|
-
"Each entry needs userId, networkId (the shared
|
|
515
|
+
"Each entry needs userId, networkId (the shared network), and optionally profile (name, bio, skills, interests) and intents (intentId, payload). " +
|
|
509
516
|
"Gather this data by calling read_user_contexts and read_intents for each party BEFORE calling discover_opportunities. " +
|
|
510
|
-
"All entities must share the same networkId (the shared
|
|
517
|
+
"All entities must share the same networkId (the shared network where both parties are members)."),
|
|
511
518
|
hint: z
|
|
512
519
|
.string()
|
|
513
520
|
.optional()
|
|
@@ -515,9 +522,9 @@ export function createOpportunityTools(defineTool, deps) {
|
|
|
515
522
|
"'complementary skills for a startup'). Helps the evaluator produce better match reasoning."),
|
|
516
523
|
}),
|
|
517
524
|
handler: async ({ context, query }) => {
|
|
518
|
-
const scopedNetworkId = focusedNetworkId(context);
|
|
525
|
+
const scopedNetworkId = focusedNetworkId(context) ?? context.networkId?.trim();
|
|
519
526
|
const scopedIndexLabel = focusedNetworkLabel(context);
|
|
520
|
-
// Strict scope enforcement: when chat is
|
|
527
|
+
// Strict scope enforcement: when chat is network-scoped, only allow that index
|
|
521
528
|
if (scopedNetworkId &&
|
|
522
529
|
query.networkId?.trim() &&
|
|
523
530
|
query.networkId.trim() !== scopedNetworkId) {
|
|
@@ -865,7 +872,7 @@ export function createOpportunityTools(defineTool, deps) {
|
|
|
865
872
|
}
|
|
866
873
|
else if (context.scopeType === 'network' && context.scopeId) {
|
|
867
874
|
// Scoped chat: discovery is focused-network only. Self-owned writes may
|
|
868
|
-
// include personal
|
|
875
|
+
// include personal networkes, but opportunity visibility must not.
|
|
869
876
|
const scopedDiscoveryIds = deriveDiscoveryNetworkIds({
|
|
870
877
|
memberships: context.userNetworks,
|
|
871
878
|
scopeType: context.scopeType,
|
|
@@ -878,7 +885,7 @@ export function createOpportunityTools(defineTool, deps) {
|
|
|
878
885
|
indexScope = [scopedNetworkId];
|
|
879
886
|
}
|
|
880
887
|
else {
|
|
881
|
-
// No scope - use all
|
|
888
|
+
// No scope - use all networks (only in unscoped chat)
|
|
882
889
|
const _scopeGraphStart = Date.now();
|
|
883
890
|
const _scopeIndexTraceEmitter = requestContext.getStore()?.traceEmitter;
|
|
884
891
|
_scopeIndexTraceEmitter?.({ type: "graph_start", name: "index" });
|
|
@@ -1289,7 +1296,7 @@ export function createOpportunityTools(defineTool, deps) {
|
|
|
1289
1296
|
const listOpportunities = defineTool({
|
|
1290
1297
|
name: "list_opportunities",
|
|
1291
1298
|
description: "Lists the authenticated user's actionable opportunities (discovered connections). Returns opportunity cards ready for display.\n\n" +
|
|
1292
|
-
"**What are opportunities?** Matches between users whose intents complement each other within shared
|
|
1299
|
+
"**What are opportunities?** Matches between users whose intents complement each other within shared networkes. " +
|
|
1293
1300
|
"Each opportunity has a status: draft (not yet sent), pending (sent, awaiting response), accepted, rejected, or expired.\n\n" +
|
|
1294
1301
|
"**What this returns:** Only draft and pending opportunities — the ones the user can still act on. " +
|
|
1295
1302
|
"Accepted, rejected, and expired ones are not surfaced through this tool.\n\n" +
|
|
@@ -1300,16 +1307,24 @@ export function createOpportunityTools(defineTool, deps) {
|
|
|
1300
1307
|
networkId: z
|
|
1301
1308
|
.string()
|
|
1302
1309
|
.optional()
|
|
1303
|
-
.describe("
|
|
1310
|
+
.describe("Network UUID to filter opportunities to a specific community. Get from read_networks. Defaults to the scoped network in network-scoped chats. Omit to see opportunities across all networks."),
|
|
1311
|
+
scopeType: z
|
|
1312
|
+
.enum(['intent'])
|
|
1313
|
+
.optional()
|
|
1314
|
+
.describe("Optional selected scope type. Use 'intent' to narrow listed opportunities to a selected intent."),
|
|
1315
|
+
scopeId: z
|
|
1316
|
+
.string()
|
|
1317
|
+
.optional()
|
|
1318
|
+
.describe("Selected intent UUID when scopeType is 'intent'. Ignored only when absent."),
|
|
1304
1319
|
includeDigestMarkers: z
|
|
1305
1320
|
.boolean()
|
|
1306
1321
|
.optional()
|
|
1307
1322
|
.describe("Internal scheduled-digest mode only. When true, includes hidden delivery markers so the digest send pass can confirm only edited-in opportunities."),
|
|
1308
1323
|
}),
|
|
1309
1324
|
handler: async ({ context, query }) => {
|
|
1310
|
-
const scopedNetworkId = focusedNetworkId(context);
|
|
1325
|
+
const scopedNetworkId = focusedNetworkId(context) ?? context.networkId?.trim();
|
|
1311
1326
|
const scopedIndexLabel = focusedNetworkLabel(context);
|
|
1312
|
-
// Strict scope enforcement: when chat is
|
|
1327
|
+
// Strict scope enforcement: when chat is network-scoped, only allow that index
|
|
1313
1328
|
if (scopedNetworkId &&
|
|
1314
1329
|
query.networkId?.trim() &&
|
|
1315
1330
|
query.networkId.trim() !== scopedNetworkId) {
|
|
@@ -1319,6 +1334,25 @@ export function createOpportunityTools(defineTool, deps) {
|
|
|
1319
1334
|
if (effectiveIndexId && !UUID_REGEX.test(effectiveIndexId)) {
|
|
1320
1335
|
return error("Invalid network ID format.");
|
|
1321
1336
|
}
|
|
1337
|
+
const contextIntentId = focusedIntentId(context);
|
|
1338
|
+
const rawScopeId = query.scopeId?.trim() || undefined;
|
|
1339
|
+
if (query.scopeType === 'intent' && !rawScopeId) {
|
|
1340
|
+
return error("scopeId required when scopeType is intent.");
|
|
1341
|
+
}
|
|
1342
|
+
if (!query.scopeType && rawScopeId) {
|
|
1343
|
+
return error("scopeType=intent required when scopeId is provided.");
|
|
1344
|
+
}
|
|
1345
|
+
if (rawScopeId && !UUID_REGEX.test(rawScopeId)) {
|
|
1346
|
+
return error("Invalid scope ID format.");
|
|
1347
|
+
}
|
|
1348
|
+
if (contextIntentId && rawScopeId && contextIntentId !== rawScopeId) {
|
|
1349
|
+
return error("This chat is scoped to a different intent.");
|
|
1350
|
+
}
|
|
1351
|
+
const effectiveIntentScope = contextIntentId
|
|
1352
|
+
? { scopeType: 'intent', scopeId: contextIntentId }
|
|
1353
|
+
: query.scopeType === 'intent' && rawScopeId
|
|
1354
|
+
? { scopeType: 'intent', scopeId: rawScopeId }
|
|
1355
|
+
: {};
|
|
1322
1356
|
// The MCP/chat surface exposes actionable opportunities.
|
|
1323
1357
|
// `latent` is included so the introducer-as-viewer can see their unapproved
|
|
1324
1358
|
// connector-flow cards ("do you know someone for X?"). Other latent visibility
|
|
@@ -1331,6 +1365,7 @@ export function createOpportunityTools(defineTool, deps) {
|
|
|
1331
1365
|
// sort order can fill the whole window and starve the other section.
|
|
1332
1366
|
const fetched = await database.getOpportunitiesForUser(context.userId, {
|
|
1333
1367
|
networkId: effectiveIndexId,
|
|
1368
|
+
...effectiveIntentScope,
|
|
1334
1369
|
statuses,
|
|
1335
1370
|
limit: CHAT_FETCH_LIMIT,
|
|
1336
1371
|
});
|
|
@@ -1399,8 +1434,10 @@ export function createOpportunityTools(defineTool, deps) {
|
|
|
1399
1434
|
if (isDigestMode && deduped.length > 0) {
|
|
1400
1435
|
const acceptedCounterpartIds = new Set();
|
|
1401
1436
|
try {
|
|
1437
|
+
// effectiveIntentScope scopes the statuses: ["accepted"] suppression fetch.
|
|
1402
1438
|
const acceptedOpps = await database.getOpportunitiesForUser(context.userId, {
|
|
1403
1439
|
...(effectiveIndexId ? { networkId: effectiveIndexId } : {}),
|
|
1440
|
+
...effectiveIntentScope,
|
|
1404
1441
|
statuses: ["accepted"],
|
|
1405
1442
|
limit: ACCEPTED_SUPPRESSION_FETCH_LIMIT,
|
|
1406
1443
|
});
|
|
@@ -1789,12 +1826,39 @@ export function createOpportunityTools(defineTool, deps) {
|
|
|
1789
1826
|
.enum(["pending", "accepted", "rejected", "expired"])
|
|
1790
1827
|
.describe("New status: 'pending' = send the draft to the other party, 'accepted' = accept the connection, " +
|
|
1791
1828
|
"'rejected' = decline, 'expired' = mark as timed out."),
|
|
1829
|
+
scopeType: z
|
|
1830
|
+
.enum(['intent'])
|
|
1831
|
+
.optional()
|
|
1832
|
+
.describe("Optional selected scope type. Use 'intent' to require this opportunity to belong to a selected intent."),
|
|
1833
|
+
scopeId: z
|
|
1834
|
+
.string()
|
|
1835
|
+
.optional()
|
|
1836
|
+
.describe("Selected intent UUID when scopeType is 'intent'. Must match the chat's focused intent when one exists."),
|
|
1792
1837
|
}),
|
|
1793
1838
|
handler: async ({ context, query }) => {
|
|
1794
1839
|
const opportunityId = query.opportunityId?.trim();
|
|
1795
1840
|
if (!opportunityId || !UUID_REGEX.test(opportunityId)) {
|
|
1796
1841
|
return error("Valid opportunityId required.");
|
|
1797
1842
|
}
|
|
1843
|
+
const contextIntentId = focusedIntentId(context);
|
|
1844
|
+
const rawScopeId = query.scopeId?.trim() || undefined;
|
|
1845
|
+
if (query.scopeType === 'intent' && !rawScopeId) {
|
|
1846
|
+
return error("scopeId required when scopeType is intent.");
|
|
1847
|
+
}
|
|
1848
|
+
if (!query.scopeType && rawScopeId) {
|
|
1849
|
+
return error("scopeType=intent required when scopeId is provided.");
|
|
1850
|
+
}
|
|
1851
|
+
if (rawScopeId && !UUID_REGEX.test(rawScopeId)) {
|
|
1852
|
+
return error("Invalid scope ID format.");
|
|
1853
|
+
}
|
|
1854
|
+
if (contextIntentId && rawScopeId && contextIntentId !== rawScopeId) {
|
|
1855
|
+
return error("This chat is scoped to a different intent.");
|
|
1856
|
+
}
|
|
1857
|
+
const effectiveIntentScope = contextIntentId
|
|
1858
|
+
? { scopeType: 'intent', scopeId: contextIntentId }
|
|
1859
|
+
: query.scopeType === 'intent' && rawScopeId
|
|
1860
|
+
? { scopeType: 'intent', scopeId: rawScopeId }
|
|
1861
|
+
: {};
|
|
1798
1862
|
// Always fetch the opportunity — needed for actor guard and state machine
|
|
1799
1863
|
const opportunity = await systemDb.getOpportunity(opportunityId);
|
|
1800
1864
|
if (!opportunity) {
|
|
@@ -1811,18 +1875,21 @@ export function createOpportunityTools(defineTool, deps) {
|
|
|
1811
1875
|
if (UPDATE_OPPORTUNITY_BLOCKED_STATUSES.has(opportunity.status)) {
|
|
1812
1876
|
return error(`This opportunity is already ${opportunity.status} and cannot be updated.`);
|
|
1813
1877
|
}
|
|
1814
|
-
// Strict scope enforcement: when chat is
|
|
1878
|
+
// Strict scope enforcement: when chat is network-scoped, the caller's own
|
|
1815
1879
|
// actor entry on this opportunity must be anchored on the bound network.
|
|
1816
1880
|
// Mirrors the per-actor filter in getOpportunitiesForUser — relying on
|
|
1817
1881
|
// a focus-scope id or any-actor matches would let a counterpart's network
|
|
1818
1882
|
// presence shadow a viewer whose own actor is elsewhere.
|
|
1819
|
-
const scopedNetworkId = focusedNetworkId(context);
|
|
1883
|
+
const scopedNetworkId = focusedNetworkId(context) ?? context.networkId?.trim();
|
|
1820
1884
|
if (scopedNetworkId) {
|
|
1821
1885
|
const callerOnBoundNetwork = opportunity.actors?.some((a) => a.userId === context.userId && a.networkId === scopedNetworkId);
|
|
1822
1886
|
if (!callerOnBoundNetwork) {
|
|
1823
1887
|
return error("Opportunity not found.");
|
|
1824
1888
|
}
|
|
1825
1889
|
}
|
|
1890
|
+
if (!matchesSelectedIntentScope(opportunity, context.userId, effectiveIntentScope)) {
|
|
1891
|
+
return error("Opportunity not found.");
|
|
1892
|
+
}
|
|
1826
1893
|
const isSend = query.status === "pending";
|
|
1827
1894
|
const _updateGraphStart = Date.now();
|
|
1828
1895
|
const _updateTraceEmitter = requestContext.getStore()?.traceEmitter;
|