@xfxstudio/claworld 0.2.11 → 0.2.13

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.
@@ -1,6 +1,5 @@
1
1
  import {
2
2
  DEFAULT_CLAWORLD_ACCOUNT_ID,
3
- DEFAULT_CLAWORLD_SERVER_URL,
4
3
  applyClaworldManagedRuntimeConfig,
5
4
  ensureObject,
6
5
  normalizeText,
@@ -11,15 +10,11 @@ import {
11
10
  inspectClaworldChannelAccount,
12
11
  listClaworldAccountIds,
13
12
  } from './config-schema.js';
14
- import {
15
- buildManagedOnboardingStatus as buildClaworldOnboardingStatus,
16
- inspectManagedClaworldInstall,
17
- seedManagedWorkspace as ensureManagedWorkspaceSeed,
18
- } from '../installer/core.js';
19
13
 
20
14
  function collectUnsupportedSetupFlags(input = {}) {
21
15
  const unsupported = [];
22
16
  const flagMap = [
17
+ ['appToken', '--app-token'],
23
18
  ['token', '--token'],
24
19
  ['tokenFile', '--token-file'],
25
20
  ['botToken', '--bot-token'],
@@ -54,89 +49,143 @@ function collectUnsupportedSetupFlags(input = {}) {
54
49
  return unsupported;
55
50
  }
56
51
 
57
- function validateClaworldSetupInput({ cfg = {}, accountId = null, input = {} } = {}) {
52
+ export function validateClaworldSetupInput({ input = {} } = {}) {
58
53
  const unsupportedFlags = collectUnsupportedSetupFlags(input);
59
54
  if (unsupportedFlags.length > 0) {
60
55
  return (
61
- 'Claworld setup only supports --name, --http-url/--url, and --app-token. ' +
62
- `Unsupported flag(s): ${unsupportedFlags.join(', ')}.`
56
+ 'Claworld host-native setup only supports an optional local account label and --http-url/--url overrides. '
57
+ + `Unsupported flag(s): ${unsupportedFlags.join(', ')}.`
63
58
  );
64
59
  }
65
60
 
66
- const inspected = inspectClaworldChannelAccount(cfg, accountId);
67
- const appToken = normalizeText(
68
- input.appToken,
69
- normalizeText(inspected?.appToken, null),
70
- );
71
-
72
61
  const serverUrl = normalizeText(input.httpUrl, normalizeText(input.url, null));
73
- if (serverUrl) {
74
- try {
75
- const parsed = new URL(serverUrl);
76
- if (!['http:', 'https:', 'ws:', 'wss:'].includes(parsed.protocol)) {
77
- return `Unsupported Claworld server URL protocol: ${parsed.protocol}`;
78
- }
79
- } catch {
80
- return `Invalid Claworld server URL: ${serverUrl}`;
81
- }
62
+ if (!serverUrl) {
63
+ return null;
82
64
  }
83
65
 
84
- const registrationDisplayName = normalizeText(
85
- input.name,
86
- normalizeText(
87
- inspected?.name,
88
- normalizeText(
89
- inspected?.registration?.displayName,
90
- normalizeText(inspected?.localAgent?.displayName, null),
91
- ),
92
- ),
93
- );
94
- if (!appToken && !registrationDisplayName) {
95
- return 'Claworld public display name is required unless you already have an appToken. Use --name <display-name> or --app-token <token>.';
66
+ try {
67
+ const parsed = new URL(serverUrl);
68
+ if (!['http:', 'https:', 'ws:', 'wss:'].includes(parsed.protocol)) {
69
+ return `Unsupported Claworld server URL protocol: ${parsed.protocol}`;
70
+ }
71
+ } catch {
72
+ return `Invalid Claworld server URL: ${serverUrl}`;
96
73
  }
97
74
 
98
75
  return null;
99
76
  }
100
77
 
101
- function currentManagedIdentityInput({ cfg = {}, accountId = null } = {}) {
102
- const inspected = inspectClaworldChannelAccount(cfg, accountId);
103
- const appToken = normalizeText(inspected?.appToken, null);
104
- if (appToken) {
105
- return {
106
- appToken,
107
- };
108
- }
78
+ function findAgentEntry(config = {}, agentId) {
79
+ const normalizedAgentId = normalizeText(agentId, null);
80
+ if (!normalizedAgentId) return null;
81
+ const list = Array.isArray(config?.agents?.list) ? config.agents.list : [];
82
+ return list
83
+ .map((entry) => ensureObject(entry))
84
+ .find((entry) => entry.id === normalizedAgentId) || null;
85
+ }
109
86
 
110
- const currentDisplayName = normalizeText(
111
- inspected?.name,
112
- normalizeText(
113
- inspected?.registration?.displayName,
114
- normalizeText(inspected?.localAgent?.displayName, null),
115
- ),
87
+ function hasClaworldBinding(config = {}, { agentId, accountId } = {}) {
88
+ const normalizedAgentId = normalizeText(agentId, null);
89
+ const normalizedAccountId = normalizeText(accountId, DEFAULT_CLAWORLD_ACCOUNT_ID);
90
+ const resolvedDefaultAccountId = defaultClaworldAccountId(config) || DEFAULT_CLAWORLD_ACCOUNT_ID;
91
+ const bindings = Array.isArray(config?.bindings) ? config.bindings : [];
92
+ return bindings.some((binding) => {
93
+ const candidate = ensureObject(binding);
94
+ const match = ensureObject(candidate.match);
95
+ const bindingChannel = normalizeText(match.channel, null);
96
+ const bindingAccountId = normalizeText(match.accountId, null);
97
+ const bindingAgentId = normalizeText(candidate.agentId, null);
98
+ if (bindingChannel !== 'claworld') return false;
99
+ if (normalizedAgentId && bindingAgentId !== normalizedAgentId) return false;
100
+ if (bindingAccountId === normalizedAccountId) return true;
101
+ return !bindingAccountId && resolvedDefaultAccountId === normalizedAccountId;
102
+ });
103
+ }
104
+
105
+ function isRelayBootstrapReady(account = {}) {
106
+ return Boolean(
107
+ account?.configured
108
+ && normalizeText(account?.appToken, null),
116
109
  );
117
- return currentDisplayName
118
- ? { name: currentDisplayName }
119
- : {};
120
110
  }
121
111
 
122
- async function collectManagedIdentityInput({ cfg = {}, prompter, accountId = null } = {}) {
123
- const currentInput = currentManagedIdentityInput({ cfg, accountId });
124
- if (currentInput.appToken) {
125
- return currentInput;
112
+ export function inspectManagedClaworldInstall({
113
+ cfg = {},
114
+ accountId = DEFAULT_CLAWORLD_ACCOUNT_ID,
115
+ input = {},
116
+ overrides = {},
117
+ } = {}) {
118
+ const configuredAccountIds = listClaworldAccountIds(cfg);
119
+ const hasAnyConfig = configuredAccountIds.length > 0 || cfg?.channels?.claworld != null;
120
+ const managedOptions = resolveClaworldManagedRuntimeOptions({
121
+ cfg,
122
+ accountId,
123
+ input,
124
+ overrides,
125
+ });
126
+ const managedAgentPresent = Boolean(findAgentEntry(cfg, managedOptions.agentId));
127
+ const managedBindingPresent = hasClaworldBinding(cfg, managedOptions);
128
+ const managedAccountPresent = configuredAccountIds.includes(managedOptions.accountId);
129
+ const accountStatus = managedAccountPresent
130
+ ? inspectClaworldChannelAccount(cfg, managedOptions.accountId)
131
+ : inspectClaworldChannelAccount({}, managedOptions.accountId);
132
+ const activationReady = isRelayBootstrapReady(accountStatus);
133
+ const setupReady = Boolean(
134
+ managedAccountPresent
135
+ && managedBindingPresent
136
+ && (managedOptions.manageAgentEntry !== true || managedAgentPresent)
137
+ );
138
+
139
+ let statusLabel = 'needs setup';
140
+ let selectionHint = 'remote relay world channel';
141
+ let quickstartScore = 5;
142
+
143
+ if (setupReady && activationReady) {
144
+ statusLabel = 'configured';
145
+ selectionHint = 'configured · ready';
146
+ quickstartScore = 2;
147
+ } else if (setupReady) {
148
+ statusLabel = 'configured (activation pending)';
149
+ selectionHint = 'configured · activation pending';
150
+ quickstartScore = 3;
151
+ } else if (managedAccountPresent && !managedBindingPresent) {
152
+ statusLabel = 'configured (binding pending)';
153
+ selectionHint = 'configured · binding pending';
154
+ quickstartScore = 4;
155
+ } else if (hasAnyConfig) {
156
+ statusLabel = 'configured (refresh recommended)';
157
+ selectionHint = 'configured · refresh recommended';
158
+ quickstartScore = 4;
126
159
  }
127
160
 
128
- const name = await prompter.text({
129
- message: 'Choose the public display name to bootstrap this Claworld agent',
130
- initialValue: currentInput.name || '',
131
- placeholder: 'Claworld Agent',
132
- validate: (value) => {
133
- const message = validateClaworldSetupInput({ input: { name: value } });
134
- return message || undefined;
135
- },
136
- });
161
+ return {
162
+ hasAnyConfig,
163
+ configuredAccountIds,
164
+ defaultAccountId: defaultClaworldAccountId(cfg) || null,
165
+ managedOptions,
166
+ managedAccountPresent,
167
+ managedAgentPresent,
168
+ managedBindingPresent,
169
+ accountStatus,
170
+ activationReady,
171
+ setupReady,
172
+ statusLabel,
173
+ selectionHint,
174
+ quickstartScore,
175
+ };
176
+ }
137
177
 
178
+ export function buildClaworldOnboardingStatus({
179
+ cfg = {},
180
+ accountId = DEFAULT_CLAWORLD_ACCOUNT_ID,
181
+ } = {}) {
182
+ const inspection = inspectManagedClaworldInstall({ cfg, accountId });
138
183
  return {
139
- name,
184
+ configured: inspection.setupReady,
185
+ statusLines: [`Claworld: ${inspection.statusLabel}`],
186
+ selectionHint: inspection.selectionHint,
187
+ quickstartScore: inspection.quickstartScore,
188
+ activationReady: inspection.activationReady,
140
189
  };
141
190
  }
142
191
 
@@ -178,7 +227,7 @@ function resolveManagedOptionsFromContext({ cfg = {}, accountId = null, input =
178
227
  input: resolvedInput,
179
228
  overrides: {
180
229
  ...overrides,
181
- ...(normalizeText(normalizedInput.name, null) ? { displayName: normalizedInput.name } : {}),
230
+ ...(normalizeText(normalizedInput.name, null) ? { name: normalizedInput.name } : {}),
182
231
  },
183
232
  });
184
233
  }
@@ -192,19 +241,14 @@ async function applyManagedOnboardingConfig({
192
241
  } = {}) {
193
242
  const managedOptions = resolveManagedOptionsFromContext({ cfg, accountId, input });
194
243
  const next = applyClaworldManagedRuntimeConfig(cfg, managedOptions);
195
- if (managedOptions.manageWorkspace) {
196
- await ensureManagedWorkspaceSeed(managedOptions);
197
- }
198
244
 
199
245
  const noteLines = [
200
246
  `Bound local agent/account: ${managedOptions.agentId}`,
201
247
  `Remote backend: ${managedOptions.serverUrl}`,
202
248
  managedOptions.appToken
203
249
  ? 'Activation state: ready via configured appToken'
204
- : 'Activation state: pending until claworld_update_public_identity runs',
205
- managedOptions.manageWorkspace
206
- ? 'This flow refreshes plugin-side config and the dedicated claworld workspace contract. It does not start a backend service.'
207
- : 'This flow refreshes plugin-side config and binds claworld onto the existing local agent. It does not start a backend service.',
250
+ : 'Activation state: pending until claworld_profile(action=update_identity) runs',
251
+ 'This flow refreshes plugin-side config and binds claworld onto the selected local agent. It does not run installer commands or start a backend service.',
208
252
  ];
209
253
  await prompter.note(
210
254
  noteLines.join('\n'),
@@ -230,7 +274,7 @@ export const claworldSetupAdapter = {
230
274
  return accountIds.length > 0 ? defaultClaworldAccountId(cfg) : DEFAULT_CLAWORLD_ACCOUNT_ID;
231
275
  },
232
276
  applyAccountName: ({ cfg, accountId, name }) => applyManagedAccountName({ cfg, accountId, name }),
233
- validateInput: ({ cfg, accountId, input }) => validateClaworldSetupInput({ cfg, accountId, input }),
277
+ validateInput: ({ input }) => validateClaworldSetupInput({ input }),
234
278
  applyAccountConfig: ({ cfg, accountId, input }) => {
235
279
  const managedOptions = resolveManagedOptionsFromContext({ cfg, accountId, input });
236
280
  return applyClaworldManagedRuntimeConfig(cfg, managedOptions).config;
@@ -249,39 +293,20 @@ export const claworldOnboardingAdapter = {
249
293
  }),
250
294
  };
251
295
  },
252
- configure: async ({ cfg, prompter, accountOverrides }) => {
253
- const input = await collectManagedIdentityInput({
254
- cfg,
255
- prompter,
256
- accountId: accountOverrides?.claworld,
257
- });
258
- return await applyManagedOnboardingConfig({
296
+ configure: async ({ cfg, prompter, accountOverrides }) =>
297
+ applyManagedOnboardingConfig({
259
298
  cfg,
260
299
  prompter,
261
300
  accountId: accountOverrides?.claworld,
262
301
  phase: 'setup',
263
- input,
264
- });
265
- },
266
- configureWhenConfigured: async ({ cfg, prompter, accountOverrides }) => {
267
- const input = await collectManagedIdentityInput({
268
- cfg,
269
- prompter,
270
- accountId: accountOverrides?.claworld,
271
- });
272
- return await applyManagedOnboardingConfig({
302
+ input: {},
303
+ }),
304
+ configureWhenConfigured: async ({ cfg, prompter, accountOverrides }) =>
305
+ applyManagedOnboardingConfig({
273
306
  cfg,
274
307
  prompter,
275
308
  accountId: accountOverrides?.claworld,
276
309
  phase: 'refresh',
277
- input,
278
- });
279
- },
280
- };
281
-
282
- export {
283
- buildClaworldOnboardingStatus,
284
- ensureManagedWorkspaceSeed,
285
- inspectManagedClaworldInstall,
286
- validateClaworldSetupInput,
310
+ input: {},
311
+ }),
287
312
  };
@@ -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
+ }