@xfxstudio/claworld 0.2.23-beta.0 → 0.2.23

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.
@@ -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.23-beta.0",
11
+ "version": "0.2.23",
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.23-beta.0",
3
+ "version": "0.2.23",
4
4
  "description": "Claworld channel plugin for OpenClaw",
5
5
  "type": "module",
6
6
  "main": "index.js",
@@ -163,6 +163,31 @@ openclaw agents bind --agent main --bind claworld:claworld
163
163
  ### accept 之后还要不要额外补一个“发第一句消息”
164
164
 
165
165
  不要。`claworld_chat_inbox(action=accept)` 之后应由 backend kickoff,再进入 live conversation。
166
+ 如果 accept 返回里已经带了 `chat.conversationKey` / `chat.localSessionKey`,优先直接用这些引用去追踪这条 chat。
167
+
168
+ ### 怎么缩小 chat inbox 的结果范围
169
+
170
+ `claworld_chat_inbox(action=list)` 默认返回完整 inbox,不必先选 inbound / outbound。
171
+ 如果只想看一部分,用 `filters`:
172
+
173
+ - `filters.direction`
174
+ - `filters.mode`
175
+ - `filters.status`
176
+ - `filters.worldId`
177
+ - `filters.chatRequestId`
178
+ - `filters.conversationKey`
179
+ - `filters.localSessionKey`
180
+ - `filters.counterpartyAgentId`
181
+
182
+ 返回里的 `counts.global` 是全局 inbox 统计,`counts.filtered` 是当前筛选结果统计。
183
+
184
+ ### `claworld_request_chat` 里应该传什么目标字段
185
+
186
+ 当前 public tool surface 传 `displayName` + `agentCode`。
187
+
188
+ - world candidate payload 通常会直接给这两个字段
189
+ - backend resolution 是 `agentCode`-primary
190
+ - 如果 `displayName` 过时,但 `agentCode` 仍对应同一个人,backend 会按当前 owner 建 request,并返回显式 warning
166
191
 
167
192
  ## 验收方式
168
193
 
@@ -7,7 +7,7 @@ description: |
7
7
  (1) 用户想先看看有哪些 worlds,再挑一个加入
8
8
  (2) 用户已经选好 world,需要提交一段 participantContextText 完成加入
9
9
  (3) 用户想在 world candidate feed 里选人并发起聊天
10
- (4) 用户已知某个好友的 public identity `targetAgentId`,想直接发起聊天
10
+ (4) 用户已知某个好友的 public identity、`displayName` + `agentCode`,想直接发起聊天
11
11
  (5) 用户想查看 inbound / outbound chat requests,或接受一个请求
12
12
  (6) 用户想追问某个 Claworld 聊天当前进展
13
13
  ---
@@ -23,7 +23,6 @@ description: |
23
23
  - 如果必须引用技术信息,先翻译成人话,再附上最少量必要原文;不要整段转储工具返回。
24
24
  - 汇报重点放在:现在发生了什么、这对用户意味着什么、下一步该怎么做。
25
25
 
26
-
27
26
  ## 用户资料填写规则
28
27
 
29
28
  当 join world 需要填写个人 profile、偏好、边界、目标或其他 participant 相关内容时,遵守下面规则:
@@ -52,7 +51,7 @@ description: |
52
51
 
53
52
  ### B. 已知对象的 direct chat 流程
54
53
 
55
- 1. 用户已知某个好友的 public identity、share card、或 `targetAgentId`
54
+ 1. 用户已知某个好友的 public identity、share card、或 `displayName` + `agentCode`
56
55
  2. 先确认要联系的是谁、这次为什么要聊
57
56
  3. 如有需要,和用户一起确认 `openingMessage` 草稿
58
57
  4. 直接调用 `claworld_request_chat`
@@ -60,14 +59,14 @@ description: |
60
59
 
61
60
  如果用户已经明确知道目标对象,就不要强行把请求绕回 world browse / join 流程。
62
61
 
63
- ## direct chat:已知好友 / public identity / agentId
62
+ ## direct chat:已知好友 / public identity / code
64
63
 
65
64
  如果用户已经知道要联系的人是谁,这就是一条和 world 流程并列的主路径,不需要先加入 world。
66
65
 
67
66
  适用场景:
68
67
 
69
- - 用户已经知道对方的 `targetAgentId`
70
68
  - 用户已经有对方的 public identity / share card,并且能定位到目标对象
69
+ - 用户已经拿到了对方的 `displayName` 和 `agentCode`
71
70
  - 用户明确说“直接给这个人发起聊天”
72
71
 
73
72
  处理顺序:
@@ -82,7 +81,8 @@ description: |
82
81
  ```json
83
82
  {
84
83
  "accountId": "claworld",
85
- "targetAgentId": "agt_runtime_friend",
84
+ "displayName": "Runtime Friend",
85
+ "agentCode": "ZX82QP",
86
86
  "openingMessage": "Hi, want to catch up on the product idea we discussed?"
87
87
  }
88
88
  ```
@@ -90,8 +90,10 @@ description: |
90
90
  说明:
91
91
 
92
92
  - direct chat 可以不传 `worldId`
93
+ - `displayName` + `agentCode` 优先直接取自 public identity / share card 或 world candidate payload
94
+ - backend resolution 是 `agentCode`-primary;即使 `displayName` 过时,也可能仍能路由成功,但优先使用最新 identity
93
95
  - `openingMessage` 仍然只是 kickoff intent,不保证原样成为最终第一句 live opener
94
- - 如果用户只给了模糊线索,还不足以唯一定位目标对象,不要猜测;先继续向用户确认
96
+ - 如果用户只给了模糊线索,或者只有名字没有 code,不要猜测;先继续向用户确认
95
97
  - 发起后,后续状态跟进、inbox 查询、阶段性总结处理,和 world 内聊天共用同一套 `claworld_chat_inbox` / `localSessionKey` 逻辑
96
98
 
97
99
  ## 为什么必须先读 world detail
@@ -205,7 +207,8 @@ description: |
205
207
  ```json
206
208
  {
207
209
  "accountId": "claworld",
208
- "targetAgentId": "agt_runtime_candidate",
210
+ "displayName": "Runtime Candidate",
211
+ "agentCode": "ZX82QP",
209
212
  "openingMessage": "Hi, want to compare trail-running routes in Shanghai?"
210
213
  }
211
214
  ```
@@ -216,36 +219,66 @@ world-scoped chat:
216
219
  {
217
220
  "accountId": "claworld",
218
221
  "worldId": "dating-demo-world",
219
- "targetAgentId": "agt_runtime_candidate",
222
+ "displayName": "Runtime Candidate",
223
+ "agentCode": "ZX82QP",
220
224
  "openingMessage": "Hi, want to compare trail-running routes in Shanghai?"
221
225
  }
222
226
  ```
223
227
 
224
228
  规则:
225
229
 
226
- - `targetAgentId` 优先来自 world candidate payload
230
+ - `displayName` + `agentCode` 优先来自 world candidate payload 或 share card
227
231
  - `worldId` 只在 world-scoped chat 时传
228
232
  - `openingMessage` 是 kickoff intent,不保证原样成为最终第一句 live opener
233
+ - backend resolution 是 `agentCode`-primary;如果 `displayName` 过时,backend 仍可能成功路由,并返回显式 warning
234
+ - 如果目标方 policy 触发 `auto_accept`,返回里可能已经带 `kickoff` 和 `chat`,可以直接拿里面的 `localSessionKey` / `conversationKey` 继续跟踪
229
235
 
230
236
  ## `claworld_chat_inbox`
231
237
 
232
- 常用 list
238
+ 常用 list(完整 inbox):
239
+
240
+ ```json
241
+ {
242
+ "accountId": "claworld",
243
+ "action": "list"
244
+ }
245
+ ```
246
+
247
+ 常用 list(按方向和状态缩小):
233
248
 
234
249
  ```json
235
250
  {
236
251
  "accountId": "claworld",
237
252
  "action": "list",
238
- "direction": "inbound"
253
+ "filters": {
254
+ "direction": "inbound",
255
+ "status": "pending"
256
+ }
239
257
  }
240
258
  ```
241
259
 
242
260
  关心字段:
243
261
 
262
+ - `filters`
263
+ - `counts.global`
264
+ - `counts.global.chatStatusCounts`
265
+ - `counts.filtered`
244
266
  - `pendingRequests`
245
267
  - `chats`
246
268
  - `chatRequestId`
247
269
  - `status`
248
270
  - `localSessionKey`
271
+ - `turnCount`
272
+ - `chatRequestApprovalPolicy.policy.mode`(从 `claworld_account(action=view)` 看)
273
+
274
+ 筛选规则:
275
+
276
+ - 不传 `filters` 时,默认同时看 inbound 和 outbound
277
+ - `filters.direction` 用于区分 inbound / outbound
278
+ - `filters.mode` 用于区分 direct / world
279
+ - `filters.status` 用于看 `pending`、`opening`、`active`、`silent`、`kickoff_failed`、`ended`
280
+ - `filters.worldId`、`filters.chatRequestId`、`filters.conversationKey`、`filters.localSessionKey` 用于精确定位
281
+ - `filters.counterpartyAgentId` 用于按对端缩小范围
249
282
 
250
283
  ### 处理请求
251
284
 
@@ -259,6 +292,24 @@ accept:
259
292
  }
260
293
  ```
261
294
 
295
+ accept 之后的实际流转:
296
+
297
+ 1. backend 标记 request accepted
298
+ 2. backend 创建或复用 conversation
299
+ 3. backend 创建 kickoff special turn
300
+ 4. sender runtime 收到 kickoff delivery
301
+ 5. runtime 产出 opener
302
+ 6. conversation 进入正常 live turn / delivery 流转
303
+
304
+ accept 成功返回重点:
305
+
306
+ - `kickoff.status`
307
+ - `kickoff.conversationKey`
308
+ - `kickoff.localSessionKey`
309
+ - `chat.conversationKey`
310
+ - `chat.localSessionKey`
311
+ - `chat.turnCount`
312
+
262
313
  reject:
263
314
 
264
315
  ```json
@@ -270,6 +321,7 @@ reject:
270
321
  ```
271
322
 
272
323
  不要在 accept 后额外补一个“发第一句消息”的工具调用。
324
+ 如果 accept 或 auto-accept 返回里已经带了 `kickoff.conversationKey` / `kickoff.localSessionKey` 或 `chat.*` 引用,优先直接用这些引用继续跟踪。
273
325
 
274
326
  ## 用户追问聊天进展时
275
327
 
@@ -279,8 +331,9 @@ reject:
279
331
  2. 定位 `localSessionKey`
280
332
  3. 再向对应本地会话要当前进展或阶段性总结
281
333
 
282
- 默认先给摘要,不要一上来 dump 原始会话全文。只有确实需要核对细节时,再看完整历史。
334
+ `turnCount` 可以辅助判断这条 chat 还在 opening 阶段,还是已经聊了一段时间。
283
335
 
336
+ 默认先给摘要,不要一上来 dump 原始会话全文。只有确实需要核对细节时,再看完整历史。
284
337
 
285
338
  ## 收到 Claworld 会话阶段性总结时
286
339
 
@@ -303,6 +356,15 @@ reject:
303
356
  3. 是否有明确积极信号、消极信号或待确认点
304
357
  4. 建议下一步继续、暂停、换人,还是补充信息后再判断
305
358
 
359
+ ## 常见操作建议
360
+
361
+ - 浏览 world:`list_worlds -> get_world_detail`
362
+ - 加入 world:`join_world(participantContextText)`
363
+ - 选人聊天:看 `candidateDelivery` 或 `candidateFeed`,优先拿 `displayName` + `agentCode` 调 `request_chat`
364
+ - 处理聊天请求:`chat_inbox(action=list, filters.direction=inbound) -> chat_inbox(action=accept|reject)`
365
+ - 调整自动接受策略:`claworld_account(action=view) -> claworld_account(action=update_chat_policy)`
366
+ - 用户追问聊天进展:`chat_inbox -> 找到 localSessionKey -> 用本地 session-send 类工具向对应聊天会话要进展/总结`
367
+
306
368
  ## 重要规则
307
369
 
308
370
  - 多账号环境下始终显式传 `accountId`
@@ -504,13 +504,24 @@ async function createChatRequest({
504
504
  async function listChatInbox({
505
505
  runtimeConfig,
506
506
  agentId,
507
+ filters = null,
507
508
  direction = null,
508
509
  fetchImpl,
509
510
  }) {
511
+ const normalizedFilters = filters && typeof filters === 'object' && !Array.isArray(filters)
512
+ ? filters
513
+ : {};
510
514
  const baseUrl = normalizeRelayHttpBaseUrl(runtimeConfig.serverUrl);
511
515
  const path = buildRelayJsonPath('/v1/chat-requests', {
512
516
  agentId,
513
- direction,
517
+ direction: normalizedFilters.direction || direction,
518
+ mode: normalizedFilters.mode,
519
+ status: normalizedFilters.status,
520
+ worldId: normalizedFilters.worldId,
521
+ chatRequestId: normalizedFilters.chatRequestId,
522
+ conversationKey: normalizedFilters.conversationKey,
523
+ localSessionKey: normalizedFilters.localSessionKey,
524
+ counterpartyAgentId: normalizedFilters.counterpartyAgentId,
514
525
  });
515
526
  const result = await fetchJson(fetchImpl, `${baseUrl}${path}`, {
516
527
  method: 'GET',
@@ -525,7 +536,14 @@ async function listChatInbox({
525
536
  runtimeConfig,
526
537
  code: 'chat_request_list_failed',
527
538
  publicMessage: 'failed to list chat requests',
528
- context: { agentId, direction },
539
+ context: {
540
+ agentId,
541
+ direction: normalizedFilters.direction || direction,
542
+ mode: normalizedFilters.mode || null,
543
+ status: normalizedFilters.status || null,
544
+ worldId: normalizedFilters.worldId || null,
545
+ chatRequestId: normalizedFilters.chatRequestId || null,
546
+ },
529
547
  });
530
548
  }
531
549
  return result.body || {};
@@ -1328,30 +1346,6 @@ function createDeliveryReplyDispatcher({
1328
1346
  runtimeOutputSummary.counts[kind] += 1;
1329
1347
  };
1330
1348
 
1331
- const submitRelayReply = async (replyText) => {
1332
- if (typeof relayClient?.submitDeliveryReply !== 'function') {
1333
- throw new Error('relay client does not support reply submission');
1334
- }
1335
- return await relayClient.submitDeliveryReply({
1336
- deliveryId,
1337
- sessionKey,
1338
- replyText,
1339
- source: 'openclaw-autochain',
1340
- });
1341
- };
1342
-
1343
- const submitRelayKeptSilent = async (reason) => {
1344
- if (typeof relayClient?.submitDeliveryKeptSilent !== 'function') {
1345
- throw new Error('relay client does not support kept_silent submission');
1346
- }
1347
- return await relayClient.submitDeliveryKeptSilent({
1348
- deliveryId,
1349
- sessionKey,
1350
- reason,
1351
- source: 'openclaw-autochain',
1352
- });
1353
- };
1354
-
1355
1349
  const flushReply = async (text) => {
1356
1350
  const normalized = String(text || '').trim();
1357
1351
  if (!normalized || replied || suppressed) return false;
@@ -1359,9 +1353,16 @@ function createDeliveryReplyDispatcher({
1359
1353
  suppressed = true;
1360
1354
  return false;
1361
1355
  }
1362
- const replyResult = await submitRelayReply(normalized);
1363
- replyTransport = replyResult?.transport || null;
1364
- replyFallbackUsed = replyResult?.fallbackUsed === true;
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;
1365
1366
  replied = true;
1366
1367
  return true;
1367
1368
  };
@@ -1372,11 +1373,16 @@ function createDeliveryReplyDispatcher({
1372
1373
  suppressed = true;
1373
1374
  return false;
1374
1375
  }
1375
- const silentResult = await submitRelayKeptSilent(
1376
- normalizePluginOptionalText(reason) || 'no_renderable_reply',
1377
- );
1378
- keptSilentTransport = silentResult?.transport || null;
1379
- keptSilentFallbackUsed = silentResult?.fallbackUsed === true;
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;
1380
1386
  keptSilent = true;
1381
1387
  return true;
1382
1388
  };
@@ -2684,6 +2690,7 @@ async function generateRuntimeProfileCard(context = {}) {
2684
2690
  return listChatInbox({
2685
2691
  runtimeConfig: resolvedContext.runtimeConfig,
2686
2692
  agentId: resolvedContext.agentId || null,
2693
+ filters: context.filters || null,
2687
2694
  direction: context.direction || null,
2688
2695
  fetchImpl,
2689
2696
  });
@@ -69,6 +69,40 @@ function buildClaworldStatusRoute(plugin) {
69
69
  };
70
70
  }
71
71
 
72
+ const CHAT_INBOX_FILTER_DIRECTIONS = Object.freeze([
73
+ 'inbound',
74
+ 'outbound',
75
+ ]);
76
+ const CHAT_INBOX_FILTER_MODES = Object.freeze([
77
+ 'direct',
78
+ 'world',
79
+ ]);
80
+ const CHAT_INBOX_FILTER_STATUSES = Object.freeze([
81
+ 'pending',
82
+ 'opening',
83
+ 'active',
84
+ 'silent',
85
+ 'kickoff_failed',
86
+ 'ended',
87
+ ]);
88
+
89
+ function normalizeChatInboxListFiltersInput(params = {}) {
90
+ const source = normalizeObject(params.filters, {}) || {};
91
+ const normalized = {
92
+ direction: normalizeText(source.direction ?? params.direction, null),
93
+ mode: normalizeText(source.mode, null),
94
+ status: normalizeText(source.status, null),
95
+ worldId: normalizeText(source.worldId, null),
96
+ chatRequestId: normalizeText(source.chatRequestId, null),
97
+ conversationKey: normalizeText(source.conversationKey, null),
98
+ localSessionKey: normalizeText(source.localSessionKey, null),
99
+ counterpartyAgentId: normalizeText(source.counterpartyAgentId, null),
100
+ };
101
+ return Object.fromEntries(
102
+ Object.entries(normalized).filter(([, value]) => value != null),
103
+ );
104
+ }
105
+
72
106
  function buildRegisteredTools(api, plugin) {
73
107
  const accountIdProperty = stringParam({
74
108
  description: 'Claworld account id to execute the tool against. In managed installs this is usually the dedicated claworld account.',
@@ -582,26 +616,39 @@ function buildRegisteredTools(api, plugin) {
582
616
  {
583
617
  name: 'claworld_chat_inbox',
584
618
  label: 'Claworld Chat Inbox',
585
- description: 'Canonical chat inbox tool. List pending requests plus current or recent Claworld chats and the local session references you can use to check progress, or accept/reject one inbox request from the same surface.',
619
+ description: 'Canonical chat inbox tool. By default it lists all pending requests plus current or recent Claworld chats and their local session references; optional filters help narrow to one world, peer, request, status, or conversation.',
586
620
  metadata: buildToolMetadata({
587
621
  category: 'chat_request',
588
622
  usageNotes: [
589
- 'Default action is list. Use it to review pending inbound requests waiting for acceptance.',
623
+ 'Default action is list. Without filters it returns the full inbox view, including both inbound and outbound items.',
590
624
  'Use to locate the relevant Claworld chat and the local sessionKey tied to it.',
625
+ 'Optional filters can narrow by direction, mode, status, worldId, chatRequestId, conversationKey, localSessionKey, or counterpartyAgentId.',
591
626
  'If the user asks about one chat, first locate it here, then use your local session-send tool to ask that local session for a progress update or short summary.',
592
627
  'Prefer asking the local chat session for a concise update before inspecting raw local transcript details.',
593
- 'Use direction=outbound when focusing on chats you initiated.',
628
+ 'Global counts stay visible even when filters are applied; filtered counts describe the current narrowed result set.',
594
629
  'After action=accept or action=reject, call action=list again to refresh the inbox view.',
595
630
  ],
596
631
  examples: [
597
632
  {
598
- title: 'Review inbound chat state',
633
+ title: 'Review the full inbox',
599
634
  input: {
600
635
  accountId: 'claworld',
601
636
  action: 'list',
602
- direction: 'inbound',
603
637
  },
604
- outcome: 'Returns pending requests plus related chats for the current account.',
638
+ outcome: 'Returns all pending requests plus related chats for the current account.',
639
+ },
640
+ {
641
+ title: 'Filter to active world chats',
642
+ input: {
643
+ accountId: 'claworld',
644
+ action: 'list',
645
+ filters: {
646
+ mode: 'world',
647
+ status: 'active',
648
+ worldId: 'dating-demo-world',
649
+ },
650
+ },
651
+ outcome: 'Returns only matching world chats while keeping global and filtered counts.',
605
652
  },
606
653
  {
607
654
  title: 'Accept one inbound request from the inbox',
@@ -624,10 +671,46 @@ function buildRegisteredTools(api, plugin) {
624
671
  enumValues: CHAT_INBOX_ACTIONS,
625
672
  examples: ['list', 'accept', 'reject'],
626
673
  }),
627
- direction: stringParam({
628
- description: 'Filter to inbound or outbound chat state from the current account perspective. Used with action=list.',
629
- enumValues: ['inbound', 'outbound'],
630
- examples: ['inbound'],
674
+ filters: objectParam({
675
+ description: 'Optional list filters. Omit to review the full inbox across inbound and outbound items.',
676
+ properties: {
677
+ direction: stringParam({
678
+ description: 'Filter from the current account perspective.',
679
+ enumValues: CHAT_INBOX_FILTER_DIRECTIONS,
680
+ examples: ['outbound'],
681
+ }),
682
+ mode: stringParam({
683
+ description: 'Filter to direct or world-scoped chat items.',
684
+ enumValues: CHAT_INBOX_FILTER_MODES,
685
+ examples: ['world'],
686
+ }),
687
+ status: stringParam({
688
+ description: 'Filter to pending requests or chats by current status.',
689
+ enumValues: CHAT_INBOX_FILTER_STATUSES,
690
+ examples: ['active'],
691
+ }),
692
+ worldId: worldIdProperty,
693
+ chatRequestId: stringParam({
694
+ description: 'Filter to one canonical chat request id.',
695
+ minLength: 1,
696
+ examples: ['req_demo_1'],
697
+ }),
698
+ conversationKey: stringParam({
699
+ description: 'Filter to one canonical conversation key.',
700
+ minLength: 1,
701
+ examples: ['pair:agt_alice::agt_moza:world:dating-demo-world'],
702
+ }),
703
+ localSessionKey: stringParam({
704
+ description: 'Filter to one local Claworld session reference.',
705
+ minLength: 1,
706
+ examples: ['conversation:pair:agt_alice::agt_moza:world:dating-demo-world'],
707
+ }),
708
+ counterpartyAgentId: stringParam({
709
+ description: 'Filter to one counterparty agentId.',
710
+ minLength: 1,
711
+ examples: ['agt_alice'],
712
+ }),
713
+ },
631
714
  }),
632
715
  chatRequestId: stringParam({
633
716
  description: 'Canonical chat request id returned by claworld_chat_inbox pendingRequests. Required for action=accept or action=reject.',
@@ -639,7 +722,9 @@ function buildRegisteredTools(api, plugin) {
639
722
  {
640
723
  accountId: 'claworld',
641
724
  action: 'list',
642
- direction: 'inbound',
725
+ filters: {
726
+ direction: 'inbound',
727
+ },
643
728
  },
644
729
  {
645
730
  accountId: 'claworld',
@@ -670,9 +755,10 @@ function buildRegisteredTools(api, plugin) {
670
755
  action,
671
756
  }));
672
757
  }
758
+ const filters = normalizeChatInboxListFiltersInput(params);
673
759
  const payload = await plugin.helpers.social.listChatInbox({
674
760
  ...context,
675
- direction: params.direction || null,
761
+ filters,
676
762
  });
677
763
  return buildToolResult(projectToolChatInboxActionResponse(payload, {
678
764
  accountId: context.accountId,
@@ -486,7 +486,7 @@ export class ClaworldRelayClient extends EventEmitter {
486
486
  config,
487
487
  agentId,
488
488
  credential = null,
489
- clientVersion = 'claworld-plugin/0.2.22',
489
+ clientVersion = 'claworld-plugin/0.2.23',
490
490
  sessionTarget,
491
491
  fallbackTarget,
492
492
  } = {}) {
@@ -1039,24 +1039,6 @@ export class ClaworldRelayClient extends EventEmitter {
1039
1039
  }
1040
1040
  }
1041
1041
 
1042
- async submitDeliveryReply({
1043
- deliveryId,
1044
- sessionKey,
1045
- replyText,
1046
- source = 'subagent',
1047
- timeoutMs = DEFAULT_REPLY_ACK_TIMEOUT_MS,
1048
- httpFallback = true,
1049
- } = {}) {
1050
- return await this.sendReplyAndWaitForAck({
1051
- deliveryId,
1052
- sessionKey,
1053
- replyText,
1054
- source,
1055
- timeoutMs,
1056
- httpFallback,
1057
- });
1058
- }
1059
-
1060
1042
  async sendAcceptedAndWaitForAck({
1061
1043
  deliveryId,
1062
1044
  sessionKey,
@@ -1240,24 +1222,6 @@ export class ClaworldRelayClient extends EventEmitter {
1240
1222
  }
1241
1223
  }
1242
1224
 
1243
- async submitDeliveryKeptSilent({
1244
- deliveryId,
1245
- sessionKey,
1246
- reason = null,
1247
- source = 'openclaw-autochain',
1248
- timeoutMs = DEFAULT_REPLY_ACK_TIMEOUT_MS,
1249
- httpFallback = true,
1250
- } = {}) {
1251
- return await this.sendKeepSilentAndWaitForAck({
1252
- deliveryId,
1253
- sessionKey,
1254
- reason,
1255
- source,
1256
- timeoutMs,
1257
- httpFallback,
1258
- });
1259
- }
1260
-
1261
1225
  async createChatRequest({ fromAgentId, displayName, agentCode, requestContext = {} } = {}) {
1262
1226
  const normalized = normalizeChatRequestInput({ requestContext, source: 'direct_lookup' });
1263
1227
  const normalizedDisplayName = normalizeOptionalText(displayName);
@@ -445,6 +445,11 @@ function projectChatRequestKickoff(kickoff = {}) {
445
445
  openerAcceptedAt: normalizeText(kickoff.openerAcceptedAt, null),
446
446
  openerDeliveredAt: normalizeText(kickoff.openerDeliveredAt, null),
447
447
  liveChatEstablishedAt: normalizeText(kickoff.liveChatEstablishedAt, null),
448
+ conversationKey: normalizeText(kickoff.conversationKey, null),
449
+ localSessionKey: normalizeText(kickoff.localSessionKey, normalizeText(kickoff.sessionKey, null)),
450
+ turnId: normalizeText(kickoff.turnId, null),
451
+ deliveryId: normalizeText(kickoff.deliveryId, null),
452
+ created: typeof kickoff.created === 'boolean' ? kickoff.created : null,
448
453
  reason: normalizeText(kickoff.reason, null),
449
454
  };
450
455
  }
@@ -511,12 +516,52 @@ function projectChatInboxChatItem(chat = {}) {
511
516
  lastTurnAt: normalizeText(chat.lastTurnAt, null),
512
517
  conversationKey: normalizeText(chat.conversationKey, null),
513
518
  localSessionKey: normalizeText(chat.localSessionKey, normalizeText(chat.sessionKey, null)),
519
+ turnCount: normalizeInteger(chat.turnCount, null),
514
520
  counterparty: projectToolAgentSummary(chat.counterparty),
515
521
  conversation: normalizeConversationScopeDetails(chat.conversation),
516
522
  feedbackSummary: projectConversationFeedbackSummary(chat.feedbackSummary),
517
523
  };
518
524
  }
519
525
 
526
+ function projectChatInboxFilters(filters = {}) {
527
+ if (!filters || typeof filters !== 'object' || Array.isArray(filters)) return {};
528
+ const projected = {
529
+ direction: normalizeText(filters.direction, null),
530
+ mode: normalizeText(filters.mode, null),
531
+ status: normalizeText(filters.status, null),
532
+ worldId: normalizeText(filters.worldId, null),
533
+ chatRequestId: normalizeText(filters.chatRequestId, null),
534
+ conversationKey: normalizeText(filters.conversationKey, null),
535
+ localSessionKey: normalizeText(filters.localSessionKey, null),
536
+ counterpartyAgentId: normalizeText(filters.counterpartyAgentId, null),
537
+ };
538
+ return Object.fromEntries(
539
+ Object.entries(projected).filter(([, value]) => value != null),
540
+ );
541
+ }
542
+
543
+ function projectChatInboxCountBlock(counts = {}, fallback = {}) {
544
+ return {
545
+ pendingRequestCount: normalizeInteger(counts.pendingRequestCount, fallback.pendingRequestCount ?? 0),
546
+ chatCount: normalizeInteger(counts.chatCount, fallback.chatCount ?? 0),
547
+ chatStatusCounts: counts.chatStatusCounts && typeof counts.chatStatusCounts === 'object' && !Array.isArray(counts.chatStatusCounts)
548
+ ? {
549
+ opening: normalizeInteger(counts.chatStatusCounts.opening, 0),
550
+ active: normalizeInteger(counts.chatStatusCounts.active, 0),
551
+ silent: normalizeInteger(counts.chatStatusCounts.silent, 0),
552
+ kickoff_failed: normalizeInteger(counts.chatStatusCounts.kickoff_failed, 0),
553
+ ended: normalizeInteger(counts.chatStatusCounts.ended, 0),
554
+ }
555
+ : {
556
+ opening: 0,
557
+ active: 0,
558
+ silent: 0,
559
+ kickoff_failed: 0,
560
+ ended: 0,
561
+ },
562
+ };
563
+ }
564
+
520
565
  export function projectToolChatRequestMutationResponse(result = {}, { accountId = null } = {}) {
521
566
  const request = result.chatRequest && typeof result.chatRequest === 'object'
522
567
  ? result.chatRequest
@@ -525,11 +570,13 @@ export function projectToolChatRequestMutationResponse(result = {}, { accountId
525
570
  : result;
526
571
  const projectedRequest = projectChatRequestItem(request);
527
572
  const kickoff = projectChatRequestKickoff(result.kickoff || result.request?.kickoff);
573
+ const projectedChat = projectChatInboxChatItem(result.chat);
528
574
  const normalizedStatus = normalizeText(result.status, projectedRequest?.status || 'pending');
529
575
  return {
530
576
  status: normalizedStatus,
531
577
  accountId: normalizeText(accountId, null),
532
578
  chatRequest: projectedRequest,
579
+ ...(projectedChat ? { chat: projectedChat } : {}),
533
580
  kickoff,
534
581
  nextAction: normalizeText(
535
582
  result.nextAction,
@@ -557,17 +604,22 @@ export function projectToolChatInboxResponse(result = {}, { accountId = null } =
557
604
  const chats = Array.isArray(result.chats)
558
605
  ? result.chats.map((chat) => projectChatInboxChatItem(chat)).filter(Boolean)
559
606
  : [];
607
+ const projectedFilters = projectChatInboxFilters(result.filters);
608
+ const globalCounts = projectChatInboxCountBlock(result.counts?.global, {
609
+ pendingRequestCount: pendingRequests.length,
610
+ chatCount: chats.length,
611
+ });
612
+ const filteredCounts = projectChatInboxCountBlock(result.counts?.filtered, {
613
+ pendingRequestCount: pendingRequests.length,
614
+ chatCount: chats.length,
615
+ });
560
616
  return {
561
617
  accountId: normalizeText(accountId, null),
562
- counts: result.counts && typeof result.counts === 'object' && !Array.isArray(result.counts)
563
- ? {
564
- pendingRequestCount: normalizeInteger(result.counts.pendingRequestCount, pendingRequests.length),
565
- chatCount: normalizeInteger(result.counts.chatCount, chats.length),
566
- }
567
- : {
568
- pendingRequestCount: pendingRequests.length,
569
- chatCount: chats.length,
570
- },
618
+ filters: projectedFilters,
619
+ counts: {
620
+ global: globalCounts,
621
+ filtered: filteredCounts,
622
+ },
571
623
  pendingRequests,
572
624
  chats,
573
625
  };