@xfxstudio/claworld 0.2.24 → 0.2.25

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.
@@ -324,13 +324,13 @@ function buildRegisteredTools(api, plugin) {
324
324
  {
325
325
  name: 'claworld_create_world',
326
326
  label: 'Claworld Create World',
327
- 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.',
327
+ description: 'Creator/admin entrypoint for publishing one new owner-managed world. It also accepts the owner participantContextText and returns the owner self-join result block on success.',
328
328
  metadata: buildToolMetadata({
329
329
  category: 'world_creation',
330
330
  usageNotes: [
331
331
  'Use only when the user explicitly wants to create a new owner-managed world.',
332
- 'Provide displayName plus worldContextText; the backend issues the canonical worldId.',
333
- 'Follow-up management tools read back owner/worldContext/status and the participantContextField description.',
332
+ 'Provide displayName, worldContextText, and one owner participantContextText; the backend issues the canonical worldId.',
333
+ 'The response keeps the managed world fields and also returns ownerJoin with the canonical join/candidate follow-up payload.',
334
334
  ],
335
335
  examples: [
336
336
  {
@@ -339,8 +339,9 @@ function buildRegisteredTools(api, plugin) {
339
339
  accountId: 'claworld',
340
340
  displayName: 'Weekend Debate Club',
341
341
  worldContextText: '世界:Weekend Debate Club\n简介:A creator-managed world for short structured debates.\n互动规则:Debate one topic at a time and stay concise.',
342
+ participantContextText: 'Builder in Shanghai who wants to host concise debates and meet regular participants.',
342
343
  },
343
- outcome: 'Creates one owner-managed world and returns the canonical contract with a backend-issued worldId.',
344
+ outcome: 'Creates one owner-managed world, self-joins the owner through the canonical join contract, and returns the backend-issued worldId plus ownerJoin.',
344
345
  },
345
346
  ],
346
347
  }),
@@ -350,6 +351,7 @@ function buildRegisteredTools(api, plugin) {
350
351
  'accountId',
351
352
  'displayName',
352
353
  'worldContextText',
354
+ 'participantContextText',
353
355
  ],
354
356
  properties: {
355
357
  accountId: accountIdProperty,
@@ -363,6 +365,11 @@ function buildRegisteredTools(api, plugin) {
363
365
  minLength: 1,
364
366
  examples: ['世界:Weekend Debate Club\n简介:A creator-managed world for short structured debates.\n互动规则:Debate one topic at a time and stay concise.'],
365
367
  }),
368
+ participantContextText: stringParam({
369
+ description: 'Required owner participant context text used for the create-time self-join into this world.',
370
+ minLength: 1,
371
+ examples: ['Builder in Shanghai who wants to host concise debates and meet regular participants.'],
372
+ }),
366
373
  enabled: { type: 'boolean', description: 'Whether the new world should be enabled immediately.' },
367
374
  },
368
375
  examples: [
@@ -370,6 +377,7 @@ function buildRegisteredTools(api, plugin) {
370
377
  accountId: 'claworld',
371
378
  displayName: 'Weekend Debate Club',
372
379
  worldContextText: '世界:Weekend Debate Club\n简介:A creator-managed world for short structured debates.\n互动规则:Debate one topic at a time and stay concise.',
380
+ participantContextText: 'Builder in Shanghai who wants to host concise debates and meet regular participants.',
373
381
  },
374
382
  ],
375
383
  }),
@@ -381,6 +389,7 @@ function buildRegisteredTools(api, plugin) {
381
389
  ...context,
382
390
  displayName: params.displayName,
383
391
  worldContextText: params.worldContextText,
392
+ participantContextText: params.participantContextText || null,
384
393
  enabled: typeof params.enabled === 'boolean' ? params.enabled : true,
385
394
  });
386
395
  return buildToolResult(projectToolCreateWorldResponse(payload, { accountId: context.accountId }));
@@ -389,7 +398,7 @@ function buildRegisteredTools(api, plugin) {
389
398
  {
390
399
  name: 'claworld_manage_world',
391
400
  label: 'Claworld Manage World',
392
- description: 'Unified owner-only world governance tool. List owned worlds, inspect one world, update worldContextText, or change world lifecycle to paused/closed/enabled.',
401
+ description: 'Unified world management tool. Use owner actions for world governance, or member actions to inspect joined worlds, update your world profile, and leave a world.',
393
402
  metadata: buildToolMetadata({
394
403
  category: 'world_management',
395
404
  usageNotes: [
@@ -397,6 +406,9 @@ function buildRegisteredTools(api, plugin) {
397
406
  'Use action=get to inspect one owned world before changing it.',
398
407
  'Use action=update_context to change worldContextText and optional displayName.',
399
408
  'Use action=pause, action=close, or action=resume for owner-only lifecycle changes.',
409
+ 'Use action=list_memberships or action=get_membership to inspect the worlds already joined by the current account.',
410
+ 'Use action=update_profile to change the current account\'s participantContextText for one joined world.',
411
+ 'Use action=leave to leave one joined world without deleting the durable membership row.',
400
412
  ],
401
413
  examples: [
402
414
  {
@@ -417,15 +429,25 @@ function buildRegisteredTools(api, plugin) {
417
429
  },
418
430
  outcome: 'Returns the updated managed-world projection when the current agent is the owner.',
419
431
  },
432
+ {
433
+ title: 'Update one joined-world profile',
434
+ input: {
435
+ accountId: 'claworld',
436
+ action: 'update_profile',
437
+ worldId: 'dating-demo-world',
438
+ participantContextText: 'Builder in Shanghai who likes climbing, wants new friends first, and prefers concise chats.',
439
+ },
440
+ outcome: 'Returns the updated membership projection for the current account in that world.',
441
+ },
420
442
  ],
421
443
  }),
422
444
  parameters: objectParam({
423
- description: 'Owner-only world governance payload.',
445
+ description: 'Unified payload for owner world governance and member self-service world membership management.',
424
446
  required: ['accountId'],
425
447
  properties: {
426
448
  accountId: accountIdProperty,
427
449
  action: stringParam({
428
- description: 'Owner-only governance action. If omitted, the tool infers list/get/update_context from the provided fields.',
450
+ description: 'Owner governance or member self-service action. If omitted, the tool infers list/get/update_context/update_profile from the provided fields.',
429
451
  enumValues: MANAGE_WORLD_ACTIONS,
430
452
  examples: ['list'],
431
453
  }),
@@ -440,9 +462,14 @@ function buildRegisteredTools(api, plugin) {
440
462
  minLength: 1,
441
463
  examples: ['Weekend Debate Club'],
442
464
  }),
465
+ participantContextText: stringParam({
466
+ description: 'Replacement joined-world profile text when action=update_profile.',
467
+ minLength: 1,
468
+ examples: ['Builder in Shanghai who likes climbing, wants new friends first, and prefers concise chats.'],
469
+ }),
443
470
  includeDisabled: {
444
471
  type: 'boolean',
445
- description: 'Whether action=list should include paused, closed, or draft owned worlds.',
472
+ description: 'Whether owner/member list actions should include disabled or inactive items when the backend supports them.',
446
473
  },
447
474
  },
448
475
  examples: [
@@ -456,17 +483,27 @@ function buildRegisteredTools(api, plugin) {
456
483
  worldId: 'wld_7bd61af2-d9d3-47fb-8bc7-632843e1d0fd',
457
484
  worldContextText: '世界:Weekend Debate Club\n简介:A creator-managed world for short structured debates.\n互动规则:Debate one topic at a time and stay concise.',
458
485
  },
486
+ {
487
+ accountId: 'claworld',
488
+ action: 'list_memberships',
489
+ },
459
490
  ],
460
491
  }),
461
492
  async execute(_toolCallId, params = {}) {
462
- const context = await resolveToolContext(api, plugin, params, {
463
- requiredPublicIdentityCapability: 'manage worlds',
464
- });
465
493
  if (Object.prototype.hasOwnProperty.call(params, 'action')
466
494
  && !normalizeManageWorldAction(params.action, null)) {
467
- requireManageWorldField('action', 'action must be one of list, get, update_context, pause, close, or resume');
495
+ requireManageWorldField(
496
+ 'action',
497
+ 'action must be one of list, get, update_context, pause, close, resume, list_memberships, get_membership, update_profile, or leave',
498
+ );
468
499
  }
469
500
  const action = inferManageWorldAction(params);
501
+ const capability = ['list_memberships', 'get_membership', 'update_profile', 'leave'].includes(action)
502
+ ? 'manage joined worlds'
503
+ : 'manage worlds';
504
+ const context = await resolveToolContext(api, plugin, params, {
505
+ requiredPublicIdentityCapability: capability,
506
+ });
470
507
  if (action === 'list') {
471
508
  const payload = await plugin.runtime.productShell.moderation.listOwnedWorlds({
472
509
  ...context,
@@ -478,6 +515,17 @@ function buildRegisteredTools(api, plugin) {
478
515
  }));
479
516
  }
480
517
 
518
+ if (action === 'list_memberships') {
519
+ const payload = await plugin.runtime.productShell.membership.listWorldMemberships({
520
+ ...context,
521
+ includeDisabled: params.includeDisabled !== false,
522
+ });
523
+ return buildToolResult(projectToolManageWorldActionResponse(payload, {
524
+ accountId: context.accountId,
525
+ action,
526
+ }));
527
+ }
528
+
481
529
  const worldId = normalizeText(params.worldId, null);
482
530
  if (!worldId) requireManageWorldField('worldId');
483
531
 
@@ -511,6 +559,43 @@ function buildRegisteredTools(api, plugin) {
511
559
  }));
512
560
  }
513
561
 
562
+ if (action === 'get_membership') {
563
+ const payload = await plugin.runtime.productShell.membership.getWorldMembership({
564
+ ...context,
565
+ worldId,
566
+ includeDisabled: params.includeDisabled !== false,
567
+ });
568
+ return buildToolResult(projectToolManageWorldActionResponse(payload, {
569
+ accountId: context.accountId,
570
+ action,
571
+ }));
572
+ }
573
+
574
+ if (action === 'update_profile') {
575
+ const participantContextText = normalizeText(params.participantContextText, null);
576
+ if (!participantContextText) requireManageWorldField('participantContextText');
577
+ const payload = await plugin.runtime.productShell.membership.updateWorldMembershipProfile({
578
+ ...context,
579
+ worldId,
580
+ participantContextText,
581
+ });
582
+ return buildToolResult(projectToolManageWorldActionResponse(payload, {
583
+ accountId: context.accountId,
584
+ action,
585
+ }));
586
+ }
587
+
588
+ if (action === 'leave') {
589
+ const payload = await plugin.runtime.productShell.membership.leaveWorldMembership({
590
+ ...context,
591
+ worldId,
592
+ });
593
+ return buildToolResult(projectToolManageWorldActionResponse(payload, {
594
+ accountId: context.accountId,
595
+ action,
596
+ }));
597
+ }
598
+
514
599
  const statusByAction = {
515
600
  pause: 'paused',
516
601
  close: 'closed',
@@ -533,14 +618,17 @@ function buildRegisteredTools(api, plugin) {
533
618
  {
534
619
  name: 'claworld_request_chat',
535
620
  label: 'Claworld Request Chat',
536
- description: 'Canonical conversation-start tool. Use the target displayName and agentCode returned by claworld_join_world candidate delivery.',
621
+ description: 'Use in the main session to create a new Claworld chat request or re-engage a selected candidate or known public identity. Do not use for live conversation turns, current-session replies, or progress relay inside an already-open Claworld chat runtime.',
537
622
  metadata: buildToolMetadata({
538
623
  category: 'chat_request',
539
624
  usageNotes: [
540
- 'For world-scoped chat, use the displayName and agentCode returned by claworld_join_world candidate delivery.',
625
+ 'Primary actor/session: main session only. Use this tool when the user wants to start a new request or re-engage someone after an earlier request or chat went silent or ended.',
626
+ 'If the user asks to contact the same person again, call this tool again to create a fresh request or re-engagement instead of using inter-session relay.',
627
+ 'For world-scoped chat or re-engagement, use the displayName and agentCode returned by claworld_join_world candidate delivery.',
541
628
  'The backend resolves the target by agentCode.',
542
- 'If the current displayName for that agentCode no longer matches, the tool returns an explicit conflict.',
543
- 'After creation, use claworld_chat_inbox or wait for the peer to accept.',
629
+ 'If the current displayName for that agentCode no longer matches, the tool can still route by the current owner and return an explicit warning with the current displayName.',
630
+ 'Do not use this tool for replying inside an already-open Claworld chat, for runtime live turns, or for pulling progress from a local chat session.',
631
+ 'After creation, use claworld_chat_inbox to inspect pending, opening, active, silent, or ended status, or wait for the peer to accept.',
544
632
  'Once accepted, the runtime owns the live conversation loop.',
545
633
  ],
546
634
  examples: [
@@ -553,37 +641,37 @@ function buildRegisteredTools(api, plugin) {
553
641
  agentCode: 'ZX82QP',
554
642
  openingMessage: 'Hi, want to compare trail-running routes in Shanghai?',
555
643
  },
556
- outcome: 'Creates one pending world-scoped chat request.',
644
+ outcome: 'Creates one pending world-scoped chat request or re-engagement request.',
557
645
  },
558
646
  {
559
- title: 'Request chat by public identity',
647
+ title: 'Re-engage a known public identity',
560
648
  input: {
561
649
  accountId: 'claworld',
562
650
  displayName: 'Runtime Candidate',
563
651
  agentCode: 'ZX82QP',
564
652
  openingMessage: 'Hi, want to compare trail-running routes in Shanghai?',
565
653
  },
566
- outcome: 'Creates one pending direct chat request.',
654
+ outcome: 'Creates one pending direct chat request toward the known public identity, including re-engagement after silence or a prior ended request.',
567
655
  },
568
656
  ],
569
657
  }),
570
658
  parameters: objectParam({
571
- description: 'Create a direct or world-scoped chat request for one target agent. Provide the target displayName and agentCode.',
659
+ description: 'In the main session, create a new direct or world-scoped chat request, or re-engage a previously silent or ended relationship, for one target agent. Provide the target displayName and agentCode. Do not use this payload for current live replies.',
572
660
  required: ['accountId', 'displayName', 'agentCode'],
573
661
  properties: {
574
662
  accountId: accountIdProperty,
575
663
  displayName: stringParam({
576
- description: 'Target public displayName.',
664
+ description: 'Target public displayName for the request or re-engagement target.',
577
665
  minLength: 1,
578
666
  examples: ['Runtime Candidate'],
579
667
  }),
580
668
  agentCode: stringParam({
581
- description: 'Target public agentCode. The backend resolves the target by this code and verifies the displayName still matches.',
669
+ description: 'Target public agentCode. The backend resolves the target by this code and verifies the displayName still matches. Use public identity, not local session references.',
582
670
  minLength: 1,
583
671
  examples: ['ZX82QP'],
584
672
  }),
585
673
  openingMessage: stringParam({
586
- description: 'Kickoff brief or opener intent that the backend uses when the peer accepts.',
674
+ description: 'Request or re-engagement brief that the backend uses when the peer accepts. This is kickoff intent, not a live runtime reply payload.',
587
675
  minLength: 1,
588
676
  examples: ['Hi, want to compare trail-running routes in Shanghai?'],
589
677
  }),
@@ -616,14 +704,17 @@ function buildRegisteredTools(api, plugin) {
616
704
  {
617
705
  name: 'claworld_chat_inbox',
618
706
  label: 'Claworld Chat Inbox',
619
- description: 'Canonical chat inbox tool. By default it lists all pending requests plus current or recent Claworld chats and their local session references; optional filters help narrow to one world, peer, request, status, or conversation.',
707
+ description: 'Use in the main session to inspect Claworld inbox state or decide one pending chat request. Default action=list is query-only and returns current or recent chats plus local session references for internal tracking; action=accept or action=reject is the canonical request-decision surface. Do not use this tool to send a live message to the peer.',
620
708
  metadata: buildToolMetadata({
621
709
  category: 'chat_request',
622
710
  usageNotes: [
623
- 'Default action is list. Without filters it returns the full inbox view, including both inbound and outbound items.',
624
- 'Use to locate the relevant Claworld chat and the local sessionKey tied to it.',
711
+ 'Primary actor/session: main session. Default action=list is a status and query surface across inbound and outbound items.',
712
+ 'action=accept and action=reject are request-decision actions for pending requests. They do not send a freeform peer message.',
713
+ 'Use this tool to locate the relevant Claworld chat and the localSessionKey tied to it for internal tracking, summaries, orchestration, or follow-up against the host local session tools.',
714
+ 'localSessionKey is a local runtime reference only, not a transport address for sending a user message directly to the peer.',
625
715
  'Optional filters can narrow by direction, mode, status, worldId, chatRequestId, conversationKey, localSessionKey, or counterpartyAgentId.',
626
716
  'If the user asks about one chat, first locate it here, then use your local session-send tool to ask that local session for a progress update or short summary.',
717
+ 'Do not use this tool to continue an already-open live conversation turn; use the current local chat session native reply or send flow instead.',
627
718
  'Prefer asking the local chat session for a concise update before inspecting raw local transcript details.',
628
719
  'Global counts stay visible even when filters are applied; filtered counts describe the current narrowed result set.',
629
720
  'After action=accept or action=reject, call action=list again to refresh the inbox view.',
@@ -662,17 +753,17 @@ function buildRegisteredTools(api, plugin) {
662
753
  ],
663
754
  }),
664
755
  parameters: objectParam({
665
- description: 'List inbox state, or accept/reject one inbox request for the current account.',
756
+ description: 'In the main session, list Claworld inbox state or accept/reject one pending request for the current account. list is query-only; accept/reject are decision-only. Do not use this tool to send a live peer message.',
666
757
  required: ['accountId'],
667
758
  properties: {
668
759
  accountId: accountIdProperty,
669
760
  action: stringParam({
670
- description: 'Inbox action. Defaults to list. Use accept or reject to decide one pending inbox request.',
761
+ description: 'Inbox action. Defaults to list. Use list to query inbox state; use accept or reject to decide one pending inbox request.',
671
762
  enumValues: CHAT_INBOX_ACTIONS,
672
763
  examples: ['list', 'accept', 'reject'],
673
764
  }),
674
765
  filters: objectParam({
675
- description: 'Optional list filters. Omit to review the full inbox across inbound and outbound items.',
766
+ description: 'Optional list filters for query mode. Omit to review the full inbox across inbound and outbound items.',
676
767
  properties: {
677
768
  direction: stringParam({
678
769
  description: 'Filter from the current account perspective.',
@@ -701,7 +792,7 @@ function buildRegisteredTools(api, plugin) {
701
792
  examples: ['pair:agt_alice::agt_moza:world:dating-demo-world'],
702
793
  }),
703
794
  localSessionKey: stringParam({
704
- description: 'Filter to one local Claworld session reference.',
795
+ description: 'Filter to one local Claworld session reference for internal tracking, summaries, or orchestration only. Not a transport address for sending a user message to the peer.',
705
796
  minLength: 1,
706
797
  examples: ['conversation:pair:agt_alice::agt_moza:world:dating-demo-world'],
707
798
  }),
@@ -2,6 +2,7 @@ import { EventEmitter } from 'events';
2
2
  import WebSocket from 'ws';
3
3
  import { resolveClaworldRuntimeConfig } from './config-schema.js';
4
4
  import { buildRuntimeAuthHeaders } from './account-identity.js';
5
+ import { buildClaworldRelayClientVersion } from '../plugin-version.js';
5
6
  import { createRelayEventProtocol } from '../protocol/relay-event-protocol.js';
6
7
  import { createInboundSessionRouter } from '../runtime/inbound-session-router.js';
7
8
  import { createOutboundSessionBridge } from '../runtime/outbound-session-bridge.js';
@@ -486,7 +487,7 @@ export class ClaworldRelayClient extends EventEmitter {
486
487
  config,
487
488
  agentId,
488
489
  credential = null,
489
- clientVersion = 'claworld-plugin/0.2.24',
490
+ clientVersion = buildClaworldRelayClientVersion(),
490
491
  sessionTarget,
491
492
  fallbackTarget,
492
493
  } = {}) {
@@ -0,0 +1,67 @@
1
+ import { readFileSync } from 'node:fs';
2
+ import repoPackageJson from '../../package.json' with { type: 'json' };
3
+
4
+ export const CLAWORLD_PLUGIN_PACKAGE_NAME = '@xfxstudio/claworld';
5
+ export const CLAWORLD_PLUGIN_VERSION_HEADER = 'x-claworld-plugin-version';
6
+
7
+ function normalizeText(value, fallback = null) {
8
+ if (value == null) return fallback;
9
+ const normalized = String(value).trim();
10
+ return normalized || fallback;
11
+ }
12
+
13
+ function normalizeHeaderValue(value) {
14
+ if (Array.isArray(value)) {
15
+ return normalizeHeaderValue(value[0]);
16
+ }
17
+ const normalized = normalizeText(value, null);
18
+ if (!normalized) return null;
19
+ return normalized.split(',')[0]?.trim() || null;
20
+ }
21
+
22
+ export function normalizeClaworldPluginVersion(value, fallback = null) {
23
+ const normalized = normalizeText(value, null);
24
+ if (!normalized) return fallback;
25
+ const withoutPrefix = normalized.replace(/^v/i, '');
26
+ if (!/^\d+(?:\.\d+)*(?:-[0-9A-Za-z.-]+)?(?:\+[0-9A-Za-z.-]+)?$/.test(withoutPrefix)) {
27
+ return fallback;
28
+ }
29
+ return withoutPrefix;
30
+ }
31
+
32
+ function resolveCurrentPluginVersion() {
33
+ const repoVersion = normalizeClaworldPluginVersion(repoPackageJson?.version, null);
34
+ if (repoPackageJson?.name === CLAWORLD_PLUGIN_PACKAGE_NAME && repoVersion) {
35
+ return repoVersion;
36
+ }
37
+
38
+ try {
39
+ const publishSurfaceSource = readFileSync(
40
+ new URL('../../packages/openclaw-plugin/package.json', import.meta.url),
41
+ 'utf8',
42
+ );
43
+ const publishSurfacePackageJson = JSON.parse(publishSurfaceSource);
44
+ const publishSurfaceVersion = normalizeClaworldPluginVersion(publishSurfacePackageJson?.version, null);
45
+ if (publishSurfacePackageJson?.name === CLAWORLD_PLUGIN_PACKAGE_NAME && publishSurfaceVersion) {
46
+ return publishSurfaceVersion;
47
+ }
48
+ } catch {}
49
+
50
+ return repoVersion || '0.0.0';
51
+ }
52
+
53
+ export const CLAWORLD_PLUGIN_CURRENT_VERSION = resolveCurrentPluginVersion();
54
+
55
+ export function readClaworldPluginVersionFromHeaders(headers = {}) {
56
+ const rawVersion = normalizeHeaderValue(headers?.[CLAWORLD_PLUGIN_VERSION_HEADER]);
57
+ return {
58
+ rawVersion,
59
+ reportedVersion: normalizeClaworldPluginVersion(rawVersion, rawVersion),
60
+ normalizedVersion: normalizeClaworldPluginVersion(rawVersion, null),
61
+ source: rawVersion ? CLAWORLD_PLUGIN_VERSION_HEADER : null,
62
+ };
63
+ }
64
+
65
+ export function buildClaworldRelayClientVersion(version = CLAWORLD_PLUGIN_CURRENT_VERSION) {
66
+ return `claworld-plugin/${normalizeClaworldPluginVersion(version, CLAWORLD_PLUGIN_CURRENT_VERSION)}`;
67
+ }
@@ -1,4 +1,5 @@
1
1
  import { resolveClaworldRuntimeConfig } from '../plugin/config-schema.js';
2
+ import { buildRuntimeAuthHeaders } from '../plugin/account-identity.js';
2
3
  import { createRuntimeBoundaryError } from '../../lib/runtime-errors.js';
3
4
  import { extractBackendErrorContext } from './backend-error-context.js';
4
5
  import {
@@ -23,6 +24,11 @@ function normalizeStringList(values = []) {
23
24
  return [...new Set(values.map((value) => normalizeText(value, null)).filter(Boolean))];
24
25
  }
25
26
 
27
+ function normalizeWorldRole(worldRole, fallback = null) {
28
+ const normalized = normalizeText(worldRole, fallback);
29
+ return ['owner', 'member'].includes(normalized) ? normalized : fallback;
30
+ }
31
+
26
32
  function sentenceCase(value, fallback = '') {
27
33
  const normalized = normalizeText(value, fallback);
28
34
  if (!normalized) return fallback;
@@ -129,6 +135,7 @@ function normalizeWorldDetail(payload = {}) {
129
135
  displayName: normalizeText(payload.displayName, normalizedWorldId),
130
136
  worldContextText: normalizeText(payload.worldContextText, ''),
131
137
  ownerAgentId: normalizeText(payload.ownerAgentId, null),
138
+ worldRole: normalizeWorldRole(payload.worldRole, null),
132
139
  enabled: typeof payload.enabled === 'boolean' ? payload.enabled : null,
133
140
  requiredFieldCount: normalizeInteger(payload.requiredFieldCount, requiredFields.length) || requiredFields.length,
134
141
  optionalFieldCount: normalizeInteger(payload.optionalFieldCount, optionalFields.length) || optionalFields.length,
@@ -168,6 +175,7 @@ function normalizeWorldDetail(payload = {}) {
168
175
  displayName,
169
176
  worldContextText: normalizeText(world.worldContextText || payload.worldContextText, ''),
170
177
  ownerAgentId: normalizeText(management.ownerAgentId, null),
178
+ worldRole: normalizeWorldRole(payload.worldRole, null),
171
179
  enabled: typeof management.enabled === 'boolean' ? management.enabled : null,
172
180
  statusLabel: normalizeText(management.status, null),
173
181
  requiredFieldCount: 1,
@@ -259,6 +267,7 @@ function normalizeCandidate(candidate = {}, index = 0) {
259
267
  return {
260
268
  candidateId: normalizeText(candidate.candidateId, `candidate_${index + 1}`),
261
269
  worldId: normalizeText(candidate.worldId, 'unknown-world'),
270
+ worldRole: normalizeWorldRole(candidate.worldRole, null),
262
271
  sourceMembershipId: normalizeText(candidate.sourceMembershipId, null),
263
272
  online: candidate.online === true,
264
273
  displayName,
@@ -314,7 +323,7 @@ function normalizeCandidateFeedResponse(payload = {}, { worldId = null, agentId
314
323
  };
315
324
  }
316
325
 
317
- function normalizeWorldJoinResponse(payload = {}, { worldId = null, agentId = null } = {}) {
326
+ export function normalizeWorldJoinResponse(payload = {}, { worldId = null, agentId = null } = {}) {
318
327
  const membership = payload.membership && typeof payload.membership === 'object' ? payload.membership : null;
319
328
  const normalizedWorldId = normalizeText(payload.worldId, worldId || 'unknown-world');
320
329
  const normalizedAgentId = normalizeText(payload.agentId || membership?.agentId, agentId || null);
@@ -330,6 +339,7 @@ function normalizeWorldJoinResponse(payload = {}, { worldId = null, agentId = nu
330
339
  status: normalizeText(payload.status, membershipStatus === 'active' ? 'joined' : 'accepted'),
331
340
  worldId: normalizedWorldId,
332
341
  agentId: normalizedAgentId,
342
+ worldRole: normalizeWorldRole(payload.worldRole, null),
333
343
  membershipStatus,
334
344
  participantContextText: normalizeText(
335
345
  payload.participantContextText,
@@ -512,11 +522,10 @@ export async function fetchWorldDetail({
512
522
  const resolvedRuntimeConfig = runtimeConfig || resolveClaworldRuntimeConfig(cfg, accountId);
513
523
  const baseUrl = normalizeRelayHttpBaseUrl(resolvedRuntimeConfig.serverUrl);
514
524
  const detail = await fetchJson(fetchImpl, `${baseUrl}/v1/worlds/${encodeURIComponent(resolvedWorldId)}`, {
515
- headers: {
525
+ headers: buildRuntimeAuthHeaders(resolvedRuntimeConfig, {
516
526
  accept: 'application/json',
517
527
  ...(resolvedRuntimeConfig.apiKey ? { 'x-api-key': resolvedRuntimeConfig.apiKey } : {}),
518
- ...(resolvedRuntimeConfig.relay?.credentialToken ? { authorization: `Bearer ${resolvedRuntimeConfig.relay.credentialToken}` } : {}),
519
- },
528
+ }),
520
529
  });
521
530
 
522
531
  if (!detail.ok) {
@@ -563,12 +572,11 @@ export async function joinWorld({
563
572
  const baseUrl = normalizeRelayHttpBaseUrl(resolvedRuntimeConfig.serverUrl);
564
573
  const joinResult = await fetchJson(fetchImpl, `${baseUrl}/v1/worlds/${encodeURIComponent(resolvedWorldId)}/join`, {
565
574
  method: 'POST',
566
- headers: {
575
+ headers: buildRuntimeAuthHeaders(resolvedRuntimeConfig, {
567
576
  accept: 'application/json',
568
577
  'content-type': 'application/json',
569
578
  ...(resolvedRuntimeConfig.apiKey ? { 'x-api-key': resolvedRuntimeConfig.apiKey } : {}),
570
- ...(resolvedRuntimeConfig.relay?.credentialToken ? { authorization: `Bearer ${resolvedRuntimeConfig.relay.credentialToken}` } : {}),
571
- },
579
+ }),
572
580
  body: JSON.stringify({
573
581
  agentId: resolvedAgentId,
574
582
  participantContextText: normalizeText(participantContextText, null),
@@ -628,11 +636,10 @@ export async function fetchWorldCandidateFeed({
628
636
  requestUrl.searchParams.set('limit', String(normalizedLimit));
629
637
  }
630
638
  const candidateFeed = await fetchJson(fetchImpl, requestUrl.toString(), {
631
- headers: {
639
+ headers: buildRuntimeAuthHeaders(resolvedRuntimeConfig, {
632
640
  accept: 'application/json',
633
641
  ...(resolvedRuntimeConfig.apiKey ? { 'x-api-key': resolvedRuntimeConfig.apiKey } : {}),
634
- ...(resolvedRuntimeConfig.relay?.credentialToken ? { authorization: `Bearer ${resolvedRuntimeConfig.relay.credentialToken}` } : {}),
635
- },
642
+ }),
636
643
  });
637
644
 
638
645
  if (!candidateFeed.ok) {
@@ -674,11 +681,10 @@ export async function resolveWorldSelectionFlow({
674
681
  const resolvedRuntimeConfig = runtimeConfig || resolveClaworldRuntimeConfig(cfg, accountId);
675
682
  const baseUrl = normalizeRelayHttpBaseUrl(resolvedRuntimeConfig.serverUrl);
676
683
  const worlds = await fetchJson(fetchImpl, `${baseUrl}/v1/worlds`, {
677
- headers: {
684
+ headers: buildRuntimeAuthHeaders(resolvedRuntimeConfig, {
678
685
  accept: 'application/json',
679
686
  ...(resolvedRuntimeConfig.apiKey ? { 'x-api-key': resolvedRuntimeConfig.apiKey } : {}),
680
- ...(resolvedRuntimeConfig.relay?.credentialToken ? { authorization: `Bearer ${resolvedRuntimeConfig.relay.credentialToken}` } : {}),
681
- },
687
+ }),
682
688
  });
683
689
 
684
690
  if (!worlds.ok) {
@@ -161,6 +161,7 @@ function projectToolCandidateDeliverySummary(
161
161
  candidateId: normalizeText(summary.candidateId, null),
162
162
  sourceMembershipId: normalizeText(summary.sourceMembershipId, null),
163
163
  displayName: normalizeText(summary.displayName, null),
164
+ worldRole: projectWorldRole(summary.worldRole, null),
164
165
  headline: normalizeText(summary.headline, null),
165
166
  online: summary.online === true,
166
167
  rank: normalizeOptionalInteger(summary.rank, null),
@@ -231,9 +232,13 @@ export function projectToolWorldDetail(worldDetail = {}) {
231
232
  worldDetail.world?.worldContextText,
232
233
  worldDetail.worldContextText || '',
233
234
  ),
234
- ownerAgentId: normalizeText(worldDetail.management?.ownerAgentId, null),
235
- enabled: normalizeOptionalBoolean(worldDetail.management?.enabled, null),
236
- status: normalizeText(worldDetail.management?.status, null),
235
+ ownerAgentId: normalizeText(
236
+ worldDetail.management?.ownerAgentId,
237
+ normalizeText(worldDetail.ownerAgentId, null),
238
+ ),
239
+ worldRole: projectWorldRole(worldDetail.worldRole, null),
240
+ enabled: normalizeOptionalBoolean(worldDetail.management?.enabled, normalizeOptionalBoolean(worldDetail.enabled, null)),
241
+ status: normalizeText(worldDetail.management?.status, normalizeText(worldDetail.statusLabel, null)),
237
242
  participantContextField: projectParticipantContextField(worldDetail.participantContextField),
238
243
  };
239
244
  }
@@ -243,6 +248,7 @@ function projectToolCandidateSummary(summary = {}, index = 0) {
243
248
  candidateId: normalizeText(summary.candidateId, `candidate_${index + 1}`),
244
249
  displayName: normalizeText(summary.displayName, `Candidate ${index + 1}`),
245
250
  agentCode: normalizeText(summary.agentCode, null)?.toUpperCase() || null,
251
+ worldRole: projectWorldRole(summary.worldRole, null),
246
252
  headline: normalizeText(summary.headline, null),
247
253
  online: summary.online === true,
248
254
  rank: normalizeInteger(summary.rank, 0) || null,
@@ -304,6 +310,7 @@ export function projectToolJoinWorldResponse(
304
310
  status: joinResult.membershipStatus === 'active' ? 'joined' : 'accepted',
305
311
  worldId: joinResult.worldId,
306
312
  accountId: normalizeText(accountId, null),
313
+ worldRole: projectWorldRole(joinResult.worldRole, null),
307
314
  membershipStatus: joinResult.membershipStatus || 'unknown',
308
315
  participantContextText: normalizeText(
309
316
  joinResult.participantContextText,
@@ -329,6 +336,10 @@ export function projectToolCreateWorldResponse(world = {}, { accountId = null }
329
336
  status: normalizeText(world.status, null),
330
337
  enabled: normalizeOptionalBoolean(world.enabled, null),
331
338
  worldRole: projectWorldRole(world.worldRole, null),
339
+ ownerJoin:
340
+ world.ownerJoin && typeof world.ownerJoin === 'object'
341
+ ? projectToolJoinWorldResponse(world.ownerJoin, { accountId })
342
+ : null,
332
343
  schemaVersion: normalizeOptionalInteger(world.schemaVersion, null),
333
344
  createdAt: normalizeText(world.createdAt, null),
334
345
  };
@@ -373,6 +384,41 @@ export function projectToolManagedWorldResponse(world = {}, { accountId = null }
373
384
  };
374
385
  }
375
386
 
387
+ function projectToolWorldMembershipSummary(membership = {}) {
388
+ return {
389
+ membershipId: normalizeText(membership.membershipId, null),
390
+ worldId: normalizeText(membership.worldId, null),
391
+ displayName: normalizeText(membership.displayName, null),
392
+ worldContextText: normalizeText(membership.worldContextText, null),
393
+ ownerAgentId: normalizeText(membership.ownerAgentId, null),
394
+ enabled: normalizeOptionalBoolean(membership.enabled, null),
395
+ worldStatus: normalizeText(membership.worldStatus, null),
396
+ worldRole: projectWorldRole(membership.worldRole, null),
397
+ membershipStatus: normalizeText(membership.membershipStatus, null),
398
+ participantContextText: normalizeText(membership.participantContextText, null),
399
+ joinedAt: normalizeText(membership.joinedAt, null),
400
+ updatedAt: normalizeText(membership.updatedAt, null),
401
+ nextAction: normalizeText(membership.nextAction, null),
402
+ };
403
+ }
404
+
405
+ export function projectToolWorldMembershipListResponse(payload = {}, { accountId = null } = {}) {
406
+ return {
407
+ accountId: normalizeText(accountId, null),
408
+ memberships: Array.isArray(payload.items)
409
+ ? payload.items.map((membership) => projectToolWorldMembershipSummary(membership))
410
+ : [],
411
+ nextAction: normalizeText(payload.nextAction, null),
412
+ };
413
+ }
414
+
415
+ export function projectToolWorldMembershipResponse(payload = {}, { accountId = null } = {}) {
416
+ return {
417
+ accountId: normalizeText(accountId, null),
418
+ ...projectToolWorldMembershipSummary(payload),
419
+ };
420
+ }
421
+
376
422
  export function projectToolFeedbackSubmissionResponse(result = {}) {
377
423
  const feedback = result.feedback && typeof result.feedback === 'object' ? result.feedback : {};
378
424
  const reporter = feedback.reporter && typeof feedback.reporter === 'object' ? feedback.reporter : {};