@indexnetwork/protocol 4.5.0-rc.323.1 → 4.5.0-rc.325.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.
Files changed (130) hide show
  1. package/dist/agent/agent.tools.js +1 -1
  2. package/dist/agent/agent.tools.js.map +1 -1
  3. package/dist/chat/chat.interrupt.classifier.js +1 -1
  4. package/dist/chat/chat.interrupt.classifier.js.map +1 -1
  5. package/dist/chat/chat.suggester.js +3 -3
  6. package/dist/chat/chat.suggester.js.map +1 -1
  7. package/dist/chat/chat.title.generator.js +2 -2
  8. package/dist/chat/chat.title.generator.js.map +1 -1
  9. package/dist/contact/contact.tools.js +1 -1
  10. package/dist/contact/contact.tools.js.map +1 -1
  11. package/dist/enrichment/enrichment.graph.d.ts.map +1 -1
  12. package/dist/enrichment/enrichment.graph.js +9 -3
  13. package/dist/enrichment/enrichment.graph.js.map +1 -1
  14. package/dist/index.d.ts +3 -0
  15. package/dist/index.d.ts.map +1 -1
  16. package/dist/index.js +2 -0
  17. package/dist/index.js.map +1 -1
  18. package/dist/integration/integration.tools.js +1 -1
  19. package/dist/integration/integration.tools.js.map +1 -1
  20. package/dist/intent/intent.graph.d.ts.map +1 -1
  21. package/dist/intent/intent.graph.js +12 -10
  22. package/dist/intent/intent.graph.js.map +1 -1
  23. package/dist/intent/intent.indexer.d.ts.map +1 -1
  24. package/dist/intent/intent.indexer.js +4 -3
  25. package/dist/intent/intent.indexer.js.map +1 -1
  26. package/dist/intent/intent.inferrer.d.ts.map +1 -1
  27. package/dist/intent/intent.inferrer.js +2 -1
  28. package/dist/intent/intent.inferrer.js.map +1 -1
  29. package/dist/intent/intent.reconciler.d.ts.map +1 -1
  30. package/dist/intent/intent.reconciler.js +5 -4
  31. package/dist/intent/intent.reconciler.js.map +1 -1
  32. package/dist/intent/intent.verifier.d.ts.map +1 -1
  33. package/dist/intent/intent.verifier.js +4 -3
  34. package/dist/intent/intent.verifier.js.map +1 -1
  35. package/dist/maintenance/maintenance.graph.d.ts.map +1 -1
  36. package/dist/maintenance/maintenance.graph.js +27 -5
  37. package/dist/maintenance/maintenance.graph.js.map +1 -1
  38. package/dist/mcp/mcp.server.d.ts.map +1 -1
  39. package/dist/mcp/mcp.server.js +5 -4
  40. package/dist/mcp/mcp.server.js.map +1 -1
  41. package/dist/negotiation/insight.generator.js +2 -2
  42. package/dist/negotiation/insight.generator.js.map +1 -1
  43. package/dist/negotiation/negotiation.graph.d.ts.map +1 -1
  44. package/dist/negotiation/negotiation.graph.js +15 -11
  45. package/dist/negotiation/negotiation.graph.js.map +1 -1
  46. package/dist/negotiation/negotiation.tools.js +1 -1
  47. package/dist/negotiation/negotiation.tools.js.map +1 -1
  48. package/dist/network/network.recommender.d.ts.map +1 -1
  49. package/dist/network/network.recommender.js +5 -4
  50. package/dist/network/network.recommender.js.map +1 -1
  51. package/dist/network/network.tools.d.ts.map +1 -1
  52. package/dist/network/network.tools.js +4 -2
  53. package/dist/network/network.tools.js.map +1 -1
  54. package/dist/opportunity/feed/feed.graph.d.ts +17 -1
  55. package/dist/opportunity/feed/feed.graph.d.ts.map +1 -1
  56. package/dist/opportunity/feed/feed.graph.js +131 -27
  57. package/dist/opportunity/feed/feed.graph.js.map +1 -1
  58. package/dist/opportunity/feed/feed.state.d.ts +16 -0
  59. package/dist/opportunity/feed/feed.state.d.ts.map +1 -1
  60. package/dist/opportunity/feed/feed.state.js +10 -0
  61. package/dist/opportunity/feed/feed.state.js.map +1 -1
  62. package/dist/opportunity/opportunity.discover.d.ts +1 -1
  63. package/dist/opportunity/opportunity.discover.d.ts.map +1 -1
  64. package/dist/opportunity/opportunity.discover.js +12 -19
  65. package/dist/opportunity/opportunity.discover.js.map +1 -1
  66. package/dist/opportunity/opportunity.enricher.js +2 -2
  67. package/dist/opportunity/opportunity.enricher.js.map +1 -1
  68. package/dist/opportunity/opportunity.evaluator.d.ts.map +1 -1
  69. package/dist/opportunity/opportunity.evaluator.js +9 -7
  70. package/dist/opportunity/opportunity.evaluator.js.map +1 -1
  71. package/dist/opportunity/opportunity.graph.d.ts.map +1 -1
  72. package/dist/opportunity/opportunity.graph.js +123 -105
  73. package/dist/opportunity/opportunity.graph.js.map +1 -1
  74. package/dist/opportunity/opportunity.introducer.js +5 -5
  75. package/dist/opportunity/opportunity.introducer.js.map +1 -1
  76. package/dist/opportunity/opportunity.persist.js +2 -2
  77. package/dist/opportunity/opportunity.persist.js.map +1 -1
  78. package/dist/opportunity/opportunity.presenter.d.ts.map +1 -1
  79. package/dist/opportunity/opportunity.presenter.js +4 -2
  80. package/dist/opportunity/opportunity.presenter.js.map +1 -1
  81. package/dist/opportunity/opportunity.tools.d.ts.map +1 -1
  82. package/dist/opportunity/opportunity.tools.js +7 -5
  83. package/dist/opportunity/opportunity.tools.js.map +1 -1
  84. package/dist/opportunity/opportunity.utils.d.ts +4 -1
  85. package/dist/opportunity/opportunity.utils.d.ts.map +1 -1
  86. package/dist/opportunity/opportunity.utils.js +42 -11
  87. package/dist/opportunity/opportunity.utils.js.map +1 -1
  88. package/dist/premise/premise.analyzer.d.ts.map +1 -1
  89. package/dist/premise/premise.analyzer.js +3 -2
  90. package/dist/premise/premise.analyzer.js.map +1 -1
  91. package/dist/premise/premise.decomposer.d.ts.map +1 -1
  92. package/dist/premise/premise.decomposer.js +12 -3
  93. package/dist/premise/premise.decomposer.js.map +1 -1
  94. package/dist/premise/premise.graph.d.ts.map +1 -1
  95. package/dist/premise/premise.graph.js +19 -9
  96. package/dist/premise/premise.graph.js.map +1 -1
  97. package/dist/premise/premise.indexer.d.ts.map +1 -1
  98. package/dist/premise/premise.indexer.js +2 -1
  99. package/dist/premise/premise.indexer.js.map +1 -1
  100. package/dist/premise/premise.tools.d.ts.map +1 -1
  101. package/dist/premise/premise.tools.js +8 -4
  102. package/dist/premise/premise.tools.js.map +1 -1
  103. package/dist/questioner/questioner.ask.tool.d.ts.map +1 -1
  104. package/dist/questioner/questioner.ask.tool.js +2 -10
  105. package/dist/questioner/questioner.ask.tool.js.map +1 -1
  106. package/dist/questioner/questioner.env.d.ts +50 -0
  107. package/dist/questioner/questioner.env.d.ts.map +1 -0
  108. package/dist/questioner/questioner.env.js +76 -0
  109. package/dist/questioner/questioner.env.js.map +1 -0
  110. package/dist/questioner/questioner.types.d.ts +7 -0
  111. package/dist/questioner/questioner.types.d.ts.map +1 -1
  112. package/dist/questioner/questioner.types.js.map +1 -1
  113. package/dist/shared/agent/tool.factory.d.ts.map +1 -1
  114. package/dist/shared/agent/tool.factory.js +4 -2
  115. package/dist/shared/agent/tool.factory.js.map +1 -1
  116. package/dist/shared/agent/tool.registry.d.ts.map +1 -1
  117. package/dist/shared/agent/tool.registry.js +6 -4
  118. package/dist/shared/agent/tool.registry.js.map +1 -1
  119. package/dist/shared/agent/tool.runtime.js +1 -1
  120. package/dist/shared/agent/tool.runtime.js.map +1 -1
  121. package/dist/shared/interfaces/database.interface.d.ts +1 -1
  122. package/dist/shared/interfaces/database.interface.js.map +1 -1
  123. package/dist/shared/observability/log.d.ts +3 -2
  124. package/dist/shared/observability/log.d.ts.map +1 -1
  125. package/dist/shared/observability/log.js +72 -9
  126. package/dist/shared/observability/log.js.map +1 -1
  127. package/dist/shared/observability/protocol.logger.d.ts.map +1 -1
  128. package/dist/shared/observability/protocol.logger.js +5 -3
  129. package/dist/shared/observability/protocol.logger.js.map +1 -1
  130. package/package.json +1 -1
@@ -15,13 +15,20 @@ import { OpportunityPresenter, gatherPresenterContext } from '../opportunity.pre
15
15
  import { loadNegotiationContext } from '../negotiation-context.loader.js';
16
16
  import { HomeCategorizerAgent } from './feed.categorizer.js';
17
17
  import { canUserSeeOpportunity, isActionableForViewer, selectByComposition } from '../opportunity.utils.js';
18
- import { resolveHomeSectionIcon } from '../../shared/ui/lucide.icon-catalog.js';
18
+ import { resolveHomeSectionIcon, DEFAULT_HOME_SECTION_ICON } from '../../shared/ui/lucide.icon-catalog.js';
19
19
  import { getPrimaryActionLabel, SECONDARY_ACTION_LABEL } from '../opportunity.labels.js';
20
20
  import { safeFallbackSummary } from '../opportunity.safe-presentation.js';
21
21
  import { protocolLogger } from '../../shared/observability/protocol.logger.js';
22
22
  import { timed } from '../../shared/observability/performance.js';
23
23
  import { requestContext } from "../../shared/observability/request-context.js";
24
24
  const logger = protocolLogger('HomeGraph');
25
+ const checkCategorizerCacheLog = protocolLogger('HomeGraph:checkCategorizerCache');
26
+ const cacheCategorizerResultsLog = protocolLogger('HomeGraph:cacheCategorizerResults');
27
+ const checkPresenterCacheLog = protocolLogger('HomeGraph:checkPresenterCache');
28
+ const cachePresenterResultsLog = protocolLogger('HomeGraph:cachePresenterResults');
29
+ const categorizeDynamicallyLog = protocolLogger('HomeGraph:categorizeDynamically');
30
+ const normalizeAndSortLog = protocolLogger('HomeGraph:normalizeAndSort');
31
+ const generateCardTextLog = protocolLogger('HomeGraph:generateCardText');
25
32
  /** Default home-feed statuses: the lifecycle stages a viewer can act on today. */
26
33
  export const DEFAULT_HOME_STATUSES = ['latent', 'pending'];
27
34
  // Exhaustive registry — keys must cover every OpportunityStatus union member.
@@ -170,7 +177,42 @@ export class HomeGraphFactory {
170
177
  // Do not pass conversationId: home view excludes draft opportunities (chat-only drafts).
171
178
  const raw = await this.database.getOpportunitiesForUser(state.userId, options);
172
179
  const visible = raw.filter((opp) => canUserSeeOpportunity(opp.actors, opp.status, state.userId));
173
- const visibleForFeed = visible.filter((opp) => isActionableForViewer(opp.actors, opp.status, state.userId));
180
+ // Actionability only gates the live statuses a viewer could act on:
181
+ // latent/pending cards the viewer cannot act on are noise, but
182
+ // terminal/internal statuses (accepted, rejected, expired, negotiating,
183
+ // stalled, draft) are deliberate history — when a caller explicitly
184
+ // requests them via `statuses`, they must pass through (they are never
185
+ // actionable by rule 5, so filtering them here would return nothing).
186
+ // The requested-status membership check is defense-in-depth: rows
187
+ // outside the requested set are dropped even if the adapter drifts.
188
+ const requestedStatuses = new Set(statuses);
189
+ const visibleForFeed = visible.filter((opp) => {
190
+ if (!requestedStatuses.has(opp.status))
191
+ return false;
192
+ if (opp.status === 'latent' || opp.status === 'pending') {
193
+ return isActionableForViewer(opp.actors, opp.status, state.userId);
194
+ }
195
+ return true;
196
+ });
197
+ const explicitStatuses = (state.statuses?.length ?? 0) > 0;
198
+ if (explicitStatuses) {
199
+ // Lifecycle view (e.g. intent radar): newest-first so counterpart
200
+ // dedup keeps each person's most recent state (an accepted
201
+ // opportunity supersedes an older pending one), no composition
202
+ // capping — the caller wants the full pipeline up to `limit`.
203
+ const newestFirst = [...visibleForFeed].sort((a, b) => safeParseDate(b.updatedAt) - safeParseDate(a.updatedAt));
204
+ const seenIds = new Set();
205
+ const dedupedByCounterpart = newestFirst.filter((opp) => {
206
+ const counterpartIds = getUniqueCounterpartUserIds(opp, state.userId);
207
+ const hasOverlap = [...counterpartIds].some((id) => seenIds.has(id));
208
+ if (hasOverlap)
209
+ return false;
210
+ for (const id of counterpartIds)
211
+ seenIds.add(id);
212
+ return true;
213
+ });
214
+ return { opportunities: dedupedByCounterpart.slice(0, state.limit) };
215
+ }
174
216
  const sorted = [...visibleForFeed].sort((a, b) => {
175
217
  // Connections before connector-flow so dedup claims counterpart IDs
176
218
  // for direct connections first — prevents introducer cards from
@@ -213,7 +255,7 @@ export class HomeGraphFactory {
213
255
  return { cachedCards: new Map(), uncachedOpportunities: [] };
214
256
  }
215
257
  if (state.noCache) {
216
- logger.verbose('[HomeGraph:checkPresenterCache] noCache=true, skipping cache');
258
+ checkPresenterCacheLog.verbose('noCache=true, skipping cache');
217
259
  return { cachedCards: new Map(), uncachedOpportunities: opportunities };
218
260
  }
219
261
  try {
@@ -234,13 +276,15 @@ export class HomeGraphFactory {
234
276
  const cached = results[i];
235
277
  if (cached) {
236
278
  const originalIndex = opportunities.indexOf(cacheable[i]);
237
- cachedCards.set(cacheable[i].id, { ...cached, _cardIndex: originalIndex });
279
+ // Stamp the live status: pre-status cache entries lack the field,
280
+ // and the key already guarantees it matches the current status.
281
+ cachedCards.set(cacheable[i].id, { ...cached, status: cacheable[i].status, _cardIndex: originalIndex });
238
282
  }
239
283
  else {
240
284
  uncachedOpportunities.push(cacheable[i]);
241
285
  }
242
286
  }
243
- logger.verbose('[HomeGraph:checkPresenterCache]', {
287
+ checkPresenterCacheLog.verbose('', {
244
288
  total: opportunities.length,
245
289
  cacheHits: cachedCards.size,
246
290
  cacheMisses: uncachedOpportunities.length,
@@ -248,7 +292,7 @@ export class HomeGraphFactory {
248
292
  return { cachedCards, uncachedOpportunities };
249
293
  }
250
294
  catch (e) {
251
- logger.warn('[HomeGraph:checkPresenterCache] cache unavailable, skipping', { error: e });
295
+ checkPresenterCacheLog.warn('cache unavailable, skipping', { error: e });
252
296
  return { cachedCards: new Map(), uncachedOpportunities: opportunities };
253
297
  }
254
298
  });
@@ -257,7 +301,7 @@ export class HomeGraphFactory {
257
301
  if (state.uncachedOpportunities.length > 0) {
258
302
  return 'generate';
259
303
  }
260
- logger.verbose('[HomeGraph] All presenter results cached, skipping generation');
304
+ logger.verbose('All presenter results cached, skipping generation');
261
305
  return 'skip';
262
306
  };
263
307
  const generateCardTextNode = async (state) => {
@@ -265,9 +309,9 @@ export class HomeGraphFactory {
265
309
  const opportunities = state.uncachedOpportunities.length > 0
266
310
  ? state.uncachedOpportunities
267
311
  : state.opportunities;
268
- logger.verbose('[HomeGraph:generateCardText] entry', { opportunitiesLength: opportunities.length, userId: state.userId });
312
+ generateCardTextLog.verbose('entry', { opportunitiesLength: opportunities.length, userId: state.userId });
269
313
  if (opportunities.length === 0) {
270
- logger.verbose('[HomeGraph:generateCardText] exit', { totalOpportunities: 0, totalSections: 0 });
314
+ generateCardTextLog.verbose('exit', { totalOpportunities: 0, totalSections: 0 });
271
315
  return { cards: [], agentTimings: [], meta: { totalOpportunities: 0, totalSections: 0 } };
272
316
  }
273
317
  const db = this.database;
@@ -329,13 +373,26 @@ export class HomeGraphFactory {
329
373
  // Fallback to profile identity name when users.name is missing (e.g. profile has display name, users row does not)
330
374
  if ((userName === 'Unknown' || !userName?.trim()) && otherActor?.userId && db.getProfile) {
331
375
  const profile = await db.getProfile(otherActor.userId).catch((err) => {
332
- logger.debug('[HomeGraph] getProfile fallback failed', { otherActorUserId: otherActor.userId, error: err });
376
+ logger.debug('getProfile fallback failed', { otherActorUserId: otherActor.userId, error: err });
333
377
  return null;
334
378
  });
335
379
  const profileName = profile?.identity?.name?.trim();
336
380
  if (profileName)
337
381
  userName = profileName;
338
382
  }
383
+ // Unresolvable display counterpart (deleted user: no users row, no
384
+ // profile fallback). Drop the card entirely instead of rendering an
385
+ // "Unknown" placeholder: such cards are unusable, excluded from the
386
+ // presenter cache (see cachePresenterResults), and would otherwise
387
+ // trigger a fresh presenter LLM call on every request — a permanent
388
+ // cache miss that keeps the whole feed slow (~9s per load).
389
+ if (userName === 'Unknown' || !userName?.trim()) {
390
+ logger.verbose('[HomeGraph:generateCardText] dropping card with unresolvable counterpart', {
391
+ opportunityId: opportunity.id,
392
+ otherActorUserId: otherActor?.userId,
393
+ });
394
+ return null;
395
+ }
339
396
  const userAvatar = otherUser?.avatar ?? null;
340
397
  // Shared sanitization standard (UUID strip, viewer-centric rewrite,
341
398
  // boundary truncation) — raw reasoning must never render verbatim.
@@ -360,9 +417,33 @@ export class HomeGraphFactory {
360
417
  }
361
418
  }
362
419
  const isCounterpartGhost = otherUser?.isGhost ?? false;
420
+ // Skeleton presentation: return an identity-only card without the
421
+ // presenter LLM or negotiation-context load. Name resolution and
422
+ // the unresolvable-counterpart drop above still apply, so the card
423
+ // set matches what the follow-up full request will return.
424
+ if (state.presentation === 'skeleton') {
425
+ return {
426
+ opportunityId: opportunity.id,
427
+ status: opportunity.status,
428
+ userId: otherActor?.userId ?? '',
429
+ name: userName,
430
+ avatar: userAvatar,
431
+ mainText: '',
432
+ cta: '',
433
+ primaryActionLabel: getPrimaryActionLabel(viewerRole),
434
+ secondaryActionLabel: SECONDARY_ACTION_LABEL,
435
+ mutualIntentsLabel: isIntroducer ? 'Connector match' : 'Shared interests',
436
+ viewerRole,
437
+ isGhost: isCounterpartGhost,
438
+ ...(secondPartyData ? { secondParty: secondPartyData } : {}),
439
+ presentationPending: true,
440
+ _cardIndex: cardIndex,
441
+ };
442
+ }
363
443
  const isPendingIntroducerFallback = isIntroducer && opportunity.status !== 'latent';
364
444
  const fallbackCard = () => ({
365
445
  opportunityId: opportunity.id,
446
+ status: opportunity.status,
366
447
  userId: otherActor?.userId ?? '',
367
448
  name: userName,
368
449
  avatar: userAvatar,
@@ -421,6 +502,7 @@ export class HomeGraphFactory {
421
502
  }
422
503
  return {
423
504
  opportunityId: opportunity.id,
505
+ status: opportunity.status,
424
506
  userId: otherActor?.userId ?? '',
425
507
  name: userName,
426
508
  avatar: userAvatar,
@@ -442,9 +524,9 @@ export class HomeGraphFactory {
442
524
  return fallbackCard();
443
525
  }
444
526
  }));
445
- cards.push(...chunkCards);
527
+ cards.push(...chunkCards.filter((c) => c !== null));
446
528
  }
447
- logger.verbose('[HomeGraph:generateCardText] exit', { totalOpportunities: state.opportunities.length, totalSections: 0 });
529
+ generateCardTextLog.verbose('exit', { totalOpportunities: state.opportunities.length, totalSections: 0 });
448
530
  return {
449
531
  cards,
450
532
  agentTimings: agentTimingsAccum,
@@ -464,6 +546,10 @@ export class HomeGraphFactory {
464
546
  // Skip persisting negotiating cards — see read-side note.
465
547
  if (!status || status === 'negotiating')
466
548
  return Promise.resolve();
549
+ // Never cache skeleton cards — they carry no presenter text and
550
+ // would otherwise be served as "complete" for the full TTL.
551
+ if (card.presentationPending)
552
+ return Promise.resolve();
467
553
  // Skip caching cards with unresolved names — transient DB failures
468
554
  // would persist "Unknown" placeholders for the full TTL.
469
555
  if (!card.name || card.name === 'Unknown')
@@ -472,7 +558,7 @@ export class HomeGraphFactory {
472
558
  }));
473
559
  }
474
560
  catch (e) {
475
- logger.warn('[HomeGraph:cachePresenterResults] cache write failed, continuing', { error: e });
561
+ cachePresenterResultsLog.warn('cache write failed, continuing', { error: e });
476
562
  }
477
563
  // Merge cached cards into full card list
478
564
  const allCards = [...cards];
@@ -483,7 +569,7 @@ export class HomeGraphFactory {
483
569
  }
484
570
  // Re-sort by _cardIndex to maintain original ordering
485
571
  allCards.sort((a, b) => a._cardIndex - b._cardIndex);
486
- logger.verbose('[HomeGraph:cachePresenterResults]', {
572
+ cachePresenterResultsLog.verbose('', {
487
573
  newlyCached: newCards.length,
488
574
  totalCards: allCards.length,
489
575
  });
@@ -498,37 +584,55 @@ export class HomeGraphFactory {
498
584
  if (state.cards.length === 0) {
499
585
  return { categoryCacheHit: false };
500
586
  }
587
+ // Skeleton runs never categorize (some cards have no text to categorize
588
+ // and the response must stay LLM-free): emit flat sections and mark the
589
+ // categorizer as "hit" so the generate node and the cache write are both
590
+ // skipped. Chunked by MAX_ITEMS_PER_SECTION because normalizeAndSort
591
+ // caps every section — one giant section would silently drop cards that
592
+ // the follow-up full request (multiple sections) does return.
593
+ if (state.presentation === 'skeleton') {
594
+ const sectionProposals = [];
595
+ for (let start = 0; start < state.cards.length; start += MAX_ITEMS_PER_SECTION) {
596
+ sectionProposals.push({
597
+ id: `all-${sectionProposals.length + 1}`,
598
+ title: 'All matches',
599
+ iconName: DEFAULT_HOME_SECTION_ICON,
600
+ itemIndices: state.cards.slice(start, start + MAX_ITEMS_PER_SECTION).map((_, i) => start + i),
601
+ });
602
+ }
603
+ return { sectionProposals, categoryCacheHit: true };
604
+ }
501
605
  if (state.noCache) {
502
- logger.verbose('[HomeGraph:checkCategorizerCache] noCache=true, skipping cache');
606
+ checkCategorizerCacheLog.verbose('noCache=true, skipping cache');
503
607
  return { categoryCacheHit: false };
504
608
  }
505
609
  try {
506
610
  const key = buildCategorizerCacheKey(state.userId, state.cards);
507
611
  const cached = await this.cache.get(key);
508
612
  if (cached) {
509
- logger.verbose('[HomeGraph:checkCategorizerCache] cache hit');
613
+ checkCategorizerCacheLog.verbose('cache hit');
510
614
  return { sectionProposals: cached, categoryCacheHit: true };
511
615
  }
512
- logger.verbose('[HomeGraph:checkCategorizerCache] cache miss');
616
+ checkCategorizerCacheLog.verbose('cache miss');
513
617
  }
514
618
  catch (e) {
515
- logger.warn('[HomeGraph:checkCategorizerCache] cache unavailable, skipping', { error: e });
619
+ checkCategorizerCacheLog.warn('cache unavailable, skipping', { error: e });
516
620
  }
517
621
  return { categoryCacheHit: false };
518
622
  });
519
623
  };
520
624
  const shouldCategorize = (state) => {
521
625
  if (state.categoryCacheHit) {
522
- logger.verbose('[HomeGraph] Categorizer results cached, skipping');
626
+ logger.verbose('Categorizer results cached, skipping');
523
627
  return 'skip';
524
628
  }
525
629
  return 'categorize';
526
630
  };
527
631
  const categorizeDynamicallyNode = async (state) => {
528
632
  return timed("HomeGraph.categorizeDynamically", async () => {
529
- logger.verbose('[HomeGraph:categorizeDynamically] entry', { cardsLength: state.cards.length });
633
+ categorizeDynamicallyLog.verbose('entry', { cardsLength: state.cards.length });
530
634
  if (state.cards.length === 0) {
531
- logger.verbose('[HomeGraph:categorizeDynamically] exit', { sectionProposalsCount: 0 });
635
+ categorizeDynamicallyLog.verbose('exit', { sectionProposalsCount: 0 });
532
636
  return { sectionProposals: [], agentTimings: [] };
533
637
  }
534
638
  const agentTimingsAccum = [];
@@ -551,7 +655,7 @@ export class HomeGraphFactory {
551
655
  ...s,
552
656
  itemIndices: s.itemIndices.filter((i) => i >= 0 && i < state.cards.length),
553
657
  }));
554
- logger.verbose('[HomeGraph:categorizeDynamically] exit', { sectionProposalsCount: proposals.length });
658
+ categorizeDynamicallyLog.verbose('exit', { sectionProposalsCount: proposals.length });
555
659
  return { sectionProposals: proposals, agentTimings: agentTimingsAccum };
556
660
  });
557
661
  };
@@ -563,12 +667,12 @@ export class HomeGraphFactory {
563
667
  try {
564
668
  const key = buildCategorizerCacheKey(state.userId, state.cards);
565
669
  await this.cache.set(key, state.sectionProposals, { ttl: HOME_CACHE_TTL });
566
- logger.verbose('[HomeGraph:cacheCategorizerResults] cached', {
670
+ cacheCategorizerResultsLog.verbose('cached', {
567
671
  sectionCount: state.sectionProposals.length,
568
672
  });
569
673
  }
570
674
  catch (e) {
571
- logger.warn('[HomeGraph:cacheCategorizerResults] cache write failed, continuing', { error: e });
675
+ cacheCategorizerResultsLog.warn('cache write failed, continuing', { error: e });
572
676
  }
573
677
  return {};
574
678
  });
@@ -577,9 +681,9 @@ export class HomeGraphFactory {
577
681
  return timed("HomeGraph.normalizeAndSort", async () => {
578
682
  const cards = state.cards;
579
683
  const proposals = state.sectionProposals;
580
- logger.verbose('[HomeGraph:normalizeAndSort] entry', { cardsLength: cards.length, proposalsLength: proposals.length });
684
+ normalizeAndSortLog.verbose('entry', { cardsLength: cards.length, proposalsLength: proposals.length });
581
685
  if (cards.length === 0) {
582
- logger.verbose('[HomeGraph:normalizeAndSort] exit', { totalOpportunities: 0, totalSections: 0 });
686
+ normalizeAndSortLog.verbose('exit', { totalOpportunities: 0, totalSections: 0 });
583
687
  return { sections: [], meta: { totalOpportunities: 0, totalSections: 0 } };
584
688
  }
585
689
  const usedIndices = new Set();
@@ -619,7 +723,7 @@ export class HomeGraphFactory {
619
723
  totalOpportunities: state.opportunities.length,
620
724
  totalSections: sections.length,
621
725
  };
622
- logger.verbose('[HomeGraph:normalizeAndSort] exit', { totalOpportunities: meta.totalOpportunities, totalSections: meta.totalSections });
726
+ normalizeAndSortLog.verbose('exit', { totalOpportunities: meta.totalOpportunities, totalSections: meta.totalSections });
623
727
  return { sections, meta };
624
728
  });
625
729
  };