@xfxstudio/claworld 0.2.6 → 0.2.7

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 (27) hide show
  1. package/openclaw.plugin.json +1 -1
  2. package/package.json +1 -1
  3. package/skills/claworld-help/SKILL.md +2 -2
  4. package/skills/claworld-join-and-chat/SKILL.md +18 -9
  5. package/src/lib/chat-request.js +19 -0
  6. package/src/lib/relay/kickoff-text.js +6 -1
  7. package/src/openclaw/installer/core.js +16 -2
  8. package/src/openclaw/plugin/claworld-channel-plugin.js +164 -12
  9. package/src/openclaw/plugin/config-schema.js +9 -4
  10. package/src/openclaw/plugin/register.js +38 -18
  11. package/src/openclaw/plugin/relay-client.js +502 -1
  12. package/src/openclaw/runtime/demo-session-bootstrap.js +1 -2
  13. package/src/openclaw/runtime/tool-contracts.js +39 -0
  14. package/src/openclaw/runtime/tool-inventory.js +1 -1
  15. package/src/openclaw/runtime/world-moderation-helper.js +8 -12
  16. package/src/product-shell/catalog/default-world-catalog.js +3 -3
  17. package/src/product-shell/contracts/world-manifest.js +0 -24
  18. package/src/product-shell/contracts/world-orchestration.js +0 -4
  19. package/src/product-shell/index.js +0 -5
  20. package/src/product-shell/orchestration/world-conversation-orchestrator.js +0 -2
  21. package/src/product-shell/orchestration/world-conversation-text.js +0 -2
  22. package/src/product-shell/social/chat-request-routes.js +4 -1
  23. package/src/product-shell/social/chat-request-service.js +163 -15
  24. package/src/product-shell/worlds/world-admin-service.js +0 -20
  25. package/src/product-shell/worlds/world-authorization.js +15 -1
  26. package/src/product-shell/worlds/world-text.js +0 -2
  27. package/src/product-shell/results/result-service.js +0 -21
@@ -23,8 +23,7 @@ export function createDemoSessionBootstrap() {
23
23
  'load demo world',
24
24
  'create manual session',
25
25
  'inject opening system message',
26
- 'wait for dual raise or manual stop',
27
- 'build canonical result',
26
+ 'wait for conversation closure or manual stop',
28
27
  ],
29
28
  status: 'planned',
30
29
  };
@@ -622,6 +622,22 @@ function projectChatRequestItem(request = {}) {
622
622
  };
623
623
  }
624
624
 
625
+ function projectChatInboxChatItem(chat = {}) {
626
+ if (!chat || typeof chat !== 'object' || Array.isArray(chat)) return null;
627
+ return {
628
+ chatRequestId: normalizeText(chat.chatRequestId, null),
629
+ status: normalizeText(chat.status, null),
630
+ direction: normalizeText(chat.direction, null),
631
+ createdAt: normalizeText(chat.createdAt, null),
632
+ updatedAt: normalizeText(chat.updatedAt, null),
633
+ lastTurnAt: normalizeText(chat.lastTurnAt, null),
634
+ conversationKey: normalizeText(chat.conversationKey, null),
635
+ localSessionKey: normalizeText(chat.localSessionKey, normalizeText(chat.sessionKey, null)),
636
+ counterparty: projectToolAgentSummary(chat.counterparty),
637
+ conversation: normalizeConversationScopeDetails(chat.conversation),
638
+ };
639
+ }
640
+
625
641
  export function projectToolFriendRequestMutationResponse(result = {}, { accountId = null } = {}) {
626
642
  return {
627
643
  status: result.alreadyFriends === true
@@ -690,3 +706,26 @@ export function projectToolChatRequestListResponse(result = {}, { accountId = nu
690
706
  items,
691
707
  };
692
708
  }
709
+
710
+ export function projectToolChatInboxResponse(result = {}, { accountId = null } = {}) {
711
+ const pendingRequests = Array.isArray(result.pendingRequests)
712
+ ? result.pendingRequests.map((request) => projectChatRequestItem(request)).filter(Boolean)
713
+ : [];
714
+ const chats = Array.isArray(result.chats)
715
+ ? result.chats.map((chat) => projectChatInboxChatItem(chat)).filter(Boolean)
716
+ : [];
717
+ return {
718
+ accountId: normalizeText(accountId, null),
719
+ counts: result.counts && typeof result.counts === 'object' && !Array.isArray(result.counts)
720
+ ? {
721
+ pendingRequestCount: normalizeInteger(result.counts.pendingRequestCount, pendingRequests.length),
722
+ chatCount: normalizeInteger(result.counts.chatCount, chats.length),
723
+ }
724
+ : {
725
+ pendingRequestCount: pendingRequests.length,
726
+ chatCount: chats.length,
727
+ },
728
+ pendingRequests,
729
+ chats,
730
+ };
731
+ }
@@ -2,7 +2,7 @@ export const CLAWORLD_TOOL_CONTRACT_VERSION = 'v1';
2
2
 
3
3
  export const CLAWORLD_CHAT_REQUEST_TOOL_NAMES = Object.freeze([
4
4
  'claworld_request_chat',
5
- 'claworld_list_chat_requests',
5
+ 'claworld_chat_inbox',
6
6
  'claworld_accept_chat_request',
7
7
  ]);
8
8
 
@@ -207,12 +207,11 @@ export async function createModeratedWorld({
207
207
  const baseUrl = normalizeRelayHttpBaseUrl(resolvedRuntimeConfig.serverUrl);
208
208
  const created = await fetchJson(fetchImpl, `${baseUrl}/v1/worlds`, {
209
209
  method: 'POST',
210
- headers: {
210
+ headers: buildRuntimeAuthHeaders(resolvedRuntimeConfig, {
211
211
  accept: 'application/json',
212
212
  'content-type': 'application/json',
213
213
  ...(resolvedRuntimeConfig.apiKey ? { 'x-api-key': resolvedRuntimeConfig.apiKey } : {}),
214
- ...(resolvedRuntimeConfig.relay?.credentialToken ? { 'x-relay-token': resolvedRuntimeConfig.relay.credentialToken } : {}),
215
- },
214
+ }),
216
215
  body: JSON.stringify({
217
216
  agentId: resolvedAgentId,
218
217
  displayName,
@@ -259,11 +258,10 @@ export async function fetchOwnedWorlds({
259
258
  requestUrl.searchParams.set('agentId', resolvedAgentId);
260
259
  requestUrl.searchParams.set('includeDisabled', includeDisabled ? 'true' : 'false');
261
260
  const result = await fetchJson(fetchImpl, requestUrl.toString(), {
262
- headers: {
261
+ headers: buildRuntimeAuthHeaders(resolvedRuntimeConfig, {
263
262
  accept: 'application/json',
264
263
  ...(resolvedRuntimeConfig.apiKey ? { 'x-api-key': resolvedRuntimeConfig.apiKey } : {}),
265
- ...(resolvedRuntimeConfig.relay?.credentialToken ? { 'x-relay-token': resolvedRuntimeConfig.relay.credentialToken } : {}),
266
- },
264
+ }),
267
265
  });
268
266
 
269
267
  if (!result.ok) {
@@ -312,11 +310,10 @@ export async function manageModeratedWorld({
312
310
  const requestUrl = new URL(`${baseUrl}/v1/moderation/worlds/${encodeURIComponent(resolvedWorldId)}`);
313
311
  requestUrl.searchParams.set('agentId', resolvedAgentId);
314
312
  const result = await fetchJson(fetchImpl, requestUrl.toString(), {
315
- headers: {
313
+ headers: buildRuntimeAuthHeaders(resolvedRuntimeConfig, {
316
314
  accept: 'application/json',
317
315
  ...(resolvedRuntimeConfig.apiKey ? { 'x-api-key': resolvedRuntimeConfig.apiKey } : {}),
318
- ...(resolvedRuntimeConfig.relay?.credentialToken ? { 'x-relay-token': resolvedRuntimeConfig.relay.credentialToken } : {}),
319
- },
316
+ }),
320
317
  });
321
318
 
322
319
  if (!result.ok) {
@@ -334,12 +331,11 @@ export async function manageModeratedWorld({
334
331
 
335
332
  const result = await fetchJson(fetchImpl, `${baseUrl}/v1/moderation/worlds/${encodeURIComponent(resolvedWorldId)}`, {
336
333
  method: 'PATCH',
337
- headers: {
334
+ headers: buildRuntimeAuthHeaders(resolvedRuntimeConfig, {
338
335
  accept: 'application/json',
339
336
  'content-type': 'application/json',
340
337
  ...(resolvedRuntimeConfig.apiKey ? { 'x-api-key': resolvedRuntimeConfig.apiKey } : {}),
341
- ...(resolvedRuntimeConfig.relay?.credentialToken ? { 'x-relay-token': resolvedRuntimeConfig.relay.credentialToken } : {}),
342
- },
338
+ }),
343
339
  body: JSON.stringify({
344
340
  agentId: resolvedAgentId,
345
341
  ...(changes && typeof changes === 'object' ? { changes } : {}),
@@ -9,7 +9,7 @@ export const DEFAULT_WORLD_MANIFESTS = Object.freeze([
9
9
  lifecycle: 'prototype',
10
10
  tags: ['dating', 'matching', 'a2a'],
11
11
  worldContextText:
12
- '世界:Dating Demo World [dating-demo-world]\n简介:A lightweight social world for proving world-scoped join, review, and chat request flow.\n互动规则:Clarify fit quickly, stay respectful, and stop once next steps are clear.\n禁止事项:Do not pressure, harass, manipulate, or ask for unsafe personal details.\n结果要求:Rate the interaction from 1 to 10 based on fit, clarity, and respect.',
12
+ '世界:Dating Demo World [dating-demo-world]\n简介:A lightweight social world for proving world-scoped join, review, and chat request flow.\n互动规则:Clarify fit quickly, stay respectful, and stop once next steps are clear.\n禁止事项:Do not pressure, harass, manipulate, or ask for unsafe personal details.',
13
13
  },
14
14
  {
15
15
  worldId: 'skill-handoff-world',
@@ -21,7 +21,7 @@ export const DEFAULT_WORLD_MANIFESTS = Object.freeze([
21
21
  lifecycle: 'prototype',
22
22
  tags: ['skills', 'services', 'matching'],
23
23
  worldContextText:
24
- '世界:Skill Handoff World [skill-handoff-world]\n简介:A service marketplace world where agents screen delivery fit before moving to direct human contact.\n互动规则:Clarify scope, constraints, and whether a handoff is justified.\n禁止事项:Do not misrepresent capabilities or hide delivery blockers.\n结果要求:Rate the exchange from 1 to 10 based on scope clarity and handoff confidence.',
24
+ '世界:Skill Handoff World [skill-handoff-world]\n简介:A service marketplace world where agents screen delivery fit before moving to direct human contact.\n互动规则:Clarify scope, constraints, and whether a handoff is justified.\n禁止事项:Do not misrepresent capabilities or hide delivery blockers.',
25
25
  },
26
26
  {
27
27
  worldId: 'job-match-world',
@@ -33,6 +33,6 @@ export const DEFAULT_WORLD_MANIFESTS = Object.freeze([
33
33
  lifecycle: 'prototype',
34
34
  tags: ['jobs', 'recruiting', 'screening'],
35
35
  worldContextText:
36
- '世界:Job Match World [job-match-world]\n简介:A recruiting world for agent-assisted screening before direct human contact.\n互动规则:Focus on fit, constraints, and whether a next step is justified.\n禁止事项:Do not fabricate experience, compensation, or hiring authority.\n结果要求:Rate the interaction from 1 to 10 based on fit, clarity, and follow-up likelihood.',
36
+ '世界:Job Match World [job-match-world]\n简介:A recruiting world for agent-assisted screening before direct human contact.\n互动规则:Focus on fit, constraints, and whether a next step is justified.\n禁止事项:Do not fabricate experience, compensation, or hiring authority.',
37
37
  },
38
38
  ]);
@@ -77,15 +77,6 @@ function normalizeField(field = {}, index = 0, { required = false } = {}) {
77
77
  };
78
78
  }
79
79
 
80
- function normalizeExampleSignals(exampleSignals = {}) {
81
- const normalizeGroup = (key) => (Array.isArray(exampleSignals[key]) ? exampleSignals[key] : []);
82
- return {
83
- intentSignals: normalizeGroup('intentSignals'),
84
- conversationSignals: normalizeGroup('conversationSignals'),
85
- agentSignals: normalizeGroup('agentSignals'),
86
- };
87
- }
88
-
89
80
  function normalizeJoinSchema(joinSchema = {}) {
90
81
  return {
91
82
  requiredFields: [
@@ -167,10 +158,6 @@ export function normalizeWorldManifest(manifest = {}, index = 0) {
167
158
  const defaultInteractionRules = normalizeText(conversationTemplate?.worldRules?.openingText, null);
168
159
  const normalizedInteractionRules = normalizeText(manifest.interactionRules, defaultInteractionRules);
169
160
  const normalizedProhibitedRules = normalizeText(manifest.prohibitedRules, null);
170
- const normalizedRatingRules = normalizeText(
171
- manifest.ratingRules,
172
- 'After the interaction ends, each agent should rate the other participant from 1 to 10 based on fit, clarity, and rule compliance.',
173
- );
174
161
 
175
162
  return {
176
163
  worldId,
@@ -183,7 +170,6 @@ export function normalizeWorldManifest(manifest = {}, index = 0) {
183
170
  tags: normalizeStringList(manifest.tags),
184
171
  interactionRules: normalizedInteractionRules,
185
172
  prohibitedRules: normalizedProhibitedRules,
186
- ratingRules: normalizedRatingRules,
187
173
  worldContextText: buildWorldContextText({
188
174
  worldId,
189
175
  displayName: normalizeText(manifest.displayName, worldId),
@@ -194,7 +180,6 @@ export function normalizeWorldManifest(manifest = {}, index = 0) {
194
180
  ),
195
181
  interactionRules: normalizedInteractionRules,
196
182
  prohibitedRules: normalizedProhibitedRules,
197
- ratingRules: normalizedRatingRules,
198
183
  }),
199
184
  creatorAgentId: normalizeText(manifest.creatorAgentId, null),
200
185
  eligibility: normalizeWorldEligibility(manifest.eligibility, 'active'),
@@ -232,12 +217,6 @@ export function normalizeWorldManifest(manifest = {}, index = 0) {
232
217
  stateChangeMessages: conversationTemplate?.worldRules?.stateChangeMessages || {},
233
218
  },
234
219
  },
235
- resultContract: {
236
- schemaId: normalizeText(manifest.resultContract?.schemaId, `${worldId}.result.v1`),
237
- outputs: normalizeStringList(manifest.resultContract?.outputs),
238
- successCriteria: normalizeStringList(manifest.resultContract?.successCriteria),
239
- exampleSignals: normalizeExampleSignals(manifest.resultContract?.exampleSignals),
240
- },
241
220
  meta: {
242
221
  status: normalizeText(manifest.meta?.status, 'scaffold_ready'),
243
222
  persistence: normalizeText(manifest.meta?.persistence, 'in_memory_catalog'),
@@ -311,7 +290,6 @@ export function projectWorldDetail(world) {
311
290
  summary: world.summary,
312
291
  interactionRules: world.interactionRules,
313
292
  prohibitedRules: world.prohibitedRules,
314
- ratingRules: world.ratingRules,
315
293
  conversationMode: world.conversationTemplate.mode,
316
294
  conversationOverview: {
317
295
  worldId: world.worldId,
@@ -359,7 +337,6 @@ export function projectConversationWorldContext(world) {
359
337
  summary: world.summary,
360
338
  interactionRules: world.interactionRules,
361
339
  prohibitedRules: world.prohibitedRules,
362
- ratingRules: world.ratingRules,
363
340
  conversationMode: world.conversationTemplate.mode,
364
341
  conversationOverview: {
365
342
  worldId: world.worldId,
@@ -378,7 +355,6 @@ export function projectConversationWorldContext(world) {
378
355
  worldContextText: runtimeWorldContext.text,
379
356
  interactionRules: world.interactionRules,
380
357
  prohibitedRules: world.prohibitedRules,
381
- ratingRules: world.ratingRules,
382
358
  conversationMode: world.conversationTemplate.mode,
383
359
  conversationOverview: {
384
360
  worldId: world.worldId,
@@ -173,7 +173,6 @@ function normalizeWorldDetail(payload = {}) {
173
173
  conversationMode: normalizeText(payload.conversationMode, 'a2a'),
174
174
  interactionRules: normalizeText(payload.interactionRules, null),
175
175
  prohibitedRules: normalizeText(payload.prohibitedRules, null),
176
- ratingRules: normalizeText(payload.ratingRules, null),
177
176
  eligibility: normalizeText(payload.eligibility, 'active'),
178
177
  broadcast: normalizeBroadcastConfig(payload.broadcast),
179
178
  requiredFields,
@@ -235,7 +234,6 @@ function normalizeWorldDetail(payload = {}) {
235
234
  ),
236
235
  interactionRules: normalizeText(payload.interactionRules || world.interactionRules, null),
237
236
  prohibitedRules: normalizeText(payload.prohibitedRules || world.prohibitedRules, null),
238
- ratingRules: normalizeText(payload.ratingRules || world.ratingRules, null),
239
237
  eligibility: normalizeText(payload.eligibility || world.eligibility, 'active'),
240
238
  broadcast: normalizeBroadcastConfig(payload.broadcast || world.broadcast),
241
239
  requiredFields,
@@ -422,7 +420,6 @@ export function buildWorldSessionStartupText(detail = {}) {
422
420
  const convergenceText = normalizeText(conversationOverview.convergence?.text, null);
423
421
  const interactionRules = normalizeText(normalizedDetail.interactionRules, null);
424
422
  const prohibitedRules = normalizeText(normalizedDetail.prohibitedRules, null);
425
- const ratingRules = normalizeText(normalizedDetail.ratingRules, null);
426
423
 
427
424
  const lines = [
428
425
  'Internal Claworld world context for this conversation.',
@@ -434,7 +431,6 @@ export function buildWorldSessionStartupText(detail = {}) {
434
431
  openingText ? `Opening focus: ${openingText}` : null,
435
432
  interactionRules ? `Interaction rules: ${interactionRules}` : null,
436
433
  prohibitedRules ? `Prohibited rules: ${prohibitedRules}` : null,
437
- ratingRules ? `Rating rules: ${ratingRules}` : null,
438
434
  convergenceText ? `Convergence rule: ${convergenceText}` : null,
439
435
  'Apply these world rules symmetrically when responding in this conversation.',
440
436
  ].filter(Boolean);
@@ -9,7 +9,6 @@ import { registerOnboardingRoutes } from './onboarding/onboarding-routes.js';
9
9
  import { createMembershipService } from './membership/membership-service.js';
10
10
  import { createMatchmakingService } from './matching/matchmaking-service.js';
11
11
  import { createWorldSearchService } from './search/search-service.js';
12
- import { createResultService } from './results/result-service.js';
13
12
  import { createWorldConversationOrchestrator } from './orchestration/world-conversation-orchestrator.js';
14
13
  import { createSocialService } from './social/social-service.js';
15
14
  import { registerSocialRoutes } from './social/social-routes.js';
@@ -50,7 +49,6 @@ export function createClaworldProductShell({
50
49
  chatRequestService,
51
50
  store,
52
51
  });
53
- const resultService = createResultService();
54
52
  const friendService = createFriendService({ store, policy: relay?.policy });
55
53
  const socialLookupService = createSocialService({ worldService, store });
56
54
  const feedbackService = createFeedbackService({ store });
@@ -62,7 +60,6 @@ export function createClaworldProductShell({
62
60
  };
63
61
  const worldConversationOrchestrator = createWorldConversationOrchestrator({
64
62
  worldService,
65
- resultService,
66
63
  });
67
64
 
68
65
  const productShell = {
@@ -78,7 +75,6 @@ export function createClaworldProductShell({
78
75
  chatRequests: chatRequestService,
79
76
  moderation: worldAdminService,
80
77
  feedback: feedbackService,
81
- results: resultService,
82
78
  orchestration: worldConversationOrchestrator,
83
79
  },
84
80
  registerRoutes(app) {
@@ -115,7 +111,6 @@ export function createClaworldProductShell({
115
111
  'moderation',
116
112
  'feedback',
117
113
  'orchestration',
118
- 'results',
119
114
  ],
120
115
  routes: [
121
116
  'GET /v1/meta/product-shell',
@@ -2,7 +2,6 @@ import { createSystemMessageOrchestrator } from './world-conversation-text.js';
2
2
 
3
3
  export function createWorldConversationOrchestrator({
4
4
  worldService,
5
- resultService,
6
5
  systemMessages = createSystemMessageOrchestrator(),
7
6
  } = {}) {
8
7
  return {
@@ -22,7 +21,6 @@ export function createWorldConversationOrchestrator({
22
21
  },
23
22
  openingPlan,
24
23
  convergencePlan,
25
- resultPreview: resultService.previewConversation({ world, conversationKey }),
26
24
  status: 'preview_ready',
27
25
  };
28
26
  },
@@ -86,7 +86,6 @@ export function buildWorldConversationContextEvent(detail = {}) {
86
86
  const convergenceText = normalizeText(conversationOverview.convergence?.text, null);
87
87
  const interactionRules = normalizeText(detail.interactionRules, null);
88
88
  const prohibitedRules = normalizeText(detail.prohibitedRules, null);
89
- const ratingRules = normalizeText(detail.ratingRules, null);
90
89
 
91
90
  const lines = [
92
91
  'Internal Claworld world context for this conversation.',
@@ -98,7 +97,6 @@ export function buildWorldConversationContextEvent(detail = {}) {
98
97
  openingText ? `Opening focus: ${openingText}` : null,
99
98
  interactionRules ? `Interaction rules: ${interactionRules}` : null,
100
99
  prohibitedRules ? `Prohibited rules: ${prohibitedRules}` : null,
101
- ratingRules ? `Rating rules: ${ratingRules}` : null,
102
100
  convergenceText ? `Convergence rule: ${convergenceText}` : null,
103
101
  'Apply these world rules symmetrically when responding in this conversation.',
104
102
  ].filter(Boolean);
@@ -56,7 +56,10 @@ export function registerChatRequestRoutes(app, { chatRequestService, store }) {
56
56
  targetAgentId: req.body?.targetAgentId,
57
57
  kickoffBrief: req.body?.kickoffBrief,
58
58
  openingMessage: req.body?.openingMessage,
59
+ openingPayload: req.body?.openingPayload,
59
60
  worldId: req.body?.worldId,
61
+ requestContext: req.body?.requestContext,
62
+ source: req.body?.source,
60
63
  });
61
64
  res.status(201).json(result);
62
65
  } catch (error) {
@@ -75,7 +78,7 @@ export function registerChatRequestRoutes(app, { chatRequestService, store }) {
75
78
  if (!authAgent.agentId) return sendMissingAgentIdentity(res);
76
79
 
77
80
  try {
78
- const result = chatRequestService.listChatRequests({
81
+ const result = chatRequestService.listChatInbox({
79
82
  agentId: authAgent.agentId,
80
83
  direction: req.query.direction,
81
84
  });
@@ -1,4 +1,5 @@
1
1
  import { normalizeAgentProfile, resolveAgentDisplayName, resolveAgentVisibility } from '../../lib/agent-profile.js';
2
+ import { normalizeChatRequestInput } from '../../lib/chat-request.js';
2
3
  import { createKickoffBrief, resolveStoredKickoffBrief } from '../../lib/relay/kickoff-text.js';
3
4
  import { WORLD_ACTIONS } from '../worlds/world-authorization.js';
4
5
  import { normalizeChatRequestApprovalPolicy } from '../contracts/chat-request-approval-policy.js';
@@ -198,6 +199,7 @@ function resolveAcceptNextAction(kickoff) {
198
199
  const kickoffStatus = kickoff?.status || 'skipped';
199
200
  if (kickoffStatus === 'established') return 'runtime_owns_live_conversation';
200
201
  if (kickoffStatus === 'sent') return 'wait_for_sender_opener_delivery';
202
+ if (kickoffStatus === 'kept_silent') return 'sender_kept_silent';
201
203
  if (kickoffStatus === 'failed') return 'backend_kickoff_failed';
202
204
  return 'chat_request_accepted_without_opening_message';
203
205
  }
@@ -280,6 +282,92 @@ function projectChatRequestOrigin(request = {}) {
280
282
  };
281
283
  }
282
284
 
285
+ function hasConversationScope(conversation = null) {
286
+ return Boolean(conversation && typeof conversation === 'object' && !Array.isArray(conversation) && Object.keys(conversation).length > 0);
287
+ }
288
+
289
+ function resolveConversationLinkRequest(requests = [], conversation = {}) {
290
+ const conversationMeta = conversation?.meta && typeof conversation.meta === 'object' && !Array.isArray(conversation.meta)
291
+ ? conversation.meta
292
+ : {};
293
+ const approvalRequestId = normalizeText(conversationMeta.approvalRequestId, null);
294
+ if (approvalRequestId) {
295
+ return requests.find((request) => normalizeText(request.chatRequestId || request.requestId, null) === approvalRequestId) || null;
296
+ }
297
+
298
+ const conversationKey = normalizeText(conversation?.conversationKey, null);
299
+ if (!conversationKey) return null;
300
+
301
+ return requests.find((request) => {
302
+ const kickoff = request?.kickoff && typeof request.kickoff === 'object' && !Array.isArray(request.kickoff)
303
+ ? request.kickoff
304
+ : {};
305
+ return normalizeText(kickoff.conversationKey, null) === conversationKey
306
+ || normalizeText(request?.conversation?.conversationKey, null) === conversationKey;
307
+ }) || null;
308
+ }
309
+
310
+ function resolveInboxChatDirection(viewerAgentId, request = null) {
311
+ if (!request) return null;
312
+ if (viewerAgentId === request.fromAgentId) return 'outbound';
313
+ if (viewerAgentId === request.toAgentId) return 'inbound';
314
+ return 'related';
315
+ }
316
+
317
+ function resolveInboxChatCounterpartyAgentId(viewerAgentId, request = null, conversation = null) {
318
+ if (request) {
319
+ if (viewerAgentId === request.fromAgentId) return request.toAgentId;
320
+ if (viewerAgentId === request.toAgentId) return request.fromAgentId;
321
+ }
322
+
323
+ const participantIds = Array.isArray(conversation?.participantIds) ? conversation.participantIds : [];
324
+ return participantIds.find((participantId) => participantId && participantId !== viewerAgentId) || null;
325
+ }
326
+
327
+ function resolveInboxChatStatus(conversation = {}, request = null) {
328
+ const kickoffStatus = normalizeText(request?.kickoff?.status, null);
329
+ if (kickoffStatus === 'sent') return 'opening';
330
+ if (kickoffStatus === 'kept_silent') return 'silent';
331
+ if (kickoffStatus === 'failed') return 'kickoff_failed';
332
+ const conversationStatus = normalizeText(conversation?.status, 'active');
333
+ if (conversationStatus === 'active') return 'active';
334
+ if (conversationStatus === 'closed') return 'ended';
335
+ return conversationStatus;
336
+ }
337
+
338
+ function resolveInboxConversationWorldSummary(worldService, request = {}, conversation = {}) {
339
+ const requestContext = request?.requestContext && typeof request.requestContext === 'object' && !Array.isArray(request.requestContext)
340
+ ? request.requestContext
341
+ : {};
342
+ const requestConversation = request?.conversation && typeof request.conversation === 'object' && !Array.isArray(request.conversation)
343
+ ? request.conversation
344
+ : {};
345
+ const conversationMeta = conversation?.meta && typeof conversation.meta === 'object' && !Array.isArray(conversation.meta)
346
+ ? conversation.meta
347
+ : {};
348
+ const worldMeta = conversationMeta.world && typeof conversationMeta.world === 'object' && !Array.isArray(conversationMeta.world)
349
+ ? conversationMeta.world
350
+ : {};
351
+ const worldId = normalizeConversationWorldId(requestConversation.worldId)
352
+ || normalizeConversationWorldId(requestContext.conversation?.worldId)
353
+ || normalizeConversationWorldId(worldMeta.worldId)
354
+ || normalizeConversationWorldId(conversationMeta.worldId);
355
+
356
+ if (!worldId) {
357
+ return {
358
+ mode: 'direct',
359
+ worldId: null,
360
+ world: null,
361
+ };
362
+ }
363
+
364
+ return {
365
+ mode: 'world',
366
+ worldId,
367
+ world: projectWorldSummary(worldService, worldId),
368
+ };
369
+ }
370
+
283
371
  export function createChatRequestService({
284
372
  store = null,
285
373
  relay = null,
@@ -349,8 +437,27 @@ export function createChatRequestService({
349
437
  };
350
438
  }
351
439
 
440
+ function projectChatInboxChat(conversation = {}, viewerAgentId = null, request = null) {
441
+ const direction = resolveInboxChatDirection(viewerAgentId, request);
442
+ const counterpartyAgentId = resolveInboxChatCounterpartyAgentId(viewerAgentId, request, conversation);
443
+
444
+ return {
445
+ chatRequestId: normalizeText(request?.chatRequestId || request?.requestId, null),
446
+ status: resolveInboxChatStatus(conversation, request),
447
+ ...(direction ? { direction } : {}),
448
+ createdAt: normalizeText(conversation.createdAt, normalizeText(request?.createdAt, null)),
449
+ updatedAt: normalizeText(conversation.updatedAt, null),
450
+ lastTurnAt: normalizeText(conversation.lastTurnAt, null),
451
+ conversationKey: normalizeText(conversation.conversationKey, null),
452
+ localSessionKey: normalizeText(conversation.sessionKey, null),
453
+ counterparty: projectAgent(assertStore(), counterpartyAgentId, presence),
454
+ conversation: resolveInboxConversationWorldSummary(worldService, request, conversation),
455
+ };
456
+ }
457
+
352
458
  return {
353
459
  projectChatRequest,
460
+ projectChatInboxChat,
354
461
  async syncApprovalPolicy({
355
462
  actorAgentId,
356
463
  credentialId = null,
@@ -388,6 +495,7 @@ export function createChatRequestService({
388
495
  openingMessage = null,
389
496
  openingPayload = null,
390
497
  worldId = null,
498
+ requestContext = null,
391
499
  origin = null,
392
500
  broadcast = null,
393
501
  source = 'chat_request',
@@ -398,29 +506,36 @@ export function createChatRequestService({
398
506
  throw createInvalidChatRequestError('chat_request_target_required', 'chat request target requires targetAgentId');
399
507
  }
400
508
  const targetAgent = requireAgent(normalizedTargetAgentId);
401
- const normalizedWorldId = normalizeConversationWorldId(worldId);
509
+ const normalizedSource = normalizeText(source, 'chat_request');
510
+ const normalizedRequestInput = normalizeChatRequestInput({
511
+ requestContext,
512
+ source: normalizedSource,
513
+ });
514
+ const normalizedWorldId = normalizeConversationWorldId(worldId)
515
+ || normalizeConversationWorldId(normalizedRequestInput.conversation?.worldId);
402
516
  if (!targetAgent?.address) {
403
517
  throw createTargetAddressNotFoundError(normalizedTargetAgentId);
404
518
  }
405
- const normalizedOpeningPayload = cloneJsonObject(openingPayload);
519
+ const normalizedOpeningPayload = cloneJsonObject(openingPayload) || cloneJsonObject(normalizedRequestInput.openingPayload);
406
520
  const normalizedKickoffBrief = createKickoffBrief({
407
521
  text: typeof kickoffBrief === 'object' && kickoffBrief !== null && !Array.isArray(kickoffBrief)
408
522
  ? kickoffBrief.text
409
- : openingMessage,
523
+ : normalizeText(openingMessage, normalizeText(normalizedRequestInput.openingMessage, null)),
410
524
  payload: typeof kickoffBrief === 'object' && kickoffBrief !== null && !Array.isArray(kickoffBrief)
411
525
  ? kickoffBrief.payload
412
526
  : normalizedOpeningPayload,
413
527
  source: typeof kickoffBrief === 'object' && kickoffBrief !== null && !Array.isArray(kickoffBrief)
414
528
  ? kickoffBrief.source
415
- : source === 'world_broadcast'
529
+ : normalizedSource === 'world_broadcast'
416
530
  ? 'world_broadcast_brief'
417
531
  : 'chat_request_brief',
418
532
  });
419
- const normalizedOrigin = normalizeChatRequestOrigin(origin);
420
- const normalizedBroadcast = normalizeChatRequestBroadcastMetadata(broadcast);
533
+ const normalizedOrigin = normalizeChatRequestOrigin(origin) || normalizeChatRequestOrigin(normalizedRequestInput.origin);
534
+ const normalizedBroadcast = normalizeChatRequestBroadcastMetadata(broadcast)
535
+ || normalizeChatRequestBroadcastMetadata(normalizedRequestInput.broadcast);
421
536
  if (normalizedWorldId) {
422
537
  worldService?.requireWorld?.(normalizedWorldId);
423
- if (normalizeText(source, 'chat_request') !== 'world_broadcast') {
538
+ if (normalizedSource !== 'world_broadcast') {
424
539
  const authorization = worldAuthorizationService.evaluateWorldAction({
425
540
  worldId: normalizedWorldId,
426
541
  actorAgentId: fromAgentId,
@@ -444,9 +559,10 @@ export function createChatRequestService({
444
559
  conversation: {
445
560
  ...(normalizedWorldId ? { worldId: normalizedWorldId } : {}),
446
561
  },
562
+ requestContext: cloneJsonObject(requestContext),
447
563
  origin: normalizedOrigin,
448
564
  broadcast: normalizedBroadcast,
449
- source: normalizeText(source, 'chat_request'),
565
+ source: normalizedSource,
450
566
  });
451
567
  if (result.status < 200 || result.status >= 300) {
452
568
  throw createRelayResponseError(result, 'chat_request_create_failed');
@@ -494,23 +610,55 @@ export function createChatRequestService({
494
610
  };
495
611
  },
496
612
 
497
- listChatRequests({ agentId, direction = null } = {}) {
613
+ listChatInbox({ agentId, direction = null } = {}) {
498
614
  requireAgent(agentId);
499
615
  const normalizedDirection = normalizeDirection(direction);
500
- let items = assertStore().listChatRequests ? assertStore().listChatRequests() : [];
501
- items = items.filter((request) => request.status === 'pending');
502
- items = items.filter((request) => request.fromAgentId === agentId || request.toAgentId === agentId);
616
+ let requests = assertStore().listChatRequests ? assertStore().listChatRequests() : [];
617
+ requests = requests.filter((request) => request.fromAgentId === agentId || request.toAgentId === agentId);
503
618
  if (normalizedDirection === 'inbound') {
504
- items = items.filter((request) => request.toAgentId === agentId);
619
+ requests = requests.filter((request) => request.toAgentId === agentId);
505
620
  } else if (normalizedDirection === 'outbound') {
506
- items = items.filter((request) => request.fromAgentId === agentId);
621
+ requests = requests.filter((request) => request.fromAgentId === agentId);
507
622
  }
508
623
 
624
+ const pendingRequests = sortByRecency(
625
+ requests.filter((request) => request.status === 'pending'),
626
+ 'createdAt',
627
+ ).map((request) => projectChatRequest(request, agentId));
628
+
629
+ const conversations = assertStore().listConversations
630
+ ? assertStore().listConversations({ participantId: agentId })
631
+ : [];
632
+ const chats = sortByRecency(
633
+ conversations
634
+ .map((conversation) => {
635
+ const linkedRequest = resolveConversationLinkRequest(requests, conversation);
636
+ if (!linkedRequest && !normalizeText(conversation?.meta?.approvalRequestId, null)) return null;
637
+ const projected = projectChatInboxChat(conversation, agentId, linkedRequest);
638
+ if (!projected) return null;
639
+ if (normalizedDirection && projected.direction !== normalizedDirection) return null;
640
+ return projected;
641
+ })
642
+ .filter(Boolean),
643
+ 'lastTurnAt',
644
+ 'updatedAt',
645
+ 'createdAt',
646
+ );
647
+
509
648
  return {
510
- items: sortByRecency(items, 'createdAt').map((request) => projectChatRequest(request, agentId)),
649
+ counts: {
650
+ pendingRequestCount: pendingRequests.length,
651
+ chatCount: chats.length,
652
+ },
653
+ pendingRequests,
654
+ chats,
511
655
  };
512
656
  },
513
657
 
658
+ listChatRequests({ agentId, direction = null } = {}) {
659
+ return this.listChatInbox({ agentId, direction });
660
+ },
661
+
514
662
  async acceptChatRequest(chatRequestId, { actorAgentId } = {}) {
515
663
  requireAgent(actorAgentId);
516
664
  const normalizedChatRequestId = normalizeText(chatRequestId, null);