@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.
Files changed (69) hide show
  1. package/README.md +60 -0
  2. package/bin/claworld.mjs +9 -0
  3. package/index.js +51 -0
  4. package/openclaw.plugin.json +470 -0
  5. package/package.json +76 -0
  6. package/setup-entry.js +6 -0
  7. package/src/lib/accepted-chat-kickoff.js +192 -0
  8. package/src/lib/agent-address.js +46 -0
  9. package/src/lib/agent-profile.js +69 -0
  10. package/src/lib/http-auth.js +151 -0
  11. package/src/lib/policy.js +118 -0
  12. package/src/lib/runtime-errors.js +149 -0
  13. package/src/lib/runtime-guidance.js +458 -0
  14. package/src/openclaw/index.js +53 -0
  15. package/src/openclaw/installer/cli.js +349 -0
  16. package/src/openclaw/installer/constants.js +6 -0
  17. package/src/openclaw/installer/core.js +1548 -0
  18. package/src/openclaw/installer/doctor.js +690 -0
  19. package/src/openclaw/installer/workspace-contract.js +403 -0
  20. package/src/openclaw/plugin/account-identity.js +66 -0
  21. package/src/openclaw/plugin/claworld-channel-plugin.js +3118 -0
  22. package/src/openclaw/plugin/config-schema.js +464 -0
  23. package/src/openclaw/plugin/lifecycle.js +114 -0
  24. package/src/openclaw/plugin/managed-config.js +648 -0
  25. package/src/openclaw/plugin/onboarding.js +291 -0
  26. package/src/openclaw/plugin/register.js +961 -0
  27. package/src/openclaw/plugin/relay-client.js +783 -0
  28. package/src/openclaw/plugin/runtime.js +12 -0
  29. package/src/openclaw/protocol/relay-event-protocol.js +31 -0
  30. package/src/openclaw/runtime/canonical-result-builder.js +116 -0
  31. package/src/openclaw/runtime/demo-session-bootstrap.js +37 -0
  32. package/src/openclaw/runtime/feedback-helper.js +145 -0
  33. package/src/openclaw/runtime/inbound-session-router.js +36 -0
  34. package/src/openclaw/runtime/outbound-session-bridge.js +17 -0
  35. package/src/openclaw/runtime/product-shell-helper.js +1712 -0
  36. package/src/openclaw/runtime/runtime-path.js +19 -0
  37. package/src/openclaw/runtime/system-message-orchestrator.js +1 -0
  38. package/src/openclaw/runtime/tool-contracts.js +714 -0
  39. package/src/openclaw/runtime/tool-inventory.js +92 -0
  40. package/src/openclaw/runtime/world-moderation-helper.js +415 -0
  41. package/src/openclaw/runtime/world-session-startup.js +1 -0
  42. package/src/product-shell/catalog/default-world-catalog.js +296 -0
  43. package/src/product-shell/contracts/candidate-feed.js +330 -0
  44. package/src/product-shell/contracts/chat-request-approval-policy.js +98 -0
  45. package/src/product-shell/contracts/world-manifest.js +435 -0
  46. package/src/product-shell/contracts/world-orchestration.js +1024 -0
  47. package/src/product-shell/feedback/feedback-contract.js +13 -0
  48. package/src/product-shell/feedback/feedback-routes.js +98 -0
  49. package/src/product-shell/feedback/feedback-service.js +254 -0
  50. package/src/product-shell/index.js +163 -0
  51. package/src/product-shell/matching/matchmaking-service.js +340 -0
  52. package/src/product-shell/membership/membership-service.js +277 -0
  53. package/src/product-shell/onboarding/onboarding-routes.js +37 -0
  54. package/src/product-shell/onboarding/onboarding-service.js +230 -0
  55. package/src/product-shell/orchestration/session-orchestrator.js +38 -0
  56. package/src/product-shell/results/result-service.js +15 -0
  57. package/src/product-shell/search/search-service.js +359 -0
  58. package/src/product-shell/social/chat-request-approval-policy.js +332 -0
  59. package/src/product-shell/social/chat-request-routes.js +108 -0
  60. package/src/product-shell/social/chat-request-service.js +632 -0
  61. package/src/product-shell/social/friend-routes.js +82 -0
  62. package/src/product-shell/social/friend-service.js +560 -0
  63. package/src/product-shell/social/social-routes.js +21 -0
  64. package/src/product-shell/social/social-service.js +140 -0
  65. package/src/product-shell/worlds/world-admin-service.js +705 -0
  66. package/src/product-shell/worlds/world-authorization.js +135 -0
  67. package/src/product-shell/worlds/world-broadcast-service.js +299 -0
  68. package/src/product-shell/worlds/world-routes.js +410 -0
  69. package/src/product-shell/worlds/world-service.js +89 -0
@@ -0,0 +1,632 @@
1
+ import { normalizeAgentProfile, resolveAgentDisplayName, resolveAgentVisibility } from '../../lib/agent-profile.js';
2
+ import { createKickoffBrief, resolveStoredKickoffBrief } from '../../lib/accepted-chat-kickoff.js';
3
+ import { WORLD_ACTIONS } from '../worlds/world-authorization.js';
4
+ import { normalizeChatRequestApprovalPolicy } from '../contracts/chat-request-approval-policy.js';
5
+ import {
6
+ createStoredChatRequestApprovalPolicy,
7
+ evaluateChatRequestApprovalPolicy,
8
+ } from './chat-request-approval-policy.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 normalizeDirection(direction) {
17
+ const normalized = normalizeText(direction, null);
18
+ return normalized === 'inbound' || normalized === 'outbound' ? normalized : null;
19
+ }
20
+
21
+ function normalizePositiveInteger(value, fallback = null) {
22
+ const normalized = Number(value);
23
+ if (!Number.isFinite(normalized) || normalized <= 0) return fallback;
24
+ return Math.max(1, Math.trunc(normalized));
25
+ }
26
+
27
+ function cloneJsonObject(value) {
28
+ if (!value || typeof value !== 'object' || Array.isArray(value)) return null;
29
+ try {
30
+ const cloned = JSON.parse(JSON.stringify(value));
31
+ if (!cloned || typeof cloned !== 'object' || Array.isArray(cloned)) return null;
32
+ return cloned;
33
+ } catch {
34
+ return null;
35
+ }
36
+ }
37
+
38
+ function normalizeConversationWorldId(value) {
39
+ return normalizeText(value, null);
40
+ }
41
+
42
+ function parseEpisodePolicy(episodePolicy = null) {
43
+ if (episodePolicy == null) return { value: null, error: null };
44
+ if (!episodePolicy || typeof episodePolicy !== 'object' || Array.isArray(episodePolicy)) {
45
+ return {
46
+ value: null,
47
+ error: {
48
+ code: 'chat_request_episode_policy_invalid',
49
+ message: 'episodePolicy must be an object',
50
+ },
51
+ };
52
+ }
53
+
54
+ const hasMaxTurns = Object.prototype.hasOwnProperty.call(episodePolicy, 'maxTurns');
55
+ const hasTurnTimeoutMs = Object.prototype.hasOwnProperty.call(episodePolicy, 'turnTimeoutMs');
56
+ const hasRaiseHandPolicy = Object.prototype.hasOwnProperty.call(episodePolicy, 'raiseHandPolicy');
57
+
58
+ const maxTurns = !hasMaxTurns
59
+ ? null
60
+ : episodePolicy.maxTurns === null
61
+ ? null
62
+ : normalizePositiveInteger(episodePolicy.maxTurns, null);
63
+ if (hasMaxTurns && episodePolicy.maxTurns != null && maxTurns == null) {
64
+ return {
65
+ value: null,
66
+ error: {
67
+ code: 'chat_request_episode_policy_invalid',
68
+ message: 'episodePolicy.maxTurns must be a positive integer',
69
+ },
70
+ };
71
+ }
72
+
73
+ const turnTimeoutMs = !hasTurnTimeoutMs
74
+ ? null
75
+ : episodePolicy.turnTimeoutMs === null
76
+ ? null
77
+ : normalizePositiveInteger(episodePolicy.turnTimeoutMs, null);
78
+ if (hasTurnTimeoutMs && episodePolicy.turnTimeoutMs != null && turnTimeoutMs == null) {
79
+ return {
80
+ value: null,
81
+ error: {
82
+ code: 'chat_request_episode_policy_invalid',
83
+ message: 'episodePolicy.turnTimeoutMs must be a positive integer',
84
+ },
85
+ };
86
+ }
87
+
88
+ if (
89
+ hasRaiseHandPolicy
90
+ && episodePolicy.raiseHandPolicy != null
91
+ && (
92
+ typeof episodePolicy.raiseHandPolicy !== 'object'
93
+ || Array.isArray(episodePolicy.raiseHandPolicy)
94
+ )
95
+ ) {
96
+ return {
97
+ value: null,
98
+ error: {
99
+ code: 'chat_request_episode_policy_invalid',
100
+ message: 'episodePolicy.raiseHandPolicy must be an object',
101
+ },
102
+ };
103
+ }
104
+
105
+ const raiseHandPolicy = cloneJsonObject(episodePolicy.raiseHandPolicy);
106
+ const value = {
107
+ ...(maxTurns != null ? { maxTurns } : {}),
108
+ ...(turnTimeoutMs != null ? { turnTimeoutMs } : {}),
109
+ ...(raiseHandPolicy ? { raiseHandPolicy } : {}),
110
+ };
111
+
112
+ return {
113
+ value: Object.keys(value).length > 0 ? value : null,
114
+ error: null,
115
+ };
116
+ }
117
+
118
+ function projectEpisodePolicy(conversation = {}) {
119
+ const parsed = parseEpisodePolicy(conversation);
120
+ return parsed.error ? null : parsed.value;
121
+ }
122
+
123
+ function sortByRecency(items = [], ...fieldNames) {
124
+ return items.slice().sort((left, right) => {
125
+ for (const fieldName of fieldNames) {
126
+ const rightTs = Date.parse(right?.[fieldName] || '');
127
+ const leftTs = Date.parse(left?.[fieldName] || '');
128
+ const normalizedRightTs = Number.isFinite(rightTs) ? rightTs : -Infinity;
129
+ const normalizedLeftTs = Number.isFinite(leftTs) ? leftTs : -Infinity;
130
+ if (normalizedRightTs !== normalizedLeftTs) return normalizedRightTs - normalizedLeftTs;
131
+ }
132
+ return String(right?.requestId || '').localeCompare(String(left?.requestId || ''));
133
+ });
134
+ }
135
+
136
+ function createConfigurationError(code = 'chat_request_service_unavailable') {
137
+ const error = new Error(code);
138
+ error.code = code;
139
+ error.status = 500;
140
+ return error;
141
+ }
142
+
143
+ function createAgentNotFoundError(agentId) {
144
+ const error = new Error(`agent_not_found:${agentId}`);
145
+ error.code = 'agent_not_found';
146
+ error.status = 404;
147
+ return error;
148
+ }
149
+
150
+ function createTargetAddressNotFoundError(targetAgentId) {
151
+ const error = new Error(`chat_request_target_not_found:${targetAgentId}`);
152
+ error.code = 'chat_request_target_not_found';
153
+ error.status = 404;
154
+ error.responseBody = {
155
+ error: error.code,
156
+ message: 'chat request target is not available',
157
+ targetAgentId: normalizeText(targetAgentId, null),
158
+ };
159
+ return error;
160
+ }
161
+
162
+ function createInvalidChatRequestError(code, message, extra = {}) {
163
+ const error = new Error(code);
164
+ error.code = code;
165
+ error.status = 400;
166
+ error.responseBody = {
167
+ error: code,
168
+ message,
169
+ ...extra,
170
+ };
171
+ return error;
172
+ }
173
+
174
+ function createWorldChatRequestMembershipNotActiveError(worldId, agentId) {
175
+ const error = new Error(`world_chat_request_membership_not_active:${worldId}:${agentId}`);
176
+ error.code = 'world_chat_request_membership_not_active';
177
+ error.status = 409;
178
+ error.responseBody = {
179
+ error: error.code,
180
+ message: 'agent must have an active world membership before creating a world-scoped chat request',
181
+ worldId: normalizeText(worldId, null),
182
+ agentId: normalizeText(agentId, null),
183
+ requiredMembershipStatus: 'active',
184
+ };
185
+ return error;
186
+ }
187
+
188
+ function createRelayResponseError(result = {}, fallbackCode = 'chat_request_failed') {
189
+ const code = normalizeText(result?.body?.error, fallbackCode);
190
+ const error = new Error(code);
191
+ error.code = code;
192
+ error.status = Number.isInteger(result?.status) ? result.status : 500;
193
+ error.responseBody = result?.body && typeof result.body === 'object'
194
+ ? result.body
195
+ : { error: code };
196
+ return error;
197
+ }
198
+
199
+ function projectAgent(store, agentId, presence = null) {
200
+ if (!store || !agentId) return null;
201
+ const agent = store.getAgent(agentId);
202
+ if (!agent) return null;
203
+ const visibility = resolveAgentVisibility(agent);
204
+ const presenceState = presence?.getPresence?.(agent.agentId) || store.getPresence(agent.agentId);
205
+ return {
206
+ agentId: agent.agentId,
207
+ agentCode: agent.agentCode,
208
+ domain: agent.domain,
209
+ address: agent.address,
210
+ displayName: resolveAgentDisplayName(agent),
211
+ profile: normalizeAgentProfile(agent.profile),
212
+ discoverable: visibility.discoverable,
213
+ contactable: visibility.contactable,
214
+ createdAt: agent.createdAt,
215
+ ...presenceState,
216
+ };
217
+ }
218
+
219
+ function projectWorldSummary(worldService, worldId) {
220
+ const normalizedWorldId = normalizeText(worldId, null);
221
+ if (!normalizedWorldId) return null;
222
+ const world = worldService?.getWorld?.(normalizedWorldId) || null;
223
+ if (!world) {
224
+ return {
225
+ worldId: normalizedWorldId,
226
+ slug: null,
227
+ displayName: null,
228
+ summary: null,
229
+ };
230
+ }
231
+ return {
232
+ worldId: world.worldId,
233
+ slug: world.slug || null,
234
+ displayName: world.displayName || null,
235
+ summary: world.summary || null,
236
+ };
237
+ }
238
+
239
+ function projectKickoffBrief(brief = null) {
240
+ if (!brief || typeof brief !== 'object' || Array.isArray(brief)) return null;
241
+ return {
242
+ text: normalizeText(brief.text, null),
243
+ payload: cloneJsonObject(brief.payload),
244
+ source: normalizeText(brief.source, 'chat_request_brief'),
245
+ };
246
+ }
247
+
248
+ function resolveKickoffBrief(request = {}, requestContext = {}) {
249
+ const directBrief = request.kickoffBrief && typeof request.kickoffBrief === 'object' && !Array.isArray(request.kickoffBrief)
250
+ ? request.kickoffBrief
251
+ : null;
252
+ return directBrief || resolveStoredKickoffBrief(requestContext);
253
+ }
254
+
255
+ function resolveOpeningMessage(request = {}, requestContext = {}) {
256
+ return normalizeText(
257
+ resolveKickoffBrief(request, requestContext)?.text,
258
+ normalizeText(
259
+ request?.openingMessage,
260
+ normalizeText(requestContext.openingPayload?.text, normalizeText(requestContext.message, null)),
261
+ ),
262
+ );
263
+ }
264
+
265
+ function projectKickoff(kickoff = {}) {
266
+ if (!kickoff || typeof kickoff !== 'object') return null;
267
+ return {
268
+ status: normalizeText(kickoff.status, 'skipped'),
269
+ deliveredAt: normalizeText(kickoff.deliveredAt, null),
270
+ reason: normalizeText(kickoff.reason, null),
271
+ };
272
+ }
273
+
274
+ function resolveAcceptNextAction(kickoff) {
275
+ const kickoffStatus = kickoff?.status || 'skipped';
276
+ if (kickoffStatus === 'sent') return 'runtime_owns_live_conversation';
277
+ if (kickoffStatus === 'failed') return 'backend_kickoff_failed';
278
+ return 'chat_request_accepted_without_opening_message';
279
+ }
280
+
281
+ function resolveCreateNextAction({ verdict = 'pending', kickoff = null } = {}) {
282
+ if (verdict === 'auto_accept') return resolveAcceptNextAction(kickoff);
283
+ if (verdict === 'reject') return 'request_rejected_by_policy';
284
+ return 'wait_for_peer_acceptance';
285
+ }
286
+
287
+ function normalizeChatRequestOrigin(origin = {}) {
288
+ if (!origin || typeof origin !== 'object' || Array.isArray(origin)) return null;
289
+ const type = normalizeText(origin.type, null);
290
+ const broadcastId = normalizeText(origin.broadcastId, null);
291
+ if (!type && !broadcastId) return null;
292
+ return {
293
+ ...(type ? { type } : {}),
294
+ ...(broadcastId ? { broadcastId } : {}),
295
+ };
296
+ }
297
+
298
+ function normalizeChatRequestBroadcastMetadata(broadcast = {}) {
299
+ if (!broadcast || typeof broadcast !== 'object' || Array.isArray(broadcast)) return null;
300
+ const broadcastId = normalizeText(broadcast.broadcastId, null);
301
+ if (!broadcastId) return null;
302
+ return {
303
+ broadcastId,
304
+ ...(normalizeText(broadcast.worldId, null) ? { worldId: normalizeText(broadcast.worldId, null) } : {}),
305
+ ...(normalizeText(broadcast.audience, null) ? { audience: normalizeText(broadcast.audience, null) } : {}),
306
+ ...(normalizeText(broadcast.senderRole, null) ? { senderRole: normalizeText(broadcast.senderRole, null) } : {}),
307
+ ...(normalizeText(broadcast.eligibility, null) ? { eligibility: normalizeText(broadcast.eligibility, null) } : {}),
308
+ ...(typeof broadcast.excludeSelf === 'boolean' ? { excludeSelf: broadcast.excludeSelf } : {}),
309
+ };
310
+ }
311
+
312
+ function projectStoredApprovalPolicy(record = {}) {
313
+ if (!record || typeof record !== 'object' || Array.isArray(record)) return null;
314
+ return {
315
+ agentId: normalizeText(record.agentId, null),
316
+ schemaVersion: Number.isInteger(record.schemaVersion) ? record.schemaVersion : null,
317
+ syncedAt: normalizeText(record.syncedAt, null),
318
+ credentialId: normalizeText(record.credentialId, null),
319
+ source: record.source && typeof record.source === 'object' && !Array.isArray(record.source)
320
+ ? {
321
+ channel: normalizeText(record.source.channel, null),
322
+ integration: normalizeText(record.source.integration, null),
323
+ accountId: normalizeText(record.source.accountId, null),
324
+ }
325
+ : {},
326
+ policy: normalizeChatRequestApprovalPolicy(record.policy || {}),
327
+ };
328
+ }
329
+
330
+ function projectChatRequestOrigin(request = {}) {
331
+ const directOrigin = request.origin && typeof request.origin === 'object' && !Array.isArray(request.origin)
332
+ ? request.origin
333
+ : null;
334
+ if (directOrigin) {
335
+ return {
336
+ type: normalizeText(directOrigin.type, normalizeText(request.source, 'chat_request')),
337
+ broadcastId: normalizeText(directOrigin.broadcastId, null),
338
+ };
339
+ }
340
+
341
+ const requestContext = request.requestContext && typeof request.requestContext === 'object' && !Array.isArray(request.requestContext)
342
+ ? request.requestContext
343
+ : {};
344
+ const origin = requestContext.origin && typeof requestContext.origin === 'object' && !Array.isArray(requestContext.origin)
345
+ ? requestContext.origin
346
+ : {};
347
+ const broadcast = requestContext.broadcast && typeof requestContext.broadcast === 'object' && !Array.isArray(requestContext.broadcast)
348
+ ? requestContext.broadcast
349
+ : {};
350
+ const broadcastId = normalizeText(origin.broadcastId, normalizeText(broadcast.broadcastId, null));
351
+ const source = normalizeText(request.source, null);
352
+ const type = normalizeText(origin.type, source === 'world_broadcast' ? 'world_broadcast' : 'chat_request');
353
+ return {
354
+ type,
355
+ broadcastId,
356
+ };
357
+ }
358
+
359
+ export function createChatRequestService({
360
+ store = null,
361
+ relay = null,
362
+ presence = null,
363
+ worldService = null,
364
+ worldAuthorizationService = null,
365
+ } = {}) {
366
+ function assertStore() {
367
+ if (!store) throw createConfigurationError('chat_request_store_unavailable');
368
+ return store;
369
+ }
370
+
371
+ function assertRelay(methodName) {
372
+ if (!relay || typeof relay[methodName] !== 'function') {
373
+ throw createConfigurationError('chat_request_relay_unavailable');
374
+ }
375
+ return relay;
376
+ }
377
+
378
+ function requireAgent(agentId) {
379
+ const normalizedAgentId = normalizeText(agentId, null);
380
+ if (!normalizedAgentId) throw createAgentNotFoundError(agentId);
381
+ const agent = assertStore().getAgent(normalizedAgentId);
382
+ if (!agent) throw createAgentNotFoundError(normalizedAgentId);
383
+ return agent;
384
+ }
385
+
386
+ function projectChatRequest(request = {}, viewerAgentId = null) {
387
+ const requestContext = request.requestContext && typeof request.requestContext === 'object' && !Array.isArray(request.requestContext)
388
+ ? request.requestContext
389
+ : {};
390
+ const conversation = request.conversation && typeof request.conversation === 'object' && !Array.isArray(request.conversation)
391
+ ? request.conversation
392
+ : {};
393
+ const worldId = normalizeConversationWorldId(conversation.worldId)
394
+ || normalizeConversationWorldId(requestContext.conversation?.worldId);
395
+ const episodePolicy = projectEpisodePolicy(conversation)
396
+ || projectEpisodePolicy(requestContext.conversation || {});
397
+ const counterpartyAgentId =
398
+ viewerAgentId === request.fromAgentId
399
+ ? request.toAgentId
400
+ : viewerAgentId === request.toAgentId
401
+ ? request.fromAgentId
402
+ : null;
403
+
404
+ return {
405
+ chatRequestId: normalizeText(request.chatRequestId || request.requestId, null),
406
+ status: normalizeText(request.status, 'pending'),
407
+ direction:
408
+ viewerAgentId === request.fromAgentId
409
+ ? 'outbound'
410
+ : viewerAgentId === request.toAgentId
411
+ ? 'inbound'
412
+ : 'related',
413
+ openingMessage: resolveOpeningMessage(request, requestContext),
414
+ kickoffBrief: projectKickoffBrief(resolveKickoffBrief(request, requestContext)),
415
+ createdAt: normalizeText(request.createdAt, null),
416
+ respondedAt: normalizeText(request.respondedAt, null),
417
+ expiresAt: normalizeText(request.expiresAt, null),
418
+ origin: projectChatRequestOrigin(request),
419
+ fromAgent: projectAgent(assertStore(), request.fromAgentId, presence),
420
+ toAgent: projectAgent(assertStore(), request.toAgentId, presence),
421
+ counterparty: projectAgent(assertStore(), counterpartyAgentId, presence),
422
+ conversation: {
423
+ mode: worldId ? 'world' : 'direct',
424
+ worldId,
425
+ world: projectWorldSummary(worldService, worldId),
426
+ ...(episodePolicy ? { episodePolicy } : {}),
427
+ },
428
+ };
429
+ }
430
+
431
+ return {
432
+ projectChatRequest,
433
+ async syncApprovalPolicy({
434
+ actorAgentId,
435
+ credentialId = null,
436
+ accountId = null,
437
+ approval = null,
438
+ } = {}) {
439
+ requireAgent(actorAgentId);
440
+ if (!assertStore().upsertChatRequestApprovalPolicy) {
441
+ throw createConfigurationError('chat_request_approval_policy_store_unavailable');
442
+ }
443
+
444
+ const record = await assertStore().upsertChatRequestApprovalPolicy(
445
+ createStoredChatRequestApprovalPolicy({
446
+ agentId: actorAgentId,
447
+ credentialId,
448
+ syncedAt: assertStore().now(),
449
+ policy: normalizeChatRequestApprovalPolicy(approval || {}),
450
+ source: {
451
+ channel: 'claworld',
452
+ integration: 'openclaw_plugin',
453
+ accountId,
454
+ },
455
+ }),
456
+ );
457
+
458
+ return {
459
+ status: 'synced',
460
+ approvalPolicy: projectStoredApprovalPolicy(record),
461
+ };
462
+ },
463
+ async createChatRequest({
464
+ fromAgentId,
465
+ targetAgentId,
466
+ kickoffBrief = null,
467
+ openingMessage = null,
468
+ openingPayload = null,
469
+ worldId = null,
470
+ episodePolicy = null,
471
+ origin = null,
472
+ broadcast = null,
473
+ source = 'chat_request',
474
+ } = {}) {
475
+ requireAgent(fromAgentId);
476
+ const normalizedTargetAgentId = normalizeText(targetAgentId, null);
477
+ if (!normalizedTargetAgentId) {
478
+ throw createInvalidChatRequestError('chat_request_target_required', 'chat request target requires targetAgentId');
479
+ }
480
+ const targetAgent = requireAgent(normalizedTargetAgentId);
481
+ const normalizedWorldId = normalizeConversationWorldId(worldId);
482
+ if (!targetAgent?.address) {
483
+ throw createTargetAddressNotFoundError(normalizedTargetAgentId);
484
+ }
485
+ const normalizedOpeningPayload = cloneJsonObject(openingPayload);
486
+ const normalizedKickoffBrief = createKickoffBrief({
487
+ text: typeof kickoffBrief === 'object' && kickoffBrief !== null && !Array.isArray(kickoffBrief)
488
+ ? kickoffBrief.text
489
+ : openingMessage,
490
+ payload: typeof kickoffBrief === 'object' && kickoffBrief !== null && !Array.isArray(kickoffBrief)
491
+ ? kickoffBrief.payload
492
+ : normalizedOpeningPayload,
493
+ source: typeof kickoffBrief === 'object' && kickoffBrief !== null && !Array.isArray(kickoffBrief)
494
+ ? kickoffBrief.source
495
+ : source === 'world_broadcast'
496
+ ? 'world_broadcast_brief'
497
+ : 'chat_request_brief',
498
+ });
499
+ const normalizedOrigin = normalizeChatRequestOrigin(origin);
500
+ const normalizedBroadcast = normalizeChatRequestBroadcastMetadata(broadcast);
501
+ const normalizedEpisodePolicy = parseEpisodePolicy(episodePolicy);
502
+ if (normalizedEpisodePolicy.error) {
503
+ throw createInvalidChatRequestError(
504
+ normalizedEpisodePolicy.error.code,
505
+ normalizedEpisodePolicy.error.message,
506
+ );
507
+ }
508
+ if (normalizedWorldId) {
509
+ if (normalizedEpisodePolicy.value) {
510
+ throw createInvalidChatRequestError(
511
+ 'world_chat_request_episode_policy_not_supported',
512
+ 'world-scoped chat requests inherit episode policy from the world template',
513
+ );
514
+ }
515
+ worldService?.requireWorld?.(normalizedWorldId);
516
+ if (normalizeText(source, 'chat_request') !== 'world_broadcast') {
517
+ const authorization = worldAuthorizationService.evaluateWorldAction({
518
+ worldId: normalizedWorldId,
519
+ actorAgentId: fromAgentId,
520
+ action: WORLD_ACTIONS.CREATE_CHAT_REQUEST,
521
+ });
522
+ if (!authorization.allowed || authorization.membership?.status !== 'active') {
523
+ throw createWorldChatRequestMembershipNotActiveError(normalizedWorldId, fromAgentId);
524
+ }
525
+ }
526
+ }
527
+
528
+ const result = await assertRelay('createChatRequest').createChatRequest({
529
+ fromAgentId,
530
+ toAddress: targetAgent.address,
531
+ kickoffBrief: normalizedKickoffBrief,
532
+ openingMessage: normalizeText(
533
+ normalizedKickoffBrief?.text,
534
+ normalizeText(openingMessage, null),
535
+ ),
536
+ openingPayload: cloneJsonObject(normalizedKickoffBrief?.payload) || normalizedOpeningPayload,
537
+ conversation: {
538
+ ...(normalizedWorldId ? { worldId: normalizedWorldId } : {}),
539
+ ...(normalizedEpisodePolicy.value || {}),
540
+ },
541
+ origin: normalizedOrigin,
542
+ broadcast: normalizedBroadcast,
543
+ source: normalizeText(source, 'chat_request'),
544
+ });
545
+ if (result.status < 200 || result.status >= 300) {
546
+ throw createRelayResponseError(result, 'chat_request_create_failed');
547
+ }
548
+
549
+ let storedRequest = result.body || {};
550
+ let kickoff = null;
551
+ const evaluation = evaluateChatRequestApprovalPolicy({
552
+ policyRecord: assertStore().getChatRequestApprovalPolicy?.(storedRequest.toAgentId) || null,
553
+ request: storedRequest,
554
+ store: assertStore(),
555
+ presence,
556
+ worldService,
557
+ });
558
+
559
+ if (evaluation.verdict === 'auto_accept') {
560
+ const accepted = await assertRelay('acceptChatRequest').acceptChatRequest(storedRequest.chatRequestId || storedRequest.requestId, {
561
+ actorAgentId: storedRequest.toAgentId,
562
+ });
563
+ if (accepted.status < 200 || accepted.status >= 300) {
564
+ throw createRelayResponseError(accepted, 'chat_request_auto_accept_failed');
565
+ }
566
+ storedRequest = accepted.body?.request || storedRequest;
567
+ kickoff = projectKickoff(accepted.body?.kickoff || storedRequest.kickoff);
568
+ } else if (evaluation.verdict === 'reject') {
569
+ const rejected = await assertRelay('rejectChatRequest').rejectChatRequest(storedRequest.chatRequestId || storedRequest.requestId, {
570
+ actorAgentId: storedRequest.toAgentId,
571
+ });
572
+ if (rejected.status < 200 || rejected.status >= 300) {
573
+ throw createRelayResponseError(rejected, 'chat_request_policy_reject_failed');
574
+ }
575
+ storedRequest = rejected.body || storedRequest;
576
+ }
577
+
578
+ return {
579
+ status: normalizeText(storedRequest.status, 'pending'),
580
+ verdict: evaluation.verdict,
581
+ reason: evaluation.reason,
582
+ chatRequest: projectChatRequest(storedRequest, fromAgentId),
583
+ ...(kickoff ? { kickoff } : {}),
584
+ nextAction: resolveCreateNextAction({
585
+ verdict: evaluation.verdict,
586
+ kickoff,
587
+ }),
588
+ };
589
+ },
590
+
591
+ listChatRequests({ agentId, direction = null } = {}) {
592
+ requireAgent(agentId);
593
+ const normalizedDirection = normalizeDirection(direction);
594
+ let items = assertStore().listChatRequests ? assertStore().listChatRequests() : [];
595
+ items = items.filter((request) => request.status === 'pending');
596
+ items = items.filter((request) => request.fromAgentId === agentId || request.toAgentId === agentId);
597
+ if (normalizedDirection === 'inbound') {
598
+ items = items.filter((request) => request.toAgentId === agentId);
599
+ } else if (normalizedDirection === 'outbound') {
600
+ items = items.filter((request) => request.fromAgentId === agentId);
601
+ }
602
+
603
+ return {
604
+ items: sortByRecency(items, 'createdAt').map((request) => projectChatRequest(request, agentId)),
605
+ };
606
+ },
607
+
608
+ async acceptChatRequest(chatRequestId, { actorAgentId } = {}) {
609
+ requireAgent(actorAgentId);
610
+ const normalizedChatRequestId = normalizeText(chatRequestId, null);
611
+ if (!normalizedChatRequestId) {
612
+ throw createInvalidChatRequestError('chat_request_id_required', 'chatRequestId is required');
613
+ }
614
+
615
+ const result = await assertRelay('acceptChatRequest').acceptChatRequest(normalizedChatRequestId, {
616
+ actorAgentId,
617
+ });
618
+ if (result.status < 200 || result.status >= 300) {
619
+ throw createRelayResponseError(result, 'chat_request_accept_failed');
620
+ }
621
+
622
+ const kickoff = projectKickoff(result.body?.kickoff || result.body?.request?.kickoff);
623
+ return {
624
+ status: 'accepted',
625
+ verdict: 'manual_accept',
626
+ chatRequest: projectChatRequest(result.body?.request || {}, actorAgentId),
627
+ kickoff,
628
+ nextAction: resolveAcceptNextAction(kickoff),
629
+ };
630
+ },
631
+ };
632
+ }