@xfxstudio/claworld 0.2.11 → 0.2.12

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.
@@ -8,7 +8,7 @@
8
8
  ],
9
9
  "name": "Claworld Persona Relay",
10
10
  "description": "Claworld relay world channel plugin for OpenClaw.",
11
- "version": "0.2.11",
11
+ "version": "0.2.12",
12
12
  "configSchema": {
13
13
  "type": "object",
14
14
  "additionalProperties": false,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@xfxstudio/claworld",
3
- "version": "0.2.11",
3
+ "version": "0.2.12",
4
4
  "description": "Claworld channel plugin for OpenClaw",
5
5
  "type": "module",
6
6
  "main": "index.js",
@@ -300,7 +300,7 @@ function printInstallSummary(result) {
300
300
  console.log(`Managed account: ${result.transformed?.config?.channels?.claworld?.defaultAccount || 'claworld'}`);
301
301
  console.log(`OpenClaw version: ${result.host?.version || '(unknown)'}`);
302
302
  console.log(`Plugin action: ${result.plugin?.action || 'unknown'}`);
303
- console.log(`Activation: ${result.activationStatus === 'ready' ? 'ready' : 'pending via claworld_update_public_identity'}`);
303
+ console.log(`Activation: ${result.activationStatus === 'ready' ? 'ready' : 'pending via claworld_profile'}`);
304
304
  console.log(`Runtime refresh: ${result.runtimeRefresh?.action || 'unknown'}`);
305
305
  console.log(`Config path: ${result.configPath}`);
306
306
  console.log(`Backup path: ${result.backupPath || '(unchanged or new file)'}`);
@@ -319,7 +319,7 @@ function printUpdateSummary(result, doctorResult) {
319
319
  console.log(`OpenClaw version: ${result.host?.version || '(unknown)'}`);
320
320
  console.log(`Plugin action: ${result.plugin?.action || 'unknown'}`);
321
321
  console.log(`Tracked install: ${trackedSummary}`);
322
- console.log(`Activation: ${result.activationStatus === 'ready' ? 'ready' : 'pending via claworld_update_public_identity'}`);
322
+ console.log(`Activation: ${result.activationStatus === 'ready' ? 'ready' : 'pending via claworld_profile'}`);
323
323
  console.log(`Runtime refresh: ${result.runtimeRefresh?.action || 'unknown'}`);
324
324
  console.log(`Doctor status: ${doctorResult?.status || 'unknown'}`);
325
325
  console.log(`Config path: ${result.configPath}`);
@@ -528,7 +528,7 @@ export async function runClaworldDoctor({
528
528
  : `Managed account token status is ${configuredAccount.tokenStatus}; activation is still pending.`,
529
529
  action: configuredAccount.tokenStatus === 'available'
530
530
  ? null
531
- : 'Open a live OpenClaw session, run `claworld_pair_agent`, and then complete `claworld_update_public_identity` when prompted.',
531
+ : 'Open a live OpenClaw session, run `claworld_pair_agent`, and then complete `claworld_profile` with `action=update_identity` when prompted.',
532
532
  details: { tokenSource: configuredAccount.tokenSource, tokenStatus: configuredAccount.tokenStatus },
533
533
  }));
534
534
 
@@ -593,7 +593,7 @@ export async function runClaworldDoctor({
593
593
  label: 'Stable relay binding',
594
594
  status: 'warn',
595
595
  summary: 'Doctor could not verify the managed relay binding because activation is still pending.',
596
- action: 'Open a live OpenClaw session, run `claworld_pair_agent`, and then complete `claworld_update_public_identity` when prompted.',
596
+ action: 'Open a live OpenClaw session, run `claworld_pair_agent`, and then complete `claworld_profile` with `action=update_identity` when prompted.',
597
597
  }));
598
598
  } else {
599
599
  checks.push(createCheck({
@@ -676,7 +676,7 @@ export async function runClaworldDoctor({
676
676
  action: channelHealthy
677
677
  ? null
678
678
  : channelPendingActivation
679
- ? 'Open a live OpenClaw session, run `claworld_pair_agent`, and then complete `claworld_update_public_identity` when prompted.'
679
+ ? 'Open a live OpenClaw session, run `claworld_pair_agent`, and then complete `claworld_profile` with `action=update_identity` when prompted.'
680
680
  : `Rerun \`${CLAWORLD_INSTALLER_COMMAND}\` or restart the gateway to recover the managed account runtime.`,
681
681
  details: { channelAccount },
682
682
  }));
@@ -908,6 +908,8 @@ async function fetchJson(fetchImpl, url, init = {}) {
908
908
  async function fetchPublicIdentity({
909
909
  runtimeConfig,
910
910
  agentId = null,
911
+ generateShareCard = false,
912
+ expiresInSeconds = null,
911
913
  fetchImpl,
912
914
  }) {
913
915
  if (!resolveRuntimeAppToken(runtimeConfig)) {
@@ -930,7 +932,7 @@ async function fetchPublicIdentity({
930
932
  recommendedDisplayName,
931
933
  nextAction: 'set_public_identity',
932
934
  requiredAction: 'set_public_identity',
933
- nextTool: 'claworld_update_public_identity',
935
+ nextTool: 'claworld_profile',
934
936
  missingFields: [
935
937
  {
936
938
  fieldId: 'displayName',
@@ -953,15 +955,21 @@ async function fetchPublicIdentity({
953
955
  }
954
956
 
955
957
  const baseUrl = normalizeRelayHttpBaseUrl(runtimeConfig.serverUrl);
956
- const path = buildRelayJsonPath('/v1/profile/public-identity', {
957
- agentId,
958
- });
959
- const result = await fetchJson(fetchImpl, `${baseUrl}${path}`, {
960
- method: 'GET',
958
+ const result = await fetchJson(fetchImpl, `${baseUrl}/v1/profile`, {
959
+ method: 'POST',
961
960
  headers: {
961
+ 'content-type': 'application/json',
962
962
  ...(runtimeConfig.apiKey ? { 'x-api-key': runtimeConfig.apiKey } : {}),
963
963
  ...buildRuntimeAuthHeaders(runtimeConfig),
964
964
  },
965
+ body: JSON.stringify({
966
+ ...(agentId ? { agentId } : {}),
967
+ action: 'view',
968
+ ...(generateShareCard === true ? { generateShareCard: true } : {}),
969
+ ...(normalizeClaworldInteger(expiresInSeconds, null) > 0
970
+ ? { expiresInSeconds: normalizeClaworldInteger(expiresInSeconds, null) }
971
+ : {}),
972
+ }),
965
973
  });
966
974
  if (!result.ok) {
967
975
  createRelayRouteError({
@@ -979,6 +987,8 @@ async function updatePublicIdentity({
979
987
  runtimeConfig,
980
988
  agentId = null,
981
989
  displayName = null,
990
+ generateShareCard = true,
991
+ expiresInSeconds = null,
982
992
  fetchImpl,
983
993
  }) {
984
994
  const normalizedDisplayName = normalizeClaworldText(displayName, null);
@@ -1039,8 +1049,8 @@ async function updatePublicIdentity({
1039
1049
  resolvedAgentId = activatedAgentId;
1040
1050
  }
1041
1051
  const baseUrl = normalizeRelayHttpBaseUrl(runtimeConfig.serverUrl);
1042
- const result = await fetchJson(fetchImpl, `${baseUrl}/v1/profile/public-identity`, {
1043
- method: 'PUT',
1052
+ const result = await fetchJson(fetchImpl, `${baseUrl}/v1/profile`, {
1053
+ method: 'POST',
1044
1054
  headers: {
1045
1055
  'content-type': 'application/json',
1046
1056
  ...(resolvedRuntimeConfig.apiKey ? { 'x-api-key': resolvedRuntimeConfig.apiKey } : {}),
@@ -1048,7 +1058,12 @@ async function updatePublicIdentity({
1048
1058
  },
1049
1059
  body: JSON.stringify({
1050
1060
  ...(resolvedAgentId ? { agentId: resolvedAgentId } : {}),
1061
+ action: 'update_identity',
1051
1062
  displayName: normalizedDisplayName,
1063
+ ...(generateShareCard === true ? { generateShareCard: true } : {}),
1064
+ ...(normalizeClaworldInteger(expiresInSeconds, null) > 0
1065
+ ? { expiresInSeconds: normalizeClaworldInteger(expiresInSeconds, null) }
1066
+ : {}),
1052
1067
  }),
1053
1068
  });
1054
1069
  if (!result.ok) {
@@ -1074,6 +1089,60 @@ async function updatePublicIdentity({
1074
1089
  };
1075
1090
  }
1076
1091
 
1092
+ async function renderAgentCard({
1093
+ runtimeConfig,
1094
+ agentId = null,
1095
+ expiresInSeconds = null,
1096
+ forceRegenerate = true,
1097
+ fetchImpl,
1098
+ }) {
1099
+ const resolvedAgentId = normalizeClaworldText(
1100
+ agentId,
1101
+ normalizeClaworldText(runtimeConfig?.relay?.agentId, null),
1102
+ );
1103
+ if (!resolvedAgentId) {
1104
+ throw createRuntimeBoundaryError({
1105
+ code: 'tool_input_invalid',
1106
+ category: 'input',
1107
+ status: 400,
1108
+ message: 'claworld profile card rendering requires agentId',
1109
+ publicMessage: 'claworld profile card rendering requires agentId',
1110
+ recoverable: true,
1111
+ context: { field: 'agentId' },
1112
+ });
1113
+ }
1114
+
1115
+ const baseUrl = normalizeRelayHttpBaseUrl(runtimeConfig.serverUrl);
1116
+ const normalizedExpiresInSeconds = normalizeClaworldInteger(expiresInSeconds, null);
1117
+ const result = await fetchJson(fetchImpl, `${baseUrl}/v1/agent-cards/render`, {
1118
+ method: 'POST',
1119
+ headers: {
1120
+ 'content-type': 'application/json',
1121
+ ...(runtimeConfig.apiKey ? { 'x-api-key': runtimeConfig.apiKey } : {}),
1122
+ ...buildRuntimeAuthHeaders(runtimeConfig),
1123
+ },
1124
+ body: JSON.stringify({
1125
+ agentId: resolvedAgentId,
1126
+ forceRegenerate: forceRegenerate === true,
1127
+ ...(normalizedExpiresInSeconds && normalizedExpiresInSeconds > 0
1128
+ ? { expiresInSeconds: normalizedExpiresInSeconds }
1129
+ : {}),
1130
+ }),
1131
+ });
1132
+ if (!result.ok) {
1133
+ createRelayRouteError({
1134
+ result,
1135
+ runtimeConfig,
1136
+ code: 'agent_card_render_failed',
1137
+ publicMessage: 'failed to generate public identity card',
1138
+ context: {
1139
+ agentId: resolvedAgentId,
1140
+ },
1141
+ });
1142
+ }
1143
+ return result.body || {};
1144
+ }
1145
+
1077
1146
  async function registerRelayBinding({ runtimeConfig, fetchImpl, logger }) {
1078
1147
  if (typeof fetchImpl !== 'function') {
1079
1148
  throw new Error('fetch is unavailable for relay registration');
@@ -2443,6 +2512,8 @@ export function createClaworldChannelPlugin({
2443
2512
  return fetchPublicIdentity({
2444
2513
  runtimeConfig: resolvedContext.runtimeConfig,
2445
2514
  agentId: resolvedContext.agentId || null,
2515
+ generateShareCard: context.generateShareCard === true,
2516
+ expiresInSeconds: context.expiresInSeconds ?? null,
2446
2517
  fetchImpl,
2447
2518
  });
2448
2519
  }
@@ -2453,6 +2524,8 @@ export function createClaworldChannelPlugin({
2453
2524
  runtimeConfig: resolvedContext.runtimeConfig,
2454
2525
  agentId: resolvedContext.agentId || null,
2455
2526
  displayName: context.displayName || null,
2527
+ generateShareCard: context.generateShareCard !== false,
2528
+ expiresInSeconds: context.expiresInSeconds ?? null,
2456
2529
  fetchImpl,
2457
2530
  });
2458
2531
 
@@ -2504,11 +2577,21 @@ export function createClaworldChannelPlugin({
2504
2577
  const payload = updateResult && typeof updateResult === 'object' && !Array.isArray(updateResult)
2505
2578
  ? { ...updateResult }
2506
2579
  : {};
2507
- delete payload.runtimeActivation;
2508
2580
  delete payload.runtimeConfig;
2509
2581
  return payload;
2510
2582
  }
2511
2583
 
2584
+ async function generateRuntimeProfileCard(context = {}) {
2585
+ const resolvedContext = await resolveBoundRuntimeContext(context);
2586
+ return renderAgentCard({
2587
+ runtimeConfig: resolvedContext.runtimeConfig,
2588
+ agentId: context.agentId || resolvedContext.agentId || null,
2589
+ expiresInSeconds: context.expiresInSeconds ?? null,
2590
+ forceRegenerate: context.forceRegenerate !== false,
2591
+ fetchImpl,
2592
+ });
2593
+ }
2594
+
2512
2595
  return {
2513
2596
  id: 'claworld',
2514
2597
  meta: {
@@ -2763,6 +2846,7 @@ export function createClaworldChannelPlugin({
2763
2846
  profile: {
2764
2847
  getPublicIdentity: getRuntimePublicIdentity,
2765
2848
  updatePublicIdentity: updateRuntimePublicIdentity,
2849
+ generateShareCard: generateRuntimeProfileCard,
2766
2850
  },
2767
2851
  postSetup: {
2768
2852
  fetchWorldDirectory: async (context = {}) => {
@@ -2917,6 +3001,7 @@ export function createClaworldChannelPlugin({
2917
3001
  profile: {
2918
3002
  getPublicIdentity: getRuntimePublicIdentity,
2919
3003
  updatePublicIdentity: updateRuntimePublicIdentity,
3004
+ generateShareCard: generateRuntimeProfileCard,
2920
3005
  },
2921
3006
  fetchWorldDirectory: async (context = {}) => {
2922
3007
  const resolvedContext = await resolveBoundRuntimeContext(context);
@@ -201,7 +201,7 @@ async function applyManagedOnboardingConfig({
201
201
  `Remote backend: ${managedOptions.serverUrl}`,
202
202
  managedOptions.appToken
203
203
  ? 'Activation state: ready via configured appToken'
204
- : 'Activation state: pending until claworld_update_public_identity runs',
204
+ : 'Activation state: pending until claworld_profile runs',
205
205
  managedOptions.manageWorkspace
206
206
  ? 'This flow refreshes plugin-side config and the dedicated claworld workspace contract. It does not start a backend service.'
207
207
  : 'This flow refreshes plugin-side config and binds claworld onto the existing local agent. It does not start a backend service.',
@@ -239,6 +239,17 @@ function integerParam({
239
239
  };
240
240
  }
241
241
 
242
+ function booleanParam({
243
+ description = null,
244
+ defaultValue = null,
245
+ } = {}) {
246
+ return {
247
+ type: 'boolean',
248
+ ...(description ? { description } : {}),
249
+ ...(typeof defaultValue === 'boolean' ? { default: defaultValue } : {}),
250
+ };
251
+ }
252
+
242
253
  function objectParam({
243
254
  description = null,
244
255
  properties = {},
@@ -337,6 +348,118 @@ function projectToolManageWorldActionResponse(payload = {}, { accountId = null,
337
348
  };
338
349
  }
339
350
 
351
+ const PROFILE_ACTIONS = Object.freeze([
352
+ 'view',
353
+ 'update_identity',
354
+ ]);
355
+
356
+ function normalizeProfileAction(value, fallback = null) {
357
+ const normalized = normalizeText(value, fallback);
358
+ return PROFILE_ACTIONS.includes(normalized) ? normalized : fallback;
359
+ }
360
+
361
+ function inferProfileAction(params = {}) {
362
+ const explicitAction = normalizeProfileAction(params.action, null);
363
+ if (explicitAction) return explicitAction;
364
+ if (normalizeText(params.displayName, null)) return 'update_identity';
365
+ return 'view';
366
+ }
367
+
368
+ function projectToolPublicIdentity(payload = null) {
369
+ if (!payload || typeof payload !== 'object') return null;
370
+ return {
371
+ status: payload.status || null,
372
+ ready: payload.ready ?? null,
373
+ publicIdentity: payload.publicIdentity && typeof payload.publicIdentity === 'object'
374
+ ? {
375
+ status: payload.publicIdentity.status || null,
376
+ displayIdentity: payload.publicIdentity.displayIdentity || null,
377
+ displayName: payload.publicIdentity.displayName || null,
378
+ code: payload.publicIdentity.code || null,
379
+ confirmedAt: payload.publicIdentity.confirmedAt || null,
380
+ updatedAt: payload.publicIdentity.updatedAt || null,
381
+ }
382
+ : null,
383
+ recommendedDisplayName: payload.recommendedDisplayName || null,
384
+ requiredAction: payload.requiredAction || null,
385
+ nextAction: payload.nextAction || null,
386
+ nextTool: payload.nextTool || null,
387
+ missingFields: Array.isArray(payload.missingFields) ? payload.missingFields : [],
388
+ feedbackSummary: payload.feedbackSummary && typeof payload.feedbackSummary === 'object'
389
+ ? {
390
+ totalLikesReceived: Number(payload.feedbackSummary.totalLikesReceived || 0),
391
+ totalDislikesReceived: Number(payload.feedbackSummary.totalDislikesReceived || 0),
392
+ totalLikesGiven: Number(payload.feedbackSummary.totalLikesGiven || 0),
393
+ totalDislikesGiven: Number(payload.feedbackSummary.totalDislikesGiven || 0),
394
+ }
395
+ : null,
396
+ };
397
+ }
398
+
399
+ function projectToolShareCard(payload = null) {
400
+ const card = payload?.card && typeof payload.card === 'object' ? payload.card : null;
401
+ const imageUrl = normalizeText(card?.imageUrl, normalizeText(payload?.imageUrl, null));
402
+ const downloadUrl = normalizeText(card?.downloadUrl, normalizeText(payload?.downloadUrl, imageUrl));
403
+ const templateId = normalizeText(card?.templateId, normalizeText(payload?.templateId, null));
404
+ const expiresAt = normalizeText(card?.expiresAt, normalizeText(payload?.expiresAt, null));
405
+ const description = normalizeText(card?.description, normalizeText(payload?.description, null));
406
+ if (!imageUrl && !downloadUrl && !templateId && !expiresAt && !description) {
407
+ return {
408
+ status: normalizeText(payload?.status, 'unavailable'),
409
+ reason: normalizeText(payload?.reason, null),
410
+ message: normalizeText(payload?.message, null),
411
+ };
412
+ }
413
+ return {
414
+ status: normalizeText(payload?.status, 'ready'),
415
+ imageUrl,
416
+ downloadUrl,
417
+ templateId,
418
+ expiresAt,
419
+ description,
420
+ };
421
+ }
422
+
423
+ function projectToolProfileResponse({
424
+ action = 'view',
425
+ accountId = null,
426
+ identityPayload = null,
427
+ shareCard = undefined,
428
+ runtimeActivation = null,
429
+ } = {}) {
430
+ const projectedIdentity = projectToolPublicIdentity(identityPayload);
431
+ const resolvedShareCard = shareCard !== undefined
432
+ ? shareCard
433
+ : (identityPayload && Object.prototype.hasOwnProperty.call(identityPayload, 'shareCard')
434
+ ? projectToolShareCard(identityPayload.shareCard)
435
+ : undefined);
436
+ const ready = projectedIdentity?.ready === true;
437
+ return {
438
+ action,
439
+ status: ready ? 'ready' : 'pending',
440
+ ready,
441
+ accountId: normalizeText(accountId, null),
442
+ ...(projectedIdentity || {
443
+ publicIdentity: null,
444
+ recommendedDisplayName: null,
445
+ requiredAction: null,
446
+ nextAction: null,
447
+ nextTool: null,
448
+ missingFields: [],
449
+ feedbackSummary: null,
450
+ }),
451
+ ...(resolvedShareCard !== undefined ? { shareCard: resolvedShareCard } : {}),
452
+ ...(runtimeActivation ? { runtimeActivation } : {}),
453
+ ...(action === 'update_identity'
454
+ ? {
455
+ updated: resolvedShareCard && resolvedShareCard.status === 'ready'
456
+ ? ['publicIdentity', 'shareCard']
457
+ : ['publicIdentity'],
458
+ }
459
+ : {}),
460
+ };
461
+ }
462
+
340
463
  function buildRegisteredTools(api, plugin) {
341
464
  const accountIdProperty = stringParam({
342
465
  description: 'Claworld account id to execute the tool against. In managed installs this is usually the dedicated claworld account.',
@@ -1145,7 +1268,7 @@ function buildRegisteredTools(api, plugin) {
1145
1268
  reason: payload.reason || null,
1146
1269
  requiredAction: publicIdentity?.requiredAction || (ready ? null : 'set_public_identity'),
1147
1270
  nextAction: publicIdentity?.nextAction || (ready ? 'continue_claworld_flow' : 'set_public_identity'),
1148
- nextTool: publicIdentity?.nextTool || (ready ? null : 'claworld_update_public_identity'),
1271
+ nextTool: publicIdentity?.nextTool || (ready ? null : 'claworld_profile'),
1149
1272
  missingFields: Array.isArray(publicIdentity?.missingFields) ? publicIdentity.missingFields : [],
1150
1273
  accountId: payload.runtimeConfig?.accountId || accountId,
1151
1274
  bindingSource: payload.bindingSource || null,
@@ -1186,59 +1309,110 @@ function buildRegisteredTools(api, plugin) {
1186
1309
  },
1187
1310
  },
1188
1311
  {
1189
- name: 'claworld_get_public_identity',
1190
- label: 'Claworld Get Public Identity',
1191
- description: 'Read the current public identity state for the paired Claworld agent. Use this before world join or request-chat if you need to confirm whether public naming is complete.',
1312
+ name: 'claworld_profile',
1313
+ label: 'Claworld Profile',
1314
+ description: 'View or update the paired Claworld public profile. This surface covers public identity readiness and, when requested, generates a temporary public identity card for sharing.',
1192
1315
  metadata: buildToolMetadata({
1193
- category: 'bootstrap',
1316
+ category: 'profile',
1194
1317
  usageNotes: [
1195
- 'Use when pair_agent indicates public identity is still pending.',
1196
- 'Use before requesting a public-name confirmation from the user.',
1318
+ 'Default action is view; omit action to inspect the current public identity state.',
1319
+ 'Use action=update_identity after the user confirms a public-facing display name.',
1320
+ 'Set generateShareCard=true to return a temporary public identity card URL.',
1197
1321
  ],
1198
- }),
1199
- parameters: objectParam({
1200
- description: 'Read the current public identity state for one Claworld account.',
1201
- required: ['accountId'],
1202
- properties: {
1203
- accountId: accountIdProperty,
1204
- },
1205
- }),
1206
- async execute(_toolCallId, params = {}) {
1207
- const context = await resolveToolContext(api, plugin, params, { bindRuntime: false });
1208
- const payload = await plugin.runtime.productShell.profile.getPublicIdentity(context);
1209
- return buildToolResult(payload);
1210
- },
1211
- },
1212
- {
1213
- name: 'claworld_update_public_identity',
1214
- label: 'Claworld Update Public Identity',
1215
- description: 'Set or update the public display name for the paired Claworld agent. On first setup, the backend will generate a stable unique code and return the final displayName#code identity.',
1216
- metadata: buildToolMetadata({
1217
- category: 'bootstrap',
1218
- usageNotes: [
1219
- 'Use after the user confirms a public-facing name.',
1220
- 'On first setup, this generates the stable public code and completes identity readiness.',
1322
+ examples: [
1323
+ {
1324
+ title: 'View the current profile state',
1325
+ input: {
1326
+ accountId: 'claworld',
1327
+ action: 'view',
1328
+ },
1329
+ outcome: 'Returns the current public identity and readiness state.',
1330
+ },
1331
+ {
1332
+ title: 'Update public identity and return a share card',
1333
+ input: {
1334
+ accountId: 'claworld',
1335
+ action: 'update_identity',
1336
+ displayName: '小发发',
1337
+ generateShareCard: true,
1338
+ },
1339
+ outcome: 'Persists the display name, keeps the stable code, and returns a temporary share-card URL.',
1340
+ },
1221
1341
  ],
1222
1342
  }),
1223
1343
  parameters: objectParam({
1224
- description: 'Update the public display name for one Claworld account.',
1225
- required: ['accountId', 'displayName'],
1344
+ description: 'View or update the public profile for one Claworld account.',
1345
+ required: ['accountId'],
1226
1346
  properties: {
1227
1347
  accountId: accountIdProperty,
1348
+ action: stringParam({
1349
+ description: 'Profile action. Defaults to view; inferred as update_identity when displayName is present.',
1350
+ enumValues: PROFILE_ACTIONS,
1351
+ examples: ['view', 'update_identity'],
1352
+ }),
1228
1353
  displayName: stringParam({
1229
- description: 'Public-facing display name. # is reserved and must not appear here.',
1354
+ description: 'Public-facing display name. Required for action=update_identity. # is reserved and must not appear here.',
1230
1355
  minLength: 1,
1231
1356
  examples: ['Moza', '小发发'],
1232
1357
  }),
1358
+ generateShareCard: booleanParam({
1359
+ description: 'When true, return a temporary public identity card URL. Defaults to false for view and true for update_identity.',
1360
+ }),
1361
+ expiresInSeconds: integerParam({
1362
+ description: 'Optional temporary share-card TTL in seconds.',
1363
+ minimum: 1,
1364
+ examples: [7200],
1365
+ }),
1233
1366
  },
1367
+ examples: [
1368
+ {
1369
+ accountId: 'claworld',
1370
+ action: 'view',
1371
+ },
1372
+ {
1373
+ accountId: 'claworld',
1374
+ action: 'update_identity',
1375
+ displayName: '小发发',
1376
+ generateShareCard: true,
1377
+ },
1378
+ ],
1234
1379
  }),
1235
1380
  async execute(_toolCallId, params = {}) {
1236
1381
  const context = await resolveToolContext(api, plugin, params, { bindRuntime: false });
1237
- const payload = await plugin.runtime.productShell.profile.updatePublicIdentity({
1382
+ const action = inferProfileAction(params);
1383
+ const generateShareCard = typeof params.generateShareCard === 'boolean'
1384
+ ? params.generateShareCard
1385
+ : action === 'update_identity';
1386
+
1387
+ if (action === 'update_identity') {
1388
+ const displayName = normalizeText(params.displayName, null);
1389
+ if (!displayName) {
1390
+ requireManageWorldField('displayName', 'displayName is required for action=update_identity');
1391
+ }
1392
+ const payload = await plugin.runtime.productShell.profile.updatePublicIdentity({
1393
+ ...context,
1394
+ displayName,
1395
+ generateShareCard,
1396
+ expiresInSeconds: params.expiresInSeconds ?? null,
1397
+ });
1398
+ return buildToolResult(projectToolProfileResponse({
1399
+ action,
1400
+ accountId: context.accountId,
1401
+ identityPayload: payload,
1402
+ runtimeActivation: payload?.runtimeActivation || null,
1403
+ }));
1404
+ }
1405
+
1406
+ const payload = await plugin.runtime.productShell.profile.getPublicIdentity({
1238
1407
  ...context,
1239
- displayName: params.displayName,
1408
+ generateShareCard,
1409
+ expiresInSeconds: params.expiresInSeconds ?? null,
1240
1410
  });
1241
- return buildToolResult(payload);
1411
+ return buildToolResult(projectToolProfileResponse({
1412
+ action,
1413
+ accountId: context.accountId,
1414
+ identityPayload: payload,
1415
+ }));
1242
1416
  },
1243
1417
  },
1244
1418
  ].map((tool) => ({
@@ -12,8 +12,7 @@ export const CLAWORLD_BOOTSTRAP_TOOL_NAMES = Object.freeze([
12
12
  ]);
13
13
 
14
14
  export const CLAWORLD_PROFILE_TOOL_NAMES = Object.freeze([
15
- 'claworld_get_public_identity',
16
- 'claworld_update_public_identity',
15
+ 'claworld_profile',
17
16
  ]);
18
17
 
19
18
  export const CLAWORLD_FEEDBACK_TOOL_NAMES = Object.freeze([
@@ -0,0 +1,64 @@
1
+ import { resolveAuthenticatedAgentId } from '../../lib/http-auth.js';
2
+
3
+ function sendAgentCardError(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 buildAbsoluteUrl(req, publicPath) {
13
+ return `${req.protocol}://${req.get('host')}${publicPath}`;
14
+ }
15
+
16
+ export function registerAgentCardRoutes(app, { store, agentCardService }) {
17
+ app.get('/v1/meta/agent-cards', (_req, res) => {
18
+ res.json(agentCardService.getManifest());
19
+ });
20
+
21
+ app.post('/v1/agent-cards/render', async (req, res) => {
22
+ const resolvedAgent = resolveAuthenticatedAgentId({
23
+ store,
24
+ req,
25
+ providedAgentId: req.body?.agentId || null,
26
+ fieldName: 'agentId',
27
+ });
28
+ if (!resolvedAgent.ok) {
29
+ return res.status(resolvedAgent.status).json(resolvedAgent.body);
30
+ }
31
+
32
+ try {
33
+ const result = await agentCardService.renderCard({
34
+ agentId: resolvedAgent.agentId,
35
+ publicHandle: req.body?.publicHandle,
36
+ displayName: req.body?.displayName,
37
+ templateId: req.body?.templateId,
38
+ templateVersion: req.body?.templateVersion,
39
+ themeId: req.body?.themeId,
40
+ title: req.body?.title,
41
+ subtitle: req.body?.subtitle,
42
+ ctaLines: req.body?.ctaLines,
43
+ qrTargetUrl: req.body?.qrTargetUrl,
44
+ footerLabel: req.body?.footerLabel,
45
+ badgeText: req.body?.badgeText,
46
+ expiresInSeconds: req.body?.expiresInSeconds,
47
+ forceRegenerate: req.body?.forceRegenerate === true,
48
+ });
49
+
50
+ const imageUrl = result.card.imageUrl || (result.card.publicPath ? buildAbsoluteUrl(req, result.card.publicPath) : null);
51
+ const downloadUrl = result.card.downloadUrl || imageUrl;
52
+ return res.status(result.cacheHit ? 200 : 201).json({
53
+ ...result,
54
+ card: {
55
+ ...result.card,
56
+ ...(imageUrl ? { imageUrl } : {}),
57
+ ...(downloadUrl ? { downloadUrl } : {}),
58
+ },
59
+ });
60
+ } catch (error) {
61
+ return sendAgentCardError(res, error);
62
+ }
63
+ });
64
+ }