@indexnetwork/protocol 3.6.3 → 3.6.4
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/README.md +90 -26
- package/dist/agent/tests/fakes.d.ts.map +1 -1
- package/dist/agent/tests/fakes.js.map +1 -1
- package/dist/chat/chat.agent.d.ts.map +1 -1
- package/dist/chat/chat.agent.js +3 -3
- package/dist/chat/chat.agent.js.map +1 -1
- package/dist/chat/chat.streamer.d.ts.map +1 -1
- package/dist/chat/chat.streamer.js +1 -1
- package/dist/chat/chat.streamer.js.map +1 -1
- package/dist/chat/chat.summarizer.d.ts.map +1 -1
- package/dist/chat/chat.summarizer.js +1 -1
- package/dist/chat/chat.summarizer.js.map +1 -1
- package/dist/chat/tests/chat.graph.mocks.d.ts.map +1 -1
- package/dist/chat/tests/chat.graph.mocks.js +0 -1
- package/dist/chat/tests/chat.graph.mocks.js.map +1 -1
- package/dist/index.d.ts +21 -21
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +10 -10
- package/dist/index.js.map +1 -1
- package/dist/intent/intent.graph.js +1 -1
- package/dist/intent/intent.graph.js.map +1 -1
- package/dist/maintenance/maintenance.graph.d.ts.map +1 -1
- package/dist/maintenance/maintenance.graph.js +1 -1
- package/dist/maintenance/maintenance.graph.js.map +1 -1
- package/dist/negotiation/negotiation.agent.d.ts.map +1 -1
- package/dist/negotiation/negotiation.agent.js +1 -1
- package/dist/negotiation/negotiation.agent.js.map +1 -1
- package/dist/negotiation/negotiation.summarizer.d.ts.map +1 -1
- package/dist/negotiation/negotiation.summarizer.js +1 -1
- package/dist/negotiation/negotiation.summarizer.js.map +1 -1
- package/dist/network/indexer/indexer.graph.d.ts.map +1 -1
- package/dist/network/indexer/indexer.graph.js +2 -2
- package/dist/network/indexer/indexer.graph.js.map +1 -1
- package/dist/opportunity/discovery-question.helper.d.ts.map +1 -1
- package/dist/opportunity/discovery-question.helper.js.map +1 -1
- package/dist/opportunity/feed/feed.graph.d.ts.map +1 -1
- package/dist/opportunity/feed/feed.graph.js +1 -1
- package/dist/opportunity/feed/feed.graph.js.map +1 -1
- package/dist/opportunity/negotiation-summary.builder.d.ts.map +1 -1
- package/dist/opportunity/negotiation-summary.builder.js.map +1 -1
- package/dist/opportunity/opportunity.discover.d.ts.map +1 -1
- package/dist/opportunity/opportunity.discover.js +1 -1
- package/dist/opportunity/opportunity.discover.js.map +1 -1
- package/dist/opportunity/opportunity.enricher.d.ts.map +1 -1
- package/dist/opportunity/opportunity.enricher.js.map +1 -1
- package/dist/opportunity/opportunity.graph.d.ts.map +1 -1
- package/dist/opportunity/opportunity.graph.js +5 -5
- package/dist/opportunity/opportunity.graph.js.map +1 -1
- package/dist/opportunity/opportunity.persist.d.ts.map +1 -1
- package/dist/opportunity/opportunity.persist.js.map +1 -1
- package/dist/opportunity/opportunity.presentation.d.ts +9 -0
- package/dist/opportunity/opportunity.presentation.d.ts.map +1 -1
- package/dist/opportunity/opportunity.presentation.js +23 -0
- package/dist/opportunity/opportunity.presentation.js.map +1 -1
- package/dist/opportunity/opportunity.presenter.d.ts.map +1 -1
- package/dist/opportunity/opportunity.presenter.js +9 -3
- package/dist/opportunity/opportunity.presenter.js.map +1 -1
- package/dist/opportunity/question.generator.d.ts.map +1 -1
- package/dist/opportunity/question.generator.js +2 -2
- package/dist/opportunity/question.generator.js.map +1 -1
- package/dist/premise/premise.graph.d.ts.map +1 -1
- package/dist/premise/premise.graph.js +1 -1
- package/dist/premise/premise.graph.js.map +1 -1
- package/dist/profile/profile.graph.js +1 -1
- package/dist/profile/profile.graph.js.map +1 -1
- package/dist/questioner/questioner.agent.d.ts.map +1 -1
- package/dist/questioner/questioner.agent.js +1 -1
- package/dist/questioner/questioner.agent.js.map +1 -1
- package/dist/questioner/questioner.presets.d.ts.map +1 -1
- package/dist/questioner/questioner.presets.js +1 -1
- package/dist/questioner/questioner.presets.js.map +1 -1
- package/dist/shared/agent/response.streamer.d.ts.map +1 -1
- package/dist/shared/agent/response.streamer.js +1 -1
- package/dist/shared/agent/response.streamer.js.map +1 -1
- package/dist/shared/agent/tool.factory.d.ts.map +1 -1
- package/dist/shared/agent/tool.factory.js +1 -1
- package/dist/shared/agent/tool.factory.js.map +1 -1
- package/dist/shared/agent/tool.helpers.d.ts.map +1 -1
- package/dist/shared/agent/tool.helpers.js.map +1 -1
- package/dist/shared/assignment/network-assignment.policy.d.ts.map +1 -1
- package/dist/shared/assignment/network-assignment.policy.js.map +1 -1
- package/dist/shared/interfaces/questioner.interface.d.ts.map +1 -1
- package/dist/shared/interfaces/questioner.interface.js.map +1 -1
- package/package.json +1 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"opportunity.persist.d.ts","sourceRoot":"/","sources":["opportunity/opportunity.persist.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,KAAK,
|
|
1
|
+
{"version":3,"file":"opportunity.persist.d.ts","sourceRoot":"/","sources":["opportunity/opportunity.persist.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,KAAK,EAAE,qBAAqB,EAAE,WAAW,EAAE,iBAAiB,EAAE,MAAM,4CAA4C,CAAC;AACxH,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,4CAA4C,CAAC;AAC3E,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,2BAA2B,CAAC;AAMlE,MAAM,MAAM,0BAA0B,GAAG,gBAAgB,GAAG;IAC1D,iBAAiB,CAAC,IAAI,EAAE,qBAAqB,GAAG,OAAO,CAAC,WAAW,CAAC,CAAC;IACrE,uBAAuB,CAAC,EAAE,EAAE,MAAM,EAAE,MAAM,EAAE,iBAAiB,GAAG,OAAO,CAAC,IAAI,GAAG,WAAW,GAAG,IAAI,CAAC,CAAC;IACnG,6BAA6B,CAAC,CAC5B,IAAI,EAAE,qBAAqB,EAC3B,SAAS,EAAE,MAAM,EAAE,GAClB,OAAO,CAAC;QAAE,OAAO,EAAE,WAAW,CAAC;QAAC,OAAO,EAAE,WAAW,EAAE,CAAA;KAAE,CAAC,CAAC;IAC7D,kEAAkE;IAClE,cAAc,CAAC,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,WAAW,GAAG,IAAI,CAAC,CAAC;CAC1D,CAAC;AAEF,MAAM,WAAW,0BAA0B;IACzC,QAAQ,EAAE,0BAA0B,CAAC;IACrC,QAAQ,EAAE,QAAQ,CAAC;IACnB,KAAK,EAAE,qBAAqB,EAAE,CAAC;IAC/B,UAAU,CAAC,EAAE,CAAC,WAAW,EAAE,WAAW,KAAK,OAAO,CAAC,OAAO,CAAC,CAAC;CAC7D;AAED,MAAM,WAAW,yBAAyB;IACxC,SAAS,EAAE,MAAM,CAAC;IAClB,KAAK,EAAE,OAAO,CAAC;CAChB;AAED,MAAM,WAAW,0BAA0B;IACzC,OAAO,EAAE,WAAW,EAAE,CAAC;IACvB,OAAO,EAAE,WAAW,EAAE,CAAC;IACvB,MAAM,CAAC,EAAE,yBAAyB,EAAE,CAAC;CACtC;AAED;;;;GAIG;AACH,wBAAsB,oBAAoB,CAAC,MAAM,EAAE,0BAA0B,GAAG,OAAO,CAAC,0BAA0B,CAAC,CAoDlH"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"opportunity.persist.js","sourceRoot":"/","sources":["opportunity/opportunity.persist.ts"],"names":[],"mappings":"AAAA;;;GAGG;
|
|
1
|
+
{"version":3,"file":"opportunity.persist.js","sourceRoot":"/","sources":["opportunity/opportunity.persist.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Shared persist phase for opportunity creation: enrichOrCreate → create (or create+expire) → optional chat injection.\n * Used by the opportunity graph persist node and by the manual opportunity service for consistency.\n */\n\nimport type { CreateOpportunityData, Opportunity, OpportunityStatus } from '../shared/interfaces/database.interface.js';\nimport type { Embedder } from '../shared/interfaces/embedder.interface.js';\nimport type { EnricherDatabase } from './opportunity.enricher.js';\nimport { enrichOrCreate } from './opportunity.enricher.js';\nimport { protocolLogger } from '../shared/observability/protocol.logger.js';\n\nconst logger = protocolLogger('OpportunityPersist');\n\nexport type PersistOpportunityDatabase = EnricherDatabase & {\n createOpportunity(data: CreateOpportunityData): Promise<Opportunity>;\n updateOpportunityStatus(id: string, status: OpportunityStatus): Promise<void | Opportunity | null>;\n createOpportunityAndExpireIds?(\n data: CreateOpportunityData,\n expireIds: string[]\n ): Promise<{ created: Opportunity; expired: Opportunity[] }>;\n /** Optional: used to populate expired list in non-atomic path. */\n getOpportunity?(id: string): Promise<Opportunity | null>;\n};\n\nexport interface PersistOpportunitiesParams {\n database: PersistOpportunityDatabase;\n embedder: Embedder;\n items: CreateOpportunityData[];\n injectChat?: (opportunity: Opportunity) => Promise<unknown>;\n}\n\nexport interface PersistOpportunitiesError {\n itemIndex: number;\n error: unknown;\n}\n\nexport interface PersistOpportunitiesResult {\n created: Opportunity[];\n expired: Opportunity[];\n errors?: PersistOpportunitiesError[];\n}\n\n/**\n * Persist one or more opportunities: enrich (merge overlapping), create, expire replaced, optional chat injection.\n * When the database has createOpportunityAndExpireIds and enrichment produced expireIds, uses it for atomic create+expire.\n * Returns both created and expired so callers can emit events (e.g. manual service).\n */\nexport async function persistOpportunities(params: PersistOpportunitiesParams): Promise<PersistOpportunitiesResult> {\n const { database, embedder, items, injectChat } = params;\n const created: Opportunity[] = [];\n const expired: Opportunity[] = [];\n const errors: PersistOpportunitiesError[] = [];\n\n for (let itemIndex = 0; itemIndex < items.length; itemIndex++) {\n const data = items[itemIndex];\n try {\n const enrichment = await enrichOrCreate(database, embedder, data);\n const toCreate = enrichment.data;\n if (enrichment.enriched) {\n toCreate.status = enrichment.resolvedStatus;\n }\n\n if (\n database.createOpportunityAndExpireIds &&\n enrichment.enriched &&\n enrichment.expiredIds.length > 0\n ) {\n const result = await database.createOpportunityAndExpireIds(toCreate, enrichment.expiredIds);\n created.push(result.created);\n expired.push(...result.expired);\n } else {\n const c = await database.createOpportunity(toCreate);\n created.push(c);\n if (enrichment.enriched && enrichment.expiredIds.length > 0) {\n for (const id of enrichment.expiredIds) {\n await database.updateOpportunityStatus(id, 'expired');\n }\n if (database.getOpportunity) {\n for (const id of enrichment.expiredIds) {\n const opp = await database.getOpportunity(id);\n if (opp) expired.push(opp);\n }\n }\n }\n }\n\n const lastCreated = created[created.length - 1];\n if (lastCreated?.status === 'pending' && injectChat) {\n await injectChat(lastCreated).catch((err) => {\n logger.warn('[PersistOpportunities] Chat injection failed', { opportunityId: lastCreated.id, error: err });\n });\n }\n } catch (err) {\n errors.push({ itemIndex, error: err });\n logger.warn('[PersistOpportunities] Item failed', { itemIndex, error: err });\n }\n }\n\n return { created, expired, ...(errors.length > 0 ? { errors } : {}) };\n}\n"]}
|
|
@@ -19,6 +19,15 @@ export interface UserInfo {
|
|
|
19
19
|
*/
|
|
20
20
|
export declare function presentOpportunity(opp: Opportunity, viewerId: string, otherPartyInfo: UserInfo, introducerInfo: UserInfo | null, format: 'card' | 'email' | 'notification'): OpportunityPresentation;
|
|
21
21
|
export declare function stripUuids(text: string): string;
|
|
22
|
+
/**
|
|
23
|
+
* Truncate user-facing text to at most `maxChars` without cutting mid-word.
|
|
24
|
+
*
|
|
25
|
+
* Prefers a sentence boundary, then a word boundary, and only falls back to a
|
|
26
|
+
* hard slice if no boundary exists within the limit. An ellipsis is appended
|
|
27
|
+
* when the text is actually shortened. Used by presenter fallbacks so a degraded
|
|
28
|
+
* card never shows a sentence chopped mid-word (e.g. "His focus on 'indiv").
|
|
29
|
+
*/
|
|
30
|
+
export declare function truncateAtBoundary(text: string, maxChars: number): string;
|
|
22
31
|
/**
|
|
23
32
|
* Strips introducer mentions from opportunity summary text.
|
|
24
33
|
* Removes patterns like:
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"opportunity.presentation.d.ts","sourceRoot":"/","sources":["opportunity/opportunity.presentation.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,4CAA4C,CAAC;AAG9E,MAAM,WAAW,uBAAuB;IACtC,KAAK,EAAE,MAAM,CAAC;IACd,WAAW,EAAE,MAAM,CAAC;IACpB,YAAY,EAAE,MAAM,CAAC;CACtB;AAED,MAAM,WAAW,QAAQ;IACvB,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;CACvB;AAED;;;GAGG;AACH,wBAAgB,kBAAkB,CAChC,GAAG,EAAE,WAAW,EAChB,QAAQ,EAAE,MAAM,EAChB,cAAc,EAAE,QAAQ,EACxB,cAAc,EAAE,QAAQ,GAAG,IAAI,EAC/B,MAAM,EAAE,MAAM,GAAG,OAAO,GAAG,cAAc,GACxC,uBAAuB,CAsEzB;AAQD,wBAAgB,UAAU,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAkB/C;AAED;;;;;;;;;;;GAWG;AACH,wBAAgB,uBAAuB,CACrC,IAAI,EAAE,MAAM,EACZ,cAAc,EAAE,MAAM,GAAG,SAAS,GACjC,MAAM,CAqFR;AA0BD;;;;;;;;;;;;GAYG;AACH,wBAAgB,wBAAwB,CACtC,SAAS,EAAE,MAAM,EACjB,eAAe,EAAE,MAAM,EACvB,QAAQ,GAAE,MAAoC,EAC9C,UAAU,CAAC,EAAE,MAAM,EACnB,cAAc,CAAC,EAAE,MAAM,GACtB,MAAM,CAmGR;AAOD;;;;;;;;;;;;;;;;;GAiBG;AACH,wBAAgB,2BAA2B,CACzC,SAAS,EAAE,MAAM,EACjB,eAAe,EAAE,MAAM,EACvB,UAAU,CAAC,EAAE,MAAM,GAClB,MAAM,CAiDR"}
|
|
1
|
+
{"version":3,"file":"opportunity.presentation.d.ts","sourceRoot":"/","sources":["opportunity/opportunity.presentation.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,4CAA4C,CAAC;AAG9E,MAAM,WAAW,uBAAuB;IACtC,KAAK,EAAE,MAAM,CAAC;IACd,WAAW,EAAE,MAAM,CAAC;IACpB,YAAY,EAAE,MAAM,CAAC;CACtB;AAED,MAAM,WAAW,QAAQ;IACvB,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;CACvB;AAED;;;GAGG;AACH,wBAAgB,kBAAkB,CAChC,GAAG,EAAE,WAAW,EAChB,QAAQ,EAAE,MAAM,EAChB,cAAc,EAAE,QAAQ,EACxB,cAAc,EAAE,QAAQ,GAAG,IAAI,EAC/B,MAAM,EAAE,MAAM,GAAG,OAAO,GAAG,cAAc,GACxC,uBAAuB,CAsEzB;AAQD,wBAAgB,UAAU,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAkB/C;AAED;;;;;;;GAOG;AACH,wBAAgB,kBAAkB,CAAC,IAAI,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,MAAM,CAoBzE;AAED;;;;;;;;;;;GAWG;AACH,wBAAgB,uBAAuB,CACrC,IAAI,EAAE,MAAM,EACZ,cAAc,EAAE,MAAM,GAAG,SAAS,GACjC,MAAM,CAqFR;AA0BD;;;;;;;;;;;;GAYG;AACH,wBAAgB,wBAAwB,CACtC,SAAS,EAAE,MAAM,EACjB,eAAe,EAAE,MAAM,EACvB,QAAQ,GAAE,MAAoC,EAC9C,UAAU,CAAC,EAAE,MAAM,EACnB,cAAc,CAAC,EAAE,MAAM,GACtB,MAAM,CAmGR;AAOD;;;;;;;;;;;;;;;;;GAiBG;AACH,wBAAgB,2BAA2B,CACzC,SAAS,EAAE,MAAM,EACjB,eAAe,EAAE,MAAM,EACvB,UAAU,CAAC,EAAE,MAAM,GAClB,MAAM,CAiDR"}
|
|
@@ -96,6 +96,29 @@ export function stripUuids(text) {
|
|
|
96
96
|
.replace(/\s{2,}/g, ' ')
|
|
97
97
|
.trim();
|
|
98
98
|
}
|
|
99
|
+
/**
|
|
100
|
+
* Truncate user-facing text to at most `maxChars` without cutting mid-word.
|
|
101
|
+
*
|
|
102
|
+
* Prefers a sentence boundary, then a word boundary, and only falls back to a
|
|
103
|
+
* hard slice if no boundary exists within the limit. An ellipsis is appended
|
|
104
|
+
* when the text is actually shortened. Used by presenter fallbacks so a degraded
|
|
105
|
+
* card never shows a sentence chopped mid-word (e.g. "His focus on 'indiv").
|
|
106
|
+
*/
|
|
107
|
+
export function truncateAtBoundary(text, maxChars) {
|
|
108
|
+
const trimmed = text.trim();
|
|
109
|
+
if (trimmed.length <= maxChars)
|
|
110
|
+
return trimmed;
|
|
111
|
+
const slice = trimmed.slice(0, maxChars);
|
|
112
|
+
// Prefer ending on the last completed sentence within the limit.
|
|
113
|
+
const lastSentence = Math.max(slice.lastIndexOf(". "), slice.lastIndexOf("! "), slice.lastIndexOf("? "));
|
|
114
|
+
if (lastSentence >= maxChars * 0.5) {
|
|
115
|
+
return slice.slice(0, lastSentence + 1).trim();
|
|
116
|
+
}
|
|
117
|
+
// Otherwise back off to the last whole word and add an ellipsis.
|
|
118
|
+
const lastSpace = slice.lastIndexOf(" ");
|
|
119
|
+
const body = lastSpace > 0 ? slice.slice(0, lastSpace) : slice;
|
|
120
|
+
return body.replace(/[\s,;:.!?'"-]+$/, "").trim() + "\u2026";
|
|
121
|
+
}
|
|
99
122
|
/**
|
|
100
123
|
* Strips introducer mentions from opportunity summary text.
|
|
101
124
|
* Removes patterns like:
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"opportunity.presentation.js","sourceRoot":"/","sources":["opportunity/opportunity.presentation.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Pure presentation layer for opportunities.\n * Generates title, description, and CTA based on viewer context — no DB access.\n */\n\nimport type { Opportunity } from '../shared/interfaces/database.interface.js';\nimport { MINIMAL_MAIN_TEXT_MAX_CHARS } from \"./opportunity.labels.js\";\n\nexport interface OpportunityPresentation {\n title: string;\n description: string;\n callToAction: string;\n}\n\nexport interface UserInfo {\n id: string;\n name: string;\n avatar: string | null;\n}\n\n/**\n * Generate presentation copy for an opportunity based on viewer context.\n * Pure function — no side effects, no database access.\n */\nexport function presentOpportunity(\n opp: Opportunity,\n viewerId: string,\n otherPartyInfo: UserInfo,\n introducerInfo: UserInfo | null,\n format: 'card' | 'email' | 'notification'\n): OpportunityPresentation {\n const myActor = opp.actors.find((a) => a.userId === viewerId);\n const introducer = opp.actors.find((a) => a.role === 'introducer');\n\n if (!myActor) {\n throw new Error('Viewer is not an actor in this opportunity');\n }\n\n const otherName = otherPartyInfo.name;\n let title: string;\n let description: string;\n let descriptionIsReasoning = false;\n\n switch (myActor.role) {\n case 'agent':\n title = `You can help ${otherName}`;\n description = `Based on your expertise, ${otherName} might benefit from connecting with you.`;\n break;\n case 'patient':\n title = `${otherName} might be able to help you`;\n description = `${otherName} has skills that align with what you're looking for.`;\n break;\n case 'peer':\n title = `Potential collaboration with ${otherName}`;\n description = `You and ${otherName} have complementary interests.`;\n break;\n case 'mentee':\n title = `${otherName} could mentor you`;\n description = `${otherName} has experience that could help guide your journey.`;\n break;\n case 'mentor':\n title = `${otherName} is looking for guidance`;\n description = `Your expertise could help ${otherName} on their path.`;\n break;\n case 'founder':\n title = `${otherName} might be interested in your venture`;\n description = `${otherName}'s investment focus aligns with what you're building.`;\n break;\n case 'investor':\n title = `${otherName} is building something interesting`;\n description = `${otherName}'s venture might fit your investment thesis.`;\n break;\n case 'party':\n default:\n if (introducer && introducerInfo) {\n title = `${introducerInfo.name} thinks you should meet ${otherName}`;\n description = opp.interpretation.reasoning;\n descriptionIsReasoning = true;\n } else {\n title = `Opportunity with ${otherName}`;\n description = opp.interpretation.reasoning;\n descriptionIsReasoning = true;\n }\n break;\n }\n\n if (!descriptionIsReasoning) {\n description += `\\n\\n${opp.interpretation.reasoning}`;\n }\n\n if (format === 'notification') {\n description =\n description.length > 100 ? description.slice(0, 97) + '...' : description;\n }\n\n return {\n title,\n description,\n callToAction: 'View Opportunity',\n };\n}\n\n/**\n * Strips UUID patterns from user-facing text to prevent internal ID leaks.\n */\n\nconst UUID_PATTERN = /[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}/gi;\n\nexport function stripUuids(text: string): string {\n return text\n .replace(/\\(([^)]*)\\)/g, (_match, inner: string) => {\n if (!UUID_PATTERN.test(inner)) {\n UUID_PATTERN.lastIndex = 0;\n return _match;\n }\n UUID_PATTERN.lastIndex = 0;\n const cleaned = inner\n .replace(UUID_PATTERN, '')\n .replace(/,\\s*,/g, ',')\n .replace(/\\b(?:from|and)\\b/gi, '')\n .replace(/^[\\s,]+|[\\s,]+$/g, '');\n return cleaned ? `(${cleaned})` : '';\n })\n .replace(UUID_PATTERN, '')\n .replace(/\\s{2,}/g, ' ')\n .trim();\n}\n\n/**\n * Strips introducer mentions from opportunity summary text.\n * Removes patterns like:\n * - \"[Introducer] introduced you to [Counterpart]\"\n * - \"[Introducer] thinks you should meet [Counterpart]\"\n * - \"[Introducer] connected you to [Counterpart]\"\n * - \"[Introducer] suggested you meet [Counterpart]\"\n *\n * @param text - The text to clean (personalizedSummary)\n * @param introducerName - Full name of the introducer to strip\n * @returns Text with introducer mentions removed, counterpart preserved\n */\nexport function stripIntroducerMentions(\n text: string,\n introducerName: string | undefined,\n): string {\n if (!introducerName?.trim()) return text;\n\n const fullName = introducerName.trim();\n const firstName = fullName.split(/\\s+/)[0];\n const namesToCheck = [fullName];\n if (firstName && firstName.length > 1) {\n namesToCheck.push(firstName);\n }\n\n let result = text;\n\n for (const [idx, name] of namesToCheck.entries()) {\n const escapedName = escapeRegex(name);\n\n // Pattern: \"Name introduced you to \" (with or without comma, optionally with \"directly\")\n result = result.replace(\n new RegExp(`\\\\b${escapedName}\\\\s+introduced\\\\s+you\\\\s+(?:directly\\\\s+)?to\\\\s*`, \"gi\"),\n \"\",\n );\n\n // Pattern: \"Name thinks you should meet \"\n result = result.replace(\n new RegExp(`\\\\b${escapedName}\\\\s+thinks\\\\s+you\\\\s+should\\\\s+meet\\\\s*`, \"gi\"),\n \"\",\n );\n\n // Pattern: \"Name connected you to \"\n result = result.replace(\n new RegExp(`\\\\b${escapedName}\\\\s+connected\\\\s+you\\\\s+(?:to|with)\\\\s*`, \"gi\"),\n \"\",\n );\n\n // Pattern: \"Name suggested you meet \"\n result = result.replace(\n new RegExp(`\\\\b${escapedName}\\\\s+suggested\\\\s+you\\\\s+(?:meet|connect\\\\s+(?:to|with))\\\\s*`, \"gi\"),\n \"\",\n );\n\n // Pattern: \"Name recommended you meet \"\n result = result.replace(\n new RegExp(`\\\\b${escapedName}\\\\s+recommended\\\\s+you\\\\s+(?:meet|connect)\\\\s*`, \"gi\"),\n \"\",\n );\n\n // Pattern: \"Name thinks you and Counterpart should meet\" -> remove entire phrase up to Counterpart\n result = result.replace(\n new RegExp(`\\\\b${escapedName}\\\\s+thinks\\\\s+you\\\\s+and\\\\s+`, \"gi\"),\n \"\",\n );\n\n // Pattern: \"Name also thought...\" - remove sentences starting with Name + also/also thought\n result = result.replace(\n new RegExp(`\\\\b${escapedName}\\\\s+(?:also\\\\s+)?(?:thought|thinks?|believes?|felt)\\\\s*`, \"gi\"),\n \"\",\n );\n\n // General: Remove any remaining standalone mention of the introducer name at sentence start.\n // Only apply for fullName (idx === 0) to avoid stripping valid counterpart first names\n // (e.g. \"David Smith\" intro to \"David Johnson\" → we strip \"David Smith\" but not \"David\" in \"David Johnson\").\n if (idx === 0) {\n result = result.replace(\n new RegExp(`(?:^|\\\\.\\\\s*)\\\\b${escapedName}\\\\s+`, \"gi\"),\n (match, offset) => {\n if (offset === 0 || match.startsWith(\".\")) {\n return match.startsWith(\".\") ? \". \" : \"\";\n }\n return match;\n },\n );\n }\n }\n\n // Clean up: remove leading/trailing whitespace and common punctuation artifacts\n result = result\n .replace(/^[\\,\\s]+/, \"\") // Remove leading commas/spaces\n .replace(/\\s{2,}/g, \" \") // Normalize multiple spaces\n .trim();\n\n // Capitalize first letter if we removed from start\n if (result.length > 0) {\n result = result.charAt(0).toUpperCase() + result.slice(1);\n }\n\n return result;\n}\n\n// Helper function\nfunction escapeRegex(s: string): string {\n return s.replace(/[.*+?^${}()|[\\]\\\\]/g, \"\\\\$&\");\n}\n\n/**\n * Viewer-centric text for opportunity cards.\n * The card is shown to the viewer (logged-in user) and should introduce the\n * counterpart, not describe the viewer to themselves.\n */\n\n/**\n * Splits text into sentences using (?<=[.!?])\\s+ (period/exclamation/question followed by whitespace).\n * Note: splits after any such punctuation, including abbreviations like \"Dr.\" or \"e.g.\".\n */\nfunction splitSentences(text: string): string[] {\n const trimmed = text.trim();\n if (!trimmed) return [];\n return trimmed\n .split(/(?<=[.!?])\\s+/)\n .map((s) => s.trim())\n .filter(Boolean);\n}\n\n/**\n * Returns viewer-centric main text for an opportunity card.\n * Prefers the part of the reasoning that describes the counterpart (the person\n * on the card), so the viewer sees an introduction to the counterpart rather\n * than a description of themselves.\n *\n * @param reasoning - Raw interpretation.reasoning (may describe both parties).\n * @param counterpartName - Display name of the suggested connection (e.g. \"Alex Chen\").\n * @param maxChars - Max length of returned string (default MINIMAL_MAIN_TEXT_MAX_CHARS).\n * @param viewerName - Optional display name of the viewer (signed-in user). When provided, sentences or prefixes describing the viewer are skipped so the card introduces the counterpart, not the viewer.\n * @param introducerName - Optional display name of the introducer. When provided, introducer phrases (e.g., \"X introduced you to...\") are stripped from the summary to keep the body text focused on match quality.\n * @returns Viewer-centric snippet mentioning the counterpart when possible; if counterpartName is empty, returns reasoning truncated to maxChars. Never null; may be \"A suggested connection.\" when reasoning is empty.\n */\nexport function viewerCentricCardSummary(\n reasoning: string,\n counterpartName: string,\n maxChars: number = MINIMAL_MAIN_TEXT_MAX_CHARS,\n viewerName?: string,\n introducerName?: string,\n): string {\n const raw = stripUuids(reasoning);\n if (!raw) return \"A suggested connection.\";\n\n const name = counterpartName.trim();\n if (!name) {\n let out = raw.length <= maxChars ? raw : raw.slice(0, maxChars) + \"...\";\n // Strip introducer mentions BEFORE replacing viewer name to avoid \"you introduced...\" artifacts\n if (introducerName) {\n out = stripIntroducerMentions(out, introducerName);\n }\n out = replaceViewerNameWithYou(out, viewerName);\n return out;\n }\n\n const sentences = splitSentences(raw);\n const nameLower = name.toLowerCase();\n const firstWordOfName = name.split(/\\s+/)[0]?.toLowerCase();\n const hasCounterpartName = (s: string) =>\n s.toLowerCase().includes(nameLower) ||\n (firstWordOfName && firstWordOfName.length > 1 && s.toLowerCase().includes(firstWordOfName));\n\n const viewer = viewerName?.trim().toLowerCase();\n const viewerFirstWord = viewerName?.trim().split(/\\s+/)[0]?.toLowerCase();\n const startsWithViewer = (s: string) => {\n if (!viewer) return false;\n const sl = s.toLowerCase();\n return sl.startsWith(viewer) ||\n (viewerFirstWord && viewerFirstWord.length > 1 && sl.startsWith(viewerFirstWord));\n };\n\n // When viewerName is provided, prefer sentences that mention the counterpart\n // but do NOT start with the viewer's name.\n if (viewer) {\n // First pass: find a sentence that mentions counterpart and doesn't start with viewer\n const cleanIdx = sentences.findIndex(\n (s) => hasCounterpartName(s) && !startsWithViewer(s),\n );\n if (cleanIdx !== -1) {\n const result = sentences.slice(cleanIdx).join(\" \").trim();\n let out = result.length <= maxChars ? result : result.slice(0, maxChars) + \"...\";\n // Strip introducer mentions BEFORE replacing viewer name to avoid \"you introduced...\" artifacts\n if (introducerName) {\n out = stripIntroducerMentions(out, introducerName);\n }\n out = replaceViewerNameWithYou(out, viewerName, [name]);\n return out;\n }\n\n // Second pass: sentence mentions counterpart but starts with viewer (compound sentence).\n // Try to extract the counterpart portion after the counterpart's name.\n const compoundIdx = sentences.findIndex(\n (s) => hasCounterpartName(s) && startsWithViewer(s),\n );\n if (compoundIdx !== -1) {\n const sentence = sentences[compoundIdx];\n // Find where the counterpart name appears and extract from there\n // Use case-insensitive Unicode-aware regex so the index is correct\n // even when toLowerCase() changes string length (e.g. Turkish İ→i, German ß→ss).\n const cpMatch = sentence.match(new RegExp(escapeRegex(name), \"iu\"));\n const cpIdx = cpMatch?.index ?? -1;\n if (cpIdx > 0) {\n const extracted = sentence.slice(cpIdx).trim();\n const rest = sentences.slice(compoundIdx + 1).join(\" \").trim();\n const result = rest ? `${extracted} ${rest}` : extracted;\n let out = result.length <= maxChars ? result : result.slice(0, maxChars) + \"...\";\n // Strip introducer mentions BEFORE replacing viewer name to avoid \"you introduced...\" artifacts\n if (introducerName) {\n out = stripIntroducerMentions(out, introducerName);\n }\n out = replaceViewerNameWithYou(out, viewerName, [name]);\n return out;\n }\n }\n }\n\n // Fallback: original logic without viewer awareness\n const idx = sentences.findIndex(hasCounterpartName);\n if (idx === -1) {\n let out = raw.length <= maxChars ? raw : raw.slice(0, maxChars) + \"...\";\n // Strip introducer mentions BEFORE replacing viewer name to avoid \"you introduced...\" artifacts\n if (introducerName) {\n out = stripIntroducerMentions(out, introducerName);\n }\n out = replaceViewerNameWithYou(out, viewerName, [name]);\n return out;\n }\n\n const fromCounterpart = sentences.slice(idx).join(\" \").trim();\n let out =\n fromCounterpart.length <= maxChars\n ? fromCounterpart\n : fromCounterpart.slice(0, maxChars) + \"...\";\n // Strip introducer mentions BEFORE replacing viewer name to avoid \"you introduced...\" artifacts\n if (introducerName) {\n out = stripIntroducerMentions(out, introducerName);\n }\n out = replaceViewerNameWithYou(out, viewerName, [name]);\n return out;\n}\n\n/** Max length for narrator chip text (matches LLM presenter schema). */\nconst NARRATOR_MAX_CHARS = 80;\n\nconst FALLBACK_REMARK = \"A potential connection worth exploring.\";\n\n/**\n * Generates a short narrator remark from opportunity reasoning for the narrator chip.\n * Used by the minimal (no-LLM) card path so each card gets a unique remark\n * instead of the same static text.\n *\n * Extracts domain keywords (e.g. \"AI\", \"design\", \"machine learning\") from the\n * reasoning and frames them in a short template like \"Shared interest in AI and design.\"\n *\n * This is a regex-based heuristic — an alternative is OpportunityPresenter.presentHomeCard()\n * which generates narratorRemark via LLM with much higher quality (already used by\n * home.graph.ts and opportunity.discover.ts). See buildMinimalOpportunityCard() in\n * opportunity.tools.ts for the trade-off discussion.\n *\n * @param reasoning - Raw interpretation.reasoning text.\n * @param counterpartName - Display name of the counterpart (stripped from output).\n * @param viewerName - Optional display name of the viewer (stripped from output).\n * @returns A short remark (max ~80 chars) suitable for the narrator chip. Never truncated with \"...\".\n */\nexport function narratorRemarkFromReasoning(\n reasoning: string,\n counterpartName: string,\n viewerName?: string,\n): string {\n const raw = stripUuids(reasoning).trim();\n if (!raw) return FALLBACK_REMARK;\n\n // Strip all person names from the text so we work only with topics.\n let cleaned = raw;\n for (const name of [counterpartName, viewerName]) {\n if (!name?.trim()) continue;\n const full = name.trim();\n cleaned = cleaned.replace(new RegExp(escapeRegex(full), \"gi\"), \"\").trim();\n const first = full.split(/\\s+/)[0];\n if (first && first.length > 1) {\n cleaned = cleaned.replace(new RegExp(`\\\\b${escapeRegex(first)}\\\\b`, \"gi\"), \"\").trim();\n }\n }\n\n // Extract domain/topic noun phrases from the cleaned text.\n // Match multi-word capitalized phrases (e.g. \"AI operations toolkit\") and\n // known domain terms.\n const domainTerms = extractDomainTerms(cleaned);\n\n if (domainTerms.length > 0) {\n // Build \"Shared interest in X and Y.\" or \"Overlap in X, Y, and Z.\"\n const prefixes = [\n \"Shared interest in\",\n \"Overlap in\",\n \"Common ground in\",\n \"Aligned on\",\n \"Mutual interest in\",\n ];\n // Pick prefix deterministically based on first term's char code\n const prefixIdx = domainTerms[0].charCodeAt(0) % prefixes.length;\n const prefix = prefixes[prefixIdx];\n const joined = joinTerms(domainTerms, NARRATOR_MAX_CHARS - prefix.length - 2); // -2 for \" \" and \".\"\n const remark = `${prefix} ${joined}.`;\n if (remark.length <= NARRATOR_MAX_CHARS) return remark;\n }\n\n // Fallback: try to extract a short relationship phrase\n const relationshipMatch = cleaned.match(\n /\\b(complementary skills|shared expertise|overlapping intents|similar interests|strong match|mutual fit|potential collaboration|looking for (?:a |an )?[\\w\\s]{3,20})\\b/i,\n );\n if (relationshipMatch) {\n const phrase = relationshipMatch[0];\n const remark = `Spotted ${phrase.toLowerCase()}.`;\n if (remark.length <= NARRATOR_MAX_CHARS) return remark;\n }\n\n return FALLBACK_REMARK;\n}\n\n/**\n * Extracts domain/topic terms from text by matching known patterns:\n * - Acronyms (AI, ML, UX, API)\n * - Multi-word domain phrases (machine learning, game development)\n * - Capitalized proper nouns that look like topics\n */\nfunction extractDomainTerms(text: string): string[] {\n const seen = new Set<string>();\n const terms: string[] = [];\n\n // Known domain phrases (order matters — longer first)\n const knownPhrases = [\n /\\b(machine learning|artificial intelligence|software development|game development|web development|data science|deep learning|natural language processing|computer vision|cloud computing|mobile development|product design|user experience|graphic design|character design|frontend development|backend development|full[- ]stack|smart contracts|visual art|creative writing|content creation|digital marketing|venture capital|angel invest(?:ing|ment)|open source|blockchain|cryptocurrency|decentralized finance|social impact|community building|music production|film(?:making| production)|photography|illustration|animation|3D modeling|startup|co-?founding|entrepreneurship|research|consulting|mentoring|freelanc(?:e|ing))\\b/gi,\n /\\b(AI|ML|UX|UI|API|NLP|SaaS|DeFi|DevOps|DeSci|NFT|DAO|React|Node|Python|TypeScript|JavaScript|Rust|Solidity|Go|Swift|Kotlin|Figma|Blender|Unity|Unreal)\\b/g,\n ];\n\n for (const pattern of knownPhrases) {\n for (const match of text.matchAll(pattern)) {\n const term = match[1] ?? match[0];\n const key = term.toLowerCase();\n if (!seen.has(key)) {\n seen.add(key);\n // Preserve case for short acronyms/proper nouns; lowercase multi-word phrases\n if (term.length <= 5 && /^[A-Z]/.test(term)) {\n terms.push(term); // Keep React, AI, ML, etc. as-is\n } else {\n terms.push(key);\n }\n }\n }\n }\n\n // If no known phrases found, look for capitalized multi-word phrases\n // that look like explicit topic references (e.g. \"Visual Art\", \"Smart Contracts\").\n // Only accept capitalized words to avoid grabbing meta-language from evaluator reasoning\n // (e.g. \"discoverer\", \"explicitly\", \"states\" which are about the matching process, not topics).\n if (terms.length === 0) {\n // Multi-word capitalized phrases first (e.g. \"Visual Art\", \"Creative Writing\")\n const multiWordPattern = /\\b([A-Z][a-z]+(?:\\s+[A-Z][a-z]+)+)\\b/g;\n for (const match of text.matchAll(multiWordPattern)) {\n const term = match[1];\n const key = term.toLowerCase();\n if (!seen.has(key)) {\n seen.add(key);\n terms.push(key);\n if (terms.length >= 3) break;\n }\n }\n\n // Single capitalized words as last resort (skip common sentence-starters and meta-words)\n if (terms.length === 0) {\n const skipCapitalized = new Set([\n // Articles / conjunctions / prepositions (capitalized at sentence start)\n \"the\", \"and\", \"but\", \"for\", \"from\", \"with\", \"without\", \"between\",\n \"into\", \"about\", \"after\", \"before\", \"over\", \"under\", \"through\",\n // Common sentence starters / pronouns / determiners\n \"both\", \"their\", \"they\", \"this\", \"that\", \"these\", \"those\",\n \"here\", \"there\", \"would\", \"could\", \"should\", \"also\", \"very\",\n \"one\", \"another\", \"other\", \"each\", \"some\", \"many\", \"most\",\n \"such\", \"clear\", \"high\", \"good\", \"well\", \"just\", \"even\",\n // Generic matching/relationship language\n \"strong\", \"match\", \"based\", \"making\", \"looking\", \"seeking\",\n \"connection\", \"relationship\", \"opportunity\", \"overlap\",\n \"complementary\", \"potential\", \"interested\", \"collaborate\",\n // Evaluator meta-language (about the matching process, not topics)\n \"intent\", \"intents\", \"profile\", \"user\", \"users\", \"person\",\n \"discoverer\", \"explicitly\", \"states\", \"expressed\", \"mentioned\",\n \"indicates\", \"suggests\", \"demonstrates\", \"describes\", \"involves\",\n \"inference\", \"preparatory\", \"sincerity\", \"evaluator\", \"classifier\",\n \"semantic\", \"pragmatic\", \"verification\", \"reconciliation\",\n \"assertive\", \"commissive\", \"directive\", \"illocutionary\",\n \"felicity\", \"utterance\", \"detected\", \"analysis\", \"confirmed\",\n \"genuine\", \"conditions\", \"determined\",\n // Discourse markers\n \"particularly\", \"specifically\", \"especially\", \"primarily\",\n \"overall\", \"furthermore\", \"however\", \"therefore\", \"moreover\",\n ]);\n const capWords = text.match(/\\b[A-Z][a-z]{2,}\\b/g) ?? [];\n for (const w of capWords) {\n const key = w.toLowerCase();\n if (!skipCapitalized.has(key) && !seen.has(key)) {\n seen.add(key);\n terms.push(key);\n if (terms.length >= 3) break;\n }\n }\n }\n }\n\n return terms.slice(0, 3); // Max 3 terms\n}\n\n/** Joins terms into \"X, Y, and Z\" form, dropping terms if too long. */\nfunction joinTerms(terms: string[], maxLen: number): string {\n if (terms.length === 1) return terms[0];\n // Try all terms first\n for (let count = terms.length; count >= 1; count--) {\n const subset = terms.slice(0, count);\n let joined: string;\n if (subset.length === 1) {\n joined = subset[0];\n } else if (subset.length === 2) {\n joined = `${subset[0]} and ${subset[1]}`;\n } else {\n joined = `${subset.slice(0, -1).join(\", \")}, and ${subset[subset.length - 1]}`;\n }\n if (joined.length <= maxLen) return joined;\n }\n return terms[0].slice(0, maxLen);\n}\n\n/**\n * Replaces viewer's name with \"you\"/\"your\" so the card addresses the viewer in second person.\n * Applied to mainText when viewerName is provided.\n * @param otherNames - Other actor names in the card; first-name replacement is\n * skipped when the viewer's first name matches any other actor's first name.\n */\nfunction replaceViewerNameWithYou(text: string, viewerName?: string, otherNames?: string[]): string {\n if (!viewerName?.trim()) return text;\n const full = viewerName.trim();\n const first = full.split(/\\s+/)[0];\n let out = text;\n // Possessive: \"Yankı's\" → \"your\", \"Yankı Ekin Yüksel's\" → \"your\"\n out = out.replace(new RegExp(`\\\\b${escapeRegex(full)}'s\\\\b`, \"gi\"), \"your\");\n\n const otherFirstNames = (otherNames ?? [])\n .map(n => n.trim().split(/\\s+/)[0]?.toLowerCase())\n .filter(Boolean);\n const firstNameCollides = first && otherFirstNames.includes(first.toLowerCase());\n\n if (first && first.length > 1 && !firstNameCollides) {\n out = out.replace(new RegExp(`\\\\b${escapeRegex(first)}'s\\\\b`, \"gi\"), \"your\");\n }\n // Standalone: full name then first name so we don't break \"Yankı Ekin Yüksel\"\n out = out.replace(new RegExp(`\\\\b${escapeRegex(full)}\\\\b`, \"gi\"), \"you\");\n if (first && first.length > 1 && !firstNameCollides) {\n out = out.replace(new RegExp(`\\\\b${escapeRegex(first)}\\\\b`, \"gi\"), \"you\");\n }\n return out;\n}\n"]}
|
|
1
|
+
{"version":3,"file":"opportunity.presentation.js","sourceRoot":"/","sources":["opportunity/opportunity.presentation.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Pure presentation layer for opportunities.\n * Generates title, description, and CTA based on viewer context — no DB access.\n */\n\nimport type { Opportunity } from '../shared/interfaces/database.interface.js';\nimport { MINIMAL_MAIN_TEXT_MAX_CHARS } from \"./opportunity.labels.js\";\n\nexport interface OpportunityPresentation {\n title: string;\n description: string;\n callToAction: string;\n}\n\nexport interface UserInfo {\n id: string;\n name: string;\n avatar: string | null;\n}\n\n/**\n * Generate presentation copy for an opportunity based on viewer context.\n * Pure function — no side effects, no database access.\n */\nexport function presentOpportunity(\n opp: Opportunity,\n viewerId: string,\n otherPartyInfo: UserInfo,\n introducerInfo: UserInfo | null,\n format: 'card' | 'email' | 'notification'\n): OpportunityPresentation {\n const myActor = opp.actors.find((a) => a.userId === viewerId);\n const introducer = opp.actors.find((a) => a.role === 'introducer');\n\n if (!myActor) {\n throw new Error('Viewer is not an actor in this opportunity');\n }\n\n const otherName = otherPartyInfo.name;\n let title: string;\n let description: string;\n let descriptionIsReasoning = false;\n\n switch (myActor.role) {\n case 'agent':\n title = `You can help ${otherName}`;\n description = `Based on your expertise, ${otherName} might benefit from connecting with you.`;\n break;\n case 'patient':\n title = `${otherName} might be able to help you`;\n description = `${otherName} has skills that align with what you're looking for.`;\n break;\n case 'peer':\n title = `Potential collaboration with ${otherName}`;\n description = `You and ${otherName} have complementary interests.`;\n break;\n case 'mentee':\n title = `${otherName} could mentor you`;\n description = `${otherName} has experience that could help guide your journey.`;\n break;\n case 'mentor':\n title = `${otherName} is looking for guidance`;\n description = `Your expertise could help ${otherName} on their path.`;\n break;\n case 'founder':\n title = `${otherName} might be interested in your venture`;\n description = `${otherName}'s investment focus aligns with what you're building.`;\n break;\n case 'investor':\n title = `${otherName} is building something interesting`;\n description = `${otherName}'s venture might fit your investment thesis.`;\n break;\n case 'party':\n default:\n if (introducer && introducerInfo) {\n title = `${introducerInfo.name} thinks you should meet ${otherName}`;\n description = opp.interpretation.reasoning;\n descriptionIsReasoning = true;\n } else {\n title = `Opportunity with ${otherName}`;\n description = opp.interpretation.reasoning;\n descriptionIsReasoning = true;\n }\n break;\n }\n\n if (!descriptionIsReasoning) {\n description += `\\n\\n${opp.interpretation.reasoning}`;\n }\n\n if (format === 'notification') {\n description =\n description.length > 100 ? description.slice(0, 97) + '...' : description;\n }\n\n return {\n title,\n description,\n callToAction: 'View Opportunity',\n };\n}\n\n/**\n * Strips UUID patterns from user-facing text to prevent internal ID leaks.\n */\n\nconst UUID_PATTERN = /[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}/gi;\n\nexport function stripUuids(text: string): string {\n return text\n .replace(/\\(([^)]*)\\)/g, (_match, inner: string) => {\n if (!UUID_PATTERN.test(inner)) {\n UUID_PATTERN.lastIndex = 0;\n return _match;\n }\n UUID_PATTERN.lastIndex = 0;\n const cleaned = inner\n .replace(UUID_PATTERN, '')\n .replace(/,\\s*,/g, ',')\n .replace(/\\b(?:from|and)\\b/gi, '')\n .replace(/^[\\s,]+|[\\s,]+$/g, '');\n return cleaned ? `(${cleaned})` : '';\n })\n .replace(UUID_PATTERN, '')\n .replace(/\\s{2,}/g, ' ')\n .trim();\n}\n\n/**\n * Truncate user-facing text to at most `maxChars` without cutting mid-word.\n *\n * Prefers a sentence boundary, then a word boundary, and only falls back to a\n * hard slice if no boundary exists within the limit. An ellipsis is appended\n * when the text is actually shortened. Used by presenter fallbacks so a degraded\n * card never shows a sentence chopped mid-word (e.g. \"His focus on 'indiv\").\n */\nexport function truncateAtBoundary(text: string, maxChars: number): string {\n const trimmed = text.trim();\n if (trimmed.length <= maxChars) return trimmed;\n\n const slice = trimmed.slice(0, maxChars);\n\n // Prefer ending on the last completed sentence within the limit.\n const lastSentence = Math.max(\n slice.lastIndexOf(\". \"),\n slice.lastIndexOf(\"! \"),\n slice.lastIndexOf(\"? \"),\n );\n if (lastSentence >= maxChars * 0.5) {\n return slice.slice(0, lastSentence + 1).trim();\n }\n\n // Otherwise back off to the last whole word and add an ellipsis.\n const lastSpace = slice.lastIndexOf(\" \");\n const body = lastSpace > 0 ? slice.slice(0, lastSpace) : slice;\n return body.replace(/[\\s,;:.!?'\"-]+$/, \"\").trim() + \"\\u2026\";\n}\n\n/**\n * Strips introducer mentions from opportunity summary text.\n * Removes patterns like:\n * - \"[Introducer] introduced you to [Counterpart]\"\n * - \"[Introducer] thinks you should meet [Counterpart]\"\n * - \"[Introducer] connected you to [Counterpart]\"\n * - \"[Introducer] suggested you meet [Counterpart]\"\n *\n * @param text - The text to clean (personalizedSummary)\n * @param introducerName - Full name of the introducer to strip\n * @returns Text with introducer mentions removed, counterpart preserved\n */\nexport function stripIntroducerMentions(\n text: string,\n introducerName: string | undefined,\n): string {\n if (!introducerName?.trim()) return text;\n\n const fullName = introducerName.trim();\n const firstName = fullName.split(/\\s+/)[0];\n const namesToCheck = [fullName];\n if (firstName && firstName.length > 1) {\n namesToCheck.push(firstName);\n }\n\n let result = text;\n\n for (const [idx, name] of namesToCheck.entries()) {\n const escapedName = escapeRegex(name);\n\n // Pattern: \"Name introduced you to \" (with or without comma, optionally with \"directly\")\n result = result.replace(\n new RegExp(`\\\\b${escapedName}\\\\s+introduced\\\\s+you\\\\s+(?:directly\\\\s+)?to\\\\s*`, \"gi\"),\n \"\",\n );\n\n // Pattern: \"Name thinks you should meet \"\n result = result.replace(\n new RegExp(`\\\\b${escapedName}\\\\s+thinks\\\\s+you\\\\s+should\\\\s+meet\\\\s*`, \"gi\"),\n \"\",\n );\n\n // Pattern: \"Name connected you to \"\n result = result.replace(\n new RegExp(`\\\\b${escapedName}\\\\s+connected\\\\s+you\\\\s+(?:to|with)\\\\s*`, \"gi\"),\n \"\",\n );\n\n // Pattern: \"Name suggested you meet \"\n result = result.replace(\n new RegExp(`\\\\b${escapedName}\\\\s+suggested\\\\s+you\\\\s+(?:meet|connect\\\\s+(?:to|with))\\\\s*`, \"gi\"),\n \"\",\n );\n\n // Pattern: \"Name recommended you meet \"\n result = result.replace(\n new RegExp(`\\\\b${escapedName}\\\\s+recommended\\\\s+you\\\\s+(?:meet|connect)\\\\s*`, \"gi\"),\n \"\",\n );\n\n // Pattern: \"Name thinks you and Counterpart should meet\" -> remove entire phrase up to Counterpart\n result = result.replace(\n new RegExp(`\\\\b${escapedName}\\\\s+thinks\\\\s+you\\\\s+and\\\\s+`, \"gi\"),\n \"\",\n );\n\n // Pattern: \"Name also thought...\" - remove sentences starting with Name + also/also thought\n result = result.replace(\n new RegExp(`\\\\b${escapedName}\\\\s+(?:also\\\\s+)?(?:thought|thinks?|believes?|felt)\\\\s*`, \"gi\"),\n \"\",\n );\n\n // General: Remove any remaining standalone mention of the introducer name at sentence start.\n // Only apply for fullName (idx === 0) to avoid stripping valid counterpart first names\n // (e.g. \"David Smith\" intro to \"David Johnson\" → we strip \"David Smith\" but not \"David\" in \"David Johnson\").\n if (idx === 0) {\n result = result.replace(\n new RegExp(`(?:^|\\\\.\\\\s*)\\\\b${escapedName}\\\\s+`, \"gi\"),\n (match, offset) => {\n if (offset === 0 || match.startsWith(\".\")) {\n return match.startsWith(\".\") ? \". \" : \"\";\n }\n return match;\n },\n );\n }\n }\n\n // Clean up: remove leading/trailing whitespace and common punctuation artifacts\n result = result\n .replace(/^[\\,\\s]+/, \"\") // Remove leading commas/spaces\n .replace(/\\s{2,}/g, \" \") // Normalize multiple spaces\n .trim();\n\n // Capitalize first letter if we removed from start\n if (result.length > 0) {\n result = result.charAt(0).toUpperCase() + result.slice(1);\n }\n\n return result;\n}\n\n// Helper function\nfunction escapeRegex(s: string): string {\n return s.replace(/[.*+?^${}()|[\\]\\\\]/g, \"\\\\$&\");\n}\n\n/**\n * Viewer-centric text for opportunity cards.\n * The card is shown to the viewer (logged-in user) and should introduce the\n * counterpart, not describe the viewer to themselves.\n */\n\n/**\n * Splits text into sentences using (?<=[.!?])\\s+ (period/exclamation/question followed by whitespace).\n * Note: splits after any such punctuation, including abbreviations like \"Dr.\" or \"e.g.\".\n */\nfunction splitSentences(text: string): string[] {\n const trimmed = text.trim();\n if (!trimmed) return [];\n return trimmed\n .split(/(?<=[.!?])\\s+/)\n .map((s) => s.trim())\n .filter(Boolean);\n}\n\n/**\n * Returns viewer-centric main text for an opportunity card.\n * Prefers the part of the reasoning that describes the counterpart (the person\n * on the card), so the viewer sees an introduction to the counterpart rather\n * than a description of themselves.\n *\n * @param reasoning - Raw interpretation.reasoning (may describe both parties).\n * @param counterpartName - Display name of the suggested connection (e.g. \"Alex Chen\").\n * @param maxChars - Max length of returned string (default MINIMAL_MAIN_TEXT_MAX_CHARS).\n * @param viewerName - Optional display name of the viewer (signed-in user). When provided, sentences or prefixes describing the viewer are skipped so the card introduces the counterpart, not the viewer.\n * @param introducerName - Optional display name of the introducer. When provided, introducer phrases (e.g., \"X introduced you to...\") are stripped from the summary to keep the body text focused on match quality.\n * @returns Viewer-centric snippet mentioning the counterpart when possible; if counterpartName is empty, returns reasoning truncated to maxChars. Never null; may be \"A suggested connection.\" when reasoning is empty.\n */\nexport function viewerCentricCardSummary(\n reasoning: string,\n counterpartName: string,\n maxChars: number = MINIMAL_MAIN_TEXT_MAX_CHARS,\n viewerName?: string,\n introducerName?: string,\n): string {\n const raw = stripUuids(reasoning);\n if (!raw) return \"A suggested connection.\";\n\n const name = counterpartName.trim();\n if (!name) {\n let out = raw.length <= maxChars ? raw : raw.slice(0, maxChars) + \"...\";\n // Strip introducer mentions BEFORE replacing viewer name to avoid \"you introduced...\" artifacts\n if (introducerName) {\n out = stripIntroducerMentions(out, introducerName);\n }\n out = replaceViewerNameWithYou(out, viewerName);\n return out;\n }\n\n const sentences = splitSentences(raw);\n const nameLower = name.toLowerCase();\n const firstWordOfName = name.split(/\\s+/)[0]?.toLowerCase();\n const hasCounterpartName = (s: string) =>\n s.toLowerCase().includes(nameLower) ||\n (firstWordOfName && firstWordOfName.length > 1 && s.toLowerCase().includes(firstWordOfName));\n\n const viewer = viewerName?.trim().toLowerCase();\n const viewerFirstWord = viewerName?.trim().split(/\\s+/)[0]?.toLowerCase();\n const startsWithViewer = (s: string) => {\n if (!viewer) return false;\n const sl = s.toLowerCase();\n return sl.startsWith(viewer) ||\n (viewerFirstWord && viewerFirstWord.length > 1 && sl.startsWith(viewerFirstWord));\n };\n\n // When viewerName is provided, prefer sentences that mention the counterpart\n // but do NOT start with the viewer's name.\n if (viewer) {\n // First pass: find a sentence that mentions counterpart and doesn't start with viewer\n const cleanIdx = sentences.findIndex(\n (s) => hasCounterpartName(s) && !startsWithViewer(s),\n );\n if (cleanIdx !== -1) {\n const result = sentences.slice(cleanIdx).join(\" \").trim();\n let out = result.length <= maxChars ? result : result.slice(0, maxChars) + \"...\";\n // Strip introducer mentions BEFORE replacing viewer name to avoid \"you introduced...\" artifacts\n if (introducerName) {\n out = stripIntroducerMentions(out, introducerName);\n }\n out = replaceViewerNameWithYou(out, viewerName, [name]);\n return out;\n }\n\n // Second pass: sentence mentions counterpart but starts with viewer (compound sentence).\n // Try to extract the counterpart portion after the counterpart's name.\n const compoundIdx = sentences.findIndex(\n (s) => hasCounterpartName(s) && startsWithViewer(s),\n );\n if (compoundIdx !== -1) {\n const sentence = sentences[compoundIdx];\n // Find where the counterpart name appears and extract from there\n // Use case-insensitive Unicode-aware regex so the index is correct\n // even when toLowerCase() changes string length (e.g. Turkish İ→i, German ß→ss).\n const cpMatch = sentence.match(new RegExp(escapeRegex(name), \"iu\"));\n const cpIdx = cpMatch?.index ?? -1;\n if (cpIdx > 0) {\n const extracted = sentence.slice(cpIdx).trim();\n const rest = sentences.slice(compoundIdx + 1).join(\" \").trim();\n const result = rest ? `${extracted} ${rest}` : extracted;\n let out = result.length <= maxChars ? result : result.slice(0, maxChars) + \"...\";\n // Strip introducer mentions BEFORE replacing viewer name to avoid \"you introduced...\" artifacts\n if (introducerName) {\n out = stripIntroducerMentions(out, introducerName);\n }\n out = replaceViewerNameWithYou(out, viewerName, [name]);\n return out;\n }\n }\n }\n\n // Fallback: original logic without viewer awareness\n const idx = sentences.findIndex(hasCounterpartName);\n if (idx === -1) {\n let out = raw.length <= maxChars ? raw : raw.slice(0, maxChars) + \"...\";\n // Strip introducer mentions BEFORE replacing viewer name to avoid \"you introduced...\" artifacts\n if (introducerName) {\n out = stripIntroducerMentions(out, introducerName);\n }\n out = replaceViewerNameWithYou(out, viewerName, [name]);\n return out;\n }\n\n const fromCounterpart = sentences.slice(idx).join(\" \").trim();\n let out =\n fromCounterpart.length <= maxChars\n ? fromCounterpart\n : fromCounterpart.slice(0, maxChars) + \"...\";\n // Strip introducer mentions BEFORE replacing viewer name to avoid \"you introduced...\" artifacts\n if (introducerName) {\n out = stripIntroducerMentions(out, introducerName);\n }\n out = replaceViewerNameWithYou(out, viewerName, [name]);\n return out;\n}\n\n/** Max length for narrator chip text (matches LLM presenter schema). */\nconst NARRATOR_MAX_CHARS = 80;\n\nconst FALLBACK_REMARK = \"A potential connection worth exploring.\";\n\n/**\n * Generates a short narrator remark from opportunity reasoning for the narrator chip.\n * Used by the minimal (no-LLM) card path so each card gets a unique remark\n * instead of the same static text.\n *\n * Extracts domain keywords (e.g. \"AI\", \"design\", \"machine learning\") from the\n * reasoning and frames them in a short template like \"Shared interest in AI and design.\"\n *\n * This is a regex-based heuristic — an alternative is OpportunityPresenter.presentHomeCard()\n * which generates narratorRemark via LLM with much higher quality (already used by\n * home.graph.ts and opportunity.discover.ts). See buildMinimalOpportunityCard() in\n * opportunity.tools.ts for the trade-off discussion.\n *\n * @param reasoning - Raw interpretation.reasoning text.\n * @param counterpartName - Display name of the counterpart (stripped from output).\n * @param viewerName - Optional display name of the viewer (stripped from output).\n * @returns A short remark (max ~80 chars) suitable for the narrator chip. Never truncated with \"...\".\n */\nexport function narratorRemarkFromReasoning(\n reasoning: string,\n counterpartName: string,\n viewerName?: string,\n): string {\n const raw = stripUuids(reasoning).trim();\n if (!raw) return FALLBACK_REMARK;\n\n // Strip all person names from the text so we work only with topics.\n let cleaned = raw;\n for (const name of [counterpartName, viewerName]) {\n if (!name?.trim()) continue;\n const full = name.trim();\n cleaned = cleaned.replace(new RegExp(escapeRegex(full), \"gi\"), \"\").trim();\n const first = full.split(/\\s+/)[0];\n if (first && first.length > 1) {\n cleaned = cleaned.replace(new RegExp(`\\\\b${escapeRegex(first)}\\\\b`, \"gi\"), \"\").trim();\n }\n }\n\n // Extract domain/topic noun phrases from the cleaned text.\n // Match multi-word capitalized phrases (e.g. \"AI operations toolkit\") and\n // known domain terms.\n const domainTerms = extractDomainTerms(cleaned);\n\n if (domainTerms.length > 0) {\n // Build \"Shared interest in X and Y.\" or \"Overlap in X, Y, and Z.\"\n const prefixes = [\n \"Shared interest in\",\n \"Overlap in\",\n \"Common ground in\",\n \"Aligned on\",\n \"Mutual interest in\",\n ];\n // Pick prefix deterministically based on first term's char code\n const prefixIdx = domainTerms[0].charCodeAt(0) % prefixes.length;\n const prefix = prefixes[prefixIdx];\n const joined = joinTerms(domainTerms, NARRATOR_MAX_CHARS - prefix.length - 2); // -2 for \" \" and \".\"\n const remark = `${prefix} ${joined}.`;\n if (remark.length <= NARRATOR_MAX_CHARS) return remark;\n }\n\n // Fallback: try to extract a short relationship phrase\n const relationshipMatch = cleaned.match(\n /\\b(complementary skills|shared expertise|overlapping intents|similar interests|strong match|mutual fit|potential collaboration|looking for (?:a |an )?[\\w\\s]{3,20})\\b/i,\n );\n if (relationshipMatch) {\n const phrase = relationshipMatch[0];\n const remark = `Spotted ${phrase.toLowerCase()}.`;\n if (remark.length <= NARRATOR_MAX_CHARS) return remark;\n }\n\n return FALLBACK_REMARK;\n}\n\n/**\n * Extracts domain/topic terms from text by matching known patterns:\n * - Acronyms (AI, ML, UX, API)\n * - Multi-word domain phrases (machine learning, game development)\n * - Capitalized proper nouns that look like topics\n */\nfunction extractDomainTerms(text: string): string[] {\n const seen = new Set<string>();\n const terms: string[] = [];\n\n // Known domain phrases (order matters — longer first)\n const knownPhrases = [\n /\\b(machine learning|artificial intelligence|software development|game development|web development|data science|deep learning|natural language processing|computer vision|cloud computing|mobile development|product design|user experience|graphic design|character design|frontend development|backend development|full[- ]stack|smart contracts|visual art|creative writing|content creation|digital marketing|venture capital|angel invest(?:ing|ment)|open source|blockchain|cryptocurrency|decentralized finance|social impact|community building|music production|film(?:making| production)|photography|illustration|animation|3D modeling|startup|co-?founding|entrepreneurship|research|consulting|mentoring|freelanc(?:e|ing))\\b/gi,\n /\\b(AI|ML|UX|UI|API|NLP|SaaS|DeFi|DevOps|DeSci|NFT|DAO|React|Node|Python|TypeScript|JavaScript|Rust|Solidity|Go|Swift|Kotlin|Figma|Blender|Unity|Unreal)\\b/g,\n ];\n\n for (const pattern of knownPhrases) {\n for (const match of text.matchAll(pattern)) {\n const term = match[1] ?? match[0];\n const key = term.toLowerCase();\n if (!seen.has(key)) {\n seen.add(key);\n // Preserve case for short acronyms/proper nouns; lowercase multi-word phrases\n if (term.length <= 5 && /^[A-Z]/.test(term)) {\n terms.push(term); // Keep React, AI, ML, etc. as-is\n } else {\n terms.push(key);\n }\n }\n }\n }\n\n // If no known phrases found, look for capitalized multi-word phrases\n // that look like explicit topic references (e.g. \"Visual Art\", \"Smart Contracts\").\n // Only accept capitalized words to avoid grabbing meta-language from evaluator reasoning\n // (e.g. \"discoverer\", \"explicitly\", \"states\" which are about the matching process, not topics).\n if (terms.length === 0) {\n // Multi-word capitalized phrases first (e.g. \"Visual Art\", \"Creative Writing\")\n const multiWordPattern = /\\b([A-Z][a-z]+(?:\\s+[A-Z][a-z]+)+)\\b/g;\n for (const match of text.matchAll(multiWordPattern)) {\n const term = match[1];\n const key = term.toLowerCase();\n if (!seen.has(key)) {\n seen.add(key);\n terms.push(key);\n if (terms.length >= 3) break;\n }\n }\n\n // Single capitalized words as last resort (skip common sentence-starters and meta-words)\n if (terms.length === 0) {\n const skipCapitalized = new Set([\n // Articles / conjunctions / prepositions (capitalized at sentence start)\n \"the\", \"and\", \"but\", \"for\", \"from\", \"with\", \"without\", \"between\",\n \"into\", \"about\", \"after\", \"before\", \"over\", \"under\", \"through\",\n // Common sentence starters / pronouns / determiners\n \"both\", \"their\", \"they\", \"this\", \"that\", \"these\", \"those\",\n \"here\", \"there\", \"would\", \"could\", \"should\", \"also\", \"very\",\n \"one\", \"another\", \"other\", \"each\", \"some\", \"many\", \"most\",\n \"such\", \"clear\", \"high\", \"good\", \"well\", \"just\", \"even\",\n // Generic matching/relationship language\n \"strong\", \"match\", \"based\", \"making\", \"looking\", \"seeking\",\n \"connection\", \"relationship\", \"opportunity\", \"overlap\",\n \"complementary\", \"potential\", \"interested\", \"collaborate\",\n // Evaluator meta-language (about the matching process, not topics)\n \"intent\", \"intents\", \"profile\", \"user\", \"users\", \"person\",\n \"discoverer\", \"explicitly\", \"states\", \"expressed\", \"mentioned\",\n \"indicates\", \"suggests\", \"demonstrates\", \"describes\", \"involves\",\n \"inference\", \"preparatory\", \"sincerity\", \"evaluator\", \"classifier\",\n \"semantic\", \"pragmatic\", \"verification\", \"reconciliation\",\n \"assertive\", \"commissive\", \"directive\", \"illocutionary\",\n \"felicity\", \"utterance\", \"detected\", \"analysis\", \"confirmed\",\n \"genuine\", \"conditions\", \"determined\",\n // Discourse markers\n \"particularly\", \"specifically\", \"especially\", \"primarily\",\n \"overall\", \"furthermore\", \"however\", \"therefore\", \"moreover\",\n ]);\n const capWords = text.match(/\\b[A-Z][a-z]{2,}\\b/g) ?? [];\n for (const w of capWords) {\n const key = w.toLowerCase();\n if (!skipCapitalized.has(key) && !seen.has(key)) {\n seen.add(key);\n terms.push(key);\n if (terms.length >= 3) break;\n }\n }\n }\n }\n\n return terms.slice(0, 3); // Max 3 terms\n}\n\n/** Joins terms into \"X, Y, and Z\" form, dropping terms if too long. */\nfunction joinTerms(terms: string[], maxLen: number): string {\n if (terms.length === 1) return terms[0];\n // Try all terms first\n for (let count = terms.length; count >= 1; count--) {\n const subset = terms.slice(0, count);\n let joined: string;\n if (subset.length === 1) {\n joined = subset[0];\n } else if (subset.length === 2) {\n joined = `${subset[0]} and ${subset[1]}`;\n } else {\n joined = `${subset.slice(0, -1).join(\", \")}, and ${subset[subset.length - 1]}`;\n }\n if (joined.length <= maxLen) return joined;\n }\n return terms[0].slice(0, maxLen);\n}\n\n/**\n * Replaces viewer's name with \"you\"/\"your\" so the card addresses the viewer in second person.\n * Applied to mainText when viewerName is provided.\n * @param otherNames - Other actor names in the card; first-name replacement is\n * skipped when the viewer's first name matches any other actor's first name.\n */\nfunction replaceViewerNameWithYou(text: string, viewerName?: string, otherNames?: string[]): string {\n if (!viewerName?.trim()) return text;\n const full = viewerName.trim();\n const first = full.split(/\\s+/)[0];\n let out = text;\n // Possessive: \"Yankı's\" → \"your\", \"Yankı Ekin Yüksel's\" → \"your\"\n out = out.replace(new RegExp(`\\\\b${escapeRegex(full)}'s\\\\b`, \"gi\"), \"your\");\n\n const otherFirstNames = (otherNames ?? [])\n .map(n => n.trim().split(/\\s+/)[0]?.toLowerCase())\n .filter(Boolean);\n const firstNameCollides = first && otherFirstNames.includes(first.toLowerCase());\n\n if (first && first.length > 1 && !firstNameCollides) {\n out = out.replace(new RegExp(`\\\\b${escapeRegex(first)}'s\\\\b`, \"gi\"), \"your\");\n }\n // Standalone: full name then first name so we don't break \"Yankı Ekin Yüksel\"\n out = out.replace(new RegExp(`\\\\b${escapeRegex(full)}\\\\b`, \"gi\"), \"you\");\n if (first && first.length > 1 && !firstNameCollides) {\n out = out.replace(new RegExp(`\\\\b${escapeRegex(first)}\\\\b`, \"gi\"), \"you\");\n }\n return out;\n}\n"]}
|
|
@@ -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;AAG1E;;;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,CAAC;AAE/E,oGAAoG;AACpG,MAAM,WAAW,sBAAuB,SAAQ,cAAc;IAC5D,yEAAyE;IACzE,iBAAiB,CAAC,EAAE,MAAM,CAAC;IAC3B;;;;;OAKG;IACH,kBAAkB,CAAC,EAAE,kBAAkB,CAAC;CACzC;AAED,6GAA6G;AAC7G,eAAO,MAAM,iBAAiB;;;;;;;;;;;;;;;;;;;;;;;;EA+B5B,CAAC;AAEH,2GAA2G;AAC3G,MAAM,MAAM,iBAAiB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,iBAAiB,CAAC,CAAC;AAElE,gGAAgG;AAChG,MAAM,MAAM,0BAA0B,GAAG,iBAAiB,GAAG;IAC3D,kBAAkB,EAAE,MAAM,CAAC;IAC3B,oBAAoB,EAAE,MAAM,CAAC;CAC9B,CAAC;AAMF,qEAAqE;AACrE,MAAM,WAAW,cAAc;IAC7B,aAAa,EAAE,MAAM,CAAC;IACtB,iBAAiB,EAAE,MAAM,CAAC;IAC1B,cAAc,EAAE,MAAM,CAAC;IACvB,QAAQ,EAAE,MAAM,CAAC;IACjB,UAAU,EAAE,MAAM,CAAC;IACnB,cAAc,EAAE,MAAM,CAAC;IACvB,SAAS,EAAE,MAAM,CAAC;IAClB,UAAU,EAAE,MAAM,CAAC;IACnB,iBAAiB,CAAC,EAAE,MAAM,CAAC;IAC3B,qGAAqG;IACrG,cAAc,CAAC,EAAE,OAAO,CAAC;IACzB,mEAAmE;IACnE,cAAc,CAAC,EAAE,MAAM,CAAC;CACzB;AAmID,qBAAa,oBAAoB;IAC/B,OAAO,CAAC,KAAK,CAAW;IACxB,OAAO,CAAC,aAAa,CAAW;;YAWlB,iBAAiB;IA4B/B;;OAEG;IAEU,OAAO,CAClB,KAAK,EAAE,cAAc,GACpB,OAAO,CAAC,6BAA6B,CAAC;
|
|
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;AAG1E;;;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,CAAC;AAE/E,oGAAoG;AACpG,MAAM,WAAW,sBAAuB,SAAQ,cAAc;IAC5D,yEAAyE;IACzE,iBAAiB,CAAC,EAAE,MAAM,CAAC;IAC3B;;;;;OAKG;IACH,kBAAkB,CAAC,EAAE,kBAAkB,CAAC;CACzC;AAED,6GAA6G;AAC7G,eAAO,MAAM,iBAAiB;;;;;;;;;;;;;;;;;;;;;;;;EA+B5B,CAAC;AAEH,2GAA2G;AAC3G,MAAM,MAAM,iBAAiB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,iBAAiB,CAAC,CAAC;AAElE,gGAAgG;AAChG,MAAM,MAAM,0BAA0B,GAAG,iBAAiB,GAAG;IAC3D,kBAAkB,EAAE,MAAM,CAAC;IAC3B,oBAAoB,EAAE,MAAM,CAAC;CAC9B,CAAC;AAMF,qEAAqE;AACrE,MAAM,WAAW,cAAc;IAC7B,aAAa,EAAE,MAAM,CAAC;IACtB,iBAAiB,EAAE,MAAM,CAAC;IAC1B,cAAc,EAAE,MAAM,CAAC;IACvB,QAAQ,EAAE,MAAM,CAAC;IACjB,UAAU,EAAE,MAAM,CAAC;IACnB,cAAc,EAAE,MAAM,CAAC;IACvB,SAAS,EAAE,MAAM,CAAC;IAClB,UAAU,EAAE,MAAM,CAAC;IACnB,iBAAiB,CAAC,EAAE,MAAM,CAAC;IAC3B,qGAAqG;IACrG,cAAc,CAAC,EAAE,OAAO,CAAC;IACzB,mEAAmE;IACnE,cAAc,CAAC,EAAE,MAAM,CAAC;CACzB;AAmID,qBAAa,oBAAoB;IAC/B,OAAO,CAAC,KAAK,CAAW;IACxB,OAAO,CAAC,aAAa,CAAW;;YAWlB,iBAAiB;IA4B/B;;OAEG;IAEU,OAAO,CAClB,KAAK,EAAE,cAAc,GACpB,OAAO,CAAC,6BAA6B,CAAC;IAsDzC;;;;;;;OAOG;IAEU,eAAe,CAC1B,KAAK,EAAE,sBAAsB,GAC5B,OAAO,CAAC,iBAAiB,CAAC;IAwG7B;;OAEG;IAEU,YAAY,CACvB,MAAM,EAAE,cAAc,EAAE,EACxB,OAAO,CAAC,EAAE;QAAE,WAAW,CAAC,EAAE,MAAM,CAAA;KAAE,GACjC,OAAO,CAAC,6BAA6B,EAAE,CAAC;IAa3C;;;OAGG;IAEU,oBAAoB,CAC/B,MAAM,EAAE,sBAAsB,EAAE,EAChC,OAAO,CAAC,EAAE;QAAE,WAAW,CAAC,EAAE,MAAM,CAAA;KAAE,GACjC,OAAO,CAAC,iBAAiB,EAAE,CAAC;CAYhC;AA0ED;;;;;GAKG;AACH,wBAAsB,sBAAsB,CAC1C,QAAQ,EAAE,iBAAiB,EAC3B,WAAW,EAAE,WAAW,EACxB,QAAQ,EAAE,MAAM,EAChB,wBAAwB,CAAC,EAAE,MAAM,GAChC,OAAO,CAAC,cAAc,CAAC,CA0OzB"}
|
|
@@ -21,7 +21,7 @@ import { Timed } from "../shared/observability/performance.js";
|
|
|
21
21
|
import { protocolLogger } from "../shared/observability/protocol.logger.js";
|
|
22
22
|
import { createModel } from "../shared/agent/model.config.js";
|
|
23
23
|
import { viewerCentricCardSummary } from "./opportunity.presentation.js";
|
|
24
|
-
import { stripUuids, stripIntroducerMentions } from "./opportunity.presentation.js";
|
|
24
|
+
import { stripUuids, stripIntroducerMentions, truncateAtBoundary } from "./opportunity.presentation.js";
|
|
25
25
|
const logger = protocolLogger("OpportunityPresenter");
|
|
26
26
|
const LLM_TIMEOUT_MS = 20000;
|
|
27
27
|
const model = createModel("opportunityPresenter");
|
|
@@ -265,12 +265,15 @@ Produce headline, personalizedSummary (2-3 sentences in "you" language), suggest
|
|
|
265
265
|
const message = e instanceof Error ? e.message : String(e);
|
|
266
266
|
const timeoutReason = message.includes("timed out") ? message : undefined;
|
|
267
267
|
logger.warn("[OpportunityPresenter.present] LLM failed, returning fallback", {
|
|
268
|
+
event: "presenter_fallback",
|
|
269
|
+
presenter: "opportunity",
|
|
270
|
+
reason: timeoutReason ? "timeout" : "parse_error",
|
|
268
271
|
message,
|
|
269
272
|
timeoutReason,
|
|
270
273
|
});
|
|
271
274
|
return {
|
|
272
275
|
headline: "A promising connection",
|
|
273
|
-
personalizedSummary: stripUuids(input.matchReasoning
|
|
276
|
+
personalizedSummary: truncateAtBoundary(stripUuids(input.matchReasoning), 300),
|
|
274
277
|
suggestedAction: "Take a look and decide whether to reach out.",
|
|
275
278
|
greeting: "",
|
|
276
279
|
};
|
|
@@ -346,10 +349,13 @@ Produce headline, personalizedSummary, digestSummary, suggestedAction, narratorR
|
|
|
346
349
|
const message = e instanceof Error ? e.message : String(e);
|
|
347
350
|
const timeoutReason = message.includes("timed out") ? message : undefined;
|
|
348
351
|
logger.warn("[OpportunityPresenter.presentHomeCard] LLM failed, returning fallback", {
|
|
352
|
+
event: "presenter_fallback",
|
|
353
|
+
presenter: "home_card",
|
|
354
|
+
reason: timeoutReason ? "timeout" : "parse_error",
|
|
349
355
|
message,
|
|
350
356
|
timeoutReason,
|
|
351
357
|
});
|
|
352
|
-
let fallbackSummary = stripUuids(input.matchReasoning
|
|
358
|
+
let fallbackSummary = truncateAtBoundary(stripUuids(input.matchReasoning), 300);
|
|
353
359
|
if (input.isIntroduction && input.introducerName) {
|
|
354
360
|
fallbackSummary = stripIntroducerMentions(fallbackSummary, input.introducerName);
|
|
355
361
|
}
|