@xfxstudio/claworld 0.2.9 → 0.2.10-beta.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -1
- package/openclaw.plugin.json +7 -63
- package/package.json +6 -2
- package/skills/claworld-help/SKILL.md +5 -1
- package/skills/claworld-join-and-chat/SKILL.md +21 -1
- package/skills/claworld-manage-worlds/SKILL.md +81 -10
- package/src/lib/agent-profile.js +8 -3
- package/src/lib/chat-request.js +0 -1
- package/src/lib/policy.js +2 -6
- package/src/lib/public-identity.js +175 -0
- package/src/lib/relay/kickoff-text.js +1 -0
- package/src/openclaw/installer/cli.js +46 -1
- package/src/openclaw/installer/constants.js +1 -0
- package/src/openclaw/installer/core.js +234 -3
- package/src/openclaw/installer/doctor.js +2 -2
- package/src/openclaw/plugin/account-identity.js +1 -2
- package/src/openclaw/plugin/claworld-channel-plugin.js +270 -255
- package/src/openclaw/plugin/config-schema.js +9 -23
- package/src/openclaw/plugin/managed-config.js +284 -79
- package/src/openclaw/plugin/onboarding.js +22 -42
- package/src/openclaw/plugin/register.js +109 -10
- package/src/openclaw/plugin/relay-client.js +233 -17
- package/src/openclaw/runtime/backend-error-context.js +91 -0
- package/src/openclaw/runtime/feedback-helper.js +1 -2
- package/src/openclaw/runtime/product-shell-helper.js +43 -9
- package/src/openclaw/runtime/tool-contracts.js +26 -3
- package/src/openclaw/runtime/tool-inventory.js +7 -0
- package/src/openclaw/runtime/world-moderation-helper.js +3 -19
- package/src/product-shell/contracts/candidate-feed.js +7 -0
- package/src/product-shell/contracts/world-manifest.js +0 -1
- package/src/product-shell/contracts/world-orchestration.js +10 -1
- package/src/product-shell/conversation-feedback/conversation-feedback-service.js +261 -0
- package/src/product-shell/feedback/feedback-routes.js +0 -1
- package/src/product-shell/feedback/feedback-service.js +4 -9
- package/src/product-shell/index.js +40 -7
- package/src/product-shell/matching/matchmaking-service.js +22 -1
- package/src/product-shell/membership/membership-service.js +5 -1
- package/src/product-shell/onboarding/onboarding-service.js +10 -21
- package/src/product-shell/profile/public-identity-routes.js +60 -0
- package/src/product-shell/profile/public-identity-service.js +190 -0
- package/src/product-shell/search/search-service.js +9 -2
- package/src/product-shell/social/chat-request-service.js +22 -7
- package/src/product-shell/social/friend-routes.js +1 -1
- package/src/product-shell/social/friend-service.js +16 -19
- package/src/product-shell/social/social-routes.js +2 -2
- package/src/product-shell/social/social-service.js +31 -35
- package/src/product-shell/worlds/world-admin-service.js +31 -10
- package/src/product-shell/worlds/world-broadcast-service.js +2 -2
- package/src/lib/agent-address.js +0 -46
|
@@ -7,27 +7,16 @@ import {
|
|
|
7
7
|
resolveClaworldManagedRuntimeOptions,
|
|
8
8
|
} from './managed-config.js';
|
|
9
9
|
import {
|
|
10
|
-
LOCAL_AGENT_BOOTSTRAP_SCHEMA,
|
|
11
10
|
defaultClaworldAccountId,
|
|
12
11
|
inspectClaworldChannelAccount,
|
|
13
12
|
listClaworldAccountIds,
|
|
14
13
|
} from './config-schema.js';
|
|
15
|
-
import { parseAgentHandle } from '../../lib/agent-address.js';
|
|
16
14
|
import {
|
|
17
15
|
buildManagedOnboardingStatus as buildClaworldOnboardingStatus,
|
|
18
16
|
inspectManagedClaworldInstall,
|
|
19
17
|
seedManagedWorkspace as ensureManagedWorkspaceSeed,
|
|
20
18
|
} from '../installer/core.js';
|
|
21
19
|
|
|
22
|
-
const LOCAL_AGENT_CODE_REGEX = new RegExp(
|
|
23
|
-
LOCAL_AGENT_BOOTSTRAP_SCHEMA?.properties?.agentCode?.pattern || '^[A-Za-z0-9._:+~-]+$',
|
|
24
|
-
'i',
|
|
25
|
-
);
|
|
26
|
-
|
|
27
|
-
function isCanonicalAgentHandle(value) {
|
|
28
|
-
return Boolean(parseAgentHandle(value)?.canonical);
|
|
29
|
-
}
|
|
30
|
-
|
|
31
20
|
function collectUnsupportedSetupFlags(input = {}) {
|
|
32
21
|
const unsupported = [];
|
|
33
22
|
const flagMap = [
|
|
@@ -69,7 +58,7 @@ function validateClaworldSetupInput({ cfg = {}, accountId = null, input = {} } =
|
|
|
69
58
|
const unsupportedFlags = collectUnsupportedSetupFlags(input);
|
|
70
59
|
if (unsupportedFlags.length > 0) {
|
|
71
60
|
return (
|
|
72
|
-
'Claworld setup only supports --name, --http-url/--url, --app-token
|
|
61
|
+
'Claworld setup only supports --name, --http-url/--url, and --app-token. ' +
|
|
73
62
|
`Unsupported flag(s): ${unsupportedFlags.join(', ')}.`
|
|
74
63
|
);
|
|
75
64
|
}
|
|
@@ -92,24 +81,15 @@ function validateClaworldSetupInput({ cfg = {}, accountId = null, input = {} } =
|
|
|
92
81
|
}
|
|
93
82
|
}
|
|
94
83
|
|
|
95
|
-
const
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
84
|
+
const registrationDisplayName = normalizeText(
|
|
85
|
+
input.name,
|
|
86
|
+
normalizeText(
|
|
87
|
+
inspected?.registration?.displayName,
|
|
88
|
+
normalizeText(inspected?.localAgent?.displayName, null),
|
|
89
|
+
),
|
|
99
90
|
);
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
if (!appToken && !explicitRegistrationAgentCode && existingRegistrationAgentCode && !isCanonicalAgentHandle(existingRegistrationAgentCode)) {
|
|
103
|
-
return 'Existing Claworld registration.agentCode is legacy/raw. Re-run setup with --code <local@namespace> so the namespace stays explicit.';
|
|
104
|
-
}
|
|
105
|
-
if (!appToken && !registrationAgentCode) {
|
|
106
|
-
return 'Claworld identity handle is required unless you already have an appToken. Use --code <local@namespace> or --app-token <token>.';
|
|
107
|
-
}
|
|
108
|
-
if (!appToken && explicitRegistrationAgentCode && !LOCAL_AGENT_CODE_REGEX.test(explicitRegistrationAgentCode)) {
|
|
109
|
-
return 'Claworld identity handle must match ^[A-Za-z0-9._:+~-]+(?:@[A-Za-z0-9._:+~-]+)?$.';
|
|
110
|
-
}
|
|
111
|
-
if (!appToken && explicitRegistrationAgentCode && !isCanonicalAgentHandle(explicitRegistrationAgentCode)) {
|
|
112
|
-
return 'Claworld identity handle must use canonical local@namespace syntax (for example "xiaofafa@robin").';
|
|
91
|
+
if (!appToken && !registrationDisplayName) {
|
|
92
|
+
return 'Claworld public display name is required unless you already have an appToken. Use --name <display-name> or --app-token <token>.';
|
|
113
93
|
}
|
|
114
94
|
|
|
115
95
|
return null;
|
|
@@ -124,12 +104,12 @@ function currentManagedIdentityInput({ cfg = {}, accountId = null } = {}) {
|
|
|
124
104
|
};
|
|
125
105
|
}
|
|
126
106
|
|
|
127
|
-
const
|
|
128
|
-
inspected?.registration?.
|
|
129
|
-
normalizeText(inspected?.localAgent?.
|
|
107
|
+
const currentDisplayName = normalizeText(
|
|
108
|
+
inspected?.registration?.displayName,
|
|
109
|
+
normalizeText(inspected?.localAgent?.displayName, null),
|
|
130
110
|
);
|
|
131
|
-
return
|
|
132
|
-
? {
|
|
111
|
+
return currentDisplayName
|
|
112
|
+
? { name: currentDisplayName }
|
|
133
113
|
: {};
|
|
134
114
|
}
|
|
135
115
|
|
|
@@ -139,18 +119,18 @@ async function collectManagedIdentityInput({ cfg = {}, prompter, accountId = nul
|
|
|
139
119
|
return currentInput;
|
|
140
120
|
}
|
|
141
121
|
|
|
142
|
-
const
|
|
143
|
-
message: 'Choose
|
|
144
|
-
initialValue: currentInput.
|
|
145
|
-
placeholder: '
|
|
122
|
+
const name = await prompter.text({
|
|
123
|
+
message: 'Choose the public display name to bootstrap this Claworld agent',
|
|
124
|
+
initialValue: currentInput.name || '',
|
|
125
|
+
placeholder: 'Claworld Agent',
|
|
146
126
|
validate: (value) => {
|
|
147
|
-
const message = validateClaworldSetupInput({ input: {
|
|
127
|
+
const message = validateClaworldSetupInput({ input: { name: value } });
|
|
148
128
|
return message || undefined;
|
|
149
129
|
},
|
|
150
130
|
});
|
|
151
131
|
|
|
152
132
|
return {
|
|
153
|
-
|
|
133
|
+
name,
|
|
154
134
|
};
|
|
155
135
|
}
|
|
156
136
|
|
|
@@ -207,8 +187,8 @@ async function applyManagedOnboardingConfig({
|
|
|
207
187
|
const noteLines = [
|
|
208
188
|
`Bound local agent/account: ${managedOptions.agentId}`,
|
|
209
189
|
`Remote backend: ${managedOptions.serverUrl}`,
|
|
210
|
-
managedOptions.
|
|
211
|
-
? `Bootstrap mode: registration (${managedOptions.
|
|
190
|
+
managedOptions.registrationDisplayName
|
|
191
|
+
? `Bootstrap mode: registration (${managedOptions.registrationDisplayName})`
|
|
212
192
|
: 'Bootstrap mode: appToken/manual binding',
|
|
213
193
|
managedOptions.manageWorkspace
|
|
214
194
|
? 'This flow refreshes plugin-side config and the dedicated claworld workspace contract. It does not start a backend service.'
|
|
@@ -18,6 +18,11 @@ import {
|
|
|
18
18
|
logRuntimeBoundary,
|
|
19
19
|
normalizeRuntimeBoundaryError,
|
|
20
20
|
} from '../../lib/runtime-errors.js';
|
|
21
|
+
import {
|
|
22
|
+
normalizeBackendFieldError,
|
|
23
|
+
normalizeBackendMissingField,
|
|
24
|
+
normalizeBackendPublicIdentity,
|
|
25
|
+
} from '../runtime/backend-error-context.js';
|
|
21
26
|
|
|
22
27
|
const INTERNAL_REQUESTER_SESSION_KEY_PARAM = '__claworldRequesterSessionKey';
|
|
23
28
|
|
|
@@ -52,14 +57,30 @@ function buildPublicToolErrorExtras(error) {
|
|
|
52
57
|
const backendCode = normalizeText(context.backendCode, null);
|
|
53
58
|
const backendMessage = normalizeText(context.backendMessage, null);
|
|
54
59
|
const fieldErrors = Array.isArray(context.fieldErrors)
|
|
55
|
-
? context.fieldErrors
|
|
60
|
+
? context.fieldErrors
|
|
61
|
+
.map((fieldError) => normalizeBackendFieldError(fieldError) || normalizePublicFieldError(fieldError))
|
|
62
|
+
.filter(Boolean)
|
|
63
|
+
: [];
|
|
64
|
+
const requiredAction = normalizeText(context.requiredAction, null);
|
|
65
|
+
const nextAction = normalizeText(context.nextAction, null);
|
|
66
|
+
const nextTool = normalizeText(context.nextTool, null);
|
|
67
|
+
const missingFields = Array.isArray(context.missingFields)
|
|
68
|
+
? context.missingFields
|
|
69
|
+
.map((field) => normalizeBackendMissingField(field))
|
|
70
|
+
.filter(Boolean)
|
|
56
71
|
: [];
|
|
72
|
+
const publicIdentity = normalizeBackendPublicIdentity(context.publicIdentity);
|
|
57
73
|
|
|
58
74
|
const extra = {
|
|
59
75
|
...(Number.isInteger(httpStatus) && httpStatus > 0 ? { httpStatus } : {}),
|
|
60
76
|
...(backendCode ? { backendCode } : {}),
|
|
61
77
|
...(backendMessage ? { backendMessage } : {}),
|
|
62
78
|
...(fieldErrors.length > 0 ? { fieldErrors } : {}),
|
|
79
|
+
...(requiredAction ? { requiredAction } : {}),
|
|
80
|
+
...(nextAction ? { nextAction } : {}),
|
|
81
|
+
...(nextTool ? { nextTool } : {}),
|
|
82
|
+
...(missingFields.length > 0 ? { missingFields } : {}),
|
|
83
|
+
...(publicIdentity ? { publicIdentity } : {}),
|
|
63
84
|
};
|
|
64
85
|
|
|
65
86
|
return Object.keys(extra).length > 0 ? extra : null;
|
|
@@ -553,7 +574,7 @@ function buildRegisteredTools(api, plugin) {
|
|
|
553
574
|
...context,
|
|
554
575
|
displayName: params.displayName,
|
|
555
576
|
worldContextText: params.worldContextText,
|
|
556
|
-
enabled: params.enabled === true,
|
|
577
|
+
enabled: typeof params.enabled === 'boolean' ? params.enabled : true,
|
|
557
578
|
});
|
|
558
579
|
return buildToolResult(projectToolCreateWorldResponse(payload, { accountId: context.accountId }));
|
|
559
580
|
},
|
|
@@ -1009,10 +1030,6 @@ function buildRegisteredTools(api, plugin) {
|
|
|
1009
1030
|
description: 'Optional peer agentId related to the issue.',
|
|
1010
1031
|
examples: ['agt_runtime_candidate'],
|
|
1011
1032
|
}),
|
|
1012
|
-
targetAgentCode: stringParam({
|
|
1013
|
-
description: 'Optional compatibility agentCode for the peer.',
|
|
1014
|
-
examples: ['runtimecandidate@relay.local'],
|
|
1015
|
-
}),
|
|
1016
1033
|
tags: arrayParam({
|
|
1017
1034
|
description: 'Short labels used for moderation and triage filtering.',
|
|
1018
1035
|
maxItems: 10,
|
|
@@ -1110,6 +1127,15 @@ function buildRegisteredTools(api, plugin) {
|
|
|
1110
1127
|
accountId,
|
|
1111
1128
|
runtimeConfig,
|
|
1112
1129
|
});
|
|
1130
|
+
const pairedAgentId = payload.runtimeConfig?.relay?.agentId || payload.relayAgent?.agentId || null;
|
|
1131
|
+
const publicIdentity = pairedAgentId
|
|
1132
|
+
? await plugin.runtime.productShell.profile.getPublicIdentity({
|
|
1133
|
+
cfg,
|
|
1134
|
+
accountId,
|
|
1135
|
+
runtimeConfig,
|
|
1136
|
+
agentId: pairedAgentId,
|
|
1137
|
+
})
|
|
1138
|
+
: null;
|
|
1113
1139
|
return buildToolResult({
|
|
1114
1140
|
status: payload.status,
|
|
1115
1141
|
reason: payload.reason || null,
|
|
@@ -1117,17 +1143,90 @@ function buildRegisteredTools(api, plugin) {
|
|
|
1117
1143
|
bindingSource: payload.bindingSource || null,
|
|
1118
1144
|
relay: {
|
|
1119
1145
|
agentId: payload.runtimeConfig?.relay?.agentId || payload.relayAgent?.agentId || null,
|
|
1120
|
-
agentCode: payload.relayAgent?.agentCode || null,
|
|
1121
|
-
relayLocalCode: payload.relayAgent?.relayLocalCode || null,
|
|
1122
|
-
address: payload.relayAgent?.address || null,
|
|
1123
|
-
domain: payload.relayAgent?.domain || null,
|
|
1124
1146
|
displayName: payload.relayAgent?.displayName || null,
|
|
1125
1147
|
discoverable: payload.relayAgent?.discoverable ?? null,
|
|
1126
1148
|
contactable: payload.relayAgent?.contactable ?? null,
|
|
1127
1149
|
online: payload.relayAgent?.online ?? null,
|
|
1128
1150
|
resolved: payload.relayAgent?.resolved ?? null,
|
|
1129
1151
|
},
|
|
1152
|
+
publicIdentity: publicIdentity
|
|
1153
|
+
? {
|
|
1154
|
+
status: publicIdentity.status || null,
|
|
1155
|
+
ready: publicIdentity.ready ?? null,
|
|
1156
|
+
displayIdentity: publicIdentity.publicIdentity?.displayIdentity || null,
|
|
1157
|
+
displayName: publicIdentity.publicIdentity?.displayName || null,
|
|
1158
|
+
code: publicIdentity.publicIdentity?.code || null,
|
|
1159
|
+
nextAction: publicIdentity.nextAction || null,
|
|
1160
|
+
nextTool: publicIdentity.nextTool || null,
|
|
1161
|
+
missingFields: Array.isArray(publicIdentity.missingFields) ? publicIdentity.missingFields : [],
|
|
1162
|
+
recommendedDisplayName: publicIdentity.recommendedDisplayName || null,
|
|
1163
|
+
feedbackSummary: publicIdentity.feedbackSummary && typeof publicIdentity.feedbackSummary === 'object'
|
|
1164
|
+
? {
|
|
1165
|
+
totalLikesReceived: Number(publicIdentity.feedbackSummary.totalLikesReceived || 0),
|
|
1166
|
+
totalDislikesReceived: Number(publicIdentity.feedbackSummary.totalDislikesReceived || 0),
|
|
1167
|
+
totalLikesGiven: Number(publicIdentity.feedbackSummary.totalLikesGiven || 0),
|
|
1168
|
+
totalDislikesGiven: Number(publicIdentity.feedbackSummary.totalDislikesGiven || 0),
|
|
1169
|
+
}
|
|
1170
|
+
: null,
|
|
1171
|
+
}
|
|
1172
|
+
: null,
|
|
1173
|
+
});
|
|
1174
|
+
},
|
|
1175
|
+
},
|
|
1176
|
+
{
|
|
1177
|
+
name: 'claworld_get_public_identity',
|
|
1178
|
+
label: 'Claworld Get Public Identity',
|
|
1179
|
+
description: 'Read the current public identity state for the paired Claworld agent. Use this before world join or request-chat if you need to confirm whether public naming is complete.',
|
|
1180
|
+
metadata: buildToolMetadata({
|
|
1181
|
+
category: 'bootstrap',
|
|
1182
|
+
usageNotes: [
|
|
1183
|
+
'Use when pair_agent indicates public identity is still pending.',
|
|
1184
|
+
'Use before requesting a public-name confirmation from the user.',
|
|
1185
|
+
],
|
|
1186
|
+
}),
|
|
1187
|
+
parameters: objectParam({
|
|
1188
|
+
description: 'Read the current public identity state for one Claworld account.',
|
|
1189
|
+
required: ['accountId'],
|
|
1190
|
+
properties: {
|
|
1191
|
+
accountId: accountIdProperty,
|
|
1192
|
+
},
|
|
1193
|
+
}),
|
|
1194
|
+
async execute(_toolCallId, params = {}) {
|
|
1195
|
+
const context = await resolveToolContext(api, plugin, params);
|
|
1196
|
+
const payload = await plugin.runtime.productShell.profile.getPublicIdentity(context);
|
|
1197
|
+
return buildToolResult(payload);
|
|
1198
|
+
},
|
|
1199
|
+
},
|
|
1200
|
+
{
|
|
1201
|
+
name: 'claworld_update_public_identity',
|
|
1202
|
+
label: 'Claworld Update Public Identity',
|
|
1203
|
+
description: 'Set or update the public display name for the paired Claworld agent. On first setup, the backend will generate a stable unique code and return the final displayName#code identity.',
|
|
1204
|
+
metadata: buildToolMetadata({
|
|
1205
|
+
category: 'bootstrap',
|
|
1206
|
+
usageNotes: [
|
|
1207
|
+
'Use after the user confirms a public-facing name.',
|
|
1208
|
+
'On first setup, this generates the stable public code and completes identity readiness.',
|
|
1209
|
+
],
|
|
1210
|
+
}),
|
|
1211
|
+
parameters: objectParam({
|
|
1212
|
+
description: 'Update the public display name for one Claworld account.',
|
|
1213
|
+
required: ['accountId', 'displayName'],
|
|
1214
|
+
properties: {
|
|
1215
|
+
accountId: accountIdProperty,
|
|
1216
|
+
displayName: stringParam({
|
|
1217
|
+
description: 'Public-facing display name. # is reserved and must not appear here.',
|
|
1218
|
+
minLength: 1,
|
|
1219
|
+
examples: ['Moza', '小发发'],
|
|
1220
|
+
}),
|
|
1221
|
+
},
|
|
1222
|
+
}),
|
|
1223
|
+
async execute(_toolCallId, params = {}) {
|
|
1224
|
+
const context = await resolveToolContext(api, plugin, params);
|
|
1225
|
+
const payload = await plugin.runtime.productShell.profile.updatePublicIdentity({
|
|
1226
|
+
...context,
|
|
1227
|
+
displayName: params.displayName,
|
|
1130
1228
|
});
|
|
1229
|
+
return buildToolResult(payload);
|
|
1131
1230
|
},
|
|
1132
1231
|
},
|
|
1133
1232
|
].map((tool) => ({
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { EventEmitter } from 'events';
|
|
2
2
|
import WebSocket from 'ws';
|
|
3
|
+
import { v4 as uuidv4 } from 'uuid';
|
|
3
4
|
import { resolveClaworldRuntimeConfig } from './config-schema.js';
|
|
4
5
|
import { buildRuntimeAuthHeaders } from './account-identity.js';
|
|
5
6
|
import { createRelayEventProtocol } from '../protocol/relay-event-protocol.js';
|
|
@@ -43,6 +44,9 @@ function buildInboundEnvelope(message = {}) {
|
|
|
43
44
|
eventType: data.eventType || 'delivery',
|
|
44
45
|
deliveryId: data.deliveryId || null,
|
|
45
46
|
sessionKey: data.sessionKey || null,
|
|
47
|
+
createdAt: data.createdAt || null,
|
|
48
|
+
updatedAt: data.updatedAt || null,
|
|
49
|
+
turnCreatedAt: data.turnCreatedAt || null,
|
|
46
50
|
payload: data.payload && typeof data.payload === 'object' && !Array.isArray(data.payload)
|
|
47
51
|
? { ...data.payload }
|
|
48
52
|
: {},
|
|
@@ -56,6 +60,10 @@ function normalizeOptionalText(value) {
|
|
|
56
60
|
return normalized || null;
|
|
57
61
|
}
|
|
58
62
|
|
|
63
|
+
function resolveClientMessageId(value = null) {
|
|
64
|
+
return normalizeOptionalText(value) || `cmsg_${uuidv4()}`;
|
|
65
|
+
}
|
|
66
|
+
|
|
59
67
|
function buildReplyAckTimeoutError({ deliveryId, timeoutMs, context = {} } = {}) {
|
|
60
68
|
return createRuntimeBoundaryError({
|
|
61
69
|
code: 'relay_reply_ack_timeout',
|
|
@@ -68,6 +76,18 @@ function buildReplyAckTimeoutError({ deliveryId, timeoutMs, context = {} } = {})
|
|
|
68
76
|
});
|
|
69
77
|
}
|
|
70
78
|
|
|
79
|
+
function buildAcceptedAckTimeoutError({ deliveryId, timeoutMs, context = {} } = {}) {
|
|
80
|
+
return createRuntimeBoundaryError({
|
|
81
|
+
code: 'relay_delivery_accept_ack_timeout',
|
|
82
|
+
category: 'transport',
|
|
83
|
+
status: 504,
|
|
84
|
+
message: `timed out waiting for relay delivery acceptance acknowledgement for ${deliveryId || 'unknown-delivery'} after ${timeoutMs}ms`,
|
|
85
|
+
publicMessage: 'relay delivery acceptance acknowledgement timed out',
|
|
86
|
+
recoverable: true,
|
|
87
|
+
context,
|
|
88
|
+
});
|
|
89
|
+
}
|
|
90
|
+
|
|
71
91
|
function buildReplyFallbackError({
|
|
72
92
|
deliveryId,
|
|
73
93
|
status,
|
|
@@ -655,6 +675,34 @@ export class ClaworldRelayClient extends EventEmitter {
|
|
|
655
675
|
this.send({ type: 'heartbeat' });
|
|
656
676
|
}
|
|
657
677
|
|
|
678
|
+
sendAccepted({ deliveryId, sessionKey, source = 'runtime_dispatch' } = {}) {
|
|
679
|
+
const normalizedDeliveryId = normalizeOptionalText(deliveryId);
|
|
680
|
+
if (!normalizedDeliveryId) {
|
|
681
|
+
throw createRuntimeBoundaryError({
|
|
682
|
+
code: 'relay_delivery_id_required',
|
|
683
|
+
category: 'input',
|
|
684
|
+
status: 400,
|
|
685
|
+
message: 'deliveryId is required to acknowledge relay delivery acceptance',
|
|
686
|
+
publicMessage: 'deliveryId is required',
|
|
687
|
+
recoverable: true,
|
|
688
|
+
});
|
|
689
|
+
}
|
|
690
|
+
const envelope = {
|
|
691
|
+
deliveryId: normalizedDeliveryId,
|
|
692
|
+
sessionKey: normalizeOptionalText(sessionKey) || null,
|
|
693
|
+
source: normalizeOptionalText(source) || 'runtime_dispatch',
|
|
694
|
+
};
|
|
695
|
+
this.send({
|
|
696
|
+
type: 'accepted',
|
|
697
|
+
deliveryId: envelope.deliveryId,
|
|
698
|
+
sessionKey: envelope.sessionKey,
|
|
699
|
+
payload: {
|
|
700
|
+
source: envelope.source,
|
|
701
|
+
},
|
|
702
|
+
});
|
|
703
|
+
return envelope;
|
|
704
|
+
}
|
|
705
|
+
|
|
658
706
|
sendReply({ deliveryId, sessionKey, replyText, source = 'subagent' } = {}) {
|
|
659
707
|
const envelope = this.outbound.createReplyEnvelope({
|
|
660
708
|
deliveryId,
|
|
@@ -791,6 +839,85 @@ export class ClaworldRelayClient extends EventEmitter {
|
|
|
791
839
|
});
|
|
792
840
|
}
|
|
793
841
|
|
|
842
|
+
waitForAcceptedAck({ deliveryId, timeoutMs = DEFAULT_REPLY_ACK_TIMEOUT_MS } = {}) {
|
|
843
|
+
const normalizedDeliveryId = normalizeOptionalText(deliveryId);
|
|
844
|
+
if (!normalizedDeliveryId) {
|
|
845
|
+
return Promise.reject(createRuntimeBoundaryError({
|
|
846
|
+
code: 'relay_delivery_id_required',
|
|
847
|
+
category: 'input',
|
|
848
|
+
status: 400,
|
|
849
|
+
message: 'deliveryId is required to wait for relay delivery acceptance acknowledgement',
|
|
850
|
+
publicMessage: 'deliveryId is required',
|
|
851
|
+
recoverable: true,
|
|
852
|
+
}));
|
|
853
|
+
}
|
|
854
|
+
|
|
855
|
+
return new Promise((resolve, reject) => {
|
|
856
|
+
let settled = false;
|
|
857
|
+
let timeout = null;
|
|
858
|
+
|
|
859
|
+
const cleanup = () => {
|
|
860
|
+
if (timeout) clearTimeout(timeout);
|
|
861
|
+
this.off('delivery.accepted', onAccepted);
|
|
862
|
+
this.off('disconnect', onDisconnect);
|
|
863
|
+
this.off('close', onDisconnect);
|
|
864
|
+
};
|
|
865
|
+
|
|
866
|
+
const settleResolve = (value) => {
|
|
867
|
+
if (settled) return;
|
|
868
|
+
settled = true;
|
|
869
|
+
cleanup();
|
|
870
|
+
resolve(value);
|
|
871
|
+
};
|
|
872
|
+
|
|
873
|
+
const settleReject = (error) => {
|
|
874
|
+
if (settled) return;
|
|
875
|
+
settled = true;
|
|
876
|
+
cleanup();
|
|
877
|
+
reject(error);
|
|
878
|
+
};
|
|
879
|
+
|
|
880
|
+
const onAccepted = (message = {}) => {
|
|
881
|
+
const acceptedDeliveryId = normalizeOptionalText(message?.data?.acceptedDeliveryId);
|
|
882
|
+
if (acceptedDeliveryId !== normalizedDeliveryId) return;
|
|
883
|
+
settleResolve(message);
|
|
884
|
+
};
|
|
885
|
+
|
|
886
|
+
const onDisconnect = (info = {}) => {
|
|
887
|
+
settleReject(createRuntimeBoundaryError({
|
|
888
|
+
code: 'relay_delivery_accept_ack_disconnected',
|
|
889
|
+
category: 'transport',
|
|
890
|
+
status: 502,
|
|
891
|
+
message: `relay websocket closed before delivery acceptance acknowledgement for ${normalizedDeliveryId}`,
|
|
892
|
+
publicMessage: 'relay websocket closed before delivery acceptance acknowledgement',
|
|
893
|
+
recoverable: true,
|
|
894
|
+
context: this.buildBoundaryContext({
|
|
895
|
+
stage: 'delivery_accept_ack_wait',
|
|
896
|
+
deliveryId: normalizedDeliveryId,
|
|
897
|
+
closeCode: info?.code ?? null,
|
|
898
|
+
closeReason: info?.reason || null,
|
|
899
|
+
}),
|
|
900
|
+
}));
|
|
901
|
+
};
|
|
902
|
+
|
|
903
|
+
this.on('delivery.accepted', onAccepted);
|
|
904
|
+
this.on('disconnect', onDisconnect);
|
|
905
|
+
this.on('close', onDisconnect);
|
|
906
|
+
|
|
907
|
+
timeout = setTimeout(() => {
|
|
908
|
+
settleReject(buildAcceptedAckTimeoutError({
|
|
909
|
+
deliveryId: normalizedDeliveryId,
|
|
910
|
+
timeoutMs,
|
|
911
|
+
context: this.buildBoundaryContext({
|
|
912
|
+
stage: 'delivery_accept_ack_wait',
|
|
913
|
+
deliveryId: normalizedDeliveryId,
|
|
914
|
+
}),
|
|
915
|
+
}));
|
|
916
|
+
}, timeoutMs);
|
|
917
|
+
if (typeof timeout.unref === 'function') timeout.unref();
|
|
918
|
+
});
|
|
919
|
+
}
|
|
920
|
+
|
|
794
921
|
waitForKeepSilentAck({ deliveryId, timeoutMs = DEFAULT_REPLY_ACK_TIMEOUT_MS } = {}) {
|
|
795
922
|
const normalizedDeliveryId = normalizeOptionalText(deliveryId);
|
|
796
923
|
if (!normalizedDeliveryId) {
|
|
@@ -897,6 +1024,31 @@ export class ClaworldRelayClient extends EventEmitter {
|
|
|
897
1024
|
};
|
|
898
1025
|
}
|
|
899
1026
|
|
|
1027
|
+
async acceptDeliveryHttp({ deliveryId, sessionKey = null, source = 'runtime_dispatch' } = {}) {
|
|
1028
|
+
const normalizedDeliveryId = normalizeOptionalText(deliveryId);
|
|
1029
|
+
const result = await this.requestJson(`/v1/deliveries/${encodeURIComponent(normalizedDeliveryId)}/accepted`, {
|
|
1030
|
+
method: 'POST',
|
|
1031
|
+
headers: buildRuntimeAuthHeaders(this.runtimeConfig, { 'content-type': 'application/json' }),
|
|
1032
|
+
body: JSON.stringify({
|
|
1033
|
+
fromAgentId: this.boundAgentId,
|
|
1034
|
+
sessionKey: normalizeOptionalText(sessionKey) || null,
|
|
1035
|
+
source: normalizeOptionalText(source) || 'runtime_dispatch',
|
|
1036
|
+
}),
|
|
1037
|
+
}, {
|
|
1038
|
+
code: 'relay_delivery_accept_fallback_failed',
|
|
1039
|
+
message: 'failed to submit relay delivery acceptance fallback',
|
|
1040
|
+
publicMessage: 'failed to submit relay delivery acceptance fallback',
|
|
1041
|
+
});
|
|
1042
|
+
return {
|
|
1043
|
+
...result,
|
|
1044
|
+
envelope: {
|
|
1045
|
+
deliveryId: normalizedDeliveryId,
|
|
1046
|
+
sessionKey: normalizeOptionalText(sessionKey) || null,
|
|
1047
|
+
source: normalizeOptionalText(source) || 'runtime_dispatch',
|
|
1048
|
+
},
|
|
1049
|
+
};
|
|
1050
|
+
}
|
|
1051
|
+
|
|
900
1052
|
async sendReplyAndWaitForAck({
|
|
901
1053
|
deliveryId,
|
|
902
1054
|
sessionKey,
|
|
@@ -984,6 +1136,75 @@ export class ClaworldRelayClient extends EventEmitter {
|
|
|
984
1136
|
}
|
|
985
1137
|
}
|
|
986
1138
|
|
|
1139
|
+
async sendAcceptedAndWaitForAck({
|
|
1140
|
+
deliveryId,
|
|
1141
|
+
sessionKey,
|
|
1142
|
+
source = 'runtime_dispatch',
|
|
1143
|
+
timeoutMs = DEFAULT_REPLY_ACK_TIMEOUT_MS,
|
|
1144
|
+
httpFallback = true,
|
|
1145
|
+
} = {}) {
|
|
1146
|
+
const envelope = this.sendAccepted({
|
|
1147
|
+
deliveryId,
|
|
1148
|
+
sessionKey,
|
|
1149
|
+
source,
|
|
1150
|
+
});
|
|
1151
|
+
|
|
1152
|
+
try {
|
|
1153
|
+
const ack = await this.waitForAcceptedAck({
|
|
1154
|
+
deliveryId: envelope.deliveryId,
|
|
1155
|
+
timeoutMs,
|
|
1156
|
+
});
|
|
1157
|
+
return {
|
|
1158
|
+
ok: true,
|
|
1159
|
+
envelope,
|
|
1160
|
+
ack,
|
|
1161
|
+
transport: 'websocket',
|
|
1162
|
+
fallbackUsed: false,
|
|
1163
|
+
};
|
|
1164
|
+
} catch (error) {
|
|
1165
|
+
if (!httpFallback) throw error;
|
|
1166
|
+
|
|
1167
|
+
this.logger.warn?.('[claworld:relay-client] delivery acceptance websocket acknowledgement failed; attempting HTTP fallback', {
|
|
1168
|
+
accountId: this.runtimeConfig?.accountId || null,
|
|
1169
|
+
agentId: this.boundAgentId,
|
|
1170
|
+
deliveryId: envelope.deliveryId,
|
|
1171
|
+
sessionKey: envelope.sessionKey,
|
|
1172
|
+
error: error?.message || String(error),
|
|
1173
|
+
});
|
|
1174
|
+
|
|
1175
|
+
const fallbackResult = await this.acceptDeliveryHttp({
|
|
1176
|
+
deliveryId: envelope.deliveryId,
|
|
1177
|
+
sessionKey: envelope.sessionKey,
|
|
1178
|
+
source: envelope.source,
|
|
1179
|
+
});
|
|
1180
|
+
|
|
1181
|
+
if (fallbackResult.status >= 200 && fallbackResult.status < 300) {
|
|
1182
|
+
return {
|
|
1183
|
+
ok: true,
|
|
1184
|
+
envelope,
|
|
1185
|
+
ack: {
|
|
1186
|
+
event: 'delivery.accepted',
|
|
1187
|
+
data: fallbackResult.body,
|
|
1188
|
+
},
|
|
1189
|
+
transport: 'http',
|
|
1190
|
+
fallbackUsed: true,
|
|
1191
|
+
};
|
|
1192
|
+
}
|
|
1193
|
+
|
|
1194
|
+
throw buildReplyFallbackError({
|
|
1195
|
+
deliveryId: envelope.deliveryId,
|
|
1196
|
+
status: fallbackResult.status,
|
|
1197
|
+
body: fallbackResult.body,
|
|
1198
|
+
context: this.buildBoundaryContext({
|
|
1199
|
+
stage: 'delivery_accept_fallback',
|
|
1200
|
+
deliveryId: envelope.deliveryId,
|
|
1201
|
+
sessionKey: envelope.sessionKey,
|
|
1202
|
+
fallbackFrom: error?.code || error?.message || null,
|
|
1203
|
+
}),
|
|
1204
|
+
});
|
|
1205
|
+
}
|
|
1206
|
+
}
|
|
1207
|
+
|
|
987
1208
|
async keepDeliverySilentHttp({ deliveryId, reason = null, source = 'openclaw-autochain' } = {}) {
|
|
988
1209
|
const normalizedDeliveryId = normalizeOptionalText(deliveryId);
|
|
989
1210
|
const result = await this.requestJson(`/v1/deliveries/${encodeURIComponent(normalizedDeliveryId)}/kept-silent`, {
|
|
@@ -1096,24 +1317,14 @@ export class ClaworldRelayClient extends EventEmitter {
|
|
|
1096
1317
|
}
|
|
1097
1318
|
}
|
|
1098
1319
|
|
|
1099
|
-
async createChatRequest({ fromAgentId,
|
|
1100
|
-
const target = await this.requestJson(`/v1/agents/resolve?address=${encodeURIComponent(toAddress)}`, {
|
|
1101
|
-
method: 'GET',
|
|
1102
|
-
headers: buildRuntimeAuthHeaders(this.runtimeConfig),
|
|
1103
|
-
}, {
|
|
1104
|
-
code: 'relay_target_resolve_failed',
|
|
1105
|
-
message: 'failed to resolve relay target',
|
|
1106
|
-
publicMessage: 'failed to resolve relay target',
|
|
1107
|
-
});
|
|
1108
|
-
if (target.status >= 400) return target;
|
|
1109
|
-
|
|
1320
|
+
async createChatRequest({ fromAgentId, targetAgentId, requestContext = {} } = {}) {
|
|
1110
1321
|
const normalized = normalizeChatRequestInput({ requestContext, source: 'direct_lookup' });
|
|
1111
1322
|
return await this.requestJson('/v1/chat-requests', {
|
|
1112
1323
|
method: 'POST',
|
|
1113
1324
|
headers: buildRuntimeAuthHeaders(this.runtimeConfig, { 'content-type': 'application/json' }),
|
|
1114
1325
|
body: JSON.stringify({
|
|
1115
1326
|
fromAgentId,
|
|
1116
|
-
targetAgentId
|
|
1327
|
+
targetAgentId,
|
|
1117
1328
|
kickoffBrief: normalized.kickoffBrief || null,
|
|
1118
1329
|
openingMessage: normalized.openingMessage || null,
|
|
1119
1330
|
worldId: normalized.conversation?.worldId || null,
|
|
@@ -1164,16 +1375,21 @@ export class ClaworldRelayClient extends EventEmitter {
|
|
|
1164
1375
|
});
|
|
1165
1376
|
}
|
|
1166
1377
|
|
|
1167
|
-
async deliverMessage({ fromAgentId,
|
|
1168
|
-
|
|
1378
|
+
async deliverMessage({ fromAgentId, targetAgentId, clientMessageId = null, payload = {}, conversation = {} } = {}) {
|
|
1379
|
+
const resolvedClientMessageId = resolveClientMessageId(clientMessageId);
|
|
1380
|
+
const result = await this.requestJson('/v1/messages', {
|
|
1169
1381
|
method: 'POST',
|
|
1170
1382
|
headers: buildRuntimeAuthHeaders(this.runtimeConfig, { 'content-type': 'application/json' }),
|
|
1171
|
-
body: JSON.stringify({ fromAgentId,
|
|
1383
|
+
body: JSON.stringify({ fromAgentId, targetAgentId, clientMessageId: resolvedClientMessageId, payload, conversation }),
|
|
1172
1384
|
}, {
|
|
1173
1385
|
code: 'relay_message_delivery_failed',
|
|
1174
1386
|
message: 'failed to deliver relay message',
|
|
1175
1387
|
publicMessage: 'failed to deliver relay message',
|
|
1176
1388
|
});
|
|
1389
|
+
return {
|
|
1390
|
+
...result,
|
|
1391
|
+
clientMessageId: resolvedClientMessageId,
|
|
1392
|
+
};
|
|
1177
1393
|
}
|
|
1178
1394
|
|
|
1179
1395
|
waitFor(eventNameOrPredicate, timeoutMs = 8000) {
|
|
@@ -1201,7 +1417,7 @@ export class ClaworldRelayClient extends EventEmitter {
|
|
|
1201
1417
|
});
|
|
1202
1418
|
}
|
|
1203
1419
|
|
|
1204
|
-
async establishConversation({ fromAgentId,
|
|
1420
|
+
async establishConversation({ fromAgentId, targetAgentId, requestContext = {}, openingPayload = {} } = {}) {
|
|
1205
1421
|
const normalizedRequestContext = requestContext && typeof requestContext === 'object' && !Array.isArray(requestContext)
|
|
1206
1422
|
? { ...requestContext }
|
|
1207
1423
|
: {};
|
|
@@ -1217,7 +1433,7 @@ export class ClaworldRelayClient extends EventEmitter {
|
|
|
1217
1433
|
|
|
1218
1434
|
const requestResult = await this.createChatRequest({
|
|
1219
1435
|
fromAgentId,
|
|
1220
|
-
|
|
1436
|
+
targetAgentId,
|
|
1221
1437
|
requestContext: normalizedRequestContext,
|
|
1222
1438
|
});
|
|
1223
1439
|
if (requestResult.status !== 201) {
|