@indexnetwork/protocol 65.0.0 → 66.0.0-rc.609.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +10 -0
- package/IMPLEMENTATION.md +11 -12
- package/STABILITY.md +0 -1
- package/dist/index.d.ts +5 -7
- package/dist/index.js +3 -6
- package/dist/internal/opportunities/opportunity.cards.d.ts +78 -0
- package/dist/internal/opportunities/opportunity.cards.js +165 -0
- package/dist/internal/opportunities/opportunity.graph.modes.d.ts +0 -1
- package/dist/internal/opportunities/opportunity.graph.modes.js +0 -5
- package/dist/internal/opportunities/opportunity.labels.d.ts +0 -2
- package/dist/internal/opportunities/opportunity.labels.js +0 -2
- package/dist/internal/opportunities/opportunity.presentation.d.ts +21 -185
- package/dist/internal/opportunities/opportunity.presentation.js +35 -651
- package/dist/internal/opportunities/opportunity.utils.d.ts +0 -42
- package/dist/internal/opportunities/opportunity.utils.js +0 -102
- package/dist/platform/database/capabilities.d.ts +2 -2
- package/dist/platform/database/entities.d.ts +1 -1
- package/package.json +1 -1
- package/dist/internal/opportunities/opportunity.feed-selection.d.ts +0 -24
- package/dist/internal/opportunities/opportunity.feed-selection.js +0 -40
- package/dist/internal/opportunities/radar/radar.graph.d.ts +0 -230
- package/dist/internal/opportunities/radar/radar.graph.js +0 -537
- package/dist/internal/opportunities/radar/radar.state.d.ts +0 -88
- package/dist/internal/opportunities/radar/radar.state.js +0 -87
- package/dist/internal/shared/utils/claim-safety.d.ts +0 -24
- package/dist/internal/shared/utils/claim-safety.js +0 -88
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,15 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 66.0.0
|
|
4
|
+
|
|
5
|
+
### Breaking changes
|
|
6
|
+
|
|
7
|
+
- Opportunity copy comes only from the LLM presenter. `OpportunityPresenter.present()` and `presentCard()` throw on LLM failure, timeout, or invalid output instead of returning fallback copy; `isFallback` and `fallbackReason` are gone from their results.
|
|
8
|
+
- Remove `presentOpportunity`, `stripUuids`, `truncateAtBoundary`, `safeFallbackSummary`, `DEFAULT_FALLBACK_HEADLINE`, `hasUnsupportedOpportunityClaim`, `stripUnsupportedOpportunityClaims`, and `stripUnsupportedOpportunityClaimsText` from the public entry point. The regex claim-safety guard is deleted; the presenter prompts carry the rule.
|
|
9
|
+
- `readOpportunities` items no longer carry `reasoning`.
|
|
10
|
+
- Remove `RadarGraphFactory` and the radar graph. Use `listOpportunityCards(deps, { viewerId, statuses, networkId?, intentId?, limit, noCache?, skeleton? })` for a presented card list and `presentOpportunityCard(deps, opportunity, viewerId, { intentId?, skeleton? })` for one card; both return `OpportunityCard`. `statuses` is required, and the list is newest first with one card per counterpart. A card whose presenter call fails is dropped from the list; `presentOpportunityCard` throws.
|
|
11
|
+
- Rename `RadarGraphDatabase` to `OpportunityCardsDatabase` and `buildRadarCardPresentationCacheKey` to `buildOpportunityCardCacheKey` (keys now start with `card:`). Remove `RADAR_SOFT_TARGETS` and `selectByComposition`.
|
|
12
|
+
|
|
3
13
|
## 65.0.0
|
|
4
14
|
|
|
5
15
|
### Breaking changes
|
package/IMPLEMENTATION.md
CHANGED
|
@@ -128,26 +128,25 @@ negotiation state and presentation contracts.
|
|
|
128
128
|
Optional capabilities default to a
|
|
129
129
|
degraded-but-functional mode when omitted.
|
|
130
130
|
|
|
131
|
-
##
|
|
131
|
+
## Opportunity cards
|
|
132
132
|
|
|
133
|
-
|
|
133
|
+
Presented opportunity cards are plain async functions over
|
|
134
|
+
`{ database: OpportunityCardsDatabase, cache: OpportunityCache, presenter: OpportunityPresenter }`:
|
|
134
135
|
|
|
135
136
|
```typescript
|
|
136
137
|
import {
|
|
137
|
-
|
|
138
|
+
listOpportunityCards,
|
|
139
|
+
presentOpportunityCard,
|
|
138
140
|
} from "@indexnetwork/protocol";
|
|
139
141
|
```
|
|
140
142
|
|
|
141
|
-
|
|
142
|
-
|
|
143
|
+
`listOpportunityCards` returns the viewer's opportunities in the requested
|
|
144
|
+
statuses, newest first with one card per counterpart, serving cached cards and
|
|
145
|
+
dropping any whose presenter call fails. `presentOpportunityCard` presents one
|
|
146
|
+
opportunity and throws when the presenter fails.
|
|
143
147
|
|
|
144
|
-
The intent and community graphs are
|
|
145
|
-
|
|
146
|
-
[Intents](#intents) and [Networks](#networks) below).
|
|
147
|
-
|
|
148
|
-
| Factory | Workflow |
|
|
149
|
-
|---|---|
|
|
150
|
-
| `RadarGraphFactory` | Build the radar view: flat presenter-card list, optionally intent-scoped |
|
|
148
|
+
The intent and community graphs are reached through the `Intents` and `Networks`
|
|
149
|
+
module classes (see [Intents](#intents) and [Networks](#networks) below).
|
|
151
150
|
|
|
152
151
|
## On-demand discovery
|
|
153
152
|
|
package/STABILITY.md
CHANGED
|
@@ -46,7 +46,6 @@ Covered by SemVer below. Breaking changes require a **major** bump.
|
|
|
46
46
|
| **Public API** | Model config helpers, request scope helpers (`deriveAllowedNetworkIds`, `deriveDiscoveryNetworkIds`), `requestContext`. |
|
|
47
47
|
| **Interfaces** | Every port you implement to inject infrastructure (databases, embedder, cache, scraper, integration, …). |
|
|
48
48
|
| **Shared schemas** | Zod schemas + inferred types that cross the boundary (underspecification, identity, network-assignment, chat-context, …). |
|
|
49
|
-
| **Graph factories** | `*GraphFactory` classes (for example, `RadarGraphFactory`). |
|
|
50
49
|
| **Intents** | `Intents` — the whole signal capability as one class (lifecycle graph, verification, preparation) plus `IntentsDeps` and the `Prepare*` / `RecoveryField*` types. Replaced the six separate intent exports in 18.0.0. |
|
|
51
50
|
| **Agents** | Structured LLM agents (`OpportunityEvaluator`, …). |
|
|
52
51
|
|
package/dist/index.d.ts
CHANGED
|
@@ -6,7 +6,7 @@ export { requestContext, setRequestContextStore } from "./internal/shared/observ
|
|
|
6
6
|
export { setLoggerFactory } from "./internal/shared/observability/log.js";
|
|
7
7
|
export { setTimingWrapper } from "./internal/shared/observability/performance.js";
|
|
8
8
|
export type { Cache, CacheOptions, OpportunityCache } from "./platform/discovery/cache.js";
|
|
9
|
-
export type { CompositeDatabase, UserDatabase, SystemDatabase, OpportunityDatabase, OpportunityControllerDatabase,
|
|
9
|
+
export type { CompositeDatabase, UserDatabase, SystemDatabase, OpportunityDatabase, OpportunityControllerDatabase, OpportunityCardsDatabase, IntentGraphDatabase, Opportunity, OpportunityActor, OpportunityStatus, AssignmentNetworkMembership, IntentNetworkFinalAssignmentResult, CreateOpportunityData, IntentLifecycleStatus, TransitionLifecycleResult, NegotiationContextDatabase, NegotiationContextRecord, NegotiationContextOutcome, NegotiationContextTurn, } from "./platform/database.js";
|
|
10
10
|
export type { Embedder, VectorStoreOption, VectorSearchResult } from "./platform/discovery/embedder.js";
|
|
11
11
|
export type { IntentFollowUp } from "./platform/runtime/follow-up.js";
|
|
12
12
|
export type { Scraper } from "./platform/discovery/scraper.js";
|
|
@@ -43,14 +43,12 @@ export type { OpportunityEvidence } from "./protocol/schemas/network-assignment.
|
|
|
43
43
|
export type { CreateIntentCounterpartyData, OpenedNegotiation, } from "./internal/opportunities/opportunity.counterparties.js";
|
|
44
44
|
export { gatherPresenterContext, OpportunityPresenter, } from "./internal/opportunities/opportunity.presentation.js";
|
|
45
45
|
export type { PresenterDatabase, } from "./internal/opportunities/opportunity.presentation.js";
|
|
46
|
-
export { hasUnsupportedOpportunityClaim, stripUnsupportedOpportunityClaims, stripUnsupportedOpportunityClaims as stripUnsupportedOpportunityClaimsText, } from "./internal/shared/utils/claim-safety.js";
|
|
47
46
|
export { getPrimaryActionLabel, } from "./internal/opportunities/opportunity.labels.js";
|
|
48
|
-
export { buildApiChatCardPresentationCacheKey,
|
|
49
|
-
export { presentOpportunity, stripUuids, truncateAtBoundary, } from "./internal/opportunities/opportunity.presentation.js";
|
|
47
|
+
export { buildApiChatCardPresentationCacheKey, buildOpportunityCardCacheKey, } from "./internal/opportunities/opportunity.presentation.js";
|
|
50
48
|
export type { UserInfo, } from "./internal/opportunities/opportunity.presentation.js";
|
|
51
|
-
export {
|
|
52
|
-
export {
|
|
53
|
-
export {
|
|
49
|
+
export { canUserSeeOpportunity, classifyOpportunity, isActionableForViewer, validateOpportunityActors, } from "./internal/opportunities/opportunity.utils.js";
|
|
50
|
+
export { listOpportunityCards, presentOpportunityCard, } from "./internal/opportunities/opportunity.cards.js";
|
|
51
|
+
export type { OpportunityCard, } from "./internal/opportunities/opportunity.cards.js";
|
|
54
52
|
export { readOpportunities } from './internal/opportunities/opportunity.graph.modes.js';
|
|
55
53
|
export { admitOpportunityEvent, projectOpportunity } from './internal/opportunities/opportunity.events.js';
|
|
56
54
|
export type { OpportunityAdmission, OpportunityEventType, OpportunityLogEvent, OpportunityProjection, OpportunityProjectionStatus } from './internal/opportunities/opportunity.events.js';
|
package/dist/index.js
CHANGED
|
@@ -47,13 +47,10 @@ export { normalizeTelegramHandle } from './internal/shared/utils/telegram-handle
|
|
|
47
47
|
*/
|
|
48
48
|
export { pairKeyOf, } from "./internal/opportunities/opportunity.counterparties.js";
|
|
49
49
|
export { gatherPresenterContext, OpportunityPresenter, } from "./internal/opportunities/opportunity.presentation.js";
|
|
50
|
-
export { hasUnsupportedOpportunityClaim, stripUnsupportedOpportunityClaims, stripUnsupportedOpportunityClaims as stripUnsupportedOpportunityClaimsText, } from "./internal/shared/utils/claim-safety.js";
|
|
51
50
|
export { getPrimaryActionLabel, } from "./internal/opportunities/opportunity.labels.js";
|
|
52
|
-
export { buildApiChatCardPresentationCacheKey,
|
|
53
|
-
export {
|
|
54
|
-
export {
|
|
55
|
-
export { canUserSeeOpportunity, classifyOpportunity, isActionableForViewer, RADAR_SOFT_TARGETS, selectByComposition, validateOpportunityActors, } from "./internal/opportunities/opportunity.utils.js";
|
|
56
|
-
export { RadarGraphFactory, } from "./internal/opportunities/radar/radar.graph.js";
|
|
51
|
+
export { buildApiChatCardPresentationCacheKey, buildOpportunityCardCacheKey, } from "./internal/opportunities/opportunity.presentation.js";
|
|
52
|
+
export { canUserSeeOpportunity, classifyOpportunity, isActionableForViewer, validateOpportunityActors, } from "./internal/opportunities/opportunity.utils.js";
|
|
53
|
+
export { listOpportunityCards, presentOpportunityCard, } from "./internal/opportunities/opportunity.cards.js";
|
|
57
54
|
export { readOpportunities } from './internal/opportunities/opportunity.graph.modes.js';
|
|
58
55
|
export { admitOpportunityEvent, projectOpportunity } from './internal/opportunities/opportunity.events.js';
|
|
59
56
|
export { resolveDiscoveryNetworkScope, renderDiscoveryNetworkContext } from './protocol/discovery.rules.js';
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Opportunity cards: presenter-texted cards for a viewer, as a list or one at
|
|
3
|
+
* a time. The list loads, filters, and dedupes the viewer's opportunities,
|
|
4
|
+
* serves cached cards, and presents the misses through OpportunityPresenter.
|
|
5
|
+
*/
|
|
6
|
+
import type { Opportunity, OpportunityCardsDatabase, OpportunityStatus } from '../../platform/database.js';
|
|
7
|
+
import type { OpportunityCache } from '../../platform/discovery/cache.js';
|
|
8
|
+
import { OpportunityPresenter } from './opportunity.presentation.js';
|
|
9
|
+
/** One opportunity with its presenter-driven display contract. */
|
|
10
|
+
export interface OpportunityCard {
|
|
11
|
+
opportunityId: string;
|
|
12
|
+
createdAt?: string;
|
|
13
|
+
/** Lifecycle status of the underlying opportunity at render time. */
|
|
14
|
+
status?: OpportunityStatus;
|
|
15
|
+
userId: string;
|
|
16
|
+
name: string;
|
|
17
|
+
avatar: string | null;
|
|
18
|
+
mainText: string;
|
|
19
|
+
cta: string;
|
|
20
|
+
headline?: string;
|
|
21
|
+
primaryActionLabel: string;
|
|
22
|
+
secondaryActionLabel: string;
|
|
23
|
+
mutualIntentsLabel: string;
|
|
24
|
+
narratorChip?: {
|
|
25
|
+
name: string;
|
|
26
|
+
text: string;
|
|
27
|
+
avatar?: string | null;
|
|
28
|
+
userId?: string;
|
|
29
|
+
};
|
|
30
|
+
/** Viewer's role in this opportunity (e.g. 'party', 'agent', 'patient', 'peer'). */
|
|
31
|
+
viewerRole?: string;
|
|
32
|
+
/**
|
|
33
|
+
* True for a skeleton card: identity fields are real but mainText/cta are
|
|
34
|
+
* empty because the presenter was skipped. Never cached.
|
|
35
|
+
*/
|
|
36
|
+
presentationPending?: boolean;
|
|
37
|
+
}
|
|
38
|
+
/** Everything card presentation reaches for. */
|
|
39
|
+
export interface OpportunityCardsDeps {
|
|
40
|
+
database: OpportunityCardsDatabase;
|
|
41
|
+
cache: OpportunityCache;
|
|
42
|
+
presenter: OpportunityPresenter;
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* Present one opportunity as a card for the viewer.
|
|
46
|
+
*
|
|
47
|
+
* @param deps - Database, cache, and presenter.
|
|
48
|
+
* @param opportunity - The opportunity to present.
|
|
49
|
+
* @param viewerId - The viewing user.
|
|
50
|
+
* @param options - `intentId` focuses viewer context on one intent; `skeleton` skips the presenter.
|
|
51
|
+
* @returns The card, or null when the counterpart's name cannot be resolved.
|
|
52
|
+
* @throws When the presenter fails.
|
|
53
|
+
*/
|
|
54
|
+
export declare function presentOpportunityCard(deps: OpportunityCardsDeps, opportunity: Opportunity, viewerId: string, options?: {
|
|
55
|
+
intentId?: string;
|
|
56
|
+
skeleton?: boolean;
|
|
57
|
+
}): Promise<OpportunityCard | null>;
|
|
58
|
+
/**
|
|
59
|
+
* List the viewer's opportunities in the given statuses as presented cards,
|
|
60
|
+
* newest first, one card per counterpart. Cards whose presentation fails are
|
|
61
|
+
* dropped.
|
|
62
|
+
*
|
|
63
|
+
* @param deps - Database, cache, and presenter.
|
|
64
|
+
* @param input - Viewer, statuses, optional network/intent scope, limit, cache bypass, skeleton mode.
|
|
65
|
+
* @returns The cards and the number of opportunities selected.
|
|
66
|
+
*/
|
|
67
|
+
export declare function listOpportunityCards(deps: OpportunityCardsDeps, input: {
|
|
68
|
+
viewerId: string;
|
|
69
|
+
statuses: OpportunityStatus[];
|
|
70
|
+
networkId?: string;
|
|
71
|
+
intentId?: string;
|
|
72
|
+
limit: number;
|
|
73
|
+
noCache?: boolean;
|
|
74
|
+
skeleton?: boolean;
|
|
75
|
+
}): Promise<{
|
|
76
|
+
cards: OpportunityCard[];
|
|
77
|
+
totalOpportunities: number;
|
|
78
|
+
}>;
|
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Opportunity cards: presenter-texted cards for a viewer, as a list or one at
|
|
3
|
+
* a time. The list loads, filters, and dedupes the viewer's opportunities,
|
|
4
|
+
* serves cached cards, and presents the misses through OpportunityPresenter.
|
|
5
|
+
*/
|
|
6
|
+
import { protocolLogger } from '../shared/observability/protocol.logger.js';
|
|
7
|
+
import { buildOpportunityCardCacheKey, gatherPresenterContext } from './opportunity.presentation.js';
|
|
8
|
+
import { loadNegotiationContext } from './negotiation-context.loader.js';
|
|
9
|
+
import { canUserSeeOpportunity, isActionableForViewer } from './opportunity.utils.js';
|
|
10
|
+
import { getPrimaryActionLabel, SECONDARY_ACTION_LABEL } from './opportunity.labels.js';
|
|
11
|
+
const logger = protocolLogger('OpportunityCards');
|
|
12
|
+
const PRESENTATION_CONCURRENCY = 50;
|
|
13
|
+
const CARD_CACHE_TTL = 24 * 60 * 60;
|
|
14
|
+
const ROLE_PRIORITY = new Map([
|
|
15
|
+
['patient', 0],
|
|
16
|
+
['party', 1],
|
|
17
|
+
['agent', 2],
|
|
18
|
+
['peer', 3],
|
|
19
|
+
]);
|
|
20
|
+
/** Prefer direct counterpart roles, then a stable sort by user id. */
|
|
21
|
+
function pickDisplayCounterpartActor(opportunity, viewerId) {
|
|
22
|
+
const candidates = opportunity.actors.filter((actor) => actor.userId !== viewerId);
|
|
23
|
+
return [...candidates].sort((a, b) => {
|
|
24
|
+
const aPriority = ROLE_PRIORITY.get(a.role) ?? 99;
|
|
25
|
+
const bPriority = ROLE_PRIORITY.get(b.role) ?? 99;
|
|
26
|
+
if (aPriority !== bPriority)
|
|
27
|
+
return aPriority - bPriority;
|
|
28
|
+
return a.userId.localeCompare(b.userId);
|
|
29
|
+
})[0] ?? null;
|
|
30
|
+
}
|
|
31
|
+
function counterpartUserIds(opportunity, viewerId) {
|
|
32
|
+
return new Set(opportunity.actors.map((a) => a.userId).filter((id) => id && id !== viewerId));
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* Present one opportunity as a card for the viewer.
|
|
36
|
+
*
|
|
37
|
+
* @param deps - Database, cache, and presenter.
|
|
38
|
+
* @param opportunity - The opportunity to present.
|
|
39
|
+
* @param viewerId - The viewing user.
|
|
40
|
+
* @param options - `intentId` focuses viewer context on one intent; `skeleton` skips the presenter.
|
|
41
|
+
* @returns The card, or null when the counterpart's name cannot be resolved.
|
|
42
|
+
* @throws When the presenter fails.
|
|
43
|
+
*/
|
|
44
|
+
export async function presentOpportunityCard(deps, opportunity, viewerId, options = {}) {
|
|
45
|
+
const { database } = deps;
|
|
46
|
+
const viewerRole = opportunity.actors.find((a) => a.userId === viewerId)?.role ?? 'party';
|
|
47
|
+
const counterpart = pickDisplayCounterpartActor(opportunity, viewerId);
|
|
48
|
+
if (!counterpart)
|
|
49
|
+
return null;
|
|
50
|
+
const user = await database.getUser(counterpart.userId).catch(() => null);
|
|
51
|
+
let name = user?.name?.trim();
|
|
52
|
+
if (!name) {
|
|
53
|
+
const profile = await database.getProfile(counterpart.userId).catch(() => null);
|
|
54
|
+
name = profile?.identity?.name?.trim();
|
|
55
|
+
}
|
|
56
|
+
if (!name)
|
|
57
|
+
return null;
|
|
58
|
+
const identity = {
|
|
59
|
+
opportunityId: opportunity.id,
|
|
60
|
+
createdAt: opportunity.createdAt.toISOString(),
|
|
61
|
+
status: opportunity.status,
|
|
62
|
+
userId: counterpart.userId,
|
|
63
|
+
name,
|
|
64
|
+
avatar: user?.avatar ?? null,
|
|
65
|
+
primaryActionLabel: getPrimaryActionLabel(viewerRole),
|
|
66
|
+
secondaryActionLabel: SECONDARY_ACTION_LABEL,
|
|
67
|
+
viewerRole,
|
|
68
|
+
};
|
|
69
|
+
if (options.skeleton) {
|
|
70
|
+
return { ...identity, mainText: '', cta: '', mutualIntentsLabel: 'Shared interests', presentationPending: true };
|
|
71
|
+
}
|
|
72
|
+
const [context, negotiationContext] = await Promise.all([
|
|
73
|
+
gatherPresenterContext(database, opportunity, viewerId, counterpart.userId, options.intentId),
|
|
74
|
+
loadNegotiationContext(database, opportunity.id, opportunity.status, viewerId),
|
|
75
|
+
]);
|
|
76
|
+
const presentation = await deps.presenter.presentCard({
|
|
77
|
+
...context,
|
|
78
|
+
opportunityStatus: opportunity.status,
|
|
79
|
+
...(negotiationContext ? { negotiationContext } : {}),
|
|
80
|
+
});
|
|
81
|
+
return {
|
|
82
|
+
...identity,
|
|
83
|
+
mainText: presentation.personalizedSummary,
|
|
84
|
+
cta: presentation.suggestedAction,
|
|
85
|
+
headline: presentation.headline,
|
|
86
|
+
mutualIntentsLabel: presentation.mutualIntentsLabel,
|
|
87
|
+
narratorChip: { name: 'Index', text: presentation.narratorRemark },
|
|
88
|
+
};
|
|
89
|
+
}
|
|
90
|
+
/**
|
|
91
|
+
* List the viewer's opportunities in the given statuses as presented cards,
|
|
92
|
+
* newest first, one card per counterpart. Cards whose presentation fails are
|
|
93
|
+
* dropped.
|
|
94
|
+
*
|
|
95
|
+
* @param deps - Database, cache, and presenter.
|
|
96
|
+
* @param input - Viewer, statuses, optional network/intent scope, limit, cache bypass, skeleton mode.
|
|
97
|
+
* @returns The cards and the number of opportunities selected.
|
|
98
|
+
*/
|
|
99
|
+
export async function listOpportunityCards(deps, input) {
|
|
100
|
+
const { viewerId, intentId } = input;
|
|
101
|
+
const raw = await deps.database.getOpportunitiesForUser(viewerId, {
|
|
102
|
+
limit: Math.min(150, Math.max(50, input.limit * 3)),
|
|
103
|
+
statuses: input.statuses,
|
|
104
|
+
...(input.networkId ? { networkId: input.networkId } : {}),
|
|
105
|
+
...(intentId ? { scopeType: 'intent', scopeId: intentId } : {}),
|
|
106
|
+
});
|
|
107
|
+
const requested = new Set(input.statuses);
|
|
108
|
+
const visible = raw
|
|
109
|
+
.filter((opp) => requested.has(opp.status) && canUserSeeOpportunity(opp.actors, opp.status, viewerId))
|
|
110
|
+
.filter((opp) => opp.status !== 'pending'
|
|
111
|
+
|| isActionableForViewer(opp.actors, opp.status, viewerId, opp.committedActorIds ?? []))
|
|
112
|
+
.sort((a, b) => b.createdAt.getTime() - a.createdAt.getTime());
|
|
113
|
+
const seen = new Set();
|
|
114
|
+
const opportunities = visible.filter((opp) => {
|
|
115
|
+
const ids = counterpartUserIds(opp, viewerId);
|
|
116
|
+
if ([...ids].some((id) => seen.has(id)))
|
|
117
|
+
return false;
|
|
118
|
+
for (const id of ids)
|
|
119
|
+
seen.add(id);
|
|
120
|
+
return true;
|
|
121
|
+
}).slice(0, input.limit);
|
|
122
|
+
const cacheable = (opp) => opp.status !== 'negotiating';
|
|
123
|
+
const keyFor = (opp) => buildOpportunityCardCacheKey(opp.id, opp.status, viewerId, intentId);
|
|
124
|
+
const cacheableOpps = input.noCache ? [] : opportunities.filter(cacheable);
|
|
125
|
+
const cached = new Map();
|
|
126
|
+
if (cacheableOpps.length > 0) {
|
|
127
|
+
try {
|
|
128
|
+
const hits = await deps.cache.mget(cacheableOpps.map(keyFor));
|
|
129
|
+
cacheableOpps.forEach((opp, i) => {
|
|
130
|
+
const hit = hits[i];
|
|
131
|
+
if (hit)
|
|
132
|
+
cached.set(opp.id, { ...hit, status: opp.status });
|
|
133
|
+
});
|
|
134
|
+
}
|
|
135
|
+
catch (error) {
|
|
136
|
+
logger.warn('card cache read failed, presenting all', { error });
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
const cards = [];
|
|
140
|
+
for (let i = 0; i < opportunities.length; i += PRESENTATION_CONCURRENCY) {
|
|
141
|
+
const chunk = opportunities.slice(i, i + PRESENTATION_CONCURRENCY);
|
|
142
|
+
cards.push(...await Promise.all(chunk.map(async (opp) => {
|
|
143
|
+
const hit = cached.get(opp.id);
|
|
144
|
+
if (hit)
|
|
145
|
+
return hit;
|
|
146
|
+
try {
|
|
147
|
+
const card = await presentOpportunityCard(deps, opp, viewerId, { intentId, skeleton: input.skeleton });
|
|
148
|
+
if (card && !card.presentationPending && cacheable(opp)) {
|
|
149
|
+
await deps.cache.set(keyFor(opp), card, { ttl: CARD_CACHE_TTL }).catch((error) => {
|
|
150
|
+
logger.warn('card cache write failed', { opportunityId: opp.id, error });
|
|
151
|
+
});
|
|
152
|
+
}
|
|
153
|
+
return card;
|
|
154
|
+
}
|
|
155
|
+
catch (error) {
|
|
156
|
+
logger.warn('presenter failed, dropping card', { opportunityId: opp.id, error });
|
|
157
|
+
return null;
|
|
158
|
+
}
|
|
159
|
+
})));
|
|
160
|
+
}
|
|
161
|
+
return {
|
|
162
|
+
cards: cards.filter((card) => card !== null),
|
|
163
|
+
totalOpportunities: opportunities.length,
|
|
164
|
+
};
|
|
165
|
+
}
|
|
@@ -29,7 +29,6 @@ export declare function readOpportunities(deps: {
|
|
|
29
29
|
networkName: string;
|
|
30
30
|
connectedWith: string[];
|
|
31
31
|
suggestedBy: null;
|
|
32
|
-
reasoning: string;
|
|
33
32
|
status: import("../../platform/database.js").OpportunityStatus;
|
|
34
33
|
category: string;
|
|
35
34
|
confidence: number | null;
|
|
@@ -10,7 +10,6 @@
|
|
|
10
10
|
* `readResult` / `mutationResult` exactly as before.
|
|
11
11
|
*/
|
|
12
12
|
import { timed } from '../shared/observability/performance.js';
|
|
13
|
-
import { safeFallbackSummary } from "./opportunity.presentation.js";
|
|
14
13
|
import { protocolLogger } from '../shared/observability/protocol.logger.js';
|
|
15
14
|
const readLog = protocolLogger('Opportunity:Read');
|
|
16
15
|
/**
|
|
@@ -101,10 +100,6 @@ export async function readOpportunities(deps, request) {
|
|
|
101
100
|
networkName: networkRecord?.title ?? (actorNetworkId ?? ''),
|
|
102
101
|
connectedWith,
|
|
103
102
|
suggestedBy,
|
|
104
|
-
reasoning: safeFallbackSummary(opp.interpretation?.reasoning, {
|
|
105
|
-
counterpartName: connectedWith.join(' and '),
|
|
106
|
-
emptyText: 'Connection opportunity',
|
|
107
|
-
}),
|
|
108
103
|
status: opp.status,
|
|
109
104
|
category,
|
|
110
105
|
confidence: confidence != null ? confidence : null,
|
|
@@ -1,5 +1,3 @@
|
|
|
1
|
-
/** Max chars for main text in minimal opportunity cards. Full text shown so cards are not truncated. */
|
|
2
|
-
export declare const MINIMAL_MAIN_TEXT_MAX_CHARS = 2000;
|
|
3
1
|
/** Hardcoded button labels for opportunity cards (not LLM-generated). */
|
|
4
2
|
export declare const PRIMARY_ACTION_LABEL_DEFAULT = "Start Chat";
|
|
5
3
|
export declare const SECONDARY_ACTION_LABEL = "Skip";
|
|
@@ -1,5 +1,3 @@
|
|
|
1
|
-
/** Max chars for main text in minimal opportunity cards. Full text shown so cards are not truncated. */
|
|
2
|
-
export const MINIMAL_MAIN_TEXT_MAX_CHARS = 2000;
|
|
3
1
|
/** Hardcoded button labels for opportunity cards (not LLM-generated). */
|
|
4
2
|
export const PRIMARY_ACTION_LABEL_DEFAULT = "Start Chat";
|
|
5
3
|
export const SECONDARY_ACTION_LABEL = "Skip";
|