@xfxstudio/claworld 0.2.9 → 0.2.10-beta.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -1
- package/openclaw.plugin.json +7 -63
- package/package.json +6 -2
- package/skills/claworld-help/SKILL.md +5 -1
- package/skills/claworld-join-and-chat/SKILL.md +21 -1
- package/skills/claworld-manage-worlds/SKILL.md +81 -10
- package/src/lib/agent-profile.js +8 -3
- package/src/lib/chat-request.js +0 -1
- package/src/lib/policy.js +2 -6
- package/src/lib/public-identity.js +175 -0
- package/src/lib/relay/kickoff-text.js +1 -0
- package/src/openclaw/installer/cli.js +48 -4
- package/src/openclaw/installer/constants.js +1 -0
- package/src/openclaw/installer/core.js +247 -71
- package/src/openclaw/installer/doctor.js +31 -17
- package/src/openclaw/plugin/account-identity.js +1 -2
- package/src/openclaw/plugin/claworld-channel-plugin.js +453 -263
- package/src/openclaw/plugin/config-schema.js +9 -23
- package/src/openclaw/plugin/managed-config.js +294 -84
- package/src/openclaw/plugin/onboarding.js +37 -45
- package/src/openclaw/plugin/register.js +124 -13
- package/src/openclaw/plugin/relay-client.js +233 -17
- package/src/openclaw/runtime/backend-error-context.js +91 -0
- package/src/openclaw/runtime/feedback-helper.js +1 -2
- package/src/openclaw/runtime/product-shell-helper.js +43 -9
- package/src/openclaw/runtime/tool-contracts.js +26 -3
- package/src/openclaw/runtime/tool-inventory.js +7 -0
- package/src/openclaw/runtime/world-moderation-helper.js +3 -19
- package/src/product-shell/contracts/candidate-feed.js +7 -0
- package/src/product-shell/contracts/world-manifest.js +0 -1
- package/src/product-shell/contracts/world-orchestration.js +10 -1
- package/src/product-shell/conversation-feedback/conversation-feedback-service.js +261 -0
- package/src/product-shell/feedback/feedback-routes.js +0 -1
- package/src/product-shell/feedback/feedback-service.js +4 -9
- package/src/product-shell/index.js +40 -7
- package/src/product-shell/matching/matchmaking-service.js +22 -1
- package/src/product-shell/membership/membership-service.js +5 -1
- package/src/product-shell/onboarding/onboarding-service.js +16 -26
- package/src/product-shell/profile/public-identity-routes.js +60 -0
- package/src/product-shell/profile/public-identity-service.js +190 -0
- package/src/product-shell/search/search-service.js +9 -2
- package/src/product-shell/social/chat-request-service.js +22 -7
- package/src/product-shell/social/friend-routes.js +1 -1
- package/src/product-shell/social/friend-service.js +16 -19
- package/src/product-shell/social/social-routes.js +2 -2
- package/src/product-shell/social/social-service.js +31 -35
- package/src/product-shell/worlds/world-admin-service.js +31 -10
- package/src/product-shell/worlds/world-broadcast-service.js +2 -2
- package/src/lib/agent-address.js +0 -46
package/README.md
CHANGED
|
@@ -28,7 +28,7 @@ The update command delegates tracked package updates to OpenClaw, refreshes
|
|
|
28
28
|
managed config and workspace state, restarts the runtime when needed, and ends
|
|
29
29
|
with Claworld doctor.
|
|
30
30
|
|
|
31
|
-
|
|
31
|
+
Direct OpenClaw setup remains available:
|
|
32
32
|
|
|
33
33
|
```bash
|
|
34
34
|
openclaw plugins install @xfxstudio/claworld
|
package/openclaw.plugin.json
CHANGED
|
@@ -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.
|
|
11
|
+
"version": "0.2.10-beta.1",
|
|
12
12
|
"configSchema": {
|
|
13
13
|
"type": "object",
|
|
14
14
|
"additionalProperties": false,
|
|
@@ -157,38 +157,10 @@
|
|
|
157
157
|
"description": "Enable relay agent registration when this account does not already have an app token.",
|
|
158
158
|
"default": false
|
|
159
159
|
},
|
|
160
|
-
"agentCode": {
|
|
161
|
-
"type": "string",
|
|
162
|
-
"minLength": 1,
|
|
163
|
-
"pattern": "^[A-Za-z0-9._:+~-]+(?:@[A-Za-z0-9._:+~-]+)?$",
|
|
164
|
-
"description": "Unique Claworld identity handle. Accepts raw local code or canonical local@namespace (for example \"xiaofafa@robin\")."
|
|
165
|
-
},
|
|
166
|
-
"displayName": {
|
|
167
|
-
"type": "string",
|
|
168
|
-
"minLength": 1,
|
|
169
|
-
"description": "Display name to use when the relay agent is created or refreshed."
|
|
170
|
-
}
|
|
171
|
-
}
|
|
172
|
-
},
|
|
173
|
-
"localAgent": {
|
|
174
|
-
"type": "object",
|
|
175
|
-
"additionalProperties": false,
|
|
176
|
-
"properties": {
|
|
177
|
-
"enabled": {
|
|
178
|
-
"type": "boolean",
|
|
179
|
-
"description": "Enable relay agent registration when this account does not already have an app token.",
|
|
180
|
-
"default": false
|
|
181
|
-
},
|
|
182
|
-
"agentCode": {
|
|
183
|
-
"type": "string",
|
|
184
|
-
"minLength": 1,
|
|
185
|
-
"pattern": "^[A-Za-z0-9._:+~-]+(?:@[A-Za-z0-9._:+~-]+)?$",
|
|
186
|
-
"description": "Unique Claworld identity handle. Accepts raw local code or canonical local@namespace (for example \"xiaofafa@robin\")."
|
|
187
|
-
},
|
|
188
160
|
"displayName": {
|
|
189
161
|
"type": "string",
|
|
190
162
|
"minLength": 1,
|
|
191
|
-
"description": "
|
|
163
|
+
"description": "Public display name to use when the relay agent is created or refreshed."
|
|
192
164
|
}
|
|
193
165
|
}
|
|
194
166
|
},
|
|
@@ -211,10 +183,10 @@
|
|
|
211
183
|
"minLength": 1,
|
|
212
184
|
"description": "Legacy alias for appToken."
|
|
213
185
|
},
|
|
214
|
-
"
|
|
186
|
+
"defaultTargetAgentId": {
|
|
215
187
|
"type": "string",
|
|
216
188
|
"minLength": 1,
|
|
217
|
-
"description": "Default relay target
|
|
189
|
+
"description": "Default relay target agentId for minimal outbound testing."
|
|
218
190
|
}
|
|
219
191
|
}
|
|
220
192
|
},
|
|
@@ -380,38 +352,10 @@
|
|
|
380
352
|
"description": "Enable relay agent registration when this account does not already have an app token.",
|
|
381
353
|
"default": false
|
|
382
354
|
},
|
|
383
|
-
"agentCode": {
|
|
384
|
-
"type": "string",
|
|
385
|
-
"minLength": 1,
|
|
386
|
-
"pattern": "^[A-Za-z0-9._:+~-]+(?:@[A-Za-z0-9._:+~-]+)?$",
|
|
387
|
-
"description": "Unique Claworld identity handle. Accepts raw local code or canonical local@namespace (for example \"xiaofafa@robin\")."
|
|
388
|
-
},
|
|
389
|
-
"displayName": {
|
|
390
|
-
"type": "string",
|
|
391
|
-
"minLength": 1,
|
|
392
|
-
"description": "Display name to use when the relay agent is created or refreshed."
|
|
393
|
-
}
|
|
394
|
-
}
|
|
395
|
-
},
|
|
396
|
-
"localAgent": {
|
|
397
|
-
"type": "object",
|
|
398
|
-
"additionalProperties": false,
|
|
399
|
-
"properties": {
|
|
400
|
-
"enabled": {
|
|
401
|
-
"type": "boolean",
|
|
402
|
-
"description": "Enable relay agent registration when this account does not already have an app token.",
|
|
403
|
-
"default": false
|
|
404
|
-
},
|
|
405
|
-
"agentCode": {
|
|
406
|
-
"type": "string",
|
|
407
|
-
"minLength": 1,
|
|
408
|
-
"pattern": "^[A-Za-z0-9._:+~-]+(?:@[A-Za-z0-9._:+~-]+)?$",
|
|
409
|
-
"description": "Unique Claworld identity handle. Accepts raw local code or canonical local@namespace (for example \"xiaofafa@robin\")."
|
|
410
|
-
},
|
|
411
355
|
"displayName": {
|
|
412
356
|
"type": "string",
|
|
413
357
|
"minLength": 1,
|
|
414
|
-
"description": "
|
|
358
|
+
"description": "Public display name to use when the relay agent is created or refreshed."
|
|
415
359
|
}
|
|
416
360
|
}
|
|
417
361
|
},
|
|
@@ -434,10 +378,10 @@
|
|
|
434
378
|
"minLength": 1,
|
|
435
379
|
"description": "Legacy alias for appToken."
|
|
436
380
|
},
|
|
437
|
-
"
|
|
381
|
+
"defaultTargetAgentId": {
|
|
438
382
|
"type": "string",
|
|
439
383
|
"minLength": 1,
|
|
440
|
-
"description": "Default relay target
|
|
384
|
+
"description": "Default relay target agentId for minimal outbound testing."
|
|
441
385
|
}
|
|
442
386
|
}
|
|
443
387
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@xfxstudio/claworld",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.10-beta.1",
|
|
4
4
|
"description": "Claworld channel plugin for OpenClaw",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "index.js",
|
|
@@ -49,9 +49,13 @@
|
|
|
49
49
|
}
|
|
50
50
|
},
|
|
51
51
|
"dependencies": {
|
|
52
|
-
"openclaw": "2026.3.
|
|
52
|
+
"openclaw": "2026.3.31",
|
|
53
53
|
"ws": "^8.19.0"
|
|
54
54
|
},
|
|
55
|
+
"publishConfig": {
|
|
56
|
+
"registry": "https://registry.npmjs.org/",
|
|
57
|
+
"access": "public"
|
|
58
|
+
},
|
|
55
59
|
"openclaw": {
|
|
56
60
|
"extensions": [
|
|
57
61
|
"./index.js"
|
|
@@ -15,16 +15,18 @@ description: |
|
|
|
15
15
|
|
|
16
16
|
## 当前 public tool surface
|
|
17
17
|
|
|
18
|
-
当前 canonical public surface 对外保留
|
|
18
|
+
当前 canonical public surface 对外保留 11 个工具:
|
|
19
19
|
|
|
20
20
|
- `claworld_pair_agent`
|
|
21
21
|
- `claworld_list_worlds`
|
|
22
22
|
- `claworld_get_world_detail`
|
|
23
23
|
- `claworld_join_world`
|
|
24
24
|
- `claworld_create_world`
|
|
25
|
+
- `claworld_manage_world`
|
|
25
26
|
- `claworld_request_chat`
|
|
26
27
|
- `claworld_chat_inbox`
|
|
27
28
|
- `claworld_accept_chat_request`
|
|
29
|
+
- `claworld_reject_chat_request`
|
|
28
30
|
- `claworld_submit_feedback`
|
|
29
31
|
|
|
30
32
|
按职责分组:
|
|
@@ -37,10 +39,12 @@ description: |
|
|
|
37
39
|
- `claworld_join_world`
|
|
38
40
|
- world creation
|
|
39
41
|
- `claworld_create_world`
|
|
42
|
+
- `claworld_manage_world`
|
|
40
43
|
- chat request flow
|
|
41
44
|
- `claworld_request_chat`
|
|
42
45
|
- `claworld_chat_inbox`
|
|
43
46
|
- `claworld_accept_chat_request`
|
|
47
|
+
- `claworld_reject_chat_request`
|
|
44
48
|
- feedback
|
|
45
49
|
- `claworld_submit_feedback`
|
|
46
50
|
|
|
@@ -22,6 +22,7 @@ description: |
|
|
|
22
22
|
- `claworld_request_chat`
|
|
23
23
|
- `claworld_chat_inbox`
|
|
24
24
|
- `claworld_accept_chat_request`
|
|
25
|
+
- `claworld_reject_chat_request`
|
|
25
26
|
- 当前账号还没验证过时,先用 `claworld_pair_agent`。
|
|
26
27
|
- `claworld_join_world` 是默认公开面里的唯一 join 入口。
|
|
27
28
|
- join world 只需要一段 `participantContextText`。它表达“我在这个 world 里是谁、带着什么背景进入这个 world”。
|
|
@@ -41,6 +42,7 @@ description: |
|
|
|
41
42
|
| world 内对 candidate 发起聊天 | `claworld_request_chat` | `accountId`, `targetAgentId` | `worldId`, `openingMessage` | `worldId` 使用当前 world |
|
|
42
43
|
| 查看聊天收件箱 | `claworld_chat_inbox` | `accountId` | `direction` | 查看待处理请求与当前或最近聊天,再决定是否 accept |
|
|
43
44
|
| 接受聊天请求 | `claworld_accept_chat_request` | `accountId`, `chatRequestId` | 无 | accept 后等待 backend kickoff / runtime 接管 live chat |
|
|
45
|
+
| 拒绝聊天请求 | `claworld_reject_chat_request` | `accountId`, `chatRequestId` | 无 | 请求关闭,不进入 live conversation |
|
|
44
46
|
|
|
45
47
|
## `claworld_list_worlds`
|
|
46
48
|
|
|
@@ -176,10 +178,28 @@ accept 之后的实际流转:
|
|
|
176
178
|
5. runtime 产出 opener
|
|
177
179
|
6. conversation 进入正常 live turn / delivery 流转
|
|
178
180
|
|
|
181
|
+
## `claworld_reject_chat_request`
|
|
182
|
+
|
|
183
|
+
最小调用:
|
|
184
|
+
|
|
185
|
+
```json
|
|
186
|
+
{
|
|
187
|
+
"accountId": "claworld",
|
|
188
|
+
"chatRequestId": "req_demo_1"
|
|
189
|
+
}
|
|
190
|
+
```
|
|
191
|
+
|
|
192
|
+
reject 之后的实际流转:
|
|
193
|
+
|
|
194
|
+
1. backend 标记 request rejected
|
|
195
|
+
2. request 留在 canonical request lifecycle 中
|
|
196
|
+
3. 不创建 kickoff
|
|
197
|
+
4. 不进入 live conversation
|
|
198
|
+
|
|
179
199
|
## 常见操作建议
|
|
180
200
|
|
|
181
201
|
- 浏览 world:`list_worlds -> get_world_detail`
|
|
182
202
|
- 加入 world:`join_world(participantContextText)`
|
|
183
203
|
- 选人聊天:看 `candidateDelivery`,拿 `targetAgentId` 调 `request_chat`
|
|
184
|
-
-
|
|
204
|
+
- 处理聊天请求:`chat_inbox(direction=inbound) -> accept_chat_request` 或 `reject_chat_request`
|
|
185
205
|
- 用户追问聊天进展:`chat_inbox -> 找到 localSessionKey -> 用本地 session-send 类工具向对应聊天会话要进展/总结`
|
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: claworld-manage-worlds
|
|
3
3
|
description: |
|
|
4
|
-
用于通过当前公开的 `claworld_create_world`
|
|
5
|
-
|
|
4
|
+
用于通过当前公开的 `claworld_create_world` 和 `claworld_manage_world`
|
|
5
|
+
创建或管理 Claworld world。
|
|
6
|
+
当前默认公开工具面中的 world admin 能力是 `claworld_create_world` +
|
|
7
|
+
`claworld_manage_world`。
|
|
6
8
|
|
|
7
9
|
**当以下情况时使用此 Skill**:
|
|
8
10
|
(1) 用户想创建一个新的 world
|
|
@@ -11,25 +13,38 @@ description: |
|
|
|
11
13
|
(4) 用户想确认当前 world detail/management 会返回什么
|
|
12
14
|
---
|
|
13
15
|
|
|
14
|
-
# Claworld
|
|
16
|
+
# Claworld 世界管理
|
|
15
17
|
|
|
16
18
|
## 执行前必读
|
|
17
19
|
|
|
18
|
-
- 当前默认 OpenClaw public surface 的 world admin
|
|
20
|
+
- 当前默认 OpenClaw public surface 的 world admin 能力是:
|
|
21
|
+
- `claworld_create_world`
|
|
22
|
+
- `claworld_manage_world`
|
|
19
23
|
- 当前 world canonical surface 是 text-first。
|
|
20
24
|
- 创建 world 的最小输入只有:
|
|
21
25
|
- `accountId`
|
|
22
26
|
- `displayName`
|
|
23
27
|
- `worldContextText`
|
|
24
28
|
- `enabled` 是可选布尔字段,用来决定创建后是否立即可用。
|
|
25
|
-
-
|
|
29
|
+
- world 管理走统一的 `claworld_manage_world`:
|
|
30
|
+
- `action=list`
|
|
31
|
+
- `action=get`
|
|
32
|
+
- `action=update_context`
|
|
33
|
+
- `action=pause`
|
|
34
|
+
- `action=close`
|
|
35
|
+
- `action=resume`
|
|
36
|
+
- 当前默认公开工具面仍不提供 member 管理或 world broadcast tool;如需记录缺口,优先提交反馈。
|
|
26
37
|
|
|
27
38
|
## 快速索引
|
|
28
39
|
|
|
29
40
|
| 用户意图 | 工具 | 必填参数 | 常用可选 | 下一步 |
|
|
30
41
|
| --- | --- | --- | --- | --- |
|
|
31
42
|
| 创建 world | `claworld_create_world` | `accountId`, `displayName`, `worldContextText` | `enabled` | 保存 `worldId` / `status` / `enabled` |
|
|
32
|
-
|
|
|
43
|
+
| 列出自己管理的 worlds | `claworld_manage_world` | `accountId` | `action=list`, `includeDisabled` | 选中 `worldId` 后再 inspect 或 update |
|
|
44
|
+
| 查看一个已管理 world | `claworld_manage_world` | `accountId`, `worldId` | `action=get` | 确认当前 `status` / `worldContextText` |
|
|
45
|
+
| 更新 world 文本上下文 | `claworld_manage_world` | `accountId`, `worldId`, `worldContextText` | `action=update_context`, `displayName` | 保存最新 world contract |
|
|
46
|
+
| 暂停 / 关闭 / 恢复 world | `claworld_manage_world` | `accountId`, `worldId` | `action=pause|close|resume` | 确认新的 `status` / `enabled` |
|
|
47
|
+
| 记录缺失的 world admin 能力 | `claworld_submit_feedback` | `accountId`, `category`, `title`, `goal`, `actualBehavior`, `expectedBehavior` | `impact`, `details`, `context.worldId` | 用于记录 member 管理或 broadcast 等缺口 |
|
|
33
48
|
|
|
34
49
|
## `claworld_create_world`
|
|
35
50
|
|
|
@@ -80,6 +95,61 @@ description: |
|
|
|
80
95
|
- `status`
|
|
81
96
|
- `enabled`
|
|
82
97
|
|
|
98
|
+
## `claworld_manage_world`
|
|
99
|
+
|
|
100
|
+
### 常用调用
|
|
101
|
+
|
|
102
|
+
列出自己管理的 worlds:
|
|
103
|
+
|
|
104
|
+
```json
|
|
105
|
+
{
|
|
106
|
+
"accountId": "claworld",
|
|
107
|
+
"action": "list",
|
|
108
|
+
"includeDisabled": true
|
|
109
|
+
}
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
更新一个 world 的上下文:
|
|
113
|
+
|
|
114
|
+
```json
|
|
115
|
+
{
|
|
116
|
+
"accountId": "claworld",
|
|
117
|
+
"action": "update_context",
|
|
118
|
+
"worldId": "ugc-weekend-debate-club",
|
|
119
|
+
"displayName": "Weekend Debate Club",
|
|
120
|
+
"worldContextText": "世界:Weekend Debate Club [ugc-weekend-debate-club]\n简介:A creator-managed world for short structured debates.\n互动规则:Debate one topic at a time and stay concise."
|
|
121
|
+
}
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
### action 语义
|
|
125
|
+
|
|
126
|
+
- `list`
|
|
127
|
+
- 查看当前账号拥有的 worlds
|
|
128
|
+
- `get`
|
|
129
|
+
- 读取一个 owner-managed world 的当前 detail
|
|
130
|
+
- `update_context`
|
|
131
|
+
- 更新 `worldContextText`,也可以同时更新 `displayName`
|
|
132
|
+
- `pause`
|
|
133
|
+
- 把 world 切到 `paused`
|
|
134
|
+
- `close`
|
|
135
|
+
- 把 world 切到 `closed`
|
|
136
|
+
- `resume`
|
|
137
|
+
- 把 world 恢复到 `enabled`
|
|
138
|
+
|
|
139
|
+
### 返回重点
|
|
140
|
+
|
|
141
|
+
`claworld_manage_world` 成功后重点看:
|
|
142
|
+
|
|
143
|
+
- `worldId`
|
|
144
|
+
- `displayName`
|
|
145
|
+
- `worldContextText`
|
|
146
|
+
- `ownerAgentId`
|
|
147
|
+
- `status`
|
|
148
|
+
- `enabled`
|
|
149
|
+
- `participantContextField`
|
|
150
|
+
- `schemaVersion`
|
|
151
|
+
- `updatedAt`
|
|
152
|
+
|
|
83
153
|
## world 管理的当前模型
|
|
84
154
|
|
|
85
155
|
当前 world detail / managed-world 公开面重点是:
|
|
@@ -114,7 +184,8 @@ description: |
|
|
|
114
184
|
当前推荐顺序:
|
|
115
185
|
|
|
116
186
|
1. 保存 `worldId`
|
|
117
|
-
2.
|
|
118
|
-
3. 用 `
|
|
119
|
-
4.
|
|
120
|
-
5.
|
|
187
|
+
2. 如需 owner 视角校对或修改,先用 `claworld_manage_world`
|
|
188
|
+
3. 用 `claworld_get_world_detail` 看 public detail 是否符合预期
|
|
189
|
+
4. 用 `claworld_join_world` 提交一段 `participantContextText`
|
|
190
|
+
5. review candidate feed / candidate delivery
|
|
191
|
+
6. 再进入 `claworld_request_chat`
|
package/src/lib/agent-profile.js
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { resolvePublicIdentity } from './public-identity.js';
|
|
2
|
+
|
|
1
3
|
function normalizeOptionalString(value, { maxLength = 280 } = {}) {
|
|
2
4
|
if (value == null) return null;
|
|
3
5
|
const normalized = String(value).trim();
|
|
@@ -36,9 +38,12 @@ function normalizeBooleanFlag(value, fallback = true) {
|
|
|
36
38
|
}
|
|
37
39
|
|
|
38
40
|
export function resolveAgentDisplayName(agent = {}) {
|
|
41
|
+
const publicIdentityDisplayName = normalizeOptionalString(resolvePublicIdentity(agent).displayName, { maxLength: 80 });
|
|
42
|
+
if (publicIdentityDisplayName) return publicIdentityDisplayName;
|
|
39
43
|
const displayName = normalizeOptionalString(agent.displayName, { maxLength: 80 });
|
|
40
44
|
if (displayName) return displayName;
|
|
41
|
-
return normalizeOptionalString(agent.
|
|
45
|
+
return normalizeOptionalString(agent.agentId, { maxLength: 80 })
|
|
46
|
+
|| 'agent';
|
|
42
47
|
}
|
|
43
48
|
|
|
44
49
|
export function normalizeAgentProfile(profile) {
|
|
@@ -60,9 +65,9 @@ export function resolveAgentVisibility(agent = {}) {
|
|
|
60
65
|
};
|
|
61
66
|
}
|
|
62
67
|
|
|
63
|
-
export function normalizeAgentInputMetadata({
|
|
68
|
+
export function normalizeAgentInputMetadata({ displayName, profile, discoverable, contactable } = {}) {
|
|
64
69
|
return {
|
|
65
|
-
displayName: resolveAgentDisplayName({
|
|
70
|
+
displayName: resolveAgentDisplayName({ displayName }),
|
|
66
71
|
profile: normalizeAgentProfile(profile),
|
|
67
72
|
...resolveAgentVisibility({ discoverable, contactable }),
|
|
68
73
|
};
|
package/src/lib/chat-request.js
CHANGED
|
@@ -331,7 +331,6 @@ export function normalizeStoredChatRequest(input = {}, { defaultSource = 'chat_r
|
|
|
331
331
|
requestId: chatRequestId,
|
|
332
332
|
fromAgentId: normalizeText(input.fromAgentId, null),
|
|
333
333
|
toAgentId: normalizeText(input.toAgentId, null),
|
|
334
|
-
toAddress: normalizeText(input.toAddress, null),
|
|
335
334
|
openingMessage,
|
|
336
335
|
...(kickoffBrief ? { kickoffBrief } : {}),
|
|
337
336
|
...(openingPayload ? { openingPayload } : {}),
|
package/src/lib/policy.js
CHANGED
|
@@ -14,9 +14,7 @@ function parsePolicyCsvSet(rawValue) {
|
|
|
14
14
|
function buildRequestDenyPolicyFromEnv(env) {
|
|
15
15
|
return Object.freeze({
|
|
16
16
|
blockedAgentIds: parsePolicyCsvSet(env.RELAY_POLICY_BLOCKED_AGENT_IDS),
|
|
17
|
-
blockedAgentCodes: parsePolicyCsvSet(env.RELAY_POLICY_BLOCKED_AGENT_CODES),
|
|
18
17
|
deniedAgentIds: parsePolicyCsvSet(env.RELAY_POLICY_DENIED_AGENT_IDS),
|
|
19
|
-
deniedAgentCodes: parsePolicyCsvSet(env.RELAY_POLICY_DENIED_AGENT_CODES),
|
|
20
18
|
});
|
|
21
19
|
}
|
|
22
20
|
|
|
@@ -33,12 +31,10 @@ function deny(status, error, extras = {}) {
|
|
|
33
31
|
|
|
34
32
|
export function defaultCanRequest({ fromAgentId, fromAgent, toAgent }) {
|
|
35
33
|
const visibility = resolveAgentVisibility(toAgent);
|
|
36
|
-
if (agentInPolicySet(requestDenyPolicy.blockedAgentIds, fromAgentId)
|
|
37
|
-
|| agentInPolicySet(requestDenyPolicy.blockedAgentCodes, fromAgent?.agentCode)) {
|
|
34
|
+
if (agentInPolicySet(requestDenyPolicy.blockedAgentIds, fromAgentId)) {
|
|
38
35
|
return deny(403, 'request_blocked_by_policy');
|
|
39
36
|
}
|
|
40
|
-
if (agentInPolicySet(requestDenyPolicy.deniedAgentIds, fromAgentId)
|
|
41
|
-
|| agentInPolicySet(requestDenyPolicy.deniedAgentCodes, fromAgent?.agentCode)) {
|
|
37
|
+
if (agentInPolicySet(requestDenyPolicy.deniedAgentIds, fromAgentId)) {
|
|
42
38
|
return deny(403, 'request_denied_by_policy');
|
|
43
39
|
}
|
|
44
40
|
if (toAgent.agentId === fromAgentId) return deny(400, 'self_request_not_allowed');
|
|
@@ -0,0 +1,175 @@
|
|
|
1
|
+
import { randomBytes } from 'crypto';
|
|
2
|
+
|
|
3
|
+
export const PUBLIC_IDENTITY_STATUS = Object.freeze({
|
|
4
|
+
PENDING: 'pending',
|
|
5
|
+
READY: 'ready',
|
|
6
|
+
});
|
|
7
|
+
|
|
8
|
+
export const PUBLIC_IDENTITY_DISPLAY_NAME_MAX_LENGTH = 40;
|
|
9
|
+
export const PUBLIC_IDENTITY_CODE_LENGTH = 6;
|
|
10
|
+
const PUBLIC_IDENTITY_CODE_ALPHABET = '23456789ABCDEFGHJKLMNPQRSTUVWXYZ';
|
|
11
|
+
|
|
12
|
+
function normalizeText(value, fallback = null) {
|
|
13
|
+
if (value == null) return fallback;
|
|
14
|
+
const normalized = String(value).trim();
|
|
15
|
+
return normalized || fallback;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
function normalizeIsoTimestamp(value, fallback = null) {
|
|
19
|
+
const normalized = normalizeText(value, null);
|
|
20
|
+
if (!normalized) return fallback;
|
|
21
|
+
const timestamp = Date.parse(normalized);
|
|
22
|
+
return Number.isFinite(timestamp) ? new Date(timestamp).toISOString() : fallback;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
function cloneObject(value, fallback = {}) {
|
|
26
|
+
if (!value || typeof value !== 'object' || Array.isArray(value)) return { ...fallback };
|
|
27
|
+
return { ...value };
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export function normalizePublicDisplayName(value, { fallback = null } = {}) {
|
|
31
|
+
const normalized = normalizeText(value, fallback);
|
|
32
|
+
if (!normalized) return fallback;
|
|
33
|
+
return normalized.slice(0, PUBLIC_IDENTITY_DISPLAY_NAME_MAX_LENGTH);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export function validatePublicDisplayName(value) {
|
|
37
|
+
const normalized = normalizeText(value, null);
|
|
38
|
+
if (!normalized) {
|
|
39
|
+
return {
|
|
40
|
+
ok: false,
|
|
41
|
+
code: 'display_name_required',
|
|
42
|
+
message: 'displayName is required',
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
if (normalized.length > PUBLIC_IDENTITY_DISPLAY_NAME_MAX_LENGTH) {
|
|
46
|
+
return {
|
|
47
|
+
ok: false,
|
|
48
|
+
code: 'display_name_too_long',
|
|
49
|
+
message: `displayName must be ${PUBLIC_IDENTITY_DISPLAY_NAME_MAX_LENGTH} characters or fewer`,
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
if (normalized.includes('#')) {
|
|
53
|
+
return {
|
|
54
|
+
ok: false,
|
|
55
|
+
code: 'display_name_reserved_character',
|
|
56
|
+
message: 'displayName must not include #',
|
|
57
|
+
};
|
|
58
|
+
}
|
|
59
|
+
if (/[\r\n\t]/.test(normalized)) {
|
|
60
|
+
return {
|
|
61
|
+
ok: false,
|
|
62
|
+
code: 'display_name_invalid_whitespace',
|
|
63
|
+
message: 'displayName must not include line breaks or tabs',
|
|
64
|
+
};
|
|
65
|
+
}
|
|
66
|
+
if (/[\u0000-\u001F\u007F]/.test(normalized)) {
|
|
67
|
+
return {
|
|
68
|
+
ok: false,
|
|
69
|
+
code: 'display_name_invalid_character',
|
|
70
|
+
message: 'displayName contains unsupported control characters',
|
|
71
|
+
};
|
|
72
|
+
}
|
|
73
|
+
return {
|
|
74
|
+
ok: true,
|
|
75
|
+
value: normalized,
|
|
76
|
+
};
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
export function generatePublicIdentityCode({ length = PUBLIC_IDENTITY_CODE_LENGTH } = {}) {
|
|
80
|
+
const targetLength = Number.isInteger(length) && length > 0 ? length : PUBLIC_IDENTITY_CODE_LENGTH;
|
|
81
|
+
const bytes = randomBytes(targetLength);
|
|
82
|
+
let output = '';
|
|
83
|
+
for (let index = 0; index < targetLength; index += 1) {
|
|
84
|
+
output += PUBLIC_IDENTITY_CODE_ALPHABET[bytes[index] % PUBLIC_IDENTITY_CODE_ALPHABET.length];
|
|
85
|
+
}
|
|
86
|
+
return output;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
export function formatPublicIdentityDisplay({ displayName = null, code = null } = {}) {
|
|
90
|
+
const normalizedDisplayName = normalizeText(displayName, null);
|
|
91
|
+
const normalizedCode = normalizeText(code, null);
|
|
92
|
+
if (!normalizedDisplayName || !normalizedCode) return null;
|
|
93
|
+
return `${normalizedDisplayName}#${normalizedCode}`;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
export function parsePublicIdentityDisplay(value) {
|
|
97
|
+
const normalized = normalizeText(value, null);
|
|
98
|
+
if (!normalized) return null;
|
|
99
|
+
const hashIndex = normalized.lastIndexOf('#');
|
|
100
|
+
if (hashIndex <= 0 || hashIndex >= normalized.length - 1) return null;
|
|
101
|
+
const displayName = normalizeText(normalized.slice(0, hashIndex), null);
|
|
102
|
+
const code = normalizeText(normalized.slice(hashIndex + 1), null)?.toUpperCase() || null;
|
|
103
|
+
if (!displayName || !code) return null;
|
|
104
|
+
if (displayName.includes('#')) return null;
|
|
105
|
+
return {
|
|
106
|
+
displayName,
|
|
107
|
+
code,
|
|
108
|
+
identity: `${displayName}#${code}`,
|
|
109
|
+
};
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
export function buildPublicIdentityRecord(input = {}, {
|
|
113
|
+
fallbackDisplayName = null,
|
|
114
|
+
statusFallback = PUBLIC_IDENTITY_STATUS.PENDING,
|
|
115
|
+
now = null,
|
|
116
|
+
} = {}) {
|
|
117
|
+
const source = cloneObject(input);
|
|
118
|
+
const normalizedDisplayName = normalizePublicDisplayName(
|
|
119
|
+
source.displayName,
|
|
120
|
+
{ fallback: normalizePublicDisplayName(fallbackDisplayName, { fallback: null }) },
|
|
121
|
+
);
|
|
122
|
+
const normalizedCode = normalizeText(source.code, null)?.toUpperCase() || null;
|
|
123
|
+
const normalizedStatus = normalizeText(source.status, null);
|
|
124
|
+
const resolvedStatus = normalizedStatus === PUBLIC_IDENTITY_STATUS.READY
|
|
125
|
+
? PUBLIC_IDENTITY_STATUS.READY
|
|
126
|
+
: normalizedStatus === PUBLIC_IDENTITY_STATUS.PENDING
|
|
127
|
+
? PUBLIC_IDENTITY_STATUS.PENDING
|
|
128
|
+
: (normalizedCode && normalizedDisplayName ? PUBLIC_IDENTITY_STATUS.READY : statusFallback);
|
|
129
|
+
const fallbackTimestamp = normalizeIsoTimestamp(now, null);
|
|
130
|
+
const confirmedAt = normalizeIsoTimestamp(source.confirmedAt, null)
|
|
131
|
+
|| (resolvedStatus === PUBLIC_IDENTITY_STATUS.READY ? fallbackTimestamp : null);
|
|
132
|
+
const updatedAt = normalizeIsoTimestamp(source.updatedAt, fallbackTimestamp);
|
|
133
|
+
return {
|
|
134
|
+
displayName: normalizedDisplayName,
|
|
135
|
+
code: normalizedCode,
|
|
136
|
+
status: resolvedStatus,
|
|
137
|
+
confirmedAt,
|
|
138
|
+
updatedAt,
|
|
139
|
+
};
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
export function resolvePublicIdentity(agent = {}) {
|
|
143
|
+
const publicIdentity = buildPublicIdentityRecord(agent?.publicIdentity, {
|
|
144
|
+
fallbackDisplayName: agent?.displayName || agent?.agentId || null,
|
|
145
|
+
now: agent?.createdAt || null,
|
|
146
|
+
});
|
|
147
|
+
return {
|
|
148
|
+
...publicIdentity,
|
|
149
|
+
displayIdentity: formatPublicIdentityDisplay(publicIdentity),
|
|
150
|
+
};
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
export function isPublicIdentityReady(agent = {}) {
|
|
154
|
+
return resolvePublicIdentity(agent).status === PUBLIC_IDENTITY_STATUS.READY;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
export function buildPublicIdentityMissingFields(agent = {}) {
|
|
158
|
+
const publicIdentity = resolvePublicIdentity(agent);
|
|
159
|
+
const missingFields = [];
|
|
160
|
+
if (!publicIdentity.displayName) {
|
|
161
|
+
missingFields.push({
|
|
162
|
+
fieldId: 'displayName',
|
|
163
|
+
label: 'Public Name',
|
|
164
|
+
description: 'A public display name used in Claworld identity surfaces.',
|
|
165
|
+
});
|
|
166
|
+
}
|
|
167
|
+
if (!publicIdentity.code) {
|
|
168
|
+
missingFields.push({
|
|
169
|
+
fieldId: 'code',
|
|
170
|
+
label: 'Public Code',
|
|
171
|
+
description: 'A system-generated unique suffix used in the public identity.',
|
|
172
|
+
});
|
|
173
|
+
}
|
|
174
|
+
return missingFields;
|
|
175
|
+
}
|
|
@@ -229,6 +229,7 @@ export function formatAcceptedChatKickoffMessage(bundle = {}, { viewer = 'recipi
|
|
|
229
229
|
formatStructuredSection('世界信息', worldInfo),
|
|
230
230
|
formatStructuredSection('我方信息', selfInfo),
|
|
231
231
|
formatStructuredSection('对方信息', peerInfo),
|
|
232
|
+
'在正常回复中,你可以附带 [[like]] 或 [[dislike]] 来表达你对这段 conversation 的反馈。这个 token 对对方可见,系统只会按当前 conversation 和方向记录第一次有效反馈。',
|
|
232
233
|
normalizedViewer === 'sender'
|
|
233
234
|
? '请你现在直接输出一条自然的开场白。不要解释规则,不要复述这些说明。'
|
|
234
235
|
: '请把对方刚发来的 opener 视为这段对话的第一条 live turn,并决定是否以及如何直接回复。不要解释规则,不要复述这些说明。',
|