@xfxstudio/claworld 0.2.7 → 0.2.9

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -8,7 +8,7 @@
8
8
  ],
9
9
  "name": "Claworld Persona Relay",
10
10
  "description": "Claworld relay world channel plugin for OpenClaw.",
11
- "version": "0.2.7",
11
+ "version": "0.2.9",
12
12
  "configSchema": {
13
13
  "type": "object",
14
14
  "additionalProperties": false,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@xfxstudio/claworld",
3
- "version": "0.2.7",
3
+ "version": "0.2.9",
4
4
  "description": "Claworld channel plugin for OpenClaw",
5
5
  "type": "module",
6
6
  "main": "index.js",
@@ -6,8 +6,11 @@ import {
6
6
  DEFAULT_CLAWORLD_AGENT_ID,
7
7
  DEFAULT_CLAWORLD_SERVER_URL,
8
8
  expandUserPath,
9
+ getEffectiveAgentSandboxMode,
10
+ MIN_MANAGED_SESSION_VISIBILITY,
9
11
  normalizeText,
10
12
  resolveClaworldManagedRuntimeOptions,
13
+ sandboxModeNeedsSessionToolsVisibility,
11
14
  } from '../plugin/managed-config.js';
12
15
  import {
13
16
  CLAWORLD_DOCTOR_COMMAND,
@@ -87,6 +90,18 @@ function findChannelAccount(channelStatus, accountId) {
87
90
  return entries.find((entry) => entry?.accountId === accountId) || null;
88
91
  }
89
92
 
93
+ const SESSION_VISIBILITY_RANK = Object.freeze({
94
+ self: 0,
95
+ tree: 1,
96
+ agent: 2,
97
+ all: 3,
98
+ });
99
+
100
+ function resolveConfiguredSessionVisibility(config = {}) {
101
+ const visibility = normalizeText(config?.tools?.sessions?.visibility, null);
102
+ return Object.prototype.hasOwnProperty.call(SESSION_VISIBILITY_RANK, visibility) ? visibility : null;
103
+ }
104
+
90
105
  function normalizeGatewayBaseUrl(gatewayStatus) {
91
106
  const probeUrl = normalizeText(gatewayStatus?.gateway?.probeUrl, null);
92
107
  if (probeUrl) {
@@ -455,6 +470,54 @@ export async function runClaworldDoctor({
455
470
  details: { issues: configuredAccount.issues },
456
471
  }));
457
472
 
473
+ const configuredSessionVisibility = resolveConfiguredSessionVisibility(config);
474
+ const sessionVisibilityHealthy = (
475
+ configuredSessionVisibility != null
476
+ && SESSION_VISIBILITY_RANK[configuredSessionVisibility] >= SESSION_VISIBILITY_RANK[MIN_MANAGED_SESSION_VISIBILITY]
477
+ );
478
+ checks.push(createCheck({
479
+ id: 'session-tools-visibility',
480
+ category: 'Managed config',
481
+ label: 'Session tool visibility',
482
+ status: sessionVisibilityHealthy ? 'pass' : 'warn',
483
+ summary: sessionVisibilityHealthy
484
+ ? `tools.sessions.visibility is \`${configuredSessionVisibility}\`, which satisfies the managed same-agent minimum \`${MIN_MANAGED_SESSION_VISIBILITY}\`.`
485
+ : `tools.sessions.visibility is \`${configuredSessionVisibility || 'missing'}\`, but the managed same-agent follow-up path needs at least \`${MIN_MANAGED_SESSION_VISIBILITY}\`.`,
486
+ action: sessionVisibilityHealthy
487
+ ? null
488
+ : `Rerun \`${CLAWORLD_INSTALLER_COMMAND}\` to raise session visibility for managed same-agent follow-up routing.`,
489
+ details: {
490
+ actualVisibility: configuredSessionVisibility,
491
+ minimumVisibility: MIN_MANAGED_SESSION_VISIBILITY,
492
+ },
493
+ }));
494
+
495
+ const effectiveSandboxMode = getEffectiveAgentSandboxMode(config, resolvedAgentId);
496
+ if (sandboxModeNeedsSessionToolsVisibility(effectiveSandboxMode)) {
497
+ const sandboxSessionToolsVisibility = normalizeText(
498
+ config?.agents?.defaults?.sandbox?.sessionToolsVisibility,
499
+ null,
500
+ );
501
+ const sandboxVisibilityHealthy = sandboxSessionToolsVisibility === 'all';
502
+ checks.push(createCheck({
503
+ id: 'sandbox-session-tools-visibility',
504
+ category: 'Managed config',
505
+ label: 'Sandbox session tool visibility',
506
+ status: sandboxVisibilityHealthy ? 'pass' : 'warn',
507
+ summary: sandboxVisibilityHealthy
508
+ ? `Effective sandbox mode is \`${effectiveSandboxMode}\` and agents.defaults.sandbox.sessionToolsVisibility is correctly set to \`all\`.`
509
+ : `Effective sandbox mode is \`${effectiveSandboxMode}\`, so agents.defaults.sandbox.sessionToolsVisibility should be \`all\` (currently \`${sandboxSessionToolsVisibility || 'missing'}\`).`,
510
+ action: sandboxVisibilityHealthy
511
+ ? null
512
+ : `Rerun \`${CLAWORLD_INSTALLER_COMMAND}\` to widen sandbox session-tool visibility for managed follow-up routing.`,
513
+ details: {
514
+ effectiveSandboxMode,
515
+ sessionToolsVisibility: sandboxSessionToolsVisibility,
516
+ requiredSessionToolsVisibility: 'all',
517
+ },
518
+ }));
519
+ }
520
+
458
521
  checks.push(createCheck({
459
522
  id: 'app-token',
460
523
  category: 'Credentials and runtime',
@@ -720,6 +720,34 @@ async function acceptChatRequest({
720
720
  return result.body || {};
721
721
  }
722
722
 
723
+ async function rejectChatRequest({
724
+ runtimeConfig,
725
+ actorAgentId,
726
+ chatRequestId,
727
+ fetchImpl,
728
+ }) {
729
+ const baseUrl = normalizeRelayHttpBaseUrl(runtimeConfig.serverUrl);
730
+ const result = await fetchJson(fetchImpl, `${baseUrl}/v1/chat-requests/${encodeURIComponent(chatRequestId)}/reject`, {
731
+ method: 'POST',
732
+ headers: {
733
+ 'content-type': 'application/json',
734
+ ...(runtimeConfig.apiKey ? { 'x-api-key': runtimeConfig.apiKey } : {}),
735
+ ...buildRuntimeAuthHeaders(runtimeConfig),
736
+ },
737
+ body: JSON.stringify({ actorAgentId }),
738
+ });
739
+ if (!result.ok) {
740
+ createRelayRouteError({
741
+ result,
742
+ runtimeConfig,
743
+ code: 'chat_request_reject_failed',
744
+ publicMessage: 'failed to reject chat request',
745
+ context: { actorAgentId, chatRequestId },
746
+ });
747
+ }
748
+ return result.body || {};
749
+ }
750
+
723
751
  async function syncChatRequestApprovalPolicy({
724
752
  runtimeConfig,
725
753
  fetchImpl,
@@ -1472,6 +1500,7 @@ function createDeliveryReplyDispatcher({
1472
1500
  });
1473
1501
 
1474
1502
  const markDispatchIdle = async () => {
1503
+ await dispatchApi.dispatcher.waitForIdle?.();
1475
1504
  if (!replied && !suppressed) {
1476
1505
  const continuation = buildRelayContinuationText({
1477
1506
  finalTexts,
@@ -2539,6 +2568,15 @@ export function createClaworldChannelPlugin({
2539
2568
  fetchImpl,
2540
2569
  });
2541
2570
  },
2571
+ rejectChatRequest: async (context = {}) => {
2572
+ const resolvedContext = await resolveBoundRuntimeContext(context);
2573
+ return rejectChatRequest({
2574
+ runtimeConfig: resolvedContext.runtimeConfig,
2575
+ actorAgentId: resolvedContext.agentId || null,
2576
+ chatRequestId: context.chatRequestId || null,
2577
+ fetchImpl,
2578
+ });
2579
+ },
2542
2580
  },
2543
2581
  postSetup: {
2544
2582
  fetchWorldDirectory: async (context = {}) => {
@@ -2676,6 +2714,7 @@ export function createClaworldChannelPlugin({
2676
2714
  mode: context.mode || 'get',
2677
2715
  changes: context.changes || null,
2678
2716
  enabled: Object.prototype.hasOwnProperty.call(context, 'enabled') ? context.enabled : null,
2717
+ status: context.status || null,
2679
2718
  fetchImpl,
2680
2719
  logger,
2681
2720
  });
@@ -2864,6 +2903,7 @@ export function createClaworldChannelPlugin({
2864
2903
  mode: context.mode || 'get',
2865
2904
  changes: context.changes || null,
2866
2905
  enabled: Object.prototype.hasOwnProperty.call(context, 'enabled') ? context.enabled : null,
2906
+ status: context.status || null,
2867
2907
  fetchImpl,
2868
2908
  logger,
2869
2909
  });
@@ -23,6 +23,8 @@ export const DEFAULT_CLAWORLD_APPROVAL_MODE = DEFAULT_CHAT_REQUEST_APPROVAL_POLI
23
23
  export const DEFAULT_CLAWORLD_SESSION_TARGET = 'mainagent';
24
24
  export const DEFAULT_CLAWORLD_FALLBACK_TARGET = 'mainagent';
25
25
  export const CLAWORLD_PLUGIN_TOOL_ALLOW_ENTRY = 'claworld';
26
+ export const MIN_MANAGED_SESSION_VISIBILITY = 'agent';
27
+ export const REQUIRED_SANDBOX_SESSION_TOOLS_VISIBILITY = 'all';
26
28
 
27
29
  export const TOOL_PROFILES = CLAWORLD_TOOL_PROFILES;
28
30
 
@@ -128,6 +130,109 @@ function findManagedAccountEntry(config = {}, accountId) {
128
130
  return {};
129
131
  }
130
132
 
133
+ const SESSION_VISIBILITY_RANK = Object.freeze({
134
+ self: 0,
135
+ tree: 1,
136
+ agent: 2,
137
+ all: 3,
138
+ });
139
+
140
+ const SANDBOX_SESSION_TOOLS_VISIBILITY_RANK = Object.freeze({
141
+ spawned: 0,
142
+ all: 1,
143
+ });
144
+
145
+ function normalizeSessionVisibility(value, fallback = null) {
146
+ const normalized = normalizeText(value, fallback);
147
+ return Object.prototype.hasOwnProperty.call(SESSION_VISIBILITY_RANK, normalized)
148
+ ? normalized
149
+ : fallback;
150
+ }
151
+
152
+ function normalizeSandboxSessionToolsVisibility(value, fallback = null) {
153
+ const normalized = normalizeText(value, fallback);
154
+ return Object.prototype.hasOwnProperty.call(SANDBOX_SESSION_TOOLS_VISIBILITY_RANK, normalized)
155
+ ? normalized
156
+ : fallback;
157
+ }
158
+
159
+ function compareRankedSetting(value, target, rankMap) {
160
+ const nextValue = normalizeText(value, null);
161
+ const nextTarget = normalizeText(target, null);
162
+ const valueRank = Object.prototype.hasOwnProperty.call(rankMap, nextValue) ? rankMap[nextValue] : null;
163
+ const targetRank = Object.prototype.hasOwnProperty.call(rankMap, nextTarget) ? rankMap[nextTarget] : null;
164
+ if (valueRank == null && targetRank == null) return 0;
165
+ if (valueRank == null) return -1;
166
+ if (targetRank == null) return 1;
167
+ return valueRank - targetRank;
168
+ }
169
+
170
+ export function getEffectiveAgentSandboxMode(config = {}, agentId = DEFAULT_CLAWORLD_AGENT_ID) {
171
+ const normalizedAgentId = normalizeText(agentId, DEFAULT_CLAWORLD_AGENT_ID);
172
+ const agentEntry = findAgentEntry(config, normalizedAgentId);
173
+ const agentSandboxMode = normalizeText(agentEntry?.sandbox?.mode, null);
174
+ if (agentSandboxMode) return agentSandboxMode;
175
+ return normalizeText(config?.agents?.defaults?.sandbox?.mode, 'off');
176
+ }
177
+
178
+ export function sandboxModeNeedsSessionToolsVisibility(mode) {
179
+ return mode === 'all' || mode === 'non-main';
180
+ }
181
+
182
+ function ensureManagedSessionRoutingVisibility(config = {}, {
183
+ agentId = DEFAULT_CLAWORLD_AGENT_ID,
184
+ summary = [],
185
+ } = {}) {
186
+ config.tools = ensureObject(config.tools);
187
+ const existingSessionTools = ensureObject(config.tools.sessions);
188
+ const existingVisibility = normalizeSessionVisibility(existingSessionTools.visibility, null);
189
+ if (compareRankedSetting(existingVisibility, MIN_MANAGED_SESSION_VISIBILITY, SESSION_VISIBILITY_RANK) < 0) {
190
+ config.tools.sessions = {
191
+ ...existingSessionTools,
192
+ visibility: MIN_MANAGED_SESSION_VISIBILITY,
193
+ };
194
+ summary.push(
195
+ existingVisibility
196
+ ? `tools.sessions.visibility raised from ${existingVisibility} to ${MIN_MANAGED_SESSION_VISIBILITY}`
197
+ : `tools.sessions.visibility set to ${MIN_MANAGED_SESSION_VISIBILITY}`,
198
+ );
199
+ } else if (Object.keys(existingSessionTools).length > 0) {
200
+ config.tools.sessions = existingSessionTools;
201
+ }
202
+
203
+ const effectiveSandboxMode = getEffectiveAgentSandboxMode(config, agentId);
204
+ if (!sandboxModeNeedsSessionToolsVisibility(effectiveSandboxMode)) {
205
+ return;
206
+ }
207
+
208
+ config.agents = ensureObject(config.agents);
209
+ config.agents.defaults = ensureObject(config.agents.defaults);
210
+ const existingSandbox = ensureObject(config.agents.defaults.sandbox);
211
+ const existingSessionToolsVisibility = normalizeSandboxSessionToolsVisibility(
212
+ existingSandbox.sessionToolsVisibility,
213
+ null,
214
+ );
215
+ if (
216
+ compareRankedSetting(
217
+ existingSessionToolsVisibility,
218
+ REQUIRED_SANDBOX_SESSION_TOOLS_VISIBILITY,
219
+ SANDBOX_SESSION_TOOLS_VISIBILITY_RANK,
220
+ ) < 0
221
+ ) {
222
+ config.agents.defaults.sandbox = {
223
+ ...existingSandbox,
224
+ sessionToolsVisibility: REQUIRED_SANDBOX_SESSION_TOOLS_VISIBILITY,
225
+ };
226
+ summary.push(
227
+ existingSessionToolsVisibility
228
+ ? `agents.defaults.sandbox.sessionToolsVisibility raised from ${existingSessionToolsVisibility} to ${REQUIRED_SANDBOX_SESSION_TOOLS_VISIBILITY}`
229
+ : `agents.defaults.sandbox.sessionToolsVisibility set to ${REQUIRED_SANDBOX_SESSION_TOOLS_VISIBILITY}`,
230
+ );
231
+ } else if (Object.keys(existingSandbox).length > 0) {
232
+ config.agents.defaults.sandbox = existingSandbox;
233
+ }
234
+ }
235
+
131
236
  function inferExistingAgentId(config = {}, accountId = DEFAULT_CLAWORLD_ACCOUNT_ID) {
132
237
  const bindings = Array.isArray(config?.bindings) ? config.bindings : [];
133
238
  const bindingMatch = bindings
@@ -716,6 +821,11 @@ export function applyClaworldManagedRuntimeConfig(inputConfig = {}, options = {}
716
821
  : `reconciled claworld binding for ${options.accountId}`,
717
822
  );
718
823
 
824
+ ensureManagedSessionRoutingVisibility(config, {
825
+ agentId: options.agentId,
826
+ summary,
827
+ });
828
+
719
829
  return {
720
830
  config,
721
831
  summary,
@@ -266,6 +266,56 @@ function buildToolMetadata({
266
266
  };
267
267
  }
268
268
 
269
+ const MANAGE_WORLD_ACTIONS = Object.freeze([
270
+ 'list',
271
+ 'get',
272
+ 'update_context',
273
+ 'pause',
274
+ 'close',
275
+ 'resume',
276
+ ]);
277
+
278
+ function normalizeManageWorldAction(value, fallback = null) {
279
+ const normalized = normalizeText(value, fallback);
280
+ return MANAGE_WORLD_ACTIONS.includes(normalized) ? normalized : fallback;
281
+ }
282
+
283
+ function inferManageWorldAction(params = {}) {
284
+ const explicitAction = normalizeManageWorldAction(params.action, null);
285
+ if (explicitAction) return explicitAction;
286
+ if (!normalizeText(params.worldId, null)) return 'list';
287
+ if (normalizeText(params.worldContextText, null) || normalizeText(params.displayName, null)) {
288
+ return 'update_context';
289
+ }
290
+ return 'get';
291
+ }
292
+
293
+ function requireManageWorldField(fieldId, message = `${fieldId} is required`) {
294
+ throw createRuntimeBoundaryError({
295
+ code: 'tool_input_invalid',
296
+ category: 'input',
297
+ status: 400,
298
+ message,
299
+ publicMessage: message,
300
+ recoverable: true,
301
+ context: { field: fieldId },
302
+ });
303
+ }
304
+
305
+ function projectToolManageWorldActionResponse(payload = {}, { accountId = null, action = null } = {}) {
306
+ const resolvedAction = normalizeManageWorldAction(action, null) || 'get';
307
+ if (resolvedAction === 'list') {
308
+ return {
309
+ action: resolvedAction,
310
+ ...projectToolOwnedWorldsResponse(payload, { accountId }),
311
+ };
312
+ }
313
+ return {
314
+ action: resolvedAction,
315
+ ...projectToolManagedWorldResponse(payload, { accountId }),
316
+ };
317
+ }
318
+
269
319
  function buildRegisteredTools(api, plugin) {
270
320
  const accountIdProperty = stringParam({
271
321
  description: 'Claworld account id to execute the tool against. In managed installs this is usually the dedicated claworld account.',
@@ -509,67 +559,31 @@ function buildRegisteredTools(api, plugin) {
509
559
  },
510
560
  },
511
561
  {
512
- name: 'claworld_list_owned_worlds',
513
- label: 'Claworld List Owned Worlds',
514
- description: 'Owner-focused governance tool. List the worlds owned by the current account before choosing one to manage.',
562
+ name: 'claworld_manage_world',
563
+ label: 'Claworld Manage World',
564
+ description: 'Unified owner-only world governance tool. List owned worlds, inspect one world, update worldContextText, or change world lifecycle to paused/closed/enabled.',
515
565
  metadata: buildToolMetadata({
516
566
  category: 'world_management',
517
567
  usageNotes: [
518
- 'Use when the user wants to inspect or pick from worlds they own.',
519
- 'This tool only returns owned worlds, not general directory worlds.',
520
- 'Pair this with claworld_manage_world for owner-only worldContext updates.',
568
+ 'Use action=list to inspect the worlds owned by the current account.',
569
+ 'Use action=get to inspect one owned world before changing it.',
570
+ 'Use action=update_context to change worldContextText and optional displayName.',
571
+ 'Use action=pause, action=close, or action=resume for owner-only lifecycle changes.',
521
572
  ],
522
573
  examples: [
523
574
  {
524
575
  title: 'List owned worlds',
525
576
  input: {
526
577
  accountId: 'claworld',
578
+ action: 'list',
527
579
  },
528
580
  outcome: 'Returns owner-managed worlds for the current account.',
529
581
  },
530
- ],
531
- }),
532
- parameters: objectParam({
533
- description: 'Minimal payload for listing worlds owned by the current account.',
534
- required: ['accountId'],
535
- properties: {
536
- accountId: accountIdProperty,
537
- includeDisabled: {
538
- type: 'boolean',
539
- description: 'Whether to include disabled or draft owned worlds.',
540
- },
541
- },
542
- examples: [
543
- {
544
- accountId: 'claworld',
545
- },
546
- ],
547
- }),
548
- async execute(_toolCallId, params = {}) {
549
- const context = await resolveToolContext(api, plugin, params);
550
- const payload = await plugin.runtime.productShell.moderation.listOwnedWorlds({
551
- ...context,
552
- includeDisabled: params.includeDisabled !== false,
553
- });
554
- return buildToolResult(projectToolOwnedWorldsResponse(payload, { accountId: context.accountId }));
555
- },
556
- },
557
- {
558
- name: 'claworld_manage_world',
559
- label: 'Claworld Manage World',
560
- description: 'Owner-only world management tool. Update the canonical worldContextText for one owner-managed world, with optional displayName/enabled changes.',
561
- metadata: buildToolMetadata({
562
- category: 'world_management',
563
- usageNotes: [
564
- 'Use only when the current agent owns the target world.',
565
- 'This is the minimal management tool on the current public surface.',
566
- 'Prefer updating worldContextText directly instead of editing legacy world schema fields.',
567
- ],
568
- examples: [
569
582
  {
570
583
  title: 'Update one owned world context',
571
584
  input: {
572
585
  accountId: 'claworld',
586
+ action: 'update_context',
573
587
  worldId: 'ugc-weekend-debate-club',
574
588
  worldContextText: '世界:Weekend Debate Club [ugc-weekend-debate-club]\n简介:A creator-managed world for short structured debates.\n互动规则:Debate one topic at a time and stay concise.',
575
589
  },
@@ -578,29 +592,39 @@ function buildRegisteredTools(api, plugin) {
578
592
  ],
579
593
  }),
580
594
  parameters: objectParam({
581
- description: 'Minimal owner-only world management payload.',
582
- required: ['accountId', 'worldId', 'worldContextText'],
595
+ description: 'Owner-only world governance payload. action=list replaces the old list_owned_worlds tool.',
596
+ required: ['accountId'],
583
597
  properties: {
584
598
  accountId: accountIdProperty,
599
+ action: stringParam({
600
+ description: 'Owner-only governance action. If omitted, the tool infers list/get/update_context from the provided fields.',
601
+ enumValues: MANAGE_WORLD_ACTIONS,
602
+ examples: ['list'],
603
+ }),
585
604
  worldId: worldIdProperty,
586
605
  worldContextText: stringParam({
587
- description: 'Replacement canonical world context text for the owned world.',
606
+ description: 'Replacement canonical world context text for update_context.',
588
607
  minLength: 1,
589
608
  examples: ['世界:Weekend Debate Club [ugc-weekend-debate-club]\n简介:A creator-managed world for short structured debates.\n互动规则:Debate one topic at a time and stay concise.'],
590
609
  }),
591
610
  displayName: stringParam({
592
- description: 'Optional new display name for the owned world.',
611
+ description: 'Optional new display name when action=update_context.',
593
612
  minLength: 1,
594
613
  examples: ['Weekend Debate Club'],
595
614
  }),
596
- enabled: {
615
+ includeDisabled: {
597
616
  type: 'boolean',
598
- description: 'Optional enabled flag for the owned world.',
617
+ description: 'Whether action=list should include paused, closed, or draft owned worlds.',
599
618
  },
600
619
  },
601
620
  examples: [
602
621
  {
603
622
  accountId: 'claworld',
623
+ action: 'list',
624
+ },
625
+ {
626
+ accountId: 'claworld',
627
+ action: 'update_context',
604
628
  worldId: 'ugc-weekend-debate-club',
605
629
  worldContextText: '世界:Weekend Debate Club [ugc-weekend-debate-club]\n简介:A creator-managed world for short structured debates.\n互动规则:Debate one topic at a time and stay concise.',
606
630
  },
@@ -608,18 +632,72 @@ function buildRegisteredTools(api, plugin) {
608
632
  }),
609
633
  async execute(_toolCallId, params = {}) {
610
634
  const context = await resolveToolContext(api, plugin, params);
611
- const changes = {
612
- worldContextText: params.worldContextText,
613
- ...(params.displayName ? { displayName: params.displayName } : {}),
635
+ if (Object.prototype.hasOwnProperty.call(params, 'action')
636
+ && !normalizeManageWorldAction(params.action, null)) {
637
+ requireManageWorldField('action', 'action must be one of list, get, update_context, pause, close, or resume');
638
+ }
639
+ const action = inferManageWorldAction(params);
640
+ if (action === 'list') {
641
+ const payload = await plugin.runtime.productShell.moderation.listOwnedWorlds({
642
+ ...context,
643
+ includeDisabled: params.includeDisabled !== false,
644
+ });
645
+ return buildToolResult(projectToolManageWorldActionResponse(payload, {
646
+ accountId: context.accountId,
647
+ action,
648
+ }));
649
+ }
650
+
651
+ const worldId = normalizeText(params.worldId, null);
652
+ if (!worldId) requireManageWorldField('worldId');
653
+
654
+ if (action === 'get') {
655
+ const payload = await plugin.runtime.productShell.moderation.manageWorld({
656
+ ...context,
657
+ worldId,
658
+ mode: 'get',
659
+ });
660
+ return buildToolResult(projectToolManageWorldActionResponse(payload, {
661
+ accountId: context.accountId,
662
+ action,
663
+ }));
664
+ }
665
+
666
+ if (action === 'update_context') {
667
+ const worldContextText = normalizeText(params.worldContextText, null);
668
+ if (!worldContextText) requireManageWorldField('worldContextText');
669
+ const payload = await plugin.runtime.productShell.moderation.manageWorld({
670
+ ...context,
671
+ worldId,
672
+ mode: 'patch',
673
+ changes: {
674
+ worldContextText,
675
+ ...(normalizeText(params.displayName, null) ? { displayName: normalizeText(params.displayName, null) } : {}),
676
+ },
677
+ });
678
+ return buildToolResult(projectToolManageWorldActionResponse(payload, {
679
+ accountId: context.accountId,
680
+ action,
681
+ }));
682
+ }
683
+
684
+ const statusByAction = {
685
+ pause: 'paused',
686
+ close: 'closed',
687
+ resume: 'enabled',
614
688
  };
689
+ const status = statusByAction[action] || null;
615
690
  const payload = await plugin.runtime.productShell.moderation.manageWorld({
616
691
  ...context,
617
- worldId: params.worldId,
692
+ worldId,
618
693
  mode: 'patch',
619
- changes,
620
- ...(Object.prototype.hasOwnProperty.call(params, 'enabled') ? { enabled: params.enabled === true } : {}),
694
+ status,
695
+ enabled: action === 'resume',
621
696
  });
622
- return buildToolResult(projectToolManagedWorldResponse(payload, { accountId: context.accountId }));
697
+ return buildToolResult(projectToolManageWorldActionResponse(payload, {
698
+ accountId: context.accountId,
699
+ action,
700
+ }));
623
701
  },
624
702
  },
625
703
  {
@@ -782,6 +860,54 @@ function buildRegisteredTools(api, plugin) {
782
860
  return buildToolResult(projectToolChatRequestMutationResponse(payload, { accountId: context.accountId }));
783
861
  },
784
862
  },
863
+ {
864
+ name: 'claworld_reject_chat_request',
865
+ label: 'Claworld Reject Chat Request',
866
+ description: 'Canonical rejection tool for one inbound chat request. Use this when the current account explicitly declines the request instead of accepting it.',
867
+ metadata: buildToolMetadata({
868
+ category: 'chat_request',
869
+ usageNotes: [
870
+ 'Use the chatRequestId returned by claworld_list_chat_requests.',
871
+ 'Use only for inbound requests that the current account wants to reject.',
872
+ ],
873
+ examples: [
874
+ {
875
+ title: 'Reject one inbound request',
876
+ input: {
877
+ accountId: 'claworld',
878
+ chatRequestId: 'req_demo_1',
879
+ },
880
+ outcome: 'Marks the request rejected and returns the closed request projection.',
881
+ },
882
+ ],
883
+ }),
884
+ parameters: objectParam({
885
+ description: 'Reject one inbound chat request for the current account.',
886
+ required: ['accountId', 'chatRequestId'],
887
+ properties: {
888
+ accountId: accountIdProperty,
889
+ chatRequestId: stringParam({
890
+ description: 'Canonical chat request id returned by claworld_list_chat_requests.',
891
+ minLength: 1,
892
+ examples: ['req_demo_1'],
893
+ }),
894
+ },
895
+ examples: [
896
+ {
897
+ accountId: 'claworld',
898
+ chatRequestId: 'req_demo_1',
899
+ },
900
+ ],
901
+ }),
902
+ async execute(_toolCallId, params = {}) {
903
+ const context = await resolveToolContext(api, plugin, params);
904
+ const payload = await plugin.helpers.social.rejectChatRequest({
905
+ ...context,
906
+ chatRequestId: params.chatRequestId,
907
+ });
908
+ return buildToolResult(projectToolChatRequestMutationResponse(payload, { accountId: context.accountId }));
909
+ },
910
+ },
785
911
  {
786
912
  name: 'claworld_submit_feedback',
787
913
  label: 'Claworld Submit Feedback',
@@ -564,7 +564,7 @@ export class ClaworldRelayClient extends EventEmitter {
564
564
  config,
565
565
  agentId,
566
566
  credential = null,
567
- clientVersion = 'claworld-plugin/0.2.7',
567
+ clientVersion = 'claworld-plugin/0.2.8',
568
568
  sessionTarget,
569
569
  fallbackTarget,
570
570
  } = {}) {
@@ -978,6 +978,7 @@ export class ClaworldRelayClient extends EventEmitter {
978
978
  stage: 'reply_fallback',
979
979
  deliveryId: envelope.deliveryId,
980
980
  sessionKey: envelope.sessionKey,
981
+ fallbackFrom: error?.code || error?.message || null,
981
982
  }),
982
983
  });
983
984
  }
@@ -1151,6 +1152,18 @@ export class ClaworldRelayClient extends EventEmitter {
1151
1152
  return result;
1152
1153
  }
1153
1154
 
1155
+ async rejectChatRequest(requestId, { actorAgentId, ...options } = {}) {
1156
+ return await this.requestJson(`/v1/chat-requests/${requestId}/reject`, {
1157
+ method: 'POST',
1158
+ headers: buildRuntimeAuthHeaders(this.runtimeConfig, { 'content-type': 'application/json' }),
1159
+ body: JSON.stringify({ actorAgentId, ...options }),
1160
+ }, {
1161
+ code: 'relay_request_reject_failed',
1162
+ message: 'failed to reject relay chat request',
1163
+ publicMessage: 'failed to reject relay chat request',
1164
+ });
1165
+ }
1166
+
1154
1167
  async deliverMessage({ fromAgentId, toAddress, payload = {}, conversation = {} } = {}) {
1155
1168
  return await this.requestJson('/v1/messages', {
1156
1169
  method: 'POST',
@@ -391,7 +391,7 @@ export function projectToolOwnedWorldsResponse(payload = {}, { accountId = null
391
391
  ? payload.items.map((world) => ({
392
392
  worldId: world.worldId,
393
393
  displayName: world.displayName,
394
- summary: world.summary,
394
+ worldContextText: normalizeText(world.worldContextText, null),
395
395
  enabled: normalizeOptionalBoolean(world.enabled, null),
396
396
  status: normalizeText(world.status, null),
397
397
  worldRole: projectWorldRole(world.worldRole, null),
@@ -4,6 +4,7 @@ export const CLAWORLD_CHAT_REQUEST_TOOL_NAMES = Object.freeze([
4
4
  'claworld_request_chat',
5
5
  'claworld_chat_inbox',
6
6
  'claworld_accept_chat_request',
7
+ 'claworld_reject_chat_request',
7
8
  ]);
8
9
 
9
10
  export const CLAWORLD_BOOTSTRAP_TOOL_NAMES = Object.freeze([
@@ -22,11 +23,11 @@ export const CLAWORLD_WORLD_TOOL_NAMES = Object.freeze([
22
23
 
23
24
  export const CLAWORLD_WORLD_ADMIN_PUBLIC_TOOL_NAMES = Object.freeze([
24
25
  'claworld_create_world',
25
- 'claworld_list_owned_worlds',
26
26
  'claworld_manage_world',
27
27
  ]);
28
28
 
29
29
  export const CLAWORLD_COMPATIBILITY_TOOL_NAMES = Object.freeze([
30
+ 'claworld_list_owned_worlds',
30
31
  'claworld_prepare_world_join',
31
32
  'claworld_search_world',
32
33
  ]);
@@ -287,6 +287,7 @@ export async function manageModeratedWorld({
287
287
  mode = 'get',
288
288
  changes = null,
289
289
  enabled = null,
290
+ status = null,
290
291
  fetchImpl,
291
292
  logger = console,
292
293
  } = {}) {
@@ -340,6 +341,7 @@ export async function manageModeratedWorld({
340
341
  agentId: resolvedAgentId,
341
342
  ...(changes && typeof changes === 'object' ? { changes } : {}),
342
343
  ...(enabled == null ? {} : { enabled }),
344
+ ...(normalizeText(status, null) ? { status: normalizeText(status, null) } : {}),
343
345
  }),
344
346
  });
345
347
 
@@ -12,6 +12,36 @@ function normalizeBoolean(value, fallback = false) {
12
12
  return fallback;
13
13
  }
14
14
 
15
+ const OWNER_WORLD_STATUSES = new Set(['draft', 'enabled', 'paused', 'closed', 'disabled']);
16
+
17
+ function normalizeOwnerWorldStatus(value, fallback = null) {
18
+ const normalized = normalizeText(value, fallback);
19
+ return OWNER_WORLD_STATUSES.has(normalized) ? normalized : fallback;
20
+ }
21
+
22
+ function inferEnabledFromOwnerWorldStatus(status, fallback = null) {
23
+ const normalizedStatus = normalizeOwnerWorldStatus(status, null);
24
+ if (!normalizedStatus) return fallback;
25
+ return normalizedStatus === 'enabled';
26
+ }
27
+
28
+ function projectOwnerWorldMetaStatus(status, enabled) {
29
+ const normalizedStatus = normalizeOwnerWorldStatus(status, enabled === true ? 'enabled' : 'draft');
30
+ switch (normalizedStatus) {
31
+ case 'enabled':
32
+ return 'creator_enabled';
33
+ case 'paused':
34
+ return 'creator_paused';
35
+ case 'closed':
36
+ return 'creator_closed';
37
+ case 'disabled':
38
+ return 'creator_disabled';
39
+ case 'draft':
40
+ default:
41
+ return 'creator_draft';
42
+ }
43
+ }
44
+
15
45
  function summarizeWorldContextText(worldContextText, fallback = null) {
16
46
  const normalized = normalizeText(worldContextText, null);
17
47
  if (!normalized) return fallback;
@@ -154,7 +184,7 @@ function buildWorldRecord({
154
184
  status = null,
155
185
  existingMetrics = null,
156
186
  } = {}) {
157
- const resolvedStatus = status || (enabled ? 'enabled' : 'draft');
187
+ const resolvedStatus = normalizeOwnerWorldStatus(status, enabled ? 'enabled' : 'draft');
158
188
  const participantContextField = buildDefaultEntryProfileField();
159
189
  const resolvedWorldContextText = buildWorldContextText({
160
190
  worldId,
@@ -186,7 +216,7 @@ function buildWorldRecord({
186
216
  matching: buildMatchingStrategy(),
187
217
  conversationTemplate: buildConversationTemplate(null, null),
188
218
  meta: {
189
- status: resolvedStatus === 'enabled' ? 'creator_enabled' : 'creator_draft',
219
+ status: projectOwnerWorldMetaStatus(resolvedStatus, enabled),
190
220
  persistence: 'store',
191
221
  },
192
222
  creatorAgentId,
@@ -314,6 +344,7 @@ export function createWorldAdminService({ worldService, worldAuthorizationServic
314
344
  displayName,
315
345
  worldContextText,
316
346
  enabled = false,
347
+ status = null,
317
348
  } = {}) {
318
349
  const storeBacked = assertStore();
319
350
  const resolvedOwnerAgentId = assertActorAgent(ownerAgentId || creatorAgentId);
@@ -330,6 +361,7 @@ export function createWorldAdminService({ worldService, worldAuthorizationServic
330
361
  summary: summarizeWorldContextText(resolvedWorldContextText, resolvedDisplayName),
331
362
  worldContextText: resolvedWorldContextText,
332
363
  enabled: normalizeBoolean(enabled, false),
364
+ status: normalizeOwnerWorldStatus(status, normalizeBoolean(enabled, false) ? 'enabled' : 'draft'),
333
365
  });
334
366
 
335
367
  const created = await storeBacked.createWorldConfig(worldRecord);
@@ -367,10 +399,14 @@ export function createWorldAdminService({ worldService, worldAuthorizationServic
367
399
  worldRole: authorization.worldRole,
368
400
  });
369
401
  },
370
- async manageWorld({ actorAgentId, creatorAgentId, worldId, changes = null, enabled = null } = {}) {
402
+ async manageWorld({ actorAgentId, creatorAgentId, worldId, changes = null, enabled = null, status = null } = {}) {
371
403
  const storeBacked = assertStore();
372
404
  const resolvedActorAgentId = assertActorAgent(actorAgentId || creatorAgentId);
373
405
  const hasChanges = changes && typeof changes === 'object' && !Array.isArray(changes);
406
+ const normalizedStatus = normalizeOwnerWorldStatus(status, null);
407
+ const resolvedEnabled = normalizedStatus != null
408
+ ? inferEnabledFromOwnerWorldStatus(normalizedStatus, null)
409
+ : (enabled == null ? null : normalizeBoolean(enabled, false));
374
410
 
375
411
  const authorization = requireWorldOwner({
376
412
  worldId,
@@ -378,7 +414,7 @@ export function createWorldAdminService({ worldService, worldAuthorizationServic
378
414
  });
379
415
 
380
416
  const existingWorld = authorization.world;
381
- if (!hasChanges && enabled == null) {
417
+ if (!hasChanges && resolvedEnabled == null && normalizedStatus == null) {
382
418
  return projectManagedWorld(storeBacked, existingWorld, {
383
419
  worldRole: authorization.worldRole,
384
420
  });
@@ -397,20 +433,24 @@ export function createWorldAdminService({ worldService, worldAuthorizationServic
397
433
  displayName: nextDisplayName,
398
434
  summary: summarizeWorldContextText(nextWorldContextText, nextDisplayName),
399
435
  worldContextText: nextWorldContextText,
400
- enabled: enabled == null ? existingWorld.enabled === true : normalizeBoolean(enabled, false),
401
- status: enabled == null
402
- ? existingWorld.status
403
- : (normalizeBoolean(enabled, false) ? 'enabled' : 'disabled'),
436
+ enabled: resolvedEnabled == null ? existingWorld.enabled === true : resolvedEnabled,
437
+ status: normalizedStatus || (
438
+ resolvedEnabled == null
439
+ ? existingWorld.status
440
+ : (resolvedEnabled ? 'enabled' : 'disabled')
441
+ ),
404
442
  existingMetrics: existingWorld.metrics || null,
405
443
  });
406
- } else if (enabled != null) {
444
+ } else if (resolvedEnabled != null || normalizedStatus != null) {
445
+ const nextEnabled = resolvedEnabled == null ? existingWorld.enabled === true : resolvedEnabled;
446
+ const nextStatus = normalizedStatus || (nextEnabled ? 'enabled' : 'disabled');
407
447
  nextRecord = {
408
448
  ...existingWorld,
409
- enabled: normalizeBoolean(enabled, false),
410
- status: normalizeBoolean(enabled, false) ? 'enabled' : 'disabled',
449
+ enabled: nextEnabled,
450
+ status: nextStatus,
411
451
  meta: {
412
452
  ...(existingWorld.meta || {}),
413
- status: normalizeBoolean(enabled, false) ? 'creator_enabled' : 'creator_disabled',
453
+ status: projectOwnerWorldMetaStatus(nextStatus, nextEnabled),
414
454
  },
415
455
  };
416
456
  }
@@ -393,6 +393,7 @@ export function registerWorldRoutes(
393
393
  worldId: req.params.worldId,
394
394
  changes: req.body?.changes || null,
395
395
  enabled: Object.prototype.hasOwnProperty.call(req.body || {}, 'enabled') ? req.body.enabled : null,
396
+ status: req.body?.status || null,
396
397
  });
397
398
  res.json(result);
398
399
  } catch (error) {