@indexnetwork/protocol 5.2.0-rc.368.1 → 5.2.2-rc.370.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 +3 -0
- package/dist/enrichment/enrichment.generator.d.ts +3 -1
- package/dist/enrichment/enrichment.generator.d.ts.map +1 -1
- package/dist/enrichment/enrichment.generator.js +3 -3
- package/dist/enrichment/enrichment.generator.js.map +1 -1
- package/dist/opportunity/opportunity.evaluator.d.ts +3 -0
- package/dist/opportunity/opportunity.evaluator.d.ts.map +1 -1
- package/dist/opportunity/opportunity.evaluator.js +4 -4
- package/dist/opportunity/opportunity.evaluator.js.map +1 -1
- package/dist/opportunity/opportunity.presenter.d.ts +6 -2
- package/dist/opportunity/opportunity.presenter.d.ts.map +1 -1
- package/dist/opportunity/opportunity.presenter.js +10 -8
- package/dist/opportunity/opportunity.presenter.js.map +1 -1
- package/dist/premise/premise.analyzer.d.ts +3 -1
- package/dist/premise/premise.analyzer.d.ts.map +1 -1
- package/dist/premise/premise.analyzer.js +3 -3
- package/dist/premise/premise.analyzer.js.map +1 -1
- package/dist/premise/premise.decomposer.d.ts +3 -1
- package/dist/premise/premise.decomposer.d.ts.map +1 -1
- package/dist/premise/premise.decomposer.js +3 -3
- package/dist/premise/premise.decomposer.js.map +1 -1
- package/package.json +2 -1
package/CHANGELOG.md
CHANGED
|
@@ -13,6 +13,7 @@ See [STABILITY.md](./STABILITY.md) for the public-contract and tier definitions.
|
|
|
13
13
|
## [Unreleased]
|
|
14
14
|
|
|
15
15
|
### Added
|
|
16
|
+
- Provider-free privacy-aware eval artifact viewer with explicit shared v1/v2 and HyDE-public adapters, allowlisted redaction, attempt-aware execution inspection, baseline deltas, accessible offline navigation, safe failure pages, and atomic read-only output (IND-446).
|
|
16
17
|
- Default-off Lens B outcome-question shadow: pure, outcome-blind trade-off hypothesis mining over a user's OWN explicit opportunity decisions, with one unique counterpart per captured opportunity, recipient-scoped counterpart deduplication, run-local candidate aliases (raw opportunity ids are never sent to the LLM), trimmed/unique/non-empty compared sides, conflicting classifier assignments excluded from support, at least five genuinely distinct independent examples per side, small-cell suppression, aggregate-only telemetry, and an `OutcomeOutbox` contract enabling transaction-held scope revalidation plus atomic same-transaction outcome capture in the winning owner-action transition (IND-434).
|
|
17
18
|
- Default-off Lens C negotiation-evidence shadow mining from future negotiation tasks with immutable intent snapshots, exact task-linked allowlisted evidence, strict participant/source verification, recurrence across at least five distinct opportunities, and aggregate-only telemetry (IND-433).
|
|
18
19
|
- Default-off frame-v1 HyDE generation with source-only frame extraction, post-generation entity/constraint validation, partial/all rejection, ephemeral fail-open behavior, and mode/source/generation-isolated cache persistence (IND-426).
|
|
@@ -25,6 +26,7 @@ See [STABILITY.md](./STABILITY.md) for the public-contract and tier definitions.
|
|
|
25
26
|
- QUD-typed intent clarification (`missing_constituent`, `missing_constraint`, and `open_alternative_set`) across the live intent elaboration and Questioner flows, with internal detection metadata and exact-match eval coverage (IND-425).
|
|
26
27
|
|
|
27
28
|
### Changed
|
|
29
|
+
- Made matching, opportunity, premise, and profile eval retries, failures, timeouts, cancellations, and incomplete runs first-class attempt evidence; incomplete runs now persist diagnostics but never compare against or update baselines (IND-444).
|
|
28
30
|
- Added the pool-question drift lifecycle: exact recipient+intent final freshness gates, shared inclusive `0.7` Jaccard admission, system-voided stale snapshots, durable MODE cadence suppression, intent-edit invalidation, and audit-preserved stale scoped adjustments excluded from ranking (IND-422).
|
|
29
31
|
- Retargeted the HyDE evidence-v2 harness to background-only discovery: 75 saved-intent cases plus 15 independently authored user-context cases (90 cases/900 candidates), with private saved-intent -> internal `query` and user-context -> `context` graph-source provenance, production-shaped saved-intent discoverer context, source-specific non-gating diagnostics, and no direct-search cohort. The four counterbalanced paired runs, blinded independent human adjudication, hierarchical bootstrap intervals, eight fixed gates, and production agents remain unchanged; this changes eval evidence and documentation only (IND-426).
|
|
30
32
|
- Marked atomically claimed, user-balanced and privacy-thresholded frame-centroid observation plus the privacy-thresholded non-causal yield proxy as shipped by IND-430, while explicitly leaving immutable per-discovery provenance and causal drift diagnosis as future work; protocol runtime behavior is unchanged.
|
|
@@ -34,6 +36,7 @@ See [STABILITY.md](./STABILITY.md) for the public-contract and tier definitions.
|
|
|
34
36
|
- Included protocol documentation files in the published package tarball so README links remain available to package consumers.
|
|
35
37
|
|
|
36
38
|
### Fixed
|
|
39
|
+
- Forwarded per-attempt `AbortSignal`s through eval provider paths and hardened failure provenance against secret leakage, hostile rejection objects, classifier failures, and concurrent artifact writers (IND-444).
|
|
37
40
|
- Aligned HyDE evidence scoring with the live background `0.30` cutoff, retained per-lens cosines for score/ranking revalidation, required report-stage parent recomputation, and prevented forced outputs from overwriting input evidence artifacts (IND-426).
|
|
38
41
|
- Scoped pool-question adjustments to the exact answering recipient and selected intent, ignored legacy unscoped factors, and restricted Tier-0/newborn writes to exact trigger-intent provenance so shared opportunities cannot re-rank another viewer or intent.
|
|
39
42
|
- Made trigger-intent discovery fail closed over current intent assignments, active owner memberships, and explicit caller scope; enforced active candidate membership across intent/premise/context retrieval plus pre-evaluation/pre-persistence rechecks and selected-intent Radar reads.
|
|
@@ -22,7 +22,9 @@ export declare class EnrichmentGenerator {
|
|
|
22
22
|
private model;
|
|
23
23
|
constructor();
|
|
24
24
|
private toString;
|
|
25
|
-
invoke(input: string
|
|
25
|
+
invoke(input: string, options?: {
|
|
26
|
+
signal?: AbortSignal;
|
|
27
|
+
}): Promise<{
|
|
26
28
|
output: {
|
|
27
29
|
identity: {
|
|
28
30
|
name: string;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"enrichment.generator.d.ts","sourceRoot":"/","sources":["enrichment/enrichment.generator.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,CAAC,EAAE,MAAM,QAAQ,CAAC;AAkB3B,QAAA,MAAM,cAAc;;;;;;;;;;;;;iBAalB,CAAC;AAEH,KAAK,OAAO,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,cAAc,CAAC,CAAC;AAC9C,MAAM,MAAM,gBAAgB,GAAG,OAAO,GAAG;IAAE,MAAM,EAAE,MAAM,CAAA;CAAE,CAAC;AAE5D,qBAAa,mBAAmB;IAC9B,OAAO,CAAC,MAAM,CAAC,WAAW,CAAuD;IACjF,OAAO,CAAC,KAAK,CAAkF;;IAQ/F,OAAO,CAAC,QAAQ;IAeH,MAAM,CAAC,KAAK,EAAE,MAAM;;;;;;;;;;;;;;;;;
|
|
1
|
+
{"version":3,"file":"enrichment.generator.d.ts","sourceRoot":"/","sources":["enrichment/enrichment.generator.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,CAAC,EAAE,MAAM,QAAQ,CAAC;AAkB3B,QAAA,MAAM,cAAc;;;;;;;;;;;;;iBAalB,CAAC;AAEH,KAAK,OAAO,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,cAAc,CAAC,CAAC;AAC9C,MAAM,MAAM,gBAAgB,GAAG,OAAO,GAAG;IAAE,MAAM,EAAE,MAAM,CAAA;CAAE,CAAC;AAE5D,qBAAa,mBAAmB;IAC9B,OAAO,CAAC,MAAM,CAAC,WAAW,CAAuD;IACjF,OAAO,CAAC,KAAK,CAAkF;;IAQ/F,OAAO,CAAC,QAAQ;IAeH,MAAM,CAAC,KAAK,EAAE,MAAM,EAAE,OAAO,GAAE;QAAE,MAAM,CAAC,EAAE,WAAW,CAAA;KAAO;;;;;;;;;;;;;;;;;WAgB3D,MAAM;;;;;;;;;;;;;;;;;;;;;;;CAerB"}
|
|
@@ -57,13 +57,13 @@ export class EnrichmentGenerator {
|
|
|
57
57
|
'## Skills', profile.attributes.skills.join(', ')
|
|
58
58
|
].join('\n');
|
|
59
59
|
}
|
|
60
|
-
async invoke(input) {
|
|
60
|
+
async invoke(input, options = {}) {
|
|
61
61
|
logger.verbose("Received input", { inputLength: input?.length });
|
|
62
62
|
const messages = [
|
|
63
63
|
new SystemMessage(systemPrompt),
|
|
64
64
|
new HumanMessage(`Here is the raw data:\n${input}`)
|
|
65
65
|
];
|
|
66
|
-
const result = await invokeWithAbortSignal(this.model, messages);
|
|
66
|
+
const result = await invokeWithAbortSignal(this.model, messages, options.signal);
|
|
67
67
|
const output = responseFormat.parse(result);
|
|
68
68
|
const textToEmbed = this.toString(output);
|
|
69
69
|
logger.verbose("Generated profile", {
|
|
@@ -88,7 +88,7 @@ export class EnrichmentGenerator {
|
|
|
88
88
|
__decorate([
|
|
89
89
|
Timed(),
|
|
90
90
|
__metadata("design:type", Function),
|
|
91
|
-
__metadata("design:paramtypes", [String]),
|
|
91
|
+
__metadata("design:paramtypes", [String, Object]),
|
|
92
92
|
__metadata("design:returntype", Promise)
|
|
93
93
|
], EnrichmentGenerator.prototype, "invoke", null);
|
|
94
94
|
//# sourceMappingURL=enrichment.generator.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"enrichment.generator.js","sourceRoot":"/","sources":["enrichment/enrichment.generator.ts"],"names":[],"mappings":";;;;;;;;;AAAA,OAAO,EAAE,YAAY,EAAE,aAAa,EAAE,MAAM,0BAA0B,CAAC;AACvE,OAAO,EAAE,IAAI,EAAE,MAAM,uBAAuB,CAAC;AAC7C,OAAO,EAAE,CAAC,EAAE,MAAM,QAAQ,CAAC;AAC3B,OAAO,EAAE,cAAc,EAAE,MAAM,4CAA4C,CAAC;AAC5E,OAAO,EAAE,KAAK,EAAE,MAAM,wCAAwC,CAAC;AAC/D,OAAO,EAAE,qBAAqB,EAAE,MAAM,iCAAiC,CAAC;AACxE,OAAO,EAAE,qBAAqB,EAAE,MAAM,iCAAiC,CAAC;AAExE,MAAM,MAAM,GAAG,cAAc,CAAC,qBAAqB,CAAC,CAAC;AAErD,MAAM,YAAY,GAAG;;;;;;;;CAQpB,CAAC;AAEF,MAAM,cAAc,GAAG,CAAC,CAAC,MAAM,CAAC;IAC9B,QAAQ,EAAE,CAAC,CAAC,MAAM,CAAC;QACjB,IAAI,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,CAAC,sBAAsB,CAAC;QACjD,GAAG,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,CAAC,2HAA2H,CAAC;QACrJ,QAAQ,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,CAAC,+CAA+C,CAAC;KAC/E,CAAC;IACF,SAAS,EAAE,CAAC,CAAC,MAAM,CAAC;QAClB,OAAO,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,CAAC,oGAAoG,CAAC;KACnI,CAAC;IACF,UAAU,EAAE,CAAC,CAAC,MAAM,CAAC;QACnB,SAAS,EAAE,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,MAAM,EAAE,CAAC,CAAC,QAAQ,CAAC,gCAAgC,CAAC;QACzE,MAAM,EAAE,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,MAAM,EAAE,CAAC,CAAC,QAAQ,CAAC,qBAAqB,CAAC;KAC5D,CAAC;CACH,CAAC,CAAC;AAKH,MAAM,OAAO,mBAAmB;IAI9B;QACE,IAAI,CAAC,KAAK,GAAG,mBAAmB,CAAC,WAAW,KAA/B,mBAAmB,CAAC,WAAW,GAAK,qBAAqB,CAAC,kBAAkB,EAAE,cAAc,EAAE;YACzG,IAAI,EAAE,mBAAmB;SAC1B,CAAC,CAAA,CAAC;IACL,CAAC;IAEO,QAAQ,CAAC,OAAgB;QAC/B,OAAO;YACL,YAAY;YACZ,SAAS,EAAE,OAAO,CAAC,QAAQ,CAAC,IAAI;YAChC,QAAQ,EAAE,OAAO,CAAC,QAAQ,CAAC,GAAG;YAC9B,aAAa,EAAE,OAAO,CAAC,QAAQ,CAAC,QAAQ;YACxC,aAAa;YACb,YAAY,EAAE,OAAO,CAAC,SAAS,CAAC,OAAO;YACvC,cAAc;YACd,cAAc,EAAE,OAAO,CAAC,UAAU,CAAC,SAAS,CAAC,IAAI,CAAC,IAAI,CAAC;YACvD,WAAW,EAAE,OAAO,CAAC,UAAU,CAAC,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC;SAClD,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IACf,CAAC;IAGY,AAAN,KAAK,CAAC,MAAM,CAAC,KAAa;
|
|
1
|
+
{"version":3,"file":"enrichment.generator.js","sourceRoot":"/","sources":["enrichment/enrichment.generator.ts"],"names":[],"mappings":"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","sourcesContent":["import { HumanMessage, SystemMessage } from \"@langchain/core/messages\";\nimport { tool } from \"@langchain/core/tools\";\nimport { z } from \"zod/v4\";\nimport { protocolLogger } from \"../shared/observability/protocol.logger.js\";\nimport { Timed } from \"../shared/observability/performance.js\";\nimport { createStructuredModel } from \"../shared/agent/model.config.js\";\nimport { invokeWithAbortSignal } from \"../shared/agent/model-signal.js\";\n\nconst logger = protocolLogger(\"EnrichmentGenerator\");\n\nconst systemPrompt = `\n You are an expert profiler. Your task is to synthesize a structured User Profile from raw data or user requests.\n\n When given EXISTING PROFILE + USER REQUEST: Apply the request to the existing profile. Add, update, or remove skills and interests as the user asks. Preserve everything else. Output the full updated profile.\n\n When given raw data only: Infer name, bio, location, narrative.context, and extract skills and interests.\n\n PRIVACY: identity.bio and narrative.context are public-facing. Never include email addresses, phone numbers, physical addresses, government IDs, or other contact identifiers — even if they appear in the raw data. Describe the person professionally; do not embed ways to contact them.\n`;\n\nconst responseFormat = z.object({\n identity: z.object({\n name: z.string().describe(\"The user's full name\"),\n bio: z.string().describe(\"Professional summary (2-3 sentences) only; no email, phone, physical address, government ID, or other contact identifiers\"),\n location: z.string().describe(\"Inferred location (City, Country) or 'Remote'\"),\n }),\n narrative: z.object({\n context: z.string().describe(\"Rich narrative without email, phone, physical address, government ID, or other contact identifiers\"),\n }),\n attributes: z.object({\n interests: z.array(z.string()).describe(\"Inferred or explicit interests\"),\n skills: z.array(z.string()).describe(\"Professional skills\"),\n }),\n});\n\ntype Profile = z.infer<typeof responseFormat>;\nexport type GeneratedProfile = Profile & { userId: string };\n\nexport class EnrichmentGenerator {\n private static sharedModel: ReturnType<typeof createStructuredModel> | undefined;\n private model: { invoke(input: unknown, config?: { signal?: AbortSignal }): Promise<unknown> };\n\n constructor() {\n this.model = EnrichmentGenerator.sharedModel ??= createStructuredModel(\"profileGenerator\", responseFormat, {\n name: \"profile_generator\"\n });\n }\n\n private toString(profile: Profile): string {\n return [\n '# Identity',\n '## Name', profile.identity.name,\n '## Bio', profile.identity.bio,\n '## Location', profile.identity.location,\n '# Narrative',\n '## Context', profile.narrative.context,\n '# Attributes',\n '## Interests', profile.attributes.interests.join(', '),\n '## Skills', profile.attributes.skills.join(', ')\n ].join('\\n');\n }\n\n @Timed()\n public async invoke(input: string, options: { signal?: AbortSignal } = {}) {\n logger.verbose(\"Received input\", { inputLength: input?.length });\n const messages = [\n new SystemMessage(systemPrompt),\n new HumanMessage(`Here is the raw data:\\n${input}`)\n ];\n const result = await invokeWithAbortSignal(this.model, messages, options.signal);\n const output = responseFormat.parse(result);\n const textToEmbed = this.toString(output);\n logger.verbose(\"Generated profile\", {\n skillsCount: output.attributes.skills.length,\n interestsCount: output.attributes.interests.length\n });\n return { output, textToEmbed };\n }\n\n public static asTool() {\n return tool(\n async (args: { input: string }) => {\n const profileGenerator = new EnrichmentGenerator();\n return await profileGenerator.invoke(args.input);\n },\n {\n name: 'profileGenerator',\n description: 'Profile Generator',\n schema: z.object({\n input: z.string().describe('Raw data scraped from the web (via Parallel.ai)'),\n })\n }\n );\n }\n}"]}
|
|
@@ -207,6 +207,8 @@ interface OpportunityEvaluatorOptions {
|
|
|
207
207
|
candidates?: CandidateProfile[];
|
|
208
208
|
filter?: Record<string, unknown>;
|
|
209
209
|
initialStatus?: OpportunityStatus;
|
|
210
|
+
/** Optional caller cancellation; does not change retry/fallback policy. */
|
|
211
|
+
signal?: AbortSignal;
|
|
210
212
|
}
|
|
211
213
|
/** Optional test double for entity-bundle model (avoids live LLM in unit tests). */
|
|
212
214
|
export type OpportunityEvaluatorOptionsConstructor = {
|
|
@@ -235,6 +237,7 @@ export declare class OpportunityEvaluator {
|
|
|
235
237
|
invokeEntityBundle(input: EvaluatorInput, options?: {
|
|
236
238
|
minScore?: number;
|
|
237
239
|
returnAll?: boolean;
|
|
240
|
+
signal?: AbortSignal;
|
|
238
241
|
}): Promise<EvaluatedOpportunityWithActors[]>;
|
|
239
242
|
/**
|
|
240
243
|
* Factory method to expose the agent as a LangChain tool.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"opportunity.evaluator.d.ts","sourceRoot":"/","sources":["opportunity/opportunity.evaluator.ts"],"names":[],"mappings":"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
|
|
1
|
+
{"version":3,"file":"opportunity.evaluator.d.ts","sourceRoot":"/","sources":["opportunity/opportunity.evaluator.ts"],"names":[],"mappings":"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"}
|
|
@@ -214,7 +214,7 @@ export class OpportunityEvaluator {
|
|
|
214
214
|
const promises = candidates.map(async (candidate) => {
|
|
215
215
|
// Pass existing opportunities context if provided
|
|
216
216
|
const existingContext = options.existingOpportunities || '';
|
|
217
|
-
return this.analyzeMatch(sourceProfileContext, candidate, candidate.userId, existingContext);
|
|
217
|
+
return this.analyzeMatch(sourceProfileContext, candidate, candidate.userId, existingContext, options.signal);
|
|
218
218
|
});
|
|
219
219
|
const results = await Promise.all(promises);
|
|
220
220
|
results.flat().forEach(op => {
|
|
@@ -230,7 +230,7 @@ export class OpportunityEvaluator {
|
|
|
230
230
|
/**
|
|
231
231
|
* Analyze a single match pair using the primary Agent model.
|
|
232
232
|
*/
|
|
233
|
-
async analyzeMatch(sourceProfileContext, candidateProfile, candidateUserId, existingOpportunities) {
|
|
233
|
+
async analyzeMatch(sourceProfileContext, candidateProfile, candidateUserId, existingOpportunities, signal) {
|
|
234
234
|
try {
|
|
235
235
|
// Construct the source context part of the prompt
|
|
236
236
|
const sourceContext = `SOURCE PROFILE:\n${sourceProfileContext}`;
|
|
@@ -252,7 +252,7 @@ export class OpportunityEvaluator {
|
|
|
252
252
|
new SystemMessage(systemPrompt),
|
|
253
253
|
new HumanMessage(`${sourceContext}\n${existingContextPart}\nCANDIDATE PROFILE:\n${candidateContext}`)
|
|
254
254
|
];
|
|
255
|
-
const result = await invokeWithAbortSignal(this.model, messages);
|
|
255
|
+
const result = await invokeWithAbortSignal(this.model, messages, signal);
|
|
256
256
|
const output = responseFormat.parse(result);
|
|
257
257
|
const mappedOpportunities = output.opportunities.map((op) => ({
|
|
258
258
|
...op,
|
|
@@ -375,7 +375,7 @@ ${renderOpportunityEvidenceForPrompt(e.evidence ?? [])}`;
|
|
|
375
375
|
];
|
|
376
376
|
let parsedTotal = 0;
|
|
377
377
|
try {
|
|
378
|
-
const result = await invokeWithAbortSignal(this.entityBundleModel, messages);
|
|
378
|
+
const result = await invokeWithAbortSignal(this.entityBundleModel, messages, options.signal);
|
|
379
379
|
const parsed = entityBundleResponseFormat.parse(result);
|
|
380
380
|
for (const op of parsed.opportunities) {
|
|
381
381
|
op.reasoning = stripUuids(op.reasoning);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"opportunity.evaluator.js","sourceRoot":"/","sources":["opportunity/opportunity.evaluator.ts"],"names":[],"mappings":"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","sourcesContent":["import type { Runnable } from \"@langchain/core/runnables\";\nimport { HumanMessage, SystemMessage } from \"@langchain/core/messages\";\nimport { tool } from \"@langchain/core/tools\";\nimport { z } from \"zod\";\nimport { protocolLogger } from \"../shared/observability/protocol.logger.js\";\nimport type { Lens } from \"../shared/hyde/lens.inferrer.js\";\nimport type { OpportunityStatus } from \"../shared/interfaces/database.interface.js\";\nimport { Timed } from \"../shared/observability/performance.js\";\nimport { stripUuids } from \"./opportunity.presentation.js\";\nimport { createStructuredModel } from \"../shared/agent/model.config.js\";\nimport { invokeWithAbortSignal } from \"../shared/agent/model-signal.js\";\nimport type { OpportunityEvidence } from '../shared/schemas/network-assignment.schema.js';\nimport { renderOpportunityEvidenceForPrompt } from './opportunity.evidence.js';\nimport { hasUnsupportedOpportunityClaim } from './opportunity.claim-safety.js';\n\nconst logger = protocolLogger(\"OpportunityEvaluator\");\nconst invokeLog = protocolLogger(\"OpportunityEvaluator:invoke\");\nconst invokeEntityBundleLog = protocolLogger(\"OpportunityEvaluator:invokeEntityBundle\");\n\n\n// ──────────────────────────────────────────────────────────────\n// 1. SYSTEM PROMPT\n// ──────────────────────────────────────────────────────────────\n\n\nconst systemPrompt = `\n You are an expert \"Opportunity Matcher\" and super-connector.\n Your Goal: Analyze a Source User's profile against a Candidate User's profile to identify A SINGLE HIGH-VALUE opportunity.\n\n Input:\n - Source Context: The Source User's own Profile.\n - Candidate Profile (JSON)\n - Existing Opportunities (Context of matches already made)\n\n Output:\n - A list containing EXACTLY ONE \"Opportunity\" if a match exists.\n - If NO match exists, return an empty list.\n - Score (0-100): How strong is this match?\n - 90-100: \"Must Meet\" (Perfect alignment).\n - 70-89: \"Should Meet\" (Strong overlaps, clear potential).\n - <70: No opportunity (Return empty list).\n\n **CRITICAL: VALENCY & REASONING**\n \n 1. **Valency Analysis**:\n - Determine the semantic role of the Candidate relative to the Source's goal.\n - \"Agent\": The Candidate CAN DO something for the Source (e.g., Source needs a dev, Candidate IS a dev).\n - \"Patient\": The Candidate NEEDS something from the Source (e.g., Source is a mentor, Candidate needs mentoring).\n - \"Peer\": Symmetric collaboration.\n\n 2. **Reasoning (Third-Party Analytical Perspective)**:\n - **reasoning**: A neutral, third-party explanation of why this opportunity exists. Written for other LLM agents to read and understand.\n - Mention BOTH users by their roles (e.g. \"The source user\" and \"The candidate\") and explain why they are a match.\n - Do NOT address either user as \"you\". Write from an objective observer's perspective.\n - Include what each side brings to the connection and why it is mutually valuable.\n - NEVER leak private intents. If someone's intent is confidential, describe their relevant attributes instead.\n\n **VISIBILITY IMPLICATIONS OF ROLE ASSIGNMENT**\n\n The valency role you assign directly controls who sees the opportunity and when:\n - \"Agent\" (helper/provider): LAST to see the opportunity — only after the Patient has committed to reaching out. Agents are protected from noise; they only see high-intent connections.\n - \"Patient\" (seeker/requester): Sees the opportunity early and decides whether to reach out.\n - \"Peer\" (symmetric): Both parties see the opportunity immediately and either can initiate.\n\n Choose the role carefully — it determines the entire flow of how the connection unfolds.\n\n Rules:\n 1. SYNTHESIS (CRITICAL): If multiple distinct match angles exist, SYNTHESIZE them into a SINGLE, robust opportunity.\n 2. NEVER address either user directly — always use third-party references (\"the source\", \"the candidate\").\n 3. COMPREHENSIVE: The single opportunity must capture ALL the value of the connection.\n 4. Be specific about the \"Why\" for BOTH sides in the reasoning.\n 5. DEDUPLICATION: Do NOT suggest opportunities that duplicate \"Existing Opportunities\".\n 6. Do not suggest an opportunity if the source and candidate clearly already know each other (e.g. same company, co-founders, same team).\n 7. SAME-SIDE MATCHING: If both the source and candidate are SEEKING the same resource (e.g., both looking for investors, both seeking a co-founder), this is not an opportunity. Return an empty list unless one side clearly OFFERS what the other SEEKS.\n 8. NETWORK CONTEXT IS NOT PERSON EVIDENCE: A network assignment, title, type, or metadata is retrieval and thematic context only. It is never proof that a person attended an event, belongs to a group, resides in a place, knows anyone else from the network, or shared a session, time, or location with anyone. Do not state or infer those claims in opportunity reasoning.\n`;\n\n// Entity-bundle system prompt (C2): entities + four match patterns + actors output\n// NOTE: entityBundleSystemPrompt uses a >= 30 threshold (permissive) while\n// systemPrompt uses >= 70 (strict). This is intentional: batch mode casts a wide\n// net so the calling pipeline can apply its own filters; pairwise mode is strict\n// because it returns a single yes/no decision per candidate pair.\nconst entityBundleSystemPrompt = `\nYou are an expert \"Opportunity Matcher\" and super-connector.\nYour Goal: Analyze a set of entities (people), each with a profile and optional intents, and identify HIGH-VALUE opportunities among them.\n\nInput:\n- DISCOVERER: The user ID who triggered discovery (for context; they may or may not be in the entity list).\n- ENTITIES: A set of entities. Each entity has:\n - userId, networkId (the index through which they were found)\n - evidenceKey (stable key for the matched resource when available)\n - profile: name, bio, location, interests, skills, context\n - intents (optional): list of { intentId, payload, summary } — some entities are profile-only, some have intents\n - ragScore, matchedVia (how they were found)\n - evidence (typed retrieval evidence: discovery kind, network, score, lens, source/candidate ids)\n- EXISTING OPPORTUNITIES: Context of matches already made (for deduplication).\n\nBEFORE SCORING — determine role satisfiability:\n\nDefinitions:\n SUBSTITUTIVE ROLE: The candidate can directly fill the open position in the discoverer's intent. The candidate IS the person/entity the discoverer is seeking. Example: discoverer seeks \"co-founder\" → candidate is an engineer willing to co-found.\n COMPLEMENTARY ROLE: The candidate's contribution is defined relative to the seeker-sought relation from the outside — they fund, advise, recruit for, or enable the sought relationship rather than participating in it as the target. Example: discoverer seeks \"co-founder\" → candidate is a VC (funds the company, does not co-found it).\n\nStep 1 — Identify the open argument in each discoverer intent: what type of person or entity would satisfy the intent if found?\nStep 2 — For each candidate, ask: can this candidate directly fill that open argument position?\n YES → substitutive role. Proceed to scoring.\n NO → complementary role. Apply Rule 7 (score ≤ 30, return no opportunity).\nStep 3 — Contextual override: if the candidate's profile contains explicit evidence that they currently function in the substitutive role (e.g., a former investor who is now building full-time as a technical co-founder), re-evaluate Step 2 against their current role, not their categorical label.\n\nMatch patterns to consider:\n1. Profile-to-profile: Complementary backgrounds (skills, interests, location).\n2. Profile-to-intents+profile: Someone's skills/background match another's stated goals (intents).\n3. Intents+profile-to-profile: Someone's stated goals match another's skills/background.\n4. Intents+profile-to-intents+profile: Complementary or reciprocal goals between two or more people.\n\nOutput:\n- A list of 0..N opportunities. Each opportunity has:\n - reasoning: Third-party analytical explanation (for other LLM agents). Mention entities by role. Do NOT use \"you\". Never leak private intents.\n - score: 0-100.\n - 90-100: Must Meet — candidate's PRIMARY role directly matches what the discoverer seeks.\n Example: discoverer seeks \"AI/ML co-founder\" → candidate IS an AI/ML engineer who wants to co-found.\n - 70-89: Should Meet — meaningful overlap on role type AND complementary intent.\n - 50-69: Worth Considering — tangential overlap only.\n - <30 (return empty): Complementary-role mismatch (candidate cannot fill the discoverer's open argument position), same-side match, or already acquainted.\n Example: discoverer seeks \"co-founder\" → candidate is a VC investor. The investor's contribution is external to the co-founding relation; they cannot substitute into it. Score 0.\n - IMPORTANT: Include ALL reasonable matches with scores >= 30, and ONLY those. Any candidate you would reject (complementary-role mismatch, same-side, already-acquainted, or a hard location mismatch) must score strictly below 30 and be omitted entirely — the surfacing threshold is 30, so a rejected candidate parked at exactly 30 would be wrongly surfaced.\n - actors: At least 2 actors per opportunity. Each actor has:\n - userId\n - role: \"agent\" (can do something for others), \"patient\" (needs something from others), \"peer\" (symmetric collaboration)\n - intentId (optional): if the match is intent-driven, the specific intent ID for that user\n - evidenceKey (optional): copy the entity evidenceKey when the actor is driven by a specific listed entity\n\nVISIBILITY (role controls who sees the opportunity when):\n- agent: Last to see — after the patient has committed to reaching out.\n- patient: Sees early and decides whether to reach out.\n- peer: Both see immediately; either can initiate.\n\nRules:\n1. ONE OPPORTUNITY PER CANDIDATE: Create a SEPARATE opportunity for EACH candidate who matches. Do NOT combine multiple candidates into one opportunity. Each opportunity should have exactly 2 actors: the DISCOVERER and ONE candidate.\n2. INDIVIDUAL REASONING: Write specific reasoning for EACH candidate individually. Do NOT mention other candidates in the reasoning. Focus on why THIS specific candidate matches THIS specific discoverer.\n3. DEDUPLICATION: Do not suggest opportunities that duplicate Existing Opportunities.\n4. Write reasoning from an objective observer's perspective; be specific about the \"Why\" for each side.\n5. When in introduction mode, each opportunity must have exactly two actors — the two people being introduced. The discoverer (introducer) is added by the system and must not be included in your actors list.\n6. ALREADY KNOW EACH OTHER: Do NOT suggest an opportunity if the entities clearly already know each other. Examples: co-founders of the same company, same team at the same organization, same employer, or any relationship that is obviously existing from their profiles (bio, context). When in doubt, if both profiles mention the same company/org/team in a way that implies they work together, return an empty list for that pair.\n7. ROLE-SATISFIABILITY (evaluate before scoring): A candidate satisfies a discoverer's intent only if they can fill the SUBSTITUTIVE ROLE — the open argument position in the intent (the type of person the discoverer is seeking). A candidate in a COMPLEMENTARY ROLE (one that funds, advises, recruits for, or otherwise enables the sought relation from outside it) does not satisfy the intent, regardless of how closely associated their domain is.\n COMPLEMENTARY-ROLE CAP: If the candidate occupies a complementary rather than substitutive role relative to the discoverer's intent, score below 30 (e.g. 0–20). Return no opportunity. A \"reject\" score must be strictly below 30 so the candidate is not surfaced — never park a rejected candidate at exactly 30.\n CONTEXTUAL OVERRIDE: If the candidate's profile contains explicit evidence that they currently function in the substitutive role (not merely historically or tangentially), treat them as substitutive and score normally.\n8. SAME-SIDE MATCHING: Before scoring, check whether the DISCOVERER and CANDIDATE are both SEEKING the same thing. Look at both parties' intents for directionality:\n - SEEKING signals: \"looking for\", \"seeking\", \"want to find\", \"need\", \"raising\", \"hiring\"\n - OFFERING signals: \"can offer\", \"expert in\", \"investing in\", \"mentoring\", \"available for\"\n If both parties have SEEKING intents targeting the same resource (e.g., both seeking investors, both seeking co-founders, both seeking mentorship), this is NOT an opportunity — score <30. An opportunity requires one side to OFFER what the other SEEKS.\n9. LOCATION MATCHING: When the DISCOVERY REQUEST mentions a specific location (city, region, or country):\n a. If a candidate's profile.location is KNOWN and clearly does NOT match the requested location (different city/region), score below 30 (e.g. 0–25) and return no opportunity. An explicitly requested location is a hard constraint: a known geographic mismatch excludes the candidate.\n b. If a candidate's profile.location is UNKNOWN, EMPTY, or AMBIGUOUS, do NOT penalize — allow them through and score based on other factors. Note in reasoning that their location is unverified.\n c. If a candidate's profile.location matches or is reasonably close (e.g., \"Bay Area\" matches \"San Francisco\", \"Remote\" matches any location), score normally.\n d. \"Remote\" or \"Global\" locations are compatible with any requested location.\n10. EVENT NETWORK CONTEXT: When NETWORK CONTEXTS includes an event-type network (identified by dates, location, schedule, or themes):\n a. TEMPORAL RELEVANCE: Event start/end dates may affect whether an explicitly stated event-related intent is timely. After an event ends, event-specific intents lose relevance unless the connection has lasting value.\n b. THEME ALIGNMENT: Event themes or tracks may be used as thematic retrieval context when a person's own profile or intent independently aligns with them.\n c. PROHIBITED INFERENCE: Network assignment, title, type, schedule, location, and metadata are retrieval context only. They are never proof that any person attended or will attend, belongs to the event or network, resides in its location, knows another person from it, or shared a session, time, place, or location with anyone. Do not infer co-attendance, same-place/same-time presence, session attendance, or peer roles from network co-membership.\n`;\n\n// ──────────────────────────────────────────────────────────────\n// 2. RESPONSE SCHEMA (Zod)\n// ──────────────────────────────────────────────────────────────\n\nconst OpportunitySchema = z.object({\n reasoning: z.string().describe('Third-party analytical explanation of why this opportunity exists. Mentions both users by role. Written for other LLM agents to understand the match.'),\n score: z.number().min(0).max(100).describe('Relevance score 0-100'),\n valencyRole: z.enum(['Agent', 'Patient', 'Peer']).describe(\"The semantic role of the Candidate relative to the Source\"),\n sourceId: z.string().describe('The user ID of the source'),\n candidateId: z.string().describe('The user ID of the candidate'),\n});\n\nconst responseFormat = z.object({\n opportunities: z.array(OpportunitySchema).describe(\"List of opportunities identified\"),\n});\n\n// ─── Entity-bundle evaluator (C1): types and output schema with actors ───\n\nexport interface EvaluatorEntity {\n userId: string;\n profile: {\n name?: string;\n bio?: string;\n location?: string;\n interests?: string[];\n skills?: string[];\n context?: string;\n };\n intents?: Array<{\n intentId: string;\n payload: string;\n summary?: string;\n }>;\n networkId: string;\n evidenceKey?: string;\n ragScore?: number;\n matchedVia?: string;\n evidence?: OpportunityEvidence[];\n}\n\nexport interface EvaluatorInput {\n /** The user who triggered discovery (for context, not special treatment). */\n discovererId: string;\n /** All relevant entities. In introduction mode, only the people being introduced (no introducer). */\n entities: EvaluatorEntity[];\n /** Existing opportunities for deduplication. */\n existingOpportunities?: string;\n /** When true, DISCOVERER is the introducer; reasoning and actors must be only among ENTITIES. */\n introductionMode?: boolean;\n /** Name of the introducer (for attribution in reasoning when introductionMode is true). */\n introducerName?: string;\n /** Optional hint/context from the introducer about why these people should meet. */\n introductionHint?: string;\n /** Optional discovery query (e.g. from chat). When set, only suggest opportunities where candidates clearly match this request. */\n discoveryQuery?: string;\n /** Pre-rendered network context markdown, keyed by networkId. */\n networkContexts?: Record<string, string>;\n}\n\nconst ActorSchema = z.object({\n userId: z.string(),\n role: z.enum(['agent', 'patient', 'peer']),\n intentId: z.string().nullable().describe('If the match is intent-driven, the specific intent ID; null otherwise'),\n evidenceKey: z.string().nullable().optional().describe('Stable evidence key for the matched entity; null if unknown'),\n});\n\nconst OpportunityWithActorsSchema = z.object({\n reasoning: z.string(),\n score: z.number().min(0).max(100),\n actors: z.array(ActorSchema).min(2).describe('All actors in this opportunity with their roles'),\n});\n\nconst entityBundleResponseFormat = z.object({\n opportunities: z.array(OpportunityWithActorsSchema).describe('List of opportunities (0..N)'),\n});\n\nexport type EvaluatorActor = z.infer<typeof ActorSchema>;\nexport type EvaluatedOpportunityWithActors = z.infer<typeof OpportunityWithActorsSchema>;\nexport type EvaluatorOutputBundle = z.infer<typeof entityBundleResponseFormat>;\n\n// ──────────────────────────────────────────────────────────────\n// 3. TYPE DEFINITIONS\n// ──────────────────────────────────────────────────────────────\n\ntype Opportunity = z.infer<typeof OpportunitySchema>;\ntype EvaluatorOutput = z.infer<typeof responseFormat>;\n\n// Define CandidateProfile type (simplified for now, ideally imported from shared types)\nexport interface CandidateProfile {\n userId: string;\n identity?: { name?: string; bio?: string; location?: string };\n attributes?: { interests?: string[]; skills?: string[] };\n narrative?: { context?: string };\n score?: number; // Search score\n}\n\ninterface OpportunityEvaluatorOptions {\n minScore?: number;\n limit?: number;\n hydeDescription?: string;\n /** Pre-inferred lenses (if not provided, lens inference runs automatically in HyDE graph). */\n lenses?: Lens[];\n existingOpportunities?: string;\n candidates?: CandidateProfile[]; // For direct evaluation\n filter?: Record<string, unknown>;\n initialStatus?: OpportunityStatus;\n}\n\n// ──────────────────────────────────────────────────────────────\n// 4. CLASS DEFINITION\n// ──────────────────────────────────────────────────────────────\n\n/** Optional test double for entity-bundle model (avoids live LLM in unit tests). */\nexport type OpportunityEvaluatorOptionsConstructor = {\n entityBundleModel?: Runnable;\n};\n\nexport class OpportunityEvaluator {\n private model: Runnable;\n private entityBundleModel: Runnable;\n\n constructor(options?: OpportunityEvaluatorOptionsConstructor) {\n this.model = createStructuredModel(\"opportunityEvaluator\", responseFormat, {\n name: \"opportunity_evaluator\"\n });\n this.entityBundleModel = options?.entityBundleModel ?? createStructuredModel(\"opportunityEvaluator\", entityBundleResponseFormat, {\n name: \"opportunity_evaluator_entity_bundle\"\n });\n }\n\n /**\n * Main Entry Point: Batch analysis of candidates.\n * \n * @param sourceProfileContext - The profile context string of the user we are finding opportunities FOR.\n * @param candidates - List of potential matches to evaluate.\n * @param options - Config (minScore, valid types, etc).\n * @returns A sorted list of high-value `Opportunity` objects.\n */\n @Timed()\n public async invoke(\n sourceProfileContext: string,\n candidates: CandidateProfile[],\n options: OpportunityEvaluatorOptions\n ): Promise<Opportunity[]> {\n const minScore = options.minScore || 70;\n\n invokeLog.verbose('Analyzing candidates', { count: candidates.length });\n\n if (candidates.length === 0) {\n logger.verbose('No candidates provided.');\n return [];\n }\n\n const opportunities: Opportunity[] = [];\n\n // Analyze each candidate in parallel (bounded)\n const promises = candidates.map(async (candidate) => {\n // Pass existing opportunities context if provided\n const existingContext = options.existingOpportunities || '';\n return this.analyzeMatch(sourceProfileContext, candidate, candidate.userId, existingContext);\n });\n\n const results = await Promise.all(promises);\n results.flat().forEach(op => {\n if (op.score >= minScore) {\n opportunities.push(op as Opportunity);\n }\n });\n\n // Sort by score and take top 1\n const out = opportunities.sort((a, b) => b.score - a.score).slice(0, 1);\n invokeLog.verbose('Done', { accepted: out.length });\n return out;\n }\n\n /**\n * Analyze a single match pair using the primary Agent model.\n */\n private async analyzeMatch(\n sourceProfileContext: string,\n candidateProfile: CandidateProfile,\n candidateUserId: string,\n existingOpportunities: string\n ): Promise<Opportunity[]> {\n try {\n // Construct the source context part of the prompt\n const sourceContext = `SOURCE PROFILE:\\n${sourceProfileContext}`;\n\n const existingContextPart = existingOpportunities\n ? `\\nEXISTING OPPORTUNITIES (Deduplication Context):\\n${existingOpportunities}\\n`\n : '';\n\n // Create candidate context using template string\n const candidateContext = `\n ID: ${candidateUserId}\n Name: ${candidateProfile.identity?.name || 'Unknown'}\n Bio: ${candidateProfile.identity?.bio || ''}\n Location: ${candidateProfile.identity?.location || ''}\n Interests: ${candidateProfile.attributes?.interests?.join(', ') || ''}\n Skills: ${candidateProfile.attributes?.skills?.join(', ') || ''}\n\n Context: ${candidateProfile.narrative?.context || ''}\n `;\n\n const messages = [\n new SystemMessage(systemPrompt),\n new HumanMessage(`${sourceContext}\\n${existingContextPart}\\nCANDIDATE PROFILE:\\n${candidateContext}`)\n ];\n\n const result = await invokeWithAbortSignal(this.model, messages);\n const output = responseFormat.parse(result);\n\n const mappedOpportunities = output.opportunities.map((op: Opportunity) => ({\n ...op,\n reasoning: stripUuids(op.reasoning),\n candidateId: candidateUserId,\n }));\n\n return mappedOpportunities;\n } catch (e: unknown) {\n const message = e instanceof Error ? e.message : String(e);\n logger.warn('Analysis failed for candidate', { candidateUserId, message });\n throw e;\n }\n }\n\n /**\n * Entity-bundle entry point (C3): single LLM call with all entities, returns 0..N opportunities with actors.\n */\n @Timed()\n public async invokeEntityBundle(\n input: EvaluatorInput,\n options: { minScore?: number; returnAll?: boolean } = {}\n ): Promise<EvaluatedOpportunityWithActors[]> {\n const minScore = options.minScore ?? 70;\n const returnAll = options.returnAll ?? false;\n const totalEntities = input.entities?.length ?? 0;\n if (!input.entities?.length) {\n invokeEntityBundleLog.verbose('No entities.');\n return [];\n }\n const existingPart = input.existingOpportunities\n ? `\\nEXISTING OPPORTUNITIES:\\n${input.existingOpportunities}\\n`\n : '';\n const introModePart = input.introductionMode\n ? `\\nINTRODUCTION MODE: This is a human-curated introduction. ${input.introducerName ?? 'The introducer'} (DISCOVERER: ${input.discovererId}) explicitly wants these people to connect. This is NOT an automatic discovery — a real person saw value in this connection.\n\nCRITICAL REASONING INSTRUCTIONS FOR INTRODUCTIONS:\n- Your reasoning MUST acknowledge that this is an introduction initiated by ${input.introducerName ?? 'the introducer'}, not a system-discovered match.\n- Start reasoning with something like \"${input.introducerName ?? 'The introducer'} is connecting [Name A] and [Name B] because...\" or \"This introduction by ${input.introducerName ?? 'the introducer'} brings together...\"\n- Even if the parties' intents or profiles don't obviously overlap, the introduction is still valid because the introducer saw the connection. Explain what the introducer likely sees in this match.\n- If explicit intents align, mention them — but frame it as supporting the introducer's judgment, not as the primary reason.${input.introductionHint ? `\\nINTRODUCER'S CONTEXT: \"${input.introductionHint}\" — use this to inform your reasoning about why the introducer made this connection.` : ''}\n- Actors must refer ONLY to the ENTITIES below (the people being introduced). Do not include the DISCOVERER as an actor.\n- You must output exactly two actors per opportunity (the two people being introduced). The introducer is added separately; do not include them in actors.\n- Be generous with scoring (70+ for any introduction with a plausible basis, since a human made the judgment).\n`\n : '';\n const discoveryQueryPart = input.discoveryQuery?.trim()\n ? `\\nDISCOVERY REQUEST: The user asked: \"${input.discoveryQuery.trim()}\"\n\nCRITICAL SCORING RULES FOR DISCOVERY REQUESTS:\n\n0. QUERY IS PRIMARY: The DISCOVERY REQUEST above is the primary evaluation criterion. The source user's stored INTENTS (if listed below) are background context — use them ONLY to fill in blanks when the query is too broad or vague to evaluate on its own. If the query is specific enough to score candidates, score strictly against the query and IGNORE stored intents. Never let a stored intent override or replace the query as the basis for scoring. When a specific query imposes a hard identity, role, capability, or location constraint, candidates that only match background context or thematic adjacency must score strictly below 30 and be omitted.\n\n1. CLASSIFY THE QUERY TYPE — determine the predication type before scoring:\n\n IDENTITY/ROLE QUERY: The query term is a count noun or sortal that can serve as a predicate nominal — \"X IS A [query]\" is grammatical and meaningful. Examples: \"samurai\", \"investors\", \"designers\", \"nurses\", \"founders\". The user wants someone who IS that thing — not someone who works with, creates content about, or is tangentially associated with it.\n → Test: Can you say \"this person IS a [query term]\"? If yes, this is an identity query.\n → CRITICAL: Subject-matter contact is NOT identity. A character designer who draws samurai IS NOT a samurai. An engineer who raised funding IS NOT an investor. A journalist who covers finance IS NOT an investor. IS-A is not transitive through creative subject matter, professional adjacency, or domain familiarity.\n\n TOPICAL/DOMAIN QUERY: The query is a field, domain, or abstract topic — \"X IS A [query]\" is ungrammatical. Examples: \"machine learning\", \"sustainability\", \"blockchain\". The user wants someone who works in or has expertise in this domain.\n → Test: \"This person IS a machine learning\" makes no sense → topical query.\n\n NEED/CAPABILITY QUERY: The query contains a purposive frame — \"someone to help with X\", \"need a Y\", \"looking for Z\". The user describes a specific need.\n\n2. APPLY TYPE-SPECIFIC SCORING:\n\n For IDENTITY/ROLE queries — apply a categorical gate:\n - First, make a binary IS-A judgment for each candidate: does this person's PRIMARY professional identity or self-description place them within the extension of the query term?\n - IS-A = TRUE → score 75-100 (modulated by intent alignment and profile richness)\n - IS-A = FALSE → score <30. Hard ceiling. Return no opportunity. Do NOT award surfacing credit for subject-matter adjacency, thematic association, creative output involving the query term, having received the thing, or being adjacent to people who are the thing.\n - Do NOT rescue a failed IS-A judgment with background intents. If the user searched \"samurai\" and the candidate is a visual artist (IS-A = false), the candidate's alignment with a background intent like \"connect with visual artists\" does not matter — the explicit query takes priority. If the user searched \"investors\" and the candidate merely raised money, score <30. If the user searched \"scouts\" and the candidate was scouted, score <30. If the user searched \"art director\", a solo illustrator is not enough: require explicit evidence that the candidate owns visual direction, leads/briefs other artists, manages a visual system, or is described as an art/creative director. General visual taste, composition skill, or illustration output alone is a failed IS-A judgment and must score <30.\n\n For TOPICAL/DOMAIN queries — apply gradient scoring:\n - 90-100: Deep expertise, primary focus area\n - 70-89: Substantial engagement, meaningful work in the domain\n - 50-69: Peripheral involvement, tangential connection\n - <50: No meaningful engagement\n\n For NEED/CAPABILITY queries — apply capability + availability scoring:\n - Score based on both capability match AND openness to the described collaboration\n - 90-100: Perfect capability match with explicit availability\n - 70-89: Strong capability, plausible availability\n\n3. SAME-SIDE CHECK: If the candidate's intents show they are ALSO SEEKING what the discoverer is seeking (e.g., both looking for investors, both looking for co-founders), this is a same-side match. Score <30 regardless of keyword overlap in bios. The candidate must BE or OFFER what the discoverer is looking for, not also be looking for it.\n\n4. LOCATION ENFORCEMENT: If the discovery request mentions a specific location (e.g., \"in SF\", \"based in London\", \"Istanbul\"), check each candidate's profile.location:\n - KNOWN MISMATCH (e.g., request says \"SF\" but candidate is \"New York\"): Score <30 and return no opportunity. State the mismatch in reasoning if returning diagnostic output. A known location mismatch is a hard query failure, even when the candidate otherwise matches the source's background interests.\n - UNKNOWN/EMPTY location: Do not penalize, and do NOT reject solely because the query contains a location. Treat location as unverified/missing evidence, then score on the non-location parts of the query. If the candidate strongly satisfies the requested role/domain (e.g., query \"Unreal Engine developers in SF\" and candidate is clearly an Unreal Engine developer with empty location), score normally enough to surface (typically 60-89) and note that location is unverified.\n - MATCH or COMPATIBLE (e.g., \"Bay Area\" ≈ \"SF\", \"Remote\" ≈ any): Score normally.\n`\n : '';\n const hasDiscoveryQuery = !!input.discoveryQuery?.trim();\n const entitiesBlock = input.entities.map((e) => {\n const isSource = e.userId === input.discovererId;\n // When an explicit discovery query is active, label the source user's stored\n // intents as background context so the LLM treats the query as primary.\n const intentsLabel = isSource && hasDiscoveryQuery ? 'BACKGROUND INTENTS (use only if query is too vague)' : 'INTENTS';\n const intentsPart = e.intents?.length\n ? `\\n ${intentsLabel}:\\n${e.intents.map((i) => ` - ${i.intentId}: ${i.payload}`).join('\\n')}`\n : '';\n // Mask the discoverer's name so the LLM cannot leak it into reasoning.\n // The system prompt already says \"use third-party references\", but the LLM\n // ignores this when the actual name is visible. Masking it forces role-based\n // language (\"the source user is looking…\" instead of \"Alice is looking…\").\n const displayName = e.userId === input.discovererId\n ? '(source user)'\n : (e.profile.name ?? '');\n return `\n USER: ${e.userId}\n INDEX: ${e.networkId}\n EVIDENCE KEY: ${e.evidenceKey ?? '—'}\n PROFILE: Name: ${displayName} | Bio: ${e.profile.bio ?? ''} | Location: ${e.profile.location ?? ''} | Interests: ${e.profile.interests?.join(', ') ?? ''} | Skills: ${e.profile.skills?.join(', ') ?? ''} | Context: ${e.profile.context ?? ''}${intentsPart}\n RAG SCORE: ${e.ragScore ?? '—'}\n MATCHED VIA: ${e.matchedVia ?? '—'}\n EVIDENCE:\n${renderOpportunityEvidenceForPrompt(e.evidence ?? [])}`;\n }).join('\\n');\n const networkContextPart = input.networkContexts && Object.keys(input.networkContexts).length > 0\n ? `\\n\\nNETWORK CONTEXTS:\\n${Object.entries(input.networkContexts).map(([nid, ctx]) => `[INDEX: ${nid}]\\n${ctx}`).join('\\n\\n')}`\n : '';\n const humanContent = `DISCOVERER: ${input.discovererId}${introModePart}${discoveryQueryPart}${networkContextPart}\\n\\nENTITIES:\\n${entitiesBlock}${existingPart}`;\n const messages = [\n new SystemMessage(entityBundleSystemPrompt),\n new HumanMessage(humanContent),\n ];\n let parsedTotal = 0;\n try {\n const result = await invokeWithAbortSignal(this.entityBundleModel, messages);\n const parsed = entityBundleResponseFormat.parse(result);\n for (const op of parsed.opportunities) {\n op.reasoning = stripUuids(op.reasoning);\n }\n parsedTotal = parsed.opportunities.length;\n // Persistence safety is deterministic and precedes every score/returnAll\n // path. Network/context placement is not typed support provenance, so an\n // unsupported affiliation or presence claim rejects the whole result.\n const claimSafe = parsed.opportunities.filter(\n (op) => !hasUnsupportedOpportunityClaim(op.reasoning),\n );\n const introGuard =\n input.introductionMode\n ? claimSafe.filter((op) => op.actors.length === 2)\n : claimSafe;\n const filtered = introGuard.filter((op) => op.score >= minScore);\n invokeEntityBundleLog.verbose('Done', {\n total: parsed.opportunities.length,\n afterClaimGuard: claimSafe.length,\n afterIntroGuard: introGuard.length,\n accepted: filtered.length,\n returnAll,\n });\n return returnAll ? introGuard : filtered;\n } catch (llmError) {\n invokeEntityBundleLog.error('Failed', {\n discovererId: input.discovererId,\n totalEntities,\n parsedTotal,\n minScore,\n llmError,\n });\n throw llmError;\n }\n }\n\n /**\n * Factory method to expose the agent as a LangChain tool.\n * Simplified to only accept direct evaluation arguments.\n * PURE: Does not perform any database lookups.\n */\n public static asTool() {\n return tool(\n async (args: {\n sourceProfileContext: string;\n candidatesJson?: string;\n minScore?: number;\n }) => {\n const agent = new OpportunityEvaluator();\n\n const sourceProfileContext = args.sourceProfileContext;\n\n let candidates: CandidateProfile[] = [];\n if (args.candidatesJson) {\n try {\n candidates = JSON.parse(args.candidatesJson);\n } catch (e) {\n logger.error(\"Failed to parse candidates JSON\");\n }\n }\n\n const options: OpportunityEvaluatorOptions = {\n minScore: args.minScore,\n };\n\n return await agent.invoke(sourceProfileContext, candidates, options);\n },\n {\n name: 'opportunity_evaluator',\n description: 'Evaluates candidates against a source profile. SOURCE PROFILE CONTEXT MUST BE PROVIDED.',\n schema: z.object({\n sourceProfileContext: z.string().describe('The resolved source user profile context'),\n candidatesJson: z.string().optional().describe('JSON string list of Candidates'),\n minScore: z.number().optional().describe('Minimum score to accept a match')\n })\n }\n );\n }\n}\n"]}
|
|
1
|
+
{"version":3,"file":"opportunity.evaluator.js","sourceRoot":"/","sources":["opportunity/opportunity.evaluator.ts"],"names":[],"mappings":"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","sourcesContent":["import type { Runnable } from \"@langchain/core/runnables\";\nimport { HumanMessage, SystemMessage } from \"@langchain/core/messages\";\nimport { tool } from \"@langchain/core/tools\";\nimport { z } from \"zod\";\nimport { protocolLogger } from \"../shared/observability/protocol.logger.js\";\nimport type { Lens } from \"../shared/hyde/lens.inferrer.js\";\nimport type { OpportunityStatus } from \"../shared/interfaces/database.interface.js\";\nimport { Timed } from \"../shared/observability/performance.js\";\nimport { stripUuids } from \"./opportunity.presentation.js\";\nimport { createStructuredModel } from \"../shared/agent/model.config.js\";\nimport { invokeWithAbortSignal } from \"../shared/agent/model-signal.js\";\nimport type { OpportunityEvidence } from '../shared/schemas/network-assignment.schema.js';\nimport { renderOpportunityEvidenceForPrompt } from './opportunity.evidence.js';\nimport { hasUnsupportedOpportunityClaim } from './opportunity.claim-safety.js';\n\nconst logger = protocolLogger(\"OpportunityEvaluator\");\nconst invokeLog = protocolLogger(\"OpportunityEvaluator:invoke\");\nconst invokeEntityBundleLog = protocolLogger(\"OpportunityEvaluator:invokeEntityBundle\");\n\n\n// ──────────────────────────────────────────────────────────────\n// 1. SYSTEM PROMPT\n// ──────────────────────────────────────────────────────────────\n\n\nconst systemPrompt = `\n You are an expert \"Opportunity Matcher\" and super-connector.\n Your Goal: Analyze a Source User's profile against a Candidate User's profile to identify A SINGLE HIGH-VALUE opportunity.\n\n Input:\n - Source Context: The Source User's own Profile.\n - Candidate Profile (JSON)\n - Existing Opportunities (Context of matches already made)\n\n Output:\n - A list containing EXACTLY ONE \"Opportunity\" if a match exists.\n - If NO match exists, return an empty list.\n - Score (0-100): How strong is this match?\n - 90-100: \"Must Meet\" (Perfect alignment).\n - 70-89: \"Should Meet\" (Strong overlaps, clear potential).\n - <70: No opportunity (Return empty list).\n\n **CRITICAL: VALENCY & REASONING**\n \n 1. **Valency Analysis**:\n - Determine the semantic role of the Candidate relative to the Source's goal.\n - \"Agent\": The Candidate CAN DO something for the Source (e.g., Source needs a dev, Candidate IS a dev).\n - \"Patient\": The Candidate NEEDS something from the Source (e.g., Source is a mentor, Candidate needs mentoring).\n - \"Peer\": Symmetric collaboration.\n\n 2. **Reasoning (Third-Party Analytical Perspective)**:\n - **reasoning**: A neutral, third-party explanation of why this opportunity exists. Written for other LLM agents to read and understand.\n - Mention BOTH users by their roles (e.g. \"The source user\" and \"The candidate\") and explain why they are a match.\n - Do NOT address either user as \"you\". Write from an objective observer's perspective.\n - Include what each side brings to the connection and why it is mutually valuable.\n - NEVER leak private intents. If someone's intent is confidential, describe their relevant attributes instead.\n\n **VISIBILITY IMPLICATIONS OF ROLE ASSIGNMENT**\n\n The valency role you assign directly controls who sees the opportunity and when:\n - \"Agent\" (helper/provider): LAST to see the opportunity — only after the Patient has committed to reaching out. Agents are protected from noise; they only see high-intent connections.\n - \"Patient\" (seeker/requester): Sees the opportunity early and decides whether to reach out.\n - \"Peer\" (symmetric): Both parties see the opportunity immediately and either can initiate.\n\n Choose the role carefully — it determines the entire flow of how the connection unfolds.\n\n Rules:\n 1. SYNTHESIS (CRITICAL): If multiple distinct match angles exist, SYNTHESIZE them into a SINGLE, robust opportunity.\n 2. NEVER address either user directly — always use third-party references (\"the source\", \"the candidate\").\n 3. COMPREHENSIVE: The single opportunity must capture ALL the value of the connection.\n 4. Be specific about the \"Why\" for BOTH sides in the reasoning.\n 5. DEDUPLICATION: Do NOT suggest opportunities that duplicate \"Existing Opportunities\".\n 6. Do not suggest an opportunity if the source and candidate clearly already know each other (e.g. same company, co-founders, same team).\n 7. SAME-SIDE MATCHING: If both the source and candidate are SEEKING the same resource (e.g., both looking for investors, both seeking a co-founder), this is not an opportunity. Return an empty list unless one side clearly OFFERS what the other SEEKS.\n 8. NETWORK CONTEXT IS NOT PERSON EVIDENCE: A network assignment, title, type, or metadata is retrieval and thematic context only. It is never proof that a person attended an event, belongs to a group, resides in a place, knows anyone else from the network, or shared a session, time, or location with anyone. Do not state or infer those claims in opportunity reasoning.\n`;\n\n// Entity-bundle system prompt (C2): entities + four match patterns + actors output\n// NOTE: entityBundleSystemPrompt uses a >= 30 threshold (permissive) while\n// systemPrompt uses >= 70 (strict). This is intentional: batch mode casts a wide\n// net so the calling pipeline can apply its own filters; pairwise mode is strict\n// because it returns a single yes/no decision per candidate pair.\nconst entityBundleSystemPrompt = `\nYou are an expert \"Opportunity Matcher\" and super-connector.\nYour Goal: Analyze a set of entities (people), each with a profile and optional intents, and identify HIGH-VALUE opportunities among them.\n\nInput:\n- DISCOVERER: The user ID who triggered discovery (for context; they may or may not be in the entity list).\n- ENTITIES: A set of entities. Each entity has:\n - userId, networkId (the index through which they were found)\n - evidenceKey (stable key for the matched resource when available)\n - profile: name, bio, location, interests, skills, context\n - intents (optional): list of { intentId, payload, summary } — some entities are profile-only, some have intents\n - ragScore, matchedVia (how they were found)\n - evidence (typed retrieval evidence: discovery kind, network, score, lens, source/candidate ids)\n- EXISTING OPPORTUNITIES: Context of matches already made (for deduplication).\n\nBEFORE SCORING — determine role satisfiability:\n\nDefinitions:\n SUBSTITUTIVE ROLE: The candidate can directly fill the open position in the discoverer's intent. The candidate IS the person/entity the discoverer is seeking. Example: discoverer seeks \"co-founder\" → candidate is an engineer willing to co-found.\n COMPLEMENTARY ROLE: The candidate's contribution is defined relative to the seeker-sought relation from the outside — they fund, advise, recruit for, or enable the sought relationship rather than participating in it as the target. Example: discoverer seeks \"co-founder\" → candidate is a VC (funds the company, does not co-found it).\n\nStep 1 — Identify the open argument in each discoverer intent: what type of person or entity would satisfy the intent if found?\nStep 2 — For each candidate, ask: can this candidate directly fill that open argument position?\n YES → substitutive role. Proceed to scoring.\n NO → complementary role. Apply Rule 7 (score ≤ 30, return no opportunity).\nStep 3 — Contextual override: if the candidate's profile contains explicit evidence that they currently function in the substitutive role (e.g., a former investor who is now building full-time as a technical co-founder), re-evaluate Step 2 against their current role, not their categorical label.\n\nMatch patterns to consider:\n1. Profile-to-profile: Complementary backgrounds (skills, interests, location).\n2. Profile-to-intents+profile: Someone's skills/background match another's stated goals (intents).\n3. Intents+profile-to-profile: Someone's stated goals match another's skills/background.\n4. Intents+profile-to-intents+profile: Complementary or reciprocal goals between two or more people.\n\nOutput:\n- A list of 0..N opportunities. Each opportunity has:\n - reasoning: Third-party analytical explanation (for other LLM agents). Mention entities by role. Do NOT use \"you\". Never leak private intents.\n - score: 0-100.\n - 90-100: Must Meet — candidate's PRIMARY role directly matches what the discoverer seeks.\n Example: discoverer seeks \"AI/ML co-founder\" → candidate IS an AI/ML engineer who wants to co-found.\n - 70-89: Should Meet — meaningful overlap on role type AND complementary intent.\n - 50-69: Worth Considering — tangential overlap only.\n - <30 (return empty): Complementary-role mismatch (candidate cannot fill the discoverer's open argument position), same-side match, or already acquainted.\n Example: discoverer seeks \"co-founder\" → candidate is a VC investor. The investor's contribution is external to the co-founding relation; they cannot substitute into it. Score 0.\n - IMPORTANT: Include ALL reasonable matches with scores >= 30, and ONLY those. Any candidate you would reject (complementary-role mismatch, same-side, already-acquainted, or a hard location mismatch) must score strictly below 30 and be omitted entirely — the surfacing threshold is 30, so a rejected candidate parked at exactly 30 would be wrongly surfaced.\n - actors: At least 2 actors per opportunity. Each actor has:\n - userId\n - role: \"agent\" (can do something for others), \"patient\" (needs something from others), \"peer\" (symmetric collaboration)\n - intentId (optional): if the match is intent-driven, the specific intent ID for that user\n - evidenceKey (optional): copy the entity evidenceKey when the actor is driven by a specific listed entity\n\nVISIBILITY (role controls who sees the opportunity when):\n- agent: Last to see — after the patient has committed to reaching out.\n- patient: Sees early and decides whether to reach out.\n- peer: Both see immediately; either can initiate.\n\nRules:\n1. ONE OPPORTUNITY PER CANDIDATE: Create a SEPARATE opportunity for EACH candidate who matches. Do NOT combine multiple candidates into one opportunity. Each opportunity should have exactly 2 actors: the DISCOVERER and ONE candidate.\n2. INDIVIDUAL REASONING: Write specific reasoning for EACH candidate individually. Do NOT mention other candidates in the reasoning. Focus on why THIS specific candidate matches THIS specific discoverer.\n3. DEDUPLICATION: Do not suggest opportunities that duplicate Existing Opportunities.\n4. Write reasoning from an objective observer's perspective; be specific about the \"Why\" for each side.\n5. When in introduction mode, each opportunity must have exactly two actors — the two people being introduced. The discoverer (introducer) is added by the system and must not be included in your actors list.\n6. ALREADY KNOW EACH OTHER: Do NOT suggest an opportunity if the entities clearly already know each other. Examples: co-founders of the same company, same team at the same organization, same employer, or any relationship that is obviously existing from their profiles (bio, context). When in doubt, if both profiles mention the same company/org/team in a way that implies they work together, return an empty list for that pair.\n7. ROLE-SATISFIABILITY (evaluate before scoring): A candidate satisfies a discoverer's intent only if they can fill the SUBSTITUTIVE ROLE — the open argument position in the intent (the type of person the discoverer is seeking). A candidate in a COMPLEMENTARY ROLE (one that funds, advises, recruits for, or otherwise enables the sought relation from outside it) does not satisfy the intent, regardless of how closely associated their domain is.\n COMPLEMENTARY-ROLE CAP: If the candidate occupies a complementary rather than substitutive role relative to the discoverer's intent, score below 30 (e.g. 0–20). Return no opportunity. A \"reject\" score must be strictly below 30 so the candidate is not surfaced — never park a rejected candidate at exactly 30.\n CONTEXTUAL OVERRIDE: If the candidate's profile contains explicit evidence that they currently function in the substitutive role (not merely historically or tangentially), treat them as substitutive and score normally.\n8. SAME-SIDE MATCHING: Before scoring, check whether the DISCOVERER and CANDIDATE are both SEEKING the same thing. Look at both parties' intents for directionality:\n - SEEKING signals: \"looking for\", \"seeking\", \"want to find\", \"need\", \"raising\", \"hiring\"\n - OFFERING signals: \"can offer\", \"expert in\", \"investing in\", \"mentoring\", \"available for\"\n If both parties have SEEKING intents targeting the same resource (e.g., both seeking investors, both seeking co-founders, both seeking mentorship), this is NOT an opportunity — score <30. An opportunity requires one side to OFFER what the other SEEKS.\n9. LOCATION MATCHING: When the DISCOVERY REQUEST mentions a specific location (city, region, or country):\n a. If a candidate's profile.location is KNOWN and clearly does NOT match the requested location (different city/region), score below 30 (e.g. 0–25) and return no opportunity. An explicitly requested location is a hard constraint: a known geographic mismatch excludes the candidate.\n b. If a candidate's profile.location is UNKNOWN, EMPTY, or AMBIGUOUS, do NOT penalize — allow them through and score based on other factors. Note in reasoning that their location is unverified.\n c. If a candidate's profile.location matches or is reasonably close (e.g., \"Bay Area\" matches \"San Francisco\", \"Remote\" matches any location), score normally.\n d. \"Remote\" or \"Global\" locations are compatible with any requested location.\n10. EVENT NETWORK CONTEXT: When NETWORK CONTEXTS includes an event-type network (identified by dates, location, schedule, or themes):\n a. TEMPORAL RELEVANCE: Event start/end dates may affect whether an explicitly stated event-related intent is timely. After an event ends, event-specific intents lose relevance unless the connection has lasting value.\n b. THEME ALIGNMENT: Event themes or tracks may be used as thematic retrieval context when a person's own profile or intent independently aligns with them.\n c. PROHIBITED INFERENCE: Network assignment, title, type, schedule, location, and metadata are retrieval context only. They are never proof that any person attended or will attend, belongs to the event or network, resides in its location, knows another person from it, or shared a session, time, place, or location with anyone. Do not infer co-attendance, same-place/same-time presence, session attendance, or peer roles from network co-membership.\n`;\n\n// ──────────────────────────────────────────────────────────────\n// 2. RESPONSE SCHEMA (Zod)\n// ──────────────────────────────────────────────────────────────\n\nconst OpportunitySchema = z.object({\n reasoning: z.string().describe('Third-party analytical explanation of why this opportunity exists. Mentions both users by role. Written for other LLM agents to understand the match.'),\n score: z.number().min(0).max(100).describe('Relevance score 0-100'),\n valencyRole: z.enum(['Agent', 'Patient', 'Peer']).describe(\"The semantic role of the Candidate relative to the Source\"),\n sourceId: z.string().describe('The user ID of the source'),\n candidateId: z.string().describe('The user ID of the candidate'),\n});\n\nconst responseFormat = z.object({\n opportunities: z.array(OpportunitySchema).describe(\"List of opportunities identified\"),\n});\n\n// ─── Entity-bundle evaluator (C1): types and output schema with actors ───\n\nexport interface EvaluatorEntity {\n userId: string;\n profile: {\n name?: string;\n bio?: string;\n location?: string;\n interests?: string[];\n skills?: string[];\n context?: string;\n };\n intents?: Array<{\n intentId: string;\n payload: string;\n summary?: string;\n }>;\n networkId: string;\n evidenceKey?: string;\n ragScore?: number;\n matchedVia?: string;\n evidence?: OpportunityEvidence[];\n}\n\nexport interface EvaluatorInput {\n /** The user who triggered discovery (for context, not special treatment). */\n discovererId: string;\n /** All relevant entities. In introduction mode, only the people being introduced (no introducer). */\n entities: EvaluatorEntity[];\n /** Existing opportunities for deduplication. */\n existingOpportunities?: string;\n /** When true, DISCOVERER is the introducer; reasoning and actors must be only among ENTITIES. */\n introductionMode?: boolean;\n /** Name of the introducer (for attribution in reasoning when introductionMode is true). */\n introducerName?: string;\n /** Optional hint/context from the introducer about why these people should meet. */\n introductionHint?: string;\n /** Optional discovery query (e.g. from chat). When set, only suggest opportunities where candidates clearly match this request. */\n discoveryQuery?: string;\n /** Pre-rendered network context markdown, keyed by networkId. */\n networkContexts?: Record<string, string>;\n}\n\nconst ActorSchema = z.object({\n userId: z.string(),\n role: z.enum(['agent', 'patient', 'peer']),\n intentId: z.string().nullable().describe('If the match is intent-driven, the specific intent ID; null otherwise'),\n evidenceKey: z.string().nullable().optional().describe('Stable evidence key for the matched entity; null if unknown'),\n});\n\nconst OpportunityWithActorsSchema = z.object({\n reasoning: z.string(),\n score: z.number().min(0).max(100),\n actors: z.array(ActorSchema).min(2).describe('All actors in this opportunity with their roles'),\n});\n\nconst entityBundleResponseFormat = z.object({\n opportunities: z.array(OpportunityWithActorsSchema).describe('List of opportunities (0..N)'),\n});\n\nexport type EvaluatorActor = z.infer<typeof ActorSchema>;\nexport type EvaluatedOpportunityWithActors = z.infer<typeof OpportunityWithActorsSchema>;\nexport type EvaluatorOutputBundle = z.infer<typeof entityBundleResponseFormat>;\n\n// ──────────────────────────────────────────────────────────────\n// 3. TYPE DEFINITIONS\n// ──────────────────────────────────────────────────────────────\n\ntype Opportunity = z.infer<typeof OpportunitySchema>;\ntype EvaluatorOutput = z.infer<typeof responseFormat>;\n\n// Define CandidateProfile type (simplified for now, ideally imported from shared types)\nexport interface CandidateProfile {\n userId: string;\n identity?: { name?: string; bio?: string; location?: string };\n attributes?: { interests?: string[]; skills?: string[] };\n narrative?: { context?: string };\n score?: number; // Search score\n}\n\ninterface OpportunityEvaluatorOptions {\n minScore?: number;\n limit?: number;\n hydeDescription?: string;\n /** Pre-inferred lenses (if not provided, lens inference runs automatically in HyDE graph). */\n lenses?: Lens[];\n existingOpportunities?: string;\n candidates?: CandidateProfile[]; // For direct evaluation\n filter?: Record<string, unknown>;\n initialStatus?: OpportunityStatus;\n /** Optional caller cancellation; does not change retry/fallback policy. */\n signal?: AbortSignal;\n}\n\n// ──────────────────────────────────────────────────────────────\n// 4. CLASS DEFINITION\n// ──────────────────────────────────────────────────────────────\n\n/** Optional test double for entity-bundle model (avoids live LLM in unit tests). */\nexport type OpportunityEvaluatorOptionsConstructor = {\n entityBundleModel?: Runnable;\n};\n\nexport class OpportunityEvaluator {\n private model: Runnable;\n private entityBundleModel: Runnable;\n\n constructor(options?: OpportunityEvaluatorOptionsConstructor) {\n this.model = createStructuredModel(\"opportunityEvaluator\", responseFormat, {\n name: \"opportunity_evaluator\"\n });\n this.entityBundleModel = options?.entityBundleModel ?? createStructuredModel(\"opportunityEvaluator\", entityBundleResponseFormat, {\n name: \"opportunity_evaluator_entity_bundle\"\n });\n }\n\n /**\n * Main Entry Point: Batch analysis of candidates.\n * \n * @param sourceProfileContext - The profile context string of the user we are finding opportunities FOR.\n * @param candidates - List of potential matches to evaluate.\n * @param options - Config (minScore, valid types, etc).\n * @returns A sorted list of high-value `Opportunity` objects.\n */\n @Timed()\n public async invoke(\n sourceProfileContext: string,\n candidates: CandidateProfile[],\n options: OpportunityEvaluatorOptions\n ): Promise<Opportunity[]> {\n const minScore = options.minScore || 70;\n\n invokeLog.verbose('Analyzing candidates', { count: candidates.length });\n\n if (candidates.length === 0) {\n logger.verbose('No candidates provided.');\n return [];\n }\n\n const opportunities: Opportunity[] = [];\n\n // Analyze each candidate in parallel (bounded)\n const promises = candidates.map(async (candidate) => {\n // Pass existing opportunities context if provided\n const existingContext = options.existingOpportunities || '';\n return this.analyzeMatch(sourceProfileContext, candidate, candidate.userId, existingContext, options.signal);\n });\n\n const results = await Promise.all(promises);\n results.flat().forEach(op => {\n if (op.score >= minScore) {\n opportunities.push(op as Opportunity);\n }\n });\n\n // Sort by score and take top 1\n const out = opportunities.sort((a, b) => b.score - a.score).slice(0, 1);\n invokeLog.verbose('Done', { accepted: out.length });\n return out;\n }\n\n /**\n * Analyze a single match pair using the primary Agent model.\n */\n private async analyzeMatch(\n sourceProfileContext: string,\n candidateProfile: CandidateProfile,\n candidateUserId: string,\n existingOpportunities: string,\n signal?: AbortSignal,\n ): Promise<Opportunity[]> {\n try {\n // Construct the source context part of the prompt\n const sourceContext = `SOURCE PROFILE:\\n${sourceProfileContext}`;\n\n const existingContextPart = existingOpportunities\n ? `\\nEXISTING OPPORTUNITIES (Deduplication Context):\\n${existingOpportunities}\\n`\n : '';\n\n // Create candidate context using template string\n const candidateContext = `\n ID: ${candidateUserId}\n Name: ${candidateProfile.identity?.name || 'Unknown'}\n Bio: ${candidateProfile.identity?.bio || ''}\n Location: ${candidateProfile.identity?.location || ''}\n Interests: ${candidateProfile.attributes?.interests?.join(', ') || ''}\n Skills: ${candidateProfile.attributes?.skills?.join(', ') || ''}\n\n Context: ${candidateProfile.narrative?.context || ''}\n `;\n\n const messages = [\n new SystemMessage(systemPrompt),\n new HumanMessage(`${sourceContext}\\n${existingContextPart}\\nCANDIDATE PROFILE:\\n${candidateContext}`)\n ];\n\n const result = await invokeWithAbortSignal(this.model, messages, signal);\n const output = responseFormat.parse(result);\n\n const mappedOpportunities = output.opportunities.map((op: Opportunity) => ({\n ...op,\n reasoning: stripUuids(op.reasoning),\n candidateId: candidateUserId,\n }));\n\n return mappedOpportunities;\n } catch (e: unknown) {\n const message = e instanceof Error ? e.message : String(e);\n logger.warn('Analysis failed for candidate', { candidateUserId, message });\n throw e;\n }\n }\n\n /**\n * Entity-bundle entry point (C3): single LLM call with all entities, returns 0..N opportunities with actors.\n */\n @Timed()\n public async invokeEntityBundle(\n input: EvaluatorInput,\n options: { minScore?: number; returnAll?: boolean; signal?: AbortSignal } = {}\n ): Promise<EvaluatedOpportunityWithActors[]> {\n const minScore = options.minScore ?? 70;\n const returnAll = options.returnAll ?? false;\n const totalEntities = input.entities?.length ?? 0;\n if (!input.entities?.length) {\n invokeEntityBundleLog.verbose('No entities.');\n return [];\n }\n const existingPart = input.existingOpportunities\n ? `\\nEXISTING OPPORTUNITIES:\\n${input.existingOpportunities}\\n`\n : '';\n const introModePart = input.introductionMode\n ? `\\nINTRODUCTION MODE: This is a human-curated introduction. ${input.introducerName ?? 'The introducer'} (DISCOVERER: ${input.discovererId}) explicitly wants these people to connect. This is NOT an automatic discovery — a real person saw value in this connection.\n\nCRITICAL REASONING INSTRUCTIONS FOR INTRODUCTIONS:\n- Your reasoning MUST acknowledge that this is an introduction initiated by ${input.introducerName ?? 'the introducer'}, not a system-discovered match.\n- Start reasoning with something like \"${input.introducerName ?? 'The introducer'} is connecting [Name A] and [Name B] because...\" or \"This introduction by ${input.introducerName ?? 'the introducer'} brings together...\"\n- Even if the parties' intents or profiles don't obviously overlap, the introduction is still valid because the introducer saw the connection. Explain what the introducer likely sees in this match.\n- If explicit intents align, mention them — but frame it as supporting the introducer's judgment, not as the primary reason.${input.introductionHint ? `\\nINTRODUCER'S CONTEXT: \"${input.introductionHint}\" — use this to inform your reasoning about why the introducer made this connection.` : ''}\n- Actors must refer ONLY to the ENTITIES below (the people being introduced). Do not include the DISCOVERER as an actor.\n- You must output exactly two actors per opportunity (the two people being introduced). The introducer is added separately; do not include them in actors.\n- Be generous with scoring (70+ for any introduction with a plausible basis, since a human made the judgment).\n`\n : '';\n const discoveryQueryPart = input.discoveryQuery?.trim()\n ? `\\nDISCOVERY REQUEST: The user asked: \"${input.discoveryQuery.trim()}\"\n\nCRITICAL SCORING RULES FOR DISCOVERY REQUESTS:\n\n0. QUERY IS PRIMARY: The DISCOVERY REQUEST above is the primary evaluation criterion. The source user's stored INTENTS (if listed below) are background context — use them ONLY to fill in blanks when the query is too broad or vague to evaluate on its own. If the query is specific enough to score candidates, score strictly against the query and IGNORE stored intents. Never let a stored intent override or replace the query as the basis for scoring. When a specific query imposes a hard identity, role, capability, or location constraint, candidates that only match background context or thematic adjacency must score strictly below 30 and be omitted.\n\n1. CLASSIFY THE QUERY TYPE — determine the predication type before scoring:\n\n IDENTITY/ROLE QUERY: The query term is a count noun or sortal that can serve as a predicate nominal — \"X IS A [query]\" is grammatical and meaningful. Examples: \"samurai\", \"investors\", \"designers\", \"nurses\", \"founders\". The user wants someone who IS that thing — not someone who works with, creates content about, or is tangentially associated with it.\n → Test: Can you say \"this person IS a [query term]\"? If yes, this is an identity query.\n → CRITICAL: Subject-matter contact is NOT identity. A character designer who draws samurai IS NOT a samurai. An engineer who raised funding IS NOT an investor. A journalist who covers finance IS NOT an investor. IS-A is not transitive through creative subject matter, professional adjacency, or domain familiarity.\n\n TOPICAL/DOMAIN QUERY: The query is a field, domain, or abstract topic — \"X IS A [query]\" is ungrammatical. Examples: \"machine learning\", \"sustainability\", \"blockchain\". The user wants someone who works in or has expertise in this domain.\n → Test: \"This person IS a machine learning\" makes no sense → topical query.\n\n NEED/CAPABILITY QUERY: The query contains a purposive frame — \"someone to help with X\", \"need a Y\", \"looking for Z\". The user describes a specific need.\n\n2. APPLY TYPE-SPECIFIC SCORING:\n\n For IDENTITY/ROLE queries — apply a categorical gate:\n - First, make a binary IS-A judgment for each candidate: does this person's PRIMARY professional identity or self-description place them within the extension of the query term?\n - IS-A = TRUE → score 75-100 (modulated by intent alignment and profile richness)\n - IS-A = FALSE → score <30. Hard ceiling. Return no opportunity. Do NOT award surfacing credit for subject-matter adjacency, thematic association, creative output involving the query term, having received the thing, or being adjacent to people who are the thing.\n - Do NOT rescue a failed IS-A judgment with background intents. If the user searched \"samurai\" and the candidate is a visual artist (IS-A = false), the candidate's alignment with a background intent like \"connect with visual artists\" does not matter — the explicit query takes priority. If the user searched \"investors\" and the candidate merely raised money, score <30. If the user searched \"scouts\" and the candidate was scouted, score <30. If the user searched \"art director\", a solo illustrator is not enough: require explicit evidence that the candidate owns visual direction, leads/briefs other artists, manages a visual system, or is described as an art/creative director. General visual taste, composition skill, or illustration output alone is a failed IS-A judgment and must score <30.\n\n For TOPICAL/DOMAIN queries — apply gradient scoring:\n - 90-100: Deep expertise, primary focus area\n - 70-89: Substantial engagement, meaningful work in the domain\n - 50-69: Peripheral involvement, tangential connection\n - <50: No meaningful engagement\n\n For NEED/CAPABILITY queries — apply capability + availability scoring:\n - Score based on both capability match AND openness to the described collaboration\n - 90-100: Perfect capability match with explicit availability\n - 70-89: Strong capability, plausible availability\n\n3. SAME-SIDE CHECK: If the candidate's intents show they are ALSO SEEKING what the discoverer is seeking (e.g., both looking for investors, both looking for co-founders), this is a same-side match. Score <30 regardless of keyword overlap in bios. The candidate must BE or OFFER what the discoverer is looking for, not also be looking for it.\n\n4. LOCATION ENFORCEMENT: If the discovery request mentions a specific location (e.g., \"in SF\", \"based in London\", \"Istanbul\"), check each candidate's profile.location:\n - KNOWN MISMATCH (e.g., request says \"SF\" but candidate is \"New York\"): Score <30 and return no opportunity. State the mismatch in reasoning if returning diagnostic output. A known location mismatch is a hard query failure, even when the candidate otherwise matches the source's background interests.\n - UNKNOWN/EMPTY location: Do not penalize, and do NOT reject solely because the query contains a location. Treat location as unverified/missing evidence, then score on the non-location parts of the query. If the candidate strongly satisfies the requested role/domain (e.g., query \"Unreal Engine developers in SF\" and candidate is clearly an Unreal Engine developer with empty location), score normally enough to surface (typically 60-89) and note that location is unverified.\n - MATCH or COMPATIBLE (e.g., \"Bay Area\" ≈ \"SF\", \"Remote\" ≈ any): Score normally.\n`\n : '';\n const hasDiscoveryQuery = !!input.discoveryQuery?.trim();\n const entitiesBlock = input.entities.map((e) => {\n const isSource = e.userId === input.discovererId;\n // When an explicit discovery query is active, label the source user's stored\n // intents as background context so the LLM treats the query as primary.\n const intentsLabel = isSource && hasDiscoveryQuery ? 'BACKGROUND INTENTS (use only if query is too vague)' : 'INTENTS';\n const intentsPart = e.intents?.length\n ? `\\n ${intentsLabel}:\\n${e.intents.map((i) => ` - ${i.intentId}: ${i.payload}`).join('\\n')}`\n : '';\n // Mask the discoverer's name so the LLM cannot leak it into reasoning.\n // The system prompt already says \"use third-party references\", but the LLM\n // ignores this when the actual name is visible. Masking it forces role-based\n // language (\"the source user is looking…\" instead of \"Alice is looking…\").\n const displayName = e.userId === input.discovererId\n ? '(source user)'\n : (e.profile.name ?? '');\n return `\n USER: ${e.userId}\n INDEX: ${e.networkId}\n EVIDENCE KEY: ${e.evidenceKey ?? '—'}\n PROFILE: Name: ${displayName} | Bio: ${e.profile.bio ?? ''} | Location: ${e.profile.location ?? ''} | Interests: ${e.profile.interests?.join(', ') ?? ''} | Skills: ${e.profile.skills?.join(', ') ?? ''} | Context: ${e.profile.context ?? ''}${intentsPart}\n RAG SCORE: ${e.ragScore ?? '—'}\n MATCHED VIA: ${e.matchedVia ?? '—'}\n EVIDENCE:\n${renderOpportunityEvidenceForPrompt(e.evidence ?? [])}`;\n }).join('\\n');\n const networkContextPart = input.networkContexts && Object.keys(input.networkContexts).length > 0\n ? `\\n\\nNETWORK CONTEXTS:\\n${Object.entries(input.networkContexts).map(([nid, ctx]) => `[INDEX: ${nid}]\\n${ctx}`).join('\\n\\n')}`\n : '';\n const humanContent = `DISCOVERER: ${input.discovererId}${introModePart}${discoveryQueryPart}${networkContextPart}\\n\\nENTITIES:\\n${entitiesBlock}${existingPart}`;\n const messages = [\n new SystemMessage(entityBundleSystemPrompt),\n new HumanMessage(humanContent),\n ];\n let parsedTotal = 0;\n try {\n const result = await invokeWithAbortSignal(this.entityBundleModel, messages, options.signal);\n const parsed = entityBundleResponseFormat.parse(result);\n for (const op of parsed.opportunities) {\n op.reasoning = stripUuids(op.reasoning);\n }\n parsedTotal = parsed.opportunities.length;\n // Persistence safety is deterministic and precedes every score/returnAll\n // path. Network/context placement is not typed support provenance, so an\n // unsupported affiliation or presence claim rejects the whole result.\n const claimSafe = parsed.opportunities.filter(\n (op) => !hasUnsupportedOpportunityClaim(op.reasoning),\n );\n const introGuard =\n input.introductionMode\n ? claimSafe.filter((op) => op.actors.length === 2)\n : claimSafe;\n const filtered = introGuard.filter((op) => op.score >= minScore);\n invokeEntityBundleLog.verbose('Done', {\n total: parsed.opportunities.length,\n afterClaimGuard: claimSafe.length,\n afterIntroGuard: introGuard.length,\n accepted: filtered.length,\n returnAll,\n });\n return returnAll ? introGuard : filtered;\n } catch (llmError) {\n invokeEntityBundleLog.error('Failed', {\n discovererId: input.discovererId,\n totalEntities,\n parsedTotal,\n minScore,\n llmError,\n });\n throw llmError;\n }\n }\n\n /**\n * Factory method to expose the agent as a LangChain tool.\n * Simplified to only accept direct evaluation arguments.\n * PURE: Does not perform any database lookups.\n */\n public static asTool() {\n return tool(\n async (args: {\n sourceProfileContext: string;\n candidatesJson?: string;\n minScore?: number;\n }) => {\n const agent = new OpportunityEvaluator();\n\n const sourceProfileContext = args.sourceProfileContext;\n\n let candidates: CandidateProfile[] = [];\n if (args.candidatesJson) {\n try {\n candidates = JSON.parse(args.candidatesJson);\n } catch (e) {\n logger.error(\"Failed to parse candidates JSON\");\n }\n }\n\n const options: OpportunityEvaluatorOptions = {\n minScore: args.minScore,\n };\n\n return await agent.invoke(sourceProfileContext, candidates, options);\n },\n {\n name: 'opportunity_evaluator',\n description: 'Evaluates candidates against a source profile. SOURCE PROFILE CONTEXT MUST BE PROVIDED.',\n schema: z.object({\n sourceProfileContext: z.string().describe('The resolved source user profile context'),\n candidatesJson: z.string().optional().describe('JSON string list of Candidates'),\n minScore: z.number().optional().describe('Minimum score to accept a match')\n })\n }\n );\n }\n}\n"]}
|
|
@@ -32,8 +32,10 @@ declare const PresentationSchema: z.ZodObject<{
|
|
|
32
32
|
greeting: string;
|
|
33
33
|
}>;
|
|
34
34
|
export type OpportunityPresentationResult = z.infer<typeof PresentationSchema> & {
|
|
35
|
-
/** True when
|
|
35
|
+
/** True when any output field used resilience fallback copy. */
|
|
36
36
|
isFallback?: boolean;
|
|
37
|
+
/** Diagnostic category; never changes production fallback policy. */
|
|
38
|
+
fallbackReason?: "timeout" | "error" | "sanitization";
|
|
37
39
|
};
|
|
38
40
|
/** Input for home-card presenter call; extends PresenterInput with optional mutual intent count. */
|
|
39
41
|
export interface HomeCardPresenterInput extends PresenterInput {
|
|
@@ -112,7 +114,9 @@ export declare class OpportunityPresenter {
|
|
|
112
114
|
/**
|
|
113
115
|
* Generate personalized presentation for a single opportunity.
|
|
114
116
|
*/
|
|
115
|
-
present(input: PresenterInput
|
|
117
|
+
present(input: PresenterInput, options?: {
|
|
118
|
+
signal?: AbortSignal;
|
|
119
|
+
}): Promise<OpportunityPresentationResult>;
|
|
116
120
|
/**
|
|
117
121
|
* Generate LLM-powered home-card content (headline, body, narrator remark, mutual-intent label).
|
|
118
122
|
* Callers append button labels from opportunity.constants.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"opportunity.presenter.d.ts","sourceRoot":"/","sources":["opportunity/opportunity.presenter.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAIH,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAOxB,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,4CAA4C,CAAC;AAC9E,OAAO,KAAK,EAAE,0BAA0B,EAAE,MAAM,4CAA4C,CAAC;AAC7F,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,iCAAiC,CAAC;AAK1E;;;GAGG;AACH,MAAM,MAAM,iBAAiB,GAAG,IAAI,CAClC,0BAA0B,EAC1B,YAAY,GAAG,kBAAkB,GAAG,YAAY,GAAG,oBAAoB,CACxE,CAAC;AAcF,QAAA,MAAM,kBAAkB;;;;;;;;;;;;;;;EAatB,CAAC;AAMH,MAAM,MAAM,6BAA6B,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,kBAAkB,CAAC,GAAG;IAC/E
|
|
1
|
+
{"version":3,"file":"opportunity.presenter.d.ts","sourceRoot":"/","sources":["opportunity/opportunity.presenter.ts"],"names":[],"mappings":"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"}
|
|
@@ -218,13 +218,12 @@ export class OpportunityPresenter {
|
|
|
218
218
|
name: "opportunity_presenter_home_card",
|
|
219
219
|
});
|
|
220
220
|
}
|
|
221
|
-
async invokeWithTimeout(targetModel, messages) {
|
|
221
|
+
async invokeWithTimeout(targetModel, messages, signal) {
|
|
222
222
|
const timeoutReason = `LLM invoke timed out after ${LLM_TIMEOUT_MS}ms`;
|
|
223
223
|
const controller = new AbortController();
|
|
224
224
|
let timeoutId;
|
|
225
|
-
const
|
|
226
|
-
|
|
227
|
-
});
|
|
225
|
+
const combinedSignal = signal ? AbortSignal.any([signal, controller.signal]) : controller.signal;
|
|
226
|
+
const invokePromise = targetModel.invoke(messages, { signal: combinedSignal });
|
|
228
227
|
const timeoutPromise = new Promise((_, reject) => {
|
|
229
228
|
timeoutId = setTimeout(() => {
|
|
230
229
|
controller.abort(timeoutReason);
|
|
@@ -243,7 +242,7 @@ export class OpportunityPresenter {
|
|
|
243
242
|
/**
|
|
244
243
|
* Generate personalized presentation for a single opportunity.
|
|
245
244
|
*/
|
|
246
|
-
async present(input) {
|
|
245
|
+
async present(input, options = {}) {
|
|
247
246
|
const introContext = input.isIntroduction
|
|
248
247
|
? `\nINTRODUCTION CONTEXT: This opportunity was created by an explicit introduction from ${input.introducerName ?? "someone in the community"}. It was NOT discovered automatically — a real person made this connection.\n`
|
|
249
248
|
: "";
|
|
@@ -270,7 +269,7 @@ Produce headline, personalizedSummary (2-3 sentences in "you" language), suggest
|
|
|
270
269
|
new SystemMessage(systemPrompt),
|
|
271
270
|
new HumanMessage(humanContent),
|
|
272
271
|
];
|
|
273
|
-
const result = await this.invokeWithTimeout(this.model, messages);
|
|
272
|
+
const result = await this.invokeWithTimeout(this.model, messages, options.signal);
|
|
274
273
|
const parsed = responseFormat.parse(result);
|
|
275
274
|
const headline = sanitizePresenterField(parsed.presentation.headline, DEFAULT_FALLBACK_HEADLINE);
|
|
276
275
|
const summary = sanitizePresenterField(parsed.presentation.personalizedSummary, DEFAULT_EMPTY_FALLBACK_TEXT);
|
|
@@ -282,10 +281,12 @@ Produce headline, personalizedSummary (2-3 sentences in "you" language), suggest
|
|
|
282
281
|
personalizedSummary: summary.value,
|
|
283
282
|
suggestedAction: action.value,
|
|
284
283
|
greeting: greeting.value,
|
|
285
|
-
...(usedFallback ? { isFallback: true } : {}),
|
|
284
|
+
...(usedFallback ? { isFallback: true, fallbackReason: "sanitization" } : {}),
|
|
286
285
|
};
|
|
287
286
|
}
|
|
288
287
|
catch (e) {
|
|
288
|
+
if (options.signal?.aborted)
|
|
289
|
+
throw e;
|
|
289
290
|
const message = e instanceof Error ? e.message : String(e);
|
|
290
291
|
const timeoutReason = message.includes("timed out") ? message : undefined;
|
|
291
292
|
presentLog.warn("LLM failed, returning fallback", {
|
|
@@ -301,6 +302,7 @@ Produce headline, personalizedSummary (2-3 sentences in "you" language), suggest
|
|
|
301
302
|
suggestedAction: DEFAULT_FALLBACK_ACTION,
|
|
302
303
|
greeting: "",
|
|
303
304
|
isFallback: true,
|
|
305
|
+
fallbackReason: timeoutReason ? "timeout" : "error",
|
|
304
306
|
};
|
|
305
307
|
}
|
|
306
308
|
}
|
|
@@ -452,7 +454,7 @@ Produce headline, personalizedSummary, digestSummary, suggestedAction, narratorR
|
|
|
452
454
|
__decorate([
|
|
453
455
|
Timed(),
|
|
454
456
|
__metadata("design:type", Function),
|
|
455
|
-
__metadata("design:paramtypes", [Object]),
|
|
457
|
+
__metadata("design:paramtypes", [Object, Object]),
|
|
456
458
|
__metadata("design:returntype", Promise)
|
|
457
459
|
], OpportunityPresenter.prototype, "present", null);
|
|
458
460
|
__decorate([
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"opportunity.presenter.js","sourceRoot":"/","sources":["opportunity/opportunity.presenter.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Opportunity Presenter Agent\n *\n * Generates personalized, second-person explanations of why an opportunity\n * matters to the viewing user. Uses full opportunity data (interpretation,\n * actors, profiles, intents, index) to produce headline, personalizedSummary,\n * and suggestedAction for chat tools and user-facing surfaces.\n */\n\nimport type { Runnable } from \"@langchain/core/runnables\";\nimport { HumanMessage, SystemMessage } from \"@langchain/core/messages\";\nimport { z } from \"zod\";\n\nimport { Timed } from \"../shared/observability/performance.js\";\n\nimport { protocolLogger } from \"../shared/observability/protocol.logger.js\";\nimport { createStructuredModel } from \"../shared/agent/model.config.js\";\nimport { viewerCentricCardSummary } from \"./opportunity.presentation.js\";\nimport type { Opportunity } from \"../shared/interfaces/database.interface.js\";\nimport type { ChatGraphCompositeDatabase } from \"../shared/interfaces/database.interface.js\";\nimport type { NegotiationContext } from \"./negotiation-context.loader.js\";\nimport { stripUuids, stripIntroducerMentions } from \"./opportunity.presentation.js\";\nimport { stripUnsupportedOpportunityClaims } from \"./opportunity.claim-safety.js\";\nimport { DEFAULT_EMPTY_FALLBACK_TEXT, DEFAULT_FALLBACK_ACTION, DEFAULT_FALLBACK_HEADLINE, safeFallbackSummary } from \"./opportunity.safe-presentation.js\";\n\n/**\n * Minimal database interface required by gatherPresenterContext.\n * Any database adapter that implements these three methods can be passed.\n */\nexport type PresenterDatabase = Pick<\n ChatGraphCompositeDatabase,\n \"getProfile\" | \"getActiveIntents\" | \"getNetwork\" | \"getPremisesForUser\"\n>;\n\nconst presentLog = protocolLogger(\"OpportunityPresenter:present\");\nconst presentHomeCardLog = protocolLogger(\"OpportunityPresenter:presentHomeCard\");\nconst LLM_TIMEOUT_MS = 20_000;\n\n\nconst GREETING_DESCRIPTION =\n \"A 2-4 sentence first-person message the viewer could send to the counterpart, in the viewer's voice, referencing what they have in common. Plain prose only — no markdown, no greeting prefix like 'Hey {Name},'. Example body: 'Saw we're both working on regenerative coordination tooling — your post on consent flows resonated. Would love to compare notes if you have time this week.'\";\n\n// ──────────────────────────────────────────────────────────────\n// SCHEMA & TYPES\n// ──────────────────────────────────────────────────────────────\n\nconst PresentationSchema = z.object({\n headline: z\n .string()\n .describe(\n \"Short, compelling headline for this opportunity (e.g., 'A React expert who needs your design skills')\",\n ),\n personalizedSummary: z\n .string()\n .describe(\n \"2-3 sentence explanation using 'you' language, explaining why this opportunity is specifically valuable for the viewer based on their intents and profile\",\n ),\n suggestedAction: z.string().describe(\"Brief suggested next step\"),\n greeting: z.string().max(500).describe(GREETING_DESCRIPTION),\n});\n\nconst responseFormat = z.object({\n presentation: PresentationSchema,\n});\n\nexport type OpportunityPresentationResult = z.infer<typeof PresentationSchema> & {\n /** True when the LLM call failed and this is fallback-shaped copy built from raw reasoning. */\n isFallback?: boolean;\n};\n\n/** Input for home-card presenter call; extends PresenterInput with optional mutual intent count. */\nexport interface HomeCardPresenterInput extends PresenterInput {\n /** Number of overlapping intents (for generating mutualIntentsLabel). */\n mutualIntentCount?: number;\n /**\n * Snapshot of the opportunity's negotiation, if one exists. When status is\n * `negotiating`, the presenter returns a templated chip without invoking\n * the LLM. For `pending`/`stalled`/`accepted`/`rejected`, the full\n * transcript and outcome ground the LLM's explanation.\n */\n negotiationContext?: NegotiationContext;\n}\n\n/** LLM-generated fields for home-card presentation (buttons are hardcoded by callers, not LLM-generated). */\nexport const HomeCardLLMSchema = z.object({\n headline: z\n .string()\n .describe(\"Short, compelling headline for this opportunity\"),\n personalizedSummary: z\n .string()\n .describe(\n \"2-3 sentence explanation in 'you' language for the main card body\",\n ),\n digestSummary: z\n .string()\n .max(220)\n .describe(\n \"One concise digest-ready sentence for a morning brief. It must be addressed to the viewer and mention the counterpart by name, e.g. 'You might like meeting Paul because ...'. No markdown.\",\n ),\n suggestedAction: z\n .string()\n .describe(\"Brief suggested next step (e.g. CTA line)\"),\n narratorRemark: z\n .string()\n .max(80)\n .describe(\n \"One short sentence for the narrator chip, max ~80 chars (e.g. who is suggesting and why)\",\n ),\n mutualIntentsLabel: z\n .string()\n .max(48)\n .describe(\n \"Short line for the subtitle under the other party name (e.g. '3 mutual intents', 'Shared interests', 'Aligned goals'). NEVER output '0 mutual intents' — use a qualitative phrase like 'Shared interests' when no numeric count is available.\",\n ),\n greeting: z.string().max(500).describe(GREETING_DESCRIPTION),\n});\n\n/** LLM-generated result from presentHomeCard (callers append button labels from opportunity.constants). */\nexport type HomeCardLLMResult = z.infer<typeof HomeCardLLMSchema> & {\n /**\n * True when the LLM call failed and this is fallback-shaped copy built from\n * raw match reasoning. Callers with strict quality requirements (digests,\n * long-lived caches) should check this before sending/persisting — fallback\n * output is otherwise indistinguishable from genuine LLM output.\n */\n isFallback?: boolean;\n};\n\n/** Full home-card display contract including hardcoded button labels (assembled by callers). */\nexport type HomeCardPresentationResult = HomeCardLLMResult & {\n primaryActionLabel: string;\n secondaryActionLabel: string;\n};\n\nconst homeCardResponseFormat = z.object({\n presentation: HomeCardLLMSchema,\n});\n\n/** Input for a single presenter call (all context pre-assembled). */\nexport interface PresenterInput {\n viewerContext: string;\n otherPartyContext: string;\n matchReasoning: string;\n category: string;\n confidence: number;\n signalsSummary: string;\n indexName: string;\n viewerRole: string;\n opportunityStatus?: string;\n /** True when this opportunity was created via an explicit introduction (not automatic discovery). */\n isIntroduction?: boolean;\n /** Name of the person who made the introduction, if applicable. */\n introducerName?: string;\n}\n\n// ──────────────────────────────────────────────────────────────\n// SYSTEM PROMPT\n// ──────────────────────────────────────────────────────────────\n\nconst systemPrompt = `\nYou are an expert at presenting connection opportunities to users in a way that feels personal and compelling.\n\nYour goal: Given raw context about the viewer (their profile, intents), the other person(s), and why the system matched them, produce a short headline, a personalized summary, and a suggested action.\n\nRules:\n1. Address the VIEWER directly using \"you\" and \"your\". This is for them.\n2. Be concise and compelling — not analytical or third-party. No \"The source user\" or \"The candidate\"; use names or \"they\" where needed.\n3. Do not leak private or confidential details. Use only the context provided.\n4. Vary user-facing nouns naturally. Do not repeatedly use the same label in one response.\n5. If possible, avoid repeating \"opportunity\" in both headline and summary. Prefer alternatives like \"connection\", \"thought partner\", \"mutual fit\", \"valuable conversation\", or \"peer\".\n6. Prefer first names in user-facing copy. Do not repeatedly use full names unless needed to disambiguate.\n7. Network assignment, network title/type, and network/event metadata are retrieval context only. They are NEVER proof that a person attended or will attend, belongs to a group, resides in a place, knows anyone from the network, or shared a session, time, place, or location with anyone. Do not make co-attendance, membership, residence, shared-session, or same-place/same-time claims from network co-membership.\n\n**Introduction-originated opportunities:**\nWhen INTRODUCTION CONTEXT is provided, this opportunity was explicitly created by an introducer (a real person who saw value in this connection). This is NOT an automatic system discovery — someone made a deliberate judgment.\n- For ALL roles: acknowledge the introducer's role naturally. E.g., \"[Introducer name] thinks you should meet [other person]\" or \"[Introducer name] connected you because...\"\n- The introduction itself is a strong signal — treat it with the weight of a personal recommendation.\n- If the parties' intents don't obviously overlap, that's fine — the introducer saw something worth connecting. Focus on what the introducer likely saw.\n\n**Role-Specific Presentation:**\n\n**If viewer is \"introducer\":**\n- The viewer suggested this connection between two (or more) OTHER people. The opportunity is NOT about the viewer's own needs.\n- Headline: describe the connection between the parties (e.g., \"Connecting a React expert with a startup founder\").\n- Personalized summary: explain why the people YOU are introducing should meet. Reference THEIR profiles and intents, not yours. Frame it as \"you're connecting X and Y because...\" rather than \"this matches your intent\".\n- Suggested action: guide sharing (e.g., \"Share this with [name] to get things started\").\n- CRITICAL: Do NOT reference the introducer's own intents, skills, or needs. The introducer is the matchmaker, not a party.\n\n**If viewer is \"patient\" or \"party\":**\n- Reference their specific intents, skills, or interests that align with this opportunity.\n- If this is an introduction: mention who introduced them and frame it as a personal recommendation.\n- Headline: one short line that hooks (e.g., \"[Name] thinks you should meet [Other]\" or \"A React expert who needs your design skills\").\n- Personalized summary: 2-3 sentences. Why is this opportunity for *them*? If introduced, lead with the introduction.\n- Suggested action: encourage action (\"Send a message to start the conversation\" or \"Share this intro\").\n\n**If viewer is \"agent\":**\n- They are seeing this because someone already reached out.\n- If this is an introduction: mention who made the introduction.\n- Reference their skills/expertise that make them a match.\n- Headline: what the other person needs that they can provide.\n- Personalized summary: 2-3 sentences. Why someone reached out to them.\n- Suggested action: \"Someone is interested in connecting — check their message\" or \"Review and respond\".\n\n**If viewer is \"peer\":**\n- Mutual opportunity. Reference shared or complementary interests.\n- If this is an introduction: mention who connected them.\n- Headline: the mutual connection angle.\n- Personalized summary: 2-3 sentences. Why this is mutually valuable.\n- Suggested action: \"Send an intro to connect\" or \"Start a conversation\".\n`;\n\nconst homeCardSystemPrompt = `\nYou are an expert at presenting connection opportunities for a home feed card.\n\nGiven context about the viewer, the other person, and why they were matched, produce:\n1. headline: one short hook line.\n2. personalizedSummary: 2-3 sentences in \"you\" language (main body text).\n3. digestSummary: one polished morning-brief sentence that can be printed directly after the person's linked name. No markdown, no field labels.\n4. suggestedAction: one brief suggested next step.\n5. narratorRemark: one short sentence for the narrator chip (who is suggesting and why; max ~80 chars).\n6. greeting: a 2-4 sentence first-person message the viewer could send to the counterpart. Plain prose, no greeting prefix, no markdown.\n7. mutualIntentsLabel: short subtitle under the other party's name. Examples: \"3 mutual intents\", \"Shared interests\", \"Aligned goals\" — keep it brief. NEVER output \"0 mutual intents\" or any zero-count label; use a qualitative phrase instead.\n\nRules:\n- Address the viewer with \"you\"/\"your\". Be concise and compelling.\n- narratorRemark should feel like a single sentence from the narrator (Index or a person), not meta-commentary.\n- narratorRemark is displayed with the narrator name prepended (e.g. \"Index: …\" or \"Alice: …\"). Do NOT start narratorRemark with the narrator's name or repeat it; write only the remark (e.g. \"Based on your overlapping intents\" or \"introduced you two, sensing a valuable connection\").\n- Vary wording for the match itself. Do not repeat \"opportunity\" across headline, summary, and narratorRemark when alternatives fit.\n- Prefer first names in user-facing copy. Avoid repeated full names unless disambiguation is necessary.\n- Network assignment, network title/type, and network/event metadata are retrieval context only. They are NEVER proof that a person attended or will attend, belongs to a group, resides in a place, knows anyone from the network, or shared a session, time, place, or location with anyone. Do not make co-attendance, membership, residence, shared-session, or same-place/same-time claims from network co-membership.\n- digestSummary must be grammatically complete as a standalone sentence. It should usually start with \"You might like meeting {Name} because ...\" for direct connections, or \"You may be able to help {Name} because ...\" for connector/introducer cards.\n- digestSummary must NOT use awkward third-person fragments like \"Name is...\", \"they're ..., and is...\", \"you is...\", or \"the discoverer's query\".\n- digestSummary must be one sentence, MUST fit within 180 characters when possible, and MUST contain no markdown links; the caller will attach links.\n- If you cannot fit every detail, choose one clear reason and stop. Do not rely on downstream truncation.\n\n**Introduction-originated opportunities (ONLY when INTRODUCTION CONTEXT is provided):**\nWhen INTRODUCTION CONTEXT is provided, this opportunity was explicitly created by an introducer. It was NOT automatically discovered.\n- For parties/patients/agents/peers viewing an introduction: keep the introducer signal in narratorRemark (and narrator chip), not in personalizedSummary.\n- For these introduced parties, personalizedSummary must focus ONLY on fit/value between viewer and counterpart. Do NOT mention the introducer there.\n- narratorRemark should carry the introduction signal (e.g., \"saw strong alignment between you two\" or \"thought this connection could be valuable\"), without repeating the narrator name at the start.\n- This is a personal recommendation, not an algorithm match. Frame it accordingly.\n\n**CRITICAL: NEVER include introducer names in personalizedSummary. Examples:**\n❌ WRONG: \"Seref introduced you to Lucy, who is actively seeking a product co-founder...\"\n✅ CORRECT: \"Lucy is actively seeking a product co-founder for a niche APAC marketplace. With your expertise in UX and AI, this could be an ideal collaboration.\"\n\n❌ WRONG: \"Bob thinks you should meet Alice because your React skills align with her needs.\"\n✅ CORRECT: \"Alice is building a React-based platform and needs frontend expertise. Your experience with component architecture makes you a strong fit.\"\n\n❌ WRONG: \"Jane connected you to Mark, who is looking for a designer.\"\n✅ CORRECT: \"Mark is building a consumer app and needs design expertise. Your background in user-centered design aligns well with what he's building.\"\n\nRemember: The introducer's name goes ONLY in narratorRemark, NEVER in personalizedSummary.\n\n**When INTRODUCTION CONTEXT is NOT provided (system-discovered match):**\n- Do NOT use introducer-style wording. Do NOT say \"you suggested\", \"this is an introduction you suggested\", or \"you suggested this connection\". The system found this match; no human introducer was involved.\n- Instead, narratorRemark should describe why the match is relevant (e.g. \"Based on your overlapping intents\", \"Your skills align with what they need\").\n\n**Negotiation-grounded explanations (ONLY when NEGOTIATION CONTEXT is provided):**\nWhen NEGOTIATION CONTEXT is provided, this opportunity passed through an agent-to-agent negotiation. Use the transcript to ground your explanation in the concrete reasoning the agents exchanged.\n- Personalize the summary with *why* the negotiation produced this match — reference the roles the agents agreed on, the specific concerns raised, and how they were resolved.\n- For status \"stalled\" with reason \"turn_cap\": the agents hit the turn limit without reaching agreement. Frame the card as a hedged possibility rather than a confident match; narratorRemark should signal \"agents couldn't fully converge\" without sounding negative.\n- For status \"stalled\" with reason \"timeout\": one side went silent. Suggest the user re-engage if interested.\n- For status \"accepted\": the agents agreed; the card should confidently explain *why* they agreed.\n- For status \"rejected\": the agents declined. The card should explain the reason briefly so the user understands — not dwell on it.\n- Do NOT invent turn content. Only reference what is in the NEGOTIATION CONTEXT block.\n\n- Exception for connector/introducer: if viewer role is \"introducer\" (any status), this is a curation/connector card. Use:\n - suggestedAction: one short line about sharing the intro or confirming the match.\n - mutualIntentsLabel: a short connector label (e.g. \"Connector match\", \"You can bridge this\").\n - headline: describe the connection between the parties (e.g., \"Connecting a PhD researcher with a translator\"). Do NOT reference the introducer's own needs.\n - personalizedSummary: explain why the parties you're introducing should meet, referencing THEIR profiles and intents, not yours.\n\n**CRITICAL for latent introducer cards (opportunity status is \"latent\"):**\nWhen the viewer is the introducer and the opportunity status is \"latent\", the introducer has NOT yet approved this match. They are evaluating whether to make the introduction.\n- narratorRemark MUST use evaluation/curation language (e.g. \"Could be a strong match\", \"Worth introducing?\", \"Interesting overlap here\").\n- Do NOT say \"you suggested\", \"you introduced\", \"you connected\", or any past-tense language implying the introduction was already made.\n- suggestedAction should encourage evaluation (e.g. \"Approve if you see the fit\").\n- Exception for new-connection reveal: if viewer role is \"agent\", status is \"accepted\", and there is an introducer, this is the agent's first time seeing this opportunity. Use:\n - suggestedAction: a short line about joining the conversation.\n`;\n\n// ──────────────────────────────────────────────────────────────\n// DETERMINISTIC OUTPUT VALIDATION\n// ──────────────────────────────────────────────────────────────\n\nfunction sanitizePresenterField(\n value: string,\n fallback: string,\n allowEmpty = fallback === \"\",\n): { value: string; usedFallback: boolean } {\n const cleaned = stripUnsupportedOpportunityClaims(stripUuids(value));\n if (cleaned || allowEmpty) {\n return { value: cleaned, usedFallback: false };\n }\n return { value: fallback, usedFallback: true };\n}\n\n// ──────────────────────────────────────────────────────────────\n// CLASS\n// ──────────────────────────────────────────────────────────────\n\nexport class OpportunityPresenter {\n private model: Runnable;\n private homeCardModel: Runnable;\n\n constructor() {\n this.model = createStructuredModel(\"opportunityPresenter\", responseFormat, {\n name: \"opportunity_presenter\",\n });\n this.homeCardModel = createStructuredModel(\"opportunityPresenter\", homeCardResponseFormat, {\n name: \"opportunity_presenter_home_card\",\n });\n }\n\n private async invokeWithTimeout(\n targetModel: Runnable,\n messages: (SystemMessage | HumanMessage)[],\n ): Promise<unknown> {\n const timeoutReason = `LLM invoke timed out after ${LLM_TIMEOUT_MS}ms`;\n const controller = new AbortController();\n let timeoutId: ReturnType<typeof setTimeout> | undefined;\n\n const invokePromise = targetModel.invoke(messages, {\n signal: controller.signal,\n });\n\n const timeoutPromise = new Promise<never>((_, reject) => {\n timeoutId = setTimeout(() => {\n controller.abort(timeoutReason);\n reject(new Error(timeoutReason));\n }, LLM_TIMEOUT_MS);\n });\n\n try {\n return await Promise.race([invokePromise, timeoutPromise]);\n } finally {\n if (timeoutId) {\n clearTimeout(timeoutId);\n }\n }\n }\n\n /**\n * Generate personalized presentation for a single opportunity.\n */\n @Timed()\n public async present(\n input: PresenterInput,\n ): Promise<OpportunityPresentationResult> {\n const introContext = input.isIntroduction\n ? `\\nINTRODUCTION CONTEXT: This opportunity was created by an explicit introduction from ${input.introducerName ?? \"someone in the community\"}. It was NOT discovered automatically — a real person made this connection.\\n`\n : \"\";\n const humanContent = `\nVIEWER (the person seeing this opportunity):\n${input.viewerContext}\n\nOTHER PARTY:\n${input.otherPartyContext}\n\nMATCH CONTEXT:\n- Category: ${input.category}\n- Confidence: ${input.confidence}\n- Why we matched: ${input.matchReasoning}\n- Signals: ${input.signalsSummary}\n${introContext}\nCOMMUNITY: ${input.indexName}\nViewer's role in this opportunity: ${input.viewerRole}\n\nProduce headline, personalizedSummary (2-3 sentences in \"you\" language), suggestedAction, and greeting.\n`;\n\n try {\n const messages = [\n new SystemMessage(systemPrompt),\n new HumanMessage(humanContent),\n ];\n const result = await this.invokeWithTimeout(this.model, messages);\n const parsed = responseFormat.parse(result);\n const headline = sanitizePresenterField(\n parsed.presentation.headline,\n DEFAULT_FALLBACK_HEADLINE,\n );\n const summary = sanitizePresenterField(\n parsed.presentation.personalizedSummary,\n DEFAULT_EMPTY_FALLBACK_TEXT,\n );\n const action = sanitizePresenterField(\n parsed.presentation.suggestedAction,\n DEFAULT_FALLBACK_ACTION,\n );\n const greeting = sanitizePresenterField(parsed.presentation.greeting, \"\");\n const usedFallback = headline.usedFallback || summary.usedFallback || action.usedFallback || greeting.usedFallback;\n return {\n headline: headline.value,\n personalizedSummary: summary.value,\n suggestedAction: action.value,\n greeting: greeting.value,\n ...(usedFallback ? { isFallback: true } : {}),\n };\n } catch (e) {\n const message = e instanceof Error ? e.message : String(e);\n const timeoutReason = message.includes(\"timed out\") ? message : undefined;\n presentLog.warn(\n \"LLM failed, returning fallback\",\n {\n event: \"presenter_fallback\",\n presenter: \"opportunity\",\n reason: timeoutReason ? \"timeout\" : \"parse_error\",\n message,\n timeoutReason,\n },\n );\n return {\n headline: DEFAULT_FALLBACK_HEADLINE,\n personalizedSummary: safeFallbackSummary(input.matchReasoning),\n suggestedAction: DEFAULT_FALLBACK_ACTION,\n greeting: \"\",\n isFallback: true,\n };\n }\n }\n\n /**\n * Generate LLM-powered home-card content (headline, body, narrator remark, mutual-intent label).\n * Callers append button labels from opportunity.constants.\n *\n * When `negotiationContext.status === 'negotiating'`, returns a templated\n * chip synchronously without invoking the LLM — the card just reflects\n * \"negotiation in progress\" at that point.\n */\n @Timed()\n public async presentHomeCard(\n input: HomeCardPresenterInput,\n ): Promise<HomeCardLLMResult> {\n if (input.negotiationContext?.status === 'negotiating') {\n return buildNegotiatingChip(input);\n }\n\n const mutualHint =\n input.mutualIntentCount != null && input.mutualIntentCount > 0\n ? `There are ${input.mutualIntentCount} overlapping intent(s) between viewer and other party.`\n : \"Match is based on profile and intent alignment. Do not cite a numeric intent count.\";\n const introContext = input.isIntroduction\n ? `\\nINTRODUCTION CONTEXT: This opportunity was created by an explicit introduction from ${input.introducerName ?? \"someone in the community\"}. It was NOT discovered automatically — a real person made this connection.\\n`\n : \"\";\n const negotiationBlock = buildNegotiationPromptBlock(input.negotiationContext);\n // When negotiation context exists, lead with it — these cards exist\n // *because* the negotiation happened. Trailing the block lets weaker\n // models lean on surface signals and ignore the transcript entirely.\n const negotiationDirective = negotiationBlock\n ? `\\nIMPORTANT: This opportunity surfaced because the agents negotiated and converged. Both your personalizedSummary AND your digestSummary MUST reference at least one specific signal from the NEGOTIATION CONTEXT block below — what concern was raised, what was confirmed, what the agents agreed on. The digestSummary is the one-line morning-brief sentence a user reads before deciding to act, so it must communicate *why this specific match* surfaced now (the negotiation that led to it), not a generic skill-complementarity line. Do not produce the generic summary every card looked like before this negotiation happened.\\n`\n : \"\";\n const humanContent = `\n${negotiationBlock}${negotiationDirective}\nVIEWER (the person seeing this opportunity):\n${input.viewerContext}\n\nOTHER PARTY:\n${input.otherPartyContext}\n\nMATCH CONTEXT:\n- Category: ${input.category}\n- Confidence: ${input.confidence}\n- Why we matched: ${input.matchReasoning}\n- Signals: ${input.signalsSummary}\n- ${mutualHint}\n${introContext}\nCOMMUNITY: ${input.indexName}\nViewer's role in this opportunity: ${input.viewerRole}\nOpportunity status: ${input.opportunityStatus ?? \"pending\"}\n\nProduce headline, personalizedSummary, digestSummary, suggestedAction, narratorRemark, greeting, and mutualIntentsLabel.\n`;\n\n const isIntroducer = input.viewerRole === \"introducer\";\n\n try {\n const messages = [\n new SystemMessage(homeCardSystemPrompt),\n new HumanMessage(humanContent),\n ];\n const result = await this.invokeWithTimeout(this.homeCardModel, messages);\n const parsed = homeCardResponseFormat.parse(result);\n if (/^0\\s+(mutual|overlapping)\\s+intent/i.test(parsed.presentation.mutualIntentsLabel)) {\n parsed.presentation.mutualIntentsLabel = \"Shared interests\";\n }\n if (input.isIntroduction && input.introducerName) {\n parsed.presentation.personalizedSummary = stripIntroducerMentions(\n parsed.presentation.personalizedSummary,\n input.introducerName,\n );\n parsed.presentation.digestSummary = stripIntroducerMentions(\n parsed.presentation.digestSummary,\n input.introducerName,\n );\n }\n\n const fields = {\n headline: sanitizePresenterField(parsed.presentation.headline, DEFAULT_FALLBACK_HEADLINE),\n personalizedSummary: sanitizePresenterField(parsed.presentation.personalizedSummary, DEFAULT_EMPTY_FALLBACK_TEXT),\n digestSummary: sanitizePresenterField(\n parsed.presentation.digestSummary,\n isIntroducer\n ? \"You may be able to help make a useful introduction here.\"\n : \"You might like meeting them based on your current interests.\",\n ),\n suggestedAction: sanitizePresenterField(parsed.presentation.suggestedAction, DEFAULT_FALLBACK_ACTION),\n narratorRemark: sanitizePresenterField(parsed.presentation.narratorRemark, \"Worth a look.\"),\n mutualIntentsLabel: sanitizePresenterField(\n parsed.presentation.mutualIntentsLabel,\n isIntroducer ? \"Connector match\" : \"Shared interests\",\n ),\n greeting: sanitizePresenterField(parsed.presentation.greeting, \"\"),\n };\n const usedFallback = Object.values(fields).some((field) => field.usedFallback);\n return {\n headline: fields.headline.value,\n personalizedSummary: fields.personalizedSummary.value,\n digestSummary: fields.digestSummary.value,\n suggestedAction: fields.suggestedAction.value,\n narratorRemark: fields.narratorRemark.value,\n mutualIntentsLabel: fields.mutualIntentsLabel.value,\n greeting: fields.greeting.value,\n ...(usedFallback ? { isFallback: true } : {}),\n };\n } catch (e) {\n const message = e instanceof Error ? e.message : String(e);\n const timeoutReason = message.includes(\"timed out\") ? message : undefined;\n presentHomeCardLog.warn(\n \"LLM failed, returning fallback\",\n {\n event: \"presenter_fallback\",\n presenter: \"home_card\",\n reason: timeoutReason ? \"timeout\" : \"parse_error\",\n message,\n timeoutReason,\n },\n );\n const fallbackSummary = safeFallbackSummary(input.matchReasoning, {\n introducerName: input.isIntroduction ? input.introducerName : undefined,\n });\n return {\n headline: \"A promising connection\",\n personalizedSummary: fallbackSummary,\n digestSummary: isIntroducer\n ? \"You may be able to help make a useful introduction here.\"\n : \"You might like meeting them based on your current interests.\",\n suggestedAction: isIntroducer\n ? \"Share this introduction to get things started.\"\n : \"Take a look and decide whether to reach out.\",\n narratorRemark: \"Worth a look.\",\n mutualIntentsLabel: isIntroducer\n ? \"Connector match\"\n : input.mutualIntentCount != null && input.mutualIntentCount > 0\n ? `${input.mutualIntentCount} mutual intent${input.mutualIntentCount !== 1 ? \"s\" : \"\"}`\n : \"Shared interests\",\n greeting: \"\",\n isFallback: true,\n };\n }\n }\n\n /**\n * Process multiple opportunities in parallel with bounded concurrency.\n */\n @Timed()\n public async presentBatch(\n inputs: PresenterInput[],\n options?: { concurrency?: number },\n ): Promise<OpportunityPresentationResult[]> {\n const concurrency = options?.concurrency ?? 5;\n const results: OpportunityPresentationResult[] = [];\n for (let i = 0; i < inputs.length; i += concurrency) {\n const chunk = inputs.slice(i, i + concurrency);\n const chunkResults = await Promise.all(\n chunk.map((inp) => this.present(inp)),\n );\n results.push(...chunkResults);\n }\n return results;\n }\n\n /**\n * Process multiple opportunities as home cards in parallel with bounded concurrency.\n * Returns full home-card display contracts (headline, body, narrator remark, action labels, mutual-intent label).\n */\n @Timed()\n public async presentHomeCardBatch(\n inputs: HomeCardPresenterInput[],\n options?: { concurrency?: number },\n ): Promise<HomeCardLLMResult[]> {\n const concurrency = options?.concurrency ?? 5;\n const results: HomeCardLLMResult[] = [];\n for (let i = 0; i < inputs.length; i += concurrency) {\n const chunk = inputs.slice(i, i + concurrency);\n const chunkResults = await Promise.all(\n chunk.map((inp) => this.presentHomeCard(inp)),\n );\n results.push(...chunkResults);\n }\n return results;\n }\n}\n\n// ──────────────────────────────────────────────────────────────\n// NEGOTIATION CONTEXT HELPERS\n// ──────────────────────────────────────────────────────────────\n\n/**\n * Builds a \"NEGOTIATION CONTEXT:\" block for the home-card prompt. Returns an\n * empty string when the opportunity has no meaningful negotiation context\n * (draft/latent) or when the opportunity is still negotiating (handled via\n * the templated chip, not the LLM).\n */\nfunction buildNegotiationPromptBlock(context: NegotiationContext | undefined): string {\n if (!context || context.status === 'negotiating') return \"\";\n\n const turnCapLabel = context.turnCap > 0 ? `${context.turnCap}` : \"unlimited\";\n const reason = context.outcome?.reason;\n const reasonLabel = reason === 'turn_cap'\n ? \"agents hit the turn cap without converging\"\n : reason === 'timeout'\n ? \"counterpart went silent before responding\"\n : undefined;\n\n const turnLines = (context.turns ?? []).map((turn, index) => {\n const action = turn.action;\n const reasoning = turn.assessment?.reasoning ?? \"(no reasoning)\";\n const message = turn.message ? ` — said: \"${turn.message}\"` : \"\";\n return `Turn ${index + 1} (${action}): ${reasoning}${message}`;\n });\n\n const outcomeSummary = context.outcome\n ? `Final outcome: ${context.outcome.hasOpportunity ? \"agreed\" : \"declined\"} — ${context.outcome.reasoning}`\n : \"Final outcome: not recorded.\";\n\n return `\nNEGOTIATION CONTEXT:\n- Negotiation status: ${context.status}${reasonLabel ? ` (${reasonLabel})` : \"\"}\n- Turns exchanged: ${context.turnCount} of ${turnCapLabel}\n- Transcript:\n${turnLines.length > 0 ? turnLines.map((l) => ` ${l}`).join(\"\\n\") : \" (no turns recorded)\"}\n- ${outcomeSummary}\n`;\n}\n\n/**\n * Builds a templated home-card result for an opportunity whose negotiation\n * is still in progress. Bypasses the LLM so users see a stable \"currently\n * negotiating\" chip while turns are still being exchanged.\n */\nfunction buildNegotiatingChip(input: HomeCardPresenterInput): HomeCardLLMResult {\n const ctx = input.negotiationContext;\n const turnCount = ctx?.turnCount ?? 0;\n const turnCap = ctx?.turnCap && ctx.turnCap > 0 ? ctx.turnCap : undefined;\n const narratorRemark = turnCap\n ? `Currently negotiating · turn ${turnCount} of ${turnCap}`\n : `Currently negotiating · turn ${turnCount}`;\n\n return {\n headline: \"Negotiation in progress\",\n personalizedSummary: \"Your agent is still talking with theirs to see if this connection makes sense. We'll surface the full match as soon as they converge.\",\n digestSummary: \"Your agent is still checking whether this connection makes sense.\",\n suggestedAction: \"Check back shortly — no action needed yet.\",\n narratorRemark,\n mutualIntentsLabel: input.mutualIntentCount && input.mutualIntentCount > 0\n ? `${input.mutualIntentCount} mutual intent${input.mutualIntentCount !== 1 ? \"s\" : \"\"}`\n : \"Shared interests\",\n greeting: \"\",\n };\n}\n\n// ──────────────────────────────────────────────────────────────\n// CONTEXT GATHERER (used by tools)\n// ──────────────────────────────────────────────────────────────\n\n/**\n * Build the LLM-facing signal summary while excluding pool adjustments. Pool\n * disposition is rendered deterministically by the card chip; asking the\n * presenter to interpret it could turn a demotion into a positive rationale.\n */\nexport function summarizeSignalsForPresenter(\n signals: Opportunity['interpretation']['signals'],\n): string {\n const safeSignals = signals?.filter((signal) => signal.type !== 'pool_discriminator') ?? [];\n if (safeSignals.length === 0) return 'Match based on profile and intent alignment.';\n return safeSignals.map((signal) => `${signal.type}: ${signal.detail ?? signal.type}`).join('; ');\n}\n\n/**\n * Gather all context needed for the presenter from the database.\n * Fetches viewer profile, viewer intents, other party profile(s), and index in parallel.\n *\n * @param displayCounterpartUserId - When set (e.g. for home card), only this counterpart is included in otherPartyContext so the presenter writes about the person on the card. Omitted for introducer view (card shows both parties).\n */\nexport async function gatherPresenterContext(\n database: PresenterDatabase,\n opportunity: Opportunity,\n viewerId: string,\n displayCounterpartUserId?: string,\n): Promise<PresenterInput> {\n const myActor = opportunity.actors.find((a) => a.userId === viewerId);\n if (!myActor) {\n throw new Error(\"Viewer is not an actor in this opportunity\");\n }\n\n const isIntroducer = myActor.role === \"introducer\";\n const otherActors = opportunity.actors.filter((a) => a.userId !== viewerId);\n let otherPartyIds = [...new Set(otherActors.map((a) => a.userId))];\n if (\n displayCounterpartUserId &&\n !isIntroducer &&\n otherPartyIds.includes(displayCounterpartUserId)\n ) {\n otherPartyIds = [displayCounterpartUserId];\n }\n\n const contextIndexId = opportunity.context?.networkId;\n\n // For introducers: fetch profiles + intents for both parties; skip introducer's own intents.\n // For other roles: fetch viewer's profile + intents and other party profiles.\n const [viewerProfile, indexRecord, ...otherProfiles] = await Promise.all([\n database.getProfile(viewerId),\n contextIndexId ? database.getNetwork(contextIndexId) : Promise.resolve(null),\n ...otherPartyIds.map((uid) => database.getProfile(uid)),\n ]);\n\n // Fetch intents: for introducer, fetch each party's intents; otherwise fetch viewer's intents.\n let viewerIntents:\n | Awaited<ReturnType<typeof database.getActiveIntents>>\n | undefined;\n let partyIntentsMap:\n | Map<string, Awaited<ReturnType<typeof database.getActiveIntents>>>\n | undefined;\n\n if (isIntroducer) {\n const partyIntentResults = await Promise.all(\n otherPartyIds.map(async (uid) => ({\n uid,\n intents: await database.getActiveIntents(uid),\n })),\n );\n partyIntentsMap = new Map(\n partyIntentResults.map((r) => [r.uid, r.intents]),\n );\n } else {\n viewerIntents = await database.getActiveIntents(viewerId);\n }\n\n // Fetch premises when any actor is premise-grounded\n const premiseGroundedActors = opportunity.actors.filter((a) => a.premise);\n let viewerPremiseContext = '';\n let otherPremiseContext = '';\n\n if (premiseGroundedActors.length > 0) {\n // Only fetch premises for actors that are actually premise-grounded, not all parties\n const groundedOtherIds = premiseGroundedActors\n .filter((a) => a.userId !== viewerId)\n .map((a) => a.userId);\n const viewerIsGrounded = premiseGroundedActors.some((a) => a.userId === viewerId);\n\n const results = await Promise.all([\n ...(viewerIsGrounded ? [database.getPremisesForUser(viewerId, 'ACTIVE')] : []),\n ...groundedOtherIds.map((uid) => database.getPremisesForUser(uid, 'ACTIVE')),\n ]);\n\n let idx = 0;\n if (viewerIsGrounded) {\n const viewerPremises = results[idx++];\n if (viewerPremises?.length) {\n viewerPremiseContext =\n '\\nPremises (self-descriptions):\\n' +\n viewerPremises\n .slice(0, 5)\n .map((p) => `- ${p.assertion.text}`)\n .join('\\n');\n }\n }\n\n const otherPremiseLines: string[] = [];\n for (let i = 0; i < groundedOtherIds.length; i++) {\n const premises = results[idx++];\n if (premises?.length) {\n for (const p of premises.slice(0, 3)) {\n otherPremiseLines.push(`- ${p.assertion.text}`);\n }\n }\n }\n if (otherPremiseLines.length > 0) {\n otherPremiseContext = '\\nPremises (self-descriptions):\\n' + otherPremiseLines.join('\\n');\n }\n }\n\n let viewerContext: string;\n let otherPartyContext: string;\n\n if (isIntroducer) {\n // Introducer view: minimal viewer context (just name + role), rich other-party context with intents\n const introducerApproved = opportunity.actors.find(a => a.role === 'introducer')?.approved === true;\n const hasApproved = introducerApproved || opportunity.status !== 'latent';\n viewerContext = [\n \"Profile:\",\n `Name: ${viewerProfile?.identity?.name ?? \"Unknown\"}`,\n hasApproved\n ? \"Role: You are the introducer who suggested this connection.\"\n : \"Role: You are being asked whether these two people would benefit from meeting. You have NOT yet approved this introduction.\",\n ].join(\"\\n\");\n\n const otherParts = otherPartyIds.map((uid, idx) => {\n const profile = otherProfiles[idx] as Awaited<\n ReturnType<typeof database.getProfile>\n >;\n const name = profile?.identity?.name ?? \"Unknown\";\n const bio = profile?.identity?.bio ?? \"\";\n const location = profile?.identity?.location ?? \"\";\n const context = profile?.context ?? \"\";\n const intents = partyIntentsMap?.get(uid);\n const intentLines = intents?.length\n ? intents\n .slice(0, 5)\n .map((i) => ` - ${i.payload}${i.summary ? ` (${i.summary})` : \"\"}`)\n : [\" (no active intents)\"];\n return [\n `${name}:`,\n ` Bio: ${bio}`,\n location ? ` Location: ${location}` : null,\n context ? ` Context: ${context}` : null,\n ` Active intents:`,\n ...intentLines,\n ]\n .filter(Boolean)\n .join(\"\\n\");\n });\n otherPartyContext =\n otherParts.join(\"\\n\\n\") || \"Parties (details not available).\";\n } else {\n // Non-introducer view: full viewer profile + intents, other party profiles\n const viewerContextLines = [\n \"Profile:\",\n `Name: ${viewerProfile?.identity?.name ?? \"Unknown\"}`,\n `Bio: ${viewerProfile?.identity?.bio ?? \"\"}`,\n `Location: ${viewerProfile?.identity?.location ?? \"\"}`,\n `Context: ${viewerProfile?.context ?? \"\"}`,\n \"Active intents:\",\n ...(viewerIntents?.length\n ? viewerIntents.map(\n (i) => `- ${i.payload}${i.summary ? ` (${i.summary})` : \"\"}`,\n )\n : [\"(none listed)\"]),\n ];\n viewerContext = viewerContextLines.join(\"\\n\");\n\n const otherParts = otherPartyIds.map((uid, idx) => {\n const profile = otherProfiles[idx] as Awaited<\n ReturnType<typeof database.getProfile>\n >;\n const name = profile?.identity?.name ?? \"Unknown\";\n const bio = profile?.identity?.bio ?? \"\";\n return `${name}: ${bio}`;\n });\n otherPartyContext =\n otherParts.join(\"\\n\\n\") || \"Other party (details not available).\";\n }\n\n const interp = opportunity.interpretation;\n const signalsSummary = summarizeSignalsForPresenter(interp.signals);\n\n // Detect introduction-originated opportunities: only when there is an explicit introducer actor.\n // Do NOT use detection.source === \"manual\" alone — system-discovered opportunities can have manual source without an introducer.\n const introducerActor = opportunity.actors.find(\n (a) => a.role === \"introducer\",\n );\n const isIntroduction = !!introducerActor;\n let introducerName: string | undefined;\n if (introducerActor) {\n introducerName = opportunity.detection.createdByName;\n if (!introducerName) {\n const introducerProfile = await database.getProfile(\n introducerActor.userId,\n );\n introducerName = introducerProfile?.identity?.name ?? undefined;\n }\n }\n\n const counterpartName =\n otherPartyIds.length === 1 && otherProfiles[0]\n ? (otherProfiles[0] as { identity?: { name?: string } })?.identity?.name?.trim()\n : undefined;\n const viewerNameForFilter = viewerProfile?.identity?.name?.trim();\n const matchReasoning =\n counterpartName && interp.reasoning\n ? viewerCentricCardSummary(\n interp.reasoning,\n counterpartName,\n 400,\n viewerNameForFilter,\n introducerName,\n )\n : stripUuids(interp.reasoning);\n\n if (viewerPremiseContext) {\n viewerContext += viewerPremiseContext;\n }\n if (otherPremiseContext) {\n otherPartyContext += otherPremiseContext;\n }\n\n const result: PresenterInput = {\n viewerContext,\n otherPartyContext,\n matchReasoning,\n category: interp.category ?? \"connection\",\n confidence:\n typeof interp.confidence === \"number\"\n ? interp.confidence\n : parseFloat(String(interp.confidence ?? 0)) || 0,\n signalsSummary,\n indexName: indexRecord?.title ?? contextIndexId ?? \"\",\n viewerRole: myActor.role ?? \"party\",\n isIntroduction,\n introducerName,\n };\n\n return result;\n}\n"]}
|
|
1
|
+
{"version":3,"file":"opportunity.presenter.js","sourceRoot":"/","sources":["opportunity/opportunity.presenter.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Opportunity Presenter Agent\n *\n * Generates personalized, second-person explanations of why an opportunity\n * matters to the viewing user. Uses full opportunity data (interpretation,\n * actors, profiles, intents, index) to produce headline, personalizedSummary,\n * and suggestedAction for chat tools and user-facing surfaces.\n */\n\nimport type { Runnable } from \"@langchain/core/runnables\";\nimport { HumanMessage, SystemMessage } from \"@langchain/core/messages\";\nimport { z } from \"zod\";\n\nimport { Timed } from \"../shared/observability/performance.js\";\n\nimport { protocolLogger } from \"../shared/observability/protocol.logger.js\";\nimport { createStructuredModel } from \"../shared/agent/model.config.js\";\nimport { viewerCentricCardSummary } from \"./opportunity.presentation.js\";\nimport type { Opportunity } from \"../shared/interfaces/database.interface.js\";\nimport type { ChatGraphCompositeDatabase } from \"../shared/interfaces/database.interface.js\";\nimport type { NegotiationContext } from \"./negotiation-context.loader.js\";\nimport { stripUuids, stripIntroducerMentions } from \"./opportunity.presentation.js\";\nimport { stripUnsupportedOpportunityClaims } from \"./opportunity.claim-safety.js\";\nimport { DEFAULT_EMPTY_FALLBACK_TEXT, DEFAULT_FALLBACK_ACTION, DEFAULT_FALLBACK_HEADLINE, safeFallbackSummary } from \"./opportunity.safe-presentation.js\";\n\n/**\n * Minimal database interface required by gatherPresenterContext.\n * Any database adapter that implements these three methods can be passed.\n */\nexport type PresenterDatabase = Pick<\n ChatGraphCompositeDatabase,\n \"getProfile\" | \"getActiveIntents\" | \"getNetwork\" | \"getPremisesForUser\"\n>;\n\nconst presentLog = protocolLogger(\"OpportunityPresenter:present\");\nconst presentHomeCardLog = protocolLogger(\"OpportunityPresenter:presentHomeCard\");\nconst LLM_TIMEOUT_MS = 20_000;\n\n\nconst GREETING_DESCRIPTION =\n \"A 2-4 sentence first-person message the viewer could send to the counterpart, in the viewer's voice, referencing what they have in common. Plain prose only — no markdown, no greeting prefix like 'Hey {Name},'. Example body: 'Saw we're both working on regenerative coordination tooling — your post on consent flows resonated. Would love to compare notes if you have time this week.'\";\n\n// ──────────────────────────────────────────────────────────────\n// SCHEMA & TYPES\n// ──────────────────────────────────────────────────────────────\n\nconst PresentationSchema = z.object({\n headline: z\n .string()\n .describe(\n \"Short, compelling headline for this opportunity (e.g., 'A React expert who needs your design skills')\",\n ),\n personalizedSummary: z\n .string()\n .describe(\n \"2-3 sentence explanation using 'you' language, explaining why this opportunity is specifically valuable for the viewer based on their intents and profile\",\n ),\n suggestedAction: z.string().describe(\"Brief suggested next step\"),\n greeting: z.string().max(500).describe(GREETING_DESCRIPTION),\n});\n\nconst responseFormat = z.object({\n presentation: PresentationSchema,\n});\n\nexport type OpportunityPresentationResult = z.infer<typeof PresentationSchema> & {\n /** True when any output field used resilience fallback copy. */\n isFallback?: boolean;\n /** Diagnostic category; never changes production fallback policy. */\n fallbackReason?: \"timeout\" | \"error\" | \"sanitization\";\n};\n\n/** Input for home-card presenter call; extends PresenterInput with optional mutual intent count. */\nexport interface HomeCardPresenterInput extends PresenterInput {\n /** Number of overlapping intents (for generating mutualIntentsLabel). */\n mutualIntentCount?: number;\n /**\n * Snapshot of the opportunity's negotiation, if one exists. When status is\n * `negotiating`, the presenter returns a templated chip without invoking\n * the LLM. For `pending`/`stalled`/`accepted`/`rejected`, the full\n * transcript and outcome ground the LLM's explanation.\n */\n negotiationContext?: NegotiationContext;\n}\n\n/** LLM-generated fields for home-card presentation (buttons are hardcoded by callers, not LLM-generated). */\nexport const HomeCardLLMSchema = z.object({\n headline: z\n .string()\n .describe(\"Short, compelling headline for this opportunity\"),\n personalizedSummary: z\n .string()\n .describe(\n \"2-3 sentence explanation in 'you' language for the main card body\",\n ),\n digestSummary: z\n .string()\n .max(220)\n .describe(\n \"One concise digest-ready sentence for a morning brief. It must be addressed to the viewer and mention the counterpart by name, e.g. 'You might like meeting Paul because ...'. No markdown.\",\n ),\n suggestedAction: z\n .string()\n .describe(\"Brief suggested next step (e.g. CTA line)\"),\n narratorRemark: z\n .string()\n .max(80)\n .describe(\n \"One short sentence for the narrator chip, max ~80 chars (e.g. who is suggesting and why)\",\n ),\n mutualIntentsLabel: z\n .string()\n .max(48)\n .describe(\n \"Short line for the subtitle under the other party name (e.g. '3 mutual intents', 'Shared interests', 'Aligned goals'). NEVER output '0 mutual intents' — use a qualitative phrase like 'Shared interests' when no numeric count is available.\",\n ),\n greeting: z.string().max(500).describe(GREETING_DESCRIPTION),\n});\n\n/** LLM-generated result from presentHomeCard (callers append button labels from opportunity.constants). */\nexport type HomeCardLLMResult = z.infer<typeof HomeCardLLMSchema> & {\n /**\n * True when the LLM call failed and this is fallback-shaped copy built from\n * raw match reasoning. Callers with strict quality requirements (digests,\n * long-lived caches) should check this before sending/persisting — fallback\n * output is otherwise indistinguishable from genuine LLM output.\n */\n isFallback?: boolean;\n};\n\n/** Full home-card display contract including hardcoded button labels (assembled by callers). */\nexport type HomeCardPresentationResult = HomeCardLLMResult & {\n primaryActionLabel: string;\n secondaryActionLabel: string;\n};\n\nconst homeCardResponseFormat = z.object({\n presentation: HomeCardLLMSchema,\n});\n\n/** Input for a single presenter call (all context pre-assembled). */\nexport interface PresenterInput {\n viewerContext: string;\n otherPartyContext: string;\n matchReasoning: string;\n category: string;\n confidence: number;\n signalsSummary: string;\n indexName: string;\n viewerRole: string;\n opportunityStatus?: string;\n /** True when this opportunity was created via an explicit introduction (not automatic discovery). */\n isIntroduction?: boolean;\n /** Name of the person who made the introduction, if applicable. */\n introducerName?: string;\n}\n\n// ──────────────────────────────────────────────────────────────\n// SYSTEM PROMPT\n// ──────────────────────────────────────────────────────────────\n\nconst systemPrompt = `\nYou are an expert at presenting connection opportunities to users in a way that feels personal and compelling.\n\nYour goal: Given raw context about the viewer (their profile, intents), the other person(s), and why the system matched them, produce a short headline, a personalized summary, and a suggested action.\n\nRules:\n1. Address the VIEWER directly using \"you\" and \"your\". This is for them.\n2. Be concise and compelling — not analytical or third-party. No \"The source user\" or \"The candidate\"; use names or \"they\" where needed.\n3. Do not leak private or confidential details. Use only the context provided.\n4. Vary user-facing nouns naturally. Do not repeatedly use the same label in one response.\n5. If possible, avoid repeating \"opportunity\" in both headline and summary. Prefer alternatives like \"connection\", \"thought partner\", \"mutual fit\", \"valuable conversation\", or \"peer\".\n6. Prefer first names in user-facing copy. Do not repeatedly use full names unless needed to disambiguate.\n7. Network assignment, network title/type, and network/event metadata are retrieval context only. They are NEVER proof that a person attended or will attend, belongs to a group, resides in a place, knows anyone from the network, or shared a session, time, place, or location with anyone. Do not make co-attendance, membership, residence, shared-session, or same-place/same-time claims from network co-membership.\n\n**Introduction-originated opportunities:**\nWhen INTRODUCTION CONTEXT is provided, this opportunity was explicitly created by an introducer (a real person who saw value in this connection). This is NOT an automatic system discovery — someone made a deliberate judgment.\n- For ALL roles: acknowledge the introducer's role naturally. E.g., \"[Introducer name] thinks you should meet [other person]\" or \"[Introducer name] connected you because...\"\n- The introduction itself is a strong signal — treat it with the weight of a personal recommendation.\n- If the parties' intents don't obviously overlap, that's fine — the introducer saw something worth connecting. Focus on what the introducer likely saw.\n\n**Role-Specific Presentation:**\n\n**If viewer is \"introducer\":**\n- The viewer suggested this connection between two (or more) OTHER people. The opportunity is NOT about the viewer's own needs.\n- Headline: describe the connection between the parties (e.g., \"Connecting a React expert with a startup founder\").\n- Personalized summary: explain why the people YOU are introducing should meet. Reference THEIR profiles and intents, not yours. Frame it as \"you're connecting X and Y because...\" rather than \"this matches your intent\".\n- Suggested action: guide sharing (e.g., \"Share this with [name] to get things started\").\n- CRITICAL: Do NOT reference the introducer's own intents, skills, or needs. The introducer is the matchmaker, not a party.\n\n**If viewer is \"patient\" or \"party\":**\n- Reference their specific intents, skills, or interests that align with this opportunity.\n- If this is an introduction: mention who introduced them and frame it as a personal recommendation.\n- Headline: one short line that hooks (e.g., \"[Name] thinks you should meet [Other]\" or \"A React expert who needs your design skills\").\n- Personalized summary: 2-3 sentences. Why is this opportunity for *them*? If introduced, lead with the introduction.\n- Suggested action: encourage action (\"Send a message to start the conversation\" or \"Share this intro\").\n\n**If viewer is \"agent\":**\n- They are seeing this because someone already reached out.\n- If this is an introduction: mention who made the introduction.\n- Reference their skills/expertise that make them a match.\n- Headline: what the other person needs that they can provide.\n- Personalized summary: 2-3 sentences. Why someone reached out to them.\n- Suggested action: \"Someone is interested in connecting — check their message\" or \"Review and respond\".\n\n**If viewer is \"peer\":**\n- Mutual opportunity. Reference shared or complementary interests.\n- If this is an introduction: mention who connected them.\n- Headline: the mutual connection angle.\n- Personalized summary: 2-3 sentences. Why this is mutually valuable.\n- Suggested action: \"Send an intro to connect\" or \"Start a conversation\".\n`;\n\nconst homeCardSystemPrompt = `\nYou are an expert at presenting connection opportunities for a home feed card.\n\nGiven context about the viewer, the other person, and why they were matched, produce:\n1. headline: one short hook line.\n2. personalizedSummary: 2-3 sentences in \"you\" language (main body text).\n3. digestSummary: one polished morning-brief sentence that can be printed directly after the person's linked name. No markdown, no field labels.\n4. suggestedAction: one brief suggested next step.\n5. narratorRemark: one short sentence for the narrator chip (who is suggesting and why; max ~80 chars).\n6. greeting: a 2-4 sentence first-person message the viewer could send to the counterpart. Plain prose, no greeting prefix, no markdown.\n7. mutualIntentsLabel: short subtitle under the other party's name. Examples: \"3 mutual intents\", \"Shared interests\", \"Aligned goals\" — keep it brief. NEVER output \"0 mutual intents\" or any zero-count label; use a qualitative phrase instead.\n\nRules:\n- Address the viewer with \"you\"/\"your\". Be concise and compelling.\n- narratorRemark should feel like a single sentence from the narrator (Index or a person), not meta-commentary.\n- narratorRemark is displayed with the narrator name prepended (e.g. \"Index: …\" or \"Alice: …\"). Do NOT start narratorRemark with the narrator's name or repeat it; write only the remark (e.g. \"Based on your overlapping intents\" or \"introduced you two, sensing a valuable connection\").\n- Vary wording for the match itself. Do not repeat \"opportunity\" across headline, summary, and narratorRemark when alternatives fit.\n- Prefer first names in user-facing copy. Avoid repeated full names unless disambiguation is necessary.\n- Network assignment, network title/type, and network/event metadata are retrieval context only. They are NEVER proof that a person attended or will attend, belongs to a group, resides in a place, knows anyone from the network, or shared a session, time, place, or location with anyone. Do not make co-attendance, membership, residence, shared-session, or same-place/same-time claims from network co-membership.\n- digestSummary must be grammatically complete as a standalone sentence. It should usually start with \"You might like meeting {Name} because ...\" for direct connections, or \"You may be able to help {Name} because ...\" for connector/introducer cards.\n- digestSummary must NOT use awkward third-person fragments like \"Name is...\", \"they're ..., and is...\", \"you is...\", or \"the discoverer's query\".\n- digestSummary must be one sentence, MUST fit within 180 characters when possible, and MUST contain no markdown links; the caller will attach links.\n- If you cannot fit every detail, choose one clear reason and stop. Do not rely on downstream truncation.\n\n**Introduction-originated opportunities (ONLY when INTRODUCTION CONTEXT is provided):**\nWhen INTRODUCTION CONTEXT is provided, this opportunity was explicitly created by an introducer. It was NOT automatically discovered.\n- For parties/patients/agents/peers viewing an introduction: keep the introducer signal in narratorRemark (and narrator chip), not in personalizedSummary.\n- For these introduced parties, personalizedSummary must focus ONLY on fit/value between viewer and counterpart. Do NOT mention the introducer there.\n- narratorRemark should carry the introduction signal (e.g., \"saw strong alignment between you two\" or \"thought this connection could be valuable\"), without repeating the narrator name at the start.\n- This is a personal recommendation, not an algorithm match. Frame it accordingly.\n\n**CRITICAL: NEVER include introducer names in personalizedSummary. Examples:**\n❌ WRONG: \"Seref introduced you to Lucy, who is actively seeking a product co-founder...\"\n✅ CORRECT: \"Lucy is actively seeking a product co-founder for a niche APAC marketplace. With your expertise in UX and AI, this could be an ideal collaboration.\"\n\n❌ WRONG: \"Bob thinks you should meet Alice because your React skills align with her needs.\"\n✅ CORRECT: \"Alice is building a React-based platform and needs frontend expertise. Your experience with component architecture makes you a strong fit.\"\n\n❌ WRONG: \"Jane connected you to Mark, who is looking for a designer.\"\n✅ CORRECT: \"Mark is building a consumer app and needs design expertise. Your background in user-centered design aligns well with what he's building.\"\n\nRemember: The introducer's name goes ONLY in narratorRemark, NEVER in personalizedSummary.\n\n**When INTRODUCTION CONTEXT is NOT provided (system-discovered match):**\n- Do NOT use introducer-style wording. Do NOT say \"you suggested\", \"this is an introduction you suggested\", or \"you suggested this connection\". The system found this match; no human introducer was involved.\n- Instead, narratorRemark should describe why the match is relevant (e.g. \"Based on your overlapping intents\", \"Your skills align with what they need\").\n\n**Negotiation-grounded explanations (ONLY when NEGOTIATION CONTEXT is provided):**\nWhen NEGOTIATION CONTEXT is provided, this opportunity passed through an agent-to-agent negotiation. Use the transcript to ground your explanation in the concrete reasoning the agents exchanged.\n- Personalize the summary with *why* the negotiation produced this match — reference the roles the agents agreed on, the specific concerns raised, and how they were resolved.\n- For status \"stalled\" with reason \"turn_cap\": the agents hit the turn limit without reaching agreement. Frame the card as a hedged possibility rather than a confident match; narratorRemark should signal \"agents couldn't fully converge\" without sounding negative.\n- For status \"stalled\" with reason \"timeout\": one side went silent. Suggest the user re-engage if interested.\n- For status \"accepted\": the agents agreed; the card should confidently explain *why* they agreed.\n- For status \"rejected\": the agents declined. The card should explain the reason briefly so the user understands — not dwell on it.\n- Do NOT invent turn content. Only reference what is in the NEGOTIATION CONTEXT block.\n\n- Exception for connector/introducer: if viewer role is \"introducer\" (any status), this is a curation/connector card. Use:\n - suggestedAction: one short line about sharing the intro or confirming the match.\n - mutualIntentsLabel: a short connector label (e.g. \"Connector match\", \"You can bridge this\").\n - headline: describe the connection between the parties (e.g., \"Connecting a PhD researcher with a translator\"). Do NOT reference the introducer's own needs.\n - personalizedSummary: explain why the parties you're introducing should meet, referencing THEIR profiles and intents, not yours.\n\n**CRITICAL for latent introducer cards (opportunity status is \"latent\"):**\nWhen the viewer is the introducer and the opportunity status is \"latent\", the introducer has NOT yet approved this match. They are evaluating whether to make the introduction.\n- narratorRemark MUST use evaluation/curation language (e.g. \"Could be a strong match\", \"Worth introducing?\", \"Interesting overlap here\").\n- Do NOT say \"you suggested\", \"you introduced\", \"you connected\", or any past-tense language implying the introduction was already made.\n- suggestedAction should encourage evaluation (e.g. \"Approve if you see the fit\").\n- Exception for new-connection reveal: if viewer role is \"agent\", status is \"accepted\", and there is an introducer, this is the agent's first time seeing this opportunity. Use:\n - suggestedAction: a short line about joining the conversation.\n`;\n\n// ──────────────────────────────────────────────────────────────\n// DETERMINISTIC OUTPUT VALIDATION\n// ──────────────────────────────────────────────────────────────\n\nfunction sanitizePresenterField(\n value: string,\n fallback: string,\n allowEmpty = fallback === \"\",\n): { value: string; usedFallback: boolean } {\n const cleaned = stripUnsupportedOpportunityClaims(stripUuids(value));\n if (cleaned || allowEmpty) {\n return { value: cleaned, usedFallback: false };\n }\n return { value: fallback, usedFallback: true };\n}\n\n// ──────────────────────────────────────────────────────────────\n// CLASS\n// ──────────────────────────────────────────────────────────────\n\nexport class OpportunityPresenter {\n private model: Runnable;\n private homeCardModel: Runnable;\n\n constructor() {\n this.model = createStructuredModel(\"opportunityPresenter\", responseFormat, {\n name: \"opportunity_presenter\",\n });\n this.homeCardModel = createStructuredModel(\"opportunityPresenter\", homeCardResponseFormat, {\n name: \"opportunity_presenter_home_card\",\n });\n }\n\n private async invokeWithTimeout(\n targetModel: Runnable,\n messages: (SystemMessage | HumanMessage)[],\n signal?: AbortSignal,\n ): Promise<unknown> {\n const timeoutReason = `LLM invoke timed out after ${LLM_TIMEOUT_MS}ms`;\n const controller = new AbortController();\n let timeoutId: ReturnType<typeof setTimeout> | undefined;\n\n const combinedSignal = signal ? AbortSignal.any([signal, controller.signal]) : controller.signal;\n const invokePromise = targetModel.invoke(messages, { signal: combinedSignal });\n\n const timeoutPromise = new Promise<never>((_, reject) => {\n timeoutId = setTimeout(() => {\n controller.abort(timeoutReason);\n reject(new Error(timeoutReason));\n }, LLM_TIMEOUT_MS);\n });\n\n try {\n return await Promise.race([invokePromise, timeoutPromise]);\n } finally {\n if (timeoutId) {\n clearTimeout(timeoutId);\n }\n }\n }\n\n /**\n * Generate personalized presentation for a single opportunity.\n */\n @Timed()\n public async present(\n input: PresenterInput,\n options: { signal?: AbortSignal } = {},\n ): Promise<OpportunityPresentationResult> {\n const introContext = input.isIntroduction\n ? `\\nINTRODUCTION CONTEXT: This opportunity was created by an explicit introduction from ${input.introducerName ?? \"someone in the community\"}. It was NOT discovered automatically — a real person made this connection.\\n`\n : \"\";\n const humanContent = `\nVIEWER (the person seeing this opportunity):\n${input.viewerContext}\n\nOTHER PARTY:\n${input.otherPartyContext}\n\nMATCH CONTEXT:\n- Category: ${input.category}\n- Confidence: ${input.confidence}\n- Why we matched: ${input.matchReasoning}\n- Signals: ${input.signalsSummary}\n${introContext}\nCOMMUNITY: ${input.indexName}\nViewer's role in this opportunity: ${input.viewerRole}\n\nProduce headline, personalizedSummary (2-3 sentences in \"you\" language), suggestedAction, and greeting.\n`;\n\n try {\n const messages = [\n new SystemMessage(systemPrompt),\n new HumanMessage(humanContent),\n ];\n const result = await this.invokeWithTimeout(this.model, messages, options.signal);\n const parsed = responseFormat.parse(result);\n const headline = sanitizePresenterField(\n parsed.presentation.headline,\n DEFAULT_FALLBACK_HEADLINE,\n );\n const summary = sanitizePresenterField(\n parsed.presentation.personalizedSummary,\n DEFAULT_EMPTY_FALLBACK_TEXT,\n );\n const action = sanitizePresenterField(\n parsed.presentation.suggestedAction,\n DEFAULT_FALLBACK_ACTION,\n );\n const greeting = sanitizePresenterField(parsed.presentation.greeting, \"\");\n const usedFallback = headline.usedFallback || summary.usedFallback || action.usedFallback || greeting.usedFallback;\n return {\n headline: headline.value,\n personalizedSummary: summary.value,\n suggestedAction: action.value,\n greeting: greeting.value,\n ...(usedFallback ? { isFallback: true, fallbackReason: \"sanitization\" as const } : {}),\n };\n } catch (e) {\n if (options.signal?.aborted) throw e;\n const message = e instanceof Error ? e.message : String(e);\n const timeoutReason = message.includes(\"timed out\") ? message : undefined;\n presentLog.warn(\n \"LLM failed, returning fallback\",\n {\n event: \"presenter_fallback\",\n presenter: \"opportunity\",\n reason: timeoutReason ? \"timeout\" : \"parse_error\",\n message,\n timeoutReason,\n },\n );\n return {\n headline: DEFAULT_FALLBACK_HEADLINE,\n personalizedSummary: safeFallbackSummary(input.matchReasoning),\n suggestedAction: DEFAULT_FALLBACK_ACTION,\n greeting: \"\",\n isFallback: true,\n fallbackReason: timeoutReason ? \"timeout\" : \"error\",\n };\n }\n }\n\n /**\n * Generate LLM-powered home-card content (headline, body, narrator remark, mutual-intent label).\n * Callers append button labels from opportunity.constants.\n *\n * When `negotiationContext.status === 'negotiating'`, returns a templated\n * chip synchronously without invoking the LLM — the card just reflects\n * \"negotiation in progress\" at that point.\n */\n @Timed()\n public async presentHomeCard(\n input: HomeCardPresenterInput,\n ): Promise<HomeCardLLMResult> {\n if (input.negotiationContext?.status === 'negotiating') {\n return buildNegotiatingChip(input);\n }\n\n const mutualHint =\n input.mutualIntentCount != null && input.mutualIntentCount > 0\n ? `There are ${input.mutualIntentCount} overlapping intent(s) between viewer and other party.`\n : \"Match is based on profile and intent alignment. Do not cite a numeric intent count.\";\n const introContext = input.isIntroduction\n ? `\\nINTRODUCTION CONTEXT: This opportunity was created by an explicit introduction from ${input.introducerName ?? \"someone in the community\"}. It was NOT discovered automatically — a real person made this connection.\\n`\n : \"\";\n const negotiationBlock = buildNegotiationPromptBlock(input.negotiationContext);\n // When negotiation context exists, lead with it — these cards exist\n // *because* the negotiation happened. Trailing the block lets weaker\n // models lean on surface signals and ignore the transcript entirely.\n const negotiationDirective = negotiationBlock\n ? `\\nIMPORTANT: This opportunity surfaced because the agents negotiated and converged. Both your personalizedSummary AND your digestSummary MUST reference at least one specific signal from the NEGOTIATION CONTEXT block below — what concern was raised, what was confirmed, what the agents agreed on. The digestSummary is the one-line morning-brief sentence a user reads before deciding to act, so it must communicate *why this specific match* surfaced now (the negotiation that led to it), not a generic skill-complementarity line. Do not produce the generic summary every card looked like before this negotiation happened.\\n`\n : \"\";\n const humanContent = `\n${negotiationBlock}${negotiationDirective}\nVIEWER (the person seeing this opportunity):\n${input.viewerContext}\n\nOTHER PARTY:\n${input.otherPartyContext}\n\nMATCH CONTEXT:\n- Category: ${input.category}\n- Confidence: ${input.confidence}\n- Why we matched: ${input.matchReasoning}\n- Signals: ${input.signalsSummary}\n- ${mutualHint}\n${introContext}\nCOMMUNITY: ${input.indexName}\nViewer's role in this opportunity: ${input.viewerRole}\nOpportunity status: ${input.opportunityStatus ?? \"pending\"}\n\nProduce headline, personalizedSummary, digestSummary, suggestedAction, narratorRemark, greeting, and mutualIntentsLabel.\n`;\n\n const isIntroducer = input.viewerRole === \"introducer\";\n\n try {\n const messages = [\n new SystemMessage(homeCardSystemPrompt),\n new HumanMessage(humanContent),\n ];\n const result = await this.invokeWithTimeout(this.homeCardModel, messages);\n const parsed = homeCardResponseFormat.parse(result);\n if (/^0\\s+(mutual|overlapping)\\s+intent/i.test(parsed.presentation.mutualIntentsLabel)) {\n parsed.presentation.mutualIntentsLabel = \"Shared interests\";\n }\n if (input.isIntroduction && input.introducerName) {\n parsed.presentation.personalizedSummary = stripIntroducerMentions(\n parsed.presentation.personalizedSummary,\n input.introducerName,\n );\n parsed.presentation.digestSummary = stripIntroducerMentions(\n parsed.presentation.digestSummary,\n input.introducerName,\n );\n }\n\n const fields = {\n headline: sanitizePresenterField(parsed.presentation.headline, DEFAULT_FALLBACK_HEADLINE),\n personalizedSummary: sanitizePresenterField(parsed.presentation.personalizedSummary, DEFAULT_EMPTY_FALLBACK_TEXT),\n digestSummary: sanitizePresenterField(\n parsed.presentation.digestSummary,\n isIntroducer\n ? \"You may be able to help make a useful introduction here.\"\n : \"You might like meeting them based on your current interests.\",\n ),\n suggestedAction: sanitizePresenterField(parsed.presentation.suggestedAction, DEFAULT_FALLBACK_ACTION),\n narratorRemark: sanitizePresenterField(parsed.presentation.narratorRemark, \"Worth a look.\"),\n mutualIntentsLabel: sanitizePresenterField(\n parsed.presentation.mutualIntentsLabel,\n isIntroducer ? \"Connector match\" : \"Shared interests\",\n ),\n greeting: sanitizePresenterField(parsed.presentation.greeting, \"\"),\n };\n const usedFallback = Object.values(fields).some((field) => field.usedFallback);\n return {\n headline: fields.headline.value,\n personalizedSummary: fields.personalizedSummary.value,\n digestSummary: fields.digestSummary.value,\n suggestedAction: fields.suggestedAction.value,\n narratorRemark: fields.narratorRemark.value,\n mutualIntentsLabel: fields.mutualIntentsLabel.value,\n greeting: fields.greeting.value,\n ...(usedFallback ? { isFallback: true } : {}),\n };\n } catch (e) {\n const message = e instanceof Error ? e.message : String(e);\n const timeoutReason = message.includes(\"timed out\") ? message : undefined;\n presentHomeCardLog.warn(\n \"LLM failed, returning fallback\",\n {\n event: \"presenter_fallback\",\n presenter: \"home_card\",\n reason: timeoutReason ? \"timeout\" : \"parse_error\",\n message,\n timeoutReason,\n },\n );\n const fallbackSummary = safeFallbackSummary(input.matchReasoning, {\n introducerName: input.isIntroduction ? input.introducerName : undefined,\n });\n return {\n headline: \"A promising connection\",\n personalizedSummary: fallbackSummary,\n digestSummary: isIntroducer\n ? \"You may be able to help make a useful introduction here.\"\n : \"You might like meeting them based on your current interests.\",\n suggestedAction: isIntroducer\n ? \"Share this introduction to get things started.\"\n : \"Take a look and decide whether to reach out.\",\n narratorRemark: \"Worth a look.\",\n mutualIntentsLabel: isIntroducer\n ? \"Connector match\"\n : input.mutualIntentCount != null && input.mutualIntentCount > 0\n ? `${input.mutualIntentCount} mutual intent${input.mutualIntentCount !== 1 ? \"s\" : \"\"}`\n : \"Shared interests\",\n greeting: \"\",\n isFallback: true,\n };\n }\n }\n\n /**\n * Process multiple opportunities in parallel with bounded concurrency.\n */\n @Timed()\n public async presentBatch(\n inputs: PresenterInput[],\n options?: { concurrency?: number },\n ): Promise<OpportunityPresentationResult[]> {\n const concurrency = options?.concurrency ?? 5;\n const results: OpportunityPresentationResult[] = [];\n for (let i = 0; i < inputs.length; i += concurrency) {\n const chunk = inputs.slice(i, i + concurrency);\n const chunkResults = await Promise.all(\n chunk.map((inp) => this.present(inp)),\n );\n results.push(...chunkResults);\n }\n return results;\n }\n\n /**\n * Process multiple opportunities as home cards in parallel with bounded concurrency.\n * Returns full home-card display contracts (headline, body, narrator remark, action labels, mutual-intent label).\n */\n @Timed()\n public async presentHomeCardBatch(\n inputs: HomeCardPresenterInput[],\n options?: { concurrency?: number },\n ): Promise<HomeCardLLMResult[]> {\n const concurrency = options?.concurrency ?? 5;\n const results: HomeCardLLMResult[] = [];\n for (let i = 0; i < inputs.length; i += concurrency) {\n const chunk = inputs.slice(i, i + concurrency);\n const chunkResults = await Promise.all(\n chunk.map((inp) => this.presentHomeCard(inp)),\n );\n results.push(...chunkResults);\n }\n return results;\n }\n}\n\n// ──────────────────────────────────────────────────────────────\n// NEGOTIATION CONTEXT HELPERS\n// ──────────────────────────────────────────────────────────────\n\n/**\n * Builds a \"NEGOTIATION CONTEXT:\" block for the home-card prompt. Returns an\n * empty string when the opportunity has no meaningful negotiation context\n * (draft/latent) or when the opportunity is still negotiating (handled via\n * the templated chip, not the LLM).\n */\nfunction buildNegotiationPromptBlock(context: NegotiationContext | undefined): string {\n if (!context || context.status === 'negotiating') return \"\";\n\n const turnCapLabel = context.turnCap > 0 ? `${context.turnCap}` : \"unlimited\";\n const reason = context.outcome?.reason;\n const reasonLabel = reason === 'turn_cap'\n ? \"agents hit the turn cap without converging\"\n : reason === 'timeout'\n ? \"counterpart went silent before responding\"\n : undefined;\n\n const turnLines = (context.turns ?? []).map((turn, index) => {\n const action = turn.action;\n const reasoning = turn.assessment?.reasoning ?? \"(no reasoning)\";\n const message = turn.message ? ` — said: \"${turn.message}\"` : \"\";\n return `Turn ${index + 1} (${action}): ${reasoning}${message}`;\n });\n\n const outcomeSummary = context.outcome\n ? `Final outcome: ${context.outcome.hasOpportunity ? \"agreed\" : \"declined\"} — ${context.outcome.reasoning}`\n : \"Final outcome: not recorded.\";\n\n return `\nNEGOTIATION CONTEXT:\n- Negotiation status: ${context.status}${reasonLabel ? ` (${reasonLabel})` : \"\"}\n- Turns exchanged: ${context.turnCount} of ${turnCapLabel}\n- Transcript:\n${turnLines.length > 0 ? turnLines.map((l) => ` ${l}`).join(\"\\n\") : \" (no turns recorded)\"}\n- ${outcomeSummary}\n`;\n}\n\n/**\n * Builds a templated home-card result for an opportunity whose negotiation\n * is still in progress. Bypasses the LLM so users see a stable \"currently\n * negotiating\" chip while turns are still being exchanged.\n */\nfunction buildNegotiatingChip(input: HomeCardPresenterInput): HomeCardLLMResult {\n const ctx = input.negotiationContext;\n const turnCount = ctx?.turnCount ?? 0;\n const turnCap = ctx?.turnCap && ctx.turnCap > 0 ? ctx.turnCap : undefined;\n const narratorRemark = turnCap\n ? `Currently negotiating · turn ${turnCount} of ${turnCap}`\n : `Currently negotiating · turn ${turnCount}`;\n\n return {\n headline: \"Negotiation in progress\",\n personalizedSummary: \"Your agent is still talking with theirs to see if this connection makes sense. We'll surface the full match as soon as they converge.\",\n digestSummary: \"Your agent is still checking whether this connection makes sense.\",\n suggestedAction: \"Check back shortly — no action needed yet.\",\n narratorRemark,\n mutualIntentsLabel: input.mutualIntentCount && input.mutualIntentCount > 0\n ? `${input.mutualIntentCount} mutual intent${input.mutualIntentCount !== 1 ? \"s\" : \"\"}`\n : \"Shared interests\",\n greeting: \"\",\n };\n}\n\n// ──────────────────────────────────────────────────────────────\n// CONTEXT GATHERER (used by tools)\n// ──────────────────────────────────────────────────────────────\n\n/**\n * Build the LLM-facing signal summary while excluding pool adjustments. Pool\n * disposition is rendered deterministically by the card chip; asking the\n * presenter to interpret it could turn a demotion into a positive rationale.\n */\nexport function summarizeSignalsForPresenter(\n signals: Opportunity['interpretation']['signals'],\n): string {\n const safeSignals = signals?.filter((signal) => signal.type !== 'pool_discriminator') ?? [];\n if (safeSignals.length === 0) return 'Match based on profile and intent alignment.';\n return safeSignals.map((signal) => `${signal.type}: ${signal.detail ?? signal.type}`).join('; ');\n}\n\n/**\n * Gather all context needed for the presenter from the database.\n * Fetches viewer profile, viewer intents, other party profile(s), and index in parallel.\n *\n * @param displayCounterpartUserId - When set (e.g. for home card), only this counterpart is included in otherPartyContext so the presenter writes about the person on the card. Omitted for introducer view (card shows both parties).\n */\nexport async function gatherPresenterContext(\n database: PresenterDatabase,\n opportunity: Opportunity,\n viewerId: string,\n displayCounterpartUserId?: string,\n): Promise<PresenterInput> {\n const myActor = opportunity.actors.find((a) => a.userId === viewerId);\n if (!myActor) {\n throw new Error(\"Viewer is not an actor in this opportunity\");\n }\n\n const isIntroducer = myActor.role === \"introducer\";\n const otherActors = opportunity.actors.filter((a) => a.userId !== viewerId);\n let otherPartyIds = [...new Set(otherActors.map((a) => a.userId))];\n if (\n displayCounterpartUserId &&\n !isIntroducer &&\n otherPartyIds.includes(displayCounterpartUserId)\n ) {\n otherPartyIds = [displayCounterpartUserId];\n }\n\n const contextIndexId = opportunity.context?.networkId;\n\n // For introducers: fetch profiles + intents for both parties; skip introducer's own intents.\n // For other roles: fetch viewer's profile + intents and other party profiles.\n const [viewerProfile, indexRecord, ...otherProfiles] = await Promise.all([\n database.getProfile(viewerId),\n contextIndexId ? database.getNetwork(contextIndexId) : Promise.resolve(null),\n ...otherPartyIds.map((uid) => database.getProfile(uid)),\n ]);\n\n // Fetch intents: for introducer, fetch each party's intents; otherwise fetch viewer's intents.\n let viewerIntents:\n | Awaited<ReturnType<typeof database.getActiveIntents>>\n | undefined;\n let partyIntentsMap:\n | Map<string, Awaited<ReturnType<typeof database.getActiveIntents>>>\n | undefined;\n\n if (isIntroducer) {\n const partyIntentResults = await Promise.all(\n otherPartyIds.map(async (uid) => ({\n uid,\n intents: await database.getActiveIntents(uid),\n })),\n );\n partyIntentsMap = new Map(\n partyIntentResults.map((r) => [r.uid, r.intents]),\n );\n } else {\n viewerIntents = await database.getActiveIntents(viewerId);\n }\n\n // Fetch premises when any actor is premise-grounded\n const premiseGroundedActors = opportunity.actors.filter((a) => a.premise);\n let viewerPremiseContext = '';\n let otherPremiseContext = '';\n\n if (premiseGroundedActors.length > 0) {\n // Only fetch premises for actors that are actually premise-grounded, not all parties\n const groundedOtherIds = premiseGroundedActors\n .filter((a) => a.userId !== viewerId)\n .map((a) => a.userId);\n const viewerIsGrounded = premiseGroundedActors.some((a) => a.userId === viewerId);\n\n const results = await Promise.all([\n ...(viewerIsGrounded ? [database.getPremisesForUser(viewerId, 'ACTIVE')] : []),\n ...groundedOtherIds.map((uid) => database.getPremisesForUser(uid, 'ACTIVE')),\n ]);\n\n let idx = 0;\n if (viewerIsGrounded) {\n const viewerPremises = results[idx++];\n if (viewerPremises?.length) {\n viewerPremiseContext =\n '\\nPremises (self-descriptions):\\n' +\n viewerPremises\n .slice(0, 5)\n .map((p) => `- ${p.assertion.text}`)\n .join('\\n');\n }\n }\n\n const otherPremiseLines: string[] = [];\n for (let i = 0; i < groundedOtherIds.length; i++) {\n const premises = results[idx++];\n if (premises?.length) {\n for (const p of premises.slice(0, 3)) {\n otherPremiseLines.push(`- ${p.assertion.text}`);\n }\n }\n }\n if (otherPremiseLines.length > 0) {\n otherPremiseContext = '\\nPremises (self-descriptions):\\n' + otherPremiseLines.join('\\n');\n }\n }\n\n let viewerContext: string;\n let otherPartyContext: string;\n\n if (isIntroducer) {\n // Introducer view: minimal viewer context (just name + role), rich other-party context with intents\n const introducerApproved = opportunity.actors.find(a => a.role === 'introducer')?.approved === true;\n const hasApproved = introducerApproved || opportunity.status !== 'latent';\n viewerContext = [\n \"Profile:\",\n `Name: ${viewerProfile?.identity?.name ?? \"Unknown\"}`,\n hasApproved\n ? \"Role: You are the introducer who suggested this connection.\"\n : \"Role: You are being asked whether these two people would benefit from meeting. You have NOT yet approved this introduction.\",\n ].join(\"\\n\");\n\n const otherParts = otherPartyIds.map((uid, idx) => {\n const profile = otherProfiles[idx] as Awaited<\n ReturnType<typeof database.getProfile>\n >;\n const name = profile?.identity?.name ?? \"Unknown\";\n const bio = profile?.identity?.bio ?? \"\";\n const location = profile?.identity?.location ?? \"\";\n const context = profile?.context ?? \"\";\n const intents = partyIntentsMap?.get(uid);\n const intentLines = intents?.length\n ? intents\n .slice(0, 5)\n .map((i) => ` - ${i.payload}${i.summary ? ` (${i.summary})` : \"\"}`)\n : [\" (no active intents)\"];\n return [\n `${name}:`,\n ` Bio: ${bio}`,\n location ? ` Location: ${location}` : null,\n context ? ` Context: ${context}` : null,\n ` Active intents:`,\n ...intentLines,\n ]\n .filter(Boolean)\n .join(\"\\n\");\n });\n otherPartyContext =\n otherParts.join(\"\\n\\n\") || \"Parties (details not available).\";\n } else {\n // Non-introducer view: full viewer profile + intents, other party profiles\n const viewerContextLines = [\n \"Profile:\",\n `Name: ${viewerProfile?.identity?.name ?? \"Unknown\"}`,\n `Bio: ${viewerProfile?.identity?.bio ?? \"\"}`,\n `Location: ${viewerProfile?.identity?.location ?? \"\"}`,\n `Context: ${viewerProfile?.context ?? \"\"}`,\n \"Active intents:\",\n ...(viewerIntents?.length\n ? viewerIntents.map(\n (i) => `- ${i.payload}${i.summary ? ` (${i.summary})` : \"\"}`,\n )\n : [\"(none listed)\"]),\n ];\n viewerContext = viewerContextLines.join(\"\\n\");\n\n const otherParts = otherPartyIds.map((uid, idx) => {\n const profile = otherProfiles[idx] as Awaited<\n ReturnType<typeof database.getProfile>\n >;\n const name = profile?.identity?.name ?? \"Unknown\";\n const bio = profile?.identity?.bio ?? \"\";\n return `${name}: ${bio}`;\n });\n otherPartyContext =\n otherParts.join(\"\\n\\n\") || \"Other party (details not available).\";\n }\n\n const interp = opportunity.interpretation;\n const signalsSummary = summarizeSignalsForPresenter(interp.signals);\n\n // Detect introduction-originated opportunities: only when there is an explicit introducer actor.\n // Do NOT use detection.source === \"manual\" alone — system-discovered opportunities can have manual source without an introducer.\n const introducerActor = opportunity.actors.find(\n (a) => a.role === \"introducer\",\n );\n const isIntroduction = !!introducerActor;\n let introducerName: string | undefined;\n if (introducerActor) {\n introducerName = opportunity.detection.createdByName;\n if (!introducerName) {\n const introducerProfile = await database.getProfile(\n introducerActor.userId,\n );\n introducerName = introducerProfile?.identity?.name ?? undefined;\n }\n }\n\n const counterpartName =\n otherPartyIds.length === 1 && otherProfiles[0]\n ? (otherProfiles[0] as { identity?: { name?: string } })?.identity?.name?.trim()\n : undefined;\n const viewerNameForFilter = viewerProfile?.identity?.name?.trim();\n const matchReasoning =\n counterpartName && interp.reasoning\n ? viewerCentricCardSummary(\n interp.reasoning,\n counterpartName,\n 400,\n viewerNameForFilter,\n introducerName,\n )\n : stripUuids(interp.reasoning);\n\n if (viewerPremiseContext) {\n viewerContext += viewerPremiseContext;\n }\n if (otherPremiseContext) {\n otherPartyContext += otherPremiseContext;\n }\n\n const result: PresenterInput = {\n viewerContext,\n otherPartyContext,\n matchReasoning,\n category: interp.category ?? \"connection\",\n confidence:\n typeof interp.confidence === \"number\"\n ? interp.confidence\n : parseFloat(String(interp.confidence ?? 0)) || 0,\n signalsSummary,\n indexName: indexRecord?.title ?? contextIndexId ?? \"\",\n viewerRole: myActor.role ?? \"party\",\n isIntroduction,\n introducerName,\n };\n\n return result;\n}\n"]}
|
|
@@ -28,7 +28,9 @@ export type PremiseAnalyzerOutput = z.infer<typeof responseFormat>;
|
|
|
28
28
|
export declare class PremiseAnalyzer {
|
|
29
29
|
private model;
|
|
30
30
|
constructor();
|
|
31
|
-
invoke(premiseText: string, profileContext?: string
|
|
31
|
+
invoke(premiseText: string, profileContext?: string, options?: {
|
|
32
|
+
signal?: AbortSignal;
|
|
33
|
+
}): Promise<PremiseAnalyzerOutput>;
|
|
32
34
|
}
|
|
33
35
|
export {};
|
|
34
36
|
//# sourceMappingURL=premise.analyzer.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"premise.analyzer.d.ts","sourceRoot":"/","sources":["premise/premise.analyzer.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAiExB,QAAA,MAAM,cAAc;;;;;;;;;;;;;;;;;;;;;EAoBlB,CAAC;AAEH,MAAM,MAAM,qBAAqB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,cAAc,CAAC,CAAC;AAEnE;;GAEG;AACH,qBAAa,eAAe;IAC1B,OAAO,CAAC,KAAK,CAA2C;;IAS3C,MAAM,
|
|
1
|
+
{"version":3,"file":"premise.analyzer.d.ts","sourceRoot":"/","sources":["premise/premise.analyzer.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAiExB,QAAA,MAAM,cAAc;;;;;;;;;;;;;;;;;;;;;EAoBlB,CAAC;AAEH,MAAM,MAAM,qBAAqB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,cAAc,CAAC,CAAC;AAEnE;;GAEG;AACH,qBAAa,eAAe;IAC1B,OAAO,CAAC,KAAK,CAA2C;;IAS3C,MAAM,CACjB,WAAW,EAAE,MAAM,EACnB,cAAc,CAAC,EAAE,MAAM,EACvB,OAAO,GAAE;QAAE,MAAM,CAAC,EAAE,WAAW,CAAA;KAAO,GACrC,OAAO,CAAC,qBAAqB,CAAC;CAwBlC"}
|
|
@@ -88,7 +88,7 @@ export class PremiseAnalyzer {
|
|
|
88
88
|
name: "premise_analyzer"
|
|
89
89
|
});
|
|
90
90
|
}
|
|
91
|
-
async invoke(premiseText, profileContext) {
|
|
91
|
+
async invoke(premiseText, profileContext, options = {}) {
|
|
92
92
|
invokeLog.verbose('Analyzing premise text', { preview: premiseText.substring(0, 50) });
|
|
93
93
|
const contextBlock = profileContext
|
|
94
94
|
? `\n# Speaker Profile (Context)\n${profileContext}\n`
|
|
@@ -102,7 +102,7 @@ Classify this premise and score its felicity conditions.`;
|
|
|
102
102
|
new SystemMessage(systemPrompt),
|
|
103
103
|
new HumanMessage(prompt),
|
|
104
104
|
];
|
|
105
|
-
const result = await invokeWithAbortSignal(this.model, messages);
|
|
105
|
+
const result = await invokeWithAbortSignal(this.model, messages, options.signal);
|
|
106
106
|
const output = responseFormat.parse(result);
|
|
107
107
|
invokeLog.verbose('Analysis result', { speechActType: output.speechActType, semanticEntropy: output.semanticEntropy });
|
|
108
108
|
return output;
|
|
@@ -111,7 +111,7 @@ Classify this premise and score its felicity conditions.`;
|
|
|
111
111
|
__decorate([
|
|
112
112
|
Timed(),
|
|
113
113
|
__metadata("design:type", Function),
|
|
114
|
-
__metadata("design:paramtypes", [String, String]),
|
|
114
|
+
__metadata("design:paramtypes", [String, String, Object]),
|
|
115
115
|
__metadata("design:returntype", Promise)
|
|
116
116
|
], PremiseAnalyzer.prototype, "invoke", null);
|
|
117
117
|
//# sourceMappingURL=premise.analyzer.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"premise.analyzer.js","sourceRoot":"/","sources":["premise/premise.analyzer.ts"],"names":[],"mappings":";;;;;;;;;AAAA,OAAO,EAAE,YAAY,EAAE,aAAa,EAAE,MAAM,0BAA0B,CAAC;AACvE,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AACxB,OAAO,EAAE,cAAc,EAAE,MAAM,4CAA4C,CAAC;AAC5E,OAAO,EAAE,KAAK,EAAE,MAAM,wCAAwC,CAAC;AAC/D,OAAO,EAAE,qBAAqB,EAAE,MAAM,iCAAiC,CAAC;AACxE,OAAO,EAAE,qBAAqB,EAAE,MAAM,iCAAiC,CAAC;AAExE,MAAM,MAAM,GAAG,cAAc,CAAC,iBAAiB,CAAC,CAAC;AACjD,MAAM,SAAS,GAAG,cAAc,CAAC,wBAAwB,CAAC,CAAC;AAE3D,MAAM,YAAY,GAAG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAsDpB,CAAC;AAEF,MAAM,cAAc,GAAG,CAAC,CAAC,MAAM,CAAC;IAC9B,SAAS,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,CAC5B,+EAA+E;QAC/E,oCAAoC,CACrC;IACD,aAAa,EAAE,CAAC,CAAC,IAAI,CAAC,CAAC,aAAa,EAAE,WAAW,CAAC,CAAC,CAAC,QAAQ,CAC1D,6FAA6F,CAC9F;IACD,iBAAiB,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC,QAAQ,CACpD,8EAA8E,CAC/E;IACD,iBAAiB,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC,QAAQ,CACpD,8DAA8D,CAC/D;IACD,eAAe,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC,QAAQ,CAClD,+DAA+D,CAChE;IACD,eAAe,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,QAAQ,CAChD,8EAA8E,CAC/E;CACF,CAAC,CAAC;AAIH;;GAEG;AACH,MAAM,OAAO,eAAe;IAG1B;QACE,IAAI,CAAC,KAAK,GAAG,qBAAqB,CAAC,iBAAiB,EAAE,cAAc,EAAE;YACpE,IAAI,EAAE,kBAAkB;SACzB,CAAC,CAAC;IACL,CAAC;IAGY,AAAN,KAAK,CAAC,MAAM,
|
|
1
|
+
{"version":3,"file":"premise.analyzer.js","sourceRoot":"/","sources":["premise/premise.analyzer.ts"],"names":[],"mappings":"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","sourcesContent":["import { HumanMessage, SystemMessage } from \"@langchain/core/messages\";\nimport { z } from \"zod\";\nimport { protocolLogger } from \"../shared/observability/protocol.logger.js\";\nimport { Timed } from \"../shared/observability/performance.js\";\nimport { createStructuredModel } from \"../shared/agent/model.config.js\";\nimport { invokeWithAbortSignal } from \"../shared/agent/model-signal.js\";\n\nconst logger = protocolLogger(\"PremiseAnalyzer\");\nconst invokeLog = protocolLogger(\"PremiseAnalyzer:invoke\");\n\nconst systemPrompt = `\nYou are the Premise Analyzer for the Index Network — an intent-driven discovery protocol.\n\nYour job: classify a self-descriptive premise using adapted Speech Act Theory, then score its felicity conditions.\n\nA premise is a proposition a person asserts about themselves. It is NOT a desire or request (those are intents). Premises are conditions of possibility — facts about who someone is that ground opportunity discovery.\n\nAlways reason before classifying. Output reasoning first.\n\n═══════════════════════════════════════════════════\nSTEP 1 — CLASSIFY THE SPEECH ACT\n═══════════════════════════════════════════════════\n\nDECLARATIVE: The premise constitutes a fact about the speaker's identity, role, or status.\n Examples:\n · \"I am a climate-tech founder\" → DECLARATIVE\n · \"I hold a PhD in computational biology\" → DECLARATIVE\n · \"I am based in Berlin\" → DECLARATIVE\n · \"I am raising Series A\" → DECLARATIVE (constitutes current status)\n\nASSERTIVE: The premise describes a capability, experience, or characteristic.\n Examples:\n · \"I have 10 years of experience in distributed systems\" → ASSERTIVE\n · \"I built a collaboration platform used by 50k users\" → ASSERTIVE\n · \"I speak fluent Mandarin and German\" → ASSERTIVE\n · \"I specialize in zero-knowledge proofs\" → ASSERTIVE\n\n═══════════════════════════════════════════════════\nSTEP 2 — SCORE THE FELICITY CONDITIONS (0–100)\n═══════════════════════════════════════════════════\n\nAUTHORITY (Preparatory Condition)\n Does the speaker plausibly have standing to assert this?\n 100 → Highly specific, verifiable claim (\"I founded X in 2019\")\n 60 → Plausible but unverifiable (\"I have deep expertise in AI\")\n 20 → Implausible or grandiose (\"I am the world's leading expert\")\n\nSINCERITY (Sincerity Condition)\n Does the linguistic form suggest genuine self-description vs. aspiration?\n 100 → Present tense, first person, specific (\"I am a YC-backed founder\")\n 60 → Hedged or aspirational (\"I'm sort of getting into crypto\")\n 20 → Clearly aspirational masquerading as fact (\"I'm basically a VC\")\n\nCLARITY (Essential Condition)\n How specific and matchable is this premise?\n 100 → \"I build distributed database systems in Rust at a Series B startup\"\n 60 → \"I work in tech\" (clear direction, vague spec)\n 20 → \"I do things\" (barely informative)\n\nSEMANTIC ENTROPY → 0.0 to 1.0\n 0.0 = maximally constrained (role + domain + location + stage all specified)\n 1.0 = no constraints at all\n 0.0 example: \"I am a senior ML engineer at Google Brain in Mountain View\"\n 1.0 example: \"I'm a person\"\n`;\n\nconst responseFormat = z.object({\n reasoning: z.string().describe(\n \"Step-by-step analysis: (1) whether this is DECLARATIVE or ASSERTIVE and why, \" +\n \"(2) felicity condition assessment.\"\n ),\n speechActType: z.enum([\"DECLARATIVE\", \"ASSERTIVE\"]).describe(\n \"DECLARATIVE = constitutes identity/role/status; ASSERTIVE = describes capability/experience\"\n ),\n felicityAuthority: z.number().min(0).max(100).describe(\n \"Preparatory: does the speaker plausibly have standing to assert this (0-100)\"\n ),\n felicitySincerity: z.number().min(0).max(100).describe(\n \"Sincerity: genuine self-description vs. aspirational (0-100)\"\n ),\n felicityClarity: z.number().min(0).max(100).describe(\n \"Essential: how specific and matchable is this premise (0-100)\"\n ),\n semanticEntropy: z.number().min(0).max(1).describe(\n \"Constraint density: 0.0 = maximally specific, 1.0 = completely unconstrained\"\n ),\n});\n\nexport type PremiseAnalyzerOutput = z.infer<typeof responseFormat>;\n\n/**\n * Classifies a premise using adapted Speech Act Theory and scores felicity conditions.\n */\nexport class PremiseAnalyzer {\n private model: ReturnType<typeof createStructuredModel>;\n\n constructor() {\n this.model = createStructuredModel(\"premiseAnalyzer\", responseFormat, {\n name: \"premise_analyzer\"\n });\n }\n\n @Timed()\n public async invoke(\n premiseText: string,\n profileContext?: string,\n options: { signal?: AbortSignal } = {},\n ): Promise<PremiseAnalyzerOutput> {\n invokeLog.verbose('Analyzing premise text', { preview: premiseText.substring(0, 50) });\n\n const contextBlock = profileContext\n ? `\\n# Speaker Profile (Context)\\n${profileContext}\\n`\n : \"\";\n\n const prompt = `${contextBlock}\n# Premise to Analyze\n\"${premiseText}\"\n\nClassify this premise and score its felicity conditions.`;\n\n const messages = [\n new SystemMessage(systemPrompt),\n new HumanMessage(prompt),\n ];\n\n const result = await invokeWithAbortSignal(this.model, messages, options.signal);\n const output = responseFormat.parse(result);\n\n invokeLog.verbose('Analysis result', { speechActType: output.speechActType, semanticEntropy: output.semanticEntropy });\n return output;\n }\n}\n"]}
|
|
@@ -62,7 +62,9 @@ export type DecomposedPremise = z.infer<typeof premiseItemSchema>;
|
|
|
62
62
|
export declare class PremiseDecomposer {
|
|
63
63
|
private model;
|
|
64
64
|
constructor();
|
|
65
|
-
invoke(input: string, existingPremises?: ExistingPremiseRef[], currentBio?: string
|
|
65
|
+
invoke(input: string, existingPremises?: ExistingPremiseRef[], currentBio?: string, options?: {
|
|
66
|
+
signal?: AbortSignal;
|
|
67
|
+
}): Promise<PremiseDecomposerOutput>;
|
|
66
68
|
}
|
|
67
69
|
export {};
|
|
68
70
|
//# sourceMappingURL=premise.decomposer.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"premise.decomposer.d.ts","sourceRoot":"/","sources":["premise/premise.decomposer.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAmIxB,QAAA,MAAM,iBAAiB;;;;;;;;;;;;EAQrB,CAAC;AAEH,QAAA,MAAM,cAAc;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EAalB,CAAC;AAEH,oFAAoF;AACpF,MAAM,WAAW,kBAAkB;IACjC,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,MAAM,CAAC;CACd;AAED,MAAM,MAAM,uBAAuB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,cAAc,CAAC,CAAC;AACrE,MAAM,MAAM,iBAAiB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,iBAAiB,CAAC,CAAC;AAElE;;;GAGG;AACH,qBAAa,iBAAiB;IAC5B,OAAO,CAAC,KAAK,CAA2C;;IAS3C,MAAM,
|
|
1
|
+
{"version":3,"file":"premise.decomposer.d.ts","sourceRoot":"/","sources":["premise/premise.decomposer.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAmIxB,QAAA,MAAM,iBAAiB;;;;;;;;;;;;EAQrB,CAAC;AAEH,QAAA,MAAM,cAAc;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EAalB,CAAC;AAEH,oFAAoF;AACpF,MAAM,WAAW,kBAAkB;IACjC,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,MAAM,CAAC;CACd;AAED,MAAM,MAAM,uBAAuB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,cAAc,CAAC,CAAC;AACrE,MAAM,MAAM,iBAAiB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,iBAAiB,CAAC,CAAC;AAElE;;;GAGG;AACH,qBAAa,iBAAiB;IAC5B,OAAO,CAAC,KAAK,CAA2C;;IAS3C,MAAM,CACjB,KAAK,EAAE,MAAM,EACb,gBAAgB,CAAC,EAAE,kBAAkB,EAAE,EACvC,UAAU,CAAC,EAAE,MAAM,EACnB,OAAO,GAAE;QAAE,MAAM,CAAC,EAAE,WAAW,CAAA;KAAO,GACrC,OAAO,CAAC,uBAAuB,CAAC;CAyDpC"}
|
|
@@ -157,7 +157,7 @@ export class PremiseDecomposer {
|
|
|
157
157
|
name: "premise_decomposer",
|
|
158
158
|
});
|
|
159
159
|
}
|
|
160
|
-
async invoke(input, existingPremises, currentBio) {
|
|
160
|
+
async invoke(input, existingPremises, currentBio, options = {}) {
|
|
161
161
|
invokeLog.verbose('Decomposing input', {
|
|
162
162
|
inputChars: input.length,
|
|
163
163
|
existingPremiseCount: existingPremises?.length ?? 0,
|
|
@@ -176,7 +176,7 @@ export class PremiseDecomposer {
|
|
|
176
176
|
new SystemMessage(systemPrompt),
|
|
177
177
|
new HumanMessage(prompt),
|
|
178
178
|
];
|
|
179
|
-
const result = await invokeWithAbortSignal(this.model, messages);
|
|
179
|
+
const result = await invokeWithAbortSignal(this.model, messages, options.signal);
|
|
180
180
|
const output = responseFormat.parse(result);
|
|
181
181
|
// Guard against hallucinated ids: only keep retractions that reference premises we offered.
|
|
182
182
|
const knownIds = new Set((existingPremises ?? []).map((p) => p.id));
|
|
@@ -209,7 +209,7 @@ export class PremiseDecomposer {
|
|
|
209
209
|
__decorate([
|
|
210
210
|
Timed(),
|
|
211
211
|
__metadata("design:type", Function),
|
|
212
|
-
__metadata("design:paramtypes", [String, Array, String]),
|
|
212
|
+
__metadata("design:paramtypes", [String, Array, String, Object]),
|
|
213
213
|
__metadata("design:returntype", Promise)
|
|
214
214
|
], PremiseDecomposer.prototype, "invoke", null);
|
|
215
215
|
//# sourceMappingURL=premise.decomposer.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"premise.decomposer.js","sourceRoot":"/","sources":["premise/premise.decomposer.ts"],"names":[],"mappings":"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","sourcesContent":["import { HumanMessage, SystemMessage } from \"@langchain/core/messages\";\nimport { z } from \"zod\";\nimport { protocolLogger } from \"../shared/observability/protocol.logger.js\";\nimport { Timed } from \"../shared/observability/performance.js\";\nimport { createStructuredModel } from \"../shared/agent/model.config.js\";\nimport { invokeWithAbortSignal } from \"../shared/agent/model-signal.js\";\n\nconst logger = protocolLogger(\"PremiseDecomposer\");\nconst invokeLog = protocolLogger(\"PremiseDecomposer:invoke\");\n\nconst systemPrompt = `\nYou are the Premise Decomposer for the Index Network — an intent-driven discovery protocol.\n\nYour job: decompose free-text input about a person into individual, atomic premises.\n\nA premise is a single self-descriptive proposition — a fact someone asserts about themselves.\nEach premise should be:\n1. **Atomic** — one fact per premise, not compound statements\n2. **First-person** — phrased as \"I am...\", \"I work at...\", \"I specialize in...\"\n3. **Self-descriptive** — about the person's identity, role, skills, interests, location, or context\n4. **Non-redundant** — do not repeat the same information in multiple premises\n\n═══════════════════════════════════════════════════\nINPUT TYPES\n═══════════════════════════════════════════════════\n\nYou may receive:\n- **Chat messages**: \"I'm a software engineer at Google, based in SF. I'm interested in AI safety.\"\n- **Bio text**: A paragraph describing someone's background\n- **Scraped content**: LinkedIn/GitHub/Twitter data about someone\n- **Mixed input**: Name, location, and free-text combined\n\n═══════════════════════════════════════════════════\nDECOMPOSITION RULES\n═══════════════════════════════════════════════════\n\n1. Extract every distinct factual claim about the person\n2. Convert third-person (\"She works at...\") to first-person (\"I work at...\")\n3. Separate compound statements: \"I know Python and Rust\" → two premises\n4. Capture the FULL breadth of self-description, not just job/title. In particular,\n do not drop:\n - **Skills & expertise** (\"I specialize in distributed systems\", \"I know Rust\")\n - **Interests & focus areas** (\"I am interested in AI safety\", \"I care about climate\")\n - **Background/narrative context** (education, prior roles, domains, affiliations)\n These map to what a profile would store as interests/skills/context — every such\n fact must surface as its own premise so nothing is lost.\n5. Classify each premise:\n - \\`assertive\\` — stable identity facts (role, skills, interests, education, location)\n - \\`contextual\\` — temporal/situational (current projects, events, fundraising status)\n6. For \\`contextual\\` premises, estimate \\`validityDays\\` — how many days the fact is\n likely to remain true from now (e.g. \"speaking next week\" → ~7, \"raising a Series A\"\n → ~120, \"currently building X\" → ~180). Use null for \\`assertive\\` premises, which\n have no natural expiry.\n7. Skip vague or uninformative statements (\"I like stuff\", \"I'm a person\")\n8. Skip desires, requests, or intents (\"I'm looking for...\", \"I want to...\") — those are intents, not premises\n9. If the input contains NO extractable premises (e.g. just \"Yes\" or \"Hello\"), return an empty array\n10. NEVER extract denials or removal instructions as premises. \"I have nothing to do with X\",\n \"I am not a Y\", or \"remove X from my profile\" are retractions, not facts — handle them\n via the retraction rules below and do NOT emit a premise for them.\n\n═════════════════════════════════════════════════\nRETRACTION RULES\n═════════════════════════════════════════════════\n\nWhen the message includes the user's EXISTING PREMISES (listed with ids), you must also\ndetect which of them the input disavows and return their ids in \\`retractedPremiseIds\\`.\n\nA premise must be retracted when the input:\n- Explicitly asks to remove it (\"remove all mentions of X\", \"delete the part about Y\")\n- Denies it (\"I have nothing to do with X\", \"I never worked at Y\", \"that's not true\")\n- States it no longer holds (\"I no longer live in Berlin\", \"I left Google\")\n- Directly contradicts it (\"I'm based in Istanbul\" contradicts \"I am based in Ankara\")\n\nRules:\n- Only return ids that appear in the provided EXISTING PREMISES list — never invent ids.\n- Retract EVERY existing premise that matches the disavowed topic, not just the first one.\n- Do not retract premises the input merely omits — silence is not disavowal.\n- If no existing premises are provided, or nothing is disavowed, return an empty array.\n- Retraction and extraction are independent: an input can retract old premises AND\n contribute new ones in the same pass.\n\n═══════════════════════════════════════════════════\nBIO REVISION RULES\n═══════════════════════════════════════════════════\n\nWhen the message includes the user's CURRENT BIO, check whether the input disavows,\nremoves, or corrects anything that appears in it.\n\n- If it does, return \\`revisedBio\\`: the bio rewritten with those facts removed or\n corrected. Preserve everything the input does not dispute — same tone, similar\n length, no inventions.\n- If the input explicitly rewrites the bio (\"update my bio to X\"), return that\n rewritten bio.\n- NEVER add new facts to the bio — new information becomes premises, not bio edits.\n Purely additive input (\"I also enjoy X\") must return revisedBio: null.\n- If the bio is unaffected, or no CURRENT BIO was provided, return null.\n- Never return a revised bio that still mentions a disavowed fact.\n\n═══════════════════════════════════════════════════\nEXAMPLES\n═══════════════════════════════════════════════════\n\nInput: \"I'm a climate tech founder based in Berlin. I have a PhD in renewable energy systems and I'm currently raising Series A.\"\nOutput:\n 1. \"I am a climate tech founder\" (assertive, validityDays: null)\n 2. \"I am based in Berlin\" (assertive, validityDays: null)\n 3. \"I hold a PhD in renewable energy systems\" (assertive, validityDays: null)\n 4. \"I am currently raising Series A\" (contextual, validityDays: 120)\n\nInput: \"John Doe is a senior ML engineer at Meta in Menlo Park. He has 8 years of experience in NLP and computer vision.\"\nOutput:\n 1. \"I am a senior ML engineer\" (assertive, validityDays: null)\n 2. \"I work at Meta\" (assertive, validityDays: null)\n 3. \"I am based in Menlo Park\" (assertive, validityDays: null)\n 4. \"I have 8 years of experience in NLP\" (assertive, validityDays: null)\n 5. \"I have experience in computer vision\" (assertive, validityDays: null)\n\nInput: \"Yes, create my profile\"\nOutput: [] (empty — no premises)\n\nInput: \"Remove all mentions of the HOPE programming language. I have nothing to do with it. I specialize in compiler design.\"\nExisting premises:\n - id: aaaa-1 · \"I am the creator of the HOPE programming language\"\n - id: aaaa-2 · \"I specialize in compiler design\"\n - id: aaaa-3 · \"I am based in Istanbul\"\nCurrent bio: \"Software engineer in Istanbul. Creator of the HOPE programming language. Specializes in compiler design.\"\nOutput:\n premises: [] (compiler design already exists as aaaa-2 — nothing new to add)\n retractedPremiseIds: [\"aaaa-1\"]\n revisedBio: \"Software engineer in Istanbul. Specializes in compiler design.\"\n`;\n\nconst premiseItemSchema = z.object({\n text: z.string().describe(\"The premise text in first person (e.g. 'I am a software engineer at Google')\"),\n tier: z.enum([\"assertive\", \"contextual\"]).describe(\n \"assertive = stable identity fact; contextual = temporal/situational\"\n ),\n validityDays: z.number().int().positive().nullable().describe(\n \"For contextual premises: estimated days the fact remains true from now (its validity window). null for assertive premises, which do not expire.\"\n ),\n});\n\nconst responseFormat = z.object({\n reasoning: z.string().describe(\n \"Brief analysis of what factual claims can be extracted from the input\"\n ),\n premises: z.array(premiseItemSchema).describe(\n \"Array of extracted premises. Empty if input contains no self-descriptive facts.\"\n ),\n retractedPremiseIds: z.array(z.string()).default([]).describe(\n \"Ids of EXISTING premises (from the provided list) that the input disavows, denies, or asks to remove. Empty when nothing is disavowed or no existing premises were provided.\"\n ),\n revisedBio: z.string().nullable().default(null).describe(\n \"When the input disavows or corrects facts that appear in the provided CURRENT BIO, the bio rewritten without those facts (preserving everything else). null when the bio is unaffected or no bio was provided.\"\n ),\n});\n\n/** An existing ACTIVE premise offered to the decomposer for retraction matching. */\nexport interface ExistingPremiseRef {\n id: string;\n text: string;\n}\n\nexport type PremiseDecomposerOutput = z.infer<typeof responseFormat>;\nexport type DecomposedPremise = z.infer<typeof premiseItemSchema>;\n\n/**\n * Decomposes free-text input (chat messages, bios, scraped content) into\n * individual atomic premises suitable for the premise graph.\n */\nexport class PremiseDecomposer {\n private model: ReturnType<typeof createStructuredModel>;\n\n constructor() {\n this.model = createStructuredModel(\"premiseDecomposer\", responseFormat, {\n name: \"premise_decomposer\",\n });\n }\n\n @Timed()\n public async invoke(input: string, existingPremises?: ExistingPremiseRef[], currentBio?: string): Promise<PremiseDecomposerOutput> {\n invokeLog.verbose('Decomposing input', {\n inputChars: input.length,\n existingPremiseCount: existingPremises?.length ?? 0,\n hasBio: Boolean(currentBio),\n });\n\n const existingBlock = existingPremises?.length\n ? `\\n\\nEXISTING PREMISES (retract by id when the input disavows them):\\n${existingPremises\n .map((p) => `- id: ${p.id} · \"${p.text}\"`)\n .join(\"\\n\")}`\n : \"\";\n\n const bioBlock = currentBio?.trim()\n ? `\\n\\nCURRENT BIO (return revisedBio when the input disavows or corrects anything in it):\\n${currentBio.trim()}`\n : \"\";\n\n const prompt = `Decompose the following text into individual premises:\\n\\n${input}${existingBlock}${bioBlock}`;\n\n const messages = [\n new SystemMessage(systemPrompt),\n new HumanMessage(prompt),\n ];\n\n const result = await invokeWithAbortSignal(this.model, messages);\n const output = responseFormat.parse(result);\n\n // Guard against hallucinated ids: only keep retractions that reference premises we offered.\n const knownIds = new Set((existingPremises ?? []).map((p) => p.id));\n const droppedIds = output.retractedPremiseIds.filter((id) => !knownIds.has(id));\n if (droppedIds.length > 0) {\n invokeLog.warn('Dropped unknown retraction ids', { count: droppedIds.length, droppedIds });\n }\n output.retractedPremiseIds = output.retractedPremiseIds.filter((id) => knownIds.has(id));\n\n // A revised bio is only meaningful when we offered one to revise. Also\n // normalize structured-output quirks: literal \"null\", empty strings, and\n // no-op rewrites all mean \"bio unaffected\".\n const normalizedBio = output.revisedBio?.trim();\n if (\n !currentBio?.trim() ||\n !normalizedBio ||\n normalizedBio.toLowerCase() === 'null' ||\n normalizedBio === currentBio.trim()\n ) {\n output.revisedBio = null;\n } else {\n output.revisedBio = normalizedBio;\n }\n\n invokeLog.verbose('Extracted premises and retractions', {\n premiseCount: output.premises.length,\n retractionCount: output.retractedPremiseIds.length,\n hasRevisedBio: Boolean(output.revisedBio),\n });\n return output;\n }\n}\n"]}
|
|
1
|
+
{"version":3,"file":"premise.decomposer.js","sourceRoot":"/","sources":["premise/premise.decomposer.ts"],"names":[],"mappings":"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","sourcesContent":["import { HumanMessage, SystemMessage } from \"@langchain/core/messages\";\nimport { z } from \"zod\";\nimport { protocolLogger } from \"../shared/observability/protocol.logger.js\";\nimport { Timed } from \"../shared/observability/performance.js\";\nimport { createStructuredModel } from \"../shared/agent/model.config.js\";\nimport { invokeWithAbortSignal } from \"../shared/agent/model-signal.js\";\n\nconst logger = protocolLogger(\"PremiseDecomposer\");\nconst invokeLog = protocolLogger(\"PremiseDecomposer:invoke\");\n\nconst systemPrompt = `\nYou are the Premise Decomposer for the Index Network — an intent-driven discovery protocol.\n\nYour job: decompose free-text input about a person into individual, atomic premises.\n\nA premise is a single self-descriptive proposition — a fact someone asserts about themselves.\nEach premise should be:\n1. **Atomic** — one fact per premise, not compound statements\n2. **First-person** — phrased as \"I am...\", \"I work at...\", \"I specialize in...\"\n3. **Self-descriptive** — about the person's identity, role, skills, interests, location, or context\n4. **Non-redundant** — do not repeat the same information in multiple premises\n\n═══════════════════════════════════════════════════\nINPUT TYPES\n═══════════════════════════════════════════════════\n\nYou may receive:\n- **Chat messages**: \"I'm a software engineer at Google, based in SF. I'm interested in AI safety.\"\n- **Bio text**: A paragraph describing someone's background\n- **Scraped content**: LinkedIn/GitHub/Twitter data about someone\n- **Mixed input**: Name, location, and free-text combined\n\n═══════════════════════════════════════════════════\nDECOMPOSITION RULES\n═══════════════════════════════════════════════════\n\n1. Extract every distinct factual claim about the person\n2. Convert third-person (\"She works at...\") to first-person (\"I work at...\")\n3. Separate compound statements: \"I know Python and Rust\" → two premises\n4. Capture the FULL breadth of self-description, not just job/title. In particular,\n do not drop:\n - **Skills & expertise** (\"I specialize in distributed systems\", \"I know Rust\")\n - **Interests & focus areas** (\"I am interested in AI safety\", \"I care about climate\")\n - **Background/narrative context** (education, prior roles, domains, affiliations)\n These map to what a profile would store as interests/skills/context — every such\n fact must surface as its own premise so nothing is lost.\n5. Classify each premise:\n - \\`assertive\\` — stable identity facts (role, skills, interests, education, location)\n - \\`contextual\\` — temporal/situational (current projects, events, fundraising status)\n6. For \\`contextual\\` premises, estimate \\`validityDays\\` — how many days the fact is\n likely to remain true from now (e.g. \"speaking next week\" → ~7, \"raising a Series A\"\n → ~120, \"currently building X\" → ~180). Use null for \\`assertive\\` premises, which\n have no natural expiry.\n7. Skip vague or uninformative statements (\"I like stuff\", \"I'm a person\")\n8. Skip desires, requests, or intents (\"I'm looking for...\", \"I want to...\") — those are intents, not premises\n9. If the input contains NO extractable premises (e.g. just \"Yes\" or \"Hello\"), return an empty array\n10. NEVER extract denials or removal instructions as premises. \"I have nothing to do with X\",\n \"I am not a Y\", or \"remove X from my profile\" are retractions, not facts — handle them\n via the retraction rules below and do NOT emit a premise for them.\n\n═════════════════════════════════════════════════\nRETRACTION RULES\n═════════════════════════════════════════════════\n\nWhen the message includes the user's EXISTING PREMISES (listed with ids), you must also\ndetect which of them the input disavows and return their ids in \\`retractedPremiseIds\\`.\n\nA premise must be retracted when the input:\n- Explicitly asks to remove it (\"remove all mentions of X\", \"delete the part about Y\")\n- Denies it (\"I have nothing to do with X\", \"I never worked at Y\", \"that's not true\")\n- States it no longer holds (\"I no longer live in Berlin\", \"I left Google\")\n- Directly contradicts it (\"I'm based in Istanbul\" contradicts \"I am based in Ankara\")\n\nRules:\n- Only return ids that appear in the provided EXISTING PREMISES list — never invent ids.\n- Retract EVERY existing premise that matches the disavowed topic, not just the first one.\n- Do not retract premises the input merely omits — silence is not disavowal.\n- If no existing premises are provided, or nothing is disavowed, return an empty array.\n- Retraction and extraction are independent: an input can retract old premises AND\n contribute new ones in the same pass.\n\n═══════════════════════════════════════════════════\nBIO REVISION RULES\n═══════════════════════════════════════════════════\n\nWhen the message includes the user's CURRENT BIO, check whether the input disavows,\nremoves, or corrects anything that appears in it.\n\n- If it does, return \\`revisedBio\\`: the bio rewritten with those facts removed or\n corrected. Preserve everything the input does not dispute — same tone, similar\n length, no inventions.\n- If the input explicitly rewrites the bio (\"update my bio to X\"), return that\n rewritten bio.\n- NEVER add new facts to the bio — new information becomes premises, not bio edits.\n Purely additive input (\"I also enjoy X\") must return revisedBio: null.\n- If the bio is unaffected, or no CURRENT BIO was provided, return null.\n- Never return a revised bio that still mentions a disavowed fact.\n\n═══════════════════════════════════════════════════\nEXAMPLES\n═══════════════════════════════════════════════════\n\nInput: \"I'm a climate tech founder based in Berlin. I have a PhD in renewable energy systems and I'm currently raising Series A.\"\nOutput:\n 1. \"I am a climate tech founder\" (assertive, validityDays: null)\n 2. \"I am based in Berlin\" (assertive, validityDays: null)\n 3. \"I hold a PhD in renewable energy systems\" (assertive, validityDays: null)\n 4. \"I am currently raising Series A\" (contextual, validityDays: 120)\n\nInput: \"John Doe is a senior ML engineer at Meta in Menlo Park. He has 8 years of experience in NLP and computer vision.\"\nOutput:\n 1. \"I am a senior ML engineer\" (assertive, validityDays: null)\n 2. \"I work at Meta\" (assertive, validityDays: null)\n 3. \"I am based in Menlo Park\" (assertive, validityDays: null)\n 4. \"I have 8 years of experience in NLP\" (assertive, validityDays: null)\n 5. \"I have experience in computer vision\" (assertive, validityDays: null)\n\nInput: \"Yes, create my profile\"\nOutput: [] (empty — no premises)\n\nInput: \"Remove all mentions of the HOPE programming language. I have nothing to do with it. I specialize in compiler design.\"\nExisting premises:\n - id: aaaa-1 · \"I am the creator of the HOPE programming language\"\n - id: aaaa-2 · \"I specialize in compiler design\"\n - id: aaaa-3 · \"I am based in Istanbul\"\nCurrent bio: \"Software engineer in Istanbul. Creator of the HOPE programming language. Specializes in compiler design.\"\nOutput:\n premises: [] (compiler design already exists as aaaa-2 — nothing new to add)\n retractedPremiseIds: [\"aaaa-1\"]\n revisedBio: \"Software engineer in Istanbul. Specializes in compiler design.\"\n`;\n\nconst premiseItemSchema = z.object({\n text: z.string().describe(\"The premise text in first person (e.g. 'I am a software engineer at Google')\"),\n tier: z.enum([\"assertive\", \"contextual\"]).describe(\n \"assertive = stable identity fact; contextual = temporal/situational\"\n ),\n validityDays: z.number().int().positive().nullable().describe(\n \"For contextual premises: estimated days the fact remains true from now (its validity window). null for assertive premises, which do not expire.\"\n ),\n});\n\nconst responseFormat = z.object({\n reasoning: z.string().describe(\n \"Brief analysis of what factual claims can be extracted from the input\"\n ),\n premises: z.array(premiseItemSchema).describe(\n \"Array of extracted premises. Empty if input contains no self-descriptive facts.\"\n ),\n retractedPremiseIds: z.array(z.string()).default([]).describe(\n \"Ids of EXISTING premises (from the provided list) that the input disavows, denies, or asks to remove. Empty when nothing is disavowed or no existing premises were provided.\"\n ),\n revisedBio: z.string().nullable().default(null).describe(\n \"When the input disavows or corrects facts that appear in the provided CURRENT BIO, the bio rewritten without those facts (preserving everything else). null when the bio is unaffected or no bio was provided.\"\n ),\n});\n\n/** An existing ACTIVE premise offered to the decomposer for retraction matching. */\nexport interface ExistingPremiseRef {\n id: string;\n text: string;\n}\n\nexport type PremiseDecomposerOutput = z.infer<typeof responseFormat>;\nexport type DecomposedPremise = z.infer<typeof premiseItemSchema>;\n\n/**\n * Decomposes free-text input (chat messages, bios, scraped content) into\n * individual atomic premises suitable for the premise graph.\n */\nexport class PremiseDecomposer {\n private model: ReturnType<typeof createStructuredModel>;\n\n constructor() {\n this.model = createStructuredModel(\"premiseDecomposer\", responseFormat, {\n name: \"premise_decomposer\",\n });\n }\n\n @Timed()\n public async invoke(\n input: string,\n existingPremises?: ExistingPremiseRef[],\n currentBio?: string,\n options: { signal?: AbortSignal } = {},\n ): Promise<PremiseDecomposerOutput> {\n invokeLog.verbose('Decomposing input', {\n inputChars: input.length,\n existingPremiseCount: existingPremises?.length ?? 0,\n hasBio: Boolean(currentBio),\n });\n\n const existingBlock = existingPremises?.length\n ? `\\n\\nEXISTING PREMISES (retract by id when the input disavows them):\\n${existingPremises\n .map((p) => `- id: ${p.id} · \"${p.text}\"`)\n .join(\"\\n\")}`\n : \"\";\n\n const bioBlock = currentBio?.trim()\n ? `\\n\\nCURRENT BIO (return revisedBio when the input disavows or corrects anything in it):\\n${currentBio.trim()}`\n : \"\";\n\n const prompt = `Decompose the following text into individual premises:\\n\\n${input}${existingBlock}${bioBlock}`;\n\n const messages = [\n new SystemMessage(systemPrompt),\n new HumanMessage(prompt),\n ];\n\n const result = await invokeWithAbortSignal(this.model, messages, options.signal);\n const output = responseFormat.parse(result);\n\n // Guard against hallucinated ids: only keep retractions that reference premises we offered.\n const knownIds = new Set((existingPremises ?? []).map((p) => p.id));\n const droppedIds = output.retractedPremiseIds.filter((id) => !knownIds.has(id));\n if (droppedIds.length > 0) {\n invokeLog.warn('Dropped unknown retraction ids', { count: droppedIds.length, droppedIds });\n }\n output.retractedPremiseIds = output.retractedPremiseIds.filter((id) => knownIds.has(id));\n\n // A revised bio is only meaningful when we offered one to revise. Also\n // normalize structured-output quirks: literal \"null\", empty strings, and\n // no-op rewrites all mean \"bio unaffected\".\n const normalizedBio = output.revisedBio?.trim();\n if (\n !currentBio?.trim() ||\n !normalizedBio ||\n normalizedBio.toLowerCase() === 'null' ||\n normalizedBio === currentBio.trim()\n ) {\n output.revisedBio = null;\n } else {\n output.revisedBio = normalizedBio;\n }\n\n invokeLog.verbose('Extracted premises and retractions', {\n premiseCount: output.premises.length,\n retractionCount: output.retractedPremiseIds.length,\n hasRevisedBio: Boolean(output.revisedBio),\n });\n return output;\n }\n}\n"]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@indexnetwork/protocol",
|
|
3
|
-
"version": "5.2.
|
|
3
|
+
"version": "5.2.2-rc.370.1",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"main": "./dist/index.js",
|
|
6
6
|
"types": "./dist/index.d.ts",
|
|
@@ -28,6 +28,7 @@
|
|
|
28
28
|
"eval:profile": "bun --env-file=../../.env.test ./eval/profile/profile.eval.ts",
|
|
29
29
|
"eval:opportunity": "bun --env-file=../../.env.test ./eval/opportunity/opportunity.eval.ts",
|
|
30
30
|
"eval:clarification": "bun --env-file=../../.env.test ./eval/clarification/clarification.eval.ts",
|
|
31
|
+
"eval:view": "bun ./eval/viewer/viewer.eval.ts",
|
|
31
32
|
"eval:verify": "bun ./eval/verify.ts"
|
|
32
33
|
},
|
|
33
34
|
"dependencies": {
|