@insta-dev01/intclaw 1.0.10 → 1.1.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.
Files changed (49) hide show
  1. package/LICENSE +1 -1
  2. package/README.en.md +424 -0
  3. package/README.md +365 -164
  4. package/index.ts +28 -0
  5. package/openclaw.plugin.json +12 -41
  6. package/package.json +69 -40
  7. package/src/channel.ts +557 -0
  8. package/src/config/accounts.ts +230 -0
  9. package/src/config/schema.ts +144 -0
  10. package/src/core/connection.ts +733 -0
  11. package/src/core/message-handler.ts +1268 -0
  12. package/src/core/provider.ts +106 -0
  13. package/src/core/state.ts +54 -0
  14. package/src/directory.ts +95 -0
  15. package/src/gateway-methods.ts +237 -0
  16. package/src/onboarding.ts +387 -0
  17. package/src/policy.ts +19 -0
  18. package/src/probe.ts +213 -0
  19. package/src/reply-dispatcher.ts +674 -0
  20. package/src/runtime.ts +7 -0
  21. package/src/sdk/helpers.ts +317 -0
  22. package/src/sdk/types.ts +515 -0
  23. package/src/secret-input.ts +19 -0
  24. package/src/services/media/audio.ts +54 -0
  25. package/src/services/media/chunk-upload.ts +293 -0
  26. package/src/services/media/common.ts +154 -0
  27. package/src/services/media/file.ts +70 -0
  28. package/src/services/media/image.ts +67 -0
  29. package/src/services/media/index.ts +10 -0
  30. package/src/services/media/video.ts +162 -0
  31. package/src/services/media.ts +1134 -0
  32. package/src/services/messaging/index.ts +16 -0
  33. package/src/services/messaging/send.ts +137 -0
  34. package/src/services/messaging.ts +800 -0
  35. package/src/targets.ts +45 -0
  36. package/src/types/index.ts +52 -0
  37. package/src/utils/agent.ts +63 -0
  38. package/src/utils/async.ts +51 -0
  39. package/src/utils/constants.ts +9 -0
  40. package/src/utils/http-client.ts +84 -0
  41. package/src/utils/index.ts +8 -0
  42. package/src/utils/logger.ts +78 -0
  43. package/src/utils/session.ts +118 -0
  44. package/src/utils/token.ts +94 -0
  45. package/src/utils/utils-legacy.ts +506 -0
  46. package/.env.example +0 -11
  47. package/skills/intclaw_matrix/SKILL.md +0 -20
  48. package/src/channel/intclaw_channel.js +0 -155
  49. package/src/index.js +0 -23
@@ -0,0 +1,106 @@
1
+ /**
2
+ * IntClaw消息流 Provider 入口
3
+ *
4
+ * 职责:
5
+ * - 提供 monitorIntclawProvider 函数作为IntClaw消息流的统一入口
6
+ * - 协调单账号和多账号监控场景
7
+ * - 并行导入连接层和消息处理模块,避免循环依赖
8
+ *
9
+ * 主要功能:
10
+ * - 根据 accountId 参数决定启动单账号或所有账号
11
+ * - 验证账号配置状态
12
+ * - 并行启动多个账号的消息流连接
13
+ */
14
+ import type { ClawdbotConfig, RuntimeEnv } from "openclaw/plugin-sdk";
15
+ import * as monitorState from "./state";
16
+
17
+ // 只解构 monitorState 的导出
18
+ const {
19
+ clearIntclawWebhookRateLimitStateForTest,
20
+ getIntclawWebhookRateLimitStateSizeForTest,
21
+ isWebhookRateLimitedForTest,
22
+ stopIntclawMonitorState,
23
+ } = monitorState;
24
+
25
+ export type MonitorIntclawOpts = {
26
+ config?: ClawdbotConfig;
27
+ runtime?: RuntimeEnv;
28
+ abortSignal?: AbortSignal;
29
+ accountId?: string;
30
+ };
31
+
32
+ export {
33
+ clearIntclawWebhookRateLimitStateForTest,
34
+ getIntclawWebhookRateLimitStateSizeForTest,
35
+ isWebhookRateLimitedForTest,
36
+ } from "./state";
37
+
38
+ // 只导出类型,不 re-export 函数(避免循环依赖)
39
+ export type { IntclawReactionCreatedEvent } from "./connection";
40
+
41
+ export async function monitorIntclawProvider(opts: MonitorIntclawOpts = {}): Promise<void> {
42
+ const cfg = opts.config;
43
+ if (!cfg) {
44
+ throw new Error("Config is required for IntClaw monitor");
45
+ }
46
+
47
+ const log = opts.runtime?.log;
48
+
49
+ // 并行导入所有模块(无循环依赖,可以并行)
50
+ const [accountsModule, monitorAccountModule, monitorSingleModule] = await Promise.all([
51
+ import("../config/accounts"),
52
+ import("./message-handler"),
53
+ import("./connection"),
54
+ ]);
55
+
56
+ const { resolveIntclawAccount, listEnabledIntclawAccounts } = accountsModule;
57
+ const { handleIntClawMessage } = monitorAccountModule;
58
+ const { monitorSingleAccount, resolveReactionSyntheticEvent } = monitorSingleModule;
59
+
60
+ if (opts.accountId) {
61
+ const account = resolveIntclawAccount({ cfg, accountId: opts.accountId });
62
+ if (!account.enabled || !account.configured) {
63
+ throw new Error(`IntClaw account "${opts.accountId}" not configured or disabled`);
64
+ }
65
+ return monitorSingleAccount({
66
+ cfg,
67
+ account,
68
+ runtime: opts.runtime,
69
+ abortSignal: opts.abortSignal,
70
+ messageHandler: handleIntClawMessage,
71
+ });
72
+ }
73
+
74
+ const accounts = listEnabledIntclawAccounts(cfg);
75
+ if (accounts.length === 0) {
76
+ throw new Error("No enabled IntClaw accounts configured");
77
+ }
78
+
79
+ log?.info?.(
80
+ `intclaw-connector: starting ${accounts.length} account(s): ${accounts.map((a) => a.accountId).join(", ")}`,
81
+ );
82
+
83
+ const monitorPromises: Promise<void>[] = [];
84
+ for (const account of accounts) {
85
+ if (opts.abortSignal?.aborted) {
86
+ log?.info?.("intclaw-connector: abort signal received during startup preflight; stopping startup");
87
+ break;
88
+ }
89
+
90
+ monitorPromises.push(
91
+ monitorSingleAccount({
92
+ cfg,
93
+ account,
94
+ runtime: opts.runtime,
95
+ abortSignal: opts.abortSignal,
96
+ messageHandler: handleIntClawMessage,
97
+ }),
98
+ );
99
+ }
100
+
101
+ await Promise.all(monitorPromises);
102
+ }
103
+
104
+ export function stopIntclawMonitor(accountId?: string): void {
105
+ stopIntclawMonitorState(accountId);
106
+ }
@@ -0,0 +1,54 @@
1
+ /**
2
+ * IntClaw消息流状态管理
3
+ *
4
+ * 职责:
5
+ * - 管理每个IntClaw账号的运行状态
6
+ * - 存储 AbortController 用于优雅停止消息流
7
+ * - 提供测试工具函数
8
+ *
9
+ * 核心功能:
10
+ * - setIntclawMonitorState: 设置账号运行状态
11
+ * - getIntclawMonitorState: 获取账号运行状态
12
+ * - stopIntclawMonitorState: 停止单个或多个账号的消息流
13
+ * - 测试工具:clearIntclawWebhookRateLimitStateForTest 等
14
+ */
15
+ const monitorState = new Map<string, { running: boolean; abortController?: AbortController }>();
16
+
17
+ export function setIntclawMonitorState(accountId: string, state: { running: boolean; abortController?: AbortController }): void {
18
+ monitorState.set(accountId, state);
19
+ }
20
+
21
+ export function getIntclawMonitorState(accountId: string): { running: boolean; abortController?: AbortController } | undefined {
22
+ return monitorState.get(accountId);
23
+ }
24
+
25
+ export function stopIntclawMonitorState(accountId?: string): void {
26
+ if (accountId) {
27
+ const state = monitorState.get(accountId);
28
+ if (state?.abortController) {
29
+ state.abortController.abort();
30
+ }
31
+ monitorState.delete(accountId);
32
+ } else {
33
+ // Stop all monitors
34
+ for (const [id, state] of monitorState.entries()) {
35
+ if (state.abortController) {
36
+ state.abortController.abort();
37
+ }
38
+ }
39
+ monitorState.clear();
40
+ }
41
+ }
42
+
43
+ // Test utilities
44
+ export function clearIntclawWebhookRateLimitStateForTest(): void {
45
+ // IntClaw doesn't use webhook rate limiting
46
+ }
47
+
48
+ export function getIntclawWebhookRateLimitStateSizeForTest(): number {
49
+ return 0;
50
+ }
51
+
52
+ export function isWebhookRateLimitedForTest(): boolean {
53
+ return false;
54
+ }
@@ -0,0 +1,95 @@
1
+ import type { ClawdbotConfig } from "openclaw/plugin-sdk";
2
+ import { resolveIntclawAccount } from "./config/accounts.ts";
3
+ import { normalizeIntclawTarget } from "./targets.ts";
4
+
5
+ export type IntclawDirectoryPeer = {
6
+ kind: "user";
7
+ id: string;
8
+ name?: string;
9
+ };
10
+
11
+ export type IntclawDirectoryGroup = {
12
+ kind: "group";
13
+ id: string;
14
+ name?: string;
15
+ };
16
+
17
+ export async function listIntclawDirectoryPeers(params: {
18
+ cfg: ClawdbotConfig;
19
+ query?: string;
20
+ limit?: number;
21
+ accountId?: string;
22
+ }): Promise<IntclawDirectoryPeer[]> {
23
+ const account = resolveIntclawAccount({ cfg: params.cfg, accountId: params.accountId });
24
+ const intclawCfg = account.config;
25
+ const q = params.query?.trim().toLowerCase() || "";
26
+ const ids = new Set<string>();
27
+
28
+ for (const entry of intclawCfg?.allowFrom ?? []) {
29
+ const trimmed = String(entry).trim();
30
+ if (trimmed && trimmed !== "*") {
31
+ ids.add(trimmed);
32
+ }
33
+ }
34
+
35
+ return Array.from(ids)
36
+ .map((raw) => raw.trim())
37
+ .filter(Boolean)
38
+ .map((raw) => normalizeIntclawTarget(raw) ?? raw)
39
+ .filter((id) => (q ? id.toLowerCase().includes(q) : true))
40
+ .slice(0, params.limit && params.limit > 0 ? params.limit : undefined)
41
+ .map((id) => ({ kind: "user" as const, id }));
42
+ }
43
+
44
+ export async function listIntclawDirectoryGroups(params: {
45
+ cfg: ClawdbotConfig;
46
+ query?: string;
47
+ limit?: number;
48
+ accountId?: string;
49
+ }): Promise<IntclawDirectoryGroup[]> {
50
+ const account = resolveIntclawAccount({ cfg: params.cfg, accountId: params.accountId });
51
+ const intclawCfg = account.config;
52
+ const q = params.query?.trim().toLowerCase() || "";
53
+ const ids = new Set<string>();
54
+
55
+ for (const groupId of Object.keys(intclawCfg?.groups ?? {})) {
56
+ const trimmed = groupId.trim();
57
+ if (trimmed && trimmed !== "*") {
58
+ ids.add(trimmed);
59
+ }
60
+ }
61
+
62
+ for (const entry of intclawCfg?.groupAllowFrom ?? []) {
63
+ const trimmed = String(entry).trim();
64
+ if (trimmed && trimmed !== "*") {
65
+ ids.add(trimmed);
66
+ }
67
+ }
68
+
69
+ return Array.from(ids)
70
+ .map((raw) => raw.trim())
71
+ .filter(Boolean)
72
+ .filter((id) => (q ? id.toLowerCase().includes(q) : true))
73
+ .slice(0, params.limit && params.limit > 0 ? params.limit : undefined)
74
+ .map((id) => ({ kind: "group" as const, id }));
75
+ }
76
+
77
+ export async function listIntclawDirectoryPeersLive(params: {
78
+ cfg: ClawdbotConfig;
79
+ query?: string;
80
+ limit?: number;
81
+ accountId?: string;
82
+ }): Promise<IntclawDirectoryPeer[]> {
83
+ // IntClaw doesn't have a public API to list users, so we fall back to static list
84
+ return listIntclawDirectoryPeers(params);
85
+ }
86
+
87
+ export async function listIntclawDirectoryGroupsLive(params: {
88
+ cfg: ClawdbotConfig;
89
+ query?: string;
90
+ limit?: number;
91
+ accountId?: string;
92
+ }): Promise<IntclawDirectoryGroup[]> {
93
+ // IntClaw doesn't have a public API to list groups, so we fall back to static list
94
+ return listIntclawDirectoryGroups(params);
95
+ }
@@ -0,0 +1,237 @@
1
+ /**
2
+ * Gateway Methods 注册
3
+ *
4
+ * 提供IntClaw插件的 RPC 接口,允许外部系统、AI Agent 和其他插件调用IntClaw功能
5
+ */
6
+
7
+ import type { OpenClawPluginApi } from "openclaw/plugin-sdk";
8
+ import { resolveIntclawAccount } from "./config/accounts.ts";
9
+
10
+ import { sendProactive } from "./services/messaging.ts";
11
+ import { getUnionId } from "./utils/utils-legacy.ts";
12
+
13
+ /**
14
+ * 注册所有 Gateway Methods
15
+ */
16
+ export function registerGatewayMethods(api: OpenClawPluginApi) {
17
+ const log = api.logger;
18
+
19
+ // ============ 消息发送类 ============
20
+
21
+ /**
22
+ * 主动发送单聊消息
23
+ *
24
+ * @example
25
+ * ```typescript
26
+ * await gateway.call('intclaw-connector.sendToUser', {
27
+ * userId: 'user123',
28
+ * content: '任务已完成!',
29
+ * * });
30
+ * ```
31
+ */
32
+ api.registerGatewayMethod('intclaw-connector.sendToUser', async ({ context, params, respond }) => {
33
+ const cfg = context.deps.getConfig();
34
+ try {
35
+ const { userId, userIds, content, msgType, title, accountId } = params || {};
36
+ const account = resolveIntclawAccount({ cfg, accountId });
37
+
38
+ if (!account.config?.clientId) {
39
+ return respond(false, { error: 'IntClaw not configured' });
40
+ }
41
+
42
+ const targetUserIds = userIds || (userId ? [userId] : []);
43
+ if (targetUserIds.length === 0) {
44
+ return respond(false, { error: 'userId or userIds is required' });
45
+ }
46
+
47
+ if (!content) {
48
+ return respond(false, { error: 'content is required' });
49
+ }
50
+
51
+ // 构建目标
52
+ const target = targetUserIds.length === 1
53
+ ? { userId: targetUserIds[0] }
54
+ : { userIds: targetUserIds };
55
+
56
+ const result = await sendProactive(account.config, target, content, {
57
+ msgType,
58
+ title,
59
+ log,
60
+ });
61
+
62
+ respond(result.ok, result);
63
+ } catch (err: any) {
64
+ log?.error?.(`[Gateway][sendToUser] 错误: ${err.message}`);
65
+ respond(false, { error: err.message });
66
+ }
67
+ });
68
+
69
+ /**
70
+ * 主动发送群聊消息
71
+ *
72
+ * @example
73
+ * ```typescript
74
+ * await gateway.call('intclaw-connector.sendToGroup', {
75
+ * openConversationId: 'cid123',
76
+ * content: '构建失败,请检查日志',
77
+ * * });
78
+ * ```
79
+ */
80
+ api.registerGatewayMethod('intclaw-connector.sendToGroup', async ({ context, params, respond }) => {
81
+ const cfg = context.deps.getConfig();
82
+ try {
83
+ const { openConversationId, content, msgType, title, accountId } = params || {};
84
+ const account = resolveIntclawAccount({ cfg, accountId });
85
+
86
+ if (!account.config?.clientId) {
87
+ return respond(false, { error: 'IntClaw not configured' });
88
+ }
89
+
90
+ if (!openConversationId) {
91
+ return respond(false, { error: 'openConversationId is required' });
92
+ }
93
+
94
+ if (!content) {
95
+ return respond(false, { error: 'content is required' });
96
+ }
97
+
98
+ const result = await sendProactive(account.config, { openConversationId }, content, {
99
+ msgType,
100
+ title,
101
+ log,
102
+ });
103
+
104
+ respond(result.ok, result);
105
+ } catch (err: any) {
106
+ log?.error?.(`[Gateway][sendToGroup] 错误: ${err.message}`);
107
+ respond(false, { error: err.message });
108
+ }
109
+ });
110
+
111
+ /**
112
+ * 智能发送消息(自动识别目标类型)
113
+ *
114
+ * @example
115
+ * ```typescript
116
+ * // 发送给用户
117
+ * await gateway.call('intclaw-connector.send', {
118
+ * target: 'user:user123',
119
+ * content: '你好!'
120
+ * });
121
+ *
122
+ * // 发送到群
123
+ * await gateway.call('intclaw-connector.send', {
124
+ * target: 'group:cid123',
125
+ * content: '大家好!'
126
+ * });
127
+ * ```
128
+ */
129
+ api.registerGatewayMethod('intclaw-connector.send', async ({ context, params, respond }) => {
130
+ const cfg = context.deps.getConfig();
131
+ try {
132
+ const { target, content, message, msgType, title, accountId } = params || {};
133
+ const actualContent = content || message;
134
+ const account = resolveIntclawAccount({ cfg, accountId });
135
+
136
+ log?.info?.(`[Gateway][send] 收到请求: target=${target}, contentLen=${actualContent?.length}`);
137
+
138
+ if (!account.config?.clientId) {
139
+ return respond(false, { error: 'IntClaw not configured' });
140
+ }
141
+
142
+ if (!target) {
143
+ return respond(false, { error: 'target is required (format: user:<userId> or group:<openConversationId>)' });
144
+ }
145
+
146
+ if (!actualContent) {
147
+ return respond(false, { error: 'content is required' });
148
+ }
149
+
150
+ const targetStr = String(target);
151
+ let sendTarget: { userId?: string; openConversationId?: string };
152
+
153
+ if (targetStr.startsWith('user:')) {
154
+ sendTarget = { userId: targetStr.slice(5) };
155
+ } else if (targetStr.startsWith('group:')) {
156
+ sendTarget = { openConversationId: targetStr.slice(6) };
157
+ } else {
158
+ // 默认当作 userId
159
+ sendTarget = { userId: targetStr };
160
+ }
161
+
162
+ const result = await sendProactive(account.config, sendTarget, actualContent, {
163
+ msgType,
164
+ title,
165
+ log,
166
+ });
167
+
168
+ respond(result.ok, result);
169
+ } catch (err: any) {
170
+ log?.error?.(`[Gateway][send] 错误: ${err.message}`);
171
+ respond(false, { error: err.message });
172
+ }
173
+ });
174
+
175
+
176
+
177
+ // ============ 状态检查类 ============
178
+
179
+ /**
180
+ * 检查插件状态
181
+ *
182
+ * @example
183
+ * ```typescript
184
+ * const result = await gateway.call('intclaw-connector.status');
185
+ * console.log('配置状态:', result);
186
+ * ```
187
+ */
188
+ api.registerGatewayMethod('intclaw-connector.status', async ({ context, respond }) => {
189
+ const cfg = context.deps.getConfig();
190
+ try {
191
+ const account = resolveIntclawAccount({ cfg });
192
+ const configured = Boolean(account.config?.clientId && account.config?.clientSecret);
193
+
194
+ respond(true, {
195
+ configured,
196
+ enabled: account.enabled,
197
+ accountId: account.accountId,
198
+ clientId: account.config?.clientId,
199
+ });
200
+ } catch (err: any) {
201
+ log?.error?.(`[Gateway][status] 错误: ${err.message}`);
202
+ respond(false, { error: err.message });
203
+ }
204
+ });
205
+
206
+ /**
207
+ * 探测IntClaw连接
208
+ *
209
+ * @example
210
+ * ```typescript
211
+ * const result = await gateway.call('intclaw-connector.probe');
212
+ * if (result.ok) {
213
+ * console.log('连接正常');
214
+ * }
215
+ * ```
216
+ */
217
+ api.registerGatewayMethod('intclaw-connector.probe', async ({ context, respond }) => {
218
+ const cfg = context.deps.getConfig();
219
+ try {
220
+ const account = resolveIntclawAccount({ cfg });
221
+
222
+ if (!account.config?.clientId || !account.config?.clientSecret) {
223
+ return respond(false, { error: 'Not configured' });
224
+ }
225
+
226
+ // 尝试获取 access token 来验证连接
227
+ const { getAccessToken } = await import('./utils/utils-legacy.ts');
228
+ await getAccessToken(account.config);
229
+
230
+ respond(true, { ok: true, details: { clientId: account.config.clientId } });
231
+ } catch (err: any) {
232
+ log?.error?.(`[Gateway][probe] 错误: ${err.message}`);
233
+ respond(false, { ok: false, error: err.message });
234
+ }
235
+ });
236
+
237
+ }