@indexnetwork/protocol 8.3.0-rc.424.1 → 8.4.0-rc.426.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/IMPLEMENTATION.md +2 -2
- package/dist/capabilities/opportunities.facade.d.ts +5 -5
- package/dist/capabilities/opportunities.facade.js +4 -4
- package/dist/index.d.ts +6 -6
- package/dist/index.js +4 -4
- package/dist/maintenance/maintenance.graph.d.ts +9 -9
- package/dist/maintenance/maintenance.graph.js +20 -20
- package/dist/maintenance/maintenance.state.d.ts +5 -5
- package/dist/maintenance/maintenance.state.js +3 -3
- package/dist/opportunity/application/delivery-card.cache.js +1 -1
- package/dist/opportunity/application/index.d.ts +2 -3
- package/dist/opportunity/application/index.js +3 -4
- package/dist/opportunity/application/negotiation-context.loader.d.ts +1 -1
- package/dist/opportunity/application/negotiation-context.loader.js +1 -1
- package/dist/opportunity/application/opportunity.enricher.js +2 -2
- package/dist/opportunity/application/opportunity.presenter.d.ts +15 -15
- package/dist/opportunity/application/opportunity.presenter.js +17 -17
- package/dist/opportunity/application/opportunity.tools.d.ts +1 -1
- package/dist/opportunity/application/opportunity.tools.js +3 -3
- package/dist/opportunity/discriminator/discriminator.adjustments.js +1 -1
- package/dist/opportunity/discriminator/discriminator.env.d.ts +1 -1
- package/dist/opportunity/discriminator/discriminator.env.js +1 -1
- package/dist/opportunity/domain/index.d.ts +3 -3
- package/dist/opportunity/domain/index.js +3 -3
- package/dist/opportunity/domain/opportunity.presentation-cache.d.ts +1 -2
- package/dist/opportunity/domain/opportunity.presentation-cache.js +2 -5
- package/dist/opportunity/domain/opportunity.presentation.d.ts +1 -1
- package/dist/opportunity/domain/opportunity.presentation.js +1 -1
- package/dist/opportunity/domain/opportunity.safe-presentation.d.ts +2 -2
- package/dist/opportunity/domain/opportunity.safe-presentation.js +1 -1
- package/dist/opportunity/domain/opportunity.utils.d.ts +5 -5
- package/dist/opportunity/domain/opportunity.utils.js +8 -8
- package/dist/opportunity/index.d.ts +1 -1
- package/dist/opportunity/index.js +1 -1
- package/dist/opportunity/opportunity.presenter.d.ts +3 -3
- package/dist/opportunity/opportunity.presenter.js +2 -2
- package/dist/opportunity/public/index.d.ts +5 -5
- package/dist/opportunity/public/index.js +6 -6
- package/dist/opportunity/{feed/feed.graph.d.ts → radar/radar.graph.d.ts} +42 -73
- package/dist/opportunity/{feed/feed.graph.js → radar/radar.graph.js} +60 -222
- package/dist/opportunity/{feed/feed.health.d.ts → radar/radar.health.d.ts} +7 -7
- package/dist/opportunity/{feed/feed.health.js → radar/radar.health.js} +8 -8
- package/dist/opportunity/{feed/feed.state.d.ts → radar/radar.state.d.ts} +21 -47
- package/dist/opportunity/{feed/feed.state.js → radar/radar.state.js} +15 -24
- package/dist/shared/agent/tool.helpers.d.ts +1 -1
- package/dist/shared/interfaces/database.interface.d.ts +3 -3
- package/package.json +1 -1
- package/dist/opportunity/feed/feed.categorizer.d.ts +0 -27
- package/dist/opportunity/feed/feed.categorizer.js +0 -168
- package/dist/shared/ui/lucide.icon-catalog.d.ts +0 -21
- package/dist/shared/ui/lucide.icon-catalog.js +0 -100
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import type { Opportunity, OpportunityStatus } from '../../shared/interfaces/database.interface.js';
|
|
2
2
|
import type { DebugMetaAgent } from '../../capabilities/participant-agents.debug.facade.js';
|
|
3
3
|
/**
|
|
4
|
-
*
|
|
4
|
+
* Radar card item: one opportunity with full presenter-driven display contract.
|
|
5
5
|
*/
|
|
6
|
-
export interface
|
|
6
|
+
export interface RadarCardItem {
|
|
7
7
|
opportunityId: string;
|
|
8
8
|
/** Lifecycle status of the underlying opportunity at render time (client bucketing, e.g. intent radar). */
|
|
9
9
|
status?: OpportunityStatus;
|
|
@@ -46,77 +46,51 @@ export interface HomeCardItem {
|
|
|
46
46
|
presentationPending?: boolean;
|
|
47
47
|
/** Internal marker: safe deterministic fallback rendered, but must not be cached. */
|
|
48
48
|
_presentationFallback?: boolean;
|
|
49
|
-
/**
|
|
49
|
+
/** Internal: original position in the loaded opportunity list, for stable ordering. */
|
|
50
50
|
_cardIndex: number;
|
|
51
51
|
}
|
|
52
|
+
/** Card item as returned in API responses (no internal fields). */
|
|
53
|
+
export type RadarResponseItem = Omit<RadarCardItem, '_cardIndex' | '_presentationFallback'>;
|
|
52
54
|
/**
|
|
53
|
-
*
|
|
55
|
+
* Radar Graph State (Annotation-based).
|
|
56
|
+
* Flow: loadOpportunities → checkPresenterCache → [generateCardText if misses]
|
|
57
|
+
* → cachePresenterResults → normalizeItems.
|
|
54
58
|
*/
|
|
55
|
-
export
|
|
56
|
-
id: string;
|
|
57
|
-
title: string;
|
|
58
|
-
subtitle?: string;
|
|
59
|
-
iconName: string;
|
|
60
|
-
itemIndices: number[];
|
|
61
|
-
}
|
|
62
|
-
/** Card item as returned in API (no internal _cardIndex). */
|
|
63
|
-
export type HomeSectionItem = Omit<HomeCardItem, '_cardIndex' | '_presentationFallback'>;
|
|
64
|
-
/**
|
|
65
|
-
* Final section for API response.
|
|
66
|
-
*/
|
|
67
|
-
export interface HomeSection {
|
|
68
|
-
id: string;
|
|
69
|
-
title: string;
|
|
70
|
-
subtitle?: string;
|
|
71
|
-
iconName: string;
|
|
72
|
-
items: HomeSectionItem[];
|
|
73
|
-
}
|
|
74
|
-
/**
|
|
75
|
-
* Home Graph State (Annotation-based).
|
|
76
|
-
* Flow: loadOpportunities → generateCardText → categorizeDynamically → normalizeAndSort → finalizeResponse.
|
|
77
|
-
*/
|
|
78
|
-
export declare const HomeGraphState: import("@langchain/langgraph").AnnotationRoot<{
|
|
59
|
+
export declare const RadarGraphState: import("@langchain/langgraph").AnnotationRoot<{
|
|
79
60
|
userId: import("@langchain/langgraph").BaseChannel<string, string | import("@langchain/langgraph").OverwriteValue<string>, unknown>;
|
|
80
61
|
networkId: import("@langchain/langgraph").BaseChannel<string | undefined, string | import("@langchain/langgraph").OverwriteValue<string | undefined> | undefined, unknown>;
|
|
81
62
|
scopeType: import("@langchain/langgraph").BaseChannel<"intent" | undefined, "intent" | import("@langchain/langgraph").OverwriteValue<"intent" | undefined> | undefined, unknown>;
|
|
82
63
|
scopeId: import("@langchain/langgraph").BaseChannel<string | undefined, string | import("@langchain/langgraph").OverwriteValue<string | undefined> | undefined, unknown>;
|
|
83
64
|
limit: import("@langchain/langgraph").BaseChannel<number, number | import("@langchain/langgraph").OverwriteValue<number>, unknown>;
|
|
84
|
-
/** When true, bypass presenter
|
|
65
|
+
/** When true, bypass the presenter Redis cache. */
|
|
85
66
|
noCache: import("@langchain/langgraph").BaseChannel<boolean, boolean | import("@langchain/langgraph").OverwriteValue<boolean>, unknown>;
|
|
86
67
|
/**
|
|
87
|
-
* Presentation depth. 'full' (default) runs the presenter LLM
|
|
88
|
-
* 'skeleton' skips
|
|
89
|
-
*
|
|
90
|
-
*
|
|
68
|
+
* Presentation depth. 'full' (default) runs the presenter LLM for cache
|
|
69
|
+
* misses. 'skeleton' skips it: uncached cards come back with resolved
|
|
70
|
+
* identity (name/avatar/status) and `presentationPending: true`, cached
|
|
71
|
+
* cards come back complete.
|
|
91
72
|
*/
|
|
92
73
|
presentation: import("@langchain/langgraph").BaseChannel<"full" | "skeleton", "full" | "skeleton" | import("@langchain/langgraph").OverwriteValue<"full" | "skeleton">, unknown>;
|
|
93
|
-
/** Optional status filter. When undefined, the graph uses `
|
|
74
|
+
/** Optional status filter. When undefined, the graph uses `DEFAULT_RADAR_STATUSES`. */
|
|
94
75
|
statuses: import("@langchain/langgraph").BaseChannel<OpportunityStatus[] | undefined, OpportunityStatus[] | import("@langchain/langgraph").OverwriteValue<OpportunityStatus[] | undefined> | undefined, unknown>;
|
|
95
76
|
/** Raw opportunities visible to the viewer (after visibility filter). */
|
|
96
77
|
opportunities: import("@langchain/langgraph").BaseChannel<Opportunity[], Opportunity[] | import("@langchain/langgraph").OverwriteValue<Opportunity[]>, unknown>;
|
|
97
78
|
/** Cards with presenter output and narrator chip. */
|
|
98
|
-
cards: import("@langchain/langgraph").BaseChannel<
|
|
99
|
-
/**
|
|
100
|
-
|
|
101
|
-
/**
|
|
102
|
-
|
|
103
|
-
/** Presenter results retrieved from cache (opportunityId → HomeCardItem). */
|
|
104
|
-
cachedCards: import("@langchain/langgraph").BaseChannel<Map<string, HomeCardItem>, Map<string, HomeCardItem> | import("@langchain/langgraph").OverwriteValue<Map<string, HomeCardItem>>, unknown>;
|
|
79
|
+
cards: import("@langchain/langgraph").BaseChannel<RadarCardItem[], RadarCardItem[] | import("@langchain/langgraph").OverwriteValue<RadarCardItem[]>, unknown>;
|
|
80
|
+
/** Final items for response (internal fields stripped). */
|
|
81
|
+
items: import("@langchain/langgraph").BaseChannel<RadarResponseItem[], RadarResponseItem[] | import("@langchain/langgraph").OverwriteValue<RadarResponseItem[]>, unknown>;
|
|
82
|
+
/** Presenter results retrieved from cache (opportunityId → RadarCardItem). */
|
|
83
|
+
cachedCards: import("@langchain/langgraph").BaseChannel<Map<string, RadarCardItem>, Map<string, RadarCardItem> | import("@langchain/langgraph").OverwriteValue<Map<string, RadarCardItem>>, unknown>;
|
|
105
84
|
/** Opportunities that had no cache hit and need presenter generation. */
|
|
106
85
|
uncachedOpportunities: import("@langchain/langgraph").BaseChannel<Opportunity[], Opportunity[] | import("@langchain/langgraph").OverwriteValue<Opportunity[]>, unknown>;
|
|
107
|
-
/** Whether categorizer results were found in cache. */
|
|
108
|
-
categoryCacheHit: import("@langchain/langgraph").BaseChannel<boolean, boolean | import("@langchain/langgraph").OverwriteValue<boolean>, unknown>;
|
|
109
86
|
error: import("@langchain/langgraph").BaseChannel<string | undefined, string | import("@langchain/langgraph").OverwriteValue<string | undefined> | undefined, unknown>;
|
|
110
|
-
/** Meta for response (e.g. totalOpportunities
|
|
87
|
+
/** Meta for response (e.g. totalOpportunities). */
|
|
111
88
|
meta: import("@langchain/langgraph").BaseChannel<{
|
|
112
89
|
totalOpportunities: number;
|
|
113
|
-
totalSections: number;
|
|
114
90
|
}, {
|
|
115
91
|
totalOpportunities: number;
|
|
116
|
-
totalSections: number;
|
|
117
92
|
} | import("@langchain/langgraph").OverwriteValue<{
|
|
118
93
|
totalOpportunities: number;
|
|
119
|
-
totalSections: number;
|
|
120
94
|
}>, unknown>;
|
|
121
95
|
/** Timing records for each agent invocation within this graph run. */
|
|
122
96
|
agentTimings: import("@langchain/langgraph").BaseChannel<DebugMetaAgent[], DebugMetaAgent[] | import("@langchain/langgraph").OverwriteValue<DebugMetaAgent[]>, unknown>;
|
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
import { Annotation } from '@langchain/langgraph';
|
|
2
2
|
/**
|
|
3
|
-
*
|
|
4
|
-
* Flow: loadOpportunities →
|
|
3
|
+
* Radar Graph State (Annotation-based).
|
|
4
|
+
* Flow: loadOpportunities → checkPresenterCache → [generateCardText if misses]
|
|
5
|
+
* → cachePresenterResults → normalizeItems.
|
|
5
6
|
*/
|
|
6
|
-
export const
|
|
7
|
+
export const RadarGraphState = Annotation.Root({
|
|
7
8
|
userId: Annotation({
|
|
8
9
|
reducer: (curr, next) => next ?? curr,
|
|
9
10
|
default: () => '',
|
|
@@ -24,22 +25,22 @@ export const HomeGraphState = Annotation.Root({
|
|
|
24
25
|
reducer: (curr, next) => next ?? curr,
|
|
25
26
|
default: () => 50,
|
|
26
27
|
}),
|
|
27
|
-
/** When true, bypass presenter
|
|
28
|
+
/** When true, bypass the presenter Redis cache. */
|
|
28
29
|
noCache: Annotation({
|
|
29
30
|
reducer: (curr, next) => next ?? curr,
|
|
30
31
|
default: () => false,
|
|
31
32
|
}),
|
|
32
33
|
/**
|
|
33
|
-
* Presentation depth. 'full' (default) runs the presenter LLM
|
|
34
|
-
* 'skeleton' skips
|
|
35
|
-
*
|
|
36
|
-
*
|
|
34
|
+
* Presentation depth. 'full' (default) runs the presenter LLM for cache
|
|
35
|
+
* misses. 'skeleton' skips it: uncached cards come back with resolved
|
|
36
|
+
* identity (name/avatar/status) and `presentationPending: true`, cached
|
|
37
|
+
* cards come back complete.
|
|
37
38
|
*/
|
|
38
39
|
presentation: Annotation({
|
|
39
40
|
reducer: (curr, next) => next ?? curr,
|
|
40
41
|
default: () => 'full',
|
|
41
42
|
}),
|
|
42
|
-
/** Optional status filter. When undefined, the graph uses `
|
|
43
|
+
/** Optional status filter. When undefined, the graph uses `DEFAULT_RADAR_STATUSES`. */
|
|
43
44
|
statuses: Annotation({
|
|
44
45
|
reducer: (curr, next) => next ?? curr,
|
|
45
46
|
default: () => undefined,
|
|
@@ -54,17 +55,12 @@ export const HomeGraphState = Annotation.Root({
|
|
|
54
55
|
reducer: (curr, next) => next ?? curr,
|
|
55
56
|
default: () => [],
|
|
56
57
|
}),
|
|
57
|
-
/**
|
|
58
|
-
|
|
58
|
+
/** Final items for response (internal fields stripped). */
|
|
59
|
+
items: Annotation({
|
|
59
60
|
reducer: (curr, next) => next ?? curr,
|
|
60
61
|
default: () => [],
|
|
61
62
|
}),
|
|
62
|
-
/**
|
|
63
|
-
sections: Annotation({
|
|
64
|
-
reducer: (curr, next) => next ?? curr,
|
|
65
|
-
default: () => [],
|
|
66
|
-
}),
|
|
67
|
-
/** Presenter results retrieved from cache (opportunityId → HomeCardItem). */
|
|
63
|
+
/** Presenter results retrieved from cache (opportunityId → RadarCardItem). */
|
|
68
64
|
cachedCards: Annotation({
|
|
69
65
|
reducer: (curr, next) => next ?? curr,
|
|
70
66
|
default: () => new Map(),
|
|
@@ -74,19 +70,14 @@ export const HomeGraphState = Annotation.Root({
|
|
|
74
70
|
reducer: (curr, next) => next ?? curr,
|
|
75
71
|
default: () => [],
|
|
76
72
|
}),
|
|
77
|
-
/** Whether categorizer results were found in cache. */
|
|
78
|
-
categoryCacheHit: Annotation({
|
|
79
|
-
reducer: (curr, next) => next ?? curr,
|
|
80
|
-
default: () => false,
|
|
81
|
-
}),
|
|
82
73
|
error: Annotation({
|
|
83
74
|
reducer: (curr, next) => next ?? curr,
|
|
84
75
|
default: () => undefined,
|
|
85
76
|
}),
|
|
86
|
-
/** Meta for response (e.g. totalOpportunities
|
|
77
|
+
/** Meta for response (e.g. totalOpportunities). */
|
|
87
78
|
meta: Annotation({
|
|
88
79
|
reducer: (curr, next) => next ?? curr,
|
|
89
|
-
default: () => ({ totalOpportunities: 0
|
|
80
|
+
default: () => ({ totalOpportunities: 0 }),
|
|
90
81
|
}),
|
|
91
82
|
/** Timing records for each agent invocation within this graph run. */
|
|
92
83
|
agentTimings: Annotation({
|
|
@@ -425,7 +425,7 @@ interface ToolDepsBindings {
|
|
|
425
425
|
*/
|
|
426
426
|
opportunityPresentation?: {
|
|
427
427
|
createPresenter?: () => {
|
|
428
|
-
|
|
428
|
+
presentCard(input: unknown): Promise<unknown>;
|
|
429
429
|
};
|
|
430
430
|
gatherPresenterContext?: (...args: unknown[]) => Promise<unknown>;
|
|
431
431
|
};
|
|
@@ -537,7 +537,7 @@ export interface OpportunityQueryOptions {
|
|
|
537
537
|
role?: string;
|
|
538
538
|
limit?: number;
|
|
539
539
|
offset?: number;
|
|
540
|
-
/** When set, include draft opportunities for this chat session. When unset, exclude all draft opportunities (e.g.
|
|
540
|
+
/** When set, include draft opportunities for this chat session. When unset, exclude all draft opportunities (e.g. radar view, API). */
|
|
541
541
|
conversationId?: string;
|
|
542
542
|
}
|
|
543
543
|
/**
|
|
@@ -2284,9 +2284,9 @@ export type NetworkMembershipGraphDatabase = Pick<Database, 'isNetworkMember' |
|
|
|
2284
2284
|
*/
|
|
2285
2285
|
export type HydeGraphDatabase = Pick<Database, 'getHydeDocument' | 'getHydeDocumentsForSource' | 'saveHydeDocument' | 'getIntent'>;
|
|
2286
2286
|
/**
|
|
2287
|
-
* Database interface for
|
|
2287
|
+
* Database interface for Radar Graph (opportunity radar view).
|
|
2288
2288
|
* Load opportunities, enrich with profile/index, and support presenter context.
|
|
2289
2289
|
*
|
|
2290
2290
|
* Access layer: UserDatabase (own opportunities and profile)
|
|
2291
2291
|
*/
|
|
2292
|
-
export type
|
|
2292
|
+
export type RadarGraphDatabase = Pick<Database, 'getOpportunitiesForUser' | 'getOpportunity' | 'getProfile' | 'getActiveIntents' | 'getNetwork' | 'getUser'> & Pick<NegotiationGraphDatabase, 'getNegotiationTaskForOpportunity' | 'getMessagesForConversation' | 'getArtifactsForTask'>;
|
package/package.json
CHANGED
|
@@ -1,27 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Home Categorizer Agent
|
|
3
|
-
*
|
|
4
|
-
* Takes a list of presenter-produced opportunity cards and returns dynamic sections
|
|
5
|
-
* with CTA-style titles and Lucide icon names. Used by the home graph after
|
|
6
|
-
* generateCardText.
|
|
7
|
-
*/
|
|
8
|
-
import type { HomeSectionProposal } from './feed.state.js';
|
|
9
|
-
export type CategorizerInputItem = {
|
|
10
|
-
index: number;
|
|
11
|
-
headline?: string;
|
|
12
|
-
mainText: string;
|
|
13
|
-
name: string;
|
|
14
|
-
viewerRole?: string;
|
|
15
|
-
opportunityStatus?: string;
|
|
16
|
-
};
|
|
17
|
-
export type CategorizerResult = {
|
|
18
|
-
sections: HomeSectionProposal[];
|
|
19
|
-
};
|
|
20
|
-
export declare class HomeCategorizerAgent {
|
|
21
|
-
private model;
|
|
22
|
-
constructor();
|
|
23
|
-
/**
|
|
24
|
-
* Categorize presenter-produced cards into 1–5 sections with CTA-style titles and icons.
|
|
25
|
-
*/
|
|
26
|
-
categorize(cards: CategorizerInputItem[]): Promise<CategorizerResult>;
|
|
27
|
-
}
|
|
@@ -1,168 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Home Categorizer Agent
|
|
3
|
-
*
|
|
4
|
-
* Takes a list of presenter-produced opportunity cards and returns dynamic sections
|
|
5
|
-
* with CTA-style titles and Lucide icon names. Used by the home graph after
|
|
6
|
-
* generateCardText.
|
|
7
|
-
*/
|
|
8
|
-
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
|
|
9
|
-
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
10
|
-
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
11
|
-
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
|
|
12
|
-
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
13
|
-
};
|
|
14
|
-
var __metadata = (this && this.__metadata) || function (k, v) {
|
|
15
|
-
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
|
|
16
|
-
};
|
|
17
|
-
import { HumanMessage, SystemMessage } from '@langchain/core/messages';
|
|
18
|
-
import { z } from 'zod';
|
|
19
|
-
import { getIconNamesForPrompt, DEFAULT_HOME_SECTION_ICON } from '../../shared/ui/lucide.icon-catalog.js';
|
|
20
|
-
import { protocolLogger } from '../../shared/observability/protocol.logger.js';
|
|
21
|
-
import { Timed } from "../../shared/observability/performance.js";
|
|
22
|
-
import { createStructuredModel } from "../../shared/agent/model.config.js";
|
|
23
|
-
import { invokeWithAbortSignal } from "../../shared/agent/model-signal.js";
|
|
24
|
-
import { hasUnsupportedOpportunityClaim } from '../opportunity.claim-safety.js';
|
|
25
|
-
const logger = protocolLogger('HomeCategorizer');
|
|
26
|
-
const categorizationSchema = z.object({
|
|
27
|
-
sections: z.array(z.object({
|
|
28
|
-
id: z.string().describe('Short kebab-case id for the section'),
|
|
29
|
-
title: z
|
|
30
|
-
.string()
|
|
31
|
-
.describe('Call-to-action style section heading (e.g. REACH OUT NOW, YOUR MOVE, CONNECTIONS WAITING FOR YOU)'),
|
|
32
|
-
subtitle: z.string().nullable().describe('Optional one-line description; use null if none'),
|
|
33
|
-
iconName: z.string().describe('Lucide icon name in kebab-case from the allowed list'),
|
|
34
|
-
itemIndices: z.array(z.number()).describe('Indices of cards in this section (0-based)'),
|
|
35
|
-
})),
|
|
36
|
-
});
|
|
37
|
-
const UNCATEGORIZED_SECTION_ID = 'uncategorized';
|
|
38
|
-
const UNCATEGORIZED_SECTION_TITLE = 'UNCATEGORIZED OPPORTUNITIES';
|
|
39
|
-
/** Single-section fallback grouping every card under one generic heading. */
|
|
40
|
-
function buildFallbackResult(cards) {
|
|
41
|
-
return {
|
|
42
|
-
sections: [
|
|
43
|
-
{
|
|
44
|
-
id: 'opportunities',
|
|
45
|
-
title: 'OPPORTUNITIES',
|
|
46
|
-
iconName: DEFAULT_HOME_SECTION_ICON,
|
|
47
|
-
itemIndices: cards.map((c) => c.index),
|
|
48
|
-
},
|
|
49
|
-
],
|
|
50
|
-
};
|
|
51
|
-
}
|
|
52
|
-
/**
|
|
53
|
-
* Build card summaries for the categorizer prompt.
|
|
54
|
-
*/
|
|
55
|
-
function buildCardSummaries(cards) {
|
|
56
|
-
return cards
|
|
57
|
-
.map((c) => {
|
|
58
|
-
const role = c.viewerRole ?? 'party';
|
|
59
|
-
const status = c.opportunityStatus ?? 'pending';
|
|
60
|
-
return `[${c.index}] role=${role}; status=${status}; ${c.headline ?? c.mainText.slice(0, 60)}... (${c.name})`;
|
|
61
|
-
})
|
|
62
|
-
.join('\n');
|
|
63
|
-
}
|
|
64
|
-
function reconcileSections(sections, maxIndex) {
|
|
65
|
-
const assigned = new Set();
|
|
66
|
-
const normalizedSections = sections.map((section) => {
|
|
67
|
-
const uniqueIndices = Array.from(new Set(section.itemIndices.filter((index) => index >= 0 && index <= maxIndex))).sort((a, b) => a - b);
|
|
68
|
-
for (const index of uniqueIndices) {
|
|
69
|
-
assigned.add(index);
|
|
70
|
-
}
|
|
71
|
-
return {
|
|
72
|
-
...section,
|
|
73
|
-
itemIndices: uniqueIndices,
|
|
74
|
-
};
|
|
75
|
-
});
|
|
76
|
-
const missingIndices = [];
|
|
77
|
-
for (let index = 0; index <= maxIndex; index += 1) {
|
|
78
|
-
if (!assigned.has(index)) {
|
|
79
|
-
missingIndices.push(index);
|
|
80
|
-
}
|
|
81
|
-
}
|
|
82
|
-
if (missingIndices.length === 0) {
|
|
83
|
-
return normalizedSections;
|
|
84
|
-
}
|
|
85
|
-
const fallbackSection = normalizedSections.find((section) => section.id === UNCATEGORIZED_SECTION_ID);
|
|
86
|
-
if (fallbackSection) {
|
|
87
|
-
fallbackSection.itemIndices = Array.from(new Set([...fallbackSection.itemIndices, ...missingIndices])).sort((a, b) => a - b);
|
|
88
|
-
return normalizedSections;
|
|
89
|
-
}
|
|
90
|
-
return [
|
|
91
|
-
...normalizedSections,
|
|
92
|
-
{
|
|
93
|
-
id: UNCATEGORIZED_SECTION_ID,
|
|
94
|
-
title: UNCATEGORIZED_SECTION_TITLE,
|
|
95
|
-
iconName: DEFAULT_HOME_SECTION_ICON,
|
|
96
|
-
itemIndices: missingIndices,
|
|
97
|
-
},
|
|
98
|
-
];
|
|
99
|
-
}
|
|
100
|
-
export class HomeCategorizerAgent {
|
|
101
|
-
constructor() {
|
|
102
|
-
this.model = createStructuredModel("homeCategorizer", categorizationSchema, { name: 'home_sections' });
|
|
103
|
-
}
|
|
104
|
-
/**
|
|
105
|
-
* Categorize presenter-produced cards into 1–5 sections with CTA-style titles and icons.
|
|
106
|
-
*/
|
|
107
|
-
async categorize(cards) {
|
|
108
|
-
if (cards.length === 0) {
|
|
109
|
-
return { sections: [] };
|
|
110
|
-
}
|
|
111
|
-
const iconList = getIconNamesForPrompt();
|
|
112
|
-
const cardSummaries = buildCardSummaries(cards);
|
|
113
|
-
const maxIndex = cards.length - 1;
|
|
114
|
-
const systemPrompt = `You are organizing connection opportunities into dynamic sections for a user's home feed.
|
|
115
|
-
|
|
116
|
-
Given a list of opportunity cards (each with index, headline/summary, and name), group them into 1–5 sections.
|
|
117
|
-
|
|
118
|
-
Each section must have:
|
|
119
|
-
- id: short kebab-case identifier (e.g. waiting-for-action, your-perspective, connector)
|
|
120
|
-
- title: CALL-TO-ACTION style section heading in uppercase. Write as an action or invitation, not a neutral label. Examples: REACH OUT NOW, YOUR MOVE, CONNECTIONS WAITING FOR YOU, INTROS READY TO SEND, PEOPLE YOU SHOULD MEET
|
|
121
|
-
- subtitle: optional one-line description
|
|
122
|
-
- iconName: exactly ONE icon from this list (use the name as-is): ${iconList}
|
|
123
|
-
- itemIndices: array of card indices that belong in this section (each index 0 to ${maxIndex} at most once)
|
|
124
|
-
|
|
125
|
-
Rules:
|
|
126
|
-
- Use only icon names from the list above.
|
|
127
|
-
- Every card index must appear in exactly one section.
|
|
128
|
-
- Section titles must read as calls-to-action or invitations, not passive labels.
|
|
129
|
-
- If a card has role=introducer and status=pending, prioritize grouping those cards under a connector curation section with a CTA title like "YOU'RE THE CONNECTOR THEY NEED", "MAKE THE INTRO", or "DECIDE IF IT'S A GOOD MATCH".`;
|
|
130
|
-
const userContent = `Cards to categorize:\n${cardSummaries}\n\nOutput sections with id, title (CTA-style), subtitle (optional), iconName, and itemIndices.`;
|
|
131
|
-
try {
|
|
132
|
-
const result = await invokeWithAbortSignal(this.model, [
|
|
133
|
-
new SystemMessage(systemPrompt),
|
|
134
|
-
new HumanMessage(userContent),
|
|
135
|
-
]);
|
|
136
|
-
const parsed = categorizationSchema.safeParse(result);
|
|
137
|
-
if (!parsed.success) {
|
|
138
|
-
logger.warn('HomeCategorizer parse failed', { error: parsed.error });
|
|
139
|
-
return buildFallbackResult(cards);
|
|
140
|
-
}
|
|
141
|
-
const sections = parsed.data.sections.map((s) => ({
|
|
142
|
-
id: s.id,
|
|
143
|
-
title: s.title,
|
|
144
|
-
subtitle: s.subtitle ?? undefined,
|
|
145
|
-
iconName: s.iconName,
|
|
146
|
-
itemIndices: s.itemIndices.filter((i) => i >= 0 && i <= maxIndex),
|
|
147
|
-
}));
|
|
148
|
-
if (sections.some((section) => hasUnsupportedOpportunityClaim(section.title)
|
|
149
|
-
|| hasUnsupportedOpportunityClaim(section.subtitle))) {
|
|
150
|
-
logger.warn('HomeCategorizer emitted unsupported affiliation/presence claim; using fallback');
|
|
151
|
-
return buildFallbackResult(cards);
|
|
152
|
-
}
|
|
153
|
-
const reconciledSections = reconcileSections(sections, maxIndex);
|
|
154
|
-
return { sections: reconciledSections };
|
|
155
|
-
}
|
|
156
|
-
catch (e) {
|
|
157
|
-
const err = e instanceof Error ? { message: e.message, name: e.name } : String(e);
|
|
158
|
-
logger.error('HomeCategorizer categorize failed', { error: err });
|
|
159
|
-
return buildFallbackResult(cards);
|
|
160
|
-
}
|
|
161
|
-
}
|
|
162
|
-
}
|
|
163
|
-
__decorate([
|
|
164
|
-
Timed(),
|
|
165
|
-
__metadata("design:type", Function),
|
|
166
|
-
__metadata("design:paramtypes", [Array]),
|
|
167
|
-
__metadata("design:returntype", Promise)
|
|
168
|
-
], HomeCategorizerAgent.prototype, "categorize", null);
|
|
@@ -1,21 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Lucide icon catalog for home view section headings.
|
|
3
|
-
* Icon names are kebab-case for use with lucide-react DynamicIcon (name prop).
|
|
4
|
-
* Used by home graph LLM to pick an icon per dynamic section.
|
|
5
|
-
*/
|
|
6
|
-
/** Default icon when LLM returns invalid or missing icon name. */
|
|
7
|
-
export declare const DEFAULT_HOME_SECTION_ICON = "hourglass";
|
|
8
|
-
/** Allowed icon names for home sections (subset of Lucide icons). */
|
|
9
|
-
export declare const HOME_SECTION_ICON_NAMES: readonly string[];
|
|
10
|
-
/**
|
|
11
|
-
* Normalize icon name: lowercase, strip spaces (allow kebab-case from LLM).
|
|
12
|
-
*/
|
|
13
|
-
export declare function normalizeIconName(name: string | undefined | null): string;
|
|
14
|
-
/**
|
|
15
|
-
* Validate and return an allowed icon name; fallback to default if unknown.
|
|
16
|
-
*/
|
|
17
|
-
export declare function resolveHomeSectionIcon(name: string | undefined | null): string;
|
|
18
|
-
/**
|
|
19
|
-
* Return a formatted list of icon names for LLM prompts (comma-separated, truncated if needed).
|
|
20
|
-
*/
|
|
21
|
-
export declare function getIconNamesForPrompt(maxItems?: number): string;
|
|
@@ -1,100 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Lucide icon catalog for home view section headings.
|
|
3
|
-
* Icon names are kebab-case for use with lucide-react DynamicIcon (name prop).
|
|
4
|
-
* Used by home graph LLM to pick an icon per dynamic section.
|
|
5
|
-
*/
|
|
6
|
-
/** Default icon when LLM returns invalid or missing icon name. */
|
|
7
|
-
export const DEFAULT_HOME_SECTION_ICON = 'hourglass';
|
|
8
|
-
/** Allowed icon names for home sections (subset of Lucide icons). */
|
|
9
|
-
export const HOME_SECTION_ICON_NAMES = [
|
|
10
|
-
'hourglass',
|
|
11
|
-
'telescope',
|
|
12
|
-
'route',
|
|
13
|
-
'bot',
|
|
14
|
-
'user',
|
|
15
|
-
'users',
|
|
16
|
-
'message-circle',
|
|
17
|
-
'sparkles',
|
|
18
|
-
'handshake',
|
|
19
|
-
'target',
|
|
20
|
-
'zap',
|
|
21
|
-
'lightbulb',
|
|
22
|
-
'compass',
|
|
23
|
-
'globe',
|
|
24
|
-
'heart',
|
|
25
|
-
'star',
|
|
26
|
-
'trending-up',
|
|
27
|
-
'briefcase',
|
|
28
|
-
'rocket',
|
|
29
|
-
'search',
|
|
30
|
-
'filter',
|
|
31
|
-
'list',
|
|
32
|
-
'layout-grid',
|
|
33
|
-
'inbox',
|
|
34
|
-
'send',
|
|
35
|
-
'mail',
|
|
36
|
-
'calendar',
|
|
37
|
-
'clock',
|
|
38
|
-
'timer',
|
|
39
|
-
'flag',
|
|
40
|
-
'bookmark',
|
|
41
|
-
'link',
|
|
42
|
-
'share-2',
|
|
43
|
-
'thumbs-up',
|
|
44
|
-
'award',
|
|
45
|
-
'graduation-cap',
|
|
46
|
-
'building-2',
|
|
47
|
-
'network',
|
|
48
|
-
'git-branch',
|
|
49
|
-
'code',
|
|
50
|
-
'palette',
|
|
51
|
-
'pen-tool',
|
|
52
|
-
'folder',
|
|
53
|
-
'file-text',
|
|
54
|
-
'bar-chart',
|
|
55
|
-
'pie-chart',
|
|
56
|
-
'activity',
|
|
57
|
-
'arrow-right',
|
|
58
|
-
'chevron-right',
|
|
59
|
-
'circle-dot',
|
|
60
|
-
'circle-user',
|
|
61
|
-
'user-plus',
|
|
62
|
-
'user-check',
|
|
63
|
-
'users-round',
|
|
64
|
-
'hand',
|
|
65
|
-
'hand-metal',
|
|
66
|
-
'megaphone',
|
|
67
|
-
'bell',
|
|
68
|
-
'eye',
|
|
69
|
-
'eye-off',
|
|
70
|
-
'lock',
|
|
71
|
-
'unlock',
|
|
72
|
-
'shield',
|
|
73
|
-
'shield-check',
|
|
74
|
-
];
|
|
75
|
-
const ALLOWED_SET = new Set(HOME_SECTION_ICON_NAMES);
|
|
76
|
-
/**
|
|
77
|
-
* Normalize icon name: lowercase, strip spaces (allow kebab-case from LLM).
|
|
78
|
-
*/
|
|
79
|
-
export function normalizeIconName(name) {
|
|
80
|
-
if (name == null || typeof name !== 'string')
|
|
81
|
-
return DEFAULT_HOME_SECTION_ICON;
|
|
82
|
-
const normalized = name.trim().toLowerCase().replace(/\s+/g, '-');
|
|
83
|
-
if (!normalized)
|
|
84
|
-
return DEFAULT_HOME_SECTION_ICON;
|
|
85
|
-
return normalized;
|
|
86
|
-
}
|
|
87
|
-
/**
|
|
88
|
-
* Validate and return an allowed icon name; fallback to default if unknown.
|
|
89
|
-
*/
|
|
90
|
-
export function resolveHomeSectionIcon(name) {
|
|
91
|
-
const normalized = normalizeIconName(name);
|
|
92
|
-
return ALLOWED_SET.has(normalized) ? normalized : DEFAULT_HOME_SECTION_ICON;
|
|
93
|
-
}
|
|
94
|
-
/**
|
|
95
|
-
* Return a formatted list of icon names for LLM prompts (comma-separated, truncated if needed).
|
|
96
|
-
*/
|
|
97
|
-
export function getIconNamesForPrompt(maxItems = 60) {
|
|
98
|
-
const list = HOME_SECTION_ICON_NAMES.slice(0, maxItems);
|
|
99
|
-
return list.join(', ');
|
|
100
|
-
}
|