@clawling/clawchat-plugin-openclaw 2026.10.9-3 → 2026.10.9-4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/src/host-tools-guard.js +5 -3
- package/dist/src/outbound.js +28 -2
- package/dist/src/runtime.js +1 -1
- package/dist/src/storage.js +112 -13
- package/dist/src/terminal-send.js +69 -12
- package/dist/src/tools.js +203 -21
- package/dist/src/turn-boundary.js +17 -0
- package/package.json +1 -1
- package/src/host-tools-guard.ts +5 -2
- package/src/outbound.ts +39 -3
- package/src/runtime.ts +1 -1
- package/src/storage.ts +134 -15
- package/src/terminal-send.ts +97 -13
- package/src/tools.ts +216 -22
- package/src/turn-boundary.ts +18 -0
package/src/tools.ts
CHANGED
|
@@ -22,8 +22,10 @@ import {
|
|
|
22
22
|
getClawChatStore,
|
|
23
23
|
makeOwnerProfilePersister,
|
|
24
24
|
type ClawChatStore,
|
|
25
|
+
type SeenMessage,
|
|
25
26
|
} from "./storage.ts";
|
|
26
|
-
import { sendOpenclawClawlingMentionMessage, sendOpenclawClawlingReaction } from "./outbound.ts";
|
|
27
|
+
import { isPlausibleMessageId, sendOpenclawClawlingMentionMessage, sendOpenclawClawlingReaction } from "./outbound.ts";
|
|
28
|
+
import { isValidChatId } from "./ws-client.ts";
|
|
27
29
|
import {
|
|
28
30
|
getOpenclawClawlingClient,
|
|
29
31
|
} from "./runtime.ts";
|
|
@@ -43,7 +45,7 @@ import {
|
|
|
43
45
|
resolveMemoryScope,
|
|
44
46
|
type MemoryScope,
|
|
45
47
|
} from "./memory-scope.ts";
|
|
46
|
-
import { clawChatSessionAccountId, NON_OWNER_TOOL_ALLOWLIST } from "./turn-boundary.ts";
|
|
48
|
+
import { clawChatSessionAccountId, NON_OWNER_TOOL_ALLOWLIST, normalizeToolChatId } from "./turn-boundary.ts";
|
|
47
49
|
import {
|
|
48
50
|
pullGroupMetadata,
|
|
49
51
|
pullOwnerMetadata,
|
|
@@ -223,6 +225,16 @@ function validationError(message: string) {
|
|
|
223
225
|
return jsonResponse({ error: "validation", message });
|
|
224
226
|
}
|
|
225
227
|
|
|
228
|
+
function reactionBlockedError(chatId: string, reason: "invalid_chat_id" | "chat_rejected_by_server") {
|
|
229
|
+
return jsonResponse({
|
|
230
|
+
error: "send_blocked",
|
|
231
|
+
reason,
|
|
232
|
+
message: reason === "invalid_chat_id"
|
|
233
|
+
? `clawchat-plugin-openclaw: "${chatId}" is not a ClawChat chat id (expected cnv_…); the reaction was not sent`
|
|
234
|
+
: "clawchat-plugin-openclaw: this chat was rejected by the server; the reaction was not sent",
|
|
235
|
+
});
|
|
236
|
+
}
|
|
237
|
+
|
|
226
238
|
function genericError(err: unknown) {
|
|
227
239
|
return jsonResponse({
|
|
228
240
|
error: "unknown",
|
|
@@ -263,7 +275,15 @@ type ToolStore = Pick<
|
|
|
263
275
|
ClawChatStore,
|
|
264
276
|
"recordToolCall"
|
|
265
277
|
> &
|
|
266
|
-
Partial<Pick<
|
|
278
|
+
Partial<Pick<
|
|
279
|
+
ClawChatStore,
|
|
280
|
+
| "lookupActivationIdentity"
|
|
281
|
+
| "listRecentGroupMessages"
|
|
282
|
+
| "findSeenMessageInChat"
|
|
283
|
+
| "findStoredChatId"
|
|
284
|
+
| "claimMessageOnce"
|
|
285
|
+
| "markMessageAcknowledged"
|
|
286
|
+
>>;
|
|
267
287
|
|
|
268
288
|
function toolDescription(...parts: string[]): string {
|
|
269
289
|
return `${parts.join("")} ${DIRECT_TOOL_GUARD}`;
|
|
@@ -325,6 +345,13 @@ function planMomentImages(
|
|
|
325
345
|
return { ok: true, entries };
|
|
326
346
|
}
|
|
327
347
|
|
|
348
|
+
const MESSAGE_ID_ECHO_LIMIT = 64;
|
|
349
|
+
|
|
350
|
+
function notAMessageIdMessage(field: string, value: string): string {
|
|
351
|
+
const shown = value.length > MESSAGE_ID_ECHO_LIMIT ? `${value.slice(0, MESSAGE_ID_ECHO_LIMIT)}…` : value;
|
|
352
|
+
return `clawchat-plugin-openclaw: ${field} "${shown}" is not a message in this chat; use the real message id from this chat's message metadata (e.g. msg-…), or omit it`;
|
|
353
|
+
}
|
|
354
|
+
|
|
328
355
|
function validateMentionMessageParams(params: unknown):
|
|
329
356
|
| { ok: true; params: ClawchatMentionMessageParams }
|
|
330
357
|
| { ok: false; message: string } {
|
|
@@ -332,7 +359,8 @@ function validateMentionMessageParams(params: unknown):
|
|
|
332
359
|
return { ok: false, message: "clawchat-plugin-openclaw: params are required" };
|
|
333
360
|
}
|
|
334
361
|
const p = params as Partial<ClawchatMentionMessageParams>;
|
|
335
|
-
|
|
362
|
+
const chatId = normalizeToolChatId(p.chatId);
|
|
363
|
+
if (!chatId) {
|
|
336
364
|
return { ok: false, message: "clawchat-plugin-openclaw: chatId is required" };
|
|
337
365
|
}
|
|
338
366
|
if (p.chatType !== undefined && p.chatType !== "group" && p.chatType !== "direct") {
|
|
@@ -368,11 +396,12 @@ function validateMentionMessageParams(params: unknown):
|
|
|
368
396
|
ok: true,
|
|
369
397
|
params: {
|
|
370
398
|
...p,
|
|
399
|
+
chatId,
|
|
371
400
|
replyToMessageId,
|
|
372
401
|
} as ClawchatMentionMessageParams,
|
|
373
402
|
};
|
|
374
403
|
}
|
|
375
|
-
return { ok: true, params: p as ClawchatMentionMessageParams };
|
|
404
|
+
return { ok: true, params: { ...p, chatId } as ClawchatMentionMessageParams };
|
|
376
405
|
}
|
|
377
406
|
|
|
378
407
|
/**
|
|
@@ -404,6 +433,18 @@ export function resolveOpenclawClawlingToolAccountId(ctx: OpenClawPluginToolCont
|
|
|
404
433
|
return null;
|
|
405
434
|
}
|
|
406
435
|
|
|
436
|
+
/**
|
|
437
|
+
* True when the host says this tool call's run is a ClawChat run: every
|
|
438
|
+
* channel the trusted ctx names (`deliveryContext.channel`, `messageChannel`)
|
|
439
|
+
* is this channel, and at least one is present. Missing or foreign → false.
|
|
440
|
+
*/
|
|
441
|
+
export function isClawChatToolRun(ctx: OpenClawPluginToolContext): boolean {
|
|
442
|
+
const channels = [ctx.deliveryContext?.channel, ctx.messageChannel]
|
|
443
|
+
.map((channel) => (typeof channel === "string" ? channel.trim().toLowerCase() : ""))
|
|
444
|
+
.filter(Boolean);
|
|
445
|
+
return channels.length > 0 && channels.every((channel) => channel === CHANNEL_ID);
|
|
446
|
+
}
|
|
447
|
+
|
|
407
448
|
// Names this plugin registered through its wrapper below. The host-tool guard
|
|
408
449
|
// lets an allowlisted `clawchat_*` name through only when it is in this set,
|
|
409
450
|
// so another plugin's tool under a lookalike name is not trusted by prefix.
|
|
@@ -476,6 +517,94 @@ export function registerOpenclawClawlingTools(
|
|
|
476
517
|
}
|
|
477
518
|
}
|
|
478
519
|
|
|
520
|
+
let warnedMessageIdStore = false;
|
|
521
|
+
/**
|
|
522
|
+
* An explicit message id (react target, mention reply) must be one the
|
|
523
|
+
* plugin has stored for this chat; the stored chat id and message id (their
|
|
524
|
+
* real case) are what gets sent. When the store cannot answer, fall back to
|
|
525
|
+
* the message-id shape check rather than block every call, keep the ids as
|
|
526
|
+
* given, and warn once without ids or arguments.
|
|
527
|
+
*/
|
|
528
|
+
function resolveExplicitMessageId(input: {
|
|
529
|
+
field: "targetMessageId" | "replyToMessageId";
|
|
530
|
+
value: string;
|
|
531
|
+
accountId: string;
|
|
532
|
+
chatId: string;
|
|
533
|
+
}): { ok: true; chatId: string; messageId: string } | { ok: false; error: ReturnType<typeof validationError> } {
|
|
534
|
+
let seen: SeenMessage | false | null = null;
|
|
535
|
+
try {
|
|
536
|
+
const store = resolveStore();
|
|
537
|
+
seen = store?.findSeenMessageInChat
|
|
538
|
+
? store.findSeenMessageInChat({
|
|
539
|
+
platform: "openclaw",
|
|
540
|
+
accountId: input.accountId,
|
|
541
|
+
chatId: input.chatId,
|
|
542
|
+
messageId: input.value,
|
|
543
|
+
})
|
|
544
|
+
: null;
|
|
545
|
+
} catch {
|
|
546
|
+
seen = null;
|
|
547
|
+
}
|
|
548
|
+
if (seen) return { ok: true, chatId: seen.chatId, messageId: seen.messageId };
|
|
549
|
+
if (seen === null) {
|
|
550
|
+
if (!warnedMessageIdStore) {
|
|
551
|
+
warnedMessageIdStore = true;
|
|
552
|
+
api.logger.warn(
|
|
553
|
+
"clawchat-plugin-openclaw: message-id store check unavailable; falling back to the message-id shape check",
|
|
554
|
+
);
|
|
555
|
+
}
|
|
556
|
+
if (isPlausibleMessageId(input.value)) return { ok: true, chatId: input.chatId, messageId: input.value };
|
|
557
|
+
}
|
|
558
|
+
return { ok: false, error: validationError(notAMessageIdMessage(input.field, input.value)) };
|
|
559
|
+
}
|
|
560
|
+
|
|
561
|
+
/**
|
|
562
|
+
* The chat id in the case the ledger already stores for it, so a send, its
|
|
563
|
+
* terminal marker and its ledger row all name the chat as the inbound path
|
|
564
|
+
* does (a second case splits the chat for memory scope). The id as given
|
|
565
|
+
* when the ledger has no row for the chat or cannot answer.
|
|
566
|
+
*/
|
|
567
|
+
function storedChatIdCase(accountId: string, chatId: string): string {
|
|
568
|
+
try {
|
|
569
|
+
const stored = resolveStore()?.findStoredChatId?.({ platform: "openclaw", accountId, chatId });
|
|
570
|
+
return typeof stored === "string" && stored ? stored : chatId;
|
|
571
|
+
} catch {
|
|
572
|
+
return chatId;
|
|
573
|
+
}
|
|
574
|
+
}
|
|
575
|
+
|
|
576
|
+
/**
|
|
577
|
+
* Best-effort ledger row for a sent mention, so its id can be reacted to or
|
|
578
|
+
* quoted later. Written after the ack; a store failure never affects the send.
|
|
579
|
+
*/
|
|
580
|
+
function recordMentionSent(input: { accountId: string; chatId: string; messageId: string; text: string; acceptedAt?: number }): void {
|
|
581
|
+
try {
|
|
582
|
+
const store = resolveStore();
|
|
583
|
+
const claimed = store?.claimMessageOnce?.({
|
|
584
|
+
platform: "openclaw",
|
|
585
|
+
accountId: input.accountId,
|
|
586
|
+
kind: "message",
|
|
587
|
+
direction: "outbound",
|
|
588
|
+
eventType: "message.send",
|
|
589
|
+
chatId: input.chatId,
|
|
590
|
+
messageId: input.messageId,
|
|
591
|
+
text: input.text,
|
|
592
|
+
raw: { mode: "tool-mention" },
|
|
593
|
+
});
|
|
594
|
+
if (claimed !== true) return;
|
|
595
|
+
store?.markMessageAcknowledged?.({
|
|
596
|
+
accountId: input.accountId,
|
|
597
|
+
kind: "message",
|
|
598
|
+
direction: "outbound",
|
|
599
|
+
messageId: input.messageId,
|
|
600
|
+
protocolMessageId: input.messageId,
|
|
601
|
+
ackedAt: input.acceptedAt ?? Date.now(),
|
|
602
|
+
});
|
|
603
|
+
} catch {
|
|
604
|
+
// Diagnostics only; the mention has already been sent.
|
|
605
|
+
}
|
|
606
|
+
}
|
|
607
|
+
|
|
479
608
|
function detailsError(details: unknown): string | null {
|
|
480
609
|
if (!details || typeof details !== "object") return null;
|
|
481
610
|
const raw = details as { error?: unknown; message?: unknown };
|
|
@@ -1288,7 +1417,7 @@ export function registerOpenclawClawlingTools(
|
|
|
1288
1417
|
const built = buildClient(accountId);
|
|
1289
1418
|
if (!built.ok) return built.error;
|
|
1290
1419
|
try {
|
|
1291
|
-
const data = await built.client.getConversation(p.conversationId);
|
|
1420
|
+
const data = await built.client.getConversation(normalizeToolChatId(p.conversationId));
|
|
1292
1421
|
return jsonResponse(data);
|
|
1293
1422
|
} catch (err) {
|
|
1294
1423
|
if (err instanceof ClawlingApiError) {
|
|
@@ -1358,7 +1487,7 @@ export function registerOpenclawClawlingTools(
|
|
|
1358
1487
|
const built = buildClient(accountId);
|
|
1359
1488
|
if (!built.ok) return built.error;
|
|
1360
1489
|
try {
|
|
1361
|
-
const data = await built.client.leaveConversation(p.conversationId);
|
|
1490
|
+
const data = await built.client.leaveConversation(normalizeToolChatId(p.conversationId));
|
|
1362
1491
|
return jsonResponse(data);
|
|
1363
1492
|
} catch (err) {
|
|
1364
1493
|
if (err instanceof ClawlingApiError) {
|
|
@@ -1393,7 +1522,7 @@ export function registerOpenclawClawlingTools(
|
|
|
1393
1522
|
const built = buildClient(accountId);
|
|
1394
1523
|
if (!built.ok) return built.error;
|
|
1395
1524
|
try {
|
|
1396
|
-
const data = await built.client.addConversationMember(p.conversationId, p.userId);
|
|
1525
|
+
const data = await built.client.addConversationMember(normalizeToolChatId(p.conversationId), p.userId);
|
|
1397
1526
|
return jsonResponse(data);
|
|
1398
1527
|
} catch (err) {
|
|
1399
1528
|
if (err instanceof ClawlingApiError) {
|
|
@@ -1434,6 +1563,19 @@ export function registerOpenclawClawlingTools(
|
|
|
1434
1563
|
}
|
|
1435
1564
|
try {
|
|
1436
1565
|
const p = validated.params;
|
|
1566
|
+
let chatId = isValidChatId(p.chatId) ? storedChatIdCase(account.accountId, p.chatId) : p.chatId;
|
|
1567
|
+
let replyToMessageId = p.replyToMessageId;
|
|
1568
|
+
if (replyToMessageId) {
|
|
1569
|
+
const resolved = resolveExplicitMessageId({
|
|
1570
|
+
field: "replyToMessageId",
|
|
1571
|
+
value: replyToMessageId,
|
|
1572
|
+
accountId: account.accountId,
|
|
1573
|
+
chatId,
|
|
1574
|
+
});
|
|
1575
|
+
if (!resolved.ok) return resolved.error;
|
|
1576
|
+
chatId = resolved.chatId;
|
|
1577
|
+
replyToMessageId = resolved.messageId;
|
|
1578
|
+
}
|
|
1437
1579
|
const client = getOpenclawClawlingClient(account.accountId);
|
|
1438
1580
|
if (!client) {
|
|
1439
1581
|
return configError("ClawChat websocket client is not ready");
|
|
@@ -1442,26 +1584,38 @@ export function registerOpenclawClawlingTools(
|
|
|
1442
1584
|
client,
|
|
1443
1585
|
account,
|
|
1444
1586
|
to: {
|
|
1445
|
-
chatId
|
|
1587
|
+
chatId,
|
|
1446
1588
|
chatType: p.chatType ?? "group",
|
|
1447
1589
|
},
|
|
1448
1590
|
...(p.text !== undefined ? { text: p.text } : {}),
|
|
1449
1591
|
mentions: p.mentions,
|
|
1450
|
-
...(
|
|
1592
|
+
...(replyToMessageId
|
|
1451
1593
|
? {
|
|
1452
1594
|
replyCtx: {
|
|
1453
|
-
replyToMessageId
|
|
1595
|
+
replyToMessageId,
|
|
1454
1596
|
},
|
|
1455
1597
|
}
|
|
1456
1598
|
: {}),
|
|
1457
1599
|
log: api.logger,
|
|
1458
1600
|
});
|
|
1459
1601
|
if (!result) return jsonResponse(result);
|
|
1460
|
-
|
|
1602
|
+
recordMentionSent({
|
|
1461
1603
|
accountId: account.accountId,
|
|
1462
|
-
chatId
|
|
1604
|
+
chatId,
|
|
1463
1605
|
messageId: result.messageId,
|
|
1606
|
+
text: p.text ?? "",
|
|
1607
|
+
...(result.acceptedAt !== undefined ? { acceptedAt: result.acceptedAt } : {}),
|
|
1464
1608
|
});
|
|
1609
|
+
// Same trust rule as the react defaults: only a ClawChat run's
|
|
1610
|
+
// turn may have its follow-up reply suppressed.
|
|
1611
|
+
if (isClawChatToolRun(ctx)) {
|
|
1612
|
+
markTerminalClawChatSend({
|
|
1613
|
+
accountId: account.accountId,
|
|
1614
|
+
chatId,
|
|
1615
|
+
messageId: result.messageId,
|
|
1616
|
+
sessionKey: ctx.sessionKey,
|
|
1617
|
+
});
|
|
1618
|
+
}
|
|
1465
1619
|
return jsonResponse({
|
|
1466
1620
|
sent: true,
|
|
1467
1621
|
terminal: true,
|
|
@@ -1505,9 +1659,31 @@ export function registerOpenclawClawlingTools(
|
|
|
1505
1659
|
if (!account.configured) {
|
|
1506
1660
|
return configError("clawchat-plugin-openclaw: account is not configured");
|
|
1507
1661
|
}
|
|
1508
|
-
|
|
1509
|
-
|
|
1510
|
-
|
|
1662
|
+
// Defaults come only from this call's own turn: the open turn in
|
|
1663
|
+
// its async context registered for the same session key as the
|
|
1664
|
+
// trusted tool ctx (see terminal-send.ts). Anything else fails as
|
|
1665
|
+
// a validation error asking for explicit ids.
|
|
1666
|
+
// The run must also be a ClawChat run: under a session key shared
|
|
1667
|
+
// with another channel, that channel's turns never register or
|
|
1668
|
+
// supersede a ClawChat turn, so its calls get no defaults.
|
|
1669
|
+
const scope = isClawChatToolRun(ctx)
|
|
1670
|
+
? getCurrentTerminalSendScope({ sessionKey: ctx.sessionKey })
|
|
1671
|
+
: null;
|
|
1672
|
+
// Only a missing or blank chatId defaults. A given one that strips
|
|
1673
|
+
// to nothing (`clawchat-plugin-openclaw:`, `group:`) stays as given
|
|
1674
|
+
// so outbound refuses it as invalid_chat_id instead of it silently
|
|
1675
|
+
// reaching the current turn's chat.
|
|
1676
|
+
const rawChatId = typeof p.chatId === "string" ? p.chatId.trim() : "";
|
|
1677
|
+
let chatId = rawChatId ? normalizeToolChatId(rawChatId) || rawChatId : scope?.chatId;
|
|
1678
|
+
const explicitTargetMessageId = typeof p.targetMessageId === "string" ? p.targetMessageId.trim() : "";
|
|
1679
|
+
// The default target is the current turn's message, so it applies
|
|
1680
|
+
// only to that turn's chat: chatId omitted, or naming the scope's
|
|
1681
|
+
// chat (any case, host target form). The send then goes to the
|
|
1682
|
+
// scope's chat id. In any other chat that id names no message.
|
|
1683
|
+
const defaultsToScopeChat = Boolean(scope)
|
|
1684
|
+
&& (!rawChatId || (chatId ?? "").toLowerCase() === scope!.chatId.toLowerCase());
|
|
1685
|
+
let targetMessageId = explicitTargetMessageId || (defaultsToScopeChat ? scope!.messageId : undefined);
|
|
1686
|
+
if (!explicitTargetMessageId && defaultsToScopeChat) chatId = scope!.chatId;
|
|
1511
1687
|
if (!chatId) {
|
|
1512
1688
|
return validationError(
|
|
1513
1689
|
"clawchat-plugin-openclaw: chatId is required (no current-turn chat to default from)",
|
|
@@ -1518,6 +1694,21 @@ export function registerOpenclawClawlingTools(
|
|
|
1518
1694
|
"clawchat-plugin-openclaw: targetMessageId is required (no current-turn message to default from)",
|
|
1519
1695
|
);
|
|
1520
1696
|
}
|
|
1697
|
+
// Only an explicit id is checked; the current-turn default is the
|
|
1698
|
+
// triggering message's own id. A chat id outbound would refuse is
|
|
1699
|
+
// reported as that refusal first.
|
|
1700
|
+
if (explicitTargetMessageId) {
|
|
1701
|
+
if (!isValidChatId(chatId)) return reactionBlockedError(chatId, "invalid_chat_id");
|
|
1702
|
+
const resolved = resolveExplicitMessageId({
|
|
1703
|
+
field: "targetMessageId",
|
|
1704
|
+
value: explicitTargetMessageId,
|
|
1705
|
+
accountId: account.accountId,
|
|
1706
|
+
chatId,
|
|
1707
|
+
});
|
|
1708
|
+
if (!resolved.ok) return resolved.error;
|
|
1709
|
+
chatId = resolved.chatId;
|
|
1710
|
+
targetMessageId = resolved.messageId;
|
|
1711
|
+
}
|
|
1521
1712
|
const client = getOpenclawClawlingClient(account.accountId);
|
|
1522
1713
|
if (!client) {
|
|
1523
1714
|
return configError("ClawChat websocket client is not ready");
|
|
@@ -1525,7 +1716,10 @@ export function registerOpenclawClawlingTools(
|
|
|
1525
1716
|
const emoji = p.emoji.trim();
|
|
1526
1717
|
const removed = p.remove === true;
|
|
1527
1718
|
try {
|
|
1528
|
-
sendOpenclawClawlingReaction({ client, chatId, targetMessageId, emoji, removed, log: api.logger });
|
|
1719
|
+
const sent = sendOpenclawClawlingReaction({ client, chatId, targetMessageId, emoji, removed, log: api.logger });
|
|
1720
|
+
// A locally refused reaction never left the plugin: report the
|
|
1721
|
+
// refusal so the model does not tell the user it reacted.
|
|
1722
|
+
if (!sent.sent) return reactionBlockedError(chatId, sent.reason);
|
|
1529
1723
|
return jsonResponse({ reacted: true, targetMessageId, emoji, removed });
|
|
1530
1724
|
} catch (err) {
|
|
1531
1725
|
return genericError(err);
|
|
@@ -2136,7 +2330,7 @@ export function registerOpenclawClawlingTools(
|
|
|
2136
2330
|
async execute(_callId, params) {
|
|
2137
2331
|
return await recordClawchatToolCall(accountId, "clawchat_orchestrate_get_group", params, async () => {
|
|
2138
2332
|
const p = params as ClawchatOrchGetGroupParams;
|
|
2139
|
-
return await withClient(accountId, (c) => c.orchGetGroup(p.conversationId));
|
|
2333
|
+
return await withClient(accountId, (c) => c.orchGetGroup(normalizeToolChatId(p.conversationId)));
|
|
2140
2334
|
});
|
|
2141
2335
|
},
|
|
2142
2336
|
};
|
|
@@ -2169,7 +2363,7 @@ export function registerOpenclawClawlingTools(
|
|
|
2169
2363
|
params,
|
|
2170
2364
|
async () => {
|
|
2171
2365
|
const p = params as ClawchatOrchSetGroupPromptParams;
|
|
2172
|
-
return await withClient(accountId, (c) => c.orchSetGroupPrompt(p.conversationId, p.description));
|
|
2366
|
+
return await withClient(accountId, (c) => c.orchSetGroupPrompt(normalizeToolChatId(p.conversationId), p.description));
|
|
2173
2367
|
},
|
|
2174
2368
|
);
|
|
2175
2369
|
},
|
|
@@ -2219,7 +2413,7 @@ export function registerOpenclawClawlingTools(
|
|
|
2219
2413
|
params,
|
|
2220
2414
|
async () => {
|
|
2221
2415
|
const p = params as ClawchatOrchGroupMemberParams;
|
|
2222
|
-
return await withClient(accountId, (c) => c.orchAddGroupMember(p.conversationId, p.agentId));
|
|
2416
|
+
return await withClient(accountId, (c) => c.orchAddGroupMember(normalizeToolChatId(p.conversationId), p.agentId));
|
|
2223
2417
|
},
|
|
2224
2418
|
);
|
|
2225
2419
|
},
|
|
@@ -2246,7 +2440,7 @@ export function registerOpenclawClawlingTools(
|
|
|
2246
2440
|
params,
|
|
2247
2441
|
async () => {
|
|
2248
2442
|
const p = params as ClawchatOrchGroupMemberParams;
|
|
2249
|
-
return await withClient(accountId, (c) => c.orchRemoveGroupMember(p.conversationId, p.agentId));
|
|
2443
|
+
return await withClient(accountId, (c) => c.orchRemoveGroupMember(normalizeToolChatId(p.conversationId), p.agentId));
|
|
2250
2444
|
},
|
|
2251
2445
|
);
|
|
2252
2446
|
},
|
|
@@ -2276,7 +2470,7 @@ export function registerOpenclawClawlingTools(
|
|
|
2276
2470
|
async () => {
|
|
2277
2471
|
const p = params as ClawchatOrchSetGroupAgentSettingsParams;
|
|
2278
2472
|
return await withClient(accountId, (c) =>
|
|
2279
|
-
c.orchSetGroupAgentSettings(p.conversationId, p.agentId, {
|
|
2473
|
+
c.orchSetGroupAgentSettings(normalizeToolChatId(p.conversationId), p.agentId, {
|
|
2280
2474
|
muted: p.muted,
|
|
2281
2475
|
replyMode: p.replyMode,
|
|
2282
2476
|
batchDelaySeconds: p.batchDelaySeconds,
|
package/src/turn-boundary.ts
CHANGED
|
@@ -16,6 +16,24 @@ export const NON_OWNER_TOOL_ALLOWLIST: readonly string[] = Object.freeze([
|
|
|
16
16
|
|
|
17
17
|
const norm = (v: string | undefined | null) => (typeof v === "string" ? v.trim().toLowerCase() : "");
|
|
18
18
|
|
|
19
|
+
// `<channel>:` then `direct:`/`group:`, each optional and stripped at most once.
|
|
20
|
+
const TOOL_CHAT_ID_TARGET_PREFIX = new RegExp(`^(?:${CHANNEL_ID}:)?(?:(?:direct|group):)?`, "i");
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* The bare ClawChat chat id a tool argument names. The host shows the model
|
|
24
|
+
* this chat's `chat_id` as the channel-prefixed origin target
|
|
25
|
+
* (`clawchat-plugin-openclaw:cnv_…` / `clawchat-plugin-openclaw:group:cnv_…`)
|
|
26
|
+
* and a group's label as `group:cnv_…`; models copy either form into tool
|
|
27
|
+
* arguments. Only that form is stripped; case is kept, and whatever remains is
|
|
28
|
+
* still validated by the send path (`isValidChatId`) or the server. Every tool
|
|
29
|
+
* that takes a ClawChat chat id, and the non-owner current-chat check, use
|
|
30
|
+
* this one helper so they agree on which chat an argument names.
|
|
31
|
+
*/
|
|
32
|
+
export function normalizeToolChatId(value: unknown): string {
|
|
33
|
+
if (typeof value !== "string") return "";
|
|
34
|
+
return value.trim().replace(TOOL_CHAT_ID_TARGET_PREFIX, "").trim();
|
|
35
|
+
}
|
|
36
|
+
|
|
19
37
|
/**
|
|
20
38
|
* Host session keys (dmScope per-account-channel-peer):
|
|
21
39
|
* agent:<agent>:<channel>:<account>:direct:<chat>
|