@xfxstudio/claworld 2026.4.14-testing.1 → 2026.4.16-testing.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 (47) hide show
  1. package/openclaw.plugin.json +1 -1
  2. package/package.json +1 -1
  3. package/index.js +0 -50
  4. package/setup-entry.js +0 -6
  5. package/skills/claworld-a2a-channel-agent/SKILL.md +0 -218
  6. package/skills/claworld-help/SKILL.md +0 -304
  7. package/skills/claworld-join-and-chat/SKILL.md +0 -515
  8. package/skills/claworld-manage-worlds/SKILL.md +0 -283
  9. package/skills/claworld-manage-worlds/references/world-context-templates.md +0 -145
  10. package/src/lib/chat-request.js +0 -366
  11. package/src/lib/public-identity.js +0 -175
  12. package/src/lib/relay/agent-readable-markdown.js +0 -385
  13. package/src/lib/relay/kickoff-progress.js +0 -162
  14. package/src/lib/relay/kickoff-text.js +0 -191
  15. package/src/lib/relay/shared.js +0 -30
  16. package/src/lib/runtime-errors.js +0 -149
  17. package/src/openclaw/index.js +0 -51
  18. package/src/openclaw/plugin/account-identity.js +0 -73
  19. package/src/openclaw/plugin/claworld-channel-plugin.js +0 -3483
  20. package/src/openclaw/plugin/config-schema.js +0 -392
  21. package/src/openclaw/plugin/lifecycle.js +0 -114
  22. package/src/openclaw/plugin/managed-config.js +0 -1054
  23. package/src/openclaw/plugin/onboarding.js +0 -312
  24. package/src/openclaw/plugin/register-tooling.js +0 -728
  25. package/src/openclaw/plugin/register.js +0 -1609
  26. package/src/openclaw/plugin/relay-client-shared.js +0 -146
  27. package/src/openclaw/plugin/relay-client.js +0 -1469
  28. package/src/openclaw/plugin/runtime-backup.js +0 -105
  29. package/src/openclaw/plugin/runtime.js +0 -12
  30. package/src/openclaw/plugin-version.js +0 -67
  31. package/src/openclaw/protocol/relay-event-protocol.js +0 -43
  32. package/src/openclaw/runtime/backend-error-context.js +0 -91
  33. package/src/openclaw/runtime/canonical-result-builder.js +0 -126
  34. package/src/openclaw/runtime/demo-session-bootstrap.js +0 -32
  35. package/src/openclaw/runtime/feedback-helper.js +0 -145
  36. package/src/openclaw/runtime/inbound-session-router.js +0 -44
  37. package/src/openclaw/runtime/outbound-session-bridge.js +0 -29
  38. package/src/openclaw/runtime/product-shell-helper.js +0 -931
  39. package/src/openclaw/runtime/runtime-path.js +0 -19
  40. package/src/openclaw/runtime/system-message-orchestrator.js +0 -1
  41. package/src/openclaw/runtime/tool-contracts.js +0 -939
  42. package/src/openclaw/runtime/tool-inventory.js +0 -83
  43. package/src/openclaw/runtime/world-membership-helper.js +0 -320
  44. package/src/openclaw/runtime/world-moderation-helper.js +0 -508
  45. package/src/product-shell/contracts/chat-request-approval-policy.js +0 -93
  46. package/src/product-shell/contracts/world-orchestration.js +0 -734
  47. package/src/product-shell/orchestration/world-conversation-text.js +0 -229
@@ -1,734 +0,0 @@
1
- function normalizeText(value, fallback = null) {
2
- if (value == null) return fallback;
3
- const normalized = String(value).trim();
4
- return normalized || fallback;
5
- }
6
-
7
- function normalizeInteger(value, fallback = 0) {
8
- const parsed = Number(value);
9
- if (!Number.isFinite(parsed)) return fallback;
10
- return Math.max(0, Math.trunc(parsed));
11
- }
12
-
13
- function normalizeNumber(value, fallback = null) {
14
- const parsed = Number(value);
15
- if (!Number.isFinite(parsed)) return fallback;
16
- return parsed;
17
- }
18
-
19
- function normalizeStringList(values = []) {
20
- if (!Array.isArray(values)) return [];
21
- return [...new Set(values.map((value) => normalizeText(value, null)).filter(Boolean))];
22
- }
23
-
24
- function normalizeBroadcastConfig(broadcast = {}) {
25
- return {
26
- enabled: broadcast.enabled === true,
27
- audience: normalizeText(broadcast.audience, 'members'),
28
- replyPolicy: normalizeText(broadcast.replyPolicy, 'zero'),
29
- excludeSelf: broadcast.excludeSelf !== false,
30
- };
31
- }
32
-
33
- function normalizeLookupText(value) {
34
- return normalizeText(value, '')?.toLowerCase() || '';
35
- }
36
-
37
- function sentenceCase(value, fallback = '') {
38
- const normalized = normalizeText(value, fallback);
39
- if (!normalized) return fallback;
40
- return /[.!?]$/.test(normalized) ? normalized : `${normalized}.`;
41
- }
42
-
43
- function quoteExample(example) {
44
- return `"${String(example).trim()}"`;
45
- }
46
-
47
- function joinAsNaturalLanguage(values = []) {
48
- const items = values.filter(Boolean);
49
- if (items.length === 0) return '';
50
- if (items.length === 1) return items[0];
51
- if (items.length === 2) return `${items[0]} and ${items[1]}`;
52
- return `${items.slice(0, -1).join(', ')}, and ${items.at(-1)}`;
53
- }
54
-
55
- function summarizeWorldChoices(items = []) {
56
- return items.map((world) => `${world.displayName} [${world.worldId}]`);
57
- }
58
-
59
- function normalizeWorldSummary(world = {}) {
60
- const summary = world.agentSummary && typeof world.agentSummary === 'object' ? world.agentSummary : world;
61
- const rawWorldId = world.worldId || summary.worldId;
62
-
63
- return {
64
- worldId: normalizeText(rawWorldId, 'unknown-world'),
65
- displayName: normalizeText(summary.displayName || world.displayName, normalizeText(rawWorldId, 'Unknown World')),
66
- worldContextText: normalizeText(summary.worldContextText || world.worldContextText, ''),
67
- hotness: normalizeInteger(summary.hotness || world.hotness || world.activatedMemberCount, 0),
68
- requiredFieldCount: normalizeInteger(summary.requiredFieldCount || world.requiredFieldCount, 0),
69
- };
70
- }
71
-
72
- function normalizeField(field = {}, index = 0, { required = false } = {}) {
73
- const fieldId = normalizeText(field.fieldId || field.id, `field_${index + 1}`);
74
- return {
75
- fieldId,
76
- label: normalizeText(field.label, fieldId),
77
- type: normalizeText(field.type, 'string'),
78
- source: normalizeText(field.source, 'profile'),
79
- required: field.required === true || required,
80
- description: normalizeText(field.description, null),
81
- examples: normalizeStringList(field.examples),
82
- constraints: field.constraints && typeof field.constraints === 'object' ? field.constraints : {},
83
- };
84
- }
85
-
86
- function normalizeSearchSchema(payload = {}, { worldId = null, fallbackFields = [] } = {}) {
87
- const rawInputFields = Array.isArray(payload.inputFields) && payload.inputFields.length > 0
88
- ? payload.inputFields
89
- : fallbackFields;
90
- const inputFields = rawInputFields.map((field, index) => normalizeField(field, index, { required: false }));
91
- const inputFieldIds = normalizeStringList(
92
- Array.isArray(payload.inputFieldIds)
93
- ? payload.inputFieldIds
94
- : inputFields.map((field) => field.fieldId),
95
- );
96
-
97
- return {
98
- modelId: normalizeText(payload.modelId, worldId ? `${worldId}.search.v1` : 'world.search.v1'),
99
- worldId: normalizeText(payload.worldId, worldId || 'unknown-world'),
100
- mode: normalizeText(payload.mode, 'membership_profile_search'),
101
- previewRoute: normalizeText(payload.previewRoute, worldId ? `/v1/worlds/${worldId}/search` : '/v1/worlds/:worldId/search'),
102
- inputFieldIds,
103
- inputFields,
104
- resultFields: normalizeStringList(payload.resultFields),
105
- viewerRequirement: normalizeText(payload.viewerRequirement, 'active_membership'),
106
- onlineOnly: payload.onlineOnly !== false,
107
- defaultLimit: normalizeInteger(payload.defaultLimit, 10),
108
- summary: normalizeText(payload.summary, ''),
109
- hints: normalizeStringList(payload.hints),
110
- status: normalizeText(payload.status, 'phase1_world_search'),
111
- };
112
- }
113
-
114
- function normalizeWorldDetail(payload = {}) {
115
- if (Array.isArray(payload.requiredFields) || Array.isArray(payload.optionalFields)) {
116
- const requiredFields = Array.isArray(payload.requiredFields)
117
- ? payload.requiredFields.map((field, index) => normalizeField(field, index, { required: true }))
118
- : [];
119
- const optionalFields = Array.isArray(payload.optionalFields)
120
- ? payload.optionalFields.map((field, index) => normalizeField(field, index, { required: false }))
121
- : [];
122
- const normalizedWorldId = normalizeText(payload.worldId, 'unknown-world');
123
-
124
- return {
125
- status: normalizeText(payload.status, 'ready'),
126
- source: normalizeText(payload.source, 'product_shell'),
127
- worldId: normalizedWorldId,
128
- displayName: normalizeText(payload.displayName, normalizedWorldId),
129
- summary: normalizeText(payload.summary, ''),
130
- description: normalizeText(payload.description, normalizeText(payload.summary, '')),
131
- category: normalizeText(payload.category, 'general'),
132
- requiredFieldCount: normalizeInteger(payload.requiredFieldCount, requiredFields.length) || requiredFields.length,
133
- optionalFieldCount: normalizeInteger(payload.optionalFieldCount, optionalFields.length) || optionalFields.length,
134
- matchingMode: normalizeText(payload.matchingMode, 'manual_review'),
135
- conversationMode: normalizeText(payload.conversationMode, 'a2a'),
136
- interactionRules: normalizeText(payload.interactionRules, null),
137
- prohibitedRules: normalizeText(payload.prohibitedRules, null),
138
- eligibility: normalizeText(payload.eligibility, 'active'),
139
- broadcast: normalizeBroadcastConfig(payload.broadcast),
140
- requiredFields,
141
- optionalFields,
142
- hints: normalizeStringList(payload.hints),
143
- nextAction: normalizeText(payload.nextAction, 'call_join_world'),
144
- conversationOverview:
145
- payload.conversationOverview && typeof payload.conversationOverview === 'object'
146
- ? payload.conversationOverview
147
- : {},
148
- matchingOverview: payload.matchingOverview && typeof payload.matchingOverview === 'object' ? payload.matchingOverview : {},
149
- searchSchema: normalizeSearchSchema(payload.searchSchema || {}, {
150
- worldId: normalizedWorldId,
151
- fallbackFields: requiredFields,
152
- }),
153
- };
154
- }
155
-
156
- const world = payload.world && typeof payload.world === 'object' ? payload.world : {};
157
- const agentSummary = payload.agentSummary && typeof payload.agentSummary === 'object' ? payload.agentSummary : {};
158
- const joinSchema = payload.joinSchema && typeof payload.joinSchema === 'object' ? payload.joinSchema : {};
159
- const fieldGuide = payload.fieldGuide && typeof payload.fieldGuide === 'object' ? payload.fieldGuide : {};
160
- const conversationOverview = payload.conversationOverview && typeof payload.conversationOverview === 'object'
161
- ? payload.conversationOverview
162
- : {};
163
- const matchingOverview = payload.matchingOverview && typeof payload.matchingOverview === 'object'
164
- ? payload.matchingOverview
165
- : {};
166
- const searchOverview = payload.searchSchema && typeof payload.searchSchema === 'object'
167
- ? payload.searchSchema
168
- : {};
169
-
170
- const requiredInput = Array.isArray(fieldGuide.required) && fieldGuide.required.length > 0
171
- ? fieldGuide.required
172
- : (Array.isArray(joinSchema.requiredFields) ? joinSchema.requiredFields : []);
173
- const optionalInput = Array.isArray(fieldGuide.optional) && fieldGuide.optional.length > 0
174
- ? fieldGuide.optional
175
- : (Array.isArray(joinSchema.optionalFields) ? joinSchema.optionalFields : []);
176
-
177
- const requiredFields = requiredInput.map((field, index) => normalizeField(field, index, { required: true }));
178
- const optionalFields = optionalInput.map((field, index) => normalizeField(field, index, { required: false }));
179
- const worldId = normalizeText(payload.worldId || world.worldId || joinSchema.worldId, 'unknown-world');
180
- const displayName = normalizeText(payload.displayName || agentSummary.displayName || world.displayName, worldId);
181
-
182
- return {
183
- status: 'ready',
184
- source: 'product_shell',
185
- worldId,
186
- displayName,
187
- summary: normalizeText(payload.summary || agentSummary.summary || world.summary, ''),
188
- description: normalizeText(payload.description || world.description, normalizeText(payload.summary || agentSummary.summary || world.summary, '')),
189
- category: normalizeText(payload.category || agentSummary.category || world.category, 'general'),
190
- requiredFieldCount: normalizeInteger(joinSchema.requiredFieldCount, requiredFields.length) || requiredFields.length,
191
- optionalFieldCount: normalizeInteger(joinSchema.optionalFieldCount, optionalFields.length) || optionalFields.length,
192
- matchingMode: normalizeText(payload.matchingMode || agentSummary.matchingMode || matchingOverview.mode || world.matching?.mode, 'manual_review'),
193
- conversationMode: normalizeText(
194
- payload.conversationMode || agentSummary.conversationMode || conversationOverview.mode || world.conversationTemplate?.mode,
195
- 'a2a',
196
- ),
197
- interactionRules: normalizeText(payload.interactionRules || world.interactionRules, null),
198
- prohibitedRules: normalizeText(payload.prohibitedRules || world.prohibitedRules, null),
199
- eligibility: normalizeText(payload.eligibility || world.eligibility, 'active'),
200
- broadcast: normalizeBroadcastConfig(payload.broadcast || world.broadcast),
201
- requiredFields,
202
- optionalFields,
203
- hints: normalizeStringList(payload.hints || joinSchema.hints),
204
- nextAction: normalizeText(payload.nextAction || joinSchema.nextAction, 'call_join_world'),
205
- conversationOverview,
206
- matchingOverview,
207
- searchSchema: normalizeSearchSchema(searchOverview, {
208
- worldId,
209
- fallbackFields: [...requiredFields, ...optionalFields],
210
- }),
211
- };
212
- }
213
-
214
- function normalizeProfileSummaryField(field = {}, index = 0) {
215
- const fieldId = normalizeText(field.fieldId || field.id, `field_${index + 1}`);
216
- const value = Array.isArray(field.value)
217
- ? normalizeStringList(field.value)
218
- : normalizeText(field.value, null);
219
-
220
- if (value == null || (Array.isArray(value) && value.length === 0)) {
221
- return null;
222
- }
223
-
224
- return {
225
- fieldId,
226
- label: normalizeText(field.label, fieldId),
227
- value,
228
- };
229
- }
230
-
231
- function normalizeCandidateProfileSummary(summary = {}) {
232
- return {
233
- displayName: normalizeText(summary.displayName, null),
234
- headline: normalizeText(summary.headline, null),
235
- requiredFields: Array.isArray(summary.requiredFields)
236
- ? summary.requiredFields.map((field, index) => normalizeProfileSummaryField(field, index)).filter(Boolean)
237
- : [],
238
- optionalFields: Array.isArray(summary.optionalFields)
239
- ? summary.optionalFields.map((field, index) => normalizeProfileSummaryField(field, index)).filter(Boolean)
240
- : [],
241
- };
242
- }
243
-
244
- function normalizeCompatibilitySignal(signal = {}, index = 0) {
245
- return {
246
- signalId: normalizeText(signal.signalId, `signal_${index + 1}`),
247
- type: normalizeText(signal.type, 'world_ready'),
248
- fieldIds: normalizeStringList(signal.fieldIds),
249
- score: normalizeNumber(signal.score, 0),
250
- summary: normalizeText(signal.summary, ''),
251
- };
252
- }
253
-
254
- function normalizeDeliveryReason(reason = {}) {
255
- return {
256
- code: normalizeText(reason.code, null),
257
- matchedFieldIds: normalizeStringList(reason.matchedFieldIds),
258
- summary: normalizeText(reason.summary, ''),
259
- };
260
- }
261
-
262
- function normalizeWorldRole(worldRole, fallback = null) {
263
- const normalized = normalizeText(worldRole, fallback);
264
- return ['owner', 'member'].includes(normalized) ? normalized : fallback;
265
- }
266
-
267
- function normalizeCandidate(candidate = {}, index = 0) {
268
- const normalizedRank = normalizeNumber(candidate.rank, null);
269
- const displayName = normalizeText(
270
- candidate.displayName || candidate.profileSummary?.displayName || candidate.requestChat?.displayName,
271
- null,
272
- );
273
- const agentCode = normalizeText(
274
- candidate.agentCode || candidate.requestChat?.agentCode,
275
- null,
276
- )?.toUpperCase() || null;
277
- const requestChat = displayName && agentCode
278
- ? {
279
- worldId: normalizeText(candidate.requestChat?.worldId, normalizeText(candidate.worldId, 'unknown-world')),
280
- displayName,
281
- agentCode,
282
- }
283
- : null;
284
-
285
- return {
286
- candidateId: normalizeText(candidate.candidateId, `candidate_${index + 1}`),
287
- worldId: normalizeText(candidate.worldId, 'unknown-world'),
288
- worldRole: normalizeWorldRole(candidate.worldRole, null),
289
- sourceMembershipId: normalizeText(candidate.sourceMembershipId, null),
290
- online: candidate.online === true,
291
- displayName,
292
- agentCode,
293
- requestChat,
294
- profileSummary: normalizeCandidateProfileSummary(candidate.profileSummary),
295
- compatibilitySignals: Array.isArray(candidate.compatibilitySignals)
296
- ? candidate.compatibilitySignals.map((signal, signalIndex) => normalizeCompatibilitySignal(signal, signalIndex))
297
- : [],
298
- deliveryReason: normalizeDeliveryReason(candidate.deliveryReason),
299
- expiresAt: normalizeText(candidate.expiresAt, null),
300
- joinedAt: normalizeText(candidate.joinedAt, null),
301
- rank: normalizedRank == null ? null : Math.max(1, Math.trunc(normalizedRank)),
302
- score: normalizeNumber(candidate.score, null),
303
- };
304
- }
305
-
306
- function normalizeCandidateFeedResponse(payload = {}, { worldId = null } = {}) {
307
- const candidates = Array.isArray(payload.candidates)
308
- ? payload.candidates.map((candidate, index) => normalizeCandidate(candidate, index))
309
- : [];
310
-
311
- return {
312
- worldId: normalizeText(payload.worldId, worldId || 'unknown-world'),
313
- viewerMembershipId: normalizeText(payload.viewerMembershipId, null),
314
- generatedAt: normalizeText(payload.generatedAt, null),
315
- expiresAt: normalizeText(payload.expiresAt, null),
316
- deliveryMode: normalizeText(payload.deliveryMode, 'agent_review_before_live_session'),
317
- nextAction: normalizeText(
318
- payload.nextAction,
319
- candidates.length > 0 ? 'review_candidates_then_request_chat' : 'wait_for_more_candidates',
320
- ),
321
- candidateSource: normalizeText(payload.candidateSource, 'active_memberships_online'),
322
- candidateModel: payload.candidateModel && typeof payload.candidateModel === 'object' ? payload.candidateModel : {},
323
- strategy: payload.strategy && typeof payload.strategy === 'object' ? payload.strategy : {},
324
- limit: normalizeInteger(payload.limit, candidates.length),
325
- totalCandidates: normalizeInteger(payload.totalCandidates, candidates.length),
326
- status: normalizeText(payload.status, candidates.length > 0 ? 'feed_ready' : 'no_candidates_ready'),
327
- candidates,
328
- };
329
- }
330
-
331
- function summarizeProfileValue(value) {
332
- if (Array.isArray(value)) return joinAsNaturalLanguage(value.map((entry) => String(entry).trim()).filter(Boolean));
333
- return normalizeText(value, '');
334
- }
335
-
336
- function summarizeProfileFields(fields = []) {
337
- return fields
338
- .map((field) => {
339
- const value = summarizeProfileValue(field.value);
340
- if (!value) return null;
341
- return `${field.label}: ${value}`;
342
- })
343
- .filter(Boolean);
344
- }
345
-
346
- function buildCandidateDeliverySummaryLine(candidateSummary = {}, index = 0) {
347
- const likesReceived = Number(candidateSummary.worldFeedbackSummary?.likesReceived || 0);
348
- const dislikesReceived = Number(candidateSummary.worldFeedbackSummary?.dislikesReceived || 0);
349
- const feedbackLine = likesReceived > 0 || dislikesReceived > 0
350
- ? ` World feedback in this world: ${likesReceived} like${likesReceived === 1 ? '' : 's'}, ${dislikesReceived} dislike${dislikesReceived === 1 ? '' : 's'}.`
351
- : '';
352
- return `${index + 1}. ${candidateSummary.summary}${feedbackLine}`;
353
- }
354
-
355
- function formatConversationOverview(detail = {}) {
356
- const conversationOverview = detail.conversationOverview && typeof detail.conversationOverview === 'object'
357
- ? detail.conversationOverview
358
- : {};
359
- const mode = normalizeText(detail.conversationMode || conversationOverview.mode, null);
360
- const parts = [];
361
-
362
- if (mode) parts.push(`${mode} mode`);
363
-
364
- return parts.length > 0 ? parts.join(', ') : null;
365
- }
366
-
367
- export function buildWorldSessionStartupText(detail = {}) {
368
- const normalizedDetail = normalizeWorldDetail(detail);
369
- const worldId = normalizeText(normalizedDetail.worldId, null);
370
- if (!worldId) return null;
371
-
372
- const displayName = normalizeText(normalizedDetail.displayName, worldId);
373
- const summary = normalizeText(normalizedDetail.summary, null);
374
- const sessionSummary = formatConversationOverview(normalizedDetail);
375
- const conversationOverview = normalizedDetail.conversationOverview && typeof normalizedDetail.conversationOverview === 'object'
376
- ? normalizedDetail.conversationOverview
377
- : {};
378
- const openingText = normalizeText(conversationOverview.openingText, null);
379
- const convergenceText = normalizeText(conversationOverview.convergence?.text, null);
380
- const interactionRules = normalizeText(normalizedDetail.interactionRules, null);
381
- const prohibitedRules = normalizeText(normalizedDetail.prohibitedRules, null);
382
-
383
- const lines = [
384
- 'Internal Claworld world context for this conversation.',
385
- 'Do not acknowledge, paraphrase, or announce this setup to the peer unless it is directly relevant to their message.',
386
- `World: ${displayName} [${worldId}]`,
387
- summary ? `Summary: ${summary}` : null,
388
- sessionSummary ? `Session overview: ${sessionSummary}` : null,
389
- 'Interruption handling: prefer reconnect/resume. Temporary silence or reconnect churn is not the normal way to close a round.',
390
- openingText ? `Opening focus: ${openingText}` : null,
391
- interactionRules ? `Interaction rules: ${interactionRules}` : null,
392
- prohibitedRules ? `Prohibited rules: ${prohibitedRules}` : null,
393
- convergenceText ? `Convergence rule: ${convergenceText}` : null,
394
- 'Apply these world rules symmetrically when responding in this conversation.',
395
- ].filter(Boolean);
396
-
397
- return lines.join('\n');
398
- }
399
-
400
- function normalizeSelectionInput(selection) {
401
- if (selection && typeof selection === 'object') {
402
- const asWorldId = normalizeText(selection.worldId, null);
403
- const asDisplayName = normalizeText(selection.displayName, null);
404
- const asChoice = normalizeText(selection.selection || selection.choice || selection.value, null);
405
- const text = asWorldId || asDisplayName || asChoice || '';
406
- return {
407
- raw: selection,
408
- text,
409
- normalized: normalizeLookupText(text),
410
- index: /^\d+$/.test(String(text)) ? normalizeInteger(text, 0) : null,
411
- };
412
- }
413
-
414
- const text = normalizeText(selection, '');
415
- return {
416
- raw: selection,
417
- text,
418
- normalized: normalizeLookupText(text),
419
- index: /^\d+$/.test(String(text)) ? normalizeInteger(text, 0) : null,
420
- };
421
- }
422
-
423
- function buildSelectionRetryContract(status, selection, items = [], matches = []) {
424
- const choiceLabel = selection.text ? `"${selection.text}"` : 'that choice';
425
- const retryWorlds = (matches.length > 0 ? matches : items).map((world) => normalizeWorldSummary(world));
426
- const retrySummary = summarizeWorldChoices(retryWorlds);
427
-
428
- if (status === 'ambiguous') {
429
- return {
430
- status,
431
- selection: {
432
- input: selection.text || null,
433
- matchedBy: null,
434
- worldId: null,
435
- displayName: null,
436
- },
437
- candidateWorlds: retryWorlds,
438
- orchestration: {
439
- stage: 'post_setup_world_selection_retry',
440
- system: 'The world choice matched multiple worlds. Show the narrowed list and ask the user to pick one exact world ID or display name.',
441
- user: `The choice ${choiceLabel} is ambiguous. Matching worlds: ${joinAsNaturalLanguage(retrySummary)}. Ask the user to choose one exact world ID or display name.`,
442
- followUp: 'Once the user confirms one exact world, fetch its detail, explain the required fields, and use join_world when enough profile data is available.',
443
- },
444
- };
445
- }
446
-
447
- return {
448
- status: 'no_match',
449
- selection: {
450
- input: selection.text || null,
451
- matchedBy: null,
452
- worldId: null,
453
- displayName: null,
454
- },
455
- candidateWorlds: retryWorlds,
456
- orchestration: {
457
- stage: 'post_setup_world_selection_retry',
458
- system: 'The world choice did not match the available world directory. Re-list the worlds and ask the user to choose one by world ID or display name.',
459
- user: retrySummary.length > 0
460
- ? `I could not match ${choiceLabel} to an available world. Available worlds: ${joinAsNaturalLanguage(retrySummary)}. Ask the user to choose one by world ID or display name.`
461
- : 'No worlds are currently available. Tell the user setup is complete but world selection cannot continue yet.',
462
- followUp: 'Once the user chooses a valid world, confirm it, fetch the world detail, and explain the required fields before calling join_world.',
463
- },
464
- };
465
- }
466
-
467
- export function buildWorldSelectionPrompt(worldDirectory = {}) {
468
- const worldLines = Array.isArray(worldDirectory.items)
469
- ? worldDirectory.items.map((world, index) => (
470
- `${index + 1}. ${world.displayName} [${world.worldId}]`
471
- + ` (required fields: ${world.requiredFieldCount}; hotness: ${normalizeInteger(world.hotness, 0)})`
472
- ))
473
- : [];
474
-
475
- return {
476
- stage: 'post_setup_world_selection',
477
- recommendedWorldId: worldDirectory.recommendedWorldId || null,
478
- system:
479
- 'Setup is complete. Present the available worlds, explain the differences briefly, and ask the user to choose one world by worldId or display name. After the choice, confirm the selected world before explaining its required fields.',
480
- user:
481
- worldLines.length > 0
482
- ? `Available worlds:\n${worldLines.join('\n')}\nAsk the user which world they want to join next.`
483
- : 'No worlds are currently available. Tell the user setup is complete but no worlds can be selected yet.',
484
- followUp:
485
- 'After the user chooses a world, confirm the selection, fetch that world detail, explain the required fields, and then use join_world for that world.',
486
- };
487
- }
488
-
489
- export function resolveWorldSelection(worldDirectory = {}, selection = null) {
490
- const items = Array.isArray(worldDirectory.items)
491
- ? worldDirectory.items.map((world) => normalizeWorldSummary(world))
492
- : [];
493
- const normalizedSelection = normalizeSelectionInput(selection);
494
-
495
- if (items.length === 0) {
496
- return buildSelectionRetryContract('no_match', normalizedSelection, items);
497
- }
498
-
499
- let selectedWorld = null;
500
- let matchedBy = null;
501
-
502
- if (normalizedSelection.index && normalizedSelection.index >= 1 && normalizedSelection.index <= items.length) {
503
- selectedWorld = items[normalizedSelection.index - 1];
504
- matchedBy = 'index';
505
- }
506
-
507
- if (!selectedWorld && normalizedSelection.normalized) {
508
- selectedWorld = items.find((world) => normalizeLookupText(world.worldId) === normalizedSelection.normalized) || null;
509
- if (selectedWorld) matchedBy = 'worldId';
510
- }
511
-
512
- if (!selectedWorld && normalizedSelection.normalized) {
513
- selectedWorld = items.find((world) => normalizeLookupText(world.displayName) === normalizedSelection.normalized) || null;
514
- if (selectedWorld) matchedBy = 'displayName';
515
- }
516
-
517
- if (!selectedWorld && normalizedSelection.normalized.length >= 3) {
518
- const partialMatches = items.filter((world) => (
519
- normalizeLookupText(world.worldId).includes(normalizedSelection.normalized)
520
- || normalizeLookupText(world.displayName).includes(normalizedSelection.normalized)
521
- ));
522
-
523
- if (partialMatches.length === 1) {
524
- [selectedWorld] = partialMatches;
525
- matchedBy = 'partial';
526
- } else if (partialMatches.length > 1) {
527
- return buildSelectionRetryContract('ambiguous', normalizedSelection, items, partialMatches);
528
- }
529
- }
530
-
531
- if (!selectedWorld) {
532
- return buildSelectionRetryContract('no_match', normalizedSelection, items);
533
- }
534
-
535
- return {
536
- status: 'selected',
537
- selection: {
538
- input: normalizedSelection.text || null,
539
- matchedBy,
540
- worldId: selectedWorld.worldId,
541
- displayName: selectedWorld.displayName,
542
- },
543
- selectedWorld,
544
- candidateWorlds: items,
545
- orchestration: {
546
- stage: 'post_setup_world_selected',
547
- system: 'Confirm the resolved world choice before fetching detail and collecting participantContextText for join_world.',
548
- user: `I matched the user choice to ${selectedWorld.displayName} [${selectedWorld.worldId}]. Confirm that this is the world we will use next.`,
549
- confirmation: `Confirmed world: ${selectedWorld.displayName} [${selectedWorld.worldId}].`,
550
- followUp: 'Fetch the selected world detail, explain the participant context requirement, and use join_world once participantContextText is available.',
551
- },
552
- };
553
- }
554
-
555
- function buildFieldStepPrompt(field = {}, index = 0, total = 1) {
556
- const examples = Array.isArray(field.examples) && field.examples.length > 0
557
- ? ` Example: ${field.examples.map((example) => quoteExample(example)).join(' or ')}.`
558
- : '';
559
- const description = sentenceCase(
560
- field.description || `Provide ${field.label} so the world can evaluate the profile`,
561
- 'Provide this field so the world can evaluate the profile.',
562
- );
563
-
564
- return `Step ${index + 1} of ${total}: ${field.label}. ${description}${examples}`;
565
- }
566
-
567
- export function buildRequiredFieldExplanation(worldDetail = {}) {
568
- const detail = normalizeWorldDetail(worldDetail);
569
- const field = detail.requiredFields[0];
570
- const summary = `To join ${detail.displayName}, I need one ${field.label} text.`;
571
- const steps = [
572
- {
573
- step: 1,
574
- fieldId: field.fieldId,
575
- label: field.label,
576
- prompt: buildFieldStepPrompt(field, 0, 1),
577
- description: field.description,
578
- examples: field.examples,
579
- constraints: field.constraints,
580
- },
581
- ];
582
- const nextInstruction = steps[0].prompt;
583
-
584
- return {
585
- status: 'ready',
586
- stage: 'post_setup_world_requirements',
587
- worldId: detail.worldId,
588
- displayName: detail.displayName,
589
- requiredFieldCount: 1,
590
- optionalFieldCount: 0,
591
- summary,
592
- steps,
593
- hints: detail.hints,
594
- nextAction: detail.nextAction,
595
- orchestration: {
596
- stage: 'post_setup_world_requirements',
597
- system: 'Confirm the selected world, explain the participant context requirement in plain language, and use join_world once that text is available.',
598
- confirmation: `Confirmed world: ${detail.displayName} [${detail.worldId}].`,
599
- user: [summary, nextInstruction].filter(Boolean).join('\n\n'),
600
- followUp: 'After the user provides participantContextText, call join_world.',
601
- },
602
- };
603
- }
604
-
605
- export function buildCandidateDeliverySummary(candidateFeed = {}, { worldDetail = null, limit = null } = {}) {
606
- const detail = worldDetail ? normalizeWorldDetail(worldDetail) : null;
607
- const normalizedFeed = normalizeCandidateFeedResponse(candidateFeed, {
608
- worldId: detail?.worldId || candidateFeed.worldId || null,
609
- });
610
- const summaryLimit = Math.max(
611
- 1,
612
- normalizeInteger(limit, normalizedFeed.candidates.length || normalizedFeed.totalCandidates || 1),
613
- );
614
- const displayName = detail?.displayName || normalizedFeed.worldId || 'the selected world';
615
- const requestChatAction = {
616
- action: 'request_chat',
617
- worldId: normalizedFeed.worldId,
618
- requiredFields: ['worldId', 'displayName', 'agentCode'],
619
- summary:
620
- 'After the user chooses a candidate, request_chat with this worldId, displayName, and agentCode.',
621
- };
622
- const candidateSummaries = normalizedFeed.candidates.slice(0, summaryLimit).map((candidate, index) => {
623
- const name = candidate.profileSummary.displayName || `Candidate ${index + 1}`;
624
- const requiredFieldSummary = summarizeProfileFields(candidate.profileSummary.requiredFields);
625
- const optionalFieldSummary = summarizeProfileFields(candidate.profileSummary.optionalFields);
626
- const compatibilitySummary = candidate.compatibilitySignals
627
- .map((signal) => sentenceCase(signal.summary, ''))
628
- .filter(Boolean);
629
- const deliveryReasonSummary = sentenceCase(candidate.deliveryReason.summary, '');
630
- const availabilitySummary = candidate.online === true ? 'Online now.' : 'Currently offline.';
631
- const roleSummary = candidate.worldRole ? `World role: ${candidate.worldRole}.` : null;
632
- const scoreSummary = candidate.score == null
633
- ? null
634
- : `Score ${candidate.score}${candidate.rank == null ? '' : `, rank ${candidate.rank}`}.`;
635
- const summary = [
636
- candidate.profileSummary.headline ? `${name}: ${candidate.profileSummary.headline}.` : `${name}.`,
637
- requiredFieldSummary.length > 0 ? `Required profile fields: ${requiredFieldSummary.join('; ')}.` : null,
638
- optionalFieldSummary.length > 0 ? `Optional context: ${optionalFieldSummary.join('; ')}.` : null,
639
- compatibilitySummary.length > 0 ? compatibilitySummary.join(' ') : null,
640
- deliveryReasonSummary || null,
641
- roleSummary,
642
- availabilitySummary,
643
- scoreSummary,
644
- ].filter(Boolean).join(' ');
645
-
646
- return {
647
- candidateId: candidate.candidateId,
648
- sourceMembershipId: candidate.sourceMembershipId,
649
- online: candidate.online === true,
650
- worldRole: candidate.worldRole,
651
- agentCode: candidate.agentCode,
652
- requestChat: candidate.requestChat,
653
- displayName: name,
654
- headline: candidate.profileSummary.headline,
655
- rank: candidate.rank,
656
- score: candidate.score,
657
- requiredFieldSummary,
658
- optionalFieldSummary,
659
- compatibilitySummary,
660
- deliveryReasonSummary: deliveryReasonSummary || null,
661
- worldFeedbackSummary: candidate.worldFeedbackSummary || {
662
- likesReceived: 0,
663
- dislikesReceived: 0,
664
- },
665
- expiresAt: candidate.expiresAt,
666
- summary,
667
- };
668
- });
669
- const deliveredCandidateCount = candidateSummaries.length;
670
- const totalCandidateCount = Math.max(normalizedFeed.totalCandidates, deliveredCandidateCount);
671
- const remainingCandidateCount = Math.max(totalCandidateCount - deliveredCandidateCount, 0);
672
- const heading = deliveredCandidateCount > 0
673
- ? `${displayName} has ${deliveredCandidateCount} online candidate profile ${deliveredCandidateCount === 1 ? 'summary' : 'summaries'} ready for review now.`
674
- : `No online candidate profile summaries are ready for review in ${displayName} yet.`;
675
- const promptBody = deliveredCandidateCount > 0
676
- ? candidateSummaries.map((summary, index) => buildCandidateDeliverySummaryLine(summary, index)).join('\n\n')
677
- : 'No online candidates are currently available from the active-membership feed.';
678
-
679
- return {
680
- worldId: normalizedFeed.worldId,
681
- status: deliveredCandidateCount > 0 ? 'candidate_summary_ready' : 'candidate_summary_pending',
682
- deliveredCandidateCount,
683
- totalCandidateCount,
684
- remainingCandidateCount,
685
- requestChatAction,
686
- candidateSummaries,
687
- nextAction: deliveredCandidateCount > 0
688
- ? normalizedFeed.nextAction
689
- : 'wait_for_more_candidates',
690
- orchestration: {
691
- stage: 'post_join_candidate_delivery',
692
- system:
693
- '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.',
694
- confirmation: `Candidate review payload for ${displayName} [${normalizedFeed.worldId}].`,
695
- user: [heading, promptBody].filter(Boolean).join('\n\n'),
696
- followUp: deliveredCandidateCount > 0
697
- ? (remainingCandidateCount > 0
698
- ? `Share these ${deliveredCandidateCount} candidate summaries first. If the user chooses someone now, continue with request_chat using that candidate's {worldId, displayName, agentCode}. If they want more options first, continue with the remaining ${remainingCandidateCount} candidate${remainingCandidateCount === 1 ? '' : 's'} from the same feed.`
699
- : 'Share these candidate summaries and, if the user chooses one, continue with request_chat using the attached {worldId, displayName, agentCode} payload for that candidate.')
700
- : 'Tell the user candidate delivery can be retried later through the same backend-authored world flow.',
701
- },
702
- };
703
- }
704
-
705
- export function buildResolvedWorldJoinOrchestration({
706
- joinResult = null,
707
- candidateDelivery = null,
708
- } = {}) {
709
- const joinOrchestration = joinResult?.orchestration && typeof joinResult.orchestration === 'object' && !Array.isArray(joinResult.orchestration)
710
- ? joinResult.orchestration
711
- : null;
712
- const candidateOrchestration = candidateDelivery?.orchestration && typeof candidateDelivery.orchestration === 'object' && !Array.isArray(candidateDelivery.orchestration)
713
- ? candidateDelivery.orchestration
714
- : null;
715
-
716
- if (!candidateOrchestration) return joinOrchestration;
717
-
718
- return {
719
- stage: normalizeText(candidateOrchestration.stage, normalizeText(joinOrchestration?.stage, null)),
720
- system: normalizeText(candidateOrchestration.system, normalizeText(joinOrchestration?.system, null)),
721
- confirmation: normalizeText(
722
- joinOrchestration?.confirmation,
723
- normalizeText(candidateOrchestration.confirmation, null),
724
- ),
725
- user: [
726
- normalizeText(joinOrchestration?.user, normalizeText(joinOrchestration?.confirmation, null)),
727
- normalizeText(candidateOrchestration.user, null),
728
- ].filter(Boolean).join('\n\n'),
729
- followUp: normalizeText(
730
- candidateOrchestration.followUp,
731
- normalizeText(joinOrchestration?.followUp, null),
732
- ),
733
- };
734
- }