@indexnetwork/protocol 4.3.4-rc.309.1 → 4.3.4-rc.310.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/IMPLEMENTATION.md +2 -2
- package/dist/chat/chat.agent.d.ts +1 -1
- package/dist/chat/chat.agent.js +1 -1
- package/dist/chat/chat.agent.js.map +1 -1
- package/dist/chat/chat.prompt.js +30 -30
- package/dist/chat/chat.prompt.js.map +1 -1
- package/dist/chat/chat.prompt.modules.js +8 -8
- package/dist/chat/chat.prompt.modules.js.map +1 -1
- package/dist/chat/chat.state.d.ts +1 -1
- package/dist/chat/chat.state.d.ts.map +1 -1
- package/dist/chat/chat.state.js.map +1 -1
- package/dist/chat/tests/chat.graph.mocks.d.ts +1 -1
- package/dist/chat/tests/chat.graph.mocks.js +1 -1
- package/dist/chat/tests/chat.graph.mocks.js.map +1 -1
- package/dist/contact/contact.tools.js +6 -6
- package/dist/contact/contact.tools.js.map +1 -1
- package/dist/enrichment/enrichment.tools.js +7 -7
- package/dist/enrichment/enrichment.tools.js.map +1 -1
- package/dist/integration/integration.tools.js +1 -1
- package/dist/integration/integration.tools.js.map +1 -1
- package/dist/intent/intent.graph.d.ts.map +1 -1
- package/dist/intent/intent.graph.js +9 -9
- package/dist/intent/intent.graph.js.map +1 -1
- package/dist/intent/intent.indexer.d.ts +1 -1
- package/dist/intent/intent.indexer.js +4 -4
- package/dist/intent/intent.indexer.js.map +1 -1
- package/dist/intent/intent.state.d.ts +7 -7
- package/dist/intent/intent.state.js +7 -7
- package/dist/intent/intent.state.js.map +1 -1
- package/dist/intent/intent.tools.js +26 -26
- package/dist/intent/intent.tools.js.map +1 -1
- package/dist/maintenance/maintenance.graph.d.ts +2 -2
- package/dist/maintenance/maintenance.graph.d.ts.map +1 -1
- package/dist/maintenance/maintenance.graph.js.map +1 -1
- package/dist/mcp/mcp.server.d.ts +2 -2
- package/dist/mcp/mcp.server.js +4 -4
- package/dist/mcp/mcp.server.js.map +1 -1
- package/dist/network/indexer/indexer.graph.d.ts +2 -2
- package/dist/network/indexer/indexer.graph.d.ts.map +1 -1
- package/dist/network/indexer/indexer.graph.js +13 -13
- package/dist/network/indexer/indexer.graph.js.map +1 -1
- package/dist/network/indexer/indexer.state.d.ts +3 -3
- package/dist/network/indexer/indexer.state.d.ts.map +1 -1
- package/dist/network/indexer/indexer.state.js +4 -4
- package/dist/network/indexer/indexer.state.js.map +1 -1
- package/dist/network/membership/membership.graph.d.ts +1 -1
- package/dist/network/membership/membership.graph.js +21 -21
- package/dist/network/membership/membership.graph.js.map +1 -1
- package/dist/network/membership/membership.state.d.ts +2 -2
- package/dist/network/membership/membership.state.js +2 -2
- package/dist/network/membership/membership.state.js.map +1 -1
- package/dist/network/network.graph.d.ts +1 -1
- package/dist/network/network.graph.d.ts.map +1 -1
- package/dist/network/network.graph.js +13 -13
- package/dist/network/network.graph.js.map +1 -1
- package/dist/network/network.state.d.ts +3 -3
- package/dist/network/network.state.d.ts.map +1 -1
- package/dist/network/network.state.js +3 -3
- package/dist/network/network.state.js.map +1 -1
- package/dist/network/network.tools.d.ts.map +1 -1
- package/dist/network/network.tools.js +95 -107
- package/dist/network/network.tools.js.map +1 -1
- package/dist/opportunity/opportunity.discover.d.ts +1 -1
- package/dist/opportunity/opportunity.discover.d.ts.map +1 -1
- package/dist/opportunity/opportunity.discover.js +1 -1
- package/dist/opportunity/opportunity.discover.js.map +1 -1
- package/dist/opportunity/opportunity.graph.js +10 -10
- package/dist/opportunity/opportunity.graph.js.map +1 -1
- package/dist/opportunity/opportunity.introducer.d.ts +3 -3
- package/dist/opportunity/opportunity.introducer.d.ts.map +1 -1
- package/dist/opportunity/opportunity.introducer.js +2 -2
- package/dist/opportunity/opportunity.introducer.js.map +1 -1
- package/dist/opportunity/opportunity.state.d.ts +1 -1
- package/dist/opportunity/opportunity.state.js +1 -1
- package/dist/opportunity/opportunity.state.js.map +1 -1
- package/dist/opportunity/opportunity.tools.js +18 -18
- package/dist/opportunity/opportunity.tools.js.map +1 -1
- package/dist/premise/premise.indexer.d.ts +1 -1
- package/dist/premise/premise.indexer.js +5 -5
- package/dist/premise/premise.indexer.js.map +1 -1
- package/dist/questioner/questioner.tools.js +1 -1
- package/dist/questioner/questioner.tools.js.map +1 -1
- package/dist/shared/agent/tool.factory.d.ts +1 -1
- package/dist/shared/agent/tool.factory.js +1 -1
- package/dist/shared/agent/tool.factory.js.map +1 -1
- package/dist/shared/agent/tool.helpers.d.ts +6 -6
- package/dist/shared/agent/tool.helpers.d.ts.map +1 -1
- package/dist/shared/agent/tool.helpers.js +2 -2
- package/dist/shared/agent/tool.helpers.js.map +1 -1
- package/dist/shared/agent/tool.scope.js +1 -1
- package/dist/shared/agent/tool.scope.js.map +1 -1
- package/dist/shared/agent/utility.tools.js +20 -20
- package/dist/shared/agent/utility.tools.js.map +1 -1
- package/dist/shared/interfaces/database.interface.d.ts +59 -59
- package/dist/shared/interfaces/database.interface.d.ts.map +1 -1
- package/dist/shared/interfaces/database.interface.js.map +1 -1
- package/dist/shared/interfaces/embedder.interface.d.ts +2 -2
- package/dist/shared/interfaces/embedder.interface.d.ts.map +1 -1
- package/dist/shared/interfaces/embedder.interface.js.map +1 -1
- package/package.json +1 -1
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { Annotation } from "@langchain/langgraph";
|
|
2
2
|
/**
|
|
3
|
-
*
|
|
3
|
+
* Network Graph State.
|
|
4
4
|
* Handles CRUD operations for indexes (communities).
|
|
5
5
|
*
|
|
6
6
|
* Flow:
|
|
@@ -10,7 +10,7 @@ export const NetworkGraphState = Annotation.Root({
|
|
|
10
10
|
// --- Core Inputs (from ChatGraph via ToolContext) ---
|
|
11
11
|
/** User performing the action. Always required. */
|
|
12
12
|
userId: (Annotation),
|
|
13
|
-
/** Target
|
|
13
|
+
/** Target network ID. Required for read/update/delete. From ChatGraph or tool arg. */
|
|
14
14
|
networkId: Annotation({
|
|
15
15
|
reducer: (_, next) => next,
|
|
16
16
|
default: () => undefined,
|
|
@@ -31,7 +31,7 @@ export const NetworkGraphState = Annotation.Root({
|
|
|
31
31
|
reducer: (_, next) => next,
|
|
32
32
|
default: () => undefined,
|
|
33
33
|
}),
|
|
34
|
-
/** When true and
|
|
34
|
+
/** When true and network-scoped, read returns all user indexes (not just scoped one). */
|
|
35
35
|
showAll: Annotation({
|
|
36
36
|
reducer: (_, next) => next,
|
|
37
37
|
default: () => false,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"network.state.js","sourceRoot":"/","sources":["network/network.state.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,sBAAsB,CAAC;AAElD;;;;;;GAMG;AACH,MAAM,CAAC,MAAM,iBAAiB,GAAG,UAAU,CAAC,IAAI,CAAC;IAC/C,uDAAuD;IAEvD,mDAAmD;IACnD,MAAM,EAAE,CAAA,UAAkB,CAAA;IAE1B,
|
|
1
|
+
{"version":3,"file":"network.state.js","sourceRoot":"/","sources":["network/network.state.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,sBAAsB,CAAC;AAElD;;;;;;GAMG;AACH,MAAM,CAAC,MAAM,iBAAiB,GAAG,UAAU,CAAC,IAAI,CAAC;IAC/C,uDAAuD;IAEvD,mDAAmD;IACnD,MAAM,EAAE,CAAA,UAAkB,CAAA;IAE1B,sFAAsF;IACtF,SAAS,EAAE,UAAU,CAAqB;QACxC,OAAO,EAAE,CAAC,CAAC,EAAE,IAAI,EAAE,EAAE,CAAC,IAAI;QAC1B,OAAO,EAAE,GAAG,EAAE,CAAC,SAAS;KACzB,CAAC;IAEF,sBAAsB;IACtB,aAAa,EAAE,UAAU,CAA0C;QACjE,OAAO,EAAE,CAAC,IAAI,EAAE,IAAI,EAAE,EAAE,CAAC,IAAI,IAAI,IAAI;QACrC,OAAO,EAAE,GAAG,EAAE,CAAC,MAAe;KAC/B,CAAC;IAEF,+BAA+B;IAE/B,4CAA4C;IAC5C,WAAW,EAAE,UAAU,CAKR;QACb,OAAO,EAAE,CAAC,CAAC,EAAE,IAAI,EAAE,EAAE,CAAC,IAAI;QAC1B,OAAO,EAAE,GAAG,EAAE,CAAC,SAAS;KACzB,CAAC;IAEF,yCAAyC;IACzC,WAAW,EAAE,UAAU,CAMR;QACb,OAAO,EAAE,CAAC,CAAC,EAAE,IAAI,EAAE,EAAE,CAAC,IAAI;QAC1B,OAAO,EAAE,GAAG,EAAE,CAAC,SAAS;KACzB,CAAC;IAEF,yFAAyF;IACzF,OAAO,EAAE,UAAU,CAAU;QAC3B,OAAO,EAAE,CAAC,CAAC,EAAE,IAAI,EAAE,EAAE,CAAC,IAAI;QAC1B,OAAO,EAAE,GAAG,EAAE,CAAC,KAAK;KACrB,CAAC;IAEF,kBAAkB;IAElB,4BAA4B;IAC5B,UAAU,EAAE,UAAU,CA8BP;QACb,OAAO,EAAE,CAAC,CAAC,EAAE,IAAI,EAAE,EAAE,CAAC,IAAI;QAC1B,OAAO,EAAE,GAAG,EAAE,CAAC,SAAS;KACzB,CAAC;IAEF,6CAA6C;IAC7C,cAAc,EAAE,UAAU,CAMX;QACb,OAAO,EAAE,CAAC,CAAC,EAAE,IAAI,EAAE,EAAE,CAAC,IAAI;QAC1B,OAAO,EAAE,GAAG,EAAE,CAAC,SAAS;KACzB,CAAC;IAEF,iDAAiD;IACjD,KAAK,EAAE,UAAU,CAAgB;QAC/B,OAAO,EAAE,CAAC,CAAC,EAAE,IAAI,EAAE,EAAE,CAAC,IAAI;QAC1B,OAAO,EAAE,GAAG,EAAE,CAAC,IAAI;KACpB,CAAC;CACH,CAAC,CAAC","sourcesContent":["import { Annotation } from \"@langchain/langgraph\";\n\n/**\n * Network Graph State.\n * Handles CRUD operations for indexes (communities).\n *\n * Flow:\n * START → routerNode → {createNode | readNode | updateNode | deleteNode} → END\n */\nexport const NetworkGraphState = Annotation.Root({\n // --- Core Inputs (from ChatGraph via ToolContext) ---\n\n /** User performing the action. Always required. */\n userId: Annotation<string>,\n\n /** Target network ID. Required for read/update/delete. From ChatGraph or tool arg. */\n networkId: Annotation<string | undefined>({\n reducer: (_, next) => next,\n default: () => undefined,\n }),\n\n /** Operation mode. */\n operationMode: Annotation<'create' | 'read' | 'update' | 'delete'>({\n reducer: (curr, next) => next ?? curr,\n default: () => 'read' as const,\n }),\n\n // --- Mode-Specific Inputs ---\n\n /** For create mode: index creation data. */\n createInput: Annotation<{\n title: string;\n prompt?: string;\n imageUrl?: string | null;\n joinPolicy?: 'anyone' | 'invite_only';\n } | undefined>({\n reducer: (_, next) => next,\n default: () => undefined,\n }),\n\n /** For update mode: fields to update. */\n updateInput: Annotation<{\n title?: string;\n prompt?: string | null;\n imageUrl?: string | null;\n joinPolicy?: 'anyone' | 'invite_only';\n allowGuestVibeCheck?: boolean;\n } | undefined>({\n reducer: (_, next) => next,\n default: () => undefined,\n }),\n\n /** When true and network-scoped, read returns all user indexes (not just scoped one). */\n showAll: Annotation<boolean>({\n reducer: (_, next) => next,\n default: () => false,\n }),\n\n // --- Outputs ---\n\n /** Output for read mode. */\n readResult: Annotation<{\n memberOf: Array<{\n networkId: string;\n title: string;\n prompt: string | null;\n autoAssign: boolean;\n isPersonal: boolean;\n joinedAt: Date;\n }>;\n owns: Array<{\n networkId: string;\n title: string;\n prompt: string | null;\n memberCount: number;\n intentCount: number;\n joinPolicy: string;\n }>;\n publicNetworks?: Array<{\n networkId: string;\n title: string;\n prompt: string | null;\n memberCount: number;\n owner: { name: string; avatar: string | null } | null;\n }>;\n stats: {\n memberOfCount: number;\n ownsCount: number;\n publicNetworksCount?: number;\n scopeNote?: string;\n };\n } | undefined>({\n reducer: (_, next) => next,\n default: () => undefined,\n }),\n\n /** Output for create/update/delete modes. */\n mutationResult: Annotation<{\n success: boolean;\n networkId?: string;\n title?: string;\n message?: string;\n error?: string;\n } | undefined>({\n reducer: (_, next) => next,\n default: () => undefined,\n }),\n\n /** Error message if graph could not complete. */\n error: Annotation<string | null>({\n reducer: (_, next) => next,\n default: () => null,\n }),\n});\n"]}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"network.tools.d.ts","sourceRoot":"/","sources":["network/network.tools.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,UAAU,EAAE,QAAQ,EAAE,MAAM,iCAAiC,CAAC;
|
|
1
|
+
{"version":3,"file":"network.tools.d.ts","sourceRoot":"/","sources":["network/network.tools.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,UAAU,EAAE,QAAQ,EAAE,MAAM,iCAAiC,CAAC;AAQ5E,wBAAgB,kBAAkB,CAAC,UAAU,EAAE,UAAU,EAAE,IAAI,EAAE,QAAQ,gDA+mBxE"}
|
|
@@ -2,7 +2,6 @@ import { z } from "zod";
|
|
|
2
2
|
import { requestContext } from "../shared/observability/request-context.js";
|
|
3
3
|
import { renderNetworkContext } from '../shared/network/metadata.renderer.js';
|
|
4
4
|
import { success, error, UUID_REGEX } from "../shared/agent/tool.helpers.js";
|
|
5
|
-
import { focusedNetworkId, focusedNetworkLabel } from "../shared/agent/tool.scope.js";
|
|
6
5
|
import { NetworkRecommender } from "./network.recommender.js";
|
|
7
6
|
// Lazy singleton — only instantiated on first onboarding ranking call so that
|
|
8
7
|
// importing this module does not require OPENROUTER_API_KEY at load time.
|
|
@@ -26,7 +25,7 @@ export function createNetworkTools(defineTool, deps) {
|
|
|
26
25
|
"**Returns:** Up to three lists — `memberOf` (networks the user joined), `owns` (networks the user created), and `publicNetworks` " +
|
|
27
26
|
"(publicly joinable communities the user is not yet a member of). Entries in `memberOf` include `isPersonal` set to `true` for the user's " +
|
|
28
27
|
"personal network.\n\n" +
|
|
29
|
-
"**Note:** In
|
|
28
|
+
"**Note:** In network-scoped chats, only the scoped network is returned. During onboarding, `orderedNetworkIds` " +
|
|
30
29
|
"may be returned alongside `publicNetworks` \u2014 a ranked array of network IDs ordered by relevance to the user's profile (omitted when ranking is unavailable or fails).",
|
|
31
30
|
querySchema: z.object({
|
|
32
31
|
userId: z.string().optional().describe("Must be the current user's ID or omitted. Cannot list another user's indexes."),
|
|
@@ -35,14 +34,12 @@ export function createNetworkTools(defineTool, deps) {
|
|
|
35
34
|
if (query.userId && query.userId.trim() !== context.userId) {
|
|
36
35
|
return error("You can only list your own indexes. Omit userId to see the current user's indexes.");
|
|
37
36
|
}
|
|
38
|
-
const scopedNetworkId = focusedNetworkId(context);
|
|
39
|
-
const scopedIndexLabel = focusedNetworkLabel(context);
|
|
40
37
|
const _readIndexGraphStart = Date.now();
|
|
41
38
|
const _readIndexTraceEmitter = requestContext.getStore()?.traceEmitter;
|
|
42
39
|
_readIndexTraceEmitter?.({ type: "graph_start", name: "index" });
|
|
43
40
|
const result = await graphs.index.invoke({
|
|
44
41
|
userId: context.userId,
|
|
45
|
-
networkId:
|
|
42
|
+
networkId: context.networkId || undefined,
|
|
46
43
|
operationMode: 'read',
|
|
47
44
|
showAll: false, // Never allow bypass - strict scope enforcement
|
|
48
45
|
});
|
|
@@ -60,13 +57,13 @@ export function createNetworkTools(defineTool, deps) {
|
|
|
60
57
|
...(Array.isArray(rr.publicNetworks) ? { publicNetworks: enrichWithContext(rr.publicNetworks) } : {}),
|
|
61
58
|
};
|
|
62
59
|
// When scoped, add clear metadata so model knows results are limited
|
|
63
|
-
if (
|
|
60
|
+
if (context.networkId) {
|
|
64
61
|
return success({
|
|
65
62
|
...enriched,
|
|
66
63
|
scopeRestriction: {
|
|
67
64
|
isScoped: true,
|
|
68
|
-
scopedToIndex:
|
|
69
|
-
message: `Results are limited to "${
|
|
65
|
+
scopedToIndex: context.indexName ?? context.networkId,
|
|
66
|
+
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.`,
|
|
70
67
|
},
|
|
71
68
|
_graphTimings: [{ name: 'index', durationMs: _readIndexGraphMs, agents: result.agentTimings ?? [] }],
|
|
72
69
|
});
|
|
@@ -143,41 +140,39 @@ export function createNetworkTools(defineTool, deps) {
|
|
|
143
140
|
});
|
|
144
141
|
const readIndexMemberships = defineTool({
|
|
145
142
|
name: "read_network_memberships",
|
|
146
|
-
description: "Reads
|
|
143
|
+
description: "Reads network membership information — who is in which community. Essential for understanding the social graph before " +
|
|
147
144
|
"creating introductions or exploring intents.\n\n" +
|
|
148
145
|
"**Usage modes:**\n" +
|
|
149
146
|
"- With `networkId` only: lists ALL members of that index — returns userId, name, avatar, permissions (owner/member), intentCount, and joinedAt. " +
|
|
150
147
|
"Use this to see who's in a community before browsing their intents or creating introductions.\n" +
|
|
151
|
-
"- With `userId` only (or omit for self): lists all
|
|
148
|
+
"- With `userId` only (or omit for self): lists all networks that user belongs to — returns networkId, networkTitle, permissions, joinedAt.\n" +
|
|
152
149
|
"- With both `networkId` and `userId`: checks whether that specific user is a member of that specific index (returns isMember boolean).\n\n" +
|
|
153
|
-
"**When to use:** Before creating introductions (need to verify shared
|
|
150
|
+
"**When to use:** Before creating introductions (need to verify shared network membership), to explore community members, " +
|
|
154
151
|
"or to check if a user belongs to a specific index.\n\n" +
|
|
155
152
|
"**Returns:** Member list with user details, or membership list with index details, or a membership check result.\n\n" +
|
|
156
|
-
"**Personal
|
|
153
|
+
"**Personal network semantics.** The personal network (`isPersonal: true` on the membership) is the user's " +
|
|
157
154
|
"contact list — members of that index are the user's contacts. For another user, this tool only reveals the " +
|
|
158
155
|
"indexes you already share with them.\n\n" +
|
|
159
156
|
"**Shared-context pattern.** To find overlap with another user: (1) omit `userId` to read your own " +
|
|
160
|
-
"memberships, (2) call this tool with the other person's actual `userId` to get the shared
|
|
157
|
+
"memberships, (2) call this tool with the other person's actual `userId` to get the shared networks, " +
|
|
161
158
|
"(3) call read_intents for each shared network to see what each is looking for there, (4) call " +
|
|
162
159
|
"read_user_contexts for the other party. That sequence gives you enough to decide whether to propose a " +
|
|
163
160
|
"direct connection or an introduction.",
|
|
164
161
|
querySchema: z.object({
|
|
165
|
-
networkId: z.string().optional().describe("
|
|
166
|
-
userId: z.string().optional().describe("User ID — lists that user's
|
|
162
|
+
networkId: z.string().optional().describe("Network UUID — lists all members of this network. Get from read_networks. In network-scoped chats, only the scoped index can be queried."),
|
|
163
|
+
userId: z.string().optional().describe("User ID — lists that user's network memberships. Omit to get the current user's memberships. When combined with networkId, checks if this user is in that specific index."),
|
|
167
164
|
}),
|
|
168
165
|
handler: async ({ context, query }) => {
|
|
169
|
-
const scopedNetworkId = focusedNetworkId(context);
|
|
170
|
-
const scopedIndexLabel = focusedNetworkLabel(context);
|
|
171
166
|
const networkId = query.networkId?.trim() || undefined;
|
|
172
167
|
const userId = query.userId?.trim() || undefined;
|
|
173
168
|
if (networkId && !UUID_REGEX.test(networkId)) {
|
|
174
|
-
return error("Invalid
|
|
169
|
+
return error("Invalid network ID format. Use the exact UUID from read_networks.");
|
|
175
170
|
}
|
|
176
171
|
// Mode 1: list members of an index
|
|
177
172
|
if (networkId && !userId) {
|
|
178
|
-
// Enforce strict scope: when chat is
|
|
179
|
-
if (
|
|
180
|
-
return error(`This chat is scoped to ${
|
|
173
|
+
// Enforce strict scope: when chat is network-scoped, only allow querying that index
|
|
174
|
+
if (context.networkId && networkId !== context.networkId) {
|
|
175
|
+
return error(`This chat is scoped to ${context.indexName ?? 'this network'}. You can only query members of this network.`);
|
|
181
176
|
}
|
|
182
177
|
const _readMembersGraphStart = Date.now();
|
|
183
178
|
const _readMembersTraceEmitter = requestContext.getStore()?.traceEmitter;
|
|
@@ -195,19 +190,19 @@ export function createNetworkTools(defineTool, deps) {
|
|
|
195
190
|
if (result.readResult) {
|
|
196
191
|
return success({ ...result.readResult, _graphTimings: [{ name: 'network_membership', durationMs: _readMembersGraphMs, agents: result.agentTimings ?? [] }] });
|
|
197
192
|
}
|
|
198
|
-
return error("Failed to fetch
|
|
193
|
+
return error("Failed to fetch network members.");
|
|
199
194
|
}
|
|
200
195
|
// Mode 2: list a user's memberships (indexes they belong to)
|
|
201
196
|
const targetUserId = userId || context.userId;
|
|
202
|
-
// Use userDb for own memberships, but systemDb access is implicit through shared
|
|
197
|
+
// Use userDb for own memberships, but systemDb access is implicit through shared network scope
|
|
203
198
|
let memberships;
|
|
204
199
|
if (targetUserId !== context.userId) {
|
|
205
|
-
// Cross-user access: systemDb will validate shared
|
|
200
|
+
// Cross-user access: systemDb will validate shared network membership
|
|
206
201
|
const callerMemberships = await userDb.getNetworkMemberships();
|
|
207
202
|
if (networkId) {
|
|
208
|
-
// Strict scope enforcement: when chat is
|
|
209
|
-
if (
|
|
210
|
-
return error(`This chat is scoped to ${
|
|
203
|
+
// Strict scope enforcement: when chat is network-scoped, only allow querying that index
|
|
204
|
+
if (context.networkId && networkId !== context.networkId) {
|
|
205
|
+
return error(`This chat is scoped to ${context.indexName ?? 'this network'}. You can only query membership in this community.`);
|
|
211
206
|
}
|
|
212
207
|
const callerInIndex = callerMemberships.some((m) => m.networkId === networkId);
|
|
213
208
|
if (!callerInIndex) {
|
|
@@ -218,37 +213,37 @@ export function createNetworkTools(defineTool, deps) {
|
|
|
218
213
|
if (isMember) {
|
|
219
214
|
return success({ isMember: true, userId: targetUserId, networkId });
|
|
220
215
|
}
|
|
221
|
-
return success({ isMember: false, userId: targetUserId, networkId, message: "User is not a member of this
|
|
216
|
+
return success({ isMember: false, userId: targetUserId, networkId, message: "User is not a member of this network." });
|
|
222
217
|
}
|
|
223
218
|
else {
|
|
224
|
-
// Strict scope enforcement: when chat is
|
|
225
|
-
if (
|
|
226
|
-
const isMember = await systemDb.isNetworkMember(
|
|
219
|
+
// Strict scope enforcement: when chat is network-scoped, only check the scoped index
|
|
220
|
+
if (context.networkId) {
|
|
221
|
+
const isMember = await systemDb.isNetworkMember(context.networkId, targetUserId);
|
|
227
222
|
if (isMember) {
|
|
228
223
|
return success({
|
|
229
224
|
isMember: true,
|
|
230
225
|
userId: targetUserId,
|
|
231
|
-
networkId:
|
|
226
|
+
networkId: context.networkId,
|
|
232
227
|
scopeRestriction: {
|
|
233
228
|
isScoped: true,
|
|
234
|
-
scopedToIndex:
|
|
235
|
-
message: `This chat is scoped to "${
|
|
229
|
+
scopedToIndex: context.indexName ?? context.networkId,
|
|
230
|
+
message: `This chat is scoped to "${context.indexName ?? 'this network'}". Only membership in this community is shown.`,
|
|
236
231
|
},
|
|
237
232
|
});
|
|
238
233
|
}
|
|
239
234
|
return success({
|
|
240
235
|
isMember: false,
|
|
241
236
|
userId: targetUserId,
|
|
242
|
-
networkId:
|
|
237
|
+
networkId: context.networkId,
|
|
243
238
|
message: "User is not a member of this community.",
|
|
244
239
|
scopeRestriction: {
|
|
245
240
|
isScoped: true,
|
|
246
|
-
scopedToIndex:
|
|
247
|
-
message: `This chat is scoped to "${
|
|
241
|
+
scopedToIndex: context.indexName ?? context.networkId,
|
|
242
|
+
message: `This chat is scoped to "${context.indexName ?? 'this network'}". Only membership in this community was checked.`,
|
|
248
243
|
},
|
|
249
244
|
});
|
|
250
245
|
}
|
|
251
|
-
// Unscoped chat: show overlap with shared
|
|
246
|
+
// Unscoped chat: show overlap with shared networks (intersection of caller and target memberships)
|
|
252
247
|
const sharedIndexes = [];
|
|
253
248
|
for (const m of callerMemberships) {
|
|
254
249
|
if (await systemDb.isNetworkMember(m.networkId, targetUserId)) {
|
|
@@ -266,25 +261,26 @@ export function createNetworkTools(defineTool, deps) {
|
|
|
266
261
|
networkId: m.networkId,
|
|
267
262
|
networkTitle: m.networkTitle,
|
|
268
263
|
})),
|
|
269
|
-
note: "Only showing shared
|
|
264
|
+
note: "Only showing shared networks.",
|
|
270
265
|
});
|
|
271
266
|
}
|
|
272
267
|
}
|
|
273
268
|
else {
|
|
274
269
|
// Own memberships - use userDb
|
|
275
270
|
memberships = await userDb.getNetworkMemberships();
|
|
276
|
-
//
|
|
277
|
-
// what we show, not what we can reach.
|
|
278
|
-
//
|
|
279
|
-
|
|
280
|
-
|
|
271
|
+
// NOTE: context.networkId here is the *focus* of the scoped chat — it filters
|
|
272
|
+
// what we show, not what we can reach. indexScope is the reach; the bound
|
|
273
|
+
// network is one element of it. See IND-306 for the equivalent in read_intents.
|
|
274
|
+
// Strict scope enforcement: when chat is network-scoped, only return the scoped network membership
|
|
275
|
+
if (context.networkId && !networkId) {
|
|
276
|
+
memberships = memberships.filter((m) => m.networkId === context.networkId);
|
|
281
277
|
}
|
|
282
278
|
}
|
|
283
279
|
// If both networkId and userId: filter to that specific membership
|
|
284
280
|
if (networkId) {
|
|
285
|
-
// Strict scope enforcement: when chat is
|
|
286
|
-
if (
|
|
287
|
-
return error(`This chat is scoped to ${
|
|
281
|
+
// Strict scope enforcement: when chat is network-scoped, only allow querying that index
|
|
282
|
+
if (context.networkId && networkId !== context.networkId) {
|
|
283
|
+
return error(`This chat is scoped to ${context.indexName ?? 'this network'}. You can only query membership in this community.`);
|
|
288
284
|
}
|
|
289
285
|
const callerMemberships = await userDb.getNetworkMemberships();
|
|
290
286
|
const callerInIndex = targetUserId === context.userId ||
|
|
@@ -294,7 +290,7 @@ export function createNetworkTools(defineTool, deps) {
|
|
|
294
290
|
}
|
|
295
291
|
const match = memberships.find((m) => m.networkId === networkId);
|
|
296
292
|
if (!match) {
|
|
297
|
-
return success({ isMember: false, userId: targetUserId, networkId, message: "User is not a member of this
|
|
293
|
+
return success({ isMember: false, userId: targetUserId, networkId, message: "User is not a member of this network." });
|
|
298
294
|
}
|
|
299
295
|
return success({
|
|
300
296
|
isMember: true,
|
|
@@ -306,7 +302,7 @@ export function createNetworkTools(defineTool, deps) {
|
|
|
306
302
|
});
|
|
307
303
|
}
|
|
308
304
|
// When scoped, add clear metadata so model knows results are limited
|
|
309
|
-
if (
|
|
305
|
+
if (context.networkId && targetUserId === context.userId) {
|
|
310
306
|
return success({
|
|
311
307
|
userId: targetUserId,
|
|
312
308
|
count: memberships.length,
|
|
@@ -318,8 +314,8 @@ export function createNetworkTools(defineTool, deps) {
|
|
|
318
314
|
})),
|
|
319
315
|
scopeRestriction: {
|
|
320
316
|
isScoped: true,
|
|
321
|
-
scopedToIndex:
|
|
322
|
-
message: `Results are limited to "${
|
|
317
|
+
scopedToIndex: context.indexName ?? context.networkId,
|
|
318
|
+
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.`,
|
|
323
319
|
},
|
|
324
320
|
});
|
|
325
321
|
}
|
|
@@ -344,28 +340,26 @@ export function createNetworkTools(defineTool, deps) {
|
|
|
344
340
|
}).strict();
|
|
345
341
|
const updateNetwork = defineTool({
|
|
346
342
|
name: "update_network",
|
|
347
|
-
description: "Updates settings of an existing
|
|
343
|
+
description: "Updates settings of an existing network (community). Only the network owner can perform updates.\n\n" +
|
|
348
344
|
"**Updatable fields:** title (display name), prompt (purpose description used for intent auto-assignment), " +
|
|
349
345
|
"imageUrl (community avatar), joinPolicy ('anyone' for open or 'invite_only'), allowGuestVibeCheck (allow non-members to preview).\n\n" +
|
|
350
|
-
"**When to use:** When
|
|
346
|
+
"**When to use:** When a network owner wants to change their community's settings — e.g. update the purpose description, " +
|
|
351
347
|
"change from invite-only to open, or update the community image.\n\n" +
|
|
352
|
-
"**Important:** Changing the prompt affects how future intents are evaluated for auto-assignment to this
|
|
353
|
-
"Existing intent-
|
|
348
|
+
"**Important:** Changing the prompt affects how future intents are evaluated for auto-assignment to this network. " +
|
|
349
|
+
"Existing intent-network links are not re-evaluated automatically.\n\n" +
|
|
354
350
|
"**Returns:** Confirmation with the list of settings that were updated.",
|
|
355
351
|
querySchema: z.object({
|
|
356
|
-
networkId: z.string().optional().describe("
|
|
352
|
+
networkId: z.string().optional().describe("Network UUID to update. Get from read_networks. Defaults to the scoped index in network-scoped chats."),
|
|
357
353
|
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."),
|
|
358
354
|
}),
|
|
359
355
|
handler: async ({ context, query }) => {
|
|
360
|
-
const
|
|
361
|
-
const scopedIndexLabel = focusedNetworkLabel(context);
|
|
362
|
-
const effectiveIndexId = (query.networkId?.trim() || scopedNetworkId) ?? null;
|
|
356
|
+
const effectiveIndexId = (query.networkId?.trim() || context.networkId) ?? null;
|
|
363
357
|
if (!effectiveIndexId || !UUID_REGEX.test(effectiveIndexId)) {
|
|
364
358
|
return error("Valid networkId required.");
|
|
365
359
|
}
|
|
366
|
-
// Strict scope enforcement: when chat is
|
|
367
|
-
if (
|
|
368
|
-
return error(`This chat is scoped to ${
|
|
360
|
+
// Strict scope enforcement: when chat is network-scoped, only allow updating that index
|
|
361
|
+
if (context.networkId && effectiveIndexId !== context.networkId) {
|
|
362
|
+
return error(`This chat is scoped to ${context.indexName ?? 'this network'}. You can only update this community's settings.`);
|
|
369
363
|
}
|
|
370
364
|
const _updateNetworkGraphStart = Date.now();
|
|
371
365
|
const _updateNetworkTraceEmitter = requestContext.getStore()?.traceEmitter;
|
|
@@ -386,15 +380,15 @@ export function createNetworkTools(defineTool, deps) {
|
|
|
386
380
|
});
|
|
387
381
|
const createNetwork = defineTool({
|
|
388
382
|
name: "create_network",
|
|
389
|
-
description: "Creates a new
|
|
390
|
-
"**What is
|
|
391
|
-
"(complementary matches) between members. The
|
|
383
|
+
description: "Creates a new network (community/group). The authenticated user becomes the owner with full control over settings and membership.\n\n" +
|
|
384
|
+
"**What is a network?** A shared space where members post intents (what they're looking for) and the system discovers opportunities " +
|
|
385
|
+
"(complementary matches) between members. The network's prompt guides what kinds of intents belong.\n\n" +
|
|
392
386
|
"**When to use:** When the user wants to create a new community — e.g. a professional network, interest group, or project team.\n\n" +
|
|
393
|
-
"**Returns:** The new
|
|
387
|
+
"**Returns:** The new network's networkId (UUID) and title. Use the networkId to add members (create_network_membership), " +
|
|
394
388
|
"link intents (create_intent_index), or run discovery (discover_opportunities with networkId).",
|
|
395
389
|
querySchema: z.object({
|
|
396
|
-
title: z.string().describe("Display name of the
|
|
397
|
-
prompt: z.string().optional().describe("Description of what this community is about (e.g. 'Early-stage AI/ML founders in Berlin looking for co-founders, advisors, and investors'). Used by the system to evaluate which intents belong in this
|
|
390
|
+
title: z.string().describe("Display name of the network (e.g. 'AI Founders Berlin', 'Design Co-op'). Required."),
|
|
391
|
+
prompt: z.string().optional().describe("Description of what this community is about (e.g. 'Early-stage AI/ML founders in Berlin looking for co-founders, advisors, and investors'). Used by the system to evaluate which intents belong in this network. Highly recommended for better auto-assignment."),
|
|
398
392
|
imageUrl: z.string().url().optional().describe("URL for the community's avatar/image. Optional."),
|
|
399
393
|
joinPolicy: z.enum(['anyone', 'invite_only']).optional().describe("'anyone' = open (any user can self-join), 'invite_only' = only the owner can add members. Defaults to 'invite_only'."),
|
|
400
394
|
}),
|
|
@@ -427,31 +421,29 @@ export function createNetworkTools(defineTool, deps) {
|
|
|
427
421
|
_graphTimings: [{ name: 'index', durationMs: _createNetworkGraphMs, agents: result.agentTimings ?? [] }],
|
|
428
422
|
});
|
|
429
423
|
}
|
|
430
|
-
return error(result.mutationResult.error || "Failed to create
|
|
424
|
+
return error(result.mutationResult.error || "Failed to create network.");
|
|
431
425
|
}
|
|
432
|
-
return error("Failed to create
|
|
426
|
+
return error("Failed to create network.");
|
|
433
427
|
},
|
|
434
428
|
});
|
|
435
429
|
const deleteNetwork = defineTool({
|
|
436
430
|
name: "delete_network",
|
|
437
|
-
description: "Permanently deletes
|
|
438
|
-
"(remove all members first with delete_network_membership). Personal
|
|
439
|
-
"**When to use:** When the owner wants to disband a community. This is irreversible — all intent-
|
|
431
|
+
description: "Permanently deletes a network (community). Only the owner can delete, and the network must have no other members " +
|
|
432
|
+
"(remove all members first with delete_network_membership). Personal networks cannot be deleted.\n\n" +
|
|
433
|
+
"**When to use:** When the owner wants to disband a community. This is irreversible — all intent-network links to this network are removed.\n\n" +
|
|
440
434
|
"**Prerequisites:** Must be the owner. Must be the sole remaining member (remove others first).\n\n" +
|
|
441
|
-
"**Returns:** Confirmation that the
|
|
435
|
+
"**Returns:** Confirmation that the network was deleted.",
|
|
442
436
|
querySchema: z.object({
|
|
443
|
-
networkId: z.string().optional().describe("
|
|
437
|
+
networkId: z.string().optional().describe("Network UUID to delete. Get from read_networks. Defaults to the scoped index in network-scoped chats. Cannot be a personal network."),
|
|
444
438
|
}),
|
|
445
439
|
handler: async ({ context, query }) => {
|
|
446
|
-
const
|
|
447
|
-
const scopedIndexLabel = focusedNetworkLabel(context);
|
|
448
|
-
const networkId = query.networkId?.trim() || scopedNetworkId;
|
|
440
|
+
const networkId = query.networkId?.trim() || context.networkId;
|
|
449
441
|
if (!networkId || !UUID_REGEX.test(networkId)) {
|
|
450
442
|
return error("Valid networkId required.");
|
|
451
443
|
}
|
|
452
|
-
// Strict scope enforcement: when chat is
|
|
453
|
-
if (
|
|
454
|
-
return error(`This chat is scoped to ${
|
|
444
|
+
// Strict scope enforcement: when chat is network-scoped, only allow deleting that index
|
|
445
|
+
if (context.networkId && networkId !== context.networkId) {
|
|
446
|
+
return error(`This chat is scoped to ${context.indexName ?? 'this network'}. You can only delete this community.`);
|
|
455
447
|
}
|
|
456
448
|
const _deleteNetworkGraphStart = Date.now();
|
|
457
449
|
const _deleteNetworkTraceEmitter = requestContext.getStore()?.traceEmitter;
|
|
@@ -464,36 +456,34 @@ export function createNetworkTools(defineTool, deps) {
|
|
|
464
456
|
const _deleteNetworkGraphMs = Date.now() - _deleteNetworkGraphStart;
|
|
465
457
|
_deleteNetworkTraceEmitter?.({ type: "graph_end", name: "index", durationMs: _deleteNetworkGraphMs });
|
|
466
458
|
if (result.mutationResult && !result.mutationResult.success) {
|
|
467
|
-
return error(result.mutationResult.error || "Failed to delete
|
|
459
|
+
return error(result.mutationResult.error || "Failed to delete network.");
|
|
468
460
|
}
|
|
469
461
|
return success({ message: "Network deleted.", _graphTimings: [{ name: 'index', durationMs: _deleteNetworkGraphMs, agents: result.agentTimings ?? [] }] });
|
|
470
462
|
},
|
|
471
463
|
});
|
|
472
464
|
const createNetworkMembership = defineTool({
|
|
473
465
|
name: "create_network_membership",
|
|
474
|
-
description: "Adds a user as a member of
|
|
466
|
+
description: "Adds a user as a member of a network (community). Membership enables the user to post intents in the network and be discovered " +
|
|
475
467
|
"by other members through opportunity matching.\n\n" +
|
|
476
468
|
"**Usage modes:**\n" +
|
|
477
|
-
"- Omit userId: self-join (only works for
|
|
478
|
-
"- With userId: add another user (only the
|
|
479
|
-
"**When to use:** When the user wants to join an open community, or when
|
|
469
|
+
"- Omit userId: self-join (only works for networks with joinPolicy 'anyone').\n" +
|
|
470
|
+
"- With userId: add another user (only the network owner can do this for 'invite_only' indexes).\n\n" +
|
|
471
|
+
"**When to use:** When the user wants to join an open community, or when a network owner wants to invite someone.\n\n" +
|
|
480
472
|
"**Returns:** Confirmation that the member was added (or a note that they were already a member). " +
|
|
481
|
-
"After joining, the user's existing intents with autoAssign=true may be evaluated against the new
|
|
473
|
+
"After joining, the user's existing intents with autoAssign=true may be evaluated against the new network.",
|
|
482
474
|
querySchema: z.object({
|
|
483
|
-
userId: z.string().optional().describe("User ID to add as a member. Omit to join the
|
|
484
|
-
networkId: z.string().optional().describe("
|
|
475
|
+
userId: z.string().optional().describe("User ID to add as a member. Omit to join the network yourself. Get user IDs from read_user_contexts(query=name) or read_network_memberships."),
|
|
476
|
+
networkId: z.string().optional().describe("Network UUID to add the member to. Get from read_networks. Defaults to the scoped index in network-scoped chats."),
|
|
485
477
|
}),
|
|
486
478
|
handler: async ({ context, query }) => {
|
|
487
|
-
const
|
|
488
|
-
const scopedIndexLabel = focusedNetworkLabel(context);
|
|
489
|
-
const networkId = query.networkId?.trim() || scopedNetworkId;
|
|
479
|
+
const networkId = query.networkId?.trim() || context.networkId;
|
|
490
480
|
const targetUserId = query.userId?.trim() || context.userId;
|
|
491
481
|
if (!networkId || !UUID_REGEX.test(networkId)) {
|
|
492
|
-
return error("Invalid
|
|
482
|
+
return error("Invalid network ID format. Use the exact UUID from read_networks.");
|
|
493
483
|
}
|
|
494
|
-
// Strict scope enforcement: when chat is
|
|
495
|
-
if (
|
|
496
|
-
return error(`This chat is scoped to ${
|
|
484
|
+
// Strict scope enforcement: when chat is network-scoped, only allow adding to that index
|
|
485
|
+
if (context.networkId && networkId !== context.networkId) {
|
|
486
|
+
return error(`This chat is scoped to ${context.indexName ?? 'this network'}. You can only add members to this community.`);
|
|
497
487
|
}
|
|
498
488
|
const _createMembershipGraphStart = Date.now();
|
|
499
489
|
const _createMembershipTraceEmitter = requestContext.getStore()?.traceEmitter;
|
|
@@ -522,20 +512,18 @@ export function createNetworkTools(defineTool, deps) {
|
|
|
522
512
|
});
|
|
523
513
|
const deleteNetworkMembership = defineTool({
|
|
524
514
|
name: "delete_network_membership",
|
|
525
|
-
description: "Removes a user from
|
|
515
|
+
description: "Removes a user from a network (community). After removal, the user's intents are unlinked from this network " +
|
|
526
516
|
"and they can no longer participate in opportunity discovery within it.\n\n" +
|
|
527
|
-
"**Permissions:** Only the
|
|
528
|
-
"**When to use:** When
|
|
517
|
+
"**Permissions:** Only the network owner can remove members. The owner themselves cannot be removed (delete the network instead).\n\n" +
|
|
518
|
+
"**When to use:** When a network owner wants to remove a member from the community. " +
|
|
529
519
|
"Use read_network_memberships(networkId) first to get the userId of the member to remove.\n\n" +
|
|
530
520
|
"**Returns:** Confirmation that the member was removed.",
|
|
531
521
|
querySchema: z.object({
|
|
532
|
-
userId: z.string().describe("User ID of the member to remove. Get from read_network_memberships(networkId). Cannot be the
|
|
533
|
-
networkId: z.string().optional().describe("
|
|
522
|
+
userId: z.string().describe("User ID of the member to remove. Get from read_network_memberships(networkId). Cannot be the network owner."),
|
|
523
|
+
networkId: z.string().optional().describe("Network UUID to remove the member from. Get from read_networks. Defaults to the scoped index in network-scoped chats."),
|
|
534
524
|
}),
|
|
535
525
|
handler: async ({ context, query }) => {
|
|
536
|
-
const
|
|
537
|
-
const scopedIndexLabel = focusedNetworkLabel(context);
|
|
538
|
-
const networkId = query.networkId?.trim() || scopedNetworkId;
|
|
526
|
+
const networkId = query.networkId?.trim() || context.networkId;
|
|
539
527
|
const targetUserId = query.userId?.trim();
|
|
540
528
|
if (!networkId || !UUID_REGEX.test(networkId)) {
|
|
541
529
|
return error("Valid networkId required. Use the exact UUID from read_networks.");
|
|
@@ -543,9 +531,9 @@ export function createNetworkTools(defineTool, deps) {
|
|
|
543
531
|
if (!targetUserId) {
|
|
544
532
|
return error("userId is required.");
|
|
545
533
|
}
|
|
546
|
-
// Strict scope enforcement: when chat is
|
|
547
|
-
if (
|
|
548
|
-
return error(`This chat is scoped to ${
|
|
534
|
+
// Strict scope enforcement: when chat is network-scoped, only allow that index
|
|
535
|
+
if (context.networkId && networkId !== context.networkId) {
|
|
536
|
+
return error(`This chat is scoped to ${context.indexName ?? 'this network'}. You can only manage members of this community.`);
|
|
549
537
|
}
|
|
550
538
|
const _deleteMembershipGraphStart = Date.now();
|
|
551
539
|
const _deleteMembershipTraceEmitter = requestContext.getStore()?.traceEmitter;
|