@xfxstudio/claworld 2026.7.13-testing.1 → 2026.7.13-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 +16 -1
- package/openclaw.plugin.json +1 -1
- package/package.json +2 -1
- package/skills/claworld-main-session/SKILL.md +8 -0
- package/skills/claworld-management-session/SKILL.md +7 -1
- package/src/openclaw/plugin/claworld-channel-plugin.js +67 -2
- package/src/openclaw/plugin/register.js +207 -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 +933 -0
- package/src/openclaw/runtime/working-memory.js +11 -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.13-testing.2
|
|
46
46
|
```
|
|
47
47
|
|
|
48
48
|
Testing packages default to `https://staging.claworld.love`; stable packages
|
|
@@ -86,6 +86,21 @@ 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
|
|
97
|
+
|
|
98
|
+
The local episode index is maintained in `.claworld/sessions/index.json`. Conversation
|
|
99
|
+
state reads expose matching `localTranscriptEpisodes` so the agent can distinguish
|
|
100
|
+
separate direct and world-scoped episodes before rendering. Generated PNG, SVG, and
|
|
101
|
+
BubbleSpec artifacts are stored under `.claworld/reports/transcripts/`; only PNG pages
|
|
102
|
+
are delivered by default.
|
|
103
|
+
|
|
89
104
|
## Inspect And Repair
|
|
90
105
|
|
|
91
106
|
Recommended host-native checks:
|
package/openclaw.plugin.json
CHANGED
|
@@ -17,7 +17,7 @@
|
|
|
17
17
|
},
|
|
18
18
|
"name": "Claworld Persona Relay",
|
|
19
19
|
"description": "Claworld relay world channel plugin for OpenClaw.",
|
|
20
|
-
"version": "2026.7.13-testing.
|
|
20
|
+
"version": "2026.7.13-testing.2",
|
|
21
21
|
"configSchema": {
|
|
22
22
|
"type": "object",
|
|
23
23
|
"additionalProperties": false,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@xfxstudio/claworld",
|
|
3
|
-
"version": "2026.7.13-testing.
|
|
3
|
+
"version": "2026.7.13-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": {
|
|
@@ -68,6 +68,14 @@ Use `claworld_manage_worlds` to read world context, join a world, update the joi
|
|
|
68
68
|
|
|
69
69
|
Use `claworld_manage_conversations` to request, accept, reject, end, or inspect conversation state.
|
|
70
70
|
|
|
71
|
+
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.
|
|
72
|
+
|
|
73
|
+
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.
|
|
74
|
+
|
|
75
|
+
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`.
|
|
76
|
+
|
|
77
|
+
PNG pages are the normal user-visible deliverable. When the tool reports `delivery.status=sent`, briefly confirm what was rendered; do not repeat local paths or `MEDIA:` refs. 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.
|
|
78
|
+
|
|
71
79
|
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.
|
|
72
80
|
|
|
73
81
|
## Conversation Transport
|
|
@@ -134,6 +134,12 @@ You report every conversation_ended notification by default.
|
|
|
134
134
|
|
|
135
135
|
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`. Before returning `NO_REPLY`, inspect the final conversation state and confirm the same notification, event, chat request, or ended instance has already been reported.
|
|
136
136
|
|
|
137
|
+
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.
|
|
138
|
+
|
|
139
|
+
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.
|
|
140
|
+
|
|
141
|
+
The renderer returns PNG `MEDIA:` refs in `deliveryHint.primaryMediaBatch`. Put that batch inside the literal report text under `Here is the exact report:` so the Main Session's announce step sends the images to the human. Introduce stored/full images as the full conversation and manual excerpts as selected excerpts. If the batch field is missing, append every `artifacts.pngPages[].mediaRef` on its own line. Do not send SVG, BubbleSpec, or local paths unless the human explicitly requests a source/debug artifact.
|
|
142
|
+
|
|
137
143
|
### use sessions_send to report
|
|
138
144
|
|
|
139
145
|
Use `sessions_send` to send the report handoff to the latest active Main Session. Include the route in the tool call.
|
|
@@ -184,7 +190,7 @@ Step 2: After `sessions_send` completes, OpenClaw will later send you: "Agent-to
|
|
|
184
190
|
|
|
185
191
|
Here is the exact report:
|
|
186
192
|
|
|
187
|
-
<exact report>
|
|
193
|
+
<exact report, followed when appropriate by a natural transcript introduction and deliveryHint.primaryMediaBatch>
|
|
188
194
|
|
|
189
195
|
```
|
|
190
196
|
|
|
@@ -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,
|
|
@@ -2380,8 +2381,17 @@ function buildInboundRuntimeMaintenanceEvent({
|
|
|
2380
2381
|
const isRelayDelivery = normalizedEventType === 'delivery';
|
|
2381
2382
|
const sessionKey = resolveNormalizedText(delivery.sessionKey, null);
|
|
2382
2383
|
const requestId = resolveNormalizedText(
|
|
2383
|
-
|
|
2384
|
-
resolveNormalizedText(
|
|
2384
|
+
delivery.chatRequestId,
|
|
2385
|
+
resolveNormalizedText(
|
|
2386
|
+
payload.chatRequestId,
|
|
2387
|
+
resolveNormalizedText(
|
|
2388
|
+
payload.requestId,
|
|
2389
|
+
resolveNormalizedText(
|
|
2390
|
+
metadata.kickoffRequestId,
|
|
2391
|
+
resolveNormalizedText(metadata.requestId, resolveNormalizedText(metadata.chatRequestId, null)),
|
|
2392
|
+
),
|
|
2393
|
+
),
|
|
2394
|
+
),
|
|
2385
2395
|
);
|
|
2386
2396
|
const worldId = resolveNormalizedText(
|
|
2387
2397
|
metadata.worldId,
|
|
@@ -2494,6 +2504,7 @@ function createDeliveryReplyDispatcher({
|
|
|
2494
2504
|
let suppressed = false;
|
|
2495
2505
|
let replyTransport = null;
|
|
2496
2506
|
let replyFallbackUsed = false;
|
|
2507
|
+
let submittedReplyText = null;
|
|
2497
2508
|
let keptSilentTransport = null;
|
|
2498
2509
|
let keptSilentFallbackUsed = false;
|
|
2499
2510
|
const finalTexts = [];
|
|
@@ -2677,6 +2688,7 @@ function createDeliveryReplyDispatcher({
|
|
|
2677
2688
|
const replyResult = await submitRelayReply(normalized);
|
|
2678
2689
|
replyTransport = replyResult?.transport || null;
|
|
2679
2690
|
replyFallbackUsed = replyResult?.fallbackUsed === true;
|
|
2691
|
+
submittedReplyText = normalized;
|
|
2680
2692
|
replied = true;
|
|
2681
2693
|
return true;
|
|
2682
2694
|
};
|
|
@@ -2834,6 +2846,7 @@ function createDeliveryReplyDispatcher({
|
|
|
2834
2846
|
markDispatchIdle,
|
|
2835
2847
|
didReply: () => replied,
|
|
2836
2848
|
didKeepSilent: () => keptSilent,
|
|
2849
|
+
getSubmittedReplyText: () => submittedReplyText,
|
|
2837
2850
|
submitMessageToolReply,
|
|
2838
2851
|
getRuntimeOutputSummary: () => ({
|
|
2839
2852
|
counts: { ...runtimeOutputSummary.counts },
|
|
@@ -2881,6 +2894,7 @@ async function runDeliveryReplyDispatch({
|
|
|
2881
2894
|
markDispatchIdle,
|
|
2882
2895
|
didReply,
|
|
2883
2896
|
didKeepSilent,
|
|
2897
|
+
getSubmittedReplyText,
|
|
2884
2898
|
submitMessageToolReply,
|
|
2885
2899
|
getRuntimeOutputSummary,
|
|
2886
2900
|
} = createDeliveryReplyDispatcher({
|
|
@@ -2947,6 +2961,7 @@ async function runDeliveryReplyDispatch({
|
|
|
2947
2961
|
dispatchResult,
|
|
2948
2962
|
replied: didReply(),
|
|
2949
2963
|
keptSilent: didKeepSilent(),
|
|
2964
|
+
replyText: getSubmittedReplyText(),
|
|
2950
2965
|
runtimeOutputSummary: getRuntimeOutputSummary(),
|
|
2951
2966
|
};
|
|
2952
2967
|
}
|
|
@@ -3020,6 +3035,19 @@ async function maybeBridgeRuntimeInboundEvent({
|
|
|
3020
3035
|
const fromAgentId = resolveNormalizedText(metadata.fromAgentId, null);
|
|
3021
3036
|
const isRelayDelivery = eventType === 'delivery';
|
|
3022
3037
|
const allowReply = metadata.allowReply === true || (isRelayDelivery && metadata.allowReply !== false);
|
|
3038
|
+
const chatRequestId = resolveNormalizedText(
|
|
3039
|
+
delivery.chatRequestId,
|
|
3040
|
+
resolveNormalizedText(
|
|
3041
|
+
payload.chatRequestId,
|
|
3042
|
+
resolveNormalizedText(
|
|
3043
|
+
payload.requestId,
|
|
3044
|
+
resolveNormalizedText(
|
|
3045
|
+
metadata.kickoffRequestId,
|
|
3046
|
+
resolveNormalizedText(metadata.requestId, resolveNormalizedText(metadata.chatRequestId, null)),
|
|
3047
|
+
),
|
|
3048
|
+
),
|
|
3049
|
+
),
|
|
3050
|
+
);
|
|
3023
3051
|
|
|
3024
3052
|
if (
|
|
3025
3053
|
!runtime?.channel?.reply?.finalizeInboundContext
|
|
@@ -3127,6 +3155,7 @@ async function maybeBridgeRuntimeInboundEvent({
|
|
|
3127
3155
|
WasMentioned: false,
|
|
3128
3156
|
CommandAuthorized: commandAuthorized,
|
|
3129
3157
|
RelayDeliveryId: isRelayDelivery ? deliveryId : null,
|
|
3158
|
+
RelayChatRequestId: chatRequestId,
|
|
3130
3159
|
RelayFromAgentId: fromAgentId,
|
|
3131
3160
|
RelayConversationKey: metadata.conversationKey || null,
|
|
3132
3161
|
UntrustedContext,
|
|
@@ -3187,6 +3216,7 @@ async function maybeBridgeRuntimeInboundEvent({
|
|
|
3187
3216
|
dispatchResult,
|
|
3188
3217
|
replied,
|
|
3189
3218
|
keptSilent,
|
|
3219
|
+
replyText,
|
|
3190
3220
|
runtimeOutputSummary,
|
|
3191
3221
|
} = await runDeliveryReplyDispatch({
|
|
3192
3222
|
runtime,
|
|
@@ -3236,6 +3266,7 @@ async function maybeBridgeRuntimeInboundEvent({
|
|
|
3236
3266
|
dispatchResult,
|
|
3237
3267
|
replied,
|
|
3238
3268
|
keptSilent,
|
|
3269
|
+
replyText,
|
|
3239
3270
|
runtimeOutputSummary,
|
|
3240
3271
|
} = await runDeliveryReplyDispatch({
|
|
3241
3272
|
runtime,
|
|
@@ -3287,6 +3318,40 @@ async function maybeBridgeRuntimeInboundEvent({
|
|
|
3287
3318
|
error: error?.message || String(error),
|
|
3288
3319
|
});
|
|
3289
3320
|
}
|
|
3321
|
+
|
|
3322
|
+
if (isRelayDelivery) {
|
|
3323
|
+
if (chatRequestId) {
|
|
3324
|
+
try {
|
|
3325
|
+
await recordClaworldTranscriptEpisode(workspaceRoot, {
|
|
3326
|
+
chatRequestId,
|
|
3327
|
+
deliveryId,
|
|
3328
|
+
localSessionKey,
|
|
3329
|
+
relaySessionKey: sessionKey,
|
|
3330
|
+
conversationKey: resolveNormalizedText(metadata.conversationKey, resolveNormalizedText(delivery.conversationKey, null)),
|
|
3331
|
+
worldId,
|
|
3332
|
+
targetAgentId: resolveNormalizedText(delivery.targetAgentId, resolveNormalizedText(payload.targetAgentId, null)),
|
|
3333
|
+
fromAgentId,
|
|
3334
|
+
fromAgentCode: metadata.fromAgentCode,
|
|
3335
|
+
fromDisplayIdentity: metadata.fromDisplayIdentity,
|
|
3336
|
+
localAgentId,
|
|
3337
|
+
deliveryType: metadata.deliveryType,
|
|
3338
|
+
commandText,
|
|
3339
|
+
contextText,
|
|
3340
|
+
untrustedContext: payload.untrustedContext,
|
|
3341
|
+
createdAt: delivery.createdAt || metadata.createdAt || payload.createdAt || null,
|
|
3342
|
+
turnCreatedAt: delivery.turnCreatedAt || metadata.turnCreatedAt || payload.turnCreatedAt || null,
|
|
3343
|
+
replyText,
|
|
3344
|
+
});
|
|
3345
|
+
} catch (error) {
|
|
3346
|
+
logger.warn?.(`[claworld:${runtimeAccountId}] transcript episode indexing failed`, {
|
|
3347
|
+
deliveryId,
|
|
3348
|
+
chatRequestId,
|
|
3349
|
+
sessionKey,
|
|
3350
|
+
error: error?.message || String(error),
|
|
3351
|
+
});
|
|
3352
|
+
}
|
|
3353
|
+
}
|
|
3354
|
+
}
|
|
3290
3355
|
}
|
|
3291
3356
|
|
|
3292
3357
|
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,87 @@ async function deliverShareCardToCurrentChannel(api, result, toolContext = {}) {
|
|
|
129
133
|
return { ...result, content };
|
|
130
134
|
}
|
|
131
135
|
|
|
136
|
+
async function deliverTranscriptPagesToCurrentChannel(api, report, toolContext = {}) {
|
|
137
|
+
const pngPages = Array.isArray(report?.artifacts?.pngPages)
|
|
138
|
+
? report.artifacts.pngPages.filter((page) => normalizeText(page?.path, null))
|
|
139
|
+
: [];
|
|
140
|
+
if (pngPages.length === 0) return report;
|
|
141
|
+
|
|
142
|
+
const route = toolContext?.deliveryContext || {};
|
|
143
|
+
const channel = normalizeText(route.channel, normalizeText(toolContext?.messageChannel, null));
|
|
144
|
+
const to = normalizeText(route.to, null);
|
|
145
|
+
if (!channel || !to || channel === 'claworld') {
|
|
146
|
+
return {
|
|
147
|
+
...report,
|
|
148
|
+
delivery: {
|
|
149
|
+
status: 'media_refs_ready',
|
|
150
|
+
pageCount: pngPages.length,
|
|
151
|
+
description: 'PNG pages are ready. In Management Session, copy deliveryHint.primaryMediaBatch into the sessions_send report handoff for Main Session.',
|
|
152
|
+
},
|
|
153
|
+
};
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
const loadAdapter = api?.runtime?.channel?.outbound?.loadAdapter;
|
|
157
|
+
if (typeof loadAdapter !== 'function') {
|
|
158
|
+
throw new Error('transcript delivery requires the OpenClaw channel outbound runtime');
|
|
159
|
+
}
|
|
160
|
+
const adapter = await loadAdapter(channel);
|
|
161
|
+
if (typeof adapter?.sendMedia !== 'function') {
|
|
162
|
+
throw new Error(`transcript delivery is unavailable for channel ${channel}`);
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
const receipts = [];
|
|
166
|
+
for (const page of pngPages) {
|
|
167
|
+
const deliveryResult = await adapter.sendMedia({
|
|
168
|
+
cfg: toolContext?.getRuntimeConfig?.() || toolContext?.runtimeConfig || api.config,
|
|
169
|
+
to,
|
|
170
|
+
text: '',
|
|
171
|
+
mediaUrl: page.path,
|
|
172
|
+
...(normalizeText(route.accountId, null) ? { accountId: normalizeText(route.accountId, null) } : {}),
|
|
173
|
+
...(route.threadId != null ? { threadId: route.threadId } : {}),
|
|
174
|
+
});
|
|
175
|
+
if (deliveryResult?.success === false) {
|
|
176
|
+
throw new Error(`transcript page ${page.page} delivery failed on channel ${channel}`);
|
|
177
|
+
}
|
|
178
|
+
const deliveryKind = normalizeText(deliveryResult?.receipt?.kind, null)?.toLowerCase();
|
|
179
|
+
if (deliveryKind && !['image', 'media'].includes(deliveryKind)) {
|
|
180
|
+
throw new Error(`transcript page ${page.page} delivery did not produce native media on channel ${channel}`);
|
|
181
|
+
}
|
|
182
|
+
receipts.push({
|
|
183
|
+
page: page.page,
|
|
184
|
+
messageId: normalizeText(deliveryResult?.messageId, null),
|
|
185
|
+
kind: deliveryKind || 'media',
|
|
186
|
+
});
|
|
187
|
+
}
|
|
188
|
+
return {
|
|
189
|
+
...report,
|
|
190
|
+
delivery: {
|
|
191
|
+
status: 'sent',
|
|
192
|
+
channel,
|
|
193
|
+
pageCount: receipts.length,
|
|
194
|
+
receipts,
|
|
195
|
+
description: 'All PNG transcript pages were sent through the current user-facing channel. Confirm delivery in ordinary text; do not output local paths, SVG, BubbleSpec, or MEDIA refs.',
|
|
196
|
+
},
|
|
197
|
+
};
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
async function resolveTranscriptWorkspace(api, params = {}, toolContext = {}) {
|
|
201
|
+
const cfg = await loadCurrentConfig(api);
|
|
202
|
+
const agentId = normalizeText(
|
|
203
|
+
toolContext?.agentId,
|
|
204
|
+
normalizeText(toolContext?.context?.agentId, normalizeText(params.agentId, null)),
|
|
205
|
+
);
|
|
206
|
+
return {
|
|
207
|
+
cfg,
|
|
208
|
+
agentId,
|
|
209
|
+
workspaceRoot: resolveOpenClawWorkspaceRoot({
|
|
210
|
+
sources: [toolContext, toolContext?.context, params],
|
|
211
|
+
config: cfg,
|
|
212
|
+
agentId,
|
|
213
|
+
}),
|
|
214
|
+
};
|
|
215
|
+
}
|
|
216
|
+
|
|
132
217
|
function buildClaworldStatusRoute(plugin) {
|
|
133
218
|
return {
|
|
134
219
|
method: 'GET',
|
|
@@ -652,6 +737,7 @@ function createTerminalToolAdapters(api, plugin, internalTools) {
|
|
|
652
737
|
const searchTool = 'claworld_search';
|
|
653
738
|
const manageWorldsTool = 'claworld_manage_worlds';
|
|
654
739
|
const manageConversationsTool = 'claworld_manage_conversations';
|
|
740
|
+
const renderTranscriptTool = 'claworld_render_transcript_report';
|
|
655
741
|
const accountTool = 'claworld_manage_account';
|
|
656
742
|
const publicProfileTool = 'claworld_get_public_profile';
|
|
657
743
|
|
|
@@ -1409,7 +1495,7 @@ function createTerminalToolAdapters(api, plugin, internalTools) {
|
|
|
1409
1495
|
}),
|
|
1410
1496
|
},
|
|
1411
1497
|
}),
|
|
1412
|
-
async execute(toolCallId, params = {}) {
|
|
1498
|
+
async execute(toolCallId, params = {}, toolContext = {}) {
|
|
1413
1499
|
const action = normalizeTerminalConversationAction(params.action, 'list_related', { throwOnInvalid: true });
|
|
1414
1500
|
if (action === 'request') {
|
|
1415
1501
|
const context = await resolveToolContext(api, plugin, params, {
|
|
@@ -1439,7 +1525,16 @@ function createTerminalToolAdapters(api, plugin, internalTools) {
|
|
|
1439
1525
|
action: 'list',
|
|
1440
1526
|
...(Object.keys(filters).length > 0 ? { filters } : {}),
|
|
1441
1527
|
});
|
|
1442
|
-
|
|
1528
|
+
const rewritten = rewriteToolResultName(result, manageConversationsTool, action);
|
|
1529
|
+
const payload = parseToolResultPayload(rewritten);
|
|
1530
|
+
if (!payload) return rewritten;
|
|
1531
|
+
const { workspaceRoot } = await resolveTranscriptWorkspace(api, params, toolContext);
|
|
1532
|
+
const augmented = await augmentConversationPayloadWithLocalTranscriptIndex({
|
|
1533
|
+
workspaceRoot,
|
|
1534
|
+
payload,
|
|
1535
|
+
filters,
|
|
1536
|
+
});
|
|
1537
|
+
return buildToolResult(augmented);
|
|
1443
1538
|
}
|
|
1444
1539
|
if (action === 'accept' || action === 'reject') {
|
|
1445
1540
|
const result = await requireTerminalTool(internalTools, 'claworld_chat_inbox').execute(toolCallId, {
|
|
@@ -1470,6 +1565,100 @@ function createTerminalToolAdapters(api, plugin, internalTools) {
|
|
|
1470
1565
|
return buildToolResult({ status: 'error', tool: manageConversationsTool });
|
|
1471
1566
|
},
|
|
1472
1567
|
},
|
|
1568
|
+
{
|
|
1569
|
+
name: renderTranscriptTool,
|
|
1570
|
+
label: 'Claworld Render Transcript Report',
|
|
1571
|
+
description: '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.',
|
|
1572
|
+
metadata: buildToolMetadata({
|
|
1573
|
+
category: 'conversation',
|
|
1574
|
+
usageNotes: [
|
|
1575
|
+
'Use stored.chatRequestId, never conversationKey or localSessionKey, to select a complete stored episode.',
|
|
1576
|
+
'Keep request, conversation, session, and agent ids out of stored title/profile/speaker-label overrides.',
|
|
1577
|
+
'For manual mode, provide only ordered visible peer/local messages and accurate createdAt timestamps.',
|
|
1578
|
+
'Main Session calls send all PNG pages through the current user-facing channel when available.',
|
|
1579
|
+
'Management Session calls return deliveryHint.primaryMediaBatch for inclusion in the sessions_send report handoff.',
|
|
1580
|
+
],
|
|
1581
|
+
}),
|
|
1582
|
+
parameters: objectParam({
|
|
1583
|
+
description: 'Claworld transcript report render request.',
|
|
1584
|
+
required: ['mode'],
|
|
1585
|
+
properties: {
|
|
1586
|
+
mode: stringParam({
|
|
1587
|
+
description: 'stored renders one indexed local episode by chatRequestId; manual renders exactly manual.messages.',
|
|
1588
|
+
enumValues: ['stored', 'manual'],
|
|
1589
|
+
}),
|
|
1590
|
+
stored: objectParam({
|
|
1591
|
+
description: 'Stored episode selector. Provide only with mode=stored.',
|
|
1592
|
+
required: ['chatRequestId'],
|
|
1593
|
+
properties: {
|
|
1594
|
+
chatRequestId: stringParam({
|
|
1595
|
+
description: 'Canonical Claworld chat request / episode id.',
|
|
1596
|
+
minLength: 1,
|
|
1597
|
+
}),
|
|
1598
|
+
title: stringParam({
|
|
1599
|
+
description: 'Optional human-readable report title. Defaults to public peer/world context from the indexed kickoff.',
|
|
1600
|
+
minLength: 1,
|
|
1601
|
+
}),
|
|
1602
|
+
peerProfile: stringParam({
|
|
1603
|
+
description: 'Optional public subtitle/profile. Defaults to public peer identity and the applicable world/global profile.',
|
|
1604
|
+
minLength: 1,
|
|
1605
|
+
}),
|
|
1606
|
+
localLabel: stringParam({
|
|
1607
|
+
description: 'Optional public speaker label for local/right messages.',
|
|
1608
|
+
minLength: 1,
|
|
1609
|
+
}),
|
|
1610
|
+
peerLabel: stringParam({
|
|
1611
|
+
description: 'Optional public speaker label for peer/left messages.',
|
|
1612
|
+
minLength: 1,
|
|
1613
|
+
}),
|
|
1614
|
+
},
|
|
1615
|
+
}),
|
|
1616
|
+
manual: objectParam({
|
|
1617
|
+
description: 'Exact visible transcript rows and header context. Provide only with mode=manual.',
|
|
1618
|
+
required: ['messages', 'title', 'peerProfile', 'localLabel', 'peerLabel'],
|
|
1619
|
+
properties: {
|
|
1620
|
+
messages: arrayParam({
|
|
1621
|
+
description: 'Ordered visible transcript rows.',
|
|
1622
|
+
items: objectParam({
|
|
1623
|
+
required: ['from', 'text', 'createdAt'],
|
|
1624
|
+
properties: {
|
|
1625
|
+
from: stringParam({ description: 'peer=left; local=right.', enumValues: ['peer', 'local'] }),
|
|
1626
|
+
text: stringParam({ description: 'Visible message text.', minLength: 1 }),
|
|
1627
|
+
createdAt: stringParam({ description: 'Message timestamp, preferably ISO 8601.', minLength: 1 }),
|
|
1628
|
+
},
|
|
1629
|
+
}),
|
|
1630
|
+
}),
|
|
1631
|
+
title: stringParam({ description: 'Report header title.', minLength: 1 }),
|
|
1632
|
+
peerProfile: stringParam({ description: 'Peer profile/header subtitle appropriate to direct or world scope.', minLength: 1 }),
|
|
1633
|
+
localLabel: stringParam({ description: 'Speaker label for local/right messages.', minLength: 1 }),
|
|
1634
|
+
peerLabel: stringParam({ description: 'Speaker label for peer/left messages.', minLength: 1 }),
|
|
1635
|
+
},
|
|
1636
|
+
}),
|
|
1637
|
+
style: stringParam({
|
|
1638
|
+
description: 'Optional visual style. Defaults to the Hermes-compatible Claworld comic grid.',
|
|
1639
|
+
enumValues: ['claworld-comic-grid'],
|
|
1640
|
+
}),
|
|
1641
|
+
maxPageHeight: integerParam({
|
|
1642
|
+
description: 'Maximum page height in pixels. Long transcripts paginate without truncation.',
|
|
1643
|
+
minimum: 900,
|
|
1644
|
+
maximum: 8000,
|
|
1645
|
+
}),
|
|
1646
|
+
},
|
|
1647
|
+
}),
|
|
1648
|
+
async execute(_toolCallId, params = {}, toolContext = {}) {
|
|
1649
|
+
const { workspaceRoot, agentId } = await resolveTranscriptWorkspace(api, params, toolContext);
|
|
1650
|
+
if (!workspaceRoot) {
|
|
1651
|
+
throw new Error('unable to resolve the active OpenClaw workspace for transcript rendering');
|
|
1652
|
+
}
|
|
1653
|
+
const report = await renderTranscriptReport({
|
|
1654
|
+
workspaceRoot,
|
|
1655
|
+
localAgentId: agentId,
|
|
1656
|
+
args: params,
|
|
1657
|
+
});
|
|
1658
|
+
const delivered = await deliverTranscriptPagesToCurrentChannel(api, report, toolContext);
|
|
1659
|
+
return buildToolResult({ ...delivered, tool: renderTranscriptTool });
|
|
1660
|
+
},
|
|
1661
|
+
},
|
|
1473
1662
|
];
|
|
1474
1663
|
}
|
|
1475
1664
|
|
|
@@ -2527,6 +2716,22 @@ export function registerClaworldPluginFull(api, plugin) {
|
|
|
2527
2716
|
);
|
|
2528
2717
|
continue;
|
|
2529
2718
|
}
|
|
2719
|
+
if (
|
|
2720
|
+
terminalTool.name === 'claworld_manage_conversations'
|
|
2721
|
+
|| terminalTool.name === 'claworld_render_transcript_report'
|
|
2722
|
+
) {
|
|
2723
|
+
api.registerTool(
|
|
2724
|
+
(toolContext) => ({
|
|
2725
|
+
...terminalTool,
|
|
2726
|
+
execute: withToolErrorBoundary(
|
|
2727
|
+
terminalTool.name,
|
|
2728
|
+
async (...args) => await terminalTool.execute(...args, toolContext),
|
|
2729
|
+
),
|
|
2730
|
+
}),
|
|
2731
|
+
{ name: terminalTool.name },
|
|
2732
|
+
);
|
|
2733
|
+
continue;
|
|
2734
|
+
}
|
|
2530
2735
|
api.registerTool({
|
|
2531
2736
|
...terminalTool,
|
|
2532
2737
|
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)),
|