openclaw-channel-dmwork 0.2.2 → 0.2.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "openclaw-channel-dmwork",
3
- "version": "0.2.2",
3
+ "version": "0.2.3",
4
4
  "description": "DMWork channel plugin for OpenClaw via WuKongIM WebSocket",
5
5
  "main": "index.ts",
6
6
  "type": "module",
package/src/channel.ts CHANGED
@@ -16,7 +16,9 @@ import { registerBot, sendMessage, sendHeartbeat } from "./api-fetch.js";
16
16
  import { WKSocket } from "./socket.js";
17
17
  import { handleInboundMessage, type DmworkStatusSink } from "./inbound.js";
18
18
  import { ChannelType, MessageType, type BotMessage, type MessagePayload } from "./types.js";
19
- import { DEFAULT_GROUP_HISTORY_LIMIT, type HistoryEntry } from "openclaw/plugin-sdk";
19
+ // HistoryEntry type - compatible with any version
20
+ type HistoryEntry = { sender: string; body: string; timestamp: number };
21
+ const DEFAULT_GROUP_HISTORY_LIMIT = 20;
20
22
 
21
23
  const meta = {
22
24
  id: "dmwork",
@@ -176,7 +178,7 @@ export const dmworkPlugin: ChannelPlugin<ResolvedDmworkAccount> = {
176
178
  };
177
179
 
178
180
  // 4. Group history map for mention gating context
179
- const groupHistories = new Map<string, HistoryEntry[]>();
181
+ const groupHistories = new Map<string, any[]>();
180
182
 
181
183
  // 5. Token refresh state — detect stale cached token
182
184
  let hasRefreshedToken = false;
package/src/inbound.ts CHANGED
@@ -4,13 +4,33 @@ import type { ResolvedDmworkAccount } from "./accounts.js";
4
4
  import type { BotMessage } from "./types.js";
5
5
  import { ChannelType, MessageType } from "./types.js";
6
6
  import { getDmworkRuntime } from "./runtime.js";
7
- import {
8
- recordPendingHistoryEntryIfEnabled,
9
- buildPendingHistoryContextFromMap,
10
- clearHistoryEntriesIfEnabled,
11
- DEFAULT_GROUP_HISTORY_LIMIT,
12
- type HistoryEntry,
13
- } from "openclaw/plugin-sdk";
7
+
8
+ // Defensive imports — these may not exist in older OpenClaw versions
9
+ let recordPendingHistoryEntryIfEnabled: any;
10
+ let buildPendingHistoryContextFromMap: any;
11
+ let clearHistoryEntriesIfEnabled: any;
12
+ let DEFAULT_GROUP_HISTORY_LIMIT = 20;
13
+
14
+ try {
15
+ const sdk = await import("openclaw/plugin-sdk");
16
+ recordPendingHistoryEntryIfEnabled = sdk.recordPendingHistoryEntryIfEnabled;
17
+ buildPendingHistoryContextFromMap = sdk.buildPendingHistoryContextFromMap;
18
+ clearHistoryEntriesIfEnabled = sdk.clearHistoryEntriesIfEnabled;
19
+ if (sdk.DEFAULT_GROUP_HISTORY_LIMIT) {
20
+ DEFAULT_GROUP_HISTORY_LIMIT = sdk.DEFAULT_GROUP_HISTORY_LIMIT;
21
+ }
22
+ } catch {
23
+ // Older OpenClaw versions may not export these
24
+ }
25
+
26
+
27
+
28
+ // Re-export a minimal HistoryEntry type for when SDK doesn't have it
29
+ export interface HistoryEntryCompat {
30
+ sender: string;
31
+ body: string;
32
+ timestamp: number;
33
+ }
14
34
 
15
35
  export type DmworkStatusSink = (patch: {
16
36
  lastInboundAt?: number;
@@ -29,7 +49,7 @@ export async function handleInboundMessage(params: {
29
49
  account: ResolvedDmworkAccount;
30
50
  message: BotMessage;
31
51
  botUid: string;
32
- groupHistories: Map<string, HistoryEntry[]>;
52
+ groupHistories: Map<string, any[]>;
33
53
  log?: ChannelLogSink;
34
54
  statusSink?: DmworkStatusSink;
35
55
  }) {
@@ -53,11 +73,7 @@ export async function handleInboundMessage(params: {
53
73
  }
54
74
 
55
75
  // --- Mention gating for group messages ---
56
- // In groups, only respond when the bot is explicitly @mentioned via
57
- // payload.mention.uids (structured mention from WuKongIM).
58
- // Unmentioned messages are recorded as history context for when the bot
59
- // IS mentioned later.
60
- const requireMention = account.config.requireMention !== false; // default true
76
+ const requireMention = account.config.requireMention !== false;
61
77
  let historyPrefix = "";
62
78
 
63
79
  if (isGroup && requireMention) {
@@ -66,17 +82,34 @@ export async function handleInboundMessage(params: {
66
82
  const isMentioned = mentionAll || mentionUids.includes(botUid);
67
83
 
68
84
  if (!isMentioned) {
69
- // Record as pending history for future context
70
- recordPendingHistoryEntryIfEnabled({
71
- historyMap: groupHistories,
72
- historyKey: sessionId,
73
- entry: {
85
+ // Record as pending history — with fallback for older SDK
86
+ if (typeof recordPendingHistoryEntryIfEnabled === "function") {
87
+ recordPendingHistoryEntryIfEnabled({
88
+ historyMap: groupHistories,
89
+ historyKey: sessionId,
90
+ entry: {
91
+ sender: message.from_uid,
92
+ body: rawBody,
93
+ timestamp: message.timestamp ? message.timestamp * 1000 : Date.now(),
94
+ },
95
+ limit: DEFAULT_GROUP_HISTORY_LIMIT,
96
+ });
97
+ } else {
98
+ // Manual fallback: store history in the map directly
99
+ if (!groupHistories.has(sessionId)) {
100
+ groupHistories.set(sessionId, []);
101
+ }
102
+ const entries = groupHistories.get(sessionId)!;
103
+ entries.push({
74
104
  sender: message.from_uid,
75
105
  body: rawBody,
76
106
  timestamp: message.timestamp ? message.timestamp * 1000 : Date.now(),
77
- },
78
- limit: DEFAULT_GROUP_HISTORY_LIMIT,
79
- });
107
+ });
108
+ // Trim to limit
109
+ while (entries.length > DEFAULT_GROUP_HISTORY_LIMIT) {
110
+ entries.shift();
111
+ }
112
+ }
80
113
  log?.info?.(
81
114
  `dmwork: group message not mentioning bot, recorded as history context`,
82
115
  );
@@ -84,22 +117,38 @@ export async function handleInboundMessage(params: {
84
117
  }
85
118
 
86
119
  // Bot IS mentioned — prepend history context
87
- const enrichedBody = buildPendingHistoryContextFromMap({
88
- historyMap: groupHistories,
89
- historyKey: sessionId,
90
- currentMessage: rawBody,
91
- limit: DEFAULT_GROUP_HISTORY_LIMIT,
92
- });
93
- if (enrichedBody !== rawBody) {
94
- historyPrefix = enrichedBody.slice(0, enrichedBody.length - rawBody.length);
95
- log?.info?.(`dmwork: prepending history context (${historyPrefix.length} chars)`);
120
+ if (typeof buildPendingHistoryContextFromMap === "function") {
121
+ const enrichedBody = buildPendingHistoryContextFromMap({
122
+ historyMap: groupHistories,
123
+ historyKey: sessionId,
124
+ currentMessage: rawBody,
125
+ limit: DEFAULT_GROUP_HISTORY_LIMIT,
126
+ });
127
+ if (enrichedBody !== rawBody) {
128
+ historyPrefix = enrichedBody.slice(0, enrichedBody.length - rawBody.length);
129
+ log?.info?.(`dmwork: prepending history context (${historyPrefix.length} chars)`);
130
+ }
131
+ } else {
132
+ // Manual fallback: build history prefix
133
+ const entries = groupHistories.get(sessionId) ?? [];
134
+ if (entries.length > 0) {
135
+ historyPrefix = entries
136
+ .map((e: any) => `[${e.sender}]: ${e.body}`)
137
+ .join("\n") + "\n---\n";
138
+ log?.info?.(`dmwork: prepending history context (${historyPrefix.length} chars, fallback)`);
139
+ }
96
140
  }
141
+
97
142
  // Clear history after consuming
98
- clearHistoryEntriesIfEnabled({
99
- historyMap: groupHistories,
100
- historyKey: sessionId,
101
- limit: DEFAULT_GROUP_HISTORY_LIMIT,
102
- });
143
+ if (typeof clearHistoryEntriesIfEnabled === "function") {
144
+ clearHistoryEntriesIfEnabled({
145
+ historyMap: groupHistories,
146
+ historyKey: sessionId,
147
+ limit: DEFAULT_GROUP_HISTORY_LIMIT,
148
+ });
149
+ } else {
150
+ groupHistories.delete(sessionId);
151
+ }
103
152
  }
104
153
 
105
154
  const core = getDmworkRuntime();
@@ -129,13 +178,15 @@ export async function handleInboundMessage(params: {
129
178
  sessionKey: route.sessionKey,
130
179
  });
131
180
 
181
+ const finalBody = historyPrefix ? historyPrefix + rawBody : rawBody;
182
+
132
183
  const body = core.channel.reply.formatAgentEnvelope({
133
184
  channel: "DMWork",
134
185
  from: fromLabel,
135
186
  timestamp: message.timestamp ? message.timestamp * 1000 : undefined,
136
187
  previousTimestamp,
137
188
  envelope: envelopeOptions,
138
- body: rawBody,
189
+ body: finalBody,
139
190
  });
140
191
 
141
192
  const ctxPayload = core.channel.reply.finalizeInboundContext({
@@ -172,7 +223,7 @@ export async function handleInboundMessage(params: {
172
223
  const replyChannelId = isGroup ? message.channel_id! : message.from_uid;
173
224
  const replyChannelType = isGroup ? ChannelType.Group : ChannelType.DM;
174
225
 
175
- // 已读回执 + 正在输入 — fire-and-forget,失败不影响主流程
226
+ // 已读回执 + 正在输入 — fire-and-forget
176
227
  log?.info?.(`dmwork: sending readReceipt+typing to channel=${replyChannelId} type=${replyChannelType} apiUrl=${account.config.apiUrl}`);
177
228
  const messageIds = message.message_id ? [message.message_id] : [];
178
229
  sendReadReceipt({ apiUrl: account.config.apiUrl, botToken: account.config.botToken ?? "", channelId: replyChannelId, channelType: replyChannelType, messageIds })
@@ -202,9 +253,6 @@ export async function handleInboundMessage(params: {
202
253
  const content = contentParts.join("\n").trim();
203
254
  if (!content) return;
204
255
 
205
- const replyChannelId = isGroup ? message.channel_id! : message.from_uid;
206
- const replyChannelType = isGroup ? ChannelType.Group : ChannelType.DM;
207
-
208
256
  await sendMessage({
209
257
  apiUrl: account.config.apiUrl,
210
258
  botToken: account.config.botToken ?? "",