@xfxstudio/claworld 0.2.23 → 0.2.25

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.
@@ -37,6 +37,12 @@ import {
37
37
  fetchOwnedWorlds,
38
38
  manageModeratedWorld,
39
39
  } from '../runtime/world-moderation-helper.js';
40
+ import {
41
+ fetchWorldMembership,
42
+ fetchWorldMemberships,
43
+ leaveWorldMembership,
44
+ updateWorldMembershipProfile,
45
+ } from '../runtime/world-membership-helper.js';
40
46
  import { submitFeedbackReport } from '../runtime/feedback-helper.js';
41
47
  import {
42
48
  buildWorldSelectionPrompt,
@@ -50,6 +56,9 @@ import {
50
56
  } from '../runtime/product-shell-helper.js';
51
57
  import { extractBackendErrorContext } from '../runtime/backend-error-context.js';
52
58
  import { getClaworldRuntime } from './runtime.js';
59
+ import {
60
+ CLAWORLD_PLUGIN_CURRENT_VERSION,
61
+ } from '../plugin-version.js';
53
62
  import {
54
63
  createRuntimeBoundaryError,
55
64
  normalizeRuntimeBoundaryError,
@@ -114,6 +123,101 @@ function resolveNormalizedText(value, fallback = null) {
114
123
  return normalizeClaworldText(value, fallback);
115
124
  }
116
125
 
126
+ function isAgentScopedSessionKey(sessionKey) {
127
+ return /^agent:[^:]+:/i.test(String(sessionKey || ''));
128
+ }
129
+
130
+ function buildAgentScopedLocalSessionKey({ sessionKey, localAgentId } = {}) {
131
+ const normalizedSessionKey = resolveNormalizedText(sessionKey, null);
132
+ if (!normalizedSessionKey) return null;
133
+ if (isAgentScopedSessionKey(normalizedSessionKey)) {
134
+ return normalizedSessionKey;
135
+ }
136
+ const normalizedLocalAgentId = resolveNormalizedText(localAgentId, null);
137
+ if (!normalizedLocalAgentId) {
138
+ return normalizedSessionKey;
139
+ }
140
+ return `agent:${normalizedLocalAgentId}:${normalizedSessionKey}`;
141
+ }
142
+
143
+ function stripAgentScopedLocalSessionKey({ sessionKey, localAgentId } = {}) {
144
+ const normalizedSessionKey = resolveNormalizedText(sessionKey, null);
145
+ if (!normalizedSessionKey) return null;
146
+ const normalizedLocalAgentId = resolveNormalizedText(localAgentId, null);
147
+ if (!normalizedLocalAgentId) {
148
+ return normalizedSessionKey;
149
+ }
150
+ const prefix = `agent:${normalizedLocalAgentId}:`;
151
+ if (normalizedSessionKey.startsWith(prefix)) {
152
+ return normalizedSessionKey.slice(prefix.length) || null;
153
+ }
154
+ return normalizedSessionKey;
155
+ }
156
+
157
+ function normalizeLocalSessionKeyFields(record = null, { localAgentId = null } = {}) {
158
+ if (!record || typeof record !== 'object' || Array.isArray(record)) {
159
+ return record;
160
+ }
161
+ const nextRecord = { ...record };
162
+ const normalizedLocalSessionKey = buildAgentScopedLocalSessionKey({
163
+ sessionKey: resolveNormalizedText(record.localSessionKey, resolveNormalizedText(record.sessionKey, null)),
164
+ localAgentId,
165
+ });
166
+ if (normalizedLocalSessionKey) {
167
+ nextRecord.localSessionKey = normalizedLocalSessionKey;
168
+ }
169
+ return nextRecord;
170
+ }
171
+
172
+ function normalizeChatInboxPayloadSessionKeys(payload = null, { localAgentId = null } = {}) {
173
+ if (!payload || typeof payload !== 'object' || Array.isArray(payload)) {
174
+ return payload;
175
+ }
176
+ const nextPayload = { ...payload };
177
+ if (payload.filters && typeof payload.filters === 'object' && !Array.isArray(payload.filters)) {
178
+ const normalizedFilterLocalSessionKey = buildAgentScopedLocalSessionKey({
179
+ sessionKey: payload.filters.localSessionKey,
180
+ localAgentId,
181
+ });
182
+ nextPayload.filters = {
183
+ ...payload.filters,
184
+ ...(normalizedFilterLocalSessionKey ? { localSessionKey: normalizedFilterLocalSessionKey } : {}),
185
+ };
186
+ }
187
+ if (Array.isArray(payload.chats)) {
188
+ nextPayload.chats = payload.chats.map((chat) => normalizeLocalSessionKeyFields(chat, { localAgentId }));
189
+ }
190
+ if (payload.kickoff && typeof payload.kickoff === 'object' && !Array.isArray(payload.kickoff)) {
191
+ nextPayload.kickoff = normalizeLocalSessionKeyFields(payload.kickoff, { localAgentId });
192
+ }
193
+ if (payload.chat && typeof payload.chat === 'object' && !Array.isArray(payload.chat)) {
194
+ nextPayload.chat = normalizeLocalSessionKeyFields(payload.chat, { localAgentId });
195
+ }
196
+ return nextPayload;
197
+ }
198
+
199
+ function resolveRelaySessionKeyFromOutboundContext(outboundContext = {}) {
200
+ const metadata = outboundContext?.metadata && typeof outboundContext.metadata === 'object' && !Array.isArray(outboundContext.metadata)
201
+ ? outboundContext.metadata
202
+ : {};
203
+ return normalizeClaworldText(
204
+ outboundContext.relaySessionKey,
205
+ normalizeClaworldText(
206
+ outboundContext.RelaySessionKey,
207
+ normalizeClaworldText(
208
+ metadata.relaySessionKey,
209
+ normalizeClaworldText(
210
+ metadata.sessionKey,
211
+ normalizeClaworldText(
212
+ outboundContext.sessionKey,
213
+ normalizeClaworldText(outboundContext.SessionKey, null),
214
+ ),
215
+ ),
216
+ ),
217
+ ),
218
+ );
219
+ }
220
+
117
221
  function normalizeClaworldInteger(value, fallback = null) {
118
222
  const normalized = Number(value);
119
223
  if (!Number.isFinite(normalized)) return fallback;
@@ -174,6 +278,20 @@ const CLAWORLD_RELAY_OPERATIONAL_NOTICE_PATTERNS = [
174
278
  /^⚠️\s*Agent failed before reply:/i,
175
279
  ];
176
280
 
281
+ // Older/runtime-variant OpenClaw hosts may surface provider/runtime failures as
282
+ // plain final text without setting `isError`. Keep this fallback at the bridge
283
+ // boundary so business logic never has to guess.
284
+ const CLAWORLD_RELAY_RUNTIME_ERROR_PATTERNS = [
285
+ /^⚠️\s*Agent failed before reply:/i,
286
+ /^LLM request failed:/i,
287
+ /^LLM request timed out\./i,
288
+ /^LLM request unauthorized\./i,
289
+ /^The AI service is temporarily overloaded\./i,
290
+ /^The AI service returned an error\./i,
291
+ /^⚠️\s*API rate limit reached\./i,
292
+ /^⚠️\s*.+\s+returned a billing error\b/i,
293
+ ];
294
+
177
295
  const CLAWORLD_RELAY_OPERATIONAL_SUFFIX_PATTERNS = [
178
296
  /^Usage:\s+.+\s+in\s+\/\s+.+\s+out(?:\s+·\s+est\s+.+)?$/i,
179
297
  ];
@@ -201,18 +319,21 @@ function classifyRelayContinuationText(text) {
201
319
  if (!normalized) {
202
320
  return {
203
321
  text: '',
204
- operational: Boolean(String(text || '').trim()),
322
+ operationalNotice: Boolean(String(text || '').trim()),
323
+ runtimeError: false,
205
324
  };
206
325
  }
207
326
  if (CLAWORLD_RELAY_OPERATIONAL_NOTICE_PATTERNS.some((pattern) => pattern.test(normalized))) {
208
327
  return {
209
328
  text: '',
210
- operational: true,
329
+ operationalNotice: true,
330
+ runtimeError: false,
211
331
  };
212
332
  }
213
333
  return {
214
334
  text: normalized,
215
- operational: false,
335
+ operationalNotice: false,
336
+ runtimeError: false,
216
337
  };
217
338
  }
218
339
 
@@ -220,6 +341,45 @@ function sanitizeRelayContinuationText(text) {
220
341
  return classifyRelayContinuationText(text).text;
221
342
  }
222
343
 
344
+ function classifyRelayContinuationPayload(payload = {}) {
345
+ const rawText = String(payload?.text ?? payload?.body ?? '').trim();
346
+ const normalized = stripRelayOperationalSuffix(rawText);
347
+ const textClassification = classifyRelayContinuationText(rawText);
348
+ const runtimeError = payload?.isError === true
349
+ || CLAWORLD_RELAY_RUNTIME_ERROR_PATTERNS.some((pattern) => pattern.test(normalized));
350
+ if (runtimeError) {
351
+ return {
352
+ text: '',
353
+ previewText: normalized,
354
+ operationalNotice: false,
355
+ runtimeError: true,
356
+ nonRenderable: true,
357
+ };
358
+ }
359
+ return {
360
+ text: textClassification.text,
361
+ previewText: normalized,
362
+ operationalNotice: textClassification.operationalNotice,
363
+ runtimeError: false,
364
+ nonRenderable: textClassification.operationalNotice,
365
+ };
366
+ }
367
+
368
+ function resolveRelaySilentReason(runtimeOutputSummary = {}, continuation = {}) {
369
+ const counts = runtimeOutputSummary?.counts || {};
370
+ if (Number(counts.runtimeErrorFinal || 0) > 0) {
371
+ return 'runtime_failed_before_reply';
372
+ }
373
+ if (Number(counts.operationalNotice || 0) > 0 && Number(counts.nonRenderableFinal || 0) === Number(counts.final || 0)) {
374
+ return 'operational_notice_only';
375
+ }
376
+ const normalizedSource = normalizePluginOptionalText(continuation?.source);
377
+ if (normalizedSource && normalizedSource !== 'none') {
378
+ return normalizedSource;
379
+ }
380
+ return 'no_renderable_reply';
381
+ }
382
+
223
383
  function previewRuntimeOutputText(text, maxLength = 120) {
224
384
  const normalized = String(text || '').replace(/\s+/g, ' ').trim();
225
385
  if (!normalized) return '';
@@ -285,6 +445,10 @@ function buildRelayContinuationText({
285
445
  };
286
446
  }
287
447
 
448
+ function isExactNoReplyToken(text) {
449
+ return String(text || '').trim() === 'NO_REPLY';
450
+ }
451
+
288
452
  function resolveContinuationState(turnData = {}) {
289
453
  const continuation = turnData?.continuation;
290
454
  if (!continuation || typeof continuation !== 'object' || Array.isArray(continuation)) {
@@ -351,6 +515,7 @@ async function deliverRelayMessage({ runtimeConfig, to, text, fetchImpl, logger,
351
515
  const clientMessageId = normalizePluginOptionalText(
352
516
  outboundContext.clientMessageId || outboundContext.metadata?.clientMessageId || null
353
517
  ) || buildGeneratedClientMessageId();
518
+ const relaySessionKey = resolveRelaySessionKeyFromOutboundContext(outboundContext);
354
519
 
355
520
  const baseUrl = normalizeRelayHttpBaseUrl(runtimeConfig.serverUrl);
356
521
  const result = await fetchJson(fetchImpl, `${baseUrl}/v1/messages`, {
@@ -371,7 +536,7 @@ async function deliverRelayMessage({ runtimeConfig, to, text, fetchImpl, logger,
371
536
  scope: outboundContext.scope || outboundContext.metadata?.scope || null,
372
537
  conversationId: outboundContext.conversationId || outboundContext.metadata?.conversationId || null,
373
538
  threadId: outboundContext.threadId || outboundContext.metadata?.threadId || null,
374
- sessionKey: outboundContext.sessionKey || outboundContext.SessionKey || null,
539
+ sessionKey: relaySessionKey,
375
540
  },
376
541
  }),
377
542
  });
@@ -401,7 +566,7 @@ async function deliverRelayMessage({ runtimeConfig, to, text, fetchImpl, logger,
401
566
  timestamp: Date.now(),
402
567
  meta: {
403
568
  clientMessageId,
404
- sessionKey: result.body?.delivery?.sessionKey || outboundContext.sessionKey || outboundContext.SessionKey || null,
569
+ sessionKey: result.body?.delivery?.sessionKey || relaySessionKey,
405
570
  turnId: result.body?.turn?.turnId || null,
406
571
  conversationKey: result.body?.conversationKey || null,
407
572
  targetAgentId,
@@ -504,6 +669,7 @@ async function createChatRequest({
504
669
  async function listChatInbox({
505
670
  runtimeConfig,
506
671
  agentId,
672
+ localAgentId = null,
507
673
  filters = null,
508
674
  direction = null,
509
675
  fetchImpl,
@@ -511,6 +677,10 @@ async function listChatInbox({
511
677
  const normalizedFilters = filters && typeof filters === 'object' && !Array.isArray(filters)
512
678
  ? filters
513
679
  : {};
680
+ const relayLocalSessionKey = stripAgentScopedLocalSessionKey({
681
+ sessionKey: normalizedFilters.localSessionKey,
682
+ localAgentId,
683
+ });
514
684
  const baseUrl = normalizeRelayHttpBaseUrl(runtimeConfig.serverUrl);
515
685
  const path = buildRelayJsonPath('/v1/chat-requests', {
516
686
  agentId,
@@ -520,7 +690,7 @@ async function listChatInbox({
520
690
  worldId: normalizedFilters.worldId,
521
691
  chatRequestId: normalizedFilters.chatRequestId,
522
692
  conversationKey: normalizedFilters.conversationKey,
523
- localSessionKey: normalizedFilters.localSessionKey,
693
+ localSessionKey: relayLocalSessionKey,
524
694
  counterpartyAgentId: normalizedFilters.counterpartyAgentId,
525
695
  });
526
696
  const result = await fetchJson(fetchImpl, `${baseUrl}${path}`, {
@@ -546,13 +716,14 @@ async function listChatInbox({
546
716
  },
547
717
  });
548
718
  }
549
- return result.body || {};
719
+ return normalizeChatInboxPayloadSessionKeys(result.body || {}, { localAgentId });
550
720
  }
551
721
 
552
722
  async function acceptChatRequest({
553
723
  runtimeConfig,
554
724
  actorAgentId,
555
725
  chatRequestId,
726
+ localAgentId = null,
556
727
  fetchImpl,
557
728
  }) {
558
729
  const baseUrl = normalizeRelayHttpBaseUrl(runtimeConfig.serverUrl);
@@ -574,7 +745,7 @@ async function acceptChatRequest({
574
745
  context: { actorAgentId, chatRequestId },
575
746
  });
576
747
  }
577
- return result.body || {};
748
+ return normalizeChatInboxPayloadSessionKeys(result.body || {}, { localAgentId });
578
749
  }
579
750
 
580
751
  async function rejectChatRequest({
@@ -1195,6 +1366,7 @@ function buildDeliveryInboundEnvelope({
1195
1366
  timestamp = null,
1196
1367
  deliveryId,
1197
1368
  sessionKey,
1369
+ localSessionKey = null,
1198
1370
  worldId = null,
1199
1371
  conversationKey = null,
1200
1372
  untrustedContext = [],
@@ -1215,7 +1387,8 @@ function buildDeliveryInboundEnvelope({
1215
1387
  `[claworld peer ${remoteLabel}]`,
1216
1388
  ...(worldId ? [`[claworld world ${worldId}]`] : []),
1217
1389
  ...(conversationKey ? [`[claworld conversation ${conversationKey}]`] : []),
1218
- `[claworld session ${sessionKey}]`,
1390
+ ...(localSessionKey && localSessionKey !== sessionKey ? [`[claworld local session ${localSessionKey}]`] : []),
1391
+ `[claworld relay session ${sessionKey}]`,
1219
1392
  `[claworld delivery ${deliveryId}]`,
1220
1393
  ], untrustedContext);
1221
1394
  const envelopeTimestamp = Number.isFinite(timestamp) ? new Date(timestamp) : new Date();
@@ -1287,7 +1460,9 @@ function createDeliveryReplyDispatcher({
1287
1460
  reasoningEnd: 0,
1288
1461
  compactionStart: 0,
1289
1462
  compactionEnd: 0,
1463
+ nonRenderableFinal: 0,
1290
1464
  operationalNotice: 0,
1465
+ runtimeErrorFinal: 0,
1291
1466
  },
1292
1467
  previews: {
1293
1468
  final: [],
@@ -1296,6 +1471,7 @@ function createDeliveryReplyDispatcher({
1296
1471
  partial: [],
1297
1472
  reasoning: [],
1298
1473
  operationalNotice: [],
1474
+ runtimeErrorFinal: [],
1299
1475
  },
1300
1476
  relayContinuationSource: 'none',
1301
1477
  relayContinuationPreview: null,
@@ -1306,14 +1482,21 @@ function createDeliveryReplyDispatcher({
1306
1482
  runtimeOutputSummary.counts[kind] += 1;
1307
1483
  const text = String(payload?.text ?? payload?.body ?? '').trim();
1308
1484
  if (kind === 'final') {
1309
- if (text) {
1310
- finalTexts.push(text);
1311
- appendRuntimeOutputPreview(runtimeOutputSummary.previews.final, text);
1312
- const classified = classifyRelayContinuationText(text);
1313
- if (classified.operational) {
1314
- runtimeOutputSummary.counts.operationalNotice += 1;
1315
- appendRuntimeOutputPreview(runtimeOutputSummary.previews.operationalNotice, text);
1316
- }
1485
+ const classified = classifyRelayContinuationPayload(payload);
1486
+ if (classified.text) {
1487
+ finalTexts.push(classified.text);
1488
+ appendRuntimeOutputPreview(runtimeOutputSummary.previews.final, classified.text);
1489
+ }
1490
+ if (classified.nonRenderable) {
1491
+ runtimeOutputSummary.counts.nonRenderableFinal += 1;
1492
+ }
1493
+ if (classified.operationalNotice) {
1494
+ runtimeOutputSummary.counts.operationalNotice += 1;
1495
+ appendRuntimeOutputPreview(runtimeOutputSummary.previews.operationalNotice, classified.previewText || text);
1496
+ }
1497
+ if (classified.runtimeError) {
1498
+ runtimeOutputSummary.counts.runtimeErrorFinal += 1;
1499
+ appendRuntimeOutputPreview(runtimeOutputSummary.previews.runtimeErrorFinal, classified.previewText || text);
1317
1500
  }
1318
1501
  return;
1319
1502
  }
@@ -1346,6 +1529,30 @@ function createDeliveryReplyDispatcher({
1346
1529
  runtimeOutputSummary.counts[kind] += 1;
1347
1530
  };
1348
1531
 
1532
+ const submitRelayReply = async (replyText) => {
1533
+ if (typeof relayClient?.submitDeliveryReply !== 'function') {
1534
+ throw new Error('relay client does not support reply submission');
1535
+ }
1536
+ return await relayClient.submitDeliveryReply({
1537
+ deliveryId,
1538
+ sessionKey,
1539
+ replyText,
1540
+ source: 'openclaw-autochain',
1541
+ });
1542
+ };
1543
+
1544
+ const submitRelayKeptSilent = async (reason) => {
1545
+ if (typeof relayClient?.submitDeliveryKeptSilent !== 'function') {
1546
+ throw new Error('relay client does not support kept_silent submission');
1547
+ }
1548
+ return await relayClient.submitDeliveryKeptSilent({
1549
+ deliveryId,
1550
+ sessionKey,
1551
+ reason,
1552
+ source: 'openclaw-autochain',
1553
+ });
1554
+ };
1555
+
1349
1556
  const flushReply = async (text) => {
1350
1557
  const normalized = String(text || '').trim();
1351
1558
  if (!normalized || replied || suppressed) return false;
@@ -1353,16 +1560,9 @@ function createDeliveryReplyDispatcher({
1353
1560
  suppressed = true;
1354
1561
  return false;
1355
1562
  }
1356
- const replyResult = await relayClient.replyToDeliveryHttp({
1357
- deliveryId,
1358
- replyText: normalized,
1359
- source: 'openclaw-autochain',
1360
- });
1361
- if (replyResult.status < 200 || replyResult.status >= 300) {
1362
- throw new Error(`failed to submit relay reply: ${replyResult.status}`);
1363
- }
1364
- replyTransport = 'http';
1365
- replyFallbackUsed = false;
1563
+ const replyResult = await submitRelayReply(normalized);
1564
+ replyTransport = replyResult?.transport || null;
1565
+ replyFallbackUsed = replyResult?.fallbackUsed === true;
1366
1566
  replied = true;
1367
1567
  return true;
1368
1568
  };
@@ -1373,16 +1573,11 @@ function createDeliveryReplyDispatcher({
1373
1573
  suppressed = true;
1374
1574
  return false;
1375
1575
  }
1376
- const silentResult = await relayClient.keepDeliverySilentHttp({
1377
- deliveryId,
1378
- reason: normalizePluginOptionalText(reason) || 'no_renderable_reply',
1379
- source: 'openclaw-autochain',
1380
- });
1381
- if (silentResult.status < 200 || silentResult.status >= 300) {
1382
- throw new Error(`failed to submit relay kept_silent: ${silentResult.status}`);
1383
- }
1384
- keptSilentTransport = 'http';
1385
- keptSilentFallbackUsed = false;
1576
+ const silentResult = await submitRelayKeptSilent(
1577
+ normalizePluginOptionalText(reason) || 'no_renderable_reply',
1578
+ );
1579
+ keptSilentTransport = silentResult?.transport || null;
1580
+ keptSilentFallbackUsed = silentResult?.fallbackUsed === true;
1386
1581
  keptSilent = true;
1387
1582
  return true;
1388
1583
  };
@@ -1417,21 +1612,39 @@ function createDeliveryReplyDispatcher({
1417
1612
  const markDispatchIdle = async () => {
1418
1613
  await dispatchApi.dispatcher.waitForIdle?.();
1419
1614
  if (!replied && !suppressed) {
1420
- const continuation = buildRelayContinuationText({
1615
+ const allowPartialFallback = (
1616
+ runtimeOutputSummary.counts.final > 0
1617
+ && finalTexts.length === 0
1618
+ && blockTexts.length === 0
1619
+ && runtimeOutputSummary.counts.nonRenderableFinal === 0
1620
+ );
1621
+ const safeContinuation = buildRelayContinuationText({
1421
1622
  finalTexts,
1422
1623
  blockTexts,
1423
1624
  partialText: partialContinuationText,
1424
- allowPartialFallback:
1425
- runtimeOutputSummary.counts.final > 0 && finalTexts.length === 0 && blockTexts.length === 0,
1625
+ allowPartialFallback,
1426
1626
  });
1427
- runtimeOutputSummary.relayContinuationSource = continuation.source;
1428
- runtimeOutputSummary.relayContinuationPreview = continuation.text
1429
- ? previewRuntimeOutputText(continuation.text)
1627
+ runtimeOutputSummary.relayContinuationSource = safeContinuation.source;
1628
+ runtimeOutputSummary.relayContinuationPreview = safeContinuation.text
1629
+ ? previewRuntimeOutputText(safeContinuation.text)
1430
1630
  : null;
1431
- if (continuation.text) {
1432
- await flushReply(continuation.text);
1631
+ if (safeContinuation.text && isExactNoReplyToken(safeContinuation.text)) {
1632
+ runtimeOutputSummary.relayContinuationSource = 'no_reply_token';
1633
+ runtimeOutputSummary.relayContinuationPreview = 'NO_REPLY';
1634
+ await flushKeptSilent('no_reply_token');
1635
+ } else if (safeContinuation.text) {
1636
+ await flushReply(safeContinuation.text);
1433
1637
  } else {
1434
- await flushKeptSilent(continuation.source);
1638
+ const silentReason = resolveRelaySilentReason(runtimeOutputSummary, safeContinuation);
1639
+ if (runtimeOutputSummary.counts.runtimeErrorFinal > 0) {
1640
+ logger.warn?.(`[claworld:${runtimeAccountId}] runtime produced non-renderable error finals; returning kept_silent`, {
1641
+ deliveryId,
1642
+ sessionKey,
1643
+ localAgentId,
1644
+ runtimeOutputSummary,
1645
+ });
1646
+ }
1647
+ await flushKeptSilent(silentReason);
1435
1648
  }
1436
1649
  }
1437
1650
  await dispatchApi.markDispatchIdle?.();
@@ -1476,13 +1689,14 @@ function createDeliveryReplyDispatcher({
1476
1689
  final: [...runtimeOutputSummary.previews.final],
1477
1690
  block: [...runtimeOutputSummary.previews.block],
1478
1691
  tool: [...runtimeOutputSummary.previews.tool],
1479
- partial: [...runtimeOutputSummary.previews.partial],
1480
- reasoning: [...runtimeOutputSummary.previews.reasoning],
1481
- operationalNotice: [...runtimeOutputSummary.previews.operationalNotice],
1482
- },
1483
- relayContinuationSource: runtimeOutputSummary.relayContinuationSource,
1484
- relayContinuationPreview: runtimeOutputSummary.relayContinuationPreview,
1485
- replyTransport,
1692
+ partial: [...runtimeOutputSummary.previews.partial],
1693
+ reasoning: [...runtimeOutputSummary.previews.reasoning],
1694
+ operationalNotice: [...runtimeOutputSummary.previews.operationalNotice],
1695
+ runtimeErrorFinal: [...runtimeOutputSummary.previews.runtimeErrorFinal],
1696
+ },
1697
+ relayContinuationSource: runtimeOutputSummary.relayContinuationSource,
1698
+ relayContinuationPreview: runtimeOutputSummary.relayContinuationPreview,
1699
+ replyTransport,
1486
1700
  replyFallbackUsed,
1487
1701
  keptSilentTransport,
1488
1702
  keptSilentFallbackUsed,
@@ -1623,7 +1837,24 @@ async function maybeBridgeRuntimeDelivery({
1623
1837
  return { skipped: true, reason: 'missing_delivery_payload' };
1624
1838
  }
1625
1839
 
1626
- const currentCfg = cfg || await runtime.config?.loadConfig?.() || {};
1840
+ const loadedCfg = await runtime.config?.loadConfig?.() || {};
1841
+ const currentCfg = {
1842
+ ...(loadedCfg && typeof loadedCfg === 'object' && !Array.isArray(loadedCfg) ? loadedCfg : {}),
1843
+ ...(cfg && typeof cfg === 'object' && !Array.isArray(cfg) ? cfg : {}),
1844
+ agents: cfg?.agents || loadedCfg?.agents,
1845
+ bindings: cfg?.bindings || loadedCfg?.bindings,
1846
+ channels: cfg?.channels || loadedCfg?.channels,
1847
+ session: cfg?.session || loadedCfg?.session,
1848
+ };
1849
+ const localAgentId = resolveBoundLocalAgentId({
1850
+ cfg: currentCfg,
1851
+ runtimeConfig,
1852
+ relayClient,
1853
+ });
1854
+ const localSessionKey = buildAgentScopedLocalSessionKey({
1855
+ sessionKey,
1856
+ localAgentId,
1857
+ });
1627
1858
  const routed = inbound?.routeInboundEvent?.(delivery, {
1628
1859
  sessionTarget: runtimeConfig.routing?.sessionTarget,
1629
1860
  fallbackTarget: runtimeConfig.routing?.fallbackTarget,
@@ -1644,6 +1875,7 @@ async function maybeBridgeRuntimeDelivery({
1644
1875
  timestamp: inboundTimestamp,
1645
1876
  deliveryId,
1646
1877
  sessionKey,
1878
+ localSessionKey,
1647
1879
  worldId,
1648
1880
  conversationKey: metadata.conversationKey || null,
1649
1881
  untrustedContext: payload.untrustedContext,
@@ -1657,7 +1889,8 @@ async function maybeBridgeRuntimeDelivery({
1657
1889
  BodyForCommands,
1658
1890
  From: `claworld:${remoteIdentity}`,
1659
1891
  To: `claworld:${localIdentity}`,
1660
- SessionKey: sessionKey,
1892
+ SessionKey: localSessionKey || sessionKey,
1893
+ RelaySessionKey: sessionKey,
1661
1894
  AccountId: runtimeConfig.accountId,
1662
1895
  OriginatingChannel: 'claworld',
1663
1896
  OriginatingFrom: remoteIdentity,
@@ -1677,11 +1910,6 @@ async function maybeBridgeRuntimeDelivery({
1677
1910
  RelayFromAgentId: fromAgentId,
1678
1911
  UntrustedContext,
1679
1912
  });
1680
- const localAgentId = resolveBoundLocalAgentId({
1681
- cfg: currentCfg,
1682
- runtimeConfig,
1683
- relayClient,
1684
- });
1685
1913
 
1686
1914
  if (runtime?.channel?.session?.recordInboundSession && runtime?.channel?.session?.resolveStorePath && localAgentId) {
1687
1915
  const storePath = runtime.channel.session.resolveStorePath(currentCfg.session?.store, {
@@ -1695,6 +1923,7 @@ async function maybeBridgeRuntimeDelivery({
1695
1923
  logger.error?.(`[claworld:${runtimeAccountId}] failed to record inbound session`, {
1696
1924
  deliveryId,
1697
1925
  sessionKey,
1926
+ localSessionKey,
1698
1927
  localAgentId,
1699
1928
  error: error?.message || String(error),
1700
1929
  });
@@ -1705,6 +1934,7 @@ async function maybeBridgeRuntimeDelivery({
1705
1934
  logger.info?.(`[claworld:${runtimeAccountId}] routing delivery into runtime session`, {
1706
1935
  deliveryId,
1707
1936
  sessionKey,
1937
+ localSessionKey,
1708
1938
  localAgentId,
1709
1939
  remoteIdentity,
1710
1940
  routeStatus: routed?.status || null,
@@ -1727,6 +1957,7 @@ async function maybeBridgeRuntimeDelivery({
1727
1957
  logger.warn?.(`[claworld:${runtimeAccountId}] delivery acceptance acknowledgement failed`, {
1728
1958
  deliveryId,
1729
1959
  sessionKey,
1960
+ localSessionKey,
1730
1961
  localAgentId,
1731
1962
  error: error?.message || String(error),
1732
1963
  });
@@ -1755,8 +1986,8 @@ async function maybeBridgeRuntimeDelivery({
1755
1986
  && metadata.allowReply !== false
1756
1987
  && replied !== true
1757
1988
  && runtimeOutputSummary.counts.final > 0
1758
- && runtimeOutputSummary.counts.operationalNotice > 0
1759
- && runtimeOutputSummary.counts.final === runtimeOutputSummary.counts.operationalNotice
1989
+ && runtimeOutputSummary.counts.nonRenderableFinal > 0
1990
+ && runtimeOutputSummary.counts.final === runtimeOutputSummary.counts.nonRenderableFinal
1760
1991
  && runtimeOutputSummary.counts.block === 0
1761
1992
  && runtimeOutputSummary.counts.tool === 0
1762
1993
  && runtimeOutputSummary.counts.partial === 0
@@ -1772,6 +2003,7 @@ async function maybeBridgeRuntimeDelivery({
1772
2003
  logger.warn?.(`[claworld:${runtimeAccountId}] kickoff delivery produced only operational notices; retrying dispatch once`, {
1773
2004
  deliveryId,
1774
2005
  sessionKey,
2006
+ localSessionKey,
1775
2007
  localAgentId,
1776
2008
  runtimeOutputSummary,
1777
2009
  });
@@ -1798,6 +2030,7 @@ async function maybeBridgeRuntimeDelivery({
1798
2030
  logger.info?.(`[claworld:${runtimeAccountId}] delivery bridge completed`, {
1799
2031
  deliveryId,
1800
2032
  sessionKey,
2033
+ localSessionKey,
1801
2034
  queuedFinal: Boolean(dispatchResult?.queuedFinal),
1802
2035
  replied,
1803
2036
  keptSilent,
@@ -1812,6 +2045,7 @@ async function maybeBridgeRuntimeDelivery({
1812
2045
  keptSilent,
1813
2046
  queuedFinal: Boolean(dispatchResult?.queuedFinal),
1814
2047
  sessionKey,
2048
+ localSessionKey,
1815
2049
  routeStatus: routed?.status || null,
1816
2050
  };
1817
2051
  }
@@ -2076,6 +2310,14 @@ export function createClaworldChannelPlugin({
2076
2310
  };
2077
2311
  }
2078
2312
 
2313
+ function resolveContextBoundLocalAgentId(context = {}) {
2314
+ return resolveBoundLocalAgentId({
2315
+ cfg: context.cfg || {},
2316
+ runtimeConfig: context.runtimeConfig || {},
2317
+ relayClient: relayClients.get(context.accountId || 'default') || null,
2318
+ });
2319
+ }
2320
+
2079
2321
  function getAccountLifecycle(accountKey = 'default') {
2080
2322
  if (lifecycles.has(accountKey)) return lifecycles.get(accountKey);
2081
2323
 
@@ -2375,7 +2617,7 @@ export function createClaworldChannelPlugin({
2375
2617
  return {
2376
2618
  ok: true,
2377
2619
  pluginId: 'claworld',
2378
- version: '0.3.0',
2620
+ version: CLAWORLD_PLUGIN_CURRENT_VERSION,
2379
2621
  defaultAccountId: null,
2380
2622
  accounts: accountSnapshots,
2381
2623
  relayClients: Object.fromEntries(
@@ -2524,7 +2766,7 @@ async function generateRuntimeProfileCard(context = {}) {
2524
2766
  docsPath: '/channels/claworld',
2525
2767
  docsLabel: 'claworld',
2526
2768
  blurb: 'Claworld relay channel backed by the Claworld backend.',
2527
- version: '0.3.0',
2769
+ version: CLAWORLD_PLUGIN_CURRENT_VERSION,
2528
2770
  forceAccountBinding: true,
2529
2771
  },
2530
2772
  onboarding: claworldOnboardingAdapter,
@@ -2690,6 +2932,7 @@ async function generateRuntimeProfileCard(context = {}) {
2690
2932
  return listChatInbox({
2691
2933
  runtimeConfig: resolvedContext.runtimeConfig,
2692
2934
  agentId: resolvedContext.agentId || null,
2935
+ localAgentId: resolveContextBoundLocalAgentId(resolvedContext),
2693
2936
  filters: context.filters || null,
2694
2937
  direction: context.direction || null,
2695
2938
  fetchImpl,
@@ -2701,6 +2944,7 @@ async function generateRuntimeProfileCard(context = {}) {
2701
2944
  runtimeConfig: resolvedContext.runtimeConfig,
2702
2945
  actorAgentId: resolvedContext.agentId || null,
2703
2946
  chatRequestId: context.chatRequestId || null,
2947
+ localAgentId: resolveContextBoundLocalAgentId(resolvedContext),
2704
2948
  fetchImpl,
2705
2949
  });
2706
2950
  },
@@ -2801,6 +3045,7 @@ async function generateRuntimeProfileCard(context = {}) {
2801
3045
  agentId: resolvedContext.agentId || null,
2802
3046
  displayName: context.displayName || null,
2803
3047
  worldContextText: context.worldContextText || null,
3048
+ participantContextText: context.participantContextText || null,
2804
3049
  enabled: typeof context.enabled === 'boolean' ? context.enabled : true,
2805
3050
  fetchImpl,
2806
3051
  logger,
@@ -2835,6 +3080,60 @@ async function generateRuntimeProfileCard(context = {}) {
2835
3080
  });
2836
3081
  },
2837
3082
  },
3083
+ membership: {
3084
+ listWorldMemberships: async (context = {}) => {
3085
+ const resolvedContext = await resolveBoundRuntimeContext(context);
3086
+ return fetchWorldMemberships({
3087
+ cfg: resolvedContext.cfg || {},
3088
+ accountId: resolvedContext.accountId || null,
3089
+ runtimeConfig: resolvedContext.runtimeConfig || null,
3090
+ agentId: resolvedContext.agentId || null,
3091
+ status: context.status || null,
3092
+ includeInactive: context.includeInactive === true,
3093
+ includeDisabled: context.includeDisabled !== false,
3094
+ fetchImpl,
3095
+ logger,
3096
+ });
3097
+ },
3098
+ getWorldMembership: async (context = {}) => {
3099
+ const resolvedContext = await resolveBoundRuntimeContext(context);
3100
+ return fetchWorldMembership({
3101
+ cfg: resolvedContext.cfg || {},
3102
+ accountId: resolvedContext.accountId || null,
3103
+ runtimeConfig: resolvedContext.runtimeConfig || null,
3104
+ agentId: resolvedContext.agentId || null,
3105
+ worldId: context.worldId || null,
3106
+ includeDisabled: context.includeDisabled !== false,
3107
+ fetchImpl,
3108
+ logger,
3109
+ });
3110
+ },
3111
+ updateWorldMembershipProfile: async (context = {}) => {
3112
+ const resolvedContext = await resolveBoundRuntimeContext(context);
3113
+ return updateWorldMembershipProfile({
3114
+ cfg: resolvedContext.cfg || {},
3115
+ accountId: resolvedContext.accountId || null,
3116
+ runtimeConfig: resolvedContext.runtimeConfig || null,
3117
+ agentId: resolvedContext.agentId || null,
3118
+ worldId: context.worldId || null,
3119
+ participantContextText: context.participantContextText || null,
3120
+ fetchImpl,
3121
+ logger,
3122
+ });
3123
+ },
3124
+ leaveWorldMembership: async (context = {}) => {
3125
+ const resolvedContext = await resolveBoundRuntimeContext(context);
3126
+ return leaveWorldMembership({
3127
+ cfg: resolvedContext.cfg || {},
3128
+ accountId: resolvedContext.accountId || null,
3129
+ runtimeConfig: resolvedContext.runtimeConfig || null,
3130
+ agentId: resolvedContext.agentId || null,
3131
+ worldId: context.worldId || null,
3132
+ fetchImpl,
3133
+ logger,
3134
+ });
3135
+ },
3136
+ },
2838
3137
  },
2839
3138
  runtime: {
2840
3139
  protocol,
@@ -2951,6 +3250,7 @@ async function generateRuntimeProfileCard(context = {}) {
2951
3250
  agentId: resolvedContext.agentId || null,
2952
3251
  displayName: context.displayName || null,
2953
3252
  worldContextText: context.worldContextText || null,
3253
+ participantContextText: context.participantContextText || null,
2954
3254
  enabled: typeof context.enabled === 'boolean' ? context.enabled : true,
2955
3255
  fetchImpl,
2956
3256
  logger,
@@ -2985,6 +3285,60 @@ async function generateRuntimeProfileCard(context = {}) {
2985
3285
  });
2986
3286
  },
2987
3287
  },
3288
+ membership: {
3289
+ listWorldMemberships: async (context = {}) => {
3290
+ const resolvedContext = await resolveBoundRuntimeContext(context);
3291
+ return fetchWorldMemberships({
3292
+ cfg: resolvedContext.cfg || {},
3293
+ accountId: resolvedContext.accountId || null,
3294
+ runtimeConfig: resolvedContext.runtimeConfig || null,
3295
+ agentId: resolvedContext.agentId || null,
3296
+ status: context.status || null,
3297
+ includeInactive: context.includeInactive === true,
3298
+ includeDisabled: context.includeDisabled !== false,
3299
+ fetchImpl,
3300
+ logger,
3301
+ });
3302
+ },
3303
+ getWorldMembership: async (context = {}) => {
3304
+ const resolvedContext = await resolveBoundRuntimeContext(context);
3305
+ return fetchWorldMembership({
3306
+ cfg: resolvedContext.cfg || {},
3307
+ accountId: resolvedContext.accountId || null,
3308
+ runtimeConfig: resolvedContext.runtimeConfig || null,
3309
+ agentId: resolvedContext.agentId || null,
3310
+ worldId: context.worldId || null,
3311
+ includeDisabled: context.includeDisabled !== false,
3312
+ fetchImpl,
3313
+ logger,
3314
+ });
3315
+ },
3316
+ updateWorldMembershipProfile: async (context = {}) => {
3317
+ const resolvedContext = await resolveBoundRuntimeContext(context);
3318
+ return updateWorldMembershipProfile({
3319
+ cfg: resolvedContext.cfg || {},
3320
+ accountId: resolvedContext.accountId || null,
3321
+ runtimeConfig: resolvedContext.runtimeConfig || null,
3322
+ agentId: resolvedContext.agentId || null,
3323
+ worldId: context.worldId || null,
3324
+ participantContextText: context.participantContextText || null,
3325
+ fetchImpl,
3326
+ logger,
3327
+ });
3328
+ },
3329
+ leaveWorldMembership: async (context = {}) => {
3330
+ const resolvedContext = await resolveBoundRuntimeContext(context);
3331
+ return leaveWorldMembership({
3332
+ cfg: resolvedContext.cfg || {},
3333
+ accountId: resolvedContext.accountId || null,
3334
+ runtimeConfig: resolvedContext.runtimeConfig || null,
3335
+ agentId: resolvedContext.agentId || null,
3336
+ worldId: context.worldId || null,
3337
+ fetchImpl,
3338
+ logger,
3339
+ });
3340
+ },
3341
+ },
2988
3342
  },
2989
3343
  createRelayClient: (options = {}) => relayClientFactory({ logger, inbound, outbound, protocol, ...options }),
2990
3344
  },