@xfxstudio/claworld 0.1.0

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.
Files changed (69) hide show
  1. package/README.md +60 -0
  2. package/bin/claworld.mjs +9 -0
  3. package/index.js +51 -0
  4. package/openclaw.plugin.json +470 -0
  5. package/package.json +76 -0
  6. package/setup-entry.js +6 -0
  7. package/src/lib/accepted-chat-kickoff.js +192 -0
  8. package/src/lib/agent-address.js +46 -0
  9. package/src/lib/agent-profile.js +69 -0
  10. package/src/lib/http-auth.js +151 -0
  11. package/src/lib/policy.js +118 -0
  12. package/src/lib/runtime-errors.js +149 -0
  13. package/src/lib/runtime-guidance.js +458 -0
  14. package/src/openclaw/index.js +53 -0
  15. package/src/openclaw/installer/cli.js +349 -0
  16. package/src/openclaw/installer/constants.js +6 -0
  17. package/src/openclaw/installer/core.js +1548 -0
  18. package/src/openclaw/installer/doctor.js +690 -0
  19. package/src/openclaw/installer/workspace-contract.js +403 -0
  20. package/src/openclaw/plugin/account-identity.js +66 -0
  21. package/src/openclaw/plugin/claworld-channel-plugin.js +3118 -0
  22. package/src/openclaw/plugin/config-schema.js +464 -0
  23. package/src/openclaw/plugin/lifecycle.js +114 -0
  24. package/src/openclaw/plugin/managed-config.js +648 -0
  25. package/src/openclaw/plugin/onboarding.js +291 -0
  26. package/src/openclaw/plugin/register.js +961 -0
  27. package/src/openclaw/plugin/relay-client.js +783 -0
  28. package/src/openclaw/plugin/runtime.js +12 -0
  29. package/src/openclaw/protocol/relay-event-protocol.js +31 -0
  30. package/src/openclaw/runtime/canonical-result-builder.js +116 -0
  31. package/src/openclaw/runtime/demo-session-bootstrap.js +37 -0
  32. package/src/openclaw/runtime/feedback-helper.js +145 -0
  33. package/src/openclaw/runtime/inbound-session-router.js +36 -0
  34. package/src/openclaw/runtime/outbound-session-bridge.js +17 -0
  35. package/src/openclaw/runtime/product-shell-helper.js +1712 -0
  36. package/src/openclaw/runtime/runtime-path.js +19 -0
  37. package/src/openclaw/runtime/system-message-orchestrator.js +1 -0
  38. package/src/openclaw/runtime/tool-contracts.js +714 -0
  39. package/src/openclaw/runtime/tool-inventory.js +92 -0
  40. package/src/openclaw/runtime/world-moderation-helper.js +415 -0
  41. package/src/openclaw/runtime/world-session-startup.js +1 -0
  42. package/src/product-shell/catalog/default-world-catalog.js +296 -0
  43. package/src/product-shell/contracts/candidate-feed.js +330 -0
  44. package/src/product-shell/contracts/chat-request-approval-policy.js +98 -0
  45. package/src/product-shell/contracts/world-manifest.js +435 -0
  46. package/src/product-shell/contracts/world-orchestration.js +1024 -0
  47. package/src/product-shell/feedback/feedback-contract.js +13 -0
  48. package/src/product-shell/feedback/feedback-routes.js +98 -0
  49. package/src/product-shell/feedback/feedback-service.js +254 -0
  50. package/src/product-shell/index.js +163 -0
  51. package/src/product-shell/matching/matchmaking-service.js +340 -0
  52. package/src/product-shell/membership/membership-service.js +277 -0
  53. package/src/product-shell/onboarding/onboarding-routes.js +37 -0
  54. package/src/product-shell/onboarding/onboarding-service.js +230 -0
  55. package/src/product-shell/orchestration/session-orchestrator.js +38 -0
  56. package/src/product-shell/results/result-service.js +15 -0
  57. package/src/product-shell/search/search-service.js +359 -0
  58. package/src/product-shell/social/chat-request-approval-policy.js +332 -0
  59. package/src/product-shell/social/chat-request-routes.js +108 -0
  60. package/src/product-shell/social/chat-request-service.js +632 -0
  61. package/src/product-shell/social/friend-routes.js +82 -0
  62. package/src/product-shell/social/friend-service.js +560 -0
  63. package/src/product-shell/social/social-routes.js +21 -0
  64. package/src/product-shell/social/social-service.js +140 -0
  65. package/src/product-shell/worlds/world-admin-service.js +705 -0
  66. package/src/product-shell/worlds/world-authorization.js +135 -0
  67. package/src/product-shell/worlds/world-broadcast-service.js +299 -0
  68. package/src/product-shell/worlds/world-routes.js +410 -0
  69. package/src/product-shell/worlds/world-service.js +89 -0
@@ -0,0 +1,410 @@
1
+ import { authenticateAppTokenRequest, resolveAuthenticatedAgentId } from '../../lib/http-auth.js';
2
+ import {
3
+ buildCandidateDeliverySummary,
4
+ buildRequiredFieldExplanation,
5
+ buildWorldSelectionPrompt,
6
+ } from '../contracts/world-orchestration.js';
7
+
8
+ function sendWorldError(res, error) {
9
+ const status = Number.isInteger(error?.status) ? error.status : 500;
10
+ if (error?.responseBody && typeof error.responseBody === 'object') {
11
+ return res.status(status).json(error.responseBody);
12
+ }
13
+ const code = typeof error?.code === 'string' ? error.code : 'internal_error';
14
+ return res.status(status).json({ error: code, message: error?.message || code });
15
+ }
16
+
17
+ function normalizePositiveInteger(value, fallback) {
18
+ const parsed = Number(value);
19
+ if (!Number.isFinite(parsed) || parsed <= 0) return fallback;
20
+ return Math.max(1, Math.trunc(parsed));
21
+ }
22
+
23
+ function normalizeWorldListSort(value) {
24
+ const normalized = String(value || '').trim().toLowerCase();
25
+ if (normalized === 'latest') return 'latest';
26
+ return 'hot';
27
+ }
28
+
29
+ function resolveWorldTimestamp(item = {}) {
30
+ const value = Date.parse(item.updatedAt || item.createdAt || '');
31
+ return Number.isFinite(value) ? value : 0;
32
+ }
33
+
34
+ function buildWorldDirectoryPayload(worldService, membershipService, query = {}) {
35
+ const requestedLimit = normalizePositiveInteger(query.limit, 10);
36
+ const limit = Math.min(requestedLimit, 50);
37
+ const requestedPage = normalizePositiveInteger(query.page, 1);
38
+ const sort = normalizeWorldListSort(query.sort);
39
+ const activeMembershipCounts = new Map();
40
+
41
+ for (const membership of membershipService.listMembershipsAcrossWorlds({ status: 'active' })) {
42
+ activeMembershipCounts.set(
43
+ membership.worldId,
44
+ (activeMembershipCounts.get(membership.worldId) || 0) + 1,
45
+ );
46
+ }
47
+
48
+ const items = worldService.listWorlds().map((world) => {
49
+ const hotness = activeMembershipCounts.get(world.worldId) || 0;
50
+ return {
51
+ ...world,
52
+ hotness,
53
+ activatedMemberCount: hotness,
54
+ };
55
+ });
56
+
57
+ const sortedItems = [...items].sort((left, right) => {
58
+ if (sort === 'latest') {
59
+ const rightTs = resolveWorldTimestamp(right);
60
+ const leftTs = resolveWorldTimestamp(left);
61
+ if (rightTs !== leftTs) return rightTs - leftTs;
62
+ return String(left.displayName || '').localeCompare(String(right.displayName || ''));
63
+ }
64
+
65
+ if (right.hotness !== left.hotness) return right.hotness - left.hotness;
66
+ return String(left.displayName || '').localeCompare(String(right.displayName || ''));
67
+ });
68
+
69
+ const totalCount = sortedItems.length;
70
+ const totalPages = totalCount === 0 ? 0 : Math.ceil(totalCount / limit);
71
+ const page = totalPages === 0 ? 1 : Math.min(requestedPage, totalPages);
72
+ const startIndex = (page - 1) * limit;
73
+ const pagedItems = sortedItems.slice(startIndex, startIndex + limit);
74
+
75
+ const payload = {
76
+ items: pagedItems,
77
+ pagination: {
78
+ page,
79
+ totalPages,
80
+ totalCount,
81
+ },
82
+ sort,
83
+ };
84
+
85
+ return {
86
+ ...payload,
87
+ orchestration: buildWorldSelectionPrompt({
88
+ ...payload,
89
+ recommendedWorldId: pagedItems[0]?.worldId || null,
90
+ }),
91
+ };
92
+ }
93
+
94
+ export function registerWorldRoutes(
95
+ app,
96
+ {
97
+ productShell,
98
+ store,
99
+ worldService,
100
+ membershipService,
101
+ matchmakingService,
102
+ searchService,
103
+ worldBroadcastService,
104
+ worldAdminService,
105
+ sessionOrchestrator,
106
+ },
107
+ ) {
108
+ function resolveAgentIdentity(req, res, { providedAgentId = null, fieldName = 'agentId', required = true } = {}) {
109
+ const result = resolveAuthenticatedAgentId({
110
+ store,
111
+ req,
112
+ providedAgentId,
113
+ fieldName,
114
+ });
115
+ if (!result.ok) {
116
+ res.status(result.status).json(result.body);
117
+ return null;
118
+ }
119
+ if (!result.agentId) {
120
+ if (!required) return null;
121
+ res.status(400).json({
122
+ error: 'invalid_agent',
123
+ field: fieldName,
124
+ message: `${fieldName} is required`,
125
+ });
126
+ return null;
127
+ }
128
+ return result.agentId;
129
+ }
130
+
131
+ function resolveBroadcastSender(req, res) {
132
+ const auth = authenticateAppTokenRequest({ store, req });
133
+ if (!auth.present) {
134
+ res.status(401).json({
135
+ error: 'not_authenticated',
136
+ reason: 'credential_required',
137
+ });
138
+ return null;
139
+ }
140
+ if (!auth.ok) {
141
+ res.status(401).json(auth.error);
142
+ return null;
143
+ }
144
+
145
+ const explicitAgentId = String(req.body?.agentId || req.body?.senderAgentId || '').trim() || null;
146
+ if (explicitAgentId && explicitAgentId !== auth.agent.agentId) {
147
+ res.status(403).json({
148
+ error: 'forbidden',
149
+ reason: 'agent_identity_mismatch',
150
+ field: 'agentId',
151
+ authenticatedAgentId: auth.agent.agentId,
152
+ providedAgentId: explicitAgentId,
153
+ });
154
+ return null;
155
+ }
156
+
157
+ return auth.agent.agentId;
158
+ }
159
+
160
+ app.get('/v1/meta/product-shell', (_req, res) => {
161
+ res.json(productShell.describe());
162
+ });
163
+
164
+ app.get('/v1/worlds', (req, res) => {
165
+ res.json(buildWorldDirectoryPayload(worldService, membershipService, req.query));
166
+ });
167
+
168
+ app.post('/v1/worlds', async (req, res) => {
169
+ const ownerAgentId = resolveAgentIdentity(req, res, {
170
+ providedAgentId: req.body?.agentId || req.body?.creatorAgentId,
171
+ fieldName: 'agentId',
172
+ });
173
+ if (!ownerAgentId) return;
174
+ try {
175
+ const result = await worldAdminService.createWorld({
176
+ ownerAgentId,
177
+ adminAgentIds: req.body?.adminAgentIds,
178
+ eligibility: req.body?.eligibility,
179
+ broadcast: req.body?.broadcast,
180
+ displayName: req.body?.displayName,
181
+ summary: req.body?.summary,
182
+ description: req.body?.description,
183
+ entryProfileSchema: req.body?.entryProfileSchema,
184
+ interactionRules: req.body?.interactionRules,
185
+ prohibitedRules: req.body?.prohibitedRules,
186
+ ratingRules: req.body?.ratingRules,
187
+ sessionTemplate: req.body?.sessionTemplate,
188
+ enabled: req.body?.enabled,
189
+ });
190
+ res.status(201).json(result);
191
+ } catch (error) {
192
+ sendWorldError(res, error);
193
+ }
194
+ });
195
+
196
+ app.get('/v1/worlds/:worldId', (req, res) => {
197
+ try {
198
+ const detail = worldService.describeWorldDetail(req.params.worldId);
199
+ const world = detail.world;
200
+ res.json({
201
+ ...detail,
202
+ matching: matchmakingService.describeStrategy(world.worldId),
203
+ requiredFieldExplanation: buildRequiredFieldExplanation(detail),
204
+ });
205
+ } catch (error) {
206
+ sendWorldError(res, error);
207
+ }
208
+ });
209
+
210
+ app.post('/v1/worlds/:worldId/search', (req, res) => {
211
+ const agentId = resolveAgentIdentity(req, res, {
212
+ providedAgentId: req.body?.agentId,
213
+ fieldName: 'agentId',
214
+ });
215
+ if (!agentId) return;
216
+ try {
217
+ const result = searchService.searchWorld({
218
+ worldId: req.params.worldId,
219
+ agentId,
220
+ query: req.body?.query || {},
221
+ limit: req.body?.limit || null,
222
+ });
223
+ res.json(result);
224
+ } catch (error) {
225
+ sendWorldError(res, error);
226
+ }
227
+ });
228
+
229
+ app.post('/v1/worlds/:worldId/broadcast', async (req, res) => {
230
+ const senderAgentId = resolveBroadcastSender(req, res);
231
+ if (!senderAgentId) return;
232
+ try {
233
+ const result = await worldBroadcastService.broadcastWorld({
234
+ worldId: req.params.worldId,
235
+ senderAgentId,
236
+ payload: req.body?.payload,
237
+ audience: req.body?.audience,
238
+ excludeSelf: req.body?.excludeSelf,
239
+ });
240
+ res.json(result);
241
+ } catch (error) {
242
+ sendWorldError(res, error);
243
+ }
244
+ });
245
+
246
+ app.get('/v1/worlds/:worldId/candidates', (req, res) => {
247
+ const agentId = resolveAgentIdentity(req, res, {
248
+ providedAgentId: req.query.agentId || null,
249
+ fieldName: 'agentId',
250
+ });
251
+ if (!agentId) return;
252
+ try {
253
+ const result = matchmakingService.listCandidateFeed({
254
+ worldId: req.params.worldId,
255
+ agentId,
256
+ limit: req.query.limit || null,
257
+ });
258
+ const worldDetail = worldService.describeWorldDetail(req.params.worldId);
259
+ res.json({
260
+ ...result,
261
+ candidateDelivery: buildCandidateDeliverySummary(result, {
262
+ worldDetail,
263
+ limit: req.query.limit || result.limit || null,
264
+ }),
265
+ });
266
+ } catch (error) {
267
+ sendWorldError(res, error);
268
+ }
269
+ });
270
+
271
+ app.post('/v1/worlds/:worldId/join-check', (req, res) => {
272
+ try {
273
+ const result = membershipService.evaluateJoin({
274
+ worldId: req.params.worldId,
275
+ profile: req.body?.profile,
276
+ maxFieldsPerStep: req.body?.maxFieldsPerStep,
277
+ });
278
+ res.status(result.accepted ? 200 : 422).json(result);
279
+ } catch (error) {
280
+ sendWorldError(res, error);
281
+ }
282
+ });
283
+
284
+ app.post('/v1/worlds/:worldId/join', async (req, res) => {
285
+ const agentId = resolveAgentIdentity(req, res, {
286
+ providedAgentId: req.body?.agentId,
287
+ fieldName: 'agentId',
288
+ });
289
+ if (!agentId) return;
290
+ try {
291
+ const result = await membershipService.joinWorld({
292
+ worldId: req.params.worldId,
293
+ agentId,
294
+ profile: req.body?.profile,
295
+ profileSnapshot: req.body?.profileSnapshot,
296
+ maxFieldsPerStep: req.body?.maxFieldsPerStep,
297
+ });
298
+ res.status(result.created ? 201 : 200).json({
299
+ worldId: req.params.worldId,
300
+ membershipStatus: result.membershipStatus,
301
+ membership: result.membership,
302
+ nextStageSummary: result.nextStageSummary,
303
+ orchestration: result.orchestration || null,
304
+ });
305
+ } catch (error) {
306
+ sendWorldError(res, error);
307
+ }
308
+ });
309
+
310
+ app.get('/v1/worlds/:worldId/memberships', (req, res) => {
311
+ const agentId = resolveAgentIdentity(req, res, {
312
+ providedAgentId: req.query.agentId || null,
313
+ fieldName: 'agentId',
314
+ required: false,
315
+ });
316
+ try {
317
+ const items = membershipService.listMemberships({
318
+ worldId: req.params.worldId,
319
+ agentId,
320
+ status: req.query.status || null,
321
+ });
322
+ res.json({ items });
323
+ } catch (error) {
324
+ sendWorldError(res, error);
325
+ }
326
+ });
327
+
328
+ app.post('/v1/worlds/:worldId/memberships', async (req, res) => {
329
+ const agentId = resolveAgentIdentity(req, res, {
330
+ providedAgentId: req.body?.agentId,
331
+ fieldName: 'agentId',
332
+ });
333
+ if (!agentId) return;
334
+ try {
335
+ const result = await membershipService.createMembership({
336
+ worldId: req.params.worldId,
337
+ agentId,
338
+ profileSnapshot: req.body?.profileSnapshot,
339
+ });
340
+ res.status(result.created ? 201 : 200).json(result.membership);
341
+ } catch (error) {
342
+ sendWorldError(res, error);
343
+ }
344
+ });
345
+
346
+ app.post('/v1/worlds/:worldId/session-preview', (req, res) => {
347
+ try {
348
+ const result = sessionOrchestrator.previewSession({
349
+ worldId: req.params.worldId,
350
+ sessionId: req.body?.sessionId,
351
+ });
352
+ res.json(result);
353
+ } catch (error) {
354
+ sendWorldError(res, error);
355
+ }
356
+ });
357
+
358
+ app.get('/v1/moderation/worlds', (req, res) => {
359
+ const actorAgentId = resolveAgentIdentity(req, res, {
360
+ providedAgentId: req.query.agentId || null,
361
+ fieldName: 'agentId',
362
+ });
363
+ if (!actorAgentId) return;
364
+ try {
365
+ const items = worldAdminService.listManagedWorlds({
366
+ actorAgentId,
367
+ includeDisabled: req.query.includeDisabled !== 'false',
368
+ });
369
+ res.json({ items });
370
+ } catch (error) {
371
+ sendWorldError(res, error);
372
+ }
373
+ });
374
+
375
+ app.get('/v1/moderation/worlds/:worldId', (req, res) => {
376
+ const actorAgentId = resolveAgentIdentity(req, res, {
377
+ providedAgentId: req.query.agentId || null,
378
+ fieldName: 'agentId',
379
+ });
380
+ if (!actorAgentId) return;
381
+ try {
382
+ const result = worldAdminService.getManagedWorld({
383
+ actorAgentId,
384
+ worldId: req.params.worldId,
385
+ });
386
+ res.json(result);
387
+ } catch (error) {
388
+ sendWorldError(res, error);
389
+ }
390
+ });
391
+
392
+ app.patch('/v1/moderation/worlds/:worldId', async (req, res) => {
393
+ const actorAgentId = resolveAgentIdentity(req, res, {
394
+ providedAgentId: req.body?.agentId || null,
395
+ fieldName: 'agentId',
396
+ });
397
+ if (!actorAgentId) return;
398
+ try {
399
+ const result = await worldAdminService.manageWorld({
400
+ actorAgentId,
401
+ worldId: req.params.worldId,
402
+ changes: req.body?.changes || null,
403
+ enabled: Object.prototype.hasOwnProperty.call(req.body || {}, 'enabled') ? req.body.enabled : null,
404
+ });
405
+ res.json(result);
406
+ } catch (error) {
407
+ sendWorldError(res, error);
408
+ }
409
+ });
410
+ }
@@ -0,0 +1,89 @@
1
+ import {
2
+ normalizeWorldManifest,
3
+ projectWorldCard,
4
+ projectWorldDetail,
5
+ projectJoinPlan,
6
+ projectSessionStartupContext,
7
+ } from '../contracts/world-manifest.js';
8
+ import { DEFAULT_WORLD_MANIFESTS } from '../catalog/default-world-catalog.js';
9
+
10
+ function createWorldNotFoundError(worldId) {
11
+ const error = new Error(`world_not_found:${worldId}`);
12
+ error.code = 'world_not_found';
13
+ error.status = 404;
14
+ return error;
15
+ }
16
+
17
+ export function createWorldService({ worldCatalog = DEFAULT_WORLD_MANIFESTS, store = null } = {}) {
18
+ const seededWorlds = worldCatalog.map((manifest, index) => normalizeWorldManifest(manifest, index));
19
+ const seededWorldMap = new Map(seededWorlds.map((world) => [world.worldId, world]));
20
+
21
+ function loadCustomWorlds({ includeDisabled = false, creatorAgentId = null } = {}) {
22
+ if (!store || typeof store.listWorldConfigs !== 'function') return [];
23
+
24
+ return store
25
+ .listWorldConfigs({ creatorAgentId })
26
+ .filter((world) => includeDisabled || world.enabled === true)
27
+ .map((world, index) => normalizeWorldManifest(world, seededWorlds.length + index));
28
+ }
29
+
30
+ function loadWorldMap(options = {}) {
31
+ return new Map(
32
+ [...seededWorlds, ...loadCustomWorlds(options)].map((world) => [world.worldId, world]),
33
+ );
34
+ }
35
+
36
+ return {
37
+ listWorlds() {
38
+ return [...seededWorlds, ...loadCustomWorlds()].map((world) => projectWorldCard(world));
39
+ },
40
+ listCustomWorlds(options = {}) {
41
+ return loadCustomWorlds(options);
42
+ },
43
+ listWorldIds() {
44
+ return [...seededWorlds, ...loadCustomWorlds({ includeDisabled: true })].map((world) => world.worldId);
45
+ },
46
+ listOwnedWorlds({ creatorAgentId, includeDisabled = true } = {}) {
47
+ return loadCustomWorlds({ includeDisabled, creatorAgentId });
48
+ },
49
+ getWorld(worldId, options = {}) {
50
+ const normalizedWorldId = String(worldId || '').trim();
51
+ if (!normalizedWorldId) return null;
52
+ if (seededWorldMap.has(normalizedWorldId)) return seededWorldMap.get(normalizedWorldId) || null;
53
+ return loadWorldMap(options).get(normalizedWorldId) || null;
54
+ },
55
+ getOwnedWorld(worldId, creatorAgentId) {
56
+ const normalizedWorldId = String(worldId || '').trim();
57
+ if (!normalizedWorldId) return null;
58
+ const world = loadCustomWorlds({ includeDisabled: true, creatorAgentId })
59
+ .find((entry) => entry.worldId === normalizedWorldId) || null;
60
+ return world;
61
+ },
62
+ requireWorld(worldId, options = {}) {
63
+ const world = this.getWorld(worldId, options);
64
+ if (!world) throw createWorldNotFoundError(worldId);
65
+ return world;
66
+ },
67
+ requireOwnedWorld(worldId, creatorAgentId) {
68
+ const world = this.getOwnedWorld(worldId, creatorAgentId);
69
+ if (!world) throw createWorldNotFoundError(worldId);
70
+ return world;
71
+ },
72
+ describeWorldDetail(worldId) {
73
+ return projectWorldDetail(this.requireWorld(worldId));
74
+ },
75
+ buildJoinPlan(worldId) {
76
+ return projectJoinPlan(this.requireWorld(worldId));
77
+ },
78
+ describeSessionStartupContext(worldId) {
79
+ return projectSessionStartupContext(this.requireWorld(worldId));
80
+ },
81
+ describeCatalog() {
82
+ return {
83
+ worldCount: this.listWorldIds().length,
84
+ worldIds: this.listWorldIds(),
85
+ status: store ? 'seeded_plus_store' : 'seeded_catalog',
86
+ };
87
+ },
88
+ };
89
+ }