openclaw-channel-dmwork 0.2.21 → 0.2.23

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.
@@ -10,6 +10,8 @@ export interface DmworkAccountConfig {
10
10
  heartbeatIntervalMs?: number;
11
11
  requireMention?: boolean;
12
12
  botUid?: string;
13
+ historyLimit?: number; // 群聊历史消息条数限制(默认20)
14
+ historyPromptTemplate?: string; // Template for group history context injection
13
15
  }
14
16
 
15
17
  export interface DmworkConfig {
@@ -22,34 +24,48 @@ export interface DmworkConfig {
22
24
  heartbeatIntervalMs?: number;
23
25
  requireMention?: boolean;
24
26
  botUid?: string;
27
+ historyLimit?: number; // 群聊历史消息条数限制(默认20)
28
+ historyPromptTemplate?: string; // Template for group history context injection
25
29
  accounts?: Record<string, DmworkAccountConfig | undefined>;
26
30
  }
27
31
 
32
+ // Default English template for history prompt (supports {messages}, {count} placeholders)
33
+ export const DEFAULT_HISTORY_PROMPT_TEMPLATE =
34
+ "[Group Chat History] Below are messages from others since your last reply (sender is user ID, body is message content):\n```json\n{messages}\n```\nPlease respond to the current @mention based on this context.\n\n";
35
+
28
36
  // JSON Schema for OpenClaw plugin config validation
29
37
  export const DmworkConfigJsonSchema = {
30
- type: "object" as const,
31
- properties: {
32
- name: { type: "string" },
33
- enabled: { type: "boolean" },
34
- botToken: { type: "string" },
35
- apiUrl: { type: "string" },
36
- wsUrl: { type: "string" },
37
- pollIntervalMs: { type: "number", minimum: 500 },
38
- heartbeatIntervalMs: { type: "number", minimum: 5000 },
39
- requireMention: { type: "boolean" },
40
- botUid: { type: "string" },
41
- accounts: {
42
- type: "object",
43
- additionalProperties: {
38
+ schema: {
39
+ type: "object" as const,
40
+ properties: {
41
+ name: { type: "string" },
42
+ enabled: { type: "boolean" },
43
+ botToken: { type: "string" },
44
+ apiUrl: { type: "string" },
45
+ wsUrl: { type: "string" },
46
+ pollIntervalMs: { type: "number", minimum: 500 },
47
+ heartbeatIntervalMs: { type: "number", minimum: 5000 },
48
+ requireMention: { type: "boolean" },
49
+ botUid: { type: "string" },
50
+ historyLimit: { type: "number", minimum: 1, maximum: 100 },
51
+ historyPromptTemplate: { type: "string" },
52
+ accounts: {
44
53
  type: "object",
45
- properties: {
46
- name: { type: "string" },
47
- enabled: { type: "boolean" },
48
- botToken: { type: "string" },
49
- apiUrl: { type: "string" },
50
- wsUrl: { type: "string" },
51
- requireMention: { type: "boolean" },
52
- botUid: { type: "string" },
54
+ additionalProperties: {
55
+ type: "object",
56
+ properties: {
57
+ name: { type: "string" },
58
+ enabled: { type: "boolean" },
59
+ botToken: { type: "string" },
60
+ apiUrl: { type: "string" },
61
+ wsUrl: { type: "string" },
62
+ pollIntervalMs: { type: "number", minimum: 500 },
63
+ heartbeatIntervalMs: { type: "number", minimum: 5000 },
64
+ requireMention: { type: "boolean" },
65
+ botUid: { type: "string" },
66
+ historyLimit: { type: "number", minimum: 1, maximum: 100 },
67
+ historyPromptTemplate: { type: "string" },
68
+ },
53
69
  },
54
70
  },
55
71
  },
@@ -0,0 +1,168 @@
1
+ import { describe, it, expect } from "vitest";
2
+ import { ChannelType, MessageType, type MentionPayload } from "./types.js";
3
+ import { DEFAULT_HISTORY_PROMPT_TEMPLATE } from "./config-schema.js";
4
+
5
+ /**
6
+ * Tests for mention.all detection logic.
7
+ *
8
+ * The API can return mention.all as either:
9
+ * - boolean `true` (newer API versions)
10
+ * - number `1` (older API versions / WuKongIM native format)
11
+ *
12
+ * Both should be treated as "mention all".
13
+ */
14
+ describe("mention.all detection", () => {
15
+ // Helper to simulate the detection logic from inbound.ts
16
+ function isMentionAll(mention?: MentionPayload): boolean {
17
+ const mentionAllRaw = mention?.all;
18
+ return mentionAllRaw === true || mentionAllRaw === 1;
19
+ }
20
+
21
+ it("should detect mention.all when all is boolean true", () => {
22
+ const mention: MentionPayload = { all: true };
23
+ expect(isMentionAll(mention)).toBe(true);
24
+ });
25
+
26
+ it("should detect mention.all when all is numeric 1", () => {
27
+ const mention: MentionPayload = { all: 1 };
28
+ expect(isMentionAll(mention)).toBe(true);
29
+ });
30
+
31
+ it("should NOT detect mention.all when all is false", () => {
32
+ const mention: MentionPayload = { all: false as unknown as boolean | number };
33
+ expect(isMentionAll(mention)).toBe(false);
34
+ });
35
+
36
+ it("should NOT detect mention.all when all is 0", () => {
37
+ const mention: MentionPayload = { all: 0 };
38
+ expect(isMentionAll(mention)).toBe(false);
39
+ });
40
+
41
+ it("should NOT detect mention.all when all is undefined", () => {
42
+ const mention: MentionPayload = { uids: ["user1"] };
43
+ expect(isMentionAll(mention)).toBe(false);
44
+ });
45
+
46
+ it("should NOT detect mention.all when mention is undefined", () => {
47
+ expect(isMentionAll(undefined)).toBe(false);
48
+ });
49
+
50
+ it("should NOT detect mention.all when all is a different number", () => {
51
+ const mention: MentionPayload = { all: 2 };
52
+ expect(isMentionAll(mention)).toBe(false);
53
+ });
54
+ });
55
+
56
+ /**
57
+ * Tests for historyPromptTemplate configuration.
58
+ *
59
+ * The template supports placeholders:
60
+ * - {messages}: JSON stringified array of {sender, body} objects
61
+ * - {count}: Number of messages in the history
62
+ */
63
+ describe("historyPromptTemplate", () => {
64
+ // Helper to render template (mirrors logic from inbound.ts)
65
+ function renderHistoryPrompt(
66
+ template: string,
67
+ entries: Array<{ sender: string; body: string }>,
68
+ ): string {
69
+ const messagesJson = JSON.stringify(
70
+ entries.map((e) => ({ sender: e.sender, body: e.body })),
71
+ null,
72
+ 2,
73
+ );
74
+ return template
75
+ .replace("{messages}", messagesJson)
76
+ .replace("{count}", String(entries.length));
77
+ }
78
+
79
+ it("should use English as default template", () => {
80
+ expect(DEFAULT_HISTORY_PROMPT_TEMPLATE).toContain("[Group Chat History]");
81
+ expect(DEFAULT_HISTORY_PROMPT_TEMPLATE).toContain("{messages}");
82
+ });
83
+
84
+ it("should replace {messages} placeholder with JSON", () => {
85
+ const entries = [
86
+ { sender: "user1", body: "Hello" },
87
+ { sender: "user2", body: "Hi there" },
88
+ ];
89
+ const result = renderHistoryPrompt(DEFAULT_HISTORY_PROMPT_TEMPLATE, entries);
90
+
91
+ expect(result).toContain('"sender": "user1"');
92
+ expect(result).toContain('"body": "Hello"');
93
+ expect(result).toContain('"sender": "user2"');
94
+ expect(result).toContain('"body": "Hi there"');
95
+ });
96
+
97
+ it("should replace {count} placeholder with message count", () => {
98
+ const customTemplate = "You have {count} messages:\n{messages}";
99
+ const entries = [
100
+ { sender: "user1", body: "Hello" },
101
+ { sender: "user2", body: "Hi" },
102
+ { sender: "user3", body: "Hey" },
103
+ ];
104
+ const result = renderHistoryPrompt(customTemplate, entries);
105
+
106
+ expect(result).toContain("You have 3 messages:");
107
+ });
108
+
109
+ it("should support custom templates with both placeholders", () => {
110
+ const customTemplate =
111
+ "--- History ({count} messages) ---\n{messages}\n--- End History ---";
112
+ const entries = [{ sender: "alice", body: "Test message" }];
113
+ const result = renderHistoryPrompt(customTemplate, entries);
114
+
115
+ expect(result).toContain("--- History (1 messages) ---");
116
+ expect(result).toContain('"sender": "alice"');
117
+ expect(result).toContain("--- End History ---");
118
+ });
119
+
120
+ it("should handle empty entries array", () => {
121
+ const result = renderHistoryPrompt(DEFAULT_HISTORY_PROMPT_TEMPLATE, []);
122
+ expect(result).toContain("[]");
123
+ });
124
+ });
125
+
126
+ /**
127
+ * Tests for timestamp standardization.
128
+ *
129
+ * getChannelMessages should return timestamps in milliseconds (internal standard),
130
+ * converting from the API's seconds-based timestamps.
131
+ */
132
+ describe("timestamp standardization", () => {
133
+ it("should convert seconds to milliseconds", () => {
134
+ // Simulate the conversion logic from getChannelMessages
135
+ const apiTimestampSeconds = 1709654400; // Example: 2024-03-05 in seconds
136
+ const expectedMs = apiTimestampSeconds * 1000;
137
+
138
+ // This mirrors the conversion in api-fetch.ts
139
+ const convertedTimestamp = apiTimestampSeconds * 1000;
140
+
141
+ expect(convertedTimestamp).toBe(expectedMs);
142
+ expect(convertedTimestamp).toBe(1709654400000);
143
+ });
144
+
145
+ it("should handle undefined timestamp with fallback", () => {
146
+ // Simulate fallback logic: (m.timestamp ?? Math.floor(Date.now() / 1000)) * 1000
147
+ const now = Date.now();
148
+ const fallbackSeconds = Math.floor(now / 1000);
149
+ const apiTimestamp: number | undefined = undefined;
150
+ const result = (apiTimestamp ?? fallbackSeconds) * 1000;
151
+
152
+ // Result should be close to current time in ms
153
+ expect(result).toBeGreaterThan(now - 1000);
154
+ expect(result).toBeLessThanOrEqual(now + 1000);
155
+ });
156
+
157
+ it("timestamp from getChannelMessages should be in milliseconds range", () => {
158
+ // Typical millisecond timestamp has 13 digits (until year 2286)
159
+ const msTimestamp = 1709654400000;
160
+ const secondsTimestamp = 1709654400;
161
+
162
+ expect(String(msTimestamp).length).toBe(13);
163
+ expect(String(secondsTimestamp).length).toBe(10);
164
+
165
+ // After conversion, seconds become milliseconds
166
+ expect(String(secondsTimestamp * 1000).length).toBe(13);
167
+ });
168
+ });