clawgram 2.19.0 → 2.19.3
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 +12 -2
- package/dist/channel.js +73 -11
- package/openclaw.plugin.json +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -748,11 +748,12 @@ attachment *metadata* — kind, file name, size, duration — and fetch nothing.
|
|
|
748
748
|
out: an image posted in a chat before the agent was addressed, and any reuse of an image at all,
|
|
749
749
|
because the file the inbound path read is deleted the moment the reading ends.
|
|
750
750
|
|
|
751
|
-
`fetch-media` covers both. It takes one message and returns what is attached to it.
|
|
751
|
+
`fetch-media` covers both. It takes one message and returns what is attached to it. The action
|
|
752
|
+
also answers to **`download-file`**, core's own name for it — see the note on naming below.
|
|
752
753
|
|
|
753
754
|
| Parameter | Aliases | Notes |
|
|
754
755
|
|---|---|---|
|
|
755
|
-
| `chatId` | `
|
|
756
|
+
| `chatId` | `chat` | The chat: `@username`, numeric id, `me`. **Not `target`** — core reserves that for actions in its own vocabulary and refuses it here |
|
|
756
757
|
| `messageId` | `id`, `message`, `msgId` | The id `read` reported for the message |
|
|
757
758
|
| `mode` | — | `both` (default), `read`, `file` |
|
|
758
759
|
|
|
@@ -768,6 +769,15 @@ The action is confined by `readChats`, the same scope that gates history and mem
|
|
|
768
769
|
account may not read history from cannot be a source of bytes either. The action name also answers
|
|
769
770
|
to `fetchMedia`, `download-media`, `downloadMedia` and `getMedia`.
|
|
770
771
|
|
|
772
|
+
**On the two names.** Core keys its target policy by its own action vocabulary
|
|
773
|
+
(`CHANNEL_MESSAGE_ACTION_NAMES`), and an action outside it is treated as both *requiring* a target
|
|
774
|
+
and *not accepting* one — the same lookup returns `undefined` for the first check and defaults to
|
|
775
|
+
`"none"` for the second. A caller then gets `Action fetch-media requires a target.` without a
|
|
776
|
+
target and `Action fetch-media does not accept a target.` with one, whatever it tries.
|
|
777
|
+
`download-file` is in that vocabulary and maps to `"none"`, so the contradiction does not arise;
|
|
778
|
+
`fetch-media` is made usable by declaring `chatId` as its destination param
|
|
779
|
+
(`messageActionTargetAliases`). Both names run the same code.
|
|
780
|
+
|
|
771
781
|
What comes back is `ok: true` with `media` (the same metadata `read` reports), `understanding`
|
|
772
782
|
(`description` or `transcript`), and `text` and/or `filePath` per the mode. A fetch that yields
|
|
773
783
|
nothing is not an error — it says which nothing it was:
|
package/dist/channel.js
CHANGED
|
@@ -3,7 +3,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
3
3
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
4
|
};
|
|
5
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
-
exports.createChannelPlugin = void 0;
|
|
6
|
+
exports.createChannelPlugin = exports.CORE_ACTION_SYNONYMS = void 0;
|
|
7
7
|
const core_1 = require("openclaw/plugin-sdk/core");
|
|
8
8
|
const node_os_1 = __importDefault(require("node:os"));
|
|
9
9
|
const node_path_1 = __importDefault(require("node:path"));
|
|
@@ -177,6 +177,30 @@ function readAccountManageChats(account) {
|
|
|
177
177
|
const entries = Array.isArray(raw) ? raw : [raw];
|
|
178
178
|
return entries.map((entry) => String(entry).trim()).filter(Boolean);
|
|
179
179
|
}
|
|
180
|
+
/**
|
|
181
|
+
* Core's own name for a clawgram action, and the only thing that makes the
|
|
182
|
+
* action reachable from the agent's `message` tool.
|
|
183
|
+
*
|
|
184
|
+
* Core keys its target policy by `CHANNEL_MESSAGE_ACTION_NAMES`, and an action
|
|
185
|
+
* outside that vocabulary is simultaneously "requires a target" and "does not
|
|
186
|
+
* accept a target" — there is no call that satisfies both. Declaring `chatId`
|
|
187
|
+
* through `messageActionTargetAliases` looks like the fix and is not: core
|
|
188
|
+
* resolves the channel with `getBootstrapChannelPlugin`, which only knows
|
|
189
|
+
* bundled channels, so a plugin channel's declaration is never read. Measured
|
|
190
|
+
* on 2026-08-30 — `thread-list` reached `handleAction` and `topics` did not,
|
|
191
|
+
* from the same caller, on the same chat.
|
|
192
|
+
*
|
|
193
|
+
* Every name on the right maps to core target mode `"none"` except
|
|
194
|
+
* `channel-info`, which is `"to"` and therefore arrives with the chat in
|
|
195
|
+
* `params.to` — a spelling all of these parsers already accept.
|
|
196
|
+
*/
|
|
197
|
+
exports.CORE_ACTION_SYNONYMS = {
|
|
198
|
+
"thread-list": "topics",
|
|
199
|
+
"channel-list": "dialogs",
|
|
200
|
+
"channel-info": "chatInfo",
|
|
201
|
+
"member-info": "participants",
|
|
202
|
+
"download-file": "fetch-media",
|
|
203
|
+
};
|
|
180
204
|
/** Canonical management action for every accepted spelling. */
|
|
181
205
|
const MANAGE_ACTION_ALIASES = {
|
|
182
206
|
createGroup: "createGroup",
|
|
@@ -405,11 +429,12 @@ const createChannelPlugin = (runtimes, pluginRuntime) => {
|
|
|
405
429
|
"Explicit targets may be @username, numeric Telegram user id, phone/contact resolvable by Telegram, group chat ids, or clawgram:<target>.",
|
|
406
430
|
"For Telegram forum topics, send to the group chat id and pass the topic id separately as `threadId`.",
|
|
407
431
|
"Use the `react` action to acknowledge a message with an emoji instead of sending a reply; pass an empty `emoji` (or `remove: true`) to take the reaction back.",
|
|
408
|
-
"Use the `
|
|
409
|
-
"Use the `
|
|
432
|
+
"Use the `channel-info` action (clawgram also answers to `chatInfo`) to learn what a chat is — title, type, member count, description, pinned message — instead of guessing from its id.",
|
|
433
|
+
"Use the `thread-list` action to list a forum's topics by name (optional `query` narrows by title); that is where a `threadId` comes from when someone names a topic instead of quoting a message in it. Name the chat with `chatId` and do not pass `target` — core refuses it for this action. `topics` is the same call under a name core does not know, and is only reachable through the gateway RPC.",
|
|
410
434
|
"Pass that `threadId` to `read` as well: without it a forum read returns every topic interleaved rather than the one that was asked about.",
|
|
411
|
-
"Use the `
|
|
412
|
-
"Use the `
|
|
435
|
+
"Use the `download-file` action (or its alias `fetch-media`) to fetch the attachment on a message `read` reported. Name the chat with `chatId` and the message with `messageId`; do not pass `target` — core refuses it for this action: `mode: \"read\"` returns a description of an image or a transcript of a voice note, `\"file\"` returns a path to reuse, `\"both\"` (default) returns both. `read` only says an attachment exists; this is what brings it.",
|
|
436
|
+
"Use the `channel-list` action (clawgram also answers to `dialogs`) to find out which group chats this account is actually in — including ones nobody has configured yet. It reports id, title and type only, never direct chats, and only when the account enables `discoverChats`.",
|
|
437
|
+
"Use `member-info` with a `chatId` to list who is in a chat; `joins` has no name in core's vocabulary and is reachable only through the gateway RPC.",
|
|
413
438
|
"Use `createGroup` (title, optional about, optional users) to create a new Telegram supergroup; `addMembers`/`removeMember` change who is in a managed chat, `promoteAdmin`/`demoteAdmin` grant or revoke admin rights, `transferOwnership` hands the chat over, `inviteLink` issues an invite link for people Telegram refused to add directly.",
|
|
414
439
|
],
|
|
415
440
|
messageToolCapabilities: () => [
|
|
@@ -1389,10 +1414,26 @@ const createChannelPlugin = (runtimes, pluginRuntime) => {
|
|
|
1389
1414
|
// file stays on disk. That is exactly what happened on 2026-08-07.
|
|
1390
1415
|
actions: [
|
|
1391
1416
|
"send", "read", "participants", "joins", "react", "chatInfo", "topics", "dialogs", "upload-file",
|
|
1417
|
+
// Core's own names for four of the above. Without them the
|
|
1418
|
+
// action is advertised and unreachable — see CORE_ACTION_SYNONYMS.
|
|
1419
|
+
"thread-list", "channel-list", "channel-info", "member-info",
|
|
1392
1420
|
// Reading an attachment that is already in a chat. `read` reports
|
|
1393
1421
|
// that a photo exists; this is what turns it into something the
|
|
1394
1422
|
// agent can look at or pass on.
|
|
1395
|
-
|
|
1423
|
+
//
|
|
1424
|
+
// Two names on purpose. `download-file` is core's own vocabulary
|
|
1425
|
+
// (`CHANNEL_MESSAGE_ACTION_NAMES`), and core's target policy is
|
|
1426
|
+
// keyed by that vocabulary: an action it does not know is both
|
|
1427
|
+
// "requires a target" (`MESSAGE_ACTION_TARGET_MODE[action] !==
|
|
1428
|
+
// "none"` is true for `undefined`) and "does not accept a target"
|
|
1429
|
+
// (the same lookup defaults to `"none"` when a target is passed).
|
|
1430
|
+
// That contradiction is unresolvable from the caller's side —
|
|
1431
|
+
// measured on 2026-08-24, when the agent tried every combination
|
|
1432
|
+
// and got one of the two errors each time. `download-file` is
|
|
1433
|
+
// mapped to `"none"`, so it has no such contradiction;
|
|
1434
|
+
// `fetch-media` stays as the descriptive name and is made usable
|
|
1435
|
+
// by the alias declaration below.
|
|
1436
|
+
"fetch-media", "download-file",
|
|
1396
1437
|
// Chat management (2.12.0) — gated by the account's manageChats
|
|
1397
1438
|
// scope; without it every one of these is refused.
|
|
1398
1439
|
"createGroup", "addMembers", "removeMember",
|
|
@@ -1404,6 +1445,27 @@ const createChannelPlugin = (runtimes, pluginRuntime) => {
|
|
|
1404
1445
|
},
|
|
1405
1446
|
};
|
|
1406
1447
|
},
|
|
1448
|
+
// Core asks the channel which params name a destination when the action
|
|
1449
|
+
// is not one of its own. Without this, `chatId` is invisible to
|
|
1450
|
+
// `actionHasTarget` and the call is refused as targetless before it ever
|
|
1451
|
+
// reaches `handleAction`. The chat is named by `chatId` rather than
|
|
1452
|
+
// `target` because core reserves `target` for actions in its own
|
|
1453
|
+
// vocabulary and throws on it for everything else.
|
|
1454
|
+
//
|
|
1455
|
+
// This declaration alone does not rescue an action, and 2.19.1 read too
|
|
1456
|
+
// much into it. Core resolves the channel through
|
|
1457
|
+
// `getBootstrapChannelPlugin`, which only ever returns a *bundled*
|
|
1458
|
+
// channel; for a plugin channel the lookup misses and the declaration is
|
|
1459
|
+
// never consulted. Measured on the live server on 2026-08-30: `topics`
|
|
1460
|
+
// was refused for `target`, `chatId`, `groupId` and the prefixed form
|
|
1461
|
+
// alike even with `chatId` declared here. What actually carried
|
|
1462
|
+
// `fetch-media` through was its second name, `download-file` — see
|
|
1463
|
+
// CORE_ACTION_SYNONYMS. This stays because it costs nothing and is
|
|
1464
|
+
// correct the day core consults plugin channels too.
|
|
1465
|
+
messageActionTargetAliases: {
|
|
1466
|
+
"fetch-media": { aliases: ["chatId"] },
|
|
1467
|
+
"download-file": { aliases: ["chatId"] },
|
|
1468
|
+
},
|
|
1407
1469
|
extractToolSend: ({ args }) => (0, tool_send_1.extractToolSend)(args, "sendMessage"),
|
|
1408
1470
|
handleAction: async ({ action, params, cfg, accountId, dryRun: dryRunFlag, toolContext }) => {
|
|
1409
1471
|
// Core passes the flag beside `params`; callers write it inside.
|
|
@@ -1464,7 +1526,7 @@ const createChannelPlugin = (runtimes, pluginRuntime) => {
|
|
|
1464
1526
|
// account was never allowed to read.
|
|
1465
1527
|
if (action === "fetch-media" || action === "fetchMedia" ||
|
|
1466
1528
|
action === "download-media" || action === "downloadMedia" ||
|
|
1467
|
-
action === "getMedia") {
|
|
1529
|
+
action === "getMedia" || action === "download-file") {
|
|
1468
1530
|
const fetchParams = (0, fetch_media_1.parseFetchMediaParams)(params);
|
|
1469
1531
|
const fetchAccountId = resolveRuntimeAccountId(cfg, accountId);
|
|
1470
1532
|
if (!fetchAccountId) {
|
|
@@ -1616,7 +1678,7 @@ const createChannelPlugin = (runtimes, pluginRuntime) => {
|
|
|
1616
1678
|
// Membership is a read, so the same `readChats` scope that gates history
|
|
1617
1679
|
// gates it too: this cannot become a way to enumerate chats the account
|
|
1618
1680
|
// was never allowed to read.
|
|
1619
|
-
if (action === "participants" || action === "members") {
|
|
1681
|
+
if (action === "participants" || action === "members" || action === "member-info") {
|
|
1620
1682
|
const participantsParams = (0, history_1.parseListParticipantsParams)(params);
|
|
1621
1683
|
const participantsAccountId = resolveRuntimeAccountId(cfg, accountId);
|
|
1622
1684
|
if (!participantsAccountId) {
|
|
@@ -1656,7 +1718,7 @@ const createChannelPlugin = (runtimes, pluginRuntime) => {
|
|
|
1656
1718
|
// id could only be lifted off an inbound message — so a topic nobody had
|
|
1657
1719
|
// written in yet was unreachable, and one named in words was unfindable.
|
|
1658
1720
|
// Titles say what a chat is working on, so the read scope gates them.
|
|
1659
|
-
if (action === "topics" || action === "forumTopics") {
|
|
1721
|
+
if (action === "topics" || action === "forumTopics" || action === "thread-list") {
|
|
1660
1722
|
const topicsParams = (0, topics_1.parseTopicsParams)(params);
|
|
1661
1723
|
const topicsAccountId = resolveRuntimeAccountId(cfg, accountId);
|
|
1662
1724
|
if (!topicsAccountId) {
|
|
@@ -1693,7 +1755,7 @@ const createChannelPlugin = (runtimes, pluginRuntime) => {
|
|
|
1693
1755
|
// Which chats this account is in. Not gated by `readChats` — the whole
|
|
1694
1756
|
// point is to find chats that are not in it yet — so it has a gate of
|
|
1695
1757
|
// its own, is metadata only, and never reports direct chats.
|
|
1696
|
-
if (action === "dialogs" || action === "chats") {
|
|
1758
|
+
if (action === "dialogs" || action === "chats" || action === "channel-list") {
|
|
1697
1759
|
const dialogsParams = (0, dialogs_1.parseDialogsParams)(params);
|
|
1698
1760
|
const dialogsAccountId = resolveRuntimeAccountId(cfg, accountId);
|
|
1699
1761
|
if (!dialogsAccountId) {
|
|
@@ -1753,7 +1815,7 @@ const createChannelPlugin = (runtimes, pluginRuntime) => {
|
|
|
1753
1815
|
// Describing a chat is a read, so the same `readChats` scope that gates
|
|
1754
1816
|
// history gates it too — this must not become a way to learn the title
|
|
1755
1817
|
// and size of a chat the account was never allowed to read.
|
|
1756
|
-
if (action === "chatInfo" || action === "getChatInfo"
|
|
1818
|
+
if (action === "chatInfo" || action === "getChatInfo" || action === "channel-info"
|
|
1757
1819
|
|| action === "chatMetadata" || action === "getChatMetadata") {
|
|
1758
1820
|
const chatInfoParams = (0, chat_info_1.parseChatInfoParams)(params, toolContext);
|
|
1759
1821
|
const chatInfoAccountId = resolveRuntimeAccountId(cfg, accountId);
|
package/openclaw.plugin.json
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"id": "clawgram",
|
|
3
3
|
"name": "Clawgram",
|
|
4
4
|
"description": "Clawgram — personal Telegram (MTProto userbot) channel for OpenClaw. Your AI assistant reads and responds as you.",
|
|
5
|
-
"version": "2.19.
|
|
5
|
+
"version": "2.19.3",
|
|
6
6
|
"configSchema": {
|
|
7
7
|
"type": "object",
|
|
8
8
|
"additionalProperties": false,
|
package/package.json
CHANGED