@indexnetwork/protocol 6.14.0-rc.406.1 → 8.0.1-rc.417.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 +307 -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/capabilities/signals.tools.port.d.ts +1 -1
- 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 +10 -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 +13 -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.graph.js +4 -15
- package/dist/signals/application/intent.tools.js +34 -14
- package/dist/signals/domain/intent.proposal.d.ts +35 -0
- package/dist/signals/domain/intent.proposal.js +12 -0
- 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
package/dist/chat/chat.prompt.js
CHANGED
|
@@ -35,12 +35,13 @@ function buildCoreHead(ctx) {
|
|
|
35
35
|
return `You are Index. You help the right people find the user and help the user find them.
|
|
36
36
|
Here's what you can do:
|
|
37
37
|
Get to know the user: what they're building, what they care about, and what they're open to right now. They can tell you directly, or you can learn quietly from places like GitHub or LinkedIn.
|
|
38
|
-
|
|
38
|
+
Build useful signals: help the user create or refine what they are looking for, offering, or exploring. Approved signals are evaluated by background queues, and meaningful opportunities are persisted for later review.
|
|
39
|
+
Review opportunities: use persisted opportunity cards to help the user understand relevant connections and decide what to do next. Opportunities can appear on the home page and in chat history after background processing completes.
|
|
39
40
|
Learn about people: the user can share a name or link, and you research them, map shared ground, and help them decide whether it's worth reaching out. They can also add people to their network so potential connections are tracked over time.
|
|
40
41
|
Help the user stay connected: see who's in their communities, start new ones, add members, and connect people when it makes sense.
|
|
41
42
|
When the conversation is open-ended (e.g. after a greeting or after you've finished helping with something), you may invite the user with a short prompt like "What's on your mind?" — but do not end every message with this; use it sparingly and only when it fits naturally.
|
|
42
43
|
|
|
43
|
-
**CRITICAL:
|
|
44
|
+
**CRITICAL: Background processing, not this conversation, evaluates approved signals.** Do not claim that asking in chat starts background evaluation or produces live results. When the user asks for connections, help create or refine a signal, or review persisted opportunities. Do not promise when background results will appear; they may be available later on the home page or when the user reviews chat history.
|
|
44
45
|
|
|
45
46
|
## Voice and constraints
|
|
46
47
|
- **Identity**: You are not a search engine. You do not use hype, corporate, or professional networking language. You do not pressure users. You do not take external actions without explicit approval.
|
|
@@ -53,7 +54,7 @@ When the conversation is open-ended (e.g. after a greeting or after you've finis
|
|
|
53
54
|
Instead of "search", always use:
|
|
54
55
|
- "looking up" — for indexed data you already have
|
|
55
56
|
- "looking for" / "look for" — when describing what you're doing
|
|
56
|
-
- "find" / "finding" — for
|
|
57
|
+
- "find" / "finding" — to create or refine signals for background matching
|
|
57
58
|
- "check" — for verification
|
|
58
59
|
- "discover" — for exploration
|
|
59
60
|
|
|
@@ -165,8 +166,8 @@ ${focusedNetworkId(ctx) ? `6. **Community discovery (skipped — already in scop
|
|
|
165
166
|
|
|
166
167
|
8. **Wrap up** (must happen in the same response as step 7)
|
|
167
168
|
- Call \`complete_onboarding()\` — this is REQUIRED and marks onboarding as finished. It will fail unless the profile is confirmed and the first active signal exists.
|
|
168
|
-
- Close with: "You're all set.
|
|
169
|
-
- Offer next actions as a natural question (not buttons): "What do you want to do first?
|
|
169
|
+
- Close with: "You're all set. Your signals are matched in the background, and you can review persisted opportunities on your home page as they become available."
|
|
170
|
+
- Offer next actions as a natural question (not buttons): "What do you want to do first? We can refine your signals, explore who's in your network, or look into someone specific."
|
|
170
171
|
|
|
171
172
|
### CRITICAL: Profile Confirmation Handling
|
|
172
173
|
When the user says "yes", "looks good", "that's right", "correct", or any affirmation after you show them their profile:
|
|
@@ -288,8 +289,7 @@ All tools are simple read/write operations. No hidden logic.
|
|
|
288
289
|
| **create_intent_index** | intentId, networkId | Link intent to network |
|
|
289
290
|
| **read_intent_indexes** | intentId?, networkId?, userId? | Read intent↔network links |
|
|
290
291
|
| **delete_intent_index** | intentId, networkId | Unlink intent from network |
|
|
291
|
-
| **
|
|
292
|
-
| **list_opportunities** | networkId? | List draft and pending opportunities the user can act on. Use when user wants to review existing opportunities. |
|
|
292
|
+
| **list_opportunities** | networkId? | Read persisted opportunity cards. Use only to review existing background-created opportunities; use update_opportunity for their actions. |
|
|
293
293
|
| **update_opportunity** | opportunityId, status | Change status: pending (send draft or latent), accepted, rejected, expired |
|
|
294
294
|
| **scrape_url** | url, objective? | Extract text from web page |
|
|
295
295
|
| **read_docs** | topic? | Protocol documentation |
|
|
@@ -313,11 +313,11 @@ ${scopedIntentId
|
|
|
313
313
|
? `- This chat is scoped to one selected intent (id: ${scopedIntentId}). Only that intent is available here.
|
|
314
314
|
- **Scope enforcement**: read_intents returns exactly the selected intent. update_intent/delete_intent/create_intent_index/delete_intent_index must only act on that selected intent. Do not create a different intent from this chat.
|
|
315
315
|
- **Related context**: for any question about existing matches, call list_opportunities with no arguments; it is automatically narrowed to opportunities from this intent and includes negotiation-derived card context when available. For pending questions, call read_pending_questions with no arguments; it is automatically narrowed to direct intent questions plus discovery/negotiation questions tied to this intent's opportunities.
|
|
316
|
-
- **
|
|
316
|
+
- **Persisted opportunities**: list_opportunities with no intentId reviews persisted cards for this selected intent. If an intentId is supplied it must match ${scopedIntentId}.
|
|
317
317
|
- Never imply results represent the user's other intents.`
|
|
318
318
|
: scopedNetworkId
|
|
319
319
|
? `- This chat is scoped to network "${ctx.indexName ?? "Unknown"}" (id: ${scopedNetworkId}). Default networkId for create_intent is ${scopedNetworkId}. read_intents (no params) returns the caller's own intents across their reachable networks (the bound community plus their personal network) — there is no implicit "default networkId" for read_intents; pass ${scopedNetworkId} explicitly to browse all members' intents in this community.
|
|
320
|
-
- **Scope enforcement**: read_intents with no args returns caller-owned intents across the reachable networks (bound + personal). read_intents(networkId) browses all members' intents in that community. read_intents(userId) in a scoped chat reads that member's intents in the bound community.
|
|
320
|
+
- **Scope enforcement**: read_intents with no args returns caller-owned intents across the reachable networks (bound + personal). read_intents(networkId) browses all members' intents in that community. read_intents(userId) in a scoped chat reads that member's intents in the bound community. list_opportunities with no networkId arg is limited to this focused community only; the personal network is still used for self-owned writes/assignments, not for scoped opportunity visibility. create_intent still checks **all** of the user's intents across communities (to avoid duplicates and update similar ones). Do not infer "no similar signals" or "fresh slate" from an empty read_intents result here.
|
|
321
321
|
- **Communicating scope**: When tool results include \`scopeRestriction\`, inform the user that results are limited to this community and they may have other memberships not shown. Never imply the scoped results represent all their data.
|
|
322
322
|
- To query other communities, the user must start a new unscoped chat or switch to a different community.
|
|
323
323
|
- When presenting, you may use the network title; avoid being vocal about internal terminology unless the user asks.`
|
|
@@ -393,7 +393,7 @@ What NOT to narrate (group silently with the main action):
|
|
|
393
393
|
- Markdown: **bold** for emphasis, bullets for lists. Concise but complete.
|
|
394
394
|
- **Never expose IDs, UUIDs, field names, tool names, or code** to the user. Never mention internal tool names (e.g. read_user_contexts, create_intent, scrape_url) or suggest the user call them. Tools are invisible infrastructure — the user should only see natural language.
|
|
395
395
|
- **Never use internal vocabulary** (intent, index, opportunity, profile) in replies. In user-facing replies, avoid mentioning indexes (or communities) unless the user asked or it's one of: sign-up, leave, owner settings. Use neutral language otherwise.
|
|
396
|
-
- **Opportunity cards**: Never write a \`\`\`opportunity block yourself — always call
|
|
396
|
+
- **Opportunity cards**: Never write a \`\`\`opportunity block yourself — always call list_opportunities first. Only the tool provides valid, correctly-formatted blocks. When list_opportunities returns \`\`\`opportunity code blocks, you MUST include them exactly as-is in your response. These blocks are rendered as interactive cards in the UI. Do NOT summarize or rephrase them — copy them verbatim. Include a brief framing sentence (1–2 sentences max), then paste the cards one after another. Do NOT write individual descriptions for each person — the cards are self-contained and show the explanation. Do not enumerate or introduce each match in text before showing the cards.
|
|
397
397
|
- **Intent proposal cards**: Never write a \`\`\`intent_proposal block yourself — always call create_intent first. When create_intent returns \`\`\`intent_proposal code blocks, include them exactly as-is in your response (they contain proposalId and description; only the tool provides valid blocks). These blocks are rendered as interactive cards. Add a brief note that creating this intent enables background discovery of relevant people.
|
|
398
398
|
- For person references, prefer first names in user-facing copy. Use full names only when needed to disambiguate people with the same first name.
|
|
399
399
|
- Do not label intents as "goals" in user-facing language. Prefer: "what you're looking for", "your signals", "your interests".
|
|
@@ -52,108 +52,6 @@ function hasIntroductionArgs(recentTools) {
|
|
|
52
52
|
// ═══════════════════════════════════════════════════════════════════════════════
|
|
53
53
|
// MODULE DEFINITIONS
|
|
54
54
|
// ═══════════════════════════════════════════════════════════════════════════════
|
|
55
|
-
const discoveryModule = {
|
|
56
|
-
id: "discovery",
|
|
57
|
-
triggers: ["discover_opportunities", "update_opportunity", "list_opportunities"],
|
|
58
|
-
triggerFilter: (iterCtx) => !hasIntroductionArgs(iterCtx.recentTools),
|
|
59
|
-
content: () => `
|
|
60
|
-
### 1. User wants to find connections or discover (default for connection-seeking)
|
|
61
|
-
|
|
62
|
-
For open-ended connection-seeking ("find me a mentor", "who needs a React dev", "I want to meet people in AI", "looking for investors", "find me X"), run **discovery first**.
|
|
63
|
-
|
|
64
|
-
**CRITICAL: DO NOT create an intent first. Discovery comes FIRST.**
|
|
65
|
-
|
|
66
|
-
**Network scoping**: When the user says "in my network", "from my contacts", "people I know", "among my connections", or similar network-scoping language, pass the user's **personal network ID** as \`networkId\`. The personal network (\`isPersonal: true\` in preloaded memberships) contains the user's contacts — scoping discovery to it restricts results to people the user already knows. If no network-scoping language is used, do not pass a personal network ID — let discovery run across all networks as usual.
|
|
67
|
-
|
|
68
|
-
- Call \`discover_opportunities(searchQuery=user's request)\` IMMEDIATELY (with networkId when scoped).
|
|
69
|
-
- Do NOT call \`create_intent\` unless the user **explicitly** asks to "create", "save", "add", or "remember" an intent/signal.
|
|
70
|
-
- Phrases like "looking for X", "find me X", "I want to meet X", "I need X" are discovery requests — NOT intent creation requests.
|
|
71
|
-
- If the tool returns \`createIntentSuggested\` and \`suggestedIntentDescription\`, the system will create an intent and retry discovery automatically; use the final result (candidates or "no matches") for your reply.
|
|
72
|
-
- If the tool returns \`suggestIntentCreationForVisibility: true\` and \`suggestedIntentDescription\`, after presenting the opportunity cards ask the user whether they'd also like to create a signal so others can find them (e.g. *"Would you also like to create a signal for this so others can find you?"*). If the user agrees, call \`create_intent(description=suggestedIntentDescription)\` and include the returned \`\`\`intent_proposal block verbatim — this is the same proposal flow as explicit intent creation; the user approves or skips via the card. Ask only once per conversation; do not repeat the question on follow-up turns.
|
|
73
|
-
- When the tool indicates all results are exhausted (no remaining candidates), do NOT offer to "show more". Instead suggest the user create a signal so others can find them. This uses the same \`create_intent\` flow as above.
|
|
74
|
-
- If the user **explicitly** says they want to create/save an intent (e.g. "add a signal", "create an intent", "save that I'm looking for X", "remember this"), use pattern 2 instead.
|
|
75
|
-
|
|
76
|
-
### 1a. User wants to connect with a specific mentioned person
|
|
77
|
-
|
|
78
|
-
When the user mentions a specific person via @mention or name AND expresses interest in connecting, collaborating, or exploring overlap (e.g. "what can I do with @X", "connect me with @X", user says "yes" after you present shared context with someone):
|
|
79
|
-
|
|
80
|
-
**This is a direct connection — NOT an introduction (introductions connect two OTHER people).**
|
|
81
|
-
|
|
82
|
-
\`\`\`
|
|
83
|
-
1. If not already done: read_user_contexts(userId=X) + read_network_memberships(userId=X)
|
|
84
|
-
2. Find shared networks with the user (intersect with preloaded memberships)
|
|
85
|
-
3. If no shared networks: tell the user you can't find a connection path
|
|
86
|
-
4. discover_opportunities(targetUserId=X, searchQuery="<synthesized reason for connecting based on shared context>")
|
|
87
|
-
5. Present the opportunity card
|
|
88
|
-
\`\`\`
|
|
89
|
-
|
|
90
|
-
**Do NOT call read_intents before discover_opportunities here.** The opportunity tool fetches intents internally for both discovery and direct connection modes. Only introduction mode (partyUserIds + entities) requires pre-gathered intents.
|
|
91
|
-
|
|
92
|
-
The searchQuery should be a brief description of why they'd connect (e.g. "shared interest in design and technology, both in Kernel community"). This gives the evaluator context for scoring.
|
|
93
|
-
|
|
94
|
-
### 7. Opportunities in chat
|
|
95
|
-
|
|
96
|
-
- **discover_opportunities** — discovers new connections (discovery, introduction, or direct connection).
|
|
97
|
-
- **list_opportunities** — lists existing draft and pending opportunities the user can act on.
|
|
98
|
-
|
|
99
|
-
When the user asks to review, revise, check, or see their current opportunities, call \`list_opportunities\`. Only use \`discover_opportunities\` for discovery ("find me connections"), introductions, or direct connections.
|
|
100
|
-
|
|
101
|
-
When either tool returns \`\`\`opportunity code blocks, include them verbatim in your reply so they render as cards.
|
|
102
|
-
|
|
103
|
-
When \`discover_opportunities\` returns a \`questions\` array, do **not** rephrase or summarize them in your prose. The frontend renders them as an interactive decision card surface. You may write a single short line referencing that there are decision prompts below; otherwise, leave them alone.
|
|
104
|
-
|
|
105
|
-
Draft or latent opportunities can be sent (update_opportunity with status='pending'). Status translation: draft/latent → "draft", pending → "sent", accepted → "connected"
|
|
106
|
-
|
|
107
|
-
**CRITICAL: Only describe what the tool response confirms happened.** "pending" sends a notification — not a message or invite. "accepted" adds a contact — for ghost users, the invite email is sent only when the user opens a chat and messages them. Never claim you sent invites, connection requests, or messages on behalf of the user.
|
|
108
|
-
|
|
109
|
-
### Discovery-first; intent as follow-up
|
|
110
|
-
- For connection-seeking (find connections, discover, who's looking for X), use \`discover_opportunities(searchQuery=...)\` first. Do not lead with \`create_intent\` unless the user explicitly asks to create or save an intent.
|
|
111
|
-
- When the tool returns \`createIntentSuggested\`, the system may create an intent and retry; respond from the final discovery result.
|
|
112
|
-
- Visibility-signal follow-up: apply the Pattern 1 rule above (\`suggestIntentCreationForVisibility\` → ask once; on yes, call \`create_intent(description=suggestedIntentDescription)\` and include the returned \`\`\`intent_proposal block).
|
|
113
|
-
- When the tool response says "These are all the connections I found", suggest the user create a signal so others can discover them. Use the existing \`suggestIntentCreationForVisibility\` flow: call \`create_intent(description=suggestedIntentDescription)\` if the user agrees. Do not ask "Would you like to see more?" when there are no more candidates.
|
|
114
|
-
- **Introducer exception**: Never suggest signal/intent creation when \`introTargetUserId\` was used. The search describes the other person's needs, not the signed-in user's — creating a signal from it would be meaningless.
|
|
115
|
-
- Only call \`discover_opportunities\` for: (a) discovery ("find me connections"), (b) introductions between two other people, or (c) direct connection with a specific mentioned person (Pattern 1a).
|
|
116
|
-
`,
|
|
117
|
-
};
|
|
118
|
-
const introductionModule = {
|
|
119
|
-
id: "introduction",
|
|
120
|
-
triggers: ["discover_opportunities"],
|
|
121
|
-
excludes: ["discovery"],
|
|
122
|
-
triggerFilter: (iterCtx) => hasIntroductionArgs(iterCtx.recentTools),
|
|
123
|
-
content: () => `
|
|
124
|
-
### 6. Introduce two people
|
|
125
|
-
|
|
126
|
-
**An introduction is always between exactly two people.** Do not call discover_opportunities for an introduction unless you have exactly two parties (two distinct people to introduce to each other). The entities array must have exactly two entities. The introducer (current user) must not be included in the entities array; entities must refer to two distinct other users.
|
|
127
|
-
|
|
128
|
-
**You MUST gather all context before calling discover_opportunities. The tool does NOT fetch data internally.**
|
|
129
|
-
|
|
130
|
-
\`\`\`
|
|
131
|
-
1. read_network_memberships(userId=A) + read_network_memberships(userId=B) → find shared networks
|
|
132
|
-
2. If no shared networks: tell user they're not in any shared community
|
|
133
|
-
3. read_user_contexts(userId=A) + read_user_contexts(userId=B)
|
|
134
|
-
4. For each shared network: read_intents(networkId=X, userId=A) + read_intents(networkId=X, userId=B)
|
|
135
|
-
5. Summarize to user: "Here's what I found about A and B..."
|
|
136
|
-
6. discover_opportunities(partyUserIds=[A,B], entities=[{userId:A, profile:{...}, intents:[...], networkId:shared}, {userId:B, ...}], hint="user's reason")
|
|
137
|
-
7. Present the draft introduction
|
|
138
|
-
\`\`\`
|
|
139
|
-
|
|
140
|
-
The entities array must include each party's userId, profile data, intents from shared networks, and the shared networkId. The hint is the user's stated reason (e.g. "both AI devs"). If the user asks to introduce only one person or to "introduce" themselves to someone, explain that introductions connect two other people and suggest they name two people to connect.
|
|
141
|
-
|
|
142
|
-
### 6a. Discover who to introduce to someone
|
|
143
|
-
|
|
144
|
-
**When the user asks "who should I introduce to @Person" or "find connections for @Person"** — they want YOU to discover good connections for that person, presented as introduction cards.
|
|
145
|
-
|
|
146
|
-
\`\`\`
|
|
147
|
-
1. Identify the person's userId from the @mention (call it mentionedUserId)
|
|
148
|
-
2. discover_opportunities(introTargetUserId=mentionedUserId, searchQuery="<optional refinement>")
|
|
149
|
-
3. Present the returned cards (they will be formatted as introduction cards automatically)
|
|
150
|
-
\`\`\`
|
|
151
|
-
|
|
152
|
-
This is different from Pattern 6 (where user names BOTH parties). Here the user names ONE person and asks you to find connections for them. Do NOT use Pattern 6 for this — Pattern 6 requires both parties to be known upfront. Do NOT ask the user for a second person. Do NOT use targetUserId or partyUserIds. The system will find connections automatically.
|
|
153
|
-
|
|
154
|
-
**CRITICAL — no signal creation in introducer flows:** When \`introTargetUserId\` is used (Patterns 6 and 6a), the user is searching for connections on behalf of someone else — the search reflects the other person's needs, not the user's own. Do NOT suggest creating a signal or intent in this context. The search query describes what the *other person* needs (e.g. "biotech investors for Levi"), so creating a signal from it for the signed-in user would be wrong. Never offer signal/intent creation CTAs after introducer discovery — not for the other person (users can only create signals for themselves) and not for the signed-in user (the query doesn't represent their intent).
|
|
155
|
-
`,
|
|
156
|
-
};
|
|
157
55
|
const intentCreationModule = {
|
|
158
56
|
id: "intent-creation",
|
|
159
57
|
triggers: ["create_intent"],
|
|
@@ -312,8 +210,6 @@ const mentionsModule = {
|
|
|
312
210
|
// ═══════════════════════════════════════════════════════════════════════════════
|
|
313
211
|
/** All registered prompt modules. */
|
|
314
212
|
export const PROMPT_MODULES = [
|
|
315
|
-
discoveryModule,
|
|
316
|
-
introductionModule,
|
|
317
213
|
intentCreationModule,
|
|
318
214
|
intentManagementModule,
|
|
319
215
|
personLookupModule,
|
package/dist/chat/chat.tools.js
CHANGED
|
@@ -7,9 +7,11 @@ export function createChatTools(defineTool, deps) {
|
|
|
7
7
|
}
|
|
8
8
|
const listConversations = defineTool({
|
|
9
9
|
name: "list_conversations",
|
|
10
|
-
description: "Lists the authenticated user's past chat conversations
|
|
11
|
-
"asks about their prior chats, wants to resume a
|
|
12
|
-
"history. Only returns sessions the caller
|
|
10
|
+
description: "Lists the authenticated user's past H2A chat conversations (direct chats with the Index agent), " +
|
|
11
|
+
"most-recently-active first. Use when the user asks about their prior chats, wants to resume a " +
|
|
12
|
+
"conversation, or is orienting themselves in their own history. Only returns H2A sessions the caller " +
|
|
13
|
+
"participates in. Human-to-human (H2H) DMs are NEVER exposed through this tool, and A2A negotiation " +
|
|
14
|
+
"chats are only available via `list_negotiations` / `get_negotiation`.\n\n" +
|
|
13
15
|
"**Returns:** `conversations: [{ sessionId, title, messageCount, lastMessageAt, createdAt }]`. Use " +
|
|
14
16
|
"`sessionId` with `get_conversation` to read the full thread.",
|
|
15
17
|
querySchema: z.object({
|
|
@@ -28,10 +30,12 @@ export function createChatTools(defineTool, deps) {
|
|
|
28
30
|
});
|
|
29
31
|
const getConversation = defineTool({
|
|
30
32
|
name: "get_conversation",
|
|
31
|
-
description: "Fetches a single chat conversation
|
|
32
|
-
"Use after `list_conversations` has yielded a specific
|
|
33
|
-
"you to pick up a prior thread by topic or title. Returns
|
|
34
|
-
"the caller is not a participant
|
|
33
|
+
description: "Fetches a single H2A chat conversation (a direct chat with the Index agent) belonging to the " +
|
|
34
|
+
"authenticated user, including its messages. Use after `list_conversations` has yielded a specific " +
|
|
35
|
+
"`sessionId` — for example when the user asks you to pick up a prior thread by topic or title. Returns " +
|
|
36
|
+
"an error if the session does not exist, the caller is not a participant, or the session is not an H2A " +
|
|
37
|
+
"chat: human-to-human (H2H) DMs are NEVER exposed through this tool, and A2A negotiation transcripts " +
|
|
38
|
+
"are only available via `get_negotiation`.\n\n" +
|
|
35
39
|
"**Returns:** `{ sessionId, title, messageCount, lastMessageAt, createdAt, messages: [{ role, content, createdAt }] }`.",
|
|
36
40
|
querySchema: z.object({
|
|
37
41
|
sessionId: z.string().describe("Session UUID from list_conversations."),
|
|
@@ -158,7 +158,6 @@ export function createNegotiatorPersona(opts) {
|
|
|
158
158
|
buildSystemContent: (ctx, iterCtx) => buildNegotiatorSystemContent(ctx, opts, iterCtx),
|
|
159
159
|
createTools: (deps, preResolvedContext) => createNegotiatorTools(deps, preResolvedContext),
|
|
160
160
|
loopBehaviors: {
|
|
161
|
-
createIntentCallback: false,
|
|
162
161
|
hallucinationRecovery: true,
|
|
163
162
|
},
|
|
164
163
|
};
|
|
@@ -95,7 +95,6 @@ export const ONBOARDING_PERSONA = {
|
|
|
95
95
|
buildSystemContent: (ctx, iterCtx) => buildOnboardingSystemContent(ctx, iterCtx),
|
|
96
96
|
createTools: (deps, preResolvedContext) => createOnboardingTools(deps, preResolvedContext),
|
|
97
97
|
loopBehaviors: {
|
|
98
|
-
createIntentCallback: false,
|
|
99
98
|
hallucinationRecovery: true,
|
|
100
99
|
},
|
|
101
100
|
};
|
|
@@ -9,7 +9,7 @@ export declare const REPORTER_PERSONA_ID = "reporter";
|
|
|
9
9
|
* Exact positive allowlist for the reporter persona. New shared tools remain
|
|
10
10
|
* unavailable until they are explicitly reviewed here.
|
|
11
11
|
*/
|
|
12
|
-
export declare const REPORTER_TOOL_NAMES: readonly ["read_intents", "search_intents", "read_user_contexts", "preview_user_context", "read_premises", "read_networks", "read_network_memberships", "read_pending_questions", "list_opportunities", "
|
|
12
|
+
export declare const REPORTER_TOOL_NAMES: readonly ["read_intents", "search_intents", "read_user_contexts", "preview_user_context", "read_premises", "read_networks", "read_network_memberships", "read_pending_questions", "list_opportunities", "read_activity_summary"];
|
|
13
13
|
interface ReporterToolBoundary {
|
|
14
14
|
context: ResolvedToolContext;
|
|
15
15
|
userDb: UserDatabase;
|
|
@@ -23,7 +23,7 @@ export const REPORTER_TOOL_NAMES = [
|
|
|
23
23
|
"read_network_memberships",
|
|
24
24
|
"read_pending_questions",
|
|
25
25
|
"list_opportunities",
|
|
26
|
-
"
|
|
26
|
+
"read_activity_summary",
|
|
27
27
|
];
|
|
28
28
|
const REPORTER_TOOL_ALLOWLIST = new Set(REPORTER_TOOL_NAMES);
|
|
29
29
|
/** Filters a shared registry through the reporter's positive allowlist. */
|
|
@@ -203,7 +203,6 @@ export const REPORTER_PERSONA = {
|
|
|
203
203
|
createTools: (deps, preResolvedContext) => createReporterTools(deps, preResolvedContext),
|
|
204
204
|
resolveDeterministicResponse: (_ctx, iterCtx) => resolveReporterDeterministicResponse(iterCtx),
|
|
205
205
|
loopBehaviors: {
|
|
206
|
-
createIntentCallback: false,
|
|
207
206
|
hallucinationRecovery: false,
|
|
208
207
|
},
|
|
209
208
|
};
|
|
@@ -50,7 +50,7 @@ function buildBriefingGuidance(iterCtx) {
|
|
|
50
50
|
return `
|
|
51
51
|
|
|
52
52
|
## Opening briefing
|
|
53
|
-
This is the Agent-surface briefing kickoff. Call
|
|
53
|
+
This is the Agent-surface briefing kickoff. Call read_activity_summary first with the default window, then call the read tools needed to ground the four transparency asks below. Present one concise briefing covering:
|
|
54
54
|
1. summarize all my signals;
|
|
55
55
|
2. what did you do today?;
|
|
56
56
|
3. how do I look to others?;
|
|
@@ -65,7 +65,7 @@ function buildTurnGuidance(iterCtx) {
|
|
|
65
65
|
## Turn discipline
|
|
66
66
|
- The detailed four-section opening briefing is reserved for the exact reporter-briefing-kickoff marker. Do not repeat the full briefing or duplicate all reads for a focused follow-up.
|
|
67
67
|
- For every non-kickoff message, answer only the user's current request and perform only the reads needed to answer it.
|
|
68
|
-
${confirmation ? `- This message is only a natural-language acknowledgement. Never execute, claim execution, create, or reuse an action proposal in response. Do not call
|
|
68
|
+
${confirmation ? `- This message is only a natural-language acknowledgement. Never execute, claim execution, create, or reuse an action proposal in response. Do not call read_activity_summary or propose_cleanup_actions. Tell the owner to use the visible proposal card's Confirm control; “${currentMessage?.trim()}” is not endpoint confirmation.` : ""}`;
|
|
69
69
|
}
|
|
70
70
|
/**
|
|
71
71
|
* Builds the read-only reporter persona prompt.
|
|
@@ -105,11 +105,11 @@ ${roleGuidance}${actionGuidance}
|
|
|
105
105
|
|
|
106
106
|
## Hard rules
|
|
107
107
|
- Every factual claim, number, status, or trend must come from a tool result in the current turn. Never invent, estimate, or reuse an unverified metric.
|
|
108
|
-
- Use
|
|
108
|
+
- Use read_activity_summary for activity counts and read_intents/read_user_contexts/read_premises/read_networks/read_network_memberships/read_pending_questions for the underlying current state.
|
|
109
109
|
- Counterparties are identity-free aggregate data only: never reveal names, IDs, transcripts, message text, or per-counterparty rows. Do not infer what another person thinks from a match or negotiation.
|
|
110
110
|
${mutationRule}
|
|
111
111
|
- Do not write observed behavior back as a preference or premise. The user decides whether to act on a suggestion.
|
|
112
|
-
- If opportunity information is relevant, use only the restricted list_opportunities result or
|
|
112
|
+
- If opportunity information is relevant, use only the restricted list_opportunities result or read_activity_summary result. Do not expose raw evaluator reasoning, matchReason, or internal JSON. Any opportunity copy must be presenter-backed; this persona's list view is aggregate-only.
|
|
113
113
|
- Be transparent about missing data and the reporting window. Keep the response concise, calm, and useful without hype.
|
|
114
114
|
|
|
115
115
|
## Four transparency asks
|
|
@@ -124,7 +124,7 @@ Be ready to answer:
|
|
|
124
124
|
- Own context reads: read_user_contexts, preview_user_context, read_premises.
|
|
125
125
|
- Own community context: read_networks, read_network_memberships.
|
|
126
126
|
- Own pending-question reads: read_pending_questions (never answer them).
|
|
127
|
-
- Aggregate activity reporting:
|
|
127
|
+
- Aggregate activity reporting: read_activity_summary.
|
|
128
128
|
- Aggregate current opportunity reporting: list_opportunities (no counterpart identities or rows).${ctx.actionToolsEnabled ? "\n- Cleanup-action requests: propose_cleanup_actions (request only; owner confirmation is required)." : ""}
|
|
129
129
|
|
|
130
130
|
## Session identity (preloaded)
|
|
@@ -315,7 +315,6 @@ export const SIGNAL_PERSONA = {
|
|
|
315
315
|
createTools: (deps, preResolvedContext) => createSignalTools(deps, preResolvedContext),
|
|
316
316
|
loopBehaviors: {
|
|
317
317
|
// Direct discovery is absent, so its create-intent retry callback must stay off.
|
|
318
|
-
createIntentCallback: false,
|
|
319
318
|
// create_intent can legitimately return proposal cards; retain recovery/stripping.
|
|
320
319
|
hallucinationRecovery: true,
|
|
321
320
|
},
|
|
@@ -3,7 +3,22 @@ import { requestContext } from "../../shared/observability/request-context.js";
|
|
|
3
3
|
import { log } from "../../shared/observability/log.js";
|
|
4
4
|
import { renderNetworkContext } from "../../shared/network/metadata.renderer.js";
|
|
5
5
|
import { success, error, UUID_REGEX } from "../../shared/agent/tool.helpers.js";
|
|
6
|
+
import { focusedNetworkId } from "../../shared/agent/tool.scope.js";
|
|
6
7
|
import { NetworkRecommender } from "./network.recommender.js";
|
|
8
|
+
/**
|
|
9
|
+
* Resolves the community this caller is hard-bound to, if any.
|
|
10
|
+
*
|
|
11
|
+
* A user-driven network-scoped chat carries `context.networkId`; a network-scoped
|
|
12
|
+
* agent (personal/external API key bound to one community over MCP) instead
|
|
13
|
+
* carries the focused scope envelope (`scopeType='network'`, `scopeId`) applied
|
|
14
|
+
* at the MCP boundary, with `networkId` left unset. Both must clamp community
|
|
15
|
+
* and roster reads/writes to the exact bound community BEFORE any graph/adapter
|
|
16
|
+
* work — not only via the scoped-deps data clamp — so a foreign community is
|
|
17
|
+
* denied with a stable message and never read or mutated (IND-591).
|
|
18
|
+
*/
|
|
19
|
+
function boundCommunityId(context) {
|
|
20
|
+
return focusedNetworkId(context) ?? context.networkId;
|
|
21
|
+
}
|
|
7
22
|
// Lazy singleton — only instantiated on first onboarding ranking call so that
|
|
8
23
|
// importing this module does not require OPENROUTER_API_KEY at load time.
|
|
9
24
|
let recommender;
|
|
@@ -54,7 +69,7 @@ export function createNetworkTools(defineTool, deps) {
|
|
|
54
69
|
const readIndexes = defineTool({
|
|
55
70
|
name: "read_networks",
|
|
56
71
|
description: "Lists the authenticated user's networks (communities), including ones they own and public communities they can join.\n\n" +
|
|
57
|
-
"**When to use:** To find network IDs for scoping other operations (read_intents,
|
|
72
|
+
"**When to use:** To find network IDs for scoping other operations (read_intents, list_opportunities, read_network_memberships), " +
|
|
58
73
|
"or to show the user which communities they belong to.\n\n" +
|
|
59
74
|
"**Returns:** Up to three lists — `memberOf` (networks the user joined), `owns` (networks the user created), and `publicNetworks` " +
|
|
60
75
|
"(publicly joinable communities the user is not yet a member of). Entries in `memberOf` include `isPersonal` set to `true` for the user's " +
|
|
@@ -68,12 +83,13 @@ export function createNetworkTools(defineTool, deps) {
|
|
|
68
83
|
if (query.userId && query.userId.trim() !== context.userId) {
|
|
69
84
|
return error("You can only list your own networks. Omit userId to see the current user's networks.");
|
|
70
85
|
}
|
|
86
|
+
const boundNetworkId = boundCommunityId(context);
|
|
71
87
|
const _readGraphStart = Date.now();
|
|
72
88
|
const _readTraceEmitter = requestContext.getStore()?.traceEmitter;
|
|
73
89
|
_readTraceEmitter?.({ type: "graph_start", name: "index" });
|
|
74
90
|
const result = await graphs.index.invoke({
|
|
75
91
|
userId: context.userId,
|
|
76
|
-
networkId:
|
|
92
|
+
networkId: boundNetworkId || undefined,
|
|
77
93
|
operationMode: 'read',
|
|
78
94
|
showAll: false, // Never allow bypass — strict scope enforcement
|
|
79
95
|
});
|
|
@@ -91,12 +107,12 @@ export function createNetworkTools(defineTool, deps) {
|
|
|
91
107
|
...(Array.isArray(rr.publicNetworks) ? { publicNetworks: enrichWithContext(rr.publicNetworks) } : {}),
|
|
92
108
|
};
|
|
93
109
|
// When scoped, add clear metadata so model knows results are limited
|
|
94
|
-
if (
|
|
110
|
+
if (boundNetworkId) {
|
|
95
111
|
return success({
|
|
96
112
|
...enriched,
|
|
97
113
|
scopeRestriction: {
|
|
98
114
|
isScoped: true,
|
|
99
|
-
scopedToNetwork: context.indexName ??
|
|
115
|
+
scopedToNetwork: context.indexName ?? boundNetworkId,
|
|
100
116
|
message: `Results are limited to "${context.indexName ?? 'this network'}" because this chat is scoped to that community. The user may belong to other communities not shown here.`,
|
|
101
117
|
},
|
|
102
118
|
_graphTimings: [{ name: 'index', durationMs: _readGraphMs, agents: result.agentTimings ?? [] }],
|
|
@@ -192,13 +208,14 @@ export function createNetworkTools(defineTool, deps) {
|
|
|
192
208
|
handler: async ({ context, query }) => {
|
|
193
209
|
const networkId = query.networkId?.trim() || undefined;
|
|
194
210
|
const userId = query.userId?.trim() || undefined;
|
|
211
|
+
const boundNetworkId = boundCommunityId(context);
|
|
195
212
|
if (networkId && !UUID_REGEX.test(networkId)) {
|
|
196
213
|
return error("Invalid network ID format. Use the exact UUID from read_networks.");
|
|
197
214
|
}
|
|
198
215
|
// Mode 1: list members of a network
|
|
199
216
|
if (networkId && !userId) {
|
|
200
217
|
// Enforce strict scope: when chat is network-scoped, only allow querying that network
|
|
201
|
-
if (
|
|
218
|
+
if (boundNetworkId && networkId !== boundNetworkId) {
|
|
202
219
|
return error(`This chat is scoped to ${context.indexName ?? 'this network'}. You can only query members of this network.`);
|
|
203
220
|
}
|
|
204
221
|
const _readMembersStart = Date.now();
|
|
@@ -226,7 +243,7 @@ export function createNetworkTools(defineTool, deps) {
|
|
|
226
243
|
// Cross-user access: validate shared membership scope
|
|
227
244
|
const callerMemberships = await userDb.getNetworkMemberships();
|
|
228
245
|
if (networkId) {
|
|
229
|
-
if (
|
|
246
|
+
if (boundNetworkId && networkId !== boundNetworkId) {
|
|
230
247
|
return error(`This chat is scoped to ${context.indexName ?? 'this network'}. You can only query membership in this community.`);
|
|
231
248
|
}
|
|
232
249
|
const callerInNetwork = callerMemberships.some((m) => m.networkId === networkId);
|
|
@@ -241,16 +258,16 @@ export function createNetworkTools(defineTool, deps) {
|
|
|
241
258
|
}
|
|
242
259
|
else {
|
|
243
260
|
// Strict scope enforcement: when chat is network-scoped, only check the scoped network
|
|
244
|
-
if (
|
|
245
|
-
const isMember = await systemDb.isNetworkMember(
|
|
261
|
+
if (boundNetworkId) {
|
|
262
|
+
const isMember = await systemDb.isNetworkMember(boundNetworkId, targetUserId);
|
|
246
263
|
if (isMember) {
|
|
247
264
|
return success({
|
|
248
265
|
isMember: true,
|
|
249
266
|
userId: targetUserId,
|
|
250
|
-
networkId:
|
|
267
|
+
networkId: boundNetworkId,
|
|
251
268
|
scopeRestriction: {
|
|
252
269
|
isScoped: true,
|
|
253
|
-
scopedToNetwork: context.indexName ??
|
|
270
|
+
scopedToNetwork: context.indexName ?? boundNetworkId,
|
|
254
271
|
message: `This chat is scoped to "${context.indexName ?? 'this network'}". Only membership in this community is shown.`,
|
|
255
272
|
},
|
|
256
273
|
});
|
|
@@ -258,11 +275,11 @@ export function createNetworkTools(defineTool, deps) {
|
|
|
258
275
|
return success({
|
|
259
276
|
isMember: false,
|
|
260
277
|
userId: targetUserId,
|
|
261
|
-
networkId:
|
|
278
|
+
networkId: boundNetworkId,
|
|
262
279
|
message: "User is not a member of this community.",
|
|
263
280
|
scopeRestriction: {
|
|
264
281
|
isScoped: true,
|
|
265
|
-
scopedToNetwork: context.indexName ??
|
|
282
|
+
scopedToNetwork: context.indexName ?? boundNetworkId,
|
|
266
283
|
message: `This chat is scoped to "${context.indexName ?? 'this network'}". Only membership in this community was checked.`,
|
|
267
284
|
},
|
|
268
285
|
});
|
|
@@ -292,13 +309,13 @@ export function createNetworkTools(defineTool, deps) {
|
|
|
292
309
|
// Own memberships — use userDb
|
|
293
310
|
memberships = await userDb.getNetworkMemberships();
|
|
294
311
|
// Strict scope: when chat is network-scoped, only return the scoped network membership
|
|
295
|
-
if (
|
|
296
|
-
memberships = memberships.filter((m) => m.networkId ===
|
|
312
|
+
if (boundNetworkId && !networkId) {
|
|
313
|
+
memberships = memberships.filter((m) => m.networkId === boundNetworkId);
|
|
297
314
|
}
|
|
298
315
|
}
|
|
299
316
|
// If both networkId and userId: filter to that specific membership
|
|
300
317
|
if (networkId) {
|
|
301
|
-
if (
|
|
318
|
+
if (boundNetworkId && networkId !== boundNetworkId) {
|
|
302
319
|
return error(`This chat is scoped to ${context.indexName ?? 'this network'}. You can only query membership in this community.`);
|
|
303
320
|
}
|
|
304
321
|
const callerMemberships = await userDb.getNetworkMemberships();
|
|
@@ -321,7 +338,7 @@ export function createNetworkTools(defineTool, deps) {
|
|
|
321
338
|
});
|
|
322
339
|
}
|
|
323
340
|
// Own memberships in scoped chat
|
|
324
|
-
if (
|
|
341
|
+
if (boundNetworkId && targetUserId === context.userId) {
|
|
325
342
|
return success({
|
|
326
343
|
userId: targetUserId,
|
|
327
344
|
count: memberships.length,
|
|
@@ -333,7 +350,7 @@ export function createNetworkTools(defineTool, deps) {
|
|
|
333
350
|
})),
|
|
334
351
|
scopeRestriction: {
|
|
335
352
|
isScoped: true,
|
|
336
|
-
scopedToNetwork: context.indexName ??
|
|
353
|
+
scopedToNetwork: context.indexName ?? boundNetworkId,
|
|
337
354
|
message: `Results are limited to "${context.indexName ?? 'this network'}" because this chat is scoped to that community. The user may belong to other communities not shown here.`,
|
|
338
355
|
},
|
|
339
356
|
});
|
|
@@ -372,11 +389,12 @@ export function createNetworkTools(defineTool, deps) {
|
|
|
372
389
|
settings: updateNetworkSettingsSchema.describe("Object with fields to update. All fields are optional — only include the ones to change. title: display name. prompt: purpose description (used for intent auto-assignment). imageUrl: community image URL (null to remove). joinPolicy: 'anyone' or 'invite_only'. allowGuestVibeCheck: boolean."),
|
|
373
390
|
}),
|
|
374
391
|
handler: async ({ context, query }) => {
|
|
375
|
-
const
|
|
392
|
+
const boundNetworkId = boundCommunityId(context);
|
|
393
|
+
const effectiveNetworkId = (query.networkId?.trim() || boundNetworkId) ?? null;
|
|
376
394
|
if (!effectiveNetworkId || !UUID_REGEX.test(effectiveNetworkId)) {
|
|
377
395
|
return error("Valid networkId required.");
|
|
378
396
|
}
|
|
379
|
-
if (
|
|
397
|
+
if (boundNetworkId && effectiveNetworkId !== boundNetworkId) {
|
|
380
398
|
return error(`This chat is scoped to ${context.indexName ?? 'this network'}. You can only update this community's settings.`);
|
|
381
399
|
}
|
|
382
400
|
const _updateStart = Date.now();
|
|
@@ -403,7 +421,7 @@ export function createNetworkTools(defineTool, deps) {
|
|
|
403
421
|
"(complementary matches) between members. The network's prompt guides what kinds of intents belong.\n\n" +
|
|
404
422
|
"**When to use:** When the user wants to create a new community — e.g. a professional network, interest group, or project team.\n\n" +
|
|
405
423
|
"**Returns:** The new network's networkId (UUID) and title. Use the networkId to add members (create_network_membership), " +
|
|
406
|
-
"link intents (create_intent_index)
|
|
424
|
+
"link intents (create_intent_index). Approved signals are matched in the background; use list_opportunities only to review persisted opportunities.",
|
|
407
425
|
querySchema: z.object({
|
|
408
426
|
title: z.string().describe("Display name of the network (e.g. 'AI Founders Berlin', 'Design Co-op'). Required."),
|
|
409
427
|
prompt: z.string().optional().describe("Description of what this community is about. Used by the system to evaluate which intents belong in this network. Highly recommended for better auto-assignment."),
|
|
@@ -455,11 +473,12 @@ export function createNetworkTools(defineTool, deps) {
|
|
|
455
473
|
networkId: z.string().optional().describe("Network UUID to delete. Get from read_networks. Defaults to the scoped network in network-scoped chats. Cannot be a personal network."),
|
|
456
474
|
}),
|
|
457
475
|
handler: async ({ context, query }) => {
|
|
458
|
-
const
|
|
476
|
+
const boundNetworkId = boundCommunityId(context);
|
|
477
|
+
const networkId = query.networkId?.trim() || boundNetworkId;
|
|
459
478
|
if (!networkId || !UUID_REGEX.test(networkId)) {
|
|
460
479
|
return error("Valid networkId required.");
|
|
461
480
|
}
|
|
462
|
-
if (
|
|
481
|
+
if (boundNetworkId && networkId !== boundNetworkId) {
|
|
463
482
|
return error(`This chat is scoped to ${context.indexName ?? 'this network'}. You can only delete this community.`);
|
|
464
483
|
}
|
|
465
484
|
const _deleteStart = Date.now();
|
|
@@ -493,12 +512,13 @@ export function createNetworkTools(defineTool, deps) {
|
|
|
493
512
|
networkId: z.string().optional().describe("Network UUID to add the member to. Get from read_networks. Defaults to the scoped network in network-scoped chats."),
|
|
494
513
|
}),
|
|
495
514
|
handler: async ({ context, query }) => {
|
|
496
|
-
const
|
|
515
|
+
const boundNetworkId = boundCommunityId(context);
|
|
516
|
+
const networkId = query.networkId?.trim() || boundNetworkId;
|
|
497
517
|
const targetUserId = query.userId?.trim() || context.userId;
|
|
498
518
|
if (!networkId || !UUID_REGEX.test(networkId)) {
|
|
499
519
|
return error("Invalid network ID format. Use the exact UUID from read_networks.");
|
|
500
520
|
}
|
|
501
|
-
if (
|
|
521
|
+
if (boundNetworkId && networkId !== boundNetworkId) {
|
|
502
522
|
return error(`This chat is scoped to ${context.indexName ?? 'this network'}. You can only add members to this community.`);
|
|
503
523
|
}
|
|
504
524
|
const _createMemberStart = Date.now();
|
|
@@ -529,7 +549,7 @@ export function createNetworkTools(defineTool, deps) {
|
|
|
529
549
|
const deleteNetworkMembership = defineTool({
|
|
530
550
|
name: "delete_network_membership",
|
|
531
551
|
description: "Removes a user from a network (community). After removal, the user's intents are unlinked from this network " +
|
|
532
|
-
"and
|
|
552
|
+
"and their approved signals are no longer eligible for background matching within it.\n\n" +
|
|
533
553
|
"**Permissions:** Only the network owner can remove members. The owner themselves cannot be removed (delete the network instead).\n\n" +
|
|
534
554
|
"**When to use:** When a network owner wants to remove a member from the community. " +
|
|
535
555
|
"Use read_network_memberships(networkId) first to get the userId of the member to remove.\n\n" +
|
|
@@ -539,7 +559,8 @@ export function createNetworkTools(defineTool, deps) {
|
|
|
539
559
|
networkId: z.string().optional().describe("Network UUID to remove the member from. Get from read_networks. Defaults to the scoped network in network-scoped chats."),
|
|
540
560
|
}),
|
|
541
561
|
handler: async ({ context, query }) => {
|
|
542
|
-
const
|
|
562
|
+
const boundNetworkId = boundCommunityId(context);
|
|
563
|
+
const networkId = query.networkId?.trim() || boundNetworkId;
|
|
543
564
|
const targetUserId = query.userId?.trim();
|
|
544
565
|
if (!networkId || !UUID_REGEX.test(networkId)) {
|
|
545
566
|
return error("Valid networkId required. Use the exact UUID from read_networks.");
|
|
@@ -547,7 +568,7 @@ export function createNetworkTools(defineTool, deps) {
|
|
|
547
568
|
if (!targetUserId) {
|
|
548
569
|
return error("userId is required.");
|
|
549
570
|
}
|
|
550
|
-
if (
|
|
571
|
+
if (boundNetworkId && networkId !== boundNetworkId) {
|
|
551
572
|
return error(`This chat is scoped to ${context.indexName ?? 'this network'}. You can only manage members of this community.`);
|
|
552
573
|
}
|
|
553
574
|
const _deleteMemberStart = Date.now();
|