@xfxstudio/claworld 2026.7.15-testing.1 → 2026.7.15-testing.2
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 +18 -1
- package/openclaw.plugin.json +3 -2
- package/package.json +2 -1
- package/skills/claworld-main-session/SKILL.md +8 -0
- package/skills/claworld-management-session/SKILL.md +27 -3
- package/src/openclaw/plugin/claworld-channel-plugin.js +67 -2
- package/src/openclaw/plugin/register.js +144 -2
- package/src/openclaw/plugin/relay-client-shared.js +14 -0
- package/src/openclaw/runtime/tool-inventory.js +1 -0
- package/src/openclaw/runtime/transcript-report-comic-grid.js +475 -0
- package/src/openclaw/runtime/transcript-report-stylekit.js +189 -0
- package/src/openclaw/runtime/transcript-report.js +923 -0
- package/src/openclaw/runtime/working-memory.js +12 -3
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.15-testing.2
|
|
46
46
|
```
|
|
47
47
|
|
|
48
48
|
Testing packages default to `https://staging.claworld.love`; stable packages
|
|
@@ -86,6 +86,23 @@ 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
|
+
|
|
89
106
|
## Inspect And Repair
|
|
90
107
|
|
|
91
108
|
Recommended host-native checks:
|
package/openclaw.plugin.json
CHANGED
|
@@ -12,12 +12,13 @@
|
|
|
12
12
|
"claworld_search",
|
|
13
13
|
"claworld_get_public_profile",
|
|
14
14
|
"claworld_manage_worlds",
|
|
15
|
-
"claworld_manage_conversations"
|
|
15
|
+
"claworld_manage_conversations",
|
|
16
|
+
"claworld_render_transcript_report"
|
|
16
17
|
]
|
|
17
18
|
},
|
|
18
19
|
"name": "Claworld Persona Relay",
|
|
19
20
|
"description": "Claworld relay world channel plugin for OpenClaw.",
|
|
20
|
-
"version": "2026.7.15-testing.
|
|
21
|
+
"version": "2026.7.15-testing.2",
|
|
21
22
|
"configSchema": {
|
|
22
23
|
"type": "object",
|
|
23
24
|
"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.2",
|
|
4
4
|
"description": "Claworld channel plugin for OpenClaw",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "index.js",
|
|
@@ -45,6 +45,7 @@
|
|
|
45
45
|
}
|
|
46
46
|
},
|
|
47
47
|
"dependencies": {
|
|
48
|
+
"sharp": "^0.35.3",
|
|
48
49
|
"ws": "^8.19.0"
|
|
49
50
|
},
|
|
50
51
|
"devDependencies": {
|
|
@@ -91,6 +91,14 @@ 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
|
+
|
|
94
102
|
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.
|
|
95
103
|
|
|
96
104
|
## Conversation Transport
|
|
@@ -156,6 +156,29 @@ Always report the outcome to the human. A low-value or no-decision conversation
|
|
|
156
156
|
|
|
157
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.
|
|
158
158
|
|
|
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.
|
|
160
|
+
|
|
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.
|
|
162
|
+
|
|
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`.
|
|
164
|
+
|
|
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:
|
|
168
|
+
|
|
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
|
+
```
|
|
179
|
+
|
|
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.
|
|
181
|
+
|
|
159
182
|
### use sessions_send to report
|
|
160
183
|
|
|
161
184
|
Use `sessions_send` to send the report handoff to the latest active Main Session. Include the route in the tool call.
|
|
@@ -206,7 +229,7 @@ Step 2: After `sessions_send` completes, OpenClaw will later send you: "Agent-to
|
|
|
206
229
|
|
|
207
230
|
Here is the exact report:
|
|
208
231
|
|
|
209
|
-
<exact report>
|
|
232
|
+
<exact report, including a natural transcript introduction when appropriate but no paths or MEDIA refs>
|
|
210
233
|
|
|
211
234
|
```
|
|
212
235
|
|
|
@@ -340,11 +363,12 @@ After `sessions_send` returns, record what happened in local working memory when
|
|
|
340
363
|
- source event, notification, chat request, or conversation ids
|
|
341
364
|
- timestamp
|
|
342
365
|
- 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
|
|
343
367
|
|
|
344
368
|
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.
|
|
345
369
|
|
|
346
|
-
If `sessions_send` returns `status=ok` and Main returns a substantive reply, the
|
|
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.
|
|
347
371
|
|
|
348
|
-
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.
|
|
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.
|
|
349
373
|
|
|
350
374
|
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,6 +43,7 @@ import {
|
|
|
43
43
|
appendClaworldJournalEvent,
|
|
44
44
|
buildClaworldRuntimeMaintenanceEvent,
|
|
45
45
|
} from '../runtime/working-memory.js';
|
|
46
|
+
import { recordClaworldTranscriptEpisode } from '../runtime/transcript-report.js';
|
|
46
47
|
import {
|
|
47
48
|
broadcastModeratedWorld,
|
|
48
49
|
createModeratedWorld,
|
|
@@ -2384,8 +2385,17 @@ function buildInboundRuntimeMaintenanceEvent({
|
|
|
2384
2385
|
const isRelayDelivery = normalizedEventType === 'delivery';
|
|
2385
2386
|
const sessionKey = resolveNormalizedText(delivery.sessionKey, null);
|
|
2386
2387
|
const requestId = resolveNormalizedText(
|
|
2387
|
-
|
|
2388
|
-
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
2399
|
);
|
|
2390
2400
|
const worldId = resolveNormalizedText(
|
|
2391
2401
|
metadata.worldId,
|
|
@@ -2498,6 +2508,7 @@ function createDeliveryReplyDispatcher({
|
|
|
2498
2508
|
let suppressed = false;
|
|
2499
2509
|
let replyTransport = null;
|
|
2500
2510
|
let replyFallbackUsed = false;
|
|
2511
|
+
let submittedReplyText = null;
|
|
2501
2512
|
let keptSilentTransport = null;
|
|
2502
2513
|
let keptSilentFallbackUsed = false;
|
|
2503
2514
|
const finalTexts = [];
|
|
@@ -2681,6 +2692,7 @@ function createDeliveryReplyDispatcher({
|
|
|
2681
2692
|
const replyResult = await submitRelayReply(normalized);
|
|
2682
2693
|
replyTransport = replyResult?.transport || null;
|
|
2683
2694
|
replyFallbackUsed = replyResult?.fallbackUsed === true;
|
|
2695
|
+
submittedReplyText = normalized;
|
|
2684
2696
|
replied = true;
|
|
2685
2697
|
return true;
|
|
2686
2698
|
};
|
|
@@ -2838,6 +2850,7 @@ function createDeliveryReplyDispatcher({
|
|
|
2838
2850
|
markDispatchIdle,
|
|
2839
2851
|
didReply: () => replied,
|
|
2840
2852
|
didKeepSilent: () => keptSilent,
|
|
2853
|
+
getSubmittedReplyText: () => submittedReplyText,
|
|
2841
2854
|
submitMessageToolReply,
|
|
2842
2855
|
getRuntimeOutputSummary: () => ({
|
|
2843
2856
|
counts: { ...runtimeOutputSummary.counts },
|
|
@@ -2885,6 +2898,7 @@ async function runDeliveryReplyDispatch({
|
|
|
2885
2898
|
markDispatchIdle,
|
|
2886
2899
|
didReply,
|
|
2887
2900
|
didKeepSilent,
|
|
2901
|
+
getSubmittedReplyText,
|
|
2888
2902
|
submitMessageToolReply,
|
|
2889
2903
|
getRuntimeOutputSummary,
|
|
2890
2904
|
} = createDeliveryReplyDispatcher({
|
|
@@ -2951,6 +2965,7 @@ async function runDeliveryReplyDispatch({
|
|
|
2951
2965
|
dispatchResult,
|
|
2952
2966
|
replied: didReply(),
|
|
2953
2967
|
keptSilent: didKeepSilent(),
|
|
2968
|
+
replyText: getSubmittedReplyText(),
|
|
2954
2969
|
runtimeOutputSummary: getRuntimeOutputSummary(),
|
|
2955
2970
|
};
|
|
2956
2971
|
}
|
|
@@ -3024,6 +3039,19 @@ async function maybeBridgeRuntimeInboundEvent({
|
|
|
3024
3039
|
const fromAgentId = resolveNormalizedText(metadata.fromAgentId, null);
|
|
3025
3040
|
const isRelayDelivery = eventType === 'delivery';
|
|
3026
3041
|
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
|
+
);
|
|
3027
3055
|
|
|
3028
3056
|
if (
|
|
3029
3057
|
!runtime?.channel?.reply?.finalizeInboundContext
|
|
@@ -3131,6 +3159,7 @@ async function maybeBridgeRuntimeInboundEvent({
|
|
|
3131
3159
|
WasMentioned: false,
|
|
3132
3160
|
CommandAuthorized: commandAuthorized,
|
|
3133
3161
|
RelayDeliveryId: isRelayDelivery ? deliveryId : null,
|
|
3162
|
+
RelayChatRequestId: chatRequestId,
|
|
3134
3163
|
RelayFromAgentId: fromAgentId,
|
|
3135
3164
|
RelayConversationKey: metadata.conversationKey || null,
|
|
3136
3165
|
UntrustedContext,
|
|
@@ -3191,6 +3220,7 @@ async function maybeBridgeRuntimeInboundEvent({
|
|
|
3191
3220
|
dispatchResult,
|
|
3192
3221
|
replied,
|
|
3193
3222
|
keptSilent,
|
|
3223
|
+
replyText,
|
|
3194
3224
|
runtimeOutputSummary,
|
|
3195
3225
|
} = await runDeliveryReplyDispatch({
|
|
3196
3226
|
runtime,
|
|
@@ -3240,6 +3270,7 @@ async function maybeBridgeRuntimeInboundEvent({
|
|
|
3240
3270
|
dispatchResult,
|
|
3241
3271
|
replied,
|
|
3242
3272
|
keptSilent,
|
|
3273
|
+
replyText,
|
|
3243
3274
|
runtimeOutputSummary,
|
|
3244
3275
|
} = await runDeliveryReplyDispatch({
|
|
3245
3276
|
runtime,
|
|
@@ -3291,6 +3322,40 @@ async function maybeBridgeRuntimeInboundEvent({
|
|
|
3291
3322
|
error: error?.message || String(error),
|
|
3292
3323
|
});
|
|
3293
3324
|
}
|
|
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
|
+
}
|
|
3294
3359
|
}
|
|
3295
3360
|
|
|
3296
3361
|
logger.info?.(`[claworld:${runtimeAccountId}] ${isRelayDelivery ? 'delivery bridge completed' : 'inbound bridge completed'}`, {
|
|
@@ -25,6 +25,10 @@ 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';
|
|
28
32
|
import { setClaworldRuntime } from './runtime.js';
|
|
29
33
|
import { PUBLIC_TOOL_ACTION_CATALOG } from '../../product-shell/contracts/search-item.js';
|
|
30
34
|
import {
|
|
@@ -129,6 +133,23 @@ async function deliverShareCardToCurrentChannel(api, result, toolContext = {}) {
|
|
|
129
133
|
return { ...result, content };
|
|
130
134
|
}
|
|
131
135
|
|
|
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
|
+
|
|
132
153
|
function buildClaworldStatusRoute(plugin) {
|
|
133
154
|
return {
|
|
134
155
|
method: 'GET',
|
|
@@ -652,6 +673,7 @@ function createTerminalToolAdapters(api, plugin, internalTools) {
|
|
|
652
673
|
const searchTool = 'claworld_search';
|
|
653
674
|
const manageWorldsTool = 'claworld_manage_worlds';
|
|
654
675
|
const manageConversationsTool = 'claworld_manage_conversations';
|
|
676
|
+
const renderTranscriptTool = 'claworld_render_transcript_report';
|
|
655
677
|
const accountTool = 'claworld_manage_account';
|
|
656
678
|
const publicProfileTool = 'claworld_get_public_profile';
|
|
657
679
|
|
|
@@ -1409,7 +1431,7 @@ function createTerminalToolAdapters(api, plugin, internalTools) {
|
|
|
1409
1431
|
}),
|
|
1410
1432
|
},
|
|
1411
1433
|
}),
|
|
1412
|
-
async execute(toolCallId, params = {}) {
|
|
1434
|
+
async execute(toolCallId, params = {}, toolContext = {}) {
|
|
1413
1435
|
const action = normalizeTerminalConversationAction(params.action, 'list_related', { throwOnInvalid: true });
|
|
1414
1436
|
if (action === 'request') {
|
|
1415
1437
|
const context = await resolveToolContext(api, plugin, params, {
|
|
@@ -1439,7 +1461,16 @@ function createTerminalToolAdapters(api, plugin, internalTools) {
|
|
|
1439
1461
|
action: 'list',
|
|
1440
1462
|
...(Object.keys(filters).length > 0 ? { filters } : {}),
|
|
1441
1463
|
});
|
|
1442
|
-
|
|
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);
|
|
1443
1474
|
}
|
|
1444
1475
|
if (action === 'accept' || action === 'reject') {
|
|
1445
1476
|
const result = await requireTerminalTool(internalTools, 'claworld_chat_inbox').execute(toolCallId, {
|
|
@@ -1470,6 +1501,101 @@ function createTerminalToolAdapters(api, plugin, internalTools) {
|
|
|
1470
1501
|
return buildToolResult({ status: 'error', tool: manageConversationsTool });
|
|
1471
1502
|
},
|
|
1472
1503
|
},
|
|
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
|
+
},
|
|
1473
1599
|
];
|
|
1474
1600
|
}
|
|
1475
1601
|
|
|
@@ -2527,6 +2653,22 @@ export function registerClaworldPluginFull(api, plugin) {
|
|
|
2527
2653
|
);
|
|
2528
2654
|
continue;
|
|
2529
2655
|
}
|
|
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
|
+
}
|
|
2530
2672
|
api.registerTool({
|
|
2531
2673
|
...terminalTool,
|
|
2532
2674
|
execute: withToolErrorBoundary(terminalTool.name, terminalTool.execute),
|
|
@@ -97,6 +97,8 @@ export function buildInboundEnvelope(message = {}) {
|
|
|
97
97
|
'notification',
|
|
98
98
|
'conversationKey',
|
|
99
99
|
'worldId',
|
|
100
|
+
'chatRequestId',
|
|
101
|
+
'requestId',
|
|
100
102
|
]) {
|
|
101
103
|
if (payload[key] == null && data[key] != null) payload[key] = data[key];
|
|
102
104
|
}
|
|
@@ -134,6 +136,17 @@ export function buildInboundEnvelope(message = {}) {
|
|
|
134
136
|
data.eventName,
|
|
135
137
|
normalizeEnvelopeText(payload.eventName, isDeliveryEvent ? null : normalizeEnvelopeText(message.event, null)),
|
|
136
138
|
);
|
|
139
|
+
const chatRequestId = [
|
|
140
|
+
data.chatRequestId,
|
|
141
|
+
data.requestId,
|
|
142
|
+
payload.chatRequestId,
|
|
143
|
+
payload.requestId,
|
|
144
|
+
metadata.kickoffRequestId,
|
|
145
|
+
metadata.chatRequestId,
|
|
146
|
+
metadata.requestId,
|
|
147
|
+
notification.chatRequestId,
|
|
148
|
+
notification.relatedObjects?.chatRequestId,
|
|
149
|
+
].map((candidate) => normalizeEnvelopeText(candidate, null)).find(Boolean) || null;
|
|
137
150
|
return {
|
|
138
151
|
eventType: eventType || 'delivery',
|
|
139
152
|
eventName,
|
|
@@ -141,6 +154,7 @@ export function buildInboundEnvelope(message = {}) {
|
|
|
141
154
|
deliveryId,
|
|
142
155
|
sessionKey,
|
|
143
156
|
targetAgentId,
|
|
157
|
+
chatRequestId,
|
|
144
158
|
conversationKey: normalizeEnvelopeText(
|
|
145
159
|
data.conversationKey,
|
|
146
160
|
normalizeEnvelopeText(payload.conversationKey, normalizeEnvelopeText(notification.relatedObjects?.conversationKey, null)),
|