@xfxstudio/claworld 0.2.15 → 0.2.17

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.
@@ -0,0 +1,146 @@
1
+ import { createRuntimeBoundaryError } from '../../lib/runtime-errors.js';
2
+
3
+ export const DUPLICATE_CONNECTION_CLOSE_CODE = 4001;
4
+ export const STALE_CONNECTION_CLOSE_CODE = 4002;
5
+ export const TERMINAL_CLOSE_REASONS = new Set(['duplicate_connection_replaced', 'stale_connection']);
6
+ export const DEFAULT_REPLY_ACK_TIMEOUT_MS = 5000;
7
+
8
+ export function normalizeRelayWebSocketUrl(serverUrl) {
9
+ const parsed = new URL(serverUrl);
10
+ if (parsed.protocol === 'http:') parsed.protocol = 'ws:';
11
+ if (parsed.protocol === 'https:') parsed.protocol = 'wss:';
12
+
13
+ const pathname = parsed.pathname || '/';
14
+ const normalizedPathname = pathname.replace(/\/+$/, '') || '/';
15
+ parsed.pathname = normalizedPathname === '/' || normalizedPathname === ''
16
+ ? '/ws'
17
+ : normalizedPathname.endsWith('/ws')
18
+ ? normalizedPathname
19
+ : normalizedPathname + '/ws';
20
+
21
+ return parsed.toString();
22
+ }
23
+
24
+ export function buildInboundEnvelope(message = {}) {
25
+ const data = message.data || {};
26
+ if (message.event !== 'delivery') return null;
27
+ const metadata = data.metadata && typeof data.metadata === 'object' && !Array.isArray(data.metadata)
28
+ ? { ...data.metadata }
29
+ : {};
30
+ return {
31
+ eventType: data.eventType || 'delivery',
32
+ deliveryId: data.deliveryId || null,
33
+ sessionKey: data.sessionKey || null,
34
+ createdAt: data.createdAt || null,
35
+ updatedAt: data.updatedAt || null,
36
+ turnCreatedAt: data.turnCreatedAt || null,
37
+ payload: data.payload && typeof data.payload === 'object' && !Array.isArray(data.payload)
38
+ ? { ...data.payload }
39
+ : {},
40
+ metadata,
41
+ };
42
+ }
43
+
44
+ export function normalizeOptionalText(value) {
45
+ if (value == null) return null;
46
+ const normalized = String(value).trim();
47
+ return normalized || null;
48
+ }
49
+
50
+ export function requireClientMessageId(value = null) {
51
+ const normalized = normalizeOptionalText(value);
52
+ if (!normalized) {
53
+ throw new Error('claworld relay clientMessageId is required for POST /v1/messages');
54
+ }
55
+ return normalized;
56
+ }
57
+
58
+ export function buildReplyAckTimeoutError({ deliveryId, timeoutMs, context = {} } = {}) {
59
+ return createRuntimeBoundaryError({
60
+ code: 'relay_reply_ack_timeout',
61
+ category: 'transport',
62
+ status: 504,
63
+ message: `timed out waiting for relay reply acknowledgement for ${deliveryId || 'unknown-delivery'} after ${timeoutMs}ms`,
64
+ publicMessage: 'relay reply acknowledgement timed out',
65
+ recoverable: true,
66
+ context,
67
+ });
68
+ }
69
+
70
+ export function buildAcceptedAckTimeoutError({ deliveryId, timeoutMs, context = {} } = {}) {
71
+ return createRuntimeBoundaryError({
72
+ code: 'relay_delivery_accept_ack_timeout',
73
+ category: 'transport',
74
+ status: 504,
75
+ message: `timed out waiting for relay delivery acceptance acknowledgement for ${deliveryId || 'unknown-delivery'} after ${timeoutMs}ms`,
76
+ publicMessage: 'relay delivery acceptance acknowledgement timed out',
77
+ recoverable: true,
78
+ context,
79
+ });
80
+ }
81
+
82
+ export function buildReplyFallbackError({
83
+ deliveryId = null,
84
+ status = 502,
85
+ body = {},
86
+ context = {},
87
+ } = {}) {
88
+ return createRuntimeBoundaryError({
89
+ code: normalizeOptionalText(body?.code) || normalizeOptionalText(body?.error) || 'relay_reply_fallback_failed',
90
+ category: status >= 500 ? 'runtime' : 'transport',
91
+ status: Number.isInteger(status) ? status : 502,
92
+ message: normalizeOptionalText(body?.message) || normalizeOptionalText(body?.reason) || 'relay reply fallback failed',
93
+ publicMessage: 'relay reply fallback failed',
94
+ recoverable: status >= 500,
95
+ context: {
96
+ deliveryId: normalizeOptionalText(deliveryId),
97
+ ...context,
98
+ },
99
+ });
100
+ }
101
+
102
+ export function buildKeepSilentAckTimeoutError({ deliveryId, timeoutMs, context = {} } = {}) {
103
+ return createRuntimeBoundaryError({
104
+ code: 'relay_kept_silent_ack_timeout',
105
+ category: 'transport',
106
+ status: 504,
107
+ message: `timed out waiting for relay kept_silent acknowledgement for ${deliveryId || 'unknown-delivery'} after ${timeoutMs}ms`,
108
+ publicMessage: 'relay kept_silent acknowledgement timed out',
109
+ recoverable: true,
110
+ context,
111
+ });
112
+ }
113
+
114
+ export function buildKeepSilentFallbackError({
115
+ deliveryId = null,
116
+ status = 502,
117
+ body = {},
118
+ context = {},
119
+ } = {}) {
120
+ return createRuntimeBoundaryError({
121
+ code: normalizeOptionalText(body?.code) || normalizeOptionalText(body?.error) || 'relay_kept_silent_fallback_failed',
122
+ category: status >= 500 ? 'runtime' : 'transport',
123
+ status: Number.isInteger(status) ? status : 502,
124
+ message: normalizeOptionalText(body?.message) || normalizeOptionalText(body?.reason) || 'relay kept_silent fallback failed',
125
+ publicMessage: 'relay kept_silent fallback failed',
126
+ recoverable: status >= 500,
127
+ context: {
128
+ deliveryId: normalizeOptionalText(deliveryId),
129
+ ...context,
130
+ },
131
+ });
132
+ }
133
+
134
+ export function isReplyAlreadyApplied(result = null, deliveryId = null) {
135
+ if (!result || result.status !== 409) return false;
136
+ if (normalizeOptionalText(result.body?.reason) !== 'delivery_not_replyable') return false;
137
+ if (normalizeOptionalText(result.body?.delivery?.deliveryId) !== normalizeOptionalText(deliveryId)) return false;
138
+ return normalizeOptionalText(result.body?.delivery?.status) === 'replied';
139
+ }
140
+
141
+ export function isDeliveryKeptSilentAlreadyApplied(result = null, deliveryId = null) {
142
+ if (!result || result.status !== 409) return false;
143
+ if (normalizeOptionalText(result.body?.reason) !== 'delivery_not_replyable') return false;
144
+ if (normalizeOptionalText(result.body?.delivery?.deliveryId) !== normalizeOptionalText(deliveryId)) return false;
145
+ return normalizeOptionalText(result.body?.delivery?.status) === 'kept_silent';
146
+ }
@@ -11,151 +11,23 @@ import {
11
11
  createRuntimeBoundaryError,
12
12
  logRuntimeBoundary,
13
13
  } from '../../lib/runtime-errors.js';
14
-
15
- const DUPLICATE_CONNECTION_CLOSE_CODE = 4001;
16
- const STALE_CONNECTION_CLOSE_CODE = 4002;
17
- const TERMINAL_CLOSE_REASONS = new Set(['duplicate_connection_replaced', 'stale_connection']);
18
- const DEFAULT_REPLY_ACK_TIMEOUT_MS = 5000;
19
-
20
- function normalizeRelayWebSocketUrl(serverUrl) {
21
- const parsed = new URL(serverUrl);
22
- if (parsed.protocol === 'http:') parsed.protocol = 'ws:';
23
- if (parsed.protocol === 'https:') parsed.protocol = 'wss:';
24
-
25
- const pathname = parsed.pathname || '/';
26
- const normalizedPathname = pathname.replace(/\/+$/, '') || '/';
27
- parsed.pathname = normalizedPathname === '/' || normalizedPathname === ''
28
- ? '/ws'
29
- : normalizedPathname.endsWith('/ws')
30
- ? normalizedPathname
31
- : normalizedPathname + '/ws';
32
-
33
- return parsed.toString();
34
- }
35
-
36
- function buildInboundEnvelope(message = {}) {
37
- const data = message.data || {};
38
- if (message.event !== 'delivery') return null;
39
- const metadata = data.metadata && typeof data.metadata === 'object' && !Array.isArray(data.metadata)
40
- ? { ...data.metadata }
41
- : {};
42
- return {
43
- eventType: data.eventType || 'delivery',
44
- deliveryId: data.deliveryId || null,
45
- sessionKey: data.sessionKey || null,
46
- createdAt: data.createdAt || null,
47
- updatedAt: data.updatedAt || null,
48
- turnCreatedAt: data.turnCreatedAt || null,
49
- payload: data.payload && typeof data.payload === 'object' && !Array.isArray(data.payload)
50
- ? { ...data.payload }
51
- : {},
52
- metadata,
53
- };
54
- }
55
-
56
- function normalizeOptionalText(value) {
57
- if (value == null) return null;
58
- const normalized = String(value).trim();
59
- return normalized || null;
60
- }
61
-
62
- function requireClientMessageId(value = null) {
63
- const normalized = normalizeOptionalText(value);
64
- if (!normalized) {
65
- throw new Error('claworld relay clientMessageId is required for POST /v1/messages');
66
- }
67
- return normalized;
68
- }
69
-
70
- function buildReplyAckTimeoutError({ deliveryId, timeoutMs, context = {} } = {}) {
71
- return createRuntimeBoundaryError({
72
- code: 'relay_reply_ack_timeout',
73
- category: 'transport',
74
- status: 504,
75
- message: `timed out waiting for relay reply acknowledgement for ${deliveryId || 'unknown-delivery'} after ${timeoutMs}ms`,
76
- publicMessage: 'relay reply acknowledgement timed out',
77
- recoverable: true,
78
- context,
79
- });
80
- }
81
-
82
- function buildAcceptedAckTimeoutError({ deliveryId, timeoutMs, context = {} } = {}) {
83
- return createRuntimeBoundaryError({
84
- code: 'relay_delivery_accept_ack_timeout',
85
- category: 'transport',
86
- status: 504,
87
- message: `timed out waiting for relay delivery acceptance acknowledgement for ${deliveryId || 'unknown-delivery'} after ${timeoutMs}ms`,
88
- publicMessage: 'relay delivery acceptance acknowledgement timed out',
89
- recoverable: true,
90
- context,
91
- });
92
- }
93
-
94
- function buildReplyFallbackError({
95
- deliveryId,
96
- status,
97
- body,
98
- context = {},
99
- } = {}) {
100
- return createRuntimeBoundaryError({
101
- code: normalizeOptionalText(body?.code) || normalizeOptionalText(body?.error) || 'relay_reply_fallback_failed',
102
- category: status >= 500 ? 'runtime' : 'transport',
103
- status: Number.isInteger(status) ? status : 502,
104
- message: normalizeOptionalText(body?.message) || normalizeOptionalText(body?.reason) || 'relay reply fallback failed',
105
- publicMessage: 'relay reply fallback failed',
106
- recoverable: status >= 500,
107
- context: {
108
- deliveryId: normalizeOptionalText(deliveryId),
109
- ...context,
110
- },
111
- });
112
- }
113
-
114
- function buildKeepSilentAckTimeoutError({ deliveryId, timeoutMs, context = {} } = {}) {
115
- return createRuntimeBoundaryError({
116
- code: 'relay_kept_silent_ack_timeout',
117
- category: 'transport',
118
- status: 504,
119
- message: `timed out waiting for relay kept_silent acknowledgement for ${deliveryId || 'unknown-delivery'} after ${timeoutMs}ms`,
120
- publicMessage: 'relay kept_silent acknowledgement timed out',
121
- recoverable: true,
122
- context,
123
- });
124
- }
125
-
126
- function buildKeepSilentFallbackError({
127
- deliveryId,
128
- status,
129
- body,
130
- context = {},
131
- } = {}) {
132
- return createRuntimeBoundaryError({
133
- code: normalizeOptionalText(body?.code) || normalizeOptionalText(body?.error) || 'relay_kept_silent_fallback_failed',
134
- category: status >= 500 ? 'runtime' : 'transport',
135
- status: Number.isInteger(status) ? status : 502,
136
- message: normalizeOptionalText(body?.message) || normalizeOptionalText(body?.reason) || 'relay kept_silent fallback failed',
137
- publicMessage: 'relay kept_silent fallback failed',
138
- recoverable: status >= 500,
139
- context: {
140
- deliveryId: normalizeOptionalText(deliveryId),
141
- ...context,
142
- },
143
- });
144
- }
145
-
146
- function isReplyAlreadyApplied(result = null, deliveryId = null) {
147
- if (!result || result.status !== 409) return false;
148
- if (normalizeOptionalText(result.body?.reason) !== 'delivery_not_replyable') return false;
149
- if (normalizeOptionalText(result.body?.delivery?.deliveryId) !== normalizeOptionalText(deliveryId)) return false;
150
- return normalizeOptionalText(result.body?.delivery?.status) === 'replied';
151
- }
152
-
153
- function isDeliveryKeptSilentAlreadyApplied(result = null, deliveryId = null) {
154
- if (!result || result.status !== 409) return false;
155
- if (normalizeOptionalText(result.body?.reason) !== 'delivery_not_replyable') return false;
156
- if (normalizeOptionalText(result.body?.delivery?.deliveryId) !== normalizeOptionalText(deliveryId)) return false;
157
- return normalizeOptionalText(result.body?.delivery?.status) === 'kept_silent';
158
- }
14
+ import {
15
+ buildAcceptedAckTimeoutError,
16
+ buildInboundEnvelope,
17
+ buildKeepSilentAckTimeoutError,
18
+ buildKeepSilentFallbackError,
19
+ buildReplyAckTimeoutError,
20
+ buildReplyFallbackError,
21
+ DEFAULT_REPLY_ACK_TIMEOUT_MS,
22
+ DUPLICATE_CONNECTION_CLOSE_CODE,
23
+ isDeliveryKeptSilentAlreadyApplied,
24
+ isReplyAlreadyApplied,
25
+ normalizeOptionalText,
26
+ normalizeRelayWebSocketUrl,
27
+ requireClientMessageId,
28
+ STALE_CONNECTION_CLOSE_CODE,
29
+ TERMINAL_CLOSE_REASONS,
30
+ } from './relay-client-shared.js';
159
31
 
160
32
  export class ClaworldRelayClient extends EventEmitter {
161
33
  constructor({
@@ -587,7 +459,7 @@ export class ClaworldRelayClient extends EventEmitter {
587
459
  config,
588
460
  agentId,
589
461
  credential = null,
590
- clientVersion = 'claworld-plugin/0.2.15',
462
+ clientVersion = 'claworld-plugin/0.2.17',
591
463
  sessionTarget,
592
464
  fallbackTarget,
593
465
  } = {}) {
@@ -1320,14 +1192,17 @@ export class ClaworldRelayClient extends EventEmitter {
1320
1192
  }
1321
1193
  }
1322
1194
 
1323
- async createChatRequest({ fromAgentId, targetAgentId, requestContext = {} } = {}) {
1195
+ async createChatRequest({ fromAgentId, displayName, agentCode, requestContext = {} } = {}) {
1324
1196
  const normalized = normalizeChatRequestInput({ requestContext, source: 'direct_lookup' });
1197
+ const normalizedDisplayName = normalizeOptionalText(displayName);
1198
+ const normalizedAgentCode = normalizeOptionalText(agentCode)?.toUpperCase() || null;
1325
1199
  return await this.requestJson('/v1/chat-requests', {
1326
1200
  method: 'POST',
1327
1201
  headers: buildRuntimeAuthHeaders(this.runtimeConfig, { 'content-type': 'application/json' }),
1328
1202
  body: JSON.stringify({
1329
1203
  fromAgentId,
1330
- targetAgentId,
1204
+ ...(normalizedDisplayName ? { displayName: normalizedDisplayName } : {}),
1205
+ ...(normalizedAgentCode ? { agentCode: normalizedAgentCode } : {}),
1331
1206
  kickoffBrief: normalized.kickoffBrief || null,
1332
1207
  openingMessage: normalized.openingMessage || null,
1333
1208
  worldId: normalized.conversation?.worldId || null,
@@ -1420,7 +1295,7 @@ export class ClaworldRelayClient extends EventEmitter {
1420
1295
  });
1421
1296
  }
1422
1297
 
1423
- async establishConversation({ fromAgentId, targetAgentId, requestContext = {}, openingPayload = {} } = {}) {
1298
+ async establishConversation({ fromAgentId, displayName = null, agentCode = null, requestContext = {}, openingPayload = {} } = {}) {
1424
1299
  const normalizedRequestContext = requestContext && typeof requestContext === 'object' && !Array.isArray(requestContext)
1425
1300
  ? { ...requestContext }
1426
1301
  : {};
@@ -1436,7 +1311,8 @@ export class ClaworldRelayClient extends EventEmitter {
1436
1311
 
1437
1312
  const requestResult = await this.createChatRequest({
1438
1313
  fromAgentId,
1439
- targetAgentId,
1314
+ displayName,
1315
+ agentCode,
1440
1316
  requestContext: normalizedRequestContext,
1441
1317
  });
1442
1318
  if (requestResult.status !== 201) {
@@ -23,48 +23,6 @@ function normalizeStringList(values = []) {
23
23
  return [...new Set(values.map((value) => normalizeText(value, null)).filter(Boolean))];
24
24
  }
25
25
 
26
- function isEmptyProfileValue(value) {
27
- if (value == null) return true;
28
- if (typeof value === 'string') return value.trim() === '';
29
- if (Array.isArray(value)) return value.length === 0 || value.every((entry) => isEmptyProfileValue(entry));
30
- if (typeof value === 'object') {
31
- const entries = Object.values(value);
32
- return entries.length === 0 || entries.every((entry) => isEmptyProfileValue(entry));
33
- }
34
- return false;
35
- }
36
-
37
- function normalizeProfileValue(value) {
38
- if (typeof value === 'string') return value.trim();
39
- if (Array.isArray(value)) {
40
- return value
41
- .map((entry) => normalizeProfileValue(entry))
42
- .filter((entry) => entry !== undefined && !isEmptyProfileValue(entry));
43
- }
44
- if (value && typeof value === 'object') {
45
- return Object.fromEntries(
46
- Object.entries(value)
47
- .filter(([, entry]) => entry !== undefined)
48
- .map(([key, entry]) => [key, normalizeProfileValue(entry)]),
49
- );
50
- }
51
- return value;
52
- }
53
-
54
- function normalizeProfile(profile = {}) {
55
- if (!profile || typeof profile !== 'object' || Array.isArray(profile)) return {};
56
-
57
- return Object.fromEntries(
58
- Object.entries(profile)
59
- .filter(([key, value]) => normalizeText(key, null) && value !== undefined)
60
- .map(([key, value]) => [normalizeText(key, null), normalizeProfileValue(value)]),
61
- );
62
- }
63
-
64
- function normalizeLookupText(value) {
65
- return normalizeText(value, '')?.toLowerCase() || '';
66
- }
67
-
68
26
  function sentenceCase(value, fallback = '') {
69
27
  const normalized = normalizeText(value, fallback);
70
28
  if (!normalized) return fallback;
@@ -83,10 +41,6 @@ function joinAsNaturalLanguage(values = []) {
83
41
  return `${items.slice(0, -1).join(', ')}, and ${items.at(-1)}`;
84
42
  }
85
43
 
86
- function summarizeWorldChoices(items = []) {
87
- return items.map((world) => `${world.displayName} [${world.worldId}]`);
88
- }
89
-
90
44
  function normalizeWorldSummary(world = {}) {
91
45
  const summary = world.agentSummary && typeof world.agentSummary === 'object' ? world.agentSummary : world;
92
46
  const rawWorldId = world.worldId || summary.worldId;
@@ -284,43 +238,31 @@ function normalizeDeliveryReason(reason = {}) {
284
238
  };
285
239
  }
286
240
 
287
- function normalizeCandidateScoreBreakdown(entries = []) {
288
- if (!Array.isArray(entries)) return [];
289
-
290
- return entries.map((entry, index) => ({
291
- signalId: normalizeText(entry.signalId, `score_signal_${index + 1}`),
292
- label: normalizeText(entry.label, `Signal ${index + 1}`),
293
- weight: normalizeNumber(entry.weight, 0),
294
- sourceFieldIds: normalizeStringList(entry.sourceFieldIds),
295
- matched: entry.matched === true,
296
- requesterValue: entry.requesterValue ?? null,
297
- candidateValue: entry.candidateValue ?? null,
298
- sharedValues: Array.isArray(entry.sharedValues) ? normalizeStringList(entry.sharedValues) : [],
299
- overlapCount: normalizeInteger(entry.overlapCount, 0),
300
- contribution: normalizeNumber(entry.contribution, 0),
301
- }));
302
- }
303
-
304
241
  function normalizeCandidate(candidate = {}, index = 0) {
305
242
  const normalizedRank = normalizeNumber(candidate.rank, null);
306
- const targetAgentId = normalizeText(
307
- candidate.targetAgentId || candidate.requestChat?.targetAgentId,
243
+ const displayName = normalizeText(
244
+ candidate.displayName || candidate.profileSummary?.displayName || candidate.requestChat?.displayName,
308
245
  null,
309
246
  );
310
- const requestChat = targetAgentId
247
+ const agentCode = normalizeText(
248
+ candidate.agentCode || candidate.requestChat?.agentCode,
249
+ null,
250
+ )?.toUpperCase() || null;
251
+ const requestChat = displayName && agentCode
311
252
  ? {
312
253
  worldId: normalizeText(candidate.requestChat?.worldId, normalizeText(candidate.worldId, 'unknown-world')),
313
- targetAgentId,
254
+ displayName,
255
+ agentCode,
314
256
  }
315
257
  : null;
316
258
 
317
259
  return {
318
260
  candidateId: normalizeText(candidate.candidateId, `candidate_${index + 1}`),
319
261
  worldId: normalizeText(candidate.worldId, 'unknown-world'),
320
- targetAgentId: normalizeText(candidate.targetAgentId, null),
321
262
  sourceMembershipId: normalizeText(candidate.sourceMembershipId, null),
322
263
  online: candidate.online === true,
323
- targetAgentId,
264
+ displayName,
265
+ agentCode,
324
266
  requestChat,
325
267
  profileSummary: normalizeCandidateProfileSummary(candidate.profileSummary),
326
268
  compatibilitySignals: Array.isArray(candidate.compatibilitySignals)
@@ -340,8 +282,6 @@ function normalizeCandidate(candidate = {}, index = 0) {
340
282
  joinedAt: normalizeText(candidate.joinedAt, null),
341
283
  rank: normalizedRank == null ? null : Math.max(1, Math.trunc(normalizedRank)),
342
284
  score: normalizeNumber(candidate.score, null),
343
- scoreBreakdown: normalizeCandidateScoreBreakdown(candidate.scoreBreakdown),
344
- scoringInputs: candidate.scoringInputs && typeof candidate.scoringInputs === 'object' ? candidate.scoringInputs : {},
345
285
  };
346
286
  }
347
287
 
@@ -391,10 +331,9 @@ function normalizeWorldJoinResponse(payload = {}, { worldId = null, agentId = nu
391
331
  worldId: normalizedWorldId,
392
332
  agentId: normalizedAgentId,
393
333
  membershipStatus,
394
- normalizedProfile: normalizeProfile(
395
- payload.normalizedProfile && typeof payload.normalizedProfile === 'object'
396
- ? payload.normalizedProfile
397
- : membership?.profileSnapshot,
334
+ participantContextText: normalizeText(
335
+ payload.participantContextText,
336
+ membership?.participantContextText || null,
398
337
  ),
399
338
  membership,
400
339
  nextAction: normalizeText(
@@ -443,73 +382,6 @@ export function buildCandidateDeliverySummary(candidateFeed = {}, { worldDetail
443
382
  return buildBackendCandidateDeliverySummary(candidateFeed, { worldDetail, limit });
444
383
  }
445
384
 
446
- function normalizeSelectionInput(selection) {
447
- if (selection && typeof selection === 'object') {
448
- const asWorldId = normalizeText(selection.worldId, null);
449
- const asDisplayName = normalizeText(selection.displayName, null);
450
- const asChoice = normalizeText(selection.selection || selection.choice || selection.value, null);
451
- const text = asWorldId || asDisplayName || asChoice || '';
452
- return {
453
- raw: selection,
454
- text,
455
- normalized: normalizeLookupText(text),
456
- index: /^\d+$/.test(String(text)) ? normalizeInteger(text, 0) : null,
457
- };
458
- }
459
-
460
- const text = normalizeText(selection, '');
461
- return {
462
- raw: selection,
463
- text,
464
- normalized: normalizeLookupText(text),
465
- index: /^\d+$/.test(String(text)) ? normalizeInteger(text, 0) : null,
466
- };
467
- }
468
-
469
- function buildSelectionRetryContract(status, selection, items = [], matches = []) {
470
- const choiceLabel = selection.text ? `"${selection.text}"` : 'that choice';
471
- const retryWorlds = (matches.length > 0 ? matches : items).map((world) => normalizeWorldSummary(world));
472
- const retrySummary = summarizeWorldChoices(retryWorlds);
473
-
474
- if (status === 'ambiguous') {
475
- return {
476
- status,
477
- selection: {
478
- input: selection.text || null,
479
- matchedBy: null,
480
- worldId: null,
481
- displayName: null,
482
- },
483
- candidateWorlds: retryWorlds,
484
- orchestration: {
485
- stage: 'post_setup_world_selection_retry',
486
- system: 'The world choice matched multiple worlds. Show the narrowed list and ask the user to pick one exact world ID or display name.',
487
- user: `The choice ${choiceLabel} is ambiguous. Matching worlds: ${joinAsNaturalLanguage(retrySummary)}. Ask the user to choose one exact world ID or display name.`,
488
- 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.',
489
- },
490
- };
491
- }
492
-
493
- return {
494
- status: 'no_match',
495
- selection: {
496
- input: selection.text || null,
497
- matchedBy: null,
498
- worldId: null,
499
- displayName: null,
500
- },
501
- candidateWorlds: retryWorlds,
502
- orchestration: {
503
- stage: 'post_setup_world_selection_retry',
504
- system: 'The world choice did not match the available world directory. Re-list the worlds and ask the user to choose one by world ID or display name.',
505
- user: retrySummary.length > 0
506
- ? `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.`
507
- : 'No worlds are currently available. Tell the user setup is complete but world selection cannot continue yet.',
508
- followUp: 'Once the user chooses a valid world, confirm it, fetch the world detail, and explain the required fields before calling join_world.',
509
- },
510
- };
511
- }
512
-
513
385
  export function buildWorldSelectionPrompt(worldDirectory = {}) {
514
386
  return worldDirectory?.orchestration && typeof worldDirectory.orchestration === 'object'
515
387
  ? worldDirectory.orchestration
@@ -97,19 +97,22 @@ function projectRequestChatPayload(
97
97
  ) {
98
98
  if (!requestChat || typeof requestChat !== 'object' || Array.isArray(requestChat)) return null;
99
99
  const worldId = normalizeText(requestChat.worldId, null);
100
- const targetAgentId = normalizeText(requestChat.targetAgentId, null);
101
- if (!worldId || !targetAgentId) return null;
100
+ const displayName = normalizeText(requestChat.displayName, null);
101
+ const agentCode = normalizeText(requestChat.agentCode, null)?.toUpperCase() || null;
102
+ if (!worldId || !displayName || !agentCode) return null;
102
103
 
103
104
  const normalizedAccountId = normalizeText(accountId, null);
104
105
 
105
106
  return {
106
107
  worldId,
107
- targetAgentId,
108
+ displayName,
109
+ agentCode,
108
110
  requestTool: normalizeText(requestToolName, null),
109
111
  requestPayload: {
110
112
  ...(normalizedAccountId ? { accountId: normalizedAccountId } : {}),
111
113
  worldId,
112
- targetAgentId,
114
+ displayName,
115
+ agentCode,
113
116
  },
114
117
  };
115
118
  }
@@ -135,7 +138,8 @@ function projectRequestChatAction(
135
138
  requestPayloadTemplate: {
136
139
  ...(normalizedAccountId ? { accountId: normalizedAccountId } : {}),
137
140
  worldId,
138
- targetAgentId: ':targetAgentId',
141
+ displayName: ':displayName',
142
+ agentCode: ':agentCode',
139
143
  },
140
144
  summary: normalizeText(requestChatAction.summary, null),
141
145
  };
@@ -159,7 +163,7 @@ function projectToolCandidateDeliverySummary(
159
163
  online: summary.online === true,
160
164
  rank: normalizeOptionalInteger(summary.rank, null),
161
165
  score: normalizeOptionalInteger(summary.score, null),
162
- targetAgentId: normalizeText(summary.targetAgentId, summary.requestChat?.targetAgentId || null),
166
+ agentCode: normalizeText(summary.agentCode, summary.requestChat?.agentCode || null)?.toUpperCase() || null,
163
167
  requestChat: projectRequestChatPayload(summary.requestChat, {
164
168
  accountId,
165
169
  requestToolName,
@@ -235,8 +239,8 @@ export function projectToolWorldDetail(worldDetail = {}) {
235
239
  function projectToolCandidateSummary(summary = {}, index = 0) {
236
240
  return {
237
241
  candidateId: normalizeText(summary.candidateId, `candidate_${index + 1}`),
238
- targetAgentId: normalizeText(summary.targetAgentId, null),
239
242
  displayName: normalizeText(summary.displayName, `Candidate ${index + 1}`),
243
+ agentCode: normalizeText(summary.agentCode, null)?.toUpperCase() || null,
240
244
  headline: normalizeText(summary.headline, null),
241
245
  online: summary.online === true,
242
246
  rank: normalizeInteger(summary.rank, 0) || null,
@@ -259,8 +263,8 @@ function projectToolCandidateFeed(joinResult = {}) {
259
263
  : Array.isArray(candidateFeed?.candidates)
260
264
  ? candidateFeed.candidates.map((candidate, index) => projectToolCandidateSummary({
261
265
  candidateId: candidate.candidateId,
262
- targetAgentId: candidate.targetAgentId,
263
266
  displayName: candidate.profileSummary?.displayName,
267
+ agentCode: normalizeText(candidate.agentCode, candidate.requestChat?.agentCode || null),
264
268
  headline: candidate.profileSummary?.headline,
265
269
  online: candidate.online === true,
266
270
  rank: candidate.rank,
@@ -294,9 +298,6 @@ export function projectToolJoinWorldResponse(
294
298
  joinResult = {},
295
299
  { accountId = null } = {},
296
300
  ) {
297
- const normalizedProfile = joinResult.normalizedProfile && typeof joinResult.normalizedProfile === 'object'
298
- ? joinResult.normalizedProfile
299
- : {};
300
301
  return {
301
302
  status: joinResult.membershipStatus === 'active' ? 'joined' : 'accepted',
302
303
  worldId: joinResult.worldId,
@@ -304,9 +305,8 @@ export function projectToolJoinWorldResponse(
304
305
  membershipStatus: joinResult.membershipStatus || 'unknown',
305
306
  participantContextText: normalizeText(
306
307
  joinResult.participantContextText,
307
- normalizedProfile.participantContextText || joinResult.membership?.participantContextText || null,
308
+ joinResult.membership?.participantContextText || null,
308
309
  ),
309
- normalizedProfile,
310
310
  nextAction: normalizeText(joinResult.nextAction, 'review_candidate_feed'),
311
311
  candidateFeed: projectToolCandidateFeed(joinResult),
312
312
  requestChatTool: 'claworld_request_chat',
@@ -538,9 +538,11 @@ export function projectToolChatRequestMutationResponse(result = {}, { accountId
538
538
  ? 'runtime_owns_live_conversation'
539
539
  : kickoff?.status === 'sent'
540
540
  ? 'wait_for_sender_opener_delivery'
541
- : kickoff?.status === 'failed'
542
- ? 'backend_kickoff_failed'
543
- : 'chat_request_accepted_without_opening_message'
541
+ : kickoff?.status === 'queued'
542
+ ? 'wait_for_sender_runtime_availability'
543
+ : kickoff?.status === 'failed'
544
+ ? 'backend_kickoff_failed'
545
+ : 'chat_request_accepted_without_opening_message'
544
546
  : normalizedStatus === 'pending'
545
547
  ? 'wait_for_peer_acceptance'
546
548
  : 'chat_request_closed',