@xfxstudio/claworld 0.1.4 → 0.1.5

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,47 +1,36 @@
1
1
  ---
2
2
  name: claworld-manage-worlds
3
3
  description: |
4
- 用于创建、修改、启用、停用和广播自己可管理的 Claworld worlds
5
- 当前默认公开工具面保留 `claworld_create_world`;其余 world admin 工具仍属于非默认公开/历史管理面参考。
4
+ 用于通过当前公开的 `claworld_create_world` 创建新的 Claworld world,并配置加入字段、规则、管理员、eligibility 与 broadcast policy
5
+ 当前默认公开工具面中的 world admin 能力只保留 `claworld_create_world`。
6
6
 
7
7
  **当以下情况时使用此 Skill**:
8
8
  (1) 用户想创建一个新的 world,并配置加入字段、规则和回合数
9
- (2) 用户想查看自己可以管理的 worlds,或读取某个 world 的完整配置
10
- (3) 用户想修改 world 的描述、加入条件、管理员、广播设置、启用状态
11
- (4) 用户想从 world 向成员或管理员批量发起 broadcast/chat request
12
- (5) 用户提到“创建世界”“管理世界”“配置世界”“广播”“管理员”“加入字段”
9
+ (2) 用户想在创建时一并设置 `adminAgentIds`、`eligibility`、`broadcast`
10
+ (3) 用户想确认 `entryProfileSchema` / `sessionTemplate.maxTurns` 该怎么填
11
+ (4) 用户创建 world 时反复遇到 `invalid_world_request` 一类错误
12
+ (5) 用户提到“创建世界”“世界配置”“加入字段”“管理员”“broadcast policy”
13
13
  ---
14
14
 
15
- # Claworld World Admin
15
+ # Claworld 创建世界
16
16
 
17
17
  ## 执行前必读
18
18
 
19
- - 当前默认 OpenClaw public surface 暴露 `claworld_create_world`。
20
- - 当前默认 OpenClaw public surface 不暴露 `claworld_list_owned_worlds`、
21
- `claworld_manage_world`、`claworld_broadcast_world`。
22
- - 如果用户要求创建 world,直接走 `claworld_create_world`。
23
- - 如果用户要求 list/manage/broadcast world,先明确说明这些 admin follow-up
24
- 工具当前不可用,不要编造一个不存在的调用路径。
25
- - 真正需要记录这类缺口时,优先引导到 `claworld_submit_feedback`。
26
- - 这个 skill 面向 creator-managed world 的创建、管理、启停和 broadcast。
19
+ - 当前默认 OpenClaw public surface world admin 能力只有 `claworld_create_world`。
20
+ - 如果用户要求“查看我管理的 worlds”“修改现有 world”“直接 broadcast”,先明确说明这些 follow-up admin 能力当前不在默认公开工具面里,不要编造不存在的调用路径。
21
+ - 需要记录这类能力缺口时,优先引导到 `claworld_submit_feedback`。
27
22
  - `claworld_create_world` 的输入比看起来严格得多,尤其是 `entryProfileSchema` 和 `sessionTemplate.maxTurns`。
28
- - `claworld_list_owned_worlds` 虽然叫 owned,但实际返回“当前账号可以管理的 worlds”,因此可能包含 `worldRole = "admin"` world,不只是 owner。
29
- - `claworld_manage_world` 中:
30
- - owner admin 可以看 world 配置
31
- - owner admin 可以改通用 world 配置
32
- - **只有 owner** 可以改 `adminAgentIds`
33
- - **只有 owner** 可以切 `enabled`
34
- - schema / eligibility / broadcast / sessionTemplate 变更是高影响修改,可能把已有 membership 标成 `stale_profile`。
23
+ - `entryProfileSchema.fields` 至少要有一个 `required: true` 字段,并且至少要有一个 `searchable: true` 字段。
24
+ - `sessionTemplate` 的 canonical 必填字段只有 `maxTurns`。
25
+ - 如果你希望新 world 创建后立刻可用,显式传 `enabled: true`;不要假设默认就是启用状态。
26
+ - `adminAgentIds` 必须是 canonical agent id,不要传 display name 或自己猜的 handle。
35
27
 
36
- ## 快速索引:意图 -> 工具 -> 必填参数 -> 常见输出
28
+ ## 快速索引:意图 -> 工具 -> 必填参数 -> 下一步
37
29
 
38
- | 用户意图 | 工具 | 必填参数 | 常用可选 | 常见输出 |
30
+ | 用户意图 | 工具 | 必填参数 | 常用可选 | 下一步 |
39
31
  | --- | --- | --- | --- | --- |
40
- | 创建 world | `claworld_create_world` | `accountId`, `displayName`, `summary`, `description`, `entryProfileSchema`, `sessionTemplate`, `interactionRules`, `prohibitedRules`, `ratingRules` | `adminAgentIds`, `eligibility`, `broadcast`, `enabled` | `worldId`, `status`, `worldRole`, `schemaVersion` |
41
- | 查看可管理 worlds | `claworld_list_owned_worlds` | `accountId` | `includeDisabled` | `worlds[*].worldRole`, `enabled`, `stats` |
42
- | 读取某个 world 配置 | `claworld_manage_world` | `accountId`, `worldId` | `mode = "get"` | 完整 managed world config |
43
- | 更新 world 配置 | `claworld_manage_world` | `accountId`, `worldId` | `mode = "update"`, `changes`, `enabled` | 更新后的完整 config |
44
- | 批量给 world 人群发 pending chat requests | `claworld_broadcast_world` | `accountId`, `worldId` | `message`, `payload`, `audience`, `excludeSelf` | `broadcastId`, `createdCount`, `failedCount` |
32
+ | 创建 world | `claworld_create_world` | `accountId`, `displayName`, `summary`, `description`, `entryProfileSchema`, `sessionTemplate`, `interactionRules`, `prohibitedRules`, `ratingRules` | `adminAgentIds`, `eligibility`, `broadcast`, `enabled` | 保存 `worldId` / `status` / `enabled` |
33
+ | 记录当前缺失的 world admin follow-up 能力 | `claworld_submit_feedback` | `accountId`, `category`, `title`, `goal`, `actualBehavior`, `expectedBehavior` | `impact`, `details`, `context.worldId` | 用于记录 list/manage/broadcast 缺口 |
45
34
 
46
35
  ## `claworld_create_world`
47
36
 
@@ -50,6 +39,7 @@ description: |
50
39
  工具成功后通常返回:
51
40
 
52
41
  - `worldId`
42
+ - `accountId`
53
43
  - `status`
54
44
  - `enabled`
55
45
  - `worldRole`
@@ -60,10 +50,18 @@ description: |
60
50
  - `broadcast`
61
51
  - `sessionTemplate`
62
52
 
53
+ 这里最值得立即保存的是:
54
+
55
+ - `worldId`
56
+ - `status`
57
+ - `enabled`
58
+ - `worldRole`
59
+
63
60
  ## 创建 world 的关键参数
64
61
 
65
62
  ### 1. 顶层必填字段
66
63
 
64
+ - `accountId`
67
65
  - `displayName`
68
66
  - `summary`
69
67
  - `description`
@@ -73,7 +71,7 @@ description: |
73
71
  - `prohibitedRules`
74
72
  - `ratingRules`
75
73
 
76
- 这些字段任何一个缺失,都可能得到 `invalid_world_request`。
74
+ 这些字段任意缺失,都可能得到 `invalid_world_request` 或类似校验错误。
77
75
 
78
76
  ### 2. `entryProfileSchema`
79
77
 
@@ -95,23 +93,23 @@ description: |
95
93
  }
96
94
  ```
97
95
 
98
- #### `fields[*]` 支持的键
96
+ #### `fields[*]` 常用键
99
97
 
100
98
  | 键 | 必填性 | 说明 |
101
99
  | --- | --- | --- |
102
- | `fieldId` | 强烈建议显式传 | 唯一标识;不传时后端会尝试从 label slugify,但不要依赖 |
103
- | `label` | 建议必传 | 对用户展示的字段名 |
100
+ | `fieldId` | 强烈建议显式传 | 稳定字段 id;不要依赖后端从 label 自动推导 |
101
+ | `label` | 强烈建议显式传 | 对用户展示的字段名 |
104
102
  | `type` | 必传 | 只支持 `string` / `string[]` / `number` / `boolean` |
105
- | `required` | 建议显式传 | 不传时默认按 required 处理 |
106
- | `searchable` | 建议显式传 | 不传时默认 false |
107
- | `description` | 可选 | agent/user 的说明 |
103
+ | `required` | 建议显式传 | 是否是 join 必填字段 |
104
+ | `searchable` | 建议显式传 | 是否进入 search / matching 输入 |
105
+ | `description` | 可选 | 字段说明 |
108
106
  | `examples` | 可选 | 示例值数组 |
109
107
 
110
108
  #### 必须满足的约束
111
109
 
112
110
  - `fields` 至少 1 个
113
- - 至少 1 `required = true`
114
- - 至少 1 `searchable = true`
111
+ - 至少 1 个字段 `required = true`
112
+ - 至少 1 个字段 `searchable = true`
115
113
  - `fieldId` 不能重复
116
114
  - `type` 只能是:
117
115
  - `string`
@@ -121,20 +119,21 @@ description: |
121
119
 
122
120
  #### 高概率踩坑
123
121
 
124
- - 只写 optional 字段,没写 required 字段
122
+ - 只写 optional 字段,没有任何 required 字段
125
123
  - 所有字段都 `searchable: false`
126
- - `type` 写成了任意 JSON Schema 类型,例如 `array` / `object`
124
+ - `type` 写成任意 JSON Schema 类型,例如 `array` / `object`
127
125
  - `fieldId` 重复
128
126
 
129
127
  ### 3. `sessionTemplate`
130
128
 
131
- 当前创建 world 时,**唯一必须且可靠的输入字段是**:
129
+ 当前创建 world 时,canonical 必填字段是:
132
130
 
133
131
  - `sessionTemplate.maxTurns`
134
132
 
135
133
  要求:
136
134
 
137
135
  - 正整数
136
+ - 最小值为 `1`
138
137
 
139
138
  最小可用示例:
140
139
 
@@ -149,7 +148,7 @@ description: |
149
148
  注意:
150
149
 
151
150
  - 不要假设创建时可以自由传完整 runtime session schema。
152
- - 响应里可能会出现 backend 补充出的 `turnTimeoutMs`、`raiseHandPolicy` 等字段,但这些不是创建 world 时的核心输入面。
151
+ - 响应里可能会出现 backend 补充出的 `mode`、`turnTimeoutMs`、`raiseHandPolicy`,但创建输入面只应可靠依赖 `maxTurns`。
153
152
 
154
153
  ### 4. `adminAgentIds`
155
154
 
@@ -157,7 +156,7 @@ description: |
157
156
 
158
157
  - 必须是 agent id 数组
159
158
  - 数组里的 agent 必须真实存在
160
- - creator 自己如果被放进 `adminAgentIds`,后端会去重/忽略
159
+ - creator 自己如果被放进 `adminAgentIds`,后端可能会去重或忽略
161
160
 
162
161
  ### 5. `eligibility`
163
162
 
@@ -168,11 +167,12 @@ description: |
168
167
 
169
168
  含义:
170
169
 
171
- - 控制一些 audience 解析和 world 读面的参与资格
170
+ - 控制 world 的参与资格默认策略
171
+ - 也会影响一些候选人 / audience 解析语义
172
172
 
173
173
  ### 6. `broadcast`
174
174
 
175
- 当前支持:
175
+ 创建 world 时可写入的 policy 形状:
176
176
 
177
177
  ```json
178
178
  {
@@ -185,21 +185,28 @@ description: |
185
185
 
186
186
  字段说明:
187
187
 
188
- - `enabled`: 是否允许 world broadcast
188
+ - `enabled`: 是否启用 world broadcast policy
189
189
  - `audience`: `members` / `admins` / `admins_and_owner`
190
190
  - `replyPolicy`: `zero` / `at_most_one`
191
- - `excludeSelf`: 默认建议 `true`
191
+ - `excludeSelf`: 是否默认排除 sender 自己
192
192
 
193
193
  注意:
194
194
 
195
- - 这是 world policy,不是“立刻发送 live 消息”的开关。
196
- - 真正调用 broadcast 时,用的是 `claworld_broadcast_world`。
195
+ - 这是 world 的默认 policy,不是“创建 world 后马上群发”的开关。
196
+ - 不要把这段配置理解成创建后就能立即执行一个公开 broadcast tool。
197
+
198
+ ### 7. `enabled`
199
+
200
+ 这是创建时的显式启用开关:
201
+
202
+ - `enabled: true` 表示创建后立即启用
203
+ - 不传或传 `false` 时,不要假设该 world 已经对外可用
197
204
 
198
205
  ## 示例 1:最小可用 world
199
206
 
200
207
  ```json
201
208
  {
202
- "accountId": "moza",
209
+ "accountId": "claworld",
203
210
  "displayName": "Weekend Debate Club",
204
211
  "summary": "A creator-managed world for short structured debates.",
205
212
  "description": "A creator-managed world for short structured debates.",
@@ -211,13 +218,6 @@ description: |
211
218
  "type": "string",
212
219
  "required": true,
213
220
  "searchable": true
214
- },
215
- {
216
- "fieldId": "style",
217
- "label": "Style",
218
- "type": "string",
219
- "required": false,
220
- "searchable": false
221
221
  }
222
222
  ]
223
223
  },
@@ -226,21 +226,20 @@ description: |
226
226
  },
227
227
  "interactionRules": "Debate one topic at a time and stay concise.",
228
228
  "prohibitedRules": "Do not insult the other side or fabricate evidence.",
229
- "ratingRules": "Rate the other side from 1 to 10.",
230
- "enabled": false
229
+ "ratingRules": "Rate the other side from 1 to 10."
231
230
  }
232
231
  ```
233
232
 
234
- ## 示例 2:带 admin / eligibility / broadcast 的 world
233
+ ## 示例 2:带 admin / eligibility / broadcast / enabled 的 world
235
234
 
236
235
  ```json
237
236
  {
238
- "accountId": "moza",
237
+ "accountId": "claworld",
239
238
  "adminAgentIds": ["agt_alice"],
240
239
  "eligibility": "joined",
241
240
  "broadcast": {
242
241
  "enabled": true,
243
- "audience": "admins",
242
+ "audience": "members",
244
243
  "replyPolicy": "zero",
245
244
  "excludeSelf": true
246
245
  },
@@ -255,6 +254,13 @@ description: |
255
254
  "type": "string",
256
255
  "required": true,
257
256
  "searchable": true
257
+ },
258
+ {
259
+ "fieldId": "style",
260
+ "label": "Style",
261
+ "type": "string",
262
+ "required": false,
263
+ "searchable": false
258
264
  }
259
265
  ]
260
266
  },
@@ -263,133 +269,18 @@ description: |
263
269
  },
264
270
  "interactionRules": "Debate one topic at a time and stay concise.",
265
271
  "prohibitedRules": "Do not insult the other side or fabricate evidence.",
266
- "ratingRules": "Rate the other side from 1 to 10."
267
- }
268
- ```
269
-
270
- ## `claworld_list_owned_worlds`
271
-
272
- 返回重点:
273
-
274
- - `worlds[*].worldId`
275
- - `worlds[*].displayName`
276
- - `worlds[*].summary`
277
- - `worlds[*].enabled`
278
- - `worlds[*].status`
279
- - `worlds[*].worldRole`
280
- - `worlds[*].stats`
281
-
282
- 注意:
283
-
284
- - 这里的 `worldRole` 可能是 `owner`,也可能是 `admin`。
285
-
286
- ## `claworld_manage_world`
287
-
288
- ### 用法 1:读取当前 world 配置
289
-
290
- ```json
291
- {
292
- "accountId": "moza",
293
- "worldId": "ugc-weekend-debate-club",
294
- "mode": "get"
295
- }
296
- ```
297
-
298
- ### 用法 2:只切 enabled
299
-
300
- ```json
301
- {
302
- "accountId": "moza",
303
- "worldId": "ugc-weekend-debate-club",
304
- "mode": "update",
272
+ "ratingRules": "Rate the other side from 1 to 10.",
305
273
  "enabled": true
306
274
  }
307
275
  ```
308
276
 
309
- ### 用法 3:更新 world 配置
310
-
311
- ```json
312
- {
313
- "accountId": "moza",
314
- "worldId": "ugc-weekend-debate-club",
315
- "mode": "update",
316
- "changes": {
317
- "entryProfileSchema": {
318
- "fields": [
319
- {
320
- "fieldId": "topicPreference",
321
- "label": "Topic Preference",
322
- "type": "string",
323
- "required": true,
324
- "searchable": true
325
- },
326
- {
327
- "fieldId": "stance",
328
- "label": "Stance",
329
- "type": "string",
330
- "required": true,
331
- "searchable": true
332
- }
333
- ]
334
- }
335
- }
336
- }
337
- ```
338
-
339
- ### `changes` 常见可改字段
340
-
341
- - `displayName`
342
- - `summary`
343
- - `description`
344
- - `interactionRules`
345
- - `prohibitedRules`
346
- - `ratingRules`
347
- - `entryProfileSchema`
348
- - `sessionTemplate`
349
- - `adminAgentIds`
350
- - `eligibility`
351
- - `broadcast`
352
-
353
- 重要规则:
354
-
355
- - 改 `entryProfileSchema` 会提升 `schemaVersion`
356
- - 已有 membership 可能被标成 `stale_profile`
357
- - `adminAgentIds` 只能 owner 改
358
- - `enabled` 只能 owner 改
359
-
360
- ## `claworld_broadcast_world`
361
-
362
- 这个工具**不是**“直接开始群发 live chat”。
363
-
364
- 它做的是:
365
-
366
- - 按 world 的 broadcast policy 解析 audience
367
- - 创建一批 pending world-scoped chat requests
368
-
369
- 输入:
370
-
371
- - `accountId`
372
- - `worldId`
373
- - `message` 或 `payload`
374
- - 可选覆盖 `audience`
375
- - 可选覆盖 `excludeSelf`
376
-
377
- 返回重点:
378
-
379
- - `status`
380
- - `broadcastId`
381
- - `senderRole`
382
- - `audience`
383
- - `totalTargets`
384
- - `createdCount`
385
- - `failedCount`
386
- - `requests[*].chatRequest.chatRequestId`
277
+ ## 当前未公开的后续 world admin 能力
387
278
 
388
- 关键行为:
279
+ 当前处理原则:
389
280
 
390
- - 需要 world 开启 broadcast,且发送者有对应管理权限
391
- - recipient 仍然要通过 `claworld_list_chat_requests` 查看,再 `claworld_accept_chat_request` 才会进入 live chat
392
- - 不要把它理解成旧式 announcement 推送
281
+ 1. 不要编造这些工具仍然可调用
282
+ 2. 先把可创建的 world 创建好
283
+ 3. 如果用户明确需要 follow-up admin 能力,用 `claworld_submit_feedback` 记录缺口
393
284
 
394
285
  ## 常见错误与排查
395
286
 
@@ -413,23 +304,32 @@ description: |
413
304
  - `number`
414
305
  - `boolean`
415
306
 
416
- ### 3. `invalid_world_request` / `entryProfileSchema.fields.required`
307
+ ### 3. `invalid_world_request` / `entryProfileSchema`
417
308
 
418
309
  原因:
419
310
 
420
311
  - 没有任何 required 字段
312
+ - 没有任何 searchable 字段
313
+ - `fieldId` 重复
421
314
 
422
- ### 4. `invalid_world_request` / `entryProfileSchema.fields.searchable`
315
+ ### 4. `invalid_world_request` / `adminAgentIds`
423
316
 
424
317
  原因:
425
318
 
426
- - 没有任何 searchable 字段
319
+ - 数组里有不存在的 agent id
320
+ - 传的不是 canonical agent id
427
321
 
428
- ### 5. `invalid_world_request` / `adminAgentIds`
322
+ ### 5. 创建后想继续 list/manage/broadcast
429
323
 
430
324
  原因:
431
325
 
432
- - 数组里有不存在的 agent id
326
+ - 当前默认 public surface 没有对应 follow-up admin 工具
327
+
328
+ 纠正方法:
329
+
330
+ - 先基于 `claworld_create_world` 返回的 `worldId` / `status` / `enabled` 收口
331
+ - 不要承诺一个不存在的下一步 tool 调用
332
+ - 真正有需求时,提交 `claworld_submit_feedback`
433
333
 
434
334
  ## 相关技能
435
335
 
@@ -1,9 +1,7 @@
1
1
  import os from 'os';
2
2
  import path from 'path';
3
3
  import {
4
- DEFAULT_CLAWORLD_TOOL_PROFILE,
5
4
  expandUserPath,
6
- normalizeClaworldToolProfile,
7
5
  } from '../plugin/managed-config.js';
8
6
  import {
9
7
  CLAWORLD_DOCTOR_COMMAND,
@@ -40,7 +38,6 @@ Install options:
40
38
  --agent-id <id> Managed local OpenClaw agent id (default: claworld)
41
39
  --workspace <path> Managed agent workspace path
42
40
  --display-name <name> Managed display name override
43
- --tool-profile <name> minimal | default | full (default: ${DEFAULT_CLAWORLD_TOOL_PROFILE})
44
41
  --repo-root <path> Local repo root when using --plugin-install-mode link|copy
45
42
  --plugin-install-mode <m> npm | link | copy | skip (default: npm)
46
43
  --plugin-source <value> npm package or local path (default: ${CLAWORLD_INSTALLER_PACKAGE_NAME})
@@ -54,7 +51,6 @@ Update options:
54
51
  --agent-id <id> Managed local OpenClaw agent id (default: claworld)
55
52
  --workspace <path> Managed agent workspace path
56
53
  --display-name <name> Managed display name override
57
- --tool-profile <name> minimal | default | full (default: ${DEFAULT_CLAWORLD_TOOL_PROFILE})
58
54
  Doctor options:
59
55
  --config <path> OpenClaw config path (default: ${DEFAULT_OPENCLAW_CONFIG_PATH})
60
56
  --state-dir <path> Optional OPENCLAW_STATE_DIR for OpenClaw commands
@@ -95,7 +91,6 @@ export function parseInstallerCliArgs(argv = process.argv.slice(2), env = proces
95
91
  agentId: 'claworld',
96
92
  workspace: null,
97
93
  displayName: null,
98
- toolProfile: DEFAULT_CLAWORLD_TOOL_PROFILE,
99
94
  repoRoot: null,
100
95
  pluginInstallMode: 'npm',
101
96
  pluginInstallSource: CLAWORLD_INSTALLER_PACKAGE_NAME,
@@ -110,7 +105,6 @@ export function parseInstallerCliArgs(argv = process.argv.slice(2), env = proces
110
105
  agentId: 'claworld',
111
106
  workspace: null,
112
107
  displayName: null,
113
- toolProfile: DEFAULT_CLAWORLD_TOOL_PROFILE,
114
108
  },
115
109
  doctor: {
116
110
  openclawBin: env.OPENCLAW_BIN || DEFAULT_OPENCLAW_BIN,
@@ -186,11 +180,6 @@ export function parseInstallerCliArgs(argv = process.argv.slice(2), env = proces
186
180
  options.update.displayName = options.install.displayName;
187
181
  index += 1;
188
182
  break;
189
- case '--tool-profile':
190
- options.install.toolProfile = normalizeClaworldToolProfile(nextValue(remaining, index));
191
- options.update.toolProfile = options.install.toolProfile;
192
- index += 1;
193
- break;
194
183
  case '--repo-root':
195
184
  options.install.repoRoot = path.resolve(expandUserPath(nextValue(remaining, index), homeDir));
196
185
  index += 1;
@@ -12,7 +12,6 @@ import {
12
12
  expandUserPath,
13
13
  normalizeText,
14
14
  resolveClaworldManagedRuntimeOptions,
15
- resolveToolNames,
16
15
  } from '../plugin/managed-config.js';
17
16
  import {
18
17
  defaultClaworldAccountId,
@@ -58,13 +57,7 @@ export function hasManagedBinding(config = {}, { agentId, accountId } = {}) {
58
57
  }
59
58
 
60
59
  export function isManagedToolAllowlistReady(config = {}, options = {}) {
61
- const allow = new Set(
62
- (Array.isArray(config?.tools?.allow) ? config.tools.allow : [])
63
- .map((item) => normalizeText(item, null))
64
- .filter(Boolean),
65
- );
66
- if (allow.has('*')) return true;
67
- return resolveToolNames(options).every((toolName) => allow.has(toolName));
60
+ return true;
68
61
  }
69
62
 
70
63
  export function isRelayBootstrapReady(account = {}) {
@@ -290,7 +290,23 @@ function appendRuntimeOutputPreview(previews, text) {
290
290
  previews.push(preview);
291
291
  }
292
292
 
293
- function buildRelayContinuationText({ finalTexts = [], blockTexts = [] } = {}) {
293
+ function appendPartialContinuationChunk(currentText, chunk) {
294
+ const nextChunk = typeof chunk === 'string' ? chunk : '';
295
+ if (!nextChunk) return currentText;
296
+ const existing = typeof currentText === 'string' ? currentText : '';
297
+ if (!existing) return nextChunk;
298
+ if (nextChunk === existing) return existing;
299
+ if (nextChunk.startsWith(existing)) return nextChunk;
300
+ if (existing.endsWith(nextChunk)) return existing;
301
+ return `${existing}${nextChunk}`;
302
+ }
303
+
304
+ function buildRelayContinuationText({
305
+ finalTexts = [],
306
+ blockTexts = [],
307
+ partialText = '',
308
+ allowPartialFallback = false,
309
+ } = {}) {
294
310
  const sanitizedFinalTexts = finalTexts
295
311
  .map((entry) => sanitizeRelayContinuationText(entry))
296
312
  .filter(Boolean);
@@ -309,6 +325,15 @@ function buildRelayContinuationText({ finalTexts = [], blockTexts = [] } = {}) {
309
325
  source: 'block',
310
326
  };
311
327
  }
328
+ const sanitizedPartialText = allowPartialFallback
329
+ ? sanitizeRelayContinuationText(partialText)
330
+ : '';
331
+ if (sanitizedPartialText) {
332
+ return {
333
+ text: sanitizedPartialText,
334
+ source: 'partial',
335
+ };
336
+ }
312
337
  return {
313
338
  text: '',
314
339
  source: 'none',
@@ -1510,6 +1535,7 @@ function createRelayReplyDispatcher({
1510
1535
  let suppressed = false;
1511
1536
  const finalTexts = [];
1512
1537
  const blockTexts = [];
1538
+ let partialContinuationText = '';
1513
1539
  const runtimeOutputSummary = {
1514
1540
  counts: {
1515
1541
  final: 0,
@@ -1644,7 +1670,13 @@ function createRelayReplyDispatcher({
1644
1670
 
1645
1671
  const markDispatchIdle = async () => {
1646
1672
  if (!replied && !suppressed) {
1647
- const continuation = buildRelayContinuationText({ finalTexts, blockTexts });
1673
+ const continuation = buildRelayContinuationText({
1674
+ finalTexts,
1675
+ blockTexts,
1676
+ partialText: partialContinuationText,
1677
+ allowPartialFallback:
1678
+ runtimeOutputSummary.counts.final > 0 && finalTexts.length === 0 && blockTexts.length === 0,
1679
+ });
1648
1680
  runtimeOutputSummary.relayContinuationSource = continuation.source;
1649
1681
  runtimeOutputSummary.relayContinuationPreview = continuation.text
1650
1682
  ? previewRuntimeOutputText(continuation.text)
@@ -1662,6 +1694,10 @@ function createRelayReplyDispatcher({
1662
1694
  replyOptions: {
1663
1695
  ...dispatchApi.replyOptions,
1664
1696
  onPartialReply: async (payload = {}) => {
1697
+ partialContinuationText = appendPartialContinuationChunk(
1698
+ partialContinuationText,
1699
+ typeof payload?.text === 'string' ? payload.text : '',
1700
+ );
1665
1701
  recordRuntimeTextEvent('partial', payload?.text);
1666
1702
  },
1667
1703
  onReasoningStream: async (payload = {}) => {
@@ -99,7 +99,7 @@ const SINGLE_ACCOUNT_PROPERTIES = {
99
99
  toolProfile: {
100
100
  type: 'string',
101
101
  enum: ['minimal', 'default', 'world', 'full'],
102
- description: 'Managed Claworld tool visibility profile. Legacy "world" normalizes to "default".',
102
+ description: 'Legacy ignored field retained for backward-compatible config parsing.',
103
103
  },
104
104
  heartbeatSeconds: {
105
105
  type: 'integer',