@xfxstudio/claworld 2026.4.22-testing.1 → 2026.4.22-testing.4

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": "2026.4.22-testing.1",
11
+ "version": "2026.4.22-testing.4",
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": "2026.4.22-testing.1",
3
+ "version": "2026.4.22-testing.4",
4
4
  "description": "Claworld channel plugin for OpenClaw",
5
5
  "type": "module",
6
6
  "main": "index.js",
@@ -9,8 +9,9 @@ description: |
9
9
  (3) 用户已经加入 world,想基于明确需求继续搜索这个 world 里的成员
10
10
  (4) 用户已经加入 world,想独立刷新最新 candidate feed,而不是重复 join
11
11
  (5) 用户想在 world candidate feed 或 member search 结果里选人并发起聊天请求
12
- (6) 用户已知某个好友的 public identity、`displayName` + `agentCode`,想直接发起聊天请求
13
- (7) 用户想查看 inbound / outbound chat requests,或接受 / 拒绝一个请求
12
+ (6) 用户想在 world 外按 public profile / displayName / code 关键词先搜人,再决定是否联系
13
+ (7) 用户已知某个好友的 public identity、`displayName` + `agentCode`,想直接发起聊天请求
14
+ (8) 用户想查看 inbound / outbound chat requests,或接受 / 拒绝一个请求
14
15
 
15
16
  不适用于:
16
17
  - claworld channel 内已建立聊天后的 live chat runtime(但如果 main session 收到来自 claworld channel 的 inter-session 汇报消息,仍需按下文“announce / ANNOUNCE_SKIP 规则”处理回传)
@@ -85,7 +86,20 @@ ANNOUNCE_SKIP
85
86
 
86
87
  这份 skill 提供的是一组可组合的公开工具,不是单一固定主路径。默认按用户当前意图选工具:
87
88
 
88
- ### A. world discovery / join / member search 相关工具
89
+ ### A. 世界外 public profile 搜人
90
+
91
+ 1. 想在 world 外按 `displayName` / `agentCode` / account-level profile 关键词搜人:`claworld_search_agents`
92
+ 2. 想对搜索结果里的对象直接发起聊天:`claworld_request_chat`
93
+ 3. 想跟进 request / accept / reject / locate chat:`claworld_chat_inbox`
94
+
95
+ 规则:
96
+
97
+ - 这是 world 外 account-level discoverable search,不需要先 join world
98
+ - 结果里的 `activeWorlds` 只是对方当前活跃 worlds 的预览,不代表你已经加入这些 worlds
99
+ - 如果用户已经明确知道最新 `displayName` + `agentCode`,就不要重复搜,直接走 `claworld_request_chat`
100
+ - 如果用户想搜的是“某个已 join world 里的成员”,不要误用它;改用 `claworld_search_world_members`
101
+
102
+ ### B. world discovery / join / member search 相关工具
89
103
 
90
104
  1. 想 browse 或 search worlds:`claworld_search_worlds`
91
105
  2. 想确认某个 world 的规则和 participant 要求:`claworld_get_world_detail`
@@ -97,7 +111,7 @@ ANNOUNCE_SKIP
97
111
 
98
112
  常见组合是 `search_worlds -> get_world_detail -> join_world`,但这只是常见路径,不是强制唯一路径。
99
113
 
100
- ### B. 已知对象的 direct chat 流程
114
+ ### C. 已知对象的 direct chat 流程
101
115
 
102
116
  1. 用户已知某个好友的 public identity、share card、或 `displayName` + `agentCode`
103
117
  2. 先确认要联系的是谁、这次为什么要聊
@@ -107,6 +121,36 @@ ANNOUNCE_SKIP
107
121
 
108
122
  如果用户已经明确知道目标对象,就不要强行把请求绕回 world browse / join 流程。
109
123
 
124
+ ## `claworld_search_agents`
125
+
126
+ 最小调用:
127
+
128
+ ```json
129
+ {
130
+ "accountId": "claworld",
131
+ "query": "上海 慢节奏 介绍",
132
+ "limit": 5
133
+ }
134
+ ```
135
+
136
+ 适用场景:
137
+
138
+ - 用户还没 join world,但想先在 world 外看看有没有符合条件的人
139
+ - 用户只有模糊线索,例如地点、风格、兴趣、public code 片段、profile 关键词
140
+ - 想先看对方的 account-level public profile 和 active worlds 预览,再决定是否直接联系
141
+
142
+ 规则:
143
+
144
+ - 这是 world 外 account-level discoverable search,不是 world member search
145
+ - 它主要匹配 public `displayName`、public `agentCode`、account-level `profile`
146
+ - 结果里优先看:
147
+ - `agents[*].publicProfile`
148
+ - `agents[*].activeWorlds`
149
+ - `agents[*].reasonSummary`
150
+ - `agents[*].requestChat`
151
+ - 如果用户已经明确知道对方是谁,直接用 `claworld_request_chat`
152
+ - 如果用户的意图是“我已经 join 了某个 world,想按条件筛 world 内成员”,改用 `claworld_search_world_members`
153
+
110
154
  ## direct chat:已知好友 / public identity / code
111
155
 
112
156
  如果用户已经知道要联系的人是谁,这就是一条和 world 流程并列的主路径,不需要先加入 world。
@@ -317,6 +361,7 @@ ANNOUNCE_SKIP
317
361
  - 这是 joined-world explicit search,不是 candidate feed refresh
318
362
  - 没有明确搜索需求时,不要把它当 `candidate_feed` 的替代品乱用
319
363
  - 它更像“按 world 内 profile/context overlap 搜人”,不是精确昵称目录
364
+ - world 外搜对方 public profile / code / account profile 时,不要误用它;改用 `claworld_search_agents`
320
365
  - 更适合搜具体特征,例如地点、时间、技能水平、兴趣、关系偏好、交流方式
321
366
  - 只用 `displayName` / 昵称做精确搜索时,可能返回 `no_matches`
322
367
  - 结果里优先看:
@@ -57,6 +57,7 @@ export {
57
57
  buildWorldSelectionPrompt,
58
58
  resolveWorldSelection,
59
59
  fetchWorldDetail,
60
+ searchAgents,
60
61
  joinWorld,
61
62
  fetchWorldCandidateFeed,
62
63
  buildCandidateDeliverySummary,
@@ -52,6 +52,7 @@ import {
52
52
  fetchWorldCandidateFeed,
53
53
  fetchWorldDetail,
54
54
  joinWorld,
55
+ searchAgents,
55
56
  searchWorldMembers,
56
57
  searchWorlds,
57
58
  resolveWorldSelection,
@@ -2930,6 +2931,18 @@ async function generateRuntimeProfileCard(context = {}) {
2930
2931
  }),
2931
2932
  },
2932
2933
  social: {
2934
+ searchAgents: async (context = {}) => {
2935
+ const resolvedContext = await resolveBoundRuntimeContext(context);
2936
+ return searchAgents({
2937
+ cfg: resolvedContext.cfg || {},
2938
+ accountId: resolvedContext.accountId || null,
2939
+ runtimeConfig: resolvedContext.runtimeConfig || null,
2940
+ query: context.query ?? context.queryText ?? null,
2941
+ limit: context.limit ?? null,
2942
+ fetchImpl,
2943
+ logger,
2944
+ });
2945
+ },
2933
2946
  requestChat: async (context = {}) => {
2934
2947
  const resolvedContext = await resolveBoundRuntimeContext(context);
2935
2948
  const requestContext = resolvedContext.requesterSessionKey
@@ -3216,6 +3229,18 @@ async function generateRuntimeProfileCard(context = {}) {
3216
3229
  updateChatRequestApprovalPolicy: updateRuntimeChatRequestApprovalPolicy,
3217
3230
  generateShareCard: generateRuntimeProfileCard,
3218
3231
  },
3232
+ searchAgents: async (context = {}) => {
3233
+ const resolvedContext = await resolveBoundRuntimeContext(context);
3234
+ return searchAgents({
3235
+ cfg: resolvedContext.cfg || {},
3236
+ accountId: resolvedContext.accountId || null,
3237
+ runtimeConfig: resolvedContext.runtimeConfig || null,
3238
+ query: context.query ?? context.queryText ?? null,
3239
+ limit: context.limit ?? null,
3240
+ fetchImpl,
3241
+ logger,
3242
+ });
3243
+ },
3219
3244
  fetchWorldDirectory: async (context = {}) => {
3220
3245
  const resolvedContext = await resolveBoundRuntimeContext(context);
3221
3246
  return fetchPostSetupWorldDirectory({
@@ -5,6 +5,7 @@ import {
5
5
  projectToolCreateWorldResponse,
6
6
  projectToolFeedbackSubmissionResponse,
7
7
  projectToolJoinWorldResponse,
8
+ projectToolSocialAgentSearchResponse,
8
9
  projectToolWorldDetail,
9
10
  projectToolWorldList,
10
11
  projectToolWorldMemberSearchResponse,
@@ -1405,6 +1406,65 @@ function buildRegisteredTools(api, plugin) {
1405
1406
  return buildToolResult(projectToolFeedbackSubmissionResponse(payload));
1406
1407
  },
1407
1408
  },
1409
+ {
1410
+ name: 'claworld_search_agents',
1411
+ label: 'Claworld Search Agents',
1412
+ description: 'World-external public profile discovery tool. Search discoverable Claworld accounts by public display name, public code, or account-level profile keywords before joining a world.',
1413
+ metadata: buildToolMetadata({
1414
+ category: 'social_discovery',
1415
+ usageNotes: [
1416
+ 'Use this when the user wants to find a person outside world membership search.',
1417
+ 'This searches discoverable account-level public identity and profile text, not joined-world membership context.',
1418
+ 'Expected behavior: returns public profile summaries, active world previews, and direct request_chat follow-up payloads.',
1419
+ 'If the user already knows the exact target displayName + agentCode, you can skip this tool and call claworld_request_chat directly.',
1420
+ ],
1421
+ examples: [
1422
+ {
1423
+ title: 'Search outside worlds by profile keywords',
1424
+ input: {
1425
+ accountId: 'claworld',
1426
+ query: '上海 慢节奏 介绍',
1427
+ limit: 5,
1428
+ },
1429
+ outcome: 'Returns matching public profiles plus request_chat payloads for follow-up.',
1430
+ },
1431
+ ],
1432
+ }),
1433
+ parameters: objectParam({
1434
+ description: 'World-external discoverable agent search payload.',
1435
+ required: ['accountId', 'query'],
1436
+ properties: {
1437
+ accountId: accountIdProperty,
1438
+ query: stringParam({
1439
+ description: 'Required free-form search text. Best for public display name, public code, hobby, location, style, or account-level profile keywords.',
1440
+ minLength: 1,
1441
+ examples: ['上海 慢节奏 介绍', 'ZX82QP', 'running shanghai'],
1442
+ }),
1443
+ limit: integerParam({
1444
+ description: 'Optional maximum number of profiles to return.',
1445
+ minimum: 1,
1446
+ maximum: 25,
1447
+ examples: [5],
1448
+ }),
1449
+ },
1450
+ examples: [
1451
+ {
1452
+ accountId: 'claworld',
1453
+ query: '上海 慢节奏 介绍',
1454
+ limit: 5,
1455
+ },
1456
+ ],
1457
+ }),
1458
+ async execute(_toolCallId, params = {}) {
1459
+ const context = await resolveToolContext(api, plugin, params);
1460
+ const payload = await plugin.runtime.productShell.searchAgents({
1461
+ ...context,
1462
+ query: params.query || null,
1463
+ limit: params.limit ?? null,
1464
+ });
1465
+ return buildToolResult(projectToolSocialAgentSearchResponse(payload, { accountId: context.accountId }));
1466
+ },
1467
+ },
1408
1468
  {
1409
1469
  name: 'claworld_account',
1410
1470
  label: 'Claworld Account',
@@ -517,6 +517,75 @@ export function normalizeWorldMemberSearchResponse(payload = {}, { accountId = n
517
517
  };
518
518
  }
519
519
 
520
+ function normalizeSocialPublicProfile(profile = {}) {
521
+ return {
522
+ identity: normalizeText(profile.identity, null),
523
+ displayName: normalizeText(profile.displayName, null),
524
+ code: normalizeText(profile.code, null)?.toUpperCase() || null,
525
+ profile: normalizeText(profile.profile, null),
526
+ discoverable: typeof profile.discoverable === 'boolean' ? profile.discoverable : null,
527
+ contactable: typeof profile.contactable === 'boolean' ? profile.contactable : null,
528
+ };
529
+ }
530
+
531
+ function normalizeSocialActiveWorldItem(world = {}) {
532
+ return {
533
+ worldId: normalizeText(world.worldId, null),
534
+ displayName: normalizeText(world.displayName, null),
535
+ summary: normalizeText(world.summary, null),
536
+ category: normalizeText(world.category, null),
537
+ };
538
+ }
539
+
540
+ function normalizeSocialRequestChat(requestChat = null, publicProfile = {}) {
541
+ const candidate = requestChat && typeof requestChat === 'object' && !Array.isArray(requestChat)
542
+ ? requestChat
543
+ : {};
544
+ const displayName = normalizeText(candidate.displayName, normalizeText(publicProfile.displayName, null));
545
+ const agentCode = normalizeText(candidate.agentCode, normalizeText(publicProfile.code, null))?.toUpperCase() || null;
546
+ if (!displayName || !agentCode) return null;
547
+ return {
548
+ displayName,
549
+ agentCode,
550
+ };
551
+ }
552
+
553
+ function normalizeSocialAgentSearchItem(item = {}) {
554
+ const publicProfile = normalizeSocialPublicProfile(item.publicProfile || {});
555
+ const activeWorldItems = item.activeWorlds?.items && Array.isArray(item.activeWorlds.items)
556
+ ? item.activeWorlds.items.map((world) => normalizeSocialActiveWorldItem(world))
557
+ : [];
558
+
559
+ return {
560
+ publicProfile,
561
+ activeWorlds: {
562
+ totalCount: normalizeInteger(item.activeWorlds?.totalCount, activeWorldItems.length),
563
+ items: activeWorldItems,
564
+ },
565
+ requestChat: normalizeSocialRequestChat(item.requestChat, publicProfile),
566
+ score: normalizeInteger(item.score, 0),
567
+ matchedFieldIds: normalizeStringList(item.matchedFieldIds),
568
+ reasonSummary: normalizeText(item.reasonSummary, null),
569
+ };
570
+ }
571
+
572
+ export function normalizeSocialAgentSearchResponse(payload = {}, { accountId = null } = {}) {
573
+ const items = Array.isArray(payload.items)
574
+ ? payload.items.map((item) => normalizeSocialAgentSearchItem(item))
575
+ : [];
576
+
577
+ return {
578
+ status: normalizeText(payload.status, items.length > 0 ? 'search_ready' : 'no_matches'),
579
+ source: 'product_shell',
580
+ accountId: normalizeText(accountId, null),
581
+ query: normalizeText(payload.query, null),
582
+ limit: normalizeInteger(payload.limit, items.length > 0 ? items.length : 10),
583
+ totalMatches: normalizeInteger(payload.totalMatches, items.length),
584
+ nextAction: normalizeText(payload.nextAction, items.length > 0 ? 'review_profiles_or_request_chat' : 'refine_agent_search'),
585
+ items,
586
+ };
587
+ }
588
+
520
589
  export function resolveWorldSelection(worldDirectory = {}, selection = null) {
521
590
  return resolveBackendWorldSelection(worldDirectory, selection);
522
591
  }
@@ -681,6 +750,56 @@ export async function searchWorlds({
681
750
  });
682
751
  }
683
752
 
753
+ export async function searchAgents({
754
+ cfg = {},
755
+ accountId = null,
756
+ runtimeConfig = null,
757
+ query = null,
758
+ limit = null,
759
+ fetchImpl,
760
+ logger = console,
761
+ } = {}) {
762
+ if (typeof fetchImpl !== 'function') {
763
+ throw new Error('fetch is unavailable for claworld product-shell social search helper');
764
+ }
765
+
766
+ const normalizedQuery = normalizeText(query, null);
767
+ if (!normalizedQuery) {
768
+ throw new Error('claworld product-shell social search helper requires query');
769
+ }
770
+
771
+ const resolvedRuntimeConfig = runtimeConfig || resolveClaworldRuntimeConfig(cfg, accountId);
772
+ const baseUrl = normalizeRelayHttpBaseUrl(resolvedRuntimeConfig.serverUrl);
773
+ const searchResult = await fetchJson(fetchImpl, `${baseUrl}/v1/social/agents/search`, {
774
+ method: 'POST',
775
+ headers: buildRuntimeAuthHeaders(resolvedRuntimeConfig, {
776
+ accept: 'application/json',
777
+ 'content-type': 'application/json',
778
+ ...(resolvedRuntimeConfig.apiKey ? { 'x-api-key': resolvedRuntimeConfig.apiKey } : {}),
779
+ }),
780
+ body: JSON.stringify({
781
+ query: normalizedQuery,
782
+ limit: limit == null ? null : normalizeInteger(limit, 0),
783
+ }),
784
+ });
785
+
786
+ if (!searchResult.ok) {
787
+ logger.error?.('[claworld:product-shell] social agent search failed', {
788
+ status: searchResult.status,
789
+ query: normalizedQuery,
790
+ accountId: resolvedRuntimeConfig.accountId || accountId || null,
791
+ body: searchResult.body,
792
+ });
793
+ throw createProductShellHttpError('social_agent_search', searchResult, {
794
+ accountId: resolvedRuntimeConfig.accountId || accountId || null,
795
+ });
796
+ }
797
+
798
+ return normalizeSocialAgentSearchResponse(searchResult.body, {
799
+ accountId: resolvedRuntimeConfig.accountId || accountId || null,
800
+ });
801
+ }
802
+
684
803
  export async function joinWorld({
685
804
  cfg = {},
686
805
  accountId = null,
@@ -122,21 +122,21 @@ function projectRequestChatPayload(
122
122
  } = {},
123
123
  ) {
124
124
  if (!requestChat || typeof requestChat !== 'object' || Array.isArray(requestChat)) return null;
125
- const worldId = normalizeText(requestChat.worldId, null);
126
125
  const displayName = normalizeText(requestChat.displayName, null);
127
126
  const agentCode = normalizeText(requestChat.agentCode, null)?.toUpperCase() || null;
128
- if (!worldId || !displayName || !agentCode) return null;
127
+ const worldId = normalizeText(requestChat.worldId, null);
128
+ if (!displayName || !agentCode) return null;
129
129
 
130
130
  const normalizedAccountId = normalizeText(accountId, null);
131
131
 
132
132
  return {
133
- worldId,
133
+ ...(worldId ? { worldId } : {}),
134
134
  displayName,
135
135
  agentCode,
136
136
  requestTool: normalizeText(requestToolName, null),
137
137
  requestPayload: {
138
138
  ...(normalizedAccountId ? { accountId: normalizedAccountId } : {}),
139
- worldId,
139
+ ...(worldId ? { worldId } : {}),
140
140
  displayName,
141
141
  agentCode,
142
142
  },
@@ -675,6 +675,54 @@ export function projectToolWorldMemberSearchResponse(payload = {}, { accountId =
675
675
  };
676
676
  }
677
677
 
678
+ function projectToolSocialActiveWorldSummary(world = {}) {
679
+ return {
680
+ worldId: normalizeText(world.worldId, null),
681
+ displayName: normalizeText(world.displayName, null),
682
+ summary: normalizeText(world.summary, null),
683
+ category: normalizeText(world.category, null),
684
+ };
685
+ }
686
+
687
+ function projectToolSocialPublicProfile(profile = {}) {
688
+ return {
689
+ identity: normalizeText(profile.identity, null),
690
+ displayName: normalizeText(profile.displayName, null),
691
+ code: normalizeText(profile.code, null)?.toUpperCase() || null,
692
+ profile: normalizeText(profile.profile, null),
693
+ discoverable: typeof profile.discoverable === 'boolean' ? profile.discoverable : null,
694
+ contactable: typeof profile.contactable === 'boolean' ? profile.contactable : null,
695
+ };
696
+ }
697
+
698
+ export function projectToolSocialAgentSearchResponse(payload = {}, { accountId = null } = {}) {
699
+ const items = Array.isArray(payload.items)
700
+ ? payload.items.map((item) => ({
701
+ publicProfile: projectToolSocialPublicProfile(item.publicProfile || {}),
702
+ activeWorlds: {
703
+ totalCount: normalizeInteger(item.activeWorlds?.totalCount, Array.isArray(item.activeWorlds?.items) ? item.activeWorlds.items.length : 0),
704
+ items: Array.isArray(item.activeWorlds?.items)
705
+ ? item.activeWorlds.items.map((world) => projectToolSocialActiveWorldSummary(world))
706
+ : [],
707
+ },
708
+ matchedFieldIds: normalizeStringList(item.matchedFieldIds),
709
+ reasonSummary: normalizeText(item.reasonSummary, null),
710
+ score: normalizeInteger(item.score, 0),
711
+ requestChat: projectRequestChatPayload(item.requestChat, { accountId }),
712
+ }))
713
+ : [];
714
+
715
+ return {
716
+ accountId: normalizeText(accountId, null),
717
+ status: normalizeText(payload.status, items.length > 0 ? 'search_ready' : 'no_matches'),
718
+ query: normalizeText(payload.query, null),
719
+ limit: normalizeInteger(payload.limit, items.length > 0 ? items.length : 10),
720
+ totalMatches: normalizeInteger(payload.totalMatches, items.length),
721
+ nextAction: normalizeText(payload.nextAction, items.length > 0 ? 'review_profiles_or_request_chat' : 'refine_agent_search'),
722
+ agents: items,
723
+ };
724
+ }
725
+
678
726
  export function projectToolFeedbackSubmissionResponse(result = {}) {
679
727
  const feedback = result.feedback && typeof result.feedback === 'object' ? result.feedback : {};
680
728
  const reporter = feedback.reporter && typeof feedback.reporter === 'object' ? feedback.reporter : {};
@@ -9,6 +9,10 @@ export const CLAWORLD_ACCOUNT_TOOL_NAMES = Object.freeze([
9
9
  'claworld_account',
10
10
  ]);
11
11
 
12
+ export const CLAWORLD_SOCIAL_DISCOVERY_TOOL_NAMES = Object.freeze([
13
+ 'claworld_search_agents',
14
+ ]);
15
+
12
16
  export const CLAWORLD_FEEDBACK_TOOL_NAMES = Object.freeze([
13
17
  'claworld_submit_feedback',
14
18
  ]);
@@ -29,6 +33,7 @@ export const CLAWORLD_WORLD_ADMIN_PUBLIC_TOOL_NAMES = Object.freeze([
29
33
 
30
34
  export const CLAWORLD_REGISTERED_TOOL_NAMES = Object.freeze([
31
35
  ...CLAWORLD_ACCOUNT_TOOL_NAMES,
36
+ ...CLAWORLD_SOCIAL_DISCOVERY_TOOL_NAMES,
32
37
  ...CLAWORLD_WORLD_TOOL_NAMES,
33
38
  ...CLAWORLD_WORLD_ADMIN_PUBLIC_TOOL_NAMES,
34
39
  ...CLAWORLD_CHAT_REQUEST_TOOL_NAMES,
@@ -53,6 +58,7 @@ export const CLAWORLD_READ_ONLY_OPENCLAW_TOOL_NAMES = Object.freeze([
53
58
 
54
59
  export const CLAWORLD_PLUGIN_SMOKE_REQUIRED_TOOL_NAMES = Object.freeze([
55
60
  ...CLAWORLD_ACCOUNT_TOOL_NAMES,
61
+ ...CLAWORLD_SOCIAL_DISCOVERY_TOOL_NAMES,
56
62
  ...CLAWORLD_WORLD_TOOL_NAMES,
57
63
  ...CLAWORLD_WORLD_ADMIN_PUBLIC_TOOL_NAMES,
58
64
  ...CLAWORLD_CHAT_REQUEST_TOOL_NAMES,