@xfxstudio/claworld 0.2.24 → 2026.4.14-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.
- package/openclaw.plugin.json +1 -1
- package/package.json +1 -1
- package/skills/claworld-a2a-channel-agent/SKILL.md +218 -0
- package/skills/claworld-help/SKILL.md +77 -3
- package/skills/claworld-join-and-chat/SKILL.md +186 -43
- package/skills/claworld-manage-worlds/SKILL.md +57 -5
- package/src/lib/relay/agent-readable-markdown.js +385 -0
- package/src/lib/relay/kickoff-text.js +6 -217
- package/src/openclaw/index.js +6 -0
- package/src/openclaw/plugin/account-identity.js +11 -2
- package/src/openclaw/plugin/claworld-channel-plugin.js +221 -6
- package/src/openclaw/plugin/managed-config.js +19 -0
- package/src/openclaw/plugin/register-tooling.js +60 -1
- package/src/openclaw/plugin/register.js +442 -44
- package/src/openclaw/plugin/relay-client.js +2 -1
- package/src/openclaw/plugin-version.js +67 -0
- package/src/openclaw/runtime/product-shell-helper.js +220 -15
- package/src/openclaw/runtime/tool-contracts.js +327 -23
- package/src/openclaw/runtime/tool-inventory.js +3 -0
- package/src/openclaw/runtime/world-membership-helper.js +320 -0
- package/src/openclaw/runtime/world-moderation-helper.js +158 -1
- package/src/product-shell/contracts/world-orchestration.js +9 -0
|
@@ -0,0 +1,320 @@
|
|
|
1
|
+
import { resolveClaworldRuntimeConfig } from '../plugin/config-schema.js';
|
|
2
|
+
import { buildRuntimeAuthHeaders } from '../plugin/account-identity.js';
|
|
3
|
+
import { createRuntimeBoundaryError } from '../../lib/runtime-errors.js';
|
|
4
|
+
import { extractBackendErrorContext } from './backend-error-context.js';
|
|
5
|
+
|
|
6
|
+
function normalizeText(value, fallback = null) {
|
|
7
|
+
if (value == null) return fallback;
|
|
8
|
+
const normalized = String(value).trim();
|
|
9
|
+
return normalized || fallback;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
function normalizeOptionalBoolean(value, fallback = null) {
|
|
13
|
+
if (typeof value === 'boolean') return value;
|
|
14
|
+
return fallback;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
function normalizeWorldRole(worldRole, fallback = null) {
|
|
18
|
+
const normalized = normalizeText(worldRole, fallback);
|
|
19
|
+
return ['owner', 'member'].includes(normalized) ? normalized : fallback;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
function normalizeManagedWorldMembership(payload = {}) {
|
|
23
|
+
return {
|
|
24
|
+
membershipId: normalizeText(payload.membershipId, null),
|
|
25
|
+
worldId: normalizeText(payload.worldId, null),
|
|
26
|
+
displayName: normalizeText(payload.displayName, null),
|
|
27
|
+
worldContextText: normalizeText(payload.worldContextText, null),
|
|
28
|
+
ownerAgentId: normalizeText(payload.ownerAgentId, null),
|
|
29
|
+
enabled: normalizeOptionalBoolean(payload.enabled, null),
|
|
30
|
+
worldStatus: normalizeText(payload.worldStatus, null),
|
|
31
|
+
worldRole: normalizeWorldRole(payload.worldRole, null),
|
|
32
|
+
membershipStatus: normalizeText(payload.membershipStatus, null),
|
|
33
|
+
participantContextText: normalizeText(payload.participantContextText, null),
|
|
34
|
+
joinedAt: normalizeText(payload.joinedAt, null),
|
|
35
|
+
updatedAt: normalizeText(payload.updatedAt, null),
|
|
36
|
+
nextAction: normalizeText(payload.nextAction, null),
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
function normalizeMembershipList(payload = {}) {
|
|
41
|
+
return {
|
|
42
|
+
items: Array.isArray(payload.items)
|
|
43
|
+
? payload.items.map((item) => normalizeManagedWorldMembership(item))
|
|
44
|
+
: [],
|
|
45
|
+
nextAction: normalizeText(payload.nextAction, null),
|
|
46
|
+
};
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
async function fetchJson(fetchImpl, url, init = {}) {
|
|
50
|
+
let response;
|
|
51
|
+
try {
|
|
52
|
+
response = await fetchImpl(url, init);
|
|
53
|
+
} catch (error) {
|
|
54
|
+
throw createRuntimeBoundaryError({
|
|
55
|
+
code: 'relay_fetch_failed',
|
|
56
|
+
category: 'transport',
|
|
57
|
+
status: 502,
|
|
58
|
+
message: `fetch failed: ${error?.message || String(error)}`,
|
|
59
|
+
publicMessage: 'relay fetch failed',
|
|
60
|
+
recoverable: true,
|
|
61
|
+
context: {
|
|
62
|
+
fetchUrl: url,
|
|
63
|
+
fetchMethod: init?.method || 'GET',
|
|
64
|
+
},
|
|
65
|
+
cause: error,
|
|
66
|
+
});
|
|
67
|
+
}
|
|
68
|
+
let body = null;
|
|
69
|
+
try {
|
|
70
|
+
body = await response.json();
|
|
71
|
+
} catch {
|
|
72
|
+
body = null;
|
|
73
|
+
}
|
|
74
|
+
return { ok: response.ok, status: response.status, body };
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
function normalizeRelayHttpBaseUrl(serverUrl) {
|
|
78
|
+
const parsed = new URL(serverUrl);
|
|
79
|
+
if (parsed.protocol === 'ws:') parsed.protocol = 'http:';
|
|
80
|
+
if (parsed.protocol === 'wss:') parsed.protocol = 'https:';
|
|
81
|
+
parsed.pathname = '';
|
|
82
|
+
parsed.search = '';
|
|
83
|
+
parsed.hash = '';
|
|
84
|
+
return parsed.toString().replace(/\/$/, '');
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
function inferHttpErrorCategory(status) {
|
|
88
|
+
if (status === 401) return 'auth';
|
|
89
|
+
if (status === 403) return 'policy';
|
|
90
|
+
if (status === 404) return 'input';
|
|
91
|
+
if (status === 409) return 'conflict';
|
|
92
|
+
if (status >= 400 && status < 500) return 'input';
|
|
93
|
+
return 'runtime';
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
function createWorldMembershipHttpError(action, response, { accountId = null, worldId = null } = {}) {
|
|
97
|
+
const backendCode = normalizeText(response?.body?.error, null);
|
|
98
|
+
const backendMessage = normalizeText(response?.body?.message, `claworld world membership ${action} failed`);
|
|
99
|
+
|
|
100
|
+
return createRuntimeBoundaryError({
|
|
101
|
+
code: backendCode || `claworld_world_membership_${action}_failed`,
|
|
102
|
+
category: inferHttpErrorCategory(response?.status),
|
|
103
|
+
status: response?.status ?? 500,
|
|
104
|
+
message: `claworld world membership ${action} failed: ${response?.status ?? 500}`,
|
|
105
|
+
publicMessage: backendMessage,
|
|
106
|
+
recoverable: Number(response?.status) >= 400 && Number(response?.status) < 500,
|
|
107
|
+
context: {
|
|
108
|
+
action: `world_membership_${action}`,
|
|
109
|
+
accountId,
|
|
110
|
+
...(worldId ? { worldId } : {}),
|
|
111
|
+
httpStatus: response?.status ?? 500,
|
|
112
|
+
...extractBackendErrorContext(response?.body),
|
|
113
|
+
},
|
|
114
|
+
});
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
export async function fetchWorldMemberships({
|
|
118
|
+
cfg = {},
|
|
119
|
+
accountId = null,
|
|
120
|
+
runtimeConfig = null,
|
|
121
|
+
agentId = null,
|
|
122
|
+
status = null,
|
|
123
|
+
includeInactive = false,
|
|
124
|
+
includeDisabled = true,
|
|
125
|
+
fetchImpl,
|
|
126
|
+
logger = console,
|
|
127
|
+
} = {}) {
|
|
128
|
+
if (typeof fetchImpl !== 'function') {
|
|
129
|
+
throw new Error('fetch is unavailable for claworld world membership helper');
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
const resolvedAgentId = normalizeText(agentId, null);
|
|
133
|
+
if (!resolvedAgentId) {
|
|
134
|
+
throw new Error('claworld world membership helper requires agentId');
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
const resolvedRuntimeConfig = runtimeConfig || resolveClaworldRuntimeConfig(cfg, accountId);
|
|
138
|
+
const baseUrl = normalizeRelayHttpBaseUrl(resolvedRuntimeConfig.serverUrl);
|
|
139
|
+
const requestUrl = new URL(`${baseUrl}/v1/world-memberships`);
|
|
140
|
+
requestUrl.searchParams.set('agentId', resolvedAgentId);
|
|
141
|
+
if (normalizeText(status, null)) requestUrl.searchParams.set('status', normalizeText(status, null));
|
|
142
|
+
if (includeInactive) requestUrl.searchParams.set('includeInactive', 'true');
|
|
143
|
+
requestUrl.searchParams.set('includeDisabled', includeDisabled ? 'true' : 'false');
|
|
144
|
+
const result = await fetchJson(fetchImpl, requestUrl.toString(), {
|
|
145
|
+
headers: buildRuntimeAuthHeaders(resolvedRuntimeConfig, {
|
|
146
|
+
accept: 'application/json',
|
|
147
|
+
...(resolvedRuntimeConfig.apiKey ? { 'x-api-key': resolvedRuntimeConfig.apiKey } : {}),
|
|
148
|
+
}),
|
|
149
|
+
});
|
|
150
|
+
|
|
151
|
+
if (!result.ok) {
|
|
152
|
+
logger.error?.('[claworld:membership] world memberships fetch failed', {
|
|
153
|
+
status: result.status,
|
|
154
|
+
accountId: resolvedRuntimeConfig.accountId || accountId || null,
|
|
155
|
+
body: result.body,
|
|
156
|
+
});
|
|
157
|
+
throw createWorldMembershipHttpError('list', result, {
|
|
158
|
+
accountId: resolvedRuntimeConfig.accountId || accountId || null,
|
|
159
|
+
});
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
return normalizeMembershipList(result.body);
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
export async function fetchWorldMembership({
|
|
166
|
+
cfg = {},
|
|
167
|
+
accountId = null,
|
|
168
|
+
runtimeConfig = null,
|
|
169
|
+
agentId = null,
|
|
170
|
+
worldId = null,
|
|
171
|
+
includeDisabled = true,
|
|
172
|
+
fetchImpl,
|
|
173
|
+
logger = console,
|
|
174
|
+
} = {}) {
|
|
175
|
+
if (typeof fetchImpl !== 'function') {
|
|
176
|
+
throw new Error('fetch is unavailable for claworld world membership helper');
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
const resolvedAgentId = normalizeText(agentId, null);
|
|
180
|
+
if (!resolvedAgentId) {
|
|
181
|
+
throw new Error('claworld world membership helper requires agentId');
|
|
182
|
+
}
|
|
183
|
+
const resolvedWorldId = normalizeText(worldId, null);
|
|
184
|
+
if (!resolvedWorldId) {
|
|
185
|
+
throw new Error('claworld world membership helper requires worldId');
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
const resolvedRuntimeConfig = runtimeConfig || resolveClaworldRuntimeConfig(cfg, accountId);
|
|
189
|
+
const baseUrl = normalizeRelayHttpBaseUrl(resolvedRuntimeConfig.serverUrl);
|
|
190
|
+
const requestUrl = new URL(`${baseUrl}/v1/worlds/${encodeURIComponent(resolvedWorldId)}/membership`);
|
|
191
|
+
requestUrl.searchParams.set('agentId', resolvedAgentId);
|
|
192
|
+
requestUrl.searchParams.set('includeDisabled', includeDisabled ? 'true' : 'false');
|
|
193
|
+
const result = await fetchJson(fetchImpl, requestUrl.toString(), {
|
|
194
|
+
headers: buildRuntimeAuthHeaders(resolvedRuntimeConfig, {
|
|
195
|
+
accept: 'application/json',
|
|
196
|
+
...(resolvedRuntimeConfig.apiKey ? { 'x-api-key': resolvedRuntimeConfig.apiKey } : {}),
|
|
197
|
+
}),
|
|
198
|
+
});
|
|
199
|
+
|
|
200
|
+
if (!result.ok) {
|
|
201
|
+
logger.error?.('[claworld:membership] world membership fetch failed', {
|
|
202
|
+
status: result.status,
|
|
203
|
+
worldId: resolvedWorldId,
|
|
204
|
+
accountId: resolvedRuntimeConfig.accountId || accountId || null,
|
|
205
|
+
body: result.body,
|
|
206
|
+
});
|
|
207
|
+
throw createWorldMembershipHttpError('get', result, {
|
|
208
|
+
accountId: resolvedRuntimeConfig.accountId || accountId || null,
|
|
209
|
+
worldId: resolvedWorldId,
|
|
210
|
+
});
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
return normalizeManagedWorldMembership(result.body);
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
export async function updateWorldMembershipProfile({
|
|
217
|
+
cfg = {},
|
|
218
|
+
accountId = null,
|
|
219
|
+
runtimeConfig = null,
|
|
220
|
+
agentId = null,
|
|
221
|
+
worldId = null,
|
|
222
|
+
participantContextText = null,
|
|
223
|
+
fetchImpl,
|
|
224
|
+
logger = console,
|
|
225
|
+
} = {}) {
|
|
226
|
+
if (typeof fetchImpl !== 'function') {
|
|
227
|
+
throw new Error('fetch is unavailable for claworld world membership helper');
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
const resolvedAgentId = normalizeText(agentId, null);
|
|
231
|
+
if (!resolvedAgentId) {
|
|
232
|
+
throw new Error('claworld world membership helper requires agentId');
|
|
233
|
+
}
|
|
234
|
+
const resolvedWorldId = normalizeText(worldId, null);
|
|
235
|
+
if (!resolvedWorldId) {
|
|
236
|
+
throw new Error('claworld world membership helper requires worldId');
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
const resolvedRuntimeConfig = runtimeConfig || resolveClaworldRuntimeConfig(cfg, accountId);
|
|
240
|
+
const baseUrl = normalizeRelayHttpBaseUrl(resolvedRuntimeConfig.serverUrl);
|
|
241
|
+
const result = await fetchJson(fetchImpl, `${baseUrl}/v1/worlds/${encodeURIComponent(resolvedWorldId)}/membership`, {
|
|
242
|
+
method: 'PATCH',
|
|
243
|
+
headers: buildRuntimeAuthHeaders(resolvedRuntimeConfig, {
|
|
244
|
+
accept: 'application/json',
|
|
245
|
+
'content-type': 'application/json',
|
|
246
|
+
...(resolvedRuntimeConfig.apiKey ? { 'x-api-key': resolvedRuntimeConfig.apiKey } : {}),
|
|
247
|
+
}),
|
|
248
|
+
body: JSON.stringify({
|
|
249
|
+
agentId: resolvedAgentId,
|
|
250
|
+
participantContextText: normalizeText(participantContextText, null),
|
|
251
|
+
}),
|
|
252
|
+
});
|
|
253
|
+
|
|
254
|
+
if (!result.ok) {
|
|
255
|
+
logger.error?.('[claworld:membership] world membership profile update failed', {
|
|
256
|
+
status: result.status,
|
|
257
|
+
worldId: resolvedWorldId,
|
|
258
|
+
accountId: resolvedRuntimeConfig.accountId || accountId || null,
|
|
259
|
+
body: result.body,
|
|
260
|
+
});
|
|
261
|
+
throw createWorldMembershipHttpError('update_profile', result, {
|
|
262
|
+
accountId: resolvedRuntimeConfig.accountId || accountId || null,
|
|
263
|
+
worldId: resolvedWorldId,
|
|
264
|
+
});
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
return normalizeManagedWorldMembership(result.body);
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
export async function leaveWorldMembership({
|
|
271
|
+
cfg = {},
|
|
272
|
+
accountId = null,
|
|
273
|
+
runtimeConfig = null,
|
|
274
|
+
agentId = null,
|
|
275
|
+
worldId = null,
|
|
276
|
+
fetchImpl,
|
|
277
|
+
logger = console,
|
|
278
|
+
} = {}) {
|
|
279
|
+
if (typeof fetchImpl !== 'function') {
|
|
280
|
+
throw new Error('fetch is unavailable for claworld world membership helper');
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
const resolvedAgentId = normalizeText(agentId, null);
|
|
284
|
+
if (!resolvedAgentId) {
|
|
285
|
+
throw new Error('claworld world membership helper requires agentId');
|
|
286
|
+
}
|
|
287
|
+
const resolvedWorldId = normalizeText(worldId, null);
|
|
288
|
+
if (!resolvedWorldId) {
|
|
289
|
+
throw new Error('claworld world membership helper requires worldId');
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
const resolvedRuntimeConfig = runtimeConfig || resolveClaworldRuntimeConfig(cfg, accountId);
|
|
293
|
+
const baseUrl = normalizeRelayHttpBaseUrl(resolvedRuntimeConfig.serverUrl);
|
|
294
|
+
const result = await fetchJson(fetchImpl, `${baseUrl}/v1/worlds/${encodeURIComponent(resolvedWorldId)}/membership/leave`, {
|
|
295
|
+
method: 'POST',
|
|
296
|
+
headers: buildRuntimeAuthHeaders(resolvedRuntimeConfig, {
|
|
297
|
+
accept: 'application/json',
|
|
298
|
+
'content-type': 'application/json',
|
|
299
|
+
...(resolvedRuntimeConfig.apiKey ? { 'x-api-key': resolvedRuntimeConfig.apiKey } : {}),
|
|
300
|
+
}),
|
|
301
|
+
body: JSON.stringify({
|
|
302
|
+
agentId: resolvedAgentId,
|
|
303
|
+
}),
|
|
304
|
+
});
|
|
305
|
+
|
|
306
|
+
if (!result.ok) {
|
|
307
|
+
logger.error?.('[claworld:membership] world membership leave failed', {
|
|
308
|
+
status: result.status,
|
|
309
|
+
worldId: resolvedWorldId,
|
|
310
|
+
accountId: resolvedRuntimeConfig.accountId || accountId || null,
|
|
311
|
+
body: result.body,
|
|
312
|
+
});
|
|
313
|
+
throw createWorldMembershipHttpError('leave', result, {
|
|
314
|
+
accountId: resolvedRuntimeConfig.accountId || accountId || null,
|
|
315
|
+
worldId: resolvedWorldId,
|
|
316
|
+
});
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
return normalizeManagedWorldMembership(result.body);
|
|
320
|
+
}
|
|
@@ -2,6 +2,7 @@ import { resolveClaworldRuntimeConfig } from '../plugin/config-schema.js';
|
|
|
2
2
|
import { buildRuntimeAuthHeaders } from '../plugin/account-identity.js';
|
|
3
3
|
import { createRuntimeBoundaryError } from '../../lib/runtime-errors.js';
|
|
4
4
|
import { extractBackendErrorContext } from './backend-error-context.js';
|
|
5
|
+
import { normalizeWorldJoinResponse } from './product-shell-helper.js';
|
|
5
6
|
|
|
6
7
|
function normalizeText(value, fallback = null) {
|
|
7
8
|
if (value == null) return fallback;
|
|
@@ -68,6 +69,29 @@ function normalizeWorldRole(worldRole, fallback = null) {
|
|
|
68
69
|
return ['owner', 'member'].includes(normalized) ? normalized : fallback;
|
|
69
70
|
}
|
|
70
71
|
|
|
72
|
+
function normalizeBroadcastAudience(value, fallback = 'members') {
|
|
73
|
+
const normalized = normalizeText(value, fallback);
|
|
74
|
+
if (normalized === 'admins') return 'admins';
|
|
75
|
+
if (normalized === 'admins_and_owner') return 'admins_and_owner';
|
|
76
|
+
return 'members';
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
function normalizeBroadcastReplyPolicy(value, fallback = 'zero') {
|
|
80
|
+
const normalized = normalizeText(value, fallback);
|
|
81
|
+
if (normalized === 'at_most_one') return 'at_most_one';
|
|
82
|
+
return 'zero';
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
function normalizeWorldBroadcastConfig(broadcast = null) {
|
|
86
|
+
if (!broadcast || typeof broadcast !== 'object' || Array.isArray(broadcast)) return null;
|
|
87
|
+
return {
|
|
88
|
+
enabled: normalizeOptionalBoolean(broadcast.enabled, null),
|
|
89
|
+
audience: normalizeBroadcastAudience(broadcast.audience, 'members'),
|
|
90
|
+
replyPolicy: normalizeBroadcastReplyPolicy(broadcast.replyPolicy, 'zero'),
|
|
91
|
+
excludeSelf: normalizeOptionalBoolean(broadcast.excludeSelf, null),
|
|
92
|
+
};
|
|
93
|
+
}
|
|
94
|
+
|
|
71
95
|
function normalizeManagedWorld(payload = {}) {
|
|
72
96
|
return {
|
|
73
97
|
worldId: normalizeText(payload.worldId, null),
|
|
@@ -81,10 +105,25 @@ function normalizeManagedWorld(payload = {}) {
|
|
|
81
105
|
createdAt: normalizeText(payload.createdAt, null),
|
|
82
106
|
updatedAt: normalizeText(payload.updatedAt, null),
|
|
83
107
|
participantContextField: normalizeParticipantContextFieldPayload(payload.participantContextField),
|
|
108
|
+
broadcast: normalizeWorldBroadcastConfig(payload.broadcast),
|
|
84
109
|
stats: normalizeWorldStats(payload.stats),
|
|
85
110
|
};
|
|
86
111
|
}
|
|
87
112
|
|
|
113
|
+
function normalizeCreatedWorld(payload = {}) {
|
|
114
|
+
const world = normalizeManagedWorld(payload);
|
|
115
|
+
return {
|
|
116
|
+
...world,
|
|
117
|
+
ownerJoin:
|
|
118
|
+
payload.ownerJoin && typeof payload.ownerJoin === 'object'
|
|
119
|
+
? normalizeWorldJoinResponse(payload.ownerJoin, {
|
|
120
|
+
worldId: world.worldId,
|
|
121
|
+
agentId: world.ownerAgentId,
|
|
122
|
+
})
|
|
123
|
+
: null,
|
|
124
|
+
};
|
|
125
|
+
}
|
|
126
|
+
|
|
88
127
|
function normalizeOwnedWorldSummary(payload = {}) {
|
|
89
128
|
return {
|
|
90
129
|
worldId: normalizeText(payload.worldId, null),
|
|
@@ -96,10 +135,62 @@ function normalizeOwnedWorldSummary(payload = {}) {
|
|
|
96
135
|
worldRole: normalizeWorldRole(payload.worldRole, null),
|
|
97
136
|
createdAt: normalizeText(payload.createdAt, null),
|
|
98
137
|
updatedAt: normalizeText(payload.updatedAt, null),
|
|
138
|
+
broadcast: normalizeWorldBroadcastConfig(payload.broadcast),
|
|
99
139
|
stats: normalizeWorldStats(payload.stats),
|
|
100
140
|
};
|
|
101
141
|
}
|
|
102
142
|
|
|
143
|
+
function normalizeWorldBroadcastRequestItem(item = {}) {
|
|
144
|
+
return {
|
|
145
|
+
agentId: normalizeText(item.agentId, null),
|
|
146
|
+
status: normalizeText(item.status, null),
|
|
147
|
+
verdict: normalizeText(item.verdict, null),
|
|
148
|
+
chatRequest: item.chatRequest && typeof item.chatRequest === 'object' && !Array.isArray(item.chatRequest)
|
|
149
|
+
? item.chatRequest
|
|
150
|
+
: null,
|
|
151
|
+
kickoff: item.kickoff && typeof item.kickoff === 'object' && !Array.isArray(item.kickoff)
|
|
152
|
+
? item.kickoff
|
|
153
|
+
: null,
|
|
154
|
+
};
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
function normalizeWorldBroadcastFailureItem(item = {}) {
|
|
158
|
+
return {
|
|
159
|
+
agentId: normalizeText(item.agentId, null),
|
|
160
|
+
status: normalizeText(item.status, 'failed'),
|
|
161
|
+
httpStatus: normalizeOptionalInteger(item.httpStatus, null),
|
|
162
|
+
error: normalizeText(item.error, null),
|
|
163
|
+
reason: normalizeText(item.reason, null),
|
|
164
|
+
message: normalizeText(item.message, null),
|
|
165
|
+
};
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
function normalizeWorldBroadcastResponse(payload = {}) {
|
|
169
|
+
return {
|
|
170
|
+
status: normalizeText(payload.status, null),
|
|
171
|
+
worldId: normalizeText(payload.worldId, null),
|
|
172
|
+
senderAgentId: normalizeText(payload.senderAgentId, null),
|
|
173
|
+
senderRole: normalizeWorldRole(payload.senderRole, null),
|
|
174
|
+
audience: normalizeBroadcastAudience(payload.audience, 'members'),
|
|
175
|
+
excludeSelf: normalizeOptionalBoolean(payload.excludeSelf, null),
|
|
176
|
+
eligibility: normalizeText(payload.eligibility, null),
|
|
177
|
+
broadcastId: normalizeText(payload.broadcastId, null),
|
|
178
|
+
totalTargets: normalizeOptionalInteger(payload.totalTargets, null),
|
|
179
|
+
createdCount: normalizeOptionalInteger(payload.createdCount, null),
|
|
180
|
+
failedCount: normalizeOptionalInteger(payload.failedCount, null),
|
|
181
|
+
pendingCount: normalizeOptionalInteger(payload.pendingCount, null),
|
|
182
|
+
autoAcceptedCount: normalizeOptionalInteger(payload.autoAcceptedCount, null),
|
|
183
|
+
rejectedCount: normalizeOptionalInteger(payload.rejectedCount, null),
|
|
184
|
+
nextAction: normalizeText(payload.nextAction, null),
|
|
185
|
+
requests: Array.isArray(payload.requests)
|
|
186
|
+
? payload.requests.map((item) => normalizeWorldBroadcastRequestItem(item))
|
|
187
|
+
: [],
|
|
188
|
+
failures: Array.isArray(payload.failures)
|
|
189
|
+
? payload.failures.map((item) => normalizeWorldBroadcastFailureItem(item))
|
|
190
|
+
: [],
|
|
191
|
+
};
|
|
192
|
+
}
|
|
193
|
+
|
|
103
194
|
async function fetchJson(fetchImpl, url, init = {}) {
|
|
104
195
|
let response;
|
|
105
196
|
try {
|
|
@@ -174,6 +265,7 @@ export async function createModeratedWorld({
|
|
|
174
265
|
agentId = null,
|
|
175
266
|
displayName = null,
|
|
176
267
|
worldContextText = null,
|
|
268
|
+
participantContextText = null,
|
|
177
269
|
enabled = true,
|
|
178
270
|
fetchImpl,
|
|
179
271
|
logger = console,
|
|
@@ -200,6 +292,7 @@ export async function createModeratedWorld({
|
|
|
200
292
|
agentId: resolvedAgentId,
|
|
201
293
|
displayName,
|
|
202
294
|
worldContextText,
|
|
295
|
+
participantContextText: normalizeText(participantContextText, null),
|
|
203
296
|
enabled,
|
|
204
297
|
}),
|
|
205
298
|
});
|
|
@@ -215,7 +308,7 @@ export async function createModeratedWorld({
|
|
|
215
308
|
});
|
|
216
309
|
}
|
|
217
310
|
|
|
218
|
-
return
|
|
311
|
+
return normalizeCreatedWorld(created.body);
|
|
219
312
|
}
|
|
220
313
|
|
|
221
314
|
export async function fetchOwnedWorlds({
|
|
@@ -349,3 +442,67 @@ export async function manageModeratedWorld({
|
|
|
349
442
|
|
|
350
443
|
return normalizeManagedWorld(result.body);
|
|
351
444
|
}
|
|
445
|
+
|
|
446
|
+
export async function broadcastModeratedWorld({
|
|
447
|
+
cfg = {},
|
|
448
|
+
accountId = null,
|
|
449
|
+
runtimeConfig = null,
|
|
450
|
+
agentId = null,
|
|
451
|
+
worldId = null,
|
|
452
|
+
announcementText = null,
|
|
453
|
+
audience = null,
|
|
454
|
+
excludeSelf = null,
|
|
455
|
+
fetchImpl,
|
|
456
|
+
logger = console,
|
|
457
|
+
} = {}) {
|
|
458
|
+
if (typeof fetchImpl !== 'function') {
|
|
459
|
+
throw new Error('fetch is unavailable for claworld world broadcast helper');
|
|
460
|
+
}
|
|
461
|
+
|
|
462
|
+
const resolvedAgentId = normalizeText(agentId, null);
|
|
463
|
+
if (!resolvedAgentId) {
|
|
464
|
+
throw new Error('claworld world broadcast helper requires agentId');
|
|
465
|
+
}
|
|
466
|
+
const resolvedWorldId = normalizeText(worldId, null);
|
|
467
|
+
if (!resolvedWorldId) {
|
|
468
|
+
throw new Error('claworld world broadcast helper requires worldId');
|
|
469
|
+
}
|
|
470
|
+
const resolvedAnnouncementText = normalizeText(announcementText, null);
|
|
471
|
+
if (!resolvedAnnouncementText) {
|
|
472
|
+
throw new Error('claworld world broadcast helper requires announcementText');
|
|
473
|
+
}
|
|
474
|
+
|
|
475
|
+
const resolvedRuntimeConfig = runtimeConfig || resolveClaworldRuntimeConfig(cfg, accountId);
|
|
476
|
+
const baseUrl = normalizeRelayHttpBaseUrl(resolvedRuntimeConfig.serverUrl);
|
|
477
|
+
const result = await fetchJson(fetchImpl, `${baseUrl}/v1/worlds/${encodeURIComponent(resolvedWorldId)}/broadcast`, {
|
|
478
|
+
method: 'POST',
|
|
479
|
+
headers: buildRuntimeAuthHeaders(resolvedRuntimeConfig, {
|
|
480
|
+
accept: 'application/json',
|
|
481
|
+
'content-type': 'application/json',
|
|
482
|
+
...(resolvedRuntimeConfig.apiKey ? { 'x-api-key': resolvedRuntimeConfig.apiKey } : {}),
|
|
483
|
+
}),
|
|
484
|
+
body: JSON.stringify({
|
|
485
|
+
agentId: resolvedAgentId,
|
|
486
|
+
payload: {
|
|
487
|
+
text: resolvedAnnouncementText,
|
|
488
|
+
},
|
|
489
|
+
...(normalizeText(audience, null) ? { audience: normalizeText(audience, null) } : {}),
|
|
490
|
+
...(typeof excludeSelf === 'boolean' ? { excludeSelf } : {}),
|
|
491
|
+
}),
|
|
492
|
+
});
|
|
493
|
+
|
|
494
|
+
if (!result.ok) {
|
|
495
|
+
logger.error?.('[claworld:moderation] world broadcast failed', {
|
|
496
|
+
status: result.status,
|
|
497
|
+
worldId: resolvedWorldId,
|
|
498
|
+
accountId: resolvedRuntimeConfig.accountId || accountId || null,
|
|
499
|
+
body: result.body,
|
|
500
|
+
});
|
|
501
|
+
throw createModerationHttpError('broadcast', result, {
|
|
502
|
+
accountId: resolvedRuntimeConfig.accountId || accountId || null,
|
|
503
|
+
worldId: resolvedWorldId,
|
|
504
|
+
});
|
|
505
|
+
}
|
|
506
|
+
|
|
507
|
+
return normalizeWorldBroadcastResponse(result.body);
|
|
508
|
+
}
|
|
@@ -259,6 +259,11 @@ function normalizeDeliveryReason(reason = {}) {
|
|
|
259
259
|
};
|
|
260
260
|
}
|
|
261
261
|
|
|
262
|
+
function normalizeWorldRole(worldRole, fallback = null) {
|
|
263
|
+
const normalized = normalizeText(worldRole, fallback);
|
|
264
|
+
return ['owner', 'member'].includes(normalized) ? normalized : fallback;
|
|
265
|
+
}
|
|
266
|
+
|
|
262
267
|
function normalizeCandidate(candidate = {}, index = 0) {
|
|
263
268
|
const normalizedRank = normalizeNumber(candidate.rank, null);
|
|
264
269
|
const displayName = normalizeText(
|
|
@@ -280,6 +285,7 @@ function normalizeCandidate(candidate = {}, index = 0) {
|
|
|
280
285
|
return {
|
|
281
286
|
candidateId: normalizeText(candidate.candidateId, `candidate_${index + 1}`),
|
|
282
287
|
worldId: normalizeText(candidate.worldId, 'unknown-world'),
|
|
288
|
+
worldRole: normalizeWorldRole(candidate.worldRole, null),
|
|
283
289
|
sourceMembershipId: normalizeText(candidate.sourceMembershipId, null),
|
|
284
290
|
online: candidate.online === true,
|
|
285
291
|
displayName,
|
|
@@ -622,6 +628,7 @@ export function buildCandidateDeliverySummary(candidateFeed = {}, { worldDetail
|
|
|
622
628
|
.filter(Boolean);
|
|
623
629
|
const deliveryReasonSummary = sentenceCase(candidate.deliveryReason.summary, '');
|
|
624
630
|
const availabilitySummary = candidate.online === true ? 'Online now.' : 'Currently offline.';
|
|
631
|
+
const roleSummary = candidate.worldRole ? `World role: ${candidate.worldRole}.` : null;
|
|
625
632
|
const scoreSummary = candidate.score == null
|
|
626
633
|
? null
|
|
627
634
|
: `Score ${candidate.score}${candidate.rank == null ? '' : `, rank ${candidate.rank}`}.`;
|
|
@@ -631,6 +638,7 @@ export function buildCandidateDeliverySummary(candidateFeed = {}, { worldDetail
|
|
|
631
638
|
optionalFieldSummary.length > 0 ? `Optional context: ${optionalFieldSummary.join('; ')}.` : null,
|
|
632
639
|
compatibilitySummary.length > 0 ? compatibilitySummary.join(' ') : null,
|
|
633
640
|
deliveryReasonSummary || null,
|
|
641
|
+
roleSummary,
|
|
634
642
|
availabilitySummary,
|
|
635
643
|
scoreSummary,
|
|
636
644
|
].filter(Boolean).join(' ');
|
|
@@ -639,6 +647,7 @@ export function buildCandidateDeliverySummary(candidateFeed = {}, { worldDetail
|
|
|
639
647
|
candidateId: candidate.candidateId,
|
|
640
648
|
sourceMembershipId: candidate.sourceMembershipId,
|
|
641
649
|
online: candidate.online === true,
|
|
650
|
+
worldRole: candidate.worldRole,
|
|
642
651
|
agentCode: candidate.agentCode,
|
|
643
652
|
requestChat: candidate.requestChat,
|
|
644
653
|
displayName: name,
|