cclawd 1.0.7 → 1.0.8
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/dist/{active-listener-DYmI7imH.js → active-listener-Dkhmfuwx.js} +2 -2
- package/dist/{api-key-rotation-DLU4jvSu.js → api-key-rotation-BOfI3cG3.js} +1 -1
- package/dist/{audio-preflight-C9TMbRb4.js → audio-preflight-CtkZ5SAs.js} +15 -15
- package/dist/{audio-transcription-runner-Q5zG_hYd.js → audio-transcription-runner-CbPqoiHX.js} +10 -10
- package/dist/{audit-membership-runtime-DhxSwFnF.js → audit-membership-runtime-hXUuer4x.js} +6 -6
- package/dist/build-info.json +3 -3
- package/dist/bundled/boot-md/handler.js +35 -35
- package/dist/bundled/bootstrap-extra-files/handler.js +5 -5
- package/dist/bundled/command-logger/handler.js +2 -2
- package/dist/bundled/session-memory/handler.js +35 -35
- package/dist/{channel-activity-Bx08UTAg.js → channel-activity-DWAER4wd.js} +2 -2
- package/dist/{commands-registry-llLVCTH9.js → commands-registry-BUyiA7nE.js} +2 -2
- package/dist/compact.runtime-DpcZpcTl.js +39 -0
- package/dist/{deliver-DJf2ZBpe.js → deliver-aHOaRbkt.js} +19 -19
- package/dist/deliver-runtime-D4bCsr6d.js +19 -0
- package/dist/deps-send-discord.runtime-BziKU-pE.js +19 -0
- package/dist/deps-send-imessage.runtime-CFRnDTqp.js +18 -0
- package/dist/deps-send-signal.runtime-BuOtABJm.js +17 -0
- package/dist/deps-send-slack.runtime-BOLqvMxW.js +17 -0
- package/dist/deps-send-telegram.runtime-DeEoFLv5.js +20 -0
- package/dist/deps-send-whatsapp.runtime-CG1uXYLY.js +43 -0
- package/dist/{diagnostic-BCCMF3O_.js → diagnostic-BdcXX9iJ.js} +2 -2
- package/dist/{env-aYXLHjfZ.js → env-lw2hsIUY.js} +1 -1
- package/dist/{fetch-bvgIiupu.js → fetch-C0iyt-Iz.js} +3 -3
- package/dist/{fetch-DCTUdr1U.js → fetch-D9NUULbj.js} +5 -5
- package/dist/{fetch-guard-CqpEmMQ2.js → fetch-guard-B5ZMnGaN.js} +2 -2
- package/dist/{frontmatter-DjZuS525.js → frontmatter-C_obXuTp.js} +3 -3
- package/dist/{github-copilot-token-CQmATy5E.js → github-copilot-token-8N63GdbE.js} +7 -7
- package/dist/{image-Q8E1-lZn.js → image-4x07m4Jl.js} +4 -4
- package/dist/image-runtime-smkMrIol.js +12 -0
- package/dist/{ir-CzM3SxId.js → ir-CsgNUpOU.js} +6 -6
- package/dist/llm-slug-generator.js +35 -35
- package/dist/{logger-ChbX1G7s.js → logger-CbUVl62f.js} +7 -7
- package/dist/{login-B0mtU11X.js → login-p_O59TVQ.js} +4 -4
- package/dist/{login-qr-DY_i60f5.js → login-qr-BCJpDsAy.js} +10 -10
- package/dist/{manager-FAQPC0uO.js → manager-CwYv8O3T.js} +12 -12
- package/dist/manager-runtime-D_jEoBr9.js +15 -0
- package/dist/{model-selection-wf3OY5DX.js → model-selection-Cv2Puf5z.js} +122 -122
- package/dist/{outbound-Bw0dOVS7.js → outbound-Chpiwybe.js} +6 -6
- package/dist/{outbound-attachment-1R6r9Pg_.js → outbound-attachment-BnAVJDLe.js} +2 -2
- package/dist/{paths-C0HLtPu0.js → paths-CehYKFsO.js} +7 -7
- package/dist/{paths-hfkBoC7i.js → paths-DkxwiA8g.js} +5 -5
- package/dist/{pi-embedded-BAHaY-Oh.js → pi-embedded-CJVNBk0y.js} +150 -150
- package/dist/pi-model-discovery-7IzK0Uc3.js +131 -0
- package/dist/pi-model-discovery-runtime-DABef3qy.js +12 -0
- package/dist/{pi-tools.before-tool-call.runtime-D_mthvtC.js → pi-tools.before-tool-call.runtime-BP2UvGJb.js} +10 -10
- package/dist/plugin-sdk/active-listener-CN-tMEvN.js +35 -0
- package/dist/plugin-sdk/api-key-rotation-CimGYMBc.js +176 -0
- package/dist/plugin-sdk/audio-preflight-C-xXBoE2.js +51 -0
- package/dist/plugin-sdk/audio-transcription-runner-CTIHpebA.js +2173 -0
- package/dist/plugin-sdk/audit-membership-runtime-BFatB2LJ.js +58 -0
- package/dist/plugin-sdk/channel-activity-DO0FEzyj.js +95 -0
- package/dist/plugin-sdk/channel-web-Da-__nUF.js +2238 -0
- package/dist/plugin-sdk/commands-registry-6no2NNrY.js +1118 -0
- package/dist/plugin-sdk/compact.runtime-CCoclu5e.js +35 -0
- package/dist/plugin-sdk/compat.js +35 -35
- package/dist/plugin-sdk/config-B9ODwgpz.js +37426 -0
- package/dist/plugin-sdk/deliver-B1fFpKjV.js +1757 -0
- package/dist/plugin-sdk/deliver-runtime-DB-VRMe1.js +15 -0
- package/dist/plugin-sdk/deps-send-discord.runtime-DklqycYG.js +15 -0
- package/dist/plugin-sdk/deps-send-imessage.runtime-Chs8zeon.js +14 -0
- package/dist/plugin-sdk/deps-send-signal.runtime-clW9aSJP.js +13 -0
- package/dist/plugin-sdk/deps-send-slack.runtime-BUx0LYY1.js +13 -0
- package/dist/plugin-sdk/deps-send-telegram.runtime-LECSHgMG.js +16 -0
- package/dist/plugin-sdk/deps-send-whatsapp.runtime-D2d65fw0.js +40 -0
- package/dist/plugin-sdk/diagnostic-CxIvS-C2.js +315 -0
- package/dist/plugin-sdk/dispatch-BqlR4dPx.js +105863 -0
- package/dist/plugin-sdk/env-b9k1PHMI.js +34 -0
- package/dist/plugin-sdk/fetch-PoxzAANT.js +326 -0
- package/dist/plugin-sdk/fetch-guard-4UVSZ0uS.js +164 -0
- package/dist/plugin-sdk/image-Ch6M4tnJ.js +2420 -0
- package/dist/plugin-sdk/image-runtime-CSh2o5wY.js +8 -0
- package/dist/plugin-sdk/ir-CugsqGH8.js +1312 -0
- package/dist/plugin-sdk/local-roots-adnEg9zb.js +217 -0
- package/dist/plugin-sdk/logger-D6zRubj0.js +1164 -0
- package/dist/plugin-sdk/login-CYvkQ0At.js +54 -0
- package/dist/plugin-sdk/login-qr-ll4NtaT5.js +316 -0
- package/dist/plugin-sdk/manager-CHy8IclH.js +3959 -0
- package/dist/plugin-sdk/manager-runtime-C70EkEr7.js +11 -0
- package/dist/plugin-sdk/outbound-Wzs2iN7X.js +216 -0
- package/dist/plugin-sdk/outbound-attachment-khXJwucX.js +17 -0
- package/dist/plugin-sdk/paths-BtVqCdw4.js +3063 -0
- package/dist/{pi-model-discovery-ItS07aJB.js → plugin-sdk/pi-model-discovery-Dh4ziodY.js} +2 -2
- package/dist/plugin-sdk/pi-model-discovery-runtime-b83Xe-HT.js +8 -0
- package/dist/plugin-sdk/pi-tools.before-tool-call.runtime-C1z5CDBF.js +349 -0
- package/dist/{proxy-fetch-c1ZUFFcO.js → plugin-sdk/proxy-fetch-CJEmoBxi.js} +1 -1
- package/dist/plugin-sdk/pw-ai-Dj3Cvlzl.js +1990 -0
- package/dist/plugin-sdk/qmd-manager-egHUAseQ.js +1581 -0
- package/dist/plugin-sdk/resolve-outbound-target-BiICvIKs.js +38 -0
- package/dist/plugin-sdk/runtime-whatsapp-login.runtime-DNApufzW.js +9 -0
- package/dist/plugin-sdk/runtime-whatsapp-outbound.runtime-CBmtfIQ8.js +13 -0
- package/dist/plugin-sdk/send-CScblaI4.js +532 -0
- package/dist/plugin-sdk/send-CeHhnld6.js +407 -0
- package/dist/plugin-sdk/send-DP_c8JfR.js +3277 -0
- package/dist/plugin-sdk/send-Dc5fI6e8.js +495 -0
- package/dist/plugin-sdk/send-l-77_s1_.js +2507 -0
- package/dist/plugin-sdk/session-CkOKZaqa.js +166 -0
- package/dist/plugin-sdk/signal.js +2 -2
- package/dist/plugin-sdk/skill-commands-BohYCgkq.js +336 -0
- package/dist/plugin-sdk/slash-commands.runtime-DpLfVTM6.js +8 -0
- package/dist/plugin-sdk/slash-dispatch.runtime-CASMHwpm.js +35 -0
- package/dist/plugin-sdk/slash-skill-commands.runtime-D7rrJEci.js +9 -0
- package/dist/plugin-sdk/sqlite-CJE3X7Mv.js +1005 -0
- package/dist/plugin-sdk/subagent-registry-runtime-B1oo5bih.js +35 -0
- package/dist/plugin-sdk/tables-D5VgpTmm.js +53 -0
- package/dist/plugin-sdk/target-errors-C6zZ_OpA.js +191 -0
- package/dist/{tokens-BWDIKewp.js → plugin-sdk/tokens-DUnJnpMS.js} +1 -1
- package/dist/plugin-sdk/web-TfUM1nSi.js +39 -0
- package/dist/plugin-sdk/whatsapp-actions-DuWJ0j1r.js +71 -0
- package/dist/proxy-fetch-BOh1PLOW.js +54 -0
- package/dist/{pw-ai-Ok6KGelf.js → pw-ai-DwH5GpEO.js} +9 -9
- package/dist/{qmd-manager-DhfEz4Ar.js → qmd-manager-DEscZz5_.js} +6 -6
- package/dist/{query-expansion-GqNV2iIE.js → query-expansion-BErUY8P2.js} +4 -4
- package/dist/runtime-whatsapp-login.runtime-BI3U306v.js +13 -0
- package/dist/runtime-whatsapp-outbound.runtime-Bsc2uD09.js +17 -0
- package/dist/{send-DwAoiT2p.js → send-BDnOgWIp.js} +25 -25
- package/dist/{send-CS0ocZHl.js → send-C-Q_WPMf.js} +3 -3
- package/dist/{send-6R8b9zsj.js → send-DUibfNQD.js} +5 -5
- package/dist/{send-DPflcjM5.js → send-DtBvCnPQ.js} +6 -6
- package/dist/{send-CEg4P96c.js → send-ORtn50qg.js} +5 -5
- package/dist/{session-BoIID5UR.js → session-B7imi6T5.js} +7 -7
- package/dist/{skill-commands-DhdiziMs.js → skill-commands-B9brPuiL.js} +9 -9
- package/dist/slash-commands.runtime-Cf6ygfBp.js +12 -0
- package/dist/slash-dispatch.runtime-CsmvhO5K.js +39 -0
- package/dist/slash-skill-commands.runtime-CX7stIEP.js +13 -0
- package/dist/subagent-registry-runtime-B_S1nf7y.js +39 -0
- package/dist/{subsystem-C8z6w6xC.js → subsystem-DfXy5gUB.js} +14 -14
- package/dist/{tables-DQusRhkD.js → tables-CjQqTOdD.js} +1 -1
- package/dist/{target-errors-CfavnC9U.js → target-errors-BZE1mc-W.js} +1 -1
- package/dist/tokens-6ul2IrzG.js +50 -0
- package/dist/{web-CrcrTQ2c.js → web-Cd8yK1Zq.js} +39 -39
- package/dist/{whatsapp-actions-B0u0ZAme.js → whatsapp-actions-CYEzUMBI.js} +15 -15
- package/dist/{workspace-CWDYHR27.js → workspace-DGIcKCCW.js} +20 -20
- package/extensions/cclawd-mfa-auth/README.md +118 -0
- package/extensions/cclawd-mfa-auth/index.ts +382 -0
- package/extensions/cclawd-mfa-auth/openclaw.plugin.json +34 -0
- package/extensions/cclawd-mfa-auth/package.json +27 -0
- package/extensions/cclawd-mfa-auth/src/auth-manager.ts +536 -0
- package/extensions/cclawd-mfa-auth/src/config.ts +82 -0
- package/extensions/cclawd-mfa-auth/src/dabby-client.ts +200 -0
- package/extensions/cclawd-mfa-auth/src/notification-service.ts +417 -0
- package/extensions/cclawd-mfa-auth/src/polling-manager.ts +232 -0
- package/extensions/cclawd-mfa-auth/src/providers/base.ts +25 -0
- package/extensions/cclawd-mfa-auth/src/providers/qr-code.ts +98 -0
- package/extensions/cclawd-mfa-auth/src/sensitive-detector.ts +159 -0
- package/extensions/cclawd-mfa-auth/src/session-resolver.ts +159 -0
- package/extensions/cclawd-mfa-auth/src/types.ts +156 -0
- package/extensions/mfa-auth/index.ts +675 -1028
- package/extensions/mfa-auth/openclaw.plugin.json +1 -1
- package/extensions/mfa-auth/src/notification-service.ts +2 -8
- package/package.json +453 -453
- package/dist/compact.runtime-BEn3giMt.js +0 -39
- package/dist/deliver-runtime-DkQ3XzGv.js +0 -19
- package/dist/deps-send-discord.runtime-BLpqSj6s.js +0 -19
- package/dist/deps-send-imessage.runtime-BFzyYqvR.js +0 -18
- package/dist/deps-send-signal.runtime-DT0TYCy1.js +0 -17
- package/dist/deps-send-slack.runtime-BhaGFfMX.js +0 -17
- package/dist/deps-send-telegram.runtime-B6Cic9NX.js +0 -20
- package/dist/deps-send-whatsapp.runtime-WtEhIq2S.js +0 -43
- package/dist/image-runtime-B1LFYfQ2.js +0 -12
- package/dist/manager-runtime-Da7ME9vS.js +0 -15
- package/dist/pi-model-discovery-runtime-DjM7Z1fx.js +0 -12
- package/dist/runtime-whatsapp-login.runtime-D4BRhQkK.js +0 -13
- package/dist/runtime-whatsapp-outbound.runtime-DJPpS6g-.js +0 -17
- package/dist/slash-commands.runtime-Cu1lTjV9.js +0 -12
- package/dist/slash-dispatch.runtime-DRVJEF4l.js +0 -39
- package/dist/slash-skill-commands.runtime-C373PJjv.js +0 -13
- package/dist/subagent-registry-runtime-D7hWBo1G.js +0 -39
|
@@ -0,0 +1,232 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Cclawd MFA Auth Plugin - Polling Manager
|
|
3
|
+
*
|
|
4
|
+
* 管理认证状态轮询任务
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import type { OpenClawPluginApi } from 'openclaw/plugin-sdk';
|
|
8
|
+
import { authManager } from './auth-manager.js';
|
|
9
|
+
import { config } from './config.js';
|
|
10
|
+
import { dabbyClient } from './dabby-client.js';
|
|
11
|
+
import type { PollingTask, AuthTriggerType } from './types.js';
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* 轮询管理器
|
|
15
|
+
*/
|
|
16
|
+
export class PollingManager {
|
|
17
|
+
private tasks = new Map<string, PollingTask>();
|
|
18
|
+
private config = config;
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* 启动轮询任务
|
|
22
|
+
*/
|
|
23
|
+
startPolling(
|
|
24
|
+
api: OpenClawPluginApi,
|
|
25
|
+
userId: string,
|
|
26
|
+
sessionId: string,
|
|
27
|
+
context: {
|
|
28
|
+
triggerType: AuthTriggerType;
|
|
29
|
+
isReauth: boolean;
|
|
30
|
+
channel?: string;
|
|
31
|
+
accountId?: string;
|
|
32
|
+
to?: string;
|
|
33
|
+
sessionKey?: string;
|
|
34
|
+
},
|
|
35
|
+
): string {
|
|
36
|
+
const taskId = `poll-${sessionId}-${Date.now()}`;
|
|
37
|
+
|
|
38
|
+
api.logger.info(
|
|
39
|
+
`[cclawd-mfa-auth] Starting polling for auth status: userId=${userId}, sessionId=${sessionId}`,
|
|
40
|
+
);
|
|
41
|
+
|
|
42
|
+
const interval = setInterval(async () => {
|
|
43
|
+
try {
|
|
44
|
+
await this.pollAuthStatus(api, userId, sessionId, context);
|
|
45
|
+
} catch (error) {
|
|
46
|
+
api.logger.error(
|
|
47
|
+
`[cclawd-mfa-auth] Polling error for session ${sessionId}: ${String(error)}`,
|
|
48
|
+
);
|
|
49
|
+
}
|
|
50
|
+
}, 2000);
|
|
51
|
+
|
|
52
|
+
const task: PollingTask = {
|
|
53
|
+
taskId,
|
|
54
|
+
sessionId,
|
|
55
|
+
userId,
|
|
56
|
+
triggerType: context.triggerType,
|
|
57
|
+
startTime: Date.now(),
|
|
58
|
+
interval,
|
|
59
|
+
context: {
|
|
60
|
+
channel: context.channel,
|
|
61
|
+
accountId: context.accountId,
|
|
62
|
+
to: context.to,
|
|
63
|
+
sessionKey: context.sessionKey,
|
|
64
|
+
isReauth: context.isReauth,
|
|
65
|
+
},
|
|
66
|
+
};
|
|
67
|
+
|
|
68
|
+
this.tasks.set(taskId, task);
|
|
69
|
+
|
|
70
|
+
// 设置超时自动清理
|
|
71
|
+
setTimeout(() => {
|
|
72
|
+
this.stopPolling(taskId);
|
|
73
|
+
}, config.timeout + 10000);
|
|
74
|
+
|
|
75
|
+
return taskId;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* 停止轮询任务
|
|
80
|
+
*/
|
|
81
|
+
stopPolling(taskId: string): void {
|
|
82
|
+
const task = this.tasks.get(taskId);
|
|
83
|
+
if (task) {
|
|
84
|
+
clearInterval(task.interval);
|
|
85
|
+
this.tasks.delete(taskId);
|
|
86
|
+
console.log(`[cclawd-mfa-auth] Stopped polling task: ${taskId}`);
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* 停止用户的所有轮询任务
|
|
92
|
+
*/
|
|
93
|
+
stopPollingForUser(userId: string): void {
|
|
94
|
+
for (const [taskId, task] of this.tasks.entries()) {
|
|
95
|
+
if (task.userId === userId) {
|
|
96
|
+
this.stopPolling(taskId);
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
/**
|
|
102
|
+
* 轮询认证状态
|
|
103
|
+
*/
|
|
104
|
+
private async pollAuthStatus(
|
|
105
|
+
api: OpenClawPluginApi,
|
|
106
|
+
userId: string,
|
|
107
|
+
sessionId: string,
|
|
108
|
+
context: {
|
|
109
|
+
triggerType: AuthTriggerType;
|
|
110
|
+
isReauth: boolean;
|
|
111
|
+
channel?: string;
|
|
112
|
+
accountId?: string;
|
|
113
|
+
to?: string;
|
|
114
|
+
sessionKey?: string;
|
|
115
|
+
},
|
|
116
|
+
): Promise<void> {
|
|
117
|
+
// 检查是否已验证
|
|
118
|
+
let isVerified = false;
|
|
119
|
+
if (context.triggerType === 'first_message') {
|
|
120
|
+
isVerified = authManager.isUserVerifiedForFirstMessage(userId);
|
|
121
|
+
} else {
|
|
122
|
+
isVerified = authManager.isUserVerifiedForSensitiveOps(userId);
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
if (!isVerified) {
|
|
126
|
+
// 检查认证状态
|
|
127
|
+
const session = authManager.getSession(sessionId);
|
|
128
|
+
if (session && session.certToken) {
|
|
129
|
+
try {
|
|
130
|
+
const authResult = await dabbyClient.getAuthResult(session.certToken);
|
|
131
|
+
|
|
132
|
+
if (authResult.status === 'verified') {
|
|
133
|
+
api.logger.info(`[cclawd-mfa-auth] Auth verification successful for session ${sessionId}`);
|
|
134
|
+
authManager.markUserVerified(
|
|
135
|
+
userId,
|
|
136
|
+
context.triggerType === 'first_message' ? 'first_message' : 'sensitive_operation',
|
|
137
|
+
context.isReauth,
|
|
138
|
+
);
|
|
139
|
+
isVerified = true;
|
|
140
|
+
} else if (authResult.status === 'failed' || authResult.status === 'expired') {
|
|
141
|
+
api.logger.warn(
|
|
142
|
+
`[cclawd-mfa-auth] Auth failed or expired for session ${sessionId}: ${authResult.error}`,
|
|
143
|
+
);
|
|
144
|
+
// 停止该会话的轮询
|
|
145
|
+
this.stopPollingForUser(userId);
|
|
146
|
+
return;
|
|
147
|
+
}
|
|
148
|
+
} catch (error) {
|
|
149
|
+
api.logger.error(
|
|
150
|
+
`[cclawd-mfa-auth] Failed to check auth status for session ${sessionId}: ${String(error)}`,
|
|
151
|
+
);
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
// 如果已验证,发送通知并停止轮询
|
|
157
|
+
if (isVerified) {
|
|
158
|
+
this.stopPollingForUser(userId);
|
|
159
|
+
|
|
160
|
+
const notificationInfo = authManager.checkAndConsumeNotification(userId);
|
|
161
|
+
if (notificationInfo) {
|
|
162
|
+
api.logger.info(`[cclawd-mfa-auth] Polling detected verification for user ${userId}`);
|
|
163
|
+
|
|
164
|
+
const messageText = notificationInfo.isReauth
|
|
165
|
+
? '✅ 重新认证成功,请继续对话。'
|
|
166
|
+
: notificationInfo.triggerType === 'first_message'
|
|
167
|
+
? '✅ 首次认证成功,请继续对话。'
|
|
168
|
+
: '✅ 二次认证成功,请重新发送之前的命令(或回复"确认")即可执行。';
|
|
169
|
+
|
|
170
|
+
// 使用 notification-service 发送消息
|
|
171
|
+
const { NotificationService } = await import('./notification-service.js');
|
|
172
|
+
const notificationService = NotificationService.getInstance();
|
|
173
|
+
|
|
174
|
+
const session = authManager.getSession(sessionId);
|
|
175
|
+
if (session) {
|
|
176
|
+
await notificationService.sendAuthNotification(session, messageText);
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
return;
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
// 检查会话是否还存在
|
|
183
|
+
const session = authManager.getSession(sessionId);
|
|
184
|
+
if (!session) {
|
|
185
|
+
this.stopPollingForUser(userId);
|
|
186
|
+
api.logger.info(
|
|
187
|
+
`[cclawd-mfa-auth] Polling stopped: session ${sessionId} not found and user not verified`,
|
|
188
|
+
);
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
/**
|
|
193
|
+
* 获取活跃的轮询任务数量
|
|
194
|
+
*/
|
|
195
|
+
getActiveTaskCount(): number {
|
|
196
|
+
return this.tasks.size;
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
/**
|
|
200
|
+
* 清理所有轮询任务
|
|
201
|
+
*/
|
|
202
|
+
cleanup(): void {
|
|
203
|
+
for (const [taskId] of this.tasks) {
|
|
204
|
+
this.stopPolling(taskId);
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
/**
|
|
210
|
+
* 轮询管理器工厂
|
|
211
|
+
*/
|
|
212
|
+
class PollingManagerFactory {
|
|
213
|
+
private static instance: PollingManager | null = null;
|
|
214
|
+
|
|
215
|
+
static getInstance(): PollingManager {
|
|
216
|
+
if (!this.instance) {
|
|
217
|
+
this.instance = new PollingManager();
|
|
218
|
+
console.log('[PollingManagerFactory] Created new PollingManager instance');
|
|
219
|
+
}
|
|
220
|
+
return this.instance;
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
static reset(): void {
|
|
224
|
+
if (this.instance) {
|
|
225
|
+
this.instance.cleanup();
|
|
226
|
+
}
|
|
227
|
+
this.instance = null;
|
|
228
|
+
console.log('[PollingManagerFactory] Reset PollingManager instance');
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
export const pollingManager = PollingManagerFactory.getInstance();
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Cclawd MFA Auth Plugin - Base Auth Provider
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import type { AuthMethodProvider, AuthSession, AuthResult } from '../types.js';
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* 认证提供者基类
|
|
9
|
+
*/
|
|
10
|
+
export abstract class BaseAuthProvider implements AuthMethodProvider {
|
|
11
|
+
abstract readonly methodType: AuthSession['authMethod'];
|
|
12
|
+
abstract readonly name: string;
|
|
13
|
+
abstract readonly description: string;
|
|
14
|
+
|
|
15
|
+
abstract initialize(session: AuthSession): Promise<void>;
|
|
16
|
+
abstract verify(sessionId: string, userInput?: string): Promise<AuthResult>;
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* 清理会话资源
|
|
20
|
+
*/
|
|
21
|
+
cleanup(sessionId: string): void {
|
|
22
|
+
// 子类可以重写此方法以清理资源
|
|
23
|
+
console.log(`[cclawd-mfa-auth] Cleanup called for session: ${sessionId}`);
|
|
24
|
+
}
|
|
25
|
+
}
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Cclawd MFA Auth Plugin - QR Code Auth Provider
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { authManager } from '../auth-manager.js';
|
|
6
|
+
import { dabbyClient } from '../dabby-client.js';
|
|
7
|
+
import type { AuthSession, AuthResult } from '../types.js';
|
|
8
|
+
import { BaseAuthProvider } from './base.js';
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* QR 码认证提供者
|
|
12
|
+
*/
|
|
13
|
+
export class QrCodeAuthProvider extends BaseAuthProvider {
|
|
14
|
+
readonly methodType = 'qr-code' as const;
|
|
15
|
+
readonly name = 'QR Code Authentication';
|
|
16
|
+
readonly description = 'Scan QR code to authenticate';
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* 初始化二维码认证
|
|
20
|
+
*/
|
|
21
|
+
async initialize(session: AuthSession): Promise<void> {
|
|
22
|
+
try {
|
|
23
|
+
const tokenInfo = await dabbyClient.getVerifyCode();
|
|
24
|
+
|
|
25
|
+
authManager.updateAuthStatus(session.sessionId, 'pending');
|
|
26
|
+
session.certToken = tokenInfo.certToken;
|
|
27
|
+
session.qrCodeUrl = tokenInfo.qrCodeUrl;
|
|
28
|
+
session.expireTimeMs = Date.now() + 5 * 60 * 1000; // 5 分钟有效期
|
|
29
|
+
session.authStatus = 'pending';
|
|
30
|
+
|
|
31
|
+
console.log(`[cclawd-mfa-auth] QR code initialized for session ${session.sessionId}`);
|
|
32
|
+
} catch (error) {
|
|
33
|
+
console.error(`[cclawd-mfa-auth] Failed to initialize QR code: ${error}`);
|
|
34
|
+
throw error;
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* 验证二维码扫描状态
|
|
40
|
+
*/
|
|
41
|
+
async verify(sessionId: string, userInput?: string): Promise<AuthResult> {
|
|
42
|
+
const session = authManager.getSession(sessionId);
|
|
43
|
+
if (!session) {
|
|
44
|
+
return { success: false, error: 'Session not found', status: 'failed' };
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
if (!session.certToken) {
|
|
48
|
+
return { success: false, error: 'QR code not initialized', status: 'failed' };
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
// 检查是否过期
|
|
52
|
+
if (session.expireTimeMs && Date.now() > session.expireTimeMs) {
|
|
53
|
+
authManager.updateAuthStatus(sessionId, 'expired');
|
|
54
|
+
return { success: false, error: 'QR code expired', status: 'expired' };
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
try {
|
|
58
|
+
const result = await dabbyClient.getAuthResult(session.certToken);
|
|
59
|
+
|
|
60
|
+
if (result.status === 'verified') {
|
|
61
|
+
authManager.updateAuthStatus(sessionId, 'verified');
|
|
62
|
+
return { success: true, status: 'verified' };
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
if (result.status === 'failed') {
|
|
66
|
+
authManager.updateAuthStatus(sessionId, 'failed');
|
|
67
|
+
return { success: false, error: result.error || 'Authentication failed', status: 'failed' };
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
return { success: false, status: result.status };
|
|
71
|
+
} catch (error) {
|
|
72
|
+
console.error(`[cclawd-mfa-auth] Failed to verify QR code: ${error}`);
|
|
73
|
+
return { success: false, error: String(error), status: 'failed' };
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* QR 码认证提供者工厂
|
|
80
|
+
*/
|
|
81
|
+
class QrCodeAuthProviderFactory {
|
|
82
|
+
private static instance: QrCodeAuthProvider | null = null;
|
|
83
|
+
|
|
84
|
+
static getInstance(): QrCodeAuthProvider {
|
|
85
|
+
if (!this.instance) {
|
|
86
|
+
this.instance = new QrCodeAuthProvider();
|
|
87
|
+
console.log('[QrCodeAuthProviderFactory] Created new QrCodeAuthProvider instance');
|
|
88
|
+
}
|
|
89
|
+
return this.instance;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
static reset(): void {
|
|
93
|
+
this.instance = null;
|
|
94
|
+
console.log('[QrCodeAuthProviderFactory] Reset QrCodeAuthProvider instance');
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
export const qrCodeAuthProvider = QrCodeAuthProviderFactory.getInstance();
|
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Cclawd MFA Auth Plugin - Sensitive Operation Detector
|
|
3
|
+
*
|
|
4
|
+
* 检测敏感操作
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { config } from './config.js';
|
|
8
|
+
import type { SensitiveCheckResult } from './types.js';
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* 敏感操作检测器
|
|
12
|
+
*/
|
|
13
|
+
export class SensitiveDetector {
|
|
14
|
+
private config = config;
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* 检查文本是否包含敏感内容
|
|
18
|
+
*/
|
|
19
|
+
check(text: string): SensitiveCheckResult {
|
|
20
|
+
const lowerText = text.toLowerCase();
|
|
21
|
+
|
|
22
|
+
if (this.config.debug) {
|
|
23
|
+
console.log(`[cclawd-mfa-auth] Checking sensitive keywords for: ${text}`);
|
|
24
|
+
console.log(
|
|
25
|
+
`[cclawd-mfa-auth] Sensitive keywords configured: ${JSON.stringify(this.config.sensitiveKeywords)}`,
|
|
26
|
+
);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
const matchedKeywords: string[] = [];
|
|
30
|
+
|
|
31
|
+
for (const keyword of this.config.sensitiveKeywords) {
|
|
32
|
+
if (lowerText.includes(keyword.toLowerCase())) {
|
|
33
|
+
matchedKeywords.push(keyword);
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
if (matchedKeywords.length > 0) {
|
|
38
|
+
if (this.config.debug) {
|
|
39
|
+
console.log(`[cclawd-mfa-auth] Sensitive keywords matched: ${matchedKeywords.join(', ')}`);
|
|
40
|
+
}
|
|
41
|
+
return {
|
|
42
|
+
isSensitive: true,
|
|
43
|
+
preview: text,
|
|
44
|
+
matchedKeywords,
|
|
45
|
+
};
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
if (this.config.debug) {
|
|
49
|
+
console.log(`[cclawd-mfa-auth] No sensitive keyword matched`);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
return {
|
|
53
|
+
isSensitive: false,
|
|
54
|
+
preview: '',
|
|
55
|
+
matchedKeywords: [],
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* 检查工具调用是否为敏感操作
|
|
61
|
+
*/
|
|
62
|
+
checkToolCall(toolName: string, params: Record<string, unknown>): SensitiveCheckResult {
|
|
63
|
+
// 定义敏感工具列表
|
|
64
|
+
const sensitiveTools = ['bash', 'exec', 'runCommand', 'command', 'process'];
|
|
65
|
+
|
|
66
|
+
if (!sensitiveTools.includes(toolName)) {
|
|
67
|
+
if (this.config.debug) {
|
|
68
|
+
console.log(`[cclawd-mfa-auth] Tool ${toolName} is not in sensitive list, allowing`);
|
|
69
|
+
}
|
|
70
|
+
return {
|
|
71
|
+
isSensitive: false,
|
|
72
|
+
preview: '',
|
|
73
|
+
matchedKeywords: [],
|
|
74
|
+
};
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
// 提取命令内容
|
|
78
|
+
const command =
|
|
79
|
+
typeof params?.command === 'string'
|
|
80
|
+
? params.command
|
|
81
|
+
: typeof params?.cmd === 'string'
|
|
82
|
+
? params.cmd
|
|
83
|
+
: typeof params?.input === 'string'
|
|
84
|
+
? params.input
|
|
85
|
+
: typeof params?.args === 'string'
|
|
86
|
+
? params.args
|
|
87
|
+
: '';
|
|
88
|
+
|
|
89
|
+
if (this.config.debug) {
|
|
90
|
+
console.log(`[cclawd-mfa-auth] Extracted command from ${toolName}: ${command}`);
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
if (!command) {
|
|
94
|
+
if (this.config.debug) {
|
|
95
|
+
console.log(`[cclawd-mfa-auth] No command found in params, allowing`);
|
|
96
|
+
}
|
|
97
|
+
return {
|
|
98
|
+
isSensitive: false,
|
|
99
|
+
preview: '',
|
|
100
|
+
matchedKeywords: [],
|
|
101
|
+
};
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
// 检查命令内容
|
|
105
|
+
return this.check(command);
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
/**
|
|
109
|
+
* 添加自定义敏感关键词
|
|
110
|
+
*/
|
|
111
|
+
addSensitiveKeyword(keyword: string): void {
|
|
112
|
+
const normalized = keyword.toLowerCase().trim();
|
|
113
|
+
if (normalized && !this.config.sensitiveKeywords.includes(normalized)) {
|
|
114
|
+
this.config.sensitiveKeywords.push(normalized);
|
|
115
|
+
console.log(`[cclawd-mfa-auth] Added sensitive keyword: ${normalized}`);
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
/**
|
|
120
|
+
* 移除敏感关键词
|
|
121
|
+
*/
|
|
122
|
+
removeSensitiveKeyword(keyword: string): void {
|
|
123
|
+
const normalized = keyword.toLowerCase().trim();
|
|
124
|
+
const index = this.config.sensitiveKeywords.indexOf(normalized);
|
|
125
|
+
if (index !== -1) {
|
|
126
|
+
this.config.sensitiveKeywords.splice(index, 1);
|
|
127
|
+
console.log(`[cclawd-mfa-auth] Removed sensitive keyword: ${normalized}`);
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
/**
|
|
132
|
+
* 获取所有敏感关键词
|
|
133
|
+
*/
|
|
134
|
+
getSensitiveKeywords(): string[] {
|
|
135
|
+
return [...this.config.sensitiveKeywords];
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
/**
|
|
140
|
+
* 敏感操作检测器工厂
|
|
141
|
+
*/
|
|
142
|
+
class SensitiveDetectorFactory {
|
|
143
|
+
private static instance: SensitiveDetector | null = null;
|
|
144
|
+
|
|
145
|
+
static getInstance(): SensitiveDetector {
|
|
146
|
+
if (!this.instance) {
|
|
147
|
+
this.instance = new SensitiveDetector();
|
|
148
|
+
console.log('[SensitiveDetectorFactory] Created new SensitiveDetector instance');
|
|
149
|
+
}
|
|
150
|
+
return this.instance;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
static reset(): void {
|
|
154
|
+
this.instance = null;
|
|
155
|
+
console.log('[SensitiveDetectorFactory] Reset SensitiveDetector instance');
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
export const sensitiveDetector = SensitiveDetectorFactory.getInstance();
|
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Cclawd MFA Auth Plugin - Session Resolver
|
|
3
|
+
*
|
|
4
|
+
* 统一的会话解析器,处理不同渠道的 sessionKey 解析
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import type { ResolvedSession } from './types.js';
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* 会话解析器
|
|
11
|
+
*/
|
|
12
|
+
export class SessionResolver {
|
|
13
|
+
/**
|
|
14
|
+
* 从 sessionKey 解析会话信息
|
|
15
|
+
*/
|
|
16
|
+
static resolveFromSessionKey(sessionKey: string): ResolvedSession {
|
|
17
|
+
const parts = sessionKey.split(':').filter(Boolean);
|
|
18
|
+
|
|
19
|
+
// 格式 1: agent:main:<userId>
|
|
20
|
+
if (parts.length === 3 && parts[0] === 'agent' && parts[1] === 'main') {
|
|
21
|
+
return {
|
|
22
|
+
userId: parts[2],
|
|
23
|
+
channel: 'webchat',
|
|
24
|
+
sessionKey,
|
|
25
|
+
};
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
// 格式 2: webchat:<userId>
|
|
29
|
+
if (parts.length === 2 && parts[0] === 'webchat') {
|
|
30
|
+
return {
|
|
31
|
+
userId: parts[1],
|
|
32
|
+
channel: 'webchat',
|
|
33
|
+
sessionKey,
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
// 格式 3: <channel>:<accountId>:<userId>
|
|
38
|
+
if (parts.length === 3) {
|
|
39
|
+
return {
|
|
40
|
+
userId: parts[2],
|
|
41
|
+
channel: parts[0],
|
|
42
|
+
accountId: parts[1],
|
|
43
|
+
sessionKey,
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
// 格式 4: <channel>:<accountId>:<peerKind>:<userId>
|
|
48
|
+
if (parts.length === 4) {
|
|
49
|
+
return {
|
|
50
|
+
userId: parts[3],
|
|
51
|
+
channel: parts[0],
|
|
52
|
+
accountId: parts[1] === 'direct' || parts[1] === 'group' || parts[1] === 'dm' ? undefined : parts[1],
|
|
53
|
+
sessionKey,
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
// 格式 5: 单独的 userId
|
|
58
|
+
if (parts.length === 1) {
|
|
59
|
+
return {
|
|
60
|
+
userId: parts[0],
|
|
61
|
+
sessionKey,
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
// 默认返回原始 sessionKey 作为 userId
|
|
66
|
+
return {
|
|
67
|
+
userId: sessionKey,
|
|
68
|
+
sessionKey,
|
|
69
|
+
};
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* 从事件上下文解析会话信息
|
|
74
|
+
*/
|
|
75
|
+
static resolveFromContext(params: {
|
|
76
|
+
sessionKey?: string;
|
|
77
|
+
channelId?: string;
|
|
78
|
+
conversationId?: string;
|
|
79
|
+
accountId?: string;
|
|
80
|
+
from?: string;
|
|
81
|
+
senderId?: string;
|
|
82
|
+
}): ResolvedSession {
|
|
83
|
+
// 优先使用 sessionKey
|
|
84
|
+
if (params.sessionKey) {
|
|
85
|
+
return this.resolveFromSessionKey(params.sessionKey);
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
// 使用 conversationId
|
|
89
|
+
if (params.conversationId) {
|
|
90
|
+
return {
|
|
91
|
+
userId: params.conversationId,
|
|
92
|
+
channel: params.channelId,
|
|
93
|
+
accountId: params.accountId,
|
|
94
|
+
sessionKey: params.conversationId,
|
|
95
|
+
};
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
// 使用 from 或 senderId
|
|
99
|
+
const userId = params.from || params.senderId || 'unknown';
|
|
100
|
+
const channel = params.channelId;
|
|
101
|
+
|
|
102
|
+
// 构建会话键
|
|
103
|
+
let sessionKey = userId;
|
|
104
|
+
if (channel && channel !== 'webchat' && channel !== 'web') {
|
|
105
|
+
const accountId = params.accountId || '';
|
|
106
|
+
sessionKey = `${channel}:${accountId}:${userId}`;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
return {
|
|
110
|
+
userId,
|
|
111
|
+
channel,
|
|
112
|
+
accountId: params.accountId,
|
|
113
|
+
sessionKey,
|
|
114
|
+
};
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
/**
|
|
118
|
+
* 构建 sessionKey
|
|
119
|
+
*/
|
|
120
|
+
static buildSessionKey(params: {
|
|
121
|
+
channel?: string;
|
|
122
|
+
accountId?: string;
|
|
123
|
+
userId: string;
|
|
124
|
+
}): string {
|
|
125
|
+
const { channel, accountId, userId } = params;
|
|
126
|
+
|
|
127
|
+
// Web/WebChat 直接使用 userId
|
|
128
|
+
if (channel === 'webchat' || channel === 'web') {
|
|
129
|
+
return userId;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
// 其他渠道使用完整格式
|
|
133
|
+
if (channel && accountId) {
|
|
134
|
+
return `${channel}:${accountId}:${userId}`;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
if (channel) {
|
|
138
|
+
return `${channel}::${userId}`;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
return userId;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
/**
|
|
145
|
+
* 判断是否为 Web/WebChat 渠道
|
|
146
|
+
*/
|
|
147
|
+
static isWebChannel(channel?: string): boolean {
|
|
148
|
+
return channel === 'webchat' || channel === 'web';
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
/**
|
|
152
|
+
* 获取 Web/WebChat 标准化的会话键
|
|
153
|
+
*/
|
|
154
|
+
static normalizeWebchatSessionKey(userId: string): string {
|
|
155
|
+
return userId;
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
export { SessionResolver };
|