@indexnetwork/protocol 1.25.4-rc.214.1 → 1.26.0-rc.216.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.
@@ -16,7 +16,7 @@ function isMeaningfulEnrichment(enrichment) {
16
16
  enrichment.attributes.interests.length > 0);
17
17
  }
18
18
  export function createProfileTools(defineTool, deps) {
19
- const { userDb, systemDb, database, graphs, enricher, grantDefaultSystemPermissions } = deps;
19
+ const { userDb, systemDb, database, graphs, enricher, grantDefaultSystemPermissions, reportToolError } = deps;
20
20
  function trimToUndefined(value) {
21
21
  const trimmed = value?.trim();
22
22
  return trimmed && trimmed.length > 0 ? trimmed : undefined;
@@ -99,6 +99,40 @@ export function createProfileTools(defineTool, deps) {
99
99
  return [];
100
100
  return Object.entries(socials).map(([label, value]) => ({ label, value }));
101
101
  }
102
+ async function enqueueProfileRun(context, operation, input) {
103
+ if (!context.isMcp || !deps.profileRuns || !deps.profileRunQueue)
104
+ return null;
105
+ const run = await deps.profileRuns.create({
106
+ userId: context.userId,
107
+ agentId: context.agentId ?? null,
108
+ operation,
109
+ input,
110
+ context: {
111
+ userId: context.userId,
112
+ userName: context.userName,
113
+ userEmail: context.userEmail,
114
+ ...(context.networkId ? { networkId: context.networkId } : {}),
115
+ ...(context.indexName ? { indexName: context.indexName } : {}),
116
+ indexScope: context.indexScope,
117
+ ...(context.sessionId ? { sessionId: context.sessionId } : {}),
118
+ ...(context.agentId ? { agentId: context.agentId } : {}),
119
+ ...(context.clientSurface ? { clientSurface: context.clientSurface } : {}),
120
+ },
121
+ });
122
+ try {
123
+ await deps.profileRunQueue.enqueue(run.id);
124
+ }
125
+ catch (err) {
126
+ const message = err instanceof Error ? err.message : String(err);
127
+ await deps.profileRuns.markFailed(run.id, message);
128
+ if (err instanceof Error)
129
+ throw err;
130
+ const wrapped = new Error(`Failed to enqueue profile run: ${message}`);
131
+ wrapped.cause = err;
132
+ throw wrapped;
133
+ }
134
+ return run.id;
135
+ }
102
136
  async function persistApprovedProfileContext(profile, user, networkId) {
103
137
  await userDb.updateUser({
104
138
  name: profile.identity.name,
@@ -394,7 +428,8 @@ export function createProfileTools(defineTool, deps) {
394
428
  const previewUserProfile = defineTool({
395
429
  name: "preview_user_profile",
396
430
  description: "Builds a structured profile draft for onboarding without saving anything. Use this after recording privacy consent and before asking the user to approve the profile. " +
397
- "If allowPublicLookup is false, this tool uses only explicit text, EdgeOS/event data the user allowed, and user-provided social URLs. If allowPublicLookup is true, persisted public lookup consent is required.",
431
+ "If allowPublicLookup is false, this tool uses only explicit text, EdgeOS/event data the user allowed, and user-provided social URLs. If allowPublicLookup is true, persisted public lookup consent is required. " +
432
+ "In MCP contexts, starts an async profile run and returns `profileRunId`; poll get_profile_run until status is `succeeded`, then present its `result`.",
398
433
  querySchema: z.object({
399
434
  name: z.string().optional().describe("Name explicitly provided by the user. For authenticated public lookup, the account identity is used first and this is only a fallback."),
400
435
  location: z.string().optional().describe("Location explicitly provided by the user or allowed event data."),
@@ -410,6 +445,14 @@ export function createProfileTools(defineTool, deps) {
410
445
  const user = await userDb.getUser();
411
446
  if (!user)
412
447
  return error("User not found.");
448
+ const profileRunId = await enqueueProfileRun(context, "preview_user_profile", query);
449
+ if (profileRunId) {
450
+ return success({
451
+ status: "queued",
452
+ profileRunId,
453
+ message: `Profile preview started. Call get_profile_run with profileRunId="${profileRunId}" until it succeeds, fails, or is cancelled.`,
454
+ });
455
+ }
413
456
  const onboarding = user.onboarding ?? context.user.onboarding;
414
457
  const hasEdgeosConsent = hasEdgeosImportConsent(onboarding);
415
458
  const seed = hasEdgeosConsent ? selectProfileSeed(onboarding, context.networkId) : undefined;
@@ -801,7 +844,8 @@ export function createProfileTools(defineTool, deps) {
801
844
  "- `socials={ telegram: \"@alice\" }` to silently add a reachable chat handle without regenerating the profile.\n\n" +
802
845
  "**When to use:** When the user wants to make specific changes without regenerating the whole profile. For full profile regeneration from social URLs, use create_user_profile instead.\n\n" +
803
846
  "**Important:** If the user provides a URL to update from, call scrape_url first, then pass the scraped content in `details`.\n\n" +
804
- "**Returns:** Confirmation that the profile was updated.",
847
+ "**MCP behavior:** For MCP clients, text/profile graph updates are accepted immediately and completed in the background to avoid transport timeouts. Social-only updates still complete synchronously.\n\n" +
848
+ "**Returns:** Confirmation that the profile was updated or accepted for background update.",
805
849
  querySchema: z.object({
806
850
  profileId: z.string().optional().describe("Profile UUID from read_user_profiles. Omit to update the current user's own profile (most common usage)."),
807
851
  action: z.string().optional().describe("Natural language description of ALL changes to make in a single call. Examples: 'update bio to focus on AI research', 'add Python and Rust to skills', 'change location to Berlin and add machine learning to interests'. Optional when only updating socials."),
@@ -818,6 +862,14 @@ export function createProfileTools(defineTool, deps) {
818
862
  }
819
863
  return error("Please specify what to update (e.g. action: 'update bio to X') or provide socials.");
820
864
  }
865
+ const profileRunId = await enqueueProfileRun(context, "update_user_profile", query);
866
+ if (profileRunId) {
867
+ return success({
868
+ status: "queued",
869
+ profileRunId,
870
+ message: `Profile update started. Call get_profile_run with profileRunId="${profileRunId}" until it succeeds, fails, or is cancelled.`,
871
+ });
872
+ }
821
873
  // Use profileGraph query mode to validate profile existence and get id
822
874
  const _updateQueryProfileGraphStart = Date.now();
823
875
  const _updateQueryProfileTraceEmitter = requestContext.getStore()?.traceEmitter;
@@ -836,6 +888,56 @@ export function createProfileTools(defineTool, deps) {
836
888
  if (socialUpdates.length > 0) {
837
889
  await mergeUserSocials(socialUpdates);
838
890
  }
891
+ if (context.isMcp) {
892
+ const _backgroundWriteProfileGraphStart = Date.now();
893
+ const _backgroundWriteProfileTraceEmitter = requestContext.getStore()?.traceEmitter;
894
+ _backgroundWriteProfileTraceEmitter?.({ type: "graph_start", name: "profile" });
895
+ graphs.profile.invoke({
896
+ userId: context.userId,
897
+ operationMode: "write",
898
+ input: inputForProfile,
899
+ forceUpdate: true,
900
+ }).then((writeResult) => {
901
+ if (writeResult.error) {
902
+ logger.error("Background profile update failed", {
903
+ userId: context.userId,
904
+ error: writeResult.error,
905
+ });
906
+ reportToolError?.(new Error(writeResult.error), {
907
+ subsystem: "profile",
908
+ operation: "profile.update_background",
909
+ toolName: "update_user_profile",
910
+ userId: context.userId,
911
+ tags: { toolName: "update_user_profile", execution: "background" },
912
+ context: { profileId: existingProfileId ?? providedProfileId },
913
+ });
914
+ }
915
+ }).catch((err) => {
916
+ const message = err instanceof Error ? err.message : String(err);
917
+ logger.error("Background profile update failed", {
918
+ userId: context.userId,
919
+ error: message,
920
+ });
921
+ reportToolError?.(err, {
922
+ subsystem: "profile",
923
+ operation: "profile.update_background",
924
+ toolName: "update_user_profile",
925
+ userId: context.userId,
926
+ tags: { toolName: "update_user_profile", execution: "background" },
927
+ context: { profileId: existingProfileId ?? providedProfileId },
928
+ });
929
+ }).finally(() => {
930
+ const _backgroundWriteProfileGraphMs = Date.now() - _backgroundWriteProfileGraphStart;
931
+ _backgroundWriteProfileTraceEmitter?.({ type: "graph_end", name: "profile", durationMs: _backgroundWriteProfileGraphMs });
932
+ });
933
+ return success({
934
+ accepted: true,
935
+ message: "Profile update accepted. The structured profile will refresh in the background.",
936
+ _graphTimings: [
937
+ { name: 'profile', durationMs: _updateQueryProfileGraphMs, agents: queryResult.agentTimings ?? [] },
938
+ ],
939
+ });
940
+ }
839
941
  // Execute update directly
840
942
  const _updateWriteProfileGraphStart = Date.now();
841
943
  const _updateWriteProfileTraceEmitter = requestContext.getStore()?.traceEmitter;
@@ -860,6 +962,72 @@ export function createProfileTools(defineTool, deps) {
860
962
  });
861
963
  },
862
964
  });
965
+ const getProfileRun = defineTool({
966
+ name: "get_profile_run",
967
+ description: "Checks the status of an async profile preview/update run started by preview_user_profile or update_user_profile in MCP contexts. " +
968
+ "Poll this tool with the profileRunId until status is succeeded, failed, or cancelled. When succeeded, present the result to the user.",
969
+ querySchema: z.object({
970
+ profileRunId: z.string().describe("Profile run ID returned by preview_user_profile or update_user_profile."),
971
+ }),
972
+ handler: async ({ context, query }) => {
973
+ if (!deps.profileRuns) {
974
+ return error("Profile run polling is not available in this environment.");
975
+ }
976
+ const run = await deps.profileRuns.get(query.profileRunId, context.userId);
977
+ if (!run)
978
+ return error("Profile run not found.");
979
+ return success({
980
+ profileRunId: run.id,
981
+ operation: run.operation,
982
+ status: run.status,
983
+ progress: run.progress ?? null,
984
+ result: run.result ?? null,
985
+ error: run.error ?? null,
986
+ createdAt: run.createdAt.toISOString?.() ?? null,
987
+ startedAt: run.startedAt?.toISOString?.() ?? null,
988
+ completedAt: run.completedAt?.toISOString?.() ?? null,
989
+ });
990
+ },
991
+ });
992
+ const cancelProfileRun = defineTool({
993
+ name: "cancel_profile_run",
994
+ description: "Requests cancellation for an async profile run. If the queued job has not started, it is removed and marked cancelled. " +
995
+ "If already running, the worker observes the cancellation request and aborts where supported.",
996
+ querySchema: z.object({
997
+ profileRunId: z.string().describe("Profile run ID returned by preview_user_profile or update_user_profile."),
998
+ }),
999
+ handler: async ({ context, query }) => {
1000
+ if (!deps.profileRuns || !deps.profileRunQueue) {
1001
+ return error("Profile run cancellation is not available in this environment.");
1002
+ }
1003
+ const existing = await deps.profileRuns.get(query.profileRunId, context.userId);
1004
+ if (!existing)
1005
+ return error("Profile run not found.");
1006
+ if (!["queued", "running"].includes(existing.status)) {
1007
+ return success({
1008
+ profileRunId: existing.id,
1009
+ status: existing.status,
1010
+ message: `Profile run is already ${existing.status}.`,
1011
+ });
1012
+ }
1013
+ const run = await deps.profileRuns.requestCancel(query.profileRunId, context.userId);
1014
+ if (!run)
1015
+ return error("Profile run not found or cannot be cancelled.");
1016
+ const removed = await deps.profileRunQueue.cancel(run.id);
1017
+ if (removed) {
1018
+ await deps.profileRuns.markCancelled(run.id, "cancelled before worker start");
1019
+ }
1020
+ const updated = await deps.profileRuns.get(run.id, context.userId);
1021
+ return success({
1022
+ profileRunId: run.id,
1023
+ status: updated?.status ?? run.status,
1024
+ cancelled: true,
1025
+ message: removed
1026
+ ? "Profile run cancelled before it started."
1027
+ : "Cancellation requested while the profile run is running or queued.",
1028
+ });
1029
+ },
1030
+ });
863
1031
  const completeOnboarding = defineTool({
864
1032
  name: "complete_onboarding",
865
1033
  description: "Marks the user's onboarding as complete, unlocking full platform access. This is the final step in the new-user setup flow.\n\n" +
@@ -909,6 +1077,6 @@ export function createProfileTools(defineTool, deps) {
909
1077
  return success({ message: "Onboarding complete." });
910
1078
  },
911
1079
  });
912
- return [readUserProfiles, recordOnboardingPrivacyConsent, previewUserProfile, confirmUserProfile, createUserProfile, updateUserProfile, completeOnboarding];
1080
+ return [readUserProfiles, recordOnboardingPrivacyConsent, previewUserProfile, confirmUserProfile, createUserProfile, updateUserProfile, getProfileRun, cancelProfileRun, completeOnboarding];
913
1081
  }
914
1082
  //# sourceMappingURL=profile.tools.js.map