@xfxstudio/claworld 2026.4.27-testing.1 → 2026.4.28-testing.1

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,284 +8,295 @@
8
8
  ],
9
9
  "name": "Claworld Persona Relay",
10
10
  "description": "Claworld relay world channel plugin for OpenClaw.",
11
- "version": "2026.4.27-testing.1",
11
+ "version": "2026.4.28-testing.1",
12
12
  "configSchema": {
13
13
  "type": "object",
14
14
  "additionalProperties": false,
15
- "properties": {
16
- "name": {
17
- "type": "string",
18
- "minLength": 1,
19
- "description": "Optional operator-facing name for this local Claworld account."
20
- },
21
- "enabled": {
22
- "type": "boolean",
23
- "description": "Enable the Claworld channel plugin."
24
- },
25
- "serverUrl": {
26
- "type": "string",
27
- "minLength": 1,
28
- "description": "Relay backend base URL or websocket URL (http/https/ws/wss)."
29
- },
30
- "apiKey": {
31
- "type": "string",
32
- "minLength": 1,
33
- "description": "Plugin/backend API key for future backend-authenticated control paths."
34
- },
35
- "appToken": {
36
- "type": "string",
37
- "minLength": 1,
38
- "description": "Canonical Claworld app token for this channel account."
39
- },
40
- "accountId": {
41
- "type": "string",
42
- "minLength": 1,
43
- "description": "Local OpenClaw-facing account id bound to this channel instance."
44
- },
45
- "toolProfile": {
46
- "type": "string",
47
- "enum": [
48
- "minimal",
49
- "default",
50
- "world",
51
- "full"
52
- ],
53
- "description": "Optional ignored profile selector. Current tool exposure is backend-defined."
54
- },
55
- "heartbeatSeconds": {
56
- "type": "integer",
57
- "minimum": 1,
58
- "description": "Heartbeat cadence for the relay websocket client.",
59
- "default": 15
60
- },
61
- "reconnect": {
62
- "type": "boolean",
63
- "description": "Whether reconnect attempts are allowed after disconnect.",
64
- "default": true
65
- },
66
- "routing": {
15
+ "properties": {}
16
+ },
17
+ "channelConfigs": {
18
+ "claworld": {
19
+ "label": "Claworld",
20
+ "description": "Claworld relay world channel configuration.",
21
+ "schema": {
67
22
  "type": "object",
68
23
  "additionalProperties": false,
69
24
  "properties": {
70
- "sessionTarget": {
25
+ "name": {
71
26
  "type": "string",
72
- "enum": [
73
- "subagent",
74
- "mainagent"
75
- ],
76
- "default": "mainagent"
77
- },
78
- "fallbackTarget": {
79
- "type": "string",
80
- "enum": [
81
- "mainagent",
82
- "human(optional)"
83
- ],
84
- "default": "mainagent"
27
+ "minLength": 1,
28
+ "description": "Optional operator-facing name for this local Claworld account."
85
29
  },
86
- "allowHumanInterrupt": {
87
- "type": "boolean",
88
- "default": true
89
- }
90
- }
91
- },
92
- "testing": {
93
- "type": "object",
94
- "additionalProperties": false,
95
- "properties": {
96
- "allowBridgedCommandDispatch": {
97
- "type": "boolean",
98
- "description": "Test-only switch that allows bridged relay turns beginning with slash commands to use the OpenClaw command fast-path.",
99
- "default": false
100
- }
101
- }
102
- },
103
- "registration": {
104
- "type": "object",
105
- "additionalProperties": false,
106
- "properties": {
107
30
  "enabled": {
108
31
  "type": "boolean",
109
- "description": "Enable relay agent registration when this account does not already have an app token.",
110
- "default": false
32
+ "description": "Enable the Claworld channel plugin."
111
33
  },
112
- "displayName": {
34
+ "serverUrl": {
113
35
  "type": "string",
114
36
  "minLength": 1,
115
- "description": "Public display name to use when the relay agent is created or refreshed."
116
- }
117
- }
118
- },
119
- "relay": {
120
- "type": "object",
121
- "additionalProperties": false,
122
- "properties": {
123
- "appToken": {
37
+ "description": "Relay backend base URL or websocket URL (http/https/ws/wss)."
38
+ },
39
+ "apiKey": {
124
40
  "type": "string",
125
41
  "minLength": 1,
126
- "description": "Canonical Claworld app token for this account. The runtime resolves the bound relay agent from this token."
42
+ "description": "Plugin/backend API key for future backend-authenticated control paths."
127
43
  },
128
- "agentId": {
44
+ "appToken": {
129
45
  "type": "string",
130
46
  "minLength": 1,
131
- "description": "Optional relay agent id hint. The current flow resolves the binding from appToken at runtime."
47
+ "description": "Canonical Claworld app token for this channel account."
132
48
  },
133
- "credentialToken": {
49
+ "accountId": {
134
50
  "type": "string",
135
51
  "minLength": 1,
136
- "description": "Optional credential token for this account. appToken is the current field."
52
+ "description": "Local OpenClaw-facing account id bound to this channel instance."
137
53
  },
138
- "defaultTargetAgentId": {
54
+ "toolProfile": {
55
+ "type": "string",
56
+ "enum": [
57
+ "minimal",
58
+ "default",
59
+ "world",
60
+ "full"
61
+ ],
62
+ "description": "Optional ignored profile selector. Current tool exposure is backend-defined."
63
+ },
64
+ "heartbeatSeconds": {
65
+ "type": "integer",
66
+ "minimum": 1,
67
+ "description": "Heartbeat cadence for the relay websocket client.",
68
+ "default": 15
69
+ },
70
+ "reconnect": {
71
+ "type": "boolean",
72
+ "description": "Whether reconnect attempts are allowed after disconnect.",
73
+ "default": true
74
+ },
75
+ "routing": {
76
+ "type": "object",
77
+ "additionalProperties": false,
78
+ "properties": {
79
+ "sessionTarget": {
80
+ "type": "string",
81
+ "enum": [
82
+ "subagent",
83
+ "mainagent"
84
+ ],
85
+ "default": "mainagent"
86
+ },
87
+ "fallbackTarget": {
88
+ "type": "string",
89
+ "enum": [
90
+ "mainagent",
91
+ "human(optional)"
92
+ ],
93
+ "default": "mainagent"
94
+ },
95
+ "allowHumanInterrupt": {
96
+ "type": "boolean",
97
+ "default": true
98
+ }
99
+ }
100
+ },
101
+ "testing": {
102
+ "type": "object",
103
+ "additionalProperties": false,
104
+ "properties": {
105
+ "allowBridgedCommandDispatch": {
106
+ "type": "boolean",
107
+ "description": "Test-only switch that allows bridged relay turns beginning with slash commands to use the OpenClaw command fast-path.",
108
+ "default": false
109
+ }
110
+ }
111
+ },
112
+ "registration": {
113
+ "type": "object",
114
+ "additionalProperties": false,
115
+ "properties": {
116
+ "enabled": {
117
+ "type": "boolean",
118
+ "description": "Enable relay agent registration when this account does not already have an app token.",
119
+ "default": false
120
+ },
121
+ "displayName": {
122
+ "type": "string",
123
+ "minLength": 1,
124
+ "description": "Public display name to use when the relay agent is created or refreshed."
125
+ }
126
+ }
127
+ },
128
+ "relay": {
129
+ "type": "object",
130
+ "additionalProperties": false,
131
+ "properties": {
132
+ "appToken": {
133
+ "type": "string",
134
+ "minLength": 1,
135
+ "description": "Canonical Claworld app token for this account. The runtime resolves the bound relay agent from this token."
136
+ },
137
+ "agentId": {
138
+ "type": "string",
139
+ "minLength": 1,
140
+ "description": "Optional relay agent id hint. The current flow resolves the binding from appToken at runtime."
141
+ },
142
+ "credentialToken": {
143
+ "type": "string",
144
+ "minLength": 1,
145
+ "description": "Optional credential token for this account. appToken is the current field."
146
+ },
147
+ "defaultTargetAgentId": {
148
+ "type": "string",
149
+ "minLength": 1,
150
+ "description": "Default relay target agentId for minimal outbound testing."
151
+ }
152
+ }
153
+ },
154
+ "defaultAccount": {
139
155
  "type": "string",
140
156
  "minLength": 1,
141
- "description": "Default relay target agentId for minimal outbound testing."
142
- }
143
- }
144
- },
145
- "defaultAccount": {
146
- "type": "string",
147
- "minLength": 1,
148
- "description": "Default account id to use when multiple claworld accounts are configured."
149
- },
150
- "accounts": {
151
- "type": "object",
152
- "minProperties": 1,
153
- "additionalProperties": {
154
- "type": "object",
155
- "additionalProperties": false,
156
- "required": [
157
- "enabled",
158
- "serverUrl",
159
- "apiKey",
160
- "accountId"
161
- ],
162
- "properties": {
163
- "name": {
164
- "type": "string",
165
- "minLength": 1,
166
- "description": "Optional operator-facing name for this local Claworld account."
167
- },
168
- "enabled": {
169
- "type": "boolean",
170
- "description": "Enable the Claworld channel plugin."
171
- },
172
- "serverUrl": {
173
- "type": "string",
174
- "minLength": 1,
175
- "description": "Relay backend base URL or websocket URL (http/https/ws/wss)."
176
- },
177
- "apiKey": {
178
- "type": "string",
179
- "minLength": 1,
180
- "description": "Plugin/backend API key for future backend-authenticated control paths."
181
- },
182
- "appToken": {
183
- "type": "string",
184
- "minLength": 1,
185
- "description": "Canonical Claworld app token for this channel account."
186
- },
187
- "accountId": {
188
- "type": "string",
189
- "minLength": 1,
190
- "description": "Local OpenClaw-facing account id bound to this channel instance."
191
- },
192
- "toolProfile": {
193
- "type": "string",
194
- "enum": [
195
- "minimal",
196
- "default",
197
- "world",
198
- "full"
199
- ],
200
- "description": "Optional ignored profile selector. Current tool exposure is backend-defined."
201
- },
202
- "heartbeatSeconds": {
203
- "type": "integer",
204
- "minimum": 1,
205
- "description": "Heartbeat cadence for the relay websocket client.",
206
- "default": 15
207
- },
208
- "reconnect": {
209
- "type": "boolean",
210
- "description": "Whether reconnect attempts are allowed after disconnect.",
211
- "default": true
212
- },
213
- "routing": {
157
+ "description": "Default account id to use when multiple claworld accounts are configured."
158
+ },
159
+ "accounts": {
160
+ "type": "object",
161
+ "minProperties": 1,
162
+ "additionalProperties": {
214
163
  "type": "object",
215
164
  "additionalProperties": false,
165
+ "required": [
166
+ "enabled",
167
+ "serverUrl",
168
+ "apiKey",
169
+ "accountId"
170
+ ],
216
171
  "properties": {
217
- "sessionTarget": {
218
- "type": "string",
219
- "enum": [
220
- "subagent",
221
- "mainagent"
222
- ],
223
- "default": "mainagent"
224
- },
225
- "fallbackTarget": {
172
+ "name": {
226
173
  "type": "string",
227
- "enum": [
228
- "mainagent",
229
- "human(optional)"
230
- ],
231
- "default": "mainagent"
174
+ "minLength": 1,
175
+ "description": "Optional operator-facing name for this local Claworld account."
232
176
  },
233
- "allowHumanInterrupt": {
234
- "type": "boolean",
235
- "default": true
236
- }
237
- }
238
- },
239
- "testing": {
240
- "type": "object",
241
- "additionalProperties": false,
242
- "properties": {
243
- "allowBridgedCommandDispatch": {
244
- "type": "boolean",
245
- "description": "Test-only switch that allows bridged relay turns beginning with slash commands to use the OpenClaw command fast-path.",
246
- "default": false
247
- }
248
- }
249
- },
250
- "registration": {
251
- "type": "object",
252
- "additionalProperties": false,
253
- "properties": {
254
177
  "enabled": {
255
178
  "type": "boolean",
256
- "description": "Enable relay agent registration when this account does not already have an app token.",
257
- "default": false
179
+ "description": "Enable the Claworld channel plugin."
258
180
  },
259
- "displayName": {
181
+ "serverUrl": {
260
182
  "type": "string",
261
183
  "minLength": 1,
262
- "description": "Public display name to use when the relay agent is created or refreshed."
263
- }
264
- }
265
- },
266
- "relay": {
267
- "type": "object",
268
- "additionalProperties": false,
269
- "properties": {
270
- "appToken": {
184
+ "description": "Relay backend base URL or websocket URL (http/https/ws/wss)."
185
+ },
186
+ "apiKey": {
271
187
  "type": "string",
272
188
  "minLength": 1,
273
- "description": "Canonical Claworld app token for this account. The runtime resolves the bound relay agent from this token."
189
+ "description": "Plugin/backend API key for future backend-authenticated control paths."
274
190
  },
275
- "agentId": {
191
+ "appToken": {
276
192
  "type": "string",
277
193
  "minLength": 1,
278
- "description": "Optional relay agent id hint. The current flow resolves the binding from appToken at runtime."
194
+ "description": "Canonical Claworld app token for this channel account."
279
195
  },
280
- "credentialToken": {
196
+ "accountId": {
281
197
  "type": "string",
282
198
  "minLength": 1,
283
- "description": "Optional credential token for this account. appToken is the current field."
199
+ "description": "Local OpenClaw-facing account id bound to this channel instance."
284
200
  },
285
- "defaultTargetAgentId": {
201
+ "toolProfile": {
286
202
  "type": "string",
287
- "minLength": 1,
288
- "description": "Default relay target agentId for minimal outbound testing."
203
+ "enum": [
204
+ "minimal",
205
+ "default",
206
+ "world",
207
+ "full"
208
+ ],
209
+ "description": "Optional ignored profile selector. Current tool exposure is backend-defined."
210
+ },
211
+ "heartbeatSeconds": {
212
+ "type": "integer",
213
+ "minimum": 1,
214
+ "description": "Heartbeat cadence for the relay websocket client.",
215
+ "default": 15
216
+ },
217
+ "reconnect": {
218
+ "type": "boolean",
219
+ "description": "Whether reconnect attempts are allowed after disconnect.",
220
+ "default": true
221
+ },
222
+ "routing": {
223
+ "type": "object",
224
+ "additionalProperties": false,
225
+ "properties": {
226
+ "sessionTarget": {
227
+ "type": "string",
228
+ "enum": [
229
+ "subagent",
230
+ "mainagent"
231
+ ],
232
+ "default": "mainagent"
233
+ },
234
+ "fallbackTarget": {
235
+ "type": "string",
236
+ "enum": [
237
+ "mainagent",
238
+ "human(optional)"
239
+ ],
240
+ "default": "mainagent"
241
+ },
242
+ "allowHumanInterrupt": {
243
+ "type": "boolean",
244
+ "default": true
245
+ }
246
+ }
247
+ },
248
+ "testing": {
249
+ "type": "object",
250
+ "additionalProperties": false,
251
+ "properties": {
252
+ "allowBridgedCommandDispatch": {
253
+ "type": "boolean",
254
+ "description": "Test-only switch that allows bridged relay turns beginning with slash commands to use the OpenClaw command fast-path.",
255
+ "default": false
256
+ }
257
+ }
258
+ },
259
+ "registration": {
260
+ "type": "object",
261
+ "additionalProperties": false,
262
+ "properties": {
263
+ "enabled": {
264
+ "type": "boolean",
265
+ "description": "Enable relay agent registration when this account does not already have an app token.",
266
+ "default": false
267
+ },
268
+ "displayName": {
269
+ "type": "string",
270
+ "minLength": 1,
271
+ "description": "Public display name to use when the relay agent is created or refreshed."
272
+ }
273
+ }
274
+ },
275
+ "relay": {
276
+ "type": "object",
277
+ "additionalProperties": false,
278
+ "properties": {
279
+ "appToken": {
280
+ "type": "string",
281
+ "minLength": 1,
282
+ "description": "Canonical Claworld app token for this account. The runtime resolves the bound relay agent from this token."
283
+ },
284
+ "agentId": {
285
+ "type": "string",
286
+ "minLength": 1,
287
+ "description": "Optional relay agent id hint. The current flow resolves the binding from appToken at runtime."
288
+ },
289
+ "credentialToken": {
290
+ "type": "string",
291
+ "minLength": 1,
292
+ "description": "Optional credential token for this account. appToken is the current field."
293
+ },
294
+ "defaultTargetAgentId": {
295
+ "type": "string",
296
+ "minLength": 1,
297
+ "description": "Default relay target agentId for minimal outbound testing."
298
+ }
299
+ }
289
300
  }
290
301
  }
291
302
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@xfxstudio/claworld",
3
- "version": "2026.4.27-testing.1",
3
+ "version": "2026.4.28-testing.1",
4
4
  "description": "Claworld channel plugin for OpenClaw",
5
5
  "type": "module",
6
6
  "main": "index.js",
@@ -4,6 +4,7 @@ export {
4
4
  CLAWORLD_CHANNEL_ID,
5
5
  claworldChannelConfigSchema,
6
6
  claworldChannelConfigJsonSchema,
7
+ claworldPluginConfigJsonSchema,
7
8
  validateClaworldChannelConfig,
8
9
  inspectClaworldChannelAccount,
9
10
  resolveClaworldChannelAccount,
@@ -13,16 +13,13 @@ export function normalizeRuntimeRegistration(candidate = {}) {
13
13
  const registration = candidate.registration && typeof candidate.registration === 'object'
14
14
  ? candidate.registration
15
15
  : {};
16
- const legacyLocalAgent = candidate.localAgent && typeof candidate.localAgent === 'object'
17
- ? candidate.localAgent
18
- : {};
19
- const enabled = registration.enabled === true || legacyLocalAgent.enabled === true;
16
+ const enabled = registration.enabled === true;
20
17
 
21
18
  if (!enabled) return { enabled: false };
22
19
 
23
20
  return {
24
21
  enabled: true,
25
- displayName: normalizeText(registration.displayName, normalizeText(legacyLocalAgent.displayName, null)),
22
+ displayName: normalizeText(registration.displayName, null),
26
23
  };
27
24
  }
28
25
 
@@ -35,8 +35,11 @@ import { createDemoSessionBootstrap } from '../runtime/demo-session-bootstrap.js
35
35
  import {
36
36
  broadcastModeratedWorld,
37
37
  createModeratedWorld,
38
+ fetchModeratedWorldInvites,
38
39
  fetchOwnedWorlds,
40
+ inviteModeratedWorldMember,
39
41
  manageModeratedWorld,
42
+ revokeModeratedWorldInvite,
40
43
  } from '../runtime/world-moderation-helper.js';
41
44
  import {
42
45
  fetchWorldMembership,
@@ -809,6 +812,48 @@ async function rejectChatRequest({
809
812
  return result.body || {};
810
813
  }
811
814
 
815
+ async function closeConversation({
816
+ runtimeConfig,
817
+ actorAgentId,
818
+ conversationKey = null,
819
+ localSessionKey = null,
820
+ localAgentId = null,
821
+ fetchImpl,
822
+ }) {
823
+ const relayLocalSessionKey = stripAgentScopedLocalSessionKey({
824
+ sessionKey: localSessionKey,
825
+ localAgentId,
826
+ });
827
+ const baseUrl = normalizeRelayHttpBaseUrl(runtimeConfig.serverUrl);
828
+ const result = await fetchJson(fetchImpl, `${baseUrl}/v1/chat-requests/conversations/close`, {
829
+ method: 'POST',
830
+ headers: {
831
+ 'content-type': 'application/json',
832
+ ...(runtimeConfig.apiKey ? { 'x-api-key': runtimeConfig.apiKey } : {}),
833
+ ...buildRuntimeAuthHeaders(runtimeConfig),
834
+ },
835
+ body: JSON.stringify({
836
+ actorAgentId,
837
+ ...(normalizeClaworldText(conversationKey, null) ? { conversationKey: normalizeClaworldText(conversationKey, null) } : {}),
838
+ ...(normalizeClaworldText(relayLocalSessionKey, null) ? { localSessionKey: normalizeClaworldText(relayLocalSessionKey, null) } : {}),
839
+ }),
840
+ });
841
+ if (!result.ok) {
842
+ createRelayRouteError({
843
+ result,
844
+ runtimeConfig,
845
+ code: 'conversation_close_failed',
846
+ publicMessage: 'failed to close conversation',
847
+ context: {
848
+ actorAgentId,
849
+ conversationKey: normalizeClaworldText(conversationKey, null),
850
+ localSessionKey: relayLocalSessionKey,
851
+ },
852
+ });
853
+ }
854
+ return normalizeChatInboxPayloadSessionKeys(result.body || {}, { localAgentId });
855
+ }
856
+
812
857
  function waitForAbort(signal) {
813
858
  return new Promise((resolve) => {
814
859
  if (!signal) return resolve({ reason: 'missing_abort_signal' });
@@ -1313,7 +1358,7 @@ async function resolveRelayAgentSummary({
1313
1358
 
1314
1359
  return {
1315
1360
  agentId: normalizedAgentId,
1316
- displayName: normalizeClaworldText(runtimeConfig.registration?.displayName, normalizeClaworldText(runtimeConfig.localAgent?.displayName, null)),
1361
+ displayName: normalizeClaworldText(runtimeConfig.registration?.displayName, null),
1317
1362
  publicIdentity: null,
1318
1363
  discoverable: null,
1319
1364
  contactable: null,
@@ -1628,7 +1673,7 @@ async function fetchRuntimeWorldMembers({
1628
1673
  }
1629
1674
  const baseUrl = normalizeRelayHttpBaseUrl(runtimeConfig.serverUrl);
1630
1675
  const requestUrl = new URL(`${baseUrl}/v1/worlds/${encodeURIComponent(normalizedWorldId)}/memberships`);
1631
- if (agentId) requestUrl.searchParams.set('agentId', agentId);
1676
+ if (agentId) requestUrl.searchParams.set('actorAgentId', agentId);
1632
1677
  if (status) requestUrl.searchParams.set('status', status);
1633
1678
  const normalizedLimit = normalizeClaworldInteger(limit, null);
1634
1679
  if (normalizedLimit > 0) requestUrl.searchParams.set('limit', String(normalizedLimit));
@@ -3303,6 +3348,17 @@ async function generateRuntimeProfileCard(context = {}) {
3303
3348
  fetchImpl,
3304
3349
  });
3305
3350
  },
3351
+ closeConversation: async (context = {}) => {
3352
+ const resolvedContext = await resolveBoundRuntimeContext(context);
3353
+ return closeConversation({
3354
+ runtimeConfig: resolvedContext.runtimeConfig,
3355
+ actorAgentId: resolvedContext.agentId || null,
3356
+ conversationKey: context.conversationKey || null,
3357
+ localSessionKey: context.localSessionKey || null,
3358
+ localAgentId: resolveContextBoundLocalAgentId(resolvedContext),
3359
+ fetchImpl,
3360
+ });
3361
+ },
3306
3362
  },
3307
3363
  profile: {
3308
3364
  getPublicIdentity: getRuntimePublicIdentity,
@@ -3565,6 +3621,47 @@ async function generateRuntimeProfileCard(context = {}) {
3565
3621
  logger,
3566
3622
  });
3567
3623
  },
3624
+ inviteMember: async (context = {}) => {
3625
+ const resolvedContext = await resolveBoundRuntimeContext(context);
3626
+ return inviteModeratedWorldMember({
3627
+ cfg: resolvedContext.cfg || {},
3628
+ accountId: resolvedContext.accountId || null,
3629
+ runtimeConfig: resolvedContext.runtimeConfig || null,
3630
+ agentId: resolvedContext.agentId || null,
3631
+ worldId: context.worldId || null,
3632
+ targetAgentId: context.targetAgentId || null,
3633
+ identity: context.identity || null,
3634
+ inviteMessage: context.inviteMessage || null,
3635
+ fetchImpl,
3636
+ logger,
3637
+ });
3638
+ },
3639
+ revokeInvite: async (context = {}) => {
3640
+ const resolvedContext = await resolveBoundRuntimeContext(context);
3641
+ return revokeModeratedWorldInvite({
3642
+ cfg: resolvedContext.cfg || {},
3643
+ accountId: resolvedContext.accountId || null,
3644
+ runtimeConfig: resolvedContext.runtimeConfig || null,
3645
+ agentId: resolvedContext.agentId || null,
3646
+ worldId: context.worldId || null,
3647
+ targetAgentId: context.targetAgentId || null,
3648
+ fetchImpl,
3649
+ logger,
3650
+ });
3651
+ },
3652
+ listInvites: async (context = {}) => {
3653
+ const resolvedContext = await resolveBoundRuntimeContext(context);
3654
+ return fetchModeratedWorldInvites({
3655
+ cfg: resolvedContext.cfg || {},
3656
+ accountId: resolvedContext.accountId || null,
3657
+ runtimeConfig: resolvedContext.runtimeConfig || null,
3658
+ agentId: resolvedContext.agentId || null,
3659
+ worldId: context.worldId || null,
3660
+ status: context.status || 'invited',
3661
+ fetchImpl,
3662
+ logger,
3663
+ });
3664
+ },
3568
3665
  },
3569
3666
  membership: {
3570
3667
  listWorldMembers: async (context = {}) => {
@@ -3939,6 +4036,47 @@ async function generateRuntimeProfileCard(context = {}) {
3939
4036
  logger,
3940
4037
  });
3941
4038
  },
4039
+ inviteMember: async (context = {}) => {
4040
+ const resolvedContext = await resolveBoundRuntimeContext(context);
4041
+ return inviteModeratedWorldMember({
4042
+ cfg: resolvedContext.cfg || {},
4043
+ accountId: resolvedContext.accountId || null,
4044
+ runtimeConfig: resolvedContext.runtimeConfig || null,
4045
+ agentId: resolvedContext.agentId || null,
4046
+ worldId: context.worldId || null,
4047
+ targetAgentId: context.targetAgentId || null,
4048
+ identity: context.identity || null,
4049
+ inviteMessage: context.inviteMessage || null,
4050
+ fetchImpl,
4051
+ logger,
4052
+ });
4053
+ },
4054
+ revokeInvite: async (context = {}) => {
4055
+ const resolvedContext = await resolveBoundRuntimeContext(context);
4056
+ return revokeModeratedWorldInvite({
4057
+ cfg: resolvedContext.cfg || {},
4058
+ accountId: resolvedContext.accountId || null,
4059
+ runtimeConfig: resolvedContext.runtimeConfig || null,
4060
+ agentId: resolvedContext.agentId || null,
4061
+ worldId: context.worldId || null,
4062
+ targetAgentId: context.targetAgentId || null,
4063
+ fetchImpl,
4064
+ logger,
4065
+ });
4066
+ },
4067
+ listInvites: async (context = {}) => {
4068
+ const resolvedContext = await resolveBoundRuntimeContext(context);
4069
+ return fetchModeratedWorldInvites({
4070
+ cfg: resolvedContext.cfg || {},
4071
+ accountId: resolvedContext.accountId || null,
4072
+ runtimeConfig: resolvedContext.runtimeConfig || null,
4073
+ agentId: resolvedContext.agentId || null,
4074
+ worldId: context.worldId || null,
4075
+ status: context.status || 'invited',
4076
+ fetchImpl,
4077
+ logger,
4078
+ });
4079
+ },
3942
4080
  },
3943
4081
  membership: {
3944
4082
  listWorldMembers: async (context = {}) => {
@@ -8,6 +8,12 @@ const REQUIRED_KEYS = ['enabled', 'serverUrl', 'apiKey', 'accountId'];
8
8
 
9
9
  export const CLAWORLD_CHANNEL_ID = 'claworld';
10
10
 
11
+ export const claworldPluginConfigJsonSchema = {
12
+ type: 'object',
13
+ additionalProperties: false,
14
+ properties: {},
15
+ };
16
+
11
17
  const AGENT_REGISTRATION_SCHEMA = {
12
18
  type: 'object',
13
19
  additionalProperties: false,
@@ -647,10 +647,7 @@ export function resolveClaworldManagedRuntimeOptions({
647
647
  const name = normalizeText(overrides.name, normalizeText(existingBackup.name, displayName));
648
648
  const existingRegistrationDisplayName = normalizeRegistrationDisplayName(
649
649
  existingAccount?.registration?.displayName,
650
- normalizeRegistrationDisplayName(
651
- existingAccount?.localAgent?.displayName,
652
- normalizeRegistrationDisplayName(existingBackup.registrationDisplayName, null),
653
- ),
650
+ normalizeRegistrationDisplayName(existingBackup.registrationDisplayName, null),
654
651
  );
655
652
  const registrationDisplayName = appToken && !explicitRegistrationDisplayName
656
653
  ? null
@@ -173,13 +173,16 @@ const TERMINAL_WORLD_ACTIONS = Object.freeze([
173
173
  'list_world_activity',
174
174
  'list_broadcast_history',
175
175
  'manage_members',
176
+ 'list_invites',
177
+ 'invite_member',
178
+ 'revoke_invite',
176
179
  ]);
177
180
 
178
181
  const TERMINAL_CONVERSATION_ACTIONS = Object.freeze([
179
182
  'request',
180
183
  'accept',
181
184
  'reject',
182
- 'end',
185
+ 'close',
183
186
  'get_state',
184
187
  'list_related',
185
188
  ]);
@@ -235,6 +238,7 @@ function normalizeTerminalWorldAction(params = {}) {
235
238
  requireManageWorldField('action', `action must be one of ${TERMINAL_WORLD_ACTIONS.join(', ')}`);
236
239
  }
237
240
  if (!normalizeText(params.worldId, null)) return 'list_owned_worlds';
241
+ if (normalizeText(params.targetAgentId, null) || normalizeText(params.identity, null)) return 'invite_member';
238
242
  if (normalizeText(params.announcementText, null)) return 'publish_broadcast';
239
243
  if (normalizeText(params.participantContextText, null)) return 'update_world_profile';
240
244
  if (
@@ -711,6 +715,9 @@ function createTerminalToolAdapters(api, plugin, internalTools) {
711
715
  broadcastEnabled: booleanParam({ description: 'Whether a world subscription should receive broadcasts.' }),
712
716
  broadcast: objectParam({ description: 'Optional broadcast config for update_world or set_world_broadcast_preference.', additionalProperties: true }),
713
717
  subscriptionId: stringParam({ description: 'Existing subscription id for unsubscribe_world.', minLength: 1 }),
718
+ targetAgentId: stringParam({ description: 'Target agent id for private-world invitation actions.', minLength: 1 }),
719
+ identity: stringParam({ description: 'Target public identity displayName#code for private-world invitation actions.', minLength: 1 }),
720
+ inviteMessage: stringParam({ description: 'Optional private-world invitation note.', minLength: 1 }),
714
721
  limit: integerParam({ description: 'Maximum rows for activity/member listing actions.', minimum: 1, maximum: 100 }),
715
722
  status: stringParam({ description: 'Optional membership/subscription status filter.', minLength: 1 }),
716
723
  },
@@ -796,6 +803,54 @@ function createTerminalToolAdapters(api, plugin, internalTools) {
796
803
  });
797
804
  return buildTerminalActionResult({ tool: manageWorldsTool, action, payload });
798
805
  }
806
+ if (action === 'list_invites') {
807
+ const worldId = normalizeText(params.worldId, null);
808
+ if (!worldId) requireManageWorldField('worldId');
809
+ const context = await resolveToolContext(api, plugin, params, {
810
+ requiredPublicIdentityCapability: 'list world invites',
811
+ });
812
+ const payload = await plugin.runtime.productShell.moderation.listInvites({
813
+ ...context,
814
+ worldId,
815
+ status: params.status || 'invited',
816
+ });
817
+ return buildTerminalActionResult({ tool: manageWorldsTool, action, payload });
818
+ }
819
+ if (action === 'invite_member') {
820
+ const worldId = normalizeText(params.worldId, null);
821
+ if (!worldId) requireManageWorldField('worldId');
822
+ const targetAgentId = normalizeText(params.targetAgentId, null);
823
+ const identity = normalizeText(params.identity, null);
824
+ if (!targetAgentId && !identity) {
825
+ requireManageWorldField('targetAgentId', 'targetAgentId or identity is required for action=invite_member');
826
+ }
827
+ const context = await resolveToolContext(api, plugin, params, {
828
+ requiredPublicIdentityCapability: 'invite world member',
829
+ });
830
+ const payload = await plugin.runtime.productShell.moderation.inviteMember({
831
+ ...context,
832
+ worldId,
833
+ targetAgentId,
834
+ identity,
835
+ inviteMessage: normalizeText(params.inviteMessage, null),
836
+ });
837
+ return buildTerminalActionResult({ tool: manageWorldsTool, action, payload });
838
+ }
839
+ if (action === 'revoke_invite') {
840
+ const worldId = normalizeText(params.worldId, null);
841
+ if (!worldId) requireManageWorldField('worldId');
842
+ const targetAgentId = normalizeText(params.targetAgentId, null);
843
+ if (!targetAgentId) requireManageWorldField('targetAgentId');
844
+ const context = await resolveToolContext(api, plugin, params, {
845
+ requiredPublicIdentityCapability: 'revoke world invite',
846
+ });
847
+ const payload = await plugin.runtime.productShell.moderation.revokeInvite({
848
+ ...context,
849
+ worldId,
850
+ targetAgentId,
851
+ });
852
+ return buildTerminalActionResult({ tool: manageWorldsTool, action, payload });
853
+ }
799
854
  if (
800
855
  action === 'update_world'
801
856
  && typeof params.enabled === 'boolean'
@@ -850,7 +905,8 @@ function createTerminalToolAdapters(api, plugin, internalTools) {
850
905
  category: 'conversation_management',
851
906
  usageNotes: [
852
907
  'action=request starts a direct or world-scoped chat request.',
853
- 'action=list_related/get_state, accept, reject, and end manage product-level conversation state decisions.',
908
+ 'action=list_related/get_state, accept, reject, and close manage product-level conversation state decisions.',
909
+ 'action=close is a backend close; natural peer-facing endings still use [[request_conversation_end]] inside the Conversation Session.',
854
910
  'Do not use this tool for live conversation turns.',
855
911
  ],
856
912
  }),
@@ -870,9 +926,8 @@ function createTerminalToolAdapters(api, plugin, internalTools) {
870
926
  worldId: worldIdProperty,
871
927
  filters: objectParam({ description: 'List filters.', additionalProperties: true }),
872
928
  chatRequestId: stringParam({ description: 'Request id for accept/reject.', minLength: 1 }),
873
- conversationKey: stringParam({ description: 'Conversation key for get_state/end.', minLength: 1 }),
874
- localSessionKey: stringParam({ description: 'Local conversation session key for get_state/end guidance.', minLength: 1 }),
875
- endingMessage: stringParam({ description: 'Optional final peer-facing message to pair with the conversation-end control token.', minLength: 1 }),
929
+ conversationKey: stringParam({ description: 'Conversation key for get_state/close.', minLength: 1 }),
930
+ localSessionKey: stringParam({ description: 'Local conversation session key for get_state/close.', minLength: 1 }),
876
931
  },
877
932
  }),
878
933
  async execute(toolCallId, params = {}) {
@@ -891,41 +946,22 @@ function createTerminalToolAdapters(api, plugin, internalTools) {
891
946
  });
892
947
  return rewriteToolResultName(result, manageConversationsTool, action);
893
948
  }
894
- if (action === 'end') {
949
+ if (action === 'close') {
895
950
  const conversationKey = normalizeText(params.conversationKey, null);
896
951
  const localSessionKey = normalizeText(params.localSessionKey, null);
897
952
  if (!conversationKey && !localSessionKey) {
898
- requireManageWorldField('conversationKey', 'conversationKey or localSessionKey is required for action=end');
953
+ requireManageWorldField('conversationKey', 'conversationKey or localSessionKey is required for action=close');
899
954
  }
900
- const filters = {
901
- ...(normalizeObject(params.filters, {}) || {}),
902
- ...(conversationKey ? { conversationKey } : {}),
903
- ...(localSessionKey ? { localSessionKey } : {}),
904
- };
905
- const result = await requireTerminalTool(internalTools, 'claworld_chat_inbox').execute(toolCallId, {
906
- ...params,
907
- action: 'list',
908
- filters,
955
+ const context = await resolveToolContext(api, plugin, params);
956
+ const payload = await plugin.helpers.social.closeConversation({
957
+ ...context,
958
+ conversationKey,
959
+ localSessionKey,
909
960
  });
910
- const parsed = JSON.parse(result.content?.[0]?.text || '{}');
911
961
  return buildTerminalActionResult({
912
962
  tool: manageConversationsTool,
913
963
  action,
914
- payload: {
915
- ...parsed,
916
- status: 'conversation_session_required',
917
- ending: {
918
- status: 'conversation_session_required',
919
- controlToken: '[[request_conversation_end]]',
920
- conversationKey,
921
- localSessionKey,
922
- endingMessage: normalizeText(params.endingMessage, null),
923
- instruction:
924
- 'Send one final peer-facing reply from the Conversation Session and include [[request_conversation_end]] to request a formal close.',
925
- },
926
- requiresUserDecision: false,
927
- nextAction: 'send_final_conversation_session_reply_with_request_conversation_end',
928
- },
964
+ payload,
929
965
  });
930
966
  }
931
967
  requireManageWorldField('action', `action must be one of ${TERMINAL_CONVERSATION_ACTIONS.join(', ')}`);
@@ -1074,7 +1110,7 @@ function buildRegisteredTools(api, plugin) {
1074
1110
  'This is the only public join entrypoint for the default flow.',
1075
1111
  'Provide one participantContextText that describes who the agent is in this world.',
1076
1112
  'Expected behavior: on success it creates or updates the caller\'s active membership for that world and returns member-search, activity, subscription, and optional request-chat follow-up actions.',
1077
- 'When status is joined, use memberSearchAction or worldActivityAction before requestChatAction unless a target member is already known.',
1113
+ 'When membershipStatus is active, use memberSearchAction or worldActivityAction before requestChatAction unless a target member is already known.',
1078
1114
  'If the agent later needs fresh member results for the same world, call claworld_search(scope=world_members).',
1079
1115
  ],
1080
1116
  examples: [
@@ -1336,6 +1336,22 @@ export class ClaworldRelayClient extends EventEmitter {
1336
1336
  });
1337
1337
  }
1338
1338
 
1339
+ async closeConversation({ actorAgentId, conversationKey = null, localSessionKey = null } = {}) {
1340
+ return await this.requestJson('/v1/chat-requests/conversations/close', {
1341
+ method: 'POST',
1342
+ headers: buildRuntimeAuthHeaders(this.runtimeConfig, { 'content-type': 'application/json' }),
1343
+ body: JSON.stringify({
1344
+ actorAgentId,
1345
+ ...(conversationKey ? { conversationKey } : {}),
1346
+ ...(localSessionKey ? { localSessionKey } : {}),
1347
+ }),
1348
+ }, {
1349
+ code: 'relay_conversation_close_failed',
1350
+ message: 'failed to close relay conversation',
1351
+ publicMessage: 'failed to close relay conversation',
1352
+ });
1353
+ }
1354
+
1339
1355
  async deliverMessage({ fromAgentId, targetAgentId, clientMessageId = null, payload = {}, conversation = {} } = {}) {
1340
1356
  const resolvedClientMessageId = requireClientMessageId(clientMessageId);
1341
1357
  const result = await this.requestJson('/v1/orchestration/messages', {
@@ -268,8 +268,9 @@ export function normalizeWorldJoinResponse(payload = {}, { worldId = null, agent
268
268
  const normalizedWorldId = normalizeText(payload.worldId, worldId || 'unknown-world');
269
269
  const normalizedAgentId = normalizeText(payload.agentId || membership?.agentId, agentId || null);
270
270
  const membershipStatus = normalizeText(payload.membershipStatus || membership?.status, 'unknown');
271
+ const responseStatus = normalizeText(payload.status, membershipStatus === 'active' ? 'active' : 'accepted');
271
272
  return {
272
- status: normalizeText(payload.status, membershipStatus === 'active' ? 'joined' : 'accepted'),
273
+ status: responseStatus,
273
274
  worldId: normalizedWorldId,
274
275
  agentId: normalizedAgentId,
275
276
  worldRole: normalizeWorldRole(payload.worldRole, null),
@@ -339,7 +339,7 @@ export function projectToolJoinWorldResponse(
339
339
  { accountId = null } = {},
340
340
  ) {
341
341
  return {
342
- status: joinResult.membershipStatus === 'active' ? 'joined' : 'accepted',
342
+ status: joinResult.membershipStatus === 'active' ? 'active' : 'accepted',
343
343
  worldId: normalizeText(joinResult.worldId, null),
344
344
  accountId: normalizeText(accountId, null),
345
345
  worldRole: projectWorldRole(joinResult.worldRole, null),
@@ -92,6 +92,34 @@ function normalizeWorldBroadcastConfig(broadcast = null) {
92
92
  };
93
93
  }
94
94
 
95
+ function normalizeWorldInvite(payload = {}) {
96
+ return {
97
+ status: normalizeText(payload.status, null),
98
+ worldId: normalizeText(payload.worldId, null),
99
+ displayName: normalizeText(payload.displayName, null),
100
+ targetAgentId: normalizeText(payload.targetAgentId, null),
101
+ targetIdentity: normalizeText(payload.targetIdentity, null),
102
+ invitedByAgentId: normalizeText(payload.invitedByAgentId, null),
103
+ membershipId: normalizeText(payload.membershipId, null),
104
+ membershipStatus: normalizeText(payload.membershipStatus, null),
105
+ created: normalizeOptionalBoolean(payload.created, null),
106
+ invitedAt: normalizeText(payload.invitedAt, null),
107
+ inviteMessage: normalizeText(payload.inviteMessage, null),
108
+ inviteRevokedAt: normalizeText(payload.inviteRevokedAt, null),
109
+ notificationId: normalizeText(payload.notificationId, null),
110
+ nextAction: normalizeText(payload.nextAction, null),
111
+ };
112
+ }
113
+
114
+ function normalizeWorldInviteList(payload = {}) {
115
+ return {
116
+ worldId: normalizeText(payload.worldId, null),
117
+ items: Array.isArray(payload.items) ? payload.items.map((item) => normalizeWorldInvite(item)) : [],
118
+ totalItems: normalizeOptionalInteger(payload.totalItems, 0),
119
+ nextAction: normalizeText(payload.nextAction, null),
120
+ };
121
+ }
122
+
95
123
  function normalizeManagedWorld(payload = {}) {
96
124
  return {
97
125
  worldId: normalizeText(payload.worldId, null),
@@ -465,6 +493,170 @@ export async function manageModeratedWorld({
465
493
  return normalizeManagedWorld(result.body);
466
494
  }
467
495
 
496
+ export async function inviteModeratedWorldMember({
497
+ cfg = {},
498
+ accountId = null,
499
+ runtimeConfig = null,
500
+ agentId = null,
501
+ worldId = null,
502
+ targetAgentId = null,
503
+ identity = null,
504
+ inviteMessage = null,
505
+ fetchImpl,
506
+ logger = console,
507
+ } = {}) {
508
+ if (typeof fetchImpl !== 'function') {
509
+ throw new Error('fetch is unavailable for claworld world invite helper');
510
+ }
511
+
512
+ const resolvedAgentId = normalizeText(agentId, null);
513
+ if (!resolvedAgentId) {
514
+ throw new Error('claworld world invite helper requires agentId');
515
+ }
516
+ const resolvedWorldId = normalizeText(worldId, null);
517
+ if (!resolvedWorldId) {
518
+ throw new Error('claworld world invite helper requires worldId');
519
+ }
520
+ const resolvedRuntimeConfig = runtimeConfig || resolveClaworldRuntimeConfig(cfg, accountId);
521
+ const baseUrl = normalizeRelayHttpBaseUrl(resolvedRuntimeConfig.serverUrl);
522
+ const result = await fetchJson(fetchImpl, `${baseUrl}/v1/moderation/worlds/${encodeURIComponent(resolvedWorldId)}/invitations`, {
523
+ method: 'POST',
524
+ headers: buildRuntimeAuthHeaders(resolvedRuntimeConfig, {
525
+ accept: 'application/json',
526
+ 'content-type': 'application/json',
527
+ ...(resolvedRuntimeConfig.apiKey ? { 'x-api-key': resolvedRuntimeConfig.apiKey } : {}),
528
+ }),
529
+ body: JSON.stringify({
530
+ agentId: resolvedAgentId,
531
+ ...(normalizeText(targetAgentId, null) ? { targetAgentId: normalizeText(targetAgentId, null) } : {}),
532
+ ...(normalizeText(identity, null) ? { identity: normalizeText(identity, null) } : {}),
533
+ ...(normalizeText(inviteMessage, null) ? { inviteMessage: normalizeText(inviteMessage, null) } : {}),
534
+ }),
535
+ });
536
+
537
+ if (!result.ok) {
538
+ logger.error?.('[claworld:moderation] world invite failed', {
539
+ status: result.status,
540
+ worldId: resolvedWorldId,
541
+ accountId: resolvedRuntimeConfig.accountId || accountId || null,
542
+ body: result.body,
543
+ });
544
+ throw createModerationHttpError('invite_member', result, {
545
+ accountId: resolvedRuntimeConfig.accountId || accountId || null,
546
+ worldId: resolvedWorldId,
547
+ });
548
+ }
549
+
550
+ return normalizeWorldInvite(result.body);
551
+ }
552
+
553
+ export async function revokeModeratedWorldInvite({
554
+ cfg = {},
555
+ accountId = null,
556
+ runtimeConfig = null,
557
+ agentId = null,
558
+ worldId = null,
559
+ targetAgentId = null,
560
+ fetchImpl,
561
+ logger = console,
562
+ } = {}) {
563
+ if (typeof fetchImpl !== 'function') {
564
+ throw new Error('fetch is unavailable for claworld world invite revoke helper');
565
+ }
566
+
567
+ const resolvedAgentId = normalizeText(agentId, null);
568
+ if (!resolvedAgentId) {
569
+ throw new Error('claworld world invite revoke helper requires agentId');
570
+ }
571
+ const resolvedWorldId = normalizeText(worldId, null);
572
+ if (!resolvedWorldId) {
573
+ throw new Error('claworld world invite revoke helper requires worldId');
574
+ }
575
+ const resolvedTargetAgentId = normalizeText(targetAgentId, null);
576
+ if (!resolvedTargetAgentId) {
577
+ throw new Error('claworld world invite revoke helper requires targetAgentId');
578
+ }
579
+ const resolvedRuntimeConfig = runtimeConfig || resolveClaworldRuntimeConfig(cfg, accountId);
580
+ const baseUrl = normalizeRelayHttpBaseUrl(resolvedRuntimeConfig.serverUrl);
581
+ const result = await fetchJson(fetchImpl, `${baseUrl}/v1/moderation/worlds/${encodeURIComponent(resolvedWorldId)}/invitations/revoke`, {
582
+ method: 'POST',
583
+ headers: buildRuntimeAuthHeaders(resolvedRuntimeConfig, {
584
+ accept: 'application/json',
585
+ 'content-type': 'application/json',
586
+ ...(resolvedRuntimeConfig.apiKey ? { 'x-api-key': resolvedRuntimeConfig.apiKey } : {}),
587
+ }),
588
+ body: JSON.stringify({
589
+ agentId: resolvedAgentId,
590
+ targetAgentId: resolvedTargetAgentId,
591
+ }),
592
+ });
593
+
594
+ if (!result.ok) {
595
+ logger.error?.('[claworld:moderation] world invite revoke failed', {
596
+ status: result.status,
597
+ worldId: resolvedWorldId,
598
+ accountId: resolvedRuntimeConfig.accountId || accountId || null,
599
+ body: result.body,
600
+ });
601
+ throw createModerationHttpError('revoke_invite', result, {
602
+ accountId: resolvedRuntimeConfig.accountId || accountId || null,
603
+ worldId: resolvedWorldId,
604
+ });
605
+ }
606
+
607
+ return normalizeWorldInvite(result.body);
608
+ }
609
+
610
+ export async function fetchModeratedWorldInvites({
611
+ cfg = {},
612
+ accountId = null,
613
+ runtimeConfig = null,
614
+ agentId = null,
615
+ worldId = null,
616
+ status = 'invited',
617
+ fetchImpl,
618
+ logger = console,
619
+ } = {}) {
620
+ if (typeof fetchImpl !== 'function') {
621
+ throw new Error('fetch is unavailable for claworld world invites helper');
622
+ }
623
+
624
+ const resolvedAgentId = normalizeText(agentId, null);
625
+ if (!resolvedAgentId) {
626
+ throw new Error('claworld world invites helper requires agentId');
627
+ }
628
+ const resolvedWorldId = normalizeText(worldId, null);
629
+ if (!resolvedWorldId) {
630
+ throw new Error('claworld world invites helper requires worldId');
631
+ }
632
+ const resolvedRuntimeConfig = runtimeConfig || resolveClaworldRuntimeConfig(cfg, accountId);
633
+ const baseUrl = normalizeRelayHttpBaseUrl(resolvedRuntimeConfig.serverUrl);
634
+ const requestUrl = new URL(`${baseUrl}/v1/moderation/worlds/${encodeURIComponent(resolvedWorldId)}/invitations`);
635
+ requestUrl.searchParams.set('agentId', resolvedAgentId);
636
+ if (normalizeText(status, null)) requestUrl.searchParams.set('status', normalizeText(status, null));
637
+ const result = await fetchJson(fetchImpl, requestUrl.toString(), {
638
+ headers: buildRuntimeAuthHeaders(resolvedRuntimeConfig, {
639
+ accept: 'application/json',
640
+ ...(resolvedRuntimeConfig.apiKey ? { 'x-api-key': resolvedRuntimeConfig.apiKey } : {}),
641
+ }),
642
+ });
643
+
644
+ if (!result.ok) {
645
+ logger.error?.('[claworld:moderation] world invites fetch failed', {
646
+ status: result.status,
647
+ worldId: resolvedWorldId,
648
+ accountId: resolvedRuntimeConfig.accountId || accountId || null,
649
+ body: result.body,
650
+ });
651
+ throw createModerationHttpError('list_invites', result, {
652
+ accountId: resolvedRuntimeConfig.accountId || accountId || null,
653
+ worldId: resolvedWorldId,
654
+ });
655
+ }
656
+
657
+ return normalizeWorldInviteList(result.body);
658
+ }
659
+
468
660
  export async function broadcastModeratedWorld({
469
661
  cfg = {},
470
662
  accountId = null,