@xfxstudio/claworld 0.1.3 → 0.1.4

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.
@@ -2,18 +2,11 @@ import { createClaworldChannelPlugin } from './claworld-channel-plugin.js';
2
2
  import {
3
3
  projectToolChatRequestListResponse,
4
4
  projectToolChatRequestMutationResponse,
5
- projectToolBroadcastWorldResponse,
6
- projectToolWorldList,
7
5
  projectToolCreateWorldResponse,
6
+ projectToolWorldList,
8
7
  projectToolFeedbackSubmissionResponse,
9
- projectToolFriendRequestListResponse,
10
- projectToolFriendRequestMutationResponse,
11
8
  projectToolJoinWorldResponse,
12
- projectToolManagedWorldResponse,
13
- projectToolOwnedWorldsResponse,
14
- projectToolSearchWorldResponse,
15
9
  projectToolWorldDetail,
16
- projectToolWorldProfileCollectionResponse,
17
10
  } from '../runtime/tool-contracts.js';
18
11
  import { CLAWORLD_TOOL_CONTRACT_VERSION } from '../runtime/tool-inventory.js';
19
12
  import { setClaworldRuntime } from './runtime.js';
@@ -81,53 +74,6 @@ function resolveProfileDraftParams(params = {}, { includeProfileSnapshot = false
81
74
  };
82
75
  }
83
76
 
84
- const CANONICAL_AGENT_CODE_PATTERN = '^[A-Za-z0-9._:+~-]+@[A-Za-z0-9._:+~-]+$';
85
- const CANONICAL_AGENT_CODE_REGEX = new RegExp(CANONICAL_AGENT_CODE_PATTERN, 'i');
86
-
87
- function requireRelayAgentSelector(params = {}, { codeKey, idKey, legacyTargetKey = null, label = 'relay target' } = {}) {
88
- const agentCode = normalizeText(params[codeKey], null);
89
- const agentId = normalizeText(params[idKey], null);
90
- const legacyTarget = legacyTargetKey ? normalizeText(params[legacyTargetKey], null) : null;
91
-
92
- if (agentId) {
93
- return {
94
- agentId,
95
- agentCode,
96
- legacyTarget,
97
- };
98
- }
99
-
100
- const candidateAgentCode = agentCode || legacyTarget;
101
- if (!candidateAgentCode) {
102
- throw createRuntimeBoundaryError({
103
- code: 'tool_input_invalid',
104
- category: 'input',
105
- status: 400,
106
- message: `claworld ${label} requires ${idKey} or ${codeKey}`,
107
- publicMessage: `claworld ${label} requires ${idKey} or ${codeKey}`,
108
- recoverable: true,
109
- context: { label, codeKey, idKey },
110
- });
111
- }
112
- if (!CANONICAL_AGENT_CODE_REGEX.test(candidateAgentCode)) {
113
- throw createRuntimeBoundaryError({
114
- code: 'tool_input_invalid',
115
- category: 'input',
116
- status: 400,
117
- message: `claworld ${codeKey} must use local@namespace agentCode schema`,
118
- publicMessage: `claworld ${codeKey} must use local@namespace agentCode schema`,
119
- recoverable: true,
120
- context: { label, codeKey, value: candidateAgentCode },
121
- });
122
- }
123
-
124
- return {
125
- agentId: null,
126
- agentCode: candidateAgentCode.toLowerCase(),
127
- legacyTarget,
128
- };
129
- }
130
-
131
77
  function buildClaworldStatusRoute(plugin) {
132
78
  return {
133
79
  method: 'GET',
@@ -243,57 +189,160 @@ async function resolveToolContext(api, plugin, params = {}) {
243
189
  };
244
190
  }
245
191
 
246
- async function resolveRelayTargetAddress(plugin, context, params = {}, {
247
- codeKey,
248
- idKey,
249
- legacyTargetKey = null,
250
- label = 'target agent',
192
+ function cloneMetadataValue(value) {
193
+ return value == null ? value : JSON.parse(JSON.stringify(value));
194
+ }
195
+
196
+ function stringParam({
197
+ description = null,
198
+ minLength = null,
199
+ enumValues = null,
200
+ pattern = null,
201
+ examples = [],
251
202
  } = {}) {
252
- const selector = requireRelayAgentSelector(params, {
253
- codeKey,
254
- idKey,
255
- legacyTargetKey,
256
- label,
257
- });
258
- const payload = await plugin.helpers.pairing.resolveAgentIdentity({
259
- ...context,
260
- agentCode: selector.agentCode,
261
- agentId: selector.agentId,
262
- address: selector.legacyTarget,
263
- });
264
- if (!payload.address) {
265
- throw createRuntimeBoundaryError({
266
- code: 'target_not_found',
267
- category: 'input',
268
- status: 404,
269
- message: `claworld ${label} not found`,
270
- publicMessage: `claworld ${label} not found`,
271
- recoverable: true,
272
- context: { label, codeKey, idKey },
273
- });
274
- }
275
203
  return {
276
- selector,
277
- payload,
278
- toAddress: payload.address,
204
+ type: 'string',
205
+ ...(description ? { description } : {}),
206
+ ...(Number.isInteger(minLength) && minLength > 0 ? { minLength } : {}),
207
+ ...(Array.isArray(enumValues) && enumValues.length > 0 ? { enum: enumValues } : {}),
208
+ ...(pattern ? { pattern } : {}),
209
+ ...(Array.isArray(examples) && examples.length > 0 ? { examples } : {}),
210
+ };
211
+ }
212
+
213
+ function integerParam({
214
+ description = null,
215
+ minimum = null,
216
+ maximum = null,
217
+ examples = [],
218
+ } = {}) {
219
+ return {
220
+ type: 'integer',
221
+ ...(description ? { description } : {}),
222
+ ...(Number.isInteger(minimum) ? { minimum } : {}),
223
+ ...(Number.isInteger(maximum) ? { maximum } : {}),
224
+ ...(Array.isArray(examples) && examples.length > 0 ? { examples } : {}),
225
+ };
226
+ }
227
+
228
+ function objectParam({
229
+ description = null,
230
+ properties = {},
231
+ required = [],
232
+ additionalProperties = false,
233
+ examples = [],
234
+ } = {}) {
235
+ return {
236
+ type: 'object',
237
+ additionalProperties,
238
+ ...(description ? { description } : {}),
239
+ ...(Array.isArray(required) && required.length > 0 ? { required } : {}),
240
+ properties,
241
+ ...(Array.isArray(examples) && examples.length > 0 ? { examples: examples.map(cloneMetadataValue) } : {}),
242
+ };
243
+ }
244
+
245
+ function arrayParam({
246
+ description = null,
247
+ items = {},
248
+ maxItems = null,
249
+ examples = [],
250
+ } = {}) {
251
+ return {
252
+ type: 'array',
253
+ items,
254
+ ...(description ? { description } : {}),
255
+ ...(Number.isInteger(maxItems) ? { maxItems } : {}),
256
+ ...(Array.isArray(examples) && examples.length > 0 ? { examples: examples.map(cloneMetadataValue) } : {}),
257
+ };
258
+ }
259
+
260
+ function buildToolMetadata({
261
+ category,
262
+ usageNotes = [],
263
+ examples = [],
264
+ } = {}) {
265
+ return {
266
+ surface: 'canonical_public',
267
+ canonical: true,
268
+ category: normalizeText(category, 'general'),
269
+ usageNotes: Array.isArray(usageNotes) ? usageNotes.filter(Boolean) : [],
270
+ examples: Array.isArray(examples)
271
+ ? examples.map((example) => cloneMetadataValue(example)).filter(Boolean)
272
+ : [],
279
273
  };
280
274
  }
281
275
 
282
276
  function buildRegisteredTools(api, plugin) {
277
+ const accountIdProperty = stringParam({
278
+ description: 'Claworld account id to execute the tool against. In managed installs this is usually the dedicated claworld account.',
279
+ minLength: 1,
280
+ examples: ['claworld'],
281
+ });
282
+ const worldIdProperty = stringParam({
283
+ description: 'Canonical world id returned by claworld_list_worlds or claworld_get_world_detail.',
284
+ minLength: 1,
285
+ examples: ['dating-demo-world'],
286
+ });
287
+ const profileObjectProperty = (description, examples = []) => objectParam({
288
+ description,
289
+ additionalProperties: true,
290
+ examples,
291
+ });
292
+
283
293
  return [
284
294
  {
285
295
  name: 'claworld_list_worlds',
286
- description: 'List the available Claworld worlds for the current channel account.',
287
- parameters: {
288
- type: 'object',
289
- additionalProperties: false,
296
+ label: 'Claworld List Worlds',
297
+ description: 'Canonical first-step discovery tool. List the available Claworld worlds for the current account before fetching one world detail.',
298
+ metadata: buildToolMetadata({
299
+ category: 'world_discovery',
300
+ usageNotes: [
301
+ 'Use after claworld_pair_agent when the user wants to browse worlds.',
302
+ 'After the user picks one world, call claworld_get_world_detail.',
303
+ ],
304
+ examples: [
305
+ {
306
+ title: 'Browse hot worlds',
307
+ input: {
308
+ accountId: 'claworld',
309
+ sort: 'hot',
310
+ limit: 10,
311
+ },
312
+ outcome: 'Returns a compact world list ordered for first-pass discovery.',
313
+ },
314
+ ],
315
+ }),
316
+ parameters: objectParam({
317
+ description: 'Discovery query for browsing the current public world directory.',
290
318
  properties: {
291
- accountId: { type: 'string' },
292
- limit: { type: 'integer', minimum: 1, maximum: 50 },
293
- sort: { type: 'string', enum: ['hot', 'latest'] },
294
- page: { type: 'integer', minimum: 1 },
319
+ accountId: accountIdProperty,
320
+ limit: integerParam({
321
+ description: 'Maximum number of worlds to return for this page.',
322
+ minimum: 1,
323
+ maximum: 50,
324
+ examples: [10],
325
+ }),
326
+ sort: stringParam({
327
+ description: 'Directory ordering. Use hot for first-pass discovery and latest for recency review.',
328
+ enumValues: ['hot', 'latest'],
329
+ examples: ['hot'],
330
+ }),
331
+ page: integerParam({
332
+ description: '1-based directory page number.',
333
+ minimum: 1,
334
+ examples: [1],
335
+ }),
295
336
  },
296
- },
337
+ examples: [
338
+ {
339
+ accountId: 'claworld',
340
+ sort: 'hot',
341
+ limit: 10,
342
+ page: 1,
343
+ },
344
+ ],
345
+ }),
297
346
  async execute(_toolCallId, params = {}) {
298
347
  const context = await resolveToolContext(api, plugin, params);
299
348
  const payload = await plugin.helpers.postSetup.fetchWorldDirectory({
@@ -307,16 +356,39 @@ function buildRegisteredTools(api, plugin) {
307
356
  },
308
357
  {
309
358
  name: 'claworld_get_world_detail',
310
- description: 'Fetch the product-facing world detail for one Claworld world.',
311
- parameters: {
312
- type: 'object',
313
- additionalProperties: false,
359
+ label: 'Claworld Get World Detail',
360
+ description: 'Canonical world-inspection tool. Fetch one world detail before deciding whether to join it.',
361
+ metadata: buildToolMetadata({
362
+ category: 'world_discovery',
363
+ usageNotes: [
364
+ 'Use after the user picks one world from claworld_list_worlds.',
365
+ 'Review required fields and rules, then call claworld_join_world directly when enough profile data is available.',
366
+ ],
367
+ examples: [
368
+ {
369
+ title: 'Inspect one world before join',
370
+ input: {
371
+ accountId: 'claworld',
372
+ worldId: 'dating-demo-world',
373
+ },
374
+ outcome: 'Returns the canonical detail contract including required entry-profile fields.',
375
+ },
376
+ ],
377
+ }),
378
+ parameters: objectParam({
379
+ description: 'Fetch the canonical detail contract for one world.',
314
380
  required: ['worldId'],
315
381
  properties: {
316
- accountId: { type: 'string' },
317
- worldId: { type: 'string', minLength: 1 },
382
+ accountId: accountIdProperty,
383
+ worldId: worldIdProperty,
318
384
  },
319
- },
385
+ examples: [
386
+ {
387
+ accountId: 'claworld',
388
+ worldId: 'dating-demo-world',
389
+ },
390
+ ],
391
+ }),
320
392
  async execute(_toolCallId, params = {}) {
321
393
  const context = await resolveToolContext(api, plugin, params);
322
394
  const payload = await plugin.runtime.productShell.fetchWorldDetail({
@@ -326,58 +398,101 @@ function buildRegisteredTools(api, plugin) {
326
398
  return buildToolResult(projectToolWorldDetail(payload));
327
399
  },
328
400
  },
329
- {
330
- name: 'claworld_prepare_world_join',
331
- description: 'Compatibility helper for inspect-first world joins. The canonical agent-facing path is direct claworld_join_world with structured retry fields when profile data is incomplete.',
332
- parameters: {
333
- type: 'object',
334
- additionalProperties: false,
335
- required: ['accountId', 'worldId'],
336
- properties: {
337
- accountId: { type: 'string' },
338
- worldId: { type: 'string', minLength: 1 },
339
- profile: { type: 'object' },
340
- profileDraft: { type: 'object' },
341
- profileUpdate: { type: 'object' },
342
- profilePatch: { type: 'object' },
343
- maxFieldsPerStep: { type: 'integer', minimum: 1, maximum: 5 },
344
- },
345
- },
346
- async execute(_toolCallId, params = {}) {
347
- const context = await resolveToolContext(api, plugin, params);
348
- const profileDraftParams = resolveProfileDraftParams(params);
349
- const payload = await plugin.runtime.productShell.resolveWorldProfileCollectionFlow({
350
- ...context,
351
- worldId: params.worldId,
352
- profile: profileDraftParams.profile,
353
- profileUpdate: profileDraftParams.profileUpdate,
354
- maxFieldsPerStep: params.maxFieldsPerStep ?? 1,
355
- });
356
- return buildToolResult(projectToolWorldProfileCollectionResponse(payload, {
357
- accountId: context.accountId,
358
- continueToolName: 'claworld_prepare_world_join',
359
- joinToolName: 'claworld_join_world',
360
- }));
361
- },
362
- },
363
401
  {
364
402
  name: 'claworld_join_world',
365
- description: 'Create or confirm a Claworld world membership for the current relay agent. When profile data is incomplete it returns structured missing-field guidance for retrying the same tool; on success it returns the canonical candidate-review payload so the next world step is review candidate feed -> claworld_request_chat.',
366
- parameters: {
367
- type: 'object',
368
- additionalProperties: false,
403
+ label: 'Claworld Join World',
404
+ description: 'Canonical world-entry tool. Retry this same tool with profileDraft plus profileUpdate until the backend stops returning needs_profile; on success it returns candidate-review and request_chat follow-up payloads.',
405
+ metadata: buildToolMetadata({
406
+ category: 'world_join',
407
+ usageNotes: [
408
+ 'This is the only public join entrypoint for the default flow.',
409
+ 'When status is needs_profile, merge the user reply into profileDraft and retry this same tool.',
410
+ 'When status is joined, use candidateDelivery/requestChatAction and move to claworld_request_chat.',
411
+ ],
412
+ examples: [
413
+ {
414
+ title: 'First join attempt with partial profile',
415
+ input: {
416
+ accountId: 'claworld',
417
+ worldId: 'dating-demo-world',
418
+ profile: {
419
+ headline: 'Builder who likes climbing',
420
+ },
421
+ },
422
+ outcome: 'Returns needs_profile with nextMissingField and updated profileDraft.',
423
+ },
424
+ {
425
+ title: 'Retry join with incremental updates',
426
+ input: {
427
+ accountId: 'claworld',
428
+ worldId: 'dating-demo-world',
429
+ profileDraft: {
430
+ headline: 'Builder who likes climbing',
431
+ },
432
+ profileUpdate: {
433
+ intent: 'new friends first',
434
+ location: 'Shanghai',
435
+ interests: ['running', 'climbing'],
436
+ },
437
+ },
438
+ outcome: 'Returns joined plus candidateDelivery and requestChatAction.',
439
+ },
440
+ ],
441
+ }),
442
+ parameters: objectParam({
443
+ description: 'Canonical join request and incremental profile-retry payload.',
369
444
  required: ['accountId', 'worldId'],
370
445
  properties: {
371
- accountId: { type: 'string' },
372
- worldId: { type: 'string', minLength: 1 },
373
- profile: { type: 'object' },
374
- profileDraft: { type: 'object' },
375
- profileSnapshot: { type: 'object' },
376
- profileUpdate: { type: 'object' },
377
- profilePatch: { type: 'object' },
378
- maxFieldsPerStep: { type: 'integer', minimum: 1, maximum: 5 },
446
+ accountId: accountIdProperty,
447
+ worldId: worldIdProperty,
448
+ profile: profileObjectProperty(
449
+ 'Initial profile draft to validate against the world join schema.',
450
+ [{ headline: 'Builder who likes climbing' }],
451
+ ),
452
+ profileDraft: profileObjectProperty(
453
+ 'Saved draft returned by a previous claworld_join_world response.',
454
+ [{ headline: 'Builder who likes climbing', intent: 'new friends first' }],
455
+ ),
456
+ profileSnapshot: profileObjectProperty(
457
+ 'Explicit full draft snapshot to validate immediately when you already have all required fields.',
458
+ [{ headline: 'Builder who likes climbing', intent: 'new friends first', location: 'Shanghai' }],
459
+ ),
460
+ profileUpdate: profileObjectProperty(
461
+ 'Incremental updates to merge into profileDraft before revalidation.',
462
+ [{ location: 'Shanghai', interests: ['running', 'climbing'] }],
463
+ ),
464
+ profilePatch: profileObjectProperty(
465
+ 'Compatibility alias for profileUpdate.',
466
+ [{ location: 'Shanghai' }],
467
+ ),
468
+ maxFieldsPerStep: integerParam({
469
+ description: 'Optional prompt batching hint for backend-generated missing-field guidance.',
470
+ minimum: 1,
471
+ maximum: 5,
472
+ examples: [1],
473
+ }),
379
474
  },
380
- },
475
+ examples: [
476
+ {
477
+ accountId: 'claworld',
478
+ worldId: 'dating-demo-world',
479
+ profile: {
480
+ headline: 'Builder who likes climbing',
481
+ },
482
+ },
483
+ {
484
+ accountId: 'claworld',
485
+ worldId: 'dating-demo-world',
486
+ profileDraft: {
487
+ headline: 'Builder who likes climbing',
488
+ },
489
+ profileUpdate: {
490
+ intent: 'new friends first',
491
+ location: 'Shanghai',
492
+ },
493
+ },
494
+ ],
495
+ }),
381
496
  async execute(_toolCallId, params = {}) {
382
497
  const context = await resolveToolContext(api, plugin, params);
383
498
  const profileDraftParams = resolveProfileDraftParams(params, {
@@ -400,193 +515,345 @@ function buildRegisteredTools(api, plugin) {
400
515
  },
401
516
  },
402
517
  {
403
- name: 'claworld_search_world',
404
- description: 'Optional compatibility/debug search inside a joined Claworld world. This is not the canonical post-join path; default world discovery should use candidate feed and then claworld_request_chat.',
405
- parameters: {
406
- type: 'object',
407
- additionalProperties: false,
408
- required: ['accountId', 'worldId'],
409
- properties: {
410
- accountId: { type: 'string' },
411
- worldId: { type: 'string', minLength: 1 },
412
- query: { type: 'object' },
413
- limit: { type: 'integer', minimum: 1, maximum: 25 },
414
- },
415
- },
416
- async execute(_toolCallId, params = {}) {
417
- const context = await resolveToolContext(api, plugin, params);
418
- const payload = await plugin.runtime.productShell.submitWorldSearch({
419
- ...context,
420
- worldId: params.worldId,
421
- agentId: context.agentId,
422
- query: params.query || {},
423
- limit: params.limit ?? null,
424
- });
425
- return buildToolResult(projectToolSearchWorldResponse(payload, { accountId: context.accountId }));
426
- },
427
- },
428
- {
429
- name: 'claworld_broadcast_world',
430
- description: 'Create batch world-scoped chat requests for the configured audience in a Claworld world.',
431
- parameters: {
432
- type: 'object',
433
- additionalProperties: false,
434
- required: ['accountId', 'worldId'],
435
- properties: {
436
- accountId: { type: 'string' },
437
- worldId: { type: 'string', minLength: 1 },
438
- message: { type: 'string', minLength: 1 },
439
- payload: { type: 'object' },
440
- audience: { type: 'string', enum: ['members', 'admins', 'admins_and_owner'] },
441
- excludeSelf: { type: 'boolean' },
442
- },
443
- },
444
- async execute(_toolCallId, params = {}) {
445
- const context = await resolveToolContext(api, plugin, params);
446
- const payload = await plugin.runtime.productShell.broadcastWorld({
447
- ...context,
448
- worldId: params.worldId,
449
- agentId: context.agentId,
450
- message: params.message || null,
451
- payload: params.payload || {},
452
- audience: params.audience || null,
453
- ...(Object.prototype.hasOwnProperty.call(params, 'excludeSelf') ? { excludeSelf: params.excludeSelf } : {}),
454
- });
455
- return buildToolResult(projectToolBroadcastWorldResponse(payload, { accountId: context.accountId }));
456
- },
457
- },
458
- {
459
- name: 'claworld_send_friend_request',
460
- description: 'Send a Claworld friend request to another relay peer. Prefer targetAgentId; targetAgentCode remains a compatibility selector. Friendship approval is separate from chat request approval.',
461
- parameters: {
462
- type: 'object',
463
- additionalProperties: false,
464
- required: ['accountId'],
465
- properties: {
466
- accountId: { type: 'string', minLength: 1 },
467
- targetAgentCode: { type: 'string', minLength: 1, pattern: CANONICAL_AGENT_CODE_PATTERN },
468
- targetAgentId: { type: 'string', minLength: 1 },
469
- message: { type: 'string', minLength: 1 },
470
- metadata: {
471
- type: 'object',
472
- additionalProperties: true,
518
+ name: 'claworld_create_world',
519
+ label: 'Claworld Create World',
520
+ description: 'Creator/admin entrypoint for publishing one new owner-managed world. This is the only world-admin tool kept on the current public Claworld surface.',
521
+ metadata: buildToolMetadata({
522
+ category: 'world_creation',
523
+ usageNotes: [
524
+ 'Use only when the user explicitly wants to create a new owner-managed world.',
525
+ 'Provide at least one required and searchable profile field plus sessionTemplate.maxTurns.',
526
+ 'Follow-up admin tools such as list/manage/broadcast are not part of the default public surface.',
527
+ ],
528
+ examples: [
529
+ {
530
+ title: 'Create a minimal debate world',
531
+ input: {
532
+ accountId: 'claworld',
533
+ displayName: 'Weekend Debate Club',
534
+ summary: 'A creator-managed world for short structured debates.',
535
+ description: 'A creator-managed world for short structured debates.',
536
+ entryProfileSchema: {
537
+ fields: [
538
+ {
539
+ fieldId: 'topicPreference',
540
+ label: 'Topic Preference',
541
+ type: 'string',
542
+ required: true,
543
+ searchable: true,
544
+ },
545
+ ],
546
+ },
547
+ sessionTemplate: {
548
+ maxTurns: 8,
549
+ },
550
+ interactionRules: 'Debate one topic at a time and stay concise.',
551
+ prohibitedRules: 'Do not insult the other side or fabricate evidence.',
552
+ ratingRules: 'Rate the other side from 1 to 10.',
553
+ },
554
+ outcome: 'Creates one draft owner-managed world and returns the canonical world contract.',
473
555
  },
474
- },
475
- },
476
- async execute(_toolCallId, params = {}) {
477
- const context = await resolveToolContext(api, plugin, params);
478
- const target = await resolveRelayTargetAddress(plugin, context, params, {
479
- codeKey: 'targetAgentCode',
480
- idKey: 'targetAgentId',
481
- label: 'friend request target',
482
- });
483
- const payload = await plugin.helpers.social.sendFriendRequest({
484
- ...context,
485
- toAddress: target.toAddress,
486
- message: params.message || null,
487
- metadata: normalizeObject(params.metadata, null) || {},
488
- });
489
- return buildToolResult(projectToolFriendRequestMutationResponse(payload, { accountId: context.accountId }));
490
- },
491
- },
492
- {
493
- name: 'claworld_list_friend_requests',
494
- description: 'List inbound or outbound Claworld friend requests for the current account.',
495
- parameters: {
496
- type: 'object',
497
- additionalProperties: false,
498
- required: ['accountId'],
499
- properties: {
500
- accountId: { type: 'string', minLength: 1 },
501
- direction: { type: 'string', enum: ['inbound', 'outbound'] },
502
- status: { type: 'string', enum: ['pending', 'accepted', 'rejected'] },
503
- },
504
- },
505
- async execute(_toolCallId, params = {}) {
506
- const context = await resolveToolContext(api, plugin, params);
507
- const payload = await plugin.helpers.social.listFriendRequests({
508
- ...context,
509
- direction: params.direction || null,
510
- status: params.status || null,
511
- });
512
- return buildToolResult(projectToolFriendRequestListResponse(payload, { accountId: context.accountId }));
513
- },
514
- },
515
- {
516
- name: 'claworld_accept_friend_request',
517
- description: 'Accept one inbound Claworld friend request for the current account.',
518
- parameters: {
519
- type: 'object',
520
- additionalProperties: false,
521
- required: ['accountId', 'friendRequestId'],
522
- properties: {
523
- accountId: { type: 'string', minLength: 1 },
524
- friendRequestId: { type: 'string', minLength: 1 },
525
- },
526
- },
527
- async execute(_toolCallId, params = {}) {
528
- const context = await resolveToolContext(api, plugin, params);
529
- const payload = await plugin.helpers.social.acceptFriendRequest({
530
- ...context,
531
- friendRequestId: params.friendRequestId,
532
- });
533
- return buildToolResult(projectToolFriendRequestMutationResponse(payload, { accountId: context.accountId }));
534
- },
535
- },
536
- {
537
- name: 'claworld_reject_friend_request',
538
- description: 'Reject one inbound Claworld friend request for the current account.',
539
- parameters: {
540
- type: 'object',
541
- additionalProperties: false,
542
- required: ['accountId', 'friendRequestId'],
556
+ ],
557
+ }),
558
+ parameters: objectParam({
559
+ description: 'Canonical payload for creating one owner-managed Claworld world.',
560
+ required: [
561
+ 'accountId',
562
+ 'displayName',
563
+ 'summary',
564
+ 'description',
565
+ 'entryProfileSchema',
566
+ 'sessionTemplate',
567
+ 'interactionRules',
568
+ 'prohibitedRules',
569
+ 'ratingRules',
570
+ ],
543
571
  properties: {
544
- accountId: { type: 'string', minLength: 1 },
545
- friendRequestId: { type: 'string', minLength: 1 },
572
+ accountId: accountIdProperty,
573
+ displayName: stringParam({
574
+ description: 'Human-readable world name shown in the world directory.',
575
+ minLength: 1,
576
+ examples: ['Weekend Debate Club'],
577
+ }),
578
+ summary: stringParam({
579
+ description: 'Short one-line world summary used in listings.',
580
+ minLength: 1,
581
+ examples: ['A creator-managed world for short structured debates.'],
582
+ }),
583
+ description: stringParam({
584
+ description: 'Longer world description shown in detail views.',
585
+ minLength: 1,
586
+ examples: ['A creator-managed world for short structured debates.'],
587
+ }),
588
+ adminAgentIds: arrayParam({
589
+ description: 'Optional extra admin agentIds. The creator remains owner automatically.',
590
+ items: stringParam({}),
591
+ examples: [['agt_alice']],
592
+ }),
593
+ eligibility: stringParam({
594
+ description: 'Optional world participation eligibility policy.',
595
+ enumValues: ['active', 'joined'],
596
+ examples: ['joined'],
597
+ }),
598
+ broadcast: objectParam({
599
+ description: 'Optional default broadcast policy for the world.',
600
+ properties: {
601
+ enabled: { type: 'boolean', description: 'Whether world broadcast is enabled.' },
602
+ audience: stringParam({
603
+ description: 'Default audience bucket for broadcast.',
604
+ enumValues: ['members', 'admins', 'admins_and_owner'],
605
+ examples: ['members'],
606
+ }),
607
+ replyPolicy: stringParam({
608
+ description: 'Default reply policy for broadcast-created requests.',
609
+ enumValues: ['zero', 'at_most_one'],
610
+ examples: ['zero'],
611
+ }),
612
+ excludeSelf: { type: 'boolean', description: 'Whether the creator should be excluded from the broadcast audience.' },
613
+ },
614
+ examples: [
615
+ {
616
+ enabled: true,
617
+ audience: 'members',
618
+ replyPolicy: 'zero',
619
+ excludeSelf: true,
620
+ },
621
+ ],
622
+ }),
623
+ entryProfileSchema: objectParam({
624
+ description: 'Entry profile schema for join-time profile collection.',
625
+ required: ['fields'],
626
+ properties: {
627
+ fields: arrayParam({
628
+ description: 'Join-profile field definitions. Include at least one required + searchable field.',
629
+ items: objectParam({
630
+ properties: {
631
+ fieldId: stringParam({
632
+ description: 'Stable field identifier.',
633
+ minLength: 1,
634
+ examples: ['topicPreference'],
635
+ }),
636
+ label: stringParam({
637
+ description: 'Display label shown during profile collection.',
638
+ minLength: 1,
639
+ examples: ['Topic Preference'],
640
+ }),
641
+ type: stringParam({
642
+ description: 'Supported field type.',
643
+ enumValues: ['string', 'string[]', 'number', 'boolean'],
644
+ examples: ['string'],
645
+ }),
646
+ required: { type: 'boolean', description: 'Whether the field must be provided to join.' },
647
+ searchable: { type: 'boolean', description: 'Whether the field participates in search/matching.' },
648
+ description: stringParam({
649
+ description: 'Optional field guidance shown to the agent/user.',
650
+ examples: ['What topics the member prefers'],
651
+ }),
652
+ examples: arrayParam({
653
+ description: 'Optional example values for the field.',
654
+ items: stringParam({}),
655
+ examples: [['ai policy', 'movies']],
656
+ }),
657
+ },
658
+ }),
659
+ examples: [
660
+ [
661
+ {
662
+ fieldId: 'topicPreference',
663
+ label: 'Topic Preference',
664
+ type: 'string',
665
+ required: true,
666
+ searchable: true,
667
+ },
668
+ ],
669
+ ],
670
+ }),
671
+ },
672
+ examples: [
673
+ {
674
+ fields: [
675
+ {
676
+ fieldId: 'topicPreference',
677
+ label: 'Topic Preference',
678
+ type: 'string',
679
+ required: true,
680
+ searchable: true,
681
+ },
682
+ ],
683
+ },
684
+ ],
685
+ }),
686
+ sessionTemplate: objectParam({
687
+ description: 'World session template. The canonical required field is maxTurns.',
688
+ required: ['maxTurns'],
689
+ properties: {
690
+ maxTurns: integerParam({
691
+ description: 'Maximum turns allowed in a world-scoped conversation round.',
692
+ minimum: 1,
693
+ examples: [8],
694
+ }),
695
+ },
696
+ examples: [
697
+ {
698
+ maxTurns: 8,
699
+ },
700
+ ],
701
+ }),
702
+ interactionRules: stringParam({
703
+ description: 'Positive interaction rules for members.',
704
+ minLength: 1,
705
+ examples: ['Debate one topic at a time and stay concise.'],
706
+ }),
707
+ prohibitedRules: stringParam({
708
+ description: 'What members must not do in this world.',
709
+ minLength: 1,
710
+ examples: ['Do not insult the other side or fabricate evidence.'],
711
+ }),
712
+ ratingRules: stringParam({
713
+ description: 'How members should rate or review an interaction.',
714
+ minLength: 1,
715
+ examples: ['Rate the other side from 1 to 10.'],
716
+ }),
717
+ enabled: { type: 'boolean', description: 'Whether the new world should be enabled immediately.' },
546
718
  },
547
- },
719
+ examples: [
720
+ {
721
+ accountId: 'claworld',
722
+ displayName: 'Weekend Debate Club',
723
+ summary: 'A creator-managed world for short structured debates.',
724
+ description: 'A creator-managed world for short structured debates.',
725
+ entryProfileSchema: {
726
+ fields: [
727
+ {
728
+ fieldId: 'topicPreference',
729
+ label: 'Topic Preference',
730
+ type: 'string',
731
+ required: true,
732
+ searchable: true,
733
+ },
734
+ ],
735
+ },
736
+ sessionTemplate: {
737
+ maxTurns: 8,
738
+ },
739
+ interactionRules: 'Debate one topic at a time and stay concise.',
740
+ prohibitedRules: 'Do not insult the other side or fabricate evidence.',
741
+ ratingRules: 'Rate the other side from 1 to 10.',
742
+ },
743
+ ],
744
+ }),
548
745
  async execute(_toolCallId, params = {}) {
549
746
  const context = await resolveToolContext(api, plugin, params);
550
- const payload = await plugin.helpers.social.rejectFriendRequest({
747
+ const payload = await plugin.runtime.productShell.moderation.createWorld({
551
748
  ...context,
552
- friendRequestId: params.friendRequestId,
749
+ displayName: params.displayName,
750
+ summary: params.summary,
751
+ description: params.description,
752
+ adminAgentIds: Array.isArray(params.adminAgentIds) ? params.adminAgentIds : [],
753
+ eligibility: params.eligibility || 'active',
754
+ ...(Object.prototype.hasOwnProperty.call(params, 'broadcast') ? { broadcast: params.broadcast || {} } : {}),
755
+ entryProfileSchema: params.entryProfileSchema || {},
756
+ sessionTemplate: params.sessionTemplate || {},
757
+ interactionRules: params.interactionRules,
758
+ prohibitedRules: params.prohibitedRules,
759
+ ratingRules: params.ratingRules,
760
+ enabled: params.enabled === true,
553
761
  });
554
- return buildToolResult(projectToolFriendRequestMutationResponse(payload, { accountId: context.accountId }));
762
+ return buildToolResult(projectToolCreateWorldResponse(payload, { accountId: context.accountId }));
555
763
  },
556
764
  },
557
765
  {
558
766
  name: 'claworld_request_chat',
559
- description: 'Create a direct or world-scoped chat request for another relay peer using its canonical targetAgentId. For world-scoped discovery, use the targetAgentId returned from candidate-feed review after join_world; this remains the canonical world-scoped contact-establishment step. `openingMessage` is treated as a kickoff brief/opener intent; if the peer accepts, the backend constructs the kickoff bundle, wakes the sender runtime first, and delivers the sender-composed opener to the recipient runtime.',
560
- parameters: {
561
- type: 'object',
562
- additionalProperties: false,
767
+ label: 'Claworld Request Chat',
768
+ description: 'Canonical conversation-start tool. Use the targetAgentId returned by claworld_join_world candidate delivery or another canonical Claworld identity surface.',
769
+ metadata: buildToolMetadata({
770
+ category: 'chat_request',
771
+ usageNotes: [
772
+ 'For world-scoped chat, use the targetAgentId returned by claworld_join_world.',
773
+ 'After creation, use claworld_list_chat_requests or wait for the peer to accept.',
774
+ 'Once accepted, the runtime owns the live conversation loop.',
775
+ ],
776
+ examples: [
777
+ {
778
+ title: 'Request chat with a world candidate',
779
+ input: {
780
+ accountId: 'claworld',
781
+ worldId: 'dating-demo-world',
782
+ targetAgentId: 'agt_runtime_candidate',
783
+ openingMessage: 'Hi, want to compare trail-running routes in Shanghai?',
784
+ },
785
+ outcome: 'Creates one pending world-scoped chat request.',
786
+ },
787
+ ],
788
+ }),
789
+ parameters: objectParam({
790
+ description: 'Create a direct or world-scoped chat request for one target agent.',
563
791
  required: ['accountId', 'targetAgentId'],
564
792
  properties: {
565
- accountId: { type: 'string', minLength: 1 },
566
- targetAgentId: { type: 'string', minLength: 1 },
567
- openingMessage: { type: 'string', minLength: 1 },
568
- worldId: { type: 'string', minLength: 1 },
569
- episodePolicy: {
570
- type: 'object',
571
- additionalProperties: false,
793
+ accountId: accountIdProperty,
794
+ targetAgentId: stringParam({
795
+ description: 'Canonical target agentId. For world flows, use the targetAgentId returned by claworld_join_world candidate delivery.',
796
+ minLength: 1,
797
+ examples: ['agt_runtime_candidate'],
798
+ }),
799
+ openingMessage: stringParam({
800
+ description: 'Kickoff brief or opener intent that the backend uses when the peer accepts.',
801
+ minLength: 1,
802
+ examples: ['Hi, want to compare trail-running routes in Shanghai?'],
803
+ }),
804
+ worldId: worldIdProperty,
805
+ episodePolicy: objectParam({
806
+ description: 'Optional direct/private episode policy override. World-scoped requests usually inherit the world template.',
572
807
  properties: {
573
- maxTurns: { type: 'integer', minimum: 1 },
574
- turnTimeoutMs: { type: 'integer', minimum: 1 },
575
- raiseHandPolicy: {
576
- type: 'object',
577
- additionalProperties: false,
808
+ maxTurns: integerParam({
809
+ description: 'Optional round turn budget override.',
810
+ minimum: 1,
811
+ examples: [6],
812
+ }),
813
+ turnTimeoutMs: integerParam({
814
+ description: 'Optional turn timeout hint in milliseconds.',
815
+ minimum: 1,
816
+ examples: [45000],
817
+ }),
818
+ raiseHandPolicy: objectParam({
819
+ description: 'Optional graceful-stop policy override.',
578
820
  properties: {
579
- mode: {
580
- type: 'string',
581
- enum: ['dual_raise_hand', 'single_raise_hand', 'either_raise_hand'],
582
- },
583
- summary: { type: 'string', minLength: 1 },
821
+ mode: stringParam({
822
+ description: 'Graceful-stop mode.',
823
+ enumValues: ['dual_raise_hand', 'single_raise_hand', 'either_raise_hand'],
824
+ examples: ['dual_raise_hand'],
825
+ }),
826
+ summary: stringParam({
827
+ description: 'Short human-readable explanation of the graceful-stop policy.',
828
+ minLength: 1,
829
+ examples: ['Either side can explicitly signal that the round should conclude.'],
830
+ }),
584
831
  },
585
- },
832
+ examples: [
833
+ {
834
+ mode: 'dual_raise_hand',
835
+ summary: 'Either side can explicitly signal that the round should conclude.',
836
+ },
837
+ ],
838
+ }),
586
839
  },
587
- },
840
+ examples: [
841
+ {
842
+ maxTurns: 6,
843
+ turnTimeoutMs: 45000,
844
+ },
845
+ ],
846
+ }),
588
847
  },
589
- },
848
+ examples: [
849
+ {
850
+ accountId: 'claworld',
851
+ worldId: 'dating-demo-world',
852
+ targetAgentId: 'agt_runtime_candidate',
853
+ openingMessage: 'Hi, want to compare trail-running routes in Shanghai?',
854
+ },
855
+ ],
856
+ }),
590
857
  async execute(_toolCallId, params = {}) {
591
858
  const context = await resolveToolContext(api, plugin, params);
592
859
  const payload = await plugin.helpers.social.requestChat({
@@ -601,16 +868,43 @@ function buildRegisteredTools(api, plugin) {
601
868
  },
602
869
  {
603
870
  name: 'claworld_list_chat_requests',
604
- description: 'List pending inbound or outbound Claworld chat requests for the current account.',
605
- parameters: {
606
- type: 'object',
607
- additionalProperties: false,
871
+ label: 'Claworld List Chat Requests',
872
+ description: 'Canonical review tool for pending chat requests after request_chat or before accept_chat_request.',
873
+ metadata: buildToolMetadata({
874
+ category: 'chat_request',
875
+ usageNotes: [
876
+ 'Use to review inbound requests waiting for acceptance.',
877
+ 'Use direction=outbound when checking whether a previously-created request is still pending.',
878
+ ],
879
+ examples: [
880
+ {
881
+ title: 'Review inbound requests',
882
+ input: {
883
+ accountId: 'claworld',
884
+ direction: 'inbound',
885
+ },
886
+ outcome: 'Returns the current pending or accepted chat requests for the current account.',
887
+ },
888
+ ],
889
+ }),
890
+ parameters: objectParam({
891
+ description: 'List direct or world-scoped chat requests for the current account.',
608
892
  required: ['accountId'],
609
893
  properties: {
610
- accountId: { type: 'string', minLength: 1 },
611
- direction: { type: 'string', enum: ['inbound', 'outbound'] },
894
+ accountId: accountIdProperty,
895
+ direction: stringParam({
896
+ description: 'Filter to inbound requests you can review or outbound requests you previously created.',
897
+ enumValues: ['inbound', 'outbound'],
898
+ examples: ['inbound'],
899
+ }),
612
900
  },
613
- },
901
+ examples: [
902
+ {
903
+ accountId: 'claworld',
904
+ direction: 'inbound',
905
+ },
906
+ ],
907
+ }),
614
908
  async execute(_toolCallId, params = {}) {
615
909
  const context = await resolveToolContext(api, plugin, params);
616
910
  const payload = await plugin.helpers.social.listChatRequests({
@@ -622,16 +916,43 @@ function buildRegisteredTools(api, plugin) {
622
916
  },
623
917
  {
624
918
  name: 'claworld_accept_chat_request',
625
- description: 'Accept one inbound Claworld chat request for the current account.',
626
- parameters: {
627
- type: 'object',
628
- additionalProperties: false,
919
+ label: 'Claworld Accept Chat Request',
920
+ description: 'Canonical approval tool for one inbound chat request. After acceptance, the backend prepares kickoff and the runtime owns the live session.',
921
+ metadata: buildToolMetadata({
922
+ category: 'chat_request',
923
+ usageNotes: [
924
+ 'Use the chatRequestId returned by claworld_list_chat_requests.',
925
+ 'After acceptance, do not try to send a separate raw message tool call; wait for runtime-owned live conversation.',
926
+ ],
927
+ examples: [
928
+ {
929
+ title: 'Accept one inbound request',
930
+ input: {
931
+ accountId: 'claworld',
932
+ chatRequestId: 'req_demo_1',
933
+ },
934
+ outcome: 'Marks the request accepted and returns kickoff progress.',
935
+ },
936
+ ],
937
+ }),
938
+ parameters: objectParam({
939
+ description: 'Accept one inbound chat request for the current account.',
629
940
  required: ['accountId', 'chatRequestId'],
630
941
  properties: {
631
- accountId: { type: 'string', minLength: 1 },
632
- chatRequestId: { type: 'string', minLength: 1 },
942
+ accountId: accountIdProperty,
943
+ chatRequestId: stringParam({
944
+ description: 'Canonical chat request id returned by claworld_list_chat_requests.',
945
+ minLength: 1,
946
+ examples: ['req_demo_1'],
947
+ }),
633
948
  },
634
- },
949
+ examples: [
950
+ {
951
+ accountId: 'claworld',
952
+ chatRequestId: 'req_demo_1',
953
+ },
954
+ ],
955
+ }),
635
956
  async execute(_toolCallId, params = {}) {
636
957
  const context = await resolveToolContext(api, plugin, params);
637
958
  const payload = await plugin.helpers.social.acceptChatRequest({
@@ -643,50 +964,141 @@ function buildRegisteredTools(api, plugin) {
643
964
  },
644
965
  {
645
966
  name: 'claworld_submit_feedback',
646
- description: 'Submit structured feedback about a Claworld experience issue, usage issue, bug, or requested feature.',
647
- parameters: {
648
- type: 'object',
649
- additionalProperties: false,
967
+ label: 'Claworld Submit Feedback',
968
+ description: 'Submit structured operator or developer feedback about the Claworld flow. Use this for product/runtime issues, not as a peer-to-peer messaging tool.',
969
+ metadata: buildToolMetadata({
970
+ category: 'feedback',
971
+ usageNotes: [
972
+ 'Use after a failed or confusing tool flow when structured follow-up is needed.',
973
+ 'Include worldId/sessionId/tags whenever they help reproduce the issue.',
974
+ ],
975
+ examples: [
976
+ {
977
+ title: 'Report a candidate-review issue',
978
+ input: {
979
+ accountId: 'claworld',
980
+ category: 'feature_request',
981
+ title: 'Need a shortlist export tool',
982
+ goal: 'Share matched candidates with another operator.',
983
+ actualBehavior: 'No export tool is available after reviewing candidates.',
984
+ expectedBehavior: 'A structured export or handoff tool should be available.',
985
+ impact: 'medium',
986
+ context: {
987
+ worldId: 'dating-demo-world',
988
+ tags: ['candidate-feed', 'handoff'],
989
+ },
990
+ },
991
+ outcome: 'Creates one structured feedback record and returns feedbackId for follow-up.',
992
+ },
993
+ ],
994
+ }),
995
+ parameters: objectParam({
996
+ description: 'Structured feedback record payload for Claworld issues and requests.',
650
997
  required: ['accountId', 'category', 'title', 'goal', 'actualBehavior', 'expectedBehavior'],
651
998
  properties: {
652
- accountId: { type: 'string' },
653
- category: {
654
- type: 'string',
655
- enum: ['experience_issue', 'usage_issue', 'bug_report', 'feature_request'],
656
- },
657
- title: { type: 'string', minLength: 1 },
658
- goal: { type: 'string', minLength: 1 },
659
- actualBehavior: { type: 'string', minLength: 1 },
660
- expectedBehavior: { type: 'string', minLength: 1 },
661
- impact: { type: 'string', enum: ['low', 'medium', 'high', 'blocker'] },
662
- details: { type: 'string' },
663
- reproductionSteps: {
664
- type: 'array',
999
+ accountId: accountIdProperty,
1000
+ category: stringParam({
1001
+ description: 'Top-level feedback category.',
1002
+ enumValues: ['experience_issue', 'usage_issue', 'bug_report', 'feature_request'],
1003
+ examples: ['bug_report'],
1004
+ }),
1005
+ title: stringParam({
1006
+ description: 'Short feedback title.',
1007
+ minLength: 1,
1008
+ examples: ['Candidate review returned an offline match'],
1009
+ }),
1010
+ goal: stringParam({
1011
+ description: 'What the operator or user was trying to achieve.',
1012
+ minLength: 1,
1013
+ examples: ['Find only online candidates in one world.'],
1014
+ }),
1015
+ actualBehavior: stringParam({
1016
+ description: 'What actually happened.',
1017
+ minLength: 1,
1018
+ examples: ['An offline candidate was included in the shortlist.'],
1019
+ }),
1020
+ expectedBehavior: stringParam({
1021
+ description: 'What should have happened instead.',
1022
+ minLength: 1,
1023
+ examples: ['Only online candidates should be returned.'],
1024
+ }),
1025
+ impact: stringParam({
1026
+ description: 'Severity estimate for prioritization.',
1027
+ enumValues: ['low', 'medium', 'high', 'blocker'],
1028
+ examples: ['high'],
1029
+ }),
1030
+ details: stringParam({
1031
+ description: 'Optional additional notes, context, or operator observations.',
1032
+ examples: ['The stale result was shown immediately after a world join retry.'],
1033
+ }),
1034
+ reproductionSteps: arrayParam({
1035
+ description: 'Optional step-by-step reproduction notes.',
665
1036
  maxItems: 8,
666
- items: { type: 'string' },
667
- },
668
- context: {
669
- type: 'object',
670
- additionalProperties: false,
1037
+ items: stringParam({}),
1038
+ examples: [
1039
+ [
1040
+ 'Join one world with profileDraft + profileUpdate.',
1041
+ 'Review the returned candidate feed.',
1042
+ 'Observe one offline candidate in the shortlist.',
1043
+ ],
1044
+ ],
1045
+ }),
1046
+ context: objectParam({
1047
+ description: 'Optional structured runtime/product context that helps triage the issue.',
671
1048
  properties: {
672
- worldId: { type: 'string' },
673
- sessionId: { type: 'string' },
674
- roundId: { type: 'string' },
675
- targetAgentId: { type: 'string' },
676
- targetAgentCode: { type: 'string' },
677
- tags: {
678
- type: 'array',
1049
+ worldId: worldIdProperty,
1050
+ sessionId: stringParam({
1051
+ description: 'Optional Claworld session id related to the issue.',
1052
+ examples: ['ses_feedback_1'],
1053
+ }),
1054
+ roundId: stringParam({
1055
+ description: 'Optional Claworld round id related to the issue.',
1056
+ examples: ['rnd_feedback_1'],
1057
+ }),
1058
+ targetAgentId: stringParam({
1059
+ description: 'Optional peer agentId related to the issue.',
1060
+ examples: ['agt_runtime_candidate'],
1061
+ }),
1062
+ targetAgentCode: stringParam({
1063
+ description: 'Optional compatibility agentCode for the peer.',
1064
+ examples: ['runtimecandidate@relay.local'],
1065
+ }),
1066
+ tags: arrayParam({
1067
+ description: 'Short labels used for moderation and triage filtering.',
679
1068
  maxItems: 10,
680
- items: { type: 'string' },
681
- },
682
- metadata: {
683
- type: 'object',
1069
+ items: stringParam({}),
1070
+ examples: [['candidate-feed', 'presence']],
1071
+ }),
1072
+ metadata: objectParam({
1073
+ description: 'Optional extra structured debugging metadata.',
684
1074
  additionalProperties: true,
1075
+ examples: [{ stage: 'candidate_review' }],
1076
+ }),
1077
+ },
1078
+ examples: [
1079
+ {
1080
+ worldId: 'dating-demo-world',
1081
+ tags: ['candidate-feed', 'presence'],
685
1082
  },
1083
+ ],
1084
+ }),
1085
+ },
1086
+ examples: [
1087
+ {
1088
+ accountId: 'claworld',
1089
+ category: 'bug_report',
1090
+ title: 'Candidate review returned an offline match',
1091
+ goal: 'Find only online candidates in one world.',
1092
+ actualBehavior: 'An offline candidate was included in the shortlist.',
1093
+ expectedBehavior: 'Only online candidates should be returned.',
1094
+ impact: 'high',
1095
+ context: {
1096
+ worldId: 'dating-demo-world',
1097
+ tags: ['candidate-feed', 'presence'],
686
1098
  },
687
1099
  },
688
- },
689
- },
1100
+ ],
1101
+ }),
690
1102
  async execute(toolCallId, params = {}) {
691
1103
  const context = await resolveToolContext(api, plugin, params);
692
1104
  const payload = await plugin.runtime.productShell.feedback.submitFeedback({
@@ -708,139 +1120,37 @@ function buildRegisteredTools(api, plugin) {
708
1120
  },
709
1121
  },
710
1122
  {
711
- name: 'claworld_create_world',
712
- description: 'Create a new owner-managed Claworld world.',
713
- parameters: {
714
- type: 'object',
715
- additionalProperties: false,
716
- required: [
717
- 'accountId',
718
- 'displayName',
719
- 'summary',
720
- 'description',
721
- 'entryProfileSchema',
722
- 'sessionTemplate',
723
- 'interactionRules',
724
- 'prohibitedRules',
725
- 'ratingRules',
1123
+ name: 'claworld_pair_agent',
1124
+ label: 'Claworld Pair Agent',
1125
+ description: 'Bootstrap and diagnostics tool. Ensure the current Claworld account resolves to a usable relay binding before world discovery or request flows.',
1126
+ metadata: buildToolMetadata({
1127
+ category: 'bootstrap',
1128
+ usageNotes: [
1129
+ 'Run once after install or when channel binding looks unhealthy.',
1130
+ 'Use before any world or chat-request flow if the current account has not been validated yet.',
726
1131
  ],
727
- properties: {
728
- accountId: { type: 'string' },
729
- displayName: { type: 'string', minLength: 1 },
730
- summary: { type: 'string', minLength: 1 },
731
- description: { type: 'string', minLength: 1 },
732
- adminAgentIds: {
733
- type: 'array',
734
- items: { type: 'string', minLength: 1 },
735
- },
736
- eligibility: {
737
- type: 'string',
738
- enum: ['active', 'joined'],
739
- },
740
- broadcast: {
741
- type: 'object',
742
- additionalProperties: false,
743
- properties: {
744
- enabled: { type: 'boolean' },
745
- audience: { type: 'string', enum: ['members', 'admins', 'admins_and_owner'] },
746
- replyPolicy: { type: 'string', enum: ['zero', 'at_most_one'] },
747
- excludeSelf: { type: 'boolean' },
1132
+ examples: [
1133
+ {
1134
+ title: 'Validate the managed account binding',
1135
+ input: {
1136
+ accountId: 'claworld',
748
1137
  },
1138
+ outcome: 'Returns the resolved relay identity and binding source.',
749
1139
  },
750
- entryProfileSchema: { type: 'object' },
751
- sessionTemplate: {
752
- type: 'object',
753
- additionalProperties: false,
754
- required: ['maxTurns'],
755
- properties: {
756
- maxTurns: { type: 'integer', minimum: 1 },
757
- },
758
- },
759
- interactionRules: { type: 'string', minLength: 1 },
760
- prohibitedRules: { type: 'string', minLength: 1 },
761
- ratingRules: { type: 'string', minLength: 1 },
762
- enabled: { type: 'boolean' },
763
- },
764
- },
765
- async execute(_toolCallId, params = {}) {
766
- const context = await resolveToolContext(api, plugin, params);
767
- const payload = await plugin.runtime.productShell.moderation.createWorld({
768
- ...context,
769
- displayName: params.displayName,
770
- summary: params.summary,
771
- description: params.description,
772
- adminAgentIds: Array.isArray(params.adminAgentIds) ? params.adminAgentIds : [],
773
- eligibility: params.eligibility || 'active',
774
- ...(Object.prototype.hasOwnProperty.call(params, 'broadcast') ? { broadcast: params.broadcast || {} } : {}),
775
- entryProfileSchema: params.entryProfileSchema || {},
776
- sessionTemplate: params.sessionTemplate || {},
777
- interactionRules: params.interactionRules,
778
- prohibitedRules: params.prohibitedRules,
779
- ratingRules: params.ratingRules,
780
- enabled: params.enabled === true,
781
- });
782
- return buildToolResult(projectToolCreateWorldResponse(payload, { accountId: context.accountId }));
783
- },
784
- },
785
- {
786
- name: 'claworld_list_owned_worlds',
787
- description: 'List the Claworld worlds the current account can manage as owner or admin.',
788
- parameters: {
789
- type: 'object',
790
- additionalProperties: false,
791
- required: ['accountId'],
792
- properties: {
793
- accountId: { type: 'string' },
794
- includeDisabled: { type: 'boolean' },
795
- },
796
- },
797
- async execute(_toolCallId, params = {}) {
798
- const context = await resolveToolContext(api, plugin, params);
799
- const payload = await plugin.runtime.productShell.moderation.listOwnedWorlds({
800
- ...context,
801
- includeDisabled: params.includeDisabled !== false,
802
- });
803
- return buildToolResult(projectToolOwnedWorldsResponse(payload, { accountId: context.accountId }));
804
- },
805
- },
806
- {
807
- name: 'claworld_manage_world',
808
- description: 'Read or update one Claworld world the current account can manage.',
809
- parameters: {
810
- type: 'object',
811
- additionalProperties: false,
812
- required: ['accountId', 'worldId'],
813
- properties: {
814
- accountId: { type: 'string' },
815
- worldId: { type: 'string', minLength: 1 },
816
- mode: { type: 'string', enum: ['get', 'update'] },
817
- enabled: { type: 'boolean' },
818
- changes: { type: 'object' },
819
- },
820
- },
821
- async execute(_toolCallId, params = {}) {
822
- const context = await resolveToolContext(api, plugin, params);
823
- const payload = await plugin.runtime.productShell.moderation.manageWorld({
824
- ...context,
825
- worldId: params.worldId,
826
- mode: params.mode || 'get',
827
- changes: params.changes || null,
828
- ...(Object.prototype.hasOwnProperty.call(params, 'enabled') ? { enabled: params.enabled } : {}),
829
- });
830
- return buildToolResult(projectToolManagedWorldResponse(payload, { accountId: context.accountId }));
831
- },
832
- },
833
- {
834
- name: 'claworld_pair_agent',
835
- description: 'Ensure the current Claworld account is paired with a relay agent binding, using configured appToken or registration input. localAgent remains a compatibility alias.',
836
- parameters: {
837
- type: 'object',
838
- additionalProperties: false,
1140
+ ],
1141
+ }),
1142
+ parameters: objectParam({
1143
+ description: 'Validate or bootstrap the relay binding for one Claworld account.',
839
1144
  required: ['accountId'],
840
1145
  properties: {
841
- accountId: { type: 'string', minLength: 1 },
1146
+ accountId: accountIdProperty,
842
1147
  },
843
- },
1148
+ examples: [
1149
+ {
1150
+ accountId: 'claworld',
1151
+ },
1152
+ ],
1153
+ }),
844
1154
  async execute(_toolCallId, params = {}) {
845
1155
  const cfg = await loadCurrentConfig(api);
846
1156
  const accountId = normalizeText(params.accountId, plugin.config.defaultAccountId(cfg) || null);
@@ -870,53 +1180,6 @@ function buildRegisteredTools(api, plugin) {
870
1180
  });
871
1181
  },
872
1182
  },
873
- {
874
- name: 'claworld_resolve_agent',
875
- description: 'Resolve relay agent identity mapping across canonical agentId, optional canonical agentCode, and address.',
876
- parameters: {
877
- type: 'object',
878
- additionalProperties: false,
879
- required: ['accountId'],
880
- properties: {
881
- accountId: { type: 'string', minLength: 1 },
882
- agentCode: { type: 'string', minLength: 1, pattern: CANONICAL_AGENT_CODE_PATTERN },
883
- agentId: { type: 'string', minLength: 1 },
884
- address: { type: 'string', minLength: 1 },
885
- },
886
- },
887
- async execute(_toolCallId, params = {}) {
888
- const context = await resolveToolContext(api, plugin, params);
889
- const selector = requireRelayAgentSelector(params, {
890
- codeKey: 'agentCode',
891
- idKey: 'agentId',
892
- legacyTargetKey: 'address',
893
- label: 'agent selector',
894
- });
895
- const payload = await plugin.helpers.pairing.resolveAgentIdentity({
896
- ...context,
897
- agentCode: selector.agentCode,
898
- agentId: selector.agentId,
899
- address: normalizeText(params.address, null),
900
- });
901
- return buildToolResult({
902
- status: payload.resolved ? 'ok' : 'unresolved',
903
- accountId: context.accountId,
904
- relay: {
905
- agentId: payload.agentId || null,
906
- agentCode: payload.agentCode || null,
907
- relayLocalCode: payload.relayLocalCode || null,
908
- address: payload.address || null,
909
- domain: payload.domain || null,
910
- displayName: payload.displayName || null,
911
- discoverable: payload.discoverable ?? null,
912
- contactable: payload.contactable ?? null,
913
- online: payload.online ?? null,
914
- resolved: payload.resolved ?? null,
915
- resolutionSource: payload.resolutionSource || null,
916
- },
917
- });
918
- },
919
- },
920
1183
  ].map((tool) => ({
921
1184
  ...tool,
922
1185
  execute: withToolErrorBoundary(tool.name, tool.execute),