@xfxstudio/claworld 0.1.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.
- package/README.md +60 -0
- package/bin/claworld.mjs +9 -0
- package/index.js +51 -0
- package/openclaw.plugin.json +470 -0
- package/package.json +76 -0
- package/setup-entry.js +6 -0
- package/src/lib/accepted-chat-kickoff.js +192 -0
- package/src/lib/agent-address.js +46 -0
- package/src/lib/agent-profile.js +69 -0
- package/src/lib/http-auth.js +151 -0
- package/src/lib/policy.js +118 -0
- package/src/lib/runtime-errors.js +149 -0
- package/src/lib/runtime-guidance.js +458 -0
- package/src/openclaw/index.js +53 -0
- package/src/openclaw/installer/cli.js +349 -0
- package/src/openclaw/installer/constants.js +6 -0
- package/src/openclaw/installer/core.js +1548 -0
- package/src/openclaw/installer/doctor.js +690 -0
- package/src/openclaw/installer/workspace-contract.js +403 -0
- package/src/openclaw/plugin/account-identity.js +66 -0
- package/src/openclaw/plugin/claworld-channel-plugin.js +3118 -0
- package/src/openclaw/plugin/config-schema.js +464 -0
- package/src/openclaw/plugin/lifecycle.js +114 -0
- package/src/openclaw/plugin/managed-config.js +648 -0
- package/src/openclaw/plugin/onboarding.js +291 -0
- package/src/openclaw/plugin/register.js +961 -0
- package/src/openclaw/plugin/relay-client.js +783 -0
- package/src/openclaw/plugin/runtime.js +12 -0
- package/src/openclaw/protocol/relay-event-protocol.js +31 -0
- package/src/openclaw/runtime/canonical-result-builder.js +116 -0
- package/src/openclaw/runtime/demo-session-bootstrap.js +37 -0
- package/src/openclaw/runtime/feedback-helper.js +145 -0
- package/src/openclaw/runtime/inbound-session-router.js +36 -0
- package/src/openclaw/runtime/outbound-session-bridge.js +17 -0
- package/src/openclaw/runtime/product-shell-helper.js +1712 -0
- package/src/openclaw/runtime/runtime-path.js +19 -0
- package/src/openclaw/runtime/system-message-orchestrator.js +1 -0
- package/src/openclaw/runtime/tool-contracts.js +714 -0
- package/src/openclaw/runtime/tool-inventory.js +92 -0
- package/src/openclaw/runtime/world-moderation-helper.js +415 -0
- package/src/openclaw/runtime/world-session-startup.js +1 -0
- package/src/product-shell/catalog/default-world-catalog.js +296 -0
- package/src/product-shell/contracts/candidate-feed.js +330 -0
- package/src/product-shell/contracts/chat-request-approval-policy.js +98 -0
- package/src/product-shell/contracts/world-manifest.js +435 -0
- package/src/product-shell/contracts/world-orchestration.js +1024 -0
- package/src/product-shell/feedback/feedback-contract.js +13 -0
- package/src/product-shell/feedback/feedback-routes.js +98 -0
- package/src/product-shell/feedback/feedback-service.js +254 -0
- package/src/product-shell/index.js +163 -0
- package/src/product-shell/matching/matchmaking-service.js +340 -0
- package/src/product-shell/membership/membership-service.js +277 -0
- package/src/product-shell/onboarding/onboarding-routes.js +37 -0
- package/src/product-shell/onboarding/onboarding-service.js +230 -0
- package/src/product-shell/orchestration/session-orchestrator.js +38 -0
- package/src/product-shell/results/result-service.js +15 -0
- package/src/product-shell/search/search-service.js +359 -0
- package/src/product-shell/social/chat-request-approval-policy.js +332 -0
- package/src/product-shell/social/chat-request-routes.js +108 -0
- package/src/product-shell/social/chat-request-service.js +632 -0
- package/src/product-shell/social/friend-routes.js +82 -0
- package/src/product-shell/social/friend-service.js +560 -0
- package/src/product-shell/social/social-routes.js +21 -0
- package/src/product-shell/social/social-service.js +140 -0
- package/src/product-shell/worlds/world-admin-service.js +705 -0
- package/src/product-shell/worlds/world-authorization.js +135 -0
- package/src/product-shell/worlds/world-broadcast-service.js +299 -0
- package/src/product-shell/worlds/world-routes.js +410 -0
- package/src/product-shell/worlds/world-service.js +89 -0
|
@@ -0,0 +1,1024 @@
|
|
|
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 isEmptyProfileValue(value) {
|
|
34
|
+
if (value == null) return true;
|
|
35
|
+
if (typeof value === 'string') return value.trim() === '';
|
|
36
|
+
if (Array.isArray(value)) return value.length === 0 || value.every((entry) => isEmptyProfileValue(entry));
|
|
37
|
+
if (typeof value === 'object') {
|
|
38
|
+
const entries = Object.values(value);
|
|
39
|
+
return entries.length === 0 || entries.every((entry) => isEmptyProfileValue(entry));
|
|
40
|
+
}
|
|
41
|
+
return false;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
function normalizeProfileValue(value) {
|
|
45
|
+
if (typeof value === 'string') return value.trim();
|
|
46
|
+
if (Array.isArray(value)) {
|
|
47
|
+
return value
|
|
48
|
+
.map((entry) => normalizeProfileValue(entry))
|
|
49
|
+
.filter((entry) => entry !== undefined && !isEmptyProfileValue(entry));
|
|
50
|
+
}
|
|
51
|
+
if (value && typeof value === 'object') {
|
|
52
|
+
return Object.fromEntries(
|
|
53
|
+
Object.entries(value)
|
|
54
|
+
.filter(([, entry]) => entry !== undefined)
|
|
55
|
+
.map(([key, entry]) => [key, normalizeProfileValue(entry)]),
|
|
56
|
+
);
|
|
57
|
+
}
|
|
58
|
+
return value;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
export function normalizeProfile(profile = {}) {
|
|
62
|
+
if (!profile || typeof profile !== 'object' || Array.isArray(profile)) return {};
|
|
63
|
+
|
|
64
|
+
return Object.fromEntries(
|
|
65
|
+
Object.entries(profile)
|
|
66
|
+
.filter(([key, value]) => normalizeText(key, null) && value !== undefined)
|
|
67
|
+
.map(([key, value]) => [normalizeText(key, null), normalizeProfileValue(value)]),
|
|
68
|
+
);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
export function mergeProfileState(profile = {}, profileUpdate = {}) {
|
|
72
|
+
return {
|
|
73
|
+
...normalizeProfile(profile),
|
|
74
|
+
...normalizeProfile(profileUpdate),
|
|
75
|
+
};
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
function normalizeLookupText(value) {
|
|
79
|
+
return normalizeText(value, '')?.toLowerCase() || '';
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
function sentenceCase(value, fallback = '') {
|
|
83
|
+
const normalized = normalizeText(value, fallback);
|
|
84
|
+
if (!normalized) return fallback;
|
|
85
|
+
return /[.!?]$/.test(normalized) ? normalized : `${normalized}.`;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
function quoteExample(example) {
|
|
89
|
+
return `"${String(example).trim()}"`;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
function joinAsNaturalLanguage(values = []) {
|
|
93
|
+
const items = values.filter(Boolean);
|
|
94
|
+
if (items.length === 0) return '';
|
|
95
|
+
if (items.length === 1) return items[0];
|
|
96
|
+
if (items.length === 2) return `${items[0]} and ${items[1]}`;
|
|
97
|
+
return `${items.slice(0, -1).join(', ')}, and ${items.at(-1)}`;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
function summarizeWorldChoices(items = []) {
|
|
101
|
+
return items.map((world) => `${world.displayName} [${world.worldId}]`);
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
function normalizeWorldSummary(world = {}) {
|
|
105
|
+
const summary = world.agentSummary && typeof world.agentSummary === 'object' ? world.agentSummary : world;
|
|
106
|
+
const rawWorldId = world.worldId || summary.worldId;
|
|
107
|
+
|
|
108
|
+
return {
|
|
109
|
+
worldId: normalizeText(rawWorldId, 'unknown-world'),
|
|
110
|
+
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'),
|
|
113
|
+
hotness: normalizeInteger(summary.hotness || world.hotness || world.activatedMemberCount, 0),
|
|
114
|
+
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
|
+
};
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
function normalizeField(field = {}, index = 0, { required = false } = {}) {
|
|
121
|
+
const fieldId = normalizeText(field.fieldId || field.id, `field_${index + 1}`);
|
|
122
|
+
return {
|
|
123
|
+
fieldId,
|
|
124
|
+
label: normalizeText(field.label, fieldId),
|
|
125
|
+
type: normalizeText(field.type, 'string'),
|
|
126
|
+
source: normalizeText(field.source, 'profile'),
|
|
127
|
+
required: field.required === true || required,
|
|
128
|
+
description: normalizeText(field.description, null),
|
|
129
|
+
examples: normalizeStringList(field.examples),
|
|
130
|
+
constraints: field.constraints && typeof field.constraints === 'object' ? field.constraints : {},
|
|
131
|
+
};
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
function normalizeSearchSchema(payload = {}, { worldId = null, fallbackFields = [] } = {}) {
|
|
135
|
+
const rawInputFields = Array.isArray(payload.inputFields) && payload.inputFields.length > 0
|
|
136
|
+
? payload.inputFields
|
|
137
|
+
: fallbackFields;
|
|
138
|
+
const inputFields = rawInputFields.map((field, index) => normalizeField(field, index, { required: false }));
|
|
139
|
+
const inputFieldIds = normalizeStringList(
|
|
140
|
+
Array.isArray(payload.inputFieldIds)
|
|
141
|
+
? payload.inputFieldIds
|
|
142
|
+
: inputFields.map((field) => field.fieldId),
|
|
143
|
+
);
|
|
144
|
+
|
|
145
|
+
return {
|
|
146
|
+
modelId: normalizeText(payload.modelId, worldId ? `${worldId}.search.v1` : 'world.search.v1'),
|
|
147
|
+
worldId: normalizeText(payload.worldId, worldId || 'unknown-world'),
|
|
148
|
+
mode: normalizeText(payload.mode, 'membership_profile_search'),
|
|
149
|
+
previewRoute: normalizeText(payload.previewRoute, worldId ? `/v1/worlds/${worldId}/search` : '/v1/worlds/:worldId/search'),
|
|
150
|
+
inputFieldIds,
|
|
151
|
+
inputFields,
|
|
152
|
+
resultFields: normalizeStringList(payload.resultFields),
|
|
153
|
+
viewerRequirement: normalizeText(payload.viewerRequirement, 'active_membership'),
|
|
154
|
+
onlineOnly: payload.onlineOnly !== false,
|
|
155
|
+
defaultLimit: normalizeInteger(payload.defaultLimit, 10),
|
|
156
|
+
summary: normalizeText(payload.summary, ''),
|
|
157
|
+
hints: normalizeStringList(payload.hints),
|
|
158
|
+
status: normalizeText(payload.status, 'phase1_world_search'),
|
|
159
|
+
};
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
function normalizeWorldDetail(payload = {}) {
|
|
163
|
+
if (Array.isArray(payload.requiredFields) || Array.isArray(payload.optionalFields)) {
|
|
164
|
+
const requiredFields = Array.isArray(payload.requiredFields)
|
|
165
|
+
? payload.requiredFields.map((field, index) => normalizeField(field, index, { required: true }))
|
|
166
|
+
: [];
|
|
167
|
+
const optionalFields = Array.isArray(payload.optionalFields)
|
|
168
|
+
? payload.optionalFields.map((field, index) => normalizeField(field, index, { required: false }))
|
|
169
|
+
: [];
|
|
170
|
+
const normalizedWorldId = normalizeText(payload.worldId, 'unknown-world');
|
|
171
|
+
|
|
172
|
+
return {
|
|
173
|
+
status: normalizeText(payload.status, 'ready'),
|
|
174
|
+
source: normalizeText(payload.source, 'product_shell'),
|
|
175
|
+
worldId: normalizedWorldId,
|
|
176
|
+
displayName: normalizeText(payload.displayName, normalizedWorldId),
|
|
177
|
+
summary: normalizeText(payload.summary, ''),
|
|
178
|
+
description: normalizeText(payload.description, normalizeText(payload.summary, '')),
|
|
179
|
+
category: normalizeText(payload.category, 'general'),
|
|
180
|
+
requiredFieldCount: normalizeInteger(payload.requiredFieldCount, requiredFields.length) || requiredFields.length,
|
|
181
|
+
optionalFieldCount: normalizeInteger(payload.optionalFieldCount, optionalFields.length) || optionalFields.length,
|
|
182
|
+
matchingMode: normalizeText(payload.matchingMode, 'manual_review'),
|
|
183
|
+
sessionMode: normalizeText(payload.sessionMode, 'a2a'),
|
|
184
|
+
interactionRules: normalizeText(payload.interactionRules, null),
|
|
185
|
+
prohibitedRules: normalizeText(payload.prohibitedRules, null),
|
|
186
|
+
ratingRules: normalizeText(payload.ratingRules, null),
|
|
187
|
+
adminAgentIds: normalizeStringList(payload.adminAgentIds),
|
|
188
|
+
eligibility: normalizeText(payload.eligibility, 'active'),
|
|
189
|
+
broadcast: normalizeBroadcastConfig(payload.broadcast),
|
|
190
|
+
requiredFields,
|
|
191
|
+
optionalFields,
|
|
192
|
+
hints: normalizeStringList(payload.hints),
|
|
193
|
+
nextAction: normalizeText(payload.nextAction, 'collect_profile_fields_then_call_join'),
|
|
194
|
+
sessionOverview: payload.sessionOverview && typeof payload.sessionOverview === 'object' ? payload.sessionOverview : {},
|
|
195
|
+
matchingOverview: payload.matchingOverview && typeof payload.matchingOverview === 'object' ? payload.matchingOverview : {},
|
|
196
|
+
searchSchema: normalizeSearchSchema(payload.searchSchema || {}, {
|
|
197
|
+
worldId: normalizedWorldId,
|
|
198
|
+
fallbackFields: requiredFields,
|
|
199
|
+
}),
|
|
200
|
+
};
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
const world = payload.world && typeof payload.world === 'object' ? payload.world : {};
|
|
204
|
+
const agentSummary = payload.agentSummary && typeof payload.agentSummary === 'object' ? payload.agentSummary : {};
|
|
205
|
+
const joinSchema = payload.joinSchema && typeof payload.joinSchema === 'object' ? payload.joinSchema : {};
|
|
206
|
+
const fieldGuide = payload.fieldGuide && typeof payload.fieldGuide === 'object' ? payload.fieldGuide : {};
|
|
207
|
+
const sessionOverview = payload.sessionOverview && typeof payload.sessionOverview === 'object'
|
|
208
|
+
? payload.sessionOverview
|
|
209
|
+
: {};
|
|
210
|
+
const matchingOverview = payload.matchingOverview && typeof payload.matchingOverview === 'object'
|
|
211
|
+
? payload.matchingOverview
|
|
212
|
+
: {};
|
|
213
|
+
const searchOverview = payload.searchSchema && typeof payload.searchSchema === 'object'
|
|
214
|
+
? payload.searchSchema
|
|
215
|
+
: {};
|
|
216
|
+
|
|
217
|
+
const requiredInput = Array.isArray(fieldGuide.required) && fieldGuide.required.length > 0
|
|
218
|
+
? fieldGuide.required
|
|
219
|
+
: (Array.isArray(joinSchema.requiredFields) ? joinSchema.requiredFields : []);
|
|
220
|
+
const optionalInput = Array.isArray(fieldGuide.optional) && fieldGuide.optional.length > 0
|
|
221
|
+
? fieldGuide.optional
|
|
222
|
+
: (Array.isArray(joinSchema.optionalFields) ? joinSchema.optionalFields : []);
|
|
223
|
+
|
|
224
|
+
const requiredFields = requiredInput.map((field, index) => normalizeField(field, index, { required: true }));
|
|
225
|
+
const optionalFields = optionalInput.map((field, index) => normalizeField(field, index, { required: false }));
|
|
226
|
+
const worldId = normalizeText(payload.worldId || world.worldId || joinSchema.worldId, 'unknown-world');
|
|
227
|
+
const displayName = normalizeText(payload.displayName || agentSummary.displayName || world.displayName, worldId);
|
|
228
|
+
|
|
229
|
+
return {
|
|
230
|
+
status: 'ready',
|
|
231
|
+
source: 'product_shell',
|
|
232
|
+
worldId,
|
|
233
|
+
displayName,
|
|
234
|
+
summary: normalizeText(payload.summary || agentSummary.summary || world.summary, ''),
|
|
235
|
+
description: normalizeText(payload.description || world.description, normalizeText(payload.summary || agentSummary.summary || world.summary, '')),
|
|
236
|
+
category: normalizeText(payload.category || agentSummary.category || world.category, 'general'),
|
|
237
|
+
requiredFieldCount: normalizeInteger(joinSchema.requiredFieldCount, requiredFields.length) || requiredFields.length,
|
|
238
|
+
optionalFieldCount: normalizeInteger(joinSchema.optionalFieldCount, optionalFields.length) || optionalFields.length,
|
|
239
|
+
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'),
|
|
241
|
+
interactionRules: normalizeText(payload.interactionRules || world.interactionRules, null),
|
|
242
|
+
prohibitedRules: normalizeText(payload.prohibitedRules || world.prohibitedRules, null),
|
|
243
|
+
ratingRules: normalizeText(payload.ratingRules || world.ratingRules, null),
|
|
244
|
+
adminAgentIds: normalizeStringList(payload.adminAgentIds || world.adminAgentIds),
|
|
245
|
+
eligibility: normalizeText(payload.eligibility || world.eligibility, 'active'),
|
|
246
|
+
broadcast: normalizeBroadcastConfig(payload.broadcast || world.broadcast),
|
|
247
|
+
requiredFields,
|
|
248
|
+
optionalFields,
|
|
249
|
+
hints: normalizeStringList(payload.hints || joinSchema.hints),
|
|
250
|
+
nextAction: normalizeText(payload.nextAction || joinSchema.nextAction, 'collect_profile_fields_then_call_join'),
|
|
251
|
+
sessionOverview,
|
|
252
|
+
matchingOverview,
|
|
253
|
+
searchSchema: normalizeSearchSchema(searchOverview, {
|
|
254
|
+
worldId,
|
|
255
|
+
fallbackFields: [...requiredFields, ...optionalFields],
|
|
256
|
+
}),
|
|
257
|
+
};
|
|
258
|
+
}
|
|
259
|
+
|
|
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 ? 'join_world_when_membership_persistence_exists' : 'collect_missing_profile_fields',
|
|
305
|
+
),
|
|
306
|
+
};
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
function normalizeProfileSummaryField(field = {}, index = 0) {
|
|
310
|
+
const fieldId = normalizeText(field.fieldId || field.id, `field_${index + 1}`);
|
|
311
|
+
const value = Array.isArray(field.value)
|
|
312
|
+
? normalizeStringList(field.value)
|
|
313
|
+
: normalizeText(field.value, null);
|
|
314
|
+
|
|
315
|
+
if (value == null || (Array.isArray(value) && value.length === 0)) {
|
|
316
|
+
return null;
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
return {
|
|
320
|
+
fieldId,
|
|
321
|
+
label: normalizeText(field.label, fieldId),
|
|
322
|
+
value,
|
|
323
|
+
};
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
function normalizeCandidateProfileSummary(summary = {}) {
|
|
327
|
+
return {
|
|
328
|
+
displayName: normalizeText(summary.displayName, null),
|
|
329
|
+
headline: normalizeText(summary.headline, null),
|
|
330
|
+
requiredFields: Array.isArray(summary.requiredFields)
|
|
331
|
+
? summary.requiredFields.map((field, index) => normalizeProfileSummaryField(field, index)).filter(Boolean)
|
|
332
|
+
: [],
|
|
333
|
+
optionalFields: Array.isArray(summary.optionalFields)
|
|
334
|
+
? summary.optionalFields.map((field, index) => normalizeProfileSummaryField(field, index)).filter(Boolean)
|
|
335
|
+
: [],
|
|
336
|
+
};
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
function normalizeCompatibilitySignal(signal = {}, index = 0) {
|
|
340
|
+
return {
|
|
341
|
+
signalId: normalizeText(signal.signalId, `signal_${index + 1}`),
|
|
342
|
+
type: normalizeText(signal.type, 'world_ready'),
|
|
343
|
+
fieldIds: normalizeStringList(signal.fieldIds),
|
|
344
|
+
score: normalizeNumber(signal.score, 0),
|
|
345
|
+
summary: normalizeText(signal.summary, ''),
|
|
346
|
+
};
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
function normalizeDeliveryReason(reason = {}) {
|
|
350
|
+
return {
|
|
351
|
+
code: normalizeText(reason.code, null),
|
|
352
|
+
matchedFieldIds: normalizeStringList(reason.matchedFieldIds),
|
|
353
|
+
summary: normalizeText(reason.summary, ''),
|
|
354
|
+
};
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
function normalizeCandidateScoreBreakdown(entries = []) {
|
|
358
|
+
if (!Array.isArray(entries)) return [];
|
|
359
|
+
|
|
360
|
+
return entries.map((entry, index) => ({
|
|
361
|
+
signalId: normalizeText(entry.signalId, `score_signal_${index + 1}`),
|
|
362
|
+
label: normalizeText(entry.label, `Signal ${index + 1}`),
|
|
363
|
+
weight: normalizeNumber(entry.weight, 0),
|
|
364
|
+
sourceFieldIds: normalizeStringList(entry.sourceFieldIds),
|
|
365
|
+
matched: entry.matched === true,
|
|
366
|
+
requesterValue: entry.requesterValue ?? null,
|
|
367
|
+
candidateValue: entry.candidateValue ?? null,
|
|
368
|
+
sharedValues: Array.isArray(entry.sharedValues) ? normalizeStringList(entry.sharedValues) : [],
|
|
369
|
+
overlapCount: normalizeInteger(entry.overlapCount, 0),
|
|
370
|
+
contribution: normalizeNumber(entry.contribution, 0),
|
|
371
|
+
}));
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
function normalizeCandidate(candidate = {}, index = 0) {
|
|
375
|
+
const normalizedRank = normalizeNumber(candidate.rank, null);
|
|
376
|
+
|
|
377
|
+
return {
|
|
378
|
+
candidateId: normalizeText(candidate.candidateId, `candidate_${index + 1}`),
|
|
379
|
+
worldId: normalizeText(candidate.worldId, 'unknown-world'),
|
|
380
|
+
sourceMembershipId: normalizeText(candidate.sourceMembershipId, null),
|
|
381
|
+
profileSummary: normalizeCandidateProfileSummary(candidate.profileSummary),
|
|
382
|
+
compatibilitySignals: Array.isArray(candidate.compatibilitySignals)
|
|
383
|
+
? candidate.compatibilitySignals.map((signal, signalIndex) => normalizeCompatibilitySignal(signal, signalIndex))
|
|
384
|
+
: [],
|
|
385
|
+
deliveryReason: normalizeDeliveryReason(candidate.deliveryReason),
|
|
386
|
+
expiresAt: normalizeText(candidate.expiresAt, null),
|
|
387
|
+
joinedAt: normalizeText(candidate.joinedAt, null),
|
|
388
|
+
rank: normalizedRank == null ? null : Math.max(1, Math.trunc(normalizedRank)),
|
|
389
|
+
score: normalizeNumber(candidate.score, null),
|
|
390
|
+
scoreBreakdown: normalizeCandidateScoreBreakdown(candidate.scoreBreakdown),
|
|
391
|
+
scoringInputs: candidate.scoringInputs && typeof candidate.scoringInputs === 'object' ? candidate.scoringInputs : {},
|
|
392
|
+
};
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
function normalizeCandidateFeedResponse(payload = {}, { worldId = null, agentId = null } = {}) {
|
|
396
|
+
const candidates = Array.isArray(payload.candidates)
|
|
397
|
+
? payload.candidates.map((candidate, index) => normalizeCandidate(candidate, index))
|
|
398
|
+
: [];
|
|
399
|
+
|
|
400
|
+
return {
|
|
401
|
+
worldId: normalizeText(payload.worldId, worldId || 'unknown-world'),
|
|
402
|
+
agentId: normalizeText(payload.agentId, agentId || null),
|
|
403
|
+
viewerMembershipId: normalizeText(payload.viewerMembershipId, null),
|
|
404
|
+
generatedAt: normalizeText(payload.generatedAt, null),
|
|
405
|
+
expiresAt: normalizeText(payload.expiresAt, null),
|
|
406
|
+
deliveryMode: normalizeText(payload.deliveryMode, 'agent_review_before_live_session'),
|
|
407
|
+
nextAction: normalizeText(
|
|
408
|
+
payload.nextAction,
|
|
409
|
+
candidates.length > 0 ? 'review_candidates_before_requesting_live_session' : 'wait_for_more_candidates',
|
|
410
|
+
),
|
|
411
|
+
candidateSource: normalizeText(payload.candidateSource, 'active_memberships'),
|
|
412
|
+
candidateModel: payload.candidateModel && typeof payload.candidateModel === 'object' ? payload.candidateModel : {},
|
|
413
|
+
strategy: payload.strategy && typeof payload.strategy === 'object' ? payload.strategy : {},
|
|
414
|
+
limit: normalizeInteger(payload.limit, candidates.length),
|
|
415
|
+
totalCandidates: normalizeInteger(payload.totalCandidates, candidates.length),
|
|
416
|
+
status: normalizeText(payload.status, candidates.length > 0 ? 'feed_ready' : 'no_candidates_ready'),
|
|
417
|
+
candidates,
|
|
418
|
+
};
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
function summarizeProfileValue(value) {
|
|
422
|
+
if (Array.isArray(value)) return joinAsNaturalLanguage(value.map((entry) => String(entry).trim()).filter(Boolean));
|
|
423
|
+
return normalizeText(value, '');
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
function summarizeProfileFields(fields = []) {
|
|
427
|
+
return fields
|
|
428
|
+
.map((field) => {
|
|
429
|
+
const value = summarizeProfileValue(field.value);
|
|
430
|
+
if (!value) return null;
|
|
431
|
+
return `${field.label}: ${value}`;
|
|
432
|
+
})
|
|
433
|
+
.filter(Boolean);
|
|
434
|
+
}
|
|
435
|
+
|
|
436
|
+
function buildCandidateDeliverySummaryLine(candidateSummary = {}, index = 0) {
|
|
437
|
+
return `${index + 1}. ${candidateSummary.summary}`;
|
|
438
|
+
}
|
|
439
|
+
|
|
440
|
+
function formatSessionOverview(detail = {}) {
|
|
441
|
+
const sessionOverview = detail.sessionOverview && typeof detail.sessionOverview === 'object'
|
|
442
|
+
? detail.sessionOverview
|
|
443
|
+
: {};
|
|
444
|
+
const mode = normalizeText(detail.sessionMode || sessionOverview.mode, null);
|
|
445
|
+
const maxTurns = normalizeInteger(sessionOverview.maxTurns, null);
|
|
446
|
+
const turnTimeoutMs = normalizeInteger(sessionOverview.turnTimeoutMs, null);
|
|
447
|
+
const parts = [];
|
|
448
|
+
|
|
449
|
+
if (mode) parts.push(`${mode} mode`);
|
|
450
|
+
if (maxTurns != null) parts.push(`max ${maxTurns} turns`);
|
|
451
|
+
if (turnTimeoutMs != null) parts.push(`${turnTimeoutMs}ms turn timeout`);
|
|
452
|
+
|
|
453
|
+
return parts.length > 0 ? parts.join(', ') : null;
|
|
454
|
+
}
|
|
455
|
+
|
|
456
|
+
function buildRaiseHandDirective(sessionOverview = {}) {
|
|
457
|
+
const mode = normalizeText(sessionOverview.raiseHandPolicy?.mode, null);
|
|
458
|
+
if (mode === 'dual_raise_hand') {
|
|
459
|
+
return 'When you are ready to conclude, include [[CLAWORLD_RAISE_HAND]] in your reply. The round closes once both agents raise hand.';
|
|
460
|
+
}
|
|
461
|
+
if (mode === 'single_raise_hand' || mode === 'either_raise_hand') {
|
|
462
|
+
return 'When you are ready to conclude, include [[CLAWORLD_RAISE_HAND]] in your reply to close the round.';
|
|
463
|
+
}
|
|
464
|
+
return null;
|
|
465
|
+
}
|
|
466
|
+
|
|
467
|
+
export function buildWorldSessionStartupText(detail = {}) {
|
|
468
|
+
const normalizedDetail = normalizeWorldDetail(detail);
|
|
469
|
+
const worldId = normalizeText(normalizedDetail.worldId, null);
|
|
470
|
+
if (!worldId) return null;
|
|
471
|
+
|
|
472
|
+
const displayName = normalizeText(normalizedDetail.displayName, worldId);
|
|
473
|
+
const summary = normalizeText(normalizedDetail.summary, null);
|
|
474
|
+
const sessionSummary = formatSessionOverview(normalizedDetail);
|
|
475
|
+
const sessionOverview = normalizedDetail.sessionOverview && typeof normalizedDetail.sessionOverview === 'object'
|
|
476
|
+
? normalizedDetail.sessionOverview
|
|
477
|
+
: {};
|
|
478
|
+
const raiseHandSummary = normalizeText(sessionOverview.raiseHandPolicy?.summary, null);
|
|
479
|
+
const openingText = normalizeText(sessionOverview.openingText, null);
|
|
480
|
+
const convergenceText = normalizeText(sessionOverview.convergence?.text, null);
|
|
481
|
+
const raiseHandDirective = buildRaiseHandDirective(sessionOverview);
|
|
482
|
+
const interactionRules = normalizeText(normalizedDetail.interactionRules, null);
|
|
483
|
+
const prohibitedRules = normalizeText(normalizedDetail.prohibitedRules, null);
|
|
484
|
+
const ratingRules = normalizeText(normalizedDetail.ratingRules, null);
|
|
485
|
+
|
|
486
|
+
const lines = [
|
|
487
|
+
'Internal Claworld world context for this session.',
|
|
488
|
+
'Do not acknowledge, paraphrase, or announce this setup to the peer unless it is directly relevant to their message.',
|
|
489
|
+
`World: ${displayName} [${worldId}]`,
|
|
490
|
+
summary ? `Summary: ${summary}` : null,
|
|
491
|
+
sessionSummary ? `Session overview: ${sessionSummary}` : null,
|
|
492
|
+
raiseHandSummary ? `Completion rule: ${raiseHandSummary}` : null,
|
|
493
|
+
openingText ? `Opening focus: ${openingText}` : null,
|
|
494
|
+
interactionRules ? `Interaction rules: ${interactionRules}` : null,
|
|
495
|
+
prohibitedRules ? `Prohibited rules: ${prohibitedRules}` : null,
|
|
496
|
+
ratingRules ? `Rating rules: ${ratingRules}` : null,
|
|
497
|
+
convergenceText ? `Convergence rule: ${convergenceText}` : null,
|
|
498
|
+
raiseHandDirective ? `Completion signal: ${raiseHandDirective}` : null,
|
|
499
|
+
'Apply these world rules symmetrically when responding in this session.',
|
|
500
|
+
].filter(Boolean);
|
|
501
|
+
|
|
502
|
+
return lines.join('\n');
|
|
503
|
+
}
|
|
504
|
+
|
|
505
|
+
function normalizeSelectionInput(selection) {
|
|
506
|
+
if (selection && typeof selection === 'object') {
|
|
507
|
+
const asWorldId = normalizeText(selection.worldId, null);
|
|
508
|
+
const asDisplayName = normalizeText(selection.displayName, null);
|
|
509
|
+
const asChoice = normalizeText(selection.selection || selection.choice || selection.value, null);
|
|
510
|
+
const text = asWorldId || asDisplayName || asChoice || '';
|
|
511
|
+
return {
|
|
512
|
+
raw: selection,
|
|
513
|
+
text,
|
|
514
|
+
normalized: normalizeLookupText(text),
|
|
515
|
+
index: /^\d+$/.test(String(text)) ? normalizeInteger(text, 0) : null,
|
|
516
|
+
};
|
|
517
|
+
}
|
|
518
|
+
|
|
519
|
+
const text = normalizeText(selection, '');
|
|
520
|
+
return {
|
|
521
|
+
raw: selection,
|
|
522
|
+
text,
|
|
523
|
+
normalized: normalizeLookupText(text),
|
|
524
|
+
index: /^\d+$/.test(String(text)) ? normalizeInteger(text, 0) : null,
|
|
525
|
+
};
|
|
526
|
+
}
|
|
527
|
+
|
|
528
|
+
function buildSelectionRetryContract(status, selection, items = [], matches = []) {
|
|
529
|
+
const choiceLabel = selection.text ? `"${selection.text}"` : 'that choice';
|
|
530
|
+
const retryWorlds = (matches.length > 0 ? matches : items).map((world) => normalizeWorldSummary(world));
|
|
531
|
+
const retrySummary = summarizeWorldChoices(retryWorlds);
|
|
532
|
+
|
|
533
|
+
if (status === 'ambiguous') {
|
|
534
|
+
return {
|
|
535
|
+
status,
|
|
536
|
+
selection: {
|
|
537
|
+
input: selection.text || null,
|
|
538
|
+
matchedBy: null,
|
|
539
|
+
worldId: null,
|
|
540
|
+
displayName: null,
|
|
541
|
+
},
|
|
542
|
+
candidateWorlds: retryWorlds,
|
|
543
|
+
orchestration: {
|
|
544
|
+
stage: 'post_setup_world_selection_retry',
|
|
545
|
+
system: 'The world choice matched multiple worlds. Show the narrowed list and ask the user to pick one exact world ID or display name.',
|
|
546
|
+
user: `The choice ${choiceLabel} is ambiguous. Matching worlds: ${joinAsNaturalLanguage(retrySummary)}. Ask the user to choose one exact world ID or display name.`,
|
|
547
|
+
followUp: 'Once the user confirms one exact world, fetch its detail and explain the required fields before join-check.',
|
|
548
|
+
},
|
|
549
|
+
};
|
|
550
|
+
}
|
|
551
|
+
|
|
552
|
+
return {
|
|
553
|
+
status: 'no_match',
|
|
554
|
+
selection: {
|
|
555
|
+
input: selection.text || null,
|
|
556
|
+
matchedBy: null,
|
|
557
|
+
worldId: null,
|
|
558
|
+
displayName: null,
|
|
559
|
+
},
|
|
560
|
+
candidateWorlds: retryWorlds,
|
|
561
|
+
orchestration: {
|
|
562
|
+
stage: 'post_setup_world_selection_retry',
|
|
563
|
+
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.',
|
|
564
|
+
user: retrySummary.length > 0
|
|
565
|
+
? `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.`
|
|
566
|
+
: 'No worlds are currently available. Tell the user setup is complete but world selection cannot continue yet.',
|
|
567
|
+
followUp: 'Once the user chooses a valid world, confirm it, fetch the world detail, and explain the required fields before join-check.',
|
|
568
|
+
},
|
|
569
|
+
};
|
|
570
|
+
}
|
|
571
|
+
|
|
572
|
+
export function buildWorldSelectionPrompt(worldDirectory = {}) {
|
|
573
|
+
const worldLines = Array.isArray(worldDirectory.items)
|
|
574
|
+
? worldDirectory.items.map((world, index) => (
|
|
575
|
+
`${index + 1}. ${world.displayName} [${world.worldId}] - ${world.summary}`
|
|
576
|
+
+ ` (category: ${world.category}; required fields: ${world.requiredFieldCount};`
|
|
577
|
+
+ ` matching: ${world.matchingMode}; session: ${world.sessionMode})`
|
|
578
|
+
))
|
|
579
|
+
: [];
|
|
580
|
+
|
|
581
|
+
return {
|
|
582
|
+
stage: 'post_setup_world_selection',
|
|
583
|
+
recommendedWorldId: worldDirectory.recommendedWorldId || null,
|
|
584
|
+
system:
|
|
585
|
+
'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.',
|
|
586
|
+
user:
|
|
587
|
+
worldLines.length > 0
|
|
588
|
+
? `Available worlds:\n${worldLines.join('\n')}\nAsk the user which world they want to join next.`
|
|
589
|
+
: 'No worlds are currently available. Tell the user setup is complete but no worlds can be selected yet.',
|
|
590
|
+
followUp:
|
|
591
|
+
'After the user chooses a world, confirm the selection, fetch that world detail, explain the required fields, and then use join-check for that world.',
|
|
592
|
+
};
|
|
593
|
+
}
|
|
594
|
+
|
|
595
|
+
export function resolveWorldSelection(worldDirectory = {}, selection = null) {
|
|
596
|
+
const items = Array.isArray(worldDirectory.items)
|
|
597
|
+
? worldDirectory.items.map((world) => normalizeWorldSummary(world))
|
|
598
|
+
: [];
|
|
599
|
+
const normalizedSelection = normalizeSelectionInput(selection);
|
|
600
|
+
|
|
601
|
+
if (items.length === 0) {
|
|
602
|
+
return buildSelectionRetryContract('no_match', normalizedSelection, items);
|
|
603
|
+
}
|
|
604
|
+
|
|
605
|
+
let selectedWorld = null;
|
|
606
|
+
let matchedBy = null;
|
|
607
|
+
|
|
608
|
+
if (normalizedSelection.index && normalizedSelection.index >= 1 && normalizedSelection.index <= items.length) {
|
|
609
|
+
selectedWorld = items[normalizedSelection.index - 1];
|
|
610
|
+
matchedBy = 'index';
|
|
611
|
+
}
|
|
612
|
+
|
|
613
|
+
if (!selectedWorld && normalizedSelection.normalized) {
|
|
614
|
+
selectedWorld = items.find((world) => normalizeLookupText(world.worldId) === normalizedSelection.normalized) || null;
|
|
615
|
+
if (selectedWorld) matchedBy = 'worldId';
|
|
616
|
+
}
|
|
617
|
+
|
|
618
|
+
if (!selectedWorld && normalizedSelection.normalized) {
|
|
619
|
+
selectedWorld = items.find((world) => normalizeLookupText(world.displayName) === normalizedSelection.normalized) || null;
|
|
620
|
+
if (selectedWorld) matchedBy = 'displayName';
|
|
621
|
+
}
|
|
622
|
+
|
|
623
|
+
if (!selectedWorld && normalizedSelection.normalized.length >= 3) {
|
|
624
|
+
const partialMatches = items.filter((world) => (
|
|
625
|
+
normalizeLookupText(world.worldId).includes(normalizedSelection.normalized)
|
|
626
|
+
|| normalizeLookupText(world.displayName).includes(normalizedSelection.normalized)
|
|
627
|
+
));
|
|
628
|
+
|
|
629
|
+
if (partialMatches.length === 1) {
|
|
630
|
+
[selectedWorld] = partialMatches;
|
|
631
|
+
matchedBy = 'partial';
|
|
632
|
+
} else if (partialMatches.length > 1) {
|
|
633
|
+
return buildSelectionRetryContract('ambiguous', normalizedSelection, items, partialMatches);
|
|
634
|
+
}
|
|
635
|
+
}
|
|
636
|
+
|
|
637
|
+
if (!selectedWorld) {
|
|
638
|
+
return buildSelectionRetryContract('no_match', normalizedSelection, items);
|
|
639
|
+
}
|
|
640
|
+
|
|
641
|
+
return {
|
|
642
|
+
status: 'selected',
|
|
643
|
+
selection: {
|
|
644
|
+
input: normalizedSelection.text || null,
|
|
645
|
+
matchedBy,
|
|
646
|
+
worldId: selectedWorld.worldId,
|
|
647
|
+
displayName: selectedWorld.displayName,
|
|
648
|
+
},
|
|
649
|
+
selectedWorld,
|
|
650
|
+
candidateWorlds: items,
|
|
651
|
+
orchestration: {
|
|
652
|
+
stage: 'post_setup_world_selected',
|
|
653
|
+
system: 'Confirm the resolved world choice before fetching detail and explaining the required fields.',
|
|
654
|
+
user: `I matched the user choice to ${selectedWorld.displayName} [${selectedWorld.worldId}]. Confirm that this is the world we will use next.`,
|
|
655
|
+
confirmation: `Confirmed world: ${selectedWorld.displayName} [${selectedWorld.worldId}].`,
|
|
656
|
+
followUp: 'Fetch the selected world detail and explain the required fields one by one before join-check.',
|
|
657
|
+
},
|
|
658
|
+
};
|
|
659
|
+
}
|
|
660
|
+
|
|
661
|
+
function buildFieldStepPrompt(field = {}, index = 0, total = 1) {
|
|
662
|
+
const examples = Array.isArray(field.examples) && field.examples.length > 0
|
|
663
|
+
? ` Example: ${field.examples.map((example) => quoteExample(example)).join(' or ')}.`
|
|
664
|
+
: '';
|
|
665
|
+
const description = sentenceCase(
|
|
666
|
+
field.description || `Provide ${field.label} so the world can evaluate the profile`,
|
|
667
|
+
'Provide this field so the world can evaluate the profile.',
|
|
668
|
+
);
|
|
669
|
+
|
|
670
|
+
return `Step ${index + 1} of ${total}: ${field.label}. ${description}${examples}`;
|
|
671
|
+
}
|
|
672
|
+
|
|
673
|
+
export function buildRequiredFieldExplanation(worldDetail = {}) {
|
|
674
|
+
const detail = normalizeWorldDetail(worldDetail);
|
|
675
|
+
const requiredFields = detail.requiredFields;
|
|
676
|
+
const optionalFields = detail.optionalFields;
|
|
677
|
+
const requiredFieldLabels = requiredFields.map((field) => field.label);
|
|
678
|
+
const optionalFieldLabels = optionalFields.map((field) => field.label);
|
|
679
|
+
const summary = requiredFields.length > 0
|
|
680
|
+
? `To join ${detail.displayName}, I need ${requiredFields.length} required field${requiredFields.length === 1 ? '' : 's'}: ${joinAsNaturalLanguage(requiredFieldLabels)}.`
|
|
681
|
+
: `${detail.displayName} does not require any mandatory profile fields before join-check.`;
|
|
682
|
+
const steps = requiredFields.map((field, index) => ({
|
|
683
|
+
step: index + 1,
|
|
684
|
+
fieldId: field.fieldId,
|
|
685
|
+
label: field.label,
|
|
686
|
+
prompt: buildFieldStepPrompt(field, index, requiredFields.length),
|
|
687
|
+
description: field.description,
|
|
688
|
+
examples: field.examples,
|
|
689
|
+
constraints: field.constraints,
|
|
690
|
+
}));
|
|
691
|
+
const optionalContextSummary = optionalFieldLabels.length > 0
|
|
692
|
+
? `Optional context you can add later: ${joinAsNaturalLanguage(optionalFieldLabels)}.`
|
|
693
|
+
: null;
|
|
694
|
+
const nextInstruction = steps[0]?.prompt || 'All required fields are already explained. You can move to join-check.';
|
|
695
|
+
|
|
696
|
+
return {
|
|
697
|
+
status: 'ready',
|
|
698
|
+
stage: 'post_setup_world_requirements',
|
|
699
|
+
worldId: detail.worldId,
|
|
700
|
+
displayName: detail.displayName,
|
|
701
|
+
requiredFieldCount: detail.requiredFieldCount,
|
|
702
|
+
optionalFieldCount: detail.optionalFieldCount,
|
|
703
|
+
summary,
|
|
704
|
+
steps,
|
|
705
|
+
hints: detail.hints,
|
|
706
|
+
optionalContextSummary,
|
|
707
|
+
nextAction: detail.nextAction,
|
|
708
|
+
orchestration: {
|
|
709
|
+
stage: 'post_setup_world_requirements',
|
|
710
|
+
system: 'Confirm the selected world, explain its required fields in plain language, and collect them one at a time before join-check.',
|
|
711
|
+
confirmation: `Confirmed world: ${detail.displayName} [${detail.worldId}].`,
|
|
712
|
+
user: [summary, nextInstruction, optionalContextSummary].filter(Boolean).join('\n\n'),
|
|
713
|
+
followUp: requiredFields.length > 1
|
|
714
|
+
? `After the user answers ${steps[0].label}, continue with the remaining required fields in order, then call join-check.`
|
|
715
|
+
: (requiredFields.length === 1
|
|
716
|
+
? 'After the user answers the required field, call join-check.'
|
|
717
|
+
: 'No required fields remain. Call join-check now.'),
|
|
718
|
+
},
|
|
719
|
+
};
|
|
720
|
+
}
|
|
721
|
+
|
|
722
|
+
function buildFieldLookup(worldDetail = {}) {
|
|
723
|
+
const detail = normalizeWorldDetail(worldDetail);
|
|
724
|
+
return new Map(
|
|
725
|
+
[...detail.requiredFields, ...detail.optionalFields].map((field) => [field.fieldId, field]),
|
|
726
|
+
);
|
|
727
|
+
}
|
|
728
|
+
|
|
729
|
+
function selectPromptFields(joinCheck = {}, worldDetail = {}, maxFieldsPerStep = 1) {
|
|
730
|
+
if (joinCheck.accepted) return [];
|
|
731
|
+
|
|
732
|
+
const fieldLookup = buildFieldLookup(worldDetail);
|
|
733
|
+
const orderedMissingFields = Array.isArray(joinCheck.missingFieldGuidance?.orderedMissingFields)
|
|
734
|
+
&& joinCheck.missingFieldGuidance.orderedMissingFields.length > 0
|
|
735
|
+
? joinCheck.missingFieldGuidance.orderedMissingFields
|
|
736
|
+
: (Array.isArray(joinCheck.missingFields) ? joinCheck.missingFields : []);
|
|
737
|
+
|
|
738
|
+
return orderedMissingFields.slice(0, Math.max(1, maxFieldsPerStep)).map((field, index) => {
|
|
739
|
+
const detailField = fieldLookup.get(field.fieldId) || {};
|
|
740
|
+
return normalizeField(
|
|
741
|
+
{
|
|
742
|
+
...detailField,
|
|
743
|
+
...field,
|
|
744
|
+
description: normalizeText(field.description, detailField.description || null),
|
|
745
|
+
examples: Array.isArray(detailField.examples) ? detailField.examples : field.examples,
|
|
746
|
+
constraints: detailField.constraints || field.constraints,
|
|
747
|
+
},
|
|
748
|
+
index,
|
|
749
|
+
{ required: true },
|
|
750
|
+
);
|
|
751
|
+
});
|
|
752
|
+
}
|
|
753
|
+
|
|
754
|
+
function buildProfileFieldPrompt(field = {}, index = 0, total = 1) {
|
|
755
|
+
const examples = Array.isArray(field.examples) && field.examples.length > 0
|
|
756
|
+
? ` Example: ${field.examples.map((example) => quoteExample(example)).join(' or ')}.`
|
|
757
|
+
: '';
|
|
758
|
+
const description = sentenceCase(
|
|
759
|
+
field.description || `Provide ${field.label} so the world can evaluate the profile`,
|
|
760
|
+
'Provide this field so the world can evaluate the profile.',
|
|
761
|
+
);
|
|
762
|
+
const prefix = total > 1 ? `${index + 1}. ` : '';
|
|
763
|
+
|
|
764
|
+
return `${prefix}${field.label}. ${description}${examples}`;
|
|
765
|
+
}
|
|
766
|
+
|
|
767
|
+
function listProvidedRequiredFieldIds(worldDetail = {}, profile = {}, missingFieldIds = []) {
|
|
768
|
+
const missingSet = new Set(normalizeStringList(missingFieldIds));
|
|
769
|
+
return normalizeWorldDetail(worldDetail).requiredFields
|
|
770
|
+
.filter((field) => !missingSet.has(field.fieldId) && !isEmptyProfileValue(profile[field.fieldId]))
|
|
771
|
+
.map((field) => field.fieldId);
|
|
772
|
+
}
|
|
773
|
+
|
|
774
|
+
function buildProfileCollectionFollowUp(promptFields = []) {
|
|
775
|
+
if (promptFields.length === 0) {
|
|
776
|
+
return 'The current profile is already eligible. Continue with the next world step without restarting profile collection.';
|
|
777
|
+
}
|
|
778
|
+
|
|
779
|
+
const labels = promptFields.map((field) => field.label);
|
|
780
|
+
if (promptFields.length === 1) {
|
|
781
|
+
return `After the user answers ${labels[0]}, merge it into the saved profile draft and re-run join-check before asking anything else.`;
|
|
782
|
+
}
|
|
783
|
+
|
|
784
|
+
return `After the user answers ${joinAsNaturalLanguage(labels)}, merge those fields into the saved profile draft and re-run join-check before asking anything else.`;
|
|
785
|
+
}
|
|
786
|
+
|
|
787
|
+
export function buildWorldProfileCollectionFlow({
|
|
788
|
+
worldDetail = {},
|
|
789
|
+
joinCheck = {},
|
|
790
|
+
profile = {},
|
|
791
|
+
maxFieldsPerStep = 1,
|
|
792
|
+
} = {}) {
|
|
793
|
+
const detail = normalizeWorldDetail(worldDetail);
|
|
794
|
+
const normalizedJoinCheck = normalizeJoinCheckResponse(joinCheck, {
|
|
795
|
+
worldId: detail.worldId,
|
|
796
|
+
profile,
|
|
797
|
+
});
|
|
798
|
+
const promptLimit = Math.max(1, normalizeInteger(maxFieldsPerStep, 1));
|
|
799
|
+
const promptFields = selectPromptFields(normalizedJoinCheck, detail, promptLimit);
|
|
800
|
+
const promptFieldIds = promptFields.map((field) => field.fieldId);
|
|
801
|
+
const providedRequiredFieldIds = listProvidedRequiredFieldIds(
|
|
802
|
+
detail,
|
|
803
|
+
normalizedJoinCheck.normalizedProfile,
|
|
804
|
+
normalizedJoinCheck.missingFieldGuidance.orderedMissingFieldIds,
|
|
805
|
+
);
|
|
806
|
+
const providedRequiredLabels = detail.requiredFields
|
|
807
|
+
.filter((field) => providedRequiredFieldIds.includes(field.fieldId))
|
|
808
|
+
.map((field) => field.label);
|
|
809
|
+
const optionalFieldLabels = detail.optionalFields.map((field) => field.label);
|
|
810
|
+
const remainingCount = normalizedJoinCheck.missingFieldGuidance.remainingRequiredFieldCount;
|
|
811
|
+
const summary = normalizedJoinCheck.accepted
|
|
812
|
+
? `All required fields for ${detail.displayName} are currently present.`
|
|
813
|
+
: `${remainingCount} required field${remainingCount === 1 ? '' : 's'} still need to be collected for ${detail.displayName}.`;
|
|
814
|
+
const savedSummary = providedRequiredLabels.length > 0
|
|
815
|
+
? `Already saved required fields: ${joinAsNaturalLanguage(providedRequiredLabels)}.`
|
|
816
|
+
: 'No required fields are saved yet.';
|
|
817
|
+
const promptSummary = promptFields.length === 0
|
|
818
|
+
? null
|
|
819
|
+
: (promptFields.length === 1
|
|
820
|
+
? 'Ask the user for this required field next:'
|
|
821
|
+
: `Ask the user for these ${promptFields.length} required fields next, and accept them in one reply if convenient:`);
|
|
822
|
+
const promptBody = promptFields.length === 0
|
|
823
|
+
? null
|
|
824
|
+
: promptFields.map((field, index) => buildProfileFieldPrompt(field, index, promptFields.length)).join('\n');
|
|
825
|
+
const optionalContextSummary = optionalFieldLabels.length > 0
|
|
826
|
+
? `Optional context the user can add or edit later: ${joinAsNaturalLanguage(optionalFieldLabels)}.`
|
|
827
|
+
: null;
|
|
828
|
+
const revalidationMode = normalizedJoinCheck.accepted
|
|
829
|
+
? 'complete'
|
|
830
|
+
: (promptFields.length > 1 ? 'after_current_batch' : 'after_each_reply');
|
|
831
|
+
|
|
832
|
+
return {
|
|
833
|
+
accepted: normalizedJoinCheck.accepted,
|
|
834
|
+
status: normalizedJoinCheck.status,
|
|
835
|
+
source: 'product_shell',
|
|
836
|
+
worldId: detail.worldId,
|
|
837
|
+
displayName: detail.displayName,
|
|
838
|
+
profile: normalizedJoinCheck.normalizedProfile,
|
|
839
|
+
promptFields,
|
|
840
|
+
promptFieldIds,
|
|
841
|
+
providedRequiredFieldIds,
|
|
842
|
+
remainingRequiredFieldCount: remainingCount,
|
|
843
|
+
nextAction: normalizedJoinCheck.nextAction,
|
|
844
|
+
revalidationCheckpoint: {
|
|
845
|
+
mode: revalidationMode,
|
|
846
|
+
promptFieldIds,
|
|
847
|
+
},
|
|
848
|
+
orchestration: {
|
|
849
|
+
stage: 'post_setup_world_profile_collection',
|
|
850
|
+
system:
|
|
851
|
+
'Use backend join-check 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 re-run join-check.',
|
|
852
|
+
confirmation: `Confirmed world: ${detail.displayName} [${detail.worldId}].`,
|
|
853
|
+
user: [summary, savedSummary, promptSummary, promptBody, optionalContextSummary].filter(Boolean).join('\n\n'),
|
|
854
|
+
followUp: buildProfileCollectionFollowUp(promptFields),
|
|
855
|
+
},
|
|
856
|
+
};
|
|
857
|
+
}
|
|
858
|
+
|
|
859
|
+
export function buildWorldJoinOutcomeOrchestration({ worldDetail = {}, joinResult = {} } = {}) {
|
|
860
|
+
const detail = normalizeWorldDetail(worldDetail);
|
|
861
|
+
const membershipStatus = normalizeText(
|
|
862
|
+
joinResult.membershipStatus || joinResult.membership?.status,
|
|
863
|
+
'unknown',
|
|
864
|
+
);
|
|
865
|
+
const joinSummary = joinResult.nextStageSummary?.summary
|
|
866
|
+
? sentenceCase(joinResult.nextStageSummary.summary, '')
|
|
867
|
+
: null;
|
|
868
|
+
|
|
869
|
+
return {
|
|
870
|
+
stage: 'world_join_result',
|
|
871
|
+
status: membershipStatus,
|
|
872
|
+
system:
|
|
873
|
+
'The backend already resolved the world join result. Reflect the authoritative membership outcome before any search, candidate review, or live-session follow-up.',
|
|
874
|
+
confirmation: `World membership in ${detail.displayName} [${detail.worldId}] is ${membershipStatus}.`,
|
|
875
|
+
user: membershipStatus === 'active'
|
|
876
|
+
? ['Joined ' + detail.displayName + ' successfully. World membership is active.', joinSummary].filter(Boolean).join(' ')
|
|
877
|
+
: `The join result for ${detail.displayName} is ${membershipStatus}.`,
|
|
878
|
+
followUp: membershipStatus === 'active'
|
|
879
|
+
? 'Use backend-authored next-stage summary and candidate-delivery payloads for the next world step.'
|
|
880
|
+
: 'Do not continue to world-member-only follow-up until membership becomes active.',
|
|
881
|
+
};
|
|
882
|
+
}
|
|
883
|
+
|
|
884
|
+
export function buildResolvedWorldSelectionOrchestration({
|
|
885
|
+
selection = null,
|
|
886
|
+
profileCollectionFlow = null,
|
|
887
|
+
} = {}) {
|
|
888
|
+
const selectionOrchestration = selection?.orchestration && typeof selection.orchestration === 'object' && !Array.isArray(selection.orchestration)
|
|
889
|
+
? selection.orchestration
|
|
890
|
+
: null;
|
|
891
|
+
const profileOrchestration = profileCollectionFlow?.orchestration && typeof profileCollectionFlow.orchestration === 'object' && !Array.isArray(profileCollectionFlow.orchestration)
|
|
892
|
+
? profileCollectionFlow.orchestration
|
|
893
|
+
: null;
|
|
894
|
+
|
|
895
|
+
if (!profileOrchestration) return selectionOrchestration;
|
|
896
|
+
|
|
897
|
+
return {
|
|
898
|
+
stage: normalizeText(profileOrchestration.stage, normalizeText(selectionOrchestration?.stage, null)),
|
|
899
|
+
system: normalizeText(profileOrchestration.system, normalizeText(selectionOrchestration?.system, null)),
|
|
900
|
+
confirmation: normalizeText(
|
|
901
|
+
profileOrchestration.confirmation,
|
|
902
|
+
normalizeText(selectionOrchestration?.confirmation, null),
|
|
903
|
+
),
|
|
904
|
+
user: [
|
|
905
|
+
normalizeText(profileOrchestration.confirmation, null),
|
|
906
|
+
normalizeText(profileOrchestration.user, null),
|
|
907
|
+
].filter(Boolean).join('\n\n'),
|
|
908
|
+
followUp: normalizeText(
|
|
909
|
+
profileOrchestration.followUp,
|
|
910
|
+
normalizeText(selectionOrchestration?.followUp, null),
|
|
911
|
+
),
|
|
912
|
+
};
|
|
913
|
+
}
|
|
914
|
+
|
|
915
|
+
export function buildCandidateDeliverySummary(candidateFeed = {}, { worldDetail = null, limit = null } = {}) {
|
|
916
|
+
const detail = worldDetail ? normalizeWorldDetail(worldDetail) : null;
|
|
917
|
+
const normalizedFeed = normalizeCandidateFeedResponse(candidateFeed, {
|
|
918
|
+
worldId: detail?.worldId || candidateFeed.worldId || null,
|
|
919
|
+
agentId: candidateFeed.agentId || null,
|
|
920
|
+
});
|
|
921
|
+
const summaryLimit = Math.max(
|
|
922
|
+
1,
|
|
923
|
+
normalizeInteger(limit, normalizedFeed.candidates.length || normalizedFeed.totalCandidates || 1),
|
|
924
|
+
);
|
|
925
|
+
const displayName = detail?.displayName || normalizedFeed.worldId || 'the selected world';
|
|
926
|
+
const candidateSummaries = normalizedFeed.candidates.slice(0, summaryLimit).map((candidate, index) => {
|
|
927
|
+
const name = candidate.profileSummary.displayName || `Candidate ${index + 1}`;
|
|
928
|
+
const requiredFieldSummary = summarizeProfileFields(candidate.profileSummary.requiredFields);
|
|
929
|
+
const optionalFieldSummary = summarizeProfileFields(candidate.profileSummary.optionalFields);
|
|
930
|
+
const compatibilitySummary = candidate.compatibilitySignals
|
|
931
|
+
.map((signal) => sentenceCase(signal.summary, ''))
|
|
932
|
+
.filter(Boolean);
|
|
933
|
+
const deliveryReasonSummary = sentenceCase(candidate.deliveryReason.summary, '');
|
|
934
|
+
const scoreSummary = candidate.score == null
|
|
935
|
+
? null
|
|
936
|
+
: `Score ${candidate.score}${candidate.rank == null ? '' : `, rank ${candidate.rank}`}.`;
|
|
937
|
+
const summary = [
|
|
938
|
+
candidate.profileSummary.headline ? `${name}: ${candidate.profileSummary.headline}.` : `${name}.`,
|
|
939
|
+
requiredFieldSummary.length > 0 ? `Required profile fields: ${requiredFieldSummary.join('; ')}.` : null,
|
|
940
|
+
optionalFieldSummary.length > 0 ? `Optional context: ${optionalFieldSummary.join('; ')}.` : null,
|
|
941
|
+
compatibilitySummary.length > 0 ? compatibilitySummary.join(' ') : null,
|
|
942
|
+
deliveryReasonSummary || null,
|
|
943
|
+
scoreSummary,
|
|
944
|
+
].filter(Boolean).join(' ');
|
|
945
|
+
|
|
946
|
+
return {
|
|
947
|
+
candidateId: candidate.candidateId,
|
|
948
|
+
sourceMembershipId: candidate.sourceMembershipId,
|
|
949
|
+
displayName: name,
|
|
950
|
+
headline: candidate.profileSummary.headline,
|
|
951
|
+
rank: candidate.rank,
|
|
952
|
+
score: candidate.score,
|
|
953
|
+
requiredFieldSummary,
|
|
954
|
+
optionalFieldSummary,
|
|
955
|
+
compatibilitySummary,
|
|
956
|
+
deliveryReasonSummary: deliveryReasonSummary || null,
|
|
957
|
+
expiresAt: candidate.expiresAt,
|
|
958
|
+
summary,
|
|
959
|
+
};
|
|
960
|
+
});
|
|
961
|
+
const deliveredCandidateCount = candidateSummaries.length;
|
|
962
|
+
const totalCandidateCount = Math.max(normalizedFeed.totalCandidates, deliveredCandidateCount);
|
|
963
|
+
const remainingCandidateCount = Math.max(totalCandidateCount - deliveredCandidateCount, 0);
|
|
964
|
+
const heading = deliveredCandidateCount > 0
|
|
965
|
+
? `${displayName} has ${deliveredCandidateCount} candidate profile ${deliveredCandidateCount === 1 ? 'summary' : 'summaries'} ready for review now.`
|
|
966
|
+
: `No candidate profile summaries are ready for review in ${displayName} yet.`;
|
|
967
|
+
const promptBody = deliveredCandidateCount > 0
|
|
968
|
+
? candidateSummaries.map((summary, index) => buildCandidateDeliverySummaryLine(summary, index)).join('\n\n')
|
|
969
|
+
: 'No candidates are currently available from the active-membership feed.';
|
|
970
|
+
|
|
971
|
+
return {
|
|
972
|
+
status: deliveredCandidateCount > 0 ? 'candidate_summary_ready' : 'candidate_summary_pending',
|
|
973
|
+
deliveredCandidateCount,
|
|
974
|
+
totalCandidateCount,
|
|
975
|
+
remainingCandidateCount,
|
|
976
|
+
candidateSummaries,
|
|
977
|
+
nextAction: deliveredCandidateCount > 0
|
|
978
|
+
? normalizedFeed.nextAction
|
|
979
|
+
: 'wait_for_more_candidates',
|
|
980
|
+
orchestration: {
|
|
981
|
+
stage: 'post_join_candidate_delivery',
|
|
982
|
+
system:
|
|
983
|
+
'Use the backend-authored candidate summaries already attached to this payload. Do not invent additional candidate-delivery business text locally.',
|
|
984
|
+
confirmation: `Candidate review payload for ${displayName} [${normalizedFeed.worldId}].`,
|
|
985
|
+
user: [heading, promptBody].filter(Boolean).join('\n\n'),
|
|
986
|
+
followUp: deliveredCandidateCount > 0
|
|
987
|
+
? (remainingCandidateCount > 0
|
|
988
|
+
? `Share these ${deliveredCandidateCount} candidate summaries first. If the user wants more, continue with the remaining ${remainingCandidateCount} candidate${remainingCandidateCount === 1 ? '' : 's'} from the same feed before requesting a live session.`
|
|
989
|
+
: 'Share these candidate summaries and ask whether the user wants to review them further or continue toward a live session later.')
|
|
990
|
+
: 'Tell the user candidate delivery can be retried later through the same backend-authored world flow.',
|
|
991
|
+
},
|
|
992
|
+
};
|
|
993
|
+
}
|
|
994
|
+
|
|
995
|
+
export function buildResolvedWorldJoinOrchestration({
|
|
996
|
+
joinResult = null,
|
|
997
|
+
candidateDelivery = null,
|
|
998
|
+
} = {}) {
|
|
999
|
+
const joinOrchestration = joinResult?.orchestration && typeof joinResult.orchestration === 'object' && !Array.isArray(joinResult.orchestration)
|
|
1000
|
+
? joinResult.orchestration
|
|
1001
|
+
: null;
|
|
1002
|
+
const candidateOrchestration = candidateDelivery?.orchestration && typeof candidateDelivery.orchestration === 'object' && !Array.isArray(candidateDelivery.orchestration)
|
|
1003
|
+
? candidateDelivery.orchestration
|
|
1004
|
+
: null;
|
|
1005
|
+
|
|
1006
|
+
if (!candidateOrchestration) return joinOrchestration;
|
|
1007
|
+
|
|
1008
|
+
return {
|
|
1009
|
+
stage: normalizeText(candidateOrchestration.stage, normalizeText(joinOrchestration?.stage, null)),
|
|
1010
|
+
system: normalizeText(candidateOrchestration.system, normalizeText(joinOrchestration?.system, null)),
|
|
1011
|
+
confirmation: normalizeText(
|
|
1012
|
+
joinOrchestration?.confirmation,
|
|
1013
|
+
normalizeText(candidateOrchestration.confirmation, null),
|
|
1014
|
+
),
|
|
1015
|
+
user: [
|
|
1016
|
+
normalizeText(joinOrchestration?.user, normalizeText(joinOrchestration?.confirmation, null)),
|
|
1017
|
+
normalizeText(candidateOrchestration.user, null),
|
|
1018
|
+
].filter(Boolean).join('\n\n'),
|
|
1019
|
+
followUp: normalizeText(
|
|
1020
|
+
candidateOrchestration.followUp,
|
|
1021
|
+
normalizeText(joinOrchestration?.followUp, null),
|
|
1022
|
+
),
|
|
1023
|
+
};
|
|
1024
|
+
}
|