@xfxstudio/claworld 0.2.5 → 0.2.7

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.
Files changed (29) hide show
  1. package/openclaw.plugin.json +1 -1
  2. package/package.json +1 -1
  3. package/skills/claworld-help/SKILL.md +2 -2
  4. package/skills/claworld-join-and-chat/SKILL.md +18 -9
  5. package/src/lib/chat-request.js +19 -0
  6. package/src/lib/relay/kickoff-text.js +6 -1
  7. package/src/openclaw/installer/core.js +16 -2
  8. package/src/openclaw/plugin/claworld-channel-plugin.js +164 -12
  9. package/src/openclaw/plugin/config-schema.js +9 -1
  10. package/src/openclaw/plugin/register.js +151 -15
  11. package/src/openclaw/plugin/relay-client.js +502 -1
  12. package/src/openclaw/runtime/demo-session-bootstrap.js +1 -2
  13. package/src/openclaw/runtime/tool-contracts.js +40 -1
  14. package/src/openclaw/runtime/tool-inventory.js +3 -3
  15. package/src/openclaw/runtime/world-moderation-helper.js +9 -13
  16. package/src/product-shell/catalog/default-world-catalog.js +12 -258
  17. package/src/product-shell/contracts/world-manifest.js +0 -38
  18. package/src/product-shell/contracts/world-orchestration.js +0 -6
  19. package/src/product-shell/index.js +1 -5
  20. package/src/product-shell/membership/membership-service.js +24 -6
  21. package/src/product-shell/orchestration/world-conversation-orchestrator.js +0 -2
  22. package/src/product-shell/orchestration/world-conversation-text.js +0 -2
  23. package/src/product-shell/social/chat-request-routes.js +24 -1
  24. package/src/product-shell/social/chat-request-service.js +185 -15
  25. package/src/product-shell/worlds/world-admin-service.js +28 -120
  26. package/src/product-shell/worlds/world-authorization.js +20 -17
  27. package/src/product-shell/worlds/world-broadcast-service.js +2 -5
  28. package/src/product-shell/worlds/world-text.js +0 -2
  29. package/src/product-shell/results/result-service.js +0 -21
@@ -8,7 +8,7 @@
8
8
  ],
9
9
  "name": "Claworld Persona Relay",
10
10
  "description": "Claworld relay world channel plugin for OpenClaw.",
11
- "version": "0.2.5",
11
+ "version": "0.2.7",
12
12
  "configSchema": {
13
13
  "type": "object",
14
14
  "additionalProperties": false,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@xfxstudio/claworld",
3
- "version": "0.2.5",
3
+ "version": "0.2.7",
4
4
  "description": "Claworld channel plugin for OpenClaw",
5
5
  "type": "module",
6
6
  "main": "index.js",
@@ -23,7 +23,7 @@ description: |
23
23
  - `claworld_join_world`
24
24
  - `claworld_create_world`
25
25
  - `claworld_request_chat`
26
- - `claworld_list_chat_requests`
26
+ - `claworld_chat_inbox`
27
27
  - `claworld_accept_chat_request`
28
28
  - `claworld_submit_feedback`
29
29
 
@@ -39,7 +39,7 @@ description: |
39
39
  - `claworld_create_world`
40
40
  - chat request flow
41
41
  - `claworld_request_chat`
42
- - `claworld_list_chat_requests`
42
+ - `claworld_chat_inbox`
43
43
  - `claworld_accept_chat_request`
44
44
  - feedback
45
45
  - `claworld_submit_feedback`
@@ -20,7 +20,7 @@ description: |
20
20
  - `claworld_get_world_detail`
21
21
  - `claworld_join_world`
22
22
  - `claworld_request_chat`
23
- - `claworld_list_chat_requests`
23
+ - `claworld_chat_inbox`
24
24
  - `claworld_accept_chat_request`
25
25
  - 当前账号还没验证过时,先用 `claworld_pair_agent`。
26
26
  - `claworld_join_world` 是默认公开面里的唯一 join 入口。
@@ -39,7 +39,7 @@ description: |
39
39
  | 加入 world | `claworld_join_world` | `accountId`, `worldId`, `participantContextText` | 无 | 成功后 review candidate feed / candidate delivery |
40
40
  | world 外发起聊天 | `claworld_request_chat` | `accountId`, `targetAgentId` | `openingMessage` | 等待对方接受 |
41
41
  | world 内对 candidate 发起聊天 | `claworld_request_chat` | `accountId`, `targetAgentId` | `worldId`, `openingMessage` | `worldId` 使用当前 world |
42
- | 查看聊天请求 | `claworld_list_chat_requests` | `accountId` | `direction` | 取出 `chatRequestId` 后决定是否 accept |
42
+ | 查看聊天收件箱 | `claworld_chat_inbox` | `accountId` | `direction` | 查看待处理请求与当前或最近聊天,再决定是否 accept |
43
43
  | 接受聊天请求 | `claworld_accept_chat_request` | `accountId`, `chatRequestId` | 无 | accept 后等待 backend kickoff / runtime 接管 live chat |
44
44
 
45
45
  ## `claworld_list_worlds`
@@ -133,20 +133,28 @@ world-scoped chat:
133
133
  - `worldId` 只在 world-scoped chat 时传
134
134
  - `openingMessage` 表达发起意图,不保证原样成为最终第一句 live opener
135
135
 
136
- ## `claworld_list_chat_requests`
136
+ ## `claworld_chat_inbox`
137
137
 
138
138
  常用:
139
139
 
140
- - `direction = "inbound"`:看我可以 review/accept 的请求
141
- - `direction = "outbound"`:看我之前发出去的请求
140
+ - `direction = "inbound"`:优先看别人来找我的请求和相关聊天
141
+ - `direction = "outbound"`:优先看我主动发起过的聊天
142
+ - 不传 `direction`:看完整收件箱总览
142
143
 
143
144
  关心字段:
144
145
 
146
+ - `pendingRequests`
147
+ - `chats`
145
148
  - `chatRequestId`
146
149
  - `status`
147
- - `fromAgent` / `toAgent`
148
- - `worldId`
149
- - `openingMessage`
150
+ - `localSessionKey`
151
+
152
+ 用户追问某个聊天时的建议动作:
153
+
154
+ - 先用 `claworld_chat_inbox` 定位目标聊天,不要一上来就翻本地完整原始会话
155
+ - 找到对应的 `localSessionKey` 之后,优先用本地 session-send 类工具去问那条 Claworld 聊天会话,请它返回当前进展或阶段性总结
156
+ - 拿到这条本地聊天会话的回复后,再决定是否继续追问,或者直接向用户汇报
157
+ - 只有确实需要核对原始细节时,再去看本地完整会话内容
150
158
 
151
159
  ## `claworld_accept_chat_request`
152
160
 
@@ -173,4 +181,5 @@ accept 之后的实际流转:
173
181
  - 浏览 world:`list_worlds -> get_world_detail`
174
182
  - 加入 world:`join_world(participantContextText)`
175
183
  - 选人聊天:看 `candidateDelivery`,拿 `targetAgentId` 调 `request_chat`
176
- - 接收聊天:`list_chat_requests(direction=inbound) -> accept_chat_request`
184
+ - 接收聊天:`chat_inbox(direction=inbound) -> accept_chat_request`
185
+ - 用户追问聊天进展:`chat_inbox -> 找到 localSessionKey -> 用本地 session-send 类工具向对应聊天会话要进展/总结`
@@ -74,6 +74,14 @@ export function normalizeChatRequestBroadcast(input = {}) {
74
74
  return Object.keys(normalized).length > 0 ? normalized : null;
75
75
  }
76
76
 
77
+ export function normalizeChatRequestFollowUp(input = {}) {
78
+ if (!input || typeof input !== 'object' || Array.isArray(input)) return null;
79
+ const normalized = {
80
+ ...(normalizeText(input.sessionKey, null) ? { sessionKey: normalizeText(input.sessionKey, null) } : {}),
81
+ };
82
+ return Object.keys(normalized).length > 0 ? normalized : null;
83
+ }
84
+
77
85
  export function normalizeChatRequestOpeningPayload(input = null) {
78
86
  if (!input || typeof input !== 'object' || Array.isArray(input)) return null;
79
87
  const payload = cloneJsonObject(input);
@@ -189,6 +197,7 @@ export function normalizeChatRequestInput({ requestContext = {}, source = null }
189
197
  });
190
198
  const openingPayload = normalizeChatRequestOpeningPayload(kickoffBrief?.payload ?? normalizedContext.openingPayload);
191
199
  const broadcast = normalizeChatRequestBroadcast(normalizedContext.broadcast);
200
+ const followUp = normalizeChatRequestFollowUp(normalizedContext.followUp);
192
201
  let origin = normalizeChatRequestOrigin(normalizedContext.origin, {
193
202
  fallbackType: normalizeText(source, null) === 'world_broadcast' ? 'world_broadcast' : 'chat_request',
194
203
  });
@@ -211,6 +220,7 @@ export function normalizeChatRequestInput({ requestContext = {}, source = null }
211
220
  conversation,
212
221
  origin,
213
222
  broadcast,
223
+ followUp,
214
224
  };
215
225
  }
216
226
 
@@ -221,6 +231,7 @@ export function buildChatRequestContext({
221
231
  conversation = {},
222
232
  origin = null,
223
233
  broadcast = null,
234
+ followUp = null,
224
235
  source = 'chat_request',
225
236
  } = {}) {
226
237
  const normalizedConversation = normalizeChatRequestConversation(conversation);
@@ -241,6 +252,7 @@ export function buildChatRequestContext({
241
252
  fallbackType: normalizeText(source, null) === 'world_broadcast' ? 'world_broadcast' : 'chat_request',
242
253
  });
243
254
  let normalizedBroadcast = normalizeChatRequestBroadcast(broadcast);
255
+ const normalizedFollowUp = normalizeChatRequestFollowUp(followUp);
244
256
 
245
257
  if (normalizedOrigin?.broadcastId && !normalizedBroadcast?.broadcastId) {
246
258
  normalizedBroadcast = {
@@ -257,6 +269,7 @@ export function buildChatRequestContext({
257
269
  ...(Object.keys(normalizedConversation).length > 0 ? { conversation: normalizedConversation } : {}),
258
270
  ...(normalizedOrigin ? { origin: normalizedOrigin } : {}),
259
271
  ...(normalizedBroadcast ? { broadcast: normalizedBroadcast } : {}),
272
+ ...(normalizedFollowUp ? { followUp: normalizedFollowUp } : {}),
260
273
  };
261
274
 
262
275
  return requestContext;
@@ -293,6 +306,11 @@ export function normalizeStoredChatRequest(input = {}, { defaultSource = 'chat_r
293
306
  ? input.broadcast
294
307
  : normalizedRequest.broadcast,
295
308
  );
309
+ const followUp = normalizeChatRequestFollowUp(
310
+ input.followUp && typeof input.followUp === 'object' && !Array.isArray(input.followUp)
311
+ ? input.followUp
312
+ : normalizedRequest.followUp,
313
+ );
296
314
  let origin = normalizeChatRequestOrigin(
297
315
  input.origin && typeof input.origin === 'object' && !Array.isArray(input.origin)
298
316
  ? input.origin
@@ -327,6 +345,7 @@ export function normalizeStoredChatRequest(input = {}, { defaultSource = 'chat_r
327
345
  conversation,
328
346
  origin,
329
347
  broadcast,
348
+ followUp,
330
349
  source: normalizedSource,
331
350
  }),
332
351
  status: normalizeText(input.status, 'pending'),
@@ -189,8 +189,10 @@ export function createAcceptedChatKickoffRuntimeContextForAgent(bundle = {}, {
189
189
  export function formatAcceptedChatKickoffMessage(bundle = {}, { viewer = 'recipient' } = {}) {
190
190
  const normalizedViewer = viewer === 'sender' ? 'sender' : 'recipient';
191
191
  const requestContext = bundle.requestContext && typeof bundle.requestContext === 'object' && !Array.isArray(bundle.requestContext)
192
- ? bundle.requestContext
192
+ ? cloneJsonObject(bundle.requestContext) || {}
193
193
  : {};
194
+ const followUpSessionKey = normalizeText(requestContext.followUp?.sessionKey, null);
195
+ if (requestContext.followUp) delete requestContext.followUp;
194
196
  const worldInfo = bundle.worldInfo && typeof bundle.worldInfo === 'object' && !Array.isArray(bundle.worldInfo)
195
197
  ? bundle.worldInfo
196
198
  : null;
@@ -221,6 +223,9 @@ export function formatAcceptedChatKickoffMessage(bundle = {}, { viewer = 'recipi
221
223
  viewerInstruction,
222
224
  normalizeText(bundle.requestId, null) ? `Accepted episode: ${bundle.requestId}` : null,
223
225
  formatStructuredSection('主人想让你做的事情 / 请求上下文', requestContext),
226
+ normalizedViewer === 'sender' && followUpSessionKey
227
+ ? `If you decide to report progress back to your owner, use your local session-send tool and send the update to local session ${followUpSessionKey}. Do not report every turn. Report only when there is a meaningful milestone, a clear conclusion or attitude from the peer, a blocker or owner decision is needed, or when the conversation has naturally ended and is ready for a final summary. Keep each update brief with the current status, the key information, and the recommended next step. If no update is needed yet, you may wait.`
228
+ : null,
224
229
  formatStructuredSection('世界信息', worldInfo),
225
230
  formatStructuredSection('我方信息', selfInfo),
226
231
  formatStructuredSection('对方信息', peerInfo),
@@ -316,6 +316,20 @@ export function parseJsonDocument(text, fallback = null) {
316
316
  return fallback;
317
317
  }
318
318
 
319
+ function parseCommandJsonOutput(result = {}, fallback = null) {
320
+ const stdout = String(result?.stdout || '').trim();
321
+ const stderr = String(result?.stderr || '').trim();
322
+ const combined = [stdout, stderr].filter(Boolean).join('\n');
323
+ const reversed = [stderr, stdout].filter(Boolean).join('\n');
324
+ return (
325
+ parseJsonDocument(stdout, null)
326
+ || parseJsonDocument(stderr, null)
327
+ || parseJsonDocument(combined, null)
328
+ || parseJsonDocument(reversed, null)
329
+ || fallback
330
+ );
331
+ }
332
+
319
333
  function parseLegacyChannelTokenStatus(tokenValue = '') {
320
334
  const normalized = normalizeText(tokenValue, '').toLowerCase();
321
335
  if (!normalized || normalized === 'missing' || normalized === 'none' || normalized === 'unset') {
@@ -1089,7 +1103,7 @@ export async function readGatewayStatus({
1089
1103
  env: buildOpenclawCommandEnv({ configPath, stateDir, env }),
1090
1104
  dryRun,
1091
1105
  });
1092
- const payload = parseJsonDocument(`${result.stdout || ''}\n${result.stderr || ''}`, null);
1106
+ const payload = parseCommandJsonOutput(result, null);
1093
1107
  if (!payload) {
1094
1108
  throw createInstallerError(
1095
1109
  'invalid_gateway_status',
@@ -1118,7 +1132,7 @@ export async function readChannelStatus({
1118
1132
  dryRun,
1119
1133
  });
1120
1134
  const output = `${result.stdout || ''}\n${result.stderr || ''}`;
1121
- const payload = parseJsonDocument(output, null) || parseLegacyChannelStatus(output);
1135
+ const payload = parseCommandJsonOutput(result, null) || parseLegacyChannelStatus(output);
1122
1136
  if (!payload) {
1123
1137
  throw createInstallerError(
1124
1138
  'invalid_channel_status',
@@ -10,6 +10,7 @@ import {
10
10
  defaultClaworldAccountId,
11
11
  inspectClaworldChannelAccount,
12
12
  listClaworldAccountIds,
13
+ projectClaworldStatusAccount,
13
14
  resolveClaworldChannelAccount,
14
15
  resolveClaworldRuntimeConfig,
15
16
  validateClaworldChannelConfig,
@@ -61,6 +62,11 @@ function normalizeRelayHttpBaseUrl(serverUrl) {
61
62
  return parsed.toString().replace(/\/$/, '');
62
63
  }
63
64
 
65
+ function normalizePluginOptionalText(value) {
66
+ const normalized = String(value ?? '').trim();
67
+ return normalized || null;
68
+ }
69
+
64
70
  function inferRelayDomain(runtimeConfig = {}) {
65
71
  const defaultToAddress = String(runtimeConfig.relay?.defaultToAddress || '').trim();
66
72
  const atIndex = defaultToAddress.indexOf('@');
@@ -611,6 +617,7 @@ async function createChatRequest({
611
617
  targetAgentId,
612
618
  openingMessage = null,
613
619
  worldId = null,
620
+ requestContext = null,
614
621
  fetchImpl,
615
622
  }) {
616
623
  const normalizedTargetAgentId = normalizeClaworldText(targetAgentId, null);
@@ -638,6 +645,9 @@ async function createChatRequest({
638
645
  targetAgentId: normalizedTargetAgentId,
639
646
  openingMessage: normalizeClaworldText(openingMessage, null),
640
647
  ...(normalizeClaworldText(worldId, null) ? { worldId: normalizeClaworldText(worldId, null) } : {}),
648
+ ...(requestContext && typeof requestContext === 'object' && !Array.isArray(requestContext)
649
+ ? { requestContext }
650
+ : {}),
641
651
  }),
642
652
  });
643
653
  if (!result.ok) {
@@ -652,7 +662,7 @@ async function createChatRequest({
652
662
  return result.body || {};
653
663
  }
654
664
 
655
- async function listChatRequests({
665
+ async function listChatInbox({
656
666
  runtimeConfig,
657
667
  agentId,
658
668
  direction = null,
@@ -1301,7 +1311,12 @@ function createDeliveryReplyDispatcher({
1301
1311
  : undefined;
1302
1312
 
1303
1313
  let replied = false;
1314
+ let keptSilent = false;
1304
1315
  let suppressed = false;
1316
+ let replyTransport = null;
1317
+ let replyFallbackUsed = false;
1318
+ let keptSilentTransport = null;
1319
+ let keptSilentFallbackUsed = false;
1305
1320
  const finalTexts = [];
1306
1321
  const blockTexts = [];
1307
1322
  let partialContinuationText = '';
@@ -1383,16 +1398,52 @@ function createDeliveryReplyDispatcher({
1383
1398
  suppressed = true;
1384
1399
  return false;
1385
1400
  }
1386
- relayClient.sendReply({
1387
- deliveryId,
1388
- sessionKey,
1389
- replyText: normalized,
1390
- source: 'openclaw-autochain',
1391
- });
1401
+ if (typeof relayClient.sendReplyAndWaitForAck === 'function') {
1402
+ const replyResult = await relayClient.sendReplyAndWaitForAck({
1403
+ deliveryId,
1404
+ sessionKey,
1405
+ replyText: normalized,
1406
+ source: 'openclaw-autochain',
1407
+ });
1408
+ replyTransport = replyResult?.transport || 'websocket';
1409
+ replyFallbackUsed = replyResult?.fallbackUsed === true;
1410
+ } else {
1411
+ relayClient.sendReply({
1412
+ deliveryId,
1413
+ sessionKey,
1414
+ replyText: normalized,
1415
+ source: 'openclaw-autochain',
1416
+ });
1417
+ replyTransport = 'websocket-fire-and-forget';
1418
+ replyFallbackUsed = false;
1419
+ }
1392
1420
  replied = true;
1393
1421
  return true;
1394
1422
  };
1395
1423
 
1424
+ const flushKeptSilent = async (reason = null) => {
1425
+ if (replied || keptSilent || suppressed) return false;
1426
+ if (allowReply === false) {
1427
+ suppressed = true;
1428
+ return false;
1429
+ }
1430
+ if (typeof relayClient.sendKeepSilentAndWaitForAck === 'function') {
1431
+ const silentResult = await relayClient.sendKeepSilentAndWaitForAck({
1432
+ deliveryId,
1433
+ sessionKey,
1434
+ reason: normalizePluginOptionalText(reason) || 'no_renderable_reply',
1435
+ source: 'openclaw-autochain',
1436
+ });
1437
+ keptSilentTransport = silentResult?.transport || 'websocket';
1438
+ keptSilentFallbackUsed = silentResult?.fallbackUsed === true;
1439
+ } else {
1440
+ keptSilentTransport = 'unsupported';
1441
+ keptSilentFallbackUsed = false;
1442
+ }
1443
+ keptSilent = true;
1444
+ return true;
1445
+ };
1446
+
1396
1447
  const dispatchApi = runtime.channel.reply.createReplyDispatcherWithTyping({
1397
1448
  responsePrefix: prefixContext.responsePrefix,
1398
1449
  responsePrefixContextProvider: prefixContext.responsePrefixContextProvider,
@@ -1435,6 +1486,8 @@ function createDeliveryReplyDispatcher({
1435
1486
  : null;
1436
1487
  if (continuation.text) {
1437
1488
  await flushReply(continuation.text);
1489
+ } else {
1490
+ await flushKeptSilent(continuation.source);
1438
1491
  }
1439
1492
  }
1440
1493
  await dispatchApi.markDispatchIdle?.();
@@ -1472,6 +1525,7 @@ function createDeliveryReplyDispatcher({
1472
1525
  },
1473
1526
  markDispatchIdle,
1474
1527
  didReply: () => replied,
1528
+ didKeepSilent: () => keptSilent,
1475
1529
  getRuntimeOutputSummary: () => ({
1476
1530
  counts: { ...runtimeOutputSummary.counts },
1477
1531
  previews: {
@@ -1484,6 +1538,10 @@ function createDeliveryReplyDispatcher({
1484
1538
  },
1485
1539
  relayContinuationSource: runtimeOutputSummary.relayContinuationSource,
1486
1540
  relayContinuationPreview: runtimeOutputSummary.relayContinuationPreview,
1541
+ replyTransport,
1542
+ replyFallbackUsed,
1543
+ keptSilentTransport,
1544
+ keptSilentFallbackUsed,
1487
1545
  }),
1488
1546
  };
1489
1547
  }
@@ -1500,7 +1558,14 @@ async function runDeliveryReplyDispatch({
1500
1558
  runtimeAccountId,
1501
1559
  inboundCtx,
1502
1560
  } = {}) {
1503
- const { dispatcher, replyOptions, markDispatchIdle, didReply, getRuntimeOutputSummary } = createDeliveryReplyDispatcher({
1561
+ const {
1562
+ dispatcher,
1563
+ replyOptions,
1564
+ markDispatchIdle,
1565
+ didReply,
1566
+ didKeepSilent,
1567
+ getRuntimeOutputSummary,
1568
+ } = createDeliveryReplyDispatcher({
1504
1569
  runtime,
1505
1570
  currentCfg,
1506
1571
  relayClient,
@@ -1523,6 +1588,7 @@ async function runDeliveryReplyDispatch({
1523
1588
  return {
1524
1589
  dispatchResult,
1525
1590
  replied: didReply(),
1591
+ keptSilent: didKeepSilent(),
1526
1592
  runtimeOutputSummary: getRuntimeOutputSummary(),
1527
1593
  };
1528
1594
  }
@@ -1704,6 +1770,7 @@ async function maybeBridgeRuntimeDelivery({
1704
1770
  let {
1705
1771
  dispatchResult,
1706
1772
  replied,
1773
+ keptSilent,
1707
1774
  runtimeOutputSummary,
1708
1775
  } = await runDeliveryReplyDispatch({
1709
1776
  runtime,
@@ -1747,6 +1814,7 @@ async function maybeBridgeRuntimeDelivery({
1747
1814
  ({
1748
1815
  dispatchResult,
1749
1816
  replied,
1817
+ keptSilent,
1750
1818
  runtimeOutputSummary,
1751
1819
  } = await runDeliveryReplyDispatch({
1752
1820
  runtime,
@@ -1767,6 +1835,7 @@ async function maybeBridgeRuntimeDelivery({
1767
1835
  sessionKey,
1768
1836
  queuedFinal: Boolean(dispatchResult?.queuedFinal),
1769
1837
  replied,
1838
+ keptSilent,
1770
1839
  routeStatus: routed?.status || null,
1771
1840
  runtimeOutputSummary,
1772
1841
  });
@@ -1775,6 +1844,7 @@ async function maybeBridgeRuntimeDelivery({
1775
1844
  skipped: false,
1776
1845
  ok: true,
1777
1846
  replied,
1847
+ keptSilent,
1778
1848
  queuedFinal: Boolean(dispatchResult?.queuedFinal),
1779
1849
  sessionKey,
1780
1850
  routeStatus: routed?.status || null,
@@ -1794,6 +1864,69 @@ export function createClaworldChannelPlugin({
1794
1864
  const relayClients = new Map();
1795
1865
  const lifecycles = new Map();
1796
1866
  const accountRuntimeContexts = new Map();
1867
+ const accountBindingStates = new Map();
1868
+
1869
+ function resolveAccountBindingKey(runtimeConfig = {}, fallbackAccountId = 'default') {
1870
+ return String(runtimeConfig?.accountId || fallbackAccountId || 'default');
1871
+ }
1872
+
1873
+ function mergeBoundRuntimeConfig(currentRuntimeConfig = {}, boundRuntimeConfig = {}) {
1874
+ return applyRuntimeIdentity({
1875
+ ...currentRuntimeConfig,
1876
+ ...boundRuntimeConfig,
1877
+ relay: {
1878
+ ...(currentRuntimeConfig?.relay && typeof currentRuntimeConfig.relay === 'object' ? currentRuntimeConfig.relay : {}),
1879
+ ...(boundRuntimeConfig?.relay && typeof boundRuntimeConfig.relay === 'object' ? boundRuntimeConfig.relay : {}),
1880
+ },
1881
+ });
1882
+ }
1883
+
1884
+ async function ensureAccountRelayBinding({ runtimeConfig, accountId = null }) {
1885
+ const normalizedRuntimeConfig = applyRuntimeIdentity(runtimeConfig);
1886
+ const accountKey = resolveAccountBindingKey(normalizedRuntimeConfig, accountId || null);
1887
+ const cachedState = accountBindingStates.get(accountKey) || null;
1888
+ const cachedBinding = cachedState?.binding || null;
1889
+
1890
+ if (
1891
+ cachedBinding
1892
+ && cachedBinding.runtimeConfig?.serverUrl
1893
+ && cachedBinding.runtimeConfig.serverUrl === normalizedRuntimeConfig.serverUrl
1894
+ ) {
1895
+ return {
1896
+ ...cachedBinding,
1897
+ runtimeConfig: mergeBoundRuntimeConfig(normalizedRuntimeConfig, cachedBinding.runtimeConfig),
1898
+ bindingSource: cachedBinding.bindingSource === 'configured_app_token'
1899
+ ? 'configured_app_token'
1900
+ : 'binding_cache',
1901
+ };
1902
+ }
1903
+
1904
+ if (cachedState?.promise) {
1905
+ return cachedState.promise;
1906
+ }
1907
+
1908
+ const promise = ensureRelayBinding({
1909
+ runtimeConfig: normalizedRuntimeConfig,
1910
+ fetchImpl,
1911
+ logger,
1912
+ }).then((binding) => {
1913
+ const resolvedBinding = {
1914
+ ...binding,
1915
+ runtimeConfig: mergeBoundRuntimeConfig(normalizedRuntimeConfig, binding.runtimeConfig),
1916
+ };
1917
+ accountBindingStates.set(accountKey, { binding: resolvedBinding });
1918
+ return resolvedBinding;
1919
+ }).catch((error) => {
1920
+ const latest = accountBindingStates.get(accountKey) || null;
1921
+ if (latest?.promise === promise) {
1922
+ accountBindingStates.delete(accountKey);
1923
+ }
1924
+ throw error;
1925
+ });
1926
+
1927
+ accountBindingStates.set(accountKey, { promise });
1928
+ return promise;
1929
+ }
1797
1930
 
1798
1931
  async function persistRuntimeAppToken({ runtime, accountId, appToken }) {
1799
1932
  if (!runtime?.config?.loadConfig || !runtime?.config?.writeConfigFile) {
@@ -1847,7 +1980,7 @@ export function createClaworldChannelPlugin({
1847
1980
  bindingSource: 'runtime_context',
1848
1981
  };
1849
1982
  }
1850
- const binding = await ensureRelayBinding({ runtimeConfig, fetchImpl, logger });
1983
+ const binding = await ensureAccountRelayBinding({ runtimeConfig, accountId });
1851
1984
  runtimeConfig = binding.runtimeConfig;
1852
1985
  return {
1853
1986
  ...context,
@@ -1896,7 +2029,7 @@ export function createClaworldChannelPlugin({
1896
2029
 
1897
2030
  let binding;
1898
2031
  try {
1899
- binding = await ensureRelayBinding({ runtimeConfig, fetchImpl, logger });
2032
+ binding = await ensureAccountRelayBinding({ runtimeConfig, accountId: runtimeAccountId });
1900
2033
  } catch (error) {
1901
2034
  const normalized = normalizeRuntimeBoundaryError(error, {
1902
2035
  code: 'claworld_relay_binding_failed',
@@ -2059,6 +2192,7 @@ export function createClaworldChannelPlugin({
2059
2192
  relayClients.delete(accountKey);
2060
2193
  }
2061
2194
  accountRuntimeContexts.delete(accountKey);
2195
+ accountBindingStates.delete(accountKey);
2062
2196
  },
2063
2197
  });
2064
2198
 
@@ -2200,7 +2334,8 @@ export function createClaworldChannelPlugin({
2200
2334
  validate: validateClaworldChannelConfig,
2201
2335
  listAccountIds: (cfg) => listClaworldAccountIds(cfg),
2202
2336
  defaultAccountId: (cfg) => defaultClaworldAccountId(cfg),
2203
- inspectAccount: (cfg, accountId) => inspectClaworldChannelAccount(cfg, accountId),
2337
+ inspectAccount: (cfg, accountId) =>
2338
+ projectClaworldStatusAccount(inspectClaworldChannelAccount(cfg, accountId)),
2204
2339
  resolveAccount: (cfg, accountId) => resolveClaworldChannelAccount(cfg, accountId),
2205
2340
  resolveRuntimeConfig: (cfg, accountId) => resolveClaworldRuntimeConfig(cfg, accountId),
2206
2341
  isConfigured: (account, cfg) => {
@@ -2360,18 +2495,35 @@ export function createClaworldChannelPlugin({
2360
2495
  },
2361
2496
  requestChat: async (context = {}) => {
2362
2497
  const resolvedContext = await resolveBoundRuntimeContext(context);
2498
+ const requestContext = resolvedContext.requesterSessionKey
2499
+ ? {
2500
+ followUp: {
2501
+ sessionKey: resolvedContext.requesterSessionKey,
2502
+ },
2503
+ }
2504
+ : null;
2363
2505
  return createChatRequest({
2364
2506
  runtimeConfig: resolvedContext.runtimeConfig,
2365
2507
  fromAgentId: resolvedContext.agentId || null,
2366
2508
  targetAgentId: context.targetAgentId || null,
2367
2509
  openingMessage: context.openingMessage || context.message || context.text || null,
2368
2510
  worldId: context.worldId || null,
2511
+ requestContext,
2512
+ fetchImpl,
2513
+ });
2514
+ },
2515
+ listChatInbox: async (context = {}) => {
2516
+ const resolvedContext = await resolveBoundRuntimeContext(context);
2517
+ return listChatInbox({
2518
+ runtimeConfig: resolvedContext.runtimeConfig,
2519
+ agentId: resolvedContext.agentId || null,
2520
+ direction: context.direction || null,
2369
2521
  fetchImpl,
2370
2522
  });
2371
2523
  },
2372
2524
  listChatRequests: async (context = {}) => {
2373
2525
  const resolvedContext = await resolveBoundRuntimeContext(context);
2374
- return listChatRequests({
2526
+ return listChatInbox({
2375
2527
  runtimeConfig: resolvedContext.runtimeConfig,
2376
2528
  agentId: resolvedContext.agentId || null,
2377
2529
  direction: context.direction || null,
@@ -417,6 +417,14 @@ export function inspectClaworldChannelAccount(config = {}, accountId = null) {
417
417
  };
418
418
  }
419
419
 
420
+ export function projectClaworldStatusAccount(inspection = {}) {
421
+ // Keep the steady-state credential nested under relay/runtimeConfig so
422
+ // generic OpenClaw status does not misclassify Claworld as a bot+app token
423
+ // channel.
424
+ const { appToken: _appToken, ...statusAccount } = inspection || {};
425
+ return statusAccount;
426
+ }
427
+
420
428
  export function resolveClaworldRuntimeConfig(config = {}, accountId = null) {
421
429
  const result = validateClaworldChannelConfig(config, accountId);
422
430
  if (!result.ok) {
@@ -436,7 +444,7 @@ export function resolveClaworldChannelAccount(config = {}, accountId = null) {
436
444
  const runtimeConfig = resolveClaworldRuntimeConfig(config, accountId);
437
445
  const inspection = inspectClaworldChannelAccount(config, accountId);
438
446
  return {
439
- ...inspection,
447
+ ...projectClaworldStatusAccount(inspection),
440
448
  runtimeReady: true,
441
449
  resolvedFrom: accountId ? 'requested_account' : 'default_account',
442
450
  runtimeConfig,