@xfxstudio/claworld 2026.4.27-testing.1 → 2026.4.28-testing

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.
@@ -125,6 +125,14 @@ function projectToolAction(action = null) {
125
125
  payloadTemplate: { ...payloadTemplate, action: 'get_world' },
126
126
  };
127
127
  }
128
+ if (tool === 'claworld_search_world_members') {
129
+ return {
130
+ tool: 'claworld_search',
131
+ summary: normalizeText(action.summary, null),
132
+ payload: { ...payload, scope: 'world_members' },
133
+ payloadTemplate: { ...payloadTemplate, scope: 'world_members' },
134
+ };
135
+ }
128
136
  return {
129
137
  tool,
130
138
  summary: normalizeText(action.summary, null),
@@ -193,6 +201,55 @@ function projectRequestChatAction(
193
201
  };
194
202
  }
195
203
 
204
+ function projectToolCandidateDeliverySummary(
205
+ candidateDelivery = {},
206
+ {
207
+ accountId = null,
208
+ requestToolName = 'claworld_manage_conversations',
209
+ } = {},
210
+ ) {
211
+ if (!candidateDelivery || typeof candidateDelivery !== 'object' || Array.isArray(candidateDelivery)) return null;
212
+
213
+ const candidateSummaries = Array.isArray(candidateDelivery.candidateSummaries)
214
+ ? candidateDelivery.candidateSummaries.map((summary) => ({
215
+ candidateId: normalizeText(summary.candidateId, null),
216
+ sourceMembershipId: normalizeText(summary.sourceMembershipId, null),
217
+ displayName: normalizeText(summary.displayName, null),
218
+ worldRole: projectWorldRole(summary.worldRole, null),
219
+ headline: normalizeText(summary.headline, null),
220
+ online: summary.online === true,
221
+ rank: normalizeOptionalInteger(summary.rank, null),
222
+ score: normalizeOptionalInteger(summary.score, null),
223
+ agentCode: normalizeText(summary.agentCode, summary.requestChat?.agentCode || null)?.toUpperCase() || null,
224
+ requestChat: projectRequestChatPayload(summary.requestChat, {
225
+ accountId,
226
+ requestToolName,
227
+ }),
228
+ requiredFieldSummary: normalizeStringList(summary.requiredFieldSummary),
229
+ optionalFieldSummary: normalizeStringList(summary.optionalFieldSummary),
230
+ compatibilitySummary: normalizeStringList(summary.compatibilitySummary),
231
+ deliveryReasonSummary: normalizeText(summary.deliveryReasonSummary, null),
232
+ expiresAt: normalizeText(summary.expiresAt, null),
233
+ summary: normalizeText(summary.summary, null),
234
+ }))
235
+ : [];
236
+
237
+ return {
238
+ status: normalizeText(candidateDelivery.status, null),
239
+ worldId: normalizeText(candidateDelivery.worldId, null),
240
+ deliveredCandidateCount: normalizeInteger(candidateDelivery.deliveredCandidateCount, candidateSummaries.length),
241
+ totalCandidateCount: normalizeInteger(candidateDelivery.totalCandidateCount, candidateSummaries.length),
242
+ remainingCandidateCount: normalizeInteger(candidateDelivery.remainingCandidateCount, 0),
243
+ nextAction: normalizeText(candidateDelivery.nextAction, null),
244
+ requestChatAction: projectRequestChatAction(candidateDelivery.requestChatAction, {
245
+ accountId,
246
+ requestToolName,
247
+ }),
248
+ candidateSummaries,
249
+ orchestration: projectOrchestration(candidateDelivery.orchestration),
250
+ };
251
+ }
252
+
196
253
  export function projectToolWorldList(worldDirectory = {}) {
197
254
  const worlds = Array.isArray(worldDirectory.items)
198
255
  ? worldDirectory.items.map((world) => ({
@@ -302,7 +359,23 @@ export function projectToolWorldDetail(worldDetail = {}, { accountId = null } =
302
359
  };
303
360
  }
304
361
 
305
- function projectMemberProfileSummary(summary = {}) {
362
+ function projectToolCandidateSummary(summary = {}, index = 0) {
363
+ return {
364
+ candidateId: normalizeText(summary.candidateId, `candidate_${index + 1}`),
365
+ displayName: normalizeText(summary.displayName, `Candidate ${index + 1}`),
366
+ agentCode: normalizeText(summary.agentCode, null)?.toUpperCase() || null,
367
+ worldRole: projectWorldRole(summary.worldRole, null),
368
+ headline: normalizeText(summary.headline, null),
369
+ online: summary.online === true,
370
+ rank: normalizeInteger(summary.rank, 0) || null,
371
+ score: normalizeInteger(summary.score, 0) || null,
372
+ summary: normalizeText(summary.summary, null),
373
+ expiresAt: normalizeText(summary.expiresAt, null),
374
+ worldFeedbackSummary: projectWorldFeedbackSummary(summary.worldFeedbackSummary),
375
+ };
376
+ }
377
+
378
+ function projectCandidateProfileSummary(summary = {}) {
306
379
  return {
307
380
  displayName: normalizeText(summary.displayName, null),
308
381
  headline: normalizeText(summary.headline, null),
@@ -323,6 +396,123 @@ function projectMemberProfileSummary(summary = {}) {
323
396
  };
324
397
  }
325
398
 
399
+ function resolveCandidateProjectionPayload(payload = {}) {
400
+ if (!payload || typeof payload !== 'object' || Array.isArray(payload)) {
401
+ return {
402
+ worldId: null,
403
+ nextAction: null,
404
+ candidateFeed: null,
405
+ candidateDelivery: null,
406
+ requestChatAction: null,
407
+ };
408
+ }
409
+
410
+ const candidateFeed = payload.candidateFeed && typeof payload.candidateFeed === 'object' && !Array.isArray(payload.candidateFeed)
411
+ ? payload.candidateFeed
412
+ : Array.isArray(payload.candidates)
413
+ ? payload
414
+ : null;
415
+ const candidateDelivery = payload.candidateDelivery && typeof payload.candidateDelivery === 'object' && !Array.isArray(payload.candidateDelivery)
416
+ ? payload.candidateDelivery
417
+ : candidateFeed?.candidateDelivery && typeof candidateFeed.candidateDelivery === 'object' && !Array.isArray(candidateFeed.candidateDelivery)
418
+ ? candidateFeed.candidateDelivery
419
+ : null;
420
+ const candidateModelRequestChatAction = candidateFeed?.candidateModel && typeof candidateFeed.candidateModel === 'object' && !Array.isArray(candidateFeed.candidateModel)
421
+ ? candidateFeed.candidateModel.requestChatAction
422
+ : null;
423
+
424
+ return {
425
+ worldId: normalizeText(payload.worldId, normalizeText(candidateFeed?.worldId, normalizeText(candidateDelivery?.worldId, null))),
426
+ nextAction: normalizeText(payload.nextAction, normalizeText(candidateFeed?.nextAction, normalizeText(candidateDelivery?.nextAction, null))),
427
+ candidateFeed,
428
+ candidateDelivery,
429
+ requestChatAction: payload.requestChatAction && typeof payload.requestChatAction === 'object' && !Array.isArray(payload.requestChatAction)
430
+ ? payload.requestChatAction
431
+ : candidateDelivery?.requestChatAction && typeof candidateDelivery.requestChatAction === 'object' && !Array.isArray(candidateDelivery.requestChatAction)
432
+ ? candidateDelivery.requestChatAction
433
+ : candidateModelRequestChatAction && typeof candidateModelRequestChatAction === 'object' && !Array.isArray(candidateModelRequestChatAction)
434
+ ? candidateModelRequestChatAction
435
+ : null,
436
+ };
437
+ }
438
+
439
+ function projectToolCandidateFeed(payload = {}) {
440
+ const { candidateFeed, candidateDelivery } = resolveCandidateProjectionPayload(payload);
441
+ const candidateSummaries = Array.isArray(candidateDelivery?.candidateSummaries)
442
+ ? candidateDelivery.candidateSummaries.map((summary, index) => projectToolCandidateSummary(summary, index))
443
+ : Array.isArray(candidateFeed?.candidates)
444
+ ? candidateFeed.candidates.map((candidate, index) => projectToolCandidateSummary({
445
+ candidateId: candidate.candidateId,
446
+ displayName: candidate.profileSummary?.displayName,
447
+ agentCode: normalizeText(candidate.agentCode, candidate.requestChat?.agentCode || null),
448
+ worldRole: candidate.worldRole,
449
+ headline: candidate.profileSummary?.headline,
450
+ online: candidate.online === true,
451
+ rank: candidate.rank,
452
+ score: candidate.score,
453
+ summary: normalizeText(candidate.deliveryReason?.summary, null),
454
+ expiresAt: candidate.expiresAt,
455
+ worldFeedbackSummary: candidate.worldFeedbackSummary,
456
+ }, index))
457
+ : [];
458
+
459
+ return {
460
+ status: normalizeText(
461
+ candidateDelivery?.status,
462
+ normalizeText(candidateFeed?.status, candidateSummaries.length > 0 ? 'candidate_summary_ready' : 'candidate_summary_pending'),
463
+ ),
464
+ nextAction: normalizeText(
465
+ candidateDelivery?.nextAction,
466
+ normalizeText(candidateFeed?.nextAction, candidateSummaries.length > 0 ? 'review_candidates_then_request_chat' : 'wait_for_more_candidates'),
467
+ ),
468
+ deliveredCandidateCount: normalizeInteger(candidateDelivery?.deliveredCandidateCount, candidateSummaries.length),
469
+ totalCandidateCount: normalizeInteger(
470
+ candidateDelivery?.totalCandidateCount,
471
+ normalizeInteger(candidateFeed?.totalCandidates, candidateSummaries.length),
472
+ ),
473
+ remainingCandidateCount: normalizeInteger(candidateDelivery?.remainingCandidateCount, 0),
474
+ candidates: candidateSummaries,
475
+ };
476
+ }
477
+
478
+ function projectToolCandidateFlowResponse(payload = {}, { accountId = null } = {}) {
479
+ const {
480
+ worldId,
481
+ nextAction,
482
+ candidateFeed,
483
+ candidateDelivery,
484
+ requestChatAction,
485
+ } = resolveCandidateProjectionPayload(payload);
486
+ const projectedFeed = projectToolCandidateFeed({ candidateFeed, candidateDelivery });
487
+ const projectedDelivery = projectToolCandidateDeliverySummary(candidateDelivery, { accountId });
488
+ const projectedRequestChatAction = projectRequestChatAction(requestChatAction, { accountId });
489
+
490
+ return {
491
+ worldId: normalizeText(worldId, normalizeText(projectedDelivery?.worldId, null)),
492
+ nextAction: normalizeText(nextAction, normalizeText(projectedFeed?.nextAction, normalizeText(projectedDelivery?.nextAction, null))),
493
+ candidateFeed: projectedFeed,
494
+ requestChatTool: 'claworld_manage_conversations',
495
+ candidateDelivery: projectedDelivery,
496
+ requestChatAction: projectedRequestChatAction,
497
+ };
498
+ }
499
+
500
+ export function projectToolCandidateFeedResponse(candidateFeedPayload = {}, { accountId = null } = {}) {
501
+ const candidateFlow = projectToolCandidateFlowResponse(candidateFeedPayload, { accountId });
502
+
503
+ return {
504
+ status: normalizeText(candidateFeedPayload.status, normalizeText(candidateFlow.candidateFeed?.status, 'no_candidates_ready')),
505
+ worldId: candidateFlow.worldId,
506
+ accountId: normalizeText(accountId, null),
507
+ nextAction: candidateFlow.nextAction,
508
+ candidateFeed: candidateFlow.candidateFeed,
509
+ requestChatTool: candidateFlow.requestChatTool,
510
+ candidateDelivery: candidateFlow.candidateDelivery,
511
+ requestChatAction: candidateFlow.requestChatAction,
512
+ };
513
+ }
514
+
515
+
326
516
  function projectToolJoinAction(action = null, { accountId = null, requestToolName = null } = {}) {
327
517
  if (!action || typeof action !== 'object' || Array.isArray(action)) return null;
328
518
  const normalizedAccountId = normalizeText(accountId, null);
@@ -516,22 +706,21 @@ export function projectToolWorldMemberSearchResponse(payload = {}, { accountId =
516
706
  status: normalizeText(payload.status, 'no_matches'),
517
707
  worldId: normalizeText(payload.worldId, null),
518
708
  query: normalizeText(payload.query, null),
519
- sort: normalizeText(payload.sort, 'relevance'),
709
+ sort: normalizeText(payload.sort, 'match'),
520
710
  limit: normalizeInteger(payload.limit, 0),
521
711
  totalMatches: normalizeInteger(payload.totalMatches, Array.isArray(payload.items) ? payload.items.length : 0),
522
712
  nextAction: normalizeText(payload.nextAction, null),
523
713
  members: Array.isArray(payload.items)
524
714
  ? payload.items.map((item, index) => ({
525
- memberId: normalizeText(item.membershipId, `member_${index + 1}`),
526
- membershipId: normalizeText(item.membershipId, null),
527
- displayName: normalizeText(item.displayName, `Member ${index + 1}`),
715
+ candidateId: normalizeText(item.membershipId, `candidate_${index + 1}`),
716
+ displayName: normalizeText(item.displayName, `Candidate ${index + 1}`),
528
717
  agentCode: normalizeText(item.agentCode, null)?.toUpperCase() || null,
529
718
  headline: normalizeText(item.headline, null),
530
719
  online: item.online === true,
531
720
  score: normalizeInteger(item.score, 0),
532
721
  matchedFieldIds: normalizeStringList(item.matchedFieldIds),
533
722
  reasonSummary: normalizeText(item.reasonSummary, null),
534
- profileSummary: projectMemberProfileSummary(item.profileSummary || {}),
723
+ profileSummary: projectCandidateProfileSummary(item.profileSummary || {}),
535
724
  worldFeedbackSummary: projectWorldFeedbackSummary(item.worldFeedbackSummary),
536
725
  requestChat: projectRequestChatPayload(item.requestChat, { accountId }),
537
726
  }))
@@ -17,12 +17,16 @@ export const CLAWORLD_CONVERSATION_TOOL_NAMES = Object.freeze([
17
17
  'claworld_manage_conversations',
18
18
  ]);
19
19
 
20
+ export const CLAWORLD_FEEDBACK_TOOL_NAMES = Object.freeze([
21
+ 'claworld_submit_feedback',
22
+ ]);
20
23
 
21
24
  export const CLAWORLD_REGISTERED_TOOL_NAMES = Object.freeze([
22
25
  ...CLAWORLD_ACCOUNT_TOOL_NAMES,
23
26
  ...CLAWORLD_SEARCH_TOOL_NAMES,
24
27
  ...CLAWORLD_WORLD_TOOL_NAMES,
25
28
  ...CLAWORLD_CONVERSATION_TOOL_NAMES,
29
+ ...CLAWORLD_FEEDBACK_TOOL_NAMES,
26
30
  ]);
27
31
 
28
32
  export const CLAWORLD_PUBLIC_TOOL_NAMES = Object.freeze([
@@ -30,6 +34,17 @@ export const CLAWORLD_PUBLIC_TOOL_NAMES = Object.freeze([
30
34
  ]);
31
35
 
32
36
  export const CLAWORLD_RETIRED_PUBLIC_TOOL_NAMES = Object.freeze([
37
+ 'claworld_account',
38
+ 'claworld_search_worlds',
39
+ 'claworld_list_worlds',
40
+ 'claworld_get_world_detail',
41
+ 'claworld_join_world',
42
+ 'claworld_search_world_members',
43
+ 'claworld_get_candidate_feed',
44
+ 'claworld_create_world',
45
+ 'claworld_manage_world',
46
+ 'claworld_request_chat',
47
+ 'claworld_chat_inbox',
33
48
  ]);
34
49
 
35
50
  export const CLAWORLD_MINIMAL_OPENCLAW_TOOL_NAMES = Object.freeze([
@@ -602,6 +602,7 @@ function compactResultPayload(payload = {}) {
602
602
  'displayName',
603
603
  'chatRequestId',
604
604
  'conversationKey',
605
+ 'candidateId',
605
606
  'feedbackId',
606
607
  'nextAction',
607
608
  'requiredAction',
@@ -631,6 +632,7 @@ export function buildClaworldToolMaintenanceEvent({
631
632
  worldId: params.worldId || payload.worldId,
632
633
  chatRequestId: params.chatRequestId || payload.chatRequestId,
633
634
  conversationKey: params.conversationKey || payload.conversationKey,
635
+ candidateId: params.candidateId || payload.candidateId,
634
636
  agentCode: params.agentCode || payload.agentCode,
635
637
  };
636
638
  return buildClaworldMaintenanceEvent({
@@ -101,10 +101,6 @@ function normalizeManagedWorld(payload = {}) {
101
101
  enabled: normalizeOptionalBoolean(payload.enabled, null),
102
102
  status: normalizeText(payload.status, null),
103
103
  worldRole: normalizeWorldRole(payload.worldRole, null),
104
- visibility: normalizeText(payload.visibility, 'public'),
105
- identityMode: normalizeText(payload.identityMode, 'imaginary'),
106
- joinPolicy: normalizeText(payload.joinPolicy, 'open'),
107
- approvalPolicy: normalizeText(payload.approvalPolicy, 'auto'),
108
104
  schemaVersion: normalizeOptionalInteger(payload.schemaVersion, null),
109
105
  createdAt: normalizeText(payload.createdAt, null),
110
106
  updatedAt: normalizeText(payload.updatedAt, null),
@@ -137,10 +133,6 @@ function normalizeOwnedWorldSummary(payload = {}) {
137
133
  enabled: normalizeOptionalBoolean(payload.enabled, null),
138
134
  status: normalizeText(payload.status, null),
139
135
  worldRole: normalizeWorldRole(payload.worldRole, null),
140
- visibility: normalizeText(payload.visibility, 'public'),
141
- identityMode: normalizeText(payload.identityMode, 'imaginary'),
142
- joinPolicy: normalizeText(payload.joinPolicy, 'open'),
143
- approvalPolicy: normalizeText(payload.approvalPolicy, 'auto'),
144
136
  createdAt: normalizeText(payload.createdAt, null),
145
137
  updatedAt: normalizeText(payload.updatedAt, null),
146
138
  broadcast: normalizeWorldBroadcastConfig(payload.broadcast),
@@ -281,10 +273,6 @@ export async function createModeratedWorld({
281
273
  worldContextText = null,
282
274
  participantContextText = null,
283
275
  enabled = true,
284
- visibility = null,
285
- identityMode = null,
286
- joinPolicy = null,
287
- approvalPolicy = null,
288
276
  fetchImpl,
289
277
  logger = console,
290
278
  } = {}) {
@@ -312,10 +300,6 @@ export async function createModeratedWorld({
312
300
  worldContextText,
313
301
  participantContextText: normalizeText(participantContextText, null),
314
302
  enabled,
315
- ...(normalizeText(visibility, null) ? { visibility: normalizeText(visibility, null) } : {}),
316
- ...(normalizeText(identityMode, null) ? { identityMode: normalizeText(identityMode, null) } : {}),
317
- ...(normalizeText(joinPolicy, null) ? { joinPolicy: normalizeText(joinPolicy, null) } : {}),
318
- ...(normalizeText(approvalPolicy, null) ? { approvalPolicy: normalizeText(approvalPolicy, null) } : {}),
319
303
  }),
320
304
  });
321
305
 
@@ -10,6 +10,12 @@ function normalizeInteger(value, fallback = 0) {
10
10
  return Math.max(0, Math.trunc(parsed));
11
11
  }
12
12
 
13
+ function normalizeNumber(value, fallback = null) {
14
+ const parsed = Number(value);
15
+ if (!Number.isFinite(parsed)) return fallback;
16
+ return parsed;
17
+ }
18
+
13
19
  function normalizeStringList(values = []) {
14
20
  if (!Array.isArray(values)) return [];
15
21
  return [...new Set(values.map((value) => normalizeText(value, null)).filter(Boolean))];
@@ -208,6 +214,147 @@ function normalizeWorldDetail(payload = {}) {
208
214
  };
209
215
  }
210
216
 
217
+ function normalizeProfileSummaryField(field = {}, index = 0) {
218
+ const fieldId = normalizeText(field.fieldId || field.id, `field_${index + 1}`);
219
+ const value = Array.isArray(field.value)
220
+ ? normalizeStringList(field.value)
221
+ : normalizeText(field.value, null);
222
+
223
+ if (value == null || (Array.isArray(value) && value.length === 0)) {
224
+ return null;
225
+ }
226
+
227
+ return {
228
+ fieldId,
229
+ label: normalizeText(field.label, fieldId),
230
+ value,
231
+ };
232
+ }
233
+
234
+ function normalizeCandidateProfileSummary(summary = {}) {
235
+ return {
236
+ displayName: normalizeText(summary.displayName, null),
237
+ headline: normalizeText(summary.headline, null),
238
+ requiredFields: Array.isArray(summary.requiredFields)
239
+ ? summary.requiredFields.map((field, index) => normalizeProfileSummaryField(field, index)).filter(Boolean)
240
+ : [],
241
+ optionalFields: Array.isArray(summary.optionalFields)
242
+ ? summary.optionalFields.map((field, index) => normalizeProfileSummaryField(field, index)).filter(Boolean)
243
+ : [],
244
+ };
245
+ }
246
+
247
+ function normalizeCompatibilitySignal(signal = {}, index = 0) {
248
+ return {
249
+ signalId: normalizeText(signal.signalId, `signal_${index + 1}`),
250
+ type: normalizeText(signal.type, 'world_ready'),
251
+ fieldIds: normalizeStringList(signal.fieldIds),
252
+ score: normalizeNumber(signal.score, 0),
253
+ summary: normalizeText(signal.summary, ''),
254
+ };
255
+ }
256
+
257
+ function normalizeDeliveryReason(reason = {}) {
258
+ return {
259
+ code: normalizeText(reason.code, null),
260
+ matchedFieldIds: normalizeStringList(reason.matchedFieldIds),
261
+ summary: normalizeText(reason.summary, ''),
262
+ };
263
+ }
264
+
265
+ function normalizeWorldRole(worldRole, fallback = null) {
266
+ const normalized = normalizeText(worldRole, fallback);
267
+ return ['owner', 'member'].includes(normalized) ? normalized : fallback;
268
+ }
269
+
270
+ function normalizeCandidate(candidate = {}, index = 0) {
271
+ const normalizedRank = normalizeNumber(candidate.rank, null);
272
+ const displayName = normalizeText(
273
+ candidate.displayName || candidate.profileSummary?.displayName || candidate.requestChat?.displayName,
274
+ null,
275
+ );
276
+ const agentCode = normalizeText(
277
+ candidate.agentCode || candidate.requestChat?.agentCode,
278
+ null,
279
+ )?.toUpperCase() || null;
280
+ const requestChat = displayName && agentCode
281
+ ? {
282
+ worldId: normalizeText(candidate.requestChat?.worldId, normalizeText(candidate.worldId, 'unknown-world')),
283
+ displayName,
284
+ agentCode,
285
+ }
286
+ : null;
287
+
288
+ return {
289
+ candidateId: normalizeText(candidate.candidateId, `candidate_${index + 1}`),
290
+ worldId: normalizeText(candidate.worldId, 'unknown-world'),
291
+ worldRole: normalizeWorldRole(candidate.worldRole, null),
292
+ sourceMembershipId: normalizeText(candidate.sourceMembershipId, null),
293
+ online: candidate.online === true,
294
+ displayName,
295
+ agentCode,
296
+ requestChat,
297
+ profileSummary: normalizeCandidateProfileSummary(candidate.profileSummary),
298
+ compatibilitySignals: Array.isArray(candidate.compatibilitySignals)
299
+ ? candidate.compatibilitySignals.map((signal, signalIndex) => normalizeCompatibilitySignal(signal, signalIndex))
300
+ : [],
301
+ deliveryReason: normalizeDeliveryReason(candidate.deliveryReason),
302
+ expiresAt: normalizeText(candidate.expiresAt, null),
303
+ joinedAt: normalizeText(candidate.joinedAt, null),
304
+ rank: normalizedRank == null ? null : Math.max(1, Math.trunc(normalizedRank)),
305
+ score: normalizeNumber(candidate.score, null),
306
+ };
307
+ }
308
+
309
+ function normalizeCandidateFeedResponse(payload = {}, { worldId = null } = {}) {
310
+ const candidates = Array.isArray(payload.candidates)
311
+ ? payload.candidates.map((candidate, index) => normalizeCandidate(candidate, index))
312
+ : [];
313
+
314
+ return {
315
+ worldId: normalizeText(payload.worldId, worldId || 'unknown-world'),
316
+ viewerMembershipId: normalizeText(payload.viewerMembershipId, null),
317
+ generatedAt: normalizeText(payload.generatedAt, null),
318
+ expiresAt: normalizeText(payload.expiresAt, null),
319
+ deliveryMode: normalizeText(payload.deliveryMode, 'agent_review_before_live_session'),
320
+ nextAction: normalizeText(
321
+ payload.nextAction,
322
+ candidates.length > 0 ? 'review_candidates_then_request_chat' : 'wait_for_more_candidates',
323
+ ),
324
+ candidateSource: normalizeText(payload.candidateSource, 'active_memberships_online'),
325
+ candidateModel: payload.candidateModel && typeof payload.candidateModel === 'object' ? payload.candidateModel : {},
326
+ strategy: payload.strategy && typeof payload.strategy === 'object' ? payload.strategy : {},
327
+ limit: normalizeInteger(payload.limit, candidates.length),
328
+ totalCandidates: normalizeInteger(payload.totalCandidates, candidates.length),
329
+ status: normalizeText(payload.status, candidates.length > 0 ? 'feed_ready' : 'no_candidates_ready'),
330
+ candidates,
331
+ };
332
+ }
333
+
334
+ function summarizeProfileValue(value) {
335
+ if (Array.isArray(value)) return joinAsNaturalLanguage(value.map((entry) => String(entry).trim()).filter(Boolean));
336
+ return normalizeText(value, '');
337
+ }
338
+
339
+ function summarizeProfileFields(fields = []) {
340
+ return fields
341
+ .map((field) => {
342
+ const value = summarizeProfileValue(field.value);
343
+ if (!value) return null;
344
+ return `${field.label}: ${value}`;
345
+ })
346
+ .filter(Boolean);
347
+ }
348
+
349
+ function buildCandidateDeliverySummaryLine(candidateSummary = {}, index = 0) {
350
+ const likesReceived = Number(candidateSummary.worldFeedbackSummary?.likesReceived || 0);
351
+ const dislikesReceived = Number(candidateSummary.worldFeedbackSummary?.dislikesReceived || 0);
352
+ const feedbackLine = likesReceived > 0 || dislikesReceived > 0
353
+ ? ` World feedback in this world: ${likesReceived} like${likesReceived === 1 ? '' : 's'}, ${dislikesReceived} dislike${dislikesReceived === 1 ? '' : 's'}.`
354
+ : '';
355
+ return `${index + 1}. ${candidateSummary.summary}${feedbackLine}`;
356
+ }
357
+
211
358
  function formatConversationOverview(detail = {}) {
212
359
  const conversationOverview = detail.conversationOverview && typeof detail.conversationOverview === 'object'
213
360
  ? detail.conversationOverview
@@ -458,11 +605,133 @@ export function buildRequiredFieldExplanation(worldDetail = {}) {
458
605
  };
459
606
  }
460
607
 
608
+ export function buildCandidateDeliverySummary(candidateFeed = {}, { worldDetail = null, limit = null } = {}) {
609
+ const detail = worldDetail ? normalizeWorldDetail(worldDetail) : null;
610
+ const normalizedFeed = normalizeCandidateFeedResponse(candidateFeed, {
611
+ worldId: detail?.worldId || candidateFeed.worldId || null,
612
+ });
613
+ const summaryLimit = Math.max(
614
+ 1,
615
+ normalizeInteger(limit, normalizedFeed.candidates.length || normalizedFeed.totalCandidates || 1),
616
+ );
617
+ const displayName = detail?.displayName || normalizedFeed.worldId || 'the selected world';
618
+ const requestChatAction = {
619
+ action: 'request_chat',
620
+ worldId: normalizedFeed.worldId,
621
+ requiredFields: ['worldId', 'displayName', 'agentCode', 'openingMessage'],
622
+ summary:
623
+ 'After the user chooses a candidate, request_chat with this worldId, displayName, agentCode, and a non-blank openingMessage.',
624
+ };
625
+ const candidateSummaries = normalizedFeed.candidates.slice(0, summaryLimit).map((candidate, index) => {
626
+ const name = candidate.profileSummary.displayName || `Candidate ${index + 1}`;
627
+ const requiredFieldSummary = summarizeProfileFields(candidate.profileSummary.requiredFields);
628
+ const optionalFieldSummary = summarizeProfileFields(candidate.profileSummary.optionalFields);
629
+ const compatibilitySummary = candidate.compatibilitySignals
630
+ .map((signal) => sentenceCase(signal.summary, ''))
631
+ .filter(Boolean);
632
+ const deliveryReasonSummary = sentenceCase(candidate.deliveryReason.summary, '');
633
+ const availabilitySummary = candidate.online === true ? 'Online now.' : 'Currently offline.';
634
+ const roleSummary = candidate.worldRole ? `World role: ${candidate.worldRole}.` : null;
635
+ const scoreSummary = candidate.score == null
636
+ ? null
637
+ : `Score ${candidate.score}${candidate.rank == null ? '' : `, rank ${candidate.rank}`}.`;
638
+ const summary = [
639
+ candidate.profileSummary.headline ? `${name}: ${candidate.profileSummary.headline}.` : `${name}.`,
640
+ requiredFieldSummary.length > 0 ? `Required profile fields: ${requiredFieldSummary.join('; ')}.` : null,
641
+ optionalFieldSummary.length > 0 ? `Optional context: ${optionalFieldSummary.join('; ')}.` : null,
642
+ compatibilitySummary.length > 0 ? compatibilitySummary.join(' ') : null,
643
+ deliveryReasonSummary || null,
644
+ roleSummary,
645
+ availabilitySummary,
646
+ scoreSummary,
647
+ ].filter(Boolean).join(' ');
648
+
649
+ return {
650
+ candidateId: candidate.candidateId,
651
+ sourceMembershipId: candidate.sourceMembershipId,
652
+ online: candidate.online === true,
653
+ worldRole: candidate.worldRole,
654
+ agentCode: candidate.agentCode,
655
+ requestChat: candidate.requestChat,
656
+ displayName: name,
657
+ headline: candidate.profileSummary.headline,
658
+ rank: candidate.rank,
659
+ score: candidate.score,
660
+ requiredFieldSummary,
661
+ optionalFieldSummary,
662
+ compatibilitySummary,
663
+ deliveryReasonSummary: deliveryReasonSummary || null,
664
+ worldFeedbackSummary: candidate.worldFeedbackSummary || {
665
+ likesReceived: 0,
666
+ dislikesReceived: 0,
667
+ },
668
+ expiresAt: candidate.expiresAt,
669
+ summary,
670
+ };
671
+ });
672
+ const deliveredCandidateCount = candidateSummaries.length;
673
+ const totalCandidateCount = Math.max(normalizedFeed.totalCandidates, deliveredCandidateCount);
674
+ const remainingCandidateCount = Math.max(totalCandidateCount - deliveredCandidateCount, 0);
675
+ const heading = deliveredCandidateCount > 0
676
+ ? `${displayName} has ${deliveredCandidateCount} online candidate profile ${deliveredCandidateCount === 1 ? 'summary' : 'summaries'} ready for review now.`
677
+ : `No online candidate profile summaries are ready for review in ${displayName} yet.`;
678
+ const promptBody = deliveredCandidateCount > 0
679
+ ? candidateSummaries.map((summary, index) => buildCandidateDeliverySummaryLine(summary, index)).join('\n\n')
680
+ : 'No online candidates are currently available from the active-membership feed.';
681
+
682
+ return {
683
+ worldId: normalizedFeed.worldId,
684
+ status: deliveredCandidateCount > 0 ? 'candidate_summary_ready' : 'candidate_summary_pending',
685
+ deliveredCandidateCount,
686
+ totalCandidateCount,
687
+ remainingCandidateCount,
688
+ requestChatAction,
689
+ candidateSummaries,
690
+ nextAction: deliveredCandidateCount > 0
691
+ ? normalizedFeed.nextAction
692
+ : 'wait_for_more_candidates',
693
+ orchestration: {
694
+ stage: 'post_join_candidate_delivery',
695
+ system:
696
+ 'Use the backend-authored candidate summaries already attached to this payload. Candidate requestChat payloads are the canonical follow-up inputs for world-scoped contact establishment.',
697
+ confirmation: `Candidate review payload for ${displayName} [${normalizedFeed.worldId}].`,
698
+ user: [heading, promptBody].filter(Boolean).join('\n\n'),
699
+ followUp: deliveredCandidateCount > 0
700
+ ? (remainingCandidateCount > 0
701
+ ? `Share these ${deliveredCandidateCount} candidate summaries first. If the user chooses someone now, continue with request_chat using that candidate's {worldId, displayName, agentCode} plus a non-blank openingMessage from the user. If they want more options first, continue with the remaining ${remainingCandidateCount} candidate${remainingCandidateCount === 1 ? '' : 's'} from the same feed.`
702
+ : 'Share these candidate summaries and, if the user chooses one, continue with request_chat using the attached {worldId, displayName, agentCode} payload plus a non-blank openingMessage from the user.')
703
+ : 'Tell the user candidate delivery can be retried later through the same backend-authored world flow.',
704
+ },
705
+ };
706
+ }
707
+
461
708
  export function buildResolvedWorldJoinOrchestration({
462
709
  joinResult = null,
710
+ candidateDelivery = null,
463
711
  } = {}) {
464
712
  const joinOrchestration = joinResult?.orchestration && typeof joinResult.orchestration === 'object' && !Array.isArray(joinResult.orchestration)
465
713
  ? joinResult.orchestration
466
714
  : null;
467
- return joinOrchestration;
715
+ const candidateOrchestration = candidateDelivery?.orchestration && typeof candidateDelivery.orchestration === 'object' && !Array.isArray(candidateDelivery.orchestration)
716
+ ? candidateDelivery.orchestration
717
+ : null;
718
+
719
+ if (!candidateOrchestration) return joinOrchestration;
720
+
721
+ return {
722
+ stage: normalizeText(candidateOrchestration.stage, normalizeText(joinOrchestration?.stage, null)),
723
+ system: normalizeText(candidateOrchestration.system, normalizeText(joinOrchestration?.system, null)),
724
+ confirmation: normalizeText(
725
+ joinOrchestration?.confirmation,
726
+ normalizeText(candidateOrchestration.confirmation, null),
727
+ ),
728
+ user: [
729
+ normalizeText(joinOrchestration?.user, normalizeText(joinOrchestration?.confirmation, null)),
730
+ normalizeText(candidateOrchestration.user, null),
731
+ ].filter(Boolean).join('\n\n'),
732
+ followUp: normalizeText(
733
+ candidateOrchestration.followUp,
734
+ normalizeText(joinOrchestration?.followUp, null),
735
+ ),
736
+ };
468
737
  }