@xfxstudio/claworld 0.1.4 → 0.2.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 (55) hide show
  1. package/README.md +12 -29
  2. package/openclaw.plugin.json +9 -33
  3. package/package.json +2 -10
  4. package/skills/claworld-help/SKILL.md +86 -160
  5. package/skills/claworld-join-and-chat/SKILL.md +107 -203
  6. package/skills/claworld-manage-worlds/SKILL.md +75 -392
  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 +14 -16
  11. package/src/openclaw/installer/core.js +13 -14
  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 +156 -625
  15. package/src/openclaw/plugin/config-schema.js +4 -16
  16. package/src/openclaw/plugin/managed-config.js +127 -75
  17. package/src/openclaw/plugin/onboarding.js +7 -3
  18. package/src/openclaw/plugin/register.js +40 -339
  19. package/src/openclaw/plugin/relay-client.js +112 -102
  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 +45 -637
  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 +36 -258
  30. package/src/openclaw/runtime/world-moderation-helper.js +11 -65
  31. package/src/product-shell/catalog/default-world-catalog.js +15 -33
  32. package/src/product-shell/contracts/candidate-feed.js +40 -5
  33. package/src/product-shell/contracts/chat-request-approval-policy.js +3 -3
  34. package/src/product-shell/contracts/world-manifest.js +134 -161
  35. package/src/product-shell/contracts/world-orchestration.js +55 -326
  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 +6 -7
  39. package/src/product-shell/matching/matchmaking-service.js +39 -5
  40. package/src/product-shell/membership/membership-service.js +125 -147
  41. package/src/product-shell/onboarding/onboarding-service.js +2 -2
  42. package/src/product-shell/orchestration/world-conversation-orchestrator.js +30 -0
  43. package/src/product-shell/orchestration/world-conversation-text.js +231 -0
  44. package/src/product-shell/results/result-service.js +9 -3
  45. package/src/product-shell/search/search-service.js +28 -1
  46. package/src/product-shell/social/chat-request-routes.js +0 -1
  47. package/src/product-shell/social/chat-request-service.js +1 -102
  48. package/src/product-shell/worlds/world-admin-service.js +86 -277
  49. package/src/product-shell/worlds/world-authorization.js +3 -5
  50. package/src/product-shell/worlds/world-routes.js +8 -38
  51. package/src/product-shell/worlds/world-service.js +3 -3
  52. package/src/product-shell/worlds/world-text.js +77 -0
  53. package/src/lib/runtime-guidance.js +0 -457
  54. package/src/openclaw/runtime/world-session-startup.js +0 -1
  55. 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)
@@ -388,6 +335,7 @@ function normalizeCandidate(candidate = {}, index = 0) {
388
335
  candidateId: normalizeText(candidate.candidateId, `candidate_${index + 1}`),
389
336
  worldId: normalizeText(candidate.worldId, 'unknown-world'),
390
337
  sourceMembershipId: normalizeText(candidate.sourceMembershipId, null),
338
+ online: candidate.online === true,
391
339
  targetAgentId,
392
340
  requestChat,
393
341
  profileSummary: normalizeCandidateProfileSummary(candidate.profileSummary),
@@ -420,7 +368,7 @@ function normalizeCandidateFeedResponse(payload = {}, { worldId = null, agentId
420
368
  payload.nextAction,
421
369
  candidates.length > 0 ? 'review_candidates_then_request_chat' : 'wait_for_more_candidates',
422
370
  ),
423
- candidateSource: normalizeText(payload.candidateSource, 'active_memberships'),
371
+ candidateSource: normalizeText(payload.candidateSource, 'active_memberships_online'),
424
372
  candidateModel: payload.candidateModel && typeof payload.candidateModel === 'object' ? payload.candidateModel : {},
425
373
  strategy: payload.strategy && typeof payload.strategy === 'object' ? payload.strategy : {},
426
374
  limit: normalizeInteger(payload.limit, candidates.length),
@@ -449,31 +397,18 @@ function buildCandidateDeliverySummaryLine(candidateSummary = {}, index = 0) {
449
397
  return `${index + 1}. ${candidateSummary.summary}`;
450
398
  }
451
399
 
452
- function formatSessionOverview(detail = {}) {
453
- const sessionOverview = detail.sessionOverview && typeof detail.sessionOverview === 'object'
454
- ? detail.sessionOverview
400
+ function formatConversationOverview(detail = {}) {
401
+ const conversationOverview = detail.conversationOverview && typeof detail.conversationOverview === 'object'
402
+ ? detail.conversationOverview
455
403
  : {};
456
- const mode = normalizeText(detail.sessionMode || sessionOverview.mode, null);
457
- const maxTurns = normalizeInteger(sessionOverview.maxTurns, null);
404
+ const mode = normalizeText(detail.conversationMode || conversationOverview.mode, null);
458
405
  const parts = [];
459
406
 
460
407
  if (mode) parts.push(`${mode} mode`);
461
- if (maxTurns != null) parts.push(`max ${maxTurns} turns`);
462
408
 
463
409
  return parts.length > 0 ? parts.join(', ') : null;
464
410
  }
465
411
 
466
- function buildRaiseHandDirective(sessionOverview = {}) {
467
- const mode = normalizeText(sessionOverview.raiseHandPolicy?.mode, null);
468
- if (mode === 'dual_raise_hand') {
469
- return 'When you are ready to conclude, include [[CLAWORLD_RAISE_HAND]] in your reply. The round closes once both agents raise hand.';
470
- }
471
- if (mode === 'single_raise_hand' || mode === 'either_raise_hand') {
472
- return 'When you are ready to conclude, include [[CLAWORLD_RAISE_HAND]] in your reply to close the round.';
473
- }
474
- return null;
475
- }
476
-
477
412
  export function buildWorldSessionStartupText(detail = {}) {
478
413
  const normalizedDetail = normalizeWorldDetail(detail);
479
414
  const worldId = normalizeText(normalizedDetail.worldId, null);
@@ -481,33 +416,29 @@ export function buildWorldSessionStartupText(detail = {}) {
481
416
 
482
417
  const displayName = normalizeText(normalizedDetail.displayName, worldId);
483
418
  const summary = normalizeText(normalizedDetail.summary, null);
484
- const sessionSummary = formatSessionOverview(normalizedDetail);
485
- const sessionOverview = normalizedDetail.sessionOverview && typeof normalizedDetail.sessionOverview === 'object'
486
- ? normalizedDetail.sessionOverview
419
+ const sessionSummary = formatConversationOverview(normalizedDetail);
420
+ const conversationOverview = normalizedDetail.conversationOverview && typeof normalizedDetail.conversationOverview === 'object'
421
+ ? normalizedDetail.conversationOverview
487
422
  : {};
488
- const raiseHandSummary = normalizeText(sessionOverview.raiseHandPolicy?.summary, null);
489
- const openingText = normalizeText(sessionOverview.openingText, null);
490
- const convergenceText = normalizeText(sessionOverview.convergence?.text, null);
491
- const raiseHandDirective = buildRaiseHandDirective(sessionOverview);
423
+ const openingText = normalizeText(conversationOverview.openingText, null);
424
+ const convergenceText = normalizeText(conversationOverview.convergence?.text, null);
492
425
  const interactionRules = normalizeText(normalizedDetail.interactionRules, null);
493
426
  const prohibitedRules = normalizeText(normalizedDetail.prohibitedRules, null);
494
427
  const ratingRules = normalizeText(normalizedDetail.ratingRules, null);
495
428
 
496
429
  const lines = [
497
- 'Internal Claworld world context for this session.',
430
+ 'Internal Claworld world context for this conversation.',
498
431
  'Do not acknowledge, paraphrase, or announce this setup to the peer unless it is directly relevant to their message.',
499
432
  `World: ${displayName} [${worldId}]`,
500
433
  summary ? `Summary: ${summary}` : null,
501
434
  sessionSummary ? `Session overview: ${sessionSummary}` : null,
502
- raiseHandSummary ? `Completion rule: ${raiseHandSummary}` : null,
503
435
  'Interruption handling: prefer reconnect/resume. Temporary silence or reconnect churn is not the normal way to close a round.',
504
436
  openingText ? `Opening focus: ${openingText}` : null,
505
437
  interactionRules ? `Interaction rules: ${interactionRules}` : null,
506
438
  prohibitedRules ? `Prohibited rules: ${prohibitedRules}` : null,
507
439
  ratingRules ? `Rating rules: ${ratingRules}` : null,
508
440
  convergenceText ? `Convergence rule: ${convergenceText}` : null,
509
- raiseHandDirective ? `Completion signal: ${raiseHandDirective}` : null,
510
- 'Apply these world rules symmetrically when responding in this session.',
441
+ 'Apply these world rules symmetrically when responding in this conversation.',
511
442
  ].filter(Boolean);
512
443
 
513
444
  return lines.join('\n');
@@ -583,9 +514,8 @@ function buildSelectionRetryContract(status, selection, items = [], matches = []
583
514
  export function buildWorldSelectionPrompt(worldDirectory = {}) {
584
515
  const worldLines = Array.isArray(worldDirectory.items)
585
516
  ? worldDirectory.items.map((world, index) => (
586
- `${index + 1}. ${world.displayName} [${world.worldId}] - ${world.summary}`
587
- + ` (category: ${world.category}; required fields: ${world.requiredFieldCount};`
588
- + ` matching: ${world.matchingMode}; session: ${world.sessionMode})`
517
+ `${index + 1}. ${world.displayName} [${world.worldId}]`
518
+ + ` (required fields: ${world.requiredFieldCount}; hotness: ${normalizeInteger(world.hotness, 0)})`
589
519
  ))
590
520
  : [];
591
521
 
@@ -661,10 +591,10 @@ export function resolveWorldSelection(worldDirectory = {}, selection = null) {
661
591
  candidateWorlds: items,
662
592
  orchestration: {
663
593
  stage: 'post_setup_world_selected',
664
- 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.',
665
595
  user: `I matched the user choice to ${selectedWorld.displayName} [${selectedWorld.worldId}]. Confirm that this is the world we will use next.`,
666
596
  confirmation: `Confirmed world: ${selectedWorld.displayName} [${selectedWorld.worldId}].`,
667
- 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.',
668
598
  },
669
599
  };
670
600
  }
@@ -683,246 +613,42 @@ function buildFieldStepPrompt(field = {}, index = 0, total = 1) {
683
613
 
684
614
  export function buildRequiredFieldExplanation(worldDetail = {}) {
685
615
  const detail = normalizeWorldDetail(worldDetail);
686
- const requiredFields = detail.requiredFields;
687
- const optionalFields = detail.optionalFields;
688
- const requiredFieldLabels = requiredFields.map((field) => field.label);
689
- const optionalFieldLabels = optionalFields.map((field) => field.label);
690
- const summary = requiredFields.length > 0
691
- ? `To join ${detail.displayName}, I need ${requiredFields.length} required field${requiredFields.length === 1 ? '' : 's'}: ${joinAsNaturalLanguage(requiredFieldLabels)}.`
692
- : `${detail.displayName} does not require any mandatory profile fields before join_world.`;
693
- const steps = requiredFields.map((field, index) => ({
694
- step: index + 1,
695
- fieldId: field.fieldId,
696
- label: field.label,
697
- prompt: buildFieldStepPrompt(field, index, requiredFields.length),
698
- description: field.description,
699
- examples: field.examples,
700
- constraints: field.constraints,
701
- }));
702
- const optionalContextSummary = optionalFieldLabels.length > 0
703
- ? `Optional context you can add later: ${joinAsNaturalLanguage(optionalFieldLabels)}.`
704
- : null;
705
- 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;
706
630
 
707
631
  return {
708
632
  status: 'ready',
709
633
  stage: 'post_setup_world_requirements',
710
634
  worldId: detail.worldId,
711
635
  displayName: detail.displayName,
712
- requiredFieldCount: detail.requiredFieldCount,
713
- optionalFieldCount: detail.optionalFieldCount,
636
+ requiredFieldCount: 1,
637
+ optionalFieldCount: 0,
714
638
  summary,
715
639
  steps,
716
640
  hints: detail.hints,
717
- optionalContextSummary,
718
641
  nextAction: detail.nextAction,
719
642
  orchestration: {
720
643
  stage: 'post_setup_world_requirements',
721
- 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.',
722
645
  confirmation: `Confirmed world: ${detail.displayName} [${detail.worldId}].`,
723
- user: [summary, nextInstruction, optionalContextSummary].filter(Boolean).join('\n\n'),
724
- followUp: requiredFields.length > 1
725
- ? `After the user answers ${steps[0].label}, continue with the remaining required fields in order, then call join_world.`
726
- : (requiredFields.length === 1
727
- ? 'After the user answers the required field, call join_world.'
728
- : '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.',
729
648
  },
730
649
  };
731
650
  }
732
651
 
733
- function buildFieldLookup(worldDetail = {}) {
734
- const detail = normalizeWorldDetail(worldDetail);
735
- return new Map(
736
- [...detail.requiredFields, ...detail.optionalFields].map((field) => [field.fieldId, field]),
737
- );
738
- }
739
-
740
- function selectPromptFields(joinCheck = {}, worldDetail = {}, maxFieldsPerStep = 1) {
741
- if (joinCheck.accepted) return [];
742
-
743
- const fieldLookup = buildFieldLookup(worldDetail);
744
- const orderedMissingFields = Array.isArray(joinCheck.missingFieldGuidance?.orderedMissingFields)
745
- && joinCheck.missingFieldGuidance.orderedMissingFields.length > 0
746
- ? joinCheck.missingFieldGuidance.orderedMissingFields
747
- : (Array.isArray(joinCheck.missingFields) ? joinCheck.missingFields : []);
748
-
749
- return orderedMissingFields.slice(0, Math.max(1, maxFieldsPerStep)).map((field, index) => {
750
- const detailField = fieldLookup.get(field.fieldId) || {};
751
- return normalizeField(
752
- {
753
- ...detailField,
754
- ...field,
755
- description: normalizeText(field.description, detailField.description || null),
756
- examples: Array.isArray(detailField.examples) ? detailField.examples : field.examples,
757
- constraints: detailField.constraints || field.constraints,
758
- },
759
- index,
760
- { required: true },
761
- );
762
- });
763
- }
764
-
765
- function buildProfileFieldPrompt(field = {}, index = 0, total = 1) {
766
- const examples = Array.isArray(field.examples) && field.examples.length > 0
767
- ? ` Example: ${field.examples.map((example) => quoteExample(example)).join(' or ')}.`
768
- : '';
769
- const description = sentenceCase(
770
- field.description || `Provide ${field.label} so the world can evaluate the profile`,
771
- 'Provide this field so the world can evaluate the profile.',
772
- );
773
- const prefix = total > 1 ? `${index + 1}. ` : '';
774
-
775
- return `${prefix}${field.label}. ${description}${examples}`;
776
- }
777
-
778
- function listProvidedRequiredFieldIds(worldDetail = {}, profile = {}, missingFieldIds = []) {
779
- const missingSet = new Set(normalizeStringList(missingFieldIds));
780
- return normalizeWorldDetail(worldDetail).requiredFields
781
- .filter((field) => !missingSet.has(field.fieldId) && !isEmptyProfileValue(profile[field.fieldId]))
782
- .map((field) => field.fieldId);
783
- }
784
-
785
- function buildProfileCollectionFollowUp(promptFields = []) {
786
- if (promptFields.length === 0) {
787
- return 'The current profile is already eligible. Continue with the next world step without restarting profile collection.';
788
- }
789
-
790
- const labels = promptFields.map((field) => field.label);
791
- if (promptFields.length === 1) {
792
- return `After the user answers ${labels[0]}, merge it into the saved profile draft and retry join_world before asking anything else.`;
793
- }
794
-
795
- return `After the user answers ${joinAsNaturalLanguage(labels)}, merge those fields into the saved profile draft and retry join_world before asking anything else.`;
796
- }
797
-
798
- export function buildWorldProfileCollectionFlow({
799
- worldDetail = {},
800
- joinCheck = {},
801
- profile = {},
802
- maxFieldsPerStep = 1,
803
- } = {}) {
804
- const detail = normalizeWorldDetail(worldDetail);
805
- const normalizedJoinCheck = normalizeJoinCheckResponse(joinCheck, {
806
- worldId: detail.worldId,
807
- profile,
808
- });
809
- const promptLimit = Math.max(1, normalizeInteger(maxFieldsPerStep, 1));
810
- const promptFields = selectPromptFields(normalizedJoinCheck, detail, promptLimit);
811
- const promptFieldIds = promptFields.map((field) => field.fieldId);
812
- const providedRequiredFieldIds = listProvidedRequiredFieldIds(
813
- detail,
814
- normalizedJoinCheck.normalizedProfile,
815
- normalizedJoinCheck.missingFieldGuidance.orderedMissingFieldIds,
816
- );
817
- const providedRequiredLabels = detail.requiredFields
818
- .filter((field) => providedRequiredFieldIds.includes(field.fieldId))
819
- .map((field) => field.label);
820
- const optionalFieldLabels = detail.optionalFields.map((field) => field.label);
821
- const remainingCount = normalizedJoinCheck.missingFieldGuidance.remainingRequiredFieldCount;
822
- const summary = normalizedJoinCheck.accepted
823
- ? `All required fields for ${detail.displayName} are currently present.`
824
- : `${remainingCount} required field${remainingCount === 1 ? '' : 's'} still need to be collected for ${detail.displayName}.`;
825
- const savedSummary = providedRequiredLabels.length > 0
826
- ? `Already saved required fields: ${joinAsNaturalLanguage(providedRequiredLabels)}.`
827
- : 'No required fields are saved yet.';
828
- const promptSummary = promptFields.length === 0
829
- ? null
830
- : (promptFields.length === 1
831
- ? 'Ask the user for this required field next:'
832
- : `Ask the user for these ${promptFields.length} required fields next, and accept them in one reply if convenient:`);
833
- const promptBody = promptFields.length === 0
834
- ? null
835
- : promptFields.map((field, index) => buildProfileFieldPrompt(field, index, promptFields.length)).join('\n');
836
- const optionalContextSummary = optionalFieldLabels.length > 0
837
- ? `Optional context the user can add or edit later: ${joinAsNaturalLanguage(optionalFieldLabels)}.`
838
- : null;
839
- const revalidationMode = normalizedJoinCheck.accepted
840
- ? 'complete'
841
- : (promptFields.length > 1 ? 'after_current_batch' : 'after_each_reply');
842
-
843
- return {
844
- accepted: normalizedJoinCheck.accepted,
845
- status: normalizedJoinCheck.status,
846
- source: 'product_shell',
847
- worldId: detail.worldId,
848
- displayName: detail.displayName,
849
- profile: normalizedJoinCheck.normalizedProfile,
850
- promptFields,
851
- promptFieldIds,
852
- providedRequiredFieldIds,
853
- remainingRequiredFieldCount: remainingCount,
854
- nextAction: normalizedJoinCheck.nextAction,
855
- revalidationCheckpoint: {
856
- mode: revalidationMode,
857
- promptFieldIds,
858
- },
859
- orchestration: {
860
- stage: 'post_setup_world_profile_collection',
861
- system:
862
- '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.',
863
- confirmation: `Confirmed world: ${detail.displayName} [${detail.worldId}].`,
864
- user: [summary, savedSummary, promptSummary, promptBody, optionalContextSummary].filter(Boolean).join('\n\n'),
865
- followUp: buildProfileCollectionFollowUp(promptFields),
866
- },
867
- };
868
- }
869
-
870
- export function buildWorldJoinOutcomeOrchestration({ worldDetail = {}, joinResult = {} } = {}) {
871
- const detail = normalizeWorldDetail(worldDetail);
872
- const membershipStatus = normalizeText(
873
- joinResult.membershipStatus || joinResult.membership?.status,
874
- 'unknown',
875
- );
876
- const joinSummary = joinResult.nextStageSummary?.summary
877
- ? sentenceCase(joinResult.nextStageSummary.summary, '')
878
- : null;
879
-
880
- return {
881
- stage: 'world_join_result',
882
- status: membershipStatus,
883
- system:
884
- '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.',
885
- confirmation: `World membership in ${detail.displayName} [${detail.worldId}] is ${membershipStatus}.`,
886
- user: membershipStatus === 'active'
887
- ? ['Joined ' + detail.displayName + ' successfully. World membership is active.', joinSummary].filter(Boolean).join(' ')
888
- : `The join result for ${detail.displayName} is ${membershipStatus}.`,
889
- followUp: membershipStatus === 'active'
890
- ? '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.'
891
- : 'Do not continue to world-member-only follow-up until membership becomes active.',
892
- };
893
- }
894
-
895
- export function buildResolvedWorldSelectionOrchestration({
896
- selection = null,
897
- profileCollectionFlow = null,
898
- } = {}) {
899
- const selectionOrchestration = selection?.orchestration && typeof selection.orchestration === 'object' && !Array.isArray(selection.orchestration)
900
- ? selection.orchestration
901
- : null;
902
- const profileOrchestration = profileCollectionFlow?.orchestration && typeof profileCollectionFlow.orchestration === 'object' && !Array.isArray(profileCollectionFlow.orchestration)
903
- ? profileCollectionFlow.orchestration
904
- : null;
905
-
906
- if (!profileOrchestration) return selectionOrchestration;
907
-
908
- return {
909
- stage: normalizeText(profileOrchestration.stage, normalizeText(selectionOrchestration?.stage, null)),
910
- system: normalizeText(profileOrchestration.system, normalizeText(selectionOrchestration?.system, null)),
911
- confirmation: normalizeText(
912
- profileOrchestration.confirmation,
913
- normalizeText(selectionOrchestration?.confirmation, null),
914
- ),
915
- user: [
916
- normalizeText(profileOrchestration.confirmation, null),
917
- normalizeText(profileOrchestration.user, null),
918
- ].filter(Boolean).join('\n\n'),
919
- followUp: normalizeText(
920
- profileOrchestration.followUp,
921
- normalizeText(selectionOrchestration?.followUp, null),
922
- ),
923
- };
924
- }
925
-
926
652
  export function buildCandidateDeliverySummary(candidateFeed = {}, { worldDetail = null, limit = null } = {}) {
927
653
  const detail = worldDetail ? normalizeWorldDetail(worldDetail) : null;
928
654
  const normalizedFeed = normalizeCandidateFeedResponse(candidateFeed, {
@@ -949,6 +675,7 @@ export function buildCandidateDeliverySummary(candidateFeed = {}, { worldDetail
949
675
  .map((signal) => sentenceCase(signal.summary, ''))
950
676
  .filter(Boolean);
951
677
  const deliveryReasonSummary = sentenceCase(candidate.deliveryReason.summary, '');
678
+ const availabilitySummary = candidate.online === true ? 'Online now.' : 'Currently offline.';
952
679
  const scoreSummary = candidate.score == null
953
680
  ? null
954
681
  : `Score ${candidate.score}${candidate.rank == null ? '' : `, rank ${candidate.rank}`}.`;
@@ -958,12 +685,14 @@ export function buildCandidateDeliverySummary(candidateFeed = {}, { worldDetail
958
685
  optionalFieldSummary.length > 0 ? `Optional context: ${optionalFieldSummary.join('; ')}.` : null,
959
686
  compatibilitySummary.length > 0 ? compatibilitySummary.join(' ') : null,
960
687
  deliveryReasonSummary || null,
688
+ availabilitySummary,
961
689
  scoreSummary,
962
690
  ].filter(Boolean).join(' ');
963
691
 
964
692
  return {
965
693
  candidateId: candidate.candidateId,
966
694
  sourceMembershipId: candidate.sourceMembershipId,
695
+ online: candidate.online === true,
967
696
  targetAgentId: candidate.targetAgentId,
968
697
  requestChat: candidate.requestChat,
969
698
  displayName: name,
@@ -982,11 +711,11 @@ export function buildCandidateDeliverySummary(candidateFeed = {}, { worldDetail
982
711
  const totalCandidateCount = Math.max(normalizedFeed.totalCandidates, deliveredCandidateCount);
983
712
  const remainingCandidateCount = Math.max(totalCandidateCount - deliveredCandidateCount, 0);
984
713
  const heading = deliveredCandidateCount > 0
985
- ? `${displayName} has ${deliveredCandidateCount} candidate profile ${deliveredCandidateCount === 1 ? 'summary' : 'summaries'} ready for review now.`
986
- : `No candidate profile summaries are ready for review in ${displayName} yet.`;
714
+ ? `${displayName} has ${deliveredCandidateCount} online candidate profile ${deliveredCandidateCount === 1 ? 'summary' : 'summaries'} ready for review now.`
715
+ : `No online candidate profile summaries are ready for review in ${displayName} yet.`;
987
716
  const promptBody = deliveredCandidateCount > 0
988
717
  ? candidateSummaries.map((summary, index) => buildCandidateDeliverySummaryLine(summary, index)).join('\n\n')
989
- : 'No candidates are currently available from the active-membership feed.';
718
+ : 'No online candidates are currently available from the active-membership feed.';
990
719
 
991
720
  return {
992
721
  worldId: normalizedFeed.worldId,
@@ -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
  };