@indexnetwork/protocol 6.6.2-rc.388.1 → 6.6.3-rc.389.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 CHANGED
@@ -45,6 +45,7 @@ See [STABILITY.md](./STABILITY.md) for the public-contract and tier definitions.
45
45
  - Included protocol documentation files in the published package tarball so README links remain available to package consumers.
46
46
 
47
47
  ### Fixed
48
+ - Made owned-intent opportunity persistence trigger-aware: recent/lifecycle dedup now reuses only rows linked to the same trigger intent, cross-trigger rows remain independently visible, enrichment cannot absorb another trigger's row, and final persistence reports typed same-trigger/active-negotiation conflicts (IND-495).
48
49
  - Clamped intent-pinned `list_negotiations` results to the user's signal, added explicit signal/all scope metadata, and prevented stale cross-signal history from being presented as current negotiations (IND-483).
49
50
  - Routed continuation-created and recovered opportunities through the normal negotiation boundary, threaded each persisted attempt version into atomic negotiation-task claiming, protected active/input-required tasks from duplicate negotiation, compensated pre-task failures and timeouts to truthful draft/latent states, and refreshed continuation cards from current lifecycle state (IND-470).
50
51
  - Normalized opportunity actor intent IDs at evaluator, graph, and shared persistence boundaries so blank or null-like model sentinels are omitted, valid branded string IDs remain supported, enrichment cannot use or reintroduce malformed provenance, and legacy negotiation reads fail closed (IND-469).
@@ -45,6 +45,11 @@ export type EnrichOrCreateOptions = {
45
45
  * Pass an empty array `[]` to consider all statuses.
46
46
  */
47
47
  excludeStatuses?: OpportunityStatus[];
48
+ /** Restrict merge/expiration candidates to the authoritative owned intent. */
49
+ ownedIntentScope?: {
50
+ triggerIntentId: string;
51
+ ownerUserId: string;
52
+ };
48
53
  };
49
54
  /**
50
55
  * Enrich or create: find overlapping opportunities, filter by semantic relatedness
@@ -1 +1 @@
1
- {"version":3,"file":"opportunity.enricher.d.ts","sourceRoot":"/","sources":["opportunity/opportunity.enricher.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,KAAK,EAAE,qBAAqB,EAAE,WAAW,EAAkE,iBAAiB,EAAE,MAAM,4CAA4C,CAAC;AAExL,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,4CAA4C,CAAC;AAS3E;;;;;;;;;;;;;;GAcG;AACH,eAAO,MAAM,iCAAiC,EAAE,iBAAiB,EAA2C,CAAC;AAE7G,MAAM,MAAM,gBAAgB,GAAG;IAC7B,yBAAyB,CACvB,QAAQ,EAAE,MAAM,EAAE,EAClB,OAAO,CAAC,EAAE;QAAE,kBAAkB,CAAC,EAAE,OAAO,CAAC;QAAC,QAAQ,CAAC,EAAE,iBAAiB,EAAE,CAAC;QAAC,eAAe,CAAC,EAAE,iBAAiB,EAAE,CAAA;KAAE,GAChH,OAAO,CAAC,WAAW,EAAE,CAAC,CAAC;CAC3B,CAAC;AAEF,MAAM,MAAM,gBAAgB,GACxB;IAAE,QAAQ,EAAE,KAAK,CAAC;IAAC,IAAI,EAAE,qBAAqB,CAAA;CAAE,GAChD;IAAE,QAAQ,EAAE,IAAI,CAAC;IAAC,IAAI,EAAE,qBAAqB,CAAC;IAAC,UAAU,EAAE,MAAM,EAAE,CAAC;IAAC,cAAc,EAAE,iBAAiB,CAAA;CAAE,CAAC;AAE7G,MAAM,MAAM,qBAAqB,GAAG;IAClC,mBAAmB,CAAC,EAAE,MAAM,CAAC;IAC7B;;;;OAIG;IACH,eAAe,CAAC,EAAE,iBAAiB,EAAE,CAAC;CACvC,CAAC;AAiJF;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAsB,cAAc,CAClC,QAAQ,EAAE,gBAAgB,EAC1B,QAAQ,EAAE,QAAQ,EAClB,OAAO,EAAE,qBAAqB,EAC9B,OAAO,CAAC,EAAE,qBAAqB,GAC9B,OAAO,CAAC,gBAAgB,CAAC,CAsG3B"}
1
+ {"version":3,"file":"opportunity.enricher.d.ts","sourceRoot":"/","sources":["opportunity/opportunity.enricher.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,KAAK,EAAE,qBAAqB,EAAE,WAAW,EAAkE,iBAAiB,EAAE,MAAM,4CAA4C,CAAC;AAExL,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,4CAA4C,CAAC;AAS3E;;;;;;;;;;;;;;GAcG;AACH,eAAO,MAAM,iCAAiC,EAAE,iBAAiB,EAA2C,CAAC;AAE7G,MAAM,MAAM,gBAAgB,GAAG;IAC7B,yBAAyB,CACvB,QAAQ,EAAE,MAAM,EAAE,EAClB,OAAO,CAAC,EAAE;QAAE,kBAAkB,CAAC,EAAE,OAAO,CAAC;QAAC,QAAQ,CAAC,EAAE,iBAAiB,EAAE,CAAC;QAAC,eAAe,CAAC,EAAE,iBAAiB,EAAE,CAAA;KAAE,GAChH,OAAO,CAAC,WAAW,EAAE,CAAC,CAAC;CAC3B,CAAC;AAEF,MAAM,MAAM,gBAAgB,GACxB;IAAE,QAAQ,EAAE,KAAK,CAAC;IAAC,IAAI,EAAE,qBAAqB,CAAA;CAAE,GAChD;IAAE,QAAQ,EAAE,IAAI,CAAC;IAAC,IAAI,EAAE,qBAAqB,CAAC;IAAC,UAAU,EAAE,MAAM,EAAE,CAAC;IAAC,cAAc,EAAE,iBAAiB,CAAA;CAAE,CAAC;AAE7G,MAAM,MAAM,qBAAqB,GAAG;IAClC,mBAAmB,CAAC,EAAE,MAAM,CAAC;IAC7B;;;;OAIG;IACH,eAAe,CAAC,EAAE,iBAAiB,EAAE,CAAC;IACtC,8EAA8E;IAC9E,gBAAgB,CAAC,EAAE;QAAE,eAAe,EAAE,MAAM,CAAC;QAAC,WAAW,EAAE,MAAM,CAAA;KAAE,CAAC;CACrE,CAAC;AA0JF;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAsB,cAAc,CAClC,QAAQ,EAAE,gBAAgB,EAC1B,QAAQ,EAAE,QAAQ,EAClB,OAAO,EAAE,qBAAqB,EAC9B,OAAO,CAAC,EAAE,qBAAqB,GAC9B,OAAO,CAAC,gBAAgB,CAAC,CAiH3B"}
@@ -79,6 +79,11 @@ function getNonIntroducerUserIds(data) {
79
79
  /**
80
80
  * Extract intent IDs from actors.
81
81
  */
82
+ function belongsToOwnedIntent(opportunity, scope) {
83
+ if (opportunity.detection.triggeredBy === scope.triggerIntentId)
84
+ return true;
85
+ return opportunity.actors.some((actor) => actor.userId === scope.ownerUserId && actor.intent === scope.triggerIntentId);
86
+ }
82
87
  function getIntentIdsFromActors(actors) {
83
88
  const ids = new Set();
84
89
  for (const a of actors) {
@@ -178,7 +183,18 @@ export async function enrichOrCreate(database, embedder, newData, options) {
178
183
  return { enriched: false, data: newData };
179
184
  }
180
185
  const excludeStatuses = options?.excludeStatuses ?? DEFAULT_ENRICHER_EXCLUDE_STATUSES;
181
- const overlapping = await database.findOpportunitiesByActors(actorUserIds, { excludeStatuses });
186
+ const pairOverlaps = await database.findOpportunitiesByActors(actorUserIds, { excludeStatuses });
187
+ const ownedIntentScope = options?.ownedIntentScope;
188
+ const overlapping = ownedIntentScope
189
+ ? pairOverlaps.filter((opportunity) => belongsToOwnedIntent(opportunity, ownedIntentScope))
190
+ : pairOverlaps;
191
+ if (ownedIntentScope && pairOverlaps.length > overlapping.length) {
192
+ logger.info('Excluded cross-trigger overlaps from owned-intent enrichment', {
193
+ triggerIntentId: ownedIntentScope.triggerIntentId,
194
+ pairOverlapCount: pairOverlaps.length,
195
+ sameTriggerOverlapCount: overlapping.length,
196
+ });
197
+ }
182
198
  if (overlapping.length === 0) {
183
199
  return { enriched: false, data: newData };
184
200
  }
@@ -1 +1 @@
1
- {"version":3,"file":"opportunity.enricher.js","sourceRoot":"/","sources":["opportunity/opportunity.enricher.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Opportunity enricher: when creating an opportunity, find overlapping existing\n * opportunities (by non-introducer actor userId), check semantic relatedness, and\n * optionally merge into a single enriched opportunity and expire the old one(s).\n */\n\nimport type { CreateOpportunityData, Opportunity, OpportunityActor, OpportunityInterpretation, OpportunitySignal, OpportunityStatus } from '../shared/interfaces/database.interface.js';\nimport { getAbortSignalConfig } from '../shared/agent/model-signal.js';\nimport type { Embedder } from '../shared/interfaces/embedder.interface.js';\nimport type { Id } from '../shared/interfaces/database.interface.js';\nimport { protocolLogger } from '../shared/observability/protocol.logger.js';\n\nconst logger = protocolLogger('OpportunityEnricher');\n\nconst DEFAULT_SIMILARITY_THRESHOLD = 0.7;\nconst MIN_REASONING_LENGTH_FOR_EMBEDDING = 10;\n\n/**\n * Statuses excluded from the merge-candidate pool by default.\n *\n * - 'accepted': the pair already connected — do NOT fold a new discovery into\n * the historical opp. IND-237 surfaces the existing conversation separately.\n * - 'negotiating': a negotiation is in-flight for this pair; rolling a new\n * candidate into it would blur the outcome of the active turn. Wait for the\n * negotiation to finalize (→ draft/pending/rejected/stalled) first, then\n * enrichment can pick it up on the next pass.\n * - 'expired': already superseded by a later enriched opportunity. Without this\n * exclusion, each enrichment cycle re-merges previously-expired enriched\n * opportunities, compounding their actor arrays into 100+ entries.\n *\n * Exported for callers that want to extend rather than replace the default.\n */\nexport const DEFAULT_ENRICHER_EXCLUDE_STATUSES: OpportunityStatus[] = ['accepted', 'negotiating', 'expired'];\n\nexport type EnricherDatabase = {\n findOpportunitiesByActors(\n actorIds: string[],\n options?: { includeIntroducers?: boolean; statuses?: OpportunityStatus[]; excludeStatuses?: OpportunityStatus[] }\n ): Promise<Opportunity[]>;\n};\n\nexport type EnrichmentResult =\n | { enriched: false; data: CreateOpportunityData }\n | { enriched: true; data: CreateOpportunityData; expiredIds: string[]; resolvedStatus: OpportunityStatus };\n\nexport type EnrichOrCreateOptions = {\n similarityThreshold?: number;\n /**\n * Statuses to exclude from the merge-candidate pool. Defaults to\n * {@link DEFAULT_ENRICHER_EXCLUDE_STATUSES} (`['accepted', 'negotiating']`).\n * Pass an empty array `[]` to consider all statuses.\n */\n excludeStatuses?: OpportunityStatus[];\n};\n\n/**\n * Cosine similarity between two vectors (0–1).\n */\nfunction cosineSimilarity(a: number[], b: number[]): number {\n if (a.length !== b.length || a.length === 0) return 0;\n let dot = 0;\n let normA = 0;\n let normB = 0;\n for (let i = 0; i < a.length; i++) {\n dot += a[i] * b[i];\n normA += a[i] * a[i];\n normB += b[i] * b[i];\n }\n const denom = Math.sqrt(normA) * Math.sqrt(normB);\n if (denom === 0) return 0;\n const sim = dot / denom;\n return Math.max(0, Math.min(1, sim));\n}\n\n/**\n * Resolve enriched opportunity status from related opportunities' statuses and the incoming status.\n * Priority: accepted > pending > rejected > stalled > draft (only when incoming is draft) > latent.\n * The incoming status is included so we do not wrongly downgrade when the new opportunity has a higher-priority status.\n * When incoming is 'draft' (e.g. from in-chat discovery), we preserve draft so the opportunity stays chat-only and\n * does not appear on the home view (home excludes draft).\n * When incoming is NOT draft (e.g. 'latent' from the background broker), existing draft status does NOT contaminate\n * the result — the broker-created opportunity retains its own status and can appear on the home view.\n */\nfunction resolveEnrichedStatus(relatedStatuses: string[], incomingStatus?: string): OpportunityStatus {\n const statuses = incomingStatus ? [...relatedStatuses, incomingStatus] : relatedStatuses;\n if (statuses.includes('accepted')) return 'accepted';\n if (statuses.includes('pending')) return 'pending';\n if (statuses.includes('rejected')) return 'rejected';\n if (statuses.includes('stalled')) return 'stalled';\n if (incomingStatus === 'draft') return 'draft';\n return 'latent';\n}\n\n/**\n * Extract non-introducer actor userIds from create data.\n */\nfunction getNonIntroducerUserIds(data: CreateOpportunityData): Id<'users'>[] {\n const ids = data.actors\n .filter((a) => a.role !== 'introducer')\n .map((a) => a.userId);\n return [...new Set(ids)];\n}\n\n/**\n * Extract intent IDs from actors.\n */\nfunction getIntentIdsFromActors(actors: OpportunityActor[]): Set<string> {\n const ids = new Set<string>();\n for (const a of actors) {\n if (a.intent) ids.add(a.intent);\n }\n return ids;\n}\n\n/**\n * Check if two opportunities share at least one intent ID.\n */\nfunction shareIntentIds(\n newData: CreateOpportunityData,\n existing: Opportunity\n): boolean {\n const newIntents = getIntentIdsFromActors(newData.actors);\n const existingIntents = getIntentIdsFromActors(existing.actors);\n for (const id of newIntents) {\n if (existingIntents.has(id)) return true;\n }\n return false;\n}\n\n/**\n * Merge actors from new data and existing opportunities: union by (networkId, userId, intent),\n * preserving all unique introducers and preferring newer role on conflict.\n */\nfunction mergeActors(\n newData: CreateOpportunityData,\n existingList: Opportunity[]\n): OpportunityActor[] {\n const key = (a: OpportunityActor) =>\n `${a.networkId}:${a.userId}:${a.intent ?? ''}`;\n const map = new Map<string, OpportunityActor>();\n\n // Add all from existing first (older)\n for (const opp of existingList) {\n for (const a of opp.actors) {\n map.set(key(a), a);\n }\n }\n // Add/overwrite with new (newer wins for same key, e.g. role)\n for (const a of newData.actors) {\n map.set(key(a), a);\n }\n\n return [...map.values()];\n}\n\n/**\n * Merge interpretation: single reasoning (new data only), max confidence, merged signals.\n * We use only the new opportunity's reasoning to avoid repetitive concatenation when\n * multiple overlapping opportunities share the same or similar text (e.g. same pair\n * across indexes), which previously produced long duplicated paragraphs in chat cards.\n */\nfunction mergeInterpretation(\n newData: CreateOpportunityData,\n existingList: Opportunity[]\n): OpportunityInterpretation {\n const reasoning = newData.interpretation.reasoning;\n\n let maxConf =\n typeof newData.interpretation.confidence === 'number'\n ? newData.interpretation.confidence\n : parseFloat(String(newData.interpretation.confidence ?? 0));\n for (const o of existingList) {\n const c = o.interpretation?.confidence;\n const cNum = typeof c === 'number' ? c : parseFloat(String(c ?? 0));\n if (cNum > maxConf) maxConf = cNum;\n }\n const confidence = maxConf;\n\n const signals: OpportunitySignal[] = [\n ...(newData.interpretation.signals ?? []),\n ...existingList.flatMap((o) => o.interpretation?.signals ?? []),\n ];\n const seen = new Set<string>();\n const dedupedSignals = signals.filter((s) => {\n const k = `${s.type}:${s.detail ?? ''}`;\n if (seen.has(k)) return false;\n seen.add(k);\n return true;\n });\n\n return {\n category: newData.interpretation.category,\n reasoning,\n confidence: typeof confidence === 'string' ? parseFloat(confidence) : confidence,\n signals: dedupedSignals.length > 0 ? dedupedSignals : newData.interpretation.signals,\n };\n}\n\n/**\n * Enrich or create: find overlapping opportunities, filter by semantic relatedness\n * using a two-phase approach, merge actors and interpretation into a single\n * CreateOpportunityData, and return the data plus IDs to expire. If no related\n * overlap, return original data unchanged.\n *\n * Phase 1 — Intent check (free, no API call):\n * Shared intent IDs mean the same declared user goal drove both opportunities.\n * This is rare because the IntentReconciler already deduplicates intents per-user\n * upstream, but when it fires it is definitive.\n *\n * Phase 2 — Batched embedding similarity (one API call for all remaining):\n * For opportunities without shared intents, embed all reasoning texts in a single\n * batch call and compare cosine similarity. Cross-user intent comparison (e.g.\n * Alice's \"find ML co-founder\" vs Bob's \"join ML startup\") is implicitly handled\n * here since reasoning text synthesizes both users' intents.\n */\nexport async function enrichOrCreate(\n database: EnricherDatabase,\n embedder: Embedder,\n newData: CreateOpportunityData,\n options?: EnrichOrCreateOptions\n): Promise<EnrichmentResult> {\n const actorUserIds = getNonIntroducerUserIds(newData);\n if (actorUserIds.length === 0) {\n return { enriched: false, data: newData };\n }\n\n const excludeStatuses = options?.excludeStatuses ?? DEFAULT_ENRICHER_EXCLUDE_STATUSES;\n const overlapping = await database.findOpportunitiesByActors(actorUserIds, { excludeStatuses });\n if (overlapping.length === 0) {\n return { enriched: false, data: newData };\n }\n\n const threshold = options?.similarityThreshold ?? DEFAULT_SIMILARITY_THRESHOLD;\n\n // Phase 1: Intent-based relatedness (free, strongest signal)\n const related: Opportunity[] = [];\n const remaining: Opportunity[] = [];\n for (const opp of overlapping) {\n if (shareIntentIds(newData, opp)) {\n related.push(opp);\n } else {\n remaining.push(opp);\n }\n }\n\n // Phase 2: Batched embedding similarity (one API call for all remaining)\n if (remaining.length > 0) {\n const newReasoning = (newData.interpretation?.reasoning ?? '').trim();\n\n // Only embed opps where both reasonings are long enough\n const embeddable: Opportunity[] = [];\n for (const opp of remaining) {\n const existingReasoning = (opp.interpretation?.reasoning ?? '').trim();\n if (\n newReasoning.length >= MIN_REASONING_LENGTH_FOR_EMBEDDING &&\n existingReasoning.length >= MIN_REASONING_LENGTH_FOR_EMBEDDING\n ) {\n embeddable.push(opp);\n }\n // Short reasoning + no shared intents (Phase 1 missed) → not related\n }\n\n if (embeddable.length > 0) {\n try {\n const textsToEmbed = [\n newReasoning,\n ...embeddable.map((o) => (o.interpretation?.reasoning ?? '').trim()),\n ];\n const vectors = (await embedder.generate(textsToEmbed, undefined, getAbortSignalConfig())) as number[][];\n const newVec = vectors[0];\n if (newVec?.length) {\n for (let i = 0; i < embeddable.length; i++) {\n const existingVec = vectors[i + 1];\n if (existingVec?.length && cosineSimilarity(newVec, existingVec) >= threshold) {\n related.push(embeddable[i]);\n }\n }\n }\n } catch (e) {\n logger.warn('Embedding check failed; intent-matched opportunities already captured', { error: e });\n // Phase 1 matches are preserved; remaining opps without shared intents are not related\n }\n }\n }\n\n if (related.length === 0) {\n return { enriched: false, data: newData };\n }\n\n const mergedActors = mergeActors(newData, related);\n const mergedInterpretation = mergeInterpretation(newData, related);\n const enrichedFrom = related.map((o) => o.id);\n const mergedConfidence =\n typeof mergedInterpretation.confidence === 'number'\n ? String(mergedInterpretation.confidence)\n : mergedInterpretation.confidence;\n\n const enrichedData: CreateOpportunityData = {\n ...newData,\n detection: {\n ...newData.detection,\n source: 'enrichment',\n enrichedFrom,\n },\n actors: mergedActors,\n interpretation: mergedInterpretation,\n confidence: mergedConfidence,\n };\n\n const resolvedStatus = resolveEnrichedStatus(related.map((o) => o.status), newData.status);\n\n logger.verbose('Enriched opportunity', {\n enrichedFrom,\n actorCount: mergedActors.length,\n });\n\n return {\n enriched: true,\n data: enrichedData,\n expiredIds: enrichedFrom,\n resolvedStatus,\n };\n}\n"]}
1
+ {"version":3,"file":"opportunity.enricher.js","sourceRoot":"/","sources":["opportunity/opportunity.enricher.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Opportunity enricher: when creating an opportunity, find overlapping existing\n * opportunities (by non-introducer actor userId), check semantic relatedness, and\n * optionally merge into a single enriched opportunity and expire the old one(s).\n */\n\nimport type { CreateOpportunityData, Opportunity, OpportunityActor, OpportunityInterpretation, OpportunitySignal, OpportunityStatus } from '../shared/interfaces/database.interface.js';\nimport { getAbortSignalConfig } from '../shared/agent/model-signal.js';\nimport type { Embedder } from '../shared/interfaces/embedder.interface.js';\nimport type { Id } from '../shared/interfaces/database.interface.js';\nimport { protocolLogger } from '../shared/observability/protocol.logger.js';\n\nconst logger = protocolLogger('OpportunityEnricher');\n\nconst DEFAULT_SIMILARITY_THRESHOLD = 0.7;\nconst MIN_REASONING_LENGTH_FOR_EMBEDDING = 10;\n\n/**\n * Statuses excluded from the merge-candidate pool by default.\n *\n * - 'accepted': the pair already connected — do NOT fold a new discovery into\n * the historical opp. IND-237 surfaces the existing conversation separately.\n * - 'negotiating': a negotiation is in-flight for this pair; rolling a new\n * candidate into it would blur the outcome of the active turn. Wait for the\n * negotiation to finalize (→ draft/pending/rejected/stalled) first, then\n * enrichment can pick it up on the next pass.\n * - 'expired': already superseded by a later enriched opportunity. Without this\n * exclusion, each enrichment cycle re-merges previously-expired enriched\n * opportunities, compounding their actor arrays into 100+ entries.\n *\n * Exported for callers that want to extend rather than replace the default.\n */\nexport const DEFAULT_ENRICHER_EXCLUDE_STATUSES: OpportunityStatus[] = ['accepted', 'negotiating', 'expired'];\n\nexport type EnricherDatabase = {\n findOpportunitiesByActors(\n actorIds: string[],\n options?: { includeIntroducers?: boolean; statuses?: OpportunityStatus[]; excludeStatuses?: OpportunityStatus[] }\n ): Promise<Opportunity[]>;\n};\n\nexport type EnrichmentResult =\n | { enriched: false; data: CreateOpportunityData }\n | { enriched: true; data: CreateOpportunityData; expiredIds: string[]; resolvedStatus: OpportunityStatus };\n\nexport type EnrichOrCreateOptions = {\n similarityThreshold?: number;\n /**\n * Statuses to exclude from the merge-candidate pool. Defaults to\n * {@link DEFAULT_ENRICHER_EXCLUDE_STATUSES} (`['accepted', 'negotiating']`).\n * Pass an empty array `[]` to consider all statuses.\n */\n excludeStatuses?: OpportunityStatus[];\n /** Restrict merge/expiration candidates to the authoritative owned intent. */\n ownedIntentScope?: { triggerIntentId: string; ownerUserId: string };\n};\n\n/**\n * Cosine similarity between two vectors (0–1).\n */\nfunction cosineSimilarity(a: number[], b: number[]): number {\n if (a.length !== b.length || a.length === 0) return 0;\n let dot = 0;\n let normA = 0;\n let normB = 0;\n for (let i = 0; i < a.length; i++) {\n dot += a[i] * b[i];\n normA += a[i] * a[i];\n normB += b[i] * b[i];\n }\n const denom = Math.sqrt(normA) * Math.sqrt(normB);\n if (denom === 0) return 0;\n const sim = dot / denom;\n return Math.max(0, Math.min(1, sim));\n}\n\n/**\n * Resolve enriched opportunity status from related opportunities' statuses and the incoming status.\n * Priority: accepted > pending > rejected > stalled > draft (only when incoming is draft) > latent.\n * The incoming status is included so we do not wrongly downgrade when the new opportunity has a higher-priority status.\n * When incoming is 'draft' (e.g. from in-chat discovery), we preserve draft so the opportunity stays chat-only and\n * does not appear on the home view (home excludes draft).\n * When incoming is NOT draft (e.g. 'latent' from the background broker), existing draft status does NOT contaminate\n * the result — the broker-created opportunity retains its own status and can appear on the home view.\n */\nfunction resolveEnrichedStatus(relatedStatuses: string[], incomingStatus?: string): OpportunityStatus {\n const statuses = incomingStatus ? [...relatedStatuses, incomingStatus] : relatedStatuses;\n if (statuses.includes('accepted')) return 'accepted';\n if (statuses.includes('pending')) return 'pending';\n if (statuses.includes('rejected')) return 'rejected';\n if (statuses.includes('stalled')) return 'stalled';\n if (incomingStatus === 'draft') return 'draft';\n return 'latent';\n}\n\n/**\n * Extract non-introducer actor userIds from create data.\n */\nfunction getNonIntroducerUserIds(data: CreateOpportunityData): Id<'users'>[] {\n const ids = data.actors\n .filter((a) => a.role !== 'introducer')\n .map((a) => a.userId);\n return [...new Set(ids)];\n}\n\n/**\n * Extract intent IDs from actors.\n */\nfunction belongsToOwnedIntent(\n opportunity: Opportunity,\n scope: { triggerIntentId: string; ownerUserId: string },\n): boolean {\n if (opportunity.detection.triggeredBy === scope.triggerIntentId) return true;\n return opportunity.actors.some((actor) =>\n actor.userId === scope.ownerUserId && actor.intent === scope.triggerIntentId);\n}\n\nfunction getIntentIdsFromActors(actors: OpportunityActor[]): Set<string> {\n const ids = new Set<string>();\n for (const a of actors) {\n if (a.intent) ids.add(a.intent);\n }\n return ids;\n}\n\n/**\n * Check if two opportunities share at least one intent ID.\n */\nfunction shareIntentIds(\n newData: CreateOpportunityData,\n existing: Opportunity\n): boolean {\n const newIntents = getIntentIdsFromActors(newData.actors);\n const existingIntents = getIntentIdsFromActors(existing.actors);\n for (const id of newIntents) {\n if (existingIntents.has(id)) return true;\n }\n return false;\n}\n\n/**\n * Merge actors from new data and existing opportunities: union by (networkId, userId, intent),\n * preserving all unique introducers and preferring newer role on conflict.\n */\nfunction mergeActors(\n newData: CreateOpportunityData,\n existingList: Opportunity[]\n): OpportunityActor[] {\n const key = (a: OpportunityActor) =>\n `${a.networkId}:${a.userId}:${a.intent ?? ''}`;\n const map = new Map<string, OpportunityActor>();\n\n // Add all from existing first (older)\n for (const opp of existingList) {\n for (const a of opp.actors) {\n map.set(key(a), a);\n }\n }\n // Add/overwrite with new (newer wins for same key, e.g. role)\n for (const a of newData.actors) {\n map.set(key(a), a);\n }\n\n return [...map.values()];\n}\n\n/**\n * Merge interpretation: single reasoning (new data only), max confidence, merged signals.\n * We use only the new opportunity's reasoning to avoid repetitive concatenation when\n * multiple overlapping opportunities share the same or similar text (e.g. same pair\n * across indexes), which previously produced long duplicated paragraphs in chat cards.\n */\nfunction mergeInterpretation(\n newData: CreateOpportunityData,\n existingList: Opportunity[]\n): OpportunityInterpretation {\n const reasoning = newData.interpretation.reasoning;\n\n let maxConf =\n typeof newData.interpretation.confidence === 'number'\n ? newData.interpretation.confidence\n : parseFloat(String(newData.interpretation.confidence ?? 0));\n for (const o of existingList) {\n const c = o.interpretation?.confidence;\n const cNum = typeof c === 'number' ? c : parseFloat(String(c ?? 0));\n if (cNum > maxConf) maxConf = cNum;\n }\n const confidence = maxConf;\n\n const signals: OpportunitySignal[] = [\n ...(newData.interpretation.signals ?? []),\n ...existingList.flatMap((o) => o.interpretation?.signals ?? []),\n ];\n const seen = new Set<string>();\n const dedupedSignals = signals.filter((s) => {\n const k = `${s.type}:${s.detail ?? ''}`;\n if (seen.has(k)) return false;\n seen.add(k);\n return true;\n });\n\n return {\n category: newData.interpretation.category,\n reasoning,\n confidence: typeof confidence === 'string' ? parseFloat(confidence) : confidence,\n signals: dedupedSignals.length > 0 ? dedupedSignals : newData.interpretation.signals,\n };\n}\n\n/**\n * Enrich or create: find overlapping opportunities, filter by semantic relatedness\n * using a two-phase approach, merge actors and interpretation into a single\n * CreateOpportunityData, and return the data plus IDs to expire. If no related\n * overlap, return original data unchanged.\n *\n * Phase 1 — Intent check (free, no API call):\n * Shared intent IDs mean the same declared user goal drove both opportunities.\n * This is rare because the IntentReconciler already deduplicates intents per-user\n * upstream, but when it fires it is definitive.\n *\n * Phase 2 — Batched embedding similarity (one API call for all remaining):\n * For opportunities without shared intents, embed all reasoning texts in a single\n * batch call and compare cosine similarity. Cross-user intent comparison (e.g.\n * Alice's \"find ML co-founder\" vs Bob's \"join ML startup\") is implicitly handled\n * here since reasoning text synthesizes both users' intents.\n */\nexport async function enrichOrCreate(\n database: EnricherDatabase,\n embedder: Embedder,\n newData: CreateOpportunityData,\n options?: EnrichOrCreateOptions\n): Promise<EnrichmentResult> {\n const actorUserIds = getNonIntroducerUserIds(newData);\n if (actorUserIds.length === 0) {\n return { enriched: false, data: newData };\n }\n\n const excludeStatuses = options?.excludeStatuses ?? DEFAULT_ENRICHER_EXCLUDE_STATUSES;\n const pairOverlaps = await database.findOpportunitiesByActors(actorUserIds, { excludeStatuses });\n const ownedIntentScope = options?.ownedIntentScope;\n const overlapping = ownedIntentScope\n ? pairOverlaps.filter((opportunity) => belongsToOwnedIntent(opportunity, ownedIntentScope))\n : pairOverlaps;\n if (ownedIntentScope && pairOverlaps.length > overlapping.length) {\n logger.info('Excluded cross-trigger overlaps from owned-intent enrichment', {\n triggerIntentId: ownedIntentScope.triggerIntentId,\n pairOverlapCount: pairOverlaps.length,\n sameTriggerOverlapCount: overlapping.length,\n });\n }\n if (overlapping.length === 0) {\n return { enriched: false, data: newData };\n }\n\n const threshold = options?.similarityThreshold ?? DEFAULT_SIMILARITY_THRESHOLD;\n\n // Phase 1: Intent-based relatedness (free, strongest signal)\n const related: Opportunity[] = [];\n const remaining: Opportunity[] = [];\n for (const opp of overlapping) {\n if (shareIntentIds(newData, opp)) {\n related.push(opp);\n } else {\n remaining.push(opp);\n }\n }\n\n // Phase 2: Batched embedding similarity (one API call for all remaining)\n if (remaining.length > 0) {\n const newReasoning = (newData.interpretation?.reasoning ?? '').trim();\n\n // Only embed opps where both reasonings are long enough\n const embeddable: Opportunity[] = [];\n for (const opp of remaining) {\n const existingReasoning = (opp.interpretation?.reasoning ?? '').trim();\n if (\n newReasoning.length >= MIN_REASONING_LENGTH_FOR_EMBEDDING &&\n existingReasoning.length >= MIN_REASONING_LENGTH_FOR_EMBEDDING\n ) {\n embeddable.push(opp);\n }\n // Short reasoning + no shared intents (Phase 1 missed) → not related\n }\n\n if (embeddable.length > 0) {\n try {\n const textsToEmbed = [\n newReasoning,\n ...embeddable.map((o) => (o.interpretation?.reasoning ?? '').trim()),\n ];\n const vectors = (await embedder.generate(textsToEmbed, undefined, getAbortSignalConfig())) as number[][];\n const newVec = vectors[0];\n if (newVec?.length) {\n for (let i = 0; i < embeddable.length; i++) {\n const existingVec = vectors[i + 1];\n if (existingVec?.length && cosineSimilarity(newVec, existingVec) >= threshold) {\n related.push(embeddable[i]);\n }\n }\n }\n } catch (e) {\n logger.warn('Embedding check failed; intent-matched opportunities already captured', { error: e });\n // Phase 1 matches are preserved; remaining opps without shared intents are not related\n }\n }\n }\n\n if (related.length === 0) {\n return { enriched: false, data: newData };\n }\n\n const mergedActors = mergeActors(newData, related);\n const mergedInterpretation = mergeInterpretation(newData, related);\n const enrichedFrom = related.map((o) => o.id);\n const mergedConfidence =\n typeof mergedInterpretation.confidence === 'number'\n ? String(mergedInterpretation.confidence)\n : mergedInterpretation.confidence;\n\n const enrichedData: CreateOpportunityData = {\n ...newData,\n detection: {\n ...newData.detection,\n source: 'enrichment',\n enrichedFrom,\n },\n actors: mergedActors,\n interpretation: mergedInterpretation,\n confidence: mergedConfidence,\n };\n\n const resolvedStatus = resolveEnrichedStatus(related.map((o) => o.status), newData.status);\n\n logger.verbose('Enriched opportunity', {\n enrichedFrom,\n actorCount: mergedActors.length,\n });\n\n return {\n enriched: true,\n data: enrichedData,\n expiredIds: enrichedFrom,\n resolvedStatus,\n };\n}\n"]}
@@ -41,7 +41,7 @@ export type OpportunityEvaluatorLike = {
41
41
  }>>;
42
42
  };
43
43
  import type { Embedder } from '../shared/interfaces/embedder.interface.js';
44
- import type { CreateOpportunityData, Opportunity } from '../shared/interfaces/database.interface.js';
44
+ import type { CreateOpportunityData, Opportunity, OpportunityStatus } from '../shared/interfaces/database.interface.js';
45
45
  import type { NegotiationGraphLike, UserNegotiationContext } from "../negotiation/negotiation.state.js";
46
46
  import type { AgentDispatcher } from "../shared/interfaces/agent-dispatcher.interface.js";
47
47
  interface NegotiationIntentSource {
@@ -185,8 +185,11 @@ export declare class OpportunityGraphFactory {
185
185
  candidateUserId: Id<"users">;
186
186
  networkId: Id<"networks">;
187
187
  existingOpportunityId?: Id<"opportunities">;
188
- existingStatus?: import("../shared/interfaces/database.interface.js").OpportunityStatus;
188
+ existingStatus?: OpportunityStatus;
189
+ reason?: "same_trigger_recent_duplicate" | "pair_active_negotiation" | "final_atomic_conflict";
190
+ existingTriggerIntentId?: string;
189
191
  }[];
192
+ persistenceOutcome: import("./opportunity.state.js").OpportunityPersistenceOutcome | undefined;
190
193
  error: string | undefined;
191
194
  readResult: {
192
195
  count: number;
@@ -308,13 +311,18 @@ export declare class OpportunityGraphFactory {
308
311
  candidateUserId: Id<"users">;
309
312
  networkId: Id<"networks">;
310
313
  existingOpportunityId?: Id<"opportunities">;
311
- existingStatus?: import("../shared/interfaces/database.interface.js").OpportunityStatus;
314
+ existingStatus?: OpportunityStatus;
315
+ reason?: "same_trigger_recent_duplicate" | "pair_active_negotiation" | "final_atomic_conflict";
316
+ existingTriggerIntentId?: string;
312
317
  }[] | import("@langchain/langgraph").OverwriteValue<{
313
318
  candidateUserId: Id<"users">;
314
319
  networkId: Id<"networks">;
315
320
  existingOpportunityId?: Id<"opportunities">;
316
- existingStatus?: import("../shared/interfaces/database.interface.js").OpportunityStatus;
321
+ existingStatus?: OpportunityStatus;
322
+ reason?: "same_trigger_recent_duplicate" | "pair_active_negotiation" | "final_atomic_conflict";
323
+ existingTriggerIntentId?: string;
317
324
  }[]> | undefined;
325
+ persistenceOutcome?: import("./opportunity.state.js").OpportunityPersistenceOutcome | import("@langchain/langgraph").OverwriteValue<import("./opportunity.state.js").OpportunityPersistenceOutcome | undefined> | undefined;
318
326
  error?: string | import("@langchain/langgraph").OverwriteValue<string | undefined> | undefined;
319
327
  readResult?: {
320
328
  count: number;
@@ -496,18 +504,25 @@ export declare class OpportunityGraphFactory {
496
504
  candidateUserId: Id<"users">;
497
505
  networkId: Id<"networks">;
498
506
  existingOpportunityId?: Id<"opportunities">;
499
- existingStatus?: import("../shared/interfaces/database.interface.js").OpportunityStatus;
507
+ existingStatus?: OpportunityStatus;
508
+ reason?: "same_trigger_recent_duplicate" | "pair_active_negotiation" | "final_atomic_conflict";
509
+ existingTriggerIntentId?: string;
500
510
  }[], {
501
511
  candidateUserId: Id<"users">;
502
512
  networkId: Id<"networks">;
503
513
  existingOpportunityId?: Id<"opportunities">;
504
- existingStatus?: import("../shared/interfaces/database.interface.js").OpportunityStatus;
514
+ existingStatus?: OpportunityStatus;
515
+ reason?: "same_trigger_recent_duplicate" | "pair_active_negotiation" | "final_atomic_conflict";
516
+ existingTriggerIntentId?: string;
505
517
  }[] | import("@langchain/langgraph").OverwriteValue<{
506
518
  candidateUserId: Id<"users">;
507
519
  networkId: Id<"networks">;
508
520
  existingOpportunityId?: Id<"opportunities">;
509
- existingStatus?: import("../shared/interfaces/database.interface.js").OpportunityStatus;
521
+ existingStatus?: OpportunityStatus;
522
+ reason?: "same_trigger_recent_duplicate" | "pair_active_negotiation" | "final_atomic_conflict";
523
+ existingTriggerIntentId?: string;
510
524
  }[]>, unknown>;
525
+ persistenceOutcome: import("@langchain/langgraph").BaseChannel<import("./opportunity.state.js").OpportunityPersistenceOutcome | undefined, import("./opportunity.state.js").OpportunityPersistenceOutcome | import("@langchain/langgraph").OverwriteValue<import("./opportunity.state.js").OpportunityPersistenceOutcome | undefined> | undefined, unknown>;
511
526
  error: import("@langchain/langgraph").BaseChannel<string | undefined, string | import("@langchain/langgraph").OverwriteValue<string | undefined> | undefined, unknown>;
512
527
  readResult: import("@langchain/langgraph").BaseChannel<{
513
528
  count: number;
@@ -736,18 +751,25 @@ export declare class OpportunityGraphFactory {
736
751
  candidateUserId: Id<"users">;
737
752
  networkId: Id<"networks">;
738
753
  existingOpportunityId?: Id<"opportunities">;
739
- existingStatus?: import("../shared/interfaces/database.interface.js").OpportunityStatus;
754
+ existingStatus?: OpportunityStatus;
755
+ reason?: "same_trigger_recent_duplicate" | "pair_active_negotiation" | "final_atomic_conflict";
756
+ existingTriggerIntentId?: string;
740
757
  }[], {
741
758
  candidateUserId: Id<"users">;
742
759
  networkId: Id<"networks">;
743
760
  existingOpportunityId?: Id<"opportunities">;
744
- existingStatus?: import("../shared/interfaces/database.interface.js").OpportunityStatus;
761
+ existingStatus?: OpportunityStatus;
762
+ reason?: "same_trigger_recent_duplicate" | "pair_active_negotiation" | "final_atomic_conflict";
763
+ existingTriggerIntentId?: string;
745
764
  }[] | import("@langchain/langgraph").OverwriteValue<{
746
765
  candidateUserId: Id<"users">;
747
766
  networkId: Id<"networks">;
748
767
  existingOpportunityId?: Id<"opportunities">;
749
- existingStatus?: import("../shared/interfaces/database.interface.js").OpportunityStatus;
768
+ existingStatus?: OpportunityStatus;
769
+ reason?: "same_trigger_recent_duplicate" | "pair_active_negotiation" | "final_atomic_conflict";
770
+ existingTriggerIntentId?: string;
750
771
  }[]>, unknown>;
772
+ persistenceOutcome: import("@langchain/langgraph").BaseChannel<import("./opportunity.state.js").OpportunityPersistenceOutcome | undefined, import("./opportunity.state.js").OpportunityPersistenceOutcome | import("@langchain/langgraph").OverwriteValue<import("./opportunity.state.js").OpportunityPersistenceOutcome | undefined> | undefined, unknown>;
751
773
  error: import("@langchain/langgraph").BaseChannel<string | undefined, string | import("@langchain/langgraph").OverwriteValue<string | undefined> | undefined, unknown>;
752
774
  readResult: import("@langchain/langgraph").BaseChannel<{
753
775
  count: number;
@@ -1158,7 +1180,7 @@ export declare class OpportunityGraphFactory {
1158
1180
  createdByName: string | undefined;
1159
1181
  };
1160
1182
  options: {
1161
- initialStatus: import("../shared/interfaces/database.interface.js").OpportunityStatus;
1183
+ initialStatus: OpportunityStatus;
1162
1184
  minScore?: number;
1163
1185
  limit?: number;
1164
1186
  lenses?: import("../shared/hyde/lens.inferrer.js").Lens[];
@@ -1182,7 +1204,7 @@ export declare class OpportunityGraphFactory {
1182
1204
  createdByName: string | undefined;
1183
1205
  };
1184
1206
  options: {
1185
- initialStatus: import("../shared/interfaces/database.interface.js").OpportunityStatus;
1207
+ initialStatus: OpportunityStatus;
1186
1208
  minScore?: number;
1187
1209
  limit?: number;
1188
1210
  lenses?: import("../shared/hyde/lens.inferrer.js").Lens[];
@@ -1195,247 +1217,87 @@ export declare class OpportunityGraphFactory {
1195
1217
  error?: undefined;
1196
1218
  trace?: undefined;
1197
1219
  };
1198
- persist: import("@langchain/langgraph").UpdateType<{
1199
- userId: import("@langchain/langgraph").BaseChannel<Id<"users">, Id<"users"> | import("@langchain/langgraph").OverwriteValue<Id<"users">>, unknown>;
1200
- searchQuery: import("@langchain/langgraph").BaseChannel<string | undefined, string | import("@langchain/langgraph").OverwriteValue<string | undefined> | undefined, unknown>;
1201
- networkId: import("@langchain/langgraph").BaseChannel<Id<"networks"> | undefined, Id<"networks"> | import("@langchain/langgraph").OverwriteValue<Id<"networks"> | undefined> | undefined, unknown>;
1202
- indexScope: import("@langchain/langgraph").BaseChannel<Id<"networks">[] | undefined, Id<"networks">[] | import("@langchain/langgraph").OverwriteValue<Id<"networks">[] | undefined> | undefined, unknown>;
1203
- triggerIntentId: import("@langchain/langgraph").BaseChannel<Id<"intents"> | undefined, Id<"intents"> | import("@langchain/langgraph").OverwriteValue<Id<"intents"> | undefined> | undefined, unknown>;
1204
- targetUserId: import("@langchain/langgraph").BaseChannel<Id<"users"> | undefined, Id<"users"> | import("@langchain/langgraph").OverwriteValue<Id<"users"> | undefined> | undefined, unknown>;
1205
- onBehalfOfUserId: import("@langchain/langgraph").BaseChannel<Id<"users"> | undefined, Id<"users"> | import("@langchain/langgraph").OverwriteValue<Id<"users"> | undefined> | undefined, unknown>;
1206
- options: import("@langchain/langgraph").BaseChannel<import("./opportunity.state.js").OpportunityGraphOptions, import("./opportunity.state.js").OpportunityGraphOptions | import("@langchain/langgraph").OverwriteValue<import("./opportunity.state.js").OpportunityGraphOptions>, unknown>;
1207
- trigger: import("@langchain/langgraph").BaseChannel<import("./opportunity.state.js").OpportunityTrigger, import("./opportunity.state.js").OpportunityTrigger | import("@langchain/langgraph").OverwriteValue<import("./opportunity.state.js").OpportunityTrigger>, unknown>;
1208
- dedupAlreadyAccepted: import("@langchain/langgraph").BaseChannel<{
1209
- opportunityId: string;
1210
- counterpartyUserId: string;
1211
- }[], {
1212
- opportunityId: string;
1213
- counterpartyUserId: string;
1214
- }[] | import("@langchain/langgraph").OverwriteValue<{
1215
- opportunityId: string;
1216
- counterpartyUserId: string;
1217
- }[]>, unknown>;
1218
- operationMode: import("@langchain/langgraph").BaseChannel<"delete" | "send" | "update" | "create" | "create_introduction" | "continue_discovery" | "read" | "negotiate_existing" | "approve_introduction", "delete" | "send" | "update" | "create" | "create_introduction" | "continue_discovery" | "read" | "negotiate_existing" | "approve_introduction" | import("@langchain/langgraph").OverwriteValue<"delete" | "send" | "update" | "create" | "create_introduction" | "continue_discovery" | "read" | "negotiate_existing" | "approve_introduction">, unknown>;
1219
- introductionEntities: import("@langchain/langgraph").BaseChannel<EvaluatorEntity[], EvaluatorEntity[] | import("@langchain/langgraph").OverwriteValue<EvaluatorEntity[]>, unknown>;
1220
- introductionHint: import("@langchain/langgraph").BaseChannel<string | undefined, string | import("@langchain/langgraph").OverwriteValue<string | undefined> | undefined, unknown>;
1221
- requiredNetworkId: import("@langchain/langgraph").BaseChannel<Id<"networks"> | undefined, Id<"networks"> | import("@langchain/langgraph").OverwriteValue<Id<"networks"> | undefined> | undefined, unknown>;
1222
- introductionContext: import("@langchain/langgraph").BaseChannel<{
1223
- createdByName?: string;
1224
- } | undefined, {
1225
- createdByName?: string;
1226
- } | import("@langchain/langgraph").OverwriteValue<{
1227
- createdByName?: string;
1228
- } | undefined> | undefined, unknown>;
1229
- opportunityId: import("@langchain/langgraph").BaseChannel<string | undefined, string | import("@langchain/langgraph").OverwriteValue<string | undefined> | undefined, unknown>;
1230
- newStatus: import("@langchain/langgraph").BaseChannel<string | undefined, string | import("@langchain/langgraph").OverwriteValue<string | undefined> | undefined, unknown>;
1231
- indexedIntents: import("@langchain/langgraph").BaseChannel<IndexedIntent[], IndexedIntent[] | import("@langchain/langgraph").OverwriteValue<IndexedIntent[]>, unknown>;
1232
- userNetworks: import("@langchain/langgraph").BaseChannel<Id<"networks">[], Id<"networks">[] | import("@langchain/langgraph").OverwriteValue<Id<"networks">[]>, unknown>;
1233
- targetNetworks: import("@langchain/langgraph").BaseChannel<TargetNetwork[], TargetNetwork[] | import("@langchain/langgraph").OverwriteValue<TargetNetwork[]>, unknown>;
1234
- indexRelevancyScores: import("@langchain/langgraph").BaseChannel<Record<string, number>, Record<string, number> | import("@langchain/langgraph").OverwriteValue<Record<string, number>>, unknown>;
1235
- discoverySource: import("@langchain/langgraph").BaseChannel<"intent" | "context", "intent" | "context" | import("@langchain/langgraph").OverwriteValue<"intent" | "context">, unknown>;
1236
- resolvedTriggerIntentId: import("@langchain/langgraph").BaseChannel<Id<"intents"> | undefined, Id<"intents"> | import("@langchain/langgraph").OverwriteValue<Id<"intents"> | undefined> | undefined, unknown>;
1237
- sourceProfile: import("@langchain/langgraph").BaseChannel<SourceProfileData | null, SourceProfileData | import("@langchain/langgraph").OverwriteValue<SourceProfileData | null> | null, unknown>;
1238
- sourcePremises: import("@langchain/langgraph").BaseChannel<{
1239
- premiseId: Id<"premises">;
1240
- embedding: number[];
1241
- }[], {
1242
- premiseId: Id<"premises">;
1243
- embedding: number[];
1244
- }[] | import("@langchain/langgraph").OverwriteValue<{
1245
- premiseId: Id<"premises">;
1246
- embedding: number[];
1247
- }[]>, unknown>;
1248
- sourceContexts: import("@langchain/langgraph").BaseChannel<{
1249
- contextId: string;
1250
- networkId: Id<"networks">;
1251
- text: string;
1252
- embedding: number[];
1253
- }[], {
1254
- contextId: string;
1255
- networkId: Id<"networks">;
1256
- text: string;
1257
- embedding: number[];
1258
- }[] | import("@langchain/langgraph").OverwriteValue<{
1259
- contextId: string;
1260
- networkId: Id<"networks">;
1261
- text: string;
1262
- embedding: number[];
1263
- }[]>, unknown>;
1264
- resolvedIntentInIndex: import("@langchain/langgraph").BaseChannel<boolean, boolean | import("@langchain/langgraph").OverwriteValue<boolean>, unknown>;
1265
- createIntentSuggested: import("@langchain/langgraph").BaseChannel<boolean, boolean | import("@langchain/langgraph").OverwriteValue<boolean>, unknown>;
1266
- suggestedIntentDescription: import("@langchain/langgraph").BaseChannel<string | undefined, string | import("@langchain/langgraph").OverwriteValue<string | undefined> | undefined, unknown>;
1267
- hydeEmbeddings: import("@langchain/langgraph").BaseChannel<Record<string, number[]>, Record<string, number[]> | import("@langchain/langgraph").OverwriteValue<Record<string, number[]>>, unknown>;
1268
- candidates: import("@langchain/langgraph").BaseChannel<CandidateMatch[], CandidateMatch[] | import("@langchain/langgraph").OverwriteValue<CandidateMatch[]>, unknown>;
1269
- remainingCandidates: import("@langchain/langgraph").BaseChannel<CandidateMatch[], CandidateMatch[] | import("@langchain/langgraph").OverwriteValue<CandidateMatch[]>, unknown>;
1270
- discoveryId: import("@langchain/langgraph").BaseChannel<string | null, string | import("@langchain/langgraph").OverwriteValue<string | null> | null, unknown>;
1271
- evaluatedCandidates: import("@langchain/langgraph").BaseChannel<EvaluatedCandidate[], EvaluatedCandidate[] | import("@langchain/langgraph").OverwriteValue<EvaluatedCandidate[]>, unknown>;
1272
- evaluatedOpportunities: import("@langchain/langgraph").BaseChannel<EvaluatedOpportunity[], EvaluatedOpportunity[] | import("@langchain/langgraph").OverwriteValue<EvaluatedOpportunity[]>, unknown>;
1273
- opportunities: import("@langchain/langgraph").BaseChannel<Opportunity[], Opportunity[] | import("@langchain/langgraph").OverwriteValue<Opportunity[]>, unknown>;
1274
- existingBetweenActors: import("@langchain/langgraph").BaseChannel<{
1275
- candidateUserId: Id<"users">;
1276
- networkId: Id<"networks">;
1277
- existingOpportunityId?: Id<"opportunities">;
1278
- existingStatus?: import("../shared/interfaces/database.interface.js").OpportunityStatus;
1279
- }[], {
1280
- candidateUserId: Id<"users">;
1281
- networkId: Id<"networks">;
1282
- existingOpportunityId?: Id<"opportunities">;
1283
- existingStatus?: import("../shared/interfaces/database.interface.js").OpportunityStatus;
1284
- }[] | import("@langchain/langgraph").OverwriteValue<{
1220
+ persist: {
1221
+ opportunities: never[];
1222
+ persistenceOutcome: {
1223
+ evaluatedCount: number;
1224
+ createdCount: number;
1225
+ reactivatedCount: number;
1226
+ sameTriggerDuplicateSuppressions: number;
1227
+ pairActiveNegotiationSuppressions: number;
1228
+ crossTriggerAllowedCount: number;
1229
+ finalAtomicConflictCount: number;
1230
+ };
1231
+ existingBetweenActors?: undefined;
1232
+ dedupAlreadyAccepted?: undefined;
1233
+ trace?: undefined;
1234
+ error?: undefined;
1235
+ } | {
1236
+ opportunities: never[];
1237
+ persistenceOutcome?: undefined;
1238
+ existingBetweenActors?: undefined;
1239
+ dedupAlreadyAccepted?: undefined;
1240
+ trace?: undefined;
1241
+ error?: undefined;
1242
+ } | {
1243
+ opportunities: Opportunity[];
1244
+ existingBetweenActors: {
1285
1245
  candidateUserId: Id<"users">;
1286
1246
  networkId: Id<"networks">;
1287
1247
  existingOpportunityId?: Id<"opportunities">;
1288
- existingStatus?: import("../shared/interfaces/database.interface.js").OpportunityStatus;
1289
- }[]>, unknown>;
1290
- error: import("@langchain/langgraph").BaseChannel<string | undefined, string | import("@langchain/langgraph").OverwriteValue<string | undefined> | undefined, unknown>;
1291
- readResult: import("@langchain/langgraph").BaseChannel<{
1292
- count: number;
1293
- message?: string;
1294
- opportunities: Array<{
1295
- id: string;
1296
- indexName: string;
1297
- connectedWith: string[];
1298
- suggestedBy: string | null;
1299
- reasoning: string;
1300
- status: string;
1301
- category: string;
1302
- confidence: number | null;
1303
- source: string | null;
1304
- }>;
1305
- } | undefined, {
1306
- count: number;
1307
- message?: string;
1308
- opportunities: Array<{
1309
- id: string;
1310
- indexName: string;
1311
- connectedWith: string[];
1312
- suggestedBy: string | null;
1313
- reasoning: string;
1314
- status: string;
1315
- category: string;
1316
- confidence: number | null;
1317
- source: string | null;
1318
- }>;
1319
- } | import("@langchain/langgraph").OverwriteValue<{
1320
- count: number;
1321
- message?: string;
1322
- opportunities: Array<{
1323
- id: string;
1324
- indexName: string;
1325
- connectedWith: string[];
1326
- suggestedBy: string | null;
1327
- reasoning: string;
1328
- status: string;
1329
- category: string;
1330
- confidence: number | null;
1331
- source: string | null;
1332
- }>;
1333
- } | undefined> | undefined, unknown>;
1334
- mutationResult: import("@langchain/langgraph").BaseChannel<{
1335
- success: boolean;
1336
- message?: string;
1337
- opportunityId?: string;
1338
- notified?: string[];
1339
- conversationId?: string;
1340
- error?: string;
1341
- } | undefined, {
1342
- success: boolean;
1343
- message?: string;
1344
- opportunityId?: string;
1345
- notified?: string[];
1346
- conversationId?: string;
1347
- error?: string;
1348
- } | import("@langchain/langgraph").OverwriteValue<{
1349
- success: boolean;
1350
- message?: string;
1351
- opportunityId?: string;
1352
- notified?: string[];
1353
- conversationId?: string;
1354
- error?: string;
1355
- } | undefined> | undefined, unknown>;
1356
- trace: import("@langchain/langgraph").BaseChannel<{
1357
- node: string;
1358
- detail?: string;
1359
- data?: Record<string, unknown>;
1360
- }[], {
1361
- node: string;
1362
- detail?: string;
1363
- data?: Record<string, unknown>;
1364
- }[] | import("@langchain/langgraph").OverwriteValue<{
1248
+ existingStatus?: OpportunityStatus;
1249
+ reason?: "same_trigger_recent_duplicate" | "pair_active_negotiation" | "final_atomic_conflict";
1250
+ existingTriggerIntentId?: string;
1251
+ }[];
1252
+ dedupAlreadyAccepted: {
1253
+ opportunityId: string;
1254
+ counterpartyUserId: string;
1255
+ }[];
1256
+ persistenceOutcome: {
1257
+ evaluatedCount: number;
1258
+ createdCount: number;
1259
+ reactivatedCount: number;
1260
+ sameTriggerDuplicateSuppressions: number;
1261
+ pairActiveNegotiationSuppressions: number;
1262
+ crossTriggerAllowedCount: number;
1263
+ finalAtomicConflictCount: number;
1264
+ };
1265
+ trace: {
1365
1266
  node: string;
1366
- detail?: string;
1367
- data?: Record<string, unknown>;
1368
- }[]>, unknown>;
1369
- agentTimings: import("@langchain/langgraph").BaseChannel<DebugMetaAgent[], DebugMetaAgent[] | import("@langchain/langgraph").OverwriteValue<DebugMetaAgent[]>, unknown>;
1370
- discoveryNegotiations: import("@langchain/langgraph").BaseChannel<{
1371
- outcome: {
1372
- reasoning: string;
1373
- hasOpportunity: boolean;
1374
- reason?: "timeout" | "turn_cap" | "screened_out" | undefined;
1375
- agreedRoles?: {
1376
- userId: string;
1377
- role: "agent" | "patient" | "peer";
1378
- }[] | undefined;
1379
- };
1380
- counterpartyHint: string;
1381
- indexContext: string;
1382
- counterpartyId: string;
1383
- turns: {
1384
- reasoning: string;
1385
- action: "propose" | "accept" | "reject" | "counter" | "question" | "outreach" | "withdraw" | "decline" | "ask_user";
1386
- suggestedRoles: {
1387
- ownUser: "agent" | "patient" | "peer";
1388
- otherUser: "agent" | "patient" | "peer";
1267
+ detail: string;
1268
+ data: {
1269
+ created: number;
1270
+ reactivated: number;
1271
+ existingSkipped: number;
1272
+ alreadyAccepted: number;
1273
+ totalOutput: number;
1274
+ persistenceOutcome: {
1275
+ evaluatedCount: number;
1276
+ createdCount: number;
1277
+ reactivatedCount: number;
1278
+ sameTriggerDuplicateSuppressions: number;
1279
+ pairActiveNegotiationSuppressions: number;
1280
+ crossTriggerAllowedCount: number;
1281
+ finalAtomicConflictCount: number;
1389
1282
  };
1390
- }[];
1391
- seedAssessmentScore?: number | undefined;
1392
- }[], {
1393
- outcome: {
1394
- reasoning: string;
1395
- hasOpportunity: boolean;
1396
- reason?: "timeout" | "turn_cap" | "screened_out" | undefined;
1397
- agreedRoles?: {
1398
- userId: string;
1399
- role: "agent" | "patient" | "peer";
1400
- }[] | undefined;
1283
+ durationMs: number;
1401
1284
  };
1402
- counterpartyHint: string;
1403
- indexContext: string;
1404
- counterpartyId: string;
1405
- turns: {
1406
- reasoning: string;
1407
- action: "propose" | "accept" | "reject" | "counter" | "question" | "outreach" | "withdraw" | "decline" | "ask_user";
1408
- suggestedRoles: {
1409
- ownUser: "agent" | "patient" | "peer";
1410
- otherUser: "agent" | "patient" | "peer";
1411
- };
1412
- }[];
1413
- seedAssessmentScore?: number | undefined;
1414
- }[] | import("@langchain/langgraph").OverwriteValue<{
1415
- outcome: {
1416
- reasoning: string;
1417
- hasOpportunity: boolean;
1418
- reason?: "timeout" | "turn_cap" | "screened_out" | undefined;
1419
- agreedRoles?: {
1420
- userId: string;
1421
- role: "agent" | "patient" | "peer";
1422
- }[] | undefined;
1285
+ }[];
1286
+ error?: undefined;
1287
+ } | {
1288
+ opportunities: never[];
1289
+ existingBetweenActors: never[];
1290
+ error: string;
1291
+ trace: {
1292
+ node: string;
1293
+ detail: string;
1294
+ data: {
1295
+ error: string;
1423
1296
  };
1424
- counterpartyHint: string;
1425
- indexContext: string;
1426
- counterpartyId: string;
1427
- turns: {
1428
- reasoning: string;
1429
- action: "propose" | "accept" | "reject" | "counter" | "question" | "outreach" | "withdraw" | "decline" | "ask_user";
1430
- suggestedRoles: {
1431
- ownUser: "agent" | "patient" | "peer";
1432
- otherUser: "agent" | "patient" | "peer";
1433
- };
1434
- }[];
1435
- seedAssessmentScore?: number | undefined;
1436
- }[]>, unknown>;
1437
- discoverySummary: import("@langchain/langgraph").BaseChannel<import("../index.js").DiscoverySummary | null, import("../index.js").DiscoverySummary | import("@langchain/langgraph").OverwriteValue<import("../index.js").DiscoverySummary | null> | null, unknown>;
1438
- }>;
1297
+ }[];
1298
+ persistenceOutcome?: undefined;
1299
+ dedupAlreadyAccepted?: undefined;
1300
+ };
1439
1301
  read: {
1440
1302
  readResult: {
1441
1303
  count: number;
@@ -1446,7 +1308,7 @@ export declare class OpportunityGraphFactory {
1446
1308
  connectedWith: string[];
1447
1309
  suggestedBy: string | null;
1448
1310
  reasoning: string;
1449
- status: import("../shared/interfaces/database.interface.js").OpportunityStatus;
1311
+ status: OpportunityStatus;
1450
1312
  category: string;
1451
1313
  confidence: number | null;
1452
1314
  source: string | null;