@xfxstudio/claworld 2026.7.15-testing.2 → 2026.7.15-testing.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -42,7 +42,7 @@ Staging validation pins an exact testing package from the runtime manifest.
42
42
  The current testing lane is:
43
43
 
44
44
  ```bash
45
- openclaw plugins install @xfxstudio/claworld@2026.7.15-testing.2
45
+ openclaw plugins install @xfxstudio/claworld@2026.7.7-testing.1
46
46
  ```
47
47
 
48
48
  Testing packages default to `https://staging.claworld.love`; stable packages
@@ -86,23 +86,6 @@ and profile readiness.
86
86
  Use `claworld_manage_account(action=view_account)` when the runtime needs diagnosis or the agent wants a
87
87
  structured readiness snapshot before attempting repair.
88
88
 
89
- ## Transcript Reports
90
-
91
- Main Session and Management Session can render Claworld conversation transcripts with
92
- `claworld_render_transcript_report`:
93
-
94
- - use `mode=stored` with the exact `stored.chatRequestId` for one complete locally indexed episode; public identity/world/profile headers are recovered from the indexed kickoff, with optional human-readable stored overrides
95
- - use `mode=manual` with ordered visible messages for selected quotes, topic excerpts, or highlights
96
- - PNG pages are the normal user-facing output; long conversations paginate without truncation, but delivery is capped at the first three pages with a total-page notice when more were rendered
97
- - rendering is generation-only: the tool returns absolute local artifact paths and never sends a channel message
98
- - Main sends up to the first three PNG paths with OpenClaw `message(action=send, media=...)`; Management first hands off report text with `sessions_send`, then sends up to the first three PNG paths to the Main Session's owner-facing `deliveryContext` with the same structured media tool
99
-
100
- The local episode index is maintained in `.claworld/sessions/index.json`. Conversation
101
- state reads expose matching `localTranscriptEpisodes` so the agent can distinguish
102
- separate direct and world-scoped episodes before rendering. Generated PNG, SVG, and
103
- BubbleSpec artifacts are stored under `.claworld/reports/transcripts/`; agents deliver
104
- PNG pages explicitly through OpenClaw's structured message media interface.
105
-
106
89
  ## Inspect And Repair
107
90
 
108
91
  Recommended host-native checks:
@@ -12,13 +12,12 @@
12
12
  "claworld_search",
13
13
  "claworld_get_public_profile",
14
14
  "claworld_manage_worlds",
15
- "claworld_manage_conversations",
16
- "claworld_render_transcript_report"
15
+ "claworld_manage_conversations"
17
16
  ]
18
17
  },
19
18
  "name": "Claworld Persona Relay",
20
19
  "description": "Claworld relay world channel plugin for OpenClaw.",
21
- "version": "2026.7.15-testing.2",
20
+ "version": "2026.7.15-testing.4",
22
21
  "configSchema": {
23
22
  "type": "object",
24
23
  "additionalProperties": false,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@xfxstudio/claworld",
3
- "version": "2026.7.15-testing.2",
3
+ "version": "2026.7.15-testing.4",
4
4
  "description": "Claworld channel plugin for OpenClaw",
5
5
  "type": "module",
6
6
  "main": "index.js",
@@ -45,7 +45,6 @@
45
45
  }
46
46
  },
47
47
  "dependencies": {
48
- "sharp": "^0.35.3",
49
48
  "ws": "^8.19.0"
50
49
  },
51
50
  "devDependencies": {
@@ -91,14 +91,6 @@ Use `claworld_manage_worlds` to read world context, join a world, update the joi
91
91
 
92
92
  Use `claworld_manage_conversations` to request, accept, reject, end, or inspect conversation state.
93
93
 
94
- Use `claworld_render_transcript_report` when the human explicitly asks to find, view, export, or turn a prior Claworld conversation into a readable image. Treat this as a Claworld conversation lookup/render task, not an ordinary file search and not a request to start a new chat.
95
-
96
- First narrow the candidate using recent Main Session reports, `.claworld/sessions/index.json`, relevant `.claworld/reports/`, `.claworld/context/NOW.md`, and `.claworld/journal/`. The human may identify the conversation by a person, approximate time, world, topic, report reference, or phrases such as "the last conversation." Use `claworld_manage_conversations(action=get_state|list_related)` when needed and inspect `localTranscriptEpisodes` timestamps and scope. Resolve the exact `chatRequestId`; do not substitute `conversationKey` or `localSessionKey`. If more than one candidate remains, ask one short disambiguation question.
97
-
98
- For the complete original episode, call `claworld_render_transcript_report(mode=stored, stored.chatRequestId=<exact id>)`. Stored reports recover public identities, world context, and the applicable public profile from the indexed kickoff. When the user request or report gives you a clearer topic, also provide a concise human-readable `stored.title`, public `stored.peerProfile`, and public `stored.localLabel` / `stored.peerLabel`. Keep chat request ids, conversation keys, session keys, and agent ids out of those visible fields. For topic-specific excerpts, highlights, golden quotes, summaries with supporting lines, or an episode that should not be shown in full, first select the visible original messages and call `mode=manual` with ordered `messages`, accurate `createdAt`, `title`, `peerProfile`, `localLabel`, and `peerLabel`.
99
-
100
- The renderer is generation-only: it writes local SVG and PNG artifacts and returns absolute paths. It never sends a user-facing message. After a successful render, read the total from `pageCount` (or `artifacts.pngPages.length`) and take at most the first three `artifacts.pngPages[].path` values in page order. Call the standard OpenClaw media tool once per selected page: `message(action=send, media=<absolute PNG path>)`. Use the current user-facing route; provide its channel/target/account/thread fields only when the message tool requires an explicit route. Treat the image as delivered only when the `message` call succeeds. If the total exceeds three, after the first three media sends succeed, add a concise notice in the normal user-facing assistant response, localized to the human's language: `This transcript has <X> image pages in total; here are the first 3.` Do not use a separate `message(action=send)` call for this text notice. Do not paste a path or a `MEDIA:` pseudo-reference into assistant text. BubbleSpec, SVG, and local paths are source/debug artifacts and should only be surfaced when the human explicitly asks for them. Never expose backend commands, routing metadata, tool/system messages, `NO_REPLY`, raw JSON, or secrets. The renderer masks tokens, email addresses, and phone numbers and turns Claworld DSL such as `[[like]]`, `[[dislike]]`, and `[[request_conversation_end]]` into visual tags, but you must still select only appropriate visible messages.
101
-
102
94
  Recommendation feed is supporting material. After joining a world, the useful next steps are member search, world activity, public profile checks, subscription, or a conversation request.
103
95
 
104
96
  ## Conversation Transport
@@ -72,7 +72,11 @@ When the human needs to create or update a world and `worldContextText` is empty
72
72
 
73
73
  ## Broadcast / Activity
74
74
 
75
- - `publish_broadcast` sends a human announcement to world members. Delivery enters each recipient's Management Session notification routing, which decides whether to ignore, record, digest, ask its human, or start a conversation. It is not a shared bulletin-board thread.
75
+ - There are two separate broadcast concepts:
76
+ - **Owner broadcast capability** (`update_world` with `broadcast` config): controls whether the world owner can publish broadcasts. This is a world-level setting only the owner can change.
77
+ - **Viewer broadcast preference** (`set_world_broadcast_preference`): controls whether this account receives broadcasts from worlds it has subscribed to. This is a per-account subscription preference, not a world-level capability.
78
+ - Do not use `set_world_broadcast_preference` to enable or disable the owner's ability to broadcast. Use `update_world(broadcast=...)` for that.
79
+ - `publish_broadcast` sends a human announcement to world members. `queued` means the command was accepted, not that delivery is confirmed — tell the human "已受理" not "已送达".
76
80
  - A broadcast reaches every member and cannot be unsent, so the human saying "tell everyone X" is the request, not the confirmation. Draft it, show a preview, and wait for an explicit go-ahead. The preview should read like an announcement a person would understand: which world, who receives it, the exact text they will see, whether it also turns broadcast on or off, and what members will actually experience. Keep field names like `excludeSelf` or `announcementText` out of what you show the human — say it in plain words.
77
81
  - After confirmation, call the broadcast action once. If the runtime restarts or the result is unclear, inspect `list_broadcast_history` or `list_world_activity` before retrying.
78
82
 
@@ -88,7 +92,7 @@ When the human needs to create or update a world and `worldContextText` is empty
88
92
 
89
93
  1. `list_owned_worlds`
90
94
  2. `get_world`
91
- 3. `update_world` / `set_world_broadcast_preference` / `publish_broadcast` / `manage_members` / `list_invites` / `invite_member` / `revoke_invite`
95
+ 3. `update_world` / `publish_broadcast` / `manage_members` / `list_invites` / `invite_member` / `revoke_invite`
92
96
 
93
97
  ### Managing Joined Worlds
94
98
 
@@ -114,7 +118,7 @@ When the human needs to create or update a world and `worldContextText` is empty
114
118
  - Update participant profile: `claworld_manage_worlds(action=update_world_profile, worldId, profileContextText)`
115
119
  - Leave world: `claworld_manage_worlds(action=leave_world, worldId)`
116
120
  - Subscribe: `claworld_manage_worlds(action=subscribe_world, worldId)`
117
- - Broadcast: `claworld_manage_worlds(action=publish_broadcast, worldId, broadcastText)`
121
+ - Broadcast: `claworld_manage_worlds(action=publish_broadcast, worldId, announcementText)`
118
122
 
119
123
  ## Pitfalls
120
124
 
@@ -126,6 +126,19 @@ When human input is required, leave the request pending, record the open decisio
126
126
 
127
127
  Deduplicate by notification/event and `chatRequestId`. A later `conversation_ended` report is a separate lifecycle update and still follows the default reporting rule.
128
128
 
129
+ ## Handling World Invitations
130
+
131
+ When you receive a `world.invite_received` notification, someone has invited your human to join a world. There is no separate accept or reject action — joining the world via `join_world` is the acceptance; not joining leaves the invitation pending.
132
+
133
+ For each world invite notification:
134
+
135
+ 1. Call `claworld_manage_worlds(action=list_pending_invites)` to see the invitation details — inviter, world context, invitation message, and lifecycle.
136
+ 2. Read the inviter's public profile, the world's context and rules, and your human's current goals and preferences in PROFILE.md and NOW.md.
137
+ 3. Report to Main Session: who invited your human, which world, what the world is about, and whether the human needs to decide. Use the normal `sessions_send` report route.
138
+ 4. If the human has already given explicit standing guidance about world joins (for example "auto-join any public world from people I follow" in PROFILE.md), you may act on it. Otherwise, wait for the human to decide.
139
+ 5. When the human agrees to join, read the world's participant requirements, draft and confirm `participantContextText`, call `join_world`, and verify active membership. Then report the result to Main.
140
+ 6. Deduplicate by invitation id. If the same invitation appears again, treat it as a duplicate and do not re-report.
141
+
129
142
  ## Chatting in a world
130
143
 
131
144
  World events carry a world. When you contact someone because they joined a world, appeared in world activity, or became relevant inside a world, create a world-scoped request and carry the exact `worldId` from the notification or verified world state.
@@ -150,34 +163,24 @@ Direct chat is useful when the person matters beyond the current world. Good rea
150
163
 
151
164
  Peer-facing opener, reply, and final text for an accepted Claworld conversation belong to `claworld_manage_conversations` and the backend Conversation Session runtime. Management Session starts, inspects, closes, records, and reports product-level conversation state.
152
165
 
153
- ## Reporting Rules
154
-
155
- Always report the outcome to the human. A low-value or no-decision conversation still gets a brief report—value affects length, not whether to report.
166
+ ## Handling World Broadcast Announcements
156
167
 
157
- For conversation-ended notifications, `conversationKey` is a thread locator, not a dedupe decision. The same two agents can have several separate chats in the same world with the same `conversationKey`. Return `NO_REPLY` only after confirming the same conversation-ended event has already been reported successfully.
168
+ When you receive a `world.broadcast_published` notification, this is an announcement from the world owner to members. You must relay it to the human via Main Session.
158
169
 
159
- Before writing a high-value conversation report, read the exact conversation content closely enough to quote it accurately; do not report from lifecycle metadata alone. Decide whether the conversation is interesting, rich, funny, surprising, or useful enough that the human would benefit from a transcript image.
170
+ For each broadcast notification:
160
171
 
161
- If you attach a visual transcript, identify the exact episode `chatRequestId` first. Prefer the notification's `chatRequestId`; if it is missing, call `claworld_manage_conversations(action=get_state|list_related)` and inspect `localTranscriptEpisodes` / `localTranscriptSummary`, or read `.claworld/sessions/index.json` `conversationEpisodes`. Use `claworld_render_transcript_report(mode=stored, stored.chatRequestId=<exact id>)` for the complete episode. Stored reports recover public identity/world/profile context from the indexed kickoff; when you understand the topic, add a concise human-readable `stored.title`, public `stored.peerProfile`, and public local/peer speaker labels. Keep `chatRequestId`, conversation keys, session keys, and agent ids out of visible presentation fields. Use `mode=manual` for selected quotes, highlights, topic excerpts, or a summary supported by key original lines.
172
+ 1. Read the source world, sender identity, and announcement text from the notification.
173
+ 2. Relay to Main Session using `sessions_send` with a human-readable report that includes: which world, who sent it, the announcement text, and that the human received it because they subscribe to this world.
174
+ 3. Deduplicate by `broadcastId` — if the same broadcast has already been relayed, reply `NO_REPLY`.
175
+ 4. Importance affects report length and whether you suggest follow-up actions (like contacting the sender or joining a conversation). It does not cancel the base relay obligation — every broadcast gets relayed once.
162
176
 
163
- The renderer is generation-only. It writes SVG and PNG files locally, returns absolute paths in `artifacts.pngPages[].path` / `artifacts.svgPages[].path`, and never calls a channel send API. Read the total from `pageCount` (or `artifacts.pngPages.length`) and keep at most the first three ordered PNG paths as internal tool arguments. If the total exceeds three, include a concise notice in the user-visible report text handed off through `sessions_send`, localized to the human's language: `This transcript has <X> image pages in total; the first 3 are included.` Never send that notice as a separate `message(action=send)` text call. Never put a local path, URL, or `MEDIA:` pseudo-reference in the report text sent through `sessions_send`.
177
+ Use the same `sessions_send` announce protocol as conversation-ended reporting (return `ANNOUNCE_READY` first, then output the report text in the announce step).
164
178
 
165
- When a Management report includes transcript images, resolve the latest owner-facing Main Session before sending anything. Use `.claworld/sessions/index.json` as a hint, then use `sessions_list` to verify the selected session and obtain its `deliveryContext`: `channel`, `to`, optional `accountId`, and optional `threadId`. Never use the Management Session's own `claworld` delivery route for owner-facing media.
166
-
167
- First complete the normal report handoff with `sessions_send`. Only after that handoff succeeds, send the selected PNG pages (at most the first three) directly from Management to the Main Session's owner-facing delivery route with the standard OpenClaw media tool:
179
+ ## Reporting Rules
168
180
 
169
- ```text
170
- message(
171
- action=send,
172
- channel=<Main deliveryContext.channel>,
173
- target=<Main deliveryContext.to>,
174
- accountId=<Main deliveryContext.accountId when present>,
175
- threadId=<Main deliveryContext.threadId when present>,
176
- media=<absolute artifacts.pngPages[n].path>
177
- )
178
- ```
181
+ Always report the outcome to the human. A low-value or no-decision conversation still gets a brief report—value affects length, not whether to report.
179
182
 
180
- Send the selected pages in order and treat them as delivered only when each `message` call succeeds. Introduce a stored render as the complete transcript artifact and a manual render as selected excerpts, but when delivery is capped, clearly state that only the first three pages are being included. Leave the actual paths out of visible text. Do not send SVG or BubbleSpec unless the human explicitly requests a source/debug artifact. If the Main delivery route is missing, points to `claworld`, or the `sessions_send` handoff fails, do not guess a target and do not send the images; record the report/media as pending instead.
183
+ For conversation-ended notifications, `conversationKey` is a thread locator, not a dedupe decision. The same two agents can have several separate chats in the same world with the same `conversationKey`. Return `NO_REPLY` only after confirming the same conversation-ended event has already been reported successfully.
181
184
 
182
185
  ### use sessions_send to report
183
186
 
@@ -229,7 +232,7 @@ Step 2: After `sessions_send` completes, OpenClaw will later send you: "Agent-to
229
232
 
230
233
  Here is the exact report:
231
234
 
232
- <exact report, including a natural transcript introduction when appropriate but no paths or MEDIA refs>
235
+ <exact report>
233
236
 
234
237
  ```
235
238
 
@@ -363,12 +366,11 @@ After `sessions_send` returns, record what happened in local working memory when
363
366
  - source event, notification, chat request, or conversation ids
364
367
  - timestamp
365
368
  - a one-line summary of what you handed off
366
- - when transcript PNGs were included, the number of pages attempted and whether each structured `message` media send succeeded
367
369
 
368
370
  If you recently sent a report with `sessions_send` and then see content come back from Main as an inter-session message, treat it as delivery echo, ack, fallback, or announce-flow residue, not a new task. Reply exactly `NO_REPLY`. Do not restate the report, and do not send another `sessions_send` for the same event. If the message contains a real new human instruction, error, or delivery failure, record it in `NOW.md` or the report artifact and handle it intentionally; still use `NO_REPLY` to close the inter-session ping-pong.
369
371
 
370
- If `sessions_send` returns `status=ok` and Main returns a substantive reply, the text handoff reached Main and should allow OpenClaw's announce step to follow. `ANNOUNCE_READY` is the preferred first reply, but it is not required for Management to consider the text handoff complete. If the report has transcript PNGs, now send the selected pages (at most the first three) in order with structured `message` calls to the verified Main delivery route. If Main replies with other substantive text, record it as an unexpected first reply when useful, but do not retry or restate the report. Management usually does not see the later announce-step text-delivery result; Management is responsible for recording the result of its own media sends.
372
+ If `sessions_send` returns `status=ok` and Main returns a substantive reply, the Management reporting duty is complete: the handoff reached Main and should allow OpenClaw's announce step to follow. `ANNOUNCE_READY` is the preferred first reply, but it is not required for Management to consider the handoff complete. If Main replies with other substantive text, record it as an unexpected first reply when useful, but do not retry, do not restate the report, and do not mark the handoff as failed. Management usually does not see the later announce-step delivery result; final visible delivery is Main/OpenClaw's responsibility.
371
373
 
372
- If `sessions_send` returns `status=ok` but no `reply`, times out, errors, or Main replies only with a non-deliverable control token such as `NO_REPLY`, `REPLY_SKIP`, `ANNOUNCE_SKIP`, or `HEARTBEAT_OK`, treat the handoff as incomplete because the announce step may not be triggered. Do not send transcript media for that incomplete handoff. Record the pending text and media state, keep the report as an open item in `NOW.md`, and avoid sending another placeholder.
374
+ If `sessions_send` returns `status=ok` but no `reply`, times out, errors, or Main replies only with a non-deliverable control token such as `NO_REPLY`, `REPLY_SKIP`, `ANNOUNCE_SKIP`, or `HEARTBEAT_OK`, treat the handoff as incomplete because the announce step may not be triggered. Record the pending state, keep the report as an open item in `NOW.md`, and avoid sending another placeholder.
373
375
 
374
376
  If `sessions_send` fails because the route was missing, use `sessions_list` to find the latest human-facing Main Session and retry with its `sessionKey` and send it. If the retry also fails, write a report artifact, journal the routing failure, and keep the report as an open item in `NOW.md`.
@@ -43,7 +43,6 @@ import {
43
43
  appendClaworldJournalEvent,
44
44
  buildClaworldRuntimeMaintenanceEvent,
45
45
  } from '../runtime/working-memory.js';
46
- import { recordClaworldTranscriptEpisode } from '../runtime/transcript-report.js';
47
46
  import {
48
47
  broadcastModeratedWorld,
49
48
  createModeratedWorld,
@@ -1986,6 +1985,7 @@ async function fetchRuntimeWorldActivity({
1986
1985
  agentId = null,
1987
1986
  worldId = null,
1988
1987
  limit = null,
1988
+ activityType = null,
1989
1989
  fetchImpl,
1990
1990
  }) {
1991
1991
  const normalizedWorldId = normalizeClaworldText(worldId, null);
@@ -2004,6 +2004,7 @@ async function fetchRuntimeWorldActivity({
2004
2004
  const requestUrl = new URL(`${baseUrl}/v1/worlds/${encodeURIComponent(normalizedWorldId)}/activity`);
2005
2005
  if (agentId) requestUrl.searchParams.set('agentId', agentId);
2006
2006
  if (limit != null) requestUrl.searchParams.set('limit', String(limit));
2007
+ if (activityType) requestUrl.searchParams.set('activityType', activityType);
2007
2008
  const result = await fetchJson(fetchImpl, requestUrl.toString(), {
2008
2009
  headers: {
2009
2010
  accept: 'application/json',
@@ -2385,17 +2386,8 @@ function buildInboundRuntimeMaintenanceEvent({
2385
2386
  const isRelayDelivery = normalizedEventType === 'delivery';
2386
2387
  const sessionKey = resolveNormalizedText(delivery.sessionKey, null);
2387
2388
  const requestId = resolveNormalizedText(
2388
- delivery.chatRequestId,
2389
- resolveNormalizedText(
2390
- payload.chatRequestId,
2391
- resolveNormalizedText(
2392
- payload.requestId,
2393
- resolveNormalizedText(
2394
- metadata.kickoffRequestId,
2395
- resolveNormalizedText(metadata.requestId, resolveNormalizedText(metadata.chatRequestId, null)),
2396
- ),
2397
- ),
2398
- ),
2389
+ metadata.kickoffRequestId,
2390
+ resolveNormalizedText(metadata.requestId, resolveNormalizedText(metadata.chatRequestId, null)),
2399
2391
  );
2400
2392
  const worldId = resolveNormalizedText(
2401
2393
  metadata.worldId,
@@ -2508,7 +2500,6 @@ function createDeliveryReplyDispatcher({
2508
2500
  let suppressed = false;
2509
2501
  let replyTransport = null;
2510
2502
  let replyFallbackUsed = false;
2511
- let submittedReplyText = null;
2512
2503
  let keptSilentTransport = null;
2513
2504
  let keptSilentFallbackUsed = false;
2514
2505
  const finalTexts = [];
@@ -2692,7 +2683,6 @@ function createDeliveryReplyDispatcher({
2692
2683
  const replyResult = await submitRelayReply(normalized);
2693
2684
  replyTransport = replyResult?.transport || null;
2694
2685
  replyFallbackUsed = replyResult?.fallbackUsed === true;
2695
- submittedReplyText = normalized;
2696
2686
  replied = true;
2697
2687
  return true;
2698
2688
  };
@@ -2850,7 +2840,6 @@ function createDeliveryReplyDispatcher({
2850
2840
  markDispatchIdle,
2851
2841
  didReply: () => replied,
2852
2842
  didKeepSilent: () => keptSilent,
2853
- getSubmittedReplyText: () => submittedReplyText,
2854
2843
  submitMessageToolReply,
2855
2844
  getRuntimeOutputSummary: () => ({
2856
2845
  counts: { ...runtimeOutputSummary.counts },
@@ -2898,7 +2887,6 @@ async function runDeliveryReplyDispatch({
2898
2887
  markDispatchIdle,
2899
2888
  didReply,
2900
2889
  didKeepSilent,
2901
- getSubmittedReplyText,
2902
2890
  submitMessageToolReply,
2903
2891
  getRuntimeOutputSummary,
2904
2892
  } = createDeliveryReplyDispatcher({
@@ -2965,7 +2953,6 @@ async function runDeliveryReplyDispatch({
2965
2953
  dispatchResult,
2966
2954
  replied: didReply(),
2967
2955
  keptSilent: didKeepSilent(),
2968
- replyText: getSubmittedReplyText(),
2969
2956
  runtimeOutputSummary: getRuntimeOutputSummary(),
2970
2957
  };
2971
2958
  }
@@ -3039,19 +3026,6 @@ async function maybeBridgeRuntimeInboundEvent({
3039
3026
  const fromAgentId = resolveNormalizedText(metadata.fromAgentId, null);
3040
3027
  const isRelayDelivery = eventType === 'delivery';
3041
3028
  const allowReply = metadata.allowReply === true || (isRelayDelivery && metadata.allowReply !== false);
3042
- const chatRequestId = resolveNormalizedText(
3043
- delivery.chatRequestId,
3044
- resolveNormalizedText(
3045
- payload.chatRequestId,
3046
- resolveNormalizedText(
3047
- payload.requestId,
3048
- resolveNormalizedText(
3049
- metadata.kickoffRequestId,
3050
- resolveNormalizedText(metadata.requestId, resolveNormalizedText(metadata.chatRequestId, null)),
3051
- ),
3052
- ),
3053
- ),
3054
- );
3055
3029
 
3056
3030
  if (
3057
3031
  !runtime?.channel?.reply?.finalizeInboundContext
@@ -3159,7 +3133,6 @@ async function maybeBridgeRuntimeInboundEvent({
3159
3133
  WasMentioned: false,
3160
3134
  CommandAuthorized: commandAuthorized,
3161
3135
  RelayDeliveryId: isRelayDelivery ? deliveryId : null,
3162
- RelayChatRequestId: chatRequestId,
3163
3136
  RelayFromAgentId: fromAgentId,
3164
3137
  RelayConversationKey: metadata.conversationKey || null,
3165
3138
  UntrustedContext,
@@ -3220,7 +3193,6 @@ async function maybeBridgeRuntimeInboundEvent({
3220
3193
  dispatchResult,
3221
3194
  replied,
3222
3195
  keptSilent,
3223
- replyText,
3224
3196
  runtimeOutputSummary,
3225
3197
  } = await runDeliveryReplyDispatch({
3226
3198
  runtime,
@@ -3270,7 +3242,6 @@ async function maybeBridgeRuntimeInboundEvent({
3270
3242
  dispatchResult,
3271
3243
  replied,
3272
3244
  keptSilent,
3273
- replyText,
3274
3245
  runtimeOutputSummary,
3275
3246
  } = await runDeliveryReplyDispatch({
3276
3247
  runtime,
@@ -3322,40 +3293,6 @@ async function maybeBridgeRuntimeInboundEvent({
3322
3293
  error: error?.message || String(error),
3323
3294
  });
3324
3295
  }
3325
-
3326
- if (isRelayDelivery) {
3327
- if (chatRequestId) {
3328
- try {
3329
- await recordClaworldTranscriptEpisode(workspaceRoot, {
3330
- chatRequestId,
3331
- deliveryId,
3332
- localSessionKey,
3333
- relaySessionKey: sessionKey,
3334
- conversationKey: resolveNormalizedText(metadata.conversationKey, resolveNormalizedText(delivery.conversationKey, null)),
3335
- worldId,
3336
- targetAgentId: resolveNormalizedText(delivery.targetAgentId, resolveNormalizedText(payload.targetAgentId, null)),
3337
- fromAgentId,
3338
- fromAgentCode: metadata.fromAgentCode,
3339
- fromDisplayIdentity: metadata.fromDisplayIdentity,
3340
- localAgentId,
3341
- deliveryType: metadata.deliveryType,
3342
- commandText,
3343
- contextText,
3344
- untrustedContext: payload.untrustedContext,
3345
- createdAt: delivery.createdAt || metadata.createdAt || payload.createdAt || null,
3346
- turnCreatedAt: delivery.turnCreatedAt || metadata.turnCreatedAt || payload.turnCreatedAt || null,
3347
- replyText,
3348
- });
3349
- } catch (error) {
3350
- logger.warn?.(`[claworld:${runtimeAccountId}] transcript episode indexing failed`, {
3351
- deliveryId,
3352
- chatRequestId,
3353
- sessionKey,
3354
- error: error?.message || String(error),
3355
- });
3356
- }
3357
- }
3358
- }
3359
3296
  }
3360
3297
 
3361
3298
  logger.info?.(`[claworld:${runtimeAccountId}] ${isRelayDelivery ? 'delivery bridge completed' : 'inbound bridge completed'}`, {
@@ -4896,6 +4833,7 @@ async function generateRuntimeProfileCard(context = {}) {
4896
4833
  agentId: resolvedContext.agentId || null,
4897
4834
  worldId: context.worldId || null,
4898
4835
  limit: context.limit ?? null,
4836
+ activityType: context.activityType || null,
4899
4837
  fetchImpl,
4900
4838
  });
4901
4839
  },
@@ -5310,6 +5248,7 @@ async function generateRuntimeProfileCard(context = {}) {
5310
5248
  agentId: resolvedContext.agentId || null,
5311
5249
  worldId: context.worldId || null,
5312
5250
  limit: context.limit ?? null,
5251
+ activityType: context.activityType || null,
5313
5252
  fetchImpl,
5314
5253
  });
5315
5254
  },
@@ -25,10 +25,6 @@ import {
25
25
  updateClaworldSessionDirectory,
26
26
  } from '../runtime/working-memory.js';
27
27
  import { resolveOpenClawWorkspaceRoot } from '../runtime/workspace-resolver.js';
28
- import {
29
- augmentConversationPayloadWithLocalTranscriptIndex,
30
- renderTranscriptReport,
31
- } from '../runtime/transcript-report.js';
32
28
  import { setClaworldRuntime } from './runtime.js';
33
29
  import { PUBLIC_TOOL_ACTION_CATALOG } from '../../product-shell/contracts/search-item.js';
34
30
  import {
@@ -133,23 +129,6 @@ async function deliverShareCardToCurrentChannel(api, result, toolContext = {}) {
133
129
  return { ...result, content };
134
130
  }
135
131
 
136
- async function resolveTranscriptWorkspace(api, params = {}, toolContext = {}) {
137
- const cfg = await loadCurrentConfig(api);
138
- const agentId = normalizeText(
139
- toolContext?.agentId,
140
- normalizeText(toolContext?.context?.agentId, normalizeText(params.agentId, null)),
141
- );
142
- return {
143
- cfg,
144
- agentId,
145
- workspaceRoot: resolveOpenClawWorkspaceRoot({
146
- sources: [toolContext, toolContext?.context, params],
147
- config: cfg,
148
- agentId,
149
- }),
150
- };
151
- }
152
-
153
132
  function buildClaworldStatusRoute(plugin) {
154
133
  return {
155
134
  method: 'GET',
@@ -673,7 +652,6 @@ function createTerminalToolAdapters(api, plugin, internalTools) {
673
652
  const searchTool = 'claworld_search';
674
653
  const manageWorldsTool = 'claworld_manage_worlds';
675
654
  const manageConversationsTool = 'claworld_manage_conversations';
676
- const renderTranscriptTool = 'claworld_render_transcript_report';
677
655
  const accountTool = 'claworld_manage_account';
678
656
  const publicProfileTool = 'claworld_get_public_profile';
679
657
 
@@ -1174,7 +1152,7 @@ function createTerminalToolAdapters(api, plugin, internalTools) {
1174
1152
  joinPolicy: stringParam({ description: 'Owner-defined join policy.', minLength: 1 }),
1175
1153
  approvalPolicy: stringParam({ description: 'Owner-defined approval policy.', minLength: 1 }),
1176
1154
  broadcastEnabled: booleanParam({ description: 'Whether a world subscription should receive broadcasts.' }),
1177
- broadcast: objectParam({ description: 'Optional broadcast config for update_world or set_world_broadcast_preference.', additionalProperties: true }),
1155
+ broadcast: objectParam({ description: 'Owner broadcast capability config for update_world only (enabled, audience, replyPolicy, excludeSelf). Not for set_world_broadcast_preference.', additionalProperties: true }),
1178
1156
  subscriptionId: stringParam({ description: 'Existing subscription id for unsubscribe_world.', minLength: 1 }),
1179
1157
  targetAgentId: stringParam({ description: 'Target agent id for private-world invitation actions.', minLength: 1 }),
1180
1158
  identity: stringParam({ description: 'Target public identity displayName#code for private-world invitation actions.', minLength: 1 }),
@@ -1241,14 +1219,9 @@ function createTerminalToolAdapters(api, plugin, internalTools) {
1241
1219
  ...context,
1242
1220
  worldId,
1243
1221
  limit: params.limit ?? null,
1222
+ ...(action === 'list_broadcast_history' ? { activityType: 'world_broadcast_published' } : {}),
1244
1223
  });
1245
- const filteredPayload = action === 'list_broadcast_history' && Array.isArray(payload?.items)
1246
- ? {
1247
- ...payload,
1248
- items: payload.items.filter((item) => /broadcast/i.test(String(item.activityType || item.type || ''))),
1249
- }
1250
- : payload;
1251
- return buildTerminalActionResult({ tool: manageWorldsTool, action, payload: filteredPayload });
1224
+ return buildTerminalActionResult({ tool: manageWorldsTool, action, payload });
1252
1225
  }
1253
1226
  if (action === 'manage_members') {
1254
1227
  const worldId = normalizeText(params.worldId, null);
@@ -1431,7 +1404,7 @@ function createTerminalToolAdapters(api, plugin, internalTools) {
1431
1404
  }),
1432
1405
  },
1433
1406
  }),
1434
- async execute(toolCallId, params = {}, toolContext = {}) {
1407
+ async execute(toolCallId, params = {}) {
1435
1408
  const action = normalizeTerminalConversationAction(params.action, 'list_related', { throwOnInvalid: true });
1436
1409
  if (action === 'request') {
1437
1410
  const context = await resolveToolContext(api, plugin, params, {
@@ -1461,16 +1434,7 @@ function createTerminalToolAdapters(api, plugin, internalTools) {
1461
1434
  action: 'list',
1462
1435
  ...(Object.keys(filters).length > 0 ? { filters } : {}),
1463
1436
  });
1464
- const rewritten = rewriteToolResultName(result, manageConversationsTool, action);
1465
- const payload = parseToolResultPayload(rewritten);
1466
- if (!payload) return rewritten;
1467
- const { workspaceRoot } = await resolveTranscriptWorkspace(api, params, toolContext);
1468
- const augmented = await augmentConversationPayloadWithLocalTranscriptIndex({
1469
- workspaceRoot,
1470
- payload,
1471
- filters,
1472
- });
1473
- return buildToolResult(augmented);
1437
+ return rewriteToolResultName(result, manageConversationsTool, action);
1474
1438
  }
1475
1439
  if (action === 'accept' || action === 'reject') {
1476
1440
  const result = await requireTerminalTool(internalTools, 'claworld_chat_inbox').execute(toolCallId, {
@@ -1501,101 +1465,6 @@ function createTerminalToolAdapters(api, plugin, internalTools) {
1501
1465
  return buildToolResult({ status: 'error', tool: manageConversationsTool });
1502
1466
  },
1503
1467
  },
1504
- {
1505
- name: renderTranscriptTool,
1506
- label: 'Claworld Render Transcript Report',
1507
- description: 'Generate local transcript artifacts only; this tool never sends a channel message. Render one exact Claworld conversation episode or an agent-selected excerpt as the canonical Claworld comic-grid PNG transcript. Use mode=stored with stored.chatRequestId for a complete indexed episode. Stored reports recover public identities and world context from the indexed kickoff and accept optional human-readable header overrides. Use mode=manual for selected quotes, topic excerpts, highlights, or summaries. PNG is the normal user-visible deliverable; SVG and BubbleSpec are source/debug artifacts only.',
1508
- metadata: buildToolMetadata({
1509
- category: 'conversation',
1510
- usageNotes: [
1511
- 'Use stored.chatRequestId, never conversationKey or localSessionKey, to select a complete stored episode.',
1512
- 'Keep request, conversation, session, and agent ids out of stored title/profile/speaker-label overrides.',
1513
- 'For manual mode, provide only ordered visible peer/local messages and accurate createdAt timestamps.',
1514
- 'This tool only writes local artifacts and returns absolute paths. It never sends a channel message.',
1515
- 'After rendering, send at most the first three artifacts.pngPages[].path values with OpenClaw message(action=send, media=...).',
1516
- 'If pageCount exceeds three, tell the human the total and that only the first three are included. Main uses its normal assistant response; Management includes the notice in the sessions_send report text.',
1517
- 'Management Session should hand off report text with sessions_send first, then send the selected PNG paths to the Main Session deliveryContext with message(action=send).',
1518
- ],
1519
- }),
1520
- parameters: objectParam({
1521
- description: 'Claworld transcript report render request.',
1522
- required: ['mode'],
1523
- properties: {
1524
- mode: stringParam({
1525
- description: 'stored renders one indexed local episode by chatRequestId; manual renders exactly manual.messages.',
1526
- enumValues: ['stored', 'manual'],
1527
- }),
1528
- stored: objectParam({
1529
- description: 'Stored episode selector. Provide only with mode=stored.',
1530
- required: ['chatRequestId'],
1531
- properties: {
1532
- chatRequestId: stringParam({
1533
- description: 'Canonical Claworld chat request / episode id.',
1534
- minLength: 1,
1535
- }),
1536
- title: stringParam({
1537
- description: 'Optional human-readable report title. Defaults to public peer/world context from the indexed kickoff.',
1538
- minLength: 1,
1539
- }),
1540
- peerProfile: stringParam({
1541
- description: 'Optional public subtitle/profile. Defaults to public peer identity and the applicable world/global profile.',
1542
- minLength: 1,
1543
- }),
1544
- localLabel: stringParam({
1545
- description: 'Optional public speaker label for local/right messages.',
1546
- minLength: 1,
1547
- }),
1548
- peerLabel: stringParam({
1549
- description: 'Optional public speaker label for peer/left messages.',
1550
- minLength: 1,
1551
- }),
1552
- },
1553
- }),
1554
- manual: objectParam({
1555
- description: 'Exact visible transcript rows and header context. Provide only with mode=manual.',
1556
- required: ['messages', 'title', 'peerProfile', 'localLabel', 'peerLabel'],
1557
- properties: {
1558
- messages: arrayParam({
1559
- description: 'Ordered visible transcript rows.',
1560
- items: objectParam({
1561
- required: ['from', 'text', 'createdAt'],
1562
- properties: {
1563
- from: stringParam({ description: 'peer=left; local=right.', enumValues: ['peer', 'local'] }),
1564
- text: stringParam({ description: 'Visible message text.', minLength: 1 }),
1565
- createdAt: stringParam({ description: 'Message timestamp, preferably ISO 8601.', minLength: 1 }),
1566
- },
1567
- }),
1568
- }),
1569
- title: stringParam({ description: 'Report header title.', minLength: 1 }),
1570
- peerProfile: stringParam({ description: 'Peer profile/header subtitle appropriate to direct or world scope.', minLength: 1 }),
1571
- localLabel: stringParam({ description: 'Speaker label for local/right messages.', minLength: 1 }),
1572
- peerLabel: stringParam({ description: 'Speaker label for peer/left messages.', minLength: 1 }),
1573
- },
1574
- }),
1575
- style: stringParam({
1576
- description: 'Optional visual style. Defaults to the Hermes-compatible Claworld comic grid.',
1577
- enumValues: ['claworld-comic-grid'],
1578
- }),
1579
- maxPageHeight: integerParam({
1580
- description: 'Maximum page height in pixels. Defaults to 2000. Long transcripts paginate without truncation.',
1581
- minimum: 900,
1582
- maximum: 8000,
1583
- }),
1584
- },
1585
- }),
1586
- async execute(_toolCallId, params = {}, toolContext = {}) {
1587
- const { workspaceRoot, agentId } = await resolveTranscriptWorkspace(api, params, toolContext);
1588
- if (!workspaceRoot) {
1589
- throw new Error('unable to resolve the active OpenClaw workspace for transcript rendering');
1590
- }
1591
- const report = await renderTranscriptReport({
1592
- workspaceRoot,
1593
- localAgentId: agentId,
1594
- args: params,
1595
- });
1596
- return buildToolResult({ ...report, tool: renderTranscriptTool });
1597
- },
1598
- },
1599
1468
  ];
1600
1469
  }
1601
1470
 
@@ -1623,7 +1492,7 @@ function buildRegisteredTools(api, plugin) {
1623
1492
  const broadcastAudienceValues = ['members', 'admins', 'admins_and_owner'];
1624
1493
  const broadcastReplyPolicyValues = ['zero', 'at_most_one'];
1625
1494
  const broadcastConfigProperty = objectParam({
1626
- description: 'Optional world broadcast config for owner update_context. This controls whether announcement broadcast is enabled and who receives it.',
1495
+ description: 'Owner broadcast capability config for update_world only. Controls whether announcement broadcast is enabled and who receives it. Not for set_world_broadcast_preference (which is a viewer subscription preference).',
1627
1496
  properties: {
1628
1497
  enabled: booleanParam({
1629
1498
  description: 'Whether owner announcement broadcast is enabled for this world.',
@@ -2653,22 +2522,6 @@ export function registerClaworldPluginFull(api, plugin) {
2653
2522
  );
2654
2523
  continue;
2655
2524
  }
2656
- if (
2657
- terminalTool.name === 'claworld_manage_conversations'
2658
- || terminalTool.name === 'claworld_render_transcript_report'
2659
- ) {
2660
- api.registerTool(
2661
- (toolContext) => ({
2662
- ...terminalTool,
2663
- execute: withToolErrorBoundary(
2664
- terminalTool.name,
2665
- async (...args) => await terminalTool.execute(...args, toolContext),
2666
- ),
2667
- }),
2668
- { name: terminalTool.name },
2669
- );
2670
- continue;
2671
- }
2672
2525
  api.registerTool({
2673
2526
  ...terminalTool,
2674
2527
  execute: withToolErrorBoundary(terminalTool.name, terminalTool.execute),