ddchat 0.2.0 → 0.3.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/src/channel.ts CHANGED
@@ -1,101 +1,101 @@
1
- import { createChatChannelPlugin, type OpenClawConfig } from "openclaw/plugin-sdk/core";
2
- import { patchScopedAccountConfig, prepareScopedSetupConfig } from "openclaw/plugin-sdk/setup";
3
- import { DDCHAT_CHANNEL_ID } from "./constants.js";
4
- import { ddchatGateway } from "./gateway.js";
5
- import { ddchatOutbound } from "./outbound.js";
6
- import { ddchatPairing } from "./pairing.js";
7
- import { listDdchatAccountIds, resolveDdchatAccount, type DdchatResolvedAccount } from "./types.js";
8
-
9
- function inspectDdchatAccount(cfg: OpenClawConfig, accountId?: string | null) {
10
- const account = resolveDdchatAccount(cfg, accountId);
11
- return {
12
- enabled: account.enabled,
13
- configured: account.configured,
14
- tokenStatus: account.token ? "available" : "missing",
15
- connectionMode: account.connectionMode,
16
- dmPolicy: account.dmPolicy,
17
- groupPolicy: account.groupPolicy,
18
- streaming: account.streaming,
19
- streamingMode: account.streamingMode,
20
- };
21
- }
22
-
23
- export const ddchatPlugin = createChatChannelPlugin<DdchatResolvedAccount>({
24
- base: {
25
- id: DDCHAT_CHANNEL_ID,
26
- meta: {
27
- id: DDCHAT_CHANNEL_ID,
28
- label: "DDChat",
29
- selectionLabel: "DDChat (IM)",
30
- blurb: "DDChat internal IM integration.",
31
- order: 90,
32
- },
33
- capabilities: {
34
- chatTypes: ["direct", "channel"],
35
- media: true,
36
- threads: false,
37
- polls: false,
38
- },
39
- config: {
40
- listAccountIds: (cfg) => listDdchatAccountIds(cfg),
41
- resolveAccount: (cfg, accountId) => resolveDdchatAccount(cfg, accountId),
42
- inspectAccount: inspectDdchatAccount,
43
- isEnabled: (account) => account.enabled,
44
- isConfigured: (account) => account.configured,
45
- },
46
- setup: {
47
- resolveAccountId: ({ accountId }) => accountId ?? "default",
48
- applyAccountName: ({ cfg, accountId, name }) =>
49
- prepareScopedSetupConfig({
50
- cfg,
51
- channelKey: DDCHAT_CHANNEL_ID,
52
- accountId,
53
- name,
54
- alwaysUseAccounts: true,
55
- }),
56
- validateInput: ({ input }) => {
57
- const token = typeof input.token === "string" ? input.token.trim() : "";
58
- return token ? null : "ddchat requires --token";
59
- },
60
- applyAccountConfig: ({ cfg, accountId, input }) => {
61
- const token = typeof input.token === "string" ? input.token.trim() : "";
62
- const next = prepareScopedSetupConfig({
63
- cfg,
64
- channelKey: DDCHAT_CHANNEL_ID,
65
- accountId,
66
- name: input.name,
67
- alwaysUseAccounts: true,
68
- });
69
- const patch: Record<string, unknown> = {};
70
- if (token) {
71
- patch.token = token;
72
- }
73
- return patchScopedAccountConfig({
74
- cfg: next,
75
- channelKey: DDCHAT_CHANNEL_ID,
76
- accountId,
77
- patch,
78
- accountPatch: patch,
79
- ensureChannelEnabled: true,
80
- ensureAccountEnabled: true,
81
- scopeDefaultToAccounts: true,
82
- });
83
- },
84
- },
85
- gateway: ddchatGateway,
86
- },
87
- pairing: ddchatPairing,
88
- security: {
89
- dm: {
90
- channelKey: DDCHAT_CHANNEL_ID,
91
- resolvePolicy: (account) => account.dmPolicy,
92
- resolveAllowFrom: (account) => account.allowFrom,
93
- defaultPolicy: "pairing",
94
- normalizeEntry: (raw) => raw.replace(/^ddchat:/i, "").trim(),
95
- },
96
- },
97
- outbound: ddchatOutbound,
98
- threading: {
99
- topLevelReplyToMode: "off",
100
- },
101
- });
1
+ import { createChatChannelPlugin, type OpenClawConfig } from "openclaw/plugin-sdk/core";
2
+ import { patchScopedAccountConfig, prepareScopedSetupConfig } from "openclaw/plugin-sdk/setup";
3
+ import { DDCHAT_CHANNEL_ID } from "./constants.js";
4
+ import { ddchatGateway } from "./gateway.js";
5
+ import { ddchatOutbound } from "./outbound.js";
6
+ import { ddchatPairing } from "./pairing.js";
7
+ import { listDdchatAccountIds, resolveDdchatAccount, type DdchatResolvedAccount } from "./types.js";
8
+
9
+ function inspectDdchatAccount(cfg: OpenClawConfig, accountId?: string | null) {
10
+ const account = resolveDdchatAccount(cfg, accountId);
11
+ return {
12
+ enabled: account.enabled,
13
+ configured: account.configured,
14
+ tokenStatus: account.token ? "available" : "missing",
15
+ connectionMode: account.connectionMode,
16
+ dmPolicy: account.dmPolicy,
17
+ groupPolicy: account.groupPolicy,
18
+ streaming: account.streaming,
19
+ streamingMode: account.streamingMode,
20
+ };
21
+ }
22
+
23
+ export const ddchatPlugin = createChatChannelPlugin<DdchatResolvedAccount>({
24
+ base: {
25
+ id: DDCHAT_CHANNEL_ID,
26
+ meta: {
27
+ id: DDCHAT_CHANNEL_ID,
28
+ label: "DDChat",
29
+ selectionLabel: "DDChat (IM)",
30
+ blurb: "DDChat internal IM integration.",
31
+ order: 90,
32
+ },
33
+ capabilities: {
34
+ chatTypes: ["direct", "channel"],
35
+ media: true,
36
+ threads: false,
37
+ polls: false,
38
+ },
39
+ config: {
40
+ listAccountIds: (cfg) => listDdchatAccountIds(cfg),
41
+ resolveAccount: (cfg, accountId) => resolveDdchatAccount(cfg, accountId),
42
+ inspectAccount: inspectDdchatAccount,
43
+ isEnabled: (account) => account.enabled,
44
+ isConfigured: (account) => account.configured,
45
+ },
46
+ setup: {
47
+ resolveAccountId: ({ accountId }) => accountId ?? "default",
48
+ applyAccountName: ({ cfg, accountId, name }) =>
49
+ prepareScopedSetupConfig({
50
+ cfg,
51
+ channelKey: DDCHAT_CHANNEL_ID,
52
+ accountId,
53
+ name,
54
+ alwaysUseAccounts: true,
55
+ }),
56
+ validateInput: ({ input }) => {
57
+ const token = typeof input.token === "string" ? input.token.trim() : "";
58
+ return token ? null : "ddchat requires --token";
59
+ },
60
+ applyAccountConfig: ({ cfg, accountId, input }) => {
61
+ const token = typeof input.token === "string" ? input.token.trim() : "";
62
+ const next = prepareScopedSetupConfig({
63
+ cfg,
64
+ channelKey: DDCHAT_CHANNEL_ID,
65
+ accountId,
66
+ name: input.name,
67
+ alwaysUseAccounts: true,
68
+ });
69
+ const patch: Record<string, unknown> = {};
70
+ if (token) {
71
+ patch.token = token;
72
+ }
73
+ return patchScopedAccountConfig({
74
+ cfg: next,
75
+ channelKey: DDCHAT_CHANNEL_ID,
76
+ accountId,
77
+ patch,
78
+ accountPatch: patch,
79
+ ensureChannelEnabled: true,
80
+ ensureAccountEnabled: true,
81
+ scopeDefaultToAccounts: true,
82
+ });
83
+ },
84
+ },
85
+ gateway: ddchatGateway,
86
+ },
87
+ pairing: ddchatPairing,
88
+ security: {
89
+ dm: {
90
+ channelKey: DDCHAT_CHANNEL_ID,
91
+ resolvePolicy: (account) => account.dmPolicy,
92
+ resolveAllowFrom: (account) => account.allowFrom,
93
+ defaultPolicy: "pairing",
94
+ normalizeEntry: (raw) => raw.replace(/^ddchat:/i, "").trim(),
95
+ },
96
+ },
97
+ outbound: ddchatOutbound,
98
+ threading: {
99
+ topLevelReplyToMode: "off",
100
+ },
101
+ });
package/src/constants.ts CHANGED
@@ -1,5 +1,5 @@
1
- export const DDCHAT_CHANNEL_ID = "ddchat";
2
- export const DDCHAT_DEFAULT_ACCOUNT_ID = "default";
3
-
4
- /** Default WebSocket endpoint when `channels.ddchat.wsUrl` / per-account `wsUrl` is unset (`token` query appended at connect). */
5
- export const DDCHAT_PLUGIN_WS_BASE_URL = "wss://chat.ddjf.info/socket/ai/plugin";
1
+ export const DDCHAT_CHANNEL_ID = "ddchat";
2
+ export const DDCHAT_DEFAULT_ACCOUNT_ID = "default";
3
+
4
+ /** Default WebSocket endpoint when `channels.ddchat.wsUrl` / per-account `wsUrl` is unset (`token` query appended at connect). */
5
+ export const DDCHAT_PLUGIN_WS_BASE_URL = "wss://chat.ddjf.info/socket/ai/claw";
package/src/dedupe.ts CHANGED
@@ -1,31 +1,51 @@
1
- const DEFAULT_TTL_MS = 48 * 60 * 60 * 1000;
2
-
3
- export class DdchatDedupeStore {
4
- private readonly seen = new Map<string, number>();
5
- private readonly ttlMs: number;
6
-
7
- constructor(ttlMs = DEFAULT_TTL_MS) {
8
- this.ttlMs = ttlMs;
9
- }
10
-
11
- isDuplicate(accountId: string, messageId: string): boolean {
12
- this.gc();
13
- const key = `${accountId}:${messageId}`;
14
- const now = Date.now();
15
- const expiresAt = this.seen.get(key);
16
- if (expiresAt && expiresAt > now) {
17
- return true;
18
- }
19
- this.seen.set(key, now + this.ttlMs);
20
- return false;
21
- }
22
-
23
- private gc(): void {
24
- const now = Date.now();
25
- for (const [key, expiresAt] of this.seen.entries()) {
26
- if (expiresAt <= now) {
27
- this.seen.delete(key);
28
- }
29
- }
30
- }
31
- }
1
+ const DEFAULT_TTL_MS = 48 * 60 * 60 * 1000;
2
+ const DEFAULT_GC_INTERVAL_MS = 60 * 1000;
3
+ const DEFAULT_GC_CHECK_INTERVAL = 1000;
4
+
5
+ export class DdchatDedupeStore {
6
+ private readonly seen = new Map<string, number>();
7
+ private readonly ttlMs: number;
8
+ private lastGcAt = 0;
9
+ private checksSinceGc = 0;
10
+
11
+ constructor(
12
+ ttlMs = DEFAULT_TTL_MS,
13
+ private readonly gcIntervalMs = DEFAULT_GC_INTERVAL_MS,
14
+ private readonly gcCheckInterval = DEFAULT_GC_CHECK_INTERVAL,
15
+ ) {
16
+ this.ttlMs = ttlMs;
17
+ }
18
+
19
+ isDuplicate(accountId: string, messageId: string): boolean {
20
+ const key = `${accountId}:${messageId}`;
21
+ const now = Date.now();
22
+ this.gcIfNeeded(now);
23
+ const expiresAt = this.seen.get(key);
24
+ if (expiresAt && expiresAt > now) {
25
+ return true;
26
+ }
27
+ this.seen.set(key, now + this.ttlMs);
28
+ return false;
29
+ }
30
+
31
+ private gcIfNeeded(now: number): void {
32
+ this.checksSinceGc += 1;
33
+ if (
34
+ now - this.lastGcAt < this.gcIntervalMs &&
35
+ this.checksSinceGc < this.gcCheckInterval
36
+ ) {
37
+ return;
38
+ }
39
+ this.lastGcAt = now;
40
+ this.checksSinceGc = 0;
41
+ this.gc(now);
42
+ }
43
+
44
+ private gc(now: number): void {
45
+ for (const [key, expiresAt] of this.seen.entries()) {
46
+ if (expiresAt <= now) {
47
+ this.seen.delete(key);
48
+ }
49
+ }
50
+ }
51
+ }