@xfxstudio/claworld 0.2.5 → 0.2.7
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-help/SKILL.md +2 -2
- package/skills/claworld-join-and-chat/SKILL.md +18 -9
- package/src/lib/chat-request.js +19 -0
- package/src/lib/relay/kickoff-text.js +6 -1
- package/src/openclaw/installer/core.js +16 -2
- package/src/openclaw/plugin/claworld-channel-plugin.js +164 -12
- package/src/openclaw/plugin/config-schema.js +9 -1
- package/src/openclaw/plugin/register.js +151 -15
- package/src/openclaw/plugin/relay-client.js +502 -1
- package/src/openclaw/runtime/demo-session-bootstrap.js +1 -2
- package/src/openclaw/runtime/tool-contracts.js +40 -1
- package/src/openclaw/runtime/tool-inventory.js +3 -3
- package/src/openclaw/runtime/world-moderation-helper.js +9 -13
- package/src/product-shell/catalog/default-world-catalog.js +12 -258
- package/src/product-shell/contracts/world-manifest.js +0 -38
- package/src/product-shell/contracts/world-orchestration.js +0 -6
- package/src/product-shell/index.js +1 -5
- package/src/product-shell/membership/membership-service.js +24 -6
- package/src/product-shell/orchestration/world-conversation-orchestrator.js +0 -2
- package/src/product-shell/orchestration/world-conversation-text.js +0 -2
- package/src/product-shell/social/chat-request-routes.js +24 -1
- package/src/product-shell/social/chat-request-service.js +185 -15
- package/src/product-shell/worlds/world-admin-service.js +28 -120
- package/src/product-shell/worlds/world-authorization.js +20 -17
- package/src/product-shell/worlds/world-broadcast-service.js +2 -5
- package/src/product-shell/worlds/world-text.js +0 -2
- package/src/product-shell/results/result-service.js +0 -21
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { normalizeAgentProfile, resolveAgentDisplayName, resolveAgentVisibility } from '../../lib/agent-profile.js';
|
|
2
|
+
import { normalizeChatRequestInput } from '../../lib/chat-request.js';
|
|
2
3
|
import { createKickoffBrief, resolveStoredKickoffBrief } from '../../lib/relay/kickoff-text.js';
|
|
3
4
|
import { WORLD_ACTIONS } from '../worlds/world-authorization.js';
|
|
4
5
|
import { normalizeChatRequestApprovalPolicy } from '../contracts/chat-request-approval-policy.js';
|
|
@@ -198,6 +199,7 @@ function resolveAcceptNextAction(kickoff) {
|
|
|
198
199
|
const kickoffStatus = kickoff?.status || 'skipped';
|
|
199
200
|
if (kickoffStatus === 'established') return 'runtime_owns_live_conversation';
|
|
200
201
|
if (kickoffStatus === 'sent') return 'wait_for_sender_opener_delivery';
|
|
202
|
+
if (kickoffStatus === 'kept_silent') return 'sender_kept_silent';
|
|
201
203
|
if (kickoffStatus === 'failed') return 'backend_kickoff_failed';
|
|
202
204
|
return 'chat_request_accepted_without_opening_message';
|
|
203
205
|
}
|
|
@@ -280,6 +282,92 @@ function projectChatRequestOrigin(request = {}) {
|
|
|
280
282
|
};
|
|
281
283
|
}
|
|
282
284
|
|
|
285
|
+
function hasConversationScope(conversation = null) {
|
|
286
|
+
return Boolean(conversation && typeof conversation === 'object' && !Array.isArray(conversation) && Object.keys(conversation).length > 0);
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
function resolveConversationLinkRequest(requests = [], conversation = {}) {
|
|
290
|
+
const conversationMeta = conversation?.meta && typeof conversation.meta === 'object' && !Array.isArray(conversation.meta)
|
|
291
|
+
? conversation.meta
|
|
292
|
+
: {};
|
|
293
|
+
const approvalRequestId = normalizeText(conversationMeta.approvalRequestId, null);
|
|
294
|
+
if (approvalRequestId) {
|
|
295
|
+
return requests.find((request) => normalizeText(request.chatRequestId || request.requestId, null) === approvalRequestId) || null;
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
const conversationKey = normalizeText(conversation?.conversationKey, null);
|
|
299
|
+
if (!conversationKey) return null;
|
|
300
|
+
|
|
301
|
+
return requests.find((request) => {
|
|
302
|
+
const kickoff = request?.kickoff && typeof request.kickoff === 'object' && !Array.isArray(request.kickoff)
|
|
303
|
+
? request.kickoff
|
|
304
|
+
: {};
|
|
305
|
+
return normalizeText(kickoff.conversationKey, null) === conversationKey
|
|
306
|
+
|| normalizeText(request?.conversation?.conversationKey, null) === conversationKey;
|
|
307
|
+
}) || null;
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
function resolveInboxChatDirection(viewerAgentId, request = null) {
|
|
311
|
+
if (!request) return null;
|
|
312
|
+
if (viewerAgentId === request.fromAgentId) return 'outbound';
|
|
313
|
+
if (viewerAgentId === request.toAgentId) return 'inbound';
|
|
314
|
+
return 'related';
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
function resolveInboxChatCounterpartyAgentId(viewerAgentId, request = null, conversation = null) {
|
|
318
|
+
if (request) {
|
|
319
|
+
if (viewerAgentId === request.fromAgentId) return request.toAgentId;
|
|
320
|
+
if (viewerAgentId === request.toAgentId) return request.fromAgentId;
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
const participantIds = Array.isArray(conversation?.participantIds) ? conversation.participantIds : [];
|
|
324
|
+
return participantIds.find((participantId) => participantId && participantId !== viewerAgentId) || null;
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
function resolveInboxChatStatus(conversation = {}, request = null) {
|
|
328
|
+
const kickoffStatus = normalizeText(request?.kickoff?.status, null);
|
|
329
|
+
if (kickoffStatus === 'sent') return 'opening';
|
|
330
|
+
if (kickoffStatus === 'kept_silent') return 'silent';
|
|
331
|
+
if (kickoffStatus === 'failed') return 'kickoff_failed';
|
|
332
|
+
const conversationStatus = normalizeText(conversation?.status, 'active');
|
|
333
|
+
if (conversationStatus === 'active') return 'active';
|
|
334
|
+
if (conversationStatus === 'closed') return 'ended';
|
|
335
|
+
return conversationStatus;
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
function resolveInboxConversationWorldSummary(worldService, request = {}, conversation = {}) {
|
|
339
|
+
const requestContext = request?.requestContext && typeof request.requestContext === 'object' && !Array.isArray(request.requestContext)
|
|
340
|
+
? request.requestContext
|
|
341
|
+
: {};
|
|
342
|
+
const requestConversation = request?.conversation && typeof request.conversation === 'object' && !Array.isArray(request.conversation)
|
|
343
|
+
? request.conversation
|
|
344
|
+
: {};
|
|
345
|
+
const conversationMeta = conversation?.meta && typeof conversation.meta === 'object' && !Array.isArray(conversation.meta)
|
|
346
|
+
? conversation.meta
|
|
347
|
+
: {};
|
|
348
|
+
const worldMeta = conversationMeta.world && typeof conversationMeta.world === 'object' && !Array.isArray(conversationMeta.world)
|
|
349
|
+
? conversationMeta.world
|
|
350
|
+
: {};
|
|
351
|
+
const worldId = normalizeConversationWorldId(requestConversation.worldId)
|
|
352
|
+
|| normalizeConversationWorldId(requestContext.conversation?.worldId)
|
|
353
|
+
|| normalizeConversationWorldId(worldMeta.worldId)
|
|
354
|
+
|| normalizeConversationWorldId(conversationMeta.worldId);
|
|
355
|
+
|
|
356
|
+
if (!worldId) {
|
|
357
|
+
return {
|
|
358
|
+
mode: 'direct',
|
|
359
|
+
worldId: null,
|
|
360
|
+
world: null,
|
|
361
|
+
};
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
return {
|
|
365
|
+
mode: 'world',
|
|
366
|
+
worldId,
|
|
367
|
+
world: projectWorldSummary(worldService, worldId),
|
|
368
|
+
};
|
|
369
|
+
}
|
|
370
|
+
|
|
283
371
|
export function createChatRequestService({
|
|
284
372
|
store = null,
|
|
285
373
|
relay = null,
|
|
@@ -349,8 +437,27 @@ export function createChatRequestService({
|
|
|
349
437
|
};
|
|
350
438
|
}
|
|
351
439
|
|
|
440
|
+
function projectChatInboxChat(conversation = {}, viewerAgentId = null, request = null) {
|
|
441
|
+
const direction = resolveInboxChatDirection(viewerAgentId, request);
|
|
442
|
+
const counterpartyAgentId = resolveInboxChatCounterpartyAgentId(viewerAgentId, request, conversation);
|
|
443
|
+
|
|
444
|
+
return {
|
|
445
|
+
chatRequestId: normalizeText(request?.chatRequestId || request?.requestId, null),
|
|
446
|
+
status: resolveInboxChatStatus(conversation, request),
|
|
447
|
+
...(direction ? { direction } : {}),
|
|
448
|
+
createdAt: normalizeText(conversation.createdAt, normalizeText(request?.createdAt, null)),
|
|
449
|
+
updatedAt: normalizeText(conversation.updatedAt, null),
|
|
450
|
+
lastTurnAt: normalizeText(conversation.lastTurnAt, null),
|
|
451
|
+
conversationKey: normalizeText(conversation.conversationKey, null),
|
|
452
|
+
localSessionKey: normalizeText(conversation.sessionKey, null),
|
|
453
|
+
counterparty: projectAgent(assertStore(), counterpartyAgentId, presence),
|
|
454
|
+
conversation: resolveInboxConversationWorldSummary(worldService, request, conversation),
|
|
455
|
+
};
|
|
456
|
+
}
|
|
457
|
+
|
|
352
458
|
return {
|
|
353
459
|
projectChatRequest,
|
|
460
|
+
projectChatInboxChat,
|
|
354
461
|
async syncApprovalPolicy({
|
|
355
462
|
actorAgentId,
|
|
356
463
|
credentialId = null,
|
|
@@ -388,6 +495,7 @@ export function createChatRequestService({
|
|
|
388
495
|
openingMessage = null,
|
|
389
496
|
openingPayload = null,
|
|
390
497
|
worldId = null,
|
|
498
|
+
requestContext = null,
|
|
391
499
|
origin = null,
|
|
392
500
|
broadcast = null,
|
|
393
501
|
source = 'chat_request',
|
|
@@ -398,29 +506,36 @@ export function createChatRequestService({
|
|
|
398
506
|
throw createInvalidChatRequestError('chat_request_target_required', 'chat request target requires targetAgentId');
|
|
399
507
|
}
|
|
400
508
|
const targetAgent = requireAgent(normalizedTargetAgentId);
|
|
401
|
-
const
|
|
509
|
+
const normalizedSource = normalizeText(source, 'chat_request');
|
|
510
|
+
const normalizedRequestInput = normalizeChatRequestInput({
|
|
511
|
+
requestContext,
|
|
512
|
+
source: normalizedSource,
|
|
513
|
+
});
|
|
514
|
+
const normalizedWorldId = normalizeConversationWorldId(worldId)
|
|
515
|
+
|| normalizeConversationWorldId(normalizedRequestInput.conversation?.worldId);
|
|
402
516
|
if (!targetAgent?.address) {
|
|
403
517
|
throw createTargetAddressNotFoundError(normalizedTargetAgentId);
|
|
404
518
|
}
|
|
405
|
-
const normalizedOpeningPayload = cloneJsonObject(openingPayload);
|
|
519
|
+
const normalizedOpeningPayload = cloneJsonObject(openingPayload) || cloneJsonObject(normalizedRequestInput.openingPayload);
|
|
406
520
|
const normalizedKickoffBrief = createKickoffBrief({
|
|
407
521
|
text: typeof kickoffBrief === 'object' && kickoffBrief !== null && !Array.isArray(kickoffBrief)
|
|
408
522
|
? kickoffBrief.text
|
|
409
|
-
: openingMessage,
|
|
523
|
+
: normalizeText(openingMessage, normalizeText(normalizedRequestInput.openingMessage, null)),
|
|
410
524
|
payload: typeof kickoffBrief === 'object' && kickoffBrief !== null && !Array.isArray(kickoffBrief)
|
|
411
525
|
? kickoffBrief.payload
|
|
412
526
|
: normalizedOpeningPayload,
|
|
413
527
|
source: typeof kickoffBrief === 'object' && kickoffBrief !== null && !Array.isArray(kickoffBrief)
|
|
414
528
|
? kickoffBrief.source
|
|
415
|
-
:
|
|
529
|
+
: normalizedSource === 'world_broadcast'
|
|
416
530
|
? 'world_broadcast_brief'
|
|
417
531
|
: 'chat_request_brief',
|
|
418
532
|
});
|
|
419
|
-
const normalizedOrigin = normalizeChatRequestOrigin(origin);
|
|
420
|
-
const normalizedBroadcast = normalizeChatRequestBroadcastMetadata(broadcast)
|
|
533
|
+
const normalizedOrigin = normalizeChatRequestOrigin(origin) || normalizeChatRequestOrigin(normalizedRequestInput.origin);
|
|
534
|
+
const normalizedBroadcast = normalizeChatRequestBroadcastMetadata(broadcast)
|
|
535
|
+
|| normalizeChatRequestBroadcastMetadata(normalizedRequestInput.broadcast);
|
|
421
536
|
if (normalizedWorldId) {
|
|
422
537
|
worldService?.requireWorld?.(normalizedWorldId);
|
|
423
|
-
if (
|
|
538
|
+
if (normalizedSource !== 'world_broadcast') {
|
|
424
539
|
const authorization = worldAuthorizationService.evaluateWorldAction({
|
|
425
540
|
worldId: normalizedWorldId,
|
|
426
541
|
actorAgentId: fromAgentId,
|
|
@@ -444,9 +559,10 @@ export function createChatRequestService({
|
|
|
444
559
|
conversation: {
|
|
445
560
|
...(normalizedWorldId ? { worldId: normalizedWorldId } : {}),
|
|
446
561
|
},
|
|
562
|
+
requestContext: cloneJsonObject(requestContext),
|
|
447
563
|
origin: normalizedOrigin,
|
|
448
564
|
broadcast: normalizedBroadcast,
|
|
449
|
-
source:
|
|
565
|
+
source: normalizedSource,
|
|
450
566
|
});
|
|
451
567
|
if (result.status < 200 || result.status >= 300) {
|
|
452
568
|
throw createRelayResponseError(result, 'chat_request_create_failed');
|
|
@@ -494,23 +610,55 @@ export function createChatRequestService({
|
|
|
494
610
|
};
|
|
495
611
|
},
|
|
496
612
|
|
|
497
|
-
|
|
613
|
+
listChatInbox({ agentId, direction = null } = {}) {
|
|
498
614
|
requireAgent(agentId);
|
|
499
615
|
const normalizedDirection = normalizeDirection(direction);
|
|
500
|
-
let
|
|
501
|
-
|
|
502
|
-
items = items.filter((request) => request.fromAgentId === agentId || request.toAgentId === agentId);
|
|
616
|
+
let requests = assertStore().listChatRequests ? assertStore().listChatRequests() : [];
|
|
617
|
+
requests = requests.filter((request) => request.fromAgentId === agentId || request.toAgentId === agentId);
|
|
503
618
|
if (normalizedDirection === 'inbound') {
|
|
504
|
-
|
|
619
|
+
requests = requests.filter((request) => request.toAgentId === agentId);
|
|
505
620
|
} else if (normalizedDirection === 'outbound') {
|
|
506
|
-
|
|
621
|
+
requests = requests.filter((request) => request.fromAgentId === agentId);
|
|
507
622
|
}
|
|
508
623
|
|
|
624
|
+
const pendingRequests = sortByRecency(
|
|
625
|
+
requests.filter((request) => request.status === 'pending'),
|
|
626
|
+
'createdAt',
|
|
627
|
+
).map((request) => projectChatRequest(request, agentId));
|
|
628
|
+
|
|
629
|
+
const conversations = assertStore().listConversations
|
|
630
|
+
? assertStore().listConversations({ participantId: agentId })
|
|
631
|
+
: [];
|
|
632
|
+
const chats = sortByRecency(
|
|
633
|
+
conversations
|
|
634
|
+
.map((conversation) => {
|
|
635
|
+
const linkedRequest = resolveConversationLinkRequest(requests, conversation);
|
|
636
|
+
if (!linkedRequest && !normalizeText(conversation?.meta?.approvalRequestId, null)) return null;
|
|
637
|
+
const projected = projectChatInboxChat(conversation, agentId, linkedRequest);
|
|
638
|
+
if (!projected) return null;
|
|
639
|
+
if (normalizedDirection && projected.direction !== normalizedDirection) return null;
|
|
640
|
+
return projected;
|
|
641
|
+
})
|
|
642
|
+
.filter(Boolean),
|
|
643
|
+
'lastTurnAt',
|
|
644
|
+
'updatedAt',
|
|
645
|
+
'createdAt',
|
|
646
|
+
);
|
|
647
|
+
|
|
509
648
|
return {
|
|
510
|
-
|
|
649
|
+
counts: {
|
|
650
|
+
pendingRequestCount: pendingRequests.length,
|
|
651
|
+
chatCount: chats.length,
|
|
652
|
+
},
|
|
653
|
+
pendingRequests,
|
|
654
|
+
chats,
|
|
511
655
|
};
|
|
512
656
|
},
|
|
513
657
|
|
|
658
|
+
listChatRequests({ agentId, direction = null } = {}) {
|
|
659
|
+
return this.listChatInbox({ agentId, direction });
|
|
660
|
+
},
|
|
661
|
+
|
|
514
662
|
async acceptChatRequest(chatRequestId, { actorAgentId } = {}) {
|
|
515
663
|
requireAgent(actorAgentId);
|
|
516
664
|
const normalizedChatRequestId = normalizeText(chatRequestId, null);
|
|
@@ -534,5 +682,27 @@ export function createChatRequestService({
|
|
|
534
682
|
nextAction: resolveAcceptNextAction(kickoff),
|
|
535
683
|
};
|
|
536
684
|
},
|
|
685
|
+
|
|
686
|
+
async rejectChatRequest(chatRequestId, { actorAgentId } = {}) {
|
|
687
|
+
requireAgent(actorAgentId);
|
|
688
|
+
const normalizedChatRequestId = normalizeText(chatRequestId, null);
|
|
689
|
+
if (!normalizedChatRequestId) {
|
|
690
|
+
throw createInvalidChatRequestError('chat_request_id_required', 'chatRequestId is required');
|
|
691
|
+
}
|
|
692
|
+
|
|
693
|
+
const result = await assertRelay('rejectChatRequest').rejectChatRequest(normalizedChatRequestId, {
|
|
694
|
+
actorAgentId,
|
|
695
|
+
});
|
|
696
|
+
if (result.status < 200 || result.status >= 300) {
|
|
697
|
+
throw createRelayResponseError(result, 'chat_request_reject_failed');
|
|
698
|
+
}
|
|
699
|
+
|
|
700
|
+
return {
|
|
701
|
+
status: 'rejected',
|
|
702
|
+
verdict: 'manual_reject',
|
|
703
|
+
chatRequest: projectChatRequest(result.body || {}, actorAgentId),
|
|
704
|
+
nextAction: 'request_rejected_by_peer',
|
|
705
|
+
};
|
|
706
|
+
},
|
|
537
707
|
};
|
|
538
708
|
}
|
|
@@ -12,36 +12,6 @@ function normalizeBoolean(value, fallback = false) {
|
|
|
12
12
|
return fallback;
|
|
13
13
|
}
|
|
14
14
|
|
|
15
|
-
function normalizeStringList(values = []) {
|
|
16
|
-
if (!Array.isArray(values)) return [];
|
|
17
|
-
return [...new Set(values.map((value) => normalizeText(value, null)).filter(Boolean))];
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
function normalizePositiveInteger(value, fallback = null) {
|
|
21
|
-
const parsed = Number(value);
|
|
22
|
-
if (!Number.isFinite(parsed) || parsed <= 0) return fallback;
|
|
23
|
-
return Math.max(1, Math.trunc(parsed));
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
function normalizeWorldEligibility(value, fallback = 'active') {
|
|
27
|
-
const normalized = normalizeText(value, fallback);
|
|
28
|
-
if (normalized === 'joined') return 'joined';
|
|
29
|
-
return 'active';
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
function normalizeBroadcastAudience(value, fallback = 'members') {
|
|
33
|
-
const normalized = normalizeText(value, fallback);
|
|
34
|
-
if (normalized === 'admins') return 'admins';
|
|
35
|
-
if (normalized === 'admins_and_owner') return 'admins_and_owner';
|
|
36
|
-
return 'members';
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
function normalizeBroadcastReplyPolicy(value, fallback = 'zero') {
|
|
40
|
-
const normalized = normalizeText(value, fallback);
|
|
41
|
-
if (normalized === 'at_most_one') return 'at_most_one';
|
|
42
|
-
return 'zero';
|
|
43
|
-
}
|
|
44
|
-
|
|
45
15
|
function summarizeWorldContextText(worldContextText, fallback = null) {
|
|
46
16
|
const normalized = normalizeText(worldContextText, null);
|
|
47
17
|
if (!normalized) return fallback;
|
|
@@ -78,7 +48,7 @@ function createWorldActionNotAllowedError({
|
|
|
78
48
|
worldId,
|
|
79
49
|
agentId,
|
|
80
50
|
action,
|
|
81
|
-
actorRole =
|
|
51
|
+
actorRole = null,
|
|
82
52
|
allowedRoles = [],
|
|
83
53
|
} = {}) {
|
|
84
54
|
const error = new Error(`world_action_not_allowed:${action}:${worldId}:${agentId}`);
|
|
@@ -87,7 +57,6 @@ function createWorldActionNotAllowedError({
|
|
|
87
57
|
const messageByAction = {
|
|
88
58
|
[WORLD_ACTIONS.VIEW_MANAGEMENT]: 'agent does not have permission to access world management',
|
|
89
59
|
[WORLD_ACTIONS.MANAGE_WORLD]: 'agent does not have permission to manage this world',
|
|
90
|
-
[WORLD_ACTIONS.MANAGE_WORLD_ROLES]: 'only the world owner can manage world admins',
|
|
91
60
|
[WORLD_ACTIONS.CHANGE_ENABLED_STATE]: 'only the world owner can enable or disable this world',
|
|
92
61
|
};
|
|
93
62
|
error.responseBody = {
|
|
@@ -125,7 +94,7 @@ function normalizeAgentId(agentId) {
|
|
|
125
94
|
|
|
126
95
|
function normalizeWorldRole(worldRole, fallback = null) {
|
|
127
96
|
const normalized = normalizeText(worldRole, fallback);
|
|
128
|
-
return [WORLD_ROLES.OWNER, WORLD_ROLES.
|
|
97
|
+
return [WORLD_ROLES.OWNER, WORLD_ROLES.MEMBER].includes(normalized) ? normalized : fallback;
|
|
129
98
|
}
|
|
130
99
|
|
|
131
100
|
function buildDefaultEntryProfileField() {
|
|
@@ -175,60 +144,39 @@ function buildConversationTemplate(interactionRules, prohibitedRules, { existing
|
|
|
175
144
|
};
|
|
176
145
|
}
|
|
177
146
|
|
|
178
|
-
function buildResultContract(worldId, ratingRules) {
|
|
179
|
-
return {
|
|
180
|
-
schemaId: `${worldId}.result.v1`,
|
|
181
|
-
outputs: ['rating', 'recommendation', 'notes'],
|
|
182
|
-
successCriteria: [ratingRules || 'Each side gives an explicit 1 to 10 rating.'],
|
|
183
|
-
exampleSignals: {
|
|
184
|
-
intentSignals: [],
|
|
185
|
-
conversationSignals: [],
|
|
186
|
-
agentSignals: [],
|
|
187
|
-
},
|
|
188
|
-
};
|
|
189
|
-
}
|
|
190
|
-
|
|
191
147
|
function buildWorldRecord({
|
|
192
148
|
worldId,
|
|
193
149
|
creatorAgentId,
|
|
194
150
|
displayName,
|
|
195
151
|
summary,
|
|
196
|
-
description,
|
|
197
|
-
interactionRules,
|
|
198
|
-
prohibitedRules,
|
|
199
|
-
ratingRules,
|
|
200
152
|
worldContextText = null,
|
|
201
153
|
enabled = false,
|
|
202
154
|
status = null,
|
|
203
|
-
schemaVersion = 1,
|
|
204
155
|
existingMetrics = null,
|
|
205
|
-
existingConversationTemplate = null,
|
|
206
156
|
} = {}) {
|
|
207
157
|
const resolvedStatus = status || (enabled ? 'enabled' : 'draft');
|
|
208
158
|
const participantContextField = buildDefaultEntryProfileField();
|
|
159
|
+
const resolvedWorldContextText = buildWorldContextText({
|
|
160
|
+
worldId,
|
|
161
|
+
displayName,
|
|
162
|
+
summary,
|
|
163
|
+
worldContextText,
|
|
164
|
+
interactionRules: null,
|
|
165
|
+
prohibitedRules: null,
|
|
166
|
+
});
|
|
209
167
|
|
|
210
168
|
return {
|
|
211
169
|
worldId,
|
|
212
170
|
slug: slugify(displayName, worldId),
|
|
213
171
|
displayName,
|
|
214
172
|
summary,
|
|
215
|
-
description,
|
|
173
|
+
description: resolvedWorldContextText,
|
|
216
174
|
category: 'ugc',
|
|
217
175
|
lifecycle: 'creator_managed',
|
|
218
176
|
tags: ['ugc', 'creator-managed'],
|
|
219
|
-
interactionRules,
|
|
220
|
-
prohibitedRules,
|
|
221
|
-
|
|
222
|
-
worldContextText: buildWorldContextText({
|
|
223
|
-
worldId,
|
|
224
|
-
displayName,
|
|
225
|
-
summary,
|
|
226
|
-
worldContextText,
|
|
227
|
-
interactionRules,
|
|
228
|
-
prohibitedRules,
|
|
229
|
-
ratingRules,
|
|
230
|
-
}),
|
|
231
|
-
roles: [],
|
|
177
|
+
interactionRules: null,
|
|
178
|
+
prohibitedRules: null,
|
|
179
|
+
worldContextText: resolvedWorldContextText,
|
|
232
180
|
joinSchema: {
|
|
233
181
|
requiredFields: [participantContextField],
|
|
234
182
|
optionalFields: [],
|
|
@@ -236,16 +184,12 @@ function buildWorldRecord({
|
|
|
236
184
|
},
|
|
237
185
|
searchSchema: buildSearchSchema(),
|
|
238
186
|
matching: buildMatchingStrategy(),
|
|
239
|
-
conversationTemplate: buildConversationTemplate(
|
|
240
|
-
existingConversationTemplate,
|
|
241
|
-
}),
|
|
242
|
-
resultContract: buildResultContract(worldId, ratingRules),
|
|
187
|
+
conversationTemplate: buildConversationTemplate(null, null),
|
|
243
188
|
meta: {
|
|
244
189
|
status: resolvedStatus === 'enabled' ? 'creator_enabled' : 'creator_draft',
|
|
245
190
|
persistence: 'store',
|
|
246
191
|
},
|
|
247
192
|
creatorAgentId,
|
|
248
|
-
adminAgentIds: [],
|
|
249
193
|
eligibility: 'active',
|
|
250
194
|
broadcast: {
|
|
251
195
|
enabled: false,
|
|
@@ -255,7 +199,7 @@ function buildWorldRecord({
|
|
|
255
199
|
},
|
|
256
200
|
status: resolvedStatus,
|
|
257
201
|
enabled,
|
|
258
|
-
schemaVersion,
|
|
202
|
+
schemaVersion: 1,
|
|
259
203
|
metrics: existingMetrics || {
|
|
260
204
|
totalConversationCount: 0,
|
|
261
205
|
},
|
|
@@ -339,29 +283,18 @@ export function createWorldAdminService({ worldService, worldAuthorizationServic
|
|
|
339
283
|
return actorAgentId;
|
|
340
284
|
}
|
|
341
285
|
|
|
342
|
-
|
|
343
|
-
const membershipStore = assertStore();
|
|
344
|
-
const memberships = membershipStore.listMemberships({ worldId });
|
|
345
|
-
for (const membership of memberships) {
|
|
346
|
-
if (!['joined', 'active'].includes(membership.status)) continue;
|
|
347
|
-
await membershipStore.updateMembership(membership.membershipId, {
|
|
348
|
-
status: 'stale_profile',
|
|
349
|
-
});
|
|
350
|
-
}
|
|
351
|
-
}
|
|
352
|
-
|
|
353
|
-
function requireWorldAction({ worldId, actorAgentId, action } = {}) {
|
|
286
|
+
function requireWorldOwner({ worldId, actorAgentId } = {}) {
|
|
354
287
|
const authorization = worldAuthorizationService.evaluateWorldAction({
|
|
355
288
|
worldId,
|
|
356
289
|
actorAgentId,
|
|
357
|
-
action,
|
|
290
|
+
action: WORLD_ACTIONS.MANAGE_WORLD,
|
|
358
291
|
includeDisabled: true,
|
|
359
292
|
});
|
|
360
293
|
if (!authorization.allowed) {
|
|
361
294
|
throw createWorldActionNotAllowedError({
|
|
362
295
|
worldId: authorization.world.worldId,
|
|
363
296
|
agentId: actorAgentId,
|
|
364
|
-
action,
|
|
297
|
+
action: WORLD_ACTIONS.MANAGE_WORLD,
|
|
365
298
|
actorRole: authorization.worldRole,
|
|
366
299
|
allowedRoles: authorization.allowedRoles,
|
|
367
300
|
});
|
|
@@ -395,10 +328,6 @@ export function createWorldAdminService({ worldService, worldAuthorizationServic
|
|
|
395
328
|
creatorAgentId: resolvedOwnerAgentId,
|
|
396
329
|
displayName: resolvedDisplayName,
|
|
397
330
|
summary: summarizeWorldContextText(resolvedWorldContextText, resolvedDisplayName),
|
|
398
|
-
description: resolvedWorldContextText,
|
|
399
|
-
interactionRules: null,
|
|
400
|
-
prohibitedRules: null,
|
|
401
|
-
ratingRules: null,
|
|
402
331
|
worldContextText: resolvedWorldContextText,
|
|
403
332
|
enabled: normalizeBoolean(enabled, false),
|
|
404
333
|
});
|
|
@@ -413,8 +342,13 @@ export function createWorldAdminService({ worldService, worldAuthorizationServic
|
|
|
413
342
|
listManagedWorlds({ actorAgentId, creatorAgentId, includeDisabled = true } = {}) {
|
|
414
343
|
const storeBacked = assertStore();
|
|
415
344
|
const resolvedActorAgentId = assertActorAgent(actorAgentId || creatorAgentId);
|
|
416
|
-
return
|
|
417
|
-
.
|
|
345
|
+
return worldService
|
|
346
|
+
.listOwnedWorlds({ creatorAgentId: resolvedActorAgentId, includeDisabled })
|
|
347
|
+
.map((world) => worldAuthorizationService.resolveWorldActorContext({
|
|
348
|
+
worldId: world.worldId,
|
|
349
|
+
actorAgentId: resolvedActorAgentId,
|
|
350
|
+
includeDisabled,
|
|
351
|
+
}))
|
|
418
352
|
.map((context) => projectManagedWorldSummary(storeBacked, context.world, {
|
|
419
353
|
worldRole: context.worldRole,
|
|
420
354
|
}));
|
|
@@ -425,10 +359,9 @@ export function createWorldAdminService({ worldService, worldAuthorizationServic
|
|
|
425
359
|
getManagedWorld({ actorAgentId, creatorAgentId, worldId } = {}) {
|
|
426
360
|
const storeBacked = assertStore();
|
|
427
361
|
const resolvedActorAgentId = assertActorAgent(actorAgentId || creatorAgentId);
|
|
428
|
-
const authorization =
|
|
362
|
+
const authorization = requireWorldOwner({
|
|
429
363
|
worldId,
|
|
430
364
|
actorAgentId: resolvedActorAgentId,
|
|
431
|
-
action: WORLD_ACTIONS.VIEW_MANAGEMENT,
|
|
432
365
|
});
|
|
433
366
|
return projectManagedWorld(storeBacked, authorization.world, {
|
|
434
367
|
worldRole: authorization.worldRole,
|
|
@@ -439,25 +372,10 @@ export function createWorldAdminService({ worldService, worldAuthorizationServic
|
|
|
439
372
|
const resolvedActorAgentId = assertActorAgent(actorAgentId || creatorAgentId);
|
|
440
373
|
const hasChanges = changes && typeof changes === 'object' && !Array.isArray(changes);
|
|
441
374
|
|
|
442
|
-
|
|
375
|
+
const authorization = requireWorldOwner({
|
|
443
376
|
worldId,
|
|
444
377
|
actorAgentId: resolvedActorAgentId,
|
|
445
|
-
action: WORLD_ACTIONS.VIEW_MANAGEMENT,
|
|
446
378
|
});
|
|
447
|
-
if (hasManageWorldChanges(changes)) {
|
|
448
|
-
authorization = requireWorldAction({
|
|
449
|
-
worldId,
|
|
450
|
-
actorAgentId: resolvedActorAgentId,
|
|
451
|
-
action: WORLD_ACTIONS.MANAGE_WORLD,
|
|
452
|
-
});
|
|
453
|
-
}
|
|
454
|
-
if (enabled != null) {
|
|
455
|
-
authorization = requireWorldAction({
|
|
456
|
-
worldId,
|
|
457
|
-
actorAgentId: resolvedActorAgentId,
|
|
458
|
-
action: WORLD_ACTIONS.CHANGE_ENABLED_STATE,
|
|
459
|
-
});
|
|
460
|
-
}
|
|
461
379
|
|
|
462
380
|
const existingWorld = authorization.world;
|
|
463
381
|
if (!hasChanges && enabled == null) {
|
|
@@ -466,7 +384,6 @@ export function createWorldAdminService({ worldService, worldAuthorizationServic
|
|
|
466
384
|
});
|
|
467
385
|
}
|
|
468
386
|
|
|
469
|
-
let nextSchemaVersion = Number(existingWorld.schemaVersion || 1);
|
|
470
387
|
let nextRecord = existingWorld;
|
|
471
388
|
|
|
472
389
|
if (hasChanges) {
|
|
@@ -479,18 +396,12 @@ export function createWorldAdminService({ worldService, worldAuthorizationServic
|
|
|
479
396
|
creatorAgentId: existingWorld.creatorAgentId,
|
|
480
397
|
displayName: nextDisplayName,
|
|
481
398
|
summary: summarizeWorldContextText(nextWorldContextText, nextDisplayName),
|
|
482
|
-
description: nextWorldContextText,
|
|
483
|
-
interactionRules: null,
|
|
484
|
-
prohibitedRules: null,
|
|
485
|
-
ratingRules: null,
|
|
486
399
|
worldContextText: nextWorldContextText,
|
|
487
400
|
enabled: enabled == null ? existingWorld.enabled === true : normalizeBoolean(enabled, false),
|
|
488
401
|
status: enabled == null
|
|
489
402
|
? existingWorld.status
|
|
490
403
|
: (normalizeBoolean(enabled, false) ? 'enabled' : 'disabled'),
|
|
491
|
-
schemaVersion: nextSchemaVersion,
|
|
492
404
|
existingMetrics: existingWorld.metrics || null,
|
|
493
|
-
existingConversationTemplate: existingWorld.conversationTemplate || null,
|
|
494
405
|
});
|
|
495
406
|
} else if (enabled != null) {
|
|
496
407
|
nextRecord = {
|
|
@@ -505,9 +416,6 @@ export function createWorldAdminService({ worldService, worldAuthorizationServic
|
|
|
505
416
|
}
|
|
506
417
|
|
|
507
418
|
const updated = await storeBacked.updateWorldConfig(existingWorld.worldId, nextRecord);
|
|
508
|
-
if (nextSchemaVersion !== Number(existingWorld.schemaVersion || 1)) {
|
|
509
|
-
await markMembershipsStale(existingWorld.worldId);
|
|
510
|
-
}
|
|
511
419
|
const normalizedWorld = worldService.requireWorld(updated.worldId, { includeDisabled: true });
|
|
512
420
|
return projectManagedWorld(storeBacked, normalizedWorld, {
|
|
513
421
|
worldRole: authorization.worldRole,
|
|
@@ -7,13 +7,11 @@ function normalizeText(value, fallback = null) {
|
|
|
7
7
|
export const WORLD_ROLES = Object.freeze({
|
|
8
8
|
OWNER: 'owner',
|
|
9
9
|
MEMBER: 'member',
|
|
10
|
-
NONE: 'none',
|
|
11
10
|
});
|
|
12
11
|
|
|
13
12
|
export const WORLD_ACTIONS = Object.freeze({
|
|
14
13
|
VIEW_MANAGEMENT: 'view_world_management',
|
|
15
14
|
MANAGE_WORLD: 'manage_world',
|
|
16
|
-
MANAGE_WORLD_ROLES: 'manage_world_roles',
|
|
17
15
|
CHANGE_ENABLED_STATE: 'change_world_enabled_state',
|
|
18
16
|
BROADCAST: 'broadcast_world',
|
|
19
17
|
SEARCH: 'search_world',
|
|
@@ -21,10 +19,22 @@ export const WORLD_ACTIONS = Object.freeze({
|
|
|
21
19
|
CREATE_CHAT_REQUEST: 'create_world_chat_request',
|
|
22
20
|
});
|
|
23
21
|
|
|
24
|
-
function resolveWorldRole({ isOwner = false,
|
|
22
|
+
function resolveWorldRole({ isOwner = false, isMember = false } = {}) {
|
|
25
23
|
if (isOwner) return WORLD_ROLES.OWNER;
|
|
26
24
|
if (isMember) return WORLD_ROLES.MEMBER;
|
|
27
|
-
return
|
|
25
|
+
return null;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
function resolveEffectiveRoles({ isOwner = false, isMember = false } = {}) {
|
|
29
|
+
const roles = [];
|
|
30
|
+
if (isOwner) {
|
|
31
|
+
roles.push(WORLD_ROLES.OWNER);
|
|
32
|
+
// World owners should retain member-scoped capabilities for worlds they are actively in.
|
|
33
|
+
if (isMember) roles.push(WORLD_ROLES.MEMBER);
|
|
34
|
+
return roles;
|
|
35
|
+
}
|
|
36
|
+
if (isMember) roles.push(WORLD_ROLES.MEMBER);
|
|
37
|
+
return roles;
|
|
28
38
|
}
|
|
29
39
|
|
|
30
40
|
function resolveActionRequirement(action) {
|
|
@@ -37,11 +47,6 @@ function resolveActionRequirement(action) {
|
|
|
37
47
|
allowedRoles: [WORLD_ROLES.OWNER],
|
|
38
48
|
reason: 'owner_role_required',
|
|
39
49
|
};
|
|
40
|
-
case WORLD_ACTIONS.MANAGE_WORLD_ROLES:
|
|
41
|
-
return {
|
|
42
|
-
allowedRoles: [WORLD_ROLES.OWNER],
|
|
43
|
-
reason: 'owner_role_required',
|
|
44
|
-
};
|
|
45
50
|
case WORLD_ACTIONS.SEARCH:
|
|
46
51
|
case WORLD_ACTIONS.VIEW_CANDIDATE_FEED:
|
|
47
52
|
case WORLD_ACTIONS.CREATE_CHAT_REQUEST:
|
|
@@ -77,22 +82,20 @@ export function createWorldAuthorizationService({ worldService, membershipServic
|
|
|
77
82
|
? resolveMembership(world.worldId, normalizedActorAgentId, { includeDisabled })
|
|
78
83
|
: null;
|
|
79
84
|
const isOwner = normalizedActorAgentId != null && normalizedActorAgentId === world.creatorAgentId;
|
|
80
|
-
const isAdmin = normalizedActorAgentId != null
|
|
81
|
-
&& Array.isArray(world.adminAgentIds)
|
|
82
|
-
&& world.adminAgentIds.includes(normalizedActorAgentId);
|
|
83
85
|
const isMember = membership?.status === 'active';
|
|
84
|
-
const worldRole = resolveWorldRole({ isOwner,
|
|
86
|
+
const worldRole = resolveWorldRole({ isOwner, isMember });
|
|
87
|
+
const effectiveRoles = resolveEffectiveRoles({ isOwner, isMember });
|
|
85
88
|
|
|
86
89
|
return {
|
|
87
90
|
world,
|
|
88
91
|
actorAgentId: normalizedActorAgentId,
|
|
89
92
|
worldRole,
|
|
90
|
-
|
|
93
|
+
effectiveRoles,
|
|
94
|
+
managementRole: isOwner ? WORLD_ROLES.OWNER : null,
|
|
91
95
|
membership,
|
|
92
96
|
membershipStatus: membership?.status || null,
|
|
93
97
|
roles: {
|
|
94
98
|
owner: isOwner,
|
|
95
|
-
admin: isAdmin,
|
|
96
99
|
member: isMember,
|
|
97
100
|
},
|
|
98
101
|
};
|
|
@@ -111,7 +114,7 @@ export function createWorldAuthorizationService({ worldService, membershipServic
|
|
|
111
114
|
includeDisabled,
|
|
112
115
|
});
|
|
113
116
|
const requirement = resolveActionRequirement(action);
|
|
114
|
-
const allowed = requirement.allowedRoles.includes(
|
|
117
|
+
const allowed = actor.effectiveRoles.some((role) => requirement.allowedRoles.includes(role));
|
|
115
118
|
|
|
116
119
|
return {
|
|
117
120
|
...actor,
|
|
@@ -125,7 +128,7 @@ export function createWorldAuthorizationService({ worldService, membershipServic
|
|
|
125
128
|
|
|
126
129
|
listManagedWorlds({ actorAgentId, includeDisabled = true } = {}) {
|
|
127
130
|
return worldService
|
|
128
|
-
.
|
|
131
|
+
.listOwnedWorlds({ creatorAgentId: actorAgentId, includeDisabled })
|
|
129
132
|
.map((world) => resolveWorldActorContextForWorld(world, actorAgentId, { includeDisabled }))
|
|
130
133
|
.filter((context) => context.managementRole != null);
|
|
131
134
|
},
|