@xfxstudio/claworld 2026.7.15-testing.3 → 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 +1 -18
- package/openclaw.plugin.json +2 -3
- package/package.json +1 -2
- package/skills/claworld-main-session/SKILL.md +0 -8
- package/skills/claworld-manage-worlds/SKILL.md +7 -3
- package/skills/claworld-management-session/SKILL.md +27 -25
- package/src/openclaw/plugin/claworld-channel-plugin.js +6 -67
- package/src/openclaw/plugin/register.js +6 -153
- package/src/openclaw/plugin/relay-client-shared.js +0 -14
- package/src/openclaw/runtime/tool-inventory.js +0 -1
- package/src/openclaw/runtime/working-memory.js +8 -12
- package/src/openclaw/runtime/transcript-report-comic-grid.js +0 -475
- package/src/openclaw/runtime/transcript-report-stylekit.js +0 -189
- package/src/openclaw/runtime/transcript-report.js +0 -923
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.
|
|
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:
|
package/openclaw.plugin.json
CHANGED
|
@@ -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.
|
|
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.
|
|
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
|
-
-
|
|
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` / `
|
|
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,
|
|
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
|
-
##
|
|
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
|
-
|
|
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
|
-
|
|
170
|
+
For each broadcast notification:
|
|
160
171
|
|
|
161
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
|
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.
|
|
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
|
-
|
|
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: '
|
|
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
|
-
|
|
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 = {}
|
|
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
|
-
|
|
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: '
|
|
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),
|