@xfxstudio/claworld 0.2.13 → 0.2.14
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 +4 -4
- package/index.js +0 -1
- package/openclaw.plugin.json +1 -1
- package/package.json +1 -1
- package/skills/claworld-help/SKILL.md +19 -27
- package/skills/claworld-join-and-chat/SKILL.md +9 -9
- package/src/openclaw/index.js +0 -3
- package/src/openclaw/plugin/account-identity.js +0 -1
- package/src/openclaw/plugin/claworld-channel-plugin.js +8 -253
- package/src/openclaw/plugin/managed-config.js +1 -7
- package/src/openclaw/plugin/onboarding.js +1 -1
- package/src/openclaw/plugin/register.js +183 -232
- package/src/openclaw/plugin/relay-client.js +8 -5
- package/src/openclaw/runtime/product-shell-helper.js +11 -364
- package/src/openclaw/runtime/tool-contracts.js +0 -182
- package/src/openclaw/runtime/tool-inventory.js +4 -27
- package/src/lib/agent-profile.js +0 -74
- package/src/lib/http-auth.js +0 -151
- package/src/lib/policy.js +0 -114
- package/src/openclaw/installer/constants.js +0 -14
- package/src/product-shell/agent-cards/card-routes.js +0 -64
- package/src/product-shell/agent-cards/card-service.js +0 -287
- package/src/product-shell/agent-cards/spec-builder.js +0 -167
- package/src/product-shell/agent-cards/storage/image-host-storage.js +0 -192
- package/src/product-shell/agent-cards/storage/local-public-storage.js +0 -74
- package/src/product-shell/agent-cards/svg-renderer.js +0 -325
- package/src/product-shell/agent-cards/template-registry.js +0 -131
- package/src/product-shell/catalog/default-world-catalog.js +0 -38
- package/src/product-shell/contracts/candidate-feed.js +0 -393
- package/src/product-shell/contracts/world-manifest.js +0 -369
- package/src/product-shell/conversation-feedback/conversation-feedback-service.js +0 -261
- package/src/product-shell/feedback/feedback-contract.js +0 -13
- package/src/product-shell/feedback/feedback-routes.js +0 -98
- package/src/product-shell/feedback/feedback-service.js +0 -252
- package/src/product-shell/index.js +0 -212
- package/src/product-shell/matching/matchmaking-service.js +0 -395
- package/src/product-shell/membership/membership-service.js +0 -284
- package/src/product-shell/onboarding/onboarding-routes.js +0 -37
- package/src/product-shell/onboarding/onboarding-service.js +0 -222
- package/src/product-shell/orchestration/world-conversation-orchestrator.js +0 -28
- package/src/product-shell/profile/profile-service.js +0 -142
- package/src/product-shell/profile/public-identity-routes.js +0 -160
- package/src/product-shell/profile/public-identity-service.js +0 -192
- package/src/product-shell/search/search-service.js +0 -393
- package/src/product-shell/social/chat-request-approval-policy.js +0 -332
- package/src/product-shell/social/chat-request-routes.js +0 -130
- package/src/product-shell/social/chat-request-service.js +0 -723
- package/src/product-shell/social/friend-routes.js +0 -82
- package/src/product-shell/social/friend-service.js +0 -557
- package/src/product-shell/social/social-routes.js +0 -21
- package/src/product-shell/social/social-service.js +0 -136
- package/src/product-shell/worlds/world-admin-service.js +0 -486
- package/src/product-shell/worlds/world-authorization.js +0 -136
- package/src/product-shell/worlds/world-broadcast-service.js +0 -296
- package/src/product-shell/worlds/world-routes.js +0 -403
- package/src/product-shell/worlds/world-service.js +0 -89
- package/src/product-shell/worlds/world-text.js +0 -75
|
@@ -1,192 +0,0 @@
|
|
|
1
|
-
import {
|
|
2
|
-
buildPublicIdentityMissingFields,
|
|
3
|
-
formatPublicIdentityDisplay,
|
|
4
|
-
generatePublicIdentityCode,
|
|
5
|
-
PUBLIC_IDENTITY_STATUS,
|
|
6
|
-
resolvePublicIdentity,
|
|
7
|
-
validatePublicDisplayName,
|
|
8
|
-
} from '../../lib/public-identity.js';
|
|
9
|
-
|
|
10
|
-
function normalizeText(value, fallback = null) {
|
|
11
|
-
if (value == null) return fallback;
|
|
12
|
-
const normalized = String(value).trim();
|
|
13
|
-
return normalized || fallback;
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
function createConfigurationError() {
|
|
17
|
-
const error = new Error('public_identity_store_unavailable');
|
|
18
|
-
error.code = 'public_identity_store_unavailable';
|
|
19
|
-
error.status = 500;
|
|
20
|
-
return error;
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
function createAgentNotFoundError(agentId) {
|
|
24
|
-
const error = new Error(`agent_not_found:${agentId}`);
|
|
25
|
-
error.code = 'agent_not_found';
|
|
26
|
-
error.status = 404;
|
|
27
|
-
error.responseBody = {
|
|
28
|
-
error: error.code,
|
|
29
|
-
message: 'agent not found',
|
|
30
|
-
agentId: normalizeText(agentId, null),
|
|
31
|
-
};
|
|
32
|
-
return error;
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
function createInvalidPublicIdentityRequest(fieldId, message, code = 'invalid_public_identity') {
|
|
36
|
-
const error = new Error(code);
|
|
37
|
-
error.code = code;
|
|
38
|
-
error.status = 400;
|
|
39
|
-
error.responseBody = {
|
|
40
|
-
error: code,
|
|
41
|
-
message: 'public identity request is invalid',
|
|
42
|
-
fieldErrors: [
|
|
43
|
-
{
|
|
44
|
-
fieldId,
|
|
45
|
-
message,
|
|
46
|
-
},
|
|
47
|
-
],
|
|
48
|
-
};
|
|
49
|
-
return error;
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
const DEFAULT_PUBLIC_IDENTITY_NEXT_TOOL = 'claworld_profile';
|
|
53
|
-
|
|
54
|
-
function createPublicIdentityIncompleteError(agent, {
|
|
55
|
-
capability = null,
|
|
56
|
-
nextTool = DEFAULT_PUBLIC_IDENTITY_NEXT_TOOL,
|
|
57
|
-
} = {}) {
|
|
58
|
-
const projected = projectPublicIdentityStatus(agent, { nextTool });
|
|
59
|
-
const capabilityLabel = normalizeText(capability, 'this Claworld capability');
|
|
60
|
-
const error = new Error(`public_identity_incomplete:${agent?.agentId || 'unknown'}`);
|
|
61
|
-
error.code = 'public_identity_incomplete';
|
|
62
|
-
error.status = 409;
|
|
63
|
-
error.responseBody = {
|
|
64
|
-
status: 'blocked',
|
|
65
|
-
error: error.code,
|
|
66
|
-
message: `${capabilityLabel} requires a public Claworld identity`,
|
|
67
|
-
agentId: normalizeText(agent?.agentId, null),
|
|
68
|
-
requiredAction: projected.requiredAction,
|
|
69
|
-
nextAction: projected.nextAction,
|
|
70
|
-
nextTool: projected.nextTool,
|
|
71
|
-
missingFields: projected.missingFields,
|
|
72
|
-
publicIdentity: projected.publicIdentity,
|
|
73
|
-
};
|
|
74
|
-
return error;
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
function projectPublicIdentityStatus(agent, {
|
|
78
|
-
nextTool = DEFAULT_PUBLIC_IDENTITY_NEXT_TOOL,
|
|
79
|
-
conversationFeedbackService = null,
|
|
80
|
-
} = {}) {
|
|
81
|
-
const publicIdentity = resolvePublicIdentity(agent);
|
|
82
|
-
const ready = publicIdentity.status === PUBLIC_IDENTITY_STATUS.READY;
|
|
83
|
-
return {
|
|
84
|
-
status: ready ? 'ready' : 'pending',
|
|
85
|
-
agentId: normalizeText(agent?.agentId, null),
|
|
86
|
-
ready,
|
|
87
|
-
publicIdentity: {
|
|
88
|
-
status: publicIdentity.status,
|
|
89
|
-
displayName: publicIdentity.displayName,
|
|
90
|
-
code: publicIdentity.code,
|
|
91
|
-
displayIdentity: formatPublicIdentityDisplay(publicIdentity),
|
|
92
|
-
confirmedAt: publicIdentity.confirmedAt,
|
|
93
|
-
updatedAt: publicIdentity.updatedAt,
|
|
94
|
-
},
|
|
95
|
-
recommendedDisplayName: normalizeText(agent?.displayName, publicIdentity.displayName || null),
|
|
96
|
-
nextAction: ready ? 'continue_claworld_flow' : 'set_public_identity',
|
|
97
|
-
requiredAction: ready ? null : 'set_public_identity',
|
|
98
|
-
nextTool: ready ? null : nextTool,
|
|
99
|
-
missingFields: ready ? [] : buildPublicIdentityMissingFields(agent),
|
|
100
|
-
feedbackSummary: conversationFeedbackService?.summarizeAgent?.({ agentId: agent?.agentId }) || {
|
|
101
|
-
totalLikesReceived: 0,
|
|
102
|
-
totalDislikesReceived: 0,
|
|
103
|
-
totalLikesGiven: 0,
|
|
104
|
-
totalDislikesGiven: 0,
|
|
105
|
-
},
|
|
106
|
-
};
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
export function createPublicIdentityService({ store = null, conversationFeedbackService = null } = {}) {
|
|
110
|
-
function assertStore() {
|
|
111
|
-
if (!store) throw createConfigurationError();
|
|
112
|
-
return store;
|
|
113
|
-
}
|
|
114
|
-
|
|
115
|
-
function requireAgent(agentId) {
|
|
116
|
-
const normalizedAgentId = normalizeText(agentId, null);
|
|
117
|
-
if (!normalizedAgentId) {
|
|
118
|
-
throw createInvalidPublicIdentityRequest('agentId', 'agentId is required');
|
|
119
|
-
}
|
|
120
|
-
const agent = assertStore().getAgent(normalizedAgentId);
|
|
121
|
-
if (!agent) throw createAgentNotFoundError(normalizedAgentId);
|
|
122
|
-
return agent;
|
|
123
|
-
}
|
|
124
|
-
|
|
125
|
-
function codeExists(code, excludeAgentId = null) {
|
|
126
|
-
return assertStore()
|
|
127
|
-
.listAgents()
|
|
128
|
-
.some((agent) => agent.agentId !== excludeAgentId && resolvePublicIdentity(agent).code === code);
|
|
129
|
-
}
|
|
130
|
-
|
|
131
|
-
function issueStableCode(agentId) {
|
|
132
|
-
for (let attempt = 0; attempt < 32; attempt += 1) {
|
|
133
|
-
const code = generatePublicIdentityCode();
|
|
134
|
-
if (!codeExists(code, agentId)) return code;
|
|
135
|
-
}
|
|
136
|
-
throw new Error('public_identity_code_generation_failed');
|
|
137
|
-
}
|
|
138
|
-
|
|
139
|
-
function isPublicIdentityCodeConflict(error) {
|
|
140
|
-
return error?.code === 'public_identity_code_conflict';
|
|
141
|
-
}
|
|
142
|
-
|
|
143
|
-
return {
|
|
144
|
-
getPublicIdentityStatus({ agentId } = {}) {
|
|
145
|
-
const agent = requireAgent(agentId);
|
|
146
|
-
return projectPublicIdentityStatus(agent, { conversationFeedbackService });
|
|
147
|
-
},
|
|
148
|
-
|
|
149
|
-
async updatePublicIdentity({ agentId, displayName } = {}) {
|
|
150
|
-
const agent = requireAgent(agentId);
|
|
151
|
-
const validation = validatePublicDisplayName(displayName);
|
|
152
|
-
if (!validation.ok) {
|
|
153
|
-
throw createInvalidPublicIdentityRequest('displayName', validation.message, validation.code);
|
|
154
|
-
}
|
|
155
|
-
const existingIdentity = resolvePublicIdentity(agent);
|
|
156
|
-
const stableCode = existingIdentity.code || null;
|
|
157
|
-
|
|
158
|
-
for (let attempt = 0; attempt < 32; attempt += 1) {
|
|
159
|
-
const now = assertStore().now();
|
|
160
|
-
const nextCode = stableCode || issueStableCode(agent.agentId);
|
|
161
|
-
try {
|
|
162
|
-
const updatedAgent = await assertStore().updateAgent(agent.agentId, {
|
|
163
|
-
displayName: validation.value,
|
|
164
|
-
publicIdentity: {
|
|
165
|
-
displayName: validation.value,
|
|
166
|
-
code: nextCode,
|
|
167
|
-
status: PUBLIC_IDENTITY_STATUS.READY,
|
|
168
|
-
confirmedAt: existingIdentity.confirmedAt || now,
|
|
169
|
-
updatedAt: now,
|
|
170
|
-
},
|
|
171
|
-
});
|
|
172
|
-
return projectPublicIdentityStatus(updatedAgent, { conversationFeedbackService });
|
|
173
|
-
} catch (error) {
|
|
174
|
-
if (!stableCode && isPublicIdentityCodeConflict(error)) {
|
|
175
|
-
continue;
|
|
176
|
-
}
|
|
177
|
-
throw error;
|
|
178
|
-
}
|
|
179
|
-
}
|
|
180
|
-
|
|
181
|
-
throw new Error('public_identity_code_generation_failed');
|
|
182
|
-
},
|
|
183
|
-
|
|
184
|
-
assertPublicIdentityReady({ agentId, capability = null } = {}) {
|
|
185
|
-
const agent = requireAgent(agentId);
|
|
186
|
-
if (resolvePublicIdentity(agent).status !== PUBLIC_IDENTITY_STATUS.READY) {
|
|
187
|
-
throw createPublicIdentityIncompleteError(agent, { capability });
|
|
188
|
-
}
|
|
189
|
-
return projectPublicIdentityStatus(agent, { conversationFeedbackService });
|
|
190
|
-
},
|
|
191
|
-
};
|
|
192
|
-
}
|
|
@@ -1,393 +0,0 @@
|
|
|
1
|
-
import { projectSearchModel } from '../contracts/world-manifest.js';
|
|
2
|
-
import { WORLD_ACTIONS } from '../worlds/world-authorization.js';
|
|
3
|
-
|
|
4
|
-
function isEmptyValue(value) {
|
|
5
|
-
if (value == null) return true;
|
|
6
|
-
if (typeof value === 'string') return value.trim() === '';
|
|
7
|
-
if (Array.isArray(value)) return value.length === 0;
|
|
8
|
-
return false;
|
|
9
|
-
}
|
|
10
|
-
|
|
11
|
-
function normalizeText(value, fallback = null) {
|
|
12
|
-
if (value == null) return fallback;
|
|
13
|
-
const normalized = String(value).trim();
|
|
14
|
-
return normalized || fallback;
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
function normalizeAgentId(agentId) {
|
|
18
|
-
return normalizeText(agentId, null);
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
function normalizeInteger(value, fallback = 0) {
|
|
22
|
-
const parsed = Number(value);
|
|
23
|
-
if (!Number.isFinite(parsed)) return fallback;
|
|
24
|
-
return Math.max(0, Math.trunc(parsed));
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
function normalizeStringList(value) {
|
|
28
|
-
if (!Array.isArray(value)) return [];
|
|
29
|
-
return [...new Set(
|
|
30
|
-
value
|
|
31
|
-
.map((entry) => normalizeText(entry, null)?.toLowerCase() || null)
|
|
32
|
-
.filter(Boolean),
|
|
33
|
-
)];
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
function normalizeComparableText(value) {
|
|
37
|
-
return normalizeText(value, null)?.toLowerCase() || null;
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
function tokenizeText(value) {
|
|
41
|
-
return [...new Set(
|
|
42
|
-
String(value || '')
|
|
43
|
-
.toLowerCase()
|
|
44
|
-
.split(/[^a-z0-9\u4e00-\u9fff]+/i)
|
|
45
|
-
.map((entry) => entry.trim())
|
|
46
|
-
.filter((entry) => entry.length >= 2),
|
|
47
|
-
)];
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
function normalizeSearchLimit(limit, fallback = 10) {
|
|
51
|
-
const normalized = normalizeInteger(limit, fallback);
|
|
52
|
-
if (normalized <= 0) return fallback;
|
|
53
|
-
return Math.min(normalized, 25);
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
function createConfigurationError() {
|
|
57
|
-
const error = new Error('membership_store_unavailable');
|
|
58
|
-
error.code = 'membership_store_unavailable';
|
|
59
|
-
error.status = 500;
|
|
60
|
-
return error;
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
function createInvalidSearchRequestError(fieldId, message = `${fieldId} is required`) {
|
|
64
|
-
const error = new Error(`invalid_search_request:${fieldId}`);
|
|
65
|
-
error.code = 'invalid_search_request';
|
|
66
|
-
error.status = 400;
|
|
67
|
-
error.responseBody = {
|
|
68
|
-
error: error.code,
|
|
69
|
-
message: 'world search request is missing required fields',
|
|
70
|
-
fieldErrors: [
|
|
71
|
-
{
|
|
72
|
-
fieldId,
|
|
73
|
-
message,
|
|
74
|
-
},
|
|
75
|
-
],
|
|
76
|
-
};
|
|
77
|
-
return error;
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
function createAgentNotFoundError(agentId) {
|
|
81
|
-
const error = new Error(`agent_not_found:${agentId}`);
|
|
82
|
-
error.code = 'agent_not_found';
|
|
83
|
-
error.status = 404;
|
|
84
|
-
return error;
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
function createSearchMembershipNotActiveError(worldId, agentId) {
|
|
88
|
-
const error = new Error(`world_search_membership_not_active:${worldId}:${agentId}`);
|
|
89
|
-
error.code = 'world_search_membership_not_active';
|
|
90
|
-
error.status = 409;
|
|
91
|
-
error.responseBody = {
|
|
92
|
-
error: error.code,
|
|
93
|
-
message: 'agent must have an active world membership before searching the world',
|
|
94
|
-
worldId,
|
|
95
|
-
agentId,
|
|
96
|
-
requiredMembershipStatus: 'active',
|
|
97
|
-
};
|
|
98
|
-
return error;
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
function normalizeQuery(world, query = {}, viewerProfile = {}) {
|
|
102
|
-
const baseProfile = viewerProfile && typeof viewerProfile === 'object' ? viewerProfile : {};
|
|
103
|
-
const overrides = query && typeof query === 'object' ? query : {};
|
|
104
|
-
|
|
105
|
-
return Object.fromEntries(
|
|
106
|
-
world.searchSchema.inputFields
|
|
107
|
-
.map((field) => {
|
|
108
|
-
const hasOverride = Object.prototype.hasOwnProperty.call(overrides, field.fieldId);
|
|
109
|
-
const value = hasOverride ? overrides[field.fieldId] : baseProfile[field.fieldId];
|
|
110
|
-
return [field.fieldId, value];
|
|
111
|
-
})
|
|
112
|
-
.filter(([, value]) => !isEmptyValue(value)),
|
|
113
|
-
);
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
function resolveFieldWeight(worldId, field) {
|
|
117
|
-
const fieldId = field.fieldId;
|
|
118
|
-
if (worldId === 'dating-demo-world') {
|
|
119
|
-
if (fieldId === 'intent') return { exact: 50, overlapUnit: 10, overlapCap: 20 };
|
|
120
|
-
if (fieldId === 'location') return { exact: 30, overlapUnit: 10, overlapCap: 20 };
|
|
121
|
-
if (fieldId === 'interests') return { exact: 20, overlapUnit: 10, overlapCap: 20 };
|
|
122
|
-
}
|
|
123
|
-
if (worldId === 'skill-handoff-world') {
|
|
124
|
-
if (fieldId === 'capabilities') return { exact: 50, overlapUnit: 10, overlapCap: 50 };
|
|
125
|
-
if (fieldId === 'budgetBand') return { exact: 20, overlapUnit: 10, overlapCap: 20 };
|
|
126
|
-
}
|
|
127
|
-
if (worldId === 'job-match-world') {
|
|
128
|
-
if (fieldId === 'targetRole') return { exact: 50, overlapUnit: 10, overlapCap: 20 };
|
|
129
|
-
if (fieldId === 'location') return { exact: 15, overlapUnit: 10, overlapCap: 20 };
|
|
130
|
-
if (fieldId === 'workMode') return { exact: 15, overlapUnit: 10, overlapCap: 20 };
|
|
131
|
-
if (fieldId === 'experienceSummary') return { exact: 20, overlapUnit: 10, overlapCap: 20 };
|
|
132
|
-
}
|
|
133
|
-
|
|
134
|
-
return {
|
|
135
|
-
exact: field.required ? 30 : 15,
|
|
136
|
-
overlapUnit: field.required ? 10 : 6,
|
|
137
|
-
overlapCap: field.required ? 20 : 12,
|
|
138
|
-
};
|
|
139
|
-
}
|
|
140
|
-
|
|
141
|
-
function compareField(field, queryValue, candidateValue, worldId) {
|
|
142
|
-
if (isEmptyValue(queryValue) || isEmptyValue(candidateValue)) return null;
|
|
143
|
-
|
|
144
|
-
const weights = resolveFieldWeight(worldId, field);
|
|
145
|
-
|
|
146
|
-
if (field.fieldId === 'participantContextText') {
|
|
147
|
-
const queryTokens = tokenizeText(queryValue);
|
|
148
|
-
const candidateTokens = tokenizeText(candidateValue);
|
|
149
|
-
const sharedValues = queryTokens.filter((entry) => candidateTokens.includes(entry));
|
|
150
|
-
if (sharedValues.length === 0) return null;
|
|
151
|
-
|
|
152
|
-
return {
|
|
153
|
-
fieldId: field.fieldId,
|
|
154
|
-
label: field.label,
|
|
155
|
-
matchType: 'overlap',
|
|
156
|
-
queryValue: normalizeText(queryValue, ''),
|
|
157
|
-
candidateValue: normalizeText(candidateValue, ''),
|
|
158
|
-
sharedValues,
|
|
159
|
-
contribution: Math.min(sharedValues.length * 8, 48),
|
|
160
|
-
};
|
|
161
|
-
}
|
|
162
|
-
|
|
163
|
-
if (field.type === 'string[]') {
|
|
164
|
-
const queryItems = normalizeStringList(queryValue);
|
|
165
|
-
const candidateItems = normalizeStringList(candidateValue);
|
|
166
|
-
const sharedValues = queryItems.filter((entry) => candidateItems.includes(entry));
|
|
167
|
-
if (sharedValues.length === 0) return null;
|
|
168
|
-
|
|
169
|
-
return {
|
|
170
|
-
fieldId: field.fieldId,
|
|
171
|
-
label: field.label,
|
|
172
|
-
matchType: 'overlap',
|
|
173
|
-
queryValue: queryItems,
|
|
174
|
-
candidateValue: candidateItems,
|
|
175
|
-
sharedValues,
|
|
176
|
-
contribution: Math.min(sharedValues.length * weights.overlapUnit, weights.overlapCap),
|
|
177
|
-
};
|
|
178
|
-
}
|
|
179
|
-
|
|
180
|
-
const normalizedQueryValue = normalizeComparableText(queryValue);
|
|
181
|
-
const normalizedCandidateValue = normalizeComparableText(candidateValue);
|
|
182
|
-
if (!normalizedQueryValue || !normalizedCandidateValue) return null;
|
|
183
|
-
if (normalizedQueryValue !== normalizedCandidateValue) return null;
|
|
184
|
-
|
|
185
|
-
return {
|
|
186
|
-
fieldId: field.fieldId,
|
|
187
|
-
label: field.label,
|
|
188
|
-
matchType: 'exact',
|
|
189
|
-
queryValue: normalizeText(queryValue, ''),
|
|
190
|
-
candidateValue: normalizeText(candidateValue, ''),
|
|
191
|
-
sharedValues: [],
|
|
192
|
-
contribution: weights.exact,
|
|
193
|
-
};
|
|
194
|
-
}
|
|
195
|
-
|
|
196
|
-
function projectSummaryField(field, profile = {}) {
|
|
197
|
-
const value = profile[field.fieldId];
|
|
198
|
-
if (isEmptyValue(value)) return null;
|
|
199
|
-
|
|
200
|
-
return {
|
|
201
|
-
fieldId: field.fieldId,
|
|
202
|
-
label: field.label,
|
|
203
|
-
value: Array.isArray(value) ? value : normalizeText(value, null),
|
|
204
|
-
};
|
|
205
|
-
}
|
|
206
|
-
|
|
207
|
-
function projectProfileSummary(world, profile = {}, agent = null) {
|
|
208
|
-
return {
|
|
209
|
-
displayName: normalizeText(agent?.displayName, null),
|
|
210
|
-
headline: normalizeText(profile.participantContextText, normalizeText(profile.headline, null)),
|
|
211
|
-
requiredFields: world.joinSchema.requiredFields
|
|
212
|
-
.map((field) => projectSummaryField(field, profile))
|
|
213
|
-
.filter(Boolean),
|
|
214
|
-
optionalFields: world.joinSchema.optionalFields
|
|
215
|
-
.map((field) => projectSummaryField(field, profile))
|
|
216
|
-
.filter(Boolean),
|
|
217
|
-
};
|
|
218
|
-
}
|
|
219
|
-
|
|
220
|
-
function resolveSearchResultAgentId(item = {}, fallback = null) {
|
|
221
|
-
return normalizeText(item.agentId, normalizeText(item.playerId, fallback));
|
|
222
|
-
}
|
|
223
|
-
|
|
224
|
-
function compareSearchResults(left, right) {
|
|
225
|
-
if (right.score !== left.score) return right.score - left.score;
|
|
226
|
-
if (right.matchedFieldIds.length !== left.matchedFieldIds.length) {
|
|
227
|
-
return right.matchedFieldIds.length - left.matchedFieldIds.length;
|
|
228
|
-
}
|
|
229
|
-
if (left.joinedAt !== right.joinedAt) {
|
|
230
|
-
return String(left.joinedAt).localeCompare(String(right.joinedAt));
|
|
231
|
-
}
|
|
232
|
-
return String(resolveSearchResultAgentId(left, '')).localeCompare(String(resolveSearchResultAgentId(right, '')));
|
|
233
|
-
}
|
|
234
|
-
|
|
235
|
-
function compareRecentFallbackResults(left, right) {
|
|
236
|
-
if (left.lastHeartbeatAt !== right.lastHeartbeatAt) {
|
|
237
|
-
return String(right.lastHeartbeatAt || '').localeCompare(String(left.lastHeartbeatAt || ''));
|
|
238
|
-
}
|
|
239
|
-
if (left.joinedAt !== right.joinedAt) {
|
|
240
|
-
return String(right.joinedAt || '').localeCompare(String(left.joinedAt || ''));
|
|
241
|
-
}
|
|
242
|
-
return String(resolveSearchResultAgentId(left, '')).localeCompare(String(resolveSearchResultAgentId(right, '')));
|
|
243
|
-
}
|
|
244
|
-
|
|
245
|
-
function summarizeMatchedFields(matchedFields = []) {
|
|
246
|
-
if (matchedFields.length === 0) {
|
|
247
|
-
return 'Recently active online world member with no direct profile overlap in the current query.';
|
|
248
|
-
}
|
|
249
|
-
|
|
250
|
-
return `Matched on ${matchedFields.map((field) => field.label).join(', ')}.`;
|
|
251
|
-
}
|
|
252
|
-
|
|
253
|
-
export function createWorldSearchService({
|
|
254
|
-
worldService,
|
|
255
|
-
worldAuthorizationService,
|
|
256
|
-
store = null,
|
|
257
|
-
presence = null,
|
|
258
|
-
conversationFeedbackService = null,
|
|
259
|
-
} = {}) {
|
|
260
|
-
function assertStore() {
|
|
261
|
-
if (!store) throw createConfigurationError();
|
|
262
|
-
return store;
|
|
263
|
-
}
|
|
264
|
-
|
|
265
|
-
function resolvePresence(agentId) {
|
|
266
|
-
if (!presence) {
|
|
267
|
-
return {
|
|
268
|
-
online: true,
|
|
269
|
-
connectedAt: null,
|
|
270
|
-
lastHeartbeatAt: null,
|
|
271
|
-
};
|
|
272
|
-
}
|
|
273
|
-
return presence.getPresence(agentId);
|
|
274
|
-
}
|
|
275
|
-
|
|
276
|
-
function buildViewerContext(world, membershipStore, normalizedAgentId) {
|
|
277
|
-
const viewerAgent = membershipStore.getAgent(normalizedAgentId);
|
|
278
|
-
if (!viewerAgent) {
|
|
279
|
-
throw createAgentNotFoundError(normalizedAgentId);
|
|
280
|
-
}
|
|
281
|
-
|
|
282
|
-
const authorization = worldAuthorizationService.evaluateWorldAction({
|
|
283
|
-
worldId: world.worldId,
|
|
284
|
-
actorAgentId: normalizedAgentId,
|
|
285
|
-
action: WORLD_ACTIONS.SEARCH,
|
|
286
|
-
});
|
|
287
|
-
|
|
288
|
-
if (!authorization.allowed || authorization.membership?.status !== 'active') {
|
|
289
|
-
throw createSearchMembershipNotActiveError(world.worldId, normalizedAgentId);
|
|
290
|
-
}
|
|
291
|
-
|
|
292
|
-
return {
|
|
293
|
-
viewerAgent,
|
|
294
|
-
viewerMembership: authorization.membership,
|
|
295
|
-
};
|
|
296
|
-
}
|
|
297
|
-
|
|
298
|
-
return {
|
|
299
|
-
describeSearch(worldId) {
|
|
300
|
-
const world = worldService.requireWorld(worldId);
|
|
301
|
-
return projectSearchModel(world);
|
|
302
|
-
},
|
|
303
|
-
searchWorld({ worldId, agentId, query = {}, limit = null } = {}) {
|
|
304
|
-
const world = worldService.requireWorld(worldId);
|
|
305
|
-
const membershipStore = assertStore();
|
|
306
|
-
const normalizedAgentId = normalizeAgentId(agentId);
|
|
307
|
-
if (!normalizedAgentId) {
|
|
308
|
-
throw createInvalidSearchRequestError('agentId');
|
|
309
|
-
}
|
|
310
|
-
|
|
311
|
-
const { viewerAgent, viewerMembership } = buildViewerContext(world, membershipStore, normalizedAgentId);
|
|
312
|
-
const searchInput = normalizeQuery(world, query, viewerMembership.profileSnapshot || viewerAgent.profile || {});
|
|
313
|
-
const normalizedLimit = normalizeSearchLimit(limit, world.searchSchema.defaultLimit);
|
|
314
|
-
const onlineOnly = world.searchSchema.onlineOnly;
|
|
315
|
-
const activeMemberships = membershipStore.listMemberships({
|
|
316
|
-
worldId: world.worldId,
|
|
317
|
-
status: 'active',
|
|
318
|
-
});
|
|
319
|
-
|
|
320
|
-
const items = activeMemberships
|
|
321
|
-
.filter((membership) => membership.agentId !== normalizedAgentId)
|
|
322
|
-
.map((membership) => {
|
|
323
|
-
const candidateAgent = membershipStore.getAgent(membership.agentId);
|
|
324
|
-
if (!candidateAgent) return null;
|
|
325
|
-
|
|
326
|
-
const presenceState = resolvePresence(candidateAgent.agentId);
|
|
327
|
-
if (onlineOnly && presenceState.online !== true) return null;
|
|
328
|
-
|
|
329
|
-
const matchedFields = world.searchSchema.inputFields
|
|
330
|
-
.map((field) => compareField(
|
|
331
|
-
field,
|
|
332
|
-
searchInput[field.fieldId],
|
|
333
|
-
membership.profileSnapshot?.[field.fieldId],
|
|
334
|
-
world.worldId,
|
|
335
|
-
))
|
|
336
|
-
.filter(Boolean);
|
|
337
|
-
|
|
338
|
-
return {
|
|
339
|
-
agentId: candidateAgent.agentId,
|
|
340
|
-
playerId: candidateAgent.agentId,
|
|
341
|
-
membershipId: membership.membershipId,
|
|
342
|
-
worldId: world.worldId,
|
|
343
|
-
displayName: normalizeText(candidateAgent.displayName, candidateAgent.agentId),
|
|
344
|
-
headline: normalizeText(membership.profileSnapshot?.headline, null),
|
|
345
|
-
online: presenceState.online === true,
|
|
346
|
-
connectedAt: presenceState.connectedAt,
|
|
347
|
-
lastHeartbeatAt: presenceState.lastHeartbeatAt,
|
|
348
|
-
score: matchedFields.reduce((sum, field) => sum + field.contribution, 0),
|
|
349
|
-
matchedFieldIds: matchedFields.map((field) => field.fieldId),
|
|
350
|
-
matchedFields,
|
|
351
|
-
resultType: matchedFields.length > 0 ? 'matched' : 'fallback',
|
|
352
|
-
reasonSummary: summarizeMatchedFields(matchedFields),
|
|
353
|
-
joinedAt: membership.joinedAt,
|
|
354
|
-
profileSummary: projectProfileSummary(world, membership.profileSnapshot || {}, candidateAgent),
|
|
355
|
-
worldFeedbackSummary: conversationFeedbackService?.summarizeWorldAgent?.({
|
|
356
|
-
worldId: world.worldId,
|
|
357
|
-
agentId: candidateAgent.agentId,
|
|
358
|
-
}) || {
|
|
359
|
-
likesReceived: 0,
|
|
360
|
-
dislikesReceived: 0,
|
|
361
|
-
},
|
|
362
|
-
};
|
|
363
|
-
})
|
|
364
|
-
.filter(Boolean);
|
|
365
|
-
|
|
366
|
-
const matchedItems = items
|
|
367
|
-
.filter((item) => item.resultType === 'matched')
|
|
368
|
-
.sort(compareSearchResults);
|
|
369
|
-
const fallbackItems = items
|
|
370
|
-
.filter((item) => item.resultType === 'fallback')
|
|
371
|
-
.sort(compareRecentFallbackResults);
|
|
372
|
-
const orderedItems = [...matchedItems, ...fallbackItems];
|
|
373
|
-
|
|
374
|
-
return {
|
|
375
|
-
worldId: world.worldId,
|
|
376
|
-
agentId: normalizedAgentId,
|
|
377
|
-
viewerMembershipId: viewerMembership.membershipId,
|
|
378
|
-
searchModel: projectSearchModel(world),
|
|
379
|
-
searchInput,
|
|
380
|
-
onlineOnly,
|
|
381
|
-
candidateSource: onlineOnly ? 'active_memberships_online' : 'active_memberships',
|
|
382
|
-
limit: normalizedLimit,
|
|
383
|
-
totalMatches: orderedItems.length,
|
|
384
|
-
items: orderedItems.slice(0, normalizedLimit),
|
|
385
|
-
nextAction: orderedItems.length > 0 ? 'request_chat_with_selected_candidate' : 'broaden_search_or_wait',
|
|
386
|
-
status: orderedItems.length > 0 ? 'search_ready' : 'no_matches',
|
|
387
|
-
};
|
|
388
|
-
},
|
|
389
|
-
searchPlayers(options = {}) {
|
|
390
|
-
return this.searchWorld(options);
|
|
391
|
-
},
|
|
392
|
-
};
|
|
393
|
-
}
|