@indexnetwork/protocol 1.26.3-rc.223.1 → 1.26.3-rc.225.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.
@@ -1 +1 @@
1
- {"version":3,"file":"opportunity.tools.d.ts","sourceRoot":"/","sources":["opportunity/opportunity.tools.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"opportunity.tools.d.ts","sourceRoot":"/","sources":["opportunity/opportunity.tools.ts"],"names":[],"mappings":"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"}
@@ -2,9 +2,10 @@ import { z } from "zod";
2
2
  import { requestContext } from "../shared/observability/request-context.js";
3
3
  import { success, error, UUID_REGEX } from "../shared/agent/tool.helpers.js";
4
4
  import { MINIMAL_MAIN_TEXT_MAX_CHARS, getPrimaryActionLabel, SECONDARY_ACTION_LABEL } from "./opportunity.labels.js";
5
- import { viewerCentricCardSummary, narratorRemarkFromReasoning } from "./opportunity.presentation.js";
5
+ import { viewerCentricCardSummary, narratorRemarkFromReasoning, stripUuids } from "./opportunity.presentation.js";
6
6
  import { runDiscoverFromQuery, continueDiscovery } from "./opportunity.discover.js";
7
- import { OpportunityPresenter } from "./opportunity.presenter.js";
7
+ import { OpportunityPresenter, gatherPresenterContext } from "./opportunity.presenter.js";
8
+ import { stripLeadingNarratorName } from "./feed/feed.graph.js";
8
9
  import { protocolLogger } from "../shared/observability/protocol.logger.js";
9
10
  import { selectByComposition, deduplicateByPerson } from "./opportunity.utils.js";
10
11
  import { mergePendingQuestions } from "./opportunity.pending-questions.js";
@@ -1230,75 +1231,230 @@ export function createOpportunityTools(defineTool, deps) {
1230
1231
  if (user)
1231
1232
  userMap.set(userId, user);
1232
1233
  });
1234
+ const isDigestMode = context.isMcp === true && query.includeDigestMarkers === true;
1233
1235
  const cardDataList = [];
1234
1236
  const seenOpportunityIds = new Set();
1235
1237
  const skippedIds = [];
1236
- for (const opp of opportunities) {
1237
- if (seenOpportunityIds.has(opp.id))
1238
- continue;
1239
- seenOpportunityIds.add(opp.id);
1240
- try {
1241
- const counterpartActor = opp.actors.find((a) => a.userId !== context.userId && a.role !== "introducer");
1242
- const counterpartUserId = counterpartActor?.userId;
1243
- if (!counterpartUserId)
1238
+ if (isDigestMode) {
1239
+ // ── Digest mode: use LLM presenter for rich, second-person card text ──
1240
+ const presenter = new OpportunityPresenter();
1241
+ const presenterDb = database;
1242
+ const PRESENTER_CONCURRENCY = 6;
1243
+ for (let i = 0; i < opportunities.length; i += PRESENTER_CONCURRENCY) {
1244
+ const chunk = opportunities.slice(i, i + PRESENTER_CONCURRENCY);
1245
+ const chunkCards = await Promise.all(chunk.map(async (opp) => {
1246
+ if (seenOpportunityIds.has(opp.id))
1247
+ return null;
1248
+ seenOpportunityIds.add(opp.id);
1249
+ try {
1250
+ const counterpartActor = opp.actors.find((a) => a.userId !== context.userId && a.role !== "introducer");
1251
+ const counterpartUserId = counterpartActor?.userId;
1252
+ if (!counterpartUserId)
1253
+ return null;
1254
+ const viewerIsIntroducerHere = opp.actors.some((a) => a.role === "introducer" && a.userId === context.userId);
1255
+ const secondPartyActorForHeadline = viewerIsIntroducerHere
1256
+ ? opp.actors.find((a) => a.userId !== context.userId &&
1257
+ a.userId !== counterpartUserId &&
1258
+ a.role !== "introducer")
1259
+ : undefined;
1260
+ const introducerActor = opp.actors.find((a) => a.role === "introducer" && a.userId !== context.userId);
1261
+ const createdByName = opp.detection.createdByName;
1262
+ const counterpartUser = userMap.get(counterpartUserId) ?? null;
1263
+ const counterpartName = profileMap.get(counterpartUserId)?.identity?.name ??
1264
+ counterpartUser?.name ??
1265
+ "Someone";
1266
+ const introducerName = createdByName ??
1267
+ (introducerActor
1268
+ ? (profileMap.get(introducerActor.userId)?.identity?.name ?? null)
1269
+ : null);
1270
+ const introducerUser = introducerActor
1271
+ ? userMap.get(introducerActor.userId) ?? null
1272
+ : null;
1273
+ const secondPartyUser = secondPartyActorForHeadline
1274
+ ? userMap.get(secondPartyActorForHeadline.userId) ?? null
1275
+ : null;
1276
+ const secondPartyNameForHeadline = secondPartyActorForHeadline
1277
+ ? (profileMap.get(secondPartyActorForHeadline.userId)?.identity?.name ??
1278
+ secondPartyUser?.name ??
1279
+ undefined)
1280
+ : undefined;
1281
+ const viewerActor = opp.actors.find((a) => a.userId === context.userId);
1282
+ const viewerRole = viewerActor?.role ?? "party";
1283
+ const isCounterpartGhost = counterpartUser?.isGhost ?? false;
1284
+ // Build fallback card in case LLM presenter fails
1285
+ const fallbackCard = buildMinimalOpportunityCard(opp, context.userId, counterpartUserId, counterpartName, counterpartUser?.avatar ?? null, introducerName, introducerUser?.avatar ?? null, viewerName, secondPartyNameForHeadline, secondPartyUser?.avatar ?? null, secondPartyActorForHeadline?.userId, isCounterpartGhost);
1286
+ try {
1287
+ const ctx = await gatherPresenterContext(presenterDb, opp, context.userId, counterpartUserId);
1288
+ const presentation = await presenter.presentHomeCard({
1289
+ ...ctx,
1290
+ opportunityStatus: opp.status,
1291
+ });
1292
+ // Build narrator chip from presenter output
1293
+ let narratorChip;
1294
+ const introducerIsCounterpart = introducerActor && counterpartActor && introducerActor.userId === counterpartActor.userId;
1295
+ if (introducerActor && introducerActor.userId !== context.userId && !introducerIsCounterpart) {
1296
+ const narratorName = introducerName ?? "Someone";
1297
+ narratorChip = {
1298
+ name: narratorName,
1299
+ text: stripLeadingNarratorName(presentation.narratorRemark, narratorName),
1300
+ avatar: introducerUser?.avatar ?? null,
1301
+ userId: introducerActor.userId,
1302
+ };
1303
+ }
1304
+ else if (introducerActor?.userId === context.userId) {
1305
+ narratorChip = { name: "You", text: presentation.narratorRemark, userId: context.userId };
1306
+ }
1307
+ else {
1308
+ narratorChip = { name: "Index", text: presentation.narratorRemark };
1309
+ }
1310
+ const card = {
1311
+ opportunityId: opp.id,
1312
+ userId: counterpartUserId,
1313
+ name: counterpartName,
1314
+ avatar: counterpartUser?.avatar ?? null,
1315
+ mainText: stripUuids(presentation.personalizedSummary),
1316
+ cta: presentation.suggestedAction,
1317
+ headline: viewerIsIntroducerHere && secondPartyNameForHeadline
1318
+ ? `${counterpartName} → ${secondPartyNameForHeadline}`
1319
+ : presentation.headline,
1320
+ primaryActionLabel: getPrimaryActionLabel(viewerRole),
1321
+ secondaryActionLabel: SECONDARY_ACTION_LABEL,
1322
+ mutualIntentsLabel: presentation.mutualIntentsLabel,
1323
+ narratorChip,
1324
+ viewerRole,
1325
+ score: typeof opp.interpretation?.confidence === "number"
1326
+ ? opp.interpretation.confidence
1327
+ : undefined,
1328
+ status: opp.status,
1329
+ isGhost: isCounterpartGhost,
1330
+ ...(viewerIsIntroducerHere && secondPartyNameForHeadline
1331
+ ? {
1332
+ secondParty: {
1333
+ name: secondPartyNameForHeadline,
1334
+ ...(secondPartyUser?.avatar != null ? { avatar: secondPartyUser.avatar } : {}),
1335
+ ...(secondPartyActorForHeadline?.userId ? { userId: secondPartyActorForHeadline.userId } : {}),
1336
+ },
1337
+ }
1338
+ : {}),
1339
+ };
1340
+ // Attach actionable links for MCP callers
1341
+ if (context.isMcp && deps.mintConnectLink) {
1342
+ const viewerApproved = viewerActor?.role === "introducer" ? viewerActor.approved === true : undefined;
1343
+ await attachActionableLinks(card, {
1344
+ viewerId: context.userId,
1345
+ viewerApproved,
1346
+ counterpartUserId,
1347
+ mintConnectLink: deps.mintConnectLink,
1348
+ frontendUrl: deps.frontendUrl,
1349
+ preferredSurface: context.clientSurface,
1350
+ });
1351
+ }
1352
+ return card;
1353
+ }
1354
+ catch (presenterErr) {
1355
+ logger.warn("LLM presenter failed for list_opportunities digest card, using fallback", {
1356
+ opportunityId: opp.id,
1357
+ err: presenterErr,
1358
+ });
1359
+ // Attach links to fallback card too
1360
+ if (context.isMcp && deps.mintConnectLink) {
1361
+ const viewerApproved = viewerActor?.role === "introducer" ? viewerActor.approved === true : undefined;
1362
+ await attachActionableLinks(fallbackCard, {
1363
+ viewerId: context.userId,
1364
+ viewerApproved,
1365
+ counterpartUserId,
1366
+ mintConnectLink: deps.mintConnectLink,
1367
+ frontendUrl: deps.frontendUrl,
1368
+ preferredSurface: context.clientSurface,
1369
+ });
1370
+ }
1371
+ return fallbackCard;
1372
+ }
1373
+ }
1374
+ catch (err) {
1375
+ logger.warn("Skipping opportunity that failed to build card", {
1376
+ opportunityId: opp.id,
1377
+ err,
1378
+ });
1379
+ skippedIds.push(opp.id);
1380
+ return null;
1381
+ }
1382
+ }));
1383
+ for (const card of chunkCards) {
1384
+ if (card)
1385
+ cardDataList.push(card);
1386
+ }
1387
+ }
1388
+ }
1389
+ else {
1390
+ // ── Chat mode: fast heuristic path (no LLM) ──
1391
+ for (const opp of opportunities) {
1392
+ if (seenOpportunityIds.has(opp.id))
1244
1393
  continue;
1245
- const viewerIsIntroducerHere = opp.actors.some((a) => a.role === "introducer" && a.userId === context.userId);
1246
- const secondPartyActorForHeadline = viewerIsIntroducerHere
1247
- ? opp.actors.find((a) => a.userId !== context.userId &&
1248
- a.userId !== counterpartUserId &&
1249
- a.role !== "introducer")
1250
- : undefined;
1251
- const secondPartyNameForHeadline = secondPartyActorForHeadline
1252
- ? (profileMap.get(secondPartyActorForHeadline.userId)?.identity?.name ??
1253
- userMap.get(secondPartyActorForHeadline.userId)?.name ??
1254
- undefined)
1255
- : undefined;
1256
- const introducerActor = opp.actors.find((a) => a.role === "introducer" && a.userId !== context.userId);
1257
- const createdByName = opp.detection.createdByName;
1258
- const counterpartProfile = profileMap.get(counterpartUserId) ?? null;
1259
- const counterpartUser = userMap.get(counterpartUserId) ?? null;
1260
- const introducerProfile = introducerActor && !createdByName
1261
- ? profileMap.get(introducerActor.userId) ?? null
1262
- : null;
1263
- const counterpartName = counterpartProfile?.identity?.name ??
1264
- counterpartUser?.name ??
1265
- "Someone";
1266
- const introducerName = createdByName ??
1267
- (introducerActor ? introducerProfile?.identity?.name ?? null : null);
1268
- const introducerUser = introducerActor
1269
- ? userMap.get(introducerActor.userId) ?? null
1270
- : null;
1271
- const secondPartyUser = secondPartyActorForHeadline
1272
- ? userMap.get(secondPartyActorForHeadline.userId) ?? null
1273
- : null;
1274
- const cardData = buildMinimalOpportunityCard(opp, context.userId, counterpartUserId, counterpartName, counterpartUser?.avatar ?? null, introducerName, introducerUser?.avatar ?? null, viewerName, secondPartyNameForHeadline, secondPartyUser?.avatar ?? null, secondPartyActorForHeadline?.userId);
1275
- // For MCP callers (e.g. Edge Claw), mint a connect token and attach
1276
- // acceptUrl + profileUrl when the (status, viewerRole) is actionable
1277
- // for the viewer. Non-actionable combos (sender-on-draft,
1278
- // pending-on-introducer-waiting, rejected, etc.) deliberately get
1279
- // no link — the LLM would otherwise hallucinate `/api/.../connect`
1280
- // URLs from the exposed opportunityId.
1281
- if (context.isMcp && deps.mintConnectLink) {
1282
- const viewerActor = opp.actors.find((a) => a.userId === context.userId);
1283
- const viewerApproved = viewerActor?.role === "introducer" ? viewerActor.approved === true : undefined;
1284
- await attachActionableLinks(cardData, {
1285
- viewerId: context.userId,
1286
- viewerApproved,
1287
- counterpartUserId,
1288
- mintConnectLink: deps.mintConnectLink,
1289
- frontendUrl: deps.frontendUrl,
1290
- preferredSurface: context.clientSurface,
1394
+ seenOpportunityIds.add(opp.id);
1395
+ try {
1396
+ const counterpartActor = opp.actors.find((a) => a.userId !== context.userId && a.role !== "introducer");
1397
+ const counterpartUserId = counterpartActor?.userId;
1398
+ if (!counterpartUserId)
1399
+ continue;
1400
+ const viewerIsIntroducerHere = opp.actors.some((a) => a.role === "introducer" && a.userId === context.userId);
1401
+ const secondPartyActorForHeadline = viewerIsIntroducerHere
1402
+ ? opp.actors.find((a) => a.userId !== context.userId &&
1403
+ a.userId !== counterpartUserId &&
1404
+ a.role !== "introducer")
1405
+ : undefined;
1406
+ const secondPartyNameForHeadline = secondPartyActorForHeadline
1407
+ ? (profileMap.get(secondPartyActorForHeadline.userId)?.identity?.name ??
1408
+ userMap.get(secondPartyActorForHeadline.userId)?.name ??
1409
+ undefined)
1410
+ : undefined;
1411
+ const introducerActor = opp.actors.find((a) => a.role === "introducer" && a.userId !== context.userId);
1412
+ const createdByName = opp.detection.createdByName;
1413
+ const counterpartProfile = profileMap.get(counterpartUserId) ?? null;
1414
+ const counterpartUser = userMap.get(counterpartUserId) ?? null;
1415
+ const introducerProfile = introducerActor && !createdByName
1416
+ ? profileMap.get(introducerActor.userId) ?? null
1417
+ : null;
1418
+ const counterpartName = counterpartProfile?.identity?.name ??
1419
+ counterpartUser?.name ??
1420
+ "Someone";
1421
+ const introducerName = createdByName ??
1422
+ (introducerActor ? introducerProfile?.identity?.name ?? null : null);
1423
+ const introducerUser = introducerActor
1424
+ ? userMap.get(introducerActor.userId) ?? null
1425
+ : null;
1426
+ const secondPartyUser = secondPartyActorForHeadline
1427
+ ? userMap.get(secondPartyActorForHeadline.userId) ?? null
1428
+ : null;
1429
+ const cardData = buildMinimalOpportunityCard(opp, context.userId, counterpartUserId, counterpartName, counterpartUser?.avatar ?? null, introducerName, introducerUser?.avatar ?? null, viewerName, secondPartyNameForHeadline, secondPartyUser?.avatar ?? null, secondPartyActorForHeadline?.userId);
1430
+ // For MCP callers (e.g. Edge Claw), mint a connect token and attach
1431
+ // acceptUrl + profileUrl when the (status, viewerRole) is actionable
1432
+ // for the viewer. Non-actionable combos (sender-on-draft,
1433
+ // pending-on-introducer-waiting, rejected, etc.) deliberately get
1434
+ // no link — the LLM would otherwise hallucinate `/api/.../connect`
1435
+ // URLs from the exposed opportunityId.
1436
+ if (context.isMcp && deps.mintConnectLink) {
1437
+ const viewerActor = opp.actors.find((a) => a.userId === context.userId);
1438
+ const viewerApproved = viewerActor?.role === "introducer" ? viewerActor.approved === true : undefined;
1439
+ await attachActionableLinks(cardData, {
1440
+ viewerId: context.userId,
1441
+ viewerApproved,
1442
+ counterpartUserId,
1443
+ mintConnectLink: deps.mintConnectLink,
1444
+ frontendUrl: deps.frontendUrl,
1445
+ preferredSurface: context.clientSurface,
1446
+ });
1447
+ }
1448
+ cardDataList.push(cardData);
1449
+ }
1450
+ catch (err) {
1451
+ logger.warn("Skipping opportunity that failed to build minimal card", {
1452
+ opportunityId: opp.id,
1453
+ err,
1291
1454
  });
1455
+ skippedIds.push(opp.id);
1456
+ continue;
1292
1457
  }
1293
- cardDataList.push(cardData);
1294
- }
1295
- catch (err) {
1296
- logger.warn("Skipping opportunity that failed to build minimal card", {
1297
- opportunityId: opp.id,
1298
- err,
1299
- });
1300
- skippedIds.push(opp.id);
1301
- continue;
1302
1458
  }
1303
1459
  }
1304
1460
  const listDebugSteps = [];