@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,1712 @@
|
|
|
1
|
+
import { resolveClaworldRuntimeConfig } from '../plugin/config-schema.js';
|
|
2
|
+
import { buildRuntimeAuthHeaders } from '../plugin/account-identity.js';
|
|
3
|
+
import { createRuntimeBoundaryError } from '../../lib/runtime-errors.js';
|
|
4
|
+
import {
|
|
5
|
+
buildCandidateDeliverySummary as buildBackendCandidateDeliverySummary,
|
|
6
|
+
buildWorldJoinOutcomeOrchestration as buildBackendWorldJoinOutcomeOrchestration,
|
|
7
|
+
buildRequiredFieldExplanation as buildBackendRequiredFieldExplanation,
|
|
8
|
+
buildWorldProfileCollectionFlow as buildBackendWorldProfileCollectionFlow,
|
|
9
|
+
buildWorldSelectionPrompt as buildBackendWorldSelectionPrompt,
|
|
10
|
+
resolveWorldSelection as resolveBackendWorldSelection,
|
|
11
|
+
} from '../../product-shell/contracts/world-orchestration.js';
|
|
12
|
+
|
|
13
|
+
function normalizeText(value, fallback = null) {
|
|
14
|
+
if (value == null) return fallback;
|
|
15
|
+
const normalized = String(value).trim();
|
|
16
|
+
return normalized || fallback;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
function normalizeInteger(value, fallback = 0) {
|
|
20
|
+
const parsed = Number(value);
|
|
21
|
+
if (!Number.isFinite(parsed)) return fallback;
|
|
22
|
+
return Math.max(0, Math.trunc(parsed));
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
function normalizeStringList(values = []) {
|
|
26
|
+
if (!Array.isArray(values)) return [];
|
|
27
|
+
return [...new Set(values.map((value) => normalizeText(value, null)).filter(Boolean))];
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
function normalizeBroadcastConfig(broadcast = {}) {
|
|
31
|
+
return {
|
|
32
|
+
enabled: broadcast.enabled === true,
|
|
33
|
+
audience: normalizeText(broadcast.audience, 'members'),
|
|
34
|
+
replyPolicy: normalizeText(broadcast.replyPolicy, 'zero'),
|
|
35
|
+
excludeSelf: broadcast.excludeSelf !== false,
|
|
36
|
+
};
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
function isEmptyProfileValue(value) {
|
|
40
|
+
if (value == null) return true;
|
|
41
|
+
if (typeof value === 'string') return value.trim() === '';
|
|
42
|
+
if (Array.isArray(value)) return value.length === 0 || value.every((entry) => isEmptyProfileValue(entry));
|
|
43
|
+
if (typeof value === 'object') {
|
|
44
|
+
const entries = Object.values(value);
|
|
45
|
+
return entries.length === 0 || entries.every((entry) => isEmptyProfileValue(entry));
|
|
46
|
+
}
|
|
47
|
+
return false;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
function normalizeProfileValue(value) {
|
|
51
|
+
if (typeof value === 'string') return value.trim();
|
|
52
|
+
if (Array.isArray(value)) {
|
|
53
|
+
return value
|
|
54
|
+
.map((entry) => normalizeProfileValue(entry))
|
|
55
|
+
.filter((entry) => entry !== undefined && !isEmptyProfileValue(entry));
|
|
56
|
+
}
|
|
57
|
+
if (value && typeof value === 'object') {
|
|
58
|
+
return Object.fromEntries(
|
|
59
|
+
Object.entries(value)
|
|
60
|
+
.filter(([, entry]) => entry !== undefined)
|
|
61
|
+
.map(([key, entry]) => [key, normalizeProfileValue(entry)]),
|
|
62
|
+
);
|
|
63
|
+
}
|
|
64
|
+
return value;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
function normalizeProfile(profile = {}) {
|
|
68
|
+
if (!profile || typeof profile !== 'object' || Array.isArray(profile)) return {};
|
|
69
|
+
|
|
70
|
+
return Object.fromEntries(
|
|
71
|
+
Object.entries(profile)
|
|
72
|
+
.filter(([key, value]) => normalizeText(key, null) && value !== undefined)
|
|
73
|
+
.map(([key, value]) => [normalizeText(key, null), normalizeProfileValue(value)]),
|
|
74
|
+
);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
function mergeProfileState(profile = {}, profileUpdate = {}) {
|
|
78
|
+
return {
|
|
79
|
+
...normalizeProfile(profile),
|
|
80
|
+
...normalizeProfile(profileUpdate),
|
|
81
|
+
};
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
function normalizeLookupText(value) {
|
|
85
|
+
return normalizeText(value, '')?.toLowerCase() || '';
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
function sentenceCase(value, fallback = '') {
|
|
89
|
+
const normalized = normalizeText(value, fallback);
|
|
90
|
+
if (!normalized) return fallback;
|
|
91
|
+
return /[.!?]$/.test(normalized) ? normalized : `${normalized}.`;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
function quoteExample(example) {
|
|
95
|
+
return `"${String(example).trim()}"`;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
function joinAsNaturalLanguage(values = []) {
|
|
99
|
+
const items = values.filter(Boolean);
|
|
100
|
+
if (items.length === 0) return '';
|
|
101
|
+
if (items.length === 1) return items[0];
|
|
102
|
+
if (items.length === 2) return `${items[0]} and ${items[1]}`;
|
|
103
|
+
return `${items.slice(0, -1).join(', ')}, and ${items.at(-1)}`;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
function summarizeWorldChoices(items = []) {
|
|
107
|
+
return items.map((world) => `${world.displayName} [${world.worldId}]`);
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
function normalizeWorldSummary(world = {}) {
|
|
111
|
+
const summary = world.agentSummary && typeof world.agentSummary === 'object' ? world.agentSummary : world;
|
|
112
|
+
const rawWorldId = world.worldId || summary.worldId;
|
|
113
|
+
|
|
114
|
+
return {
|
|
115
|
+
worldId: normalizeText(rawWorldId, 'unknown-world'),
|
|
116
|
+
displayName: normalizeText(summary.displayName || world.displayName, normalizeText(rawWorldId, 'Unknown World')),
|
|
117
|
+
summary: normalizeText(summary.summary || world.summary, ''),
|
|
118
|
+
category: normalizeText(summary.category || world.category, 'general'),
|
|
119
|
+
hotness: normalizeInteger(summary.hotness || world.hotness || world.activatedMemberCount, 0),
|
|
120
|
+
requiredFieldCount: normalizeInteger(summary.requiredFieldCount || world.requiredFieldCount, 0),
|
|
121
|
+
matchingMode: normalizeText(summary.matchingMode || world.matchingMode, 'manual_review'),
|
|
122
|
+
sessionMode: normalizeText(summary.sessionMode || world.sessionMode, 'a2a'),
|
|
123
|
+
};
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
function normalizeField(field = {}, index = 0, { required = false } = {}) {
|
|
127
|
+
const fieldId = normalizeText(field.fieldId || field.id, `field_${index + 1}`);
|
|
128
|
+
return {
|
|
129
|
+
fieldId,
|
|
130
|
+
label: normalizeText(field.label, fieldId),
|
|
131
|
+
type: normalizeText(field.type, 'string'),
|
|
132
|
+
source: normalizeText(field.source, 'profile'),
|
|
133
|
+
required: field.required === true || required,
|
|
134
|
+
description: normalizeText(field.description, null),
|
|
135
|
+
examples: normalizeStringList(field.examples),
|
|
136
|
+
constraints: field.constraints && typeof field.constraints === 'object' ? field.constraints : {},
|
|
137
|
+
};
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
function normalizeSearchSchema(payload = {}, { worldId = null, fallbackFields = [] } = {}) {
|
|
141
|
+
const rawInputFields = Array.isArray(payload.inputFields) && payload.inputFields.length > 0
|
|
142
|
+
? payload.inputFields
|
|
143
|
+
: fallbackFields;
|
|
144
|
+
const inputFields = rawInputFields.map((field, index) => normalizeField(field, index, { required: false }));
|
|
145
|
+
const inputFieldIds = normalizeStringList(
|
|
146
|
+
Array.isArray(payload.inputFieldIds)
|
|
147
|
+
? payload.inputFieldIds
|
|
148
|
+
: inputFields.map((field) => field.fieldId),
|
|
149
|
+
);
|
|
150
|
+
|
|
151
|
+
return {
|
|
152
|
+
modelId: normalizeText(payload.modelId, worldId ? `${worldId}.search.v1` : 'world.search.v1'),
|
|
153
|
+
worldId: normalizeText(payload.worldId, worldId || 'unknown-world'),
|
|
154
|
+
mode: normalizeText(payload.mode, 'membership_profile_search'),
|
|
155
|
+
previewRoute: normalizeText(payload.previewRoute, worldId ? `/v1/worlds/${worldId}/search` : '/v1/worlds/:worldId/search'),
|
|
156
|
+
inputFieldIds,
|
|
157
|
+
inputFields,
|
|
158
|
+
resultFields: normalizeStringList(payload.resultFields),
|
|
159
|
+
viewerRequirement: normalizeText(payload.viewerRequirement, 'active_membership'),
|
|
160
|
+
onlineOnly: payload.onlineOnly !== false,
|
|
161
|
+
defaultLimit: normalizeInteger(payload.defaultLimit, 10),
|
|
162
|
+
summary: normalizeText(payload.summary, ''),
|
|
163
|
+
hints: normalizeStringList(payload.hints),
|
|
164
|
+
status: normalizeText(payload.status, 'phase1_world_search'),
|
|
165
|
+
};
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
function normalizeWorldDetail(payload = {}) {
|
|
169
|
+
if (Array.isArray(payload.requiredFields) || Array.isArray(payload.optionalFields)) {
|
|
170
|
+
const requiredFields = Array.isArray(payload.requiredFields)
|
|
171
|
+
? payload.requiredFields.map((field, index) => normalizeField(field, index, { required: true }))
|
|
172
|
+
: [];
|
|
173
|
+
const optionalFields = Array.isArray(payload.optionalFields)
|
|
174
|
+
? payload.optionalFields.map((field, index) => normalizeField(field, index, { required: false }))
|
|
175
|
+
: [];
|
|
176
|
+
const normalizedWorldId = normalizeText(payload.worldId, 'unknown-world');
|
|
177
|
+
|
|
178
|
+
return {
|
|
179
|
+
status: normalizeText(payload.status, 'ready'),
|
|
180
|
+
source: normalizeText(payload.source, 'product_shell'),
|
|
181
|
+
worldId: normalizedWorldId,
|
|
182
|
+
displayName: normalizeText(payload.displayName, normalizedWorldId),
|
|
183
|
+
summary: normalizeText(payload.summary, ''),
|
|
184
|
+
description: normalizeText(payload.description, normalizeText(payload.summary, '')),
|
|
185
|
+
category: normalizeText(payload.category, 'general'),
|
|
186
|
+
requiredFieldCount: normalizeInteger(payload.requiredFieldCount, requiredFields.length) || requiredFields.length,
|
|
187
|
+
optionalFieldCount: normalizeInteger(payload.optionalFieldCount, optionalFields.length) || optionalFields.length,
|
|
188
|
+
matchingMode: normalizeText(payload.matchingMode, 'manual_review'),
|
|
189
|
+
sessionMode: normalizeText(payload.sessionMode, 'a2a'),
|
|
190
|
+
interactionRules: normalizeText(payload.interactionRules, null),
|
|
191
|
+
prohibitedRules: normalizeText(payload.prohibitedRules, null),
|
|
192
|
+
ratingRules: normalizeText(payload.ratingRules, null),
|
|
193
|
+
adminAgentIds: normalizeStringList(payload.adminAgentIds),
|
|
194
|
+
eligibility: normalizeText(payload.eligibility, 'active'),
|
|
195
|
+
broadcast: normalizeBroadcastConfig(payload.broadcast),
|
|
196
|
+
requiredFields,
|
|
197
|
+
optionalFields,
|
|
198
|
+
hints: normalizeStringList(payload.hints),
|
|
199
|
+
nextAction: normalizeText(payload.nextAction, 'collect_profile_fields_then_call_join'),
|
|
200
|
+
sessionOverview: payload.sessionOverview && typeof payload.sessionOverview === 'object' ? payload.sessionOverview : {},
|
|
201
|
+
matchingOverview: payload.matchingOverview && typeof payload.matchingOverview === 'object' ? payload.matchingOverview : {},
|
|
202
|
+
requiredFieldExplanation:
|
|
203
|
+
payload.requiredFieldExplanation && typeof payload.requiredFieldExplanation === 'object'
|
|
204
|
+
? payload.requiredFieldExplanation
|
|
205
|
+
: null,
|
|
206
|
+
runtimeStartupContext:
|
|
207
|
+
payload.runtimeStartupContext && typeof payload.runtimeStartupContext === 'object'
|
|
208
|
+
? payload.runtimeStartupContext
|
|
209
|
+
: null,
|
|
210
|
+
searchSchema: normalizeSearchSchema(payload.searchSchema || {}, {
|
|
211
|
+
worldId: normalizedWorldId,
|
|
212
|
+
fallbackFields: requiredFields,
|
|
213
|
+
}),
|
|
214
|
+
};
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
const world = payload.world && typeof payload.world === 'object' ? payload.world : {};
|
|
218
|
+
const agentSummary = payload.agentSummary && typeof payload.agentSummary === 'object' ? payload.agentSummary : {};
|
|
219
|
+
const joinSchema = payload.joinSchema && typeof payload.joinSchema === 'object' ? payload.joinSchema : {};
|
|
220
|
+
const fieldGuide = payload.fieldGuide && typeof payload.fieldGuide === 'object' ? payload.fieldGuide : {};
|
|
221
|
+
const sessionOverview = payload.sessionOverview && typeof payload.sessionOverview === 'object'
|
|
222
|
+
? payload.sessionOverview
|
|
223
|
+
: {};
|
|
224
|
+
const matchingOverview = payload.matchingOverview && typeof payload.matchingOverview === 'object'
|
|
225
|
+
? payload.matchingOverview
|
|
226
|
+
: {};
|
|
227
|
+
const searchOverview = payload.searchSchema && typeof payload.searchSchema === 'object'
|
|
228
|
+
? payload.searchSchema
|
|
229
|
+
: {};
|
|
230
|
+
|
|
231
|
+
const requiredInput = Array.isArray(fieldGuide.required) && fieldGuide.required.length > 0
|
|
232
|
+
? fieldGuide.required
|
|
233
|
+
: (Array.isArray(joinSchema.requiredFields) ? joinSchema.requiredFields : []);
|
|
234
|
+
const optionalInput = Array.isArray(fieldGuide.optional) && fieldGuide.optional.length > 0
|
|
235
|
+
? fieldGuide.optional
|
|
236
|
+
: (Array.isArray(joinSchema.optionalFields) ? joinSchema.optionalFields : []);
|
|
237
|
+
|
|
238
|
+
const requiredFields = requiredInput.map((field, index) => normalizeField(field, index, { required: true }));
|
|
239
|
+
const optionalFields = optionalInput.map((field, index) => normalizeField(field, index, { required: false }));
|
|
240
|
+
const worldId = normalizeText(world.worldId || joinSchema.worldId, 'unknown-world');
|
|
241
|
+
const displayName = normalizeText(agentSummary.displayName || world.displayName, worldId);
|
|
242
|
+
|
|
243
|
+
return {
|
|
244
|
+
status: 'ready',
|
|
245
|
+
source: 'product_shell',
|
|
246
|
+
worldId,
|
|
247
|
+
displayName,
|
|
248
|
+
summary: normalizeText(agentSummary.summary || world.summary, ''),
|
|
249
|
+
description: normalizeText(world.description, normalizeText(agentSummary.summary || world.summary, '')),
|
|
250
|
+
category: normalizeText(agentSummary.category || world.category, 'general'),
|
|
251
|
+
requiredFieldCount: normalizeInteger(joinSchema.requiredFieldCount, requiredFields.length) || requiredFields.length,
|
|
252
|
+
optionalFieldCount: normalizeInteger(joinSchema.optionalFieldCount, optionalFields.length) || optionalFields.length,
|
|
253
|
+
matchingMode: normalizeText(agentSummary.matchingMode || matchingOverview.mode || world.matching?.mode, 'manual_review'),
|
|
254
|
+
sessionMode: normalizeText(agentSummary.sessionMode || sessionOverview.mode || world.sessionTemplate?.mode, 'a2a'),
|
|
255
|
+
interactionRules: normalizeText(world.interactionRules, null),
|
|
256
|
+
prohibitedRules: normalizeText(world.prohibitedRules, null),
|
|
257
|
+
ratingRules: normalizeText(world.ratingRules, null),
|
|
258
|
+
adminAgentIds: normalizeStringList(world.adminAgentIds),
|
|
259
|
+
eligibility: normalizeText(world.eligibility, 'active'),
|
|
260
|
+
broadcast: normalizeBroadcastConfig(world.broadcast),
|
|
261
|
+
requiredFields,
|
|
262
|
+
optionalFields,
|
|
263
|
+
hints: normalizeStringList(joinSchema.hints),
|
|
264
|
+
nextAction: normalizeText(joinSchema.nextAction, 'collect_profile_fields_then_call_join'),
|
|
265
|
+
sessionOverview,
|
|
266
|
+
matchingOverview,
|
|
267
|
+
requiredFieldExplanation:
|
|
268
|
+
payload.requiredFieldExplanation && typeof payload.requiredFieldExplanation === 'object'
|
|
269
|
+
? payload.requiredFieldExplanation
|
|
270
|
+
: null,
|
|
271
|
+
runtimeStartupContext:
|
|
272
|
+
payload.runtimeStartupContext && typeof payload.runtimeStartupContext === 'object'
|
|
273
|
+
? payload.runtimeStartupContext
|
|
274
|
+
: null,
|
|
275
|
+
searchSchema: normalizeSearchSchema(searchOverview, {
|
|
276
|
+
worldId,
|
|
277
|
+
fallbackFields: [...requiredFields, ...optionalFields],
|
|
278
|
+
}),
|
|
279
|
+
};
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
function normalizeJoinCheckField(field = {}, index = 0) {
|
|
283
|
+
return normalizeField(field, index, { required: true });
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
function normalizeJoinCheckResponse(payload = {}, { worldId = null, profile = {} } = {}) {
|
|
287
|
+
const missingFields = Array.isArray(payload.missingFields)
|
|
288
|
+
? payload.missingFields.map((field, index) => normalizeJoinCheckField(field, index))
|
|
289
|
+
: [];
|
|
290
|
+
const guidance = payload.missingFieldGuidance && typeof payload.missingFieldGuidance === 'object'
|
|
291
|
+
? payload.missingFieldGuidance
|
|
292
|
+
: {};
|
|
293
|
+
const orderedMissingFields = Array.isArray(guidance.orderedMissingFields)
|
|
294
|
+
? guidance.orderedMissingFields.map((field, index) => normalizeJoinCheckField(field, index))
|
|
295
|
+
: missingFields;
|
|
296
|
+
const fallbackNextMissingField = orderedMissingFields[0] || missingFields[0] || null;
|
|
297
|
+
const nextMissingField = payload.nextMissingField
|
|
298
|
+
? normalizeJoinCheckField(payload.nextMissingField)
|
|
299
|
+
: (guidance.nextMissingField ? normalizeJoinCheckField(guidance.nextMissingField) : fallbackNextMissingField);
|
|
300
|
+
const accepted = payload.accepted === true || orderedMissingFields.length === 0;
|
|
301
|
+
|
|
302
|
+
return {
|
|
303
|
+
worldId: normalizeText(payload.worldId, worldId || 'unknown-world'),
|
|
304
|
+
accepted,
|
|
305
|
+
status: normalizeText(payload.status, accepted ? 'eligible' : 'needs_profile'),
|
|
306
|
+
missingFields,
|
|
307
|
+
nextMissingField,
|
|
308
|
+
missingFieldGuidance: {
|
|
309
|
+
mode: normalizeText(guidance.mode, nextMissingField ? 'ordered_required_fields' : 'complete'),
|
|
310
|
+
orderedMissingFields,
|
|
311
|
+
orderedMissingFieldIds: normalizeStringList(
|
|
312
|
+
Array.isArray(guidance.orderedMissingFieldIds)
|
|
313
|
+
? guidance.orderedMissingFieldIds
|
|
314
|
+
: orderedMissingFields.map((field) => field.fieldId),
|
|
315
|
+
),
|
|
316
|
+
nextMissingField,
|
|
317
|
+
remainingRequiredFieldCount: normalizeInteger(guidance.remainingRequiredFieldCount, orderedMissingFields.length),
|
|
318
|
+
},
|
|
319
|
+
normalizedProfile: normalizeProfile(
|
|
320
|
+
payload.normalizedProfile && typeof payload.normalizedProfile === 'object'
|
|
321
|
+
? payload.normalizedProfile
|
|
322
|
+
: profile,
|
|
323
|
+
),
|
|
324
|
+
nextAction: normalizeText(
|
|
325
|
+
payload.nextAction,
|
|
326
|
+
accepted ? 'join_world_when_membership_persistence_exists' : 'collect_missing_profile_fields',
|
|
327
|
+
),
|
|
328
|
+
profileCollectionFlow:
|
|
329
|
+
payload.profileCollectionFlow && typeof payload.profileCollectionFlow === 'object'
|
|
330
|
+
? payload.profileCollectionFlow
|
|
331
|
+
: null,
|
|
332
|
+
};
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
function normalizeNumber(value, fallback = null) {
|
|
336
|
+
const parsed = Number(value);
|
|
337
|
+
if (!Number.isFinite(parsed)) return fallback;
|
|
338
|
+
return parsed;
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
function normalizeProfileSummaryField(field = {}, index = 0) {
|
|
342
|
+
const fieldId = normalizeText(field.fieldId || field.id, `field_${index + 1}`);
|
|
343
|
+
const value = Array.isArray(field.value)
|
|
344
|
+
? normalizeStringList(field.value)
|
|
345
|
+
: normalizeText(field.value, null);
|
|
346
|
+
|
|
347
|
+
if (value == null || (Array.isArray(value) && value.length === 0)) {
|
|
348
|
+
return null;
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
return {
|
|
352
|
+
fieldId,
|
|
353
|
+
label: normalizeText(field.label, fieldId),
|
|
354
|
+
value,
|
|
355
|
+
};
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
function normalizeCandidateProfileSummary(summary = {}) {
|
|
359
|
+
return {
|
|
360
|
+
displayName: normalizeText(summary.displayName, null),
|
|
361
|
+
headline: normalizeText(summary.headline, null),
|
|
362
|
+
requiredFields: Array.isArray(summary.requiredFields)
|
|
363
|
+
? summary.requiredFields.map((field, index) => normalizeProfileSummaryField(field, index)).filter(Boolean)
|
|
364
|
+
: [],
|
|
365
|
+
optionalFields: Array.isArray(summary.optionalFields)
|
|
366
|
+
? summary.optionalFields.map((field, index) => normalizeProfileSummaryField(field, index)).filter(Boolean)
|
|
367
|
+
: [],
|
|
368
|
+
};
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
function normalizeCompatibilitySignal(signal = {}, index = 0) {
|
|
372
|
+
return {
|
|
373
|
+
signalId: normalizeText(signal.signalId, `signal_${index + 1}`),
|
|
374
|
+
type: normalizeText(signal.type, 'world_ready'),
|
|
375
|
+
fieldIds: normalizeStringList(signal.fieldIds),
|
|
376
|
+
score: normalizeNumber(signal.score, 0),
|
|
377
|
+
summary: normalizeText(signal.summary, ''),
|
|
378
|
+
};
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
function normalizeDeliveryReason(reason = {}) {
|
|
382
|
+
return {
|
|
383
|
+
code: normalizeText(reason.code, null),
|
|
384
|
+
matchedFieldIds: normalizeStringList(reason.matchedFieldIds),
|
|
385
|
+
summary: normalizeText(reason.summary, ''),
|
|
386
|
+
};
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
function normalizeCandidateScoreBreakdown(entries = []) {
|
|
390
|
+
if (!Array.isArray(entries)) return [];
|
|
391
|
+
|
|
392
|
+
return entries.map((entry, index) => ({
|
|
393
|
+
signalId: normalizeText(entry.signalId, `score_signal_${index + 1}`),
|
|
394
|
+
label: normalizeText(entry.label, `Signal ${index + 1}`),
|
|
395
|
+
weight: normalizeNumber(entry.weight, 0),
|
|
396
|
+
sourceFieldIds: normalizeStringList(entry.sourceFieldIds),
|
|
397
|
+
matched: entry.matched === true,
|
|
398
|
+
requesterValue: entry.requesterValue ?? null,
|
|
399
|
+
candidateValue: entry.candidateValue ?? null,
|
|
400
|
+
sharedValues: Array.isArray(entry.sharedValues) ? normalizeStringList(entry.sharedValues) : [],
|
|
401
|
+
overlapCount: normalizeInteger(entry.overlapCount, 0),
|
|
402
|
+
contribution: normalizeNumber(entry.contribution, 0),
|
|
403
|
+
}));
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
function normalizeCandidate(candidate = {}, index = 0) {
|
|
407
|
+
const normalizedRank = normalizeNumber(candidate.rank, null);
|
|
408
|
+
|
|
409
|
+
return {
|
|
410
|
+
candidateId: normalizeText(candidate.candidateId, `candidate_${index + 1}`),
|
|
411
|
+
worldId: normalizeText(candidate.worldId, 'unknown-world'),
|
|
412
|
+
sourceMembershipId: normalizeText(candidate.sourceMembershipId, null),
|
|
413
|
+
profileSummary: normalizeCandidateProfileSummary(candidate.profileSummary),
|
|
414
|
+
compatibilitySignals: Array.isArray(candidate.compatibilitySignals)
|
|
415
|
+
? candidate.compatibilitySignals.map((signal, signalIndex) => normalizeCompatibilitySignal(signal, signalIndex))
|
|
416
|
+
: [],
|
|
417
|
+
deliveryReason: normalizeDeliveryReason(candidate.deliveryReason),
|
|
418
|
+
expiresAt: normalizeText(candidate.expiresAt, null),
|
|
419
|
+
joinedAt: normalizeText(candidate.joinedAt, null),
|
|
420
|
+
rank: normalizedRank == null ? null : Math.max(1, Math.trunc(normalizedRank)),
|
|
421
|
+
score: normalizeNumber(candidate.score, null),
|
|
422
|
+
scoreBreakdown: normalizeCandidateScoreBreakdown(candidate.scoreBreakdown),
|
|
423
|
+
scoringInputs: candidate.scoringInputs && typeof candidate.scoringInputs === 'object' ? candidate.scoringInputs : {},
|
|
424
|
+
};
|
|
425
|
+
}
|
|
426
|
+
|
|
427
|
+
function normalizeCandidateFeedResponse(payload = {}, { worldId = null, agentId = null } = {}) {
|
|
428
|
+
const candidates = Array.isArray(payload.candidates)
|
|
429
|
+
? payload.candidates.map((candidate, index) => normalizeCandidate(candidate, index))
|
|
430
|
+
: [];
|
|
431
|
+
|
|
432
|
+
return {
|
|
433
|
+
worldId: normalizeText(payload.worldId, worldId || 'unknown-world'),
|
|
434
|
+
agentId: normalizeText(payload.agentId, agentId || null),
|
|
435
|
+
viewerMembershipId: normalizeText(payload.viewerMembershipId, null),
|
|
436
|
+
generatedAt: normalizeText(payload.generatedAt, null),
|
|
437
|
+
expiresAt: normalizeText(payload.expiresAt, null),
|
|
438
|
+
deliveryMode: normalizeText(payload.deliveryMode, 'agent_review_before_live_session'),
|
|
439
|
+
nextAction: normalizeText(
|
|
440
|
+
payload.nextAction,
|
|
441
|
+
candidates.length > 0 ? 'review_candidates_before_requesting_live_session' : 'wait_for_more_candidates',
|
|
442
|
+
),
|
|
443
|
+
candidateDelivery: payload.candidateDelivery && typeof payload.candidateDelivery === 'object'
|
|
444
|
+
? payload.candidateDelivery
|
|
445
|
+
: null,
|
|
446
|
+
candidateSource: normalizeText(payload.candidateSource, 'active_memberships'),
|
|
447
|
+
candidateModel: payload.candidateModel && typeof payload.candidateModel === 'object' ? payload.candidateModel : {},
|
|
448
|
+
strategy: payload.strategy && typeof payload.strategy === 'object' ? payload.strategy : {},
|
|
449
|
+
limit: normalizeInteger(payload.limit, candidates.length),
|
|
450
|
+
totalCandidates: normalizeInteger(payload.totalCandidates, candidates.length),
|
|
451
|
+
status: normalizeText(payload.status, candidates.length > 0 ? 'feed_ready' : 'no_candidates_ready'),
|
|
452
|
+
candidates,
|
|
453
|
+
};
|
|
454
|
+
}
|
|
455
|
+
|
|
456
|
+
function normalizeWorldJoinResponse(payload = {}, { worldId = null, agentId = null } = {}) {
|
|
457
|
+
const membership = payload.membership && typeof payload.membership === 'object' ? payload.membership : null;
|
|
458
|
+
|
|
459
|
+
return {
|
|
460
|
+
worldId: normalizeText(payload.worldId, worldId || 'unknown-world'),
|
|
461
|
+
agentId: normalizeText(payload.agentId || membership?.agentId, agentId || null),
|
|
462
|
+
membershipStatus: normalizeText(payload.membershipStatus || membership?.status, 'unknown'),
|
|
463
|
+
membership,
|
|
464
|
+
nextStageSummary: payload.nextStageSummary && typeof payload.nextStageSummary === 'object'
|
|
465
|
+
? payload.nextStageSummary
|
|
466
|
+
: {},
|
|
467
|
+
orchestration: payload.orchestration && typeof payload.orchestration === 'object'
|
|
468
|
+
? payload.orchestration
|
|
469
|
+
: null,
|
|
470
|
+
};
|
|
471
|
+
}
|
|
472
|
+
|
|
473
|
+
function projectMissingRequiredFields(fields = []) {
|
|
474
|
+
return fields.map((field) => ({
|
|
475
|
+
fieldId: field.fieldId,
|
|
476
|
+
label: field.label,
|
|
477
|
+
description: field.description || null,
|
|
478
|
+
}));
|
|
479
|
+
}
|
|
480
|
+
|
|
481
|
+
function buildJoinRequestBody({ agentId = null, profile = {}, profileSnapshot = null } = {}) {
|
|
482
|
+
const normalizedAgentId = normalizeText(agentId, null);
|
|
483
|
+
const normalizedProfile = normalizeProfile(profile);
|
|
484
|
+
const normalizedProfileSnapshot = normalizeProfile(profileSnapshot);
|
|
485
|
+
|
|
486
|
+
return {
|
|
487
|
+
agentId: normalizedAgentId,
|
|
488
|
+
...(Object.keys(normalizedProfileSnapshot).length > 0
|
|
489
|
+
? { profileSnapshot: normalizedProfileSnapshot }
|
|
490
|
+
: (Object.keys(normalizedProfile).length > 0 ? { profile: normalizedProfile } : {})),
|
|
491
|
+
};
|
|
492
|
+
}
|
|
493
|
+
|
|
494
|
+
function resolveJoinSubmissionProfile({
|
|
495
|
+
profile = {},
|
|
496
|
+
profileSnapshot = null,
|
|
497
|
+
profileUpdate = null,
|
|
498
|
+
profilePatch = null,
|
|
499
|
+
} = {}) {
|
|
500
|
+
const normalizedProfile = normalizeProfile(profile);
|
|
501
|
+
const normalizedProfileSnapshot = normalizeProfile(profileSnapshot);
|
|
502
|
+
const appliedProfileUpdate = normalizeProfile(profileUpdate || profilePatch || {});
|
|
503
|
+
const baseProfile = Object.keys(normalizedProfileSnapshot).length > 0
|
|
504
|
+
? normalizedProfileSnapshot
|
|
505
|
+
: normalizedProfile;
|
|
506
|
+
|
|
507
|
+
return {
|
|
508
|
+
baseProfile,
|
|
509
|
+
appliedProfileUpdate,
|
|
510
|
+
mergedProfile: mergeProfileState(baseProfile, appliedProfileUpdate),
|
|
511
|
+
preferProfileSnapshot:
|
|
512
|
+
Object.keys(normalizedProfileSnapshot).length > 0
|
|
513
|
+
|| Object.keys(appliedProfileUpdate).length > 0,
|
|
514
|
+
};
|
|
515
|
+
}
|
|
516
|
+
|
|
517
|
+
function normalizeSearchMatchedField(field = {}, index = 0) {
|
|
518
|
+
return {
|
|
519
|
+
fieldId: normalizeText(field.fieldId, `field_${index + 1}`),
|
|
520
|
+
label: normalizeText(field.label, `Field ${index + 1}`),
|
|
521
|
+
matchType: normalizeText(field.matchType, 'exact'),
|
|
522
|
+
queryValue: field.queryValue ?? null,
|
|
523
|
+
candidateValue: field.candidateValue ?? null,
|
|
524
|
+
sharedValues: Array.isArray(field.sharedValues) ? normalizeStringList(field.sharedValues) : [],
|
|
525
|
+
contribution: normalizeNumber(field.contribution, 0),
|
|
526
|
+
};
|
|
527
|
+
}
|
|
528
|
+
|
|
529
|
+
function normalizeWorldSearchItem(item = {}, index = 0) {
|
|
530
|
+
const resolvedAgentId = normalizeText(item.agentId, normalizeText(item.playerId, `agent_${index + 1}`));
|
|
531
|
+
return {
|
|
532
|
+
agentId: resolvedAgentId,
|
|
533
|
+
playerId: normalizeText(item.playerId, resolvedAgentId),
|
|
534
|
+
membershipId: normalizeText(item.membershipId, null),
|
|
535
|
+
worldId: normalizeText(item.worldId, 'unknown-world'),
|
|
536
|
+
displayName: normalizeText(item.displayName, `Player ${index + 1}`),
|
|
537
|
+
headline: normalizeText(item.headline, null),
|
|
538
|
+
address: normalizeText(item.address, null),
|
|
539
|
+
online: item.online === true,
|
|
540
|
+
connectedAt: normalizeText(item.connectedAt, null),
|
|
541
|
+
lastHeartbeatAt: normalizeText(item.lastHeartbeatAt, null),
|
|
542
|
+
score: normalizeNumber(item.score, 0),
|
|
543
|
+
matchedFieldIds: normalizeStringList(item.matchedFieldIds),
|
|
544
|
+
matchedFields: Array.isArray(item.matchedFields)
|
|
545
|
+
? item.matchedFields.map((field, fieldIndex) => normalizeSearchMatchedField(field, fieldIndex))
|
|
546
|
+
: [],
|
|
547
|
+
resultType: normalizeText(item.resultType, 'matched'),
|
|
548
|
+
reasonSummary: normalizeText(item.reasonSummary, ''),
|
|
549
|
+
joinedAt: normalizeText(item.joinedAt, null),
|
|
550
|
+
profileSummary: normalizeCandidateProfileSummary(item.profileSummary),
|
|
551
|
+
};
|
|
552
|
+
}
|
|
553
|
+
|
|
554
|
+
function normalizeWorldSearchResponse(payload = {}, { worldId = null, agentId = null } = {}) {
|
|
555
|
+
const items = Array.isArray(payload.items)
|
|
556
|
+
? payload.items.map((item, index) => normalizeWorldSearchItem(item, index))
|
|
557
|
+
: [];
|
|
558
|
+
|
|
559
|
+
return {
|
|
560
|
+
worldId: normalizeText(payload.worldId, worldId || 'unknown-world'),
|
|
561
|
+
agentId: normalizeText(payload.agentId, agentId || null),
|
|
562
|
+
viewerMembershipId: normalizeText(payload.viewerMembershipId, null),
|
|
563
|
+
searchModel: normalizeSearchSchema(payload.searchModel || {}, {
|
|
564
|
+
worldId: normalizeText(payload.worldId, worldId || 'unknown-world'),
|
|
565
|
+
fallbackFields: [],
|
|
566
|
+
}),
|
|
567
|
+
searchInput: normalizeProfile(payload.searchInput && typeof payload.searchInput === 'object' ? payload.searchInput : {}),
|
|
568
|
+
onlineOnly: payload.onlineOnly !== false,
|
|
569
|
+
candidateSource: normalizeText(payload.candidateSource, 'active_memberships_online'),
|
|
570
|
+
limit: normalizeInteger(payload.limit, items.length),
|
|
571
|
+
totalMatches: normalizeInteger(payload.totalMatches, items.length),
|
|
572
|
+
status: normalizeText(payload.status, items.length > 0 ? 'search_ready' : 'no_matches'),
|
|
573
|
+
nextAction: normalizeText(
|
|
574
|
+
payload.nextAction,
|
|
575
|
+
items.length > 0 ? 'select_player_and_start_conversation' : 'broaden_search_or_wait',
|
|
576
|
+
),
|
|
577
|
+
items,
|
|
578
|
+
};
|
|
579
|
+
}
|
|
580
|
+
|
|
581
|
+
function summarizeProfileValue(value) {
|
|
582
|
+
if (Array.isArray(value)) return joinAsNaturalLanguage(value.map((entry) => String(entry).trim()).filter(Boolean));
|
|
583
|
+
return normalizeText(value, '');
|
|
584
|
+
}
|
|
585
|
+
|
|
586
|
+
function summarizeProfileFields(fields = []) {
|
|
587
|
+
return fields
|
|
588
|
+
.map((field) => {
|
|
589
|
+
const value = summarizeProfileValue(field.value);
|
|
590
|
+
if (!value) return null;
|
|
591
|
+
return `${field.label}: ${value}`;
|
|
592
|
+
})
|
|
593
|
+
.filter(Boolean);
|
|
594
|
+
}
|
|
595
|
+
|
|
596
|
+
function buildCandidateDeliverySummaryLine(candidateSummary = {}, index = 0) {
|
|
597
|
+
return `${index + 1}. ${candidateSummary.summary}`;
|
|
598
|
+
}
|
|
599
|
+
|
|
600
|
+
export function buildCandidateDeliverySummary(candidateFeed = {}, { worldDetail = null, limit = null } = {}) {
|
|
601
|
+
if (candidateFeed?.candidateDelivery && typeof candidateFeed.candidateDelivery === 'object') {
|
|
602
|
+
return candidateFeed.candidateDelivery;
|
|
603
|
+
}
|
|
604
|
+
if (worldDetail?.candidateDelivery && typeof worldDetail.candidateDelivery === 'object') {
|
|
605
|
+
return worldDetail.candidateDelivery;
|
|
606
|
+
}
|
|
607
|
+
// Older responses may still omit candidateDelivery; keep the shared builder as
|
|
608
|
+
// a compatibility fallback, not the canonical orchestration owner.
|
|
609
|
+
return buildBackendCandidateDeliverySummary(candidateFeed, { worldDetail, limit });
|
|
610
|
+
}
|
|
611
|
+
|
|
612
|
+
function buildFieldLookup(worldDetail = {}) {
|
|
613
|
+
const detail = normalizeWorldDetail(worldDetail);
|
|
614
|
+
return new Map(
|
|
615
|
+
[...detail.requiredFields, ...detail.optionalFields].map((field) => [field.fieldId, field]),
|
|
616
|
+
);
|
|
617
|
+
}
|
|
618
|
+
|
|
619
|
+
function selectPromptFields(joinCheck = {}, worldDetail = {}, maxFieldsPerStep = 1) {
|
|
620
|
+
if (joinCheck.accepted) return [];
|
|
621
|
+
|
|
622
|
+
const fieldLookup = buildFieldLookup(worldDetail);
|
|
623
|
+
const orderedMissingFields = Array.isArray(joinCheck.missingFieldGuidance?.orderedMissingFields)
|
|
624
|
+
&& joinCheck.missingFieldGuidance.orderedMissingFields.length > 0
|
|
625
|
+
? joinCheck.missingFieldGuidance.orderedMissingFields
|
|
626
|
+
: (Array.isArray(joinCheck.missingFields) ? joinCheck.missingFields : []);
|
|
627
|
+
|
|
628
|
+
return orderedMissingFields.slice(0, Math.max(1, maxFieldsPerStep)).map((field, index) => {
|
|
629
|
+
const detailField = fieldLookup.get(field.fieldId) || {};
|
|
630
|
+
return normalizeField(
|
|
631
|
+
{
|
|
632
|
+
...detailField,
|
|
633
|
+
...field,
|
|
634
|
+
description: normalizeText(field.description, detailField.description || null),
|
|
635
|
+
examples: Array.isArray(detailField.examples) ? detailField.examples : field.examples,
|
|
636
|
+
constraints: detailField.constraints || field.constraints,
|
|
637
|
+
},
|
|
638
|
+
index,
|
|
639
|
+
{ required: true },
|
|
640
|
+
);
|
|
641
|
+
});
|
|
642
|
+
}
|
|
643
|
+
|
|
644
|
+
function buildProfileFieldPrompt(field = {}, index = 0, total = 1) {
|
|
645
|
+
const examples = Array.isArray(field.examples) && field.examples.length > 0
|
|
646
|
+
? ` Example: ${field.examples.map((example) => quoteExample(example)).join(' or ')}.`
|
|
647
|
+
: '';
|
|
648
|
+
const description = sentenceCase(
|
|
649
|
+
field.description || `Provide ${field.label} so the world can evaluate the profile`,
|
|
650
|
+
'Provide this field so the world can evaluate the profile.',
|
|
651
|
+
);
|
|
652
|
+
const prefix = total > 1 ? `${index + 1}. ` : '';
|
|
653
|
+
|
|
654
|
+
return `${prefix}${field.label}. ${description}${examples}`;
|
|
655
|
+
}
|
|
656
|
+
|
|
657
|
+
function listProvidedRequiredFieldIds(worldDetail = {}, profile = {}, missingFieldIds = []) {
|
|
658
|
+
const missingSet = new Set(normalizeStringList(missingFieldIds));
|
|
659
|
+
return normalizeWorldDetail(worldDetail).requiredFields
|
|
660
|
+
.filter((field) => !missingSet.has(field.fieldId) && !isEmptyProfileValue(profile[field.fieldId]))
|
|
661
|
+
.map((field) => field.fieldId);
|
|
662
|
+
}
|
|
663
|
+
|
|
664
|
+
function buildProfileCollectionFollowUp(promptFields = []) {
|
|
665
|
+
if (promptFields.length === 0) {
|
|
666
|
+
return 'The current profile is already eligible. Continue with the next world step without restarting profile collection.';
|
|
667
|
+
}
|
|
668
|
+
|
|
669
|
+
const labels = promptFields.map((field) => field.label);
|
|
670
|
+
if (promptFields.length === 1) {
|
|
671
|
+
return `After the user answers ${labels[0]}, merge it into the saved profile draft and re-run join-check before asking anything else.`;
|
|
672
|
+
}
|
|
673
|
+
|
|
674
|
+
return `After the user answers ${joinAsNaturalLanguage(labels)}, merge those fields into the saved profile draft and re-run join-check before asking anything else.`;
|
|
675
|
+
}
|
|
676
|
+
|
|
677
|
+
function normalizeSelectionInput(selection) {
|
|
678
|
+
if (selection && typeof selection === 'object') {
|
|
679
|
+
const asWorldId = normalizeText(selection.worldId, null);
|
|
680
|
+
const asDisplayName = normalizeText(selection.displayName, null);
|
|
681
|
+
const asChoice = normalizeText(selection.selection || selection.choice || selection.value, null);
|
|
682
|
+
const text = asWorldId || asDisplayName || asChoice || '';
|
|
683
|
+
return {
|
|
684
|
+
raw: selection,
|
|
685
|
+
text,
|
|
686
|
+
normalized: normalizeLookupText(text),
|
|
687
|
+
index: /^\d+$/.test(String(text)) ? normalizeInteger(text, 0) : null,
|
|
688
|
+
};
|
|
689
|
+
}
|
|
690
|
+
|
|
691
|
+
const text = normalizeText(selection, '');
|
|
692
|
+
return {
|
|
693
|
+
raw: selection,
|
|
694
|
+
text,
|
|
695
|
+
normalized: normalizeLookupText(text),
|
|
696
|
+
index: /^\d+$/.test(String(text)) ? normalizeInteger(text, 0) : null,
|
|
697
|
+
};
|
|
698
|
+
}
|
|
699
|
+
|
|
700
|
+
function buildSelectionRetryContract(status, selection, items = [], matches = []) {
|
|
701
|
+
const choiceLabel = selection.text ? `"${selection.text}"` : 'that choice';
|
|
702
|
+
const retryWorlds = (matches.length > 0 ? matches : items).map((world) => normalizeWorldSummary(world));
|
|
703
|
+
const retrySummary = summarizeWorldChoices(retryWorlds);
|
|
704
|
+
|
|
705
|
+
if (status === 'ambiguous') {
|
|
706
|
+
return {
|
|
707
|
+
status,
|
|
708
|
+
selection: {
|
|
709
|
+
input: selection.text || null,
|
|
710
|
+
matchedBy: null,
|
|
711
|
+
worldId: null,
|
|
712
|
+
displayName: null,
|
|
713
|
+
},
|
|
714
|
+
candidateWorlds: retryWorlds,
|
|
715
|
+
orchestration: {
|
|
716
|
+
stage: 'post_setup_world_selection_retry',
|
|
717
|
+
system: 'The world choice matched multiple worlds. Show the narrowed list and ask the user to pick one exact world ID or display name.',
|
|
718
|
+
user: `The choice ${choiceLabel} is ambiguous. Matching worlds: ${joinAsNaturalLanguage(retrySummary)}. Ask the user to choose one exact world ID or display name.`,
|
|
719
|
+
followUp: 'Once the user confirms one exact world, fetch its detail and explain the required fields before join-check.',
|
|
720
|
+
},
|
|
721
|
+
};
|
|
722
|
+
}
|
|
723
|
+
|
|
724
|
+
return {
|
|
725
|
+
status: 'no_match',
|
|
726
|
+
selection: {
|
|
727
|
+
input: selection.text || null,
|
|
728
|
+
matchedBy: null,
|
|
729
|
+
worldId: null,
|
|
730
|
+
displayName: null,
|
|
731
|
+
},
|
|
732
|
+
candidateWorlds: retryWorlds,
|
|
733
|
+
orchestration: {
|
|
734
|
+
stage: 'post_setup_world_selection_retry',
|
|
735
|
+
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.',
|
|
736
|
+
user: retrySummary.length > 0
|
|
737
|
+
? `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.`
|
|
738
|
+
: 'No worlds are currently available. Tell the user setup is complete but world selection cannot continue yet.',
|
|
739
|
+
followUp: 'Once the user chooses a valid world, confirm it, fetch the world detail, and explain the required fields before join-check.',
|
|
740
|
+
},
|
|
741
|
+
};
|
|
742
|
+
}
|
|
743
|
+
|
|
744
|
+
export function buildWorldSelectionPrompt(worldDirectory = {}) {
|
|
745
|
+
if (worldDirectory?.orchestration && typeof worldDirectory.orchestration === 'object') {
|
|
746
|
+
return worldDirectory.orchestration;
|
|
747
|
+
}
|
|
748
|
+
// Compatibility fallback for older directory payloads that predate
|
|
749
|
+
// backend-authored orchestration.
|
|
750
|
+
return buildBackendWorldSelectionPrompt(worldDirectory);
|
|
751
|
+
}
|
|
752
|
+
|
|
753
|
+
export function buildPostSetupWorldDirectory(payload = {}, { accountId = null } = {}) {
|
|
754
|
+
const items = Array.isArray(payload.items) ? payload.items.map((world) => normalizeWorldSummary(world)) : [];
|
|
755
|
+
const recommendedWorldId = items[0]?.worldId || null;
|
|
756
|
+
const pagination = payload.pagination && typeof payload.pagination === 'object'
|
|
757
|
+
? {
|
|
758
|
+
page: normalizeInteger(payload.pagination.page, 1) || 1,
|
|
759
|
+
totalPages: normalizeInteger(payload.pagination.totalPages, 0),
|
|
760
|
+
totalCount: normalizeInteger(payload.pagination.totalCount, items.length),
|
|
761
|
+
}
|
|
762
|
+
: {
|
|
763
|
+
page: 1,
|
|
764
|
+
totalPages: items.length > 0 ? 1 : 0,
|
|
765
|
+
totalCount: items.length,
|
|
766
|
+
};
|
|
767
|
+
const sort = normalizeText(payload.sort, 'hot');
|
|
768
|
+
|
|
769
|
+
return {
|
|
770
|
+
status: 'ready',
|
|
771
|
+
source: 'product_shell',
|
|
772
|
+
accountId: normalizeText(accountId, null),
|
|
773
|
+
worldCount: pagination.totalCount,
|
|
774
|
+
recommendedWorldId,
|
|
775
|
+
items,
|
|
776
|
+
pagination,
|
|
777
|
+
sort,
|
|
778
|
+
orchestration: payload.orchestration && typeof payload.orchestration === 'object'
|
|
779
|
+
? payload.orchestration
|
|
780
|
+
: buildWorldSelectionPrompt({ items, recommendedWorldId }),
|
|
781
|
+
};
|
|
782
|
+
}
|
|
783
|
+
|
|
784
|
+
export function resolveWorldSelection(worldDirectory = {}, selection = null) {
|
|
785
|
+
return resolveBackendWorldSelection(worldDirectory, selection);
|
|
786
|
+
}
|
|
787
|
+
|
|
788
|
+
async function fetchJson(fetchImpl, url, init = {}) {
|
|
789
|
+
let response;
|
|
790
|
+
try {
|
|
791
|
+
response = await fetchImpl(url, init);
|
|
792
|
+
} catch (error) {
|
|
793
|
+
throw createRuntimeBoundaryError({
|
|
794
|
+
code: 'relay_fetch_failed',
|
|
795
|
+
category: 'transport',
|
|
796
|
+
status: 502,
|
|
797
|
+
message: `fetch failed: ${error?.message || String(error)}`,
|
|
798
|
+
publicMessage: 'relay fetch failed',
|
|
799
|
+
recoverable: true,
|
|
800
|
+
context: {
|
|
801
|
+
fetchUrl: url,
|
|
802
|
+
fetchMethod: init?.method || 'GET',
|
|
803
|
+
},
|
|
804
|
+
cause: error,
|
|
805
|
+
});
|
|
806
|
+
}
|
|
807
|
+
let body = null;
|
|
808
|
+
|
|
809
|
+
try {
|
|
810
|
+
body = await response.json();
|
|
811
|
+
} catch {
|
|
812
|
+
body = null;
|
|
813
|
+
}
|
|
814
|
+
|
|
815
|
+
return { ok: response.ok, status: response.status, body };
|
|
816
|
+
}
|
|
817
|
+
|
|
818
|
+
function normalizeRelayHttpBaseUrl(serverUrl) {
|
|
819
|
+
const parsed = new URL(serverUrl);
|
|
820
|
+
if (parsed.protocol === 'ws:') parsed.protocol = 'http:';
|
|
821
|
+
if (parsed.protocol === 'wss:') parsed.protocol = 'https:';
|
|
822
|
+
parsed.pathname = '';
|
|
823
|
+
parsed.search = '';
|
|
824
|
+
parsed.hash = '';
|
|
825
|
+
return parsed.toString().replace(/\/$/, '');
|
|
826
|
+
}
|
|
827
|
+
|
|
828
|
+
function inferHttpErrorCategory(status) {
|
|
829
|
+
if (status === 401) return 'auth';
|
|
830
|
+
if (status === 403) return 'policy';
|
|
831
|
+
if (status === 409) return 'conflict';
|
|
832
|
+
if (status >= 400 && status < 500) return 'input';
|
|
833
|
+
return 'runtime';
|
|
834
|
+
}
|
|
835
|
+
|
|
836
|
+
function createProductShellHttpError(action, response, { accountId = null, worldId = null } = {}) {
|
|
837
|
+
const backendCode = normalizeText(response?.body?.error, null);
|
|
838
|
+
const backendMessage = normalizeText(response?.body?.message, `claworld product-shell ${action} failed`);
|
|
839
|
+
|
|
840
|
+
return createRuntimeBoundaryError({
|
|
841
|
+
code: backendCode || `claworld_product_shell_${action}_failed`,
|
|
842
|
+
category: inferHttpErrorCategory(response?.status),
|
|
843
|
+
status: response?.status ?? 500,
|
|
844
|
+
message: `claworld product-shell ${action} failed: ${response?.status ?? 500}`,
|
|
845
|
+
publicMessage: backendMessage,
|
|
846
|
+
recoverable: Number(response?.status) >= 400 && Number(response?.status) < 500,
|
|
847
|
+
context: {
|
|
848
|
+
action,
|
|
849
|
+
accountId,
|
|
850
|
+
...(worldId ? { worldId } : {}),
|
|
851
|
+
httpStatus: response?.status ?? 500,
|
|
852
|
+
backendCode,
|
|
853
|
+
backendMessage,
|
|
854
|
+
},
|
|
855
|
+
});
|
|
856
|
+
}
|
|
857
|
+
|
|
858
|
+
function normalizeBroadcastRequestPayload(payload = {}, { message = null } = {}) {
|
|
859
|
+
const normalizedPayload = payload && typeof payload === 'object' && !Array.isArray(payload)
|
|
860
|
+
? { ...payload }
|
|
861
|
+
: {};
|
|
862
|
+
const normalizedMessage = normalizeText(message, null);
|
|
863
|
+
if (!normalizedMessage) return normalizedPayload;
|
|
864
|
+
return {
|
|
865
|
+
...normalizedPayload,
|
|
866
|
+
...(normalizedPayload.text == null ? { text: normalizedMessage } : {}),
|
|
867
|
+
};
|
|
868
|
+
}
|
|
869
|
+
|
|
870
|
+
function normalizeBroadcastResult(payload = {}) {
|
|
871
|
+
const requests = Array.isArray(payload.requests)
|
|
872
|
+
? payload.requests.map((request) => ({
|
|
873
|
+
agentId: normalizeText(request.agentId, null),
|
|
874
|
+
status: normalizeText(request.status, 'created'),
|
|
875
|
+
chatRequest: request.chatRequest && typeof request.chatRequest === 'object' && !Array.isArray(request.chatRequest)
|
|
876
|
+
? request.chatRequest
|
|
877
|
+
: null,
|
|
878
|
+
}))
|
|
879
|
+
: [];
|
|
880
|
+
const failures = Array.isArray(payload.failures)
|
|
881
|
+
? payload.failures.map((failure) => ({
|
|
882
|
+
agentId: normalizeText(failure.agentId, null),
|
|
883
|
+
status: normalizeText(failure.status, 'failed'),
|
|
884
|
+
httpStatus: normalizeInteger(failure.httpStatus, 0),
|
|
885
|
+
error: normalizeText(failure.error, null),
|
|
886
|
+
reason: normalizeText(failure.reason, null),
|
|
887
|
+
message: normalizeText(failure.message, null),
|
|
888
|
+
}))
|
|
889
|
+
: [];
|
|
890
|
+
return {
|
|
891
|
+
status: normalizeText(payload.status, 'requests_created'),
|
|
892
|
+
worldId: normalizeText(payload.worldId, null),
|
|
893
|
+
senderAgentId: normalizeText(payload.senderAgentId, null),
|
|
894
|
+
senderRole: normalizeText(payload.senderRole, 'none'),
|
|
895
|
+
audience: normalizeText(payload.audience, 'members'),
|
|
896
|
+
excludeSelf: payload.excludeSelf !== false,
|
|
897
|
+
eligibility: normalizeText(payload.eligibility, 'active'),
|
|
898
|
+
broadcastId: normalizeText(payload.broadcastId, null),
|
|
899
|
+
totalTargets: normalizeInteger(payload.totalTargets, 0),
|
|
900
|
+
createdCount: normalizeInteger(payload.createdCount, requests.length),
|
|
901
|
+
failedCount: normalizeInteger(payload.failedCount, failures.length),
|
|
902
|
+
nextAction: normalizeText(payload.nextAction, 'recipients_review_pending_requests'),
|
|
903
|
+
requests,
|
|
904
|
+
failures,
|
|
905
|
+
};
|
|
906
|
+
}
|
|
907
|
+
|
|
908
|
+
export async function fetchWorldDetail({
|
|
909
|
+
cfg = {},
|
|
910
|
+
accountId = null,
|
|
911
|
+
runtimeConfig = null,
|
|
912
|
+
worldId = null,
|
|
913
|
+
fetchImpl,
|
|
914
|
+
logger = console,
|
|
915
|
+
} = {}) {
|
|
916
|
+
if (typeof fetchImpl !== 'function') {
|
|
917
|
+
throw new Error('fetch is unavailable for claworld product-shell detail helper');
|
|
918
|
+
}
|
|
919
|
+
|
|
920
|
+
const resolvedWorldId = normalizeText(worldId, null);
|
|
921
|
+
if (!resolvedWorldId) {
|
|
922
|
+
throw new Error('claworld product-shell detail helper requires worldId');
|
|
923
|
+
}
|
|
924
|
+
|
|
925
|
+
const resolvedRuntimeConfig = runtimeConfig || resolveClaworldRuntimeConfig(cfg, accountId);
|
|
926
|
+
const baseUrl = normalizeRelayHttpBaseUrl(resolvedRuntimeConfig.serverUrl);
|
|
927
|
+
const detail = await fetchJson(fetchImpl, `${baseUrl}/v1/worlds/${encodeURIComponent(resolvedWorldId)}`, {
|
|
928
|
+
headers: {
|
|
929
|
+
accept: 'application/json',
|
|
930
|
+
...(resolvedRuntimeConfig.apiKey ? { 'x-api-key': resolvedRuntimeConfig.apiKey } : {}),
|
|
931
|
+
...(resolvedRuntimeConfig.relay?.credentialToken ? { 'x-relay-token': resolvedRuntimeConfig.relay.credentialToken } : {}),
|
|
932
|
+
},
|
|
933
|
+
});
|
|
934
|
+
|
|
935
|
+
if (!detail.ok) {
|
|
936
|
+
logger.error?.('[claworld:product-shell] world detail fetch failed', {
|
|
937
|
+
status: detail.status,
|
|
938
|
+
worldId: resolvedWorldId,
|
|
939
|
+
accountId: resolvedRuntimeConfig.accountId || accountId || null,
|
|
940
|
+
body: detail.body,
|
|
941
|
+
});
|
|
942
|
+
throw new Error(`claworld product-shell world detail fetch failed: ${detail.status}`);
|
|
943
|
+
}
|
|
944
|
+
|
|
945
|
+
return normalizeWorldDetail(detail.body);
|
|
946
|
+
}
|
|
947
|
+
|
|
948
|
+
export async function fetchWorldJoinCheck({
|
|
949
|
+
cfg = {},
|
|
950
|
+
accountId = null,
|
|
951
|
+
runtimeConfig = null,
|
|
952
|
+
worldId = null,
|
|
953
|
+
profile = {},
|
|
954
|
+
maxFieldsPerStep = null,
|
|
955
|
+
fetchImpl,
|
|
956
|
+
logger = console,
|
|
957
|
+
} = {}) {
|
|
958
|
+
if (typeof fetchImpl !== 'function') {
|
|
959
|
+
throw new Error('fetch is unavailable for claworld product-shell join-check helper');
|
|
960
|
+
}
|
|
961
|
+
|
|
962
|
+
const resolvedWorldId = normalizeText(worldId, null);
|
|
963
|
+
if (!resolvedWorldId) {
|
|
964
|
+
throw new Error('claworld product-shell join-check helper requires worldId');
|
|
965
|
+
}
|
|
966
|
+
|
|
967
|
+
const resolvedRuntimeConfig = runtimeConfig || resolveClaworldRuntimeConfig(cfg, accountId);
|
|
968
|
+
const baseUrl = normalizeRelayHttpBaseUrl(resolvedRuntimeConfig.serverUrl);
|
|
969
|
+
const normalizedProfile = normalizeProfile(profile);
|
|
970
|
+
const joinCheck = await fetchJson(fetchImpl, `${baseUrl}/v1/worlds/${encodeURIComponent(resolvedWorldId)}/join-check`, {
|
|
971
|
+
method: 'POST',
|
|
972
|
+
headers: {
|
|
973
|
+
accept: 'application/json',
|
|
974
|
+
'content-type': 'application/json',
|
|
975
|
+
...(resolvedRuntimeConfig.apiKey ? { 'x-api-key': resolvedRuntimeConfig.apiKey } : {}),
|
|
976
|
+
...(resolvedRuntimeConfig.relay?.credentialToken ? { 'x-relay-token': resolvedRuntimeConfig.relay.credentialToken } : {}),
|
|
977
|
+
},
|
|
978
|
+
body: JSON.stringify({
|
|
979
|
+
profile: normalizedProfile,
|
|
980
|
+
...(maxFieldsPerStep == null ? {} : { maxFieldsPerStep }),
|
|
981
|
+
}),
|
|
982
|
+
});
|
|
983
|
+
|
|
984
|
+
if (!joinCheck.ok && joinCheck.status !== 422) {
|
|
985
|
+
logger.error?.('[claworld:product-shell] world join-check failed', {
|
|
986
|
+
status: joinCheck.status,
|
|
987
|
+
worldId: resolvedWorldId,
|
|
988
|
+
accountId: resolvedRuntimeConfig.accountId || accountId || null,
|
|
989
|
+
body: joinCheck.body,
|
|
990
|
+
});
|
|
991
|
+
throw new Error(`claworld product-shell join-check failed: ${joinCheck.status}`);
|
|
992
|
+
}
|
|
993
|
+
|
|
994
|
+
return normalizeJoinCheckResponse(joinCheck.body, {
|
|
995
|
+
worldId: resolvedWorldId,
|
|
996
|
+
profile: normalizedProfile,
|
|
997
|
+
});
|
|
998
|
+
}
|
|
999
|
+
|
|
1000
|
+
export async function joinWorld({
|
|
1001
|
+
cfg = {},
|
|
1002
|
+
accountId = null,
|
|
1003
|
+
runtimeConfig = null,
|
|
1004
|
+
worldId = null,
|
|
1005
|
+
agentId = null,
|
|
1006
|
+
profile = {},
|
|
1007
|
+
profileSnapshot = null,
|
|
1008
|
+
maxFieldsPerStep = null,
|
|
1009
|
+
fetchImpl,
|
|
1010
|
+
logger = console,
|
|
1011
|
+
} = {}) {
|
|
1012
|
+
if (typeof fetchImpl !== 'function') {
|
|
1013
|
+
throw new Error('fetch is unavailable for claworld product-shell join helper');
|
|
1014
|
+
}
|
|
1015
|
+
|
|
1016
|
+
const resolvedWorldId = normalizeText(worldId, null);
|
|
1017
|
+
if (!resolvedWorldId) {
|
|
1018
|
+
throw new Error('claworld product-shell join helper requires worldId');
|
|
1019
|
+
}
|
|
1020
|
+
|
|
1021
|
+
const resolvedAgentId = normalizeText(agentId, null);
|
|
1022
|
+
if (!resolvedAgentId) {
|
|
1023
|
+
throw new Error('claworld product-shell join helper requires agentId');
|
|
1024
|
+
}
|
|
1025
|
+
|
|
1026
|
+
const resolvedRuntimeConfig = runtimeConfig || resolveClaworldRuntimeConfig(cfg, accountId);
|
|
1027
|
+
const baseUrl = normalizeRelayHttpBaseUrl(resolvedRuntimeConfig.serverUrl);
|
|
1028
|
+
const requestBody = buildJoinRequestBody({
|
|
1029
|
+
agentId: resolvedAgentId,
|
|
1030
|
+
profile,
|
|
1031
|
+
profileSnapshot,
|
|
1032
|
+
});
|
|
1033
|
+
if (maxFieldsPerStep != null) {
|
|
1034
|
+
requestBody.maxFieldsPerStep = maxFieldsPerStep;
|
|
1035
|
+
}
|
|
1036
|
+
const joinResult = await fetchJson(fetchImpl, `${baseUrl}/v1/worlds/${encodeURIComponent(resolvedWorldId)}/join`, {
|
|
1037
|
+
method: 'POST',
|
|
1038
|
+
headers: {
|
|
1039
|
+
accept: 'application/json',
|
|
1040
|
+
'content-type': 'application/json',
|
|
1041
|
+
...(resolvedRuntimeConfig.apiKey ? { 'x-api-key': resolvedRuntimeConfig.apiKey } : {}),
|
|
1042
|
+
...(resolvedRuntimeConfig.relay?.credentialToken ? { 'x-relay-token': resolvedRuntimeConfig.relay.credentialToken } : {}),
|
|
1043
|
+
},
|
|
1044
|
+
body: JSON.stringify(requestBody),
|
|
1045
|
+
});
|
|
1046
|
+
|
|
1047
|
+
if (!joinResult.ok) {
|
|
1048
|
+
logger.error?.('[claworld:product-shell] world join failed', {
|
|
1049
|
+
status: joinResult.status,
|
|
1050
|
+
worldId: resolvedWorldId,
|
|
1051
|
+
agentId: resolvedAgentId,
|
|
1052
|
+
accountId: resolvedRuntimeConfig.accountId || accountId || null,
|
|
1053
|
+
body: joinResult.body,
|
|
1054
|
+
});
|
|
1055
|
+
throw new Error(`claworld product-shell world join failed: ${joinResult.status}`);
|
|
1056
|
+
}
|
|
1057
|
+
|
|
1058
|
+
return normalizeWorldJoinResponse(joinResult.body, {
|
|
1059
|
+
worldId: resolvedWorldId,
|
|
1060
|
+
agentId: resolvedAgentId,
|
|
1061
|
+
});
|
|
1062
|
+
}
|
|
1063
|
+
|
|
1064
|
+
export async function submitWorldJoin({
|
|
1065
|
+
cfg = {},
|
|
1066
|
+
accountId = null,
|
|
1067
|
+
runtimeConfig = null,
|
|
1068
|
+
worldId = null,
|
|
1069
|
+
agentId = null,
|
|
1070
|
+
profile = {},
|
|
1071
|
+
profileSnapshot = null,
|
|
1072
|
+
profileUpdate = null,
|
|
1073
|
+
profilePatch = null,
|
|
1074
|
+
worldDetail = null,
|
|
1075
|
+
maxFieldsPerStep = 1,
|
|
1076
|
+
fetchImpl,
|
|
1077
|
+
logger = console,
|
|
1078
|
+
} = {}) {
|
|
1079
|
+
if (typeof fetchImpl !== 'function') {
|
|
1080
|
+
throw new Error('fetch is unavailable for claworld product-shell join helper');
|
|
1081
|
+
}
|
|
1082
|
+
|
|
1083
|
+
const resolvedWorldId = normalizeText(worldId, null);
|
|
1084
|
+
if (!resolvedWorldId) {
|
|
1085
|
+
throw new Error('claworld product-shell join helper requires worldId');
|
|
1086
|
+
}
|
|
1087
|
+
|
|
1088
|
+
const resolvedAgentId = normalizeText(agentId, null);
|
|
1089
|
+
if (!resolvedAgentId) {
|
|
1090
|
+
throw new Error('claworld product-shell join helper requires agentId');
|
|
1091
|
+
}
|
|
1092
|
+
|
|
1093
|
+
const resolvedRuntimeConfig = runtimeConfig || resolveClaworldRuntimeConfig(cfg, accountId);
|
|
1094
|
+
const baseUrl = normalizeRelayHttpBaseUrl(resolvedRuntimeConfig.serverUrl);
|
|
1095
|
+
const joinProfile = resolveJoinSubmissionProfile({
|
|
1096
|
+
profile,
|
|
1097
|
+
profileSnapshot,
|
|
1098
|
+
profileUpdate,
|
|
1099
|
+
profilePatch,
|
|
1100
|
+
});
|
|
1101
|
+
const requestBody = buildJoinRequestBody({
|
|
1102
|
+
agentId: resolvedAgentId,
|
|
1103
|
+
profile: joinProfile.mergedProfile,
|
|
1104
|
+
profileSnapshot: joinProfile.preferProfileSnapshot ? joinProfile.mergedProfile : null,
|
|
1105
|
+
});
|
|
1106
|
+
if (maxFieldsPerStep != null) {
|
|
1107
|
+
requestBody.maxFieldsPerStep = maxFieldsPerStep;
|
|
1108
|
+
}
|
|
1109
|
+
const joinResult = await fetchJson(fetchImpl, `${baseUrl}/v1/worlds/${encodeURIComponent(resolvedWorldId)}/join`, {
|
|
1110
|
+
method: 'POST',
|
|
1111
|
+
headers: {
|
|
1112
|
+
accept: 'application/json',
|
|
1113
|
+
'content-type': 'application/json',
|
|
1114
|
+
...(resolvedRuntimeConfig.apiKey ? { 'x-api-key': resolvedRuntimeConfig.apiKey } : {}),
|
|
1115
|
+
...(resolvedRuntimeConfig.relay?.credentialToken ? { 'x-relay-token': resolvedRuntimeConfig.relay.credentialToken } : {}),
|
|
1116
|
+
},
|
|
1117
|
+
body: JSON.stringify(requestBody),
|
|
1118
|
+
});
|
|
1119
|
+
|
|
1120
|
+
if (!joinResult.ok && joinResult.status !== 422) {
|
|
1121
|
+
logger.error?.('[claworld:product-shell] world join failed', {
|
|
1122
|
+
status: joinResult.status,
|
|
1123
|
+
worldId: resolvedWorldId,
|
|
1124
|
+
agentId: resolvedAgentId,
|
|
1125
|
+
accountId: resolvedRuntimeConfig.accountId || accountId || null,
|
|
1126
|
+
body: joinResult.body,
|
|
1127
|
+
});
|
|
1128
|
+
throw new Error(`claworld product-shell world join failed: ${joinResult.status}`);
|
|
1129
|
+
}
|
|
1130
|
+
|
|
1131
|
+
if (joinResult.status === 422) {
|
|
1132
|
+
const detail = worldDetail
|
|
1133
|
+
? normalizeWorldDetail(worldDetail)
|
|
1134
|
+
: await fetchWorldDetail({
|
|
1135
|
+
cfg,
|
|
1136
|
+
accountId,
|
|
1137
|
+
runtimeConfig: resolvedRuntimeConfig,
|
|
1138
|
+
worldId: resolvedWorldId,
|
|
1139
|
+
fetchImpl,
|
|
1140
|
+
logger,
|
|
1141
|
+
});
|
|
1142
|
+
const joinCheck = normalizeJoinCheckResponse(joinResult.body?.joinCheck || joinResult.body, {
|
|
1143
|
+
worldId: resolvedWorldId,
|
|
1144
|
+
profile: joinProfile.mergedProfile,
|
|
1145
|
+
});
|
|
1146
|
+
const profileCollectionFlow = joinCheck.profileCollectionFlow && typeof joinCheck.profileCollectionFlow === 'object'
|
|
1147
|
+
? {
|
|
1148
|
+
...joinCheck.profileCollectionFlow,
|
|
1149
|
+
worldDetail: detail,
|
|
1150
|
+
joinCheck,
|
|
1151
|
+
profile: joinCheck.normalizedProfile,
|
|
1152
|
+
appliedProfileUpdate: joinProfile.appliedProfileUpdate,
|
|
1153
|
+
}
|
|
1154
|
+
: {
|
|
1155
|
+
...buildBackendWorldProfileCollectionFlow({
|
|
1156
|
+
worldDetail: detail,
|
|
1157
|
+
joinCheck,
|
|
1158
|
+
profile: joinCheck.normalizedProfile,
|
|
1159
|
+
maxFieldsPerStep,
|
|
1160
|
+
}),
|
|
1161
|
+
worldDetail: detail,
|
|
1162
|
+
joinCheck,
|
|
1163
|
+
profile: joinCheck.normalizedProfile,
|
|
1164
|
+
appliedProfileUpdate: joinProfile.appliedProfileUpdate,
|
|
1165
|
+
};
|
|
1166
|
+
|
|
1167
|
+
return {
|
|
1168
|
+
status: 'needs_profile',
|
|
1169
|
+
worldId: resolvedWorldId,
|
|
1170
|
+
agentId: resolvedAgentId,
|
|
1171
|
+
membershipStatus: normalizeText(joinResult.body?.membershipStatus, 'inactive'),
|
|
1172
|
+
searchEnabled: false,
|
|
1173
|
+
missingRequiredFields: projectMissingRequiredFields(joinCheck.missingFields),
|
|
1174
|
+
nextMissingField: joinCheck.nextMissingField
|
|
1175
|
+
? projectMissingRequiredFields([joinCheck.nextMissingField])[0]
|
|
1176
|
+
: null,
|
|
1177
|
+
normalizedProfile: profileCollectionFlow.profile,
|
|
1178
|
+
nextAction: 'complete_required_profile_fields_then_retry_join',
|
|
1179
|
+
joinCheck,
|
|
1180
|
+
profileCollectionFlow,
|
|
1181
|
+
orchestration: profileCollectionFlow.orchestration || null,
|
|
1182
|
+
providedRequiredFieldIds: profileCollectionFlow.providedRequiredFieldIds,
|
|
1183
|
+
promptFields: profileCollectionFlow.promptFields,
|
|
1184
|
+
remainingRequiredFieldCount: profileCollectionFlow.remainingRequiredFieldCount,
|
|
1185
|
+
revalidationCheckpoint: profileCollectionFlow.revalidationCheckpoint,
|
|
1186
|
+
};
|
|
1187
|
+
}
|
|
1188
|
+
|
|
1189
|
+
const normalizedJoinResult = normalizeWorldJoinResponse(joinResult.body, {
|
|
1190
|
+
worldId: resolvedWorldId,
|
|
1191
|
+
agentId: resolvedAgentId,
|
|
1192
|
+
});
|
|
1193
|
+
|
|
1194
|
+
return {
|
|
1195
|
+
status: normalizedJoinResult.membershipStatus === 'active' ? 'activated' : 'accepted',
|
|
1196
|
+
worldId: normalizedJoinResult.worldId,
|
|
1197
|
+
agentId: normalizedJoinResult.agentId,
|
|
1198
|
+
membershipStatus: normalizedJoinResult.membershipStatus,
|
|
1199
|
+
searchEnabled: normalizedJoinResult.membershipStatus === 'active',
|
|
1200
|
+
membership: normalizedJoinResult.membership,
|
|
1201
|
+
nextStageSummary: normalizedJoinResult.nextStageSummary,
|
|
1202
|
+
orchestration: normalizedJoinResult.orchestration || null,
|
|
1203
|
+
};
|
|
1204
|
+
}
|
|
1205
|
+
|
|
1206
|
+
export async function fetchWorldSearch({
|
|
1207
|
+
cfg = {},
|
|
1208
|
+
accountId = null,
|
|
1209
|
+
runtimeConfig = null,
|
|
1210
|
+
worldId = null,
|
|
1211
|
+
agentId = null,
|
|
1212
|
+
query = {},
|
|
1213
|
+
limit = null,
|
|
1214
|
+
fetchImpl,
|
|
1215
|
+
logger = console,
|
|
1216
|
+
} = {}) {
|
|
1217
|
+
if (typeof fetchImpl !== 'function') {
|
|
1218
|
+
throw new Error('fetch is unavailable for claworld product-shell world search helper');
|
|
1219
|
+
}
|
|
1220
|
+
|
|
1221
|
+
const resolvedWorldId = normalizeText(worldId, null);
|
|
1222
|
+
if (!resolvedWorldId) {
|
|
1223
|
+
throw new Error('claworld product-shell world search helper requires worldId');
|
|
1224
|
+
}
|
|
1225
|
+
|
|
1226
|
+
const resolvedAgentId = normalizeText(agentId, null);
|
|
1227
|
+
if (!resolvedAgentId) {
|
|
1228
|
+
throw new Error('claworld product-shell world search helper requires agentId');
|
|
1229
|
+
}
|
|
1230
|
+
|
|
1231
|
+
const resolvedRuntimeConfig = runtimeConfig || resolveClaworldRuntimeConfig(cfg, accountId);
|
|
1232
|
+
const baseUrl = normalizeRelayHttpBaseUrl(resolvedRuntimeConfig.serverUrl);
|
|
1233
|
+
const normalizedQuery = normalizeProfile(query);
|
|
1234
|
+
const searchResult = await fetchJson(fetchImpl, `${baseUrl}/v1/worlds/${encodeURIComponent(resolvedWorldId)}/search`, {
|
|
1235
|
+
method: 'POST',
|
|
1236
|
+
headers: {
|
|
1237
|
+
accept: 'application/json',
|
|
1238
|
+
'content-type': 'application/json',
|
|
1239
|
+
...(resolvedRuntimeConfig.apiKey ? { 'x-api-key': resolvedRuntimeConfig.apiKey } : {}),
|
|
1240
|
+
...(resolvedRuntimeConfig.relay?.credentialToken ? { 'x-relay-token': resolvedRuntimeConfig.relay.credentialToken } : {}),
|
|
1241
|
+
},
|
|
1242
|
+
body: JSON.stringify({
|
|
1243
|
+
agentId: resolvedAgentId,
|
|
1244
|
+
query: normalizedQuery,
|
|
1245
|
+
...(limit != null ? { limit } : {}),
|
|
1246
|
+
}),
|
|
1247
|
+
});
|
|
1248
|
+
|
|
1249
|
+
if (!searchResult.ok) {
|
|
1250
|
+
logger.error?.('[claworld:product-shell] world search failed', {
|
|
1251
|
+
status: searchResult.status,
|
|
1252
|
+
worldId: resolvedWorldId,
|
|
1253
|
+
agentId: resolvedAgentId,
|
|
1254
|
+
accountId: resolvedRuntimeConfig.accountId || accountId || null,
|
|
1255
|
+
body: searchResult.body,
|
|
1256
|
+
});
|
|
1257
|
+
throw new Error(`claworld product-shell world search failed: ${searchResult.status}`);
|
|
1258
|
+
}
|
|
1259
|
+
|
|
1260
|
+
return normalizeWorldSearchResponse(searchResult.body, {
|
|
1261
|
+
worldId: resolvedWorldId,
|
|
1262
|
+
agentId: resolvedAgentId,
|
|
1263
|
+
});
|
|
1264
|
+
}
|
|
1265
|
+
|
|
1266
|
+
export async function submitWorldSearch({
|
|
1267
|
+
cfg = {},
|
|
1268
|
+
accountId = null,
|
|
1269
|
+
runtimeConfig = null,
|
|
1270
|
+
worldId = null,
|
|
1271
|
+
agentId = null,
|
|
1272
|
+
query = {},
|
|
1273
|
+
limit = null,
|
|
1274
|
+
fetchImpl,
|
|
1275
|
+
logger = console,
|
|
1276
|
+
} = {}) {
|
|
1277
|
+
if (typeof fetchImpl !== 'function') {
|
|
1278
|
+
throw new Error('fetch is unavailable for claworld product-shell world search helper');
|
|
1279
|
+
}
|
|
1280
|
+
|
|
1281
|
+
const resolvedWorldId = normalizeText(worldId, null);
|
|
1282
|
+
if (!resolvedWorldId) {
|
|
1283
|
+
throw new Error('claworld product-shell world search helper requires worldId');
|
|
1284
|
+
}
|
|
1285
|
+
|
|
1286
|
+
const resolvedAgentId = normalizeText(agentId, null);
|
|
1287
|
+
if (!resolvedAgentId) {
|
|
1288
|
+
throw new Error('claworld product-shell world search helper requires agentId');
|
|
1289
|
+
}
|
|
1290
|
+
|
|
1291
|
+
const resolvedRuntimeConfig = runtimeConfig || resolveClaworldRuntimeConfig(cfg, accountId);
|
|
1292
|
+
const baseUrl = normalizeRelayHttpBaseUrl(resolvedRuntimeConfig.serverUrl);
|
|
1293
|
+
const normalizedQuery = normalizeProfile(query);
|
|
1294
|
+
const searchResult = await fetchJson(fetchImpl, `${baseUrl}/v1/worlds/${encodeURIComponent(resolvedWorldId)}/search`, {
|
|
1295
|
+
method: 'POST',
|
|
1296
|
+
headers: {
|
|
1297
|
+
accept: 'application/json',
|
|
1298
|
+
'content-type': 'application/json',
|
|
1299
|
+
...(resolvedRuntimeConfig.apiKey ? { 'x-api-key': resolvedRuntimeConfig.apiKey } : {}),
|
|
1300
|
+
...(resolvedRuntimeConfig.relay?.credentialToken ? { 'x-relay-token': resolvedRuntimeConfig.relay.credentialToken } : {}),
|
|
1301
|
+
},
|
|
1302
|
+
body: JSON.stringify({
|
|
1303
|
+
agentId: resolvedAgentId,
|
|
1304
|
+
query: normalizedQuery,
|
|
1305
|
+
...(limit != null ? { limit } : {}),
|
|
1306
|
+
}),
|
|
1307
|
+
});
|
|
1308
|
+
|
|
1309
|
+
if (!searchResult.ok && searchResult.status !== 409) {
|
|
1310
|
+
logger.error?.('[claworld:product-shell] world search failed', {
|
|
1311
|
+
status: searchResult.status,
|
|
1312
|
+
worldId: resolvedWorldId,
|
|
1313
|
+
agentId: resolvedAgentId,
|
|
1314
|
+
accountId: resolvedRuntimeConfig.accountId || accountId || null,
|
|
1315
|
+
body: searchResult.body,
|
|
1316
|
+
});
|
|
1317
|
+
throw new Error(`claworld product-shell world search failed: ${searchResult.status}`);
|
|
1318
|
+
}
|
|
1319
|
+
|
|
1320
|
+
if (searchResult.status === 409) {
|
|
1321
|
+
return {
|
|
1322
|
+
status: 'not_joined',
|
|
1323
|
+
worldId: resolvedWorldId,
|
|
1324
|
+
agentId: resolvedAgentId,
|
|
1325
|
+
searchEnabled: false,
|
|
1326
|
+
results: [],
|
|
1327
|
+
query: normalizedQuery,
|
|
1328
|
+
limit: normalizeInteger(limit, 10),
|
|
1329
|
+
message: 'Join the world successfully before using world search.',
|
|
1330
|
+
};
|
|
1331
|
+
}
|
|
1332
|
+
|
|
1333
|
+
const normalizedSearch = normalizeWorldSearchResponse(searchResult.body, {
|
|
1334
|
+
worldId: resolvedWorldId,
|
|
1335
|
+
agentId: resolvedAgentId,
|
|
1336
|
+
});
|
|
1337
|
+
|
|
1338
|
+
return {
|
|
1339
|
+
...normalizedSearch,
|
|
1340
|
+
status: normalizedSearch.items.length > 0 ? 'ready' : 'empty',
|
|
1341
|
+
searchEnabled: true,
|
|
1342
|
+
results: normalizedSearch.items,
|
|
1343
|
+
query: normalizedSearch.searchInput,
|
|
1344
|
+
};
|
|
1345
|
+
}
|
|
1346
|
+
|
|
1347
|
+
export async function broadcastWorld({
|
|
1348
|
+
cfg = {},
|
|
1349
|
+
accountId = null,
|
|
1350
|
+
runtimeConfig = null,
|
|
1351
|
+
worldId = null,
|
|
1352
|
+
agentId = null,
|
|
1353
|
+
message = null,
|
|
1354
|
+
payload = {},
|
|
1355
|
+
audience = null,
|
|
1356
|
+
excludeSelf = null,
|
|
1357
|
+
fetchImpl,
|
|
1358
|
+
logger = console,
|
|
1359
|
+
} = {}) {
|
|
1360
|
+
if (typeof fetchImpl !== 'function') {
|
|
1361
|
+
throw new Error('fetch is unavailable for claworld product-shell world broadcast helper');
|
|
1362
|
+
}
|
|
1363
|
+
|
|
1364
|
+
const resolvedWorldId = normalizeText(worldId, null);
|
|
1365
|
+
if (!resolvedWorldId) {
|
|
1366
|
+
throw new Error('claworld product-shell world broadcast helper requires worldId');
|
|
1367
|
+
}
|
|
1368
|
+
|
|
1369
|
+
const resolvedAgentId = normalizeText(agentId, null);
|
|
1370
|
+
if (!resolvedAgentId) {
|
|
1371
|
+
throw new Error('claworld product-shell world broadcast helper requires agentId');
|
|
1372
|
+
}
|
|
1373
|
+
|
|
1374
|
+
const resolvedRuntimeConfig = runtimeConfig || resolveClaworldRuntimeConfig(cfg, accountId);
|
|
1375
|
+
const baseUrl = normalizeRelayHttpBaseUrl(resolvedRuntimeConfig.serverUrl);
|
|
1376
|
+
const requestBody = {
|
|
1377
|
+
agentId: resolvedAgentId,
|
|
1378
|
+
payload: normalizeBroadcastRequestPayload(payload, { message }),
|
|
1379
|
+
...(audience ? { audience } : {}),
|
|
1380
|
+
...(excludeSelf == null ? {} : { excludeSelf: excludeSelf === true }),
|
|
1381
|
+
};
|
|
1382
|
+
const broadcastResult = await fetchJson(
|
|
1383
|
+
fetchImpl,
|
|
1384
|
+
`${baseUrl}/v1/worlds/${encodeURIComponent(resolvedWorldId)}/broadcast`,
|
|
1385
|
+
{
|
|
1386
|
+
method: 'POST',
|
|
1387
|
+
headers: {
|
|
1388
|
+
accept: 'application/json',
|
|
1389
|
+
'content-type': 'application/json',
|
|
1390
|
+
...(resolvedRuntimeConfig.apiKey ? { 'x-api-key': resolvedRuntimeConfig.apiKey } : {}),
|
|
1391
|
+
...(resolvedRuntimeConfig.relay?.credentialToken ? { 'x-relay-token': resolvedRuntimeConfig.relay.credentialToken } : {}),
|
|
1392
|
+
},
|
|
1393
|
+
body: JSON.stringify(requestBody),
|
|
1394
|
+
},
|
|
1395
|
+
);
|
|
1396
|
+
|
|
1397
|
+
if (!broadcastResult.ok) {
|
|
1398
|
+
logger.error?.('[claworld:product-shell] world broadcast failed', {
|
|
1399
|
+
status: broadcastResult.status,
|
|
1400
|
+
worldId: resolvedWorldId,
|
|
1401
|
+
agentId: resolvedAgentId,
|
|
1402
|
+
accountId: resolvedRuntimeConfig.accountId || accountId || null,
|
|
1403
|
+
body: broadcastResult.body,
|
|
1404
|
+
});
|
|
1405
|
+
throw createProductShellHttpError('world_broadcast', broadcastResult, {
|
|
1406
|
+
accountId: resolvedRuntimeConfig.accountId || accountId || null,
|
|
1407
|
+
worldId: resolvedWorldId,
|
|
1408
|
+
});
|
|
1409
|
+
}
|
|
1410
|
+
|
|
1411
|
+
return normalizeBroadcastResult(broadcastResult.body);
|
|
1412
|
+
}
|
|
1413
|
+
|
|
1414
|
+
export async function fetchWorldCandidateFeed({
|
|
1415
|
+
cfg = {},
|
|
1416
|
+
accountId = null,
|
|
1417
|
+
runtimeConfig = null,
|
|
1418
|
+
worldId = null,
|
|
1419
|
+
agentId = null,
|
|
1420
|
+
limit = null,
|
|
1421
|
+
fetchImpl,
|
|
1422
|
+
logger = console,
|
|
1423
|
+
} = {}) {
|
|
1424
|
+
if (typeof fetchImpl !== 'function') {
|
|
1425
|
+
throw new Error('fetch is unavailable for claworld product-shell candidate feed helper');
|
|
1426
|
+
}
|
|
1427
|
+
|
|
1428
|
+
const resolvedWorldId = normalizeText(worldId, null);
|
|
1429
|
+
if (!resolvedWorldId) {
|
|
1430
|
+
throw new Error('claworld product-shell candidate feed helper requires worldId');
|
|
1431
|
+
}
|
|
1432
|
+
|
|
1433
|
+
const resolvedAgentId = normalizeText(agentId, null);
|
|
1434
|
+
if (!resolvedAgentId) {
|
|
1435
|
+
throw new Error('claworld product-shell candidate feed helper requires agentId');
|
|
1436
|
+
}
|
|
1437
|
+
|
|
1438
|
+
const resolvedRuntimeConfig = runtimeConfig || resolveClaworldRuntimeConfig(cfg, accountId);
|
|
1439
|
+
const baseUrl = normalizeRelayHttpBaseUrl(resolvedRuntimeConfig.serverUrl);
|
|
1440
|
+
const requestUrl = new URL(`${baseUrl}/v1/worlds/${encodeURIComponent(resolvedWorldId)}/candidates`);
|
|
1441
|
+
requestUrl.searchParams.set('agentId', resolvedAgentId);
|
|
1442
|
+
const normalizedLimit = normalizeInteger(limit, 0);
|
|
1443
|
+
if (normalizedLimit > 0) {
|
|
1444
|
+
requestUrl.searchParams.set('limit', String(normalizedLimit));
|
|
1445
|
+
}
|
|
1446
|
+
const candidateFeed = await fetchJson(fetchImpl, requestUrl.toString(), {
|
|
1447
|
+
headers: {
|
|
1448
|
+
accept: 'application/json',
|
|
1449
|
+
...(resolvedRuntimeConfig.apiKey ? { 'x-api-key': resolvedRuntimeConfig.apiKey } : {}),
|
|
1450
|
+
...(resolvedRuntimeConfig.relay?.credentialToken ? { 'x-relay-token': resolvedRuntimeConfig.relay.credentialToken } : {}),
|
|
1451
|
+
},
|
|
1452
|
+
});
|
|
1453
|
+
|
|
1454
|
+
if (!candidateFeed.ok) {
|
|
1455
|
+
logger.error?.('[claworld:product-shell] candidate feed fetch failed', {
|
|
1456
|
+
status: candidateFeed.status,
|
|
1457
|
+
worldId: resolvedWorldId,
|
|
1458
|
+
agentId: resolvedAgentId,
|
|
1459
|
+
accountId: resolvedRuntimeConfig.accountId || accountId || null,
|
|
1460
|
+
body: candidateFeed.body,
|
|
1461
|
+
});
|
|
1462
|
+
throw new Error(`claworld product-shell candidate feed failed: ${candidateFeed.status}`);
|
|
1463
|
+
}
|
|
1464
|
+
|
|
1465
|
+
return normalizeCandidateFeedResponse(candidateFeed.body, {
|
|
1466
|
+
worldId: resolvedWorldId,
|
|
1467
|
+
agentId: resolvedAgentId,
|
|
1468
|
+
});
|
|
1469
|
+
}
|
|
1470
|
+
|
|
1471
|
+
function buildFieldStepPrompt(field = {}, index = 0, total = 1) {
|
|
1472
|
+
const examples = Array.isArray(field.examples) && field.examples.length > 0
|
|
1473
|
+
? ` Example: ${field.examples.map((example) => quoteExample(example)).join(' or ')}.`
|
|
1474
|
+
: '';
|
|
1475
|
+
const description = sentenceCase(
|
|
1476
|
+
field.description || `Provide ${field.label} so the world can evaluate the profile`,
|
|
1477
|
+
'Provide this field so the world can evaluate the profile.',
|
|
1478
|
+
);
|
|
1479
|
+
|
|
1480
|
+
return `Step ${index + 1} of ${total}: ${field.label}. ${description}${examples}`;
|
|
1481
|
+
}
|
|
1482
|
+
|
|
1483
|
+
export function buildRequiredFieldExplanation(worldDetail = {}) {
|
|
1484
|
+
if (worldDetail?.requiredFieldExplanation && typeof worldDetail.requiredFieldExplanation === 'object') {
|
|
1485
|
+
return worldDetail.requiredFieldExplanation;
|
|
1486
|
+
}
|
|
1487
|
+
// Compatibility fallback for older world-detail payloads that do not yet
|
|
1488
|
+
// carry backend-authored required-field explanation.
|
|
1489
|
+
return buildBackendRequiredFieldExplanation(worldDetail);
|
|
1490
|
+
}
|
|
1491
|
+
|
|
1492
|
+
export async function resolveWorldProfileCollectionFlow({
|
|
1493
|
+
cfg = {},
|
|
1494
|
+
accountId = null,
|
|
1495
|
+
runtimeConfig = null,
|
|
1496
|
+
worldId = null,
|
|
1497
|
+
worldDetail = null,
|
|
1498
|
+
profile = {},
|
|
1499
|
+
profileUpdate = null,
|
|
1500
|
+
profilePatch = null,
|
|
1501
|
+
maxFieldsPerStep = 1,
|
|
1502
|
+
fetchImpl,
|
|
1503
|
+
logger = console,
|
|
1504
|
+
} = {}) {
|
|
1505
|
+
const appliedProfileUpdate = normalizeProfile(profileUpdate || profilePatch || {});
|
|
1506
|
+
const mergedProfile = mergeProfileState(profile, appliedProfileUpdate);
|
|
1507
|
+
const detail = worldDetail
|
|
1508
|
+
? normalizeWorldDetail(worldDetail)
|
|
1509
|
+
: await fetchWorldDetail({
|
|
1510
|
+
cfg,
|
|
1511
|
+
accountId,
|
|
1512
|
+
runtimeConfig,
|
|
1513
|
+
worldId,
|
|
1514
|
+
fetchImpl,
|
|
1515
|
+
logger,
|
|
1516
|
+
});
|
|
1517
|
+
const resolvedWorldId = normalizeText(worldId || detail.worldId, detail.worldId);
|
|
1518
|
+
if (!resolvedWorldId) {
|
|
1519
|
+
throw new Error('claworld product-shell profile collection flow requires worldId');
|
|
1520
|
+
}
|
|
1521
|
+
|
|
1522
|
+
const joinCheck = await fetchWorldJoinCheck({
|
|
1523
|
+
cfg,
|
|
1524
|
+
accountId,
|
|
1525
|
+
runtimeConfig,
|
|
1526
|
+
worldId: resolvedWorldId,
|
|
1527
|
+
profile: mergedProfile,
|
|
1528
|
+
maxFieldsPerStep,
|
|
1529
|
+
fetchImpl,
|
|
1530
|
+
logger,
|
|
1531
|
+
});
|
|
1532
|
+
const backendFlow = joinCheck.profileCollectionFlow && typeof joinCheck.profileCollectionFlow === 'object'
|
|
1533
|
+
? joinCheck.profileCollectionFlow
|
|
1534
|
+
: buildBackendWorldProfileCollectionFlow({
|
|
1535
|
+
worldDetail: detail,
|
|
1536
|
+
joinCheck,
|
|
1537
|
+
profile: joinCheck.normalizedProfile,
|
|
1538
|
+
maxFieldsPerStep,
|
|
1539
|
+
});
|
|
1540
|
+
|
|
1541
|
+
return {
|
|
1542
|
+
...backendFlow,
|
|
1543
|
+
appliedProfileUpdate,
|
|
1544
|
+
worldDetail: detail,
|
|
1545
|
+
joinCheck,
|
|
1546
|
+
worldId: resolvedWorldId,
|
|
1547
|
+
displayName: detail.displayName,
|
|
1548
|
+
profile: joinCheck.normalizedProfile,
|
|
1549
|
+
accepted: joinCheck.accepted,
|
|
1550
|
+
status: joinCheck.status,
|
|
1551
|
+
};
|
|
1552
|
+
}
|
|
1553
|
+
|
|
1554
|
+
export async function resolveWorldSelectionFlow({
|
|
1555
|
+
cfg = {},
|
|
1556
|
+
accountId = null,
|
|
1557
|
+
runtimeConfig = null,
|
|
1558
|
+
worldDirectory = null,
|
|
1559
|
+
selection = null,
|
|
1560
|
+
profile = {},
|
|
1561
|
+
maxFieldsPerStep = 1,
|
|
1562
|
+
fetchImpl,
|
|
1563
|
+
logger = console,
|
|
1564
|
+
} = {}) {
|
|
1565
|
+
const directory = worldDirectory && Array.isArray(worldDirectory.items)
|
|
1566
|
+
? buildPostSetupWorldDirectory(worldDirectory, { accountId })
|
|
1567
|
+
: await (async () => {
|
|
1568
|
+
if (typeof fetchImpl !== 'function') {
|
|
1569
|
+
throw new Error('fetch is unavailable for claworld product-shell world flow');
|
|
1570
|
+
}
|
|
1571
|
+
const resolvedRuntimeConfig = runtimeConfig || resolveClaworldRuntimeConfig(cfg, accountId);
|
|
1572
|
+
const baseUrl = normalizeRelayHttpBaseUrl(resolvedRuntimeConfig.serverUrl);
|
|
1573
|
+
const worlds = await fetchJson(fetchImpl, `${baseUrl}/v1/worlds`, {
|
|
1574
|
+
headers: {
|
|
1575
|
+
accept: 'application/json',
|
|
1576
|
+
...(resolvedRuntimeConfig.apiKey ? { 'x-api-key': resolvedRuntimeConfig.apiKey } : {}),
|
|
1577
|
+
...(resolvedRuntimeConfig.relay?.credentialToken ? { 'x-relay-token': resolvedRuntimeConfig.relay.credentialToken } : {}),
|
|
1578
|
+
},
|
|
1579
|
+
});
|
|
1580
|
+
|
|
1581
|
+
if (!worlds.ok) {
|
|
1582
|
+
logger.error?.('[claworld:product-shell] world directory fetch failed during selection flow', {
|
|
1583
|
+
status: worlds.status,
|
|
1584
|
+
accountId: resolvedRuntimeConfig.accountId || accountId || null,
|
|
1585
|
+
body: worlds.body,
|
|
1586
|
+
});
|
|
1587
|
+
throw new Error(`claworld product-shell world fetch failed: ${worlds.status}`);
|
|
1588
|
+
}
|
|
1589
|
+
|
|
1590
|
+
return buildPostSetupWorldDirectory(worlds.body, {
|
|
1591
|
+
accountId: resolvedRuntimeConfig.accountId || accountId || null,
|
|
1592
|
+
});
|
|
1593
|
+
})();
|
|
1594
|
+
|
|
1595
|
+
const resolvedSelection = resolveWorldSelection(directory, selection);
|
|
1596
|
+
if (resolvedSelection.status !== 'selected') {
|
|
1597
|
+
return {
|
|
1598
|
+
...resolvedSelection,
|
|
1599
|
+
worldDirectory: directory,
|
|
1600
|
+
};
|
|
1601
|
+
}
|
|
1602
|
+
|
|
1603
|
+
const worldDetail = await fetchWorldDetail({
|
|
1604
|
+
cfg,
|
|
1605
|
+
accountId,
|
|
1606
|
+
runtimeConfig,
|
|
1607
|
+
worldId: resolvedSelection.selectedWorld.worldId,
|
|
1608
|
+
fetchImpl,
|
|
1609
|
+
logger,
|
|
1610
|
+
});
|
|
1611
|
+
const requiredFieldExplanation = buildRequiredFieldExplanation(worldDetail);
|
|
1612
|
+
const profileCollectionFlow = await resolveWorldProfileCollectionFlow({
|
|
1613
|
+
cfg,
|
|
1614
|
+
accountId,
|
|
1615
|
+
runtimeConfig,
|
|
1616
|
+
worldId: resolvedSelection.selectedWorld.worldId,
|
|
1617
|
+
worldDetail,
|
|
1618
|
+
profile,
|
|
1619
|
+
maxFieldsPerStep,
|
|
1620
|
+
fetchImpl,
|
|
1621
|
+
logger,
|
|
1622
|
+
});
|
|
1623
|
+
|
|
1624
|
+
return {
|
|
1625
|
+
status: 'selected',
|
|
1626
|
+
source: 'product_shell',
|
|
1627
|
+
worldDirectory: directory,
|
|
1628
|
+
selection: resolvedSelection.selection,
|
|
1629
|
+
selectedWorld: resolvedSelection.selectedWorld,
|
|
1630
|
+
worldDetail,
|
|
1631
|
+
requiredFieldExplanation,
|
|
1632
|
+
profileCollectionFlow,
|
|
1633
|
+
orchestration: resolvedSelection.orchestration || null,
|
|
1634
|
+
};
|
|
1635
|
+
}
|
|
1636
|
+
|
|
1637
|
+
export async function resolveWorldJoinFlow({
|
|
1638
|
+
cfg = {},
|
|
1639
|
+
accountId = null,
|
|
1640
|
+
runtimeConfig = null,
|
|
1641
|
+
worldId = null,
|
|
1642
|
+
worldDetail = null,
|
|
1643
|
+
agentId = null,
|
|
1644
|
+
profile = {},
|
|
1645
|
+
profileSnapshot = null,
|
|
1646
|
+
limit = null,
|
|
1647
|
+
fetchImpl,
|
|
1648
|
+
logger = console,
|
|
1649
|
+
} = {}) {
|
|
1650
|
+
const detail = worldDetail
|
|
1651
|
+
? normalizeWorldDetail(worldDetail)
|
|
1652
|
+
: await fetchWorldDetail({
|
|
1653
|
+
cfg,
|
|
1654
|
+
accountId,
|
|
1655
|
+
runtimeConfig,
|
|
1656
|
+
worldId,
|
|
1657
|
+
fetchImpl,
|
|
1658
|
+
logger,
|
|
1659
|
+
});
|
|
1660
|
+
const resolvedWorldId = normalizeText(worldId || detail.worldId, detail.worldId);
|
|
1661
|
+
if (!resolvedWorldId) {
|
|
1662
|
+
throw new Error('claworld product-shell join flow requires worldId');
|
|
1663
|
+
}
|
|
1664
|
+
|
|
1665
|
+
const joinResult = await joinWorld({
|
|
1666
|
+
cfg,
|
|
1667
|
+
accountId,
|
|
1668
|
+
runtimeConfig,
|
|
1669
|
+
worldId: resolvedWorldId,
|
|
1670
|
+
agentId,
|
|
1671
|
+
profile,
|
|
1672
|
+
profileSnapshot,
|
|
1673
|
+
fetchImpl,
|
|
1674
|
+
logger,
|
|
1675
|
+
});
|
|
1676
|
+
const candidateFeed = await fetchWorldCandidateFeed({
|
|
1677
|
+
cfg,
|
|
1678
|
+
accountId,
|
|
1679
|
+
runtimeConfig,
|
|
1680
|
+
worldId: resolvedWorldId,
|
|
1681
|
+
agentId: joinResult.agentId || agentId,
|
|
1682
|
+
limit,
|
|
1683
|
+
fetchImpl,
|
|
1684
|
+
logger,
|
|
1685
|
+
});
|
|
1686
|
+
const candidateDelivery = buildCandidateDeliverySummary(candidateFeed, {
|
|
1687
|
+
worldDetail: detail,
|
|
1688
|
+
limit,
|
|
1689
|
+
});
|
|
1690
|
+
const joinOrchestration = joinResult.orchestration && typeof joinResult.orchestration === 'object'
|
|
1691
|
+
? joinResult.orchestration
|
|
1692
|
+
: buildBackendWorldJoinOutcomeOrchestration({
|
|
1693
|
+
worldDetail: detail,
|
|
1694
|
+
joinResult,
|
|
1695
|
+
});
|
|
1696
|
+
|
|
1697
|
+
return {
|
|
1698
|
+
status: candidateDelivery.status,
|
|
1699
|
+
source: 'product_shell',
|
|
1700
|
+
worldId: resolvedWorldId,
|
|
1701
|
+
displayName: detail.displayName,
|
|
1702
|
+
agentId: joinResult.agentId || normalizeText(agentId, null),
|
|
1703
|
+
worldDetail: detail,
|
|
1704
|
+
membershipStatus: joinResult.membershipStatus,
|
|
1705
|
+
membership: joinResult.membership,
|
|
1706
|
+
nextStageSummary: joinResult.nextStageSummary,
|
|
1707
|
+
joinResult,
|
|
1708
|
+
candidateFeed,
|
|
1709
|
+
candidateDelivery,
|
|
1710
|
+
orchestration: joinOrchestration,
|
|
1711
|
+
};
|
|
1712
|
+
}
|