import type { Id, NegotiationContinuationExecution, NegotiationContinuationReceipt, OpportunityStatus, Opportunity } from '../../shared/interfaces/database.interface.js'; import type { Lens } from '../../shared/interfaces/embedder.interface.js'; import type { EvaluatorEntity } from '../application/opportunity.evaluator.js'; import type { DebugMetaAgent } from '../../capabilities/participant-agents.debug.facade.js'; import type { OpportunityEvidence } from '../../shared/schemas/network-assignment.schema.js'; import type { DiscoverySummary } from "../../shared/schemas/discovery-question.schema.js"; /** * Opportunity Graph State (Linear Multi-Step Workflow) * * Flow: Prep → Scope → Discovery → Evaluation → Ranking → Persist → END * * Following the intent graph pattern with Annotation-based state management. */ /** Asker's profile shape (identity + context). Used by sourceProfile annotation. */ export interface SourceProfileData { identity?: { name?: string; bio?: string; location?: string; }; context?: string; } /** * Indexed intent with hyde document (from prep node) */ export interface IndexedIntent { intentId: Id<'intents'>; payload: string; summary?: string; hydeDocumentId?: string; hydeEmbedding?: number[]; indexes: Id<'networks'>[]; } /** * Target index for search (from scope node) */ export interface TargetNetwork { networkId: Id<'networks'>; title: string; memberCount: number; } /** * Candidate match from discovery (semantic search). */ export interface CandidateMatch { candidateUserId: Id<'users'>; candidateIntentId?: Id<'intents'>; /** Source premise that produced this candidate (set when discoverySource is 'premise-similarity'). */ sourcePremiseId?: Id<'premises'>; /** Candidate premise that matched this candidate (set for premise-based matches). */ candidatePremiseId?: Id<'premises'>; /** Source context that produced this candidate (set when discoverySource is 'context-to-intent'). */ sourceContextId?: string; /** Candidate context that matched this candidate (set for user_context-based matches). */ candidateContextId?: string; networkId: Id<'networks'>; similarity: number; /** Free-text lens label that produced this match. */ lens: string; candidatePayload: string; candidateSummary?: string; /** How this candidate was found: 'query' (HyDE from search text), 'premise-similarity', 'context-to-intent', or 'context-similarity'. */ discoverySource?: 'query' | 'premise-similarity' | 'context-to-intent' | 'context-similarity'; /** Which discovery strategies found this candidate (set by mergeStrategyCandidates). */ matchedStrategies?: string[]; /** Typed evidence that explains why this candidate entered evaluation. */ evidence?: OpportunityEvidence[]; } /** * Evaluated candidate with LLM scoring (legacy; used when evaluator returns source/candidate pair). * candidateIntentId is set for intent matches; omitted for profile-only matches. */ export interface EvaluatedCandidate { sourceUserId: Id<'users'>; candidateUserId: Id<'users'>; sourceIntentId?: Id<'intents'>; candidateIntentId?: Id<'intents'>; networkId: Id<'networks'>; score: number; reasoning: string; valencyRole: 'Agent' | 'Patient' | 'Peer'; /** Free-text lens label that produced this match. */ lens: string; } /** * Actor in an evaluated opportunity (from entity-bundle evaluator). * networkId is filled from the entity bundle in the graph, not by the evaluator. */ export interface EvaluatedOpportunityActor { userId: Id<'users'>; role: 'agent' | 'patient' | 'peer'; intentId?: Id<'intents'>; networkId: Id<'networks'>; } /** * Evaluated opportunity with multi-actor output (entity-bundle evaluator). */ export interface EvaluatedOpportunity { actors: EvaluatedOpportunityActor[]; score: number; reasoning: string; evidence?: OpportunityEvidence[]; } export interface OpportunityPersistenceOutcome { evaluatedCount: number; createdCount: number; reactivatedCount: number; sameTriggerDuplicateSuppressions: number; pairActiveNegotiationSuppressions: number; crossTriggerAllowedCount: number; finalAtomicConflictCount: number; } /** * Options passed to the graph */ export interface OpportunityGraphOptions { /** Exact durable ask_user settlement being resumed; internal queue path only. */ negotiationContinuation?: NegotiationContinuationExecution; /** Initial status for created opportunities (default: 'pending') */ initialStatus?: OpportunityStatus; /** Maximum opportunities to return (default: 20) */ limit?: number; /** Pre-inferred lenses (if not provided, lens inference runs automatically in HyDE graph) */ lenses?: Lens[]; /** User's search query for HyDE generation */ hydeDescription?: string; /** Existing opportunities summary for evaluator deduplication */ existingOpportunities?: string; /** Chat session ID for draft opportunities; stored as context.conversationId for visibility filtering. */ conversationId?: string; /** * Cap the negotiate-phase wall-clock at this many milliseconds. * When set, `negotiateNode` races `negotiateCandidates(...)` against a timer; * if the timer wins, the node returns early with a `timed_out` trace and the * unawaited negotiation chains finalize each opportunity's DB status in the * background. Foreground callers omit this; background matching supplies it * only where its bounded execution requires it. */ negotiateTimeoutMs?: number; } /** * Opportunity Graph State Annotation */ export declare const OpportunityGraphState: import("@langchain/langgraph").AnnotationRoot<{ userId: import("@langchain/langgraph").BaseChannel, Id<"users"> | import("@langchain/langgraph").OverwriteValue>, unknown>; searchQuery: import("@langchain/langgraph").BaseChannel | undefined, unknown>; networkId: import("@langchain/langgraph").BaseChannel | undefined, Id<"networks"> | import("@langchain/langgraph").OverwriteValue | undefined> | undefined, unknown>; /** * Optional set of indexes discovery may search within (e.g. a network-scoped * agent's reachable networks: the bound network plus the user's personal network). * The scope node intersects this with the user's actual memberships. Ignored * when `networkId` is set (single-network override). When unset, discovery * spans all of the user's networks. */ indexScope: import("@langchain/langgraph").BaseChannel[] | undefined, Id<"networks">[] | import("@langchain/langgraph").OverwriteValue[] | undefined> | undefined, unknown>; /** Optional intent to use as discovery source and for triggeredBy. When set, used for search text (if query empty) and persist. */ triggerIntentId: import("@langchain/langgraph").BaseChannel | undefined, Id<"intents"> | import("@langchain/langgraph").OverwriteValue | undefined> | undefined, unknown>; /** Optional: restrict discovery to this specific user ID only (direct connection). */ targetUserId: import("@langchain/langgraph").BaseChannel | undefined, Id<"users"> | import("@langchain/langgraph").OverwriteValue | undefined> | undefined, unknown>; /** Optional: discover on behalf of this user (introducer flow). When set, prep/eval use this user's profile/intents; userId becomes the introducer. */ onBehalfOfUserId: import("@langchain/langgraph").BaseChannel | undefined, Id<"users"> | import("@langchain/langgraph").OverwriteValue | undefined> | undefined, unknown>; options: import("@langchain/langgraph").BaseChannel, unknown>; /** * Operation mode controls graph flow: * - 'create': Existing discover pipeline (Prep → Scope → Discovery → Evaluation → Ranking → Persist) * - 'create_introduction': Introduction path (validation → evaluation → persist) for chat-driven intros * - 'continue_discovery': Pagination path (Prep → Evaluation → Ranking → Persist) using pre-loaded candidates * - 'read': List opportunities filtered by userId and optionally networkId (fast path) * - 'update': Change opportunity status (accept, reject, etc.) * - 'delete': Expire/archive an opportunity * - 'send': Promote latent opportunity to pending + queue notification * - 'negotiate_existing': Load an existing opportunity by opportunityId and run bilateral negotiation. * Used after introducer approval to trigger the normal negotiation flow. * - 'approve_introduction': Mark the caller as having approved a latent introducer opportunity, * then enqueue a negotiate_existing job for that opportunity. * * Defaults to 'create' for backward compatibility. */ operationMode: import("@langchain/langgraph").BaseChannel<"delete" | "send" | "update" | "create" | "read" | "create_introduction" | "continue_discovery" | "negotiate_existing" | "approve_introduction", "delete" | "send" | "update" | "create" | "read" | "create_introduction" | "continue_discovery" | "negotiate_existing" | "approve_introduction" | import("@langchain/langgraph").OverwriteValue<"delete" | "send" | "update" | "create" | "read" | "create_introduction" | "continue_discovery" | "negotiate_existing" | "approve_introduction">, unknown>; /** Introduction mode: pre-gathered entities (profiles + intents per party). */ introductionEntities: import("@langchain/langgraph").BaseChannel, unknown>; /** Introduction mode: optional hint from the introducer. */ introductionHint: import("@langchain/langgraph").BaseChannel | undefined, unknown>; /** When set (e.g. chat scope), networkId must match this. */ requiredNetworkId: import("@langchain/langgraph").BaseChannel | undefined, Id<"networks"> | import("@langchain/langgraph").OverwriteValue | undefined> | undefined, unknown>; /** Set by intro_evaluation; used by persist to build manual detection and introducer actor. */ introductionContext: import("@langchain/langgraph").BaseChannel<{ createdByName?: string; } | undefined, { createdByName?: string; } | import("@langchain/langgraph").OverwriteValue<{ createdByName?: string; } | undefined> | undefined, unknown>; /** Target opportunity ID for update/delete/send modes. */ opportunityId: import("@langchain/langgraph").BaseChannel | undefined, unknown>; /** New status for update mode (e.g. 'accepted', 'rejected'). */ newStatus: import("@langchain/langgraph").BaseChannel | undefined, unknown>; /** User's indexed intents with hyde documents (from prep) */ indexedIntents: import("@langchain/langgraph").BaseChannel, unknown>; /** User's network memberships (from prep) */ userNetworks: import("@langchain/langgraph").BaseChannel[], Id<"networks">[] | import("@langchain/langgraph").OverwriteValue[]>, unknown>; /** Target indexes to search within (from scope) */ targetNetworks: import("@langchain/langgraph").BaseChannel, unknown>; /** Per-index relevancy scores for dedup tie-breaking. Background path: from intent_indexes. Chat path: transient from IntentIndexer. */ indexRelevancyScores: import("@langchain/langgraph").BaseChannel, Record | import("@langchain/langgraph").OverwriteValue>, unknown>; /** Whether discovery used intent (path A) or user context (path B/C). Used by persist for triggeredBy. In-memory routing state only; never persisted. */ discoverySource: import("@langchain/langgraph").BaseChannel<"intent" | "context", "intent" | "context" | import("@langchain/langgraph").OverwriteValue<"intent" | "context">, unknown>; /** Resolved intent ID used for this discovery run (when discoverySource is 'intent'). Set by intent-resolution. */ resolvedTriggerIntentId: import("@langchain/langgraph").BaseChannel | undefined, Id<"intents"> | import("@langchain/langgraph").OverwriteValue | undefined> | undefined, unknown>; /** Asker's profile (from prep). Used for profile-as-source discovery and evaluation. */ sourceProfile: import("@langchain/langgraph").BaseChannel | null, unknown>; /** User's active premises with embeddings (from prep). Used for premise-to-premise discovery path D. */ sourcePremises: import("@langchain/langgraph").BaseChannel<{ premiseId: Id<"premises">; embedding: number[]; }[], { premiseId: Id<"premises">; embedding: number[]; }[] | import("@langchain/langgraph").OverwriteValue<{ premiseId: Id<"premises">; embedding: number[]; }[]>, unknown>; /** User context embeddings per network (from prep). Used for context-to-intent discovery. */ sourceContexts: import("@langchain/langgraph").BaseChannel<{ contextId: string; networkId: Id<"networks">; text: string; embedding: number[]; }[], { contextId: string; networkId: Id<"networks">; text: string; embedding: number[]; }[] | import("@langchain/langgraph").OverwriteValue<{ contextId: string; networkId: Id<"networks">; text: string; embedding: number[]; }[]>, unknown>; /** Resolved intent is in at least one target index (path A vs C). */ resolvedIntentInIndex: import("@langchain/langgraph").BaseChannel, unknown>; /** Create-intent signal: when true, tool should return createIntentSuggested so agent can auto-call create_intent. */ createIntentSuggested: import("@langchain/langgraph").BaseChannel, unknown>; /** Suggested description for create_intent when createIntentSuggested is true. */ suggestedIntentDescription: import("@langchain/langgraph").BaseChannel | undefined, unknown>; /** HyDE embeddings per lens label (from discovery) */ hydeEmbeddings: import("@langchain/langgraph").BaseChannel, Record | import("@langchain/langgraph").OverwriteValue>, unknown>; /** Candidate matches from semantic search (from discovery) */ candidates: import("@langchain/langgraph").BaseChannel, unknown>; /** Candidates not yet evaluated (for pagination -- cached in Redis by caller). */ remainingCandidates: import("@langchain/langgraph").BaseChannel, unknown>; /** Discovery session ID for pagination (maps to Redis cache key). */ discoveryId: import("@langchain/langgraph").BaseChannel | null, unknown>; /** Evaluated candidates with scores (from evaluation; legacy) */ evaluatedCandidates: import("@langchain/langgraph").BaseChannel, unknown>; /** Evaluated opportunities with actors (from entity-bundle evaluator) */ evaluatedOpportunities: import("@langchain/langgraph").BaseChannel, unknown>; /** Final ranked and persisted opportunities */ opportunities: import("@langchain/langgraph").BaseChannel, unknown>; /** Discovery path: pairs skipped because an opportunity already exists between viewer and candidate (no duplicate created). */ existingBetweenActors: import("@langchain/langgraph").BaseChannel<{ candidateUserId: Id<"users">; networkId: Id<"networks">; existingOpportunityId?: Id<"opportunities">; existingStatus?: OpportunityStatus; reason?: "same_trigger_recent_duplicate" | "pair_active_negotiation" | "final_atomic_conflict"; existingTriggerIntentId?: string; }[], { candidateUserId: Id<"users">; networkId: Id<"networks">; existingOpportunityId?: Id<"opportunities">; existingStatus?: OpportunityStatus; reason?: "same_trigger_recent_duplicate" | "pair_active_negotiation" | "final_atomic_conflict"; existingTriggerIntentId?: string; }[] | import("@langchain/langgraph").OverwriteValue<{ candidateUserId: Id<"users">; networkId: Id<"networks">; existingOpportunityId?: Id<"opportunities">; existingStatus?: OpportunityStatus; reason?: "same_trigger_recent_duplicate" | "pair_active_negotiation" | "final_atomic_conflict"; existingTriggerIntentId?: string; }[]>, unknown>; /** Typed persist-node counts used by queue telemetry. */ persistenceOutcome: import("@langchain/langgraph").BaseChannel | undefined, unknown>; /** Positive exact-successor receipt for a fenced continuation. */ negotiationContinuationReceipt: import("@langchain/langgraph").BaseChannel | undefined, unknown>; /** Error message if any step fails */ error: import("@langchain/langgraph").BaseChannel | undefined, unknown>; /** Output for read mode: enriched list of opportunities. */ readResult: import("@langchain/langgraph").BaseChannel<{ count: number; message?: string; opportunities: Array<{ id: string; indexName: string; connectedWith: string[]; suggestedBy: string | null; reasoning: string; status: string; category: string; confidence: number | null; source: string | null; }>; } | undefined, { count: number; message?: string; opportunities: Array<{ id: string; indexName: string; connectedWith: string[]; suggestedBy: string | null; reasoning: string; status: string; category: string; confidence: number | null; source: string | null; }>; } | import("@langchain/langgraph").OverwriteValue<{ count: number; message?: string; opportunities: Array<{ id: string; indexName: string; connectedWith: string[]; suggestedBy: string | null; reasoning: string; status: string; category: string; confidence: number | null; source: string | null; }>; } | undefined> | undefined, unknown>; /** Output for update/delete/send modes. */ mutationResult: import("@langchain/langgraph").BaseChannel<{ success: boolean; message?: string; opportunityId?: string; notified?: string[]; conversationId?: string; error?: string; } | undefined, { success: boolean; message?: string; opportunityId?: string; notified?: string[]; conversationId?: string; error?: string; } | import("@langchain/langgraph").OverwriteValue<{ success: boolean; message?: string; opportunityId?: string; notified?: string[]; conversationId?: string; error?: string; } | undefined> | undefined, unknown>; /** * Accumulated trace entries from each graph node. * Used for observability: surfaces internal processing steps (search query, HyDE strategies, * candidates found, evaluation results) to the frontend. */ trace: import("@langchain/langgraph").BaseChannel<{ node: string; detail?: string; data?: Record; }[], { node: string; detail?: string; data?: Record; }[] | import("@langchain/langgraph").OverwriteValue<{ node: string; detail?: string; data?: Record; }[]>, unknown>; /** Timing records for each agent invocation within this graph run. */ agentTimings: import("@langchain/langgraph").BaseChannel, unknown>; /** * Per-candidate negotiation records captured by `negotiateNode`. Populated * regardless of accept/reject so the question generator sees a complete * picture. Empty when the negotiate node was skipped (no opportunities to * negotiate). */ discoveryNegotiations: import("@langchain/langgraph").BaseChannel<{ counterpartyHint: string; indexContext: string; counterpartyId: string; turns: { reasoning: string; suggestedRoles: { ownUser: "agent" | "patient" | "peer"; otherUser: "agent" | "patient" | "peer"; }; action: "propose" | "accept" | "reject" | "counter" | "question" | "outreach" | "withdraw" | "decline" | "ask_user"; }[]; outcome: { reasoning: string; hasOpportunity: boolean; reason?: "timeout" | "turn_cap" | "screened_out" | undefined; agreedRoles?: { userId: string; role: "agent" | "patient" | "peer"; }[] | undefined; }; seedAssessmentScore?: number | undefined; }[], { counterpartyHint: string; indexContext: string; counterpartyId: string; turns: { reasoning: string; suggestedRoles: { ownUser: "agent" | "patient" | "peer"; otherUser: "agent" | "patient" | "peer"; }; action: "propose" | "accept" | "reject" | "counter" | "question" | "outreach" | "withdraw" | "decline" | "ask_user"; }[]; outcome: { reasoning: string; hasOpportunity: boolean; reason?: "timeout" | "turn_cap" | "screened_out" | undefined; agreedRoles?: { userId: string; role: "agent" | "patient" | "peer"; }[] | undefined; }; seedAssessmentScore?: number | undefined; }[] | import("@langchain/langgraph").OverwriteValue<{ counterpartyHint: string; indexContext: string; counterpartyId: string; turns: { reasoning: string; suggestedRoles: { ownUser: "agent" | "patient" | "peer"; otherUser: "agent" | "patient" | "peer"; }; action: "propose" | "accept" | "reject" | "counter" | "question" | "outreach" | "withdraw" | "decline" | "ask_user"; }[]; outcome: { reasoning: string; hasOpportunity: boolean; reason?: "timeout" | "turn_cap" | "screened_out" | undefined; agreedRoles?: { userId: string; role: "agent" | "patient" | "peer"; }[] | undefined; }; seedAssessmentScore?: number | undefined; }[]>, unknown>; /** Aggregate counters across `discoveryNegotiations`. Built in the negotiate node. */ discoverySummary: import("@langchain/langgraph").BaseChannel | null, unknown>; }>;