@indexnetwork/protocol 6.15.0-rc.407.1 → 8.0.2-rc.418.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 +309 -0
- package/IMPLEMENTATION.md +7 -4
- package/dist/capabilities/opportunities.facade.d.ts +4 -0
- package/dist/capabilities/opportunities.facade.js +4 -0
- package/dist/chat/chat-streaming.types.d.ts +4 -5
- package/dist/chat/chat.agent.d.ts +0 -19
- package/dist/chat/chat.agent.js +1 -70
- package/dist/chat/chat.persona.d.ts +0 -6
- package/dist/chat/chat.persona.js +0 -1
- package/dist/chat/chat.prompt.js +10 -10
- package/dist/chat/chat.prompt.modules.js +0 -104
- package/dist/chat/chat.tools.js +11 -7
- package/dist/chat/negotiator.persona.js +0 -1
- package/dist/chat/onboarding.persona.js +0 -1
- package/dist/chat/reporter.persona.d.ts +1 -1
- package/dist/chat/reporter.persona.js +1 -2
- package/dist/chat/reporter.prompt.js +5 -5
- package/dist/chat/signal.persona.js +0 -1
- package/dist/communities/application/network.tools.js +48 -27
- package/dist/contacts/application/contact.tools.js +9 -9
- package/dist/index.d.ts +9 -2
- package/dist/index.js +5 -0
- package/dist/maintenance/maintenance.graph.js +7 -0
- package/dist/mcp/mcp.authorization-policy.d.ts +250 -0
- package/dist/mcp/mcp.authorization-policy.js +522 -0
- package/dist/mcp/mcp.server.d.ts +6 -29
- package/dist/mcp/mcp.server.js +202 -228
- package/dist/negotiation/application/negotiation.agent.js +29 -5
- package/dist/negotiation/application/negotiation.graph.d.ts +2 -5
- package/dist/negotiation/application/negotiation.graph.js +53 -23
- package/dist/negotiation/domain/index.d.ts +3 -0
- package/dist/negotiation/domain/index.js +3 -0
- package/dist/negotiation/domain/negotiation.deadlock.d.ts +7 -1
- package/dist/negotiation/domain/negotiation.deadlock.js +48 -1
- package/dist/negotiation/domain/negotiation.stance.contracts.d.ts +85 -0
- package/dist/negotiation/domain/negotiation.stance.contracts.js +140 -0
- package/dist/negotiation/domain/negotiation.state.d.ts +2 -3
- package/dist/negotiation/domain/negotiation.state.js +2 -3
- package/dist/negotiation/public/index.d.ts +2 -0
- package/dist/negotiation/public/index.js +2 -0
- package/dist/opportunity/application/index.d.ts +0 -3
- package/dist/opportunity/application/index.js +0 -3
- package/dist/opportunity/application/opportunity.graph.d.ts +0 -43
- package/dist/opportunity/application/opportunity.graph.js +7 -113
- package/dist/opportunity/application/opportunity.introducer-feature.d.ts +2 -0
- package/dist/opportunity/application/opportunity.introducer-feature.js +4 -0
- package/dist/opportunity/application/opportunity.owner-approval.d.ts +118 -0
- package/dist/opportunity/application/opportunity.owner-approval.js +34 -0
- package/dist/opportunity/application/opportunity.owner-provenance.d.ts +6 -0
- package/dist/opportunity/application/opportunity.owner-provenance.js +28 -0
- package/dist/opportunity/application/opportunity.tools.d.ts +3 -3
- package/dist/opportunity/application/opportunity.tools.js +77 -870
- package/dist/opportunity/discriminator/discriminator.shadow.js +17 -10
- package/dist/opportunity/discriminator/discriminator.types.d.ts +2 -0
- package/dist/opportunity/domain/index.d.ts +0 -2
- package/dist/opportunity/domain/index.js +0 -2
- package/dist/opportunity/domain/opportunity.state.d.ts +2 -70
- package/dist/opportunity/domain/opportunity.state.js +2 -55
- package/dist/opportunity/opportunity.owner-approval.d.ts +5 -0
- package/dist/opportunity/opportunity.owner-approval.js +5 -0
- package/dist/opportunity/ports/opportunity.tools.port.d.ts +12 -1
- package/dist/opportunity/public/index.d.ts +3 -3
- package/dist/opportunity/public/index.js +2 -1
- package/dist/participant-agents/application/agent.tools.d.ts +1 -1
- package/dist/participant-agents/application/agent.tools.js +44 -6
- package/dist/participant-agents/application/index.d.ts +5 -3
- package/dist/participant-agents/application/index.js +5 -3
- package/dist/questions/application/question.tools.js +31 -2
- package/dist/runtime/foreground/composition/tool.factory.d.ts +2 -1
- package/dist/runtime/foreground/composition/tool.factory.js +9 -2
- package/dist/runtime/foreground/composition/tool.registry.d.ts +16 -2
- package/dist/runtime/foreground/composition/tool.registry.js +41 -38
- package/dist/shared/agent/activity-projection.d.ts +271 -0
- package/dist/shared/agent/activity-projection.js +248 -0
- package/dist/shared/agent/canonical-guidance.d.ts +30 -0
- package/dist/shared/agent/canonical-guidance.js +249 -0
- package/dist/shared/agent/tool.helpers.d.ts +9 -17
- package/dist/shared/agent/tool.runtime.js +1 -3
- package/dist/shared/agent/utility.tools.d.ts +11 -1
- package/dist/shared/agent/utility.tools.js +126 -90
- package/dist/shared/interfaces/auth.interface.d.ts +2 -8
- package/dist/shared/interfaces/chat-session.interface.js +5 -0
- package/dist/shared/interfaces/database.interface.d.ts +12 -5
- package/dist/shared/observability/request-context.d.ts +4 -40
- package/dist/shared/schemas/mcp-auth.schema.d.ts +87 -15
- package/dist/shared/schemas/mcp-auth.schema.js +49 -1
- package/dist/signals/application/intent.tools.js +7 -8
- package/package.json +10 -9
- package/dist/opportunity/application/opportunity.discover.d.ts +0 -215
- package/dist/opportunity/application/opportunity.discover.js +0 -793
- package/dist/opportunity/application/opportunity.discovery-continuation-finalization.d.ts +0 -34
- package/dist/opportunity/application/opportunity.discovery-continuation-finalization.js +0 -74
- package/dist/opportunity/application/opportunity.discovery-mcp-lifecycle-finalization.d.ts +0 -44
- package/dist/opportunity/application/opportunity.discovery-mcp-lifecycle-finalization.js +0 -92
- package/dist/opportunity/domain/opportunity.discovery-run-coalescing.d.ts +0 -33
- package/dist/opportunity/domain/opportunity.discovery-run-coalescing.js +0 -33
- package/dist/opportunity/domain/opportunity.discovery.contracts.d.ts +0 -45
- package/dist/opportunity/domain/opportunity.discovery.contracts.js +0 -1
- package/dist/opportunity/opportunity.discover.d.ts +0 -5
- package/dist/opportunity/opportunity.discover.js +0 -5
- package/dist/opportunity/opportunity.discovery-continuation-finalization.d.ts +0 -5
- package/dist/opportunity/opportunity.discovery-continuation-finalization.js +0 -5
- package/dist/opportunity/opportunity.discovery-mcp-lifecycle-finalization.d.ts +0 -5
- package/dist/opportunity/opportunity.discovery-mcp-lifecycle-finalization.js +0 -5
- package/dist/opportunity/opportunity.discovery-run-coalescing.d.ts +0 -5
- package/dist/opportunity/opportunity.discovery-run-coalescing.js +0 -5
- package/dist/opportunity/opportunity.discovery.contracts.d.ts +0 -5
- package/dist/opportunity/opportunity.discovery.contracts.js +0 -5
- package/dist/shared/interfaces/discovery-run.interface.d.ts +0 -70
- package/dist/shared/interfaces/discovery-run.interface.js +0 -1
|
@@ -1,9 +1,20 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
2
|
import { requestContext } from "../observability/request-context.js";
|
|
3
|
+
import { ActivitySummaryResponseSchema, McpActivityCallerSchema, activitySummaryNetworkId, projectActivitySummary } from "./activity-projection.js";
|
|
4
|
+
import { CANONICAL_GUIDANCE_SUMMARY, CANONICAL_GUIDANCE_TOPICS, CANONICAL_GUIDANCE_TOPICS_CONTENT } from "./canonical-guidance.js";
|
|
3
5
|
import { success, error, normalizeUrl } from "./tool.helpers.js";
|
|
4
|
-
|
|
6
|
+
/** Owner-trusted surfaces (REST/chat) receive the full owner view. */
|
|
7
|
+
const HUMAN_OWNER_CALLER = {
|
|
8
|
+
kind: "human",
|
|
9
|
+
permissions: [],
|
|
10
|
+
networkScopeId: null,
|
|
11
|
+
};
|
|
12
|
+
export function createUtilityTools(defineTool, deps, options = {}) {
|
|
5
13
|
const { scraper } = deps;
|
|
6
|
-
const
|
|
14
|
+
const isMcpSurface = options.surface === "mcp";
|
|
15
|
+
// scrape_url is omitted from the MCP tool surface (IND-597). It remains
|
|
16
|
+
// available via the direct HTTP Tool API and the chat agent.
|
|
17
|
+
const scrapeUrl = isMcpSurface ? null : defineTool({
|
|
7
18
|
name: "scrape_url",
|
|
8
19
|
description: "Extracts text content from a web URL — articles, LinkedIn/GitHub profiles, documentation, project pages, etc. " +
|
|
9
20
|
"Returns the page's text content (up to 10,000 characters) for use in subsequent tool calls.\n\n" +
|
|
@@ -44,20 +55,65 @@ export function createUtilityTools(defineTool, deps) {
|
|
|
44
55
|
name: "read_docs",
|
|
45
56
|
description: "Returns comprehensive documentation about the Index Network protocol — entity model, workflows, tool usage guidance, and domain concepts. " +
|
|
46
57
|
"This is the primary way for an external agent to bootstrap understanding of the system.\n\n" +
|
|
47
|
-
"**When to use:** Call this FIRST when starting a new session
|
|
48
|
-
"Also call when you need to understand
|
|
49
|
-
"
|
|
50
|
-
|
|
51
|
-
"- Which tools to call in what order for common tasks\n" +
|
|
52
|
-
"- Authentication and API patterns\n\n" +
|
|
53
|
-
"**Returns:** Markdown documentation. Pass `topic` to get a specific section, or omit for the full reference.\n\n" +
|
|
54
|
-
"**Available topics:** 'entities', 'intents', 'opportunities', 'indexes', 'profiles', 'contacts', 'discovery', 'workflows', 'authentication', 'mcp_agent_guide'",
|
|
58
|
+
"**When to use:** Call this FIRST when starting a new session.\n" +
|
|
59
|
+
"Also call when you need to understand identity, context, premises, signals, communities, networks, opportunities, or negotiations.\n\n" +
|
|
60
|
+
"**Returns:** Markdown documentation. Pass `topic` to get a specific section, or omit for the summary.\n\n" +
|
|
61
|
+
`**Available canonical topics:** ${CANONICAL_GUIDANCE_TOPICS.join(", ")}`,
|
|
55
62
|
querySchema: z.object({
|
|
56
|
-
topic: z.string().optional().describe(
|
|
63
|
+
topic: z.string().optional().describe(`Narrow to a canonical topic: ${CANONICAL_GUIDANCE_TOPICS.join(", ")}. Omit to get the summary.`),
|
|
57
64
|
}),
|
|
58
65
|
handler: async ({ context: _context, query }) => {
|
|
59
66
|
const topic = query.topic?.trim().toLowerCase();
|
|
67
|
+
// Canonical guidance is the MCP read_docs foundation. When on MCP surface,
|
|
68
|
+
// legacy supplemental topics (entities, intents, opportunities, etc.) are
|
|
69
|
+
// omitted to avoid repeating the canonical source (IND-602/603).
|
|
70
|
+
// REST/chat surfaces retain full topic coverage for backwards compatibility.
|
|
71
|
+
if (isMcpSurface) {
|
|
72
|
+
// MCP surface: use canonical guidance only.
|
|
73
|
+
if (!topic) {
|
|
74
|
+
// Return summary of canonical guidance
|
|
75
|
+
return success({ content: CANONICAL_GUIDANCE_SUMMARY });
|
|
76
|
+
}
|
|
77
|
+
// Try to match canonical topic
|
|
78
|
+
const normalizedTopic = topic.replace(/_/g, "-").toLowerCase();
|
|
79
|
+
for (const canonicalTopic of CANONICAL_GUIDANCE_TOPICS) {
|
|
80
|
+
if (canonicalTopic === normalizedTopic || normalizedTopic.includes(canonicalTopic.split("-")[0])) {
|
|
81
|
+
return success({ topic: canonicalTopic, content: CANONICAL_GUIDANCE_TOPICS_CONTENT[canonicalTopic] });
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
// Unknown topic on MCP surface
|
|
85
|
+
return success({
|
|
86
|
+
content: `Unknown canonical topic "${topic}". Available topics: ${CANONICAL_GUIDANCE_TOPICS.join(", ")}. Request summary for full canonical guidance.`,
|
|
87
|
+
});
|
|
88
|
+
}
|
|
89
|
+
// REST/chat surfaces: provide full legacy documentation alongside canonical.
|
|
90
|
+
// Contact management tools are not exposed on the MCP surface (IND-596), so
|
|
91
|
+
// full guidance includes contact workflows. This is for REST/chat only.
|
|
92
|
+
const contactsSection = `## Contact Management
|
|
93
|
+
|
|
94
|
+
Contacts are people in a user's personal network, stored as members of their personal network with 'contact' permission.
|
|
95
|
+
|
|
96
|
+
- **Adding contacts**: Via import_contacts (bulk), add_contact (single email), or import_gmail_contacts (Google integration).
|
|
97
|
+
- **Ghost users**: When a contact email doesn't match an existing account, a ghost user is created. Ghost users are enriched with public profile data and participate in opportunity matching — they can be discovered even before joining the platform.
|
|
98
|
+
- **Personal network scope**: Approved signals from personal-network contacts are matched in the background. Use list_opportunities only to review persisted results.
|
|
99
|
+
- **Contact data**: Each contact has userId, name, email, avatar, and isGhost flag.
|
|
100
|
+
|
|
101
|
+
### Contact Workflow
|
|
102
|
+
1. import_contacts or import_gmail_contacts → bulk add to network
|
|
103
|
+
2. list_contacts → view all contacts with userId
|
|
104
|
+
3. create_intent or update_intent → create or refine an approved signal for background matching
|
|
105
|
+
4. list_opportunities → review persisted results when background processing finds matches
|
|
106
|
+
5. add_contact(email) → add individual contact
|
|
107
|
+
6. remove_contact(contactUserId) → remove from network`;
|
|
108
|
+
const managingContactsWorkflow = `### Managing Contacts
|
|
109
|
+
1. import_gmail_contacts() or import_contacts([...]) → add contacts
|
|
110
|
+
2. list_contacts() → view network
|
|
111
|
+
3. create_intent or update_intent → create or refine an approved signal for background matching
|
|
112
|
+
4. list_opportunities → review persisted results
|
|
113
|
+
|
|
114
|
+
`;
|
|
60
115
|
const sections = {
|
|
116
|
+
// Legacy topics (REST/chat only)
|
|
61
117
|
entities: `## Entity Model & Relationships
|
|
62
118
|
|
|
63
119
|
- **Users**: People on the platform. Authenticated via API key (X-API-Key header) for MCP/external agents, or session-based (Better Auth) for the web app.
|
|
@@ -98,7 +154,7 @@ Intents are the core unit of discovery — they represent what users are seeking
|
|
|
98
154
|
|
|
99
155
|
Opportunities represent discovered connections between users — potential matches worth pursuing.
|
|
100
156
|
|
|
101
|
-
1. **
|
|
157
|
+
1. **Background matching**: The opportunity graph evaluates approved signals whose intents semantically complement each other within shared networks. It uses HyDE embeddings for retrieval and an LLM evaluator for scoring.
|
|
102
158
|
2. **Roles**: Each opportunity assigns roles to actors:
|
|
103
159
|
- **introducer**: The person who triggered the introduction (may be the system or another user)
|
|
104
160
|
- **party**: The people being connected (typically 2)
|
|
@@ -108,16 +164,15 @@ Opportunities represent discovered connections between users — potential match
|
|
|
108
164
|
- **accepted**: Both parties agreed to connect.
|
|
109
165
|
- **rejected**: One party declined.
|
|
110
166
|
- **expired**: Timed out without response.
|
|
111
|
-
4. **Creation
|
|
112
|
-
- **Discovery**: Automatic — system finds matches based on intent overlap (discover_opportunities with searchQuery)
|
|
113
|
-
- **Introduction**: Manual — a user introduces two specific people (discover_opportunities with partyUserIds + entities)
|
|
114
|
-
- **Direct**: One-to-one — connect with a specific person (discover_opportunities with targetUserId)
|
|
167
|
+
4. **Creation**: Opportunities are created by background matching after approved signals are created or refined. list_opportunities only reviews persisted cards; it never starts matching or targets a person.
|
|
115
168
|
5. **Presentation**: Each opportunity includes personalized match reasoning, confidence score, and suggested next action.
|
|
116
169
|
|
|
117
170
|
### Opportunity Workflow
|
|
118
|
-
1.
|
|
119
|
-
2.
|
|
120
|
-
3.
|
|
171
|
+
1. create_intent(description) or update_intent(intentId, description) → create or refine an approved signal
|
|
172
|
+
2. background matching → persists opportunity cards when matches are found
|
|
173
|
+
3. list_opportunities() → review persisted cards
|
|
174
|
+
4. update_opportunity(opportunityId, status="pending") → sends to other party
|
|
175
|
+
5. Other party sees opportunity → calls update_opportunity(status="accepted" or "rejected")`,
|
|
121
176
|
indexes: `## Index Mechanics
|
|
122
177
|
|
|
123
178
|
Indexes (also called "networks") are communities where members share what they're looking for and the system discovers connections between them.
|
|
@@ -133,7 +188,7 @@ Indexes (also called "networks") are communities where members share what they'r
|
|
|
133
188
|
1. create_network(title, prompt) → creates new community, you become owner
|
|
134
189
|
2. create_network_membership(networkId, userId) → invite members
|
|
135
190
|
3. Members create intents → auto-assigned to the index based on prompt
|
|
136
|
-
4.
|
|
191
|
+
4. Members' approved signals are matched in the background; list_opportunities only reviews persisted results`,
|
|
137
192
|
profiles: `## Profile System
|
|
138
193
|
|
|
139
194
|
Profiles are the user's identity on the platform, used for semantic matching in opportunity discovery.
|
|
@@ -152,33 +207,19 @@ Profiles are the user's identity on the platform, used for semantic matching in
|
|
|
152
207
|
- Richer profiles produce better opportunity matches
|
|
153
208
|
- Social links enable enrichment — encourage users to add LinkedIn/GitHub
|
|
154
209
|
- Profiles are recalculated when updated, which may surface new matches`,
|
|
155
|
-
contacts:
|
|
156
|
-
|
|
157
|
-
Contacts are people in a user's personal network, stored as members of their personal network with 'contact' permission.
|
|
158
|
-
|
|
159
|
-
- **Adding contacts**: Via import_contacts (bulk), add_contact (single email), or import_gmail_contacts (Google integration).
|
|
160
|
-
- **Ghost users**: When a contact email doesn't match an existing account, a ghost user is created. Ghost users are enriched with public profile data and participate in opportunity matching — they can be discovered even before joining the platform.
|
|
161
|
-
- **Personal network scope**: Pass the personal network networkId to discover_opportunities to scope discovery to just the user's contacts.
|
|
162
|
-
- **Contact data**: Each contact has userId, name, email, avatar, and isGhost flag.
|
|
163
|
-
|
|
164
|
-
### Contact Workflow
|
|
165
|
-
1. import_contacts or import_gmail_contacts → bulk add to network
|
|
166
|
-
2. list_contacts → view all contacts with userId
|
|
167
|
-
3. discover_opportunities(networkId=personalIndexId) → find matches among contacts
|
|
168
|
-
4. add_contact(email) → add individual contact
|
|
169
|
-
5. remove_contact(contactUserId) → remove from network`,
|
|
210
|
+
contacts: contactsSection,
|
|
170
211
|
discovery: `## Discovery Mechanics
|
|
171
212
|
|
|
172
213
|
Discovery is the process of finding meaningful connections between users based on their intents and profiles.
|
|
173
214
|
|
|
174
215
|
### How Discovery Works
|
|
175
|
-
1. **Trigger**: Runs automatically when an
|
|
216
|
+
1. **Trigger**: Runs automatically when an approved signal is created or refined.
|
|
176
217
|
2. **Pipeline**: Preparation (gather user context) → Scope (determine which indexes to search) → Candidate retrieval (semantic matching via HyDE embeddings) → Evaluation (LLM scores relevance and complementarity) → Ranking → Persist as opportunities.
|
|
177
218
|
3. **Semantic matching**: Uses HyDE (Hypothetical Document Embeddings) to find candidate intents that complement the source. This goes beyond keyword matching — it understands conceptual relationships.
|
|
178
219
|
4. **Evaluation**: An LLM evaluator agent scores each candidate match on relevance, complementarity, and actionability. Low-scoring matches are filtered out.
|
|
179
220
|
5. **Results**: Persisted as draft opportunities with roles, reasoning, and confidence scores.
|
|
180
221
|
6. **Background processing**: After intent creation, a queue job continues looking for matches asynchronously.
|
|
181
|
-
7. **
|
|
222
|
+
7. **Review**: Use list_opportunities to review persisted actionable cards; it does not run matching.
|
|
182
223
|
|
|
183
224
|
### Discovery Best Practices
|
|
184
225
|
- More specific intents produce more relevant matches
|
|
@@ -193,29 +234,25 @@ Discovery is the process of finding meaningful connections between users based o
|
|
|
193
234
|
3. read_networks() → see available communities
|
|
194
235
|
4. create_network_membership(networkId) → join a community
|
|
195
236
|
5. create_intent(description) → post what you're looking for
|
|
196
|
-
6.
|
|
237
|
+
6. Background matching evaluates the approved signal; list_opportunities reviews persisted results
|
|
197
238
|
|
|
198
239
|
### Finding Connections
|
|
199
240
|
1. read_networks() → list user's communities (get networkId)
|
|
200
|
-
2.
|
|
201
|
-
3.
|
|
241
|
+
2. create_intent(description) or update_intent(intentId, description) → create or refine an approved signal in the relevant network
|
|
242
|
+
3. Background matching persists eligible cards; list_opportunities reviews them
|
|
243
|
+
4. update_opportunity(opportunityId, status="pending") → send a persisted card
|
|
202
244
|
|
|
203
|
-
###
|
|
204
|
-
1. read_network_memberships(networkId) →
|
|
205
|
-
2.
|
|
206
|
-
3.
|
|
207
|
-
4.
|
|
245
|
+
### Helping Connections Emerge
|
|
246
|
+
1. read_network_memberships(networkId) → understand the shared community
|
|
247
|
+
2. create_intent(description) or update_intent(intentId, description) → capture or refine an approved signal
|
|
248
|
+
3. Background matching evaluates approved signals
|
|
249
|
+
4. list_opportunities() → review persisted results
|
|
208
250
|
|
|
209
|
-
###
|
|
210
|
-
1. import_gmail_contacts() or import_contacts([...]) → add contacts
|
|
211
|
-
2. list_contacts() → view network
|
|
212
|
-
3. discover_opportunities(networkId=personalIndexId) → find matches among contacts
|
|
213
|
-
|
|
214
|
-
### Creating a Community
|
|
251
|
+
${managingContactsWorkflow}### Creating a Community
|
|
215
252
|
1. create_network(title, prompt) → create network
|
|
216
253
|
2. create_network_membership(networkId, userId) → invite members
|
|
217
254
|
3. Members create intents → auto-indexed
|
|
218
|
-
4.
|
|
255
|
+
4. Members' approved signals are matched in the background; list_opportunities only reviews persisted results`,
|
|
219
256
|
authentication: `## Authentication & API Access
|
|
220
257
|
|
|
221
258
|
### For External AI Agents (MCP)
|
|
@@ -235,57 +272,56 @@ Discovery is the process of finding meaningful connections between users based o
|
|
|
235
272
|
- Avoid unnecessary read_intents/read_networks calls — cache results within a conversation
|
|
236
273
|
- Use pagination (limit/page) for large result sets
|
|
237
274
|
- Call read_docs once at the start to understand the domain`,
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
- Some tool results contain interactive card markup (code blocks with \`intent_proposal\`, \`opportunity_card\` language tags). These are designed for the Index Network web UI. **As an MCP agent, ignore card markup.** Instead, extract the meaningful data from the JSON and present it conversationally.
|
|
245
|
-
- When presenting opportunities or intents, use bullet points or short paragraphs — not raw JSON objects.
|
|
246
|
-
|
|
247
|
-
### Intent Creation Workflow
|
|
248
|
-
- **Always pass \`autoApprove: true\` when calling \`create_intent\`.** This persists intents directly without returning proposal cards that require manual UI approval.
|
|
249
|
-
- The tool will return a list of created intents with their descriptions and confidence scores. Present these in natural language.
|
|
250
|
-
- Do not tell the user to "click on cards" or "approve above" — there is no UI. Intents are created immediately with autoApprove.
|
|
251
|
-
- After creating intents, proactively suggest or run discovery to find matches.
|
|
252
|
-
|
|
253
|
-
### Discovery Workflow
|
|
254
|
-
- After creating intents, proactively suggest running discovery: \`discover_opportunities(searchQuery=...)\`
|
|
255
|
-
- Present discovered opportunities in natural language with the counterpart's name, match reasoning, and suggested next steps.
|
|
256
|
-
- Do not reference "cards", "panels", or any web UI elements.
|
|
257
|
-
|
|
258
|
-
### General MCP Agent Rules
|
|
259
|
-
- You are operating via API tools, not a web interface. Never reference clicking, scrolling, cards, panels, or any visual UI elements.
|
|
260
|
-
- Be proactive: if a logical next step exists (e.g., running discovery after creating intents), suggest or execute it.
|
|
261
|
-
- Use \`list_opportunities\` to check existing matches, \`list_negotiations\` for ongoing negotiations.
|
|
262
|
-
- Use \`read_networks\` to understand which communities the user belongs to before scoping operations.
|
|
263
|
-
- When errors occur, provide clear technical context rather than vague "backend issue" messages.`,
|
|
275
|
+
// Canonical topics (on REST/chat for completeness)
|
|
276
|
+
"identity-context": CANONICAL_GUIDANCE_TOPICS_CONTENT["identity-context"],
|
|
277
|
+
premises: CANONICAL_GUIDANCE_TOPICS_CONTENT.premises,
|
|
278
|
+
signals: CANONICAL_GUIDANCE_TOPICS_CONTENT.signals,
|
|
279
|
+
"communities-networks": CANONICAL_GUIDANCE_TOPICS_CONTENT["communities-networks"],
|
|
280
|
+
negotiations: CANONICAL_GUIDANCE_TOPICS_CONTENT.negotiations,
|
|
264
281
|
};
|
|
265
282
|
if (topic) {
|
|
266
|
-
const
|
|
283
|
+
const normalizedTopic = topic.replace(/_/g, "-").toLowerCase();
|
|
284
|
+
const matched = Object.entries(sections).find(([key]) => key === normalizedTopic || key.includes(normalizedTopic) || normalizedTopic.includes(key));
|
|
267
285
|
if (matched) {
|
|
268
286
|
return success({ topic: matched[0], content: matched[1] });
|
|
269
287
|
}
|
|
270
|
-
// If topic not found, return all
|
|
271
288
|
}
|
|
272
|
-
|
|
289
|
+
// Return full documentation (summary + all topics)
|
|
290
|
+
const fullDoc = [CANONICAL_GUIDANCE_SUMMARY, ...Object.values(sections)].join("\n\n");
|
|
273
291
|
return success({ content: fullDoc });
|
|
274
292
|
},
|
|
275
293
|
});
|
|
276
|
-
const
|
|
277
|
-
name: "
|
|
278
|
-
description: "
|
|
279
|
-
"
|
|
280
|
-
"
|
|
294
|
+
const readActivitySummary = defineTool({
|
|
295
|
+
name: "read_activity_summary",
|
|
296
|
+
description: "Returns grounded, aggregate-only activity for the authenticated user's agent over a recent time window.\n\n" +
|
|
297
|
+
"**Response domains (permission-projected for agent callers):**\n" +
|
|
298
|
+
"- signals (`liveSignalsWatched`, `opportunitiesBySignal` with signal IDs/titles) — requires manage:intents.\n" +
|
|
299
|
+
"- opportunities (`opportunitiesSurfaced`) — requires manage:opportunities.\n" +
|
|
300
|
+
"- questions (`pendingQuestionsByDomain`, `answeredQuestionsByDomain`) — meta-network counts grouped by the question's affected domain; " +
|
|
301
|
+
"each domain's counts require that domain's manage:identity/premises/intents/opportunities/negotiations permission; conversational chat-mode counts are human-owner-only.\n" +
|
|
302
|
+
"- negotiations (`negotiationsStarted`, `negotiationsCompleted`) — requires manage:negotiations.\n" +
|
|
303
|
+
"Human owners receive every domain. Agent callers receive only the domains their permissions authorize; " +
|
|
304
|
+
"a network agent's network-bound aggregates are narrowed to its bound community at the query layer.\n\n" +
|
|
305
|
+
"**Privacy:** the response never returns counterparty identities, chats, turns, transcripts, per-counterparty rows, " +
|
|
306
|
+
"or any private content — only aggregate counts and (with manage:intents) the owner's own signal IDs/titles.",
|
|
281
307
|
querySchema: z.object({
|
|
282
308
|
sinceHours: z.number().int().optional().default(24).describe("Look back this many hours; values are clamped to 1-168 (default 24)."),
|
|
283
309
|
}).strict(),
|
|
284
|
-
handler: async ({ query }) => {
|
|
310
|
+
handler: async ({ context, query }) => {
|
|
285
311
|
const sinceHours = Math.max(1, Math.min(168, query.sinceHours));
|
|
286
|
-
|
|
287
|
-
|
|
312
|
+
// Absent on owner-trusted REST/chat surfaces → full owner view. On MCP the
|
|
313
|
+
// server binds the typed resolved caller context and the centralized
|
|
314
|
+
// projection decides every visible domain.
|
|
315
|
+
const caller = context.mcpCaller
|
|
316
|
+
? McpActivityCallerSchema.parse(context.mcpCaller)
|
|
317
|
+
: HUMAN_OWNER_CALLER;
|
|
318
|
+
const networkId = activitySummaryNetworkId(caller);
|
|
319
|
+
const summary = await deps.userDb.getAgentActivitySummary({
|
|
320
|
+
sinceHours,
|
|
321
|
+
...(networkId ? { networkId } : {}),
|
|
322
|
+
});
|
|
323
|
+
return success(ActivitySummaryResponseSchema.parse(projectActivitySummary(caller, summary)));
|
|
288
324
|
},
|
|
289
325
|
});
|
|
290
|
-
return [scrapeUrl, readDocs,
|
|
326
|
+
return [scrapeUrl, readDocs, readActivitySummary].filter((tool) => tool !== null);
|
|
291
327
|
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { McpAuthInput } from '../schemas/mcp-auth.schema.js';
|
|
1
|
+
import type { McpAuthInput, McpResolvedIdentity } from '../schemas/mcp-auth.schema.js';
|
|
2
2
|
/**
|
|
3
3
|
* Resolves the authenticated MCP identity from an auth input DTO.
|
|
4
4
|
* The DTO is extracted from the transport at the edge (e.g. from HTTP Request
|
|
@@ -29,13 +29,7 @@ export interface McpAuthResolver {
|
|
|
29
29
|
*
|
|
30
30
|
* @throws Error if authentication fails (no token, invalid token, etc.)
|
|
31
31
|
*/
|
|
32
|
-
resolveIdentity(input: McpAuthInput): Promise<
|
|
33
|
-
userId: string;
|
|
34
|
-
agentId?: string;
|
|
35
|
-
isSessionAuth?: boolean;
|
|
36
|
-
networkScopeId?: string | null;
|
|
37
|
-
clientSurface?: 'telegram' | 'web';
|
|
38
|
-
}>;
|
|
32
|
+
resolveIdentity(input: McpAuthInput): Promise<McpResolvedIdentity>;
|
|
39
33
|
/**
|
|
40
34
|
* Deprecated HTTP Request bridge retained for compatibility with older
|
|
41
35
|
* callers. New transport code must extract `McpAuthInput` at the edge and
|
|
@@ -9,5 +9,10 @@
|
|
|
9
9
|
// there is nothing to return.
|
|
10
10
|
// • `limit` / `messageLimit`, when provided, cap the most-recent N rows; when
|
|
11
11
|
// omitted the adapter applies its own sane default.
|
|
12
|
+
// • Category contract (IND-600): `listSessions` / `getSession` expose ONLY H2A
|
|
13
|
+
// conversations (orchestrator-persona sessions with the system agent as a
|
|
14
|
+
// participant). H2H DMs are never exposed through this port, and A2A
|
|
15
|
+
// negotiation conversations are reachable only through the negotiation
|
|
16
|
+
// tools; non-H2A session IDs behave exactly like nonexistent ones.
|
|
12
17
|
// ═══════════════════════════════════════════════════════════════════════════════
|
|
13
18
|
export {};
|
|
@@ -1599,10 +1599,10 @@ export interface AgentActivitySummary {
|
|
|
1599
1599
|
title: string;
|
|
1600
1600
|
count: number;
|
|
1601
1601
|
}>;
|
|
1602
|
-
/** Current, non-expired questions waiting for the user. */
|
|
1603
|
-
|
|
1604
|
-
/** Questions answered by the user during the window. */
|
|
1605
|
-
|
|
1602
|
+
/** Current, non-expired questions waiting for the user, grouped by affected mode (QuestionMode values). Meta-network. */
|
|
1603
|
+
pendingQuestionsByMode: Record<string, number>;
|
|
1604
|
+
/** Questions answered by the user during the window, grouped by affected mode (QuestionMode values). Meta-network. */
|
|
1605
|
+
answeredQuestionsByMode: Record<string, number>;
|
|
1606
1606
|
/** Distinct opportunity negotiations started during the window. */
|
|
1607
1607
|
negotiationsStarted: number;
|
|
1608
1608
|
/** Distinct opportunity negotiations completed during the window. */
|
|
@@ -1729,9 +1729,16 @@ export interface UserDatabase {
|
|
|
1729
1729
|
success: boolean;
|
|
1730
1730
|
alreadyMember?: boolean;
|
|
1731
1731
|
}>;
|
|
1732
|
-
/**
|
|
1732
|
+
/**
|
|
1733
|
+
* Summarize the authenticated user's own agent activity without counterparty rows.
|
|
1734
|
+
* When `networkId` is present (a network agent's bound community), the
|
|
1735
|
+
* network-bound aggregates (opportunity and negotiation counts) are narrowed
|
|
1736
|
+
* to that community inside the query; own-signal and question aggregates are
|
|
1737
|
+
* meta-network and stay global.
|
|
1738
|
+
*/
|
|
1733
1739
|
getAgentActivitySummary(input: {
|
|
1734
1740
|
sinceHours: number;
|
|
1741
|
+
networkId?: string;
|
|
1735
1742
|
}): Promise<AgentActivitySummary>;
|
|
1736
1743
|
/** Get opportunities where the authenticated user is an actor. */
|
|
1737
1744
|
getOpportunitiesForUser(options?: OpportunityQueryOptions): Promise<Opportunity[]>;
|
|
@@ -1,18 +1,8 @@
|
|
|
1
|
-
import type { Opportunity } from "../interfaces/database.interface.js";
|
|
2
1
|
import { AsyncLocalStorage } from "async_hooks";
|
|
3
2
|
/**
|
|
4
|
-
* Callback for streaming trace
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
* Carries two flavors of event:
|
|
8
|
-
* - Trace events (`graph_start | graph_end | agent_start | agent_end`) — used
|
|
9
|
-
* by the chat TRACE panel to visualize what the agent is doing.
|
|
10
|
-
* - Domain events (`opportunity_draft_ready`) — emitted by the orchestrator
|
|
11
|
-
* branch of OpportunityGraph.negotiateNode so the frontend can render each
|
|
12
|
-
* accepted draft card progressively as its negotiation resolves.
|
|
13
|
-
*
|
|
14
|
-
* Kept as a single emitter rather than splitting into two to minimize plumbing
|
|
15
|
-
* through AsyncLocalStorage; the chat.agent relay branches on event.type.
|
|
3
|
+
* Callback for streaming trace events from deep inside graph nodes back to
|
|
4
|
+
* the caller (typically chat.agent's stream pipeline). Kept as a single emitter
|
|
5
|
+
* to minimize AsyncLocalStorage plumbing.
|
|
16
6
|
*/
|
|
17
7
|
export type TraceEmitter = (event: {
|
|
18
8
|
type: "graph_start" | "graph_end" | "agent_start" | "agent_end";
|
|
@@ -27,37 +17,11 @@ export type TraceEmitter = (event: {
|
|
|
27
17
|
} | {
|
|
28
18
|
type: "status";
|
|
29
19
|
message: string;
|
|
30
|
-
} | {
|
|
31
|
-
type: "opportunity_draft_ready";
|
|
32
|
-
opportunityId: string;
|
|
33
|
-
opportunity: Opportunity;
|
|
34
|
-
/** Viewer-centric summary derived from interpretation.reasoning. */
|
|
35
|
-
personalizedSummary?: string;
|
|
36
|
-
/**
|
|
37
|
-
* Minimal counterparty data for rendering the inline card without a
|
|
38
|
-
* second-round-trip user lookup. Populated from the negotiation
|
|
39
|
-
* candidate's profile; avatar is intentionally omitted (the card
|
|
40
|
-
* falls back to initials) since UserNegotiationContext doesn't
|
|
41
|
-
* carry avatars.
|
|
42
|
-
*/
|
|
43
|
-
counterparty: {
|
|
44
|
-
userId: string;
|
|
45
|
-
name?: string;
|
|
46
|
-
};
|
|
47
20
|
}) => void;
|
|
48
21
|
interface RequestContext {
|
|
49
22
|
originUrl?: string;
|
|
50
23
|
traceEmitter?: TraceEmitter;
|
|
51
|
-
/**
|
|
52
|
-
* Signal for cooperative cancellation — propagates the caller's AbortSignal
|
|
53
|
-
* into long-running graph nodes (e.g. orchestrator negotiation fan-out) so
|
|
54
|
-
* they can stop emitting events when the chat session closes.
|
|
55
|
-
*
|
|
56
|
-
* The orchestrator branch checks this before persisting status flips or
|
|
57
|
-
* pushing `opportunity_draft_ready` events. In-flight negotiations are not
|
|
58
|
-
* forcibly cancelled — they finish or time out naturally via their park
|
|
59
|
-
* window — but their results are suppressed once the signal trips.
|
|
60
|
-
*/
|
|
24
|
+
/** Signal for cooperative cancellation in long-running graph nodes. */
|
|
61
25
|
abortSignal?: AbortSignal;
|
|
62
26
|
}
|
|
63
27
|
/**
|
|
@@ -1,17 +1,89 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
1
2
|
/**
|
|
2
|
-
*
|
|
3
|
-
* edge before the protocol auth resolver is called. Keeps the shared auth
|
|
4
|
-
* interface free of platform-specific `Request` coupling.
|
|
3
|
+
* Transport-neutral auth input extracted at the HTTP boundary.
|
|
5
4
|
*/
|
|
6
|
-
export
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
5
|
+
export declare const McpAuthInputSchema: z.ZodObject<{
|
|
6
|
+
bearerToken: z.ZodOptional<z.ZodString>;
|
|
7
|
+
apiKey: z.ZodOptional<z.ZodString>;
|
|
8
|
+
clientSurface: z.ZodOptional<z.ZodEnum<["telegram", "web"]>>;
|
|
9
|
+
telegramHandle: z.ZodOptional<z.ZodString>;
|
|
10
|
+
telegramUsername: z.ZodOptional<z.ZodString>;
|
|
11
|
+
}, "strict", z.ZodTypeAny, {
|
|
12
|
+
apiKey?: string | undefined;
|
|
13
|
+
clientSurface?: "telegram" | "web" | undefined;
|
|
14
|
+
bearerToken?: string | undefined;
|
|
15
|
+
telegramHandle?: string | undefined;
|
|
16
|
+
telegramUsername?: string | undefined;
|
|
17
|
+
}, {
|
|
18
|
+
apiKey?: string | undefined;
|
|
19
|
+
clientSurface?: "telegram" | "web" | undefined;
|
|
20
|
+
bearerToken?: string | undefined;
|
|
21
|
+
telegramHandle?: string | undefined;
|
|
22
|
+
telegramUsername?: string | undefined;
|
|
23
|
+
}>;
|
|
24
|
+
export type McpAuthInput = z.infer<typeof McpAuthInputSchema>;
|
|
25
|
+
/**
|
|
26
|
+
* Trusted API-key metadata used by the host auth composition.
|
|
27
|
+
*
|
|
28
|
+
* Enrollment and delivery flags are explicit opt-ins. Unknown metadata is
|
|
29
|
+
* ignored so unrelated Better Auth metadata remains forward-compatible.
|
|
30
|
+
*/
|
|
31
|
+
export declare const McpApiKeyMetadataSchema: z.ZodObject<{
|
|
32
|
+
agentId: z.ZodOptional<z.ZodString>;
|
|
33
|
+
enrollmentCapable: z.ZodOptional<z.ZodBoolean>;
|
|
34
|
+
isDeliveryAgent: z.ZodOptional<z.ZodBoolean>;
|
|
35
|
+
}, "passthrough", z.ZodTypeAny, z.objectOutputType<{
|
|
36
|
+
agentId: z.ZodOptional<z.ZodString>;
|
|
37
|
+
enrollmentCapable: z.ZodOptional<z.ZodBoolean>;
|
|
38
|
+
isDeliveryAgent: z.ZodOptional<z.ZodBoolean>;
|
|
39
|
+
}, z.ZodTypeAny, "passthrough">, z.objectInputType<{
|
|
40
|
+
agentId: z.ZodOptional<z.ZodString>;
|
|
41
|
+
enrollmentCapable: z.ZodOptional<z.ZodBoolean>;
|
|
42
|
+
isDeliveryAgent: z.ZodOptional<z.ZodBoolean>;
|
|
43
|
+
}, z.ZodTypeAny, "passthrough">>;
|
|
44
|
+
export type McpApiKeyMetadata = z.infer<typeof McpApiKeyMetadataSchema>;
|
|
45
|
+
/**
|
|
46
|
+
* Protocol-owned result boundary for host authentication.
|
|
47
|
+
*/
|
|
48
|
+
export declare const McpResolvedIdentitySchema: z.ZodEffects<z.ZodObject<{
|
|
49
|
+
userId: z.ZodString;
|
|
50
|
+
agentId: z.ZodOptional<z.ZodString>;
|
|
51
|
+
isSessionAuth: z.ZodOptional<z.ZodBoolean>;
|
|
52
|
+
enrollmentCapable: z.ZodOptional<z.ZodBoolean>;
|
|
53
|
+
isDeliveryAgent: z.ZodOptional<z.ZodBoolean>;
|
|
54
|
+
networkScopeId: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
55
|
+
clientSurface: z.ZodOptional<z.ZodEnum<["telegram", "web"]>>;
|
|
56
|
+
}, "strict", z.ZodTypeAny, {
|
|
57
|
+
userId: string;
|
|
58
|
+
agentId?: string | undefined;
|
|
59
|
+
clientSurface?: "telegram" | "web" | undefined;
|
|
60
|
+
networkScopeId?: string | null | undefined;
|
|
61
|
+
enrollmentCapable?: boolean | undefined;
|
|
62
|
+
isDeliveryAgent?: boolean | undefined;
|
|
63
|
+
isSessionAuth?: boolean | undefined;
|
|
64
|
+
}, {
|
|
65
|
+
userId: string;
|
|
66
|
+
agentId?: string | undefined;
|
|
67
|
+
clientSurface?: "telegram" | "web" | undefined;
|
|
68
|
+
networkScopeId?: string | null | undefined;
|
|
69
|
+
enrollmentCapable?: boolean | undefined;
|
|
70
|
+
isDeliveryAgent?: boolean | undefined;
|
|
71
|
+
isSessionAuth?: boolean | undefined;
|
|
72
|
+
}>, {
|
|
73
|
+
userId: string;
|
|
74
|
+
agentId?: string | undefined;
|
|
75
|
+
clientSurface?: "telegram" | "web" | undefined;
|
|
76
|
+
networkScopeId?: string | null | undefined;
|
|
77
|
+
enrollmentCapable?: boolean | undefined;
|
|
78
|
+
isDeliveryAgent?: boolean | undefined;
|
|
79
|
+
isSessionAuth?: boolean | undefined;
|
|
80
|
+
}, {
|
|
81
|
+
userId: string;
|
|
82
|
+
agentId?: string | undefined;
|
|
83
|
+
clientSurface?: "telegram" | "web" | undefined;
|
|
84
|
+
networkScopeId?: string | null | undefined;
|
|
85
|
+
enrollmentCapable?: boolean | undefined;
|
|
86
|
+
isDeliveryAgent?: boolean | undefined;
|
|
87
|
+
isSessionAuth?: boolean | undefined;
|
|
88
|
+
}>;
|
|
89
|
+
export type McpResolvedIdentity = z.infer<typeof McpResolvedIdentitySchema>;
|
|
@@ -1 +1,49 @@
|
|
|
1
|
-
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
/**
|
|
3
|
+
* Transport-neutral auth input extracted at the HTTP boundary.
|
|
4
|
+
*/
|
|
5
|
+
export const McpAuthInputSchema = z.object({
|
|
6
|
+
bearerToken: z.string().min(1).optional(),
|
|
7
|
+
apiKey: z.string().min(1).optional(),
|
|
8
|
+
clientSurface: z.enum(['telegram', 'web']).optional(),
|
|
9
|
+
telegramHandle: z.string().min(1).optional(),
|
|
10
|
+
telegramUsername: z.string().min(1).optional(),
|
|
11
|
+
}).strict();
|
|
12
|
+
/**
|
|
13
|
+
* Trusted API-key metadata used by the host auth composition.
|
|
14
|
+
*
|
|
15
|
+
* Enrollment and delivery flags are explicit opt-ins. Unknown metadata is
|
|
16
|
+
* ignored so unrelated Better Auth metadata remains forward-compatible.
|
|
17
|
+
*/
|
|
18
|
+
export const McpApiKeyMetadataSchema = z.object({
|
|
19
|
+
agentId: z.string().min(1).optional(),
|
|
20
|
+
enrollmentCapable: z.boolean().optional(),
|
|
21
|
+
isDeliveryAgent: z.boolean().optional(),
|
|
22
|
+
}).passthrough();
|
|
23
|
+
/**
|
|
24
|
+
* Protocol-owned result boundary for host authentication.
|
|
25
|
+
*/
|
|
26
|
+
export const McpResolvedIdentitySchema = z.object({
|
|
27
|
+
userId: z.string().min(1),
|
|
28
|
+
agentId: z.string().min(1).optional(),
|
|
29
|
+
isSessionAuth: z.boolean().optional(),
|
|
30
|
+
enrollmentCapable: z.boolean().optional(),
|
|
31
|
+
isDeliveryAgent: z.boolean().optional(),
|
|
32
|
+
networkScopeId: z.string().min(1).nullable().optional(),
|
|
33
|
+
clientSurface: z.enum(['telegram', 'web']).optional(),
|
|
34
|
+
}).strict().superRefine((identity, ctx) => {
|
|
35
|
+
if (identity.isSessionAuth === true && identity.agentId) {
|
|
36
|
+
ctx.addIssue({
|
|
37
|
+
code: z.ZodIssueCode.custom,
|
|
38
|
+
path: ['agentId'],
|
|
39
|
+
message: 'Session-authenticated identities cannot carry an agent ID.',
|
|
40
|
+
});
|
|
41
|
+
}
|
|
42
|
+
if (identity.isDeliveryAgent === true && !identity.agentId) {
|
|
43
|
+
ctx.addIssue({
|
|
44
|
+
code: z.ZodIssueCode.custom,
|
|
45
|
+
path: ['isDeliveryAgent'],
|
|
46
|
+
message: 'Delivery-agent designation requires an agent ID.',
|
|
47
|
+
});
|
|
48
|
+
}
|
|
49
|
+
});
|