@indexnetwork/protocol 14.3.2-rc.477.1 → 17.0.0-rc.479.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 +141 -0
- package/IMPLEMENTATION.md +50 -52
- package/STABILITY.md +3 -3
- package/dist/chat/chat.agent.js +2 -3
- package/dist/chat/chat.graph.d.ts +41 -1
- package/dist/chat/chat.graph.js +108 -127
- package/dist/chat/negotiator.persona.d.ts +1 -1
- package/dist/chat/negotiator.persona.js +1 -4
- package/dist/chat/negotiator.prompt.js +1 -2
- package/dist/chat/onboarding.persona.js +0 -1
- package/dist/chat/reporter.persona.js +0 -1
- package/dist/chat/signal.persona.js +0 -1
- package/dist/contacts/application/contact.tools.d.ts +6 -9
- package/dist/contacts/application/contact.tools.js +11 -92
- package/dist/contacts/application/index.d.ts +2 -14
- package/dist/contacts/application/index.js +2 -13
- package/dist/contacts/domain/contact.types.d.ts +0 -23
- package/dist/contacts/domain/index.d.ts +2 -6
- package/dist/contacts/index.d.ts +1 -1
- package/dist/contacts/index.js +1 -1
- package/dist/contacts/ports/contact.repository.port.d.ts +5 -12
- package/dist/contacts/ports/contact.repository.port.js +4 -6
- package/dist/contacts/ports/contact.tools.port.d.ts +1 -3
- package/dist/contacts/ports/contact.tools.port.js +1 -2
- package/dist/contacts/ports/index.d.ts +1 -1
- package/dist/contacts/ports/index.js +1 -1
- package/dist/discovery/hyde.frame.d.ts +4 -4
- package/dist/discovery/hyde.graph.d.ts +71 -12
- package/dist/discovery/hyde.graph.js +337 -335
- package/dist/discovery/lens.inferrer.d.ts +6 -6
- package/dist/enrichment/enrichment.graph.d.ts +127 -9
- package/dist/enrichment/enrichment.graph.js +633 -655
- package/dist/enrichment/enrichment.state.d.ts +1 -1
- package/dist/enrichment/enrichment.tools.context-read.d.ts +10 -0
- package/dist/enrichment/enrichment.tools.context-read.js +353 -0
- package/dist/enrichment/enrichment.tools.context-write.d.ts +9 -0
- package/dist/enrichment/enrichment.tools.context-write.js +417 -0
- package/dist/enrichment/enrichment.tools.d.ts +11 -1
- package/dist/enrichment/enrichment.tools.helpers.d.ts +139 -0
- package/dist/enrichment/enrichment.tools.helpers.js +234 -0
- package/dist/enrichment/enrichment.tools.js +16 -975
- package/dist/index.d.ts +27 -50
- package/dist/index.js +17 -28
- package/dist/intents/application/intent.graph.d.ts +71 -67
- package/dist/intents/application/intent.graph.execute.d.ts +59 -0
- package/dist/intents/application/intent.graph.execute.js +301 -0
- package/dist/intents/application/intent.graph.infer.d.ts +44 -0
- package/dist/intents/application/intent.graph.infer.js +97 -0
- package/dist/intents/application/intent.graph.js +96 -888
- package/dist/intents/application/intent.graph.reconcile.d.ts +71 -0
- package/dist/intents/application/intent.graph.reconcile.js +274 -0
- package/dist/intents/application/intent.graph.shared.d.ts +70 -0
- package/dist/intents/application/intent.graph.shared.js +153 -0
- package/dist/intents/domain/intent.state.d.ts +1 -1
- package/dist/maintenance/maintenance.graph.d.ts +66 -3
- package/dist/maintenance/maintenance.graph.js +155 -156
- package/dist/mcp/mcp.authorization-policy.d.ts +24 -28
- package/dist/mcp/mcp.authorization-policy.js +14 -34
- package/dist/mcp/mcp.server.d.ts +3 -3
- package/dist/mcp/mcp.server.js +14 -20
- package/dist/negotiations/application/negotiation.candidates.d.ts +83 -0
- package/dist/negotiations/application/negotiation.candidates.js +162 -0
- package/dist/negotiations/application/negotiation.graph.d.ts +91 -157
- package/dist/negotiations/application/negotiation.graph.finalize.d.ts +5 -0
- package/dist/negotiations/application/negotiation.graph.finalize.js +280 -0
- package/dist/negotiations/application/negotiation.graph.init.d.ts +54 -0
- package/dist/negotiations/application/negotiation.graph.init.js +227 -0
- package/dist/negotiations/application/negotiation.graph.js +69 -1388
- package/dist/negotiations/application/negotiation.graph.screen.d.ts +23 -0
- package/dist/negotiations/application/negotiation.graph.screen.js +108 -0
- package/dist/negotiations/application/negotiation.graph.shared.d.ts +75 -0
- package/dist/negotiations/application/negotiation.graph.shared.js +125 -0
- package/dist/negotiations/application/negotiation.graph.turn.d.ts +105 -0
- package/dist/negotiations/application/negotiation.graph.turn.js +484 -0
- package/dist/negotiations/domain/negotiation.state.d.ts +1 -1
- package/dist/negotiations/domain/negotiation.state.js +0 -1
- package/dist/networks/application/indexer.graph.d.ts +165 -7
- package/dist/networks/application/indexer.graph.js +339 -388
- package/dist/networks/application/indexer.state.d.ts +1 -1
- package/dist/networks/application/membership.graph.d.ts +83 -5
- package/dist/networks/application/membership.graph.js +177 -207
- package/dist/networks/application/network.graph.d.ts +148 -5
- package/dist/networks/application/network.graph.js +249 -278
- package/dist/networks/domain/membership.state.d.ts +1 -1
- package/dist/networks/domain/network.state.d.ts +1 -1
- package/dist/opportunities/application/delivery-card.cache.d.ts +1 -1
- package/dist/opportunities/application/delivery-card.cache.js +2 -2
- package/dist/opportunities/application/index.d.ts +2 -2
- package/dist/opportunities/application/index.js +2 -2
- package/dist/opportunities/application/opportunity.evaluator.js +1 -1
- package/dist/opportunities/application/opportunity.graph.d.ts +686 -473
- package/dist/opportunities/application/opportunity.graph.discovery-strategies.d.ts +109 -0
- package/dist/opportunities/application/opportunity.graph.discovery-strategies.js +451 -0
- package/dist/opportunities/application/opportunity.graph.discovery.d.ts +69 -0
- package/dist/opportunities/application/opportunity.graph.discovery.js +397 -0
- package/dist/opportunities/application/opportunity.graph.evaluation.d.ts +82 -0
- package/dist/opportunities/application/opportunity.graph.evaluation.js +608 -0
- package/dist/opportunities/application/opportunity.graph.js +120 -3690
- package/dist/opportunities/application/opportunity.graph.modes.d.ts +538 -0
- package/dist/opportunities/application/opportunity.graph.modes.js +538 -0
- package/dist/opportunities/application/opportunity.graph.negotiate.d.ts +109 -0
- package/dist/opportunities/application/opportunity.graph.negotiate.js +392 -0
- package/dist/opportunities/application/opportunity.graph.persist-node.d.ts +93 -0
- package/dist/opportunities/application/opportunity.graph.persist-node.js +765 -0
- package/dist/opportunities/application/opportunity.graph.prep.d.ts +150 -0
- package/dist/opportunities/application/opportunity.graph.prep.js +381 -0
- package/dist/opportunities/application/opportunity.graph.shared.d.ts +173 -0
- package/dist/opportunities/application/opportunity.graph.shared.js +199 -0
- package/dist/opportunities/application/opportunity.presentation.d.ts +399 -0
- package/dist/opportunities/application/{opportunity.presenter.js → opportunity.presentation.js} +735 -9
- package/dist/opportunities/application/opportunity.tools.cards.d.ts +152 -0
- package/dist/opportunities/application/opportunity.tools.cards.js +235 -0
- package/dist/opportunities/application/opportunity.tools.d.ts +8 -114
- package/dist/opportunities/application/opportunity.tools.js +28 -711
- package/dist/opportunities/application/opportunity.tools.list.d.ts +10 -0
- package/dist/opportunities/application/opportunity.tools.list.js +493 -0
- package/dist/opportunities/domain/index.d.ts +3 -3
- package/dist/opportunities/domain/index.js +3 -3
- package/dist/opportunities/domain/opportunity.state.d.ts +19 -19
- package/dist/opportunities/index.d.ts +6 -6
- package/dist/opportunities/index.js +4 -4
- package/dist/opportunities/ports/opportunity.tools.port.d.ts +1 -1
- package/dist/opportunities/radar/radar.graph.d.ts +57 -13
- package/dist/opportunities/radar/radar.graph.js +470 -471
- package/dist/premises/premise.graph.d.ts +88 -20
- package/dist/premises/premise.graph.js +207 -218
- package/dist/questions/domain/question.schema.d.ts +40 -40
- package/dist/shared/agent/tool.factory.js +0 -8
- package/dist/shared/agent/tool.helpers.d.ts +21 -45
- package/dist/shared/agent/tool.helpers.js +1 -2
- package/dist/shared/agent/tool.registry.d.ts +4 -3
- package/dist/shared/agent/tool.registry.js +2 -4
- package/dist/shared/agent/tool.runtime.js +0 -2
- package/dist/shared/agent/utility.tools.js +9 -13
- package/dist/shared/interfaces/database.capabilities.d.ts +100 -0
- package/dist/shared/interfaces/database.capabilities.js +7 -0
- package/dist/shared/interfaces/database.entities.d.ts +533 -0
- package/dist/shared/interfaces/database.entities.js +10 -0
- package/dist/shared/interfaces/database.identity-queries.d.ts +294 -0
- package/dist/shared/interfaces/database.identity-queries.js +4 -0
- package/dist/shared/interfaces/database.interface.d.ts +15 -2277
- package/dist/shared/interfaces/database.interface.js +8 -0
- package/dist/shared/interfaces/database.member-queries.d.ts +221 -0
- package/dist/shared/interfaces/database.member-queries.js +4 -0
- package/dist/shared/interfaces/database.negotiation.d.ts +305 -0
- package/dist/shared/interfaces/database.negotiation.js +26 -0
- package/dist/shared/interfaces/database.network-queries.d.ts +277 -0
- package/dist/shared/interfaces/database.network-queries.js +4 -0
- package/dist/shared/interfaces/database.opportunity-queries.d.ts +285 -0
- package/dist/shared/interfaces/database.opportunity-queries.js +4 -0
- package/dist/shared/interfaces/database.port.d.ts +322 -0
- package/dist/shared/interfaces/database.port.js +29 -0
- package/dist/shared/schemas/discovery-question.schema.d.ts +14 -14
- package/dist/shared/schemas/negotiation-digest.schema.d.ts +2 -2
- package/package.json +2 -15
- package/dist/contacts/application/contact.inviter.d.ts +0 -49
- package/dist/contacts/application/contact.inviter.js +0 -64
- package/dist/integrations/application/index.d.ts +0 -14
- package/dist/integrations/application/index.js +0 -14
- package/dist/integrations/application/integration.tools.d.ts +0 -27
- package/dist/integrations/application/integration.tools.js +0 -98
- package/dist/integrations/domain/index.d.ts +0 -16
- package/dist/integrations/domain/index.js +0 -1
- package/dist/integrations/domain/integration.types.d.ts +0 -51
- package/dist/integrations/domain/integration.types.js +0 -9
- package/dist/integrations/index.d.ts +0 -10
- package/dist/integrations/index.js +0 -8
- package/dist/integrations/ports/index.d.ts +0 -25
- package/dist/integrations/ports/index.js +0 -23
- package/dist/integrations/ports/integration.adapter.port.d.ts +0 -62
- package/dist/integrations/ports/integration.adapter.port.js +0 -15
- package/dist/integrations/ports/integration.importer.port.d.ts +0 -31
- package/dist/integrations/ports/integration.importer.port.js +0 -15
- package/dist/integrations/ports/integration.tools.port.d.ts +0 -25
- package/dist/integrations/ports/integration.tools.port.js +0 -18
- package/dist/opportunities/application/opportunity.card-presentation.d.ts +0 -44
- package/dist/opportunities/application/opportunity.card-presentation.js +0 -93
- package/dist/opportunities/application/opportunity.presenter.d.ts +0 -156
- package/dist/opportunities/domain/opportunity.presentation-cache.d.ts +0 -5
- package/dist/opportunities/domain/opportunity.presentation-cache.js +0 -11
- package/dist/opportunities/domain/opportunity.presentation.d.ts +0 -76
- package/dist/opportunities/domain/opportunity.presentation.js +0 -516
- package/dist/opportunities/domain/opportunity.safe-presentation.d.ts +0 -104
- package/dist/opportunities/domain/opportunity.safe-presentation.js +0 -102
|
@@ -18,7 +18,7 @@ export declare const EnrichmentGraphState: import("@langchain/langgraph").Annota
|
|
|
18
18
|
* - 'generate': Auto-enrich from user table data via enrichUserProfile Chat API,
|
|
19
19
|
* then decompose the enrichment into premises
|
|
20
20
|
*/
|
|
21
|
-
operationMode: import("@langchain/langgraph").BaseChannel<"query" | "
|
|
21
|
+
operationMode: import("@langchain/langgraph").BaseChannel<"query" | "generate" | "write", "query" | "generate" | "write" | import("@langchain/langgraph").OverwriteValue<"query" | "generate" | "write">, unknown>;
|
|
22
22
|
/**
|
|
23
23
|
* Flag to force profile regeneration even if profile exists.
|
|
24
24
|
* When true with new input, the graph will re-generate and update the profile.
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Enrichment tools that read or preview a user context.
|
|
3
|
+
*/
|
|
4
|
+
import type { DefineTool } from "../shared/agent/tool.helpers.js";
|
|
5
|
+
import type { EnrichmentToolDeps } from "../contexts/ports/participant-context.tools.port.js";
|
|
6
|
+
export declare function createUserContextReadTools(defineTool: DefineTool, deps: EnrichmentToolDeps): {
|
|
7
|
+
readUserContexts: any;
|
|
8
|
+
previewUserContext: any;
|
|
9
|
+
confirmUserContext: any;
|
|
10
|
+
};
|
|
@@ -0,0 +1,353 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Enrichment tools that read or preview a user context.
|
|
3
|
+
*/
|
|
4
|
+
import { z } from "zod";
|
|
5
|
+
import { requestContext } from "../shared/observability/request-context.js";
|
|
6
|
+
import { success, error, needsClarification, UUID_REGEX } from "../shared/agent/tool.helpers.js";
|
|
7
|
+
import { detectSocialLabel } from "../shared/utils/social-label.js";
|
|
8
|
+
import { EnrichmentGenerator } from "./enrichment.generator.js";
|
|
9
|
+
import { invokeWithAbortSignal } from "../shared/agent/model-signal.js";
|
|
10
|
+
import { focusedNetworkId, focusedNetworkLabel } from "../shared/agent/tool.scope.js";
|
|
11
|
+
import { approvedProfileDraftSchema, buildProfileInput, decomposeApprovedDraftProfile, enqueueEnrichmentRun, isPlaceholderName, logger, markApprovedProfileConfirmed, persistApprovedProfileContext, selectProfileSeed, toProfileSummary, trimToUndefined } from "./enrichment.tools.helpers.js";
|
|
12
|
+
export function createUserContextReadTools(defineTool, deps) {
|
|
13
|
+
const { userDb, systemDb, graphs, enricher, grantDefaultSystemPermissions, reportToolError, getUserContextText } = deps;
|
|
14
|
+
const readUserContexts = defineTool({
|
|
15
|
+
name: "read_user_contexts",
|
|
16
|
+
description: "Retrieves user profiles containing identity info (name, bio, location) plus a rich `context` paragraph (the user's synthesized identity text). " +
|
|
17
|
+
"Profiles are used for semantic matching in opportunity discovery — the richer the user's context, the better the matches.\n\n" +
|
|
18
|
+
"**Usage modes:**\n" +
|
|
19
|
+
"- With `query` (name search): finds members by name (case-insensitive substring) across the user's indexes. " +
|
|
20
|
+
"This is the primary way to look up a person by name. Add `networkId` to restrict search to one index. (List results return thin identity only — no `context`.)\n" +
|
|
21
|
+
"- With `userId`: returns that specific user's profile — name, bio, location, and their `context` paragraph.\n" +
|
|
22
|
+
"- With `networkId` alone: returns thin-identity profiles of ALL members in that index (no `context`).\n" +
|
|
23
|
+
"- No parameters: returns the current user's own profile, including their `context`.\n\n" +
|
|
24
|
+
"**When to use:** Before creating introductions (need profiles of both parties), when the user asks about a person, " +
|
|
25
|
+
"or to check if a profile exists before suggesting create_user_context.\n\n" +
|
|
26
|
+
"**Returns:** Profile objects with name, bio, location, and (for single-user reads) a `context` paragraph. Use userId from results with other tools like read_intents(userId, networkId).",
|
|
27
|
+
querySchema: z.object({
|
|
28
|
+
userId: z.string().optional().describe("Fetch a specific user's profile by their user ID. Get user IDs from read_network_memberships or list_contacts."),
|
|
29
|
+
networkId: z.string().optional().describe("Network UUID — fetch profiles of all members in this network, or narrow a name search to this network. Get from read_networks."),
|
|
30
|
+
query: z.string().optional().describe("Name to search for (case-insensitive substring match). Searches across all the user's indexes unless networkId is also provided. Use this when the user asks to 'find' or 'look up' someone."),
|
|
31
|
+
}),
|
|
32
|
+
handler: async ({ context, query }) => {
|
|
33
|
+
const scopedNetworkId = focusedNetworkId(context);
|
|
34
|
+
const scopedIndexLabel = focusedNetworkLabel(context);
|
|
35
|
+
const effectiveIndexId = query.networkId?.trim() || undefined;
|
|
36
|
+
const targetUserId = query.userId?.trim() || undefined;
|
|
37
|
+
const nameQuery = query.query?.trim() || undefined;
|
|
38
|
+
if (effectiveIndexId && !UUID_REGEX.test(effectiveIndexId)) {
|
|
39
|
+
return error("Invalid network ID format. Use the exact UUID from read_networks.");
|
|
40
|
+
}
|
|
41
|
+
// --- Name search mode: query provided → find members by name ---
|
|
42
|
+
if (nameQuery) {
|
|
43
|
+
const pattern = nameQuery.toLowerCase();
|
|
44
|
+
const MAX_RESULTS = 20;
|
|
45
|
+
// When chat is network-scoped, restrict name search to that index
|
|
46
|
+
const searchIndexId = effectiveIndexId || scopedNetworkId || undefined;
|
|
47
|
+
let candidates;
|
|
48
|
+
if (searchIndexId) {
|
|
49
|
+
// Scoped to a specific index
|
|
50
|
+
if (scopedNetworkId && searchIndexId !== scopedNetworkId) {
|
|
51
|
+
return error(`This chat is scoped to ${scopedIndexLabel}. You can only look up people in this community.`);
|
|
52
|
+
}
|
|
53
|
+
const callerIsMember = await systemDb.isNetworkMember(searchIndexId, context.userId);
|
|
54
|
+
if (!callerIsMember) {
|
|
55
|
+
return error("You can only look up people in indexes you are a member of.");
|
|
56
|
+
}
|
|
57
|
+
const members = await systemDb.getNetworkMembers(searchIndexId);
|
|
58
|
+
candidates = members.map((m) => ({ userId: m.userId, name: m.name, avatar: m.avatar ?? null }));
|
|
59
|
+
}
|
|
60
|
+
else {
|
|
61
|
+
// Search across all user's indexes
|
|
62
|
+
candidates = await systemDb.getMembersFromScope();
|
|
63
|
+
}
|
|
64
|
+
logger.verbose("Name search candidates", {
|
|
65
|
+
query: nameQuery,
|
|
66
|
+
pattern,
|
|
67
|
+
candidateCount: candidates.length,
|
|
68
|
+
userId: context.userId,
|
|
69
|
+
});
|
|
70
|
+
// Filter by name (case-insensitive substring), exclude self
|
|
71
|
+
const matched = candidates
|
|
72
|
+
.filter((c) => c.userId !== context.userId && c.name.toLowerCase().includes(pattern))
|
|
73
|
+
.slice(0, MAX_RESULTS);
|
|
74
|
+
if (matched.length === 0) {
|
|
75
|
+
return success({ query: nameQuery, matchCount: 0, profiles: [], message: "No members found matching that name." });
|
|
76
|
+
}
|
|
77
|
+
// Fetch full profiles for matches
|
|
78
|
+
const profiles = await Promise.all(matched.map(async (m) => {
|
|
79
|
+
try {
|
|
80
|
+
const profile = await systemDb.getProfile(m.userId);
|
|
81
|
+
// Flat thin identity for list results. skills/interests are retired; the
|
|
82
|
+
// rich identity text (global user_context) is fetched per-user via a userId read.
|
|
83
|
+
return {
|
|
84
|
+
userId: m.userId,
|
|
85
|
+
name: m.name,
|
|
86
|
+
hasProfile: !!profile,
|
|
87
|
+
...(profile
|
|
88
|
+
? { bio: profile.identity.bio, location: profile.identity.location }
|
|
89
|
+
: {}),
|
|
90
|
+
};
|
|
91
|
+
}
|
|
92
|
+
catch (err) {
|
|
93
|
+
logger.warn("read_user_contexts: getProfile failed; degrading to hasProfile=false", {
|
|
94
|
+
userId: m.userId,
|
|
95
|
+
error: err instanceof Error ? err.message : String(err),
|
|
96
|
+
});
|
|
97
|
+
return { userId: m.userId, name: m.name, hasProfile: false };
|
|
98
|
+
}
|
|
99
|
+
}));
|
|
100
|
+
return success({ query: nameQuery, matchCount: profiles.length, profiles });
|
|
101
|
+
}
|
|
102
|
+
// When no userId / networkId / query is provided, fall through to Mode 1 (self lookup).
|
|
103
|
+
// --- Mode 3: networkId provided → fetch all member profiles ---
|
|
104
|
+
if (effectiveIndexId) {
|
|
105
|
+
// Strict scope enforcement: when chat is network-scoped, only allow querying that index
|
|
106
|
+
if (scopedNetworkId && effectiveIndexId !== scopedNetworkId) {
|
|
107
|
+
return error(`This chat is scoped to ${scopedIndexLabel}. You can only read profiles from this community.`);
|
|
108
|
+
}
|
|
109
|
+
// Verify the caller is a member of the network they're querying
|
|
110
|
+
const callerIsMember = await systemDb.isNetworkMember(effectiveIndexId, context.userId);
|
|
111
|
+
if (!callerIsMember) {
|
|
112
|
+
return error("You can only read profiles from indexes you are a member of.");
|
|
113
|
+
}
|
|
114
|
+
// Use systemDb for cross-user access within shared networkes
|
|
115
|
+
const members = await systemDb.getNetworkMembers(effectiveIndexId);
|
|
116
|
+
const profiles = await Promise.all(members.map(async (member) => {
|
|
117
|
+
const profile = await systemDb.getProfile(member.userId);
|
|
118
|
+
// Flat thin identity for roster results. skills/interests are retired; fetch a
|
|
119
|
+
// member's global user_context text via a single-user (userId) read.
|
|
120
|
+
return {
|
|
121
|
+
userId: member.userId,
|
|
122
|
+
name: member.name,
|
|
123
|
+
hasProfile: !!profile,
|
|
124
|
+
...(profile
|
|
125
|
+
? { bio: profile.identity.bio, location: profile.identity.location }
|
|
126
|
+
: {}),
|
|
127
|
+
};
|
|
128
|
+
}));
|
|
129
|
+
return success({ networkId: effectiveIndexId, memberCount: members.length, profiles });
|
|
130
|
+
}
|
|
131
|
+
// --- Mode 2: userId provided (different user) → fetch single profile directly ---
|
|
132
|
+
if (targetUserId && targetUserId !== context.userId) {
|
|
133
|
+
// Strict scope enforcement: when chat is network-scoped, verify user is in that index
|
|
134
|
+
if (scopedNetworkId) {
|
|
135
|
+
const isInScopedIndex = await systemDb.isNetworkMember(scopedNetworkId, targetUserId);
|
|
136
|
+
if (!isInScopedIndex) {
|
|
137
|
+
return error(`This chat is scoped to ${scopedIndexLabel}. You can only read profiles of members in this community.`);
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
// Use systemDb for cross-user profile access (requires shared network)
|
|
141
|
+
const profile = await systemDb.getProfile(targetUserId);
|
|
142
|
+
if (profile) {
|
|
143
|
+
// Thin identity + the user's global user_context text (profile-replacing
|
|
144
|
+
// identity paragraph). skills/interests/narrative are retired (WS6).
|
|
145
|
+
const context = getUserContextText ? await getUserContextText(targetUserId) : '';
|
|
146
|
+
return success({
|
|
147
|
+
hasProfile: true,
|
|
148
|
+
name: profile.identity.name,
|
|
149
|
+
bio: profile.identity.bio,
|
|
150
|
+
location: profile.identity.location,
|
|
151
|
+
context,
|
|
152
|
+
});
|
|
153
|
+
}
|
|
154
|
+
return success({ hasProfile: false, message: "This user does not have a profile yet." });
|
|
155
|
+
}
|
|
156
|
+
// --- Mode 1: No args / self → use profileGraph query (returns id for updates) ---
|
|
157
|
+
const _readProfileGraphStart = Date.now();
|
|
158
|
+
const _readProfileTraceEmitter = requestContext.getStore()?.traceEmitter;
|
|
159
|
+
_readProfileTraceEmitter?.({ type: "graph_start", name: "enrichment" });
|
|
160
|
+
const result = await invokeWithAbortSignal(graphs.profile, {
|
|
161
|
+
userId: context.userId,
|
|
162
|
+
operationMode: 'query',
|
|
163
|
+
});
|
|
164
|
+
const _readProfileGraphMs = Date.now() - _readProfileGraphStart;
|
|
165
|
+
_readProfileTraceEmitter?.({ type: "graph_end", name: "enrichment", durationMs: _readProfileGraphMs });
|
|
166
|
+
// REST/CLI self-lookup includes onboarding status. MCP does not — it
|
|
167
|
+
// no longer gates or completes onboarding.
|
|
168
|
+
const onboardingCompletedAt = context.user.onboarding?.completedAt ?? null;
|
|
169
|
+
const onboardingFields = context.isMcp ? {} : {
|
|
170
|
+
onboardingComplete: !!onboardingCompletedAt,
|
|
171
|
+
...(onboardingCompletedAt ? { onboardingCompletedAt } : {}),
|
|
172
|
+
};
|
|
173
|
+
if (result.readResult) {
|
|
174
|
+
// Augment the graph's thin-identity readResult with the caller's global
|
|
175
|
+
// user_context text (the rich, profile-replacing identity paragraph).
|
|
176
|
+
const readResult = result.readResult;
|
|
177
|
+
// Flatten identity fields up; drop the nested `profile` object (WS11).
|
|
178
|
+
const flat = readResult.hasProfile && readResult.profile
|
|
179
|
+
? { hasProfile: true, ...readResult.profile, context: getUserContextText ? await getUserContextText(context.userId) : '' }
|
|
180
|
+
: { ...readResult };
|
|
181
|
+
return success({ ...flat, ...onboardingFields, _graphTimings: [{ name: 'enrichment', durationMs: _readProfileGraphMs, agents: result.agentTimings ?? [] }] });
|
|
182
|
+
}
|
|
183
|
+
if (result.profile) {
|
|
184
|
+
return success({
|
|
185
|
+
hasProfile: true,
|
|
186
|
+
name: result.profile.identity.name,
|
|
187
|
+
bio: result.profile.identity.bio,
|
|
188
|
+
location: result.profile.identity.location,
|
|
189
|
+
context: getUserContextText ? await getUserContextText(context.userId) : '',
|
|
190
|
+
...onboardingFields,
|
|
191
|
+
_graphTimings: [{ name: 'enrichment', durationMs: _readProfileGraphMs, agents: result.agentTimings ?? [] }],
|
|
192
|
+
});
|
|
193
|
+
}
|
|
194
|
+
return success({
|
|
195
|
+
hasProfile: false,
|
|
196
|
+
...onboardingFields,
|
|
197
|
+
message: "You don't have a profile yet. Would you like to create one? You can share your LinkedIn, GitHub, or X/Twitter profile, or just tell me about yourself.",
|
|
198
|
+
_graphTimings: [{ name: 'enrichment', durationMs: _readProfileGraphMs, agents: result.agentTimings ?? [] }],
|
|
199
|
+
});
|
|
200
|
+
},
|
|
201
|
+
});
|
|
202
|
+
const previewUserContext = defineTool({
|
|
203
|
+
name: "preview_user_context",
|
|
204
|
+
description: "Builds a structured profile draft without saving anything. Use this before asking the user to approve the profile. " +
|
|
205
|
+
"This tool never runs public internet lookup; it uses only explicit text, staged signup/import seeds, and user-provided social URLs. " +
|
|
206
|
+
"In MCP contexts, starts an async profile run and returns `profileRunId`; poll get_enrichment_run until status is `succeeded`, then present its `result`.",
|
|
207
|
+
querySchema: z.object({
|
|
208
|
+
name: z.string().optional().describe("Name explicitly provided by the user. The account identity is used first and this is only a fallback."),
|
|
209
|
+
location: z.string().optional().describe("Location explicitly provided by the user."),
|
|
210
|
+
bioOrDescription: z.string().optional().describe("Explicit self-description provided by the user."),
|
|
211
|
+
linkedinUrl: z.string().optional().describe("LinkedIn URL explicitly provided by the user."),
|
|
212
|
+
githubUrl: z.string().optional().describe("GitHub URL explicitly provided by the user."),
|
|
213
|
+
twitterUrl: z.string().optional().describe("X/Twitter URL explicitly provided by the user."),
|
|
214
|
+
websites: z.array(z.string()).optional().describe("Personal/portfolio URLs explicitly provided by the user."),
|
|
215
|
+
}),
|
|
216
|
+
handler: async ({ context, query }) => {
|
|
217
|
+
const user = await userDb.getUser();
|
|
218
|
+
if (!user)
|
|
219
|
+
return error("User not found.");
|
|
220
|
+
const profileRunId = await enqueueEnrichmentRun(deps, context, "preview_user_context", query);
|
|
221
|
+
if (profileRunId) {
|
|
222
|
+
return success({
|
|
223
|
+
status: "queued",
|
|
224
|
+
profileRunId,
|
|
225
|
+
message: `Profile preview started. Call get_enrichment_run with profileRunId="${profileRunId}" until it succeeds, fails, or is cancelled.`,
|
|
226
|
+
});
|
|
227
|
+
}
|
|
228
|
+
const scopedNetworkId = focusedNetworkId(context);
|
|
229
|
+
const onboarding = user.onboarding ?? context.user.onboarding;
|
|
230
|
+
const seed = selectProfileSeed(onboarding, scopedNetworkId);
|
|
231
|
+
// Prefer the authenticated account identity over an agent-supplied name.
|
|
232
|
+
const accountName = [trimToUndefined(user.name), trimToUndefined(context.userName)]
|
|
233
|
+
.find((candidate) => candidate !== undefined && !isPlaceholderName(candidate));
|
|
234
|
+
const name = seed?.name || accountName || query.name?.trim() || undefined;
|
|
235
|
+
const location = query.location?.trim() || seed?.location || user.location || undefined;
|
|
236
|
+
const bioOrDescription = query.bioOrDescription?.trim() || seed?.bio || user.intro || undefined;
|
|
237
|
+
const linkedinUrl = query.linkedinUrl?.trim();
|
|
238
|
+
const githubUrl = query.githubUrl?.trim();
|
|
239
|
+
const twitterUrl = query.twitterUrl?.trim();
|
|
240
|
+
const websites = query.websites?.map((url) => url.trim()).filter(Boolean) ?? [];
|
|
241
|
+
const socials = [
|
|
242
|
+
...(seed?.socials ?? []),
|
|
243
|
+
...(linkedinUrl ? [{ label: 'linkedin', value: linkedinUrl }] : []),
|
|
244
|
+
...(githubUrl ? [{ label: 'github', value: githubUrl }] : []),
|
|
245
|
+
...(twitterUrl ? [{ label: 'twitter', value: twitterUrl }] : []),
|
|
246
|
+
...websites.map((value) => ({ label: detectSocialLabel(value), value })),
|
|
247
|
+
];
|
|
248
|
+
const input = buildProfileInput({ name, location, bioOrDescription, socials });
|
|
249
|
+
if (!input.trim()) {
|
|
250
|
+
return needsClarification({
|
|
251
|
+
missingFields: ['profile_description'],
|
|
252
|
+
message: "Please share a short description or profile links so I can draft your profile.",
|
|
253
|
+
});
|
|
254
|
+
}
|
|
255
|
+
const generated = await new EnrichmentGenerator().invoke(input);
|
|
256
|
+
const profile = { ...generated.output, userId: context.userId };
|
|
257
|
+
return success({
|
|
258
|
+
preview: true,
|
|
259
|
+
persisted: false,
|
|
260
|
+
message: "Profile draft generated. Show this to the user and ask whether it looks right before calling confirm_user_context.",
|
|
261
|
+
profile: toProfileSummary(profile),
|
|
262
|
+
draft: profile,
|
|
263
|
+
});
|
|
264
|
+
},
|
|
265
|
+
});
|
|
266
|
+
const confirmUserContext = defineTool({
|
|
267
|
+
name: "confirm_user_context",
|
|
268
|
+
description: "Saves an explicitly approved profile draft. Call this only after the user has seen the draft from preview_user_context and approved it or provided corrections. " +
|
|
269
|
+
"This path uses only the approved draft/explicit correction text and does not scrape or run public lookup.",
|
|
270
|
+
querySchema: z.object({
|
|
271
|
+
draft: approvedProfileDraftSchema.optional().describe("The structured profile draft returned by preview_user_context after user approval."),
|
|
272
|
+
bioOrDescription: z.string().optional().describe("Approved correction or explicit profile text if not passing a structured draft."),
|
|
273
|
+
name: z.string().optional().describe("Approved name correction."),
|
|
274
|
+
location: z.string().optional().describe("Approved location correction."),
|
|
275
|
+
}),
|
|
276
|
+
handler: async ({ context, query }) => {
|
|
277
|
+
const user = await userDb.getUser();
|
|
278
|
+
if (query.draft) {
|
|
279
|
+
const profile = { ...query.draft, userId: context.userId };
|
|
280
|
+
await userDb.saveProfile({ userId: context.userId, identity: profile.identity, context: profile.narrative?.context ?? '' });
|
|
281
|
+
await persistApprovedProfileContext(deps, profile, user, focusedNetworkId(context));
|
|
282
|
+
await markApprovedProfileConfirmed(deps, context);
|
|
283
|
+
const decomposeLogLabel = context.isMcp
|
|
284
|
+
? 'Approved draft premise decomposition failed'
|
|
285
|
+
: 'Approved draft premise decomposition failed (web)';
|
|
286
|
+
decomposeApprovedDraftProfile(deps, profile).catch((err) => {
|
|
287
|
+
logger.error(decomposeLogLabel, {
|
|
288
|
+
userId: profile.userId,
|
|
289
|
+
error: err instanceof Error ? err.message : String(err),
|
|
290
|
+
});
|
|
291
|
+
});
|
|
292
|
+
return success({
|
|
293
|
+
created: true,
|
|
294
|
+
message: context.isMcp
|
|
295
|
+
? "Profile saved from approved draft. Premise extraction is running in the background."
|
|
296
|
+
: "Profile saved from approved draft.",
|
|
297
|
+
profile: toProfileSummary(profile),
|
|
298
|
+
});
|
|
299
|
+
}
|
|
300
|
+
const description = query.bioOrDescription?.trim();
|
|
301
|
+
if (!description) {
|
|
302
|
+
return error("Pass the approved structured draft or explicit approved profile text.");
|
|
303
|
+
}
|
|
304
|
+
const approvedName = query.name?.trim();
|
|
305
|
+
const approvedLocation = query.location?.trim();
|
|
306
|
+
const input = buildProfileInput({
|
|
307
|
+
name: approvedName,
|
|
308
|
+
location: approvedLocation,
|
|
309
|
+
bioOrDescription: description,
|
|
310
|
+
});
|
|
311
|
+
const rawProfile = {
|
|
312
|
+
identity: {
|
|
313
|
+
name: approvedName && approvedName.length > 0 ? approvedName : user?.name ?? '',
|
|
314
|
+
bio: description,
|
|
315
|
+
location: approvedLocation && approvedLocation.length > 0 ? approvedLocation : user?.location ?? '',
|
|
316
|
+
},
|
|
317
|
+
};
|
|
318
|
+
await persistApprovedProfileContext(deps, rawProfile, user, focusedNetworkId(context));
|
|
319
|
+
await markApprovedProfileConfirmed(deps, context);
|
|
320
|
+
const _confirmTraceEmitter = requestContext.getStore()?.traceEmitter;
|
|
321
|
+
const _confirmGraphStart = Date.now();
|
|
322
|
+
_confirmTraceEmitter?.({ type: "graph_start", name: "enrichment" });
|
|
323
|
+
graphs.profile.invoke({
|
|
324
|
+
userId: context.userId,
|
|
325
|
+
operationMode: 'write',
|
|
326
|
+
input,
|
|
327
|
+
forceUpdate: true,
|
|
328
|
+
}).then((result) => {
|
|
329
|
+
if (result.error || !result.profile) {
|
|
330
|
+
logger.error('Background profile generation failed', {
|
|
331
|
+
userId: context.userId,
|
|
332
|
+
error: result.error ?? 'No profile returned',
|
|
333
|
+
});
|
|
334
|
+
}
|
|
335
|
+
}).catch((err) => logger.error('Background profile generation failed', {
|
|
336
|
+
userId: context.userId,
|
|
337
|
+
error: err instanceof Error ? err.message : String(err),
|
|
338
|
+
})).finally(() => {
|
|
339
|
+
const _confirmGraphMs = Date.now() - _confirmGraphStart;
|
|
340
|
+
_confirmTraceEmitter?.({ type: "graph_end", name: "enrichment", durationMs: _confirmGraphMs });
|
|
341
|
+
});
|
|
342
|
+
return success({
|
|
343
|
+
created: true,
|
|
344
|
+
message: "Profile text accepted. Your profile is being structured in the background.",
|
|
345
|
+
profile: toProfileSummary({
|
|
346
|
+
identity: rawProfile.identity,
|
|
347
|
+
attributes: { skills: [], interests: [] },
|
|
348
|
+
}),
|
|
349
|
+
});
|
|
350
|
+
},
|
|
351
|
+
});
|
|
352
|
+
return { readUserContexts, previewUserContext, confirmUserContext };
|
|
353
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Enrichment tools that create or update a user context.
|
|
3
|
+
*/
|
|
4
|
+
import type { DefineTool } from "../shared/agent/tool.helpers.js";
|
|
5
|
+
import type { EnrichmentToolDeps } from "../contexts/ports/participant-context.tools.port.js";
|
|
6
|
+
export declare function createUserContextWriteTools(defineTool: DefineTool, deps: EnrichmentToolDeps): {
|
|
7
|
+
createUserContext: any;
|
|
8
|
+
updateUserContext: any;
|
|
9
|
+
};
|