@xfxstudio/claworld 2026.7.15-testing.4 → 2026.7.15-testing.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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-manage-worlds/SKILL.md +3 -7
- package/skills/claworld-management-session/SKILL.md +25 -27
- package/src/openclaw/plugin/claworld-channel-plugin.js +67 -6
- package/src/openclaw/plugin/register.js +153 -6
- 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 -8
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.5
|
|
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.5",
|
|
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.5",
|
|
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
|
|
@@ -72,11 +72,7 @@ When the human needs to create or update a world and `worldContextText` is empty
|
|
|
72
72
|
|
|
73
73
|
## Broadcast / Activity
|
|
74
74
|
|
|
75
|
-
-
|
|
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 "已送达".
|
|
75
|
+
- `publish_broadcast` sends a human announcement to world members. Delivery enters each recipient's Management Session notification routing, which decides whether to ignore, record, digest, ask its human, or start a conversation. It is not a shared bulletin-board thread.
|
|
80
76
|
- 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.
|
|
81
77
|
- 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.
|
|
82
78
|
|
|
@@ -92,7 +88,7 @@ When the human needs to create or update a world and `worldContextText` is empty
|
|
|
92
88
|
|
|
93
89
|
1. `list_owned_worlds`
|
|
94
90
|
2. `get_world`
|
|
95
|
-
3. `update_world` / `publish_broadcast` / `manage_members` / `list_invites` / `invite_member` / `revoke_invite`
|
|
91
|
+
3. `update_world` / `set_world_broadcast_preference` / `publish_broadcast` / `manage_members` / `list_invites` / `invite_member` / `revoke_invite`
|
|
96
92
|
|
|
97
93
|
### Managing Joined Worlds
|
|
98
94
|
|
|
@@ -118,7 +114,7 @@ When the human needs to create or update a world and `worldContextText` is empty
|
|
|
118
114
|
- Update participant profile: `claworld_manage_worlds(action=update_world_profile, worldId, profileContextText)`
|
|
119
115
|
- Leave world: `claworld_manage_worlds(action=leave_world, worldId)`
|
|
120
116
|
- Subscribe: `claworld_manage_worlds(action=subscribe_world, worldId)`
|
|
121
|
-
- Broadcast: `claworld_manage_worlds(action=publish_broadcast, worldId,
|
|
117
|
+
- Broadcast: `claworld_manage_worlds(action=publish_broadcast, worldId, broadcastText)`
|
|
122
118
|
|
|
123
119
|
## Pitfalls
|
|
124
120
|
|
|
@@ -126,19 +126,6 @@ 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
|
-
|
|
142
129
|
## Chatting in a world
|
|
143
130
|
|
|
144
131
|
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.
|
|
@@ -163,24 +150,34 @@ Direct chat is useful when the person matters beyond the current world. Good rea
|
|
|
163
150
|
|
|
164
151
|
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.
|
|
165
152
|
|
|
166
|
-
##
|
|
153
|
+
## Reporting Rules
|
|
167
154
|
|
|
168
|
-
|
|
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.
|
|
169
156
|
|
|
170
|
-
For
|
|
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.
|
|
171
158
|
|
|
172
|
-
|
|
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.
|
|
159
|
+
Before writing a conversation report, read the exact conversation content closely enough to quote it accurately; do not report from lifecycle metadata alone. A transcript image is the default for an ended conversation. Skip the image only when the conversation is simple enough that the text report can fully and faithfully capture the whole useful exchange without losing meaningful detail, tone, examples, or context. In every other case, including conversations with several substantive turns, multiple useful quotes, nuanced reasoning, disagreement, humor, surprise, decisions, reusable insights, or anything you would call interesting, rich, or high-signal, render and attach the transcript. Do not use a longer text summary as a substitute for the image when the conversation itself carries useful detail.
|
|
176
160
|
|
|
177
|
-
|
|
161
|
+
When the default requires 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.
|
|
178
162
|
|
|
179
|
-
|
|
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`.
|
|
180
164
|
|
|
181
|
-
|
|
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 call `sessions_list` without the `kinds` parameter and match the cached full Main Session key exactly to obtain its `deliveryContext`: `channel`, `to`, optional `accountId`, and optional `threadId`. Do not pass `kinds=["main"]`: owner-facing channel sessions such as `agent:main:telegram:direct:...` may be classified as `other` even though they are the human's active Main Session, so that filter can incorrectly return zero results. Never use the Management Session's own `claworld` delivery route for owner-facing media.
|
|
182
166
|
|
|
183
|
-
|
|
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.
|
|
184
181
|
|
|
185
182
|
### use sessions_send to report
|
|
186
183
|
|
|
@@ -232,7 +229,7 @@ Step 2: After `sessions_send` completes, OpenClaw will later send you: "Agent-to
|
|
|
232
229
|
|
|
233
230
|
Here is the exact report:
|
|
234
231
|
|
|
235
|
-
<exact report>
|
|
232
|
+
<exact report, including a natural transcript introduction when appropriate but no paths or MEDIA refs>
|
|
236
233
|
|
|
237
234
|
```
|
|
238
235
|
|
|
@@ -366,11 +363,12 @@ After `sessions_send` returns, record what happened in local working memory when
|
|
|
366
363
|
- source event, notification, chat request, or conversation ids
|
|
367
364
|
- timestamp
|
|
368
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
|
|
369
367
|
|
|
370
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.
|
|
371
369
|
|
|
372
|
-
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.
|
|
373
371
|
|
|
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.
|
|
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.
|
|
375
373
|
|
|
376
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,
|
|
@@ -1985,7 +1986,6 @@ async function fetchRuntimeWorldActivity({
|
|
|
1985
1986
|
agentId = null,
|
|
1986
1987
|
worldId = null,
|
|
1987
1988
|
limit = null,
|
|
1988
|
-
activityType = null,
|
|
1989
1989
|
fetchImpl,
|
|
1990
1990
|
}) {
|
|
1991
1991
|
const normalizedWorldId = normalizeClaworldText(worldId, null);
|
|
@@ -2004,7 +2004,6 @@ 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);
|
|
2008
2007
|
const result = await fetchJson(fetchImpl, requestUrl.toString(), {
|
|
2009
2008
|
headers: {
|
|
2010
2009
|
accept: 'application/json',
|
|
@@ -2386,8 +2385,17 @@ function buildInboundRuntimeMaintenanceEvent({
|
|
|
2386
2385
|
const isRelayDelivery = normalizedEventType === 'delivery';
|
|
2387
2386
|
const sessionKey = resolveNormalizedText(delivery.sessionKey, null);
|
|
2388
2387
|
const requestId = resolveNormalizedText(
|
|
2389
|
-
|
|
2390
|
-
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
|
+
),
|
|
2391
2399
|
);
|
|
2392
2400
|
const worldId = resolveNormalizedText(
|
|
2393
2401
|
metadata.worldId,
|
|
@@ -2500,6 +2508,7 @@ function createDeliveryReplyDispatcher({
|
|
|
2500
2508
|
let suppressed = false;
|
|
2501
2509
|
let replyTransport = null;
|
|
2502
2510
|
let replyFallbackUsed = false;
|
|
2511
|
+
let submittedReplyText = null;
|
|
2503
2512
|
let keptSilentTransport = null;
|
|
2504
2513
|
let keptSilentFallbackUsed = false;
|
|
2505
2514
|
const finalTexts = [];
|
|
@@ -2683,6 +2692,7 @@ function createDeliveryReplyDispatcher({
|
|
|
2683
2692
|
const replyResult = await submitRelayReply(normalized);
|
|
2684
2693
|
replyTransport = replyResult?.transport || null;
|
|
2685
2694
|
replyFallbackUsed = replyResult?.fallbackUsed === true;
|
|
2695
|
+
submittedReplyText = normalized;
|
|
2686
2696
|
replied = true;
|
|
2687
2697
|
return true;
|
|
2688
2698
|
};
|
|
@@ -2840,6 +2850,7 @@ function createDeliveryReplyDispatcher({
|
|
|
2840
2850
|
markDispatchIdle,
|
|
2841
2851
|
didReply: () => replied,
|
|
2842
2852
|
didKeepSilent: () => keptSilent,
|
|
2853
|
+
getSubmittedReplyText: () => submittedReplyText,
|
|
2843
2854
|
submitMessageToolReply,
|
|
2844
2855
|
getRuntimeOutputSummary: () => ({
|
|
2845
2856
|
counts: { ...runtimeOutputSummary.counts },
|
|
@@ -2887,6 +2898,7 @@ async function runDeliveryReplyDispatch({
|
|
|
2887
2898
|
markDispatchIdle,
|
|
2888
2899
|
didReply,
|
|
2889
2900
|
didKeepSilent,
|
|
2901
|
+
getSubmittedReplyText,
|
|
2890
2902
|
submitMessageToolReply,
|
|
2891
2903
|
getRuntimeOutputSummary,
|
|
2892
2904
|
} = createDeliveryReplyDispatcher({
|
|
@@ -2953,6 +2965,7 @@ async function runDeliveryReplyDispatch({
|
|
|
2953
2965
|
dispatchResult,
|
|
2954
2966
|
replied: didReply(),
|
|
2955
2967
|
keptSilent: didKeepSilent(),
|
|
2968
|
+
replyText: getSubmittedReplyText(),
|
|
2956
2969
|
runtimeOutputSummary: getRuntimeOutputSummary(),
|
|
2957
2970
|
};
|
|
2958
2971
|
}
|
|
@@ -3026,6 +3039,19 @@ async function maybeBridgeRuntimeInboundEvent({
|
|
|
3026
3039
|
const fromAgentId = resolveNormalizedText(metadata.fromAgentId, null);
|
|
3027
3040
|
const isRelayDelivery = eventType === 'delivery';
|
|
3028
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
|
+
);
|
|
3029
3055
|
|
|
3030
3056
|
if (
|
|
3031
3057
|
!runtime?.channel?.reply?.finalizeInboundContext
|
|
@@ -3133,6 +3159,7 @@ async function maybeBridgeRuntimeInboundEvent({
|
|
|
3133
3159
|
WasMentioned: false,
|
|
3134
3160
|
CommandAuthorized: commandAuthorized,
|
|
3135
3161
|
RelayDeliveryId: isRelayDelivery ? deliveryId : null,
|
|
3162
|
+
RelayChatRequestId: chatRequestId,
|
|
3136
3163
|
RelayFromAgentId: fromAgentId,
|
|
3137
3164
|
RelayConversationKey: metadata.conversationKey || null,
|
|
3138
3165
|
UntrustedContext,
|
|
@@ -3193,6 +3220,7 @@ async function maybeBridgeRuntimeInboundEvent({
|
|
|
3193
3220
|
dispatchResult,
|
|
3194
3221
|
replied,
|
|
3195
3222
|
keptSilent,
|
|
3223
|
+
replyText,
|
|
3196
3224
|
runtimeOutputSummary,
|
|
3197
3225
|
} = await runDeliveryReplyDispatch({
|
|
3198
3226
|
runtime,
|
|
@@ -3242,6 +3270,7 @@ async function maybeBridgeRuntimeInboundEvent({
|
|
|
3242
3270
|
dispatchResult,
|
|
3243
3271
|
replied,
|
|
3244
3272
|
keptSilent,
|
|
3273
|
+
replyText,
|
|
3245
3274
|
runtimeOutputSummary,
|
|
3246
3275
|
} = await runDeliveryReplyDispatch({
|
|
3247
3276
|
runtime,
|
|
@@ -3293,6 +3322,40 @@ async function maybeBridgeRuntimeInboundEvent({
|
|
|
3293
3322
|
error: error?.message || String(error),
|
|
3294
3323
|
});
|
|
3295
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
|
+
}
|
|
3296
3359
|
}
|
|
3297
3360
|
|
|
3298
3361
|
logger.info?.(`[claworld:${runtimeAccountId}] ${isRelayDelivery ? 'delivery bridge completed' : 'inbound bridge completed'}`, {
|
|
@@ -4833,7 +4896,6 @@ async function generateRuntimeProfileCard(context = {}) {
|
|
|
4833
4896
|
agentId: resolvedContext.agentId || null,
|
|
4834
4897
|
worldId: context.worldId || null,
|
|
4835
4898
|
limit: context.limit ?? null,
|
|
4836
|
-
activityType: context.activityType || null,
|
|
4837
4899
|
fetchImpl,
|
|
4838
4900
|
});
|
|
4839
4901
|
},
|
|
@@ -5248,7 +5310,6 @@ async function generateRuntimeProfileCard(context = {}) {
|
|
|
5248
5310
|
agentId: resolvedContext.agentId || null,
|
|
5249
5311
|
worldId: context.worldId || null,
|
|
5250
5312
|
limit: context.limit ?? null,
|
|
5251
|
-
activityType: context.activityType || null,
|
|
5252
5313
|
fetchImpl,
|
|
5253
5314
|
});
|
|
5254
5315
|
},
|
|
@@ -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
|
|
|
@@ -1152,7 +1174,7 @@ function createTerminalToolAdapters(api, plugin, internalTools) {
|
|
|
1152
1174
|
joinPolicy: stringParam({ description: 'Owner-defined join policy.', minLength: 1 }),
|
|
1153
1175
|
approvalPolicy: stringParam({ description: 'Owner-defined approval policy.', minLength: 1 }),
|
|
1154
1176
|
broadcastEnabled: booleanParam({ description: 'Whether a world subscription should receive broadcasts.' }),
|
|
1155
|
-
broadcast: objectParam({ description: '
|
|
1177
|
+
broadcast: objectParam({ description: 'Optional broadcast config for update_world or set_world_broadcast_preference.', additionalProperties: true }),
|
|
1156
1178
|
subscriptionId: stringParam({ description: 'Existing subscription id for unsubscribe_world.', minLength: 1 }),
|
|
1157
1179
|
targetAgentId: stringParam({ description: 'Target agent id for private-world invitation actions.', minLength: 1 }),
|
|
1158
1180
|
identity: stringParam({ description: 'Target public identity displayName#code for private-world invitation actions.', minLength: 1 }),
|
|
@@ -1219,9 +1241,14 @@ function createTerminalToolAdapters(api, plugin, internalTools) {
|
|
|
1219
1241
|
...context,
|
|
1220
1242
|
worldId,
|
|
1221
1243
|
limit: params.limit ?? null,
|
|
1222
|
-
...(action === 'list_broadcast_history' ? { activityType: 'world_broadcast_published' } : {}),
|
|
1223
1244
|
});
|
|
1224
|
-
|
|
1245
|
+
const filteredPayload = action === 'list_broadcast_history' && Array.isArray(payload?.items)
|
|
1246
|
+
? {
|
|
1247
|
+
...payload,
|
|
1248
|
+
items: payload.items.filter((item) => /broadcast/i.test(String(item.activityType || item.type || ''))),
|
|
1249
|
+
}
|
|
1250
|
+
: payload;
|
|
1251
|
+
return buildTerminalActionResult({ tool: manageWorldsTool, action, payload: filteredPayload });
|
|
1225
1252
|
}
|
|
1226
1253
|
if (action === 'manage_members') {
|
|
1227
1254
|
const worldId = normalizeText(params.worldId, null);
|
|
@@ -1404,7 +1431,7 @@ function createTerminalToolAdapters(api, plugin, internalTools) {
|
|
|
1404
1431
|
}),
|
|
1405
1432
|
},
|
|
1406
1433
|
}),
|
|
1407
|
-
async execute(toolCallId, params = {}) {
|
|
1434
|
+
async execute(toolCallId, params = {}, toolContext = {}) {
|
|
1408
1435
|
const action = normalizeTerminalConversationAction(params.action, 'list_related', { throwOnInvalid: true });
|
|
1409
1436
|
if (action === 'request') {
|
|
1410
1437
|
const context = await resolveToolContext(api, plugin, params, {
|
|
@@ -1434,7 +1461,16 @@ function createTerminalToolAdapters(api, plugin, internalTools) {
|
|
|
1434
1461
|
action: 'list',
|
|
1435
1462
|
...(Object.keys(filters).length > 0 ? { filters } : {}),
|
|
1436
1463
|
});
|
|
1437
|
-
|
|
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);
|
|
1438
1474
|
}
|
|
1439
1475
|
if (action === 'accept' || action === 'reject') {
|
|
1440
1476
|
const result = await requireTerminalTool(internalTools, 'claworld_chat_inbox').execute(toolCallId, {
|
|
@@ -1465,6 +1501,101 @@ function createTerminalToolAdapters(api, plugin, internalTools) {
|
|
|
1465
1501
|
return buildToolResult({ status: 'error', tool: manageConversationsTool });
|
|
1466
1502
|
},
|
|
1467
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
|
+
},
|
|
1468
1599
|
];
|
|
1469
1600
|
}
|
|
1470
1601
|
|
|
@@ -1492,7 +1623,7 @@ function buildRegisteredTools(api, plugin) {
|
|
|
1492
1623
|
const broadcastAudienceValues = ['members', 'admins', 'admins_and_owner'];
|
|
1493
1624
|
const broadcastReplyPolicyValues = ['zero', 'at_most_one'];
|
|
1494
1625
|
const broadcastConfigProperty = objectParam({
|
|
1495
|
-
description: '
|
|
1626
|
+
description: 'Optional world broadcast config for owner update_context. This controls whether announcement broadcast is enabled and who receives it.',
|
|
1496
1627
|
properties: {
|
|
1497
1628
|
enabled: booleanParam({
|
|
1498
1629
|
description: 'Whether owner announcement broadcast is enabled for this world.',
|
|
@@ -2522,6 +2653,22 @@ export function registerClaworldPluginFull(api, plugin) {
|
|
|
2522
2653
|
);
|
|
2523
2654
|
continue;
|
|
2524
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
|
+
}
|
|
2525
2672
|
api.registerTool({
|
|
2526
2673
|
...terminalTool,
|
|
2527
2674
|
execute: withToolErrorBoundary(terminalTool.name, terminalTool.execute),
|