@xfxstudio/claworld 0.1.1 → 0.1.3
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/openclaw.plugin.json +1 -1
- package/package.json +3 -3
- package/src/lib/runtime-guidance.js +1 -2
- package/src/openclaw/installer/constants.js +1 -1
- package/src/openclaw/installer/core.js +114 -10
- package/src/openclaw/installer/doctor.js +76 -5
- package/src/openclaw/plugin/claworld-channel-plugin.js +4 -25
- package/src/openclaw/plugin/register.js +5 -5
- package/src/openclaw/plugin/relay-client.js +1 -1
- package/src/openclaw/runtime/product-shell-helper.js +107 -54
- package/src/openclaw/runtime/tool-contracts.js +215 -23
- package/src/openclaw/runtime/tool-inventory.js +1 -1
- package/src/product-shell/catalog/default-world-catalog.js +12 -6
- package/src/product-shell/contracts/candidate-feed.js +23 -2
- package/src/product-shell/contracts/world-manifest.js +3 -3
- package/src/product-shell/contracts/world-orchestration.js +47 -24
- package/src/product-shell/membership/membership-service.js +14 -7
- package/src/product-shell/search/search-service.js +1 -1
- package/src/product-shell/worlds/world-admin-service.js +3 -2
- package/src/product-shell/worlds/world-routes.js +23 -1
|
@@ -72,7 +72,8 @@ export const DEFAULT_WORLD_MANIFESTS = Object.freeze([
|
|
|
72
72
|
searchSchema: {
|
|
73
73
|
mode: 'profile_overlap_search',
|
|
74
74
|
inputFieldIds: ['intent', 'location', 'interests'],
|
|
75
|
-
summary:
|
|
75
|
+
summary:
|
|
76
|
+
'Compatibility-only manual search over active online members by intent, location, and shared interests. Candidate feed review is the canonical path before request_chat.',
|
|
76
77
|
hints: [
|
|
77
78
|
'Search defaults to the viewer membership profile when no explicit query is provided.',
|
|
78
79
|
'Only online members with an active world membership are returned.',
|
|
@@ -81,7 +82,8 @@ export const DEFAULT_WORLD_MANIFESTS = Object.freeze([
|
|
|
81
82
|
matching: {
|
|
82
83
|
mode: 'scored_push',
|
|
83
84
|
cadence: 'periodic',
|
|
84
|
-
strategySummary:
|
|
85
|
+
strategySummary:
|
|
86
|
+
'Score active memberships by intent, location, and interests, deliver candidate summaries first, and route live contact through request_chat after review.',
|
|
85
87
|
candidateSources: ['active_memberships'],
|
|
86
88
|
},
|
|
87
89
|
sessionTemplate: {
|
|
@@ -170,12 +172,14 @@ export const DEFAULT_WORLD_MANIFESTS = Object.freeze([
|
|
|
170
172
|
searchSchema: {
|
|
171
173
|
mode: 'capability_overlap_search',
|
|
172
174
|
inputFieldIds: ['capabilities', 'budgetBand'],
|
|
173
|
-
summary:
|
|
175
|
+
summary:
|
|
176
|
+
'Compatibility-only manual search over active online members by capability overlap and optional budget fit. Candidate feed review is the canonical path before request_chat.',
|
|
174
177
|
},
|
|
175
178
|
matching: {
|
|
176
179
|
mode: 'intent_filter',
|
|
177
180
|
cadence: 'on_demand',
|
|
178
|
-
strategySummary:
|
|
181
|
+
strategySummary:
|
|
182
|
+
'Filter by capability overlap, deliver candidate summaries first, and let members request_chat before negotiating fit in a short session.',
|
|
179
183
|
candidateSources: ['world_members'],
|
|
180
184
|
},
|
|
181
185
|
sessionTemplate: {
|
|
@@ -262,12 +266,14 @@ export const DEFAULT_WORLD_MANIFESTS = Object.freeze([
|
|
|
262
266
|
searchSchema: {
|
|
263
267
|
mode: 'profile_overlap_search',
|
|
264
268
|
inputFieldIds: ['targetRole', 'location', 'workMode'],
|
|
265
|
-
summary:
|
|
269
|
+
summary:
|
|
270
|
+
'Compatibility-only manual search over active online members by role fit, location, and work mode. Candidate feed review is the canonical path before request_chat.',
|
|
266
271
|
},
|
|
267
272
|
matching: {
|
|
268
273
|
mode: 'profile_overlap',
|
|
269
274
|
cadence: 'periodic',
|
|
270
|
-
strategySummary:
|
|
275
|
+
strategySummary:
|
|
276
|
+
'Use target role, experience summary, and location/work mode as the first-pass scoring surface, deliver candidate summaries, and route contact through request_chat after review.',
|
|
271
277
|
candidateSources: ['world_members', 'search_results'],
|
|
272
278
|
},
|
|
273
279
|
sessionTemplate: {
|
|
@@ -5,7 +5,9 @@ const DEFAULT_CANDIDATE_FEED_LIMIT = 10;
|
|
|
5
5
|
export const CANDIDATE_OBJECT_FIELDS = Object.freeze([
|
|
6
6
|
'candidateId',
|
|
7
7
|
'worldId',
|
|
8
|
+
'targetAgentId',
|
|
8
9
|
'sourceMembershipId',
|
|
10
|
+
'requestChat',
|
|
9
11
|
'profileSummary',
|
|
10
12
|
'compatibilitySignals',
|
|
11
13
|
'deliveryReason',
|
|
@@ -79,6 +81,16 @@ function normalizeValue(value, field = {}) {
|
|
|
79
81
|
return normalized;
|
|
80
82
|
}
|
|
81
83
|
|
|
84
|
+
function buildRequestChatPayload(world, candidateMembership) {
|
|
85
|
+
const targetAgentId = normalizeText(candidateMembership?.agentId, null);
|
|
86
|
+
if (!targetAgentId) return null;
|
|
87
|
+
|
|
88
|
+
return {
|
|
89
|
+
worldId: world.worldId,
|
|
90
|
+
targetAgentId,
|
|
91
|
+
};
|
|
92
|
+
}
|
|
93
|
+
|
|
82
94
|
function normalizeComparableArray(value) {
|
|
83
95
|
if (!Array.isArray(value)) return [];
|
|
84
96
|
return [...new Set(value.map((entry) => normalizeText(entry, null)?.toLowerCase()).filter(Boolean))];
|
|
@@ -244,9 +256,15 @@ export function projectCandidateFeedModel(world) {
|
|
|
244
256
|
profileSummaryFieldShape: PROFILE_SUMMARY_FIELD_FIELDS,
|
|
245
257
|
compatibilitySignalFields: COMPATIBILITY_SIGNAL_FIELDS,
|
|
246
258
|
deliveryReasonFields: DELIVERY_REASON_FIELDS,
|
|
259
|
+
requestChatAction: {
|
|
260
|
+
action: 'request_chat',
|
|
261
|
+
requiredFields: ['worldId', 'targetAgentId'],
|
|
262
|
+
summary:
|
|
263
|
+
'After reviewing a candidate, create a world-scoped chat request with worldId and targetAgentId.',
|
|
264
|
+
},
|
|
247
265
|
liveDeliveryEvent: projectLiveDeliveryEvent(world, candidateFields),
|
|
248
266
|
summary:
|
|
249
|
-
'Active members can review candidate opportunities first, then
|
|
267
|
+
'Active members can review candidate opportunities first, then call request_chat with the selected targetAgentId when they want to start a world-scoped conversation request.',
|
|
250
268
|
status: 'phase1_candidate_feed',
|
|
251
269
|
};
|
|
252
270
|
}
|
|
@@ -265,11 +283,14 @@ function projectCandidateOpportunity({
|
|
|
265
283
|
profileSnapshot,
|
|
266
284
|
candidateMembership.membershipId,
|
|
267
285
|
);
|
|
286
|
+
const requestChat = buildRequestChatPayload(world, candidateMembership);
|
|
268
287
|
|
|
269
288
|
return {
|
|
270
289
|
candidateId: `cand_${candidateMembership.membershipId}`,
|
|
271
290
|
worldId: world.worldId,
|
|
272
291
|
sourceMembershipId: candidateMembership.membershipId,
|
|
292
|
+
targetAgentId: requestChat?.targetAgentId || null,
|
|
293
|
+
requestChat,
|
|
273
294
|
profileSummary: projectProfileSummary(world, profileSnapshot, candidateAgent),
|
|
274
295
|
compatibilitySignals,
|
|
275
296
|
deliveryReason: buildDeliveryReason(compatibilitySignals),
|
|
@@ -322,7 +343,7 @@ export function buildCandidateFeed({
|
|
|
322
343
|
generatedAt,
|
|
323
344
|
expiresAt,
|
|
324
345
|
deliveryMode: 'agent_review_before_live_session',
|
|
325
|
-
nextAction: '
|
|
346
|
+
nextAction: 'review_candidates_then_request_chat',
|
|
326
347
|
candidateModel: projectCandidateFeedModel(world),
|
|
327
348
|
candidates,
|
|
328
349
|
status: 'feed_ready',
|
|
@@ -133,7 +133,7 @@ function normalizeSearchSchema(searchSchema = {}, joinSchema = {}) {
|
|
|
133
133
|
: 10,
|
|
134
134
|
summary: normalizeText(
|
|
135
135
|
searchSchema.summary,
|
|
136
|
-
'
|
|
136
|
+
'Compatibility-only manual search over active online world members. Candidate feed review is the canonical path before request_chat.',
|
|
137
137
|
),
|
|
138
138
|
hints: normalizeStringList(searchSchema.hints),
|
|
139
139
|
};
|
|
@@ -259,7 +259,7 @@ export function projectJoinPlan(world) {
|
|
|
259
259
|
requiredFields: world.joinSchema.requiredFields,
|
|
260
260
|
optionalFields: world.joinSchema.optionalFields,
|
|
261
261
|
hints: world.joinSchema.hints,
|
|
262
|
-
nextAction: '
|
|
262
|
+
nextAction: 'call_join_world',
|
|
263
263
|
};
|
|
264
264
|
}
|
|
265
265
|
|
|
@@ -290,7 +290,7 @@ export function projectSearchModel(world) {
|
|
|
290
290
|
defaultLimit: world.searchSchema.defaultLimit,
|
|
291
291
|
summary: world.searchSchema.summary,
|
|
292
292
|
hints: world.searchSchema.hints,
|
|
293
|
-
status: '
|
|
293
|
+
status: 'compatibility_world_search',
|
|
294
294
|
};
|
|
295
295
|
}
|
|
296
296
|
|
|
@@ -190,7 +190,7 @@ function normalizeWorldDetail(payload = {}) {
|
|
|
190
190
|
requiredFields,
|
|
191
191
|
optionalFields,
|
|
192
192
|
hints: normalizeStringList(payload.hints),
|
|
193
|
-
nextAction: normalizeText(payload.nextAction, '
|
|
193
|
+
nextAction: normalizeText(payload.nextAction, 'call_join_world'),
|
|
194
194
|
sessionOverview: payload.sessionOverview && typeof payload.sessionOverview === 'object' ? payload.sessionOverview : {},
|
|
195
195
|
matchingOverview: payload.matchingOverview && typeof payload.matchingOverview === 'object' ? payload.matchingOverview : {},
|
|
196
196
|
searchSchema: normalizeSearchSchema(payload.searchSchema || {}, {
|
|
@@ -247,7 +247,7 @@ function normalizeWorldDetail(payload = {}) {
|
|
|
247
247
|
requiredFields,
|
|
248
248
|
optionalFields,
|
|
249
249
|
hints: normalizeStringList(payload.hints || joinSchema.hints),
|
|
250
|
-
nextAction: normalizeText(payload.nextAction || joinSchema.nextAction, '
|
|
250
|
+
nextAction: normalizeText(payload.nextAction || joinSchema.nextAction, 'call_join_world'),
|
|
251
251
|
sessionOverview,
|
|
252
252
|
matchingOverview,
|
|
253
253
|
searchSchema: normalizeSearchSchema(searchOverview, {
|
|
@@ -301,7 +301,7 @@ function normalizeJoinCheckResponse(payload = {}, { worldId = null, profile = {}
|
|
|
301
301
|
),
|
|
302
302
|
nextAction: normalizeText(
|
|
303
303
|
payload.nextAction,
|
|
304
|
-
accepted ? '
|
|
304
|
+
accepted ? 'call_join_world' : 'retry_join_world_after_profile_update',
|
|
305
305
|
),
|
|
306
306
|
};
|
|
307
307
|
}
|
|
@@ -373,11 +373,23 @@ function normalizeCandidateScoreBreakdown(entries = []) {
|
|
|
373
373
|
|
|
374
374
|
function normalizeCandidate(candidate = {}, index = 0) {
|
|
375
375
|
const normalizedRank = normalizeNumber(candidate.rank, null);
|
|
376
|
+
const targetAgentId = normalizeText(
|
|
377
|
+
candidate.targetAgentId || candidate.requestChat?.targetAgentId,
|
|
378
|
+
null,
|
|
379
|
+
);
|
|
380
|
+
const requestChat = targetAgentId
|
|
381
|
+
? {
|
|
382
|
+
worldId: normalizeText(candidate.requestChat?.worldId, normalizeText(candidate.worldId, 'unknown-world')),
|
|
383
|
+
targetAgentId,
|
|
384
|
+
}
|
|
385
|
+
: null;
|
|
376
386
|
|
|
377
387
|
return {
|
|
378
388
|
candidateId: normalizeText(candidate.candidateId, `candidate_${index + 1}`),
|
|
379
389
|
worldId: normalizeText(candidate.worldId, 'unknown-world'),
|
|
380
390
|
sourceMembershipId: normalizeText(candidate.sourceMembershipId, null),
|
|
391
|
+
targetAgentId,
|
|
392
|
+
requestChat,
|
|
381
393
|
profileSummary: normalizeCandidateProfileSummary(candidate.profileSummary),
|
|
382
394
|
compatibilitySignals: Array.isArray(candidate.compatibilitySignals)
|
|
383
395
|
? candidate.compatibilitySignals.map((signal, signalIndex) => normalizeCompatibilitySignal(signal, signalIndex))
|
|
@@ -406,7 +418,7 @@ function normalizeCandidateFeedResponse(payload = {}, { worldId = null, agentId
|
|
|
406
418
|
deliveryMode: normalizeText(payload.deliveryMode, 'agent_review_before_live_session'),
|
|
407
419
|
nextAction: normalizeText(
|
|
408
420
|
payload.nextAction,
|
|
409
|
-
candidates.length > 0 ? '
|
|
421
|
+
candidates.length > 0 ? 'review_candidates_then_request_chat' : 'wait_for_more_candidates',
|
|
410
422
|
),
|
|
411
423
|
candidateSource: normalizeText(payload.candidateSource, 'active_memberships'),
|
|
412
424
|
candidateModel: payload.candidateModel && typeof payload.candidateModel === 'object' ? payload.candidateModel : {},
|
|
@@ -443,12 +455,10 @@ function formatSessionOverview(detail = {}) {
|
|
|
443
455
|
: {};
|
|
444
456
|
const mode = normalizeText(detail.sessionMode || sessionOverview.mode, null);
|
|
445
457
|
const maxTurns = normalizeInteger(sessionOverview.maxTurns, null);
|
|
446
|
-
const turnTimeoutMs = normalizeInteger(sessionOverview.turnTimeoutMs, null);
|
|
447
458
|
const parts = [];
|
|
448
459
|
|
|
449
460
|
if (mode) parts.push(`${mode} mode`);
|
|
450
461
|
if (maxTurns != null) parts.push(`max ${maxTurns} turns`);
|
|
451
|
-
if (turnTimeoutMs != null) parts.push(`${turnTimeoutMs}ms turn timeout`);
|
|
452
462
|
|
|
453
463
|
return parts.length > 0 ? parts.join(', ') : null;
|
|
454
464
|
}
|
|
@@ -490,6 +500,7 @@ export function buildWorldSessionStartupText(detail = {}) {
|
|
|
490
500
|
summary ? `Summary: ${summary}` : null,
|
|
491
501
|
sessionSummary ? `Session overview: ${sessionSummary}` : null,
|
|
492
502
|
raiseHandSummary ? `Completion rule: ${raiseHandSummary}` : null,
|
|
503
|
+
'Interruption handling: prefer reconnect/resume. Temporary silence or reconnect churn is not the normal way to close a round.',
|
|
493
504
|
openingText ? `Opening focus: ${openingText}` : null,
|
|
494
505
|
interactionRules ? `Interaction rules: ${interactionRules}` : null,
|
|
495
506
|
prohibitedRules ? `Prohibited rules: ${prohibitedRules}` : null,
|
|
@@ -544,7 +555,7 @@ function buildSelectionRetryContract(status, selection, items = [], matches = []
|
|
|
544
555
|
stage: 'post_setup_world_selection_retry',
|
|
545
556
|
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
557
|
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
|
|
558
|
+
followUp: 'Once the user confirms one exact world, fetch its detail, explain the required fields, and use join_world when enough profile data is available.',
|
|
548
559
|
},
|
|
549
560
|
};
|
|
550
561
|
}
|
|
@@ -564,7 +575,7 @@ function buildSelectionRetryContract(status, selection, items = [], matches = []
|
|
|
564
575
|
user: retrySummary.length > 0
|
|
565
576
|
? `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
577
|
: '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
|
|
578
|
+
followUp: 'Once the user chooses a valid world, confirm it, fetch the world detail, and explain the required fields before calling join_world.',
|
|
568
579
|
},
|
|
569
580
|
};
|
|
570
581
|
}
|
|
@@ -588,7 +599,7 @@ export function buildWorldSelectionPrompt(worldDirectory = {}) {
|
|
|
588
599
|
? `Available worlds:\n${worldLines.join('\n')}\nAsk the user which world they want to join next.`
|
|
589
600
|
: 'No worlds are currently available. Tell the user setup is complete but no worlds can be selected yet.',
|
|
590
601
|
followUp:
|
|
591
|
-
'After the user chooses a world, confirm the selection, fetch that world detail, explain the required fields, and then use
|
|
602
|
+
'After the user chooses a world, confirm the selection, fetch that world detail, explain the required fields, and then use join_world for that world.',
|
|
592
603
|
};
|
|
593
604
|
}
|
|
594
605
|
|
|
@@ -653,7 +664,7 @@ export function resolveWorldSelection(worldDirectory = {}, selection = null) {
|
|
|
653
664
|
system: 'Confirm the resolved world choice before fetching detail and explaining the required fields.',
|
|
654
665
|
user: `I matched the user choice to ${selectedWorld.displayName} [${selectedWorld.worldId}]. Confirm that this is the world we will use next.`,
|
|
655
666
|
confirmation: `Confirmed world: ${selectedWorld.displayName} [${selectedWorld.worldId}].`,
|
|
656
|
-
followUp: 'Fetch the selected world detail
|
|
667
|
+
followUp: 'Fetch the selected world detail, explain the required fields, and use join_world once enough profile data is available.',
|
|
657
668
|
},
|
|
658
669
|
};
|
|
659
670
|
}
|
|
@@ -678,7 +689,7 @@ export function buildRequiredFieldExplanation(worldDetail = {}) {
|
|
|
678
689
|
const optionalFieldLabels = optionalFields.map((field) => field.label);
|
|
679
690
|
const summary = requiredFields.length > 0
|
|
680
691
|
? `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
|
|
692
|
+
: `${detail.displayName} does not require any mandatory profile fields before join_world.`;
|
|
682
693
|
const steps = requiredFields.map((field, index) => ({
|
|
683
694
|
step: index + 1,
|
|
684
695
|
fieldId: field.fieldId,
|
|
@@ -691,7 +702,7 @@ export function buildRequiredFieldExplanation(worldDetail = {}) {
|
|
|
691
702
|
const optionalContextSummary = optionalFieldLabels.length > 0
|
|
692
703
|
? `Optional context you can add later: ${joinAsNaturalLanguage(optionalFieldLabels)}.`
|
|
693
704
|
: null;
|
|
694
|
-
const nextInstruction = steps[0]?.prompt || 'All required fields are already explained. You can move to
|
|
705
|
+
const nextInstruction = steps[0]?.prompt || 'All required fields are already explained. You can move to join_world.';
|
|
695
706
|
|
|
696
707
|
return {
|
|
697
708
|
status: 'ready',
|
|
@@ -707,14 +718,14 @@ export function buildRequiredFieldExplanation(worldDetail = {}) {
|
|
|
707
718
|
nextAction: detail.nextAction,
|
|
708
719
|
orchestration: {
|
|
709
720
|
stage: 'post_setup_world_requirements',
|
|
710
|
-
system: 'Confirm the selected world, explain its required fields in plain language, and
|
|
721
|
+
system: 'Confirm the selected world, explain its required fields in plain language, and use join_world once the available profile data is enough for the backend to validate.',
|
|
711
722
|
confirmation: `Confirmed world: ${detail.displayName} [${detail.worldId}].`,
|
|
712
723
|
user: [summary, nextInstruction, optionalContextSummary].filter(Boolean).join('\n\n'),
|
|
713
724
|
followUp: requiredFields.length > 1
|
|
714
|
-
? `After the user answers ${steps[0].label}, continue with the remaining required fields in order, then call
|
|
725
|
+
? `After the user answers ${steps[0].label}, continue with the remaining required fields in order, then call join_world.`
|
|
715
726
|
: (requiredFields.length === 1
|
|
716
|
-
? 'After the user answers the required field, call
|
|
717
|
-
: 'No required fields remain. Call
|
|
727
|
+
? 'After the user answers the required field, call join_world.'
|
|
728
|
+
: 'No required fields remain. Call join_world now.'),
|
|
718
729
|
},
|
|
719
730
|
};
|
|
720
731
|
}
|
|
@@ -778,10 +789,10 @@ function buildProfileCollectionFollowUp(promptFields = []) {
|
|
|
778
789
|
|
|
779
790
|
const labels = promptFields.map((field) => field.label);
|
|
780
791
|
if (promptFields.length === 1) {
|
|
781
|
-
return `After the user answers ${labels[0]}, merge it into the saved profile draft and
|
|
792
|
+
return `After the user answers ${labels[0]}, merge it into the saved profile draft and retry join_world before asking anything else.`;
|
|
782
793
|
}
|
|
783
794
|
|
|
784
|
-
return `After the user answers ${joinAsNaturalLanguage(labels)}, merge those fields into the saved profile draft and
|
|
795
|
+
return `After the user answers ${joinAsNaturalLanguage(labels)}, merge those fields into the saved profile draft and retry join_world before asking anything else.`;
|
|
785
796
|
}
|
|
786
797
|
|
|
787
798
|
export function buildWorldProfileCollectionFlow({
|
|
@@ -848,7 +859,7 @@ export function buildWorldProfileCollectionFlow({
|
|
|
848
859
|
orchestration: {
|
|
849
860
|
stage: 'post_setup_world_profile_collection',
|
|
850
861
|
system:
|
|
851
|
-
'Use backend
|
|
862
|
+
'Use the backend join_world unmet-requirement guidance as the source of truth for the next required field prompt. After every user reply or the current batch checkpoint, merge the updates into the saved profile draft and retry join_world.',
|
|
852
863
|
confirmation: `Confirmed world: ${detail.displayName} [${detail.worldId}].`,
|
|
853
864
|
user: [summary, savedSummary, promptSummary, promptBody, optionalContextSummary].filter(Boolean).join('\n\n'),
|
|
854
865
|
followUp: buildProfileCollectionFollowUp(promptFields),
|
|
@@ -870,13 +881,13 @@ export function buildWorldJoinOutcomeOrchestration({ worldDetail = {}, joinResul
|
|
|
870
881
|
stage: 'world_join_result',
|
|
871
882
|
status: membershipStatus,
|
|
872
883
|
system:
|
|
873
|
-
'The backend already resolved the world join result. Reflect the authoritative membership outcome before any
|
|
884
|
+
'The backend already resolved the world join result. Reflect the authoritative membership outcome before any candidate-feed review, request_chat, or live-session follow-up.',
|
|
874
885
|
confirmation: `World membership in ${detail.displayName} [${detail.worldId}] is ${membershipStatus}.`,
|
|
875
886
|
user: membershipStatus === 'active'
|
|
876
887
|
? ['Joined ' + detail.displayName + ' successfully. World membership is active.', joinSummary].filter(Boolean).join(' ')
|
|
877
888
|
: `The join result for ${detail.displayName} is ${membershipStatus}.`,
|
|
878
889
|
followUp: membershipStatus === 'active'
|
|
879
|
-
? 'Use backend-authored
|
|
890
|
+
? 'Use the backend-authored candidate-feed and candidate-delivery payloads for the next world step, and keep request_chat as the canonical conversation-start action.'
|
|
880
891
|
: 'Do not continue to world-member-only follow-up until membership becomes active.',
|
|
881
892
|
};
|
|
882
893
|
}
|
|
@@ -923,6 +934,13 @@ export function buildCandidateDeliverySummary(candidateFeed = {}, { worldDetail
|
|
|
923
934
|
normalizeInteger(limit, normalizedFeed.candidates.length || normalizedFeed.totalCandidates || 1),
|
|
924
935
|
);
|
|
925
936
|
const displayName = detail?.displayName || normalizedFeed.worldId || 'the selected world';
|
|
937
|
+
const requestChatAction = {
|
|
938
|
+
action: 'request_chat',
|
|
939
|
+
worldId: normalizedFeed.worldId,
|
|
940
|
+
requiredFields: ['worldId', 'targetAgentId'],
|
|
941
|
+
summary:
|
|
942
|
+
'After the user chooses a candidate, request_chat with this worldId and the candidate targetAgentId.',
|
|
943
|
+
};
|
|
926
944
|
const candidateSummaries = normalizedFeed.candidates.slice(0, summaryLimit).map((candidate, index) => {
|
|
927
945
|
const name = candidate.profileSummary.displayName || `Candidate ${index + 1}`;
|
|
928
946
|
const requiredFieldSummary = summarizeProfileFields(candidate.profileSummary.requiredFields);
|
|
@@ -946,6 +964,8 @@ export function buildCandidateDeliverySummary(candidateFeed = {}, { worldDetail
|
|
|
946
964
|
return {
|
|
947
965
|
candidateId: candidate.candidateId,
|
|
948
966
|
sourceMembershipId: candidate.sourceMembershipId,
|
|
967
|
+
targetAgentId: candidate.targetAgentId,
|
|
968
|
+
requestChat: candidate.requestChat,
|
|
949
969
|
displayName: name,
|
|
950
970
|
headline: candidate.profileSummary.headline,
|
|
951
971
|
rank: candidate.rank,
|
|
@@ -969,10 +989,13 @@ export function buildCandidateDeliverySummary(candidateFeed = {}, { worldDetail
|
|
|
969
989
|
: 'No candidates are currently available from the active-membership feed.';
|
|
970
990
|
|
|
971
991
|
return {
|
|
992
|
+
worldId: normalizedFeed.worldId,
|
|
993
|
+
agentId: normalizedFeed.agentId,
|
|
972
994
|
status: deliveredCandidateCount > 0 ? 'candidate_summary_ready' : 'candidate_summary_pending',
|
|
973
995
|
deliveredCandidateCount,
|
|
974
996
|
totalCandidateCount,
|
|
975
997
|
remainingCandidateCount,
|
|
998
|
+
requestChatAction,
|
|
976
999
|
candidateSummaries,
|
|
977
1000
|
nextAction: deliveredCandidateCount > 0
|
|
978
1001
|
? normalizedFeed.nextAction
|
|
@@ -980,13 +1003,13 @@ export function buildCandidateDeliverySummary(candidateFeed = {}, { worldDetail
|
|
|
980
1003
|
orchestration: {
|
|
981
1004
|
stage: 'post_join_candidate_delivery',
|
|
982
1005
|
system:
|
|
983
|
-
'Use the backend-authored candidate summaries already attached to this payload.
|
|
1006
|
+
'Use the backend-authored candidate summaries already attached to this payload. Candidate requestChat payloads are the canonical follow-up inputs for world-scoped contact establishment.',
|
|
984
1007
|
confirmation: `Candidate review payload for ${displayName} [${normalizedFeed.worldId}].`,
|
|
985
1008
|
user: [heading, promptBody].filter(Boolean).join('\n\n'),
|
|
986
1009
|
followUp: deliveredCandidateCount > 0
|
|
987
1010
|
? (remainingCandidateCount > 0
|
|
988
|
-
? `Share these ${deliveredCandidateCount} candidate summaries first. If the user
|
|
989
|
-
: 'Share these candidate summaries and
|
|
1011
|
+
? `Share these ${deliveredCandidateCount} candidate summaries first. If the user chooses someone now, continue with request_chat using that candidate's {worldId, targetAgentId}. If they want more options first, continue with the remaining ${remainingCandidateCount} candidate${remainingCandidateCount === 1 ? '' : 's'} from the same feed.`
|
|
1012
|
+
: 'Share these candidate summaries and, if the user chooses one, continue with request_chat using the attached {worldId, targetAgentId} payload for that candidate.')
|
|
990
1013
|
: 'Tell the user candidate delivery can be retried later through the same backend-authored world flow.',
|
|
991
1014
|
},
|
|
992
1015
|
};
|
|
@@ -69,15 +69,14 @@ function createJoinNotEligibleError(joinCheck) {
|
|
|
69
69
|
error.responseBody = {
|
|
70
70
|
error: error.code,
|
|
71
71
|
message: 'profile does not satisfy world join requirements',
|
|
72
|
+
status: 'needs_profile',
|
|
72
73
|
membershipStatus: 'inactive',
|
|
73
74
|
worldId: joinCheck.worldId,
|
|
75
|
+
normalizedProfile: joinCheck.normalizedProfile,
|
|
74
76
|
missingFields: joinCheck.missingFields,
|
|
75
77
|
nextMissingField: joinCheck.nextMissingField,
|
|
76
78
|
missingFieldGuidance: joinCheck.missingFieldGuidance,
|
|
77
|
-
|
|
78
|
-
nextAction: joinCheck.nextAction,
|
|
79
|
-
joinCheck,
|
|
80
|
-
profileCollectionFlow: joinCheck.profileCollectionFlow || null,
|
|
79
|
+
nextAction: 'retry_join_world_after_profile_update',
|
|
81
80
|
};
|
|
82
81
|
return error;
|
|
83
82
|
}
|
|
@@ -98,12 +97,14 @@ function normalizeProfileSnapshot(...candidates) {
|
|
|
98
97
|
|
|
99
98
|
function buildNextStageSummary(world) {
|
|
100
99
|
const matchingSummary = world.matching.strategySummary
|
|
101
|
-
|| `The world uses ${world.matching.mode} matching before
|
|
100
|
+
|| `The world uses ${world.matching.mode} matching to deliver candidate summaries before request_chat.`;
|
|
101
|
+
const reviewSummary =
|
|
102
|
+
'Review the backend-authored candidate feed, choose a candidate, and create a world-scoped chat request.';
|
|
102
103
|
const sessionSummary = `Matched agents then enter a ${world.sessionTemplate.mode} session with up to ${world.sessionTemplate.maxTurns} turns.`;
|
|
103
104
|
|
|
104
105
|
return {
|
|
105
106
|
stage: 'matchmaking',
|
|
106
|
-
summary: `${matchingSummary} ${sessionSummary}`.trim(),
|
|
107
|
+
summary: `${matchingSummary} ${reviewSummary} ${sessionSummary}`.trim(),
|
|
107
108
|
matchingMode: world.matching.mode,
|
|
108
109
|
matchingCadence: world.matching.cadence,
|
|
109
110
|
sessionMode: world.sessionTemplate.mode,
|
|
@@ -143,7 +144,7 @@ export function createMembershipService({ worldService, store = null } = {}) {
|
|
|
143
144
|
},
|
|
144
145
|
normalizedProfile,
|
|
145
146
|
nextAction:
|
|
146
|
-
missingFields.length === 0 ? '
|
|
147
|
+
missingFields.length === 0 ? 'call_join_world' : 'retry_join_world_after_profile_update',
|
|
147
148
|
};
|
|
148
149
|
|
|
149
150
|
const worldDetail = worldService.describeWorldDetail(world.worldId);
|
|
@@ -242,15 +243,21 @@ export function createMembershipService({ worldService, store = null } = {}) {
|
|
|
242
243
|
});
|
|
243
244
|
|
|
244
245
|
return {
|
|
246
|
+
status: 'joined',
|
|
247
|
+
worldId: world.worldId,
|
|
245
248
|
membership,
|
|
246
249
|
membershipStatus: membership.status,
|
|
247
250
|
created: !existingMembership,
|
|
251
|
+
normalizedProfile: joinCheck.normalizedProfile,
|
|
252
|
+
nextAction: 'review_candidate_feed',
|
|
248
253
|
nextStageSummary: buildNextStageSummary(world),
|
|
249
254
|
orchestration: buildWorldJoinOutcomeOrchestration({
|
|
250
255
|
worldDetail: worldService.describeWorldDetail(world.worldId),
|
|
251
256
|
joinResult: {
|
|
252
257
|
membershipStatus: membership.status,
|
|
253
258
|
membership,
|
|
259
|
+
normalizedProfile: joinCheck.normalizedProfile,
|
|
260
|
+
nextAction: 'review_candidate_feed',
|
|
254
261
|
nextStageSummary: buildNextStageSummary(world),
|
|
255
262
|
},
|
|
256
263
|
}),
|
|
@@ -348,7 +348,7 @@ export function createWorldSearchService({
|
|
|
348
348
|
limit: normalizedLimit,
|
|
349
349
|
totalMatches: orderedItems.length,
|
|
350
350
|
items: orderedItems.slice(0, normalizedLimit),
|
|
351
|
-
nextAction: orderedItems.length > 0 ? '
|
|
351
|
+
nextAction: orderedItems.length > 0 ? 'request_chat_with_selected_candidate' : 'broaden_search_or_wait',
|
|
352
352
|
status: orderedItems.length > 0 ? 'search_ready' : 'no_matches',
|
|
353
353
|
};
|
|
354
354
|
},
|
|
@@ -227,7 +227,8 @@ function buildSearchSchema(entryProfileSchema) {
|
|
|
227
227
|
return {
|
|
228
228
|
mode: 'profile_overlap_search',
|
|
229
229
|
inputFieldIds: entryProfileSchema.searchableFieldIds,
|
|
230
|
-
summary:
|
|
230
|
+
summary:
|
|
231
|
+
'Compatibility-only manual search over active online members. Candidate feed review is the canonical path before request_chat.',
|
|
231
232
|
onlineOnly: true,
|
|
232
233
|
defaultLimit: 10,
|
|
233
234
|
};
|
|
@@ -238,7 +239,7 @@ function buildMatchingStrategy(entryProfileSchema) {
|
|
|
238
239
|
mode: 'profile_overlap',
|
|
239
240
|
cadence: 'on_demand',
|
|
240
241
|
strategySummary:
|
|
241
|
-
`Rank world members by overlap on ${entryProfileSchema.searchableFieldIds.join(', ')}
|
|
242
|
+
`Rank world members by overlap on ${entryProfileSchema.searchableFieldIds.join(', ')}, deliver candidate summaries first, and let members request_chat after review.`,
|
|
242
243
|
candidateSources: ['active_memberships'],
|
|
243
244
|
};
|
|
244
245
|
}
|
|
@@ -2,6 +2,7 @@ import { authenticateAppTokenRequest, resolveAuthenticatedAgentId } from '../../
|
|
|
2
2
|
import {
|
|
3
3
|
buildCandidateDeliverySummary,
|
|
4
4
|
buildRequiredFieldExplanation,
|
|
5
|
+
buildResolvedWorldJoinOrchestration,
|
|
5
6
|
buildWorldSelectionPrompt,
|
|
6
7
|
} from '../contracts/world-orchestration.js';
|
|
7
8
|
|
|
@@ -295,12 +296,33 @@ export function registerWorldRoutes(
|
|
|
295
296
|
profileSnapshot: req.body?.profileSnapshot,
|
|
296
297
|
maxFieldsPerStep: req.body?.maxFieldsPerStep,
|
|
297
298
|
});
|
|
299
|
+
const worldDetail = worldService.describeWorldDetail(req.params.worldId);
|
|
300
|
+
const candidateFeed = matchmakingService.listCandidateFeed({
|
|
301
|
+
worldId: req.params.worldId,
|
|
302
|
+
agentId,
|
|
303
|
+
limit: req.body?.candidateLimit || null,
|
|
304
|
+
});
|
|
305
|
+
const candidateDelivery = buildCandidateDeliverySummary(candidateFeed, {
|
|
306
|
+
worldDetail,
|
|
307
|
+
limit: req.body?.candidateLimit || candidateFeed.limit || null,
|
|
308
|
+
});
|
|
298
309
|
res.status(result.created ? 201 : 200).json({
|
|
310
|
+
status: result.status,
|
|
299
311
|
worldId: req.params.worldId,
|
|
300
312
|
membershipStatus: result.membershipStatus,
|
|
313
|
+
normalizedProfile: result.normalizedProfile,
|
|
301
314
|
membership: result.membership,
|
|
315
|
+
nextAction: result.nextAction,
|
|
302
316
|
nextStageSummary: result.nextStageSummary,
|
|
303
|
-
|
|
317
|
+
candidateFeed: {
|
|
318
|
+
...candidateFeed,
|
|
319
|
+
candidateDelivery,
|
|
320
|
+
},
|
|
321
|
+
candidateDelivery,
|
|
322
|
+
orchestration: buildResolvedWorldJoinOrchestration({
|
|
323
|
+
joinResult: result,
|
|
324
|
+
candidateDelivery,
|
|
325
|
+
}) || result.orchestration || null,
|
|
304
326
|
});
|
|
305
327
|
} catch (error) {
|
|
306
328
|
sendWorldError(res, error);
|