@xfxstudio/claworld 0.2.15 → 0.2.16
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/bin/claworld.mjs +9 -0
- package/openclaw.plugin.json +1 -1
- package/package.json +5 -1
- package/skills/claworld-help/SKILL.md +113 -103
- package/skills/claworld-join-and-chat/SKILL.md +205 -104
- package/skills/claworld-manage-worlds/SKILL.md +149 -109
- package/skills/claworld-manage-worlds/references/world-context-templates.md +145 -0
- package/src/lib/relay/kickoff-text.js +49 -21
- package/src/openclaw/installer/cli.js +406 -0
- package/src/openclaw/installer/constants.js +14 -0
- package/src/openclaw/installer/core.js +2115 -0
- package/src/openclaw/installer/doctor.js +876 -0
- package/src/openclaw/installer/workspace-contract.js +427 -0
- package/src/openclaw/plugin/claworld-channel-plugin.js +149 -157
- package/src/openclaw/plugin/register-tooling.js +556 -0
- package/src/openclaw/plugin/register.js +137 -569
- package/src/openclaw/plugin/relay-client-shared.js +146 -0
- package/src/openclaw/plugin/relay-client.js +26 -150
- package/src/openclaw/runtime/product-shell-helper.js +14 -142
- package/src/openclaw/runtime/tool-contracts.js +18 -16
- package/src/product-shell/contracts/chat-request-approval-policy.js +2 -7
- package/src/product-shell/contracts/world-orchestration.js +17 -71
|
@@ -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
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
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.
|
|
462
|
+
clientVersion = 'claworld-plugin/0.2.16',
|
|
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,
|
|
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
|
-
|
|
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,
|
|
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
|
-
|
|
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
|
|
307
|
-
candidate.
|
|
243
|
+
const displayName = normalizeText(
|
|
244
|
+
candidate.displayName || candidate.profileSummary?.displayName || candidate.requestChat?.displayName,
|
|
308
245
|
null,
|
|
309
246
|
);
|
|
310
|
-
const
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
395
|
-
payload.
|
|
396
|
-
|
|
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
|
|
101
|
-
|
|
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
|
-
|
|
108
|
+
displayName,
|
|
109
|
+
agentCode,
|
|
108
110
|
requestTool: normalizeText(requestToolName, null),
|
|
109
111
|
requestPayload: {
|
|
110
112
|
...(normalizedAccountId ? { accountId: normalizedAccountId } : {}),
|
|
111
113
|
worldId,
|
|
112
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
542
|
-
|
|
543
|
-
|
|
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',
|