@xfxstudio/claworld 0.1.5 → 0.2.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.
Files changed (54) hide show
  1. package/README.md +12 -29
  2. package/openclaw.plugin.json +5 -29
  3. package/package.json +4 -12
  4. package/skills/claworld-help/SKILL.md +50 -182
  5. package/skills/claworld-join-and-chat/SKILL.md +78 -288
  6. package/skills/claworld-manage-worlds/SKILL.md +71 -288
  7. package/src/lib/chat-request.js +347 -0
  8. package/src/lib/{accepted-chat-kickoff.js → relay/kickoff-text.js} +67 -26
  9. package/src/openclaw/index.js +0 -5
  10. package/src/openclaw/installer/cli.js +18 -9
  11. package/src/openclaw/installer/core.js +12 -6
  12. package/src/openclaw/installer/doctor.js +69 -31
  13. package/src/openclaw/installer/workspace-contract.js +33 -9
  14. package/src/openclaw/plugin/claworld-channel-plugin.js +118 -623
  15. package/src/openclaw/plugin/config-schema.js +3 -15
  16. package/src/openclaw/plugin/managed-config.js +98 -47
  17. package/src/openclaw/plugin/onboarding.js +7 -3
  18. package/src/openclaw/plugin/register.js +37 -336
  19. package/src/openclaw/plugin/relay-client.js +111 -101
  20. package/src/openclaw/protocol/relay-event-protocol.js +34 -22
  21. package/src/openclaw/runtime/canonical-result-builder.js +15 -5
  22. package/src/openclaw/runtime/demo-session-bootstrap.js +0 -4
  23. package/src/openclaw/runtime/feedback-helper.js +3 -2
  24. package/src/openclaw/runtime/inbound-session-router.js +28 -20
  25. package/src/openclaw/runtime/outbound-session-bridge.js +21 -9
  26. package/src/openclaw/runtime/product-shell-helper.js +43 -636
  27. package/src/openclaw/runtime/runtime-path.js +2 -2
  28. package/src/openclaw/runtime/system-message-orchestrator.js +1 -1
  29. package/src/openclaw/runtime/tool-contracts.js +33 -258
  30. package/src/openclaw/runtime/world-moderation-helper.js +11 -65
  31. package/src/product-shell/catalog/default-world-catalog.js +9 -27
  32. package/src/product-shell/contracts/candidate-feed.js +26 -1
  33. package/src/product-shell/contracts/chat-request-approval-policy.js +4 -4
  34. package/src/product-shell/contracts/world-manifest.js +115 -160
  35. package/src/product-shell/contracts/world-orchestration.js +47 -322
  36. package/src/product-shell/feedback/feedback-routes.js +4 -3
  37. package/src/product-shell/feedback/feedback-service.js +11 -8
  38. package/src/product-shell/index.js +5 -6
  39. package/src/product-shell/membership/membership-service.js +125 -147
  40. package/src/product-shell/onboarding/onboarding-service.js +2 -2
  41. package/src/product-shell/orchestration/world-conversation-orchestrator.js +30 -0
  42. package/src/product-shell/orchestration/world-conversation-text.js +231 -0
  43. package/src/product-shell/results/result-service.js +9 -3
  44. package/src/product-shell/search/search-service.js +28 -1
  45. package/src/product-shell/social/chat-request-routes.js +0 -1
  46. package/src/product-shell/social/chat-request-service.js +1 -102
  47. package/src/product-shell/worlds/world-admin-service.js +85 -276
  48. package/src/product-shell/worlds/world-authorization.js +3 -5
  49. package/src/product-shell/worlds/world-routes.js +8 -38
  50. package/src/product-shell/worlds/world-service.js +3 -3
  51. package/src/product-shell/worlds/world-text.js +77 -0
  52. package/src/lib/runtime-guidance.js +0 -457
  53. package/src/openclaw/runtime/world-session-startup.js +0 -1
  54. package/src/product-shell/orchestration/session-orchestrator.js +0 -38
@@ -68,13 +68,6 @@ export function normalizeProfile(profile = {}) {
68
68
  );
69
69
  }
70
70
 
71
- export function mergeProfileState(profile = {}, profileUpdate = {}) {
72
- return {
73
- ...normalizeProfile(profile),
74
- ...normalizeProfile(profileUpdate),
75
- };
76
- }
77
-
78
71
  function normalizeLookupText(value) {
79
72
  return normalizeText(value, '')?.toLowerCase() || '';
80
73
  }
@@ -108,12 +101,9 @@ function normalizeWorldSummary(world = {}) {
108
101
  return {
109
102
  worldId: normalizeText(rawWorldId, 'unknown-world'),
110
103
  displayName: normalizeText(summary.displayName || world.displayName, normalizeText(rawWorldId, 'Unknown World')),
111
- summary: normalizeText(summary.summary || world.summary, ''),
112
- category: normalizeText(summary.category || world.category, 'general'),
104
+ worldContextText: normalizeText(summary.worldContextText || world.worldContextText, ''),
113
105
  hotness: normalizeInteger(summary.hotness || world.hotness || world.activatedMemberCount, 0),
114
106
  requiredFieldCount: normalizeInteger(summary.requiredFieldCount || world.requiredFieldCount, 0),
115
- matchingMode: normalizeText(summary.matchingMode || world.matchingMode, 'manual_review'),
116
- sessionMode: normalizeText(summary.sessionMode || world.sessionMode, 'a2a'),
117
107
  };
118
108
  }
119
109
 
@@ -180,7 +170,7 @@ function normalizeWorldDetail(payload = {}) {
180
170
  requiredFieldCount: normalizeInteger(payload.requiredFieldCount, requiredFields.length) || requiredFields.length,
181
171
  optionalFieldCount: normalizeInteger(payload.optionalFieldCount, optionalFields.length) || optionalFields.length,
182
172
  matchingMode: normalizeText(payload.matchingMode, 'manual_review'),
183
- sessionMode: normalizeText(payload.sessionMode, 'a2a'),
173
+ conversationMode: normalizeText(payload.conversationMode, 'a2a'),
184
174
  interactionRules: normalizeText(payload.interactionRules, null),
185
175
  prohibitedRules: normalizeText(payload.prohibitedRules, null),
186
176
  ratingRules: normalizeText(payload.ratingRules, null),
@@ -191,7 +181,10 @@ function normalizeWorldDetail(payload = {}) {
191
181
  optionalFields,
192
182
  hints: normalizeStringList(payload.hints),
193
183
  nextAction: normalizeText(payload.nextAction, 'call_join_world'),
194
- sessionOverview: payload.sessionOverview && typeof payload.sessionOverview === 'object' ? payload.sessionOverview : {},
184
+ conversationOverview:
185
+ payload.conversationOverview && typeof payload.conversationOverview === 'object'
186
+ ? payload.conversationOverview
187
+ : {},
195
188
  matchingOverview: payload.matchingOverview && typeof payload.matchingOverview === 'object' ? payload.matchingOverview : {},
196
189
  searchSchema: normalizeSearchSchema(payload.searchSchema || {}, {
197
190
  worldId: normalizedWorldId,
@@ -204,8 +197,8 @@ function normalizeWorldDetail(payload = {}) {
204
197
  const agentSummary = payload.agentSummary && typeof payload.agentSummary === 'object' ? payload.agentSummary : {};
205
198
  const joinSchema = payload.joinSchema && typeof payload.joinSchema === 'object' ? payload.joinSchema : {};
206
199
  const fieldGuide = payload.fieldGuide && typeof payload.fieldGuide === 'object' ? payload.fieldGuide : {};
207
- const sessionOverview = payload.sessionOverview && typeof payload.sessionOverview === 'object'
208
- ? payload.sessionOverview
200
+ const conversationOverview = payload.conversationOverview && typeof payload.conversationOverview === 'object'
201
+ ? payload.conversationOverview
209
202
  : {};
210
203
  const matchingOverview = payload.matchingOverview && typeof payload.matchingOverview === 'object'
211
204
  ? payload.matchingOverview
@@ -237,7 +230,10 @@ function normalizeWorldDetail(payload = {}) {
237
230
  requiredFieldCount: normalizeInteger(joinSchema.requiredFieldCount, requiredFields.length) || requiredFields.length,
238
231
  optionalFieldCount: normalizeInteger(joinSchema.optionalFieldCount, optionalFields.length) || optionalFields.length,
239
232
  matchingMode: normalizeText(payload.matchingMode || agentSummary.matchingMode || matchingOverview.mode || world.matching?.mode, 'manual_review'),
240
- sessionMode: normalizeText(payload.sessionMode || agentSummary.sessionMode || sessionOverview.mode || world.sessionTemplate?.mode, 'a2a'),
233
+ conversationMode: normalizeText(
234
+ payload.conversationMode || agentSummary.conversationMode || conversationOverview.mode || world.conversationTemplate?.mode,
235
+ 'a2a',
236
+ ),
241
237
  interactionRules: normalizeText(payload.interactionRules || world.interactionRules, null),
242
238
  prohibitedRules: normalizeText(payload.prohibitedRules || world.prohibitedRules, null),
243
239
  ratingRules: normalizeText(payload.ratingRules || world.ratingRules, null),
@@ -248,7 +244,7 @@ function normalizeWorldDetail(payload = {}) {
248
244
  optionalFields,
249
245
  hints: normalizeStringList(payload.hints || joinSchema.hints),
250
246
  nextAction: normalizeText(payload.nextAction || joinSchema.nextAction, 'call_join_world'),
251
- sessionOverview,
247
+ conversationOverview,
252
248
  matchingOverview,
253
249
  searchSchema: normalizeSearchSchema(searchOverview, {
254
250
  worldId,
@@ -257,55 +253,6 @@ function normalizeWorldDetail(payload = {}) {
257
253
  };
258
254
  }
259
255
 
260
- function normalizeJoinCheckField(field = {}, index = 0) {
261
- return normalizeField(field, index, { required: true });
262
- }
263
-
264
- function normalizeJoinCheckResponse(payload = {}, { worldId = null, profile = {} } = {}) {
265
- const missingFields = Array.isArray(payload.missingFields)
266
- ? payload.missingFields.map((field, index) => normalizeJoinCheckField(field, index))
267
- : [];
268
- const guidance = payload.missingFieldGuidance && typeof payload.missingFieldGuidance === 'object'
269
- ? payload.missingFieldGuidance
270
- : {};
271
- const orderedMissingFields = Array.isArray(guidance.orderedMissingFields)
272
- ? guidance.orderedMissingFields.map((field, index) => normalizeJoinCheckField(field, index))
273
- : missingFields;
274
- const fallbackNextMissingField = orderedMissingFields[0] || missingFields[0] || null;
275
- const nextMissingField = payload.nextMissingField
276
- ? normalizeJoinCheckField(payload.nextMissingField)
277
- : (guidance.nextMissingField ? normalizeJoinCheckField(guidance.nextMissingField) : fallbackNextMissingField);
278
- const accepted = payload.accepted === true || orderedMissingFields.length === 0;
279
-
280
- return {
281
- worldId: normalizeText(payload.worldId, worldId || 'unknown-world'),
282
- accepted,
283
- status: normalizeText(payload.status, accepted ? 'eligible' : 'needs_profile'),
284
- missingFields,
285
- nextMissingField,
286
- missingFieldGuidance: {
287
- mode: normalizeText(guidance.mode, nextMissingField ? 'ordered_required_fields' : 'complete'),
288
- orderedMissingFields,
289
- orderedMissingFieldIds: normalizeStringList(
290
- Array.isArray(guidance.orderedMissingFieldIds)
291
- ? guidance.orderedMissingFieldIds
292
- : orderedMissingFields.map((field) => field.fieldId),
293
- ),
294
- nextMissingField,
295
- remainingRequiredFieldCount: normalizeInteger(guidance.remainingRequiredFieldCount, orderedMissingFields.length),
296
- },
297
- normalizedProfile: normalizeProfile(
298
- payload.normalizedProfile && typeof payload.normalizedProfile === 'object'
299
- ? payload.normalizedProfile
300
- : profile,
301
- ),
302
- nextAction: normalizeText(
303
- payload.nextAction,
304
- accepted ? 'call_join_world' : 'retry_join_world_after_profile_update',
305
- ),
306
- };
307
- }
308
-
309
256
  function normalizeProfileSummaryField(field = {}, index = 0) {
310
257
  const fieldId = normalizeText(field.fieldId || field.id, `field_${index + 1}`);
311
258
  const value = Array.isArray(field.value)
@@ -450,31 +397,18 @@ function buildCandidateDeliverySummaryLine(candidateSummary = {}, index = 0) {
450
397
  return `${index + 1}. ${candidateSummary.summary}`;
451
398
  }
452
399
 
453
- function formatSessionOverview(detail = {}) {
454
- const sessionOverview = detail.sessionOverview && typeof detail.sessionOverview === 'object'
455
- ? detail.sessionOverview
400
+ function formatConversationOverview(detail = {}) {
401
+ const conversationOverview = detail.conversationOverview && typeof detail.conversationOverview === 'object'
402
+ ? detail.conversationOverview
456
403
  : {};
457
- const mode = normalizeText(detail.sessionMode || sessionOverview.mode, null);
458
- const maxTurns = normalizeInteger(sessionOverview.maxTurns, null);
404
+ const mode = normalizeText(detail.conversationMode || conversationOverview.mode, null);
459
405
  const parts = [];
460
406
 
461
407
  if (mode) parts.push(`${mode} mode`);
462
- if (maxTurns != null) parts.push(`max ${maxTurns} turns`);
463
408
 
464
409
  return parts.length > 0 ? parts.join(', ') : null;
465
410
  }
466
411
 
467
- function buildRaiseHandDirective(sessionOverview = {}) {
468
- const mode = normalizeText(sessionOverview.raiseHandPolicy?.mode, null);
469
- if (mode === 'dual_raise_hand') {
470
- return 'When you are ready to conclude, include [[CLAWORLD_RAISE_HAND]] in your reply. The round closes once both agents raise hand.';
471
- }
472
- if (mode === 'single_raise_hand' || mode === 'either_raise_hand') {
473
- return 'When you are ready to conclude, include [[CLAWORLD_RAISE_HAND]] in your reply to close the round.';
474
- }
475
- return null;
476
- }
477
-
478
412
  export function buildWorldSessionStartupText(detail = {}) {
479
413
  const normalizedDetail = normalizeWorldDetail(detail);
480
414
  const worldId = normalizeText(normalizedDetail.worldId, null);
@@ -482,33 +416,29 @@ export function buildWorldSessionStartupText(detail = {}) {
482
416
 
483
417
  const displayName = normalizeText(normalizedDetail.displayName, worldId);
484
418
  const summary = normalizeText(normalizedDetail.summary, null);
485
- const sessionSummary = formatSessionOverview(normalizedDetail);
486
- const sessionOverview = normalizedDetail.sessionOverview && typeof normalizedDetail.sessionOverview === 'object'
487
- ? normalizedDetail.sessionOverview
419
+ const sessionSummary = formatConversationOverview(normalizedDetail);
420
+ const conversationOverview = normalizedDetail.conversationOverview && typeof normalizedDetail.conversationOverview === 'object'
421
+ ? normalizedDetail.conversationOverview
488
422
  : {};
489
- const raiseHandSummary = normalizeText(sessionOverview.raiseHandPolicy?.summary, null);
490
- const openingText = normalizeText(sessionOverview.openingText, null);
491
- const convergenceText = normalizeText(sessionOverview.convergence?.text, null);
492
- const raiseHandDirective = buildRaiseHandDirective(sessionOverview);
423
+ const openingText = normalizeText(conversationOverview.openingText, null);
424
+ const convergenceText = normalizeText(conversationOverview.convergence?.text, null);
493
425
  const interactionRules = normalizeText(normalizedDetail.interactionRules, null);
494
426
  const prohibitedRules = normalizeText(normalizedDetail.prohibitedRules, null);
495
427
  const ratingRules = normalizeText(normalizedDetail.ratingRules, null);
496
428
 
497
429
  const lines = [
498
- 'Internal Claworld world context for this session.',
430
+ 'Internal Claworld world context for this conversation.',
499
431
  'Do not acknowledge, paraphrase, or announce this setup to the peer unless it is directly relevant to their message.',
500
432
  `World: ${displayName} [${worldId}]`,
501
433
  summary ? `Summary: ${summary}` : null,
502
434
  sessionSummary ? `Session overview: ${sessionSummary}` : null,
503
- raiseHandSummary ? `Completion rule: ${raiseHandSummary}` : null,
504
435
  'Interruption handling: prefer reconnect/resume. Temporary silence or reconnect churn is not the normal way to close a round.',
505
436
  openingText ? `Opening focus: ${openingText}` : null,
506
437
  interactionRules ? `Interaction rules: ${interactionRules}` : null,
507
438
  prohibitedRules ? `Prohibited rules: ${prohibitedRules}` : null,
508
439
  ratingRules ? `Rating rules: ${ratingRules}` : null,
509
440
  convergenceText ? `Convergence rule: ${convergenceText}` : null,
510
- raiseHandDirective ? `Completion signal: ${raiseHandDirective}` : null,
511
- 'Apply these world rules symmetrically when responding in this session.',
441
+ 'Apply these world rules symmetrically when responding in this conversation.',
512
442
  ].filter(Boolean);
513
443
 
514
444
  return lines.join('\n');
@@ -584,9 +514,8 @@ function buildSelectionRetryContract(status, selection, items = [], matches = []
584
514
  export function buildWorldSelectionPrompt(worldDirectory = {}) {
585
515
  const worldLines = Array.isArray(worldDirectory.items)
586
516
  ? worldDirectory.items.map((world, index) => (
587
- `${index + 1}. ${world.displayName} [${world.worldId}] - ${world.summary}`
588
- + ` (category: ${world.category}; required fields: ${world.requiredFieldCount};`
589
- + ` matching: ${world.matchingMode}; session: ${world.sessionMode})`
517
+ `${index + 1}. ${world.displayName} [${world.worldId}]`
518
+ + ` (required fields: ${world.requiredFieldCount}; hotness: ${normalizeInteger(world.hotness, 0)})`
590
519
  ))
591
520
  : [];
592
521
 
@@ -662,10 +591,10 @@ export function resolveWorldSelection(worldDirectory = {}, selection = null) {
662
591
  candidateWorlds: items,
663
592
  orchestration: {
664
593
  stage: 'post_setup_world_selected',
665
- system: 'Confirm the resolved world choice before fetching detail and explaining the required fields.',
594
+ system: 'Confirm the resolved world choice before fetching detail and collecting participantContextText for join_world.',
666
595
  user: `I matched the user choice to ${selectedWorld.displayName} [${selectedWorld.worldId}]. Confirm that this is the world we will use next.`,
667
596
  confirmation: `Confirmed world: ${selectedWorld.displayName} [${selectedWorld.worldId}].`,
668
- followUp: 'Fetch the selected world detail, explain the required fields, and use join_world once enough profile data is available.',
597
+ followUp: 'Fetch the selected world detail, explain the participant context requirement, and use join_world once participantContextText is available.',
669
598
  },
670
599
  };
671
600
  }
@@ -684,246 +613,42 @@ function buildFieldStepPrompt(field = {}, index = 0, total = 1) {
684
613
 
685
614
  export function buildRequiredFieldExplanation(worldDetail = {}) {
686
615
  const detail = normalizeWorldDetail(worldDetail);
687
- const requiredFields = detail.requiredFields;
688
- const optionalFields = detail.optionalFields;
689
- const requiredFieldLabels = requiredFields.map((field) => field.label);
690
- const optionalFieldLabels = optionalFields.map((field) => field.label);
691
- const summary = requiredFields.length > 0
692
- ? `To join ${detail.displayName}, I need ${requiredFields.length} required field${requiredFields.length === 1 ? '' : 's'}: ${joinAsNaturalLanguage(requiredFieldLabels)}.`
693
- : `${detail.displayName} does not require any mandatory profile fields before join_world.`;
694
- const steps = requiredFields.map((field, index) => ({
695
- step: index + 1,
696
- fieldId: field.fieldId,
697
- label: field.label,
698
- prompt: buildFieldStepPrompt(field, index, requiredFields.length),
699
- description: field.description,
700
- examples: field.examples,
701
- constraints: field.constraints,
702
- }));
703
- const optionalContextSummary = optionalFieldLabels.length > 0
704
- ? `Optional context you can add later: ${joinAsNaturalLanguage(optionalFieldLabels)}.`
705
- : null;
706
- const nextInstruction = steps[0]?.prompt || 'All required fields are already explained. You can move to join_world.';
616
+ const field = detail.requiredFields[0];
617
+ const summary = `To join ${detail.displayName}, I need one ${field.label} text.`;
618
+ const steps = [
619
+ {
620
+ step: 1,
621
+ fieldId: field.fieldId,
622
+ label: field.label,
623
+ prompt: buildFieldStepPrompt(field, 0, 1),
624
+ description: field.description,
625
+ examples: field.examples,
626
+ constraints: field.constraints,
627
+ },
628
+ ];
629
+ const nextInstruction = steps[0].prompt;
707
630
 
708
631
  return {
709
632
  status: 'ready',
710
633
  stage: 'post_setup_world_requirements',
711
634
  worldId: detail.worldId,
712
635
  displayName: detail.displayName,
713
- requiredFieldCount: detail.requiredFieldCount,
714
- optionalFieldCount: detail.optionalFieldCount,
636
+ requiredFieldCount: 1,
637
+ optionalFieldCount: 0,
715
638
  summary,
716
639
  steps,
717
640
  hints: detail.hints,
718
- optionalContextSummary,
719
641
  nextAction: detail.nextAction,
720
642
  orchestration: {
721
643
  stage: 'post_setup_world_requirements',
722
- system: 'Confirm the selected world, explain its required fields in plain language, and use join_world once the available profile data is enough for the backend to validate.',
644
+ system: 'Confirm the selected world, explain the participant context requirement in plain language, and use join_world once that text is available.',
723
645
  confirmation: `Confirmed world: ${detail.displayName} [${detail.worldId}].`,
724
- user: [summary, nextInstruction, optionalContextSummary].filter(Boolean).join('\n\n'),
725
- followUp: requiredFields.length > 1
726
- ? `After the user answers ${steps[0].label}, continue with the remaining required fields in order, then call join_world.`
727
- : (requiredFields.length === 1
728
- ? 'After the user answers the required field, call join_world.'
729
- : 'No required fields remain. Call join_world now.'),
646
+ user: [summary, nextInstruction].filter(Boolean).join('\n\n'),
647
+ followUp: 'After the user provides participantContextText, call join_world.',
730
648
  },
731
649
  };
732
650
  }
733
651
 
734
- function buildFieldLookup(worldDetail = {}) {
735
- const detail = normalizeWorldDetail(worldDetail);
736
- return new Map(
737
- [...detail.requiredFields, ...detail.optionalFields].map((field) => [field.fieldId, field]),
738
- );
739
- }
740
-
741
- function selectPromptFields(joinCheck = {}, worldDetail = {}, maxFieldsPerStep = 1) {
742
- if (joinCheck.accepted) return [];
743
-
744
- const fieldLookup = buildFieldLookup(worldDetail);
745
- const orderedMissingFields = Array.isArray(joinCheck.missingFieldGuidance?.orderedMissingFields)
746
- && joinCheck.missingFieldGuidance.orderedMissingFields.length > 0
747
- ? joinCheck.missingFieldGuidance.orderedMissingFields
748
- : (Array.isArray(joinCheck.missingFields) ? joinCheck.missingFields : []);
749
-
750
- return orderedMissingFields.slice(0, Math.max(1, maxFieldsPerStep)).map((field, index) => {
751
- const detailField = fieldLookup.get(field.fieldId) || {};
752
- return normalizeField(
753
- {
754
- ...detailField,
755
- ...field,
756
- description: normalizeText(field.description, detailField.description || null),
757
- examples: Array.isArray(detailField.examples) ? detailField.examples : field.examples,
758
- constraints: detailField.constraints || field.constraints,
759
- },
760
- index,
761
- { required: true },
762
- );
763
- });
764
- }
765
-
766
- function buildProfileFieldPrompt(field = {}, index = 0, total = 1) {
767
- const examples = Array.isArray(field.examples) && field.examples.length > 0
768
- ? ` Example: ${field.examples.map((example) => quoteExample(example)).join(' or ')}.`
769
- : '';
770
- const description = sentenceCase(
771
- field.description || `Provide ${field.label} so the world can evaluate the profile`,
772
- 'Provide this field so the world can evaluate the profile.',
773
- );
774
- const prefix = total > 1 ? `${index + 1}. ` : '';
775
-
776
- return `${prefix}${field.label}. ${description}${examples}`;
777
- }
778
-
779
- function listProvidedRequiredFieldIds(worldDetail = {}, profile = {}, missingFieldIds = []) {
780
- const missingSet = new Set(normalizeStringList(missingFieldIds));
781
- return normalizeWorldDetail(worldDetail).requiredFields
782
- .filter((field) => !missingSet.has(field.fieldId) && !isEmptyProfileValue(profile[field.fieldId]))
783
- .map((field) => field.fieldId);
784
- }
785
-
786
- function buildProfileCollectionFollowUp(promptFields = []) {
787
- if (promptFields.length === 0) {
788
- return 'The current profile is already eligible. Continue with the next world step without restarting profile collection.';
789
- }
790
-
791
- const labels = promptFields.map((field) => field.label);
792
- if (promptFields.length === 1) {
793
- return `After the user answers ${labels[0]}, merge it into the saved profile draft and retry join_world before asking anything else.`;
794
- }
795
-
796
- return `After the user answers ${joinAsNaturalLanguage(labels)}, merge those fields into the saved profile draft and retry join_world before asking anything else.`;
797
- }
798
-
799
- export function buildWorldProfileCollectionFlow({
800
- worldDetail = {},
801
- joinCheck = {},
802
- profile = {},
803
- maxFieldsPerStep = 1,
804
- } = {}) {
805
- const detail = normalizeWorldDetail(worldDetail);
806
- const normalizedJoinCheck = normalizeJoinCheckResponse(joinCheck, {
807
- worldId: detail.worldId,
808
- profile,
809
- });
810
- const promptLimit = Math.max(1, normalizeInteger(maxFieldsPerStep, 1));
811
- const promptFields = selectPromptFields(normalizedJoinCheck, detail, promptLimit);
812
- const promptFieldIds = promptFields.map((field) => field.fieldId);
813
- const providedRequiredFieldIds = listProvidedRequiredFieldIds(
814
- detail,
815
- normalizedJoinCheck.normalizedProfile,
816
- normalizedJoinCheck.missingFieldGuidance.orderedMissingFieldIds,
817
- );
818
- const providedRequiredLabels = detail.requiredFields
819
- .filter((field) => providedRequiredFieldIds.includes(field.fieldId))
820
- .map((field) => field.label);
821
- const optionalFieldLabels = detail.optionalFields.map((field) => field.label);
822
- const remainingCount = normalizedJoinCheck.missingFieldGuidance.remainingRequiredFieldCount;
823
- const summary = normalizedJoinCheck.accepted
824
- ? `All required fields for ${detail.displayName} are currently present.`
825
- : `${remainingCount} required field${remainingCount === 1 ? '' : 's'} still need to be collected for ${detail.displayName}.`;
826
- const savedSummary = providedRequiredLabels.length > 0
827
- ? `Already saved required fields: ${joinAsNaturalLanguage(providedRequiredLabels)}.`
828
- : 'No required fields are saved yet.';
829
- const promptSummary = promptFields.length === 0
830
- ? null
831
- : (promptFields.length === 1
832
- ? 'Ask the user for this required field next:'
833
- : `Ask the user for these ${promptFields.length} required fields next, and accept them in one reply if convenient:`);
834
- const promptBody = promptFields.length === 0
835
- ? null
836
- : promptFields.map((field, index) => buildProfileFieldPrompt(field, index, promptFields.length)).join('\n');
837
- const optionalContextSummary = optionalFieldLabels.length > 0
838
- ? `Optional context the user can add or edit later: ${joinAsNaturalLanguage(optionalFieldLabels)}.`
839
- : null;
840
- const revalidationMode = normalizedJoinCheck.accepted
841
- ? 'complete'
842
- : (promptFields.length > 1 ? 'after_current_batch' : 'after_each_reply');
843
-
844
- return {
845
- accepted: normalizedJoinCheck.accepted,
846
- status: normalizedJoinCheck.status,
847
- source: 'product_shell',
848
- worldId: detail.worldId,
849
- displayName: detail.displayName,
850
- profile: normalizedJoinCheck.normalizedProfile,
851
- promptFields,
852
- promptFieldIds,
853
- providedRequiredFieldIds,
854
- remainingRequiredFieldCount: remainingCount,
855
- nextAction: normalizedJoinCheck.nextAction,
856
- revalidationCheckpoint: {
857
- mode: revalidationMode,
858
- promptFieldIds,
859
- },
860
- orchestration: {
861
- stage: 'post_setup_world_profile_collection',
862
- system:
863
- 'Use the backend join_world unmet-requirement guidance as the source of truth for the next required field prompt. After every user reply or the current batch checkpoint, merge the updates into the saved profile draft and retry join_world.',
864
- confirmation: `Confirmed world: ${detail.displayName} [${detail.worldId}].`,
865
- user: [summary, savedSummary, promptSummary, promptBody, optionalContextSummary].filter(Boolean).join('\n\n'),
866
- followUp: buildProfileCollectionFollowUp(promptFields),
867
- },
868
- };
869
- }
870
-
871
- export function buildWorldJoinOutcomeOrchestration({ worldDetail = {}, joinResult = {} } = {}) {
872
- const detail = normalizeWorldDetail(worldDetail);
873
- const membershipStatus = normalizeText(
874
- joinResult.membershipStatus || joinResult.membership?.status,
875
- 'unknown',
876
- );
877
- const joinSummary = joinResult.nextStageSummary?.summary
878
- ? sentenceCase(joinResult.nextStageSummary.summary, '')
879
- : null;
880
-
881
- return {
882
- stage: 'world_join_result',
883
- status: membershipStatus,
884
- system:
885
- 'The backend already resolved the world join result. Reflect the authoritative membership outcome before any candidate-feed review, request_chat, or live-session follow-up.',
886
- confirmation: `World membership in ${detail.displayName} [${detail.worldId}] is ${membershipStatus}.`,
887
- user: membershipStatus === 'active'
888
- ? ['Joined ' + detail.displayName + ' successfully. World membership is active.', joinSummary].filter(Boolean).join(' ')
889
- : `The join result for ${detail.displayName} is ${membershipStatus}.`,
890
- followUp: membershipStatus === 'active'
891
- ? 'Use the backend-authored candidate-feed and candidate-delivery payloads for the next world step, and keep request_chat as the canonical conversation-start action.'
892
- : 'Do not continue to world-member-only follow-up until membership becomes active.',
893
- };
894
- }
895
-
896
- export function buildResolvedWorldSelectionOrchestration({
897
- selection = null,
898
- profileCollectionFlow = null,
899
- } = {}) {
900
- const selectionOrchestration = selection?.orchestration && typeof selection.orchestration === 'object' && !Array.isArray(selection.orchestration)
901
- ? selection.orchestration
902
- : null;
903
- const profileOrchestration = profileCollectionFlow?.orchestration && typeof profileCollectionFlow.orchestration === 'object' && !Array.isArray(profileCollectionFlow.orchestration)
904
- ? profileCollectionFlow.orchestration
905
- : null;
906
-
907
- if (!profileOrchestration) return selectionOrchestration;
908
-
909
- return {
910
- stage: normalizeText(profileOrchestration.stage, normalizeText(selectionOrchestration?.stage, null)),
911
- system: normalizeText(profileOrchestration.system, normalizeText(selectionOrchestration?.system, null)),
912
- confirmation: normalizeText(
913
- profileOrchestration.confirmation,
914
- normalizeText(selectionOrchestration?.confirmation, null),
915
- ),
916
- user: [
917
- normalizeText(profileOrchestration.confirmation, null),
918
- normalizeText(profileOrchestration.user, null),
919
- ].filter(Boolean).join('\n\n'),
920
- followUp: normalizeText(
921
- profileOrchestration.followUp,
922
- normalizeText(selectionOrchestration?.followUp, null),
923
- ),
924
- };
925
- }
926
-
927
652
  export function buildCandidateDeliverySummary(candidateFeed = {}, { worldDetail = null, limit = null } = {}) {
928
653
  const detail = worldDetail ? normalizeWorldDetail(worldDetail) : null;
929
654
  const normalizedFeed = normalizeCandidateFeedResponse(candidateFeed, {
@@ -53,8 +53,9 @@ export function registerFeedbackRoutes(app, { store, feedbackService }) {
53
53
  details: req.body?.details,
54
54
  reproductionSteps: req.body?.reproductionSteps,
55
55
  worldId: req.body?.worldId,
56
- sessionId: req.body?.sessionId,
57
- roundId: req.body?.roundId,
56
+ conversationKey: req.body?.conversationKey,
57
+ turnId: req.body?.turnId,
58
+ deliveryId: req.body?.deliveryId,
58
59
  targetAgentId: req.body?.targetAgentId,
59
60
  targetAgentCode: req.body?.targetAgentCode,
60
61
  tags: req.body?.tags,
@@ -77,7 +78,7 @@ export function registerFeedbackRoutes(app, { store, feedbackService }) {
77
78
  accountId: req.query.accountId,
78
79
  reporterAgentId: req.query.reporterAgentId,
79
80
  worldId: req.query.worldId,
80
- sessionId: req.query.sessionId,
81
+ conversationKey: req.query.conversationKey,
81
82
  source: req.query.source,
82
83
  page: req.query.page,
83
84
  limit: req.query.limit,
@@ -122,8 +122,9 @@ function projectFeedback(feedback = {}) {
122
122
  context: feedback.context && typeof feedback.context === 'object'
123
123
  ? {
124
124
  worldId: feedback.context.worldId || null,
125
- sessionId: feedback.context.sessionId || null,
126
- roundId: feedback.context.roundId || null,
125
+ conversationKey: feedback.context.conversationKey || null,
126
+ turnId: feedback.context.turnId || null,
127
+ deliveryId: feedback.context.deliveryId || null,
127
128
  targetAgentId: feedback.context.targetAgentId || null,
128
129
  targetAgentCode: feedback.context.targetAgentCode || null,
129
130
  tags: Array.isArray(feedback.context.tags) ? feedback.context.tags : [],
@@ -133,8 +134,9 @@ function projectFeedback(feedback = {}) {
133
134
  }
134
135
  : {
135
136
  worldId: null,
136
- sessionId: null,
137
- roundId: null,
137
+ conversationKey: null,
138
+ turnId: null,
139
+ deliveryId: null,
138
140
  targetAgentId: null,
139
141
  targetAgentCode: null,
140
142
  tags: [],
@@ -181,8 +183,9 @@ export function createFeedbackService({ store } = {}) {
181
183
  },
182
184
  context: {
183
185
  worldId: normalizeText(input.worldId, normalizeText(context.worldId, null)),
184
- sessionId: normalizeText(input.sessionId, normalizeText(context.sessionId, null)),
185
- roundId: normalizeText(input.roundId, normalizeText(context.roundId, null)),
186
+ conversationKey: normalizeText(input.conversationKey, normalizeText(context.conversationKey, null)),
187
+ turnId: normalizeText(input.turnId, normalizeText(context.turnId, null)),
188
+ deliveryId: normalizeText(input.deliveryId, normalizeText(context.deliveryId, null)),
186
189
  targetAgentId: normalizeText(input.targetAgentId, normalizeText(context.targetAgentId, null)),
187
190
  targetAgentCode: normalizeText(input.targetAgentCode, normalizeText(context.targetAgentCode, null)),
188
191
  tags: normalizeStringList(input.tags || context.tags),
@@ -223,7 +226,7 @@ export function createFeedbackService({ store } = {}) {
223
226
  accountId: normalizeText(filters.accountId, null),
224
227
  reporterAgentId: normalizeText(filters.reporterAgentId, null),
225
228
  worldId: normalizeText(filters.worldId, null),
226
- sessionId: normalizeText(filters.sessionId, null),
229
+ conversationKey: normalizeText(filters.conversationKey, null),
227
230
  source: normalizeText(filters.source, null),
228
231
  page,
229
232
  limit,
@@ -245,7 +248,7 @@ export function createFeedbackService({ store } = {}) {
245
248
  accountId: normalizeText(filters.accountId, null),
246
249
  reporterAgentId: normalizeText(filters.reporterAgentId, null),
247
250
  worldId: normalizeText(filters.worldId, null),
248
- sessionId: normalizeText(filters.sessionId, null),
251
+ conversationKey: normalizeText(filters.conversationKey, null),
249
252
  source: normalizeText(filters.source, null),
250
253
  },
251
254
  };
@@ -10,7 +10,7 @@ import { createMembershipService } from './membership/membership-service.js';
10
10
  import { createMatchmakingService } from './matching/matchmaking-service.js';
11
11
  import { createWorldSearchService } from './search/search-service.js';
12
12
  import { createResultService } from './results/result-service.js';
13
- import { createSessionOrchestrator } from './orchestration/session-orchestrator.js';
13
+ import { createWorldConversationOrchestrator } from './orchestration/world-conversation-orchestrator.js';
14
14
  import { createSocialService } from './social/social-service.js';
15
15
  import { registerSocialRoutes } from './social/social-routes.js';
16
16
  import { createFriendService } from './social/friend-service.js';
@@ -60,7 +60,7 @@ export function createClaworldProductShell({
60
60
  return socialLookupService.lookupAgentByCode(input);
61
61
  },
62
62
  };
63
- const sessionOrchestrator = createSessionOrchestrator({
63
+ const worldConversationOrchestrator = createWorldConversationOrchestrator({
64
64
  worldService,
65
65
  resultService,
66
66
  });
@@ -79,7 +79,7 @@ export function createClaworldProductShell({
79
79
  moderation: worldAdminService,
80
80
  feedback: feedbackService,
81
81
  results: resultService,
82
- orchestration: sessionOrchestrator,
82
+ orchestration: worldConversationOrchestrator,
83
83
  },
84
84
  registerRoutes(app) {
85
85
  registerOnboardingRoutes(app, { onboardingService, store });
@@ -94,7 +94,7 @@ export function createClaworldProductShell({
94
94
  searchService,
95
95
  worldBroadcastService,
96
96
  worldAdminService,
97
- sessionOrchestrator,
97
+ worldConversationOrchestrator,
98
98
  });
99
99
  registerSocialRoutes(app, { socialService: socialLookupService });
100
100
  registerFeedbackRoutes(app, { store, feedbackService });
@@ -138,11 +138,10 @@ export function createClaworldProductShell({
138
138
  'POST /v1/worlds/:worldId/search',
139
139
  'POST /v1/worlds/:worldId/broadcast',
140
140
  'GET /v1/worlds/:worldId/candidates',
141
- 'POST /v1/worlds/:worldId/join-check',
142
141
  'POST /v1/worlds/:worldId/join',
143
142
  'GET /v1/worlds/:worldId/memberships',
144
143
  'POST /v1/worlds/:worldId/memberships',
145
- 'POST /v1/worlds/:worldId/session-preview',
144
+ 'POST /v1/worlds/:worldId/conversation-preview',
146
145
  'GET /v1/social/agents/lookup',
147
146
  'POST /v1/feedback',
148
147
  'GET /v1/moderation/worlds',