@xfxstudio/claworld 2026.4.30-testing.1 → 2026.4.30-testing.3
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/openclaw.plugin.json
CHANGED
package/package.json
CHANGED
|
@@ -132,6 +132,35 @@ function resolveNormalizedText(value, fallback = null) {
|
|
|
132
132
|
return normalizeClaworldText(value, fallback);
|
|
133
133
|
}
|
|
134
134
|
|
|
135
|
+
function resolveInboundMessageId({ delivery = {}, payload = {}, metadata = {} } = {}) {
|
|
136
|
+
const notification = payload.notification && typeof payload.notification === 'object' && !Array.isArray(payload.notification)
|
|
137
|
+
? payload.notification
|
|
138
|
+
: delivery.notification && typeof delivery.notification === 'object' && !Array.isArray(delivery.notification)
|
|
139
|
+
? delivery.notification
|
|
140
|
+
: {};
|
|
141
|
+
const candidates = [
|
|
142
|
+
delivery.deliveryId,
|
|
143
|
+
delivery.inboxItemId,
|
|
144
|
+
delivery.messageId,
|
|
145
|
+
delivery.eventId,
|
|
146
|
+
delivery.notificationId,
|
|
147
|
+
payload.deliveryId,
|
|
148
|
+
payload.inboxItemId,
|
|
149
|
+
payload.messageId,
|
|
150
|
+
payload.eventId,
|
|
151
|
+
payload.notificationId,
|
|
152
|
+
metadata.messageId,
|
|
153
|
+
metadata.eventId,
|
|
154
|
+
metadata.notificationId,
|
|
155
|
+
notification.notificationId,
|
|
156
|
+
];
|
|
157
|
+
for (const candidate of candidates) {
|
|
158
|
+
const normalized = resolveNormalizedText(candidate, null);
|
|
159
|
+
if (normalized) return normalized;
|
|
160
|
+
}
|
|
161
|
+
return null;
|
|
162
|
+
}
|
|
163
|
+
|
|
135
164
|
function isAgentScopedSessionKey(sessionKey) {
|
|
136
165
|
return /^agent:[^:]+:/i.test(String(sessionKey || ''));
|
|
137
166
|
}
|
|
@@ -274,6 +303,7 @@ function parseBridgeTimestampMs(value) {
|
|
|
274
303
|
|
|
275
304
|
function resolveBridgeDeliveryTimestampMs({ delivery = {}, metadata = {} } = {}) {
|
|
276
305
|
return parseBridgeTimestampMs(delivery?.createdAt)
|
|
306
|
+
|| parseBridgeTimestampMs(delivery?.availableAt)
|
|
277
307
|
|| parseBridgeTimestampMs(delivery?.turnCreatedAt)
|
|
278
308
|
|| parseBridgeTimestampMs(metadata?.createdAt)
|
|
279
309
|
|| Date.now();
|
|
@@ -1752,6 +1782,7 @@ function buildDeliveryInboundEnvelope({
|
|
|
1752
1782
|
commandText = null,
|
|
1753
1783
|
timestamp = null,
|
|
1754
1784
|
deliveryId,
|
|
1785
|
+
eventType = 'delivery',
|
|
1755
1786
|
sessionKey,
|
|
1756
1787
|
localSessionKey = null,
|
|
1757
1788
|
worldId = null,
|
|
@@ -1770,13 +1801,16 @@ function buildDeliveryInboundEnvelope({
|
|
|
1770
1801
|
const normalizedCommandText = String(commandText || '').trim();
|
|
1771
1802
|
const commandBody = normalizedCommandText || rawBody;
|
|
1772
1803
|
const bodyForAgent = bodyText || rawBody;
|
|
1804
|
+
const eventLabel = normalizePluginOptionalText(eventType) === 'delivery'
|
|
1805
|
+
? 'delivery'
|
|
1806
|
+
: `event ${normalizePluginOptionalText(eventType)}`;
|
|
1773
1807
|
const contextLines = mergeUntrustedContextLines([
|
|
1774
1808
|
`[claworld peer ${remoteLabel}]`,
|
|
1775
1809
|
...(worldId ? [`[claworld world ${worldId}]`] : []),
|
|
1776
1810
|
...(conversationKey ? [`[claworld conversation ${conversationKey}]`] : []),
|
|
1777
1811
|
...(localSessionKey && localSessionKey !== sessionKey ? [`[claworld local session ${localSessionKey}]`] : []),
|
|
1778
1812
|
`[claworld relay session ${sessionKey}]`,
|
|
1779
|
-
`[claworld
|
|
1813
|
+
`[claworld ${eventLabel} ${deliveryId}]`,
|
|
1780
1814
|
], untrustedContext);
|
|
1781
1815
|
const envelopeTimestamp = Number.isFinite(timestamp) ? new Date(timestamp) : new Date();
|
|
1782
1816
|
|
|
@@ -2173,7 +2207,7 @@ function resolveBoundLocalAgentId({ cfg = {}, runtimeConfig = {}, relayClient }
|
|
|
2173
2207
|
|| 'main';
|
|
2174
2208
|
}
|
|
2175
2209
|
|
|
2176
|
-
async function
|
|
2210
|
+
async function maybeBridgeRuntimeInboundEvent({
|
|
2177
2211
|
relayClient,
|
|
2178
2212
|
runtimeConfig,
|
|
2179
2213
|
runtimeAccountId,
|
|
@@ -2192,36 +2226,46 @@ async function maybeBridgeRuntimeDelivery({
|
|
|
2192
2226
|
const payload = delivery.payload && typeof delivery.payload === 'object' && !Array.isArray(delivery.payload)
|
|
2193
2227
|
? delivery.payload
|
|
2194
2228
|
: {};
|
|
2195
|
-
const
|
|
2229
|
+
const eventType = resolveNormalizedText(delivery.eventType, resolveNormalizedText(event?.eventType, 'delivery'));
|
|
2230
|
+
const deliveryId = resolveInboundMessageId({ delivery, payload, metadata });
|
|
2196
2231
|
const sessionKey = resolveNormalizedText(delivery.sessionKey, null);
|
|
2197
2232
|
const contextText = resolveNormalizedText(payload.contextText, null);
|
|
2198
2233
|
const incomingText = resolveNormalizedText(
|
|
2199
2234
|
payload.commandText,
|
|
2200
|
-
contextText ? null : resolveNormalizedText(payload.text, null),
|
|
2235
|
+
contextText ? null : resolveNormalizedText(payload.text, resolveNormalizedText(payload.body, null)),
|
|
2201
2236
|
);
|
|
2202
2237
|
const commandText = resolveNormalizedText(payload.commandText, incomingText);
|
|
2203
2238
|
const fromAgentId = resolveNormalizedText(metadata.fromAgentId, null);
|
|
2204
|
-
const
|
|
2239
|
+
const routeSessionKind = resolveNormalizedText(
|
|
2240
|
+
event?.route?.sessionKind,
|
|
2241
|
+
resolveNormalizedText(delivery.sessionKind, resolveNormalizedText(payload.sessionKind, null)),
|
|
2242
|
+
);
|
|
2243
|
+
const isRelayDelivery = eventType === 'delivery';
|
|
2244
|
+
const allowReply = metadata.allowReply === true || (isRelayDelivery && metadata.allowReply !== false);
|
|
2245
|
+
const remoteIdentity = fromAgentId
|
|
2246
|
+
|| resolveNormalizedText(metadata.source, routeSessionKind === 'management' ? 'claworld-management' : 'unknown-peer');
|
|
2205
2247
|
|
|
2206
2248
|
if (
|
|
2207
2249
|
!runtime?.channel?.reply?.finalizeInboundContext
|
|
2208
2250
|
|| !runtime?.channel?.reply?.dispatchReplyFromConfig
|
|
2209
2251
|
|| !runtime?.channel?.reply?.createReplyDispatcherWithTyping
|
|
2210
2252
|
) {
|
|
2211
|
-
logger.warn?.(`[claworld:${runtimeAccountId}] skipping
|
|
2253
|
+
logger.warn?.(`[claworld:${runtimeAccountId}] skipping inbound bridge: missing runtime bridge hooks`, {
|
|
2254
|
+
eventType,
|
|
2212
2255
|
deliveryId,
|
|
2213
2256
|
sessionKey,
|
|
2214
2257
|
});
|
|
2215
2258
|
return { skipped: true, reason: 'missing_runtime_bridge_hooks' };
|
|
2216
2259
|
}
|
|
2217
2260
|
if (!deliveryId || !sessionKey || (!incomingText && !contextText)) {
|
|
2218
|
-
logger.warn?.(`[claworld:${runtimeAccountId}] skipping
|
|
2261
|
+
logger.warn?.(`[claworld:${runtimeAccountId}] skipping inbound bridge: missing payload`, {
|
|
2262
|
+
eventType,
|
|
2219
2263
|
deliveryId,
|
|
2220
2264
|
sessionKey,
|
|
2221
2265
|
hasIncomingText: Boolean(incomingText),
|
|
2222
2266
|
hasContextText: Boolean(contextText),
|
|
2223
2267
|
});
|
|
2224
|
-
return { skipped: true, reason: '
|
|
2268
|
+
return { skipped: true, reason: 'missing_inbound_payload' };
|
|
2225
2269
|
}
|
|
2226
2270
|
|
|
2227
2271
|
const loadedCfg = await runtime.config?.loadConfig?.() || {};
|
|
@@ -2247,7 +2291,7 @@ async function maybeBridgeRuntimeDelivery({
|
|
|
2247
2291
|
fallbackTarget: runtimeConfig.routing?.fallbackTarget,
|
|
2248
2292
|
}) || null;
|
|
2249
2293
|
const worldId = resolveDeliveryWorldId(delivery);
|
|
2250
|
-
const commandAuthorized = shouldAuthorizeBridgedCommand({
|
|
2294
|
+
const commandAuthorized = isRelayDelivery && shouldAuthorizeBridgedCommand({
|
|
2251
2295
|
runtimeConfig,
|
|
2252
2296
|
incomingText: commandText || incomingText,
|
|
2253
2297
|
});
|
|
@@ -2261,6 +2305,7 @@ async function maybeBridgeRuntimeDelivery({
|
|
|
2261
2305
|
commandText,
|
|
2262
2306
|
timestamp: inboundTimestamp,
|
|
2263
2307
|
deliveryId,
|
|
2308
|
+
eventType,
|
|
2264
2309
|
sessionKey,
|
|
2265
2310
|
localSessionKey,
|
|
2266
2311
|
worldId,
|
|
@@ -2268,6 +2313,9 @@ async function maybeBridgeRuntimeDelivery({
|
|
|
2268
2313
|
untrustedContext: payload.untrustedContext,
|
|
2269
2314
|
});
|
|
2270
2315
|
const localIdentity = normalizeClaworldText(runtimeConfig.relay?.agentId, runtimeConfig.accountId);
|
|
2316
|
+
const isManagementSession = routeSessionKind === 'management';
|
|
2317
|
+
const senderName = isManagementSession ? 'Claworld' : remoteIdentity;
|
|
2318
|
+
const conversationLabel = isManagementSession ? 'Claworld management' : remoteIdentity;
|
|
2271
2319
|
const inboundCtx = runtime.channel.reply.finalizeInboundContext({
|
|
2272
2320
|
Body,
|
|
2273
2321
|
RawBody,
|
|
@@ -2282,18 +2330,18 @@ async function maybeBridgeRuntimeDelivery({
|
|
|
2282
2330
|
OriginatingChannel: 'claworld',
|
|
2283
2331
|
OriginatingFrom: remoteIdentity,
|
|
2284
2332
|
OriginatingTo: remoteIdentity,
|
|
2285
|
-
ChatType: 'direct',
|
|
2286
|
-
SenderName:
|
|
2333
|
+
ChatType: isManagementSession ? 'management' : 'direct',
|
|
2334
|
+
SenderName: senderName,
|
|
2287
2335
|
SenderId: remoteIdentity,
|
|
2288
2336
|
MessageId: deliveryId,
|
|
2289
2337
|
Provider: 'claworld',
|
|
2290
2338
|
Surface: 'claworld',
|
|
2291
|
-
ConversationLabel:
|
|
2339
|
+
ConversationLabel: conversationLabel,
|
|
2292
2340
|
Timestamp: inboundTimestamp,
|
|
2293
2341
|
MessageSid: deliveryId,
|
|
2294
2342
|
WasMentioned: false,
|
|
2295
2343
|
CommandAuthorized: commandAuthorized,
|
|
2296
|
-
RelayDeliveryId: deliveryId,
|
|
2344
|
+
RelayDeliveryId: isRelayDelivery ? deliveryId : null,
|
|
2297
2345
|
RelayFromAgentId: fromAgentId,
|
|
2298
2346
|
UntrustedContext,
|
|
2299
2347
|
});
|
|
@@ -2308,6 +2356,7 @@ async function maybeBridgeRuntimeDelivery({
|
|
|
2308
2356
|
ctx: inboundCtx,
|
|
2309
2357
|
onRecordError: (error) => {
|
|
2310
2358
|
logger.error?.(`[claworld:${runtimeAccountId}] failed to record inbound session`, {
|
|
2359
|
+
eventType,
|
|
2311
2360
|
deliveryId,
|
|
2312
2361
|
sessionKey,
|
|
2313
2362
|
localSessionKey,
|
|
@@ -2318,7 +2367,8 @@ async function maybeBridgeRuntimeDelivery({
|
|
|
2318
2367
|
});
|
|
2319
2368
|
}
|
|
2320
2369
|
|
|
2321
|
-
logger.info?.(`[claworld:${runtimeAccountId}] routing delivery into runtime session`, {
|
|
2370
|
+
logger.info?.(`[claworld:${runtimeAccountId}] ${isRelayDelivery ? 'routing delivery into runtime session' : 'routing inbound event into runtime session'}`, {
|
|
2371
|
+
eventType,
|
|
2322
2372
|
deliveryId,
|
|
2323
2373
|
sessionKey,
|
|
2324
2374
|
localSessionKey,
|
|
@@ -2327,27 +2377,29 @@ async function maybeBridgeRuntimeDelivery({
|
|
|
2327
2377
|
routeStatus: routed?.status || null,
|
|
2328
2378
|
bodyPreview: String(Body || '').slice(0, 240),
|
|
2329
2379
|
rawBodyPreview: String(RawBody || '').slice(0, 240),
|
|
2330
|
-
allowReply
|
|
2380
|
+
allowReply,
|
|
2331
2381
|
commandAuthorized,
|
|
2332
2382
|
});
|
|
2333
2383
|
|
|
2334
|
-
|
|
2335
|
-
|
|
2336
|
-
|
|
2337
|
-
|
|
2338
|
-
|
|
2339
|
-
|
|
2340
|
-
|
|
2341
|
-
|
|
2384
|
+
if (isRelayDelivery && metadata.acceptanceRequired !== false) {
|
|
2385
|
+
try {
|
|
2386
|
+
const acceptedResult = await relayClient.acceptDeliveryHttp({
|
|
2387
|
+
deliveryId,
|
|
2388
|
+
sessionKey,
|
|
2389
|
+
source: 'runtime_dispatch',
|
|
2390
|
+
});
|
|
2391
|
+
if (acceptedResult.status < 200 || acceptedResult.status >= 300) {
|
|
2392
|
+
throw new Error(`failed to submit relay delivery acceptance: ${acceptedResult.status}`);
|
|
2393
|
+
}
|
|
2394
|
+
} catch (error) {
|
|
2395
|
+
logger.warn?.(`[claworld:${runtimeAccountId}] delivery acceptance acknowledgement failed`, {
|
|
2396
|
+
deliveryId,
|
|
2397
|
+
sessionKey,
|
|
2398
|
+
localSessionKey,
|
|
2399
|
+
localAgentId,
|
|
2400
|
+
error: error?.message || String(error),
|
|
2401
|
+
});
|
|
2342
2402
|
}
|
|
2343
|
-
} catch (error) {
|
|
2344
|
-
logger.warn?.(`[claworld:${runtimeAccountId}] delivery acceptance acknowledgement failed`, {
|
|
2345
|
-
deliveryId,
|
|
2346
|
-
sessionKey,
|
|
2347
|
-
localSessionKey,
|
|
2348
|
-
localAgentId,
|
|
2349
|
-
error: error?.message || String(error),
|
|
2350
|
-
});
|
|
2351
2403
|
}
|
|
2352
2404
|
|
|
2353
2405
|
let {
|
|
@@ -2362,15 +2414,16 @@ async function maybeBridgeRuntimeDelivery({
|
|
|
2362
2414
|
deliveryId,
|
|
2363
2415
|
sessionKey,
|
|
2364
2416
|
localAgentId,
|
|
2365
|
-
allowReply
|
|
2417
|
+
allowReply,
|
|
2366
2418
|
logger,
|
|
2367
2419
|
runtimeAccountId,
|
|
2368
2420
|
inboundCtx,
|
|
2369
2421
|
});
|
|
2370
2422
|
|
|
2371
2423
|
const shouldRetryKickoffDispatch = (
|
|
2372
|
-
|
|
2373
|
-
&& metadata.
|
|
2424
|
+
isRelayDelivery
|
|
2425
|
+
&& metadata.deliveryType === 'kickoff'
|
|
2426
|
+
&& allowReply
|
|
2374
2427
|
&& replied !== true
|
|
2375
2428
|
&& runtimeOutputSummary.counts.final > 0
|
|
2376
2429
|
&& runtimeOutputSummary.counts.nonRenderableFinal > 0
|
|
@@ -2407,14 +2460,15 @@ async function maybeBridgeRuntimeDelivery({
|
|
|
2407
2460
|
deliveryId,
|
|
2408
2461
|
sessionKey,
|
|
2409
2462
|
localAgentId,
|
|
2410
|
-
allowReply
|
|
2463
|
+
allowReply,
|
|
2411
2464
|
logger,
|
|
2412
2465
|
runtimeAccountId,
|
|
2413
2466
|
inboundCtx,
|
|
2414
2467
|
}));
|
|
2415
2468
|
}
|
|
2416
2469
|
|
|
2417
|
-
logger.info?.(`[claworld:${runtimeAccountId}] delivery bridge completed`, {
|
|
2470
|
+
logger.info?.(`[claworld:${runtimeAccountId}] ${isRelayDelivery ? 'delivery bridge completed' : 'inbound bridge completed'}`, {
|
|
2471
|
+
eventType,
|
|
2418
2472
|
deliveryId,
|
|
2419
2473
|
sessionKey,
|
|
2420
2474
|
localSessionKey,
|
|
@@ -2874,9 +2928,9 @@ export function createClaworldChannelPlugin({
|
|
|
2874
2928
|
sessionKey: event?.delivery?.sessionKey || null,
|
|
2875
2929
|
});
|
|
2876
2930
|
|
|
2877
|
-
if (event?.
|
|
2931
|
+
if (event?.delivery?.sessionKey) {
|
|
2878
2932
|
const runtimeContext = accountRuntimeContexts.get(accountKey) || {};
|
|
2879
|
-
|
|
2933
|
+
maybeBridgeRuntimeInboundEvent({
|
|
2880
2934
|
relayClient,
|
|
2881
2935
|
runtimeConfig,
|
|
2882
2936
|
runtimeAccountId,
|
|
@@ -2886,7 +2940,7 @@ export function createClaworldChannelPlugin({
|
|
|
2886
2940
|
cfg: runtimeContext.cfg,
|
|
2887
2941
|
inbound,
|
|
2888
2942
|
}).catch((error) => {
|
|
2889
|
-
logger.error?.(`[claworld:${runtimeAccountId}]
|
|
2943
|
+
logger.error?.(`[claworld:${runtimeAccountId}] inbound bridge exception`, {
|
|
2890
2944
|
error: error?.message || String(error),
|
|
2891
2945
|
});
|
|
2892
2946
|
});
|
|
@@ -5,6 +5,51 @@ export const STALE_CONNECTION_CLOSE_CODE = 4002;
|
|
|
5
5
|
export const TERMINAL_CLOSE_REASONS = new Set(['duplicate_connection_replaced', 'stale_connection']);
|
|
6
6
|
export const DEFAULT_REPLY_ACK_TIMEOUT_MS = 5000;
|
|
7
7
|
|
|
8
|
+
function cloneObject(value, fallback = {}) {
|
|
9
|
+
if (!value || typeof value !== 'object' || Array.isArray(value)) return { ...fallback };
|
|
10
|
+
return { ...value };
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
function normalizeEnvelopeText(value, fallback = null) {
|
|
14
|
+
if (value == null) return fallback;
|
|
15
|
+
const normalized = String(value).trim();
|
|
16
|
+
return normalized || fallback;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
function resolveEnvelopeMessageId(data = {}, payload = {}) {
|
|
20
|
+
const notification = payload.notification && typeof payload.notification === 'object' && !Array.isArray(payload.notification)
|
|
21
|
+
? payload.notification
|
|
22
|
+
: data.notification && typeof data.notification === 'object' && !Array.isArray(data.notification)
|
|
23
|
+
? data.notification
|
|
24
|
+
: {};
|
|
25
|
+
const metadata = data.metadata && typeof data.metadata === 'object' && !Array.isArray(data.metadata)
|
|
26
|
+
? data.metadata
|
|
27
|
+
: payload.metadata && typeof payload.metadata === 'object' && !Array.isArray(payload.metadata)
|
|
28
|
+
? payload.metadata
|
|
29
|
+
: {};
|
|
30
|
+
const candidates = [
|
|
31
|
+
data.deliveryId,
|
|
32
|
+
data.inboxItemId,
|
|
33
|
+
data.messageId,
|
|
34
|
+
data.eventId,
|
|
35
|
+
data.notificationId,
|
|
36
|
+
payload.deliveryId,
|
|
37
|
+
payload.inboxItemId,
|
|
38
|
+
payload.messageId,
|
|
39
|
+
payload.eventId,
|
|
40
|
+
payload.notificationId,
|
|
41
|
+
metadata.messageId,
|
|
42
|
+
metadata.eventId,
|
|
43
|
+
metadata.notificationId,
|
|
44
|
+
notification.notificationId,
|
|
45
|
+
];
|
|
46
|
+
for (const candidate of candidates) {
|
|
47
|
+
const normalized = normalizeEnvelopeText(candidate, null);
|
|
48
|
+
if (normalized) return normalized;
|
|
49
|
+
}
|
|
50
|
+
return null;
|
|
51
|
+
}
|
|
52
|
+
|
|
8
53
|
export function normalizeRelayWebSocketUrl(serverUrl) {
|
|
9
54
|
const parsed = new URL(serverUrl);
|
|
10
55
|
if (parsed.protocol === 'http:') parsed.protocol = 'ws:';
|
|
@@ -23,20 +68,63 @@ export function normalizeRelayWebSocketUrl(serverUrl) {
|
|
|
23
68
|
|
|
24
69
|
export function buildInboundEnvelope(message = {}) {
|
|
25
70
|
const data = message.data || {};
|
|
26
|
-
|
|
71
|
+
const directPayload = data.payload && typeof data.payload === 'object' && !Array.isArray(data.payload)
|
|
72
|
+
? { ...data.payload }
|
|
73
|
+
: {};
|
|
27
74
|
const metadata = data.metadata && typeof data.metadata === 'object' && !Array.isArray(data.metadata)
|
|
28
75
|
? { ...data.metadata }
|
|
29
|
-
:
|
|
76
|
+
: directPayload.metadata && typeof directPayload.metadata === 'object' && !Array.isArray(directPayload.metadata)
|
|
77
|
+
? { ...directPayload.metadata }
|
|
78
|
+
: cloneObject(data.meta, {});
|
|
79
|
+
const payloadEventType = normalizeEnvelopeText(directPayload.eventType, null);
|
|
80
|
+
const dataEventType = normalizeEnvelopeText(data.eventType, null);
|
|
81
|
+
const eventType = dataEventType
|
|
82
|
+
|| payloadEventType
|
|
83
|
+
|| (message.event === 'delivery' ? 'delivery' : normalizeEnvelopeText(message.event, null));
|
|
84
|
+
const payload = Object.keys(directPayload).length > 0
|
|
85
|
+
? { ...directPayload }
|
|
86
|
+
: cloneObject(data, {});
|
|
87
|
+
if (Object.keys(directPayload).length > 0) {
|
|
88
|
+
for (const key of [
|
|
89
|
+
'eventType',
|
|
90
|
+
'eventName',
|
|
91
|
+
'sessionKind',
|
|
92
|
+
'sessionKey',
|
|
93
|
+
'targetSessionKey',
|
|
94
|
+
'targetAgentId',
|
|
95
|
+
'text',
|
|
96
|
+
'body',
|
|
97
|
+
'notification',
|
|
98
|
+
]) {
|
|
99
|
+
if (payload[key] == null && data[key] != null) payload[key] = data[key];
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
const targetAgentId = normalizeEnvelopeText(
|
|
103
|
+
data.targetAgentId,
|
|
104
|
+
normalizeEnvelopeText(payload.targetAgentId, null),
|
|
105
|
+
);
|
|
106
|
+
const sessionKey = normalizeEnvelopeText(
|
|
107
|
+
data.sessionKey,
|
|
108
|
+
normalizeEnvelopeText(
|
|
109
|
+
payload.sessionKey,
|
|
110
|
+
normalizeEnvelopeText(
|
|
111
|
+
data.targetSessionKey,
|
|
112
|
+
normalizeEnvelopeText(payload.targetSessionKey, null),
|
|
113
|
+
),
|
|
114
|
+
),
|
|
115
|
+
);
|
|
116
|
+
const isDeliveryEvent = message.event === 'delivery';
|
|
117
|
+
const isRoutableEvent = Boolean(eventType && sessionKey);
|
|
118
|
+
if (!isDeliveryEvent && !isRoutableEvent) return null;
|
|
30
119
|
return {
|
|
31
|
-
eventType:
|
|
32
|
-
deliveryId: data
|
|
33
|
-
sessionKey
|
|
34
|
-
|
|
120
|
+
eventType: eventType || 'delivery',
|
|
121
|
+
deliveryId: resolveEnvelopeMessageId(data, payload),
|
|
122
|
+
sessionKey,
|
|
123
|
+
targetAgentId,
|
|
124
|
+
createdAt: data.createdAt || data.availableAt || null,
|
|
35
125
|
updatedAt: data.updatedAt || null,
|
|
36
126
|
turnCreatedAt: data.turnCreatedAt || null,
|
|
37
|
-
payload
|
|
38
|
-
? { ...data.payload }
|
|
39
|
-
: {},
|
|
127
|
+
payload,
|
|
40
128
|
metadata,
|
|
41
129
|
};
|
|
42
130
|
}
|
|
@@ -557,19 +557,23 @@ export class ClaworldRelayClient extends EventEmitter {
|
|
|
557
557
|
this.heartbeatTimer = null;
|
|
558
558
|
}
|
|
559
559
|
|
|
560
|
+
buildWsNotConnectedError(stage = 'send') {
|
|
561
|
+
return createRuntimeBoundaryError({
|
|
562
|
+
code: 'relay_ws_not_connected',
|
|
563
|
+
category: 'transport',
|
|
564
|
+
status: 409,
|
|
565
|
+
message: 'relay websocket is not connected',
|
|
566
|
+
publicMessage: 'relay websocket is not connected',
|
|
567
|
+
recoverable: true,
|
|
568
|
+
context: this.buildBoundaryContext({
|
|
569
|
+
stage,
|
|
570
|
+
}),
|
|
571
|
+
});
|
|
572
|
+
}
|
|
573
|
+
|
|
560
574
|
send(payload) {
|
|
561
575
|
if (!this.ws || this.ws.readyState !== 1) {
|
|
562
|
-
throw
|
|
563
|
-
code: 'relay_ws_not_connected',
|
|
564
|
-
category: 'transport',
|
|
565
|
-
status: 409,
|
|
566
|
-
message: 'relay websocket is not connected',
|
|
567
|
-
publicMessage: 'relay websocket is not connected',
|
|
568
|
-
recoverable: true,
|
|
569
|
-
context: this.buildBoundaryContext({
|
|
570
|
-
stage: 'send',
|
|
571
|
-
}),
|
|
572
|
-
});
|
|
576
|
+
throw this.buildWsNotConnectedError('send');
|
|
573
577
|
}
|
|
574
578
|
this.ws.send(JSON.stringify(payload));
|
|
575
579
|
}
|
|
@@ -663,7 +667,7 @@ export class ClaworldRelayClient extends EventEmitter {
|
|
|
663
667
|
return envelope;
|
|
664
668
|
}
|
|
665
669
|
|
|
666
|
-
waitForReplyAck({ deliveryId, timeoutMs = DEFAULT_REPLY_ACK_TIMEOUT_MS } = {}) {
|
|
670
|
+
waitForReplyAck({ deliveryId, timeoutMs = DEFAULT_REPLY_ACK_TIMEOUT_MS, signal = null } = {}) {
|
|
667
671
|
const normalizedDeliveryId = normalizeOptionalText(deliveryId);
|
|
668
672
|
if (!normalizedDeliveryId) {
|
|
669
673
|
return Promise.reject(createRuntimeBoundaryError({
|
|
@@ -675,6 +679,20 @@ export class ClaworldRelayClient extends EventEmitter {
|
|
|
675
679
|
recoverable: true,
|
|
676
680
|
}));
|
|
677
681
|
}
|
|
682
|
+
if (signal?.aborted) {
|
|
683
|
+
return Promise.reject(createRuntimeBoundaryError({
|
|
684
|
+
code: 'relay_reply_ack_wait_cancelled',
|
|
685
|
+
category: 'transport',
|
|
686
|
+
status: 499,
|
|
687
|
+
message: `relay reply acknowledgement wait cancelled for ${normalizedDeliveryId}`,
|
|
688
|
+
publicMessage: 'relay reply acknowledgement wait cancelled',
|
|
689
|
+
recoverable: true,
|
|
690
|
+
context: this.buildBoundaryContext({
|
|
691
|
+
stage: 'reply_ack_wait',
|
|
692
|
+
deliveryId: normalizedDeliveryId,
|
|
693
|
+
}),
|
|
694
|
+
}));
|
|
695
|
+
}
|
|
678
696
|
|
|
679
697
|
return new Promise((resolve, reject) => {
|
|
680
698
|
let settled = false;
|
|
@@ -686,6 +704,7 @@ export class ClaworldRelayClient extends EventEmitter {
|
|
|
686
704
|
this.off('command.accepted', onCommandAccepted);
|
|
687
705
|
this.off('disconnect', onDisconnect);
|
|
688
706
|
this.off('close', onDisconnect);
|
|
707
|
+
signal?.removeEventListener('abort', onAbort);
|
|
689
708
|
};
|
|
690
709
|
|
|
691
710
|
const settleResolve = (value) => {
|
|
@@ -739,10 +758,26 @@ export class ClaworldRelayClient extends EventEmitter {
|
|
|
739
758
|
}));
|
|
740
759
|
};
|
|
741
760
|
|
|
761
|
+
const onAbort = () => {
|
|
762
|
+
settleReject(createRuntimeBoundaryError({
|
|
763
|
+
code: 'relay_reply_ack_wait_cancelled',
|
|
764
|
+
category: 'transport',
|
|
765
|
+
status: 499,
|
|
766
|
+
message: `relay reply acknowledgement wait cancelled for ${normalizedDeliveryId}`,
|
|
767
|
+
publicMessage: 'relay reply acknowledgement wait cancelled',
|
|
768
|
+
recoverable: true,
|
|
769
|
+
context: this.buildBoundaryContext({
|
|
770
|
+
stage: 'reply_ack_wait',
|
|
771
|
+
deliveryId: normalizedDeliveryId,
|
|
772
|
+
}),
|
|
773
|
+
}));
|
|
774
|
+
};
|
|
775
|
+
|
|
742
776
|
this.on('reply.accepted', onReplyAccepted);
|
|
743
777
|
this.on('command.accepted', onCommandAccepted);
|
|
744
778
|
this.on('disconnect', onDisconnect);
|
|
745
779
|
this.on('close', onDisconnect);
|
|
780
|
+
signal?.addEventListener('abort', onAbort, { once: true });
|
|
746
781
|
|
|
747
782
|
timeout = setTimeout(() => {
|
|
748
783
|
settleReject(buildReplyAckTimeoutError({
|
|
@@ -837,7 +872,7 @@ export class ClaworldRelayClient extends EventEmitter {
|
|
|
837
872
|
});
|
|
838
873
|
}
|
|
839
874
|
|
|
840
|
-
waitForKeepSilentAck({ deliveryId, timeoutMs = DEFAULT_REPLY_ACK_TIMEOUT_MS } = {}) {
|
|
875
|
+
waitForKeepSilentAck({ deliveryId, timeoutMs = DEFAULT_REPLY_ACK_TIMEOUT_MS, signal = null } = {}) {
|
|
841
876
|
const normalizedDeliveryId = normalizeOptionalText(deliveryId);
|
|
842
877
|
if (!normalizedDeliveryId) {
|
|
843
878
|
return Promise.reject(createRuntimeBoundaryError({
|
|
@@ -849,6 +884,20 @@ export class ClaworldRelayClient extends EventEmitter {
|
|
|
849
884
|
recoverable: true,
|
|
850
885
|
}));
|
|
851
886
|
}
|
|
887
|
+
if (signal?.aborted) {
|
|
888
|
+
return Promise.reject(createRuntimeBoundaryError({
|
|
889
|
+
code: 'relay_kept_silent_ack_wait_cancelled',
|
|
890
|
+
category: 'transport',
|
|
891
|
+
status: 499,
|
|
892
|
+
message: `relay kept_silent acknowledgement wait cancelled for ${normalizedDeliveryId}`,
|
|
893
|
+
publicMessage: 'relay kept_silent acknowledgement wait cancelled',
|
|
894
|
+
recoverable: true,
|
|
895
|
+
context: this.buildBoundaryContext({
|
|
896
|
+
stage: 'kept_silent_ack_wait',
|
|
897
|
+
deliveryId: normalizedDeliveryId,
|
|
898
|
+
}),
|
|
899
|
+
}));
|
|
900
|
+
}
|
|
852
901
|
|
|
853
902
|
return new Promise((resolve, reject) => {
|
|
854
903
|
let settled = false;
|
|
@@ -859,6 +908,7 @@ export class ClaworldRelayClient extends EventEmitter {
|
|
|
859
908
|
this.off('kept_silent.accepted', onKeptSilentAccepted);
|
|
860
909
|
this.off('disconnect', onDisconnect);
|
|
861
910
|
this.off('close', onDisconnect);
|
|
911
|
+
signal?.removeEventListener('abort', onAbort);
|
|
862
912
|
};
|
|
863
913
|
|
|
864
914
|
const settleResolve = (value) => {
|
|
@@ -898,9 +948,25 @@ export class ClaworldRelayClient extends EventEmitter {
|
|
|
898
948
|
}));
|
|
899
949
|
};
|
|
900
950
|
|
|
951
|
+
const onAbort = () => {
|
|
952
|
+
settleReject(createRuntimeBoundaryError({
|
|
953
|
+
code: 'relay_kept_silent_ack_wait_cancelled',
|
|
954
|
+
category: 'transport',
|
|
955
|
+
status: 499,
|
|
956
|
+
message: `relay kept_silent acknowledgement wait cancelled for ${normalizedDeliveryId}`,
|
|
957
|
+
publicMessage: 'relay kept_silent acknowledgement wait cancelled',
|
|
958
|
+
recoverable: true,
|
|
959
|
+
context: this.buildBoundaryContext({
|
|
960
|
+
stage: 'kept_silent_ack_wait',
|
|
961
|
+
deliveryId: normalizedDeliveryId,
|
|
962
|
+
}),
|
|
963
|
+
}));
|
|
964
|
+
};
|
|
965
|
+
|
|
901
966
|
this.on('kept_silent.accepted', onKeptSilentAccepted);
|
|
902
967
|
this.on('disconnect', onDisconnect);
|
|
903
968
|
this.on('close', onDisconnect);
|
|
969
|
+
signal?.addEventListener('abort', onAbort, { once: true });
|
|
904
970
|
|
|
905
971
|
timeout = setTimeout(() => {
|
|
906
972
|
settleReject(buildKeepSilentAckTimeoutError({
|
|
@@ -943,6 +1009,69 @@ export class ClaworldRelayClient extends EventEmitter {
|
|
|
943
1009
|
};
|
|
944
1010
|
}
|
|
945
1011
|
|
|
1012
|
+
async submitReplyHttpFallback({
|
|
1013
|
+
deliveryId,
|
|
1014
|
+
sessionKey,
|
|
1015
|
+
replyText,
|
|
1016
|
+
source = 'subagent',
|
|
1017
|
+
error = null,
|
|
1018
|
+
} = {}) {
|
|
1019
|
+
this.logger.warn?.('[claworld:relay-client] reply websocket transport failed; attempting HTTP fallback', {
|
|
1020
|
+
accountId: this.runtimeConfig?.accountId || null,
|
|
1021
|
+
agentId: this.boundAgentId,
|
|
1022
|
+
deliveryId: normalizeOptionalText(deliveryId),
|
|
1023
|
+
sessionKey: normalizeOptionalText(sessionKey) || null,
|
|
1024
|
+
error: error?.message || String(error),
|
|
1025
|
+
});
|
|
1026
|
+
|
|
1027
|
+
const fallbackResult = await this.replyToDeliveryHttp({
|
|
1028
|
+
deliveryId,
|
|
1029
|
+
replyText,
|
|
1030
|
+
source,
|
|
1031
|
+
});
|
|
1032
|
+
|
|
1033
|
+
if (fallbackResult.status >= 200 && fallbackResult.status < 300) {
|
|
1034
|
+
return {
|
|
1035
|
+
ok: true,
|
|
1036
|
+
envelope: fallbackResult.envelope,
|
|
1037
|
+
ack: {
|
|
1038
|
+
event: 'reply.accepted',
|
|
1039
|
+
data: fallbackResult.body,
|
|
1040
|
+
},
|
|
1041
|
+
transport: 'http',
|
|
1042
|
+
fallbackUsed: true,
|
|
1043
|
+
};
|
|
1044
|
+
}
|
|
1045
|
+
|
|
1046
|
+
if (isReplyAlreadyApplied(fallbackResult, fallbackResult.envelope.deliveryId)) {
|
|
1047
|
+
return {
|
|
1048
|
+
ok: true,
|
|
1049
|
+
envelope: fallbackResult.envelope,
|
|
1050
|
+
ack: {
|
|
1051
|
+
event: 'reply.accepted',
|
|
1052
|
+
data: {
|
|
1053
|
+
...(fallbackResult.body && typeof fallbackResult.body === 'object' ? fallbackResult.body : {}),
|
|
1054
|
+
repliedDeliveryId: fallbackResult.envelope.deliveryId,
|
|
1055
|
+
},
|
|
1056
|
+
},
|
|
1057
|
+
transport: 'http-already-applied',
|
|
1058
|
+
fallbackUsed: true,
|
|
1059
|
+
};
|
|
1060
|
+
}
|
|
1061
|
+
|
|
1062
|
+
throw buildReplyFallbackError({
|
|
1063
|
+
deliveryId: fallbackResult.envelope.deliveryId,
|
|
1064
|
+
status: fallbackResult.status,
|
|
1065
|
+
body: fallbackResult.body,
|
|
1066
|
+
context: this.buildBoundaryContext({
|
|
1067
|
+
stage: 'reply_fallback',
|
|
1068
|
+
deliveryId: fallbackResult.envelope.deliveryId,
|
|
1069
|
+
sessionKey: normalizeOptionalText(sessionKey) || null,
|
|
1070
|
+
fallbackFrom: error?.code || error?.message || null,
|
|
1071
|
+
}),
|
|
1072
|
+
});
|
|
1073
|
+
}
|
|
1074
|
+
|
|
946
1075
|
async acceptDeliveryHttp({ deliveryId, sessionKey = null, source = 'runtime_dispatch' } = {}) {
|
|
947
1076
|
const normalizedDeliveryId = normalizeOptionalText(deliveryId);
|
|
948
1077
|
const result = await this.requestJsonWithDeliveryVisibilityRetry(`/v1/runtime-deliveries/${encodeURIComponent(normalizedDeliveryId)}/accepted`, {
|
|
@@ -976,16 +1105,43 @@ export class ClaworldRelayClient extends EventEmitter {
|
|
|
976
1105
|
timeoutMs = DEFAULT_REPLY_ACK_TIMEOUT_MS,
|
|
977
1106
|
httpFallback = true,
|
|
978
1107
|
} = {}) {
|
|
1108
|
+
if (httpFallback && (!this.ws || this.ws.readyState !== 1)) {
|
|
1109
|
+
return await this.submitReplyHttpFallback({
|
|
1110
|
+
deliveryId,
|
|
1111
|
+
sessionKey,
|
|
1112
|
+
replyText,
|
|
1113
|
+
source,
|
|
1114
|
+
error: this.buildWsNotConnectedError('reply_send'),
|
|
1115
|
+
});
|
|
1116
|
+
}
|
|
1117
|
+
|
|
1118
|
+
const ackAbortController = new AbortController();
|
|
979
1119
|
const ackPromise = this.waitForReplyAck({
|
|
980
1120
|
deliveryId,
|
|
981
1121
|
timeoutMs,
|
|
1122
|
+
signal: ackAbortController.signal,
|
|
982
1123
|
});
|
|
983
|
-
|
|
984
|
-
|
|
985
|
-
|
|
986
|
-
|
|
987
|
-
|
|
988
|
-
|
|
1124
|
+
let envelope;
|
|
1125
|
+
|
|
1126
|
+
try {
|
|
1127
|
+
envelope = this.sendReply({
|
|
1128
|
+
deliveryId,
|
|
1129
|
+
sessionKey,
|
|
1130
|
+
replyText,
|
|
1131
|
+
source,
|
|
1132
|
+
});
|
|
1133
|
+
} catch (error) {
|
|
1134
|
+
ackAbortController.abort();
|
|
1135
|
+
void ackPromise.catch(() => {});
|
|
1136
|
+
if (!httpFallback) throw error;
|
|
1137
|
+
return await this.submitReplyHttpFallback({
|
|
1138
|
+
deliveryId,
|
|
1139
|
+
sessionKey,
|
|
1140
|
+
replyText,
|
|
1141
|
+
source,
|
|
1142
|
+
error,
|
|
1143
|
+
});
|
|
1144
|
+
}
|
|
989
1145
|
|
|
990
1146
|
try {
|
|
991
1147
|
const ack = await ackPromise;
|
|
@@ -999,59 +1155,12 @@ export class ClaworldRelayClient extends EventEmitter {
|
|
|
999
1155
|
} catch (error) {
|
|
1000
1156
|
if (!httpFallback) throw error;
|
|
1001
1157
|
|
|
1002
|
-
this.
|
|
1003
|
-
accountId: this.runtimeConfig?.accountId || null,
|
|
1004
|
-
agentId: this.boundAgentId,
|
|
1158
|
+
return await this.submitReplyHttpFallback({
|
|
1005
1159
|
deliveryId: envelope.deliveryId,
|
|
1006
1160
|
sessionKey: envelope.sessionKey,
|
|
1007
|
-
error: error?.message || String(error),
|
|
1008
|
-
});
|
|
1009
|
-
|
|
1010
|
-
const fallbackResult = await this.replyToDeliveryHttp({
|
|
1011
|
-
deliveryId: envelope.deliveryId,
|
|
1012
1161
|
replyText,
|
|
1013
1162
|
source,
|
|
1014
|
-
|
|
1015
|
-
|
|
1016
|
-
if (fallbackResult.status >= 200 && fallbackResult.status < 300) {
|
|
1017
|
-
return {
|
|
1018
|
-
ok: true,
|
|
1019
|
-
envelope,
|
|
1020
|
-
ack: {
|
|
1021
|
-
event: 'reply.accepted',
|
|
1022
|
-
data: fallbackResult.body,
|
|
1023
|
-
},
|
|
1024
|
-
transport: 'http',
|
|
1025
|
-
fallbackUsed: true,
|
|
1026
|
-
};
|
|
1027
|
-
}
|
|
1028
|
-
|
|
1029
|
-
if (isReplyAlreadyApplied(fallbackResult, envelope.deliveryId)) {
|
|
1030
|
-
return {
|
|
1031
|
-
ok: true,
|
|
1032
|
-
envelope,
|
|
1033
|
-
ack: {
|
|
1034
|
-
event: 'reply.accepted',
|
|
1035
|
-
data: {
|
|
1036
|
-
...(fallbackResult.body && typeof fallbackResult.body === 'object' ? fallbackResult.body : {}),
|
|
1037
|
-
repliedDeliveryId: envelope.deliveryId,
|
|
1038
|
-
},
|
|
1039
|
-
},
|
|
1040
|
-
transport: 'http-already-applied',
|
|
1041
|
-
fallbackUsed: true,
|
|
1042
|
-
};
|
|
1043
|
-
}
|
|
1044
|
-
|
|
1045
|
-
throw buildReplyFallbackError({
|
|
1046
|
-
deliveryId: envelope.deliveryId,
|
|
1047
|
-
status: fallbackResult.status,
|
|
1048
|
-
body: fallbackResult.body,
|
|
1049
|
-
context: this.buildBoundaryContext({
|
|
1050
|
-
stage: 'reply_fallback',
|
|
1051
|
-
deliveryId: envelope.deliveryId,
|
|
1052
|
-
sessionKey: envelope.sessionKey,
|
|
1053
|
-
fallbackFrom: error?.code || error?.message || null,
|
|
1054
|
-
}),
|
|
1163
|
+
error,
|
|
1055
1164
|
});
|
|
1056
1165
|
}
|
|
1057
1166
|
}
|
|
@@ -1169,6 +1278,69 @@ export class ClaworldRelayClient extends EventEmitter {
|
|
|
1169
1278
|
};
|
|
1170
1279
|
}
|
|
1171
1280
|
|
|
1281
|
+
async submitKeepSilentHttpFallback({
|
|
1282
|
+
deliveryId,
|
|
1283
|
+
sessionKey,
|
|
1284
|
+
reason = null,
|
|
1285
|
+
source = 'openclaw-autochain',
|
|
1286
|
+
error = null,
|
|
1287
|
+
} = {}) {
|
|
1288
|
+
this.logger.warn?.('[claworld:relay-client] kept_silent websocket transport failed; attempting HTTP fallback', {
|
|
1289
|
+
accountId: this.runtimeConfig?.accountId || null,
|
|
1290
|
+
agentId: this.boundAgentId,
|
|
1291
|
+
deliveryId: normalizeOptionalText(deliveryId),
|
|
1292
|
+
sessionKey: normalizeOptionalText(sessionKey) || null,
|
|
1293
|
+
error: error?.message || String(error),
|
|
1294
|
+
});
|
|
1295
|
+
|
|
1296
|
+
const fallbackResult = await this.keepDeliverySilentHttp({
|
|
1297
|
+
deliveryId,
|
|
1298
|
+
reason,
|
|
1299
|
+
source,
|
|
1300
|
+
});
|
|
1301
|
+
|
|
1302
|
+
if (fallbackResult.status >= 200 && fallbackResult.status < 300) {
|
|
1303
|
+
return {
|
|
1304
|
+
ok: true,
|
|
1305
|
+
envelope: fallbackResult.envelope,
|
|
1306
|
+
ack: {
|
|
1307
|
+
event: 'kept_silent.accepted',
|
|
1308
|
+
data: fallbackResult.body,
|
|
1309
|
+
},
|
|
1310
|
+
transport: 'http',
|
|
1311
|
+
fallbackUsed: true,
|
|
1312
|
+
};
|
|
1313
|
+
}
|
|
1314
|
+
|
|
1315
|
+
if (isDeliveryKeptSilentAlreadyApplied(fallbackResult, fallbackResult.envelope.deliveryId)) {
|
|
1316
|
+
return {
|
|
1317
|
+
ok: true,
|
|
1318
|
+
envelope: fallbackResult.envelope,
|
|
1319
|
+
ack: {
|
|
1320
|
+
event: 'kept_silent.accepted',
|
|
1321
|
+
data: {
|
|
1322
|
+
...(fallbackResult.body && typeof fallbackResult.body === 'object' ? fallbackResult.body : {}),
|
|
1323
|
+
keptSilentDeliveryId: fallbackResult.envelope.deliveryId,
|
|
1324
|
+
},
|
|
1325
|
+
},
|
|
1326
|
+
transport: 'http-already-applied',
|
|
1327
|
+
fallbackUsed: true,
|
|
1328
|
+
};
|
|
1329
|
+
}
|
|
1330
|
+
|
|
1331
|
+
throw buildKeepSilentFallbackError({
|
|
1332
|
+
deliveryId: fallbackResult.envelope.deliveryId,
|
|
1333
|
+
status: fallbackResult.status,
|
|
1334
|
+
body: fallbackResult.body,
|
|
1335
|
+
context: this.buildBoundaryContext({
|
|
1336
|
+
stage: 'kept_silent_fallback',
|
|
1337
|
+
deliveryId: fallbackResult.envelope.deliveryId,
|
|
1338
|
+
sessionKey: normalizeOptionalText(sessionKey) || null,
|
|
1339
|
+
fallbackFrom: error?.code || error?.message || null,
|
|
1340
|
+
}),
|
|
1341
|
+
});
|
|
1342
|
+
}
|
|
1343
|
+
|
|
1172
1344
|
async sendKeepSilentAndWaitForAck({
|
|
1173
1345
|
deliveryId,
|
|
1174
1346
|
sessionKey,
|
|
@@ -1177,16 +1349,43 @@ export class ClaworldRelayClient extends EventEmitter {
|
|
|
1177
1349
|
timeoutMs = DEFAULT_REPLY_ACK_TIMEOUT_MS,
|
|
1178
1350
|
httpFallback = true,
|
|
1179
1351
|
} = {}) {
|
|
1352
|
+
if (httpFallback && (!this.ws || this.ws.readyState !== 1)) {
|
|
1353
|
+
return await this.submitKeepSilentHttpFallback({
|
|
1354
|
+
deliveryId,
|
|
1355
|
+
sessionKey,
|
|
1356
|
+
reason,
|
|
1357
|
+
source,
|
|
1358
|
+
error: this.buildWsNotConnectedError('kept_silent_send'),
|
|
1359
|
+
});
|
|
1360
|
+
}
|
|
1361
|
+
|
|
1362
|
+
const ackAbortController = new AbortController();
|
|
1180
1363
|
const ackPromise = this.waitForKeepSilentAck({
|
|
1181
1364
|
deliveryId,
|
|
1182
1365
|
timeoutMs,
|
|
1366
|
+
signal: ackAbortController.signal,
|
|
1183
1367
|
});
|
|
1184
|
-
|
|
1185
|
-
|
|
1186
|
-
|
|
1187
|
-
|
|
1188
|
-
|
|
1189
|
-
|
|
1368
|
+
let envelope;
|
|
1369
|
+
|
|
1370
|
+
try {
|
|
1371
|
+
envelope = this.sendKeepSilent({
|
|
1372
|
+
deliveryId,
|
|
1373
|
+
sessionKey,
|
|
1374
|
+
reason,
|
|
1375
|
+
source,
|
|
1376
|
+
});
|
|
1377
|
+
} catch (error) {
|
|
1378
|
+
ackAbortController.abort();
|
|
1379
|
+
void ackPromise.catch(() => {});
|
|
1380
|
+
if (!httpFallback) throw error;
|
|
1381
|
+
return await this.submitKeepSilentHttpFallback({
|
|
1382
|
+
deliveryId,
|
|
1383
|
+
sessionKey,
|
|
1384
|
+
reason,
|
|
1385
|
+
source,
|
|
1386
|
+
error,
|
|
1387
|
+
});
|
|
1388
|
+
}
|
|
1190
1389
|
|
|
1191
1390
|
try {
|
|
1192
1391
|
const ack = await ackPromise;
|
|
@@ -1200,59 +1399,12 @@ export class ClaworldRelayClient extends EventEmitter {
|
|
|
1200
1399
|
} catch (error) {
|
|
1201
1400
|
if (!httpFallback) throw error;
|
|
1202
1401
|
|
|
1203
|
-
this.
|
|
1204
|
-
accountId: this.runtimeConfig?.accountId || null,
|
|
1205
|
-
agentId: this.boundAgentId,
|
|
1402
|
+
return await this.submitKeepSilentHttpFallback({
|
|
1206
1403
|
deliveryId: envelope.deliveryId,
|
|
1207
1404
|
sessionKey: envelope.sessionKey,
|
|
1208
|
-
error: error?.message || String(error),
|
|
1209
|
-
});
|
|
1210
|
-
|
|
1211
|
-
const fallbackResult = await this.keepDeliverySilentHttp({
|
|
1212
|
-
deliveryId: envelope.deliveryId,
|
|
1213
1405
|
reason: envelope.reason,
|
|
1214
1406
|
source: envelope.source,
|
|
1215
|
-
|
|
1216
|
-
|
|
1217
|
-
if (fallbackResult.status >= 200 && fallbackResult.status < 300) {
|
|
1218
|
-
return {
|
|
1219
|
-
ok: true,
|
|
1220
|
-
envelope,
|
|
1221
|
-
ack: {
|
|
1222
|
-
event: 'kept_silent.accepted',
|
|
1223
|
-
data: fallbackResult.body,
|
|
1224
|
-
},
|
|
1225
|
-
transport: 'http',
|
|
1226
|
-
fallbackUsed: true,
|
|
1227
|
-
};
|
|
1228
|
-
}
|
|
1229
|
-
|
|
1230
|
-
if (isDeliveryKeptSilentAlreadyApplied(fallbackResult, envelope.deliveryId)) {
|
|
1231
|
-
return {
|
|
1232
|
-
ok: true,
|
|
1233
|
-
envelope,
|
|
1234
|
-
ack: {
|
|
1235
|
-
event: 'kept_silent.accepted',
|
|
1236
|
-
data: {
|
|
1237
|
-
...(fallbackResult.body && typeof fallbackResult.body === 'object' ? fallbackResult.body : {}),
|
|
1238
|
-
keptSilentDeliveryId: envelope.deliveryId,
|
|
1239
|
-
},
|
|
1240
|
-
},
|
|
1241
|
-
transport: 'http-already-applied',
|
|
1242
|
-
fallbackUsed: true,
|
|
1243
|
-
};
|
|
1244
|
-
}
|
|
1245
|
-
|
|
1246
|
-
throw buildKeepSilentFallbackError({
|
|
1247
|
-
deliveryId: envelope.deliveryId,
|
|
1248
|
-
status: fallbackResult.status,
|
|
1249
|
-
body: fallbackResult.body,
|
|
1250
|
-
context: this.buildBoundaryContext({
|
|
1251
|
-
stage: 'kept_silent_fallback',
|
|
1252
|
-
deliveryId: envelope.deliveryId,
|
|
1253
|
-
sessionKey: envelope.sessionKey,
|
|
1254
|
-
fallbackFrom: error?.code || error?.message || null,
|
|
1255
|
-
}),
|
|
1407
|
+
error,
|
|
1256
1408
|
});
|
|
1257
1409
|
}
|
|
1258
1410
|
}
|
|
@@ -47,6 +47,10 @@ export function buildConversationSessionKey(conversationKey = null, fallbackSess
|
|
|
47
47
|
export function resolveRuntimeSessionTarget(event = {}, options = {}) {
|
|
48
48
|
const payload = normalizePayload(event.payload);
|
|
49
49
|
const eventType = normalizeText(event.eventType || event.type || payload.eventType, null);
|
|
50
|
+
const providedSessionKind = normalizeText(
|
|
51
|
+
event.sessionKind,
|
|
52
|
+
normalizeText(payload.sessionKind, normalizeText(options.sessionKind, null)),
|
|
53
|
+
);
|
|
50
54
|
const targetAgentId = normalizeText(
|
|
51
55
|
event.targetAgentId,
|
|
52
56
|
normalizeText(payload.targetAgentId, normalizeText(options.targetAgentId, null)),
|
|
@@ -60,15 +64,15 @@ export function resolveRuntimeSessionTarget(event = {}, options = {}) {
|
|
|
60
64
|
normalizeText(payload.sessionKey, normalizeText(options.sessionKey, null)),
|
|
61
65
|
);
|
|
62
66
|
|
|
63
|
-
if (CLAWORLD_MANAGEMENT_EVENT_TYPES.includes(eventType)) {
|
|
67
|
+
if (providedSessionKind === CLAWORLD_SESSION_KINDS.management || CLAWORLD_MANAGEMENT_EVENT_TYPES.includes(eventType)) {
|
|
64
68
|
const managementSessionKey = normalizeText(
|
|
65
69
|
options.managementSessionKey,
|
|
66
|
-
buildManagementSessionKey(targetAgentId),
|
|
70
|
+
normalizeText(providedSessionKey, buildManagementSessionKey(targetAgentId)),
|
|
67
71
|
);
|
|
68
72
|
return {
|
|
69
73
|
sessionKind: CLAWORLD_SESSION_KINDS.management,
|
|
70
74
|
target: normalizeText(options.managementTarget, 'management_session'),
|
|
71
|
-
sessionKey:
|
|
75
|
+
sessionKey: providedSessionKey || managementSessionKey,
|
|
72
76
|
managementSessionKey: managementSessionKey || null,
|
|
73
77
|
conversationSessionKey: conversationKey ? buildConversationSessionKey(conversationKey) : null,
|
|
74
78
|
targetAgentId,
|