@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.
Files changed (140) hide show
  1. package/CHANGELOG.md +107 -0
  2. package/IMPLEMENTATION.md +50 -52
  3. package/STABILITY.md +3 -3
  4. package/dist/chat/chat.agent.js +2 -2
  5. package/dist/chat/chat.graph.d.ts +41 -1
  6. package/dist/chat/chat.graph.js +108 -127
  7. package/dist/discovery/hyde.frame.d.ts +4 -4
  8. package/dist/discovery/hyde.graph.d.ts +71 -12
  9. package/dist/discovery/hyde.graph.js +337 -335
  10. package/dist/discovery/lens.inferrer.d.ts +6 -6
  11. package/dist/enrichment/enrichment.graph.d.ts +127 -9
  12. package/dist/enrichment/enrichment.graph.js +633 -655
  13. package/dist/enrichment/enrichment.state.d.ts +1 -1
  14. package/dist/enrichment/enrichment.tools.context-read.d.ts +10 -0
  15. package/dist/enrichment/enrichment.tools.context-read.js +353 -0
  16. package/dist/enrichment/enrichment.tools.context-write.d.ts +9 -0
  17. package/dist/enrichment/enrichment.tools.context-write.js +417 -0
  18. package/dist/enrichment/enrichment.tools.d.ts +11 -1
  19. package/dist/enrichment/enrichment.tools.helpers.d.ts +139 -0
  20. package/dist/enrichment/enrichment.tools.helpers.js +234 -0
  21. package/dist/enrichment/enrichment.tools.js +16 -975
  22. package/dist/index.d.ts +27 -48
  23. package/dist/index.js +17 -27
  24. package/dist/intents/application/intent.graph.d.ts +71 -67
  25. package/dist/intents/application/intent.graph.execute.d.ts +59 -0
  26. package/dist/intents/application/intent.graph.execute.js +301 -0
  27. package/dist/intents/application/intent.graph.infer.d.ts +44 -0
  28. package/dist/intents/application/intent.graph.infer.js +97 -0
  29. package/dist/intents/application/intent.graph.js +96 -888
  30. package/dist/intents/application/intent.graph.reconcile.d.ts +71 -0
  31. package/dist/intents/application/intent.graph.reconcile.js +274 -0
  32. package/dist/intents/application/intent.graph.shared.d.ts +70 -0
  33. package/dist/intents/application/intent.graph.shared.js +153 -0
  34. package/dist/intents/domain/intent.state.d.ts +1 -1
  35. package/dist/maintenance/maintenance.graph.d.ts +66 -3
  36. package/dist/maintenance/maintenance.graph.js +155 -156
  37. package/dist/mcp/mcp.authorization-policy.d.ts +24 -28
  38. package/dist/mcp/mcp.authorization-policy.js +14 -34
  39. package/dist/mcp/mcp.server.d.ts +3 -3
  40. package/dist/mcp/mcp.server.js +12 -16
  41. package/dist/negotiations/application/negotiation.candidates.d.ts +83 -0
  42. package/dist/negotiations/application/negotiation.candidates.js +162 -0
  43. package/dist/negotiations/application/negotiation.graph.d.ts +91 -157
  44. package/dist/negotiations/application/negotiation.graph.finalize.d.ts +5 -0
  45. package/dist/negotiations/application/negotiation.graph.finalize.js +280 -0
  46. package/dist/negotiations/application/negotiation.graph.init.d.ts +54 -0
  47. package/dist/negotiations/application/negotiation.graph.init.js +227 -0
  48. package/dist/negotiations/application/negotiation.graph.js +69 -1388
  49. package/dist/negotiations/application/negotiation.graph.screen.d.ts +23 -0
  50. package/dist/negotiations/application/negotiation.graph.screen.js +108 -0
  51. package/dist/negotiations/application/negotiation.graph.shared.d.ts +75 -0
  52. package/dist/negotiations/application/negotiation.graph.shared.js +125 -0
  53. package/dist/negotiations/application/negotiation.graph.turn.d.ts +105 -0
  54. package/dist/negotiations/application/negotiation.graph.turn.js +484 -0
  55. package/dist/negotiations/domain/negotiation.state.d.ts +1 -1
  56. package/dist/negotiations/domain/negotiation.state.js +0 -1
  57. package/dist/networks/application/indexer.graph.d.ts +165 -7
  58. package/dist/networks/application/indexer.graph.js +339 -388
  59. package/dist/networks/application/indexer.state.d.ts +1 -1
  60. package/dist/networks/application/membership.graph.d.ts +83 -5
  61. package/dist/networks/application/membership.graph.js +177 -207
  62. package/dist/networks/application/network.graph.d.ts +148 -5
  63. package/dist/networks/application/network.graph.js +249 -278
  64. package/dist/networks/domain/membership.state.d.ts +1 -1
  65. package/dist/networks/domain/network.state.d.ts +1 -1
  66. package/dist/opportunities/application/delivery-card.cache.d.ts +1 -1
  67. package/dist/opportunities/application/delivery-card.cache.js +2 -2
  68. package/dist/opportunities/application/index.d.ts +2 -2
  69. package/dist/opportunities/application/index.js +2 -2
  70. package/dist/opportunities/application/opportunity.evaluator.js +1 -1
  71. package/dist/opportunities/application/opportunity.graph.d.ts +686 -473
  72. package/dist/opportunities/application/opportunity.graph.discovery-strategies.d.ts +109 -0
  73. package/dist/opportunities/application/opportunity.graph.discovery-strategies.js +451 -0
  74. package/dist/opportunities/application/opportunity.graph.discovery.d.ts +69 -0
  75. package/dist/opportunities/application/opportunity.graph.discovery.js +397 -0
  76. package/dist/opportunities/application/opportunity.graph.evaluation.d.ts +82 -0
  77. package/dist/opportunities/application/opportunity.graph.evaluation.js +608 -0
  78. package/dist/opportunities/application/opportunity.graph.js +120 -3690
  79. package/dist/opportunities/application/opportunity.graph.modes.d.ts +538 -0
  80. package/dist/opportunities/application/opportunity.graph.modes.js +538 -0
  81. package/dist/opportunities/application/opportunity.graph.negotiate.d.ts +109 -0
  82. package/dist/opportunities/application/opportunity.graph.negotiate.js +392 -0
  83. package/dist/opportunities/application/opportunity.graph.persist-node.d.ts +93 -0
  84. package/dist/opportunities/application/opportunity.graph.persist-node.js +765 -0
  85. package/dist/opportunities/application/opportunity.graph.prep.d.ts +150 -0
  86. package/dist/opportunities/application/opportunity.graph.prep.js +381 -0
  87. package/dist/opportunities/application/opportunity.graph.shared.d.ts +173 -0
  88. package/dist/opportunities/application/opportunity.graph.shared.js +199 -0
  89. package/dist/opportunities/application/opportunity.presentation.d.ts +399 -0
  90. package/dist/opportunities/application/{opportunity.presenter.js → opportunity.presentation.js} +735 -9
  91. package/dist/opportunities/application/opportunity.tools.cards.d.ts +152 -0
  92. package/dist/opportunities/application/opportunity.tools.cards.js +235 -0
  93. package/dist/opportunities/application/opportunity.tools.d.ts +8 -114
  94. package/dist/opportunities/application/opportunity.tools.js +28 -711
  95. package/dist/opportunities/application/opportunity.tools.list.d.ts +10 -0
  96. package/dist/opportunities/application/opportunity.tools.list.js +493 -0
  97. package/dist/opportunities/domain/index.d.ts +3 -3
  98. package/dist/opportunities/domain/index.js +3 -3
  99. package/dist/opportunities/domain/opportunity.state.d.ts +19 -19
  100. package/dist/opportunities/index.d.ts +6 -6
  101. package/dist/opportunities/index.js +4 -4
  102. package/dist/opportunities/ports/opportunity.tools.port.d.ts +1 -1
  103. package/dist/opportunities/radar/radar.graph.d.ts +57 -13
  104. package/dist/opportunities/radar/radar.graph.js +470 -471
  105. package/dist/premises/premise.graph.d.ts +88 -20
  106. package/dist/premises/premise.graph.js +207 -218
  107. package/dist/questions/domain/question.schema.d.ts +40 -40
  108. package/dist/shared/agent/tool.helpers.d.ts +21 -0
  109. package/dist/shared/agent/tool.registry.d.ts +4 -3
  110. package/dist/shared/agent/tool.registry.js +1 -1
  111. package/dist/shared/interfaces/database.capabilities.d.ts +100 -0
  112. package/dist/shared/interfaces/database.capabilities.js +7 -0
  113. package/dist/shared/interfaces/database.entities.d.ts +533 -0
  114. package/dist/shared/interfaces/database.entities.js +10 -0
  115. package/dist/shared/interfaces/database.identity-queries.d.ts +294 -0
  116. package/dist/shared/interfaces/database.identity-queries.js +4 -0
  117. package/dist/shared/interfaces/database.interface.d.ts +15 -2277
  118. package/dist/shared/interfaces/database.interface.js +8 -0
  119. package/dist/shared/interfaces/database.member-queries.d.ts +228 -0
  120. package/dist/shared/interfaces/database.member-queries.js +4 -0
  121. package/dist/shared/interfaces/database.negotiation.d.ts +305 -0
  122. package/dist/shared/interfaces/database.negotiation.js +26 -0
  123. package/dist/shared/interfaces/database.network-queries.d.ts +277 -0
  124. package/dist/shared/interfaces/database.network-queries.js +4 -0
  125. package/dist/shared/interfaces/database.opportunity-queries.d.ts +285 -0
  126. package/dist/shared/interfaces/database.opportunity-queries.js +4 -0
  127. package/dist/shared/interfaces/database.port.d.ts +322 -0
  128. package/dist/shared/interfaces/database.port.js +29 -0
  129. package/dist/shared/schemas/discovery-question.schema.d.ts +14 -14
  130. package/dist/shared/schemas/negotiation-digest.schema.d.ts +2 -2
  131. package/package.json +2 -15
  132. package/dist/opportunities/application/opportunity.card-presentation.d.ts +0 -44
  133. package/dist/opportunities/application/opportunity.card-presentation.js +0 -93
  134. package/dist/opportunities/application/opportunity.presenter.d.ts +0 -156
  135. package/dist/opportunities/domain/opportunity.presentation-cache.d.ts +0 -5
  136. package/dist/opportunities/domain/opportunity.presentation-cache.js +0 -11
  137. package/dist/opportunities/domain/opportunity.presentation.d.ts +0 -76
  138. package/dist/opportunities/domain/opportunity.presentation.js +0 -516
  139. package/dist/opportunities/domain/opportunity.safe-presentation.d.ts +0 -104
  140. 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
- * Build the deep-link to an opportunity's A2A negotiation trace
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
- * The `?link_preview=false` hint mirrors `buildProfileUrl` chat-gateway
24
- * runtimes (e.g. Telegram delivery) strip link previews when it is present.
25
- * Trailing slashes on `frontendUrl` are stripped before concatenation.
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
- export declare function attachProfileLink(card: Record<string, unknown> & {
52
- opportunityId: string;
53
- }, opts: {
54
- counterpartUserId: string;
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];