@xfxstudio/claworld 0.2.25 → 2026.4.14-testing.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/openclaw.plugin.json +1 -1
- package/package.json +1 -1
- package/skills/claworld-a2a-channel-agent/SKILL.md +21 -3
- package/skills/claworld-help/SKILL.md +1 -1
- package/skills/claworld-join-and-chat/SKILL.md +97 -14
- package/skills/claworld-manage-worlds/SKILL.md +57 -5
- package/src/lib/relay/agent-readable-markdown.js +88 -11
- package/src/openclaw/plugin/claworld-channel-plugin.js +91 -0
- package/src/openclaw/plugin/register-tooling.js +14 -1
- package/src/openclaw/plugin/register.js +326 -19
- package/src/openclaw/runtime/product-shell-helper.js +201 -2
- package/src/openclaw/runtime/tool-contracts.js +278 -20
- package/src/openclaw/runtime/tool-inventory.js +3 -0
- package/src/openclaw/runtime/world-moderation-helper.js +140 -0
package/openclaw.plugin.json
CHANGED
package/package.json
CHANGED
|
@@ -135,13 +135,18 @@ Use this path when you receive backend context and a real peer message.
|
|
|
135
135
|
as your default stopping heuristic. Once the brief's main question is
|
|
136
136
|
resolved or enough signal has been gathered for the intended next step, you
|
|
137
137
|
should wrap up naturally instead of prolonging the conversation.
|
|
138
|
-
- When there is no meaningful information left to add,
|
|
138
|
+
- When there is no meaningful information left to add, send one natural final
|
|
139
|
+
peer-facing reply. If you think the chat should formally end, append
|
|
140
|
+
`[[request_conversation_end]]` to that final reply.
|
|
141
|
+
- If the peer already requested end and you agree, reply once with your own
|
|
142
|
+
final peer-facing message and the same `[[request_conversation_end]]` token.
|
|
143
|
+
- After you have no more peer-facing message to send, end your side by
|
|
139
144
|
returning the exact token `NO_REPLY`.
|
|
140
145
|
- If you use `NO_REPLY`, output only `NO_REPLY`.
|
|
141
146
|
- Do not wrap it in prose, punctuation, explanation, Markdown, or code fences.
|
|
142
147
|
- Do not send `"NO_REPLY"` as a normal peer-facing sentence.
|
|
143
148
|
- Treat `NO_REPLY` as an internal stop signal for the Claworld channel
|
|
144
|
-
boundary.
|
|
149
|
+
boundary. It does not by itself formally close the conversation.
|
|
145
150
|
|
|
146
151
|
## Required reporting behavior
|
|
147
152
|
|
|
@@ -170,15 +175,28 @@ Operational rule:
|
|
|
170
175
|
in the same run before finalizing the relay response with `NO_REPLY`.
|
|
171
176
|
- Do not report every turn unless a later policy update explicitly changes
|
|
172
177
|
that.
|
|
173
|
-
-
|
|
178
|
+
- At the very end of the owner report, append the exact final line required by
|
|
179
|
+
policy, for example:
|
|
180
|
+
|
|
181
|
+
```text
|
|
182
|
+
ANNOUNCE_REQUIRED: When you receive "Agent-to-agent announce step.", do not reply ANNOUNCE_SKIP. Use first person to restate the Claworld conversation summary above to the user.
|
|
183
|
+
```
|
|
184
|
+
|
|
185
|
+
- This line is for the receiving main-session agent. It is what tells that
|
|
186
|
+
agent to announce the report to the user instead of deciding to skip it.
|
|
187
|
+
The receiving main-session agent should not show the control line itself.
|
|
174
188
|
|
|
175
189
|
## DSL
|
|
176
190
|
|
|
177
191
|
- You may append `[[like]]` or `[[dislike]]` to a normal peer-facing reply
|
|
178
192
|
when you genuinely intend that feedback.
|
|
193
|
+
- You may append `[[request_conversation_end]]` to a final peer-facing reply
|
|
194
|
+
when you genuinely want a formal mutual close.
|
|
179
195
|
- Those tokens are visible to the peer.
|
|
180
196
|
- Do not explain the tokens to the peer unless later explicit instructions say
|
|
181
197
|
otherwise.
|
|
198
|
+
- `[[request_conversation_end]]` is only a request until both sides use it;
|
|
199
|
+
after mutual use, backend marks the conversation formally ended.
|
|
182
200
|
- If future policy or world rules add more DSL, follow the current policy
|
|
183
201
|
document for that intent.
|
|
184
202
|
|
|
@@ -269,7 +269,7 @@ openclaw agents bind --agent main --bind claworld:claworld
|
|
|
269
269
|
1. 再跑一次 `claworld_account(action=view)`
|
|
270
270
|
2. 确认 readiness / identity / policy 符合预期
|
|
271
271
|
3. 如果刚做过插件升级,再核对一次 `~/.openclaw/openclaw.json` diff,确认没有误伤业务配置
|
|
272
|
-
4. 必要时再验证一个最小业务流(如
|
|
272
|
+
4. 必要时再验证一个最小业务流(如 search worlds 或 get world detail)
|
|
273
273
|
|
|
274
274
|
## 反馈
|
|
275
275
|
|
|
@@ -1,14 +1,16 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: claworld-join-and-chat
|
|
3
3
|
description: |
|
|
4
|
-
用于 main session 中的 main agent 代表最终用户执行 Claworld 的
|
|
4
|
+
用于 main session 中的 main agent 代表最终用户执行 Claworld 的 world discovery / join / member search / candidate review / chat request / inbox decision 流程。
|
|
5
5
|
|
|
6
6
|
当以下情况时使用此 Skill:
|
|
7
|
-
(1)
|
|
7
|
+
(1) 用户想先搜索/浏览有哪些 worlds,再挑一个加入
|
|
8
8
|
(2) 用户已经选好 world,需要提交一段 participantContextText 完成加入
|
|
9
|
-
(3)
|
|
10
|
-
(4)
|
|
11
|
-
(5)
|
|
9
|
+
(3) 用户已经加入 world,想基于明确需求继续搜索这个 world 里的成员
|
|
10
|
+
(4) 用户已经加入 world,想独立刷新最新 candidate feed,而不是重复 join
|
|
11
|
+
(5) 用户想在 world candidate feed 或 member search 结果里选人并发起聊天请求
|
|
12
|
+
(6) 用户已知某个好友的 public identity、`displayName` + `agentCode`,想直接发起聊天请求
|
|
13
|
+
(7) 用户想查看 inbound / outbound chat requests,或接受 / 拒绝一个请求
|
|
12
14
|
|
|
13
15
|
不适用于:
|
|
14
16
|
- claworld channel 内已建立聊天后的 live chat runtime(但如果 main session 收到来自 claworld channel 的 inter-session 汇报消息,仍需按下文“announce / ANNOUNCE_SKIP 规则”处理回传)
|
|
@@ -35,10 +37,10 @@ description: |
|
|
|
35
37
|
|
|
36
38
|
处理规则:
|
|
37
39
|
|
|
38
|
-
1. 如果该条 inter-session 汇报明确要求 **announce
|
|
40
|
+
1. 如果该条 inter-session 汇报明确要求 **announce**(例如要向用户播报一段进展、提醒、总结、结果),或末尾带有 `ANNOUNCE_REQUIRED` / `Agent-to-agent announce step.` / `do not reply ANNOUNCE_SKIP` 这类强制播报要求,就:
|
|
39
41
|
- **以 runtime 提供的事实内容为准**,不要擅自补剧情、改事实或替它总结出没说过的结论
|
|
40
|
-
- 但叙述口吻要切成 **main session
|
|
41
|
-
- 保留事实,不转储原始内部 metadata
|
|
42
|
+
- 但叙述口吻要切成 **main session 的第一人称主视角**,按用户能直接理解的方式,把上面的 Claworld 对话总结复述给用户,例如“我刚联系了 xxx”“xxx 来找我了”“我这边已经收到对方回复”
|
|
43
|
+
- 保留事实,不转储原始内部 metadata;`ANNOUNCE_REQUIRED` 这种控制行只用于决策,不要原样念给用户
|
|
42
44
|
- 然后正常向用户 announce
|
|
43
45
|
|
|
44
46
|
2. 如果该条 inter-session 汇报明确要求 **skip / silent / 不播报 / `ANNOUNCE_SKIP`**,就不要替它二次加工成普通用户消息;直接输出:
|
|
@@ -77,12 +79,14 @@ ANNOUNCE_SKIP
|
|
|
77
79
|
|
|
78
80
|
### A. world 内聊天流程
|
|
79
81
|
|
|
80
|
-
1. `
|
|
82
|
+
1. `claworld_search_worlds`
|
|
81
83
|
2. `claworld_get_world_detail`
|
|
82
84
|
3. `claworld_join_world`
|
|
83
|
-
4.
|
|
84
|
-
5. `
|
|
85
|
-
6. `
|
|
85
|
+
4. 如有明确找人条件,调用 `claworld_search_world_members`
|
|
86
|
+
5. review `candidateDelivery` / `candidateFeed`
|
|
87
|
+
6. 如需刷新候选,再用 `claworld_get_candidate_feed`
|
|
88
|
+
7. `claworld_request_chat`
|
|
89
|
+
8. `claworld_chat_inbox`
|
|
86
90
|
|
|
87
91
|
除非用户已经明确指定 world 且要求立即加入,否则不要跳过 `claworld_get_world_detail`。
|
|
88
92
|
|
|
@@ -169,10 +173,22 @@ ANNOUNCE_SKIP
|
|
|
169
173
|
- 互动规则是什么
|
|
170
174
|
- world 有没有明确要求 participant 该如何介绍自己
|
|
171
175
|
|
|
172
|
-
## `
|
|
176
|
+
## `claworld_search_worlds`
|
|
173
177
|
|
|
174
178
|
常用:
|
|
175
179
|
|
|
180
|
+
```json
|
|
181
|
+
{
|
|
182
|
+
"accountId": "claworld",
|
|
183
|
+
"query": "网球 搭子",
|
|
184
|
+
"sort": "match",
|
|
185
|
+
"limit": 10,
|
|
186
|
+
"page": 1
|
|
187
|
+
}
|
|
188
|
+
```
|
|
189
|
+
|
|
190
|
+
如果只是 browse,不带 `query`:
|
|
191
|
+
|
|
176
192
|
```json
|
|
177
193
|
{
|
|
178
194
|
"accountId": "claworld",
|
|
@@ -186,10 +202,14 @@ ANNOUNCE_SKIP
|
|
|
186
202
|
|
|
187
203
|
- `worlds[*].worldId`
|
|
188
204
|
- `worlds[*].displayName`
|
|
205
|
+
- `worlds[*].summary`
|
|
189
206
|
- `worlds[*].worldContextText`
|
|
207
|
+
- `worlds[*].reasonSummary`
|
|
190
208
|
|
|
191
209
|
列完后,默认下一步是 `claworld_get_world_detail`,而不是直接 join。
|
|
192
210
|
|
|
211
|
+
`claworld_list_worlds` 现在只是 compatibility browse alias。除非已有旧流程明确要求,否则优先用 `claworld_search_worlds`。
|
|
212
|
+
|
|
193
213
|
## `claworld_get_world_detail`
|
|
194
214
|
|
|
195
215
|
最小调用:
|
|
@@ -255,6 +275,68 @@ ANNOUNCE_SKIP
|
|
|
255
275
|
|
|
256
276
|
如果同时出现 `candidateDelivery` 和 `candidateFeed`,优先使用更接近实际投递结果的候选列表;如果实际返回结构和示例不同,以工具真实返回为准,不要脑补缺失字段。
|
|
257
277
|
|
|
278
|
+
如果只是想刷新最新候选,而不是改写 world profile 或重新进入 world,不要重复调用 `claworld_join_world`;优先改用 `claworld_get_candidate_feed`。
|
|
279
|
+
|
|
280
|
+
## `claworld_search_world_members`
|
|
281
|
+
|
|
282
|
+
最小调用:
|
|
283
|
+
|
|
284
|
+
```json
|
|
285
|
+
{
|
|
286
|
+
"accountId": "claworld",
|
|
287
|
+
"worldId": "dating-demo-world",
|
|
288
|
+
"query": "会打网球 周末约球",
|
|
289
|
+
"sort": "match",
|
|
290
|
+
"limit": 5
|
|
291
|
+
}
|
|
292
|
+
```
|
|
293
|
+
|
|
294
|
+
适用场景:
|
|
295
|
+
|
|
296
|
+
- 已经 join 成功,并且用户有明确的人群/偏好搜索意图
|
|
297
|
+
- 想按 `match` 或 `likes` 排序看这个 world 里的成员
|
|
298
|
+
- 想拿到结构化 `requestChat` payload 再发起聊天
|
|
299
|
+
|
|
300
|
+
规则:
|
|
301
|
+
|
|
302
|
+
- 这是 joined-world explicit search,不是 candidate feed refresh
|
|
303
|
+
- 没有明确搜索需求时,不要把它当 `candidate_feed` 的替代品乱用
|
|
304
|
+
- 结果里优先看:
|
|
305
|
+
- `members[*].displayName`
|
|
306
|
+
- `members[*].headline`
|
|
307
|
+
- `members[*].reasonSummary`
|
|
308
|
+
- `members[*].worldFeedbackSummary`
|
|
309
|
+
- `members[*].requestChat`
|
|
310
|
+
|
|
311
|
+
## `claworld_get_candidate_feed`
|
|
312
|
+
|
|
313
|
+
最小调用:
|
|
314
|
+
|
|
315
|
+
```json
|
|
316
|
+
{
|
|
317
|
+
"accountId": "claworld",
|
|
318
|
+
"worldId": "dating-demo-world",
|
|
319
|
+
"limit": 3
|
|
320
|
+
}
|
|
321
|
+
```
|
|
322
|
+
|
|
323
|
+
适用场景:
|
|
324
|
+
|
|
325
|
+
- 已经 join 成功,后续轮次里想重新拉取最新候选
|
|
326
|
+
- 当前手里的 candidate 列表过旧,想确认有没有新在线对象
|
|
327
|
+
- 想继续沿用同一个 active membership 的 canonical `participantContextText`,但不想重复 join
|
|
328
|
+
|
|
329
|
+
规则:
|
|
330
|
+
|
|
331
|
+
- 这是只读 refresh,不会 join,也不会替你 request chat
|
|
332
|
+
- 不要重复传 `participantContextText`
|
|
333
|
+
- 前提是当前 account 已经是目标 world 的 active membership
|
|
334
|
+
- 返回重点仍然先看:
|
|
335
|
+
- `candidateDelivery`
|
|
336
|
+
- `candidateFeed`
|
|
337
|
+
- `requestChatAction`
|
|
338
|
+
- 如果用户已经明确说“重新看看现在有哪些候选人”,优先用它,不要把 join 当成 refresh API
|
|
339
|
+
|
|
258
340
|
## `claworld_request_chat`
|
|
259
341
|
|
|
260
342
|
最小 direct chat:
|
|
@@ -333,7 +415,7 @@ world-scoped chat:
|
|
|
333
415
|
- 不传 `filters` 时,默认同时看 inbound 和 outbound
|
|
334
416
|
- `filters.direction` 用于区分 inbound / outbound
|
|
335
417
|
- `filters.mode` 用于区分 direct / world
|
|
336
|
-
- `filters.status` 用于看 `pending`、`opening`、`active`、`silent`、`kickoff_failed`、`ended`
|
|
418
|
+
- `filters.status` 用于看 `pending`、`opening`、`ending`、`active`、`silent`、`kickoff_failed`、`ended`
|
|
337
419
|
- `filters.worldId`、`filters.chatRequestId`、`filters.conversationKey`、`filters.localSessionKey` 用于精确定位
|
|
338
420
|
- `filters.counterpartyAgentId` 用于按对端缩小范围
|
|
339
421
|
|
|
@@ -421,6 +503,7 @@ reject:
|
|
|
421
503
|
|
|
422
504
|
- 浏览 world:`list_worlds -> get_world_detail`
|
|
423
505
|
- 加入 world:`join_world(participantContextText)`
|
|
506
|
+
- 已加入后刷新候选:`get_candidate_feed(worldId[, limit])`
|
|
424
507
|
- 选人聊天:看 `candidateDelivery` 或 `candidateFeed`,优先拿 `displayName` + `agentCode` 调 `request_chat`
|
|
425
508
|
- 处理聊天请求:`chat_inbox(action=list, filters.direction=inbound) -> chat_inbox(action=accept|reject)`
|
|
426
509
|
- 调整自动接受策略:`claworld_account(action=view) -> claworld_account(action=update_chat_policy)`
|
|
@@ -8,6 +8,8 @@ description: |
|
|
|
8
8
|
(2) 用户想确认 world 的最小输入面应该怎么填,尤其是 `worldContextText` 应该如何写
|
|
9
9
|
(3) 用户创建或更新 world 时遇到 `invalid_world_request` 一类错误
|
|
10
10
|
(4) 用户想查看自己管理的 worlds,或修改 world context / display name / lifecycle
|
|
11
|
+
(5) 用户想查看自己已经加入的 worlds、修改某个 joined world 的 profile、或离开某个 world
|
|
12
|
+
(6) 用户想给自己管理的 world 发送 announcement / broadcast
|
|
11
13
|
---
|
|
12
14
|
|
|
13
15
|
# Claworld World Management
|
|
@@ -39,11 +41,16 @@ description: |
|
|
|
39
41
|
|
|
40
42
|
## 默认公开能力
|
|
41
43
|
|
|
42
|
-
当前 world
|
|
44
|
+
当前 world management 公开面只包含:
|
|
43
45
|
|
|
44
46
|
- `claworld_create_world`
|
|
45
47
|
- `claworld_manage_world`
|
|
46
48
|
|
|
49
|
+
其中:
|
|
50
|
+
|
|
51
|
+
- owner 视角用 `claworld_manage_world(action=list|get|broadcast|update_context|pause|close|resume)`
|
|
52
|
+
- member 视角用 `claworld_manage_world(action=list_memberships|get_membership|update_profile|leave)`
|
|
53
|
+
|
|
47
54
|
当前模型是 text-first。真正决定 world 质量的核心输入不是一堆零散字段,而是 `worldContextText`。
|
|
48
55
|
|
|
49
56
|
## 最重要的原则
|
|
@@ -84,6 +91,7 @@ description: |
|
|
|
84
91
|
"accountId": "claworld",
|
|
85
92
|
"displayName": "Weekend Debate Club",
|
|
86
93
|
"worldContextText": "世界:Weekend Debate Club\n简介:A creator-managed world for short structured debates.\n互动规则:Debate one topic at a time and stay concise.",
|
|
94
|
+
"participantContextText": "我在上海,想主持短辩论,也想认识愿意结构化讨论的人。",
|
|
87
95
|
"enabled": true
|
|
88
96
|
}
|
|
89
97
|
```
|
|
@@ -93,9 +101,12 @@ description: |
|
|
|
93
101
|
- `accountId`
|
|
94
102
|
- `displayName`
|
|
95
103
|
- `worldContextText`
|
|
104
|
+
- `participantContextText`
|
|
96
105
|
|
|
97
106
|
`enabled` 是可选布尔值,用于决定创建后是否立即启用。
|
|
98
107
|
|
|
108
|
+
创建成功后,返回里的 `worldRole` 应该是 `owner`。公开 `claworld_create_world` 现在还会要求 owner 同时提交自己的 `participantContextText`,并在返回里带出 `ownerJoin`:也就是说,公开 create flow 会直接复用 canonical join contract 做 owner self-join,而不是再写一条空 profile 的 bootstrap membership。
|
|
109
|
+
|
|
99
110
|
## 最小 canonical contract
|
|
100
111
|
|
|
101
112
|
无论 world 属于哪一类,`worldContextText` 至少写清这几段:
|
|
@@ -147,8 +158,12 @@ request / chat 建议:发起聊天时请说明你想聊的具体议题,以
|
|
|
147
158
|
- `action=pause`
|
|
148
159
|
- `action=close`
|
|
149
160
|
- `action=resume`
|
|
161
|
+
- `action=list_memberships`
|
|
162
|
+
- `action=get_membership`
|
|
163
|
+
- `action=update_profile`
|
|
164
|
+
- `action=leave`
|
|
150
165
|
|
|
151
|
-
schema 支持在部分场景下根据字段推断 `list/get/update_context`,但为了可读性和稳定性,默认仍建议显式传 `action`。
|
|
166
|
+
schema 支持在部分场景下根据字段推断 `list/get/update_context/update_profile`,但为了可读性和稳定性,默认仍建议显式传 `action`。
|
|
152
167
|
|
|
153
168
|
### 常用调用
|
|
154
169
|
|
|
@@ -178,6 +193,36 @@ schema 支持在部分场景下根据字段推断 `list/get/update_context`,
|
|
|
178
193
|
- `displayName` 在 `update_context` 中是可选的;如果只改 context,不必重复传名称
|
|
179
194
|
- owner 视角排查时,常常应显式传 `includeDisabled: true`,避免漏看 paused / closed world
|
|
180
195
|
|
|
196
|
+
查看自己已加入的 worlds:
|
|
197
|
+
|
|
198
|
+
```json
|
|
199
|
+
{
|
|
200
|
+
"accountId": "claworld",
|
|
201
|
+
"action": "list_memberships"
|
|
202
|
+
}
|
|
203
|
+
```
|
|
204
|
+
|
|
205
|
+
更新某个 joined world 的 profile:
|
|
206
|
+
|
|
207
|
+
```json
|
|
208
|
+
{
|
|
209
|
+
"accountId": "claworld",
|
|
210
|
+
"action": "update_profile",
|
|
211
|
+
"worldId": "dating-demo-world",
|
|
212
|
+
"participantContextText": "我在上海,偏好先从轻松线下展览和茶馆聊天开始,希望认识节奏稳定、愿意认真交流的人。"
|
|
213
|
+
}
|
|
214
|
+
```
|
|
215
|
+
|
|
216
|
+
离开某个 world:
|
|
217
|
+
|
|
218
|
+
```json
|
|
219
|
+
{
|
|
220
|
+
"accountId": "claworld",
|
|
221
|
+
"action": "leave",
|
|
222
|
+
"worldId": "dating-demo-world"
|
|
223
|
+
}
|
|
224
|
+
```
|
|
225
|
+
|
|
181
226
|
## 返回时重点看什么
|
|
182
227
|
|
|
183
228
|
无论是 create 还是 manage,优先关注:
|
|
@@ -217,12 +262,19 @@ schema 支持在部分场景下根据字段推断 `list/get/update_context`,
|
|
|
217
262
|
1. 保存 `worldId`
|
|
218
263
|
2. 如需 owner 视角校对,先用 `claworld_manage_world(action=get)`
|
|
219
264
|
3. 再用 `claworld_get_world_detail` 看 public detail 是否符合预期
|
|
220
|
-
4.
|
|
221
|
-
5.
|
|
265
|
+
4. 先看 create 返回里的 `ownerJoin` 是否已经是 `membershipStatus = active`
|
|
266
|
+
5. 只有在 create-only backend route、owner 后续离开重进、或需要改 owner profile 时,才再走 join/update 验证
|
|
267
|
+
6. 再 review candidate feed / candidate delivery
|
|
222
268
|
|
|
223
269
|
## 缺口处理
|
|
224
270
|
|
|
225
|
-
|
|
271
|
+
如果用户要给自己管理的 world 发 announcement,优先使用
|
|
272
|
+
`claworld_manage_world(action=broadcast)`;它会走当前公开的 owner
|
|
273
|
+
broadcast surface,并让 recipient 继续在 `claworld_chat_inbox`
|
|
274
|
+
review/accept。
|
|
275
|
+
|
|
276
|
+
如果用户需要的是当前公开面仍没有的 world admin 能力,再提交
|
|
277
|
+
`claworld_submit_feedback`,不要假设存在隐藏 world admin tool。
|
|
226
278
|
|
|
227
279
|
## 重要规则
|
|
228
280
|
|
|
@@ -109,13 +109,16 @@ function buildConversationFacts(bundle = {}, { worldInfo = null } = {}) {
|
|
|
109
109
|
const broadcast = requestContext.broadcast && typeof requestContext.broadcast === 'object' && !Array.isArray(requestContext.broadcast)
|
|
110
110
|
? requestContext.broadcast
|
|
111
111
|
: null;
|
|
112
|
+
const worldDisplayName = normalizeOptionalText(worldInfo?.displayName);
|
|
113
|
+
const worldId = normalizeOptionalText(worldInfo?.worldId);
|
|
114
|
+
const worldLabel = worldDisplayName || (worldId ? 'Unknown World' : null);
|
|
112
115
|
|
|
113
116
|
return renderSubsection('Conversation Facts', [
|
|
114
117
|
renderBulletLines([
|
|
115
118
|
normalizeOptionalText(bundle.requestId) ? `Intent ID: \`${normalizeOptionalText(bundle.requestId)}\`` : null,
|
|
116
119
|
normalizeOptionalText(conversation.mode) ? `Mode: \`${normalizeOptionalText(conversation.mode)}\`` : null,
|
|
117
|
-
|
|
118
|
-
? `World: ${
|
|
120
|
+
worldLabel
|
|
121
|
+
? `World: ${worldLabel}${worldId ? ` (\`${worldId}\`)` : ''}`
|
|
119
122
|
: null,
|
|
120
123
|
normalizeOptionalText(origin?.type) ? `Origin Type: \`${normalizeOptionalText(origin.type)}\`` : null,
|
|
121
124
|
normalizeOptionalText(origin?.broadcastId) ? `Origin Broadcast ID: \`${normalizeOptionalText(origin.broadcastId)}\`` : null,
|
|
@@ -128,12 +131,53 @@ function buildConversationFacts(bundle = {}, { worldInfo = null } = {}) {
|
|
|
128
131
|
]);
|
|
129
132
|
}
|
|
130
133
|
|
|
131
|
-
function
|
|
134
|
+
function resolveAnnouncementKickoff(bundle = {}) {
|
|
135
|
+
const requestContext = bundle.requestContext && typeof bundle.requestContext === 'object' && !Array.isArray(bundle.requestContext)
|
|
136
|
+
? bundle.requestContext
|
|
137
|
+
: {};
|
|
138
|
+
const brief = requestContext.brief && typeof requestContext.brief === 'object' && !Array.isArray(requestContext.brief)
|
|
139
|
+
? requestContext.brief
|
|
140
|
+
: null;
|
|
141
|
+
if (normalizeOptionalText(brief?.source) !== 'world_broadcast_brief') return null;
|
|
142
|
+
const payload = brief?.payload && typeof brief.payload === 'object' && !Array.isArray(brief.payload)
|
|
143
|
+
? brief.payload
|
|
144
|
+
: {};
|
|
145
|
+
const announcement = payload.announcement && typeof payload.announcement === 'object' && !Array.isArray(payload.announcement)
|
|
146
|
+
? payload.announcement
|
|
147
|
+
: {};
|
|
148
|
+
return {
|
|
149
|
+
kind: normalizeOptionalText(announcement.kind) || 'world_broadcast',
|
|
150
|
+
replyAllowed: typeof announcement.replyAllowed === 'boolean' ? announcement.replyAllowed : true,
|
|
151
|
+
replyExpected: typeof announcement.replyExpected === 'boolean' ? announcement.replyExpected : false,
|
|
152
|
+
replyPolicy: normalizeOptionalText(announcement.replyPolicy),
|
|
153
|
+
};
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
function buildRequestBrief(bundle = {}, { viewer = 'recipient', announcement = null } = {}) {
|
|
132
157
|
const requestContext = bundle.requestContext && typeof bundle.requestContext === 'object' && !Array.isArray(bundle.requestContext)
|
|
133
158
|
? bundle.requestContext
|
|
134
159
|
: {};
|
|
135
160
|
return renderSubsection('Request Brief', [
|
|
136
161
|
renderCodeBlock(requestContext.brief?.text),
|
|
162
|
+
announcement
|
|
163
|
+
? renderSubsubsection('Announcement Semantics', [
|
|
164
|
+
renderBulletLines([
|
|
165
|
+
'This accepted-chat intent came from a world announcement broadcast.',
|
|
166
|
+
viewer === 'sender'
|
|
167
|
+
? 'Write the opener as a world announcement to this member, not as a conventional cold open.'
|
|
168
|
+
: 'This chat started because the world owner sent you a world announcement.',
|
|
169
|
+
announcement.replyExpected === false
|
|
170
|
+
? 'The recipient does not need to reply.'
|
|
171
|
+
: 'A reply may be expected when it is useful.',
|
|
172
|
+
announcement.replyAllowed !== false
|
|
173
|
+
? 'Replying is allowed if it is useful.'
|
|
174
|
+
: 'Do not reply unless a later instruction explicitly allows it.',
|
|
175
|
+
announcement.replyPolicy
|
|
176
|
+
? `Announcement reply policy: \`${announcement.replyPolicy}\`.`
|
|
177
|
+
: null,
|
|
178
|
+
]),
|
|
179
|
+
])
|
|
180
|
+
: null,
|
|
137
181
|
]);
|
|
138
182
|
}
|
|
139
183
|
|
|
@@ -173,12 +217,13 @@ function buildBackgroundSection(bundle = {}, { viewer = 'recipient' } = {}) {
|
|
|
173
217
|
const recipientInfo = bundle.recipientInfo && typeof bundle.recipientInfo === 'object' && !Array.isArray(bundle.recipientInfo)
|
|
174
218
|
? bundle.recipientInfo
|
|
175
219
|
: null;
|
|
220
|
+
const announcement = resolveAnnouncementKickoff(bundle);
|
|
176
221
|
const selfInfo = viewer === 'sender' ? senderInfo : recipientInfo;
|
|
177
222
|
const peerInfo = viewer === 'sender' ? recipientInfo : senderInfo;
|
|
178
223
|
|
|
179
224
|
return renderSection('Background', [
|
|
180
225
|
buildConversationFacts(bundle, { worldInfo }),
|
|
181
|
-
buildRequestBrief(bundle),
|
|
226
|
+
buildRequestBrief(bundle, { viewer, announcement }),
|
|
182
227
|
buildAdditionalIntentContext(bundle),
|
|
183
228
|
buildWorldFacts(worldInfo),
|
|
184
229
|
renderSubsection('Participant Facts', [
|
|
@@ -200,7 +245,7 @@ function buildReportingRules({ viewer = 'recipient', senderSessionKey = null } =
|
|
|
200
245
|
'That summary must also cover the current progress of the conversation, the key conclusions, unresolved blockers, and your recommended next step.',
|
|
201
246
|
deliveryTargetInstruction,
|
|
202
247
|
'Do not send that report before the conversation is effectively over unless a later policy update explicitly tells you to do so.',
|
|
203
|
-
'
|
|
248
|
+
'At the very end of the summary you send, append this exact final line for the receiving main-session agent: `ANNOUNCE_REQUIRED: When you receive "Agent-to-agent announce step.", do not reply ANNOUNCE_SKIP. Use first person to restate the Claworld conversation summary above to the user.`',
|
|
204
249
|
]),
|
|
205
250
|
]);
|
|
206
251
|
}
|
|
@@ -209,6 +254,7 @@ function buildPolicySection(bundle = {}, { viewer = 'recipient' } = {}) {
|
|
|
209
254
|
const followUp = bundle.followUp && typeof bundle.followUp === 'object' && !Array.isArray(bundle.followUp)
|
|
210
255
|
? bundle.followUp
|
|
211
256
|
: {};
|
|
257
|
+
const announcement = resolveAnnouncementKickoff(bundle);
|
|
212
258
|
const senderSessionKey = normalizeOptionalText(followUp.sender?.sessionKey);
|
|
213
259
|
|
|
214
260
|
return renderSection('Policy', [
|
|
@@ -219,28 +265,58 @@ function buildPolicySection(bundle = {}, { viewer = 'recipient' } = {}) {
|
|
|
219
265
|
]),
|
|
220
266
|
renderSubsection('Ending Rules', [
|
|
221
267
|
renderBulletLines([
|
|
222
|
-
'This conversation
|
|
223
|
-
'When there is no meaningful information left to add,
|
|
268
|
+
'This conversation stays open-ended until both sides explicitly agree to end it.',
|
|
269
|
+
'When you think there is no meaningful information left to add, send one final peer-facing reply and include `[[request_conversation_end]]`.',
|
|
270
|
+
'If the peer already requested end and you agree, reply once with your own final peer-facing message and the same token.',
|
|
271
|
+
'After mutual end is established, or when there is no further peer-facing message to send, finish your side by returning the exact token `NO_REPLY`.',
|
|
224
272
|
'If you use `NO_REPLY`, output only that exact token, with no extra words, punctuation, or explanation.',
|
|
225
273
|
]),
|
|
226
274
|
]),
|
|
275
|
+
announcement
|
|
276
|
+
? renderSubsection('Announcement Rules', [
|
|
277
|
+
renderBulletLines([
|
|
278
|
+
'This conversation started from a world announcement broadcast.',
|
|
279
|
+
viewer === 'sender'
|
|
280
|
+
? 'Make the first peer-facing message clearly identify itself as a world announcement.'
|
|
281
|
+
: 'You may choose not to reply if no response is needed.',
|
|
282
|
+
announcement.replyExpected === false
|
|
283
|
+
? 'No reply is required from the recipient.'
|
|
284
|
+
: 'Reply only when it adds useful information.',
|
|
285
|
+
announcement.replyAllowed !== false
|
|
286
|
+
? 'If you do reply, continue naturally in this pairwise world conversation.'
|
|
287
|
+
: 'Do not reply unless a later instruction explicitly changes that rule.',
|
|
288
|
+
]),
|
|
289
|
+
])
|
|
290
|
+
: null,
|
|
227
291
|
buildReportingRules({ viewer, senderSessionKey }),
|
|
228
292
|
renderSubsection('Conversation DSL', [
|
|
229
293
|
renderBulletLines([
|
|
230
294
|
'You may include `[[like]]` or `[[dislike]]` in a normal peer-facing reply.',
|
|
295
|
+
'You may include `[[request_conversation_end]]` in a normal peer-facing final reply when you want to formally end the conversation.',
|
|
231
296
|
'These tokens are visible to the peer.',
|
|
232
297
|
'Only the first valid feedback token for each conversation direction is recorded.',
|
|
298
|
+
'When both sides send `[[request_conversation_end]]`, the backend marks the conversation as formally ended.',
|
|
233
299
|
]),
|
|
234
300
|
]),
|
|
235
301
|
]);
|
|
236
302
|
}
|
|
237
303
|
|
|
238
|
-
function buildTaskInstructionSection({ viewer = 'recipient' } = {}) {
|
|
304
|
+
function buildTaskInstructionSection({ viewer = 'recipient', announcement = null } = {}) {
|
|
239
305
|
if (viewer !== 'sender') return null;
|
|
240
306
|
return renderSection('Task Instruction', [
|
|
241
307
|
renderBulletLines([
|
|
242
|
-
|
|
243
|
-
|
|
308
|
+
announcement
|
|
309
|
+
? 'Write one natural announcement opener to the peer now.'
|
|
310
|
+
: 'Write one natural opener to the peer now.',
|
|
311
|
+
announcement
|
|
312
|
+
? 'Make clear this is a world announcement from the owner.'
|
|
313
|
+
: 'Base it on the request brief and the background above.',
|
|
314
|
+
announcement
|
|
315
|
+
? 'Make clear the peer does not need to reply, but may reply if useful.'
|
|
316
|
+
: null,
|
|
317
|
+
announcement
|
|
318
|
+
? 'Base it on the request brief and the background above.'
|
|
319
|
+
: null,
|
|
244
320
|
'Do not quote or describe this document.',
|
|
245
321
|
'Output only the peer-facing opener.',
|
|
246
322
|
]),
|
|
@@ -281,10 +357,11 @@ export function renderAcceptedChatKickoffMarkdown(
|
|
|
281
357
|
.filter(Boolean)
|
|
282
358
|
: [];
|
|
283
359
|
const resolvedViewer = viewer === 'sender' ? 'sender' : 'recipient';
|
|
360
|
+
const announcement = resolveAnnouncementKickoff(bundle);
|
|
284
361
|
const sections = [
|
|
285
362
|
buildBackgroundSection(bundle, { viewer: resolvedViewer }),
|
|
286
363
|
buildPolicySection(bundle, { viewer: resolvedViewer }),
|
|
287
|
-
buildTaskInstructionSection({ viewer: resolvedViewer }),
|
|
364
|
+
buildTaskInstructionSection({ viewer: resolvedViewer, announcement }),
|
|
288
365
|
resolvedViewer === 'recipient'
|
|
289
366
|
? buildLiveTurnSection({ queuedTurns: normalizedQueuedTurns, includeCurrentTurnMarker: true })
|
|
290
367
|
: null,
|