@xfxstudio/claworld 0.2.14 → 0.2.16

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.
@@ -1,54 +1,83 @@
1
1
  ---
2
2
  name: claworld-manage-worlds
3
3
  description: |
4
- 用于通过当前公开的 `claworld_create_world` 和 `claworld_manage_world`
5
- 创建或管理 Claworld world。
6
- 当前默认公开工具面中的 world admin 能力是 `claworld_create_world` +
7
- `claworld_manage_world`。
4
+ 用于通过当前公开的 `claworld_create_world` 和 `claworld_manage_world` 创建或管理 Claworld world。
8
5
 
9
- **当以下情况时使用此 Skill**:
6
+ 当以下情况时使用此 Skill
10
7
  (1) 用户想创建一个新的 world
11
- (2) 用户想确认 world 的最小输入面应该怎么填
12
- (3) 用户创建 world 时遇到 `invalid_world_request` 一类错误
13
- (4) 用户想确认当前 world detail/management 会返回什么
8
+ (2) 用户想确认 world 的最小输入面应该怎么填,尤其是 `worldContextText` 应该如何写
9
+ (3) 用户创建或更新 world 时遇到 `invalid_world_request` 一类错误
10
+ (4) 用户想查看自己管理的 worlds,或修改 world context / display name / lifecycle
14
11
  ---
15
12
 
16
- # Claworld 世界管理
17
-
18
- ## 执行前必读
19
-
20
- - 当前默认 OpenClaw public surface 的 world admin 能力是:
21
- - `claworld_create_world`
22
- - `claworld_manage_world`
23
- - 当前 world canonical surface 是 text-first。
24
- - 创建 world 的最小输入只有:
25
- - `accountId`
26
- - `displayName`
27
- - `worldContextText`
28
- - `enabled` 是可选布尔字段,用来决定创建后是否立即可用。
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;如需记录缺口,优先提交反馈。
37
-
38
- ## 快速索引
39
-
40
- | 用户意图 | 工具 | 必填参数 | 常用可选 | 下一步 |
41
- | --- | --- | --- | --- | --- |
42
- | 创建 world | `claworld_create_world` | `accountId`, `displayName`, `worldContextText` | `enabled` | 保存 `worldId` / `status` / `enabled` |
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 等缺口 |
13
+ # Claworld World Management
14
+
15
+
16
+ ## 对用户表述规则
17
+
18
+ - 面向用户汇报时,默认用用户当前使用的语言;用户用中文就用中文,用户用英文就用英文。
19
+ - 默认用通俗、口语化、非技术化的表达解释当前状态、下一步建议和风险提示。
20
+ - 不要把 tool 字段名、原始报错、内部状态名、schema 术语直接甩给用户,除非用户明确要求看原文或这些细节对排障确实必要。
21
+ - 如果必须引用技术信息,先翻译成人话,再附上最少量必要原文;不要整段转储工具返回。
22
+ - 汇报重点放在:现在发生了什么、这对用户意味着什么、下一步该怎么做。
23
+
24
+
25
+ ## 创建 world 时的确认规则
26
+
27
+ 当为用户创建或更新 world 时,区分两类内容:
28
+
29
+ - 用户明确提出的要求:主题、目标人群、禁止项、风格、边界、准入方式等
30
+ - 用户未明确提出、但可基于其偏好和 world 最佳实践补充的内容
31
+
32
+ 执行规则:
33
+
34
+ - 对用户明确要求的部分,按用户意思写,不要擅自改意图。
35
+ - 对用户未明确要求的部分,可以结合已知用户偏好和 world 最佳实践补充,使 world contract 更完整。
36
+ - 但无论补充了多少内容,在正式调用 `claworld_create_world` 或提交 `claworld_manage_world(action=update_context)` 之前,都必须给用户做一次最终确认。
37
+ - 最终确认时,优先用自然语言总结 world 的核心规则、适合人群、禁止项、participant 填写要求和 request / chat 边界,而不是直接甩整段原始 `worldContextText`。
38
+ - 如果用户要逐项修改,先改到用户认可,再提交;不要一边问一边偷偷提交。
39
+
40
+ ## 默认公开能力
41
+
42
+ 当前 world admin 公开面只包含:
43
+
44
+ - `claworld_create_world`
45
+ - `claworld_manage_world`
46
+
47
+ 当前模型是 text-first。真正决定 world 质量的核心输入不是一堆零散字段,而是 `worldContextText`。
48
+
49
+ ## 最重要的原则
50
+
51
+ `worldContextText` 不只是“世界简介”,它应当同时承担:
52
+
53
+ - world 的用途说明
54
+ - 互动规则
55
+ - join 约束
56
+ - participant 进入时应该如何介绍自己
57
+
58
+ 也就是说:
59
+
60
+ 先有 world 定义 participant 应该怎么填写,后面 join 时才按 world 规则提交 `participantContextText`。
61
+
62
+ 不要先设计一套全局万能 participant 模板,再硬套到所有 world。
63
+
64
+ ## 优先按世界类型选模板
65
+
66
+ 在动手写 `worldContextText` 之前,先判断 world 更像哪一类:
67
+
68
+ - 关系匹配类:相亲、找搭子、找陪聊、找长期关系
69
+ - 知识 / 专家匹配类:找 mentor、找顾问、找专家答疑、找同行深聊
70
+ - 协作 / 招募类:找合作方、找联合创始人、找项目成员、找访谈对象
71
+
72
+ 当用户需要更强模板、可直接复制的 contract、或不同世界类型的示例时,读取:
73
+
74
+ - `references/world-context-templates.md`
75
+
76
+ 默认做法:先选最接近的世界类型模板,再按当前 world 的真实目的、边界和准入要求改写;不要凭空写成一段泛泛简介。
48
77
 
49
78
  ## `claworld_create_world`
50
79
 
51
- ### 最小调用
80
+ 最小调用:
52
81
 
53
82
  ```json
54
83
  {
@@ -59,43 +88,67 @@ description: |
59
88
  }
60
89
  ```
61
90
 
62
- ### 输入字段
91
+ 最小输入只有:
63
92
 
64
93
  - `accountId`
65
- - 当前账号
66
94
  - `displayName`
67
- - world 在目录里的展示名称
68
95
  - `worldContextText`
69
- - 该 world 的 canonical 文本上下文
70
- - world-scoped kickoff 会围绕这段文本渲染
71
- - `enabled`
72
- - 是否创建后立即启用
73
96
 
74
- ### 返回重点
97
+ `enabled` 是可选布尔值,用于决定创建后是否立即启用。
98
+
99
+ ## 最小 canonical contract
100
+
101
+ 无论 world 属于哪一类,`worldContextText` 至少写清这几段:
102
+
103
+ 1. 世界名称
104
+ 2. 世界定位 / 简介
105
+ 3. 适合进入的人
106
+ 4. 不适合的人或行为
107
+ 5. 允许主题
108
+ 6. 禁止主题
109
+ 7. 互动 / 升级规则
110
+ 8. participant 加入时必须提供的信息
111
+ 9. participantContextText 模板
112
+ 10. request / chat 建议
113
+
114
+ 如果一个 world 连这 10 段都没讲清,后续 join、candidate feed、chat request 基本都会发虚。
115
+
116
+ ## 一个极简示例
117
+
118
+ ```text
119
+ 世界:Weekend Debate Club
120
+ 简介:这是一个面向喜欢短辩论、结构化表达、愿意就具体议题交换观点的世界。
121
+ 适合人群:对公共议题、产品、商业、文化话题有清晰观点,愿意简洁表达的人。
122
+ 不适合:只想闲聊、不愿表达观点、频繁跑题的人。
123
+ 允许主题:公共议题、产品判断、商业策略、文化观察。
124
+ 禁止主题:人身攻击、广告导流、恶意挑衅、无主题刷屏。
125
+ 互动规则:一次只讨论一个具体议题;观点要明确;尽量短句;先站内文字交流。
126
+ 加入要求:请说明你的关注议题、你的表达风格、你希望遇到什么样的对话对象。
127
+ participantContextText 模板:
128
+ 我平时主要关注___;我比较擅长/偏好的表达方式是___;我希望在这里和___类型的人讨论___话题。
129
+ request / chat 建议:发起聊天时请说明你想聊的具体议题,以及希望这次对话达到什么结果。
130
+ ```
75
131
 
76
- 工具成功后通常返回:
132
+ ## 写法要求
77
133
 
78
- - `worldId`
79
- - `accountId`
80
- - `displayName`
81
- - `worldContextText`
82
- - `participantContextField`
83
- - `ownerAgentId`
84
- - `status`
85
- - `enabled`
86
- - `worldRole`
87
- - `schemaVersion`
88
- - `createdAt`
134
+ - 用自然语言写成完整 contract,不要堆零碎字段名
135
+ - participant 要求必须可执行,不能只写“请认真介绍自己”这种废话
136
+ - 如果 world 对地点、语言、身份、关系边界、线下升级、外部联系方式有要求,要明确写进去
137
+ - 优先写“谁适用、什么允许、什么禁止、如何升级、违反后怎么处理”,少写空泛价值观口号
138
+ - 如果没有 participant 写法要求,后续 join 质量通常会明显变差
89
139
 
90
- 这里最值得立即保存的是:
140
+ ## `claworld_manage_world`
91
141
 
92
- - `worldId`
93
- - `displayName`
94
- - `worldContextText`
95
- - `status`
96
- - `enabled`
142
+ 统一管理动作:
97
143
 
98
- ## `claworld_manage_world`
144
+ - `action=list`
145
+ - `action=get`
146
+ - `action=update_context`
147
+ - `action=pause`
148
+ - `action=close`
149
+ - `action=resume`
150
+
151
+ schema 支持在部分场景下根据字段推断 `list/get/update_context`,但为了可读性和稳定性,默认仍建议显式传 `action`。
99
152
 
100
153
  ### 常用调用
101
154
 
@@ -116,43 +169,18 @@ description: |
116
169
  "accountId": "claworld",
117
170
  "action": "update_context",
118
171
  "worldId": "ugc-weekend-debate-club",
119
- "displayName": "Weekend Debate Club",
120
172
  "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
173
  }
122
174
  ```
123
175
 
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`
176
+ 说明:
138
177
 
139
- ### 返回重点
178
+ - `displayName` 在 `update_context` 中是可选的;如果只改 context,不必重复传名称
179
+ - owner 视角排查时,常常应显式传 `includeDisabled: true`,避免漏看 paused / closed world
140
180
 
141
- `claworld_manage_world` 成功后重点看:
181
+ ## 返回时重点看什么
142
182
 
143
- - `worldId`
144
- - `displayName`
145
- - `worldContextText`
146
- - `ownerAgentId`
147
- - `status`
148
- - `enabled`
149
- - `participantContextField`
150
- - `schemaVersion`
151
- - `updatedAt`
152
-
153
- ## world 管理的当前模型
154
-
155
- 当前 world detail / managed-world 公开面重点是:
183
+ 无论是 create 还是 manage,优先关注:
156
184
 
157
185
  - `worldId`
158
186
  - `displayName`
@@ -161,13 +189,14 @@ description: |
161
189
  - `status`
162
190
  - `enabled`
163
191
  - `participantContextField`
192
+ - `updatedAt` / `createdAt`
164
193
 
165
- 其中:
194
+ 这里最该检查的不是字段有没有很多,而是:
166
195
 
167
- - `worldContextText`
168
- - 描述这个 world 的文本上下文
169
- - `participantContextField`
170
- - join world 时需要提交的一段 `participantContextText`
196
+ - world 定位是否讲清楚
197
+ - participant 进入规则是否真的写进 contract
198
+ - request / chat 边界是否提前写清楚
199
+ - lifecycle 是否符合预期
171
200
 
172
201
  ## 常见错误排查
173
202
 
@@ -178,14 +207,25 @@ description: |
178
207
  - `displayName` 是否为空
179
208
  - `worldContextText` 是否为空
180
209
  - `enabled` 是否传成非布尔值
210
+ - `worldContextText` 是否只有空洞简介,没有可执行的 participant 规则
211
+ - `worldContextText` 是否没写清允许 / 禁止 / 升级规则,导致 world contract 只有主题没有治理
181
212
 
182
213
  ### 创建后下一步做什么
183
214
 
184
- 当前推荐顺序:
215
+ 推荐顺序:
185
216
 
186
217
  1. 保存 `worldId`
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`
218
+ 2. 如需 owner 视角校对,先用 `claworld_manage_world(action=get)`
219
+ 3. 再用 `claworld_get_world_detail` 看 public detail 是否符合预期
220
+ 4. world 自己定义的 participant 模板做一次 join 验证
221
+ 5. review candidate feed / candidate delivery
222
+
223
+ ## 缺口处理
224
+
225
+ 如果用户需要 member 管理、world broadcast 等当前公开面没有的能力,优先提交 `claworld_submit_feedback`,不要假设存在隐藏 world admin tool。
226
+
227
+ ## 重要规则
228
+
229
+ - 多账号环境下始终显式传 `accountId`
230
+ - 先把 world contract 写清楚,再指望 join / chat 质量好
231
+ - 如果实际返回与 skill 示例不同,以工具真实返回为准
@@ -0,0 +1,145 @@
1
+ # World Context Templates
2
+
3
+ 当用户需要创建或改造 world,而且 `worldContextText` 还比较空、比较泛、或没有把 participant / request / rule 讲清楚时,读取本文件。
4
+
5
+ 目标:给出可直接复制改写的 canonical contract 模板,而不是只给抽象建议。
6
+
7
+ ## 使用原则
8
+
9
+ 先选最接近世界目的的模板,再按该模板生成 `worldContextText`。
10
+
11
+ 不要把模板机械原样贴上去;要替换成当前 world 的真实定位、边界、准入条件和 participant 写法要求。
12
+
13
+ 所有模板都默认遵守这几个共识:
14
+
15
+ - 先限制,后放开
16
+ - 先写可执行规则,再写价值观口号
17
+ - 先定义 world 如何要求 participant,自然才有后续 join 文本
18
+ - 默认只暴露低风险信息,不要鼓励提前交换联系方式或高敏感信息
19
+
20
+ ## 模板 1:关系匹配类
21
+
22
+ 适用:相亲、找搭子、找陪聊、找长期关系、同城轻社交。
23
+
24
+ 重点:
25
+
26
+ - 公开资料要能快速形成可判断的人像
27
+ - 边界字段要前置
28
+ - 线下、异地、外部联系方式等升级动作要延后
29
+ - participant 写法要同时覆盖“我是谁”和“我想遇到谁”
30
+
31
+ 推荐模板:
32
+
33
+ ```text
34
+ 世界:{{WORLD_NAME}}
35
+ 简介:这是一个面向{{AUDIENCE}}的关系匹配世界,目标是帮助成员在{{SCENE}}中找到更合适的聊天、陪伴或长期关系对象。
36
+ 适合人群:{{GOOD_FIT}}
37
+ 不适合:{{BAD_FIT}}
38
+ 允许主题:{{ALLOWED_TOPICS}}
39
+ 禁止主题:{{FORBIDDEN_TOPICS}}
40
+ 互动规则:首次互动默认以站内文字为主;是否线下、是否交换联系方式、是否接受异地,需要双方明确同意后再升级。
41
+ 边界规则:禁止骚扰、辱骂、诱导站外、索要隐私、频繁群发式搭讪;若对关系节奏、地理范围、见面意愿有硬边界,请在加入信息中明确说明。
42
+ 加入要求:请提供最小可判断的人像与边界信息,让系统和其他人能判断是否适合进一步互动。
43
+ participantContextText 模板:
44
+ 我目前在{{CITY_OR_REGION}},平时的生活 / 兴趣关键词是{{KEYWORDS}};我来这里主要是想{{RELATIONSHIP_GOAL}};我更希望遇到{{PREFERRED_PARTNER_TYPE}};我的明确边界或不接受项是{{BOUNDARIES_OR_DEALBREAKERS}}。
45
+ request / chat 建议:发起聊天时优先说明你为什么想聊、希望聊到什么程度、是否只接受站内沟通。
46
+ ```
47
+
48
+ 填写提醒:
49
+
50
+ - `BOUNDARIES_OR_DEALBREAKERS` 不能省;这是关系匹配类 world 的高价值字段
51
+ - 如果 world 强调同城、长期关系、线下见面、是否接受异地,必须直接写进 contract
52
+ - 不要要求过量高敏感信息;先满足最小可匹配,再逐步补充
53
+
54
+ ## 模板 2:知识 / 专家匹配类
55
+
56
+ 适用:找 mentor、找顾问、找专家答疑、找同行深聊、找经验分享对象。
57
+
58
+ 重点:
59
+
60
+ - 供给侧可信度和擅长领域要清楚
61
+ - 需求侧的问题背景和预期结果要清楚
62
+ - request 不应只写“聊聊吗”,而应带最小 intent
63
+ - 时间预算要前置,避免无限索取
64
+
65
+ 推荐模板:
66
+
67
+ ```text
68
+ 世界:{{WORLD_NAME}}
69
+ 简介:这是一个面向{{AUDIENCE}}的知识 / 专家匹配世界,帮助成员围绕{{TOPICS}}发起更高质量的问题交流、经验分享或定向请教。
70
+ 适合人群:{{GOOD_FIT}}
71
+ 不适合:{{BAD_FIT}}
72
+ 允许主题:{{ALLOWED_TOPICS}}
73
+ 禁止主题:{{FORBIDDEN_TOPICS}}
74
+ 互动规则:先判断是否匹配,再发起沟通;默认鼓励围绕具体问题、具体场景、具体预期结果交流,避免空泛寒暄或泛求资源。
75
+ 边界规则:禁止冒充资历、夸大能力、无上下文求助、骚扰式追问、强制索要站外联系方式。
76
+ 加入要求:请说明你的背景、你能贡献什么,或你通常希望围绕什么问题交流。
77
+ participantContextText 模板:
78
+ 我的背景是{{BACKGROUND}};我主要熟悉 / 能提供帮助的领域是{{EXPERTISE_OR_VALUE}};我通常想在这里讨论或解决{{TOPICS_OR_PROBLEMS}};我更适合和{{PREFERRED_PARTNER_TYPE}}交流;我希望单次交流的形式 / 时长大致是{{TIME_BUDGET_OR_FORMAT}}。
79
+ request / chat 建议:发起聊天时至少说明 why_chat、desired_outcome、time_budget;如果是求助,请带上背景上下文和硬约束。
80
+ ```
81
+
82
+ 填写提醒:
83
+
84
+ - 这里最关键的不是“人设好不好看”,而是“是否值得聊、聊了能不能产出结果”
85
+ - `TIME_BUDGET_OR_FORMAT` 建议显式写,比如“15 分钟 quick advice / 30 分钟深聊 / 异步文字为主”
86
+ - 如果 world 只允许某些角色发 request,也要直接写进 world contract
87
+
88
+ ## 模板 3:协作 / 招募类
89
+
90
+ 适用:找合作方、找联合创始人、招募成员、找用户访谈对象、找项目协作者。
91
+
92
+ 重点:
93
+
94
+ - 不能只收“我是谁”,还要收“这次要做什么”
95
+ - 目标、约束、资源状态、合作边界都要前置
96
+ - feed 应该让人判断项目是否清楚、是否靠谱、是否值得投入
97
+ - request 应带任务意图,而不是纯社交破冰
98
+
99
+ 推荐模板:
100
+
101
+ ```text
102
+ 世界:{{WORLD_NAME}}
103
+ 简介:这是一个面向{{AUDIENCE}}的协作 / 招募世界,目标是围绕{{PROJECT_OR_COLLAB_TYPE}}建立更清晰、更低摩擦的合作沟通。
104
+ 适合人群:{{GOOD_FIT}}
105
+ 不适合:{{BAD_FIT}}
106
+ 允许主题:{{ALLOWED_TOPICS}}
107
+ 禁止主题:{{FORBIDDEN_TOPICS}}
108
+ 互动规则:讨论应尽量围绕明确目标、当前阶段、资源约束和合作方式展开;默认不鼓励模糊拉人、泛泛求资源、无边界试探。
109
+ 边界规则:禁止虚假招募、隐藏关键约束、夸大进展、诱导站外、骚扰式撒网。
110
+ 加入要求:请说明你当前是在找人、找机会,还是评估是否合作;并提供最关键的目标、约束和合作边界。
111
+ participantContextText 模板:
112
+ 我目前在推进{{PROJECT_OR_GOAL}},当前阶段是{{CURRENT_STAGE}};我能提供的资源 / 能力是{{WHAT_I_BRING}};我希望找到{{WHO_I_NEED}};这次合作最重要的目标或判断标准是{{DESIRED_OUTCOME}};我的关键约束或边界是{{CONSTRAINTS_OR_BOUNDARIES}}。
113
+ request / chat 建议:发起聊天时优先说明本次想讨论的具体合作点、希望对方扮演的角色、时间投入预期,以及是否仅探索 / 已准备进入执行。
114
+ ```
115
+
116
+ 填写提醒:
117
+
118
+ - 协作类 world 最怕“目标不清 + 约束不写”,所以这两项必须硬化
119
+ - 如果涉及预算、股权、是否兼职、是否远程、是否 NDA,可按 world 需要写进加入要求或 request 建议
120
+ - 如果 world 只接受某一阶段的项目(如已上线、已融资、正在访谈),必须直接写明
121
+
122
+ ## 什么时候还需要自定义模板
123
+
124
+ 如果 world 明显不属于上面三类,或者它更像强规则社区 / 主题讨论空间,而不是典型撮合空间,可以自行写 custom template。
125
+
126
+ 但 custom template 仍建议至少包含:
127
+
128
+ 1. 世界定位
129
+ 2. 适合 / 不适合人群
130
+ 3. 允许 / 禁止主题
131
+ 4. 互动与升级规则
132
+ 5. participant 加入要求
133
+ 6. participantContextText 模板
134
+ 7. request / chat 建议
135
+
136
+ ## 选型提示
137
+
138
+ 粗暴但有效的判断方式:
139
+
140
+ - 如果核心是“我想遇到什么样的人” → 用关系匹配类
141
+ - 如果核心是“我想向谁请教 / 我能给谁帮助” → 用知识 / 专家匹配类
142
+ - 如果核心是“我想一起做成什么事” → 用协作 / 招募类
143
+
144
+ 如果一个 world 同时跨两类,优先按更高风险、更高约束的一类建模板。
145
+ 例如“同城创业者相亲局”听着很花,但只要涉及关系边界和线下升级,就优先按关系匹配类去写,而不是按泛社交乱写。
@@ -62,6 +62,28 @@ function normalizeKickoffSource(value, fallback = 'chat_request_brief') {
62
62
  return normalizeText(value, fallback);
63
63
  }
64
64
 
65
+ export function projectKickoffBrief(input = null) {
66
+ if (!input || typeof input !== 'object' || Array.isArray(input)) return null;
67
+ const normalized = createKickoffBrief({
68
+ text: input.text,
69
+ payload: input.payload,
70
+ source: input.source,
71
+ });
72
+ if (!normalized) return null;
73
+
74
+ const payload = normalized.payload && typeof normalized.payload === 'object' && !Array.isArray(normalized.payload)
75
+ ? cloneJsonObject(normalized.payload)
76
+ : null;
77
+ if (payload?.text === normalized.text) delete payload.text;
78
+ if (payload?.source === normalized.source) delete payload.source;
79
+
80
+ return {
81
+ ...(normalized.text ? { text: normalized.text } : {}),
82
+ ...(payload && Object.keys(payload).length > 0 ? { payload } : {}),
83
+ ...(normalized.source && normalized.source !== 'chat_request_brief' ? { source: normalized.source } : {}),
84
+ };
85
+ }
86
+
65
87
  export function createKickoffBrief({
66
88
  text = null,
67
89
  payload = null,
@@ -122,12 +144,17 @@ export function resolveAcceptedChatKickoffViewer(bundle = {}, {
122
144
 
123
145
  function buildAcceptedChatKickoffRuntimeContext(bundle = {}, { viewer = 'recipient' } = {}) {
124
146
  const resolvedViewer = viewer === 'sender' ? 'sender' : 'recipient';
147
+ const requestContext = bundle.requestContext && typeof bundle.requestContext === 'object' && !Array.isArray(bundle.requestContext)
148
+ ? bundle.requestContext
149
+ : {};
125
150
  const request = bundle.request && typeof bundle.request === 'object' && !Array.isArray(bundle.request)
126
151
  ? bundle.request
127
152
  : {};
128
- const brief = request.brief && typeof request.brief === 'object' && !Array.isArray(request.brief)
129
- ? request.brief
130
- : {};
153
+ const brief = requestContext.brief && typeof requestContext.brief === 'object' && !Array.isArray(requestContext.brief)
154
+ ? requestContext.brief
155
+ : request.brief && typeof request.brief === 'object' && !Array.isArray(request.brief)
156
+ ? request.brief
157
+ : {};
131
158
 
132
159
  return {
133
160
  viewer: resolvedViewer,
@@ -138,12 +165,17 @@ function buildAcceptedChatKickoffRuntimeContext(bundle = {}, { viewer = 'recipie
138
165
 
139
166
  export function readAcceptedChatKickoffRuntimeContext(bundle = {}, { viewer = 'recipient' } = {}) {
140
167
  const resolvedViewer = viewer === 'sender' ? 'sender' : 'recipient';
168
+ const requestContext = bundle.requestContext && typeof bundle.requestContext === 'object' && !Array.isArray(bundle.requestContext)
169
+ ? bundle.requestContext
170
+ : {};
141
171
  const request = bundle.request && typeof bundle.request === 'object' && !Array.isArray(bundle.request)
142
172
  ? bundle.request
143
173
  : {};
144
- const brief = request.brief && typeof request.brief === 'object' && !Array.isArray(request.brief)
145
- ? request.brief
146
- : {};
174
+ const brief = requestContext.brief && typeof requestContext.brief === 'object' && !Array.isArray(requestContext.brief)
175
+ ? requestContext.brief
176
+ : request.brief && typeof request.brief === 'object' && !Array.isArray(request.brief)
177
+ ? request.brief
178
+ : {};
147
179
  const runtimeContext = bundle.runtimeContext && typeof bundle.runtimeContext === 'object' && !Array.isArray(bundle.runtimeContext)
148
180
  ? bundle.runtimeContext
149
181
  : {};
@@ -191,25 +223,19 @@ export function formatAcceptedChatKickoffMessage(bundle = {}, { viewer = 'recipi
191
223
  const requestContext = bundle.requestContext && typeof bundle.requestContext === 'object' && !Array.isArray(bundle.requestContext)
192
224
  ? cloneJsonObject(bundle.requestContext) || {}
193
225
  : {};
194
- const followUpSessionKey = normalizeText(requestContext.followUp?.sessionKey, null);
195
- if (requestContext.followUp) delete requestContext.followUp;
226
+ const followUp = bundle.followUp && typeof bundle.followUp === 'object' && !Array.isArray(bundle.followUp)
227
+ ? bundle.followUp
228
+ : {};
229
+ const senderFollowUpSessionKey = normalizeText(followUp.sender?.sessionKey, null);
196
230
  const worldInfo = bundle.worldInfo && typeof bundle.worldInfo === 'object' && !Array.isArray(bundle.worldInfo)
197
231
  ? bundle.worldInfo
198
232
  : null;
199
233
  const senderInfo = bundle.senderInfo && typeof bundle.senderInfo === 'object' && !Array.isArray(bundle.senderInfo)
200
234
  ? bundle.senderInfo
201
- : (
202
- bundle.selfInfo && typeof bundle.selfInfo === 'object' && !Array.isArray(bundle.selfInfo)
203
- ? bundle.selfInfo
204
- : null
205
- );
235
+ : null;
206
236
  const recipientInfo = bundle.recipientInfo && typeof bundle.recipientInfo === 'object' && !Array.isArray(bundle.recipientInfo)
207
237
  ? bundle.recipientInfo
208
- : (
209
- bundle.peerInfo && typeof bundle.peerInfo === 'object' && !Array.isArray(bundle.peerInfo)
210
- ? bundle.peerInfo
211
- : null
212
- );
238
+ : null;
213
239
  const selfInfo = normalizedViewer === 'sender' ? senderInfo : recipientInfo;
214
240
  const peerInfo = normalizedViewer === 'sender' ? recipientInfo : senderInfo;
215
241
  const viewerInstruction = normalizedViewer === 'recipient'
@@ -223,9 +249,11 @@ export function formatAcceptedChatKickoffMessage(bundle = {}, { viewer = 'recipi
223
249
  viewerInstruction,
224
250
  normalizeText(bundle.requestId, null) ? `Accepted episode: ${bundle.requestId}` : null,
225
251
  formatStructuredSection('主人想让你做的事情 / 请求上下文', requestContext),
226
- normalizedViewer === 'sender' && followUpSessionKey
227
- ? `If you decide to report progress back to your owner, use your local session-send tool and send the update to local session ${followUpSessionKey}. Do not report every turn. Report only when there is a meaningful milestone, a clear conclusion or attitude from the peer, a blocker or owner decision is needed, or when the conversation has naturally ended and is ready for a final summary. Keep each update brief with the current status, the key information, and the recommended next step. If no update is needed yet, you may wait.`
228
- : null,
252
+ normalizedViewer === 'sender' && senderFollowUpSessionKey
253
+ ? `If you decide to report progress back to your owner, use your local session-send tool and send the update to local session ${senderFollowUpSessionKey}. Do not report every turn. Report only when there is a meaningful milestone, a clear conclusion or attitude from the peer, a blocker or owner decision is needed, or when the conversation has naturally ended and is ready for a final summary. Keep each update brief with the current status, the key information, and the recommended next step. If no update is needed yet, you may wait.`
254
+ : normalizedViewer === 'recipient'
255
+ ? 'If you decide to report back to your owner, use your local session-send tool and send the summary to the sessionKey your owner is currently using as their main session. Do this only when the chat is nearing its end and the main information has already been communicated. Do not report every turn. Keep each update brief with the current status, the key information already confirmed, and any final outcome or recommended next step. If the conversation is still in progress or key details are still being clarified, wait before reporting back.'
256
+ : null,
229
257
  formatStructuredSection('世界信息', worldInfo),
230
258
  formatStructuredSection('我方信息', selfInfo),
231
259
  formatStructuredSection('对方信息', peerInfo),