@indexnetwork/protocol 6.6.2-rc.388.1 → 6.6.4-rc.390.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 +2 -0
- package/dist/negotiation/negotiation.graph.d.ts +8 -2
- package/dist/negotiation/negotiation.graph.d.ts.map +1 -1
- package/dist/negotiation/negotiation.graph.js +19 -4
- package/dist/negotiation/negotiation.graph.js.map +1 -1
- package/dist/negotiation/negotiation.state.d.ts +5 -3
- package/dist/negotiation/negotiation.state.d.ts.map +1 -1
- package/dist/negotiation/negotiation.state.js +5 -1
- package/dist/negotiation/negotiation.state.js.map +1 -1
- package/dist/opportunity/opportunity.enricher.d.ts +5 -0
- package/dist/opportunity/opportunity.enricher.d.ts.map +1 -1
- package/dist/opportunity/opportunity.enricher.js +17 -1
- package/dist/opportunity/opportunity.enricher.js.map +1 -1
- package/dist/opportunity/opportunity.graph.d.ts +109 -247
- package/dist/opportunity/opportunity.graph.d.ts.map +1 -1
- package/dist/opportunity/opportunity.graph.js +199 -4
- package/dist/opportunity/opportunity.graph.js.map +1 -1
- package/dist/opportunity/opportunity.persist.d.ts +14 -1
- package/dist/opportunity/opportunity.persist.d.ts.map +1 -1
- package/dist/opportunity/opportunity.persist.js +33 -4
- package/dist/opportunity/opportunity.persist.js.map +1 -1
- package/dist/opportunity/opportunity.state.d.ts +17 -0
- package/dist/opportunity/opportunity.state.d.ts.map +1 -1
- package/dist/opportunity/opportunity.state.js +5 -0
- package/dist/opportunity/opportunity.state.js.map +1 -1
- package/dist/shared/interfaces/database.interface.d.ts +29 -5
- package/dist/shared/interfaces/database.interface.d.ts.map +1 -1
- package/dist/shared/interfaces/database.interface.js.map +1 -1
- package/package.json +1 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"negotiation.state.js","sourceRoot":"/","sources":["negotiation/negotiation.state.ts"],"names":[],"mappings":"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","sourcesContent":["import { Annotation } from \"@langchain/langgraph\";\nimport { z } from \"zod\";\nimport type { NegotiationUserAnswer } from \"../shared/interfaces/database.interface.js\";\nimport type { ScreenDecisionRecord } from \"./negotiation.screen.js\";\nimport type { DeadlockShiftRecord } from \"./negotiation.deadlock.js\";\nimport type { NegotiatorMemoryEntry } from \"./negotiation.memory.js\";\nimport { AskUserPayloadSchema, NEGOTIATION_ACTIONS, type NegotiationProtocolVersion } from \"../shared/schemas/negotiation-state.schema.js\";\n\n/**\n * Zod schema for a single negotiation turn (DataPart payload in A2A message).\n * Accepts the full v1+v2 action union — which subset is valid for a given turn\n * is enforced by the seat-scoped schemas in `negotiation.protocol.ts`.\n */\nexport const NegotiationTurnSchema = z.object({\n action: z.enum(NEGOTIATION_ACTIONS),\n assessment: z.object({\n reasoning: z.string(),\n suggestedRoles: z.object({\n ownUser: z.enum([\"agent\", \"patient\", \"peer\"]),\n otherUser: z.enum([\"agent\", \"patient\", \"peer\"]),\n }),\n }),\n message: z.string().nullable().optional(),\n /** Present when action is `ask_user` (v2, P3.2). */\n askUser: AskUserPayloadSchema.nullable().optional(),\n});\n\n/** Restricted v1 turn schema for the system agent (no question action). */\nexport const SystemNegotiationTurnSchema = z.object({\n action: z.enum([\"propose\", \"accept\", \"reject\", \"counter\"]),\n assessment: z.object({\n reasoning: z.string(),\n suggestedRoles: z.object({\n ownUser: z.enum([\"agent\", \"patient\", \"peer\"]),\n otherUser: z.enum([\"agent\", \"patient\", \"peer\"]),\n }),\n }),\n message: z.string().nullable().optional(),\n});\n\n/** v1 turn schema for system agent's final allowed turn (must decide). */\nexport const FinalNegotiationTurnSchema = z.object({\n action: z.enum([\"accept\", \"reject\"]),\n assessment: z.object({\n reasoning: z.string(),\n suggestedRoles: z.object({\n ownUser: z.enum([\"agent\", \"patient\", \"peer\"]),\n otherUser: z.enum([\"agent\", \"patient\", \"peer\"]),\n }),\n }),\n message: z.string().nullable().optional(),\n});\n\nexport type NegotiationTurn = z.infer<typeof NegotiationTurnSchema>;\n\n/** Zod schema for the negotiation outcome (Artifact payload on COMPLETED task). */\nexport const NegotiationOutcomeSchema = z.object({\n hasOpportunity: z.boolean(),\n agreedRoles: z.array(z.object({\n userId: z.string(),\n role: z.enum([\"agent\", \"patient\", \"peer\"]),\n })),\n reasoning: z.string(),\n turnCount: z.number(),\n reason: z.enum([\"turn_cap\", \"timeout\", \"screened_out\"]).optional(),\n});\n\nexport type NegotiationOutcome = z.infer<typeof NegotiationOutcomeSchema>;\n\n/** Context each agent receives about its user. */\nexport interface UserNegotiationContext {\n id: string;\n intents: Array<{ id: string; title: string; description: string; confidence: number }>;\n profile: { name?: string; bio?: string; location?: string; interests?: string[]; skills?: string[] };\n}\n\n/** Seed assessment from the evaluator pre-filter. */\nexport interface SeedAssessment {\n reasoning: string;\n valencyRole: string;\n actors?: Array<{ userId: string; role: string }>;\n}\n\n/** Typed interface for a negotiation graph's invoke signature. */\nexport interface NegotiationGraphLike {\n invoke(input: {\n sourceUser: UserNegotiationContext;\n candidateUser: UserNegotiationContext;\n indexContext: { networkId: string; prompt: string };\n seedAssessment: Omit<SeedAssessment, \"actors\">;\n discoveryQuery?: string;\n opportunityId?: string;\n /** Exact persisted lifecycle version claimed by this negotiation attempt. */\n opportunityUpdatedAt?: Date;\n maxTurns?: number;\n timeoutMs?: number;\n /**\n * The user who holds the initiating seat for this match (v2 client-advocate\n * protocol). Stamped into task metadata by the init node. When omitted, the\n * init node resolves it: inherit from the prior task for the same\n * opportunity → conversation-scoped tie-break → fall back to sourceUser.id.\n */\n initiatorUserId?: string;\n }): Promise<{\n outcome: NegotiationOutcome | null;\n messages?: NegotiationMessage[];\n conversationId?: string;\n isContinuation?: boolean;\n priorTurnCount?: number;\n }>;\n}\n\n/** A2A message record shape (matches messages table). */\nexport interface NegotiationMessage {\n id: string;\n senderId: string;\n role: \"agent\";\n parts: unknown[];\n createdAt: Date;\n}\n\n/** LangGraph state annotation for the negotiation graph. */\nexport const NegotiationGraphState = Annotation.Root({\n sourceUser: Annotation<UserNegotiationContext>({\n reducer: (curr, next) => next ?? curr,\n default: () => ({ id: \"\", intents: [], profile: {} }),\n }),\n candidateUser: Annotation<UserNegotiationContext>({\n reducer: (curr, next) => next ?? curr,\n default: () => ({ id: \"\", intents: [], profile: {} }),\n }),\n indexContext: Annotation<{ networkId: string; prompt: string }>({\n reducer: (curr, next) => next ?? curr,\n default: () => ({ networkId: \"\", prompt: \"\" }),\n }),\n seedAssessment: Annotation<SeedAssessment>({\n reducer: (curr, next) => next ?? curr,\n default: () => ({ reasoning: \"\", valencyRole: \"\" }),\n }),\n\n /**\n * Explicit initiator seat for this match (purely additive metadata — no seat\n * rules attach to it yet). Resolution when unset happens in the init node;\n * the resolved value is written back to state and into task metadata.\n */\n initiatorUserId: Annotation<string | undefined>({\n reducer: (curr, next) => next ?? curr,\n default: () => undefined,\n }),\n\n /** The explicit search query that triggered discovery (if any). */\n discoveryQuery: Annotation<string | undefined>({\n reducer: (curr, next) => next ?? curr,\n default: () => undefined,\n }),\n /**\n * Negotiation protocol version for this session's task. Resolved by the\n * init node: inherited from the prior task on the conversation when one\n * exists (never re-stamped — a v1 conversation stays v1 mid-flight), else\n * stamped from `NEGOTIATION_PROTOCOL_VERSION` for genuinely fresh runs.\n */\n protocolVersion: Annotation<NegotiationProtocolVersion>({\n reducer: (curr, next) => next ?? curr,\n default: () => \"v1\" as const,\n }),\n\n /**\n * Screen-gate decision for this fresh run (P2.1 shadow mode). Written by the\n * screen node; null when the gate is off, on continuations, or before the\n * node runs. Mirrors `tasks.metadata.screenDecision`.\n */\n screenDecision: Annotation<ScreenDecisionRecord | null>({\n reducer: (curr, next) => next ?? curr,\n default: () => null,\n }),\n\n /**\n * First applied deadlock→bargaining shift in this session (IND-428).\n * Written by the turn node when the system agent first drafts in the\n * bargaining stance; used to record the shift exactly once per session.\n * Internal analytics only — mirrored to `tasks.metadata.deadlockShift`,\n * never into any turn payload or public projection.\n */\n deadlockShift: Annotation<DeadlockShiftRecord | null>({\n reducer: (curr, next) => next ?? curr,\n default: () => null,\n }),\n\n /**\n * Per-side negotiator-memory cache (P5.3 read path). Populated lazily the\n * first time each side's memory is retrieved (screen node for the client,\n * turn node for the speaker) so a multi-turn session pays for retrieval at\n * most once per side. `undefined` per side = not yet retrieved; `[]` =\n * retrieved and empty (flag off / no rows / retrieval failed).\n */\n memoryBySide: Annotation<Partial<Record<\"source\" | \"candidate\", NegotiatorMemoryEntry[]>>>({\n reducer: (curr, next) => ({ ...curr, ...next }),\n default: () => ({}),\n }),\n\n /** Whether this run is continuing a prior conversation with the same pair. */\n isContinuation: Annotation<boolean>({\n reducer: (curr, next) => next ?? curr,\n default: () => false,\n }),\n opportunityId: Annotation<string>({\n reducer: (curr, next) => next ?? curr,\n default: () => \"\",\n }),\n /** Exact persisted lifecycle version claimed by this negotiation attempt. */\n opportunityUpdatedAt: Annotation<Date | undefined>({\n reducer: (curr, next) => next ?? curr,\n default: () => undefined,\n }),\n conversationId: Annotation<string>({\n reducer: (curr, next) => next ?? curr,\n default: () => \"\",\n }),\n taskId: Annotation<string>({\n reducer: (curr, next) => next ?? curr,\n default: () => \"\",\n }),\n messages: Annotation<NegotiationMessage[]>({\n reducer: (curr, next) => [...curr, ...(next || [])],\n default: () => [],\n }),\n turnCount: Annotation<number>({\n reducer: (curr, next) => next ?? curr,\n default: () => 0,\n }),\n maxTurns: Annotation<number | undefined>({\n reducer: (curr, next) => next ?? curr,\n default: () => undefined,\n }),\n /**\n * Park-window budget in milliseconds. Ambient callers pass `AMBIENT_PARK_WINDOW_MS`\n * (5 minutes); orchestrator callers pass a shorter window. This annotation default\n * is a safety net for any caller that omits the field — keep it aligned with\n * `AMBIENT_PARK_WINDOW_MS` in packages/protocol/src/negotiation/negotiation.tools.ts.\n * Inlined rather than imported to avoid a state↔tools cycle.\n */\n timeoutMs: Annotation<number>({\n reducer: (curr, next) => next ?? curr,\n default: () => 5 * 60 * 1000,\n }),\n\n currentSpeaker: Annotation<\"source\" | \"candidate\">({\n reducer: (curr, next) => next ?? curr,\n default: () => \"source\" as const,\n }),\n lastTurn: Annotation<NegotiationTurn | null>({\n reducer: (curr, next) => next ?? curr,\n default: () => null,\n }),\n\n /**\n * Graph status.\n * - `active` — agents are exchanging turns (default)\n * - `waiting_for_agent` — graph suspended; awaiting external agent response or timeout\n * - `input_required` — graph suspended on an `ask_user` pause; awaiting the\n * negotiator's own client (answer or 24 h window expiry resumes it)\n * - `completed` — negotiation finalized (accept/reject/turn-cap/timeout)\n */\n status: Annotation<'active' | 'waiting_for_agent' | 'input_required' | 'completed'>({\n reducer: (curr, next) => next ?? curr,\n default: () => 'active' as const,\n }),\n\n /** Number of turns present in the conversation before this session started. */\n priorTurnCount: Annotation<number>({\n reducer: (curr, next) => next ?? curr,\n default: () => 0,\n }),\n\n /** User answers collected by the questioner between negotiation sessions. */\n userAnswers: Annotation<NegotiationUserAnswer[]>({\n reducer: (curr, next) => next ?? curr,\n default: () => [],\n }),\n\n outcome: Annotation<NegotiationOutcome | null>({\n reducer: (curr, next) => next ?? curr,\n default: () => null,\n }),\n error: Annotation<string | null>({\n reducer: (curr, next) => next ?? curr,\n default: () => null,\n }),\n});\n"]}
|
|
1
|
+
{"version":3,"file":"negotiation.state.js","sourceRoot":"/","sources":["negotiation/negotiation.state.ts"],"names":[],"mappings":"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","sourcesContent":["import { Annotation } from \"@langchain/langgraph\";\nimport { z } from \"zod\";\nimport type { NegotiationUserAnswer, OpportunityStatus } from \"../shared/interfaces/database.interface.js\";\nimport type { ScreenDecisionRecord } from \"./negotiation.screen.js\";\nimport type { DeadlockShiftRecord } from \"./negotiation.deadlock.js\";\nimport type { NegotiatorMemoryEntry } from \"./negotiation.memory.js\";\nimport { AskUserPayloadSchema, NEGOTIATION_ACTIONS, type NegotiationProtocolVersion } from \"../shared/schemas/negotiation-state.schema.js\";\n\n/**\n * Zod schema for a single negotiation turn (DataPart payload in A2A message).\n * Accepts the full v1+v2 action union — which subset is valid for a given turn\n * is enforced by the seat-scoped schemas in `negotiation.protocol.ts`.\n */\nexport const NegotiationTurnSchema = z.object({\n action: z.enum(NEGOTIATION_ACTIONS),\n assessment: z.object({\n reasoning: z.string(),\n suggestedRoles: z.object({\n ownUser: z.enum([\"agent\", \"patient\", \"peer\"]),\n otherUser: z.enum([\"agent\", \"patient\", \"peer\"]),\n }),\n }),\n message: z.string().nullable().optional(),\n /** Present when action is `ask_user` (v2, P3.2). */\n askUser: AskUserPayloadSchema.nullable().optional(),\n});\n\n/** Restricted v1 turn schema for the system agent (no question action). */\nexport const SystemNegotiationTurnSchema = z.object({\n action: z.enum([\"propose\", \"accept\", \"reject\", \"counter\"]),\n assessment: z.object({\n reasoning: z.string(),\n suggestedRoles: z.object({\n ownUser: z.enum([\"agent\", \"patient\", \"peer\"]),\n otherUser: z.enum([\"agent\", \"patient\", \"peer\"]),\n }),\n }),\n message: z.string().nullable().optional(),\n});\n\n/** v1 turn schema for system agent's final allowed turn (must decide). */\nexport const FinalNegotiationTurnSchema = z.object({\n action: z.enum([\"accept\", \"reject\"]),\n assessment: z.object({\n reasoning: z.string(),\n suggestedRoles: z.object({\n ownUser: z.enum([\"agent\", \"patient\", \"peer\"]),\n otherUser: z.enum([\"agent\", \"patient\", \"peer\"]),\n }),\n }),\n message: z.string().nullable().optional(),\n});\n\nexport type NegotiationTurn = z.infer<typeof NegotiationTurnSchema>;\n\n/** Zod schema for the negotiation outcome (Artifact payload on COMPLETED task). */\nexport const NegotiationOutcomeSchema = z.object({\n hasOpportunity: z.boolean(),\n agreedRoles: z.array(z.object({\n userId: z.string(),\n role: z.enum([\"agent\", \"patient\", \"peer\"]),\n })),\n reasoning: z.string(),\n turnCount: z.number(),\n reason: z.enum([\"turn_cap\", \"timeout\", \"screened_out\"]).optional(),\n});\n\nexport type NegotiationOutcome = z.infer<typeof NegotiationOutcomeSchema>;\n\n/** Context each agent receives about its user. */\nexport interface UserNegotiationContext {\n id: string;\n intents: Array<{ id: string; title: string; description: string; confidence: number }>;\n profile: { name?: string; bio?: string; location?: string; interests?: string[]; skills?: string[] };\n}\n\n/** Seed assessment from the evaluator pre-filter. */\nexport interface SeedAssessment {\n reasoning: string;\n valencyRole: string;\n actors?: Array<{ userId: string; role: string }>;\n}\n\n/** Typed interface for a negotiation graph's invoke signature. */\nexport interface NegotiationGraphLike {\n invoke(input: {\n sourceUser: UserNegotiationContext;\n candidateUser: UserNegotiationContext;\n indexContext: { networkId: string; prompt: string };\n seedAssessment: Omit<SeedAssessment, \"actors\">;\n discoveryQuery?: string;\n opportunityId?: string;\n /** Exact persisted lifecycle state claimed by this negotiation attempt. */\n opportunityStatus?: OpportunityStatus;\n opportunityUpdatedAt?: Date;\n maxTurns?: number;\n timeoutMs?: number;\n /**\n * The user who holds the initiating seat for this match (v2 client-advocate\n * protocol). Stamped into task metadata by the init node. When omitted, the\n * init node resolves it: inherit from the prior task for the same\n * opportunity → conversation-scoped tie-break → fall back to sourceUser.id.\n */\n initiatorUserId?: string;\n }): Promise<{\n outcome: NegotiationOutcome | null;\n messages?: NegotiationMessage[];\n conversationId?: string;\n isContinuation?: boolean;\n priorTurnCount?: number;\n }>;\n}\n\n/** A2A message record shape (matches messages table). */\nexport interface NegotiationMessage {\n id: string;\n senderId: string;\n role: \"agent\";\n parts: unknown[];\n createdAt: Date;\n}\n\n/** LangGraph state annotation for the negotiation graph. */\nexport const NegotiationGraphState = Annotation.Root({\n sourceUser: Annotation<UserNegotiationContext>({\n reducer: (curr, next) => next ?? curr,\n default: () => ({ id: \"\", intents: [], profile: {} }),\n }),\n candidateUser: Annotation<UserNegotiationContext>({\n reducer: (curr, next) => next ?? curr,\n default: () => ({ id: \"\", intents: [], profile: {} }),\n }),\n indexContext: Annotation<{ networkId: string; prompt: string }>({\n reducer: (curr, next) => next ?? curr,\n default: () => ({ networkId: \"\", prompt: \"\" }),\n }),\n seedAssessment: Annotation<SeedAssessment>({\n reducer: (curr, next) => next ?? curr,\n default: () => ({ reasoning: \"\", valencyRole: \"\" }),\n }),\n\n /**\n * Explicit initiator seat for this match (purely additive metadata — no seat\n * rules attach to it yet). Resolution when unset happens in the init node;\n * the resolved value is written back to state and into task metadata.\n */\n initiatorUserId: Annotation<string | undefined>({\n reducer: (curr, next) => next ?? curr,\n default: () => undefined,\n }),\n\n /** The explicit search query that triggered discovery (if any). */\n discoveryQuery: Annotation<string | undefined>({\n reducer: (curr, next) => next ?? curr,\n default: () => undefined,\n }),\n /**\n * Negotiation protocol version for this session's task. Resolved by the\n * init node: inherited from the prior task on the conversation when one\n * exists (never re-stamped — a v1 conversation stays v1 mid-flight), else\n * stamped from `NEGOTIATION_PROTOCOL_VERSION` for genuinely fresh runs.\n */\n protocolVersion: Annotation<NegotiationProtocolVersion>({\n reducer: (curr, next) => next ?? curr,\n default: () => \"v1\" as const,\n }),\n\n /**\n * Screen-gate decision for this fresh run (P2.1 shadow mode). Written by the\n * screen node; null when the gate is off, on continuations, or before the\n * node runs. Mirrors `tasks.metadata.screenDecision`.\n */\n screenDecision: Annotation<ScreenDecisionRecord | null>({\n reducer: (curr, next) => next ?? curr,\n default: () => null,\n }),\n\n /**\n * First applied deadlock→bargaining shift in this session (IND-428).\n * Written by the turn node when the system agent first drafts in the\n * bargaining stance; used to record the shift exactly once per session.\n * Internal analytics only — mirrored to `tasks.metadata.deadlockShift`,\n * never into any turn payload or public projection.\n */\n deadlockShift: Annotation<DeadlockShiftRecord | null>({\n reducer: (curr, next) => next ?? curr,\n default: () => null,\n }),\n\n /**\n * Per-side negotiator-memory cache (P5.3 read path). Populated lazily the\n * first time each side's memory is retrieved (screen node for the client,\n * turn node for the speaker) so a multi-turn session pays for retrieval at\n * most once per side. `undefined` per side = not yet retrieved; `[]` =\n * retrieved and empty (flag off / no rows / retrieval failed).\n */\n memoryBySide: Annotation<Partial<Record<\"source\" | \"candidate\", NegotiatorMemoryEntry[]>>>({\n reducer: (curr, next) => ({ ...curr, ...next }),\n default: () => ({}),\n }),\n\n /** Whether this run is continuing a prior conversation with the same pair. */\n isContinuation: Annotation<boolean>({\n reducer: (curr, next) => next ?? curr,\n default: () => false,\n }),\n opportunityId: Annotation<string>({\n reducer: (curr, next) => next ?? curr,\n default: () => \"\",\n }),\n /** Exact persisted lifecycle state claimed by this negotiation attempt. */\n opportunityStatus: Annotation<OpportunityStatus | undefined>({\n reducer: (curr, next) => next ?? curr,\n default: () => undefined,\n }),\n opportunityUpdatedAt: Annotation<Date | undefined>({\n reducer: (curr, next) => next ?? curr,\n default: () => undefined,\n }),\n conversationId: Annotation<string>({\n reducer: (curr, next) => next ?? curr,\n default: () => \"\",\n }),\n taskId: Annotation<string>({\n reducer: (curr, next) => next ?? curr,\n default: () => \"\",\n }),\n messages: Annotation<NegotiationMessage[]>({\n reducer: (curr, next) => [...curr, ...(next || [])],\n default: () => [],\n }),\n turnCount: Annotation<number>({\n reducer: (curr, next) => next ?? curr,\n default: () => 0,\n }),\n maxTurns: Annotation<number | undefined>({\n reducer: (curr, next) => next ?? curr,\n default: () => undefined,\n }),\n /**\n * Park-window budget in milliseconds. Ambient callers pass `AMBIENT_PARK_WINDOW_MS`\n * (5 minutes); orchestrator callers pass a shorter window. This annotation default\n * is a safety net for any caller that omits the field — keep it aligned with\n * `AMBIENT_PARK_WINDOW_MS` in packages/protocol/src/negotiation/negotiation.tools.ts.\n * Inlined rather than imported to avoid a state↔tools cycle.\n */\n timeoutMs: Annotation<number>({\n reducer: (curr, next) => next ?? curr,\n default: () => 5 * 60 * 1000,\n }),\n\n currentSpeaker: Annotation<\"source\" | \"candidate\">({\n reducer: (curr, next) => next ?? curr,\n default: () => \"source\" as const,\n }),\n lastTurn: Annotation<NegotiationTurn | null>({\n reducer: (curr, next) => next ?? curr,\n default: () => null,\n }),\n\n /**\n * Graph status.\n * - `active` — agents are exchanging turns (default)\n * - `waiting_for_agent` — graph suspended; awaiting external agent response or timeout\n * - `input_required` — graph suspended on an `ask_user` pause; awaiting the\n * negotiator's own client (answer or 24 h window expiry resumes it)\n * - `completed` — negotiation finalized (accept/reject/turn-cap/timeout)\n */\n status: Annotation<'active' | 'waiting_for_agent' | 'input_required' | 'completed'>({\n reducer: (curr, next) => next ?? curr,\n default: () => 'active' as const,\n }),\n\n /** Number of turns present in the conversation before this session started. */\n priorTurnCount: Annotation<number>({\n reducer: (curr, next) => next ?? curr,\n default: () => 0,\n }),\n\n /** User answers collected by the questioner between negotiation sessions. */\n userAnswers: Annotation<NegotiationUserAnswer[]>({\n reducer: (curr, next) => next ?? curr,\n default: () => [],\n }),\n\n outcome: Annotation<NegotiationOutcome | null>({\n reducer: (curr, next) => next ?? curr,\n default: () => null,\n }),\n error: Annotation<string | null>({\n reducer: (curr, next) => next ?? curr,\n default: () => null,\n }),\n});\n"]}
|
|
@@ -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;
|
|
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
|
|
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"]}
|