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.
Files changed (168) hide show
  1. package/dist/{active-listener-DYmI7imH.js → active-listener-Dkhmfuwx.js} +2 -2
  2. package/dist/{api-key-rotation-DLU4jvSu.js → api-key-rotation-BOfI3cG3.js} +1 -1
  3. package/dist/{audio-preflight-C9TMbRb4.js → audio-preflight-CtkZ5SAs.js} +15 -15
  4. package/dist/{audio-transcription-runner-Q5zG_hYd.js → audio-transcription-runner-CbPqoiHX.js} +10 -10
  5. package/dist/{audit-membership-runtime-DhxSwFnF.js → audit-membership-runtime-hXUuer4x.js} +6 -6
  6. package/dist/build-info.json +3 -3
  7. package/dist/bundled/boot-md/handler.js +35 -35
  8. package/dist/bundled/bootstrap-extra-files/handler.js +5 -5
  9. package/dist/bundled/command-logger/handler.js +2 -2
  10. package/dist/bundled/session-memory/handler.js +35 -35
  11. package/dist/{channel-activity-Bx08UTAg.js → channel-activity-DWAER4wd.js} +2 -2
  12. package/dist/{commands-registry-llLVCTH9.js → commands-registry-BUyiA7nE.js} +2 -2
  13. package/dist/compact.runtime-DpcZpcTl.js +39 -0
  14. package/dist/{deliver-DJf2ZBpe.js → deliver-aHOaRbkt.js} +19 -19
  15. package/dist/deliver-runtime-D4bCsr6d.js +19 -0
  16. package/dist/deps-send-discord.runtime-BziKU-pE.js +19 -0
  17. package/dist/deps-send-imessage.runtime-CFRnDTqp.js +18 -0
  18. package/dist/deps-send-signal.runtime-BuOtABJm.js +17 -0
  19. package/dist/deps-send-slack.runtime-BOLqvMxW.js +17 -0
  20. package/dist/deps-send-telegram.runtime-DeEoFLv5.js +20 -0
  21. package/dist/deps-send-whatsapp.runtime-CG1uXYLY.js +43 -0
  22. package/dist/{diagnostic-BCCMF3O_.js → diagnostic-BdcXX9iJ.js} +2 -2
  23. package/dist/{env-aYXLHjfZ.js → env-lw2hsIUY.js} +1 -1
  24. package/dist/{fetch-bvgIiupu.js → fetch-C0iyt-Iz.js} +3 -3
  25. package/dist/{fetch-DCTUdr1U.js → fetch-D9NUULbj.js} +5 -5
  26. package/dist/{fetch-guard-CqpEmMQ2.js → fetch-guard-B5ZMnGaN.js} +2 -2
  27. package/dist/{frontmatter-DjZuS525.js → frontmatter-C_obXuTp.js} +3 -3
  28. package/dist/{github-copilot-token-CQmATy5E.js → github-copilot-token-8N63GdbE.js} +7 -7
  29. package/dist/{image-Q8E1-lZn.js → image-4x07m4Jl.js} +4 -4
  30. package/dist/image-runtime-smkMrIol.js +12 -0
  31. package/dist/{ir-CzM3SxId.js → ir-CsgNUpOU.js} +6 -6
  32. package/dist/llm-slug-generator.js +35 -35
  33. package/dist/{logger-ChbX1G7s.js → logger-CbUVl62f.js} +7 -7
  34. package/dist/{login-B0mtU11X.js → login-p_O59TVQ.js} +4 -4
  35. package/dist/{login-qr-DY_i60f5.js → login-qr-BCJpDsAy.js} +10 -10
  36. package/dist/{manager-FAQPC0uO.js → manager-CwYv8O3T.js} +12 -12
  37. package/dist/manager-runtime-D_jEoBr9.js +15 -0
  38. package/dist/{model-selection-wf3OY5DX.js → model-selection-Cv2Puf5z.js} +122 -122
  39. package/dist/{outbound-Bw0dOVS7.js → outbound-Chpiwybe.js} +6 -6
  40. package/dist/{outbound-attachment-1R6r9Pg_.js → outbound-attachment-BnAVJDLe.js} +2 -2
  41. package/dist/{paths-C0HLtPu0.js → paths-CehYKFsO.js} +7 -7
  42. package/dist/{paths-hfkBoC7i.js → paths-DkxwiA8g.js} +5 -5
  43. package/dist/{pi-embedded-BAHaY-Oh.js → pi-embedded-CJVNBk0y.js} +150 -150
  44. package/dist/pi-model-discovery-7IzK0Uc3.js +131 -0
  45. package/dist/pi-model-discovery-runtime-DABef3qy.js +12 -0
  46. package/dist/{pi-tools.before-tool-call.runtime-D_mthvtC.js → pi-tools.before-tool-call.runtime-BP2UvGJb.js} +10 -10
  47. package/dist/plugin-sdk/active-listener-CN-tMEvN.js +35 -0
  48. package/dist/plugin-sdk/api-key-rotation-CimGYMBc.js +176 -0
  49. package/dist/plugin-sdk/audio-preflight-C-xXBoE2.js +51 -0
  50. package/dist/plugin-sdk/audio-transcription-runner-CTIHpebA.js +2173 -0
  51. package/dist/plugin-sdk/audit-membership-runtime-BFatB2LJ.js +58 -0
  52. package/dist/plugin-sdk/channel-activity-DO0FEzyj.js +95 -0
  53. package/dist/plugin-sdk/channel-web-Da-__nUF.js +2238 -0
  54. package/dist/plugin-sdk/commands-registry-6no2NNrY.js +1118 -0
  55. package/dist/plugin-sdk/compact.runtime-CCoclu5e.js +35 -0
  56. package/dist/plugin-sdk/compat.js +35 -35
  57. package/dist/plugin-sdk/config-B9ODwgpz.js +37426 -0
  58. package/dist/plugin-sdk/deliver-B1fFpKjV.js +1757 -0
  59. package/dist/plugin-sdk/deliver-runtime-DB-VRMe1.js +15 -0
  60. package/dist/plugin-sdk/deps-send-discord.runtime-DklqycYG.js +15 -0
  61. package/dist/plugin-sdk/deps-send-imessage.runtime-Chs8zeon.js +14 -0
  62. package/dist/plugin-sdk/deps-send-signal.runtime-clW9aSJP.js +13 -0
  63. package/dist/plugin-sdk/deps-send-slack.runtime-BUx0LYY1.js +13 -0
  64. package/dist/plugin-sdk/deps-send-telegram.runtime-LECSHgMG.js +16 -0
  65. package/dist/plugin-sdk/deps-send-whatsapp.runtime-D2d65fw0.js +40 -0
  66. package/dist/plugin-sdk/diagnostic-CxIvS-C2.js +315 -0
  67. package/dist/plugin-sdk/dispatch-BqlR4dPx.js +105863 -0
  68. package/dist/plugin-sdk/env-b9k1PHMI.js +34 -0
  69. package/dist/plugin-sdk/fetch-PoxzAANT.js +326 -0
  70. package/dist/plugin-sdk/fetch-guard-4UVSZ0uS.js +164 -0
  71. package/dist/plugin-sdk/image-Ch6M4tnJ.js +2420 -0
  72. package/dist/plugin-sdk/image-runtime-CSh2o5wY.js +8 -0
  73. package/dist/plugin-sdk/ir-CugsqGH8.js +1312 -0
  74. package/dist/plugin-sdk/local-roots-adnEg9zb.js +217 -0
  75. package/dist/plugin-sdk/logger-D6zRubj0.js +1164 -0
  76. package/dist/plugin-sdk/login-CYvkQ0At.js +54 -0
  77. package/dist/plugin-sdk/login-qr-ll4NtaT5.js +316 -0
  78. package/dist/plugin-sdk/manager-CHy8IclH.js +3959 -0
  79. package/dist/plugin-sdk/manager-runtime-C70EkEr7.js +11 -0
  80. package/dist/plugin-sdk/outbound-Wzs2iN7X.js +216 -0
  81. package/dist/plugin-sdk/outbound-attachment-khXJwucX.js +17 -0
  82. package/dist/plugin-sdk/paths-BtVqCdw4.js +3063 -0
  83. package/dist/{pi-model-discovery-ItS07aJB.js → plugin-sdk/pi-model-discovery-Dh4ziodY.js} +2 -2
  84. package/dist/plugin-sdk/pi-model-discovery-runtime-b83Xe-HT.js +8 -0
  85. package/dist/plugin-sdk/pi-tools.before-tool-call.runtime-C1z5CDBF.js +349 -0
  86. package/dist/{proxy-fetch-c1ZUFFcO.js → plugin-sdk/proxy-fetch-CJEmoBxi.js} +1 -1
  87. package/dist/plugin-sdk/pw-ai-Dj3Cvlzl.js +1990 -0
  88. package/dist/plugin-sdk/qmd-manager-egHUAseQ.js +1581 -0
  89. package/dist/plugin-sdk/resolve-outbound-target-BiICvIKs.js +38 -0
  90. package/dist/plugin-sdk/runtime-whatsapp-login.runtime-DNApufzW.js +9 -0
  91. package/dist/plugin-sdk/runtime-whatsapp-outbound.runtime-CBmtfIQ8.js +13 -0
  92. package/dist/plugin-sdk/send-CScblaI4.js +532 -0
  93. package/dist/plugin-sdk/send-CeHhnld6.js +407 -0
  94. package/dist/plugin-sdk/send-DP_c8JfR.js +3277 -0
  95. package/dist/plugin-sdk/send-Dc5fI6e8.js +495 -0
  96. package/dist/plugin-sdk/send-l-77_s1_.js +2507 -0
  97. package/dist/plugin-sdk/session-CkOKZaqa.js +166 -0
  98. package/dist/plugin-sdk/signal.js +2 -2
  99. package/dist/plugin-sdk/skill-commands-BohYCgkq.js +336 -0
  100. package/dist/plugin-sdk/slash-commands.runtime-DpLfVTM6.js +8 -0
  101. package/dist/plugin-sdk/slash-dispatch.runtime-CASMHwpm.js +35 -0
  102. package/dist/plugin-sdk/slash-skill-commands.runtime-D7rrJEci.js +9 -0
  103. package/dist/plugin-sdk/sqlite-CJE3X7Mv.js +1005 -0
  104. package/dist/plugin-sdk/subagent-registry-runtime-B1oo5bih.js +35 -0
  105. package/dist/plugin-sdk/tables-D5VgpTmm.js +53 -0
  106. package/dist/plugin-sdk/target-errors-C6zZ_OpA.js +191 -0
  107. package/dist/{tokens-BWDIKewp.js → plugin-sdk/tokens-DUnJnpMS.js} +1 -1
  108. package/dist/plugin-sdk/web-TfUM1nSi.js +39 -0
  109. package/dist/plugin-sdk/whatsapp-actions-DuWJ0j1r.js +71 -0
  110. package/dist/proxy-fetch-BOh1PLOW.js +54 -0
  111. package/dist/{pw-ai-Ok6KGelf.js → pw-ai-DwH5GpEO.js} +9 -9
  112. package/dist/{qmd-manager-DhfEz4Ar.js → qmd-manager-DEscZz5_.js} +6 -6
  113. package/dist/{query-expansion-GqNV2iIE.js → query-expansion-BErUY8P2.js} +4 -4
  114. package/dist/runtime-whatsapp-login.runtime-BI3U306v.js +13 -0
  115. package/dist/runtime-whatsapp-outbound.runtime-Bsc2uD09.js +17 -0
  116. package/dist/{send-DwAoiT2p.js → send-BDnOgWIp.js} +25 -25
  117. package/dist/{send-CS0ocZHl.js → send-C-Q_WPMf.js} +3 -3
  118. package/dist/{send-6R8b9zsj.js → send-DUibfNQD.js} +5 -5
  119. package/dist/{send-DPflcjM5.js → send-DtBvCnPQ.js} +6 -6
  120. package/dist/{send-CEg4P96c.js → send-ORtn50qg.js} +5 -5
  121. package/dist/{session-BoIID5UR.js → session-B7imi6T5.js} +7 -7
  122. package/dist/{skill-commands-DhdiziMs.js → skill-commands-B9brPuiL.js} +9 -9
  123. package/dist/slash-commands.runtime-Cf6ygfBp.js +12 -0
  124. package/dist/slash-dispatch.runtime-CsmvhO5K.js +39 -0
  125. package/dist/slash-skill-commands.runtime-CX7stIEP.js +13 -0
  126. package/dist/subagent-registry-runtime-B_S1nf7y.js +39 -0
  127. package/dist/{subsystem-C8z6w6xC.js → subsystem-DfXy5gUB.js} +14 -14
  128. package/dist/{tables-DQusRhkD.js → tables-CjQqTOdD.js} +1 -1
  129. package/dist/{target-errors-CfavnC9U.js → target-errors-BZE1mc-W.js} +1 -1
  130. package/dist/tokens-6ul2IrzG.js +50 -0
  131. package/dist/{web-CrcrTQ2c.js → web-Cd8yK1Zq.js} +39 -39
  132. package/dist/{whatsapp-actions-B0u0ZAme.js → whatsapp-actions-CYEzUMBI.js} +15 -15
  133. package/dist/{workspace-CWDYHR27.js → workspace-DGIcKCCW.js} +20 -20
  134. package/extensions/cclawd-mfa-auth/README.md +118 -0
  135. package/extensions/cclawd-mfa-auth/index.ts +382 -0
  136. package/extensions/cclawd-mfa-auth/openclaw.plugin.json +34 -0
  137. package/extensions/cclawd-mfa-auth/package.json +27 -0
  138. package/extensions/cclawd-mfa-auth/src/auth-manager.ts +536 -0
  139. package/extensions/cclawd-mfa-auth/src/config.ts +82 -0
  140. package/extensions/cclawd-mfa-auth/src/dabby-client.ts +200 -0
  141. package/extensions/cclawd-mfa-auth/src/notification-service.ts +417 -0
  142. package/extensions/cclawd-mfa-auth/src/polling-manager.ts +232 -0
  143. package/extensions/cclawd-mfa-auth/src/providers/base.ts +25 -0
  144. package/extensions/cclawd-mfa-auth/src/providers/qr-code.ts +98 -0
  145. package/extensions/cclawd-mfa-auth/src/sensitive-detector.ts +159 -0
  146. package/extensions/cclawd-mfa-auth/src/session-resolver.ts +159 -0
  147. package/extensions/cclawd-mfa-auth/src/types.ts +156 -0
  148. package/extensions/mfa-auth/index.ts +675 -1028
  149. package/extensions/mfa-auth/openclaw.plugin.json +1 -1
  150. package/extensions/mfa-auth/src/notification-service.ts +2 -8
  151. package/package.json +453 -453
  152. package/dist/compact.runtime-BEn3giMt.js +0 -39
  153. package/dist/deliver-runtime-DkQ3XzGv.js +0 -19
  154. package/dist/deps-send-discord.runtime-BLpqSj6s.js +0 -19
  155. package/dist/deps-send-imessage.runtime-BFzyYqvR.js +0 -18
  156. package/dist/deps-send-signal.runtime-DT0TYCy1.js +0 -17
  157. package/dist/deps-send-slack.runtime-BhaGFfMX.js +0 -17
  158. package/dist/deps-send-telegram.runtime-B6Cic9NX.js +0 -20
  159. package/dist/deps-send-whatsapp.runtime-WtEhIq2S.js +0 -43
  160. package/dist/image-runtime-B1LFYfQ2.js +0 -12
  161. package/dist/manager-runtime-Da7ME9vS.js +0 -15
  162. package/dist/pi-model-discovery-runtime-DjM7Z1fx.js +0 -12
  163. package/dist/runtime-whatsapp-login.runtime-D4BRhQkK.js +0 -13
  164. package/dist/runtime-whatsapp-outbound.runtime-DJPpS6g-.js +0 -17
  165. package/dist/slash-commands.runtime-Cu1lTjV9.js +0 -12
  166. package/dist/slash-dispatch.runtime-DRVJEF4l.js +0 -39
  167. package/dist/slash-skill-commands.runtime-C373PJjv.js +0 -13
  168. package/dist/subagent-registry-runtime-D7hWBo1G.js +0 -39
@@ -0,0 +1,200 @@
1
+ /**
2
+ * Cclawd MFA Auth Plugin - Dabby API Client
3
+ */
4
+
5
+ import { dabbyConfig } from './config.js';
6
+ import type {
7
+ DabbyConfig,
8
+ DabbyVerifyCodeResponse,
9
+ DabbyCheckAuthStatusResponse,
10
+ } from './types.js';
11
+
12
+ /**
13
+ * 获取 fetch 函数
14
+ */
15
+ const resolveFetch = (): typeof fetch => {
16
+ const resolved = globalThis.fetch;
17
+ if (!resolved) {
18
+ throw new Error('fetch is not available in this environment');
19
+ }
20
+ return resolved;
21
+ };
22
+
23
+ /**
24
+ * Dabby API 客户端
25
+ */
26
+ export class DabbyClient {
27
+ constructor(private config: DabbyConfig = dabbyConfig) {}
28
+
29
+ /**
30
+ * 获取验证码(二维码)
31
+ */
32
+ async getVerifyCode(): Promise<DabbyVerifyCodeResponse['data']> {
33
+ if (!this.config.apiKey) {
34
+ throw new Error('MFA_AUTH_API_KEY is not configured');
35
+ }
36
+
37
+ const fetch = resolveFetch();
38
+ const url = `${this.config.apiBaseUrl}/api/v1/getVerifyCode`;
39
+
40
+ const requestBody = {
41
+ apiKey: this.config.apiKey,
42
+ authType: 'ScanAuth',
43
+ mode: '66',
44
+ };
45
+
46
+ console.log(`[cclawd-mfa-auth] Fetching verify code from: ${url}`);
47
+ console.log(`[cclawd-mfa-auth] Request body:`, JSON.stringify(requestBody, null, 2));
48
+
49
+ // 重试机制
50
+ let lastError: Error | undefined;
51
+ for (let i = 0; i < 3; i++) {
52
+ try {
53
+ const response = await fetch(url, {
54
+ method: 'POST',
55
+ headers: {
56
+ 'Content-Type': 'application/json',
57
+ 'User-Agent': 'Cclawd/1.0 (mfa-auth)',
58
+ },
59
+ body: JSON.stringify(requestBody),
60
+ });
61
+
62
+ if (!response.ok) {
63
+ throw new Error(`HTTP ${response.status}: ${response.statusText}`);
64
+ }
65
+
66
+ const data = (await response.json()) as DabbyVerifyCodeResponse;
67
+
68
+ console.log(`[cclawd-mfa-auth] API response:`, JSON.stringify(data, null, 2));
69
+
70
+ if (data.retCode !== 0) {
71
+ throw new Error(`Dabby API error: ${data.message || data.retMessage} (code: ${data.retCode})`);
72
+ }
73
+
74
+ if (!data.data) {
75
+ throw new Error('Dabby API returned empty data');
76
+ }
77
+
78
+ console.log(
79
+ `[cclawd-mfa-auth] Verify code generated, certToken: ${data.data.certToken}, qrCodeUrl: ${data.data.qrCodeUrl}`,
80
+ );
81
+
82
+ // 追加 fromSource 参数
83
+ const qrCodeUrlWithSource = data.data.qrCodeUrl.includes('?')
84
+ ? `${data.data.qrCodeUrl}&fromSource=Cclawd`
85
+ : `${data.data.qrCodeUrl}?fromSource=Cclawd`;
86
+
87
+ console.log(`[cclawd-mfa-auth] QR code URL with fromSource: ${qrCodeUrlWithSource}`);
88
+
89
+ return {
90
+ ...data.data,
91
+ qrCodeUrl: qrCodeUrlWithSource,
92
+ };
93
+ } catch (error: unknown) {
94
+ const err = error as Error;
95
+ console.error(`[cclawd-mfa-auth] Attempt ${i + 1} failed to get verify code: ${err.message}`);
96
+ lastError = err;
97
+ await new Promise((resolve) => setTimeout(resolve, 1000));
98
+ }
99
+ }
100
+
101
+ console.error(`[cclawd-mfa-auth] Failed to get verify code after 3 attempts`);
102
+ throw lastError;
103
+ }
104
+
105
+ /**
106
+ * 获取二维码(别名,向后兼容)
107
+ */
108
+ async getQrCode(): Promise<DabbyVerifyCodeResponse['data']> {
109
+ return this.getVerifyCode();
110
+ }
111
+
112
+ /**
113
+ * 检查认证结果
114
+ */
115
+ async getAuthResult(certToken: string): Promise<{
116
+ status: 'pending' | 'verified' | 'failed' | 'expired';
117
+ error?: string;
118
+ authObject?: { idNum: string; fullName: string };
119
+ }> {
120
+ if (!this.config.apiKey) {
121
+ return { status: 'failed', error: 'MFA_AUTH_API_KEY is not configured' };
122
+ }
123
+
124
+ try {
125
+ const fetch = resolveFetch();
126
+ const url = `${this.config.apiBaseUrl}/api/v1/checkAuthStatus`;
127
+
128
+ const requestBody = {
129
+ apiKey: this.config.apiKey,
130
+ certToken,
131
+ };
132
+
133
+ console.log(`[cclawd-mfa-auth] Checking auth status for certToken: ${certToken}`);
134
+
135
+ const response = await fetch(url, {
136
+ method: 'POST',
137
+ headers: {
138
+ 'Content-Type': 'application/json',
139
+ 'User-Agent': 'Cclawd/1.0 (mfa-auth)',
140
+ },
141
+ body: JSON.stringify(requestBody),
142
+ });
143
+
144
+ if (!response.ok) {
145
+ throw new Error(`HTTP ${response.status}: ${response.statusText}`);
146
+ }
147
+
148
+ const data = (await response.json()) as DabbyCheckAuthStatusResponse;
149
+
150
+ console.log(`[cclawd-mfa-auth] CheckAuthStatus response:`, JSON.stringify(data, null, 2));
151
+
152
+ if (data.retCode !== 0) {
153
+ // 4401: 认证未完成
154
+ if (data.retCode === 4401) {
155
+ return { status: 'pending' };
156
+ }
157
+ throw new Error(`Dabby API error: ${data.message} (code: ${data.retCode})`);
158
+ }
159
+
160
+ if (data.data.authSuccess) {
161
+ return { status: 'verified', authObject: data.data.authResult };
162
+ }
163
+
164
+ return { status: 'failed', error: data.data.message || '认证失败' };
165
+ } catch (error: unknown) {
166
+ const err = error as Error;
167
+ console.error(`[cclawd-mfa-auth] Failed to get auth result: ${err.message}`);
168
+ return { status: 'failed', error: err.message };
169
+ }
170
+ }
171
+
172
+ /**
173
+ * 检查二维码是否过期
174
+ */
175
+ async checkQrCodeExpired(certToken: string, expireTimeMs: number): Promise<boolean> {
176
+ return Date.now() > expireTimeMs;
177
+ }
178
+ }
179
+
180
+ /**
181
+ * Dabby 客户端工厂
182
+ */
183
+ class DabbyClientFactory {
184
+ private static instance: DabbyClient | null = null;
185
+
186
+ static getInstance(): DabbyClient {
187
+ if (!this.instance) {
188
+ this.instance = new DabbyClient();
189
+ console.log('[DabbyClientFactory] Created new DabbyClient instance');
190
+ }
191
+ return this.instance;
192
+ }
193
+
194
+ static reset(): void {
195
+ this.instance = null;
196
+ console.log('[DabbyClientFactory] Reset DabbyClient instance');
197
+ }
198
+ }
199
+
200
+ export const dabbyClient = DabbyClientFactory.getInstance();
@@ -0,0 +1,417 @@
1
+ /**
2
+ * Cclawd MFA Auth Plugin - Notification Service
3
+ *
4
+ * 统一的消息推送服务,支持多渠道消息发送
5
+ */
6
+
7
+ import * as Lark from '@larksuiteoapi/node-sdk';
8
+ import type { ClawdbotConfig } from 'openclaw/plugin-sdk';
9
+ import { config } from './config.js';
10
+ import type { AuthSession } from './types.js';
11
+
12
+ /**
13
+ * 通知服务
14
+ */
15
+ export class NotificationService {
16
+ private static instance: NotificationService;
17
+ private cfg?: ClawdbotConfig;
18
+
19
+ private constructor() {}
20
+
21
+ /**
22
+ * 获取单例实例
23
+ */
24
+ static getInstance(): NotificationService {
25
+ if (!NotificationService.instance) {
26
+ NotificationService.instance = new NotificationService();
27
+ }
28
+ return NotificationService.instance;
29
+ }
30
+
31
+ /**
32
+ * 设置配置
33
+ */
34
+ setConfig(cfg: ClawdbotConfig): void {
35
+ this.cfg = cfg;
36
+ }
37
+
38
+ /**
39
+ * 发送认证通知
40
+ */
41
+ async sendAuthNotification(session: AuthSession, message: string): Promise<void> {
42
+ const { channel, accountId, to } = session.originalContext;
43
+
44
+ if (!this.cfg) {
45
+ console.warn('[cclawd-mfa-auth] Config not set, skipping notification');
46
+ return;
47
+ }
48
+
49
+ // Web/WebChat 渠道
50
+ if (channel === 'webchat' || channel === 'web') {
51
+ console.log(`[cclawd-mfa-auth] Web/webchat channel: sending notification via WebSocket`);
52
+ await this.sendToWebChat(session, message);
53
+ return;
54
+ }
55
+
56
+ // 飞书渠道
57
+ if (channel === 'feishu') {
58
+ await this.sendToFeishu(session, message);
59
+ return;
60
+ }
61
+
62
+ console.warn(`[cclawd-mfa-auth] Unsupported channel: ${channel}`);
63
+ }
64
+
65
+ /**
66
+ * 发送消息到 WebChat
67
+ */
68
+ private async sendToWebChat(session: AuthSession, message: string): Promise<void> {
69
+ const { sessionKey: originalSessionKey } = session.originalContext;
70
+ // 前端使用 agent:main:{userId} 格式的sessionKey
71
+ const frontendSessionKey = originalSessionKey.startsWith('agent:')
72
+ ? originalSessionKey
73
+ : `agent:main:${originalSessionKey}`;
74
+ const port = this.cfg?.gateway?.port || 18789;
75
+ const token = this.cfg?.gateway?.auth?.token;
76
+ const host = config.gatewayHost || '127.0.0.1';
77
+
78
+ console.log(`[cclawd-mfa-auth] [DEBUG] sendToWebChat START`);
79
+ console.log(`[cclawd-mfa-auth] [DEBUG] originalSessionKey=${originalSessionKey}`);
80
+ console.log(`[cclawd-mfa-auth] [DEBUG] frontendSessionKey=${frontendSessionKey}`);
81
+ console.log(`[cclawd-mfa-auth] [DEBUG] host=${host}, port=${port}`);
82
+ console.log(`[cclawd-mfa-auth] [DEBUG] token=${token ? '已配置' : '未配置'}`);
83
+ console.log(`[cclawd-mfa-auth] [DEBUG] userId=${session.userId}`);
84
+
85
+ // 动态导入 WebSocket
86
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
87
+ const wsModule = await import('ws');
88
+ const WebSocket = wsModule.default || (wsModule as any).WebSocket;
89
+
90
+ return new Promise((resolve, reject) => {
91
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
92
+ const ws: any = new WebSocket(`ws://${host}:${port}`);
93
+
94
+ const handshakeId = `mfa-handshake-${Date.now()}`;
95
+ const sessionsListId = `mfa-sessions-${Date.now()}`;
96
+ let currentInjectId = '';
97
+ let candidateSessionKeys: string[] = [];
98
+ let injectIndex = 0;
99
+
100
+ /**
101
+ * 发送消息注入请求
102
+ */
103
+ const sendInject = (targetSessionKey: string) => {
104
+ currentInjectId = `mfa-req-${Date.now()}-${injectIndex}`;
105
+ const payload = {
106
+ type: 'req',
107
+ id: currentInjectId,
108
+ method: 'chat.inject',
109
+ params: {
110
+ sessionKey: targetSessionKey,
111
+ message,
112
+ label: 'MFA Auth',
113
+ },
114
+ };
115
+ ws.send(JSON.stringify(payload));
116
+ };
117
+
118
+ ws.on('open', () => {
119
+ console.log(`[cclawd-mfa-auth] [DEBUG] WebSocket 连接已建立`);
120
+ const handshake = {
121
+ type: 'req',
122
+ id: handshakeId,
123
+ method: 'connect',
124
+ params: {
125
+ minProtocol: 1,
126
+ maxProtocol: 3,
127
+ client: {
128
+ id: 'gateway-client',
129
+ version: '1.0.0',
130
+ platform: 'node',
131
+ mode: 'backend',
132
+ },
133
+ auth: token ? { token } : undefined,
134
+ role: 'operator',
135
+ scopes: ['operator.admin'],
136
+ },
137
+ };
138
+ console.log(`[cclawd-mfa-auth] [DEBUG] 发送握手请求: id=${handshakeId}`);
139
+ ws.send(JSON.stringify(handshake));
140
+ });
141
+
142
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
143
+ ws.on('message', (data: any) => {
144
+ try {
145
+ const response = JSON.parse(data.toString());
146
+
147
+ // 握手响应
148
+ if (response.id === handshakeId) {
149
+ console.log(`[cclawd-mfa-auth] [DEBUG] 收到握手响应`);
150
+ if (!response.ok) {
151
+ ws.close();
152
+ reject(new Error(`Handshake failed: ${JSON.stringify(response.error)}`));
153
+ return;
154
+ }
155
+
156
+ // 获取会话列表
157
+ const listPayload = {
158
+ type: 'req',
159
+ id: sessionsListId,
160
+ method: 'sessions.list',
161
+ params: {
162
+ limit: 200,
163
+ includeGlobal: true,
164
+ includeUnknown: true,
165
+ },
166
+ };
167
+ console.log(`[cclawd-mfa-auth] [DEBUG] 发送会话列表请求: id=${sessionsListId}`);
168
+ ws.send(JSON.stringify(listPayload));
169
+ return;
170
+ }
171
+
172
+ // 会话列表响应
173
+ if (response.id === sessionsListId) {
174
+ console.log(`[cclawd-mfa-auth] [DEBUG] 收到会话列表响应`);
175
+ if (!response.ok) {
176
+ ws.close();
177
+ reject(new Error(`sessions.list failed: ${JSON.stringify(response.error)}`));
178
+ return;
179
+ }
180
+
181
+ // 响应格式: { ok: true, payload: { sessions: [...] } }
182
+ const payload = response.payload as Record<string, unknown> | undefined;
183
+ const sessionsRaw = Array.isArray(payload?.sessions) ? payload.sessions : [];
184
+
185
+ console.log(`[cclawd-mfa-auth] [DEBUG] 会话列表详情 (${sessionsRaw.length} 个):`);
186
+ for (const s of sessionsRaw) {
187
+ if (s && typeof s === 'object') {
188
+ const dc = (s as any).deliveryContext;
189
+ console.log(`[cclawd-mfa-auth] [DEBUG] - key=${(s as any).key}, channel=${(s as any).channel}, dc.channel=${dc?.channel}`);
190
+ }
191
+ }
192
+
193
+ // 解析候选会话
194
+ candidateSessionKeys = this.resolveWebchatSessionCandidates({
195
+ requestedSessionKey: frontendSessionKey,
196
+ userId: session.userId,
197
+ targetTo: session.originalContext.to,
198
+ sessionsListResult: { sessions: sessionsRaw },
199
+ });
200
+
201
+ console.log(
202
+ `[cclawd-mfa-auth] [DEBUG] 找到 ${candidateSessionKeys.length} 个候选会话: ${candidateSessionKeys.join(', ')}`,
203
+ );
204
+
205
+ if (candidateSessionKeys.length === 0) {
206
+ ws.close();
207
+ reject(new Error('No candidate webchat sessions found'));
208
+ return;
209
+ }
210
+
211
+ // 尝试注入到第一个候选会话
212
+ injectIndex = 0;
213
+ console.log(`[cclawd-mfa-auth] [DEBUG] 尝试注入到会话: ${candidateSessionKeys[injectIndex]}`);
214
+ sendInject(candidateSessionKeys[injectIndex]);
215
+ return;
216
+ }
217
+
218
+ // 注入响应
219
+ if (response.id === currentInjectId) {
220
+ console.log(
221
+ `[cclawd-mfa-auth] [DEBUG] 收到注入响应: ok=${response.ok}, error=${JSON.stringify(response.error)}`,
222
+ );
223
+
224
+ if (response.ok && !response.error) {
225
+ console.log(`[cclawd-mfa-auth] [DEBUG] 消息注入成功!`);
226
+ ws.close();
227
+ resolve();
228
+ return;
229
+ }
230
+
231
+ // 如果注入失败,尝试下一个候选会话
232
+ const errMsg = String(response?.error?.message ?? '').toLowerCase();
233
+ const shouldTryNext = errMsg.includes('session not found');
234
+
235
+ if (shouldTryNext && injectIndex + 1 < candidateSessionKeys.length) {
236
+ injectIndex += 1;
237
+ console.log(`[cclawd-mfa-auth] [DEBUG] 尝试下一个会话: ${candidateSessionKeys[injectIndex]}`);
238
+ sendInject(candidateSessionKeys[injectIndex]);
239
+ return;
240
+ }
241
+
242
+ ws.close();
243
+ reject(
244
+ new Error(
245
+ `Chat inject failed after trying [${candidateSessionKeys.join(', ')}]: ${JSON.stringify(response.error)}`,
246
+ ),
247
+ );
248
+ }
249
+ } catch (e) {
250
+ console.error(`[cclawd-mfa-auth] [DEBUG] 解析消息失败:`, e);
251
+ }
252
+ });
253
+
254
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
255
+ ws.on('error', (err: any) => {
256
+ console.log(`[cclawd-mfa-auth] [DEBUG] WebSocket 错误: ${err}`);
257
+ reject(err);
258
+ });
259
+
260
+ // 超时处理
261
+ setTimeout(() => {
262
+ console.log(`[cclawd-mfa-auth] [DEBUG] WebSocket 超时 (5秒)`);
263
+ try {
264
+ ws.terminate();
265
+ } catch (e) {}
266
+ reject(new Error('WebSocket timeout'));
267
+ }, 5000);
268
+ });
269
+ }
270
+
271
+ /**
272
+ * 解析 WebChat 候选会话
273
+ */
274
+ private resolveWebchatSessionCandidates(params: {
275
+ requestedSessionKey?: string;
276
+ userId: string;
277
+ targetTo?: string;
278
+ sessionsListResult: unknown;
279
+ }): string[] {
280
+ const normalizedTarget = String(params.targetTo || params.userId || '')
281
+ .trim()
282
+ .toLowerCase();
283
+
284
+ const resultObject =
285
+ params.sessionsListResult && typeof params.sessionsListResult === 'object'
286
+ ? (params.sessionsListResult as Record<string, unknown>)
287
+ : undefined;
288
+
289
+ const sessionsRaw = Array.isArray(resultObject?.sessions) ? resultObject.sessions : [];
290
+
291
+ // 过滤 webchat 会话
292
+ const webchatRows = sessionsRaw
293
+ .map((row) => (row && typeof row === 'object' ? (row as Record<string, unknown>) : undefined))
294
+ .filter((row): row is Record<string, unknown> => Boolean(row))
295
+ .filter((row) => {
296
+ const ch = String(row.channel ?? '')
297
+ .trim()
298
+ .toLowerCase();
299
+ const dc = row.deliveryContext as Record<string, unknown> | undefined;
300
+ const dcChannel = String(dc?.channel ?? '')
301
+ .trim()
302
+ .toLowerCase();
303
+ return ch === 'webchat' || ch === 'web' || dcChannel === 'webchat' || dcChannel === 'web';
304
+ });
305
+
306
+ // 精确匹配
307
+ const exactRows = webchatRows.filter((row) => {
308
+ const dc = row.deliveryContext as Record<string, unknown> | undefined;
309
+ const peer = String(dc?.to ?? row.lastTo ?? '')
310
+ .trim()
311
+ .toLowerCase();
312
+ return peer.length > 0 && peer === normalizedTarget;
313
+ });
314
+
315
+ // 回退匹配
316
+ const fallbackRows = webchatRows.filter((row) => !exactRows.includes(row));
317
+
318
+ // 模糊匹配
319
+ const fuzzyRows = webchatRows.filter((row) => {
320
+ const key = String(row.key ?? '').toLowerCase();
321
+ return (
322
+ key.includes(normalizedTarget) && !exactRows.includes(row) && !fallbackRows.includes(row)
323
+ );
324
+ });
325
+
326
+ // 按更新时间降序排序
327
+ const sortByUpdatedAtDesc = (a: Record<string, unknown>, b: Record<string, unknown>) => {
328
+ const aa = typeof a.updatedAt === 'number' ? a.updatedAt : 0;
329
+ const bb = typeof b.updatedAt === 'number' ? b.updatedAt : 0;
330
+ return bb - aa;
331
+ };
332
+
333
+ exactRows.sort(sortByUpdatedAtDesc);
334
+ fallbackRows.sort(sortByUpdatedAtDesc);
335
+ fuzzyRows.sort(sortByUpdatedAtDesc);
336
+
337
+ // 构建候选会话列表
338
+ const keys = [
339
+ params.requestedSessionKey,
340
+ ...exactRows.map((row) => String(row.key ?? '').trim()),
341
+ ...fallbackRows.map((row) => String(row.key ?? '').trim()),
342
+ ...fuzzyRows.map((row) => String(row.key ?? '').trim()),
343
+ // 标准模式
344
+ `agent:main:${normalizedTarget}`,
345
+ `webchat:${normalizedTarget}`,
346
+ normalizedTarget,
347
+ 'main',
348
+ ];
349
+
350
+ // 去重
351
+ const seen = new Set<string>();
352
+ const deduped: string[] = [];
353
+ for (const key of keys) {
354
+ const normalized = String(key ?? '').trim();
355
+ if (!normalized || seen.has(normalized)) {
356
+ continue;
357
+ }
358
+ seen.add(normalized);
359
+ deduped.push(normalized);
360
+ }
361
+
362
+ return deduped;
363
+ }
364
+
365
+ /**
366
+ * 发送消息到飞书
367
+ */
368
+ private async sendToFeishu(session: AuthSession, message: string): Promise<void> {
369
+ const { accountId, to } = session.originalContext;
370
+
371
+ if (!this.cfg) {
372
+ console.warn('[cclawd-mfa-auth] Config not set, cannot send Feishu message');
373
+ return;
374
+ }
375
+
376
+ if (!to) {
377
+ console.warn('[cclawd-mfa-auth] Feishu target "to" is missing, cannot send message');
378
+ return;
379
+ }
380
+
381
+ try {
382
+ // TODO: 实现 resolveFeishuSendTarget
383
+ console.log(`[cclawd-mfa-auth] Feishu message would be sent to ${to}`);
384
+ // 临时实现,后续需要根据实际需求补充
385
+ } catch (error) {
386
+ console.error(`[cclawd-mfa-auth] Failed to send Feishu message: ${error}`);
387
+ throw error;
388
+ }
389
+ }
390
+
391
+ /**
392
+ * 构建飞书消息负载
393
+ */
394
+ private buildFeishuPostMessagePayload(params: { messageText: string }): {
395
+ content: string;
396
+ msgType: string;
397
+ } {
398
+ const { messageText } = params;
399
+ return {
400
+ content: JSON.stringify({
401
+ zh_cn: {
402
+ content: [
403
+ [
404
+ {
405
+ tag: 'md',
406
+ text: messageText,
407
+ },
408
+ ],
409
+ ],
410
+ },
411
+ }),
412
+ msgType: 'post',
413
+ };
414
+ }
415
+ }
416
+
417
+ export { NotificationService };