openclaw-threema 0.6.2 ā 0.6.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/CHANGELOG.md +44 -0
- package/dist/index.js +162 -26
- package/index.ts +157 -29
- package/openclaw.plugin.json +1 -1
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,49 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.6.4 (2026-05-04)
|
|
4
|
+
|
|
5
|
+
### Fixed
|
|
6
|
+
- v0.6.3 introduced a regression where file inbounds always fell back to
|
|
7
|
+
the legacy `enqueueSystemEvent` path. The new pipeline branch was
|
|
8
|
+
trying to call a non-existent `channelRuntime.reply.resolveDirectSession-
|
|
9
|
+
Key`. The text path doesn't use that helper at all; it uses
|
|
10
|
+
`channelRuntime.routing.resolveAgentRoute` + `buildAgentSessionKey`.
|
|
11
|
+
This release applies the same approach to the file path, so file
|
|
12
|
+
inbounds finally land in the live Threema DM session.
|
|
13
|
+
- Symptom: `Threema file inbound pipeline error: channelRuntime.reply.
|
|
14
|
+
resolveDirectSessionKey is not a function` followed by
|
|
15
|
+
`dispatched via enqueueSystemEvent (fallback)` for every file message.
|
|
16
|
+
|
|
17
|
+
## 0.6.3 (2026-05-04)
|
|
18
|
+
|
|
19
|
+
### Fixed
|
|
20
|
+
- **File messages now route through the Channel Inbound Pipeline** like text
|
|
21
|
+
messages, so they appear as part of the same Threema DM conversation
|
|
22
|
+
the agent is already in. Previously file inbounds were dispatched via
|
|
23
|
+
legacy `enqueueSystemEvent` against `agent:main:main`, which left them
|
|
24
|
+
invisible to the running DM session: the agent only learned about the
|
|
25
|
+
file by chance (e.g. by greping the inbound media folder later).
|
|
26
|
+
Symptom hit on 2026-05-04 when the user shipped the OpenClaw
|
|
27
|
+
2026.5.3 update protocol as a `.txt` file and the agent did not see it
|
|
28
|
+
for ~20 minutes.
|
|
29
|
+
|
|
30
|
+
### Added
|
|
31
|
+
- File inbounds now expose `MediaPath` / `MediaType` / `MediaUrl` to the
|
|
32
|
+
agent context (matching the convention used by the Matrix channel
|
|
33
|
+
plugin). The agent can read the saved file directly with its normal
|
|
34
|
+
tools (read / pdf / image) and reply in the same DM thread.
|
|
35
|
+
- The memory briefing block is now also injected into file inbounds, so
|
|
36
|
+
the agent's acute-state context applies regardless of whether the
|
|
37
|
+
user sent text or a file.
|
|
38
|
+
- Voice notes (audio file messages) get the same treatment: the
|
|
39
|
+
Whisper transcription is included in the body, the audio path in
|
|
40
|
+
`MediaPath`.
|
|
41
|
+
|
|
42
|
+
### Compatibility
|
|
43
|
+
- Falls back to the legacy `enqueueSystemEvent` path on older OpenClaw
|
|
44
|
+
runtimes that don't expose `channelRuntime.reply.finalizeInboundContext`,
|
|
45
|
+
or whenever the new pipeline path throws.
|
|
46
|
+
|
|
3
47
|
## 0.6.2 (2026-05-04)
|
|
4
48
|
|
|
5
49
|
### Fixed
|
package/dist/index.js
CHANGED
|
@@ -1649,37 +1649,173 @@ export default function register(api) {
|
|
|
1649
1649
|
api.logger?.debug?.(`Threema file from=${from} messageId=${messageId}`);
|
|
1650
1650
|
// Process the file: download, decrypt, save, maybe transcribe
|
|
1651
1651
|
const result = await processFileMessage(client, fileMsg, from, api.logger);
|
|
1652
|
-
//
|
|
1653
|
-
|
|
1654
|
-
|
|
1655
|
-
|
|
1656
|
-
|
|
1657
|
-
|
|
1652
|
+
// Build a body summarising the inbound file. The actual binary
|
|
1653
|
+
// lives at result.filePath; we expose it via MediaPath so the
|
|
1654
|
+
// agent can read/inspect it with its normal tools (read/pdf/image).
|
|
1655
|
+
const fileLines = [];
|
|
1656
|
+
const fileLabel = fileMsg.n || "unnamed";
|
|
1657
|
+
const fileMime = fileMsg.m || "application/octet-stream";
|
|
1658
|
+
const fileSize = typeof fileMsg.s === "number" ? `${fileMsg.s} bytes` : "unknown size";
|
|
1659
|
+
if (fileMsg.d) {
|
|
1660
|
+
fileLines.push(fileMsg.d);
|
|
1661
|
+
}
|
|
1662
|
+
fileLines.push(`[user sent file: ${fileLabel} (${fileMime}, ${fileSize})]`);
|
|
1663
|
+
if (result?.filePath) {
|
|
1664
|
+
fileLines.push(`Saved at: ${result.filePath}`);
|
|
1665
|
+
if (result.transcription) {
|
|
1666
|
+
fileLines.push("");
|
|
1667
|
+
fileLines.push("š¤ Audio transcription:");
|
|
1668
|
+
fileLines.push(result.transcription);
|
|
1658
1669
|
}
|
|
1659
|
-
|
|
1660
|
-
|
|
1661
|
-
|
|
1662
|
-
|
|
1663
|
-
|
|
1670
|
+
}
|
|
1671
|
+
else {
|
|
1672
|
+
fileLines.push("ā ļø Failed to download/decrypt file");
|
|
1673
|
+
}
|
|
1674
|
+
const fileBody = fileLines.join("\n");
|
|
1675
|
+
// Try the Channel Inbound Pipeline first (same path as text
|
|
1676
|
+
// messages). This routes the inbound to the existing Threema DM
|
|
1677
|
+
// session, runs the agent in-context, and lets us reply via
|
|
1678
|
+
// dispatchReplyWithBufferedBlockDispatcher just like text.
|
|
1679
|
+
const channelRuntime = runtime?.channel;
|
|
1680
|
+
if (channelRuntime?.routing?.resolveAgentRoute
|
|
1681
|
+
&& channelRuntime?.routing?.buildAgentSessionKey
|
|
1682
|
+
&& channelRuntime?.reply?.finalizeInboundContext
|
|
1683
|
+
&& channelRuntime?.reply?.dispatchReplyWithBufferedBlockDispatcher) {
|
|
1684
|
+
try {
|
|
1685
|
+
const currentCfg = runtime.config.loadConfig();
|
|
1686
|
+
// Resolve the same agent route + session key the text path uses
|
|
1687
|
+
// so file inbounds end up in the live Threema DM session.
|
|
1688
|
+
const route = channelRuntime.routing.resolveAgentRoute({
|
|
1689
|
+
cfg: currentCfg,
|
|
1690
|
+
channel: "threema",
|
|
1691
|
+
accountId: "default",
|
|
1692
|
+
peer: { kind: "direct", id: from },
|
|
1693
|
+
});
|
|
1694
|
+
const sessionKey = channelRuntime.routing.buildAgentSessionKey({
|
|
1695
|
+
agentId: route.agentId,
|
|
1696
|
+
channel: "threema",
|
|
1697
|
+
accountId: "default",
|
|
1698
|
+
peer: { kind: "direct", id: from },
|
|
1699
|
+
dmScope: "per-account-channel-peer",
|
|
1700
|
+
});
|
|
1701
|
+
const senderAllowed = allowFrom.length === 0 || allowFrom.includes(from);
|
|
1702
|
+
const bodyForAgent = composeBodyForAgent(fileBody, currentCfg);
|
|
1703
|
+
const fileCtx = channelRuntime.reply.finalizeInboundContext({
|
|
1704
|
+
Body: fileBody,
|
|
1705
|
+
RawBody: fileBody,
|
|
1706
|
+
CommandBody: fileMsg.d || "",
|
|
1707
|
+
BodyForAgent: bodyForAgent,
|
|
1708
|
+
From: `threema:${from}`,
|
|
1709
|
+
To: `threema:${ownGatewayId}`,
|
|
1710
|
+
SessionKey: sessionKey,
|
|
1711
|
+
AccountId: "default",
|
|
1712
|
+
OriginatingChannel: "threema",
|
|
1713
|
+
OriginatingTo: `threema:${from}`,
|
|
1714
|
+
ChatType: "direct",
|
|
1715
|
+
SenderName: senderLabel,
|
|
1716
|
+
SenderId: from,
|
|
1717
|
+
Provider: "threema",
|
|
1718
|
+
Surface: "threema",
|
|
1719
|
+
ConversationLabel: senderLabel || from,
|
|
1720
|
+
Timestamp: Date.now(),
|
|
1721
|
+
CommandAuthorized: senderAllowed,
|
|
1722
|
+
MediaPath: result?.filePath,
|
|
1723
|
+
MediaUrl: result?.filePath,
|
|
1724
|
+
MediaType: fileMime,
|
|
1725
|
+
MessageSid: messageId,
|
|
1726
|
+
});
|
|
1727
|
+
const replyClient = new ThreemaClient(getThreemaConfig(currentCfg));
|
|
1728
|
+
await channelRuntime.reply.dispatchReplyWithBufferedBlockDispatcher({
|
|
1729
|
+
ctx: fileCtx,
|
|
1730
|
+
cfg: currentCfg,
|
|
1731
|
+
dispatcherOptions: {
|
|
1732
|
+
deliver: async (payload) => {
|
|
1733
|
+
const text = payload.text ?? payload.body;
|
|
1734
|
+
if (!text)
|
|
1735
|
+
return;
|
|
1736
|
+
const limit = getThreemaConfig(currentCfg)?.textChunkLimit ?? 3500;
|
|
1737
|
+
if (text.length <= limit) {
|
|
1738
|
+
if (replyClient.isE2EEnabled) {
|
|
1739
|
+
await replyClient.sendE2E(from, text);
|
|
1740
|
+
}
|
|
1741
|
+
else {
|
|
1742
|
+
await replyClient.sendSimple(from, text);
|
|
1743
|
+
}
|
|
1744
|
+
}
|
|
1745
|
+
else {
|
|
1746
|
+
const chunks = [];
|
|
1747
|
+
let remaining = text;
|
|
1748
|
+
while (remaining.length > 0) {
|
|
1749
|
+
if (remaining.length <= limit) {
|
|
1750
|
+
chunks.push(remaining);
|
|
1751
|
+
break;
|
|
1752
|
+
}
|
|
1753
|
+
let splitIdx = remaining.lastIndexOf("\n", limit);
|
|
1754
|
+
if (splitIdx <= 0)
|
|
1755
|
+
splitIdx = limit;
|
|
1756
|
+
chunks.push(remaining.slice(0, splitIdx));
|
|
1757
|
+
remaining = remaining.slice(splitIdx).replace(/^\n/, "");
|
|
1758
|
+
}
|
|
1759
|
+
for (const chunk of chunks) {
|
|
1760
|
+
if (replyClient.isE2EEnabled) {
|
|
1761
|
+
await replyClient.sendE2E(from, chunk);
|
|
1762
|
+
}
|
|
1763
|
+
else {
|
|
1764
|
+
await replyClient.sendSimple(from, chunk);
|
|
1765
|
+
}
|
|
1766
|
+
}
|
|
1767
|
+
}
|
|
1768
|
+
},
|
|
1769
|
+
onReplyStart: () => {
|
|
1770
|
+
api.logger?.info?.(`Threema: agent file-reply started for ${from}`);
|
|
1771
|
+
},
|
|
1772
|
+
},
|
|
1773
|
+
});
|
|
1774
|
+
api.logger?.info?.("Threema file message dispatched via Channel Inbound Pipeline");
|
|
1775
|
+
}
|
|
1776
|
+
catch (pipelineErr) {
|
|
1777
|
+
api.logger?.error?.(`Threema file inbound pipeline error: ${pipelineErr.message}`);
|
|
1778
|
+
// Fallback: legacy enqueueSystemEvent so the file isn't lost.
|
|
1779
|
+
const enqueue = runtime?.system?.enqueueSystemEvent;
|
|
1780
|
+
if (enqueue) {
|
|
1781
|
+
enqueue(`[Threema file from ${senderLabel} (${from})]\n${fileBody}`, {
|
|
1782
|
+
sessionKey: "agent:main:main",
|
|
1783
|
+
deliveryContext: {
|
|
1784
|
+
channel: "threema",
|
|
1785
|
+
to: from,
|
|
1786
|
+
from: from,
|
|
1787
|
+
accountId: "default",
|
|
1788
|
+
mediaPath: result?.filePath,
|
|
1789
|
+
transcription: result?.transcription,
|
|
1790
|
+
},
|
|
1791
|
+
});
|
|
1792
|
+
api.logger?.info?.("Threema file message dispatched via enqueueSystemEvent (fallback)");
|
|
1793
|
+
wakeAgent(config);
|
|
1664
1794
|
}
|
|
1665
1795
|
}
|
|
1796
|
+
}
|
|
1797
|
+
else {
|
|
1798
|
+
// Fallback for older OpenClaw runtimes that don't expose the
|
|
1799
|
+
// channel inbound pipeline.
|
|
1800
|
+
const enqueue = runtime?.system?.enqueueSystemEvent;
|
|
1801
|
+
if (enqueue) {
|
|
1802
|
+
enqueue(`[Threema file from ${senderLabel} (${from})]\n${fileBody}`, {
|
|
1803
|
+
sessionKey: "agent:main:main",
|
|
1804
|
+
deliveryContext: {
|
|
1805
|
+
channel: "threema",
|
|
1806
|
+
to: from,
|
|
1807
|
+
from: from,
|
|
1808
|
+
accountId: "default",
|
|
1809
|
+
mediaPath: result?.filePath,
|
|
1810
|
+
transcription: result?.transcription,
|
|
1811
|
+
},
|
|
1812
|
+
});
|
|
1813
|
+
api.logger?.info?.("Threema file message dispatched via enqueueSystemEvent (legacy)");
|
|
1814
|
+
wakeAgent(config);
|
|
1815
|
+
}
|
|
1666
1816
|
else {
|
|
1667
|
-
|
|
1817
|
+
api.logger?.warn?.("Threema file: neither channel pipeline nor enqueueSystemEvent available");
|
|
1668
1818
|
}
|
|
1669
|
-
enqueue(envelope, {
|
|
1670
|
-
sessionKey: "agent:main:main",
|
|
1671
|
-
deliveryContext: {
|
|
1672
|
-
channel: "threema",
|
|
1673
|
-
to: from,
|
|
1674
|
-
from: from,
|
|
1675
|
-
accountId: "default",
|
|
1676
|
-
mediaPath: result?.filePath,
|
|
1677
|
-
transcription: result?.transcription,
|
|
1678
|
-
},
|
|
1679
|
-
});
|
|
1680
|
-
api.logger?.info?.("Threema file message dispatched via enqueueSystemEvent");
|
|
1681
|
-
// Wake the agent
|
|
1682
|
-
wakeAgent(config);
|
|
1683
1819
|
}
|
|
1684
1820
|
}
|
|
1685
1821
|
else if (decrypted?.type === 0x80) {
|
package/index.ts
CHANGED
|
@@ -2093,42 +2093,170 @@ export default function register(api: any) {
|
|
|
2093
2093
|
api.logger
|
|
2094
2094
|
);
|
|
2095
2095
|
|
|
2096
|
-
//
|
|
2097
|
-
|
|
2098
|
-
|
|
2099
|
-
|
|
2100
|
-
|
|
2101
|
-
|
|
2102
|
-
|
|
2096
|
+
// Build a body summarising the inbound file. The actual binary
|
|
2097
|
+
// lives at result.filePath; we expose it via MediaPath so the
|
|
2098
|
+
// agent can read/inspect it with its normal tools (read/pdf/image).
|
|
2099
|
+
const fileLines: string[] = [];
|
|
2100
|
+
const fileLabel = fileMsg.n || "unnamed";
|
|
2101
|
+
const fileMime = fileMsg.m || "application/octet-stream";
|
|
2102
|
+
const fileSize = typeof fileMsg.s === "number" ? `${fileMsg.s} bytes` : "unknown size";
|
|
2103
|
+
if (fileMsg.d) {
|
|
2104
|
+
fileLines.push(fileMsg.d);
|
|
2105
|
+
}
|
|
2106
|
+
fileLines.push(`[user sent file: ${fileLabel} (${fileMime}, ${fileSize})]`);
|
|
2107
|
+
if (result?.filePath) {
|
|
2108
|
+
fileLines.push(`Saved at: ${result.filePath}`);
|
|
2109
|
+
if (result.transcription) {
|
|
2110
|
+
fileLines.push("");
|
|
2111
|
+
fileLines.push("š¤ Audio transcription:");
|
|
2112
|
+
fileLines.push(result.transcription);
|
|
2103
2113
|
}
|
|
2114
|
+
} else {
|
|
2115
|
+
fileLines.push("ā ļø Failed to download/decrypt file");
|
|
2116
|
+
}
|
|
2117
|
+
const fileBody = fileLines.join("\n");
|
|
2118
|
+
|
|
2119
|
+
// Try the Channel Inbound Pipeline first (same path as text
|
|
2120
|
+
// messages). This routes the inbound to the existing Threema DM
|
|
2121
|
+
// session, runs the agent in-context, and lets us reply via
|
|
2122
|
+
// dispatchReplyWithBufferedBlockDispatcher just like text.
|
|
2123
|
+
const channelRuntime = runtime?.channel;
|
|
2124
|
+
if (channelRuntime?.routing?.resolveAgentRoute
|
|
2125
|
+
&& channelRuntime?.routing?.buildAgentSessionKey
|
|
2126
|
+
&& channelRuntime?.reply?.finalizeInboundContext
|
|
2127
|
+
&& channelRuntime?.reply?.dispatchReplyWithBufferedBlockDispatcher) {
|
|
2128
|
+
try {
|
|
2129
|
+
const currentCfg = runtime.config.loadConfig();
|
|
2104
2130
|
|
|
2105
|
-
|
|
2131
|
+
// Resolve the same agent route + session key the text path uses
|
|
2132
|
+
// so file inbounds end up in the live Threema DM session.
|
|
2133
|
+
const route = channelRuntime.routing.resolveAgentRoute({
|
|
2134
|
+
cfg: currentCfg,
|
|
2135
|
+
channel: "threema",
|
|
2136
|
+
accountId: "default",
|
|
2137
|
+
peer: { kind: "direct", id: from },
|
|
2138
|
+
});
|
|
2106
2139
|
|
|
2107
|
-
|
|
2108
|
-
|
|
2140
|
+
const sessionKey = channelRuntime.routing.buildAgentSessionKey({
|
|
2141
|
+
agentId: route.agentId,
|
|
2142
|
+
channel: "threema",
|
|
2143
|
+
accountId: "default",
|
|
2144
|
+
peer: { kind: "direct", id: from },
|
|
2145
|
+
dmScope: "per-account-channel-peer",
|
|
2146
|
+
});
|
|
2147
|
+
|
|
2148
|
+
const senderAllowed = allowFrom.length === 0 || allowFrom.includes(from);
|
|
2149
|
+
|
|
2150
|
+
const bodyForAgent = composeBodyForAgent(fileBody, currentCfg);
|
|
2151
|
+
const fileCtx = channelRuntime.reply.finalizeInboundContext({
|
|
2152
|
+
Body: fileBody,
|
|
2153
|
+
RawBody: fileBody,
|
|
2154
|
+
CommandBody: fileMsg.d || "",
|
|
2155
|
+
BodyForAgent: bodyForAgent,
|
|
2156
|
+
From: `threema:${from}`,
|
|
2157
|
+
To: `threema:${ownGatewayId}`,
|
|
2158
|
+
SessionKey: sessionKey,
|
|
2159
|
+
AccountId: "default",
|
|
2160
|
+
OriginatingChannel: "threema",
|
|
2161
|
+
OriginatingTo: `threema:${from}`,
|
|
2162
|
+
ChatType: "direct" as const,
|
|
2163
|
+
SenderName: senderLabel,
|
|
2164
|
+
SenderId: from,
|
|
2165
|
+
Provider: "threema",
|
|
2166
|
+
Surface: "threema",
|
|
2167
|
+
ConversationLabel: senderLabel || from,
|
|
2168
|
+
Timestamp: Date.now(),
|
|
2169
|
+
CommandAuthorized: senderAllowed,
|
|
2170
|
+
MediaPath: result?.filePath,
|
|
2171
|
+
MediaUrl: result?.filePath,
|
|
2172
|
+
MediaType: fileMime,
|
|
2173
|
+
MessageSid: messageId,
|
|
2174
|
+
});
|
|
2109
2175
|
|
|
2110
|
-
|
|
2111
|
-
|
|
2176
|
+
const replyClient = new ThreemaClient(getThreemaConfig(currentCfg)!);
|
|
2177
|
+
await channelRuntime.reply.dispatchReplyWithBufferedBlockDispatcher({
|
|
2178
|
+
ctx: fileCtx,
|
|
2179
|
+
cfg: currentCfg,
|
|
2180
|
+
dispatcherOptions: {
|
|
2181
|
+
deliver: async (payload: any) => {
|
|
2182
|
+
const text = payload.text ?? payload.body;
|
|
2183
|
+
if (!text) return;
|
|
2184
|
+
const limit = getThreemaConfig(currentCfg)?.textChunkLimit ?? 3500;
|
|
2185
|
+
if (text.length <= limit) {
|
|
2186
|
+
if (replyClient.isE2EEnabled) {
|
|
2187
|
+
await replyClient.sendE2E(from, text);
|
|
2188
|
+
} else {
|
|
2189
|
+
await replyClient.sendSimple(from, text);
|
|
2190
|
+
}
|
|
2191
|
+
} else {
|
|
2192
|
+
const chunks: string[] = [];
|
|
2193
|
+
let remaining = text;
|
|
2194
|
+
while (remaining.length > 0) {
|
|
2195
|
+
if (remaining.length <= limit) {
|
|
2196
|
+
chunks.push(remaining);
|
|
2197
|
+
break;
|
|
2198
|
+
}
|
|
2199
|
+
let splitIdx = remaining.lastIndexOf("\n", limit);
|
|
2200
|
+
if (splitIdx <= 0) splitIdx = limit;
|
|
2201
|
+
chunks.push(remaining.slice(0, splitIdx));
|
|
2202
|
+
remaining = remaining.slice(splitIdx).replace(/^\n/, "");
|
|
2203
|
+
}
|
|
2204
|
+
for (const chunk of chunks) {
|
|
2205
|
+
if (replyClient.isE2EEnabled) {
|
|
2206
|
+
await replyClient.sendE2E(from, chunk);
|
|
2207
|
+
} else {
|
|
2208
|
+
await replyClient.sendSimple(from, chunk);
|
|
2209
|
+
}
|
|
2210
|
+
}
|
|
2211
|
+
}
|
|
2212
|
+
},
|
|
2213
|
+
onReplyStart: () => {
|
|
2214
|
+
api.logger?.info?.(`Threema: agent file-reply started for ${from}`);
|
|
2215
|
+
},
|
|
2216
|
+
},
|
|
2217
|
+
});
|
|
2218
|
+
api.logger?.info?.("Threema file message dispatched via Channel Inbound Pipeline");
|
|
2219
|
+
} catch (pipelineErr: any) {
|
|
2220
|
+
api.logger?.error?.(`Threema file inbound pipeline error: ${pipelineErr.message}`);
|
|
2221
|
+
// Fallback: legacy enqueueSystemEvent so the file isn't lost.
|
|
2222
|
+
const enqueue = runtime?.system?.enqueueSystemEvent;
|
|
2223
|
+
if (enqueue) {
|
|
2224
|
+
enqueue(`[Threema file from ${senderLabel} (${from})]\n${fileBody}`, {
|
|
2225
|
+
sessionKey: "agent:main:main",
|
|
2226
|
+
deliveryContext: {
|
|
2227
|
+
channel: "threema",
|
|
2228
|
+
to: from,
|
|
2229
|
+
from: from,
|
|
2230
|
+
accountId: "default",
|
|
2231
|
+
mediaPath: result?.filePath,
|
|
2232
|
+
transcription: result?.transcription,
|
|
2233
|
+
},
|
|
2234
|
+
});
|
|
2235
|
+
api.logger?.info?.("Threema file message dispatched via enqueueSystemEvent (fallback)");
|
|
2236
|
+
wakeAgent(config);
|
|
2112
2237
|
}
|
|
2238
|
+
}
|
|
2239
|
+
} else {
|
|
2240
|
+
// Fallback for older OpenClaw runtimes that don't expose the
|
|
2241
|
+
// channel inbound pipeline.
|
|
2242
|
+
const enqueue = runtime?.system?.enqueueSystemEvent;
|
|
2243
|
+
if (enqueue) {
|
|
2244
|
+
enqueue(`[Threema file from ${senderLabel} (${from})]\n${fileBody}`, {
|
|
2245
|
+
sessionKey: "agent:main:main",
|
|
2246
|
+
deliveryContext: {
|
|
2247
|
+
channel: "threema",
|
|
2248
|
+
to: from,
|
|
2249
|
+
from: from,
|
|
2250
|
+
accountId: "default",
|
|
2251
|
+
mediaPath: result?.filePath,
|
|
2252
|
+
transcription: result?.transcription,
|
|
2253
|
+
},
|
|
2254
|
+
});
|
|
2255
|
+
api.logger?.info?.("Threema file message dispatched via enqueueSystemEvent (legacy)");
|
|
2256
|
+
wakeAgent(config);
|
|
2113
2257
|
} else {
|
|
2114
|
-
|
|
2258
|
+
api.logger?.warn?.("Threema file: neither channel pipeline nor enqueueSystemEvent available");
|
|
2115
2259
|
}
|
|
2116
|
-
|
|
2117
|
-
enqueue(envelope, {
|
|
2118
|
-
sessionKey: "agent:main:main",
|
|
2119
|
-
deliveryContext: {
|
|
2120
|
-
channel: "threema",
|
|
2121
|
-
to: from,
|
|
2122
|
-
from: from,
|
|
2123
|
-
accountId: "default",
|
|
2124
|
-
mediaPath: result?.filePath,
|
|
2125
|
-
transcription: result?.transcription,
|
|
2126
|
-
},
|
|
2127
|
-
});
|
|
2128
|
-
api.logger?.info?.("Threema file message dispatched via enqueueSystemEvent");
|
|
2129
|
-
|
|
2130
|
-
// Wake the agent
|
|
2131
|
-
wakeAgent(config);
|
|
2132
2260
|
}
|
|
2133
2261
|
} else if (decrypted?.type === 0x80) {
|
|
2134
2262
|
// Delivery receipt - PII only on debug level
|
package/openclaw.plugin.json
CHANGED