clawgram 2.22.0 → 2.24.0
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/actions.js +124 -0
- package/dist/attachments.js +142 -0
- package/dist/channel.js +286 -1677
- package/dist/helpers.js +47 -0
- package/dist/inbound-pipeline.js +872 -0
- package/dist/outbound.js +260 -0
- package/openclaw.plugin.json +1 -1
- package/package.json +1 -1
package/dist/actions.js
ADDED
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
// Имена действий: что принимается и во что разрешается.
|
|
3
|
+
//
|
|
4
|
+
// Вынесено из channel.ts, который был единственным файлом на 3274 строки при
|
|
5
|
+
// следующем по величине 1165 (находка A6-11). Здесь нет ни рантайма, ни
|
|
6
|
+
// конфига — только словарь, поэтому читать и править его можно, не открывая
|
|
7
|
+
// диспетчер.
|
|
8
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
9
|
+
exports.MANAGE_ACTIONS = exports.CORE_ACTION_SYNONYMS = void 0;
|
|
10
|
+
exports.canonicalAction = canonicalAction;
|
|
11
|
+
/**
|
|
12
|
+
* Every accepted spelling of an action, mapped to its canonical name.
|
|
13
|
+
*
|
|
14
|
+
* One table, not three. The synonyms used to live in
|
|
15
|
+
* `CORE_ACTION_SYNONYMS`, again in `MANAGE_ACTION_ALIASES`, and a third time
|
|
16
|
+
* as `action === "…" || …` chains inside the dispatcher — and the dispatcher
|
|
17
|
+
* read only the chains. A name could therefore be added to a table and to the
|
|
18
|
+
* advertised list and still reach nothing, with the suite none the wiser:
|
|
19
|
+
* it only ever dispatched the native spellings (finding A6-10).
|
|
20
|
+
*
|
|
21
|
+
* `canonicalAction` is now the only place a name is resolved, and
|
|
22
|
+
* `CORE_ACTION_SYNONYMS` below is derived from this table rather than kept
|
|
23
|
+
* beside it.
|
|
24
|
+
*/
|
|
25
|
+
const ACTION_ALIASES = {
|
|
26
|
+
send: "send",
|
|
27
|
+
read: "read",
|
|
28
|
+
// `list` is accepted so a caller that guessed the other obvious name is not
|
|
29
|
+
// silently refused.
|
|
30
|
+
list: "read",
|
|
31
|
+
react: "react",
|
|
32
|
+
joins: "joins",
|
|
33
|
+
"upload-file": "upload-file",
|
|
34
|
+
sendAttachment: "upload-file",
|
|
35
|
+
"fetch-media": "fetch-media",
|
|
36
|
+
fetchMedia: "fetch-media",
|
|
37
|
+
"download-media": "fetch-media",
|
|
38
|
+
downloadMedia: "fetch-media",
|
|
39
|
+
getMedia: "fetch-media",
|
|
40
|
+
"download-file": "fetch-media",
|
|
41
|
+
participants: "participants",
|
|
42
|
+
members: "participants",
|
|
43
|
+
"member-info": "participants",
|
|
44
|
+
topics: "topics",
|
|
45
|
+
forumTopics: "topics",
|
|
46
|
+
"thread-list": "topics",
|
|
47
|
+
dialogs: "dialogs",
|
|
48
|
+
chats: "dialogs",
|
|
49
|
+
"channel-list": "dialogs",
|
|
50
|
+
chatInfo: "chatInfo",
|
|
51
|
+
getChatInfo: "chatInfo",
|
|
52
|
+
"channel-info": "chatInfo",
|
|
53
|
+
chatMetadata: "chatInfo",
|
|
54
|
+
getChatMetadata: "chatInfo",
|
|
55
|
+
// Chat management. `kick` was already accepted; the rest were advertised
|
|
56
|
+
// under names core does not know and were therefore never callable from the
|
|
57
|
+
// tool at all — 2.19.4 gives them core's nearest name. `transferOwnership`
|
|
58
|
+
// and `inviteLink` have no counterpart in that vocabulary and stay
|
|
59
|
+
// gateway-only, as does `joins`.
|
|
60
|
+
createGroup: "createGroup",
|
|
61
|
+
createChat: "createGroup",
|
|
62
|
+
"create-group": "createGroup",
|
|
63
|
+
"channel-create": "createGroup",
|
|
64
|
+
addMembers: "addMembers",
|
|
65
|
+
addMember: "addMembers",
|
|
66
|
+
"add-members": "addMembers",
|
|
67
|
+
addParticipant: "addMembers",
|
|
68
|
+
removeMember: "removeMember",
|
|
69
|
+
removeMembers: "removeMember",
|
|
70
|
+
"remove-member": "removeMember",
|
|
71
|
+
kick: "removeMember",
|
|
72
|
+
promoteAdmin: "promoteAdmin",
|
|
73
|
+
promote: "promoteAdmin",
|
|
74
|
+
"promote-admin": "promoteAdmin",
|
|
75
|
+
setAdmin: "promoteAdmin",
|
|
76
|
+
"role-add": "promoteAdmin",
|
|
77
|
+
demoteAdmin: "demoteAdmin",
|
|
78
|
+
demote: "demoteAdmin",
|
|
79
|
+
"demote-admin": "demoteAdmin",
|
|
80
|
+
"role-remove": "demoteAdmin",
|
|
81
|
+
transferOwnership: "transferOwnership",
|
|
82
|
+
transferOwner: "transferOwnership",
|
|
83
|
+
"transfer-ownership": "transferOwnership",
|
|
84
|
+
inviteLink: "inviteLink",
|
|
85
|
+
exportInviteLink: "inviteLink",
|
|
86
|
+
"invite-link": "inviteLink",
|
|
87
|
+
};
|
|
88
|
+
/** The canonical action for a spelling; an unknown name stays itself. */
|
|
89
|
+
function canonicalAction(action) {
|
|
90
|
+
return ACTION_ALIASES[action] ?? action;
|
|
91
|
+
}
|
|
92
|
+
/**
|
|
93
|
+
* Core's own name for a clawgram action, and the only thing that makes the
|
|
94
|
+
* action reachable from the agent's `message` tool.
|
|
95
|
+
*
|
|
96
|
+
* Core keys its target policy by `CHANNEL_MESSAGE_ACTION_NAMES`, and an action
|
|
97
|
+
* outside that vocabulary is simultaneously "requires a target" and "does not
|
|
98
|
+
* accept a target" — there is no call that satisfies both. Declaring `chatId`
|
|
99
|
+
* through `messageActionTargetAliases` looks like the fix and is not: core
|
|
100
|
+
* resolves the channel with `getBootstrapChannelPlugin`, which only knows
|
|
101
|
+
* bundled channels, so a plugin channel's declaration is never read. Measured
|
|
102
|
+
* on 2026-08-30 — `thread-list` reached `handleAction` and `topics` did not,
|
|
103
|
+
* from the same caller, on the same chat.
|
|
104
|
+
*
|
|
105
|
+
* Every name on the right maps to core target mode `"none"` except
|
|
106
|
+
* `channel-info`, which is `"channelId"`: the chat arrives in
|
|
107
|
+
* `params.channelId`, a spelling no parser here read until 2.21.0 — so the
|
|
108
|
+
* call fell through to the current chat and answered about the wrong one.
|
|
109
|
+
* `readChatTargetParam` is the single list of accepted spellings now.
|
|
110
|
+
*
|
|
111
|
+
* These spellings are derived from `ACTION_ALIASES` rather than kept beside
|
|
112
|
+
* it; that core actually knows each of them is asserted against the installed
|
|
113
|
+
* core in `core-action-synonyms.test.ts`.
|
|
114
|
+
*/
|
|
115
|
+
const CORE_VOCABULARY_SPELLINGS = [
|
|
116
|
+
"thread-list", "channel-list", "channel-info", "member-info", "download-file",
|
|
117
|
+
"channel-create", "addParticipant", "kick", "role-add", "role-remove",
|
|
118
|
+
];
|
|
119
|
+
exports.CORE_ACTION_SYNONYMS = Object.fromEntries(CORE_VOCABULARY_SPELLINGS.map((name) => [name, ACTION_ALIASES[name]]));
|
|
120
|
+
/** Canonical actions that go through the chat-management gate. */
|
|
121
|
+
exports.MANAGE_ACTIONS = new Set([
|
|
122
|
+
"createGroup", "addMembers", "removeMember",
|
|
123
|
+
"promoteAdmin", "demoteAdmin", "transferOwnership", "inviteLink",
|
|
124
|
+
]);
|
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
// Входящее вложение: скачать, понять, отдать текстом.
|
|
3
|
+
//
|
|
4
|
+
// Вынесено из channel.ts — файла на 3145 строк при следующем по величине
|
|
5
|
+
// 1165 (находка A6-11). Ни диспетчера, ни конфигурации канала здесь нет:
|
|
6
|
+
// только путь «файл → текст», общий у входящего контура и у `fetch-media`.
|
|
7
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
8
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
9
|
+
};
|
|
10
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
11
|
+
exports.INBOUND_MEDIA_MAX_BYTES = void 0;
|
|
12
|
+
exports.understandAttachmentFile = understandAttachmentFile;
|
|
13
|
+
exports.readInboundAttachment = readInboundAttachment;
|
|
14
|
+
const node_os_1 = __importDefault(require("node:os"));
|
|
15
|
+
const node_path_1 = __importDefault(require("node:path"));
|
|
16
|
+
const node_fs_1 = require("node:fs");
|
|
17
|
+
const media_1 = require("./media");
|
|
18
|
+
const state_dir_1 = require("./state-dir");
|
|
19
|
+
/** Attachments above this are left unread: a long recording or a huge image is
|
|
20
|
+
* a different conversation from a spoken line or a screenshot, and the
|
|
21
|
+
* transfer is not free. */
|
|
22
|
+
exports.INBOUND_MEDIA_MAX_BYTES = 25 * 1024 * 1024;
|
|
23
|
+
/**
|
|
24
|
+
* Locates the agent directory that image understanding needs.
|
|
25
|
+
*
|
|
26
|
+
* Image models are called with the agent's own credentials, so the pipeline
|
|
27
|
+
* refuses to run without this path — audio does not need it, which is why
|
|
28
|
+
* voice notes worked before images did. The platform exposes no resolver to
|
|
29
|
+
* plugins, so the documented layout is reconstructed here and checked before
|
|
30
|
+
* use: a wrong guess would fail the read anyway, and returning undefined lets
|
|
31
|
+
* the caller degrade instead of throwing.
|
|
32
|
+
*/
|
|
33
|
+
function resolveAgentDirForMedia(cfg) {
|
|
34
|
+
const stateDir = (0, state_dir_1.resolveStateDir)();
|
|
35
|
+
const configuredId = cfg?.agents?.defaults?.id;
|
|
36
|
+
const agentId = typeof configuredId === "string" && configuredId.trim() ? configuredId.trim() : "main";
|
|
37
|
+
const dir = node_path_1.default.join(stateDir, "agents", agentId, "agent");
|
|
38
|
+
return (0, node_fs_1.existsSync)(dir) ? dir : undefined;
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* Turns a downloaded attachment into text.
|
|
42
|
+
*
|
|
43
|
+
* Shared by the inbound path and by `fetch-media`: the backend choice lives in
|
|
44
|
+
* `runtime.mediaUnderstanding`, and both callers have to make exactly the same
|
|
45
|
+
* call — an image read on arrival and the same image read on request must not
|
|
46
|
+
* become two different readings because two call sites drifted.
|
|
47
|
+
*/
|
|
48
|
+
async function understandAttachmentFile(params) {
|
|
49
|
+
const media = params.runtime?.mediaUnderstanding;
|
|
50
|
+
if (!media)
|
|
51
|
+
return undefined;
|
|
52
|
+
const result = params.understanding === "transcript"
|
|
53
|
+
? await media.transcribeAudioFile({
|
|
54
|
+
filePath: params.filePath,
|
|
55
|
+
cfg: params.cfg,
|
|
56
|
+
mime: params.mimeType,
|
|
57
|
+
})
|
|
58
|
+
: await media.describeImageFile({
|
|
59
|
+
filePath: params.filePath,
|
|
60
|
+
cfg: params.cfg,
|
|
61
|
+
mime: params.mimeType,
|
|
62
|
+
agentDir: resolveAgentDirForMedia(params.cfg),
|
|
63
|
+
});
|
|
64
|
+
const text = typeof result?.text === "string" ? result.text.trim() : "";
|
|
65
|
+
return text || undefined;
|
|
66
|
+
}
|
|
67
|
+
async function readInboundAttachment(params) {
|
|
68
|
+
const media = params.runtime?.mediaUnderstanding;
|
|
69
|
+
const message = params.event?.message;
|
|
70
|
+
if (!media || !message) {
|
|
71
|
+
return undefined;
|
|
72
|
+
}
|
|
73
|
+
let downloaded;
|
|
74
|
+
try {
|
|
75
|
+
downloaded = await (0, media_1.downloadInboundMediaToTempFile)({
|
|
76
|
+
client: params.gram.getClient(),
|
|
77
|
+
message,
|
|
78
|
+
maxBytes: exports.INBOUND_MEDIA_MAX_BYTES,
|
|
79
|
+
tmpDir: node_os_1.default.tmpdir(),
|
|
80
|
+
});
|
|
81
|
+
}
|
|
82
|
+
catch (err) {
|
|
83
|
+
params.log?.info?.("clawgram attachment download failed", {
|
|
84
|
+
accountId: params.accountId,
|
|
85
|
+
chatId: params.chatId,
|
|
86
|
+
messageId: params.messageId,
|
|
87
|
+
error: String(err),
|
|
88
|
+
});
|
|
89
|
+
return undefined;
|
|
90
|
+
}
|
|
91
|
+
if (!downloaded) {
|
|
92
|
+
return undefined;
|
|
93
|
+
}
|
|
94
|
+
try {
|
|
95
|
+
const read = await understandAttachmentFile({
|
|
96
|
+
runtime: params.runtime,
|
|
97
|
+
cfg: params.cfg,
|
|
98
|
+
filePath: downloaded.path,
|
|
99
|
+
mimeType: downloaded.mimeType,
|
|
100
|
+
understanding: downloaded.understanding,
|
|
101
|
+
});
|
|
102
|
+
if (!read) {
|
|
103
|
+
params.log?.info?.("clawgram attachment read empty", {
|
|
104
|
+
accountId: params.accountId,
|
|
105
|
+
chatId: params.chatId,
|
|
106
|
+
messageId: params.messageId,
|
|
107
|
+
understanding: downloaded.understanding,
|
|
108
|
+
});
|
|
109
|
+
return undefined;
|
|
110
|
+
}
|
|
111
|
+
params.log?.info?.("clawgram attachment read", {
|
|
112
|
+
accountId: params.accountId,
|
|
113
|
+
chatId: params.chatId,
|
|
114
|
+
messageId: params.messageId,
|
|
115
|
+
understanding: downloaded.understanding,
|
|
116
|
+
characters: read.length,
|
|
117
|
+
});
|
|
118
|
+
return { text: read, understanding: downloaded.understanding };
|
|
119
|
+
}
|
|
120
|
+
catch (err) {
|
|
121
|
+
params.log?.info?.("clawgram attachment read failed", {
|
|
122
|
+
accountId: params.accountId,
|
|
123
|
+
chatId: params.chatId,
|
|
124
|
+
messageId: params.messageId,
|
|
125
|
+
understanding: downloaded.understanding,
|
|
126
|
+
error: String(err),
|
|
127
|
+
});
|
|
128
|
+
return undefined;
|
|
129
|
+
}
|
|
130
|
+
finally {
|
|
131
|
+
void (async () => {
|
|
132
|
+
try {
|
|
133
|
+
const { rm } = await import("node:fs/promises");
|
|
134
|
+
const { dirname } = await import("node:path");
|
|
135
|
+
await rm(dirname(downloaded.path), { recursive: true, force: true });
|
|
136
|
+
}
|
|
137
|
+
catch {
|
|
138
|
+
// Leaving a temp file behind is not worth failing a delivered message.
|
|
139
|
+
}
|
|
140
|
+
})();
|
|
141
|
+
}
|
|
142
|
+
}
|