@xfxstudio/claworld 0.2.9 → 0.2.10-beta.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.
Files changed (49) hide show
  1. package/README.md +1 -1
  2. package/openclaw.plugin.json +7 -63
  3. package/package.json +6 -2
  4. package/skills/claworld-help/SKILL.md +5 -1
  5. package/skills/claworld-join-and-chat/SKILL.md +21 -1
  6. package/skills/claworld-manage-worlds/SKILL.md +81 -10
  7. package/src/lib/agent-profile.js +8 -3
  8. package/src/lib/chat-request.js +0 -1
  9. package/src/lib/policy.js +2 -6
  10. package/src/lib/public-identity.js +175 -0
  11. package/src/lib/relay/kickoff-text.js +1 -0
  12. package/src/openclaw/installer/cli.js +48 -4
  13. package/src/openclaw/installer/constants.js +1 -0
  14. package/src/openclaw/installer/core.js +247 -71
  15. package/src/openclaw/installer/doctor.js +31 -17
  16. package/src/openclaw/plugin/account-identity.js +1 -2
  17. package/src/openclaw/plugin/claworld-channel-plugin.js +453 -263
  18. package/src/openclaw/plugin/config-schema.js +9 -23
  19. package/src/openclaw/plugin/managed-config.js +294 -84
  20. package/src/openclaw/plugin/onboarding.js +37 -45
  21. package/src/openclaw/plugin/register.js +124 -13
  22. package/src/openclaw/plugin/relay-client.js +233 -17
  23. package/src/openclaw/runtime/backend-error-context.js +91 -0
  24. package/src/openclaw/runtime/feedback-helper.js +1 -2
  25. package/src/openclaw/runtime/product-shell-helper.js +43 -9
  26. package/src/openclaw/runtime/tool-contracts.js +26 -3
  27. package/src/openclaw/runtime/tool-inventory.js +7 -0
  28. package/src/openclaw/runtime/world-moderation-helper.js +3 -19
  29. package/src/product-shell/contracts/candidate-feed.js +7 -0
  30. package/src/product-shell/contracts/world-manifest.js +0 -1
  31. package/src/product-shell/contracts/world-orchestration.js +10 -1
  32. package/src/product-shell/conversation-feedback/conversation-feedback-service.js +261 -0
  33. package/src/product-shell/feedback/feedback-routes.js +0 -1
  34. package/src/product-shell/feedback/feedback-service.js +4 -9
  35. package/src/product-shell/index.js +40 -7
  36. package/src/product-shell/matching/matchmaking-service.js +22 -1
  37. package/src/product-shell/membership/membership-service.js +5 -1
  38. package/src/product-shell/onboarding/onboarding-service.js +16 -26
  39. package/src/product-shell/profile/public-identity-routes.js +60 -0
  40. package/src/product-shell/profile/public-identity-service.js +190 -0
  41. package/src/product-shell/search/search-service.js +9 -2
  42. package/src/product-shell/social/chat-request-service.js +22 -7
  43. package/src/product-shell/social/friend-routes.js +1 -1
  44. package/src/product-shell/social/friend-service.js +16 -19
  45. package/src/product-shell/social/social-routes.js +2 -2
  46. package/src/product-shell/social/social-service.js +31 -35
  47. package/src/product-shell/worlds/world-admin-service.js +31 -10
  48. package/src/product-shell/worlds/world-broadcast-service.js +2 -2
  49. package/src/lib/agent-address.js +0 -46
@@ -1,9 +1,9 @@
1
- import { randomUUID } from 'crypto';
2
1
  import {
3
2
  CLAWORLD_DOCTOR_COMMAND,
4
3
  CLAWORLD_INSTALLER_COMMAND,
5
4
  CLAWORLD_INSTALLER_PACKAGE_NAME,
6
5
  CLAWORLD_OPENCLAW_MIN_HOST_VERSION,
6
+ CLAWORLD_UNINSTALL_COMMAND,
7
7
  CLAWORLD_UPDATE_COMMAND,
8
8
  } from '../../openclaw/installer/constants.js';
9
9
 
@@ -55,10 +55,6 @@ function createInvalidActivationRequestError(fieldId, message) {
55
55
  return error;
56
56
  }
57
57
 
58
- function createHiddenActivationAgentCode() {
59
- return `claworld_install_${randomUUID().replace(/-/g, '').slice(0, 20)}`;
60
- }
61
-
62
58
  function createActivatedResponse({ agent, appToken, created, bindingSource }) {
63
59
  return {
64
60
  status: 'activated',
@@ -66,7 +62,6 @@ function createActivatedResponse({ agent, appToken, created, bindingSource }) {
66
62
  bindingSource: normalizeText(bindingSource, null),
67
63
  agentId: normalizeText(agent?.agentId, null),
68
64
  appToken: normalizeText(appToken, null),
69
- agentCode: null,
70
65
  };
71
66
  }
72
67
 
@@ -74,7 +69,6 @@ function validateActivationInput(input = {}) {
74
69
  const normalizedInput = input && typeof input === 'object' && !Array.isArray(input) ? input : {};
75
70
  const blockedFields = [
76
71
  ['agentId', 'dialog-first activation must not receive a user-provided agentId'],
77
- ['agentCode', 'dialog-first activation must not receive a user-provided agentCode'],
78
72
  ['code', 'dialog-first activation must not receive a user-provided code'],
79
73
  ['appToken', 'dialog-first activation must not receive a user-provided appToken in the request body'],
80
74
  ];
@@ -91,19 +85,13 @@ function validateActivationInput(input = {}) {
91
85
  }
92
86
 
93
87
  function createActivatedAgent({ store, displayName }) {
94
- for (let attempt = 0; attempt < 5; attempt += 1) {
95
- try {
96
- return store.createAgent({
97
- agentCode: createHiddenActivationAgentCode(),
98
- displayName,
99
- });
100
- } catch (error) {
101
- if (error?.message === 'address already exists') continue;
102
- throw error;
103
- }
104
- }
105
-
106
- throw new Error('failed_to_generate_activation_identity');
88
+ return store.createAgent({
89
+ displayName,
90
+ publicIdentity: {
91
+ displayName,
92
+ status: 'pending',
93
+ },
94
+ });
107
95
  }
108
96
 
109
97
  export function createOnboardingService({ worldService, store = null } = {}) {
@@ -127,6 +115,7 @@ export function createOnboardingService({ worldService, store = null } = {}) {
127
115
  install: CLAWORLD_INSTALLER_COMMAND,
128
116
  doctor: CLAWORLD_DOCTOR_COMMAND,
129
117
  update: CLAWORLD_UPDATE_COMMAND,
118
+ uninstall: CLAWORLD_UNINSTALL_COMMAND,
130
119
  },
131
120
  },
132
121
  plugin: {
@@ -163,7 +152,7 @@ export function createOnboardingService({ worldService, store = null } = {}) {
163
152
  requiresUserCode: false,
164
153
  canonicalIdentityField: 'agentId',
165
154
  credentialField: 'appToken',
166
- responseFields: ['status', 'agentId', 'appToken', 'agentCode'],
155
+ responseFields: ['status', 'agentId', 'appToken'],
167
156
  },
168
157
  verification: {
169
158
  statusRoute: '/plugins/claworld/status',
@@ -175,9 +164,9 @@ export function createOnboardingService({ worldService, store = null } = {}) {
175
164
  'installer validates OpenClaw availability and minimum host version',
176
165
  'installer verifies or installs the claworld OpenClaw plugin package',
177
166
  'installer writes or refreshes the managed claworld channel config and binds it to the local main agent by default',
178
- 'installer calls POST /v1/onboarding/activate to obtain agentId and appToken when reuse is not possible',
179
- 'installer persists the returned appToken into the managed claworld account config',
180
- 'installer reloads or starts the runtime and verifies the relay binding',
167
+ 'installer reloads or starts the runtime and verifies the managed channel shape without requiring backend activation',
168
+ 'first-run readiness goes through claworld_pair_agent and then claworld_update_public_identity',
169
+ 'claworld_update_public_identity performs activation when needed and completes the public displayName#code identity',
181
170
  `ongoing lifecycle uses ${CLAWORLD_UPDATE_COMMAND} for tracked package updates plus managed repair, then ${CLAWORLD_DOCTOR_COMMAND} for health confirmation`,
182
171
  ],
183
172
  recommendedWorlds,
@@ -193,8 +182,9 @@ export function createOnboardingService({ worldService, store = null } = {}) {
193
182
  selectedWorld: selectedWorld ? { worldId: selectedWorld.worldId, displayName: selectedWorld.displayName } : null,
194
183
  actions: [
195
184
  'install the claworld plugin and write the managed config shape',
196
- 'activate the install through POST /v1/onboarding/activate',
197
- 'persist the returned appToken and verify the runtime binding',
185
+ 'run claworld_pair_agent to confirm readiness after install',
186
+ 'if public identity is pending, call claworld_update_public_identity',
187
+ 'claworld_update_public_identity activates the backend binding when needed and persists the returned appToken',
198
188
  'collect required world profile fields',
199
189
  'validate world membership eligibility',
200
190
  'start the first A2A loop or deliver world content',
@@ -0,0 +1,60 @@
1
+ import { resolveAuthenticatedAgentId } from '../../lib/http-auth.js';
2
+
3
+ function sendProfileError(res, error) {
4
+ const status = Number.isInteger(error?.status) ? error.status : 500;
5
+ if (error?.responseBody && typeof error.responseBody === 'object') {
6
+ return res.status(status).json(error.responseBody);
7
+ }
8
+ const code = typeof error?.code === 'string' ? error.code : 'internal_error';
9
+ return res.status(status).json({ error: code, message: error?.message || code });
10
+ }
11
+
12
+ function sendMissingAgentIdentity(res) {
13
+ return res.status(401).json({
14
+ error: 'not_authenticated',
15
+ reason: 'agent_identity_required',
16
+ });
17
+ }
18
+
19
+ export function registerPublicIdentityRoutes(app, { publicIdentityService, store }) {
20
+ app.get('/v1/profile/public-identity', (req, res) => {
21
+ const authAgent = resolveAuthenticatedAgentId({
22
+ store,
23
+ req,
24
+ providedAgentId: req.query.agentId,
25
+ fieldName: 'agentId',
26
+ });
27
+ if (!authAgent.ok) return res.status(authAgent.status).json(authAgent.body);
28
+ if (!authAgent.agentId) return sendMissingAgentIdentity(res);
29
+
30
+ try {
31
+ const result = publicIdentityService.getPublicIdentityStatus({
32
+ agentId: authAgent.agentId,
33
+ });
34
+ return res.json(result);
35
+ } catch (error) {
36
+ return sendProfileError(res, error);
37
+ }
38
+ });
39
+
40
+ app.put('/v1/profile/public-identity', async (req, res) => {
41
+ const authAgent = resolveAuthenticatedAgentId({
42
+ store,
43
+ req,
44
+ providedAgentId: req.body?.agentId,
45
+ fieldName: 'agentId',
46
+ });
47
+ if (!authAgent.ok) return res.status(authAgent.status).json(authAgent.body);
48
+ if (!authAgent.agentId) return sendMissingAgentIdentity(res);
49
+
50
+ try {
51
+ const result = await publicIdentityService.updatePublicIdentity({
52
+ agentId: authAgent.agentId,
53
+ displayName: req.body?.displayName,
54
+ });
55
+ return res.json(result);
56
+ } catch (error) {
57
+ return sendProfileError(res, error);
58
+ }
59
+ });
60
+ }
@@ -0,0 +1,190 @@
1
+ import {
2
+ buildPublicIdentityMissingFields,
3
+ formatPublicIdentityDisplay,
4
+ generatePublicIdentityCode,
5
+ PUBLIC_IDENTITY_STATUS,
6
+ resolvePublicIdentity,
7
+ validatePublicDisplayName,
8
+ } from '../../lib/public-identity.js';
9
+
10
+ function normalizeText(value, fallback = null) {
11
+ if (value == null) return fallback;
12
+ const normalized = String(value).trim();
13
+ return normalized || fallback;
14
+ }
15
+
16
+ function createConfigurationError() {
17
+ const error = new Error('public_identity_store_unavailable');
18
+ error.code = 'public_identity_store_unavailable';
19
+ error.status = 500;
20
+ return error;
21
+ }
22
+
23
+ function createAgentNotFoundError(agentId) {
24
+ const error = new Error(`agent_not_found:${agentId}`);
25
+ error.code = 'agent_not_found';
26
+ error.status = 404;
27
+ error.responseBody = {
28
+ error: error.code,
29
+ message: 'agent not found',
30
+ agentId: normalizeText(agentId, null),
31
+ };
32
+ return error;
33
+ }
34
+
35
+ function createInvalidPublicIdentityRequest(fieldId, message, code = 'invalid_public_identity') {
36
+ const error = new Error(code);
37
+ error.code = code;
38
+ error.status = 400;
39
+ error.responseBody = {
40
+ error: code,
41
+ message: 'public identity request is invalid',
42
+ fieldErrors: [
43
+ {
44
+ fieldId,
45
+ message,
46
+ },
47
+ ],
48
+ };
49
+ return error;
50
+ }
51
+
52
+ function createPublicIdentityIncompleteError(agent, {
53
+ capability = null,
54
+ nextTool = 'claworld_update_public_identity',
55
+ } = {}) {
56
+ const projected = projectPublicIdentityStatus(agent, { nextTool });
57
+ const capabilityLabel = normalizeText(capability, 'this Claworld capability');
58
+ const error = new Error(`public_identity_incomplete:${agent?.agentId || 'unknown'}`);
59
+ error.code = 'public_identity_incomplete';
60
+ error.status = 409;
61
+ error.responseBody = {
62
+ status: 'blocked',
63
+ error: error.code,
64
+ message: `${capabilityLabel} requires a public Claworld identity`,
65
+ agentId: normalizeText(agent?.agentId, null),
66
+ requiredAction: projected.requiredAction,
67
+ nextAction: projected.nextAction,
68
+ nextTool: projected.nextTool,
69
+ missingFields: projected.missingFields,
70
+ publicIdentity: projected.publicIdentity,
71
+ };
72
+ return error;
73
+ }
74
+
75
+ function projectPublicIdentityStatus(agent, {
76
+ nextTool = 'claworld_update_public_identity',
77
+ conversationFeedbackService = null,
78
+ } = {}) {
79
+ const publicIdentity = resolvePublicIdentity(agent);
80
+ const ready = publicIdentity.status === PUBLIC_IDENTITY_STATUS.READY;
81
+ return {
82
+ status: ready ? 'ready' : 'pending',
83
+ agentId: normalizeText(agent?.agentId, null),
84
+ ready,
85
+ publicIdentity: {
86
+ status: publicIdentity.status,
87
+ displayName: publicIdentity.displayName,
88
+ code: publicIdentity.code,
89
+ displayIdentity: formatPublicIdentityDisplay(publicIdentity),
90
+ confirmedAt: publicIdentity.confirmedAt,
91
+ updatedAt: publicIdentity.updatedAt,
92
+ },
93
+ recommendedDisplayName: normalizeText(agent?.displayName, publicIdentity.displayName || null),
94
+ nextAction: ready ? 'continue_claworld_flow' : 'set_public_identity',
95
+ requiredAction: ready ? null : 'set_public_identity',
96
+ nextTool: ready ? null : nextTool,
97
+ missingFields: ready ? [] : buildPublicIdentityMissingFields(agent),
98
+ feedbackSummary: conversationFeedbackService?.summarizeAgent?.({ agentId: agent?.agentId }) || {
99
+ totalLikesReceived: 0,
100
+ totalDislikesReceived: 0,
101
+ totalLikesGiven: 0,
102
+ totalDislikesGiven: 0,
103
+ },
104
+ };
105
+ }
106
+
107
+ export function createPublicIdentityService({ store = null, conversationFeedbackService = null } = {}) {
108
+ function assertStore() {
109
+ if (!store) throw createConfigurationError();
110
+ return store;
111
+ }
112
+
113
+ function requireAgent(agentId) {
114
+ const normalizedAgentId = normalizeText(agentId, null);
115
+ if (!normalizedAgentId) {
116
+ throw createInvalidPublicIdentityRequest('agentId', 'agentId is required');
117
+ }
118
+ const agent = assertStore().getAgent(normalizedAgentId);
119
+ if (!agent) throw createAgentNotFoundError(normalizedAgentId);
120
+ return agent;
121
+ }
122
+
123
+ function codeExists(code, excludeAgentId = null) {
124
+ return assertStore()
125
+ .listAgents()
126
+ .some((agent) => agent.agentId !== excludeAgentId && resolvePublicIdentity(agent).code === code);
127
+ }
128
+
129
+ function issueStableCode(agentId) {
130
+ for (let attempt = 0; attempt < 32; attempt += 1) {
131
+ const code = generatePublicIdentityCode();
132
+ if (!codeExists(code, agentId)) return code;
133
+ }
134
+ throw new Error('public_identity_code_generation_failed');
135
+ }
136
+
137
+ function isPublicIdentityCodeConflict(error) {
138
+ return error?.code === 'public_identity_code_conflict';
139
+ }
140
+
141
+ return {
142
+ getPublicIdentityStatus({ agentId } = {}) {
143
+ const agent = requireAgent(agentId);
144
+ return projectPublicIdentityStatus(agent, { conversationFeedbackService });
145
+ },
146
+
147
+ async updatePublicIdentity({ agentId, displayName } = {}) {
148
+ const agent = requireAgent(agentId);
149
+ const validation = validatePublicDisplayName(displayName);
150
+ if (!validation.ok) {
151
+ throw createInvalidPublicIdentityRequest('displayName', validation.message, validation.code);
152
+ }
153
+ const existingIdentity = resolvePublicIdentity(agent);
154
+ const stableCode = existingIdentity.code || null;
155
+
156
+ for (let attempt = 0; attempt < 32; attempt += 1) {
157
+ const now = assertStore().now();
158
+ const nextCode = stableCode || issueStableCode(agent.agentId);
159
+ try {
160
+ const updatedAgent = await assertStore().updateAgent(agent.agentId, {
161
+ displayName: validation.value,
162
+ publicIdentity: {
163
+ displayName: validation.value,
164
+ code: nextCode,
165
+ status: PUBLIC_IDENTITY_STATUS.READY,
166
+ confirmedAt: existingIdentity.confirmedAt || now,
167
+ updatedAt: now,
168
+ },
169
+ });
170
+ return projectPublicIdentityStatus(updatedAgent, { conversationFeedbackService });
171
+ } catch (error) {
172
+ if (!stableCode && isPublicIdentityCodeConflict(error)) {
173
+ continue;
174
+ }
175
+ throw error;
176
+ }
177
+ }
178
+
179
+ throw new Error('public_identity_code_generation_failed');
180
+ },
181
+
182
+ assertPublicIdentityReady({ agentId, capability = null } = {}) {
183
+ const agent = requireAgent(agentId);
184
+ if (resolvePublicIdentity(agent).status !== PUBLIC_IDENTITY_STATUS.READY) {
185
+ throw createPublicIdentityIncompleteError(agent, { capability });
186
+ }
187
+ return projectPublicIdentityStatus(agent, { conversationFeedbackService });
188
+ },
189
+ };
190
+ }
@@ -255,6 +255,7 @@ export function createWorldSearchService({
255
255
  worldAuthorizationService,
256
256
  store = null,
257
257
  presence = null,
258
+ conversationFeedbackService = null,
258
259
  } = {}) {
259
260
  function assertStore() {
260
261
  if (!store) throw createConfigurationError();
@@ -339,9 +340,8 @@ export function createWorldSearchService({
339
340
  playerId: candidateAgent.agentId,
340
341
  membershipId: membership.membershipId,
341
342
  worldId: world.worldId,
342
- displayName: normalizeText(candidateAgent.displayName, candidateAgent.agentCode),
343
+ displayName: normalizeText(candidateAgent.displayName, candidateAgent.agentId),
343
344
  headline: normalizeText(membership.profileSnapshot?.headline, null),
344
- address: candidateAgent.address,
345
345
  online: presenceState.online === true,
346
346
  connectedAt: presenceState.connectedAt,
347
347
  lastHeartbeatAt: presenceState.lastHeartbeatAt,
@@ -352,6 +352,13 @@ export function createWorldSearchService({
352
352
  reasonSummary: summarizeMatchedFields(matchedFields),
353
353
  joinedAt: membership.joinedAt,
354
354
  profileSummary: projectProfileSummary(world, membership.profileSnapshot || {}, candidateAgent),
355
+ worldFeedbackSummary: conversationFeedbackService?.summarizeWorldAgent?.({
356
+ worldId: world.worldId,
357
+ agentId: candidateAgent.agentId,
358
+ }) || {
359
+ likesReceived: 0,
360
+ dislikesReceived: 0,
361
+ },
355
362
  };
356
363
  })
357
364
  .filter(Boolean);
@@ -1,5 +1,6 @@
1
1
  import { normalizeAgentProfile, resolveAgentDisplayName, resolveAgentVisibility } from '../../lib/agent-profile.js';
2
2
  import { normalizeChatRequestInput } from '../../lib/chat-request.js';
3
+ import { resolvePublicIdentity } from '../../lib/public-identity.js';
3
4
  import { createKickoffBrief, resolveStoredKickoffBrief } from '../../lib/relay/kickoff-text.js';
4
5
  import { WORLD_ACTIONS } from '../worlds/world-authorization.js';
5
6
  import { normalizeChatRequestApprovalPolicy } from '../contracts/chat-request-approval-policy.js';
@@ -67,7 +68,7 @@ function createAgentNotFoundError(agentId) {
67
68
  return error;
68
69
  }
69
70
 
70
- function createTargetAddressNotFoundError(targetAgentId) {
71
+ function createTargetAgentNotFoundError(targetAgentId) {
71
72
  const error = new Error(`chat_request_target_not_found:${targetAgentId}`);
72
73
  error.code = 'chat_request_target_not_found';
73
74
  error.status = 404;
@@ -124,10 +125,8 @@ function projectAgent(store, agentId, presence = null) {
124
125
  const presenceState = presence?.getPresence?.(agent.agentId) || store.getPresence(agent.agentId);
125
126
  return {
126
127
  agentId: agent.agentId,
127
- agentCode: agent.agentCode,
128
- domain: agent.domain,
129
- address: agent.address,
130
128
  displayName: resolveAgentDisplayName(agent),
129
+ publicIdentity: resolvePublicIdentity(agent),
131
130
  profile: normalizeAgentProfile(agent.profile),
132
131
  discoverable: visibility.discoverable,
133
132
  contactable: visibility.contactable,
@@ -374,6 +373,8 @@ export function createChatRequestService({
374
373
  presence = null,
375
374
  worldService = null,
376
375
  worldAuthorizationService = null,
376
+ publicIdentityService = null,
377
+ conversationFeedbackService = null,
377
378
  } = {}) {
378
379
  function assertStore() {
379
380
  if (!store) throw createConfigurationError('chat_request_store_unavailable');
@@ -440,6 +441,15 @@ export function createChatRequestService({
440
441
  function projectChatInboxChat(conversation = {}, viewerAgentId = null, request = null) {
441
442
  const direction = resolveInboxChatDirection(viewerAgentId, request);
442
443
  const counterpartyAgentId = resolveInboxChatCounterpartyAgentId(viewerAgentId, request, conversation);
444
+ const feedbackSummary = conversationFeedbackService?.summarizeConversation?.({
445
+ conversationKey: conversation.conversationKey,
446
+ viewerAgentId,
447
+ }) || {
448
+ likeCount: 0,
449
+ dislikeCount: 0,
450
+ viewerGave: null,
451
+ viewerReceived: null,
452
+ };
443
453
 
444
454
  return {
445
455
  chatRequestId: normalizeText(request?.chatRequestId || request?.requestId, null),
@@ -452,6 +462,7 @@ export function createChatRequestService({
452
462
  localSessionKey: normalizeText(conversation.sessionKey, null),
453
463
  counterparty: projectAgent(assertStore(), counterpartyAgentId, presence),
454
464
  conversation: resolveInboxConversationWorldSummary(worldService, request, conversation),
465
+ feedbackSummary,
455
466
  };
456
467
  }
457
468
 
@@ -501,6 +512,10 @@ export function createChatRequestService({
501
512
  source = 'chat_request',
502
513
  } = {}) {
503
514
  requireAgent(fromAgentId);
515
+ publicIdentityService?.assertPublicIdentityReady?.({
516
+ agentId: fromAgentId,
517
+ capability: 'request chat',
518
+ });
504
519
  const normalizedTargetAgentId = normalizeText(targetAgentId, null);
505
520
  if (!normalizedTargetAgentId) {
506
521
  throw createInvalidChatRequestError('chat_request_target_required', 'chat request target requires targetAgentId');
@@ -513,8 +528,8 @@ export function createChatRequestService({
513
528
  });
514
529
  const normalizedWorldId = normalizeConversationWorldId(worldId)
515
530
  || normalizeConversationWorldId(normalizedRequestInput.conversation?.worldId);
516
- if (!targetAgent?.address) {
517
- throw createTargetAddressNotFoundError(normalizedTargetAgentId);
531
+ if (!targetAgent?.agentId) {
532
+ throw createTargetAgentNotFoundError(normalizedTargetAgentId);
518
533
  }
519
534
  const normalizedOpeningPayload = cloneJsonObject(openingPayload) || cloneJsonObject(normalizedRequestInput.openingPayload);
520
535
  const normalizedKickoffBrief = createKickoffBrief({
@@ -549,7 +564,7 @@ export function createChatRequestService({
549
564
 
550
565
  const result = await assertRelay('createChatRequest').createChatRequest({
551
566
  fromAgentId,
552
- toAddress: targetAgent.address,
567
+ targetAgentId: targetAgent.agentId,
553
568
  kickoffBrief: normalizedKickoffBrief,
554
569
  openingMessage: normalizeText(
555
570
  normalizedKickoffBrief?.text,
@@ -12,7 +12,7 @@ export function registerFriendRoutes(app, { friendService }) {
12
12
  try {
13
13
  const result = await friendService.createFriendRequest({
14
14
  fromAgentId: req.body?.fromAgentId,
15
- toAddress: req.body?.toAddress,
15
+ targetAgentId: req.body?.targetAgentId,
16
16
  message: req.body?.message,
17
17
  metadata: req.body?.metadata,
18
18
  });
@@ -1,16 +1,12 @@
1
1
  import { normalizeAgentProfile, resolveAgentDisplayName, resolveAgentVisibility } from '../../lib/agent-profile.js';
2
2
  import { createRelayPolicyHooks, evaluatePolicyHook } from '../../lib/policy.js';
3
+ import { resolvePublicIdentity } from '../../lib/public-identity.js';
3
4
 
4
5
  function normalizeAgentId(agentId) {
5
6
  const normalized = String(agentId || '').trim();
6
7
  return normalized || null;
7
8
  }
8
9
 
9
- function normalizeAddress(address) {
10
- const normalized = String(address || '').trim().toLowerCase();
11
- return normalized || null;
12
- }
13
-
14
10
  function normalizeDirection(direction) {
15
11
  const normalized = String(direction || '').trim().toLowerCase();
16
12
  return normalized === 'inbound' || normalized === 'outbound' ? normalized : null;
@@ -54,8 +50,8 @@ function createAgentNotFoundError(agentId) {
54
50
  return error;
55
51
  }
56
52
 
57
- function createTargetNotFoundError(address) {
58
- const error = new Error(`friend_target_not_found:${address}`);
53
+ function createTargetNotFoundError(targetAgentId) {
54
+ const error = new Error(`friend_target_not_found:${targetAgentId}`);
59
55
  error.code = 'friend_target_not_found';
60
56
  error.status = 404;
61
57
  return error;
@@ -131,10 +127,8 @@ function projectAgent(store, agentId) {
131
127
  const visibility = resolveAgentVisibility(agent);
132
128
  return {
133
129
  agentId: agent.agentId,
134
- agentCode: agent.agentCode,
135
- domain: agent.domain,
136
- address: agent.address,
137
130
  displayName: resolveAgentDisplayName(agent),
131
+ publicIdentity: resolvePublicIdentity(agent),
138
132
  profile: normalizeAgentProfile(agent.profile),
139
133
  discoverable: visibility.discoverable,
140
134
  contactable: visibility.contactable,
@@ -149,7 +143,7 @@ function resolvePeerAgentId(friendship, viewerAgentId) {
149
143
  return null;
150
144
  }
151
145
 
152
- export function createFriendService({ store = null, policy = null } = {}) {
146
+ export function createFriendService({ store = null, policy = null, publicIdentityService = null } = {}) {
153
147
  const relayPolicy = createRelayPolicyHooks(policy);
154
148
 
155
149
  function assertStore() {
@@ -307,23 +301,27 @@ export function createFriendService({ store = null, policy = null } = {}) {
307
301
  }
308
302
 
309
303
  return {
310
- async createFriendRequest({ fromAgentId, toAddress, message = null, metadata = {} } = {}) {
304
+ async createFriendRequest({ fromAgentId, targetAgentId, message = null, metadata = {} } = {}) {
311
305
  const friendshipStore = assertStore();
312
306
  const fromAgent = requireAgent(fromAgentId);
313
- const normalizedToAddress = normalizeAddress(toAddress);
314
- if (!normalizedToAddress) {
315
- throw createTargetNotFoundError(toAddress);
307
+ publicIdentityService?.assertPublicIdentityReady?.({
308
+ agentId: fromAgent.agentId,
309
+ capability: 'send friend request',
310
+ });
311
+ const normalizedTargetAgentId = normalizeAgentId(targetAgentId);
312
+ if (!normalizedTargetAgentId) {
313
+ throw createTargetNotFoundError(targetAgentId);
316
314
  }
317
315
 
318
- const toAgent = friendshipStore.resolveAddress(normalizedToAddress);
319
- if (!toAgent) throw createTargetNotFoundError(normalizedToAddress);
316
+ const toAgent = friendshipStore.getAgent(normalizedTargetAgentId);
317
+ if (!toAgent) throw createTargetNotFoundError(normalizedTargetAgentId);
320
318
 
321
319
  const canRequest = evaluatePolicyHook({
322
320
  hook: relayPolicy.canRequest,
323
321
  context: {
324
322
  fromAgentId: fromAgent.agentId,
325
323
  fromAgent,
326
- toAddress: normalizedToAddress,
324
+ targetAgentId: normalizedTargetAgentId,
327
325
  toAgent,
328
326
  requestContext: {
329
327
  type: 'friend_request',
@@ -382,7 +380,6 @@ export function createFriendService({ store = null, policy = null } = {}) {
382
380
  const request = await friendshipStore.createFriendRequest({
383
381
  fromAgentId: fromAgent.agentId,
384
382
  toAgentId: toAgent.agentId,
385
- toAddress: toAgent.address,
386
383
  message: normalizeMessage(message),
387
384
  metadata,
388
385
  });
@@ -10,8 +10,8 @@ function sendSocialError(res, error) {
10
10
  export function registerSocialRoutes(app, { socialService }) {
11
11
  app.get('/v1/social/agents/lookup', (req, res) => {
12
12
  try {
13
- const result = socialService.lookupAgentByCode({
14
- agentCode: req.query.agentCode,
13
+ const result = socialService.lookupAgentByIdentity({
14
+ identity: req.query.identity,
15
15
  });
16
16
  res.json(result);
17
17
  } catch (error) {