@indexnetwork/protocol 14.3.2-rc.477.1 → 16.1.1-rc.478.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 +107 -0
- package/IMPLEMENTATION.md +50 -52
- package/STABILITY.md +3 -3
- package/dist/chat/chat.agent.js +2 -2
- package/dist/chat/chat.graph.d.ts +41 -1
- package/dist/chat/chat.graph.js +108 -127
- package/dist/discovery/hyde.frame.d.ts +4 -4
- package/dist/discovery/hyde.graph.d.ts +71 -12
- package/dist/discovery/hyde.graph.js +337 -335
- package/dist/discovery/lens.inferrer.d.ts +6 -6
- package/dist/enrichment/enrichment.graph.d.ts +127 -9
- package/dist/enrichment/enrichment.graph.js +633 -655
- package/dist/enrichment/enrichment.state.d.ts +1 -1
- package/dist/enrichment/enrichment.tools.context-read.d.ts +10 -0
- package/dist/enrichment/enrichment.tools.context-read.js +353 -0
- package/dist/enrichment/enrichment.tools.context-write.d.ts +9 -0
- package/dist/enrichment/enrichment.tools.context-write.js +417 -0
- package/dist/enrichment/enrichment.tools.d.ts +11 -1
- package/dist/enrichment/enrichment.tools.helpers.d.ts +139 -0
- package/dist/enrichment/enrichment.tools.helpers.js +234 -0
- package/dist/enrichment/enrichment.tools.js +16 -975
- package/dist/index.d.ts +27 -48
- package/dist/index.js +17 -27
- package/dist/intents/application/intent.graph.d.ts +71 -67
- package/dist/intents/application/intent.graph.execute.d.ts +59 -0
- package/dist/intents/application/intent.graph.execute.js +301 -0
- package/dist/intents/application/intent.graph.infer.d.ts +44 -0
- package/dist/intents/application/intent.graph.infer.js +97 -0
- package/dist/intents/application/intent.graph.js +96 -888
- package/dist/intents/application/intent.graph.reconcile.d.ts +71 -0
- package/dist/intents/application/intent.graph.reconcile.js +274 -0
- package/dist/intents/application/intent.graph.shared.d.ts +70 -0
- package/dist/intents/application/intent.graph.shared.js +153 -0
- package/dist/intents/domain/intent.state.d.ts +1 -1
- package/dist/maintenance/maintenance.graph.d.ts +66 -3
- package/dist/maintenance/maintenance.graph.js +155 -156
- package/dist/mcp/mcp.authorization-policy.d.ts +24 -28
- package/dist/mcp/mcp.authorization-policy.js +14 -34
- package/dist/mcp/mcp.server.d.ts +3 -3
- package/dist/mcp/mcp.server.js +12 -16
- package/dist/negotiations/application/negotiation.candidates.d.ts +83 -0
- package/dist/negotiations/application/negotiation.candidates.js +162 -0
- package/dist/negotiations/application/negotiation.graph.d.ts +91 -157
- package/dist/negotiations/application/negotiation.graph.finalize.d.ts +5 -0
- package/dist/negotiations/application/negotiation.graph.finalize.js +280 -0
- package/dist/negotiations/application/negotiation.graph.init.d.ts +54 -0
- package/dist/negotiations/application/negotiation.graph.init.js +227 -0
- package/dist/negotiations/application/negotiation.graph.js +69 -1388
- package/dist/negotiations/application/negotiation.graph.screen.d.ts +23 -0
- package/dist/negotiations/application/negotiation.graph.screen.js +108 -0
- package/dist/negotiations/application/negotiation.graph.shared.d.ts +75 -0
- package/dist/negotiations/application/negotiation.graph.shared.js +125 -0
- package/dist/negotiations/application/negotiation.graph.turn.d.ts +105 -0
- package/dist/negotiations/application/negotiation.graph.turn.js +484 -0
- package/dist/negotiations/domain/negotiation.state.d.ts +1 -1
- package/dist/negotiations/domain/negotiation.state.js +0 -1
- package/dist/networks/application/indexer.graph.d.ts +165 -7
- package/dist/networks/application/indexer.graph.js +339 -388
- package/dist/networks/application/indexer.state.d.ts +1 -1
- package/dist/networks/application/membership.graph.d.ts +83 -5
- package/dist/networks/application/membership.graph.js +177 -207
- package/dist/networks/application/network.graph.d.ts +148 -5
- package/dist/networks/application/network.graph.js +249 -278
- package/dist/networks/domain/membership.state.d.ts +1 -1
- package/dist/networks/domain/network.state.d.ts +1 -1
- package/dist/opportunities/application/delivery-card.cache.d.ts +1 -1
- package/dist/opportunities/application/delivery-card.cache.js +2 -2
- package/dist/opportunities/application/index.d.ts +2 -2
- package/dist/opportunities/application/index.js +2 -2
- package/dist/opportunities/application/opportunity.evaluator.js +1 -1
- package/dist/opportunities/application/opportunity.graph.d.ts +686 -473
- package/dist/opportunities/application/opportunity.graph.discovery-strategies.d.ts +109 -0
- package/dist/opportunities/application/opportunity.graph.discovery-strategies.js +451 -0
- package/dist/opportunities/application/opportunity.graph.discovery.d.ts +69 -0
- package/dist/opportunities/application/opportunity.graph.discovery.js +397 -0
- package/dist/opportunities/application/opportunity.graph.evaluation.d.ts +82 -0
- package/dist/opportunities/application/opportunity.graph.evaluation.js +608 -0
- package/dist/opportunities/application/opportunity.graph.js +120 -3690
- package/dist/opportunities/application/opportunity.graph.modes.d.ts +538 -0
- package/dist/opportunities/application/opportunity.graph.modes.js +538 -0
- package/dist/opportunities/application/opportunity.graph.negotiate.d.ts +109 -0
- package/dist/opportunities/application/opportunity.graph.negotiate.js +392 -0
- package/dist/opportunities/application/opportunity.graph.persist-node.d.ts +93 -0
- package/dist/opportunities/application/opportunity.graph.persist-node.js +765 -0
- package/dist/opportunities/application/opportunity.graph.prep.d.ts +150 -0
- package/dist/opportunities/application/opportunity.graph.prep.js +381 -0
- package/dist/opportunities/application/opportunity.graph.shared.d.ts +173 -0
- package/dist/opportunities/application/opportunity.graph.shared.js +199 -0
- package/dist/opportunities/application/opportunity.presentation.d.ts +399 -0
- package/dist/opportunities/application/{opportunity.presenter.js → opportunity.presentation.js} +735 -9
- package/dist/opportunities/application/opportunity.tools.cards.d.ts +152 -0
- package/dist/opportunities/application/opportunity.tools.cards.js +235 -0
- package/dist/opportunities/application/opportunity.tools.d.ts +8 -114
- package/dist/opportunities/application/opportunity.tools.js +28 -711
- package/dist/opportunities/application/opportunity.tools.list.d.ts +10 -0
- package/dist/opportunities/application/opportunity.tools.list.js +493 -0
- package/dist/opportunities/domain/index.d.ts +3 -3
- package/dist/opportunities/domain/index.js +3 -3
- package/dist/opportunities/domain/opportunity.state.d.ts +19 -19
- package/dist/opportunities/index.d.ts +6 -6
- package/dist/opportunities/index.js +4 -4
- package/dist/opportunities/ports/opportunity.tools.port.d.ts +1 -1
- package/dist/opportunities/radar/radar.graph.d.ts +57 -13
- package/dist/opportunities/radar/radar.graph.js +470 -471
- package/dist/premises/premise.graph.d.ts +88 -20
- package/dist/premises/premise.graph.js +207 -218
- package/dist/questions/domain/question.schema.d.ts +40 -40
- package/dist/shared/agent/tool.helpers.d.ts +21 -0
- package/dist/shared/agent/tool.registry.d.ts +4 -3
- package/dist/shared/agent/tool.registry.js +1 -1
- package/dist/shared/interfaces/database.capabilities.d.ts +100 -0
- package/dist/shared/interfaces/database.capabilities.js +7 -0
- package/dist/shared/interfaces/database.entities.d.ts +533 -0
- package/dist/shared/interfaces/database.entities.js +10 -0
- package/dist/shared/interfaces/database.identity-queries.d.ts +294 -0
- package/dist/shared/interfaces/database.identity-queries.js +4 -0
- package/dist/shared/interfaces/database.interface.d.ts +15 -2277
- package/dist/shared/interfaces/database.interface.js +8 -0
- package/dist/shared/interfaces/database.member-queries.d.ts +228 -0
- package/dist/shared/interfaces/database.member-queries.js +4 -0
- package/dist/shared/interfaces/database.negotiation.d.ts +305 -0
- package/dist/shared/interfaces/database.negotiation.js +26 -0
- package/dist/shared/interfaces/database.network-queries.d.ts +277 -0
- package/dist/shared/interfaces/database.network-queries.js +4 -0
- package/dist/shared/interfaces/database.opportunity-queries.d.ts +285 -0
- package/dist/shared/interfaces/database.opportunity-queries.js +4 -0
- package/dist/shared/interfaces/database.port.d.ts +322 -0
- package/dist/shared/interfaces/database.port.js +29 -0
- package/dist/shared/schemas/discovery-question.schema.d.ts +14 -14
- package/dist/shared/schemas/negotiation-digest.schema.d.ts +2 -2
- package/package.json +2 -15
- package/dist/opportunities/application/opportunity.card-presentation.d.ts +0 -44
- package/dist/opportunities/application/opportunity.card-presentation.js +0 -93
- package/dist/opportunities/application/opportunity.presenter.d.ts +0 -156
- package/dist/opportunities/domain/opportunity.presentation-cache.d.ts +0 -5
- package/dist/opportunities/domain/opportunity.presentation-cache.js +0 -11
- package/dist/opportunities/domain/opportunity.presentation.d.ts +0 -76
- package/dist/opportunities/domain/opportunity.presentation.js +0 -516
- package/dist/opportunities/domain/opportunity.safe-presentation.d.ts +0 -104
- package/dist/opportunities/domain/opportunity.safe-presentation.js +0 -102
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Card rendering and link minting for the opportunity tools.
|
|
3
|
+
*
|
|
4
|
+
* These are the pieces both `list_opportunities` and the mutation tools reach
|
|
5
|
+
* for: the deep links, the minimal fallback card, and the small guards that
|
|
6
|
+
* shape tool error payloads.
|
|
7
|
+
*/
|
|
8
|
+
import { type OpportunityOwnerAction, type OpportunityOwnerApprovalVerdict } from './opportunity.owner-approval.js';
|
|
9
|
+
export declare function stripLeadingNarratorName(remark: string, narratorName: string): string;
|
|
10
|
+
import type { Opportunity } from "../../shared/interfaces/database.interface.js";
|
|
11
|
+
import type { PendingQuestionSummary } from "../../shared/schemas/pending-question.schema.js";
|
|
12
|
+
export declare const logger: import("../../shared/observability/log.js").LoggerWithSource;
|
|
13
|
+
/**
|
|
14
|
+
* Build the agent-facing profile link for a counterpart — always the Index web
|
|
15
|
+
* profile URL with `?link_preview=false`. Returns `undefined` only if
|
|
16
|
+
* `frontendUrl` is not configured.
|
|
17
|
+
*
|
|
18
|
+
* The `?link_preview=false` hint is honored by chat-gateway runtimes (e.g.
|
|
19
|
+
* OpenClaw's Telegram delivery) that strip link previews when present in the
|
|
20
|
+
* URL; consistent placement matters more than Telegram's own handling.
|
|
21
|
+
*
|
|
22
|
+
* Trailing slashes on frontendUrl are stripped before concatenation.
|
|
23
|
+
*/
|
|
24
|
+
export declare function buildProfileUrl(counterpartUserId: string, frontendUrl: string | undefined): string | undefined;
|
|
25
|
+
/**
|
|
26
|
+
* Build the deep-link to an opportunity's A2A negotiation trace
|
|
27
|
+
* (`/chat/:conversationId`) so users can see *what negotiation led to* the
|
|
28
|
+
* surfaced opportunity (EDG-50/EDG-51). Returns `undefined` when `frontendUrl`
|
|
29
|
+
* is unset or there is no negotiation conversation to link to.
|
|
30
|
+
*
|
|
31
|
+
* The `?link_preview=false` hint mirrors `buildProfileUrl` — chat-gateway
|
|
32
|
+
* runtimes (e.g. Telegram delivery) strip link previews when it is present.
|
|
33
|
+
* Trailing slashes on `frontendUrl` are stripped before concatenation.
|
|
34
|
+
*/
|
|
35
|
+
export declare function buildNegotiationUrl(conversationId: string | undefined, frontendUrl: string | undefined): string | undefined;
|
|
36
|
+
/**
|
|
37
|
+
* Build the agent-facing deep link for an opportunity — the canonical
|
|
38
|
+
* `https://index.network/o/<id>` universal link. Returns `undefined` when
|
|
39
|
+
* `frontendUrl` is unset or there is no opportunity id.
|
|
40
|
+
*
|
|
41
|
+
* This is the *only* place the protocol mints an opportunity deep link. It is
|
|
42
|
+
* a navigation link, not an authority: opening it raises the opportunity card
|
|
43
|
+
* in the Index macOS app when installed and a static Index landing page
|
|
44
|
+
* otherwise. Acceptance stays an authenticated call.
|
|
45
|
+
*
|
|
46
|
+
* No `?link_preview=false` hint here (unlike `buildProfileUrl`): the Hermes
|
|
47
|
+
* plugin mints the identical bare form for payloads the protocol does not
|
|
48
|
+
* touch, and keeping the two byte-identical is what makes its never-overwrite
|
|
49
|
+
* rule invisible.
|
|
50
|
+
*/
|
|
51
|
+
export declare function buildOpportunityAppUrl(opportunityId: string, frontendUrl: string | undefined): string | undefined;
|
|
52
|
+
/**
|
|
53
|
+
* Attach the agent-facing profile link for a counterpart to `card` (mutates
|
|
54
|
+
* in place). Every counterpart has a profile page worth linking to — without
|
|
55
|
+
* this, the agent gets a name with no URL attached and tends to fabricate
|
|
56
|
+
* one. Accept/act guidance is plain text ("accept in the Index app"); no
|
|
57
|
+
* actionable URLs are minted here.
|
|
58
|
+
*/
|
|
59
|
+
export declare function attachProfileLink(card: Record<string, unknown> & {
|
|
60
|
+
opportunityId: string;
|
|
61
|
+
}, opts: {
|
|
62
|
+
counterpartUserId: string;
|
|
63
|
+
frontendUrl: string | undefined;
|
|
64
|
+
}): void;
|
|
65
|
+
/**
|
|
66
|
+
* Attach the opportunity deep link to `card` (mutates in place) so every MCP
|
|
67
|
+
* client — Claude Desktop, the CLI, the web, Hermes — can hand the user one
|
|
68
|
+
* clickable link to the card instead of fabricating one from an id.
|
|
69
|
+
*/
|
|
70
|
+
export declare function attachOpportunityAppLink(card: Record<string, unknown> & {
|
|
71
|
+
opportunityId: string;
|
|
72
|
+
}, opts: {
|
|
73
|
+
frontendUrl: string | undefined;
|
|
74
|
+
}): void;
|
|
75
|
+
export interface PublicUptakeQuestion {
|
|
76
|
+
id: string;
|
|
77
|
+
title: string;
|
|
78
|
+
prompt: string;
|
|
79
|
+
options: Array<{
|
|
80
|
+
label: string;
|
|
81
|
+
description: string;
|
|
82
|
+
}>;
|
|
83
|
+
multiSelect: boolean;
|
|
84
|
+
}
|
|
85
|
+
export declare function publicUptakeQuestion(question: PendingQuestionSummary): PublicUptakeQuestion;
|
|
86
|
+
export declare function uptakeAdvisory(opportunityId: string, questions: PublicUptakeQuestion[]): string;
|
|
87
|
+
/**
|
|
88
|
+
* IND-593: stable fail-closed denial for the owner-approval boundary. The
|
|
89
|
+
* `missing` reason carries the fresh, server-derived interaction challenge the
|
|
90
|
+
* owner must explicitly approve; all other reasons carry no challenge.
|
|
91
|
+
*/
|
|
92
|
+
export declare function ownerApprovalDenial(opportunityId: string, action: OpportunityOwnerAction, verdict: Extract<OpportunityOwnerApprovalVerdict, {
|
|
93
|
+
kind: 'denied';
|
|
94
|
+
}>): string;
|
|
95
|
+
/**
|
|
96
|
+
* Maximum number of opportunity cards to show per chat response.
|
|
97
|
+
* Sized for `selectByComposition` to fill both feed buckets — up to 3
|
|
98
|
+
* connection + 3 connector-flow per the digest/ambient prompt rules.
|
|
99
|
+
*/
|
|
100
|
+
export declare const CHAT_DISPLAY_LIMIT = 6;
|
|
101
|
+
/**
|
|
102
|
+
* Build minimal opportunity card data for chat without calling the LLM presenter.
|
|
103
|
+
* Uses only required fields from the opportunity record and counterpart name/avatar
|
|
104
|
+
* so list_opportunities and discovery return quickly.
|
|
105
|
+
*
|
|
106
|
+
* Note: narratorChip.text is generated via regex heuristics (narratorRemarkFromReasoning)
|
|
107
|
+
* rather than the OpportunityPresenter LLM. If narrator quality becomes an issue again,
|
|
108
|
+
* consider making this function async and delegating to OpportunityPresenter.presentCard()
|
|
109
|
+
* which already produces a high-quality narratorRemark via LLM (used by the home graph
|
|
110
|
+
* and discovery pipeline). The trade-off is 5-20s latency per card.
|
|
111
|
+
*
|
|
112
|
+
* Exported for use in tests (opportunity.tools.spec.ts).
|
|
113
|
+
*/
|
|
114
|
+
export declare function buildMinimalOpportunityCard(opp: Opportunity, viewerId: string, counterpartUserId: string, counterpartName: string, counterpartAvatar: string | null, introducerName?: string | null, introducerAvatar?: string | null, viewerName?: string, secondPartyName?: string, secondPartyAvatar?: string | null, secondPartyUserId?: string, isCounterpartGhost?: boolean): {
|
|
115
|
+
opportunityId: string;
|
|
116
|
+
userId: string;
|
|
117
|
+
name: string;
|
|
118
|
+
avatar: string | null;
|
|
119
|
+
mainText: string;
|
|
120
|
+
cta: string;
|
|
121
|
+
headline: string;
|
|
122
|
+
primaryActionLabel: string;
|
|
123
|
+
secondaryActionLabel: string;
|
|
124
|
+
mutualIntentsLabel: string;
|
|
125
|
+
narratorChip: {
|
|
126
|
+
name: string;
|
|
127
|
+
text: string;
|
|
128
|
+
avatar?: string | null;
|
|
129
|
+
userId?: string;
|
|
130
|
+
};
|
|
131
|
+
viewerRole: string;
|
|
132
|
+
score: number | undefined;
|
|
133
|
+
status: string;
|
|
134
|
+
isGhost: boolean;
|
|
135
|
+
secondParty?: {
|
|
136
|
+
name: string;
|
|
137
|
+
avatar?: string | null;
|
|
138
|
+
userId?: string;
|
|
139
|
+
};
|
|
140
|
+
};
|
|
141
|
+
/**
|
|
142
|
+
* Stable, retry-classified error codes for `confirm_opportunity_delivery`.
|
|
143
|
+
*
|
|
144
|
+
* The plain `error()` envelope only carries a human message, which forced
|
|
145
|
+
* callers (the Hermes digest sweep) to treat every failure — permanent or
|
|
146
|
+
* transient — as retryable, and made "already delivered but never confirmed"
|
|
147
|
+
* impossible to distinguish from "opportunity deleted". Each code carries an
|
|
148
|
+
* explicit `retryable` flag so deterministic callers can retry transient
|
|
149
|
+
* failures and drop permanent ones instead of re-spamming the ledger.
|
|
150
|
+
*/
|
|
151
|
+
export type ConfirmDeliveryErrorCode = "unauthenticated" | "ledger_unavailable" | "invalid_opportunity_id" | "opportunity_not_found" | "not_authorized" | "confirm_failed";
|
|
152
|
+
export declare function confirmDeliveryError(code: ConfirmDeliveryErrorCode, retryable: boolean, message: string): string;
|
|
@@ -0,0 +1,235 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Card rendering and link minting for the opportunity tools.
|
|
3
|
+
*
|
|
4
|
+
* These are the pieces both `list_opportunities` and the mutation tools reach
|
|
5
|
+
* for: the deep links, the minimal fallback card, and the small guards that
|
|
6
|
+
* shape tool error payloads.
|
|
7
|
+
*/
|
|
8
|
+
import { MINIMAL_MAIN_TEXT_MAX_CHARS, getPrimaryActionLabel, SECONDARY_ACTION_LABEL } from "../domain/opportunity.labels.js";
|
|
9
|
+
import { narratorRemarkFromReasoning, safeFallbackSummary } from "./opportunity.presentation.js";
|
|
10
|
+
export function stripLeadingNarratorName(remark, narratorName) {
|
|
11
|
+
let t = remark.trim();
|
|
12
|
+
if (!t || !narratorName.trim())
|
|
13
|
+
return remark;
|
|
14
|
+
const name = narratorName.trim();
|
|
15
|
+
const nameLower = name.toLowerCase();
|
|
16
|
+
for (;;) {
|
|
17
|
+
const lower = t.toLowerCase();
|
|
18
|
+
if (!lower.startsWith(nameLower))
|
|
19
|
+
break;
|
|
20
|
+
// Require a word boundary after the name so a short name like "Al" does not
|
|
21
|
+
// mangle a longer word ("Always …" → "ways …"). The char after the name must
|
|
22
|
+
// be a separator (sentence/clause punctuation or whitespace) or end of string.
|
|
23
|
+
// Keep this set in sync with the separator stripped from `rest` below.
|
|
24
|
+
const boundary = t.charAt(name.length);
|
|
25
|
+
if (boundary && !/[\s.:,\-–—]/.test(boundary))
|
|
26
|
+
break;
|
|
27
|
+
const rest = t.slice(name.length).replace(/^\s*[.:,\-–—]\s*/i, '').trim();
|
|
28
|
+
if (rest.length === 0 || rest === t)
|
|
29
|
+
break;
|
|
30
|
+
t = rest;
|
|
31
|
+
}
|
|
32
|
+
return t;
|
|
33
|
+
}
|
|
34
|
+
import { protocolLogger } from "../../shared/observability/protocol.logger.js";
|
|
35
|
+
export const logger = protocolLogger("ChatTools:Opportunity");
|
|
36
|
+
/**
|
|
37
|
+
* Build the agent-facing profile link for a counterpart — always the Index web
|
|
38
|
+
* profile URL with `?link_preview=false`. Returns `undefined` only if
|
|
39
|
+
* `frontendUrl` is not configured.
|
|
40
|
+
*
|
|
41
|
+
* The `?link_preview=false` hint is honored by chat-gateway runtimes (e.g.
|
|
42
|
+
* OpenClaw's Telegram delivery) that strip link previews when present in the
|
|
43
|
+
* URL; consistent placement matters more than Telegram's own handling.
|
|
44
|
+
*
|
|
45
|
+
* Trailing slashes on frontendUrl are stripped before concatenation.
|
|
46
|
+
*/
|
|
47
|
+
export function buildProfileUrl(counterpartUserId, frontendUrl) {
|
|
48
|
+
// The profile link is always the Index web profile, regardless of the
|
|
49
|
+
// counterpart's socials.
|
|
50
|
+
if (!frontendUrl)
|
|
51
|
+
return undefined;
|
|
52
|
+
const base = frontendUrl.replace(/\/+$/, "");
|
|
53
|
+
return `${base}/u/${counterpartUserId}?link_preview=false`;
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* Build the deep-link to an opportunity's A2A negotiation trace
|
|
57
|
+
* (`/chat/:conversationId`) so users can see *what negotiation led to* the
|
|
58
|
+
* surfaced opportunity (EDG-50/EDG-51). Returns `undefined` when `frontendUrl`
|
|
59
|
+
* is unset or there is no negotiation conversation to link to.
|
|
60
|
+
*
|
|
61
|
+
* The `?link_preview=false` hint mirrors `buildProfileUrl` — chat-gateway
|
|
62
|
+
* runtimes (e.g. Telegram delivery) strip link previews when it is present.
|
|
63
|
+
* Trailing slashes on `frontendUrl` are stripped before concatenation.
|
|
64
|
+
*/
|
|
65
|
+
export function buildNegotiationUrl(conversationId, frontendUrl) {
|
|
66
|
+
if (!frontendUrl || !conversationId)
|
|
67
|
+
return undefined;
|
|
68
|
+
const base = frontendUrl.replace(/\/+$/, "");
|
|
69
|
+
return `${base}/chat/${conversationId}?link_preview=false`;
|
|
70
|
+
}
|
|
71
|
+
/**
|
|
72
|
+
* Build the agent-facing deep link for an opportunity — the canonical
|
|
73
|
+
* `https://index.network/o/<id>` universal link. Returns `undefined` when
|
|
74
|
+
* `frontendUrl` is unset or there is no opportunity id.
|
|
75
|
+
*
|
|
76
|
+
* This is the *only* place the protocol mints an opportunity deep link. It is
|
|
77
|
+
* a navigation link, not an authority: opening it raises the opportunity card
|
|
78
|
+
* in the Index macOS app when installed and a static Index landing page
|
|
79
|
+
* otherwise. Acceptance stays an authenticated call.
|
|
80
|
+
*
|
|
81
|
+
* No `?link_preview=false` hint here (unlike `buildProfileUrl`): the Hermes
|
|
82
|
+
* plugin mints the identical bare form for payloads the protocol does not
|
|
83
|
+
* touch, and keeping the two byte-identical is what makes its never-overwrite
|
|
84
|
+
* rule invisible.
|
|
85
|
+
*/
|
|
86
|
+
export function buildOpportunityAppUrl(opportunityId, frontendUrl) {
|
|
87
|
+
if (!frontendUrl || !opportunityId)
|
|
88
|
+
return undefined;
|
|
89
|
+
const base = frontendUrl.replace(/\/+$/, "");
|
|
90
|
+
return `${base}/o/${opportunityId}`;
|
|
91
|
+
}
|
|
92
|
+
/**
|
|
93
|
+
* Attach the agent-facing profile link for a counterpart to `card` (mutates
|
|
94
|
+
* in place). Every counterpart has a profile page worth linking to — without
|
|
95
|
+
* this, the agent gets a name with no URL attached and tends to fabricate
|
|
96
|
+
* one. Accept/act guidance is plain text ("accept in the Index app"); no
|
|
97
|
+
* actionable URLs are minted here.
|
|
98
|
+
*/
|
|
99
|
+
export function attachProfileLink(card, opts) {
|
|
100
|
+
const profileUrl = buildProfileUrl(opts.counterpartUserId, opts.frontendUrl);
|
|
101
|
+
if (profileUrl)
|
|
102
|
+
card.profileUrl = profileUrl;
|
|
103
|
+
}
|
|
104
|
+
/**
|
|
105
|
+
* Attach the opportunity deep link to `card` (mutates in place) so every MCP
|
|
106
|
+
* client — Claude Desktop, the CLI, the web, Hermes — can hand the user one
|
|
107
|
+
* clickable link to the card instead of fabricating one from an id.
|
|
108
|
+
*/
|
|
109
|
+
export function attachOpportunityAppLink(card, opts) {
|
|
110
|
+
const appUrl = buildOpportunityAppUrl(card.opportunityId, opts.frontendUrl);
|
|
111
|
+
if (appUrl)
|
|
112
|
+
card.appUrl = appUrl;
|
|
113
|
+
}
|
|
114
|
+
export function publicUptakeQuestion(question) {
|
|
115
|
+
return {
|
|
116
|
+
id: question.id,
|
|
117
|
+
title: question.title,
|
|
118
|
+
prompt: question.prompt,
|
|
119
|
+
options: question.options,
|
|
120
|
+
multiSelect: question.multiSelect,
|
|
121
|
+
};
|
|
122
|
+
}
|
|
123
|
+
export function uptakeAdvisory(opportunityId, questions) {
|
|
124
|
+
return JSON.stringify({
|
|
125
|
+
success: false,
|
|
126
|
+
error: "Resolve the pending uptake questions or explicitly continue anyway.",
|
|
127
|
+
advisory: {
|
|
128
|
+
code: "unresolved_uptake_questions",
|
|
129
|
+
advisoryOnly: true,
|
|
130
|
+
opportunityId,
|
|
131
|
+
questions,
|
|
132
|
+
acknowledgedUptakeQuestionIds: questions.map((question) => question.id),
|
|
133
|
+
},
|
|
134
|
+
});
|
|
135
|
+
}
|
|
136
|
+
/**
|
|
137
|
+
* IND-593: stable fail-closed denial for the owner-approval boundary. The
|
|
138
|
+
* `missing` reason carries the fresh, server-derived interaction challenge the
|
|
139
|
+
* owner must explicitly approve; all other reasons carry no challenge.
|
|
140
|
+
*/
|
|
141
|
+
export function ownerApprovalDenial(opportunityId, action, verdict) {
|
|
142
|
+
return JSON.stringify({
|
|
143
|
+
success: false,
|
|
144
|
+
error: `Owner approval required for this opportunity ${action} (${verdict.reason}).`,
|
|
145
|
+
approval: {
|
|
146
|
+
code: "owner_approval_required",
|
|
147
|
+
reason: verdict.reason,
|
|
148
|
+
opportunityId,
|
|
149
|
+
action,
|
|
150
|
+
...(verdict.challenge
|
|
151
|
+
? { interactionId: verdict.challenge.interactionId, expiresAt: verdict.challenge.expiresAt }
|
|
152
|
+
: {}),
|
|
153
|
+
},
|
|
154
|
+
});
|
|
155
|
+
}
|
|
156
|
+
/**
|
|
157
|
+
* Maximum number of opportunity cards to show per chat response.
|
|
158
|
+
* Sized for `selectByComposition` to fill both feed buckets — up to 3
|
|
159
|
+
* connection + 3 connector-flow per the digest/ambient prompt rules.
|
|
160
|
+
*/
|
|
161
|
+
export const CHAT_DISPLAY_LIMIT = 6;
|
|
162
|
+
/**
|
|
163
|
+
* Build minimal opportunity card data for chat without calling the LLM presenter.
|
|
164
|
+
* Uses only required fields from the opportunity record and counterpart name/avatar
|
|
165
|
+
* so list_opportunities and discovery return quickly.
|
|
166
|
+
*
|
|
167
|
+
* Note: narratorChip.text is generated via regex heuristics (narratorRemarkFromReasoning)
|
|
168
|
+
* rather than the OpportunityPresenter LLM. If narrator quality becomes an issue again,
|
|
169
|
+
* consider making this function async and delegating to OpportunityPresenter.presentCard()
|
|
170
|
+
* which already produces a high-quality narratorRemark via LLM (used by the home graph
|
|
171
|
+
* and discovery pipeline). The trade-off is 5-20s latency per card.
|
|
172
|
+
*
|
|
173
|
+
* Exported for use in tests (opportunity.tools.spec.ts).
|
|
174
|
+
*/
|
|
175
|
+
export function buildMinimalOpportunityCard(opp, viewerId, counterpartUserId, counterpartName, counterpartAvatar, introducerName, introducerAvatar, viewerName, secondPartyName, secondPartyAvatar, secondPartyUserId, isCounterpartGhost) {
|
|
176
|
+
const viewerActor = opp.actors.find((a) => a.userId === viewerId);
|
|
177
|
+
const viewerRole = viewerActor?.role ?? "party";
|
|
178
|
+
const introducerActor = opp.actors.find((a) => a.role === "introducer" && a.userId !== viewerId);
|
|
179
|
+
const viewerIsIntroducer = opp.actors.some((a) => a.role === "introducer" && a.userId === viewerId);
|
|
180
|
+
const reasoning = opp.interpretation?.reasoning ?? "";
|
|
181
|
+
// Shared sanitization standard — see opportunity.safe-presentation.ts.
|
|
182
|
+
const mainText = safeFallbackSummary(reasoning, {
|
|
183
|
+
counterpartName,
|
|
184
|
+
viewerName,
|
|
185
|
+
introducerName: introducerName ?? undefined,
|
|
186
|
+
maxChars: MINIMAL_MAIN_TEXT_MAX_CHARS,
|
|
187
|
+
emptyText: "A suggested connection.",
|
|
188
|
+
});
|
|
189
|
+
const score = typeof opp.interpretation?.confidence === "number"
|
|
190
|
+
? opp.interpretation.confidence
|
|
191
|
+
: undefined;
|
|
192
|
+
const narratorName = viewerIsIntroducer
|
|
193
|
+
? "You"
|
|
194
|
+
: introducerName?.trim() || (introducerActor ? "Someone" : "Index");
|
|
195
|
+
const primaryActionLabel = getPrimaryActionLabel(viewerRole);
|
|
196
|
+
return {
|
|
197
|
+
opportunityId: opp.id,
|
|
198
|
+
userId: counterpartUserId,
|
|
199
|
+
name: counterpartName,
|
|
200
|
+
avatar: counterpartAvatar,
|
|
201
|
+
mainText,
|
|
202
|
+
cta: "Start a conversation to connect.",
|
|
203
|
+
headline: viewerIsIntroducer && secondPartyName
|
|
204
|
+
? `${counterpartName} → ${secondPartyName}`
|
|
205
|
+
: `Connection with ${counterpartName}`,
|
|
206
|
+
primaryActionLabel,
|
|
207
|
+
secondaryActionLabel: SECONDARY_ACTION_LABEL,
|
|
208
|
+
mutualIntentsLabel: "Suggested connection",
|
|
209
|
+
narratorChip: {
|
|
210
|
+
name: narratorName,
|
|
211
|
+
text: narratorRemarkFromReasoning(reasoning, counterpartName, viewerName),
|
|
212
|
+
...(viewerIsIntroducer
|
|
213
|
+
? { userId: viewerId, avatar: null }
|
|
214
|
+
: introducerActor
|
|
215
|
+
? { userId: introducerActor.userId, avatar: introducerAvatar ?? null }
|
|
216
|
+
: {}),
|
|
217
|
+
},
|
|
218
|
+
viewerRole,
|
|
219
|
+
score,
|
|
220
|
+
status: opp.status ?? "latent",
|
|
221
|
+
isGhost: isCounterpartGhost ?? false,
|
|
222
|
+
...(viewerIsIntroducer && secondPartyName
|
|
223
|
+
? {
|
|
224
|
+
secondParty: {
|
|
225
|
+
name: secondPartyName,
|
|
226
|
+
...(secondPartyAvatar != null ? { avatar: secondPartyAvatar } : {}),
|
|
227
|
+
...(secondPartyUserId ? { userId: secondPartyUserId } : {}),
|
|
228
|
+
},
|
|
229
|
+
}
|
|
230
|
+
: {}),
|
|
231
|
+
};
|
|
232
|
+
}
|
|
233
|
+
export function confirmDeliveryError(code, retryable, message) {
|
|
234
|
+
return JSON.stringify({ success: false, error: message, code, retryable });
|
|
235
|
+
}
|
|
@@ -1,118 +1,12 @@
|
|
|
1
|
-
import type { DefineTool } from "../../shared/agent/tool.helpers.js";
|
|
2
|
-
import type { OpportunityToolDeps } from "../ports/index.js";
|
|
3
|
-
export { buildOpportunityPresentation } from "./opportunity.card-presentation.js";
|
|
4
|
-
import type { Opportunity } from "../../shared/interfaces/database.interface.js";
|
|
5
|
-
/**
|
|
6
|
-
* Build the agent-facing profile link for a counterpart — always the Index web
|
|
7
|
-
* profile URL with `?link_preview=false`. Returns `undefined` only if
|
|
8
|
-
* `frontendUrl` is not configured.
|
|
9
|
-
*
|
|
10
|
-
* The `?link_preview=false` hint is honored by chat-gateway runtimes (e.g.
|
|
11
|
-
* OpenClaw's Telegram delivery) that strip link previews when present in the
|
|
12
|
-
* URL; consistent placement matters more than Telegram's own handling.
|
|
13
|
-
*
|
|
14
|
-
* Trailing slashes on frontendUrl are stripped before concatenation.
|
|
15
|
-
*/
|
|
16
|
-
export declare function buildProfileUrl(counterpartUserId: string, frontendUrl: string | undefined): string | undefined;
|
|
17
1
|
/**
|
|
18
|
-
*
|
|
19
|
-
* (`/chat/:conversationId`) so users can see *what negotiation led to* the
|
|
20
|
-
* surfaced opportunity (EDG-50/EDG-51). Returns `undefined` when `frontendUrl`
|
|
21
|
-
* is unset or there is no negotiation conversation to link to.
|
|
2
|
+
* The opportunity tool registry: list, update, and delivery confirmation.
|
|
22
3
|
*
|
|
23
|
-
*
|
|
24
|
-
*
|
|
25
|
-
*
|
|
26
|
-
*/
|
|
27
|
-
export declare function buildNegotiationUrl(conversationId: string | undefined, frontendUrl: string | undefined): string | undefined;
|
|
28
|
-
/**
|
|
29
|
-
* Build the agent-facing deep link for an opportunity — the canonical
|
|
30
|
-
* `https://index.network/o/<id>` universal link. Returns `undefined` when
|
|
31
|
-
* `frontendUrl` is unset or there is no opportunity id.
|
|
32
|
-
*
|
|
33
|
-
* This is the *only* place the protocol mints an opportunity deep link. It is
|
|
34
|
-
* a navigation link, not an authority: opening it raises the opportunity card
|
|
35
|
-
* in the Index macOS app when installed and a static Index landing page
|
|
36
|
-
* otherwise. Acceptance stays an authenticated call.
|
|
37
|
-
*
|
|
38
|
-
* No `?link_preview=false` hint here (unlike `buildProfileUrl`): the Hermes
|
|
39
|
-
* plugin mints the identical bare form for payloads the protocol does not
|
|
40
|
-
* touch, and keeping the two byte-identical is what makes its never-overwrite
|
|
41
|
-
* rule invisible.
|
|
42
|
-
*/
|
|
43
|
-
export declare function buildOpportunityAppUrl(opportunityId: string, frontendUrl: string | undefined): string | undefined;
|
|
44
|
-
/**
|
|
45
|
-
* Attach the agent-facing profile link for a counterpart to `card` (mutates
|
|
46
|
-
* in place). Every counterpart has a profile page worth linking to — without
|
|
47
|
-
* this, the agent gets a name with no URL attached and tends to fabricate
|
|
48
|
-
* one. Accept/act guidance is plain text ("accept in the Index app"); no
|
|
49
|
-
* actionable URLs are minted here.
|
|
4
|
+
* `list_opportunities` lives in `opportunity.tools.list.ts` and the shared card
|
|
5
|
+
* and link helpers in `opportunity.tools.cards.ts`; this file wires the three
|
|
6
|
+
* tools together and owns the two mutation tools.
|
|
50
7
|
*/
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
frontendUrl: string | undefined;
|
|
56
|
-
}): void;
|
|
57
|
-
/**
|
|
58
|
-
* Attach the opportunity deep link to `card` (mutates in place) so every MCP
|
|
59
|
-
* client — Claude Desktop, the CLI, the web, Hermes — can hand the user one
|
|
60
|
-
* clickable link to the card instead of fabricating one from an id.
|
|
61
|
-
*/
|
|
62
|
-
export declare function attachOpportunityAppLink(card: Record<string, unknown> & {
|
|
63
|
-
opportunityId: string;
|
|
64
|
-
}, opts: {
|
|
65
|
-
frontendUrl: string | undefined;
|
|
66
|
-
}): void;
|
|
67
|
-
/**
|
|
68
|
-
* Build minimal opportunity card data for chat without calling the LLM presenter.
|
|
69
|
-
* Uses only required fields from the opportunity record and counterpart name/avatar
|
|
70
|
-
* so list_opportunities and discovery return quickly.
|
|
71
|
-
*
|
|
72
|
-
* Note: narratorChip.text is generated via regex heuristics (narratorRemarkFromReasoning)
|
|
73
|
-
* rather than the OpportunityPresenter LLM. If narrator quality becomes an issue again,
|
|
74
|
-
* consider making this function async and delegating to OpportunityPresenter.presentCard()
|
|
75
|
-
* which already produces a high-quality narratorRemark via LLM (used by the home graph
|
|
76
|
-
* and discovery pipeline). The trade-off is 5-20s latency per card.
|
|
77
|
-
*
|
|
78
|
-
* Exported for use in tests (opportunity.tools.spec.ts).
|
|
79
|
-
*/
|
|
80
|
-
export declare function buildMinimalOpportunityCard(opp: Opportunity, viewerId: string, counterpartUserId: string, counterpartName: string, counterpartAvatar: string | null, introducerName?: string | null, introducerAvatar?: string | null, viewerName?: string, secondPartyName?: string, secondPartyAvatar?: string | null, secondPartyUserId?: string, isCounterpartGhost?: boolean): {
|
|
81
|
-
opportunityId: string;
|
|
82
|
-
userId: string;
|
|
83
|
-
name: string;
|
|
84
|
-
avatar: string | null;
|
|
85
|
-
mainText: string;
|
|
86
|
-
cta: string;
|
|
87
|
-
headline: string;
|
|
88
|
-
primaryActionLabel: string;
|
|
89
|
-
secondaryActionLabel: string;
|
|
90
|
-
mutualIntentsLabel: string;
|
|
91
|
-
narratorChip: {
|
|
92
|
-
name: string;
|
|
93
|
-
text: string;
|
|
94
|
-
avatar?: string | null;
|
|
95
|
-
userId?: string;
|
|
96
|
-
};
|
|
97
|
-
viewerRole: string;
|
|
98
|
-
score: number | undefined;
|
|
99
|
-
status: string;
|
|
100
|
-
isGhost: boolean;
|
|
101
|
-
secondParty?: {
|
|
102
|
-
name: string;
|
|
103
|
-
avatar?: string | null;
|
|
104
|
-
userId?: string;
|
|
105
|
-
};
|
|
106
|
-
};
|
|
107
|
-
/**
|
|
108
|
-
* Stable, retry-classified error codes for `confirm_opportunity_delivery`.
|
|
109
|
-
*
|
|
110
|
-
* The plain `error()` envelope only carries a human message, which forced
|
|
111
|
-
* callers (the Hermes digest sweep) to treat every failure — permanent or
|
|
112
|
-
* transient — as retryable, and made "already delivered but never confirmed"
|
|
113
|
-
* impossible to distinguish from "opportunity deleted". Each code carries an
|
|
114
|
-
* explicit `retryable` flag so deterministic callers can retry transient
|
|
115
|
-
* failures and drop permanent ones instead of re-spamming the ledger.
|
|
116
|
-
*/
|
|
117
|
-
export type ConfirmDeliveryErrorCode = "unauthenticated" | "ledger_unavailable" | "invalid_opportunity_id" | "opportunity_not_found" | "not_authorized" | "confirm_failed";
|
|
8
|
+
import type { DefineTool } from "../../shared/agent/tool.helpers.js";
|
|
9
|
+
import type { OpportunityToolDeps } from "../ports/index.js";
|
|
10
|
+
export { buildOpportunityPresentation } from "./opportunity.presentation.js";
|
|
11
|
+
export { attachOpportunityAppLink, attachProfileLink, buildMinimalOpportunityCard, buildNegotiationUrl, buildOpportunityAppUrl, buildProfileUrl } from "./opportunity.tools.cards.js";
|
|
118
12
|
export declare function createOpportunityTools(defineTool: DefineTool, deps: OpportunityToolDeps): readonly [any, any, any];
|