@invago/mixin 1.0.14 → 1.0.16
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/index.ts +5 -2
- package/openclaw.plugin.json +1 -1
- package/package.json +1 -1
- package/src/channel.ts +24 -18
- package/src/inbound-handler.ts +96 -21
- package/src/status.ts +65 -1
package/index.ts
CHANGED
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import type { OpenClawPluginApi, OpenClawConfig } from "openclaw/plugin-sdk";
|
|
2
|
-
import { emptyPluginConfigSchema } from "openclaw/plugin-sdk";
|
|
3
2
|
import { mixinPlugin } from "./src/channel.js";
|
|
4
3
|
import {
|
|
5
4
|
buildMixinAccountsText,
|
|
@@ -33,7 +32,11 @@ const plugin = {
|
|
|
33
32
|
id: "mixin",
|
|
34
33
|
name: "Mixin Messenger Channel",
|
|
35
34
|
description: "Mixin Messenger channel via Blaze WebSocket",
|
|
36
|
-
configSchema:
|
|
35
|
+
configSchema: {
|
|
36
|
+
type: "object",
|
|
37
|
+
additionalProperties: false,
|
|
38
|
+
properties: {},
|
|
39
|
+
},
|
|
37
40
|
register(api: OpenClawPluginApi): void {
|
|
38
41
|
setMixinRuntime(api.runtime);
|
|
39
42
|
api.registerChannel({ plugin: mixinPlugin });
|
package/openclaw.plugin.json
CHANGED
package/package.json
CHANGED
package/src/channel.ts
CHANGED
|
@@ -3,8 +3,6 @@ import { promisify } from "node:util";
|
|
|
3
3
|
import { uniqueConversationID } from "@mixin.dev/mixin-node-sdk";
|
|
4
4
|
import {
|
|
5
5
|
buildChannelConfigSchema,
|
|
6
|
-
formatPairingApproveHint,
|
|
7
|
-
resolveChannelMediaMaxBytes,
|
|
8
6
|
} from "openclaw/plugin-sdk";
|
|
9
7
|
import type { ChannelGatewayContext, OpenClawConfig, ReplyPayload } from "openclaw/plugin-sdk";
|
|
10
8
|
import { runBlazeLoop } from "./blaze-service.js";
|
|
@@ -23,9 +21,10 @@ import { buildMixinAccountSnapshot, buildMixinChannelSummary, resolveMixinStatus
|
|
|
23
21
|
type ResolvedMixinAccount = ReturnType<typeof resolveAccount>;
|
|
24
22
|
|
|
25
23
|
const BASE_DELAY = 1000;
|
|
26
|
-
const MAX_DELAY = 3000;
|
|
27
|
-
const MULTIPLIER = 1.5;
|
|
28
|
-
const MEDIA_MAX_BYTES = 30 * 1024 * 1024;
|
|
24
|
+
const MAX_DELAY = 3000;
|
|
25
|
+
const MULTIPLIER = 1.5;
|
|
26
|
+
const MEDIA_MAX_BYTES = 30 * 1024 * 1024;
|
|
27
|
+
const MB = 1024 * 1024;
|
|
29
28
|
const execFileAsync = promisify(execFile);
|
|
30
29
|
const CONVERSATION_CATEGORY_CACHE_TTL_MS = 5 * 60 * 1000;
|
|
31
30
|
|
|
@@ -44,6 +43,10 @@ function createDefaultMixinRuntimeState(accountId: string): {
|
|
|
44
43
|
lastError: null,
|
|
45
44
|
};
|
|
46
45
|
}
|
|
46
|
+
|
|
47
|
+
function formatMixinPairingApproveHint(channelId: string): string {
|
|
48
|
+
return `Approve via: \`openclaw pairing list ${channelId}\` / \`openclaw pairing approve ${channelId} <code>\``;
|
|
49
|
+
}
|
|
47
50
|
|
|
48
51
|
const conversationCategoryCache = new Map<string, {
|
|
49
52
|
category: "CONTACT" | "GROUP";
|
|
@@ -172,13 +175,16 @@ async function resolveAudioDurationSeconds(filePath: string): Promise<number | n
|
|
|
172
175
|
}
|
|
173
176
|
}
|
|
174
177
|
|
|
175
|
-
function resolveMixinMediaMaxBytes(cfg: OpenClawConfig, accountId?: string | null): number {
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
178
|
+
function resolveMixinMediaMaxBytes(cfg: OpenClawConfig, accountId?: string | null): number {
|
|
179
|
+
const channelLimitMb = resolveMediaMaxMb(cfg, accountId ?? undefined);
|
|
180
|
+
if (channelLimitMb) {
|
|
181
|
+
return channelLimitMb * MB;
|
|
182
|
+
}
|
|
183
|
+
if (cfg.agents?.defaults?.mediaMaxMb) {
|
|
184
|
+
return cfg.agents.defaults.mediaMaxMb * MB;
|
|
185
|
+
}
|
|
186
|
+
return MEDIA_MAX_BYTES;
|
|
187
|
+
}
|
|
182
188
|
|
|
183
189
|
async function deliverOutboundMixinPayload(params: {
|
|
184
190
|
cfg: OpenClawConfig;
|
|
@@ -344,12 +350,12 @@ export const mixinPlugin = {
|
|
|
344
350
|
policy,
|
|
345
351
|
allowFrom,
|
|
346
352
|
policyPath: `channels.mixin${basePath}.dmPolicy`,
|
|
347
|
-
allowFromPath: `channels.mixin${basePath}.allowFrom`,
|
|
348
|
-
approveHint: policy === "pairing"
|
|
349
|
-
?
|
|
350
|
-
: allowFrom.length > 0
|
|
351
|
-
? `宸查厤缃櫧鍚嶅崟鐢ㄦ埛鏁?${allowFrom.length}锛屽皢鐢ㄦ埛鐨?Mixin UUID 娣诲姞鍒?allowFrom 鍒楄〃鍗冲彲鎺堟潈`
|
|
352
|
-
: "灏嗙敤鎴风殑 Mixin UUID 娣诲姞鍒?allowFrom 鍒楄〃鍗冲彲鎺堟潈",
|
|
353
|
+
allowFromPath: `channels.mixin${basePath}.allowFrom`,
|
|
354
|
+
approveHint: policy === "pairing"
|
|
355
|
+
? formatMixinPairingApproveHint("mixin")
|
|
356
|
+
: allowFrom.length > 0
|
|
357
|
+
? `宸查厤缃櫧鍚嶅崟鐢ㄦ埛鏁?${allowFrom.length}锛屽皢鐢ㄦ埛鐨?Mixin UUID 娣诲姞鍒?allowFrom 鍒楄〃鍗冲彲鎺堟潈`
|
|
358
|
+
: "灏嗙敤鎴风殑 Mixin UUID 娣诲姞鍒?allowFrom 鍒楄〃鍗冲彲鎺堟潈",
|
|
353
359
|
};
|
|
354
360
|
},
|
|
355
361
|
},
|
package/src/inbound-handler.ts
CHANGED
|
@@ -1,10 +1,9 @@
|
|
|
1
|
-
import fs from "node:fs/promises";
|
|
2
|
-
import os from "node:os";
|
|
3
|
-
import path from "node:path";
|
|
4
|
-
import { createRequire } from "node:module";
|
|
5
|
-
import { pathToFileURL } from "node:url";
|
|
6
|
-
import {
|
|
7
|
-
import type { AgentMediaPayload, OpenClawConfig } from "openclaw/plugin-sdk";
|
|
1
|
+
import fs from "node:fs/promises";
|
|
2
|
+
import os from "node:os";
|
|
3
|
+
import path from "node:path";
|
|
4
|
+
import { createRequire } from "node:module";
|
|
5
|
+
import { pathToFileURL } from "node:url";
|
|
6
|
+
import type { AgentMediaPayload, OpenClawConfig } from "openclaw/plugin-sdk";
|
|
8
7
|
import { getAccountConfig, resolveConversationPolicy } from "./config.js";
|
|
9
8
|
import type { MixinAccountConfig } from "./config-schema.js";
|
|
10
9
|
import { decryptMixinMessage } from "./crypto.js";
|
|
@@ -69,6 +68,13 @@ type CachedBotIdentity = {
|
|
|
69
68
|
expiresAt: number;
|
|
70
69
|
};
|
|
71
70
|
|
|
71
|
+
type GroupAccessDecision = {
|
|
72
|
+
allowed: boolean;
|
|
73
|
+
groupPolicy: "open" | "allowlist" | "disabled";
|
|
74
|
+
providerMissingFallbackApplied: boolean;
|
|
75
|
+
reason: "allowed" | "disabled" | "empty_allowlist" | "sender_not_allowlisted";
|
|
76
|
+
};
|
|
77
|
+
|
|
72
78
|
type MixinAttachmentRequest = {
|
|
73
79
|
attachmentId: string;
|
|
74
80
|
mimeType?: string;
|
|
@@ -84,7 +90,76 @@ const cachedBotIdentities = new Map<string, CachedBotIdentity>();
|
|
|
84
90
|
let cachedUpdateSessionStore:
|
|
85
91
|
| ((storePath: string, mutator: (store: Record<string, Record<string, unknown>>) => void | Promise<void>) => Promise<unknown>)
|
|
86
92
|
| null
|
|
87
|
-
| undefined;
|
|
93
|
+
| undefined;
|
|
94
|
+
|
|
95
|
+
function buildMixinAgentMediaPayload(mediaList: Array<{ path: string; contentType?: string }>): AgentMediaPayload {
|
|
96
|
+
const first = mediaList[0];
|
|
97
|
+
const mediaPaths = mediaList.map((media) => media.path);
|
|
98
|
+
const mediaTypes = mediaList.map((media) => media.contentType).filter((value): value is string => Boolean(value));
|
|
99
|
+
return {
|
|
100
|
+
MediaPath: first?.path,
|
|
101
|
+
MediaType: first?.contentType ?? undefined,
|
|
102
|
+
MediaUrl: first?.path,
|
|
103
|
+
MediaPaths: mediaPaths.length > 0 ? mediaPaths : undefined,
|
|
104
|
+
MediaUrls: mediaPaths.length > 0 ? mediaPaths : undefined,
|
|
105
|
+
MediaTypes: mediaTypes.length > 0 ? mediaTypes : undefined,
|
|
106
|
+
};
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
function resolveMixinDefaultGroupPolicy(cfg: OpenClawConfig): "open" | "allowlist" | "disabled" | undefined {
|
|
110
|
+
const groupPolicy = cfg.channels?.defaults?.groupPolicy;
|
|
111
|
+
return groupPolicy === "open" || groupPolicy === "allowlist" || groupPolicy === "disabled"
|
|
112
|
+
? groupPolicy
|
|
113
|
+
: undefined;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
function evaluateMixinSenderGroupAccess(params: {
|
|
117
|
+
providerConfigPresent: boolean;
|
|
118
|
+
configuredGroupPolicy?: "open" | "allowlist" | "disabled";
|
|
119
|
+
defaultGroupPolicy?: "open" | "allowlist" | "disabled";
|
|
120
|
+
groupAllowFrom: string[];
|
|
121
|
+
senderId: string;
|
|
122
|
+
isSenderAllowed: (senderId: string, allowFrom: string[]) => boolean;
|
|
123
|
+
}): GroupAccessDecision {
|
|
124
|
+
const configuredPolicy = params.configuredGroupPolicy ?? params.defaultGroupPolicy;
|
|
125
|
+
const providerMissingFallbackApplied = !params.providerConfigPresent && !configuredPolicy;
|
|
126
|
+
const groupPolicy = configuredPolicy ?? (params.providerConfigPresent ? "open" : "allowlist");
|
|
127
|
+
|
|
128
|
+
if (groupPolicy === "disabled") {
|
|
129
|
+
return {
|
|
130
|
+
allowed: false,
|
|
131
|
+
groupPolicy,
|
|
132
|
+
providerMissingFallbackApplied,
|
|
133
|
+
reason: "disabled",
|
|
134
|
+
};
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
if (groupPolicy === "allowlist") {
|
|
138
|
+
if (params.groupAllowFrom.length === 0) {
|
|
139
|
+
return {
|
|
140
|
+
allowed: false,
|
|
141
|
+
groupPolicy,
|
|
142
|
+
providerMissingFallbackApplied,
|
|
143
|
+
reason: "empty_allowlist",
|
|
144
|
+
};
|
|
145
|
+
}
|
|
146
|
+
if (!params.isSenderAllowed(params.senderId, params.groupAllowFrom)) {
|
|
147
|
+
return {
|
|
148
|
+
allowed: false,
|
|
149
|
+
groupPolicy,
|
|
150
|
+
providerMissingFallbackApplied,
|
|
151
|
+
reason: "sender_not_allowlisted",
|
|
152
|
+
};
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
return {
|
|
157
|
+
allowed: true,
|
|
158
|
+
groupPolicy,
|
|
159
|
+
providerMissingFallbackApplied,
|
|
160
|
+
reason: "allowed",
|
|
161
|
+
};
|
|
162
|
+
}
|
|
88
163
|
|
|
89
164
|
function isProcessed(messageId: string): boolean {
|
|
90
165
|
return processedMessages.has(messageId);
|
|
@@ -577,11 +652,11 @@ async function resolveInboundAttachment(params: {
|
|
|
577
652
|
);
|
|
578
653
|
|
|
579
654
|
return {
|
|
580
|
-
text: formatInboundAttachmentText(params.msg.category, payload),
|
|
581
|
-
mediaPayload:
|
|
582
|
-
{
|
|
583
|
-
path: saved.path,
|
|
584
|
-
contentType: saved.contentType ?? payload.mimeType ?? fetched.contentType,
|
|
655
|
+
text: formatInboundAttachmentText(params.msg.category, payload),
|
|
656
|
+
mediaPayload: buildMixinAgentMediaPayload([
|
|
657
|
+
{
|
|
658
|
+
path: saved.path,
|
|
659
|
+
contentType: saved.contentType ?? payload.mimeType ?? fetched.contentType,
|
|
585
660
|
},
|
|
586
661
|
]),
|
|
587
662
|
};
|
|
@@ -887,14 +962,14 @@ function evaluateMixinGroupAccess(params: {
|
|
|
887
962
|
};
|
|
888
963
|
}
|
|
889
964
|
|
|
890
|
-
const normalizedGroupAllowFrom = normalizeAllowEntries(conversationPolicy.groupAllowFrom);
|
|
891
|
-
const decision =
|
|
892
|
-
providerConfigPresent: true,
|
|
893
|
-
configuredGroupPolicy: conversationPolicy.groupPolicy,
|
|
894
|
-
defaultGroupPolicy:
|
|
895
|
-
groupAllowFrom: normalizedGroupAllowFrom,
|
|
896
|
-
senderId: normalizeAllowEntry(params.senderId),
|
|
897
|
-
isSenderAllowed: (senderId, allowFrom) => allowFrom.includes(normalizeAllowEntry(senderId)),
|
|
965
|
+
const normalizedGroupAllowFrom = normalizeAllowEntries(conversationPolicy.groupAllowFrom);
|
|
966
|
+
const decision = evaluateMixinSenderGroupAccess({
|
|
967
|
+
providerConfigPresent: true,
|
|
968
|
+
configuredGroupPolicy: conversationPolicy.groupPolicy,
|
|
969
|
+
defaultGroupPolicy: resolveMixinDefaultGroupPolicy(params.cfg),
|
|
970
|
+
groupAllowFrom: normalizedGroupAllowFrom,
|
|
971
|
+
senderId: normalizeAllowEntry(params.senderId),
|
|
972
|
+
isSenderAllowed: (senderId, allowFrom) => allowFrom.includes(normalizeAllowEntry(senderId)),
|
|
898
973
|
});
|
|
899
974
|
|
|
900
975
|
return {
|
package/src/status.ts
CHANGED
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import { buildBaseAccountStatusSnapshot, buildBaseChannelStatusSummary } from "openclaw/plugin-sdk";
|
|
2
1
|
import type { OpenClawConfig } from "openclaw/plugin-sdk";
|
|
3
2
|
import { getAccountConfig, resolveDefaultAccountId } from "./config.js";
|
|
4
3
|
import { getOutboxPathsSnapshot, type OutboxStatus } from "./send-service.js";
|
|
@@ -44,6 +43,71 @@ type MixinStatusAccount = {
|
|
|
44
43
|
};
|
|
45
44
|
};
|
|
46
45
|
|
|
46
|
+
function buildBaseChannelStatusSummary(snapshot: MixinChannelStatusSnapshot): {
|
|
47
|
+
configured: boolean;
|
|
48
|
+
running: boolean;
|
|
49
|
+
lastStartAt: number | null;
|
|
50
|
+
lastStopAt: number | null;
|
|
51
|
+
lastError: string | null;
|
|
52
|
+
} {
|
|
53
|
+
return {
|
|
54
|
+
configured: snapshot.configured ?? false,
|
|
55
|
+
running: snapshot.running ?? false,
|
|
56
|
+
lastStartAt: snapshot.lastStartAt ?? null,
|
|
57
|
+
lastStopAt: snapshot.lastStopAt ?? null,
|
|
58
|
+
lastError: snapshot.lastError ?? null,
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
function buildRuntimeAccountStatusSnapshot(params: {
|
|
63
|
+
runtime?: RuntimeLifecycleSnapshot | null;
|
|
64
|
+
probe?: unknown;
|
|
65
|
+
}): {
|
|
66
|
+
running: boolean;
|
|
67
|
+
lastStartAt: number | null;
|
|
68
|
+
lastStopAt: number | null;
|
|
69
|
+
lastError: string | null;
|
|
70
|
+
probe?: unknown;
|
|
71
|
+
} {
|
|
72
|
+
const { runtime, probe } = params;
|
|
73
|
+
return {
|
|
74
|
+
running: runtime?.running ?? false,
|
|
75
|
+
lastStartAt: runtime?.lastStartAt ?? null,
|
|
76
|
+
lastStopAt: runtime?.lastStopAt ?? null,
|
|
77
|
+
lastError: runtime?.lastError ?? null,
|
|
78
|
+
probe,
|
|
79
|
+
};
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
function buildBaseAccountStatusSnapshot(params: {
|
|
83
|
+
account: MixinStatusAccount;
|
|
84
|
+
runtime?: RuntimeLifecycleSnapshot | null;
|
|
85
|
+
probe?: unknown;
|
|
86
|
+
}): {
|
|
87
|
+
accountId: string;
|
|
88
|
+
name?: string;
|
|
89
|
+
enabled?: boolean;
|
|
90
|
+
configured?: boolean;
|
|
91
|
+
running: boolean;
|
|
92
|
+
lastStartAt: number | null;
|
|
93
|
+
lastStopAt: number | null;
|
|
94
|
+
lastError: string | null;
|
|
95
|
+
probe?: unknown;
|
|
96
|
+
lastInboundAt: number | null;
|
|
97
|
+
lastOutboundAt: number | null;
|
|
98
|
+
} {
|
|
99
|
+
const { account, runtime, probe } = params;
|
|
100
|
+
return {
|
|
101
|
+
accountId: account.accountId,
|
|
102
|
+
name: account.name,
|
|
103
|
+
enabled: account.enabled,
|
|
104
|
+
configured: account.configured,
|
|
105
|
+
...buildRuntimeAccountStatusSnapshot({ runtime, probe }),
|
|
106
|
+
lastInboundAt: runtime?.lastInboundAt ?? null,
|
|
107
|
+
lastOutboundAt: runtime?.lastOutboundAt ?? null,
|
|
108
|
+
};
|
|
109
|
+
}
|
|
110
|
+
|
|
47
111
|
export function resolveMixinStatusSnapshot(
|
|
48
112
|
cfg: OpenClawConfig,
|
|
49
113
|
accountId?: string,
|